From c83534f1711398c65c5231b02fc6aa5f61919f1f Mon Sep 17 00:00:00 2001 From: nictru Date: Fri, 13 Jun 2025 14:25:32 +0200 Subject: [PATCH 01/74] Skip fastqc template feature --- .nf-core.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.nf-core.yml b/.nf-core.yml index f700a2bb..c3bdb78b 100644 --- a/.nf-core.yml +++ b/.nf-core.yml @@ -12,5 +12,7 @@ template: version: 1.0.0dev force: true outdir: . - skip_features: [] + skip_features: [ + "fastqc" + ] is_nfcore: true From f3d8db43bfd59241355d9c0f37bcda2816e921a9 Mon Sep 17 00:00:00 2001 From: nictru Date: Fri, 13 Jun 2025 14:30:46 +0200 Subject: [PATCH 02/74] Fix .nf-core.yml --- .nf-core.yml | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/.nf-core.yml b/.nf-core.yml index df06a843..33ae6887 100644 --- a/.nf-core.yml +++ b/.nf-core.yml @@ -3,12 +3,12 @@ nf_core_version: 3.3.1 repository_type: pipeline template: author: Fabiola Curion - version: 1.0.0dev - force: true - outdir: . - skip_features: [ - "fastqc" - ] + description: Comprehensive pipeline for donor demultiplexing in single cell + force: false is_nfcore: true name: hadge org: nf-core + outdir: . + skip_features: + - fastqc + version: 1.0.0dev From 0408cbab38f0a9899bc1ca2979d9c28e26bdace6 Mon Sep 17 00:00:00 2001 From: nictru Date: Fri, 13 Jun 2025 14:38:59 +0200 Subject: [PATCH 03/74] Fix pipeline initialization --- assets/schema_input.json | 60 ++++++++++++++++--- conf/test.config | 6 +- nextflow.config | 1 + nextflow_schema.json | 9 ++- .../local/utils_nfcore_hadge_pipeline/main.nf | 22 ------- workflows/hadge.nf | 1 + 6 files changed, 64 insertions(+), 35 deletions(-) diff --git a/assets/schema_input.json b/assets/schema_input.json index fe5ea477..1b56e2e2 100644 --- a/assets/schema_input.json +++ b/assets/schema_input.json @@ -11,23 +11,67 @@ "type": "string", "pattern": "^\\S+$", "errorMessage": "Sample name must be provided and cannot contain spaces", - "meta": ["id"] + "meta": [ + "id" + ] }, - "fastq_1": { + "rna_matrix_raw": { + "type": "string", + "format": "directory-path", + "exists": true, + "pattern": "^\\S+$", + "errorMessage": "RNA matrix raw must be provided, cannot contain spaces and must have extension '.mtx'" + }, + "rna_matrix_filtered": { + "type": "string", + "format": "directory-path", + "exists": true, + "pattern": "^\\S+$", + "errorMessage": "RNA matrix filtered must be provided, cannot contain spaces and must have extension '.mtx'" + }, + "hto_matrix_raw": { + "type": "string", + "format": "directory-path", + "exists": true, + "pattern": "^\\S+$", + "errorMessage": "HTO matrix raw must be provided, cannot contain spaces and must have extension '.mtx'" + }, + "hto_matrix_filtered": { + "type": "string", + "format": "directory-path", + "exists": true, + "pattern": "^\\S+$", + "errorMessage": "HTO matrix filtered must be provided, cannot contain spaces and must have extension '.mtx'" + }, + "bam": { "type": "string", "format": "file-path", "exists": true, - "pattern": "^\\S+\\.f(ast)?q\\.gz$", - "errorMessage": "FastQ file for reads 1 must be provided, cannot contain spaces and must have extension '.fq.gz' or '.fastq.gz'" + "pattern": "^\\S+\\.bam$", + "errorMessage": "BAM file must be provided, cannot contain spaces and must have extension '.bam'" }, - "fastq_2": { + "bai": { "type": "string", "format": "file-path", "exists": true, - "pattern": "^\\S+\\.f(ast)?q\\.gz$", - "errorMessage": "FastQ file for reads 2 cannot contain spaces and must have extension '.fq.gz' or '.fastq.gz'" + "pattern": "^\\S+\\.bam\\.bai$", + "errorMessage": "BAM index file must be provided, cannot contain spaces and must have extension '.bam.bai'" + }, + "barcodes": { + "type": "string", + "format": "file-path", + "exists": true, + "pattern": "^\\S+\\.tsv$", + "errorMessage": "Barcodes file must be provided, cannot contain spaces and must have extension '.tsv'" + }, + "nsample": { + "type": "integer", + "minimum": 1, + "errorMessage": "Number of samples must be provided and must be greater than 0" } }, - "required": ["sample", "fastq_1"] + "required": [ + "sample" + ] } } diff --git a/conf/test.config b/conf/test.config index dda9345a..c7e02699 100644 --- a/conf/test.config +++ b/conf/test.config @@ -23,8 +23,6 @@ params { config_profile_description = 'Minimal test dataset to check pipeline function' // Input data - // TODO nf-core: Specify the paths to your test data on nf-core/test-datasets - // TODO nf-core: Give any required params for the test so that command line flags are not needed - input = params.pipelines_testdata_base_path + 'viralrecon/samplesheet/samplesheet_test_illumina_amplicon.csv'// Genome references - genome = 'R64-1-1' + input = '/home/nico/Software/nf-core/hadge/data/samplesheet.csv' + mode = 'genetic' } diff --git a/nextflow.config b/nextflow.config index 4350c353..d6667f84 100644 --- a/nextflow.config +++ b/nextflow.config @@ -12,6 +12,7 @@ params { // TODO nf-core: Specify your pipeline's command line flags // Input options input = null + mode = 'rescue' // References genome = null diff --git a/nextflow_schema.json b/nextflow_schema.json index 47a48943..7dfa6805 100644 --- a/nextflow_schema.json +++ b/nextflow_schema.json @@ -10,7 +10,7 @@ "type": "object", "fa_icon": "fas fa-terminal", "description": "Define where the pipeline should find input data and save output data.", - "required": ["input", "outdir"], + "required": ["input", "mode", "outdir"], "properties": { "input": { "type": "string", @@ -23,6 +23,13 @@ "help_text": "You will need to create a design file with information about the samples in your experiment before running the pipeline. Use this parameter to specify its location. It has to be a comma-separated file with 3 columns, and a header row. See [usage docs](https://nf-co.re/hadge/usage#samplesheet-input).", "fa_icon": "fas fa-file-csv" }, + "mode": { + "type": "string", + "description": "Mode of the pipeline.", + "fa_icon": "fas fa-cog", + "enum": ["genetic", "hashing", "rescue", "donor_match"], + "default": "rescue" + }, "outdir": { "type": "string", "format": "directory-path", diff --git a/subworkflows/local/utils_nfcore_hadge_pipeline/main.nf b/subworkflows/local/utils_nfcore_hadge_pipeline/main.nf index d8f79547..dc3cc42c 100644 --- a/subworkflows/local/utils_nfcore_hadge_pipeline/main.nf +++ b/subworkflows/local/utils_nfcore_hadge_pipeline/main.nf @@ -74,22 +74,9 @@ workflow PIPELINE_INITIALISATION { Channel .fromList(samplesheetToList(params.input, "${projectDir}/assets/schema_input.json")) - .map { - meta, fastq_1, fastq_2 -> - if (!fastq_2) { - return [ meta.id, meta + [ single_end:true ], [ fastq_1 ] ] - } else { - return [ meta.id, meta + [ single_end:false ], [ fastq_1, fastq_2 ] ] - } - } - .groupTuple() .map { samplesheet -> validateInputSamplesheet(samplesheet) } - .map { - meta, fastqs -> - return [ meta, fastqs.flatten() ] - } .set { ch_samplesheet } emit: @@ -161,15 +148,6 @@ def validateInputParameters() { // Validate channels from input samplesheet // def validateInputSamplesheet(input) { - def (metas, fastqs) = input[1..2] - - // Check that multiple runs of the same sample are of the same datatype i.e. single-end / paired-end - def endedness_ok = metas.collect{ meta -> meta.single_end }.unique().size == 1 - if (!endedness_ok) { - error("Please check input samplesheet -> Multiple runs of a sample must be of the same datatype i.e. single-end or paired-end: ${metas[0].id}") - } - - return [ metas[0], fastqs ] } // // Get attribute from genome config file e.g. fasta diff --git a/workflows/hadge.nf b/workflows/hadge.nf index f3a6259f..d7a1afb3 100644 --- a/workflows/hadge.nf +++ b/workflows/hadge.nf @@ -19,6 +19,7 @@ workflow HADGE { take: ch_samplesheet // channel: samplesheet read in from --input + main: ch_versions = Channel.empty() From 2fe00f11218a673b91f0c0bcf920a7e80f767eb8 Mon Sep 17 00:00:00 2001 From: nictru Date: Fri, 13 Jun 2025 15:13:06 +0200 Subject: [PATCH 04/74] Init top-level subworkflow structure --- subworkflows/local/donor_matching/main.nf | 8 ++++++++ .../local/genetic_demultiplexing/main.nf | 8 ++++++++ subworkflows/local/hash_demultiplexing/main.nf | 8 ++++++++ workflows/hadge.nf | 17 +++++++++++++++++ 4 files changed, 41 insertions(+) create mode 100644 subworkflows/local/donor_matching/main.nf create mode 100644 subworkflows/local/genetic_demultiplexing/main.nf create mode 100644 subworkflows/local/hash_demultiplexing/main.nf diff --git a/subworkflows/local/donor_matching/main.nf b/subworkflows/local/donor_matching/main.nf new file mode 100644 index 00000000..c8c77762 --- /dev/null +++ b/subworkflows/local/donor_matching/main.nf @@ -0,0 +1,8 @@ +workflow DONOR_MATCHING { + main: + + ch_versions = Channel.empty() + + emit: + versions = ch_versions // channel: [ versions.yml ] +} diff --git a/subworkflows/local/genetic_demultiplexing/main.nf b/subworkflows/local/genetic_demultiplexing/main.nf new file mode 100644 index 00000000..aad0bd0d --- /dev/null +++ b/subworkflows/local/genetic_demultiplexing/main.nf @@ -0,0 +1,8 @@ +workflow GENETIC_DEMULTIPLEXING { + main: + + ch_versions = Channel.empty() + + emit: + versions = ch_versions // channel: [ versions.yml ] +} diff --git a/subworkflows/local/hash_demultiplexing/main.nf b/subworkflows/local/hash_demultiplexing/main.nf new file mode 100644 index 00000000..a2a0ba29 --- /dev/null +++ b/subworkflows/local/hash_demultiplexing/main.nf @@ -0,0 +1,8 @@ +workflow HASH_DEMULTIPLEXING { + main: + + ch_versions = Channel.empty() + + emit: + versions = ch_versions // channel: [ versions.yml ] +} diff --git a/workflows/hadge.nf b/workflows/hadge.nf index d7a1afb3..1afc0113 100644 --- a/workflows/hadge.nf +++ b/workflows/hadge.nf @@ -9,6 +9,10 @@ include { paramsSummaryMultiqc } from '../subworkflows/nf-core/utils_nfcore_pi include { softwareVersionsToYAML } from '../subworkflows/nf-core/utils_nfcore_pipeline' include { methodsDescriptionText } from '../subworkflows/local/utils_nfcore_hadge_pipeline' +include { GENETIC_DEMULTIPLEXING } from '../subworkflows/local/genetic_demultiplexing/main' +include { HASH_DEMULTIPLEXING } from '../subworkflows/local/hash_demultiplexing/main' +include { DONOR_MATCHING } from '../subworkflows/local/donor_matching/main' + /* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ RUN MAIN WORKFLOW @@ -25,6 +29,19 @@ workflow HADGE { ch_versions = Channel.empty() ch_multiqc_files = Channel.empty() + if (params.mode == 'genetic') { + GENETIC_DEMULTIPLEXING(ch_samplesheet) + } else if (params.mode == 'hashing') { + HASH_DEMULTIPLEXING(ch_samplesheet) + } else if (params.mode == 'rescue') { + HASH_DEMULTIPLEXING(ch_samplesheet) + DONOR_MATCHING(ch_samplesheet) + } + + if (params.mode == 'donor_match' || params.match_donor) { + DONOR_MATCHING(ch_samplesheet) + } + // // Collate and save software versions // From 5363bb857aef0e09127942f83b0a9e220173fc7f Mon Sep 17 00:00:00 2001 From: nictru Date: Fri, 13 Jun 2025 15:15:40 +0200 Subject: [PATCH 05/74] Fix top-level structure bugs --- nextflow.config | 1 + nextflow_schema.json | 6 +++ workflows/hadge.nf | 88 ++++++++++++++++++++++++-------------------- 3 files changed, 55 insertions(+), 40 deletions(-) diff --git a/nextflow.config b/nextflow.config index d6667f84..cc00e145 100644 --- a/nextflow.config +++ b/nextflow.config @@ -13,6 +13,7 @@ params { // Input options input = null mode = 'rescue' + match_donor = false // References genome = null diff --git a/nextflow_schema.json b/nextflow_schema.json index 7dfa6805..c982f9bf 100644 --- a/nextflow_schema.json +++ b/nextflow_schema.json @@ -30,6 +30,12 @@ "enum": ["genetic", "hashing", "rescue", "donor_match"], "default": "rescue" }, + "match_donor": { + "type": "boolean", + "description": "Match donor.", + "fa_icon": "fas fa-cog", + "default": false + }, "outdir": { "type": "string", "format": "directory-path", diff --git a/workflows/hadge.nf b/workflows/hadge.nf index 1afc0113..bb968099 100644 --- a/workflows/hadge.nf +++ b/workflows/hadge.nf @@ -20,7 +20,6 @@ include { DONOR_MATCHING } from '../subworkflows/local/donor_matching/ma */ workflow HADGE { - take: ch_samplesheet // channel: samplesheet read in from --input @@ -30,16 +29,24 @@ workflow HADGE { ch_multiqc_files = Channel.empty() if (params.mode == 'genetic') { - GENETIC_DEMULTIPLEXING(ch_samplesheet) - } else if (params.mode == 'hashing') { - HASH_DEMULTIPLEXING(ch_samplesheet) - } else if (params.mode == 'rescue') { - HASH_DEMULTIPLEXING(ch_samplesheet) - DONOR_MATCHING(ch_samplesheet) + GENETIC_DEMULTIPLEXING() + ch_versions = ch_versions.mix(GENETIC_DEMULTIPLEXING.out.versions) + } + else if (params.mode == 'hashing') { + HASH_DEMULTIPLEXING() + ch_versions = ch_versions.mix(HASH_DEMULTIPLEXING.out.versions) + } + else if (params.mode == 'rescue') { + HASH_DEMULTIPLEXING() + ch_versions = ch_versions.mix(HASH_DEMULTIPLEXING.out.versions) + + DONOR_MATCHING() + ch_versions = ch_versions.mix(DONOR_MATCHING.out.versions) } if (params.mode == 'donor_match' || params.match_donor) { - DONOR_MATCHING(ch_samplesheet) + DONOR_MATCHING() + ch_versions = ch_versions.mix(DONOR_MATCHING.out.versions) } // @@ -48,59 +55,60 @@ workflow HADGE { softwareVersionsToYAML(ch_versions) .collectFile( storeDir: "${params.outdir}/pipeline_info", - name: 'nf_core_' + 'hadge_software_' + 'mqc_' + 'versions.yml', + name: 'nf_core_' + 'hadge_software_' + 'mqc_' + 'versions.yml', sort: true, - newLine: true - ).set { ch_collated_versions } + newLine: true, + ) + .set { ch_collated_versions } // // MODULE: MultiQC // - ch_multiqc_config = Channel.fromPath( - "$projectDir/assets/multiqc_config.yml", checkIfExists: true) - ch_multiqc_custom_config = params.multiqc_config ? - Channel.fromPath(params.multiqc_config, checkIfExists: true) : - Channel.empty() - ch_multiqc_logo = params.multiqc_logo ? - Channel.fromPath(params.multiqc_logo, checkIfExists: true) : - Channel.empty() - - summary_params = paramsSummaryMap( - workflow, parameters_schema: "nextflow_schema.json") + ch_multiqc_config = Channel.fromPath( + "${projectDir}/assets/multiqc_config.yml", + checkIfExists: true + ) + ch_multiqc_custom_config = params.multiqc_config + ? Channel.fromPath(params.multiqc_config, checkIfExists: true) + : Channel.empty() + ch_multiqc_logo = params.multiqc_logo + ? Channel.fromPath(params.multiqc_logo, checkIfExists: true) + : Channel.empty() + + summary_params = paramsSummaryMap( + workflow, + parameters_schema: "nextflow_schema.json" + ) ch_workflow_summary = Channel.value(paramsSummaryMultiqc(summary_params)) ch_multiqc_files = ch_multiqc_files.mix( - ch_workflow_summary.collectFile(name: 'workflow_summary_mqc.yaml')) - ch_multiqc_custom_methods_description = params.multiqc_methods_description ? - file(params.multiqc_methods_description, checkIfExists: true) : - file("$projectDir/assets/methods_description_template.yml", checkIfExists: true) - ch_methods_description = Channel.value( - methodsDescriptionText(ch_multiqc_custom_methods_description)) + ch_workflow_summary.collectFile(name: 'workflow_summary_mqc.yaml') + ) + ch_multiqc_custom_methods_description = params.multiqc_methods_description + ? file(params.multiqc_methods_description, checkIfExists: true) + : file("${projectDir}/assets/methods_description_template.yml", checkIfExists: true) + ch_methods_description = Channel.value( + methodsDescriptionText(ch_multiqc_custom_methods_description) + ) ch_multiqc_files = ch_multiqc_files.mix(ch_collated_versions) ch_multiqc_files = ch_multiqc_files.mix( ch_methods_description.collectFile( name: 'methods_description_mqc.yaml', - sort: true + sort: true, ) ) - MULTIQC ( + MULTIQC( ch_multiqc_files.collect(), ch_multiqc_config.toList(), ch_multiqc_custom_config.toList(), ch_multiqc_logo.toList(), [], - [] + [], ) - emit:multiqc_report = MULTIQC.out.report.toList() // channel: /path/to/multiqc_report.html - versions = ch_versions // channel: [ path(versions.yml) ] - + emit: + multiqc_report = MULTIQC.out.report.toList() // channel: /path/to/multiqc_report.html + versions = ch_versions // channel: [ path(versions.yml) ] } - -/* -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - THE END -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -*/ From b4ce1dd7b218fd067a210f5e1b3e733968791340 Mon Sep 17 00:00:00 2001 From: nictru Date: Fri, 13 Jun 2025 15:27:05 +0200 Subject: [PATCH 06/74] Strength samplesheet validation --- assets/schema_input.json | 22 +++-- .../local/utils_nfcore_hadge_pipeline/main.nf | 80 +++++++++++-------- 2 files changed, 63 insertions(+), 39 deletions(-) diff --git a/assets/schema_input.json b/assets/schema_input.json index 1b56e2e2..a6984089 100644 --- a/assets/schema_input.json +++ b/assets/schema_input.json @@ -11,9 +11,7 @@ "type": "string", "pattern": "^\\S+$", "errorMessage": "Sample name must be provided and cannot contain spaces", - "meta": [ - "id" - ] + "meta": ["id"] }, "rna_matrix_raw": { "type": "string", @@ -68,10 +66,22 @@ "type": "integer", "minimum": 1, "errorMessage": "Number of samples must be provided and must be greater than 0" + }, + "vcf_mixed": { + "type": "string", + "format": "file-path", + "exists": true, + "pattern": "^\\S+\\.vcf$", + "errorMessage": "VCF file must be provided, cannot contain spaces and must have extension '.vcf'" + }, + "vcf_donor": { + "type": "string", + "format": "file-path", + "exists": true, + "pattern": "^\\S+\\.vcf$", + "errorMessage": "VCF file must be provided, cannot contain spaces and must have extension '.vcf'" } }, - "required": [ - "sample" - ] + "required": ["sample"] } } diff --git a/subworkflows/local/utils_nfcore_hadge_pipeline/main.nf b/subworkflows/local/utils_nfcore_hadge_pipeline/main.nf index dc3cc42c..a701a689 100644 --- a/subworkflows/local/utils_nfcore_hadge_pipeline/main.nf +++ b/subworkflows/local/utils_nfcore_hadge_pipeline/main.nf @@ -8,14 +8,14 @@ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ -include { UTILS_NFSCHEMA_PLUGIN } from '../../nf-core/utils_nfschema_plugin' -include { paramsSummaryMap } from 'plugin/nf-schema' -include { samplesheetToList } from 'plugin/nf-schema' -include { completionEmail } from '../../nf-core/utils_nfcore_pipeline' -include { completionSummary } from '../../nf-core/utils_nfcore_pipeline' -include { imNotification } from '../../nf-core/utils_nfcore_pipeline' -include { UTILS_NFCORE_PIPELINE } from '../../nf-core/utils_nfcore_pipeline' -include { UTILS_NEXTFLOW_PIPELINE } from '../../nf-core/utils_nextflow_pipeline' +include { UTILS_NFSCHEMA_PLUGIN } from '../../nf-core/utils_nfschema_plugin' +include { paramsSummaryMap } from 'plugin/nf-schema' +include { samplesheetToList } from 'plugin/nf-schema' +include { completionEmail } from '../../nf-core/utils_nfcore_pipeline' +include { completionSummary } from '../../nf-core/utils_nfcore_pipeline' +include { imNotification } from '../../nf-core/utils_nfcore_pipeline' +include { UTILS_NFCORE_PIPELINE } from '../../nf-core/utils_nfcore_pipeline' +include { UTILS_NEXTFLOW_PIPELINE } from '../../nf-core/utils_nextflow_pipeline' /* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -24,7 +24,6 @@ include { UTILS_NEXTFLOW_PIPELINE } from '../../nf-core/utils_nextflow_pipelin */ workflow PIPELINE_INITIALISATION { - take: version // boolean: Display version and exit validate_params // boolean: Boolean whether to validate parameters against the schema at runtime @@ -40,26 +39,26 @@ workflow PIPELINE_INITIALISATION { // // Print version and exit if required and dump pipeline parameters to JSON file // - UTILS_NEXTFLOW_PIPELINE ( + UTILS_NEXTFLOW_PIPELINE( version, true, outdir, - workflow.profile.tokenize(',').intersect(['conda', 'mamba']).size() >= 1 + workflow.profile.tokenize(',').intersect(['conda', 'mamba']).size() >= 1, ) // // Validate parameters and generate parameter summary to stdout // - UTILS_NFSCHEMA_PLUGIN ( + UTILS_NFSCHEMA_PLUGIN( workflow, validate_params, - null + null, ) // // Check config provided to the pipeline // - UTILS_NFCORE_PIPELINE ( + UTILS_NFCORE_PIPELINE( nextflow_cli_args ) @@ -72,8 +71,7 @@ workflow PIPELINE_INITIALISATION { // Create channel from input file provided through params.input // - Channel - .fromList(samplesheetToList(params.input, "${projectDir}/assets/schema_input.json")) + Channel.fromList(samplesheetToList(params.input, "${projectDir}/assets/schema_input.json")) .map { samplesheet -> validateInputSamplesheet(samplesheet) } @@ -91,7 +89,6 @@ workflow PIPELINE_INITIALISATION { */ workflow PIPELINE_COMPLETION { - take: email // string: email address email_on_fail // string: email address sent on pipeline failure @@ -128,7 +125,7 @@ workflow PIPELINE_COMPLETION { } workflow.onError { - log.error "Pipeline failed. Please refer to troubleshooting docs: https://nf-co.re/docs/usage/troubleshooting" + log.error("Pipeline failed. Please refer to troubleshooting docs: https://nf-co.re/docs/usage/troubleshooting") } } @@ -148,14 +145,32 @@ def validateInputParameters() { // Validate channels from input samplesheet // def validateInputSamplesheet(input) { + def (_meta, rna_matrix_raw, rna_matrix_filtered, hto_matrix_raw, hto_matrix_filtered, bam, bai, barcodes, nsample, cell_data, vcf_mixed, vcf_donor) = input + + if (params.mode == 'hashing') { + if (rna_matrix_raw == null || rna_matrix_filtered == null || hto_matrix_raw == null || hto_matrix_filtered == null) { + error("RNA matrix raw, RNA matrix filtered, HTO matrix raw, and HTO matrix filtered must be provided for hashing mode. Please check your input samplesheet.") + } + } + else if (params.mode == 'genetic') { + if (bam == null || bai == null || barcodes == null || nsample == null || cell_data == null || vcf_mixed == null || vcf_donor == null) { + error("BAM file, BAM index file, barcodes file, number of samples, cell data, VCF file for mixed samples, and VCF file for donor samples must be provided for genetic mode. Please check your input samplesheet.") + } + } + else if (params.mode == 'rescue') { + if (rna_matrix_raw == null || rna_matrix_filtered == null || hto_matrix_raw == null || hto_matrix_filtered == null || bam == null || bai == null || barcodes == null || nsample == null || cell_data == null || vcf_mixed == null || vcf_donor == null) { + error("RNA matrix raw, RNA matrix filtered, HTO matrix raw, HTO matrix filtered, BAM file, BAM index file, barcodes file, number of samples, cell data, VCF file for mixed samples, and VCF file for donor samples must be provided for rescue mode. Please check your input samplesheet.") + } + } } + // // Get attribute from genome config file e.g. fasta // def getGenomeAttribute(attribute) { if (params.genomes && params.genome && params.genomes.containsKey(params.genome)) { - if (params.genomes[ params.genome ].containsKey(attribute)) { - return params.genomes[ params.genome ][ attribute ] + if (params.genomes[params.genome].containsKey(attribute)) { + return params.genomes[params.genome][attribute] } } return null @@ -166,11 +181,7 @@ def getGenomeAttribute(attribute) { // def genomeExistsError() { if (params.genomes && params.genome && !params.genomes.containsKey(params.genome)) { - def error_string = "~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n" + - " Genome '${params.genome}' not found in any config files provided to the pipeline.\n" + - " Currently, the available genome keys are:\n" + - " ${params.genomes.keySet().join(", ")}\n" + - "~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~" + def error_string = "~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n" + " Genome '${params.genome}' not found in any config files provided to the pipeline.\n" + " Currently, the available genome keys are:\n" + " ${params.genomes.keySet().join(", ")}\n" + "~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~" error(error_string) } } @@ -182,10 +193,10 @@ def toolCitationText() { // Can use ternary operators to dynamically construct based conditions, e.g. params["run_xyz"] ? "Tool (Foo et al. 2023)" : "", // Uncomment function in methodsDescriptionText to render in MultiQC report def citation_text = [ - "Tools used in the workflow included:", - "MultiQC (Ewels et al. 2016)", - "." - ].join(' ').trim() + "Tools used in the workflow included:", + "MultiQC (Ewels et al. 2016)", + ".", + ].join(' ').trim() return citation_text } @@ -195,8 +206,8 @@ def toolBibliographyText() { // Can use ternary operators to dynamically construct based conditions, e.g. params["run_xyz"] ? "
  • Author (2023) Pub name, Journal, DOI
  • " : "", // Uncomment function in methodsDescriptionText to render in MultiQC report def reference_text = [ - "
  • Ewels, P., Magnusson, M., Lundin, S., & Käller, M. (2016). MultiQC: summarize analysis results for multiple tools and samples in a single report. Bioinformatics , 32(19), 3047–3048. doi: /10.1093/bioinformatics/btw354
  • " - ].join(' ').trim() + "
  • Ewels, P., Magnusson, M., Lundin, S., & Käller, M. (2016). MultiQC: summarize analysis results for multiple tools and samples in a single report. Bioinformatics , 32(19), 3047–3048. doi: /10.1093/bioinformatics/btw354
  • " + ].join(' ').trim() return reference_text } @@ -218,7 +229,10 @@ def methodsDescriptionText(mqc_methods_yaml) { temp_doi_ref += "(doi: ${doi_ref.replace("https://doi.org/", "").replace(" ", "")}), " } meta["doi_text"] = temp_doi_ref.substring(0, temp_doi_ref.length() - 2) - } else meta["doi_text"] = "" + } + else { + meta["doi_text"] = "" + } meta["nodoi_text"] = meta.manifest_map.doi ? "" : "
  • If available, make sure to update the text to include the Zenodo DOI of version of the pipeline used.
  • " // Tool references @@ -232,7 +246,7 @@ def methodsDescriptionText(mqc_methods_yaml) { def methods_text = mqc_methods_yaml.text - def engine = new groovy.text.SimpleTemplateEngine() + def engine = new groovy.text.SimpleTemplateEngine() def description_html = engine.createTemplate(methods_text).make(meta) return description_html.toString() From c1e493647fc1daf952813fd77f6afd44f60dd890 Mon Sep 17 00:00:00 2001 From: nictru Date: Fri, 13 Jun 2025 16:05:37 +0200 Subject: [PATCH 07/74] Add tool selection parameters --- nextflow.config | 2 ++ nextflow_schema.json | 16 ++++++++++++++++ 2 files changed, 18 insertions(+) diff --git a/nextflow.config b/nextflow.config index cc00e145..5f2b6e91 100644 --- a/nextflow.config +++ b/nextflow.config @@ -14,6 +14,8 @@ params { input = null mode = 'rescue' match_donor = false + hash_tools = 'gmm-demux' + genetic_tools = 'vireo' // References genome = null diff --git a/nextflow_schema.json b/nextflow_schema.json index c982f9bf..7780a1da 100644 --- a/nextflow_schema.json +++ b/nextflow_schema.json @@ -36,6 +36,22 @@ "fa_icon": "fas fa-cog", "default": false }, + "hash_tools": { + "type": "string", + "description": "Tools used for hash demultiplexing.", + "help_text": "If you want to use multiple, separate with a comma. The available tools are: htodemux, multiseq, cellhashr, demuxem, gmm-demux, hasheddrops, hashsolo", + "fa_icon": "fas fa-cog", + "default": "gmm-demux", + "pattern": "^(htodemux|multiseq|cellhashr|demuxem|gmm-demux|hasheddrops|hashsolo)(,(htodemux|multiseq|cellhashr|demuxem|gmm-demux|hasheddrops|hashsolo))*$" + }, + "genetic_tools": { + "type": "string", + "description": "Tools used for genetic demultiplexing.", + "help_text": "If you want to use multiple, separate with a comma. The available tools are: vireo, demuxlet, freemuxlet, souporcell, cellsnp", + "fa_icon": "fas fa-cog", + "default": "vireo", + "pattern": "^(vireo|demuxlet|freemuxlet|souporcell|cellsnp)(,(vireo|demuxlet|freemuxlet|souporcell|cellsnp))*$" + }, "outdir": { "type": "string", "format": "directory-path", From e783439fe2dd351088eeb12b15f9e9c603b9260b Mon Sep 17 00:00:00 2001 From: nictru Date: Fri, 13 Jun 2025 16:09:20 +0200 Subject: [PATCH 08/74] Pass methods to subworkflows --- conf/test.config | 2 +- subworkflows/local/genetic_demultiplexing/main.nf | 3 +++ subworkflows/local/hash_demultiplexing/main.nf | 4 ++++ workflows/hadge.nf | 8 ++++---- 4 files changed, 12 insertions(+), 5 deletions(-) diff --git a/conf/test.config b/conf/test.config index c7e02699..03938d08 100644 --- a/conf/test.config +++ b/conf/test.config @@ -24,5 +24,5 @@ params { // Input data input = '/home/nico/Software/nf-core/hadge/data/samplesheet.csv' - mode = 'genetic' + mode = 'hashing' } diff --git a/subworkflows/local/genetic_demultiplexing/main.nf b/subworkflows/local/genetic_demultiplexing/main.nf index aad0bd0d..ffa4b95f 100644 --- a/subworkflows/local/genetic_demultiplexing/main.nf +++ b/subworkflows/local/genetic_demultiplexing/main.nf @@ -1,4 +1,7 @@ workflow GENETIC_DEMULTIPLEXING { + take: + methods // list of strings + main: ch_versions = Channel.empty() diff --git a/subworkflows/local/hash_demultiplexing/main.nf b/subworkflows/local/hash_demultiplexing/main.nf index a2a0ba29..70d2a707 100644 --- a/subworkflows/local/hash_demultiplexing/main.nf +++ b/subworkflows/local/hash_demultiplexing/main.nf @@ -1,4 +1,8 @@ workflow HASH_DEMULTIPLEXING { + + take: + methods // list of strings + main: ch_versions = Channel.empty() diff --git a/workflows/hadge.nf b/workflows/hadge.nf index bb968099..05660712 100644 --- a/workflows/hadge.nf +++ b/workflows/hadge.nf @@ -29,18 +29,18 @@ workflow HADGE { ch_multiqc_files = Channel.empty() if (params.mode == 'genetic') { - GENETIC_DEMULTIPLEXING() + GENETIC_DEMULTIPLEXING(params.genetic_tools.split(',')) ch_versions = ch_versions.mix(GENETIC_DEMULTIPLEXING.out.versions) } else if (params.mode == 'hashing') { - HASH_DEMULTIPLEXING() + HASH_DEMULTIPLEXING(params.hash_tools.split(',')) ch_versions = ch_versions.mix(HASH_DEMULTIPLEXING.out.versions) } else if (params.mode == 'rescue') { - HASH_DEMULTIPLEXING() + HASH_DEMULTIPLEXING(params.hash_tools.split(',')) ch_versions = ch_versions.mix(HASH_DEMULTIPLEXING.out.versions) - DONOR_MATCHING() + DONOR_MATCHING(params.genetic_tools.split(',')) ch_versions = ch_versions.mix(DONOR_MATCHING.out.versions) } From 646f18d62e4d2375f000d7f9220b51a11e2c9f1c Mon Sep 17 00:00:00 2001 From: nictru Date: Fri, 13 Jun 2025 16:12:34 +0200 Subject: [PATCH 09/74] Update nf-core modules and subworkflows --- modules.json | 6 +++--- modules/nf-core/multiqc/environment.yml | 4 +++- modules/nf-core/multiqc/main.nf | 4 ++-- .../nf-core/multiqc/tests/main.nf.test.snap | 18 +++++++++--------- modules/nf-core/multiqc/tests/tags.yml | 2 -- .../utils_nextflow_pipeline/tests/tags.yml | 2 -- .../utils_nfcore_pipeline/tests/tags.yml | 2 -- 7 files changed, 17 insertions(+), 21 deletions(-) delete mode 100644 modules/nf-core/multiqc/tests/tags.yml delete mode 100644 subworkflows/nf-core/utils_nextflow_pipeline/tests/tags.yml delete mode 100644 subworkflows/nf-core/utils_nfcore_pipeline/tests/tags.yml diff --git a/modules.json b/modules.json index d5d43c17..55c03315 100644 --- a/modules.json +++ b/modules.json @@ -7,7 +7,7 @@ "nf-core": { "multiqc": { "branch": "master", - "git_sha": "f0719ae309075ae4a291533883847c3f7c441dad", + "git_sha": "e594e9dfaffa7572afc11bafc634984fd4cbd87b", "installed_by": ["modules"] } } @@ -16,12 +16,12 @@ "nf-core": { "utils_nextflow_pipeline": { "branch": "master", - "git_sha": "c2b22d85f30a706a3073387f30380704fcae013b", + "git_sha": "05954dab2ff481bcb999f24455da29a5828af08d", "installed_by": ["subworkflows"] }, "utils_nfcore_pipeline": { "branch": "master", - "git_sha": "51ae5406a030d4da1e49e4dab49756844fdd6c7a", + "git_sha": "05954dab2ff481bcb999f24455da29a5828af08d", "installed_by": ["subworkflows"] }, "utils_nfschema_plugin": { diff --git a/modules/nf-core/multiqc/environment.yml b/modules/nf-core/multiqc/environment.yml index a27122ce..812fc4c5 100644 --- a/modules/nf-core/multiqc/environment.yml +++ b/modules/nf-core/multiqc/environment.yml @@ -1,5 +1,7 @@ +--- +# yaml-language-server: $schema=https://raw.githubusercontent.com/nf-core/modules/master/modules/environment-schema.json channels: - conda-forge - bioconda dependencies: - - bioconda::multiqc=1.27 + - bioconda::multiqc=1.29 diff --git a/modules/nf-core/multiqc/main.nf b/modules/nf-core/multiqc/main.nf index 58d9313c..0ac3c369 100644 --- a/modules/nf-core/multiqc/main.nf +++ b/modules/nf-core/multiqc/main.nf @@ -3,8 +3,8 @@ process MULTIQC { conda "${moduleDir}/environment.yml" container "${ workflow.containerEngine == 'singularity' && !task.ext.singularity_pull_docker_container ? - 'https://depot.galaxyproject.org/singularity/multiqc:1.27--pyhdfd78af_0' : - 'biocontainers/multiqc:1.27--pyhdfd78af_0' }" + 'https://depot.galaxyproject.org/singularity/multiqc:1.29--pyhdfd78af_0' : + 'biocontainers/multiqc:1.29--pyhdfd78af_0' }" input: path multiqc_files, stageAs: "?/*" diff --git a/modules/nf-core/multiqc/tests/main.nf.test.snap b/modules/nf-core/multiqc/tests/main.nf.test.snap index 7b7c1322..88e90571 100644 --- a/modules/nf-core/multiqc/tests/main.nf.test.snap +++ b/modules/nf-core/multiqc/tests/main.nf.test.snap @@ -2,14 +2,14 @@ "multiqc_versions_single": { "content": [ [ - "versions.yml:md5,8f3b8c1cec5388cf2708be948c9fa42f" + "versions.yml:md5,c1fe644a37468f6dae548d98bc72c2c1" ] ], "meta": { "nf-test": "0.9.2", - "nextflow": "24.10.4" + "nextflow": "25.04.2" }, - "timestamp": "2025-01-27T09:29:57.631982377" + "timestamp": "2025-05-22T11:50:41.182332996" }, "multiqc_stub": { "content": [ @@ -17,25 +17,25 @@ "multiqc_report.html", "multiqc_data", "multiqc_plots", - "versions.yml:md5,8f3b8c1cec5388cf2708be948c9fa42f" + "versions.yml:md5,c1fe644a37468f6dae548d98bc72c2c1" ] ], "meta": { "nf-test": "0.9.2", - "nextflow": "24.10.4" + "nextflow": "25.04.2" }, - "timestamp": "2025-01-27T09:30:34.743726958" + "timestamp": "2025-05-22T11:51:22.448739369" }, "multiqc_versions_config": { "content": [ [ - "versions.yml:md5,8f3b8c1cec5388cf2708be948c9fa42f" + "versions.yml:md5,c1fe644a37468f6dae548d98bc72c2c1" ] ], "meta": { "nf-test": "0.9.2", - "nextflow": "24.10.4" + "nextflow": "25.04.2" }, - "timestamp": "2025-01-27T09:30:21.44383553" + "timestamp": "2025-05-22T11:51:06.198928424" } } \ No newline at end of file diff --git a/modules/nf-core/multiqc/tests/tags.yml b/modules/nf-core/multiqc/tests/tags.yml deleted file mode 100644 index bea6c0d3..00000000 --- a/modules/nf-core/multiqc/tests/tags.yml +++ /dev/null @@ -1,2 +0,0 @@ -multiqc: - - modules/nf-core/multiqc/** diff --git a/subworkflows/nf-core/utils_nextflow_pipeline/tests/tags.yml b/subworkflows/nf-core/utils_nextflow_pipeline/tests/tags.yml deleted file mode 100644 index f8476112..00000000 --- a/subworkflows/nf-core/utils_nextflow_pipeline/tests/tags.yml +++ /dev/null @@ -1,2 +0,0 @@ -subworkflows/utils_nextflow_pipeline: - - subworkflows/nf-core/utils_nextflow_pipeline/** diff --git a/subworkflows/nf-core/utils_nfcore_pipeline/tests/tags.yml b/subworkflows/nf-core/utils_nfcore_pipeline/tests/tags.yml deleted file mode 100644 index ac8523c9..00000000 --- a/subworkflows/nf-core/utils_nfcore_pipeline/tests/tags.yml +++ /dev/null @@ -1,2 +0,0 @@ -subworkflows/utils_nfcore_pipeline: - - subworkflows/nf-core/utils_nfcore_pipeline/** From 49fd0ff4c0293242ca6abeb9226f577c4490f263 Mon Sep 17 00:00:00 2001 From: nictru Date: Fri, 13 Jun 2025 16:15:00 +0200 Subject: [PATCH 10/74] Add method condition blocks --- .../local/genetic_demultiplexing/main.nf | 9 +++++++++ subworkflows/local/hash_demultiplexing/main.nf | 16 +++++++++++++++- 2 files changed, 24 insertions(+), 1 deletion(-) diff --git a/subworkflows/local/genetic_demultiplexing/main.nf b/subworkflows/local/genetic_demultiplexing/main.nf index ffa4b95f..423208f9 100644 --- a/subworkflows/local/genetic_demultiplexing/main.nf +++ b/subworkflows/local/genetic_demultiplexing/main.nf @@ -6,6 +6,15 @@ workflow GENETIC_DEMULTIPLEXING { ch_versions = Channel.empty() + if (methods.contains('vireo')) { + } + if (methods.contains('demuxlet')) { + } + if (methods.contains('freemuxlet')) { + } + if (methods.contains('souporcell')) { + } + emit: versions = ch_versions // channel: [ versions.yml ] } diff --git a/subworkflows/local/hash_demultiplexing/main.nf b/subworkflows/local/hash_demultiplexing/main.nf index 70d2a707..8ba28f05 100644 --- a/subworkflows/local/hash_demultiplexing/main.nf +++ b/subworkflows/local/hash_demultiplexing/main.nf @@ -1,5 +1,4 @@ workflow HASH_DEMULTIPLEXING { - take: methods // list of strings @@ -7,6 +6,21 @@ workflow HASH_DEMULTIPLEXING { ch_versions = Channel.empty() + if (methods.contains('htodemux')) { + } + if (methods.contains('multiseq')) { + } + if (methods.contains('cellhashr')) { + } + if (methods.contains('demuxem')) { + } + if (methods.contains('gmm-demux')) { + } + if (methods.contains('hasheddrops')) { + } + if (methods.contains('hashsolo')) { + } + emit: versions = ch_versions // channel: [ versions.yml ] } From 4e5185ef9407c48ea9097baa0ac2b6e06ee690ac Mon Sep 17 00:00:00 2001 From: nictru Date: Fri, 13 Jun 2025 16:22:35 +0200 Subject: [PATCH 11/74] Install existing nf-core modules --- modules.json | 30 +++ modules/nf-core/cellsnp/modea/environment.yml | 7 + modules/nf-core/cellsnp/modea/main.nf | 68 +++++++ modules/nf-core/cellsnp/modea/meta.yml | 118 +++++++++++ .../nf-core/cellsnp/modea/tests/main.nf.test | 85 ++++++++ .../cellsnp/modea/tests/main.nf.test.snap | 158 +++++++++++++++ modules/nf-core/demuxem/environment.yml | 7 + modules/nf-core/demuxem/main.nf | 54 ++++++ modules/nf-core/demuxem/meta.yml | 88 +++++++++ modules/nf-core/demuxem/nextflow.config | 20 ++ modules/nf-core/demuxem/tests/main.nf.test | 74 +++++++ .../nf-core/demuxem/tests/main.nf.test.snap | 84 ++++++++ modules/nf-core/demuxem/tests/nextflow.config | 20 ++ modules/nf-core/gmmdemux/environment.yml | 7 + modules/nf-core/gmmdemux/main.nf | 73 +++++++ modules/nf-core/gmmdemux/meta.yml | 132 +++++++++++++ modules/nf-core/gmmdemux/tests/main.nf.test | 118 +++++++++++ .../nf-core/gmmdemux/tests/main.nf.test.snap | 105 ++++++++++ .../nf-core/gmmdemux/tests/nextflow.config | 16 ++ .../nf-core/popscle/demuxlet/environment.yml | 7 + modules/nf-core/popscle/demuxlet/main.nf | 52 +++++ modules/nf-core/popscle/demuxlet/meta.yml | 69 +++++++ .../popscle/demuxlet/tests/main.nf.test | 104 ++++++++++ .../popscle/demuxlet/tests/main.nf.test.snap | 38 ++++ .../popscle/demuxlet/tests/nextflow.config | 5 + .../popscle/freemuxlet/environment.yml | 7 + modules/nf-core/popscle/freemuxlet/main.nf | 61 ++++++ modules/nf-core/popscle/freemuxlet/meta.yml | 98 ++++++++++ .../popscle/freemuxlet/tests/main.nf.test | 183 ++++++++++++++++++ .../freemuxlet/tests/main.nf.test.snap | 50 +++++ .../popscle/freemuxlet/tests/nextflow.config | 5 + modules/nf-core/vireo/environment.yml | 7 + modules/nf-core/vireo/main.nf | 62 ++++++ modules/nf-core/vireo/meta.yml | 95 +++++++++ modules/nf-core/vireo/tests/main.nf.test | 90 +++++++++ modules/nf-core/vireo/tests/main.nf.test.snap | 164 ++++++++++++++++ modules/nf-core/vireo/tests/nextflow.config | 5 + 37 files changed, 2366 insertions(+) create mode 100644 modules/nf-core/cellsnp/modea/environment.yml create mode 100644 modules/nf-core/cellsnp/modea/main.nf create mode 100644 modules/nf-core/cellsnp/modea/meta.yml create mode 100644 modules/nf-core/cellsnp/modea/tests/main.nf.test create mode 100644 modules/nf-core/cellsnp/modea/tests/main.nf.test.snap create mode 100644 modules/nf-core/demuxem/environment.yml create mode 100644 modules/nf-core/demuxem/main.nf create mode 100644 modules/nf-core/demuxem/meta.yml create mode 100644 modules/nf-core/demuxem/nextflow.config create mode 100644 modules/nf-core/demuxem/tests/main.nf.test create mode 100644 modules/nf-core/demuxem/tests/main.nf.test.snap create mode 100644 modules/nf-core/demuxem/tests/nextflow.config create mode 100644 modules/nf-core/gmmdemux/environment.yml create mode 100644 modules/nf-core/gmmdemux/main.nf create mode 100644 modules/nf-core/gmmdemux/meta.yml create mode 100644 modules/nf-core/gmmdemux/tests/main.nf.test create mode 100644 modules/nf-core/gmmdemux/tests/main.nf.test.snap create mode 100644 modules/nf-core/gmmdemux/tests/nextflow.config create mode 100644 modules/nf-core/popscle/demuxlet/environment.yml create mode 100644 modules/nf-core/popscle/demuxlet/main.nf create mode 100644 modules/nf-core/popscle/demuxlet/meta.yml create mode 100644 modules/nf-core/popscle/demuxlet/tests/main.nf.test create mode 100644 modules/nf-core/popscle/demuxlet/tests/main.nf.test.snap create mode 100644 modules/nf-core/popscle/demuxlet/tests/nextflow.config create mode 100644 modules/nf-core/popscle/freemuxlet/environment.yml create mode 100644 modules/nf-core/popscle/freemuxlet/main.nf create mode 100644 modules/nf-core/popscle/freemuxlet/meta.yml create mode 100644 modules/nf-core/popscle/freemuxlet/tests/main.nf.test create mode 100644 modules/nf-core/popscle/freemuxlet/tests/main.nf.test.snap create mode 100644 modules/nf-core/popscle/freemuxlet/tests/nextflow.config create mode 100644 modules/nf-core/vireo/environment.yml create mode 100644 modules/nf-core/vireo/main.nf create mode 100644 modules/nf-core/vireo/meta.yml create mode 100644 modules/nf-core/vireo/tests/main.nf.test create mode 100644 modules/nf-core/vireo/tests/main.nf.test.snap create mode 100644 modules/nf-core/vireo/tests/nextflow.config diff --git a/modules.json b/modules.json index 55c03315..fd16645c 100644 --- a/modules.json +++ b/modules.json @@ -5,10 +5,40 @@ "https://github.com/nf-core/modules.git": { "modules": { "nf-core": { + "cellsnp/modea": { + "branch": "master", + "git_sha": "05954dab2ff481bcb999f24455da29a5828af08d", + "installed_by": ["modules"] + }, + "demuxem": { + "branch": "master", + "git_sha": "05954dab2ff481bcb999f24455da29a5828af08d", + "installed_by": ["modules"] + }, + "gmmdemux": { + "branch": "master", + "git_sha": "05954dab2ff481bcb999f24455da29a5828af08d", + "installed_by": ["modules"] + }, "multiqc": { "branch": "master", "git_sha": "e594e9dfaffa7572afc11bafc634984fd4cbd87b", "installed_by": ["modules"] + }, + "popscle/demuxlet": { + "branch": "master", + "git_sha": "05954dab2ff481bcb999f24455da29a5828af08d", + "installed_by": ["modules"] + }, + "popscle/freemuxlet": { + "branch": "master", + "git_sha": "05954dab2ff481bcb999f24455da29a5828af08d", + "installed_by": ["modules"] + }, + "vireo": { + "branch": "master", + "git_sha": "05954dab2ff481bcb999f24455da29a5828af08d", + "installed_by": ["modules"] } } }, diff --git a/modules/nf-core/cellsnp/modea/environment.yml b/modules/nf-core/cellsnp/modea/environment.yml new file mode 100644 index 00000000..fce6c9fc --- /dev/null +++ b/modules/nf-core/cellsnp/modea/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: + - bioconda::cellsnp-lite=1.2.3 diff --git a/modules/nf-core/cellsnp/modea/main.nf b/modules/nf-core/cellsnp/modea/main.nf new file mode 100644 index 00000000..048a2449 --- /dev/null +++ b/modules/nf-core/cellsnp/modea/main.nf @@ -0,0 +1,68 @@ +process CELLSNP_MODEA { + tag "$meta.id" + label 'process_medium' + + conda "${moduleDir}/environment.yml" + container "${ workflow.containerEngine == 'singularity' && !task.ext.singularity_pull_docker_container ? + 'https://depot.galaxyproject.org/singularity/cellsnp-lite:1.2.3--h6141fd1_2' : + 'biocontainers/cellsnp-lite:1.2.3--h6141fd1_2' }" + + input: + tuple val(meta), path(bam), path(bai), path(region_vcf), path(barcode) + + output: + tuple val(meta), path('*.base.vcf.gz') , emit: base + tuple val(meta), path('*.cells.vcf.gz'), emit: cell , optional: true + tuple val(meta), path('*.samples.tsv') , emit: sample + tuple val(meta), path('*.tag.AD.mtx') , emit: allele_depth + tuple val(meta), path('*.tag.DP.mtx') , emit: depth_coverage + tuple val(meta), path('*.tag.OTH.mtx') , emit: depth_other + path 'versions.yml' , emit: versions + + when: + task.ext.when == null || task.ext.when + + script: + def args = task.ext.args ?: '' + def prefix = task.ext.prefix ?: "${meta.id}" + def region_file = region_vcf ? "-R $region_vcf" : '' + """ + cellsnp-lite -s $bam \\ + -b $barcode \\ + $region_file \\ + -O . \\ + --gzip \\ + --nproc $task.cpus \\ + $args + + mv cellSNP.base.vcf.gz ${prefix}.base.vcf.gz + if [[ "$args" == *"--genotype"* ]]; then + mv cellSNP.cells.vcf.gz ${prefix}.cells.vcf.gz + fi + mv cellSNP.tag.AD.mtx ${prefix}.tag.AD.mtx + mv cellSNP.tag.DP.mtx ${prefix}.tag.DP.mtx + mv cellSNP.tag.OTH.mtx ${prefix}.tag.OTH.mtx + mv cellSNP.samples.tsv ${prefix}.samples.tsv + + cat <<-END_VERSIONS > versions.yml + "${task.process}": + cellsnp: \$(cellsnp-lite --v | awk '{print \$2}') + END_VERSIONS + """ + + stub: + def prefix = task.ext.prefix ?: "${meta.id}" + """ + mkdir $prefix + echo "" | gzip > ${prefix}.base.vcf.gz + touch ${prefix}.samples.tsv + touch ${prefix}.tag.AD.mtx + touch ${prefix}.tag.DP.mtx + touch ${prefix}.tag.OTH.mtx + + cat <<-END_VERSIONS > versions.yml + "${task.process}": + cellsnp: \$(cellsnp-lite --v | awk '{print \$2}') + END_VERSIONS + """ +} diff --git a/modules/nf-core/cellsnp/modea/meta.yml b/modules/nf-core/cellsnp/modea/meta.yml new file mode 100644 index 00000000..1b7fac56 --- /dev/null +++ b/modules/nf-core/cellsnp/modea/meta.yml @@ -0,0 +1,118 @@ +# yaml-language-server: $schema=https://raw.githubusercontent.com/nf-core/modules/master/modules/meta-schema.json +name: "cellsnp_modea" +description: Cellsnp-lite is a C/C++ tool for efficient genotyping bi-allelic SNPs + on single cells. You can use the mode A of cellsnp-lite after read alignment to + obtain the snp x cell pileup UMI or read count matrices for each alleles of given + or detected SNPs for droplet based single cell data. +keywords: + - genotyping + - single cell + - SNP + - droplet based single cells +tools: + - "cellsnp": + description: "Efficient genotyping bi-allelic SNPs on single cells" + homepage: "https://github.com/single-cell-genetics/cellsnp-lite" + documentation: "https://cellsnp-lite.readthedocs.io" + tool_dev_url: "https://github.com/single-cell-genetics/cellsnp-lite" + doi: "10.1093/bioinformatics/btab358" + licence: ["Apache-2.0"] + identifier: "" + +input: + - - meta: + type: map + description: | + Groovy Map containing sample information + e.g. `[ id:'sample1', single_end:false ]` + - bam: + type: file + description: A single BAM/SAM/CRAM file, e.g., from CellRanger. + pattern: "*.{bam,cram,sam}" + - bai: + type: file + description: The index of the BAM/CRAM file. + pattern: "*.{bai,crai}" + - region_vcf: + type: file + description: A optional vcf file listing all candidate SNPs for genotyping. + pattern: "*.{vcf, vcf.gz}" + - barcode: + type: file + description: A plain file listing all effective cell barcodes. + pattern: "*.tsv" +output: + - base: + - meta: + type: map + description: | + Groovy Map containing sample information + e.g. `[ id:'sample1', single_end:false ]` + - "*.base.vcf.gz": + type: file + description: A VCF file listing genotyped SNPs and aggregated AD & DP information + (without GT). + pattern: "*.base.vcf.gz" + - cell: + - meta: + type: map + description: | + Groovy Map containing sample information + e.g. `[ id:'sample1', single_end:false ]` + - "*.cells.vcf.gz": + type: file + description: A VCF file listing genotyped SNPs and aggregated AD & DP information + & genotype (GT) information for each cell or sample. + pattern: "*.cells.vcf.gz" + - sample: + - meta: + type: map + description: | + Groovy Map containing sample information + e.g. `[ id:'sample1', single_end:false ]` + - "*.samples.tsv": + type: file + description: A TSV file listing cell barcodes or sample IDs. + pattern: "*.tsv" + - allele_depth: + - meta: + type: map + description: | + Groovy Map containing sample information + e.g. `[ id:'sample1', single_end:false ]` + - "*.tag.AD.mtx": + type: file + description: A file in “Matrix Market exchange formats”, containing the allele + depths of the alternative (ALT) alleles. + pattern: "*.tag.AD.mtx" + - depth_coverage: + - meta: + type: map + description: | + Groovy Map containing sample information + e.g. `[ id:'sample1', single_end:false ]` + - "*.tag.DP.mtx": + type: file + description: A file in “Matrix Market exchange formats”, containing the sum + of allele depths of the reference and alternative alleles (REF + ALT). + pattern: "*.tag.DP.mtx" + - depth_other: + - meta: + type: map + description: | + Groovy Map containing sample information + e.g. `[ id:'sample1', single_end:false ]` + - "*.tag.OTH.mtx": + type: file + description: A file in “Matrix Market exchange formats”, containing the sum + of allele depths of all the alleles other than REF and ALT. + pattern: "*.tag.OTH.mtx" + - versions: + - versions.yml: + type: file + description: File containing software versions + pattern: "versions.yml" +authors: + - "@wxicu" +maintainers: + - "@wxicu" diff --git a/modules/nf-core/cellsnp/modea/tests/main.nf.test b/modules/nf-core/cellsnp/modea/tests/main.nf.test new file mode 100644 index 00000000..8f67d265 --- /dev/null +++ b/modules/nf-core/cellsnp/modea/tests/main.nf.test @@ -0,0 +1,85 @@ +// nf-core modules test cellsnp/modea +nextflow_process { + + name "Test Process CELLSNP_MODEA" + script "../main.nf" + process "CELLSNP_MODEA" + + tag "modules" + tag "modules_nfcore" + tag "cellsnp" + tag "cellsnp/modea" + tag "samtools/index" + + test("genotyping") { + setup { + run("SAMTOOLS_INDEX") { + script "../../../samtools/index/main.nf" + process { + """ + input[0] = [ + [ id:'sample1' ], + file(params.modules_testdata_base_path + '/genomics/homo_sapiens/demultiplexing/chr21.bam', checkIfExists: true) ] + """ + } + } + } + + when { + process { + """ + + input[0] = SAMTOOLS_INDEX.out.bai.collect{ meta, bai -> bai }.map{ + bai -> [[ id: 'sample1'], + file(params.modules_testdata_base_path + '/genomics/homo_sapiens/demultiplexing/chr21.bam', checkIfExists: true), + bai, + file(params.modules_testdata_base_path + '/genomics/homo_sapiens/demultiplexing/donor_genotype_chr21.vcf', checkIfExists: true), + file(params.modules_testdata_base_path + '/genomics/homo_sapiens/demultiplexing/barcodes.tsv', checkIfExists: true) ]} + """ + } + } + + then { + assertAll( + { assert process.success }, + { assert snapshot(process.out.versions).match("versions") }, + { assert path(process.out.base.get(0).get(1)).exists() }, + { assert snapshot( + process.out.sample, + process.out.allele_depth, + process.out.depth_coverage, + process.out.depth_other).match() + } + ) + } + + } + + test("genotyping - stub") { + + options "-stub" + + when { + process { + """ + + input[0] = [ + [ id:'sample1'], + file(params.modules_testdata_base_path + '/genomics/homo_sapiens/demultiplexing/chr21.bam', checkIfExists: true), + [], + file(params.modules_testdata_base_path + '/genomics/homo_sapiens/demultiplexing/donor_genotype_chr21.vcf', checkIfExists: true), + file(params.modules_testdata_base_path + '/genomics/homo_sapiens/demultiplexing/barcodes.tsv', checkIfExists: true) + ] + """ + } + } + + then { + assertAll( + { assert process.success }, + { assert snapshot(process.out).match("stub")}) + + } + } + +} diff --git a/modules/nf-core/cellsnp/modea/tests/main.nf.test.snap b/modules/nf-core/cellsnp/modea/tests/main.nf.test.snap new file mode 100644 index 00000000..07997e3f --- /dev/null +++ b/modules/nf-core/cellsnp/modea/tests/main.nf.test.snap @@ -0,0 +1,158 @@ +{ + "versions": { + "content": [ + [ + "versions.yml:md5,965121af3dc48657c2128c404589fa6b" + ] + ], + "meta": { + "nf-test": "0.8.4", + "nextflow": "23.10.1" + }, + "timestamp": "2024-05-08T22:02:32.670061197" + }, + "genotyping": { + "content": [ + [ + [ + { + "id": "sample1" + }, + "sample1.samples.tsv:md5,9e488782c1bcd63c37ee3d1c4c0a9217" + ] + ], + [ + [ + { + "id": "sample1" + }, + "sample1.tag.AD.mtx:md5,dbe2d13dca2717749554d1f2a8b85650" + ] + ], + [ + [ + { + "id": "sample1" + }, + "sample1.tag.DP.mtx:md5,29fbb6241c6c7b0a0fa31021e622c415" + ] + ], + [ + [ + { + "id": "sample1" + }, + "sample1.tag.OTH.mtx:md5,1e3429950c59edec58a80a9b4ecda552" + ] + ] + ], + "meta": { + "nf-test": "0.8.4", + "nextflow": "23.10.1" + }, + "timestamp": "2024-05-08T22:02:32.689525045" + }, + "stub": { + "content": [ + { + "0": [ + [ + { + "id": "sample1" + }, + "sample1.base.vcf.gz:md5,68b329da9893e34099c7d8ad5cb9c940" + ] + ], + "1": [ + + ], + "2": [ + [ + { + "id": "sample1" + }, + "sample1.samples.tsv:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "3": [ + [ + { + "id": "sample1" + }, + "sample1.tag.AD.mtx:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "4": [ + [ + { + "id": "sample1" + }, + "sample1.tag.DP.mtx:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "5": [ + [ + { + "id": "sample1" + }, + "sample1.tag.OTH.mtx:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "6": [ + "versions.yml:md5,965121af3dc48657c2128c404589fa6b" + ], + "allele_depth": [ + [ + { + "id": "sample1" + }, + "sample1.tag.AD.mtx:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "base": [ + [ + { + "id": "sample1" + }, + "sample1.base.vcf.gz:md5,68b329da9893e34099c7d8ad5cb9c940" + ] + ], + "cell": [ + + ], + "depth_coverage": [ + [ + { + "id": "sample1" + }, + "sample1.tag.DP.mtx:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "depth_other": [ + [ + { + "id": "sample1" + }, + "sample1.tag.OTH.mtx:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "sample": [ + [ + { + "id": "sample1" + }, + "sample1.samples.tsv:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "versions": [ + "versions.yml:md5,965121af3dc48657c2128c404589fa6b" + ] + } + ], + "meta": { + "nf-test": "0.8.4", + "nextflow": "23.10.1" + }, + "timestamp": "2024-05-08T22:02:39.591564384" + } +} \ No newline at end of file diff --git a/modules/nf-core/demuxem/environment.yml b/modules/nf-core/demuxem/environment.yml new file mode 100644 index 00000000..db39832a --- /dev/null +++ b/modules/nf-core/demuxem/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: + - bioconda::demuxem=0.1.7.post1 diff --git a/modules/nf-core/demuxem/main.nf b/modules/nf-core/demuxem/main.nf new file mode 100644 index 00000000..7c850ac0 --- /dev/null +++ b/modules/nf-core/demuxem/main.nf @@ -0,0 +1,54 @@ +process DEMUXEM { + tag "$meta.id" + label 'process_low' + + conda "${moduleDir}/environment.yml" + container "${ workflow.containerEngine == 'singularity' && !task.ext.singularity_pull_docker_container ? + 'https://depot.galaxyproject.org/singularity/demuxem:0.1.7.post1--pyhdfd78af_0' : + 'biocontainers/demuxem:0.1.7.post1--pyhdfd78af_0' }" + input: + tuple val(meta), path(input_raw_gene_bc_matrices_h5), path(input_hto_csv_file) + val output_name + val generate_gender_plot + val genome + val generate_diagnostic_plots + output: + tuple val(meta), path("*_demux.zarr.zip"), emit: zarr + tuple val(meta), path("*.out.demuxEM.zarr.zip"), emit: out_zarr + path "versions.yml" , emit: versions + + when: + task.ext.when == null || task.ext.when + + script: + def args = task.ext.args ?: '' + def prefix = task.ext.prefix ?: "${meta.id}" + def generateGenderPlot = generate_gender_plot ? "--generate-gender-plot $generate_gender_plot" : "" + def genome_file = genome ? "--genome $genome" : "" + def diagnostic_plots = generate_diagnostic_plots ? "--generate-diagnostic-plots $generate_diagnostic_plots" : "" + """ + demuxEM $input_raw_gene_bc_matrices_h5 \\ + $input_hto_csv_file $output_name \\ + $args \\ + $generateGenderPlot\\ + $genome_file\\ + $diagnostic_plots + cat <<-END_VERSIONS > versions.yml + "${task.process}":g + echo \$(demuxEM --version 2>&1) + END_VERSIONS + """ + + stub: + def prefix = task.ext.prefix ?: "${meta.id}" + """ + touch ${prefix}.out.demuxEM.zarr.zip + touch ${prefix}_demux.zarr.zip + + cat <<-END_VERSIONS > versions.yml + "${task.process}": + echo \$(demuxEM --version 2>&1) + END_VERSIONS + """ + +} diff --git a/modules/nf-core/demuxem/meta.yml b/modules/nf-core/demuxem/meta.yml new file mode 100644 index 00000000..2fb43f57 --- /dev/null +++ b/modules/nf-core/demuxem/meta.yml @@ -0,0 +1,88 @@ +name: "demuxem" +description: Demultiplexing cell nucleus hashing data, using the estimated antibody + background probability. +keywords: + - demultiplexing + - hashing-based deconvoltion + - single-cell +tools: + - demuxem: + description: "DemuxEM is the demultiplexing module of Pegasus, which works on + cell-hashing and nucleus-hashing genomics data." + homepage: "https://demuxEM.readthedocs.io" + documentation: "https://demuxEM.readthedocs.io" + tool_dev_url: "https://github.com/lilab-bcb/pegasus/tree/master" + doi: "10.1038/s41467-019-10756-2" + licence: ["BSD-3-clause"] + identifier: "" + +input: + - - meta: + type: map + description: | + Groovy Map containing sample information + e.g. [ id:'test' ] + - input_raw_gene_bc_matrices_h5: + type: string + description: | + Path to file containing input raw RNA expression matrix in 10x hdf5 format + pattern: "*.{h5}" + - input_hto_csv_file: + type: string + description: | + Path to file containing input HTO (antibody tag) count matrix in CSV format. + pattern: "*.{csv}" + - - output_name: + type: string + description: | + Output name. All outputs will use it as the prefix. + - - generate_gender_plot: + type: string + description: | + Generate violin plots using gender-specific genes (e.g. Xist). It is a comma-separated list of gene names. + - - genome: + type: string + description: | + Reference genome name. If not provided, the tools infers it from the expression matrix file + - - generate_diagnostic_plots: + type: string + description: | + Generate diagnostic plots, including the background/signal between HTO counts, estimated background probabilities, HTO distributions. +output: + - zarr: + - meta: + type: map + description: | + Groovy Map containing sample information + e.g. [ id:'sample1' ] + - "*_demux.zarr.zip": + type: file + description: | + RNA expression matrix with demultiplexed sample identities in Zarr format. + pattern: "*_demux.zarr.zip" + - out_zarr: + - meta: + type: map + description: | + Groovy Map containing sample information + e.g. [ id:'sample1' ] + - "*.out.demuxEM.zarr.zip": + type: file + description: | + DemuxEM-calculated results in Zarr format, containing two datasets, one for HTO and one for RNA. + pattern: "*.out.demuxEM.zarr.zip" + - versions: + - versions.yml: + type: file + description: File containing software versions + pattern: "versions.yml" +authors: + - "@mari-ga" + - "@maxozo" + - "@wxicu" + - "@Zethson" +maintainers: + - "@mari-ga" + - "@maxozo" + - "@wxicu" + - "@Zethson" diff --git a/modules/nf-core/demuxem/nextflow.config b/modules/nf-core/demuxem/nextflow.config new file mode 100644 index 00000000..e8465283 --- /dev/null +++ b/modules/nf-core/demuxem/nextflow.config @@ -0,0 +1,20 @@ +params { + // Optional parameters for Demuxem - Default values + threads = 1 + genome = null + alpha_on_samples = 0.0 + min_num_genes = 100 + min_num_umis = 100 + min_signal_hashtag = 10.0 + random_state = 0 + +} + + +process { + + withName: DEMUXEM { + ext.args = "--threads ${params.threads} --alpha-on-samples ${params.alpha_on_samples} --min-num-genes ${params.min_num_genes} --min-num-umis ${params.min_num_umis} --min-signal-hashtag ${params.min_signal_hashtag} --random-state ${params.random_state}" + } + +} diff --git a/modules/nf-core/demuxem/tests/main.nf.test b/modules/nf-core/demuxem/tests/main.nf.test new file mode 100644 index 00000000..49b75f70 --- /dev/null +++ b/modules/nf-core/demuxem/tests/main.nf.test @@ -0,0 +1,74 @@ +// nf-core modules test demuxem +nextflow_process { + + name "Test Process DEMUXEM" + script "../main.nf" + process "DEMUXEM" + config "./nextflow.config" + + tag "modules" + tag "modules_nfcore" + tag "demuxem" + + test("Standard_Multiome - h5 - csv") { + + when { + process { + """ + + input[0] = [ + [ id:'sample1'], + file(params.modules_testdata_base_path + "/genomics/homo_sapiens/10xgenomics/cellranger/hashing_demultiplexing/438-21-raw_feature_bc_matrix.h5",checkIfExists: true), + file(params.modules_testdata_base_path + "/genomics/homo_sapiens/10xgenomics/cellranger/hashing_demultiplexing/438_21_raw_HTO.csv",checkIfExists: true) + ] + input[1] = "results" + input[2] = "" + input[3] = "" + input[4] = "" + """ + } + } + + then { + assertAll( + { assert process.success }, + { assert path(process.out.zarr.get(0).get(1)).exists() }, + { assert path(process.out.out_zarr.get(0).get(1)).exists() }, + + ) + } + + } + + test("Standard_Multiome - h5 - csv - stub") { + + options "-stub" + + when { + process { + """ + + input[0] = [ + [ id:'sample1'], + file(params.modules_testdata_base_path + "/genomics/homo_sapiens/10xgenomics/cellranger/hashing_demultiplexing/438-21-raw_feature_bc_matrix.h5",checkIfExists: true), + file(params.modules_testdata_base_path + "/genomics/homo_sapiens/10xgenomics/cellranger/hashing_demultiplexing/438_21_raw_HTO.csv",checkIfExists: true) + ] + input[1] = "results" + input[2] = "" + input[3] = "" + input[4] = "True" + """ + } + } + + then { + assertAll( + { assert process.success }, + { assert path(process.out.zarr.get(0).get(1)).exists() }, + { assert path(process.out.out_zarr.get(0).get(1)).exists() }, + ) + } + + } + +} diff --git a/modules/nf-core/demuxem/tests/main.nf.test.snap b/modules/nf-core/demuxem/tests/main.nf.test.snap new file mode 100644 index 00000000..55b5b72b --- /dev/null +++ b/modules/nf-core/demuxem/tests/main.nf.test.snap @@ -0,0 +1,84 @@ +{ + "Standard_Multiome - h5 - csv - stub": { + "content": [ + { + "0": [ + + ], + "1": [ + + ], + "2": [ + + ], + "out_zarr": [ + + ], + "versions": [ + + ], + "zarr": [ + + ] + } + ], + "meta": { + "nf-test": "0.8.4", + "nextflow": "23.10.1" + }, + "timestamp": "2024-04-18T11:53:38.668726" + }, + "versions": { + "content": [ + [ + + ] + ], + "meta": { + "nf-test": "0.8.4", + "nextflow": "23.10.1" + }, + "timestamp": "2024-04-18T11:35:55.502389" + }, + "Standard_Multiome - h5 - csv": { + "content": [ + { + "0": [ + + ], + "1": [ + + ], + "2": [ + + ], + "out_zarr": [ + + ], + "versions": [ + + ], + "zarr": [ + + ] + } + ], + "meta": { + "nf-test": "0.8.4", + "nextflow": "23.10.1" + }, + "timestamp": "2024-04-18T11:35:55.532153" + }, + "stub-versions": { + "content": [ + [ + + ] + ], + "meta": { + "nf-test": "0.8.4", + "nextflow": "23.10.1" + }, + "timestamp": "2024-04-18T11:53:38.65021" + } +} \ No newline at end of file diff --git a/modules/nf-core/demuxem/tests/nextflow.config b/modules/nf-core/demuxem/tests/nextflow.config new file mode 100644 index 00000000..e8465283 --- /dev/null +++ b/modules/nf-core/demuxem/tests/nextflow.config @@ -0,0 +1,20 @@ +params { + // Optional parameters for Demuxem - Default values + threads = 1 + genome = null + alpha_on_samples = 0.0 + min_num_genes = 100 + min_num_umis = 100 + min_signal_hashtag = 10.0 + random_state = 0 + +} + + +process { + + withName: DEMUXEM { + ext.args = "--threads ${params.threads} --alpha-on-samples ${params.alpha_on_samples} --min-num-genes ${params.min_num_genes} --min-num-umis ${params.min_num_umis} --min-signal-hashtag ${params.min_signal_hashtag} --random-state ${params.random_state}" + } + +} diff --git a/modules/nf-core/gmmdemux/environment.yml b/modules/nf-core/gmmdemux/environment.yml new file mode 100644 index 00000000..1ddb2fa2 --- /dev/null +++ b/modules/nf-core/gmmdemux/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: + - bioconda::gmm-demux=0.2.2.3 diff --git a/modules/nf-core/gmmdemux/main.nf b/modules/nf-core/gmmdemux/main.nf new file mode 100644 index 00000000..6da98713 --- /dev/null +++ b/modules/nf-core/gmmdemux/main.nf @@ -0,0 +1,73 @@ + +process GMMDEMUX { + tag "$meta.id" + label 'process_low' + + conda "${moduleDir}/environment.yml" + container "${ workflow.containerEngine == 'singularity' && !task.ext.singularity_pull_docker_container ? + 'https://depot.galaxyproject.org/singularity/gmm-demux:0.2.2.3--pyh7cba7a3_0': + 'biocontainers/gmm-demux:0.2.2.3--pyh7cba7a3_0' }" + + input: + tuple val(meta), path(hto_matrix), val(hto_names) + val type_report + val summary_report + path skip + path examine + + output: + tuple val(meta), path("barcodes.tsv.gz" ), emit: barcodes + tuple val(meta), path("matrix.mtx.gz" ), emit: matrix + tuple val(meta), path("features.tsv.gz" ), emit: features + tuple val(meta), path("GMM_*.csv" ), emit: classification_report + tuple val(meta), path("GMM_*.config" ), emit: config_report + tuple val(meta), path("summary_report_*.txt"), emit: summary_report, optional: true + path "versions.yml" , emit: versions + + when: + task.ext.when == null || task.ext.when + + script: + def args = task.ext.args ?: '' + def prefix = task.ext.prefix ?: "${meta.id}" + def skip = skip ? "--skip $skip" : "" + def examine_cells = examine ? "--examine $examine" : "" + def VERSION = '0.2.2.3' // WARN: Version information not provided by tool on CLI. Please update version string below when bumping container versions. + def type_report = type_report ? "-f ." : "-s ." + def summary_rep = summary_report ? "-r ${prefix}_summary_report.txt" : "" + """ + if [[ ${summary_report} == true ]]; then + cat /dev/null > ${prefix}_summary_report.txt + fi + + GMM-demux $args \\ + $type_report \\ + $summary_rep \\ + $skip \\ + $examine_cells \\ + $hto_matrix \\ + $hto_names \\ + -o . + + cat <<-END_VERSIONS > versions.yml + "${task.process}": + GMM-Demux: $VERSION + END_VERSIONS + """ + + stub: + def VERSION = '0.2.2.3' + def prefix = task.ext.prefix ?: "${meta.id}" + """ + echo "" | gzip > barcodes.tsv.gz + echo "" | gzip > features.tsv.gz + echo "" | gzip > matrix.mtx.gz + touch GMM_full.config + touch GMM_full.csv + + cat <<-END_VERSIONS > versions.yml + "${task.process}": + GMM-Demux: $VERSION + END_VERSIONS + """ +} diff --git a/modules/nf-core/gmmdemux/meta.yml b/modules/nf-core/gmmdemux/meta.yml new file mode 100644 index 00000000..cca69326 --- /dev/null +++ b/modules/nf-core/gmmdemux/meta.yml @@ -0,0 +1,132 @@ +# yaml-language-server: $schema=https://raw.githubusercontent.com/nf-core/modules/master/modules/meta-schema.json +name: "gmmdemux" + +description: GMM-Demux is a Gaussian-Mixture-Model-based software for processing sample + barcoding data (cell hashing and MULTI-seq). +keywords: + - demultiplexing + - hashing-based deconvolution + - single-cell +tools: + - "gmmdemux": + description: "GMM-Demux is a Gaussian-Mixture-Model-based software for processing + sample barcoding data (cell hashing and MULTI-seq)." + homepage: "https://pypi.org/project/GMM-Demux/" + documentation: "https://github.com/CHPGenetics/GMM-Demux" + tool_dev_url: "https://github.com/CHPGenetics/GMM-demux" + doi: "10.1186/s13059-020-02084-2" + licence: ["MIT"] + identifier: "" + +input: + - - meta: + type: map + description: | + Groovy Map containing sample information + e.g. `[ id:'sample1']` + - hto_matrix: + type: file + description: path to matrix from cell hashing data, the tool receives either + CSV files or TSV, type must be specified using parameters + - hto_names: + type: string + description: | + Comma separated list of HTO names, without whitespace + - - type_report: + type: boolean + description: | + If true, full classification report is generated, otherwise the simplified classification report. + - - summary_report: + type: boolean + description: | + If true, summary report is generated. + - - skip: + type: file + description: | + Load a full classification report and skip the mtx folder as input. Require a path argument. + - - examine: + type: file + description: | + Provide the cell list. Requires a file argument. Only executes if -u is set. +output: + - barcodes: + - meta: + type: map + description: | + Groovy Map containing sample information + e.g. `[ id:'sample1' ]` + - 'barcodes.tsv.gz" ': + type: file + description: | + barcodes tsv file with removed cell-hashing-identifiable multiplets + pattern: "barcodes.tsv.gz" + - matrix: + - meta: + type: map + description: | + Groovy Map containing sample information + e.g. `[ id:'sample1' ]` + - 'matrix.mtx.gz" ': + type: file + description: | + matrix mtx.tsv file with removed cell-hashing-identifiable multiplets + pattern: "matrix.mtx.gz" + - features: + - meta: + type: map + description: | + Groovy Map containing sample information + e.g. `[ id:'sample1' ]` + - 'features.tsv.gz" ': + type: file + description: | + features tsv file with removed cell-hashing-identifiable multiplets + pattern: "features.tsv.gz" + - classification_report: + - meta: + type: map + description: | + Groovy Map containing sample information + e.g. `[ id:'sample1' ]` + - 'GMM_*.csv" ': + type: file + description: | + full or simplified classification report + pattern: "GMM_*.csv" + - config_report: + - meta: + type: map + description: | + Groovy Map containing sample information + e.g. `[ id:'sample1' ]` + - 'GMM_*.config" ': + type: file + description: | + Configuration report mapping the results obtained by the tool to the respective names of the HTOs in the classification report. + pattern: "GMM_*.csv" + - summary_report: + - meta: + type: map + description: | + Groovy Map containing sample information + e.g. `[ id:'sample1' ]` + - summary_report_*.txt: + type: file + description: | + summary report, optional output + pattern: "test/summary_report_*.txt" + - versions: + - versions.yml: + type: file + description: File containing software versions + pattern: "versions.yml" +authors: + - "@mari-ga" + - "@maxozo" + - "@wxicu" + - "@Zethson" +maintainers: + - "@mari-ga" + - "@maxozo" + - "@wxicu" + - "@Zethson" diff --git a/modules/nf-core/gmmdemux/tests/main.nf.test b/modules/nf-core/gmmdemux/tests/main.nf.test new file mode 100644 index 00000000..674a7ce0 --- /dev/null +++ b/modules/nf-core/gmmdemux/tests/main.nf.test @@ -0,0 +1,118 @@ +// nf-core modules test gmmdemux +nextflow_process { + + name "Test Process GMMDEMUX" + script "../main.nf" + process "GMMDEMUX" + + tag "modules" + tag "modules_nfcore" + tag "gmmdemux" + tag "untar" + + + test("Standard_Multiome - 10x mtx - simple") { + setup { + run("UNTAR") { + script "modules/nf-core/untar/main.nf" + process { + """ + input[0] = Channel.of([ [],file(params.modules_testdata_base_path + "/genomics/homo_sapiens/10xgenomics/cellranger/hashing_demultiplexing/hto.tar.gz", checkIfExists: true) ]) + """ + } + } + } + when { + process { + """ + input[0] = UNTAR.out.untar.map { meta, files -> [ [ id:'test', single_end:true ], files, "MS-11,MS-12" ] } + input[1] = false + input[2] = false + input[3] = [] + input[4] = [] + """ + } + } + + then { + assertAll( + { assert process.success }, + { assert path(process.out.matrix.get(0).get(1)).exists() }, + { assert path(process.out.barcodes.get(0).get(1)).exists() }, + { assert path(process.out.features.get(0).get(1)).exists() }, + { assert path(process.out.config_report.get(0).get(1)).exists() }, + + ) + } + } + + test("Standard_Multiome - 10x mtx") { + config "./nextflow.config" + setup { + run("UNTAR") { + script "modules/nf-core/untar/main.nf" + process { + """ + input[0] = Channel.of([ [],file(params.modules_testdata_base_path + "/genomics/homo_sapiens/10xgenomics/cellranger/hashing_demultiplexing/hto.tar.gz", checkIfExists: true) ]) + """ + } + } + } + when { + process { + """ + input[0] = UNTAR.out.untar.map { meta, files -> [ [ id:'test', single_end:true ], files, "MS-11,MS-12" ] } + input[1] = true + input[2] = true + input[3] = [] + input[4] = [] + """ + } + } + + then { + assertAll( + { assert process.success }, + { assert path(process.out.matrix.get(0).get(1)).exists() }, + { assert path(process.out.barcodes.get(0).get(1)).exists() }, + { assert path(process.out.features.get(0).get(1)).exists() }, + { assert path(process.out.config_report.get(0).get(1)).exists() }, + + ) + } + } + + test("Standard_Multiome - 10x mtx - stub") { + config "./nextflow.config" + options "-stub" + + when { + process { + """ + + input[0] = [ + [ id:'test'], + file(params.modules_testdata_base_path + "/genomics/homo_sapiens/10xgenomics/cellranger/hashing_demultiplexing/hto.tar.gz", checkIfExists: true), + "MS-11,MS-12" + ] + input[1] = true + input[2] = true + input[3] = [] + input[4] = [] + """ + } + } + + then { + assertAll( + { assert process.success }, + { assert path(process.out.matrix.get(0).get(1)).exists() }, + { assert path(process.out.barcodes.get(0).get(1)).exists() }, + { assert path(process.out.features.get(0).get(1)).exists() }, + { assert path(process.out.config_report.get(0).get(1)).exists() }, + ) + } + + } + +} diff --git a/modules/nf-core/gmmdemux/tests/main.nf.test.snap b/modules/nf-core/gmmdemux/tests/main.nf.test.snap new file mode 100644 index 00000000..7fc5f03c --- /dev/null +++ b/modules/nf-core/gmmdemux/tests/main.nf.test.snap @@ -0,0 +1,105 @@ +{ + "Standard_Multiome - 10x mtx - stub": { + "content": [ + { + "0": [ + [ + { + "id": "test" + }, + "barcodes.tsv.gz:md5,68b329da9893e34099c7d8ad5cb9c940" + ] + ], + "1": [ + [ + { + "id": "test" + }, + "matrix.mtx.gz:md5,68b329da9893e34099c7d8ad5cb9c940" + ] + ], + "2": [ + [ + { + "id": "test" + }, + "features.tsv.gz:md5,68b329da9893e34099c7d8ad5cb9c940" + ] + ], + "3": [ + [ + { + "id": "test" + }, + "GMM_full.csv:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "4": [ + [ + { + "id": "test" + }, + "GMM_full.config:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "5": [ + + ], + "6": [ + "versions.yml:md5,da37b016c394b4e8cc16afb83e6f4941" + ], + "barcodes": [ + [ + { + "id": "test" + }, + "barcodes.tsv.gz:md5,68b329da9893e34099c7d8ad5cb9c940" + ] + ], + "classification_report": [ + [ + { + "id": "test" + }, + "GMM_full.csv:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "config_report": [ + [ + { + "id": "test" + }, + "GMM_full.config:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "features": [ + [ + { + "id": "test" + }, + "features.tsv.gz:md5,68b329da9893e34099c7d8ad5cb9c940" + ] + ], + "matrix": [ + [ + { + "id": "test" + }, + "matrix.mtx.gz:md5,68b329da9893e34099c7d8ad5cb9c940" + ] + ], + "summary_report": [ + + ], + "versions": [ + "versions.yml:md5,da37b016c394b4e8cc16afb83e6f4941" + ] + } + ], + "meta": { + "nf-test": "0.8.4", + "nextflow": "24.04.3" + }, + "timestamp": "2024-07-30T15:00:17.240999" + } +} \ No newline at end of file diff --git a/modules/nf-core/gmmdemux/tests/nextflow.config b/modules/nf-core/gmmdemux/tests/nextflow.config new file mode 100644 index 00000000..66649e7e --- /dev/null +++ b/modules/nf-core/gmmdemux/tests/nextflow.config @@ -0,0 +1,16 @@ +params{ + // Optional parameters for GMM-demux - Default values + threshold = 0.8 + num_cells = 11089 + examine = "example_cell_types.txt" + ambiguous = 0.05 + + +} + +process { + + withName: GMMDEMUX { + ext.args = "-t ${params.threshold} --summary ${params.num_cells} -a ${params.ambiguous} " + } +} diff --git a/modules/nf-core/popscle/demuxlet/environment.yml b/modules/nf-core/popscle/demuxlet/environment.yml new file mode 100644 index 00000000..1fe132c2 --- /dev/null +++ b/modules/nf-core/popscle/demuxlet/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: + - bioconda::popscle=0.1 diff --git a/modules/nf-core/popscle/demuxlet/main.nf b/modules/nf-core/popscle/demuxlet/main.nf new file mode 100644 index 00000000..a941ebb1 --- /dev/null +++ b/modules/nf-core/popscle/demuxlet/main.nf @@ -0,0 +1,52 @@ +process POPSCLE_DEMUXLET { + tag "$meta.id" + label 'process_medium' + + conda "${moduleDir}/environment.yml" + container "${ workflow.containerEngine == 'singularity' && !task.ext.singularity_pull_docker_container ? + 'https://depot.galaxyproject.org/singularity/popscle:0.1beta--h2c78cec_0' : + 'biocontainers/popscle:0.1beta--h2c78cec_0' }" + + input: + tuple val(meta), val(plp_prefix), path(bam), path(donor_genotype) + + output: + tuple val(meta), path('*.best'), emit: demuxlet_result + path 'versions.yml' , emit: versions + + when: + task.ext.when == null || task.ext.when + + script: + def args = task.ext.args ?: '' + def prefix = task.ext.prefix ?: "${meta.id}" + def input = plp_prefix ? "--plp ${plp_prefix}" : "--sam $bam" + def VERSION = '0.1' // WARN: Version information not provided by tool on CLI. Please update version string below when bumping container versions. + + """ + popscle demuxlet \\ + $input \\ + --vcf ${donor_genotype} \\ + --out $prefix \\ + $args + + cat <<-END_VERSIONS > versions.yml + "${task.process}": + popscle demuxlet: $VERSION + END_VERSIONS + """ + + stub: + def args = task.ext.args ?: '' + def prefix = task.ext.prefix ?: "${meta.id}" + def input = plp_prefix ? "--plp ${plp_prefix}" : "--sam $bam" + def VERSION = '0.1' // WARN: Version information not provided by tool on CLI. Please update version string below when bumping container versions. + """ + touch ${prefix}.best + + cat <<-END_VERSIONS > versions.yml + "${task.process}": + popscle demuxlet: $VERSION + END_VERSIONS + """ +} diff --git a/modules/nf-core/popscle/demuxlet/meta.yml b/modules/nf-core/popscle/demuxlet/meta.yml new file mode 100644 index 00000000..1a8129ae --- /dev/null +++ b/modules/nf-core/popscle/demuxlet/meta.yml @@ -0,0 +1,69 @@ +# yaml-language-server: $schema=https://raw.githubusercontent.com/nf-core/modules/master/modules/meta-schema.json +name: "popscle_demuxlet" +description: Software to deconvolute sample identity and identify multiplets when + multiple samples are pooled by barcoded single cell sequencing and external genotyping + data for each sample is available. +keywords: + - popscle + - demultiplexing + - genotype-based deconvoltion + - single cell +tools: + - "popscle": + description: "A suite of population scale analysis tools for single-cell genomics + data including implementation of Demuxlet / Freemuxlet methods and auxiliary + tools" + homepage: "https://github.com/statgen/popscle" + documentation: "https://github.com/statgen/popscle" + tool_dev_url: "https://github.com/statgen/popscle" + doi: "10.1038/nbt.4042" + licence: ["Apache-2.0"] + identifier: "" + +input: + - - meta: + type: map + description: | + Groovy Map containing sample information + e.g. `[ id:'sample1']` + - plp_prefix: + type: string + description: Prefix of pileup files (CEL,VAR and PLP) produced by popscle/dsc_pileup. + - bam: + type: file + description: Input SAM/BAM/CRAM file without running popscle/dsc_pileup, must + be sorted by coordinates and indexed. + pattern: "*.{bam,cram,sam}" + - donor_genotype: + type: file + description: Input VCF/BCF file, containing the individual genotypes (GT), posterior + probability (GP), or genotype likelihood (PL) to assign each barcode to a + specific sample (or a pair of samples) in the VCF file. + pattern: "*.{vcf,bcf}" +output: + - demuxlet_result: + - meta: + type: map + description: | + Groovy Map containing sample information + e.g. `[ id:'sample1']` + - "*.best": + type: file + description: Result of demuxlet containing the best guess of the sample identity, + with detailed statistics to reach to the best guess. + pattern: "*.best" + - versions: + - versions.yml: + type: file + description: File containing software versions + pattern: "versions.yml" +authors: + - "@mari-ga" + - "@maxozo" + - "@wxicu" + - "@Zethson" +maintainers: + - "@mari-ga" + - "@maxozo" + - "@wxicu" + - "@Zethson" diff --git a/modules/nf-core/popscle/demuxlet/tests/main.nf.test b/modules/nf-core/popscle/demuxlet/tests/main.nf.test new file mode 100644 index 00000000..7a12a0c2 --- /dev/null +++ b/modules/nf-core/popscle/demuxlet/tests/main.nf.test @@ -0,0 +1,104 @@ +// nf-core modules test popscle/demuxlet +nextflow_process { + + name "Test Process POPSCLE_DEMUXLET" + script "../main.nf" + process "POPSCLE_DEMUXLET" + config "./nextflow.config" + tag "modules" + tag "modules_nfcore" + tag "popscle" + tag "popscle/dscpileup" + tag "popscle/demuxlet" + + test("demultiplexing - bam") { + when { + process { + """ + input[0] = [ + [ id:'sample1'], + [], + file(params.modules_testdata_base_path + '/genomics/homo_sapiens/demultiplexing/chr21.bam', checkIfExists: true), + file(params.modules_testdata_base_path + '/genomics/homo_sapiens/demultiplexing/donor_genotype_chr21.vcf', checkIfExists: true) + ] + """ + } + } + + then { + assertAll( + { assert process.success }, + { assert snapshot(process.out.versions).match("versions") }, + { assert path(process.out.demuxlet_result.get(0).get(1)).exists() }, + ) + } + + } + + test ("demultiplexing - bam - pileup"){ + setup { + run("POPSCLE_DSCPILEUP") { + script "../../dscpileup/main.nf" + process { + """ + input[0] = [ + [ id:'sample1' ], + file(params.modules_testdata_base_path + '/genomics/homo_sapiens/demultiplexing/chr21.bam', checkIfExists: true), + file(params.modules_testdata_base_path + '/genomics/homo_sapiens/demultiplexing/donor_genotype_chr21.vcf', checkIfExists: true), + ] + """ + } + } + } + + when { + process { + """ + input[0] = POPSCLE_DSCPILEUP.out.plp.collect{ meta, plp -> plp }.map{ + plp -> [[ id: 'sample1'], + plp[0].toString() - '.plp.gz', + [], + file(params.modules_testdata_base_path + '/genomics/homo_sapiens/demultiplexing/donor_genotype_chr21.vcf', checkIfExists: true)]} + """ + } + } + + then { + assertAll( + { assert process.success }, + { assert snapshot(process.out.versions).match("pileup-versions") }, + { assert path(process.out.demuxlet_result.get(0).get(1)).exists() }, + ) + } + + } + + test("demultiplexing - bam - stub") { + + options "-stub" + + when { + process { + """ + input[0] = [ + [ id:'sample1' ], + [], + file(params.modules_testdata_base_path + '/genomics/homo_sapiens/demultiplexing/chr21.bam', checkIfExists: true), + file(params.modules_testdata_base_path + '/genomics/homo_sapiens/demultiplexing/donor_genotype_chr21.vcf', checkIfExists: true), + ] + """ + } + } + + then { + assertAll( + { assert process.success }, + { assert snapshot(process.out.versions).match("stub-versions") }, + { assert path(process.out.demuxlet_result.get(0).get(1)).exists() }, + ) + } + + } + + +} diff --git a/modules/nf-core/popscle/demuxlet/tests/main.nf.test.snap b/modules/nf-core/popscle/demuxlet/tests/main.nf.test.snap new file mode 100644 index 00000000..8b6a9360 --- /dev/null +++ b/modules/nf-core/popscle/demuxlet/tests/main.nf.test.snap @@ -0,0 +1,38 @@ +{ + "versions": { + "content": [ + [ + "versions.yml:md5,0d1ca21adff5837cfe97c3ffd2ccca4a" + ] + ], + "meta": { + "nf-test": "0.8.4", + "nextflow": "23.10.1" + }, + "timestamp": "2024-04-09T14:52:27.280123424" + }, + "stub-versions": { + "content": [ + [ + "versions.yml:md5,0d1ca21adff5837cfe97c3ffd2ccca4a" + ] + ], + "meta": { + "nf-test": "0.8.4", + "nextflow": "23.10.1" + }, + "timestamp": "2024-04-09T14:55:12.24967796" + }, + "pileup-versions": { + "content": [ + [ + "versions.yml:md5,0d1ca21adff5837cfe97c3ffd2ccca4a" + ] + ], + "meta": { + "nf-test": "0.8.4", + "nextflow": "23.10.1" + }, + "timestamp": "2024-04-09T14:54:16.248941393" + } +} \ No newline at end of file diff --git a/modules/nf-core/popscle/demuxlet/tests/nextflow.config b/modules/nf-core/popscle/demuxlet/tests/nextflow.config new file mode 100644 index 00000000..165a21cf --- /dev/null +++ b/modules/nf-core/popscle/demuxlet/tests/nextflow.config @@ -0,0 +1,5 @@ +process { + withName: POPSCLE_DEMUXLET { + ext.args = '--field GT' + } +} diff --git a/modules/nf-core/popscle/freemuxlet/environment.yml b/modules/nf-core/popscle/freemuxlet/environment.yml new file mode 100644 index 00000000..1fe132c2 --- /dev/null +++ b/modules/nf-core/popscle/freemuxlet/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: + - bioconda::popscle=0.1 diff --git a/modules/nf-core/popscle/freemuxlet/main.nf b/modules/nf-core/popscle/freemuxlet/main.nf new file mode 100644 index 00000000..d94d36a1 --- /dev/null +++ b/modules/nf-core/popscle/freemuxlet/main.nf @@ -0,0 +1,61 @@ +process POPSCLE_FREEMUXLET { + tag "$meta.id" + label 'process_high' + + conda "${moduleDir}/environment.yml" + container "${ workflow.containerEngine == 'singularity' && !task.ext.singularity_pull_docker_container ? + 'https://depot.galaxyproject.org/singularity/popscle:0.1beta--h2c78cec_0' : + 'biocontainers/popscle:0.1beta--h2c78cec_0' }" + + input: + tuple val(meta), path(plp), val(n_sample) + + output: + tuple val(meta), path('*.clust1.samples.gz') , emit: result + tuple val(meta), path('*.clust1.vcf.gz') , emit: vcf + tuple val(meta), path('*.lmix') , emit: lmix + tuple val(meta), path('*.clust0.samples.gz') , emit: singlet_result , optional: true + tuple val(meta), path('*.clust0.vcf.gz') , emit: singlet_vcf , optional: true + path 'versions.yml' , emit: versions + + when: + task.ext.when == null || task.ext.when + + script: + def args = task.ext.args ?: '' + def prefix = task.ext.prefix ?: "${meta.id}" + def VERSION = '0.1' // WARN: Version information not provided by tool on CLI. Please update version string below when bumping container versions. + """ + popscle freemuxlet \\ + --plp ${plp}/$prefix \\ + --out $prefix \\ + --nsample $n_sample \\ + $args + + cat <<-END_VERSIONS > versions.yml + "${task.process}": + popscle: $VERSION + END_VERSIONS + """ + + stub: + def args = task.ext.args ?: '' + def prefix = task.ext.prefix ?: "${meta.id}" + def VERSION = '0.1' // WARN: Version information not provided by tool on CLI. Please update version string below when bumping container versions. + + """ + touch ${prefix}.clust1.samples.gz + touch ${prefix}.clust1.vcf.gz + touch ${prefix}.lmix + + if [[ "$args" == *"--aux-files"* ]]; then + touch ${prefix}.clust0.samples.gz + touch ${prefix}.clust0.vcf.gz + fi + + cat <<-END_VERSIONS > versions.yml + "${task.process}": + popscle: $VERSION + END_VERSIONS + """ +} diff --git a/modules/nf-core/popscle/freemuxlet/meta.yml b/modules/nf-core/popscle/freemuxlet/meta.yml new file mode 100644 index 00000000..ff28e1dc --- /dev/null +++ b/modules/nf-core/popscle/freemuxlet/meta.yml @@ -0,0 +1,98 @@ +# yaml-language-server: $schema=https://raw.githubusercontent.com/nf-core/modules/master/modules/meta-schema.json +name: "popscle_freemuxlet" +description: Software to deconvolute sample identity and identify multiplets when + multiple samples are pooled by barcoded single cell sequencing and external genotyping + data for each sample is not available. +keywords: + - popscle + - demultiplexing + - genotype-based deconvoltion + - single cell +tools: + - "popscle": + description: "A suite of population scale analysis tools for single-cell genomics + data including implementation of Demuxlet / Freemuxlet methods and auxiliary + tools" + homepage: "https://github.com/statgen/popscle" + documentation: "https://github.com/statgen/popscle" + tool_dev_url: "https://github.com/statgen/popscle" + doi: "10.1038/nbt.4042" + licence: ["Apache-2.0"] + identifier: "" + +input: + - - meta: + type: map + description: | + Groovy Map containing sample information + e.g. `[ id:'sample1']` + - plp: + type: directory + description: Directory contains pileup files (CEL,VAR and PLP) produced by popscle/dsc_pileup. + - n_sample: + type: integer + description: Number of samples multiplexed together. +output: + - result: + - meta: + type: map + description: | + Groovy Map containing sample information + e.g. `[ id:'sample1', single_end:false ]` + - "*.clust1.samples.gz": + type: file + description: Output file contains the best guess of the sample identity, with + detailed statistics to reach to the best guess. + pattern: "*.clust1.samples.gz" + - vcf: + - meta: + type: map + description: | + Groovy Map containing sample information + e.g. `[ id:'sample1', single_end:false ]` + - "*.clust1.vcf.gz": + type: file + description: Output vcf file for each sample inferred and clustered from freemuxlet. + pattern: "*.clust1.vcf.gz" + - lmix: + - meta: + type: map + description: | + Groovy Map containing sample information + e.g. `[ id:'sample1', single_end:false ]` + - "*.lmix": + type: file + description: Output file contains basic statistics for each barcode. + pattern: "*.lmix" + - singlet_result: + - meta: + type: map + description: | + Groovy Map containing sample information + e.g. `[ id:'sample1', single_end:false ]` + - "*.clust0.samples.gz": + type: file + description: Optional output file contains the best sample identity assuming + all droplets are singlets when writing auxiliary output files is turned on. + pattern: "*.clust0.samples.gz" + - singlet_vcf: + - meta: + type: map + description: | + Groovy Map containing sample information + e.g. `[ id:'sample1', single_end:false ]` + - "*.clust0.vcf.gz": + type: file + description: Optional output vcf file for each sample inferred and clustered + from freemuxlet assuming all droplets are singlets when writing auxiliary + output files is turned on. + pattern: "*.clust0.vcf.gz" + - versions: + - versions.yml: + type: file + description: File containing software versions + pattern: "versions.yml" +authors: + - "@wxicu" +maintainers: + - "@wxicu" diff --git a/modules/nf-core/popscle/freemuxlet/tests/main.nf.test b/modules/nf-core/popscle/freemuxlet/tests/main.nf.test new file mode 100644 index 00000000..baf642e5 --- /dev/null +++ b/modules/nf-core/popscle/freemuxlet/tests/main.nf.test @@ -0,0 +1,183 @@ +nextflow_process { + + name "Test Process POPSCLE_FREEMUXLET" + script "../main.nf" + process "POPSCLE_FREEMUXLET" + + tag "modules" + tag "modules_nfcore" + tag "popscle" + tag "popscle/dscpileup" + tag "popscle/freemuxlet" + + test("demultiplexing") { + setup { + run("POPSCLE_DSCPILEUP") { + script "../../dscpileup/main.nf" + process { + """ + input[0] = [ + [ id:'sample1' ], + file(params.modules_testdata_base_path + '/genomics/homo_sapiens/demultiplexing/chr21.bam', checkIfExists: true), + file(params.modules_testdata_base_path + '/genomics/homo_sapiens/demultiplexing/donor_genotype_chr21.vcf', checkIfExists: true), + ] + """ + } + } + } + + when { + process { + """ + input[0] = POPSCLE_DSCPILEUP.out.plp.collect{ meta, plp -> plp }.map{ + plp -> [[ id: 'sample1'], + plp[0].getParent(), + 2 ]} + """ + } + } + + + then { + assertAll( + { assert process.success }, + { assert snapshot(process.out.versions).match("versions") }, + { assert path(process.out.result.get(0).get(1)).exists() }, + { assert path(process.out.vcf.get(0).get(1)).exists() }, + { assert path(process.out.lmix.get(0).get(1)).exists() } + ) + } + + } + + test("demultiplexing - auxiliary - files") { + config "./nextflow.config" + setup { + run("POPSCLE_DSCPILEUP") { + script "../../dscpileup/main.nf" + process { + """ + input[0] = [ + [ id:'sample1' ], + file(params.modules_testdata_base_path + '/genomics/homo_sapiens/demultiplexing/chr21.bam', checkIfExists: true), + file(params.modules_testdata_base_path + '/genomics/homo_sapiens/demultiplexing/donor_genotype_chr21.vcf', checkIfExists: true), + ] + """ + } + } + } + + when { + process { + """ + input[0] = POPSCLE_DSCPILEUP.out.plp.collect{ meta, plp -> plp }.map{ + plp -> [[ id: 'sample1'], + plp[0].getParent(), + 2 ]} + """ + } + } + + + then { + assertAll( + { assert process.success }, + { assert snapshot(process.out.versions).match("versions-aux-files") }, + { assert path(process.out.result.get(0).get(1)).exists() }, + { assert path(process.out.vcf.get(0).get(1)).exists() }, + { assert path(process.out.lmix.get(0).get(1)).exists() }, + { assert path(process.out.singlet_result.get(0).get(1)).exists() }, + { assert path(process.out.singlet_vcf.get(0).get(1)).exists() } + ) + } + + } + + test("demultiplexing - stub") { + + options "-stub" + + setup { + run("POPSCLE_DSCPILEUP") { + script "../../dscpileup/main.nf" + process { + """ + input[0] = [ + [ id:'sample1' ], + file(params.modules_testdata_base_path + '/genomics/homo_sapiens/demultiplexing/chr21.bam', checkIfExists: true), + file(params.modules_testdata_base_path + '/genomics/homo_sapiens/demultiplexing/donor_genotype_chr21.vcf', checkIfExists: true), + ] + """ + } + } + } + + when { + process { + """ + input[0] = POPSCLE_DSCPILEUP.out.plp.collect{ meta, plp -> plp }.map{ + plp -> [[ id: 'sample1'], + plp[0].toString() - '.plp.gz', + 2 ]} + """ + } + } + + then { + assertAll( + { assert process.success }, + { assert snapshot(process.out.versions).match("versions-stub") }, + { assert path(process.out.result.get(0).get(1)).exists() }, + { assert path(process.out.vcf.get(0).get(1)).exists() }, + { assert path(process.out.lmix.get(0).get(1)).exists() } + ) + } + + } + + test("demultiplexing - auxiliary - files - stub") { + + options "-stub" + config "./nextflow.config" + + setup { + run("POPSCLE_DSCPILEUP") { + script "../../dscpileup/main.nf" + process { + """ + input[0] = [ + [ id:'sample1' ], + file(params.modules_testdata_base_path + '/genomics/homo_sapiens/demultiplexing/chr21.bam', checkIfExists: true), + file(params.modules_testdata_base_path + '/genomics/homo_sapiens/demultiplexing/donor_genotype_chr21.vcf', checkIfExists: true), + ] + """ + } + } + } + + when { + process { + """ + input[0] = POPSCLE_DSCPILEUP.out.plp.collect{ meta, plp -> plp }.map{ + plp -> [[ id: 'sample1'], + plp[0].toString() - '.plp.gz', + 2 ]} + """ + } + } + + then { + assertAll( + { assert process.success }, + { assert snapshot(process.out.versions).match("versions-aux-files-stub") }, + { assert path(process.out.result.get(0).get(1)).exists() }, + { assert path(process.out.vcf.get(0).get(1)).exists() }, + { assert path(process.out.lmix.get(0).get(1)).exists() }, + { assert path(process.out.singlet_result.get(0).get(1)).exists() }, + { assert path(process.out.singlet_vcf.get(0).get(1)).exists() } + ) + } + + } + +} diff --git a/modules/nf-core/popscle/freemuxlet/tests/main.nf.test.snap b/modules/nf-core/popscle/freemuxlet/tests/main.nf.test.snap new file mode 100644 index 00000000..b2502c91 --- /dev/null +++ b/modules/nf-core/popscle/freemuxlet/tests/main.nf.test.snap @@ -0,0 +1,50 @@ +{ + "versions": { + "content": [ + [ + "versions.yml:md5,8d90ac14cd657f4c831007418a55910d" + ] + ], + "meta": { + "nf-test": "0.8.4", + "nextflow": "23.10.1" + }, + "timestamp": "2024-04-25T12:49:23.684387488" + }, + "versions-aux-files": { + "content": [ + [ + "versions.yml:md5,8d90ac14cd657f4c831007418a55910d" + ] + ], + "meta": { + "nf-test": "0.8.4", + "nextflow": "23.10.1" + }, + "timestamp": "2024-04-25T12:49:35.355844696" + }, + "versions-aux-files-stub": { + "content": [ + [ + "versions.yml:md5,8d90ac14cd657f4c831007418a55910d" + ] + ], + "meta": { + "nf-test": "0.8.4", + "nextflow": "23.10.1" + }, + "timestamp": "2024-04-25T12:49:56.179166814" + }, + "versions-stub": { + "content": [ + [ + "versions.yml:md5,8d90ac14cd657f4c831007418a55910d" + ] + ], + "meta": { + "nf-test": "0.8.4", + "nextflow": "23.10.1" + }, + "timestamp": "2024-04-25T12:49:45.801327201" + } +} \ No newline at end of file diff --git a/modules/nf-core/popscle/freemuxlet/tests/nextflow.config b/modules/nf-core/popscle/freemuxlet/tests/nextflow.config new file mode 100644 index 00000000..55d2a6d1 --- /dev/null +++ b/modules/nf-core/popscle/freemuxlet/tests/nextflow.config @@ -0,0 +1,5 @@ +process { + withName: POPSCLE_FREEMUXLET { + ext.args = '--aux-files' + } +} diff --git a/modules/nf-core/vireo/environment.yml b/modules/nf-core/vireo/environment.yml new file mode 100644 index 00000000..7a537ff7 --- /dev/null +++ b/modules/nf-core/vireo/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: + - bioconda::vireosnp=0.5.8 diff --git a/modules/nf-core/vireo/main.nf b/modules/nf-core/vireo/main.nf new file mode 100644 index 00000000..2319a5f5 --- /dev/null +++ b/modules/nf-core/vireo/main.nf @@ -0,0 +1,62 @@ +process VIREO { + tag "$meta.id" + label 'process_medium' + + conda "${moduleDir}/environment.yml" + container "${ workflow.containerEngine == 'singularity' && !task.ext.singularity_pull_docker_container ? + 'https://depot.galaxyproject.org/singularity/vireosnp:0.5.8--pyh7cba7a3_0' : + 'biocontainers/vireosnp:0.5.8--pyh7cba7a3_0' }" + + input: + tuple val(meta), path(cell_data), val(n_donor), path(donor_file), path(vartrix_data) + output: + tuple val(meta), path('*_summary.tsv') , emit: summary + tuple val(meta), path('*_donor_ids.tsv') , emit: donor_ids + tuple val(meta), path('*_prob_singlet.tsv.gz'), emit: prob_singlets + tuple val(meta), path('*_prob_doublet.tsv.gz'), emit: prob_doublets + path 'versions.yml' , emit: versions + + when: + task.ext.when == null || task.ext.when + + script: + def args = task.ext.args ?: '' + def prefix = task.ext.prefix ?: "${meta.id}" + def input = cell_data ? "-c ${cell_data}" : "--vartrixData ${vartrix_data}" + + """ + vireo \\ + $input \\ + -N ${n_donor} \\ + -d ${donor_file} \\ + -p $task.cpus \\ + -o . \\ + $args + + mv summary.tsv ${prefix}_summary.tsv + mv donor_ids.tsv ${prefix}_donor_ids.tsv + mv prob_singlet.tsv.gz ${prefix}_prob_singlet.tsv.gz + mv prob_doublet.tsv.gz ${prefix}_prob_doublet.tsv.gz + + cat <<-END_VERSIONS > versions.yml + "${task.process}": + vireo: \$(vireo | sed '1!d ; s/Welcome to vireoSNP //; s/!//') + END_VERSIONS + """ + + stub: + def args = task.ext.args ?: '' + def prefix = task.ext.prefix ?: "${meta.id}" + + """ + touch ${prefix}_summary.tsv + touch ${prefix}_donor_ids.tsv + echo "" | gzip > ${prefix}_prob_singlet.tsv.gz + echo "" | gzip > ${prefix}_prob_doublet.tsv.gz + + cat <<-END_VERSIONS > versions.yml + "${task.process}": + vireo: \$(vireo | sed '1!d ; s/Welcome to vireoSNP //; s/!//') + END_VERSIONS + """ +} diff --git a/modules/nf-core/vireo/meta.yml b/modules/nf-core/vireo/meta.yml new file mode 100644 index 00000000..77e5fa42 --- /dev/null +++ b/modules/nf-core/vireo/meta.yml @@ -0,0 +1,95 @@ +# yaml-language-server: $schema=https://raw.githubusercontent.com/nf-core/modules/master/modules/meta-schema.json +name: "vireo" +description: Use vireo to perform donor deconvolution for multiplexed scRNA-seq data +keywords: + - genotype-based demultiplexing + - donor deconvolution + - cellsnp +tools: + - "vireo": + description: "vireoSNP - donor deconvolution for multiplexed scRNA-seq data" + homepage: "https://vireosnp.readthedocs.io/en/latest/" + documentation: "https://vireosnp.readthedocs.io/en/latest/" + tool_dev_url: "https://github.com/single-cell-genetics/vireo" + doi: "10.1186/s13059-019-1865-2" + licence: ["Apache-2.0"] + identifier: biotools:Vireo + +input: + - - meta: + type: map + description: | + Groovy Map containing sample information + e.g. `[ id:'sample1']` + - cell_data: + type: file + description: The cell genotype file in VCF format or cellSNP folder with sparse + matrices. + pattern: "*.vcf|*/" + - n_donor: + type: integer + description: Number of donors to demultiplex. + - donor_file: + type: file + description: The optional donor genotype file in VCF format. + pattern: "*.vcf" + - vartrix_data: + type: file + description: The optional cell genotype files in vartrix outputs. +output: + - summary: + - meta: + type: map + description: | + Groovy Map containing sample information + e.g. `[ id:'sample1' ]` + - "*_summary.tsv": + type: file + description: Summary tsv file of deconvolution result. + pattern: "*_summary.tsv" + - donor_ids: + - meta: + type: map + description: | + Groovy Map containing sample information + e.g. `[ id:'sample1' ]` + - "*_donor_ids.tsv": + type: file + description: Donor assignment with detailed statistics. + pattern: "*_donor_ids.tsv" + - prob_singlets: + - meta: + type: map + description: | + Groovy Map containing sample information + e.g. `[ id:'sample1' ]` + - "*_prob_singlet.tsv.gz": + type: file + description: contains probability of classifing singlets + pattern: "*_prob_singlet.tsv.gz" + - prob_doublets: + - meta: + type: map + description: | + Groovy Map containing sample information + e.g. `[ id:'sample1' ]` + - "*_prob_doublet.tsv.gz": + type: file + description: contains probability of classifing doublets + pattern: "*_prob_doublet.tsv.gz" + - versions: + - versions.yml: + type: file + description: File containing software versions + pattern: "versions.yml" +authors: + - "@mari-ga" + - "@maxozo" + - "@wxicu" + - "@Zethson" + +maintainers: + - "@mari-ga" + - "@maxozo" + - "@wxicu" + - "@Zethson" diff --git a/modules/nf-core/vireo/tests/main.nf.test b/modules/nf-core/vireo/tests/main.nf.test new file mode 100644 index 00000000..86ba742b --- /dev/null +++ b/modules/nf-core/vireo/tests/main.nf.test @@ -0,0 +1,90 @@ +// nf-core modules test vireo +nextflow_process { + + name "Test Process VIREO" + script "../main.nf" + process "VIREO" + config "./nextflow.config" + + tag "modules" + tag "modules_nfcore" + tag "vireo" + tag "samtools/index" + tag "cellsnp/modea" + + test("demultiplexing") { + setup { + run("SAMTOOLS_INDEX") { + script "../../samtools/index/main.nf" + process { + """ + input[0] = [ + [ id:'sample1' ], + file(params.modules_testdata_base_path + '/genomics/homo_sapiens/demultiplexing/chr21.bam', checkIfExists: true) ] + """ + } + } + run("CELLSNP_MODEA") { + script "../../cellsnp/modea/main.nf" + process { + """ + input[0] = SAMTOOLS_INDEX.out.bai.collect{ meta, bai -> bai }.map{ + bai -> [[id: 'sample1'], + file(params.modules_testdata_base_path + '/genomics/homo_sapiens/demultiplexing/chr21.bam', checkIfExists: true), + bai, + file(params.modules_testdata_base_path + '/genomics/homo_sapiens/demultiplexing/donor_genotype_chr21.vcf', checkIfExists: true), + file(params.modules_testdata_base_path + '/genomics/homo_sapiens/demultiplexing/barcodes.tsv', checkIfExists: true)]} + """ + } + } + } + + when { + process { + """ + input[0] = CELLSNP_MODEA.out.cell.collect{ meta, cell -> cell }.map{ + cell -> [[id:'test'], + cell, + 2, + file(params.modules_testdata_base_path + '/genomics/homo_sapiens/demultiplexing/donor_genotype_chr21.vcf', checkIfExists: true), + [] + ]} + """ + } + } + + then { + assertAll( + { assert process.success }, + { assert snapshot(process.out).match() } + ) + } + + } + + test("demultiplexing - stub") { + options "-stub" + + when { + process { + """ + input[0] = [[id:'sample1'], + [], + 2, + file(params.modules_testdata_base_path + '/genomics/homo_sapiens/demultiplexing/donor_genotype_chr21.vcf', checkIfExists: true), + [] + ] + """ + } + } + + then { + assertAll( + { assert process.success }, + { assert snapshot(process.out).match() } + ) + } + + } + +} diff --git a/modules/nf-core/vireo/tests/main.nf.test.snap b/modules/nf-core/vireo/tests/main.nf.test.snap new file mode 100644 index 00000000..15ccfd88 --- /dev/null +++ b/modules/nf-core/vireo/tests/main.nf.test.snap @@ -0,0 +1,164 @@ +{ + "demultiplexing - stub": { + "content": [ + { + "0": [ + [ + { + "id": "sample1" + }, + "sample1_summary.tsv:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "1": [ + [ + { + "id": "sample1" + }, + "sample1_donor_ids.tsv:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "2": [ + [ + { + "id": "sample1" + }, + "sample1_prob_singlet.tsv.gz:md5,68b329da9893e34099c7d8ad5cb9c940" + ] + ], + "3": [ + [ + { + "id": "sample1" + }, + "sample1_prob_doublet.tsv.gz:md5,68b329da9893e34099c7d8ad5cb9c940" + ] + ], + "4": [ + "versions.yml:md5,b16f367dd80f537073c53ccaf9fd547d" + ], + "donor_ids": [ + [ + { + "id": "sample1" + }, + "sample1_donor_ids.tsv:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "prob_doublets": [ + [ + { + "id": "sample1" + }, + "sample1_prob_doublet.tsv.gz:md5,68b329da9893e34099c7d8ad5cb9c940" + ] + ], + "prob_singlets": [ + [ + { + "id": "sample1" + }, + "sample1_prob_singlet.tsv.gz:md5,68b329da9893e34099c7d8ad5cb9c940" + ] + ], + "summary": [ + [ + { + "id": "sample1" + }, + "sample1_summary.tsv:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "versions": [ + "versions.yml:md5,b16f367dd80f537073c53ccaf9fd547d" + ] + } + ], + "meta": { + "nf-test": "0.8.4", + "nextflow": "23.10.1" + }, + "timestamp": "2024-05-08T22:38:03.769285328" + }, + "demultiplexing": { + "content": [ + { + "0": [ + [ + { + "id": "test" + }, + "test_summary.tsv:md5,94b0ce40cc86bbe7fbacebf98e685ee7" + ] + ], + "1": [ + [ + { + "id": "test" + }, + "test_donor_ids.tsv:md5,2d3eadd8d1a3f26428b383e7a98d2ced" + ] + ], + "2": [ + [ + { + "id": "test" + }, + "test_prob_singlet.tsv.gz:md5,d1e6d055fda3c96713912da79994b48c" + ] + ], + "3": [ + [ + { + "id": "test" + }, + "test_prob_doublet.tsv.gz:md5,6d7e979931de2b66b89618be47fc079c" + ] + ], + "4": [ + "versions.yml:md5,b16f367dd80f537073c53ccaf9fd547d" + ], + "donor_ids": [ + [ + { + "id": "test" + }, + "test_donor_ids.tsv:md5,2d3eadd8d1a3f26428b383e7a98d2ced" + ] + ], + "prob_doublets": [ + [ + { + "id": "test" + }, + "test_prob_doublet.tsv.gz:md5,6d7e979931de2b66b89618be47fc079c" + ] + ], + "prob_singlets": [ + [ + { + "id": "test" + }, + "test_prob_singlet.tsv.gz:md5,d1e6d055fda3c96713912da79994b48c" + ] + ], + "summary": [ + [ + { + "id": "test" + }, + "test_summary.tsv:md5,94b0ce40cc86bbe7fbacebf98e685ee7" + ] + ], + "versions": [ + "versions.yml:md5,b16f367dd80f537073c53ccaf9fd547d" + ] + } + ], + "meta": { + "nf-test": "0.8.4", + "nextflow": "23.10.1" + }, + "timestamp": "2024-05-08T22:35:04.435257747" + } +} \ No newline at end of file diff --git a/modules/nf-core/vireo/tests/nextflow.config b/modules/nf-core/vireo/tests/nextflow.config new file mode 100644 index 00000000..482664d2 --- /dev/null +++ b/modules/nf-core/vireo/tests/nextflow.config @@ -0,0 +1,5 @@ +process { + withName: CELLSNP_MODEA { + ext.args = '--genotype' + } +} From e4094674ad0880861f8b2aa4464ca7a993bf842b Mon Sep 17 00:00:00 2001 From: nictru Date: Fri, 13 Jun 2025 16:58:30 +0200 Subject: [PATCH 12/74] Simplify raw/filtered matrix treatment --- assets/schema_input.json | 29 +++++++------------ .../local/utils_nfcore_hadge_pipeline/main.nf | 10 +++---- 2 files changed, 16 insertions(+), 23 deletions(-) diff --git a/assets/schema_input.json b/assets/schema_input.json index a6984089..29b2f4d6 100644 --- a/assets/schema_input.json +++ b/assets/schema_input.json @@ -13,38 +13,27 @@ "errorMessage": "Sample name must be provided and cannot contain spaces", "meta": ["id"] }, - "rna_matrix_raw": { + "rna_matrix": { "type": "string", "format": "directory-path", "exists": true, + "default": null, "pattern": "^\\S+$", - "errorMessage": "RNA matrix raw must be provided, cannot contain spaces and must have extension '.mtx'" + "errorMessage": "RNA matrix must be provided and cannot contain spaces" }, - "rna_matrix_filtered": { + "hto_matrix": { "type": "string", "format": "directory-path", "exists": true, + "default": null, "pattern": "^\\S+$", - "errorMessage": "RNA matrix filtered must be provided, cannot contain spaces and must have extension '.mtx'" - }, - "hto_matrix_raw": { - "type": "string", - "format": "directory-path", - "exists": true, - "pattern": "^\\S+$", - "errorMessage": "HTO matrix raw must be provided, cannot contain spaces and must have extension '.mtx'" - }, - "hto_matrix_filtered": { - "type": "string", - "format": "directory-path", - "exists": true, - "pattern": "^\\S+$", - "errorMessage": "HTO matrix filtered must be provided, cannot contain spaces and must have extension '.mtx'" + "errorMessage": "HTO matrix must be provided and cannot contain spaces" }, "bam": { "type": "string", "format": "file-path", "exists": true, + "default": null, "pattern": "^\\S+\\.bam$", "errorMessage": "BAM file must be provided, cannot contain spaces and must have extension '.bam'" }, @@ -52,6 +41,7 @@ "type": "string", "format": "file-path", "exists": true, + "default": null, "pattern": "^\\S+\\.bam\\.bai$", "errorMessage": "BAM index file must be provided, cannot contain spaces and must have extension '.bam.bai'" }, @@ -59,6 +49,7 @@ "type": "string", "format": "file-path", "exists": true, + "default": null, "pattern": "^\\S+\\.tsv$", "errorMessage": "Barcodes file must be provided, cannot contain spaces and must have extension '.tsv'" }, @@ -71,6 +62,7 @@ "type": "string", "format": "file-path", "exists": true, + "default": null, "pattern": "^\\S+\\.vcf$", "errorMessage": "VCF file must be provided, cannot contain spaces and must have extension '.vcf'" }, @@ -78,6 +70,7 @@ "type": "string", "format": "file-path", "exists": true, + "default": null, "pattern": "^\\S+\\.vcf$", "errorMessage": "VCF file must be provided, cannot contain spaces and must have extension '.vcf'" } diff --git a/subworkflows/local/utils_nfcore_hadge_pipeline/main.nf b/subworkflows/local/utils_nfcore_hadge_pipeline/main.nf index a701a689..a88e06fe 100644 --- a/subworkflows/local/utils_nfcore_hadge_pipeline/main.nf +++ b/subworkflows/local/utils_nfcore_hadge_pipeline/main.nf @@ -145,11 +145,11 @@ def validateInputParameters() { // Validate channels from input samplesheet // def validateInputSamplesheet(input) { - def (_meta, rna_matrix_raw, rna_matrix_filtered, hto_matrix_raw, hto_matrix_filtered, bam, bai, barcodes, nsample, cell_data, vcf_mixed, vcf_donor) = input + def (_meta, rna_matrix, hto_matrix, bam, bai, barcodes, nsample, cell_data, vcf_mixed, vcf_donor) = input if (params.mode == 'hashing') { - if (rna_matrix_raw == null || rna_matrix_filtered == null || hto_matrix_raw == null || hto_matrix_filtered == null) { - error("RNA matrix raw, RNA matrix filtered, HTO matrix raw, and HTO matrix filtered must be provided for hashing mode. Please check your input samplesheet.") + if (rna_matrix == null || hto_matrix == null) { + error("RNA matrix and HTO matrix must be provided for hashing mode. Please check your input samplesheet.") } } else if (params.mode == 'genetic') { @@ -158,8 +158,8 @@ def validateInputSamplesheet(input) { } } else if (params.mode == 'rescue') { - if (rna_matrix_raw == null || rna_matrix_filtered == null || hto_matrix_raw == null || hto_matrix_filtered == null || bam == null || bai == null || barcodes == null || nsample == null || cell_data == null || vcf_mixed == null || vcf_donor == null) { - error("RNA matrix raw, RNA matrix filtered, HTO matrix raw, HTO matrix filtered, BAM file, BAM index file, barcodes file, number of samples, cell data, VCF file for mixed samples, and VCF file for donor samples must be provided for rescue mode. Please check your input samplesheet.") + if (rna_matrix == null || hto_matrix == null || bam == null || bai == null || barcodes == null || nsample == null || cell_data == null || vcf_mixed == null || vcf_donor == null) { + error("RNA matrix, HTO matrix, BAM file, BAM index file, barcodes file, number of samples, cell data, VCF file for mixed samples, and VCF file for donor samples must be provided for rescue mode. Please check your input samplesheet.") } } } From 45e0275c0c5fefa56655d8f3eed6be879e67e4bb Mon Sep 17 00:00:00 2001 From: nictru Date: Fri, 13 Jun 2025 17:36:17 +0200 Subject: [PATCH 13/74] Improve hash/genetic channel structures --- modules/nf-core/demuxem/main.nf | 35 ++++++++++--------- .../local/genetic_demultiplexing/main.nf | 3 +- .../local/hash_demultiplexing/main.nf | 8 ++++- .../local/utils_nfcore_hadge_pipeline/main.nf | 2 ++ workflows/hadge.nf | 16 ++++++--- 5 files changed, 41 insertions(+), 23 deletions(-) diff --git a/modules/nf-core/demuxem/main.nf b/modules/nf-core/demuxem/main.nf index 7c850ac0..f4e3e3b1 100644 --- a/modules/nf-core/demuxem/main.nf +++ b/modules/nf-core/demuxem/main.nf @@ -1,38 +1,40 @@ process DEMUXEM { - tag "$meta.id" + tag "${meta.id}" label 'process_low' conda "${moduleDir}/environment.yml" - container "${ workflow.containerEngine == 'singularity' && !task.ext.singularity_pull_docker_container ? - 'https://depot.galaxyproject.org/singularity/demuxem:0.1.7.post1--pyhdfd78af_0' : - 'biocontainers/demuxem:0.1.7.post1--pyhdfd78af_0' }" + container "${workflow.containerEngine == 'singularity' && !task.ext.singularity_pull_docker_container + ? 'https://depot.galaxyproject.org/singularity/demuxem:0.1.7.post1--pyhdfd78af_0' + : 'biocontainers/demuxem:0.1.7.post1--pyhdfd78af_0'}" + input: tuple val(meta), path(input_raw_gene_bc_matrices_h5), path(input_hto_csv_file) val output_name val generate_gender_plot val genome val generate_diagnostic_plots + output: tuple val(meta), path("*_demux.zarr.zip"), emit: zarr tuple val(meta), path("*.out.demuxEM.zarr.zip"), emit: out_zarr - path "versions.yml" , emit: versions + path "versions.yml", emit: versions when: task.ext.when == null || task.ext.when script: - def args = task.ext.args ?: '' - def prefix = task.ext.prefix ?: "${meta.id}" - def generateGenderPlot = generate_gender_plot ? "--generate-gender-plot $generate_gender_plot" : "" - def genome_file = genome ? "--genome $genome" : "" - def diagnostic_plots = generate_diagnostic_plots ? "--generate-diagnostic-plots $generate_diagnostic_plots" : "" + def args = task.ext.args ?: '' + def prefix = task.ext.prefix ?: "${meta.id}" + def generateGenderPlot = generate_gender_plot ? "--generate-gender-plot ${generate_gender_plot}" : "" + def genome_file = genome ? "--genome ${genome}" : "" + def diagnostic_plots = generate_diagnostic_plots ? "--generate-diagnostic-plots ${generate_diagnostic_plots}" : "" """ - demuxEM $input_raw_gene_bc_matrices_h5 \\ - $input_hto_csv_file $output_name \\ - $args \\ - $generateGenderPlot\\ - $genome_file\\ - $diagnostic_plots + demuxEM ${input_raw_gene_bc_matrices_h5} \\ + ${input_hto_csv_file} ${output_name} \\ + ${args} \\ + ${generateGenderPlot}\\ + ${genome_file}\\ + ${diagnostic_plots} cat <<-END_VERSIONS > versions.yml "${task.process}":g echo \$(demuxEM --version 2>&1) @@ -50,5 +52,4 @@ process DEMUXEM { echo \$(demuxEM --version 2>&1) END_VERSIONS """ - } diff --git a/subworkflows/local/genetic_demultiplexing/main.nf b/subworkflows/local/genetic_demultiplexing/main.nf index 423208f9..e0b70d48 100644 --- a/subworkflows/local/genetic_demultiplexing/main.nf +++ b/subworkflows/local/genetic_demultiplexing/main.nf @@ -1,6 +1,7 @@ workflow GENETIC_DEMULTIPLEXING { take: - methods // list of strings + ch_samplesheet // channel: samplesheet read in from --input + methods // list of strings main: diff --git a/subworkflows/local/hash_demultiplexing/main.nf b/subworkflows/local/hash_demultiplexing/main.nf index 8ba28f05..17127e71 100644 --- a/subworkflows/local/hash_demultiplexing/main.nf +++ b/subworkflows/local/hash_demultiplexing/main.nf @@ -1,9 +1,14 @@ +include { DEMUXEM } from '../../../modules/nf-core/demuxem' + workflow HASH_DEMULTIPLEXING { take: - methods // list of strings + ch_samplesheet // channel: samplesheet read in from --input + methods // list of strings main: + ch_samplesheet.view() + ch_versions = Channel.empty() if (methods.contains('htodemux')) { @@ -13,6 +18,7 @@ workflow HASH_DEMULTIPLEXING { if (methods.contains('cellhashr')) { } if (methods.contains('demuxem')) { + DEMUXEM(ch_samplesheet, "test", true, [], true) } if (methods.contains('gmm-demux')) { } diff --git a/subworkflows/local/utils_nfcore_hadge_pipeline/main.nf b/subworkflows/local/utils_nfcore_hadge_pipeline/main.nf index a88e06fe..6fbf3323 100644 --- a/subworkflows/local/utils_nfcore_hadge_pipeline/main.nf +++ b/subworkflows/local/utils_nfcore_hadge_pipeline/main.nf @@ -162,6 +162,8 @@ def validateInputSamplesheet(input) { error("RNA matrix, HTO matrix, BAM file, BAM index file, barcodes file, number of samples, cell data, VCF file for mixed samples, and VCF file for donor samples must be provided for rescue mode. Please check your input samplesheet.") } } + + return input } // diff --git a/workflows/hadge.nf b/workflows/hadge.nf index 05660712..c9b37c4d 100644 --- a/workflows/hadge.nf +++ b/workflows/hadge.nf @@ -28,19 +28,27 @@ workflow HADGE { ch_versions = Channel.empty() ch_multiqc_files = Channel.empty() + ch_hashing = ch_samplesheet.map { meta, rna_matrix, hto_matrix, _bam, _bai, _barcodes, _nsample, _vcf_mixed, _vcf_donor -> + [meta, rna_matrix, hto_matrix] + } + + ch_genetic = ch_samplesheet.map { meta, _rna_matrix, _hto_matrix, bam, bai, barcodes, nsample, vcf_mixed, vcf_donor -> + [meta, bam, bai, barcodes, nsample, vcf_mixed, vcf_donor] + } + if (params.mode == 'genetic') { - GENETIC_DEMULTIPLEXING(params.genetic_tools.split(',')) + GENETIC_DEMULTIPLEXING(ch_genetic, params.genetic_tools.split(',')) ch_versions = ch_versions.mix(GENETIC_DEMULTIPLEXING.out.versions) } else if (params.mode == 'hashing') { - HASH_DEMULTIPLEXING(params.hash_tools.split(',')) + HASH_DEMULTIPLEXING(ch_hashing, params.hash_tools.split(',')) ch_versions = ch_versions.mix(HASH_DEMULTIPLEXING.out.versions) } else if (params.mode == 'rescue') { - HASH_DEMULTIPLEXING(params.hash_tools.split(',')) + HASH_DEMULTIPLEXING(ch_hashing, params.hash_tools.split(',')) ch_versions = ch_versions.mix(HASH_DEMULTIPLEXING.out.versions) - DONOR_MATCHING(params.genetic_tools.split(',')) + GENETIC_DEMULTIPLEXING(ch_genetic, params.genetic_tools.split(',')) ch_versions = ch_versions.mix(DONOR_MATCHING.out.versions) } From 2a60dc066660ec814ef29c318a7ad624198c0c9d Mon Sep 17 00:00:00 2001 From: nictru Date: Fri, 13 Jun 2025 17:51:10 +0200 Subject: [PATCH 14/74] Patch demuxem module --- modules.json | 3 +- modules/nf-core/demuxem/demuxem.diff | 79 ++++++++++++++++++++++++++++ modules/nf-core/demuxem/main.nf | 15 +++--- 3 files changed, 88 insertions(+), 9 deletions(-) create mode 100644 modules/nf-core/demuxem/demuxem.diff diff --git a/modules.json b/modules.json index fd16645c..b47fc82c 100644 --- a/modules.json +++ b/modules.json @@ -13,7 +13,8 @@ "demuxem": { "branch": "master", "git_sha": "05954dab2ff481bcb999f24455da29a5828af08d", - "installed_by": ["modules"] + "installed_by": ["modules"], + "patch": "modules/nf-core/demuxem/demuxem.diff" }, "gmmdemux": { "branch": "master", diff --git a/modules/nf-core/demuxem/demuxem.diff b/modules/nf-core/demuxem/demuxem.diff new file mode 100644 index 00000000..99053bce --- /dev/null +++ b/modules/nf-core/demuxem/demuxem.diff @@ -0,0 +1,79 @@ +Changes in component 'nf-core/demuxem' +Changes in 'demuxem/main.nf': +--- modules/nf-core/demuxem/main.nf ++++ modules/nf-core/demuxem/main.nf +@@ -1,41 +1,42 @@ + process DEMUXEM { +- tag "$meta.id" ++ tag "${meta.id}" + label 'process_low' + + conda "${moduleDir}/environment.yml" +- container "${ workflow.containerEngine == 'singularity' && !task.ext.singularity_pull_docker_container ? +- 'https://depot.galaxyproject.org/singularity/demuxem:0.1.7.post1--pyhdfd78af_0' : +- 'biocontainers/demuxem:0.1.7.post1--pyhdfd78af_0' }" ++ container "${workflow.containerEngine == 'singularity' && !task.ext.singularity_pull_docker_container ++ ? 'https://depot.galaxyproject.org/singularity/demuxem:0.1.7.post1--pyhdfd78af_0' ++ : 'biocontainers/demuxem:0.1.7.post1--pyhdfd78af_0'}" ++ + input: + tuple val(meta), path(input_raw_gene_bc_matrices_h5), path(input_hto_csv_file) +- val output_name + val generate_gender_plot + val genome + val generate_diagnostic_plots ++ + output: + tuple val(meta), path("*_demux.zarr.zip"), emit: zarr + tuple val(meta), path("*.out.demuxEM.zarr.zip"), emit: out_zarr +- path "versions.yml" , emit: versions ++ path "versions.yml", emit: versions + + when: + task.ext.when == null || task.ext.when + + script: +- def args = task.ext.args ?: '' +- def prefix = task.ext.prefix ?: "${meta.id}" +- def generateGenderPlot = generate_gender_plot ? "--generate-gender-plot $generate_gender_plot" : "" +- def genome_file = genome ? "--genome $genome" : "" +- def diagnostic_plots = generate_diagnostic_plots ? "--generate-diagnostic-plots $generate_diagnostic_plots" : "" ++ def args = task.ext.args ?: '' ++ def prefix = task.ext.prefix ?: "${meta.id}" ++ def generateGenderPlot = generate_gender_plot ? "--generate-gender-plot ${generate_gender_plot}" : "" ++ def genome_file = genome ? "--genome ${genome}" : "" ++ def diagnostic_plots = generate_diagnostic_plots ? "--generate-diagnostic-plots ${generate_diagnostic_plots}" : "" + """ +- demuxEM $input_raw_gene_bc_matrices_h5 \\ +- $input_hto_csv_file $output_name \\ +- $args \\ +- $generateGenderPlot\\ +- $genome_file\\ +- $diagnostic_plots ++ demuxEM ${input_raw_gene_bc_matrices_h5} ${input_hto_csv_file} ${prefix} \\ ++ ${args} \\ ++ ${generateGenderPlot}\\ ++ ${genome_file}\\ ++ ${diagnostic_plots} ++ + cat <<-END_VERSIONS > versions.yml + "${task.process}":g +- echo \$(demuxEM --version 2>&1) ++ echo \$(demuxEM --version 2>&1) + END_VERSIONS + """ + +@@ -50,5 +51,4 @@ + echo \$(demuxEM --version 2>&1) + END_VERSIONS + """ +- + } + +'modules/nf-core/demuxem/nextflow.config' is unchanged +'modules/nf-core/demuxem/environment.yml' is unchanged +'modules/nf-core/demuxem/meta.yml' is unchanged +'modules/nf-core/demuxem/tests/main.nf.test.snap' is unchanged +'modules/nf-core/demuxem/tests/nextflow.config' is unchanged +'modules/nf-core/demuxem/tests/main.nf.test' is unchanged +************************************************************ diff --git a/modules/nf-core/demuxem/main.nf b/modules/nf-core/demuxem/main.nf index f4e3e3b1..cbf4fca5 100644 --- a/modules/nf-core/demuxem/main.nf +++ b/modules/nf-core/demuxem/main.nf @@ -9,7 +9,6 @@ process DEMUXEM { input: tuple val(meta), path(input_raw_gene_bc_matrices_h5), path(input_hto_csv_file) - val output_name val generate_gender_plot val genome val generate_diagnostic_plots @@ -29,15 +28,15 @@ process DEMUXEM { def genome_file = genome ? "--genome ${genome}" : "" def diagnostic_plots = generate_diagnostic_plots ? "--generate-diagnostic-plots ${generate_diagnostic_plots}" : "" """ - demuxEM ${input_raw_gene_bc_matrices_h5} \\ - ${input_hto_csv_file} ${output_name} \\ - ${args} \\ - ${generateGenderPlot}\\ - ${genome_file}\\ - ${diagnostic_plots} + demuxEM ${input_raw_gene_bc_matrices_h5} ${input_hto_csv_file} ${prefix} \\ + ${args} \\ + ${generateGenderPlot}\\ + ${genome_file}\\ + ${diagnostic_plots} + cat <<-END_VERSIONS > versions.yml "${task.process}":g - echo \$(demuxEM --version 2>&1) + echo \$(demuxEM --version 2>&1) END_VERSIONS """ From b46d230c614672a741989edc75936fc8eaaa0832 Mon Sep 17 00:00:00 2001 From: nictru Date: Sat, 14 Jun 2025 13:18:15 +0200 Subject: [PATCH 15/74] Implement demuxem --- .../dropletutils/mtxconvert/environment.yml | 5 +++ modules/local/dropletutils/mtxconvert/main.nf | 31 ++++++++++++++ .../mtxconvert/templates/convert.R | 40 +++++++++++++++++++ modules/nf-core/demuxem/main.nf | 11 ++--- .../local/hash_demultiplexing/main.nf | 20 ++++++++-- 5 files changed, 96 insertions(+), 11 deletions(-) create mode 100644 modules/local/dropletutils/mtxconvert/environment.yml create mode 100644 modules/local/dropletutils/mtxconvert/main.nf create mode 100644 modules/local/dropletutils/mtxconvert/templates/convert.R diff --git a/modules/local/dropletutils/mtxconvert/environment.yml b/modules/local/dropletutils/mtxconvert/environment.yml new file mode 100644 index 00000000..e5774481 --- /dev/null +++ b/modules/local/dropletutils/mtxconvert/environment.yml @@ -0,0 +1,5 @@ +channels: + - conda-forge + - bioconda +dependencies: + - bioconda::bioconductor-dropletutils=1.26.0 diff --git a/modules/local/dropletutils/mtxconvert/main.nf b/modules/local/dropletutils/mtxconvert/main.nf new file mode 100644 index 00000000..21a3401b --- /dev/null +++ b/modules/local/dropletutils/mtxconvert/main.nf @@ -0,0 +1,31 @@ +process DROPLETUTILS_MTXCONVERT { + tag "${meta.id}" + label 'process_low' + + conda "${moduleDir}/environment.yml" + container "${workflow.containerEngine == 'singularity' && !task.ext.singularity_pull_docker_container + ? 'https://community-cr-prod.seqera.io/docker/registry/v2/blobs/sha256/c9/c9f81df3cdd03c86a8133f74c0deb78719798c061895e4d9dd454f05e82ff93e/data' + : 'community.wave.seqera.io/library/bioconductor-dropletutils:1.26.0--35a578ac06f1c531'}" + + input: + tuple val(meta), path(input_mtx_dir) + val write_csv + + output: + tuple val(meta), path("*.csv"), emit: csv, optional: true + tuple val(meta), path("*.h5"), emit: h5 + path "versions.yml", emit: versions + + script: + prefix = task.ext.prefix ?: "${meta.id}" + template("convert.R") + + stub: + prefix = task.ext.prefix ?: "${meta.id}" + """ + if [[ ${write_csv} == true ]]; then + touch ${prefix}.csv + fi + touch ${prefix}.h5 + """ +} diff --git a/modules/local/dropletutils/mtxconvert/templates/convert.R b/modules/local/dropletutils/mtxconvert/templates/convert.R new file mode 100644 index 00000000..a8480124 --- /dev/null +++ b/modules/local/dropletutils/mtxconvert/templates/convert.R @@ -0,0 +1,40 @@ +#! /usr/bin/env Rscript + +library(DropletUtils) + +mtx_dir <- "${input_mtx_dir}" + +sce <- read10xCounts(mtx_dir) # Read to SingleCellExperiment object + +print(sce) + +# Convert to matrix +count_matrix <- counts(sce) +rownames(count_matrix) <- rownames(sce) +colnames(count_matrix) <- colData(sce)\$Barcode + +if ("${write_csv}" == "true") { + write.csv(as.matrix(count_matrix), file = "${prefix}.csv", row.names = TRUE) +} + +# Write to h5 file +write10xCounts("${prefix}.h5", count_matrix, type = "HDF5") + +################################################ +################################################ +## VERSIONS FILE ## +################################################ +################################################ + +r.version <- paste(R.version[['major']],R.version[['minor']], sep = ".") +dropletutils.version <- as.character(packageVersion('DropletUtils')) + +writeLines( + c( + '"${task.process}":', + paste(' r-base:', r.version), + paste(' bioconductor-dropletutils:', dropletutils.version) + ), +'versions.yml') + +############################################ diff --git a/modules/nf-core/demuxem/main.nf b/modules/nf-core/demuxem/main.nf index cbf4fca5..f1a19034 100644 --- a/modules/nf-core/demuxem/main.nf +++ b/modules/nf-core/demuxem/main.nf @@ -4,8 +4,8 @@ process DEMUXEM { conda "${moduleDir}/environment.yml" container "${workflow.containerEngine == 'singularity' && !task.ext.singularity_pull_docker_container - ? 'https://depot.galaxyproject.org/singularity/demuxem:0.1.7.post1--pyhdfd78af_0' - : 'biocontainers/demuxem:0.1.7.post1--pyhdfd78af_0'}" + ? 'https://community-cr-prod.seqera.io/docker/registry/v2/blobs/sha256/0d/0d3f96aaa8437bfa1654570e1d2b84749f1ac14d68f97978acc19b3757af7f55/data' + : 'community.wave.seqera.io/library/demuxem:0.1.7.post1--5ac55376ad7cb80e'}" input: tuple val(meta), path(input_raw_gene_bc_matrices_h5), path(input_hto_csv_file) @@ -29,13 +29,10 @@ process DEMUXEM { def diagnostic_plots = generate_diagnostic_plots ? "--generate-diagnostic-plots ${generate_diagnostic_plots}" : "" """ demuxEM ${input_raw_gene_bc_matrices_h5} ${input_hto_csv_file} ${prefix} \\ - ${args} \\ - ${generateGenderPlot}\\ - ${genome_file}\\ - ${diagnostic_plots} + -p $task.cpus cat <<-END_VERSIONS > versions.yml - "${task.process}":g + "${task.process}": echo \$(demuxEM --version 2>&1) END_VERSIONS """ diff --git a/subworkflows/local/hash_demultiplexing/main.nf b/subworkflows/local/hash_demultiplexing/main.nf index 17127e71..704284ca 100644 --- a/subworkflows/local/hash_demultiplexing/main.nf +++ b/subworkflows/local/hash_demultiplexing/main.nf @@ -1,4 +1,6 @@ -include { DEMUXEM } from '../../../modules/nf-core/demuxem' +include { DROPLETUTILS_MTXCONVERT as MTXCONVERT_RNA } from '../../../modules/local/dropletutils/mtxconvert' +include { DROPLETUTILS_MTXCONVERT as MTXCONVERT_HTO } from '../../../modules/local/dropletutils/mtxconvert' +include { DEMUXEM } from '../../../modules/nf-core/demuxem' workflow HASH_DEMULTIPLEXING { take: @@ -7,10 +9,14 @@ workflow HASH_DEMULTIPLEXING { main: - ch_samplesheet.view() - ch_versions = Channel.empty() + MTXCONVERT_RNA(ch_samplesheet.map { meta, rna, _hto -> [meta, rna] }, false) + ch_versions = ch_versions.mix(MTXCONVERT_RNA.out.versions) + + MTXCONVERT_HTO(ch_samplesheet.map { meta, _rna, hto -> [meta, hto] }, true) + ch_versions = ch_versions.mix(MTXCONVERT_HTO.out.versions) + if (methods.contains('htodemux')) { } if (methods.contains('multiseq')) { @@ -18,7 +24,13 @@ workflow HASH_DEMULTIPLEXING { if (methods.contains('cellhashr')) { } if (methods.contains('demuxem')) { - DEMUXEM(ch_samplesheet, "test", true, [], true) + DEMUXEM( + MTXCONVERT_RNA.out.h5.join(MTXCONVERT_HTO.out.csv), + true, + [], + true, + ) + ch_versions = ch_versions.mix(DEMUXEM.out.versions) } if (methods.contains('gmm-demux')) { } From cb864cafeb6e4cb7af04fd20cb1b464fb4550f2a Mon Sep 17 00:00:00 2001 From: nictru Date: Sat, 14 Jun 2025 13:37:27 +0200 Subject: [PATCH 16/74] Add GMM-Demux --- .../local/hash_demultiplexing/main.nf | 20 +++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/subworkflows/local/hash_demultiplexing/main.nf b/subworkflows/local/hash_demultiplexing/main.nf index 704284ca..dfa3174c 100644 --- a/subworkflows/local/hash_demultiplexing/main.nf +++ b/subworkflows/local/hash_demultiplexing/main.nf @@ -1,6 +1,7 @@ include { DROPLETUTILS_MTXCONVERT as MTXCONVERT_RNA } from '../../../modules/local/dropletutils/mtxconvert' include { DROPLETUTILS_MTXCONVERT as MTXCONVERT_HTO } from '../../../modules/local/dropletutils/mtxconvert' include { DEMUXEM } from '../../../modules/nf-core/demuxem' +include { GMMDEMUX } from '../../../modules/nf-core/gmmdemux' workflow HASH_DEMULTIPLEXING { take: @@ -11,17 +12,22 @@ workflow HASH_DEMULTIPLEXING { ch_versions = Channel.empty() - MTXCONVERT_RNA(ch_samplesheet.map { meta, rna, _hto -> [meta, rna] }, false) + ch_rna = ch_samplesheet.map { meta, rna, _hto -> [meta, rna] } + MTXCONVERT_RNA(ch_rna, false) ch_versions = ch_versions.mix(MTXCONVERT_RNA.out.versions) - MTXCONVERT_HTO(ch_samplesheet.map { meta, _rna, hto -> [meta, hto] }, true) + ch_hto = ch_samplesheet.map { meta, _rna, hto -> [meta, hto] } + MTXCONVERT_HTO(ch_hto, true) ch_versions = ch_versions.mix(MTXCONVERT_HTO.out.versions) if (methods.contains('htodemux')) { + error("HtoDemux not implemented") } if (methods.contains('multiseq')) { + error("MultiSeq not implemented") } if (methods.contains('cellhashr')) { + error("CellHashR not implemented") } if (methods.contains('demuxem')) { DEMUXEM( @@ -33,10 +39,20 @@ workflow HASH_DEMULTIPLEXING { ch_versions = ch_versions.mix(DEMUXEM.out.versions) } if (methods.contains('gmm-demux')) { + GMMDEMUX( + ch_hto.map { meta, hto -> [meta, hto, "MS-11,MS-12"] }, + true, + true, + [], + [], + ) + ch_versions = ch_versions.mix(GMMDEMUX.out.versions) } if (methods.contains('hasheddrops')) { + error("HashedDrops not implemented") } if (methods.contains('hashsolo')) { + error("HashSolo not implemented") } emit: From 2993c3f425fa3ad035e5a192589015af0aaaae82 Mon Sep 17 00:00:00 2001 From: nictru Date: Sat, 14 Jun 2025 14:20:05 +0200 Subject: [PATCH 17/74] Update genetic parameterization --- assets/schema_input.json | 18 +- modules.json | 10 + .../nf-core/popscle/dscpileup/environment.yml | 7 + modules/nf-core/popscle/dscpileup/main.nf | 57 ++++ modules/nf-core/popscle/dscpileup/meta.yml | 98 +++++++ .../popscle/dscpileup/tests/main.nf.test | 68 +++++ .../popscle/dscpileup/tests/main.nf.test.snap | 26 ++ .../nf-core/samtools/index/environment.yml | 8 + modules/nf-core/samtools/index/main.nf | 49 ++++ modules/nf-core/samtools/index/meta.yml | 71 +++++ .../samtools/index/tests/csi.nextflow.config | 7 + .../nf-core/samtools/index/tests/main.nf.test | 140 ++++++++++ .../samtools/index/tests/main.nf.test.snap | 250 ++++++++++++++++++ .../local/genetic_demultiplexing/main.nf | 12 + .../local/utils_nfcore_hadge_pipeline/main.nf | 10 +- workflows/hadge.nf | 6 +- 16 files changed, 812 insertions(+), 25 deletions(-) create mode 100644 modules/nf-core/popscle/dscpileup/environment.yml create mode 100644 modules/nf-core/popscle/dscpileup/main.nf create mode 100644 modules/nf-core/popscle/dscpileup/meta.yml create mode 100644 modules/nf-core/popscle/dscpileup/tests/main.nf.test create mode 100644 modules/nf-core/popscle/dscpileup/tests/main.nf.test.snap create mode 100644 modules/nf-core/samtools/index/environment.yml create mode 100644 modules/nf-core/samtools/index/main.nf create mode 100644 modules/nf-core/samtools/index/meta.yml create mode 100644 modules/nf-core/samtools/index/tests/csi.nextflow.config create mode 100644 modules/nf-core/samtools/index/tests/main.nf.test create mode 100644 modules/nf-core/samtools/index/tests/main.nf.test.snap diff --git a/assets/schema_input.json b/assets/schema_input.json index 29b2f4d6..6a03b57f 100644 --- a/assets/schema_input.json +++ b/assets/schema_input.json @@ -37,14 +37,6 @@ "pattern": "^\\S+\\.bam$", "errorMessage": "BAM file must be provided, cannot contain spaces and must have extension '.bam'" }, - "bai": { - "type": "string", - "format": "file-path", - "exists": true, - "default": null, - "pattern": "^\\S+\\.bam\\.bai$", - "errorMessage": "BAM index file must be provided, cannot contain spaces and must have extension '.bam.bai'" - }, "barcodes": { "type": "string", "format": "file-path", @@ -58,15 +50,7 @@ "minimum": 1, "errorMessage": "Number of samples must be provided and must be greater than 0" }, - "vcf_mixed": { - "type": "string", - "format": "file-path", - "exists": true, - "default": null, - "pattern": "^\\S+\\.vcf$", - "errorMessage": "VCF file must be provided, cannot contain spaces and must have extension '.vcf'" - }, - "vcf_donor": { + "vcf": { "type": "string", "format": "file-path", "exists": true, diff --git a/modules.json b/modules.json index b47fc82c..64102ea0 100644 --- a/modules.json +++ b/modules.json @@ -31,11 +31,21 @@ "git_sha": "05954dab2ff481bcb999f24455da29a5828af08d", "installed_by": ["modules"] }, + "popscle/dscpileup": { + "branch": "master", + "git_sha": "05954dab2ff481bcb999f24455da29a5828af08d", + "installed_by": ["modules"] + }, "popscle/freemuxlet": { "branch": "master", "git_sha": "05954dab2ff481bcb999f24455da29a5828af08d", "installed_by": ["modules"] }, + "samtools/index": { + "branch": "master", + "git_sha": "d090922b1a4b80ba283186459ababf8e308abcbb", + "installed_by": ["modules"] + }, "vireo": { "branch": "master", "git_sha": "05954dab2ff481bcb999f24455da29a5828af08d", diff --git a/modules/nf-core/popscle/dscpileup/environment.yml b/modules/nf-core/popscle/dscpileup/environment.yml new file mode 100644 index 00000000..1fe132c2 --- /dev/null +++ b/modules/nf-core/popscle/dscpileup/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: + - bioconda::popscle=0.1 diff --git a/modules/nf-core/popscle/dscpileup/main.nf b/modules/nf-core/popscle/dscpileup/main.nf new file mode 100644 index 00000000..2f7fbf3a --- /dev/null +++ b/modules/nf-core/popscle/dscpileup/main.nf @@ -0,0 +1,57 @@ +process POPSCLE_DSCPILEUP { + tag "$meta.id" + label 'process_medium' + + conda "${moduleDir}/environment.yml" + container "${ workflow.containerEngine == 'singularity' && !task.ext.singularity_pull_docker_container ? + 'https://depot.galaxyproject.org/singularity/popscle:0.1beta--h2c78cec_0' : + 'biocontainers/popscle:0.1beta--h2c78cec_0' }" + + input: + tuple val(meta), path(bam), path(vcf) + + output: + tuple val(meta), path('*.cel.gz'), emit: cel + tuple val(meta), path('*.plp.gz'), emit: plp + tuple val(meta), path('*.var.gz'), emit: var + tuple val(meta), path('*.umi.gz'), emit: umi + path 'versions.yml' , emit: versions + + when: + task.ext.when == null || task.ext.when + + script: + def args = task.ext.args ?: '' + def prefix = task.ext.prefix ?: "${meta.id}" + def VERSION = '0.1' // WARN: Version information not provided by tool on CLI. Please update version string below when bumping container versions. + + """ + popscle dsc-pileup \\ + --sam $bam \\ + --vcf $vcf \\ + --out $prefix \\ + $args \\ + + cat <<-END_VERSIONS > versions.yml + "${task.process}": + popscle dsc-pileup: $VERSION + END_VERSIONS + """ + + stub: + def args = task.ext.args ?: '' + def prefix = task.ext.prefix ?: "${meta.id}" + def VERSION = '0.1' // WARN: Version information not provided by tool on CLI. Please update version string below when bumping container versions. + + """ + touch ${prefix}.cel.gz + touch ${prefix}.var.gz + touch ${prefix}.plp.gz + touch ${prefix}.umi.gz + + cat <<-END_VERSIONS > versions.yml + "${task.process}": + popscle dsc-pileup: $VERSION + END_VERSIONS + """ +} diff --git a/modules/nf-core/popscle/dscpileup/meta.yml b/modules/nf-core/popscle/dscpileup/meta.yml new file mode 100644 index 00000000..3c95caa5 --- /dev/null +++ b/modules/nf-core/popscle/dscpileup/meta.yml @@ -0,0 +1,98 @@ +# yaml-language-server: $schema=https://raw.githubusercontent.com/nf-core/modules/master/modules/meta-schema.json +name: "popscle_dscpileup" +description: Software to pileup reads and corresponding base quality for each overlapping + SNPs and each barcode. +keywords: + - popscle + - demultiplexing + - genotype-based deconvoltion + - single cell + - pile up +tools: + - "popscle": + description: "A suite of population scale analysis tools for single-cell genomics + data including implementation of Demuxlet / Freemuxlet methods and auxiliary + tools" + homepage: "https://github.com/statgen/popscle" + documentation: "https://github.com/statgen/popscle" + tool_dev_url: "https://github.com/statgen/popscle" + doi: "10.1038/nbt.4042" + licence: ["Apache-2.0"] + identifier: "" + +input: + - - meta: + type: map + description: | + Groovy Map containing sample information + e.g. `[ id:'sample1' ]` + - bam: + type: file + description: Input SAM/BAM/CRAM file produced by the standard 10x sequencing + platform, or any other barcoded single cell RNA-seq. + pattern: "*.{bam,cram,sam}" + - vcf: + type: file + description: Input VCF/BCF file files containing (AC) and (AN) from referenced + population (e.g. 1000g). + pattern: "*.{vcf,bcf}" +output: + - cel: + - meta: + type: map + description: | + Groovy Map containing sample information + e.g. `[ id:'sample1']` + - "*.cel.gz": + type: file + description: Contains the relation between numerated barcode ID and barcode + and the number of SNP and number of UMI for each barcoded droplet. + pattern: "*.cel.gz" + - plp: + - meta: + type: map + description: | + Groovy Map containing sample information + e.g. `[ id:'sample1']` + - "*.plp.gz": + type: file + description: Contains the overlapping SNP and the corresponding read and base + quality for each barcode ID. + pattern: "*.plp.gz" + - var: + - meta: + type: map + description: | + Groovy Map containing sample information + e.g. `[ id:'sample1']` + - "*.var.gz": + type: file + description: Contains the position, reference allele and allele frequency for + each SNP. + pattern: "*.var.gz" + - umi: + - meta: + type: map + description: | + Groovy Map containing sample information + e.g. `[ id:'sample1']` + - "*.umi.gz": + type: file + description: Contains the position covered by each umi. + pattern: "*.umi.gz" + - versions: + - versions.yml: + type: file + description: File containing software versions + pattern: "versions.yml" +authors: + - "@mari-ga" + - "@maxozo" + - "@wxicu" + - "@Zethson" + +maintainers: + - "@mari-ga" + - "@maxozo" + - "@wxicu" + - "@Zethson" diff --git a/modules/nf-core/popscle/dscpileup/tests/main.nf.test b/modules/nf-core/popscle/dscpileup/tests/main.nf.test new file mode 100644 index 00000000..7c3334b3 --- /dev/null +++ b/modules/nf-core/popscle/dscpileup/tests/main.nf.test @@ -0,0 +1,68 @@ +nextflow_process { + + name "Test Process POPSCLE_DSCPILEUP" + script "../main.nf" + process "POPSCLE_DSCPILEUP" + + tag "modules" + tag "modules_nfcore" + tag "popscle" + tag "popscle/dscpileup" + + test("demultiplexing - bam") { + + when { + process { + """ + input[0] = [ + [ id:'sample1' ], // meta map + file(params.modules_testdata_base_path + '/genomics/homo_sapiens/demultiplexing/chr21.bam', checkIfExists: true), + file(params.modules_testdata_base_path + '/genomics/homo_sapiens/demultiplexing/donor_genotype_chr21.vcf', checkIfExists: true) + ] + """ + } + } + + then { + assertAll( + { assert process.success }, + { assert snapshot(process.out.versions).match("versions") }, + { assert path(process.out.cel.get(0).get(1)).exists() }, + { assert path(process.out.var.get(0).get(1)).exists() }, + { assert path(process.out.umi.get(0).get(1)).exists() }, + { assert path(process.out.plp.get(0).get(1)).exists() } + ) + } + + } + + test("demultiplexing - bam - stub") { + + options "-stub" + + when { + process { + """ + input[0] = [ + [ id:'sample1' ], // meta map + file(params.modules_testdata_base_path + '/genomics/homo_sapiens/demultiplexing/chr21.bam', checkIfExists: true), + file(params.modules_testdata_base_path + '/genomics/homo_sapiens/demultiplexing/donor_genotype_chr21.vcf', checkIfExists: true) + ] + """ + } + } + + then { + assertAll( + { assert process.success }, + { assert snapshot(process.out.versions).match("stub-versions") }, + { assert path(process.out.cel.get(0).get(1)).exists() }, + { assert path(process.out.var.get(0).get(1)).exists() }, + { assert path(process.out.umi.get(0).get(1)).exists() }, + { assert path(process.out.plp.get(0).get(1)).exists() } + ) + } + + } + +} diff --git a/modules/nf-core/popscle/dscpileup/tests/main.nf.test.snap b/modules/nf-core/popscle/dscpileup/tests/main.nf.test.snap new file mode 100644 index 00000000..ee6880b7 --- /dev/null +++ b/modules/nf-core/popscle/dscpileup/tests/main.nf.test.snap @@ -0,0 +1,26 @@ +{ + "versions": { + "content": [ + [ + "versions.yml:md5,b9f72a52f464ec5add0256467d30a5c3" + ] + ], + "meta": { + "nf-test": "0.8.4", + "nextflow": "23.10.1" + }, + "timestamp": "2024-03-21T07:03:50.053780794" + }, + "stub-versions": { + "content": [ + [ + "versions.yml:md5,b9f72a52f464ec5add0256467d30a5c3" + ] + ], + "meta": { + "nf-test": "0.8.4", + "nextflow": "23.10.1" + }, + "timestamp": "2024-03-21T08:23:22.002661876" + } +} \ No newline at end of file diff --git a/modules/nf-core/samtools/index/environment.yml b/modules/nf-core/samtools/index/environment.yml new file mode 100644 index 00000000..62054fc9 --- /dev/null +++ b/modules/nf-core/samtools/index/environment.yml @@ -0,0 +1,8 @@ +--- +# yaml-language-server: $schema=https://raw.githubusercontent.com/nf-core/modules/master/modules/environment-schema.json +channels: + - conda-forge + - bioconda +dependencies: + - bioconda::htslib=1.21 + - bioconda::samtools=1.21 diff --git a/modules/nf-core/samtools/index/main.nf b/modules/nf-core/samtools/index/main.nf new file mode 100644 index 00000000..7019a72e --- /dev/null +++ b/modules/nf-core/samtools/index/main.nf @@ -0,0 +1,49 @@ +process SAMTOOLS_INDEX { + tag "$meta.id" + label 'process_low' + + conda "${moduleDir}/environment.yml" + container "${ workflow.containerEngine == 'singularity' && !task.ext.singularity_pull_docker_container ? + 'https://depot.galaxyproject.org/singularity/samtools:1.21--h50ea8bc_0' : + 'biocontainers/samtools:1.21--h50ea8bc_0' }" + + input: + tuple val(meta), path(input) + + output: + tuple val(meta), path("*.bai") , optional:true, emit: bai + tuple val(meta), path("*.csi") , optional:true, emit: csi + tuple val(meta), path("*.crai"), optional:true, emit: crai + path "versions.yml" , emit: versions + + when: + task.ext.when == null || task.ext.when + + script: + def args = task.ext.args ?: '' + """ + samtools \\ + index \\ + -@ ${task.cpus} \\ + $args \\ + $input + + cat <<-END_VERSIONS > versions.yml + "${task.process}": + samtools: \$(echo \$(samtools --version 2>&1) | sed 's/^.*samtools //; s/Using.*\$//') + END_VERSIONS + """ + + stub: + def args = task.ext.args ?: '' + def extension = file(input).getExtension() == 'cram' ? + "crai" : args.contains("-c") ? "csi" : "bai" + """ + touch ${input}.${extension} + + cat <<-END_VERSIONS > versions.yml + "${task.process}": + samtools: \$(echo \$(samtools --version 2>&1) | sed 's/^.*samtools //; s/Using.*\$//') + END_VERSIONS + """ +} diff --git a/modules/nf-core/samtools/index/meta.yml b/modules/nf-core/samtools/index/meta.yml new file mode 100644 index 00000000..db8df0d5 --- /dev/null +++ b/modules/nf-core/samtools/index/meta.yml @@ -0,0 +1,71 @@ +name: samtools_index +description: Index SAM/BAM/CRAM file +keywords: + - index + - bam + - sam + - cram +tools: + - samtools: + description: | + SAMtools is a set of utilities for interacting with and post-processing + short DNA sequence read alignments in the SAM, BAM and CRAM formats, written by Heng Li. + These files are generated as output by short read aligners like BWA. + homepage: http://www.htslib.org/ + documentation: http://www.htslib.org/doc/samtools.html + doi: 10.1093/bioinformatics/btp352 + licence: ["MIT"] + identifier: biotools:samtools +input: + - - meta: + type: map + description: | + Groovy Map containing sample information + e.g. [ id:'test', single_end:false ] + - input: + type: file + description: input file +output: + - bai: + - meta: + type: map + description: | + Groovy Map containing sample information + e.g. [ id:'test', single_end:false ] + - "*.bai": + type: file + description: BAM/CRAM/SAM index file + pattern: "*.{bai,crai,sai}" + - csi: + - meta: + type: map + description: | + Groovy Map containing sample information + e.g. [ id:'test', single_end:false ] + - "*.csi": + type: file + description: CSI index file + pattern: "*.{csi}" + - crai: + - meta: + type: map + description: | + Groovy Map containing sample information + e.g. [ id:'test', single_end:false ] + - "*.crai": + type: file + description: BAM/CRAM/SAM index file + pattern: "*.{bai,crai,sai}" + - versions: + - versions.yml: + type: file + description: File containing software versions + pattern: "versions.yml" +authors: + - "@drpatelh" + - "@ewels" + - "@maxulysse" +maintainers: + - "@drpatelh" + - "@ewels" + - "@maxulysse" diff --git a/modules/nf-core/samtools/index/tests/csi.nextflow.config b/modules/nf-core/samtools/index/tests/csi.nextflow.config new file mode 100644 index 00000000..0ed260ef --- /dev/null +++ b/modules/nf-core/samtools/index/tests/csi.nextflow.config @@ -0,0 +1,7 @@ +process { + + withName: SAMTOOLS_INDEX { + ext.args = '-c' + } + +} diff --git a/modules/nf-core/samtools/index/tests/main.nf.test b/modules/nf-core/samtools/index/tests/main.nf.test new file mode 100644 index 00000000..ca34fb5c --- /dev/null +++ b/modules/nf-core/samtools/index/tests/main.nf.test @@ -0,0 +1,140 @@ +nextflow_process { + + name "Test Process SAMTOOLS_INDEX" + script "../main.nf" + process "SAMTOOLS_INDEX" + tag "modules" + tag "modules_nfcore" + tag "samtools" + tag "samtools/index" + + test("bai") { + when { + process { + """ + input[0] = Channel.of([ + [ id:'test', single_end:false ], // meta map + file(params.modules_testdata_base_path + 'genomics/sarscov2/illumina/bam/test.paired_end.sorted.bam', checkIfExists: true) + ]) + """ + } + } + + then { + assertAll ( + { assert process.success }, + { assert snapshot(process.out).match() } + ) + } + } + + test("crai") { + when { + process { + """ + input[0] = Channel.of([ + [ id:'test', single_end:false ], // meta map + file(params.modules_testdata_base_path + 'genomics/homo_sapiens/illumina/cram/test.paired_end.recalibrated.sorted.cram', checkIfExists: true) + ]) + """ + } + } + + then { + assertAll ( + { assert process.success }, + { assert snapshot(process.out).match() } + ) + } + } + + test("csi") { + config "./csi.nextflow.config" + + when { + process { + """ + input[0] = Channel.of([ + [ id:'test', single_end:false ], // meta map + file(params.modules_testdata_base_path + 'genomics/sarscov2/illumina/bam/test.paired_end.sorted.bam', checkIfExists: true) + ]) + """ + } + } + + then { + assertAll ( + { assert process.success }, + { assert snapshot( + file(process.out.csi[0][1]).name, + process.out.versions + ).match() } + ) + } + } + + test("bai - stub") { + options "-stub" + when { + process { + """ + input[0] = Channel.of([ + [ id:'test', single_end:false ], // meta map + file(params.modules_testdata_base_path + 'genomics/sarscov2/illumina/bam/test.paired_end.sorted.bam', checkIfExists: true) + ]) + """ + } + } + + then { + assertAll ( + { assert process.success }, + { assert snapshot(process.out).match() } + ) + } + } + + test("crai - stub") { + options "-stub" + when { + process { + """ + input[0] = Channel.of([ + [ id:'test', single_end:false ], // meta map + file(params.modules_testdata_base_path + 'genomics/homo_sapiens/illumina/cram/test.paired_end.recalibrated.sorted.cram', checkIfExists: true) + ]) + """ + } + } + + then { + assertAll ( + { assert process.success }, + { assert snapshot(process.out).match() } + ) + } + } + + test("csi - stub") { + options "-stub" + config "./csi.nextflow.config" + + when { + process { + """ + input[0] = Channel.of([ + [ id:'test', single_end:false ], // meta map + file(params.modules_testdata_base_path + 'genomics/sarscov2/illumina/bam/test.paired_end.sorted.bam', checkIfExists: true) + ]) + """ + } + } + + then { + assertAll ( + { assert process.success }, + { assert snapshot(process.out).match() } + ) + } + } +} diff --git a/modules/nf-core/samtools/index/tests/main.nf.test.snap b/modules/nf-core/samtools/index/tests/main.nf.test.snap new file mode 100644 index 00000000..72d65e81 --- /dev/null +++ b/modules/nf-core/samtools/index/tests/main.nf.test.snap @@ -0,0 +1,250 @@ +{ + "csi - stub": { + "content": [ + { + "0": [ + + ], + "1": [ + [ + { + "id": "test", + "single_end": false + }, + "test.paired_end.sorted.bam.csi:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "2": [ + + ], + "3": [ + "versions.yml:md5,5e09a6fdf76de396728f877193d72315" + ], + "bai": [ + + ], + "crai": [ + + ], + "csi": [ + [ + { + "id": "test", + "single_end": false + }, + "test.paired_end.sorted.bam.csi:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "versions": [ + "versions.yml:md5,5e09a6fdf76de396728f877193d72315" + ] + } + ], + "meta": { + "nf-test": "0.9.0", + "nextflow": "24.04.4" + }, + "timestamp": "2024-09-16T08:21:25.261127166" + }, + "crai - stub": { + "content": [ + { + "0": [ + + ], + "1": [ + + ], + "2": [ + [ + { + "id": "test", + "single_end": false + }, + "test.paired_end.recalibrated.sorted.cram.crai:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "3": [ + "versions.yml:md5,5e09a6fdf76de396728f877193d72315" + ], + "bai": [ + + ], + "crai": [ + [ + { + "id": "test", + "single_end": false + }, + "test.paired_end.recalibrated.sorted.cram.crai:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "csi": [ + + ], + "versions": [ + "versions.yml:md5,5e09a6fdf76de396728f877193d72315" + ] + } + ], + "meta": { + "nf-test": "0.9.0", + "nextflow": "24.04.4" + }, + "timestamp": "2024-09-16T08:21:12.653194876" + }, + "bai - stub": { + "content": [ + { + "0": [ + [ + { + "id": "test", + "single_end": false + }, + "test.paired_end.sorted.bam.bai:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "1": [ + + ], + "2": [ + + ], + "3": [ + "versions.yml:md5,5e09a6fdf76de396728f877193d72315" + ], + "bai": [ + [ + { + "id": "test", + "single_end": false + }, + "test.paired_end.sorted.bam.bai:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "crai": [ + + ], + "csi": [ + + ], + "versions": [ + "versions.yml:md5,5e09a6fdf76de396728f877193d72315" + ] + } + ], + "meta": { + "nf-test": "0.9.0", + "nextflow": "24.04.4" + }, + "timestamp": "2024-09-16T08:21:01.854932651" + }, + "csi": { + "content": [ + "test.paired_end.sorted.bam.csi", + [ + "versions.yml:md5,5e09a6fdf76de396728f877193d72315" + ] + ], + "meta": { + "nf-test": "0.9.0", + "nextflow": "24.04.4" + }, + "timestamp": "2024-09-16T08:20:51.485364222" + }, + "crai": { + "content": [ + { + "0": [ + + ], + "1": [ + + ], + "2": [ + [ + { + "id": "test", + "single_end": false + }, + "test.paired_end.recalibrated.sorted.cram.crai:md5,14bc3bd5c89cacc8f4541f9062429029" + ] + ], + "3": [ + "versions.yml:md5,5e09a6fdf76de396728f877193d72315" + ], + "bai": [ + + ], + "crai": [ + [ + { + "id": "test", + "single_end": false + }, + "test.paired_end.recalibrated.sorted.cram.crai:md5,14bc3bd5c89cacc8f4541f9062429029" + ] + ], + "csi": [ + + ], + "versions": [ + "versions.yml:md5,5e09a6fdf76de396728f877193d72315" + ] + } + ], + "meta": { + "nf-test": "0.9.0", + "nextflow": "24.04.4" + }, + "timestamp": "2024-09-16T08:20:40.518873972" + }, + "bai": { + "content": [ + { + "0": [ + [ + { + "id": "test", + "single_end": false + }, + "test.paired_end.sorted.bam.bai:md5,704c10dd1326482448ca3073fdebc2f4" + ] + ], + "1": [ + + ], + "2": [ + + ], + "3": [ + "versions.yml:md5,5e09a6fdf76de396728f877193d72315" + ], + "bai": [ + [ + { + "id": "test", + "single_end": false + }, + "test.paired_end.sorted.bam.bai:md5,704c10dd1326482448ca3073fdebc2f4" + ] + ], + "crai": [ + + ], + "csi": [ + + ], + "versions": [ + "versions.yml:md5,5e09a6fdf76de396728f877193d72315" + ] + } + ], + "meta": { + "nf-test": "0.9.0", + "nextflow": "24.04.4" + }, + "timestamp": "2024-09-16T08:20:21.184050361" + } +} \ No newline at end of file diff --git a/subworkflows/local/genetic_demultiplexing/main.nf b/subworkflows/local/genetic_demultiplexing/main.nf index e0b70d48..39d18d2d 100644 --- a/subworkflows/local/genetic_demultiplexing/main.nf +++ b/subworkflows/local/genetic_demultiplexing/main.nf @@ -1,3 +1,6 @@ +include { POPSCLE_DSCPILEUP } from '../../../modules/nf-core/popscle/dscpileup' +include { SAMTOOLS_INDEX } from '../../../modules/nf-core/samtools/index' + workflow GENETIC_DEMULTIPLEXING { take: ch_samplesheet // channel: samplesheet read in from --input @@ -7,13 +10,22 @@ workflow GENETIC_DEMULTIPLEXING { ch_versions = Channel.empty() + SAMTOOLS_INDEX(ch_samplesheet.map { meta, bam, _barcodes, _nsample, _vcf -> [meta, bam] }) + ch_versions = ch_versions.mix(SAMTOOLS_INDEX.out.versions) + + if (methods.contains('demuxlet') || methods.contains('freemuxlet')) { + } + if (methods.contains('vireo')) { + error("Vireo not implemented") } if (methods.contains('demuxlet')) { } if (methods.contains('freemuxlet')) { + error("Freemuxlet not implemented") } if (methods.contains('souporcell')) { + error("Souporcell not implemented") } emit: diff --git a/subworkflows/local/utils_nfcore_hadge_pipeline/main.nf b/subworkflows/local/utils_nfcore_hadge_pipeline/main.nf index 6fbf3323..d4f5ffd0 100644 --- a/subworkflows/local/utils_nfcore_hadge_pipeline/main.nf +++ b/subworkflows/local/utils_nfcore_hadge_pipeline/main.nf @@ -145,7 +145,7 @@ def validateInputParameters() { // Validate channels from input samplesheet // def validateInputSamplesheet(input) { - def (_meta, rna_matrix, hto_matrix, bam, bai, barcodes, nsample, cell_data, vcf_mixed, vcf_donor) = input + def (_meta, rna_matrix, hto_matrix, bam, barcodes, nsample, vcf) = input if (params.mode == 'hashing') { if (rna_matrix == null || hto_matrix == null) { @@ -153,13 +153,13 @@ def validateInputSamplesheet(input) { } } else if (params.mode == 'genetic') { - if (bam == null || bai == null || barcodes == null || nsample == null || cell_data == null || vcf_mixed == null || vcf_donor == null) { - error("BAM file, BAM index file, barcodes file, number of samples, cell data, VCF file for mixed samples, and VCF file for donor samples must be provided for genetic mode. Please check your input samplesheet.") + if (bam == null || vcf == null) { + error("BAM file and VCF file must be provided for genetic mode. Please check your input samplesheet.") } } else if (params.mode == 'rescue') { - if (rna_matrix == null || hto_matrix == null || bam == null || bai == null || barcodes == null || nsample == null || cell_data == null || vcf_mixed == null || vcf_donor == null) { - error("RNA matrix, HTO matrix, BAM file, BAM index file, barcodes file, number of samples, cell data, VCF file for mixed samples, and VCF file for donor samples must be provided for rescue mode. Please check your input samplesheet.") + if (rna_matrix == null || hto_matrix == null || bam == null || barcodes == null || nsample == null || cell_data == null || vcf == null) { + error("RNA matrix, HTO matrix, BAM file, barcodes file, number of samples, cell data, and VCF file must be provided for rescue mode. Please check your input samplesheet.") } } diff --git a/workflows/hadge.nf b/workflows/hadge.nf index c9b37c4d..c56126ae 100644 --- a/workflows/hadge.nf +++ b/workflows/hadge.nf @@ -28,12 +28,12 @@ workflow HADGE { ch_versions = Channel.empty() ch_multiqc_files = Channel.empty() - ch_hashing = ch_samplesheet.map { meta, rna_matrix, hto_matrix, _bam, _bai, _barcodes, _nsample, _vcf_mixed, _vcf_donor -> + ch_hashing = ch_samplesheet.map { meta, rna_matrix, hto_matrix, _bam, _barcodes, _nsample, _vcf -> [meta, rna_matrix, hto_matrix] } - ch_genetic = ch_samplesheet.map { meta, _rna_matrix, _hto_matrix, bam, bai, barcodes, nsample, vcf_mixed, vcf_donor -> - [meta, bam, bai, barcodes, nsample, vcf_mixed, vcf_donor] + ch_genetic = ch_samplesheet.map { meta, _rna_matrix, _hto_matrix, bam, barcodes, nsample, vcf -> + [meta, bam, barcodes, nsample, vcf] } if (params.mode == 'genetic') { From 2167d67f2f7d40958572f88b38fa644ed5cf7fea Mon Sep 17 00:00:00 2001 From: nictru Date: Sat, 14 Jun 2025 14:23:27 +0200 Subject: [PATCH 18/74] Add POPSCLE_DSCPILEUP --- subworkflows/local/genetic_demultiplexing/main.nf | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/subworkflows/local/genetic_demultiplexing/main.nf b/subworkflows/local/genetic_demultiplexing/main.nf index 39d18d2d..69629f95 100644 --- a/subworkflows/local/genetic_demultiplexing/main.nf +++ b/subworkflows/local/genetic_demultiplexing/main.nf @@ -1,5 +1,5 @@ -include { POPSCLE_DSCPILEUP } from '../../../modules/nf-core/popscle/dscpileup' include { SAMTOOLS_INDEX } from '../../../modules/nf-core/samtools/index' +include { POPSCLE_DSCPILEUP } from '../../../modules/nf-core/popscle/dscpileup' workflow GENETIC_DEMULTIPLEXING { take: @@ -14,6 +14,8 @@ workflow GENETIC_DEMULTIPLEXING { ch_versions = ch_versions.mix(SAMTOOLS_INDEX.out.versions) if (methods.contains('demuxlet') || methods.contains('freemuxlet')) { + POPSCLE_DSCPILEUP(ch_samplesheet.map { meta, bam, _barcodes, _nsample, vcf -> [meta, bam, vcf] }) + ch_versions = ch_versions.mix(POPSCLE_DSCPILEUP.out.versions) } if (methods.contains('vireo')) { From 83efd52b973643dbbfc3218320f2d8f5a1e4e951 Mon Sep 17 00:00:00 2001 From: nictru Date: Sat, 14 Jun 2025 14:29:35 +0200 Subject: [PATCH 19/74] Implement demuxlet --- conf/modules.config | 13 ++++++++----- subworkflows/local/genetic_demultiplexing/main.nf | 3 +++ 2 files changed, 11 insertions(+), 5 deletions(-) diff --git a/conf/modules.config b/conf/modules.config index f0b0d55a..16abccc7 100644 --- a/conf/modules.config +++ b/conf/modules.config @@ -15,16 +15,19 @@ process { publishDir = [ path: { "${params.outdir}/${task.process.tokenize(':')[-1].tokenize('_')[0].toLowerCase()}" }, mode: params.publish_dir_mode, - saveAs: { filename -> filename.equals('versions.yml') ? null : filename } + saveAs: { filename -> filename.equals('versions.yml') ? null : filename }, ] - withName: 'MULTIQC' { - ext.args = { params.multiqc_title ? "--title \"$params.multiqc_title\"" : '' } + withName: POPSCLE_DEMUXLET { + ext.args = '--field GT' + } + + withName: MULTIQC { + ext.args = { params.multiqc_title ? "--title \"${params.multiqc_title}\"" : '' } publishDir = [ path: { "${params.outdir}/multiqc" }, mode: params.publish_dir_mode, - saveAs: { filename -> filename.equals('versions.yml') ? null : filename } + saveAs: { filename -> filename.equals('versions.yml') ? null : filename }, ] } - } diff --git a/subworkflows/local/genetic_demultiplexing/main.nf b/subworkflows/local/genetic_demultiplexing/main.nf index 69629f95..b50db9c5 100644 --- a/subworkflows/local/genetic_demultiplexing/main.nf +++ b/subworkflows/local/genetic_demultiplexing/main.nf @@ -1,5 +1,6 @@ include { SAMTOOLS_INDEX } from '../../../modules/nf-core/samtools/index' include { POPSCLE_DSCPILEUP } from '../../../modules/nf-core/popscle/dscpileup' +include { POPSCLE_DEMUXLET } from '../../../modules/nf-core/popscle/demuxlet' workflow GENETIC_DEMULTIPLEXING { take: @@ -22,6 +23,8 @@ workflow GENETIC_DEMULTIPLEXING { error("Vireo not implemented") } if (methods.contains('demuxlet')) { + POPSCLE_DEMUXLET(ch_samplesheet.map { meta, bam, _barcodes, _nsample, vcf -> [meta, [], bam, vcf] }) + ch_versions = ch_versions.mix(POPSCLE_DEMUXLET.out.versions) } if (methods.contains('freemuxlet')) { error("Freemuxlet not implemented") From b2ab2987a411d65421103e75b24b4edf8db6e033 Mon Sep 17 00:00:00 2001 From: nictru Date: Sat, 14 Jun 2025 15:25:05 +0200 Subject: [PATCH 20/74] Implement freemuxlet --- modules/nf-core/popscle/dscpileup/main.nf | 54 ++++++++++--------- .../local/genetic_demultiplexing/main.nf | 12 +++-- .../local/utils_nfcore_hadge_pipeline/main.nf | 6 +++ 3 files changed, 43 insertions(+), 29 deletions(-) diff --git a/modules/nf-core/popscle/dscpileup/main.nf b/modules/nf-core/popscle/dscpileup/main.nf index 2f7fbf3a..f4cde4b1 100644 --- a/modules/nf-core/popscle/dscpileup/main.nf +++ b/modules/nf-core/popscle/dscpileup/main.nf @@ -1,57 +1,61 @@ process POPSCLE_DSCPILEUP { - tag "$meta.id" + tag "${meta.id}" label 'process_medium' conda "${moduleDir}/environment.yml" - container "${ workflow.containerEngine == 'singularity' && !task.ext.singularity_pull_docker_container ? - 'https://depot.galaxyproject.org/singularity/popscle:0.1beta--h2c78cec_0' : - 'biocontainers/popscle:0.1beta--h2c78cec_0' }" + container "${workflow.containerEngine == 'singularity' && !task.ext.singularity_pull_docker_container + ? 'https://depot.galaxyproject.org/singularity/popscle:0.1beta--h2c78cec_0' + : 'biocontainers/popscle:0.1beta--h2c78cec_0'}" input: tuple val(meta), path(bam), path(vcf) output: - tuple val(meta), path('*.cel.gz'), emit: cel - tuple val(meta), path('*.plp.gz'), emit: plp - tuple val(meta), path('*.var.gz'), emit: var - tuple val(meta), path('*.umi.gz'), emit: umi - path 'versions.yml' , emit: versions + tuple val(meta), path("${prefix}"), emit: directory + tuple val(meta), path("${prefix}/*.cel.gz"), emit: cel + tuple val(meta), path("${prefix}/*.plp.gz"), emit: plp + tuple val(meta), path("${prefix}/*.var.gz"), emit: var + tuple val(meta), path("${prefix}/*.umi.gz"), emit: umi + path 'versions.yml', emit: versions when: task.ext.when == null || task.ext.when script: - def args = task.ext.args ?: '' - def prefix = task.ext.prefix ?: "${meta.id}" - def VERSION = '0.1' // WARN: Version information not provided by tool on CLI. Please update version string below when bumping container versions. + def args = task.ext.args ?: '' + prefix = task.ext.prefix ?: "${meta.id}" + def VERSION = '0.1' + // WARN: Version information not provided by tool on CLI. Please update version string below when bumping container versions. """ + mkdir -p "${prefix}" popscle dsc-pileup \\ - --sam $bam \\ - --vcf $vcf \\ - --out $prefix \\ - $args \\ + --sam ${bam} \\ + --vcf ${vcf} \\ + --out ${prefix}/${prefix} \\ + ${args} \\ cat <<-END_VERSIONS > versions.yml "${task.process}": - popscle dsc-pileup: $VERSION + popscle dsc-pileup: ${VERSION} END_VERSIONS """ stub: - def args = task.ext.args ?: '' - def prefix = task.ext.prefix ?: "${meta.id}" - def VERSION = '0.1' // WARN: Version information not provided by tool on CLI. Please update version string below when bumping container versions. + prefix = task.ext.prefix ?: "${meta.id}" + def VERSION = '0.1' + // WARN: Version information not provided by tool on CLI. Please update version string below when bumping container versions. """ - touch ${prefix}.cel.gz - touch ${prefix}.var.gz - touch ${prefix}.plp.gz - touch ${prefix}.umi.gz + mkdir -p ${prefix} + touch ${prefix}/${prefix}.cel.gz + touch ${prefix}/${prefix}.var.gz + touch ${prefix}/${prefix}.plp.gz + touch ${prefix}/${prefix}.umi.gz cat <<-END_VERSIONS > versions.yml "${task.process}": - popscle dsc-pileup: $VERSION + popscle dsc-pileup: ${VERSION} END_VERSIONS """ } diff --git a/subworkflows/local/genetic_demultiplexing/main.nf b/subworkflows/local/genetic_demultiplexing/main.nf index b50db9c5..23dc6def 100644 --- a/subworkflows/local/genetic_demultiplexing/main.nf +++ b/subworkflows/local/genetic_demultiplexing/main.nf @@ -1,6 +1,7 @@ -include { SAMTOOLS_INDEX } from '../../../modules/nf-core/samtools/index' -include { POPSCLE_DSCPILEUP } from '../../../modules/nf-core/popscle/dscpileup' -include { POPSCLE_DEMUXLET } from '../../../modules/nf-core/popscle/demuxlet' +include { SAMTOOLS_INDEX } from '../../../modules/nf-core/samtools/index' +include { POPSCLE_DSCPILEUP } from '../../../modules/nf-core/popscle/dscpileup' +include { POPSCLE_DEMUXLET } from '../../../modules/nf-core/popscle/demuxlet' +include { POPSCLE_FREEMUXLET } from '../../../modules/nf-core/popscle/freemuxlet' workflow GENETIC_DEMULTIPLEXING { take: @@ -27,7 +28,10 @@ workflow GENETIC_DEMULTIPLEXING { ch_versions = ch_versions.mix(POPSCLE_DEMUXLET.out.versions) } if (methods.contains('freemuxlet')) { - error("Freemuxlet not implemented") + POPSCLE_FREEMUXLET( + POPSCLE_DSCPILEUP.out.directory.join(ch_samplesheet).map { meta, plp_dir, _bam, _barcodes, n_sample, _vcf -> [meta, plp_dir, n_sample] } + ) + ch_versions = ch_versions.mix(POPSCLE_FREEMUXLET.out.versions) } if (methods.contains('souporcell')) { error("Souporcell not implemented") diff --git a/subworkflows/local/utils_nfcore_hadge_pipeline/main.nf b/subworkflows/local/utils_nfcore_hadge_pipeline/main.nf index d4f5ffd0..0163264a 100644 --- a/subworkflows/local/utils_nfcore_hadge_pipeline/main.nf +++ b/subworkflows/local/utils_nfcore_hadge_pipeline/main.nf @@ -153,9 +153,15 @@ def validateInputSamplesheet(input) { } } else if (params.mode == 'genetic') { + def methods = params.genetic_tools.split(',') + if (bam == null || vcf == null) { error("BAM file and VCF file must be provided for genetic mode. Please check your input samplesheet.") } + + if (methods.contains('freemuxlet') && nsample == null) { + error("Number of samples not provided for sample ${_meta.id}. This is required for freemuxlet.") + } } else if (params.mode == 'rescue') { if (rna_matrix == null || hto_matrix == null || bam == null || barcodes == null || nsample == null || cell_data == null || vcf == null) { From 762eb1238adcaee0b6fa5b68b9e84316c2338f8b Mon Sep 17 00:00:00 2001 From: nictru Date: Sat, 14 Jun 2025 15:40:04 +0200 Subject: [PATCH 21/74] Implement cellsnp --- subworkflows/local/genetic_demultiplexing/main.nf | 13 ++++++++++--- .../local/utils_nfcore_hadge_pipeline/main.nf | 6 +++++- 2 files changed, 15 insertions(+), 4 deletions(-) diff --git a/subworkflows/local/genetic_demultiplexing/main.nf b/subworkflows/local/genetic_demultiplexing/main.nf index 23dc6def..45bce0c9 100644 --- a/subworkflows/local/genetic_demultiplexing/main.nf +++ b/subworkflows/local/genetic_demultiplexing/main.nf @@ -1,4 +1,5 @@ include { SAMTOOLS_INDEX } from '../../../modules/nf-core/samtools/index' +include { CELLSNP_MODEA } from '../../../modules/nf-core/cellsnp/modea' include { POPSCLE_DSCPILEUP } from '../../../modules/nf-core/popscle/dscpileup' include { POPSCLE_DEMUXLET } from '../../../modules/nf-core/popscle/demuxlet' include { POPSCLE_FREEMUXLET } from '../../../modules/nf-core/popscle/freemuxlet' @@ -12,8 +13,6 @@ workflow GENETIC_DEMULTIPLEXING { ch_versions = Channel.empty() - SAMTOOLS_INDEX(ch_samplesheet.map { meta, bam, _barcodes, _nsample, _vcf -> [meta, bam] }) - ch_versions = ch_versions.mix(SAMTOOLS_INDEX.out.versions) if (methods.contains('demuxlet') || methods.contains('freemuxlet')) { POPSCLE_DSCPILEUP(ch_samplesheet.map { meta, bam, _barcodes, _nsample, vcf -> [meta, bam, vcf] }) @@ -21,7 +20,15 @@ workflow GENETIC_DEMULTIPLEXING { } if (methods.contains('vireo')) { - error("Vireo not implemented") + SAMTOOLS_INDEX(ch_samplesheet.map { meta, bam, _barcodes, _nsample, _vcf -> [meta, bam] }) + ch_versions = ch_versions.mix(SAMTOOLS_INDEX.out.versions) + + CELLSNP_MODEA( + ch_samplesheet + .join(SAMTOOLS_INDEX.out.bai) + .map { meta, bam, barcodes, _nsample, vcf, bai -> [meta, bam, bai, vcf, barcodes] } + ) + ch_versions = ch_versions.mix(CELLSNP_MODEA.out.versions) } if (methods.contains('demuxlet')) { POPSCLE_DEMUXLET(ch_samplesheet.map { meta, bam, _barcodes, _nsample, vcf -> [meta, [], bam, vcf] }) diff --git a/subworkflows/local/utils_nfcore_hadge_pipeline/main.nf b/subworkflows/local/utils_nfcore_hadge_pipeline/main.nf index 0163264a..2778342e 100644 --- a/subworkflows/local/utils_nfcore_hadge_pipeline/main.nf +++ b/subworkflows/local/utils_nfcore_hadge_pipeline/main.nf @@ -159,9 +159,13 @@ def validateInputSamplesheet(input) { error("BAM file and VCF file must be provided for genetic mode. Please check your input samplesheet.") } - if (methods.contains('freemuxlet') && nsample == null) { + if (methods.contains('freemuxlet') && !nsample) { error("Number of samples not provided for sample ${_meta.id}. This is required for freemuxlet.") } + + if (methods.contains('vireo') && !barcodes) { + error("Barcodes file must be provided for vireo. Please check your input samplesheet.") + } } else if (params.mode == 'rescue') { if (rna_matrix == null || hto_matrix == null || bam == null || barcodes == null || nsample == null || cell_data == null || vcf == null) { From 9f51849729c2b589059f5b4792a51922c8d9a991 Mon Sep 17 00:00:00 2001 From: nictru Date: Sat, 14 Jun 2025 15:48:47 +0200 Subject: [PATCH 22/74] Implement vireo --- conf/modules.config | 4 ++ modules/nf-core/cellsnp/modea/main.nf | 42 +++++++++---------- .../local/genetic_demultiplexing/main.nf | 8 ++++ 3 files changed, 33 insertions(+), 21 deletions(-) diff --git a/conf/modules.config b/conf/modules.config index 16abccc7..642b6732 100644 --- a/conf/modules.config +++ b/conf/modules.config @@ -22,6 +22,10 @@ process { ext.args = '--field GT' } + withName: CELLSNP_MODEA { + ext.args = '--genotype' + } + withName: MULTIQC { ext.args = { params.multiqc_title ? "--title \"${params.multiqc_title}\"" : '' } publishDir = [ diff --git a/modules/nf-core/cellsnp/modea/main.nf b/modules/nf-core/cellsnp/modea/main.nf index 048a2449..6188ad77 100644 --- a/modules/nf-core/cellsnp/modea/main.nf +++ b/modules/nf-core/cellsnp/modea/main.nf @@ -1,42 +1,42 @@ process CELLSNP_MODEA { - tag "$meta.id" + tag "${meta.id}" label 'process_medium' conda "${moduleDir}/environment.yml" - container "${ workflow.containerEngine == 'singularity' && !task.ext.singularity_pull_docker_container ? - 'https://depot.galaxyproject.org/singularity/cellsnp-lite:1.2.3--h6141fd1_2' : - 'biocontainers/cellsnp-lite:1.2.3--h6141fd1_2' }" + container "${workflow.containerEngine == 'singularity' && !task.ext.singularity_pull_docker_container + ? 'https://depot.galaxyproject.org/singularity/cellsnp-lite:1.2.3--h6141fd1_2' + : 'biocontainers/cellsnp-lite:1.2.3--h6141fd1_2'}" input: tuple val(meta), path(bam), path(bai), path(region_vcf), path(barcode) output: - tuple val(meta), path('*.base.vcf.gz') , emit: base - tuple val(meta), path('*.cells.vcf.gz'), emit: cell , optional: true - tuple val(meta), path('*.samples.tsv') , emit: sample - tuple val(meta), path('*.tag.AD.mtx') , emit: allele_depth - tuple val(meta), path('*.tag.DP.mtx') , emit: depth_coverage - tuple val(meta), path('*.tag.OTH.mtx') , emit: depth_other - path 'versions.yml' , emit: versions + tuple val(meta), path('*.base.vcf.gz'), emit: base + tuple val(meta), path('*.cells.vcf.gz'), emit: cell, optional: true + tuple val(meta), path('*.samples.tsv'), emit: sample + tuple val(meta), path('*.tag.AD.mtx'), emit: allele_depth + tuple val(meta), path('*.tag.DP.mtx'), emit: depth_coverage + tuple val(meta), path('*.tag.OTH.mtx'), emit: depth_other + path 'versions.yml', emit: versions when: task.ext.when == null || task.ext.when script: - def args = task.ext.args ?: '' - def prefix = task.ext.prefix ?: "${meta.id}" - def region_file = region_vcf ? "-R $region_vcf" : '' + def args = task.ext.args ?: '' + def prefix = task.ext.prefix ?: "${meta.id}" + def region_file = region_vcf ? "-R ${region_vcf}" : '' """ - cellsnp-lite -s $bam \\ - -b $barcode \\ - $region_file \\ + cellsnp-lite -s ${bam} \\ + -b ${barcode} \\ + ${region_file} \\ -O . \\ --gzip \\ - --nproc $task.cpus \\ - $args + --nproc ${task.cpus} \\ + ${args} mv cellSNP.base.vcf.gz ${prefix}.base.vcf.gz - if [[ "$args" == *"--genotype"* ]]; then + if [[ "${args}" == *"--genotype"* ]]; then mv cellSNP.cells.vcf.gz ${prefix}.cells.vcf.gz fi mv cellSNP.tag.AD.mtx ${prefix}.tag.AD.mtx @@ -53,7 +53,7 @@ process CELLSNP_MODEA { stub: def prefix = task.ext.prefix ?: "${meta.id}" """ - mkdir $prefix + mkdir ${prefix} echo "" | gzip > ${prefix}.base.vcf.gz touch ${prefix}.samples.tsv touch ${prefix}.tag.AD.mtx diff --git a/subworkflows/local/genetic_demultiplexing/main.nf b/subworkflows/local/genetic_demultiplexing/main.nf index 45bce0c9..a973200d 100644 --- a/subworkflows/local/genetic_demultiplexing/main.nf +++ b/subworkflows/local/genetic_demultiplexing/main.nf @@ -1,5 +1,6 @@ include { SAMTOOLS_INDEX } from '../../../modules/nf-core/samtools/index' include { CELLSNP_MODEA } from '../../../modules/nf-core/cellsnp/modea' +include { VIREO } from '../../../modules/nf-core/vireo' include { POPSCLE_DSCPILEUP } from '../../../modules/nf-core/popscle/dscpileup' include { POPSCLE_DEMUXLET } from '../../../modules/nf-core/popscle/demuxlet' include { POPSCLE_FREEMUXLET } from '../../../modules/nf-core/popscle/freemuxlet' @@ -29,6 +30,13 @@ workflow GENETIC_DEMULTIPLEXING { .map { meta, bam, barcodes, _nsample, vcf, bai -> [meta, bam, bai, vcf, barcodes] } ) ch_versions = ch_versions.mix(CELLSNP_MODEA.out.versions) + + VIREO( + ch_samplesheet + .join(CELLSNP_MODEA.out.cell) + .map { meta, _bam, _barcodes, nsample, vcf, cell -> [meta, cell, nsample, vcf, []] } + ) + ch_versions = ch_versions.mix(VIREO.out.versions) } if (methods.contains('demuxlet')) { POPSCLE_DEMUXLET(ch_samplesheet.map { meta, bam, _barcodes, _nsample, vcf -> [meta, [], bam, vcf] }) From d7b92294cc045724b38c2497b0ce393847aed445 Mon Sep 17 00:00:00 2001 From: nictru Date: Sat, 14 Jun 2025 16:47:29 +0200 Subject: [PATCH 23/74] Implement BAM QC --- conf/modules.config | 13 + modules.json | 15 + modules/nf-core/samtools/sort/environment.yml | 8 + modules/nf-core/samtools/sort/main.nf | 72 ++ modules/nf-core/samtools/sort/meta.yml | 92 ++ .../nf-core/samtools/sort/tests/main.nf.test | 192 ++++ .../samtools/sort/tests/main.nf.test.snap | 287 ++++++ .../samtools/sort/tests/nextflow.config | 8 + .../samtools/sort/tests/nextflow_cram.config | 8 + modules/nf-core/samtools/view/environment.yml | 9 + modules/nf-core/samtools/view/main.nf | 104 ++ modules/nf-core/samtools/view/meta.yml | 145 +++ .../nf-core/samtools/view/tests/bam.config | 3 + .../samtools/view/tests/bam_index.config | 3 + .../samtools/view/tests/cram_index.config | 3 + .../nf-core/samtools/view/tests/main.nf.test | 463 +++++++++ .../samtools/view/tests/main.nf.test.snap | 972 ++++++++++++++++++ .../nf-core/umitools/dedup/environment.yml | 7 + modules/nf-core/umitools/dedup/main.nf | 63 ++ modules/nf-core/umitools/dedup/meta.yml | 101 ++ .../nf-core/umitools/dedup/tests/main.nf.test | 188 ++++ .../umitools/dedup/tests/main.nf.test.snap | 122 +++ .../umitools/dedup/tests/nextflow.config | 5 + nextflow.config | 1 + nextflow_schema.json | 6 + subworkflows/local/bam_qc/main.nf | 29 + .../local/genetic_demultiplexing/main.nf | 19 +- workflows/hadge.nf | 17 +- 28 files changed, 2939 insertions(+), 16 deletions(-) create mode 100644 modules/nf-core/samtools/sort/environment.yml create mode 100644 modules/nf-core/samtools/sort/main.nf create mode 100644 modules/nf-core/samtools/sort/meta.yml create mode 100644 modules/nf-core/samtools/sort/tests/main.nf.test create mode 100644 modules/nf-core/samtools/sort/tests/main.nf.test.snap create mode 100644 modules/nf-core/samtools/sort/tests/nextflow.config create mode 100644 modules/nf-core/samtools/sort/tests/nextflow_cram.config create mode 100644 modules/nf-core/samtools/view/environment.yml create mode 100644 modules/nf-core/samtools/view/main.nf create mode 100644 modules/nf-core/samtools/view/meta.yml create mode 100644 modules/nf-core/samtools/view/tests/bam.config create mode 100644 modules/nf-core/samtools/view/tests/bam_index.config create mode 100644 modules/nf-core/samtools/view/tests/cram_index.config create mode 100644 modules/nf-core/samtools/view/tests/main.nf.test create mode 100644 modules/nf-core/samtools/view/tests/main.nf.test.snap create mode 100644 modules/nf-core/umitools/dedup/environment.yml create mode 100644 modules/nf-core/umitools/dedup/main.nf create mode 100644 modules/nf-core/umitools/dedup/meta.yml create mode 100644 modules/nf-core/umitools/dedup/tests/main.nf.test create mode 100644 modules/nf-core/umitools/dedup/tests/main.nf.test.snap create mode 100644 modules/nf-core/umitools/dedup/tests/nextflow.config create mode 100644 subworkflows/local/bam_qc/main.nf diff --git a/conf/modules.config b/conf/modules.config index 642b6732..c85bfd46 100644 --- a/conf/modules.config +++ b/conf/modules.config @@ -18,6 +18,19 @@ process { saveAs: { filename -> filename.equals('versions.yml') ? null : filename }, ] + withName: SAMTOOLS_VIEW { + ext.args = '-S -b -q 10 -F 3844' + } + + withName: UMITOOLS_DEDUP { + ext.prefix = { "${meta.id}_dedup" } + ext.args = '--extract-umi-method=tag --umi-tag=UR --cell-tag=CB' + } + + withName: SAMTOOLS_SORT { + ext.prefix = { "${meta.id}_sorted" } + } + withName: POPSCLE_DEMUXLET { ext.args = '--field GT' } diff --git a/modules.json b/modules.json index 64102ea0..af3259f2 100644 --- a/modules.json +++ b/modules.json @@ -46,6 +46,21 @@ "git_sha": "d090922b1a4b80ba283186459ababf8e308abcbb", "installed_by": ["modules"] }, + "samtools/sort": { + "branch": "master", + "git_sha": "05954dab2ff481bcb999f24455da29a5828af08d", + "installed_by": ["modules"] + }, + "samtools/view": { + "branch": "master", + "git_sha": "d090922b1a4b80ba283186459ababf8e308abcbb", + "installed_by": ["modules"] + }, + "umitools/dedup": { + "branch": "master", + "git_sha": "05954dab2ff481bcb999f24455da29a5828af08d", + "installed_by": ["modules"] + }, "vireo": { "branch": "master", "git_sha": "05954dab2ff481bcb999f24455da29a5828af08d", diff --git a/modules/nf-core/samtools/sort/environment.yml b/modules/nf-core/samtools/sort/environment.yml new file mode 100644 index 00000000..62054fc9 --- /dev/null +++ b/modules/nf-core/samtools/sort/environment.yml @@ -0,0 +1,8 @@ +--- +# yaml-language-server: $schema=https://raw.githubusercontent.com/nf-core/modules/master/modules/environment-schema.json +channels: + - conda-forge + - bioconda +dependencies: + - bioconda::htslib=1.21 + - bioconda::samtools=1.21 diff --git a/modules/nf-core/samtools/sort/main.nf b/modules/nf-core/samtools/sort/main.nf new file mode 100644 index 00000000..caf3c61a --- /dev/null +++ b/modules/nf-core/samtools/sort/main.nf @@ -0,0 +1,72 @@ +process SAMTOOLS_SORT { + tag "$meta.id" + label 'process_medium' + + conda "${moduleDir}/environment.yml" + container "${ workflow.containerEngine == 'singularity' && !task.ext.singularity_pull_docker_container ? + 'https://depot.galaxyproject.org/singularity/samtools:1.21--h50ea8bc_0' : + 'biocontainers/samtools:1.21--h50ea8bc_0' }" + + input: + tuple val(meta) , path(bam) + tuple val(meta2), path(fasta) + + output: + tuple val(meta), path("*.bam"), emit: bam, optional: true + tuple val(meta), path("*.cram"), emit: cram, optional: true + tuple val(meta), path("*.crai"), emit: crai, optional: true + tuple val(meta), path("*.csi"), emit: csi, optional: true + path "versions.yml", emit: versions + + when: + task.ext.when == null || task.ext.when + + script: + def args = task.ext.args ?: '' + def prefix = task.ext.prefix ?: "${meta.id}" + def extension = args.contains("--output-fmt sam") ? "sam" : + args.contains("--output-fmt cram") ? "cram" : + "bam" + def reference = fasta ? "--reference ${fasta}" : "" + if ("$bam" == "${prefix}.bam") error "Input and output names are the same, use \"task.ext.prefix\" to disambiguate!" + + """ + samtools cat \\ + ${bam} \\ + | \\ + samtools sort \\ + $args \\ + -T ${prefix} \\ + --threads $task.cpus \\ + ${reference} \\ + -o ${prefix}.${extension} \\ + - + + cat <<-END_VERSIONS > versions.yml + "${task.process}": + samtools: \$(echo \$(samtools --version 2>&1) | sed 's/^.*samtools //; s/Using.*\$//') + END_VERSIONS + """ + + stub: + def args = task.ext.args ?: '' + def prefix = task.ext.prefix ?: "${meta.id}" + def extension = args.contains("--output-fmt sam") ? "sam" : + args.contains("--output-fmt cram") ? "cram" : + "bam" + """ + touch ${prefix}.${extension} + if [ "${extension}" == "bam" ]; + then + touch ${prefix}.${extension}.csi + elif [ "${extension}" == "cram" ]; + then + touch ${prefix}.${extension}.crai + fi + + cat <<-END_VERSIONS > versions.yml + "${task.process}": + samtools: \$(echo \$(samtools --version 2>&1) | sed 's/^.*samtools //; s/Using.*\$//') + END_VERSIONS + """ +} diff --git a/modules/nf-core/samtools/sort/meta.yml b/modules/nf-core/samtools/sort/meta.yml new file mode 100644 index 00000000..a9dbec5a --- /dev/null +++ b/modules/nf-core/samtools/sort/meta.yml @@ -0,0 +1,92 @@ +name: samtools_sort +description: Sort SAM/BAM/CRAM file +keywords: + - sort + - bam + - sam + - cram +tools: + - samtools: + description: | + SAMtools is a set of utilities for interacting with and post-processing + short DNA sequence read alignments in the SAM, BAM and CRAM formats, written by Heng Li. + These files are generated as output by short read aligners like BWA. + homepage: http://www.htslib.org/ + documentation: http://www.htslib.org/doc/samtools.html + doi: 10.1093/bioinformatics/btp352 + licence: ["MIT"] + identifier: biotools:samtools +input: + - - meta: + type: map + description: | + Groovy Map containing sample information + e.g. [ id:'test', single_end:false ] + - bam: + type: file + description: BAM/CRAM/SAM file(s) + pattern: "*.{bam,cram,sam}" + - - meta2: + type: map + description: | + Groovy Map containing reference information + e.g. [ id:'genome' ] + - fasta: + type: file + description: Reference genome FASTA file + pattern: "*.{fa,fasta,fna}" + optional: true +output: + - bam: + - meta: + type: map + description: | + Groovy Map containing sample information + e.g. [ id:'test', single_end:false ] + - "*.bam": + type: file + description: Sorted BAM file + pattern: "*.{bam}" + - cram: + - meta: + type: map + description: | + Groovy Map containing sample information + e.g. [ id:'test', single_end:false ] + - "*.cram": + type: file + description: Sorted CRAM file + pattern: "*.{cram}" + - crai: + - meta: + type: map + description: | + Groovy Map containing sample information + e.g. [ id:'test', single_end:false ] + - "*.crai": + type: file + description: CRAM index file (optional) + pattern: "*.crai" + - csi: + - meta: + type: map + description: | + Groovy Map containing sample information + e.g. [ id:'test', single_end:false ] + - "*.csi": + type: file + description: BAM index file (optional) + pattern: "*.csi" + - versions: + - versions.yml: + type: file + description: File containing software versions + pattern: "versions.yml" +authors: + - "@drpatelh" + - "@ewels" + - "@matthdsm" +maintainers: + - "@drpatelh" + - "@ewels" + - "@matthdsm" diff --git a/modules/nf-core/samtools/sort/tests/main.nf.test b/modules/nf-core/samtools/sort/tests/main.nf.test new file mode 100644 index 00000000..b05e6691 --- /dev/null +++ b/modules/nf-core/samtools/sort/tests/main.nf.test @@ -0,0 +1,192 @@ +nextflow_process { + + name "Test Process SAMTOOLS_SORT" + script "../main.nf" + process "SAMTOOLS_SORT" + tag "modules" + tag "modules_nfcore" + tag "samtools" + tag "samtools/sort" + + test("bam") { + + config "./nextflow.config" + + when { + process { + """ + input[0] = Channel.of([ + [ id:'test', single_end:false ], // meta map + file(params.modules_testdata_base_path + 'genomics/sarscov2/illumina/bam/test.paired_end.bam', checkIfExists: true) + ]) + input[1] = Channel.of([ + [ id:'fasta' ], // meta map + file(params.modules_testdata_base_path + 'genomics/sarscov2/genome/genome.fasta', checkIfExists: true) + ]) + """ + } + } + + then { + assertAll ( + { assert process.success }, + { assert snapshot( + process.out.bam, + process.out.csi.collect { it.collect { it instanceof Map ? it : file(it).name } }, + process.out.versions + ).match()} + ) + } + } + + test("multiple bam") { + + config "./nextflow.config" + + when { + process { + """ + input[0] = Channel.of([ + [ id:'test', single_end:false ], // meta map + [ + file(params.modules_testdata_base_path + 'genomics/homo_sapiens/illumina/bam/test.paired_end.sorted.bam', checkIfExists: true), + file(params.modules_testdata_base_path + 'genomics/homo_sapiens/illumina/bam/test2.paired_end.sorted.bam', checkIfExists: true) + ] + ]) + input[1] = Channel.of([ + [ id:'fasta' ], // meta map + file(params.modules_testdata_base_path + 'genomics/homo_sapiens/genome/genome.fasta', checkIfExists: true) + ]) + """ + } + } + + then { + assertAll ( + { assert process.success }, + { assert snapshot( + process.out.bam, + process.out.csi.collect { it.collect { it instanceof Map ? it : file(it).name } }, + process.out.versions + ).match()} + ) + } + } + + test("cram") { + + config "./nextflow_cram.config" + + when { + process { + """ + input[0] = Channel.of([ + [ id:'test', single_end:false ], // meta map + file(params.modules_testdata_base_path + 'genomics/homo_sapiens/illumina/cram/test.paired_end.sorted.cram', checkIfExists: true) + ]) + input[1] = Channel.of([ + [ id:'fasta' ], // meta map + file(params.modules_testdata_base_path + 'genomics/homo_sapiens/genome/genome.fasta', checkIfExists: true) + ]) + """ + } + } + + then { + assertAll ( + { assert process.success }, + { assert snapshot( + process.out.cram.collect { it.collect { it instanceof Map ? it : file(it).name } }, + process.out.crai.collect { it.collect { it instanceof Map ? it : file(it).name } }, + process.out.versions + ).match()} + ) + } + } + + test("bam - stub") { + + options "-stub" + config "./nextflow.config" + + when { + process { + """ + input[0] = Channel.of([ + [ id:'test', single_end:false ], // meta map + file(params.modules_testdata_base_path + 'genomics/sarscov2/illumina/bam/test.paired_end.bam', checkIfExists: true) + ]) + input[1] = Channel.of([ + [ id:'fasta' ], // meta map + file(params.modules_testdata_base_path + 'genomics/sarscov2/genome/genome.fasta', checkIfExists: true) + ]) + """ + } + } + + then { + assertAll ( + { assert process.success }, + { assert snapshot(process.out).match() } + ) + } + } + + test("multiple bam - stub") { + + config "./nextflow.config" + + when { + process { + """ + input[0] = Channel.of([ + [ id:'test', single_end:false ], // meta map + [ + file(params.modules_testdata_base_path + 'genomics/homo_sapiens/illumina/bam/test.paired_end.sorted.bam', checkIfExists: true), + file(params.modules_testdata_base_path + 'genomics/homo_sapiens/illumina/bam/test2.paired_end.sorted.bam', checkIfExists: true) + ] + ]) + input[1] = Channel.of([ + [ id:'fasta' ], // meta map + file(params.modules_testdata_base_path + 'genomics/homo_sapiens/genome/genome.fasta', checkIfExists: true) + ]) + """ + } + } + + then { + assertAll ( + { assert process.success }, + { assert snapshot(process.out).match() } + ) + } + } + + test("cram - stub") { + + options "-stub" + config "./nextflow_cram.config" + + when { + process { + """ + input[0] = Channel.of([ + [ id:'test', single_end:false ], // meta map + file(params.modules_testdata_base_path + 'genomics/homo_sapiens/illumina/cram/test.paired_end.sorted.cram', checkIfExists: true) + ]) + input[1] = Channel.of([ + [ id:'fasta' ], // meta map + file(params.modules_testdata_base_path + 'genomics/homo_sapiens/genome/genome.fasta', checkIfExists: true) + ]) + """ + } + } + + then { + assertAll ( + { assert process.success }, + { assert snapshot(process.out).match() } + ) + } + } +} diff --git a/modules/nf-core/samtools/sort/tests/main.nf.test.snap b/modules/nf-core/samtools/sort/tests/main.nf.test.snap new file mode 100644 index 00000000..469891fe --- /dev/null +++ b/modules/nf-core/samtools/sort/tests/main.nf.test.snap @@ -0,0 +1,287 @@ +{ + "cram": { + "content": [ + [ + [ + { + "id": "test", + "single_end": false + }, + "test.sorted.cram" + ] + ], + [ + [ + { + "id": "test", + "single_end": false + }, + "test.sorted.cram.crai" + ] + ], + [ + "versions.yml:md5,2659b187d681241451539d4c53500b9f" + ] + ], + "meta": { + "nf-test": "0.9.0", + "nextflow": "24.04.4" + }, + "timestamp": "2024-09-16T08:49:58.207549273" + }, + "bam - stub": { + "content": [ + { + "0": [ + [ + { + "id": "test", + "single_end": false + }, + "test.sorted.bam:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "1": [ + + ], + "2": [ + + ], + "3": [ + [ + { + "id": "test", + "single_end": false + }, + "test.sorted.bam.csi:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "4": [ + "versions.yml:md5,2659b187d681241451539d4c53500b9f" + ], + "bam": [ + [ + { + "id": "test", + "single_end": false + }, + "test.sorted.bam:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "crai": [ + + ], + "cram": [ + + ], + "csi": [ + [ + { + "id": "test", + "single_end": false + }, + "test.sorted.bam.csi:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "versions": [ + "versions.yml:md5,2659b187d681241451539d4c53500b9f" + ] + } + ], + "meta": { + "nf-test": "0.9.0", + "nextflow": "24.04.4" + }, + "timestamp": "2024-09-16T08:50:08.630951018" + }, + "cram - stub": { + "content": [ + { + "0": [ + + ], + "1": [ + [ + { + "id": "test", + "single_end": false + }, + "test.sorted.cram:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "2": [ + [ + { + "id": "test", + "single_end": false + }, + "test.sorted.cram.crai:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "3": [ + + ], + "4": [ + "versions.yml:md5,2659b187d681241451539d4c53500b9f" + ], + "bam": [ + + ], + "crai": [ + [ + { + "id": "test", + "single_end": false + }, + "test.sorted.cram.crai:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "cram": [ + [ + { + "id": "test", + "single_end": false + }, + "test.sorted.cram:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "csi": [ + + ], + "versions": [ + "versions.yml:md5,2659b187d681241451539d4c53500b9f" + ] + } + ], + "meta": { + "nf-test": "0.9.0", + "nextflow": "24.04.4" + }, + "timestamp": "2024-09-16T08:50:19.061912443" + }, + "multiple bam": { + "content": [ + [ + [ + { + "id": "test", + "single_end": false + }, + "test.sorted.bam:md5,8a16ba90c7d294cbb4c33ac0f7127a12" + ] + ], + [ + [ + { + "id": "test", + "single_end": false + }, + "test.sorted.bam.csi" + ] + ], + [ + "versions.yml:md5,2659b187d681241451539d4c53500b9f" + ] + ], + "meta": { + "nf-test": "0.9.0", + "nextflow": "24.09.0" + }, + "timestamp": "2024-10-08T11:59:55.479443" + }, + "multiple bam - stub": { + "content": [ + { + "0": [ + [ + { + "id": "test", + "single_end": false + }, + "test.sorted.bam:md5,8a16ba90c7d294cbb4c33ac0f7127a12" + ] + ], + "1": [ + + ], + "2": [ + + ], + "3": [ + [ + { + "id": "test", + "single_end": false + }, + "test.sorted.bam.csi:md5,d185916eaff9afeb4d0aeab3310371f9" + ] + ], + "4": [ + "versions.yml:md5,2659b187d681241451539d4c53500b9f" + ], + "bam": [ + [ + { + "id": "test", + "single_end": false + }, + "test.sorted.bam:md5,8a16ba90c7d294cbb4c33ac0f7127a12" + ] + ], + "crai": [ + + ], + "cram": [ + + ], + "csi": [ + [ + { + "id": "test", + "single_end": false + }, + "test.sorted.bam.csi:md5,d185916eaff9afeb4d0aeab3310371f9" + ] + ], + "versions": [ + "versions.yml:md5,2659b187d681241451539d4c53500b9f" + ] + } + ], + "meta": { + "nf-test": "0.9.0", + "nextflow": "24.09.0" + }, + "timestamp": "2024-10-08T11:36:13.781404" + }, + "bam": { + "content": [ + [ + [ + { + "id": "test", + "single_end": false + }, + "test.sorted.bam:md5,34aa85e86abefe637f7a4a9887f016fc" + ] + ], + [ + [ + { + "id": "test", + "single_end": false + }, + "test.sorted.bam.csi" + ] + ], + [ + "versions.yml:md5,2659b187d681241451539d4c53500b9f" + ] + ], + "meta": { + "nf-test": "0.9.0", + "nextflow": "24.09.0" + }, + "timestamp": "2024-10-08T11:59:46.372244" + } +} \ No newline at end of file diff --git a/modules/nf-core/samtools/sort/tests/nextflow.config b/modules/nf-core/samtools/sort/tests/nextflow.config new file mode 100644 index 00000000..f642771f --- /dev/null +++ b/modules/nf-core/samtools/sort/tests/nextflow.config @@ -0,0 +1,8 @@ +process { + + withName: SAMTOOLS_SORT { + ext.prefix = { "${meta.id}.sorted" } + ext.args = "--write-index" + } + +} diff --git a/modules/nf-core/samtools/sort/tests/nextflow_cram.config b/modules/nf-core/samtools/sort/tests/nextflow_cram.config new file mode 100644 index 00000000..3a8c0188 --- /dev/null +++ b/modules/nf-core/samtools/sort/tests/nextflow_cram.config @@ -0,0 +1,8 @@ +process { + + withName: SAMTOOLS_SORT { + ext.prefix = { "${meta.id}.sorted" } + ext.args = "--write-index --output-fmt cram" + } + +} diff --git a/modules/nf-core/samtools/view/environment.yml b/modules/nf-core/samtools/view/environment.yml new file mode 100644 index 00000000..8cae5712 --- /dev/null +++ b/modules/nf-core/samtools/view/environment.yml @@ -0,0 +1,9 @@ +--- +# yaml-language-server: $schema=https://raw.githubusercontent.com/nf-core/modules/master/modules/environment-schema.json +channels: + - conda-forge + - bioconda +dependencies: + # renovate: datasource=conda depName=bioconda/htslib + - bioconda::htslib=1.21 + - bioconda::samtools=1.21 diff --git a/modules/nf-core/samtools/view/main.nf b/modules/nf-core/samtools/view/main.nf new file mode 100644 index 00000000..c34cab91 --- /dev/null +++ b/modules/nf-core/samtools/view/main.nf @@ -0,0 +1,104 @@ +process SAMTOOLS_VIEW { + tag "$meta.id" + label 'process_low' + + conda "${moduleDir}/environment.yml" + container "${ workflow.containerEngine == 'singularity' && !task.ext.singularity_pull_docker_container ? + 'https://depot.galaxyproject.org/singularity/samtools:1.21--h50ea8bc_0' : + 'biocontainers/samtools:1.21--h50ea8bc_0' }" + + input: + tuple val(meta), path(input), path(index) + tuple val(meta2), path(fasta) + path qname + val index_format + + output: + tuple val(meta), path("${prefix}.bam"), emit: bam, optional: true + tuple val(meta), path("${prefix}.cram"), emit: cram, optional: true + tuple val(meta), path("${prefix}.sam"), emit: sam, optional: true + tuple val(meta), path("${prefix}.${file_type}.bai"), emit: bai, optional: true + tuple val(meta), path("${prefix}.${file_type}.csi"), emit: csi, optional: true + tuple val(meta), path("${prefix}.${file_type}.crai"), emit: crai, optional: true + tuple val(meta), path("${prefix}.unselected.${file_type}"), emit: unselected, optional: true + tuple val(meta), path("${prefix}.unselected.${file_type}.{csi,crai}"), emit: unselected_index, optional: true + path "versions.yml", emit: versions + + when: + task.ext.when == null || task.ext.when + + script: + def args = task.ext.args ?: '' + def args2 = task.ext.args2 ?: '' + prefix = task.ext.prefix ?: "${meta.id}" + def reference = fasta ? "--reference ${fasta}" : "" + file_type = args.contains("--output-fmt sam") ? "sam" : + args.contains("--output-fmt bam") ? "bam" : + args.contains("--output-fmt cram") ? "cram" : + input.getExtension() + + output_file = index_format ? "${prefix}.${file_type}##idx##${prefix}.${file_type}.${index_format} --write-index" : "${prefix}.${file_type}" + // Can't choose index type of unselected file + readnames = qname ? "--qname-file ${qname} --output-unselected ${prefix}.unselected.${file_type}": "" + + if ("$input" == "${prefix}.${file_type}") error "Input and output names are the same, use \"task.ext.prefix\" to disambiguate!" + if (index_format) { + if (!index_format.matches('bai|csi|crai')) { + error "Index format not one of bai, csi, crai." + } else if (file_type == "sam") { + error "Indexing not compatible with SAM output" + } + } + """ + # Note: --threads value represents *additional* CPUs to allocate (total CPUs = 1 + --threads). + samtools \\ + view \\ + --threads ${task.cpus-1} \\ + ${reference} \\ + ${readnames} \\ + $args \\ + -o ${output_file} \\ + $input \\ + $args2 + + cat <<-END_VERSIONS > versions.yml + "${task.process}": + samtools: \$(echo \$(samtools --version 2>&1) | sed 's/^.*samtools //; s/Using.*\$//') + END_VERSIONS + """ + + stub: + def args = task.ext.args ?: '' + prefix = task.ext.prefix ?: "${meta.id}" + file_type = args.contains("--output-fmt sam") ? "sam" : + args.contains("--output-fmt bam") ? "bam" : + args.contains("--output-fmt cram") ? "cram" : + input.getExtension() + default_index_format = + file_type == "bam" ? "csi" : + file_type == "cram" ? "crai" : "" + index = index_format ? "touch ${prefix}.${file_type}.${index_format}" : args.contains("--write-index") ? "touch ${prefix}.${file_type}.${default_index_format}" : "" + unselected = qname ? "touch ${prefix}.unselected.${file_type}" : "" + // Can't choose index type of unselected file + unselected_index = qname && (args.contains("--write-index") || index_format) ? "touch ${prefix}.unselected.${file_type}.${default_index_format}" : "" + + if ("$input" == "${prefix}.${file_type}") error "Input and output names are the same, use \"task.ext.prefix\" to disambiguate!" + if (index_format) { + if (!index_format.matches('bai|csi|crai')) { + error "Index format not one of bai, csi, crai." + } else if (file_type == "sam") { + error "Indexing not compatible with SAM output." + } + } + """ + touch ${prefix}.${file_type} + ${index} + ${unselected} + ${unselected_index} + + cat <<-END_VERSIONS > versions.yml + "${task.process}": + samtools: \$(echo \$(samtools --version 2>&1) | sed 's/^.*samtools //; s/Using.*\$//') + END_VERSIONS + """ +} diff --git a/modules/nf-core/samtools/view/meta.yml b/modules/nf-core/samtools/view/meta.yml new file mode 100644 index 00000000..28c268a6 --- /dev/null +++ b/modules/nf-core/samtools/view/meta.yml @@ -0,0 +1,145 @@ +name: samtools_view +description: filter/convert SAM/BAM/CRAM file +keywords: + - view + - bam + - sam + - cram +tools: + - samtools: + description: | + SAMtools is a set of utilities for interacting with and post-processing + short DNA sequence read alignments in the SAM, BAM and CRAM formats, written by Heng Li. + These files are generated as output by short read aligners like BWA. + homepage: http://www.htslib.org/ + documentation: http://www.htslib.org/doc/samtools.html + doi: 10.1093/bioinformatics/btp352 + licence: ["MIT"] + identifier: biotools:samtools +input: + - - meta: + type: map + description: | + Groovy Map containing sample information + e.g. [ id:'test', single_end:false ] + - input: + type: file + description: BAM/CRAM/SAM file + pattern: "*.{bam,cram,sam}" + - index: + type: file + description: BAM.BAI/BAM.CSI/CRAM.CRAI file (optional) + pattern: "*.{.bai,.csi,.crai}" + - - meta2: + type: map + description: | + Groovy Map containing reference information + e.g. [ id:'test' ] + - fasta: + type: file + description: Reference file the CRAM was created with (optional) + pattern: "*.{fasta,fa}" + - - qname: + type: file + description: Optional file with read names to output only select alignments + pattern: "*.{txt,list}" + - - index_format: + type: string + description: Index format, used together with ext.args = '--write-index' + pattern: "bai|csi|crai" +output: + - bam: + - meta: + type: map + description: | + Groovy Map containing sample information + e.g. [ id:'test', single_end:false ] + - ${prefix}.bam: + type: file + description: optional filtered/converted BAM file + pattern: "*.{bam}" + - cram: + - meta: + type: map + description: | + Groovy Map containing sample information + e.g. [ id:'test', single_end:false ] + - ${prefix}.cram: + type: file + description: optional filtered/converted CRAM file + pattern: "*.{cram}" + - sam: + - meta: + type: map + description: | + Groovy Map containing sample information + e.g. [ id:'test', single_end:false ] + - ${prefix}.sam: + type: file + description: optional filtered/converted SAM file + pattern: "*.{sam}" + - bai: + - meta: + type: map + description: | + Groovy Map containing sample information + e.g. [ id:'test', single_end:false ] + - ${prefix}.${file_type}.bai: + type: file + description: optional BAM file index + pattern: "*.{bai}" + - csi: + - meta: + type: map + description: | + Groovy Map containing sample information + e.g. [ id:'test', single_end:false ] + - ${prefix}.${file_type}.csi: + type: file + description: optional tabix BAM file index + pattern: "*.{csi}" + - crai: + - meta: + type: map + description: | + Groovy Map containing sample information + e.g. [ id:'test', single_end:false ] + - ${prefix}.${file_type}.crai: + type: file + description: optional CRAM file index + pattern: "*.{crai}" + - unselected: + - meta: + type: map + description: | + Groovy Map containing sample information + e.g. [ id:'test', single_end:false ] + - ${prefix}.unselected.${file_type}: + type: file + description: optional file with unselected alignments + pattern: "*.unselected.{bam,cram,sam}" + - unselected_index: + - meta: + type: map + description: | + Groovy Map containing sample information + e.g. [ id:'test', single_end:false ] + - ${prefix}.unselected.${file_type}.{csi,crai}: + type: file + description: index for the "unselected" file + pattern: "*.unselected.{csi,crai}" + - versions: + - versions.yml: + type: file + description: File containing software versions + pattern: "versions.yml" +authors: + - "@drpatelh" + - "@joseespinosa" + - "@FriederikeHanssen" + - "@priyanka-surana" +maintainers: + - "@drpatelh" + - "@joseespinosa" + - "@FriederikeHanssen" + - "@priyanka-surana" diff --git a/modules/nf-core/samtools/view/tests/bam.config b/modules/nf-core/samtools/view/tests/bam.config new file mode 100644 index 00000000..c10d1081 --- /dev/null +++ b/modules/nf-core/samtools/view/tests/bam.config @@ -0,0 +1,3 @@ +process { + ext.args = "--output-fmt bam" +} \ No newline at end of file diff --git a/modules/nf-core/samtools/view/tests/bam_index.config b/modules/nf-core/samtools/view/tests/bam_index.config new file mode 100644 index 00000000..771ae033 --- /dev/null +++ b/modules/nf-core/samtools/view/tests/bam_index.config @@ -0,0 +1,3 @@ +process { + ext.args = "--output-fmt bam --write-index" +} \ No newline at end of file diff --git a/modules/nf-core/samtools/view/tests/cram_index.config b/modules/nf-core/samtools/view/tests/cram_index.config new file mode 100644 index 00000000..ed87c334 --- /dev/null +++ b/modules/nf-core/samtools/view/tests/cram_index.config @@ -0,0 +1,3 @@ +process { + ext.args = "--output-fmt cram --write-index" +} diff --git a/modules/nf-core/samtools/view/tests/main.nf.test b/modules/nf-core/samtools/view/tests/main.nf.test new file mode 100644 index 00000000..d8551dd8 --- /dev/null +++ b/modules/nf-core/samtools/view/tests/main.nf.test @@ -0,0 +1,463 @@ +nextflow_process { + + name "Test Process SAMTOOLS_VIEW" + script "../main.nf" + process "SAMTOOLS_VIEW" + + tag "modules" + tag "modules_nfcore" + tag "samtools" + tag "samtools/view" + + test("bam") { + + when { + process { + """ + input[0] = Channel.of([ + [ id:'test', single_end:false ], // meta map + file(params.modules_testdata_base_path + 'genomics/sarscov2/illumina/bam/test.paired_end.bam', checkIfExists: true), + [] + ]) + input[1] = [[],[]] + input[2] = [] + input[3] = [] + """ + } + } + + then { + assertAll( + { assert process.success }, + { assert snapshot(file(process.out.bam[0][1]).name).match("bam_bam") }, + { assert snapshot(process.out.bai).match("bam_bai") }, + { assert snapshot(process.out.crai).match("bam_crai") }, + { assert snapshot(process.out.cram).match("bam_cram") }, + { assert snapshot(process.out.csi).match("bam_csi") }, + { assert snapshot(process.out.sam).match("bam_sam") }, + { assert snapshot(process.out.versions).match("bam_versions") } + ) + } + } + + test("bam_csi_index") { + + config "./bam_index.config" + + when { + process { + """ + input[0] = Channel.of([ + [ id:'test', single_end:false ], // meta map + file(params.modules_testdata_base_path + 'genomics/sarscov2/illumina/bam/test.paired_end.sorted.bam', checkIfExists: true), + [] + ]) + input[1] = [[],[]] + input[2] = [] + input[3] = 'csi' + """ + } + } + + then { + assertAll( + { assert process.success }, + { assert snapshot( + file(process.out.bam[0][1]).name, + file(process.out.csi[0][1]).name, + process.out.versions).match() + } + ) + } + } + + test("bam_bai_index") { + + config "./bam_index.config" + + when { + process { + """ + input[0] = Channel.of([ + [ id:'test', single_end:false ], // meta map + file(params.modules_testdata_base_path + 'genomics/sarscov2/illumina/bam/test.paired_end.sorted.bam', checkIfExists: true), + [] + ]) + input[1] = [[],[]] + input[2] = [] + input[3] = 'bai' + """ + } + } + + then { + assertAll( + { assert process.success }, + { assert snapshot( + file(process.out.bam[0][1]).name, + file(process.out.bai[0][1]).name, + process.out.versions).match() } + ) + } + } + + test("bam_bai_index_unselected") { + + config "./bam_index.config" + + when { + process { + """ + input[0] = Channel.of([ + [ id:'test', single_end:false ], // meta map + file(params.modules_testdata_base_path + 'genomics/sarscov2/illumina/bam/test.paired_end.sorted.bam', checkIfExists: true), + [] + ]) + input[1] = [[],[]] + input[2] = Channel.of('testN:1') + .collectFile(name: 'selected_reads.txt') + input[3] = 'bai' + """ + } + } + + then { + assertAll( + { assert process.success }, + { assert snapshot( + file(process.out.bam[0][1]).name, + file(process.out.bai[0][1]).name, + file(process.out.unselected[0][1]).name, + file(process.out.unselected_index[0][1]).name, + process.out.versions).match() + } + ) + } + } + + test("cram_crai_index_unselected") { + + config "./cram_index.config" + + when { + process { + """ + input[0] = Channel.of([ + [ id:'test', single_end:false ], // meta map + file(params.modules_testdata_base_path + 'genomics/sarscov2/illumina/bam/test.paired_end.sorted.bam', checkIfExists: true), + [] + ]) + input[1] = [[],[]] + input[2] = Channel.of('testN:1') + .collectFile(name: 'selected_reads.txt') + input[3] = 'crai' + """ + } + } + + then { + assertAll( + { assert process.success }, + { assert snapshot( + file(process.out.cram[0][1]).name, + file(process.out.crai[0][1]).name, + file(process.out.unselected[0][1]).name, + file(process.out.unselected_index[0][1]).name, + process.out.versions).match() + } + ) + } + } + + test("cram") { + + when { + process { + """ + input[0] = Channel.of([ + [ id:'test', single_end:false ], // meta map + file(params.modules_testdata_base_path + 'genomics/homo_sapiens/illumina/cram/test.paired_end.sorted.cram', checkIfExists: true), + file(params.modules_testdata_base_path + 'genomics/homo_sapiens/illumina/cram/test.paired_end.sorted.cram.crai', checkIfExists: true) + ]) + input[1] = Channel.of([ + [ id:'genome' ], // meta map + file(params.modules_testdata_base_path + 'genomics/homo_sapiens/genome/genome.fasta', checkIfExists: true) + ]) + input[2] = [] + input[3] = [] + """ + } + } + + then { + assertAll( + { assert process.success }, + { assert snapshot(file(process.out.cram[0][1]).name).match("cram_cram") }, + { assert snapshot(process.out.bai).match("cram_bai") }, + { assert snapshot(process.out.bam).match("cram_bam") }, + { assert snapshot(process.out.crai).match("cram_crai") }, + { assert snapshot(process.out.csi).match("cram_csi") }, + { assert snapshot(process.out.sam).match("cram_sam") }, + { assert snapshot(process.out.versions).match("cram_versions") } + ) + } + } + + test("cram_to_bam") { + + config "./bam.config" + + when { + process { + """ + input[0] = Channel.of([ + [ id:'test', single_end:false ], // meta map + file(params.modules_testdata_base_path + 'genomics/homo_sapiens/illumina/cram/test.paired_end.sorted.cram', checkIfExists: true), + [] + ]) + input[1] = Channel.of([ + [ id:'genome' ], // meta map + file(params.modules_testdata_base_path + 'genomics/homo_sapiens/genome/genome.fasta', checkIfExists: true) + ]) + input[2] = [] + input[3] = [] + """ + } + } + + then { + assertAll( + { assert process.success }, + { assert snapshot(file(process.out.bam[0][1]).name).match("cram_to_bam_bam") }, + { assert snapshot(process.out.bai).match("cram_to_bam_bai") }, + { assert snapshot(process.out.crai).match("cram_to_bam_crai") }, + { assert snapshot(process.out.cram).match("cram_to_bam_cram") }, + { assert snapshot(process.out.csi).match("cram_to_bam_csi") }, + { assert snapshot(process.out.sam).match("cram_to_bam_sam") }, + { assert snapshot(process.out.versions).match("cram_to_bam_versions") } + ) + } + } + + test("cram_to_bam_index") { + + config "./bam_index.config" + + when { + process { + """ + input[0] = Channel.of([ + [ id:'test', single_end:false ], // meta map + file(params.modules_testdata_base_path + 'genomics/homo_sapiens/illumina/cram/test.paired_end.sorted.cram', checkIfExists: true), + [] + ]) + input[1] = Channel.of([ + [ id:'genome' ], // meta map + file(params.modules_testdata_base_path + 'genomics/homo_sapiens/genome/genome.fasta', checkIfExists: true) + ]) + input[2] = [] + input[3] = [] + """ + } + } + + then { + assertAll( + { assert process.success }, + { assert snapshot(file(process.out.bam[0][1]).name).match("cram_to_bam_index_bam") }, + { assert snapshot(file(process.out.csi[0][1]).name).match("cram_to_bam_index_csi") }, + { assert snapshot(process.out.bai).match("cram_to_bam_index_bai") }, + { assert snapshot(process.out.crai).match("cram_to_bam_index_crai") }, + { assert snapshot(process.out.cram).match("cram_to_bam_index_cram") }, + { assert snapshot(process.out.sam).match("cram_to_bam_index_sam") }, + { assert snapshot(process.out.versions).match("cram_to_bam_index_versions") } + ) + } + } + + test("cram_to_bam_index_qname") { + + config "./bam_index.config" + + when { + process { + """ + input[0] = Channel.of([ + [ id:'test', single_end:false ], // meta map + file(params.modules_testdata_base_path + 'genomics/homo_sapiens/illumina/cram/test.paired_end.sorted.cram', checkIfExists: true), + [] + ]) + input[1] = Channel.of([ + [ id:'genome' ], // meta map + file(params.modules_testdata_base_path + 'genomics/homo_sapiens/genome/genome.fasta', checkIfExists: true) + ]) + input[2] = Channel.of("testN:2817", "testN:2814").collectFile(name: "readnames.list", newLine: true) + input[3] = [] + """ + } + } + + then { + assertAll( + { assert process.success }, + { assert snapshot(file(process.out.bam[0][1]).name).match("cram_to_bam_index_qname_bam") }, + { assert snapshot(file(process.out.csi[0][1]).name).match("cram_to_bam_index_qname_csi") }, + { assert snapshot(process.out.bai).match("cram_to_bam_index_qname_bai") }, + { assert snapshot(process.out.crai).match("cram_to_bam_index_qname_crai") }, + { assert snapshot(process.out.cram).match("cram_to_bam_index_qname_cram") }, + { assert snapshot(process.out.sam).match("cram_to_bam_index_qname_sam") }, + { assert snapshot(file(process.out.unselected[0][1]).name).match("cram_to_bam_index_qname_unselected") }, + { assert snapshot(file(process.out.unselected_index[0][1]).name).match("cram_to_bam_index_qname_unselected_csi") }, + { assert snapshot(process.out.versions).match("cram_to_bam_index_qname_versions") } + ) + } + } + + test("bam_stub") { + + options "-stub" + config "./bam_index.config" + + when { + process { + """ + input[0] = Channel.of([ + [ id:'test', single_end:false ], // meta map + file(params.modules_testdata_base_path + 'genomics/sarscov2/illumina/bam/test.paired_end.bam', checkIfExists: true), + [] + ]) + input[1] = [[],[]] + input[2] = [] + input[3] = [] + """ + } + } + + then { + assertAll( + { assert process.success }, + { assert snapshot(file(process.out.bam[0][1]).name).match("bam_stub_bam") }, + { assert snapshot(file(process.out.csi[0][1]).name).match("bam_stub_csi") }, + { assert snapshot(process.out.bai).match("bam_stub_bai") }, + { assert snapshot(process.out.crai).match("bam_stub_crai") }, + { assert snapshot(process.out.cram).match("bam_stub_cram") }, + { assert snapshot(process.out.sam).match("bam_stub_sam") }, + { assert snapshot(process.out.versions).match("bam_stub_versions") } + ) + } + } + + test("bam_csi_index - stub") { + + options "-stub" + config "./bam_index.config" + + when { + process { + """ + input[0] = Channel.of([ + [ id:'test', single_end:false ], // meta map + file(params.modules_testdata_base_path + 'genomics/sarscov2/illumina/bam/test.paired_end.sorted.bam', checkIfExists: true), + [] + ]) + input[1] = [[],[]] + input[2] = [] + input[3] = 'csi' + """ + } + } + + then { + assertAll( + { assert process.success }, + { assert snapshot(process.out).match() } + ) + } + } + + test("bam_bai_index - stub") { + + options "-stub" + config "./bam_index.config" + + when { + process { + """ + input[0] = Channel.of([ + [ id:'test', single_end:false ], // meta map + file(params.modules_testdata_base_path + 'genomics/sarscov2/illumina/bam/test.paired_end.sorted.bam', checkIfExists: true), + [] + ]) + input[1] = [[],[]] + input[2] = [] + input[3] = 'bai' + """ + } + } + + then { + assertAll( + { assert process.success }, + { assert snapshot(process.out).match() } + ) + } + } + + test("bam_bai_index_uselected - stub") { + + options "-stub" + config "./bam_index.config" + + when { + process { + """ + input[0] = Channel.of([ + [ id:'test', single_end:false ], // meta map + file(params.modules_testdata_base_path + 'genomics/sarscov2/illumina/bam/test.paired_end.sorted.bam', checkIfExists: true), + [] + ]) + input[1] = [[],[]] + input[2] = Channel.of('testN:1') + .collectFile(name: 'selected_reads.txt') + input[3] = 'bai' + """ + } + } + + then { + assertAll( + { assert process.success }, + { assert snapshot(process.out).match() } + ) + } + } + + test("cram_crai_index_unselected - stub") { + + options "-stub" + config "./cram_index.config" + + when { + process { + """ + input[0] = Channel.of([ + [ id:'test', single_end:false ], // meta map + file(params.modules_testdata_base_path + 'genomics/sarscov2/illumina/bam/test.paired_end.sorted.bam', checkIfExists: true), + [] + ]) + input[1] = [[],[]] + input[2] = Channel.of('testN:1') + .collectFile(name: 'selected_reads.txt') + input[3] = 'crai' + """ + } + } + + then { + assertAll( + { assert process.success }, + { assert snapshot(process.out).match() } + ) + } + } +} diff --git a/modules/nf-core/samtools/view/tests/main.nf.test.snap b/modules/nf-core/samtools/view/tests/main.nf.test.snap new file mode 100644 index 00000000..1cb793f2 --- /dev/null +++ b/modules/nf-core/samtools/view/tests/main.nf.test.snap @@ -0,0 +1,972 @@ +{ + "bam_bam": { + "content": [ + "test.bam" + ], + "meta": { + "nf-test": "0.8.4", + "nextflow": "23.04.3" + }, + "timestamp": "2024-02-12T19:37:51.256068" + }, + "bam_stub_bam": { + "content": [ + "test.bam" + ], + "meta": { + "nf-test": "0.8.4", + "nextflow": "23.04.3" + }, + "timestamp": "2024-02-12T19:38:32.065301" + }, + "bam_bai": { + "content": [ + [ + + ] + ], + "meta": { + "nf-test": "0.8.4", + "nextflow": "23.04.3" + }, + "timestamp": "2024-02-12T19:37:51.258578" + }, + "bam_stub_bai": { + "content": [ + [ + + ] + ], + "meta": { + "nf-test": "0.8.4", + "nextflow": "23.04.3" + }, + "timestamp": "2024-02-12T19:38:32.071284" + }, + "bam_stub_versions": { + "content": [ + [ + "versions.yml:md5,176db5ec46b965219604bcdbb3ef9e07" + ] + ], + "meta": { + "nf-test": "0.9.2", + "nextflow": "24.10.3" + }, + "timestamp": "2025-02-14T07:43:43.6526401" + }, + "cram_to_bam_index_qname_csi": { + "content": [ + "test.bam.csi" + ], + "meta": { + "nf-test": "0.8.4", + "nextflow": "23.04.3" + }, + "timestamp": "2024-02-12T19:38:23.325496" + }, + "cram_to_bam_index_qname_unselected_csi": { + "content": [ + "test.unselected.bam.csi" + ], + "meta": { + "nf-test": "0.8.4", + "nextflow": "23.04.3" + }, + "timestamp": "2024-02-12T19:38:23.328458" + }, + "bam_csi": { + "content": [ + [ + + ] + ], + "meta": { + "nf-test": "0.8.4", + "nextflow": "23.04.3" + }, + "timestamp": "2024-02-12T19:37:51.262882" + }, + "cram_to_bam_index_bam": { + "content": [ + "test.bam" + ], + "meta": { + "nf-test": "0.8.4", + "nextflow": "23.04.3" + }, + "timestamp": "2024-02-12T19:38:12.95456" + }, + "cram_to_bam_index_versions": { + "content": [ + [ + "versions.yml:md5,176db5ec46b965219604bcdbb3ef9e07" + ] + ], + "meta": { + "nf-test": "0.9.0", + "nextflow": "24.04.4" + }, + "timestamp": "2024-09-16T09:25:14.475388399" + }, + "bam_csi_index": { + "content": [ + "test.bam", + "test.bam.csi", + [ + "versions.yml:md5,176db5ec46b965219604bcdbb3ef9e07" + ] + ], + "meta": { + "nf-test": "0.9.2", + "nextflow": "24.10.3" + }, + "timestamp": "2025-02-14T07:45:19.718077276" + }, + "bam_versions": { + "content": [ + [ + "versions.yml:md5,176db5ec46b965219604bcdbb3ef9e07" + ] + ], + "meta": { + "nf-test": "0.9.2", + "nextflow": "24.10.3" + }, + "timestamp": "2025-02-13T16:13:00.739468586" + }, + "cram_crai_index_unselected - stub": { + "content": [ + { + "0": [ + + ], + "1": [ + [ + { + "id": "test", + "single_end": false + }, + "test.cram:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "2": [ + + ], + "3": [ + + ], + "4": [ + + ], + "5": [ + [ + { + "id": "test", + "single_end": false + }, + "test.cram.crai:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "6": [ + [ + { + "id": "test", + "single_end": false + }, + "test.unselected.cram:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "7": [ + [ + { + "id": "test", + "single_end": false + }, + "test.unselected.cram.crai:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "8": [ + "versions.yml:md5,176db5ec46b965219604bcdbb3ef9e07" + ], + "bai": [ + + ], + "bam": [ + + ], + "crai": [ + [ + { + "id": "test", + "single_end": false + }, + "test.cram.crai:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "cram": [ + [ + { + "id": "test", + "single_end": false + }, + "test.cram:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "csi": [ + + ], + "sam": [ + + ], + "unselected": [ + [ + { + "id": "test", + "single_end": false + }, + "test.unselected.cram:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "unselected_index": [ + [ + { + "id": "test", + "single_end": false + }, + "test.unselected.cram.crai:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "versions": [ + "versions.yml:md5,176db5ec46b965219604bcdbb3ef9e07" + ] + } + ], + "meta": { + "nf-test": "0.9.2", + "nextflow": "24.10.3" + }, + "timestamp": "2025-02-14T07:47:20.903462221" + }, + "bam_crai": { + "content": [ + [ + + ] + ], + "meta": { + "nf-test": "0.8.4", + "nextflow": "23.04.3" + }, + "timestamp": "2024-02-12T19:37:51.259774" + }, + "bam_cram": { + "content": [ + [ + + ] + ], + "meta": { + "nf-test": "0.8.4", + "nextflow": "23.04.3" + }, + "timestamp": "2024-02-12T19:37:51.261287" + }, + "cram_sam": { + "content": [ + [ + + ] + ], + "meta": { + "nf-test": "0.8.4", + "nextflow": "23.04.3" + }, + "timestamp": "2024-02-12T19:37:56.502625" + }, + "cram_versions": { + "content": [ + [ + "versions.yml:md5,176db5ec46b965219604bcdbb3ef9e07" + ] + ], + "meta": { + "nf-test": "0.9.2", + "nextflow": "24.10.3" + }, + "timestamp": "2025-02-13T16:33:28.319991831" + }, + "cram_to_bam_index_bai": { + "content": [ + [ + + ] + ], + "meta": { + "nf-test": "0.8.4", + "nextflow": "23.04.3" + }, + "timestamp": "2024-02-12T19:38:12.962863" + }, + "cram_to_bam_index_qname_sam": { + "content": [ + [ + + ] + ], + "meta": { + "nf-test": "0.8.4", + "nextflow": "23.04.3" + }, + "timestamp": "2024-02-12T19:38:23.337634" + }, + "bam_csi_index - stub": { + "content": [ + { + "0": [ + [ + { + "id": "test", + "single_end": false + }, + "test.bam:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "1": [ + + ], + "2": [ + + ], + "3": [ + + ], + "4": [ + [ + { + "id": "test", + "single_end": false + }, + "test.bam.csi:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "5": [ + + ], + "6": [ + + ], + "7": [ + + ], + "8": [ + "versions.yml:md5,176db5ec46b965219604bcdbb3ef9e07" + ], + "bai": [ + + ], + "bam": [ + [ + { + "id": "test", + "single_end": false + }, + "test.bam:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "crai": [ + + ], + "cram": [ + + ], + "csi": [ + [ + { + "id": "test", + "single_end": false + }, + "test.bam.csi:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "sam": [ + + ], + "unselected": [ + + ], + "unselected_index": [ + + ], + "versions": [ + "versions.yml:md5,176db5ec46b965219604bcdbb3ef9e07" + ] + } + ], + "meta": { + "nf-test": "0.9.2", + "nextflow": "24.10.3" + }, + "timestamp": "2025-02-14T07:46:52.477256747" + }, + "cram_to_bam_index_csi": { + "content": [ + "test.bam.csi" + ], + "meta": { + "nf-test": "0.8.4", + "nextflow": "23.04.3" + }, + "timestamp": "2024-02-12T19:38:12.958617" + }, + "bam_bai_index": { + "content": [ + "test.bam", + "test.bam.bai", + [ + "versions.yml:md5,176db5ec46b965219604bcdbb3ef9e07" + ] + ], + "meta": { + "nf-test": "0.9.2", + "nextflow": "24.10.3" + }, + "timestamp": "2025-02-14T07:45:29.205677197" + }, + "cram_to_bam_index_cram": { + "content": [ + [ + + ] + ], + "meta": { + "nf-test": "0.8.4", + "nextflow": "23.04.3" + }, + "timestamp": "2024-02-12T19:38:12.972288" + }, + "bam_bai_index - stub": { + "content": [ + { + "0": [ + [ + { + "id": "test", + "single_end": false + }, + "test.bam:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "1": [ + + ], + "2": [ + + ], + "3": [ + [ + { + "id": "test", + "single_end": false + }, + "test.bam.bai:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "4": [ + + ], + "5": [ + + ], + "6": [ + + ], + "7": [ + + ], + "8": [ + "versions.yml:md5,176db5ec46b965219604bcdbb3ef9e07" + ], + "bai": [ + [ + { + "id": "test", + "single_end": false + }, + "test.bam.bai:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "bam": [ + [ + { + "id": "test", + "single_end": false + }, + "test.bam:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "crai": [ + + ], + "cram": [ + + ], + "csi": [ + + ], + "sam": [ + + ], + "unselected": [ + + ], + "unselected_index": [ + + ], + "versions": [ + "versions.yml:md5,176db5ec46b965219604bcdbb3ef9e07" + ] + } + ], + "meta": { + "nf-test": "0.9.2", + "nextflow": "24.10.3" + }, + "timestamp": "2025-02-14T07:51:10.220507926" + }, + "cram_to_bam_sam": { + "content": [ + [ + + ] + ], + "meta": { + "nf-test": "0.8.4", + "nextflow": "23.04.3" + }, + "timestamp": "2024-02-12T19:38:04.999247" + }, + "cram_to_bam_index_sam": { + "content": [ + [ + + ] + ], + "meta": { + "nf-test": "0.8.4", + "nextflow": "23.04.3" + }, + "timestamp": "2024-02-12T19:38:12.976457" + }, + "cram_crai": { + "content": [ + [ + + ] + ], + "meta": { + "nf-test": "0.8.4", + "nextflow": "23.04.3" + }, + "timestamp": "2024-02-12T19:37:56.497581" + }, + "cram_csi": { + "content": [ + [ + + ] + ], + "meta": { + "nf-test": "0.8.4", + "nextflow": "23.04.3" + }, + "timestamp": "2024-02-12T19:37:56.50038" + }, + "cram_to_bam_cram": { + "content": [ + [ + + ] + ], + "meta": { + "nf-test": "0.8.4", + "nextflow": "23.04.3" + }, + "timestamp": "2024-02-12T19:38:04.992239" + }, + "bam_stub_sam": { + "content": [ + [ + + ] + ], + "meta": { + "nf-test": "0.8.4", + "nextflow": "23.04.3" + }, + "timestamp": "2024-02-12T19:38:32.079529" + }, + "cram_cram": { + "content": [ + "test.cram" + ], + "meta": { + "nf-test": "0.8.4", + "nextflow": "23.04.3" + }, + "timestamp": "2024-02-12T19:37:56.490286" + }, + "cram_to_bam_crai": { + "content": [ + [ + + ] + ], + "meta": { + "nf-test": "0.8.4", + "nextflow": "23.04.3" + }, + "timestamp": "2024-02-12T19:38:04.989247" + }, + "cram_to_bam_index_crai": { + "content": [ + [ + + ] + ], + "meta": { + "nf-test": "0.8.4", + "nextflow": "23.04.3" + }, + "timestamp": "2024-02-12T19:38:12.967681" + }, + "cram_crai_index_unselected": { + "content": [ + "test.cram", + "test.cram.crai", + "test.unselected.cram", + "test.unselected.cram.crai", + [ + "versions.yml:md5,176db5ec46b965219604bcdbb3ef9e07" + ] + ], + "meta": { + "nf-test": "0.9.2", + "nextflow": "24.10.3" + }, + "timestamp": "2025-02-14T07:45:48.461930073" + }, + "cram_to_bam_index_qname_versions": { + "content": [ + [ + "versions.yml:md5,176db5ec46b965219604bcdbb3ef9e07" + ] + ], + "meta": { + "nf-test": "0.9.0", + "nextflow": "24.04.4" + }, + "timestamp": "2024-09-16T09:25:51.953436682" + }, + "cram_to_bam_bam": { + "content": [ + "test.bam" + ], + "meta": { + "nf-test": "0.8.4", + "nextflow": "23.04.3" + }, + "timestamp": "2024-02-12T19:38:04.982361" + }, + "cram_to_bam_bai": { + "content": [ + [ + + ] + ], + "meta": { + "nf-test": "0.8.4", + "nextflow": "23.04.3" + }, + "timestamp": "2024-02-12T19:38:04.98601" + }, + "cram_to_bam_versions": { + "content": [ + [ + "versions.yml:md5,176db5ec46b965219604bcdbb3ef9e07" + ] + ], + "meta": { + "nf-test": "0.9.2", + "nextflow": "24.10.3" + }, + "timestamp": "2025-02-13T16:33:39.363718229" + }, + "cram_bam": { + "content": [ + [ + + ] + ], + "meta": { + "nf-test": "0.8.4", + "nextflow": "23.04.3" + }, + "timestamp": "2024-02-12T19:37:56.495512" + }, + "bam_stub_cram": { + "content": [ + [ + + ] + ], + "meta": { + "nf-test": "0.8.4", + "nextflow": "23.04.3" + }, + "timestamp": "2024-02-12T19:38:32.076908" + }, + "cram_to_bam_index_qname_bai": { + "content": [ + [ + + ] + ], + "meta": { + "nf-test": "0.8.4", + "nextflow": "23.04.3" + }, + "timestamp": "2024-02-12T19:38:23.328458" + }, + "cram_to_bam_index_qname_crai": { + "content": [ + [ + + ] + ], + "meta": { + "nf-test": "0.8.4", + "nextflow": "23.04.3" + }, + "timestamp": "2024-02-12T19:38:23.330789" + }, + "cram_bai": { + "content": [ + [ + + ] + ], + "meta": { + "nf-test": "0.8.4", + "nextflow": "23.04.3" + }, + "timestamp": "2024-02-12T19:37:56.493129" + }, + "bam_stub_crai": { + "content": [ + [ + + ] + ], + "meta": { + "nf-test": "0.8.4", + "nextflow": "23.04.3" + }, + "timestamp": "2024-02-12T19:38:32.074313" + }, + "cram_to_bam_index_qname_bam": { + "content": [ + "test.bam" + ], + "meta": { + "nf-test": "0.8.4", + "nextflow": "23.04.3" + }, + "timestamp": "2024-02-12T19:38:23.322874" + }, + "bam_bai_index_unselected": { + "content": [ + "test.bam", + "test.bam.bai", + "test.unselected.bam", + "test.unselected.bam.csi", + [ + "versions.yml:md5,176db5ec46b965219604bcdbb3ef9e07" + ] + ], + "meta": { + "nf-test": "0.9.2", + "nextflow": "24.10.3" + }, + "timestamp": "2025-02-14T07:45:38.993014707" + }, + "cram_to_bam_index_qname_cram": { + "content": [ + [ + + ] + ], + "meta": { + "nf-test": "0.8.4", + "nextflow": "23.04.3" + }, + "timestamp": "2024-02-12T19:38:23.333248" + }, + "cram_to_bam_csi": { + "content": [ + [ + + ] + ], + "meta": { + "nf-test": "0.8.4", + "nextflow": "23.04.3" + }, + "timestamp": "2024-02-12T19:38:04.995454" + }, + "bam_bai_index_uselected - stub": { + "content": [ + { + "0": [ + [ + { + "id": "test", + "single_end": false + }, + "test.bam:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "1": [ + + ], + "2": [ + + ], + "3": [ + [ + { + "id": "test", + "single_end": false + }, + "test.bam.bai:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "4": [ + + ], + "5": [ + + ], + "6": [ + [ + { + "id": "test", + "single_end": false + }, + "test.unselected.bam:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "7": [ + [ + { + "id": "test", + "single_end": false + }, + "test.unselected.bam.csi:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "8": [ + "versions.yml:md5,176db5ec46b965219604bcdbb3ef9e07" + ], + "bai": [ + [ + { + "id": "test", + "single_end": false + }, + "test.bam.bai:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "bam": [ + [ + { + "id": "test", + "single_end": false + }, + "test.bam:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "crai": [ + + ], + "cram": [ + + ], + "csi": [ + + ], + "sam": [ + + ], + "unselected": [ + [ + { + "id": "test", + "single_end": false + }, + "test.unselected.bam:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "unselected_index": [ + [ + { + "id": "test", + "single_end": false + }, + "test.unselected.bam.csi:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "versions": [ + "versions.yml:md5,176db5ec46b965219604bcdbb3ef9e07" + ] + } + ], + "meta": { + "nf-test": "0.9.2", + "nextflow": "24.10.3" + }, + "timestamp": "2025-02-14T07:51:24.947216832" + }, + "cram_to_bam_index_qname_unselected": { + "content": [ + "test.unselected.bam" + ], + "meta": { + "nf-test": "0.8.4", + "nextflow": "23.04.3" + }, + "timestamp": "2024-02-12T19:38:23.322874" + }, + "bam_sam": { + "content": [ + [ + + ] + ], + "meta": { + "nf-test": "0.8.4", + "nextflow": "23.04.3" + }, + "timestamp": "2024-02-12T19:37:51.264651" + }, + "bam_stub_csi": { + "content": [ + "test.bam.csi" + ], + "meta": { + "nf-test": "0.8.4", + "nextflow": "23.04.3" + }, + "timestamp": "2024-02-12T19:38:32.068596" + } +} \ No newline at end of file diff --git a/modules/nf-core/umitools/dedup/environment.yml b/modules/nf-core/umitools/dedup/environment.yml new file mode 100644 index 00000000..e5721f1e --- /dev/null +++ b/modules/nf-core/umitools/dedup/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: + - bioconda::umi_tools=1.1.5 diff --git a/modules/nf-core/umitools/dedup/main.nf b/modules/nf-core/umitools/dedup/main.nf new file mode 100644 index 00000000..1e2a2aae --- /dev/null +++ b/modules/nf-core/umitools/dedup/main.nf @@ -0,0 +1,63 @@ +process UMITOOLS_DEDUP { + tag "$meta.id" + label "process_medium" + + conda "${moduleDir}/environment.yml" + container "${ workflow.containerEngine == 'singularity' && !task.ext.singularity_pull_docker_container ? + 'https://depot.galaxyproject.org/singularity/umi_tools:1.1.5--py39hf95cd2a_0' : + 'biocontainers/umi_tools:1.1.5--py39hf95cd2a_0' }" + + input: + tuple val(meta), path(bam), path(bai) + val get_output_stats + + output: + tuple val(meta), path("${prefix}.bam") , emit: bam + tuple val(meta), path("*.log") , emit: log + tuple val(meta), path("*edit_distance.tsv"), optional:true, emit: tsv_edit_distance + tuple val(meta), path("*per_umi.tsv") , optional:true, emit: tsv_per_umi + tuple val(meta), path("*per_position.tsv") , optional:true, emit: tsv_umi_per_position + path "versions.yml" , emit: versions + + when: + task.ext.when == null || task.ext.when + + script: + def args = task.ext.args ?: '' + prefix = task.ext.prefix ?: "${meta.id}" + def paired = meta.single_end ? "" : "--paired" + stats = get_output_stats ? "--output-stats ${prefix}" : "" + if ("$bam" == "${prefix}.bam") error "Input and output names are the same, set prefix in module configuration to disambiguate!" + + if (!(args ==~ /.*--random-seed.*/)) {args += " --random-seed=100"} + """ + PYTHONHASHSEED=0 umi_tools \\ + dedup \\ + -I $bam \\ + -S ${prefix}.bam \\ + -L ${prefix}.log \\ + $stats \\ + $paired \\ + $args + + cat <<-END_VERSIONS > versions.yml + "${task.process}": + umitools: \$( umi_tools --version | sed '/version:/!d; s/.*: //' ) + END_VERSIONS + """ + + stub: + prefix = task.ext.prefix ?: "${meta.id}" + """ + touch ${prefix}.bam + touch ${prefix}.log + touch ${prefix}_edit_distance.tsv + touch ${prefix}_per_umi.tsv + touch ${prefix}_per_position.tsv + + cat <<-END_VERSIONS > versions.yml + "${task.process}": + umitools: \$( umi_tools --version | sed '/version:/!d; s/.*: //' ) + END_VERSIONS + """ +} diff --git a/modules/nf-core/umitools/dedup/meta.yml b/modules/nf-core/umitools/dedup/meta.yml new file mode 100644 index 00000000..6cbd8411 --- /dev/null +++ b/modules/nf-core/umitools/dedup/meta.yml @@ -0,0 +1,101 @@ +name: umitools_dedup +description: Deduplicate reads based on the mapping co-ordinate and the UMI attached + to the read. +keywords: + - umitools + - deduplication + - dedup +tools: + - umi_tools: + description: > + UMI-tools contains tools for dealing with Unique Molecular Identifiers (UMIs)/Random + Molecular Tags (RMTs) and single cell RNA-Seq cell barcodes + + documentation: https://umi-tools.readthedocs.io/en/latest/ + license: ["MIT"] + identifier: "" +input: + - - meta: + type: map + description: | + Groovy Map containing sample information + e.g. [ id:'test', single_end:false ] + - bam: + type: file + description: | + BAM file containing reads to be deduplicated via UMIs. + pattern: "*.{bam}" + - bai: + type: file + description: | + BAM index files corresponding to the input BAM file. + pattern: "*.{bai}" + - - get_output_stats: + type: boolean + description: | + Whether or not to generate output stats. +output: + - bam: + - meta: + type: map + description: | + Groovy Map containing sample information + e.g. [ id:'test', single_end:false ] + - ${prefix}.bam: + type: file + description: BAM file with deduplicated UMIs. + pattern: "*.{bam}" + - log: + - meta: + type: map + description: | + Groovy Map containing sample information + e.g. [ id:'test', single_end:false ] + - "*.log": + type: file + description: File with logging information + pattern: "*.{log}" + - tsv_edit_distance: + - meta: + type: map + description: | + Groovy Map containing sample information + e.g. [ id:'test', single_end:false ] + - "*edit_distance.tsv": + type: file + description: Reports the (binned) average edit distance between the UMIs at + each position. + pattern: "*edit_distance.tsv" + - tsv_per_umi: + - meta: + type: map + description: | + Groovy Map containing sample information + e.g. [ id:'test', single_end:false ] + - "*per_umi.tsv": + type: file + description: UMI-level summary statistics. + pattern: "*per_umi.tsv" + - tsv_umi_per_position: + - meta: + type: map + description: | + Groovy Map containing sample information + e.g. [ id:'test', single_end:false ] + - "*per_position.tsv": + type: file + description: Tabulates the counts for unique combinations of UMI and position. + pattern: "*per_position.tsv" + - versions: + - versions.yml: + type: file + description: File containing software versions + pattern: "versions.yml" +authors: + - "@drpatelh" + - "@grst" + - "@klkeys" +maintainers: + - "@drpatelh" + - "@grst" + - "@klkeys" diff --git a/modules/nf-core/umitools/dedup/tests/main.nf.test b/modules/nf-core/umitools/dedup/tests/main.nf.test new file mode 100644 index 00000000..f00a8cbe --- /dev/null +++ b/modules/nf-core/umitools/dedup/tests/main.nf.test @@ -0,0 +1,188 @@ +nextflow_process { + + name "Test Process UMITOOLS_DEDUP" + script "../main.nf" + process "UMITOOLS_DEDUP" + + tag "modules" + tag "modules_nfcore" + tag "umitools" + tag "umitools/dedup" + + test("se - no stats") { + config "./nextflow.config" + + when { + process { + """ + get_output_stats = false + + input[0] = [ + [ id:'test', single_end:true ], // meta map + file(params.modules_testdata_base_path + "genomics/sarscov2/illumina/bam/test.single_end.umi.sorted.bam", checkIfExists: true), + file(params.modules_testdata_base_path + "genomics/sarscov2/illumina/bam/test.single_end.umi.sorted.bam.bai", checkIfExists: true) + ] + input[1] = get_output_stats + """ + } + } + + then { + assertAll( + { assert process.success }, + { assert path("${process.out.log[0][1]}").exists() }, + { assert snapshot( + bam(process.out.bam[0][1]).getSamLinesMD5(), + process.out.versions).match() } + ) + } + } + + test("pe - no stats") { + config "./nextflow.config" + + when { + process { + """ + get_output_stats = false + + input[0] = [ + [ id:'test', single_end:false ], // meta map + file(params.modules_testdata_base_path + "genomics/sarscov2/illumina/bam/test.paired_end.umi.sorted.bam", checkIfExists: true), + file(params.modules_testdata_base_path + "genomics/sarscov2/illumina/bam/test.paired_end.umi.sorted.bam.bai", checkIfExists: true) + ] + input[1] = get_output_stats + """ + } + } + + then { + assertAll( + { assert process.success }, + { assert path("${process.out.log[0][1]}").exists() }, + { assert snapshot( + bam(process.out.bam[0][1]).getSamLinesMD5(), + process.out.versions).match() } + ) + } + } + + test("pe - with stats") { + config "./nextflow.config" + + when { + process { + """ + get_output_stats = true + + input[0] = [ + [ id:'test', single_end:false ], // meta map + file(params.modules_testdata_base_path + "genomics/sarscov2/illumina/bam/test.paired_end.umi.sorted.bam", checkIfExists: true), + file(params.modules_testdata_base_path + "genomics/sarscov2/illumina/bam/test.paired_end.umi.sorted.bam.bai", checkIfExists: true) + ] + input[1] = get_output_stats + """ + } + } + + then { + assertAll( + { assert process.success }, + { assert path("${process.out.log[0][1]}").exists() }, + { assert snapshot( + bam(process.out.bam[0][1]).getSamLinesMD5(), + process.out.tsv_edit_distance, + process.out.tsv_per_umi, + process.out.tsv_umi_per_position, + process.out.versions).match() } + ) + } + } + + test("se - no stats - stub") { + + options "-stub" + + config "./nextflow.config" + + when { + process { + """ + get_output_stats = false + + input[0] = [ + [ id:'test', single_end:true ], // meta map + file(params.modules_testdata_base_path + "genomics/sarscov2/illumina/bam/test.single_end.umi.sorted.bam", checkIfExists: true), + file(params.modules_testdata_base_path + "genomics/sarscov2/illumina/bam/test.single_end.umi.sorted.bam.bai", checkIfExists: true) + ] + input[1] = get_output_stats + """ + } + } + + then { + assertAll( + { assert process.success }, + { assert snapshot(process.out.bam).match() } + ) + } + } + + test("pe - no stats - stub") { + + options "-stub" + + config "./nextflow.config" + + when { + process { + """ + get_output_stats = false + + input[0] = [ + [ id:'test', single_end:false ], // meta map + file(params.modules_testdata_base_path + "genomics/sarscov2/illumina/bam/test.paired_end.umi.sorted.bam", checkIfExists: true), + file(params.modules_testdata_base_path + "genomics/sarscov2/illumina/bam/test.paired_end.umi.sorted.bam.bai", checkIfExists: true) + ] + input[1] = get_output_stats + """ + } + } + + then { + assertAll( + { assert process.success }, + { assert snapshot(process.out.bam).match() } + ) + } + } + + test("pe - with stats - stub") { + + options "-stub" + + config "./nextflow.config" + + when { + process { + """ + get_output_stats = true + + input[0] = [ + [ id:'test', single_end:false ], // meta map + file(params.modules_testdata_base_path + "genomics/sarscov2/illumina/bam/test.paired_end.umi.sorted.bam", checkIfExists: true), + file(params.modules_testdata_base_path + "genomics/sarscov2/illumina/bam/test.paired_end.umi.sorted.bam.bai", checkIfExists: true) + ] + input[1] = get_output_stats + """ + } + } + + then { + assertAll( + { assert process.success }, + { assert snapshot(process.out.bam).match() } + ) + } + } +} diff --git a/modules/nf-core/umitools/dedup/tests/main.nf.test.snap b/modules/nf-core/umitools/dedup/tests/main.nf.test.snap new file mode 100644 index 00000000..04b81692 --- /dev/null +++ b/modules/nf-core/umitools/dedup/tests/main.nf.test.snap @@ -0,0 +1,122 @@ +{ + "pe - no stats - stub": { + "content": [ + [ + [ + { + "id": "test", + "single_end": false + }, + "test.dedup.bam:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ] + ], + "meta": { + "nf-test": "0.8.4", + "nextflow": "24.04.2" + }, + "timestamp": "2024-07-03T11:40:46.802233" + }, + "pe - with stats - stub": { + "content": [ + [ + [ + { + "id": "test", + "single_end": false + }, + "test.dedup.bam:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ] + ], + "meta": { + "nf-test": "0.8.4", + "nextflow": "24.04.2" + }, + "timestamp": "2024-07-03T11:40:59.501624" + }, + "pe - with stats": { + "content": [ + "b7be15ac7aae194b04bdbb56f3534495", + [ + [ + { + "id": "test", + "single_end": false + }, + "test.dedup_edit_distance.tsv:md5,c247a49b58768e6e2e86a6c08483e612" + ] + ], + [ + [ + { + "id": "test", + "single_end": false + }, + "test.dedup_per_umi.tsv:md5,ced75f7bdbf38bf78f3137d5325a8773" + ] + ], + [ + [ + { + "id": "test", + "single_end": false + }, + "test.dedup_per_umi_per_position.tsv:md5,2e1a12e6f720510880068deddeefe063" + ] + ], + [ + "versions.yml:md5,e2f5146464c09bf7ae98c85ea5410e50" + ] + ], + "meta": { + "nf-test": "0.8.4", + "nextflow": "24.10.1" + }, + "timestamp": "2024-11-25T17:25:28.939957" + }, + "se - no stats - stub": { + "content": [ + [ + [ + { + "id": "test", + "single_end": true + }, + "test.dedup.bam:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ] + ], + "meta": { + "nf-test": "0.8.4", + "nextflow": "24.04.2" + }, + "timestamp": "2024-07-03T11:40:34.598176" + }, + "se - no stats": { + "content": [ + "9158ea6e7a0e54819e25cbac5fbc5cc0", + [ + "versions.yml:md5,e2f5146464c09bf7ae98c85ea5410e50" + ] + ], + "meta": { + "nf-test": "0.8.4", + "nextflow": "24.10.1" + }, + "timestamp": "2024-11-23T09:06:54.373171" + }, + "pe - no stats": { + "content": [ + "b7be15ac7aae194b04bdbb56f3534495", + [ + "versions.yml:md5,e2f5146464c09bf7ae98c85ea5410e50" + ] + ], + "meta": { + "nf-test": "0.8.4", + "nextflow": "24.10.1" + }, + "timestamp": "2024-11-25T17:24:51.423637" + } +} \ No newline at end of file diff --git a/modules/nf-core/umitools/dedup/tests/nextflow.config b/modules/nf-core/umitools/dedup/tests/nextflow.config new file mode 100644 index 00000000..da6652dc --- /dev/null +++ b/modules/nf-core/umitools/dedup/tests/nextflow.config @@ -0,0 +1,5 @@ +process { + withName: UMITOOLS_DEDUP { + ext.prefix = { "${meta.id}.dedup" } + } +} diff --git a/nextflow.config b/nextflow.config index 5f2b6e91..ccecc601 100644 --- a/nextflow.config +++ b/nextflow.config @@ -16,6 +16,7 @@ params { match_donor = false hash_tools = 'gmm-demux' genetic_tools = 'vireo' + bam_qc = true // References genome = null diff --git a/nextflow_schema.json b/nextflow_schema.json index 7780a1da..b717e2c3 100644 --- a/nextflow_schema.json +++ b/nextflow_schema.json @@ -52,6 +52,12 @@ "default": "vireo", "pattern": "^(vireo|demuxlet|freemuxlet|souporcell|cellsnp)(,(vireo|demuxlet|freemuxlet|souporcell|cellsnp))*$" }, + "bam_qc": { + "type": "boolean", + "description": "Perform BAM QC.", + "fa_icon": "fas fa-cog", + "default": true + }, "outdir": { "type": "string", "format": "directory-path", diff --git a/subworkflows/local/bam_qc/main.nf b/subworkflows/local/bam_qc/main.nf new file mode 100644 index 00000000..e6a1e8ac --- /dev/null +++ b/subworkflows/local/bam_qc/main.nf @@ -0,0 +1,29 @@ +include { SAMTOOLS_VIEW } from '../../../modules/nf-core/samtools/view' +include { SAMTOOLS_INDEX } from '../../../modules/nf-core/samtools/index' +include { UMITOOLS_DEDUP } from '../../../modules/nf-core/umitools/dedup' +include { SAMTOOLS_SORT } from '../../../modules/nf-core/samtools/sort' + +workflow BAM_QC { + take: + ch_bam + + main: + + ch_versions = Channel.empty() + + SAMTOOLS_VIEW(ch_bam.map { meta, bam -> [meta, bam, []] }, [[], []], [], 'bai') + ch_versions = ch_versions.mix(SAMTOOLS_VIEW.out.versions) + + SAMTOOLS_INDEX(SAMTOOLS_VIEW.out.bam) + ch_versions = ch_versions.mix(SAMTOOLS_INDEX.out.versions) + + UMITOOLS_DEDUP(SAMTOOLS_VIEW.out.bam.join(SAMTOOLS_INDEX.out.bai), true) + ch_versions = ch_versions.mix(UMITOOLS_DEDUP.out.versions) + + SAMTOOLS_SORT(UMITOOLS_DEDUP.out.bam, [[], []]) + ch_versions = ch_versions.mix(SAMTOOLS_SORT.out.versions) + + emit: + bam = SAMTOOLS_SORT.out.bam + versions = ch_versions // channel: [ versions.yml ] +} diff --git a/subworkflows/local/genetic_demultiplexing/main.nf b/subworkflows/local/genetic_demultiplexing/main.nf index a973200d..5ac79cdf 100644 --- a/subworkflows/local/genetic_demultiplexing/main.nf +++ b/subworkflows/local/genetic_demultiplexing/main.nf @@ -1,3 +1,5 @@ +include { BAM_QC } from '../bam_qc' + include { SAMTOOLS_INDEX } from '../../../modules/nf-core/samtools/index' include { CELLSNP_MODEA } from '../../../modules/nf-core/cellsnp/modea' include { VIREO } from '../../../modules/nf-core/vireo' @@ -9,11 +11,20 @@ workflow GENETIC_DEMULTIPLEXING { take: ch_samplesheet // channel: samplesheet read in from --input methods // list of strings + bam_qc // boolean main: ch_versions = Channel.empty() + if (bam_qc) { + BAM_QC(ch_samplesheet.map { meta, bam, _barcodes, _nsample, _vcf -> [meta, bam] }) + ch_versions = ch_versions.mix(BAM_QC.out.versions) + + ch_samplesheet = ch_samplesheet + .join(BAM_QC.out.bam) + .map { meta, _bam, barcodes, nsample, vcf, new_bam -> [meta, new_bam, barcodes, nsample, vcf] } + } if (methods.contains('demuxlet') || methods.contains('freemuxlet')) { POPSCLE_DSCPILEUP(ch_samplesheet.map { meta, bam, _barcodes, _nsample, vcf -> [meta, bam, vcf] }) @@ -25,16 +36,12 @@ workflow GENETIC_DEMULTIPLEXING { ch_versions = ch_versions.mix(SAMTOOLS_INDEX.out.versions) CELLSNP_MODEA( - ch_samplesheet - .join(SAMTOOLS_INDEX.out.bai) - .map { meta, bam, barcodes, _nsample, vcf, bai -> [meta, bam, bai, vcf, barcodes] } + ch_samplesheet.join(SAMTOOLS_INDEX.out.bai).map { meta, bam, barcodes, _nsample, vcf, bai -> [meta, bam, bai, vcf, barcodes] } ) ch_versions = ch_versions.mix(CELLSNP_MODEA.out.versions) VIREO( - ch_samplesheet - .join(CELLSNP_MODEA.out.cell) - .map { meta, _bam, _barcodes, nsample, vcf, cell -> [meta, cell, nsample, vcf, []] } + ch_samplesheet.join(CELLSNP_MODEA.out.cell).map { meta, _bam, _barcodes, nsample, vcf, cell -> [meta, cell, nsample, vcf, []] } ) ch_versions = ch_versions.mix(VIREO.out.versions) } diff --git a/workflows/hadge.nf b/workflows/hadge.nf index c56126ae..93d47732 100644 --- a/workflows/hadge.nf +++ b/workflows/hadge.nf @@ -36,20 +36,17 @@ workflow HADGE { [meta, bam, barcodes, nsample, vcf] } - if (params.mode == 'genetic') { - GENETIC_DEMULTIPLEXING(ch_genetic, params.genetic_tools.split(',')) + if (params.mode == 'genetic' || params.mode == 'rescue') { + GENETIC_DEMULTIPLEXING( + ch_genetic, + params.genetic_tools.split(','), + params.bam_qc + ) ch_versions = ch_versions.mix(GENETIC_DEMULTIPLEXING.out.versions) } - else if (params.mode == 'hashing') { - HASH_DEMULTIPLEXING(ch_hashing, params.hash_tools.split(',')) - ch_versions = ch_versions.mix(HASH_DEMULTIPLEXING.out.versions) - } - else if (params.mode == 'rescue') { + else if (params.mode == 'hashing' || params.mode == 'rescue') { HASH_DEMULTIPLEXING(ch_hashing, params.hash_tools.split(',')) ch_versions = ch_versions.mix(HASH_DEMULTIPLEXING.out.versions) - - GENETIC_DEMULTIPLEXING(ch_genetic, params.genetic_tools.split(',')) - ch_versions = ch_versions.mix(DONOR_MATCHING.out.versions) } if (params.mode == 'donor_match' || params.match_donor) { From 4a9d1059f965a43daa8227eeedc8584026675a2e Mon Sep 17 00:00:00 2001 From: nictru Date: Sat, 14 Jun 2025 17:03:17 +0200 Subject: [PATCH 24/74] Use PLP for demuxlet --- modules/nf-core/popscle/demuxlet/main.nf | 6 ++---- subworkflows/local/genetic_demultiplexing/main.nf | 4 +++- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/modules/nf-core/popscle/demuxlet/main.nf b/modules/nf-core/popscle/demuxlet/main.nf index a941ebb1..2e65c61e 100644 --- a/modules/nf-core/popscle/demuxlet/main.nf +++ b/modules/nf-core/popscle/demuxlet/main.nf @@ -8,7 +8,7 @@ process POPSCLE_DEMUXLET { 'biocontainers/popscle:0.1beta--h2c78cec_0' }" input: - tuple val(meta), val(plp_prefix), path(bam), path(donor_genotype) + tuple val(meta), val(plp), path(bam), path(donor_genotype) output: tuple val(meta), path('*.best'), emit: demuxlet_result @@ -20,7 +20,7 @@ process POPSCLE_DEMUXLET { script: def args = task.ext.args ?: '' def prefix = task.ext.prefix ?: "${meta.id}" - def input = plp_prefix ? "--plp ${plp_prefix}" : "--sam $bam" + def input = plp ? "--plp ${plp.toString() - '.plp.gz'}" : "--sam $bam" def VERSION = '0.1' // WARN: Version information not provided by tool on CLI. Please update version string below when bumping container versions. """ @@ -37,9 +37,7 @@ process POPSCLE_DEMUXLET { """ stub: - def args = task.ext.args ?: '' def prefix = task.ext.prefix ?: "${meta.id}" - def input = plp_prefix ? "--plp ${plp_prefix}" : "--sam $bam" def VERSION = '0.1' // WARN: Version information not provided by tool on CLI. Please update version string below when bumping container versions. """ touch ${prefix}.best diff --git a/subworkflows/local/genetic_demultiplexing/main.nf b/subworkflows/local/genetic_demultiplexing/main.nf index 5ac79cdf..7e2a024f 100644 --- a/subworkflows/local/genetic_demultiplexing/main.nf +++ b/subworkflows/local/genetic_demultiplexing/main.nf @@ -46,7 +46,9 @@ workflow GENETIC_DEMULTIPLEXING { ch_versions = ch_versions.mix(VIREO.out.versions) } if (methods.contains('demuxlet')) { - POPSCLE_DEMUXLET(ch_samplesheet.map { meta, bam, _barcodes, _nsample, vcf -> [meta, [], bam, vcf] }) + POPSCLE_DEMUXLET( + POPSCLE_DSCPILEUP.out.plp.join(ch_samplesheet).map { meta, plp, bam, _barcodes, _nsample, vcf -> [meta, plp, bam, vcf] } + ) ch_versions = ch_versions.mix(POPSCLE_DEMUXLET.out.versions) } if (methods.contains('freemuxlet')) { From f51b85dde00e468e17d3626f255e6e1ba6355320 Mon Sep 17 00:00:00 2001 From: nictru Date: Sat, 14 Jun 2025 17:31:54 +0200 Subject: [PATCH 25/74] Implement BAM subsetting to common variants --- bin/filter_bam_file_for_popscle_dsc_pileup.sh | 151 ++++++++++++++++++ modules/local/filter_bam/environment.yml | 7 + modules/local/filter_bam/main.nf | 34 ++++ nextflow.config | 1 + nextflow_schema.json | 8 + .../local/genetic_demultiplexing/main.nf | 27 +++- workflows/hadge.nf | 3 +- 7 files changed, 226 insertions(+), 5 deletions(-) create mode 100755 bin/filter_bam_file_for_popscle_dsc_pileup.sh create mode 100644 modules/local/filter_bam/environment.yml create mode 100644 modules/local/filter_bam/main.nf diff --git a/bin/filter_bam_file_for_popscle_dsc_pileup.sh b/bin/filter_bam_file_for_popscle_dsc_pileup.sh new file mode 100755 index 00000000..9cd8f8af --- /dev/null +++ b/bin/filter_bam_file_for_popscle_dsc_pileup.sh @@ -0,0 +1,151 @@ +#!/bin/bash +# +# Copyright (C): 2020-2021 - Gert Hulselmans +# +# Purpose: Filter BAM file for usage with popscle dsc-pileup by keeping reads: +# - which overlap with SNPs in the VCF file +# - and which have a cell barcode (default: "CB" tag) contained in the cell barcode list +# Keeping only relevant reads for popscle dsc-pileup can speedup it up quite significantly +# (depending on the reduction of the number of reads in the filtered BAM file vs original). + + + +# Function to check if any of the programs in a pipe failed. +check_exit_codes () { + local GET_PIPESTATUS="${PIPESTATUS[@]}"; + local exit_code; + + for exit_code in ${GET_PIPESTATUS} ; do + if [ ${exit_code} -ne 0 ] ; then + return ${exit_code}; + fi + done + + return 0; +} + + + +# Check if necessary programs are installed. +check_if_programs_exists () { + local exit_code=0; + + # Check if bedtools is installed. + if ! type bedtools > /dev/null 2>&1 ; then + printf 'Error: "bedtools" could not be found in PATH.\n' > /dev/stderr; + exit_code=2; + fi + + # Check if samtools is installed. + if ! type samtools > /dev/null 2>&1 ; then + printf 'Error: "samtools" could not be found in PATH.\n' > /dev/stderr; + exit_code=2; + fi + + if [ ${exit_code} -eq 2 ] ; then + return ${exit_code}; + fi + + # Check if samtools 1.10 or higher is installed (needs to have "-D STR:FILE" or "-D, --tag-file STR:FILE" option). + if ! samtools view --help 2>&1 | grep -q -- '-D.*STR:FILE' ; then + printf 'Error: The version of "samtools" (%s) should be 1.10 or higher (%s found).\n' \ + "$(type samtools)" \ + "$(samtools --version | head -n 1)" \ + > /dev/stderr; + exit_code=2; + fi + + return ${exit_code}; +} + + + +filter_bam_file_for_popscle_dsc_pileup () { + local input_bam_filename="${1}"; + local barcodes_tsv_filename="${2}"; + local vcf_filename="${3}"; + local output_bam_filename="${4}"; + local barcode_tag="${5:-CB}"; + + local exit_code=0; + + if [ ${#@} -lt 4 ] ; then + printf 'Usage: filter_bam_file_for_popscle_dsc_pileup input_bam_filename barcodes_tsv_filename vcf_filename output_bam_filename [barcode_tag]\n\n'; + printf 'Purpose: Filter BAM file for usage with popscle dsc-pileup by keeping reads:\n'; + printf ' - which overlap with SNPs in the VCF file\n'; + printf ' - and which have a cell barcode (default: "CB" tag) contained in the cell barcode list\n'; + printf ' Keeping only relevant reads for popscle dsc-pileup can speedup it up quite significantly\n'; + printf ' (depending on the reduction of the number of reads in the filtered BAM file vs original).\n\n'; + + return 1; + fi + + if [ ! -f "${input_bam_filename}" ] ; then + printf 'Error: Input (CellRanger) BAM file "%s" could not be found.\n' "${input_bam_filename}" > /dev/stderr; + return 2; + fi + + if [ ! -f "${barcodes_tsv_filename}" ] ; then + printf 'Error: File with barcodes "%s" could not be found.\n' "${barcodes_tsv_filename}" > /dev/stderr; + return 2; + fi + + if [ ! -f "${vcf_filename}" ] ; then + printf 'Error: File with unique SNPs per sample "%s" could not be found.\n' "${vcf_filename}" > /dev/stderr; + return 2; + fi + + if [ ${#barcode_tag} -ne 2 ] ; then + printf 'Error: Barcode tag "%s" should be 2 characters.\n' "${barcode_tag}" > /dev/stderr; + return 2; + fi + + # Check if bedtools and samtools are in PATH. + if ! check_if_programs_exists ; then + return 2; + fi + + # Create much smaller BAM file for dsc-pileup of popscle: + # - Convert VCF file with unique SNPs for each sample + # to a BED file and merge adjacent SNP regions to one. + # - Only include reads that contain a SNP position + # and which contain a cell barcode of interest. + if [ "${barcodes_tsv_filename%.gz}".gz = "${barcodes_tsv_filename}" ] ; then + # Barcodes file is compressed with gzip. + bedtools merge -i "${vcf_filename}" \ + | samtools view\ + -@ 8 \ + --write-index \ + -L - \ + -D "${barcode_tag}":<(zcat "${barcodes_tsv_filename}") \ + -o "${output_bam_filename}" \ + "${input_bam_filename}"; + + # Check if any of the previous commands failed. + check_exit_codes; + + exit_code=$?; + else + # Barcodes file is uncompressed. + bedtools merge -i "${vcf_filename}" \ + | samtools view\ + -@ 8 \ + --write-index \ + -L - \ + -D "${barcode_tag}":"${barcodes_tsv_filename}" \ + -o "${output_bam_filename}" \ + "${input_bam_filename}"; + + # Check if any of the previous commands failed. + check_exit_codes; + + exit_code=$?; + fi + + + return ${exit_code}; +} + + + +filter_bam_file_for_popscle_dsc_pileup "${@}"; diff --git a/modules/local/filter_bam/environment.yml b/modules/local/filter_bam/environment.yml new file mode 100644 index 00000000..bee35501 --- /dev/null +++ b/modules/local/filter_bam/environment.yml @@ -0,0 +1,7 @@ +channels: + - conda-forge + - bioconda +dependencies: + - bioconda::bcftools=1.22 + - bioconda::bedtools=2.31.1 + - bioconda::samtools=1.22 diff --git a/modules/local/filter_bam/main.nf b/modules/local/filter_bam/main.nf new file mode 100644 index 00000000..8da90e3e --- /dev/null +++ b/modules/local/filter_bam/main.nf @@ -0,0 +1,34 @@ +process FILTER_BAM { + tag "${meta.id}" + label 'process_low' + + conda "${moduleDir}/environment.yml" + container "${workflow.containerEngine == 'singularity' && !task.ext.singularity_pull_docker_container + ? 'https://community-cr-prod.seqera.io/docker/registry/v2/blobs/sha256/f4/f46d4b6a720d442979b57330a319a25863231b2c70c80348f7b1d1d7d422b1f6/data' + : 'community.wave.seqera.io/library/bcftools_bedtools_samtools:f1acc4ec7fbdba9e'}" + + input: + tuple val(meta), path(bam), path(barcodes) + path vcf + + output: + tuple val(meta), path("${prefix}.bam"), emit: bam + path 'versions.yml', emit: versions + + script: + prefix = task.ext.prefix ?: "${meta.id}" + if ("${bam}" == "${prefix}.bam") { + error("Input and output names are the same, use \"task.ext.prefix\" to disambiguate!") + } + """ + bcftools sort ${vcf} -Oz -o sorted.vcf.gz + filter_bam_file_for_popscle_dsc_pileup.sh ${bam} ${barcodes} sorted.vcf.gz ${prefix}.bam + + cat <<-END_VERSIONS > versions.yml + "${task.process}": + bcftools: \$(bcftools --version 2>&1 | head -n1 | sed 's/^.*bcftools //; s/ .*\$//') + bedtools: \$(bedtools --version | sed -e "s/bedtools v//g") + samtools: \$(echo \$(samtools --version 2>&1) | sed 's/^.*samtools //; s/Using.*\$//') + END_VERSIONS + """ +} diff --git a/nextflow.config b/nextflow.config index ccecc601..2c798b82 100644 --- a/nextflow.config +++ b/nextflow.config @@ -17,6 +17,7 @@ params { hash_tools = 'gmm-demux' genetic_tools = 'vireo' bam_qc = true + common_variants = null // References genome = null diff --git a/nextflow_schema.json b/nextflow_schema.json index b717e2c3..98c4e1c4 100644 --- a/nextflow_schema.json +++ b/nextflow_schema.json @@ -58,6 +58,14 @@ "fa_icon": "fas fa-cog", "default": true }, + "common_variants": { + "type": "string", + "description": "File with common variants. If provided, the BAM files will be filtered to only include reads that overlap with the common variants.", + "fa_icon": "fas fa-cog", + "default": null, + "pattern": "^\\S+\\.vcf(\\.gz)?$", + "exists": true + }, "outdir": { "type": "string", "format": "directory-path", diff --git a/subworkflows/local/genetic_demultiplexing/main.nf b/subworkflows/local/genetic_demultiplexing/main.nf index 7e2a024f..e7c1df89 100644 --- a/subworkflows/local/genetic_demultiplexing/main.nf +++ b/subworkflows/local/genetic_demultiplexing/main.nf @@ -1,5 +1,5 @@ include { BAM_QC } from '../bam_qc' - +include { FILTER_BAM } from '../../../modules/local/filter_bam' include { SAMTOOLS_INDEX } from '../../../modules/nf-core/samtools/index' include { CELLSNP_MODEA } from '../../../modules/nf-core/cellsnp/modea' include { VIREO } from '../../../modules/nf-core/vireo' @@ -9,9 +9,10 @@ include { POPSCLE_FREEMUXLET } from '../../../modules/nf-core/popscle/freemuxlet workflow GENETIC_DEMULTIPLEXING { take: - ch_samplesheet // channel: samplesheet read in from --input - methods // list of strings - bam_qc // boolean + ch_samplesheet // channel: samplesheet read in from --input + methods // list of strings + bam_qc // boolean + common_variants // file main: @@ -26,6 +27,24 @@ workflow GENETIC_DEMULTIPLEXING { .map { meta, _bam, barcodes, nsample, vcf, new_bam -> [meta, new_bam, barcodes, nsample, vcf] } } + if (common_variants) { + FILTER_BAM( + ch_samplesheet.map { meta, bam, barcodes, _nsample, _vcf -> + [ + meta, + bam, + barcodes, + ] + }, + common_variants, + ) + ch_versions = ch_versions.mix(FILTER_BAM.out.versions) + + ch_samplesheet = ch_samplesheet + .join(FILTER_BAM.out.bam) + .map { meta, _bam, barcodes, nsample, vcf, new_bam -> [meta, new_bam, barcodes, nsample, vcf] } + } + if (methods.contains('demuxlet') || methods.contains('freemuxlet')) { POPSCLE_DSCPILEUP(ch_samplesheet.map { meta, bam, _barcodes, _nsample, vcf -> [meta, bam, vcf] }) ch_versions = ch_versions.mix(POPSCLE_DSCPILEUP.out.versions) diff --git a/workflows/hadge.nf b/workflows/hadge.nf index 93d47732..da828d4f 100644 --- a/workflows/hadge.nf +++ b/workflows/hadge.nf @@ -40,7 +40,8 @@ workflow HADGE { GENETIC_DEMULTIPLEXING( ch_genetic, params.genetic_tools.split(','), - params.bam_qc + params.bam_qc, + params.common_variants ) ch_versions = ch_versions.mix(GENETIC_DEMULTIPLEXING.out.versions) } From 87a5f8dfd1a2102b60597dad9f93b6af7c0a610a Mon Sep 17 00:00:00 2001 From: nictru Date: Sun, 15 Jun 2025 10:43:23 +0200 Subject: [PATCH 26/74] Update outdir structure --- conf/modules.config | 104 +++++++++++++++++- nextflow.config | 1 + nextflow_schema.json | 6 + .../local/genetic_demultiplexing/main.nf | 32 +++--- .../local/hash_demultiplexing/main.nf | 2 +- .../local/utils_nfcore_hadge_pipeline/main.nf | 4 +- workflows/hadge.nf | 2 +- 7 files changed, 128 insertions(+), 23 deletions(-) diff --git a/conf/modules.config b/conf/modules.config index c85bfd46..b739bd5d 100644 --- a/conf/modules.config +++ b/conf/modules.config @@ -19,24 +19,120 @@ process { ] withName: SAMTOOLS_VIEW { - ext.args = '-S -b -q 10 -F 3844' + ext.args = '-S -b -q 10 -F 3844' + publishDir = [ + enabled: false + ] + } + + withName: SAMTOOLS_INDEX { + publishDir = [ + enabled: false + ] } withName: UMITOOLS_DEDUP { ext.prefix = { "${meta.id}_dedup" } - ext.args = '--extract-umi-method=tag --umi-tag=UR --cell-tag=CB' + ext.args = '--extract-umi-method=tag --umi-tag=UR --cell-tag=CB' + publishDir = [ + enabled: false + ] } withName: SAMTOOLS_SORT { ext.prefix = { "${meta.id}_sorted" } + publishDir = [ + path: { "${params.outdir}/genetic/bam_qc/" }, + mode: params.publish_dir_mode, + saveAs: { filename -> filename.equals('versions.yml') ? null : filename }, + enabled: params.save_intermediates, + ] } - withName: POPSCLE_DEMUXLET { - ext.args = '--field GT' + withName: FILTER_BAM { + publishDir = [ + path: { "${params.outdir}/genetic/bam_common_variants/" }, + mode: params.publish_dir_mode, + saveAs: { filename -> filename.equals('versions.yml') ? null : filename }, + enabled: params.save_intermediates, + ] } withName: CELLSNP_MODEA { ext.args = '--genotype' + publishDir = [ + path: { "${params.outdir}/genetic/vireo/${meta.id}" }, + mode: params.publish_dir_mode, + saveAs: { filename -> filename.equals('versions.yml') ? null : filename } + ] + } + + withName: VIREO { + publishDir = [ + path: { "${params.outdir}/genetic/vireo/${meta.id}" }, + mode: params.publish_dir_mode, + saveAs: { filename -> filename.equals('versions.yml') ? null : filename } + ] + } + + withName: POPSCLE_DSCPILEUP { + publishDir = [ + path: { "${params.outdir}/genetic/popscle/dscpileup/${meta.id}" }, + mode: params.publish_dir_mode, + saveAs: { filename -> filename.equals('versions.yml') ? null : filename }, + enabled: params.save_intermediates + ] + } + + withName: POPSCLE_DEMUXLET { + ext.args = '--field GT' + publishDir = [ + path: { "${params.outdir}/genetic/popscle/demuxlet/${meta.id}" }, + mode: params.publish_dir_mode, + saveAs: { filename -> filename.equals('versions.yml') ? null : filename } + ] + } + + withName: POPSCLE_FREEMUXLET { + publishDir = [ + path: { "${params.outdir}/genetic/popscle/freemuxlet/${meta.id}" }, + mode: params.publish_dir_mode, + saveAs: { filename -> filename.equals('versions.yml') ? null : filename } + ] + } + + withName: MTXCONVERT_RNA { + publishDir = [ + path: { "${params.outdir}/hashing/mtxconvert/rna/${meta.id}" }, + mode: params.publish_dir_mode, + saveAs: { filename -> filename.equals('versions.yml') ? null : filename }, + enabled: params.save_intermediates + ] + } + + withName: MTXCONVERT_HTO { + publishDir = [ + path: { "${params.outdir}/hashing/mtxconvert/hto/${meta.id}" }, + mode: params.publish_dir_mode, + saveAs: { filename -> filename.equals('versions.yml') ? null : filename }, + enabled: params.save_intermediates + ] + } + + withName: DEMUXEM { + publishDir = [ + path: { "${params.outdir}/hashing/demuxem/${meta.id}" }, + mode: params.publish_dir_mode, + saveAs: { filename -> filename.equals('versions.yml') ? null : filename } + ] + } + + withName: GMMDEMUX { + publishDir = [ + path: { "${params.outdir}/hashing/gmm-demux/${meta.id}" }, + mode: params.publish_dir_mode, + saveAs: { filename -> filename.equals('versions.yml') ? null : filename } + ] } withName: MULTIQC { diff --git a/nextflow.config b/nextflow.config index 2c798b82..72383866 100644 --- a/nextflow.config +++ b/nextflow.config @@ -18,6 +18,7 @@ params { genetic_tools = 'vireo' bam_qc = true common_variants = null + save_intermediates = false // References genome = null diff --git a/nextflow_schema.json b/nextflow_schema.json index 98c4e1c4..23f85cb2 100644 --- a/nextflow_schema.json +++ b/nextflow_schema.json @@ -72,6 +72,12 @@ "description": "The output directory where the results will be saved. You have to use absolute paths to storage on Cloud infrastructure.", "fa_icon": "fas fa-folder-open" }, + "save_intermediates": { + "type": "boolean", + "description": "Save intermediate files.", + "fa_icon": "fas fa-cog", + "default": false + }, "email": { "type": "string", "description": "Email address for completion summary.", diff --git a/subworkflows/local/genetic_demultiplexing/main.nf b/subworkflows/local/genetic_demultiplexing/main.nf index e7c1df89..eb459820 100644 --- a/subworkflows/local/genetic_demultiplexing/main.nf +++ b/subworkflows/local/genetic_demultiplexing/main.nf @@ -45,10 +45,6 @@ workflow GENETIC_DEMULTIPLEXING { .map { meta, _bam, barcodes, nsample, vcf, new_bam -> [meta, new_bam, barcodes, nsample, vcf] } } - if (methods.contains('demuxlet') || methods.contains('freemuxlet')) { - POPSCLE_DSCPILEUP(ch_samplesheet.map { meta, bam, _barcodes, _nsample, vcf -> [meta, bam, vcf] }) - ch_versions = ch_versions.mix(POPSCLE_DSCPILEUP.out.versions) - } if (methods.contains('vireo')) { SAMTOOLS_INDEX(ch_samplesheet.map { meta, bam, _barcodes, _nsample, _vcf -> [meta, bam] }) @@ -64,18 +60,24 @@ workflow GENETIC_DEMULTIPLEXING { ) ch_versions = ch_versions.mix(VIREO.out.versions) } - if (methods.contains('demuxlet')) { - POPSCLE_DEMUXLET( - POPSCLE_DSCPILEUP.out.plp.join(ch_samplesheet).map { meta, plp, bam, _barcodes, _nsample, vcf -> [meta, plp, bam, vcf] } - ) - ch_versions = ch_versions.mix(POPSCLE_DEMUXLET.out.versions) - } - if (methods.contains('freemuxlet')) { - POPSCLE_FREEMUXLET( - POPSCLE_DSCPILEUP.out.directory.join(ch_samplesheet).map { meta, plp_dir, _bam, _barcodes, n_sample, _vcf -> [meta, plp_dir, n_sample] } - ) - ch_versions = ch_versions.mix(POPSCLE_FREEMUXLET.out.versions) + if (methods.contains('demuxlet') || methods.contains('freemuxlet')) { + POPSCLE_DSCPILEUP(ch_samplesheet.map { meta, bam, _barcodes, _nsample, vcf -> [meta, bam, vcf] }) + ch_versions = ch_versions.mix(POPSCLE_DSCPILEUP.out.versions) + + if (methods.contains('demuxlet')) { + POPSCLE_DEMUXLET( + POPSCLE_DSCPILEUP.out.plp.join(ch_samplesheet).map { meta, plp, bam, _barcodes, _nsample, vcf -> [meta, plp, bam, vcf] } + ) + ch_versions = ch_versions.mix(POPSCLE_DEMUXLET.out.versions) + } + if (methods.contains('freemuxlet')) { + POPSCLE_FREEMUXLET( + POPSCLE_DSCPILEUP.out.directory.join(ch_samplesheet).map { meta, plp_dir, _bam, _barcodes, n_sample, _vcf -> [meta, plp_dir, n_sample] } + ) + ch_versions = ch_versions.mix(POPSCLE_FREEMUXLET.out.versions) + } } + if (methods.contains('souporcell')) { error("Souporcell not implemented") } diff --git a/subworkflows/local/hash_demultiplexing/main.nf b/subworkflows/local/hash_demultiplexing/main.nf index dfa3174c..2f5ec9f0 100644 --- a/subworkflows/local/hash_demultiplexing/main.nf +++ b/subworkflows/local/hash_demultiplexing/main.nf @@ -32,7 +32,7 @@ workflow HASH_DEMULTIPLEXING { if (methods.contains('demuxem')) { DEMUXEM( MTXCONVERT_RNA.out.h5.join(MTXCONVERT_HTO.out.csv), - true, + "", [], true, ) diff --git a/subworkflows/local/utils_nfcore_hadge_pipeline/main.nf b/subworkflows/local/utils_nfcore_hadge_pipeline/main.nf index 2778342e..129628f8 100644 --- a/subworkflows/local/utils_nfcore_hadge_pipeline/main.nf +++ b/subworkflows/local/utils_nfcore_hadge_pipeline/main.nf @@ -168,8 +168,8 @@ def validateInputSamplesheet(input) { } } else if (params.mode == 'rescue') { - if (rna_matrix == null || hto_matrix == null || bam == null || barcodes == null || nsample == null || cell_data == null || vcf == null) { - error("RNA matrix, HTO matrix, BAM file, barcodes file, number of samples, cell data, and VCF file must be provided for rescue mode. Please check your input samplesheet.") + if (rna_matrix == null || hto_matrix == null || bam == null || barcodes == null || nsample == null || vcf == null) { + error("RNA matrix, HTO matrix, BAM file, barcodes file, number of samples, and VCF file must be provided for rescue mode. Please check your input samplesheet.") } } diff --git a/workflows/hadge.nf b/workflows/hadge.nf index da828d4f..6c813c5b 100644 --- a/workflows/hadge.nf +++ b/workflows/hadge.nf @@ -45,7 +45,7 @@ workflow HADGE { ) ch_versions = ch_versions.mix(GENETIC_DEMULTIPLEXING.out.versions) } - else if (params.mode == 'hashing' || params.mode == 'rescue') { + if (params.mode == 'hashing' || params.mode == 'rescue') { HASH_DEMULTIPLEXING(ch_hashing, params.hash_tools.split(',')) ch_versions = ch_versions.mix(HASH_DEMULTIPLEXING.out.versions) } From 818acfd2a14d5e13eb43655ce52f1734b3074cb4 Mon Sep 17 00:00:00 2001 From: nictru Date: Sun, 15 Jun 2025 10:43:40 +0200 Subject: [PATCH 27/74] Update demuxem output definition --- modules/nf-core/demuxem/main.nf | 24 ++++++++++++++++-------- 1 file changed, 16 insertions(+), 8 deletions(-) diff --git a/modules/nf-core/demuxem/main.nf b/modules/nf-core/demuxem/main.nf index f1a19034..88f3f18a 100644 --- a/modules/nf-core/demuxem/main.nf +++ b/modules/nf-core/demuxem/main.nf @@ -9,13 +9,17 @@ process DEMUXEM { input: tuple val(meta), path(input_raw_gene_bc_matrices_h5), path(input_hto_csv_file) - val generate_gender_plot + val gender_genes val genome val generate_diagnostic_plots output: - tuple val(meta), path("*_demux.zarr.zip"), emit: zarr - tuple val(meta), path("*.out.demuxEM.zarr.zip"), emit: out_zarr + tuple val(meta), path("${prefix}_demux.zarr.zip"), emit: zarr + tuple val(meta), path("${prefix}.out.demuxEM.zarr.zip"), emit: out_zarr + tuple val(meta), path("${prefix}.ambient_hashtag.hist.pdf"), emit: ambient_hashtag_hist, optional: true + tuple val(meta), path("${prefix}.background_probabilities.bar.pdf"), emit: background_probabilities_bar, optional: true + tuple val(meta), path("${prefix}.real_content.hist.pdf"), emit: real_content_hist, optional: true + tuple val(meta), path("${prefix}.rna_demux.hist.pdf"), emit: rna_demux_hist, optional: true path "versions.yml", emit: versions when: @@ -23,13 +27,17 @@ process DEMUXEM { script: def args = task.ext.args ?: '' - def prefix = task.ext.prefix ?: "${meta.id}" - def generateGenderPlot = generate_gender_plot ? "--generate-gender-plot ${generate_gender_plot}" : "" - def genome_file = genome ? "--genome ${genome}" : "" - def diagnostic_plots = generate_diagnostic_plots ? "--generate-diagnostic-plots ${generate_diagnostic_plots}" : "" + prefix = task.ext.prefix ?: "${meta.id}" + def generateGenderPlot = gender_genes ? "--generate-gender-plot ${gender_genes}" : "" + def genome_args = genome ? "--genome ${genome}" : "" + def diagnostic_plots = generate_diagnostic_plots ? "--generate-diagnostic-plots" : "" """ demuxEM ${input_raw_gene_bc_matrices_h5} ${input_hto_csv_file} ${prefix} \\ - -p $task.cpus + -p ${task.cpus} \\ + ${generateGenderPlot} \\ + ${genome_args} \\ + ${diagnostic_plots} \\ + ${args} cat <<-END_VERSIONS > versions.yml "${task.process}": From f63f6ad0ff9bc1f70712865e4442d748f783a6ca Mon Sep 17 00:00:00 2001 From: nictru Date: Sun, 15 Jun 2025 11:06:02 +0200 Subject: [PATCH 28/74] Add all demuxem optional parameters --- conf/modules.config | 36 +++++++---- nextflow.config | 8 +++ nextflow_schema.json | 63 ++++++++++++++++--- .../local/hash_demultiplexing/main.nf | 4 +- 4 files changed, 90 insertions(+), 21 deletions(-) diff --git a/conf/modules.config b/conf/modules.config index b739bd5d..6c323dbb 100644 --- a/conf/modules.config +++ b/conf/modules.config @@ -59,11 +59,11 @@ process { } withName: CELLSNP_MODEA { - ext.args = '--genotype' + ext.args = '--genotype' publishDir = [ path: { "${params.outdir}/genetic/vireo/${meta.id}" }, mode: params.publish_dir_mode, - saveAs: { filename -> filename.equals('versions.yml') ? null : filename } + saveAs: { filename -> filename.equals('versions.yml') ? null : filename }, ] } @@ -71,7 +71,7 @@ process { publishDir = [ path: { "${params.outdir}/genetic/vireo/${meta.id}" }, mode: params.publish_dir_mode, - saveAs: { filename -> filename.equals('versions.yml') ? null : filename } + saveAs: { filename -> filename.equals('versions.yml') ? null : filename }, ] } @@ -80,16 +80,16 @@ process { path: { "${params.outdir}/genetic/popscle/dscpileup/${meta.id}" }, mode: params.publish_dir_mode, saveAs: { filename -> filename.equals('versions.yml') ? null : filename }, - enabled: params.save_intermediates + enabled: params.save_intermediates, ] } withName: POPSCLE_DEMUXLET { - ext.args = '--field GT' + ext.args = '--field GT' publishDir = [ path: { "${params.outdir}/genetic/popscle/demuxlet/${meta.id}" }, mode: params.publish_dir_mode, - saveAs: { filename -> filename.equals('versions.yml') ? null : filename } + saveAs: { filename -> filename.equals('versions.yml') ? null : filename }, ] } @@ -97,7 +97,7 @@ process { publishDir = [ path: { "${params.outdir}/genetic/popscle/freemuxlet/${meta.id}" }, mode: params.publish_dir_mode, - saveAs: { filename -> filename.equals('versions.yml') ? null : filename } + saveAs: { filename -> filename.equals('versions.yml') ? null : filename }, ] } @@ -106,7 +106,7 @@ process { path: { "${params.outdir}/hashing/mtxconvert/rna/${meta.id}" }, mode: params.publish_dir_mode, saveAs: { filename -> filename.equals('versions.yml') ? null : filename }, - enabled: params.save_intermediates + enabled: params.save_intermediates, ] } @@ -115,15 +115,29 @@ process { path: { "${params.outdir}/hashing/mtxconvert/hto/${meta.id}" }, mode: params.publish_dir_mode, saveAs: { filename -> filename.equals('versions.yml') ? null : filename }, - enabled: params.save_intermediates + enabled: params.save_intermediates, ] } withName: DEMUXEM { + ext.args = { + [ + "--alpha-on-samples", + params.demuxem_alpha_on_samples, + "--min-num-genes", + params.demuxem_min_num_genes, + "--min-num-umis", + params.demuxem_min_num_umis, + "--min-signal-hashtag", + params.demuxem_min_signal_hashtag, + "--random-state", + params.demuxem_random_state, + ].join(" ") + } publishDir = [ path: { "${params.outdir}/hashing/demuxem/${meta.id}" }, mode: params.publish_dir_mode, - saveAs: { filename -> filename.equals('versions.yml') ? null : filename } + saveAs: { filename -> filename.equals('versions.yml') ? null : filename }, ] } @@ -131,7 +145,7 @@ process { publishDir = [ path: { "${params.outdir}/hashing/gmm-demux/${meta.id}" }, mode: params.publish_dir_mode, - saveAs: { filename -> filename.equals('versions.yml') ? null : filename } + saveAs: { filename -> filename.equals('versions.yml') ? null : filename }, ] } diff --git a/nextflow.config b/nextflow.config index 72383866..d1209977 100644 --- a/nextflow.config +++ b/nextflow.config @@ -25,6 +25,14 @@ params { igenomes_base = 's3://ngi-igenomes/igenomes/' igenomes_ignore = false + // demuxEM + demuxem_alpha_on_samples = 0.0 + demuxem_min_num_genes = 100 + demuxem_min_num_umis = 100 + demuxem_min_signal_hashtag = 10 + demuxem_random_state = 0 + demuxem_gender_genes = "" + // MultiQC options multiqc_config = null multiqc_title = null diff --git a/nextflow_schema.json b/nextflow_schema.json index 23f85cb2..b51ac9cd 100644 --- a/nextflow_schema.json +++ b/nextflow_schema.json @@ -8,7 +8,7 @@ "input_output_options": { "title": "Input/output options", "type": "object", - "fa_icon": "fas fa-terminal", + "fa_icon": "fas fa-exchange-alt", "description": "Define where the pipeline should find input data and save output data.", "required": ["input", "mode", "outdir"], "properties": { @@ -26,21 +26,21 @@ "mode": { "type": "string", "description": "Mode of the pipeline.", - "fa_icon": "fas fa-cog", + "fa_icon": "fas fa-project-diagram", "enum": ["genetic", "hashing", "rescue", "donor_match"], "default": "rescue" }, "match_donor": { "type": "boolean", "description": "Match donor.", - "fa_icon": "fas fa-cog", + "fa_icon": "fas fa-user-check", "default": false }, "hash_tools": { "type": "string", "description": "Tools used for hash demultiplexing.", "help_text": "If you want to use multiple, separate with a comma. The available tools are: htodemux, multiseq, cellhashr, demuxem, gmm-demux, hasheddrops, hashsolo", - "fa_icon": "fas fa-cog", + "fa_icon": "fas fa-hashtag", "default": "gmm-demux", "pattern": "^(htodemux|multiseq|cellhashr|demuxem|gmm-demux|hasheddrops|hashsolo)(,(htodemux|multiseq|cellhashr|demuxem|gmm-demux|hasheddrops|hashsolo))*$" }, @@ -48,20 +48,20 @@ "type": "string", "description": "Tools used for genetic demultiplexing.", "help_text": "If you want to use multiple, separate with a comma. The available tools are: vireo, demuxlet, freemuxlet, souporcell, cellsnp", - "fa_icon": "fas fa-cog", + "fa_icon": "fas fa-dna", "default": "vireo", "pattern": "^(vireo|demuxlet|freemuxlet|souporcell|cellsnp)(,(vireo|demuxlet|freemuxlet|souporcell|cellsnp))*$" }, "bam_qc": { "type": "boolean", "description": "Perform BAM QC.", - "fa_icon": "fas fa-cog", + "fa_icon": "fas fa-chart-line", "default": true }, "common_variants": { "type": "string", "description": "File with common variants. If provided, the BAM files will be filtered to only include reads that overlap with the common variants.", - "fa_icon": "fas fa-cog", + "fa_icon": "fas fa-dna", "default": null, "pattern": "^\\S+\\.vcf(\\.gz)?$", "exists": true @@ -75,7 +75,7 @@ "save_intermediates": { "type": "boolean", "description": "Save intermediate files.", - "fa_icon": "fas fa-cog", + "fa_icon": "fas fa-save", "default": false }, "email": { @@ -131,6 +131,50 @@ } } }, + "demuxem_options": { + "title": "demuxEM options", + "type": "object", + "fa_icon": "fas fa-vial", + "description": "Options specific to the demuxEM tool for cell hashing demultiplexing.", + "properties": { + "demuxem_alpha_on_samples": { + "type": "number", + "description": "The Dirichlet prior concentration parameter (alpha) on samples. An alpha value < 1.0 will make the prior sparse.", + "default": 0.0, + "fa_icon": "fas fa-sliders-h" + }, + "demuxem_min_num_genes": { + "type": "integer", + "description": "Only demultiplex cells/nuclei with at least this number of expressed genes.", + "default": 100, + "fa_icon": "fas fa-sort-numeric-up" + }, + "demuxem_min_num_umis": { + "type": "integer", + "description": "Only demultiplex cells/nuclei with at least this number of UMIs.", + "default": 100, + "fa_icon": "fas fa-sort-amount-up" + }, + "demuxem_min_signal_hashtag": { + "type": "number", + "description": "Any cell/nucleus with less than this count of hashtags from the signal will be marked as unknown.", + "default": 10, + "fa_icon": "fas fa-signal" + }, + "demuxem_random_state": { + "type": "integer", + "description": "The random seed used in the KMeans algorithm to separate empty ADT droplets from others.", + "default": 0, + "fa_icon": "fas fa-random" + }, + "demuxem_gender_genes": { + "type": "string", + "description": "Comma-separated list of gender-specific genes (e.g. Xist) for generating violin plots.", + "default": "", + "fa_icon": "fas fa-venus-mars" + } + } + }, "institutional_config_options": { "title": "Institutional config options", "type": "object", @@ -284,6 +328,9 @@ { "$ref": "#/$defs/reference_genome_options" }, + { + "$ref": "#/$defs/demuxem_options" + }, { "$ref": "#/$defs/institutional_config_options" }, diff --git a/subworkflows/local/hash_demultiplexing/main.nf b/subworkflows/local/hash_demultiplexing/main.nf index 2f5ec9f0..ded2a736 100644 --- a/subworkflows/local/hash_demultiplexing/main.nf +++ b/subworkflows/local/hash_demultiplexing/main.nf @@ -32,8 +32,8 @@ workflow HASH_DEMULTIPLEXING { if (methods.contains('demuxem')) { DEMUXEM( MTXCONVERT_RNA.out.h5.join(MTXCONVERT_HTO.out.csv), - "", - [], + params.demuxem_gender_genes, + params.genome ?: [], true, ) ch_versions = ch_versions.mix(DEMUXEM.out.versions) From dd2dd5d69e78663e971c23701ac48834718efd29 Mon Sep 17 00:00:00 2001 From: nictru Date: Sun, 15 Jun 2025 13:34:28 +0200 Subject: [PATCH 29/74] Add GMM-demux optional params --- assets/schema_input.json | 11 ++- conf/modules.config | 8 +++ modules/nf-core/gmmdemux/main.nf | 68 ++++++++++--------- nextflow.config | 5 ++ nextflow_schema.json | 39 +++++++++++ .../local/genetic_demultiplexing/main.nf | 27 ++++---- .../local/hash_demultiplexing/main.nf | 39 ++++++++--- .../local/utils_nfcore_hadge_pipeline/main.nf | 28 -------- workflows/hadge.nf | 6 +- 9 files changed, 142 insertions(+), 89 deletions(-) diff --git a/assets/schema_input.json b/assets/schema_input.json index 6a03b57f..921ef32f 100644 --- a/assets/schema_input.json +++ b/assets/schema_input.json @@ -45,10 +45,11 @@ "pattern": "^\\S+\\.tsv$", "errorMessage": "Barcodes file must be provided, cannot contain spaces and must have extension '.tsv'" }, - "nsample": { + "n_samples": { "type": "integer", "minimum": 1, - "errorMessage": "Number of samples must be provided and must be greater than 0" + "errorMessage": "Number of samples must be greater than 0", + "meta": ["n_samples"] }, "vcf": { "type": "string", @@ -57,6 +58,12 @@ "default": null, "pattern": "^\\S+\\.vcf$", "errorMessage": "VCF file must be provided, cannot contain spaces and must have extension '.vcf'" + }, + "n_cells": { + "type": "integer", + "minimum": 1, + "errorMessage": "Number of cells must be an integer greater than 0", + "meta": ["n_cells"] } }, "required": ["sample"] diff --git a/conf/modules.config b/conf/modules.config index 6c323dbb..269e0189 100644 --- a/conf/modules.config +++ b/conf/modules.config @@ -142,6 +142,14 @@ process { } withName: GMMDEMUX { + ext.args = { + ((params.gmmdemux_extract ? ["--extract", params.gmmdemux_extract] : []) + [ + "--threshold", + params.gmmdemux_threshold, + "--random_seed", + params.gmmdemux_random_state, + ]).join(" ") + } publishDir = [ path: { "${params.outdir}/hashing/gmm-demux/${meta.id}" }, mode: params.publish_dir_mode, diff --git a/modules/nf-core/gmmdemux/main.nf b/modules/nf-core/gmmdemux/main.nf index 6da98713..51b1fcd3 100644 --- a/modules/nf-core/gmmdemux/main.nf +++ b/modules/nf-core/gmmdemux/main.nf @@ -1,63 +1,65 @@ - process GMMDEMUX { - tag "$meta.id" + tag "${meta.id}" label 'process_low' conda "${moduleDir}/environment.yml" - container "${ workflow.containerEngine == 'singularity' && !task.ext.singularity_pull_docker_container ? - 'https://depot.galaxyproject.org/singularity/gmm-demux:0.2.2.3--pyh7cba7a3_0': - 'biocontainers/gmm-demux:0.2.2.3--pyh7cba7a3_0' }" + container "${workflow.containerEngine == 'singularity' && !task.ext.singularity_pull_docker_container + ? 'https://depot.galaxyproject.org/singularity/gmm-demux:0.2.2.3--pyh7cba7a3_0' + : 'biocontainers/gmm-demux:0.2.2.3--pyh7cba7a3_0'}" input: - tuple val(meta), path(hto_matrix), val(hto_names) - val type_report + tuple val(meta), path(hto_matrix), val(hto_names), val(estimated_cells) + val full_report val summary_report - path skip - path examine + path classification_report + path cell_list output: - tuple val(meta), path("barcodes.tsv.gz" ), emit: barcodes - tuple val(meta), path("matrix.mtx.gz" ), emit: matrix - tuple val(meta), path("features.tsv.gz" ), emit: features - tuple val(meta), path("GMM_*.csv" ), emit: classification_report - tuple val(meta), path("GMM_*.config" ), emit: config_report + tuple val(meta), path("barcodes.tsv.gz"), emit: barcodes + tuple val(meta), path("matrix.mtx.gz"), emit: matrix + tuple val(meta), path("features.tsv.gz"), emit: features + tuple val(meta), path("GMM_*.csv"), emit: classification_report + tuple val(meta), path("GMM_*.config"), emit: config_report tuple val(meta), path("summary_report_*.txt"), emit: summary_report, optional: true - path "versions.yml" , emit: versions + path "versions.yml", emit: versions when: task.ext.when == null || task.ext.when script: - def args = task.ext.args ?: '' - def prefix = task.ext.prefix ?: "${meta.id}" - def skip = skip ? "--skip $skip" : "" - def examine_cells = examine ? "--examine $examine" : "" - def VERSION = '0.2.2.3' // WARN: Version information not provided by tool on CLI. Please update version string below when bumping container versions. - def type_report = type_report ? "-f ." : "-s ." - def summary_rep = summary_report ? "-r ${prefix}_summary_report.txt" : "" + // Docs: https://gmm-demux.readthedocs.io/en/latest/usage.html#command-line-tools + def args = task.ext.args ?: '' + def prefix = task.ext.prefix ?: "${meta.id}" + def skip_arg = classification_report ? "--skip ${classification_report}" : "" + def examine_arg = cell_list ? "--examine ${cell_list}" : "" + // WARN: Version information not provided by tool on CLI. Please update version string below when bumping container versions. + def VERSION = '0.2.2.3' + def report_arg = full_report ? "-f ." : "-s ." + def estimated_cells_arg = estimated_cells ? "--summary ${estimated_cells}" : "" + def summary_report_arg = summary_report ? "-r ${prefix}_summary_report.txt" : "" """ if [[ ${summary_report} == true ]]; then cat /dev/null > ${prefix}_summary_report.txt fi - GMM-demux $args \\ - $type_report \\ - $summary_rep \\ - $skip \\ - $examine_cells \\ - $hto_matrix \\ - $hto_names \\ - -o . + GMM-demux ${hto_matrix} ${hto_names} \\ + ${report_arg} \\ + ${summary_report_arg} \\ + ${estimated_cells_arg} \\ + ${skip_arg} \\ + ${examine_arg} \\ + -o . \\ + ${args} cat <<-END_VERSIONS > versions.yml "${task.process}": - GMM-Demux: $VERSION + GMM-Demux: ${VERSION} END_VERSIONS """ stub: def VERSION = '0.2.2.3' - def prefix = task.ext.prefix ?: "${meta.id}" + def prefix = task.ext.prefix ?: "${meta.id}" """ echo "" | gzip > barcodes.tsv.gz echo "" | gzip > features.tsv.gz @@ -67,7 +69,7 @@ process GMMDEMUX { cat <<-END_VERSIONS > versions.yml "${task.process}": - GMM-Demux: $VERSION + GMM-Demux: ${VERSION} END_VERSIONS """ } diff --git a/nextflow.config b/nextflow.config index d1209977..cc9bdc39 100644 --- a/nextflow.config +++ b/nextflow.config @@ -33,6 +33,11 @@ params { demuxem_random_state = 0 demuxem_gender_genes = "" + // GMM-Demux + gmmdemux_extract = null + gmmdemux_threshold = 0.8 + gmmdemux_random_state = 0 + // MultiQC options multiqc_config = null multiqc_title = null diff --git a/nextflow_schema.json b/nextflow_schema.json index b51ac9cd..c756d4c5 100644 --- a/nextflow_schema.json +++ b/nextflow_schema.json @@ -141,30 +141,36 @@ "type": "number", "description": "The Dirichlet prior concentration parameter (alpha) on samples. An alpha value < 1.0 will make the prior sparse.", "default": 0.0, + "minimum": 0, + "maximum": 1, "fa_icon": "fas fa-sliders-h" }, "demuxem_min_num_genes": { "type": "integer", "description": "Only demultiplex cells/nuclei with at least this number of expressed genes.", "default": 100, + "minimum": 0, "fa_icon": "fas fa-sort-numeric-up" }, "demuxem_min_num_umis": { "type": "integer", "description": "Only demultiplex cells/nuclei with at least this number of UMIs.", "default": 100, + "minimum": 0, "fa_icon": "fas fa-sort-amount-up" }, "demuxem_min_signal_hashtag": { "type": "number", "description": "Any cell/nucleus with less than this count of hashtags from the signal will be marked as unknown.", "default": 10, + "minimum": 0, "fa_icon": "fas fa-signal" }, "demuxem_random_state": { "type": "integer", "description": "The random seed used in the KMeans algorithm to separate empty ADT droplets from others.", "default": 0, + "minimum": 0, "fa_icon": "fas fa-random" }, "demuxem_gender_genes": { @@ -175,6 +181,36 @@ } } }, + "gmmdemux_options": { + "title": "GMM-Demux options", + "type": "object", + "fa_icon": "fas fa-layer-group", + "description": "Options specific to the GMM-Demux tool for cell hashing demultiplexing.", + "properties": { + "gmmdemux_extract": { + "type": "string", + "description": "Names of the HTO tag(s) to extract, separated by ','. Joint HTO samples are combined with '+', such as 'HTO_1+HTO_2'.", + "default": null, + "fa_icon": "fas fa-filter" + }, + "gmmdemux_threshold": { + "type": "number", + "description": "The confidence threshold value for classification. A higher value leads to more stringent classification.", + "default": 0.8, + "minimum": 0, + "maximum": 1, + "fa_icon": "fas fa-percentage", + "help_text": "Requires a float in (0,1). Default value: 0.8" + }, + "gmmdemux_random_state": { + "type": "integer", + "description": "The random seed used in the GaussianMixture algorithm.", + "default": 0, + "minimum": 0, + "fa_icon": "fas fa-dice" + } + } + }, "institutional_config_options": { "title": "Institutional config options", "type": "object", @@ -331,6 +367,9 @@ { "$ref": "#/$defs/demuxem_options" }, + { + "$ref": "#/$defs/gmmdemux_options" + }, { "$ref": "#/$defs/institutional_config_options" }, diff --git a/subworkflows/local/genetic_demultiplexing/main.nf b/subworkflows/local/genetic_demultiplexing/main.nf index eb459820..c02b0efe 100644 --- a/subworkflows/local/genetic_demultiplexing/main.nf +++ b/subworkflows/local/genetic_demultiplexing/main.nf @@ -19,17 +19,17 @@ workflow GENETIC_DEMULTIPLEXING { ch_versions = Channel.empty() if (bam_qc) { - BAM_QC(ch_samplesheet.map { meta, bam, _barcodes, _nsample, _vcf -> [meta, bam] }) + BAM_QC(ch_samplesheet.map { meta, bam, _barcodes, _vcf -> [meta, bam] }) ch_versions = ch_versions.mix(BAM_QC.out.versions) ch_samplesheet = ch_samplesheet .join(BAM_QC.out.bam) - .map { meta, _bam, barcodes, nsample, vcf, new_bam -> [meta, new_bam, barcodes, nsample, vcf] } + .map { meta, _bam, barcodes, vcf, new_bam -> [meta, new_bam, barcodes, vcf] } } if (common_variants) { FILTER_BAM( - ch_samplesheet.map { meta, bam, barcodes, _nsample, _vcf -> + ch_samplesheet.map { meta, bam, barcodes, _vcf -> [ meta, bam, @@ -42,38 +42,37 @@ workflow GENETIC_DEMULTIPLEXING { ch_samplesheet = ch_samplesheet .join(FILTER_BAM.out.bam) - .map { meta, _bam, barcodes, nsample, vcf, new_bam -> [meta, new_bam, barcodes, nsample, vcf] } + .map { meta, _bam, barcodes, vcf, new_bam -> [meta, new_bam, barcodes, vcf] } } if (methods.contains('vireo')) { - SAMTOOLS_INDEX(ch_samplesheet.map { meta, bam, _barcodes, _nsample, _vcf -> [meta, bam] }) + SAMTOOLS_INDEX(ch_samplesheet.map { meta, bam, _barcodes, _vcf -> [meta, bam] }) ch_versions = ch_versions.mix(SAMTOOLS_INDEX.out.versions) CELLSNP_MODEA( - ch_samplesheet.join(SAMTOOLS_INDEX.out.bai).map { meta, bam, barcodes, _nsample, vcf, bai -> [meta, bam, bai, vcf, barcodes] } + ch_samplesheet.join(SAMTOOLS_INDEX.out.bai).map { meta, bam, barcodes, vcf, bai -> [meta, bam, bai, vcf, barcodes] } ) ch_versions = ch_versions.mix(CELLSNP_MODEA.out.versions) VIREO( - ch_samplesheet.join(CELLSNP_MODEA.out.cell).map { meta, _bam, _barcodes, nsample, vcf, cell -> [meta, cell, nsample, vcf, []] } + ch_samplesheet.join(CELLSNP_MODEA.out.cell).map { meta, _bam, _barcodes, vcf, cell -> [meta, cell, meta.n_samples, vcf, []] } ) ch_versions = ch_versions.mix(VIREO.out.versions) } if (methods.contains('demuxlet') || methods.contains('freemuxlet')) { - POPSCLE_DSCPILEUP(ch_samplesheet.map { meta, bam, _barcodes, _nsample, vcf -> [meta, bam, vcf] }) + ch_dscpileup = ch_samplesheet.map { meta, bam, _barcodes, vcf -> [meta, bam, vcf] } + POPSCLE_DSCPILEUP(ch_dscpileup) ch_versions = ch_versions.mix(POPSCLE_DSCPILEUP.out.versions) if (methods.contains('demuxlet')) { - POPSCLE_DEMUXLET( - POPSCLE_DSCPILEUP.out.plp.join(ch_samplesheet).map { meta, plp, bam, _barcodes, _nsample, vcf -> [meta, plp, bam, vcf] } - ) + ch_demuxlet = POPSCLE_DSCPILEUP.out.plp.join(ch_samplesheet).map { meta, plp, bam, _barcodes, vcf -> [meta, plp, bam, vcf] } + POPSCLE_DEMUXLET(ch_demuxlet) ch_versions = ch_versions.mix(POPSCLE_DEMUXLET.out.versions) } if (methods.contains('freemuxlet')) { - POPSCLE_FREEMUXLET( - POPSCLE_DSCPILEUP.out.directory.join(ch_samplesheet).map { meta, plp_dir, _bam, _barcodes, n_sample, _vcf -> [meta, plp_dir, n_sample] } - ) + ch_freemuxlet = POPSCLE_DSCPILEUP.out.directory.join(ch_samplesheet).map { meta, plp_dir, _bam, _barcodes, _vcf -> [meta, plp_dir, meta.n_samples] } + POPSCLE_FREEMUXLET(ch_freemuxlet) ch_versions = ch_versions.mix(POPSCLE_FREEMUXLET.out.versions) } } diff --git a/subworkflows/local/hash_demultiplexing/main.nf b/subworkflows/local/hash_demultiplexing/main.nf index ded2a736..d483425f 100644 --- a/subworkflows/local/hash_demultiplexing/main.nf +++ b/subworkflows/local/hash_demultiplexing/main.nf @@ -12,14 +12,6 @@ workflow HASH_DEMULTIPLEXING { ch_versions = Channel.empty() - ch_rna = ch_samplesheet.map { meta, rna, _hto -> [meta, rna] } - MTXCONVERT_RNA(ch_rna, false) - ch_versions = ch_versions.mix(MTXCONVERT_RNA.out.versions) - - ch_hto = ch_samplesheet.map { meta, _rna, hto -> [meta, hto] } - MTXCONVERT_HTO(ch_hto, true) - ch_versions = ch_versions.mix(MTXCONVERT_HTO.out.versions) - if (methods.contains('htodemux')) { error("HtoDemux not implemented") } @@ -30,6 +22,23 @@ workflow HASH_DEMULTIPLEXING { error("CellHashR not implemented") } if (methods.contains('demuxem')) { + ch_samplesheet.map { meta, rna, hto -> + { + if (!rna) { + error("RNA matrix not provided for sample ${meta.id}, but this is required for DemuxEM. Please check your input samplesheet.") + } + if (!hto) { + error("HTO matrix not provided for sample ${meta.id}, but this is required for DemuxEM. Please check your input samplesheet.") + } + } + } + + MTXCONVERT_RNA(ch_samplesheet.map { meta, rna, _hto -> [meta, rna] }, false) + ch_versions = ch_versions.mix(MTXCONVERT_RNA.out.versions) + + MTXCONVERT_HTO(ch_samplesheet.map { meta, _rna, hto -> [meta, hto] }, true) + ch_versions = ch_versions.mix(MTXCONVERT_HTO.out.versions) + DEMUXEM( MTXCONVERT_RNA.out.h5.join(MTXCONVERT_HTO.out.csv), params.demuxem_gender_genes, @@ -39,8 +48,20 @@ workflow HASH_DEMULTIPLEXING { ch_versions = ch_versions.mix(DEMUXEM.out.versions) } if (methods.contains('gmm-demux')) { + ch_gmmdemux = ch_samplesheet.map { meta, _rna, hto -> [meta, hto, "MS-11,MS-12", meta.n_cells] } + + ch_gmmdemux.map { meta, hto, hto_names, _estimated_cells -> + { + if (!hto) { + error("HTO matrix not provided for sample ${meta.id}, but this is required for GMM-Demux. Please check your input samplesheet.") + } + if (!hto_names) { + error("HTO names not provided for sample ${meta.id}, but this is required for GMM-Demux. Please check your input samplesheet.") + } + } + } GMMDEMUX( - ch_hto.map { meta, hto -> [meta, hto, "MS-11,MS-12"] }, + ch_gmmdemux, true, true, [], diff --git a/subworkflows/local/utils_nfcore_hadge_pipeline/main.nf b/subworkflows/local/utils_nfcore_hadge_pipeline/main.nf index 129628f8..6234ca54 100644 --- a/subworkflows/local/utils_nfcore_hadge_pipeline/main.nf +++ b/subworkflows/local/utils_nfcore_hadge_pipeline/main.nf @@ -145,34 +145,6 @@ def validateInputParameters() { // Validate channels from input samplesheet // def validateInputSamplesheet(input) { - def (_meta, rna_matrix, hto_matrix, bam, barcodes, nsample, vcf) = input - - if (params.mode == 'hashing') { - if (rna_matrix == null || hto_matrix == null) { - error("RNA matrix and HTO matrix must be provided for hashing mode. Please check your input samplesheet.") - } - } - else if (params.mode == 'genetic') { - def methods = params.genetic_tools.split(',') - - if (bam == null || vcf == null) { - error("BAM file and VCF file must be provided for genetic mode. Please check your input samplesheet.") - } - - if (methods.contains('freemuxlet') && !nsample) { - error("Number of samples not provided for sample ${_meta.id}. This is required for freemuxlet.") - } - - if (methods.contains('vireo') && !barcodes) { - error("Barcodes file must be provided for vireo. Please check your input samplesheet.") - } - } - else if (params.mode == 'rescue') { - if (rna_matrix == null || hto_matrix == null || bam == null || barcodes == null || nsample == null || vcf == null) { - error("RNA matrix, HTO matrix, BAM file, barcodes file, number of samples, and VCF file must be provided for rescue mode. Please check your input samplesheet.") - } - } - return input } diff --git a/workflows/hadge.nf b/workflows/hadge.nf index 6c813c5b..526460c3 100644 --- a/workflows/hadge.nf +++ b/workflows/hadge.nf @@ -28,12 +28,12 @@ workflow HADGE { ch_versions = Channel.empty() ch_multiqc_files = Channel.empty() - ch_hashing = ch_samplesheet.map { meta, rna_matrix, hto_matrix, _bam, _barcodes, _nsample, _vcf -> + ch_hashing = ch_samplesheet.map { meta, rna_matrix, hto_matrix, _bam, _barcodes, _vcf -> [meta, rna_matrix, hto_matrix] } - ch_genetic = ch_samplesheet.map { meta, _rna_matrix, _hto_matrix, bam, barcodes, nsample, vcf -> - [meta, bam, barcodes, nsample, vcf] + ch_genetic = ch_samplesheet.map { meta, _rna_matrix, _hto_matrix, bam, barcodes, vcf -> + [meta, bam, barcodes, vcf] } if (params.mode == 'genetic' || params.mode == 'rescue') { From e491060fb7fd8ba1e8bd11c761e8b4738c73aa50 Mon Sep 17 00:00:00 2001 From: nictru Date: Sun, 15 Jun 2025 14:49:02 +0200 Subject: [PATCH 30/74] Add cellSNP optional parameters --- conf/modules.config | 28 ++++- nextflow.config | 277 +++++++++++++++++++++++-------------------- nextflow_schema.json | 77 ++++++++++++ 3 files changed, 249 insertions(+), 133 deletions(-) diff --git a/conf/modules.config b/conf/modules.config index 269e0189..ab526f27 100644 --- a/conf/modules.config +++ b/conf/modules.config @@ -59,7 +59,28 @@ process { } withName: CELLSNP_MODEA { - ext.args = '--genotype' + ext.args = { + [ + "--genotype", + "--cellTAG", + params.cellsnp_celltag, + "--UMItag", + params.cellsnp_umitag, + "--minCOUNT", + params.cellsnp_mincount, + "--minMAF", + params.cellsnp_minmaf, + params.cellsnp_inclflag ? "--inclFLAG ${params.cellsnp_inclflag}" : "", + params.cellsnp_exclflag ? "--exclFLAG ${params.cellsnp_exclflag}" : "", + "--minLEN", + params.cellsnp_minlen, + "--minMAPQ", + params.cellsnp_minmapq, + "--maxDEPTH", + params.cellsnp_maxdepth, + params.cellsnp_countorphan ? "--countORPHAN" : "", + ].join(" ") + } publishDir = [ path: { "${params.outdir}/genetic/vireo/${meta.id}" }, mode: params.publish_dir_mode, @@ -143,12 +164,13 @@ process { withName: GMMDEMUX { ext.args = { - ((params.gmmdemux_extract ? ["--extract", params.gmmdemux_extract] : []) + [ + [ "--threshold", params.gmmdemux_threshold, "--random_seed", params.gmmdemux_random_state, - ]).join(" ") + params.gmmdemux_extract ? "--extract ${params.gmmdemux_extract}" : "", + ].join(" ") } publishDir = [ path: { "${params.outdir}/hashing/gmm-demux/${meta.id}" }, diff --git a/nextflow.config b/nextflow.config index cc9bdc39..3525a65b 100644 --- a/nextflow.config +++ b/nextflow.config @@ -11,39 +11,51 @@ params { // TODO nf-core: Specify your pipeline's command line flags // Input options - input = null - mode = 'rescue' - match_donor = false - hash_tools = 'gmm-demux' - genetic_tools = 'vireo' - bam_qc = true - common_variants = null - save_intermediates = false + input = null + mode = 'rescue' + match_donor = false + hash_tools = 'gmm-demux' + genetic_tools = 'vireo' + bam_qc = true + common_variants = null + save_intermediates = false // References - genome = null - igenomes_base = 's3://ngi-igenomes/igenomes/' - igenomes_ignore = false + genome = null + igenomes_base = 's3://ngi-igenomes/igenomes/' + igenomes_ignore = false // demuxEM - demuxem_alpha_on_samples = 0.0 - demuxem_min_num_genes = 100 - demuxem_min_num_umis = 100 - demuxem_min_signal_hashtag = 10 - demuxem_random_state = 0 - demuxem_gender_genes = "" + demuxem_alpha_on_samples = 0.0 + demuxem_min_num_genes = 100 + demuxem_min_num_umis = 100 + demuxem_min_signal_hashtag = 10 + demuxem_random_state = 0 + demuxem_gender_genes = "" // GMM-Demux - gmmdemux_extract = null - gmmdemux_threshold = 0.8 - gmmdemux_random_state = 0 + gmmdemux_extract = null + gmmdemux_threshold = 0.8 + gmmdemux_random_state = 0 + + // CellSNP + cellsnp_celltag = 'CB' + cellsnp_umitag = 'Auto' + cellsnp_mincount = 20 + cellsnp_minmaf = 0.0 + cellsnp_inclflag = "" + cellsnp_exclflag = "" + cellsnp_minlen = 30 + cellsnp_minmapq = 20 + cellsnp_maxdepth = 0 + cellsnp_countorphan = false // MultiQC options - multiqc_config = null - multiqc_title = null - multiqc_logo = null - max_multiqc_email_size = '25.MB' - multiqc_methods_description = null + multiqc_config = null + multiqc_title = null + multiqc_logo = null + max_multiqc_email_size = '25.MB' + multiqc_methods_description = null // Boilerplate options outdir = null @@ -58,17 +70,18 @@ params { show_hidden = false version = false pipelines_testdata_base_path = 'https://raw.githubusercontent.com/nf-core/test-datasets/' - trace_report_suffix = new java.util.Date().format( 'yyyy-MM-dd_HH-mm-ss')// Config options - config_profile_name = null - config_profile_description = null + trace_report_suffix = new java.util.Date().format('yyyy-MM-dd_HH-mm-ss') + // Config options + config_profile_name = null + config_profile_description = null - custom_config_version = 'master' - custom_config_base = "https://raw.githubusercontent.com/nf-core/configs/${params.custom_config_version}" - config_profile_contact = null - config_profile_url = null + custom_config_version = 'master' + custom_config_base = "https://raw.githubusercontent.com/nf-core/configs/${params.custom_config_version}" + config_profile_contact = null + config_profile_url = null // Schema validation default options - validate_params = true + validate_params = true } // Load base.config by default for all pipelines @@ -76,90 +89,90 @@ includeConfig 'conf/base.config' profiles { debug { - dumpHashes = true - process.beforeScript = 'echo $HOSTNAME' - cleanup = false + dumpHashes = true + process.beforeScript = 'echo $HOSTNAME' + cleanup = false nextflow.enable.configProcessNamesValidation = true } conda { - conda.enabled = true - docker.enabled = false - singularity.enabled = false - podman.enabled = false - shifter.enabled = false - charliecloud.enabled = false - conda.channels = ['conda-forge', 'bioconda'] - apptainer.enabled = false + conda.enabled = true + docker.enabled = false + singularity.enabled = false + podman.enabled = false + shifter.enabled = false + charliecloud.enabled = false + conda.channels = ['conda-forge', 'bioconda'] + apptainer.enabled = false } mamba { - conda.enabled = true - conda.useMamba = true - docker.enabled = false - singularity.enabled = false - podman.enabled = false - shifter.enabled = false - charliecloud.enabled = false - apptainer.enabled = false + conda.enabled = true + conda.useMamba = true + docker.enabled = false + singularity.enabled = false + podman.enabled = false + shifter.enabled = false + charliecloud.enabled = false + apptainer.enabled = false } docker { - docker.enabled = true - conda.enabled = false - singularity.enabled = false - podman.enabled = false - shifter.enabled = false - charliecloud.enabled = false - apptainer.enabled = false - docker.runOptions = '-u $(id -u):$(id -g)' + docker.enabled = true + conda.enabled = false + singularity.enabled = false + podman.enabled = false + shifter.enabled = false + charliecloud.enabled = false + apptainer.enabled = false + docker.runOptions = '-u $(id -u):$(id -g)' } arm { - docker.runOptions = '-u $(id -u):$(id -g) --platform=linux/amd64' + docker.runOptions = '-u $(id -u):$(id -g) --platform=linux/amd64' } singularity { - singularity.enabled = true - singularity.autoMounts = true - conda.enabled = false - docker.enabled = false - podman.enabled = false - shifter.enabled = false - charliecloud.enabled = false - apptainer.enabled = false + singularity.enabled = true + singularity.autoMounts = true + conda.enabled = false + docker.enabled = false + podman.enabled = false + shifter.enabled = false + charliecloud.enabled = false + apptainer.enabled = false } podman { - podman.enabled = true - conda.enabled = false - docker.enabled = false - singularity.enabled = false - shifter.enabled = false - charliecloud.enabled = false - apptainer.enabled = false + podman.enabled = true + conda.enabled = false + docker.enabled = false + singularity.enabled = false + shifter.enabled = false + charliecloud.enabled = false + apptainer.enabled = false } shifter { - shifter.enabled = true - conda.enabled = false - docker.enabled = false - singularity.enabled = false - podman.enabled = false - charliecloud.enabled = false - apptainer.enabled = false + shifter.enabled = true + conda.enabled = false + docker.enabled = false + singularity.enabled = false + podman.enabled = false + charliecloud.enabled = false + apptainer.enabled = false } charliecloud { - charliecloud.enabled = true - conda.enabled = false - docker.enabled = false - singularity.enabled = false - podman.enabled = false - shifter.enabled = false - apptainer.enabled = false + charliecloud.enabled = true + conda.enabled = false + docker.enabled = false + singularity.enabled = false + podman.enabled = false + shifter.enabled = false + apptainer.enabled = false } apptainer { - apptainer.enabled = true - apptainer.autoMounts = true - conda.enabled = false - docker.enabled = false - singularity.enabled = false - podman.enabled = false - shifter.enabled = false - charliecloud.enabled = false + apptainer.enabled = true + apptainer.autoMounts = true + conda.enabled = false + docker.enabled = false + singularity.enabled = false + podman.enabled = false + shifter.enabled = false + charliecloud.enabled = false } wave { apptainer.ociAutoPull = true @@ -169,24 +182,28 @@ profiles { wave.strategy = 'conda,container' } gitpod { - executor.name = 'local' - executor.cpus = 4 - executor.memory = 8.GB + executor.name = 'local' + executor.cpus = 4 + executor.memory = 8.GB process { resourceLimits = [ memory: 8.GB, - cpus : 4, - time : 1.h + cpus: 4, + time: 1.h, ] } } gpu { - docker.runOptions = '-u $(id -u):$(id -g) --gpus all' - apptainer.runOptions = '--nv' - singularity.runOptions = '--nv' + docker.runOptions = '-u $(id -u):$(id -g) --gpus all' + apptainer.runOptions = '--nv' + singularity.runOptions = '--nv' + } + test { + includeConfig 'conf/test.config' + } + test_full { + includeConfig 'conf/test_full.config' } - test { includeConfig 'conf/test.config' } - test_full { includeConfig 'conf/test_full.config' } } // Load nf-core custom profiles from different institutions @@ -203,10 +220,10 @@ includeConfig params.custom_config_base && (!System.getenv('NXF_OFFLINE') || !pa // Set default registry for Apptainer, Docker, Podman, Charliecloud and Singularity independent of -profile // Will not be used unless Apptainer / Docker / Podman / Charliecloud / Singularity are enabled // Set to your registry if you have a mirror of containers -apptainer.registry = 'quay.io' -docker.registry = 'quay.io' -podman.registry = 'quay.io' -singularity.registry = 'quay.io' +apptainer.registry = 'quay.io' +docker.registry = 'quay.io' +podman.registry = 'quay.io' +singularity.registry = 'quay.io' charliecloud.registry = 'quay.io' // Load igenomes.config if required @@ -226,11 +243,11 @@ env { // Set bash options process.shell = [ "bash", - "-C", // No clobber - prevent output redirection from overwriting files. - "-e", // Exit if a tool returns a non-zero status/exit code - "-u", // Treat unset variables and parameters as an error - "-o", // Returns the status of the last command to exit.. - "pipefail" // ..with a non-zero status or zero if all successfully execute + "-C", + "-e", + "-u", + "-o", + "pipefail", ] // Disable process selector warnings by default. Use debug profile to enable warnings. @@ -255,17 +272,17 @@ dag { manifest { name = 'nf-core/hadge' - author = """Fabiola Curion""" // The author field is deprecated from Nextflow version 24.10.0, use contributors instead + author = """Fabiola Curion""" + // The author field is deprecated from Nextflow version 24.10.0, use contributors instead contributors = [ - // TODO nf-core: Update the field with the details of the contributors to your pipeline. New with Nextflow version 24.10.0 [ name: 'Fabiola Curion', affiliation: '', email: '', github: '', - contribution: [], // List of contribution types ('author', 'maintainer' or 'contributor') - orcid: '' - ], + contribution: [], + orcid: '', + ] ] homePage = 'https://github.com/nf-core/hadge' description = """Comprehensive pipeline for donor demultiplexing in single cell""" @@ -278,18 +295,18 @@ manifest { // Nextflow plugins plugins { - id 'nf-schema@2.3.0' // Validation of pipeline parameters and creation of an input channel from a sample sheet + id 'nf-schema@2.3.0' } validation { defaultIgnoreParams = ["genomes"] - monochromeLogs = params.monochrome_logs + monochromeLogs = params.monochrome_logs help { - enabled = true - command = "nextflow run nf-core/hadge -profile --input samplesheet.csv --outdir " - fullParameter = "help_full" + enabled = true + command = "nextflow run nf-core/hadge -profile --input samplesheet.csv --outdir " + fullParameter = "help_full" showHiddenParameter = "show_hidden" - beforeText = """ + beforeText = """ -\033[2m----------------------------------------------------\033[0m- \033[0;32m,--.\033[0;30m/\033[0;32m,-.\033[0m \033[0;34m ___ __ __ __ ___ \033[0;32m/,-._.--~\'\033[0m @@ -299,7 +316,7 @@ validation { \033[0;35m nf-core/hadge ${manifest.version}\033[0m -\033[2m----------------------------------------------------\033[0m- """ - afterText = """${manifest.doi ? "\n* The pipeline\n" : ""}${manifest.doi.tokenize(",").collect { " https://doi.org/${it.trim().replace('https://doi.org/','')}"}.join("\n")}${manifest.doi ? "\n" : ""} + afterText = """${manifest.doi ? "\n* The pipeline\n" : ""}${manifest.doi.tokenize(",").collect { " https://doi.org/${it.trim().replace('https://doi.org/', '')}" }.join("\n")}${manifest.doi ? "\n" : ""} * The nf-core framework https://doi.org/10.1038/s41587-020-0439-x @@ -309,7 +326,7 @@ validation { } summary { beforeText = validation.help.beforeText - afterText = validation.help.afterText + afterText = validation.help.afterText } } diff --git a/nextflow_schema.json b/nextflow_schema.json index c756d4c5..7e3221b0 100644 --- a/nextflow_schema.json +++ b/nextflow_schema.json @@ -211,6 +211,80 @@ } } }, + "cellsnp_options": { + "title": "CellSNP-lite options", + "type": "object", + "fa_icon": "fas fa-dna", + "description": "Options specific to the CellSNP-lite tool for genotyping bi-allelic SNPs on single cells.", + "properties": { + "cellsnp_celltag": { + "type": "string", + "description": "Tag for cell barcodes, e.g., CB for 10x Genomics. Set to 'None' for bulk RNA-seq or SMART-seq2.", + "default": "CB", + "fa_icon": "fas fa-tag" + }, + "cellsnp_umitag": { + "type": "string", + "description": "Tag for UMI barcodes, e.g., UB for 10x Genomics. Set to 'None' for bulk RNA-seq or SMART-seq2 without UMIs.", + "default": "Auto", + "fa_icon": "fas fa-fingerprint" + }, + "cellsnp_mincount": { + "type": "integer", + "description": "Minimum aggregated count (across cells) for SNPs to be included in the output.", + "default": 20, + "minimum": 0, + "fa_icon": "fas fa-sort-amount-down" + }, + "cellsnp_minmaf": { + "type": "number", + "description": "Minimum minor allele frequency (MAF) for SNPs to be included in the output.", + "default": 0.0, + "minimum": 0.0, + "maximum": 0.5, + "fa_icon": "fas fa-percentage" + }, + "cellsnp_inclflag": { + "type": "string", + "description": "Required flags in SAM/BAM: skip reads that don't have ALL of these flags. See SAM format specification for details.", + "default": "", + "fa_icon": "fas fa-flag" + }, + "cellsnp_exclflag": { + "type": "string", + "description": "Excluding flags in SAM/BAM: skip reads that have ANY of these flags. See SAM format specification for details.", + "default": "", + "fa_icon": "fas fa-ban" + }, + "cellsnp_minlen": { + "type": "integer", + "description": "Minimum read length (after clipping) for a read to be included.", + "default": 30, + "minimum": 0, + "fa_icon": "fas fa-ruler-horizontal" + }, + "cellsnp_minmapq": { + "type": "integer", + "description": "Minimum mapping quality for a read to be included.", + "default": 20, + "minimum": 0, + "fa_icon": "fas fa-chart-line" + }, + "cellsnp_maxdepth": { + "type": "integer", + "description": "Maximum read depth at a position per input file. Set to 0 for highest possible value.", + "default": 0, + "minimum": 0, + "fa_icon": "fas fa-layer-group" + }, + "cellsnp_countorphan": { + "type": "boolean", + "description": "If true, do not skip anomalous read pairs (i.e., count orphan reads).", + "default": false, + "fa_icon": "fas fa-child" + } + } + }, "institutional_config_options": { "title": "Institutional config options", "type": "object", @@ -370,6 +444,9 @@ { "$ref": "#/$defs/gmmdemux_options" }, + { + "$ref": "#/$defs/cellsnp_options" + }, { "$ref": "#/$defs/institutional_config_options" }, From 470d999715bcc3cb98e272bb469d60b65aaf53ab Mon Sep 17 00:00:00 2001 From: nictru Date: Sun, 15 Jun 2025 15:04:02 +0200 Subject: [PATCH 31/74] Add vireo optional params --- conf/modules.config | 20 +++++++++++ nextflow.config | 13 +++++++ nextflow_schema.json | 82 ++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 115 insertions(+) diff --git a/conf/modules.config b/conf/modules.config index ab526f27..8aad0722 100644 --- a/conf/modules.config +++ b/conf/modules.config @@ -89,6 +89,26 @@ process { } withName: VIREO { + ext.args = { + [ + "--genoTag", + params.vireo_genotag, + params.vireo_no_doublet ? "--noDoublet" : "", + "--nInit", + params.vireo_n_init, + "--extraDonor", + params.vireo_extra_donor, + "--extraDonorMode", + params.vireo_extra_donor_mode, + params.vireo_force_learn_gt ? "--forceLearnGT" : "", + params.vireo_ase_mode ? "--ASEmode" : "", + params.vireo_no_plot ? "--noPlot" : "", + "--randSeed", + params.vireo_rand_seed, + params.vireo_cell_range != "all" ? "--cellRange ${params.vireo_cell_range}" : "", + params.vireo_cell_ambient_rnas ? "--callAmbientRNAs" : "", + ].join(" ") + } publishDir = [ path: { "${params.outdir}/genetic/vireo/${meta.id}" }, mode: params.publish_dir_mode, diff --git a/nextflow.config b/nextflow.config index 3525a65b..ebdd5b24 100644 --- a/nextflow.config +++ b/nextflow.config @@ -50,6 +50,19 @@ params { cellsnp_maxdepth = 0 cellsnp_countorphan = false + // Vireo + vireo_genotag = 'GT' + vireo_no_doublet = false + vireo_n_init = 50 + vireo_extra_donor = 0 + vireo_extra_donor_mode = 'distance' + vireo_force_learn_gt = false + vireo_ase_mode = false + vireo_no_plot = false + vireo_rand_seed = 0 + vireo_cell_range = 'all' + vireo_cell_ambient_rnas = false + // MultiQC options multiqc_config = null multiqc_title = null diff --git a/nextflow_schema.json b/nextflow_schema.json index 7e3221b0..29cf58d6 100644 --- a/nextflow_schema.json +++ b/nextflow_schema.json @@ -285,6 +285,85 @@ } } }, + "vireo_options": { + "title": "Vireo options", + "type": "object", + "fa_icon": "fas fa-users", + "description": "Options specific to the Vireo tool for donor demultiplexing from single-cell RNA-seq data.", + "properties": { + "vireo_genotag": { + "type": "string", + "description": "The tag for donor genotype in VCF file. Options: GT, GP, PL.", + "default": "GT", + "enum": ["GT", "GP", "PL"], + "fa_icon": "fas fa-tag" + }, + "vireo_no_doublet": { + "type": "boolean", + "description": "If true, do not check for doublets during demultiplexing.", + "default": false, + "fa_icon": "fas fa-object-ungroup" + }, + "vireo_n_init": { + "type": "integer", + "description": "Number of random initializations when GT needs to be learned.", + "default": 50, + "minimum": 1, + "fa_icon": "fas fa-redo" + }, + "vireo_extra_donor": { + "type": "integer", + "description": "Number of extra donors in pre-cluster, when GT needs to be learned.", + "default": 0, + "minimum": 0, + "fa_icon": "fas fa-user-plus" + }, + "vireo_extra_donor_mode": { + "type": "string", + "description": "Method for searching from extra donors. 'size': n_cell per donor; 'distance': GT distance between donors.", + "default": "distance", + "enum": ["size", "distance"], + "fa_icon": "fas fa-search" + }, + "vireo_force_learn_gt": { + "type": "boolean", + "description": "If true, treat donor GT as prior only and learn genotypes from data.", + "default": false, + "fa_icon": "fas fa-graduation-cap" + }, + "vireo_ase_mode": { + "type": "boolean", + "description": "If true, turn on SNP specific allelic ratio (ASE mode).", + "default": false, + "fa_icon": "fas fa-adjust" + }, + "vireo_no_plot": { + "type": "boolean", + "description": "If true, turn off plotting GT distance.", + "default": false, + "fa_icon": "fas fa-chart-bar" + }, + "vireo_rand_seed": { + "type": "integer", + "description": "Random seed for initialization.", + "default": 0, + "minimum": 0, + "fa_icon": "fas fa-random" + }, + "vireo_cell_range": { + "type": "string", + "description": "Range of cells to process, e.g., '0-10000'. Default is 'all'.", + "default": "all", + "fa_icon": "fas fa-filter" + }, + "vireo_cell_ambient_rnas": { + "type": "boolean", + "description": "If true, detect ambient RNAs in each cell (experimental feature).", + "default": false, + "fa_icon": "fas fa-flask" + } + } + }, "institutional_config_options": { "title": "Institutional config options", "type": "object", @@ -447,6 +526,9 @@ { "$ref": "#/$defs/cellsnp_options" }, + { + "$ref": "#/$defs/vireo_options" + }, { "$ref": "#/$defs/institutional_config_options" }, From 272a0200a3f809e8745c620b3f019680f727b000 Mon Sep 17 00:00:00 2001 From: nictru Date: Sun, 15 Jun 2025 15:42:19 +0200 Subject: [PATCH 32/74] Add popscle-dscpileup optional parameters --- conf/modules.config | 24 ++++++++++++++ nextflow.config | 12 +++++++ nextflow_schema.json | 79 ++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 115 insertions(+) diff --git a/conf/modules.config b/conf/modules.config index 8aad0722..fa064453 100644 --- a/conf/modules.config +++ b/conf/modules.config @@ -117,6 +117,30 @@ process { } withName: POPSCLE_DSCPILEUP { + ext.args = { + [ + "--tag-group", + params.dsc_pileup_tag_group, + "--tag-UMI", + params.dsc_pileup_tag_umi, + "--cap-BQ", + params.dsc_pileup_cap_bq, + "--min-BQ", + params.dsc_pileup_min_bq, + "--min-MQ", + params.dsc_pileup_min_mq, + "--min-TD", + params.dsc_pileup_min_td, + "--excl-flag", + params.dsc_pileup_excl_flag, + "--min-total", + params.dsc_pileup_min_total, + "--min-uniq", + params.dsc_pileup_min_uniq, + "--min-snp", + params.dsc_pileup_min_snp, + ].join(" ") + } publishDir = [ path: { "${params.outdir}/genetic/popscle/dscpileup/${meta.id}" }, mode: params.publish_dir_mode, diff --git a/nextflow.config b/nextflow.config index ebdd5b24..2bc3c536 100644 --- a/nextflow.config +++ b/nextflow.config @@ -63,6 +63,18 @@ params { vireo_cell_range = 'all' vireo_cell_ambient_rnas = false + // DSC-Pileup + dsc_pileup_tag_group = 'CB' + dsc_pileup_tag_umi = 'UB' + dsc_pileup_cap_bq = 40 + dsc_pileup_min_bq = 13 + dsc_pileup_min_mq = 20 + dsc_pileup_min_td = 0 + dsc_pileup_excl_flag = 3844 + dsc_pileup_min_total = 0 + dsc_pileup_min_uniq = 0 + dsc_pileup_min_snp = 0 + // MultiQC options multiqc_config = null multiqc_title = null diff --git a/nextflow_schema.json b/nextflow_schema.json index 29cf58d6..6d8b9b51 100644 --- a/nextflow_schema.json +++ b/nextflow_schema.json @@ -364,6 +364,82 @@ } } }, + "dsc_pileup_options": { + "title": "DSC-Pileup options", + "type": "object", + "fa_icon": "fas fa-layer-group", + "description": "Options specific to the DSC-Pileup tool for pileup generation from single-cell BAM files.", + "properties": { + "dsc_pileup_tag_group": { + "type": "string", + "description": "Tag representing readgroup or cell barcodes to partition the BAM file into multiple groups. For 10x Genomics, use CB.", + "default": "CB", + "fa_icon": "fas fa-tag" + }, + "dsc_pileup_tag_umi": { + "type": "string", + "description": "Tag representing UMIs. For 10x Genomics, use UB.", + "default": "UB", + "fa_icon": "fas fa-fingerprint" + }, + "dsc_pileup_cap_bq": { + "type": "integer", + "description": "Maximum base quality (higher BQ will be capped).", + "default": 40, + "minimum": 0, + "maximum": 60, + "fa_icon": "fas fa-arrow-up" + }, + "dsc_pileup_min_bq": { + "type": "integer", + "description": "Minimum base quality to consider (lower BQ will be skipped).", + "default": 13, + "minimum": 0, + "fa_icon": "fas fa-arrow-down" + }, + "dsc_pileup_min_mq": { + "type": "integer", + "description": "Minimum mapping quality to consider (lower MQ will be ignored).", + "default": 20, + "minimum": 0, + "fa_icon": "fas fa-chart-line" + }, + "dsc_pileup_min_td": { + "type": "integer", + "description": "Minimum distance to the tail (lower will be ignored).", + "default": 0, + "minimum": 0, + "fa_icon": "fas fa-ruler-horizontal" + }, + "dsc_pileup_excl_flag": { + "type": "integer", + "description": "SAM/BAM FLAGs to be excluded.", + "default": 3844, + "fa_icon": "fas fa-flag" + }, + "dsc_pileup_min_total": { + "type": "integer", + "description": "Minimum number of total reads for a droplet/cell to be considered.", + "default": 0, + "minimum": 0, + "fa_icon": "fas fa-sort-amount-down" + }, + "dsc_pileup_min_uniq": { + "type": "integer", + "description": "Minimum number of unique reads (determined by UMI/SNP pair) for a droplet/cell to be considered.", + "default": 0, + "minimum": 0, + "fa_icon": "fas fa-dna" + }, + "dsc_pileup_min_snp": { + "type": "integer", + "description": "Minimum number of SNPs with coverage for a droplet/cell to be considered.", + "default": 0, + "minimum": 0, + "fa_icon": "fas fa-project-diagram" + } + } + }, "institutional_config_options": { "title": "Institutional config options", "type": "object", @@ -529,6 +605,9 @@ { "$ref": "#/$defs/vireo_options" }, + { + "$ref": "#/$defs/dsc_pileup_options" + }, { "$ref": "#/$defs/institutional_config_options" }, From b643c221535124c18360c640a034d6d04273eca3 Mon Sep 17 00:00:00 2001 From: nictru Date: Sun, 15 Jun 2025 16:21:54 +0200 Subject: [PATCH 33/74] Add demuxlet/freemuxlet optional params --- conf/modules.config | 39 +++++++++- nextflow.config | 172 ++++++++++++++++++++++++------------------- nextflow_schema.json | 136 ++++++++++++++++++++++++++++++++++ 3 files changed, 270 insertions(+), 77 deletions(-) diff --git a/conf/modules.config b/conf/modules.config index fa064453..fa356b5e 100644 --- a/conf/modules.config +++ b/conf/modules.config @@ -150,7 +150,26 @@ process { } withName: POPSCLE_DEMUXLET { - ext.args = '--field GT' + ext.args = { + [ + "--field", + params.demuxlet_field, + "--geno-error-offset", + params.demuxlet_geno_error_offset, + "--geno-error-coeff", + params.demuxlet_geno_error_coeff, + "--r2-info", + params.demuxlet_r2_info, + "--min-mac", + params.demuxlet_min_mac, + "--min-callrate", + params.demuxlet_min_callrate, + "--alpha", + params.demuxlet_alpha, + "--doublet-prior", + params.demuxlet_doublet_prior, + ].join(" ") + } publishDir = [ path: { "${params.outdir}/genetic/popscle/demuxlet/${meta.id}" }, mode: params.publish_dir_mode, @@ -159,6 +178,24 @@ process { } withName: POPSCLE_FREEMUXLET { + ext.args = { + [ + "--doublet-prior", + params.freemuxlet_doublet_prior, + "--geno-error", + params.freemuxlet_geno_error, + "--bf-thres", + params.freemuxlet_bf_thres, + "--frac-init-clust", + params.freemuxlet_frac_init_clust, + "--iter-init", + params.freemuxlet_iter_init, + params.freemuxlet_keep_init_missing ? "--keep-init-missing" : "", + params.freemuxlet_randomize_singlet_score ? "--randomize-singlet-score" : "", + "--seed", + params.freemuxlet_seed, + ].join(" ") + } publishDir = [ path: { "${params.outdir}/genetic/popscle/freemuxlet/${meta.id}" }, mode: params.publish_dir_mode, diff --git a/nextflow.config b/nextflow.config index 2bc3c536..42cce4b1 100644 --- a/nextflow.config +++ b/nextflow.config @@ -11,102 +11,122 @@ params { // TODO nf-core: Specify your pipeline's command line flags // Input options - input = null - mode = 'rescue' - match_donor = false - hash_tools = 'gmm-demux' - genetic_tools = 'vireo' - bam_qc = true - common_variants = null - save_intermediates = false + input = null + mode = 'rescue' + match_donor = false + hash_tools = 'gmm-demux' + genetic_tools = 'vireo' + bam_qc = true + common_variants = null + save_intermediates = false // References - genome = null - igenomes_base = 's3://ngi-igenomes/igenomes/' - igenomes_ignore = false + genome = null + igenomes_base = 's3://ngi-igenomes/igenomes/' + igenomes_ignore = false // demuxEM - demuxem_alpha_on_samples = 0.0 - demuxem_min_num_genes = 100 - demuxem_min_num_umis = 100 - demuxem_min_signal_hashtag = 10 - demuxem_random_state = 0 - demuxem_gender_genes = "" + demuxem_alpha_on_samples = 0.0 + demuxem_min_num_genes = 100 + demuxem_min_num_umis = 100 + demuxem_min_signal_hashtag = 10 + demuxem_random_state = 0 + demuxem_gender_genes = "" // GMM-Demux - gmmdemux_extract = null - gmmdemux_threshold = 0.8 - gmmdemux_random_state = 0 + gmmdemux_extract = null + gmmdemux_threshold = 0.8 + gmmdemux_random_state = 0 // CellSNP - cellsnp_celltag = 'CB' - cellsnp_umitag = 'Auto' - cellsnp_mincount = 20 - cellsnp_minmaf = 0.0 - cellsnp_inclflag = "" - cellsnp_exclflag = "" - cellsnp_minlen = 30 - cellsnp_minmapq = 20 - cellsnp_maxdepth = 0 - cellsnp_countorphan = false + cellsnp_celltag = 'CB' + cellsnp_umitag = 'Auto' + cellsnp_mincount = 20 + cellsnp_minmaf = 0.0 + cellsnp_inclflag = "" + cellsnp_exclflag = "" + cellsnp_minlen = 30 + cellsnp_minmapq = 20 + cellsnp_maxdepth = 0 + cellsnp_countorphan = false // Vireo - vireo_genotag = 'GT' - vireo_no_doublet = false - vireo_n_init = 50 - vireo_extra_donor = 0 - vireo_extra_donor_mode = 'distance' - vireo_force_learn_gt = false - vireo_ase_mode = false - vireo_no_plot = false - vireo_rand_seed = 0 - vireo_cell_range = 'all' - vireo_cell_ambient_rnas = false + vireo_genotag = 'GT' + vireo_no_doublet = false + vireo_n_init = 50 + vireo_extra_donor = 0 + vireo_extra_donor_mode = 'distance' + vireo_force_learn_gt = false + vireo_ase_mode = false + vireo_no_plot = false + vireo_rand_seed = 0 + vireo_cell_range = 'all' + vireo_cell_ambient_rnas = false // DSC-Pileup - dsc_pileup_tag_group = 'CB' - dsc_pileup_tag_umi = 'UB' - dsc_pileup_cap_bq = 40 - dsc_pileup_min_bq = 13 - dsc_pileup_min_mq = 20 - dsc_pileup_min_td = 0 - dsc_pileup_excl_flag = 3844 - dsc_pileup_min_total = 0 - dsc_pileup_min_uniq = 0 - dsc_pileup_min_snp = 0 + dsc_pileup_tag_group = 'CB' + dsc_pileup_tag_umi = 'UB' + dsc_pileup_cap_bq = 40 + dsc_pileup_min_bq = 13 + dsc_pileup_min_mq = 20 + dsc_pileup_min_td = 0 + dsc_pileup_excl_flag = 3844 + dsc_pileup_min_total = 0 + dsc_pileup_min_uniq = 0 + dsc_pileup_min_snp = 0 + + // Demuxlet + demuxlet_field = 'GT' + demuxlet_geno_error_offset = 0.1 + demuxlet_geno_error_coeff = 0.0 + demuxlet_r2_info = 'R2' + demuxlet_min_mac = 1 + demuxlet_min_callrate = 0.50 + demuxlet_alpha = '0.1,0.2,0.3,0.4,0.5' + demuxlet_doublet_prior = 0.5 + + // Freemuxlet + freemuxlet_doublet_prior = 0.5 + freemuxlet_geno_error = 0.1 + freemuxlet_bf_thres = 5.41 + freemuxlet_frac_init_clust = 1.0 + freemuxlet_iter_init = 10 + freemuxlet_keep_init_missing = false + freemuxlet_randomize_singlet_score = false + freemuxlet_seed = 0 // MultiQC options - multiqc_config = null - multiqc_title = null - multiqc_logo = null - max_multiqc_email_size = '25.MB' - multiqc_methods_description = null + multiqc_config = null + multiqc_title = null + multiqc_logo = null + max_multiqc_email_size = '25.MB' + multiqc_methods_description = null // Boilerplate options - outdir = null - publish_dir_mode = 'copy' - email = null - email_on_fail = null - plaintext_email = false - monochrome_logs = false - hook_url = null - help = false - help_full = false - show_hidden = false - version = false - pipelines_testdata_base_path = 'https://raw.githubusercontent.com/nf-core/test-datasets/' - trace_report_suffix = new java.util.Date().format('yyyy-MM-dd_HH-mm-ss') + outdir = null + publish_dir_mode = 'copy' + email = null + email_on_fail = null + plaintext_email = false + monochrome_logs = false + hook_url = null + help = false + help_full = false + show_hidden = false + version = false + pipelines_testdata_base_path = 'https://raw.githubusercontent.com/nf-core/test-datasets/' + trace_report_suffix = new java.util.Date().format('yyyy-MM-dd_HH-mm-ss') // Config options - config_profile_name = null - config_profile_description = null + config_profile_name = null + config_profile_description = null - custom_config_version = 'master' - custom_config_base = "https://raw.githubusercontent.com/nf-core/configs/${params.custom_config_version}" - config_profile_contact = null - config_profile_url = null + custom_config_version = 'master' + custom_config_base = "https://raw.githubusercontent.com/nf-core/configs/${params.custom_config_version}" + config_profile_contact = null + config_profile_url = null // Schema validation default options - validate_params = true + validate_params = true } // Load base.config by default for all pipelines diff --git a/nextflow_schema.json b/nextflow_schema.json index 6d8b9b51..92eded7c 100644 --- a/nextflow_schema.json +++ b/nextflow_schema.json @@ -440,6 +440,136 @@ } } }, + "demuxlet_options": { + "title": "Demuxlet options", + "type": "object", + "fa_icon": "fas fa-users-cog", + "description": "Options specific to the Demuxlet tool for genotype-based demultiplexing of single-cell RNA-seq data.", + "properties": { + "demuxlet_field": { + "type": "string", + "description": "FORMAT field to extract the genotype, likelihood, or posterior from.", + "default": "GT", + "fa_icon": "fas fa-tag" + }, + "demuxlet_geno_error_offset": { + "type": "number", + "description": "Offset of genotype error rate. [error] = [offset] + [1-offset]*[coeff]*[1-r2]", + "default": 0.1, + "minimum": 0, + "maximum": 1, + "fa_icon": "fas fa-percentage" + }, + "demuxlet_geno_error_coeff": { + "type": "number", + "description": "Slope of genotype error rate. [error] = [offset] + [1-offset]*[coeff]*[1-r2]", + "default": 0.0, + "minimum": 0, + "maximum": 1, + "fa_icon": "fas fa-chart-line" + }, + "demuxlet_r2_info": { + "type": "string", + "description": "INFO field name representing R2 value. Used for representing imputation quality.", + "default": "R2", + "fa_icon": "fas fa-info-circle" + }, + "demuxlet_min_mac": { + "type": "integer", + "description": "Minimum minor allele frequency.", + "default": 1, + "minimum": 0, + "fa_icon": "fas fa-sort-numeric-down" + }, + "demuxlet_min_callrate": { + "type": "number", + "description": "Minimum call rate.", + "default": 0.5, + "minimum": 0, + "maximum": 1, + "fa_icon": "fas fa-phone" + }, + "demuxlet_alpha": { + "type": "string", + "description": "Grid of alpha to search for.", + "default": "0.1,0.2,0.3,0.4,0.5", + "fa_icon": "fas fa-sliders-h" + }, + "demuxlet_doublet_prior": { + "type": "number", + "description": "Prior probability of doublet.", + "default": 0.5, + "minimum": 0, + "maximum": 1, + "fa_icon": "fas fa-object-group" + } + } + }, + "freemuxlet_options": { + "title": "Freemuxlet options", + "type": "object", + "fa_icon": "fas fa-users", + "description": "Options specific to the Freemuxlet tool for reference-free genotype-based demultiplexing.", + "properties": { + "freemuxlet_doublet_prior": { + "type": "number", + "description": "Prior probability of doublet.", + "default": 0.5, + "minimum": 0, + "maximum": 1, + "fa_icon": "fas fa-object-group" + }, + "freemuxlet_geno_error": { + "type": "number", + "description": "Genotype error parameter per cluster.", + "default": 0.1, + "minimum": 0, + "maximum": 1, + "fa_icon": "fas fa-exclamation-triangle" + }, + "freemuxlet_bf_thres": { + "type": "number", + "description": "Bayes Factor Threshold used in the initial clustering.", + "default": 5.41, + "minimum": 0, + "fa_icon": "fas fa-balance-scale" + }, + "freemuxlet_frac_init_clust": { + "type": "number", + "description": "Fraction of droplets to be clustered in the very first round of initial clustering procedure.", + "default": 1.0, + "minimum": 0, + "maximum": 1, + "fa_icon": "fas fa-object-ungroup" + }, + "freemuxlet_iter_init": { + "type": "integer", + "description": "Iteration for initial cluster assignment (set to zero to skip the iterations).", + "default": 10, + "minimum": 0, + "fa_icon": "fas fa-redo" + }, + "freemuxlet_keep_init_missing": { + "type": "boolean", + "description": "Keep missing cluster assignment as missing in the initial iteration.", + "default": false, + "fa_icon": "fas fa-question-circle" + }, + "freemuxlet_randomize_singlet_score": { + "type": "boolean", + "description": "Randomize the singlet scores to test its effect.", + "default": false, + "fa_icon": "fas fa-random" + }, + "freemuxlet_seed": { + "type": "integer", + "description": "Seed for random number (use clocks if not set).", + "default": 0, + "minimum": 0, + "fa_icon": "fas fa-dice" + } + } + }, "institutional_config_options": { "title": "Institutional config options", "type": "object", @@ -608,6 +738,12 @@ { "$ref": "#/$defs/dsc_pileup_options" }, + { + "$ref": "#/$defs/demuxlet_options" + }, + { + "$ref": "#/$defs/freemuxlet_options" + }, { "$ref": "#/$defs/institutional_config_options" }, From 44d663628a6df7c18597c510ca4f9fd4853580fe Mon Sep 17 00:00:00 2001 From: nictru Date: Sun, 15 Jun 2025 16:27:24 +0200 Subject: [PATCH 34/74] Use github CI runners --- .github/workflows/nf-test.yml | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) diff --git a/.github/workflows/nf-test.yml b/.github/workflows/nf-test.yml index f03aea0c..edc5b36c 100644 --- a/.github/workflows/nf-test.yml +++ b/.github/workflows/nf-test.yml @@ -34,9 +34,7 @@ env: jobs: nf-test-changes: name: nf-test-changes - runs-on: # use self-hosted runners - - runs-on=$-nf-test-changes - - runner=4cpu-linux-x64 + runs-on: ubuntu-latest outputs: shard: ${{ steps.set-shards.outputs.shard }} total_shards: ${{ steps.set-shards.outputs.total_shards }} @@ -68,9 +66,7 @@ jobs: name: "${{ matrix.profile }} | ${{ matrix.NXF_VER }} | ${{ matrix.shard }}/${{ needs.nf-test-changes.outputs.total_shards }}" needs: [nf-test-changes] if: ${{ needs.nf-test-changes.outputs.total_shards != '0' }} - runs-on: # use self-hosted runners - - runs-on=$-nf-test - - runner=4cpu-linux-x64 + runs-on: ubuntu-latest strategy: fail-fast: false matrix: @@ -109,9 +105,7 @@ jobs: confirm-pass: needs: [nf-test] if: always() - runs-on: # use self-hosted runners - - runs-on=$-confirm-pass - - runner=2cpu-linux-x64 + runs-on: ubuntu-latest steps: - name: One or more tests failed if: ${{ contains(needs.*.result, 'failure') }} From eb57d6b1c3fe284b2b9b4ce07fceab8353cfd034 Mon Sep 17 00:00:00 2001 From: Nico Trummer Date: Sat, 5 Jul 2025 11:18:15 +0200 Subject: [PATCH 35/74] Add new hash demultiplexing modules (#66) * hasheddrops inserted and they run in the pipeline * insert local preprocessing module for htodemux and multiseq (not tested yet) * adding htodemux and multiseq to the pipeline * small improvements * add parameters of modules also to the pipeline * address * run pre-commit for modules/local/preprocessing_for_htodemux_multiseq * add htodemux visualization (not tested yet) * visualization added to the pipeline (works locally) * add visualization params to config files * change strings to type boolean for htodemux visualization * improve visualization * change data types for preprocessing * change data types of nf-core modules if possible * Prettier * Pre-commit * Fix nf-core modules not matching remote * Fix PULL_REQUEST_TEMPLATE issue based on this thread: https://nfcore.slack.com/archives/CQY2U5QU9/p1747475193145499 * Solve remaining prettier issues * One more prettier fix :D * Revert "Fix nf-core modules not matching remote" This reverts commit ac3ed2439607dcb479e745045fcbe4cee34e40ed. * Make test profile work * Patch modules * Prettier * Gitignore test intermediates * Add pipeline test snapshot * Make sure hash diffs are visible * Ignore some more test snaps * Enable new hash-based tools * Set prefix to prevent file name collisions * nft-ignore some outputs --------- Co-authored-by: LuisHeinzlmeier --- .github/workflows/nf-test.yml | 2 - .gitignore | 1 + .nf-core.yml | 4 +- assets/schema_input.json | 10 +- conf/modules.config | 105 ++++ conf/test.config | 7 +- modules.json | 32 +- .../htodemux_visualization/environment.yml | 6 + modules/local/htodemux_visualization/main.nf | 75 +++ .../templates/htodemux_visualization.R | 192 ++++++++ .../environment.yml | 5 + .../main.nf | 49 ++ .../templates/pre_processing.R | 123 +++++ .../rename_genes_to_features/environment.yml | 7 + .../local/rename_genes_to_features/main.nf | 25 + .../nf-core/cellsnp/modea/cellsnp-modea.diff | 82 ++++ modules/nf-core/gmmdemux/gmmdemux.diff | 118 +++++ modules/nf-core/hasheddrops/environment.yml | 8 + modules/nf-core/hasheddrops/hasheddrops.diff | 28 ++ modules/nf-core/hasheddrops/main.nf | 75 +++ modules/nf-core/hasheddrops/meta.yml | 134 +++++ .../hasheddrops/templates/HashedDrops.R | 248 ++++++++++ .../nf-core/hasheddrops/tests/main.nf.test | 120 +++++ .../hasheddrops/tests/main.nf.test.snap | 319 ++++++++++++ modules/nf-core/htodemux/environment.yml | 8 + modules/nf-core/htodemux/main.nf | 48 ++ modules/nf-core/htodemux/meta.yml | 87 ++++ modules/nf-core/htodemux/templates/HTODemux.R | 96 ++++ modules/nf-core/htodemux/tests/main.nf.test | 58 +++ .../nf-core/htodemux/tests/main.nf.test.snap | 221 +++++++++ modules/nf-core/multiseqdemux/environment.yml | 8 + modules/nf-core/multiseqdemux/main.nf | 47 ++ modules/nf-core/multiseqdemux/meta.yml | 77 +++ .../nf-core/multiseqdemux/multiseqdemux.diff | 19 + .../multiseqdemux/templates/MultiSeqDemux.R | 86 ++++ .../nf-core/multiseqdemux/tests/main.nf.test | 59 +++ .../multiseqdemux/tests/main.nf.test.snap | 132 +++++ .../popscle/demuxlet/popscle-demuxlet.diff | 39 ++ .../popscle/dscpileup/popscle-dscpileup.diff | 97 ++++ modules/nf-core/untar/environment.yml | 12 + modules/nf-core/untar/main.nf | 84 ++++ modules/nf-core/untar/meta.yml | 52 ++ modules/nf-core/untar/tests/main.nf.test | 85 ++++ modules/nf-core/untar/tests/main.nf.test.snap | 158 ++++++ nextflow.config | 251 ++++++---- nextflow_schema.json | 432 ++++++++++++++++ .../local/hash_demultiplexing/main.nf | 85 +++- tests/.nftignore | 6 + tests/default.nf.test.snap | 460 ++++++++++++++++++ 49 files changed, 4369 insertions(+), 113 deletions(-) create mode 100644 modules/local/htodemux_visualization/environment.yml create mode 100644 modules/local/htodemux_visualization/main.nf create mode 100644 modules/local/htodemux_visualization/templates/htodemux_visualization.R create mode 100644 modules/local/preprocessing_for_htodemux_multiseq/environment.yml create mode 100644 modules/local/preprocessing_for_htodemux_multiseq/main.nf create mode 100644 modules/local/preprocessing_for_htodemux_multiseq/templates/pre_processing.R create mode 100644 modules/local/rename_genes_to_features/environment.yml create mode 100644 modules/local/rename_genes_to_features/main.nf create mode 100644 modules/nf-core/cellsnp/modea/cellsnp-modea.diff create mode 100644 modules/nf-core/gmmdemux/gmmdemux.diff create mode 100644 modules/nf-core/hasheddrops/environment.yml create mode 100644 modules/nf-core/hasheddrops/hasheddrops.diff create mode 100644 modules/nf-core/hasheddrops/main.nf create mode 100644 modules/nf-core/hasheddrops/meta.yml create mode 100644 modules/nf-core/hasheddrops/templates/HashedDrops.R create mode 100644 modules/nf-core/hasheddrops/tests/main.nf.test create mode 100644 modules/nf-core/hasheddrops/tests/main.nf.test.snap create mode 100644 modules/nf-core/htodemux/environment.yml create mode 100644 modules/nf-core/htodemux/main.nf create mode 100644 modules/nf-core/htodemux/meta.yml create mode 100755 modules/nf-core/htodemux/templates/HTODemux.R create mode 100644 modules/nf-core/htodemux/tests/main.nf.test create mode 100644 modules/nf-core/htodemux/tests/main.nf.test.snap create mode 100644 modules/nf-core/multiseqdemux/environment.yml create mode 100644 modules/nf-core/multiseqdemux/main.nf create mode 100644 modules/nf-core/multiseqdemux/meta.yml create mode 100644 modules/nf-core/multiseqdemux/multiseqdemux.diff create mode 100755 modules/nf-core/multiseqdemux/templates/MultiSeqDemux.R create mode 100644 modules/nf-core/multiseqdemux/tests/main.nf.test create mode 100644 modules/nf-core/multiseqdemux/tests/main.nf.test.snap create mode 100644 modules/nf-core/popscle/demuxlet/popscle-demuxlet.diff create mode 100644 modules/nf-core/popscle/dscpileup/popscle-dscpileup.diff create mode 100644 modules/nf-core/untar/environment.yml create mode 100644 modules/nf-core/untar/main.nf create mode 100644 modules/nf-core/untar/meta.yml create mode 100644 modules/nf-core/untar/tests/main.nf.test create mode 100644 modules/nf-core/untar/tests/main.nf.test.snap create mode 100644 tests/default.nf.test.snap diff --git a/.github/workflows/nf-test.yml b/.github/workflows/nf-test.yml index edc5b36c..f166bac5 100644 --- a/.github/workflows/nf-test.yml +++ b/.github/workflows/nf-test.yml @@ -95,8 +95,6 @@ jobs: - name: Run nf-test uses: ./.github/actions/nf-test env: - NFT_DIFF: ${{ env.NFT_DIFF }} - NFT_DIFF_ARGS: ${{ env.NFT_DIFF_ARGS }} NFT_WORKDIR: ${{ env.NFT_WORKDIR }} with: profile: ${{ matrix.profile }} diff --git a/.gitignore b/.gitignore index a42ce016..23b0c7de 100644 --- a/.gitignore +++ b/.gitignore @@ -7,3 +7,4 @@ testing/ testing* *.pyc null/ +.nf-test* diff --git a/.nf-core.yml b/.nf-core.yml index 33ae6887..79035b01 100644 --- a/.nf-core.yml +++ b/.nf-core.yml @@ -1,4 +1,6 @@ -lint: {} +lint: + files_unchanged: + - .github/PULL_REQUEST_TEMPLATE.md nf_core_version: 3.3.1 repository_type: pipeline template: diff --git a/assets/schema_input.json b/assets/schema_input.json index 921ef32f..39eb7d72 100644 --- a/assets/schema_input.json +++ b/assets/schema_input.json @@ -15,16 +15,12 @@ }, "rna_matrix": { "type": "string", - "format": "directory-path", - "exists": true, "default": null, "pattern": "^\\S+$", "errorMessage": "RNA matrix must be provided and cannot contain spaces" }, "hto_matrix": { "type": "string", - "format": "directory-path", - "exists": true, "default": null, "pattern": "^\\S+$", "errorMessage": "HTO matrix must be provided and cannot contain spaces" @@ -64,6 +60,12 @@ "minimum": 1, "errorMessage": "Number of cells must be an integer greater than 0", "meta": ["n_cells"] + }, + "hto_names": { + "type": "string", + "pattern": "^\\S+$", + "errorMessage": "HTO names must be provided and cannot contain spaces", + "meta": ["hto_names"] } }, "required": ["sample"] diff --git a/conf/modules.config b/conf/modules.config index fa356b5e..89bd8f2e 100644 --- a/conf/modules.config +++ b/conf/modules.config @@ -260,6 +260,111 @@ process { ] } + withName: RENAME_GENES_TO_FEATURES_RNA { + ext.prefix = { "${meta.id}_rna" } + } + + withName: RENAME_GENES_TO_FEATURES_HTO { + ext.prefix = { "${meta.id}_hto" } + } + + withName: HTODEMUX { + ext.quantile = params.htodemux_quantile + ext.init = params.htodemux_init + ext.nstarts = params.htodemux_nstarts + ext.kfunc = params.htodemux_kfunc + ext.nsamples = params.htodemux_nsamples + ext.seed = params.htodemux_seed + ext.verbose = params.htodemux_verbose + publishDir = [ + path: { "${params.outdir}/hashing/htodemux/${meta.id}" }, + mode: params.publish_dir_mode, + saveAs: { filename -> filename.equals('versions.yml') ? null : filename }, + ] + } + + withName: HTODEMUX_VISUALIZATION { + ext.ridgePlot = params.htodemux_visualization_ridgePlot + ext.ridgeNCol = params.htodemux_visualization_ridgeNCol + ext.featureScatter = params.htodemux_visualization_featureScatter + ext.scatterFeat1 = params.htodemux_visualization_scatterFeat1 + ext.scatterFeat2 = params.htodemux_visualization_scatterFeat2 + ext.vlnPlot = params.htodemux_visualization_vlnPlot + ext.vlnFeatures = params.htodemux_visualization_vlnFeatures + ext.vlnLog = params.htodemux_visualization_vlnLog + ext.tSNE = params.htodemux_visualization_tSNE + ext.tSNEIdents = params.htodemux_visualization_tSNEIdents + ext.tSNEInvert = params.htodemux_visualization_tSNEInvert + ext.tSNEVerbose = params.htodemux_visualization_tSNEVerbose + ext.tSNEApprox = params.htodemux_visualization_tSNEApprox + ext.tSNEDimMax = params.htodemux_visualization_tSNEDimMax + ext.tSNEPerplexity = params.htodemux_visualization_tSNEPerplexity + ext.heatMap = params.htodemux_visualization_heatMap + ext.heatMapNcells = params.htodemux_visualization_heatMapNcells + publishDir = [ + path: { "${params.outdir}/hashing/htodemux_visualization/${meta.id}" }, + mode: params.publish_dir_mode, + saveAs: { filename -> filename.equals('versions.yml') ? null : filename }, + ] + } + + withName: MULTISEQDEMUX { + ext.quantile = params.multiseqdemux_quantile + ext.autoThresh = params.multiseqdemux_autoThresh + ext.maxiter = params.multiseqdemux_maxiter + ext.qrangeFrom = params.multiseqdemux_qrangeFrom + ext.qrangeTo = params.multiseqdemux_qrangeTo + ext.qrangeBy = params.multiseqdemux_qrangeBy + ext.verbose = params.multiseqdemux_verbose + publishDir = [ + path: { "${params.outdir}/hashing/multiseqdemux/${meta.id}" }, + mode: params.publish_dir_mode, + saveAs: { filename -> filename.equals('versions.yml') ? null : filename }, + ] + } + + withName: HASHEDDROPS { + ext.lower = params.hasheddrops_lower + ext.niters = params.hasheddrops_niters + ext.testAmbient = params.hasheddrops_testAmbient + ext.round = params.hasheddrops_round + ext.byRank = params.hasheddrops_byRank + ext.isCellFDR = params.hasheddrops_isCellFDR + ext.gene_col = params.hasheddrops_gene_col + ext.ignore = params.hasheddrops_ignore + ext.alpha = params.hasheddrops_alpha + ext.ambient = params.hasheddrops_ambient + ext.minProp = params.hasheddrops_minProp + ext.pseudoCount = params.hasheddrops_pseudoCount + ext.constantAmbient = params.hasheddrops_constantAmbient + ext.doubletNmads = params.hasheddrops_doubletNmads + ext.doubletMin = params.hasheddrops_doubletMin + ext.doubletMixture = params.hasheddrops_doubletMixture + ext.confidentNmads = params.hasheddrops_confidentNmads + ext.confidentMin = params.hasheddrops_confidentMin + ext.combinations = params.hasheddrops_combinations + publishDir = [ + path: { "${params.outdir}/hashing/hasheddrops/${meta.id}" }, + mode: params.publish_dir_mode, + saveAs: { filename -> filename.equals('versions.yml') ? null : filename }, + ] + } + + withName: PREPROCESSING_FOR_HTODEMUX_MULTISEQ { + ext.sel_method = params.preprocessing_sel_method + ext.ndelim = params.preprocessing_ndelim + ext.n_features = params.preprocessing_n_features + ext.assay = params.preprocessing_assay + ext.margin = params.preprocessing_margin + ext.norm_method = params.preprocessing_norm_method + ext.gene_col = params.preprocessing_gene_col + publishDir = [ + path: { "${params.outdir}/hashing/preprocessing/${meta.id}" }, + mode: params.publish_dir_mode, + saveAs: { filename -> filename.equals('versions.yml') ? null : filename }, + ] + } + withName: MULTIQC { ext.args = { params.multiqc_title ? "--title \"${params.multiqc_title}\"" : '' } publishDir = [ diff --git a/conf/test.config b/conf/test.config index 03938d08..604fa52b 100644 --- a/conf/test.config +++ b/conf/test.config @@ -23,6 +23,9 @@ params { config_profile_description = 'Minimal test dataset to check pipeline function' // Input data - input = '/home/nico/Software/nf-core/hadge/data/samplesheet.csv' - mode = 'hashing' + input = 'https://github.com/nf-core/test-datasets/raw/refs/heads/hadge/samplesheet.csv' + hash_tools = 'htodemux,hasheddrops,multiseq,demuxem,gmm-demux' + genetic_tools = 'demuxlet,freemuxlet,vireo' + mode = 'rescue' + bam_qc = true } diff --git a/modules.json b/modules.json index af3259f2..ff7aebd9 100644 --- a/modules.json +++ b/modules.json @@ -8,7 +8,8 @@ "cellsnp/modea": { "branch": "master", "git_sha": "05954dab2ff481bcb999f24455da29a5828af08d", - "installed_by": ["modules"] + "installed_by": ["modules"], + "patch": "modules/nf-core/cellsnp/modea/cellsnp-modea.diff" }, "demuxem": { "branch": "master", @@ -19,6 +20,18 @@ "gmmdemux": { "branch": "master", "git_sha": "05954dab2ff481bcb999f24455da29a5828af08d", + "installed_by": ["modules"], + "patch": "modules/nf-core/gmmdemux/gmmdemux.diff" + }, + "hasheddrops": { + "branch": "master", + "git_sha": "ac3ee5db3835e45ff9b9f03274f45049bd7028a3", + "installed_by": ["modules"], + "patch": "modules/nf-core/hasheddrops/hasheddrops.diff" + }, + "htodemux": { + "branch": "master", + "git_sha": "e793890476782166ebaabded864cca7a5b0438dd", "installed_by": ["modules"] }, "multiqc": { @@ -26,15 +39,23 @@ "git_sha": "e594e9dfaffa7572afc11bafc634984fd4cbd87b", "installed_by": ["modules"] }, + "multiseqdemux": { + "branch": "master", + "git_sha": "ea8d54b6ad1e8f55abdceed1ce7828360e134ebd", + "installed_by": ["modules"], + "patch": "modules/nf-core/multiseqdemux/multiseqdemux.diff" + }, "popscle/demuxlet": { "branch": "master", "git_sha": "05954dab2ff481bcb999f24455da29a5828af08d", - "installed_by": ["modules"] + "installed_by": ["modules"], + "patch": "modules/nf-core/popscle/demuxlet/popscle-demuxlet.diff" }, "popscle/dscpileup": { "branch": "master", "git_sha": "05954dab2ff481bcb999f24455da29a5828af08d", - "installed_by": ["modules"] + "installed_by": ["modules"], + "patch": "modules/nf-core/popscle/dscpileup/popscle-dscpileup.diff" }, "popscle/freemuxlet": { "branch": "master", @@ -61,6 +82,11 @@ "git_sha": "05954dab2ff481bcb999f24455da29a5828af08d", "installed_by": ["modules"] }, + "untar": { + "branch": "master", + "git_sha": "05954dab2ff481bcb999f24455da29a5828af08d", + "installed_by": ["modules"] + }, "vireo": { "branch": "master", "git_sha": "05954dab2ff481bcb999f24455da29a5828af08d", diff --git a/modules/local/htodemux_visualization/environment.yml b/modules/local/htodemux_visualization/environment.yml new file mode 100644 index 00000000..977429a6 --- /dev/null +++ b/modules/local/htodemux_visualization/environment.yml @@ -0,0 +1,6 @@ +channels: + - conda-forge + - bioconda +dependencies: + - conda-forge::r-ggplot2=3.5.2 + - conda-forge::r-seurat=5.3.0 diff --git a/modules/local/htodemux_visualization/main.nf b/modules/local/htodemux_visualization/main.nf new file mode 100644 index 00000000..24817329 --- /dev/null +++ b/modules/local/htodemux_visualization/main.nf @@ -0,0 +1,75 @@ +process HTODEMUX_VISUALIZATION { + tag "${meta.id}" + label 'process_low' + + conda "${moduleDir}/environment.yml" + container "${ workflow.containerEngine == 'singularity' && !task.ext.singularity_pull_docker_container ? + 'oras://community.wave.seqera.io/library/r-ggplot2_r-seurat:dac8c905972b98df': + 'community.wave.seqera.io/library/r-ggplot2_r-seurat:eefd54806320eae0' }" + + input: + tuple val(meta), path(seurat_object), val(assay) + + output: + tuple val(meta), path("*_ridge_htodemux.jpeg") , emit: ridge_plot , optional: true + tuple val(meta), path("*_featureScatter_htodemux.jpeg"), emit: feature_scatter, optional: true + tuple val(meta), path("*_violinPlot_htodemux.jpeg") , emit: violin_plot , optional: true + tuple val(meta), path("*_tSNE_htodemux.jpeg") , emit: tsne_plot , optional: true + tuple val(meta), path("*_heatMap_htodemux.jpeg") , emit: heatmap_plot , optional: true + tuple val(meta), path("*_visual_params_htodemux.csv") , emit: params + path "versions.yml" , emit: versions + + when: + task.ext.when == null || task.ext.when + + script: + // Ridge Plot Parameters + ridgePlot = task.ext.ridgePlot ?: true // Generate ridge plot + ridgeNCol = task.ext.ridgeNCol ?: 2 // Number of columns for ridge plot + + // Feature Scatter Plot Parameters + featureScatter = task.ext.featureScatter ?: true // Generate feature scatter plot + scatterFeat1 = task.ext.scatterFeat1 ?: null // Feature 1 for scatter plot + scatterFeat2 = task.ext.scatterFeat2 ?: null // Feature 2 for scatter plot + + // Violin Plot Parameters + vlnPlot = task.ext.vlnPlot ?: true // Generate violin plot + vlnFeatures = task.ext.vlnFeatures ?: "nCount_RNA" // Features to plot (gene expression, metrics, PC scores, anything that can be retreived by FetchData) + vlnLog = task.ext.vlnLog ?: true // Plot the feature axis on log scale + + // TSNE Plot Parameters + tSNE = task.ext.tSNE ?: true // Generate a two dimensional tSNE embedding for HTOs + tSNEIdents = task.ext.tSNEIdents ?: "Negative" // What should we remove from the object (we have Singlet,Doublet and Negative) + tSNEInvert = task.ext.tSNEInvert ?: true // Invert tSNE selection + tSNEVerbose = task.ext.tSNEVerbose ?: false // Verbose tSNE + tSNEApprox = task.ext.tSNEApprox ?: false // Approximate tSNE + tSNEDimMax = task.ext.tSNEDimMax ?: 2 // Max number of donors + tSNEPerplexity = task.ext.tSNEPerplexity ?: 100 // Value for perplexity + + // Heatmap Parameters + heatMap = task.ext.heatMap ?: true // Generate heatmap + heatMapNcells = task.ext.heatMapNcells ?: 500 // Number of cells for heatmap + + // Output Parameters + prefix = task.ext.prefix ?: "${meta.id}" + + template 'htodemux_visualization.R' + + stub: + def prefix = task.ext.prefix ?: "${meta.id}" + """ + touch ${prefix}_ridge_htodemux.jpeg + touch ${prefix}_featureScatter_htodemux.jpeg + touch ${prefix}_violinPlot_htodemux.jpeg + touch ${prefix}_tSNE_htodemux.jpeg + touch ${prefix}_heatMap_htodemux.jpeg + touch ${prefix}_visual_params_htodemux.csv + + cat <<-END_VERSIONS > versions.yml + "${task.process}": + r-base: \$(Rscript -e "cat(strsplit(R.version[['version.string']], ' ')[[1]][3])") + r-seurat: \$(Rscript -e "library(Seurat); cat(as.character(packageVersion('Seurat')))") + r-ggplot2: \$(Rscript -e "library(ggplot2); cat(as.character(packageVersion('ggplot2')))") + END_VERSIONS + """ +} diff --git a/modules/local/htodemux_visualization/templates/htodemux_visualization.R b/modules/local/htodemux_visualization/templates/htodemux_visualization.R new file mode 100644 index 00000000..4641c885 --- /dev/null +++ b/modules/local/htodemux_visualization/templates/htodemux_visualization.R @@ -0,0 +1,192 @@ +#!/usr/bin/env Rscript + +################################################ +################################################ +## Fucntions ## +################################################ +################################################ + +# Helper function for NULL condition +string_to_null <- function(x, val = "null") if (x == val) NULL else x +null_to_string <- function(x, val = "NULL") if (is.null(x)) val else x + +################################################ +################################################ +## USE PARAMETERS FROM NEXTFLOW ## +################################################ +################################################ + +# cast parameters from nextflow +seurat_object <- '$seurat_object' +assay <- '$assay' +ridgePlot <- as.logical('$ridgePlot') +ridgeNCol <- as.numeric('$ridgeNCol') +featureScatter <- as.logical('$featureScatter') +scatterFeat1 <- string_to_null('$scatterFeat1') +scatterFeat2 <- string_to_null('$scatterFeat2') +vlnPlot <- as.logical('$vlnPlot') +vlnFeatures <- '$vlnFeatures' +vlnLog <- as.logical('$vlnLog') +tSNE <- as.logical('$tSNE') +tSNEIdents <- '$tSNEIdents' +tSNEInvert <- as.logical('$tSNEInvert') +tSNEVerbose <- as.logical('$tSNEVerbose') +tSNEApprox <- as.logical('$tSNEApprox') +tSNEDimMax <- as.numeric('$tSNEDimMax') +tSNEPerplexity <- as.numeric('$tSNEPerplexity') +heatMap <- as.logical('$heatMap') +heatMapNcells <- as.numeric('$heatMapNcells') +prefix <- '$prefix' + +# check if the file exists +if (! file.exists(seurat_object)){ + stop(paste0(seurat_object, ' is not a valid file')) +} + +################################################ +################################################ +## Load libraries ## +################################################ +################################################ + +library(Seurat) +library(ggplot2) + +################################################ +################################################ +## Main Process ## +################################################ +################################################ + +hashtag <- readRDS(seurat_object) + +# Ridge Plot +if (ridgePlot) { + print("Generating ridge plot...") + Idents(hashtag) <- paste0(assay, "_maxID") + RidgePlot(hashtag, assay = assay, features = rownames(hashtag[[assay]]), ncol = ridgeNCol) + ggsave(paste0(prefix, "_ridge_htodemux.jpeg"), device = "jpeg", dpi = 500) +} + +# Feature Scatter Plot +if (featureScatter) { + if (is.null(scatterFeat1) || is.null(scatterFeat2)) { + available_features <- rownames(hashtag[[assay]]) + if (length(available_features) >= 2) { + scatterFeat1 <- available_features[1] + scatterFeat2 <- available_features[2] + } else { + stop("Error: Not enough features available for scatter plot") + } + } + FeatureScatter(hashtag, feature1 = scatterFeat1, feature2 = scatterFeat2) + ggsave(paste0(prefix, "_featureScatter_htodemux.jpeg"), device = "jpeg", dpi = 500) +} + +# Violin Plot +if (vlnPlot) { + print("Generating violin plot...") + Idents(hashtag) <- paste0(assay, "_classification.global") + VlnPlot(hashtag, features = vlnFeatures, pt.size = 0.1, log = vlnLog) + ggsave(paste0(prefix, "_violinPlot_htodemux.jpeg"), device = "jpeg", dpi = 500) +} + +# tSNE Plot +if (tSNE) { + print("Generating tSNE plot...") + hashtag.subset <- subset(hashtag, idents = tSNEIdents, invert = tSNEInvert) + DefaultAssay(hashtag.subset) <- assay + hashtag.subset <- ScaleData(hashtag.subset, + features = rownames(hashtag.subset), + verbose = tSNEVerbose + ) + hashtag.subset <- RunPCA(hashtag.subset, features = rownames(hashtag.subset), approx = tSNEApprox) + hashtag.subset <- RunTSNE(hashtag.subset, dims = 1:tSNEDimMax, perplexity = tSNEPerplexity, check_duplicates = FALSE) + DimPlot(hashtag.subset) + ggsave(paste0(prefix, "_tSNE_htodemux.jpeg"), device = "jpeg", dpi = 500) +} + +# Heatmap +if (heatMap) { + print("Generating heatmap...") + HTOHeatmap(hashtag, assay = assay, ncells = heatMapNcells) + ggsave(paste0(prefix, "_heatMap_htodemux.jpeg"), device = "jpeg", dpi = 500) +} + +################################################ +################################################ +## SAVING RESULTS ## +################################################ +################################################ + +# Save parameters +Argument <- c( + "seurat_object", + "assay", + "ridgePlot", + "ridgeNCol", + "featureScatter", + "scatterFeat1", + "scatterFeat2", + "vlnPlot", + "vlnFeatures", + "vlnLog", + "tSNE", + "tSNEIdents", + "tSNEInvert", + "tSNEVerbose", + "tSNEApprox", + "tSNEDimMax", + "tSNEPerplexity", + "heatMap", + "heatMapNcells" +) + +Value <- c( + seurat_object, + assay, + ridgePlot, + ridgeNCol, + featureScatter, + null_to_string(scatterFeat1), + null_to_string(scatterFeat2), + vlnPlot, + vlnFeatures, + vlnLog, + tSNE, + tSNEIdents, + tSNEInvert, + tSNEVerbose, + tSNEApprox, + tSNEDimMax, + tSNEPerplexity, + heatMap, + heatMapNcells +) + +params <- data.frame(Argument, Value) +write.csv(params, paste0(prefix, "_visual_params_htodemux.csv")) + +################################################ +################################################ +## SAVE VERSIONS ## +################################################ +################################################ + +r.version <- paste(R.version[['major']],R.version[['minor']], sep = ".") +seurat.version <- as.character(packageVersion('Seurat')) +ggplot2.version <- as.character(packageVersion('ggplot2')) + +writeLines( + c( + '"${task.process}":', + paste(' r-base:', r.version), + paste(' r-seurat:', seurat.version), + paste(' r-ggplot2:', ggplot2.version) + ), +'versions.yml') + +################################################ +################################################ +################################################ +################################################ diff --git a/modules/local/preprocessing_for_htodemux_multiseq/environment.yml b/modules/local/preprocessing_for_htodemux_multiseq/environment.yml new file mode 100644 index 00000000..12cf993b --- /dev/null +++ b/modules/local/preprocessing_for_htodemux_multiseq/environment.yml @@ -0,0 +1,5 @@ +channels: + - conda-forge + - bioconda +dependencies: + - conda-forge::r-seurat=5.3.0 diff --git a/modules/local/preprocessing_for_htodemux_multiseq/main.nf b/modules/local/preprocessing_for_htodemux_multiseq/main.nf new file mode 100644 index 00000000..3813b936 --- /dev/null +++ b/modules/local/preprocessing_for_htodemux_multiseq/main.nf @@ -0,0 +1,49 @@ +process PREPROCESSING_FOR_HTODEMUX_MULTISEQ { + tag "${meta.id}" + label 'process_low' + + conda "${moduleDir}/environment.yml" + container "${ workflow.containerEngine == 'singularity' && !task.ext.singularity_pull_docker_container ? + 'oras://community.wave.seqera.io/library/r-seurat:5.3.0--00f94834f5eea080': + 'community.wave.seqera.io/library/r-seurat:5.3.0--eeb977835038859a' }" + + + input: + tuple val(meta), path(rna_matrix), path(hto_matrix) + + output: + tuple val(meta), path("*_preprocessed.rds") , emit: seurat_object + tuple val(meta), path("*_params_preprocessing.csv"), emit: params + path "versions.yml" , emit: versions + + when: + task.ext.when == null || task.ext.when + + script: + // preprocessing parameters + sel_method = task.ext.sel_method ?: "mean.var.plot" // Selection method + ndelim = task.ext.ndelim ?: "_" // For the initial identity class for each cell, delimiter for the cell's column name + n_features = task.ext.n_features ?: 2000 // Number of features to be used when finding variable features + assay = task.ext.assay ?: "HTO" // Assay name for hashing modality + margin = task.ext.margin ?: 2 // Margin for normalisation + norm_method = task.ext.norm_method ?: "CLR" // Normalisation method + gene_col = task.ext.gene_col ?: 2 // Specify which column of genes.tsv or features.tsv to use for gene names + + // others + prefix = task.ext.prefix ?: "${meta.id}" + + template 'pre_processing.R' + + stub: + def prefix = task.ext.prefix ?: "${meta.id}" + """ + touch ${prefix}_preprocessed.rds + touch ${prefix}_params_preprocessing.csv + + cat <<-END_VERSIONS > versions.yml + "${task.process}": + r-base: \$(Rscript -e "cat(strsplit(R.version[['version.string']], ' ')[[1]][3])") + r-seurat: \$(Rscript -e "library(Seurat); cat(as.character(packageVersion('Seurat')))") + END_VERSIONS + """ +} diff --git a/modules/local/preprocessing_for_htodemux_multiseq/templates/pre_processing.R b/modules/local/preprocessing_for_htodemux_multiseq/templates/pre_processing.R new file mode 100644 index 00000000..a14ef4cd --- /dev/null +++ b/modules/local/preprocessing_for_htodemux_multiseq/templates/pre_processing.R @@ -0,0 +1,123 @@ +#!/usr/bin/env Rscript + +################################################ +################################################ +## USE PARAMETERS FROM NEXTFLOW ## +################################################ +################################################ + +# cast parameters from nextflow +rna_matrix <- '$rna_matrix' +hto_matrix <- '$hto_matrix' +sel_method <- '$sel_method' +ndelim <- '$ndelim' +n_features <- as.numeric('$n_features') +assay <- '$assay' +margin <- as.numeric('$margin') +norm_method <- '$norm_method' +gene_col <- as.numeric('$gene_col') +prefix <- '$prefix' + +# check if the files exist +if (! file.exists(hto_matrix)){ + stop(paste0(hto_matrix, ' is not a valid file')) +} + +if (! file.exists(rna_matrix)){ + stop(paste0(rna_matrix, ' is not a valid file')) +} + +################################################ +################################################ +## Load libraries ## +################################################ +################################################ + +library(Seurat) + +################################################ +################################################ +## Main Process ## +################################################ +################################################ + +# Read 10X data +umi <- Read10X(data.dir = rna_matrix, gene.column = gene_col) +counts <- Read10X(data.dir = hto_matrix, gene.column = gene_col) + +# Select cell barcodes detected by both RNA and HTO +joint.bcs <- intersect(colnames(umi), colnames(counts)) +# Subset RNA and HTO counts by joint cell barcodes +umi <- umi[, joint.bcs] +counts <- counts[, joint.bcs] + +# Setup Seurat object +hashtag <- CreateSeuratObject(counts = umi, names.delim = ndelim) +# Normalize RNA data with log normalization +hashtag <- NormalizeData(hashtag) +# Find and scale variable features +hashtag <- FindVariableFeatures(hashtag, selection.method = sel_method) +hashtag <- ScaleData(hashtag, features = VariableFeatures(hashtag)) +# Add HTO data as a new assay independent from RNA +hashtag[[assay]] <- CreateAssayObject(counts = counts) +# Normalize HTO data +hashtag <- NormalizeData(hashtag, assay = assay, normalization.method = norm_method, margin = margin) + +################################################ +################################################ +## SAVING RESULTS ## +################################################ +################################################ + +# Save preprocessed Seurat object +saveRDS(hashtag, file = paste0(prefix, "_preprocessed.rds")) + +# Save parameters +Argument <- c( + "hto_matrix", + "rna_matrix", + "sel_method", + "ndelim", + "n_features", + "assay", + "margin", + "norm_method", + "gene_col" +) + +Value <- c( + hto_matrix, + rna_matrix, + sel_method, + ndelim, + n_features, + assay, + margin, + norm_method, + gene_col +) + +params <- data.frame(Argument, Value) +write.csv(params, paste0(prefix, "_params_preprocessing.csv")) + +################################################ +################################################ +## SAVE VERSIONS ## +################################################ +################################################ + +r.version <- paste(R.version[['major']],R.version[['minor']], sep = ".") +seurat.version <- as.character(packageVersion('Seurat')) + +writeLines( + c( + '"${task.process}":', + paste(' r-base:', r.version), + paste(' seurat:', seurat.version) + ), +'versions.yml') + +################################################ +################################################ +################################################ +################################################ diff --git a/modules/local/rename_genes_to_features/environment.yml b/modules/local/rename_genes_to_features/environment.yml new file mode 100644 index 00000000..babcfb55 --- /dev/null +++ b/modules/local/rename_genes_to_features/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: + - conda-forge::coreutils=9.3 diff --git a/modules/local/rename_genes_to_features/main.nf b/modules/local/rename_genes_to_features/main.nf new file mode 100644 index 00000000..c1c5d8fa --- /dev/null +++ b/modules/local/rename_genes_to_features/main.nf @@ -0,0 +1,25 @@ +process RENAME_GENES_TO_FEATURES { + tag "${meta.id}" + label 'process_low' + + conda "${moduleDir}/environment.yml" + container "${ workflow.containerEngine == 'singularity' && !task.ext.singularity_pull_docker_container ? + 'https://depot.galaxyproject.org/singularity/coreutils:9.3': + 'biocontainers/coreutils:9.3' }" + + input: + tuple val(meta), path(dir) + + output: + tuple val(meta), path(prefix) + + script: + prefix = task.ext.prefix ?: "${meta.id}" + """ + cp -Lr ${dir} ${prefix} + + if [ -f "${prefix}/genes.tsv.gz" ]; then + mv "${prefix}/genes.tsv.gz" "${prefix}/features.tsv.gz" + fi + """ +} diff --git a/modules/nf-core/cellsnp/modea/cellsnp-modea.diff b/modules/nf-core/cellsnp/modea/cellsnp-modea.diff new file mode 100644 index 00000000..b3342128 --- /dev/null +++ b/modules/nf-core/cellsnp/modea/cellsnp-modea.diff @@ -0,0 +1,82 @@ +Changes in component 'nf-core/cellsnp/modea' +Changes in 'cellsnp/modea/main.nf': +--- modules/nf-core/cellsnp/modea/main.nf ++++ modules/nf-core/cellsnp/modea/main.nf +@@ -1,42 +1,42 @@ + process CELLSNP_MODEA { +- tag "$meta.id" ++ tag "${meta.id}" + label 'process_medium' + + conda "${moduleDir}/environment.yml" +- container "${ workflow.containerEngine == 'singularity' && !task.ext.singularity_pull_docker_container ? +- 'https://depot.galaxyproject.org/singularity/cellsnp-lite:1.2.3--h6141fd1_2' : +- 'biocontainers/cellsnp-lite:1.2.3--h6141fd1_2' }" ++ container "${workflow.containerEngine == 'singularity' && !task.ext.singularity_pull_docker_container ++ ? 'https://depot.galaxyproject.org/singularity/cellsnp-lite:1.2.3--h6141fd1_2' ++ : 'biocontainers/cellsnp-lite:1.2.3--h6141fd1_2'}" + + input: + tuple val(meta), path(bam), path(bai), path(region_vcf), path(barcode) + + output: +- tuple val(meta), path('*.base.vcf.gz') , emit: base +- tuple val(meta), path('*.cells.vcf.gz'), emit: cell , optional: true +- tuple val(meta), path('*.samples.tsv') , emit: sample +- tuple val(meta), path('*.tag.AD.mtx') , emit: allele_depth +- tuple val(meta), path('*.tag.DP.mtx') , emit: depth_coverage +- tuple val(meta), path('*.tag.OTH.mtx') , emit: depth_other +- path 'versions.yml' , emit: versions ++ tuple val(meta), path('*.base.vcf.gz'), emit: base ++ tuple val(meta), path('*.cells.vcf.gz'), emit: cell, optional: true ++ tuple val(meta), path('*.samples.tsv'), emit: sample ++ tuple val(meta), path('*.tag.AD.mtx'), emit: allele_depth ++ tuple val(meta), path('*.tag.DP.mtx'), emit: depth_coverage ++ tuple val(meta), path('*.tag.OTH.mtx'), emit: depth_other ++ path 'versions.yml', emit: versions + + when: + task.ext.when == null || task.ext.when + + script: +- def args = task.ext.args ?: '' +- def prefix = task.ext.prefix ?: "${meta.id}" +- def region_file = region_vcf ? "-R $region_vcf" : '' ++ def args = task.ext.args ?: '' ++ def prefix = task.ext.prefix ?: "${meta.id}" ++ def region_file = region_vcf ? "-R ${region_vcf}" : '' + """ +- cellsnp-lite -s $bam \\ +- -b $barcode \\ +- $region_file \\ ++ cellsnp-lite -s ${bam} \\ ++ -b ${barcode} \\ ++ ${region_file} \\ + -O . \\ + --gzip \\ +- --nproc $task.cpus \\ +- $args ++ --nproc ${task.cpus} \\ ++ ${args} + + mv cellSNP.base.vcf.gz ${prefix}.base.vcf.gz +- if [[ "$args" == *"--genotype"* ]]; then ++ if [[ "${args}" == *"--genotype"* ]]; then + mv cellSNP.cells.vcf.gz ${prefix}.cells.vcf.gz + fi + mv cellSNP.tag.AD.mtx ${prefix}.tag.AD.mtx +@@ -53,7 +53,7 @@ + stub: + def prefix = task.ext.prefix ?: "${meta.id}" + """ +- mkdir $prefix ++ mkdir ${prefix} + echo "" | gzip > ${prefix}.base.vcf.gz + touch ${prefix}.samples.tsv + touch ${prefix}.tag.AD.mtx + +'modules/nf-core/cellsnp/modea/environment.yml' is unchanged +'modules/nf-core/cellsnp/modea/meta.yml' is unchanged +'modules/nf-core/cellsnp/modea/tests/main.nf.test.snap' is unchanged +'modules/nf-core/cellsnp/modea/tests/main.nf.test' is unchanged +************************************************************ diff --git a/modules/nf-core/gmmdemux/gmmdemux.diff b/modules/nf-core/gmmdemux/gmmdemux.diff new file mode 100644 index 00000000..fdd35963 --- /dev/null +++ b/modules/nf-core/gmmdemux/gmmdemux.diff @@ -0,0 +1,118 @@ +Changes in component 'nf-core/gmmdemux' +Changes in 'gmmdemux/main.nf': +--- modules/nf-core/gmmdemux/main.nf ++++ modules/nf-core/gmmdemux/main.nf +@@ -1,63 +1,65 @@ +- + process GMMDEMUX { +- tag "$meta.id" ++ tag "${meta.id}" + label 'process_low' + + conda "${moduleDir}/environment.yml" +- container "${ workflow.containerEngine == 'singularity' && !task.ext.singularity_pull_docker_container ? +- 'https://depot.galaxyproject.org/singularity/gmm-demux:0.2.2.3--pyh7cba7a3_0': +- 'biocontainers/gmm-demux:0.2.2.3--pyh7cba7a3_0' }" ++ container "${workflow.containerEngine == 'singularity' && !task.ext.singularity_pull_docker_container ++ ? 'https://depot.galaxyproject.org/singularity/gmm-demux:0.2.2.3--pyh7cba7a3_0' ++ : 'biocontainers/gmm-demux:0.2.2.3--pyh7cba7a3_0'}" + + input: +- tuple val(meta), path(hto_matrix), val(hto_names) +- val type_report ++ tuple val(meta), path(hto_matrix), val(hto_names), val(estimated_cells) ++ val full_report + val summary_report +- path skip +- path examine ++ path classification_report ++ path cell_list + + output: +- tuple val(meta), path("barcodes.tsv.gz" ), emit: barcodes +- tuple val(meta), path("matrix.mtx.gz" ), emit: matrix +- tuple val(meta), path("features.tsv.gz" ), emit: features +- tuple val(meta), path("GMM_*.csv" ), emit: classification_report +- tuple val(meta), path("GMM_*.config" ), emit: config_report ++ tuple val(meta), path("barcodes.tsv.gz"), emit: barcodes ++ tuple val(meta), path("matrix.mtx.gz"), emit: matrix ++ tuple val(meta), path("features.tsv.gz"), emit: features ++ tuple val(meta), path("GMM_*.csv"), emit: classification_report ++ tuple val(meta), path("GMM_*.config"), emit: config_report + tuple val(meta), path("summary_report_*.txt"), emit: summary_report, optional: true +- path "versions.yml" , emit: versions ++ path "versions.yml", emit: versions + + when: + task.ext.when == null || task.ext.when + + script: +- def args = task.ext.args ?: '' +- def prefix = task.ext.prefix ?: "${meta.id}" +- def skip = skip ? "--skip $skip" : "" +- def examine_cells = examine ? "--examine $examine" : "" +- def VERSION = '0.2.2.3' // WARN: Version information not provided by tool on CLI. Please update version string below when bumping container versions. +- def type_report = type_report ? "-f ." : "-s ." +- def summary_rep = summary_report ? "-r ${prefix}_summary_report.txt" : "" ++ // Docs: https://gmm-demux.readthedocs.io/en/latest/usage.html#command-line-tools ++ def args = task.ext.args ?: '' ++ def prefix = task.ext.prefix ?: "${meta.id}" ++ def skip_arg = classification_report ? "--skip ${classification_report}" : "" ++ def examine_arg = cell_list ? "--examine ${cell_list}" : "" ++ // WARN: Version information not provided by tool on CLI. Please update version string below when bumping container versions. ++ def VERSION = '0.2.2.3' ++ def report_arg = full_report ? "-f ." : "-s ." ++ def estimated_cells_arg = estimated_cells ? "--summary ${estimated_cells}" : "" ++ def summary_report_arg = summary_report ? "-r ${prefix}_summary_report.txt" : "" + """ + if [[ ${summary_report} == true ]]; then + cat /dev/null > ${prefix}_summary_report.txt + fi + +- GMM-demux $args \\ +- $type_report \\ +- $summary_rep \\ +- $skip \\ +- $examine_cells \\ +- $hto_matrix \\ +- $hto_names \\ +- -o . ++ GMM-demux ${hto_matrix} ${hto_names} \\ ++ ${report_arg} \\ ++ ${summary_report_arg} \\ ++ ${estimated_cells_arg} \\ ++ ${skip_arg} \\ ++ ${examine_arg} \\ ++ -o . \\ ++ ${args} + + cat <<-END_VERSIONS > versions.yml + "${task.process}": +- GMM-Demux: $VERSION ++ GMM-Demux: ${VERSION} + END_VERSIONS + """ + + stub: + def VERSION = '0.2.2.3' +- def prefix = task.ext.prefix ?: "${meta.id}" ++ def prefix = task.ext.prefix ?: "${meta.id}" + """ + echo "" | gzip > barcodes.tsv.gz + echo "" | gzip > features.tsv.gz +@@ -67,7 +69,7 @@ + + cat <<-END_VERSIONS > versions.yml + "${task.process}": +- GMM-Demux: $VERSION ++ GMM-Demux: ${VERSION} + END_VERSIONS + """ + } + +'modules/nf-core/gmmdemux/environment.yml' is unchanged +'modules/nf-core/gmmdemux/meta.yml' is unchanged +'modules/nf-core/gmmdemux/tests/main.nf.test.snap' is unchanged +'modules/nf-core/gmmdemux/tests/nextflow.config' is unchanged +'modules/nf-core/gmmdemux/tests/main.nf.test' is unchanged +************************************************************ diff --git a/modules/nf-core/hasheddrops/environment.yml b/modules/nf-core/hasheddrops/environment.yml new file mode 100644 index 00000000..64ba30ce --- /dev/null +++ b/modules/nf-core/hasheddrops/environment.yml @@ -0,0 +1,8 @@ +--- +# yaml-language-server: $schema=https://raw.githubusercontent.com/nf-core/modules/master/modules/environment-schema.json +channels: + - conda-forge + - bioconda +dependencies: + - bioconda::bioconductor-dropletutils=1.26.0 + - conda-forge::r-seurat=5.3.0 diff --git a/modules/nf-core/hasheddrops/hasheddrops.diff b/modules/nf-core/hasheddrops/hasheddrops.diff new file mode 100644 index 00000000..dcc5bc79 --- /dev/null +++ b/modules/nf-core/hasheddrops/hasheddrops.diff @@ -0,0 +1,28 @@ +Changes in component 'nf-core/hasheddrops' +'modules/nf-core/hasheddrops/main.nf' is unchanged +'modules/nf-core/hasheddrops/environment.yml' is unchanged +Changes in 'hasheddrops/meta.yml': +--- modules/nf-core/hasheddrops/meta.yml ++++ modules/nf-core/hasheddrops/meta.yml +@@ -109,7 +109,7 @@ + - "*_plot_hasheddrops.png": + type: file + description: | +- HashedDrops plot ++ HashedDrops plot + pattern: "_plot_hasheddrops.png" + + - params: +@@ -128,7 +128,6 @@ + type: file + description: File containing software versions. + +- + authors: + - "@LuisHeinzlmeier" + maintainers: + +'modules/nf-core/hasheddrops/templates/HashedDrops.R' is unchanged +'modules/nf-core/hasheddrops/tests/main.nf.test.snap' is unchanged +'modules/nf-core/hasheddrops/tests/main.nf.test' is unchanged +************************************************************ diff --git a/modules/nf-core/hasheddrops/main.nf b/modules/nf-core/hasheddrops/main.nf new file mode 100644 index 00000000..6a9e4f5e --- /dev/null +++ b/modules/nf-core/hasheddrops/main.nf @@ -0,0 +1,75 @@ +process HASHEDDROPS { + tag "$meta.id" + label 'process_low' + + conda "${moduleDir}/environment.yml" + container "${ workflow.containerEngine == 'singularity' && !task.ext.singularity_pull_docker_container ? + 'oras://community.wave.seqera.io/library/bioconductor-dropletutils_r-seurat:3cbdf18d48cd0cfa': + 'community.wave.seqera.io/library/bioconductor-dropletutils_r-seurat:e1dff3a0fb7c5920' }" + + input: + tuple val(meta), path(hto_matrix), val(runEmptyDrops), path(rna_matrix) + + output: + tuple val(meta), path("*_emptyDrops.png") , emit: empty_drops_plot + tuple val(meta), path("*_emptyDrops.csv") , emit: empty_drops_csv + tuple val(meta), path("*_emptyDrops.rds") , emit: empty_drops_rds + tuple val(meta), path("*_results_hasheddrops.csv") , emit: results + tuple val(meta), path("*_hasheddrops.rds") , emit: rds + tuple val(meta), path("*_plot_hasheddrops.png") , emit: plot + tuple val(meta), path("*_params_hasheddrops.csv") , emit: params + path "versions.yml" , emit: versions + + when: + task.ext.when == null || task.ext.when + + script: + + // emptyDrops Parameters + lower = task.ext.lower ?: "100" // A numeric scalar specifying the lower bound on the total UMI count, at or below which all barcodes are assumed to correspond to empty droplets. + niters = task.ext.niters ?: "10000" // An integer scalar specifying the number of iterations to use for the Monte Carlo p-value calculations. + testAmbient = task.ext.testAmbient ?: "TRUE" // A logical scalar indicating whether results should be returned for barcodes with totals less than or equal to lower. + round = task.ext.round ?: "TRUE" // Logical scalar indicating whether to check for non-integer values in m and, if present, round them for ambient profile estimation. + byRank = task.ext.byRank ?: "NULL" // An integer scalar parametrizing an alternative method for identifying assumed empty droplets. If set, this is used to redefine lower and any specified value for lower is ignored. + isCellFDR = task.ext.isCellFDR ?: "0.01" // Threshold to filter the cells. + gene_col = task.ext.gene_col ?: "2" // Specify which column of genes.tsv or features.tsv to use for gene names; default is 2. + + // hashedDrops Parameters + ignore = task.ext.ignore ?: "NULL" // A numeric scalar specifying the lower bound on the total UMI count, at or below which barcodes will be ignored. + alpha = task.ext.alpha ?: "NULL" // A numeric scalar specifying the scaling parameter for the Dirichlet-multinomial sampling scheme. + ambient = task.ext.ambient ?: "TRUE" // Whether to use the relative abundance of each HTO in the ambient solution from emptyDrops, set TRUE only when test_ambient is TRUE. + minProp = task.ext.minProp ?: "0.05" // Numeric scalar to be used to infer the ambient profile when ambient=NULL. + pseudoCount = task.ext.pseudoCount ?: "5" // A numeric scalar specifying the minimum pseudo-count when computing logfold changes. + constantAmbient = task.ext.constantAmbient ?: "FALSE" // Logical scalar indicating whether a constant level of ambient contamination should be used to estimate LogFC2 for all cells. + doubletNmads = task.ext.doubletNmads ?: "3" // A numeric scalar specifying the number of median absolute deviations (MADs) to use to identify doublets. + doubletMin = task.ext.doubletMin ?: "2" // A numeric scalar specifying the minimum threshold on the log-fold change to use to identify doublets. + doubletMixture = task.ext.doubletMixture ?: "FALSE" // Logical scalar indicating whether to use a 2-component mixture model to identify doublets. + confidentNmads = task.ext.confidentNmads ?: "3" // A numeric scalar specifying the number of MADs to use to identify confidently assigned singlets. + confidentMin = task.ext.confidentMin ?: "2" // A numeric scalar specifying the minimum threshold on the log-fold change to use to identify singlets. + combinations = task.ext.combinations ?: "NULL" // An integer matrix specifying valid combinations of HTOs. Each row corresponds to a single sample and specifies the indices of rows in x corresponding to the HTOs used to label that sample. + + // others + prefix = task.ext.prefix ?: "${meta.id}" // Prefix name for output files. + + template 'HashedDrops.R' + + stub: + def prefix = task.ext.prefix ?: "${meta.id}" + """ + touch ${prefix}_emptyDrops.png + touch ${prefix}_emptyDrops.csv + touch ${prefix}_emptyDrops.rds + touch ${prefix}_results_hasheddrops.csv + touch ${prefix}_hasheddrops.rds + touch ${prefix}_plot_hasheddrops.png + touch ${prefix}_params_hasheddrops.csv + + cat <<-END_VERSIONS > versions.yml + "${task.process}": + r-base: \$(Rscript -e "cat(strsplit(R.version[['version.string']], ' ')[[1]][3])") + r-seurat: \$(Rscript -e "library(Seurat); cat(as.character(packageVersion('Seurat')))") + cdropletutils: \$(Rscript -e "library(DropletUtils); cat(as.character(packageVersion('DropletUtils')))") + END_VERSIONS + """ +} + diff --git a/modules/nf-core/hasheddrops/meta.yml b/modules/nf-core/hasheddrops/meta.yml new file mode 100644 index 00000000..4df1efe1 --- /dev/null +++ b/modules/nf-core/hasheddrops/meta.yml @@ -0,0 +1,134 @@ +--- +# yaml-language-server: $schema=https://raw.githubusercontent.com/nf-core/modules/master/modules/meta-schema.json +name: "hasheddrops" +description: Generating cell hashing calls from a matrix of count data. +keywords: + - demultiplexing + - hashing-based deconvolution + - single-cell +tools: + - "hasheddrops": + description: "Demultiplex cell barcodes into their samples of origin based on the most abundant hash tag oligo (HTO). Also identify potential doublets based on the presence of multiple significant HTOs." + homepage: "https://rdrr.io/github/MarioniLab/DropletUtils/man/hashedDrops.html" + documentation: "https://rdrr.io/github/MarioniLab/DropletUtils/man/hashedDrops.html" + tool_dev_url: "https://github.com/MarioniLab/DropletUtils" + doi: "10.18129/B9.bioc.DropletUtils" + licence: ["GPL-3"] + identifier: "" + +input: + - - meta: + type: map + description: | + Groovy Map containing sample information + e.g. `[ id:'sample1' ]` + - hto_matrix: + type: file + description: | + Directory that contains the HTO matrix in a 10X format. + ontologies: + - edam: "http://edamontology.org/data_3917" # count matrix + - runEmptyDrops: + type: boolean + description: | + Run EmptyDrops() before hasheDrops() ("TRUE") or not ("FALSE"). + - rna_matrix: + type: file + description: | + Path to RNA count matrix, which is only uses if runEmptyDrops == "TRUE". + ontologies: + - edam: "http://edamontology.org/data_3917" # count matrix + +output: + - empty_drops_plot: + - meta: + type: map + description: | + Groovy Map containing sample information + e.g. `[ id:'sample1' ]` + - "*_emptyDrops.png": + type: file + description: | + EmptyDrops results plot + pattern: "_emptyDrops.png" + + - empty_drops_csv: + - meta: + type: file + description: | + Groovy Map containing sample information + e.g. `[ id:'sample1' ]` + - "*_emptyDrops.csv": + type: file + description: | + EmptyDrops results in CSV format + pattern: "_emptyDrops.csv" + + - empty_drops_rds: + - meta: + type: map + description: | + Groovy Map containing sample information + e.g. `[ id:'sample1' ]` + - "*_emptyDrops.rds": + type: file + description: | + EmptyDrops results in RDS format + pattern: "_emptyDrops.rds" + + - results: + - meta: + type: map + description: | + Groovy Map containing sample information + e.g. `[ id:'sample1' ]` + - "*_results_hasheddrops.csv": + type: file + description: | + HashedDrops results + pattern: "_results_hasheddrops.csv" + + - rds: + - meta: + type: map + description: | + Groovy Map containing sample information + e.g. `[ id:'sample1' ]` + - "*_hasheddrops.rds": + type: file + description: | + HashedDrops results in RDS format + pattern: "_hasheddrops.rds" + + - plot: + - meta: + type: map + description: | + Groovy Map containing sample information + e.g. `[ id:'sample1' ]` + - "*_plot_hasheddrops.png": + type: file + description: | + HashedDrops plot + pattern: "_plot_hasheddrops.png" + + - params: + - meta: + type: map + description: | + Groovy Map containing sample information + e.g. `[ id:'sample1' ]` + - "*_params_hasheddrops.csv": + type: file + description: | + The used parameters to call hashedDrops() in the R-Script. + pattern: "_params_hasheddrops.csv" + - versions: + - "versions.yml": + type: file + description: File containing software versions. + +authors: + - "@LuisHeinzlmeier" +maintainers: + - "@LuisHeinzlmeier" diff --git a/modules/nf-core/hasheddrops/templates/HashedDrops.R b/modules/nf-core/hasheddrops/templates/HashedDrops.R new file mode 100644 index 00000000..9be3df1c --- /dev/null +++ b/modules/nf-core/hasheddrops/templates/HashedDrops.R @@ -0,0 +1,248 @@ +#!/usr/bin/env Rscript + +################################################ +################################################ +## Fucntions ## +################################################ +################################################ + +# Helper function for NULL condition +string_to_null <- function(x, val = "NULL") if (x == val) NULL else x +null_to_string <- function(x, val = "NULL") if (is.null(x)) val else x + +string_to_logical <- function(input) { + if (input == "FALSE") { + FALSE + } else if (input == "TRUE") { + TRUE + } else { + stop(paste0(input, " is not a valid logical. Use 'FALSE' or 'TRUE'.")) + } +} + +################################################ +################################################ +## USE PARAMETERS FROM NEXTFLOW ## +################################################ +################################################ + +# cast parameters from nextflow + +# hashedDrops parameters +hto_matrix <- '$hto_matrix' +rna_matrix <- '$rna_matrix' +lower <- as.numeric('$lower') +niters <- as.numeric('$niters') +testAmbient <- string_to_logical('$testAmbient') +ignore <- string_to_null('$ignore') +alpha <- string_to_null('$alpha') +round <- string_to_logical('$round') +byRank <- string_to_null('$byRank') +isCellFDR <- as.numeric('$isCellFDR') +ambient <- string_to_logical('$ambient') +minProp <- as.numeric('$minProp') +pseudoCount <- as.numeric('$pseudoCount') +constantAmbient <- string_to_logical('$constantAmbient') +doubletNmads <- as.numeric('$doubletNmads') +doubletMin <- as.numeric('$doubletMin') +doubletMixture <- string_to_logical('$doubletMixture') +confidentNmads <- as.numeric('$confidentNmads') +confidentMin <- as.numeric('$confidentMin') +combinations <- string_to_null('$combinations') +runEmptyDrops <- string_to_logical('$runEmptyDrops') +gene_col <- as.numeric('$gene_col') +prefix <- '$prefix' + +# check if the file exists +if (! file.exists(hto_matrix)){ + stop(paste0(hto_matrix, ' is not a valid file')) +} + +################################################ +################################################ +## Finish loading libraries ## +################################################ +################################################ + +library(Seurat) # for Read10X() +library(DropletUtils) # for hashedDrops() and emptyDrops() + +################################################ +################################################ +## Main Process ## +################################################ +################################################ + +hto <- Read10X(data.dir = hto_matrix, gene.column = gene_col) + +# die bekomme ich mit runempty Drops +#is.cell <- NULL + +# determine hto_input and ambient_input +if (runEmptyDrops) { + + rna <- Read10X(data.dir = rna_matrix, gene.column = gene_col) + + emptyDrops_out <- emptyDrops( + rna, + lower = lower, + niters = niters, + test.ambient = testAmbient, + ignore = NULL, + alpha = alpha, + round = round, + by.rank = byRank + ) + + # which droplets are actual cells + is.cell <- emptyDrops_out\$FDR <= isCellFDR + hto_input <- hto[, which(is.cell)] + + if (ambient) { + ambient_input <- metadata(emptyDrops_out)\$ambient + } else { + ambient_input <- NULL + } +} else { + ambient_input <- NULL + hto_input <- hto + + # only important for saving the results + emptyDrops_out <- data.frame() +} + +hashedDrops_out <- hashedDrops( + hto_input, + min.prop = minProp, + ambient = ambient_input, + pseudo.count = pseudoCount, + constant.ambient = constantAmbient, + doublet.nmads = doubletNmads, + doublet.min = doubletMin, + doublet.mixture = doubletMixture, + confident.nmads = confidentNmads, + confident.min = confidentMin, + combinations = combinations +) + +################################################ +################################################ +## SAVING RESULTS ## +################################################ +################################################ + +#----- saving parameters in a dataframe ------# +Argument <- c( + "hto_matrix", + "lower", + "niters", + "testAmbient", + "ignore", + "alpha", + "round", + "byRank", + "isCellFDR", + "ambient", + "minProp", + "pseudoCount", + "constantAmbient", + "doubletNmads", + "doubletMin", + "doubletMixture", + "confidentNmads", + "confidentMin", + "combinations" +) + +Value <- c( + hto_matrix, + lower, + niters, + testAmbient, + null_to_string(ignore), + null_to_string(alpha), + round, + null_to_string(byRank), + isCellFDR, + ambient, + null_to_string(minProp), + pseudoCount, + constantAmbient, + doubletNmads, + doubletMin, + doubletMixture, + confidentNmads, + confidentMin, + null_to_string(combinations) +) + +params <- data.frame(Argument, Value) +write.csv(params, paste0(prefix ,"_params_hasheddrops.csv")) + +#--------- save emptyDrops() results ---------# + +# create a plot with results from emptyDrops() or save an empty png +png(paste0(prefix, "_emptyDrops.png")) +if(runEmptyDrops){ + colors <- ifelse(is.cell, "red", "black") + plot(emptyDrops_out\$Total, -emptyDrops_out\$LogProb, col = colors, xlab = "Total UMI count", ylab = "-Log Probability") +}else{ + plot.new() + +} +dev.off() + +write.csv(emptyDrops_out,paste0(prefix, "_emptyDrops.csv")) +saveRDS(emptyDrops_out,file = paste0(prefix, "_emptyDrops.rds")) + +#--------- save hashedDrops() results ---------# + +write.csv(params, paste0(prefix, "_params_hasheddrops.csv")) +write.csv(hashedDrops_out,paste0(prefix,"_results_hasheddrops.csv")) +saveRDS(hashedDrops_out,file = paste0(prefix,"_hasheddrops.rds")) + +png(paste0(prefix, "_plot_hasheddrops.png")) +if (sum(is.na(hashedDrops_out\$LogFC2)) != length(hashedDrops_out\$LogFC2)) { + + colors <- ifelse(hashedDrops_out\$Confident, + "black", + ifelse(hashedDrops_out\$Doublet, "red", "grey") + ) + + plot( + hashedDrops_out\$LogFC, + hashedDrops_out\$LogFC2, + col = colors, + xlab = "Log-fold change between best and second HTO", + ylab = "Log-fold change between second HTO and ambient" + ) +}else{ + + plot.new() +} + +dev.off() + +################################################ +################################################ +## VERSIONS FILE ## +################################################ +################################################ + +r.version <- paste(R.version[['major']],R.version[['minor']], sep = ".") +seurat.version <- as.character(packageVersion('Seurat')) +dropletutils.version <- as.character(packageVersion('DropletUtils')) + +writeLines( + c( + '"${task.process}":', + paste(' r-base:', r.version), + paste(' r-seurat:', seurat.version), + paste(' dropletutils:', dropletutils.version) + ), +'versions.yml') + +################################################ +################################################ +################################################ +################################################ diff --git a/modules/nf-core/hasheddrops/tests/main.nf.test b/modules/nf-core/hasheddrops/tests/main.nf.test new file mode 100644 index 00000000..e785b50b --- /dev/null +++ b/modules/nf-core/hasheddrops/tests/main.nf.test @@ -0,0 +1,120 @@ +nextflow_process { + + name "Test Process HASHEDDROPS" + script "../main.nf" + process "HASHEDDROPS" + + tag "modules" + tag "modules_nfcore" + tag "hasheddrops" + tag "untar" + + test("hto_matrix - runEmptyDrops:false - rna_matrix") { + + setup { + run("UNTAR") { + script "modules/nf-core/untar/main.nf" + process { + """ + input[0] = Channel.of( + [ [id: 'hto'], file(params.modules_testdata_base_path + "/genomics/homo_sapiens/10xgenomics/cellranger/hashing_demultiplexing/hto.tar.gz") ], + [ [id: 'rna'], file(params.modules_testdata_base_path + "/genomics/homo_sapiens/10xgenomics/cellranger/hashing_demultiplexing/rna.tar.gz") ] + ) + """ + } + } + } + + when { + process { + """ + input[0] = UNTAR.out.untar.filter{ meta, _f -> meta.id == 'hto' } + .combine(UNTAR.out.untar.filter{ meta, _f -> meta.id == 'rna' }) + .map{ _meta_hto, hto, _meta_rna, rna -> [[id: 'test'], hto, "FALSE", rna] } + """ + } + } + + then { + assertAll( + { assert process.success }, + { assert snapshot(process.out).match() } + ) + } + + } +/* + test("hto_matrix - runEmptyDrops:true - rna_matrix") { + + setup { + run("UNTAR") { + script "modules/nf-core/untar/main.nf" + process { + """ + input[0] = Channel.of( + [ [id: 'hto'], file(params.modules_testdata_base_path + "/genomics/homo_sapiens/10xgenomics/cellranger/hashing_demultiplexing/hto.tar.gz") ], + [ [id: 'rna'], file(params.modules_testdata_base_path + "/genomics/homo_sapiens/10xgenomics/cellranger/hashing_demultiplexing/rna.tar.gz") ] + ) + """ + } + } + } + + when { + process { + """ + input[0] = UNTAR.out.untar.filter{ meta, _f -> meta.id == 'hto' } + .combine(UNTAR.out.untar.filter{ meta, _f -> meta.id == 'rna' }) + .map{ _meta_hto, hto, _meta_rna, rna -> [[id: 'test'], hto, "TRUE", rna] } + """ + } + } + + then { + assertAll( + { assert process.success }, + { assert snapshot(process.out).match() } + ) + } + + } +*/ + test("hto_matrix - runEmptyDrops:true - rna_matrix - stub") { + + options "-stub" + + setup { + run("UNTAR") { + script "modules/nf-core/untar/main.nf" + process { + """ + input[0] = Channel.of( + [ [id: 'hto'], file(params.modules_testdata_base_path + "/genomics/homo_sapiens/10xgenomics/cellranger/hashing_demultiplexing/hto.tar.gz") ], + [ [id: 'rna'], file(params.modules_testdata_base_path + "/genomics/homo_sapiens/10xgenomics/cellranger/hashing_demultiplexing/rna.tar.gz") ] + ) + """ + } + } + } + + when { + process { + """ + input[0] = UNTAR.out.untar.filter{ meta, _f -> meta.id == 'hto' } + .combine(UNTAR.out.untar.filter{ meta, _f -> meta.id == 'rna' }) + .map{ _meta_hto, hto, _meta_rna, rna -> [[id: 'test'], hto, "TRUE", rna] } + """ + } + } + + then { + assertAll( + { assert process.success }, + { assert snapshot(process.out).match() } + ) + } + + } + +} + diff --git a/modules/nf-core/hasheddrops/tests/main.nf.test.snap b/modules/nf-core/hasheddrops/tests/main.nf.test.snap new file mode 100644 index 00000000..8447c0c0 --- /dev/null +++ b/modules/nf-core/hasheddrops/tests/main.nf.test.snap @@ -0,0 +1,319 @@ +{ + "hto_matrix - runEmptyDrops:true - rna_matrix - stub": { + "content": [ + { + "0": [ + [ + { + "id": "test" + }, + "test_emptyDrops.png:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "1": [ + [ + { + "id": "test" + }, + "test_emptyDrops.csv:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "2": [ + [ + { + "id": "test" + }, + "test_emptyDrops.rds:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "3": [ + [ + { + "id": "test" + }, + "test_results_hasheddrops.csv:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "4": [ + [ + { + "id": "test" + }, + "test_hasheddrops.rds:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "5": [ + [ + { + "id": "test" + }, + "test_plot_hasheddrops.png:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "6": [ + [ + { + "id": "test" + }, + "test_params_hasheddrops.csv:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "7": [ + "versions.yml:md5,ce84fb45f93458d1aa609015a25e7777" + ], + "empty_drops_csv": [ + [ + { + "id": "test" + }, + "test_emptyDrops.csv:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "empty_drops_plot": [ + [ + { + "id": "test" + }, + "test_emptyDrops.png:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "empty_drops_rds": [ + [ + { + "id": "test" + }, + "test_emptyDrops.rds:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "params": [ + [ + { + "id": "test" + }, + "test_params_hasheddrops.csv:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "plot": [ + [ + { + "id": "test" + }, + "test_plot_hasheddrops.png:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "rds": [ + [ + { + "id": "test" + }, + "test_hasheddrops.rds:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "results": [ + [ + { + "id": "test" + }, + "test_results_hasheddrops.csv:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "versions": [ + "versions.yml:md5,ce84fb45f93458d1aa609015a25e7777" + ] + } + ], + "meta": { + "nf-test": "0.9.2", + "nextflow": "25.04.3" + }, + "timestamp": "2025-06-18T22:27:04.672171" + }, + "hto_matrix - runEmptyDrops:true - rna_matrix": { + "content": [ + { + "0": [ + + ], + "1": [ + + ], + "2": [ + + ], + "3": [ + + ], + "4": [ + + ], + "5": [ + + ], + "6": [ + + ], + "7": [ + + ], + "empty_drops_csv": [ + + ], + "empty_drops_plot": [ + + ], + "empty_drops_rds": [ + + ], + "params": [ + + ], + "plot": [ + + ], + "rds": [ + + ], + "results": [ + + ], + "versions": [ + + ] + } + ], + "meta": { + "nf-test": "0.9.2", + "nextflow": "25.04.3" + }, + "timestamp": "2025-06-18T22:26:44.861876" + }, + "hto_matrix - runEmptyDrops:false - rna_matrix": { + "content": [ + { + "0": [ + [ + { + "id": "test" + }, + "test_emptyDrops.png:md5,2d7113a76a494b2b2c18b77c4a664063" + ] + ], + "1": [ + [ + { + "id": "test" + }, + "test_emptyDrops.csv:md5,a227d42afbcc590b4e949075cde4a5b6" + ] + ], + "2": [ + [ + { + "id": "test" + }, + "test_emptyDrops.rds:md5,bda2e88b1e3e8c8ea5a22e19e3ab243d" + ] + ], + "3": [ + [ + { + "id": "test" + }, + "test_results_hasheddrops.csv:md5,d0f2af6d9bdb47a80bfbf08a85493d2e" + ] + ], + "4": [ + [ + { + "id": "test" + }, + "test_hasheddrops.rds:md5,31ba0ecae1d94080bccf81cdbaa5afe8" + ] + ], + "5": [ + [ + { + "id": "test" + }, + "test_plot_hasheddrops.png:md5,2d7113a76a494b2b2c18b77c4a664063" + ] + ], + "6": [ + [ + { + "id": "test" + }, + "test_params_hasheddrops.csv:md5,1b120b9ed566629aa8529c4370426d6a" + ] + ], + "7": [ + "versions.yml:md5,82ab6a6b5ac26c697551e18b1526daa5" + ], + "empty_drops_csv": [ + [ + { + "id": "test" + }, + "test_emptyDrops.csv:md5,a227d42afbcc590b4e949075cde4a5b6" + ] + ], + "empty_drops_plot": [ + [ + { + "id": "test" + }, + "test_emptyDrops.png:md5,2d7113a76a494b2b2c18b77c4a664063" + ] + ], + "empty_drops_rds": [ + [ + { + "id": "test" + }, + "test_emptyDrops.rds:md5,bda2e88b1e3e8c8ea5a22e19e3ab243d" + ] + ], + "params": [ + [ + { + "id": "test" + }, + "test_params_hasheddrops.csv:md5,1b120b9ed566629aa8529c4370426d6a" + ] + ], + "plot": [ + [ + { + "id": "test" + }, + "test_plot_hasheddrops.png:md5,2d7113a76a494b2b2c18b77c4a664063" + ] + ], + "rds": [ + [ + { + "id": "test" + }, + "test_hasheddrops.rds:md5,31ba0ecae1d94080bccf81cdbaa5afe8" + ] + ], + "results": [ + [ + { + "id": "test" + }, + "test_results_hasheddrops.csv:md5,d0f2af6d9bdb47a80bfbf08a85493d2e" + ] + ], + "versions": [ + "versions.yml:md5,82ab6a6b5ac26c697551e18b1526daa5" + ] + } + ], + "meta": { + "nf-test": "0.9.2", + "nextflow": "25.04.3" + }, + "timestamp": "2025-06-21T11:50:59.635376247" + } +} \ No newline at end of file diff --git a/modules/nf-core/htodemux/environment.yml b/modules/nf-core/htodemux/environment.yml new file mode 100644 index 00000000..7a40ab60 --- /dev/null +++ b/modules/nf-core/htodemux/environment.yml @@ -0,0 +1,8 @@ +--- +# yaml-language-server: $schema=https://raw.githubusercontent.com/nf-core/modules/master/modules/environment-schema.json +channels: + - conda-forge + - bioconda +dependencies: + - "conda-forge::r-seurat=5.3.0" + - "conda-forge::r-seuratobject=5.1.0" diff --git a/modules/nf-core/htodemux/main.nf b/modules/nf-core/htodemux/main.nf new file mode 100644 index 00000000..58870cae --- /dev/null +++ b/modules/nf-core/htodemux/main.nf @@ -0,0 +1,48 @@ +process HTODEMUX { + tag "$meta.id" + label 'process_low' + + conda "${moduleDir}/environment.yml" + container "${ workflow.containerEngine == 'singularity' && !task.ext.singularity_pull_docker_container ? + 'oras://community.wave.seqera.io/library/r-seurat_r-seuratobject:4c5a804804327d29': + 'community.wave.seqera.io/library/r-seurat_r-seuratobject:b11306d1bdc82827' }" + + input: + tuple val(meta), path(seurat_object), val(assay) + + output: + tuple val(meta), path("*_params_htodemux.csv") , emit: params + tuple val(meta), path("*_assignment_htodemux.csv") , emit: assignment + tuple val(meta), path("*_classification_htodemux.csv") , emit: classification + tuple val(meta), path("*_htodemux.rds") , emit: rds + path "versions.yml" , emit: versions + + when: + task.ext.when == null || task.ext.when + + script: + quantile = task.ext.quantile ?: "0.99" + init = task.ext.init ?: "NULL" + nstarts = task.ext.nstarts ?: "100" + kfunc = task.ext.kfunc ?: "clara" + nsamples = task.ext.nsamples ?: "100" + seed = task.ext.seed ?: '42' + verbose = task.ext.verbose ?: 'TRUE' + prefix = task.ext.prefix ?: "${meta.id}" + + template 'HTODemux.R' + + stub: + def prefix = task.ext.prefix ?: "${meta.id}" + """ + touch ${prefix}_params_htodemux.csv + touch ${prefix}_assignment_htodemux.csv + touch ${prefix}_classification_htodemux.csv + touch ${prefix}_htodemux.rds + + cat <<-END_VERSIONS > versions.yml + "${task.process}": + htodemux: \$(htodemux --version) + END_VERSIONS + """ +} diff --git a/modules/nf-core/htodemux/meta.yml b/modules/nf-core/htodemux/meta.yml new file mode 100644 index 00000000..d50fc845 --- /dev/null +++ b/modules/nf-core/htodemux/meta.yml @@ -0,0 +1,87 @@ +--- +# yaml-language-server: $schema=https://raw.githubusercontent.com/nf-core/modules/master/modules/meta-schema.json +name: "htodemux" +description: Demultiplex samples based on data from cell hashing. +keywords: + - demultiplexing + - hashing-based deconvolution + - single-cell +tools: + - "htodemux": + description: "HTODemux is the demultiplexing module of Seurat, which demultiplex samples based on data from cell hashing." + homepage: "https://satijalab.org/seurat/articles/hashing_vignette" + documentation: "https://satijalab.org/seurat/reference/htodemux" + tool_dev_url: "https://github.com/satijalab/seurat" + doi: "10.1186/s13059-018-1603-1" + licence: ["MIT"] + identifier: "" + +input: + - - meta: + type: map + description: | + Groovy Map containing sample information + e.g. `[ id:'sample1' ]` + - seurat_object: + type: file + description: | + A `.rds` file containing the seurat object. Assumes that the hash tag oligo (HTO) data has been added and normalized. + - assay: + type: string + description: | + Name of the Hashtag assay, usually called "HTO" by default. Use the custom name if the assay has been named differently. +output: + - params: + - meta: + type: map + description: | + Groovy Map containing sample information + e.g. `[ id:'sample1' ]` + - "*_params_htodemux.csv": + type: file + description: The used parameters to call HTODemux in the R-Script. + pattern: "params_htodemux.csv" + + - assignment: + - meta: + type: map + description: | + Groovy Map containing sample information + e.g. `[ id:'sample1' ]` + - "*_assignment_htodemux.csv": + type: file + description: Assignment results. + pattern: "assignment_htodemux.csv" + + - classification: + - meta: + type: map + description: | + Groovy Map containing sample information + e.g. `[ id:'sample1' ]` + - "*_classification_htodemux.csv": + type: file + description: Classification results. + pattern: "classification_htodemux.csv" + + - rds: + - meta: + type: map + description: | + Groovy Map containing sample information + e.g. `[ id:'sample1' ]` + - "*_htodemux.rds": + type: file + description: SeuratObject saved as RDS. + pattern: "htodemux.rds" + + - versions: + - "versions.yml": + type: file + description: File containing software versions + pattern: "versions.yml" + +authors: + - "@LuisHeinzlmeier" +maintainers: + - "@LuisHeinzlmeier" diff --git a/modules/nf-core/htodemux/templates/HTODemux.R b/modules/nf-core/htodemux/templates/HTODemux.R new file mode 100755 index 00000000..cb699d28 --- /dev/null +++ b/modules/nf-core/htodemux/templates/HTODemux.R @@ -0,0 +1,96 @@ +#!/usr/bin/env Rscript + +################################################ +################################################ +## USE PARAMETERS FROM NEXTFLOW ## +################################################ +################################################ + +# cast parameters from nextflow +seuratObj = '$seurat_object' +assay = '$assay' +options(digits=5) +quantile = as.double('$quantile') +init = NULL +if ('$init' != "NULL") { + init = as.integer('$init') +} +nstarts = as.integer('$nstarts') +kfunc = '$kfunc' +nsamples = as.integer('$nsamples') +seed = as.integer('$seed') +verbose = as.logical('$verbose') +prefix = '$prefix' + +# check if the file exists +if (! file.exists(seuratObj)){ + stop(paste0(seuratObj, ' is not a valid file')) +} + +################################################ +################################################ +## Finish loading libraries ## +################################################ +################################################ + +library(Seurat) + +################################################ +################################################ +## Main Process ## +################################################ +################################################ + +# Loading Seurat object +hashtag <- readRDS(seuratObj) + +# Demultiplex cells based on HTO enrichment +hashtag <- HTODemux(hashtag, assay = assay, positive.quantile = quantile, init = init, nstarts = nstarts, kfunc = kfunc, seed = seed, verbose = verbose) + + +################################################ +################################################ +## SAVING RESULTS ## +################################################ +################################################ + +# create a data frame to save the used parameters in a csv file +if (is.null(init)) { + init <- "NULL" +} + +Argument <- c("seuratObject", "quantile", "kfunc", "nstarts", "nsamples", "seed", "init", "assay", "verbose") +Value <- c(seuratObj, quantile, kfunc, nstarts, nsamples, seed, init, assay, verbose) +params <- data.frame(Argument, Value) + +write.csv(params, paste0(prefix ,"_params_htodemux.csv")) + +# create csv files to save the results from HTODemux() +donors <- rownames(hashtag[[assay]]) +assignment <- hashtag[[paste0(assay, "_classification")]] +assignment[[paste0(assay, "_classification")]][!assignment[[paste0(assay, "_classification")]] %in% c(donors, "Negative")] <- "Doublet" +write.csv(assignment, paste0(prefix ,"_assignment_htodemux.csv")) +write.csv(hashtag[[paste0(assay, "_classification.global")]], paste0(prefix ,"_classification_htodemux.csv")) +saveRDS(hashtag, file = paste0(prefix ,"_htodemux.rds")) + +################################################ +################################################ +## VERSIONS FILE ## +################################################ +################################################ + +r.version <- paste(R.version[['major']],R.version[['minor']], sep = ".") +seurat.version <- as.character(packageVersion('Seurat')) + +writeLines( + c( + '"${task.process}":', + paste(' r-base:', r.version), + paste(' seurat:', seurat.version) + ), +'versions.yml') + +################################################ +################################################ +################################################ +################################################ diff --git a/modules/nf-core/htodemux/tests/main.nf.test b/modules/nf-core/htodemux/tests/main.nf.test new file mode 100644 index 00000000..d42f4f83 --- /dev/null +++ b/modules/nf-core/htodemux/tests/main.nf.test @@ -0,0 +1,58 @@ +nextflow_process { + + name "Test Process HTODEMUX" + script "../main.nf" + process "HTODEMUX" + + tag "modules" + tag "modules_nfcore" + tag "htodemux" + + test("seuratObject - rds") { + + when { + process { + """ + input[0] = [ + [ id:'test'], // meta map + file(params.modules_testdata_base_path + '/genomics/homo_sapiens/10xgenomics/cellranger/hashing_demultiplexing/htodemux.rds', checkIfExists: true), + "HTO" + ] + """ + } + } + + then { + assertAll( + { assert process.success }, + { assert snapshot(process.out).match() } + ) + } + + } + + test("seuratObject - rds - stub") { + + options "-stub" + + when { + process { + """ + input[0] = [ + [ id:'test'], // meta map + file(params.modules_testdata_base_path + '/genomics/homo_sapiens/10xgenomics/cellranger/hashing_demultiplexing/htodemux.rds', checkIfExists: true), + "HTO" + ] + """ + } + } + + then { + assertAll( + { assert process.success }, + { assert snapshot(process.out).match() } + ) + } + + } +} diff --git a/modules/nf-core/htodemux/tests/main.nf.test.snap b/modules/nf-core/htodemux/tests/main.nf.test.snap new file mode 100644 index 00000000..85174188 --- /dev/null +++ b/modules/nf-core/htodemux/tests/main.nf.test.snap @@ -0,0 +1,221 @@ +{ + "seuratObject - rds": { + "content": [ + { + "0": [ + [ + { + "id": "test" + }, + "test_params_htodemux.csv:md5,d01208cd0d0b76548cc71de04e76f3d2" + ] + ], + "1": [ + [ + { + "id": "test" + }, + "test_assignment_htodemux.csv:md5,33d72b27c3d0b0f7394af18a18490a5c" + ] + ], + "2": [ + [ + { + "id": "test" + }, + "test_classification_htodemux.csv:md5,886f595f148d2ccdd5a9134b8eb815cb" + ] + ], + "3": [ + [ + { + "id": "test" + }, + "test_htodemux.rds:md5,ec43d2d0217c3a87529f1eb4d0e05ffb" + ] + ], + "4": [ + "versions.yml:md5,11469ff7805ab3e88363f2ececd7f4a3" + ], + "assignment": [ + [ + { + "id": "test" + }, + "test_assignment_htodemux.csv:md5,33d72b27c3d0b0f7394af18a18490a5c" + ] + ], + "classification": [ + [ + { + "id": "test" + }, + "test_classification_htodemux.csv:md5,886f595f148d2ccdd5a9134b8eb815cb" + ] + ], + "params": [ + [ + { + "id": "test" + }, + "test_params_htodemux.csv:md5,d01208cd0d0b76548cc71de04e76f3d2" + ] + ], + "rds": [ + [ + { + "id": "test" + }, + "test_htodemux.rds:md5,ec43d2d0217c3a87529f1eb4d0e05ffb" + ] + ], + "versions": [ + "versions.yml:md5,11469ff7805ab3e88363f2ececd7f4a3" + ] + } + ], + "meta": { + "nf-test": "0.9.2", + "nextflow": "25.04.3" + }, + "timestamp": "2025-06-15T15:42:12.918682" + }, + "sarscov2 - bam": { + "content": [ + { + "0": [ + [ + { + "id": "test" + }, + [ + "test_assignment_htodemux.csv:md5,33d72b27c3d0b0f7394af18a18490a5c", + "test_classification_htodemux.csv:md5,886f595f148d2ccdd5a9134b8eb815cb", + "test_params_htodemux.csv:md5,61065a5ddedda3bcf5a51e9eb5a72eae" + ] + ] + ], + "1": [ + [ + { + "id": "test" + }, + "testhtodemux.rds:md5,ec43d2d0217c3a87529f1eb4d0e05ffb" + ] + ], + "2": [ + "versions.yml:md5,faa097177f5c4efd711eb8dd5701b6d2" + ], + "csv": [ + [ + { + "id": "test" + }, + [ + "test_assignment_htodemux.csv:md5,33d72b27c3d0b0f7394af18a18490a5c", + "test_classification_htodemux.csv:md5,886f595f148d2ccdd5a9134b8eb815cb", + "test_params_htodemux.csv:md5,61065a5ddedda3bcf5a51e9eb5a72eae" + ] + ] + ], + "rds": [ + [ + { + "id": "test" + }, + "testhtodemux.rds:md5,ec43d2d0217c3a87529f1eb4d0e05ffb" + ] + ], + "versions": [ + "versions.yml:md5,faa097177f5c4efd711eb8dd5701b6d2" + ] + } + ], + "meta": { + "nf-test": "0.9.2", + "nextflow": "25.04.3" + }, + "timestamp": "2025-06-14T15:21:00.420534" + }, + "seuratObject - rds - stub": { + "content": [ + { + "0": [ + [ + { + "id": "test" + }, + "test_params_htodemux.csv:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "1": [ + [ + { + "id": "test" + }, + "test_assignment_htodemux.csv:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "2": [ + [ + { + "id": "test" + }, + "test_classification_htodemux.csv:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "3": [ + [ + { + "id": "test" + }, + "test_htodemux.rds:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "4": [ + "versions.yml:md5,6ebabe2043aa21ba7c47cb07abaabba9" + ], + "assignment": [ + [ + { + "id": "test" + }, + "test_assignment_htodemux.csv:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "classification": [ + [ + { + "id": "test" + }, + "test_classification_htodemux.csv:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "params": [ + [ + { + "id": "test" + }, + "test_params_htodemux.csv:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "rds": [ + [ + { + "id": "test" + }, + "test_htodemux.rds:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "versions": [ + "versions.yml:md5,6ebabe2043aa21ba7c47cb07abaabba9" + ] + } + ], + "meta": { + "nf-test": "0.9.2", + "nextflow": "25.04.3" + }, + "timestamp": "2025-06-15T15:40:53.744484" + } +} \ No newline at end of file diff --git a/modules/nf-core/multiseqdemux/environment.yml b/modules/nf-core/multiseqdemux/environment.yml new file mode 100644 index 00000000..7a40ab60 --- /dev/null +++ b/modules/nf-core/multiseqdemux/environment.yml @@ -0,0 +1,8 @@ +--- +# yaml-language-server: $schema=https://raw.githubusercontent.com/nf-core/modules/master/modules/environment-schema.json +channels: + - conda-forge + - bioconda +dependencies: + - "conda-forge::r-seurat=5.3.0" + - "conda-forge::r-seuratobject=5.1.0" diff --git a/modules/nf-core/multiseqdemux/main.nf b/modules/nf-core/multiseqdemux/main.nf new file mode 100644 index 00000000..de8d91ef --- /dev/null +++ b/modules/nf-core/multiseqdemux/main.nf @@ -0,0 +1,47 @@ +process MULTISEQDEMUX { + tag "$meta.id" + label 'process_low' + + conda "${moduleDir}/environment.yml" + container "${ workflow.containerEngine == 'singularity' && !task.ext.singularity_pull_docker_container ? + 'oras://community.wave.seqera.io/library/r-seurat_r-seuratobject:4c5a804804327d29': + 'community.wave.seqera.io/library/r-seurat_r-seuratobject:b11306d1bdc82827' }" + + input: + tuple val(meta), path(seurat_object), val(assay) + + output: + tuple val(meta), path("*_params_multiseqdemux.csv") , emit: params + tuple val(meta), path("*_res_multiseqdemux.csv") , emit: results + tuple val(meta), path("*_multiseqdemux.rds") , emit: rds + path "versions.yml" , emit: versions + + when: + task.ext.when == null || task.ext.when + + script: + quantile = task.ext.assay ?: "0.7" + autoThresh = task.ext.autoThresh ?: "TRUE" + maxiter = task.ext.maxiter ?: "5" + qrangeFrom = task.ext.qrangeFrom ?: "0.1" + qrangeTo = task.ext.qrangeTo ?: "0.9" + qrangeBy = task.ext.qrangeBy ?: "0.05" + verbose = task.ext.verbose ?: 'TRUE' + prefix = task.ext.prefix ?: "${meta.id}" + + template 'MultiSeqDemux.R' + + stub: + def prefix = task.ext.prefix ?: "${meta.id}" + """ + touch ${prefix}_params_multiseqdemux.csv + touch ${prefix}_res_multiseqdemux.csv + touch ${prefix}_multiseqdemux.rds + + cat <<-END_VERSIONS > versions.yml + "${task.process}": + r-seurat: \$(Rscript -e "library(Seurat); cat(as.character(packageVersion('Seurat')))") + r-base: \$(Rscript -e "cat(strsplit(R.version[['version.string']], ' ')[[1]][3])") + END_VERSIONS + """ +} diff --git a/modules/nf-core/multiseqdemux/meta.yml b/modules/nf-core/multiseqdemux/meta.yml new file mode 100644 index 00000000..518f63e3 --- /dev/null +++ b/modules/nf-core/multiseqdemux/meta.yml @@ -0,0 +1,77 @@ +--- +# yaml-language-server: $schema=https://raw.githubusercontent.com/nf-core/modules/master/modules/meta-schema.json +name: "multiseqdemux" +description: Identify singlets, doublets and negative cells from multiplexing experiments. Annotate singlets by tags. +keywords: + - demultiplexing + - hashing-based deconvolution + - single-cell +tools: + - "multiseqdemux": + description: "MULTIseqDemux is the demultiplexing module of Seurat, which demultiplex samples based on data from cell hashing." + homepage: "https://satijalab.org/seurat/reference/multiseqdemux" + documentation: "https://satijalab.org/seurat/reference/multiseqdemux" + tool_dev_url: "https://github.com/satijalab/seurat" + doi: "10.1038/s41592-019-0433-8" + licence: ["MIT"] + identifier: "" + +input: + - - meta: + type: map + description: | + Groovy Map containing sample information + e.g. `[ id:'sample1' ]` + - seurat_object: + type: file + description: | + A `.rds` file containing the seurat object. Assumes that the hash tag oligo (HTO) data has been added and normalized. + - assay: + type: string + description: | + Name of the Hashtag assay, usually called "HTO" by default. Use the custom name if the assay has been named differently. + +output: + - params: + - meta: + type: map + description: | + Groovy Map containing sample information + e.g. `[ id:'sample1' ]` + - "*_params_multiseqdemux.csv": + type: file + description: The used parameters to call MULTIseqDemux in the R-Script. + pattern: "_params_multiseqdemux.csv" + + - results: + - meta: + type: map + description: | + Groovy Map containing sample information + e.g. `[ id:'sample1' ]` + - "*_res_multiseqdemux.csv": + type: file + description: Resuls of MULTIseqDemux. + pattern: "_res_multiseqdemux.csv" + + - rds: + - meta: + type: map + description: | + Groovy Map containing sample information + e.g. `[ id:'sample1' ]` + - "*_multiseqdemux.rds": + type: file + description: SeuratObject saved as RDS. + pattern: "_multiseqdemux.rds" + + - versions: + - "versions.yml": + type: file + description: File containing software versions + pattern: "versions.yml" + +authors: + - "@LuisHeinzlmeier" +maintainers: + - "@LuisHeinzlmeier" diff --git a/modules/nf-core/multiseqdemux/multiseqdemux.diff b/modules/nf-core/multiseqdemux/multiseqdemux.diff new file mode 100644 index 00000000..7cea8040 --- /dev/null +++ b/modules/nf-core/multiseqdemux/multiseqdemux.diff @@ -0,0 +1,19 @@ +Changes in component 'nf-core/multiseqdemux' +'modules/nf-core/multiseqdemux/main.nf' is unchanged +'modules/nf-core/multiseqdemux/environment.yml' is unchanged +Changes in 'multiseqdemux/meta.yml': +--- modules/nf-core/multiseqdemux/meta.yml ++++ modules/nf-core/multiseqdemux/meta.yml +@@ -32,7 +32,6 @@ + Name of the Hashtag assay, usually called "HTO" by default. Use the custom name if the assay has been named differently. + + output: +- + - params: + - meta: + type: map + +'modules/nf-core/multiseqdemux/templates/MultiSeqDemux.R' is unchanged +'modules/nf-core/multiseqdemux/tests/main.nf.test.snap' is unchanged +'modules/nf-core/multiseqdemux/tests/main.nf.test' is unchanged +************************************************************ diff --git a/modules/nf-core/multiseqdemux/templates/MultiSeqDemux.R b/modules/nf-core/multiseqdemux/templates/MultiSeqDemux.R new file mode 100755 index 00000000..8cd14dc9 --- /dev/null +++ b/modules/nf-core/multiseqdemux/templates/MultiSeqDemux.R @@ -0,0 +1,86 @@ +#!/usr/bin/env Rscript + +################################################ +################################################ +## USE PARAMETERS FROM NEXTFLOW ## +################################################ +################################################ + +# cast parameters from nextflow +seuratObj = '$seurat_object' +options(digits=5) +quantile = as.double('$quantile') +autoThresh = as.logical('$autoThresh') +maxiter = as.integer('$maxiter') +qrangeFrom = as.double('$qrangeFrom') +qrangeTo = as.double('$qrangeTo') +qrangeBy = as.double('$qrangeBy') +verbose = as.logical('$verbose') +assay ='$assay' +prefix = '$prefix' + +if (! file.exists(seuratObj)){ + stop(paste0(seuratObj, ' is not a valid file')) +} + +################################################ +################################################ +## Finish loading libraries ## +################################################ +################################################ + +library(Seurat) + +################################################ +################################################ +## Main Process ## +################################################ +################################################ + +# Loading Seurat object +hashtag <- readRDS(seuratObj) + +# Demultiplex cells +if (autoThresh == TRUE) { + hashtag <- MULTIseqDemux(hashtag, assay = assay, quantile = quantile, autoThresh = TRUE, maxiter = maxiter, qrange = seq(from = qrangeFrom, to = qrangeTo, by = qrangeBy), verbose = verbose) +} else { + hashtag <- MULTIseqDemux(hashtag, assay = assay, quantile = quantile, verbose = verbose) +} + +################################################ +################################################ +## SAVING RESULTS ## +################################################ +################################################ + +# create a data frame to save the used parameters in a csv file +Argument <- c("seuratObjectPath", "quantile", "autoThresh", "maxiter", "qrangeFrom", "qrangeTo", "qrangeBy", "verbose", "assay") +Value <- c(seuratObj, quantile, autoThresh, maxiter, qrangeFrom, qrangeTo, qrangeBy, verbose, assay) +params <- data.frame(Argument, Value) +write.csv(params, paste0(prefix ,"_params_multiseqdemux.csv")) + +# save the results from MULTIseqDemux() +write.csv(hashtag\$MULTI_ID, paste0(prefix , "_res_multiseqdemux.csv")) +saveRDS(hashtag, file = paste0(prefix ,"_multiseqdemux.rds")) + +################################################ +################################################ +## VERSIONS FILE ## +################################################ +################################################ + +r.version <- paste(R.version[['major']],R.version[['minor']], sep = ".") +seurat.version <- as.character(packageVersion('Seurat')) + +writeLines( + c( + '"${task.process}":', + paste(' r-base:', r.version), + paste(' r-seurat:', seurat.version) + ), +'versions.yml') + +################################################ +################################################ +################################################ +################################################ diff --git a/modules/nf-core/multiseqdemux/tests/main.nf.test b/modules/nf-core/multiseqdemux/tests/main.nf.test new file mode 100644 index 00000000..4ab4ea53 --- /dev/null +++ b/modules/nf-core/multiseqdemux/tests/main.nf.test @@ -0,0 +1,59 @@ +nextflow_process { + + name "Test Process MULTISEQDEMUX" + script "../main.nf" + process "MULTISEQDEMUX" + + tag "modules" + tag "modules_nfcore" + tag "multiseqdemux" + + test("seuratObject - rds") { + + when { + process { + """ + input[0] = [ + [ id:'test'], // meta map + file(params.modules_testdata_base_path + '/genomics/homo_sapiens/10xgenomics/cellranger/hashing_demultiplexing/htodemux.rds', checkIfExists: true), + "HTO" + ] + """ + } + } + + then { + assertAll( + { assert process.success }, + { assert snapshot(process.out).match() } + ) + } + + } + + test("seuratObject - rds - stub") { + + options "-stub" + + when { + process { + """ + input[0] = [ + [ id:'test'], // meta map + file(params.modules_testdata_base_path + '/genomics/homo_sapiens/10xgenomics/cellranger/hashing_demultiplexing/htodemux.rds', checkIfExists: true), + "HTO" + ] + """ + } + } + + then { + assertAll( + { assert process.success }, + { assert snapshot(process.out).match() } + ) + } + + } + +} diff --git a/modules/nf-core/multiseqdemux/tests/main.nf.test.snap b/modules/nf-core/multiseqdemux/tests/main.nf.test.snap new file mode 100644 index 00000000..29e56e27 --- /dev/null +++ b/modules/nf-core/multiseqdemux/tests/main.nf.test.snap @@ -0,0 +1,132 @@ +{ + "seuratObject - rds": { + "content": [ + { + "0": [ + [ + { + "id": "test" + }, + "test_params_multiseqdemux.csv:md5,03b37983ec6c4e9eabb789a81e936a4e" + ] + ], + "1": [ + [ + { + "id": "test" + }, + "test_res_multiseqdemux.csv:md5,9d4475b8c2778e7e361747dd521072e4" + ] + ], + "2": [ + [ + { + "id": "test" + }, + "test_multiseqdemux.rds:md5,f9b34865c0845ffaa1e0b0447adda202" + ] + ], + "3": [ + "versions.yml:md5,010faafd9d09f3a732eca24e43b9abf7" + ], + "params": [ + [ + { + "id": "test" + }, + "test_params_multiseqdemux.csv:md5,03b37983ec6c4e9eabb789a81e936a4e" + ] + ], + "rds": [ + [ + { + "id": "test" + }, + "test_multiseqdemux.rds:md5,f9b34865c0845ffaa1e0b0447adda202" + ] + ], + "results": [ + [ + { + "id": "test" + }, + "test_res_multiseqdemux.csv:md5,9d4475b8c2778e7e361747dd521072e4" + ] + ], + "versions": [ + "versions.yml:md5,010faafd9d09f3a732eca24e43b9abf7" + ] + } + ], + "meta": { + "nf-test": "0.9.2", + "nextflow": "25.04.3" + }, + "timestamp": "2025-06-15T16:33:22.37618" + }, + "seuratObject - rds - stub": { + "content": [ + { + "0": [ + [ + { + "id": "test" + }, + "test_params_multiseqdemux.csv:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "1": [ + [ + { + "id": "test" + }, + "test_res_multiseqdemux.csv:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "2": [ + [ + { + "id": "test" + }, + "test_multiseqdemux.rds:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "3": [ + "versions.yml:md5,f9d8b4d613b9361323afb1ba5f0c5528" + ], + "params": [ + [ + { + "id": "test" + }, + "test_params_multiseqdemux.csv:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "rds": [ + [ + { + "id": "test" + }, + "test_multiseqdemux.rds:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "results": [ + [ + { + "id": "test" + }, + "test_res_multiseqdemux.csv:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "versions": [ + "versions.yml:md5,f9d8b4d613b9361323afb1ba5f0c5528" + ] + } + ], + "meta": { + "nf-test": "0.9.2", + "nextflow": "25.04.3" + }, + "timestamp": "2025-06-16T12:07:37.705758" + } +} \ No newline at end of file diff --git a/modules/nf-core/popscle/demuxlet/popscle-demuxlet.diff b/modules/nf-core/popscle/demuxlet/popscle-demuxlet.diff new file mode 100644 index 00000000..7a1f9427 --- /dev/null +++ b/modules/nf-core/popscle/demuxlet/popscle-demuxlet.diff @@ -0,0 +1,39 @@ +Changes in component 'nf-core/popscle/demuxlet' +Changes in 'popscle/demuxlet/main.nf': +--- modules/nf-core/popscle/demuxlet/main.nf ++++ modules/nf-core/popscle/demuxlet/main.nf +@@ -8,7 +8,7 @@ + 'biocontainers/popscle:0.1beta--h2c78cec_0' }" + + input: +- tuple val(meta), val(plp_prefix), path(bam), path(donor_genotype) ++ tuple val(meta), val(plp), path(bam), path(donor_genotype) + + output: + tuple val(meta), path('*.best'), emit: demuxlet_result +@@ -20,7 +20,7 @@ + script: + def args = task.ext.args ?: '' + def prefix = task.ext.prefix ?: "${meta.id}" +- def input = plp_prefix ? "--plp ${plp_prefix}" : "--sam $bam" ++ def input = plp ? "--plp ${plp.toString() - '.plp.gz'}" : "--sam $bam" + def VERSION = '0.1' // WARN: Version information not provided by tool on CLI. Please update version string below when bumping container versions. + + """ +@@ -37,9 +37,7 @@ + """ + + stub: +- def args = task.ext.args ?: '' + def prefix = task.ext.prefix ?: "${meta.id}" +- def input = plp_prefix ? "--plp ${plp_prefix}" : "--sam $bam" + def VERSION = '0.1' // WARN: Version information not provided by tool on CLI. Please update version string below when bumping container versions. + """ + touch ${prefix}.best + +'modules/nf-core/popscle/demuxlet/environment.yml' is unchanged +'modules/nf-core/popscle/demuxlet/meta.yml' is unchanged +'modules/nf-core/popscle/demuxlet/tests/main.nf.test.snap' is unchanged +'modules/nf-core/popscle/demuxlet/tests/nextflow.config' is unchanged +'modules/nf-core/popscle/demuxlet/tests/main.nf.test' is unchanged +************************************************************ diff --git a/modules/nf-core/popscle/dscpileup/popscle-dscpileup.diff b/modules/nf-core/popscle/dscpileup/popscle-dscpileup.diff new file mode 100644 index 00000000..989cde17 --- /dev/null +++ b/modules/nf-core/popscle/dscpileup/popscle-dscpileup.diff @@ -0,0 +1,97 @@ +Changes in component 'nf-core/popscle/dscpileup' +Changes in 'popscle/dscpileup/main.nf': +--- modules/nf-core/popscle/dscpileup/main.nf ++++ modules/nf-core/popscle/dscpileup/main.nf +@@ -1,57 +1,61 @@ + process POPSCLE_DSCPILEUP { +- tag "$meta.id" ++ tag "${meta.id}" + label 'process_medium' + + conda "${moduleDir}/environment.yml" +- container "${ workflow.containerEngine == 'singularity' && !task.ext.singularity_pull_docker_container ? +- 'https://depot.galaxyproject.org/singularity/popscle:0.1beta--h2c78cec_0' : +- 'biocontainers/popscle:0.1beta--h2c78cec_0' }" ++ container "${workflow.containerEngine == 'singularity' && !task.ext.singularity_pull_docker_container ++ ? 'https://depot.galaxyproject.org/singularity/popscle:0.1beta--h2c78cec_0' ++ : 'biocontainers/popscle:0.1beta--h2c78cec_0'}" + + input: + tuple val(meta), path(bam), path(vcf) + + output: +- tuple val(meta), path('*.cel.gz'), emit: cel +- tuple val(meta), path('*.plp.gz'), emit: plp +- tuple val(meta), path('*.var.gz'), emit: var +- tuple val(meta), path('*.umi.gz'), emit: umi +- path 'versions.yml' , emit: versions ++ tuple val(meta), path("${prefix}"), emit: directory ++ tuple val(meta), path("${prefix}/*.cel.gz"), emit: cel ++ tuple val(meta), path("${prefix}/*.plp.gz"), emit: plp ++ tuple val(meta), path("${prefix}/*.var.gz"), emit: var ++ tuple val(meta), path("${prefix}/*.umi.gz"), emit: umi ++ path 'versions.yml', emit: versions + + when: + task.ext.when == null || task.ext.when + + script: +- def args = task.ext.args ?: '' +- def prefix = task.ext.prefix ?: "${meta.id}" +- def VERSION = '0.1' // WARN: Version information not provided by tool on CLI. Please update version string below when bumping container versions. ++ def args = task.ext.args ?: '' ++ prefix = task.ext.prefix ?: "${meta.id}" ++ def VERSION = '0.1' ++ // WARN: Version information not provided by tool on CLI. Please update version string below when bumping container versions. + + """ ++ mkdir -p "${prefix}" + popscle dsc-pileup \\ +- --sam $bam \\ +- --vcf $vcf \\ +- --out $prefix \\ +- $args \\ ++ --sam ${bam} \\ ++ --vcf ${vcf} \\ ++ --out ${prefix}/${prefix} \\ ++ ${args} \\ + + cat <<-END_VERSIONS > versions.yml + "${task.process}": +- popscle dsc-pileup: $VERSION ++ popscle dsc-pileup: ${VERSION} + END_VERSIONS + """ + + stub: +- def args = task.ext.args ?: '' +- def prefix = task.ext.prefix ?: "${meta.id}" +- def VERSION = '0.1' // WARN: Version information not provided by tool on CLI. Please update version string below when bumping container versions. ++ prefix = task.ext.prefix ?: "${meta.id}" ++ def VERSION = '0.1' ++ // WARN: Version information not provided by tool on CLI. Please update version string below when bumping container versions. + + """ +- touch ${prefix}.cel.gz +- touch ${prefix}.var.gz +- touch ${prefix}.plp.gz +- touch ${prefix}.umi.gz ++ mkdir -p ${prefix} ++ touch ${prefix}/${prefix}.cel.gz ++ touch ${prefix}/${prefix}.var.gz ++ touch ${prefix}/${prefix}.plp.gz ++ touch ${prefix}/${prefix}.umi.gz + + cat <<-END_VERSIONS > versions.yml + "${task.process}": +- popscle dsc-pileup: $VERSION ++ popscle dsc-pileup: ${VERSION} + END_VERSIONS + """ + } + +'modules/nf-core/popscle/dscpileup/environment.yml' is unchanged +'modules/nf-core/popscle/dscpileup/meta.yml' is unchanged +'modules/nf-core/popscle/dscpileup/tests/main.nf.test.snap' is unchanged +'modules/nf-core/popscle/dscpileup/tests/main.nf.test' is unchanged +************************************************************ diff --git a/modules/nf-core/untar/environment.yml b/modules/nf-core/untar/environment.yml new file mode 100644 index 00000000..9b926b1f --- /dev/null +++ b/modules/nf-core/untar/environment.yml @@ -0,0 +1,12 @@ +--- +# yaml-language-server: $schema=https://raw.githubusercontent.com/nf-core/modules/master/modules/environment-schema.json +channels: + - conda-forge + - bioconda +dependencies: + - conda-forge::coreutils=9.5 + - conda-forge::grep=3.11 + - conda-forge::gzip=1.13 + - conda-forge::lbzip2=2.5 + - conda-forge::sed=4.8 + - conda-forge::tar=1.34 diff --git a/modules/nf-core/untar/main.nf b/modules/nf-core/untar/main.nf new file mode 100644 index 00000000..e712ebe6 --- /dev/null +++ b/modules/nf-core/untar/main.nf @@ -0,0 +1,84 @@ +process UNTAR { + tag "${archive}" + label 'process_single' + + conda "${moduleDir}/environment.yml" + container "${workflow.containerEngine == 'singularity' && !task.ext.singularity_pull_docker_container + ? 'https://community-cr-prod.seqera.io/docker/registry/v2/blobs/sha256/52/52ccce28d2ab928ab862e25aae26314d69c8e38bd41ca9431c67ef05221348aa/data' + : 'community.wave.seqera.io/library/coreutils_grep_gzip_lbzip2_pruned:838ba80435a629f8'}" + + input: + tuple val(meta), path(archive) + + output: + tuple val(meta), path("${prefix}"), emit: untar + path "versions.yml", emit: versions + + when: + task.ext.when == null || task.ext.when + + script: + def args = task.ext.args ?: '' + def args2 = task.ext.args2 ?: '' + prefix = task.ext.prefix ?: (meta.id ? "${meta.id}" : archive.baseName.toString().replaceFirst(/\.tar$/, "")) + + """ + mkdir ${prefix} + + ## Ensures --strip-components only applied when top level of tar contents is a directory + ## If just files or multiple directories, place all in prefix + if [[ \$(tar -taf ${archive} | grep -o -P "^.*?\\/" | uniq | wc -l) -eq 1 ]]; then + tar \\ + -C ${prefix} --strip-components 1 \\ + -xavf \\ + ${args} \\ + ${archive} \\ + ${args2} + else + tar \\ + -C ${prefix} \\ + -xavf \\ + ${args} \\ + ${archive} \\ + ${args2} + fi + + cat <<-END_VERSIONS > versions.yml + "${task.process}": + untar: \$(echo \$(tar --version 2>&1) | sed 's/^.*(GNU tar) //; s/ Copyright.*\$//') + END_VERSIONS + """ + + stub: + prefix = task.ext.prefix ?: (meta.id ? "${meta.id}" : archive.toString().replaceFirst(/\.[^\.]+(.gz)?$/, "")) + """ + mkdir ${prefix} + ## Dry-run untaring the archive to get the files and place all in prefix + if [[ \$(tar -taf ${archive} | grep -o -P "^.*?\\/" | uniq | wc -l) -eq 1 ]]; then + for i in `tar -tf ${archive}`; + do + if [[ \$(echo "\${i}" | grep -E "/\$") == "" ]]; + then + touch \${i} + else + mkdir -p \${i} + fi + done + else + for i in `tar -tf ${archive}`; + do + if [[ \$(echo "\${i}" | grep -E "/\$") == "" ]]; + then + touch ${prefix}/\${i} + else + mkdir -p ${prefix}/\${i} + fi + done + fi + + cat <<-END_VERSIONS > versions.yml + "${task.process}": + untar: \$(echo \$(tar --version 2>&1) | sed 's/^.*(GNU tar) //; s/ Copyright.*\$//') + END_VERSIONS + """ +} diff --git a/modules/nf-core/untar/meta.yml b/modules/nf-core/untar/meta.yml new file mode 100644 index 00000000..3a37bb35 --- /dev/null +++ b/modules/nf-core/untar/meta.yml @@ -0,0 +1,52 @@ +name: untar +description: Extract files. +keywords: + - untar + - uncompress + - extract +tools: + - untar: + description: | + Extract tar.gz files. + documentation: https://www.gnu.org/software/tar/manual/ + licence: ["GPL-3.0-or-later"] + identifier: "" +input: + - - meta: + type: map + description: | + Groovy Map containing sample information + e.g. [ id:'test', single_end:false ] + - archive: + type: file + description: File to be untar + pattern: "*.{tar}.{gz}" +output: + - untar: + - meta: + type: map + description: | + Groovy Map containing sample information + e.g. [ id:'test', single_end:false ] + pattern: "*/" + - ${prefix}: + type: map + description: | + Groovy Map containing sample information + e.g. [ id:'test', single_end:false ] + pattern: "*/" + - versions: + - versions.yml: + type: file + description: File containing software versions + pattern: "versions.yml" +authors: + - "@joseespinosa" + - "@drpatelh" + - "@matthdsm" + - "@jfy133" +maintainers: + - "@joseespinosa" + - "@drpatelh" + - "@matthdsm" + - "@jfy133" diff --git a/modules/nf-core/untar/tests/main.nf.test b/modules/nf-core/untar/tests/main.nf.test new file mode 100644 index 00000000..c957517a --- /dev/null +++ b/modules/nf-core/untar/tests/main.nf.test @@ -0,0 +1,85 @@ +nextflow_process { + + name "Test Process UNTAR" + script "../main.nf" + process "UNTAR" + tag "modules" + tag "modules_nfcore" + tag "untar" + + test("test_untar") { + + when { + process { + """ + input[0] = [ [], file(params.modules_testdata_base_path + 'genomics/sarscov2/genome/db/kraken2.tar.gz', checkIfExists: true) ] + """ + } + } + + then { + assertAll ( + { assert process.success }, + { assert snapshot(process.out).match() }, + ) + } + } + + test("test_untar_onlyfiles") { + + when { + process { + """ + input[0] = [ [], file(params.modules_testdata_base_path + 'generic/tar/hello.tar.gz', checkIfExists: true) ] + """ + } + } + + then { + assertAll ( + { assert process.success }, + { assert snapshot(process.out).match() }, + ) + } + } + + test("test_untar - stub") { + + options "-stub" + + when { + process { + """ + input[0] = [ [], file(params.modules_testdata_base_path + 'genomics/sarscov2/genome/db/kraken2.tar.gz', checkIfExists: true) ] + """ + } + } + + then { + assertAll ( + { assert process.success }, + { assert snapshot(process.out).match() }, + ) + } + } + + test("test_untar_onlyfiles - stub") { + + options "-stub" + + when { + process { + """ + input[0] = [ [], file(params.modules_testdata_base_path + 'generic/tar/hello.tar.gz', checkIfExists: true) ] + """ + } + } + + then { + assertAll ( + { assert process.success }, + { assert snapshot(process.out).match() }, + ) + } + } +} diff --git a/modules/nf-core/untar/tests/main.nf.test.snap b/modules/nf-core/untar/tests/main.nf.test.snap new file mode 100644 index 00000000..ceb91b79 --- /dev/null +++ b/modules/nf-core/untar/tests/main.nf.test.snap @@ -0,0 +1,158 @@ +{ + "test_untar_onlyfiles": { + "content": [ + { + "0": [ + [ + [ + + ], + [ + "hello.txt:md5,e59ff97941044f85df5297e1c302d260" + ] + ] + ], + "1": [ + "versions.yml:md5,6063247258c56fd271d076bb04dd7536" + ], + "untar": [ + [ + [ + + ], + [ + "hello.txt:md5,e59ff97941044f85df5297e1c302d260" + ] + ] + ], + "versions": [ + "versions.yml:md5,6063247258c56fd271d076bb04dd7536" + ] + } + ], + "meta": { + "nf-test": "0.8.4", + "nextflow": "24.04.3" + }, + "timestamp": "2024-07-10T12:04:28.231047" + }, + "test_untar_onlyfiles - stub": { + "content": [ + { + "0": [ + [ + [ + + ], + [ + "hello.txt:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ] + ], + "1": [ + "versions.yml:md5,6063247258c56fd271d076bb04dd7536" + ], + "untar": [ + [ + [ + + ], + [ + "hello.txt:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ] + ], + "versions": [ + "versions.yml:md5,6063247258c56fd271d076bb04dd7536" + ] + } + ], + "meta": { + "nf-test": "0.8.4", + "nextflow": "24.04.3" + }, + "timestamp": "2024-07-10T12:04:45.773103" + }, + "test_untar - stub": { + "content": [ + { + "0": [ + [ + [ + + ], + [ + "hash.k2d:md5,d41d8cd98f00b204e9800998ecf8427e", + "opts.k2d:md5,d41d8cd98f00b204e9800998ecf8427e", + "taxo.k2d:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ] + ], + "1": [ + "versions.yml:md5,6063247258c56fd271d076bb04dd7536" + ], + "untar": [ + [ + [ + + ], + [ + "hash.k2d:md5,d41d8cd98f00b204e9800998ecf8427e", + "opts.k2d:md5,d41d8cd98f00b204e9800998ecf8427e", + "taxo.k2d:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ] + ], + "versions": [ + "versions.yml:md5,6063247258c56fd271d076bb04dd7536" + ] + } + ], + "meta": { + "nf-test": "0.8.4", + "nextflow": "24.04.3" + }, + "timestamp": "2024-07-10T12:04:36.777441" + }, + "test_untar": { + "content": [ + { + "0": [ + [ + [ + + ], + [ + "hash.k2d:md5,8b8598468f54a7087c203ad0190555d9", + "opts.k2d:md5,a033d00cf6759407010b21700938f543", + "taxo.k2d:md5,094d5891cdccf2f1468088855c214b2c" + ] + ] + ], + "1": [ + "versions.yml:md5,6063247258c56fd271d076bb04dd7536" + ], + "untar": [ + [ + [ + + ], + [ + "hash.k2d:md5,8b8598468f54a7087c203ad0190555d9", + "opts.k2d:md5,a033d00cf6759407010b21700938f543", + "taxo.k2d:md5,094d5891cdccf2f1468088855c214b2c" + ] + ] + ], + "versions": [ + "versions.yml:md5,6063247258c56fd271d076bb04dd7536" + ] + } + ], + "meta": { + "nf-test": "0.8.4", + "nextflow": "24.04.3" + }, + "timestamp": "2024-07-10T12:04:19.377674" + } +} \ No newline at end of file diff --git a/nextflow.config b/nextflow.config index 42cce4b1..716bb0ee 100644 --- a/nextflow.config +++ b/nextflow.config @@ -11,122 +11,189 @@ params { // TODO nf-core: Specify your pipeline's command line flags // Input options - input = null - mode = 'rescue' - match_donor = false - hash_tools = 'gmm-demux' - genetic_tools = 'vireo' - bam_qc = true - common_variants = null - save_intermediates = false + input = null + mode = 'rescue' + match_donor = false + hash_tools = 'gmm-demux' + genetic_tools = 'vireo' + bam_qc = true + common_variants = null + save_intermediates = false // References - genome = null - igenomes_base = 's3://ngi-igenomes/igenomes/' - igenomes_ignore = false + genome = null + igenomes_base = 's3://ngi-igenomes/igenomes/' + igenomes_ignore = false // demuxEM - demuxem_alpha_on_samples = 0.0 - demuxem_min_num_genes = 100 - demuxem_min_num_umis = 100 - demuxem_min_signal_hashtag = 10 - demuxem_random_state = 0 - demuxem_gender_genes = "" + demuxem_alpha_on_samples = 0.0 + demuxem_min_num_genes = 100 + demuxem_min_num_umis = 100 + demuxem_min_signal_hashtag = 10 + demuxem_random_state = 0 + demuxem_gender_genes = "" + + // Preprocessing for HTODemux and MultiSeq + preprocessing_sel_method = "mean.var.plot" + preprocessing_ndelim = "_" + preprocessing_n_features = 2000 + preprocessing_assay = "HTO" + preprocessing_margin = 2 + preprocessing_norm_method = "CLR" + preprocessing_gene_col = 2 // GMM-Demux - gmmdemux_extract = null - gmmdemux_threshold = 0.8 - gmmdemux_random_state = 0 + gmmdemux_extract = null + gmmdemux_threshold = 0.8 + gmmdemux_random_state = 0 + + // HTODemux + htodemux_quantile = 0.99 + htodemux_init = "NULL" + htodemux_nstarts = 100 + htodemux_kfunc = "clara" + htodemux_nsamples = 100 + htodemux_seed = 42 + htodemux_verbose = true + + // HTODemux Visualization + htodemux_visualization_ridgePlot = true + htodemux_visualization_ridgeNCol = 2 + htodemux_visualization_featureScatter = true + htodemux_visualization_scatterFeat1 = null + htodemux_visualization_scatterFeat2 = null + htodemux_visualization_vlnPlot = true + htodemux_visualization_vlnFeatures = "nCount_RNA" + htodemux_visualization_vlnLog = true + htodemux_visualization_tSNE = true + htodemux_visualization_tSNEIdents = "Negative" + htodemux_visualization_tSNEInvert = true + htodemux_visualization_tSNEVerbose = false + htodemux_visualization_tSNEApprox = false + htodemux_visualization_tSNEDimMax = 2 + htodemux_visualization_tSNEPerplexity = 100 + htodemux_visualization_heatMap = true + htodemux_visualization_heatMapNcells = 500 + + // MultiSeqDemux + multiseqdemux_quantile = 0.7 + multiseqdemux_autoThresh = true + multiseqdemux_maxiter = 5 + multiseqdemux_qrangeFrom = 0.1 + multiseqdemux_qrangeTo = 0.9 + multiseqdemux_qrangeBy = 0.05 + multiseqdemux_verbose = true + + // HashedDrops + hasheddrops_lower = 100 + hasheddrops_niters = 10000 + hasheddrops_testAmbient = "TRUE" + hasheddrops_round = "TRUE" + hasheddrops_byRank = "NULL" + hasheddrops_isCellFDR = 0.01 + hasheddrops_gene_col = 2 + hasheddrops_ignore = "NULL" + hasheddrops_alpha = "NULL" + hasheddrops_ambient = "TRUE" + hasheddrops_minProp = 0.05 + hasheddrops_pseudoCount = 5 + hasheddrops_constantAmbient = "FALSE" + hasheddrops_doubletNmads = 3 + hasheddrops_doubletMin = 2 + hasheddrops_doubletMixture = "FALSE" + hasheddrops_confidentNmads = 3 + hasheddrops_confidentMin = 2 + hasheddrops_combinations = "NULL" // CellSNP - cellsnp_celltag = 'CB' - cellsnp_umitag = 'Auto' - cellsnp_mincount = 20 - cellsnp_minmaf = 0.0 - cellsnp_inclflag = "" - cellsnp_exclflag = "" - cellsnp_minlen = 30 - cellsnp_minmapq = 20 - cellsnp_maxdepth = 0 - cellsnp_countorphan = false + cellsnp_celltag = 'CB' + cellsnp_umitag = 'Auto' + cellsnp_mincount = 20 + cellsnp_minmaf = 0.0 + cellsnp_inclflag = "" + cellsnp_exclflag = "" + cellsnp_minlen = 30 + cellsnp_minmapq = 20 + cellsnp_maxdepth = 0 + cellsnp_countorphan = false // Vireo - vireo_genotag = 'GT' - vireo_no_doublet = false - vireo_n_init = 50 - vireo_extra_donor = 0 - vireo_extra_donor_mode = 'distance' - vireo_force_learn_gt = false - vireo_ase_mode = false - vireo_no_plot = false - vireo_rand_seed = 0 - vireo_cell_range = 'all' - vireo_cell_ambient_rnas = false + vireo_genotag = 'GT' + vireo_no_doublet = false + vireo_n_init = 50 + vireo_extra_donor = 0 + vireo_extra_donor_mode = 'distance' + vireo_force_learn_gt = false + vireo_ase_mode = false + vireo_no_plot = false + vireo_rand_seed = 0 + vireo_cell_range = 'all' + vireo_cell_ambient_rnas = false // DSC-Pileup - dsc_pileup_tag_group = 'CB' - dsc_pileup_tag_umi = 'UB' - dsc_pileup_cap_bq = 40 - dsc_pileup_min_bq = 13 - dsc_pileup_min_mq = 20 - dsc_pileup_min_td = 0 - dsc_pileup_excl_flag = 3844 - dsc_pileup_min_total = 0 - dsc_pileup_min_uniq = 0 - dsc_pileup_min_snp = 0 + dsc_pileup_tag_group = 'CB' + dsc_pileup_tag_umi = 'UB' + dsc_pileup_cap_bq = 40 + dsc_pileup_min_bq = 13 + dsc_pileup_min_mq = 20 + dsc_pileup_min_td = 0 + dsc_pileup_excl_flag = 3844 + dsc_pileup_min_total = 0 + dsc_pileup_min_uniq = 0 + dsc_pileup_min_snp = 0 // Demuxlet - demuxlet_field = 'GT' - demuxlet_geno_error_offset = 0.1 - demuxlet_geno_error_coeff = 0.0 - demuxlet_r2_info = 'R2' - demuxlet_min_mac = 1 - demuxlet_min_callrate = 0.50 - demuxlet_alpha = '0.1,0.2,0.3,0.4,0.5' - demuxlet_doublet_prior = 0.5 + demuxlet_field = 'GT' + demuxlet_geno_error_offset = 0.1 + demuxlet_geno_error_coeff = 0.0 + demuxlet_r2_info = 'R2' + demuxlet_min_mac = 1 + demuxlet_min_callrate = 0.50 + demuxlet_alpha = '0.1,0.2,0.3,0.4,0.5' + demuxlet_doublet_prior = 0.5 // Freemuxlet - freemuxlet_doublet_prior = 0.5 - freemuxlet_geno_error = 0.1 - freemuxlet_bf_thres = 5.41 - freemuxlet_frac_init_clust = 1.0 - freemuxlet_iter_init = 10 - freemuxlet_keep_init_missing = false - freemuxlet_randomize_singlet_score = false - freemuxlet_seed = 0 + freemuxlet_doublet_prior = 0.5 + freemuxlet_geno_error = 0.1 + freemuxlet_bf_thres = 5.41 + freemuxlet_frac_init_clust = 1.0 + freemuxlet_iter_init = 10 + freemuxlet_keep_init_missing = false + freemuxlet_randomize_singlet_score = false + freemuxlet_seed = 0 // MultiQC options - multiqc_config = null - multiqc_title = null - multiqc_logo = null - max_multiqc_email_size = '25.MB' - multiqc_methods_description = null + multiqc_config = null + multiqc_title = null + multiqc_logo = null + max_multiqc_email_size = '25.MB' + multiqc_methods_description = null // Boilerplate options - outdir = null - publish_dir_mode = 'copy' - email = null - email_on_fail = null - plaintext_email = false - monochrome_logs = false - hook_url = null - help = false - help_full = false - show_hidden = false - version = false - pipelines_testdata_base_path = 'https://raw.githubusercontent.com/nf-core/test-datasets/' - trace_report_suffix = new java.util.Date().format('yyyy-MM-dd_HH-mm-ss') + outdir = null + publish_dir_mode = 'copy' + email = null + email_on_fail = null + plaintext_email = false + monochrome_logs = false + hook_url = null + help = false + help_full = false + show_hidden = false + version = false + pipelines_testdata_base_path = 'https://raw.githubusercontent.com/nf-core/test-datasets/' + trace_report_suffix = new java.util.Date().format('yyyy-MM-dd_HH-mm-ss') // Config options - config_profile_name = null - config_profile_description = null + config_profile_name = null + config_profile_description = null - custom_config_version = 'master' - custom_config_base = "https://raw.githubusercontent.com/nf-core/configs/${params.custom_config_version}" - config_profile_contact = null - config_profile_url = null + custom_config_version = 'master' + custom_config_base = "https://raw.githubusercontent.com/nf-core/configs/${params.custom_config_version}" + config_profile_contact = null + config_profile_url = null // Schema validation default options - validate_params = true + validate_params = true } // Load base.config by default for all pipelines diff --git a/nextflow_schema.json b/nextflow_schema.json index 92eded7c..5b3f1306 100644 --- a/nextflow_schema.json +++ b/nextflow_schema.json @@ -211,6 +211,423 @@ } } }, + "htodemux_options": { + "title": "HTODemux options", + "type": "object", + "fa_icon": "fas fa-tags", + "description": "Options specific to the HTODemux tool for cell hashing demultiplexing.", + "properties": { + "htodemux_quantile": { + "type": "number", + "description": "The quantile to use for thresholding.", + "default": 0.99, + "minimum": 0, + "maximum": 1, + "fa_icon": "fas fa-percentage" + }, + "htodemux_init": { + "type": "string", + "description": "Initialization method for clustering.", + "default": "NULL", + "fa_icon": "fas fa-play" + }, + "htodemux_nstarts": { + "type": "integer", + "description": "Number of starts for clustering.", + "default": 100, + "minimum": 1, + "fa_icon": "fas fa-redo" + }, + "htodemux_kfunc": { + "type": "string", + "description": "Clustering function to use.", + "default": "clara", + "fa_icon": "fas fa-sitemap" + }, + "htodemux_nsamples": { + "type": "integer", + "description": "Number of samples for clustering.", + "default": 100, + "minimum": 1, + "fa_icon": "fas fa-layer-group" + }, + "htodemux_seed": { + "type": "integer", + "description": "Random seed for reproducibility.", + "default": 42, + "fa_icon": "fas fa-dice" + }, + "htodemux_verbose": { + "type": "boolean", + "description": "Whether to print verbose output.", + "default": true, + "fa_icon": "fas fa-volume-up" + } + } + }, + "htodemux_visualization_options": { + "title": "HTODemux visualization options", + "type": "object", + "fa_icon": "fas fa-chart-bar", + "description": "Options specific to the HTODemux visualization tool for generating plots and visualizations.", + "properties": { + "htodemux_visualization_ridgePlot": { + "type": "boolean", + "description": "Generate ridge plot.", + "default": true, + "fa_icon": "fas fa-chart-line" + }, + "htodemux_visualization_ridgeNCol": { + "type": "integer", + "description": "The number of plots that are dispalyed next to each other in one row. The number of plots corresponds to the number of Hash Tag Oligo (HTO) identifiers.", + "default": 2, + "minimum": 1, + "fa_icon": "fas fa-columns" + }, + "htodemux_visualization_featureScatter": { + "type": "boolean", + "description": "Generate feature scatter plot. If no features are provided (one of them is null), the first two features from the assay will be used.", + "default": true, + "fa_icon": "fas fa-scatter-chart" + }, + "htodemux_visualization_scatterFeat1": { + "type": "string", + "description": "Name of a Hash Tag Oligo (HTO) identifiers, usually defined in the `feature.tsv` of the hto matrix folder.", + "default": null, + "fa_icon": "fas fa-tag" + }, + "htodemux_visualization_scatterFeat2": { + "type": "string", + "description": "Name of a Hash Tag Oligo (HTO) identifiers, usually defined in the `feature.tsv` of the hto matrix folder.", + "default": null, + "fa_icon": "fas fa-tag" + }, + "htodemux_visualization_vlnPlot": { + "type": "boolean", + "description": "Generate violin plot.", + "default": true, + "fa_icon": "fas fa-violin" + }, + "htodemux_visualization_vlnFeatures": { + "type": "string", + "description": "Features to plot (gene expression, metrics, PC scores, anything that can be retrieved by FetchData).", + "default": "nCount_RNA", + "fa_icon": "fas fa-list" + }, + "htodemux_visualization_vlnLog": { + "type": "boolean", + "description": "Plot the feature axis on log scale.", + "default": true, + "fa_icon": "fas fa-chart-line" + }, + "htodemux_visualization_tSNE": { + "type": "boolean", + "description": "Generate a two dimensional tSNE embedding for HTOs.", + "default": true, + "fa_icon": "fas fa-project-diagram" + }, + "htodemux_visualization_tSNEIdents": { + "type": "string", + "description": "What should we remove from the object (we have Singlet, Doublet and Negative).", + "default": "Negative", + "enum": ["Singlet", "Doublet", "Negative"], + "fa_icon": "fas fa-filter" + }, + "htodemux_visualization_tSNEInvert": { + "type": "boolean", + "description": "Invert tSNE selection.", + "default": true, + "fa_icon": "fas fa-exchange-alt" + }, + "htodemux_visualization_tSNEVerbose": { + "type": "boolean", + "description": "Verbose tSNE.", + "default": false, + "fa_icon": "fas fa-volume-up" + }, + "htodemux_visualization_tSNEApprox": { + "type": "boolean", + "description": "Approximate tSNE.", + "default": false, + "fa_icon": "fas fa-fast-forward" + }, + "htodemux_visualization_tSNEDimMax": { + "type": "integer", + "description": "Max number of donors.", + "default": 2, + "minimum": 1, + "fa_icon": "fas fa-users" + }, + "htodemux_visualization_tSNEPerplexity": { + "type": "integer", + "description": "Value for perplexity.", + "default": 100, + "minimum": 1, + "fa_icon": "fas fa-sliders-h" + }, + "htodemux_visualization_heatMap": { + "type": "boolean", + "description": "Generate heatmap.", + "default": true, + "fa_icon": "fas fa-fire" + }, + "htodemux_visualization_heatMapNcells": { + "type": "integer", + "description": "Number of cells for heatmap.", + "default": 500, + "minimum": 1, + "fa_icon": "fas fa-layer-group" + } + } + }, + "multiseqdemux_options": { + "title": "MultiSeqDemux options", + "type": "object", + "fa_icon": "fas fa-list-ol", + "description": "Options specific to the MultiSeqDemux tool for cell hashing demultiplexing.", + "properties": { + "multiseqdemux_quantile": { + "type": "number", + "description": "The quantile to use for thresholding.", + "default": 0.7, + "minimum": 0, + "maximum": 1, + "fa_icon": "fas fa-percentage" + }, + "multiseqdemux_autoThresh": { + "type": "boolean", + "description": "Whether to automatically determine thresholds.", + "default": true, + "fa_icon": "fas fa-magic" + }, + "multiseqdemux_maxiter": { + "type": "integer", + "description": "Maximum number of iterations.", + "default": 5, + "minimum": 1, + "fa_icon": "fas fa-sync" + }, + "multiseqdemux_qrangeFrom": { + "type": "number", + "description": "Start of quantile range.", + "default": 0.1, + "minimum": 0, + "maximum": 1, + "fa_icon": "fas fa-arrow-left" + }, + "multiseqdemux_qrangeTo": { + "type": "number", + "description": "End of quantile range.", + "default": 0.9, + "minimum": 0, + "maximum": 1, + "fa_icon": "fas fa-arrow-right" + }, + "multiseqdemux_qrangeBy": { + "type": "number", + "description": "Step size for quantile range.", + "default": 0.05, + "minimum": 0, + "maximum": 1, + "fa_icon": "fas fa-ruler" + }, + "multiseqdemux_verbose": { + "type": "boolean", + "description": "Whether to print verbose output.", + "default": true, + "fa_icon": "fas fa-volume-up" + } + } + }, + "hasheddrops_options": { + "title": "HashedDrops options", + "type": "object", + "fa_icon": "fas fa-tint", + "description": "Options specific to the HashedDrops tool for cell hashing demultiplexing.", + "properties": { + "hasheddrops_lower": { + "type": "integer", + "description": "Lower bound on total UMI count for empty droplets.", + "default": 100, + "minimum": 0, + "fa_icon": "fas fa-sort-amount-down" + }, + "hasheddrops_niters": { + "type": "integer", + "description": "Number of iterations for Monte Carlo p-value calculations.", + "default": 10000, + "minimum": 1, + "fa_icon": "fas fa-redo" + }, + "hasheddrops_testAmbient": { + "type": "string", + "description": "Whether to test ambient RNA.", + "default": "TRUE", + "enum": ["TRUE", "FALSE"], + "fa_icon": "fas fa-flask" + }, + "hasheddrops_round": { + "type": "string", + "description": "Whether to round non-integer values.", + "default": "TRUE", + "enum": ["TRUE", "FALSE"], + "fa_icon": "fas fa-circle" + }, + "hasheddrops_byRank": { + "type": "string", + "description": "Alternative method for identifying empty droplets.", + "default": "NULL", + "fa_icon": "fas fa-sort-numeric-down" + }, + "hasheddrops_isCellFDR": { + "type": "number", + "description": "FDR threshold for cell filtering.", + "default": 0.01, + "minimum": 0, + "maximum": 1, + "fa_icon": "fas fa-filter" + }, + "hasheddrops_gene_col": { + "type": "integer", + "description": "Column to use for gene names.", + "default": 2, + "minimum": 1, + "fa_icon": "fas fa-columns" + }, + "hasheddrops_ignore": { + "type": "string", + "description": "Lower bound for ignoring barcodes.", + "default": "NULL", + "fa_icon": "fas fa-ban" + }, + "hasheddrops_alpha": { + "type": "string", + "description": "Scaling parameter for Dirichlet-multinomial sampling.", + "default": "NULL", + "fa_icon": "fas fa-adjust" + }, + "hasheddrops_ambient": { + "type": "string", + "description": "Whether to use ambient solution abundance.", + "default": "TRUE", + "enum": ["TRUE", "FALSE"], + "fa_icon": "fas fa-water" + }, + "hasheddrops_minProp": { + "type": "number", + "description": "Minimum proportion for ambient profile inference.", + "default": 0.05, + "minimum": 0, + "maximum": 1, + "fa_icon": "fas fa-percentage" + }, + "hasheddrops_pseudoCount": { + "type": "integer", + "description": "Minimum pseudo-count for log-fold change computation.", + "default": 5, + "minimum": 0, + "fa_icon": "fas fa-plus" + }, + "hasheddrops_constantAmbient": { + "type": "string", + "description": "Whether to use constant ambient contamination level.", + "default": "FALSE", + "enum": ["TRUE", "FALSE"], + "fa_icon": "fas fa-equals" + }, + "hasheddrops_doubletNmads": { + "type": "integer", + "description": "Number of MADs to identify doublets.", + "default": 3, + "minimum": 0, + "fa_icon": "fas fa-object-group" + }, + "hasheddrops_doubletMin": { + "type": "integer", + "description": "Minimum threshold for doublet identification.", + "default": 2, + "minimum": 0, + "fa_icon": "fas fa-sort-amount-down" + }, + "hasheddrops_doubletMixture": { + "type": "string", + "description": "Whether to use 2-component mixture model for doublets.", + "default": "FALSE", + "enum": ["TRUE", "FALSE"], + "fa_icon": "fas fa-object-ungroup" + }, + "hasheddrops_confidentNmads": { + "type": "integer", + "description": "Number of MADs to identify confident singlets.", + "default": 3, + "minimum": 0, + "fa_icon": "fas fa-check-circle" + }, + "hasheddrops_confidentMin": { + "type": "integer", + "description": "Minimum threshold for confident singlet identification.", + "default": 2, + "minimum": 0, + "fa_icon": "fas fa-sort-amount-up" + }, + "hasheddrops_combinations": { + "type": "string", + "description": "Valid combinations of HTOs.", + "default": "NULL", + "fa_icon": "fas fa-th-large" + } + } + }, + "preprocessing_options": { + "title": "Preprocessing options", + "type": "object", + "fa_icon": "fas fa-cogs", + "description": "Options for preprocessing data for HTODemux and MultiSeq demultiplexing.", + "properties": { + "preprocessing_sel_method": { + "type": "string", + "description": "Method for feature selection.", + "default": "mean.var.plot", + "fa_icon": "fas fa-chart-line" + }, + "preprocessing_ndelim": { + "type": "string", + "description": "Delimiter for parsing feature names.", + "default": "_", + "fa_icon": "fas fa-cut" + }, + "preprocessing_n_features": { + "type": "integer", + "description": "Number of features to select.", + "default": 2000, + "fa_icon": "fas fa-hashtag" + }, + "preprocessing_assay": { + "type": "string", + "description": "Assay type for preprocessing.", + "default": "HTO", + "fa_icon": "fas fa-flask" + }, + "preprocessing_margin": { + "type": "integer", + "description": "Margin parameter for preprocessing.", + "default": 2, + "fa_icon": "fas fa-arrows-alt-h" + }, + "preprocessing_norm_method": { + "type": "string", + "description": "Normalization method to use.", + "default": "CLR", + "fa_icon": "fas fa-balance-scale" + }, + "preprocessing_gene_col": { + "type": "integer", + "description": "Column containing gene information.", + "default": 2, + "fa_icon": "fas fa-columns" + } + } + }, "cellsnp_options": { "title": "CellSNP-lite options", "type": "object", @@ -729,6 +1146,21 @@ { "$ref": "#/$defs/gmmdemux_options" }, + { + "$ref": "#/$defs/htodemux_options" + }, + { + "$ref": "#/$defs/htodemux_visualization_options" + }, + { + "$ref": "#/$defs/multiseqdemux_options" + }, + { + "$ref": "#/$defs/hasheddrops_options" + }, + { + "$ref": "#/$defs/preprocessing_options" + }, { "$ref": "#/$defs/cellsnp_options" }, diff --git a/subworkflows/local/hash_demultiplexing/main.nf b/subworkflows/local/hash_demultiplexing/main.nf index d483425f..175d621f 100644 --- a/subworkflows/local/hash_demultiplexing/main.nf +++ b/subworkflows/local/hash_demultiplexing/main.nf @@ -1,7 +1,16 @@ -include { DROPLETUTILS_MTXCONVERT as MTXCONVERT_RNA } from '../../../modules/local/dropletutils/mtxconvert' -include { DROPLETUTILS_MTXCONVERT as MTXCONVERT_HTO } from '../../../modules/local/dropletutils/mtxconvert' -include { DEMUXEM } from '../../../modules/nf-core/demuxem' -include { GMMDEMUX } from '../../../modules/nf-core/gmmdemux' +include { UNTAR as UNTAR_RNA } from '../../../modules/nf-core/untar' +include { UNTAR as UNTAR_HTO } from '../../../modules/nf-core/untar' +include { RENAME_GENES_TO_FEATURES as RENAME_GENES_TO_FEATURES_RNA } from '../../../modules/local/rename_genes_to_features' +include { RENAME_GENES_TO_FEATURES as RENAME_GENES_TO_FEATURES_HTO } from '../../../modules/local/rename_genes_to_features' +include { DROPLETUTILS_MTXCONVERT as MTXCONVERT_RNA } from '../../../modules/local/dropletutils/mtxconvert' +include { DROPLETUTILS_MTXCONVERT as MTXCONVERT_HTO } from '../../../modules/local/dropletutils/mtxconvert' +include { PREPROCESSING_FOR_HTODEMUX_MULTISEQ } from '../../../modules/local/preprocessing_for_htodemux_multiseq' +include { HTODEMUX } from '../../../modules/nf-core/htodemux' +include { HTODEMUX_VISUALIZATION } from '../../../modules/local/htodemux_visualization' +include { MULTISEQDEMUX } from '../../../modules/nf-core/multiseqdemux' +include { DEMUXEM } from '../../../modules/nf-core/demuxem' +include { GMMDEMUX } from '../../../modules/nf-core/gmmdemux' +include { HASHEDDROPS } from '../../../modules/nf-core/hasheddrops' workflow HASH_DEMULTIPLEXING { take: @@ -12,12 +21,67 @@ workflow HASH_DEMULTIPLEXING { ch_versions = Channel.empty() - if (methods.contains('htodemux')) { - error("HtoDemux not implemented") + ch_samplesheet.map { meta, rna, hto -> + { + if (!rna) { + error("RNA matrix not provided for sample ${meta.id}, but this is required for hash demultiplexing. Please check your input samplesheet.") + } + if (!hto) { + error("HTO matrix not provided for sample ${meta.id}, but this is required for hash demultiplexing. Please check your input samplesheet.") + } + } } - if (methods.contains('multiseq')) { - error("MultiSeq not implemented") + + ch_rna = ch_samplesheet.map { meta, rna, _hto -> [meta, rna] } + .branch { _meta, rna -> + tar: rna.endsWith('.tar.gz') + directory: true + } + ch_hto = ch_samplesheet.map { meta, _rna, hto -> [meta, hto] } + .branch { _meta, hto -> + tar: hto.endsWith('.tar.gz') + directory: true + } + + UNTAR_RNA(ch_rna.tar) + ch_versions = ch_versions.mix(UNTAR_RNA.out.versions) + + UNTAR_HTO(ch_hto.tar) + ch_versions = ch_versions.mix(UNTAR_HTO.out.versions) + + ch_rna = ch_rna.directory.mix(UNTAR_RNA.out.untar) + ch_hto = ch_hto.directory.mix(UNTAR_HTO.out.untar) + + ch_rna = RENAME_GENES_TO_FEATURES_RNA(ch_rna) + ch_hto = RENAME_GENES_TO_FEATURES_HTO(ch_hto) + + ch_samplesheet = ch_samplesheet.map { meta, _rna, _hto -> [meta] }.join(ch_rna).join(ch_hto) + + if (methods.contains('htodemux') || methods.contains('multiseq')) { + PREPROCESSING_FOR_HTODEMUX_MULTISEQ( + ch_samplesheet + ) + ch_versions = ch_versions.mix(PREPROCESSING_FOR_HTODEMUX_MULTISEQ.out.versions) + + if (methods.contains('htodemux')) { + HTODEMUX( + PREPROCESSING_FOR_HTODEMUX_MULTISEQ.out.seurat_object.map { meta, seurat_object -> [meta, seurat_object, "HTO"] } + ) + ch_versions = ch_versions.mix(HTODEMUX.out.versions) + + HTODEMUX_VISUALIZATION( + HTODEMUX.out.rds.map { meta, seurat_object -> [meta, seurat_object, "HTO"] } + ) + ch_versions = ch_versions.mix(HTODEMUX_VISUALIZATION.out.versions) + } + if (methods.contains('multiseq')) { + MULTISEQDEMUX( + PREPROCESSING_FOR_HTODEMUX_MULTISEQ.out.seurat_object.map { meta, seurat_object -> [meta, seurat_object, "HTO"] } + ) + ch_versions = ch_versions.mix(MULTISEQDEMUX.out.versions) + } } + if (methods.contains('cellhashr')) { error("CellHashR not implemented") } @@ -70,7 +134,10 @@ workflow HASH_DEMULTIPLEXING { ch_versions = ch_versions.mix(GMMDEMUX.out.versions) } if (methods.contains('hasheddrops')) { - error("HashedDrops not implemented") + HASHEDDROPS( + ch_samplesheet.map { meta, rna, hto -> [meta, hto, "FALSE", rna] } + ) + ch_versions = ch_versions.mix(HASHEDDROPS.out.versions) } if (methods.contains('hashsolo')) { error("HashSolo not implemented") diff --git a/tests/.nftignore b/tests/.nftignore index dcdf312a..d2c9d3c7 100644 --- a/tests/.nftignore +++ b/tests/.nftignore @@ -1,8 +1,14 @@ .DS_Store multiqc/multiqc_data/multiqc.log multiqc/multiqc_data/multiqc_data.json +multiqc/multiqc_data/BETA-multiqc.parquet multiqc/multiqc_data/multiqc_sources.txt multiqc/multiqc_data/multiqc_software_versions.txt multiqc/multiqc_plots/{svg,pdf,png}/*.{svg,pdf,png} multiqc/multiqc_report.html pipeline_info/*.{html,json,txt,yml} +hashing/demuxem/** +hashing/gmm-demux/*/GMM_full.csv +hashing/htodemux/*/*_htodemux.rds +hashing/multiseqdemux/*/*_multiseqdemux.rds +hashing/preprocessing/*/*_preprocessed.rds diff --git a/tests/default.nf.test.snap b/tests/default.nf.test.snap new file mode 100644 index 00000000..ab911bf7 --- /dev/null +++ b/tests/default.nf.test.snap @@ -0,0 +1,460 @@ +{ + "-profile test": { + "content": [ + 70, + { + "CELLSNP_MODEA": { + "cellsnp": "1.2.3" + }, + "DEMUXEM": "echo 0.1.7.post1", + "GMMDEMUX": { + "GMM-Demux": "0.2.2.3" + }, + "HASHEDDROPS": { + "r-base": "4.4.3", + "r-seurat": "5.3.0", + "dropletutils": "1.26.0" + }, + "HTODEMUX": { + "r-base": "4.4.3", + "seurat": "5.3.0" + }, + "HTODEMUX_VISUALIZATION": { + "r-base": "4.4.3", + "r-seurat": "5.3.0", + "r-ggplot2": "3.5.2" + }, + "MTXCONVERT_HTO": { + "r-base": "4.4.3", + "bioconductor-dropletutils": "1.26.0" + }, + "MTXCONVERT_RNA": { + "r-base": "4.4.3", + "bioconductor-dropletutils": "1.26.0" + }, + "MULTISEQDEMUX": { + "r-base": "4.4.3", + "r-seurat": "5.3.0" + }, + "POPSCLE_DEMUXLET": { + "popscle demuxlet": 0.1 + }, + "POPSCLE_DSCPILEUP": { + "popscle dsc-pileup": 0.1 + }, + "POPSCLE_FREEMUXLET": { + "popscle": 0.1 + }, + "PREPROCESSING_FOR_HTODEMUX_MULTISEQ": { + "r-base": "4.4.3", + "seurat": "5.3.0" + }, + "SAMTOOLS_INDEX": { + "samtools": 1.21 + }, + "SAMTOOLS_SORT": { + "samtools": 1.21 + }, + "SAMTOOLS_VIEW": { + "samtools": 1.21 + }, + "UMITOOLS_DEDUP": { + "umitools": "1.1.5" + }, + "UNTAR_HTO": { + "untar": 1.34 + }, + "UNTAR_RNA": { + "untar": 1.34 + }, + "VIREO": { + "vireo": "v0.5.8" + }, + "Workflow": { + "nf-core/hadge": "v1.0.0dev" + } + }, + [ + "genetic", + "genetic/popscle", + "genetic/popscle/demuxlet", + "genetic/popscle/demuxlet/test1", + "genetic/popscle/demuxlet/test1/test1.best", + "genetic/popscle/demuxlet/test2", + "genetic/popscle/demuxlet/test2/test2.best", + "genetic/popscle/demuxlet/test3", + "genetic/popscle/demuxlet/test3/test3.best", + "genetic/popscle/freemuxlet", + "genetic/popscle/freemuxlet/test1", + "genetic/popscle/freemuxlet/test1/test1.clust1.samples.gz", + "genetic/popscle/freemuxlet/test1/test1.clust1.vcf.gz", + "genetic/popscle/freemuxlet/test1/test1.lmix", + "genetic/popscle/freemuxlet/test2", + "genetic/popscle/freemuxlet/test2/test2.clust1.samples.gz", + "genetic/popscle/freemuxlet/test2/test2.clust1.vcf.gz", + "genetic/popscle/freemuxlet/test2/test2.lmix", + "genetic/popscle/freemuxlet/test3", + "genetic/popscle/freemuxlet/test3/test3.clust1.samples.gz", + "genetic/popscle/freemuxlet/test3/test3.clust1.vcf.gz", + "genetic/popscle/freemuxlet/test3/test3.lmix", + "genetic/vireo", + "genetic/vireo/test1", + "genetic/vireo/test1/test1.base.vcf.gz", + "genetic/vireo/test1/test1.cells.vcf.gz", + "genetic/vireo/test1/test1.samples.tsv", + "genetic/vireo/test1/test1.tag.AD.mtx", + "genetic/vireo/test1/test1.tag.DP.mtx", + "genetic/vireo/test1/test1.tag.OTH.mtx", + "genetic/vireo/test1/test1_donor_ids.tsv", + "genetic/vireo/test1/test1_prob_doublet.tsv.gz", + "genetic/vireo/test1/test1_prob_singlet.tsv.gz", + "genetic/vireo/test1/test1_summary.tsv", + "genetic/vireo/test2", + "genetic/vireo/test2/test2.base.vcf.gz", + "genetic/vireo/test2/test2.cells.vcf.gz", + "genetic/vireo/test2/test2.samples.tsv", + "genetic/vireo/test2/test2.tag.AD.mtx", + "genetic/vireo/test2/test2.tag.DP.mtx", + "genetic/vireo/test2/test2.tag.OTH.mtx", + "genetic/vireo/test2/test2_donor_ids.tsv", + "genetic/vireo/test2/test2_prob_doublet.tsv.gz", + "genetic/vireo/test2/test2_prob_singlet.tsv.gz", + "genetic/vireo/test2/test2_summary.tsv", + "genetic/vireo/test3", + "genetic/vireo/test3/test3.base.vcf.gz", + "genetic/vireo/test3/test3.cells.vcf.gz", + "genetic/vireo/test3/test3.samples.tsv", + "genetic/vireo/test3/test3.tag.AD.mtx", + "genetic/vireo/test3/test3.tag.DP.mtx", + "genetic/vireo/test3/test3.tag.OTH.mtx", + "genetic/vireo/test3/test3_donor_ids.tsv", + "genetic/vireo/test3/test3_prob_doublet.tsv.gz", + "genetic/vireo/test3/test3_prob_singlet.tsv.gz", + "genetic/vireo/test3/test3_summary.tsv", + "hashing", + "hashing/demuxem", + "hashing/demuxem/test1", + "hashing/demuxem/test1/test1.ambient_hashtag.hist.pdf", + "hashing/demuxem/test1/test1.background_probabilities.bar.pdf", + "hashing/demuxem/test1/test1.out.demuxEM.zarr.zip", + "hashing/demuxem/test1/test1.real_content.hist.pdf", + "hashing/demuxem/test1/test1.rna_demux.hist.pdf", + "hashing/demuxem/test1/test1_demux.zarr.zip", + "hashing/demuxem/test2", + "hashing/demuxem/test2/test2.ambient_hashtag.hist.pdf", + "hashing/demuxem/test2/test2.background_probabilities.bar.pdf", + "hashing/demuxem/test2/test2.out.demuxEM.zarr.zip", + "hashing/demuxem/test2/test2.real_content.hist.pdf", + "hashing/demuxem/test2/test2.rna_demux.hist.pdf", + "hashing/demuxem/test2/test2_demux.zarr.zip", + "hashing/demuxem/test3", + "hashing/demuxem/test3/test3.ambient_hashtag.hist.pdf", + "hashing/demuxem/test3/test3.background_probabilities.bar.pdf", + "hashing/demuxem/test3/test3.out.demuxEM.zarr.zip", + "hashing/demuxem/test3/test3.real_content.hist.pdf", + "hashing/demuxem/test3/test3.rna_demux.hist.pdf", + "hashing/demuxem/test3/test3_demux.zarr.zip", + "hashing/gmm-demux", + "hashing/gmm-demux/test1", + "hashing/gmm-demux/test1/GMM_full.config", + "hashing/gmm-demux/test1/GMM_full.csv", + "hashing/gmm-demux/test1/barcodes.tsv.gz", + "hashing/gmm-demux/test1/features.tsv.gz", + "hashing/gmm-demux/test1/matrix.mtx.gz", + "hashing/gmm-demux/test2", + "hashing/gmm-demux/test2/GMM_full.config", + "hashing/gmm-demux/test2/GMM_full.csv", + "hashing/gmm-demux/test2/barcodes.tsv.gz", + "hashing/gmm-demux/test2/features.tsv.gz", + "hashing/gmm-demux/test2/matrix.mtx.gz", + "hashing/gmm-demux/test3", + "hashing/gmm-demux/test3/GMM_full.config", + "hashing/gmm-demux/test3/GMM_full.csv", + "hashing/gmm-demux/test3/barcodes.tsv.gz", + "hashing/gmm-demux/test3/features.tsv.gz", + "hashing/gmm-demux/test3/matrix.mtx.gz", + "hashing/hasheddrops", + "hashing/hasheddrops/test1", + "hashing/hasheddrops/test1/test1_emptyDrops.csv", + "hashing/hasheddrops/test1/test1_emptyDrops.png", + "hashing/hasheddrops/test1/test1_emptyDrops.rds", + "hashing/hasheddrops/test1/test1_hasheddrops.rds", + "hashing/hasheddrops/test1/test1_params_hasheddrops.csv", + "hashing/hasheddrops/test1/test1_plot_hasheddrops.png", + "hashing/hasheddrops/test1/test1_results_hasheddrops.csv", + "hashing/hasheddrops/test2", + "hashing/hasheddrops/test2/test2_emptyDrops.csv", + "hashing/hasheddrops/test2/test2_emptyDrops.png", + "hashing/hasheddrops/test2/test2_emptyDrops.rds", + "hashing/hasheddrops/test2/test2_hasheddrops.rds", + "hashing/hasheddrops/test2/test2_params_hasheddrops.csv", + "hashing/hasheddrops/test2/test2_plot_hasheddrops.png", + "hashing/hasheddrops/test2/test2_results_hasheddrops.csv", + "hashing/hasheddrops/test3", + "hashing/hasheddrops/test3/test3_emptyDrops.csv", + "hashing/hasheddrops/test3/test3_emptyDrops.png", + "hashing/hasheddrops/test3/test3_emptyDrops.rds", + "hashing/hasheddrops/test3/test3_hasheddrops.rds", + "hashing/hasheddrops/test3/test3_params_hasheddrops.csv", + "hashing/hasheddrops/test3/test3_plot_hasheddrops.png", + "hashing/hasheddrops/test3/test3_results_hasheddrops.csv", + "hashing/htodemux", + "hashing/htodemux/test1", + "hashing/htodemux/test1/test1_assignment_htodemux.csv", + "hashing/htodemux/test1/test1_classification_htodemux.csv", + "hashing/htodemux/test1/test1_htodemux.rds", + "hashing/htodemux/test1/test1_params_htodemux.csv", + "hashing/htodemux/test2", + "hashing/htodemux/test2/test2_assignment_htodemux.csv", + "hashing/htodemux/test2/test2_classification_htodemux.csv", + "hashing/htodemux/test2/test2_htodemux.rds", + "hashing/htodemux/test2/test2_params_htodemux.csv", + "hashing/htodemux/test3", + "hashing/htodemux/test3/test3_assignment_htodemux.csv", + "hashing/htodemux/test3/test3_classification_htodemux.csv", + "hashing/htodemux/test3/test3_htodemux.rds", + "hashing/htodemux/test3/test3_params_htodemux.csv", + "hashing/htodemux_visualization", + "hashing/htodemux_visualization/test1", + "hashing/htodemux_visualization/test1/test1_featureScatter_htodemux.jpeg", + "hashing/htodemux_visualization/test1/test1_heatMap_htodemux.jpeg", + "hashing/htodemux_visualization/test1/test1_ridge_htodemux.jpeg", + "hashing/htodemux_visualization/test1/test1_tSNE_htodemux.jpeg", + "hashing/htodemux_visualization/test1/test1_violinPlot_htodemux.jpeg", + "hashing/htodemux_visualization/test1/test1_visual_params_htodemux.csv", + "hashing/htodemux_visualization/test2", + "hashing/htodemux_visualization/test2/test2_featureScatter_htodemux.jpeg", + "hashing/htodemux_visualization/test2/test2_heatMap_htodemux.jpeg", + "hashing/htodemux_visualization/test2/test2_ridge_htodemux.jpeg", + "hashing/htodemux_visualization/test2/test2_tSNE_htodemux.jpeg", + "hashing/htodemux_visualization/test2/test2_violinPlot_htodemux.jpeg", + "hashing/htodemux_visualization/test2/test2_visual_params_htodemux.csv", + "hashing/htodemux_visualization/test3", + "hashing/htodemux_visualization/test3/test3_featureScatter_htodemux.jpeg", + "hashing/htodemux_visualization/test3/test3_heatMap_htodemux.jpeg", + "hashing/htodemux_visualization/test3/test3_ridge_htodemux.jpeg", + "hashing/htodemux_visualization/test3/test3_tSNE_htodemux.jpeg", + "hashing/htodemux_visualization/test3/test3_violinPlot_htodemux.jpeg", + "hashing/htodemux_visualization/test3/test3_visual_params_htodemux.csv", + "hashing/multiseqdemux", + "hashing/multiseqdemux/test1", + "hashing/multiseqdemux/test1/test1_multiseqdemux.rds", + "hashing/multiseqdemux/test1/test1_params_multiseqdemux.csv", + "hashing/multiseqdemux/test1/test1_res_multiseqdemux.csv", + "hashing/multiseqdemux/test2", + "hashing/multiseqdemux/test2/test2_multiseqdemux.rds", + "hashing/multiseqdemux/test2/test2_params_multiseqdemux.csv", + "hashing/multiseqdemux/test2/test2_res_multiseqdemux.csv", + "hashing/multiseqdemux/test3", + "hashing/multiseqdemux/test3/test3_multiseqdemux.rds", + "hashing/multiseqdemux/test3/test3_params_multiseqdemux.csv", + "hashing/multiseqdemux/test3/test3_res_multiseqdemux.csv", + "hashing/preprocessing", + "hashing/preprocessing/test1", + "hashing/preprocessing/test1/test1_params_preprocessing.csv", + "hashing/preprocessing/test1/test1_preprocessed.rds", + "hashing/preprocessing/test2", + "hashing/preprocessing/test2/test2_params_preprocessing.csv", + "hashing/preprocessing/test2/test2_preprocessed.rds", + "hashing/preprocessing/test3", + "hashing/preprocessing/test3/test3_params_preprocessing.csv", + "hashing/preprocessing/test3/test3_preprocessed.rds", + "multiqc", + "multiqc/multiqc_data", + "multiqc/multiqc_data/BETA-multiqc.parquet", + "multiqc/multiqc_data/multiqc.log", + "multiqc/multiqc_data/multiqc_citations.txt", + "multiqc/multiqc_data/multiqc_data.json", + "multiqc/multiqc_data/multiqc_software_versions.txt", + "multiqc/multiqc_data/multiqc_sources.txt", + "multiqc/multiqc_report.html", + "pipeline_info", + "pipeline_info/nf_core_hadge_software_mqc_versions.yml", + "rename", + "rename/test1_hto", + "rename/test1_hto/barcodes.tsv.gz", + "rename/test1_hto/features.tsv.gz", + "rename/test1_hto/matrix.mtx.gz", + "rename/test1_rna", + "rename/test1_rna/barcodes.tsv.gz", + "rename/test1_rna/features.tsv.gz", + "rename/test1_rna/matrix.mtx.gz", + "rename/test2_hto", + "rename/test2_hto/barcodes.tsv.gz", + "rename/test2_hto/features.tsv.gz", + "rename/test2_hto/matrix.mtx.gz", + "rename/test2_rna", + "rename/test2_rna/barcodes.tsv.gz", + "rename/test2_rna/features.tsv.gz", + "rename/test2_rna/matrix.mtx.gz", + "rename/test3_hto", + "rename/test3_hto/barcodes.tsv.gz", + "rename/test3_hto/features.tsv.gz", + "rename/test3_hto/matrix.mtx.gz", + "rename/test3_rna", + "rename/test3_rna/barcodes.tsv.gz", + "rename/test3_rna/features.tsv.gz", + "rename/test3_rna/matrix.mtx.gz", + "untar", + "untar/test1", + "untar/test1/barcodes.tsv.gz", + "untar/test1/features.tsv.gz", + "untar/test1/matrix.mtx.gz", + "untar/test2", + "untar/test2/barcodes.tsv.gz", + "untar/test2/features.tsv.gz", + "untar/test2/matrix.mtx.gz", + "untar/test3", + "untar/test3/barcodes.tsv.gz", + "untar/test3/features.tsv.gz", + "untar/test3/matrix.mtx.gz" + ], + [ + "test1.best:md5,1afa27ec59d7ea6b37e1b9ffbc6007e8", + "test2.best:md5,1afa27ec59d7ea6b37e1b9ffbc6007e8", + "test3.best:md5,1afa27ec59d7ea6b37e1b9ffbc6007e8", + "test1.clust1.samples.gz:md5,42f8c8ba281fccf6edaf6dbd30144210", + "test1.clust1.vcf.gz:md5,065c760b7781af86fe0d4fe8094853d3", + "test1.lmix:md5,d8be4c936ff56980a476d5ba82290b8e", + "test2.clust1.samples.gz:md5,42f8c8ba281fccf6edaf6dbd30144210", + "test2.clust1.vcf.gz:md5,065c760b7781af86fe0d4fe8094853d3", + "test2.lmix:md5,d8be4c936ff56980a476d5ba82290b8e", + "test3.clust1.samples.gz:md5,42f8c8ba281fccf6edaf6dbd30144210", + "test3.clust1.vcf.gz:md5,065c760b7781af86fe0d4fe8094853d3", + "test3.lmix:md5,d8be4c936ff56980a476d5ba82290b8e", + "test1.base.vcf.gz:md5,36eccb4bd3e2130f395985e09a66a60d", + "test1.cells.vcf.gz:md5,b28d8acaf17777c7cf9e2c7d01d93a66", + "test1.samples.tsv:md5,9e488782c1bcd63c37ee3d1c4c0a9217", + "test1.tag.AD.mtx:md5,a76d9f63c6abdcab00f583fac269a98e", + "test1.tag.DP.mtx:md5,6f8e54bbaa8ad9dcaa42c15eff1e1351", + "test1.tag.OTH.mtx:md5,45e64ea3b3b8e8a7126690d9e775d501", + "test1_donor_ids.tsv:md5,083e128fb5594df75dff060ec886d69c", + "test1_prob_doublet.tsv.gz:md5,0226d8043ee03b790fe2d130093b40df", + "test1_prob_singlet.tsv.gz:md5,959b21543d21f2214e399e541848d81a", + "test1_summary.tsv:md5,94b0ce40cc86bbe7fbacebf98e685ee7", + "test2.base.vcf.gz:md5,36eccb4bd3e2130f395985e09a66a60d", + "test2.cells.vcf.gz:md5,b28d8acaf17777c7cf9e2c7d01d93a66", + "test2.samples.tsv:md5,9e488782c1bcd63c37ee3d1c4c0a9217", + "test2.tag.AD.mtx:md5,a76d9f63c6abdcab00f583fac269a98e", + "test2.tag.DP.mtx:md5,6f8e54bbaa8ad9dcaa42c15eff1e1351", + "test2.tag.OTH.mtx:md5,45e64ea3b3b8e8a7126690d9e775d501", + "test2_donor_ids.tsv:md5,083e128fb5594df75dff060ec886d69c", + "test2_prob_doublet.tsv.gz:md5,0226d8043ee03b790fe2d130093b40df", + "test2_prob_singlet.tsv.gz:md5,959b21543d21f2214e399e541848d81a", + "test2_summary.tsv:md5,94b0ce40cc86bbe7fbacebf98e685ee7", + "test3.base.vcf.gz:md5,36eccb4bd3e2130f395985e09a66a60d", + "test3.cells.vcf.gz:md5,b28d8acaf17777c7cf9e2c7d01d93a66", + "test3.samples.tsv:md5,9e488782c1bcd63c37ee3d1c4c0a9217", + "test3.tag.AD.mtx:md5,a76d9f63c6abdcab00f583fac269a98e", + "test3.tag.DP.mtx:md5,6f8e54bbaa8ad9dcaa42c15eff1e1351", + "test3.tag.OTH.mtx:md5,45e64ea3b3b8e8a7126690d9e775d501", + "test3_donor_ids.tsv:md5,083e128fb5594df75dff060ec886d69c", + "test3_prob_doublet.tsv.gz:md5,0226d8043ee03b790fe2d130093b40df", + "test3_prob_singlet.tsv.gz:md5,959b21543d21f2214e399e541848d81a", + "test3_summary.tsv:md5,94b0ce40cc86bbe7fbacebf98e685ee7", + "GMM_full.config:md5,5d74af7b15c1cd4abaa4696be78631b2", + "barcodes.tsv.gz:md5,8ce05fd0599d95476158177deaa48543", + "features.tsv.gz:md5,6ac5b411e9e5b8eee2bc58ddf3ce6f2f", + "matrix.mtx.gz:md5,a203d4315dfae4ff939fe60dec9b9846", + "GMM_full.config:md5,5d74af7b15c1cd4abaa4696be78631b2", + "barcodes.tsv.gz:md5,8ce05fd0599d95476158177deaa48543", + "features.tsv.gz:md5,6ac5b411e9e5b8eee2bc58ddf3ce6f2f", + "matrix.mtx.gz:md5,a203d4315dfae4ff939fe60dec9b9846", + "GMM_full.config:md5,5d74af7b15c1cd4abaa4696be78631b2", + "barcodes.tsv.gz:md5,8ce05fd0599d95476158177deaa48543", + "features.tsv.gz:md5,6ac5b411e9e5b8eee2bc58ddf3ce6f2f", + "matrix.mtx.gz:md5,a203d4315dfae4ff939fe60dec9b9846", + "test1_emptyDrops.csv:md5,a227d42afbcc590b4e949075cde4a5b6", + "test1_emptyDrops.png:md5,2d7113a76a494b2b2c18b77c4a664063", + "test1_emptyDrops.rds:md5,bda2e88b1e3e8c8ea5a22e19e3ab243d", + "test1_hasheddrops.rds:md5,31ba0ecae1d94080bccf81cdbaa5afe8", + "test1_params_hasheddrops.csv:md5,4843ded1642bba8d8e38e70a9733d047", + "test1_plot_hasheddrops.png:md5,2d7113a76a494b2b2c18b77c4a664063", + "test1_results_hasheddrops.csv:md5,d0f2af6d9bdb47a80bfbf08a85493d2e", + "test2_emptyDrops.csv:md5,a227d42afbcc590b4e949075cde4a5b6", + "test2_emptyDrops.png:md5,2d7113a76a494b2b2c18b77c4a664063", + "test2_emptyDrops.rds:md5,bda2e88b1e3e8c8ea5a22e19e3ab243d", + "test2_hasheddrops.rds:md5,31ba0ecae1d94080bccf81cdbaa5afe8", + "test2_params_hasheddrops.csv:md5,c9edfc98fbf87bb8ba9b29ba51c9a55b", + "test2_plot_hasheddrops.png:md5,2d7113a76a494b2b2c18b77c4a664063", + "test2_results_hasheddrops.csv:md5,d0f2af6d9bdb47a80bfbf08a85493d2e", + "test3_emptyDrops.csv:md5,a227d42afbcc590b4e949075cde4a5b6", + "test3_emptyDrops.png:md5,2d7113a76a494b2b2c18b77c4a664063", + "test3_emptyDrops.rds:md5,bda2e88b1e3e8c8ea5a22e19e3ab243d", + "test3_hasheddrops.rds:md5,31ba0ecae1d94080bccf81cdbaa5afe8", + "test3_params_hasheddrops.csv:md5,295547e2ed166ea01f21ac99a31d7c48", + "test3_plot_hasheddrops.png:md5,2d7113a76a494b2b2c18b77c4a664063", + "test3_results_hasheddrops.csv:md5,d0f2af6d9bdb47a80bfbf08a85493d2e", + "test1_assignment_htodemux.csv:md5,065f73102e5e28f47d1ae5d679bacc4d", + "test1_classification_htodemux.csv:md5,7627fa55b5ec02b06a356ee2f55e916f", + "test1_params_htodemux.csv:md5,08b6c065b0c383e6be3c20787ec01d3e", + "test2_assignment_htodemux.csv:md5,065f73102e5e28f47d1ae5d679bacc4d", + "test2_classification_htodemux.csv:md5,7627fa55b5ec02b06a356ee2f55e916f", + "test2_params_htodemux.csv:md5,484909d87cacd9c2b9cf60a3c853a5fd", + "test3_assignment_htodemux.csv:md5,065f73102e5e28f47d1ae5d679bacc4d", + "test3_classification_htodemux.csv:md5,7627fa55b5ec02b06a356ee2f55e916f", + "test3_params_htodemux.csv:md5,a0a3f900a632eea39057b038438a89b9", + "test1_featureScatter_htodemux.jpeg:md5,2785c5d3287f77cc0a20b0a9e9f0646b", + "test1_heatMap_htodemux.jpeg:md5,3805678904edcc047974a0011fc8f03f", + "test1_ridge_htodemux.jpeg:md5,124199413ddd4a8ff58e523af36e1b97", + "test1_tSNE_htodemux.jpeg:md5,6a9972d5e77fdaec39c640bee832ea49", + "test1_violinPlot_htodemux.jpeg:md5,5d6dce10b324e98f628aff64bc09056c", + "test1_visual_params_htodemux.csv:md5,ba3a19f1ee84f653ab1f2c34f5a9930a", + "test2_featureScatter_htodemux.jpeg:md5,2785c5d3287f77cc0a20b0a9e9f0646b", + "test2_heatMap_htodemux.jpeg:md5,3805678904edcc047974a0011fc8f03f", + "test2_ridge_htodemux.jpeg:md5,124199413ddd4a8ff58e523af36e1b97", + "test2_tSNE_htodemux.jpeg:md5,6a9972d5e77fdaec39c640bee832ea49", + "test2_violinPlot_htodemux.jpeg:md5,5d6dce10b324e98f628aff64bc09056c", + "test2_visual_params_htodemux.csv:md5,5b1689a66e8c39a5de4b6c51ff201aef", + "test3_featureScatter_htodemux.jpeg:md5,2785c5d3287f77cc0a20b0a9e9f0646b", + "test3_heatMap_htodemux.jpeg:md5,3805678904edcc047974a0011fc8f03f", + "test3_ridge_htodemux.jpeg:md5,124199413ddd4a8ff58e523af36e1b97", + "test3_tSNE_htodemux.jpeg:md5,6a9972d5e77fdaec39c640bee832ea49", + "test3_violinPlot_htodemux.jpeg:md5,5d6dce10b324e98f628aff64bc09056c", + "test3_visual_params_htodemux.csv:md5,145eb3a098205f568cf89a5dffadf742", + "test1_params_multiseqdemux.csv:md5,60a096f79d78655870aef2af9130cf9d", + "test1_res_multiseqdemux.csv:md5,8a59e5066250adf6830555fda26e077f", + "test2_params_multiseqdemux.csv:md5,bb7b4009e6f709ebf35407b7849bfac3", + "test2_res_multiseqdemux.csv:md5,8a59e5066250adf6830555fda26e077f", + "test3_params_multiseqdemux.csv:md5,336308c45e52f92981173ecc7f295581", + "test3_res_multiseqdemux.csv:md5,8a59e5066250adf6830555fda26e077f", + "test1_params_preprocessing.csv:md5,b77b94588a53835e913cb55b3791d309", + "test2_params_preprocessing.csv:md5,0ed6b213e9f08ee5fc0f89b7184a3fb9", + "test3_params_preprocessing.csv:md5,c5b931c6fe281d0cc63cb3ed13cb2d10", + "multiqc_citations.txt:md5,4c806e63a283ec1b7e78cdae3a923d4f", + "barcodes.tsv.gz:md5,291349dc31e204c71c4b121de4959686", + "features.tsv.gz:md5,6ac5b411e9e5b8eee2bc58ddf3ce6f2f", + "matrix.mtx.gz:md5,9d676ae60340181e1f227b3d92bd384e", + "barcodes.tsv.gz:md5,f62a276e262fdd85262a889d0f48556b", + "features.tsv.gz:md5,f7fe456894f58241d622077e2fe4b548", + "matrix.mtx.gz:md5,eb787b86f095acac4435c9014e09daa7", + "barcodes.tsv.gz:md5,291349dc31e204c71c4b121de4959686", + "features.tsv.gz:md5,6ac5b411e9e5b8eee2bc58ddf3ce6f2f", + "matrix.mtx.gz:md5,9d676ae60340181e1f227b3d92bd384e", + "barcodes.tsv.gz:md5,f62a276e262fdd85262a889d0f48556b", + "features.tsv.gz:md5,f7fe456894f58241d622077e2fe4b548", + "matrix.mtx.gz:md5,eb787b86f095acac4435c9014e09daa7", + "barcodes.tsv.gz:md5,291349dc31e204c71c4b121de4959686", + "features.tsv.gz:md5,6ac5b411e9e5b8eee2bc58ddf3ce6f2f", + "matrix.mtx.gz:md5,9d676ae60340181e1f227b3d92bd384e", + "barcodes.tsv.gz:md5,f62a276e262fdd85262a889d0f48556b", + "features.tsv.gz:md5,f7fe456894f58241d622077e2fe4b548", + "matrix.mtx.gz:md5,eb787b86f095acac4435c9014e09daa7", + "barcodes.tsv.gz:md5,f62a276e262fdd85262a889d0f48556b", + "features.tsv.gz:md5,f7fe456894f58241d622077e2fe4b548", + "matrix.mtx.gz:md5,eb787b86f095acac4435c9014e09daa7", + "barcodes.tsv.gz:md5,f62a276e262fdd85262a889d0f48556b", + "features.tsv.gz:md5,f7fe456894f58241d622077e2fe4b548", + "matrix.mtx.gz:md5,eb787b86f095acac4435c9014e09daa7", + "barcodes.tsv.gz:md5,f62a276e262fdd85262a889d0f48556b", + "features.tsv.gz:md5,f7fe456894f58241d622077e2fe4b548", + "matrix.mtx.gz:md5,eb787b86f095acac4435c9014e09daa7" + ] + ], + "meta": { + "nf-test": "0.9.2", + "nextflow": "25.04.3" + }, + "timestamp": "2025-07-05T08:26:05.633684679" + } +} \ No newline at end of file From d7329504b5140c0c00c68d06611b60763e941d52 Mon Sep 17 00:00:00 2001 From: LuisHeinzlmeier Date: Tue, 16 Sep 2025 19:32:18 +0200 Subject: [PATCH 36/74] Aggregate hashing (#68) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * first steps * current changes (not tested yet) * save changes * htodemux and multiseq in hash_summary.py (without mudata and anndata) and debugging of ch_hashing_summary * solve summary input with join * pipeline finally calls the script * empty inputs solved * add all inputs to main.nf (empty inputs still work) * update htodemux in script * script passed and produced results * correct results * solve incorrect merging * add demuxem (runs but merging to the summary table is not working) * remove the duplicate variable name * add demuxem to summary (has more barcodes than htodemux and multiseq) * add hasheddrops * add gmmdemux to summary * first steps to improve hasheddrops outputs * fix hasheddrops output * fix using unknown as value * remove comments * adding hashsolo doesn't work (cannot access the correct cell_hashing_columns) * add bff summary function * bff works * join into barcodes from raw hto file to adress demuxem having more output barcodes that input barcodes * pipeline pass with class Arguments * move testing to Argument class * restructure hash_summary.py * mudata and anndata outputs are created * hashsolo works * replace manual str with args variable * update hasheddrops * update modules and improve linting * Skip “prettier” failing – covered in #69 * clean up code 1 * clean code 2 (remove hard-coded parameters) * clean code 3 (hash_sumary.py) * update modules bff and hashsolo * update schema * remove properties created by nf-core pipelines schema build --no-prompts * structure hashing paramters of nextflow.config * update hasheddrops (removed one TODO in hash summary, hardcoding of mapping from id to hash) * remove hardcoded hash_list * remove linting error * use left join for classification and small improvements for summary csv * remove debugging outputs * Fix demuxem MPLCONFIGDIR * Add hash_summary environment variables * Comment out arg printing in hash_summary * Update demuxem patch file * Prettier * update snapshot --------- Co-authored-by: Nico Trummer --- conf/modules.config | 135 +++- conf/test.config | 2 +- modules.json | 16 +- modules/local/extract_hashes/main.nf | 28 + modules/local/hash_summary/environment.yml | 11 + modules/local/hash_summary/main.nf | 42 ++ .../hash_summary/templates/hash_summary.py | 426 +++++++++++ modules/nf-core/bff/environment.yml | 8 + modules/nf-core/bff/main.nf | 39 + modules/nf-core/bff/meta.yml | 83 +++ modules/nf-core/bff/templates/bff.R | 202 ++++++ modules/nf-core/bff/tests/main.nf.test | 164 +++++ modules/nf-core/bff/tests/main.nf.test.snap | 392 ++++++++++ modules/nf-core/bff/tests/nextflow.config | 5 + modules/nf-core/demuxem/demuxem.diff | 56 +- modules/nf-core/demuxem/main.nf | 3 + modules/nf-core/hasheddrops/main.nf | 56 +- modules/nf-core/hasheddrops/meta.yml | 75 +- .../hasheddrops/templates/HashedDrops.R | 302 +++++--- .../hasheddrops/tests/main.nf.test.snap | 115 ++- modules/nf-core/htodemux/main.nf | 27 +- modules/nf-core/htodemux/meta.yml | 40 +- modules/nf-core/htodemux/templates/HTODemux.R | 140 +++- .../nf-core/htodemux/tests/main.nf.test.snap | 61 +- modules/nf-core/multiseqdemux/main.nf | 25 +- modules/nf-core/multiseqdemux/meta.yml | 37 +- .../multiseqdemux/templates/MultiSeqDemux.R | 110 ++- .../nf-core/scanpy/hashsolo/environment.yml | 11 + modules/nf-core/scanpy/hashsolo/main.nf | 46 ++ modules/nf-core/scanpy/hashsolo/meta.yml | 91 +++ .../scanpy/hashsolo/templates/hashsolo.py | 204 ++++++ .../scanpy/hashsolo/tests/main.nf.test | 235 ++++++ .../scanpy/hashsolo/tests/main.nf.test.snap | 668 ++++++++++++++++++ .../scanpy/hashsolo/tests/nextflow.config | 5 + nextflow.config | 90 ++- nextflow_schema.json | 270 +++++-- .../local/hash_demultiplexing/main.nf | 148 +++- tests/default.nf.test.snap | 113 ++- 38 files changed, 3917 insertions(+), 564 deletions(-) create mode 100644 modules/local/extract_hashes/main.nf create mode 100644 modules/local/hash_summary/environment.yml create mode 100644 modules/local/hash_summary/main.nf create mode 100644 modules/local/hash_summary/templates/hash_summary.py create mode 100644 modules/nf-core/bff/environment.yml create mode 100644 modules/nf-core/bff/main.nf create mode 100644 modules/nf-core/bff/meta.yml create mode 100644 modules/nf-core/bff/templates/bff.R create mode 100644 modules/nf-core/bff/tests/main.nf.test create mode 100644 modules/nf-core/bff/tests/main.nf.test.snap create mode 100644 modules/nf-core/bff/tests/nextflow.config create mode 100644 modules/nf-core/scanpy/hashsolo/environment.yml create mode 100644 modules/nf-core/scanpy/hashsolo/main.nf create mode 100644 modules/nf-core/scanpy/hashsolo/meta.yml create mode 100644 modules/nf-core/scanpy/hashsolo/templates/hashsolo.py create mode 100644 modules/nf-core/scanpy/hashsolo/tests/main.nf.test create mode 100644 modules/nf-core/scanpy/hashsolo/tests/main.nf.test.snap create mode 100644 modules/nf-core/scanpy/hashsolo/tests/nextflow.config diff --git a/conf/modules.config b/conf/modules.config index 89bd8f2e..393a5c4d 100644 --- a/conf/modules.config +++ b/conf/modules.config @@ -243,6 +243,7 @@ process { ] } + withName: GMMDEMUX { ext.args = { [ @@ -269,13 +270,23 @@ process { } withName: HTODEMUX { - ext.quantile = params.htodemux_quantile - ext.init = params.htodemux_init - ext.nstarts = params.htodemux_nstarts - ext.kfunc = params.htodemux_kfunc - ext.nsamples = params.htodemux_nsamples - ext.seed = params.htodemux_seed - ext.verbose = params.htodemux_verbose + ext.args = { + [ + "--quantile", + params.htodemux_quantile, + params.htodemux_init ? "--init ${params.htodemux_init}" : "", + "--nstarts", + params.htodemux_nstarts, + "--kfunc", + params.htodemux_kfunc, + "--nsamples", + params.htodemux_nsamples, + "--seed", + params.htodemux_seed, + "--verbose", + params.htodemux_verbose.toString().toUpperCase() + ].join(" ") + } publishDir = [ path: { "${params.outdir}/hashing/htodemux/${meta.id}" }, mode: params.publish_dir_mode, @@ -283,6 +294,7 @@ process { ] } + // TODO update to ext.args withName: HTODEMUX_VISUALIZATION { ext.ridgePlot = params.htodemux_visualization_ridgePlot ext.ridgeNCol = params.htodemux_visualization_ridgeNCol @@ -309,13 +321,24 @@ process { } withName: MULTISEQDEMUX { - ext.quantile = params.multiseqdemux_quantile - ext.autoThresh = params.multiseqdemux_autoThresh - ext.maxiter = params.multiseqdemux_maxiter - ext.qrangeFrom = params.multiseqdemux_qrangeFrom - ext.qrangeTo = params.multiseqdemux_qrangeTo - ext.qrangeBy = params.multiseqdemux_qrangeBy - ext.verbose = params.multiseqdemux_verbose + ext.args = { + [ + "--quantile", + params.multiseqdemux_quantile, + "--autoThresh", + params.multiseqdemux_autoThresh.toString().toUpperCase(), + "--maxiter", + params.multiseqdemux_maxiter, + "--qrangeFrom", + params.multiseqdemux_qrangeFrom, + "--qrangeTo", + params.multiseqdemux_qrangeTo, + "--qrangeBy", + params.multiseqdemux_qrangeBy, + "--verbose", + params.multiseqdemux_verbose.toString().toUpperCase() + ].join(" ") + } publishDir = [ path: { "${params.outdir}/hashing/multiseqdemux/${meta.id}" }, mode: params.publish_dir_mode, @@ -324,25 +347,44 @@ process { } withName: HASHEDDROPS { - ext.lower = params.hasheddrops_lower - ext.niters = params.hasheddrops_niters - ext.testAmbient = params.hasheddrops_testAmbient - ext.round = params.hasheddrops_round - ext.byRank = params.hasheddrops_byRank - ext.isCellFDR = params.hasheddrops_isCellFDR - ext.gene_col = params.hasheddrops_gene_col - ext.ignore = params.hasheddrops_ignore - ext.alpha = params.hasheddrops_alpha - ext.ambient = params.hasheddrops_ambient - ext.minProp = params.hasheddrops_minProp - ext.pseudoCount = params.hasheddrops_pseudoCount - ext.constantAmbient = params.hasheddrops_constantAmbient - ext.doubletNmads = params.hasheddrops_doubletNmads - ext.doubletMin = params.hasheddrops_doubletMin - ext.doubletMixture = params.hasheddrops_doubletMixture - ext.confidentNmads = params.hasheddrops_confidentNmads - ext.confidentMin = params.hasheddrops_confidentMin - ext.combinations = params.hasheddrops_combinations + ext.args = { + [ + "--lower", + params.hasheddrops_lower, + "--niters", + params.hasheddrops_niters, + "--testAmbient", + params.hasheddrops_testAmbient.toString().toUpperCase(), + "--round", + params.hasheddrops_round.toString().toUpperCase(), + params.hasheddrops_byRank ? "--byRank ${params.hasheddrops_byRank}" : "", + "--isCellFDR", + params.hasheddrops_isCellFDR, + "--gene_col", + params.hasheddrops_gene_col, + params.hasheddrops_ignore ? "--ignore ${params.hasheddrops_ignore}" : "", + params.hasheddrops_alpha ? "--alpha ${params.hasheddrops_alpha}" : "", + "--ambient", + params.hasheddrops_ambient.toString().toUpperCase(), + "--minProp", + params.hasheddrops_minProp, + "--pseudoCount", + params.hasheddrops_pseudoCount, + "--constantAmbient", + params.hasheddrops_constantAmbient.toString().toUpperCase(), + "--doubletNmads", + params.hasheddrops_doubletNmads, + "--doubletMin", + params.hasheddrops_doubletMin, + "--doubletMixture", + params.hasheddrops_doubletMixture.toString().toUpperCase(), + "--confidentNmads", + params.hasheddrops_confidentNmads, + "--confidentMin", + params.hasheddrops_confidentMin, + params.hasheddrops_combinations ? "--combinations ${params.hasheddrops_combinations}" : "", + ].join(" ") + } publishDir = [ path: { "${params.outdir}/hashing/hasheddrops/${meta.id}" }, mode: params.publish_dir_mode, @@ -350,6 +392,15 @@ process { ] } + withName: HASH_SUMMARY { + publishDir = [ + path: { "${params.outdir}/hashing/summary/${meta.id}" }, + mode: params.publish_dir_mode, + saveAs: { filename -> filename.equals('versions.yml') ? null : filename }, + ] + } + + // TODO update to ext.args withName: PREPROCESSING_FOR_HTODEMUX_MULTISEQ { ext.sel_method = params.preprocessing_sel_method ext.ndelim = params.preprocessing_ndelim @@ -373,4 +424,22 @@ process { saveAs: { filename -> filename.equals('versions.yml') ? null : filename }, ] } + + withName: SCANPY_HASHSOLO { + ext.args = { + [ + "--priors", + params.hashsolo_priors.split(",").join(" "), + params.hashsolo_pre_existing_clusters ? "--pre_existing_clusters ${params.hashsolo_pre_existing_clusters}" : "", + params.hashsolo_clustering_data ? "--clustering_data ${params.hashsolo_clustering_data}" : "", + params.hashsolo_number_of_noise_barcodes ? "--number_of_noise_barcodes ${params.hashsolo_number_of_noise_barcodes}" : "", + params.hashsolo_round_digits ? "--round_digits ${params.hashsolo_round_digits}" : "" + ].join(" ") + } + publishDir = [ + path: { "${params.outdir}/hashing/hashsolo/${meta.id}" }, + mode: params.publish_dir_mode, + saveAs: { filename -> filename.equals('versions.yml') ? null : filename }, + ] + } } diff --git a/conf/test.config b/conf/test.config index 604fa52b..580f6312 100644 --- a/conf/test.config +++ b/conf/test.config @@ -24,7 +24,7 @@ params { // Input data input = 'https://github.com/nf-core/test-datasets/raw/refs/heads/hadge/samplesheet.csv' - hash_tools = 'htodemux,hasheddrops,multiseq,demuxem,gmm-demux' + hash_tools = 'htodemux,hasheddrops,multiseq,demuxem,gmm-demux,cellhashr,hashsolo' genetic_tools = 'demuxlet,freemuxlet,vireo' mode = 'rescue' bam_qc = true diff --git a/modules.json b/modules.json index ff7aebd9..04935b5d 100644 --- a/modules.json +++ b/modules.json @@ -5,6 +5,11 @@ "https://github.com/nf-core/modules.git": { "modules": { "nf-core": { + "bff": { + "branch": "master", + "git_sha": "4c2012fe04a7850c9e222ca5e81c8220b94027de", + "installed_by": ["modules"] + }, "cellsnp/modea": { "branch": "master", "git_sha": "05954dab2ff481bcb999f24455da29a5828af08d", @@ -25,13 +30,13 @@ }, "hasheddrops": { "branch": "master", - "git_sha": "ac3ee5db3835e45ff9b9f03274f45049bd7028a3", + "git_sha": "0cb99468d3666ac3babf10b270720e1b99319f90", "installed_by": ["modules"], "patch": "modules/nf-core/hasheddrops/hasheddrops.diff" }, "htodemux": { "branch": "master", - "git_sha": "e793890476782166ebaabded864cca7a5b0438dd", + "git_sha": "6d8e4eb4f0e2790646d0466d1073db95610883f0", "installed_by": ["modules"] }, "multiqc": { @@ -41,7 +46,7 @@ }, "multiseqdemux": { "branch": "master", - "git_sha": "ea8d54b6ad1e8f55abdceed1ce7828360e134ebd", + "git_sha": "6d8e4eb4f0e2790646d0466d1073db95610883f0", "installed_by": ["modules"], "patch": "modules/nf-core/multiseqdemux/multiseqdemux.diff" }, @@ -77,6 +82,11 @@ "git_sha": "d090922b1a4b80ba283186459ababf8e308abcbb", "installed_by": ["modules"] }, + "scanpy/hashsolo": { + "branch": "master", + "git_sha": "2b474484e0f4c130392c4d67225e15614732e2a8", + "installed_by": ["modules"] + }, "umitools/dedup": { "branch": "master", "git_sha": "05954dab2ff481bcb999f24455da29a5828af08d", diff --git a/modules/local/extract_hashes/main.nf b/modules/local/extract_hashes/main.nf new file mode 100644 index 00000000..439d4c53 --- /dev/null +++ b/modules/local/extract_hashes/main.nf @@ -0,0 +1,28 @@ +process EXTRACT_HASHES { + tag "${meta.id}" + label 'process_low' + + input: + tuple val(meta), path(hto_matrix) + + output: + tuple val(meta), path("*_hashes.txt"), emit: hashes + + when: + task.ext.when == null || task.ext.when + + script: + prefix = task.ext.prefix ?: "${meta.id}" + + script: + """ + zcat $hto_matrix | awk '{print \$2}' | paste -sd, > ${prefix}_hashes.txt + """ + + stub: + def prefix = task.ext.prefix ?: "${meta.id}" + + """ + touch ${prefix}_hashes.txt + """ +} diff --git a/modules/local/hash_summary/environment.yml b/modules/local/hash_summary/environment.yml new file mode 100644 index 00000000..b9c006b3 --- /dev/null +++ b/modules/local/hash_summary/environment.yml @@ -0,0 +1,11 @@ +channels: + - conda-forge + - bioconda +dependencies: + - bioconda::pegasusio=0.10.0 + - conda-forge::anndata=0.11.4 + - conda-forge::mudata=0.3.1 + - conda-forge::numpy=1.24.2 + - conda-forge::pandas=2.3.1 + - conda-forge::pathlib=1.0.1 + - conda-forge::scanpy=1.11.2 diff --git a/modules/local/hash_summary/main.nf b/modules/local/hash_summary/main.nf new file mode 100644 index 00000000..e2d95ee6 --- /dev/null +++ b/modules/local/hash_summary/main.nf @@ -0,0 +1,42 @@ +process HASH_SUMMARY { + tag "${meta.id}" + label 'process_low' + + conda "${moduleDir}/environment.yml" + container "${ workflow.containerEngine == 'singularity' && !task.ext.singularity_pull_docker_container ? + 'https://community-cr-prod.seqera.io/docker/registry/v2/blobs/sha256/a5/a5f3952003b974094e3b9d92a6b3499b56554db8de0d7622e5b959842d11759e/data': + 'community.wave.seqera.io/library/pegasusio_anndata_mudata_numpy_pruned:9d13d0d12376624e' }" + + input: + tuple val(meta), path(rna_matrix), path(hto_matrix), path(htodemux_assignments), path (htodemux_classification), path(multiseq), path(bff), path(demuxem), path(gmmdemux_results), path(gmmdemux_config), path(hasheddrops_results), path(hasheddrops_id_to_hash), path(hashsolo) + tuple val (generate_anndata), val(generate_mudata), val(bff_methods) + + output: + tuple val(meta), path("*_hashing_summary_assignment.csv") , emit: assignment , optional: false + tuple val(meta), path("*_hashing_summary_classification.csv"), emit: classification, optional: false + tuple val(meta), path("*_hashing_summary.h5ad") , emit: h5ad , optional: true + tuple val(meta), path("*_hashing_summary.h5mu") , emit: h5mu , optional: true + + when: + task.ext.when == null || task.ext.when + + script: + prefix = task.ext.prefix ?: "${meta.id}" + hash_list = "${meta.hashes}".split(",") + + template 'hash_summary.py' + + stub: + def prefix = task.ext.prefix ?: "${meta.id}" + """ + touch ${prefix}_hashing_summary_assignment.csv + touch ${prefix}_hashing_summary_classification.csv + + + cat <<-END_VERSIONS > versions.yml + "${task.process}": + r-seurat: \$(Rscript -e "library(Seurat); cat(as.character(packageVersion('Seurat')))") + r-base: \$(Rscript -e "cat(strsplit(R.version[['version.string']], ' ')[[1]][3])") + END_VERSIONS + """ +} diff --git a/modules/local/hash_summary/templates/hash_summary.py b/modules/local/hash_summary/templates/hash_summary.py new file mode 100644 index 00000000..b018a31f --- /dev/null +++ b/modules/local/hash_summary/templates/hash_summary.py @@ -0,0 +1,426 @@ +#!/usr/bin/env python3 + +import os + +os.environ["MPLCONFIGDIR"] = "./tmp/mpl" +os.environ["NUMBA_CACHE_DIR"] = "./tmp/numba" + +import pandas as pd +import scanpy as sc +import numpy as np +from pathlib import Path +from mudata import MuData +from anndata import AnnData +from typing import Dict +from typing import Tuple +import pegasusio as io + +class Arguments: + # adopted from mygene module (Suzanne Jin) + """ + Parses the arguments, including the ones coming from $task.ext.args. + """ + + def __init__(self) -> None: + + self.singlet_str = "singlet" + self.doublet_str = "doublet" + self.negative_str = "negative" + self.parse_input_args() + self.creat_output_dirs() + self.testing_inputs() + + def parse_input_args(self) -> None: + + + self.prefix = "$task.ext.prefix" if "$task.ext.prefix" != "null" else "$meta.id" + + self.rna_matrix = "${rna_matrix}" + self.hto_matrix = "${hto_matrix}" + self.htodemux_assignments = "${htodemux_assignments}" + self.htodemux_classification = "${htodemux_classification}" + self.multiseq = "${multiseq}" + self.bff = "${bff}" + self.demuxem = "${demuxem}" + self.gmmdemux_results = "${gmmdemux_results}" + self.gmmdemux_config = "${gmmdemux_config}" + self.hasheddrops_results = "${hasheddrops_results}" + self.hasheddrops_id_to_hash = "${hasheddrops_id_to_hash}" + self.hashsolo = "${hashsolo}" + + self.generate_anndata = "${generate_anndata}" + self.generate_mudata = "${generate_mudata}" + self.bff_methods = "${bff_methods}" + self.hash_list = "${hash_list}" + + path_vars = { + "rna_matrix", + "hto_matrix", + "htodemux_assignments", + "htodemux_classification", + "multiseq", + "bff", + "demuxem", + "gmmdemux_results", + "gmmdemux_config", + "hasheddrops_results", + "hasheddrops_id_to_hash", + "hashsolo", + } + + boolean_vars = { + "generate_anndata", + "generate_mudata" + } + + other_vars = { + "bff_methods", + "hash_list" + } + + def _tranlate_to_python(input_str,value_str): + if value_str.strip() == "": + return None + else: + if input_str in path_vars: + return Path(value_str) + elif input_str in boolean_vars: + if value_str == "true": + return True + else: + return False + elif input_str == "bff_methods": + if value_str == 'RAW': + return ['bff_raw'] + elif value_str == 'CLUSTER': + return ['bff_cluster'] + elif value_str == 'BOTH': + return ['bff_raw', 'bff_cluster','bff_consensuscall'] + else: + raise ValueError(f"Methods ({value_str}) for bff not specified correctly. Choose RAW, CLUSTER or BOTH as input.") + elif input_str == "hash_list": + return set(hash.strip() for hash in "${hash_list}".strip("[]").split(",")) + + vars = path_vars | boolean_vars | other_vars + + for var in vars: + raw_value = getattr(self, var) + processed_value = _tranlate_to_python(var, raw_value) + setattr(self, var, processed_value) + + def creat_output_dirs(self) -> None: + directories = { + 'assignment': '_hashing_summary_assignment.csv', + 'classification': '_hashing_summary_classification.csv', + 'h5mu': '_hashing_summary.h5mu', + 'h5ad': '_hashing_summary.h5ad' + } + + for output, directory in directories.items(): + setattr(self, output, self.prefix + directory) + + def testing_inputs(self) -> None: + if [self.htodemux_assignments, self.htodemux_classification].count(None) == 1: + raise ValueError("The assignment or classification file of htodemux is empty.") + + if [self.gmmdemux_results, self.gmmdemux_config].count(None) == 1: + raise ValueError("The results or config file of gmmdemux is empty.") + + def print_args(self) -> None: + """ + Print the arguments. + """ + for attr in vars(self): + print(f"{attr}: {getattr(self, attr)}") + +class ProcessModuleOutput: + + def __init__(self): + # necessary to verify which functions should be called + # because gmmdemux, hasheddrops and htodemux need two input files + self.function_name_to_args_name = { + 'demuxem': 'demuxem', + 'hashsolo': 'hashsolo', + 'hasheddrops': 'hasheddrops_results', + 'multiseq': 'multiseq', + 'htodemux': 'htodemux_assignments', + 'gmmdemux': 'gmmdemux_results', + 'bff': 'bff' + } + + self.checkHashNames = True + self.chechEmptyInput = True + + # TODO add Barcode as index in all functions and add the index name "Barcode" + + def demuxem(self, args: Arguments) -> Tuple[pd.DataFrame, pd.DataFrame]: + + data = io.read_input(str(args.demuxem)) + classification = data.obs['demux_type'].to_frame() + classification.reset_index(inplace=True) + classification.columns = ["Barcode", "demuxem"] + classification['demuxem'] = classification['demuxem'].cat.rename_categories({"unknown": args.negative_str}) + + # TODO demuxem has more output barcodes than input barcodes metioned here: https://github.com/lilab-bcb/demuxEM/issues/20 + assignment = data.obs['assignment'].to_frame() + assignment.reset_index(inplace=True) + assignment.columns = ["Barcode", "demuxem"] + + return assignment, classification + + def hashsolo(self, args: Arguments) -> Tuple[pd.DataFrame, pd.DataFrame]: + + results = pd.read_csv(args.hashsolo, index_col=0) + assignment = results[["Classification"]] + + assignment.columns = ["hashsolo"] + assignment = assignment.replace( + {"Doublet": args.doublet_str, + "Negative": args.negative_str} + ) + + classification = results[["most_likely_hypothesis"]].copy() + classification["most_likely_hypothesis"] = ( + classification["most_likely_hypothesis"] + .replace({0.0: args.negative_str, 1.0: args.singlet_str, 2.0: args.doublet_str}) + ) + + return assignment, classification + + def hasheddrops(self, args: Arguments) -> Tuple[pd.DataFrame, pd.DataFrame]: + + idx_to_htoname_df = pd.read_csv(args.hasheddrops_id_to_hash) + print(idx_to_htoname_df) + idx_to_htoname_df.loc[len(idx_to_htoname_df)] = [np.nan, args.negative_str] + idx_to_htoname_map = idx_to_htoname_df.set_index('Index')['HTO'].to_dict() + + obs_res = pd.read_csv(args.hasheddrops_results) + + obs_res["Classification"] = np.where( + obs_res["Confident"] & obs_res["Confident"].notna(), + args.singlet_str, + np.where(obs_res["Doublet"] & obs_res["Doublet"].notna(), args.doublet_str, args.negative_str) + ) + + obs_res["Assignment"] = np.where( + obs_res["Classification"].isin([args.doublet_str, args.negative_str]), + obs_res["Classification"], + obs_res["Best"].map(idx_to_htoname_map), + ) + + obs_res.rename(columns={obs_res.columns[0]: "Barcode"}, inplace=True) + + print(obs_res) + + classification = obs_res[["Barcode", "Classification"]].rename(columns={"Classification": "hasheddrops"}) + assignment = obs_res[["Barcode", "Assignment"]].rename(columns={"Assignment": "hasheddrops"}) + + return assignment,classification + + def multiseq(self, args: Arguments) -> Tuple[pd.DataFrame, pd.DataFrame]: + + assignment = pd.read_csv(args.multiseq) + assignment.columns = ["Barcode", "multiseq"] + assignment.replace( + {"Doublet": args.doublet_str, "Negative": args.negative_str}, inplace=True + ) + + classification = assignment.copy() + classification.loc[(classification["multiseq"] != args.doublet_str) & (classification["multiseq"] != args.negative_str), "multiseq"] = args.singlet_str + + return assignment, classification + + def htodemux(self, args: Arguments) -> Tuple[pd.DataFrame, pd.DataFrame]: + + assignment = pd.read_csv(args.htodemux_assignments) + assignment.columns = ["Barcode", "htodemux"] + assignment.replace("Doublet", args.doublet_str, inplace=True) + assignment.replace( + {"Doublet": args.doublet_str, "Negative": args.negative_str}, inplace=True + ) + + classification = pd.read_csv(args.htodemux_classification) + classification.columns = ["Barcode", "htodemux"] + classification.replace( + {"Singlet": args.singlet_str, "Doublet": args.doublet_str, "Negative": args.negative_str}, inplace=True + ) + + return assignment, classification + + def gmmdemux(self, args: Arguments) -> Tuple[pd.DataFrame, pd.DataFrame]: + + number_of_hashes = len(args.hash_list) + + df_config = pd.read_csv(args.gmmdemux_config, header=None, skipinitialspace=True) + df_config.columns = ["Cluster_id", "Description"] + + def _classify_hash(cluster_id: int, number_hashes: int) -> str: + if cluster_id == 0: + return args.negative_str + elif 1 <= cluster_id <= number_hashes: + return args.singlet_str + else: + return args.doublet_str + + df_config["Classification"] = df_config["Cluster_id"].apply( + lambda cluster_id: _classify_hash(cluster_id, number_of_hashes) + ) + + df_config["Assignment"] = df_config["Description"].where( + df_config["Classification"] == args.singlet_str, + other=df_config["Classification"] + ) + + # results with Cluster_id's + df_results = pd.read_csv(args.gmmdemux_results) + df_results.columns = ["Barcode", "Cluster_id", "Confidence"] + + df_results = df_results.merge(df_config, on="Cluster_id", how="left") + + assignment = df_results[["Barcode", "Assignment"]] + assignment.columns = ["Barcode", "gmmdemux"] + + classification = df_results[["Barcode", "Classification"]] + classification.columns = ["Barcode", "gmmdemux"] + + return assignment, classification + + def bff(self, args: Arguments) -> Tuple[pd.DataFrame, pd.DataFrame]: + + df_result = pd.read_csv(args.bff) + + df_result.rename(columns={ + 'cellbarcode': 'Barcode', + 'consensuscall': 'bff_consensuscall' + }, inplace=True) + + assignment = df_result[['Barcode'] + args.bff_methods].copy() + + assignment[args.bff_methods] = assignment[args.bff_methods].replace({ + 'Doublet': args.doublet_str, + 'Negative': args.negative_str, + 'Discordant': 'discordant' + }) + + valid_values = {args.negative_str, args.doublet_str, 'discordant'} + + # Define classification function + def classify_value(x): + if x in valid_values: + return x + elif x in args.hash_list: + return args.singlet_str + else: + raise ValueError(f"Value '{x}' in BFF is not 'Negative', 'Doublet', or one of the hashes in the used hashes list") + + if len(args.bff_methods) == 3: + # use the classification of consensuscall.global + used_methods = args.bff_methods - ["bff_consensuscall"] + ["consensuscall.global"] + else: + used_methods = args.bff_methods + + # apply classification only to used_methods columns + classification = assignment[['Barcode'] + used_methods].copy() + classification.rename(columns={'consensuscall.global': 'bff_consensuscall'}, inplace=True) + + classification[used_methods] = classification[used_methods].applymap(classify_value) + + return assignment, classification + +def printProccedOutput() -> None: + # TODO add a function that shows and maybe checks processed results before joining + print("----- Assignments -----") + print("") + + for assignment in assignments: + counts = assignment[assignment.columns[1]].value_counts() + length = len(assignment) + print(counts) + print("length: ", length) + print("") + + print("----- Classifications -----") + print("") + + for classification in classifications: + counts = classification[classification.columns[1]].value_counts() + length = len(classification) + print(counts) + print("length: ", length) + print("") + +if __name__ == "__main__": + + # ======================== process nextflow input arguments ======================== + + args = Arguments() + # args.print_args() + + + # ========================= process results from modules =========================== + + rna_data = sc.read_10x_mtx(args.rna_matrix) + hto_data = sc.read_10x_mtx(args.hto_matrix, gex_only=False) + + # call all functions that process the module outptus + + assignments = [] + classifications = [] + + functions = ProcessModuleOutput() + function_names = list(functions.function_name_to_args_name.keys()) + for function in function_names: + if getattr(args,functions.function_name_to_args_name.get(function)) is not None: + assignment, classification = getattr(functions,function)(args) + assignments.append(assignment) + classifications.append(classification) + + + # ================================== save results ================================== + + # ----------------------------------- save csv's ----------------------------------- + + # TODO restructure the if statement if I keep using the hto_data + # have to to this because demuxem has more barcodes as output that it received as input + # https://github.com/lilab-bcb/demuxEM/issues/20 + + assignment_summary = pd.DataFrame(hto_data.obs_names, columns=['Barcode']) + classification_summary = assignment_summary.copy() + + for assignment in assignments: + assignment_summary = pd.merge(assignment_summary, assignment, on="Barcode", how="left").replace("", args.negative_str) + + assignment_summary.to_csv(args.assignment, index=False) + + for classification in classifications: + classification_summary = pd.merge(classification_summary, classification, on="Barcode", how="left") + + classification_summary.to_csv(args.classification, index=False) + + assignment_summary.set_index("Barcode", inplace=True) + print(assignment_summary) + + # -------------------------------- save mudata/anndata ----------------------------- + + if args.generate_mudata or args.generate_anndata: + # join on index (Barcode) + rna_data.obs = rna_data.obs.join(assignment_summary, how="left") + # fill all empty of the used modules with negative values (for expression data) + used_modules = list(assignment_summary.columns) + for col in used_modules: + if pd.api.types.is_categorical_dtype(rna_data.obs[col]): + if args.negative_str not in rna_data.obs[col].cat.categories: + rna_data.obs[col] = rna_data.obs[col].cat.add_categories([args.negative_str]) + + rna_data.obs[used_modules] = rna_data.obs[used_modules].fillna(args.negative_str) + rna_data.obs[used_modules] = rna_data.obs[used_modules].astype(str) + + if args.generate_mudata: + # join on index (Barcode) and create a mudata object + hto_data.obs = hto_data.obs.join(assignment_summary, how="left") + mudata = MuData({"rna": rna_data, "hto": hto_data}) + # TODO mudata update? + mudata.write(args.h5mu) + + if args.generate_anndata: + rna_data.write(args.h5ad) diff --git a/modules/nf-core/bff/environment.yml b/modules/nf-core/bff/environment.yml new file mode 100644 index 00000000..ef2967cd --- /dev/null +++ b/modules/nf-core/bff/environment.yml @@ -0,0 +1,8 @@ +--- +# yaml-language-server: $schema=https://raw.githubusercontent.com/nf-core/modules/master/modules/environment-schema.json +channels: + - conda-forge + - bioconda +dependencies: + - bioconda::bioconductor-cellhashr=1.04 + - conda-forge::r-seurat=4.3.0 diff --git a/modules/nf-core/bff/main.nf b/modules/nf-core/bff/main.nf new file mode 100644 index 00000000..8d86008c --- /dev/null +++ b/modules/nf-core/bff/main.nf @@ -0,0 +1,39 @@ +process BFF { + tag "${meta.id}" + label 'process_medium' + + conda "${moduleDir}/environment.yml" + container "${ workflow.containerEngine == 'singularity' && !task.ext.singularity_pull_docker_container ? + 'oras://community.wave.seqera.io/library/bioconductor-cellhashr_r-seurat:1c94360d8ed188c4': + 'community.wave.seqera.io/library/bioconductor-cellhashr_r-seurat:25c4bc76749af5ac' }" + + input: + tuple val(meta), path(hto_matrix), val(methods), val(preprocessing) + + output: + tuple val(meta), path("*_assignment_bff.csv"), emit: assignment + tuple val(meta), path("*_metrics_bff.csv") , emit: metrics + tuple val(meta), path("*_params_bff.csv") , emit: params + path "versions.yml" , emit: versions + + when: + task.ext.when == null || task.ext.when + + script: + template('bff.R') + + stub: + def prefix = task.ext.prefix ?: "${meta.id}" + """ + touch ${prefix}_assignment_bff.csv + touch ${prefix}_metrics_bff.csv + touch ${prefix}_params_bff.csv + + cat <<-END_VERSIONS > versions.yml + "${task.process}": + r-base: \$(Rscript -e "cat(strsplit(R.version[['version.string']], ' ')[[1]][3])") + r-seurat: \$(Rscript -e "library(Seurat); cat(as.character(packageVersion('Seurat')))") + cellhashR: \$(Rscript -e "library(cellhashR); cat(as.character(packageVersion('cellhashR')))") + END_VERSIONS + """ +} diff --git a/modules/nf-core/bff/meta.yml b/modules/nf-core/bff/meta.yml new file mode 100644 index 00000000..4396a4b1 --- /dev/null +++ b/modules/nf-core/bff/meta.yml @@ -0,0 +1,83 @@ +--- +# yaml-language-server: $schema=https://raw.githubusercontent.com/nf-core/modules/master/modules/meta-schema.json +name: "bff" +description: Generating cell hashing calls from a matrix of count data. +keywords: + - demultiplexing + - hashing-based deconvolution + - single-cell +tools: + - "bff": + description: "A toolkit for quality control, analysis, and exploration of single cell RNA sequencing data. 'Seurat' aims to enable users to identify and interpret sources of heterogeneity from single cell transcriptomic measurements, and to integrate diverse types of single cell data. See Satija R, Farrell J, Gennert D, et al (2015) , Macosko E, Basu A, Satija R, et al (2015) , and Butler A and Satija R (2017) for more details." + homepage: "https://rdrr.io/github/BimberLab/cellhashR/man/GenerateCellHashingCalls.html" + documentation: "https://rdrr.io/github/BimberLab/cellhashR/man/GenerateCellHashingCalls.html" + tool_dev_url: "https://github.com/BimberLab/cellhashR" + doi: "10.5281/zenodo.6402477" + licence: ["GPL-3"] + identifier: "" + +input: + - - meta: + type: map + description: | + Groovy Map containing sample information + e.g. `[ id:'sample1' ]` + - hto_matrix: + type: file + description: | + Directory that contains the HTO matrix in a 10X format. + ontologies: + - edam: "http://edamontology.org/data_3917" # count matrix + - methods: + type: string + description: | + Decides whether 'RAW', 'CLUSTER' OR 'COMBINED' should be selected as the call method. + - preprocessing: + type: string + description: | + Decides whether the HTO matrix should undergo a preprocessing step ('TRUE') or not ('FALSE'). + +output: + assignment: + - - meta: + type: file + description: | + Groovy Map containing sample information + e.g. `[ id:'sample1' ]` + - "*_assignment_bff.csv": + type: file + description: Contains the assignment results of demultiplexing. + pattern: "assignment_bff.csv" + + metrics: + - - meta: + type: file + description: | + Groovy Map containing sample information + e.g. `[ id:'sample1' ]` + - "*_metrics_bff.csv": + type: file + description: Summary metrics will be written to this file. + pattern: "_metrics_bff.csv" + + params: + - - meta: + type: map + description: | + Groovy Map containing sample information + e.g. `[ id:'sample1' ]` + - "*_params_bff.csv": + type: file + description: The used parameters to call HTODemux in the R-Script. + pattern: "params_htodemux.csv" + + versions: + - versions.yml: + type: file + description: File containing software versions. + pattern: "versions.yml" + +authors: + - "@LuisHeinzlmeier" +maintainers: + - "@LuisHeinzlmeier" diff --git a/modules/nf-core/bff/templates/bff.R b/modules/nf-core/bff/templates/bff.R new file mode 100644 index 00000000..c114dd18 --- /dev/null +++ b/modules/nf-core/bff/templates/bff.R @@ -0,0 +1,202 @@ +#!/usr/bin/env Rscript + +################################################ +################################################ +## Fucntions ## +################################################ +################################################ + +# Helper function for NULL condition +null_to_string <- function(x, val = "NULL") if (is.null(x)) val else x + +#' Check for Non-Empty, Non-Whitespace String +#' +#' This function checks if the input is non-NULL and contains more than just whitespace. +#' It returns TRUE if the input is a non-empty, non-whitespace string, and FALSE otherwise. +#' +#' @param input A variable to check. +#' @return A logical value: TRUE if the input is a valid, non-empty, non-whitespace string; FALSE otherwise. + +is_valid_string <- function(input) { + !is.null(input) && nzchar(trimws(input)) +} + +#' Parse out options from a string without recourse to optparse +#' +#' @param x Long-form argument list like --opt1 val1 --opt2 val2 +#' +#' @return named list of options and values similar to optparse + +parse_args <- function(x){ + args_list <- unlist(strsplit(x, ' ?--')[[1]])[-1] + args_vals <- lapply(args_list, function(x) scan(text=x, what='character', quiet = TRUE)) + + # Ensure the option vectors are length 2 (key/ value) to catch empty ones + args_vals <- lapply(args_vals, function(z){ length(z) <- 2; z}) + + parsed_args <- structure(lapply(args_vals, function(x) x[2]), names = lapply(args_vals, function(x) x[1])) + parsed_args[! is.na(parsed_args)] +} + +################################################ +################################################ +## PARSE PARAMETERS FROM NEXTFLOW ## +################################################ +################################################ + +# Set defaults and classes +opt <- list( + # module input parameters + hto_matrix = '$hto_matrix', # Path to the HTO matrix file + methods = '$methods', # Methods to use: COMBINED, RAW, CLUSTER + preprocessing = '$preprocessing', # Preprocessing method + + # ext.args for preprocessing + barcodeWhitelist = NULL, # A vector of barcode names to retain, used for preprocessing step + + # ext.args for GenerateCellHashingCalls() + cellbarcodeWhitelist = NULL, # A vector of expected cell barcodes. Allows reporting on the total set of expected barcodes, not just those in the filtered count matrix + methodsForConsensus = NULL, # By default, a consensus call will be generated using all methods, NULL, RAW or CLUSTER + metricsFile = NULL, # Path to metrics file (output) + doTSNE = FALSE, # If true, tSNE will be run on the resulting hashing calls after each caller + doHeatmap = TRUE, # If true, Seurat::HTOHeatmap will be run on the results of each caller + perCellSaturation = NULL, # An optional dataframe with the columns cellbarcode and saturation + majorityConsensusThreshold = NULL, # This applies to calculating a consensus call when multiple algorithms are used + chemistry = "10xV3", # This string is passed to EstimateMultipletRate. Should be either 10xV2 or 10xV3 + callerDisagreementThreshold = NULL, # If provided, the agreement rate will be calculated between each caller and the simple majority call, ignoring discordant and no-call cells + + prefix = ifelse('$task.ext.prefix' == 'null', '$meta.id', '$task.ext.prefix') # Prefix name for output files +) +opt_types <- lapply(opt, class) + +# Apply parameter overrides +args_string <- '$task.ext.args' +args_opt <- if (is_valid_string(args_string)) parse_args(args_string) else list() +for ( ao in names(args_opt)){ + if (! ao %in% names(opt)){ + stop(paste("Invalid option:", ao)) + }else{ + if (! is.null(opt[[ao]])){ + # Preserve classes from defaults where possible + opt[[ao]] <- as(args_opt[[ao]], opt_types[[ao]]) + } else { + opt[[ao]] <- args_opt[[ao]] + } + } +} + +# Set individual variables for backward compatibility and cleaner code +hto_matrix <- opt\$hto_matrix +methods <- opt\$methods +preprocessing <- opt\$preprocessing +barcodeWhitelist <- opt\$barcodeWhitelist +cellbarcodeWhitelist <- opt\$cellbarcodeWhitelist +methodsForConsensus <- opt\$methodsForConsensus +metricsFile <- paste0(opt\$prefix, "_metrics_bff.csv") +doTSNE <- opt\$doTSNE +doHeatmap <- opt\$doHeatmap +perCellSaturation <- opt\$perCellSaturation +majorityConsensusThreshold <- opt\$majorityConsensusThreshold +chemistry <- opt\$chemistry +callerDisagreementThreshold <- opt\$callerDisagreementThreshold +prefix <- opt\$prefix + +# check if the file exists +if (! file.exists(hto_matrix)){ + stop(paste0(hto_matrix, ' is not a valid file')) +} + +################################################ +################################################ +## Finish loading libraries ## +################################################ +################################################ + +library(Seurat) # for Read10X() +library(cellhashR) # for ProcessCountMatrix() and GenerateCellHashingCalls() + +################################################ +################################################ +## Main Process ## +################################################ +################################################ + +# perform a basic preprocessing or direcly read the data +if(preprocessing){ + + if(is.null(barcodeWhitelist)){ + barcodes_list <- NULL + }else{ + # separate the barcodes by comma and remove leading/trailing whitespace from each word + barcodes_list <- trimws(strsplit(barcodeWhitelist, ",")[[1]]) + } + + # perform preprocessing + counts <- ProcessCountMatrix(rawCountData = hto_matrix, barcodeWhitelist = barcodes_list) +}else{ + # perform preprocessing + counts <- Read10X(hto_matrix) +} + +# determine the methods that should be used +methods_input <- switch( + methods, + "COMBINED" = c("bff_raw", "bff_cluster"), + "RAW" = c("bff_raw"), + "CLUSTER" = c("bff_cluster"), + stop("'methods' must be either 'RAW', 'CLUSTER', or 'COMBINED'") +) + +# determine the final consensus call based on RAW, CLUSTER or BOTH (NULL) +methodsForConsensus_input = NULL +if (!is.null(methodsForConsensus)) { + methodsForConsensus_input <- switch(methodsForConsensus, + "RAW" = "bff_raw", + "CLUSTER" = "bff_cluster", + stop("'methodsForConsensus' must be either 'RAW', 'CLUSTER', or NULL") + ) +} + +results <- GenerateCellHashingCalls(barcodeMatrix = counts, methods = methods_input, doTSNE = doTSNE, doHeatmap = doHeatmap, methodsForConsensus = methodsForConsensus_input,cellbarcodeWhitelist = cellbarcodeWhitelist,metricsFile = metricsFile, perCellSaturation = perCellSaturation, majorityConsensusThreshold = majorityConsensusThreshold, chemistry = chemistry, callerDisagreementThreshold = callerDisagreementThreshold) + +################################################ +################################################ +## SAVING RESULTS ## +################################################ +################################################ + +#saving parameters in a dataframe +Argument <- c("hto_matrix", "methods", "methodsForConsensus", "cellbarcodeWhitelist", "metricsFile", "perCellSaturation","majorityConsensusThreshold","callerDisagreementThreshold", "doTSNE","doHeatmap","chemistry") +Value <- c(hto_matrix, null_to_string(methods), null_to_string(methodsForConsensus), null_to_string(cellbarcodeWhitelist), metricsFile, null_to_string(perCellSaturation), null_to_string(majorityConsensusThreshold), null_to_string(callerDisagreementThreshold), doTSNE, doHeatmap, chemistry) +params <- data.frame(Argument, Value) +write.csv(params, paste0(prefix ,"_params_bff.csv")) + +# save the results of GenerateCellHashingCalls or an empty dataframe if NULL +if(is.null(results)){ + results <- data.frame() +} +write.csv(results, paste0(prefix, "_assignment_bff.csv"), row.names=FALSE) + +################################################ +################################################ +## VERSIONS FILE ## +################################################ +################################################ + +r.version <- paste(R.version[['major']],R.version[['minor']], sep = ".") +seurat.version <- as.character(packageVersion('Seurat')) +cellhashR.version <- as.character(packageVersion('cellhashR')) + +writeLines( + c( + '"${task.process}":', + paste(' r-base:', r.version), + paste(' r-seurat:', seurat.version), + paste(' cellhashR:', cellhashR.version) + ), +'versions.yml') + +################################################ +################################################ +################################################ +################################################ diff --git a/modules/nf-core/bff/tests/main.nf.test b/modules/nf-core/bff/tests/main.nf.test new file mode 100644 index 00000000..5cff228a --- /dev/null +++ b/modules/nf-core/bff/tests/main.nf.test @@ -0,0 +1,164 @@ +nextflow_process { + + name "Test Process BFF" + script "../main.nf" + process "BFF" + + tag "modules" + tag "modules_nfcore" + tag "bff" + tag "untar" + + + test("hto_matrix - combined - preprocessing") { + + setup { + run("UNTAR") { + script "modules/nf-core/untar/main.nf" + process { + """ + input[0] = Channel.of([ [],file(params.modules_testdata_base_path + "/genomics/homo_sapiens/10xgenomics/cellranger/hashing_demultiplexing/hto.tar.gz", checkIfExists: true) ]) + """ + } + } + } + when { + params { + doTSNE = false + } + process { + """ + input[0] = UNTAR.out.untar.map { meta, files -> [ [ id:'test'], files, "COMBINED", "TRUE"] } + """ + } + } + + then { + assertAll( + { assert process.success }, + { assert snapshot(process.out).match() } + ) + } + } + + test("hto_matrix - combined - preprocessing - test ext.args") { + config "./nextflow.config" + + setup { + run("UNTAR") { + script "modules/nf-core/untar/main.nf" + process { + """ + input[0] = Channel.of([ [],file(params.modules_testdata_base_path + "/genomics/homo_sapiens/10xgenomics/cellranger/hashing_demultiplexing/hto.tar.gz", checkIfExists: true) ]) + """ + } + } + } + when { + params { + module_args = "--doHeatmap false" + } + process { + """ + input[0] = UNTAR.out.untar.map { meta, files -> [ [ id:'test'], files, "COMBINED", "TRUE"] } + """ + } + } + + then { + assertAll( + { assert process.success }, + { assert snapshot(process.out).match() } + ) + } + } + + + test("hto_matrix - raw - preprocessing") { + setup { + run("UNTAR") { + script "modules/nf-core/untar/main.nf" + process { + """ + input[0] = Channel.of([ [],file(params.modules_testdata_base_path + "/genomics/homo_sapiens/10xgenomics/cellranger/hashing_demultiplexing/hto.tar.gz", checkIfExists: true) ]) + """ + } + } + } + when { + process { + """ + input[0] = UNTAR.out.untar.map { meta, files -> [ [ id:'test'], files, "RAW", "TRUE"] } + """ + } + } + + then { + assertAll( + { assert process.success }, + { assert snapshot(process.out).match() } + ) + } + } + + + test("hto_matrix - combined - no preprocessing") { + setup { + run("UNTAR") { + script "modules/nf-core/untar/main.nf" + process { + """ + input[0] = Channel.of([ [],file(params.modules_testdata_base_path + "/genomics/homo_sapiens/10xgenomics/cellranger/hashing_demultiplexing/hto.tar.gz", checkIfExists: true) ]) + """ + } + } + } + when { + process { + """ + input[0] = UNTAR.out.untar.map { meta, files -> [ [ id:'test'], files, "COMBINED", "FALSE"] } + """ + } + } + + then { + assertAll( + { assert process.success }, + { assert snapshot(process.out).match() } + ) + } + } + + + + test("hto_matrix - combined - preprocessing - stub") { + + options "-stub" + + setup { + run("UNTAR") { + script "modules/nf-core/untar/main.nf" + process { + """ + input[0] = Channel.of([ [],file(params.modules_testdata_base_path + "/genomics/homo_sapiens/10xgenomics/cellranger/hashing_demultiplexing/hto.tar.gz", checkIfExists: true) ]) + """ + } + } + } + when { + process { + """ + input[0] = UNTAR.out.untar.map { meta, files -> [ [ id:'test'], files, "COMBINED", "TRUE"] } + """ + } + } + + then { + assertAll( + { assert process.success }, + { assert snapshot(process.out).match() } + ) + } + } + +} diff --git a/modules/nf-core/bff/tests/main.nf.test.snap b/modules/nf-core/bff/tests/main.nf.test.snap new file mode 100644 index 00000000..97a32d09 --- /dev/null +++ b/modules/nf-core/bff/tests/main.nf.test.snap @@ -0,0 +1,392 @@ +{ + "hto_matrix - combined - preprocessing": { + "content": [ + { + "0": [ + [ + { + "id": "test" + }, + "test_assignment_bff.csv:md5,5346a38c04d6c6e7798f1b5e46c9ca91" + ] + ], + "1": [ + [ + { + "id": "test" + }, + "test_metrics_bff.csv:md5,0b565a76802e8acf998bb35feeab35f8" + ] + ], + "2": [ + [ + { + "id": "test" + }, + "test_params_bff.csv:md5,a8885b3d8aaa64b3350137e91856ca3b" + ] + ], + "3": [ + "versions.yml:md5,71a50af83aebf56493d64a87f468a6f2" + ], + "assignment": [ + [ + { + "id": "test" + }, + "test_assignment_bff.csv:md5,5346a38c04d6c6e7798f1b5e46c9ca91" + ] + ], + "metrics": [ + [ + { + "id": "test" + }, + "test_metrics_bff.csv:md5,0b565a76802e8acf998bb35feeab35f8" + ] + ], + "params": [ + [ + { + "id": "test" + }, + "test_params_bff.csv:md5,a8885b3d8aaa64b3350137e91856ca3b" + ] + ], + "versions": [ + "versions.yml:md5,71a50af83aebf56493d64a87f468a6f2" + ] + } + ], + "meta": { + "nf-test": "0.9.2", + "nextflow": "25.04.3" + }, + "timestamp": "2025-08-07T09:36:16.222081" + }, + "hto_matrix - combined - preprocessing - stub": { + "content": [ + { + "0": [ + [ + { + "id": "test" + }, + "test_assignment_bff.csv:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "1": [ + [ + { + "id": "test" + }, + "test_metrics_bff.csv:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "2": [ + [ + { + "id": "test" + }, + "test_params_bff.csv:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "3": [ + "versions.yml:md5,71a50af83aebf56493d64a87f468a6f2" + ], + "assignment": [ + [ + { + "id": "test" + }, + "test_assignment_bff.csv:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "metrics": [ + [ + { + "id": "test" + }, + "test_metrics_bff.csv:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "params": [ + [ + { + "id": "test" + }, + "test_params_bff.csv:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "versions": [ + "versions.yml:md5,71a50af83aebf56493d64a87f468a6f2" + ] + } + ], + "meta": { + "nf-test": "0.9.2", + "nextflow": "25.04.3" + }, + "timestamp": "2025-06-17T23:03:24.350098" + }, + "hto_matrix - combined - no preprocessing": { + "content": [ + { + "0": [ + [ + { + "id": "test" + }, + "test_assignment_bff.csv:md5,d57dd3182d02b980ee231adfcef0581f" + ] + ], + "1": [ + [ + { + "id": "test" + }, + "test_metrics_bff.csv:md5,0b565a76802e8acf998bb35feeab35f8" + ] + ], + "2": [ + [ + { + "id": "test" + }, + "test_params_bff.csv:md5,a8885b3d8aaa64b3350137e91856ca3b" + ] + ], + "3": [ + "versions.yml:md5,71a50af83aebf56493d64a87f468a6f2" + ], + "assignment": [ + [ + { + "id": "test" + }, + "test_assignment_bff.csv:md5,d57dd3182d02b980ee231adfcef0581f" + ] + ], + "metrics": [ + [ + { + "id": "test" + }, + "test_metrics_bff.csv:md5,0b565a76802e8acf998bb35feeab35f8" + ] + ], + "params": [ + [ + { + "id": "test" + }, + "test_params_bff.csv:md5,a8885b3d8aaa64b3350137e91856ca3b" + ] + ], + "versions": [ + "versions.yml:md5,71a50af83aebf56493d64a87f468a6f2" + ] + } + ], + "meta": { + "nf-test": "0.9.2", + "nextflow": "25.04.3" + }, + "timestamp": "2025-08-07T09:36:51.751444" + }, + "hto_matrix - combined - preprocessing - test ext.args": { + "content": [ + { + "0": [ + [ + { + "id": "test" + }, + "test_assignment_bff.csv:md5,5346a38c04d6c6e7798f1b5e46c9ca91" + ] + ], + "1": [ + [ + { + "id": "test" + }, + "test_metrics_bff.csv:md5,0b565a76802e8acf998bb35feeab35f8" + ] + ], + "2": [ + [ + { + "id": "test" + }, + "test_params_bff.csv:md5,fbc8cbbe0c24be222cb2a0ea6261db99" + ] + ], + "3": [ + "versions.yml:md5,71a50af83aebf56493d64a87f468a6f2" + ], + "assignment": [ + [ + { + "id": "test" + }, + "test_assignment_bff.csv:md5,5346a38c04d6c6e7798f1b5e46c9ca91" + ] + ], + "metrics": [ + [ + { + "id": "test" + }, + "test_metrics_bff.csv:md5,0b565a76802e8acf998bb35feeab35f8" + ] + ], + "params": [ + [ + { + "id": "test" + }, + "test_params_bff.csv:md5,fbc8cbbe0c24be222cb2a0ea6261db99" + ] + ], + "versions": [ + "versions.yml:md5,71a50af83aebf56493d64a87f468a6f2" + ] + } + ], + "meta": { + "nf-test": "0.9.2", + "nextflow": "25.04.3" + }, + "timestamp": "2025-08-07T09:48:46.370263" + }, + "hto_matrix - raw - preprocessing": { + "content": [ + { + "0": [ + [ + { + "id": "test" + }, + "test_assignment_bff.csv:md5,b9225b970358a61d62d2e67c0011b432" + ] + ], + "1": [ + [ + { + "id": "test" + }, + "test_metrics_bff.csv:md5,14354e1e722db19e4dd9bbaabcce7ca5" + ] + ], + "2": [ + [ + { + "id": "test" + }, + "test_params_bff.csv:md5,1e3c54b1dc9786e9e21c7baf2247ddef" + ] + ], + "3": [ + "versions.yml:md5,71a50af83aebf56493d64a87f468a6f2" + ], + "assignment": [ + [ + { + "id": "test" + }, + "test_assignment_bff.csv:md5,b9225b970358a61d62d2e67c0011b432" + ] + ], + "metrics": [ + [ + { + "id": "test" + }, + "test_metrics_bff.csv:md5,14354e1e722db19e4dd9bbaabcce7ca5" + ] + ], + "params": [ + [ + { + "id": "test" + }, + "test_params_bff.csv:md5,1e3c54b1dc9786e9e21c7baf2247ddef" + ] + ], + "versions": [ + "versions.yml:md5,71a50af83aebf56493d64a87f468a6f2" + ] + } + ], + "meta": { + "nf-test": "0.9.2", + "nextflow": "25.04.3" + }, + "timestamp": "2025-08-07T09:36:35.771859" + }, + "hto_matrix - combined - preprocessing - with ext.args": { + "content": [ + { + "0": [ + [ + { + "id": "test" + }, + "test_assignment_bff.csv:md5,5346a38c04d6c6e7798f1b5e46c9ca91" + ] + ], + "1": [ + [ + { + "id": "test" + }, + "test_metrics_bff.csv:md5,0b565a76802e8acf998bb35feeab35f8" + ] + ], + "2": [ + [ + { + "id": "test" + }, + "test_params_bff.csv:md5,761dec35875c035c038fb44a5957eab4" + ] + ], + "3": [ + "versions.yml:md5,71a50af83aebf56493d64a87f468a6f2" + ], + "assignment": [ + [ + { + "id": "test" + }, + "test_assignment_bff.csv:md5,5346a38c04d6c6e7798f1b5e46c9ca91" + ] + ], + "metrics": [ + [ + { + "id": "test" + }, + "test_metrics_bff.csv:md5,0b565a76802e8acf998bb35feeab35f8" + ] + ], + "params": [ + [ + { + "id": "test" + }, + "test_params_bff.csv:md5,761dec35875c035c038fb44a5957eab4" + ] + ], + "versions": [ + "versions.yml:md5,71a50af83aebf56493d64a87f468a6f2" + ] + } + ], + "meta": { + "nf-test": "0.9.2", + "nextflow": "25.04.3" + }, + "timestamp": "2025-08-07T09:47:18.772948" + } +} \ No newline at end of file diff --git a/modules/nf-core/bff/tests/nextflow.config b/modules/nf-core/bff/tests/nextflow.config new file mode 100644 index 00000000..cdd9ea33 --- /dev/null +++ b/modules/nf-core/bff/tests/nextflow.config @@ -0,0 +1,5 @@ +process { + withName: "BFF" { + ext.args = params.module_args + } +} diff --git a/modules/nf-core/demuxem/demuxem.diff b/modules/nf-core/demuxem/demuxem.diff index 99053bce..9cb369d4 100644 --- a/modules/nf-core/demuxem/demuxem.diff +++ b/modules/nf-core/demuxem/demuxem.diff @@ -1,8 +1,9 @@ Changes in component 'nf-core/demuxem' +'modules/nf-core/demuxem/environment.yml' is unchanged Changes in 'demuxem/main.nf': --- modules/nf-core/demuxem/main.nf +++ modules/nf-core/demuxem/main.nf -@@ -1,41 +1,42 @@ +@@ -1,47 +1,56 @@ process DEMUXEM { - tag "$meta.id" + tag "${meta.id}" @@ -13,20 +14,27 @@ Changes in 'demuxem/main.nf': - 'https://depot.galaxyproject.org/singularity/demuxem:0.1.7.post1--pyhdfd78af_0' : - 'biocontainers/demuxem:0.1.7.post1--pyhdfd78af_0' }" + container "${workflow.containerEngine == 'singularity' && !task.ext.singularity_pull_docker_container -+ ? 'https://depot.galaxyproject.org/singularity/demuxem:0.1.7.post1--pyhdfd78af_0' -+ : 'biocontainers/demuxem:0.1.7.post1--pyhdfd78af_0'}" ++ ? 'https://community-cr-prod.seqera.io/docker/registry/v2/blobs/sha256/0d/0d3f96aaa8437bfa1654570e1d2b84749f1ac14d68f97978acc19b3757af7f55/data' ++ : 'community.wave.seqera.io/library/demuxem:0.1.7.post1--5ac55376ad7cb80e'}" + input: tuple val(meta), path(input_raw_gene_bc_matrices_h5), path(input_hto_csv_file) - val output_name - val generate_gender_plot +- val generate_gender_plot ++ val gender_genes val genome val generate_diagnostic_plots + output: - tuple val(meta), path("*_demux.zarr.zip"), emit: zarr - tuple val(meta), path("*.out.demuxEM.zarr.zip"), emit: out_zarr +- tuple val(meta), path("*_demux.zarr.zip"), emit: zarr +- tuple val(meta), path("*.out.demuxEM.zarr.zip"), emit: out_zarr - path "versions.yml" , emit: versions ++ tuple val(meta), path("${prefix}_demux.zarr.zip"), emit: zarr ++ tuple val(meta), path("${prefix}.out.demuxEM.zarr.zip"), emit: out_zarr ++ tuple val(meta), path("${prefix}.ambient_hashtag.hist.pdf"), emit: ambient_hashtag_hist, optional: true ++ tuple val(meta), path("${prefix}.background_probabilities.bar.pdf"), emit: background_probabilities_bar, optional: true ++ tuple val(meta), path("${prefix}.real_content.hist.pdf"), emit: real_content_hist, optional: true ++ tuple val(meta), path("${prefix}.rna_demux.hist.pdf"), emit: rna_demux_hist, optional: true + path "versions.yml", emit: versions when: @@ -39,10 +47,10 @@ Changes in 'demuxem/main.nf': - def genome_file = genome ? "--genome $genome" : "" - def diagnostic_plots = generate_diagnostic_plots ? "--generate-diagnostic-plots $generate_diagnostic_plots" : "" + def args = task.ext.args ?: '' -+ def prefix = task.ext.prefix ?: "${meta.id}" -+ def generateGenderPlot = generate_gender_plot ? "--generate-gender-plot ${generate_gender_plot}" : "" -+ def genome_file = genome ? "--genome ${genome}" : "" -+ def diagnostic_plots = generate_diagnostic_plots ? "--generate-diagnostic-plots ${generate_diagnostic_plots}" : "" ++ prefix = task.ext.prefix ?: "${meta.id}" ++ def generateGenderPlot = gender_genes ? "--generate-gender-plot ${gender_genes}" : "" ++ def genome_args = genome ? "--genome ${genome}" : "" ++ def diagnostic_plots = generate_diagnostic_plots ? "--generate-diagnostic-plots" : "" """ - demuxEM $input_raw_gene_bc_matrices_h5 \\ - $input_hto_csv_file $output_name \\ @@ -50,30 +58,40 @@ Changes in 'demuxem/main.nf': - $generateGenderPlot\\ - $genome_file\\ - $diagnostic_plots ++ export MPLCONFIGDIR=./tmp/mpl ++ + demuxEM ${input_raw_gene_bc_matrices_h5} ${input_hto_csv_file} ${prefix} \\ -+ ${args} \\ -+ ${generateGenderPlot}\\ -+ ${genome_file}\\ -+ ${diagnostic_plots} ++ -p ${task.cpus} \\ ++ ${generateGenderPlot} \\ ++ ${genome_args} \\ ++ ${diagnostic_plots} \\ ++ ${args} + cat <<-END_VERSIONS > versions.yml - "${task.process}":g +- "${task.process}":g - echo \$(demuxEM --version 2>&1) ++ "${task.process}": + echo \$(demuxEM --version 2>&1) END_VERSIONS """ -@@ -50,5 +51,4 @@ + stub: + def prefix = task.ext.prefix ?: "${meta.id}" + """ ++ export MPLCONFIGDIR=./tmp/mpl + touch ${prefix}.out.demuxEM.zarr.zip + touch ${prefix}_demux.zarr.zip + +@@ -50,5 +59,4 @@ echo \$(demuxEM --version 2>&1) END_VERSIONS """ - } -'modules/nf-core/demuxem/nextflow.config' is unchanged -'modules/nf-core/demuxem/environment.yml' is unchanged 'modules/nf-core/demuxem/meta.yml' is unchanged +'modules/nf-core/demuxem/nextflow.config' is unchanged 'modules/nf-core/demuxem/tests/main.nf.test.snap' is unchanged -'modules/nf-core/demuxem/tests/nextflow.config' is unchanged 'modules/nf-core/demuxem/tests/main.nf.test' is unchanged +'modules/nf-core/demuxem/tests/nextflow.config' is unchanged ************************************************************ diff --git a/modules/nf-core/demuxem/main.nf b/modules/nf-core/demuxem/main.nf index 88f3f18a..12e9e557 100644 --- a/modules/nf-core/demuxem/main.nf +++ b/modules/nf-core/demuxem/main.nf @@ -32,6 +32,8 @@ process DEMUXEM { def genome_args = genome ? "--genome ${genome}" : "" def diagnostic_plots = generate_diagnostic_plots ? "--generate-diagnostic-plots" : "" """ + export MPLCONFIGDIR=./tmp/mpl + demuxEM ${input_raw_gene_bc_matrices_h5} ${input_hto_csv_file} ${prefix} \\ -p ${task.cpus} \\ ${generateGenderPlot} \\ @@ -48,6 +50,7 @@ process DEMUXEM { stub: def prefix = task.ext.prefix ?: "${meta.id}" """ + export MPLCONFIGDIR=./tmp/mpl touch ${prefix}.out.demuxEM.zarr.zip touch ${prefix}_demux.zarr.zip diff --git a/modules/nf-core/hasheddrops/main.nf b/modules/nf-core/hasheddrops/main.nf index 6a9e4f5e..0b45a00f 100644 --- a/modules/nf-core/hasheddrops/main.nf +++ b/modules/nf-core/hasheddrops/main.nf @@ -1,65 +1,40 @@ process HASHEDDROPS { - tag "$meta.id" + tag "${meta.id}" label 'process_low' conda "${moduleDir}/environment.yml" container "${ workflow.containerEngine == 'singularity' && !task.ext.singularity_pull_docker_container ? - 'oras://community.wave.seqera.io/library/bioconductor-dropletutils_r-seurat:3cbdf18d48cd0cfa': + 'https://community-cr-prod.seqera.io/docker/registry/v2/blobs/sha256/43/431b27926fac88d6334ee3e8f63479f69a1a69340b305a05b70bc84083d301aa/data': 'community.wave.seqera.io/library/bioconductor-dropletutils_r-seurat:e1dff3a0fb7c5920' }" input: tuple val(meta), path(hto_matrix), val(runEmptyDrops), path(rna_matrix) output: - tuple val(meta), path("*_emptyDrops.png") , emit: empty_drops_plot - tuple val(meta), path("*_emptyDrops.csv") , emit: empty_drops_csv - tuple val(meta), path("*_emptyDrops.rds") , emit: empty_drops_rds - tuple val(meta), path("*_results_hasheddrops.csv") , emit: results - tuple val(meta), path("*_hasheddrops.rds") , emit: rds - tuple val(meta), path("*_plot_hasheddrops.png") , emit: plot - tuple val(meta), path("*_params_hasheddrops.csv") , emit: params - path "versions.yml" , emit: versions + tuple val(meta), path("*_emptyDrops.png") , emit: empty_drops_plot + tuple val(meta), path("*_emptyDrops.csv") , emit: empty_drops_csv + tuple val(meta), path("*_emptyDrops.rds") , emit: empty_drops_rds + tuple val(meta), path("*_results_hasheddrops.csv"), emit: results + tuple val(meta), path("*_id_to_hash.csv") , emit: id_to_hash + tuple val(meta), path("*_hasheddrops.rds") , emit: rds + tuple val(meta), path("*_plot_hasheddrops.png") , emit: plot + tuple val(meta), path("*_params_hasheddrops.csv") , emit: params + path "versions.yml" , emit: versions when: task.ext.when == null || task.ext.when script: - - // emptyDrops Parameters - lower = task.ext.lower ?: "100" // A numeric scalar specifying the lower bound on the total UMI count, at or below which all barcodes are assumed to correspond to empty droplets. - niters = task.ext.niters ?: "10000" // An integer scalar specifying the number of iterations to use for the Monte Carlo p-value calculations. - testAmbient = task.ext.testAmbient ?: "TRUE" // A logical scalar indicating whether results should be returned for barcodes with totals less than or equal to lower. - round = task.ext.round ?: "TRUE" // Logical scalar indicating whether to check for non-integer values in m and, if present, round them for ambient profile estimation. - byRank = task.ext.byRank ?: "NULL" // An integer scalar parametrizing an alternative method for identifying assumed empty droplets. If set, this is used to redefine lower and any specified value for lower is ignored. - isCellFDR = task.ext.isCellFDR ?: "0.01" // Threshold to filter the cells. - gene_col = task.ext.gene_col ?: "2" // Specify which column of genes.tsv or features.tsv to use for gene names; default is 2. - - // hashedDrops Parameters - ignore = task.ext.ignore ?: "NULL" // A numeric scalar specifying the lower bound on the total UMI count, at or below which barcodes will be ignored. - alpha = task.ext.alpha ?: "NULL" // A numeric scalar specifying the scaling parameter for the Dirichlet-multinomial sampling scheme. - ambient = task.ext.ambient ?: "TRUE" // Whether to use the relative abundance of each HTO in the ambient solution from emptyDrops, set TRUE only when test_ambient is TRUE. - minProp = task.ext.minProp ?: "0.05" // Numeric scalar to be used to infer the ambient profile when ambient=NULL. - pseudoCount = task.ext.pseudoCount ?: "5" // A numeric scalar specifying the minimum pseudo-count when computing logfold changes. - constantAmbient = task.ext.constantAmbient ?: "FALSE" // Logical scalar indicating whether a constant level of ambient contamination should be used to estimate LogFC2 for all cells. - doubletNmads = task.ext.doubletNmads ?: "3" // A numeric scalar specifying the number of median absolute deviations (MADs) to use to identify doublets. - doubletMin = task.ext.doubletMin ?: "2" // A numeric scalar specifying the minimum threshold on the log-fold change to use to identify doublets. - doubletMixture = task.ext.doubletMixture ?: "FALSE" // Logical scalar indicating whether to use a 2-component mixture model to identify doublets. - confidentNmads = task.ext.confidentNmads ?: "3" // A numeric scalar specifying the number of MADs to use to identify confidently assigned singlets. - confidentMin = task.ext.confidentMin ?: "2" // A numeric scalar specifying the minimum threshold on the log-fold change to use to identify singlets. - combinations = task.ext.combinations ?: "NULL" // An integer matrix specifying valid combinations of HTOs. Each row corresponds to a single sample and specifies the indices of rows in x corresponding to the HTOs used to label that sample. - - // others - prefix = task.ext.prefix ?: "${meta.id}" // Prefix name for output files. - - template 'HashedDrops.R' + template('HashedDrops.R') stub: - def prefix = task.ext.prefix ?: "${meta.id}" + prefix = task.ext.prefix ?: "${meta.id}" """ touch ${prefix}_emptyDrops.png touch ${prefix}_emptyDrops.csv touch ${prefix}_emptyDrops.rds touch ${prefix}_results_hasheddrops.csv + touch ${prefix}_id_to_hash.csv touch ${prefix}_hasheddrops.rds touch ${prefix}_plot_hasheddrops.png touch ${prefix}_params_hasheddrops.csv @@ -68,8 +43,7 @@ process HASHEDDROPS { "${task.process}": r-base: \$(Rscript -e "cat(strsplit(R.version[['version.string']], ' ')[[1]][3])") r-seurat: \$(Rscript -e "library(Seurat); cat(as.character(packageVersion('Seurat')))") - cdropletutils: \$(Rscript -e "library(DropletUtils); cat(as.character(packageVersion('DropletUtils')))") + dropletutils: \$(Rscript -e "library(DropletUtils); cat(as.character(packageVersion('DropletUtils')))") END_VERSIONS """ } - diff --git a/modules/nf-core/hasheddrops/meta.yml b/modules/nf-core/hasheddrops/meta.yml index 4df1efe1..43a33ce9 100644 --- a/modules/nf-core/hasheddrops/meta.yml +++ b/modules/nf-core/hasheddrops/meta.yml @@ -1,4 +1,3 @@ ---- # yaml-language-server: $schema=https://raw.githubusercontent.com/nf-core/modules/master/modules/meta-schema.json name: "hasheddrops" description: Generating cell hashing calls from a matrix of count data. @@ -8,7 +7,9 @@ keywords: - single-cell tools: - "hasheddrops": - description: "Demultiplex cell barcodes into their samples of origin based on the most abundant hash tag oligo (HTO). Also identify potential doublets based on the presence of multiple significant HTOs." + description: "Demultiplex cell barcodes into their samples of origin based on + the most abundant hash tag oligo (HTO). Also identify potential doublets based + on the presence of multiple significant HTOs." homepage: "https://rdrr.io/github/MarioniLab/DropletUtils/man/hashedDrops.html" documentation: "https://rdrr.io/github/MarioniLab/DropletUtils/man/hashedDrops.html" tool_dev_url: "https://github.com/MarioniLab/DropletUtils" @@ -31,7 +32,7 @@ input: - runEmptyDrops: type: boolean description: | - Run EmptyDrops() before hasheDrops() ("TRUE") or not ("FALSE"). + Run EmptyDrops() before hashedDrops() ("TRUE") or not ("FALSE"). - rna_matrix: type: file description: | @@ -40,8 +41,8 @@ input: - edam: "http://edamontology.org/data_3917" # count matrix output: - - empty_drops_plot: - - meta: + empty_drops_plot: + - - meta: type: map description: | Groovy Map containing sample information @@ -51,10 +52,10 @@ output: description: | EmptyDrops results plot pattern: "_emptyDrops.png" - - - empty_drops_csv: - - meta: - type: file + ontologies: [] + empty_drops_csv: + - - meta: + type: map description: | Groovy Map containing sample information e.g. `[ id:'sample1' ]` @@ -63,9 +64,10 @@ output: description: | EmptyDrops results in CSV format pattern: "_emptyDrops.csv" - - - empty_drops_rds: - - meta: + ontologies: + - edam: http://edamontology.org/format_3752 # CSV + empty_drops_rds: + - - meta: type: map description: | Groovy Map containing sample information @@ -75,9 +77,9 @@ output: description: | EmptyDrops results in RDS format pattern: "_emptyDrops.rds" - - - results: - - meta: + ontologies: [] + results: + - - meta: type: map description: | Groovy Map containing sample information @@ -87,9 +89,23 @@ output: description: | HashedDrops results pattern: "_results_hasheddrops.csv" - - - rds: - - meta: + ontologies: + - edam: http://edamontology.org/format_3752 # CSV + id_to_hash: + - - meta: + type: map + description: | + Groovy Map containing sample information + e.g. `[ id:'sample1' ]` + - "*_id_to_hash.csv": + type: file + description: | + A table mapping integer indices used by hashedDrops results (e.g. the column `Best` or `Second`) to HTO names (or combinations of HTO names joined with `+` if `combinations` is speficied in `ext.args`). + pattern: "_id_to_hash.csv" + ontologies: + - edam: http://edamontology.org/format_3752 # CSV + rds: + - - meta: type: map description: | Groovy Map containing sample information @@ -99,9 +115,9 @@ output: description: | HashedDrops results in RDS format pattern: "_hasheddrops.rds" - - - plot: - - meta: + ontologies: [] + plot: + - - meta: type: map description: | Groovy Map containing sample information @@ -111,9 +127,9 @@ output: description: | HashedDrops plot pattern: "_plot_hasheddrops.png" - - - params: - - meta: + ontologies: [] + params: + - - meta: type: map description: | Groovy Map containing sample information @@ -123,11 +139,14 @@ output: description: | The used parameters to call hashedDrops() in the R-Script. pattern: "_params_hasheddrops.csv" - - versions: - - "versions.yml": - type: file - description: File containing software versions. + ontologies: + - edam: http://edamontology.org/format_3752 # CSV + versions: + - versions.yml: + type: file + description: File containing software versions. + ontologies: [] authors: - "@LuisHeinzlmeier" maintainers: diff --git a/modules/nf-core/hasheddrops/templates/HashedDrops.R b/modules/nf-core/hasheddrops/templates/HashedDrops.R index 9be3df1c..5d5cb7d4 100644 --- a/modules/nf-core/hasheddrops/templates/HashedDrops.R +++ b/modules/nf-core/hasheddrops/templates/HashedDrops.R @@ -2,7 +2,7 @@ ################################################ ################################################ -## Fucntions ## +## Functions ## ################################################ ################################################ @@ -11,47 +11,130 @@ string_to_null <- function(x, val = "NULL") if (x == val) NULL else x null_to_string <- function(x, val = "NULL") if (is.null(x)) val else x string_to_logical <- function(input) { - if (input == "FALSE") { - FALSE - } else if (input == "TRUE") { - TRUE - } else { - stop(paste0(input, " is not a valid logical. Use 'FALSE' or 'TRUE'.")) - } + if (input == "FALSE") { + FALSE + } else if (input == "TRUE") { + TRUE + } else { + stop(paste0(input, " is not a valid logical. Use 'FALSE' or 'TRUE'.")) + } +} + +#' Check for Non-Empty, Non-Whitespace String +#' +#' This function checks if the input is non-NULL and contains more than just whitespace. +#' It returns TRUE if the input is a non-empty, non-whitespace string, and FALSE otherwise. +#' +#' @param input A variable to check. +#' @return A logical value: TRUE if the input is a valid, non-empty, non-whitespace string; FALSE otherwise. + +is_valid_string <- function(input) { + !is.null(input) && nzchar(trimws(input)) +} + +#' Parse out options from a string without recourse to optparse +#' +#' @param x Long-form argument list like --opt1 val1 --opt2 val2 +#' +#' @return named list of options and values similar to optparse + +parse_args <- function(x){ + args_list <- unlist(strsplit(x, ' ?--')[[1]])[-1] + args_vals <- lapply(args_list, function(x) scan(text=x, what='character', quiet = TRUE)) + + # Ensure the option vectors are length 2 (key/ value) to catch empty ones + args_vals <- lapply(args_vals, function(z){ length(z) <- 2; z}) + + parsed_args <- structure(lapply(args_vals, function(x) x[2]), names = lapply(args_vals, function(x) x[1])) + parsed_args[! is.na(parsed_args)] } ################################################ ################################################ -## USE PARAMETERS FROM NEXTFLOW ## +## PARSE PARAMETERS FROM NEXTFLOW ## ################################################ ################################################ -# cast parameters from nextflow +# Set defaults and classes +opt <- list( + # File inputs + hto_matrix = '$hto_matrix', + rna_matrix = '$rna_matrix', + runEmptyDrops = string_to_logical('$runEmptyDrops'), + + # emptyDrops Parameters + lower = 100, # A numeric scalar specifying the lower bound on the total UMI count, at or below which all barcodes are assumed to correspond to empty droplets. + niters = 10000, # An integer scalar specifying the number of iterations to use for the Monte Carlo p-value calculations. + testAmbient = TRUE, # A logical scalar indicating whether results should be returned for barcodes with totals less than or equal to lower. + ignore = NULL, # A numeric scalar specifying the lower bound on the total UMI count, at or below which barcodes will be ignored. + alpha = Inf, # A numeric scalar specifying the scaling parameter for the Dirichlet-multinomial sampling scheme. + round = TRUE, # Logical scalar indicating whether to check for non-integer values in m and, if present, round them for ambient profile estimation. + byRank = NULL, # An integer scalar parametrizing an alternative method for identifying assumed empty droplets. If set, this is used to redefine lower and any specified value for lower is ignored. + isCellFDR = 0.01, # Threshold to filter the cells. + + # hashedDrops Parameters + ambient = TRUE, # Whether to use the relative abundance of each HTO in the ambient solution from emptyDrops, set TRUE only when test_ambient is TRUE. + minProp = 0.05, # Numeric scalar to be used to infer the ambient profile when ambient=NULL. + pseudoCount = 5, # A numeric scalar specifying the minimum pseudo-count when computing logfold changes. + constantAmbient = FALSE, # Logical scalar indicating whether a constant level of ambient contamination should be used to estimate LogFC2 for all cells. + doubletNmads = 3, # A numeric scalar specifying the number of median absolute deviations (MADs) to use to identify doublets. + doubletMin = 2, # A numeric scalar specifying the minimum threshold on the log-fold change to use to identify doublets. + doubletMixture = FALSE, # Logical scalar indicating whether to use a 2-component mixture model to identify doublets. + confidentNmads = 3, # A numeric scalar specifying the number of MADs to use to identify confidently assigned singlets. + confidentMin = 2, # A numeric scalar specifying the minimum threshold on the log-fold change to use to identify singlets. + combinations = NULL, # An integer matrix specifying valid combinations of HTOs. Each row corresponds to a single sample and specifies the indices of rows in x corresponding to the HTOs used to label that sample. + + # others + gene_col = 2, # Specify which column of genes.tsv or features.tsv to use for gene names; default is 2. + prefix = ifelse('$task.ext.prefix' == 'null', '$meta.id', '$task.ext.prefix') # Prefix name for output files. +) +opt_types <- lapply(opt, class) + +# Apply parameter overrides +args_string <- '$task.ext.args' +args_opt <- if (is_valid_string(args_string)) parse_args(args_string) else list() +for ( ao in names(args_opt)){ + if (! ao %in% names(opt)){ + stop(paste("Invalid option:", ao)) + }else{ + # Handle special cases for NULL values and logicals + if (args_opt[[ao]] == "NULL") { + opt[[ao]] <- NULL + } else if (opt_types[[ao]] == "logical") { + opt[[ao]] <- string_to_logical(args_opt[[ao]]) + } else if (! is.null(opt[[ao]])){ + # Preserve classes from defaults where possible + opt[[ao]] <- as(args_opt[[ao]], opt_types[[ao]]) + } else { + opt[[ao]] <- args_opt[[ao]] + } + } +} -# hashedDrops parameters -hto_matrix <- '$hto_matrix' -rna_matrix <- '$rna_matrix' -lower <- as.numeric('$lower') -niters <- as.numeric('$niters') -testAmbient <- string_to_logical('$testAmbient') -ignore <- string_to_null('$ignore') -alpha <- string_to_null('$alpha') -round <- string_to_logical('$round') -byRank <- string_to_null('$byRank') -isCellFDR <- as.numeric('$isCellFDR') -ambient <- string_to_logical('$ambient') -minProp <- as.numeric('$minProp') -pseudoCount <- as.numeric('$pseudoCount') -constantAmbient <- string_to_logical('$constantAmbient') -doubletNmads <- as.numeric('$doubletNmads') -doubletMin <- as.numeric('$doubletMin') -doubletMixture <- string_to_logical('$doubletMixture') -confidentNmads <- as.numeric('$confidentNmads') -confidentMin <- as.numeric('$confidentMin') -combinations <- string_to_null('$combinations') -runEmptyDrops <- string_to_logical('$runEmptyDrops') -gene_col <- as.numeric('$gene_col') -prefix <- '$prefix' +# Set individual variables for backward compatibility and cleaner code +hto_matrix <- opt\$hto_matrix +rna_matrix <- opt\$rna_matrix +runEmptyDrops <- opt\$runEmptyDrops +lower <- opt\$lower +niters <- opt\$niters +testAmbient <- opt\$testAmbient +round <- opt\$round +byRank <- opt\$byRank +isCellFDR <- opt\$isCellFDR +gene_col <- opt\$gene_col +ignore <- opt\$ignore +alpha <- opt\$alpha +ambient <- opt\$ambient +minProp <- opt\$minProp +pseudoCount <- opt\$pseudoCount +constantAmbient <- opt\$constantAmbient +doubletNmads <- opt\$doubletNmads +doubletMin <- opt\$doubletMin +doubletMixture <- opt\$doubletMixture +confidentNmads <- opt\$confidentNmads +confidentMin <- opt\$confidentMin +combinations <- opt\$combinations +prefix <- opt\$prefix # check if the file exists if (! file.exists(hto_matrix)){ @@ -75,27 +158,24 @@ library(DropletUtils) # for hashedDrops() and emptyDrops() hto <- Read10X(data.dir = hto_matrix, gene.column = gene_col) -# die bekomme ich mit runempty Drops -#is.cell <- NULL - # determine hto_input and ambient_input if (runEmptyDrops) { rna <- Read10X(data.dir = rna_matrix, gene.column = gene_col) emptyDrops_out <- emptyDrops( - rna, - lower = lower, - niters = niters, - test.ambient = testAmbient, - ignore = NULL, - alpha = alpha, - round = round, - by.rank = byRank + rna, + lower = lower, + niters = niters, + test.ambient = testAmbient, + ignore = NULL, + alpha = alpha, + round = round, + by.rank = byRank ) # which droplets are actual cells - is.cell <- emptyDrops_out\$FDR <= isCellFDR + is.cell <- emptyDrops_out\$FDR <= isCellFDR & !is.na(emptyDrops_out\$FDR) hto_input <- hto[, which(is.cell)] if (ambient) { @@ -112,17 +192,17 @@ if (runEmptyDrops) { } hashedDrops_out <- hashedDrops( - hto_input, - min.prop = minProp, - ambient = ambient_input, - pseudo.count = pseudoCount, - constant.ambient = constantAmbient, - doublet.nmads = doubletNmads, - doublet.min = doubletMin, - doublet.mixture = doubletMixture, - confident.nmads = confidentNmads, - confident.min = confidentMin, - combinations = combinations + hto_input, + min.prop = minProp, + ambient = ambient_input, + pseudo.count = pseudoCount, + constant.ambient = constantAmbient, + doublet.nmads = doubletNmads, + doublet.min = doubletMin, + doublet.mixture = doubletMixture, + confident.nmads = confidentNmads, + confident.min = confidentMin, + combinations = combinations ) ################################################ @@ -133,47 +213,49 @@ hashedDrops_out <- hashedDrops( #----- saving parameters in a dataframe ------# Argument <- c( - "hto_matrix", - "lower", - "niters", - "testAmbient", - "ignore", - "alpha", - "round", - "byRank", - "isCellFDR", - "ambient", - "minProp", - "pseudoCount", - "constantAmbient", - "doubletNmads", - "doubletMin", - "doubletMixture", - "confidentNmads", - "confidentMin", - "combinations" + "hto_matrix", + "lower", + "niters", + "testAmbient", + "ignore", + "alpha", + "round", + "byRank", + "isCellFDR", + "gene_col", + "ambient", + "minProp", + "pseudoCount", + "constantAmbient", + "doubletNmads", + "doubletMin", + "doubletMixture", + "confidentNmads", + "confidentMin", + "combinations" ) Value <- c( - hto_matrix, - lower, - niters, - testAmbient, - null_to_string(ignore), - null_to_string(alpha), - round, - null_to_string(byRank), - isCellFDR, - ambient, - null_to_string(minProp), - pseudoCount, - constantAmbient, - doubletNmads, - doubletMin, - doubletMixture, - confidentNmads, - confidentMin, - null_to_string(combinations) + hto_matrix, + lower, + niters, + testAmbient, + null_to_string(ignore), + null_to_string(alpha), + round, + null_to_string(byRank), + isCellFDR, + gene_col, + ambient, + null_to_string(minProp), + pseudoCount, + constantAmbient, + doubletNmads, + doubletMin, + doubletMixture, + confidentNmads, + confidentMin, + null_to_string(combinations) ) params <- data.frame(Argument, Value) @@ -188,7 +270,6 @@ if(runEmptyDrops){ plot(emptyDrops_out\$Total, -emptyDrops_out\$LogProb, col = colors, xlab = "Total UMI count", ylab = "-Log Probability") }else{ plot.new() - } dev.off() @@ -197,6 +278,7 @@ saveRDS(emptyDrops_out,file = paste0(prefix, "_emptyDrops.rds")) #--------- save hashedDrops() results ---------# + write.csv(params, paste0(prefix, "_params_hasheddrops.csv")) write.csv(hashedDrops_out,paste0(prefix,"_results_hasheddrops.csv")) saveRDS(hashedDrops_out,file = paste0(prefix,"_hasheddrops.rds")) @@ -221,8 +303,36 @@ if (sum(is.na(hashedDrops_out\$LogFC2)) != length(hashedDrops_out\$LogFC2)) { plot.new() } +# mapping from integers (e.g., in Best) to HTO names or combinations. +# If combinations are specified, the index will map to the joined HTO names separated by a "+". +# Otherwise, it will simply use the row name. +hto_names <- rownames(hto) +if (!is.null(combinations)){ + hto_names <- apply(combinations, 1, function(row) paste(row, collapse = "+")) + # In some applications, samples are labelled with a combination of HTOs to enable achieve greater + # multiplexing throughput. This is accommodated by passing combinations to specify the valid + # HTO combinations that were used for sample labelling. Each row of combinations corresponds + # to a sample and should contain non-duplicated row indices of x corresponding to the HTOs used in + # that sample. + # Source: https://bioconductor.statistik.tu-dortmund.de/packages/3.18/bioc/manuals/DropletUtils/man/DropletUtils.pdf + + # If combinations is specified, Best instead specifies the sample (i.e., row index of combinations). + # Source: https://rdrr.io/github/MarioniLab/DropletUtils/man/hashedDrops.html +} + +# Create a data frame mapping names to indices +hto_map <- data.frame( +Index = seq_along(hto_names), +HTO = hto_names +) + +# Write to CSV +write.csv(hto_map, file = paste0(prefix,"_id_to_hash.csv"), row.names = FALSE) + + dev.off() + ################################################ ################################################ ## VERSIONS FILE ## diff --git a/modules/nf-core/hasheddrops/tests/main.nf.test.snap b/modules/nf-core/hasheddrops/tests/main.nf.test.snap index 8447c0c0..b75e8f16 100644 --- a/modules/nf-core/hasheddrops/tests/main.nf.test.snap +++ b/modules/nf-core/hasheddrops/tests/main.nf.test.snap @@ -39,7 +39,7 @@ { "id": "test" }, - "test_hasheddrops.rds:md5,d41d8cd98f00b204e9800998ecf8427e" + "test_id_to_hash.csv:md5,d41d8cd98f00b204e9800998ecf8427e" ] ], "5": [ @@ -47,7 +47,7 @@ { "id": "test" }, - "test_plot_hasheddrops.png:md5,d41d8cd98f00b204e9800998ecf8427e" + "test_hasheddrops.rds:md5,d41d8cd98f00b204e9800998ecf8427e" ] ], "6": [ @@ -55,11 +55,19 @@ { "id": "test" }, - "test_params_hasheddrops.csv:md5,d41d8cd98f00b204e9800998ecf8427e" + "test_plot_hasheddrops.png:md5,d41d8cd98f00b204e9800998ecf8427e" ] ], "7": [ - "versions.yml:md5,ce84fb45f93458d1aa609015a25e7777" + [ + { + "id": "test" + }, + "test_params_hasheddrops.csv:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "8": [ + "versions.yml:md5,82ab6a6b5ac26c697551e18b1526daa5" ], "empty_drops_csv": [ [ @@ -85,6 +93,14 @@ "test_emptyDrops.rds:md5,d41d8cd98f00b204e9800998ecf8427e" ] ], + "id_to_hash": [ + [ + { + "id": "test" + }, + "test_id_to_hash.csv:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], "params": [ [ { @@ -118,66 +134,7 @@ ] ], "versions": [ - "versions.yml:md5,ce84fb45f93458d1aa609015a25e7777" - ] - } - ], - "meta": { - "nf-test": "0.9.2", - "nextflow": "25.04.3" - }, - "timestamp": "2025-06-18T22:27:04.672171" - }, - "hto_matrix - runEmptyDrops:true - rna_matrix": { - "content": [ - { - "0": [ - - ], - "1": [ - - ], - "2": [ - - ], - "3": [ - - ], - "4": [ - - ], - "5": [ - - ], - "6": [ - - ], - "7": [ - - ], - "empty_drops_csv": [ - - ], - "empty_drops_plot": [ - - ], - "empty_drops_rds": [ - - ], - "params": [ - - ], - "plot": [ - - ], - "rds": [ - - ], - "results": [ - - ], - "versions": [ - + "versions.yml:md5,82ab6a6b5ac26c697551e18b1526daa5" ] } ], @@ -185,7 +142,7 @@ "nf-test": "0.9.2", "nextflow": "25.04.3" }, - "timestamp": "2025-06-18T22:26:44.861876" + "timestamp": "2025-08-11T11:41:43.705133" }, "hto_matrix - runEmptyDrops:false - rna_matrix": { "content": [ @@ -227,7 +184,7 @@ { "id": "test" }, - "test_hasheddrops.rds:md5,31ba0ecae1d94080bccf81cdbaa5afe8" + "test_id_to_hash.csv:md5,a015ca1178177038db21a97fa2070874" ] ], "5": [ @@ -235,7 +192,7 @@ { "id": "test" }, - "test_plot_hasheddrops.png:md5,2d7113a76a494b2b2c18b77c4a664063" + "test_hasheddrops.rds:md5,31ba0ecae1d94080bccf81cdbaa5afe8" ] ], "6": [ @@ -243,10 +200,18 @@ { "id": "test" }, - "test_params_hasheddrops.csv:md5,1b120b9ed566629aa8529c4370426d6a" + "test_plot_hasheddrops.png:md5,2d7113a76a494b2b2c18b77c4a664063" ] ], "7": [ + [ + { + "id": "test" + }, + "test_params_hasheddrops.csv:md5,144fd97597ea5df4ad7bdddf2f6fabac" + ] + ], + "8": [ "versions.yml:md5,82ab6a6b5ac26c697551e18b1526daa5" ], "empty_drops_csv": [ @@ -273,12 +238,20 @@ "test_emptyDrops.rds:md5,bda2e88b1e3e8c8ea5a22e19e3ab243d" ] ], + "id_to_hash": [ + [ + { + "id": "test" + }, + "test_id_to_hash.csv:md5,a015ca1178177038db21a97fa2070874" + ] + ], "params": [ [ { "id": "test" }, - "test_params_hasheddrops.csv:md5,1b120b9ed566629aa8529c4370426d6a" + "test_params_hasheddrops.csv:md5,144fd97597ea5df4ad7bdddf2f6fabac" ] ], "plot": [ @@ -312,8 +285,8 @@ ], "meta": { "nf-test": "0.9.2", - "nextflow": "25.04.3" + "nextflow": "25.04.6" }, - "timestamp": "2025-06-21T11:50:59.635376247" + "timestamp": "2025-08-11T15:04:02.201418256" } } \ No newline at end of file diff --git a/modules/nf-core/htodemux/main.nf b/modules/nf-core/htodemux/main.nf index 58870cae..77a66434 100644 --- a/modules/nf-core/htodemux/main.nf +++ b/modules/nf-core/htodemux/main.nf @@ -1,39 +1,30 @@ process HTODEMUX { - tag "$meta.id" + tag "${meta.id}" label 'process_low' conda "${moduleDir}/environment.yml" container "${ workflow.containerEngine == 'singularity' && !task.ext.singularity_pull_docker_container ? - 'oras://community.wave.seqera.io/library/r-seurat_r-seuratobject:4c5a804804327d29': + 'https://community-cr-prod.seqera.io/docker/registry/v2/blobs/sha256/f9/f96b7927142847485eff858170a4cfd2d3924fb4f09de7043dd6677ac6acd09e/data': 'community.wave.seqera.io/library/r-seurat_r-seuratobject:b11306d1bdc82827' }" input: tuple val(meta), path(seurat_object), val(assay) output: - tuple val(meta), path("*_params_htodemux.csv") , emit: params - tuple val(meta), path("*_assignment_htodemux.csv") , emit: assignment - tuple val(meta), path("*_classification_htodemux.csv") , emit: classification - tuple val(meta), path("*_htodemux.rds") , emit: rds - path "versions.yml" , emit: versions + tuple val(meta), path("*_params_htodemux.csv") , emit: params + tuple val(meta), path("*_assignment_htodemux.csv") , emit: assignment + tuple val(meta), path("*_classification_htodemux.csv"), emit: classification + tuple val(meta), path("*_htodemux.rds") , emit: rds + path "versions.yml" , emit: versions when: task.ext.when == null || task.ext.when script: - quantile = task.ext.quantile ?: "0.99" - init = task.ext.init ?: "NULL" - nstarts = task.ext.nstarts ?: "100" - kfunc = task.ext.kfunc ?: "clara" - nsamples = task.ext.nsamples ?: "100" - seed = task.ext.seed ?: '42' - verbose = task.ext.verbose ?: 'TRUE' - prefix = task.ext.prefix ?: "${meta.id}" - - template 'HTODemux.R' + template('HTODemux.R') stub: - def prefix = task.ext.prefix ?: "${meta.id}" + prefix = task.ext.prefix ?: "${meta.id}" """ touch ${prefix}_params_htodemux.csv touch ${prefix}_assignment_htodemux.csv diff --git a/modules/nf-core/htodemux/meta.yml b/modules/nf-core/htodemux/meta.yml index d50fc845..accc7367 100644 --- a/modules/nf-core/htodemux/meta.yml +++ b/modules/nf-core/htodemux/meta.yml @@ -1,4 +1,3 @@ ---- # yaml-language-server: $schema=https://raw.githubusercontent.com/nf-core/modules/master/modules/meta-schema.json name: "htodemux" description: Demultiplex samples based on data from cell hashing. @@ -8,7 +7,8 @@ keywords: - single-cell tools: - "htodemux": - description: "HTODemux is the demultiplexing module of Seurat, which demultiplex samples based on data from cell hashing." + description: "HTODemux is the demultiplexing module of Seurat, which demultiplex + samples based on data from cell hashing." homepage: "https://satijalab.org/seurat/articles/hashing_vignette" documentation: "https://satijalab.org/seurat/reference/htodemux" tool_dev_url: "https://github.com/satijalab/seurat" @@ -26,13 +26,14 @@ input: type: file description: | A `.rds` file containing the seurat object. Assumes that the hash tag oligo (HTO) data has been added and normalized. + ontologies: [] - assay: type: string description: | Name of the Hashtag assay, usually called "HTO" by default. Use the custom name if the assay has been named differently. output: - - params: - - meta: + params: + - - meta: type: map description: | Groovy Map containing sample information @@ -42,8 +43,10 @@ output: description: The used parameters to call HTODemux in the R-Script. pattern: "params_htodemux.csv" - - assignment: - - meta: + ontologies: + - edam: http://edamontology.org/format_3752 # CSV + assignment: + - - meta: type: map description: | Groovy Map containing sample information @@ -53,8 +56,10 @@ output: description: Assignment results. pattern: "assignment_htodemux.csv" - - classification: - - meta: + ontologies: + - edam: http://edamontology.org/format_3752 # CSV + classification: + - - meta: type: map description: | Groovy Map containing sample information @@ -64,8 +69,10 @@ output: description: Classification results. pattern: "classification_htodemux.csv" - - rds: - - meta: + ontologies: + - edam: http://edamontology.org/format_3752 # CSV + rds: + - - meta: type: map description: | Groovy Map containing sample information @@ -75,12 +82,15 @@ output: description: SeuratObject saved as RDS. pattern: "htodemux.rds" - - versions: - - "versions.yml": - type: file - description: File containing software versions - pattern: "versions.yml" + ontologies: [] + versions: + - versions.yml: + type: file + description: File containing software versions + pattern: "versions.yml" + ontologies: + - edam: http://edamontology.org/format_3750 # YAML authors: - "@LuisHeinzlmeier" maintainers: diff --git a/modules/nf-core/htodemux/templates/HTODemux.R b/modules/nf-core/htodemux/templates/HTODemux.R index cb699d28..b66976fe 100755 --- a/modules/nf-core/htodemux/templates/HTODemux.R +++ b/modules/nf-core/htodemux/templates/HTODemux.R @@ -2,29 +2,98 @@ ################################################ ################################################ -## USE PARAMETERS FROM NEXTFLOW ## +## Functions ## ################################################ ################################################ -# cast parameters from nextflow -seuratObj = '$seurat_object' -assay = '$assay' -options(digits=5) -quantile = as.double('$quantile') -init = NULL -if ('$init' != "NULL") { - init = as.integer('$init') +#' Check for Non-Empty, Non-Whitespace String +#' +#' This function checks if the input is non-NULL and contains more than just whitespace. +#' It returns TRUE if the input is a non-empty, non-whitespace string, and FALSE otherwise. +#' +#' @param input A variable to check. +#' @return A logical value: TRUE if the input is a valid, non-empty, non-whitespace string; FALSE otherwise. +#' @examples +#' is_valid_string("Hello World") # Returns TRUE +#' is_valid_string(" ") # Returns FALSE +#' is_valid_string(NULL) # Returns FALSE + +is_valid_string <- function(input) { + !is.null(input) && nzchar(trimws(input)) +} + +#' Parse out options from a string without recourse to optparse +#' +#' @param x Long-form argument list like --opt1 val1 --opt2 val2 +#' +#' @return named list of options and values similar to optparse + +parse_args <- function(x){ + args_list <- unlist(strsplit(x, ' ?--')[[1]])[-1] + args_vals <- lapply(args_list, function(x) scan(text=x, what='character', quiet = TRUE)) + + # Ensure the option vectors are length 2 (key/ value) to catch empty ones + args_vals <- lapply(args_vals, function(z){ length(z) <- 2; z}) + + parsed_args <- structure(lapply(args_vals, function(x) x[2]), names = lapply(args_vals, function(x) x[1])) + parsed_args[! is.na(parsed_args)] +} + +################################################ +################################################ +## PARSE PARAMETERS FROM NEXTFLOW ## +################################################ +################################################ + +# Set defaults and classes + +opt <- list( + seurat_object = '$seurat_object', + assay = '$assay', + output_prefix = ifelse('$task.ext.prefix' == 'null', '$meta.id', '$task.ext.prefix'), + quantile = 0.99, + init = NULL, + nstarts = 100L, + kfunc = "clara", + nsamples = 100L, + seed = 42L, + verbose = TRUE +) +opt_types <- lapply(opt, class) + +# Apply parameter overrides + +args_opt <- parse_args('$task.ext.args') +for ( ao in names(args_opt)){ + if (! ao %in% names(opt)){ + stop(paste("Invalid option:", ao)) + }else{ + # Handle special cases for logical and NULL values + if (ao == "verbose") { + opt[[ao]] <- as.logical(args_opt[[ao]]) + } else if (ao == "init" && args_opt[[ao]] == "NULL") { + opt[[ao]] <- NULL + } else if (! is.null(opt[[ao]])){ + # Preserve classes from defaults where possible + opt[[ao]] <- as(args_opt[[ao]], opt_types[[ao]]) + } else { + opt[[ao]] <- args_opt[[ao]] + } + } } -nstarts = as.integer('$nstarts') -kfunc = '$kfunc' -nsamples = as.integer('$nsamples') -seed = as.integer('$seed') -verbose = as.logical('$verbose') -prefix = '$prefix' - -# check if the file exists -if (! file.exists(seuratObj)){ - stop(paste0(seuratObj, ' is not a valid file')) + +# Check file inputs are valid + +if (! is_valid_string(opt\$seurat_object)) { + stop("Please provide seurat_object", call. = FALSE) +} + +if (! file.exists(opt\$seurat_object)){ + stop(paste0('Value of seurat_object: ', opt\$seurat_object, ' is not a valid file')) +} + +if (! is_valid_string(opt\$assay)) { + stop("Please provide assay", call. = FALSE) } ################################################ @@ -42,11 +111,18 @@ library(Seurat) ################################################ # Loading Seurat object -hashtag <- readRDS(seuratObj) +hashtag <- readRDS(opt\$seurat_object) # Demultiplex cells based on HTO enrichment -hashtag <- HTODemux(hashtag, assay = assay, positive.quantile = quantile, init = init, nstarts = nstarts, kfunc = kfunc, seed = seed, verbose = verbose) - +hashtag <- HTODemux(hashtag, + assay = opt\$assay, + positive.quantile = opt\$quantile, + init = opt\$init, + nstarts = opt\$nstarts, + kfunc = opt\$kfunc, + nsamples = opt\$nsamples, + seed = opt\$seed, + verbose = opt\$verbose) ################################################ ################################################ @@ -55,23 +131,21 @@ hashtag <- HTODemux(hashtag, assay = assay, positive.quantile = quantile, init = ################################################ # create a data frame to save the used parameters in a csv file -if (is.null(init)) { - init <- "NULL" -} +init_value <- if (is.null(opt\$init)) "NULL" else opt\$init Argument <- c("seuratObject", "quantile", "kfunc", "nstarts", "nsamples", "seed", "init", "assay", "verbose") -Value <- c(seuratObj, quantile, kfunc, nstarts, nsamples, seed, init, assay, verbose) +Value <- c(opt\$seurat_object, opt\$quantile, opt\$kfunc, opt\$nstarts, opt\$nsamples, opt\$seed, init_value, opt\$assay, opt\$verbose) params <- data.frame(Argument, Value) -write.csv(params, paste0(prefix ,"_params_htodemux.csv")) +write.csv(params, paste0(opt\$output_prefix, "_params_htodemux.csv")) # create csv files to save the results from HTODemux() -donors <- rownames(hashtag[[assay]]) -assignment <- hashtag[[paste0(assay, "_classification")]] -assignment[[paste0(assay, "_classification")]][!assignment[[paste0(assay, "_classification")]] %in% c(donors, "Negative")] <- "Doublet" -write.csv(assignment, paste0(prefix ,"_assignment_htodemux.csv")) -write.csv(hashtag[[paste0(assay, "_classification.global")]], paste0(prefix ,"_classification_htodemux.csv")) -saveRDS(hashtag, file = paste0(prefix ,"_htodemux.rds")) +donors <- rownames(hashtag[[opt\$assay]]) +assignment <- hashtag[[paste0(opt\$assay, "_classification")]] +assignment[[paste0(opt\$assay, "_classification")]][!assignment[[paste0(opt\$assay, "_classification")]] %in% c(donors, "Negative")] <- "Doublet" +write.csv(assignment, paste0(opt\$output_prefix, "_assignment_htodemux.csv")) +write.csv(hashtag[[paste0(opt\$assay, "_classification.global")]], paste0(opt\$output_prefix, "_classification_htodemux.csv")) +saveRDS(hashtag, file = paste0(opt\$output_prefix, "_htodemux.rds")) ################################################ ################################################ diff --git a/modules/nf-core/htodemux/tests/main.nf.test.snap b/modules/nf-core/htodemux/tests/main.nf.test.snap index 85174188..f3be1eab 100644 --- a/modules/nf-core/htodemux/tests/main.nf.test.snap +++ b/modules/nf-core/htodemux/tests/main.nf.test.snap @@ -78,64 +78,7 @@ "nf-test": "0.9.2", "nextflow": "25.04.3" }, - "timestamp": "2025-06-15T15:42:12.918682" - }, - "sarscov2 - bam": { - "content": [ - { - "0": [ - [ - { - "id": "test" - }, - [ - "test_assignment_htodemux.csv:md5,33d72b27c3d0b0f7394af18a18490a5c", - "test_classification_htodemux.csv:md5,886f595f148d2ccdd5a9134b8eb815cb", - "test_params_htodemux.csv:md5,61065a5ddedda3bcf5a51e9eb5a72eae" - ] - ] - ], - "1": [ - [ - { - "id": "test" - }, - "testhtodemux.rds:md5,ec43d2d0217c3a87529f1eb4d0e05ffb" - ] - ], - "2": [ - "versions.yml:md5,faa097177f5c4efd711eb8dd5701b6d2" - ], - "csv": [ - [ - { - "id": "test" - }, - [ - "test_assignment_htodemux.csv:md5,33d72b27c3d0b0f7394af18a18490a5c", - "test_classification_htodemux.csv:md5,886f595f148d2ccdd5a9134b8eb815cb", - "test_params_htodemux.csv:md5,61065a5ddedda3bcf5a51e9eb5a72eae" - ] - ] - ], - "rds": [ - [ - { - "id": "test" - }, - "testhtodemux.rds:md5,ec43d2d0217c3a87529f1eb4d0e05ffb" - ] - ], - "versions": [ - "versions.yml:md5,faa097177f5c4efd711eb8dd5701b6d2" - ] - } - ], - "meta": { - "nf-test": "0.9.2", - "nextflow": "25.04.3" - }, - "timestamp": "2025-06-14T15:21:00.420534" + "timestamp": "2025-07-09T15:06:44.480302427" }, "seuratObject - rds - stub": { "content": [ @@ -216,6 +159,6 @@ "nf-test": "0.9.2", "nextflow": "25.04.3" }, - "timestamp": "2025-06-15T15:40:53.744484" + "timestamp": "2025-07-09T15:06:53.10583219" } } \ No newline at end of file diff --git a/modules/nf-core/multiseqdemux/main.nf b/modules/nf-core/multiseqdemux/main.nf index de8d91ef..0d33a745 100644 --- a/modules/nf-core/multiseqdemux/main.nf +++ b/modules/nf-core/multiseqdemux/main.nf @@ -1,38 +1,29 @@ process MULTISEQDEMUX { - tag "$meta.id" + tag "${meta.id}" label 'process_low' conda "${moduleDir}/environment.yml" container "${ workflow.containerEngine == 'singularity' && !task.ext.singularity_pull_docker_container ? - 'oras://community.wave.seqera.io/library/r-seurat_r-seuratobject:4c5a804804327d29': + 'https://community-cr-prod.seqera.io/docker/registry/v2/blobs/sha256/f9/f96b7927142847485eff858170a4cfd2d3924fb4f09de7043dd6677ac6acd09e/data': 'community.wave.seqera.io/library/r-seurat_r-seuratobject:b11306d1bdc82827' }" input: tuple val(meta), path(seurat_object), val(assay) output: - tuple val(meta), path("*_params_multiseqdemux.csv") , emit: params - tuple val(meta), path("*_res_multiseqdemux.csv") , emit: results - tuple val(meta), path("*_multiseqdemux.rds") , emit: rds - path "versions.yml" , emit: versions + tuple val(meta), path("*_params_multiseqdemux.csv"), emit: params + tuple val(meta), path("*_res_multiseqdemux.csv") , emit: results + tuple val(meta), path("*_multiseqdemux.rds") , emit: rds + path "versions.yml" , emit: versions when: task.ext.when == null || task.ext.when script: - quantile = task.ext.assay ?: "0.7" - autoThresh = task.ext.autoThresh ?: "TRUE" - maxiter = task.ext.maxiter ?: "5" - qrangeFrom = task.ext.qrangeFrom ?: "0.1" - qrangeTo = task.ext.qrangeTo ?: "0.9" - qrangeBy = task.ext.qrangeBy ?: "0.05" - verbose = task.ext.verbose ?: 'TRUE' - prefix = task.ext.prefix ?: "${meta.id}" - - template 'MultiSeqDemux.R' + template('MultiSeqDemux.R') stub: - def prefix = task.ext.prefix ?: "${meta.id}" + prefix = task.ext.prefix ?: "${meta.id}" """ touch ${prefix}_params_multiseqdemux.csv touch ${prefix}_res_multiseqdemux.csv diff --git a/modules/nf-core/multiseqdemux/meta.yml b/modules/nf-core/multiseqdemux/meta.yml index 518f63e3..153ea6e5 100644 --- a/modules/nf-core/multiseqdemux/meta.yml +++ b/modules/nf-core/multiseqdemux/meta.yml @@ -1,14 +1,15 @@ ---- # yaml-language-server: $schema=https://raw.githubusercontent.com/nf-core/modules/master/modules/meta-schema.json name: "multiseqdemux" -description: Identify singlets, doublets and negative cells from multiplexing experiments. Annotate singlets by tags. +description: Identify singlets, doublets and negative cells from multiplexing experiments. + Annotate singlets by tags. keywords: - demultiplexing - hashing-based deconvolution - single-cell tools: - "multiseqdemux": - description: "MULTIseqDemux is the demultiplexing module of Seurat, which demultiplex samples based on data from cell hashing." + description: "MULTIseqDemux is the demultiplexing module of Seurat, which demultiplex + samples based on data from cell hashing." homepage: "https://satijalab.org/seurat/reference/multiseqdemux" documentation: "https://satijalab.org/seurat/reference/multiseqdemux" tool_dev_url: "https://github.com/satijalab/seurat" @@ -26,14 +27,15 @@ input: type: file description: | A `.rds` file containing the seurat object. Assumes that the hash tag oligo (HTO) data has been added and normalized. + ontologies: [] - assay: type: string description: | Name of the Hashtag assay, usually called "HTO" by default. Use the custom name if the assay has been named differently. output: - - params: - - meta: + params: + - - meta: type: map description: | Groovy Map containing sample information @@ -43,8 +45,10 @@ output: description: The used parameters to call MULTIseqDemux in the R-Script. pattern: "_params_multiseqdemux.csv" - - results: - - meta: + ontologies: + - edam: http://edamontology.org/format_3752 # CSV + results: + - - meta: type: map description: | Groovy Map containing sample information @@ -54,8 +58,10 @@ output: description: Resuls of MULTIseqDemux. pattern: "_res_multiseqdemux.csv" - - rds: - - meta: + ontologies: + - edam: http://edamontology.org/format_3752 # CSV + rds: + - - meta: type: map description: | Groovy Map containing sample information @@ -65,12 +71,15 @@ output: description: SeuratObject saved as RDS. pattern: "_multiseqdemux.rds" - - versions: - - "versions.yml": - type: file - description: File containing software versions - pattern: "versions.yml" + ontologies: [] + versions: + - versions.yml: + type: file + description: File containing software versions + pattern: "versions.yml" + ontologies: + - edam: http://edamontology.org/format_3750 # YAML authors: - "@LuisHeinzlmeier" maintainers: diff --git a/modules/nf-core/multiseqdemux/templates/MultiSeqDemux.R b/modules/nf-core/multiseqdemux/templates/MultiSeqDemux.R index 8cd14dc9..9355b4fc 100755 --- a/modules/nf-core/multiseqdemux/templates/MultiSeqDemux.R +++ b/modules/nf-core/multiseqdemux/templates/MultiSeqDemux.R @@ -2,23 +2,109 @@ ################################################ ################################################ -## USE PARAMETERS FROM NEXTFLOW ## +## Functions ## ################################################ ################################################ -# cast parameters from nextflow -seuratObj = '$seurat_object' +#' Check for Non-Empty, Non-Whitespace String +#' +#' This function checks if the input is non-NULL and contains more than just whitespace. +#' It returns TRUE if the input is a non-empty, non-whitespace string, and FALSE otherwise. +#' +#' @param input A variable to check. +#' @return A logical value: TRUE if the input is a valid, non-empty, non-whitespace string; FALSE otherwise. + +is_valid_string <- function(input) { + !is.null(input) && nzchar(trimws(input)) +} + +#' Parse out options from a string without recourse to optparse +#' +#' @param x Long-form argument list like --opt1 val1 --opt2 val2 +#' +#' @return named list of options and values similar to optparse + +parse_args <- function(x){ + args_list <- unlist(strsplit(x, ' ?--')[[1]])[-1] + args_vals <- lapply(args_list, function(x) scan(text=x, what='character', quiet = TRUE)) + + # Ensure the option vectors are length 2 (key/ value) to catch empty ones + args_vals <- lapply(args_vals, function(z){ length(z) <- 2; z}) + + parsed_args <- structure(lapply(args_vals, function(x) x[2]), names = lapply(args_vals, function(x) x[1])) + parsed_args[! is.na(parsed_args)] +} + +string_to_logical <- function(input) { + if (input == "FALSE") { + FALSE + } else if (input == "TRUE") { + TRUE + } else { + stop(paste0(input, " is not a valid logical. Use 'FALSE' or 'TRUE'.")) + } +} + +################################################ +################################################ +## PARSE PARAMETERS FROM NEXTFLOW ## +################################################ +################################################ + +# Set defaults and classes +opt <- list( + # File inputs + seurat_object = '$seurat_object', + assay = '$assay', + + # MULTIseqDemux Parameters + quantile = 0.7, # A numeric scalar for the quantile to use for demultiplexing + autoThresh = TRUE, # A logical scalar indicating whether to use automatic thresholding + maxiter = 5L, # An integer scalar specifying the maximum number of iterations + qrangeFrom = 0.1, # A numeric scalar specifying the start of the quantile range + qrangeTo = 0.9, # A numeric scalar specifying the end of the quantile range + qrangeBy = 0.05, # A numeric scalar specifying the step size for the quantile range + verbose = TRUE, # A logical scalar indicating whether to print verbose output + + # others + prefix = ifelse('$task.ext.prefix' == 'null', '$meta.id', '$task.ext.prefix') # Prefix name for output files. +) +opt_types <- lapply(opt, class) + +# Apply parameter overrides +args_opt <- parse_args('$task.ext.args') +for ( ao in names(args_opt)){ + if (! ao %in% names(opt)){ + stop(paste("Invalid option:", ao)) + }else{ + # Handle special cases for logicals + if (opt_types[[ao]] == "logical") { + opt[[ao]] <- string_to_logical(args_opt[[ao]]) + } else if (! is.null(opt[[ao]])){ + # Preserve classes from defaults where possible + opt[[ao]] <- as(args_opt[[ao]], opt_types[[ao]]) + } else { + opt[[ao]] <- args_opt[[ao]] + } + } +} + +# Set individual variables for backward compatibility and cleaner code +seuratObj <- opt\$seurat_object +assay <- opt\$assay +quantile <- opt\$quantile +autoThresh <- opt\$autoThresh +maxiter <- opt\$maxiter +qrangeFrom <- opt\$qrangeFrom +qrangeTo <- opt\$qrangeTo +qrangeBy <- opt\$qrangeBy +verbose <- opt\$verbose +prefix <- opt\$prefix + +# Configure output precision options(digits=5) -quantile = as.double('$quantile') -autoThresh = as.logical('$autoThresh') -maxiter = as.integer('$maxiter') -qrangeFrom = as.double('$qrangeFrom') -qrangeTo = as.double('$qrangeTo') -qrangeBy = as.double('$qrangeBy') -verbose = as.logical('$verbose') -assay ='$assay' -prefix = '$prefix' +# Check if file exists if (! file.exists(seuratObj)){ stop(paste0(seuratObj, ' is not a valid file')) } diff --git a/modules/nf-core/scanpy/hashsolo/environment.yml b/modules/nf-core/scanpy/hashsolo/environment.yml new file mode 100644 index 00000000..ff6c0f11 --- /dev/null +++ b/modules/nf-core/scanpy/hashsolo/environment.yml @@ -0,0 +1,11 @@ +--- +# yaml-language-server: $schema=https://raw.githubusercontent.com/nf-core/modules/master/modules/environment-schema.json +channels: + - conda-forge + - bioconda +dependencies: + - conda-forge::matplotlib=3.10.5 + - conda-forge::pandas=2.3.1 + - conda-forge::python=3.12.11 + - conda-forge::pyyaml=6.0.2 + - conda-forge::scanpy=1.11.2 diff --git a/modules/nf-core/scanpy/hashsolo/main.nf b/modules/nf-core/scanpy/hashsolo/main.nf new file mode 100644 index 00000000..5b12c734 --- /dev/null +++ b/modules/nf-core/scanpy/hashsolo/main.nf @@ -0,0 +1,46 @@ +process SCANPY_HASHSOLO { + tag "$meta.id" + label 'process_medium' + + conda "${moduleDir}/environment.yml" + container "${ workflow.containerEngine == 'singularity' && !task.ext.singularity_pull_docker_container ? + 'https://community-cr-prod.seqera.io/docker/registry/v2/blobs/sha256/a0/a06fe0be02e1a82be29f415a554cfd3ad9d921cd63e71ebce3b141a723564426/data': + 'community.wave.seqera.io/library/matplotlib_pandas_python_pyyaml_scanpy:bf7b7ef27120d15c' }" + + input: + tuple val(meta), path(data), val(cell_hashing_columns) + + output: + tuple val(meta), path("*_assignment_hashsolo.csv"), emit: assignment + tuple val(meta), path("*_hashsolo.h5ad") , emit: h5ad + tuple val(meta), path("*_params_hashsolo.csv") , emit: params + path "versions.yml" , emit: versions + + when: + task.ext.when == null || task.ext.when + + script: + prefix = task.ext.prefix ?: "${meta.id}" + + template('hashsolo.py') + + stub: + prefix = task.ext.prefix ?: "${meta.id}" + """ + touch ${prefix}_assignment_hashsolo.csv + touch ${prefix}_hashsolo.h5ad + touch ${prefix}_params_hashsolo.csv + + # Prevent failures during scanpy import + export MPLCONFIGDIR=./tmp/mpl + export NUMBA_CACHE_DIR=./tmp/numba + + cat <<-END_VERSIONS > versions.yml + "${task.process}": + python: \$(python3 --version | cut -f 2 -d " ") + scanpy: \$(python3 -c "import scanpy; print(scanpy.__version__)") + matplotlib: \$(python3 -c "import matplotlib; print(matplotlib.__version__)") + pandas: \$(python3 -c "import pandas; print(pandas.__version__)") + END_VERSIONS + """ +} diff --git a/modules/nf-core/scanpy/hashsolo/meta.yml b/modules/nf-core/scanpy/hashsolo/meta.yml new file mode 100644 index 00000000..d94d6661 --- /dev/null +++ b/modules/nf-core/scanpy/hashsolo/meta.yml @@ -0,0 +1,91 @@ +# yaml-language-server: $schema=https://raw.githubusercontent.com/nf-core/modules/master/modules/meta-schema.json +name: "SCANPY_HASHSOLO" +description: Probabilistic demultiplexing of cell hashing data +keywords: + - anndata + - single-cell + - hashing + - demultiplexing + - scanpy +tools: + - "scanpy": + description: "Single-cell analysis in Python. Scales to >100M cells." + homepage: "https://github.com/scverse/scanpy" + documentation: "https://scanpy.readthedocs.io/en/stable/generated/scanpy.external.pp.hashsolo.html" + tool_dev_url: "https://github.com/scverse/scanpy" + doi: "10.1186/s13059-017-1382-0" + licence: ["BSD-3"] + identifier: biotools:scanpy + +input: + - - meta: + type: map + description: | + Groovy Map containing sample information + e.g. `[ id:'sample1' ]` + - data: + type: file + description: | + A directory in 10x Genomics format containing `matrix.mtx.gz`, `features.tsv.gz`, `barcodes.tsv.gz` (hashing count matrix), or an AnnData (`.h5ad`) file with hashing counts stored in `.obs`. + ontologies: + - edam: "http://edamontology.org/data_3917" # count matrix + - cell_hashing_columns: + type: list + description: | + Groovy list (`['hash_1', 'hash_2']`) of `.obs` columns that contain cell hashing counts. + Can be `[]` if the data is in 10x Genomics format, as the columns are derived from the input. + +output: + assignment: + - - meta: + type: map + description: | + Groovy Map containing sample information + e.g. `[ id:'sample1' ]` + - "*_assignment_hashsolo.csv": + type: file + description: | + CSV file containing hashsolo assignment results + pattern: "*_assignment_hashsolo.csv" + ontologies: + - edam: http://edamontology.org/format_3752 # CSV + h5ad: + - - meta: + type: map + description: | + Groovy Map containing sample information + e.g. `[ id:'sample1' ]` + - "*_hashsolo.h5ad": + type: file + description: | + Processed AnnData object containing hashsolo results + pattern: "*_hashsolo.h5ad" + ontologies: + - edam: "http://edamontology.org/format_3590" # HDF5 format + params: + - - meta: + type: map + description: | + Groovy Map containing sample information + e.g. `[ id:'sample1' ]` + pattern: "*_params_hashsolo.csv" + - "*_params_hashsolo.csv": + type: file + description: | + CSV file containing parameters used in hashsolo analysis + pattern: "*_params_hashsolo.csv" + ontologies: + - edam: http://edamontology.org/format_3752 # CSV + versions: + - versions.yml: + type: file + description: File containing software versions + pattern: "versions.yml" + ontologies: + - edam: http://edamontology.org/format_3750 # YAML +authors: + - "@seohyonkim" + - "@LuisHeinzlmeier" +maintainers: + - "@seohyonkim" + - "@LuisHeinzlmeier" diff --git a/modules/nf-core/scanpy/hashsolo/templates/hashsolo.py b/modules/nf-core/scanpy/hashsolo/templates/hashsolo.py new file mode 100644 index 00000000..2db087b4 --- /dev/null +++ b/modules/nf-core/scanpy/hashsolo/templates/hashsolo.py @@ -0,0 +1,204 @@ +#!/usr/bin/env python3 + +import os +import platform +import yaml +import shlex +import argparse + +os.environ["MPLCONFIGDIR"] = "./tmp/mpl" +os.environ["NUMBA_CACHE_DIR"] = "./tmp/numba" + +import scanpy as sc +import scanpy.external as sce +import pandas as pd +import matplotlib +import matplotlib.pyplot as plt + +class Arguments: + # adopted from mygene module (Suzanne Jin) + """ + Parses the arguments, including the ones coming from $task.ext.args. + """ + + def __init__(self) -> None: + + + + self.data = "$data" + + self.use_10x = True + if self.data.endswith(".h5ad"): + self.use_10x = False + + cell_hashing_columns = "${cell_hashing_columns.join(' ')}".split() + self.cell_hashing_columns = [str(x) for x in cell_hashing_columns] + + self.prefix = "$task.ext.prefix" if "$task.ext.prefix" != "null" else "$meta.id" + + self.path_assignment = self.prefix + "_assignment_hashsolo.csv" + # self.path_plot = self.prefix + "_hashsolo.jpg" + self.path_h5ad = self.prefix + "_hashsolo.h5ad" + self.path_params = self.prefix + "_params_hashsolo.csv" + self.parse_ext_args("$task.ext.args") + + def parse_ext_args(self, args_string: str) -> None: + """ + It parses the extended arguments. + """ + # skip when there are no extended arguments + if args_string == "null": + args_string = "" + + # Parse the extended arguments + args_list = shlex.split(args_string) # Split the string into a list of arguments + parser = argparse.ArgumentParser() + + parser.add_argument( + "--priors", + type=float, + nargs=3, + metavar=("NEGATIVE", "SINGLET", "DOUBLET"), + help="""List of priors for each hypothesis: + NEGATIVE = prior for negative hypothesis + SINGLET = prior for singlet hypothesis + DOUBLET = prior for doublet hypothesis""" + , + default=[0.01, 0.8, 0.19], + ) + + parser.add_argument( + "--pre_existing_clusters", + help="column in cell_hashing_adata.obs for how to break up demultiplexing", + type=str, + default=None, + ) + parser.add_argument( + "--clustering_data", + help="input directory containing transcriptomic data in 10x mtx format.", + type=str, + default=None, + ) + parser.add_argument( + "--number_of_noise_barcodes", + help="Number of barcodes to use to create noise distribution", + type=int, + default=None, + ) + + parser.add_argument( + "--round_digits", + help=( + "Number of decimal places to round numeric values in cell_hashing_data.obs " + "before saving. If omitted, no rounding is applied." + ), + type=int, + default=None, + ) + + args = parser.parse_args(args_list) + + # Convert "null" values to default values + # convert true to True and false to False + for attr in vars(args): + value = getattr(args, attr) + if value == "null": + setattr(args, attr, parser.get_default(attr)) + elif value == "true": + setattr(args, attr, True) + elif value == "false": + setattr(args, attr, False) + + # Assign args attributes to self attributes + for attr in vars(args): + setattr(self, attr, getattr(args, attr)) + + def print_args(self) -> None: + """ + Print the arguments. + """ + for attr in vars(self): + print(f"{attr}: {getattr(self, attr)}") + +if __name__ == "__main__": + + # ------------------------------ parse and print arguments ------------------------------ + args = Arguments() + args.print_args() + + # ----------------------------------- read input data ----------------------------------- + if args.use_10x: + cell_hashing_data = sc.read_10x_mtx(args.data, gex_only=False) + + # Move HTO data from .X to .obs columns + cell_hashing_data.obs[cell_hashing_data.var_names] = pd.DataFrame( + # Convert sparse matrix to dense array if needed, otherwise use as-is + cell_hashing_data.X.toarray() if hasattr(cell_hashing_data.X, "toarray") else cell_hashing_data.X, + index=cell_hashing_data.obs_names, + columns=cell_hashing_data.var_names + ) + args.cell_hashing_columns = list(cell_hashing_data.obs.columns) + else: + cell_hashing_data = sc.read_h5ad(args.data) + + if len(args.cell_hashing_columns) == 2: + # This edge case issue may be fixed in future versions: https://github.com/calico/solo/issues/91 + args.number_of_noise_barcodes = 1 + + + # -------------------------------------- call hashsolo ----------------------------------- + if args.clustering_data is not None: + trans_data = sc.read_10x_mtx(args.clustering_data) + trans_data.var_names_make_unique() + sce.pp.hashsolo( + cell_hashing_data, + cell_hashing_columns=args.cell_hashing_columns, + priors=args.priors, + clustering_data=trans_data, + pre_existing_clusters=args.pre_existing_clusters, + number_of_noise_barcodes=args.number_of_noise_barcodes, + ) + else: + sce.pp.hashsolo( + cell_hashing_data, + cell_hashing_columns=args.cell_hashing_columns, + priors=args.priors, + pre_existing_clusters=args.pre_existing_clusters, + number_of_noise_barcodes=args.number_of_noise_barcodes, + ) + + + # ------------------------------------- save results ------------------------------------- + + # Round numeric values if requested + if args.round_digits is not None: + numeric_cols = cell_hashing_data.obs.select_dtypes(include=["number"]).columns + cell_hashing_data.obs[numeric_cols] = cell_hashing_data.obs[numeric_cols].round(args.round_digits) + + cell_hashing_data.obs.index.name = "Barcode" + + cell_hashing_data.obs.to_csv(args.path_assignment) + + # plotting does not exist for scanpy.external.pp but we couldn't use the solo package + # sce.pp.hashsolo.plot_qc_checks_cell_hashing(cell_hashing_data) + # plt.savefig(args.path_plot, dpi=400) + + cell_hashing_data.write(args.path_h5ad) + + param_list = [[key, getattr(args, key)] for key in sorted(vars(args).keys())] + + param_df = pd.DataFrame(param_list, columns=["Argument", "Value"]) + param_df.fillna("None", inplace=True) + param_df.to_csv(args.path_params, index=False) + + versions = { + "${task.process}": { + "python" : platform.python_version(), + "scanpy" : sc.__version__, + "matplotlib": matplotlib.__version__, + "pandas" : pd.__version__, + } + } + + with open("versions.yml", "w") as f: + yaml.dump(versions, f) diff --git a/modules/nf-core/scanpy/hashsolo/tests/main.nf.test b/modules/nf-core/scanpy/hashsolo/tests/main.nf.test new file mode 100644 index 00000000..a3a558e2 --- /dev/null +++ b/modules/nf-core/scanpy/hashsolo/tests/main.nf.test @@ -0,0 +1,235 @@ +nextflow_process { + + name "Test Process SCANPY_HASHSOLO" + script "../main.nf" + process "SCANPY_HASHSOLO" + + tag "modules" + tag "modules_nfcore" + tag "hashsolo" + tag "scanpy/hashsolo" + tag "scanpy" + tag "untar" + + // ext.args + def priors = [0.20, 0.8, 0.19] + def pre_existing_clusters = null + def clustering_data = null + def number_of_noise_barcodes = null + def round_digits = 10 + + test("hto_data_10x") { + + config "./nextflow.config" + + setup { + run("UNTAR") { + script "modules/nf-core/untar/main.nf" + process { + """ + input[0] = Channel.of([ [],file(params.modules_testdata_base_path + "/genomics/homo_sapiens/10xgenomics/cellranger/hashing_demultiplexing/hto.tar.gz", checkIfExists: true) ]) + """ + } + } + } + when { + params{ + module_args = [ + round_digits ? "--round_digits $round_digits" : "" + ].findAll { it != "" }.join(" ") + } + process { + """ + input[0] = UNTAR.out.untar.map { meta, files -> + [ [ id: 'test' ], files, [] ] + } + """ + } + } + + then { + assertAll( + { assert process.success }, + { assert snapshot(process.out).match() } + ) + } + } + + test("hto_data_10x - ext.args") { + + config "./nextflow.config" + + setup { + run("UNTAR") { + script "modules/nf-core/untar/main.nf" + process { + """ + input[0] = Channel.of([ [],file(params.modules_testdata_base_path + "/genomics/homo_sapiens/10xgenomics/cellranger/hashing_demultiplexing/hto.tar.gz", checkIfExists: true) ]) + """ + } + } + } + when { + + params{ + module_args = [ + "--priors", + priors.join(" "), + pre_existing_clusters ? "--pre_existing_clusters $pre_existing_clusters" : "", + clustering_data ? "--clustering_data $clustering_data" : "", + number_of_noise_barcodes ? "--number_of_noise_barcodes $number_of_noise_barcodes" : "", + round_digits ? "--round_digits $round_digits" : "" + ].findAll { it != "" }.join(" ") + } + process { + """ + input[0] = UNTAR.out.untar.map { meta, files -> + [ [ id: 'test' ], files, [] ] + } + """ + } + } + + then { + assertAll( + { assert process.success }, + { assert snapshot(process.out).match() } + ) + } + } + + + + + test("hto_data_10x - stub") { + + options "-stub" + + setup { + run("UNTAR") { + script "modules/nf-core/untar/main.nf" + process { + """ + input[0] = Channel.of([ [],file(params.modules_testdata_base_path + "/genomics/homo_sapiens/10xgenomics/cellranger/hashing_demultiplexing/hto.tar.gz", checkIfExists: true) ]) + """ + } + } + } + when { + + process { + """ + input[0] = UNTAR.out.untar.map { meta, files -> + [ [ id: 'test' ], files, [] ] + } + """ + } + } + + then { + assertAll( + { assert process.success }, + { assert snapshot(process.out).match() } + ) + } + } + + test("hto_data_h5ad (generated, no real data)") { + + config "./nextflow.config" + + when { + params{ + module_args = [ + round_digits ? "--round_digits $round_digits" : "" + ].findAll { it != "" }.join(" ") + } + process { + """ + input[0] = [ + [ id: 'test' ], + file(params.modules_testdata_base_path + 'genomics/homo_sapiens/10xgenomics/cellranger/hashing_demultiplexing/hashsolo_anndata.h5ad', checkIfExists: true), + ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9'] + ] + """ + } + } + + then { + assertAll( + { assert process.success }, + { assert snapshot( + process.out, + process.out.versions.collect { path(it).yaml } + ).match() } + ) + } + + } + + test("hto_data_h5ad (generated, no real data) - ext.args") { + + config "./nextflow.config" + + when { + params{ + module_args = [ + "--priors", + priors.join(" "), + pre_existing_clusters ? "--pre_existing_clusters $pre_existing_clusters" : "", + clustering_data ? "--clustering_data $clustering_data" : "", + number_of_noise_barcodes ? "--number_of_noise_barcodes $number_of_noise_barcodes" : "", + round_digits ? "--round_digits $round_digits" : "" + ].findAll { it != "" }.join(" ") + } + process { + """ + input[0] = [ + [ id: 'test' ], + file(params.modules_testdata_base_path + 'genomics/homo_sapiens/10xgenomics/cellranger/hashing_demultiplexing/hashsolo_anndata.h5ad', checkIfExists: true), + ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9'] + ] + """ + } + } + + then { + assertAll( + { assert process.success }, + { assert snapshot( + process.out, + process.out.versions.collect { path(it).yaml } + ).match() } + ) + } + + } + + test("hto_data_h5ad (generated, no real data) - stub") { + + options "-stub" + + when { + process { + """ + input[0] = [ + [ id: 'test' ], + file(params.modules_testdata_base_path + 'genomics/homo_sapiens/10xgenomics/cellranger/hashing_demultiplexing/hashsolo_anndata.h5ad', checkIfExists: true), + ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9'] + ] + """ + } + } + + then { + assertAll( + { assert process.success }, + { assert snapshot( + process.out, + process.out.versions.collect { path(it).yaml } + ).match() } + ) + } + + } +} diff --git a/modules/nf-core/scanpy/hashsolo/tests/main.nf.test.snap b/modules/nf-core/scanpy/hashsolo/tests/main.nf.test.snap new file mode 100644 index 00000000..2351088f --- /dev/null +++ b/modules/nf-core/scanpy/hashsolo/tests/main.nf.test.snap @@ -0,0 +1,668 @@ +{ + "hto_data_10x": { + "content": [ + { + "0": [ + [ + { + "id": "test" + }, + "test_assignment_hashsolo.csv:md5,a970a5192150b1832a3efea197c882cd" + ] + ], + "1": [ + [ + { + "id": "test" + }, + "test_hashsolo.h5ad:md5,3e9f7ed941ab5d1b59f51f1b1fb7e528" + ] + ], + "2": [ + [ + { + "id": "test" + }, + "test_params_hashsolo.csv:md5,17ed9d21353013b8547ac829874ff514" + ] + ], + "3": [ + "versions.yml:md5,f6178c11c8b15300ccafa689830c64e3" + ], + "assignment": [ + [ + { + "id": "test" + }, + "test_assignment_hashsolo.csv:md5,a970a5192150b1832a3efea197c882cd" + ] + ], + "h5ad": [ + [ + { + "id": "test" + }, + "test_hashsolo.h5ad:md5,3e9f7ed941ab5d1b59f51f1b1fb7e528" + ] + ], + "params": [ + [ + { + "id": "test" + }, + "test_params_hashsolo.csv:md5,17ed9d21353013b8547ac829874ff514" + ] + ], + "versions": [ + "versions.yml:md5,f6178c11c8b15300ccafa689830c64e3" + ] + } + ], + "meta": { + "nf-test": "0.9.2", + "nextflow": "25.04.6" + }, + "timestamp": "2025-08-11T06:12:34.257211014" + }, + "hto_data_10x - stub": { + "content": [ + { + "0": [ + [ + { + "id": "test" + }, + "test_assignment_hashsolo.csv:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "1": [ + [ + { + "id": "test" + }, + "test_hashsolo.h5ad:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "2": [ + [ + { + "id": "test" + }, + "test_params_hashsolo.csv:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "3": [ + "versions.yml:md5,8fd954f7fb047e8ad17ff5710299ddf2" + ], + "assignment": [ + [ + { + "id": "test" + }, + "test_assignment_hashsolo.csv:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "h5ad": [ + [ + { + "id": "test" + }, + "test_hashsolo.h5ad:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "params": [ + [ + { + "id": "test" + }, + "test_params_hashsolo.csv:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "versions": [ + "versions.yml:md5,8fd954f7fb047e8ad17ff5710299ddf2" + ] + } + ], + "meta": { + "nf-test": "0.9.2", + "nextflow": "25.04.6" + }, + "timestamp": "2025-08-11T06:12:55.413127431" + }, + "hto_data_h5ad (generated, no real data)": { + "content": [ + { + "0": [ + [ + { + "id": "test" + }, + "test_assignment_hashsolo.csv:md5,e6ffa5719d0dc3d2236ced778327b50c" + ] + ], + "1": [ + [ + { + "id": "test" + }, + "test_hashsolo.h5ad:md5,df3a1793ebfd8843bbc7371b2c8b6ffe" + ] + ], + "2": [ + [ + { + "id": "test" + }, + "test_params_hashsolo.csv:md5,7054d81f10cb650a72f9dea5bb23f44b" + ] + ], + "3": [ + "versions.yml:md5,f6178c11c8b15300ccafa689830c64e3" + ], + "assignment": [ + [ + { + "id": "test" + }, + "test_assignment_hashsolo.csv:md5,e6ffa5719d0dc3d2236ced778327b50c" + ] + ], + "h5ad": [ + [ + { + "id": "test" + }, + "test_hashsolo.h5ad:md5,df3a1793ebfd8843bbc7371b2c8b6ffe" + ] + ], + "params": [ + [ + { + "id": "test" + }, + "test_params_hashsolo.csv:md5,7054d81f10cb650a72f9dea5bb23f44b" + ] + ], + "versions": [ + "versions.yml:md5,f6178c11c8b15300ccafa689830c64e3" + ] + }, + [ + { + "SCANPY_HASHSOLO": { + "matplotlib": "3.10.5", + "pandas": "2.3.1", + "python": "3.12.11", + "scanpy": "1.11.2" + } + } + ] + ], + "meta": { + "nf-test": "0.9.2", + "nextflow": "25.04.6" + }, + "timestamp": "2025-08-11T06:13:04.472233033" + }, + "generated h5ad": { + "content": [ + { + "0": [ + [ + { + "id": "test" + }, + "test_hashsolo.h5ad:md5,e1f129270b67e8575dd060d44e1c0c4b" + ] + ], + "1": [ + "versions.yml:md5,d4b55c68ad8effa4580cf202d802b007" + ], + "h5ad": [ + [ + { + "id": "test" + }, + "test_hashsolo.h5ad:md5,e1f129270b67e8575dd060d44e1c0c4b" + ] + ], + "versions": [ + "versions.yml:md5,d4b55c68ad8effa4580cf202d802b007" + ] + }, + [ + { + "SCANPY_HASHSOLO": { + "python": "3.12.11", + "scanpy": "1.11.2" + } + } + ] + ], + "meta": { + "nf-test": "0.9.2", + "nextflow": "25.04.3" + }, + "timestamp": "2025-07-02T10:01:12.076734105" + }, + "hto_data_h5ad (generated, no real data) - ext.args": { + "content": [ + { + "0": [ + [ + { + "id": "test" + }, + "test_assignment_hashsolo.csv:md5,8cdd1b5a8c910c2842518cb3caac541d" + ] + ], + "1": [ + [ + { + "id": "test" + }, + "test_hashsolo.h5ad:md5,6a2d2b734d32c9b368361795deb7306d" + ] + ], + "2": [ + [ + { + "id": "test" + }, + "test_params_hashsolo.csv:md5,c9e859256e6d28dc03049496b7eee509" + ] + ], + "3": [ + "versions.yml:md5,f6178c11c8b15300ccafa689830c64e3" + ], + "assignment": [ + [ + { + "id": "test" + }, + "test_assignment_hashsolo.csv:md5,8cdd1b5a8c910c2842518cb3caac541d" + ] + ], + "h5ad": [ + [ + { + "id": "test" + }, + "test_hashsolo.h5ad:md5,6a2d2b734d32c9b368361795deb7306d" + ] + ], + "params": [ + [ + { + "id": "test" + }, + "test_params_hashsolo.csv:md5,c9e859256e6d28dc03049496b7eee509" + ] + ], + "versions": [ + "versions.yml:md5,f6178c11c8b15300ccafa689830c64e3" + ] + }, + [ + { + "SCANPY_HASHSOLO": { + "matplotlib": "3.10.5", + "pandas": "2.3.1", + "python": "3.12.11", + "scanpy": "1.11.2" + } + } + ] + ], + "meta": { + "nf-test": "0.9.2", + "nextflow": "25.04.6" + }, + "timestamp": "2025-08-11T06:13:12.760175521" + }, + "hto_matrix": { + "content": [ + { + "0": [ + [ + { + "id": "test" + }, + [ + "test_hashsolo.csv:md5,3e7d7ceeb97d93830556311daff388cd", + "test_params_hashsolo.csv:md5,c474067bceee23596942159ed2287f41" + ] + ] + ], + "1": [ + + ], + "2": [ + [ + { + "id": "test" + }, + "test_hashsolo.h5ad:md5,043358088cc7f369ebf7ac005cca23fe" + ] + ], + "3": [ + [ + { + "id": "test" + }, + "test_params_hashsolo.csv:md5,c474067bceee23596942159ed2287f41" + ] + ], + "4": [ + "versions.yml:md5,f6178c11c8b15300ccafa689830c64e3" + ], + "assignment": [ + [ + { + "id": "test" + }, + [ + "test_hashsolo.csv:md5,3e7d7ceeb97d93830556311daff388cd", + "test_params_hashsolo.csv:md5,c474067bceee23596942159ed2287f41" + ] + ] + ], + "h5ad": [ + [ + { + "id": "test" + }, + "test_hashsolo.h5ad:md5,043358088cc7f369ebf7ac005cca23fe" + ] + ], + "params": [ + [ + { + "id": "test" + }, + "test_params_hashsolo.csv:md5,c474067bceee23596942159ed2287f41" + ] + ], + "plot": [ + + ], + "versions": [ + "versions.yml:md5,f6178c11c8b15300ccafa689830c64e3" + ] + } + ], + "meta": { + "nf-test": "0.9.2", + "nextflow": "25.04.3" + }, + "timestamp": "2025-08-08T21:32:31.163776" + }, + "hto_matrix - stub": { + "content": [ + { + "0": [ + [ + [ + + ], + [ + "[]_hashsolo.csv:md5,d41d8cd98f00b204e9800998ecf8427e", + "[]_params_hashsolo.csv:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ] + ], + "1": [ + [ + [ + + ], + "[]_hashsolo.jpg:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "2": [ + [ + [ + + ], + "[]_hashsolo.h5ad:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "3": [ + [ + [ + + ], + "[]_params_hashsolo.csv:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "4": [ + "versions.yml:md5,8fd954f7fb047e8ad17ff5710299ddf2" + ], + "assignment": [ + [ + [ + + ], + [ + "[]_hashsolo.csv:md5,d41d8cd98f00b204e9800998ecf8427e", + "[]_params_hashsolo.csv:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ] + ], + "h5ad": [ + [ + [ + + ], + "[]_hashsolo.h5ad:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "params": [ + [ + [ + + ], + "[]_params_hashsolo.csv:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "plot": [ + [ + [ + + ], + "[]_hashsolo.jpg:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "versions": [ + "versions.yml:md5,8fd954f7fb047e8ad17ff5710299ddf2" + ] + } + ], + "meta": { + "nf-test": "0.9.2", + "nextflow": "25.04.3" + }, + "timestamp": "2025-08-07T19:14:54.511322" + }, + "hto_data_h5ad (generated, no real data) - stub": { + "content": [ + { + "0": [ + [ + { + "id": "test" + }, + "test_assignment_hashsolo.csv:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "1": [ + [ + { + "id": "test" + }, + "test_hashsolo.h5ad:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "2": [ + [ + { + "id": "test" + }, + "test_params_hashsolo.csv:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "3": [ + "versions.yml:md5,8fd954f7fb047e8ad17ff5710299ddf2" + ], + "assignment": [ + [ + { + "id": "test" + }, + "test_assignment_hashsolo.csv:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "h5ad": [ + [ + { + "id": "test" + }, + "test_hashsolo.h5ad:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "params": [ + [ + { + "id": "test" + }, + "test_params_hashsolo.csv:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "versions": [ + "versions.yml:md5,8fd954f7fb047e8ad17ff5710299ddf2" + ] + }, + [ + { + "SCANPY_HASHSOLO": { + "python": "3.12.11", + "scanpy": "1.11.2", + "matplotlib": "3.10.5", + "pandas": "2.3.1" + } + } + ] + ], + "meta": { + "nf-test": "0.9.2", + "nextflow": "25.04.6" + }, + "timestamp": "2025-08-11T06:13:22.084772685" + }, + "generated h5ad - stub": { + "content": [ + { + "0": [ + [ + { + "id": "test" + }, + "test_hashsolo.h5ad:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "1": [ + "versions.yml:md5,21a56a941355317a8e1ca02d207f64a8" + ], + "h5ad": [ + [ + { + "id": "test" + }, + "test_hashsolo.h5ad:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "versions": [ + "versions.yml:md5,21a56a941355317a8e1ca02d207f64a8" + ] + }, + [ + { + "SCANPY_HASHSOLO": { + "python": "3.12.11", + "scanpy": "1.11.2" + } + } + ] + ], + "meta": { + "nf-test": "0.9.2", + "nextflow": "25.04.3" + }, + "timestamp": "2025-07-02T10:01:21.413803466" + }, + "hto_data_10x - ext.args": { + "content": [ + { + "0": [ + [ + { + "id": "test" + }, + "test_assignment_hashsolo.csv:md5,dabd4b9a92e90c0030d86110fce3be40" + ] + ], + "1": [ + [ + { + "id": "test" + }, + "test_hashsolo.h5ad:md5,2625d58a4d9f0e24c8bf4066f06c3e70" + ] + ], + "2": [ + [ + { + "id": "test" + }, + "test_params_hashsolo.csv:md5,4b992e5daf73b8f23f5d847df32dc177" + ] + ], + "3": [ + "versions.yml:md5,f6178c11c8b15300ccafa689830c64e3" + ], + "assignment": [ + [ + { + "id": "test" + }, + "test_assignment_hashsolo.csv:md5,dabd4b9a92e90c0030d86110fce3be40" + ] + ], + "h5ad": [ + [ + { + "id": "test" + }, + "test_hashsolo.h5ad:md5,2625d58a4d9f0e24c8bf4066f06c3e70" + ] + ], + "params": [ + [ + { + "id": "test" + }, + "test_params_hashsolo.csv:md5,4b992e5daf73b8f23f5d847df32dc177" + ] + ], + "versions": [ + "versions.yml:md5,f6178c11c8b15300ccafa689830c64e3" + ] + } + ], + "meta": { + "nf-test": "0.9.2", + "nextflow": "25.04.6" + }, + "timestamp": "2025-08-11T06:12:44.782219305" + } +} \ No newline at end of file diff --git a/modules/nf-core/scanpy/hashsolo/tests/nextflow.config b/modules/nf-core/scanpy/hashsolo/tests/nextflow.config new file mode 100644 index 00000000..7aa46b35 --- /dev/null +++ b/modules/nf-core/scanpy/hashsolo/tests/nextflow.config @@ -0,0 +1,5 @@ +process { + withName: "SCANPY_HASHSOLO" { + ext.args = params.module_args + } +} diff --git a/nextflow.config b/nextflow.config index 716bb0ee..ef4a2254 100644 --- a/nextflow.config +++ b/nextflow.config @@ -25,13 +25,42 @@ params { igenomes_base = 's3://ngi-igenomes/igenomes/' igenomes_ignore = false - // demuxEM + // ======================== hasing paramters ======================== + + // -------------------------- hash summary -------------------------- + generate_anndata = true + generate_mudata = true + + // ------------------------------ bff ------------------------------- + // inputs + bff_methods = 'RAW' + bff_preprocessing = true + + // ext.args for preprocessing + bff_barcodeWhitelist = null + + // ext.args for GenerateCellHashingCalls() + bff_cellbarcodeWhitelist = null + bff_methodsForConsensus = null + bff_metricsFile = null + bff_doTSNE = false + bff_doHeatmap = true + bff_perCellSaturation = null + bff_majorityConsensusThreshold = null + bff_chemistry = "10xV3" + bff_callerDisagreementThreshold = null + + // ---------------------------- demuxem ----------------------------- + // inputs + demuxem_gender_genes = "" + demuxem_generate_diagnostic_plots = true + + // ext.args demuxem_alpha_on_samples = 0.0 demuxem_min_num_genes = 100 demuxem_min_num_umis = 100 demuxem_min_signal_hashtag = 10 demuxem_random_state = 0 - demuxem_gender_genes = "" // Preprocessing for HTODemux and MultiSeq preprocessing_sel_method = "mean.var.plot" @@ -42,12 +71,21 @@ params { preprocessing_norm_method = "CLR" preprocessing_gene_col = 2 - // GMM-Demux + // ---------------------------- gmmdemux ---------------------------- + // inputs + gmmdemux_hto_names = null + gmmdemux_type_report = true + gmmdemux_summary_report = true + gmmdemux_skip = null + gmmdemux_examine = null + + // ext.args gmmdemux_extract = null gmmdemux_threshold = 0.8 gmmdemux_random_state = 0 - // HTODemux + // ---------------------------- htodemux ---------------------------- + // ext.args htodemux_quantile = 0.99 htodemux_init = "NULL" htodemux_nstarts = 100 @@ -56,7 +94,7 @@ params { htodemux_seed = 42 htodemux_verbose = true - // HTODemux Visualization + // visualization htodemux_visualization_ridgePlot = true htodemux_visualization_ridgeNCol = 2 htodemux_visualization_featureScatter = true @@ -75,7 +113,8 @@ params { htodemux_visualization_heatMap = true htodemux_visualization_heatMapNcells = 500 - // MultiSeqDemux + // -------------------------- multiseqdemux ------------------------- + // ext.args multiseqdemux_quantile = 0.7 multiseqdemux_autoThresh = true multiseqdemux_maxiter = 5 @@ -84,27 +123,44 @@ params { multiseqdemux_qrangeBy = 0.05 multiseqdemux_verbose = true - // HashedDrops + // --------------------------- hasheddrops -------------------------- + // inputs + hasheddrops_runEmptyDrops = false + + // ext.args hasheddrops_lower = 100 hasheddrops_niters = 10000 - hasheddrops_testAmbient = "TRUE" - hasheddrops_round = "TRUE" - hasheddrops_byRank = "NULL" - hasheddrops_isCellFDR = 0.01 + hasheddrops_testAmbient = true hasheddrops_gene_col = 2 - hasheddrops_ignore = "NULL" - hasheddrops_alpha = "NULL" - hasheddrops_ambient = "TRUE" + hasheddrops_ignore = null + hasheddrops_alpha = null + hasheddrops_round = true + hasheddrops_byRank = null + hasheddrops_isCellFDR = 0.01 + hasheddrops_ambient = true hasheddrops_minProp = 0.05 hasheddrops_pseudoCount = 5 - hasheddrops_constantAmbient = "FALSE" + hasheddrops_constantAmbient = false hasheddrops_doubletNmads = 3 hasheddrops_doubletMin = 2 - hasheddrops_doubletMixture = "FALSE" + hasheddrops_doubletMixture = false hasheddrops_confidentNmads = 3 hasheddrops_confidentMin = 2 - hasheddrops_combinations = "NULL" + hasheddrops_combinations = null + + // ---------------------------- hashsolo ---------------------------- + // inputs + hashsolo_cell_hashing_columns = null + + // ext.args + hashsolo_priors = "0.01,0.8,0.19" + hashsolo_pre_existing_clusters = null + hashsolo_clustering_data = null + hashsolo_number_of_noise_barcodes = null + hashsolo_round_digits = 10 + + // ======================== genetic paramters ======================== // CellSNP cellsnp_celltag = 'CB' cellsnp_umitag = 'Auto' diff --git a/nextflow_schema.json b/nextflow_schema.json index 5b3f1306..0b16b021 100644 --- a/nextflow_schema.json +++ b/nextflow_schema.json @@ -33,8 +33,7 @@ "match_donor": { "type": "boolean", "description": "Match donor.", - "fa_icon": "fas fa-user-check", - "default": false + "fa_icon": "fas fa-user-check" }, "hash_tools": { "type": "string", @@ -75,8 +74,7 @@ "save_intermediates": { "type": "boolean", "description": "Save intermediate files.", - "fa_icon": "fas fa-save", - "default": false + "fa_icon": "fas fa-save" }, "email": { "type": "string", @@ -137,6 +135,12 @@ "fa_icon": "fas fa-vial", "description": "Options specific to the demuxEM tool for cell hashing demultiplexing.", "properties": { + "demuxem_generate_diagnostic_plots": { + "type": "boolean", + "description": "Generate diagnostic plots.", + "default": true, + "fa_icon": "fas fa-chart-line" + }, "demuxem_alpha_on_samples": { "type": "number", "description": "The Dirichlet prior concentration parameter (alpha) on samples. An alpha value < 1.0 will make the prior sparse.", @@ -162,7 +166,7 @@ "demuxem_min_signal_hashtag": { "type": "number", "description": "Any cell/nucleus with less than this count of hashtags from the signal will be marked as unknown.", - "default": 10, + "default": 10.0, "minimum": 0, "fa_icon": "fas fa-signal" }, @@ -176,17 +180,127 @@ "demuxem_gender_genes": { "type": "string", "description": "Comma-separated list of gender-specific genes (e.g. Xist) for generating violin plots.", - "default": "", "fa_icon": "fas fa-venus-mars" } } }, + "bff_options": { + "title": "BFF options", + "type": "object", + "fa_icon": "fas fa-bolt", + "description": "Options specific to the BFF cell hashing demultiplexing.", + "properties": { + "bff_methods": { + "type": "string", + "description": "Method(s) to use within BFF.", + "default": "RAW", + "fa_icon": "fas fa-list" + }, + "bff_preprocessing": { + "type": "boolean", + "description": "Whether to run preprocessing steps for BFF.", + "default": true, + "fa_icon": "fas fa-cogs" + }, + "bff_barcodeWhitelist": { + "type": ["string", "null"], + "description": "Path to barcode whitelist for preprocessing.", + "default": null, + "fa_icon": "fas fa-file" + }, + "bff_cellbarcodeWhitelist": { + "type": ["string", "null"], + "description": "Path to cell barcode whitelist for GenerateCellHashingCalls().", + "default": null, + "fa_icon": "fas fa-file" + }, + "bff_methodsForConsensus": { + "type": ["string", "null"], + "description": "Methods to use for consensus calling.", + "default": null, + "fa_icon": "fas fa-layer-group" + }, + "bff_metricsFile": { + "type": ["string", "null"], + "description": "Optional metrics file path.", + "default": null, + "fa_icon": "fas fa-file-alt" + }, + "bff_doTSNE": { + "type": "boolean", + "description": "Whether to compute tSNE visualization in BFF.", + "fa_icon": "fas fa-project-diagram" + }, + "bff_doHeatmap": { + "type": "boolean", + "description": "Whether to generate heatmaps in BFF.", + "default": true, + "fa_icon": "fas fa-fire" + }, + "bff_perCellSaturation": { + "type": ["number", "null"], + "description": "Per-cell saturation value.", + "default": null, + "fa_icon": "fas fa-tint" + }, + "bff_majorityConsensusThreshold": { + "type": ["number", "null"], + "description": "Majority consensus threshold.", + "default": null, + "fa_icon": "fas fa-percentage" + }, + "bff_chemistry": { + "type": "string", + "description": "Library chemistry (e.g., 10xV3).", + "default": "10xV3", + "fa_icon": "fas fa-vial" + }, + "bff_callerDisagreementThreshold": { + "type": ["number", "null"], + "description": "Threshold for caller disagreement.", + "default": null, + "fa_icon": "fas fa-exclamation-triangle" + } + } + }, "gmmdemux_options": { "title": "GMM-Demux options", "type": "object", "fa_icon": "fas fa-layer-group", "description": "Options specific to the GMM-Demux tool for cell hashing demultiplexing.", "properties": { + "gmmdemux_hto_names": { + "type": ["string", "null"], + "description": "Comma separated list of HTO names, without whitespace. If null, hto_names are extracted from the input hto matrix from features.tsv.gz.", + "default": null, + "fa_icon": "fas fa-file-alt" + }, + "gmmdemux_type_report": { + "type": "boolean", + "description": "If true, full classification report is generated, otherwise the simplified classification report.", + "default": true, + "fa_icon": "fas fa-file-alt" + }, + "gmmdemux_summary_report": { + "type": "boolean", + "description": "If true, summary report is generated.", + "default": true, + "fa_icon": "fas fa-list" + }, + "gmmdemux_skip": { + "type": ["string", "null"], + "format": "file-path", + "description": "Load a full classification report and skip the mtx folder as input. Requires a file path argument.", + "default": null, + "fa_icon": "fas fa-file-import" + }, + "gmmdemux_examine": { + "type": ["string", "null"], + "format": "file-path", + "description": "Provide the cell list. Requires a file path argument. Only executes if -u is set.", + "default": null, + "fa_icon": "fas fa-file" + }, "gmmdemux_extract": { "type": "string", "description": "Names of the HTO tag(s) to extract, separated by ','. Joint HTO samples are combined with '+', such as 'HTO_1+HTO_2'.", @@ -211,6 +325,58 @@ } } }, + "hashsolo_options": { + "title": "Hashsolo options", + "type": "object", + "fa_icon": "fas fa-hashtag", + "description": "Options specific to the Scanpy Hashsolo demultiplexing module.", + "properties": { + "hashsolo_cell_hashing_columns": { + "type": ["array", "null"], + "items": { + "type": "string" + }, + "description": "Groovy list (['hash_1', 'hash_2']) of .obs columns that contain cell hashing counts. Can be null if the data is in 10x Genomics format, as the columns are derived from the input.", + "default": null, + "fa_icon": "fas fa-list" + }, + "hashsolo_priors": { + "type": "string", + "items": { + "type": "number" + }, + "description": "List of comma-separated priors for each hypothesis: NEGATIVE, SINGLET, DOUBLET.", + "default": "0.01,0.8,0.19", + "fa_icon": "fas fa-sliders-h" + }, + "hashsolo_pre_existing_clusters": { + "type": ["string", "null"], + "description": "Column in cell_hashing_adata.obs for how to break up demultiplexing.", + "default": null, + "fa_icon": "fas fa-columns" + }, + "hashsolo_clustering_data": { + "type": ["string", "null"], + "format": "directory-path", + "description": "Input directory containing transcriptomic data in 10x mtx format.", + "default": null, + "fa_icon": "fas fa-folder-open" + }, + "hashsolo_number_of_noise_barcodes": { + "type": ["integer", "null"], + "description": "Number of barcodes to use to create noise distribution.", + "default": null, + "fa_icon": "fas fa-sort-numeric-up" + }, + "hashsolo_round_digits": { + "type": "integer", + "description": "Number of decimal places to round numeric values in cell_hashing_data.obs before saving. If omitted, no rounding is applied.", + "default": 10, + "minimum": 0, + "fa_icon": "fas fa-hashtag" + } + } + }, "htodemux_options": { "title": "HTODemux options", "type": "object", @@ -342,13 +508,11 @@ "htodemux_visualization_tSNEVerbose": { "type": "boolean", "description": "Verbose tSNE.", - "default": false, "fa_icon": "fas fa-volume-up" }, "htodemux_visualization_tSNEApprox": { "type": "boolean", "description": "Approximate tSNE.", - "default": false, "fa_icon": "fas fa-fast-forward" }, "htodemux_visualization_tSNEDimMax": { @@ -460,23 +624,21 @@ "fa_icon": "fas fa-redo" }, "hasheddrops_testAmbient": { - "type": "string", + "type": "boolean", "description": "Whether to test ambient RNA.", - "default": "TRUE", - "enum": ["TRUE", "FALSE"], + "default": true, "fa_icon": "fas fa-flask" }, "hasheddrops_round": { - "type": "string", + "type": "boolean", "description": "Whether to round non-integer values.", - "default": "TRUE", - "enum": ["TRUE", "FALSE"], + "default": true, "fa_icon": "fas fa-circle" }, "hasheddrops_byRank": { - "type": "string", + "type": ["integer", "null"], "description": "Alternative method for identifying empty droplets.", - "default": "NULL", + "default": null, "fa_icon": "fas fa-sort-numeric-down" }, "hasheddrops_isCellFDR": { @@ -495,22 +657,21 @@ "fa_icon": "fas fa-columns" }, "hasheddrops_ignore": { - "type": "string", + "type": ["number", "null"], "description": "Lower bound for ignoring barcodes.", - "default": "NULL", + "default": null, "fa_icon": "fas fa-ban" }, "hasheddrops_alpha": { - "type": "string", + "type": ["number", "null"], "description": "Scaling parameter for Dirichlet-multinomial sampling.", - "default": "NULL", + "default": null, "fa_icon": "fas fa-adjust" }, "hasheddrops_ambient": { - "type": "string", + "type": "boolean", "description": "Whether to use ambient solution abundance.", - "default": "TRUE", - "enum": ["TRUE", "FALSE"], + "default": true, "fa_icon": "fas fa-water" }, "hasheddrops_minProp": { @@ -529,10 +690,8 @@ "fa_icon": "fas fa-plus" }, "hasheddrops_constantAmbient": { - "type": "string", + "type": "boolean", "description": "Whether to use constant ambient contamination level.", - "default": "FALSE", - "enum": ["TRUE", "FALSE"], "fa_icon": "fas fa-equals" }, "hasheddrops_doubletNmads": { @@ -550,10 +709,8 @@ "fa_icon": "fas fa-sort-amount-down" }, "hasheddrops_doubletMixture": { - "type": "string", + "type": "boolean", "description": "Whether to use 2-component mixture model for doublets.", - "default": "FALSE", - "enum": ["TRUE", "FALSE"], "fa_icon": "fas fa-object-ungroup" }, "hasheddrops_confidentNmads": { @@ -571,10 +728,18 @@ "fa_icon": "fas fa-sort-amount-up" }, "hasheddrops_combinations": { - "type": "string", - "description": "Valid combinations of HTOs.", - "default": "NULL", + "type": "array", + "items": { + "type": "integer" + }, + "description": "An integer matrix specifying valid combinations of HTOs. Number of items in each row has to be the same.", + "default": null, "fa_icon": "fas fa-th-large" + }, + "hasheddrops_runEmptyDrops": { + "type": "boolean", + "description": "Whether to run EmptyDrops analysis as part of HashedDrops.", + "fa_icon": "fas fa-tint" } } }, @@ -628,6 +793,26 @@ } } }, + "hash_summary_options": { + "title": "Hash summary options", + "type": "object", + "fa_icon": "fas fa-list", + "description": "Options controlling hash summary and downstream exported formats.", + "properties": { + "generate_anndata": { + "type": "boolean", + "description": "Generate AnnData (.h5ad) outputs for hashing results.", + "fa_icon": "fas fa-database", + "default": true + }, + "generate_mudata": { + "type": "boolean", + "description": "Generate MuData outputs for hashing results.", + "fa_icon": "fas fa-layer-group", + "default": true + } + } + }, "cellsnp_options": { "title": "CellSNP-lite options", "type": "object", @@ -657,20 +842,18 @@ "type": "number", "description": "Minimum minor allele frequency (MAF) for SNPs to be included in the output.", "default": 0.0, - "minimum": 0.0, + "minimum": 0, "maximum": 0.5, "fa_icon": "fas fa-percentage" }, "cellsnp_inclflag": { "type": "string", "description": "Required flags in SAM/BAM: skip reads that don't have ALL of these flags. See SAM format specification for details.", - "default": "", "fa_icon": "fas fa-flag" }, "cellsnp_exclflag": { "type": "string", "description": "Excluding flags in SAM/BAM: skip reads that have ANY of these flags. See SAM format specification for details.", - "default": "", "fa_icon": "fas fa-ban" }, "cellsnp_minlen": { @@ -697,7 +880,6 @@ "cellsnp_countorphan": { "type": "boolean", "description": "If true, do not skip anomalous read pairs (i.e., count orphan reads).", - "default": false, "fa_icon": "fas fa-child" } } @@ -718,7 +900,6 @@ "vireo_no_doublet": { "type": "boolean", "description": "If true, do not check for doublets during demultiplexing.", - "default": false, "fa_icon": "fas fa-object-ungroup" }, "vireo_n_init": { @@ -745,19 +926,16 @@ "vireo_force_learn_gt": { "type": "boolean", "description": "If true, treat donor GT as prior only and learn genotypes from data.", - "default": false, "fa_icon": "fas fa-graduation-cap" }, "vireo_ase_mode": { "type": "boolean", "description": "If true, turn on SNP specific allelic ratio (ASE mode).", - "default": false, "fa_icon": "fas fa-adjust" }, "vireo_no_plot": { "type": "boolean", "description": "If true, turn off plotting GT distance.", - "default": false, "fa_icon": "fas fa-chart-bar" }, "vireo_rand_seed": { @@ -776,7 +954,6 @@ "vireo_cell_ambient_rnas": { "type": "boolean", "description": "If true, detect ambient RNAs in each cell (experimental feature).", - "default": false, "fa_icon": "fas fa-flask" } } @@ -969,13 +1146,11 @@ "freemuxlet_keep_init_missing": { "type": "boolean", "description": "Keep missing cluster assignment as missing in the initial iteration.", - "default": false, "fa_icon": "fas fa-question-circle" }, "freemuxlet_randomize_singlet_score": { "type": "boolean", "description": "Randomize the singlet scores to test its effect.", - "default": false, "fa_icon": "fas fa-random" }, "freemuxlet_seed": { @@ -1143,9 +1318,15 @@ { "$ref": "#/$defs/demuxem_options" }, + { + "$ref": "#/$defs/bff_options" + }, { "$ref": "#/$defs/gmmdemux_options" }, + { + "$ref": "#/$defs/hashsolo_options" + }, { "$ref": "#/$defs/htodemux_options" }, @@ -1158,6 +1339,9 @@ { "$ref": "#/$defs/hasheddrops_options" }, + { + "$ref": "#/$defs/hash_summary_options" + }, { "$ref": "#/$defs/preprocessing_options" }, diff --git a/subworkflows/local/hash_demultiplexing/main.nf b/subworkflows/local/hash_demultiplexing/main.nf index 175d621f..b77e7f56 100644 --- a/subworkflows/local/hash_demultiplexing/main.nf +++ b/subworkflows/local/hash_demultiplexing/main.nf @@ -4,13 +4,18 @@ include { RENAME_GENES_TO_FEATURES as RENAME_GENES_TO_FEATURES_RNA } from '../.. include { RENAME_GENES_TO_FEATURES as RENAME_GENES_TO_FEATURES_HTO } from '../../../modules/local/rename_genes_to_features' include { DROPLETUTILS_MTXCONVERT as MTXCONVERT_RNA } from '../../../modules/local/dropletutils/mtxconvert' include { DROPLETUTILS_MTXCONVERT as MTXCONVERT_HTO } from '../../../modules/local/dropletutils/mtxconvert' +include { EXTRACT_HASHES } from '../../../modules/local/extract_hashes' include { PREPROCESSING_FOR_HTODEMUX_MULTISEQ } from '../../../modules/local/preprocessing_for_htodemux_multiseq' include { HTODEMUX } from '../../../modules/nf-core/htodemux' include { HTODEMUX_VISUALIZATION } from '../../../modules/local/htodemux_visualization' include { MULTISEQDEMUX } from '../../../modules/nf-core/multiseqdemux' +include { BFF } from '../../../modules/nf-core/bff' include { DEMUXEM } from '../../../modules/nf-core/demuxem' include { GMMDEMUX } from '../../../modules/nf-core/gmmdemux' +include { SCANPY_HASHSOLO as HASHSOLO } from '../../../modules/nf-core/scanpy/hashsolo' include { HASHEDDROPS } from '../../../modules/nf-core/hasheddrops' +include { HASH_SUMMARY } from '../../../modules/local/hash_summary' + workflow HASH_DEMULTIPLEXING { take: @@ -19,8 +24,21 @@ workflow HASH_DEMULTIPLEXING { main: + ch_results = Channel.empty() + ch_versions = Channel.empty() + ch_htodemux_assignments = Channel.empty() + ch_htodemux_classifications = Channel.empty() + ch_multiseq = Channel.empty() + ch_bff = Channel.empty() + ch_demuxem = Channel.empty() + ch_gmmdemux_results = Channel.empty() + ch_gmmdemux_config = Channel.empty() + ch_hasheddrops_results = Channel.empty() + ch_hasheddrops_id_to_hash = Channel.empty() + ch_hashsolo = Channel.empty() + ch_samplesheet.map { meta, rna, hto -> { if (!rna) { @@ -54,8 +72,13 @@ workflow HASH_DEMULTIPLEXING { ch_rna = RENAME_GENES_TO_FEATURES_RNA(ch_rna) ch_hto = RENAME_GENES_TO_FEATURES_HTO(ch_hto) + ch_hashes = EXTRACT_HASHES(ch_hto.map { meta, hto -> [meta, "${hto}/features.tsv.gz"] }) - ch_samplesheet = ch_samplesheet.map { meta, _rna, _hto -> [meta] }.join(ch_rna).join(ch_hto) + ch_samplesheet = ch_samplesheet.map { meta, _rna, _hto -> [meta] } + .join(ch_rna) + .join(ch_hto) + .join(ch_hashes) + .map {meta, rna, hto, hashes -> [meta+[hashes: file(hashes).text.trim()], rna, hto] } if (methods.contains('htodemux') || methods.contains('multiseq')) { PREPROCESSING_FOR_HTODEMUX_MULTISEQ( @@ -67,7 +90,22 @@ workflow HASH_DEMULTIPLEXING { HTODEMUX( PREPROCESSING_FOR_HTODEMUX_MULTISEQ.out.seurat_object.map { meta, seurat_object -> [meta, seurat_object, "HTO"] } ) - ch_versions = ch_versions.mix(HTODEMUX.out.versions) + + ch_assignments = HTODEMUX.out.assignment + .map { meta, assignment -> + [meta, [result: assignment, method: 'htodemux_assignment']] + } + + ch_classifications = HTODEMUX.out.classification + .map { meta, classification -> + [meta, [result: classification, method: 'htodemux_classification']] + } + + ch_results = ch_results + .mix(ch_assignments,ch_classifications) + + ch_htodemux_assignments = ch_htodemux_assignments.mix(HTODEMUX.out.assignment) + ch_htodemux_classifications = ch_htodemux_classifications.mix(HTODEMUX.out.classification) HTODEMUX_VISUALIZATION( HTODEMUX.out.rds.map { meta, seurat_object -> [meta, seurat_object, "HTO"] } @@ -76,26 +114,27 @@ workflow HASH_DEMULTIPLEXING { } if (methods.contains('multiseq')) { MULTISEQDEMUX( - PREPROCESSING_FOR_HTODEMUX_MULTISEQ.out.seurat_object.map { meta, seurat_object -> [meta, seurat_object, "HTO"] } + PREPROCESSING_FOR_HTODEMUX_MULTISEQ.out.seurat_object.map { meta, seurat_object -> [ + meta, + seurat_object, + "HTO" + ] + } ) + + ch_multiseq = ch_multiseq.mix(MULTISEQDEMUX.out.results) ch_versions = ch_versions.mix(MULTISEQDEMUX.out.versions) } } + // TODO rename to bff since we named the module bff if (methods.contains('cellhashr')) { - error("CellHashR not implemented") + BFF(ch_samplesheet.map { meta, _rna, hto -> [meta,hto,params.bff_methods,params.bff_preprocessing]}) + ch_bff = ch_bff.mix(BFF.out.assignment) + ch_versions = ch_versions.mix(BFF.out.versions) } + if (methods.contains('demuxem')) { - ch_samplesheet.map { meta, rna, hto -> - { - if (!rna) { - error("RNA matrix not provided for sample ${meta.id}, but this is required for DemuxEM. Please check your input samplesheet.") - } - if (!hto) { - error("HTO matrix not provided for sample ${meta.id}, but this is required for DemuxEM. Please check your input samplesheet.") - } - } - } MTXCONVERT_RNA(ch_samplesheet.map { meta, rna, _hto -> [meta, rna] }, false) ch_versions = ch_versions.mix(MTXCONVERT_RNA.out.versions) @@ -107,42 +146,87 @@ workflow HASH_DEMULTIPLEXING { MTXCONVERT_RNA.out.h5.join(MTXCONVERT_HTO.out.csv), params.demuxem_gender_genes, params.genome ?: [], - true, + params.demuxem_generate_diagnostic_plots ) + + ch_demuxem = ch_demuxem.mix(DEMUXEM.out.out_zarr) ch_versions = ch_versions.mix(DEMUXEM.out.versions) } + if (methods.contains('gmm-demux')) { - ch_gmmdemux = ch_samplesheet.map { meta, _rna, hto -> [meta, hto, "MS-11,MS-12", meta.n_cells] } - ch_gmmdemux.map { meta, hto, hto_names, _estimated_cells -> - { - if (!hto) { - error("HTO matrix not provided for sample ${meta.id}, but this is required for GMM-Demux. Please check your input samplesheet.") - } - if (!hto_names) { - error("HTO names not provided for sample ${meta.id}, but this is required for GMM-Demux. Please check your input samplesheet.") - } + // TODO do the same as for meta.n_cells as for hash_list + ch_gmmdemux_input = ch_samplesheet.map { meta, _rna, hto -> [ + meta, + hto, + params.gmmdemux_hto_names ? params.gmmdemux_hto_names : meta.hashes, + meta.n_cells + ] } - } + GMMDEMUX( - ch_gmmdemux, - true, - true, - [], - [], + ch_gmmdemux_input, + params.gmmdemux_type_report, + params.gmmdemux_summary_report, + params.gmmdemux_skip ? params.gmmdemux_skip : [], + params.gmmdemux_examine ? params.gmmdemux_examine : [] ) + ch_versions = ch_versions.mix(GMMDEMUX.out.versions) + ch_gmmdemux_results = ch_gmmdemux_results.mix(GMMDEMUX.out.classification_report) + ch_gmmdemux_config = ch_gmmdemux_config.mix(GMMDEMUX.out.config_report) } if (methods.contains('hasheddrops')) { + HASHEDDROPS( - ch_samplesheet.map { meta, rna, hto -> [meta, hto, "FALSE", rna] } + ch_samplesheet.map { meta, rna, hto -> [ + meta, + hto, + params.hasheddrops_runEmptyDrops.toString().toUpperCase(), + rna + ] + } ) + + ch_hasheddrops_results = ch_hasheddrops_results.mix(HASHEDDROPS.out.results) + ch_hasheddrops_id_to_hash = ch_hasheddrops_id_to_hash.mix(HASHEDDROPS.out.id_to_hash) ch_versions = ch_versions.mix(HASHEDDROPS.out.versions) } if (methods.contains('hashsolo')) { - error("HashSolo not implemented") + + HASHSOLO( + ch_samplesheet.map {meta, _rna, hto -> [ + meta, + hto, + params.hashsolo_cell_hashing_columns ? params.hashsolo_cell_hashing_columns : [] + ] + } + ) + + ch_hashsolo = ch_hashsolo.mix(HASHSOLO.out.assignment) + ch_versions = ch_versions.mix(HASHSOLO.out.versions) } + ch_summary = ch_samplesheet + .join(ch_htodemux_assignments, remainder: true) + .join(ch_htodemux_classifications, remainder: true) + .join(ch_multiseq, remainder: true) + .join(ch_bff, remainder: true) + .join(ch_demuxem , remainder: true) + .join(ch_gmmdemux_results, remainder: true) + .join(ch_gmmdemux_config, remainder: true) + .join(ch_hasheddrops_results, remainder: true) + .join(ch_hasheddrops_id_to_hash, remainder: true) + .join(ch_hashsolo, remainder: true) + .map { tuple -> tuple.collect { it == null ? [] : it } } + // Empty inputs solved as recommended here: + // https://nf-co.re/docs/guidelines/components/modules#optional-inputs + + HASH_SUMMARY( + ch_summary, + tuple(params.generate_anndata, params.generate_mudata, params.bff_methods) + ) + emit: versions = ch_versions // channel: [ versions.yml ] } diff --git a/tests/default.nf.test.snap b/tests/default.nf.test.snap index ab911bf7..5dbc76f9 100644 --- a/tests/default.nf.test.snap +++ b/tests/default.nf.test.snap @@ -1,8 +1,13 @@ { "-profile test": { "content": [ - 70, + 82, { + "BFF": { + "r-base": "4.3.3", + "r-seurat": "4.3.0.1", + "cellhashR": "1.0.3" + }, "CELLSNP_MODEA": { "cellsnp": "1.2.3" }, @@ -15,9 +20,11 @@ "r-seurat": "5.3.0", "dropletutils": "1.26.0" }, - "HTODEMUX": { - "r-base": "4.4.3", - "seurat": "5.3.0" + "HASHSOLO": { + "matplotlib": "3.10.5", + "pandas": "2.3.1", + "python": "3.12.11", + "scanpy": "1.11.2" }, "HTODEMUX_VISUALIZATION": { "r-base": "4.4.3", @@ -75,6 +82,20 @@ } }, [ + "bff", + "bff/test1_assignment_bff.csv", + "bff/test1_metrics_bff.csv", + "bff/test1_params_bff.csv", + "bff/test2_assignment_bff.csv", + "bff/test2_metrics_bff.csv", + "bff/test2_params_bff.csv", + "bff/test3_assignment_bff.csv", + "bff/test3_metrics_bff.csv", + "bff/test3_params_bff.csv", + "extract", + "extract/test1_hashes.txt", + "extract/test2_hashes.txt", + "extract/test3_hashes.txt", "genetic", "genetic/popscle", "genetic/popscle/demuxlet", @@ -179,6 +200,7 @@ "hashing/hasheddrops/test1/test1_emptyDrops.png", "hashing/hasheddrops/test1/test1_emptyDrops.rds", "hashing/hasheddrops/test1/test1_hasheddrops.rds", + "hashing/hasheddrops/test1/test1_id_to_hash.csv", "hashing/hasheddrops/test1/test1_params_hasheddrops.csv", "hashing/hasheddrops/test1/test1_plot_hasheddrops.png", "hashing/hasheddrops/test1/test1_results_hasheddrops.csv", @@ -187,6 +209,7 @@ "hashing/hasheddrops/test2/test2_emptyDrops.png", "hashing/hasheddrops/test2/test2_emptyDrops.rds", "hashing/hasheddrops/test2/test2_hasheddrops.rds", + "hashing/hasheddrops/test2/test2_id_to_hash.csv", "hashing/hasheddrops/test2/test2_params_hasheddrops.csv", "hashing/hasheddrops/test2/test2_plot_hasheddrops.png", "hashing/hasheddrops/test2/test2_results_hasheddrops.csv", @@ -195,9 +218,23 @@ "hashing/hasheddrops/test3/test3_emptyDrops.png", "hashing/hasheddrops/test3/test3_emptyDrops.rds", "hashing/hasheddrops/test3/test3_hasheddrops.rds", + "hashing/hasheddrops/test3/test3_id_to_hash.csv", "hashing/hasheddrops/test3/test3_params_hasheddrops.csv", "hashing/hasheddrops/test3/test3_plot_hasheddrops.png", "hashing/hasheddrops/test3/test3_results_hasheddrops.csv", + "hashing/hashsolo", + "hashing/hashsolo/test1", + "hashing/hashsolo/test1/test1_assignment_hashsolo.csv", + "hashing/hashsolo/test1/test1_hashsolo.h5ad", + "hashing/hashsolo/test1/test1_params_hashsolo.csv", + "hashing/hashsolo/test2", + "hashing/hashsolo/test2/test2_assignment_hashsolo.csv", + "hashing/hashsolo/test2/test2_hashsolo.h5ad", + "hashing/hashsolo/test2/test2_params_hashsolo.csv", + "hashing/hashsolo/test3", + "hashing/hashsolo/test3/test3_assignment_hashsolo.csv", + "hashing/hashsolo/test3/test3_hashsolo.h5ad", + "hashing/hashsolo/test3/test3_params_hashsolo.csv", "hashing/htodemux", "hashing/htodemux/test1", "hashing/htodemux/test1/test1_assignment_htodemux.csv", @@ -259,6 +296,22 @@ "hashing/preprocessing/test3", "hashing/preprocessing/test3/test3_params_preprocessing.csv", "hashing/preprocessing/test3/test3_preprocessed.rds", + "hashing/summary", + "hashing/summary/test1", + "hashing/summary/test1/test1_hashing_summary.h5ad", + "hashing/summary/test1/test1_hashing_summary.h5mu", + "hashing/summary/test1/test1_hashing_summary_assignment.csv", + "hashing/summary/test1/test1_hashing_summary_classification.csv", + "hashing/summary/test2", + "hashing/summary/test2/test2_hashing_summary.h5ad", + "hashing/summary/test2/test2_hashing_summary.h5mu", + "hashing/summary/test2/test2_hashing_summary_assignment.csv", + "hashing/summary/test2/test2_hashing_summary_classification.csv", + "hashing/summary/test3", + "hashing/summary/test3/test3_hashing_summary.h5ad", + "hashing/summary/test3/test3_hashing_summary.h5mu", + "hashing/summary/test3/test3_hashing_summary_assignment.csv", + "hashing/summary/test3/test3_hashing_summary_classification.csv", "multiqc", "multiqc/multiqc_data", "multiqc/multiqc_data/BETA-multiqc.parquet", @@ -310,17 +363,29 @@ "untar/test3/matrix.mtx.gz" ], [ + "test1_assignment_bff.csv:md5,3cc0f2c5bc6f2834eeb82696c6d71c9d", + "test1_metrics_bff.csv:md5,14354e1e722db19e4dd9bbaabcce7ca5", + "test1_params_bff.csv:md5,042c4e0ec5f5337048401e79a6057795", + "test2_assignment_bff.csv:md5,3cc0f2c5bc6f2834eeb82696c6d71c9d", + "test2_metrics_bff.csv:md5,14354e1e722db19e4dd9bbaabcce7ca5", + "test2_params_bff.csv:md5,e15f93ceef80a233a0b21069f4dc8463", + "test3_assignment_bff.csv:md5,3cc0f2c5bc6f2834eeb82696c6d71c9d", + "test3_metrics_bff.csv:md5,14354e1e722db19e4dd9bbaabcce7ca5", + "test3_params_bff.csv:md5,abfa8d5115fa563bfab13325943c5a71", + "test1_hashes.txt:md5,395e7c801af4ad59b5995d2c7533b3e1", + "test2_hashes.txt:md5,395e7c801af4ad59b5995d2c7533b3e1", + "test3_hashes.txt:md5,395e7c801af4ad59b5995d2c7533b3e1", "test1.best:md5,1afa27ec59d7ea6b37e1b9ffbc6007e8", "test2.best:md5,1afa27ec59d7ea6b37e1b9ffbc6007e8", "test3.best:md5,1afa27ec59d7ea6b37e1b9ffbc6007e8", "test1.clust1.samples.gz:md5,42f8c8ba281fccf6edaf6dbd30144210", - "test1.clust1.vcf.gz:md5,065c760b7781af86fe0d4fe8094853d3", + "test1.clust1.vcf.gz:md5,50d70db92dc4460a3e38e8b29c38c18a", "test1.lmix:md5,d8be4c936ff56980a476d5ba82290b8e", "test2.clust1.samples.gz:md5,42f8c8ba281fccf6edaf6dbd30144210", - "test2.clust1.vcf.gz:md5,065c760b7781af86fe0d4fe8094853d3", + "test2.clust1.vcf.gz:md5,50d70db92dc4460a3e38e8b29c38c18a", "test2.lmix:md5,d8be4c936ff56980a476d5ba82290b8e", "test3.clust1.samples.gz:md5,42f8c8ba281fccf6edaf6dbd30144210", - "test3.clust1.vcf.gz:md5,065c760b7781af86fe0d4fe8094853d3", + "test3.clust1.vcf.gz:md5,50d70db92dc4460a3e38e8b29c38c18a", "test3.lmix:md5,d8be4c936ff56980a476d5ba82290b8e", "test1.base.vcf.gz:md5,36eccb4bd3e2130f395985e09a66a60d", "test1.cells.vcf.gz:md5,b28d8acaf17777c7cf9e2c7d01d93a66", @@ -368,23 +433,35 @@ "test1_emptyDrops.png:md5,2d7113a76a494b2b2c18b77c4a664063", "test1_emptyDrops.rds:md5,bda2e88b1e3e8c8ea5a22e19e3ab243d", "test1_hasheddrops.rds:md5,31ba0ecae1d94080bccf81cdbaa5afe8", - "test1_params_hasheddrops.csv:md5,4843ded1642bba8d8e38e70a9733d047", + "test1_id_to_hash.csv:md5,a015ca1178177038db21a97fa2070874", + "test1_params_hasheddrops.csv:md5,b253501914df23ef5901262e8a241a8d", "test1_plot_hasheddrops.png:md5,2d7113a76a494b2b2c18b77c4a664063", "test1_results_hasheddrops.csv:md5,d0f2af6d9bdb47a80bfbf08a85493d2e", "test2_emptyDrops.csv:md5,a227d42afbcc590b4e949075cde4a5b6", "test2_emptyDrops.png:md5,2d7113a76a494b2b2c18b77c4a664063", "test2_emptyDrops.rds:md5,bda2e88b1e3e8c8ea5a22e19e3ab243d", "test2_hasheddrops.rds:md5,31ba0ecae1d94080bccf81cdbaa5afe8", - "test2_params_hasheddrops.csv:md5,c9edfc98fbf87bb8ba9b29ba51c9a55b", + "test2_id_to_hash.csv:md5,a015ca1178177038db21a97fa2070874", + "test2_params_hasheddrops.csv:md5,3612264937e5c5962fefddc43afb595f", "test2_plot_hasheddrops.png:md5,2d7113a76a494b2b2c18b77c4a664063", "test2_results_hasheddrops.csv:md5,d0f2af6d9bdb47a80bfbf08a85493d2e", "test3_emptyDrops.csv:md5,a227d42afbcc590b4e949075cde4a5b6", "test3_emptyDrops.png:md5,2d7113a76a494b2b2c18b77c4a664063", "test3_emptyDrops.rds:md5,bda2e88b1e3e8c8ea5a22e19e3ab243d", "test3_hasheddrops.rds:md5,31ba0ecae1d94080bccf81cdbaa5afe8", - "test3_params_hasheddrops.csv:md5,295547e2ed166ea01f21ac99a31d7c48", + "test3_id_to_hash.csv:md5,a015ca1178177038db21a97fa2070874", + "test3_params_hasheddrops.csv:md5,b15e94f887e3ada8f790ea18eb791d8b", "test3_plot_hasheddrops.png:md5,2d7113a76a494b2b2c18b77c4a664063", "test3_results_hasheddrops.csv:md5,d0f2af6d9bdb47a80bfbf08a85493d2e", + "test1_assignment_hashsolo.csv:md5,a970a5192150b1832a3efea197c882cd", + "test1_hashsolo.h5ad:md5,91ebf16f7c96c069bd9e4112c51d9498", + "test1_params_hashsolo.csv:md5,cad7ebf7836fa1ec5da0a2eb13501024", + "test2_assignment_hashsolo.csv:md5,a970a5192150b1832a3efea197c882cd", + "test2_hashsolo.h5ad:md5,91ebf16f7c96c069bd9e4112c51d9498", + "test2_params_hashsolo.csv:md5,3d687d34d7ea579aaf73d9175964e335", + "test3_assignment_hashsolo.csv:md5,a970a5192150b1832a3efea197c882cd", + "test3_hashsolo.h5ad:md5,91ebf16f7c96c069bd9e4112c51d9498", + "test3_params_hashsolo.csv:md5,880b0108f2f9187225805416f4068aaa", "test1_assignment_htodemux.csv:md5,065f73102e5e28f47d1ae5d679bacc4d", "test1_classification_htodemux.csv:md5,7627fa55b5ec02b06a356ee2f55e916f", "test1_params_htodemux.csv:md5,08b6c065b0c383e6be3c20787ec01d3e", @@ -421,6 +498,18 @@ "test1_params_preprocessing.csv:md5,b77b94588a53835e913cb55b3791d309", "test2_params_preprocessing.csv:md5,0ed6b213e9f08ee5fc0f89b7184a3fb9", "test3_params_preprocessing.csv:md5,c5b931c6fe281d0cc63cb3ed13cb2d10", + "test1_hashing_summary.h5ad:md5,1312ac387a45d5b37487a8fac87721e1", + "test1_hashing_summary.h5mu:md5,c6239f9fae1e006d1b787d4473d63237", + "test1_hashing_summary_assignment.csv:md5,98e51d61f0726735550c325b23848cff", + "test1_hashing_summary_classification.csv:md5,acf78ede46f45285de3988776301fdea", + "test2_hashing_summary.h5ad:md5,1312ac387a45d5b37487a8fac87721e1", + "test2_hashing_summary.h5mu:md5,c6239f9fae1e006d1b787d4473d63237", + "test2_hashing_summary_assignment.csv:md5,98e51d61f0726735550c325b23848cff", + "test2_hashing_summary_classification.csv:md5,acf78ede46f45285de3988776301fdea", + "test3_hashing_summary.h5ad:md5,1312ac387a45d5b37487a8fac87721e1", + "test3_hashing_summary.h5mu:md5,c6239f9fae1e006d1b787d4473d63237", + "test3_hashing_summary_assignment.csv:md5,98e51d61f0726735550c325b23848cff", + "test3_hashing_summary_classification.csv:md5,acf78ede46f45285de3988776301fdea", "multiqc_citations.txt:md5,4c806e63a283ec1b7e78cdae3a923d4f", "barcodes.tsv.gz:md5,291349dc31e204c71c4b121de4959686", "features.tsv.gz:md5,6ac5b411e9e5b8eee2bc58ddf3ce6f2f", @@ -453,8 +542,8 @@ ], "meta": { "nf-test": "0.9.2", - "nextflow": "25.04.3" + "nextflow": "25.04.6" }, - "timestamp": "2025-07-05T08:26:05.633684679" + "timestamp": "2025-09-16T16:24:45.507456019" } } \ No newline at end of file From 5f3a8726531829463f73ab5b9e0dc6ae84acd632 Mon Sep 17 00:00:00 2001 From: Nico Trummer Date: Tue, 16 Sep 2025 20:36:59 +0200 Subject: [PATCH 37/74] Template update to 3.3.2 (#74) * Template update for nf-core/tools version 3.3.2 * Template update for nf-core/tools version 3.3.2 * Update multiQC module * Fix vireo_cell_range issue --- .github/PULL_REQUEST_TEMPLATE.md | 4 +- .github/actions/nf-test/action.yml | 4 - .github/workflows/linting.yml | 2 +- .github/workflows/linting_comment.yml | 2 +- .github/workflows/nf-test.yml | 49 ++++---- .github/workflows/release-announcements.yml | 2 +- .nf-core.yml | 2 +- .pre-commit-config.yaml | 2 +- README.md | 6 +- assets/schema_input.json | 2 +- conf/base.config | 1 + conf/modules.config | 2 +- modules.json | 2 +- modules/nf-core/multiqc/environment.yml | 2 +- modules/nf-core/multiqc/main.nf | 4 +- modules/nf-core/multiqc/meta.yml | 110 ++++++++++-------- .../nf-core/multiqc/tests/main.nf.test.snap | 18 +-- nextflow.config | 10 +- nextflow_schema.json | 2 +- nf-test.config | 2 +- ro-crate-metadata.json | 16 +-- .../tests/nextflow.config | 2 +- tests/.nftignore | 1 + tests/nextflow.config | 6 +- 24 files changed, 137 insertions(+), 116 deletions(-) diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index 68092d60..4bf7ad0b 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -8,14 +8,14 @@ These are the most common things requested on pull requests (PRs). Remember that PRs should be made against the dev branch, unless you're preparing a pipeline release. -Learn more about contributing: [CONTRIBUTING.md](https://github.com/nf-core/hadge/tree/main/.github/CONTRIBUTING.md) +Learn more about contributing: [CONTRIBUTING.md](https://github.com/nf-core/hadge/tree/master/.github/CONTRIBUTING.md) --> ## PR checklist - [ ] This comment contains a description of changes (with reason). - [ ] If you've fixed a bug or added code that should be tested, add tests! -- [ ] If you've added a new tool - have you followed the pipeline conventions in the [contribution docs](https://github.com/nf-core/hadge/tree/main/.github/CONTRIBUTING.md) +- [ ] If you've added a new tool - have you followed the pipeline conventions in the [contribution docs](https://github.com/nf-core/hadge/tree/master/.github/CONTRIBUTING.md) - [ ] If necessary, also make a PR on the nf-core/hadge _branch_ on the [nf-core/test-datasets](https://github.com/nf-core/test-datasets) repository. - [ ] Make sure your code lints (`nf-core pipelines lint`). - [ ] Ensure the test suite passes (`nextflow run . -profile test,docker --outdir `). diff --git a/.github/actions/nf-test/action.yml b/.github/actions/nf-test/action.yml index 243e7823..bf44d961 100644 --- a/.github/actions/nf-test/action.yml +++ b/.github/actions/nf-test/action.yml @@ -54,13 +54,9 @@ runs: conda-solver: libmamba conda-remove-defaults: true - # TODO Skip failing conda tests and document their failures - # https://github.com/nf-core/modules/issues/7017 - name: Run nf-test shell: bash env: - NFT_DIFF: ${{ env.NFT_DIFF }} - NFT_DIFF_ARGS: ${{ env.NFT_DIFF_ARGS }} NFT_WORKDIR: ${{ env.NFT_WORKDIR }} run: | nf-test test \ diff --git a/.github/workflows/linting.yml b/.github/workflows/linting.yml index f2d7d1dd..8b0f88c3 100644 --- a/.github/workflows/linting.yml +++ b/.github/workflows/linting.yml @@ -13,7 +13,7 @@ jobs: steps: - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4 - - name: Set up Python 3.12 + - name: Set up Python 3.13 uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5 with: python-version: "3.13" diff --git a/.github/workflows/linting_comment.yml b/.github/workflows/linting_comment.yml index 7e8050fb..d43797d9 100644 --- a/.github/workflows/linting_comment.yml +++ b/.github/workflows/linting_comment.yml @@ -11,7 +11,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Download lint results - uses: dawidd6/action-download-artifact@4c1e823582f43b179e2cbb49c3eade4e41f992e2 # v10 + uses: dawidd6/action-download-artifact@ac66b43f0e6a346234dd65d4d0c8fbb31cb316e5 # v11 with: workflow: linting.yml workflow_conclusion: completed diff --git a/.github/workflows/nf-test.yml b/.github/workflows/nf-test.yml index f166bac5..e7b58449 100644 --- a/.github/workflows/nf-test.yml +++ b/.github/workflows/nf-test.yml @@ -1,12 +1,5 @@ name: Run nf-test on: - push: - paths-ignore: - - "docs/**" - - "**/meta.yml" - - "**/*.md" - - "**/*.png" - - "**/*.svg" pull_request: paths-ignore: - "docs/**" @@ -34,7 +27,9 @@ env: jobs: nf-test-changes: name: nf-test-changes - runs-on: ubuntu-latest + runs-on: # use self-hosted runners + - runs-on=${{ github.run_id }}-nf-test-changes + - runner=4cpu-linux-x64 outputs: shard: ${{ steps.set-shards.outputs.shard }} total_shards: ${{ steps.set-shards.outputs.total_shards }} @@ -66,7 +61,9 @@ jobs: name: "${{ matrix.profile }} | ${{ matrix.NXF_VER }} | ${{ matrix.shard }}/${{ needs.nf-test-changes.outputs.total_shards }}" needs: [nf-test-changes] if: ${{ needs.nf-test-changes.outputs.total_shards != '0' }} - runs-on: ubuntu-latest + runs-on: # use self-hosted runners + - runs-on=${{ github.run_id }}-nf-test + - runner=4cpu-linux-x64 strategy: fail-fast: false matrix: @@ -81,7 +78,7 @@ jobs: - isMain: false profile: "singularity" NXF_VER: - - "24.04.2" + - "24.10.5" - "latest-everything" env: NXF_ANSI_LOG: false @@ -93,19 +90,39 @@ jobs: fetch-depth: 0 - name: Run nf-test + id: run_nf_test uses: ./.github/actions/nf-test + continue-on-error: ${{ matrix.NXF_VER == 'latest-everything' }} env: NFT_WORKDIR: ${{ env.NFT_WORKDIR }} with: profile: ${{ matrix.profile }} shard: ${{ matrix.shard }} total_shards: ${{ env.TOTAL_SHARDS }} + + - name: Report test status + if: ${{ always() }} + run: | + if [[ "${{ steps.run_nf_test.outcome }}" == "failure" ]]; then + echo "::error::Test with ${{ matrix.NXF_VER }} failed" + # Add to workflow summary + echo "## ❌ Test failed: ${{ matrix.profile }} | ${{ matrix.NXF_VER }} | Shard ${{ matrix.shard }}/${{ env.TOTAL_SHARDS }}" >> $GITHUB_STEP_SUMMARY + if [[ "${{ matrix.NXF_VER }}" == "latest-everything" ]]; then + echo "::warning::Test with latest-everything failed but will not cause workflow failure. Please check if the error is expected or if it needs fixing." + fi + if [[ "${{ matrix.NXF_VER }}" != "latest-everything" ]]; then + exit 1 + fi + fi + confirm-pass: needs: [nf-test] if: always() - runs-on: ubuntu-latest + runs-on: # use self-hosted runners + - runs-on=${{ github.run_id }}-confirm-pass + - runner=2cpu-linux-x64 steps: - - name: One or more tests failed + - name: One or more tests failed (excluding latest-everything) if: ${{ contains(needs.*.result, 'failure') }} run: exit 1 @@ -124,11 +141,3 @@ jobs: echo "DEBUG: toJSON(needs) = ${{ toJSON(needs) }}" echo "DEBUG: toJSON(needs.*.result) = ${{ toJSON(needs.*.result) }}" echo "::endgroup::" - - - name: Clean Workspace # Purge the workspace in case it's running on a self-hosted runner - if: always() - run: | - ls -la ./ - rm -rf ./* || true - rm -rf ./.??* || true - ls -la ./ diff --git a/.github/workflows/release-announcements.yml b/.github/workflows/release-announcements.yml index 4abaf484..0f732495 100644 --- a/.github/workflows/release-announcements.yml +++ b/.github/workflows/release-announcements.yml @@ -30,7 +30,7 @@ jobs: bsky-post: runs-on: ubuntu-latest steps: - - uses: zentered/bluesky-post-action@4aa83560bb3eac05dbad1e5f221ee339118abdd2 # v0.2.0 + - uses: zentered/bluesky-post-action@6461056ea355ea43b977e149f7bf76aaa572e5e8 # v0.3.0 with: post: | Pipeline release! ${{ github.repository }} v${{ github.event.release.tag_name }} - ${{ github.event.release.name }}! diff --git a/.nf-core.yml b/.nf-core.yml index 79035b01..c96b6385 100644 --- a/.nf-core.yml +++ b/.nf-core.yml @@ -1,7 +1,7 @@ lint: files_unchanged: - .github/PULL_REQUEST_TEMPLATE.md -nf_core_version: 3.3.1 +nf_core_version: 3.3.2 repository_type: pipeline template: author: Fabiola Curion diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 9d0b248d..bb41beec 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -4,7 +4,7 @@ repos: hooks: - id: prettier additional_dependencies: - - prettier@3.5.0 + - prettier@3.6.2 - repo: https://github.com/pre-commit/pre-commit-hooks rev: v5.0.0 hooks: diff --git a/README.md b/README.md index 967ca36a..49ef7339 100644 --- a/README.md +++ b/README.md @@ -5,12 +5,12 @@ -[![GitHub Actions CI Status](https://github.com/nf-core/hadge/actions/workflows/ci.yml/badge.svg)](https://github.com/nf-core/hadge/actions/workflows/ci.yml) +[![GitHub Actions CI Status](https://github.com/nf-core/hadge/actions/workflows/nf-test.yml/badge.svg)](https://github.com/nf-core/hadge/actions/workflows/nf-test.yml) [![GitHub Actions Linting Status](https://github.com/nf-core/hadge/actions/workflows/linting.yml/badge.svg)](https://github.com/nf-core/hadge/actions/workflows/linting.yml)[![AWS CI](https://img.shields.io/badge/CI%20tests-full%20size-FF9900?labelColor=000000&logo=Amazon%20AWS)](https://nf-co.re/hadge/results)[![Cite with Zenodo](http://img.shields.io/badge/DOI-10.5281/zenodo.XXXXXXX-1073c8?labelColor=000000)](https://doi.org/10.5281/zenodo.XXXXXXX) [![nf-test](https://img.shields.io/badge/unit_tests-nf--test-337ab7.svg)](https://www.nf-test.com) -[![Nextflow](https://img.shields.io/badge/version-%E2%89%A524.04.2-green?style=flat&logo=nextflow&logoColor=white&color=%230DC09D&link=https%3A%2F%2Fnextflow.io)](https://www.nextflow.io/) -[![nf-core template version](https://img.shields.io/badge/nf--core_template-3.3.1-green?style=flat&logo=nfcore&logoColor=white&color=%2324B064&link=https%3A%2F%2Fnf-co.re)](https://github.com/nf-core/tools/releases/tag/3.3.1) +[![Nextflow](https://img.shields.io/badge/version-%E2%89%A524.10.5-green?style=flat&logo=nextflow&logoColor=white&color=%230DC09D&link=https%3A%2F%2Fnextflow.io)](https://www.nextflow.io/) +[![nf-core template version](https://img.shields.io/badge/nf--core_template-3.3.2-green?style=flat&logo=nfcore&logoColor=white&color=%2324B064&link=https%3A%2F%2Fnf-co.re)](https://github.com/nf-core/tools/releases/tag/3.3.2) [![run with conda](http://img.shields.io/badge/run%20with-conda-3EB049?labelColor=000000&logo=anaconda)](https://docs.conda.io/en/latest/) [![run with docker](https://img.shields.io/badge/run%20with-docker-0db7ed?labelColor=000000&logo=docker)](https://www.docker.com/) [![run with singularity](https://img.shields.io/badge/run%20with-singularity-1d355c.svg?labelColor=000000)](https://sylabs.io/docs/) diff --git a/assets/schema_input.json b/assets/schema_input.json index 39eb7d72..2fa8591d 100644 --- a/assets/schema_input.json +++ b/assets/schema_input.json @@ -1,6 +1,6 @@ { "$schema": "https://json-schema.org/draft/2020-12/schema", - "$id": "https://raw.githubusercontent.com/nf-core/hadge/main/assets/schema_input.json", + "$id": "https://raw.githubusercontent.com/nf-core/hadge/master/assets/schema_input.json", "title": "nf-core/hadge pipeline - params.input schema", "description": "Schema for the file provided with params.input", "type": "array", diff --git a/conf/base.config b/conf/base.config index 1a68544d..89b614c4 100644 --- a/conf/base.config +++ b/conf/base.config @@ -61,5 +61,6 @@ process { } withLabel: process_gpu { ext.use_gpu = { workflow.profile.contains('gpu') } + accelerator = { workflow.profile.contains('gpu') ? 1 : null } } } diff --git a/conf/modules.config b/conf/modules.config index 393a5c4d..eba8820e 100644 --- a/conf/modules.config +++ b/conf/modules.config @@ -105,7 +105,7 @@ process { params.vireo_no_plot ? "--noPlot" : "", "--randSeed", params.vireo_rand_seed, - params.vireo_cell_range != "all" ? "--cellRange ${params.vireo_cell_range}" : "", + (params.vireo_cell_range != "all" ? "--cellRange ${params.vireo_cell_range}" : ""), params.vireo_cell_ambient_rnas ? "--callAmbientRNAs" : "", ].join(" ") } diff --git a/modules.json b/modules.json index 04935b5d..0e927232 100644 --- a/modules.json +++ b/modules.json @@ -41,7 +41,7 @@ }, "multiqc": { "branch": "master", - "git_sha": "e594e9dfaffa7572afc11bafc634984fd4cbd87b", + "git_sha": "e10b76ca0c66213581bec2833e30d31f239dec0b", "installed_by": ["modules"] }, "multiseqdemux": { diff --git a/modules/nf-core/multiqc/environment.yml b/modules/nf-core/multiqc/environment.yml index 812fc4c5..dd513cbd 100644 --- a/modules/nf-core/multiqc/environment.yml +++ b/modules/nf-core/multiqc/environment.yml @@ -4,4 +4,4 @@ channels: - conda-forge - bioconda dependencies: - - bioconda::multiqc=1.29 + - bioconda::multiqc=1.31 diff --git a/modules/nf-core/multiqc/main.nf b/modules/nf-core/multiqc/main.nf index 0ac3c369..5288f5cc 100644 --- a/modules/nf-core/multiqc/main.nf +++ b/modules/nf-core/multiqc/main.nf @@ -3,8 +3,8 @@ process MULTIQC { conda "${moduleDir}/environment.yml" container "${ workflow.containerEngine == 'singularity' && !task.ext.singularity_pull_docker_container ? - 'https://depot.galaxyproject.org/singularity/multiqc:1.29--pyhdfd78af_0' : - 'biocontainers/multiqc:1.29--pyhdfd78af_0' }" + 'https://community-cr-prod.seqera.io/docker/registry/v2/blobs/sha256/ef/eff0eafe78d5f3b65a6639265a16b89fdca88d06d18894f90fcdb50142004329/data' : + 'community.wave.seqera.io/library/multiqc:1.31--1efbafd542a23882' }" input: path multiqc_files, stageAs: "?/*" diff --git a/modules/nf-core/multiqc/meta.yml b/modules/nf-core/multiqc/meta.yml index b16c1879..ce30eb73 100644 --- a/modules/nf-core/multiqc/meta.yml +++ b/modules/nf-core/multiqc/meta.yml @@ -15,57 +15,71 @@ tools: licence: ["GPL-3.0-or-later"] identifier: biotools:multiqc input: - - - multiqc_files: - type: file - description: | - List of reports / files recognised by MultiQC, for example the html and zip output of FastQC - - - multiqc_config: - type: file - description: Optional config yml for MultiQC - pattern: "*.{yml,yaml}" - - - extra_multiqc_config: - type: file - description: Second optional config yml for MultiQC. Will override common sections - in multiqc_config. - pattern: "*.{yml,yaml}" - - - multiqc_logo: + - multiqc_files: + type: file + description: | + List of reports / files recognised by MultiQC, for example the html and zip output of FastQC + ontologies: [] + - multiqc_config: + type: file + description: Optional config yml for MultiQC + pattern: "*.{yml,yaml}" + ontologies: + - edam: http://edamontology.org/format_3750 # YAML + - extra_multiqc_config: + type: file + description: Second optional config yml for MultiQC. Will override common sections + in multiqc_config. + pattern: "*.{yml,yaml}" + ontologies: + - edam: http://edamontology.org/format_3750 # YAML + - multiqc_logo: + type: file + description: Optional logo file for MultiQC + pattern: "*.{png}" + ontologies: [] + - replace_names: + type: file + description: | + Optional two-column sample renaming file. First column a set of + patterns, second column a set of corresponding replacements. Passed via + MultiQC's `--replace-names` option. + pattern: "*.{tsv}" + ontologies: + - edam: http://edamontology.org/format_3475 # TSV + - sample_names: + type: file + description: | + Optional TSV file with headers, passed to the MultiQC --sample_names + argument. + pattern: "*.{tsv}" + ontologies: + - edam: http://edamontology.org/format_3475 # TSV +output: + report: + - "*multiqc_report.html": type: file - description: Optional logo file for MultiQC - pattern: "*.{png}" - - - replace_names: + description: MultiQC report file + pattern: "multiqc_report.html" + ontologies: [] + data: + - "*_data": + type: directory + description: MultiQC data dir + pattern: "multiqc_data" + plots: + - "*_plots": type: file - description: | - Optional two-column sample renaming file. First column a set of - patterns, second column a set of corresponding replacements. Passed via - MultiQC's `--replace-names` option. - pattern: "*.{tsv}" - - - sample_names: + description: Plots created by MultiQC + pattern: "*_data" + ontologies: [] + versions: + - versions.yml: type: file - description: | - Optional TSV file with headers, passed to the MultiQC --sample_names - argument. - pattern: "*.{tsv}" -output: - - report: - - "*multiqc_report.html": - type: file - description: MultiQC report file - pattern: "multiqc_report.html" - - data: - - "*_data": - type: directory - description: MultiQC data dir - pattern: "multiqc_data" - - plots: - - "*_plots": - type: file - description: Plots created by MultiQC - pattern: "*_data" - - versions: - - versions.yml: - type: file - description: File containing software versions - pattern: "versions.yml" + description: File containing software versions + pattern: "versions.yml" + ontologies: + - edam: http://edamontology.org/format_3750 # YAML authors: - "@abhi18av" - "@bunop" diff --git a/modules/nf-core/multiqc/tests/main.nf.test.snap b/modules/nf-core/multiqc/tests/main.nf.test.snap index 88e90571..17881d15 100644 --- a/modules/nf-core/multiqc/tests/main.nf.test.snap +++ b/modules/nf-core/multiqc/tests/main.nf.test.snap @@ -2,14 +2,14 @@ "multiqc_versions_single": { "content": [ [ - "versions.yml:md5,c1fe644a37468f6dae548d98bc72c2c1" + "versions.yml:md5,8968b114a3e20756d8af2b80713bcc4f" ] ], "meta": { "nf-test": "0.9.2", - "nextflow": "25.04.2" + "nextflow": "25.04.6" }, - "timestamp": "2025-05-22T11:50:41.182332996" + "timestamp": "2025-09-08T20:57:36.139055243" }, "multiqc_stub": { "content": [ @@ -17,25 +17,25 @@ "multiqc_report.html", "multiqc_data", "multiqc_plots", - "versions.yml:md5,c1fe644a37468f6dae548d98bc72c2c1" + "versions.yml:md5,8968b114a3e20756d8af2b80713bcc4f" ] ], "meta": { "nf-test": "0.9.2", - "nextflow": "25.04.2" + "nextflow": "25.04.6" }, - "timestamp": "2025-05-22T11:51:22.448739369" + "timestamp": "2025-09-08T20:59:15.142230631" }, "multiqc_versions_config": { "content": [ [ - "versions.yml:md5,c1fe644a37468f6dae548d98bc72c2c1" + "versions.yml:md5,8968b114a3e20756d8af2b80713bcc4f" ] ], "meta": { "nf-test": "0.9.2", - "nextflow": "25.04.2" + "nextflow": "25.04.6" }, - "timestamp": "2025-05-22T11:51:06.198928424" + "timestamp": "2025-09-08T20:58:29.629087066" } } \ No newline at end of file diff --git a/nextflow.config b/nextflow.config index ef4a2254..2ba9ce46 100644 --- a/nextflow.config +++ b/nextflow.config @@ -440,8 +440,6 @@ dag { manifest { name = 'nf-core/hadge' - author = """Fabiola Curion""" - // The author field is deprecated from Nextflow version 24.10.0, use contributors instead contributors = [ [ name: 'Fabiola Curion', @@ -455,15 +453,15 @@ manifest { homePage = 'https://github.com/nf-core/hadge' description = """Comprehensive pipeline for donor demultiplexing in single cell""" mainScript = 'main.nf' - defaultBranch = 'main' - nextflowVersion = '!>=24.04.2' + defaultBranch = 'master' + nextflowVersion = '!>=24.10.5' version = '1.0.0dev' doi = '' } // Nextflow plugins plugins { - id 'nf-schema@2.3.0' + id 'nf-schema@2.4.2' // Validation of pipeline parameters and creation of an input channel from a sample sheet } validation { @@ -489,7 +487,7 @@ validation { https://doi.org/10.1038/s41587-020-0439-x * Software dependencies - https://github.com/nf-core/hadge/blob/main/CITATIONS.md + https://github.com/nf-core/hadge/blob/master/CITATIONS.md """ } summary { diff --git a/nextflow_schema.json b/nextflow_schema.json index 0b16b021..b2a9d0de 100644 --- a/nextflow_schema.json +++ b/nextflow_schema.json @@ -1,6 +1,6 @@ { "$schema": "https://json-schema.org/draft/2020-12/schema", - "$id": "https://raw.githubusercontent.com/nf-core/hadge/main/nextflow_schema.json", + "$id": "https://raw.githubusercontent.com/nf-core/hadge/master/nextflow_schema.json", "title": "nf-core/hadge pipeline parameters", "description": "Comprehensive pipeline for donor demultiplexing in single cell", "type": "object", diff --git a/nf-test.config b/nf-test.config index 889df760..3a1fff59 100644 --- a/nf-test.config +++ b/nf-test.config @@ -9,7 +9,7 @@ config { configFile "tests/nextflow.config" // ignore tests coming from the nf-core/modules repo - ignore 'modules/nf-core/**/*', 'subworkflows/nf-core/**/*' + ignore 'modules/nf-core/**/tests/*', 'subworkflows/nf-core/**/tests/*' // run all test with defined profile(s) from the main nextflow.config profile "test" diff --git a/ro-crate-metadata.json b/ro-crate-metadata.json index 690706d0..721c720d 100644 --- a/ro-crate-metadata.json +++ b/ro-crate-metadata.json @@ -22,8 +22,8 @@ "@id": "./", "@type": "Dataset", "creativeWorkStatus": "InProgress", - "datePublished": "2025-06-13T12:26:36+00:00", - "description": "

    \n \n \n \"nf-core/hadge\"\n \n

    \n\n[![GitHub Actions CI Status](https://github.com/nf-core/hadge/actions/workflows/ci.yml/badge.svg)](https://github.com/nf-core/hadge/actions/workflows/ci.yml)\n[![GitHub Actions Linting Status](https://github.com/nf-core/hadge/actions/workflows/linting.yml/badge.svg)](https://github.com/nf-core/hadge/actions/workflows/linting.yml)[![AWS CI](https://img.shields.io/badge/CI%20tests-full%20size-FF9900?labelColor=000000&logo=Amazon%20AWS)](https://nf-co.re/hadge/results)[![Cite with Zenodo](http://img.shields.io/badge/DOI-10.5281/zenodo.XXXXXXX-1073c8?labelColor=000000)](https://doi.org/10.5281/zenodo.XXXXXXX)\n[![nf-test](https://img.shields.io/badge/unit_tests-nf--test-337ab7.svg)](https://www.nf-test.com)\n\n[![Nextflow](https://img.shields.io/badge/version-%E2%89%A524.04.2-green?style=flat&logo=nextflow&logoColor=white&color=%230DC09D&link=https%3A%2F%2Fnextflow.io)](https://www.nextflow.io/)\n[![nf-core template version](https://img.shields.io/badge/nf--core_template-3.3.1-green?style=flat&logo=nfcore&logoColor=white&color=%2324B064&link=https%3A%2F%2Fnf-co.re)](https://github.com/nf-core/tools/releases/tag/3.3.1)\n[![run with conda](http://img.shields.io/badge/run%20with-conda-3EB049?labelColor=000000&logo=anaconda)](https://docs.conda.io/en/latest/)\n[![run with docker](https://img.shields.io/badge/run%20with-docker-0db7ed?labelColor=000000&logo=docker)](https://www.docker.com/)\n[![run with singularity](https://img.shields.io/badge/run%20with-singularity-1d355c.svg?labelColor=000000)](https://sylabs.io/docs/)\n[![Launch on Seqera Platform](https://img.shields.io/badge/Launch%20%F0%9F%9A%80-Seqera%20Platform-%234256e7)](https://cloud.seqera.io/launch?pipeline=https://github.com/nf-core/hadge)\n\n[![Get help on Slack](http://img.shields.io/badge/slack-nf--core%20%23hadge-4A154B?labelColor=000000&logo=slack)](https://nfcore.slack.com/channels/hadge)[![Follow on Bluesky](https://img.shields.io/badge/bluesky-%40nf__core-1185fe?labelColor=000000&logo=bluesky)](https://bsky.app/profile/nf-co.re)[![Follow on Mastodon](https://img.shields.io/badge/mastodon-nf__core-6364ff?labelColor=FFFFFF&logo=mastodon)](https://mstdn.science/@nf_core)[![Watch on YouTube](http://img.shields.io/badge/youtube-nf--core-FF0000?labelColor=000000&logo=youtube)](https://www.youtube.com/c/nf-core)\n\n## Introduction\n\n**nf-core/hadge** is a bioinformatics pipeline that ...\n\n\n\n\n2. Present QC for raw reads ([`MultiQC`](http://multiqc.info/))\n\n## Usage\n\n> [!NOTE]\n> If you are new to Nextflow and nf-core, please refer to [this page](https://nf-co.re/docs/usage/installation) on how to set-up Nextflow. Make sure to [test your setup](https://nf-co.re/docs/usage/introduction#how-to-run-a-pipeline) with `-profile test` before running the workflow on actual data.\n\n\n\nNow, you can run the pipeline using:\n\n\n\n```bash\nnextflow run nf-core/hadge \\\n -profile \\\n --input samplesheet.csv \\\n --outdir \n```\n\n> [!WARNING]\n> Please provide pipeline parameters via the CLI or Nextflow `-params-file` option. Custom config files including those provided by the `-c` Nextflow option can be used to provide any configuration _**except for parameters**_; see [docs](https://nf-co.re/docs/usage/getting_started/configuration#custom-configuration-files).\n\nFor more details and further functionality, please refer to the [usage documentation](https://nf-co.re/hadge/usage) and the [parameter documentation](https://nf-co.re/hadge/parameters).\n\n## Pipeline output\n\nTo see the results of an example test run with a full size dataset refer to the [results](https://nf-co.re/hadge/results) tab on the nf-core website pipeline page.\nFor more details about the output files and reports, please refer to the\n[output documentation](https://nf-co.re/hadge/output).\n\n## Credits\n\nnf-core/hadge was originally written by Fabiola Curion.\n\nWe thank the following people for their extensive assistance in the development of this pipeline:\n\n\n\n## Contributions and Support\n\nIf you would like to contribute to this pipeline, please see the [contributing guidelines](.github/CONTRIBUTING.md).\n\nFor further information or help, don't hesitate to get in touch on the [Slack `#hadge` channel](https://nfcore.slack.com/channels/hadge) (you can join with [this invite](https://nf-co.re/join/slack)).\n\n## Citations\n\n\n\n\n\n\nAn extensive list of references for the tools used by the pipeline can be found in the [`CITATIONS.md`](CITATIONS.md) file.\n\nYou can cite the `nf-core` publication as follows:\n\n> **The nf-core framework for community-curated bioinformatics pipelines.**\n>\n> Philip Ewels, Alexander Peltzer, Sven Fillinger, Harshil Patel, Johannes Alneberg, Andreas Wilm, Maxime Ulysse Garcia, Paolo Di Tommaso & Sven Nahnsen.\n>\n> _Nat Biotechnol._ 2020 Feb 13. doi: [10.1038/s41587-020-0439-x](https://dx.doi.org/10.1038/s41587-020-0439-x).\n", + "datePublished": "2025-09-16T17:37:05+00:00", + "description": "

    \n \n \n \"nf-core/hadge\"\n \n

    \n\n[![GitHub Actions CI Status](https://github.com/nf-core/hadge/actions/workflows/nf-test.yml/badge.svg)](https://github.com/nf-core/hadge/actions/workflows/nf-test.yml)\n[![GitHub Actions Linting Status](https://github.com/nf-core/hadge/actions/workflows/linting.yml/badge.svg)](https://github.com/nf-core/hadge/actions/workflows/linting.yml)[![AWS CI](https://img.shields.io/badge/CI%20tests-full%20size-FF9900?labelColor=000000&logo=Amazon%20AWS)](https://nf-co.re/hadge/results)[![Cite with Zenodo](http://img.shields.io/badge/DOI-10.5281/zenodo.XXXXXXX-1073c8?labelColor=000000)](https://doi.org/10.5281/zenodo.XXXXXXX)\n[![nf-test](https://img.shields.io/badge/unit_tests-nf--test-337ab7.svg)](https://www.nf-test.com)\n\n[![Nextflow](https://img.shields.io/badge/version-%E2%89%A524.10.5-green?style=flat&logo=nextflow&logoColor=white&color=%230DC09D&link=https%3A%2F%2Fnextflow.io)](https://www.nextflow.io/)\n[![nf-core template version](https://img.shields.io/badge/nf--core_template-3.3.2-green?style=flat&logo=nfcore&logoColor=white&color=%2324B064&link=https%3A%2F%2Fnf-co.re)](https://github.com/nf-core/tools/releases/tag/3.3.2)\n[![run with conda](http://img.shields.io/badge/run%20with-conda-3EB049?labelColor=000000&logo=anaconda)](https://docs.conda.io/en/latest/)\n[![run with docker](https://img.shields.io/badge/run%20with-docker-0db7ed?labelColor=000000&logo=docker)](https://www.docker.com/)\n[![run with singularity](https://img.shields.io/badge/run%20with-singularity-1d355c.svg?labelColor=000000)](https://sylabs.io/docs/)\n[![Launch on Seqera Platform](https://img.shields.io/badge/Launch%20%F0%9F%9A%80-Seqera%20Platform-%234256e7)](https://cloud.seqera.io/launch?pipeline=https://github.com/nf-core/hadge)\n\n[![Get help on Slack](http://img.shields.io/badge/slack-nf--core%20%23hadge-4A154B?labelColor=000000&logo=slack)](https://nfcore.slack.com/channels/hadge)[![Follow on Bluesky](https://img.shields.io/badge/bluesky-%40nf__core-1185fe?labelColor=000000&logo=bluesky)](https://bsky.app/profile/nf-co.re)[![Follow on Mastodon](https://img.shields.io/badge/mastodon-nf__core-6364ff?labelColor=FFFFFF&logo=mastodon)](https://mstdn.science/@nf_core)[![Watch on YouTube](http://img.shields.io/badge/youtube-nf--core-FF0000?labelColor=000000&logo=youtube)](https://www.youtube.com/c/nf-core)\n\n## Introduction\n\n**nf-core/hadge** is a bioinformatics pipeline that ...\n\n\n\n\n2. Present QC for raw reads ([`MultiQC`](http://multiqc.info/))\n\n## Usage\n\n> [!NOTE]\n> If you are new to Nextflow and nf-core, please refer to [this page](https://nf-co.re/docs/usage/installation) on how to set-up Nextflow. Make sure to [test your setup](https://nf-co.re/docs/usage/introduction#how-to-run-a-pipeline) with `-profile test` before running the workflow on actual data.\n\n\n\nNow, you can run the pipeline using:\n\n\n\n```bash\nnextflow run nf-core/hadge \\\n -profile \\\n --input samplesheet.csv \\\n --outdir \n```\n\n> [!WARNING]\n> Please provide pipeline parameters via the CLI or Nextflow `-params-file` option. Custom config files including those provided by the `-c` Nextflow option can be used to provide any configuration _**except for parameters**_; see [docs](https://nf-co.re/docs/usage/getting_started/configuration#custom-configuration-files).\n\nFor more details and further functionality, please refer to the [usage documentation](https://nf-co.re/hadge/usage) and the [parameter documentation](https://nf-co.re/hadge/parameters).\n\n## Pipeline output\n\nTo see the results of an example test run with a full size dataset refer to the [results](https://nf-co.re/hadge/results) tab on the nf-core website pipeline page.\nFor more details about the output files and reports, please refer to the\n[output documentation](https://nf-co.re/hadge/output).\n\n## Credits\n\nnf-core/hadge was originally written by Fabiola Curion.\n\nWe thank the following people for their extensive assistance in the development of this pipeline:\n\n\n\n## Contributions and Support\n\nIf you would like to contribute to this pipeline, please see the [contributing guidelines](.github/CONTRIBUTING.md).\n\nFor further information or help, don't hesitate to get in touch on the [Slack `#hadge` channel](https://nfcore.slack.com/channels/hadge) (you can join with [this invite](https://nf-co.re/join/slack)).\n\n## Citations\n\n\n\n\n\n\nAn extensive list of references for the tools used by the pipeline can be found in the [`CITATIONS.md`](CITATIONS.md) file.\n\nYou can cite the `nf-core` publication as follows:\n\n> **The nf-core framework for community-curated bioinformatics pipelines.**\n>\n> Philip Ewels, Alexander Peltzer, Sven Fillinger, Harshil Patel, Johannes Alneberg, Andreas Wilm, Maxime Ulysse Garcia, Paolo Di Tommaso & Sven Nahnsen.\n>\n> _Nat Biotechnol._ 2020 Feb 13. doi: [10.1038/s41587-020-0439-x](https://dx.doi.org/10.1038/s41587-020-0439-x).\n", "hasPart": [ { "@id": "main.nf" @@ -99,7 +99,7 @@ }, "mentions": [ { - "@id": "#8d712a92-e4b5-4404-9c63-ce051e2f1dab" + "@id": "#28200f4a-2487-4f5d-9b18-af665a8ea9e4" } ], "name": "nf-core/hadge" @@ -127,7 +127,7 @@ "ComputationalWorkflow" ], "dateCreated": "", - "dateModified": "2025-06-13T14:26:36Z", + "dateModified": "2025-09-16T19:37:05Z", "dct:conformsTo": "https://bioschemas.org/profiles/ComputationalWorkflow/1.0-RELEASE/", "keywords": [ "nf-core", @@ -163,14 +163,14 @@ "url": { "@id": "https://www.nextflow.io/" }, - "version": "!>=24.04.2" + "version": "!>=24.10.5" }, { - "@id": "#8d712a92-e4b5-4404-9c63-ce051e2f1dab", + "@id": "#28200f4a-2487-4f5d-9b18-af665a8ea9e4", "@type": "TestSuite", "instance": [ { - "@id": "#3e3185a0-4b63-40a8-ae46-0603b5df4d59" + "@id": "#5d2e089e-e31a-48ae-b806-b4c059d77341" } ], "mainEntity": { @@ -179,7 +179,7 @@ "name": "Test suite for nf-core/hadge" }, { - "@id": "#3e3185a0-4b63-40a8-ae46-0603b5df4d59", + "@id": "#5d2e089e-e31a-48ae-b806-b4c059d77341", "@type": "TestInstance", "name": "GitHub Actions workflow for testing nf-core/hadge", "resource": "repos/nf-core/hadge/actions/workflows/nf-test.yml", diff --git a/subworkflows/nf-core/utils_nfschema_plugin/tests/nextflow.config b/subworkflows/nf-core/utils_nfschema_plugin/tests/nextflow.config index 0907ac58..09ef842a 100644 --- a/subworkflows/nf-core/utils_nfschema_plugin/tests/nextflow.config +++ b/subworkflows/nf-core/utils_nfschema_plugin/tests/nextflow.config @@ -1,5 +1,5 @@ plugins { - id "nf-schema@2.1.0" + id "nf-schema@2.4.2" } validation { diff --git a/tests/.nftignore b/tests/.nftignore index d2c9d3c7..857a2e0d 100644 --- a/tests/.nftignore +++ b/tests/.nftignore @@ -1,4 +1,5 @@ .DS_Store +multiqc/multiqc_data/BETA-multiqc.parquet multiqc/multiqc_data/multiqc.log multiqc/multiqc_data/multiqc_data.json multiqc/multiqc_data/BETA-multiqc.parquet diff --git a/tests/nextflow.config b/tests/nextflow.config index c1caaeb9..3851c0c4 100644 --- a/tests/nextflow.config +++ b/tests/nextflow.config @@ -6,7 +6,9 @@ // TODO nf-core: Specify any additional parameters here // Or any resources requirements -params.modules_testdata_base_path = 'https://raw.githubusercontent.com/nf-core/test-datasets/modules/data/' -params.pipelines_testdata_base_path = 'https://raw.githubusercontent.com/nf-core/test-datasets/refs/heads/hadge' +params { + modules_testdata_base_path = 'https://raw.githubusercontent.com/nf-core/test-datasets/modules/data/' + pipelines_testdata_base_path = 'https://raw.githubusercontent.com/nf-core/test-datasets/refs/heads/hadge' +} aws.client.anonymous = true // fixes S3 access issues on self-hosted runners From 96ba4c72e5fe9040204c8f0fd783e6e60e3041d0 Mon Sep 17 00:00:00 2001 From: Nico Trummer Date: Fri, 14 Nov 2025 21:17:13 +0100 Subject: [PATCH 38/74] Template update to 3.4.1 (#79) * Template update for nf-core/tools version 3.3.2 * Template update for nf-core/tools version 3.3.2 * Template update for nf-core/tools version 3.3.2 * Fix some linting problems * Use github runners instead of self-hosted Updated runner configuration to use 'ubuntu-latest' for nf-test jobs. * Fix runner configuration in nf-test workflow * Template update for nf-core/tools version 3.4.1 * Fix logo images --- .devcontainer/devcontainer.json | 28 +++--- .devcontainer/setup.sh | 13 +++ .github/actions/nf-test/action.yml | 6 +- .github/workflows/awsfulltest.yml | 12 +-- .github/workflows/awstest.yml | 12 +-- .github/workflows/clean-up.yml | 2 +- .github/workflows/download_pipeline.yml | 6 +- .github/workflows/fix_linting.yml | 16 +-- .github/workflows/linting.yml | 14 +-- .github/workflows/linting_comment.yml | 2 +- .github/workflows/nf-test.yml | 21 ++-- .github/workflows/release-announcements.yml | 7 ++ .../workflows/template-version-comment.yml | 2 +- .gitpod.yml | 10 -- .nf-core.yml | 2 +- .pre-commit-config.yaml | 2 +- .prettierignore | 1 + README.md | 5 +- docs/images/nf-core-hadge_logo_dark.png | Bin 26814 -> 26654 bytes docs/usage.md | 2 +- main.nf | 5 +- modules.json | 2 +- nextflow.config | 93 +++++++----------- nextflow_schema.json | 12 +++ ro-crate-metadata.json | 16 +-- .../local/utils_nfcore_hadge_pipeline/main.nf | 47 +++++++-- .../nf-core/utils_nfschema_plugin/main.nf | 40 ++++++-- .../utils_nfschema_plugin/tests/main.nf.test | 56 +++++++++++ .../tests/nextflow.config | 4 +- tests/.nftignore | 3 +- tests/default.nf.test | 2 - 31 files changed, 277 insertions(+), 166 deletions(-) create mode 100755 .devcontainer/setup.sh delete mode 100644 .gitpod.yml diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index b290e090..97c8c97f 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -1,20 +1,20 @@ { "name": "nfcore", - "image": "nfcore/gitpod:latest", - "remoteUser": "gitpod", - "runArgs": ["--privileged"], + "image": "nfcore/devcontainer:latest", - // Configure tool-specific properties. - "customizations": { - // Configure properties specific to VS Code. - "vscode": { - // Set *default* container specific settings.json values on container create. - "settings": { - "python.defaultInterpreterPath": "/opt/conda/bin/python" - }, + "remoteUser": "root", + "privileged": true, - // Add the IDs of extensions you want installed when the container is created. - "extensions": ["ms-python.python", "ms-python.vscode-pylance", "nf-core.nf-core-extensionpack"] - } + "remoteEnv": { + // Workspace path on the host for mounting with docker-outside-of-docker + "LOCAL_WORKSPACE_FOLDER": "${localWorkspaceFolder}" + }, + + "onCreateCommand": "./.devcontainer/setup.sh", + + "hostRequirements": { + "cpus": 4, + "memory": "16gb", + "storage": "32gb" } } diff --git a/.devcontainer/setup.sh b/.devcontainer/setup.sh new file mode 100755 index 00000000..58220b0d --- /dev/null +++ b/.devcontainer/setup.sh @@ -0,0 +1,13 @@ +#!/usr/bin/env bash + +# Customise the terminal command prompt +echo "export PROMPT_DIRTRIM=2" >> $HOME/.bashrc +echo "export PS1='\[\e[3;36m\]\w ->\[\e[0m\\] '" >> $HOME/.bashrc +export PROMPT_DIRTRIM=2 +export PS1='\[\e[3;36m\]\w ->\[\e[0m\\] ' + +# Update Nextflow +nextflow self-update + +# Update welcome message +echo "Welcome to the nf-core/hadge devcontainer!" > /usr/local/etc/vscode-dev-containers/first-run-notice.txt diff --git a/.github/actions/nf-test/action.yml b/.github/actions/nf-test/action.yml index bf44d961..3b9724c7 100644 --- a/.github/actions/nf-test/action.yml +++ b/.github/actions/nf-test/action.yml @@ -25,9 +25,9 @@ runs: version: "${{ env.NXF_VERSION }}" - name: Set up Python - uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5 + uses: actions/setup-python@e797f83bcb11b83ae66e0230d6156d7c80228e7c # v6 with: - python-version: "3.13" + python-version: "3.14" - name: Install nf-test uses: nf-core/setup-nf-test@v1 @@ -52,6 +52,8 @@ runs: with: auto-update-conda: true conda-solver: libmamba + channels: conda-forge + channel-priority: strict conda-remove-defaults: true - name: Run nf-test diff --git a/.github/workflows/awsfulltest.yml b/.github/workflows/awsfulltest.yml index 7aa7480b..3760e5fc 100644 --- a/.github/workflows/awsfulltest.yml +++ b/.github/workflows/awsfulltest.yml @@ -28,15 +28,15 @@ jobs: # Add full size test data (but still relatively small datasets for few samples) # on the `test_full.config` test runs with only one set of parameters with: - workspace_id: ${{ secrets.TOWER_WORKSPACE_ID }} + workspace_id: ${{ vars.TOWER_WORKSPACE_ID }} access_token: ${{ secrets.TOWER_ACCESS_TOKEN }} - compute_env: ${{ secrets.TOWER_COMPUTE_ENV }} + compute_env: ${{ vars.TOWER_COMPUTE_ENV }} revision: ${{ steps.revision.outputs.revision }} - workdir: s3://${{ secrets.AWS_S3_BUCKET }}/work/hadge/work-${{ steps.revision.outputs.revision }} + workdir: s3://${{ vars.AWS_S3_BUCKET }}/work/hadge/work-${{ steps.revision.outputs.revision }} parameters: | { "hook_url": "${{ secrets.MEGATESTS_ALERTS_SLACK_HOOK_URL }}", - "outdir": "s3://${{ secrets.AWS_S3_BUCKET }}/hadge/results-${{ steps.revision.outputs.revision }}" + "outdir": "s3://${{ vars.AWS_S3_BUCKET }}/hadge/results-${{ steps.revision.outputs.revision }}" } profiles: test_full @@ -44,5 +44,5 @@ jobs: with: name: Seqera Platform debug log file path: | - seqera_platform_action_*.log - seqera_platform_action_*.json + tower_action_*.log + tower_action_*.json diff --git a/.github/workflows/awstest.yml b/.github/workflows/awstest.yml index f29d23a5..49a85adc 100644 --- a/.github/workflows/awstest.yml +++ b/.github/workflows/awstest.yml @@ -14,14 +14,14 @@ jobs: - name: Launch workflow via Seqera Platform uses: seqeralabs/action-tower-launch@v2 with: - workspace_id: ${{ secrets.TOWER_WORKSPACE_ID }} + workspace_id: ${{ vars.TOWER_WORKSPACE_ID }} access_token: ${{ secrets.TOWER_ACCESS_TOKEN }} - compute_env: ${{ secrets.TOWER_COMPUTE_ENV }} + compute_env: ${{ vars.TOWER_COMPUTE_ENV }} revision: ${{ github.sha }} - workdir: s3://${{ secrets.AWS_S3_BUCKET }}/work/hadge/work-${{ github.sha }} + workdir: s3://${{ vars.AWS_S3_BUCKET }}/work/hadge/work-${{ github.sha }} parameters: | { - "outdir": "s3://${{ secrets.AWS_S3_BUCKET }}/hadge/results-test-${{ github.sha }}" + "outdir": "s3://${{ vars.AWS_S3_BUCKET }}/hadge/results-test-${{ github.sha }}" } profiles: test @@ -29,5 +29,5 @@ jobs: with: name: Seqera Platform debug log file path: | - seqera_platform_action_*.log - seqera_platform_action_*.json + tower_action_*.log + tower_action_*.json diff --git a/.github/workflows/clean-up.yml b/.github/workflows/clean-up.yml index ac030fd5..6adb0fff 100644 --- a/.github/workflows/clean-up.yml +++ b/.github/workflows/clean-up.yml @@ -10,7 +10,7 @@ jobs: issues: write pull-requests: write steps: - - uses: actions/stale@5bef64f19d7facfb25b37b414482c7164d639639 # v9 + - uses: actions/stale@5f858e3efba33a5ca4407a664cc011ad407f2008 # v10 with: stale-issue-message: "This issue has been tagged as awaiting-changes or awaiting-feedback by an nf-core contributor. Remove stale label or add a comment otherwise this issue will be closed in 20 days." stale-pr-message: "This PR has been tagged as awaiting-changes or awaiting-feedback by an nf-core contributor. Remove stale label or add a comment if it is still useful." diff --git a/.github/workflows/download_pipeline.yml b/.github/workflows/download_pipeline.yml index 999bcc38..6d94bcbf 100644 --- a/.github/workflows/download_pipeline.yml +++ b/.github/workflows/download_pipeline.yml @@ -44,9 +44,9 @@ jobs: - name: Disk space cleanup uses: jlumbroso/free-disk-space@54081f138730dfa15788a46383842cd2f914a1be # v1.3.1 - - uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5 + - uses: actions/setup-python@e797f83bcb11b83ae66e0230d6156d7c80228e7c # v6 with: - python-version: "3.13" + python-version: "3.14" architecture: "x64" - name: Setup Apptainer @@ -57,7 +57,7 @@ jobs: - name: Install dependencies run: | python -m pip install --upgrade pip - pip install git+https://github.com/nf-core/tools.git@dev + pip install git+https://github.com/nf-core/tools.git - name: Make a cache directory for the container images run: | diff --git a/.github/workflows/fix_linting.yml b/.github/workflows/fix_linting.yml index 28c1ca54..c3c5a317 100644 --- a/.github/workflows/fix_linting.yml +++ b/.github/workflows/fix_linting.yml @@ -13,13 +13,13 @@ jobs: runs-on: ubuntu-latest steps: # Use the @nf-core-bot token to check out so we can push later - - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4 + - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5 with: token: ${{ secrets.nf_core_bot_auth_token }} # indication that the linting is being fixed - name: React on comment - uses: peter-evans/create-or-update-comment@71345be0265236311c031f5c7866368bd1eff043 # v4 + uses: peter-evans/create-or-update-comment@e8674b075228eee787fea43ef493e45ece1004c9 # v5 with: comment-id: ${{ github.event.comment.id }} reactions: eyes @@ -32,9 +32,9 @@ jobs: GITHUB_TOKEN: ${{ secrets.nf_core_bot_auth_token }} # Install and run pre-commit - - uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5 + - uses: actions/setup-python@e797f83bcb11b83ae66e0230d6156d7c80228e7c # v6 with: - python-version: "3.13" + python-version: "3.14" - name: Install pre-commit run: pip install pre-commit @@ -47,7 +47,7 @@ jobs: # indication that the linting has finished - name: react if linting finished succesfully if: steps.pre-commit.outcome == 'success' - uses: peter-evans/create-or-update-comment@71345be0265236311c031f5c7866368bd1eff043 # v4 + uses: peter-evans/create-or-update-comment@e8674b075228eee787fea43ef493e45ece1004c9 # v5 with: comment-id: ${{ github.event.comment.id }} reactions: "+1" @@ -67,21 +67,21 @@ jobs: - name: react if linting errors were fixed id: react-if-fixed if: steps.commit-and-push.outcome == 'success' - uses: peter-evans/create-or-update-comment@71345be0265236311c031f5c7866368bd1eff043 # v4 + uses: peter-evans/create-or-update-comment@e8674b075228eee787fea43ef493e45ece1004c9 # v5 with: comment-id: ${{ github.event.comment.id }} reactions: hooray - name: react if linting errors were not fixed if: steps.commit-and-push.outcome == 'failure' - uses: peter-evans/create-or-update-comment@71345be0265236311c031f5c7866368bd1eff043 # v4 + uses: peter-evans/create-or-update-comment@e8674b075228eee787fea43ef493e45ece1004c9 # v5 with: comment-id: ${{ github.event.comment.id }} reactions: confused - name: react if linting errors were not fixed if: steps.commit-and-push.outcome == 'failure' - uses: peter-evans/create-or-update-comment@71345be0265236311c031f5c7866368bd1eff043 # v4 + uses: peter-evans/create-or-update-comment@e8674b075228eee787fea43ef493e45ece1004c9 # v5 with: issue-number: ${{ github.event.issue.number }} body: | diff --git a/.github/workflows/linting.yml b/.github/workflows/linting.yml index 8b0f88c3..30e66026 100644 --- a/.github/workflows/linting.yml +++ b/.github/workflows/linting.yml @@ -11,12 +11,12 @@ jobs: pre-commit: runs-on: ubuntu-latest steps: - - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4 + - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5 - - name: Set up Python 3.13 - uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5 + - name: Set up Python 3.14 + uses: actions/setup-python@e797f83bcb11b83ae66e0230d6156d7c80228e7c # v6 with: - python-version: "3.13" + python-version: "3.14" - name: Install pre-commit run: pip install pre-commit @@ -28,14 +28,14 @@ jobs: runs-on: ubuntu-latest steps: - name: Check out pipeline code - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4 + uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5 - name: Install Nextflow uses: nf-core/setup-nextflow@v2 - - uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5 + - uses: actions/setup-python@e797f83bcb11b83ae66e0230d6156d7c80228e7c # v6 with: - python-version: "3.13" + python-version: "3.14" architecture: "x64" - name: read .nf-core.yml diff --git a/.github/workflows/linting_comment.yml b/.github/workflows/linting_comment.yml index d43797d9..e6e9bc26 100644 --- a/.github/workflows/linting_comment.yml +++ b/.github/workflows/linting_comment.yml @@ -21,7 +21,7 @@ jobs: run: echo "pr_number=$(cat linting-logs/PR_number.txt)" >> $GITHUB_OUTPUT - name: Post PR comment - uses: marocchino/sticky-pull-request-comment@52423e01640425a022ef5fd42c6fb5f633a02728 # v2 + uses: marocchino/sticky-pull-request-comment@773744901bac0e8cbb5a0dc842800d45e9b2b405 # v2 with: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} number: ${{ steps.pr_number.outputs.pr_number }} diff --git a/.github/workflows/nf-test.yml b/.github/workflows/nf-test.yml index e7b58449..7ce7dc4a 100644 --- a/.github/workflows/nf-test.yml +++ b/.github/workflows/nf-test.yml @@ -18,7 +18,7 @@ concurrency: env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - NFT_VER: "0.9.2" + NFT_VER: "0.9.3" NFT_WORKDIR: "~" NXF_ANSI_LOG: false NXF_SINGULARITY_CACHEDIR: ${{ github.workspace }}/.singularity @@ -27,9 +27,7 @@ env: jobs: nf-test-changes: name: nf-test-changes - runs-on: # use self-hosted runners - - runs-on=${{ github.run_id }}-nf-test-changes - - runner=4cpu-linux-x64 + runs-on: ubuntu-latest outputs: shard: ${{ steps.set-shards.outputs.shard }} total_shards: ${{ steps.set-shards.outputs.total_shards }} @@ -40,7 +38,7 @@ jobs: rm -rf ./* || true rm -rf ./.??* || true ls -la ./ - - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4 + - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5 with: fetch-depth: 0 @@ -61,9 +59,7 @@ jobs: name: "${{ matrix.profile }} | ${{ matrix.NXF_VER }} | ${{ matrix.shard }}/${{ needs.nf-test-changes.outputs.total_shards }}" needs: [nf-test-changes] if: ${{ needs.nf-test-changes.outputs.total_shards != '0' }} - runs-on: # use self-hosted runners - - runs-on=${{ github.run_id }}-nf-test - - runner=4cpu-linux-x64 + runs-on: ubuntu-latest strategy: fail-fast: false matrix: @@ -78,14 +74,14 @@ jobs: - isMain: false profile: "singularity" NXF_VER: - - "24.10.5" + - "25.04.0" - "latest-everything" env: NXF_ANSI_LOG: false TOTAL_SHARDS: ${{ needs.nf-test-changes.outputs.total_shards }} steps: - - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4 + - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5 with: fetch-depth: 0 @@ -95,6 +91,7 @@ jobs: continue-on-error: ${{ matrix.NXF_VER == 'latest-everything' }} env: NFT_WORKDIR: ${{ env.NFT_WORKDIR }} + NXF_VERSION: ${{ matrix.NXF_VER }} with: profile: ${{ matrix.profile }} shard: ${{ matrix.shard }} @@ -118,9 +115,7 @@ jobs: confirm-pass: needs: [nf-test] if: always() - runs-on: # use self-hosted runners - - runs-on=${{ github.run_id }}-confirm-pass - - runner=2cpu-linux-x64 + runs-on: ubuntu-latest steps: - name: One or more tests failed (excluding latest-everything) if: ${{ contains(needs.*.result, 'failure') }} diff --git a/.github/workflows/release-announcements.yml b/.github/workflows/release-announcements.yml index 0f732495..e64cebd6 100644 --- a/.github/workflows/release-announcements.yml +++ b/.github/workflows/release-announcements.yml @@ -14,6 +14,11 @@ jobs: run: | echo "topics=$(curl -s https://nf-co.re/pipelines.json | jq -r '.remote_workflows[] | select(.full_name == "${{ github.repository }}") | .topics[]' | awk '{print "#"$0}' | tr '\n' ' ')" | sed 's/-//g' >> $GITHUB_OUTPUT + - name: get description + id: get_topics + run: | + echo "description=$(curl -s https://nf-co.re/pipelines.json | jq -r '.remote_workflows[] | select(.full_name == "${{ github.repository }}") | .description' >> $GITHUB_OUTPUT + - uses: rzr/fediverse-action@master with: access-token: ${{ secrets.MASTODON_ACCESS_TOKEN }} @@ -23,6 +28,8 @@ jobs: message: | Pipeline release! ${{ github.repository }} v${{ github.event.release.tag_name }} - ${{ github.event.release.name }}! + ${{ steps.get_topics.outputs.description }} + Please see the changelog: ${{ github.event.release.html_url }} ${{ steps.get_topics.outputs.topics }} #nfcore #openscience #nextflow #bioinformatics diff --git a/.github/workflows/template-version-comment.yml b/.github/workflows/template-version-comment.yml index beb5c77f..c5988af9 100644 --- a/.github/workflows/template-version-comment.yml +++ b/.github/workflows/template-version-comment.yml @@ -9,7 +9,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Check out pipeline code - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4 + uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5 with: ref: ${{ github.event.pull_request.head.sha }} diff --git a/.gitpod.yml b/.gitpod.yml deleted file mode 100644 index 83599f63..00000000 --- a/.gitpod.yml +++ /dev/null @@ -1,10 +0,0 @@ -image: nfcore/gitpod:latest -tasks: - - name: Update Nextflow and setup pre-commit - command: | - pre-commit install --install-hooks - nextflow self-update - -vscode: - extensions: - - nf-core.nf-core-extensionpack # https://github.com/nf-core/vscode-extensionpack diff --git a/.nf-core.yml b/.nf-core.yml index c96b6385..6bb5915e 100644 --- a/.nf-core.yml +++ b/.nf-core.yml @@ -1,7 +1,7 @@ lint: files_unchanged: - .github/PULL_REQUEST_TEMPLATE.md -nf_core_version: 3.3.2 +nf_core_version: 3.4.1 repository_type: pipeline template: author: Fabiola Curion diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index bb41beec..d06777a8 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -6,7 +6,7 @@ repos: additional_dependencies: - prettier@3.6.2 - repo: https://github.com/pre-commit/pre-commit-hooks - rev: v5.0.0 + rev: v6.0.0 hooks: - id: trailing-whitespace args: [--markdown-linebreak-ext=md] diff --git a/.prettierignore b/.prettierignore index edd29f01..2255e3e3 100644 --- a/.prettierignore +++ b/.prettierignore @@ -10,4 +10,5 @@ testing/ testing* *.pyc bin/ +.nf-test/ ro-crate-metadata.json diff --git a/README.md b/README.md index 49ef7339..ff910d5e 100644 --- a/README.md +++ b/README.md @@ -5,12 +5,13 @@ +[![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://github.com/codespaces/new/nf-core/hadge) [![GitHub Actions CI Status](https://github.com/nf-core/hadge/actions/workflows/nf-test.yml/badge.svg)](https://github.com/nf-core/hadge/actions/workflows/nf-test.yml) [![GitHub Actions Linting Status](https://github.com/nf-core/hadge/actions/workflows/linting.yml/badge.svg)](https://github.com/nf-core/hadge/actions/workflows/linting.yml)[![AWS CI](https://img.shields.io/badge/CI%20tests-full%20size-FF9900?labelColor=000000&logo=Amazon%20AWS)](https://nf-co.re/hadge/results)[![Cite with Zenodo](http://img.shields.io/badge/DOI-10.5281/zenodo.XXXXXXX-1073c8?labelColor=000000)](https://doi.org/10.5281/zenodo.XXXXXXX) [![nf-test](https://img.shields.io/badge/unit_tests-nf--test-337ab7.svg)](https://www.nf-test.com) -[![Nextflow](https://img.shields.io/badge/version-%E2%89%A524.10.5-green?style=flat&logo=nextflow&logoColor=white&color=%230DC09D&link=https%3A%2F%2Fnextflow.io)](https://www.nextflow.io/) -[![nf-core template version](https://img.shields.io/badge/nf--core_template-3.3.2-green?style=flat&logo=nfcore&logoColor=white&color=%2324B064&link=https%3A%2F%2Fnf-co.re)](https://github.com/nf-core/tools/releases/tag/3.3.2) +[![Nextflow](https://img.shields.io/badge/version-%E2%89%A525.04.0-green?style=flat&logo=nextflow&logoColor=white&color=%230DC09D&link=https%3A%2F%2Fnextflow.io)](https://www.nextflow.io/) +[![nf-core template version](https://img.shields.io/badge/nf--core_template-3.4.1-green?style=flat&logo=nfcore&logoColor=white&color=%2324B064&link=https%3A%2F%2Fnf-co.re)](https://github.com/nf-core/tools/releases/tag/3.4.1) [![run with conda](http://img.shields.io/badge/run%20with-conda-3EB049?labelColor=000000&logo=anaconda)](https://docs.conda.io/en/latest/) [![run with docker](https://img.shields.io/badge/run%20with-docker-0db7ed?labelColor=000000&logo=docker)](https://www.docker.com/) [![run with singularity](https://img.shields.io/badge/run%20with-singularity-1d355c.svg?labelColor=000000)](https://sylabs.io/docs/) diff --git a/docs/images/nf-core-hadge_logo_dark.png b/docs/images/nf-core-hadge_logo_dark.png index 6bb709d063044194b2592639656ebda0cdf2ef26..80bf2763846e847b4a1875d21cafb2bb47f10309 100644 GIT binary patch literal 26654 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 literal 26814 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≪j%vo2k;RG9;+-ilGwp?t=cY^-=Jpmmg<5=7r&=0 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 N!Ctt6DKc~j|3Bmm()s`Z diff --git a/docs/usage.md b/docs/usage.md index 4c7112ef..1fd733d4 100644 --- a/docs/usage.md +++ b/docs/usage.md @@ -149,7 +149,7 @@ If `-profile` is not specified, the pipeline will run locally and expect all sof - `shifter` - A generic configuration profile to be used with [Shifter](https://nersc.gitlab.io/development/shifter/how-to-use/) - `charliecloud` - - A generic configuration profile to be used with [Charliecloud](https://hpc.github.io/charliecloud/) + - A generic configuration profile to be used with [Charliecloud](https://charliecloud.io/) - `apptainer` - A generic configuration profile to be used with [Apptainer](https://apptainer.org/) - `wave` diff --git a/main.nf b/main.nf index a69fa14e..f130e73d 100644 --- a/main.nf +++ b/main.nf @@ -74,7 +74,10 @@ workflow { params.monochrome_logs, args, params.outdir, - params.input + params.input, + params.help, + params.help_full, + params.show_hidden ) // diff --git a/modules.json b/modules.json index 0e927232..fd590758 100644 --- a/modules.json +++ b/modules.json @@ -118,7 +118,7 @@ }, "utils_nfschema_plugin": { "branch": "master", - "git_sha": "2fd2cd6d0e7b273747f32e465fdc6bcc3ae0814e", + "git_sha": "4b406a74dc0449c0401ed87d5bfff4252fd277fd", "installed_by": ["subworkflows"] } } diff --git a/nextflow.config b/nextflow.config index 2ba9ce46..1c762c6f 100644 --- a/nextflow.config +++ b/nextflow.config @@ -226,22 +226,23 @@ params { multiqc_methods_description = null // Boilerplate options - outdir = null - publish_dir_mode = 'copy' - email = null - email_on_fail = null - plaintext_email = false - monochrome_logs = false - hook_url = null - help = false - help_full = false - show_hidden = false - version = false - pipelines_testdata_base_path = 'https://raw.githubusercontent.com/nf-core/test-datasets/' - trace_report_suffix = new java.util.Date().format('yyyy-MM-dd_HH-mm-ss') + outdir = null + publish_dir_mode = 'copy' + email = null + email_on_fail = null + plaintext_email = false + monochrome_logs = false + hook_url = System.getenv('HOOK_URL') + help = false + help_full = false + show_hidden = false + version = false + pipelines_testdata_base_path = 'https://raw.githubusercontent.com/nf-core/test-datasets/' + trace_report_suffix = new java.util.Date().format( 'yyyy-MM-dd_HH-mm-ss') + // Config options - config_profile_name = null - config_profile_description = null + config_profile_name = null + config_profile_description = null custom_config_version = 'master' custom_config_base = "https://raw.githubusercontent.com/nf-core/configs/${params.custom_config_version}" @@ -292,8 +293,19 @@ profiles { apptainer.enabled = false docker.runOptions = '-u $(id -u):$(id -g)' } - arm { - docker.runOptions = '-u $(id -u):$(id -g) --platform=linux/amd64' + arm64 { + process.arch = 'arm64' + // TODO https://github.com/nf-core/modules/issues/6694 + // For now if you're using arm64 you have to use wave for the sake of the maintainers + // wave profile + apptainer.ociAutoPull = true + singularity.ociAutoPull = true + wave.enabled = true + wave.freeze = true + wave.strategy = 'conda,container' + } + emulate_amd64 { + docker.runOptions = '-u $(id -u):$(id -g) --platform=linux/amd64' } singularity { singularity.enabled = true @@ -349,18 +361,6 @@ profiles { wave.freeze = true wave.strategy = 'conda,container' } - gitpod { - executor.name = 'local' - executor.cpus = 4 - executor.memory = 8.GB - process { - resourceLimits = [ - memory: 8.GB, - cpus: 4, - time: 1.h, - ] - } - } gpu { docker.runOptions = '-u $(id -u):$(id -g) --gpus all' apptainer.runOptions = '--nv' @@ -374,6 +374,8 @@ profiles { } } +// Set AWS client to anonymous when using the default igenomes_base +aws.client.anonymous = !params.igenomes_ignore && params.igenomes_base?.startsWith('s3://ngi-igenomes/igenomes/') ?: false // Load nf-core custom profiles from different institutions // If params.custom_config_base is set AND either the NXF_OFFLINE environment variable is not set or params.custom_config_base is a local path, the nfcore_custom.config file from the specified base path is included. @@ -454,46 +456,19 @@ manifest { description = """Comprehensive pipeline for donor demultiplexing in single cell""" mainScript = 'main.nf' defaultBranch = 'master' - nextflowVersion = '!>=24.10.5' + nextflowVersion = '!>=25.04.0' version = '1.0.0dev' doi = '' } // Nextflow plugins plugins { - id 'nf-schema@2.4.2' // Validation of pipeline parameters and creation of an input channel from a sample sheet + id 'nf-schema@2.5.1' // Validation of pipeline parameters and creation of an input channel from a sample sheet } validation { defaultIgnoreParams = ["genomes"] - monochromeLogs = params.monochrome_logs - help { - enabled = true - command = "nextflow run nf-core/hadge -profile --input samplesheet.csv --outdir " - fullParameter = "help_full" - showHiddenParameter = "show_hidden" - beforeText = """ --\033[2m----------------------------------------------------\033[0m- - \033[0;32m,--.\033[0;30m/\033[0;32m,-.\033[0m -\033[0;34m ___ __ __ __ ___ \033[0;32m/,-._.--~\'\033[0m -\033[0;34m |\\ | |__ __ / ` / \\ |__) |__ \033[0;33m} {\033[0m -\033[0;34m | \\| | \\__, \\__/ | \\ |___ \033[0;32m\\`-._,-`-,\033[0m - \033[0;32m`._,._,\'\033[0m -\033[0;35m nf-core/hadge ${manifest.version}\033[0m --\033[2m----------------------------------------------------\033[0m- -""" - afterText = """${manifest.doi ? "\n* The pipeline\n" : ""}${manifest.doi.tokenize(",").collect { " https://doi.org/${it.trim().replace('https://doi.org/', '')}" }.join("\n")}${manifest.doi ? "\n" : ""} -* The nf-core framework - https://doi.org/10.1038/s41587-020-0439-x - -* Software dependencies - https://github.com/nf-core/hadge/blob/master/CITATIONS.md -""" - } - summary { - beforeText = validation.help.beforeText - afterText = validation.help.afterText - } + monochromeLogs = params.monochrome_logs } // Load modules.config for DSL2 module specific options diff --git a/nextflow_schema.json b/nextflow_schema.json index b2a9d0de..8ef8b151 100644 --- a/nextflow_schema.json +++ b/nextflow_schema.json @@ -1304,6 +1304,18 @@ "fa_icon": "far calendar", "description": "Suffix to add to the trace report filename. Default is the date and time in the format yyyy-MM-dd_HH-mm-ss.", "hidden": true + }, + "help": { + "type": ["boolean", "string"], + "description": "Display the help message." + }, + "help_full": { + "type": "boolean", + "description": "Display the full detailed help message." + }, + "show_hidden": { + "type": "boolean", + "description": "Display hidden parameters in the help message (only works when --help or --help_full are provided)." } } } diff --git a/ro-crate-metadata.json b/ro-crate-metadata.json index 721c720d..a08c4596 100644 --- a/ro-crate-metadata.json +++ b/ro-crate-metadata.json @@ -22,8 +22,8 @@ "@id": "./", "@type": "Dataset", "creativeWorkStatus": "InProgress", - "datePublished": "2025-09-16T17:37:05+00:00", - "description": "

    \n \n \n \"nf-core/hadge\"\n \n

    \n\n[![GitHub Actions CI Status](https://github.com/nf-core/hadge/actions/workflows/nf-test.yml/badge.svg)](https://github.com/nf-core/hadge/actions/workflows/nf-test.yml)\n[![GitHub Actions Linting Status](https://github.com/nf-core/hadge/actions/workflows/linting.yml/badge.svg)](https://github.com/nf-core/hadge/actions/workflows/linting.yml)[![AWS CI](https://img.shields.io/badge/CI%20tests-full%20size-FF9900?labelColor=000000&logo=Amazon%20AWS)](https://nf-co.re/hadge/results)[![Cite with Zenodo](http://img.shields.io/badge/DOI-10.5281/zenodo.XXXXXXX-1073c8?labelColor=000000)](https://doi.org/10.5281/zenodo.XXXXXXX)\n[![nf-test](https://img.shields.io/badge/unit_tests-nf--test-337ab7.svg)](https://www.nf-test.com)\n\n[![Nextflow](https://img.shields.io/badge/version-%E2%89%A524.10.5-green?style=flat&logo=nextflow&logoColor=white&color=%230DC09D&link=https%3A%2F%2Fnextflow.io)](https://www.nextflow.io/)\n[![nf-core template version](https://img.shields.io/badge/nf--core_template-3.3.2-green?style=flat&logo=nfcore&logoColor=white&color=%2324B064&link=https%3A%2F%2Fnf-co.re)](https://github.com/nf-core/tools/releases/tag/3.3.2)\n[![run with conda](http://img.shields.io/badge/run%20with-conda-3EB049?labelColor=000000&logo=anaconda)](https://docs.conda.io/en/latest/)\n[![run with docker](https://img.shields.io/badge/run%20with-docker-0db7ed?labelColor=000000&logo=docker)](https://www.docker.com/)\n[![run with singularity](https://img.shields.io/badge/run%20with-singularity-1d355c.svg?labelColor=000000)](https://sylabs.io/docs/)\n[![Launch on Seqera Platform](https://img.shields.io/badge/Launch%20%F0%9F%9A%80-Seqera%20Platform-%234256e7)](https://cloud.seqera.io/launch?pipeline=https://github.com/nf-core/hadge)\n\n[![Get help on Slack](http://img.shields.io/badge/slack-nf--core%20%23hadge-4A154B?labelColor=000000&logo=slack)](https://nfcore.slack.com/channels/hadge)[![Follow on Bluesky](https://img.shields.io/badge/bluesky-%40nf__core-1185fe?labelColor=000000&logo=bluesky)](https://bsky.app/profile/nf-co.re)[![Follow on Mastodon](https://img.shields.io/badge/mastodon-nf__core-6364ff?labelColor=FFFFFF&logo=mastodon)](https://mstdn.science/@nf_core)[![Watch on YouTube](http://img.shields.io/badge/youtube-nf--core-FF0000?labelColor=000000&logo=youtube)](https://www.youtube.com/c/nf-core)\n\n## Introduction\n\n**nf-core/hadge** is a bioinformatics pipeline that ...\n\n\n\n\n2. Present QC for raw reads ([`MultiQC`](http://multiqc.info/))\n\n## Usage\n\n> [!NOTE]\n> If you are new to Nextflow and nf-core, please refer to [this page](https://nf-co.re/docs/usage/installation) on how to set-up Nextflow. Make sure to [test your setup](https://nf-co.re/docs/usage/introduction#how-to-run-a-pipeline) with `-profile test` before running the workflow on actual data.\n\n\n\nNow, you can run the pipeline using:\n\n\n\n```bash\nnextflow run nf-core/hadge \\\n -profile \\\n --input samplesheet.csv \\\n --outdir \n```\n\n> [!WARNING]\n> Please provide pipeline parameters via the CLI or Nextflow `-params-file` option. Custom config files including those provided by the `-c` Nextflow option can be used to provide any configuration _**except for parameters**_; see [docs](https://nf-co.re/docs/usage/getting_started/configuration#custom-configuration-files).\n\nFor more details and further functionality, please refer to the [usage documentation](https://nf-co.re/hadge/usage) and the [parameter documentation](https://nf-co.re/hadge/parameters).\n\n## Pipeline output\n\nTo see the results of an example test run with a full size dataset refer to the [results](https://nf-co.re/hadge/results) tab on the nf-core website pipeline page.\nFor more details about the output files and reports, please refer to the\n[output documentation](https://nf-co.re/hadge/output).\n\n## Credits\n\nnf-core/hadge was originally written by Fabiola Curion.\n\nWe thank the following people for their extensive assistance in the development of this pipeline:\n\n\n\n## Contributions and Support\n\nIf you would like to contribute to this pipeline, please see the [contributing guidelines](.github/CONTRIBUTING.md).\n\nFor further information or help, don't hesitate to get in touch on the [Slack `#hadge` channel](https://nfcore.slack.com/channels/hadge) (you can join with [this invite](https://nf-co.re/join/slack)).\n\n## Citations\n\n\n\n\n\n\nAn extensive list of references for the tools used by the pipeline can be found in the [`CITATIONS.md`](CITATIONS.md) file.\n\nYou can cite the `nf-core` publication as follows:\n\n> **The nf-core framework for community-curated bioinformatics pipelines.**\n>\n> Philip Ewels, Alexander Peltzer, Sven Fillinger, Harshil Patel, Johannes Alneberg, Andreas Wilm, Maxime Ulysse Garcia, Paolo Di Tommaso & Sven Nahnsen.\n>\n> _Nat Biotechnol._ 2020 Feb 13. doi: [10.1038/s41587-020-0439-x](https://dx.doi.org/10.1038/s41587-020-0439-x).\n", + "datePublished": "2025-11-14T19:53:15+00:00", + "description": "

    \n \n \n \"nf-core/hadge\"\n \n

    \n\n[![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://github.com/codespaces/new/nf-core/hadge)\n[![GitHub Actions CI Status](https://github.com/nf-core/hadge/actions/workflows/nf-test.yml/badge.svg)](https://github.com/nf-core/hadge/actions/workflows/nf-test.yml)\n[![GitHub Actions Linting Status](https://github.com/nf-core/hadge/actions/workflows/linting.yml/badge.svg)](https://github.com/nf-core/hadge/actions/workflows/linting.yml)[![AWS CI](https://img.shields.io/badge/CI%20tests-full%20size-FF9900?labelColor=000000&logo=Amazon%20AWS)](https://nf-co.re/hadge/results)[![Cite with Zenodo](http://img.shields.io/badge/DOI-10.5281/zenodo.XXXXXXX-1073c8?labelColor=000000)](https://doi.org/10.5281/zenodo.XXXXXXX)\n[![nf-test](https://img.shields.io/badge/unit_tests-nf--test-337ab7.svg)](https://www.nf-test.com)\n\n[![Nextflow](https://img.shields.io/badge/version-%E2%89%A525.04.0-green?style=flat&logo=nextflow&logoColor=white&color=%230DC09D&link=https%3A%2F%2Fnextflow.io)](https://www.nextflow.io/)\n[![nf-core template version](https://img.shields.io/badge/nf--core_template-3.4.1-green?style=flat&logo=nfcore&logoColor=white&color=%2324B064&link=https%3A%2F%2Fnf-co.re)](https://github.com/nf-core/tools/releases/tag/3.4.1)\n[![run with conda](http://img.shields.io/badge/run%20with-conda-3EB049?labelColor=000000&logo=anaconda)](https://docs.conda.io/en/latest/)\n[![run with docker](https://img.shields.io/badge/run%20with-docker-0db7ed?labelColor=000000&logo=docker)](https://www.docker.com/)\n[![run with singularity](https://img.shields.io/badge/run%20with-singularity-1d355c.svg?labelColor=000000)](https://sylabs.io/docs/)\n[![Launch on Seqera Platform](https://img.shields.io/badge/Launch%20%F0%9F%9A%80-Seqera%20Platform-%234256e7)](https://cloud.seqera.io/launch?pipeline=https://github.com/nf-core/hadge)\n\n[![Get help on Slack](http://img.shields.io/badge/slack-nf--core%20%23hadge-4A154B?labelColor=000000&logo=slack)](https://nfcore.slack.com/channels/hadge)[![Follow on Bluesky](https://img.shields.io/badge/bluesky-%40nf__core-1185fe?labelColor=000000&logo=bluesky)](https://bsky.app/profile/nf-co.re)[![Follow on Mastodon](https://img.shields.io/badge/mastodon-nf__core-6364ff?labelColor=FFFFFF&logo=mastodon)](https://mstdn.science/@nf_core)[![Watch on YouTube](http://img.shields.io/badge/youtube-nf--core-FF0000?labelColor=000000&logo=youtube)](https://www.youtube.com/c/nf-core)\n\n## Introduction\n\n**nf-core/hadge** is a bioinformatics pipeline that ...\n\n\n\n\n2. Present QC for raw reads ([`MultiQC`](http://multiqc.info/))\n\n## Usage\n\n> [!NOTE]\n> If you are new to Nextflow and nf-core, please refer to [this page](https://nf-co.re/docs/usage/installation) on how to set-up Nextflow. Make sure to [test your setup](https://nf-co.re/docs/usage/introduction#how-to-run-a-pipeline) with `-profile test` before running the workflow on actual data.\n\n\n\nNow, you can run the pipeline using:\n\n\n\n```bash\nnextflow run nf-core/hadge \\\n -profile \\\n --input samplesheet.csv \\\n --outdir \n```\n\n> [!WARNING]\n> Please provide pipeline parameters via the CLI or Nextflow `-params-file` option. Custom config files including those provided by the `-c` Nextflow option can be used to provide any configuration _**except for parameters**_; see [docs](https://nf-co.re/docs/usage/getting_started/configuration#custom-configuration-files).\n\nFor more details and further functionality, please refer to the [usage documentation](https://nf-co.re/hadge/usage) and the [parameter documentation](https://nf-co.re/hadge/parameters).\n\n## Pipeline output\n\nTo see the results of an example test run with a full size dataset refer to the [results](https://nf-co.re/hadge/results) tab on the nf-core website pipeline page.\nFor more details about the output files and reports, please refer to the\n[output documentation](https://nf-co.re/hadge/output).\n\n## Credits\n\nnf-core/hadge was originally written by Fabiola Curion.\n\nWe thank the following people for their extensive assistance in the development of this pipeline:\n\n\n\n## Contributions and Support\n\nIf you would like to contribute to this pipeline, please see the [contributing guidelines](.github/CONTRIBUTING.md).\n\nFor further information or help, don't hesitate to get in touch on the [Slack `#hadge` channel](https://nfcore.slack.com/channels/hadge) (you can join with [this invite](https://nf-co.re/join/slack)).\n\n## Citations\n\n\n\n\n\n\nAn extensive list of references for the tools used by the pipeline can be found in the [`CITATIONS.md`](CITATIONS.md) file.\n\nYou can cite the `nf-core` publication as follows:\n\n> **The nf-core framework for community-curated bioinformatics pipelines.**\n>\n> Philip Ewels, Alexander Peltzer, Sven Fillinger, Harshil Patel, Johannes Alneberg, Andreas Wilm, Maxime Ulysse Garcia, Paolo Di Tommaso & Sven Nahnsen.\n>\n> _Nat Biotechnol._ 2020 Feb 13. doi: [10.1038/s41587-020-0439-x](https://dx.doi.org/10.1038/s41587-020-0439-x).\n", "hasPart": [ { "@id": "main.nf" @@ -99,7 +99,7 @@ }, "mentions": [ { - "@id": "#28200f4a-2487-4f5d-9b18-af665a8ea9e4" + "@id": "#c8e976d2-368f-4e69-9d0b-23329bcac81d" } ], "name": "nf-core/hadge" @@ -127,7 +127,7 @@ "ComputationalWorkflow" ], "dateCreated": "", - "dateModified": "2025-09-16T19:37:05Z", + "dateModified": "2025-11-14T20:53:15Z", "dct:conformsTo": "https://bioschemas.org/profiles/ComputationalWorkflow/1.0-RELEASE/", "keywords": [ "nf-core", @@ -163,14 +163,14 @@ "url": { "@id": "https://www.nextflow.io/" }, - "version": "!>=24.10.5" + "version": "!>=25.04.0" }, { - "@id": "#28200f4a-2487-4f5d-9b18-af665a8ea9e4", + "@id": "#c8e976d2-368f-4e69-9d0b-23329bcac81d", "@type": "TestSuite", "instance": [ { - "@id": "#5d2e089e-e31a-48ae-b806-b4c059d77341" + "@id": "#64905bd3-87de-4353-9f61-8b940ce51a05" } ], "mainEntity": { @@ -179,7 +179,7 @@ "name": "Test suite for nf-core/hadge" }, { - "@id": "#5d2e089e-e31a-48ae-b806-b4c059d77341", + "@id": "#64905bd3-87de-4353-9f61-8b940ce51a05", "@type": "TestInstance", "name": "GitHub Actions workflow for testing nf-core/hadge", "resource": "repos/nf-core/hadge/actions/workflows/nf-test.yml", diff --git a/subworkflows/local/utils_nfcore_hadge_pipeline/main.nf b/subworkflows/local/utils_nfcore_hadge_pipeline/main.nf index 6234ca54..92a6deaa 100644 --- a/subworkflows/local/utils_nfcore_hadge_pipeline/main.nf +++ b/subworkflows/local/utils_nfcore_hadge_pipeline/main.nf @@ -8,14 +8,15 @@ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ -include { UTILS_NFSCHEMA_PLUGIN } from '../../nf-core/utils_nfschema_plugin' -include { paramsSummaryMap } from 'plugin/nf-schema' -include { samplesheetToList } from 'plugin/nf-schema' -include { completionEmail } from '../../nf-core/utils_nfcore_pipeline' -include { completionSummary } from '../../nf-core/utils_nfcore_pipeline' -include { imNotification } from '../../nf-core/utils_nfcore_pipeline' -include { UTILS_NFCORE_PIPELINE } from '../../nf-core/utils_nfcore_pipeline' -include { UTILS_NEXTFLOW_PIPELINE } from '../../nf-core/utils_nextflow_pipeline' +include { UTILS_NFSCHEMA_PLUGIN } from '../../nf-core/utils_nfschema_plugin' +include { paramsSummaryMap } from 'plugin/nf-schema' +include { samplesheetToList } from 'plugin/nf-schema' +include { paramsHelp } from 'plugin/nf-schema' +include { completionEmail } from '../../nf-core/utils_nfcore_pipeline' +include { completionSummary } from '../../nf-core/utils_nfcore_pipeline' +include { imNotification } from '../../nf-core/utils_nfcore_pipeline' +include { UTILS_NFCORE_PIPELINE } from '../../nf-core/utils_nfcore_pipeline' +include { UTILS_NEXTFLOW_PIPELINE } from '../../nf-core/utils_nextflow_pipeline' /* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -31,6 +32,9 @@ workflow PIPELINE_INITIALISATION { nextflow_cli_args // array: List of positional nextflow CLI args outdir // string: The output directory where the results will be saved input // string: Path to input samplesheet + help // boolean: Display help message and exit + help_full // boolean: Show the full help message + show_hidden // boolean: Show hidden parameters in the help message main: @@ -49,10 +53,35 @@ workflow PIPELINE_INITIALISATION { // // Validate parameters and generate parameter summary to stdout // - UTILS_NFSCHEMA_PLUGIN( + before_text = """ +-\033[2m----------------------------------------------------\033[0m- + \033[0;32m,--.\033[0;30m/\033[0;32m,-.\033[0m +\033[0;34m ___ __ __ __ ___ \033[0;32m/,-._.--~\'\033[0m +\033[0;34m |\\ | |__ __ / ` / \\ |__) |__ \033[0;33m} {\033[0m +\033[0;34m | \\| | \\__, \\__/ | \\ |___ \033[0;32m\\`-._,-`-,\033[0m + \033[0;32m`._,._,\'\033[0m +\033[0;35m nf-core/hadge ${workflow.manifest.version}\033[0m +-\033[2m----------------------------------------------------\033[0m- +""" + after_text = """${workflow.manifest.doi ? "\n* The pipeline\n" : ""}${workflow.manifest.doi.tokenize(",").collect { " https://doi.org/${it.trim().replace('https://doi.org/','')}"}.join("\n")}${workflow.manifest.doi ? "\n" : ""} +* The nf-core framework + https://doi.org/10.1038/s41587-020-0439-x + +* Software dependencies + https://github.com/nf-core/hadge/blob/master/CITATIONS.md +""" + command = "nextflow run ${workflow.manifest.name} -profile --input samplesheet.csv --outdir " + + UTILS_NFSCHEMA_PLUGIN ( workflow, validate_params, null, + help, + help_full, + show_hidden, + before_text, + after_text, + command ) // diff --git a/subworkflows/nf-core/utils_nfschema_plugin/main.nf b/subworkflows/nf-core/utils_nfschema_plugin/main.nf index 4994303e..ee4738c8 100644 --- a/subworkflows/nf-core/utils_nfschema_plugin/main.nf +++ b/subworkflows/nf-core/utils_nfschema_plugin/main.nf @@ -4,6 +4,7 @@ include { paramsSummaryLog } from 'plugin/nf-schema' include { validateParameters } from 'plugin/nf-schema' +include { paramsHelp } from 'plugin/nf-schema' workflow UTILS_NFSCHEMA_PLUGIN { @@ -15,29 +16,56 @@ workflow UTILS_NFSCHEMA_PLUGIN { // when this input is empty it will automatically use the configured schema or // "${projectDir}/nextflow_schema.json" as default. This input should not be empty // for meta pipelines + help // boolean: show help message + help_full // boolean: show full help message + show_hidden // boolean: show hidden parameters in help message + before_text // string: text to show before the help message and parameters summary + after_text // string: text to show after the help message and parameters summary + command // string: an example command of the pipeline main: + if(help || help_full) { + help_options = [ + beforeText: before_text, + afterText: after_text, + command: command, + showHidden: show_hidden, + fullHelp: help_full, + ] + if(parameters_schema) { + help_options << [parametersSchema: parameters_schema] + } + log.info paramsHelp( + help_options, + params.help instanceof String ? params.help : "", + ) + exit 0 + } + // // Print parameter summary to stdout. This will display the parameters // that differ from the default given in the JSON schema // + + summary_options = [:] if(parameters_schema) { - log.info paramsSummaryLog(input_workflow, parameters_schema:parameters_schema) - } else { - log.info paramsSummaryLog(input_workflow) + summary_options << [parametersSchema: parameters_schema] } + log.info before_text + log.info paramsSummaryLog(summary_options, input_workflow) + log.info after_text // // Validate the parameters using nextflow_schema.json or the schema // given via the validation.parametersSchema configuration option // if(validate_params) { + validateOptions = [:] if(parameters_schema) { - validateParameters(parameters_schema:parameters_schema) - } else { - validateParameters() + validateOptions << [parametersSchema: parameters_schema] } + validateParameters(validateOptions) } emit: diff --git a/subworkflows/nf-core/utils_nfschema_plugin/tests/main.nf.test b/subworkflows/nf-core/utils_nfschema_plugin/tests/main.nf.test index 8fb30164..c977917a 100644 --- a/subworkflows/nf-core/utils_nfschema_plugin/tests/main.nf.test +++ b/subworkflows/nf-core/utils_nfschema_plugin/tests/main.nf.test @@ -25,6 +25,12 @@ nextflow_workflow { input[0] = workflow input[1] = validate_params input[2] = "" + input[3] = false + input[4] = false + input[5] = false + input[6] = "" + input[7] = "" + input[8] = "" """ } } @@ -51,6 +57,12 @@ nextflow_workflow { input[0] = workflow input[1] = validate_params input[2] = "" + input[3] = false + input[4] = false + input[5] = false + input[6] = "" + input[7] = "" + input[8] = "" """ } } @@ -77,6 +89,12 @@ nextflow_workflow { input[0] = workflow input[1] = validate_params input[2] = "${projectDir}/subworkflows/nf-core/utils_nfschema_plugin/tests/nextflow_schema.json" + input[3] = false + input[4] = false + input[5] = false + input[6] = "" + input[7] = "" + input[8] = "" """ } } @@ -103,6 +121,12 @@ nextflow_workflow { input[0] = workflow input[1] = validate_params input[2] = "${projectDir}/subworkflows/nf-core/utils_nfschema_plugin/tests/nextflow_schema.json" + input[3] = false + input[4] = false + input[5] = false + input[6] = "" + input[7] = "" + input[8] = "" """ } } @@ -114,4 +138,36 @@ nextflow_workflow { ) } } + + test("Should create a help message") { + + when { + + params { + test_data = '' + outdir = null + } + + workflow { + """ + validate_params = true + input[0] = workflow + input[1] = validate_params + input[2] = "${projectDir}/subworkflows/nf-core/utils_nfschema_plugin/tests/nextflow_schema.json" + input[3] = true + input[4] = false + input[5] = false + input[6] = "Before" + input[7] = "After" + input[8] = "nextflow run test/test" + """ + } + } + + then { + assertAll( + { assert workflow.success } + ) + } + } } diff --git a/subworkflows/nf-core/utils_nfschema_plugin/tests/nextflow.config b/subworkflows/nf-core/utils_nfschema_plugin/tests/nextflow.config index 09ef842a..8d8c7371 100644 --- a/subworkflows/nf-core/utils_nfschema_plugin/tests/nextflow.config +++ b/subworkflows/nf-core/utils_nfschema_plugin/tests/nextflow.config @@ -1,8 +1,8 @@ plugins { - id "nf-schema@2.4.2" + id "nf-schema@2.5.1" } validation { parametersSchema = "${projectDir}/subworkflows/nf-core/utils_nfschema_plugin/tests/nextflow_schema.json" monochromeLogs = true -} \ No newline at end of file +} diff --git a/tests/.nftignore b/tests/.nftignore index 857a2e0d..ebb7349c 100644 --- a/tests/.nftignore +++ b/tests/.nftignore @@ -1,10 +1,11 @@ .DS_Store -multiqc/multiqc_data/BETA-multiqc.parquet +multiqc/multiqc_data/multiqc.parquet multiqc/multiqc_data/multiqc.log multiqc/multiqc_data/multiqc_data.json multiqc/multiqc_data/BETA-multiqc.parquet multiqc/multiqc_data/multiqc_sources.txt multiqc/multiqc_data/multiqc_software_versions.txt +multiqc/multiqc_data/llms-full.txt multiqc/multiqc_plots/{svg,pdf,png}/*.{svg,pdf,png} multiqc/multiqc_report.html pipeline_info/*.{html,json,txt,yml} diff --git a/tests/default.nf.test b/tests/default.nf.test index d0014bab..eb04667f 100644 --- a/tests/default.nf.test +++ b/tests/default.nf.test @@ -20,8 +20,6 @@ nextflow_pipeline { assertAll( { assert workflow.success}, { assert snapshot( - // Number of successful tasks - workflow.trace.succeeded().size(), // pipeline versions.yml file for multiqc from which Nextflow version is removed because we test pipelines on multiple Nextflow versions removeNextflowVersion("$outputDir/pipeline_info/nf_core_hadge_software_mqc_versions.yml"), // All stable path name, with a relative path From b8a7d0e4ede61be535d73d3481f68c6d32dcb99b Mon Sep 17 00:00:00 2001 From: LuisHeinzlmeier Date: Mon, 24 Nov 2025 23:16:47 +0100 Subject: [PATCH 39/74] Integration of genetic modules and donor matching (#78) * save first steps * join outputs * gene_summary merges results for vireo, demuxlet and freemuxlet * merge on Barcode * all modules work for gene_summary but demuxlet has still empty values after merging * prepare donor matching * try understanding donor match * debugging donor match * donor_match produces something * donor_match produces something * donor_match seems to produce correct output files now, but it still needs to be tested with properly joined data * pipeline is running with new input data and donor_match produces correct output * first updates to souporcell * update souporcell * final versions.yml * save anndata/mudata, output before joining and left join to rna barcodes * couln't fix demuxEM error, opened an issue at developer's github * gene/hash_summary: save mudata/anndata, print results overview fuction and clean code * include all methods in donor_match and update bff() in hash_summary * improve the readability of hadge.nf * use fasta from iGenomes for souporcell but not fully tested (see #77) * remove prints and linting errors * update snapshot * Merge origin/dev * update souporcell * Fix image files * update snapshot * update snapshot 2 * fasta updates * update snapshot with 3 modules each (hash/gene) * update fasta description * update snapshot * replace set with list to keep the same order * snapshot from codespace * add souporcell outputs to .nftignore * Add trailing newline to .nftignore * fix end of file * add freemuxlet/*/*.clust1.vcf.gz to .nftignore * Incorporate review comments * update snapshot --------- Co-authored-by: nictru --- conf/modules.config | 56 +- conf/test.config | 12 +- main.nf | 7 +- modules.json | 10 + modules/local/donor_match/environment.yml | 9 + modules/local/donor_match/main.nf | 70 ++ .../local/donor_match/templates/donor_match.R | 748 ++++++++++++++++++ .../mtxconvert/templates/convert.R | 13 + modules/local/extract_hashes/main.nf | 6 +- modules/local/gene_summary/environment.yml | 10 + modules/local/gene_summary/main.nf | 52 ++ .../gene_summary/templates/gene_summary.py | 286 +++++++ modules/local/hash_summary/main.nf | 25 +- .../hash_summary/templates/hash_summary.py | 412 +++++----- modules/nf-core/csvtk/join/environment.yml | 8 + modules/nf-core/csvtk/join/main.nf | 49 ++ modules/nf-core/csvtk/join/meta.yml | 53 ++ modules/nf-core/csvtk/join/tests/main.nf.test | 64 ++ .../csvtk/join/tests/main.nf.test.snap | 68 ++ .../nf-core/csvtk/join/tests/nextflow.config | 5 + modules/nf-core/souporcell/environment.yml | 8 + modules/nf-core/souporcell/main.nf | 60 ++ modules/nf-core/souporcell/meta.yml | 109 +++ modules/nf-core/souporcell/tests/main.nf.test | 139 ++++ .../souporcell/tests/main.nf.test.snap | 111 +++ .../nf-core/souporcell/tests/nextflow.config | 5 + nextflow.config | 63 +- nextflow_schema.json | 158 +++- subworkflows/local/donor_matching/main.nf | 8 - .../local/genetic_demultiplexing/main.nf | 66 +- .../local/hash_demultiplexing/main.nf | 48 +- tests/.nftignore | 3 + tests/default.nf.test.snap | 666 +++++++++------- workflows/hadge.nf | 148 +++- 34 files changed, 2997 insertions(+), 558 deletions(-) create mode 100644 modules/local/donor_match/environment.yml create mode 100644 modules/local/donor_match/main.nf create mode 100644 modules/local/donor_match/templates/donor_match.R create mode 100644 modules/local/gene_summary/environment.yml create mode 100644 modules/local/gene_summary/main.nf create mode 100644 modules/local/gene_summary/templates/gene_summary.py create mode 100644 modules/nf-core/csvtk/join/environment.yml create mode 100644 modules/nf-core/csvtk/join/main.nf create mode 100644 modules/nf-core/csvtk/join/meta.yml create mode 100644 modules/nf-core/csvtk/join/tests/main.nf.test create mode 100644 modules/nf-core/csvtk/join/tests/main.nf.test.snap create mode 100644 modules/nf-core/csvtk/join/tests/nextflow.config create mode 100644 modules/nf-core/souporcell/environment.yml create mode 100644 modules/nf-core/souporcell/main.nf create mode 100644 modules/nf-core/souporcell/meta.yml create mode 100644 modules/nf-core/souporcell/tests/main.nf.test create mode 100644 modules/nf-core/souporcell/tests/main.nf.test.snap create mode 100644 modules/nf-core/souporcell/tests/nextflow.config delete mode 100644 subworkflows/local/donor_matching/main.nf diff --git a/conf/modules.config b/conf/modules.config index eba8820e..d120b4d6 100644 --- a/conf/modules.config +++ b/conf/modules.config @@ -58,6 +58,23 @@ process { ] } + withName: DONOR_MATCH { + publishDir = [ + path: { "${params.outdir}/donor_match/${meta.id}" }, + mode: params.publish_dir_mode, + saveAs: { filename -> filename.equals('versions.yml') ? null : filename }, + ] + } + + withName: JOIN_RESULTS { + ext.args = "--outer-join --na negative" + publishDir = [ + path: { "${params.outdir}/hash_and_gene_summary/${meta.id}" }, + mode: params.publish_dir_mode, + saveAs: { filename -> filename.equals('versions.yml') ? null : filename }, + ] + } + withName: CELLSNP_MODEA { ext.args = { [ @@ -203,6 +220,33 @@ process { ] } + withName: SOUPORCELL { + ext.args = { + [ + "-p", + params.souporcell_ploidy, + "--min_alt", + params.souporcell_min_alt, + "--min_ref", + params.souporcell_min_ref, + "--max_loci", + params.souporcell_max_loci, + "--restarts", + params.souporcell_restarts, + (params.souporcell_common_variants ? "--common_variants ${params.souporcell_common_variants}" : ""), + (params.souporcell_known_genotypes ? "--known_genotypes ${params.souporcell_known_genotypes}" : ""), + (params.souporcell_known_genotypes_sample_names ? "--known_genotypes_sample_names ${params.souporcell_known_genotypes_sample_names}" : ""), + (params.souporcell_skip_remap ? "--skip_remap" : ""), + (params.souporcell_ignore ? "--ignore" : ""), + ].findAll { arg -> arg != "" }.join(" ") + } + publishDir = [ + path: { "${params.outdir}/genetic/souporcell/${meta.id}" }, + mode: params.publish_dir_mode, + saveAs: { filename -> filename.equals('versions.yml') ? null : filename }, + ] + } + withName: MTXCONVERT_RNA { publishDir = [ path: { "${params.outdir}/hashing/mtxconvert/rna/${meta.id}" }, @@ -261,11 +305,11 @@ process { ] } - withName: RENAME_GENES_TO_FEATURES_RNA { + withName: UNTAR_RNA { ext.prefix = { "${meta.id}_rna" } } - withName: RENAME_GENES_TO_FEATURES_HTO { + withName: UNTAR_HTO { ext.prefix = { "${meta.id}_hto" } } @@ -400,6 +444,14 @@ process { ] } + withName: GENE_SUMMARY { + publishDir = [ + path: { "${params.outdir}/genetic/summary/${meta.id}" }, + mode: params.publish_dir_mode, + saveAs: { filename -> filename.equals('versions.yml') ? null : filename }, + ] + } + // TODO update to ext.args withName: PREPROCESSING_FOR_HTODEMUX_MULTISEQ { ext.sel_method = params.preprocessing_sel_method diff --git a/conf/test.config b/conf/test.config index 580f6312..992a1466 100644 --- a/conf/test.config +++ b/conf/test.config @@ -24,8 +24,16 @@ params { // Input data input = 'https://github.com/nf-core/test-datasets/raw/refs/heads/hadge/samplesheet.csv' - hash_tools = 'htodemux,hasheddrops,multiseq,demuxem,gmm-demux,cellhashr,hashsolo' - genetic_tools = 'demuxlet,freemuxlet,vireo' + hash_tools = 'hasheddrops,multiseq,hashsolo' + genetic_tools = 'freemuxlet,vireo,souporcell' mode = 'rescue' + genome = 'GRCh38' + fasta = 'https://raw.githubusercontent.com/nf-core/test-datasets/modules/data/genomics/homo_sapiens/genome/chr21/sequence/genome.fasta' bam_qc = true + + // TODO debug demuxem (See this issue: modules/local/dropletutils/mtxconvert/templates/convert.R) + // all modules + // hash_tools = 'htodemux,hasheddrops,multiseq,demuxem,gmm-demux,cellhashr,hashsolo' + // genetic_tools = 'demuxlet,freemuxlet,vireo,souporcell' + // demuxem_min_signal_hashtag = 0 } diff --git a/main.nf b/main.nf index f130e73d..bca1068e 100644 --- a/main.nf +++ b/main.nf @@ -44,6 +44,7 @@ workflow NFCORE_HADGE { take: samplesheet // channel: samplesheet read in from --input + fasta // file: /path/to/genome.fasta main: @@ -51,7 +52,8 @@ workflow NFCORE_HADGE { // WORKFLOW: Run pipeline // HADGE ( - samplesheet + samplesheet, + fasta ) emit: multiqc_report = HADGE.out.multiqc_report // channel: /path/to/multiqc_report.html @@ -84,7 +86,8 @@ workflow { // WORKFLOW: Run main workflow // NFCORE_HADGE ( - PIPELINE_INITIALISATION.out.samplesheet + PIPELINE_INITIALISATION.out.samplesheet, + params.fasta ) // // SUBWORKFLOW: Run completion tasks diff --git a/modules.json b/modules.json index fd590758..c544f2be 100644 --- a/modules.json +++ b/modules.json @@ -16,6 +16,11 @@ "installed_by": ["modules"], "patch": "modules/nf-core/cellsnp/modea/cellsnp-modea.diff" }, + "csvtk/join": { + "branch": "master", + "git_sha": "41dfa3f7c0ffabb96a6a813fe321c6d1cc5b6e46", + "installed_by": ["modules"] + }, "demuxem": { "branch": "master", "git_sha": "05954dab2ff481bcb999f24455da29a5828af08d", @@ -87,6 +92,11 @@ "git_sha": "2b474484e0f4c130392c4d67225e15614732e2a8", "installed_by": ["modules"] }, + "souporcell": { + "branch": "master", + "git_sha": "6998ba9049ad0d544c243e2f785f94f8e9f8356b", + "installed_by": ["modules"] + }, "umitools/dedup": { "branch": "master", "git_sha": "05954dab2ff481bcb999f24455da29a5828af08d", diff --git a/modules/local/donor_match/environment.yml b/modules/local/donor_match/environment.yml new file mode 100644 index 00000000..56703c6b --- /dev/null +++ b/modules/local/donor_match/environment.yml @@ -0,0 +1,9 @@ +channels: + - conda-forge + - bioconda +dependencies: + - bioconda::r-pheatmap=1.0.8 + - conda-forge::r-complexupset=1.3.3 + - conda-forge::r-data.table=1.17.8 + - conda-forge::r-tidyverse=2.0.0 + - conda-forge::r-vcfr=1.15.0 diff --git a/modules/local/donor_match/main.nf b/modules/local/donor_match/main.nf new file mode 100644 index 00000000..3eccfbd2 --- /dev/null +++ b/modules/local/donor_match/main.nf @@ -0,0 +1,70 @@ +process DONOR_MATCH { + tag "${meta.id}" + label 'process_low' + + conda "${moduleDir}/environment.yml" + container "${ workflow.containerEngine == 'singularity' && !task.ext.singularity_pull_docker_container ? + 'https://community-cr-prod.seqera.io/docker/registry/v2/blobs/sha256/45/45b060e69064c7a7894787b0cc29259bbb24357650d06b627912b56d3521899b/data': + 'community.wave.seqera.io/library/r-complexupset_r-data.table_r-pheatmap_r-r.utils_pruned:3bd8312041c22554' }" + + //TODO findVariant = true not implemented + input: + tuple val(meta), path(barcode_whitelist), path(demultiplexing_result), val(cell_genotype), val(vireo_parent_dir) + val match_donor_method1 + val match_donor_method2 + val findVariants + val variant_count + val variant_pct + + output: + // best method combination for rescue/donor_match mode (has to be optional because runs with only genetic or hashing won't return this output) + tuple val(meta), path("*_best_donor_match.csv") , emit: best_donor_match , optional:true + tuple val(meta), path("*_best_all_assignment_after_match.csv") , emit: best_assignment_after_match , optional:true + tuple val(meta), path("*_best_intersect_assignment_after_match.csv") , emit: best_intersect_assignment_after_match, optional:true + tuple val(meta), path("*_score_record.csv") , emit: score_record , optional:true + + // comparison between deconvolution methods + tuple val(meta), path("*/*_vs_*all_assignment_after_match.csv") , emit: assignment_after_match + tuple val(meta), path("*/*_vs_*intersect_assignment_after_match.csv"), emit: assignment_intersect_match + tuple val(meta), path("*/*_vs_*correlation_res.csv") , emit: correlation + tuple val(meta), path("*/*_vs_*donor_match.csv") , emit: donor_match + tuple val(meta), path("*/*_vs_*concordance_heatmap.png") , emit: concordance_heatmap + path "versions.yml" , emit: versions + + when: + task.ext.when == null || task.ext.when + + script: + // TODO for findVariant = true (not used by findVariant = false) + def cell_genotype_path = '' + def vireo_parent_path = '' + def ndonor = "${meta.n_sample}" + prefix = task.ext.prefix ?: "${meta.id}" + template('donor_match.R') + + stub: + prefix = task.ext.prefix ?: "${meta.id}" + """ + mkdir -p method1_vs_method2 + + touch ${prefix}_best_donor_match.csv + touch ${prefix}_best_all_assignment_after_match.csv + touch ${prefix}_best_intersect_assignment_after_match.csv + touch ${prefix}_score_record.csv + touch method1_vs_method2/${prefix}_method1_vs_method2_all_assignment_after_match.csv + touch method1_vs_method2/${prefix}_method1_vs_method2_intersect_assignment_after_match.csv + touch method1_vs_method2/${prefix}_method1_vs_method2_correlation_res.csv + touch method1_vs_method2/${prefix}_method1_vs_method2_donor_match.csv + touch method1_vs_method2/${prefix}_method1_vs_method2_concordance_heatmap.png + + cat <<-END_VERSIONS > versions.yml + "${task.process}": + r-base: \$(Rscript -e "cat(paste(R.version[['major']], R.version[['minor']], sep='.'))") + r-complexupset: \$(Rscript -e "library(ComplexUpset); cat(as.character(packageVersion('ComplexUpset')))") + r-data.table: \$(Rscript -e "library(data.table); cat(as.character(packageVersion('data.table')))") + r-pheatmap: \$(Rscript -e "library(pheatmap); cat(as.character(packageVersion('pheatmap')))") + r-tidyverse: \$(Rscript -e "library(tidyverse); cat(as.character(packageVersion('tidyverse')))") + r-vcfr: \$(Rscript -e "library(vcfR); cat(as.character(packageVersion('vcfR')))") + END_VERSIONS + """ +} diff --git a/modules/local/donor_match/templates/donor_match.R b/modules/local/donor_match/templates/donor_match.R new file mode 100644 index 00000000..e5bec4fc --- /dev/null +++ b/modules/local/donor_match/templates/donor_match.R @@ -0,0 +1,748 @@ +#!/usr/bin/env Rscript + +################################################ +################################################ +## Functions to handle Nextflow input ## +################################################ +################################################ + +#' Check for Non-Empty, Non-Whitespace String +#' +#' This function checks if the input is non-NULL and contains more than just whitespace. +#' It returns TRUE if the input is a non-empty, non-whitespace string, and FALSE otherwise. +#' +#' @param input A variable to check. +#' @return A logical value: TRUE if the input is a valid, non-empty, non-whitespace string; FALSE otherwise. + +is_valid_string <- function(input) { + !is.null(input) && nzchar(trimws(input)) +} + +# Helper function for NULL condition +string_to_null <- function(x, val = "[]") if (x == val) NULL else x +null_to_string <- function(x, val = "NULL") if (is.null(x)) val else x + +#' Parse out options from a string without recourse to optparse +#' +#' @param x Long-form argument list like --opt1 val1 --opt2 val2 +#' +#' @return named list of options and values similar to optparse + +parse_args <- function(x){ + args_list <- unlist(strsplit(x, ' ?--')[[1]])[-1] + args_vals <- lapply(args_list, function(x) scan(text=x, what='character', quiet = TRUE)) + + # Ensure the option vectors are length 2 (key/ value) to catch empty ones + args_vals <- lapply(args_vals, function(z){ length(z) <- 2; z}) + + parsed_args <- structure(lapply(args_vals, function(x) x[2]), names = lapply(args_vals, function(x) x[1])) + parsed_args[! is.na(parsed_args)] +} + +string_to_logical <- function(input) { + if (input == "FALSE") { + FALSE + } else if (input == "TRUE") { + TRUE + } else { + stop(paste0(input, " is not a valid logical. Use 'FALSE' or 'TRUE'.")) + } +} + +################################################ +################################################ +## Functions for the script ## +################################################ +################################################ + +convert2binary <- function(result_csv, method_name, min_cell) { + #' Convert categorical donor assignments from a method into a binary (one-hot encoded) matrix of cells vs donors. + #' Filters out invalid labels ("negative", "doublet", NA) if at least two different singlets assigments exist. + #' Returns NULL if the number of valid cells is below the specified threshold. + + method_assign <- result_csv %>% select(all_of(c("Barcode", method_name))) + donor_id <- setdiff( + unique(method_assign[[method_name]]), + c(NA, "negative", "doublet") + ) + method_assign <- + method_assign[method_assign[[method_name]] %in% donor_id, ] + if (nrow(method_assign) < min_cell) { + return(NULL) + } + if (length(unique(method_assign[[method_name]])) == 1) { + method_assign_binary <- + as.data.frame(matrix(0, nrow = nrow(result_csv), ncol = 1), + row.names = result_csv\$Barcode + ) + colnames(method_assign_binary) <- + c(unique(method_assign[[method_name]])) + method_assign_binary[rownames(method_assign_binary) %in% method_assign\$Barcode, ] <- + 1 + } else { + method_assign_binary <- + data.frame(model.matrix(~ method_assign[[method_name]] - 1, data = method_assign)) + names(method_assign_binary) <- sort(donor_id) + rownames(method_assign_binary) <- method_assign\$Barcode + } + return(method_assign_binary) +} + +################################################ +################################################ +## PARSE PARAMETERS FROM NEXTFLOW ## +################################################ +################################################ + +# Set defaults and classes +args <- list( + # File inputs + result_csv = '$demultiplexing_result', + barcode = '$barcode_whitelist', + ndonor = as.numeric('$meta.n_samples'), + cell_genotype = '$cell_genotype', + vireo_parent_dir = '$vireo_parent_dir', + + # second in puts + method1 = string_to_null('$match_donor_method1'), + method2 = string_to_null('$match_donor_method2'), + findVariants = as.logical('$findVariants'), + variant_count = as.numeric('$variant_count'), + variant_pct = as.numeric('$variant_pct'), + + # others + prefix = '$prefix', # Prefix name for output files. + outputdir = "" +) +opt_types <- lapply(args, class) + +# Apply parameter overrides +args_opt <- parse_args('$task.ext.args') +for ( ao in names(args_opt)){ + if (! ao %in% names(opt)){ + stop(paste("Invalid option:", ao)) + }else{ + # Handle special cases for logicals + if (opt_types[[ao]] == "logical") { + opt[[ao]] <- string_to_logical(args_opt[[ao]]) + } else if (! is.null(opt[[ao]])){ + # Preserve classes from defaults where possible + opt[[ao]] <- as(args_opt[[ao]], opt_types[[ao]]) + } else { + opt[[ao]] <- args_opt[[ao]] + } + } +} + +# Configure output precision +options(digits=5) + +# Check if file exists +# TODO check if files exist (not necessary for now) +# if (! file.exists(seuratObj)){ +# stop(paste0(seuratObj, ' is not a valid file')) +# } + +################################################ +################################################ +## Finish loading libraries ## +################################################ +################################################ + +library(pheatmap) +library(data.table) +library(ComplexUpset) +library(tidyverse) +library(vcfR) + +################################################ +################################################ +## Main Process ## +################################################ +################################################ + +# set TRUE to see print outputs for debugging +debugging <- FALSE + +# read assignment_all csv +result_csv <- NULL +min_cell <- 0 +if (file.exists(args\$result_csv) && !dir.exists(args\$result_csv)) { + result_csv <- + fread( + args\$result_csv, + stringsAsFactors = FALSE, + na.strings = c(NA_character_, "") + ) +} + +# remove barcode that are not in the whitelist +if (!is.null(args\$barcode)) { + barcode_whitelist <- fread(args\$barcode, + header = FALSE, + stringsAsFactors = FALSE + )\$V1 + result_csv <- + result_csv[result_csv\$Barcode %in% barcode_whitelist, ] +} + + +# finds all columns in the CSV that contain at least one real donor label (not “negative” or “doublet”), and returns their column names +colname_with_singlet <- + colnames(result_csv %>% select_if(~ any(. != "negative" & + . != "doublet"))) +colname_with_singlet <- + colname_with_singlet[colname_with_singlet != "Barcode"] + +if (length(colname_with_singlet) < 2) { + stop("Please choose more methods to run donor matching!") +} + +hashing_methods <- + c( + "demuxem", + "htodemux", + "multiseq", + "hashsolo", + "hasheddrops", + "bff_raw", + "bff_cluster", + "bff_consensuscall", + "gmmdemux" + ) +genetic_methods <- + c("demuxlet", "freemuxlet", "vireo", "scsplit", "souporcell") + + +if (!is.null(args\$method1) && !is.null(args\$method2)) { + + for (m in c("method1", "method2")) { + method <- get(paste0("args\$", m)) + if (!any(startsWith(colname_with_singlet, method))) { + warning(sprintf( + "⚠️ %s ('%s') couldn't find at least one singlet. Ensure that the method you chose has at least one real donor label (not 'negative' or 'doublet') in one of the tasks.", + tools::toTitleCase(m), method + )) + } + } + + method1_all <- colname_with_singlet[startsWith(colname_with_singlet, args\$method1)] + method2_all <- colname_with_singlet[startsWith(colname_with_singlet, args\$method2)] + +} else { + + # get all column names that are genetic + genetics_all <- + Filter(function(x) { + any(sapply(genetic_methods, function(y) { + grepl(y, x) + })) + }, colname_with_singlet) + + # get all column names that are hashing + hashing_all <- + Filter(function(x) { + any(sapply(hashing_methods, function(y) { + grepl(y, x) + })) + }, colname_with_singlet) + + + # Build pairs of methods that we want to compare in the for-loop + + # Match between genetics- and hashing-based methods + if (length(hashing_all) > 0 && length(genetics_all) > 0) { + all_methods_pair <- + expand.grid(genetics = genetics_all, hashing = hashing_all) + method1_all <- as.character(all_methods_pair\$genetics) + method2_all <- as.character(all_methods_pair\$hashing) + } + + # Compare only within hashing methods + else if (length(hashing_all) > 0) { + method_pair <- combn(hashing_all, 2) + method1_all <- method_pair[1, ] + method2_all <- method_pair[2, ] + } + + # Compare only within genetics methods + else if (length(genetics_all) > 0) { + method_pair <- combn(genetics_all, 2) + method1_all <- method_pair[1, ] + method2_all <- method_pair[2, ] + } +} + +best_result <- 0 +best_method1 <- "None" +best_method2 <- "None" +num_trial <- 1 + +result_record <- data.frame( + best_method1 = character(), + best_method2 = character(), + score = numeric(), + matched_donor = numeric(), + remain_na = logical(), + stringsAsFactors = FALSE +) + +if (is.null(method1_all) || is.null(method2_all)) { + stop("No method was found in the CSV file!") +} + +for (i in 1:length(method1_all)) { + + # extract the pair of methods we would like to compare now + method1 <- method1_all[i] + method2 <- method2_all[i] + + # put the hashing method on the second place + if (grepl(paste(hashing_methods, collapse = "|"), method1) && + (grepl(paste(genetic_methods, collapse = "|"), method2))) { + hash_method <- method1 + method1 <- method2 + method2 <- hash_method + } + + if(debugging){ + print(paste0("Comapring ", method1, " and ", method2)) + } + + outputdir <- file.path(paste0(method1, "_vs_", method2)) + if (!dir.exists(outputdir)) { + dir.create(outputdir, recursive = TRUE) + } + + filename_prefix <- paste0(args\$prefix,"_",method1, "_vs_", method2) + + method1_res <- convert2binary(result_csv, method1, min_cell) + method2_res <- convert2binary(result_csv, method2, min_cell) + if (is.null(method1_res) || is.null(method2_res)) { + next + } + + # Extract barcodes classified as singlets by both methods. + # This meaning of intersect is not true for edge cases + # where a method assigned only one singlet label (see convert2binary if-statement). + intersect_barcode <- + intersect(rownames(method1_res), rownames(method2_res)) + if (length(intersect_barcode) == 0) { + next + } + method1_res <- + method1_res[rownames(method1_res) %in% intersect_barcode, , drop = FALSE] + method2_res <- + method2_res[rownames(method2_res) %in% intersect_barcode, , drop = FALSE] + + # correlation matrix with donor x donor + correlation_res <- try( + { + apply(method1_res, 2, function(x) { + apply(method2_res, 2, function(y) { + return(cor.test(x, y)[["estimate"]][["cor"]]) + }) + }) + }, + silent = TRUE + ) + # Skip this method pair if correlation calculation failed + if (inherits(correlation_res, "try-error")) { + cat("Failed to calculate phi coefficient") + next + } + + if (is.vector(correlation_res)) { + correlation_res <- t(as.data.frame(correlation_res)) + rownames(correlation_res) <- colnames(method2_res) + } + write.csv( + correlation_res, + file.path(outputdir, paste0(filename_prefix, "_correlation_res.csv")) + ) + + match_score <- 0 + matched_donor <- 0 + geno_match <- + as.data.frame(matrix(nrow = ncol(correlation_res), ncol = 3)) + colnames(geno_match) <- c("Method1", "Method2", "Correlation") + geno_match\$Method1 <- colnames(correlation_res) + + for (id in geno_match\$Method1) { + # Checks if the max value is finite and + # if the current donor pair is a mutual best match between both methods + if (!is.infinite(-max(correlation_res[, id], na.rm = TRUE)) && + max(correlation_res[, id], na.rm = TRUE) == + max(correlation_res[which.max(correlation_res[, id]), ], na.rm = TRUE)) { + geno_match[which(geno_match\$Method1 == id), 2:3] <- + c( + rownames(correlation_res)[which.max(correlation_res[, id])], + max(correlation_res[, id], na.rm = TRUE) + ) + match_score <- + match_score + max(correlation_res[, id], na.rm = TRUE) + matched_donor <- matched_donor + 1 + } else { + geno_match[which(geno_match\$Cluster1_ID == id)] <- + c("unassigned", NA) + } + } + + write.table( + geno_match[, 1:2], + file.path(outputdir, paste0(filename_prefix, "_donor_match.csv")), + row.names = FALSE, + col.names = FALSE, + sep = " ", + quote = FALSE + ) + + # save concordance heatmap + if (!all(is.na(correlation_res))) { + newCols <- colorRampPalette(grDevices::rainbow(nrow(geno_match))) + annoCol <- newCols(nrow(geno_match)) + names(annoCol) <- colnames(correlation_res) + annoCol <- list(category = annoCol) + correlation_res <- + correlation_res[!is.na(row.names(correlation_res)), , drop = FALSE] + correlation_res <- + correlation_res[order(as.numeric(row.names(correlation_res))), , drop = FALSE] + pheatmap( + correlation_res, + treeheight_row = FALSE, + treeheight_col = FALSE, + display_numbers = TRUE, + angle_col = "45", + number_color = "white", + fontsize = 12, + cluster_rows = FALSE, + cluster_cols = FALSE, + width = 7, + height = 5, + filename = file.path(outputdir, paste0(filename_prefix, "_concordance_heatmap.png")) + ) + } + + if (grepl(paste(hashing_methods, collapse = "|"), method2) && + grepl(paste(genetic_methods, collapse = "|"), method1)) { + remain_na <- (matched_donor != args\$ndonor) + match_score <- match_score / args\$ndonor + + if (match_score > best_result && !remain_na) { + write.table( + geno_match[, 1:2], + file.path(paste0(args\$prefix,"_best_donor_match.csv")), + row.names = FALSE, + col.names = FALSE, + sep = " ", + quote = FALSE + ) + best_method1 <- method1 + best_method2 <- method2 + best_result <- match_score + + } + + new_record <- + c(method1, method2, match_score, matched_donor, remain_na) + result_record[num_trial, ] <- new_record + num_trial <- num_trial + 1 + + result_merge <- select(result_csv, "Barcode", method1, method2) + result_merge_new <- result_merge + + # replace donor ID's from the genetic assignment with HTO of hashing + for (i in 1:nrow(geno_match)) { + result_merge_new[[method1]] <- replace( + result_merge_new[[method1]], + result_merge[[method1]] == geno_match\$Method1[i], + geno_match\$Method2[i] + ) + } + + # only retain barcodes that are in the intersect (definition of intersect see above) + result_merge_new_intersect <- + result_merge_new[result_merge_new\$Barcode %in% intersect_barcode, ] + + write.csv( + result_merge_new, + file.path(outputdir,paste0(filename_prefix, "_all_assignment_after_match.csv")), + row.names = FALSE + ) + + write.csv( + result_merge_new_intersect, + file.path(outputdir, paste0(filename_prefix, "_intersect_assignment_after_match.csv")), + row.names = FALSE + ) + + if (best_result == match_score) { + write.csv( + result_merge_new, + file.path(paste0(args\$prefix,"_best_all_assignment_after_match.csv")), + row.names = FALSE + ) + + write.csv( + result_merge_new_intersect, + file.path(paste0(args\$prefix,"_best_intersect_assignment_after_match.csv")), + row.names = FALSE + ) + } + } +} + +# TODO what is if there is more than one best match between methods? +if (best_method1 != "None" && best_method2 != "None" && debugging) { + print( + paste0( + "Best method pair: ", + best_method1, + " and ", + best_method2, + " with score ", + best_result + ) + ) + print("------------------------------------------------------------------") +} + +if (nrow(result_record) > 1) { + write.csv(result_record, + row.names = FALSE, + file.path(paste0(args\$prefix,"_score_record.csv")) + ) +} + +# TODO findVariants = true not implemented yet + +if (args\$findVariants == "True" || args\$findVariants == "default") { + if (startsWith(best_method1, "vireo")) { + write.table( + best_method1, + file.path(args\$outputdir, "best_method_vireo.txt"), + sep = "\t", + row.names = FALSE, + col.names = FALSE, + quote = FALSE + ) + } else { + stop("Vireo is not the best method for donor matching!") + } + outputdir <- + file.path(args\$outputdir, paste0(best_method1, "_vs_", best_method2)) + outputdir_variant <- file.path(outputdir, "variant_filtering") + ifelse(!dir.exists(outputdir_variant), + dir.create(outputdir_variant), + FALSE + ) + result_merge_new <- + fread(file.path(outputdir, "intersect_assignment_after_match.csv"), + header = T + ) + result_merge_new\$match <- + result_merge_new[[best_method1]] == result_merge_new[[best_method2]] + matched <- result_merge_new[result_merge_new\$match, ] + unmatched <- + result_csv[!result_csv\$Barcode %in% matched\$Barcode, ]\$Barcode + cell_genotype_vcf <- read.vcfR(args\$cell_genotype) + cell_genotype_vcf_gt <- + extract.gt(cell_genotype_vcf, + element = "GT", + as.numeric = TRUE + ) + donors <- sort(unique(matched[[best_method1]])) + + representative_variant_list <- + vector(mode = "list", length = length(donors)) + representative_variant_list <- + setNames(representative_variant_list, donors) + + for (donorid in donors) { + matched_barcode <- + matched[matched[[best_method1]] == donorid]\$Barcode + matched_gt_list <- cell_genotype_vcf_gt[, matched_barcode] + matched_gt_list <- + matched_gt_list[rowSums(is.na(matched_gt_list)) != ncol(matched_gt_list), ] + matched_gt <- + as.data.frame(matrix(nrow = nrow(matched_gt_list))) + matched_gt\$ref <- rowSums(matched_gt_list == 0, na.rm = TRUE) + matched_gt\$alt <- rowSums(matched_gt_list != 0, na.rm = TRUE) + matched_gt\$V1 <- rownames(matched_gt_list) + matched_gt\$count <- matched_gt\$ref + matched_gt\$alt + matched_gt\$pct <- + matched_gt\$alt / (matched_gt\$ref + matched_gt\$alt) + matched_gt\$dominant <- ifelse(matched_gt\$pct > 0.5, 1, 0) + matched_gt <- matched_gt[(matched_gt\$pct >= args\$variant_pct | + matched_gt\$pct <= (1 - args\$variant_pct)), ] + matched_gt <- + matched_gt[matched_gt\$count >= args\$variant_count, ] + + unmatched_gt_list <- cell_genotype_vcf_gt[, unmatched] + unmatched_gt_list <- + unmatched_gt_list[rownames(unmatched_gt_list) %in% matched_gt\$V1, ] + unmatched_gt_list <- + unmatched_gt_list[rowSums(is.na(unmatched_gt_list)) != ncol(unmatched_gt_list), ] + unmatched_gt_list <- + cbind(rownames(unmatched_gt_list), unmatched_gt_list) + unmatched_gt_list <- + melt(data.table(unmatched_gt_list), id.vars = "V1") + unmatched_gt_list <- + unmatched_gt_list[!is.na(unmatched_gt_list\$value), ] + colnames(unmatched_gt_list) <- c("variant", "cell", "allele") + + write.csv(matched_gt, + file.path(outputdir_variant, paste0(donorid, "_matched_gt.csv")), + row.names = FALSE + ) + write.csv(unmatched_gt_list, + file.path(outputdir_variant, paste0(donorid, "_unmatched_gt.csv")), + row.names = FALSE + ) + + informative_variants_cells <- + merge( + matched_gt, + unmatched_gt_list, + by.x = c("V1", "dominant"), + by.y = c("variant", "allele") + ) + colnames(informative_variants_cells)[1] <- "variant" + num_informative_variants <- informative_variants_cells %>% + group_by(cell) %>% + summarise(matched = n()) + if (nrow(unmatched_gt_list[!unmatched_gt_list\$cell %in% num_informative_variants\$cell, ]) > 0) { + print(unmatched_gt_list[!unmatched_gt_list\$cell %in% num_informative_variants\$cell, ]) + } + representative_variant_list[[donorid]] <- + list(unique(informative_variants_cells\$variant)) + write.table( + unique(informative_variants_cells\$variant), + file.path( + outputdir_variant, + paste0(donorid, "_informative_variants.csv") + ), + row.names = FALSE, + col.names = FALSE + ) + } + + representative_variant <- + rbindlist(representative_variant_list, idcol = "donor") + colnames(representative_variant)[2] <- "variant" + representative_variant_df <- + dcast(data = representative_variant, variant ~ donor, length) + write.csv( + representative_variant_df, + file.path(args\$outputdir, "all_representative_variant_df.csv") + ) + + upset <- ComplexUpset::upset( + representative_variant_df, + donors, + width_ratio = 0.45, + height_ratio = 0.9, + stripes = "white", + max_degree = 1, + name = "Number of donor-specific variants", + set_sizes = ( + upset_set_size() + + geom_text( + aes(label = ..count.., size = 3), + hjust = -0.1, + stat = "count", + color = "white", + size = 2.3 + ) + + theme( + axis.text.x = element_text(angle = 90), + text = element_text(size = 10) + ) + ), + base_annotations = list("Intersection size" = intersection_size()) + ) + ggsave(file.path(args\$outputdir, "donor_specific_variants_upset.png")) + representative_variant_single <- + representative_variant_df[rowSums(representative_variant_df[, -1]) == 1, ] + representative_variant_single <- + separate( + representative_variant_single, + col = "variant", + into = c("chr", "pos"), + sep = "_" + ) + write.table( + representative_variant_single[, c("chr", "pos")], + quote = FALSE, + col.names = FALSE, + sep = "\t", + row.names = FALSE, + file.path(args\$outputdir, "donor_specific_variants.csv") + ) +} + +if (args\$findVariants == "True" || args\$findVariants == "vireo") { + if (startsWith(best_method1, "vireo")) { + write.table( + best_method1, + file.path(args\$outputdir, "best_method_vireo.txt"), + sep = "\t", + row.names = FALSE, + col.names = FALSE, + quote = FALSE + ) + } else { + stop("Vireo is not the best method1 for donor matching, variants can not be filtered!") + } + + vireo_result_dir <- file.path(args\$vireo_parent_dir, best_method1) + + representative_variant <- + list.files( + vireo_result_dir, + "filtered_variants.tsv", + full.names = TRUE, + recursive = TRUE + )[1] + representative_variant <- fread(representative_variant) + representative_variant <- separate( + representative_variant, + col = "variants", + into = c("chr", "pos"), + sep = "_", + extra = "drop" + ) + write.table( + representative_variant[, c("chr", "pos")], + quote = FALSE, + col.names = FALSE, + sep = "\t", + row.names = FALSE, + file.path(args\$outputdir, "representative_variants_vireo.csv") + ) +} + +################################################ +################################################ +## VERSIONS FILE ## +################################################ +################################################ + +r.version <- paste(R.version[['major']],R.version[['minor']], sep = ".") +pheatmap.version <- as.character(packageVersion('pheatmap')) +data_table.version <- as.character(packageVersion('data.table')) +complexUpset.version <- as.character(packageVersion('ComplexUpset')) +tidyverse.version <- as.character(packageVersion('tidyverse')) +vcfR.version <- as.character(packageVersion('vcfR')) + +writeLines( + c( + '"${task.process}":', + paste(' r-base:', r.version), + paste(' r-complexupset:', complexUpset.version), + paste(' r-data.table:', data_table.version), + paste(' r-pheatmap:', pheatmap.version), + paste(' r-tidyverse:', tidyverse.version), + paste(' r-vcfr:', vcfR.version) + ), +'versions.yml') diff --git a/modules/local/dropletutils/mtxconvert/templates/convert.R b/modules/local/dropletutils/mtxconvert/templates/convert.R index a8480124..e554196d 100644 --- a/modules/local/dropletutils/mtxconvert/templates/convert.R +++ b/modules/local/dropletutils/mtxconvert/templates/convert.R @@ -4,6 +4,7 @@ library(DropletUtils) mtx_dir <- "${input_mtx_dir}" + sce <- read10xCounts(mtx_dir) # Read to SingleCellExperiment object print(sce) @@ -17,7 +18,19 @@ if ("${write_csv}" == "true") { write.csv(as.matrix(count_matrix), file = "${prefix}.csv", row.names = TRUE) } +# TODO remove if demuxEM issue is solved # Write to h5 file +# write10xCounts( +# path = "${prefix}.h5", +# x = counts(sce), +# barcodes = colData(sce)\$Barcode, +# gene.id = rownames(sce), +# gene.symbol = if (!is.null(rowData(sce)\$Symbol)) rowData(sce)\$Symbol else rownames(sce), +# gene.type = if (!is.null(rowData(sce)\$Type)) rowData(sce)\$Type else rep("Gene Expression", nrow(sce)), +# type = "HDF5", +# version = "3", # <-- ensures /matrix layout instead of /unknown +# overwrite = TRUE +# ) write10xCounts("${prefix}.h5", count_matrix, type = "HDF5") ################################################ diff --git a/modules/local/extract_hashes/main.nf b/modules/local/extract_hashes/main.nf index 439d4c53..3c86a80d 100644 --- a/modules/local/extract_hashes/main.nf +++ b/modules/local/extract_hashes/main.nf @@ -3,7 +3,7 @@ process EXTRACT_HASHES { label 'process_low' input: - tuple val(meta), path(hto_matrix) + tuple val(meta), path(hto_dir) output: tuple val(meta), path("*_hashes.txt"), emit: hashes @@ -13,10 +13,8 @@ process EXTRACT_HASHES { script: prefix = task.ext.prefix ?: "${meta.id}" - - script: """ - zcat $hto_matrix | awk '{print \$2}' | paste -sd, > ${prefix}_hashes.txt + gunzip -c ${hto_dir}/features.tsv.gz | awk '{print \$2}' | paste -sd, - > ${prefix}_hashes.txt """ stub: diff --git a/modules/local/gene_summary/environment.yml b/modules/local/gene_summary/environment.yml new file mode 100644 index 00000000..c6c95551 --- /dev/null +++ b/modules/local/gene_summary/environment.yml @@ -0,0 +1,10 @@ +channels: + - conda-forge + - bioconda +dependencies: + - bioconda::pegasusio=0.10.0 + - conda-forge::mudata=0.3.1 + - conda-forge::numpy=1.24.2 + - conda-forge::pandas=2.3.1 + - conda-forge::pyyaml=6.0.3 + - conda-forge::scanpy=1.11.2 diff --git a/modules/local/gene_summary/main.nf b/modules/local/gene_summary/main.nf new file mode 100644 index 00000000..460adfec --- /dev/null +++ b/modules/local/gene_summary/main.nf @@ -0,0 +1,52 @@ +process GENE_SUMMARY { + tag "${meta.id}" + label 'process_low' + + conda "${moduleDir}/environment.yml" + container "${ workflow.containerEngine == 'singularity' && !task.ext.singularity_pull_docker_container ? + 'https://community-cr-prod.seqera.io/docker/registry/v2/blobs/sha256/d8/d863e56b5ce15b271e8c8666ec22217df5cfc57a9731cc23c7f92674dc7ab0c7/data': + 'community.wave.seqera.io/library/pegasusio_mudata_numpy_pandas_pruned:ecdbf7e42b2f3213' }" + + input: + tuple val(meta), + path(rna_matrix), + path(hto_matrix), + path(barcodes), + path(vireo), + path(demuxlet), + path(freemuxlet), + path(souporcell) + tuple val (generate_anndata), val(generate_mudata) + + output: + tuple val(meta), path("*_genetic_summary_assignment.csv") , emit: assignment , optional: false + tuple val(meta), path("*_genetic_summary_classification.csv"), emit: classification, optional: false + tuple val(meta), path("*_genetic_summary.h5ad") , emit: h5ad , optional: true + tuple val(meta), path("*_genetic_summary.h5mu") , emit: h5mu , optional: true + path "versions.yml" , emit: versions , optional: false + + when: + task.ext.when == null || task.ext.when + + script: + prefix = task.ext.prefix ?: "${meta.id}" + + template 'gene_summary.py' + + stub: + def prefix = task.ext.prefix ?: "${meta.id}" + """ + touch ${prefix}_genetic_summary_assignment.csv + touch ${prefix}_genetic_summary_classification.csv + + cat <<-END_VERSIONS > versions.yml + "${task.process}": + python: \$(python3 -c 'import platform; print(platform.python_version())') + pandas: \$(python3 -c 'import pandas as pd; print(pd.__version__)') + scanpy: \$(python3 -c 'import scanpy as sc; print(sc.__version__)') + numpy: \$(python3 -c 'import numpy as np; print(np.__version__)') + mudata: \$(python3 -c 'import mudata as md; print(md.__version__)') + pegasusio: \$(python3 -c 'import pegasusio as io; print(io.__version__)') + END_VERSIONS + """ +} diff --git a/modules/local/gene_summary/templates/gene_summary.py b/modules/local/gene_summary/templates/gene_summary.py new file mode 100644 index 00000000..1a0d89dc --- /dev/null +++ b/modules/local/gene_summary/templates/gene_summary.py @@ -0,0 +1,286 @@ +#!/usr/bin/env python3 + +# versions +import platform +import yaml + +import os + +os.environ["MPLCONFIGDIR"] = "./tmp/mpl" +os.environ["NUMBA_CACHE_DIR"] = "./tmp/numba" + +import pandas as pd +import scanpy as sc +import numpy as np +import mudata as md +import pegasusio as io + +from pathlib import Path +from mudata import MuData +from typing import Tuple + + +class Arguments: + """Parses the arguments, including the ones coming from $task.ext.args. + Adopted from mygene module (Suzanne Jin).""" + + def __init__(self) -> None: + self.singlet_str = "singlet" + self.doublet_str = "doublet" + self.negative_str = "negative" + self.parse_input_args() + self.creat_output_dirs() + + def parse_input_args(self) -> None: + self.prefix = "$task.ext.prefix" if "$task.ext.prefix" != "null" else "$meta.id" + + self.rna_matrix = "${rna_matrix}" + self.hto_matrix = "${hto_matrix}" + self.barcodes = "${barcodes}" + self.vireo = "${vireo}" + self.demuxlet = "${demuxlet}" + self.freemuxlet = "${freemuxlet}" + self.souporcell = "${souporcell}" + + self.generate_anndata = "${generate_anndata}" + self.generate_mudata = "${generate_mudata}" + + path_vars = { + "rna_matrix", + "hto_matrix", + "barcodes", + "vireo", + "demuxlet", + "freemuxlet", + "souporcell", + } + + boolean_vars = {"generate_anndata", "generate_mudata"} + + def _tranlate_to_python(input_str, value_str): + if value_str.strip() == "": + return None + else: + if input_str in path_vars: + return Path(value_str) + elif input_str in boolean_vars: + if value_str == "true": + return True + else: + return False + + vars = path_vars | boolean_vars + + for var in vars: + raw_value = getattr(self, var) + processed_value = _tranlate_to_python(var, raw_value) + setattr(self, var, processed_value) + + def creat_output_dirs(self) -> None: + directories = { + "assignment": "_genetic_summary_assignment.csv", + "classification": "_genetic_summary_classification.csv", + "h5mu": "_genetic_summary.h5mu", + "h5ad": "_genetic_summary.h5ad", + } + + for output, directory in directories.items(): + setattr(self, output, self.prefix + directory) + + def print_args(self) -> None: + for attr in vars(self): + print(f"{attr}: {getattr(self, attr)}") + + +class ProcessDeconvolutionMethodResult: + def __init__(self): + self.deconvolution_methods = ["demuxlet", "freemuxlet", "souporcell", "vireo"] + + self.checkHashNames = True + self.chechEmptyInput = True + + def vireo(self, args: Arguments) -> Tuple[pd.DataFrame, pd.DataFrame]: + results = pd.read_csv(args.vireo, sep="\t") + + assignment = results[["cell", "donor_id"]].rename( + columns={"cell": "Barcode", "donor_id": "vireo"} + ) + assignment["vireo"].replace({"unassigned": args.negative_str}, inplace=True) + + classification = assignment.copy() + classification["vireo"][ + ~classification["vireo"].isin([args.doublet_str, args.negative_str]) + ] = args.singlet_str + + return assignment, classification + + def souporcell(self, args: Arguments) -> Tuple[pd.DataFrame, pd.DataFrame]: + results = pd.read_csv(args.souporcell, sep="\t").iloc[:, 0:3] + results.loc[results["status"] == "doublet", "assignment"] = "doublet" + results.loc[results["status"] == "unassigned", "assignment"] = "negative" + + assignment = results[["barcode", "assignment"]].rename( + columns={"barcode": "Barcode", "assignment": "souporcell"} + ) + + classification = assignment.copy() + classification["souporcell"] = classification["souporcell"].where( + classification["souporcell"].isin(["doublet", "negative"]), "singlet" + ) + + return assignment, classification + + def demuxlet(self, args: Arguments) -> Tuple[pd.DataFrame, pd.DataFrame]: + return self.demuxlet_or_freemuxlet(args, "demuxlet") + + def freemuxlet(self, args: Arguments) -> Tuple[pd.DataFrame, pd.DataFrame]: + return self.demuxlet_or_freemuxlet(args, "freemuxlet") + + def demuxlet_or_freemuxlet( + self, args: Arguments, method: str + ) -> Tuple[pd.DataFrame, pd.DataFrame]: + file_path = getattr(args, method) + + result = pd.read_csv(file_path, sep="\t") + + result[method] = np.where( + result["BEST.GUESS"].str.split(",").str[0] + == result["BEST.GUESS"].str.split(",").str[1], + result["BEST.GUESS"].str.split(",").str[0], + args.doublet_str, + ) + + result[method] = np.where( + result["DROPLET.TYPE"] == "AMB", args.negative_str, result[method] + ) + + assignment = result[["BARCODE", method]].rename(columns={"BARCODE": "Barcode"}) + + classification = assignment.copy() + classification[method] = np.where( + classification[method].isin([args.doublet_str, args.negative_str]), + classification[method], + args.singlet_str, + ) + + return assignment, classification + + +# TODO if we keep saving AnnData/MuData in gene/hash_summary add AnnData to container for input type (https://github.com/theislab/hadge/issues/83) +# joins the assignment results with RNA, generate_anndata will return h5ad with RNA matrix +def saveAnnDataMuData( + args: Arguments, assignment_summary: pd.DataFrame, rna_data, hto_data +): + if args.generate_mudata or args.generate_anndata: + assignment_summary.set_index("Barcode", inplace=True) + rna_data.obs = rna_data.obs.join(assignment_summary, how="left").fillna( + args.negative_str + ) + + if args.generate_anndata: + rna_data.write(args.h5ad) + + if args.generate_mudata: + mudata = MuData({"rna": rna_data, "hto": hto_data}) + mudata.update() + mudata.write(args.h5mu) + + +def print_method_item_counts(dfs): + """ + Takes the list of assignment/classification DataFrames (assignments/classifications) and prints a summary table: + method name | total count | count(item1) | count(item2) | ... + An item refers to the donor label in the assignment (HTO-1, HTO-2, ...) or the classification (singlet, doublet, negative). + """ + rows = [] + all_items = set() + + # Extract items and their counts for every deconvolution method + for df in dfs: + print(df) + + method_name = df.columns[1] + counts = df[method_name].value_counts(dropna=False) + total = len(df) + all_items.update(counts.index) + + row = {"method": method_name, "count_overall": total} + row.update(counts.to_dict()) + rows.append(row) + + summary = pd.DataFrame(rows).fillna(0) + + # Convert all numeric values to int + for col in summary.columns: + if col != "method": + summary[col] = summary[col].astype(int) + + # Order columns + summary = summary[["method", "count_overall"] + sorted(list(all_items))] + + print(summary.to_string(index=False)) + + +if __name__ == "__main__": + # ======================== process nextflow input arguments ======================== + + args = Arguments() + + # ========================= process results from modules =========================== + + assignments = [] + classifications = [] + + # call all functions that process the module outputs + processing_functions = ProcessDeconvolutionMethodResult() + for method in processing_functions.deconvolution_methods: + if getattr(args, method) is not None: + assignment, classification = getattr(processing_functions, method)(args) + assignments.append(assignment) + classifications.append(classification) + + # ================================== save results ================================== + + # ----------------------------------- save csv's ----------------------------------- + + rna_data = sc.read_10x_mtx(args.rna_matrix) + hto_data = sc.read_10x_mtx(args.hto_matrix, gex_only=False) + + # Use rna_data.obs_names() as index to perform a left join + assignment_summary = pd.DataFrame(rna_data.obs_names, columns=["Barcode"]) + classification_summary = assignment_summary.copy() + + for assignment in assignments: + assignment_summary = pd.merge( + assignment_summary, assignment, on="Barcode", how="left" + ) + + for classification in classifications: + classification_summary = pd.merge( + classification_summary, classification, on="Barcode", how="left" + ) + + assignment_summary.fillna(args.negative_str).to_csv(args.assignment, index=False) + classification_summary.fillna(args.negative_str).to_csv( + args.classification, index=False + ) + + # -------------------------------- save mudata/anndata ----------------------------- + + saveAnnDataMuData(args, assignment_summary, rna_data, hto_data) + + # -------------------------------------- versions ---------------------------------- + + versions = { + "${task.process}": { + "python": platform.python_version(), + "pandas": pd.__version__, + "scanpy": sc.__version__, + "numpy": np.__version__, + "mudata": md.__version__, + "pegasusio": io.__version__, + } + } + + with open("versions.yml", "w") as f: + yaml.dump(versions, f) diff --git a/modules/local/hash_summary/main.nf b/modules/local/hash_summary/main.nf index e2d95ee6..d69eb8ba 100644 --- a/modules/local/hash_summary/main.nf +++ b/modules/local/hash_summary/main.nf @@ -4,11 +4,20 @@ process HASH_SUMMARY { conda "${moduleDir}/environment.yml" container "${ workflow.containerEngine == 'singularity' && !task.ext.singularity_pull_docker_container ? - 'https://community-cr-prod.seqera.io/docker/registry/v2/blobs/sha256/a5/a5f3952003b974094e3b9d92a6b3499b56554db8de0d7622e5b959842d11759e/data': - 'community.wave.seqera.io/library/pegasusio_anndata_mudata_numpy_pruned:9d13d0d12376624e' }" + 'https://community-cr-prod.seqera.io/docker/registry/v2/blobs/sha256/d8/d863e56b5ce15b271e8c8666ec22217df5cfc57a9731cc23c7f92674dc7ab0c7/data': + 'community.wave.seqera.io/library/pegasusio_mudata_numpy_pandas_pruned:ecdbf7e42b2f3213' }" input: - tuple val(meta), path(rna_matrix), path(hto_matrix), path(htodemux_assignments), path (htodemux_classification), path(multiseq), path(bff), path(demuxem), path(gmmdemux_results), path(gmmdemux_config), path(hasheddrops_results), path(hasheddrops_id_to_hash), path(hashsolo) + tuple val(meta), + path(rna_matrix), + path(hto_matrix), + path(htodemux_assignments), path (htodemux_classification), + path(multiseq), + path(bff), + path(demuxem), + path(gmmdemux_results), path(gmmdemux_config), + path(hasheddrops_results), path(hasheddrops_id_to_hash), + path(hashsolo) tuple val (generate_anndata), val(generate_mudata), val(bff_methods) output: @@ -16,6 +25,7 @@ process HASH_SUMMARY { tuple val(meta), path("*_hashing_summary_classification.csv"), emit: classification, optional: false tuple val(meta), path("*_hashing_summary.h5ad") , emit: h5ad , optional: true tuple val(meta), path("*_hashing_summary.h5mu") , emit: h5mu , optional: true + path "versions.yml" , emit: versions , optional: false when: task.ext.when == null || task.ext.when @@ -32,11 +42,14 @@ process HASH_SUMMARY { touch ${prefix}_hashing_summary_assignment.csv touch ${prefix}_hashing_summary_classification.csv - cat <<-END_VERSIONS > versions.yml "${task.process}": - r-seurat: \$(Rscript -e "library(Seurat); cat(as.character(packageVersion('Seurat')))") - r-base: \$(Rscript -e "cat(strsplit(R.version[['version.string']], ' ')[[1]][3])") + python: \$(python3 -c 'import platform; print(platform.python_version())') + pandas: \$(python3 -c 'import pandas as pd; print(pd.__version__)') + scanpy: \$(python3 -c 'import scanpy as sc; print(sc.__version__)') + numpy: \$(python3 -c 'import numpy as np; print(np.__version__)') + mudata: \$(python3 -c 'import mudata as md; print(md.__version__)') + pegasusio: \$(python3 -c 'import pegasusio as io; print(io.__version__)') END_VERSIONS """ } diff --git a/modules/local/hash_summary/templates/hash_summary.py b/modules/local/hash_summary/templates/hash_summary.py index b018a31f..1e6c2075 100644 --- a/modules/local/hash_summary/templates/hash_summary.py +++ b/modules/local/hash_summary/templates/hash_summary.py @@ -1,5 +1,9 @@ #!/usr/bin/env python3 +# versions +import platform +import yaml + import os os.environ["MPLCONFIGDIR"] = "./tmp/mpl" @@ -8,21 +12,19 @@ import pandas as pd import scanpy as sc import numpy as np +import mudata as md +import pegasusio as io + from pathlib import Path from mudata import MuData -from anndata import AnnData -from typing import Dict from typing import Tuple -import pegasusio as io + class Arguments: - # adopted from mygene module (Suzanne Jin) - """ - Parses the arguments, including the ones coming from $task.ext.args. - """ + """Parses the arguments, including the ones coming from $task.ext.args. + Adopted from mygene module (Suzanne Jin).""" def __init__(self) -> None: - self.singlet_str = "singlet" self.doublet_str = "doublet" self.negative_str = "negative" @@ -31,27 +33,25 @@ def __init__(self) -> None: self.testing_inputs() def parse_input_args(self) -> None: - - self.prefix = "$task.ext.prefix" if "$task.ext.prefix" != "null" else "$meta.id" - self.rna_matrix = "${rna_matrix}" - self.hto_matrix = "${hto_matrix}" - self.htodemux_assignments = "${htodemux_assignments}" - self.htodemux_classification = "${htodemux_classification}" - self.multiseq = "${multiseq}" - self.bff = "${bff}" - self.demuxem = "${demuxem}" - self.gmmdemux_results = "${gmmdemux_results}" - self.gmmdemux_config = "${gmmdemux_config}" - self.hasheddrops_results = "${hasheddrops_results}" - self.hasheddrops_id_to_hash = "${hasheddrops_id_to_hash}" - self.hashsolo = "${hashsolo}" - - self.generate_anndata = "${generate_anndata}" - self.generate_mudata = "${generate_mudata}" - self.bff_methods = "${bff_methods}" - self.hash_list = "${hash_list}" + self.rna_matrix = "${rna_matrix}" + self.hto_matrix = "${hto_matrix}" + self.htodemux_assignments = "${htodemux_assignments}" + self.htodemux_classification = "${htodemux_classification}" + self.multiseq = "${multiseq}" + self.bff = "${bff}" + self.demuxem = "${demuxem}" + self.gmmdemux_results = "${gmmdemux_results}" + self.gmmdemux_config = "${gmmdemux_config}" + self.hasheddrops_results = "${hasheddrops_results}" + self.hasheddrops_id_to_hash = "${hasheddrops_id_to_hash}" + self.hashsolo = "${hashsolo}" + + self.generate_anndata = "${generate_anndata}" + self.generate_mudata = "${generate_mudata}" + self.bff_methods = "${bff_methods}" + self.hash_list = "${hash_list}" path_vars = { "rna_matrix", @@ -68,17 +68,11 @@ def parse_input_args(self) -> None: "hashsolo", } - boolean_vars = { - "generate_anndata", - "generate_mudata" - } + boolean_vars = {"generate_anndata", "generate_mudata"} - other_vars = { - "bff_methods", - "hash_list" - } + other_vars = {"bff_methods", "hash_list"} - def _tranlate_to_python(input_str,value_str): + def _tranlate_to_python(input_str, value_str): if value_str.strip() == "": return None else: @@ -90,16 +84,20 @@ def _tranlate_to_python(input_str,value_str): else: return False elif input_str == "bff_methods": - if value_str == 'RAW': - return ['bff_raw'] - elif value_str == 'CLUSTER': - return ['bff_cluster'] - elif value_str == 'BOTH': - return ['bff_raw', 'bff_cluster','bff_consensuscall'] - else: - raise ValueError(f"Methods ({value_str}) for bff not specified correctly. Choose RAW, CLUSTER or BOTH as input.") + if value_str == "RAW": + return ["bff_raw"] + elif value_str == "CLUSTER": + return ["bff_cluster"] + elif value_str == "COMBINED": + return ["bff_raw", "bff_cluster", "bff_consensuscall"] + else: + raise ValueError( + f"Methods ({value_str}) for bff not specified correctly. Choose RAW, CLUSTER or COMBINED as input." + ) elif input_str == "hash_list": - return set(hash.strip() for hash in "${hash_list}".strip("[]").split(",")) + return set( + hash.strip() for hash in "${hash_list}".strip("[]").split(",") + ) vars = path_vars | boolean_vars | other_vars @@ -110,10 +108,10 @@ def _tranlate_to_python(input_str,value_str): def creat_output_dirs(self) -> None: directories = { - 'assignment': '_hashing_summary_assignment.csv', - 'classification': '_hashing_summary_classification.csv', - 'h5mu': '_hashing_summary.h5mu', - 'h5ad': '_hashing_summary.h5ad' + "assignment": "_hashing_summary_assignment.csv", + "classification": "_hashing_summary_classification.csv", + "h5mu": "_hashing_summary.h5mu", + "h5ad": "_hashing_summary.h5ad", } for output, directory in directories.items(): @@ -121,85 +119,88 @@ def creat_output_dirs(self) -> None: def testing_inputs(self) -> None: if [self.htodemux_assignments, self.htodemux_classification].count(None) == 1: - raise ValueError("The assignment or classification file of htodemux is empty.") + raise ValueError( + "The assignment or classification file of htodemux is empty." + ) if [self.gmmdemux_results, self.gmmdemux_config].count(None) == 1: raise ValueError("The results or config file of gmmdemux is empty.") def print_args(self) -> None: - """ - Print the arguments. - """ for attr in vars(self): print(f"{attr}: {getattr(self, attr)}") -class ProcessModuleOutput: +class ProcessModuleOutput: def __init__(self): # necessary to verify which functions should be called # because gmmdemux, hasheddrops and htodemux need two input files self.function_name_to_args_name = { - 'demuxem': 'demuxem', - 'hashsolo': 'hashsolo', - 'hasheddrops': 'hasheddrops_results', - 'multiseq': 'multiseq', - 'htodemux': 'htodemux_assignments', - 'gmmdemux': 'gmmdemux_results', - 'bff': 'bff' + "demuxem": "demuxem", + "hashsolo": "hashsolo", + "hasheddrops": "hasheddrops_results", + "multiseq": "multiseq", + "htodemux": "htodemux_assignments", + "gmmdemux": "gmmdemux_results", + "bff": "bff", } self.checkHashNames = True self.chechEmptyInput = True - # TODO add Barcode as index in all functions and add the index name "Barcode" - def demuxem(self, args: Arguments) -> Tuple[pd.DataFrame, pd.DataFrame]: - data = io.read_input(str(args.demuxem)) - classification = data.obs['demux_type'].to_frame() + classification = data.obs["demux_type"].to_frame() classification.reset_index(inplace=True) classification.columns = ["Barcode", "demuxem"] - classification['demuxem'] = classification['demuxem'].cat.rename_categories({"unknown": args.negative_str}) + classification["demuxem"] = classification["demuxem"].cat.rename_categories( + {"unknown": args.negative_str} + ) # TODO demuxem has more output barcodes than input barcodes metioned here: https://github.com/lilab-bcb/demuxEM/issues/20 - assignment = data.obs['assignment'].to_frame() + assignment = data.obs["assignment"].to_frame() assignment.reset_index(inplace=True) assignment.columns = ["Barcode", "demuxem"] return assignment, classification def hashsolo(self, args: Arguments) -> Tuple[pd.DataFrame, pd.DataFrame]: - results = pd.read_csv(args.hashsolo, index_col=0) assignment = results[["Classification"]] assignment.columns = ["hashsolo"] assignment = assignment.replace( - {"Doublet": args.doublet_str, - "Negative": args.negative_str} + {"Doublet": args.doublet_str, "Negative": args.negative_str} ) classification = results[["most_likely_hypothesis"]].copy() - classification["most_likely_hypothesis"] = ( - classification["most_likely_hypothesis"] - .replace({0.0: args.negative_str, 1.0: args.singlet_str, 2.0: args.doublet_str}) - ) + classification["most_likely_hypothesis"] = classification[ + "most_likely_hypothesis" + ].replace( + {0.0: args.negative_str, 1.0: args.singlet_str, 2.0: args.doublet_str} + ) - return assignment, classification + classification = classification.rename( + columns={"most_likely_hypothesis": "hashsolo"} + ) - def hasheddrops(self, args: Arguments) -> Tuple[pd.DataFrame, pd.DataFrame]: + return assignment.reset_index(), classification.reset_index() + def hasheddrops(self, args: Arguments) -> Tuple[pd.DataFrame, pd.DataFrame]: idx_to_htoname_df = pd.read_csv(args.hasheddrops_id_to_hash) - print(idx_to_htoname_df) idx_to_htoname_df.loc[len(idx_to_htoname_df)] = [np.nan, args.negative_str] - idx_to_htoname_map = idx_to_htoname_df.set_index('Index')['HTO'].to_dict() + idx_to_htoname_map = idx_to_htoname_df.set_index("Index")["HTO"].to_dict() obs_res = pd.read_csv(args.hasheddrops_results) obs_res["Classification"] = np.where( obs_res["Confident"] & obs_res["Confident"].notna(), args.singlet_str, - np.where(obs_res["Doublet"] & obs_res["Doublet"].notna(), args.doublet_str, args.negative_str) + np.where( + obs_res["Doublet"] & obs_res["Doublet"].notna(), + args.doublet_str, + args.negative_str, + ), ) obs_res["Assignment"] = np.where( @@ -210,28 +211,32 @@ def hasheddrops(self, args: Arguments) -> Tuple[pd.DataFrame, pd.DataFrame]: obs_res.rename(columns={obs_res.columns[0]: "Barcode"}, inplace=True) - print(obs_res) - - classification = obs_res[["Barcode", "Classification"]].rename(columns={"Classification": "hasheddrops"}) - assignment = obs_res[["Barcode", "Assignment"]].rename(columns={"Assignment": "hasheddrops"}) + classification = obs_res[["Barcode", "Classification"]].rename( + columns={"Classification": "hasheddrops"} + ) + assignment = obs_res[["Barcode", "Assignment"]].rename( + columns={"Assignment": "hasheddrops"} + ) - return assignment,classification + return assignment, classification def multiseq(self, args: Arguments) -> Tuple[pd.DataFrame, pd.DataFrame]: - assignment = pd.read_csv(args.multiseq) assignment.columns = ["Barcode", "multiseq"] assignment.replace( - {"Doublet": args.doublet_str, "Negative": args.negative_str}, inplace=True - ) + {"Doublet": args.doublet_str, "Negative": args.negative_str}, inplace=True + ) classification = assignment.copy() - classification.loc[(classification["multiseq"] != args.doublet_str) & (classification["multiseq"] != args.negative_str), "multiseq"] = args.singlet_str + classification.loc[ + (classification["multiseq"] != args.doublet_str) + & (classification["multiseq"] != args.negative_str), + "multiseq", + ] = args.singlet_str return assignment, classification def htodemux(self, args: Arguments) -> Tuple[pd.DataFrame, pd.DataFrame]: - assignment = pd.read_csv(args.htodemux_assignments) assignment.columns = ["Barcode", "htodemux"] assignment.replace("Doublet", args.doublet_str, inplace=True) @@ -242,16 +247,22 @@ def htodemux(self, args: Arguments) -> Tuple[pd.DataFrame, pd.DataFrame]: classification = pd.read_csv(args.htodemux_classification) classification.columns = ["Barcode", "htodemux"] classification.replace( - {"Singlet": args.singlet_str, "Doublet": args.doublet_str, "Negative": args.negative_str}, inplace=True + { + "Singlet": args.singlet_str, + "Doublet": args.doublet_str, + "Negative": args.negative_str, + }, + inplace=True, ) return assignment, classification def gmmdemux(self, args: Arguments) -> Tuple[pd.DataFrame, pd.DataFrame]: - number_of_hashes = len(args.hash_list) - df_config = pd.read_csv(args.gmmdemux_config, header=None, skipinitialspace=True) + df_config = pd.read_csv( + args.gmmdemux_config, header=None, skipinitialspace=True + ) df_config.columns = ["Cluster_id", "Description"] def _classify_hash(cluster_id: int, number_hashes: int) -> str: @@ -268,7 +279,7 @@ def _classify_hash(cluster_id: int, number_hashes: int) -> str: df_config["Assignment"] = df_config["Description"].where( df_config["Classification"] == args.singlet_str, - other=df_config["Classification"] + other=df_config["Classification"], ) # results with Cluster_id's @@ -285,24 +296,39 @@ def _classify_hash(cluster_id: int, number_hashes: int) -> str: return assignment, classification - def bff(self, args: Arguments) -> Tuple[pd.DataFrame, pd.DataFrame]: - + def bff(self, args: Arguments) -> Tuple[pd.DataFrame, pd.DataFrame]: df_result = pd.read_csv(args.bff) + df_result.rename(columns={"cellbarcode": "Barcode"}, inplace=True) - df_result.rename(columns={ - 'cellbarcode': 'Barcode', - 'consensuscall': 'bff_consensuscall' - }, inplace=True) + if len(args.bff_methods) == 3: + cols = ["bff_raw", "bff_cluster", "consensuscall", "consensuscall.global"] + else: + cols = args.bff_methods + + df_result[cols] = df_result[cols].replace( + { + "Singlet": args.singlet_str, + "Doublet": args.doublet_str, + "Negative": args.negative_str, + "Discordant": args.negative_str, + "Not Called": args.negative_str, + } + ) - assignment = df_result[['Barcode'] + args.bff_methods].copy() + if len(args.bff_methods) == 3: + # use the classification of consensuscall.global + assignment = df_result[ + ["Barcode", "bff_raw", "bff_cluster", "consensuscall"] + ].rename(columns={"consensuscall": "bff_consensuscall"}) - assignment[args.bff_methods] = assignment[args.bff_methods].replace({ - 'Doublet': args.doublet_str, - 'Negative': args.negative_str, - 'Discordant': 'discordant' - }) + classification = df_result[ + ["Barcode", "bff_raw", "bff_cluster", "consensuscall.global"] + ].rename(columns={"consensuscall.global": "bff_consensuscall"}) + else: + assignment = df_result[["Barcode"] + args.bff_methods] + classification = assignment.copy() - valid_values = {args.negative_str, args.doublet_str, 'discordant'} + valid_values = {args.singlet_str, args.negative_str, args.doublet_str} # Define classification function def classify_value(x): @@ -311,116 +337,142 @@ def classify_value(x): elif x in args.hash_list: return args.singlet_str else: - raise ValueError(f"Value '{x}' in BFF is not 'Negative', 'Doublet', or one of the hashes in the used hashes list") + raise ValueError( + f"Value '{x}' in BFF is not 'Negative', 'Doublet', or one of the hashes in the used hashes list" + ) - if len(args.bff_methods) == 3: - # use the classification of consensuscall.global - used_methods = args.bff_methods - ["bff_consensuscall"] + ["consensuscall.global"] - else: - used_methods = args.bff_methods + classification[args.bff_methods] = classification[args.bff_methods].applymap( + classify_value + ) - # apply classification only to used_methods columns - classification = assignment[['Barcode'] + used_methods].copy() - classification.rename(columns={'consensuscall.global': 'bff_consensuscall'}, inplace=True) + return assignment, classification - classification[used_methods] = classification[used_methods].applymap(classify_value) - return assignment, classification +# TODO if we keep saving AnnData/MuData in gene/hash_summary add AnnData to container for input type (https://github.com/theislab/hadge/issues/83) +# joins the assignment results with HTO, generate_anndata will return h5ad with HTO matrix +def saveAnnDataMuData( + args: Arguments, assignment_summary: pd.DataFrame, rna_data, hto_data +): + if args.generate_mudata or args.generate_anndata: + assignment_summary.set_index("Barcode", inplace=True) + hto_data.obs = hto_data.obs.join(assignment_summary, how="left").fillna( + args.negative_str + ) -def printProccedOutput() -> None: - # TODO add a function that shows and maybe checks processed results before joining - print("----- Assignments -----") - print("") + if args.generate_anndata: + hto_data.write(args.h5ad) - for assignment in assignments: - counts = assignment[assignment.columns[1]].value_counts() - length = len(assignment) - print(counts) - print("length: ", length) - print("") + if args.generate_mudata: + mudata = MuData({"rna": rna_data, "hto": hto_data}) + mudata.update() + mudata.write(args.h5mu) - print("----- Classifications -----") - print("") - for classification in classifications: - counts = classification[classification.columns[1]].value_counts() - length = len(classification) - print(counts) - print("length: ", length) - print("") +def print_method_item_counts(dfs): + """ + Takes the list of assignment/classification DataFrames (assignments/classifications) and prints a summary table: + method name | total count | count(item1) | count(item2) | ... + An item refers to the donor label in the assignment (HTO-1, HTO-2, ...) or the classification (singlet, doublet, negative). + """ + rows = [] + all_items = set() -if __name__ == "__main__": + # Extract items and their counts for every deconvolution method + for df in dfs: + print(df) - # ======================== process nextflow input arguments ======================== + method_name = df.columns[1] + counts = df[method_name].value_counts(dropna=False) + total = len(df) + all_items.update(counts.index) - args = Arguments() - # args.print_args() + row = {"method": method_name, "count_overall": total} + row.update(counts.to_dict()) + rows.append(row) + summary = pd.DataFrame(rows).fillna(0) - # ========================= process results from modules =========================== + # Convert all numeric values to int + for col in summary.columns: + if col != "method": + summary[col] = summary[col].astype(int) - rna_data = sc.read_10x_mtx(args.rna_matrix) - hto_data = sc.read_10x_mtx(args.hto_matrix, gex_only=False) + # Order columns + summary = summary[["method", "count_overall"] + sorted(list(all_items))] - # call all functions that process the module outptus + print(summary.to_string(index=False)) + + +if __name__ == "__main__": + # ======================== process nextflow input arguments ======================== + + args = Arguments() + + # ========================= process results from modules =========================== assignments = [] classifications = [] + # call all functions that process the module outputs functions = ProcessModuleOutput() function_names = list(functions.function_name_to_args_name.keys()) for function in function_names: - if getattr(args,functions.function_name_to_args_name.get(function)) is not None: - assignment, classification = getattr(functions,function)(args) + if ( + getattr(args, functions.function_name_to_args_name.get(function)) + is not None + ): + assignment, classification = getattr(functions, function)(args) assignments.append(assignment) classifications.append(classification) - # ================================== save results ================================== # ----------------------------------- save csv's ----------------------------------- - # TODO restructure the if statement if I keep using the hto_data - # have to to this because demuxem has more barcodes as output that it received as input - # https://github.com/lilab-bcb/demuxEM/issues/20 + rna_data = sc.read_10x_mtx(args.rna_matrix) + hto_data = sc.read_10x_mtx(args.hto_matrix, gex_only=False) - assignment_summary = pd.DataFrame(hto_data.obs_names, columns=['Barcode']) + # Need to use a left join — demuxEM outputs extra barcodes not present in the input. + # See https://github.com/lilab-bcb/demuxEM/issues/20 + + # Use hto_data.obs_names() as index to perform a left join + assignment_summary = pd.DataFrame(hto_data.obs_names, columns=["Barcode"]) classification_summary = assignment_summary.copy() for assignment in assignments: - assignment_summary = pd.merge(assignment_summary, assignment, on="Barcode", how="left").replace("", args.negative_str) - - assignment_summary.to_csv(args.assignment, index=False) + assignment_summary = pd.merge( + assignment_summary, assignment, on="Barcode", how="left" + ) for classification in classifications: - classification_summary = pd.merge(classification_summary, classification, on="Barcode", how="left") - - classification_summary.to_csv(args.classification, index=False) + classification_summary = pd.merge( + classification_summary, classification, on="Barcode", how="left" + ) - assignment_summary.set_index("Barcode", inplace=True) - print(assignment_summary) + # TODO update if demuxEM works (https://github.com/theislab/hadge/issues/81) + # .replace("", args.negative_str) + # maybe also in demuxem() + assignment_summary.fillna(args.negative_str).to_csv(args.assignment, index=False) + classification_summary.fillna(args.negative_str).to_csv( + args.classification, index=False + ) # -------------------------------- save mudata/anndata ----------------------------- - if args.generate_mudata or args.generate_anndata: - # join on index (Barcode) - rna_data.obs = rna_data.obs.join(assignment_summary, how="left") - # fill all empty of the used modules with negative values (for expression data) - used_modules = list(assignment_summary.columns) - for col in used_modules: - if pd.api.types.is_categorical_dtype(rna_data.obs[col]): - if args.negative_str not in rna_data.obs[col].cat.categories: - rna_data.obs[col] = rna_data.obs[col].cat.add_categories([args.negative_str]) - - rna_data.obs[used_modules] = rna_data.obs[used_modules].fillna(args.negative_str) - rna_data.obs[used_modules] = rna_data.obs[used_modules].astype(str) - - if args.generate_mudata: - # join on index (Barcode) and create a mudata object - hto_data.obs = hto_data.obs.join(assignment_summary, how="left") - mudata = MuData({"rna": rna_data, "hto": hto_data}) - # TODO mudata update? - mudata.write(args.h5mu) - - if args.generate_anndata: - rna_data.write(args.h5ad) + saveAnnDataMuData(args, assignment_summary, rna_data, hto_data) + + # -------------------------------------- versions ---------------------------------- + + versions = { + "${task.process}": { + "python": platform.python_version(), + "pandas": pd.__version__, + "scanpy": sc.__version__, + "numpy": np.__version__, + "mudata": md.__version__, + "pegasusio": io.__version__, + } + } + + with open("versions.yml", "w") as f: + yaml.dump(versions, f) diff --git a/modules/nf-core/csvtk/join/environment.yml b/modules/nf-core/csvtk/join/environment.yml new file mode 100644 index 00000000..47679f18 --- /dev/null +++ b/modules/nf-core/csvtk/join/environment.yml @@ -0,0 +1,8 @@ +--- +# yaml-language-server: $schema=https://raw.githubusercontent.com/nf-core/modules/master/modules/environment-schema.json +channels: + - bioconda + - conda-forge + +dependencies: + - bioconda::csvtk=0.31.0 diff --git a/modules/nf-core/csvtk/join/main.nf b/modules/nf-core/csvtk/join/main.nf new file mode 100644 index 00000000..0bd6b2a5 --- /dev/null +++ b/modules/nf-core/csvtk/join/main.nf @@ -0,0 +1,49 @@ +process CSVTK_JOIN { + tag "$meta.id" + label 'process_single' + + conda "${moduleDir}/environment.yml" + container "${ workflow.containerEngine == 'singularity' && !task.ext.singularity_pull_docker_container ? + 'https://depot.galaxyproject.org/singularity/csvtk:0.31.0--h9ee0642_0': + 'biocontainers/csvtk:0.31.0--h9ee0642_0' }" + + input: + tuple val(meta), path(csv) + + output: + tuple val(meta), path("${prefix}.${out_extension}"), emit: csv + path "versions.yml" , emit: versions + + when: + task.ext.when == null || task.ext.when + + script: + def args = task.ext.args ?: '' + prefix = task.ext.prefix ?: "${meta.id}" + out_extension = args.contains('--out-delimiter "\t"') || args.contains('-D "\t"') || args.contains("-D \$'\t'") ? "tsv" : "csv" + """ + csvtk \\ + join \\ + $args \\ + --num-cpus $task.cpus \\ + --out-file ${prefix}.${out_extension} \\ + $csv + + cat <<-END_VERSIONS > versions.yml + "${task.process}": + csvtk: \$(echo \$( csvtk version | sed -e "s/csvtk v//g" )) + END_VERSIONS + """ + + stub: + prefix = task.ext.prefix ?: "${meta.id}" + out_extension = args.contains('--out-delimiter "\t"') || args.contains('-D "\t"') || args.contains("-D \$'\t'") ? "tsv" : "csv" + """ + touch ${prefix}.${out_extension} + + cat <<-END_VERSIONS > versions.yml + "${task.process}": + csvtk: \$(echo \$( csvtk version | sed -e "s/csvtk v//g" )) + END_VERSIONS + """ +} diff --git a/modules/nf-core/csvtk/join/meta.yml b/modules/nf-core/csvtk/join/meta.yml new file mode 100644 index 00000000..ca86aca1 --- /dev/null +++ b/modules/nf-core/csvtk/join/meta.yml @@ -0,0 +1,53 @@ +name: csvtk_join +description: Join two or more CSV (or TSV) tables by selected fields into a single + table +keywords: + - join + - tsv + - csv +tools: + - csvtk: + description: A cross-platform, efficient, practical CSV/TSV toolkit + homepage: http://bioinf.shenwei.me/csvtk + documentation: http://bioinf.shenwei.me/csvtk + tool_dev_url: https://github.com/shenwei356/csvtk + licence: ["MIT"] + identifier: "" +input: + - - meta: + type: map + description: | + Groovy Map containing sample information + e.g. [ id:'test', single_end:false ] + - csv: + type: file + description: CSV/TSV formatted files + pattern: "*.{csv,tsv}" + ontologies: + - edam: http://edamontology.org/format_3752 # CSV + - edam: http://edamontology.org/format_3475 # TSV +output: + csv: + - - meta: + type: map + description: | + Groovy Map containing sample information + e.g. [ id:'test', single_end:false ] + - ${prefix}.${out_extension}: + type: file + description: Joined CSV/TSV file + pattern: "*.{csv,tsv}" + ontologies: + - edam: http://edamontology.org/format_3752 # CSV + - edam: http://edamontology.org/format_3475 # TSV + versions: + - versions.yml: + type: file + description: File containing software versions + pattern: "version.yml" + ontologies: + - edam: http://edamontology.org/format_3750 # YAML +authors: + - "@anoronh4" +maintainers: + - "@anoronh4" diff --git a/modules/nf-core/csvtk/join/tests/main.nf.test b/modules/nf-core/csvtk/join/tests/main.nf.test new file mode 100644 index 00000000..3cf178c4 --- /dev/null +++ b/modules/nf-core/csvtk/join/tests/main.nf.test @@ -0,0 +1,64 @@ +nextflow_process { + + name "Test Process CSVTK_JOIN" + script "../main.nf" + process "CSVTK_JOIN" + + tag "modules" + tag "modules_nfcore" + tag "csvtk" + tag "csvtk/join" + + test("join - csv") { + + when { + process { + """ + input[0] = [ + [ id:'test' ], // meta map + [ + file("https://github.com/nf-core/test-datasets/raw/bacass/bacass_hybrid.csv", checkIfExists: true), + file("https://github.com/nf-core/test-datasets/raw/bacass/bacass_short.csv", checkIfExists: true), + ] + ] + """ + } + } + + then { + assertAll( + { assert process.success }, + { assert snapshot(process.out).match() } + ) + } + + } + + test("join - csv - stub") { + + options "-stub" + + when { + process { + """ + input[0] = [ + [ id:'test' ], // meta map + [ + file("https://github.com/nf-core/test-datasets/raw/bacass/bacass_hybrid.csv", checkIfExists: true), + file("https://github.com/nf-core/test-datasets/raw/bacass/bacass_short.csv", checkIfExists: true), + ] + ] + """ + } + } + + then { + assertAll( + { assert process.success }, + { assert snapshot(process.out).match() } + ) + } + + } + +} diff --git a/modules/nf-core/csvtk/join/tests/main.nf.test.snap b/modules/nf-core/csvtk/join/tests/main.nf.test.snap new file mode 100644 index 00000000..8ba7b861 --- /dev/null +++ b/modules/nf-core/csvtk/join/tests/main.nf.test.snap @@ -0,0 +1,68 @@ +{ + "join - csv": { + "content": [ + { + "0": [ + [ + { + "id": "test" + }, + "test.csv:md5,d0ad82ca096c7e05eb9f9a04194c9e30" + ] + ], + "1": [ + "versions.yml:md5,b80d80628bb39bba336cff32fe502aac" + ], + "csv": [ + [ + { + "id": "test" + }, + "test.csv:md5,d0ad82ca096c7e05eb9f9a04194c9e30" + ] + ], + "versions": [ + "versions.yml:md5,b80d80628bb39bba336cff32fe502aac" + ] + } + ], + "meta": { + "nf-test": "0.9.2", + "nextflow": "24.10.3" + }, + "timestamp": "2025-01-02T06:18:42.09571517" + }, + "join - csv - stub": { + "content": [ + { + "0": [ + [ + { + "id": "test" + }, + "test.csv:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "1": [ + "versions.yml:md5,b80d80628bb39bba336cff32fe502aac" + ], + "csv": [ + [ + { + "id": "test" + }, + "test.csv:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "versions": [ + "versions.yml:md5,b80d80628bb39bba336cff32fe502aac" + ] + } + ], + "meta": { + "nf-test": "0.9.2", + "nextflow": "24.10.3" + }, + "timestamp": "2025-01-02T06:19:00.2453934" + } +} \ No newline at end of file diff --git a/modules/nf-core/csvtk/join/tests/nextflow.config b/modules/nf-core/csvtk/join/tests/nextflow.config new file mode 100644 index 00000000..1b14393a --- /dev/null +++ b/modules/nf-core/csvtk/join/tests/nextflow.config @@ -0,0 +1,5 @@ +process { + withName: CSVTK_JOIN { + ext.args = "--fields 'ID;ID' -p -e -d \"\t\" -D \",\"" + } +} diff --git a/modules/nf-core/souporcell/environment.yml b/modules/nf-core/souporcell/environment.yml new file mode 100644 index 00000000..e39a8a2d --- /dev/null +++ b/modules/nf-core/souporcell/environment.yml @@ -0,0 +1,8 @@ +--- +# yaml-language-server: $schema=https://raw.githubusercontent.com/nf-core/modules/master/modules/environment-schema.json +channels: + - conda-forge + - bioconda +dependencies: + - bioconda::souporcell=2.5 + - conda-forge::gxx=9.5.0 diff --git a/modules/nf-core/souporcell/main.nf b/modules/nf-core/souporcell/main.nf new file mode 100644 index 00000000..8c990d98 --- /dev/null +++ b/modules/nf-core/souporcell/main.nf @@ -0,0 +1,60 @@ +process SOUPORCELL { + tag "$meta.id" + label 'process_medium' + + conda "${moduleDir}/environment.yml" + container "${ workflow.containerEngine == 'singularity' && !task.ext.singularity_pull_docker_container ? + 'https://community-cr-prod.seqera.io/docker/registry/v2/blobs/sha256/9a/9a69c552c52aa5b3636a7a596f9406b2ec3e165809ccd58a012b9ea285ba6ecd/data' : + 'community.wave.seqera.io/library/souporcell_gxx:f648658dde2cdd53' }" + + input: + tuple val(meta), path(bam), path(barcodes), val(clusters) + tuple val(meta2), path(fasta) + + output: + tuple val(meta), path("*/clusters.tsv") , emit: clusters + tuple val(meta), path("*/cluster_genotypes.vcf"), emit: vcf + tuple val(meta), path("*/ambient_rna.txt") , emit: ambient_rna + path "versions.yml" , emit: versions + + when: + task.ext.when == null || task.ext.when + + script: + def prefix = task.ext.prefix ?: "${meta.id}" + def args = task.ext.args ?: "" + def VERSION = '2.5' // WARN: Version information not provided by tool on CLI. Please update this string when bumping container versions. (See this issue: https://github.com/wheaton5/souporcell/issues/262) + """ + mkdir -p temp + export TMPDIR=./temp + souporcell_pipeline.py \\ + -i $bam \\ + -b $barcodes \\ + -f $fasta \\ + -t $task.cpus \\ + -o $prefix \\ + -k $clusters \\ + $args + + cat <<-END_VERSIONS > versions.yml + "${task.process}": + souporcell: $VERSION + END_VERSIONS + """ + + stub: + def prefix = task.ext.prefix ?: "${meta.id}" + def VERSION = '2.5' // WARN: Version information not provided by tool on CLI. Please update this string when bumping container versions. (See this issue: https://github.com/wheaton5/souporcell/issues/262) + """ + mkdir -p ${prefix} + + touch ${prefix}/clusters.tsv + touch ${prefix}/cluster_genotypes.vcf + touch ${prefix}/ambient_rna.txt + + cat <<-END_VERSIONS > versions.yml + "${task.process}": + souporcell: $VERSION + END_VERSIONS + """ +} diff --git a/modules/nf-core/souporcell/meta.yml b/modules/nf-core/souporcell/meta.yml new file mode 100644 index 00000000..7f7619c3 --- /dev/null +++ b/modules/nf-core/souporcell/meta.yml @@ -0,0 +1,109 @@ +# yaml-language-server: $schema=https://raw.githubusercontent.com/nf-core/modules/master/modules/meta-schema.json +name: "souporcell" +description: souporcell is a method for clustering mixed-genotype scRNAseq + experiments by individual. +keywords: + - clustering + - mixed-genotype + - genomics +tools: + - "souporcell": + description: "Clustering scRNAseq by genotypes." + homepage: "https://github.com/wheaton5/souporcell" + documentation: "https://demultiplexing-doublet-detecting-docs.readthedocs.io/en/latest/Souporcell.html" + tool_dev_url: "https://github.com/wheaton5/souporcell" + doi: "10.1101/699637v1" + licence: ["MIT"] + identifier: biotools:souporcell + +input: + - - meta: + type: map + description: | + Groovy Map containing sample information + e.g. `[ id:'sample1' ]` + - bam: + type: file + description: A BAM file from cellranger containing single-cell RNA-seq + alignments. + pattern: "*.bam" + ontologies: + - edam: http://edamontology.org/format_2572 # BAM + - barcodes: + type: file + description: A barcode or whitelist TSV file from cellranger identifying + individual cell barcodes. + pattern: "*.tsv" + ontologies: + - edam: http://edamontology.org/format_3475 # TSV + - clusters: + type: integer + description: Number of clusters. + + - - meta2: + type: map + description: | + Groovy Map containing sample information + e.g. `[ id:'sample1' ]` + + - fasta: + type: file + description: A reference fasta file. + pattern: "*.fasta" + ontologies: + - edam: http://edamontology.org/format_1929 # FASTA + +output: + clusters: + - - meta: + type: map + description: | + Groovy Map containing sample information + e.g. `[ id:'sample1' ]` + - "*/clusters.tsv": + type: file + description: TSV file listing cell barcodes with singlet/doublet status, assigned cluster, and per-cluster log-loss metrics + pattern: "clusters.tsv" + ontologies: + - edam: http://edamontology.org/format_3475 # TSV + + vcf: + - - meta: + type: map + description: | + Groovy Map containing sample information + e.g. `[ id:'sample1' ]` + - "*/cluster_genotypes.vcf": + type: file + description: A `vcf` with genotypes for each cluster for each variant in the input `vcf` from freebayes. + pattern: "cluster_genotypes.vcf" + ontologies: + - edam: http://edamontology.org/format_3016 # VCF + + ambient_rna: + - - meta: + type: map + description: | + Groovy Map containing sample information + e.g. `[ id:'sample1' ]` + - "*/ambient_rna.txt": + type: file + description: Contains the ambient RNA percentage detected + pattern: "ambient_rna.txt" + ontologies: + - edam: http://edamontology.org/format_2330 # Plain text + + versions: + - versions.yml: + type: file + description: File containing software versions + pattern: "versions.yml" + ontologies: + - edam: http://edamontology.org/format_3750 # YAML + +authors: + - "@seohyonkim" + - "@LuisHeinzlmeier" +maintainers: + - "@seohyonkim" + - "@LuisHeinzlmeier" diff --git a/modules/nf-core/souporcell/tests/main.nf.test b/modules/nf-core/souporcell/tests/main.nf.test new file mode 100644 index 00000000..8177af02 --- /dev/null +++ b/modules/nf-core/souporcell/tests/main.nf.test @@ -0,0 +1,139 @@ +// nf-core modules test souporcell +nextflow_process { + + name "Test Process SOUPORCELL" + script "../main.nf" + process "SOUPORCELL" + + tag "modules" + tag "modules_nfcore" + tag "souporcell" + + def souporcell_ploidy = 2 + def souporcell_min_alt = 10 + def souporcell_min_ref = 10 + def souporcell_max_loci = 2048 + def souporcell_restarts = 80 // default would be 100 + def souporcell_common_variants = null + def souporcell_known_genotypes = null + def souporcell_known_genotypes_sample_names = null + def souporcell_skip_remap = false + def souporcell_ignore = false + + test("homo sapiens - bam and barcodes") { + when { + process { + """ + input[0] = [ + [ id:'test', single_end:false ], // meta map + file(params.modules_testdata_base_path + 'genomics/homo_sapiens/demultiplexing/chr21.bam', checkIfExists: true), + file(params.modules_testdata_base_path + 'genomics/homo_sapiens/demultiplexing/barcodes.tsv', checkIfExists: true), + 2 + ] + + input[1] = [ + [:], + file(params.modules_testdata_base_path + 'genomics/homo_sapiens/genome/chr21/sequence/genome.fasta', checkIfExists: true) + ] + """ + } + } + + then { + assertAll( + { assert process.success }, + { assert snapshot( + process.out.tsv, + process.out.versions, + path(process.out.versions[0]).yaml, + path(process.out.vcf[0][1]).vcf.sampleCount == 69 + ).match() } + ) + } + } + + test("homo sapiens - bam and barcodes - ext.args") { + + config "./nextflow.config" + + when { + params{ + module_args = [ + "-p", + souporcell_ploidy, + "--min_alt", + souporcell_min_alt, + "--min_ref", + souporcell_min_ref, + "--max_loci", + souporcell_max_loci, + "--restarts", + souporcell_restarts, + (souporcell_common_variants ? "--common_variants ${souporcell_common_variants}" : ""), + (souporcell_known_genotypes ? "--known_genotypes ${souporcell_known_genotypes}" : ""), + (souporcell_known_genotypes_sample_names ? "--known_genotypes_sample_names ${souporcell_known_genotypes_sample_names}" : ""), + (souporcell_skip_remap ? "--skip_remap" : ""), + (souporcell_ignore ? "--ignore" : "") + ].findAll { it != "" }.join(" ") + } + process { + """ + input[0] = [ + [ id:'test', single_end:false ], // meta map + file(params.modules_testdata_base_path + 'genomics/homo_sapiens/demultiplexing/chr21.bam', checkIfExists: true), + file(params.modules_testdata_base_path + 'genomics/homo_sapiens/demultiplexing/barcodes.tsv', checkIfExists: true), + 2 + ] + + input[1] = [ + [:], + file(params.modules_testdata_base_path + 'genomics/homo_sapiens/genome/chr21/sequence/genome.fasta', checkIfExists: true) + ] + """ + } + } + + then { + assertAll( + { assert process.success }, + { assert snapshot( + process.out.tsv, + process.out.versions, + path(process.out.versions[0]).yaml, + path(process.out.vcf[0][1]).vcf.sampleCount == 69 + ).match() } + ) + } + } + + + test("homo sapience - bam and barcodes - stub") { + + options "-stub" + + when { + process { + """ + input[0] = [ + [ id:'test', single_end:false ], // meta map + file(params.modules_testdata_base_path + 'genomics/homo_sapiens/demultiplexing/chr21.bam', checkIfExists: true), + file(params.modules_testdata_base_path + 'genomics/homo_sapiens/demultiplexing/barcodes.tsv', checkIfExists: true), + 2 + ] + + input[1] = [ + [:], + file(params.modules_testdata_base_path + 'genomics/homo_sapiens/genome/chr21/sequence/genome.fasta', checkIfExists: true) + ] + """ + } + } + + then { + assertAll( + { assert process.success }, + { assert snapshot(process.out).match() }, + ) + } + } +} diff --git a/modules/nf-core/souporcell/tests/main.nf.test.snap b/modules/nf-core/souporcell/tests/main.nf.test.snap new file mode 100644 index 00000000..78d0201a --- /dev/null +++ b/modules/nf-core/souporcell/tests/main.nf.test.snap @@ -0,0 +1,111 @@ +{ + "homo sapiens - bam and barcodes - ext.args": { + "content": [ + null, + [ + "versions.yml:md5,f337670d0ed16cb68e4609c40ae11fdb" + ], + { + "SOUPORCELL": { + "souporcell": 2.5 + } + }, + false + ], + "meta": { + "nf-test": "0.9.2", + "nextflow": "25.04.3" + }, + "timestamp": "2025-11-12T09:06:11.001555" + }, + "homo sapience - bam and barcodes - stub": { + "content": [ + { + "0": [ + [ + { + "id": "test", + "single_end": false + }, + "clusters.tsv:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "1": [ + [ + { + "id": "test", + "single_end": false + }, + "cluster_genotypes.vcf:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "2": [ + [ + { + "id": "test", + "single_end": false + }, + "ambient_rna.txt:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "3": [ + "versions.yml:md5,f337670d0ed16cb68e4609c40ae11fdb" + ], + "ambient_rna": [ + [ + { + "id": "test", + "single_end": false + }, + "ambient_rna.txt:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "clusters": [ + [ + { + "id": "test", + "single_end": false + }, + "clusters.tsv:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "vcf": [ + [ + { + "id": "test", + "single_end": false + }, + "cluster_genotypes.vcf:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "versions": [ + "versions.yml:md5,f337670d0ed16cb68e4609c40ae11fdb" + ] + } + ], + "meta": { + "nf-test": "0.9.2", + "nextflow": "25.04.3" + }, + "timestamp": "2025-11-11T22:51:43.492074" + }, + "homo sapiens - bam and barcodes": { + "content": [ + null, + [ + "versions.yml:md5,f337670d0ed16cb68e4609c40ae11fdb" + ], + { + "SOUPORCELL": { + "souporcell": 2.5 + } + }, + false + ], + "meta": { + "nf-test": "0.9.2", + "nextflow": "25.04.3" + }, + "timestamp": "2025-11-12T09:03:53.134935" + } +} \ No newline at end of file diff --git a/modules/nf-core/souporcell/tests/nextflow.config b/modules/nf-core/souporcell/tests/nextflow.config new file mode 100644 index 00000000..8fdf637a --- /dev/null +++ b/modules/nf-core/souporcell/tests/nextflow.config @@ -0,0 +1,5 @@ +process { + withName: "SOUPORCELL" { + ext.args = params.module_args + } +} diff --git a/nextflow.config b/nextflow.config index 1c762c6f..27043272 100644 --- a/nextflow.config +++ b/nextflow.config @@ -9,32 +9,49 @@ // Global default params, used in configs params { - // TODO nf-core: Specify your pipeline's command line flags - // Input options + // ============================ general ============================= + // ---------------------- main input options ------------------------ input = null mode = 'rescue' - match_donor = false + hash_tools = 'gmm-demux' genetic_tools = 'vireo' bam_qc = true common_variants = null save_intermediates = false - // References + // -------------------------- donor match --------------------------- + match_donor = true + + // first inputs + vireo_parent_dir = null + demultiplexing_result = null + cell_genotype = null + + // second inputs + match_donor_method1 = null + match_donor_method2 = null + findVariants = false + variant_count = 10 + variant_pct = 0.9 + + // ----------------------- Reference genomes ------------------------ genome = null igenomes_base = 's3://ngi-igenomes/igenomes/' igenomes_ignore = false - // ======================== hasing paramters ======================== + // ======================== hasing paramters ======================== // -------------------------- hash summary -------------------------- generate_anndata = true generate_mudata = true // ------------------------------ bff ------------------------------- // inputs - bff_methods = 'RAW' - bff_preprocessing = true + // TODO CLUSTER is not working + bff_methods = 'COMBINED' + // TODO true won't show results after joining because (see: https://github.com/theislab/hadge/issues/76) + bff_preprocessing = false // ext.args for preprocessing bff_barcodeWhitelist = null @@ -159,9 +176,22 @@ params { hashsolo_number_of_noise_barcodes = null hashsolo_round_digits = 10 - // ======================== genetic paramters ======================== - // CellSNP + // --------------------------- souporcell ---------------------------- + // ext.args + souporcell_ploidy = 2 + souporcell_min_alt = 10 + souporcell_min_ref = 10 + souporcell_max_loci = 2048 + souporcell_restarts = 100 + souporcell_common_variants = null + souporcell_known_genotypes = null + souporcell_known_genotypes_sample_names = null + souporcell_skip_remap = false + souporcell_ignore = false + + // ---------------------------- cellsnp ------------------------------ + // ext.args cellsnp_celltag = 'CB' cellsnp_umitag = 'Auto' cellsnp_mincount = 20 @@ -173,7 +203,8 @@ params { cellsnp_maxdepth = 0 cellsnp_countorphan = false - // Vireo + // ----------------------------- vireo ------------------------------- + // ext.args vireo_genotag = 'GT' vireo_no_doublet = false vireo_n_init = 50 @@ -186,7 +217,8 @@ params { vireo_cell_range = 'all' vireo_cell_ambient_rnas = false - // DSC-Pileup + // ------------- DSC-Pileup for freemuxlet and demuxlet -------------- + // ext.args dsc_pileup_tag_group = 'CB' dsc_pileup_tag_umi = 'UB' dsc_pileup_cap_bq = 40 @@ -198,7 +230,8 @@ params { dsc_pileup_min_uniq = 0 dsc_pileup_min_snp = 0 - // Demuxlet + // ---------------------------- demuxlet ----------------------------- + // ext.args demuxlet_field = 'GT' demuxlet_geno_error_offset = 0.1 demuxlet_geno_error_coeff = 0.0 @@ -208,7 +241,8 @@ params { demuxlet_alpha = '0.1,0.2,0.3,0.4,0.5' demuxlet_doublet_prior = 0.5 - // Freemuxlet + // --------------------------- freemuxlet ---------------------------- + // ext.args freemuxlet_doublet_prior = 0.5 freemuxlet_geno_error = 0.1 freemuxlet_bf_thres = 5.41 @@ -218,6 +252,9 @@ params { freemuxlet_randomize_singlet_score = false freemuxlet_seed = 0 + + // ======================== other parameters ========================= + // MultiQC options multiqc_config = null multiqc_title = null diff --git a/nextflow_schema.json b/nextflow_schema.json index 8ef8b151..3d1e66ab 100644 --- a/nextflow_schema.json +++ b/nextflow_schema.json @@ -90,6 +90,73 @@ } } }, + "donor_match_options": { + "title": "Donor match options", + "type": "object", + "fa_icon": "fas fa-user-check", + "description": "Options specific to donor matching functionality.", + "properties": { + "vireo_parent_dir": { + "type": ["string", "null"], + "format": "directory-path", + "description": "A parent folder which contains the output folder of vireo in the format of vireo_[taskID/sampleId] generated by hadge pipeline only when running in donor_match mode. In other modes, the input is passed by the pipeline automatically.", + "fa_icon": "fas fa-folder-open", + "default": null + }, + "demultiplexing_result": { + "type": ["string", "null"], + "format": "file-path", + "exists": true, + "mimetype": "text/csv", + "pattern": "^\\S+\\.csv$", + "description": "A CSV file with demultiplexing assignment only when running in donor_match mode. In other modes, the input is passed by the pipeline automatically.", + "fa_icon": "fas fa-file-csv", + "default": null + }, + "cell_genotype": { + "type": ["string", "null"], + "format": "file-path", + "exists": true, + "pattern": "^\\S+\\.vcf(\\.gz)?$", + "description": "The path to the VCF file containing the genotype of the cells.", + "fa_icon": "fas fa-dna", + "default": null + }, + "match_donor_method1": { + "type": ["string", "null"], + "description": "The method name to match donors. If null all genotype-based methods are compared.", + "fa_icon": "fas fa-user-check", + "default": null + }, + "match_donor_method2": { + "type": ["string", "null"], + "description": "The method name to match donors. If null, all hashing-based methods are compared.", + "fa_icon": "fas fa-user-check", + "default": null + }, + "findVariants": { + "type": "boolean", + "description": "Whether to extract a subset of informative variants when best genotype-based method for donor matching is vireo. default: subset as described in paper; vireo: subset by Vireo; True: subset using both methods; False: not extracting variants. Default: false", + "fa_icon": "fas fa-filter", + "default": false + }, + "variant_count": { + "type": "integer", + "description": "The threshold for the minimal read depth of a variant in the cell group when subseting the informative variants by default.", + "fa_icon": "fas fa-sort-numeric-up", + "minimum": 0, + "default": 10 + }, + "variant_pct": { + "type": "number", + "description": "The threshold for the minimal frequency of the alternative or reference allele to determine the dominant allele of a variant in the cell group when subseting the informative variants by default.", + "fa_icon": "fas fa-percentage", + "minimum": 0, + "maximum": 1, + "default": 0.9 + } + } + }, "reference_genome_options": { "title": "Reference genome options", "type": "object", @@ -109,7 +176,7 @@ "mimetype": "text/plain", "pattern": "^\\S+\\.fn?a(sta)?(\\.gz)?$", "description": "Path to FASTA genome file.", - "help_text": "This parameter is *mandatory* if `--genome` is not specified. If you don't have a BWA index available this will be generated for you automatically. Combine with `--save_reference` to save BWA index for future runs.", + "help_text": "This parameter is *mandatory* if `--genome` is not specified, and is generally recommended to speed up execution. If omitted, the pipeline will download the entire iGenomes reference dataset specified with '--genome', resulting in long download times and high bandwidth usage. If you don't have a BWA index available this will be generated for you automatically. Combine with `--save_reference` to save BWA index for future runs.", "fa_icon": "far fa-file-code" }, "igenomes_ignore": { @@ -193,13 +260,13 @@ "bff_methods": { "type": "string", "description": "Method(s) to use within BFF.", - "default": "RAW", + "default": "COMBINED", "fa_icon": "fas fa-list" }, "bff_preprocessing": { "type": "boolean", "description": "Whether to run preprocessing steps for BFF.", - "default": true, + "default": false, "fa_icon": "fas fa-cogs" }, "bff_barcodeWhitelist": { @@ -1162,6 +1229,85 @@ } } }, + "souporcell_options": { + "title": "Souporcell options", + "type": "object", + "fa_icon": "fas fa-users", + "description": "Options specific to the Souporcell tool for clustering mixed-genotype scRNAseq experiments by individual.", + "properties": { + "souporcell_ploidy": { + "type": "integer", + "description": "Ploidy, must be 1 or 2.", + "default": 2, + "enum": [1, 2], + "fa_icon": "fas fa-dna" + }, + "souporcell_min_alt": { + "type": "integer", + "description": "Min alt to use locus.", + "default": 10, + "minimum": 0, + "fa_icon": "fas fa-arrow-down" + }, + "souporcell_min_ref": { + "type": "integer", + "description": "Min ref to use locus.", + "default": 10, + "minimum": 0, + "fa_icon": "fas fa-arrow-down" + }, + "souporcell_max_loci": { + "type": "integer", + "description": "Max loci per cell, affects speed.", + "default": 2048, + "minimum": 0, + "fa_icon": "fas fa-arrow-up" + }, + "souporcell_restarts": { + "type": "integer", + "description": "Number of restarts in clustering, when there are > 12 clusters we recommend increasing this to avoid local minima.", + "default": 100, + "minimum": 0, + "fa_icon": "fas fa-redo" + }, + "souporcell_common_variants": { + "type": "string", + "format": "file-path", + "exists": true, + "pattern": "^\\S+\\.vcf(\\.gz)?$", + "description": "Common variant loci or known variant loci vcf, must be vs same reference fasta.", + "default": null, + "fa_icon": "fas fa-file-code" + }, + "souporcell_known_genotypes": { + "type": "string", + "format": "file-path", + "exists": true, + "pattern": "^\\S+\\.vcf$", + "description": "Known variants per clone in population vcf mode, must be .vcf right now we dont accept gzip or bcf sorry.", + "default": null, + "fa_icon": "fas fa-dna" + }, + "souporcell_known_genotypes_sample_names": { + "type": "string", + "description": "Which samples in population vcf from known genotypes option represent the donors in your sample. Provide space-separated sample names for multiple donors.", + "default": null, + "fa_icon": "fas fa-tag" + }, + "souporcell_skip_remap": { + "type": "boolean", + "description": "Don't remap with minimap2 (not recommended unless in conjunction with --common_variants).", + "default": false, + "fa_icon": "fas fa-skip-forward" + }, + "souporcell_ignore": { + "type": "boolean", + "description": "Set to True to ignore data error assertions.", + "default": false, + "fa_icon": "fas fa-exclamation-triangle" + } + } + }, "institutional_config_options": { "title": "Institutional config options", "type": "object", @@ -1327,6 +1473,9 @@ { "$ref": "#/$defs/reference_genome_options" }, + { + "$ref": "#/$defs/donor_match_options" + }, { "$ref": "#/$defs/demuxem_options" }, @@ -1372,6 +1521,9 @@ { "$ref": "#/$defs/freemuxlet_options" }, + { + "$ref": "#/$defs/souporcell_options" + }, { "$ref": "#/$defs/institutional_config_options" }, diff --git a/subworkflows/local/donor_matching/main.nf b/subworkflows/local/donor_matching/main.nf deleted file mode 100644 index c8c77762..00000000 --- a/subworkflows/local/donor_matching/main.nf +++ /dev/null @@ -1,8 +0,0 @@ -workflow DONOR_MATCHING { - main: - - ch_versions = Channel.empty() - - emit: - versions = ch_versions // channel: [ versions.yml ] -} diff --git a/subworkflows/local/genetic_demultiplexing/main.nf b/subworkflows/local/genetic_demultiplexing/main.nf index c02b0efe..31ccbe6d 100644 --- a/subworkflows/local/genetic_demultiplexing/main.nf +++ b/subworkflows/local/genetic_demultiplexing/main.nf @@ -6,6 +6,8 @@ include { VIREO } from '../../../modules/nf-core/vireo' include { POPSCLE_DSCPILEUP } from '../../../modules/nf-core/popscle/dscpileup' include { POPSCLE_DEMUXLET } from '../../../modules/nf-core/popscle/demuxlet' include { POPSCLE_FREEMUXLET } from '../../../modules/nf-core/popscle/freemuxlet' +include { SOUPORCELL } from '../../../modules/nf-core/souporcell' +include { GENE_SUMMARY } from '../../../modules/local/gene_summary' workflow GENETIC_DEMULTIPLEXING { take: @@ -13,10 +15,23 @@ workflow GENETIC_DEMULTIPLEXING { methods // list of strings bam_qc // boolean common_variants // file + fasta // file: /path/to/genome.fasta main: - ch_versions = Channel.empty() + ch_vireo = Channel.empty() + ch_demuxlet = Channel.empty() + ch_freemuxlet = Channel.empty() + ch_souporcell = Channel.empty() + ch_cellsnp = Channel.empty() + + ch_summary = ch_samplesheet.map{ meta, rna, hto, _bam, barcodes, _vcf -> + [meta, rna, hto, barcodes] + } + + ch_samplesheet = ch_samplesheet.map{ meta, _rna, _hto, bam, barcodes, vcf -> + [meta, bam, barcodes, vcf] + } if (bam_qc) { BAM_QC(ch_samplesheet.map { meta, bam, _barcodes, _vcf -> [meta, bam] }) @@ -45,7 +60,6 @@ workflow GENETIC_DEMULTIPLEXING { .map { meta, _bam, barcodes, vcf, new_bam -> [meta, new_bam, barcodes, vcf] } } - if (methods.contains('vireo')) { SAMTOOLS_INDEX(ch_samplesheet.map { meta, bam, _barcodes, _vcf -> [meta, bam] }) ch_versions = ch_versions.mix(SAMTOOLS_INDEX.out.versions) @@ -53,34 +67,72 @@ workflow GENETIC_DEMULTIPLEXING { CELLSNP_MODEA( ch_samplesheet.join(SAMTOOLS_INDEX.out.bai).map { meta, bam, barcodes, vcf, bai -> [meta, bam, bai, vcf, barcodes] } ) + + ch_cellsnp = ch_cellsnp.mix(CELLSNP_MODEA.out.cell) ch_versions = ch_versions.mix(CELLSNP_MODEA.out.versions) VIREO( ch_samplesheet.join(CELLSNP_MODEA.out.cell).map { meta, _bam, _barcodes, vcf, cell -> [meta, cell, meta.n_samples, vcf, []] } ) + ch_vireo = ch_vireo.mix(VIREO.out.donor_ids) ch_versions = ch_versions.mix(VIREO.out.versions) + + } + if (methods.contains('demuxlet') || methods.contains('freemuxlet')) { ch_dscpileup = ch_samplesheet.map { meta, bam, _barcodes, vcf -> [meta, bam, vcf] } POPSCLE_DSCPILEUP(ch_dscpileup) ch_versions = ch_versions.mix(POPSCLE_DSCPILEUP.out.versions) if (methods.contains('demuxlet')) { - ch_demuxlet = POPSCLE_DSCPILEUP.out.plp.join(ch_samplesheet).map { meta, plp, bam, _barcodes, vcf -> [meta, plp, bam, vcf] } - POPSCLE_DEMUXLET(ch_demuxlet) + ch_demuxlet_input = POPSCLE_DSCPILEUP.out.plp.join(ch_samplesheet).map { meta, plp, bam, _barcodes, vcf -> [meta, plp, bam, vcf] } + POPSCLE_DEMUXLET(ch_demuxlet_input) + ch_demuxlet = ch_demuxlet.mix(POPSCLE_DEMUXLET.out.demuxlet_result) ch_versions = ch_versions.mix(POPSCLE_DEMUXLET.out.versions) + } if (methods.contains('freemuxlet')) { - ch_freemuxlet = POPSCLE_DSCPILEUP.out.directory.join(ch_samplesheet).map { meta, plp_dir, _bam, _barcodes, _vcf -> [meta, plp_dir, meta.n_samples] } - POPSCLE_FREEMUXLET(ch_freemuxlet) + ch_freemuxlet_input = POPSCLE_DSCPILEUP.out.directory.join(ch_samplesheet).map { meta, plp_dir, _bam, _barcodes, _vcf -> [meta, plp_dir, meta.n_samples] } + POPSCLE_FREEMUXLET(ch_freemuxlet_input) + ch_freemuxlet = ch_freemuxlet.mix(POPSCLE_FREEMUXLET.out.result) ch_versions = ch_versions.mix(POPSCLE_FREEMUXLET.out.versions) } } if (methods.contains('souporcell')) { - error("Souporcell not implemented") + + ch_souporcell_bam_barcodes_clusters = ch_samplesheet.map { meta, bam, barcodes, _vcf -> + [ meta, bam, barcodes, meta.n_samples ] + } + + SOUPORCELL( + ch_souporcell_bam_barcodes_clusters, + channel.value([[id: 'fasta'], file(fasta, checkIfExists: true)]) + ) + + ch_souporcell = ch_souporcell.mix(SOUPORCELL.out.clusters) + ch_versions = ch_versions.mix(SOUPORCELL.out.versions) } + ch_summary = ch_summary + .join(ch_vireo, remainder: true) + .join(ch_demuxlet, remainder: true) + .join(ch_freemuxlet, remainder: true) + .join(ch_souporcell, remainder: true) + .map { tuple -> tuple.collect { it == null ? [] : it } } + + GENE_SUMMARY( + ch_summary, + tuple(params.generate_anndata, params.generate_mudata) + ) + + ch_versions = ch_versions.mix(GENE_SUMMARY.out.versions) + + emit: + summary_assignment = GENE_SUMMARY.out.assignment + summary_classification = GENE_SUMMARY.out.classification + cell_genotype = ch_cellsnp versions = ch_versions // channel: [ versions.yml ] } diff --git a/subworkflows/local/hash_demultiplexing/main.nf b/subworkflows/local/hash_demultiplexing/main.nf index b77e7f56..b028d1e6 100644 --- a/subworkflows/local/hash_demultiplexing/main.nf +++ b/subworkflows/local/hash_demultiplexing/main.nf @@ -1,16 +1,11 @@ -include { UNTAR as UNTAR_RNA } from '../../../modules/nf-core/untar' -include { UNTAR as UNTAR_HTO } from '../../../modules/nf-core/untar' -include { RENAME_GENES_TO_FEATURES as RENAME_GENES_TO_FEATURES_RNA } from '../../../modules/local/rename_genes_to_features' -include { RENAME_GENES_TO_FEATURES as RENAME_GENES_TO_FEATURES_HTO } from '../../../modules/local/rename_genes_to_features' -include { DROPLETUTILS_MTXCONVERT as MTXCONVERT_RNA } from '../../../modules/local/dropletutils/mtxconvert' -include { DROPLETUTILS_MTXCONVERT as MTXCONVERT_HTO } from '../../../modules/local/dropletutils/mtxconvert' -include { EXTRACT_HASHES } from '../../../modules/local/extract_hashes' include { PREPROCESSING_FOR_HTODEMUX_MULTISEQ } from '../../../modules/local/preprocessing_for_htodemux_multiseq' include { HTODEMUX } from '../../../modules/nf-core/htodemux' include { HTODEMUX_VISUALIZATION } from '../../../modules/local/htodemux_visualization' include { MULTISEQDEMUX } from '../../../modules/nf-core/multiseqdemux' include { BFF } from '../../../modules/nf-core/bff' include { DEMUXEM } from '../../../modules/nf-core/demuxem' +include { DROPLETUTILS_MTXCONVERT as MTXCONVERT_RNA } from '../../../modules/local/dropletutils/mtxconvert/main' +include { DROPLETUTILS_MTXCONVERT as MTXCONVERT_HTO } from '../../../modules/local/dropletutils/mtxconvert/main' include { GMMDEMUX } from '../../../modules/nf-core/gmmdemux' include { SCANPY_HASHSOLO as HASHSOLO } from '../../../modules/nf-core/scanpy/hashsolo' include { HASHEDDROPS } from '../../../modules/nf-core/hasheddrops' @@ -24,8 +19,6 @@ workflow HASH_DEMULTIPLEXING { main: - ch_results = Channel.empty() - ch_versions = Channel.empty() ch_htodemux_assignments = Channel.empty() @@ -50,36 +43,6 @@ workflow HASH_DEMULTIPLEXING { } } - ch_rna = ch_samplesheet.map { meta, rna, _hto -> [meta, rna] } - .branch { _meta, rna -> - tar: rna.endsWith('.tar.gz') - directory: true - } - ch_hto = ch_samplesheet.map { meta, _rna, hto -> [meta, hto] } - .branch { _meta, hto -> - tar: hto.endsWith('.tar.gz') - directory: true - } - - UNTAR_RNA(ch_rna.tar) - ch_versions = ch_versions.mix(UNTAR_RNA.out.versions) - - UNTAR_HTO(ch_hto.tar) - ch_versions = ch_versions.mix(UNTAR_HTO.out.versions) - - ch_rna = ch_rna.directory.mix(UNTAR_RNA.out.untar) - ch_hto = ch_hto.directory.mix(UNTAR_HTO.out.untar) - - ch_rna = RENAME_GENES_TO_FEATURES_RNA(ch_rna) - ch_hto = RENAME_GENES_TO_FEATURES_HTO(ch_hto) - ch_hashes = EXTRACT_HASHES(ch_hto.map { meta, hto -> [meta, "${hto}/features.tsv.gz"] }) - - ch_samplesheet = ch_samplesheet.map { meta, _rna, _hto -> [meta] } - .join(ch_rna) - .join(ch_hto) - .join(ch_hashes) - .map {meta, rna, hto, hashes -> [meta+[hashes: file(hashes).text.trim()], rna, hto] } - if (methods.contains('htodemux') || methods.contains('multiseq')) { PREPROCESSING_FOR_HTODEMUX_MULTISEQ( ch_samplesheet @@ -101,9 +64,6 @@ workflow HASH_DEMULTIPLEXING { [meta, [result: classification, method: 'htodemux_classification']] } - ch_results = ch_results - .mix(ch_assignments,ch_classifications) - ch_htodemux_assignments = ch_htodemux_assignments.mix(HTODEMUX.out.assignment) ch_htodemux_classifications = ch_htodemux_classifications.mix(HTODEMUX.out.classification) @@ -227,6 +187,10 @@ workflow HASH_DEMULTIPLEXING { tuple(params.generate_anndata, params.generate_mudata, params.bff_methods) ) + ch_versions = ch_versions.mix(HASH_SUMMARY.out.versions) + emit: + summary_assignment = HASH_SUMMARY.out.assignment + summary_classification = HASH_SUMMARY.out.classification versions = ch_versions // channel: [ versions.yml ] } diff --git a/tests/.nftignore b/tests/.nftignore index ebb7349c..e429e8a3 100644 --- a/tests/.nftignore +++ b/tests/.nftignore @@ -14,3 +14,6 @@ hashing/gmm-demux/*/GMM_full.csv hashing/htodemux/*/*_htodemux.rds hashing/multiseqdemux/*/*_multiseqdemux.rds hashing/preprocessing/*/*_preprocessed.rds +genetic/popscle/freemuxlet/*/*.clust1.vcf.gz +genetic/souporcell/*/*/ambient_rna.txt +genetic/souporcell/*/*/cluster_genotypes.vcf diff --git a/tests/default.nf.test.snap b/tests/default.nf.test.snap index 5dbc76f9..6f904f31 100644 --- a/tests/default.nf.test.snap +++ b/tests/default.nf.test.snap @@ -1,19 +1,25 @@ { "-profile test": { "content": [ - 82, { - "BFF": { - "r-base": "4.3.3", - "r-seurat": "4.3.0.1", - "cellhashR": "1.0.3" - }, "CELLSNP_MODEA": { "cellsnp": "1.2.3" }, - "DEMUXEM": "echo 0.1.7.post1", - "GMMDEMUX": { - "GMM-Demux": "0.2.2.3" + "DONOR_MATCH": { + "r-base": "4.5.1", + "r-complexupset": "1.3.3", + "r-data.table": "1.17.8", + "r-pheatmap": "1.0.13", + "r-tidyverse": "2.0.0", + "r-vcfr": "1.15.0" + }, + "GENE_SUMMARY": { + "mudata": "0.3.1", + "numpy": "1.24.2", + "pandas": "2.3.1", + "pegasusio": "0.10.0", + "python": "3.11.14", + "scanpy": "1.11.2" }, "HASHEDDROPS": { "r-base": "4.4.3", @@ -26,26 +32,21 @@ "python": "3.12.11", "scanpy": "1.11.2" }, - "HTODEMUX_VISUALIZATION": { - "r-base": "4.4.3", - "r-seurat": "5.3.0", - "r-ggplot2": "3.5.2" - }, - "MTXCONVERT_HTO": { - "r-base": "4.4.3", - "bioconductor-dropletutils": "1.26.0" + "HASH_SUMMARY": { + "mudata": "0.3.1", + "numpy": "1.24.2", + "pandas": "2.3.1", + "pegasusio": "0.10.0", + "python": "3.11.14", + "scanpy": "1.11.2" }, - "MTXCONVERT_RNA": { - "r-base": "4.4.3", - "bioconductor-dropletutils": "1.26.0" + "JOIN_RESULTS": { + "csvtk": "0.31.0" }, "MULTISEQDEMUX": { "r-base": "4.4.3", "r-seurat": "5.3.0" }, - "POPSCLE_DEMUXLET": { - "popscle demuxlet": 0.1 - }, "POPSCLE_DSCPILEUP": { "popscle dsc-pileup": 0.1 }, @@ -65,6 +66,9 @@ "SAMTOOLS_VIEW": { "samtools": 1.21 }, + "SOUPORCELL": { + "souporcell": 2.5 + }, "UMITOOLS_DEDUP": { "umitools": "1.1.5" }, @@ -82,29 +86,136 @@ } }, [ - "bff", - "bff/test1_assignment_bff.csv", - "bff/test1_metrics_bff.csv", - "bff/test1_params_bff.csv", - "bff/test2_assignment_bff.csv", - "bff/test2_metrics_bff.csv", - "bff/test2_params_bff.csv", - "bff/test3_assignment_bff.csv", - "bff/test3_metrics_bff.csv", - "bff/test3_params_bff.csv", + "donor_match", + "donor_match/test1", + "donor_match/test1/freemuxlet_vs_hasheddrops", + "donor_match/test1/freemuxlet_vs_hasheddrops/test1_freemuxlet_vs_hasheddrops_all_assignment_after_match.csv", + "donor_match/test1/freemuxlet_vs_hasheddrops/test1_freemuxlet_vs_hasheddrops_concordance_heatmap.png", + "donor_match/test1/freemuxlet_vs_hasheddrops/test1_freemuxlet_vs_hasheddrops_correlation_res.csv", + "donor_match/test1/freemuxlet_vs_hasheddrops/test1_freemuxlet_vs_hasheddrops_donor_match.csv", + "donor_match/test1/freemuxlet_vs_hasheddrops/test1_freemuxlet_vs_hasheddrops_intersect_assignment_after_match.csv", + "donor_match/test1/freemuxlet_vs_hashsolo", + "donor_match/test1/freemuxlet_vs_hashsolo/test1_freemuxlet_vs_hashsolo_all_assignment_after_match.csv", + "donor_match/test1/freemuxlet_vs_hashsolo/test1_freemuxlet_vs_hashsolo_concordance_heatmap.png", + "donor_match/test1/freemuxlet_vs_hashsolo/test1_freemuxlet_vs_hashsolo_correlation_res.csv", + "donor_match/test1/freemuxlet_vs_hashsolo/test1_freemuxlet_vs_hashsolo_donor_match.csv", + "donor_match/test1/freemuxlet_vs_hashsolo/test1_freemuxlet_vs_hashsolo_intersect_assignment_after_match.csv", + "donor_match/test1/freemuxlet_vs_multiseq", + "donor_match/test1/freemuxlet_vs_multiseq/test1_freemuxlet_vs_multiseq_all_assignment_after_match.csv", + "donor_match/test1/freemuxlet_vs_multiseq/test1_freemuxlet_vs_multiseq_concordance_heatmap.png", + "donor_match/test1/freemuxlet_vs_multiseq/test1_freemuxlet_vs_multiseq_correlation_res.csv", + "donor_match/test1/freemuxlet_vs_multiseq/test1_freemuxlet_vs_multiseq_donor_match.csv", + "donor_match/test1/freemuxlet_vs_multiseq/test1_freemuxlet_vs_multiseq_intersect_assignment_after_match.csv", + "donor_match/test1/souporcell_vs_hasheddrops", + "donor_match/test1/souporcell_vs_hasheddrops/test1_souporcell_vs_hasheddrops_all_assignment_after_match.csv", + "donor_match/test1/souporcell_vs_hasheddrops/test1_souporcell_vs_hasheddrops_concordance_heatmap.png", + "donor_match/test1/souporcell_vs_hasheddrops/test1_souporcell_vs_hasheddrops_correlation_res.csv", + "donor_match/test1/souporcell_vs_hasheddrops/test1_souporcell_vs_hasheddrops_donor_match.csv", + "donor_match/test1/souporcell_vs_hasheddrops/test1_souporcell_vs_hasheddrops_intersect_assignment_after_match.csv", + "donor_match/test1/souporcell_vs_hashsolo", + "donor_match/test1/souporcell_vs_hashsolo/test1_souporcell_vs_hashsolo_all_assignment_after_match.csv", + "donor_match/test1/souporcell_vs_hashsolo/test1_souporcell_vs_hashsolo_concordance_heatmap.png", + "donor_match/test1/souporcell_vs_hashsolo/test1_souporcell_vs_hashsolo_correlation_res.csv", + "donor_match/test1/souporcell_vs_hashsolo/test1_souporcell_vs_hashsolo_donor_match.csv", + "donor_match/test1/souporcell_vs_hashsolo/test1_souporcell_vs_hashsolo_intersect_assignment_after_match.csv", + "donor_match/test1/souporcell_vs_multiseq", + "donor_match/test1/souporcell_vs_multiseq/test1_souporcell_vs_multiseq_all_assignment_after_match.csv", + "donor_match/test1/souporcell_vs_multiseq/test1_souporcell_vs_multiseq_concordance_heatmap.png", + "donor_match/test1/souporcell_vs_multiseq/test1_souporcell_vs_multiseq_correlation_res.csv", + "donor_match/test1/souporcell_vs_multiseq/test1_souporcell_vs_multiseq_donor_match.csv", + "donor_match/test1/souporcell_vs_multiseq/test1_souporcell_vs_multiseq_intersect_assignment_after_match.csv", + "donor_match/test1/test1_best_all_assignment_after_match.csv", + "donor_match/test1/test1_best_donor_match.csv", + "donor_match/test1/test1_best_intersect_assignment_after_match.csv", + "donor_match/test1/test1_score_record.csv", + "donor_match/test2", + "donor_match/test2/freemuxlet_vs_hasheddrops", + "donor_match/test2/freemuxlet_vs_hasheddrops/test2_freemuxlet_vs_hasheddrops_all_assignment_after_match.csv", + "donor_match/test2/freemuxlet_vs_hasheddrops/test2_freemuxlet_vs_hasheddrops_concordance_heatmap.png", + "donor_match/test2/freemuxlet_vs_hasheddrops/test2_freemuxlet_vs_hasheddrops_correlation_res.csv", + "donor_match/test2/freemuxlet_vs_hasheddrops/test2_freemuxlet_vs_hasheddrops_donor_match.csv", + "donor_match/test2/freemuxlet_vs_hasheddrops/test2_freemuxlet_vs_hasheddrops_intersect_assignment_after_match.csv", + "donor_match/test2/freemuxlet_vs_hashsolo", + "donor_match/test2/freemuxlet_vs_hashsolo/test2_freemuxlet_vs_hashsolo_all_assignment_after_match.csv", + "donor_match/test2/freemuxlet_vs_hashsolo/test2_freemuxlet_vs_hashsolo_concordance_heatmap.png", + "donor_match/test2/freemuxlet_vs_hashsolo/test2_freemuxlet_vs_hashsolo_correlation_res.csv", + "donor_match/test2/freemuxlet_vs_hashsolo/test2_freemuxlet_vs_hashsolo_donor_match.csv", + "donor_match/test2/freemuxlet_vs_hashsolo/test2_freemuxlet_vs_hashsolo_intersect_assignment_after_match.csv", + "donor_match/test2/freemuxlet_vs_multiseq", + "donor_match/test2/freemuxlet_vs_multiseq/test2_freemuxlet_vs_multiseq_all_assignment_after_match.csv", + "donor_match/test2/freemuxlet_vs_multiseq/test2_freemuxlet_vs_multiseq_concordance_heatmap.png", + "donor_match/test2/freemuxlet_vs_multiseq/test2_freemuxlet_vs_multiseq_correlation_res.csv", + "donor_match/test2/freemuxlet_vs_multiseq/test2_freemuxlet_vs_multiseq_donor_match.csv", + "donor_match/test2/freemuxlet_vs_multiseq/test2_freemuxlet_vs_multiseq_intersect_assignment_after_match.csv", + "donor_match/test2/souporcell_vs_hasheddrops", + "donor_match/test2/souporcell_vs_hasheddrops/test2_souporcell_vs_hasheddrops_all_assignment_after_match.csv", + "donor_match/test2/souporcell_vs_hasheddrops/test2_souporcell_vs_hasheddrops_concordance_heatmap.png", + "donor_match/test2/souporcell_vs_hasheddrops/test2_souporcell_vs_hasheddrops_correlation_res.csv", + "donor_match/test2/souporcell_vs_hasheddrops/test2_souporcell_vs_hasheddrops_donor_match.csv", + "donor_match/test2/souporcell_vs_hasheddrops/test2_souporcell_vs_hasheddrops_intersect_assignment_after_match.csv", + "donor_match/test2/souporcell_vs_hashsolo", + "donor_match/test2/souporcell_vs_hashsolo/test2_souporcell_vs_hashsolo_all_assignment_after_match.csv", + "donor_match/test2/souporcell_vs_hashsolo/test2_souporcell_vs_hashsolo_concordance_heatmap.png", + "donor_match/test2/souporcell_vs_hashsolo/test2_souporcell_vs_hashsolo_correlation_res.csv", + "donor_match/test2/souporcell_vs_hashsolo/test2_souporcell_vs_hashsolo_donor_match.csv", + "donor_match/test2/souporcell_vs_hashsolo/test2_souporcell_vs_hashsolo_intersect_assignment_after_match.csv", + "donor_match/test2/souporcell_vs_multiseq", + "donor_match/test2/souporcell_vs_multiseq/test2_souporcell_vs_multiseq_all_assignment_after_match.csv", + "donor_match/test2/souporcell_vs_multiseq/test2_souporcell_vs_multiseq_concordance_heatmap.png", + "donor_match/test2/souporcell_vs_multiseq/test2_souporcell_vs_multiseq_correlation_res.csv", + "donor_match/test2/souporcell_vs_multiseq/test2_souporcell_vs_multiseq_donor_match.csv", + "donor_match/test2/souporcell_vs_multiseq/test2_souporcell_vs_multiseq_intersect_assignment_after_match.csv", + "donor_match/test2/test2_best_all_assignment_after_match.csv", + "donor_match/test2/test2_best_donor_match.csv", + "donor_match/test2/test2_best_intersect_assignment_after_match.csv", + "donor_match/test2/test2_score_record.csv", + "donor_match/test3", + "donor_match/test3/freemuxlet_vs_hasheddrops", + "donor_match/test3/freemuxlet_vs_hasheddrops/test3_freemuxlet_vs_hasheddrops_all_assignment_after_match.csv", + "donor_match/test3/freemuxlet_vs_hasheddrops/test3_freemuxlet_vs_hasheddrops_concordance_heatmap.png", + "donor_match/test3/freemuxlet_vs_hasheddrops/test3_freemuxlet_vs_hasheddrops_correlation_res.csv", + "donor_match/test3/freemuxlet_vs_hasheddrops/test3_freemuxlet_vs_hasheddrops_donor_match.csv", + "donor_match/test3/freemuxlet_vs_hasheddrops/test3_freemuxlet_vs_hasheddrops_intersect_assignment_after_match.csv", + "donor_match/test3/freemuxlet_vs_hashsolo", + "donor_match/test3/freemuxlet_vs_hashsolo/test3_freemuxlet_vs_hashsolo_all_assignment_after_match.csv", + "donor_match/test3/freemuxlet_vs_hashsolo/test3_freemuxlet_vs_hashsolo_concordance_heatmap.png", + "donor_match/test3/freemuxlet_vs_hashsolo/test3_freemuxlet_vs_hashsolo_correlation_res.csv", + "donor_match/test3/freemuxlet_vs_hashsolo/test3_freemuxlet_vs_hashsolo_donor_match.csv", + "donor_match/test3/freemuxlet_vs_hashsolo/test3_freemuxlet_vs_hashsolo_intersect_assignment_after_match.csv", + "donor_match/test3/freemuxlet_vs_multiseq", + "donor_match/test3/freemuxlet_vs_multiseq/test3_freemuxlet_vs_multiseq_all_assignment_after_match.csv", + "donor_match/test3/freemuxlet_vs_multiseq/test3_freemuxlet_vs_multiseq_concordance_heatmap.png", + "donor_match/test3/freemuxlet_vs_multiseq/test3_freemuxlet_vs_multiseq_correlation_res.csv", + "donor_match/test3/freemuxlet_vs_multiseq/test3_freemuxlet_vs_multiseq_donor_match.csv", + "donor_match/test3/freemuxlet_vs_multiseq/test3_freemuxlet_vs_multiseq_intersect_assignment_after_match.csv", + "donor_match/test3/souporcell_vs_hasheddrops", + "donor_match/test3/souporcell_vs_hasheddrops/test3_souporcell_vs_hasheddrops_all_assignment_after_match.csv", + "donor_match/test3/souporcell_vs_hasheddrops/test3_souporcell_vs_hasheddrops_concordance_heatmap.png", + "donor_match/test3/souporcell_vs_hasheddrops/test3_souporcell_vs_hasheddrops_correlation_res.csv", + "donor_match/test3/souporcell_vs_hasheddrops/test3_souporcell_vs_hasheddrops_donor_match.csv", + "donor_match/test3/souporcell_vs_hasheddrops/test3_souporcell_vs_hasheddrops_intersect_assignment_after_match.csv", + "donor_match/test3/souporcell_vs_hashsolo", + "donor_match/test3/souporcell_vs_hashsolo/test3_souporcell_vs_hashsolo_all_assignment_after_match.csv", + "donor_match/test3/souporcell_vs_hashsolo/test3_souporcell_vs_hashsolo_concordance_heatmap.png", + "donor_match/test3/souporcell_vs_hashsolo/test3_souporcell_vs_hashsolo_correlation_res.csv", + "donor_match/test3/souporcell_vs_hashsolo/test3_souporcell_vs_hashsolo_donor_match.csv", + "donor_match/test3/souporcell_vs_hashsolo/test3_souporcell_vs_hashsolo_intersect_assignment_after_match.csv", + "donor_match/test3/souporcell_vs_multiseq", + "donor_match/test3/souporcell_vs_multiseq/test3_souporcell_vs_multiseq_all_assignment_after_match.csv", + "donor_match/test3/souporcell_vs_multiseq/test3_souporcell_vs_multiseq_concordance_heatmap.png", + "donor_match/test3/souporcell_vs_multiseq/test3_souporcell_vs_multiseq_correlation_res.csv", + "donor_match/test3/souporcell_vs_multiseq/test3_souporcell_vs_multiseq_donor_match.csv", + "donor_match/test3/souporcell_vs_multiseq/test3_souporcell_vs_multiseq_intersect_assignment_after_match.csv", + "donor_match/test3/test3_best_all_assignment_after_match.csv", + "donor_match/test3/test3_best_donor_match.csv", + "donor_match/test3/test3_best_intersect_assignment_after_match.csv", + "donor_match/test3/test3_score_record.csv", "extract", "extract/test1_hashes.txt", "extract/test2_hashes.txt", "extract/test3_hashes.txt", "genetic", "genetic/popscle", - "genetic/popscle/demuxlet", - "genetic/popscle/demuxlet/test1", - "genetic/popscle/demuxlet/test1/test1.best", - "genetic/popscle/demuxlet/test2", - "genetic/popscle/demuxlet/test2/test2.best", - "genetic/popscle/demuxlet/test3", - "genetic/popscle/demuxlet/test3/test3.best", "genetic/popscle/freemuxlet", "genetic/popscle/freemuxlet/test1", "genetic/popscle/freemuxlet/test1/test1.clust1.samples.gz", @@ -118,6 +229,38 @@ "genetic/popscle/freemuxlet/test3/test3.clust1.samples.gz", "genetic/popscle/freemuxlet/test3/test3.clust1.vcf.gz", "genetic/popscle/freemuxlet/test3/test3.lmix", + "genetic/souporcell", + "genetic/souporcell/test1", + "genetic/souporcell/test1/test1", + "genetic/souporcell/test1/test1/ambient_rna.txt", + "genetic/souporcell/test1/test1/cluster_genotypes.vcf", + "genetic/souporcell/test1/test1/clusters.tsv", + "genetic/souporcell/test2", + "genetic/souporcell/test2/test2", + "genetic/souporcell/test2/test2/ambient_rna.txt", + "genetic/souporcell/test2/test2/cluster_genotypes.vcf", + "genetic/souporcell/test2/test2/clusters.tsv", + "genetic/souporcell/test3", + "genetic/souporcell/test3/test3", + "genetic/souporcell/test3/test3/ambient_rna.txt", + "genetic/souporcell/test3/test3/cluster_genotypes.vcf", + "genetic/souporcell/test3/test3/clusters.tsv", + "genetic/summary", + "genetic/summary/test1", + "genetic/summary/test1/test1_genetic_summary.h5ad", + "genetic/summary/test1/test1_genetic_summary.h5mu", + "genetic/summary/test1/test1_genetic_summary_assignment.csv", + "genetic/summary/test1/test1_genetic_summary_classification.csv", + "genetic/summary/test2", + "genetic/summary/test2/test2_genetic_summary.h5ad", + "genetic/summary/test2/test2_genetic_summary.h5mu", + "genetic/summary/test2/test2_genetic_summary_assignment.csv", + "genetic/summary/test2/test2_genetic_summary_classification.csv", + "genetic/summary/test3", + "genetic/summary/test3/test3_genetic_summary.h5ad", + "genetic/summary/test3/test3_genetic_summary.h5mu", + "genetic/summary/test3/test3_genetic_summary_assignment.csv", + "genetic/summary/test3/test3_genetic_summary_classification.csv", "genetic/vireo", "genetic/vireo/test1", "genetic/vireo/test1/test1.base.vcf.gz", @@ -152,48 +295,14 @@ "genetic/vireo/test3/test3_prob_doublet.tsv.gz", "genetic/vireo/test3/test3_prob_singlet.tsv.gz", "genetic/vireo/test3/test3_summary.tsv", + "hash_and_gene_summary", + "hash_and_gene_summary/test1", + "hash_and_gene_summary/test1/test1.csv", + "hash_and_gene_summary/test2", + "hash_and_gene_summary/test2/test2.csv", + "hash_and_gene_summary/test3", + "hash_and_gene_summary/test3/test3.csv", "hashing", - "hashing/demuxem", - "hashing/demuxem/test1", - "hashing/demuxem/test1/test1.ambient_hashtag.hist.pdf", - "hashing/demuxem/test1/test1.background_probabilities.bar.pdf", - "hashing/demuxem/test1/test1.out.demuxEM.zarr.zip", - "hashing/demuxem/test1/test1.real_content.hist.pdf", - "hashing/demuxem/test1/test1.rna_demux.hist.pdf", - "hashing/demuxem/test1/test1_demux.zarr.zip", - "hashing/demuxem/test2", - "hashing/demuxem/test2/test2.ambient_hashtag.hist.pdf", - "hashing/demuxem/test2/test2.background_probabilities.bar.pdf", - "hashing/demuxem/test2/test2.out.demuxEM.zarr.zip", - "hashing/demuxem/test2/test2.real_content.hist.pdf", - "hashing/demuxem/test2/test2.rna_demux.hist.pdf", - "hashing/demuxem/test2/test2_demux.zarr.zip", - "hashing/demuxem/test3", - "hashing/demuxem/test3/test3.ambient_hashtag.hist.pdf", - "hashing/demuxem/test3/test3.background_probabilities.bar.pdf", - "hashing/demuxem/test3/test3.out.demuxEM.zarr.zip", - "hashing/demuxem/test3/test3.real_content.hist.pdf", - "hashing/demuxem/test3/test3.rna_demux.hist.pdf", - "hashing/demuxem/test3/test3_demux.zarr.zip", - "hashing/gmm-demux", - "hashing/gmm-demux/test1", - "hashing/gmm-demux/test1/GMM_full.config", - "hashing/gmm-demux/test1/GMM_full.csv", - "hashing/gmm-demux/test1/barcodes.tsv.gz", - "hashing/gmm-demux/test1/features.tsv.gz", - "hashing/gmm-demux/test1/matrix.mtx.gz", - "hashing/gmm-demux/test2", - "hashing/gmm-demux/test2/GMM_full.config", - "hashing/gmm-demux/test2/GMM_full.csv", - "hashing/gmm-demux/test2/barcodes.tsv.gz", - "hashing/gmm-demux/test2/features.tsv.gz", - "hashing/gmm-demux/test2/matrix.mtx.gz", - "hashing/gmm-demux/test3", - "hashing/gmm-demux/test3/GMM_full.config", - "hashing/gmm-demux/test3/GMM_full.csv", - "hashing/gmm-demux/test3/barcodes.tsv.gz", - "hashing/gmm-demux/test3/features.tsv.gz", - "hashing/gmm-demux/test3/matrix.mtx.gz", "hashing/hasheddrops", "hashing/hasheddrops/test1", "hashing/hasheddrops/test1/test1_emptyDrops.csv", @@ -235,44 +344,6 @@ "hashing/hashsolo/test3/test3_assignment_hashsolo.csv", "hashing/hashsolo/test3/test3_hashsolo.h5ad", "hashing/hashsolo/test3/test3_params_hashsolo.csv", - "hashing/htodemux", - "hashing/htodemux/test1", - "hashing/htodemux/test1/test1_assignment_htodemux.csv", - "hashing/htodemux/test1/test1_classification_htodemux.csv", - "hashing/htodemux/test1/test1_htodemux.rds", - "hashing/htodemux/test1/test1_params_htodemux.csv", - "hashing/htodemux/test2", - "hashing/htodemux/test2/test2_assignment_htodemux.csv", - "hashing/htodemux/test2/test2_classification_htodemux.csv", - "hashing/htodemux/test2/test2_htodemux.rds", - "hashing/htodemux/test2/test2_params_htodemux.csv", - "hashing/htodemux/test3", - "hashing/htodemux/test3/test3_assignment_htodemux.csv", - "hashing/htodemux/test3/test3_classification_htodemux.csv", - "hashing/htodemux/test3/test3_htodemux.rds", - "hashing/htodemux/test3/test3_params_htodemux.csv", - "hashing/htodemux_visualization", - "hashing/htodemux_visualization/test1", - "hashing/htodemux_visualization/test1/test1_featureScatter_htodemux.jpeg", - "hashing/htodemux_visualization/test1/test1_heatMap_htodemux.jpeg", - "hashing/htodemux_visualization/test1/test1_ridge_htodemux.jpeg", - "hashing/htodemux_visualization/test1/test1_tSNE_htodemux.jpeg", - "hashing/htodemux_visualization/test1/test1_violinPlot_htodemux.jpeg", - "hashing/htodemux_visualization/test1/test1_visual_params_htodemux.csv", - "hashing/htodemux_visualization/test2", - "hashing/htodemux_visualization/test2/test2_featureScatter_htodemux.jpeg", - "hashing/htodemux_visualization/test2/test2_heatMap_htodemux.jpeg", - "hashing/htodemux_visualization/test2/test2_ridge_htodemux.jpeg", - "hashing/htodemux_visualization/test2/test2_tSNE_htodemux.jpeg", - "hashing/htodemux_visualization/test2/test2_violinPlot_htodemux.jpeg", - "hashing/htodemux_visualization/test2/test2_visual_params_htodemux.csv", - "hashing/htodemux_visualization/test3", - "hashing/htodemux_visualization/test3/test3_featureScatter_htodemux.jpeg", - "hashing/htodemux_visualization/test3/test3_heatMap_htodemux.jpeg", - "hashing/htodemux_visualization/test3/test3_ridge_htodemux.jpeg", - "hashing/htodemux_visualization/test3/test3_tSNE_htodemux.jpeg", - "hashing/htodemux_visualization/test3/test3_violinPlot_htodemux.jpeg", - "hashing/htodemux_visualization/test3/test3_visual_params_htodemux.csv", "hashing/multiseqdemux", "hashing/multiseqdemux/test1", "hashing/multiseqdemux/test1/test1_multiseqdemux.rds", @@ -314,8 +385,9 @@ "hashing/summary/test3/test3_hashing_summary_classification.csv", "multiqc", "multiqc/multiqc_data", - "multiqc/multiqc_data/BETA-multiqc.parquet", + "multiqc/multiqc_data/llms-full.txt", "multiqc/multiqc_data/multiqc.log", + "multiqc/multiqc_data/multiqc.parquet", "multiqc/multiqc_data/multiqc_citations.txt", "multiqc/multiqc_data/multiqc_data.json", "multiqc/multiqc_data/multiqc_software_versions.txt", @@ -323,70 +395,159 @@ "multiqc/multiqc_report.html", "pipeline_info", "pipeline_info/nf_core_hadge_software_mqc_versions.yml", - "rename", - "rename/test1_hto", - "rename/test1_hto/barcodes.tsv.gz", - "rename/test1_hto/features.tsv.gz", - "rename/test1_hto/matrix.mtx.gz", - "rename/test1_rna", - "rename/test1_rna/barcodes.tsv.gz", - "rename/test1_rna/features.tsv.gz", - "rename/test1_rna/matrix.mtx.gz", - "rename/test2_hto", - "rename/test2_hto/barcodes.tsv.gz", - "rename/test2_hto/features.tsv.gz", - "rename/test2_hto/matrix.mtx.gz", - "rename/test2_rna", - "rename/test2_rna/barcodes.tsv.gz", - "rename/test2_rna/features.tsv.gz", - "rename/test2_rna/matrix.mtx.gz", - "rename/test3_hto", - "rename/test3_hto/barcodes.tsv.gz", - "rename/test3_hto/features.tsv.gz", - "rename/test3_hto/matrix.mtx.gz", - "rename/test3_rna", - "rename/test3_rna/barcodes.tsv.gz", - "rename/test3_rna/features.tsv.gz", - "rename/test3_rna/matrix.mtx.gz", "untar", - "untar/test1", - "untar/test1/barcodes.tsv.gz", - "untar/test1/features.tsv.gz", - "untar/test1/matrix.mtx.gz", - "untar/test2", - "untar/test2/barcodes.tsv.gz", - "untar/test2/features.tsv.gz", - "untar/test2/matrix.mtx.gz", - "untar/test3", - "untar/test3/barcodes.tsv.gz", - "untar/test3/features.tsv.gz", - "untar/test3/matrix.mtx.gz" + "untar/test1_hto", + "untar/test1_hto/barcodes.tsv.gz", + "untar/test1_hto/features.tsv.gz", + "untar/test1_hto/matrix.mtx.gz", + "untar/test1_rna", + "untar/test1_rna/barcodes.tsv.gz", + "untar/test1_rna/features.tsv.gz", + "untar/test1_rna/matrix.mtx.gz", + "untar/test2_hto", + "untar/test2_hto/barcodes.tsv.gz", + "untar/test2_hto/features.tsv.gz", + "untar/test2_hto/matrix.mtx.gz", + "untar/test2_rna", + "untar/test2_rna/barcodes.tsv.gz", + "untar/test2_rna/features.tsv.gz", + "untar/test2_rna/matrix.mtx.gz", + "untar/test3_hto", + "untar/test3_hto/barcodes.tsv.gz", + "untar/test3_hto/features.tsv.gz", + "untar/test3_hto/matrix.mtx.gz", + "untar/test3_rna", + "untar/test3_rna/barcodes.tsv.gz", + "untar/test3_rna/features.tsv.gz", + "untar/test3_rna/matrix.mtx.gz" ], [ - "test1_assignment_bff.csv:md5,3cc0f2c5bc6f2834eeb82696c6d71c9d", - "test1_metrics_bff.csv:md5,14354e1e722db19e4dd9bbaabcce7ca5", - "test1_params_bff.csv:md5,042c4e0ec5f5337048401e79a6057795", - "test2_assignment_bff.csv:md5,3cc0f2c5bc6f2834eeb82696c6d71c9d", - "test2_metrics_bff.csv:md5,14354e1e722db19e4dd9bbaabcce7ca5", - "test2_params_bff.csv:md5,e15f93ceef80a233a0b21069f4dc8463", - "test3_assignment_bff.csv:md5,3cc0f2c5bc6f2834eeb82696c6d71c9d", - "test3_metrics_bff.csv:md5,14354e1e722db19e4dd9bbaabcce7ca5", - "test3_params_bff.csv:md5,abfa8d5115fa563bfab13325943c5a71", - "test1_hashes.txt:md5,395e7c801af4ad59b5995d2c7533b3e1", - "test2_hashes.txt:md5,395e7c801af4ad59b5995d2c7533b3e1", - "test3_hashes.txt:md5,395e7c801af4ad59b5995d2c7533b3e1", - "test1.best:md5,1afa27ec59d7ea6b37e1b9ffbc6007e8", - "test2.best:md5,1afa27ec59d7ea6b37e1b9ffbc6007e8", - "test3.best:md5,1afa27ec59d7ea6b37e1b9ffbc6007e8", + "test1_freemuxlet_vs_hasheddrops_all_assignment_after_match.csv:md5,05e486189b9c3bbfa381f17191ba5b9d", + "test1_freemuxlet_vs_hasheddrops_concordance_heatmap.png:md5,666ff7d8b8bb12d055ea6fdb081cb714", + "test1_freemuxlet_vs_hasheddrops_correlation_res.csv:md5,b0915774342ca64fe9077505a0b98a5f", + "test1_freemuxlet_vs_hasheddrops_donor_match.csv:md5,2d6da63e3801454389610977089c027f", + "test1_freemuxlet_vs_hasheddrops_intersect_assignment_after_match.csv:md5,c9ee6eed6505ae762f80e15e02f3a0c7", + "test1_freemuxlet_vs_hashsolo_all_assignment_after_match.csv:md5,90d108f1f5686f0c1a0f1d63ee84996c", + "test1_freemuxlet_vs_hashsolo_concordance_heatmap.png:md5,57a72e1a6a34fb333be8cf8d343c8b21", + "test1_freemuxlet_vs_hashsolo_correlation_res.csv:md5,07726a0bdac892ee79bc40b71780c187", + "test1_freemuxlet_vs_hashsolo_donor_match.csv:md5,2d6da63e3801454389610977089c027f", + "test1_freemuxlet_vs_hashsolo_intersect_assignment_after_match.csv:md5,9b0c416e27572acc4c8ba6b97480ca86", + "test1_freemuxlet_vs_multiseq_all_assignment_after_match.csv:md5,ff848ce7685a658315ad2af8f2d39c4b", + "test1_freemuxlet_vs_multiseq_concordance_heatmap.png:md5,64bc44fb9aeeb1581ebbf708c8765b4d", + "test1_freemuxlet_vs_multiseq_correlation_res.csv:md5,8a690fde26636e0e4c44b41a156ae47a", + "test1_freemuxlet_vs_multiseq_donor_match.csv:md5,b92352374bf4b32f7e3a1b032a4c8b5f", + "test1_freemuxlet_vs_multiseq_intersect_assignment_after_match.csv:md5,7a178c3e2f08aa338d4bb6e9f1e2cb44", + "test1_souporcell_vs_hasheddrops_all_assignment_after_match.csv:md5,a705ae7739121396c7c92999abbddd39", + "test1_souporcell_vs_hasheddrops_concordance_heatmap.png:md5,53fcd062b4a8e63cebb1af6e41a59336", + "test1_souporcell_vs_hasheddrops_correlation_res.csv:md5,bfb61a5e0261fdfd1c8c46f3f3dbbe6e", + "test1_souporcell_vs_hasheddrops_donor_match.csv:md5,102fd8c326c1bf1a7ba7b0bd11ea3838", + "test1_souporcell_vs_hasheddrops_intersect_assignment_after_match.csv:md5,c84bd46c4b3c12012144c73398165b55", + "test1_souporcell_vs_hashsolo_all_assignment_after_match.csv:md5,39234357d62c2c519beb4691338f2797", + "test1_souporcell_vs_hashsolo_concordance_heatmap.png:md5,91d93741a44e57515defad0cd4837cca", + "test1_souporcell_vs_hashsolo_correlation_res.csv:md5,c2292c9cc36e62449cdd3fdf6dfb1a1f", + "test1_souporcell_vs_hashsolo_donor_match.csv:md5,102fd8c326c1bf1a7ba7b0bd11ea3838", + "test1_souporcell_vs_hashsolo_intersect_assignment_after_match.csv:md5,21ed58633a51cce2e6d8e24bbf122579", + "test1_souporcell_vs_multiseq_all_assignment_after_match.csv:md5,a409afeefe73dd91eddd3f6abbde2606", + "test1_souporcell_vs_multiseq_concordance_heatmap.png:md5,987ae361340b8b77daecc726ffa6b9de", + "test1_souporcell_vs_multiseq_correlation_res.csv:md5,a5f9d7ebd675403b062cd0f4a59b1592", + "test1_souporcell_vs_multiseq_donor_match.csv:md5,d1feea9baad73b5bc106472a226cbbf2", + "test1_souporcell_vs_multiseq_intersect_assignment_after_match.csv:md5,327f460700c49fe1f7d3447db2f0a2ea", + "test1_best_all_assignment_after_match.csv:md5,a705ae7739121396c7c92999abbddd39", + "test1_best_donor_match.csv:md5,102fd8c326c1bf1a7ba7b0bd11ea3838", + "test1_best_intersect_assignment_after_match.csv:md5,c84bd46c4b3c12012144c73398165b55", + "test1_score_record.csv:md5,92516e666292a795953beac514fe0cb1", + "test2_freemuxlet_vs_hasheddrops_all_assignment_after_match.csv:md5,05e486189b9c3bbfa381f17191ba5b9d", + "test2_freemuxlet_vs_hasheddrops_concordance_heatmap.png:md5,666ff7d8b8bb12d055ea6fdb081cb714", + "test2_freemuxlet_vs_hasheddrops_correlation_res.csv:md5,b0915774342ca64fe9077505a0b98a5f", + "test2_freemuxlet_vs_hasheddrops_donor_match.csv:md5,2d6da63e3801454389610977089c027f", + "test2_freemuxlet_vs_hasheddrops_intersect_assignment_after_match.csv:md5,c9ee6eed6505ae762f80e15e02f3a0c7", + "test2_freemuxlet_vs_hashsolo_all_assignment_after_match.csv:md5,90d108f1f5686f0c1a0f1d63ee84996c", + "test2_freemuxlet_vs_hashsolo_concordance_heatmap.png:md5,57a72e1a6a34fb333be8cf8d343c8b21", + "test2_freemuxlet_vs_hashsolo_correlation_res.csv:md5,07726a0bdac892ee79bc40b71780c187", + "test2_freemuxlet_vs_hashsolo_donor_match.csv:md5,2d6da63e3801454389610977089c027f", + "test2_freemuxlet_vs_hashsolo_intersect_assignment_after_match.csv:md5,9b0c416e27572acc4c8ba6b97480ca86", + "test2_freemuxlet_vs_multiseq_all_assignment_after_match.csv:md5,ff848ce7685a658315ad2af8f2d39c4b", + "test2_freemuxlet_vs_multiseq_concordance_heatmap.png:md5,64bc44fb9aeeb1581ebbf708c8765b4d", + "test2_freemuxlet_vs_multiseq_correlation_res.csv:md5,8a690fde26636e0e4c44b41a156ae47a", + "test2_freemuxlet_vs_multiseq_donor_match.csv:md5,b92352374bf4b32f7e3a1b032a4c8b5f", + "test2_freemuxlet_vs_multiseq_intersect_assignment_after_match.csv:md5,7a178c3e2f08aa338d4bb6e9f1e2cb44", + "test2_souporcell_vs_hasheddrops_all_assignment_after_match.csv:md5,a705ae7739121396c7c92999abbddd39", + "test2_souporcell_vs_hasheddrops_concordance_heatmap.png:md5,53fcd062b4a8e63cebb1af6e41a59336", + "test2_souporcell_vs_hasheddrops_correlation_res.csv:md5,bfb61a5e0261fdfd1c8c46f3f3dbbe6e", + "test2_souporcell_vs_hasheddrops_donor_match.csv:md5,102fd8c326c1bf1a7ba7b0bd11ea3838", + "test2_souporcell_vs_hasheddrops_intersect_assignment_after_match.csv:md5,c84bd46c4b3c12012144c73398165b55", + "test2_souporcell_vs_hashsolo_all_assignment_after_match.csv:md5,39234357d62c2c519beb4691338f2797", + "test2_souporcell_vs_hashsolo_concordance_heatmap.png:md5,91d93741a44e57515defad0cd4837cca", + "test2_souporcell_vs_hashsolo_correlation_res.csv:md5,c2292c9cc36e62449cdd3fdf6dfb1a1f", + "test2_souporcell_vs_hashsolo_donor_match.csv:md5,102fd8c326c1bf1a7ba7b0bd11ea3838", + "test2_souporcell_vs_hashsolo_intersect_assignment_after_match.csv:md5,21ed58633a51cce2e6d8e24bbf122579", + "test2_souporcell_vs_multiseq_all_assignment_after_match.csv:md5,a409afeefe73dd91eddd3f6abbde2606", + "test2_souporcell_vs_multiseq_concordance_heatmap.png:md5,987ae361340b8b77daecc726ffa6b9de", + "test2_souporcell_vs_multiseq_correlation_res.csv:md5,a5f9d7ebd675403b062cd0f4a59b1592", + "test2_souporcell_vs_multiseq_donor_match.csv:md5,d1feea9baad73b5bc106472a226cbbf2", + "test2_souporcell_vs_multiseq_intersect_assignment_after_match.csv:md5,327f460700c49fe1f7d3447db2f0a2ea", + "test2_best_all_assignment_after_match.csv:md5,a705ae7739121396c7c92999abbddd39", + "test2_best_donor_match.csv:md5,102fd8c326c1bf1a7ba7b0bd11ea3838", + "test2_best_intersect_assignment_after_match.csv:md5,c84bd46c4b3c12012144c73398165b55", + "test2_score_record.csv:md5,92516e666292a795953beac514fe0cb1", + "test3_freemuxlet_vs_hasheddrops_all_assignment_after_match.csv:md5,05e486189b9c3bbfa381f17191ba5b9d", + "test3_freemuxlet_vs_hasheddrops_concordance_heatmap.png:md5,666ff7d8b8bb12d055ea6fdb081cb714", + "test3_freemuxlet_vs_hasheddrops_correlation_res.csv:md5,b0915774342ca64fe9077505a0b98a5f", + "test3_freemuxlet_vs_hasheddrops_donor_match.csv:md5,2d6da63e3801454389610977089c027f", + "test3_freemuxlet_vs_hasheddrops_intersect_assignment_after_match.csv:md5,c9ee6eed6505ae762f80e15e02f3a0c7", + "test3_freemuxlet_vs_hashsolo_all_assignment_after_match.csv:md5,90d108f1f5686f0c1a0f1d63ee84996c", + "test3_freemuxlet_vs_hashsolo_concordance_heatmap.png:md5,57a72e1a6a34fb333be8cf8d343c8b21", + "test3_freemuxlet_vs_hashsolo_correlation_res.csv:md5,07726a0bdac892ee79bc40b71780c187", + "test3_freemuxlet_vs_hashsolo_donor_match.csv:md5,2d6da63e3801454389610977089c027f", + "test3_freemuxlet_vs_hashsolo_intersect_assignment_after_match.csv:md5,9b0c416e27572acc4c8ba6b97480ca86", + "test3_freemuxlet_vs_multiseq_all_assignment_after_match.csv:md5,ff848ce7685a658315ad2af8f2d39c4b", + "test3_freemuxlet_vs_multiseq_concordance_heatmap.png:md5,64bc44fb9aeeb1581ebbf708c8765b4d", + "test3_freemuxlet_vs_multiseq_correlation_res.csv:md5,8a690fde26636e0e4c44b41a156ae47a", + "test3_freemuxlet_vs_multiseq_donor_match.csv:md5,b92352374bf4b32f7e3a1b032a4c8b5f", + "test3_freemuxlet_vs_multiseq_intersect_assignment_after_match.csv:md5,7a178c3e2f08aa338d4bb6e9f1e2cb44", + "test3_souporcell_vs_hasheddrops_all_assignment_after_match.csv:md5,a705ae7739121396c7c92999abbddd39", + "test3_souporcell_vs_hasheddrops_concordance_heatmap.png:md5,53fcd062b4a8e63cebb1af6e41a59336", + "test3_souporcell_vs_hasheddrops_correlation_res.csv:md5,bfb61a5e0261fdfd1c8c46f3f3dbbe6e", + "test3_souporcell_vs_hasheddrops_donor_match.csv:md5,102fd8c326c1bf1a7ba7b0bd11ea3838", + "test3_souporcell_vs_hasheddrops_intersect_assignment_after_match.csv:md5,c84bd46c4b3c12012144c73398165b55", + "test3_souporcell_vs_hashsolo_all_assignment_after_match.csv:md5,39234357d62c2c519beb4691338f2797", + "test3_souporcell_vs_hashsolo_concordance_heatmap.png:md5,91d93741a44e57515defad0cd4837cca", + "test3_souporcell_vs_hashsolo_correlation_res.csv:md5,c2292c9cc36e62449cdd3fdf6dfb1a1f", + "test3_souporcell_vs_hashsolo_donor_match.csv:md5,102fd8c326c1bf1a7ba7b0bd11ea3838", + "test3_souporcell_vs_hashsolo_intersect_assignment_after_match.csv:md5,21ed58633a51cce2e6d8e24bbf122579", + "test3_souporcell_vs_multiseq_all_assignment_after_match.csv:md5,a409afeefe73dd91eddd3f6abbde2606", + "test3_souporcell_vs_multiseq_concordance_heatmap.png:md5,987ae361340b8b77daecc726ffa6b9de", + "test3_souporcell_vs_multiseq_correlation_res.csv:md5,a5f9d7ebd675403b062cd0f4a59b1592", + "test3_souporcell_vs_multiseq_donor_match.csv:md5,d1feea9baad73b5bc106472a226cbbf2", + "test3_souporcell_vs_multiseq_intersect_assignment_after_match.csv:md5,327f460700c49fe1f7d3447db2f0a2ea", + "test3_best_all_assignment_after_match.csv:md5,a705ae7739121396c7c92999abbddd39", + "test3_best_donor_match.csv:md5,102fd8c326c1bf1a7ba7b0bd11ea3838", + "test3_best_intersect_assignment_after_match.csv:md5,c84bd46c4b3c12012144c73398165b55", + "test3_score_record.csv:md5,92516e666292a795953beac514fe0cb1", + "test1_hashes.txt:md5,56390704cb4a0f31ca5a2d9293d673c0", + "test2_hashes.txt:md5,56390704cb4a0f31ca5a2d9293d673c0", + "test3_hashes.txt:md5,56390704cb4a0f31ca5a2d9293d673c0", "test1.clust1.samples.gz:md5,42f8c8ba281fccf6edaf6dbd30144210", - "test1.clust1.vcf.gz:md5,50d70db92dc4460a3e38e8b29c38c18a", "test1.lmix:md5,d8be4c936ff56980a476d5ba82290b8e", "test2.clust1.samples.gz:md5,42f8c8ba281fccf6edaf6dbd30144210", - "test2.clust1.vcf.gz:md5,50d70db92dc4460a3e38e8b29c38c18a", "test2.lmix:md5,d8be4c936ff56980a476d5ba82290b8e", "test3.clust1.samples.gz:md5,42f8c8ba281fccf6edaf6dbd30144210", - "test3.clust1.vcf.gz:md5,50d70db92dc4460a3e38e8b29c38c18a", "test3.lmix:md5,d8be4c936ff56980a476d5ba82290b8e", + "clusters.tsv:md5,3443284fb0e0ace31b5da0f329568392", + "clusters.tsv:md5,3443284fb0e0ace31b5da0f329568392", + "clusters.tsv:md5,3443284fb0e0ace31b5da0f329568392", + "test1_genetic_summary.h5ad:md5,10587503bf9706b5e2bd6db031acd1f3", + "test1_genetic_summary.h5mu:md5,42349934b0d68f6b817d954e719f905f", + "test1_genetic_summary_assignment.csv:md5,6caeede567715d29c674d0d1c32c07fc", + "test1_genetic_summary_classification.csv:md5,19faffcc11a5e05a24a8efd49fb186ca", + "test2_genetic_summary.h5ad:md5,10587503bf9706b5e2bd6db031acd1f3", + "test2_genetic_summary.h5mu:md5,42349934b0d68f6b817d954e719f905f", + "test2_genetic_summary_assignment.csv:md5,6caeede567715d29c674d0d1c32c07fc", + "test2_genetic_summary_classification.csv:md5,19faffcc11a5e05a24a8efd49fb186ca", + "test3_genetic_summary.h5ad:md5,10587503bf9706b5e2bd6db031acd1f3", + "test3_genetic_summary.h5mu:md5,42349934b0d68f6b817d954e719f905f", + "test3_genetic_summary_assignment.csv:md5,6caeede567715d29c674d0d1c32c07fc", + "test3_genetic_summary_classification.csv:md5,19faffcc11a5e05a24a8efd49fb186ca", "test1.base.vcf.gz:md5,36eccb4bd3e2130f395985e09a66a60d", "test1.cells.vcf.gz:md5,b28d8acaf17777c7cf9e2c7d01d93a66", "test1.samples.tsv:md5,9e488782c1bcd63c37ee3d1c4c0a9217", @@ -417,133 +578,88 @@ "test3_prob_doublet.tsv.gz:md5,0226d8043ee03b790fe2d130093b40df", "test3_prob_singlet.tsv.gz:md5,959b21543d21f2214e399e541848d81a", "test3_summary.tsv:md5,94b0ce40cc86bbe7fbacebf98e685ee7", - "GMM_full.config:md5,5d74af7b15c1cd4abaa4696be78631b2", - "barcodes.tsv.gz:md5,8ce05fd0599d95476158177deaa48543", - "features.tsv.gz:md5,6ac5b411e9e5b8eee2bc58ddf3ce6f2f", - "matrix.mtx.gz:md5,a203d4315dfae4ff939fe60dec9b9846", - "GMM_full.config:md5,5d74af7b15c1cd4abaa4696be78631b2", - "barcodes.tsv.gz:md5,8ce05fd0599d95476158177deaa48543", - "features.tsv.gz:md5,6ac5b411e9e5b8eee2bc58ddf3ce6f2f", - "matrix.mtx.gz:md5,a203d4315dfae4ff939fe60dec9b9846", - "GMM_full.config:md5,5d74af7b15c1cd4abaa4696be78631b2", - "barcodes.tsv.gz:md5,8ce05fd0599d95476158177deaa48543", - "features.tsv.gz:md5,6ac5b411e9e5b8eee2bc58ddf3ce6f2f", - "matrix.mtx.gz:md5,a203d4315dfae4ff939fe60dec9b9846", + "test1.csv:md5,0ae6066c69fd930f16e5d6c8ddfa9258", + "test2.csv:md5,0ae6066c69fd930f16e5d6c8ddfa9258", + "test3.csv:md5,0ae6066c69fd930f16e5d6c8ddfa9258", "test1_emptyDrops.csv:md5,a227d42afbcc590b4e949075cde4a5b6", "test1_emptyDrops.png:md5,2d7113a76a494b2b2c18b77c4a664063", "test1_emptyDrops.rds:md5,bda2e88b1e3e8c8ea5a22e19e3ab243d", - "test1_hasheddrops.rds:md5,31ba0ecae1d94080bccf81cdbaa5afe8", - "test1_id_to_hash.csv:md5,a015ca1178177038db21a97fa2070874", + "test1_hasheddrops.rds:md5,354124932e1ec5c230ee344ee33417b3", + "test1_id_to_hash.csv:md5,1833c29abc6feec1c8d400342019fed2", "test1_params_hasheddrops.csv:md5,b253501914df23ef5901262e8a241a8d", "test1_plot_hasheddrops.png:md5,2d7113a76a494b2b2c18b77c4a664063", - "test1_results_hasheddrops.csv:md5,d0f2af6d9bdb47a80bfbf08a85493d2e", + "test1_results_hasheddrops.csv:md5,9614789e65fc843e6a11fa60686fe89a", "test2_emptyDrops.csv:md5,a227d42afbcc590b4e949075cde4a5b6", "test2_emptyDrops.png:md5,2d7113a76a494b2b2c18b77c4a664063", "test2_emptyDrops.rds:md5,bda2e88b1e3e8c8ea5a22e19e3ab243d", - "test2_hasheddrops.rds:md5,31ba0ecae1d94080bccf81cdbaa5afe8", - "test2_id_to_hash.csv:md5,a015ca1178177038db21a97fa2070874", + "test2_hasheddrops.rds:md5,354124932e1ec5c230ee344ee33417b3", + "test2_id_to_hash.csv:md5,1833c29abc6feec1c8d400342019fed2", "test2_params_hasheddrops.csv:md5,3612264937e5c5962fefddc43afb595f", "test2_plot_hasheddrops.png:md5,2d7113a76a494b2b2c18b77c4a664063", - "test2_results_hasheddrops.csv:md5,d0f2af6d9bdb47a80bfbf08a85493d2e", + "test2_results_hasheddrops.csv:md5,9614789e65fc843e6a11fa60686fe89a", "test3_emptyDrops.csv:md5,a227d42afbcc590b4e949075cde4a5b6", "test3_emptyDrops.png:md5,2d7113a76a494b2b2c18b77c4a664063", "test3_emptyDrops.rds:md5,bda2e88b1e3e8c8ea5a22e19e3ab243d", - "test3_hasheddrops.rds:md5,31ba0ecae1d94080bccf81cdbaa5afe8", - "test3_id_to_hash.csv:md5,a015ca1178177038db21a97fa2070874", + "test3_hasheddrops.rds:md5,354124932e1ec5c230ee344ee33417b3", + "test3_id_to_hash.csv:md5,1833c29abc6feec1c8d400342019fed2", "test3_params_hasheddrops.csv:md5,b15e94f887e3ada8f790ea18eb791d8b", "test3_plot_hasheddrops.png:md5,2d7113a76a494b2b2c18b77c4a664063", - "test3_results_hasheddrops.csv:md5,d0f2af6d9bdb47a80bfbf08a85493d2e", - "test1_assignment_hashsolo.csv:md5,a970a5192150b1832a3efea197c882cd", - "test1_hashsolo.h5ad:md5,91ebf16f7c96c069bd9e4112c51d9498", - "test1_params_hashsolo.csv:md5,cad7ebf7836fa1ec5da0a2eb13501024", - "test2_assignment_hashsolo.csv:md5,a970a5192150b1832a3efea197c882cd", - "test2_hashsolo.h5ad:md5,91ebf16f7c96c069bd9e4112c51d9498", - "test2_params_hashsolo.csv:md5,3d687d34d7ea579aaf73d9175964e335", - "test3_assignment_hashsolo.csv:md5,a970a5192150b1832a3efea197c882cd", - "test3_hashsolo.h5ad:md5,91ebf16f7c96c069bd9e4112c51d9498", - "test3_params_hashsolo.csv:md5,880b0108f2f9187225805416f4068aaa", - "test1_assignment_htodemux.csv:md5,065f73102e5e28f47d1ae5d679bacc4d", - "test1_classification_htodemux.csv:md5,7627fa55b5ec02b06a356ee2f55e916f", - "test1_params_htodemux.csv:md5,08b6c065b0c383e6be3c20787ec01d3e", - "test2_assignment_htodemux.csv:md5,065f73102e5e28f47d1ae5d679bacc4d", - "test2_classification_htodemux.csv:md5,7627fa55b5ec02b06a356ee2f55e916f", - "test2_params_htodemux.csv:md5,484909d87cacd9c2b9cf60a3c853a5fd", - "test3_assignment_htodemux.csv:md5,065f73102e5e28f47d1ae5d679bacc4d", - "test3_classification_htodemux.csv:md5,7627fa55b5ec02b06a356ee2f55e916f", - "test3_params_htodemux.csv:md5,a0a3f900a632eea39057b038438a89b9", - "test1_featureScatter_htodemux.jpeg:md5,2785c5d3287f77cc0a20b0a9e9f0646b", - "test1_heatMap_htodemux.jpeg:md5,3805678904edcc047974a0011fc8f03f", - "test1_ridge_htodemux.jpeg:md5,124199413ddd4a8ff58e523af36e1b97", - "test1_tSNE_htodemux.jpeg:md5,6a9972d5e77fdaec39c640bee832ea49", - "test1_violinPlot_htodemux.jpeg:md5,5d6dce10b324e98f628aff64bc09056c", - "test1_visual_params_htodemux.csv:md5,ba3a19f1ee84f653ab1f2c34f5a9930a", - "test2_featureScatter_htodemux.jpeg:md5,2785c5d3287f77cc0a20b0a9e9f0646b", - "test2_heatMap_htodemux.jpeg:md5,3805678904edcc047974a0011fc8f03f", - "test2_ridge_htodemux.jpeg:md5,124199413ddd4a8ff58e523af36e1b97", - "test2_tSNE_htodemux.jpeg:md5,6a9972d5e77fdaec39c640bee832ea49", - "test2_violinPlot_htodemux.jpeg:md5,5d6dce10b324e98f628aff64bc09056c", - "test2_visual_params_htodemux.csv:md5,5b1689a66e8c39a5de4b6c51ff201aef", - "test3_featureScatter_htodemux.jpeg:md5,2785c5d3287f77cc0a20b0a9e9f0646b", - "test3_heatMap_htodemux.jpeg:md5,3805678904edcc047974a0011fc8f03f", - "test3_ridge_htodemux.jpeg:md5,124199413ddd4a8ff58e523af36e1b97", - "test3_tSNE_htodemux.jpeg:md5,6a9972d5e77fdaec39c640bee832ea49", - "test3_violinPlot_htodemux.jpeg:md5,5d6dce10b324e98f628aff64bc09056c", - "test3_visual_params_htodemux.csv:md5,145eb3a098205f568cf89a5dffadf742", + "test3_results_hasheddrops.csv:md5,9614789e65fc843e6a11fa60686fe89a", + "test1_assignment_hashsolo.csv:md5,95de378e2f00e06a642925ba7b4b2a13", + "test1_hashsolo.h5ad:md5,3f9de5dd57023a525961ce126a6a2f5b", + "test1_params_hashsolo.csv:md5,e7b8c6c56164c1e458fd281ac44a70f3", + "test2_assignment_hashsolo.csv:md5,95de378e2f00e06a642925ba7b4b2a13", + "test2_hashsolo.h5ad:md5,3f9de5dd57023a525961ce126a6a2f5b", + "test2_params_hashsolo.csv:md5,504ae4e442d33b7be74926244a46eba2", + "test3_assignment_hashsolo.csv:md5,95de378e2f00e06a642925ba7b4b2a13", + "test3_hashsolo.h5ad:md5,3f9de5dd57023a525961ce126a6a2f5b", + "test3_params_hashsolo.csv:md5,1f6d75d89ed76c033155277713661dc3", "test1_params_multiseqdemux.csv:md5,60a096f79d78655870aef2af9130cf9d", - "test1_res_multiseqdemux.csv:md5,8a59e5066250adf6830555fda26e077f", + "test1_res_multiseqdemux.csv:md5,9d4475b8c2778e7e361747dd521072e4", "test2_params_multiseqdemux.csv:md5,bb7b4009e6f709ebf35407b7849bfac3", - "test2_res_multiseqdemux.csv:md5,8a59e5066250adf6830555fda26e077f", + "test2_res_multiseqdemux.csv:md5,9d4475b8c2778e7e361747dd521072e4", "test3_params_multiseqdemux.csv:md5,336308c45e52f92981173ecc7f295581", - "test3_res_multiseqdemux.csv:md5,8a59e5066250adf6830555fda26e077f", + "test3_res_multiseqdemux.csv:md5,9d4475b8c2778e7e361747dd521072e4", "test1_params_preprocessing.csv:md5,b77b94588a53835e913cb55b3791d309", "test2_params_preprocessing.csv:md5,0ed6b213e9f08ee5fc0f89b7184a3fb9", "test3_params_preprocessing.csv:md5,c5b931c6fe281d0cc63cb3ed13cb2d10", - "test1_hashing_summary.h5ad:md5,1312ac387a45d5b37487a8fac87721e1", - "test1_hashing_summary.h5mu:md5,c6239f9fae1e006d1b787d4473d63237", - "test1_hashing_summary_assignment.csv:md5,98e51d61f0726735550c325b23848cff", - "test1_hashing_summary_classification.csv:md5,acf78ede46f45285de3988776301fdea", - "test2_hashing_summary.h5ad:md5,1312ac387a45d5b37487a8fac87721e1", - "test2_hashing_summary.h5mu:md5,c6239f9fae1e006d1b787d4473d63237", - "test2_hashing_summary_assignment.csv:md5,98e51d61f0726735550c325b23848cff", - "test2_hashing_summary_classification.csv:md5,acf78ede46f45285de3988776301fdea", - "test3_hashing_summary.h5ad:md5,1312ac387a45d5b37487a8fac87721e1", - "test3_hashing_summary.h5mu:md5,c6239f9fae1e006d1b787d4473d63237", - "test3_hashing_summary_assignment.csv:md5,98e51d61f0726735550c325b23848cff", - "test3_hashing_summary_classification.csv:md5,acf78ede46f45285de3988776301fdea", + "test1_hashing_summary.h5ad:md5,acb501d66ea93044b3de232f1dd9be7f", + "test1_hashing_summary.h5mu:md5,48fa1ca52991c1f869f565304f41151d", + "test1_hashing_summary_assignment.csv:md5,5574ae2ab0b030a0c307a660f1808b4f", + "test1_hashing_summary_classification.csv:md5,dc0c3f4754580b897451aabb417372e4", + "test2_hashing_summary.h5ad:md5,acb501d66ea93044b3de232f1dd9be7f", + "test2_hashing_summary.h5mu:md5,48fa1ca52991c1f869f565304f41151d", + "test2_hashing_summary_assignment.csv:md5,5574ae2ab0b030a0c307a660f1808b4f", + "test2_hashing_summary_classification.csv:md5,dc0c3f4754580b897451aabb417372e4", + "test3_hashing_summary.h5ad:md5,acb501d66ea93044b3de232f1dd9be7f", + "test3_hashing_summary.h5mu:md5,48fa1ca52991c1f869f565304f41151d", + "test3_hashing_summary_assignment.csv:md5,5574ae2ab0b030a0c307a660f1808b4f", + "test3_hashing_summary_classification.csv:md5,dc0c3f4754580b897451aabb417372e4", "multiqc_citations.txt:md5,4c806e63a283ec1b7e78cdae3a923d4f", - "barcodes.tsv.gz:md5,291349dc31e204c71c4b121de4959686", - "features.tsv.gz:md5,6ac5b411e9e5b8eee2bc58ddf3ce6f2f", - "matrix.mtx.gz:md5,9d676ae60340181e1f227b3d92bd384e", - "barcodes.tsv.gz:md5,f62a276e262fdd85262a889d0f48556b", - "features.tsv.gz:md5,f7fe456894f58241d622077e2fe4b548", - "matrix.mtx.gz:md5,eb787b86f095acac4435c9014e09daa7", - "barcodes.tsv.gz:md5,291349dc31e204c71c4b121de4959686", - "features.tsv.gz:md5,6ac5b411e9e5b8eee2bc58ddf3ce6f2f", - "matrix.mtx.gz:md5,9d676ae60340181e1f227b3d92bd384e", - "barcodes.tsv.gz:md5,f62a276e262fdd85262a889d0f48556b", - "features.tsv.gz:md5,f7fe456894f58241d622077e2fe4b548", - "matrix.mtx.gz:md5,eb787b86f095acac4435c9014e09daa7", - "barcodes.tsv.gz:md5,291349dc31e204c71c4b121de4959686", - "features.tsv.gz:md5,6ac5b411e9e5b8eee2bc58ddf3ce6f2f", - "matrix.mtx.gz:md5,9d676ae60340181e1f227b3d92bd384e", - "barcodes.tsv.gz:md5,f62a276e262fdd85262a889d0f48556b", - "features.tsv.gz:md5,f7fe456894f58241d622077e2fe4b548", - "matrix.mtx.gz:md5,eb787b86f095acac4435c9014e09daa7", - "barcodes.tsv.gz:md5,f62a276e262fdd85262a889d0f48556b", - "features.tsv.gz:md5,f7fe456894f58241d622077e2fe4b548", - "matrix.mtx.gz:md5,eb787b86f095acac4435c9014e09daa7", - "barcodes.tsv.gz:md5,f62a276e262fdd85262a889d0f48556b", - "features.tsv.gz:md5,f7fe456894f58241d622077e2fe4b548", - "matrix.mtx.gz:md5,eb787b86f095acac4435c9014e09daa7", - "barcodes.tsv.gz:md5,f62a276e262fdd85262a889d0f48556b", - "features.tsv.gz:md5,f7fe456894f58241d622077e2fe4b548", - "matrix.mtx.gz:md5,eb787b86f095acac4435c9014e09daa7" + "barcodes.tsv.gz:md5,298331318a633cd9fdcb29416bf6ae41", + "features.tsv.gz:md5,e244612671c0667117cf06b846e7307a", + "matrix.mtx.gz:md5,af1bcd7c84f4f4838a4b732908a884f9", + "barcodes.tsv.gz:md5,298331318a633cd9fdcb29416bf6ae41", + "features.tsv.gz:md5,29c98c53ef1a9fa601a5bd4f29633e14", + "matrix.mtx.gz:md5,a54e91362b29715b36ec33e96f981d12", + "barcodes.tsv.gz:md5,298331318a633cd9fdcb29416bf6ae41", + "features.tsv.gz:md5,e244612671c0667117cf06b846e7307a", + "matrix.mtx.gz:md5,af1bcd7c84f4f4838a4b732908a884f9", + "barcodes.tsv.gz:md5,298331318a633cd9fdcb29416bf6ae41", + "features.tsv.gz:md5,29c98c53ef1a9fa601a5bd4f29633e14", + "matrix.mtx.gz:md5,a54e91362b29715b36ec33e96f981d12", + "barcodes.tsv.gz:md5,298331318a633cd9fdcb29416bf6ae41", + "features.tsv.gz:md5,e244612671c0667117cf06b846e7307a", + "matrix.mtx.gz:md5,af1bcd7c84f4f4838a4b732908a884f9", + "barcodes.tsv.gz:md5,298331318a633cd9fdcb29416bf6ae41", + "features.tsv.gz:md5,29c98c53ef1a9fa601a5bd4f29633e14", + "matrix.mtx.gz:md5,a54e91362b29715b36ec33e96f981d12" ] ], "meta": { "nf-test": "0.9.2", "nextflow": "25.04.6" }, - "timestamp": "2025-09-16T16:24:45.507456019" + "timestamp": "2025-11-22T07:38:00.471623041" } } \ No newline at end of file diff --git a/workflows/hadge.nf b/workflows/hadge.nf index 526460c3..eacea3a7 100644 --- a/workflows/hadge.nf +++ b/workflows/hadge.nf @@ -9,9 +9,17 @@ include { paramsSummaryMultiqc } from '../subworkflows/nf-core/utils_nfcore_pi include { softwareVersionsToYAML } from '../subworkflows/nf-core/utils_nfcore_pipeline' include { methodsDescriptionText } from '../subworkflows/local/utils_nfcore_hadge_pipeline' -include { GENETIC_DEMULTIPLEXING } from '../subworkflows/local/genetic_demultiplexing/main' -include { HASH_DEMULTIPLEXING } from '../subworkflows/local/hash_demultiplexing/main' -include { DONOR_MATCHING } from '../subworkflows/local/donor_matching/main' +include { UNTAR as UNTAR_RNA } from '../modules/nf-core/untar/main' +include { UNTAR as UNTAR_HTO } from '../modules/nf-core/untar/main' +include { RENAME_GENES_TO_FEATURES as RENAME_GENES_TO_FEATURES_RNA } from '../modules/local/rename_genes_to_features/main' +include { RENAME_GENES_TO_FEATURES as RENAME_GENES_TO_FEATURES_HTO } from '../modules/local/rename_genes_to_features/main' +include { EXTRACT_HASHES } from '../modules/local/extract_hashes/main' + +include { GENETIC_DEMULTIPLEXING } from '../subworkflows/local/genetic_demultiplexing/main' +include { HASH_DEMULTIPLEXING } from '../subworkflows/local/hash_demultiplexing/main' +include { CSVTK_JOIN as JOIN_RESULTS } from '../modules/nf-core/csvtk/join/main' +include { DONOR_MATCH } from '../modules/local/donor_match/main' + /* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -22,37 +30,149 @@ include { DONOR_MATCHING } from '../subworkflows/local/donor_matching/ma workflow HADGE { take: ch_samplesheet // channel: samplesheet read in from --input + fasta // file: /path/to/genome.fasta main: ch_versions = Channel.empty() ch_multiqc_files = Channel.empty() - ch_hashing = ch_samplesheet.map { meta, rna_matrix, hto_matrix, _bam, _barcodes, _vcf -> - [meta, rna_matrix, hto_matrix] + + // ------------------------------ preprocessing start ------------------------------- + + ch_rna = ch_samplesheet.map { meta, rna, _hto, _bam, _barcodes, _vcf -> [meta, rna] } + .branch { _meta, rna -> + tar: rna.endsWith('.tar.gz') + directory: true + } + ch_hto = ch_samplesheet.map { meta, _rna, hto, _bam, _barcodes, _vcf -> [meta, hto] } + .branch { _meta, hto -> + tar: hto.endsWith('.tar.gz') + directory: true + } + + ch_remaining_input = ch_samplesheet.map { meta, _rna, _hto, bam, barcodes, vcf -> [meta, bam, barcodes, vcf] } + + UNTAR_RNA(ch_rna.tar) + ch_versions = ch_versions.mix(UNTAR_RNA.out.versions) + + UNTAR_HTO(ch_hto.tar) + ch_versions = ch_versions.mix(UNTAR_HTO.out.versions) + + ch_rna = ch_rna.directory.mix(UNTAR_RNA.out.untar) + ch_hto = ch_hto.directory.mix(UNTAR_HTO.out.untar) + + // TODO remove completely if not used anymore + // ch_rna = RENAME_GENES_TO_FEATURES_RNA(ch_rna) + // ch_hto = RENAME_GENES_TO_FEATURES_HTO(ch_hto) + + // TODO maybe remove changes to extract hashes + ch_hashes = EXTRACT_HASHES(ch_hto) + + ch_genetic = ch_samplesheet.map { meta, _rna, _hto, _bam, _barcodes, _vcf -> [meta] } + .join(ch_rna) + .join(ch_hto) + .join(ch_remaining_input) + .join(ch_hashes) + .map {meta, rna, hto, bam, barcodes, vcf, hashes -> [meta+[hashes: file(hashes).text.trim()], rna, hto, bam, barcodes, vcf] } + + ch_hashing = ch_genetic.map { meta, rna, hto, _bam, _barcodes, _vcf -> + [meta, rna, hto] } - ch_genetic = ch_samplesheet.map { meta, _rna_matrix, _hto_matrix, bam, barcodes, vcf -> - [meta, bam, barcodes, vcf] + ch_donor_match = ch_genetic.map { meta, _rna, _hto, _bam, barcodes, _vcf -> + [meta, barcodes] } - if (params.mode == 'genetic' || params.mode == 'rescue') { + // ------------------------------- preprocessing end -------------------------------- + + if (params.mode == 'genetic'){ + GENETIC_DEMULTIPLEXING( ch_genetic, params.genetic_tools.split(','), params.bam_qc, - params.common_variants + params.common_variants, + fasta ) + + if(params.match_donor){ + ch_donor_match = ch_donor_match + .join(GENETIC_DEMULTIPLEXING.out.summary_assignment) + .join(GENETIC_DEMULTIPLEXING.out.cell_genotype) + .map{ meta, barcodes, gene_summary, cell_genotype -> [meta, barcodes, gene_summary, cell_genotype, []] } + } + ch_versions = ch_versions.mix(GENETIC_DEMULTIPLEXING.out.versions) } - if (params.mode == 'hashing' || params.mode == 'rescue') { - HASH_DEMULTIPLEXING(ch_hashing, params.hash_tools.split(',')) + else if (params.mode == 'hashing'){ + //TODO should mode hashing work with cell_genotype? + HASH_DEMULTIPLEXING( + ch_hashing, + params.hash_tools.split(',') + ) + + ch_donor_match = ch_donor_match + .join(HASH_DEMULTIPLEXING.out.summary_assignment) + .map{ meta, barcodes, hash_summary-> [meta, barcodes, hash_summary,[],[]] } + ch_versions = ch_versions.mix(HASH_DEMULTIPLEXING.out.versions) } + else if ( params.mode == 'rescue' ){ + + GENETIC_DEMULTIPLEXING( + ch_genetic, + params.genetic_tools.split(','), + params.bam_qc, + params.common_variants, + fasta + ) + + HASH_DEMULTIPLEXING( + ch_hashing, + params.hash_tools.split(',') + ) + + ch_donor_match = ch_donor_match + .join(GENETIC_DEMULTIPLEXING.out.summary_assignment) + .join(GENETIC_DEMULTIPLEXING.out.cell_genotype) + .join(HASH_DEMULTIPLEXING.out.summary_assignment) + + JOIN_RESULTS(ch_donor_match.map{ + meta, _barcodes, gene_summary, _cell_genotype, hash_summary -> + [meta, [gene_summary,hash_summary]] + }) + + ch_donor_match = ch_donor_match + .join(JOIN_RESULTS.out.csv) + .map{ + meta, barcodes, _gene_summary, cell_genotype, _hash_summary, joined_summary -> + [meta, barcodes, joined_summary, cell_genotype, []] + } + + ch_versions = ch_versions.mix(GENETIC_DEMULTIPLEXING.out.versions) + ch_versions = ch_versions.mix(HASH_DEMULTIPLEXING.out.versions) + ch_versions = ch_versions.mix(JOIN_RESULTS.out.versions) + } + else if ( params.mode == 'donor_match' ){ + + ch_donor_match = ch_donor_match.map{ + meta, barcodes -> + [meta, barcodes, params.demultiplexing_result, params.celldata, params.vireo_parent_dir] + } + + } + + if (params.match_donor) { + DONOR_MATCH(ch_donor_match, + params.match_donor_method1 ?: [], + params.match_donor_method2 ?: [], + params.findVariants, + params.variant_count, + params.variant_pct + ) - if (params.mode == 'donor_match' || params.match_donor) { - DONOR_MATCHING() - ch_versions = ch_versions.mix(DONOR_MATCHING.out.versions) + ch_versions = ch_versions.mix(DONOR_MATCH.out.versions) } // From 5c19f6ac183c610247715d2adf4a585e0eff541e Mon Sep 17 00:00:00 2001 From: LuisHeinzlmeier Date: Wed, 11 Feb 2026 07:23:52 +0100 Subject: [PATCH 40/74] Add find_variants, tests, and documentation (#96) * implementation of find_variants * subset gt_donors in mode 'donor_match' and update nextflow schema * clean find_variants * clean subset_gt_donors (update env) * clean donor_match * fix donor_match * add versions.yml (closes #87) * remove own TODOs and add error message for HTO names (closes #75, closes #73) * remove rename_genes_to_features (not used anymore) * add an own anndata/mudata module and remove it from summary modules (closes #83) * code and dependency cleanup of summary scripts * code and dependency cleanup of create_anndata_mudata.py * cleanup outdir * add a comment * closes #76 * add method overview output (closes #86) * add warning method mentioned in #77 * add test for genetic mode (see #95) * add tests for hashing and donor_match mode (see #95) * update nf-core modules vireo and souporcell (closes #94) * update input paths and add new tests to the github workflow (closes #95) * remove duplicate parameter * fix linting errors * update snapshots for rescue and donor_match * codespace snapshot default.nf.test * fix create anndata/mudata issue (hto==null in genetic mode) * add contributors * change defaultBranch * update contributor information * add another contributor * Add intro page * add rescue and genetic mode to the documentation * update samplesheet and add usage documentation * restructure steps of the pipeline * outputs for hashing, genetic and summary * close details * place output files below the headline * update and describe donor_match and find_variant outputs * push pipeline png * add citation of the relevant tools and adjust outdir * add seo's orcid * move checking the params and samplesheet inputs to 'utils_nfcore_hadge_pipeline' * move the validation of hto_names to utils_nfcore_hadge_pipeline with a function * add ext.prefix of JOIN_RESULTS with regex * add specific profiles to the tests and test vcf files * add nft-vcf plugin * update snapshot for rescue and donor match mode * update profile name of donor_match * update default snap with codespaces * ingore cluster_genotypes.vcf, sort AnnData columns and mention test profiles in docs * bash script to update all test snapshots * update snapshots with codespaces * update snapshot from test_genetic * update ignore souporcell vcf * add a seed for reproducible t-SNE and PCA * add extract hashes env.yml * nf-core pipelines lint --fix rocrate_readme_sync * nf-core pipelines lint --dir . --fix files_unchanged * Revert "nf-core pipelines lint --dir . --fix files_unchanged" This reverts commit 9bdae0b3765c8b96012fec8a9f4c431ca08e985b. * add comment for ignoring cluster_genotypes.vcf * Samplesheet Input Requirements by Module * small fix in docs * remove rna_matrix from genetic mode as it is not needed * simplify preprocessing channels * clean up formatting * remove barcodes from donor_match because it was already joined on barcodes.tsv in gene_summary.py * simplify tuple nesting issue in ch_donor_match * update docs * test new snapshots * update donor_match snapshot * fix donor_match snapshot issue * test new jpeg image * remove the random seed from htodemux (snapshot differences are due to OS variations) * update snapshot hashing * update rescue snapshot * update genetic snapshot * only update *_tSNE_htodemux.jpeg snapshot and date (multiqc throws an error in codespaces) * incorporate feedback * nf-core pipelines lint --fix rocrate_readme_sync * own line for each sentence --- CITATIONS.md | 56 +- README.md | 68 +- assets/samplesheet.csv | 7 +- assets/schema_input.json | 12 - bin/update_snapshots.sh | 46 + conf/modules.config | 80 +- conf/test.config | 14 +- conf/test_donor_match.config | 32 + conf/test_genetic.config | 32 + conf/test_hashing.config | 31 + docs/images/pipeline.png | Bin 0 -> 379669 bytes docs/output.md | 121 ++- docs/usage.md | 178 +++- modules.json | 2 +- .../create_anndata_mudata/environment.yml | 9 + modules/local/create_anndata_mudata/main.nf | 49 + .../templates/create_anndata_mudata.py | 140 +++ modules/local/donor_match/environment.yml | 4 +- modules/local/donor_match/main.nf | 25 +- .../local/donor_match/templates/donor_match.R | 364 +------ .../mtxconvert/templates/convert.R | 2 +- modules/local/extract_hashes/environment.yml | 7 + modules/local/extract_hashes/main.nf | 19 + modules/local/find_variants/environment.yml | 8 + modules/local/find_variants/main.nf | 54 ++ .../find_variants/templates/find_variants.R | 291 ++++++ modules/local/gene_summary/environment.yml | 8 +- modules/local/gene_summary/main.nf | 21 +- .../gene_summary/templates/gene_summary.py | 114 +-- modules/local/hash_summary/environment.yml | 10 +- modules/local/hash_summary/main.nf | 22 +- .../hash_summary/templates/hash_summary.py | 102 +- .../rename_genes_to_features/environment.yml | 7 - .../local/rename_genes_to_features/main.nf | 25 - .../local/subset_gt_donors/environment.yml | 5 + modules/local/subset_gt_donors/main.nf | 45 + modules/nf-core/bff/templates/bff.R | 8 + modules/nf-core/vireo/main.nf | 30 +- modules/nf-core/vireo/meta.yml | 61 +- modules/nf-core/vireo/tests/main.nf.test | 8 +- modules/nf-core/vireo/tests/main.nf.test.snap | 88 +- modules/nf-core/vireo/tests/nextflow.config | 5 +- nextflow.config | 90 +- nextflow_schema.json | 175 ++-- nf-test.config | 3 +- ro-crate-metadata.json | 2 +- .../local/genetic_demultiplexing/main.nf | 38 +- .../local/hash_demultiplexing/main.nf | 31 +- .../local/utils_nfcore_hadge_pipeline/main.nf | 85 ++ tests/.nftignore | 1 + tests/default.nf.test | 8 +- tests/default.nf.test.snap | 890 ++++++++++-------- tests/test_donor_match.nf.test | 40 + tests/test_donor_match.nf.test.snap | 428 +++++++++ tests/test_genetic.nf.test | 40 + tests/test_genetic.nf.test.snap | 272 ++++++ tests/test_hashing.nf.test | 40 + tests/test_hashing.nf.test.snap | 791 ++++++++++++++++ workflows/hadge.nf | 214 +++-- 59 files changed, 4125 insertions(+), 1233 deletions(-) create mode 100755 bin/update_snapshots.sh create mode 100644 conf/test_donor_match.config create mode 100644 conf/test_genetic.config create mode 100644 conf/test_hashing.config create mode 100644 docs/images/pipeline.png create mode 100644 modules/local/create_anndata_mudata/environment.yml create mode 100644 modules/local/create_anndata_mudata/main.nf create mode 100644 modules/local/create_anndata_mudata/templates/create_anndata_mudata.py create mode 100644 modules/local/extract_hashes/environment.yml create mode 100644 modules/local/find_variants/environment.yml create mode 100644 modules/local/find_variants/main.nf create mode 100644 modules/local/find_variants/templates/find_variants.R delete mode 100644 modules/local/rename_genes_to_features/environment.yml delete mode 100644 modules/local/rename_genes_to_features/main.nf create mode 100644 modules/local/subset_gt_donors/environment.yml create mode 100644 modules/local/subset_gt_donors/main.nf create mode 100644 tests/test_donor_match.nf.test create mode 100644 tests/test_donor_match.nf.test.snap create mode 100644 tests/test_genetic.nf.test create mode 100644 tests/test_genetic.nf.test.snap create mode 100644 tests/test_hashing.nf.test create mode 100644 tests/test_hashing.nf.test.snap diff --git a/CITATIONS.md b/CITATIONS.md index 3e1d59a4..6cb647ba 100644 --- a/CITATIONS.md +++ b/CITATIONS.md @@ -10,9 +10,63 @@ ## Pipeline tools +### Genetic-based deconvolution + +- [Demuxlet](https://pubmed.ncbi.nlm.nih.gov/29227470/) + + > Kang HM, Subramaniam M, Targ S, Nguyen M, Maliskova L, McCarthy E, Wan E, Wong S, Byrnes L, Lanata CM, Gate RE, Mostafavi S, Marson A, Zaitlen N, Criswell LA, Ye CJ. Multiplexed droplet single-cell RNA-sequencing using natural genetic variation. Nat Biotechnol. 2018 Jan;36(1):89-94. doi: 10.1038/nbt.4042. Epub 2017 Dec 11. Erratum in: Nat Biotechnol. 2020 Nov;38(11):1356. doi: 10.1038/s41587-020-0715-9. PMID: 29227470; PMCID: PMC5784859. + +- [Freemuxlet](https://deepblue.lib.umich.edu/items/ee3d8051-2485-4e1d-9819-52c732d561e8) + +> Zhang, F. Leveraging genetic variants for rapid and robust upstream analysis of massive sequence data. (2019). [Doctoral Dissertation, University of Michigan] https://deepblue.lib.umich.edu/handle/2027.42/151524. + +- [Souporcell](https://github.com/wheaton5/souporcell) + + > Heaton H, Talman AM, Knights A, Imaz M, Gaffney DJ, Durbin R, Hemberg M, Lawniczak MKN. Souporcell: robust clustering of single-cell RNA-seq data by genotype without reference genotypes. Nat Methods. 2020 Jun;17(6):615-620. doi: 10.1038/s41592-020-0820-1. Epub 2020 May 4. PMID: 32366989; PMCID: PMC7617080. + +- [Vireo](https://pubmed.ncbi.nlm.nih.gov/31836005/) + + > Huang Y, McCarthy DJ, Stegle O. Vireo: Bayesian demultiplexing of pooled single-cell RNA-seq data without genotype reference. Genome Biol. 2019 Dec 13;20(1):273. doi: 10.1186/s13059-019-1865-2. PMID: 31836005; PMCID: PMC6909514. + +### Hashing-based deconvolution + +- [BFF](https://pubmed.ncbi.nlm.nih.gov/35561167/) + + > Boggy GJ, McElfresh GW, Mahyari E, Ventura AB, Hansen SG, Picker LJ, Bimber BN. BFF and cellhashR: analysis tools for accurate demultiplexing of cell hashing data. Bioinformatics. 2022 May 13;38(10):2791-2801. doi: 10.1093/bioinformatics/btac213. PMID: 35561167; PMCID: PMC9113275. + +- [Demuxem](https://pubmed.ncbi.nlm.nih.gov/31266958/) + + > Gaublomme JT, Li B, McCabe C, Knecht A, Yang Y, Drokhlyansky E, Van Wittenberghe N, Waldman J, Dionne D, Nguyen L, De Jager PL, Yeung B, Zhao X, Habib N, Rozenblatt-Rosen O, Regev A. Nuclei multiplexing with barcoded antibodies for single-nucleus genomics. Nat Commun. 2019 Jul 2;10(1):2907. doi: 10.1038/s41467-019-10756-2. Erratum in: Nat Commun. 2020 Nov 2;11(1):5619. doi: 10.1038/s41467-020-19357-w. PMID: 31266958; PMCID: PMC6606589. + +- [GMM-Demux](https://pubmed.ncbi.nlm.nih.gov/32731885/) + + > Xin H, Lian Q, Jiang Y, Luo J, Wang X, Erb C, Xu Z, Zhang X, Heidrich-O'Hare E, Yan Q, Duerr RH, Chen K, Chen W. GMM-Demux: sample demultiplexing, multiplet detection, experiment planning, and novel cell-type verification in single cell sequencing. Genome Biol. 2020 Jul 30;21(1):188. doi: 10.1186/s13059-020-02084-2. PMID: 32731885; PMCID: PMC7393741. + +- [HashedDrops](https://pubmed.ncbi.nlm.nih.gov/30902100/) + + > Lun ATL, Riesenfeld S, Andrews T, Dao TP, Gomes T; participants in the 1st Human Cell Atlas Jamboree; Marioni JC. EmptyDrops: distinguishing cells from empty droplets in droplet-based single-cell RNA sequencing data. Genome Biol. 2019 Mar 22;20(1):63. doi: 10.1186/s13059-019-1662-y. PMID: 30902100; PMCID: PMC6431044. + +- [Hashsolo](https://pubmed.ncbi.nlm.nih.gov/32592658/) + + > Bernstein NJ, Fong NL, Lam I, Roy MA, Hendrickson DG, Kelley DR. Solo: Doublet Identification in Single-Cell RNA-Seq via Semi-Supervised Deep Learning. Cell Syst. 2020 Jul 22;11(1):95-101.e5. doi: 10.1016/j.cels.2020.05.010. Epub 2020 Jun 26. PMID: 32592658. + +- [HTODemux](https://pubmed.ncbi.nlm.nih.gov/28759029/) + + > Stoeckius M, Hafemeister C, Stephenson W, Houck-Loomis B, Chattopadhyay PK, Swerdlow H, Satija R, Smibert P. Simultaneous epitope and transcriptome measurement in single cells. Nat Methods. 2017 Sep;14(9):865-868. doi: 10.1038/nmeth.4380. Epub 2017 Jul 31. PMID: 28759029; PMCID: PMC5669064. + +- [Multiseq](https://pubmed.ncbi.nlm.nih.gov/31209384/) + + > McGinnis CS, Patterson DM, Winkler J, Conrad DN, Hein MY, Srivastava V, Hu JL, Murrow LM, Weissman JS, Werb Z, Chow ED, Gartner ZJ. MULTI-seq: sample multiplexing for single-cell RNA sequencing using lipid-tagged indices. Nat Methods. 2019 Jul;16(7):619-626. doi: 10.1038/s41592-019-0433-8. Epub 2019 Jun 17. PMID: 31209384; PMCID: PMC6837808. + +### Others + +- [Cellsnp-lite](https://pubmed.ncbi.nlm.nih.gov/33963851/) + + > Huang X, Huang Y. Cellsnp-lite: an efficient tool for genotyping single cells. Bioinformatics. 2021 Dec 7;37(23):4569-4571. doi: 10.1093/bioinformatics/btab358. PMID: 33963851. + - [MultiQC](https://pubmed.ncbi.nlm.nih.gov/27312411/) -> Ewels P, Magnusson M, Lundin S, Käller M. MultiQC: summarize analysis results for multiple tools and samples in a single report. Bioinformatics. 2016 Oct 1;32(19):3047-8. doi: 10.1093/bioinformatics/btw354. Epub 2016 Jun 16. PubMed PMID: 27312411; PubMed Central PMCID: PMC5039924. + > Ewels P, Magnusson M, Lundin S, Käller M. MultiQC: summarize analysis results for multiple tools and samples in a single report. Bioinformatics. 2016 Oct 1;32(19):3047-8. doi: 10.1093/bioinformatics/btw354. Epub 2016 Jun 16. PubMed PMID: 27312411; PubMed Central PMCID: PMC5039924. ## Software packaging/containerisation tools diff --git a/README.md b/README.md index ff910d5e..9dd081e9 100644 --- a/README.md +++ b/README.md @@ -21,48 +21,65 @@ ## Introduction -**nf-core/hadge** is a bioinformatics pipeline that ... - - - - -2. Present QC for raw reads ([`MultiQC`](http://multiqc.info/)) +**nf-core/hadge** (**ha**shing **d**econvolution combined with **ge**notype information) is a bioinformatics pipeline that combines 11 methods to perform both hashing- and genotype-based deconvolution on single cell multiplexing data. +It takes a samplesheet with count matrices, BAM and VCF files as input, performs deconvolution with every method, joins all results and finally recovers previously discarded cells by combining the best performing methods (donor matching). + +![nf-core/hadge metro map](docs/images/pipeline.png) + +1. Untar matrices +2. Extract hto names from matrix +3. Perform genetic-based deconvolution + 1. Get single cell genotype [`cellSNP`](https://github.com/single-cell-genetics/cellSNP) + 2. [`vireo`](https://github.com/single-cell-genetics/vireo) + 3. [`demuxlet`](https://github.com/statgen/popscle) + 4. [`freemuxlet`](https://github.com/statgen/popscle) + 5. [`souporcell`](https://github.com/wheaton5/souporcell) +4. summarize assignments and classifications +5. Perform hashing-based deconvolution + 1. [`htodemux`](https://satijalab.org/seurat/articles/hashing_vignette) + 2. [`multiseq`](https://satijalab.org/seurat/reference/multiseqdemux) + 3. [`bff`](https://github.com/BimberLab/cellhashR) + 4. [`demuxem`](https://demuxem.readthedocs.io/en/latest/) + 5. [`gmm-demux`](https://github.com/CHPGenetics/GMM-demux) + 6. [`hasheddrops`](https://github.com/MarioniLab/DropletUtils) + 7. [`hashsolo`](https://scanpy.readthedocs.io/en/stable/generated/scanpy.external.pp.hashsolo.html) +6. summarize assignments and classifications +7. Join all results +8. Donor match +9. Find informative variants +10. Create AnnData and Mudata objects +11. [`MultiQC`](http://multiqc.info/) ## Usage > [!NOTE] -> If you are new to Nextflow and nf-core, please refer to [this page](https://nf-co.re/docs/usage/installation) on how to set-up Nextflow. Make sure to [test your setup](https://nf-co.re/docs/usage/introduction#how-to-run-a-pipeline) with `-profile test` before running the workflow on actual data. - - +Each row contains data from a single-cell multiplexing experiment. The RNA-seq (`rna_matrix`) and hashing (`hto_matrix`) count matrices are provided in a 10x Genomics format and compressed as `.tar.gz`. +Genetic deconvolution requires both the alignment file (`bam`) and a list of common SNPs (`vcf`). Users must specify the number of multiplexed donors (`n_samples`) and identify the target cells for deconvolution (`barcodes`). Now, you can run the pipeline using: - - ```bash nextflow run nf-core/hadge \ -profile \ --input samplesheet.csv \ - --outdir + --outdir \ + --mode rescue \ + --hash_tools htodemux,hasheddrops,multiseq,gmm-demux,bff,hashsolo \ + --genetic_tools demuxlet,freemuxlet,vireo,souporcell \ + --fasta ``` > [!WARNING] @@ -78,10 +95,13 @@ For more details about the output files and reports, please refer to the ## Credits -nf-core/hadge was originally written by Fabiola Curion. +nf-core/hadge was originally written by Fabiola Curion ([@bio-la](https://github.com/bio-la)), Xichen Wu ([@wxicu](https://github.com/wxicu)), Lukas Heumos ([@zethson](https://github.com/Zethson)) and Mariana Gonzales Andre ([@mari-ga](https://github.com/mari-ga)). We thank the following people for their extensive assistance in the development of this pipeline: +- [Luis Heinzlmeier](https://github.com/LuisHeinzlmeier) +- [Nico Trummer](https://github.com/nictru) +- [Seo Hyon Kim](https://github.com/seohyonkim) ## Contributions and Support diff --git a/assets/samplesheet.csv b/assets/samplesheet.csv index 5f653ab7..8651f8c7 100644 --- a/assets/samplesheet.csv +++ b/assets/samplesheet.csv @@ -1,3 +1,4 @@ -sample,fastq_1,fastq_2 -SAMPLE_PAIRED_END,/path/to/fastq/files/AEG588A1_S1_L002_R1_001.fastq.gz,/path/to/fastq/files/AEG588A1_S1_L002_R2_001.fastq.gz -SAMPLE_SINGLE_END,/path/to/fastq/files/AEG588A4_S4_L003_R1_001.fastq.gz, +sample,rna_matrix,hto_matrix,bam,vcf,n_samples,barcodes +test1,https://github.com/nf-core/test-datasets/raw/refs/heads/hadge/testdata/rna.tar.gz,https://github.com/nf-core/test-datasets/raw/refs/heads/hadge/testdata/hto.tar.gz,https://github.com/nf-core/test-datasets/raw/refs/heads/hadge/testdata/chr21.bam,https://github.com/nf-core/test-datasets/raw/refs/heads/hadge/testdata/donor_genotype_chr21.vcf,2,https://github.com/nf-core/test-datasets/raw/refs/heads/hadge/testdata/barcodes.tsv +test2,https://github.com/nf-core/test-datasets/raw/refs/heads/hadge/testdata/rna.tar.gz,https://github.com/nf-core/test-datasets/raw/refs/heads/hadge/testdata/hto.tar.gz,https://github.com/nf-core/test-datasets/raw/refs/heads/hadge/testdata/chr21.bam,https://github.com/nf-core/test-datasets/raw/refs/heads/hadge/testdata/donor_genotype_chr21.vcf,2,https://github.com/nf-core/test-datasets/raw/refs/heads/hadge/testdata/barcodes.tsv +test3,https://github.com/nf-core/test-datasets/raw/refs/heads/hadge/testdata/rna.tar.gz,https://github.com/nf-core/test-datasets/raw/refs/heads/hadge/testdata/hto.tar.gz,https://github.com/nf-core/test-datasets/raw/refs/heads/hadge/testdata/chr21.bam,https://github.com/nf-core/test-datasets/raw/refs/heads/hadge/testdata/donor_genotype_chr21.vcf,2,https://github.com/nf-core/test-datasets/raw/refs/heads/hadge/testdata/barcodes.tsv diff --git a/assets/schema_input.json b/assets/schema_input.json index 2fa8591d..1b03be15 100644 --- a/assets/schema_input.json +++ b/assets/schema_input.json @@ -54,18 +54,6 @@ "default": null, "pattern": "^\\S+\\.vcf$", "errorMessage": "VCF file must be provided, cannot contain spaces and must have extension '.vcf'" - }, - "n_cells": { - "type": "integer", - "minimum": 1, - "errorMessage": "Number of cells must be an integer greater than 0", - "meta": ["n_cells"] - }, - "hto_names": { - "type": "string", - "pattern": "^\\S+$", - "errorMessage": "HTO names must be provided and cannot contain spaces", - "meta": ["hto_names"] } }, "required": ["sample"] diff --git a/bin/update_snapshots.sh b/bin/update_snapshots.sh new file mode 100755 index 00000000..7a3b3b93 --- /dev/null +++ b/bin/update_snapshots.sh @@ -0,0 +1,46 @@ +#!/bin/bash + +# Script to update snapshots for all nf-test test files +# ./bin/update_snapshots.sh # update only +# ./bin/update_snapshots.sh -c # update + consistency check + +CHECK_CONSISTENCY=false + +while getopts "c" opt; do + case $opt in + c) CHECK_CONSISTENCY=true ;; + esac +done + +echo "Updating snapshots for all tests..." +echo + +test_files=("default" "test_donor_match" "test_genetic" "test_hashing") + +for test_file in "${test_files[@]}"; do + if [ "$test_file" != "default" ]; then + test_profile="$test_file" + else + test_profile="test" + fi + + command="nf-test test tests/${test_file}.nf.test --profile ${test_profile},docker --update-snapshot" + + echo "Updating snapshot for: $test_file" + echo "Running: ${command}" + + eval "$command" + + echo "✓ Snapshot updated for: $test_file" + + # test if testing is consistent + if [[ "$CHECK_CONSISTENCY" == "true" ]]; then + echo "Re-running test to verify snapshot consistency for: $test_file" + command="nf-test test tests/${test_file}.nf.test --profile ${test_profile},docker" + echo "Running: ${command}" + eval "$command" + echo "✓ Consistency check passed for: $test_file" + fi +done + +echo "All snapshots have been updated!" diff --git a/conf/modules.config b/conf/modules.config index d120b4d6..c16a576d 100644 --- a/conf/modules.config +++ b/conf/modules.config @@ -31,6 +31,12 @@ process { ] } + withName: UNTAR { + publishDir = [ + enabled: false + ] + } + withName: UMITOOLS_DEDUP { ext.prefix = { "${meta.id}_dedup" } ext.args = '--extract-umi-method=tag --umi-tag=UR --cell-tag=CB' @@ -39,6 +45,12 @@ process { ] } + withName: EXTRACT_HASHES { + publishDir = [ + enabled: false + ] + } + withName: SAMTOOLS_SORT { ext.prefix = { "${meta.id}_sorted" } publishDir = [ @@ -66,10 +78,42 @@ process { ] } - withName: JOIN_RESULTS { + withName: FIND_VARIANTS { + publishDir = [ + path: { "${params.outdir}/find_variants/${meta.id}" }, + mode: params.publish_dir_mode, + saveAs: { filename -> filename.equals('versions.yml') ? null : filename }, + ] + } + + withName: SUBSET_GT_DONORS { + publishDir = [ + path: { "${params.outdir}/find_variants/${meta.id}/subset_gt_donors/" }, + mode: params.publish_dir_mode, + saveAs: { filename -> filename.equals('versions.yml') ? null : filename }, + ] + } + + withName: '.*:JOIN_RESULTS_(ASSIGNMENT|CLASSIFICATION)' { ext.args = "--outer-join --na negative" publishDir = [ - path: { "${params.outdir}/hash_and_gene_summary/${meta.id}" }, + path: { "${params.outdir}/summary/${meta.id}" }, + mode: params.publish_dir_mode, + saveAs: { filename -> filename.equals('versions.yml') ? null : filename }, + ] + } + + withName: JOIN_RESULTS_ASSIGNMENT { + ext.prefix = { "${meta.id}_assignment" } + } + + withName: JOIN_RESULTS_CLASSIFICATION { + ext.prefix = { "${meta.id}_classification" } + } + + withName: CREATE_ANNDATA_MUDATA { + publishDir = [ + path: { "${params.outdir}/summary/${meta.id}" }, mode: params.publish_dir_mode, saveAs: { filename -> filename.equals('versions.yml') ? null : filename }, ] @@ -287,6 +331,30 @@ process { ] } + withName: BFF { + ext.args = { + [ + params.bff_barcodeWhitelist ? "--barcodeWhitelist ${params.bff_barcodeWhitelist}" : "", + params.bff_cellbarcodeWhitelist ? "--cellbarcodeWhitelist ${params.bff_cellbarcodeWhitelist}" : "", + params.bff_methodsForConsensus ? "--methodsForConsensus ${params.bff_methodsForConsensus}" : "", + params.bff_metricsFile ? "--metricsFile ${params.bff_metricsFile}" : "", + "--doTSNE", + params.bff_doTSNE.toString().toUpperCase(), + "--doHeatmap", + params.bff_doHeatmap.toString().toUpperCase(), + params.bff_perCellSaturation ? "--perCellSaturation ${params.bff_perCellSaturation}" : "", + params.bff_majorityConsensusThreshold ? "--majorityConsensusThreshold ${params.bff_majorityConsensusThreshold}" : "", + "--chemistry", + params.bff_chemistry, + params.bff_callerDisagreementThreshold ? "--callerDisagreementThreshold ${params.bff_callerDisagreementThreshold}" : "", + ].findAll { arg -> arg != "" }.join(" ") + } + publishDir = [ + path: { "${params.outdir}/hashing/bff/${meta.id}" }, + mode: params.publish_dir_mode, + saveAs: { filename -> filename.equals('versions.yml') ? null : filename }, + ] + } withName: GMMDEMUX { ext.args = { @@ -338,7 +406,6 @@ process { ] } - // TODO update to ext.args withName: HTODEMUX_VISUALIZATION { ext.ridgePlot = params.htodemux_visualization_ridgePlot ext.ridgeNCol = params.htodemux_visualization_ridgeNCol @@ -358,7 +425,7 @@ process { ext.heatMap = params.htodemux_visualization_heatMap ext.heatMapNcells = params.htodemux_visualization_heatMapNcells publishDir = [ - path: { "${params.outdir}/hashing/htodemux_visualization/${meta.id}" }, + path: { "${params.outdir}/hashing/htodemux/${meta.id}/visualization" }, mode: params.publish_dir_mode, saveAs: { filename -> filename.equals('versions.yml') ? null : filename }, ] @@ -452,7 +519,6 @@ process { ] } - // TODO update to ext.args withName: PREPROCESSING_FOR_HTODEMUX_MULTISEQ { ext.sel_method = params.preprocessing_sel_method ext.ndelim = params.preprocessing_ndelim @@ -462,9 +528,7 @@ process { ext.norm_method = params.preprocessing_norm_method ext.gene_col = params.preprocessing_gene_col publishDir = [ - path: { "${params.outdir}/hashing/preprocessing/${meta.id}" }, - mode: params.publish_dir_mode, - saveAs: { filename -> filename.equals('versions.yml') ? null : filename }, + enabled: false ] } diff --git a/conf/test.config b/conf/test.config index 992a1466..bbef78f3 100644 --- a/conf/test.config +++ b/conf/test.config @@ -1,6 +1,6 @@ /* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - Nextflow config file for running minimal tests + Nextflow config file for running minimal tests (rescue mode) ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Defines input files and everything required to run a fast and simple pipeline test. @@ -23,17 +23,15 @@ params { config_profile_description = 'Minimal test dataset to check pipeline function' // Input data - input = 'https://github.com/nf-core/test-datasets/raw/refs/heads/hadge/samplesheet.csv' - hash_tools = 'hasheddrops,multiseq,hashsolo' - genetic_tools = 'freemuxlet,vireo,souporcell' mode = 'rescue' + hash_tools = 'hasheddrops,bff,gmm-demux' + genetic_tools = 'freemuxlet,vireo,souporcell' + input = 'https://github.com/nf-core/test-datasets/raw/refs/heads/hadge/samplesheet/samplesheet_rescue.csv' genome = 'GRCh38' fasta = 'https://raw.githubusercontent.com/nf-core/test-datasets/modules/data/genomics/homo_sapiens/genome/chr21/sequence/genome.fasta' bam_qc = true - // TODO debug demuxem (See this issue: modules/local/dropletutils/mtxconvert/templates/convert.R) - // all modules - // hash_tools = 'htodemux,hasheddrops,multiseq,demuxem,gmm-demux,cellhashr,hashsolo' + // all possible modules + // hash_tools = 'htodemux,hasheddrops,multiseq,demuxem,gmm-demux,bff,hashsolo' // genetic_tools = 'demuxlet,freemuxlet,vireo,souporcell' - // demuxem_min_signal_hashtag = 0 } diff --git a/conf/test_donor_match.config b/conf/test_donor_match.config new file mode 100644 index 00000000..fe0bc2c9 --- /dev/null +++ b/conf/test_donor_match.config @@ -0,0 +1,32 @@ +/* +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + Nextflow config file for running minimal tests +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + Defines input files and everything required to run a fast and simple donor matching test. + + Use as follows: + nextflow run nf-core/hadge -profile test_donor_match, --outdir + +---------------------------------------------------------------------------------------- +*/ + +process { + resourceLimits = [ + cpus: 4, + memory: '15.GB', + time: '1.h' + ] +} + +params { + config_profile_name = 'Test donor_match mode' + config_profile_description = 'Minimal test dataset to check pipeline function of donor_match mode' + + // Input data + mode = 'donor_match' + input = 'https://github.com/nf-core/test-datasets/raw/refs/heads/hadge/samplesheet/samplesheet_donor_match.csv' + demultiplexing_result = 'https://github.com/nf-core/test-datasets/raw/refs/heads/hadge/testdata/donor_match_assignment.csv' + vireo_filtered_variants = 'https://github.com/nf-core/test-datasets/raw/refs/heads/hadge/testdata/donor_match_filtered_variants.tsv' + cell_genotype = 'https://github.com/nf-core/test-datasets/raw/refs/heads/hadge/testdata/donor_match.cells.vcf.gz' + gt_donors = 'https://github.com/nf-core/test-datasets/raw/refs/heads/hadge/testdata/donor_match_GT_donors.vireo.vcf.gz' +} diff --git a/conf/test_genetic.config b/conf/test_genetic.config new file mode 100644 index 00000000..646efc75 --- /dev/null +++ b/conf/test_genetic.config @@ -0,0 +1,32 @@ +/* +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + Nextflow config file for running minimal tests +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + Defines input files and everything required to run a fast and simple genetic demultiplexing test. + + Use as follows: + nextflow run nf-core/hadge -profile test_genetic, --outdir + +---------------------------------------------------------------------------------------- +*/ + +process { + resourceLimits = [ + cpus: 4, + memory: '15.GB', + time: '1.h' + ] +} + +params { + config_profile_name = 'Test genetic mode' + config_profile_description = 'Minimal test dataset to check pipeline function of all genetic deconvolution methods in genetic mode' + + // Input data + mode = 'genetic' + genetic_tools = 'demuxlet,freemuxlet,vireo,souporcell' + input = 'https://github.com/nf-core/test-datasets/raw/refs/heads/hadge/samplesheet/samplesheet_genetic.csv' + genome = 'GRCh38' + fasta = 'https://raw.githubusercontent.com/nf-core/test-datasets/modules/data/genomics/homo_sapiens/genome/chr21/sequence/genome.fasta' + bam_qc = true +} diff --git a/conf/test_hashing.config b/conf/test_hashing.config new file mode 100644 index 00000000..ef30803a --- /dev/null +++ b/conf/test_hashing.config @@ -0,0 +1,31 @@ +/* +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + Nextflow config file for running minimal tests +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + Defines input files and everything required to run a fast and simple hashing demultiplexing test. + + Use as follows: + nextflow run nf-core/hadge -profile test_hashing, --outdir + +---------------------------------------------------------------------------------------- +*/ + +process { + resourceLimits = [ + cpus: 4, + memory: '15.GB', + time: '1.h' + ] +} + +params { + config_profile_name = 'Test hashing mode' + config_profile_description = 'Minimal test dataset to check pipeline function of all hash deconvolution methods in hashing mode' + + // Input data + mode = 'hashing' + hash_tools = 'htodemux,hasheddrops,multiseq,gmm-demux,bff,hashsolo' + input = 'https://github.com/nf-core/test-datasets/raw/refs/heads/hadge/samplesheet/samplesheet_hashing.csv' + + // TODO demuxem: include demuxem to hash_tools if #81 is fixed +} diff --git a/docs/images/pipeline.png b/docs/images/pipeline.png new file mode 100644 index 0000000000000000000000000000000000000000..b8a555e3446b0db76a4b3935cc11857e25f1add4 GIT binary patch literal 379669 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�{Txs$BppUT*rf?uT^%m^xX8$Md9=Li)|8Oc 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 +### Genetic-based deconvolution + +

    +Output files + +- `genetic/` + - `popscle/demuxlet/`: See [modules/popscle_demuxlet](https://nf-co.re/modules/popscle_demuxlet/) for detailed output information. + - `popscle/freemuxlet/`: See [modules/popscle_freemuxlet](https://nf-co.re/modules/popscle_freemuxlet/) for detailed output information. + - `souporcell/`: See [modules/souporcell](https://nf-co.re/modules/souporcell/) for detailed output information. + - `vireo/`: See [modules/vireo](https://nf-co.re/modules/vireo/) for detailed output information. +- `genetic/summary/` + - `/*/*_genetic_summary_(assignment|classification).csv`: Summary of all assigned/classified cells from each genetic-based deconvolution tool, merged into a single table. + - `/*/*_genetic_overview_(assignment|classification).csv`: This table summarizes each genetic-based deconvolution tool (before merging) by reporting its total barcode count, the number of barcodes it shares with every other method, and the counts of each donor label or classification category (e.g., `0`, `1`, `singlet`, `doublet`, `negative`). + +
    + +This subworkflow runs when the `--mode` is set to `genetic` or `rescue`. It saves the results of each genetic-based deconvolution specified with `--genetic_tools` in a folder named after the corresponding tool. Additionally, this step summarizes all deconvolution results in a single summary table. The assignments CSV provides, for each cell barcode, one of the following categories: + +| Category | Description | +| ------------- | ---------------------------------------------- | +| Cluster label | A cluster label (e.g., `0`, `1`, …) | +| `doublet` | More than one cluster is assigned to this cell | +| `negative` | All other cases (e.g., undetected cells) | + +Classification works similarly, except that cluster labels are replaced by the label `singlet`. + +### Hashing-based deconvolution + +
    +Output files + +- `hashing/` + - `bff/`: See [modules/bff](https://nf-co.re/modules/bff/) for detailed output information. + - `demuxem/`: See [modules/demuxem](https://nf-co.re/modules/bff/) for detailed output information. + - `gmm-demux/`: See [modules/gmmdemux](https://nf-co.re/modules/gmmdemux/) for detailed output information. + - `hasheddrops/`: See [modules/hasheddrops](https://nf-co.re/modules/hasheddrops/) for detailed output information. + - `hashsolo/`: See [modules/scanpy_hashsolo](https://nf-co.re/modules/scanpy_hashsolo/) for detailed output information. + - `htodemux/`: See [modules/htodemux](https://nf-co.re/modules/htodemux/) for detailed output information. + - `htodemux/visualization/`: Visualizations of htodemux results. + - `multiseq/`: See [modules/multiseqdemux](https://nf-co.re/modules/multiseqdemux/) for detailed output information. +- `hashing/summary/` + - `/*/*_hashing_summary_(assignment|classification).csv`: Summary of all assigned/classified cells from each hashing-based deconvolution tool, merged into a single table. + - `/*/*_hashing_overview_(assignment|classification).csv`: This table summarizes each hashing-based deconvolution tool (before merging) by reporting its total barcode count, the number of barcodes it shares with every other method, and the counts of each donor label or classification category (e.g., `HTO-1`, `HTO-2`, `singlet`, `doublet`, `negative`). + +
    + +This subworkflow runs when the `--mode` is set to `hashing` or `rescue`. It saves the results of each hashing-based deconvolution specified with `--hash_tools` in a folder named after the corresponding tool. Additionally, this step summarizes all deconvolution results in a single summary table. The assignments CSV provides, for each cell barcode, one of the following categories: + +| Category | Description | +| ----------- | ----------------------------------------------------------------- | +| Donor label | A HTO label that identifies the donor (e.g., `HTO-1`, `HTO-2`, …) | +| `doublet` | More than one donor is assigned to this cell | +| `negative` | All other cases (e.g., undetected cells) | + +Classification works similarly, except that cluster labels are replaced by the label `singlet`. + +### Donor matching + +
    +Output files + +- `donor_match/` + - `*_best_all_assignment_after_match.csv`: assignment of all cell barcodes based on the donor matching of the optimal match + - `*_best_donor_match.csv`: a map between hashtags and donor identities based on the donor matching of the optimal match + - `*_best_intersect_assignment_after_match.csv`: assignment of joint singlets based on the donor matching of the optimal match + - `*_score_record.csv`: a CSV file storing the matching score and the number of matched donors for each method pair +- `donor_match/[method1]_vs_[method2]/` + - `*_all_assignment_after_match.csv`: assignment of all cell barcodes after donor matching + - `*_concordance_heatmap.png`: a heatmap visualising the the correlation scores + - `*_correlation_res.csv`: correlation scores of donor matching + - `*_donor_match.csv`: a map between hashtag and donor identity. + - `*_intersect_assignment_after_match.csv`: assignment of joint singlets after donor matching + +
    + +For each hashing–genetic deconvolution method pair, pairwise Pearson correlations are computed between binarized cell assignment vectors to match donors. +A matching score is obtained by summing the non-negative correlations and dividing by the number of expected donors, providing a measure of agreement between methods. + +Donor matching is executed only when `--match_donor` is enabled (default). +Full output is produced in `rescue` and `donor_match` mode, while other modes generate a reduced set of files, as matching is restricted to comparisons within the same method type (genetic-to-genetic and hashing-to-hashing) and not across types. + +### Find variants + +
    +Output files + +- `find_variants/` + - `*_all_representative_variants.csv`: a list of representative variants from all donors + - `*_donor_specific_variants.csv`: a list of donor-specific variants + - `*_donor_specific_variants_upset.png`: an upset plot showing the number of donor-specific variants + - `*_vireo_variants.csv`: a list of discriminatory variants filtered by Vireo +- `donor_match/[hto_name]/` + - `*_informative_variants.csv`: informative variants for the donor indicated in the file name + - `*_matched_gt.csv`: genotype of the donor indicated in the file name + - `*_unmatched_gt.csv`: genotype of all other donors +- `donor_match/subset_gt_donors/` + - `_donor_specific.vcf.gz`: Donor genotypes of donor-specific variants + - `_vireo.vcf.gz`: Donor genotypes of a set of discriminatory variants filtered by Vireo + +
    + +Find variants runs only when `--match_donor` is enabled (default) and the workflow is in `rescue` or `donor_match` mode. +It outputs two sets of variants: informative variants from vireo and donor specific variants as described in the [paper](https://link.springer.com/article/10.1186/s13059-024-03249-z#:~:text=Vireo%20is%20implemented,others%20during%20deconvolution.). +A subset VCF of donor genotypes (produced by vireo) is then generated. + +### Summary + +
    +Output files + +- `summary/` + - `*_(assignment|classification).csv`: Combined assignment/classification table from all used genetic- and hashing-based deconvolution tools. + - `*_genetic.h5ad`: The RNA-seq count matrix with the assignments/classifications of all genetic tools saved in `.obs`. If the `barcodes.tsv` from the samplesheet input contains barcodes not present in the RNA-seq count matrix, those barcodes will be dropped when joining the results from the genetic tools. Therefore, it is recommended that both files share the same set of barcodes. + - `*_hashing.h5ad`: The hashing count matrix with the assignments/classifications of all hashing tools saved in `.obs`. + - `*_genetic_and_hashing.h5mu`: Both `genetic.h5ad` and `hashing.h5ad` combined in a MuData object. + +
    + +When running the pipeline in `rescue` mode, the combined assignment/classification table from all used genetic- and hashing-based deconvolution tools will be generated here. +Additionally, an AnnData/MuData object is created with the corresponding count matrices. ## Pipeline overview diff --git a/docs/usage.md b/docs/usage.md index 1fd733d4..e4d3c7c9 100644 --- a/docs/usage.md +++ b/docs/usage.md @@ -6,49 +6,171 @@ ## Introduction - +### The rescue mode -## Samplesheet input +The joint call of hashing and genetic deconvolution methods has been shown to be beneficial for cell recovery rate and calling accuracy. +hadge provides a rescue mode to run both genotype- and hashing-based approaches jointly to rescue problematic hashing experiments in cases where donors are genetically distinct. +In this scenario, samples of both hashing and genetic multiplexing experiments are deconvoluted simultaneously. +Furthermore, hadge allows for the automatic determination of the best combination of hashing and SNP-based donor deconvolution tools. -You will need to create a samplesheet with information about the samples you would like to analyse before running the pipeline. Use this parameter to specify its location. It has to be a comma-separated file with 3 columns, and a header row as shown in the examples below. +```csv title="samplesheet.csv" +sample,rna_matrix,hto_matrix,bam,vcf,n_samples,barcodes +id1,rna.tar.gz,hto.tar.gz,chr21.bam,donor_genotype_chr21.vcf,2,barcodes.tsv +id2,rna.tar.gz,hto.tar.gz,chr21.bam,donor_genotype_chr21.vcf,2,barcodes.tsv +id3,rna.tar.gz,hto.tar.gz,chr21.bam,donor_genotype_chr21.vcf,2,barcodes.tsv +``` + +Now, you can run the pipeline using: ```bash ---input '[path to samplesheet file]' +nextflow run nf-core/hadge \ + -profile \ + --input samplesheet.csv \ + --outdir \ + --mode rescue \ + --hash_tools htodemux,hasheddrops,multiseq,gmm-demux,bff,hashsolo \ + --genetic_tools demuxlet,freemuxlet,vireo,souporcell \ + --fasta ``` -### Multiple runs of the same sample +### The genetic mode -The `sample` identifiers have to be the same when you have re-sequenced the same sample more than once e.g. to increase sequencing depth. The pipeline will concatenate the raw reads before performing any downstream analysis. Below is an example for the same sample sequenced across 3 lanes: +Genotyped-based deconvolution assigns cells to donors using genetic variation. +This can be performed with donor genotypes or, if these are unavailable, using reference panels in genotype-free mode (e.g., 1000 Genomes). +Finally, it assigns SNPs to cells to determine donor identity but requires additional genotyping. ```csv title="samplesheet.csv" -sample,fastq_1,fastq_2 -CONTROL_REP1,AEG588A1_S1_L002_R1_001.fastq.gz,AEG588A1_S1_L002_R2_001.fastq.gz -CONTROL_REP1,AEG588A1_S1_L003_R1_001.fastq.gz,AEG588A1_S1_L003_R2_001.fastq.gz -CONTROL_REP1,AEG588A1_S1_L004_R1_001.fastq.gz,AEG588A1_S1_L004_R2_001.fastq.gz +sample,bam,vcf,n_samples,barcodes +id1,donor_genotype_chr21.vcf,2,barcodes.tsv +id2,donor_genotype_chr21.vcf,2,barcodes.tsv +id3,chr21.bam,donor_genotype_chr21.vcf,2,barcodes.tsv ``` -### Full samplesheet +Now, you can run the pipeline using: + +```bash +nextflow run nf-core/hadge \ + -profile \ + --input samplesheet.csv \ + --outdir \ + --mode genetic \ + --genetic_tools demuxlet,freemuxlet,vireo,souporcell \ + --fasta +``` + +:::info +A FASTA file is only required if `--genetic_tools` includes souporcell. +If a FASTA file is unavailable, you can specify the organism using `--genome`, and the pipeline will download the full reference genome automatically. +However, to avoid long download times and high bandwidth usage, we recommend providing your own local reference genome with `--fasta`. +::: + +### The hashing mode + +Cell hashing tags cells with unique oligo barcodes so samples can be pooled. +Separate scRNA and HTO libraries are sequenced, producing count matrices used to determine each cell’s sample of origin. + +```csv title="samplesheet.csv" +sample,rna_matrix,hto_matrix +id1,rna.tar.gz,hto.tar.gz +id2,rna.tar.gz,hto.tar.gz +id3,rna.tar.gz,hto.tar.gz +``` + +Now, you can run the pipeline using: + +```bash +nextflow run nf-core/hadge \ + -profile \ + --input samplesheet.csv \ + --outdir \ + --mode hashing + --hash_tools htodemux,hasheddrops,multiseq,gmm-demux,bff,hashsolo \ +``` -The pipeline will auto-detect whether a sample is single- or paired-end using the information provided in the samplesheet. The samplesheet can have as many columns as you desire, however, there is a strict requirement for the first 3 columns to match those defined in the table below. +### The donor match mode -A final samplesheet file consisting of both single- and paired-end data may look something like the one below. This is for 6 samples, where `TREATMENT_REP3` has been sequenced twice. +This mode utilizes the donor matching component from the rescue mode, but requires manual input for several stages. +To run all steps of donor matching, you must provide the demultiplexing results, filtered variants, and both cell and donor genotypes. +For detailed specifications on these input parameters, refer to the [parameter documentation](https://nf-co.re/hadge/parameters). ```csv title="samplesheet.csv" -sample,fastq_1,fastq_2 -CONTROL_REP1,AEG588A1_S1_L002_R1_001.fastq.gz,AEG588A1_S1_L002_R2_001.fastq.gz -CONTROL_REP2,AEG588A2_S2_L002_R1_001.fastq.gz,AEG588A2_S2_L002_R2_001.fastq.gz -CONTROL_REP3,AEG588A3_S3_L002_R1_001.fastq.gz,AEG588A3_S3_L002_R2_001.fastq.gz -TREATMENT_REP1,AEG588A4_S4_L003_R1_001.fastq.gz, -TREATMENT_REP2,AEG588A5_S5_L003_R1_001.fastq.gz, -TREATMENT_REP3,AEG588A6_S6_L003_R1_001.fastq.gz, -TREATMENT_REP3,AEG588A6_S6_L004_R1_001.fastq.gz, -``` - -| Column | Description | -| --------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `sample` | Custom sample name. This entry will be identical for multiple sequencing libraries/runs from the same sample. Spaces in sample names are automatically converted to underscores (`_`). | -| `fastq_1` | Full path to FastQ file for Illumina short reads 1. File has to be gzipped and have the extension ".fastq.gz" or ".fq.gz". | -| `fastq_2` | Full path to FastQ file for Illumina short reads 2. File has to be gzipped and have the extension ".fastq.gz" or ".fq.gz". | +sample,n_samples +id1,2 +``` + +Now, you can run the pipeline using: + +```bash +nextflow run nf-core/hadge \ + -profile \ + --input samplesheet.csv \ + --outdir \ + --mode donor_match + --demultiplexing_result
    \ + --vireo_filtered_variants \ + --cell_genotype \ + --gt_donors \ +``` + +## Samplesheet input + +You will need to create a samplesheet with information about the samples you would like to analyse before running the pipeline. +Use this parameter to specify its location. +It has to be a comma-separated file with a header row as shown in the examples below. + +```bash +--input '[path to samplesheet file]' +``` + +### Full samplesheet + +Each row in the sample sheet represents a distinct single-cell multiplexing experiment. +The `sample` column must contain a unique identifier for each experiment. +This format allows you to process multiple deconvolutions in a single run. +While a full example is provided below, some columns may be optional depending on the mode you select. + +```csv title="samplesheet.csv" +sample,rna_matrix,hto_matrix,bam,vcf,n_samples,barcodes +id1,rna.tar.gz,hto.tar.gz,chr21.bam,donor_genotype_chr21.vcf,2,barcodes.tsv +id2,rna.tar.gz,hto.tar.gz,chr21.bam,donor_genotype_chr21.vcf,2,barcodes.tsv +id3,rna.tar.gz,hto.tar.gz,chr21.bam,donor_genotype_chr21.vcf,2,barcodes.tsv +``` + +| Column | Description | +| ------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `sample` | Custom sample name. This entry will be identical for multiple sequencing libraries/runs from the same sample. Spaces in sample names are automatically converted to underscores (`_`). | +| `rna_matrix` | Full path to the RNA-Seq count matrices provided in a 10x Genomics format and compressed as `.tar.gz`. | +| `hto_matrix` | Full path to the hashing count matrices provided in a 10x Genomics format and compressed as `.tar.gz`. | +| `bam` | Full path to the alignment file (`.bam`). | +| `vcf` | Full path to the list of common SNPs (`.vcf`). | +| `n_samples` | The number of multiplexed donors. | +| `barcodes` | Full path to the list of cell barcodes (e.g., `barcodes.tsv` from Cell Ranger) | + +:::tip{collapse title="Samplesheet Input Requirements by Module"} + +| Mode | sample | rna_matrix | hto_matrix | bam | barcodes | n_samples | vcf | +| ----------- | :----: | :--------: | :--------: | :-: | :------: | :-------: | :-: | +| rescue | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | +| genetic | ✅ | ❌ | ❌ | ✅ | ✅ | ✅ | ✅ | +| hashing | ✅ | ✅ | ✅ | ❌ | ❌ | ❌ | ❌ | +| donor_match | ✅ | ❌ | ❌ | ❌ | ❌ | ✅ | ❌ | + +| Module | sample | rna_matrix | hto_matrix | bam | barcodes | n_samples | vcf | +| ----------- | :----: | :--------: | :--------: | :-: | :------: | :-------: | :-: | +| htodemux | ✅ | ✅ | ✅ | ❌ | ❌ | ❌ | ❌ | +| multiseq | ✅ | ✅ | ✅ | ❌ | ❌ | ❌ | ❌ | +| bff | ✅ | ❌ | ✅ | ❌ | ❌ | ❌ | ❌ | +| demuxem | ✅ | ✅ | ✅ | ❌ | ❌ | ❌ | ❌ | +| gmm-demux | ✅ | ❌ | ✅ | ❌ | ❌ | ❌ | ❌ | +| hasheddrops | ✅ | ✅\* | ✅ | ❌ | ❌ | ❌ | ❌ | +| hashsolo | ✅ | ❌ | ✅ | ❌ | ❌ | ❌ | ❌ | +| vireo | ✅ | ❌ | ❌ | ✅ | ✅ | ✅ | ✅ | +| demuxlet | ✅ | ❌ | ❌ | ✅ | ❌ | ❌ | ✅ | +| freemuxlet | ✅ | ❌ | ❌ | ✅ | ❌ | ✅ | ✅ | +| souporcell | ✅ | ❌ | ❌ | ✅ | ✅ | ✅ | ❌ | + +\* if `params.hasheddrops_runEmptyDrops` is true +::: An [example samplesheet](../assets/samplesheet.csv) has been provided with the pipeline. diff --git a/modules.json b/modules.json index c544f2be..f7f93ab4 100644 --- a/modules.json +++ b/modules.json @@ -109,7 +109,7 @@ }, "vireo": { "branch": "master", - "git_sha": "05954dab2ff481bcb999f24455da29a5828af08d", + "git_sha": "adfd5ba7915e96ccf41ccc0b4be859c398d0eb95", "installed_by": ["modules"] } } diff --git a/modules/local/create_anndata_mudata/environment.yml b/modules/local/create_anndata_mudata/environment.yml new file mode 100644 index 00000000..e1185d62 --- /dev/null +++ b/modules/local/create_anndata_mudata/environment.yml @@ -0,0 +1,9 @@ +channels: + - conda-forge + - bioconda +dependencies: + - conda-forge::anndata=0.12.7 + - conda-forge::mudata=0.3.2 + - conda-forge::pandas=2.3.3 + - conda-forge::pyyaml=6.0.3 + - conda-forge::scanpy=1.11.5 diff --git a/modules/local/create_anndata_mudata/main.nf b/modules/local/create_anndata_mudata/main.nf new file mode 100644 index 00000000..9976ad42 --- /dev/null +++ b/modules/local/create_anndata_mudata/main.nf @@ -0,0 +1,49 @@ +process CREATE_ANNDATA_MUDATA { + tag "${meta.id}" + label 'process_low' + + conda "${moduleDir}/environment.yml" + container "${ workflow.containerEngine == 'singularity' && !task.ext.singularity_pull_docker_container ? + 'https://community-cr-prod.seqera.io/docker/registry/v2/blobs/sha256/31/31c261a4a1ed9c3b409457fe778a363fb941152f7307bfa76cb4c42d44235ddf/data': + 'community.wave.seqera.io/library/anndata_mudata_pandas_pyyaml_scanpy:e96a91e210372525' }" + + input: + tuple val(meta), + path(rna_matrix), + path(hto_matrix), + path(genetic_summary_assignment), + path(genetic_summary_classification), + path(hashing_summary_assignment), + path(hashing_summary_classification) + + output: + tuple val(meta), path("*_genetic.h5ad") , emit: h5ad_genetic, optional: true + tuple val(meta), path("*_hashing.h5ad") , emit: h5ad_hashing, optional: true + tuple val(meta), path("*_genetic_and_hashing.h5mu"), emit: h5mu , optional: true + path "versions.yml" , emit: versions + + when: + task.ext.when == null || task.ext.when + + script: + prefix = task.ext.prefix ?: "${meta.id}" + + template 'create_anndata_mudata.py' + + stub: + def prefix = task.ext.prefix ?: "${meta.id}" + """ + touch ${prefix}_genetic.h5ad + touch ${prefix}_hashing.h5ad + touch ${prefix}_genetic_and_hashing.h5mu + + cat <<-END_VERSIONS > versions.yml + "${task.process}": + python: \$(python3 -c 'import platform; print(platform.python_version())') + pandas: \$(python3 -c 'import pandas as pd; print(pd.__version__)') + scanpy: \$(python3 -c 'import scanpy as sc; print(sc.__version__)') + mudata: \$(python3 -c 'import mudata as md; print(md.__version__)') + anndata: \$(python3 -c 'import anndata as ad; print(ad.__version__)') + END_VERSIONS + """ +} diff --git a/modules/local/create_anndata_mudata/templates/create_anndata_mudata.py b/modules/local/create_anndata_mudata/templates/create_anndata_mudata.py new file mode 100644 index 00000000..86ac15cf --- /dev/null +++ b/modules/local/create_anndata_mudata/templates/create_anndata_mudata.py @@ -0,0 +1,140 @@ +#!/usr/bin/env python3 + +# versions +import platform +import yaml + +import pandas as pd +import scanpy as sc +import mudata as md +import anndata as ad + +from anndata import AnnData +from pathlib import Path +from mudata import MuData + + +class Arguments: + """Parses the arguments, including the ones coming from $task.ext.args. + Adopted from mygene module (Suzanne Jin).""" + + def __init__(self) -> None: + self.parse_input_args() + self.creat_output_dirs() + + def parse_input_args(self) -> None: + self.prefix = "$task.ext.prefix" if "$task.ext.prefix" != "null" else "$meta.id" + self.negative_str = "negative" + + self.hto_matrix = "${hto_matrix}" + self.rna_matrix = "${rna_matrix}" + self.hashing_summary_assignment = "${hashing_summary_assignment}" + self.hashing_summary_classification = "${hashing_summary_classification}" + self.genetic_summary_assignment = "${genetic_summary_assignment}" + self.genetic_summary_classification = "${genetic_summary_classification}" + + path_vars = { + "rna_matrix", + "hto_matrix", + "hashing_summary_assignment", + "hashing_summary_classification", + "genetic_summary_assignment", + "genetic_summary_classification", + } + + def _tranlate_to_python(input_str, value_str): + if value_str.strip() == "": + return None + else: + path = Path(value_str) + if not path.exists(): + raise FileNotFoundError(f"Path does not exist: {path}") + return path + + for var in path_vars: + raw_value = getattr(self, var) + processed_value = _tranlate_to_python(var, raw_value) + setattr(self, var, processed_value) + + def creat_output_dirs(self) -> None: + directories = { + "h5mu": "_genetic_and_hashing.h5mu", + "genetic": "_genetic.h5ad", + "hashing": "_hashing.h5ad", + } + + for output, directory in directories.items(): + setattr(self, output, self.prefix + directory) + + def print_args(self) -> None: + for attr in vars(self): + print(f"{attr}: {getattr(self, attr)}") + + +def save_adata(args: Arguments, is_rna: bool, count_data: AnnData) -> AnnData: + if is_rna: + summary_files = { + "genetic_summary_assignment", + "genetic_summary_classification", + } + else: + summary_files = { + "hashing_summary_assignment", + "hashing_summary_classification", + } + + for file in summary_files: + path = getattr(args, file) + if path is None: + continue + summary_table = pd.read_csv(path) + summary_table.set_index("Barcode", inplace=True) + summary_table = summary_table.add_suffix(f"_{file.split('_')[-1]}") + count_data.obs = count_data.obs.join(summary_table, how="left").fillna( + args.negative_str + ) + + # sort columns for consistent output + count_data.obs = count_data.obs[sorted(count_data.obs.columns)] + + if is_rna: + count_data.write(args.genetic) + else: + count_data.write(args.hashing) + + return count_data + +if __name__ == "__main__": + args = Arguments() + + if args.rna_matrix is not None and args.hto_matrix is not None: + rna_data = sc.read_10x_mtx(args.rna_matrix) + hto_data = sc.read_10x_mtx(args.hto_matrix, gex_only=False) + + rna_data = save_adata(args, True, rna_data) + hto_data = save_adata(args, False, hto_data) + + mudata = MuData({"rna": rna_data, "hto": hto_data}) + mudata.update() + mudata.write(args.h5mu) + + elif args.rna_matrix is not None: + rna_data = sc.read_10x_mtx(args.rna_matrix) + save_adata(args, True, rna_data) + + elif args.hto_matrix is not None: + hto_data = sc.read_10x_mtx(args.hto_matrix, gex_only=False) + save_adata(args, False, hto_data) + + versions = { + "${task.process}": { + "python": platform.python_version(), + "pandas": pd.__version__, + "scanpy": sc.__version__, + "mudata": md.__version__, + "anndata": ad.__version__, + } + } + + with open("versions.yml", "w") as f: + yaml.dump(versions, f) diff --git a/modules/local/donor_match/environment.yml b/modules/local/donor_match/environment.yml index 56703c6b..834a490c 100644 --- a/modules/local/donor_match/environment.yml +++ b/modules/local/donor_match/environment.yml @@ -2,8 +2,6 @@ channels: - conda-forge - bioconda dependencies: - - bioconda::r-pheatmap=1.0.8 - - conda-forge::r-complexupset=1.3.3 - conda-forge::r-data.table=1.17.8 + - conda-forge::r-pheatmap=1.0.13 - conda-forge::r-tidyverse=2.0.0 - - conda-forge::r-vcfr=1.15.0 diff --git a/modules/local/donor_match/main.nf b/modules/local/donor_match/main.nf index 3eccfbd2..f014c13a 100644 --- a/modules/local/donor_match/main.nf +++ b/modules/local/donor_match/main.nf @@ -4,28 +4,25 @@ process DONOR_MATCH { conda "${moduleDir}/environment.yml" container "${ workflow.containerEngine == 'singularity' && !task.ext.singularity_pull_docker_container ? - 'https://community-cr-prod.seqera.io/docker/registry/v2/blobs/sha256/45/45b060e69064c7a7894787b0cc29259bbb24357650d06b627912b56d3521899b/data': - 'community.wave.seqera.io/library/r-complexupset_r-data.table_r-pheatmap_r-r.utils_pruned:3bd8312041c22554' }" + 'https://community-cr-prod.seqera.io/docker/registry/v2/blobs/sha256/d9/d9138b380ca73daad0b5ad74a10b46324ca4f676efdf199f9dc9cb9145a4590c/data': + 'community.wave.seqera.io/library/r-data.table_r-pheatmap_r-tidyverse:ac2dbc33f827dbb9' }" - //TODO findVariant = true not implemented input: - tuple val(meta), path(barcode_whitelist), path(demultiplexing_result), val(cell_genotype), val(vireo_parent_dir) + tuple val(meta), path(demultiplexing_result) val match_donor_method1 val match_donor_method2 - val findVariants - val variant_count - val variant_pct output: - // best method combination for rescue/donor_match mode (has to be optional because runs with only genetic or hashing won't return this output) + // donor match will return the optional outputs in rescue and donor_match mode but not in genetic or hashing mode + // best combination of a genetic and hashing deconvolution method tuple val(meta), path("*_best_donor_match.csv") , emit: best_donor_match , optional:true - tuple val(meta), path("*_best_all_assignment_after_match.csv") , emit: best_assignment_after_match , optional:true + tuple val(meta), path("*_best_all_assignment_after_match.csv") , emit: best_all_assignment_after_match , optional:true tuple val(meta), path("*_best_intersect_assignment_after_match.csv") , emit: best_intersect_assignment_after_match, optional:true tuple val(meta), path("*_score_record.csv") , emit: score_record , optional:true // comparison between deconvolution methods - tuple val(meta), path("*/*_vs_*all_assignment_after_match.csv") , emit: assignment_after_match - tuple val(meta), path("*/*_vs_*intersect_assignment_after_match.csv"), emit: assignment_intersect_match + tuple val(meta), path("*/*_vs_*all_assignment_after_match.csv") , emit: assignment_after_match , optional:true + tuple val(meta), path("*/*_vs_*intersect_assignment_after_match.csv"), emit: assignment_intersect_match , optional:true tuple val(meta), path("*/*_vs_*correlation_res.csv") , emit: correlation tuple val(meta), path("*/*_vs_*donor_match.csv") , emit: donor_match tuple val(meta), path("*/*_vs_*concordance_heatmap.png") , emit: concordance_heatmap @@ -35,10 +32,6 @@ process DONOR_MATCH { task.ext.when == null || task.ext.when script: - // TODO for findVariant = true (not used by findVariant = false) - def cell_genotype_path = '' - def vireo_parent_path = '' - def ndonor = "${meta.n_sample}" prefix = task.ext.prefix ?: "${meta.id}" template('donor_match.R') @@ -60,11 +53,9 @@ process DONOR_MATCH { cat <<-END_VERSIONS > versions.yml "${task.process}": r-base: \$(Rscript -e "cat(paste(R.version[['major']], R.version[['minor']], sep='.'))") - r-complexupset: \$(Rscript -e "library(ComplexUpset); cat(as.character(packageVersion('ComplexUpset')))") r-data.table: \$(Rscript -e "library(data.table); cat(as.character(packageVersion('data.table')))") r-pheatmap: \$(Rscript -e "library(pheatmap); cat(as.character(packageVersion('pheatmap')))") r-tidyverse: \$(Rscript -e "library(tidyverse); cat(as.character(packageVersion('tidyverse')))") - r-vcfr: \$(Rscript -e "library(vcfR); cat(as.character(packageVersion('vcfR')))") END_VERSIONS """ } diff --git a/modules/local/donor_match/templates/donor_match.R b/modules/local/donor_match/templates/donor_match.R index e5bec4fc..9eb6ac0d 100644 --- a/modules/local/donor_match/templates/donor_match.R +++ b/modules/local/donor_match/templates/donor_match.R @@ -1,54 +1,5 @@ #!/usr/bin/env Rscript -################################################ -################################################ -## Functions to handle Nextflow input ## -################################################ -################################################ - -#' Check for Non-Empty, Non-Whitespace String -#' -#' This function checks if the input is non-NULL and contains more than just whitespace. -#' It returns TRUE if the input is a non-empty, non-whitespace string, and FALSE otherwise. -#' -#' @param input A variable to check. -#' @return A logical value: TRUE if the input is a valid, non-empty, non-whitespace string; FALSE otherwise. - -is_valid_string <- function(input) { - !is.null(input) && nzchar(trimws(input)) -} - -# Helper function for NULL condition -string_to_null <- function(x, val = "[]") if (x == val) NULL else x -null_to_string <- function(x, val = "NULL") if (is.null(x)) val else x - -#' Parse out options from a string without recourse to optparse -#' -#' @param x Long-form argument list like --opt1 val1 --opt2 val2 -#' -#' @return named list of options and values similar to optparse - -parse_args <- function(x){ - args_list <- unlist(strsplit(x, ' ?--')[[1]])[-1] - args_vals <- lapply(args_list, function(x) scan(text=x, what='character', quiet = TRUE)) - - # Ensure the option vectors are length 2 (key/ value) to catch empty ones - args_vals <- lapply(args_vals, function(z){ length(z) <- 2; z}) - - parsed_args <- structure(lapply(args_vals, function(x) x[2]), names = lapply(args_vals, function(x) x[1])) - parsed_args[! is.na(parsed_args)] -} - -string_to_logical <- function(input) { - if (input == "FALSE") { - FALSE - } else if (input == "TRUE") { - TRUE - } else { - stop(paste0(input, " is not a valid logical. Use 'FALSE' or 'TRUE'.")) - } -} - ################################################ ################################################ ## Functions for the script ## @@ -58,7 +9,6 @@ string_to_logical <- function(input) { convert2binary <- function(result_csv, method_name, min_cell) { #' Convert categorical donor assignments from a method into a binary (one-hot encoded) matrix of cells vs donors. #' Filters out invalid labels ("negative", "doublet", NA) if at least two different singlets assigments exist. - #' Returns NULL if the number of valid cells is below the specified threshold. method_assign <- result_csv %>% select(all_of(c("Barcode", method_name))) donor_id <- setdiff( @@ -88,60 +38,49 @@ convert2binary <- function(result_csv, method_name, min_cell) { return(method_assign_binary) } +################################################ +################################################ +## Functions to handle Nextflow input ## +################################################ +################################################ + +string_to_null <- function(x, val = "[]") if (x == val) NULL else x + +check_files <- function(args) { + + files <- c( + "result_csv" + ) + + for (f in files) { + if(!file.exists(args[[f]])){ + stop(sprintf("'%s' does not exist.", f)) + } + if(dir.exists(args[[f]])){ + stop(sprintf("'%s' is a directory but must be a file.", f)) + } + } +} + ################################################ ################################################ ## PARSE PARAMETERS FROM NEXTFLOW ## ################################################ ################################################ -# Set defaults and classes args <- list( - # File inputs result_csv = '$demultiplexing_result', - barcode = '$barcode_whitelist', ndonor = as.numeric('$meta.n_samples'), - cell_genotype = '$cell_genotype', - vireo_parent_dir = '$vireo_parent_dir', - - # second in puts method1 = string_to_null('$match_donor_method1'), method2 = string_to_null('$match_donor_method2'), - findVariants = as.logical('$findVariants'), - variant_count = as.numeric('$variant_count'), - variant_pct = as.numeric('$variant_pct'), - - # others - prefix = '$prefix', # Prefix name for output files. + prefix = '$prefix', outputdir = "" ) -opt_types <- lapply(args, class) - -# Apply parameter overrides -args_opt <- parse_args('$task.ext.args') -for ( ao in names(args_opt)){ - if (! ao %in% names(opt)){ - stop(paste("Invalid option:", ao)) - }else{ - # Handle special cases for logicals - if (opt_types[[ao]] == "logical") { - opt[[ao]] <- string_to_logical(args_opt[[ao]]) - } else if (! is.null(opt[[ao]])){ - # Preserve classes from defaults where possible - opt[[ao]] <- as(args_opt[[ao]], opt_types[[ao]]) - } else { - opt[[ao]] <- args_opt[[ao]] - } - } -} -# Configure output precision -options(digits=5) +check_files(args) -# Check if file exists -# TODO check if files exist (not necessary for now) -# if (! file.exists(seuratObj)){ -# stop(paste0(seuratObj, ' is not a valid file')) -# } +# configure output precision +options(digits=5) ################################################ ################################################ @@ -151,9 +90,7 @@ options(digits=5) library(pheatmap) library(data.table) -library(ComplexUpset) library(tidyverse) -library(vcfR) ################################################ ################################################ @@ -164,7 +101,6 @@ library(vcfR) # set TRUE to see print outputs for debugging debugging <- FALSE -# read assignment_all csv result_csv <- NULL min_cell <- 0 if (file.exists(args\$result_csv) && !dir.exists(args\$result_csv)) { @@ -176,18 +112,7 @@ if (file.exists(args\$result_csv) && !dir.exists(args\$result_csv)) { ) } -# remove barcode that are not in the whitelist -if (!is.null(args\$barcode)) { - barcode_whitelist <- fread(args\$barcode, - header = FALSE, - stringsAsFactors = FALSE - )\$V1 - result_csv <- - result_csv[result_csv\$Barcode %in% barcode_whitelist, ] -} - - -# finds all columns in the CSV that contain at least one real donor label (not “negative” or “doublet”), and returns their column names +# finds all columns in the CSV that contain at least one real donor label (not negative or doublet) colname_with_singlet <- colnames(result_csv %>% select_if(~ any(. != "negative" & . != "doublet"))) @@ -231,7 +156,7 @@ if (!is.null(args\$method1) && !is.null(args\$method2)) { } else { - # get all column names that are genetic + # genetic column names genetics_all <- Filter(function(x) { any(sapply(genetic_methods, function(y) { @@ -239,7 +164,7 @@ if (!is.null(args\$method1) && !is.null(args\$method2)) { })) }, colname_with_singlet) - # get all column names that are hashing + # hashing column names hashing_all <- Filter(function(x) { any(sapply(hashing_methods, function(y) { @@ -249,23 +174,17 @@ if (!is.null(args\$method1) && !is.null(args\$method2)) { # Build pairs of methods that we want to compare in the for-loop - - # Match between genetics- and hashing-based methods if (length(hashing_all) > 0 && length(genetics_all) > 0) { all_methods_pair <- expand.grid(genetics = genetics_all, hashing = hashing_all) method1_all <- as.character(all_methods_pair\$genetics) method2_all <- as.character(all_methods_pair\$hashing) } - - # Compare only within hashing methods else if (length(hashing_all) > 0) { method_pair <- combn(hashing_all, 2) method1_all <- method_pair[1, ] method2_all <- method_pair[2, ] } - - # Compare only within genetics methods else if (length(genetics_all) > 0) { method_pair <- combn(genetics_all, 2) method1_all <- method_pair[1, ] @@ -293,7 +212,6 @@ if (is.null(method1_all) || is.null(method2_all)) { for (i in 1:length(method1_all)) { - # extract the pair of methods we would like to compare now method1 <- method1_all[i] method2 <- method2_all[i] @@ -323,7 +241,7 @@ for (i in 1:length(method1_all)) { } # Extract barcodes classified as singlets by both methods. - # This meaning of intersect is not true for edge cases + # This meaning of intersect is not true for edge cases # where a method assigned only one singlet label (see convert2binary if-statement). intersect_barcode <- intersect(rownames(method1_res), rownames(method2_res)) @@ -346,7 +264,7 @@ for (i in 1:length(method1_all)) { }, silent = TRUE ) - # Skip this method pair if correlation calculation failed + if (inherits(correlation_res, "try-error")) { cat("Failed to calculate phi coefficient") next @@ -492,7 +410,7 @@ for (i in 1:length(method1_all)) { } } -# TODO what is if there is more than one best match between methods? +# if there is more than one best match between methods the last best_method will be printed if (best_method1 != "None" && best_method2 != "None" && debugging) { print( paste0( @@ -514,214 +432,6 @@ if (nrow(result_record) > 1) { ) } -# TODO findVariants = true not implemented yet - -if (args\$findVariants == "True" || args\$findVariants == "default") { - if (startsWith(best_method1, "vireo")) { - write.table( - best_method1, - file.path(args\$outputdir, "best_method_vireo.txt"), - sep = "\t", - row.names = FALSE, - col.names = FALSE, - quote = FALSE - ) - } else { - stop("Vireo is not the best method for donor matching!") - } - outputdir <- - file.path(args\$outputdir, paste0(best_method1, "_vs_", best_method2)) - outputdir_variant <- file.path(outputdir, "variant_filtering") - ifelse(!dir.exists(outputdir_variant), - dir.create(outputdir_variant), - FALSE - ) - result_merge_new <- - fread(file.path(outputdir, "intersect_assignment_after_match.csv"), - header = T - ) - result_merge_new\$match <- - result_merge_new[[best_method1]] == result_merge_new[[best_method2]] - matched <- result_merge_new[result_merge_new\$match, ] - unmatched <- - result_csv[!result_csv\$Barcode %in% matched\$Barcode, ]\$Barcode - cell_genotype_vcf <- read.vcfR(args\$cell_genotype) - cell_genotype_vcf_gt <- - extract.gt(cell_genotype_vcf, - element = "GT", - as.numeric = TRUE - ) - donors <- sort(unique(matched[[best_method1]])) - - representative_variant_list <- - vector(mode = "list", length = length(donors)) - representative_variant_list <- - setNames(representative_variant_list, donors) - - for (donorid in donors) { - matched_barcode <- - matched[matched[[best_method1]] == donorid]\$Barcode - matched_gt_list <- cell_genotype_vcf_gt[, matched_barcode] - matched_gt_list <- - matched_gt_list[rowSums(is.na(matched_gt_list)) != ncol(matched_gt_list), ] - matched_gt <- - as.data.frame(matrix(nrow = nrow(matched_gt_list))) - matched_gt\$ref <- rowSums(matched_gt_list == 0, na.rm = TRUE) - matched_gt\$alt <- rowSums(matched_gt_list != 0, na.rm = TRUE) - matched_gt\$V1 <- rownames(matched_gt_list) - matched_gt\$count <- matched_gt\$ref + matched_gt\$alt - matched_gt\$pct <- - matched_gt\$alt / (matched_gt\$ref + matched_gt\$alt) - matched_gt\$dominant <- ifelse(matched_gt\$pct > 0.5, 1, 0) - matched_gt <- matched_gt[(matched_gt\$pct >= args\$variant_pct | - matched_gt\$pct <= (1 - args\$variant_pct)), ] - matched_gt <- - matched_gt[matched_gt\$count >= args\$variant_count, ] - - unmatched_gt_list <- cell_genotype_vcf_gt[, unmatched] - unmatched_gt_list <- - unmatched_gt_list[rownames(unmatched_gt_list) %in% matched_gt\$V1, ] - unmatched_gt_list <- - unmatched_gt_list[rowSums(is.na(unmatched_gt_list)) != ncol(unmatched_gt_list), ] - unmatched_gt_list <- - cbind(rownames(unmatched_gt_list), unmatched_gt_list) - unmatched_gt_list <- - melt(data.table(unmatched_gt_list), id.vars = "V1") - unmatched_gt_list <- - unmatched_gt_list[!is.na(unmatched_gt_list\$value), ] - colnames(unmatched_gt_list) <- c("variant", "cell", "allele") - - write.csv(matched_gt, - file.path(outputdir_variant, paste0(donorid, "_matched_gt.csv")), - row.names = FALSE - ) - write.csv(unmatched_gt_list, - file.path(outputdir_variant, paste0(donorid, "_unmatched_gt.csv")), - row.names = FALSE - ) - - informative_variants_cells <- - merge( - matched_gt, - unmatched_gt_list, - by.x = c("V1", "dominant"), - by.y = c("variant", "allele") - ) - colnames(informative_variants_cells)[1] <- "variant" - num_informative_variants <- informative_variants_cells %>% - group_by(cell) %>% - summarise(matched = n()) - if (nrow(unmatched_gt_list[!unmatched_gt_list\$cell %in% num_informative_variants\$cell, ]) > 0) { - print(unmatched_gt_list[!unmatched_gt_list\$cell %in% num_informative_variants\$cell, ]) - } - representative_variant_list[[donorid]] <- - list(unique(informative_variants_cells\$variant)) - write.table( - unique(informative_variants_cells\$variant), - file.path( - outputdir_variant, - paste0(donorid, "_informative_variants.csv") - ), - row.names = FALSE, - col.names = FALSE - ) - } - - representative_variant <- - rbindlist(representative_variant_list, idcol = "donor") - colnames(representative_variant)[2] <- "variant" - representative_variant_df <- - dcast(data = representative_variant, variant ~ donor, length) - write.csv( - representative_variant_df, - file.path(args\$outputdir, "all_representative_variant_df.csv") - ) - - upset <- ComplexUpset::upset( - representative_variant_df, - donors, - width_ratio = 0.45, - height_ratio = 0.9, - stripes = "white", - max_degree = 1, - name = "Number of donor-specific variants", - set_sizes = ( - upset_set_size() + - geom_text( - aes(label = ..count.., size = 3), - hjust = -0.1, - stat = "count", - color = "white", - size = 2.3 - ) + - theme( - axis.text.x = element_text(angle = 90), - text = element_text(size = 10) - ) - ), - base_annotations = list("Intersection size" = intersection_size()) - ) - ggsave(file.path(args\$outputdir, "donor_specific_variants_upset.png")) - representative_variant_single <- - representative_variant_df[rowSums(representative_variant_df[, -1]) == 1, ] - representative_variant_single <- - separate( - representative_variant_single, - col = "variant", - into = c("chr", "pos"), - sep = "_" - ) - write.table( - representative_variant_single[, c("chr", "pos")], - quote = FALSE, - col.names = FALSE, - sep = "\t", - row.names = FALSE, - file.path(args\$outputdir, "donor_specific_variants.csv") - ) -} - -if (args\$findVariants == "True" || args\$findVariants == "vireo") { - if (startsWith(best_method1, "vireo")) { - write.table( - best_method1, - file.path(args\$outputdir, "best_method_vireo.txt"), - sep = "\t", - row.names = FALSE, - col.names = FALSE, - quote = FALSE - ) - } else { - stop("Vireo is not the best method1 for donor matching, variants can not be filtered!") - } - - vireo_result_dir <- file.path(args\$vireo_parent_dir, best_method1) - - representative_variant <- - list.files( - vireo_result_dir, - "filtered_variants.tsv", - full.names = TRUE, - recursive = TRUE - )[1] - representative_variant <- fread(representative_variant) - representative_variant <- separate( - representative_variant, - col = "variants", - into = c("chr", "pos"), - sep = "_", - extra = "drop" - ) - write.table( - representative_variant[, c("chr", "pos")], - quote = FALSE, - col.names = FALSE, - sep = "\t", - row.names = FALSE, - file.path(args\$outputdir, "representative_variants_vireo.csv") - ) -} - ################################################ ################################################ ## VERSIONS FILE ## @@ -731,18 +441,14 @@ if (args\$findVariants == "True" || args\$findVariants == "vireo") { r.version <- paste(R.version[['major']],R.version[['minor']], sep = ".") pheatmap.version <- as.character(packageVersion('pheatmap')) data_table.version <- as.character(packageVersion('data.table')) -complexUpset.version <- as.character(packageVersion('ComplexUpset')) tidyverse.version <- as.character(packageVersion('tidyverse')) -vcfR.version <- as.character(packageVersion('vcfR')) writeLines( c( '"${task.process}":', paste(' r-base:', r.version), - paste(' r-complexupset:', complexUpset.version), paste(' r-data.table:', data_table.version), paste(' r-pheatmap:', pheatmap.version), - paste(' r-tidyverse:', tidyverse.version), - paste(' r-vcfr:', vcfR.version) + paste(' r-tidyverse:', tidyverse.version) ), 'versions.yml') diff --git a/modules/local/dropletutils/mtxconvert/templates/convert.R b/modules/local/dropletutils/mtxconvert/templates/convert.R index e554196d..8d43487e 100644 --- a/modules/local/dropletutils/mtxconvert/templates/convert.R +++ b/modules/local/dropletutils/mtxconvert/templates/convert.R @@ -18,7 +18,7 @@ if ("${write_csv}" == "true") { write.csv(as.matrix(count_matrix), file = "${prefix}.csv", row.names = TRUE) } -# TODO remove if demuxEM issue is solved +# TODO demuxem: remove if demuxEM issue is solved (https://github.com/theislab/hadge/issues/81) # Write to h5 file # write10xCounts( # path = "${prefix}.h5", diff --git a/modules/local/extract_hashes/environment.yml b/modules/local/extract_hashes/environment.yml new file mode 100644 index 00000000..4c0ab7e4 --- /dev/null +++ b/modules/local/extract_hashes/environment.yml @@ -0,0 +1,7 @@ +channels: + - conda-forge + - bioconda +dependencies: + - conda-forge::coreutils=9.5 + - conda-forge::gawk=5.3.1 + - conda-forge::gzip=1.14 diff --git a/modules/local/extract_hashes/main.nf b/modules/local/extract_hashes/main.nf index 3c86a80d..1a756d44 100644 --- a/modules/local/extract_hashes/main.nf +++ b/modules/local/extract_hashes/main.nf @@ -2,6 +2,11 @@ process EXTRACT_HASHES { tag "${meta.id}" label 'process_low' + conda "${moduleDir}/environment.yml" + container "${workflow.containerEngine == 'singularity' && !task.ext.singularity_pull_docker_container + ? 'https://community-cr-prod.seqera.io/docker/registry/v2/blobs/sha256/74/749b3cf99e0a33f46d2b49ab60d6e408ce467476c0ec86da1775e73ac4b5ba7b/data' + : 'community.wave.seqera.io/library/coreutils_gawk_gzip:3d2dde6df78e314a'}" + input: tuple val(meta), path(hto_dir) @@ -15,6 +20,13 @@ process EXTRACT_HASHES { prefix = task.ext.prefix ?: "${meta.id}" """ gunzip -c ${hto_dir}/features.tsv.gz | awk '{print \$2}' | paste -sd, - > ${prefix}_hashes.txt + + cat <<-END_VERSIONS > versions.yml + "${task.process}": + gzip: \$(gzip --version 2>&1 | head -n1 | sed 's/^.*gzip //; s/ .*\$//') + gawk: \$(awk -Wversion | sed '1!d; s/.*Awk //; s/,.*//') + coreutils: \$( cat --version | sed '1!d; s/.* //' ) + END_VERSIONS """ stub: @@ -22,5 +34,12 @@ process EXTRACT_HASHES { """ touch ${prefix}_hashes.txt + + cat <<-END_VERSIONS > versions.yml + "${task.process}": + gzip: \$(gzip --version 2>&1 | head -n1 | sed 's/^.*gzip //; s/ .*\$//') + gawk: \$(awk -Wversion | sed '1!d; s/.*Awk //; s/,.*//') + coreutils: \$( cat --version | sed '1!d; s/.* //' ) + END_VERSIONS """ } diff --git a/modules/local/find_variants/environment.yml b/modules/local/find_variants/environment.yml new file mode 100644 index 00000000..f1ced77a --- /dev/null +++ b/modules/local/find_variants/environment.yml @@ -0,0 +1,8 @@ +channels: + - conda-forge + - bioconda +dependencies: + - conda-forge::r-complexupset=1.3.3 + - conda-forge::r-data.table=1.17.8 + - conda-forge::r-tidyverse=2.0.0 + - conda-forge::r-vcfr=1.15.0 diff --git a/modules/local/find_variants/main.nf b/modules/local/find_variants/main.nf new file mode 100644 index 00000000..134fa4bc --- /dev/null +++ b/modules/local/find_variants/main.nf @@ -0,0 +1,54 @@ +process FIND_VARIANTS { + tag "${meta.id}" + label 'process_low' + + conda "${moduleDir}/environment.yml" + container "${ workflow.containerEngine == 'singularity' && !task.ext.singularity_pull_docker_container ? + 'https://community-cr-prod.seqera.io/docker/registry/v2/blobs/sha256/75/755e81f7b523df9db3a6f574fdb876ddfc1e1faf1e912260b99bca773f6dba2d/data': + 'community.wave.seqera.io/library/r-complexupset_r-data.table_r-tidyverse_r-vcfr:87602a1274fab432' }" + + input: + tuple val(meta), path(best_intersect_assignment_after_match), path(cell_genotype), path(variants_vireo), path(demultiplexing_result) + val variant_count + val variant_pct + + output: + tuple val(meta), path("*/*_matched_gt.csv") , emit: matched_gt + tuple val(meta), path("*/*_unmatched_gt.csv") , emit: unmatched_gt + tuple val(meta), path("*/*_informative_variants.csv") , emit: informative_variants + tuple val(meta), path("*_all_representative_variants.csv") , emit: all_representative_variants + tuple val(meta), path("*_donor_specific_variants_upset.png"), emit: donor_specific_variants_upset + tuple val(meta), path("*_donor_specific_variants.csv") , emit: donor_specific_variants + tuple val(meta), path("*_vireo_variants.csv") , emit: vireo_variants, optional: true + path "versions.yml" , emit: versions + + when: + task.ext.when == null || task.ext.when + + script: + prefix = task.ext.prefix ?: "${meta.id}" + template('find_variants.R') + + stub: + prefix = task.ext.prefix ?: "${meta.id}" + """ + mkdir -p hto1 + + touch ${prefix}_all_representative_variant.csv + touch ${prefix}_donor_specific_variants_upset.png + touch ${prefix}_donor_specific_representative_variants.csv + touch ${prefix}_vireo_representative_variants.csv + touch hto1/${prefix}_hto1_matched_gt.csv + touch hto1/${prefix}_hto1_unmatched_gt.csv + touch hto1/${prefix}_hto1_informative_variants.csv + + cat <<-END_VERSIONS > versions.yml + "${task.process}": + r-base: \$(Rscript -e "cat(paste(R.version[['major']], R.version[['minor']], sep='.'))") + r-complexupset: \$(Rscript -e "library(ComplexUpset); cat(as.character(packageVersion('ComplexUpset')))") + r-data.table: \$(Rscript -e "library(data.table); cat(as.character(packageVersion('data.table')))") + r-tidyverse: \$(Rscript -e "library(tidyverse); cat(as.character(packageVersion('tidyverse')))") + r-vcfr: \$(Rscript -e "library(vcfR); cat(as.character(packageVersion('vcfR')))") + END_VERSIONS + """ +} diff --git a/modules/local/find_variants/templates/find_variants.R b/modules/local/find_variants/templates/find_variants.R new file mode 100644 index 00000000..6604b676 --- /dev/null +++ b/modules/local/find_variants/templates/find_variants.R @@ -0,0 +1,291 @@ +#!/usr/bin/env Rscript + +################################################ +################################################ +## Functions to handle Nextflow input ## +################################################ +################################################ + +string_to_null <- function(x, val = "[]") if (x == val) NULL else x + +check_files <- function(args) { + + files <- c( + "best_intersect_assignment_after_match", + "cell_genotype", + "variants_vireo", + "result_csv" + ) + + for (f in files) { + if (!is.null(args[[f]])){ + if(!file.exists(args[[f]])){ + stop(sprintf("'%s' does not exist.", f)) + } + if(dir.exists(args[[f]])){ + stop(sprintf("'%s' is a directory but must be a file.", f)) + } + } + } +} + +################################################ +################################################ +## PARSE PARAMETERS FROM NEXTFLOW ## +################################################ +################################################ + +args <- list( + # File inputs + best_intersect_assignment_after_match = '$best_intersect_assignment_after_match', + cell_genotype = string_to_null('$cell_genotype'), + variants_vireo = string_to_null('$variants_vireo'), + result_csv = string_to_null('$demultiplexing_result'), # only to have all barcodes + + # second in puts + variant_count = as.numeric('$variant_count'), + variant_pct = as.numeric('$variant_pct'), + + # others + prefix = '$prefix' # Prefix name for output files. +) + +check_files(args) + +# Configure output precision +options(digits=5) + +################################################ +################################################ +## Finish loading libraries ## +################################################ +################################################ + +library(data.table) +library(ComplexUpset) +library(tidyverse) +library(vcfR) + +################################################ +################################################ +## Main Process ## +################################################ +################################################ + +# set TRUE to see print outputs for debugging +debugging <- FALSE + +if (!is.null(args\$cell_genotype)) { + + result_csv <- + fread( + args\$result_csv, + stringsAsFactors = FALSE, + na.strings = c(NA_character_, "") + ) + + result_merge_new <- + fread(file.path(args\$best_intersect_assignment_after_match), + header = T + ) + + best_method1 <- names(result_merge_new)[2] + best_method2 <- names(result_merge_new)[3] + + result_merge_new\$match <- + result_merge_new[[best_method1]] == result_merge_new[[best_method2]] + matched <- result_merge_new[result_merge_new\$match, ] + unmatched <- + result_csv[!result_csv\$Barcode %in% matched\$Barcode, ]\$Barcode + cell_genotype_vcf <- read.vcfR(args\$cell_genotype) + cell_genotype_vcf_gt <- + extract.gt(cell_genotype_vcf, + element = "GT", + as.numeric = TRUE + ) + donors <- sort(unique(matched[[best_method1]])) + + representative_variant_list <- + vector(mode = "list", length = length(donors)) + representative_variant_list <- + setNames(representative_variant_list, donors) + + for (donorid in donors) { + matched_barcode <- + matched[matched[[best_method1]] == donorid]\$Barcode + matched_gt_list <- cell_genotype_vcf_gt[, matched_barcode] + matched_gt_list <- + matched_gt_list[rowSums(is.na(matched_gt_list)) != ncol(matched_gt_list), ] + matched_gt <- + as.data.frame(matrix(nrow = nrow(matched_gt_list))) + + # how many cells show homozygous reference + matched_gt\$ref <- rowSums(matched_gt_list == 0, na.rm = TRUE) + # how many cells show heterozygous or homozygous alternative + matched_gt\$alt <- rowSums(matched_gt_list != 0, na.rm = TRUE) + # chromosome positions + matched_gt\$V1 <- rownames(matched_gt_list) + matched_gt\$count <- matched_gt\$ref + matched_gt\$alt + matched_gt\$pct <- + matched_gt\$alt / (matched_gt\$ref + matched_gt\$alt) + matched_gt\$dominant <- ifelse(matched_gt\$pct > 0.5, 1, 0) + + # variant_pct has to be in a range between [0,5;1[ + # 0.9 for example means we only keep variabts with higher than 90% or lower than 10% frequency + matched_gt <- matched_gt[(matched_gt\$pct >= args\$variant_pct | + matched_gt\$pct <= (1 - args\$variant_pct)), ] + matched_gt <- + matched_gt[matched_gt\$count >= args\$variant_count, ] + + unmatched_gt_list <- cell_genotype_vcf_gt[, unmatched] + unmatched_gt_list <- + unmatched_gt_list[rownames(unmatched_gt_list) %in% matched_gt\$V1, ] + unmatched_gt_list <- + unmatched_gt_list[rowSums(is.na(unmatched_gt_list)) != ncol(unmatched_gt_list), ] + unmatched_gt_list <- + cbind(rownames(unmatched_gt_list), unmatched_gt_list) + unmatched_gt_list <- + melt(data.table(unmatched_gt_list), id.vars = "V1") + unmatched_gt_list <- + unmatched_gt_list[!is.na(unmatched_gt_list\$value), ] + colnames(unmatched_gt_list) <- c("variant", "cell", "allele") + + + outputdir <- file.path(donorid) + if (!dir.exists(outputdir)) { + dir.create(outputdir, recursive = TRUE) + } + + write.csv(matched_gt, + file.path(outputdir, paste0(args\$prefix, "_", donorid, "_matched_gt.csv")), + row.names = FALSE + ) + write.csv(unmatched_gt_list, + file.path(outputdir, paste0(args\$prefix, "_", donorid, "_unmatched_gt.csv")), + row.names = FALSE + ) + + informative_variants_cells <- + merge( + matched_gt, + unmatched_gt_list, + by.x = c("V1", "dominant"), + by.y = c("variant", "allele") + ) + colnames(informative_variants_cells)[1] <- "variant" + num_informative_variants <- informative_variants_cells %>% + group_by(cell) %>% + summarise(matched = n()) + if (nrow(unmatched_gt_list[!unmatched_gt_list\$cell %in% num_informative_variants\$cell, ]) > 0 | debugging) { + print(unmatched_gt_list[!unmatched_gt_list\$cell %in% num_informative_variants\$cell, ]) + } + representative_variant_list[[donorid]] <- + list(unique(informative_variants_cells\$variant)) + write.table( + unique(informative_variants_cells\$variant), + file.path(outputdir, + paste0(args\$prefix, "_", donorid, "_informative_variants.csv") + ), + row.names = FALSE, + col.names = FALSE + ) + } + + representative_variant <- + rbindlist(representative_variant_list, idcol = "donor") + colnames(representative_variant)[2] <- "variant" + representative_variant_df <- + dcast(data = representative_variant, variant ~ donor, length) + write.csv( + representative_variant_df, + file.path(paste0(args\$prefix, "_all_representative_variants.csv")) + ) + + upset <- ComplexUpset::upset( + representative_variant_df, + donors, + width_ratio = 0.45, + height_ratio = 0.9, + stripes = "white", + max_degree = 1, + name = "Number of donor-specific variants", + set_sizes = ( + upset_set_size() + + geom_text( + aes(label = ..count.., size = 3), + hjust = -0.1, + stat = "count", + color = "white", + size = 2.3 + ) + + theme( + axis.text.x = element_text(angle = 90), + text = element_text(size = 10) + ) + ), + base_annotations = list("Intersection size" = intersection_size()) + ) + ggsave(file.path(paste0(args\$prefix, "_donor_specific_variants_upset.png"))) + + # variants that are unique to exactly one donor + representative_variant_single <- + representative_variant_df[rowSums(representative_variant_df[, -1]) == 1, ] + representative_variant_single <- + separate( + representative_variant_single, + col = "variant", + into = c("chr", "pos"), + sep = "_" + ) + write.table( + representative_variant_single[, c("chr", "pos")], + quote = FALSE, + col.names = FALSE, + sep = "\t", + row.names = FALSE, + file.path(paste0(args\$prefix, "_donor_specific_variants.csv")) + ) +} + +if (!is.null(args\$variants_vireo)) { + + representative_variant <- fread(args\$variants_vireo) + representative_variant <- separate( + representative_variant, + col = "variants", + into = c("chr", "pos"), + sep = "_", + extra = "drop" + ) + write.table( + representative_variant[, c("chr", "pos")], + quote = FALSE, + col.names = FALSE, + sep = "\t", + row.names = FALSE, + file.path(paste0(args\$prefix, "_vireo_variants.csv")) + ) +} + +################################################ +################################################ +## VERSIONS FILE ## +################################################ +################################################ + +r.version <- paste(R.version[['major']],R.version[['minor']], sep = ".") +data_table.version <- as.character(packageVersion('data.table')) +complexUpset.version <- as.character(packageVersion('ComplexUpset')) +tidyverse.version <- as.character(packageVersion('tidyverse')) +vcfR.version <- as.character(packageVersion('vcfR')) + +writeLines( + c( + '"${task.process}":', + paste(' r-base:', r.version), + paste(' r-complexupset:', complexUpset.version), + paste(' r-data.table:', data_table.version), + paste(' r-tidyverse:', tidyverse.version), + paste(' r-vcfr:', vcfR.version) + ), +'versions.yml') diff --git a/modules/local/gene_summary/environment.yml b/modules/local/gene_summary/environment.yml index c6c95551..d166cb22 100644 --- a/modules/local/gene_summary/environment.yml +++ b/modules/local/gene_summary/environment.yml @@ -2,9 +2,7 @@ channels: - conda-forge - bioconda dependencies: - - bioconda::pegasusio=0.10.0 - - conda-forge::mudata=0.3.1 - - conda-forge::numpy=1.24.2 - - conda-forge::pandas=2.3.1 + - conda-forge::numpy=2.3.5 + - conda-forge::pandas=2.3.3 - conda-forge::pyyaml=6.0.3 - - conda-forge::scanpy=1.11.2 + - conda-forge::scanpy=1.11.5 diff --git a/modules/local/gene_summary/main.nf b/modules/local/gene_summary/main.nf index 460adfec..aa8b2b76 100644 --- a/modules/local/gene_summary/main.nf +++ b/modules/local/gene_summary/main.nf @@ -4,26 +4,23 @@ process GENE_SUMMARY { conda "${moduleDir}/environment.yml" container "${ workflow.containerEngine == 'singularity' && !task.ext.singularity_pull_docker_container ? - 'https://community-cr-prod.seqera.io/docker/registry/v2/blobs/sha256/d8/d863e56b5ce15b271e8c8666ec22217df5cfc57a9731cc23c7f92674dc7ab0c7/data': - 'community.wave.seqera.io/library/pegasusio_mudata_numpy_pandas_pruned:ecdbf7e42b2f3213' }" + 'https://community-cr-prod.seqera.io/docker/registry/v2/blobs/sha256/6d/6d63210b90bdadc321e15610f40c337ab08fa724719b7d4be0785944a86755fb/data': + 'community.wave.seqera.io/library/numpy_pandas_pyyaml_scanpy:d959777f7735763f' }" input: tuple val(meta), - path(rna_matrix), - path(hto_matrix), path(barcodes), path(vireo), path(demuxlet), path(freemuxlet), path(souporcell) - tuple val (generate_anndata), val(generate_mudata) output: - tuple val(meta), path("*_genetic_summary_assignment.csv") , emit: assignment , optional: false - tuple val(meta), path("*_genetic_summary_classification.csv"), emit: classification, optional: false - tuple val(meta), path("*_genetic_summary.h5ad") , emit: h5ad , optional: true - tuple val(meta), path("*_genetic_summary.h5mu") , emit: h5mu , optional: true - path "versions.yml" , emit: versions , optional: false + tuple val(meta), path("*_genetic_summary_assignment.csv") , emit: assignment + tuple val(meta), path("*_genetic_summary_classification.csv") , emit: classification + tuple val(meta), path("*_genetic_overview_assignment.csv") , emit: overview_assignment + tuple val(meta), path("*_genetic_overview_classification.csv"), emit: overview_classification + path "versions.yml" , emit: versions when: task.ext.when == null || task.ext.when @@ -38,6 +35,8 @@ process GENE_SUMMARY { """ touch ${prefix}_genetic_summary_assignment.csv touch ${prefix}_genetic_summary_classification.csv + touch ${prefix}_genetic_overview_assignment.csv + touch ${prefix}_genetic_overview_classification.csv cat <<-END_VERSIONS > versions.yml "${task.process}": @@ -45,8 +44,6 @@ process GENE_SUMMARY { pandas: \$(python3 -c 'import pandas as pd; print(pd.__version__)') scanpy: \$(python3 -c 'import scanpy as sc; print(sc.__version__)') numpy: \$(python3 -c 'import numpy as np; print(np.__version__)') - mudata: \$(python3 -c 'import mudata as md; print(md.__version__)') - pegasusio: \$(python3 -c 'import pegasusio as io; print(io.__version__)') END_VERSIONS """ } diff --git a/modules/local/gene_summary/templates/gene_summary.py b/modules/local/gene_summary/templates/gene_summary.py index 1a0d89dc..ad822375 100644 --- a/modules/local/gene_summary/templates/gene_summary.py +++ b/modules/local/gene_summary/templates/gene_summary.py @@ -12,12 +12,9 @@ import pandas as pd import scanpy as sc import numpy as np -import mudata as md -import pegasusio as io from pathlib import Path -from mudata import MuData -from typing import Tuple +from typing import Tuple, List class Arguments: @@ -34,20 +31,13 @@ def __init__(self) -> None: def parse_input_args(self) -> None: self.prefix = "$task.ext.prefix" if "$task.ext.prefix" != "null" else "$meta.id" - self.rna_matrix = "${rna_matrix}" - self.hto_matrix = "${hto_matrix}" self.barcodes = "${barcodes}" self.vireo = "${vireo}" self.demuxlet = "${demuxlet}" self.freemuxlet = "${freemuxlet}" self.souporcell = "${souporcell}" - self.generate_anndata = "${generate_anndata}" - self.generate_mudata = "${generate_mudata}" - path_vars = { - "rna_matrix", - "hto_matrix", "barcodes", "vireo", "demuxlet", @@ -55,23 +45,16 @@ def parse_input_args(self) -> None: "souporcell", } - boolean_vars = {"generate_anndata", "generate_mudata"} - def _tranlate_to_python(input_str, value_str): if value_str.strip() == "": return None else: - if input_str in path_vars: - return Path(value_str) - elif input_str in boolean_vars: - if value_str == "true": - return True - else: - return False - - vars = path_vars | boolean_vars + path = Path(value_str) + if not path.exists(): + raise FileNotFoundError(f"Path does not exist: {path}") + return path - for var in vars: + for var in path_vars: raw_value = getattr(self, var) processed_value = _tranlate_to_python(var, raw_value) setattr(self, var, processed_value) @@ -80,8 +63,8 @@ def creat_output_dirs(self) -> None: directories = { "assignment": "_genetic_summary_assignment.csv", "classification": "_genetic_summary_classification.csv", - "h5mu": "_genetic_summary.h5mu", - "h5ad": "_genetic_summary.h5ad", + "overview_assignment": "_genetic_overview_assignment.csv", + "overview_classification": "_genetic_overview_classification.csv", } for output, directory in directories.items(): @@ -166,59 +149,54 @@ def demuxlet_or_freemuxlet( return assignment, classification -# TODO if we keep saving AnnData/MuData in gene/hash_summary add AnnData to container for input type (https://github.com/theislab/hadge/issues/83) -# joins the assignment results with RNA, generate_anndata will return h5ad with RNA matrix -def saveAnnDataMuData( - args: Arguments, assignment_summary: pd.DataFrame, rna_data, hto_data -): - if args.generate_mudata or args.generate_anndata: - assignment_summary.set_index("Barcode", inplace=True) - rna_data.obs = rna_data.obs.join(assignment_summary, how="left").fillna( - args.negative_str - ) - - if args.generate_anndata: - rna_data.write(args.h5ad) - - if args.generate_mudata: - mudata = MuData({"rna": rna_data, "hto": hto_data}) - mudata.update() - mudata.write(args.h5mu) - - -def print_method_item_counts(dfs): +def create_overview_table(dfs: List[pd.DataFrame]): """ Takes the list of assignment/classification DataFrames (assignments/classifications) and prints a summary table: - method name | total count | count(item1) | count(item2) | ... + method name | total count | match_method1 | match_method2 | ... | count(item1) | count(item2) | ... + Match to a method counts the number of barcodes that a method has in common with another method. An item refers to the donor label in the assignment (HTO-1, HTO-2, ...) or the classification (singlet, doublet, negative). """ rows = [] all_items = set() + match_cols = set() - # Extract items and their counts for every deconvolution method + # extract items and their counts for every deconvolution method for df in dfs: - print(df) - + # add method name and number of barcodes method_name = df.columns[1] - counts = df[method_name].value_counts(dropna=False) total = len(df) - all_items.update(counts.index) - row = {"method": method_name, "count_overall": total} + + # add the number of matching barcodes to the other methods + for df2 in dfs: + method_name_2 = df2.columns[1] + match_col_name = f"match_{method_name_2}" + match_cols.add(match_col_name) + new_match_col = { + match_col_name: len(pd.merge(df, df2, on="Barcode", how="inner")) + } + row.update(new_match_col) + + # add the counts for each item + counts = df[method_name].value_counts(dropna=False) + all_items.update(counts.index) row.update(counts.to_dict()) + rows.append(row) summary = pd.DataFrame(rows).fillna(0) - # Convert all numeric values to int + # convert all numeric values to int for col in summary.columns: if col != "method": summary[col] = summary[col].astype(int) - # Order columns - summary = summary[["method", "count_overall"] + sorted(list(all_items))] + # order columns + summary = summary[ + ["method", "count_overall"] + sorted(match_cols) + sorted(list(all_items)) + ] - print(summary.to_string(index=False)) + return summary if __name__ == "__main__": @@ -243,12 +221,18 @@ def print_method_item_counts(dfs): # ----------------------------------- save csv's ----------------------------------- - rna_data = sc.read_10x_mtx(args.rna_matrix) - hto_data = sc.read_10x_mtx(args.hto_matrix, gex_only=False) + # save overview tables + overview_assignment = create_overview_table(assignments) + overview_assignment.to_csv(args.overview_assignment, index=False) + overview_classifications = create_overview_table(classifications) + overview_classifications.to_csv(args.overview_classification, index=False) + + # save summary of all deconvolution methods + barcodes_df = pd.read_csv(args.barcodes, header=None, sep="\t", names=["Barcode"]) - # Use rna_data.obs_names() as index to perform a left join - assignment_summary = pd.DataFrame(rna_data.obs_names, columns=["Barcode"]) - classification_summary = assignment_summary.copy() + # Use barcodes.tsv as index to perform a left join + assignment_summary = barcodes_df.copy() + classification_summary = barcodes_df.copy() for assignment in assignments: assignment_summary = pd.merge( @@ -265,10 +249,6 @@ def print_method_item_counts(dfs): args.classification, index=False ) - # -------------------------------- save mudata/anndata ----------------------------- - - saveAnnDataMuData(args, assignment_summary, rna_data, hto_data) - # -------------------------------------- versions ---------------------------------- versions = { @@ -277,8 +257,6 @@ def print_method_item_counts(dfs): "pandas": pd.__version__, "scanpy": sc.__version__, "numpy": np.__version__, - "mudata": md.__version__, - "pegasusio": io.__version__, } } diff --git a/modules/local/hash_summary/environment.yml b/modules/local/hash_summary/environment.yml index b9c006b3..43927119 100644 --- a/modules/local/hash_summary/environment.yml +++ b/modules/local/hash_summary/environment.yml @@ -3,9 +3,7 @@ channels: - bioconda dependencies: - bioconda::pegasusio=0.10.0 - - conda-forge::anndata=0.11.4 - - conda-forge::mudata=0.3.1 - - conda-forge::numpy=1.24.2 - - conda-forge::pandas=2.3.1 - - conda-forge::pathlib=1.0.1 - - conda-forge::scanpy=1.11.2 + - conda-forge::numpy=2.3.5 + - conda-forge::pandas=2.3.3 + - conda-forge::pyyaml=6.0.3 + - conda-forge::scanpy=1.11.5 diff --git a/modules/local/hash_summary/main.nf b/modules/local/hash_summary/main.nf index d69eb8ba..22eb8c8e 100644 --- a/modules/local/hash_summary/main.nf +++ b/modules/local/hash_summary/main.nf @@ -4,12 +4,11 @@ process HASH_SUMMARY { conda "${moduleDir}/environment.yml" container "${ workflow.containerEngine == 'singularity' && !task.ext.singularity_pull_docker_container ? - 'https://community-cr-prod.seqera.io/docker/registry/v2/blobs/sha256/d8/d863e56b5ce15b271e8c8666ec22217df5cfc57a9731cc23c7f92674dc7ab0c7/data': - 'community.wave.seqera.io/library/pegasusio_mudata_numpy_pandas_pruned:ecdbf7e42b2f3213' }" + 'https://community-cr-prod.seqera.io/docker/registry/v2/blobs/sha256/cb/cb8601e2171467026ea36c22328a15eb25025bbe686ff1a0ea04ab407c735aee/data': + 'community.wave.seqera.io/library/pegasusio_numpy_pandas_pyyaml_scanpy:e16c3756496aa20c' }" input: tuple val(meta), - path(rna_matrix), path(hto_matrix), path(htodemux_assignments), path (htodemux_classification), path(multiseq), @@ -18,21 +17,21 @@ process HASH_SUMMARY { path(gmmdemux_results), path(gmmdemux_config), path(hasheddrops_results), path(hasheddrops_id_to_hash), path(hashsolo) - tuple val (generate_anndata), val(generate_mudata), val(bff_methods) + val(bff_methods) output: - tuple val(meta), path("*_hashing_summary_assignment.csv") , emit: assignment , optional: false - tuple val(meta), path("*_hashing_summary_classification.csv"), emit: classification, optional: false - tuple val(meta), path("*_hashing_summary.h5ad") , emit: h5ad , optional: true - tuple val(meta), path("*_hashing_summary.h5mu") , emit: h5mu , optional: true - path "versions.yml" , emit: versions , optional: false + tuple val(meta), path("*_hashing_summary_assignment.csv") , emit: assignment + tuple val(meta), path("*_hashing_summary_classification.csv") , emit: classification + tuple val(meta), path("*_hashing_overview_assignment.csv") , emit: overview_assignment + tuple val(meta), path("*_hashing_overview_classification.csv"), emit: overview_classification + path "versions.yml" , emit: versions when: task.ext.when == null || task.ext.when script: prefix = task.ext.prefix ?: "${meta.id}" - hash_list = "${meta.hashes}".split(",") + hash_list = "${meta.hto_names}".split(",") template 'hash_summary.py' @@ -41,6 +40,8 @@ process HASH_SUMMARY { """ touch ${prefix}_hashing_summary_assignment.csv touch ${prefix}_hashing_summary_classification.csv + touch ${prefix}_hashing_overview_assignment.csv + touch ${prefix}_hashing_overview_classification.csv cat <<-END_VERSIONS > versions.yml "${task.process}": @@ -48,7 +49,6 @@ process HASH_SUMMARY { pandas: \$(python3 -c 'import pandas as pd; print(pd.__version__)') scanpy: \$(python3 -c 'import scanpy as sc; print(sc.__version__)') numpy: \$(python3 -c 'import numpy as np; print(np.__version__)') - mudata: \$(python3 -c 'import mudata as md; print(md.__version__)') pegasusio: \$(python3 -c 'import pegasusio as io; print(io.__version__)') END_VERSIONS """ diff --git a/modules/local/hash_summary/templates/hash_summary.py b/modules/local/hash_summary/templates/hash_summary.py index 1e6c2075..59aaf726 100644 --- a/modules/local/hash_summary/templates/hash_summary.py +++ b/modules/local/hash_summary/templates/hash_summary.py @@ -12,12 +12,10 @@ import pandas as pd import scanpy as sc import numpy as np -import mudata as md import pegasusio as io from pathlib import Path -from mudata import MuData -from typing import Tuple +from typing import Tuple, List class Arguments: @@ -35,7 +33,6 @@ def __init__(self) -> None: def parse_input_args(self) -> None: self.prefix = "$task.ext.prefix" if "$task.ext.prefix" != "null" else "$meta.id" - self.rna_matrix = "${rna_matrix}" self.hto_matrix = "${hto_matrix}" self.htodemux_assignments = "${htodemux_assignments}" self.htodemux_classification = "${htodemux_classification}" @@ -48,13 +45,10 @@ def parse_input_args(self) -> None: self.hasheddrops_id_to_hash = "${hasheddrops_id_to_hash}" self.hashsolo = "${hashsolo}" - self.generate_anndata = "${generate_anndata}" - self.generate_mudata = "${generate_mudata}" self.bff_methods = "${bff_methods}" self.hash_list = "${hash_list}" path_vars = { - "rna_matrix", "hto_matrix", "htodemux_assignments", "htodemux_classification", @@ -68,8 +62,6 @@ def parse_input_args(self) -> None: "hashsolo", } - boolean_vars = {"generate_anndata", "generate_mudata"} - other_vars = {"bff_methods", "hash_list"} def _tranlate_to_python(input_str, value_str): @@ -77,12 +69,10 @@ def _tranlate_to_python(input_str, value_str): return None else: if input_str in path_vars: - return Path(value_str) - elif input_str in boolean_vars: - if value_str == "true": - return True - else: - return False + path = Path(value_str) + if not path.exists(): + raise FileNotFoundError(f"Path does not exist: {path}") + return path elif input_str == "bff_methods": if value_str == "RAW": return ["bff_raw"] @@ -99,7 +89,7 @@ def _tranlate_to_python(input_str, value_str): hash.strip() for hash in "${hash_list}".strip("[]").split(",") ) - vars = path_vars | boolean_vars | other_vars + vars = path_vars | other_vars for var in vars: raw_value = getattr(self, var) @@ -110,8 +100,8 @@ def creat_output_dirs(self) -> None: directories = { "assignment": "_hashing_summary_assignment.csv", "classification": "_hashing_summary_classification.csv", - "h5mu": "_hashing_summary.h5mu", - "h5ad": "_hashing_summary.h5ad", + "overview_assignment": "_hashing_overview_assignment.csv", + "overview_classification": "_hashing_overview_classification.csv", } for output, directory in directories.items(): @@ -157,7 +147,7 @@ def demuxem(self, args: Arguments) -> Tuple[pd.DataFrame, pd.DataFrame]: {"unknown": args.negative_str} ) - # TODO demuxem has more output barcodes than input barcodes metioned here: https://github.com/lilab-bcb/demuxEM/issues/20 + # TODO demuxem: demuxem has more output barcodes than input barcodes metioned here: https://github.com/lilab-bcb/demuxEM/issues/20 assignment = data.obs["assignment"].to_frame() assignment.reset_index(inplace=True) assignment.columns = ["Barcode", "demuxem"] @@ -348,59 +338,54 @@ def classify_value(x): return assignment, classification -# TODO if we keep saving AnnData/MuData in gene/hash_summary add AnnData to container for input type (https://github.com/theislab/hadge/issues/83) -# joins the assignment results with HTO, generate_anndata will return h5ad with HTO matrix -def saveAnnDataMuData( - args: Arguments, assignment_summary: pd.DataFrame, rna_data, hto_data -): - if args.generate_mudata or args.generate_anndata: - assignment_summary.set_index("Barcode", inplace=True) - hto_data.obs = hto_data.obs.join(assignment_summary, how="left").fillna( - args.negative_str - ) - - if args.generate_anndata: - hto_data.write(args.h5ad) - - if args.generate_mudata: - mudata = MuData({"rna": rna_data, "hto": hto_data}) - mudata.update() - mudata.write(args.h5mu) - - -def print_method_item_counts(dfs): +def create_overview_table(dfs: List[pd.DataFrame]): """ Takes the list of assignment/classification DataFrames (assignments/classifications) and prints a summary table: - method name | total count | count(item1) | count(item2) | ... + method name | total count | match_method1 | match_method2 | ... | count(item1) | count(item2) | ... + Match to a method counts the number of barcodes that a method has in common with another method. An item refers to the donor label in the assignment (HTO-1, HTO-2, ...) or the classification (singlet, doublet, negative). """ rows = [] all_items = set() + match_cols = set() - # Extract items and their counts for every deconvolution method + # extract items and their counts for every deconvolution method for df in dfs: - print(df) - + # add method name and number of barcodes method_name = df.columns[1] - counts = df[method_name].value_counts(dropna=False) total = len(df) - all_items.update(counts.index) - row = {"method": method_name, "count_overall": total} + + # add the number of matching barcodes to the other methods + for df2 in dfs: + method_name_2 = df2.columns[1] + match_col_name = f"match_{method_name_2}" + match_cols.add(match_col_name) + new_match_col = { + match_col_name: len(pd.merge(df, df2, on="Barcode", how="inner")) + } + row.update(new_match_col) + + # add the counts for each item + counts = df[method_name].value_counts(dropna=False) + all_items.update(counts.index) row.update(counts.to_dict()) + rows.append(row) summary = pd.DataFrame(rows).fillna(0) - # Convert all numeric values to int + # convert all numeric values to int for col in summary.columns: if col != "method": summary[col] = summary[col].astype(int) - # Order columns - summary = summary[["method", "count_overall"] + sorted(list(all_items))] + # order columns + summary = summary[ + ["method", "count_overall"] + sorted(match_cols) + sorted(list(all_items)) + ] - print(summary.to_string(index=False)) + return summary if __name__ == "__main__": @@ -429,7 +414,13 @@ def print_method_item_counts(dfs): # ----------------------------------- save csv's ----------------------------------- - rna_data = sc.read_10x_mtx(args.rna_matrix) + # save overview tables + overview_assignment = create_overview_table(assignments) + overview_assignment.to_csv(args.overview_assignment, index=False) + overview_classifications = create_overview_table(classifications) + overview_classifications.to_csv(args.overview_classification, index=False) + + # save summary of all deconvolution methods hto_data = sc.read_10x_mtx(args.hto_matrix, gex_only=False) # Need to use a left join — demuxEM outputs extra barcodes not present in the input. @@ -449,7 +440,7 @@ def print_method_item_counts(dfs): classification_summary, classification, on="Barcode", how="left" ) - # TODO update if demuxEM works (https://github.com/theislab/hadge/issues/81) + # TODO demuxem: update if demuxEM works (https://github.com/theislab/hadge/issues/81) # .replace("", args.negative_str) # maybe also in demuxem() assignment_summary.fillna(args.negative_str).to_csv(args.assignment, index=False) @@ -457,10 +448,6 @@ def print_method_item_counts(dfs): args.classification, index=False ) - # -------------------------------- save mudata/anndata ----------------------------- - - saveAnnDataMuData(args, assignment_summary, rna_data, hto_data) - # -------------------------------------- versions ---------------------------------- versions = { @@ -469,7 +456,6 @@ def print_method_item_counts(dfs): "pandas": pd.__version__, "scanpy": sc.__version__, "numpy": np.__version__, - "mudata": md.__version__, "pegasusio": io.__version__, } } diff --git a/modules/local/rename_genes_to_features/environment.yml b/modules/local/rename_genes_to_features/environment.yml deleted file mode 100644 index babcfb55..00000000 --- a/modules/local/rename_genes_to_features/environment.yml +++ /dev/null @@ -1,7 +0,0 @@ ---- -# yaml-language-server: $schema=https://raw.githubusercontent.com/nf-core/modules/master/modules/environment-schema.json -channels: - - conda-forge - - bioconda -dependencies: - - conda-forge::coreutils=9.3 diff --git a/modules/local/rename_genes_to_features/main.nf b/modules/local/rename_genes_to_features/main.nf deleted file mode 100644 index c1c5d8fa..00000000 --- a/modules/local/rename_genes_to_features/main.nf +++ /dev/null @@ -1,25 +0,0 @@ -process RENAME_GENES_TO_FEATURES { - tag "${meta.id}" - label 'process_low' - - conda "${moduleDir}/environment.yml" - container "${ workflow.containerEngine == 'singularity' && !task.ext.singularity_pull_docker_container ? - 'https://depot.galaxyproject.org/singularity/coreutils:9.3': - 'biocontainers/coreutils:9.3' }" - - input: - tuple val(meta), path(dir) - - output: - tuple val(meta), path(prefix) - - script: - prefix = task.ext.prefix ?: "${meta.id}" - """ - cp -Lr ${dir} ${prefix} - - if [ -f "${prefix}/genes.tsv.gz" ]; then - mv "${prefix}/genes.tsv.gz" "${prefix}/features.tsv.gz" - fi - """ -} diff --git a/modules/local/subset_gt_donors/environment.yml b/modules/local/subset_gt_donors/environment.yml new file mode 100644 index 00000000..f1217ec2 --- /dev/null +++ b/modules/local/subset_gt_donors/environment.yml @@ -0,0 +1,5 @@ +channels: + - conda-forge + - bioconda +dependencies: + - bioconda::bcftools=1.22 diff --git a/modules/local/subset_gt_donors/main.nf b/modules/local/subset_gt_donors/main.nf new file mode 100644 index 00000000..484b8617 --- /dev/null +++ b/modules/local/subset_gt_donors/main.nf @@ -0,0 +1,45 @@ +process SUBSET_GT_DONORS { + tag "${meta.id}" + label 'process_low' + + conda "${moduleDir}/environment.yml" + container "${ workflow.containerEngine == 'singularity' && !task.ext.singularity_pull_docker_container ? + 'https://community-cr-prod.seqera.io/docker/registry/v2/blobs/sha256/4e/4eb863d427b6e327abab4be2112d17760f86947eeaf3a214e1450bc680f14a49/data': + 'community.wave.seqera.io/library/bcftools:1.22--a51ee80717c2467e' }" + + input: + tuple val(meta), path(subset_variants), val(output_basename), path(gt_donors_vcf), path(donor_match) + + output: + tuple val(meta), path("*_${output_basename}.vcf.gz"), emit: donor_subset_vcf + path "versions.yml" , emit: versions + + when: + task.ext.when == null || task.ext.when + + script: + prefix = task.ext.prefix ?: "${meta.id}" + + """ + bcftools sort $gt_donors_vcf -Oz -o ${prefix}_sorted.vcf.gz + bcftools index ${prefix}_sorted.vcf.gz + bcftools filter ${prefix}_sorted.vcf.gz -R $subset_variants -Oz -o ${prefix}_filtered.vcf.gz + bcftools reheader ${prefix}_filtered.vcf.gz --samples $donor_match -o ${prefix}_${output_basename}.vcf.gz + + cat <<-END_VERSIONS > versions.yml + "${task.process}": + bcftools: \$(bcftools --version 2>&1 | head -n1 | sed 's/^.*bcftools //; s/ .*\$//') + END_VERSIONS + """ + + stub: + prefix = task.ext.prefix ?: "${meta.id}" + """ + touch ${prefix}_${output_basename}.vcf.gz + + cat <<-END_VERSIONS > versions.yml + "${task.process}": + bcftools: \$(bcftools --version 2>&1 | head -n1 | sed 's/^.*bcftools //; s/ .*\$//') + END_VERSIONS + """ +} diff --git a/modules/nf-core/bff/templates/bff.R b/modules/nf-core/bff/templates/bff.R index c114dd18..9609f977 100644 --- a/modules/nf-core/bff/templates/bff.R +++ b/modules/nf-core/bff/templates/bff.R @@ -85,6 +85,8 @@ for ( ao in names(args_opt)){ } } +print(opt) + # Set individual variables for backward compatibility and cleaner code hto_matrix <- opt\$hto_matrix methods <- opt\$methods @@ -133,6 +135,12 @@ if(preprocessing){ # perform preprocessing counts <- ProcessCountMatrix(rawCountData = hto_matrix, barcodeWhitelist = barcodes_list) + + # Add '-1' suffix back to match original 10X barcodes (is removed by calling Read10X(strip.suffix = TRUE) in ProcessCountMatrix()) + barcodes <- readLines(gzfile(paste0(hto_matrix,"/barcodes.tsv.gz"))) + if(all(grepl("\\\\-1\$", barcodes))) { + colnames(counts) <- paste0(colnames(counts), "-1") + } }else{ # perform preprocessing counts <- Read10X(hto_matrix) diff --git a/modules/nf-core/vireo/main.nf b/modules/nf-core/vireo/main.nf index 2319a5f5..8c4034ec 100644 --- a/modules/nf-core/vireo/main.nf +++ b/modules/nf-core/vireo/main.nf @@ -10,17 +10,24 @@ process VIREO { input: tuple val(meta), path(cell_data), val(n_donor), path(donor_file), path(vartrix_data) output: - tuple val(meta), path('*_summary.tsv') , emit: summary - tuple val(meta), path('*_donor_ids.tsv') , emit: donor_ids - tuple val(meta), path('*_prob_singlet.tsv.gz'), emit: prob_singlets - tuple val(meta), path('*_prob_doublet.tsv.gz'), emit: prob_doublets - path 'versions.yml' , emit: versions + tuple val(meta), path('*_summary.tsv') , emit: summary + tuple val(meta), path('*_donor_ids.tsv') , emit: donor_ids + tuple val(meta), path('*_prob_singlet.tsv.gz') , emit: prob_singlets + tuple val(meta), path('*_prob_doublet.tsv.gz') , emit: prob_doublets + tuple val(meta), path('*_GT_donors.vireo.vcf.gz'), emit: genotype_vcf , optional: true + tuple val(meta), path('*_filtered_variants.tsv') , emit: filtered_variants, optional: true + path 'versions.yml' , emit: versions when: task.ext.when == null || task.ext.when script: def args = task.ext.args ?: '' + // hardcode a default random seed + if (!(args ==~ /.*--randSeed.*/)) {args += " --randSeed 42"} + // use the same randSeed of vireo for GTbarcode if specified in args + def matcher = (args =~ /(--randSeed\s+\d+)/) + def randSeed_GTbarcode = matcher ? matcher[0][1] : '' def prefix = task.ext.prefix ?: "${meta.id}" def input = cell_data ? "-c ${cell_data}" : "--vartrixData ${vartrix_data}" @@ -37,6 +44,10 @@ process VIREO { mv donor_ids.tsv ${prefix}_donor_ids.tsv mv prob_singlet.tsv.gz ${prefix}_prob_singlet.tsv.gz mv prob_doublet.tsv.gz ${prefix}_prob_doublet.tsv.gz + if [[ -f GT_donors.vireo.vcf.gz ]]; then + mv GT_donors.vireo.vcf.gz "${prefix}_GT_donors.vireo.vcf.gz" + GTbarcode -i ${prefix}_GT_donors.vireo.vcf.gz -o ./${prefix}_filtered_variants.tsv ${randSeed_GTbarcode} + fi cat <<-END_VERSIONS > versions.yml "${task.process}": @@ -47,6 +58,13 @@ process VIREO { stub: def args = task.ext.args ?: '' def prefix = task.ext.prefix ?: "${meta.id}" + def optional_files = '' + if (args.contains('--forceLearnGT')) { + optional_files = """ + echo "" | gzip > ${prefix}_GT_donors.vireo.vcf.gz + touch ${prefix}_filtered_variants.tsv + """ + } """ touch ${prefix}_summary.tsv @@ -54,6 +72,8 @@ process VIREO { echo "" | gzip > ${prefix}_prob_singlet.tsv.gz echo "" | gzip > ${prefix}_prob_doublet.tsv.gz + ${optional_files} + cat <<-END_VERSIONS > versions.yml "${task.process}": vireo: \$(vireo | sed '1!d ; s/Welcome to vireoSNP //; s/!//') diff --git a/modules/nf-core/vireo/meta.yml b/modules/nf-core/vireo/meta.yml index 77e5fa42..935b4118 100644 --- a/modules/nf-core/vireo/meta.yml +++ b/modules/nf-core/vireo/meta.yml @@ -26,6 +26,7 @@ input: description: The cell genotype file in VCF format or cellSNP folder with sparse matrices. pattern: "*.vcf|*/" + ontologies: [] - n_donor: type: integer description: Number of donors to demultiplex. @@ -33,12 +34,14 @@ input: type: file description: The optional donor genotype file in VCF format. pattern: "*.vcf" + ontologies: [] - vartrix_data: type: file description: The optional cell genotype files in vartrix outputs. + ontologies: [] output: - - summary: - - meta: + summary: + - - meta: type: map description: | Groovy Map containing sample information @@ -47,8 +50,10 @@ output: type: file description: Summary tsv file of deconvolution result. pattern: "*_summary.tsv" - - donor_ids: - - meta: + ontologies: + - edam: http://edamontology.org/format_3475 # TSV + donor_ids: + - - meta: type: map description: | Groovy Map containing sample information @@ -57,8 +62,10 @@ output: type: file description: Donor assignment with detailed statistics. pattern: "*_donor_ids.tsv" - - prob_singlets: - - meta: + ontologies: + - edam: http://edamontology.org/format_3475 # TSV + prob_singlets: + - - meta: type: map description: | Groovy Map containing sample information @@ -67,8 +74,10 @@ output: type: file description: contains probability of classifing singlets pattern: "*_prob_singlet.tsv.gz" - - prob_doublets: - - meta: + ontologies: + - edam: http://edamontology.org/format_3989 # GZIP format + prob_doublets: + - - meta: type: map description: | Groovy Map containing sample information @@ -77,11 +86,39 @@ output: type: file description: contains probability of classifing doublets pattern: "*_prob_doublet.tsv.gz" - - versions: - - versions.yml: + ontologies: + - edam: http://edamontology.org/format_3989 # GZIP format + genotype_vcf: + - - meta: + type: map + description: | + Groovy Map containing sample information + e.g. `[ id:'sample1' ]` + - "*_GT_donors.vireo.vcf.gz": + type: file + description: contains vireo’s inferred donor genotypes at each SNP (only created if `--forceLearnGT` is set in `ext.args`, see n) + pattern: "*_GT_donors.vireo.vcf.gz" + ontologies: + - edam: http://edamontology.org/format_3016 # VCF + filtered_variants: + - - meta: + type: map + description: | + Groovy Map containing sample information + e.g. `[ id:'sample1' ]` + - "*_filtered_variants.tsv": type: file - description: File containing software versions - pattern: "versions.yml" + description: contains the minimal set of discriminatory variants created by `GTbarcode` (only created if `--forceLearnGT` is set in `ext.args`, see the hadge pipeline as an example, and only shows consistent results if `--randSeed` is set) + pattern: "*_filtered_variants.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: - "@mari-ga" - "@maxozo" diff --git a/modules/nf-core/vireo/tests/main.nf.test b/modules/nf-core/vireo/tests/main.nf.test index 86ba742b..047fea38 100644 --- a/modules/nf-core/vireo/tests/main.nf.test +++ b/modules/nf-core/vireo/tests/main.nf.test @@ -23,13 +23,13 @@ nextflow_process { file(params.modules_testdata_base_path + '/genomics/homo_sapiens/demultiplexing/chr21.bam', checkIfExists: true) ] """ } - } + } run("CELLSNP_MODEA") { script "../../cellsnp/modea/main.nf" process { """ - input[0] = SAMTOOLS_INDEX.out.bai.collect{ meta, bai -> bai }.map{ - bai -> [[id: 'sample1'], + input[0] = SAMTOOLS_INDEX.out.bai.collect{ meta, bai -> bai }.map{ + bai -> [[id: 'sample1'], file(params.modules_testdata_base_path + '/genomics/homo_sapiens/demultiplexing/chr21.bam', checkIfExists: true), bai, file(params.modules_testdata_base_path + '/genomics/homo_sapiens/demultiplexing/donor_genotype_chr21.vcf', checkIfExists: true), @@ -42,7 +42,7 @@ nextflow_process { when { process { """ - input[0] = CELLSNP_MODEA.out.cell.collect{ meta, cell -> cell }.map{ + input[0] = CELLSNP_MODEA.out.cell.collect{ meta, cell -> cell }.map{ cell -> [[id:'test'], cell, 2, diff --git a/modules/nf-core/vireo/tests/main.nf.test.snap b/modules/nf-core/vireo/tests/main.nf.test.snap index 15ccfd88..f312054a 100644 --- a/modules/nf-core/vireo/tests/main.nf.test.snap +++ b/modules/nf-core/vireo/tests/main.nf.test.snap @@ -35,6 +35,22 @@ ] ], "4": [ + [ + { + "id": "sample1" + }, + "sample1_GT_donors.vireo.vcf.gz:md5,68b329da9893e34099c7d8ad5cb9c940" + ] + ], + "5": [ + [ + { + "id": "sample1" + }, + "sample1_filtered_variants.tsv:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "6": [ "versions.yml:md5,b16f367dd80f537073c53ccaf9fd547d" ], "donor_ids": [ @@ -45,6 +61,22 @@ "sample1_donor_ids.tsv:md5,d41d8cd98f00b204e9800998ecf8427e" ] ], + "filtered_variants": [ + [ + { + "id": "sample1" + }, + "sample1_filtered_variants.tsv:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "genotype_vcf": [ + [ + { + "id": "sample1" + }, + "sample1_GT_donors.vireo.vcf.gz:md5,68b329da9893e34099c7d8ad5cb9c940" + ] + ], "prob_doublets": [ [ { @@ -75,10 +107,10 @@ } ], "meta": { - "nf-test": "0.8.4", - "nextflow": "23.10.1" + "nf-test": "0.9.3", + "nextflow": "25.10.2" }, - "timestamp": "2024-05-08T22:38:03.769285328" + "timestamp": "2026-01-01T18:26:12.595851456" }, "demultiplexing": { "content": [ @@ -96,7 +128,7 @@ { "id": "test" }, - "test_donor_ids.tsv:md5,2d3eadd8d1a3f26428b383e7a98d2ced" + "test_donor_ids.tsv:md5,f9036fcf2d801c59dbabba7fb2c8c14d" ] ], "2": [ @@ -104,7 +136,7 @@ { "id": "test" }, - "test_prob_singlet.tsv.gz:md5,d1e6d055fda3c96713912da79994b48c" + "test_prob_singlet.tsv.gz:md5,ee76b1a2fc7ae4fdd8ff6764baf09d2b" ] ], "3": [ @@ -112,10 +144,26 @@ { "id": "test" }, - "test_prob_doublet.tsv.gz:md5,6d7e979931de2b66b89618be47fc079c" + "test_prob_doublet.tsv.gz:md5,3aff77cd983f24e7954c81430662c577" ] ], "4": [ + [ + { + "id": "test" + }, + "test_GT_donors.vireo.vcf.gz:md5,b5336b91f2ce207cd9bf4273e58b9776" + ] + ], + "5": [ + [ + { + "id": "test" + }, + "test_filtered_variants.tsv:md5,840cbbeb9d1aed7c699f5f8634c6933f" + ] + ], + "6": [ "versions.yml:md5,b16f367dd80f537073c53ccaf9fd547d" ], "donor_ids": [ @@ -123,7 +171,23 @@ { "id": "test" }, - "test_donor_ids.tsv:md5,2d3eadd8d1a3f26428b383e7a98d2ced" + "test_donor_ids.tsv:md5,f9036fcf2d801c59dbabba7fb2c8c14d" + ] + ], + "filtered_variants": [ + [ + { + "id": "test" + }, + "test_filtered_variants.tsv:md5,840cbbeb9d1aed7c699f5f8634c6933f" + ] + ], + "genotype_vcf": [ + [ + { + "id": "test" + }, + "test_GT_donors.vireo.vcf.gz:md5,b5336b91f2ce207cd9bf4273e58b9776" ] ], "prob_doublets": [ @@ -131,7 +195,7 @@ { "id": "test" }, - "test_prob_doublet.tsv.gz:md5,6d7e979931de2b66b89618be47fc079c" + "test_prob_doublet.tsv.gz:md5,3aff77cd983f24e7954c81430662c577" ] ], "prob_singlets": [ @@ -139,7 +203,7 @@ { "id": "test" }, - "test_prob_singlet.tsv.gz:md5,d1e6d055fda3c96713912da79994b48c" + "test_prob_singlet.tsv.gz:md5,ee76b1a2fc7ae4fdd8ff6764baf09d2b" ] ], "summary": [ @@ -156,9 +220,9 @@ } ], "meta": { - "nf-test": "0.8.4", - "nextflow": "23.10.1" + "nf-test": "0.9.3", + "nextflow": "25.10.2" }, - "timestamp": "2024-05-08T22:35:04.435257747" + "timestamp": "2026-01-01T18:23:23.190661422" } } \ No newline at end of file diff --git a/modules/nf-core/vireo/tests/nextflow.config b/modules/nf-core/vireo/tests/nextflow.config index 482664d2..5b647db1 100644 --- a/modules/nf-core/vireo/tests/nextflow.config +++ b/modules/nf-core/vireo/tests/nextflow.config @@ -1,5 +1,8 @@ process { withName: CELLSNP_MODEA { - ext.args = '--genotype' + ext.args = '--genotype' + } + withName: VIREO { + ext.args = '--forceLearnGT' } } diff --git a/nextflow.config b/nextflow.config index 27043272..305e1905 100644 --- a/nextflow.config +++ b/nextflow.config @@ -23,19 +23,23 @@ params { // -------------------------- donor match --------------------------- match_donor = true - // first inputs - vireo_parent_dir = null + // first inputs (necessary only for donor_match mode) demultiplexing_result = null + vireo_filtered_variants = null cell_genotype = null // second inputs match_donor_method1 = null match_donor_method2 = null - findVariants = false + + // -------------------------- find variant -------------------------- + find_variants = true + subset_gt_donors = true variant_count = 10 variant_pct = 0.9 + gt_donors = null - // ----------------------- Reference genomes ------------------------ + // ----------------------- reference genomes ------------------------ genome = null igenomes_base = 's3://ngi-igenomes/igenomes/' igenomes_ignore = false @@ -48,10 +52,8 @@ params { // ------------------------------ bff ------------------------------- // inputs - // TODO CLUSTER is not working - bff_methods = 'COMBINED' - // TODO true won't show results after joining because (see: https://github.com/theislab/hadge/issues/76) - bff_preprocessing = false + bff_methods = 'COMBINED' + bff_preprocessing = true // ext.args for preprocessing bff_barcodeWhitelist = null @@ -91,6 +93,7 @@ params { // ---------------------------- gmmdemux ---------------------------- // inputs gmmdemux_hto_names = null + gmmdemux_estimated_n_cells = null gmmdemux_type_report = true gmmdemux_summary_report = true gmmdemux_skip = null @@ -210,7 +213,7 @@ params { vireo_n_init = 50 vireo_extra_donor = 0 vireo_extra_donor_mode = 'distance' - vireo_force_learn_gt = false + vireo_force_learn_gt = true vireo_ase_mode = false vireo_no_plot = false vireo_rand_seed = 0 @@ -409,6 +412,15 @@ profiles { test_full { includeConfig 'conf/test_full.config' } + test_genetic { + includeConfig 'conf/test_genetic.config' + } + test_hashing { + includeConfig 'conf/test_hashing.config' + } + test_donor_match { + includeConfig 'conf/test_donor_match.config' + } } // Set AWS client to anonymous when using the default igenomes_base @@ -482,20 +494,68 @@ manifest { contributors = [ [ name: 'Fabiola Curion', - affiliation: '', + affiliation: 'Institute of Computational Biology, Helmholtz Zentrum München, Munich, Germany', + email: '', + github: '@bio-la', + contribution: ['author'], + orcid: '0000-0003-2502-8803', + ], + [ + name: 'Lukas Heumos', + affiliation: 'Institute of Computational Biology, Helmholtz Zentrum München, Munich, Germany', + email: 'lukas.heumos@helmholtz-munich.de', + github: '@zethson', + contribution: ['author'], + orcid: '0000-0002-8937-3457', + ], + [ + name: 'Luis Heinzlmeier', + affiliation: 'Institute of Computational Biology, Helmholtz Zentrum München, Munich, Germany', + email: 'luis.heinzlmeier@helmholtz-munich.de', + github: '@LuisHeinzlmeier', + contribution: ['maintainer'], + orcid: '0009-0002-3261-4472', + ], + [ + name: 'Nico Trummer', + affiliation: 'Technical University of Munich', + email: 'nico.trummer@tum.de', + github: '@nictru', + contribution: ['maintainer'], + orcid: '0000-0002-4639-0935', + ], + [ + name: 'Xichen Wu', + affiliation: 'Institute of Computational Biology, Helmholtz Zentrum München, Munich, Germany', email: '', - github: '', - contribution: [], - orcid: '', + github: '@wxicu', + contribution: ['author'], + orcid: '0009-0008-2168-4508', + ], + [ + name: 'Mylène Mariana Gonzales André', + affiliation: 'Technical University of Munich', + email: '', + github: '@mari-ga', + contribution: ['author'], + orcid: '0000-0002-9846-0130', + ], + [ + name: 'Seo Hyon Kim', + affiliation: 'Institute of Computational Biology, Helmholtz Zentrum München, Munich, Germany', + email: 'seohyon.l.kim@gmail.com', + github: '@seohyonkim', + contribution: ['contributor'], + orcid: '0009-0007-3062-4681', ] ] homePage = 'https://github.com/nf-core/hadge' description = """Comprehensive pipeline for donor demultiplexing in single cell""" mainScript = 'main.nf' - defaultBranch = 'master' + defaultBranch = 'main' nextflowVersion = '!>=25.04.0' version = '1.0.0dev' - doi = '' + doi = '10.1101/2023.07.23.550061' } // Nextflow plugins diff --git a/nextflow_schema.json b/nextflow_schema.json index 3d1e66ab..ec4ec154 100644 --- a/nextflow_schema.json +++ b/nextflow_schema.json @@ -30,18 +30,13 @@ "enum": ["genetic", "hashing", "rescue", "donor_match"], "default": "rescue" }, - "match_donor": { - "type": "boolean", - "description": "Match donor.", - "fa_icon": "fas fa-user-check" - }, "hash_tools": { "type": "string", "description": "Tools used for hash demultiplexing.", - "help_text": "If you want to use multiple, separate with a comma. The available tools are: htodemux, multiseq, cellhashr, demuxem, gmm-demux, hasheddrops, hashsolo", + "help_text": "If you want to use multiple, separate with a comma. The available tools are: htodemux, multiseq, bff, demuxem, gmm-demux, hasheddrops, hashsolo", "fa_icon": "fas fa-hashtag", "default": "gmm-demux", - "pattern": "^(htodemux|multiseq|cellhashr|demuxem|gmm-demux|hasheddrops|hashsolo)(,(htodemux|multiseq|cellhashr|demuxem|gmm-demux|hasheddrops|hashsolo))*$" + "pattern": "^(htodemux|multiseq|bff|demuxem|gmm-demux|hasheddrops|hashsolo)(,(htodemux|multiseq|bff|demuxem|gmm-demux|hasheddrops|hashsolo))*$" }, "genetic_tools": { "type": "string", @@ -90,73 +85,6 @@ } } }, - "donor_match_options": { - "title": "Donor match options", - "type": "object", - "fa_icon": "fas fa-user-check", - "description": "Options specific to donor matching functionality.", - "properties": { - "vireo_parent_dir": { - "type": ["string", "null"], - "format": "directory-path", - "description": "A parent folder which contains the output folder of vireo in the format of vireo_[taskID/sampleId] generated by hadge pipeline only when running in donor_match mode. In other modes, the input is passed by the pipeline automatically.", - "fa_icon": "fas fa-folder-open", - "default": null - }, - "demultiplexing_result": { - "type": ["string", "null"], - "format": "file-path", - "exists": true, - "mimetype": "text/csv", - "pattern": "^\\S+\\.csv$", - "description": "A CSV file with demultiplexing assignment only when running in donor_match mode. In other modes, the input is passed by the pipeline automatically.", - "fa_icon": "fas fa-file-csv", - "default": null - }, - "cell_genotype": { - "type": ["string", "null"], - "format": "file-path", - "exists": true, - "pattern": "^\\S+\\.vcf(\\.gz)?$", - "description": "The path to the VCF file containing the genotype of the cells.", - "fa_icon": "fas fa-dna", - "default": null - }, - "match_donor_method1": { - "type": ["string", "null"], - "description": "The method name to match donors. If null all genotype-based methods are compared.", - "fa_icon": "fas fa-user-check", - "default": null - }, - "match_donor_method2": { - "type": ["string", "null"], - "description": "The method name to match donors. If null, all hashing-based methods are compared.", - "fa_icon": "fas fa-user-check", - "default": null - }, - "findVariants": { - "type": "boolean", - "description": "Whether to extract a subset of informative variants when best genotype-based method for donor matching is vireo. default: subset as described in paper; vireo: subset by Vireo; True: subset using both methods; False: not extracting variants. Default: false", - "fa_icon": "fas fa-filter", - "default": false - }, - "variant_count": { - "type": "integer", - "description": "The threshold for the minimal read depth of a variant in the cell group when subseting the informative variants by default.", - "fa_icon": "fas fa-sort-numeric-up", - "minimum": 0, - "default": 10 - }, - "variant_pct": { - "type": "number", - "description": "The threshold for the minimal frequency of the alternative or reference allele to determine the dominant allele of a variant in the cell group when subseting the informative variants by default.", - "fa_icon": "fas fa-percentage", - "minimum": 0, - "maximum": 1, - "default": 0.9 - } - } - }, "reference_genome_options": { "title": "Reference genome options", "type": "object", @@ -196,6 +124,87 @@ } } }, + "donor_match_options": { + "title": "Donor match options", + "type": "object", + "fa_icon": "fas fa-user-check", + "description": "Options for matching donors between different demultiplexing methods.", + "properties": { + "match_donor": { + "type": "boolean", + "description": "Match donor between different demultiplexing methods.", + "default": true, + "fa_icon": "fas fa-user-check" + }, + "demultiplexing_result": { + "type": ["string", "null"], + "format": "file-path", + "description": "Path to demultiplexing result CSV file (necessary only for donor_match mode).", + "default": null, + "fa_icon": "fas fa-file-csv" + }, + "vireo_filtered_variants": { + "type": ["string", "null"], + "format": "file-path", + "description": "Path to Vireo filtered variants file (necessary only for donor_match mode).", + "default": null, + "fa_icon": "fas fa-file-code" + }, + "cell_genotype": { + "type": ["string", "null"], + "format": "file-path", + "description": "Path to cell genotype file (necessary only for donor_match mode).", + "default": null, + "fa_icon": "fas fa-dna" + }, + "match_donor_method1": { + "type": ["string", "null"], + "description": "First method to use for donor matching.", + "default": null, + "fa_icon": "fas fa-list" + }, + "match_donor_method2": { + "type": ["string", "null"], + "description": "Second method to use for donor matching.", + "default": null, + "fa_icon": "fas fa-list" + }, + "find_variants": { + "type": "boolean", + "description": "Find variants for donor matching.", + "default": true, + "fa_icon": "fas fa-search" + }, + "subset_gt_donors": { + "type": "boolean", + "description": "Option to subset the donor genotype based on detected variants.", + "default": true, + "fa_icon": "fas fa-filter" + }, + "variant_count": { + "type": "integer", + "description": "Minimum variant count threshold.", + "default": 10, + "minimum": 0, + "fa_icon": "fas fa-sort-numeric-up" + }, + "variant_pct": { + "type": "number", + "description": "The Minimal percentage of a variant for filtering. Has to be in a range between `[0,5;1[`. For example, 0.9 means that we only keep variants with a frequency higher than 90% or lower than 10%.", + "default": 0.9, + "minimum": 0.5, + "maximum": 1, + "fa_icon": "fas fa-percentage" + }, + "gt_donors": { + "type": ["string", "null"], + "format": "file-path", + "description": "Path to cell genotype file (necessary only for donor_match mode).", + "default": null, + "fa_icon": "fas fa-dna" + } + } + }, "demuxem_options": { "title": "demuxEM options", "type": "object", @@ -266,7 +275,7 @@ "bff_preprocessing": { "type": "boolean", "description": "Whether to run preprocessing steps for BFF.", - "default": false, + "default": true, "fa_icon": "fas fa-cogs" }, "bff_barcodeWhitelist": { @@ -342,6 +351,13 @@ "default": null, "fa_icon": "fas fa-file-alt" }, + "gmmdemux_estimated_n_cells": { + "type": ["integer", "null"], + "description": "If specified, it will generate the statistic summary of the dataset, including MSM and SSM rates. This requires an estimated total number of cells in the assay as input.", + "default": null, + "minimum": 1, + "fa_icon": "fas fa-calculator" + }, "gmmdemux_type_report": { "type": "boolean", "description": "If true, full classification report is generated, otherwise the simplified classification report.", @@ -521,7 +537,7 @@ "type": "boolean", "description": "Generate feature scatter plot. If no features are provided (one of them is null), the first two features from the assay will be used.", "default": true, - "fa_icon": "fas fa-scatter-chart" + "fa_icon": "far fa-chart-bar" }, "htodemux_visualization_scatterFeat1": { "type": "string", @@ -539,7 +555,7 @@ "type": "boolean", "description": "Generate violin plot.", "default": true, - "fa_icon": "fas fa-violin" + "fa_icon": "far fa-chart-bar" }, "htodemux_visualization_vlnFeatures": { "type": "string", @@ -993,7 +1009,8 @@ "vireo_force_learn_gt": { "type": "boolean", "description": "If true, treat donor GT as prior only and learn genotypes from data.", - "fa_icon": "fas fa-graduation-cap" + "fa_icon": "fas fa-graduation-cap", + "default": true }, "vireo_ase_mode": { "type": "boolean", @@ -1298,7 +1315,7 @@ "type": "boolean", "description": "Don't remap with minimap2 (not recommended unless in conjunction with --common_variants).", "default": false, - "fa_icon": "fas fa-skip-forward" + "fa_icon": "fas fa-angle-double-right" }, "souporcell_ignore": { "type": "boolean", diff --git a/nf-test.config b/nf-test.config index 3a1fff59..8e869e2a 100644 --- a/nf-test.config +++ b/nf-test.config @@ -15,10 +15,11 @@ config { profile "test" // list of filenames or patterns that should be trigger a full test run - triggers 'nextflow.config', 'nf-test.config', 'conf/test.config', 'tests/nextflow.config', 'tests/.nftignore' + triggers 'nextflow.config', 'nf-test.config', 'conf/test.config', 'conf/test_genetic.config', 'conf/test_hashing.config', 'conf/test_donor_match.config', 'tests/nextflow.config', 'tests/.nftignore' // load the necessary plugins plugins { load "nft-utils@0.0.3" + load "nft-vcf@1.0.7" } } diff --git a/ro-crate-metadata.json b/ro-crate-metadata.json index a08c4596..1e7e8f38 100644 --- a/ro-crate-metadata.json +++ b/ro-crate-metadata.json @@ -23,7 +23,7 @@ "@type": "Dataset", "creativeWorkStatus": "InProgress", "datePublished": "2025-11-14T19:53:15+00:00", - "description": "

    \n \n \n \"nf-core/hadge\"\n \n

    \n\n[![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://github.com/codespaces/new/nf-core/hadge)\n[![GitHub Actions CI Status](https://github.com/nf-core/hadge/actions/workflows/nf-test.yml/badge.svg)](https://github.com/nf-core/hadge/actions/workflows/nf-test.yml)\n[![GitHub Actions Linting Status](https://github.com/nf-core/hadge/actions/workflows/linting.yml/badge.svg)](https://github.com/nf-core/hadge/actions/workflows/linting.yml)[![AWS CI](https://img.shields.io/badge/CI%20tests-full%20size-FF9900?labelColor=000000&logo=Amazon%20AWS)](https://nf-co.re/hadge/results)[![Cite with Zenodo](http://img.shields.io/badge/DOI-10.5281/zenodo.XXXXXXX-1073c8?labelColor=000000)](https://doi.org/10.5281/zenodo.XXXXXXX)\n[![nf-test](https://img.shields.io/badge/unit_tests-nf--test-337ab7.svg)](https://www.nf-test.com)\n\n[![Nextflow](https://img.shields.io/badge/version-%E2%89%A525.04.0-green?style=flat&logo=nextflow&logoColor=white&color=%230DC09D&link=https%3A%2F%2Fnextflow.io)](https://www.nextflow.io/)\n[![nf-core template version](https://img.shields.io/badge/nf--core_template-3.4.1-green?style=flat&logo=nfcore&logoColor=white&color=%2324B064&link=https%3A%2F%2Fnf-co.re)](https://github.com/nf-core/tools/releases/tag/3.4.1)\n[![run with conda](http://img.shields.io/badge/run%20with-conda-3EB049?labelColor=000000&logo=anaconda)](https://docs.conda.io/en/latest/)\n[![run with docker](https://img.shields.io/badge/run%20with-docker-0db7ed?labelColor=000000&logo=docker)](https://www.docker.com/)\n[![run with singularity](https://img.shields.io/badge/run%20with-singularity-1d355c.svg?labelColor=000000)](https://sylabs.io/docs/)\n[![Launch on Seqera Platform](https://img.shields.io/badge/Launch%20%F0%9F%9A%80-Seqera%20Platform-%234256e7)](https://cloud.seqera.io/launch?pipeline=https://github.com/nf-core/hadge)\n\n[![Get help on Slack](http://img.shields.io/badge/slack-nf--core%20%23hadge-4A154B?labelColor=000000&logo=slack)](https://nfcore.slack.com/channels/hadge)[![Follow on Bluesky](https://img.shields.io/badge/bluesky-%40nf__core-1185fe?labelColor=000000&logo=bluesky)](https://bsky.app/profile/nf-co.re)[![Follow on Mastodon](https://img.shields.io/badge/mastodon-nf__core-6364ff?labelColor=FFFFFF&logo=mastodon)](https://mstdn.science/@nf_core)[![Watch on YouTube](http://img.shields.io/badge/youtube-nf--core-FF0000?labelColor=000000&logo=youtube)](https://www.youtube.com/c/nf-core)\n\n## Introduction\n\n**nf-core/hadge** is a bioinformatics pipeline that ...\n\n\n\n\n2. Present QC for raw reads ([`MultiQC`](http://multiqc.info/))\n\n## Usage\n\n> [!NOTE]\n> If you are new to Nextflow and nf-core, please refer to [this page](https://nf-co.re/docs/usage/installation) on how to set-up Nextflow. Make sure to [test your setup](https://nf-co.re/docs/usage/introduction#how-to-run-a-pipeline) with `-profile test` before running the workflow on actual data.\n\n\n\nNow, you can run the pipeline using:\n\n\n\n```bash\nnextflow run nf-core/hadge \\\n -profile \\\n --input samplesheet.csv \\\n --outdir \n```\n\n> [!WARNING]\n> Please provide pipeline parameters via the CLI or Nextflow `-params-file` option. Custom config files including those provided by the `-c` Nextflow option can be used to provide any configuration _**except for parameters**_; see [docs](https://nf-co.re/docs/usage/getting_started/configuration#custom-configuration-files).\n\nFor more details and further functionality, please refer to the [usage documentation](https://nf-co.re/hadge/usage) and the [parameter documentation](https://nf-co.re/hadge/parameters).\n\n## Pipeline output\n\nTo see the results of an example test run with a full size dataset refer to the [results](https://nf-co.re/hadge/results) tab on the nf-core website pipeline page.\nFor more details about the output files and reports, please refer to the\n[output documentation](https://nf-co.re/hadge/output).\n\n## Credits\n\nnf-core/hadge was originally written by Fabiola Curion.\n\nWe thank the following people for their extensive assistance in the development of this pipeline:\n\n\n\n## Contributions and Support\n\nIf you would like to contribute to this pipeline, please see the [contributing guidelines](.github/CONTRIBUTING.md).\n\nFor further information or help, don't hesitate to get in touch on the [Slack `#hadge` channel](https://nfcore.slack.com/channels/hadge) (you can join with [this invite](https://nf-co.re/join/slack)).\n\n## Citations\n\n\n\n\n\n\nAn extensive list of references for the tools used by the pipeline can be found in the [`CITATIONS.md`](CITATIONS.md) file.\n\nYou can cite the `nf-core` publication as follows:\n\n> **The nf-core framework for community-curated bioinformatics pipelines.**\n>\n> Philip Ewels, Alexander Peltzer, Sven Fillinger, Harshil Patel, Johannes Alneberg, Andreas Wilm, Maxime Ulysse Garcia, Paolo Di Tommaso & Sven Nahnsen.\n>\n> _Nat Biotechnol._ 2020 Feb 13. doi: [10.1038/s41587-020-0439-x](https://dx.doi.org/10.1038/s41587-020-0439-x).\n", + "description": "

    \n \n \n \"nf-core/hadge\"\n \n

    \n\n[![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://github.com/codespaces/new/nf-core/hadge)\n[![GitHub Actions CI Status](https://github.com/nf-core/hadge/actions/workflows/nf-test.yml/badge.svg)](https://github.com/nf-core/hadge/actions/workflows/nf-test.yml)\n[![GitHub Actions Linting Status](https://github.com/nf-core/hadge/actions/workflows/linting.yml/badge.svg)](https://github.com/nf-core/hadge/actions/workflows/linting.yml)[![AWS CI](https://img.shields.io/badge/CI%20tests-full%20size-FF9900?labelColor=000000&logo=Amazon%20AWS)](https://nf-co.re/hadge/results)[![Cite with Zenodo](http://img.shields.io/badge/DOI-10.5281/zenodo.XXXXXXX-1073c8?labelColor=000000)](https://doi.org/10.5281/zenodo.XXXXXXX)\n[![nf-test](https://img.shields.io/badge/unit_tests-nf--test-337ab7.svg)](https://www.nf-test.com)\n\n[![Nextflow](https://img.shields.io/badge/version-%E2%89%A525.04.0-green?style=flat&logo=nextflow&logoColor=white&color=%230DC09D&link=https%3A%2F%2Fnextflow.io)](https://www.nextflow.io/)\n[![nf-core template version](https://img.shields.io/badge/nf--core_template-3.4.1-green?style=flat&logo=nfcore&logoColor=white&color=%2324B064&link=https%3A%2F%2Fnf-co.re)](https://github.com/nf-core/tools/releases/tag/3.4.1)\n[![run with conda](http://img.shields.io/badge/run%20with-conda-3EB049?labelColor=000000&logo=anaconda)](https://docs.conda.io/en/latest/)\n[![run with docker](https://img.shields.io/badge/run%20with-docker-0db7ed?labelColor=000000&logo=docker)](https://www.docker.com/)\n[![run with singularity](https://img.shields.io/badge/run%20with-singularity-1d355c.svg?labelColor=000000)](https://sylabs.io/docs/)\n[![Launch on Seqera Platform](https://img.shields.io/badge/Launch%20%F0%9F%9A%80-Seqera%20Platform-%234256e7)](https://cloud.seqera.io/launch?pipeline=https://github.com/nf-core/hadge)\n\n[![Get help on Slack](http://img.shields.io/badge/slack-nf--core%20%23hadge-4A154B?labelColor=000000&logo=slack)](https://nfcore.slack.com/channels/hadge)[![Follow on Bluesky](https://img.shields.io/badge/bluesky-%40nf__core-1185fe?labelColor=000000&logo=bluesky)](https://bsky.app/profile/nf-co.re)[![Follow on Mastodon](https://img.shields.io/badge/mastodon-nf__core-6364ff?labelColor=FFFFFF&logo=mastodon)](https://mstdn.science/@nf_core)[![Watch on YouTube](http://img.shields.io/badge/youtube-nf--core-FF0000?labelColor=000000&logo=youtube)](https://www.youtube.com/c/nf-core)\n\n## Introduction\n\n**nf-core/hadge** (**ha**shing **d**econvolution combined with **ge**notype information) is a bioinformatics pipeline that combines 11 methods to perform both hashing- and genotype-based deconvolution on single cell multiplexing data.\nIt takes a samplesheet with count matrices, BAM and VCF files as input, performs deconvolution with every method, joins all results and finally recovers previously discarded cells by combining the best performing methods (donor matching).\n\n![nf-core/hadge metro map](docs/images/pipeline.png)\n\n1. Untar matrices\n2. Extract hto names from matrix\n3. Perform genetic-based deconvolution\n 1. Get single cell genotype [`cellSNP`](https://github.com/single-cell-genetics/cellSNP)\n 2. [`vireo`](https://github.com/single-cell-genetics/vireo)\n 3. [`demuxlet`](https://github.com/statgen/popscle)\n 4. [`freemuxlet`](https://github.com/statgen/popscle)\n 5. [`souporcell`](https://github.com/wheaton5/souporcell)\n4. summarize assignments and classifications\n5. Perform hashing-based deconvolution\n 1. [`htodemux`](https://satijalab.org/seurat/articles/hashing_vignette)\n 2. [`multiseq`](https://satijalab.org/seurat/reference/multiseqdemux)\n 3. [`bff`](https://github.com/BimberLab/cellhashR)\n 4. [`demuxem`](https://demuxem.readthedocs.io/en/latest/)\n 5. [`gmm-demux`](https://github.com/CHPGenetics/GMM-demux)\n 6. [`hasheddrops`](https://github.com/MarioniLab/DropletUtils)\n 7. [`hashsolo`](https://scanpy.readthedocs.io/en/stable/generated/scanpy.external.pp.hashsolo.html)\n6. summarize assignments and classifications\n7. Join all results\n8. Donor match\n9. Find informative variants\n10. Create AnnData and Mudata objects\n11. [`MultiQC`](http://multiqc.info/)\n\n## Usage\n\n> [!NOTE]\n> If you are new to Nextflow and nf-core, please refer to [this page](https://nf-co.re/docs/usage/installation) on how to set-up Nextflow. Make sure to [test your setup](https://nf-co.re/docs/usage/introduction#how-to-run-a-pipeline) with `-profile test` before running the workflow on actual data. The profile `test` is used to test hadge's rescue mode, but you can also test the other modes with the profiles `test_genetic`, `test_hashing` and `test_donor_match`.\n\nFirst, prepare a samplesheet with your input data that looks as follows:\n\n`samplesheet.csv`:\n\n```csv\nsample,rna_matrix,hto_matrix,bam,vcf,n_samples,barcodes\nid1,rna.tar.gz,hto.tar.gz,chr21.bam,donor_genotype_chr21.vcf,2,barcodes.tsv\nid2,rna.tar.gz,hto.tar.gz,chr21.bam,donor_genotype_chr21.vcf,2,barcodes.tsv\nid3,rna.tar.gz,hto.tar.gz,chr21.bam,donor_genotype_chr21.vcf,2,barcodes.tsv\n```\n\nEach row contains data from a single-cell multiplexing experiment. The RNA-seq (`rna_matrix`) and hashing (`hto_matrix`) count matrices are provided in a 10x Genomics format and compressed as `.tar.gz`.\nGenetic deconvolution requires both the alignment file (`bam`) and a list of common SNPs (`vcf`). Users must specify the number of multiplexed donors (`n_samples`) and identify the target cells for deconvolution (`barcodes`).\n\nNow, you can run the pipeline using:\n\n```bash\nnextflow run nf-core/hadge \\\n -profile \\\n --input samplesheet.csv \\\n --outdir \\\n --mode rescue \\\n --hash_tools htodemux,hasheddrops,multiseq,gmm-demux,bff,hashsolo \\\n --genetic_tools demuxlet,freemuxlet,vireo,souporcell \\\n --fasta \n```\n\n> [!WARNING]\n> Please provide pipeline parameters via the CLI or Nextflow `-params-file` option. Custom config files including those provided by the `-c` Nextflow option can be used to provide any configuration _**except for parameters**_; see [docs](https://nf-co.re/docs/usage/getting_started/configuration#custom-configuration-files).\n\nFor more details and further functionality, please refer to the [usage documentation](https://nf-co.re/hadge/usage) and the [parameter documentation](https://nf-co.re/hadge/parameters).\n\n## Pipeline output\n\nTo see the results of an example test run with a full size dataset refer to the [results](https://nf-co.re/hadge/results) tab on the nf-core website pipeline page.\nFor more details about the output files and reports, please refer to the\n[output documentation](https://nf-co.re/hadge/output).\n\n## Credits\n\nnf-core/hadge was originally written by Fabiola Curion ([@bio-la](https://github.com/bio-la)), Xichen Wu ([@wxicu](https://github.com/wxicu)), Lukas Heumos ([@zethson](https://github.com/Zethson)) and Mariana Gonzales Andre ([@mari-ga](https://github.com/mari-ga)).\n\nWe thank the following people for their extensive assistance in the development of this pipeline:\n\n- [Luis Heinzlmeier](https://github.com/LuisHeinzlmeier)\n- [Nico Trummer](https://github.com/nictru)\n- [Seo Hyon Kim](https://github.com/seohyonkim)\n\n\n## Contributions and Support\n\nIf you would like to contribute to this pipeline, please see the [contributing guidelines](.github/CONTRIBUTING.md).\n\nFor further information or help, don't hesitate to get in touch on the [Slack `#hadge` channel](https://nfcore.slack.com/channels/hadge) (you can join with [this invite](https://nf-co.re/join/slack)).\n\n## Citations\n\n\n\n\n\n\nAn extensive list of references for the tools used by the pipeline can be found in the [`CITATIONS.md`](CITATIONS.md) file.\n\nYou can cite the `nf-core` publication as follows:\n\n> **The nf-core framework for community-curated bioinformatics pipelines.**\n>\n> Philip Ewels, Alexander Peltzer, Sven Fillinger, Harshil Patel, Johannes Alneberg, Andreas Wilm, Maxime Ulysse Garcia, Paolo Di Tommaso & Sven Nahnsen.\n>\n> _Nat Biotechnol._ 2020 Feb 13. doi: [10.1038/s41587-020-0439-x](https://dx.doi.org/10.1038/s41587-020-0439-x).\n", "hasPart": [ { "@id": "main.nf" diff --git a/subworkflows/local/genetic_demultiplexing/main.nf b/subworkflows/local/genetic_demultiplexing/main.nf index 31ccbe6d..b1fd2483 100644 --- a/subworkflows/local/genetic_demultiplexing/main.nf +++ b/subworkflows/local/genetic_demultiplexing/main.nf @@ -23,13 +23,15 @@ workflow GENETIC_DEMULTIPLEXING { ch_demuxlet = Channel.empty() ch_freemuxlet = Channel.empty() ch_souporcell = Channel.empty() - ch_cellsnp = Channel.empty() + ch_gt_cells = Channel.empty() + ch_gt_donors = Channel.empty() + ch_vireo_filtered_variants = Channel.empty() - ch_summary = ch_samplesheet.map{ meta, rna, hto, _bam, barcodes, _vcf -> - [meta, rna, hto, barcodes] + ch_summary = ch_samplesheet.map{ meta, _bam, barcodes, _vcf -> + [meta, barcodes] } - ch_samplesheet = ch_samplesheet.map{ meta, _rna, _hto, bam, barcodes, vcf -> + ch_samplesheet = ch_samplesheet.map{ meta, bam, barcodes, vcf -> [meta, bam, barcodes, vcf] } @@ -60,7 +62,9 @@ workflow GENETIC_DEMULTIPLEXING { .map { meta, _bam, barcodes, vcf, new_bam -> [meta, new_bam, barcodes, vcf] } } - if (methods.contains('vireo')) { + + + if ( params.find_variants | methods.contains('vireo')){ SAMTOOLS_INDEX(ch_samplesheet.map { meta, bam, _barcodes, _vcf -> [meta, bam] }) ch_versions = ch_versions.mix(SAMTOOLS_INDEX.out.versions) @@ -68,16 +72,19 @@ workflow GENETIC_DEMULTIPLEXING { ch_samplesheet.join(SAMTOOLS_INDEX.out.bai).map { meta, bam, barcodes, vcf, bai -> [meta, bam, bai, vcf, barcodes] } ) - ch_cellsnp = ch_cellsnp.mix(CELLSNP_MODEA.out.cell) + ch_gt_cells = ch_gt_cells.mix(CELLSNP_MODEA.out.cell) ch_versions = ch_versions.mix(CELLSNP_MODEA.out.versions) + } + if (methods.contains('vireo')) { VIREO( - ch_samplesheet.join(CELLSNP_MODEA.out.cell).map { meta, _bam, _barcodes, vcf, cell -> [meta, cell, meta.n_samples, vcf, []] } + ch_samplesheet.join(ch_gt_cells).map { meta, _bam, _barcodes, vcf, cell -> [meta, cell, meta.n_samples, vcf, []] } ) + ch_vireo = ch_vireo.mix(VIREO.out.donor_ids) + ch_vireo_filtered_variants = ch_vireo_filtered_variants.mix(VIREO.out.filtered_variants) + ch_gt_donors = ch_gt_donors.mix(VIREO.out.genotype_vcf) ch_versions = ch_versions.mix(VIREO.out.versions) - - } if (methods.contains('demuxlet') || methods.contains('freemuxlet')) { @@ -106,6 +113,10 @@ workflow GENETIC_DEMULTIPLEXING { [ meta, bam, barcodes, meta.n_samples ] } + if (! params.fasta ) { + log.warn("The pipeline is downloading the full reference genome from ${params.genome} because only `genome` and not `fasta` is set. To reduce long download times and high bandwidth usage, provide your own reference by specifying `fasta`.") + } + SOUPORCELL( ch_souporcell_bam_barcodes_clusters, channel.value([[id: 'fasta'], file(fasta, checkIfExists: true)]) @@ -122,10 +133,7 @@ workflow GENETIC_DEMULTIPLEXING { .join(ch_souporcell, remainder: true) .map { tuple -> tuple.collect { it == null ? [] : it } } - GENE_SUMMARY( - ch_summary, - tuple(params.generate_anndata, params.generate_mudata) - ) + GENE_SUMMARY(ch_summary) ch_versions = ch_versions.mix(GENE_SUMMARY.out.versions) @@ -133,6 +141,8 @@ workflow GENETIC_DEMULTIPLEXING { emit: summary_assignment = GENE_SUMMARY.out.assignment summary_classification = GENE_SUMMARY.out.classification - cell_genotype = ch_cellsnp + vireo_filtered_variants = ch_vireo_filtered_variants + gt_cells = ch_gt_cells + gt_donors = ch_gt_donors versions = ch_versions // channel: [ versions.yml ] } diff --git a/subworkflows/local/hash_demultiplexing/main.nf b/subworkflows/local/hash_demultiplexing/main.nf index b028d1e6..cdd60a8d 100644 --- a/subworkflows/local/hash_demultiplexing/main.nf +++ b/subworkflows/local/hash_demultiplexing/main.nf @@ -1,3 +1,4 @@ +include { validateHtoNames } from '../../../subworkflows/local/utils_nfcore_hadge_pipeline' include { PREPROCESSING_FOR_HTODEMUX_MULTISEQ } from '../../../modules/local/preprocessing_for_htodemux_multiseq' include { HTODEMUX } from '../../../modules/nf-core/htodemux' include { HTODEMUX_VISUALIZATION } from '../../../modules/local/htodemux_visualization' @@ -11,7 +12,6 @@ include { SCANPY_HASHSOLO as HASHSOLO } from '../.. include { HASHEDDROPS } from '../../../modules/nf-core/hasheddrops' include { HASH_SUMMARY } from '../../../modules/local/hash_summary' - workflow HASH_DEMULTIPLEXING { take: ch_samplesheet // channel: samplesheet read in from --input @@ -32,18 +32,12 @@ workflow HASH_DEMULTIPLEXING { ch_hasheddrops_id_to_hash = Channel.empty() ch_hashsolo = Channel.empty() - ch_samplesheet.map { meta, rna, hto -> - { - if (!rna) { - error("RNA matrix not provided for sample ${meta.id}, but this is required for hash demultiplexing. Please check your input samplesheet.") - } - if (!hto) { - error("HTO matrix not provided for sample ${meta.id}, but this is required for hash demultiplexing. Please check your input samplesheet.") - } + if (methods.contains('htodemux') || methods.contains('multiseq')) { + + ch_samplesheet.map { meta, _rna, _hto -> + validateHtoNames(meta) } - } - if (methods.contains('htodemux') || methods.contains('multiseq')) { PREPROCESSING_FOR_HTODEMUX_MULTISEQ( ch_samplesheet ) @@ -87,8 +81,7 @@ workflow HASH_DEMULTIPLEXING { } } - // TODO rename to bff since we named the module bff - if (methods.contains('cellhashr')) { + if (methods.contains('bff')) { BFF(ch_samplesheet.map { meta, _rna, hto -> [meta,hto,params.bff_methods,params.bff_preprocessing]}) ch_bff = ch_bff.mix(BFF.out.assignment) ch_versions = ch_versions.mix(BFF.out.versions) @@ -115,12 +108,11 @@ workflow HASH_DEMULTIPLEXING { if (methods.contains('gmm-demux')) { - // TODO do the same as for meta.n_cells as for hash_list ch_gmmdemux_input = ch_samplesheet.map { meta, _rna, hto -> [ meta, hto, - params.gmmdemux_hto_names ? params.gmmdemux_hto_names : meta.hashes, - meta.n_cells + params.gmmdemux_hto_names ? params.gmmdemux_hto_names : meta.hto_names, + params.gmmdemux_estimated_n_cells ? gmmdemux_estimated_n_cells : [], ] } @@ -167,7 +159,7 @@ workflow HASH_DEMULTIPLEXING { ch_versions = ch_versions.mix(HASHSOLO.out.versions) } - ch_summary = ch_samplesheet + ch_summary = ch_samplesheet.map { meta, rna, hto -> [meta,hto] } .join(ch_htodemux_assignments, remainder: true) .join(ch_htodemux_classifications, remainder: true) .join(ch_multiseq, remainder: true) @@ -182,10 +174,7 @@ workflow HASH_DEMULTIPLEXING { // Empty inputs solved as recommended here: // https://nf-co.re/docs/guidelines/components/modules#optional-inputs - HASH_SUMMARY( - ch_summary, - tuple(params.generate_anndata, params.generate_mudata, params.bff_methods) - ) + HASH_SUMMARY(ch_summary,params.bff_methods) ch_versions = ch_versions.mix(HASH_SUMMARY.out.versions) diff --git a/subworkflows/local/utils_nfcore_hadge_pipeline/main.nf b/subworkflows/local/utils_nfcore_hadge_pipeline/main.nf index 92a6deaa..6f053e2f 100644 --- a/subworkflows/local/utils_nfcore_hadge_pipeline/main.nf +++ b/subworkflows/local/utils_nfcore_hadge_pipeline/main.nf @@ -163,17 +163,102 @@ workflow PIPELINE_COMPLETION { FUNCTIONS ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ + // // Check and validate pipeline parameters // + +def checkParams(String paramName, String process, String mode, boolean isFile) { + def value = params[paramName] + + if( !value ) + error "Parameter '${paramName}' must be specified to run ${process} with mode '${mode}'" + + if( !value && !mode ) + error "Parameter '${paramName}' must be specified to run ${process}" + + if( isFile && !file(value).exists() ) + error "File specified for parameter '${paramName}' does not exist: ${value}" + + return true +} + def validateInputParameters() { + + // check parameters to run DONOR_MATCH or FIND_VARIANTS in 'donor_match' mode + if ( params.mode == 'donor_match' ){ + checkParams('demultiplexing_result', 'DONOR_MATCH', 'donor_match', true) + if ( params.find_variants ){ + ['cell_genotype', 'vireo_filtered_variants'].each { p -> + checkParams(p, 'FIND_VARIANTS', 'donor_match', true) + } + } + } + + // check parameters to run SUBSET_GT_DONORS in 'rescue' or 'donor_match' mode + if ( params.find_variants && params.subset_gt_donors ) { + if ( params.mode == 'rescue' && !(params.genetic_tools && params.genetic_tools.split(',').contains('vireo')) ){ + error "'SUBSET_GT_DONORS' requires the donor genotype as input. In rescue mode, please add 'vireo' to 'genetic_tools' or set 'subset_gt_donors' to false." + } + else if ( params.mode == 'donor_match' && !params.gt_donors ) { + error "'SUBSET_GT_DONORS' requires the donor genotype as input. In donor_match mode, please provide an existing file in 'gt_donors' or set 'subset_gt_donors' to false." + } + } + genomeExistsError() } + // // Validate channels from input samplesheet // + +def validateHtoNames(Map meta){ + if(meta.hto_names.split(",").any { it.contains('_') }){ + def bad = meta.hto_names.split(",").findAll { it.contains('_') }.join(', ') + throw new IllegalArgumentException( + "Running hadge with the methods htodemux or multiseq does not allow to use underscores ('_') in HTO names. Both tools require a SeuratObject as input, which will replace '_' with '-' leading to ambiguous or misleading assignment summaries. Please remove underscores ('_') from: ${bad}" + ) + } +} + +def checkSamplesheetInput(String colName, Object colValue, String mode, boolean isFile) { + if( !colValue ) + error "Samplesheet input '${colName}' must be specified to run hadge with mode '${mode}'" + + if( isFile && !file(colValue).exists() ) + error "File specified for samplesheet input '${colName}' does not exist: ${colValue}" +} + def validateInputSamplesheet(input) { + + def (meta, rna, hto, bam, barcodes, vcf) = input + + def inputs = [ + rna_matrix: rna, + hto_matrix: hto, + bam: bam, + barcodes: barcodes, + n_samples: meta.n_samples, + vcf: vcf + ] + + // define required columns for each mode + def modeColumns = [ + genetic: ['bam', 'vcf', 'n_samples', 'barcodes'], + hashing: ['rna_matrix', 'hto_matrix'], + rescue: ['rna_matrix', 'hto_matrix', 'bam', 'vcf', 'n_samples', 'barcodes'], + donor_match: ['n_samples'] + ] + + def colsToCheck = modeColumns[params.mode] + + colsToCheck.each { colName -> + def colValue = inputs[colName] + def isFile = colName != 'n_samples' // n_samples is not a file + checkSamplesheetInput(colName, colValue, params.mode, isFile) + } + return input } diff --git a/tests/.nftignore b/tests/.nftignore index e429e8a3..82efe04c 100644 --- a/tests/.nftignore +++ b/tests/.nftignore @@ -17,3 +17,4 @@ hashing/preprocessing/*/*_preprocessed.rds genetic/popscle/freemuxlet/*/*.clust1.vcf.gz genetic/souporcell/*/*/ambient_rna.txt genetic/souporcell/*/*/cluster_genotypes.vcf +find_variants/*/subset_gt_donors/*.vcf.gz diff --git a/tests/default.nf.test b/tests/default.nf.test index eb04667f..14da0d05 100644 --- a/tests/default.nf.test +++ b/tests/default.nf.test @@ -3,6 +3,7 @@ nextflow_pipeline { name "Test pipeline" script "../main.nf" tag "pipeline" + tag "test" test("-profile test") { @@ -17,6 +18,9 @@ nextflow_pipeline { def stable_name = getAllFilesFromDir(params.outdir, relative: true, includeDir: true, ignore: ['pipeline_info/*.{html,json,txt}']) // stable_path: All files in ${params.outdir}/ with stable content def stable_path = getAllFilesFromDir(params.outdir, ignoreFile: 'tests/.nftignore') + // VCF files (cluster_genotypes.vcf md5 differs but unused in pipeline) + def vcf_files = getAllFilesFromDir(params.outdir, include: ['**/*.vcf{,.gz}'], ignore: ['genetic/souporcell/*/*/cluster_genotypes.vcf']) + assertAll( { assert workflow.success}, { assert snapshot( @@ -25,7 +29,9 @@ nextflow_pipeline { // All stable path name, with a relative path stable_name, // All files with stable contents - stable_path + stable_path, + // VCF files + vcf_files.isEmpty() ? 'No VCF files' : vcf_files.collect { file -> file.getName() + ":md5," + path(file.toString()).vcf.variantsMD5 } ).match() } ) } diff --git a/tests/default.nf.test.snap b/tests/default.nf.test.snap index 6f904f31..0b39bae7 100644 --- a/tests/default.nf.test.snap +++ b/tests/default.nf.test.snap @@ -2,50 +2,53 @@ "-profile test": { "content": [ { + "BFF": { + "r-base": "4.3.3", + "r-seurat": "4.3.0.1", + "cellhashR": "1.0.3" + }, "CELLSNP_MODEA": { "cellsnp": "1.2.3" }, "DONOR_MATCH": { - "r-base": "4.5.1", - "r-complexupset": "1.3.3", + "r-base": "4.5.2", "r-data.table": "1.17.8", "r-pheatmap": "1.0.13", + "r-tidyverse": "2.0.0" + }, + "FIND_VARIANTS": { + "r-base": "4.5.2", + "r-complexupset": "1.3.3", + "r-data.table": "1.17.8", "r-tidyverse": "2.0.0", "r-vcfr": "1.15.0" }, "GENE_SUMMARY": { - "mudata": "0.3.1", - "numpy": "1.24.2", - "pandas": "2.3.1", - "pegasusio": "0.10.0", - "python": "3.11.14", - "scanpy": "1.11.2" + "numpy": "2.3.5", + "pandas": "2.3.3", + "python": "3.14.2", + "scanpy": "1.11.5" + }, + "GMMDEMUX": { + "GMM-Demux": "0.2.2.3" }, "HASHEDDROPS": { "r-base": "4.4.3", "r-seurat": "5.3.0", "dropletutils": "1.26.0" }, - "HASHSOLO": { - "matplotlib": "3.10.5", - "pandas": "2.3.1", - "python": "3.12.11", - "scanpy": "1.11.2" - }, "HASH_SUMMARY": { - "mudata": "0.3.1", - "numpy": "1.24.2", - "pandas": "2.3.1", + "numpy": "2.3.5", + "pandas": "2.3.3", "pegasusio": "0.10.0", - "python": "3.11.14", - "scanpy": "1.11.2" + "python": "3.12.12", + "scanpy": "1.11.5" }, - "JOIN_RESULTS": { + "JOIN_RESULTS_ASSIGNMENT": { "csvtk": "0.31.0" }, - "MULTISEQDEMUX": { - "r-base": "4.4.3", - "r-seurat": "5.3.0" + "JOIN_RESULTS_CLASSIFICATION": { + "csvtk": "0.31.0" }, "POPSCLE_DSCPILEUP": { "popscle dsc-pileup": 0.1 @@ -53,10 +56,6 @@ "POPSCLE_FREEMUXLET": { "popscle": 0.1 }, - "PREPROCESSING_FOR_HTODEMUX_MULTISEQ": { - "r-base": "4.4.3", - "seurat": "5.3.0" - }, "SAMTOOLS_INDEX": { "samtools": 1.21 }, @@ -69,6 +68,9 @@ "SOUPORCELL": { "souporcell": 2.5 }, + "SUBSET_GT_DONORS": { + "bcftools": 1.22 + }, "UMITOOLS_DEDUP": { "umitools": "1.1.5" }, @@ -88,132 +90,213 @@ [ "donor_match", "donor_match/test1", + "donor_match/test1/freemuxlet_vs_bff_consensuscall", + "donor_match/test1/freemuxlet_vs_bff_consensuscall/test1_freemuxlet_vs_bff_consensuscall_all_assignment_after_match.csv", + "donor_match/test1/freemuxlet_vs_bff_consensuscall/test1_freemuxlet_vs_bff_consensuscall_concordance_heatmap.png", + "donor_match/test1/freemuxlet_vs_bff_consensuscall/test1_freemuxlet_vs_bff_consensuscall_correlation_res.csv", + "donor_match/test1/freemuxlet_vs_bff_consensuscall/test1_freemuxlet_vs_bff_consensuscall_donor_match.csv", + "donor_match/test1/freemuxlet_vs_bff_consensuscall/test1_freemuxlet_vs_bff_consensuscall_intersect_assignment_after_match.csv", + "donor_match/test1/freemuxlet_vs_bff_raw", + "donor_match/test1/freemuxlet_vs_bff_raw/test1_freemuxlet_vs_bff_raw_all_assignment_after_match.csv", + "donor_match/test1/freemuxlet_vs_bff_raw/test1_freemuxlet_vs_bff_raw_concordance_heatmap.png", + "donor_match/test1/freemuxlet_vs_bff_raw/test1_freemuxlet_vs_bff_raw_correlation_res.csv", + "donor_match/test1/freemuxlet_vs_bff_raw/test1_freemuxlet_vs_bff_raw_donor_match.csv", + "donor_match/test1/freemuxlet_vs_bff_raw/test1_freemuxlet_vs_bff_raw_intersect_assignment_after_match.csv", + "donor_match/test1/freemuxlet_vs_gmmdemux", + "donor_match/test1/freemuxlet_vs_gmmdemux/test1_freemuxlet_vs_gmmdemux_all_assignment_after_match.csv", + "donor_match/test1/freemuxlet_vs_gmmdemux/test1_freemuxlet_vs_gmmdemux_concordance_heatmap.png", + "donor_match/test1/freemuxlet_vs_gmmdemux/test1_freemuxlet_vs_gmmdemux_correlation_res.csv", + "donor_match/test1/freemuxlet_vs_gmmdemux/test1_freemuxlet_vs_gmmdemux_donor_match.csv", + "donor_match/test1/freemuxlet_vs_gmmdemux/test1_freemuxlet_vs_gmmdemux_intersect_assignment_after_match.csv", "donor_match/test1/freemuxlet_vs_hasheddrops", "donor_match/test1/freemuxlet_vs_hasheddrops/test1_freemuxlet_vs_hasheddrops_all_assignment_after_match.csv", "donor_match/test1/freemuxlet_vs_hasheddrops/test1_freemuxlet_vs_hasheddrops_concordance_heatmap.png", "donor_match/test1/freemuxlet_vs_hasheddrops/test1_freemuxlet_vs_hasheddrops_correlation_res.csv", "donor_match/test1/freemuxlet_vs_hasheddrops/test1_freemuxlet_vs_hasheddrops_donor_match.csv", "donor_match/test1/freemuxlet_vs_hasheddrops/test1_freemuxlet_vs_hasheddrops_intersect_assignment_after_match.csv", - "donor_match/test1/freemuxlet_vs_hashsolo", - "donor_match/test1/freemuxlet_vs_hashsolo/test1_freemuxlet_vs_hashsolo_all_assignment_after_match.csv", - "donor_match/test1/freemuxlet_vs_hashsolo/test1_freemuxlet_vs_hashsolo_concordance_heatmap.png", - "donor_match/test1/freemuxlet_vs_hashsolo/test1_freemuxlet_vs_hashsolo_correlation_res.csv", - "donor_match/test1/freemuxlet_vs_hashsolo/test1_freemuxlet_vs_hashsolo_donor_match.csv", - "donor_match/test1/freemuxlet_vs_hashsolo/test1_freemuxlet_vs_hashsolo_intersect_assignment_after_match.csv", - "donor_match/test1/freemuxlet_vs_multiseq", - "donor_match/test1/freemuxlet_vs_multiseq/test1_freemuxlet_vs_multiseq_all_assignment_after_match.csv", - "donor_match/test1/freemuxlet_vs_multiseq/test1_freemuxlet_vs_multiseq_concordance_heatmap.png", - "donor_match/test1/freemuxlet_vs_multiseq/test1_freemuxlet_vs_multiseq_correlation_res.csv", - "donor_match/test1/freemuxlet_vs_multiseq/test1_freemuxlet_vs_multiseq_donor_match.csv", - "donor_match/test1/freemuxlet_vs_multiseq/test1_freemuxlet_vs_multiseq_intersect_assignment_after_match.csv", + "donor_match/test1/souporcell_vs_bff_consensuscall", + "donor_match/test1/souporcell_vs_bff_consensuscall/test1_souporcell_vs_bff_consensuscall_all_assignment_after_match.csv", + "donor_match/test1/souporcell_vs_bff_consensuscall/test1_souporcell_vs_bff_consensuscall_concordance_heatmap.png", + "donor_match/test1/souporcell_vs_bff_consensuscall/test1_souporcell_vs_bff_consensuscall_correlation_res.csv", + "donor_match/test1/souporcell_vs_bff_consensuscall/test1_souporcell_vs_bff_consensuscall_donor_match.csv", + "donor_match/test1/souporcell_vs_bff_consensuscall/test1_souporcell_vs_bff_consensuscall_intersect_assignment_after_match.csv", + "donor_match/test1/souporcell_vs_bff_raw", + "donor_match/test1/souporcell_vs_bff_raw/test1_souporcell_vs_bff_raw_all_assignment_after_match.csv", + "donor_match/test1/souporcell_vs_bff_raw/test1_souporcell_vs_bff_raw_concordance_heatmap.png", + "donor_match/test1/souporcell_vs_bff_raw/test1_souporcell_vs_bff_raw_correlation_res.csv", + "donor_match/test1/souporcell_vs_bff_raw/test1_souporcell_vs_bff_raw_donor_match.csv", + "donor_match/test1/souporcell_vs_bff_raw/test1_souporcell_vs_bff_raw_intersect_assignment_after_match.csv", + "donor_match/test1/souporcell_vs_gmmdemux", + "donor_match/test1/souporcell_vs_gmmdemux/test1_souporcell_vs_gmmdemux_all_assignment_after_match.csv", + "donor_match/test1/souporcell_vs_gmmdemux/test1_souporcell_vs_gmmdemux_concordance_heatmap.png", + "donor_match/test1/souporcell_vs_gmmdemux/test1_souporcell_vs_gmmdemux_correlation_res.csv", + "donor_match/test1/souporcell_vs_gmmdemux/test1_souporcell_vs_gmmdemux_donor_match.csv", + "donor_match/test1/souporcell_vs_gmmdemux/test1_souporcell_vs_gmmdemux_intersect_assignment_after_match.csv", "donor_match/test1/souporcell_vs_hasheddrops", "donor_match/test1/souporcell_vs_hasheddrops/test1_souporcell_vs_hasheddrops_all_assignment_after_match.csv", "donor_match/test1/souporcell_vs_hasheddrops/test1_souporcell_vs_hasheddrops_concordance_heatmap.png", "donor_match/test1/souporcell_vs_hasheddrops/test1_souporcell_vs_hasheddrops_correlation_res.csv", "donor_match/test1/souporcell_vs_hasheddrops/test1_souporcell_vs_hasheddrops_donor_match.csv", "donor_match/test1/souporcell_vs_hasheddrops/test1_souporcell_vs_hasheddrops_intersect_assignment_after_match.csv", - "donor_match/test1/souporcell_vs_hashsolo", - "donor_match/test1/souporcell_vs_hashsolo/test1_souporcell_vs_hashsolo_all_assignment_after_match.csv", - "donor_match/test1/souporcell_vs_hashsolo/test1_souporcell_vs_hashsolo_concordance_heatmap.png", - "donor_match/test1/souporcell_vs_hashsolo/test1_souporcell_vs_hashsolo_correlation_res.csv", - "donor_match/test1/souporcell_vs_hashsolo/test1_souporcell_vs_hashsolo_donor_match.csv", - "donor_match/test1/souporcell_vs_hashsolo/test1_souporcell_vs_hashsolo_intersect_assignment_after_match.csv", - "donor_match/test1/souporcell_vs_multiseq", - "donor_match/test1/souporcell_vs_multiseq/test1_souporcell_vs_multiseq_all_assignment_after_match.csv", - "donor_match/test1/souporcell_vs_multiseq/test1_souporcell_vs_multiseq_concordance_heatmap.png", - "donor_match/test1/souporcell_vs_multiseq/test1_souporcell_vs_multiseq_correlation_res.csv", - "donor_match/test1/souporcell_vs_multiseq/test1_souporcell_vs_multiseq_donor_match.csv", - "donor_match/test1/souporcell_vs_multiseq/test1_souporcell_vs_multiseq_intersect_assignment_after_match.csv", "donor_match/test1/test1_best_all_assignment_after_match.csv", "donor_match/test1/test1_best_donor_match.csv", "donor_match/test1/test1_best_intersect_assignment_after_match.csv", "donor_match/test1/test1_score_record.csv", "donor_match/test2", + "donor_match/test2/freemuxlet_vs_bff_consensuscall", + "donor_match/test2/freemuxlet_vs_bff_consensuscall/test2_freemuxlet_vs_bff_consensuscall_all_assignment_after_match.csv", + "donor_match/test2/freemuxlet_vs_bff_consensuscall/test2_freemuxlet_vs_bff_consensuscall_concordance_heatmap.png", + "donor_match/test2/freemuxlet_vs_bff_consensuscall/test2_freemuxlet_vs_bff_consensuscall_correlation_res.csv", + "donor_match/test2/freemuxlet_vs_bff_consensuscall/test2_freemuxlet_vs_bff_consensuscall_donor_match.csv", + "donor_match/test2/freemuxlet_vs_bff_consensuscall/test2_freemuxlet_vs_bff_consensuscall_intersect_assignment_after_match.csv", + "donor_match/test2/freemuxlet_vs_bff_raw", + "donor_match/test2/freemuxlet_vs_bff_raw/test2_freemuxlet_vs_bff_raw_all_assignment_after_match.csv", + "donor_match/test2/freemuxlet_vs_bff_raw/test2_freemuxlet_vs_bff_raw_concordance_heatmap.png", + "donor_match/test2/freemuxlet_vs_bff_raw/test2_freemuxlet_vs_bff_raw_correlation_res.csv", + "donor_match/test2/freemuxlet_vs_bff_raw/test2_freemuxlet_vs_bff_raw_donor_match.csv", + "donor_match/test2/freemuxlet_vs_bff_raw/test2_freemuxlet_vs_bff_raw_intersect_assignment_after_match.csv", + "donor_match/test2/freemuxlet_vs_gmmdemux", + "donor_match/test2/freemuxlet_vs_gmmdemux/test2_freemuxlet_vs_gmmdemux_all_assignment_after_match.csv", + "donor_match/test2/freemuxlet_vs_gmmdemux/test2_freemuxlet_vs_gmmdemux_concordance_heatmap.png", + "donor_match/test2/freemuxlet_vs_gmmdemux/test2_freemuxlet_vs_gmmdemux_correlation_res.csv", + "donor_match/test2/freemuxlet_vs_gmmdemux/test2_freemuxlet_vs_gmmdemux_donor_match.csv", + "donor_match/test2/freemuxlet_vs_gmmdemux/test2_freemuxlet_vs_gmmdemux_intersect_assignment_after_match.csv", "donor_match/test2/freemuxlet_vs_hasheddrops", "donor_match/test2/freemuxlet_vs_hasheddrops/test2_freemuxlet_vs_hasheddrops_all_assignment_after_match.csv", "donor_match/test2/freemuxlet_vs_hasheddrops/test2_freemuxlet_vs_hasheddrops_concordance_heatmap.png", "donor_match/test2/freemuxlet_vs_hasheddrops/test2_freemuxlet_vs_hasheddrops_correlation_res.csv", "donor_match/test2/freemuxlet_vs_hasheddrops/test2_freemuxlet_vs_hasheddrops_donor_match.csv", "donor_match/test2/freemuxlet_vs_hasheddrops/test2_freemuxlet_vs_hasheddrops_intersect_assignment_after_match.csv", - "donor_match/test2/freemuxlet_vs_hashsolo", - "donor_match/test2/freemuxlet_vs_hashsolo/test2_freemuxlet_vs_hashsolo_all_assignment_after_match.csv", - "donor_match/test2/freemuxlet_vs_hashsolo/test2_freemuxlet_vs_hashsolo_concordance_heatmap.png", - "donor_match/test2/freemuxlet_vs_hashsolo/test2_freemuxlet_vs_hashsolo_correlation_res.csv", - "donor_match/test2/freemuxlet_vs_hashsolo/test2_freemuxlet_vs_hashsolo_donor_match.csv", - "donor_match/test2/freemuxlet_vs_hashsolo/test2_freemuxlet_vs_hashsolo_intersect_assignment_after_match.csv", - "donor_match/test2/freemuxlet_vs_multiseq", - "donor_match/test2/freemuxlet_vs_multiseq/test2_freemuxlet_vs_multiseq_all_assignment_after_match.csv", - "donor_match/test2/freemuxlet_vs_multiseq/test2_freemuxlet_vs_multiseq_concordance_heatmap.png", - "donor_match/test2/freemuxlet_vs_multiseq/test2_freemuxlet_vs_multiseq_correlation_res.csv", - "donor_match/test2/freemuxlet_vs_multiseq/test2_freemuxlet_vs_multiseq_donor_match.csv", - "donor_match/test2/freemuxlet_vs_multiseq/test2_freemuxlet_vs_multiseq_intersect_assignment_after_match.csv", + "donor_match/test2/souporcell_vs_bff_consensuscall", + "donor_match/test2/souporcell_vs_bff_consensuscall/test2_souporcell_vs_bff_consensuscall_all_assignment_after_match.csv", + "donor_match/test2/souporcell_vs_bff_consensuscall/test2_souporcell_vs_bff_consensuscall_concordance_heatmap.png", + "donor_match/test2/souporcell_vs_bff_consensuscall/test2_souporcell_vs_bff_consensuscall_correlation_res.csv", + "donor_match/test2/souporcell_vs_bff_consensuscall/test2_souporcell_vs_bff_consensuscall_donor_match.csv", + "donor_match/test2/souporcell_vs_bff_consensuscall/test2_souporcell_vs_bff_consensuscall_intersect_assignment_after_match.csv", + "donor_match/test2/souporcell_vs_bff_raw", + "donor_match/test2/souporcell_vs_bff_raw/test2_souporcell_vs_bff_raw_all_assignment_after_match.csv", + "donor_match/test2/souporcell_vs_bff_raw/test2_souporcell_vs_bff_raw_concordance_heatmap.png", + "donor_match/test2/souporcell_vs_bff_raw/test2_souporcell_vs_bff_raw_correlation_res.csv", + "donor_match/test2/souporcell_vs_bff_raw/test2_souporcell_vs_bff_raw_donor_match.csv", + "donor_match/test2/souporcell_vs_bff_raw/test2_souporcell_vs_bff_raw_intersect_assignment_after_match.csv", + "donor_match/test2/souporcell_vs_gmmdemux", + "donor_match/test2/souporcell_vs_gmmdemux/test2_souporcell_vs_gmmdemux_all_assignment_after_match.csv", + "donor_match/test2/souporcell_vs_gmmdemux/test2_souporcell_vs_gmmdemux_concordance_heatmap.png", + "donor_match/test2/souporcell_vs_gmmdemux/test2_souporcell_vs_gmmdemux_correlation_res.csv", + "donor_match/test2/souporcell_vs_gmmdemux/test2_souporcell_vs_gmmdemux_donor_match.csv", + "donor_match/test2/souporcell_vs_gmmdemux/test2_souporcell_vs_gmmdemux_intersect_assignment_after_match.csv", "donor_match/test2/souporcell_vs_hasheddrops", "donor_match/test2/souporcell_vs_hasheddrops/test2_souporcell_vs_hasheddrops_all_assignment_after_match.csv", "donor_match/test2/souporcell_vs_hasheddrops/test2_souporcell_vs_hasheddrops_concordance_heatmap.png", "donor_match/test2/souporcell_vs_hasheddrops/test2_souporcell_vs_hasheddrops_correlation_res.csv", "donor_match/test2/souporcell_vs_hasheddrops/test2_souporcell_vs_hasheddrops_donor_match.csv", "donor_match/test2/souporcell_vs_hasheddrops/test2_souporcell_vs_hasheddrops_intersect_assignment_after_match.csv", - "donor_match/test2/souporcell_vs_hashsolo", - "donor_match/test2/souporcell_vs_hashsolo/test2_souporcell_vs_hashsolo_all_assignment_after_match.csv", - "donor_match/test2/souporcell_vs_hashsolo/test2_souporcell_vs_hashsolo_concordance_heatmap.png", - "donor_match/test2/souporcell_vs_hashsolo/test2_souporcell_vs_hashsolo_correlation_res.csv", - "donor_match/test2/souporcell_vs_hashsolo/test2_souporcell_vs_hashsolo_donor_match.csv", - "donor_match/test2/souporcell_vs_hashsolo/test2_souporcell_vs_hashsolo_intersect_assignment_after_match.csv", - "donor_match/test2/souporcell_vs_multiseq", - "donor_match/test2/souporcell_vs_multiseq/test2_souporcell_vs_multiseq_all_assignment_after_match.csv", - "donor_match/test2/souporcell_vs_multiseq/test2_souporcell_vs_multiseq_concordance_heatmap.png", - "donor_match/test2/souporcell_vs_multiseq/test2_souporcell_vs_multiseq_correlation_res.csv", - "donor_match/test2/souporcell_vs_multiseq/test2_souporcell_vs_multiseq_donor_match.csv", - "donor_match/test2/souporcell_vs_multiseq/test2_souporcell_vs_multiseq_intersect_assignment_after_match.csv", "donor_match/test2/test2_best_all_assignment_after_match.csv", "donor_match/test2/test2_best_donor_match.csv", "donor_match/test2/test2_best_intersect_assignment_after_match.csv", "donor_match/test2/test2_score_record.csv", "donor_match/test3", + "donor_match/test3/freemuxlet_vs_bff_consensuscall", + "donor_match/test3/freemuxlet_vs_bff_consensuscall/test3_freemuxlet_vs_bff_consensuscall_all_assignment_after_match.csv", + "donor_match/test3/freemuxlet_vs_bff_consensuscall/test3_freemuxlet_vs_bff_consensuscall_concordance_heatmap.png", + "donor_match/test3/freemuxlet_vs_bff_consensuscall/test3_freemuxlet_vs_bff_consensuscall_correlation_res.csv", + "donor_match/test3/freemuxlet_vs_bff_consensuscall/test3_freemuxlet_vs_bff_consensuscall_donor_match.csv", + "donor_match/test3/freemuxlet_vs_bff_consensuscall/test3_freemuxlet_vs_bff_consensuscall_intersect_assignment_after_match.csv", + "donor_match/test3/freemuxlet_vs_bff_raw", + "donor_match/test3/freemuxlet_vs_bff_raw/test3_freemuxlet_vs_bff_raw_all_assignment_after_match.csv", + "donor_match/test3/freemuxlet_vs_bff_raw/test3_freemuxlet_vs_bff_raw_concordance_heatmap.png", + "donor_match/test3/freemuxlet_vs_bff_raw/test3_freemuxlet_vs_bff_raw_correlation_res.csv", + "donor_match/test3/freemuxlet_vs_bff_raw/test3_freemuxlet_vs_bff_raw_donor_match.csv", + "donor_match/test3/freemuxlet_vs_bff_raw/test3_freemuxlet_vs_bff_raw_intersect_assignment_after_match.csv", + "donor_match/test3/freemuxlet_vs_gmmdemux", + "donor_match/test3/freemuxlet_vs_gmmdemux/test3_freemuxlet_vs_gmmdemux_all_assignment_after_match.csv", + "donor_match/test3/freemuxlet_vs_gmmdemux/test3_freemuxlet_vs_gmmdemux_concordance_heatmap.png", + "donor_match/test3/freemuxlet_vs_gmmdemux/test3_freemuxlet_vs_gmmdemux_correlation_res.csv", + "donor_match/test3/freemuxlet_vs_gmmdemux/test3_freemuxlet_vs_gmmdemux_donor_match.csv", + "donor_match/test3/freemuxlet_vs_gmmdemux/test3_freemuxlet_vs_gmmdemux_intersect_assignment_after_match.csv", "donor_match/test3/freemuxlet_vs_hasheddrops", "donor_match/test3/freemuxlet_vs_hasheddrops/test3_freemuxlet_vs_hasheddrops_all_assignment_after_match.csv", "donor_match/test3/freemuxlet_vs_hasheddrops/test3_freemuxlet_vs_hasheddrops_concordance_heatmap.png", "donor_match/test3/freemuxlet_vs_hasheddrops/test3_freemuxlet_vs_hasheddrops_correlation_res.csv", "donor_match/test3/freemuxlet_vs_hasheddrops/test3_freemuxlet_vs_hasheddrops_donor_match.csv", "donor_match/test3/freemuxlet_vs_hasheddrops/test3_freemuxlet_vs_hasheddrops_intersect_assignment_after_match.csv", - "donor_match/test3/freemuxlet_vs_hashsolo", - "donor_match/test3/freemuxlet_vs_hashsolo/test3_freemuxlet_vs_hashsolo_all_assignment_after_match.csv", - "donor_match/test3/freemuxlet_vs_hashsolo/test3_freemuxlet_vs_hashsolo_concordance_heatmap.png", - "donor_match/test3/freemuxlet_vs_hashsolo/test3_freemuxlet_vs_hashsolo_correlation_res.csv", - "donor_match/test3/freemuxlet_vs_hashsolo/test3_freemuxlet_vs_hashsolo_donor_match.csv", - "donor_match/test3/freemuxlet_vs_hashsolo/test3_freemuxlet_vs_hashsolo_intersect_assignment_after_match.csv", - "donor_match/test3/freemuxlet_vs_multiseq", - "donor_match/test3/freemuxlet_vs_multiseq/test3_freemuxlet_vs_multiseq_all_assignment_after_match.csv", - "donor_match/test3/freemuxlet_vs_multiseq/test3_freemuxlet_vs_multiseq_concordance_heatmap.png", - "donor_match/test3/freemuxlet_vs_multiseq/test3_freemuxlet_vs_multiseq_correlation_res.csv", - "donor_match/test3/freemuxlet_vs_multiseq/test3_freemuxlet_vs_multiseq_donor_match.csv", - "donor_match/test3/freemuxlet_vs_multiseq/test3_freemuxlet_vs_multiseq_intersect_assignment_after_match.csv", + "donor_match/test3/souporcell_vs_bff_consensuscall", + "donor_match/test3/souporcell_vs_bff_consensuscall/test3_souporcell_vs_bff_consensuscall_all_assignment_after_match.csv", + "donor_match/test3/souporcell_vs_bff_consensuscall/test3_souporcell_vs_bff_consensuscall_concordance_heatmap.png", + "donor_match/test3/souporcell_vs_bff_consensuscall/test3_souporcell_vs_bff_consensuscall_correlation_res.csv", + "donor_match/test3/souporcell_vs_bff_consensuscall/test3_souporcell_vs_bff_consensuscall_donor_match.csv", + "donor_match/test3/souporcell_vs_bff_consensuscall/test3_souporcell_vs_bff_consensuscall_intersect_assignment_after_match.csv", + "donor_match/test3/souporcell_vs_bff_raw", + "donor_match/test3/souporcell_vs_bff_raw/test3_souporcell_vs_bff_raw_all_assignment_after_match.csv", + "donor_match/test3/souporcell_vs_bff_raw/test3_souporcell_vs_bff_raw_concordance_heatmap.png", + "donor_match/test3/souporcell_vs_bff_raw/test3_souporcell_vs_bff_raw_correlation_res.csv", + "donor_match/test3/souporcell_vs_bff_raw/test3_souporcell_vs_bff_raw_donor_match.csv", + "donor_match/test3/souporcell_vs_bff_raw/test3_souporcell_vs_bff_raw_intersect_assignment_after_match.csv", + "donor_match/test3/souporcell_vs_gmmdemux", + "donor_match/test3/souporcell_vs_gmmdemux/test3_souporcell_vs_gmmdemux_all_assignment_after_match.csv", + "donor_match/test3/souporcell_vs_gmmdemux/test3_souporcell_vs_gmmdemux_concordance_heatmap.png", + "donor_match/test3/souporcell_vs_gmmdemux/test3_souporcell_vs_gmmdemux_correlation_res.csv", + "donor_match/test3/souporcell_vs_gmmdemux/test3_souporcell_vs_gmmdemux_donor_match.csv", + "donor_match/test3/souporcell_vs_gmmdemux/test3_souporcell_vs_gmmdemux_intersect_assignment_after_match.csv", "donor_match/test3/souporcell_vs_hasheddrops", "donor_match/test3/souporcell_vs_hasheddrops/test3_souporcell_vs_hasheddrops_all_assignment_after_match.csv", "donor_match/test3/souporcell_vs_hasheddrops/test3_souporcell_vs_hasheddrops_concordance_heatmap.png", "donor_match/test3/souporcell_vs_hasheddrops/test3_souporcell_vs_hasheddrops_correlation_res.csv", "donor_match/test3/souporcell_vs_hasheddrops/test3_souporcell_vs_hasheddrops_donor_match.csv", "donor_match/test3/souporcell_vs_hasheddrops/test3_souporcell_vs_hasheddrops_intersect_assignment_after_match.csv", - "donor_match/test3/souporcell_vs_hashsolo", - "donor_match/test3/souporcell_vs_hashsolo/test3_souporcell_vs_hashsolo_all_assignment_after_match.csv", - "donor_match/test3/souporcell_vs_hashsolo/test3_souporcell_vs_hashsolo_concordance_heatmap.png", - "donor_match/test3/souporcell_vs_hashsolo/test3_souporcell_vs_hashsolo_correlation_res.csv", - "donor_match/test3/souporcell_vs_hashsolo/test3_souporcell_vs_hashsolo_donor_match.csv", - "donor_match/test3/souporcell_vs_hashsolo/test3_souporcell_vs_hashsolo_intersect_assignment_after_match.csv", - "donor_match/test3/souporcell_vs_multiseq", - "donor_match/test3/souporcell_vs_multiseq/test3_souporcell_vs_multiseq_all_assignment_after_match.csv", - "donor_match/test3/souporcell_vs_multiseq/test3_souporcell_vs_multiseq_concordance_heatmap.png", - "donor_match/test3/souporcell_vs_multiseq/test3_souporcell_vs_multiseq_correlation_res.csv", - "donor_match/test3/souporcell_vs_multiseq/test3_souporcell_vs_multiseq_donor_match.csv", - "donor_match/test3/souporcell_vs_multiseq/test3_souporcell_vs_multiseq_intersect_assignment_after_match.csv", "donor_match/test3/test3_best_all_assignment_after_match.csv", "donor_match/test3/test3_best_donor_match.csv", "donor_match/test3/test3_best_intersect_assignment_after_match.csv", "donor_match/test3/test3_score_record.csv", - "extract", - "extract/test1_hashes.txt", - "extract/test2_hashes.txt", - "extract/test3_hashes.txt", + "find_variants", + "find_variants/test1", + "find_variants/test1/hto-1", + "find_variants/test1/hto-1/test1_hto-1_informative_variants.csv", + "find_variants/test1/hto-1/test1_hto-1_matched_gt.csv", + "find_variants/test1/hto-1/test1_hto-1_unmatched_gt.csv", + "find_variants/test1/hto-2", + "find_variants/test1/hto-2/test1_hto-2_informative_variants.csv", + "find_variants/test1/hto-2/test1_hto-2_matched_gt.csv", + "find_variants/test1/hto-2/test1_hto-2_unmatched_gt.csv", + "find_variants/test1/subset_gt_donors", + "find_variants/test1/subset_gt_donors/test1_donor_specific.vcf.gz", + "find_variants/test1/subset_gt_donors/test1_vireo.vcf.gz", + "find_variants/test1/test1_all_representative_variants.csv", + "find_variants/test1/test1_donor_specific_variants.csv", + "find_variants/test1/test1_donor_specific_variants_upset.png", + "find_variants/test1/test1_vireo_variants.csv", + "find_variants/test2", + "find_variants/test2/hto-1", + "find_variants/test2/hto-1/test2_hto-1_informative_variants.csv", + "find_variants/test2/hto-1/test2_hto-1_matched_gt.csv", + "find_variants/test2/hto-1/test2_hto-1_unmatched_gt.csv", + "find_variants/test2/hto-2", + "find_variants/test2/hto-2/test2_hto-2_informative_variants.csv", + "find_variants/test2/hto-2/test2_hto-2_matched_gt.csv", + "find_variants/test2/hto-2/test2_hto-2_unmatched_gt.csv", + "find_variants/test2/subset_gt_donors", + "find_variants/test2/subset_gt_donors/test2_donor_specific.vcf.gz", + "find_variants/test2/subset_gt_donors/test2_vireo.vcf.gz", + "find_variants/test2/test2_all_representative_variants.csv", + "find_variants/test2/test2_donor_specific_variants.csv", + "find_variants/test2/test2_donor_specific_variants_upset.png", + "find_variants/test2/test2_vireo_variants.csv", + "find_variants/test3", + "find_variants/test3/hto-1", + "find_variants/test3/hto-1/test3_hto-1_informative_variants.csv", + "find_variants/test3/hto-1/test3_hto-1_matched_gt.csv", + "find_variants/test3/hto-1/test3_hto-1_unmatched_gt.csv", + "find_variants/test3/hto-2", + "find_variants/test3/hto-2/test3_hto-2_informative_variants.csv", + "find_variants/test3/hto-2/test3_hto-2_matched_gt.csv", + "find_variants/test3/hto-2/test3_hto-2_unmatched_gt.csv", + "find_variants/test3/subset_gt_donors", + "find_variants/test3/subset_gt_donors/test3_donor_specific.vcf.gz", + "find_variants/test3/subset_gt_donors/test3_vireo.vcf.gz", + "find_variants/test3/test3_all_representative_variants.csv", + "find_variants/test3/test3_donor_specific_variants.csv", + "find_variants/test3/test3_donor_specific_variants_upset.png", + "find_variants/test3/test3_vireo_variants.csv", "genetic", "genetic/popscle", "genetic/popscle/freemuxlet", @@ -247,18 +330,18 @@ "genetic/souporcell/test3/test3/clusters.tsv", "genetic/summary", "genetic/summary/test1", - "genetic/summary/test1/test1_genetic_summary.h5ad", - "genetic/summary/test1/test1_genetic_summary.h5mu", + "genetic/summary/test1/test1_genetic_overview_assignment.csv", + "genetic/summary/test1/test1_genetic_overview_classification.csv", "genetic/summary/test1/test1_genetic_summary_assignment.csv", "genetic/summary/test1/test1_genetic_summary_classification.csv", "genetic/summary/test2", - "genetic/summary/test2/test2_genetic_summary.h5ad", - "genetic/summary/test2/test2_genetic_summary.h5mu", + "genetic/summary/test2/test2_genetic_overview_assignment.csv", + "genetic/summary/test2/test2_genetic_overview_classification.csv", "genetic/summary/test2/test2_genetic_summary_assignment.csv", "genetic/summary/test2/test2_genetic_summary_classification.csv", "genetic/summary/test3", - "genetic/summary/test3/test3_genetic_summary.h5ad", - "genetic/summary/test3/test3_genetic_summary.h5mu", + "genetic/summary/test3/test3_genetic_overview_assignment.csv", + "genetic/summary/test3/test3_genetic_overview_classification.csv", "genetic/summary/test3/test3_genetic_summary_assignment.csv", "genetic/summary/test3/test3_genetic_summary_classification.csv", "genetic/vireo", @@ -269,7 +352,9 @@ "genetic/vireo/test1/test1.tag.AD.mtx", "genetic/vireo/test1/test1.tag.DP.mtx", "genetic/vireo/test1/test1.tag.OTH.mtx", + "genetic/vireo/test1/test1_GT_donors.vireo.vcf.gz", "genetic/vireo/test1/test1_donor_ids.tsv", + "genetic/vireo/test1/test1_filtered_variants.tsv", "genetic/vireo/test1/test1_prob_doublet.tsv.gz", "genetic/vireo/test1/test1_prob_singlet.tsv.gz", "genetic/vireo/test1/test1_summary.tsv", @@ -280,7 +365,9 @@ "genetic/vireo/test2/test2.tag.AD.mtx", "genetic/vireo/test2/test2.tag.DP.mtx", "genetic/vireo/test2/test2.tag.OTH.mtx", + "genetic/vireo/test2/test2_GT_donors.vireo.vcf.gz", "genetic/vireo/test2/test2_donor_ids.tsv", + "genetic/vireo/test2/test2_filtered_variants.tsv", "genetic/vireo/test2/test2_prob_doublet.tsv.gz", "genetic/vireo/test2/test2_prob_singlet.tsv.gz", "genetic/vireo/test2/test2_summary.tsv", @@ -291,18 +378,45 @@ "genetic/vireo/test3/test3.tag.AD.mtx", "genetic/vireo/test3/test3.tag.DP.mtx", "genetic/vireo/test3/test3.tag.OTH.mtx", + "genetic/vireo/test3/test3_GT_donors.vireo.vcf.gz", "genetic/vireo/test3/test3_donor_ids.tsv", + "genetic/vireo/test3/test3_filtered_variants.tsv", "genetic/vireo/test3/test3_prob_doublet.tsv.gz", "genetic/vireo/test3/test3_prob_singlet.tsv.gz", "genetic/vireo/test3/test3_summary.tsv", - "hash_and_gene_summary", - "hash_and_gene_summary/test1", - "hash_and_gene_summary/test1/test1.csv", - "hash_and_gene_summary/test2", - "hash_and_gene_summary/test2/test2.csv", - "hash_and_gene_summary/test3", - "hash_and_gene_summary/test3/test3.csv", "hashing", + "hashing/bff", + "hashing/bff/test1", + "hashing/bff/test1/test1_assignment_bff.csv", + "hashing/bff/test1/test1_metrics_bff.csv", + "hashing/bff/test1/test1_params_bff.csv", + "hashing/bff/test2", + "hashing/bff/test2/test2_assignment_bff.csv", + "hashing/bff/test2/test2_metrics_bff.csv", + "hashing/bff/test2/test2_params_bff.csv", + "hashing/bff/test3", + "hashing/bff/test3/test3_assignment_bff.csv", + "hashing/bff/test3/test3_metrics_bff.csv", + "hashing/bff/test3/test3_params_bff.csv", + "hashing/gmm-demux", + "hashing/gmm-demux/test1", + "hashing/gmm-demux/test1/GMM_full.config", + "hashing/gmm-demux/test1/GMM_full.csv", + "hashing/gmm-demux/test1/barcodes.tsv.gz", + "hashing/gmm-demux/test1/features.tsv.gz", + "hashing/gmm-demux/test1/matrix.mtx.gz", + "hashing/gmm-demux/test2", + "hashing/gmm-demux/test2/GMM_full.config", + "hashing/gmm-demux/test2/GMM_full.csv", + "hashing/gmm-demux/test2/barcodes.tsv.gz", + "hashing/gmm-demux/test2/features.tsv.gz", + "hashing/gmm-demux/test2/matrix.mtx.gz", + "hashing/gmm-demux/test3", + "hashing/gmm-demux/test3/GMM_full.config", + "hashing/gmm-demux/test3/GMM_full.csv", + "hashing/gmm-demux/test3/barcodes.tsv.gz", + "hashing/gmm-demux/test3/features.tsv.gz", + "hashing/gmm-demux/test3/matrix.mtx.gz", "hashing/hasheddrops", "hashing/hasheddrops/test1", "hashing/hasheddrops/test1/test1_emptyDrops.csv", @@ -331,56 +445,20 @@ "hashing/hasheddrops/test3/test3_params_hasheddrops.csv", "hashing/hasheddrops/test3/test3_plot_hasheddrops.png", "hashing/hasheddrops/test3/test3_results_hasheddrops.csv", - "hashing/hashsolo", - "hashing/hashsolo/test1", - "hashing/hashsolo/test1/test1_assignment_hashsolo.csv", - "hashing/hashsolo/test1/test1_hashsolo.h5ad", - "hashing/hashsolo/test1/test1_params_hashsolo.csv", - "hashing/hashsolo/test2", - "hashing/hashsolo/test2/test2_assignment_hashsolo.csv", - "hashing/hashsolo/test2/test2_hashsolo.h5ad", - "hashing/hashsolo/test2/test2_params_hashsolo.csv", - "hashing/hashsolo/test3", - "hashing/hashsolo/test3/test3_assignment_hashsolo.csv", - "hashing/hashsolo/test3/test3_hashsolo.h5ad", - "hashing/hashsolo/test3/test3_params_hashsolo.csv", - "hashing/multiseqdemux", - "hashing/multiseqdemux/test1", - "hashing/multiseqdemux/test1/test1_multiseqdemux.rds", - "hashing/multiseqdemux/test1/test1_params_multiseqdemux.csv", - "hashing/multiseqdemux/test1/test1_res_multiseqdemux.csv", - "hashing/multiseqdemux/test2", - "hashing/multiseqdemux/test2/test2_multiseqdemux.rds", - "hashing/multiseqdemux/test2/test2_params_multiseqdemux.csv", - "hashing/multiseqdemux/test2/test2_res_multiseqdemux.csv", - "hashing/multiseqdemux/test3", - "hashing/multiseqdemux/test3/test3_multiseqdemux.rds", - "hashing/multiseqdemux/test3/test3_params_multiseqdemux.csv", - "hashing/multiseqdemux/test3/test3_res_multiseqdemux.csv", - "hashing/preprocessing", - "hashing/preprocessing/test1", - "hashing/preprocessing/test1/test1_params_preprocessing.csv", - "hashing/preprocessing/test1/test1_preprocessed.rds", - "hashing/preprocessing/test2", - "hashing/preprocessing/test2/test2_params_preprocessing.csv", - "hashing/preprocessing/test2/test2_preprocessed.rds", - "hashing/preprocessing/test3", - "hashing/preprocessing/test3/test3_params_preprocessing.csv", - "hashing/preprocessing/test3/test3_preprocessed.rds", "hashing/summary", "hashing/summary/test1", - "hashing/summary/test1/test1_hashing_summary.h5ad", - "hashing/summary/test1/test1_hashing_summary.h5mu", + "hashing/summary/test1/test1_hashing_overview_assignment.csv", + "hashing/summary/test1/test1_hashing_overview_classification.csv", "hashing/summary/test1/test1_hashing_summary_assignment.csv", "hashing/summary/test1/test1_hashing_summary_classification.csv", "hashing/summary/test2", - "hashing/summary/test2/test2_hashing_summary.h5ad", - "hashing/summary/test2/test2_hashing_summary.h5mu", + "hashing/summary/test2/test2_hashing_overview_assignment.csv", + "hashing/summary/test2/test2_hashing_overview_classification.csv", "hashing/summary/test2/test2_hashing_summary_assignment.csv", "hashing/summary/test2/test2_hashing_summary_classification.csv", "hashing/summary/test3", - "hashing/summary/test3/test3_hashing_summary.h5ad", - "hashing/summary/test3/test3_hashing_summary.h5mu", + "hashing/summary/test3/test3_hashing_overview_assignment.csv", + "hashing/summary/test3/test3_hashing_overview_classification.csv", "hashing/summary/test3/test3_hashing_summary_assignment.csv", "hashing/summary/test3/test3_hashing_summary_classification.csv", "multiqc", @@ -395,138 +473,189 @@ "multiqc/multiqc_report.html", "pipeline_info", "pipeline_info/nf_core_hadge_software_mqc_versions.yml", - "untar", - "untar/test1_hto", - "untar/test1_hto/barcodes.tsv.gz", - "untar/test1_hto/features.tsv.gz", - "untar/test1_hto/matrix.mtx.gz", - "untar/test1_rna", - "untar/test1_rna/barcodes.tsv.gz", - "untar/test1_rna/features.tsv.gz", - "untar/test1_rna/matrix.mtx.gz", - "untar/test2_hto", - "untar/test2_hto/barcodes.tsv.gz", - "untar/test2_hto/features.tsv.gz", - "untar/test2_hto/matrix.mtx.gz", - "untar/test2_rna", - "untar/test2_rna/barcodes.tsv.gz", - "untar/test2_rna/features.tsv.gz", - "untar/test2_rna/matrix.mtx.gz", - "untar/test3_hto", - "untar/test3_hto/barcodes.tsv.gz", - "untar/test3_hto/features.tsv.gz", - "untar/test3_hto/matrix.mtx.gz", - "untar/test3_rna", - "untar/test3_rna/barcodes.tsv.gz", - "untar/test3_rna/features.tsv.gz", - "untar/test3_rna/matrix.mtx.gz" + "summary", + "summary/test1", + "summary/test1/test1_assignment.csv", + "summary/test1/test1_classification.csv", + "summary/test1/test1_genetic.h5ad", + "summary/test1/test1_genetic_and_hashing.h5mu", + "summary/test1/test1_hashing.h5ad", + "summary/test2", + "summary/test2/test2_assignment.csv", + "summary/test2/test2_classification.csv", + "summary/test2/test2_genetic.h5ad", + "summary/test2/test2_genetic_and_hashing.h5mu", + "summary/test2/test2_hashing.h5ad", + "summary/test3", + "summary/test3/test3_assignment.csv", + "summary/test3/test3_classification.csv", + "summary/test3/test3_genetic.h5ad", + "summary/test3/test3_genetic_and_hashing.h5mu", + "summary/test3/test3_hashing.h5ad" ], [ - "test1_freemuxlet_vs_hasheddrops_all_assignment_after_match.csv:md5,05e486189b9c3bbfa381f17191ba5b9d", - "test1_freemuxlet_vs_hasheddrops_concordance_heatmap.png:md5,666ff7d8b8bb12d055ea6fdb081cb714", - "test1_freemuxlet_vs_hasheddrops_correlation_res.csv:md5,b0915774342ca64fe9077505a0b98a5f", - "test1_freemuxlet_vs_hasheddrops_donor_match.csv:md5,2d6da63e3801454389610977089c027f", - "test1_freemuxlet_vs_hasheddrops_intersect_assignment_after_match.csv:md5,c9ee6eed6505ae762f80e15e02f3a0c7", - "test1_freemuxlet_vs_hashsolo_all_assignment_after_match.csv:md5,90d108f1f5686f0c1a0f1d63ee84996c", - "test1_freemuxlet_vs_hashsolo_concordance_heatmap.png:md5,57a72e1a6a34fb333be8cf8d343c8b21", - "test1_freemuxlet_vs_hashsolo_correlation_res.csv:md5,07726a0bdac892ee79bc40b71780c187", - "test1_freemuxlet_vs_hashsolo_donor_match.csv:md5,2d6da63e3801454389610977089c027f", - "test1_freemuxlet_vs_hashsolo_intersect_assignment_after_match.csv:md5,9b0c416e27572acc4c8ba6b97480ca86", - "test1_freemuxlet_vs_multiseq_all_assignment_after_match.csv:md5,ff848ce7685a658315ad2af8f2d39c4b", - "test1_freemuxlet_vs_multiseq_concordance_heatmap.png:md5,64bc44fb9aeeb1581ebbf708c8765b4d", - "test1_freemuxlet_vs_multiseq_correlation_res.csv:md5,8a690fde26636e0e4c44b41a156ae47a", - "test1_freemuxlet_vs_multiseq_donor_match.csv:md5,b92352374bf4b32f7e3a1b032a4c8b5f", - "test1_freemuxlet_vs_multiseq_intersect_assignment_after_match.csv:md5,7a178c3e2f08aa338d4bb6e9f1e2cb44", - "test1_souporcell_vs_hasheddrops_all_assignment_after_match.csv:md5,a705ae7739121396c7c92999abbddd39", - "test1_souporcell_vs_hasheddrops_concordance_heatmap.png:md5,53fcd062b4a8e63cebb1af6e41a59336", - "test1_souporcell_vs_hasheddrops_correlation_res.csv:md5,bfb61a5e0261fdfd1c8c46f3f3dbbe6e", - "test1_souporcell_vs_hasheddrops_donor_match.csv:md5,102fd8c326c1bf1a7ba7b0bd11ea3838", - "test1_souporcell_vs_hasheddrops_intersect_assignment_after_match.csv:md5,c84bd46c4b3c12012144c73398165b55", - "test1_souporcell_vs_hashsolo_all_assignment_after_match.csv:md5,39234357d62c2c519beb4691338f2797", - "test1_souporcell_vs_hashsolo_concordance_heatmap.png:md5,91d93741a44e57515defad0cd4837cca", - "test1_souporcell_vs_hashsolo_correlation_res.csv:md5,c2292c9cc36e62449cdd3fdf6dfb1a1f", - "test1_souporcell_vs_hashsolo_donor_match.csv:md5,102fd8c326c1bf1a7ba7b0bd11ea3838", - "test1_souporcell_vs_hashsolo_intersect_assignment_after_match.csv:md5,21ed58633a51cce2e6d8e24bbf122579", - "test1_souporcell_vs_multiseq_all_assignment_after_match.csv:md5,a409afeefe73dd91eddd3f6abbde2606", - "test1_souporcell_vs_multiseq_concordance_heatmap.png:md5,987ae361340b8b77daecc726ffa6b9de", - "test1_souporcell_vs_multiseq_correlation_res.csv:md5,a5f9d7ebd675403b062cd0f4a59b1592", - "test1_souporcell_vs_multiseq_donor_match.csv:md5,d1feea9baad73b5bc106472a226cbbf2", - "test1_souporcell_vs_multiseq_intersect_assignment_after_match.csv:md5,327f460700c49fe1f7d3447db2f0a2ea", - "test1_best_all_assignment_after_match.csv:md5,a705ae7739121396c7c92999abbddd39", - "test1_best_donor_match.csv:md5,102fd8c326c1bf1a7ba7b0bd11ea3838", - "test1_best_intersect_assignment_after_match.csv:md5,c84bd46c4b3c12012144c73398165b55", - "test1_score_record.csv:md5,92516e666292a795953beac514fe0cb1", - "test2_freemuxlet_vs_hasheddrops_all_assignment_after_match.csv:md5,05e486189b9c3bbfa381f17191ba5b9d", - "test2_freemuxlet_vs_hasheddrops_concordance_heatmap.png:md5,666ff7d8b8bb12d055ea6fdb081cb714", - "test2_freemuxlet_vs_hasheddrops_correlation_res.csv:md5,b0915774342ca64fe9077505a0b98a5f", - "test2_freemuxlet_vs_hasheddrops_donor_match.csv:md5,2d6da63e3801454389610977089c027f", - "test2_freemuxlet_vs_hasheddrops_intersect_assignment_after_match.csv:md5,c9ee6eed6505ae762f80e15e02f3a0c7", - "test2_freemuxlet_vs_hashsolo_all_assignment_after_match.csv:md5,90d108f1f5686f0c1a0f1d63ee84996c", - "test2_freemuxlet_vs_hashsolo_concordance_heatmap.png:md5,57a72e1a6a34fb333be8cf8d343c8b21", - "test2_freemuxlet_vs_hashsolo_correlation_res.csv:md5,07726a0bdac892ee79bc40b71780c187", - "test2_freemuxlet_vs_hashsolo_donor_match.csv:md5,2d6da63e3801454389610977089c027f", - "test2_freemuxlet_vs_hashsolo_intersect_assignment_after_match.csv:md5,9b0c416e27572acc4c8ba6b97480ca86", - "test2_freemuxlet_vs_multiseq_all_assignment_after_match.csv:md5,ff848ce7685a658315ad2af8f2d39c4b", - "test2_freemuxlet_vs_multiseq_concordance_heatmap.png:md5,64bc44fb9aeeb1581ebbf708c8765b4d", - "test2_freemuxlet_vs_multiseq_correlation_res.csv:md5,8a690fde26636e0e4c44b41a156ae47a", - "test2_freemuxlet_vs_multiseq_donor_match.csv:md5,b92352374bf4b32f7e3a1b032a4c8b5f", - "test2_freemuxlet_vs_multiseq_intersect_assignment_after_match.csv:md5,7a178c3e2f08aa338d4bb6e9f1e2cb44", - "test2_souporcell_vs_hasheddrops_all_assignment_after_match.csv:md5,a705ae7739121396c7c92999abbddd39", - "test2_souporcell_vs_hasheddrops_concordance_heatmap.png:md5,53fcd062b4a8e63cebb1af6e41a59336", - "test2_souporcell_vs_hasheddrops_correlation_res.csv:md5,bfb61a5e0261fdfd1c8c46f3f3dbbe6e", - "test2_souporcell_vs_hasheddrops_donor_match.csv:md5,102fd8c326c1bf1a7ba7b0bd11ea3838", - "test2_souporcell_vs_hasheddrops_intersect_assignment_after_match.csv:md5,c84bd46c4b3c12012144c73398165b55", - "test2_souporcell_vs_hashsolo_all_assignment_after_match.csv:md5,39234357d62c2c519beb4691338f2797", - "test2_souporcell_vs_hashsolo_concordance_heatmap.png:md5,91d93741a44e57515defad0cd4837cca", - "test2_souporcell_vs_hashsolo_correlation_res.csv:md5,c2292c9cc36e62449cdd3fdf6dfb1a1f", - "test2_souporcell_vs_hashsolo_donor_match.csv:md5,102fd8c326c1bf1a7ba7b0bd11ea3838", - "test2_souporcell_vs_hashsolo_intersect_assignment_after_match.csv:md5,21ed58633a51cce2e6d8e24bbf122579", - "test2_souporcell_vs_multiseq_all_assignment_after_match.csv:md5,a409afeefe73dd91eddd3f6abbde2606", - "test2_souporcell_vs_multiseq_concordance_heatmap.png:md5,987ae361340b8b77daecc726ffa6b9de", - "test2_souporcell_vs_multiseq_correlation_res.csv:md5,a5f9d7ebd675403b062cd0f4a59b1592", - "test2_souporcell_vs_multiseq_donor_match.csv:md5,d1feea9baad73b5bc106472a226cbbf2", - "test2_souporcell_vs_multiseq_intersect_assignment_after_match.csv:md5,327f460700c49fe1f7d3447db2f0a2ea", - "test2_best_all_assignment_after_match.csv:md5,a705ae7739121396c7c92999abbddd39", - "test2_best_donor_match.csv:md5,102fd8c326c1bf1a7ba7b0bd11ea3838", - "test2_best_intersect_assignment_after_match.csv:md5,c84bd46c4b3c12012144c73398165b55", - "test2_score_record.csv:md5,92516e666292a795953beac514fe0cb1", - "test3_freemuxlet_vs_hasheddrops_all_assignment_after_match.csv:md5,05e486189b9c3bbfa381f17191ba5b9d", - "test3_freemuxlet_vs_hasheddrops_concordance_heatmap.png:md5,666ff7d8b8bb12d055ea6fdb081cb714", - "test3_freemuxlet_vs_hasheddrops_correlation_res.csv:md5,b0915774342ca64fe9077505a0b98a5f", - "test3_freemuxlet_vs_hasheddrops_donor_match.csv:md5,2d6da63e3801454389610977089c027f", - "test3_freemuxlet_vs_hasheddrops_intersect_assignment_after_match.csv:md5,c9ee6eed6505ae762f80e15e02f3a0c7", - "test3_freemuxlet_vs_hashsolo_all_assignment_after_match.csv:md5,90d108f1f5686f0c1a0f1d63ee84996c", - "test3_freemuxlet_vs_hashsolo_concordance_heatmap.png:md5,57a72e1a6a34fb333be8cf8d343c8b21", - "test3_freemuxlet_vs_hashsolo_correlation_res.csv:md5,07726a0bdac892ee79bc40b71780c187", - "test3_freemuxlet_vs_hashsolo_donor_match.csv:md5,2d6da63e3801454389610977089c027f", - "test3_freemuxlet_vs_hashsolo_intersect_assignment_after_match.csv:md5,9b0c416e27572acc4c8ba6b97480ca86", - "test3_freemuxlet_vs_multiseq_all_assignment_after_match.csv:md5,ff848ce7685a658315ad2af8f2d39c4b", - "test3_freemuxlet_vs_multiseq_concordance_heatmap.png:md5,64bc44fb9aeeb1581ebbf708c8765b4d", - "test3_freemuxlet_vs_multiseq_correlation_res.csv:md5,8a690fde26636e0e4c44b41a156ae47a", - "test3_freemuxlet_vs_multiseq_donor_match.csv:md5,b92352374bf4b32f7e3a1b032a4c8b5f", - "test3_freemuxlet_vs_multiseq_intersect_assignment_after_match.csv:md5,7a178c3e2f08aa338d4bb6e9f1e2cb44", - "test3_souporcell_vs_hasheddrops_all_assignment_after_match.csv:md5,a705ae7739121396c7c92999abbddd39", - "test3_souporcell_vs_hasheddrops_concordance_heatmap.png:md5,53fcd062b4a8e63cebb1af6e41a59336", - "test3_souporcell_vs_hasheddrops_correlation_res.csv:md5,bfb61a5e0261fdfd1c8c46f3f3dbbe6e", - "test3_souporcell_vs_hasheddrops_donor_match.csv:md5,102fd8c326c1bf1a7ba7b0bd11ea3838", - "test3_souporcell_vs_hasheddrops_intersect_assignment_after_match.csv:md5,c84bd46c4b3c12012144c73398165b55", - "test3_souporcell_vs_hashsolo_all_assignment_after_match.csv:md5,39234357d62c2c519beb4691338f2797", - "test3_souporcell_vs_hashsolo_concordance_heatmap.png:md5,91d93741a44e57515defad0cd4837cca", - "test3_souporcell_vs_hashsolo_correlation_res.csv:md5,c2292c9cc36e62449cdd3fdf6dfb1a1f", - "test3_souporcell_vs_hashsolo_donor_match.csv:md5,102fd8c326c1bf1a7ba7b0bd11ea3838", - "test3_souporcell_vs_hashsolo_intersect_assignment_after_match.csv:md5,21ed58633a51cce2e6d8e24bbf122579", - "test3_souporcell_vs_multiseq_all_assignment_after_match.csv:md5,a409afeefe73dd91eddd3f6abbde2606", - "test3_souporcell_vs_multiseq_concordance_heatmap.png:md5,987ae361340b8b77daecc726ffa6b9de", - "test3_souporcell_vs_multiseq_correlation_res.csv:md5,a5f9d7ebd675403b062cd0f4a59b1592", - "test3_souporcell_vs_multiseq_donor_match.csv:md5,d1feea9baad73b5bc106472a226cbbf2", - "test3_souporcell_vs_multiseq_intersect_assignment_after_match.csv:md5,327f460700c49fe1f7d3447db2f0a2ea", - "test3_best_all_assignment_after_match.csv:md5,a705ae7739121396c7c92999abbddd39", - "test3_best_donor_match.csv:md5,102fd8c326c1bf1a7ba7b0bd11ea3838", - "test3_best_intersect_assignment_after_match.csv:md5,c84bd46c4b3c12012144c73398165b55", - "test3_score_record.csv:md5,92516e666292a795953beac514fe0cb1", - "test1_hashes.txt:md5,56390704cb4a0f31ca5a2d9293d673c0", - "test2_hashes.txt:md5,56390704cb4a0f31ca5a2d9293d673c0", - "test3_hashes.txt:md5,56390704cb4a0f31ca5a2d9293d673c0", + "test1_freemuxlet_vs_bff_consensuscall_all_assignment_after_match.csv:md5,e6c7237f7a927ab86368d9ecf28b807c", + "test1_freemuxlet_vs_bff_consensuscall_concordance_heatmap.png:md5,5c3e82573df24e781d4f422bc3b6925a", + "test1_freemuxlet_vs_bff_consensuscall_correlation_res.csv:md5,5f2f2076175e313f10396b32fac4c26c", + "test1_freemuxlet_vs_bff_consensuscall_donor_match.csv:md5,b92352374bf4b32f7e3a1b032a4c8b5f", + "test1_freemuxlet_vs_bff_consensuscall_intersect_assignment_after_match.csv:md5,3f611c4fb970117f9771ce05f4ec4d8d", + "test1_freemuxlet_vs_bff_raw_all_assignment_after_match.csv:md5,54406a297be20405919bd15348111ad8", + "test1_freemuxlet_vs_bff_raw_concordance_heatmap.png:md5,5c3e82573df24e781d4f422bc3b6925a", + "test1_freemuxlet_vs_bff_raw_correlation_res.csv:md5,5f2f2076175e313f10396b32fac4c26c", + "test1_freemuxlet_vs_bff_raw_donor_match.csv:md5,b92352374bf4b32f7e3a1b032a4c8b5f", + "test1_freemuxlet_vs_bff_raw_intersect_assignment_after_match.csv:md5,87b3ca9e977fe3df3536ba169566abed", + "test1_freemuxlet_vs_gmmdemux_all_assignment_after_match.csv:md5,e4ec8cd25409bdb7de568dcfd8ca3ad4", + "test1_freemuxlet_vs_gmmdemux_concordance_heatmap.png:md5,5c3e82573df24e781d4f422bc3b6925a", + "test1_freemuxlet_vs_gmmdemux_correlation_res.csv:md5,5f2f2076175e313f10396b32fac4c26c", + "test1_freemuxlet_vs_gmmdemux_donor_match.csv:md5,b92352374bf4b32f7e3a1b032a4c8b5f", + "test1_freemuxlet_vs_gmmdemux_intersect_assignment_after_match.csv:md5,f9092a34b0a2eef31daf452d89412f58", + "test1_freemuxlet_vs_hasheddrops_all_assignment_after_match.csv:md5,d777c2ffc8201579aa80123d42ca4c7f", + "test1_freemuxlet_vs_hasheddrops_concordance_heatmap.png:md5,5c3e82573df24e781d4f422bc3b6925a", + "test1_freemuxlet_vs_hasheddrops_correlation_res.csv:md5,5f2f2076175e313f10396b32fac4c26c", + "test1_freemuxlet_vs_hasheddrops_donor_match.csv:md5,b92352374bf4b32f7e3a1b032a4c8b5f", + "test1_freemuxlet_vs_hasheddrops_intersect_assignment_after_match.csv:md5,b3acbe48f60fc1d68dd8198950549812", + "test1_souporcell_vs_bff_consensuscall_all_assignment_after_match.csv:md5,4c61d15e4693b6bc03cd296e757b6498", + "test1_souporcell_vs_bff_consensuscall_concordance_heatmap.png:md5,d48df9ebb22dc1f2616511ad0405cdfd", + "test1_souporcell_vs_bff_consensuscall_correlation_res.csv:md5,7e632ed518a1e6f02ed1ea25a28a4e45", + "test1_souporcell_vs_bff_consensuscall_donor_match.csv:md5,d1feea9baad73b5bc106472a226cbbf2", + "test1_souporcell_vs_bff_consensuscall_intersect_assignment_after_match.csv:md5,17fe8deedeb704f60632733c9ec971ea", + "test1_souporcell_vs_bff_raw_all_assignment_after_match.csv:md5,f8e44703900103c804694041924df32a", + "test1_souporcell_vs_bff_raw_concordance_heatmap.png:md5,d48df9ebb22dc1f2616511ad0405cdfd", + "test1_souporcell_vs_bff_raw_correlation_res.csv:md5,7e632ed518a1e6f02ed1ea25a28a4e45", + "test1_souporcell_vs_bff_raw_donor_match.csv:md5,d1feea9baad73b5bc106472a226cbbf2", + "test1_souporcell_vs_bff_raw_intersect_assignment_after_match.csv:md5,cc89c4263e95c7802152d5a84e5fda5a", + "test1_souporcell_vs_gmmdemux_all_assignment_after_match.csv:md5,5a9927b30847c3c3ff82f0cc5274076f", + "test1_souporcell_vs_gmmdemux_concordance_heatmap.png:md5,d48df9ebb22dc1f2616511ad0405cdfd", + "test1_souporcell_vs_gmmdemux_correlation_res.csv:md5,7e632ed518a1e6f02ed1ea25a28a4e45", + "test1_souporcell_vs_gmmdemux_donor_match.csv:md5,d1feea9baad73b5bc106472a226cbbf2", + "test1_souporcell_vs_gmmdemux_intersect_assignment_after_match.csv:md5,d9cc57a14177d9c362b91dc923c123c8", + "test1_souporcell_vs_hasheddrops_all_assignment_after_match.csv:md5,8e5f886ef04c7d224861348ae470adc5", + "test1_souporcell_vs_hasheddrops_concordance_heatmap.png:md5,d48df9ebb22dc1f2616511ad0405cdfd", + "test1_souporcell_vs_hasheddrops_correlation_res.csv:md5,7e632ed518a1e6f02ed1ea25a28a4e45", + "test1_souporcell_vs_hasheddrops_donor_match.csv:md5,d1feea9baad73b5bc106472a226cbbf2", + "test1_souporcell_vs_hasheddrops_intersect_assignment_after_match.csv:md5,4d4e53deb9c6a9dd9ae9d6d89493d736", + "test1_best_all_assignment_after_match.csv:md5,4c61d15e4693b6bc03cd296e757b6498", + "test1_best_donor_match.csv:md5,d1feea9baad73b5bc106472a226cbbf2", + "test1_best_intersect_assignment_after_match.csv:md5,17fe8deedeb704f60632733c9ec971ea", + "test1_score_record.csv:md5,19e6d7aa257ab96338c70bb1e69442b3", + "test2_freemuxlet_vs_bff_consensuscall_all_assignment_after_match.csv:md5,e6c7237f7a927ab86368d9ecf28b807c", + "test2_freemuxlet_vs_bff_consensuscall_concordance_heatmap.png:md5,5c3e82573df24e781d4f422bc3b6925a", + "test2_freemuxlet_vs_bff_consensuscall_correlation_res.csv:md5,5f2f2076175e313f10396b32fac4c26c", + "test2_freemuxlet_vs_bff_consensuscall_donor_match.csv:md5,b92352374bf4b32f7e3a1b032a4c8b5f", + "test2_freemuxlet_vs_bff_consensuscall_intersect_assignment_after_match.csv:md5,3f611c4fb970117f9771ce05f4ec4d8d", + "test2_freemuxlet_vs_bff_raw_all_assignment_after_match.csv:md5,54406a297be20405919bd15348111ad8", + "test2_freemuxlet_vs_bff_raw_concordance_heatmap.png:md5,5c3e82573df24e781d4f422bc3b6925a", + "test2_freemuxlet_vs_bff_raw_correlation_res.csv:md5,5f2f2076175e313f10396b32fac4c26c", + "test2_freemuxlet_vs_bff_raw_donor_match.csv:md5,b92352374bf4b32f7e3a1b032a4c8b5f", + "test2_freemuxlet_vs_bff_raw_intersect_assignment_after_match.csv:md5,87b3ca9e977fe3df3536ba169566abed", + "test2_freemuxlet_vs_gmmdemux_all_assignment_after_match.csv:md5,e4ec8cd25409bdb7de568dcfd8ca3ad4", + "test2_freemuxlet_vs_gmmdemux_concordance_heatmap.png:md5,5c3e82573df24e781d4f422bc3b6925a", + "test2_freemuxlet_vs_gmmdemux_correlation_res.csv:md5,5f2f2076175e313f10396b32fac4c26c", + "test2_freemuxlet_vs_gmmdemux_donor_match.csv:md5,b92352374bf4b32f7e3a1b032a4c8b5f", + "test2_freemuxlet_vs_gmmdemux_intersect_assignment_after_match.csv:md5,f9092a34b0a2eef31daf452d89412f58", + "test2_freemuxlet_vs_hasheddrops_all_assignment_after_match.csv:md5,d777c2ffc8201579aa80123d42ca4c7f", + "test2_freemuxlet_vs_hasheddrops_concordance_heatmap.png:md5,5c3e82573df24e781d4f422bc3b6925a", + "test2_freemuxlet_vs_hasheddrops_correlation_res.csv:md5,5f2f2076175e313f10396b32fac4c26c", + "test2_freemuxlet_vs_hasheddrops_donor_match.csv:md5,b92352374bf4b32f7e3a1b032a4c8b5f", + "test2_freemuxlet_vs_hasheddrops_intersect_assignment_after_match.csv:md5,b3acbe48f60fc1d68dd8198950549812", + "test2_souporcell_vs_bff_consensuscall_all_assignment_after_match.csv:md5,4c61d15e4693b6bc03cd296e757b6498", + "test2_souporcell_vs_bff_consensuscall_concordance_heatmap.png:md5,d48df9ebb22dc1f2616511ad0405cdfd", + "test2_souporcell_vs_bff_consensuscall_correlation_res.csv:md5,7e632ed518a1e6f02ed1ea25a28a4e45", + "test2_souporcell_vs_bff_consensuscall_donor_match.csv:md5,d1feea9baad73b5bc106472a226cbbf2", + "test2_souporcell_vs_bff_consensuscall_intersect_assignment_after_match.csv:md5,17fe8deedeb704f60632733c9ec971ea", + "test2_souporcell_vs_bff_raw_all_assignment_after_match.csv:md5,f8e44703900103c804694041924df32a", + "test2_souporcell_vs_bff_raw_concordance_heatmap.png:md5,d48df9ebb22dc1f2616511ad0405cdfd", + "test2_souporcell_vs_bff_raw_correlation_res.csv:md5,7e632ed518a1e6f02ed1ea25a28a4e45", + "test2_souporcell_vs_bff_raw_donor_match.csv:md5,d1feea9baad73b5bc106472a226cbbf2", + "test2_souporcell_vs_bff_raw_intersect_assignment_after_match.csv:md5,cc89c4263e95c7802152d5a84e5fda5a", + "test2_souporcell_vs_gmmdemux_all_assignment_after_match.csv:md5,5a9927b30847c3c3ff82f0cc5274076f", + "test2_souporcell_vs_gmmdemux_concordance_heatmap.png:md5,d48df9ebb22dc1f2616511ad0405cdfd", + "test2_souporcell_vs_gmmdemux_correlation_res.csv:md5,7e632ed518a1e6f02ed1ea25a28a4e45", + "test2_souporcell_vs_gmmdemux_donor_match.csv:md5,d1feea9baad73b5bc106472a226cbbf2", + "test2_souporcell_vs_gmmdemux_intersect_assignment_after_match.csv:md5,d9cc57a14177d9c362b91dc923c123c8", + "test2_souporcell_vs_hasheddrops_all_assignment_after_match.csv:md5,8e5f886ef04c7d224861348ae470adc5", + "test2_souporcell_vs_hasheddrops_concordance_heatmap.png:md5,d48df9ebb22dc1f2616511ad0405cdfd", + "test2_souporcell_vs_hasheddrops_correlation_res.csv:md5,7e632ed518a1e6f02ed1ea25a28a4e45", + "test2_souporcell_vs_hasheddrops_donor_match.csv:md5,d1feea9baad73b5bc106472a226cbbf2", + "test2_souporcell_vs_hasheddrops_intersect_assignment_after_match.csv:md5,4d4e53deb9c6a9dd9ae9d6d89493d736", + "test2_best_all_assignment_after_match.csv:md5,4c61d15e4693b6bc03cd296e757b6498", + "test2_best_donor_match.csv:md5,d1feea9baad73b5bc106472a226cbbf2", + "test2_best_intersect_assignment_after_match.csv:md5,17fe8deedeb704f60632733c9ec971ea", + "test2_score_record.csv:md5,19e6d7aa257ab96338c70bb1e69442b3", + "test3_freemuxlet_vs_bff_consensuscall_all_assignment_after_match.csv:md5,e6c7237f7a927ab86368d9ecf28b807c", + "test3_freemuxlet_vs_bff_consensuscall_concordance_heatmap.png:md5,5c3e82573df24e781d4f422bc3b6925a", + "test3_freemuxlet_vs_bff_consensuscall_correlation_res.csv:md5,5f2f2076175e313f10396b32fac4c26c", + "test3_freemuxlet_vs_bff_consensuscall_donor_match.csv:md5,b92352374bf4b32f7e3a1b032a4c8b5f", + "test3_freemuxlet_vs_bff_consensuscall_intersect_assignment_after_match.csv:md5,3f611c4fb970117f9771ce05f4ec4d8d", + "test3_freemuxlet_vs_bff_raw_all_assignment_after_match.csv:md5,54406a297be20405919bd15348111ad8", + "test3_freemuxlet_vs_bff_raw_concordance_heatmap.png:md5,5c3e82573df24e781d4f422bc3b6925a", + "test3_freemuxlet_vs_bff_raw_correlation_res.csv:md5,5f2f2076175e313f10396b32fac4c26c", + "test3_freemuxlet_vs_bff_raw_donor_match.csv:md5,b92352374bf4b32f7e3a1b032a4c8b5f", + "test3_freemuxlet_vs_bff_raw_intersect_assignment_after_match.csv:md5,87b3ca9e977fe3df3536ba169566abed", + "test3_freemuxlet_vs_gmmdemux_all_assignment_after_match.csv:md5,e4ec8cd25409bdb7de568dcfd8ca3ad4", + "test3_freemuxlet_vs_gmmdemux_concordance_heatmap.png:md5,5c3e82573df24e781d4f422bc3b6925a", + "test3_freemuxlet_vs_gmmdemux_correlation_res.csv:md5,5f2f2076175e313f10396b32fac4c26c", + "test3_freemuxlet_vs_gmmdemux_donor_match.csv:md5,b92352374bf4b32f7e3a1b032a4c8b5f", + "test3_freemuxlet_vs_gmmdemux_intersect_assignment_after_match.csv:md5,f9092a34b0a2eef31daf452d89412f58", + "test3_freemuxlet_vs_hasheddrops_all_assignment_after_match.csv:md5,d777c2ffc8201579aa80123d42ca4c7f", + "test3_freemuxlet_vs_hasheddrops_concordance_heatmap.png:md5,5c3e82573df24e781d4f422bc3b6925a", + "test3_freemuxlet_vs_hasheddrops_correlation_res.csv:md5,5f2f2076175e313f10396b32fac4c26c", + "test3_freemuxlet_vs_hasheddrops_donor_match.csv:md5,b92352374bf4b32f7e3a1b032a4c8b5f", + "test3_freemuxlet_vs_hasheddrops_intersect_assignment_after_match.csv:md5,b3acbe48f60fc1d68dd8198950549812", + "test3_souporcell_vs_bff_consensuscall_all_assignment_after_match.csv:md5,4c61d15e4693b6bc03cd296e757b6498", + "test3_souporcell_vs_bff_consensuscall_concordance_heatmap.png:md5,d48df9ebb22dc1f2616511ad0405cdfd", + "test3_souporcell_vs_bff_consensuscall_correlation_res.csv:md5,7e632ed518a1e6f02ed1ea25a28a4e45", + "test3_souporcell_vs_bff_consensuscall_donor_match.csv:md5,d1feea9baad73b5bc106472a226cbbf2", + "test3_souporcell_vs_bff_consensuscall_intersect_assignment_after_match.csv:md5,17fe8deedeb704f60632733c9ec971ea", + "test3_souporcell_vs_bff_raw_all_assignment_after_match.csv:md5,f8e44703900103c804694041924df32a", + "test3_souporcell_vs_bff_raw_concordance_heatmap.png:md5,d48df9ebb22dc1f2616511ad0405cdfd", + "test3_souporcell_vs_bff_raw_correlation_res.csv:md5,7e632ed518a1e6f02ed1ea25a28a4e45", + "test3_souporcell_vs_bff_raw_donor_match.csv:md5,d1feea9baad73b5bc106472a226cbbf2", + "test3_souporcell_vs_bff_raw_intersect_assignment_after_match.csv:md5,cc89c4263e95c7802152d5a84e5fda5a", + "test3_souporcell_vs_gmmdemux_all_assignment_after_match.csv:md5,5a9927b30847c3c3ff82f0cc5274076f", + "test3_souporcell_vs_gmmdemux_concordance_heatmap.png:md5,d48df9ebb22dc1f2616511ad0405cdfd", + "test3_souporcell_vs_gmmdemux_correlation_res.csv:md5,7e632ed518a1e6f02ed1ea25a28a4e45", + "test3_souporcell_vs_gmmdemux_donor_match.csv:md5,d1feea9baad73b5bc106472a226cbbf2", + "test3_souporcell_vs_gmmdemux_intersect_assignment_after_match.csv:md5,d9cc57a14177d9c362b91dc923c123c8", + "test3_souporcell_vs_hasheddrops_all_assignment_after_match.csv:md5,8e5f886ef04c7d224861348ae470adc5", + "test3_souporcell_vs_hasheddrops_concordance_heatmap.png:md5,d48df9ebb22dc1f2616511ad0405cdfd", + "test3_souporcell_vs_hasheddrops_correlation_res.csv:md5,7e632ed518a1e6f02ed1ea25a28a4e45", + "test3_souporcell_vs_hasheddrops_donor_match.csv:md5,d1feea9baad73b5bc106472a226cbbf2", + "test3_souporcell_vs_hasheddrops_intersect_assignment_after_match.csv:md5,4d4e53deb9c6a9dd9ae9d6d89493d736", + "test3_best_all_assignment_after_match.csv:md5,4c61d15e4693b6bc03cd296e757b6498", + "test3_best_donor_match.csv:md5,d1feea9baad73b5bc106472a226cbbf2", + "test3_best_intersect_assignment_after_match.csv:md5,17fe8deedeb704f60632733c9ec971ea", + "test3_score_record.csv:md5,19e6d7aa257ab96338c70bb1e69442b3", + "test1_hto-1_informative_variants.csv:md5,7ba6e70ac79adf74d2aa85f723b9f0d6", + "test1_hto-1_matched_gt.csv:md5,bf3b7d684f30153fa603dbddde635583", + "test1_hto-1_unmatched_gt.csv:md5,8e52f5c81f4c467229a68b56390f7948", + "test1_hto-2_informative_variants.csv:md5,f641c48375833e592aa6f8c102ac1160", + "test1_hto-2_matched_gt.csv:md5,fc8bdfc73587eaf63925f658b6fb1c5c", + "test1_hto-2_unmatched_gt.csv:md5,c3185e489df198d888b70e1d065d8265", + "test1_all_representative_variants.csv:md5,93cf3fde5b1236b83abd53369b250f64", + "test1_donor_specific_variants.csv:md5,bd137190cb87b1ad68a7283571e9a4b6", + "test1_donor_specific_variants_upset.png:md5,5d0abe01aa07abd018d20abe8e2e99ab", + "test1_vireo_variants.csv:md5,34bf574b4fe7c16db53a86f5cf6cdbf3", + "test2_hto-1_informative_variants.csv:md5,7ba6e70ac79adf74d2aa85f723b9f0d6", + "test2_hto-1_matched_gt.csv:md5,bf3b7d684f30153fa603dbddde635583", + "test2_hto-1_unmatched_gt.csv:md5,8e52f5c81f4c467229a68b56390f7948", + "test2_hto-2_informative_variants.csv:md5,f641c48375833e592aa6f8c102ac1160", + "test2_hto-2_matched_gt.csv:md5,fc8bdfc73587eaf63925f658b6fb1c5c", + "test2_hto-2_unmatched_gt.csv:md5,c3185e489df198d888b70e1d065d8265", + "test2_all_representative_variants.csv:md5,93cf3fde5b1236b83abd53369b250f64", + "test2_donor_specific_variants.csv:md5,bd137190cb87b1ad68a7283571e9a4b6", + "test2_donor_specific_variants_upset.png:md5,5d0abe01aa07abd018d20abe8e2e99ab", + "test2_vireo_variants.csv:md5,34bf574b4fe7c16db53a86f5cf6cdbf3", + "test3_hto-1_informative_variants.csv:md5,7ba6e70ac79adf74d2aa85f723b9f0d6", + "test3_hto-1_matched_gt.csv:md5,bf3b7d684f30153fa603dbddde635583", + "test3_hto-1_unmatched_gt.csv:md5,8e52f5c81f4c467229a68b56390f7948", + "test3_hto-2_informative_variants.csv:md5,f641c48375833e592aa6f8c102ac1160", + "test3_hto-2_matched_gt.csv:md5,fc8bdfc73587eaf63925f658b6fb1c5c", + "test3_hto-2_unmatched_gt.csv:md5,c3185e489df198d888b70e1d065d8265", + "test3_all_representative_variants.csv:md5,93cf3fde5b1236b83abd53369b250f64", + "test3_donor_specific_variants.csv:md5,bd137190cb87b1ad68a7283571e9a4b6", + "test3_donor_specific_variants_upset.png:md5,5d0abe01aa07abd018d20abe8e2e99ab", + "test3_vireo_variants.csv:md5,34bf574b4fe7c16db53a86f5cf6cdbf3", "test1.clust1.samples.gz:md5,42f8c8ba281fccf6edaf6dbd30144210", "test1.lmix:md5,d8be4c936ff56980a476d5ba82290b8e", "test2.clust1.samples.gz:md5,42f8c8ba281fccf6edaf6dbd30144210", @@ -536,27 +665,29 @@ "clusters.tsv:md5,3443284fb0e0ace31b5da0f329568392", "clusters.tsv:md5,3443284fb0e0ace31b5da0f329568392", "clusters.tsv:md5,3443284fb0e0ace31b5da0f329568392", - "test1_genetic_summary.h5ad:md5,10587503bf9706b5e2bd6db031acd1f3", - "test1_genetic_summary.h5mu:md5,42349934b0d68f6b817d954e719f905f", - "test1_genetic_summary_assignment.csv:md5,6caeede567715d29c674d0d1c32c07fc", - "test1_genetic_summary_classification.csv:md5,19faffcc11a5e05a24a8efd49fb186ca", - "test2_genetic_summary.h5ad:md5,10587503bf9706b5e2bd6db031acd1f3", - "test2_genetic_summary.h5mu:md5,42349934b0d68f6b817d954e719f905f", - "test2_genetic_summary_assignment.csv:md5,6caeede567715d29c674d0d1c32c07fc", - "test2_genetic_summary_classification.csv:md5,19faffcc11a5e05a24a8efd49fb186ca", - "test3_genetic_summary.h5ad:md5,10587503bf9706b5e2bd6db031acd1f3", - "test3_genetic_summary.h5mu:md5,42349934b0d68f6b817d954e719f905f", - "test3_genetic_summary_assignment.csv:md5,6caeede567715d29c674d0d1c32c07fc", - "test3_genetic_summary_classification.csv:md5,19faffcc11a5e05a24a8efd49fb186ca", + "test1_genetic_overview_assignment.csv:md5,961503e69942d87a7887c4d3b52fd6a0", + "test1_genetic_overview_classification.csv:md5,1cbe450ce379ae815fffc44f230cfc63", + "test1_genetic_summary_assignment.csv:md5,e4c35f97306c75bf69d88cf6c39751a6", + "test1_genetic_summary_classification.csv:md5,615fa91b5f67289aa09c6116484d9bf0", + "test2_genetic_overview_assignment.csv:md5,961503e69942d87a7887c4d3b52fd6a0", + "test2_genetic_overview_classification.csv:md5,1cbe450ce379ae815fffc44f230cfc63", + "test2_genetic_summary_assignment.csv:md5,e4c35f97306c75bf69d88cf6c39751a6", + "test2_genetic_summary_classification.csv:md5,615fa91b5f67289aa09c6116484d9bf0", + "test3_genetic_overview_assignment.csv:md5,961503e69942d87a7887c4d3b52fd6a0", + "test3_genetic_overview_classification.csv:md5,1cbe450ce379ae815fffc44f230cfc63", + "test3_genetic_summary_assignment.csv:md5,e4c35f97306c75bf69d88cf6c39751a6", + "test3_genetic_summary_classification.csv:md5,615fa91b5f67289aa09c6116484d9bf0", "test1.base.vcf.gz:md5,36eccb4bd3e2130f395985e09a66a60d", "test1.cells.vcf.gz:md5,b28d8acaf17777c7cf9e2c7d01d93a66", "test1.samples.tsv:md5,9e488782c1bcd63c37ee3d1c4c0a9217", "test1.tag.AD.mtx:md5,a76d9f63c6abdcab00f583fac269a98e", "test1.tag.DP.mtx:md5,6f8e54bbaa8ad9dcaa42c15eff1e1351", "test1.tag.OTH.mtx:md5,45e64ea3b3b8e8a7126690d9e775d501", - "test1_donor_ids.tsv:md5,083e128fb5594df75dff060ec886d69c", - "test1_prob_doublet.tsv.gz:md5,0226d8043ee03b790fe2d130093b40df", - "test1_prob_singlet.tsv.gz:md5,959b21543d21f2214e399e541848d81a", + "test1_GT_donors.vireo.vcf.gz:md5,60bfaaf61b8e7f5fc2bd0950e576c3e5", + "test1_donor_ids.tsv:md5,5dd2633a15d99a044d7f6706912ed5bc", + "test1_filtered_variants.tsv:md5,d37231135d9e6e330c03ed0647fcaf0e", + "test1_prob_doublet.tsv.gz:md5,3dae1d20888be460d02782eb776043bb", + "test1_prob_singlet.tsv.gz:md5,90a717e3bdb50090c72fa73a18330e38", "test1_summary.tsv:md5,94b0ce40cc86bbe7fbacebf98e685ee7", "test2.base.vcf.gz:md5,36eccb4bd3e2130f395985e09a66a60d", "test2.cells.vcf.gz:md5,b28d8acaf17777c7cf9e2c7d01d93a66", @@ -564,9 +695,11 @@ "test2.tag.AD.mtx:md5,a76d9f63c6abdcab00f583fac269a98e", "test2.tag.DP.mtx:md5,6f8e54bbaa8ad9dcaa42c15eff1e1351", "test2.tag.OTH.mtx:md5,45e64ea3b3b8e8a7126690d9e775d501", - "test2_donor_ids.tsv:md5,083e128fb5594df75dff060ec886d69c", - "test2_prob_doublet.tsv.gz:md5,0226d8043ee03b790fe2d130093b40df", - "test2_prob_singlet.tsv.gz:md5,959b21543d21f2214e399e541848d81a", + "test2_GT_donors.vireo.vcf.gz:md5,60bfaaf61b8e7f5fc2bd0950e576c3e5", + "test2_donor_ids.tsv:md5,5dd2633a15d99a044d7f6706912ed5bc", + "test2_filtered_variants.tsv:md5,d37231135d9e6e330c03ed0647fcaf0e", + "test2_prob_doublet.tsv.gz:md5,3dae1d20888be460d02782eb776043bb", + "test2_prob_singlet.tsv.gz:md5,90a717e3bdb50090c72fa73a18330e38", "test2_summary.tsv:md5,94b0ce40cc86bbe7fbacebf98e685ee7", "test3.base.vcf.gz:md5,36eccb4bd3e2130f395985e09a66a60d", "test3.cells.vcf.gz:md5,b28d8acaf17777c7cf9e2c7d01d93a66", @@ -574,92 +707,111 @@ "test3.tag.AD.mtx:md5,a76d9f63c6abdcab00f583fac269a98e", "test3.tag.DP.mtx:md5,6f8e54bbaa8ad9dcaa42c15eff1e1351", "test3.tag.OTH.mtx:md5,45e64ea3b3b8e8a7126690d9e775d501", - "test3_donor_ids.tsv:md5,083e128fb5594df75dff060ec886d69c", - "test3_prob_doublet.tsv.gz:md5,0226d8043ee03b790fe2d130093b40df", - "test3_prob_singlet.tsv.gz:md5,959b21543d21f2214e399e541848d81a", + "test3_GT_donors.vireo.vcf.gz:md5,60bfaaf61b8e7f5fc2bd0950e576c3e5", + "test3_donor_ids.tsv:md5,5dd2633a15d99a044d7f6706912ed5bc", + "test3_filtered_variants.tsv:md5,d37231135d9e6e330c03ed0647fcaf0e", + "test3_prob_doublet.tsv.gz:md5,3dae1d20888be460d02782eb776043bb", + "test3_prob_singlet.tsv.gz:md5,90a717e3bdb50090c72fa73a18330e38", "test3_summary.tsv:md5,94b0ce40cc86bbe7fbacebf98e685ee7", - "test1.csv:md5,0ae6066c69fd930f16e5d6c8ddfa9258", - "test2.csv:md5,0ae6066c69fd930f16e5d6c8ddfa9258", - "test3.csv:md5,0ae6066c69fd930f16e5d6c8ddfa9258", + "test1_assignment_bff.csv:md5,82e9e12fec29f9eb0dcfbb1b6d8cc02a", + "test1_metrics_bff.csv:md5,1d7751cf035c9e736e4de5504ff87a6a", + "test1_params_bff.csv:md5,dbb83e4e50779916ff4e368b2d05fe91", + "test2_assignment_bff.csv:md5,82e9e12fec29f9eb0dcfbb1b6d8cc02a", + "test2_metrics_bff.csv:md5,1d7751cf035c9e736e4de5504ff87a6a", + "test2_params_bff.csv:md5,08758e268b309e4f3288069e3eab358a", + "test3_assignment_bff.csv:md5,82e9e12fec29f9eb0dcfbb1b6d8cc02a", + "test3_metrics_bff.csv:md5,1d7751cf035c9e736e4de5504ff87a6a", + "test3_params_bff.csv:md5,882b80fd11a10a375e0abd5ae29f74b0", + "GMM_full.config:md5,0a18f3d5729c4b6399ce4161609362b6", + "barcodes.tsv.gz:md5,e4cd9ce9929a07d6763304ec1bf64c23", + "features.tsv.gz:md5,441a968b2c7ee68eae8388b04bee328d", + "matrix.mtx.gz:md5,6f855b8bf68573d2e09139c29f9a2d41", + "GMM_full.config:md5,0a18f3d5729c4b6399ce4161609362b6", + "barcodes.tsv.gz:md5,e4cd9ce9929a07d6763304ec1bf64c23", + "features.tsv.gz:md5,441a968b2c7ee68eae8388b04bee328d", + "matrix.mtx.gz:md5,6f855b8bf68573d2e09139c29f9a2d41", + "GMM_full.config:md5,0a18f3d5729c4b6399ce4161609362b6", + "barcodes.tsv.gz:md5,e4cd9ce9929a07d6763304ec1bf64c23", + "features.tsv.gz:md5,441a968b2c7ee68eae8388b04bee328d", + "matrix.mtx.gz:md5,6f855b8bf68573d2e09139c29f9a2d41", "test1_emptyDrops.csv:md5,a227d42afbcc590b4e949075cde4a5b6", "test1_emptyDrops.png:md5,2d7113a76a494b2b2c18b77c4a664063", "test1_emptyDrops.rds:md5,bda2e88b1e3e8c8ea5a22e19e3ab243d", - "test1_hasheddrops.rds:md5,354124932e1ec5c230ee344ee33417b3", - "test1_id_to_hash.csv:md5,1833c29abc6feec1c8d400342019fed2", + "test1_hasheddrops.rds:md5,7bb71fa6f28e3fc97b20bbdeffbb81d2", + "test1_id_to_hash.csv:md5,e8f577103f3e0c17604f89c3bcda2ffb", "test1_params_hasheddrops.csv:md5,b253501914df23ef5901262e8a241a8d", "test1_plot_hasheddrops.png:md5,2d7113a76a494b2b2c18b77c4a664063", "test1_results_hasheddrops.csv:md5,9614789e65fc843e6a11fa60686fe89a", "test2_emptyDrops.csv:md5,a227d42afbcc590b4e949075cde4a5b6", "test2_emptyDrops.png:md5,2d7113a76a494b2b2c18b77c4a664063", "test2_emptyDrops.rds:md5,bda2e88b1e3e8c8ea5a22e19e3ab243d", - "test2_hasheddrops.rds:md5,354124932e1ec5c230ee344ee33417b3", - "test2_id_to_hash.csv:md5,1833c29abc6feec1c8d400342019fed2", + "test2_hasheddrops.rds:md5,7bb71fa6f28e3fc97b20bbdeffbb81d2", + "test2_id_to_hash.csv:md5,e8f577103f3e0c17604f89c3bcda2ffb", "test2_params_hasheddrops.csv:md5,3612264937e5c5962fefddc43afb595f", "test2_plot_hasheddrops.png:md5,2d7113a76a494b2b2c18b77c4a664063", "test2_results_hasheddrops.csv:md5,9614789e65fc843e6a11fa60686fe89a", "test3_emptyDrops.csv:md5,a227d42afbcc590b4e949075cde4a5b6", "test3_emptyDrops.png:md5,2d7113a76a494b2b2c18b77c4a664063", "test3_emptyDrops.rds:md5,bda2e88b1e3e8c8ea5a22e19e3ab243d", - "test3_hasheddrops.rds:md5,354124932e1ec5c230ee344ee33417b3", - "test3_id_to_hash.csv:md5,1833c29abc6feec1c8d400342019fed2", + "test3_hasheddrops.rds:md5,7bb71fa6f28e3fc97b20bbdeffbb81d2", + "test3_id_to_hash.csv:md5,e8f577103f3e0c17604f89c3bcda2ffb", "test3_params_hasheddrops.csv:md5,b15e94f887e3ada8f790ea18eb791d8b", "test3_plot_hasheddrops.png:md5,2d7113a76a494b2b2c18b77c4a664063", "test3_results_hasheddrops.csv:md5,9614789e65fc843e6a11fa60686fe89a", - "test1_assignment_hashsolo.csv:md5,95de378e2f00e06a642925ba7b4b2a13", - "test1_hashsolo.h5ad:md5,3f9de5dd57023a525961ce126a6a2f5b", - "test1_params_hashsolo.csv:md5,e7b8c6c56164c1e458fd281ac44a70f3", - "test2_assignment_hashsolo.csv:md5,95de378e2f00e06a642925ba7b4b2a13", - "test2_hashsolo.h5ad:md5,3f9de5dd57023a525961ce126a6a2f5b", - "test2_params_hashsolo.csv:md5,504ae4e442d33b7be74926244a46eba2", - "test3_assignment_hashsolo.csv:md5,95de378e2f00e06a642925ba7b4b2a13", - "test3_hashsolo.h5ad:md5,3f9de5dd57023a525961ce126a6a2f5b", - "test3_params_hashsolo.csv:md5,1f6d75d89ed76c033155277713661dc3", - "test1_params_multiseqdemux.csv:md5,60a096f79d78655870aef2af9130cf9d", - "test1_res_multiseqdemux.csv:md5,9d4475b8c2778e7e361747dd521072e4", - "test2_params_multiseqdemux.csv:md5,bb7b4009e6f709ebf35407b7849bfac3", - "test2_res_multiseqdemux.csv:md5,9d4475b8c2778e7e361747dd521072e4", - "test3_params_multiseqdemux.csv:md5,336308c45e52f92981173ecc7f295581", - "test3_res_multiseqdemux.csv:md5,9d4475b8c2778e7e361747dd521072e4", - "test1_params_preprocessing.csv:md5,b77b94588a53835e913cb55b3791d309", - "test2_params_preprocessing.csv:md5,0ed6b213e9f08ee5fc0f89b7184a3fb9", - "test3_params_preprocessing.csv:md5,c5b931c6fe281d0cc63cb3ed13cb2d10", - "test1_hashing_summary.h5ad:md5,acb501d66ea93044b3de232f1dd9be7f", - "test1_hashing_summary.h5mu:md5,48fa1ca52991c1f869f565304f41151d", - "test1_hashing_summary_assignment.csv:md5,5574ae2ab0b030a0c307a660f1808b4f", - "test1_hashing_summary_classification.csv:md5,dc0c3f4754580b897451aabb417372e4", - "test2_hashing_summary.h5ad:md5,acb501d66ea93044b3de232f1dd9be7f", - "test2_hashing_summary.h5mu:md5,48fa1ca52991c1f869f565304f41151d", - "test2_hashing_summary_assignment.csv:md5,5574ae2ab0b030a0c307a660f1808b4f", - "test2_hashing_summary_classification.csv:md5,dc0c3f4754580b897451aabb417372e4", - "test3_hashing_summary.h5ad:md5,acb501d66ea93044b3de232f1dd9be7f", - "test3_hashing_summary.h5mu:md5,48fa1ca52991c1f869f565304f41151d", - "test3_hashing_summary_assignment.csv:md5,5574ae2ab0b030a0c307a660f1808b4f", - "test3_hashing_summary_classification.csv:md5,dc0c3f4754580b897451aabb417372e4", + "test1_hashing_overview_assignment.csv:md5,8294c52a27689789c28a463429516b6d", + "test1_hashing_overview_classification.csv:md5,ea59642fe64b2f45954f4b088728380f", + "test1_hashing_summary_assignment.csv:md5,706c257beac2bae517d76731c5320989", + "test1_hashing_summary_classification.csv:md5,ab3df547c2e0319bd581c95432981c98", + "test2_hashing_overview_assignment.csv:md5,8294c52a27689789c28a463429516b6d", + "test2_hashing_overview_classification.csv:md5,ea59642fe64b2f45954f4b088728380f", + "test2_hashing_summary_assignment.csv:md5,706c257beac2bae517d76731c5320989", + "test2_hashing_summary_classification.csv:md5,ab3df547c2e0319bd581c95432981c98", + "test3_hashing_overview_assignment.csv:md5,8294c52a27689789c28a463429516b6d", + "test3_hashing_overview_classification.csv:md5,ea59642fe64b2f45954f4b088728380f", + "test3_hashing_summary_assignment.csv:md5,706c257beac2bae517d76731c5320989", + "test3_hashing_summary_classification.csv:md5,ab3df547c2e0319bd581c95432981c98", "multiqc_citations.txt:md5,4c806e63a283ec1b7e78cdae3a923d4f", - "barcodes.tsv.gz:md5,298331318a633cd9fdcb29416bf6ae41", - "features.tsv.gz:md5,e244612671c0667117cf06b846e7307a", - "matrix.mtx.gz:md5,af1bcd7c84f4f4838a4b732908a884f9", - "barcodes.tsv.gz:md5,298331318a633cd9fdcb29416bf6ae41", - "features.tsv.gz:md5,29c98c53ef1a9fa601a5bd4f29633e14", - "matrix.mtx.gz:md5,a54e91362b29715b36ec33e96f981d12", - "barcodes.tsv.gz:md5,298331318a633cd9fdcb29416bf6ae41", - "features.tsv.gz:md5,e244612671c0667117cf06b846e7307a", - "matrix.mtx.gz:md5,af1bcd7c84f4f4838a4b732908a884f9", - "barcodes.tsv.gz:md5,298331318a633cd9fdcb29416bf6ae41", - "features.tsv.gz:md5,29c98c53ef1a9fa601a5bd4f29633e14", - "matrix.mtx.gz:md5,a54e91362b29715b36ec33e96f981d12", - "barcodes.tsv.gz:md5,298331318a633cd9fdcb29416bf6ae41", - "features.tsv.gz:md5,e244612671c0667117cf06b846e7307a", - "matrix.mtx.gz:md5,af1bcd7c84f4f4838a4b732908a884f9", - "barcodes.tsv.gz:md5,298331318a633cd9fdcb29416bf6ae41", - "features.tsv.gz:md5,29c98c53ef1a9fa601a5bd4f29633e14", - "matrix.mtx.gz:md5,a54e91362b29715b36ec33e96f981d12" + "test1_assignment.csv:md5,4d0eefc4298446469162230bc2752a44", + "test1_classification.csv:md5,793c660b1dbba5b1f5d6a87957df32fa", + "test1_genetic.h5ad:md5,c1cec22be88ca7440034dccdd1d21722", + "test1_genetic_and_hashing.h5mu:md5,bdef3ba1ff44ea3ed9cd8b4bdf0396f5", + "test1_hashing.h5ad:md5,9ad6217e86b5118e358ca5aaa5d4bff1", + "test2_assignment.csv:md5,4d0eefc4298446469162230bc2752a44", + "test2_classification.csv:md5,793c660b1dbba5b1f5d6a87957df32fa", + "test2_genetic.h5ad:md5,c1cec22be88ca7440034dccdd1d21722", + "test2_genetic_and_hashing.h5mu:md5,bdef3ba1ff44ea3ed9cd8b4bdf0396f5", + "test2_hashing.h5ad:md5,9ad6217e86b5118e358ca5aaa5d4bff1", + "test3_assignment.csv:md5,4d0eefc4298446469162230bc2752a44", + "test3_classification.csv:md5,793c660b1dbba5b1f5d6a87957df32fa", + "test3_genetic.h5ad:md5,c1cec22be88ca7440034dccdd1d21722", + "test3_genetic_and_hashing.h5mu:md5,bdef3ba1ff44ea3ed9cd8b4bdf0396f5", + "test3_hashing.h5ad:md5,9ad6217e86b5118e358ca5aaa5d4bff1" + ], + [ + "test1_donor_specific.vcf.gz:md5,8c51c5ed9e880089a8532e30a659adac", + "test1_vireo.vcf.gz:md5,6e4bbad3892e84cd1dd53c8fc0579977", + "test2_donor_specific.vcf.gz:md5,8c51c5ed9e880089a8532e30a659adac", + "test2_vireo.vcf.gz:md5,6e4bbad3892e84cd1dd53c8fc0579977", + "test3_donor_specific.vcf.gz:md5,8c51c5ed9e880089a8532e30a659adac", + "test3_vireo.vcf.gz:md5,6e4bbad3892e84cd1dd53c8fc0579977", + "test1.clust1.vcf.gz:md5,8de6d22ca83fcc331f3d49d59973e5ac", + "test2.clust1.vcf.gz:md5,8de6d22ca83fcc331f3d49d59973e5ac", + "test3.clust1.vcf.gz:md5,8de6d22ca83fcc331f3d49d59973e5ac", + "test1.base.vcf.gz:md5,cb2af96582fa4efe089ec4e0b3f1644c", + "test1.cells.vcf.gz:md5,349e2d3b9abb1e346f0cebb621b9d7f4", + "test1_GT_donors.vireo.vcf.gz:md5,b92d531491566870c247cf3a122b2dc1", + "test2.base.vcf.gz:md5,cb2af96582fa4efe089ec4e0b3f1644c", + "test2.cells.vcf.gz:md5,349e2d3b9abb1e346f0cebb621b9d7f4", + "test2_GT_donors.vireo.vcf.gz:md5,b92d531491566870c247cf3a122b2dc1", + "test3.base.vcf.gz:md5,cb2af96582fa4efe089ec4e0b3f1644c", + "test3.cells.vcf.gz:md5,349e2d3b9abb1e346f0cebb621b9d7f4", + "test3_GT_donors.vireo.vcf.gz:md5,b92d531491566870c247cf3a122b2dc1" ] ], "meta": { - "nf-test": "0.9.2", - "nextflow": "25.04.6" + "nf-test": "0.9.3", + "nextflow": "25.10.3" }, - "timestamp": "2025-11-22T07:38:00.471623041" + "timestamp": "2026-02-01T16:47:19.348602535" } } \ No newline at end of file diff --git a/tests/test_donor_match.nf.test b/tests/test_donor_match.nf.test new file mode 100644 index 00000000..df77e882 --- /dev/null +++ b/tests/test_donor_match.nf.test @@ -0,0 +1,40 @@ +nextflow_pipeline { + + name "Test pipeline with donor_match config" + script "../main.nf" + tag "pipeline" + tag "test_donor_match" + profile "test_donor_match" + + test("-profile test_donor_match") { + + when { + params { + outdir = "$outputDir" + } + } + + then { + // stable_name: All files + folders in ${params.outdir}/ with a stable name + def stable_name = getAllFilesFromDir(params.outdir, relative: true, includeDir: true, ignore: ['pipeline_info/*.{html,json,txt}']) + // stable_path: All files in ${params.outdir}/ with stable content + def stable_path = getAllFilesFromDir(params.outdir, ignoreFile: 'tests/.nftignore') + // VCF files + def vcf_files = getAllFilesFromDir(params.outdir, include: ['**/*.vcf{,.gz}']) + + assertAll( + { assert workflow.success}, + { assert snapshot( + // pipeline versions.yml file for multiqc from which Nextflow version is removed because we test pipelines on multiple Nextflow versions + removeNextflowVersion("$outputDir/pipeline_info/nf_core_hadge_software_mqc_versions.yml"), + // All stable path name, with a relative path + stable_name, + // All files with stable contents + stable_path, + // VCF files + vcf_files.isEmpty() ? 'No VCF files' : vcf_files.collect { file -> file.getName() + ":md5," + path(file.toString()).vcf.variantsMD5 } + ).match() } + ) + } + } +} diff --git a/tests/test_donor_match.nf.test.snap b/tests/test_donor_match.nf.test.snap new file mode 100644 index 00000000..b1f7f770 --- /dev/null +++ b/tests/test_donor_match.nf.test.snap @@ -0,0 +1,428 @@ +{ + "-profile test_donor_match": { + "content": [ + { + "DONOR_MATCH": { + "r-base": "4.5.2", + "r-data.table": "1.17.8", + "r-pheatmap": "1.0.13", + "r-tidyverse": "2.0.0" + }, + "FIND_VARIANTS": { + "r-base": "4.5.2", + "r-complexupset": "1.3.3", + "r-data.table": "1.17.8", + "r-tidyverse": "2.0.0", + "r-vcfr": "1.15.0" + }, + "SUBSET_GT_DONORS": { + "bcftools": 1.22 + }, + "Workflow": { + "nf-core/hadge": "v1.0.0dev" + } + }, + [ + "donor_match", + "donor_match/test1", + "donor_match/test1/freemuxlet_vs_bff_consensuscall", + "donor_match/test1/freemuxlet_vs_bff_consensuscall/test1_freemuxlet_vs_bff_consensuscall_all_assignment_after_match.csv", + "donor_match/test1/freemuxlet_vs_bff_consensuscall/test1_freemuxlet_vs_bff_consensuscall_concordance_heatmap.png", + "donor_match/test1/freemuxlet_vs_bff_consensuscall/test1_freemuxlet_vs_bff_consensuscall_correlation_res.csv", + "donor_match/test1/freemuxlet_vs_bff_consensuscall/test1_freemuxlet_vs_bff_consensuscall_donor_match.csv", + "donor_match/test1/freemuxlet_vs_bff_consensuscall/test1_freemuxlet_vs_bff_consensuscall_intersect_assignment_after_match.csv", + "donor_match/test1/freemuxlet_vs_bff_raw", + "donor_match/test1/freemuxlet_vs_bff_raw/test1_freemuxlet_vs_bff_raw_all_assignment_after_match.csv", + "donor_match/test1/freemuxlet_vs_bff_raw/test1_freemuxlet_vs_bff_raw_concordance_heatmap.png", + "donor_match/test1/freemuxlet_vs_bff_raw/test1_freemuxlet_vs_bff_raw_correlation_res.csv", + "donor_match/test1/freemuxlet_vs_bff_raw/test1_freemuxlet_vs_bff_raw_donor_match.csv", + "donor_match/test1/freemuxlet_vs_bff_raw/test1_freemuxlet_vs_bff_raw_intersect_assignment_after_match.csv", + "donor_match/test1/freemuxlet_vs_gmmdemux", + "donor_match/test1/freemuxlet_vs_gmmdemux/test1_freemuxlet_vs_gmmdemux_all_assignment_after_match.csv", + "donor_match/test1/freemuxlet_vs_gmmdemux/test1_freemuxlet_vs_gmmdemux_concordance_heatmap.png", + "donor_match/test1/freemuxlet_vs_gmmdemux/test1_freemuxlet_vs_gmmdemux_correlation_res.csv", + "donor_match/test1/freemuxlet_vs_gmmdemux/test1_freemuxlet_vs_gmmdemux_donor_match.csv", + "donor_match/test1/freemuxlet_vs_gmmdemux/test1_freemuxlet_vs_gmmdemux_intersect_assignment_after_match.csv", + "donor_match/test1/freemuxlet_vs_hasheddrops", + "donor_match/test1/freemuxlet_vs_hasheddrops/test1_freemuxlet_vs_hasheddrops_all_assignment_after_match.csv", + "donor_match/test1/freemuxlet_vs_hasheddrops/test1_freemuxlet_vs_hasheddrops_concordance_heatmap.png", + "donor_match/test1/freemuxlet_vs_hasheddrops/test1_freemuxlet_vs_hasheddrops_correlation_res.csv", + "donor_match/test1/freemuxlet_vs_hasheddrops/test1_freemuxlet_vs_hasheddrops_donor_match.csv", + "donor_match/test1/freemuxlet_vs_hasheddrops/test1_freemuxlet_vs_hasheddrops_intersect_assignment_after_match.csv", + "donor_match/test1/souporcell_vs_bff_consensuscall", + "donor_match/test1/souporcell_vs_bff_consensuscall/test1_souporcell_vs_bff_consensuscall_all_assignment_after_match.csv", + "donor_match/test1/souporcell_vs_bff_consensuscall/test1_souporcell_vs_bff_consensuscall_concordance_heatmap.png", + "donor_match/test1/souporcell_vs_bff_consensuscall/test1_souporcell_vs_bff_consensuscall_correlation_res.csv", + "donor_match/test1/souporcell_vs_bff_consensuscall/test1_souporcell_vs_bff_consensuscall_donor_match.csv", + "donor_match/test1/souporcell_vs_bff_consensuscall/test1_souporcell_vs_bff_consensuscall_intersect_assignment_after_match.csv", + "donor_match/test1/souporcell_vs_bff_raw", + "donor_match/test1/souporcell_vs_bff_raw/test1_souporcell_vs_bff_raw_all_assignment_after_match.csv", + "donor_match/test1/souporcell_vs_bff_raw/test1_souporcell_vs_bff_raw_concordance_heatmap.png", + "donor_match/test1/souporcell_vs_bff_raw/test1_souporcell_vs_bff_raw_correlation_res.csv", + "donor_match/test1/souporcell_vs_bff_raw/test1_souporcell_vs_bff_raw_donor_match.csv", + "donor_match/test1/souporcell_vs_bff_raw/test1_souporcell_vs_bff_raw_intersect_assignment_after_match.csv", + "donor_match/test1/souporcell_vs_gmmdemux", + "donor_match/test1/souporcell_vs_gmmdemux/test1_souporcell_vs_gmmdemux_all_assignment_after_match.csv", + "donor_match/test1/souporcell_vs_gmmdemux/test1_souporcell_vs_gmmdemux_concordance_heatmap.png", + "donor_match/test1/souporcell_vs_gmmdemux/test1_souporcell_vs_gmmdemux_correlation_res.csv", + "donor_match/test1/souporcell_vs_gmmdemux/test1_souporcell_vs_gmmdemux_donor_match.csv", + "donor_match/test1/souporcell_vs_gmmdemux/test1_souporcell_vs_gmmdemux_intersect_assignment_after_match.csv", + "donor_match/test1/souporcell_vs_hasheddrops", + "donor_match/test1/souporcell_vs_hasheddrops/test1_souporcell_vs_hasheddrops_all_assignment_after_match.csv", + "donor_match/test1/souporcell_vs_hasheddrops/test1_souporcell_vs_hasheddrops_concordance_heatmap.png", + "donor_match/test1/souporcell_vs_hasheddrops/test1_souporcell_vs_hasheddrops_correlation_res.csv", + "donor_match/test1/souporcell_vs_hasheddrops/test1_souporcell_vs_hasheddrops_donor_match.csv", + "donor_match/test1/souporcell_vs_hasheddrops/test1_souporcell_vs_hasheddrops_intersect_assignment_after_match.csv", + "donor_match/test1/test1_best_all_assignment_after_match.csv", + "donor_match/test1/test1_best_donor_match.csv", + "donor_match/test1/test1_best_intersect_assignment_after_match.csv", + "donor_match/test1/test1_score_record.csv", + "donor_match/test2", + "donor_match/test2/freemuxlet_vs_bff_consensuscall", + "donor_match/test2/freemuxlet_vs_bff_consensuscall/test2_freemuxlet_vs_bff_consensuscall_all_assignment_after_match.csv", + "donor_match/test2/freemuxlet_vs_bff_consensuscall/test2_freemuxlet_vs_bff_consensuscall_concordance_heatmap.png", + "donor_match/test2/freemuxlet_vs_bff_consensuscall/test2_freemuxlet_vs_bff_consensuscall_correlation_res.csv", + "donor_match/test2/freemuxlet_vs_bff_consensuscall/test2_freemuxlet_vs_bff_consensuscall_donor_match.csv", + "donor_match/test2/freemuxlet_vs_bff_consensuscall/test2_freemuxlet_vs_bff_consensuscall_intersect_assignment_after_match.csv", + "donor_match/test2/freemuxlet_vs_bff_raw", + "donor_match/test2/freemuxlet_vs_bff_raw/test2_freemuxlet_vs_bff_raw_all_assignment_after_match.csv", + "donor_match/test2/freemuxlet_vs_bff_raw/test2_freemuxlet_vs_bff_raw_concordance_heatmap.png", + "donor_match/test2/freemuxlet_vs_bff_raw/test2_freemuxlet_vs_bff_raw_correlation_res.csv", + "donor_match/test2/freemuxlet_vs_bff_raw/test2_freemuxlet_vs_bff_raw_donor_match.csv", + "donor_match/test2/freemuxlet_vs_bff_raw/test2_freemuxlet_vs_bff_raw_intersect_assignment_after_match.csv", + "donor_match/test2/freemuxlet_vs_gmmdemux", + "donor_match/test2/freemuxlet_vs_gmmdemux/test2_freemuxlet_vs_gmmdemux_all_assignment_after_match.csv", + "donor_match/test2/freemuxlet_vs_gmmdemux/test2_freemuxlet_vs_gmmdemux_concordance_heatmap.png", + "donor_match/test2/freemuxlet_vs_gmmdemux/test2_freemuxlet_vs_gmmdemux_correlation_res.csv", + "donor_match/test2/freemuxlet_vs_gmmdemux/test2_freemuxlet_vs_gmmdemux_donor_match.csv", + "donor_match/test2/freemuxlet_vs_gmmdemux/test2_freemuxlet_vs_gmmdemux_intersect_assignment_after_match.csv", + "donor_match/test2/freemuxlet_vs_hasheddrops", + "donor_match/test2/freemuxlet_vs_hasheddrops/test2_freemuxlet_vs_hasheddrops_all_assignment_after_match.csv", + "donor_match/test2/freemuxlet_vs_hasheddrops/test2_freemuxlet_vs_hasheddrops_concordance_heatmap.png", + "donor_match/test2/freemuxlet_vs_hasheddrops/test2_freemuxlet_vs_hasheddrops_correlation_res.csv", + "donor_match/test2/freemuxlet_vs_hasheddrops/test2_freemuxlet_vs_hasheddrops_donor_match.csv", + "donor_match/test2/freemuxlet_vs_hasheddrops/test2_freemuxlet_vs_hasheddrops_intersect_assignment_after_match.csv", + "donor_match/test2/souporcell_vs_bff_consensuscall", + "donor_match/test2/souporcell_vs_bff_consensuscall/test2_souporcell_vs_bff_consensuscall_all_assignment_after_match.csv", + "donor_match/test2/souporcell_vs_bff_consensuscall/test2_souporcell_vs_bff_consensuscall_concordance_heatmap.png", + "donor_match/test2/souporcell_vs_bff_consensuscall/test2_souporcell_vs_bff_consensuscall_correlation_res.csv", + "donor_match/test2/souporcell_vs_bff_consensuscall/test2_souporcell_vs_bff_consensuscall_donor_match.csv", + "donor_match/test2/souporcell_vs_bff_consensuscall/test2_souporcell_vs_bff_consensuscall_intersect_assignment_after_match.csv", + "donor_match/test2/souporcell_vs_bff_raw", + "donor_match/test2/souporcell_vs_bff_raw/test2_souporcell_vs_bff_raw_all_assignment_after_match.csv", + "donor_match/test2/souporcell_vs_bff_raw/test2_souporcell_vs_bff_raw_concordance_heatmap.png", + "donor_match/test2/souporcell_vs_bff_raw/test2_souporcell_vs_bff_raw_correlation_res.csv", + "donor_match/test2/souporcell_vs_bff_raw/test2_souporcell_vs_bff_raw_donor_match.csv", + "donor_match/test2/souporcell_vs_bff_raw/test2_souporcell_vs_bff_raw_intersect_assignment_after_match.csv", + "donor_match/test2/souporcell_vs_gmmdemux", + "donor_match/test2/souporcell_vs_gmmdemux/test2_souporcell_vs_gmmdemux_all_assignment_after_match.csv", + "donor_match/test2/souporcell_vs_gmmdemux/test2_souporcell_vs_gmmdemux_concordance_heatmap.png", + "donor_match/test2/souporcell_vs_gmmdemux/test2_souporcell_vs_gmmdemux_correlation_res.csv", + "donor_match/test2/souporcell_vs_gmmdemux/test2_souporcell_vs_gmmdemux_donor_match.csv", + "donor_match/test2/souporcell_vs_gmmdemux/test2_souporcell_vs_gmmdemux_intersect_assignment_after_match.csv", + "donor_match/test2/souporcell_vs_hasheddrops", + "donor_match/test2/souporcell_vs_hasheddrops/test2_souporcell_vs_hasheddrops_all_assignment_after_match.csv", + "donor_match/test2/souporcell_vs_hasheddrops/test2_souporcell_vs_hasheddrops_concordance_heatmap.png", + "donor_match/test2/souporcell_vs_hasheddrops/test2_souporcell_vs_hasheddrops_correlation_res.csv", + "donor_match/test2/souporcell_vs_hasheddrops/test2_souporcell_vs_hasheddrops_donor_match.csv", + "donor_match/test2/souporcell_vs_hasheddrops/test2_souporcell_vs_hasheddrops_intersect_assignment_after_match.csv", + "donor_match/test2/test2_best_all_assignment_after_match.csv", + "donor_match/test2/test2_best_donor_match.csv", + "donor_match/test2/test2_best_intersect_assignment_after_match.csv", + "donor_match/test2/test2_score_record.csv", + "donor_match/test3", + "donor_match/test3/freemuxlet_vs_bff_consensuscall", + "donor_match/test3/freemuxlet_vs_bff_consensuscall/test3_freemuxlet_vs_bff_consensuscall_all_assignment_after_match.csv", + "donor_match/test3/freemuxlet_vs_bff_consensuscall/test3_freemuxlet_vs_bff_consensuscall_concordance_heatmap.png", + "donor_match/test3/freemuxlet_vs_bff_consensuscall/test3_freemuxlet_vs_bff_consensuscall_correlation_res.csv", + "donor_match/test3/freemuxlet_vs_bff_consensuscall/test3_freemuxlet_vs_bff_consensuscall_donor_match.csv", + "donor_match/test3/freemuxlet_vs_bff_consensuscall/test3_freemuxlet_vs_bff_consensuscall_intersect_assignment_after_match.csv", + "donor_match/test3/freemuxlet_vs_bff_raw", + "donor_match/test3/freemuxlet_vs_bff_raw/test3_freemuxlet_vs_bff_raw_all_assignment_after_match.csv", + "donor_match/test3/freemuxlet_vs_bff_raw/test3_freemuxlet_vs_bff_raw_concordance_heatmap.png", + "donor_match/test3/freemuxlet_vs_bff_raw/test3_freemuxlet_vs_bff_raw_correlation_res.csv", + "donor_match/test3/freemuxlet_vs_bff_raw/test3_freemuxlet_vs_bff_raw_donor_match.csv", + "donor_match/test3/freemuxlet_vs_bff_raw/test3_freemuxlet_vs_bff_raw_intersect_assignment_after_match.csv", + "donor_match/test3/freemuxlet_vs_gmmdemux", + "donor_match/test3/freemuxlet_vs_gmmdemux/test3_freemuxlet_vs_gmmdemux_all_assignment_after_match.csv", + "donor_match/test3/freemuxlet_vs_gmmdemux/test3_freemuxlet_vs_gmmdemux_concordance_heatmap.png", + "donor_match/test3/freemuxlet_vs_gmmdemux/test3_freemuxlet_vs_gmmdemux_correlation_res.csv", + "donor_match/test3/freemuxlet_vs_gmmdemux/test3_freemuxlet_vs_gmmdemux_donor_match.csv", + "donor_match/test3/freemuxlet_vs_gmmdemux/test3_freemuxlet_vs_gmmdemux_intersect_assignment_after_match.csv", + "donor_match/test3/freemuxlet_vs_hasheddrops", + "donor_match/test3/freemuxlet_vs_hasheddrops/test3_freemuxlet_vs_hasheddrops_all_assignment_after_match.csv", + "donor_match/test3/freemuxlet_vs_hasheddrops/test3_freemuxlet_vs_hasheddrops_concordance_heatmap.png", + "donor_match/test3/freemuxlet_vs_hasheddrops/test3_freemuxlet_vs_hasheddrops_correlation_res.csv", + "donor_match/test3/freemuxlet_vs_hasheddrops/test3_freemuxlet_vs_hasheddrops_donor_match.csv", + "donor_match/test3/freemuxlet_vs_hasheddrops/test3_freemuxlet_vs_hasheddrops_intersect_assignment_after_match.csv", + "donor_match/test3/souporcell_vs_bff_consensuscall", + "donor_match/test3/souporcell_vs_bff_consensuscall/test3_souporcell_vs_bff_consensuscall_all_assignment_after_match.csv", + "donor_match/test3/souporcell_vs_bff_consensuscall/test3_souporcell_vs_bff_consensuscall_concordance_heatmap.png", + "donor_match/test3/souporcell_vs_bff_consensuscall/test3_souporcell_vs_bff_consensuscall_correlation_res.csv", + "donor_match/test3/souporcell_vs_bff_consensuscall/test3_souporcell_vs_bff_consensuscall_donor_match.csv", + "donor_match/test3/souporcell_vs_bff_consensuscall/test3_souporcell_vs_bff_consensuscall_intersect_assignment_after_match.csv", + "donor_match/test3/souporcell_vs_bff_raw", + "donor_match/test3/souporcell_vs_bff_raw/test3_souporcell_vs_bff_raw_all_assignment_after_match.csv", + "donor_match/test3/souporcell_vs_bff_raw/test3_souporcell_vs_bff_raw_concordance_heatmap.png", + "donor_match/test3/souporcell_vs_bff_raw/test3_souporcell_vs_bff_raw_correlation_res.csv", + "donor_match/test3/souporcell_vs_bff_raw/test3_souporcell_vs_bff_raw_donor_match.csv", + "donor_match/test3/souporcell_vs_bff_raw/test3_souporcell_vs_bff_raw_intersect_assignment_after_match.csv", + "donor_match/test3/souporcell_vs_gmmdemux", + "donor_match/test3/souporcell_vs_gmmdemux/test3_souporcell_vs_gmmdemux_all_assignment_after_match.csv", + "donor_match/test3/souporcell_vs_gmmdemux/test3_souporcell_vs_gmmdemux_concordance_heatmap.png", + "donor_match/test3/souporcell_vs_gmmdemux/test3_souporcell_vs_gmmdemux_correlation_res.csv", + "donor_match/test3/souporcell_vs_gmmdemux/test3_souporcell_vs_gmmdemux_donor_match.csv", + "donor_match/test3/souporcell_vs_gmmdemux/test3_souporcell_vs_gmmdemux_intersect_assignment_after_match.csv", + "donor_match/test3/souporcell_vs_hasheddrops", + "donor_match/test3/souporcell_vs_hasheddrops/test3_souporcell_vs_hasheddrops_all_assignment_after_match.csv", + "donor_match/test3/souporcell_vs_hasheddrops/test3_souporcell_vs_hasheddrops_concordance_heatmap.png", + "donor_match/test3/souporcell_vs_hasheddrops/test3_souporcell_vs_hasheddrops_correlation_res.csv", + "donor_match/test3/souporcell_vs_hasheddrops/test3_souporcell_vs_hasheddrops_donor_match.csv", + "donor_match/test3/souporcell_vs_hasheddrops/test3_souporcell_vs_hasheddrops_intersect_assignment_after_match.csv", + "donor_match/test3/test3_best_all_assignment_after_match.csv", + "donor_match/test3/test3_best_donor_match.csv", + "donor_match/test3/test3_best_intersect_assignment_after_match.csv", + "donor_match/test3/test3_score_record.csv", + "find_variants", + "find_variants/test1", + "find_variants/test1/hto-1", + "find_variants/test1/hto-1/test1_hto-1_informative_variants.csv", + "find_variants/test1/hto-1/test1_hto-1_matched_gt.csv", + "find_variants/test1/hto-1/test1_hto-1_unmatched_gt.csv", + "find_variants/test1/hto-2", + "find_variants/test1/hto-2/test1_hto-2_informative_variants.csv", + "find_variants/test1/hto-2/test1_hto-2_matched_gt.csv", + "find_variants/test1/hto-2/test1_hto-2_unmatched_gt.csv", + "find_variants/test1/subset_gt_donors", + "find_variants/test1/subset_gt_donors/test1_donor_specific.vcf.gz", + "find_variants/test1/subset_gt_donors/test1_vireo.vcf.gz", + "find_variants/test1/test1_all_representative_variants.csv", + "find_variants/test1/test1_donor_specific_variants.csv", + "find_variants/test1/test1_donor_specific_variants_upset.png", + "find_variants/test1/test1_vireo_variants.csv", + "find_variants/test2", + "find_variants/test2/hto-1", + "find_variants/test2/hto-1/test2_hto-1_informative_variants.csv", + "find_variants/test2/hto-1/test2_hto-1_matched_gt.csv", + "find_variants/test2/hto-1/test2_hto-1_unmatched_gt.csv", + "find_variants/test2/hto-2", + "find_variants/test2/hto-2/test2_hto-2_informative_variants.csv", + "find_variants/test2/hto-2/test2_hto-2_matched_gt.csv", + "find_variants/test2/hto-2/test2_hto-2_unmatched_gt.csv", + "find_variants/test2/subset_gt_donors", + "find_variants/test2/subset_gt_donors/test2_donor_specific.vcf.gz", + "find_variants/test2/subset_gt_donors/test2_vireo.vcf.gz", + "find_variants/test2/test2_all_representative_variants.csv", + "find_variants/test2/test2_donor_specific_variants.csv", + "find_variants/test2/test2_donor_specific_variants_upset.png", + "find_variants/test2/test2_vireo_variants.csv", + "find_variants/test3", + "find_variants/test3/hto-1", + "find_variants/test3/hto-1/test3_hto-1_informative_variants.csv", + "find_variants/test3/hto-1/test3_hto-1_matched_gt.csv", + "find_variants/test3/hto-1/test3_hto-1_unmatched_gt.csv", + "find_variants/test3/hto-2", + "find_variants/test3/hto-2/test3_hto-2_informative_variants.csv", + "find_variants/test3/hto-2/test3_hto-2_matched_gt.csv", + "find_variants/test3/hto-2/test3_hto-2_unmatched_gt.csv", + "find_variants/test3/subset_gt_donors", + "find_variants/test3/subset_gt_donors/test3_donor_specific.vcf.gz", + "find_variants/test3/subset_gt_donors/test3_vireo.vcf.gz", + "find_variants/test3/test3_all_representative_variants.csv", + "find_variants/test3/test3_donor_specific_variants.csv", + "find_variants/test3/test3_donor_specific_variants_upset.png", + "find_variants/test3/test3_vireo_variants.csv", + "multiqc", + "multiqc/multiqc_data", + "multiqc/multiqc_data/llms-full.txt", + "multiqc/multiqc_data/multiqc.log", + "multiqc/multiqc_data/multiqc.parquet", + "multiqc/multiqc_data/multiqc_citations.txt", + "multiqc/multiqc_data/multiqc_data.json", + "multiqc/multiqc_data/multiqc_software_versions.txt", + "multiqc/multiqc_data/multiqc_sources.txt", + "multiqc/multiqc_report.html", + "pipeline_info", + "pipeline_info/nf_core_hadge_software_mqc_versions.yml" + ], + [ + "test1_freemuxlet_vs_bff_consensuscall_all_assignment_after_match.csv:md5,c5552086e271152756067aa99ab77fa1", + "test1_freemuxlet_vs_bff_consensuscall_concordance_heatmap.png:md5,5c3e82573df24e781d4f422bc3b6925a", + "test1_freemuxlet_vs_bff_consensuscall_correlation_res.csv:md5,5f2f2076175e313f10396b32fac4c26c", + "test1_freemuxlet_vs_bff_consensuscall_donor_match.csv:md5,b92352374bf4b32f7e3a1b032a4c8b5f", + "test1_freemuxlet_vs_bff_consensuscall_intersect_assignment_after_match.csv:md5,7827c80fbdb30275350dfea4e6ee3a46", + "test1_freemuxlet_vs_bff_raw_all_assignment_after_match.csv:md5,f64080fd544ffd711634e06719c2a574", + "test1_freemuxlet_vs_bff_raw_concordance_heatmap.png:md5,5c3e82573df24e781d4f422bc3b6925a", + "test1_freemuxlet_vs_bff_raw_correlation_res.csv:md5,5f2f2076175e313f10396b32fac4c26c", + "test1_freemuxlet_vs_bff_raw_donor_match.csv:md5,b92352374bf4b32f7e3a1b032a4c8b5f", + "test1_freemuxlet_vs_bff_raw_intersect_assignment_after_match.csv:md5,51b6c078396c57902e610e8497b52ec6", + "test1_freemuxlet_vs_gmmdemux_all_assignment_after_match.csv:md5,843151ffa9d30820ade39c0fbe3a5150", + "test1_freemuxlet_vs_gmmdemux_concordance_heatmap.png:md5,5c3e82573df24e781d4f422bc3b6925a", + "test1_freemuxlet_vs_gmmdemux_correlation_res.csv:md5,5f2f2076175e313f10396b32fac4c26c", + "test1_freemuxlet_vs_gmmdemux_donor_match.csv:md5,b92352374bf4b32f7e3a1b032a4c8b5f", + "test1_freemuxlet_vs_gmmdemux_intersect_assignment_after_match.csv:md5,e55c2902ef5d84a00e0df8c814d0b572", + "test1_freemuxlet_vs_hasheddrops_all_assignment_after_match.csv:md5,52d09d2ed176e113c88b8325edd9cb79", + "test1_freemuxlet_vs_hasheddrops_concordance_heatmap.png:md5,5c3e82573df24e781d4f422bc3b6925a", + "test1_freemuxlet_vs_hasheddrops_correlation_res.csv:md5,5f2f2076175e313f10396b32fac4c26c", + "test1_freemuxlet_vs_hasheddrops_donor_match.csv:md5,b92352374bf4b32f7e3a1b032a4c8b5f", + "test1_freemuxlet_vs_hasheddrops_intersect_assignment_after_match.csv:md5,78e11f774a11260199c5262b551c4618", + "test1_souporcell_vs_bff_consensuscall_all_assignment_after_match.csv:md5,0453e72b042219598602022c78f500b0", + "test1_souporcell_vs_bff_consensuscall_concordance_heatmap.png:md5,d48df9ebb22dc1f2616511ad0405cdfd", + "test1_souporcell_vs_bff_consensuscall_correlation_res.csv:md5,7e632ed518a1e6f02ed1ea25a28a4e45", + "test1_souporcell_vs_bff_consensuscall_donor_match.csv:md5,d1feea9baad73b5bc106472a226cbbf2", + "test1_souporcell_vs_bff_consensuscall_intersect_assignment_after_match.csv:md5,2f98b7e021001d4093324dad82177d9d", + "test1_souporcell_vs_bff_raw_all_assignment_after_match.csv:md5,9667ceb1476d7ef9155042a2bfea590a", + "test1_souporcell_vs_bff_raw_concordance_heatmap.png:md5,d48df9ebb22dc1f2616511ad0405cdfd", + "test1_souporcell_vs_bff_raw_correlation_res.csv:md5,7e632ed518a1e6f02ed1ea25a28a4e45", + "test1_souporcell_vs_bff_raw_donor_match.csv:md5,d1feea9baad73b5bc106472a226cbbf2", + "test1_souporcell_vs_bff_raw_intersect_assignment_after_match.csv:md5,245e79ea38948063f671d8553afd2d55", + "test1_souporcell_vs_gmmdemux_all_assignment_after_match.csv:md5,ab32c63ef3a5e5258b399aa2a193ffe4", + "test1_souporcell_vs_gmmdemux_concordance_heatmap.png:md5,d48df9ebb22dc1f2616511ad0405cdfd", + "test1_souporcell_vs_gmmdemux_correlation_res.csv:md5,7e632ed518a1e6f02ed1ea25a28a4e45", + "test1_souporcell_vs_gmmdemux_donor_match.csv:md5,d1feea9baad73b5bc106472a226cbbf2", + "test1_souporcell_vs_gmmdemux_intersect_assignment_after_match.csv:md5,e10f4fc3d56187b82fefa95b1a6ddfca", + "test1_souporcell_vs_hasheddrops_all_assignment_after_match.csv:md5,cc8f74509dc0a10c488c2a1e5e064644", + "test1_souporcell_vs_hasheddrops_concordance_heatmap.png:md5,d48df9ebb22dc1f2616511ad0405cdfd", + "test1_souporcell_vs_hasheddrops_correlation_res.csv:md5,7e632ed518a1e6f02ed1ea25a28a4e45", + "test1_souporcell_vs_hasheddrops_donor_match.csv:md5,d1feea9baad73b5bc106472a226cbbf2", + "test1_souporcell_vs_hasheddrops_intersect_assignment_after_match.csv:md5,f31968c6aa78e9d8d4c43b99035f2cf3", + "test1_best_all_assignment_after_match.csv:md5,0453e72b042219598602022c78f500b0", + "test1_best_donor_match.csv:md5,d1feea9baad73b5bc106472a226cbbf2", + "test1_best_intersect_assignment_after_match.csv:md5,2f98b7e021001d4093324dad82177d9d", + "test1_score_record.csv:md5,19e6d7aa257ab96338c70bb1e69442b3", + "test2_freemuxlet_vs_bff_consensuscall_all_assignment_after_match.csv:md5,c5552086e271152756067aa99ab77fa1", + "test2_freemuxlet_vs_bff_consensuscall_concordance_heatmap.png:md5,5c3e82573df24e781d4f422bc3b6925a", + "test2_freemuxlet_vs_bff_consensuscall_correlation_res.csv:md5,5f2f2076175e313f10396b32fac4c26c", + "test2_freemuxlet_vs_bff_consensuscall_donor_match.csv:md5,b92352374bf4b32f7e3a1b032a4c8b5f", + "test2_freemuxlet_vs_bff_consensuscall_intersect_assignment_after_match.csv:md5,7827c80fbdb30275350dfea4e6ee3a46", + "test2_freemuxlet_vs_bff_raw_all_assignment_after_match.csv:md5,f64080fd544ffd711634e06719c2a574", + "test2_freemuxlet_vs_bff_raw_concordance_heatmap.png:md5,5c3e82573df24e781d4f422bc3b6925a", + "test2_freemuxlet_vs_bff_raw_correlation_res.csv:md5,5f2f2076175e313f10396b32fac4c26c", + "test2_freemuxlet_vs_bff_raw_donor_match.csv:md5,b92352374bf4b32f7e3a1b032a4c8b5f", + "test2_freemuxlet_vs_bff_raw_intersect_assignment_after_match.csv:md5,51b6c078396c57902e610e8497b52ec6", + "test2_freemuxlet_vs_gmmdemux_all_assignment_after_match.csv:md5,843151ffa9d30820ade39c0fbe3a5150", + "test2_freemuxlet_vs_gmmdemux_concordance_heatmap.png:md5,5c3e82573df24e781d4f422bc3b6925a", + "test2_freemuxlet_vs_gmmdemux_correlation_res.csv:md5,5f2f2076175e313f10396b32fac4c26c", + "test2_freemuxlet_vs_gmmdemux_donor_match.csv:md5,b92352374bf4b32f7e3a1b032a4c8b5f", + "test2_freemuxlet_vs_gmmdemux_intersect_assignment_after_match.csv:md5,e55c2902ef5d84a00e0df8c814d0b572", + "test2_freemuxlet_vs_hasheddrops_all_assignment_after_match.csv:md5,52d09d2ed176e113c88b8325edd9cb79", + "test2_freemuxlet_vs_hasheddrops_concordance_heatmap.png:md5,5c3e82573df24e781d4f422bc3b6925a", + "test2_freemuxlet_vs_hasheddrops_correlation_res.csv:md5,5f2f2076175e313f10396b32fac4c26c", + "test2_freemuxlet_vs_hasheddrops_donor_match.csv:md5,b92352374bf4b32f7e3a1b032a4c8b5f", + "test2_freemuxlet_vs_hasheddrops_intersect_assignment_after_match.csv:md5,78e11f774a11260199c5262b551c4618", + "test2_souporcell_vs_bff_consensuscall_all_assignment_after_match.csv:md5,0453e72b042219598602022c78f500b0", + "test2_souporcell_vs_bff_consensuscall_concordance_heatmap.png:md5,d48df9ebb22dc1f2616511ad0405cdfd", + "test2_souporcell_vs_bff_consensuscall_correlation_res.csv:md5,7e632ed518a1e6f02ed1ea25a28a4e45", + "test2_souporcell_vs_bff_consensuscall_donor_match.csv:md5,d1feea9baad73b5bc106472a226cbbf2", + "test2_souporcell_vs_bff_consensuscall_intersect_assignment_after_match.csv:md5,2f98b7e021001d4093324dad82177d9d", + "test2_souporcell_vs_bff_raw_all_assignment_after_match.csv:md5,9667ceb1476d7ef9155042a2bfea590a", + "test2_souporcell_vs_bff_raw_concordance_heatmap.png:md5,d48df9ebb22dc1f2616511ad0405cdfd", + "test2_souporcell_vs_bff_raw_correlation_res.csv:md5,7e632ed518a1e6f02ed1ea25a28a4e45", + "test2_souporcell_vs_bff_raw_donor_match.csv:md5,d1feea9baad73b5bc106472a226cbbf2", + "test2_souporcell_vs_bff_raw_intersect_assignment_after_match.csv:md5,245e79ea38948063f671d8553afd2d55", + "test2_souporcell_vs_gmmdemux_all_assignment_after_match.csv:md5,ab32c63ef3a5e5258b399aa2a193ffe4", + "test2_souporcell_vs_gmmdemux_concordance_heatmap.png:md5,d48df9ebb22dc1f2616511ad0405cdfd", + "test2_souporcell_vs_gmmdemux_correlation_res.csv:md5,7e632ed518a1e6f02ed1ea25a28a4e45", + "test2_souporcell_vs_gmmdemux_donor_match.csv:md5,d1feea9baad73b5bc106472a226cbbf2", + "test2_souporcell_vs_gmmdemux_intersect_assignment_after_match.csv:md5,e10f4fc3d56187b82fefa95b1a6ddfca", + "test2_souporcell_vs_hasheddrops_all_assignment_after_match.csv:md5,cc8f74509dc0a10c488c2a1e5e064644", + "test2_souporcell_vs_hasheddrops_concordance_heatmap.png:md5,d48df9ebb22dc1f2616511ad0405cdfd", + "test2_souporcell_vs_hasheddrops_correlation_res.csv:md5,7e632ed518a1e6f02ed1ea25a28a4e45", + "test2_souporcell_vs_hasheddrops_donor_match.csv:md5,d1feea9baad73b5bc106472a226cbbf2", + "test2_souporcell_vs_hasheddrops_intersect_assignment_after_match.csv:md5,f31968c6aa78e9d8d4c43b99035f2cf3", + "test2_best_all_assignment_after_match.csv:md5,0453e72b042219598602022c78f500b0", + "test2_best_donor_match.csv:md5,d1feea9baad73b5bc106472a226cbbf2", + "test2_best_intersect_assignment_after_match.csv:md5,2f98b7e021001d4093324dad82177d9d", + "test2_score_record.csv:md5,19e6d7aa257ab96338c70bb1e69442b3", + "test3_freemuxlet_vs_bff_consensuscall_all_assignment_after_match.csv:md5,c5552086e271152756067aa99ab77fa1", + "test3_freemuxlet_vs_bff_consensuscall_concordance_heatmap.png:md5,5c3e82573df24e781d4f422bc3b6925a", + "test3_freemuxlet_vs_bff_consensuscall_correlation_res.csv:md5,5f2f2076175e313f10396b32fac4c26c", + "test3_freemuxlet_vs_bff_consensuscall_donor_match.csv:md5,b92352374bf4b32f7e3a1b032a4c8b5f", + "test3_freemuxlet_vs_bff_consensuscall_intersect_assignment_after_match.csv:md5,7827c80fbdb30275350dfea4e6ee3a46", + "test3_freemuxlet_vs_bff_raw_all_assignment_after_match.csv:md5,f64080fd544ffd711634e06719c2a574", + "test3_freemuxlet_vs_bff_raw_concordance_heatmap.png:md5,5c3e82573df24e781d4f422bc3b6925a", + "test3_freemuxlet_vs_bff_raw_correlation_res.csv:md5,5f2f2076175e313f10396b32fac4c26c", + "test3_freemuxlet_vs_bff_raw_donor_match.csv:md5,b92352374bf4b32f7e3a1b032a4c8b5f", + "test3_freemuxlet_vs_bff_raw_intersect_assignment_after_match.csv:md5,51b6c078396c57902e610e8497b52ec6", + "test3_freemuxlet_vs_gmmdemux_all_assignment_after_match.csv:md5,843151ffa9d30820ade39c0fbe3a5150", + "test3_freemuxlet_vs_gmmdemux_concordance_heatmap.png:md5,5c3e82573df24e781d4f422bc3b6925a", + "test3_freemuxlet_vs_gmmdemux_correlation_res.csv:md5,5f2f2076175e313f10396b32fac4c26c", + "test3_freemuxlet_vs_gmmdemux_donor_match.csv:md5,b92352374bf4b32f7e3a1b032a4c8b5f", + "test3_freemuxlet_vs_gmmdemux_intersect_assignment_after_match.csv:md5,e55c2902ef5d84a00e0df8c814d0b572", + "test3_freemuxlet_vs_hasheddrops_all_assignment_after_match.csv:md5,52d09d2ed176e113c88b8325edd9cb79", + "test3_freemuxlet_vs_hasheddrops_concordance_heatmap.png:md5,5c3e82573df24e781d4f422bc3b6925a", + "test3_freemuxlet_vs_hasheddrops_correlation_res.csv:md5,5f2f2076175e313f10396b32fac4c26c", + "test3_freemuxlet_vs_hasheddrops_donor_match.csv:md5,b92352374bf4b32f7e3a1b032a4c8b5f", + "test3_freemuxlet_vs_hasheddrops_intersect_assignment_after_match.csv:md5,78e11f774a11260199c5262b551c4618", + "test3_souporcell_vs_bff_consensuscall_all_assignment_after_match.csv:md5,0453e72b042219598602022c78f500b0", + "test3_souporcell_vs_bff_consensuscall_concordance_heatmap.png:md5,d48df9ebb22dc1f2616511ad0405cdfd", + "test3_souporcell_vs_bff_consensuscall_correlation_res.csv:md5,7e632ed518a1e6f02ed1ea25a28a4e45", + "test3_souporcell_vs_bff_consensuscall_donor_match.csv:md5,d1feea9baad73b5bc106472a226cbbf2", + "test3_souporcell_vs_bff_consensuscall_intersect_assignment_after_match.csv:md5,2f98b7e021001d4093324dad82177d9d", + "test3_souporcell_vs_bff_raw_all_assignment_after_match.csv:md5,9667ceb1476d7ef9155042a2bfea590a", + "test3_souporcell_vs_bff_raw_concordance_heatmap.png:md5,d48df9ebb22dc1f2616511ad0405cdfd", + "test3_souporcell_vs_bff_raw_correlation_res.csv:md5,7e632ed518a1e6f02ed1ea25a28a4e45", + "test3_souporcell_vs_bff_raw_donor_match.csv:md5,d1feea9baad73b5bc106472a226cbbf2", + "test3_souporcell_vs_bff_raw_intersect_assignment_after_match.csv:md5,245e79ea38948063f671d8553afd2d55", + "test3_souporcell_vs_gmmdemux_all_assignment_after_match.csv:md5,ab32c63ef3a5e5258b399aa2a193ffe4", + "test3_souporcell_vs_gmmdemux_concordance_heatmap.png:md5,d48df9ebb22dc1f2616511ad0405cdfd", + "test3_souporcell_vs_gmmdemux_correlation_res.csv:md5,7e632ed518a1e6f02ed1ea25a28a4e45", + "test3_souporcell_vs_gmmdemux_donor_match.csv:md5,d1feea9baad73b5bc106472a226cbbf2", + "test3_souporcell_vs_gmmdemux_intersect_assignment_after_match.csv:md5,e10f4fc3d56187b82fefa95b1a6ddfca", + "test3_souporcell_vs_hasheddrops_all_assignment_after_match.csv:md5,cc8f74509dc0a10c488c2a1e5e064644", + "test3_souporcell_vs_hasheddrops_concordance_heatmap.png:md5,d48df9ebb22dc1f2616511ad0405cdfd", + "test3_souporcell_vs_hasheddrops_correlation_res.csv:md5,7e632ed518a1e6f02ed1ea25a28a4e45", + "test3_souporcell_vs_hasheddrops_donor_match.csv:md5,d1feea9baad73b5bc106472a226cbbf2", + "test3_souporcell_vs_hasheddrops_intersect_assignment_after_match.csv:md5,f31968c6aa78e9d8d4c43b99035f2cf3", + "test3_best_all_assignment_after_match.csv:md5,0453e72b042219598602022c78f500b0", + "test3_best_donor_match.csv:md5,d1feea9baad73b5bc106472a226cbbf2", + "test3_best_intersect_assignment_after_match.csv:md5,2f98b7e021001d4093324dad82177d9d", + "test3_score_record.csv:md5,19e6d7aa257ab96338c70bb1e69442b3", + "test1_hto-1_informative_variants.csv:md5,7ba6e70ac79adf74d2aa85f723b9f0d6", + "test1_hto-1_matched_gt.csv:md5,bf3b7d684f30153fa603dbddde635583", + "test1_hto-1_unmatched_gt.csv:md5,964ec72473742d51e7e20788bbaa412c", + "test1_hto-2_informative_variants.csv:md5,f641c48375833e592aa6f8c102ac1160", + "test1_hto-2_matched_gt.csv:md5,fc8bdfc73587eaf63925f658b6fb1c5c", + "test1_hto-2_unmatched_gt.csv:md5,e53bfe5050c7197a53acec1f30f2daec", + "test1_all_representative_variants.csv:md5,93cf3fde5b1236b83abd53369b250f64", + "test1_donor_specific_variants.csv:md5,bd137190cb87b1ad68a7283571e9a4b6", + "test1_donor_specific_variants_upset.png:md5,5d0abe01aa07abd018d20abe8e2e99ab", + "test1_vireo_variants.csv:md5,34bf574b4fe7c16db53a86f5cf6cdbf3", + "test2_hto-1_informative_variants.csv:md5,7ba6e70ac79adf74d2aa85f723b9f0d6", + "test2_hto-1_matched_gt.csv:md5,bf3b7d684f30153fa603dbddde635583", + "test2_hto-1_unmatched_gt.csv:md5,964ec72473742d51e7e20788bbaa412c", + "test2_hto-2_informative_variants.csv:md5,f641c48375833e592aa6f8c102ac1160", + "test2_hto-2_matched_gt.csv:md5,fc8bdfc73587eaf63925f658b6fb1c5c", + "test2_hto-2_unmatched_gt.csv:md5,e53bfe5050c7197a53acec1f30f2daec", + "test2_all_representative_variants.csv:md5,93cf3fde5b1236b83abd53369b250f64", + "test2_donor_specific_variants.csv:md5,bd137190cb87b1ad68a7283571e9a4b6", + "test2_donor_specific_variants_upset.png:md5,5d0abe01aa07abd018d20abe8e2e99ab", + "test2_vireo_variants.csv:md5,34bf574b4fe7c16db53a86f5cf6cdbf3", + "test3_hto-1_informative_variants.csv:md5,7ba6e70ac79adf74d2aa85f723b9f0d6", + "test3_hto-1_matched_gt.csv:md5,bf3b7d684f30153fa603dbddde635583", + "test3_hto-1_unmatched_gt.csv:md5,964ec72473742d51e7e20788bbaa412c", + "test3_hto-2_informative_variants.csv:md5,f641c48375833e592aa6f8c102ac1160", + "test3_hto-2_matched_gt.csv:md5,fc8bdfc73587eaf63925f658b6fb1c5c", + "test3_hto-2_unmatched_gt.csv:md5,e53bfe5050c7197a53acec1f30f2daec", + "test3_all_representative_variants.csv:md5,93cf3fde5b1236b83abd53369b250f64", + "test3_donor_specific_variants.csv:md5,bd137190cb87b1ad68a7283571e9a4b6", + "test3_donor_specific_variants_upset.png:md5,5d0abe01aa07abd018d20abe8e2e99ab", + "test3_vireo_variants.csv:md5,34bf574b4fe7c16db53a86f5cf6cdbf3", + "multiqc_citations.txt:md5,4c806e63a283ec1b7e78cdae3a923d4f" + ], + [ + "test1_donor_specific.vcf.gz:md5,8c51c5ed9e880089a8532e30a659adac", + "test1_vireo.vcf.gz:md5,6e4bbad3892e84cd1dd53c8fc0579977", + "test2_donor_specific.vcf.gz:md5,8c51c5ed9e880089a8532e30a659adac", + "test2_vireo.vcf.gz:md5,6e4bbad3892e84cd1dd53c8fc0579977", + "test3_donor_specific.vcf.gz:md5,8c51c5ed9e880089a8532e30a659adac", + "test3_vireo.vcf.gz:md5,6e4bbad3892e84cd1dd53c8fc0579977" + ] + ], + "meta": { + "nf-test": "0.9.2", + "nextflow": "25.10.2" + }, + "timestamp": "2026-02-01T15:54:40.941507" + } +} \ No newline at end of file diff --git a/tests/test_genetic.nf.test b/tests/test_genetic.nf.test new file mode 100644 index 00000000..a248694c --- /dev/null +++ b/tests/test_genetic.nf.test @@ -0,0 +1,40 @@ +nextflow_pipeline { + + name "Test pipeline with genetic config" + script "../main.nf" + tag "pipeline" + tag "test_genetic" + profile "test_genetic" + + test("-profile test_genetic") { + + when { + params { + outdir = "$outputDir" + } + } + + then { + // stable_name: All files + folders in ${params.outdir}/ with a stable name + def stable_name = getAllFilesFromDir(params.outdir, relative: true, includeDir: true, ignore: ['pipeline_info/*.{html,json,txt}']) + // stable_path: All files in ${params.outdir}/ with stable content + def stable_path = getAllFilesFromDir(params.outdir, ignoreFile: 'tests/.nftignore') + // VCF files (cluster_genotypes.vcf md5 differs but unused in pipeline) + def vcf_files = getAllFilesFromDir(params.outdir, include: ['**/*.vcf{,.gz}'], ignore: ['genetic/souporcell/*/*/cluster_genotypes.vcf']) + + assertAll( + { assert workflow.success}, + { assert snapshot( + // pipeline versions.yml file for multiqc from which Nextflow version is removed because we test pipelines on multiple Nextflow versions + removeNextflowVersion("$outputDir/pipeline_info/nf_core_hadge_software_mqc_versions.yml"), + // All stable path name, with a relative path + stable_name, + // All files with stable contents + stable_path, + // VCF files + vcf_files.isEmpty() ? 'No VCF files' : vcf_files.collect { file -> file.getName() + ":md5," + path(file.toString()).vcf.variantsMD5 } + ).match() } + ) + } + } +} diff --git a/tests/test_genetic.nf.test.snap b/tests/test_genetic.nf.test.snap new file mode 100644 index 00000000..bf5ecda7 --- /dev/null +++ b/tests/test_genetic.nf.test.snap @@ -0,0 +1,272 @@ +{ + "-profile test_genetic": { + "content": [ + { + "CELLSNP_MODEA": { + "cellsnp": "1.2.3" + }, + "DONOR_MATCH": { + "r-base": "4.5.2", + "r-data.table": "1.17.8", + "r-pheatmap": "1.0.13", + "r-tidyverse": "2.0.0" + }, + "GENE_SUMMARY": { + "numpy": "2.3.5", + "pandas": "2.3.3", + "python": "3.14.2", + "scanpy": "1.11.5" + }, + "POPSCLE_DEMUXLET": { + "popscle demuxlet": 0.1 + }, + "POPSCLE_DSCPILEUP": { + "popscle dsc-pileup": 0.1 + }, + "POPSCLE_FREEMUXLET": { + "popscle": 0.1 + }, + "SAMTOOLS_INDEX": { + "samtools": 1.21 + }, + "SAMTOOLS_SORT": { + "samtools": 1.21 + }, + "SAMTOOLS_VIEW": { + "samtools": 1.21 + }, + "SOUPORCELL": { + "souporcell": 2.5 + }, + "UMITOOLS_DEDUP": { + "umitools": "1.1.5" + }, + "VIREO": { + "vireo": "v0.5.8" + }, + "Workflow": { + "nf-core/hadge": "v1.0.0dev" + } + }, + [ + "donor_match", + "donor_match/test1", + "donor_match/test1/freemuxlet_vs_souporcell", + "donor_match/test1/freemuxlet_vs_souporcell/test1_freemuxlet_vs_souporcell_concordance_heatmap.png", + "donor_match/test1/freemuxlet_vs_souporcell/test1_freemuxlet_vs_souporcell_correlation_res.csv", + "donor_match/test1/freemuxlet_vs_souporcell/test1_freemuxlet_vs_souporcell_donor_match.csv", + "donor_match/test2", + "donor_match/test2/freemuxlet_vs_souporcell", + "donor_match/test2/freemuxlet_vs_souporcell/test2_freemuxlet_vs_souporcell_concordance_heatmap.png", + "donor_match/test2/freemuxlet_vs_souporcell/test2_freemuxlet_vs_souporcell_correlation_res.csv", + "donor_match/test2/freemuxlet_vs_souporcell/test2_freemuxlet_vs_souporcell_donor_match.csv", + "donor_match/test3", + "donor_match/test3/freemuxlet_vs_souporcell", + "donor_match/test3/freemuxlet_vs_souporcell/test3_freemuxlet_vs_souporcell_concordance_heatmap.png", + "donor_match/test3/freemuxlet_vs_souporcell/test3_freemuxlet_vs_souporcell_correlation_res.csv", + "donor_match/test3/freemuxlet_vs_souporcell/test3_freemuxlet_vs_souporcell_donor_match.csv", + "genetic", + "genetic/popscle", + "genetic/popscle/demuxlet", + "genetic/popscle/demuxlet/test1", + "genetic/popscle/demuxlet/test1/test1.best", + "genetic/popscle/demuxlet/test2", + "genetic/popscle/demuxlet/test2/test2.best", + "genetic/popscle/demuxlet/test3", + "genetic/popscle/demuxlet/test3/test3.best", + "genetic/popscle/freemuxlet", + "genetic/popscle/freemuxlet/test1", + "genetic/popscle/freemuxlet/test1/test1.clust1.samples.gz", + "genetic/popscle/freemuxlet/test1/test1.clust1.vcf.gz", + "genetic/popscle/freemuxlet/test1/test1.lmix", + "genetic/popscle/freemuxlet/test2", + "genetic/popscle/freemuxlet/test2/test2.clust1.samples.gz", + "genetic/popscle/freemuxlet/test2/test2.clust1.vcf.gz", + "genetic/popscle/freemuxlet/test2/test2.lmix", + "genetic/popscle/freemuxlet/test3", + "genetic/popscle/freemuxlet/test3/test3.clust1.samples.gz", + "genetic/popscle/freemuxlet/test3/test3.clust1.vcf.gz", + "genetic/popscle/freemuxlet/test3/test3.lmix", + "genetic/souporcell", + "genetic/souporcell/test1", + "genetic/souporcell/test1/test1", + "genetic/souporcell/test1/test1/ambient_rna.txt", + "genetic/souporcell/test1/test1/cluster_genotypes.vcf", + "genetic/souporcell/test1/test1/clusters.tsv", + "genetic/souporcell/test2", + "genetic/souporcell/test2/test2", + "genetic/souporcell/test2/test2/ambient_rna.txt", + "genetic/souporcell/test2/test2/cluster_genotypes.vcf", + "genetic/souporcell/test2/test2/clusters.tsv", + "genetic/souporcell/test3", + "genetic/souporcell/test3/test3", + "genetic/souporcell/test3/test3/ambient_rna.txt", + "genetic/souporcell/test3/test3/cluster_genotypes.vcf", + "genetic/souporcell/test3/test3/clusters.tsv", + "genetic/summary", + "genetic/summary/test1", + "genetic/summary/test1/test1_genetic_overview_assignment.csv", + "genetic/summary/test1/test1_genetic_overview_classification.csv", + "genetic/summary/test1/test1_genetic_summary_assignment.csv", + "genetic/summary/test1/test1_genetic_summary_classification.csv", + "genetic/summary/test2", + "genetic/summary/test2/test2_genetic_overview_assignment.csv", + "genetic/summary/test2/test2_genetic_overview_classification.csv", + "genetic/summary/test2/test2_genetic_summary_assignment.csv", + "genetic/summary/test2/test2_genetic_summary_classification.csv", + "genetic/summary/test3", + "genetic/summary/test3/test3_genetic_overview_assignment.csv", + "genetic/summary/test3/test3_genetic_overview_classification.csv", + "genetic/summary/test3/test3_genetic_summary_assignment.csv", + "genetic/summary/test3/test3_genetic_summary_classification.csv", + "genetic/vireo", + "genetic/vireo/test1", + "genetic/vireo/test1/test1.base.vcf.gz", + "genetic/vireo/test1/test1.cells.vcf.gz", + "genetic/vireo/test1/test1.samples.tsv", + "genetic/vireo/test1/test1.tag.AD.mtx", + "genetic/vireo/test1/test1.tag.DP.mtx", + "genetic/vireo/test1/test1.tag.OTH.mtx", + "genetic/vireo/test1/test1_GT_donors.vireo.vcf.gz", + "genetic/vireo/test1/test1_donor_ids.tsv", + "genetic/vireo/test1/test1_filtered_variants.tsv", + "genetic/vireo/test1/test1_prob_doublet.tsv.gz", + "genetic/vireo/test1/test1_prob_singlet.tsv.gz", + "genetic/vireo/test1/test1_summary.tsv", + "genetic/vireo/test2", + "genetic/vireo/test2/test2.base.vcf.gz", + "genetic/vireo/test2/test2.cells.vcf.gz", + "genetic/vireo/test2/test2.samples.tsv", + "genetic/vireo/test2/test2.tag.AD.mtx", + "genetic/vireo/test2/test2.tag.DP.mtx", + "genetic/vireo/test2/test2.tag.OTH.mtx", + "genetic/vireo/test2/test2_GT_donors.vireo.vcf.gz", + "genetic/vireo/test2/test2_donor_ids.tsv", + "genetic/vireo/test2/test2_filtered_variants.tsv", + "genetic/vireo/test2/test2_prob_doublet.tsv.gz", + "genetic/vireo/test2/test2_prob_singlet.tsv.gz", + "genetic/vireo/test2/test2_summary.tsv", + "genetic/vireo/test3", + "genetic/vireo/test3/test3.base.vcf.gz", + "genetic/vireo/test3/test3.cells.vcf.gz", + "genetic/vireo/test3/test3.samples.tsv", + "genetic/vireo/test3/test3.tag.AD.mtx", + "genetic/vireo/test3/test3.tag.DP.mtx", + "genetic/vireo/test3/test3.tag.OTH.mtx", + "genetic/vireo/test3/test3_GT_donors.vireo.vcf.gz", + "genetic/vireo/test3/test3_donor_ids.tsv", + "genetic/vireo/test3/test3_filtered_variants.tsv", + "genetic/vireo/test3/test3_prob_doublet.tsv.gz", + "genetic/vireo/test3/test3_prob_singlet.tsv.gz", + "genetic/vireo/test3/test3_summary.tsv", + "multiqc", + "multiqc/multiqc_data", + "multiqc/multiqc_data/llms-full.txt", + "multiqc/multiqc_data/multiqc.log", + "multiqc/multiqc_data/multiqc.parquet", + "multiqc/multiqc_data/multiqc_citations.txt", + "multiqc/multiqc_data/multiqc_data.json", + "multiqc/multiqc_data/multiqc_software_versions.txt", + "multiqc/multiqc_data/multiqc_sources.txt", + "multiqc/multiqc_report.html", + "pipeline_info", + "pipeline_info/nf_core_hadge_software_mqc_versions.yml", + "summary", + "summary/test1", + "summary/test2", + "summary/test3" + ], + [ + "test1_freemuxlet_vs_souporcell_concordance_heatmap.png:md5,dd2e92af065ab80b23767797978d05b6", + "test1_freemuxlet_vs_souporcell_correlation_res.csv:md5,d21819b5db460f1535596d996af87976", + "test1_freemuxlet_vs_souporcell_donor_match.csv:md5,c27bb85b84d4f2e33948b4232cd4169d", + "test2_freemuxlet_vs_souporcell_concordance_heatmap.png:md5,dd2e92af065ab80b23767797978d05b6", + "test2_freemuxlet_vs_souporcell_correlation_res.csv:md5,d21819b5db460f1535596d996af87976", + "test2_freemuxlet_vs_souporcell_donor_match.csv:md5,c27bb85b84d4f2e33948b4232cd4169d", + "test3_freemuxlet_vs_souporcell_concordance_heatmap.png:md5,dd2e92af065ab80b23767797978d05b6", + "test3_freemuxlet_vs_souporcell_correlation_res.csv:md5,d21819b5db460f1535596d996af87976", + "test3_freemuxlet_vs_souporcell_donor_match.csv:md5,c27bb85b84d4f2e33948b4232cd4169d", + "test1.best:md5,1afa27ec59d7ea6b37e1b9ffbc6007e8", + "test2.best:md5,1afa27ec59d7ea6b37e1b9ffbc6007e8", + "test3.best:md5,1afa27ec59d7ea6b37e1b9ffbc6007e8", + "test1.clust1.samples.gz:md5,42f8c8ba281fccf6edaf6dbd30144210", + "test1.lmix:md5,d8be4c936ff56980a476d5ba82290b8e", + "test2.clust1.samples.gz:md5,42f8c8ba281fccf6edaf6dbd30144210", + "test2.lmix:md5,d8be4c936ff56980a476d5ba82290b8e", + "test3.clust1.samples.gz:md5,42f8c8ba281fccf6edaf6dbd30144210", + "test3.lmix:md5,d8be4c936ff56980a476d5ba82290b8e", + "clusters.tsv:md5,3443284fb0e0ace31b5da0f329568392", + "clusters.tsv:md5,3443284fb0e0ace31b5da0f329568392", + "clusters.tsv:md5,3443284fb0e0ace31b5da0f329568392", + "test1_genetic_overview_assignment.csv:md5,6e912c78c50b84deffdaf9fb5e8e3b20", + "test1_genetic_overview_classification.csv:md5,812a950b1eef4515079b2d7028b6658c", + "test1_genetic_summary_assignment.csv:md5,4a8a3b1c59ff015db2dba50eefde4f53", + "test1_genetic_summary_classification.csv:md5,938d7daf0ba588923fbc5b7c38bae90e", + "test2_genetic_overview_assignment.csv:md5,6e912c78c50b84deffdaf9fb5e8e3b20", + "test2_genetic_overview_classification.csv:md5,812a950b1eef4515079b2d7028b6658c", + "test2_genetic_summary_assignment.csv:md5,4a8a3b1c59ff015db2dba50eefde4f53", + "test2_genetic_summary_classification.csv:md5,938d7daf0ba588923fbc5b7c38bae90e", + "test3_genetic_overview_assignment.csv:md5,6e912c78c50b84deffdaf9fb5e8e3b20", + "test3_genetic_overview_classification.csv:md5,812a950b1eef4515079b2d7028b6658c", + "test3_genetic_summary_assignment.csv:md5,4a8a3b1c59ff015db2dba50eefde4f53", + "test3_genetic_summary_classification.csv:md5,938d7daf0ba588923fbc5b7c38bae90e", + "test1.base.vcf.gz:md5,36eccb4bd3e2130f395985e09a66a60d", + "test1.cells.vcf.gz:md5,b28d8acaf17777c7cf9e2c7d01d93a66", + "test1.samples.tsv:md5,9e488782c1bcd63c37ee3d1c4c0a9217", + "test1.tag.AD.mtx:md5,a76d9f63c6abdcab00f583fac269a98e", + "test1.tag.DP.mtx:md5,6f8e54bbaa8ad9dcaa42c15eff1e1351", + "test1.tag.OTH.mtx:md5,45e64ea3b3b8e8a7126690d9e775d501", + "test1_GT_donors.vireo.vcf.gz:md5,60bfaaf61b8e7f5fc2bd0950e576c3e5", + "test1_donor_ids.tsv:md5,5dd2633a15d99a044d7f6706912ed5bc", + "test1_filtered_variants.tsv:md5,d37231135d9e6e330c03ed0647fcaf0e", + "test1_prob_doublet.tsv.gz:md5,3dae1d20888be460d02782eb776043bb", + "test1_prob_singlet.tsv.gz:md5,90a717e3bdb50090c72fa73a18330e38", + "test1_summary.tsv:md5,94b0ce40cc86bbe7fbacebf98e685ee7", + "test2.base.vcf.gz:md5,36eccb4bd3e2130f395985e09a66a60d", + "test2.cells.vcf.gz:md5,b28d8acaf17777c7cf9e2c7d01d93a66", + "test2.samples.tsv:md5,9e488782c1bcd63c37ee3d1c4c0a9217", + "test2.tag.AD.mtx:md5,a76d9f63c6abdcab00f583fac269a98e", + "test2.tag.DP.mtx:md5,6f8e54bbaa8ad9dcaa42c15eff1e1351", + "test2.tag.OTH.mtx:md5,45e64ea3b3b8e8a7126690d9e775d501", + "test2_GT_donors.vireo.vcf.gz:md5,60bfaaf61b8e7f5fc2bd0950e576c3e5", + "test2_donor_ids.tsv:md5,5dd2633a15d99a044d7f6706912ed5bc", + "test2_filtered_variants.tsv:md5,d37231135d9e6e330c03ed0647fcaf0e", + "test2_prob_doublet.tsv.gz:md5,3dae1d20888be460d02782eb776043bb", + "test2_prob_singlet.tsv.gz:md5,90a717e3bdb50090c72fa73a18330e38", + "test2_summary.tsv:md5,94b0ce40cc86bbe7fbacebf98e685ee7", + "test3.base.vcf.gz:md5,36eccb4bd3e2130f395985e09a66a60d", + "test3.cells.vcf.gz:md5,b28d8acaf17777c7cf9e2c7d01d93a66", + "test3.samples.tsv:md5,9e488782c1bcd63c37ee3d1c4c0a9217", + "test3.tag.AD.mtx:md5,a76d9f63c6abdcab00f583fac269a98e", + "test3.tag.DP.mtx:md5,6f8e54bbaa8ad9dcaa42c15eff1e1351", + "test3.tag.OTH.mtx:md5,45e64ea3b3b8e8a7126690d9e775d501", + "test3_GT_donors.vireo.vcf.gz:md5,60bfaaf61b8e7f5fc2bd0950e576c3e5", + "test3_donor_ids.tsv:md5,5dd2633a15d99a044d7f6706912ed5bc", + "test3_filtered_variants.tsv:md5,d37231135d9e6e330c03ed0647fcaf0e", + "test3_prob_doublet.tsv.gz:md5,3dae1d20888be460d02782eb776043bb", + "test3_prob_singlet.tsv.gz:md5,90a717e3bdb50090c72fa73a18330e38", + "test3_summary.tsv:md5,94b0ce40cc86bbe7fbacebf98e685ee7", + "multiqc_citations.txt:md5,4c806e63a283ec1b7e78cdae3a923d4f" + ], + [ + "test1.clust1.vcf.gz:md5,8de6d22ca83fcc331f3d49d59973e5ac", + "test2.clust1.vcf.gz:md5,8de6d22ca83fcc331f3d49d59973e5ac", + "test3.clust1.vcf.gz:md5,8de6d22ca83fcc331f3d49d59973e5ac", + "test1.base.vcf.gz:md5,cb2af96582fa4efe089ec4e0b3f1644c", + "test1.cells.vcf.gz:md5,349e2d3b9abb1e346f0cebb621b9d7f4", + "test1_GT_donors.vireo.vcf.gz:md5,b92d531491566870c247cf3a122b2dc1", + "test2.base.vcf.gz:md5,cb2af96582fa4efe089ec4e0b3f1644c", + "test2.cells.vcf.gz:md5,349e2d3b9abb1e346f0cebb621b9d7f4", + "test2_GT_donors.vireo.vcf.gz:md5,b92d531491566870c247cf3a122b2dc1", + "test3.base.vcf.gz:md5,cb2af96582fa4efe089ec4e0b3f1644c", + "test3.cells.vcf.gz:md5,349e2d3b9abb1e346f0cebb621b9d7f4", + "test3_GT_donors.vireo.vcf.gz:md5,b92d531491566870c247cf3a122b2dc1" + ] + ], + "meta": { + "nf-test": "0.9.3", + "nextflow": "25.10.3" + }, + "timestamp": "2026-02-01T17:01:19.138557743" + } +} \ No newline at end of file diff --git a/tests/test_hashing.nf.test b/tests/test_hashing.nf.test new file mode 100644 index 00000000..658e9210 --- /dev/null +++ b/tests/test_hashing.nf.test @@ -0,0 +1,40 @@ +nextflow_pipeline { + + name "Test pipeline with hashing config" + script "../main.nf" + tag "pipeline" + tag "test_hashing" + profile "test_hashing" + + test("-profile test_hashing") { + + when { + params { + outdir = "$outputDir" + } + } + + then { + // stable_name: All files + folders in ${params.outdir}/ with a stable name + def stable_name = getAllFilesFromDir(params.outdir, relative: true, includeDir: true, ignore: ['pipeline_info/*.{html,json,txt}']) + // stable_path: All files in ${params.outdir}/ with stable content + def stable_path = getAllFilesFromDir(params.outdir, ignoreFile: 'tests/.nftignore') + // VCF files + def vcf_files = getAllFilesFromDir(params.outdir, include: ['**/*.vcf{,.gz}']) + + assertAll( + { assert workflow.success}, + { assert snapshot( + // pipeline versions.yml file for multiqc from which Nextflow version is removed because we test pipelines on multiple Nextflow versions + removeNextflowVersion("$outputDir/pipeline_info/nf_core_hadge_software_mqc_versions.yml"), + // All stable path name, with a relative path + stable_name, + // All files with stable contents + stable_path, + // VCF files + vcf_files.isEmpty() ? 'No VCF files' : vcf_files.collect { file -> file.getName() + ":md5," + path(file.toString()).vcf.variantsMD5 } + ).match() } + ) + } + } +} diff --git a/tests/test_hashing.nf.test.snap b/tests/test_hashing.nf.test.snap new file mode 100644 index 00000000..45a930b9 --- /dev/null +++ b/tests/test_hashing.nf.test.snap @@ -0,0 +1,791 @@ +{ + "-profile test_hashing": { + "content": [ + { + "BFF": { + "r-base": "4.3.3", + "r-seurat": "4.3.0.1", + "cellhashR": "1.0.3" + }, + "DONOR_MATCH": { + "r-base": "4.5.2", + "r-data.table": "1.17.8", + "r-pheatmap": "1.0.13", + "r-tidyverse": "2.0.0" + }, + "GMMDEMUX": { + "GMM-Demux": "0.2.2.3" + }, + "HASHEDDROPS": { + "r-base": "4.4.3", + "r-seurat": "5.3.0", + "dropletutils": "1.26.0" + }, + "HASHSOLO": { + "matplotlib": "3.10.5", + "pandas": "2.3.1", + "python": "3.12.11", + "scanpy": "1.11.2" + }, + "HASH_SUMMARY": { + "numpy": "2.3.5", + "pandas": "2.3.3", + "pegasusio": "0.10.0", + "python": "3.12.12", + "scanpy": "1.11.5" + }, + "HTODEMUX_VISUALIZATION": { + "r-base": "4.4.3", + "r-seurat": "5.3.0", + "r-ggplot2": "3.5.2" + }, + "MULTISEQDEMUX": { + "r-base": "4.4.3", + "r-seurat": "5.3.0" + }, + "PREPROCESSING_FOR_HTODEMUX_MULTISEQ": { + "r-base": "4.4.3", + "seurat": "5.3.0" + }, + "UNTAR_HTO": { + "untar": 1.34 + }, + "UNTAR_RNA": { + "untar": 1.34 + }, + "Workflow": { + "nf-core/hadge": "v1.0.0dev" + } + }, + [ + "donor_match", + "donor_match/test1", + "donor_match/test1/bff_raw_vs_bff_consensuscall", + "donor_match/test1/bff_raw_vs_bff_consensuscall/test1_bff_raw_vs_bff_consensuscall_concordance_heatmap.png", + "donor_match/test1/bff_raw_vs_bff_consensuscall/test1_bff_raw_vs_bff_consensuscall_correlation_res.csv", + "donor_match/test1/bff_raw_vs_bff_consensuscall/test1_bff_raw_vs_bff_consensuscall_donor_match.csv", + "donor_match/test1/gmmdemux_vs_bff_consensuscall", + "donor_match/test1/gmmdemux_vs_bff_consensuscall/test1_gmmdemux_vs_bff_consensuscall_concordance_heatmap.png", + "donor_match/test1/gmmdemux_vs_bff_consensuscall/test1_gmmdemux_vs_bff_consensuscall_correlation_res.csv", + "donor_match/test1/gmmdemux_vs_bff_consensuscall/test1_gmmdemux_vs_bff_consensuscall_donor_match.csv", + "donor_match/test1/gmmdemux_vs_bff_raw", + "donor_match/test1/gmmdemux_vs_bff_raw/test1_gmmdemux_vs_bff_raw_concordance_heatmap.png", + "donor_match/test1/gmmdemux_vs_bff_raw/test1_gmmdemux_vs_bff_raw_correlation_res.csv", + "donor_match/test1/gmmdemux_vs_bff_raw/test1_gmmdemux_vs_bff_raw_donor_match.csv", + "donor_match/test1/hasheddrops_vs_bff_consensuscall", + "donor_match/test1/hasheddrops_vs_bff_consensuscall/test1_hasheddrops_vs_bff_consensuscall_concordance_heatmap.png", + "donor_match/test1/hasheddrops_vs_bff_consensuscall/test1_hasheddrops_vs_bff_consensuscall_correlation_res.csv", + "donor_match/test1/hasheddrops_vs_bff_consensuscall/test1_hasheddrops_vs_bff_consensuscall_donor_match.csv", + "donor_match/test1/hasheddrops_vs_bff_raw", + "donor_match/test1/hasheddrops_vs_bff_raw/test1_hasheddrops_vs_bff_raw_concordance_heatmap.png", + "donor_match/test1/hasheddrops_vs_bff_raw/test1_hasheddrops_vs_bff_raw_correlation_res.csv", + "donor_match/test1/hasheddrops_vs_bff_raw/test1_hasheddrops_vs_bff_raw_donor_match.csv", + "donor_match/test1/hasheddrops_vs_gmmdemux", + "donor_match/test1/hasheddrops_vs_gmmdemux/test1_hasheddrops_vs_gmmdemux_concordance_heatmap.png", + "donor_match/test1/hasheddrops_vs_gmmdemux/test1_hasheddrops_vs_gmmdemux_correlation_res.csv", + "donor_match/test1/hasheddrops_vs_gmmdemux/test1_hasheddrops_vs_gmmdemux_donor_match.csv", + "donor_match/test1/hasheddrops_vs_htodemux", + "donor_match/test1/hasheddrops_vs_htodemux/test1_hasheddrops_vs_htodemux_concordance_heatmap.png", + "donor_match/test1/hasheddrops_vs_htodemux/test1_hasheddrops_vs_htodemux_correlation_res.csv", + "donor_match/test1/hasheddrops_vs_htodemux/test1_hasheddrops_vs_htodemux_donor_match.csv", + "donor_match/test1/hasheddrops_vs_multiseq", + "donor_match/test1/hasheddrops_vs_multiseq/test1_hasheddrops_vs_multiseq_concordance_heatmap.png", + "donor_match/test1/hasheddrops_vs_multiseq/test1_hasheddrops_vs_multiseq_correlation_res.csv", + "donor_match/test1/hasheddrops_vs_multiseq/test1_hasheddrops_vs_multiseq_donor_match.csv", + "donor_match/test1/hashsolo_vs_bff_consensuscall", + "donor_match/test1/hashsolo_vs_bff_consensuscall/test1_hashsolo_vs_bff_consensuscall_concordance_heatmap.png", + "donor_match/test1/hashsolo_vs_bff_consensuscall/test1_hashsolo_vs_bff_consensuscall_correlation_res.csv", + "donor_match/test1/hashsolo_vs_bff_consensuscall/test1_hashsolo_vs_bff_consensuscall_donor_match.csv", + "donor_match/test1/hashsolo_vs_bff_raw", + "donor_match/test1/hashsolo_vs_bff_raw/test1_hashsolo_vs_bff_raw_concordance_heatmap.png", + "donor_match/test1/hashsolo_vs_bff_raw/test1_hashsolo_vs_bff_raw_correlation_res.csv", + "donor_match/test1/hashsolo_vs_bff_raw/test1_hashsolo_vs_bff_raw_donor_match.csv", + "donor_match/test1/hashsolo_vs_gmmdemux", + "donor_match/test1/hashsolo_vs_gmmdemux/test1_hashsolo_vs_gmmdemux_concordance_heatmap.png", + "donor_match/test1/hashsolo_vs_gmmdemux/test1_hashsolo_vs_gmmdemux_correlation_res.csv", + "donor_match/test1/hashsolo_vs_gmmdemux/test1_hashsolo_vs_gmmdemux_donor_match.csv", + "donor_match/test1/hashsolo_vs_hasheddrops", + "donor_match/test1/hashsolo_vs_hasheddrops/test1_hashsolo_vs_hasheddrops_concordance_heatmap.png", + "donor_match/test1/hashsolo_vs_hasheddrops/test1_hashsolo_vs_hasheddrops_correlation_res.csv", + "donor_match/test1/hashsolo_vs_hasheddrops/test1_hashsolo_vs_hasheddrops_donor_match.csv", + "donor_match/test1/hashsolo_vs_htodemux", + "donor_match/test1/hashsolo_vs_htodemux/test1_hashsolo_vs_htodemux_concordance_heatmap.png", + "donor_match/test1/hashsolo_vs_htodemux/test1_hashsolo_vs_htodemux_correlation_res.csv", + "donor_match/test1/hashsolo_vs_htodemux/test1_hashsolo_vs_htodemux_donor_match.csv", + "donor_match/test1/hashsolo_vs_multiseq", + "donor_match/test1/hashsolo_vs_multiseq/test1_hashsolo_vs_multiseq_concordance_heatmap.png", + "donor_match/test1/hashsolo_vs_multiseq/test1_hashsolo_vs_multiseq_correlation_res.csv", + "donor_match/test1/hashsolo_vs_multiseq/test1_hashsolo_vs_multiseq_donor_match.csv", + "donor_match/test1/htodemux_vs_bff_consensuscall", + "donor_match/test1/htodemux_vs_bff_consensuscall/test1_htodemux_vs_bff_consensuscall_concordance_heatmap.png", + "donor_match/test1/htodemux_vs_bff_consensuscall/test1_htodemux_vs_bff_consensuscall_correlation_res.csv", + "donor_match/test1/htodemux_vs_bff_consensuscall/test1_htodemux_vs_bff_consensuscall_donor_match.csv", + "donor_match/test1/htodemux_vs_bff_raw", + "donor_match/test1/htodemux_vs_bff_raw/test1_htodemux_vs_bff_raw_concordance_heatmap.png", + "donor_match/test1/htodemux_vs_bff_raw/test1_htodemux_vs_bff_raw_correlation_res.csv", + "donor_match/test1/htodemux_vs_bff_raw/test1_htodemux_vs_bff_raw_donor_match.csv", + "donor_match/test1/htodemux_vs_gmmdemux", + "donor_match/test1/htodemux_vs_gmmdemux/test1_htodemux_vs_gmmdemux_concordance_heatmap.png", + "donor_match/test1/htodemux_vs_gmmdemux/test1_htodemux_vs_gmmdemux_correlation_res.csv", + "donor_match/test1/htodemux_vs_gmmdemux/test1_htodemux_vs_gmmdemux_donor_match.csv", + "donor_match/test1/multiseq_vs_bff_consensuscall", + "donor_match/test1/multiseq_vs_bff_consensuscall/test1_multiseq_vs_bff_consensuscall_concordance_heatmap.png", + "donor_match/test1/multiseq_vs_bff_consensuscall/test1_multiseq_vs_bff_consensuscall_correlation_res.csv", + "donor_match/test1/multiseq_vs_bff_consensuscall/test1_multiseq_vs_bff_consensuscall_donor_match.csv", + "donor_match/test1/multiseq_vs_bff_raw", + "donor_match/test1/multiseq_vs_bff_raw/test1_multiseq_vs_bff_raw_concordance_heatmap.png", + "donor_match/test1/multiseq_vs_bff_raw/test1_multiseq_vs_bff_raw_correlation_res.csv", + "donor_match/test1/multiseq_vs_bff_raw/test1_multiseq_vs_bff_raw_donor_match.csv", + "donor_match/test1/multiseq_vs_gmmdemux", + "donor_match/test1/multiseq_vs_gmmdemux/test1_multiseq_vs_gmmdemux_concordance_heatmap.png", + "donor_match/test1/multiseq_vs_gmmdemux/test1_multiseq_vs_gmmdemux_correlation_res.csv", + "donor_match/test1/multiseq_vs_gmmdemux/test1_multiseq_vs_gmmdemux_donor_match.csv", + "donor_match/test1/multiseq_vs_htodemux", + "donor_match/test1/multiseq_vs_htodemux/test1_multiseq_vs_htodemux_concordance_heatmap.png", + "donor_match/test1/multiseq_vs_htodemux/test1_multiseq_vs_htodemux_correlation_res.csv", + "donor_match/test1/multiseq_vs_htodemux/test1_multiseq_vs_htodemux_donor_match.csv", + "donor_match/test2", + "donor_match/test2/bff_raw_vs_bff_consensuscall", + "donor_match/test2/bff_raw_vs_bff_consensuscall/test2_bff_raw_vs_bff_consensuscall_concordance_heatmap.png", + "donor_match/test2/bff_raw_vs_bff_consensuscall/test2_bff_raw_vs_bff_consensuscall_correlation_res.csv", + "donor_match/test2/bff_raw_vs_bff_consensuscall/test2_bff_raw_vs_bff_consensuscall_donor_match.csv", + "donor_match/test2/gmmdemux_vs_bff_consensuscall", + "donor_match/test2/gmmdemux_vs_bff_consensuscall/test2_gmmdemux_vs_bff_consensuscall_concordance_heatmap.png", + "donor_match/test2/gmmdemux_vs_bff_consensuscall/test2_gmmdemux_vs_bff_consensuscall_correlation_res.csv", + "donor_match/test2/gmmdemux_vs_bff_consensuscall/test2_gmmdemux_vs_bff_consensuscall_donor_match.csv", + "donor_match/test2/gmmdemux_vs_bff_raw", + "donor_match/test2/gmmdemux_vs_bff_raw/test2_gmmdemux_vs_bff_raw_concordance_heatmap.png", + "donor_match/test2/gmmdemux_vs_bff_raw/test2_gmmdemux_vs_bff_raw_correlation_res.csv", + "donor_match/test2/gmmdemux_vs_bff_raw/test2_gmmdemux_vs_bff_raw_donor_match.csv", + "donor_match/test2/hasheddrops_vs_bff_consensuscall", + "donor_match/test2/hasheddrops_vs_bff_consensuscall/test2_hasheddrops_vs_bff_consensuscall_concordance_heatmap.png", + "donor_match/test2/hasheddrops_vs_bff_consensuscall/test2_hasheddrops_vs_bff_consensuscall_correlation_res.csv", + "donor_match/test2/hasheddrops_vs_bff_consensuscall/test2_hasheddrops_vs_bff_consensuscall_donor_match.csv", + "donor_match/test2/hasheddrops_vs_bff_raw", + "donor_match/test2/hasheddrops_vs_bff_raw/test2_hasheddrops_vs_bff_raw_concordance_heatmap.png", + "donor_match/test2/hasheddrops_vs_bff_raw/test2_hasheddrops_vs_bff_raw_correlation_res.csv", + "donor_match/test2/hasheddrops_vs_bff_raw/test2_hasheddrops_vs_bff_raw_donor_match.csv", + "donor_match/test2/hasheddrops_vs_gmmdemux", + "donor_match/test2/hasheddrops_vs_gmmdemux/test2_hasheddrops_vs_gmmdemux_concordance_heatmap.png", + "donor_match/test2/hasheddrops_vs_gmmdemux/test2_hasheddrops_vs_gmmdemux_correlation_res.csv", + "donor_match/test2/hasheddrops_vs_gmmdemux/test2_hasheddrops_vs_gmmdemux_donor_match.csv", + "donor_match/test2/hasheddrops_vs_htodemux", + "donor_match/test2/hasheddrops_vs_htodemux/test2_hasheddrops_vs_htodemux_concordance_heatmap.png", + "donor_match/test2/hasheddrops_vs_htodemux/test2_hasheddrops_vs_htodemux_correlation_res.csv", + "donor_match/test2/hasheddrops_vs_htodemux/test2_hasheddrops_vs_htodemux_donor_match.csv", + "donor_match/test2/hasheddrops_vs_multiseq", + "donor_match/test2/hasheddrops_vs_multiseq/test2_hasheddrops_vs_multiseq_concordance_heatmap.png", + "donor_match/test2/hasheddrops_vs_multiseq/test2_hasheddrops_vs_multiseq_correlation_res.csv", + "donor_match/test2/hasheddrops_vs_multiseq/test2_hasheddrops_vs_multiseq_donor_match.csv", + "donor_match/test2/hashsolo_vs_bff_consensuscall", + "donor_match/test2/hashsolo_vs_bff_consensuscall/test2_hashsolo_vs_bff_consensuscall_concordance_heatmap.png", + "donor_match/test2/hashsolo_vs_bff_consensuscall/test2_hashsolo_vs_bff_consensuscall_correlation_res.csv", + "donor_match/test2/hashsolo_vs_bff_consensuscall/test2_hashsolo_vs_bff_consensuscall_donor_match.csv", + "donor_match/test2/hashsolo_vs_bff_raw", + "donor_match/test2/hashsolo_vs_bff_raw/test2_hashsolo_vs_bff_raw_concordance_heatmap.png", + "donor_match/test2/hashsolo_vs_bff_raw/test2_hashsolo_vs_bff_raw_correlation_res.csv", + "donor_match/test2/hashsolo_vs_bff_raw/test2_hashsolo_vs_bff_raw_donor_match.csv", + "donor_match/test2/hashsolo_vs_gmmdemux", + "donor_match/test2/hashsolo_vs_gmmdemux/test2_hashsolo_vs_gmmdemux_concordance_heatmap.png", + "donor_match/test2/hashsolo_vs_gmmdemux/test2_hashsolo_vs_gmmdemux_correlation_res.csv", + "donor_match/test2/hashsolo_vs_gmmdemux/test2_hashsolo_vs_gmmdemux_donor_match.csv", + "donor_match/test2/hashsolo_vs_hasheddrops", + "donor_match/test2/hashsolo_vs_hasheddrops/test2_hashsolo_vs_hasheddrops_concordance_heatmap.png", + "donor_match/test2/hashsolo_vs_hasheddrops/test2_hashsolo_vs_hasheddrops_correlation_res.csv", + "donor_match/test2/hashsolo_vs_hasheddrops/test2_hashsolo_vs_hasheddrops_donor_match.csv", + "donor_match/test2/hashsolo_vs_htodemux", + "donor_match/test2/hashsolo_vs_htodemux/test2_hashsolo_vs_htodemux_concordance_heatmap.png", + "donor_match/test2/hashsolo_vs_htodemux/test2_hashsolo_vs_htodemux_correlation_res.csv", + "donor_match/test2/hashsolo_vs_htodemux/test2_hashsolo_vs_htodemux_donor_match.csv", + "donor_match/test2/hashsolo_vs_multiseq", + "donor_match/test2/hashsolo_vs_multiseq/test2_hashsolo_vs_multiseq_concordance_heatmap.png", + "donor_match/test2/hashsolo_vs_multiseq/test2_hashsolo_vs_multiseq_correlation_res.csv", + "donor_match/test2/hashsolo_vs_multiseq/test2_hashsolo_vs_multiseq_donor_match.csv", + "donor_match/test2/htodemux_vs_bff_consensuscall", + "donor_match/test2/htodemux_vs_bff_consensuscall/test2_htodemux_vs_bff_consensuscall_concordance_heatmap.png", + "donor_match/test2/htodemux_vs_bff_consensuscall/test2_htodemux_vs_bff_consensuscall_correlation_res.csv", + "donor_match/test2/htodemux_vs_bff_consensuscall/test2_htodemux_vs_bff_consensuscall_donor_match.csv", + "donor_match/test2/htodemux_vs_bff_raw", + "donor_match/test2/htodemux_vs_bff_raw/test2_htodemux_vs_bff_raw_concordance_heatmap.png", + "donor_match/test2/htodemux_vs_bff_raw/test2_htodemux_vs_bff_raw_correlation_res.csv", + "donor_match/test2/htodemux_vs_bff_raw/test2_htodemux_vs_bff_raw_donor_match.csv", + "donor_match/test2/htodemux_vs_gmmdemux", + "donor_match/test2/htodemux_vs_gmmdemux/test2_htodemux_vs_gmmdemux_concordance_heatmap.png", + "donor_match/test2/htodemux_vs_gmmdemux/test2_htodemux_vs_gmmdemux_correlation_res.csv", + "donor_match/test2/htodemux_vs_gmmdemux/test2_htodemux_vs_gmmdemux_donor_match.csv", + "donor_match/test2/multiseq_vs_bff_consensuscall", + "donor_match/test2/multiseq_vs_bff_consensuscall/test2_multiseq_vs_bff_consensuscall_concordance_heatmap.png", + "donor_match/test2/multiseq_vs_bff_consensuscall/test2_multiseq_vs_bff_consensuscall_correlation_res.csv", + "donor_match/test2/multiseq_vs_bff_consensuscall/test2_multiseq_vs_bff_consensuscall_donor_match.csv", + "donor_match/test2/multiseq_vs_bff_raw", + "donor_match/test2/multiseq_vs_bff_raw/test2_multiseq_vs_bff_raw_concordance_heatmap.png", + "donor_match/test2/multiseq_vs_bff_raw/test2_multiseq_vs_bff_raw_correlation_res.csv", + "donor_match/test2/multiseq_vs_bff_raw/test2_multiseq_vs_bff_raw_donor_match.csv", + "donor_match/test2/multiseq_vs_gmmdemux", + "donor_match/test2/multiseq_vs_gmmdemux/test2_multiseq_vs_gmmdemux_concordance_heatmap.png", + "donor_match/test2/multiseq_vs_gmmdemux/test2_multiseq_vs_gmmdemux_correlation_res.csv", + "donor_match/test2/multiseq_vs_gmmdemux/test2_multiseq_vs_gmmdemux_donor_match.csv", + "donor_match/test2/multiseq_vs_htodemux", + "donor_match/test2/multiseq_vs_htodemux/test2_multiseq_vs_htodemux_concordance_heatmap.png", + "donor_match/test2/multiseq_vs_htodemux/test2_multiseq_vs_htodemux_correlation_res.csv", + "donor_match/test2/multiseq_vs_htodemux/test2_multiseq_vs_htodemux_donor_match.csv", + "donor_match/test3", + "donor_match/test3/bff_raw_vs_bff_consensuscall", + "donor_match/test3/bff_raw_vs_bff_consensuscall/test3_bff_raw_vs_bff_consensuscall_concordance_heatmap.png", + "donor_match/test3/bff_raw_vs_bff_consensuscall/test3_bff_raw_vs_bff_consensuscall_correlation_res.csv", + "donor_match/test3/bff_raw_vs_bff_consensuscall/test3_bff_raw_vs_bff_consensuscall_donor_match.csv", + "donor_match/test3/gmmdemux_vs_bff_consensuscall", + "donor_match/test3/gmmdemux_vs_bff_consensuscall/test3_gmmdemux_vs_bff_consensuscall_concordance_heatmap.png", + "donor_match/test3/gmmdemux_vs_bff_consensuscall/test3_gmmdemux_vs_bff_consensuscall_correlation_res.csv", + "donor_match/test3/gmmdemux_vs_bff_consensuscall/test3_gmmdemux_vs_bff_consensuscall_donor_match.csv", + "donor_match/test3/gmmdemux_vs_bff_raw", + "donor_match/test3/gmmdemux_vs_bff_raw/test3_gmmdemux_vs_bff_raw_concordance_heatmap.png", + "donor_match/test3/gmmdemux_vs_bff_raw/test3_gmmdemux_vs_bff_raw_correlation_res.csv", + "donor_match/test3/gmmdemux_vs_bff_raw/test3_gmmdemux_vs_bff_raw_donor_match.csv", + "donor_match/test3/hasheddrops_vs_bff_consensuscall", + "donor_match/test3/hasheddrops_vs_bff_consensuscall/test3_hasheddrops_vs_bff_consensuscall_concordance_heatmap.png", + "donor_match/test3/hasheddrops_vs_bff_consensuscall/test3_hasheddrops_vs_bff_consensuscall_correlation_res.csv", + "donor_match/test3/hasheddrops_vs_bff_consensuscall/test3_hasheddrops_vs_bff_consensuscall_donor_match.csv", + "donor_match/test3/hasheddrops_vs_bff_raw", + "donor_match/test3/hasheddrops_vs_bff_raw/test3_hasheddrops_vs_bff_raw_concordance_heatmap.png", + "donor_match/test3/hasheddrops_vs_bff_raw/test3_hasheddrops_vs_bff_raw_correlation_res.csv", + "donor_match/test3/hasheddrops_vs_bff_raw/test3_hasheddrops_vs_bff_raw_donor_match.csv", + "donor_match/test3/hasheddrops_vs_gmmdemux", + "donor_match/test3/hasheddrops_vs_gmmdemux/test3_hasheddrops_vs_gmmdemux_concordance_heatmap.png", + "donor_match/test3/hasheddrops_vs_gmmdemux/test3_hasheddrops_vs_gmmdemux_correlation_res.csv", + "donor_match/test3/hasheddrops_vs_gmmdemux/test3_hasheddrops_vs_gmmdemux_donor_match.csv", + "donor_match/test3/hasheddrops_vs_htodemux", + "donor_match/test3/hasheddrops_vs_htodemux/test3_hasheddrops_vs_htodemux_concordance_heatmap.png", + "donor_match/test3/hasheddrops_vs_htodemux/test3_hasheddrops_vs_htodemux_correlation_res.csv", + "donor_match/test3/hasheddrops_vs_htodemux/test3_hasheddrops_vs_htodemux_donor_match.csv", + "donor_match/test3/hasheddrops_vs_multiseq", + "donor_match/test3/hasheddrops_vs_multiseq/test3_hasheddrops_vs_multiseq_concordance_heatmap.png", + "donor_match/test3/hasheddrops_vs_multiseq/test3_hasheddrops_vs_multiseq_correlation_res.csv", + "donor_match/test3/hasheddrops_vs_multiseq/test3_hasheddrops_vs_multiseq_donor_match.csv", + "donor_match/test3/hashsolo_vs_bff_consensuscall", + "donor_match/test3/hashsolo_vs_bff_consensuscall/test3_hashsolo_vs_bff_consensuscall_concordance_heatmap.png", + "donor_match/test3/hashsolo_vs_bff_consensuscall/test3_hashsolo_vs_bff_consensuscall_correlation_res.csv", + "donor_match/test3/hashsolo_vs_bff_consensuscall/test3_hashsolo_vs_bff_consensuscall_donor_match.csv", + "donor_match/test3/hashsolo_vs_bff_raw", + "donor_match/test3/hashsolo_vs_bff_raw/test3_hashsolo_vs_bff_raw_concordance_heatmap.png", + "donor_match/test3/hashsolo_vs_bff_raw/test3_hashsolo_vs_bff_raw_correlation_res.csv", + "donor_match/test3/hashsolo_vs_bff_raw/test3_hashsolo_vs_bff_raw_donor_match.csv", + "donor_match/test3/hashsolo_vs_gmmdemux", + "donor_match/test3/hashsolo_vs_gmmdemux/test3_hashsolo_vs_gmmdemux_concordance_heatmap.png", + "donor_match/test3/hashsolo_vs_gmmdemux/test3_hashsolo_vs_gmmdemux_correlation_res.csv", + "donor_match/test3/hashsolo_vs_gmmdemux/test3_hashsolo_vs_gmmdemux_donor_match.csv", + "donor_match/test3/hashsolo_vs_hasheddrops", + "donor_match/test3/hashsolo_vs_hasheddrops/test3_hashsolo_vs_hasheddrops_concordance_heatmap.png", + "donor_match/test3/hashsolo_vs_hasheddrops/test3_hashsolo_vs_hasheddrops_correlation_res.csv", + "donor_match/test3/hashsolo_vs_hasheddrops/test3_hashsolo_vs_hasheddrops_donor_match.csv", + "donor_match/test3/hashsolo_vs_htodemux", + "donor_match/test3/hashsolo_vs_htodemux/test3_hashsolo_vs_htodemux_concordance_heatmap.png", + "donor_match/test3/hashsolo_vs_htodemux/test3_hashsolo_vs_htodemux_correlation_res.csv", + "donor_match/test3/hashsolo_vs_htodemux/test3_hashsolo_vs_htodemux_donor_match.csv", + "donor_match/test3/hashsolo_vs_multiseq", + "donor_match/test3/hashsolo_vs_multiseq/test3_hashsolo_vs_multiseq_concordance_heatmap.png", + "donor_match/test3/hashsolo_vs_multiseq/test3_hashsolo_vs_multiseq_correlation_res.csv", + "donor_match/test3/hashsolo_vs_multiseq/test3_hashsolo_vs_multiseq_donor_match.csv", + "donor_match/test3/htodemux_vs_bff_consensuscall", + "donor_match/test3/htodemux_vs_bff_consensuscall/test3_htodemux_vs_bff_consensuscall_concordance_heatmap.png", + "donor_match/test3/htodemux_vs_bff_consensuscall/test3_htodemux_vs_bff_consensuscall_correlation_res.csv", + "donor_match/test3/htodemux_vs_bff_consensuscall/test3_htodemux_vs_bff_consensuscall_donor_match.csv", + "donor_match/test3/htodemux_vs_bff_raw", + "donor_match/test3/htodemux_vs_bff_raw/test3_htodemux_vs_bff_raw_concordance_heatmap.png", + "donor_match/test3/htodemux_vs_bff_raw/test3_htodemux_vs_bff_raw_correlation_res.csv", + "donor_match/test3/htodemux_vs_bff_raw/test3_htodemux_vs_bff_raw_donor_match.csv", + "donor_match/test3/htodemux_vs_gmmdemux", + "donor_match/test3/htodemux_vs_gmmdemux/test3_htodemux_vs_gmmdemux_concordance_heatmap.png", + "donor_match/test3/htodemux_vs_gmmdemux/test3_htodemux_vs_gmmdemux_correlation_res.csv", + "donor_match/test3/htodemux_vs_gmmdemux/test3_htodemux_vs_gmmdemux_donor_match.csv", + "donor_match/test3/multiseq_vs_bff_consensuscall", + "donor_match/test3/multiseq_vs_bff_consensuscall/test3_multiseq_vs_bff_consensuscall_concordance_heatmap.png", + "donor_match/test3/multiseq_vs_bff_consensuscall/test3_multiseq_vs_bff_consensuscall_correlation_res.csv", + "donor_match/test3/multiseq_vs_bff_consensuscall/test3_multiseq_vs_bff_consensuscall_donor_match.csv", + "donor_match/test3/multiseq_vs_bff_raw", + "donor_match/test3/multiseq_vs_bff_raw/test3_multiseq_vs_bff_raw_concordance_heatmap.png", + "donor_match/test3/multiseq_vs_bff_raw/test3_multiseq_vs_bff_raw_correlation_res.csv", + "donor_match/test3/multiseq_vs_bff_raw/test3_multiseq_vs_bff_raw_donor_match.csv", + "donor_match/test3/multiseq_vs_gmmdemux", + "donor_match/test3/multiseq_vs_gmmdemux/test3_multiseq_vs_gmmdemux_concordance_heatmap.png", + "donor_match/test3/multiseq_vs_gmmdemux/test3_multiseq_vs_gmmdemux_correlation_res.csv", + "donor_match/test3/multiseq_vs_gmmdemux/test3_multiseq_vs_gmmdemux_donor_match.csv", + "donor_match/test3/multiseq_vs_htodemux", + "donor_match/test3/multiseq_vs_htodemux/test3_multiseq_vs_htodemux_concordance_heatmap.png", + "donor_match/test3/multiseq_vs_htodemux/test3_multiseq_vs_htodemux_correlation_res.csv", + "donor_match/test3/multiseq_vs_htodemux/test3_multiseq_vs_htodemux_donor_match.csv", + "hashing", + "hashing/bff", + "hashing/bff/test1", + "hashing/bff/test1/test1_assignment_bff.csv", + "hashing/bff/test1/test1_metrics_bff.csv", + "hashing/bff/test1/test1_params_bff.csv", + "hashing/bff/test2", + "hashing/bff/test2/test2_assignment_bff.csv", + "hashing/bff/test2/test2_metrics_bff.csv", + "hashing/bff/test2/test2_params_bff.csv", + "hashing/bff/test3", + "hashing/bff/test3/test3_assignment_bff.csv", + "hashing/bff/test3/test3_metrics_bff.csv", + "hashing/bff/test3/test3_params_bff.csv", + "hashing/gmm-demux", + "hashing/gmm-demux/test1", + "hashing/gmm-demux/test1/GMM_full.config", + "hashing/gmm-demux/test1/GMM_full.csv", + "hashing/gmm-demux/test1/barcodes.tsv.gz", + "hashing/gmm-demux/test1/features.tsv.gz", + "hashing/gmm-demux/test1/matrix.mtx.gz", + "hashing/gmm-demux/test2", + "hashing/gmm-demux/test2/GMM_full.config", + "hashing/gmm-demux/test2/GMM_full.csv", + "hashing/gmm-demux/test2/barcodes.tsv.gz", + "hashing/gmm-demux/test2/features.tsv.gz", + "hashing/gmm-demux/test2/matrix.mtx.gz", + "hashing/gmm-demux/test3", + "hashing/gmm-demux/test3/GMM_full.config", + "hashing/gmm-demux/test3/GMM_full.csv", + "hashing/gmm-demux/test3/barcodes.tsv.gz", + "hashing/gmm-demux/test3/features.tsv.gz", + "hashing/gmm-demux/test3/matrix.mtx.gz", + "hashing/hasheddrops", + "hashing/hasheddrops/test1", + "hashing/hasheddrops/test1/test1_emptyDrops.csv", + "hashing/hasheddrops/test1/test1_emptyDrops.png", + "hashing/hasheddrops/test1/test1_emptyDrops.rds", + "hashing/hasheddrops/test1/test1_hasheddrops.rds", + "hashing/hasheddrops/test1/test1_id_to_hash.csv", + "hashing/hasheddrops/test1/test1_params_hasheddrops.csv", + "hashing/hasheddrops/test1/test1_plot_hasheddrops.png", + "hashing/hasheddrops/test1/test1_results_hasheddrops.csv", + "hashing/hasheddrops/test2", + "hashing/hasheddrops/test2/test2_emptyDrops.csv", + "hashing/hasheddrops/test2/test2_emptyDrops.png", + "hashing/hasheddrops/test2/test2_emptyDrops.rds", + "hashing/hasheddrops/test2/test2_hasheddrops.rds", + "hashing/hasheddrops/test2/test2_id_to_hash.csv", + "hashing/hasheddrops/test2/test2_params_hasheddrops.csv", + "hashing/hasheddrops/test2/test2_plot_hasheddrops.png", + "hashing/hasheddrops/test2/test2_results_hasheddrops.csv", + "hashing/hasheddrops/test3", + "hashing/hasheddrops/test3/test3_emptyDrops.csv", + "hashing/hasheddrops/test3/test3_emptyDrops.png", + "hashing/hasheddrops/test3/test3_emptyDrops.rds", + "hashing/hasheddrops/test3/test3_hasheddrops.rds", + "hashing/hasheddrops/test3/test3_id_to_hash.csv", + "hashing/hasheddrops/test3/test3_params_hasheddrops.csv", + "hashing/hasheddrops/test3/test3_plot_hasheddrops.png", + "hashing/hasheddrops/test3/test3_results_hasheddrops.csv", + "hashing/hashsolo", + "hashing/hashsolo/test1", + "hashing/hashsolo/test1/test1_assignment_hashsolo.csv", + "hashing/hashsolo/test1/test1_hashsolo.h5ad", + "hashing/hashsolo/test1/test1_params_hashsolo.csv", + "hashing/hashsolo/test2", + "hashing/hashsolo/test2/test2_assignment_hashsolo.csv", + "hashing/hashsolo/test2/test2_hashsolo.h5ad", + "hashing/hashsolo/test2/test2_params_hashsolo.csv", + "hashing/hashsolo/test3", + "hashing/hashsolo/test3/test3_assignment_hashsolo.csv", + "hashing/hashsolo/test3/test3_hashsolo.h5ad", + "hashing/hashsolo/test3/test3_params_hashsolo.csv", + "hashing/htodemux", + "hashing/htodemux/test1", + "hashing/htodemux/test1/test1_assignment_htodemux.csv", + "hashing/htodemux/test1/test1_classification_htodemux.csv", + "hashing/htodemux/test1/test1_htodemux.rds", + "hashing/htodemux/test1/test1_params_htodemux.csv", + "hashing/htodemux/test1/visualization", + "hashing/htodemux/test1/visualization/test1_featureScatter_htodemux.jpeg", + "hashing/htodemux/test1/visualization/test1_heatMap_htodemux.jpeg", + "hashing/htodemux/test1/visualization/test1_ridge_htodemux.jpeg", + "hashing/htodemux/test1/visualization/test1_tSNE_htodemux.jpeg", + "hashing/htodemux/test1/visualization/test1_violinPlot_htodemux.jpeg", + "hashing/htodemux/test1/visualization/test1_visual_params_htodemux.csv", + "hashing/htodemux/test2", + "hashing/htodemux/test2/test2_assignment_htodemux.csv", + "hashing/htodemux/test2/test2_classification_htodemux.csv", + "hashing/htodemux/test2/test2_htodemux.rds", + "hashing/htodemux/test2/test2_params_htodemux.csv", + "hashing/htodemux/test2/visualization", + "hashing/htodemux/test2/visualization/test2_featureScatter_htodemux.jpeg", + "hashing/htodemux/test2/visualization/test2_heatMap_htodemux.jpeg", + "hashing/htodemux/test2/visualization/test2_ridge_htodemux.jpeg", + "hashing/htodemux/test2/visualization/test2_tSNE_htodemux.jpeg", + "hashing/htodemux/test2/visualization/test2_violinPlot_htodemux.jpeg", + "hashing/htodemux/test2/visualization/test2_visual_params_htodemux.csv", + "hashing/htodemux/test3", + "hashing/htodemux/test3/test3_assignment_htodemux.csv", + "hashing/htodemux/test3/test3_classification_htodemux.csv", + "hashing/htodemux/test3/test3_htodemux.rds", + "hashing/htodemux/test3/test3_params_htodemux.csv", + "hashing/htodemux/test3/visualization", + "hashing/htodemux/test3/visualization/test3_featureScatter_htodemux.jpeg", + "hashing/htodemux/test3/visualization/test3_heatMap_htodemux.jpeg", + "hashing/htodemux/test3/visualization/test3_ridge_htodemux.jpeg", + "hashing/htodemux/test3/visualization/test3_tSNE_htodemux.jpeg", + "hashing/htodemux/test3/visualization/test3_violinPlot_htodemux.jpeg", + "hashing/htodemux/test3/visualization/test3_visual_params_htodemux.csv", + "hashing/multiseqdemux", + "hashing/multiseqdemux/test1", + "hashing/multiseqdemux/test1/test1_multiseqdemux.rds", + "hashing/multiseqdemux/test1/test1_params_multiseqdemux.csv", + "hashing/multiseqdemux/test1/test1_res_multiseqdemux.csv", + "hashing/multiseqdemux/test2", + "hashing/multiseqdemux/test2/test2_multiseqdemux.rds", + "hashing/multiseqdemux/test2/test2_params_multiseqdemux.csv", + "hashing/multiseqdemux/test2/test2_res_multiseqdemux.csv", + "hashing/multiseqdemux/test3", + "hashing/multiseqdemux/test3/test3_multiseqdemux.rds", + "hashing/multiseqdemux/test3/test3_params_multiseqdemux.csv", + "hashing/multiseqdemux/test3/test3_res_multiseqdemux.csv", + "hashing/summary", + "hashing/summary/test1", + "hashing/summary/test1/test1_hashing_overview_assignment.csv", + "hashing/summary/test1/test1_hashing_overview_classification.csv", + "hashing/summary/test1/test1_hashing_summary_assignment.csv", + "hashing/summary/test1/test1_hashing_summary_classification.csv", + "hashing/summary/test2", + "hashing/summary/test2/test2_hashing_overview_assignment.csv", + "hashing/summary/test2/test2_hashing_overview_classification.csv", + "hashing/summary/test2/test2_hashing_summary_assignment.csv", + "hashing/summary/test2/test2_hashing_summary_classification.csv", + "hashing/summary/test3", + "hashing/summary/test3/test3_hashing_overview_assignment.csv", + "hashing/summary/test3/test3_hashing_overview_classification.csv", + "hashing/summary/test3/test3_hashing_summary_assignment.csv", + "hashing/summary/test3/test3_hashing_summary_classification.csv", + "multiqc", + "multiqc/multiqc_data", + "multiqc/multiqc_data/llms-full.txt", + "multiqc/multiqc_data/multiqc.log", + "multiqc/multiqc_data/multiqc.parquet", + "multiqc/multiqc_data/multiqc_citations.txt", + "multiqc/multiqc_data/multiqc_data.json", + "multiqc/multiqc_data/multiqc_software_versions.txt", + "multiqc/multiqc_data/multiqc_sources.txt", + "multiqc/multiqc_report.html", + "pipeline_info", + "pipeline_info/nf_core_hadge_software_mqc_versions.yml", + "summary", + "summary/test1", + "summary/test1/test1_genetic.h5ad", + "summary/test1/test1_genetic_and_hashing.h5mu", + "summary/test1/test1_hashing.h5ad", + "summary/test2", + "summary/test2/test2_genetic.h5ad", + "summary/test2/test2_genetic_and_hashing.h5mu", + "summary/test2/test2_hashing.h5ad", + "summary/test3", + "summary/test3/test3_genetic.h5ad", + "summary/test3/test3_genetic_and_hashing.h5mu", + "summary/test3/test3_hashing.h5ad" + ], + [ + "test1_bff_raw_vs_bff_consensuscall_concordance_heatmap.png:md5,70228780e63708c0255b9fefd17af97f", + "test1_bff_raw_vs_bff_consensuscall_correlation_res.csv:md5,fd2c1ab13a371736d4ce2210fdbbb49a", + "test1_bff_raw_vs_bff_consensuscall_donor_match.csv:md5,3dd18b42dded6bc972f54f877b62d744", + "test1_gmmdemux_vs_bff_consensuscall_concordance_heatmap.png:md5,70228780e63708c0255b9fefd17af97f", + "test1_gmmdemux_vs_bff_consensuscall_correlation_res.csv:md5,fd2c1ab13a371736d4ce2210fdbbb49a", + "test1_gmmdemux_vs_bff_consensuscall_donor_match.csv:md5,3dd18b42dded6bc972f54f877b62d744", + "test1_gmmdemux_vs_bff_raw_concordance_heatmap.png:md5,70228780e63708c0255b9fefd17af97f", + "test1_gmmdemux_vs_bff_raw_correlation_res.csv:md5,fd2c1ab13a371736d4ce2210fdbbb49a", + "test1_gmmdemux_vs_bff_raw_donor_match.csv:md5,3dd18b42dded6bc972f54f877b62d744", + "test1_hasheddrops_vs_bff_consensuscall_concordance_heatmap.png:md5,70228780e63708c0255b9fefd17af97f", + "test1_hasheddrops_vs_bff_consensuscall_correlation_res.csv:md5,fd2c1ab13a371736d4ce2210fdbbb49a", + "test1_hasheddrops_vs_bff_consensuscall_donor_match.csv:md5,3dd18b42dded6bc972f54f877b62d744", + "test1_hasheddrops_vs_bff_raw_concordance_heatmap.png:md5,70228780e63708c0255b9fefd17af97f", + "test1_hasheddrops_vs_bff_raw_correlation_res.csv:md5,fd2c1ab13a371736d4ce2210fdbbb49a", + "test1_hasheddrops_vs_bff_raw_donor_match.csv:md5,3dd18b42dded6bc972f54f877b62d744", + "test1_hasheddrops_vs_gmmdemux_concordance_heatmap.png:md5,70228780e63708c0255b9fefd17af97f", + "test1_hasheddrops_vs_gmmdemux_correlation_res.csv:md5,fd2c1ab13a371736d4ce2210fdbbb49a", + "test1_hasheddrops_vs_gmmdemux_donor_match.csv:md5,3dd18b42dded6bc972f54f877b62d744", + "test1_hasheddrops_vs_htodemux_concordance_heatmap.png:md5,70228780e63708c0255b9fefd17af97f", + "test1_hasheddrops_vs_htodemux_correlation_res.csv:md5,fd2c1ab13a371736d4ce2210fdbbb49a", + "test1_hasheddrops_vs_htodemux_donor_match.csv:md5,3dd18b42dded6bc972f54f877b62d744", + "test1_hasheddrops_vs_multiseq_concordance_heatmap.png:md5,70228780e63708c0255b9fefd17af97f", + "test1_hasheddrops_vs_multiseq_correlation_res.csv:md5,fd2c1ab13a371736d4ce2210fdbbb49a", + "test1_hasheddrops_vs_multiseq_donor_match.csv:md5,3dd18b42dded6bc972f54f877b62d744", + "test1_hashsolo_vs_bff_consensuscall_concordance_heatmap.png:md5,70228780e63708c0255b9fefd17af97f", + "test1_hashsolo_vs_bff_consensuscall_correlation_res.csv:md5,fd2c1ab13a371736d4ce2210fdbbb49a", + "test1_hashsolo_vs_bff_consensuscall_donor_match.csv:md5,3dd18b42dded6bc972f54f877b62d744", + "test1_hashsolo_vs_bff_raw_concordance_heatmap.png:md5,70228780e63708c0255b9fefd17af97f", + "test1_hashsolo_vs_bff_raw_correlation_res.csv:md5,fd2c1ab13a371736d4ce2210fdbbb49a", + "test1_hashsolo_vs_bff_raw_donor_match.csv:md5,3dd18b42dded6bc972f54f877b62d744", + "test1_hashsolo_vs_gmmdemux_concordance_heatmap.png:md5,70228780e63708c0255b9fefd17af97f", + "test1_hashsolo_vs_gmmdemux_correlation_res.csv:md5,fd2c1ab13a371736d4ce2210fdbbb49a", + "test1_hashsolo_vs_gmmdemux_donor_match.csv:md5,3dd18b42dded6bc972f54f877b62d744", + "test1_hashsolo_vs_hasheddrops_concordance_heatmap.png:md5,70228780e63708c0255b9fefd17af97f", + "test1_hashsolo_vs_hasheddrops_correlation_res.csv:md5,fd2c1ab13a371736d4ce2210fdbbb49a", + "test1_hashsolo_vs_hasheddrops_donor_match.csv:md5,3dd18b42dded6bc972f54f877b62d744", + "test1_hashsolo_vs_htodemux_concordance_heatmap.png:md5,70228780e63708c0255b9fefd17af97f", + "test1_hashsolo_vs_htodemux_correlation_res.csv:md5,fd2c1ab13a371736d4ce2210fdbbb49a", + "test1_hashsolo_vs_htodemux_donor_match.csv:md5,3dd18b42dded6bc972f54f877b62d744", + "test1_hashsolo_vs_multiseq_concordance_heatmap.png:md5,70228780e63708c0255b9fefd17af97f", + "test1_hashsolo_vs_multiseq_correlation_res.csv:md5,fd2c1ab13a371736d4ce2210fdbbb49a", + "test1_hashsolo_vs_multiseq_donor_match.csv:md5,3dd18b42dded6bc972f54f877b62d744", + "test1_htodemux_vs_bff_consensuscall_concordance_heatmap.png:md5,70228780e63708c0255b9fefd17af97f", + "test1_htodemux_vs_bff_consensuscall_correlation_res.csv:md5,fd2c1ab13a371736d4ce2210fdbbb49a", + "test1_htodemux_vs_bff_consensuscall_donor_match.csv:md5,3dd18b42dded6bc972f54f877b62d744", + "test1_htodemux_vs_bff_raw_concordance_heatmap.png:md5,70228780e63708c0255b9fefd17af97f", + "test1_htodemux_vs_bff_raw_correlation_res.csv:md5,fd2c1ab13a371736d4ce2210fdbbb49a", + "test1_htodemux_vs_bff_raw_donor_match.csv:md5,3dd18b42dded6bc972f54f877b62d744", + "test1_htodemux_vs_gmmdemux_concordance_heatmap.png:md5,70228780e63708c0255b9fefd17af97f", + "test1_htodemux_vs_gmmdemux_correlation_res.csv:md5,fd2c1ab13a371736d4ce2210fdbbb49a", + "test1_htodemux_vs_gmmdemux_donor_match.csv:md5,3dd18b42dded6bc972f54f877b62d744", + "test1_multiseq_vs_bff_consensuscall_concordance_heatmap.png:md5,70228780e63708c0255b9fefd17af97f", + "test1_multiseq_vs_bff_consensuscall_correlation_res.csv:md5,fd2c1ab13a371736d4ce2210fdbbb49a", + "test1_multiseq_vs_bff_consensuscall_donor_match.csv:md5,3dd18b42dded6bc972f54f877b62d744", + "test1_multiseq_vs_bff_raw_concordance_heatmap.png:md5,70228780e63708c0255b9fefd17af97f", + "test1_multiseq_vs_bff_raw_correlation_res.csv:md5,fd2c1ab13a371736d4ce2210fdbbb49a", + "test1_multiseq_vs_bff_raw_donor_match.csv:md5,3dd18b42dded6bc972f54f877b62d744", + "test1_multiseq_vs_gmmdemux_concordance_heatmap.png:md5,70228780e63708c0255b9fefd17af97f", + "test1_multiseq_vs_gmmdemux_correlation_res.csv:md5,fd2c1ab13a371736d4ce2210fdbbb49a", + "test1_multiseq_vs_gmmdemux_donor_match.csv:md5,3dd18b42dded6bc972f54f877b62d744", + "test1_multiseq_vs_htodemux_concordance_heatmap.png:md5,70228780e63708c0255b9fefd17af97f", + "test1_multiseq_vs_htodemux_correlation_res.csv:md5,fd2c1ab13a371736d4ce2210fdbbb49a", + "test1_multiseq_vs_htodemux_donor_match.csv:md5,3dd18b42dded6bc972f54f877b62d744", + "test2_bff_raw_vs_bff_consensuscall_concordance_heatmap.png:md5,70228780e63708c0255b9fefd17af97f", + "test2_bff_raw_vs_bff_consensuscall_correlation_res.csv:md5,fd2c1ab13a371736d4ce2210fdbbb49a", + "test2_bff_raw_vs_bff_consensuscall_donor_match.csv:md5,3dd18b42dded6bc972f54f877b62d744", + "test2_gmmdemux_vs_bff_consensuscall_concordance_heatmap.png:md5,70228780e63708c0255b9fefd17af97f", + "test2_gmmdemux_vs_bff_consensuscall_correlation_res.csv:md5,fd2c1ab13a371736d4ce2210fdbbb49a", + "test2_gmmdemux_vs_bff_consensuscall_donor_match.csv:md5,3dd18b42dded6bc972f54f877b62d744", + "test2_gmmdemux_vs_bff_raw_concordance_heatmap.png:md5,70228780e63708c0255b9fefd17af97f", + "test2_gmmdemux_vs_bff_raw_correlation_res.csv:md5,fd2c1ab13a371736d4ce2210fdbbb49a", + "test2_gmmdemux_vs_bff_raw_donor_match.csv:md5,3dd18b42dded6bc972f54f877b62d744", + "test2_hasheddrops_vs_bff_consensuscall_concordance_heatmap.png:md5,70228780e63708c0255b9fefd17af97f", + "test2_hasheddrops_vs_bff_consensuscall_correlation_res.csv:md5,fd2c1ab13a371736d4ce2210fdbbb49a", + "test2_hasheddrops_vs_bff_consensuscall_donor_match.csv:md5,3dd18b42dded6bc972f54f877b62d744", + "test2_hasheddrops_vs_bff_raw_concordance_heatmap.png:md5,70228780e63708c0255b9fefd17af97f", + "test2_hasheddrops_vs_bff_raw_correlation_res.csv:md5,fd2c1ab13a371736d4ce2210fdbbb49a", + "test2_hasheddrops_vs_bff_raw_donor_match.csv:md5,3dd18b42dded6bc972f54f877b62d744", + "test2_hasheddrops_vs_gmmdemux_concordance_heatmap.png:md5,70228780e63708c0255b9fefd17af97f", + "test2_hasheddrops_vs_gmmdemux_correlation_res.csv:md5,fd2c1ab13a371736d4ce2210fdbbb49a", + "test2_hasheddrops_vs_gmmdemux_donor_match.csv:md5,3dd18b42dded6bc972f54f877b62d744", + "test2_hasheddrops_vs_htodemux_concordance_heatmap.png:md5,70228780e63708c0255b9fefd17af97f", + "test2_hasheddrops_vs_htodemux_correlation_res.csv:md5,fd2c1ab13a371736d4ce2210fdbbb49a", + "test2_hasheddrops_vs_htodemux_donor_match.csv:md5,3dd18b42dded6bc972f54f877b62d744", + "test2_hasheddrops_vs_multiseq_concordance_heatmap.png:md5,70228780e63708c0255b9fefd17af97f", + "test2_hasheddrops_vs_multiseq_correlation_res.csv:md5,fd2c1ab13a371736d4ce2210fdbbb49a", + "test2_hasheddrops_vs_multiseq_donor_match.csv:md5,3dd18b42dded6bc972f54f877b62d744", + "test2_hashsolo_vs_bff_consensuscall_concordance_heatmap.png:md5,70228780e63708c0255b9fefd17af97f", + "test2_hashsolo_vs_bff_consensuscall_correlation_res.csv:md5,fd2c1ab13a371736d4ce2210fdbbb49a", + "test2_hashsolo_vs_bff_consensuscall_donor_match.csv:md5,3dd18b42dded6bc972f54f877b62d744", + "test2_hashsolo_vs_bff_raw_concordance_heatmap.png:md5,70228780e63708c0255b9fefd17af97f", + "test2_hashsolo_vs_bff_raw_correlation_res.csv:md5,fd2c1ab13a371736d4ce2210fdbbb49a", + "test2_hashsolo_vs_bff_raw_donor_match.csv:md5,3dd18b42dded6bc972f54f877b62d744", + "test2_hashsolo_vs_gmmdemux_concordance_heatmap.png:md5,70228780e63708c0255b9fefd17af97f", + "test2_hashsolo_vs_gmmdemux_correlation_res.csv:md5,fd2c1ab13a371736d4ce2210fdbbb49a", + "test2_hashsolo_vs_gmmdemux_donor_match.csv:md5,3dd18b42dded6bc972f54f877b62d744", + "test2_hashsolo_vs_hasheddrops_concordance_heatmap.png:md5,70228780e63708c0255b9fefd17af97f", + "test2_hashsolo_vs_hasheddrops_correlation_res.csv:md5,fd2c1ab13a371736d4ce2210fdbbb49a", + "test2_hashsolo_vs_hasheddrops_donor_match.csv:md5,3dd18b42dded6bc972f54f877b62d744", + "test2_hashsolo_vs_htodemux_concordance_heatmap.png:md5,70228780e63708c0255b9fefd17af97f", + "test2_hashsolo_vs_htodemux_correlation_res.csv:md5,fd2c1ab13a371736d4ce2210fdbbb49a", + "test2_hashsolo_vs_htodemux_donor_match.csv:md5,3dd18b42dded6bc972f54f877b62d744", + "test2_hashsolo_vs_multiseq_concordance_heatmap.png:md5,70228780e63708c0255b9fefd17af97f", + "test2_hashsolo_vs_multiseq_correlation_res.csv:md5,fd2c1ab13a371736d4ce2210fdbbb49a", + "test2_hashsolo_vs_multiseq_donor_match.csv:md5,3dd18b42dded6bc972f54f877b62d744", + "test2_htodemux_vs_bff_consensuscall_concordance_heatmap.png:md5,70228780e63708c0255b9fefd17af97f", + "test2_htodemux_vs_bff_consensuscall_correlation_res.csv:md5,fd2c1ab13a371736d4ce2210fdbbb49a", + "test2_htodemux_vs_bff_consensuscall_donor_match.csv:md5,3dd18b42dded6bc972f54f877b62d744", + "test2_htodemux_vs_bff_raw_concordance_heatmap.png:md5,70228780e63708c0255b9fefd17af97f", + "test2_htodemux_vs_bff_raw_correlation_res.csv:md5,fd2c1ab13a371736d4ce2210fdbbb49a", + "test2_htodemux_vs_bff_raw_donor_match.csv:md5,3dd18b42dded6bc972f54f877b62d744", + "test2_htodemux_vs_gmmdemux_concordance_heatmap.png:md5,70228780e63708c0255b9fefd17af97f", + "test2_htodemux_vs_gmmdemux_correlation_res.csv:md5,fd2c1ab13a371736d4ce2210fdbbb49a", + "test2_htodemux_vs_gmmdemux_donor_match.csv:md5,3dd18b42dded6bc972f54f877b62d744", + "test2_multiseq_vs_bff_consensuscall_concordance_heatmap.png:md5,70228780e63708c0255b9fefd17af97f", + "test2_multiseq_vs_bff_consensuscall_correlation_res.csv:md5,fd2c1ab13a371736d4ce2210fdbbb49a", + "test2_multiseq_vs_bff_consensuscall_donor_match.csv:md5,3dd18b42dded6bc972f54f877b62d744", + "test2_multiseq_vs_bff_raw_concordance_heatmap.png:md5,70228780e63708c0255b9fefd17af97f", + "test2_multiseq_vs_bff_raw_correlation_res.csv:md5,fd2c1ab13a371736d4ce2210fdbbb49a", + "test2_multiseq_vs_bff_raw_donor_match.csv:md5,3dd18b42dded6bc972f54f877b62d744", + "test2_multiseq_vs_gmmdemux_concordance_heatmap.png:md5,70228780e63708c0255b9fefd17af97f", + "test2_multiseq_vs_gmmdemux_correlation_res.csv:md5,fd2c1ab13a371736d4ce2210fdbbb49a", + "test2_multiseq_vs_gmmdemux_donor_match.csv:md5,3dd18b42dded6bc972f54f877b62d744", + "test2_multiseq_vs_htodemux_concordance_heatmap.png:md5,70228780e63708c0255b9fefd17af97f", + "test2_multiseq_vs_htodemux_correlation_res.csv:md5,fd2c1ab13a371736d4ce2210fdbbb49a", + "test2_multiseq_vs_htodemux_donor_match.csv:md5,3dd18b42dded6bc972f54f877b62d744", + "test3_bff_raw_vs_bff_consensuscall_concordance_heatmap.png:md5,70228780e63708c0255b9fefd17af97f", + "test3_bff_raw_vs_bff_consensuscall_correlation_res.csv:md5,fd2c1ab13a371736d4ce2210fdbbb49a", + "test3_bff_raw_vs_bff_consensuscall_donor_match.csv:md5,3dd18b42dded6bc972f54f877b62d744", + "test3_gmmdemux_vs_bff_consensuscall_concordance_heatmap.png:md5,70228780e63708c0255b9fefd17af97f", + "test3_gmmdemux_vs_bff_consensuscall_correlation_res.csv:md5,fd2c1ab13a371736d4ce2210fdbbb49a", + "test3_gmmdemux_vs_bff_consensuscall_donor_match.csv:md5,3dd18b42dded6bc972f54f877b62d744", + "test3_gmmdemux_vs_bff_raw_concordance_heatmap.png:md5,70228780e63708c0255b9fefd17af97f", + "test3_gmmdemux_vs_bff_raw_correlation_res.csv:md5,fd2c1ab13a371736d4ce2210fdbbb49a", + "test3_gmmdemux_vs_bff_raw_donor_match.csv:md5,3dd18b42dded6bc972f54f877b62d744", + "test3_hasheddrops_vs_bff_consensuscall_concordance_heatmap.png:md5,70228780e63708c0255b9fefd17af97f", + "test3_hasheddrops_vs_bff_consensuscall_correlation_res.csv:md5,fd2c1ab13a371736d4ce2210fdbbb49a", + "test3_hasheddrops_vs_bff_consensuscall_donor_match.csv:md5,3dd18b42dded6bc972f54f877b62d744", + "test3_hasheddrops_vs_bff_raw_concordance_heatmap.png:md5,70228780e63708c0255b9fefd17af97f", + "test3_hasheddrops_vs_bff_raw_correlation_res.csv:md5,fd2c1ab13a371736d4ce2210fdbbb49a", + "test3_hasheddrops_vs_bff_raw_donor_match.csv:md5,3dd18b42dded6bc972f54f877b62d744", + "test3_hasheddrops_vs_gmmdemux_concordance_heatmap.png:md5,70228780e63708c0255b9fefd17af97f", + "test3_hasheddrops_vs_gmmdemux_correlation_res.csv:md5,fd2c1ab13a371736d4ce2210fdbbb49a", + "test3_hasheddrops_vs_gmmdemux_donor_match.csv:md5,3dd18b42dded6bc972f54f877b62d744", + "test3_hasheddrops_vs_htodemux_concordance_heatmap.png:md5,70228780e63708c0255b9fefd17af97f", + "test3_hasheddrops_vs_htodemux_correlation_res.csv:md5,fd2c1ab13a371736d4ce2210fdbbb49a", + "test3_hasheddrops_vs_htodemux_donor_match.csv:md5,3dd18b42dded6bc972f54f877b62d744", + "test3_hasheddrops_vs_multiseq_concordance_heatmap.png:md5,70228780e63708c0255b9fefd17af97f", + "test3_hasheddrops_vs_multiseq_correlation_res.csv:md5,fd2c1ab13a371736d4ce2210fdbbb49a", + "test3_hasheddrops_vs_multiseq_donor_match.csv:md5,3dd18b42dded6bc972f54f877b62d744", + "test3_hashsolo_vs_bff_consensuscall_concordance_heatmap.png:md5,70228780e63708c0255b9fefd17af97f", + "test3_hashsolo_vs_bff_consensuscall_correlation_res.csv:md5,fd2c1ab13a371736d4ce2210fdbbb49a", + "test3_hashsolo_vs_bff_consensuscall_donor_match.csv:md5,3dd18b42dded6bc972f54f877b62d744", + "test3_hashsolo_vs_bff_raw_concordance_heatmap.png:md5,70228780e63708c0255b9fefd17af97f", + "test3_hashsolo_vs_bff_raw_correlation_res.csv:md5,fd2c1ab13a371736d4ce2210fdbbb49a", + "test3_hashsolo_vs_bff_raw_donor_match.csv:md5,3dd18b42dded6bc972f54f877b62d744", + "test3_hashsolo_vs_gmmdemux_concordance_heatmap.png:md5,70228780e63708c0255b9fefd17af97f", + "test3_hashsolo_vs_gmmdemux_correlation_res.csv:md5,fd2c1ab13a371736d4ce2210fdbbb49a", + "test3_hashsolo_vs_gmmdemux_donor_match.csv:md5,3dd18b42dded6bc972f54f877b62d744", + "test3_hashsolo_vs_hasheddrops_concordance_heatmap.png:md5,70228780e63708c0255b9fefd17af97f", + "test3_hashsolo_vs_hasheddrops_correlation_res.csv:md5,fd2c1ab13a371736d4ce2210fdbbb49a", + "test3_hashsolo_vs_hasheddrops_donor_match.csv:md5,3dd18b42dded6bc972f54f877b62d744", + "test3_hashsolo_vs_htodemux_concordance_heatmap.png:md5,70228780e63708c0255b9fefd17af97f", + "test3_hashsolo_vs_htodemux_correlation_res.csv:md5,fd2c1ab13a371736d4ce2210fdbbb49a", + "test3_hashsolo_vs_htodemux_donor_match.csv:md5,3dd18b42dded6bc972f54f877b62d744", + "test3_hashsolo_vs_multiseq_concordance_heatmap.png:md5,70228780e63708c0255b9fefd17af97f", + "test3_hashsolo_vs_multiseq_correlation_res.csv:md5,fd2c1ab13a371736d4ce2210fdbbb49a", + "test3_hashsolo_vs_multiseq_donor_match.csv:md5,3dd18b42dded6bc972f54f877b62d744", + "test3_htodemux_vs_bff_consensuscall_concordance_heatmap.png:md5,70228780e63708c0255b9fefd17af97f", + "test3_htodemux_vs_bff_consensuscall_correlation_res.csv:md5,fd2c1ab13a371736d4ce2210fdbbb49a", + "test3_htodemux_vs_bff_consensuscall_donor_match.csv:md5,3dd18b42dded6bc972f54f877b62d744", + "test3_htodemux_vs_bff_raw_concordance_heatmap.png:md5,70228780e63708c0255b9fefd17af97f", + "test3_htodemux_vs_bff_raw_correlation_res.csv:md5,fd2c1ab13a371736d4ce2210fdbbb49a", + "test3_htodemux_vs_bff_raw_donor_match.csv:md5,3dd18b42dded6bc972f54f877b62d744", + "test3_htodemux_vs_gmmdemux_concordance_heatmap.png:md5,70228780e63708c0255b9fefd17af97f", + "test3_htodemux_vs_gmmdemux_correlation_res.csv:md5,fd2c1ab13a371736d4ce2210fdbbb49a", + "test3_htodemux_vs_gmmdemux_donor_match.csv:md5,3dd18b42dded6bc972f54f877b62d744", + "test3_multiseq_vs_bff_consensuscall_concordance_heatmap.png:md5,70228780e63708c0255b9fefd17af97f", + "test3_multiseq_vs_bff_consensuscall_correlation_res.csv:md5,fd2c1ab13a371736d4ce2210fdbbb49a", + "test3_multiseq_vs_bff_consensuscall_donor_match.csv:md5,3dd18b42dded6bc972f54f877b62d744", + "test3_multiseq_vs_bff_raw_concordance_heatmap.png:md5,70228780e63708c0255b9fefd17af97f", + "test3_multiseq_vs_bff_raw_correlation_res.csv:md5,fd2c1ab13a371736d4ce2210fdbbb49a", + "test3_multiseq_vs_bff_raw_donor_match.csv:md5,3dd18b42dded6bc972f54f877b62d744", + "test3_multiseq_vs_gmmdemux_concordance_heatmap.png:md5,70228780e63708c0255b9fefd17af97f", + "test3_multiseq_vs_gmmdemux_correlation_res.csv:md5,fd2c1ab13a371736d4ce2210fdbbb49a", + "test3_multiseq_vs_gmmdemux_donor_match.csv:md5,3dd18b42dded6bc972f54f877b62d744", + "test3_multiseq_vs_htodemux_concordance_heatmap.png:md5,70228780e63708c0255b9fefd17af97f", + "test3_multiseq_vs_htodemux_correlation_res.csv:md5,fd2c1ab13a371736d4ce2210fdbbb49a", + "test3_multiseq_vs_htodemux_donor_match.csv:md5,3dd18b42dded6bc972f54f877b62d744", + "test1_assignment_bff.csv:md5,82e9e12fec29f9eb0dcfbb1b6d8cc02a", + "test1_metrics_bff.csv:md5,1d7751cf035c9e736e4de5504ff87a6a", + "test1_params_bff.csv:md5,dbb83e4e50779916ff4e368b2d05fe91", + "test2_assignment_bff.csv:md5,82e9e12fec29f9eb0dcfbb1b6d8cc02a", + "test2_metrics_bff.csv:md5,1d7751cf035c9e736e4de5504ff87a6a", + "test2_params_bff.csv:md5,08758e268b309e4f3288069e3eab358a", + "test3_assignment_bff.csv:md5,82e9e12fec29f9eb0dcfbb1b6d8cc02a", + "test3_metrics_bff.csv:md5,1d7751cf035c9e736e4de5504ff87a6a", + "test3_params_bff.csv:md5,882b80fd11a10a375e0abd5ae29f74b0", + "GMM_full.config:md5,0a18f3d5729c4b6399ce4161609362b6", + "barcodes.tsv.gz:md5,e4cd9ce9929a07d6763304ec1bf64c23", + "features.tsv.gz:md5,441a968b2c7ee68eae8388b04bee328d", + "matrix.mtx.gz:md5,6f855b8bf68573d2e09139c29f9a2d41", + "GMM_full.config:md5,0a18f3d5729c4b6399ce4161609362b6", + "barcodes.tsv.gz:md5,e4cd9ce9929a07d6763304ec1bf64c23", + "features.tsv.gz:md5,441a968b2c7ee68eae8388b04bee328d", + "matrix.mtx.gz:md5,6f855b8bf68573d2e09139c29f9a2d41", + "GMM_full.config:md5,0a18f3d5729c4b6399ce4161609362b6", + "barcodes.tsv.gz:md5,e4cd9ce9929a07d6763304ec1bf64c23", + "features.tsv.gz:md5,441a968b2c7ee68eae8388b04bee328d", + "matrix.mtx.gz:md5,6f855b8bf68573d2e09139c29f9a2d41", + "test1_emptyDrops.csv:md5,a227d42afbcc590b4e949075cde4a5b6", + "test1_emptyDrops.png:md5,2d7113a76a494b2b2c18b77c4a664063", + "test1_emptyDrops.rds:md5,bda2e88b1e3e8c8ea5a22e19e3ab243d", + "test1_hasheddrops.rds:md5,7bb71fa6f28e3fc97b20bbdeffbb81d2", + "test1_id_to_hash.csv:md5,e8f577103f3e0c17604f89c3bcda2ffb", + "test1_params_hasheddrops.csv:md5,b253501914df23ef5901262e8a241a8d", + "test1_plot_hasheddrops.png:md5,2d7113a76a494b2b2c18b77c4a664063", + "test1_results_hasheddrops.csv:md5,9614789e65fc843e6a11fa60686fe89a", + "test2_emptyDrops.csv:md5,a227d42afbcc590b4e949075cde4a5b6", + "test2_emptyDrops.png:md5,2d7113a76a494b2b2c18b77c4a664063", + "test2_emptyDrops.rds:md5,bda2e88b1e3e8c8ea5a22e19e3ab243d", + "test2_hasheddrops.rds:md5,7bb71fa6f28e3fc97b20bbdeffbb81d2", + "test2_id_to_hash.csv:md5,e8f577103f3e0c17604f89c3bcda2ffb", + "test2_params_hasheddrops.csv:md5,3612264937e5c5962fefddc43afb595f", + "test2_plot_hasheddrops.png:md5,2d7113a76a494b2b2c18b77c4a664063", + "test2_results_hasheddrops.csv:md5,9614789e65fc843e6a11fa60686fe89a", + "test3_emptyDrops.csv:md5,a227d42afbcc590b4e949075cde4a5b6", + "test3_emptyDrops.png:md5,2d7113a76a494b2b2c18b77c4a664063", + "test3_emptyDrops.rds:md5,bda2e88b1e3e8c8ea5a22e19e3ab243d", + "test3_hasheddrops.rds:md5,7bb71fa6f28e3fc97b20bbdeffbb81d2", + "test3_id_to_hash.csv:md5,e8f577103f3e0c17604f89c3bcda2ffb", + "test3_params_hasheddrops.csv:md5,b15e94f887e3ada8f790ea18eb791d8b", + "test3_plot_hasheddrops.png:md5,2d7113a76a494b2b2c18b77c4a664063", + "test3_results_hasheddrops.csv:md5,9614789e65fc843e6a11fa60686fe89a", + "test1_assignment_hashsolo.csv:md5,78cb7c0c26de18064e22a68dcf5e81fd", + "test1_hashsolo.h5ad:md5,b359b676ca95b91ba7397b56112ad1b1", + "test1_params_hashsolo.csv:md5,e39b2354a531ff7c65a0d3d0ad500a81", + "test2_assignment_hashsolo.csv:md5,78cb7c0c26de18064e22a68dcf5e81fd", + "test2_hashsolo.h5ad:md5,b359b676ca95b91ba7397b56112ad1b1", + "test2_params_hashsolo.csv:md5,581ecdaff2557e6402ae2acf90d4167f", + "test3_assignment_hashsolo.csv:md5,78cb7c0c26de18064e22a68dcf5e81fd", + "test3_hashsolo.h5ad:md5,b359b676ca95b91ba7397b56112ad1b1", + "test3_params_hashsolo.csv:md5,4eac179a88b560899622d0de4cd5b079", + "test1_assignment_htodemux.csv:md5,33d72b27c3d0b0f7394af18a18490a5c", + "test1_classification_htodemux.csv:md5,886f595f148d2ccdd5a9134b8eb815cb", + "test1_params_htodemux.csv:md5,08b6c065b0c383e6be3c20787ec01d3e", + "test1_featureScatter_htodemux.jpeg:md5,c67268ac0ff18c485899b4ee58f8f948", + "test1_heatMap_htodemux.jpeg:md5,2ebdb027344db91ae89deaa3d1011993", + "test1_ridge_htodemux.jpeg:md5,8bbc209b475b437a80f853277daa4ce9", + "test1_tSNE_htodemux.jpeg:md5,470812a92d09b88ea6e33bcdadb13f11", + "test1_violinPlot_htodemux.jpeg:md5,ef866e13e18a31ab43370299245d33c7", + "test1_visual_params_htodemux.csv:md5,0a175b9868be2b4447867ab38705c11d", + "test2_assignment_htodemux.csv:md5,33d72b27c3d0b0f7394af18a18490a5c", + "test2_classification_htodemux.csv:md5,886f595f148d2ccdd5a9134b8eb815cb", + "test2_params_htodemux.csv:md5,484909d87cacd9c2b9cf60a3c853a5fd", + "test2_featureScatter_htodemux.jpeg:md5,c67268ac0ff18c485899b4ee58f8f948", + "test2_heatMap_htodemux.jpeg:md5,2ebdb027344db91ae89deaa3d1011993", + "test2_ridge_htodemux.jpeg:md5,8bbc209b475b437a80f853277daa4ce9", + "test2_tSNE_htodemux.jpeg:md5,470812a92d09b88ea6e33bcdadb13f11", + "test2_violinPlot_htodemux.jpeg:md5,ef866e13e18a31ab43370299245d33c7", + "test2_visual_params_htodemux.csv:md5,062e94620e3207810ddf2c65bf0d2914", + "test3_assignment_htodemux.csv:md5,33d72b27c3d0b0f7394af18a18490a5c", + "test3_classification_htodemux.csv:md5,886f595f148d2ccdd5a9134b8eb815cb", + "test3_params_htodemux.csv:md5,a0a3f900a632eea39057b038438a89b9", + "test3_featureScatter_htodemux.jpeg:md5,c67268ac0ff18c485899b4ee58f8f948", + "test3_heatMap_htodemux.jpeg:md5,2ebdb027344db91ae89deaa3d1011993", + "test3_ridge_htodemux.jpeg:md5,8bbc209b475b437a80f853277daa4ce9", + "test3_tSNE_htodemux.jpeg:md5,470812a92d09b88ea6e33bcdadb13f11", + "test3_violinPlot_htodemux.jpeg:md5,ef866e13e18a31ab43370299245d33c7", + "test3_visual_params_htodemux.csv:md5,b6f8f5593aeb86d846292f2f23fc518d", + "test1_params_multiseqdemux.csv:md5,60a096f79d78655870aef2af9130cf9d", + "test1_res_multiseqdemux.csv:md5,9d4475b8c2778e7e361747dd521072e4", + "test2_params_multiseqdemux.csv:md5,bb7b4009e6f709ebf35407b7849bfac3", + "test2_res_multiseqdemux.csv:md5,9d4475b8c2778e7e361747dd521072e4", + "test3_params_multiseqdemux.csv:md5,336308c45e52f92981173ecc7f295581", + "test3_res_multiseqdemux.csv:md5,9d4475b8c2778e7e361747dd521072e4", + "test1_hashing_overview_assignment.csv:md5,d0356ab124e50b77f5d60a05a38ba585", + "test1_hashing_overview_classification.csv:md5,73e83a9cc51560a08c28357921d1baf9", + "test1_hashing_summary_assignment.csv:md5,53ccb1fc3a6b85d10ff2cf580e9dfbe3", + "test1_hashing_summary_classification.csv:md5,052d8aad0e934cb2384fae9b9f313e37", + "test2_hashing_overview_assignment.csv:md5,d0356ab124e50b77f5d60a05a38ba585", + "test2_hashing_overview_classification.csv:md5,73e83a9cc51560a08c28357921d1baf9", + "test2_hashing_summary_assignment.csv:md5,53ccb1fc3a6b85d10ff2cf580e9dfbe3", + "test2_hashing_summary_classification.csv:md5,052d8aad0e934cb2384fae9b9f313e37", + "test3_hashing_overview_assignment.csv:md5,d0356ab124e50b77f5d60a05a38ba585", + "test3_hashing_overview_classification.csv:md5,73e83a9cc51560a08c28357921d1baf9", + "test3_hashing_summary_assignment.csv:md5,53ccb1fc3a6b85d10ff2cf580e9dfbe3", + "test3_hashing_summary_classification.csv:md5,052d8aad0e934cb2384fae9b9f313e37", + "multiqc_citations.txt:md5,4c806e63a283ec1b7e78cdae3a923d4f", + "test1_genetic.h5ad:md5,6493fa69aa7797d7530e5fdeb1e60048", + "test1_genetic_and_hashing.h5mu:md5,dc47ea1661289d1f61f7fa2d2dc4cf2d", + "test1_hashing.h5ad:md5,d3f3c7a57e654e70ce3c4cf40b99f227", + "test2_genetic.h5ad:md5,6493fa69aa7797d7530e5fdeb1e60048", + "test2_genetic_and_hashing.h5mu:md5,dc47ea1661289d1f61f7fa2d2dc4cf2d", + "test2_hashing.h5ad:md5,d3f3c7a57e654e70ce3c4cf40b99f227", + "test3_genetic.h5ad:md5,6493fa69aa7797d7530e5fdeb1e60048", + "test3_genetic_and_hashing.h5mu:md5,dc47ea1661289d1f61f7fa2d2dc4cf2d", + "test3_hashing.h5ad:md5,d3f3c7a57e654e70ce3c4cf40b99f227" + ], + "No VCF files" + ], + "meta": { + "nf-test": "0.9.3", + "nextflow": "25.10.3" + }, + "timestamp": "2026-02-01T17:35:31.298514478" + } +} \ No newline at end of file diff --git a/workflows/hadge.nf b/workflows/hadge.nf index eacea3a7..9d2bbbec 100644 --- a/workflows/hadge.nf +++ b/workflows/hadge.nf @@ -3,23 +3,22 @@ IMPORT MODULES / SUBWORKFLOWS / FUNCTIONS ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ -include { MULTIQC } from '../modules/nf-core/multiqc/main' -include { paramsSummaryMap } from 'plugin/nf-schema' -include { paramsSummaryMultiqc } from '../subworkflows/nf-core/utils_nfcore_pipeline' -include { softwareVersionsToYAML } from '../subworkflows/nf-core/utils_nfcore_pipeline' -include { methodsDescriptionText } from '../subworkflows/local/utils_nfcore_hadge_pipeline' - -include { UNTAR as UNTAR_RNA } from '../modules/nf-core/untar/main' -include { UNTAR as UNTAR_HTO } from '../modules/nf-core/untar/main' -include { RENAME_GENES_TO_FEATURES as RENAME_GENES_TO_FEATURES_RNA } from '../modules/local/rename_genes_to_features/main' -include { RENAME_GENES_TO_FEATURES as RENAME_GENES_TO_FEATURES_HTO } from '../modules/local/rename_genes_to_features/main' -include { EXTRACT_HASHES } from '../modules/local/extract_hashes/main' - -include { GENETIC_DEMULTIPLEXING } from '../subworkflows/local/genetic_demultiplexing/main' -include { HASH_DEMULTIPLEXING } from '../subworkflows/local/hash_demultiplexing/main' -include { CSVTK_JOIN as JOIN_RESULTS } from '../modules/nf-core/csvtk/join/main' -include { DONOR_MATCH } from '../modules/local/donor_match/main' - +include { MULTIQC } from '../modules/nf-core/multiqc/main' +include { paramsSummaryMap } from 'plugin/nf-schema' +include { paramsSummaryMultiqc } from '../subworkflows/nf-core/utils_nfcore_pipeline' +include { softwareVersionsToYAML } from '../subworkflows/nf-core/utils_nfcore_pipeline' +include { methodsDescriptionText } from '../subworkflows/local/utils_nfcore_hadge_pipeline' +include { UNTAR as UNTAR_RNA } from '../modules/nf-core/untar/main' +include { UNTAR as UNTAR_HTO } from '../modules/nf-core/untar/main' +include { EXTRACT_HASHES } from '../modules/local/extract_hashes/main' +include { GENETIC_DEMULTIPLEXING } from '../subworkflows/local/genetic_demultiplexing/main' +include { HASH_DEMULTIPLEXING } from '../subworkflows/local/hash_demultiplexing/main' +include { CREATE_ANNDATA_MUDATA } from '../modules/local/create_anndata_mudata/main' +include { CSVTK_JOIN as JOIN_RESULTS_ASSIGNMENT } from '../modules/nf-core/csvtk/join/main' +include { CSVTK_JOIN as JOIN_RESULTS_CLASSIFICATION } from '../modules/nf-core/csvtk/join/main' +include { DONOR_MATCH } from '../modules/local/donor_match/main' +include { FIND_VARIANTS } from '../modules/local/find_variants/main' +include { SUBSET_GT_DONORS } from '../modules/local/subset_gt_donors/main' /* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -30,29 +29,31 @@ include { DONOR_MATCH } from '../modules/local/donor_match/main' workflow HADGE { take: ch_samplesheet // channel: samplesheet read in from --input - fasta // file: /path/to/genome.fasta + fasta // file: /path/to/genome.fasta main: ch_versions = Channel.empty() + ch_donor_match = Channel.empty() + ch_find_variants = Channel.empty() + ch_subset_gt_donors = Channel.empty() ch_multiqc_files = Channel.empty() - // ------------------------------ preprocessing start ------------------------------- - + // untar matrices ch_rna = ch_samplesheet.map { meta, rna, _hto, _bam, _barcodes, _vcf -> [meta, rna] } .branch { _meta, rna -> - tar: rna.endsWith('.tar.gz') + tar: rna != null && rna.endsWith('.tar.gz') directory: true } + + ch_hto = ch_samplesheet.map { meta, _rna, hto, _bam, _barcodes, _vcf -> [meta, hto] } .branch { _meta, hto -> - tar: hto.endsWith('.tar.gz') + tar: hto != null && hto.endsWith('.tar.gz') directory: true } - ch_remaining_input = ch_samplesheet.map { meta, _rna, _hto, bam, barcodes, vcf -> [meta, bam, barcodes, vcf] } - UNTAR_RNA(ch_rna.tar) ch_versions = ch_versions.mix(UNTAR_RNA.out.versions) @@ -62,27 +63,27 @@ workflow HADGE { ch_rna = ch_rna.directory.mix(UNTAR_RNA.out.untar) ch_hto = ch_hto.directory.mix(UNTAR_HTO.out.untar) - // TODO remove completely if not used anymore - // ch_rna = RENAME_GENES_TO_FEATURES_RNA(ch_rna) - // ch_hto = RENAME_GENES_TO_FEATURES_HTO(ch_hto) + // extract hto names (hto can be null in genetic or donor_match mode) + ch_hashes_non_null = EXTRACT_HASHES(ch_hto.filter { _meta, hto -> hto != null }) + ch_hashes_null = ch_hto.filter { _meta, hto -> hto == null } + ch_hashes = ch_hashes_non_null.mix(ch_hashes_null) - // TODO maybe remove changes to extract hashes - ch_hashes = EXTRACT_HASHES(ch_hto) - - ch_genetic = ch_samplesheet.map { meta, _rna, _hto, _bam, _barcodes, _vcf -> [meta] } + // join preprocessed channels + ch_remaining_input = ch_samplesheet.map { meta, _rna, _hto, bam, barcodes, vcf -> [meta, bam, barcodes, vcf] } + ch_preprocessed = ch_samplesheet.map { meta, _rna, _hto, _bam, _barcodes, _vcf -> [meta] } .join(ch_rna) .join(ch_hto) .join(ch_remaining_input) .join(ch_hashes) - .map {meta, rna, hto, bam, barcodes, vcf, hashes -> [meta+[hashes: file(hashes).text.trim()], rna, hto, bam, barcodes, vcf] } + .map {meta, rna, hto, bam, barcodes, vcf, hashes -> + if(hashes!= null){ meta += [hto_names: file(hashes).text.trim()] } + [meta, rna, hto, bam, barcodes, vcf] + } - ch_hashing = ch_genetic.map { meta, rna, hto, _bam, _barcodes, _vcf -> - [meta, rna, hto] - } - - ch_donor_match = ch_genetic.map { meta, _rna, _hto, _bam, barcodes, _vcf -> - [meta, barcodes] - } + // create channels for deconvolution tools + ch_genetic = ch_preprocessed.map { meta, rna, _hto, bam, barcodes, vcf -> [meta, bam, barcodes, vcf] } + ch_hashing = ch_preprocessed.map { meta, rna, hto, _bam, _barcodes, _vcf -> [meta, rna, hto] } + ch_create_anndata_mudata = ch_preprocessed.map { meta, rna, hto, _bam, _barcodes, _vcf -> [meta, rna, hto] } // ------------------------------- preprocessing end -------------------------------- @@ -96,28 +97,36 @@ workflow HADGE { fasta ) - if(params.match_donor){ - ch_donor_match = ch_donor_match - .join(GENETIC_DEMULTIPLEXING.out.summary_assignment) - .join(GENETIC_DEMULTIPLEXING.out.cell_genotype) - .map{ meta, barcodes, gene_summary, cell_genotype -> [meta, barcodes, gene_summary, cell_genotype, []] } - } + ch_create_anndata_mudata = ch_create_anndata_mudata + .join(GENETIC_DEMULTIPLEXING.out.summary_assignment) + .join(GENETIC_DEMULTIPLEXING.out.summary_classification) + .map { meta, rna, hto, gene_a, gene_c -> + [meta, rna, hto, gene_a, gene_c, [], []] + } + + ch_donor_match = GENETIC_DEMULTIPLEXING.out.summary_assignment ch_versions = ch_versions.mix(GENETIC_DEMULTIPLEXING.out.versions) } + else if (params.mode == 'hashing'){ - //TODO should mode hashing work with cell_genotype? + HASH_DEMULTIPLEXING( ch_hashing, params.hash_tools.split(',') ) - ch_donor_match = ch_donor_match + ch_create_anndata_mudata = ch_create_anndata_mudata + .map { meta, rna, hto -> [meta, rna, hto, [], []]} .join(HASH_DEMULTIPLEXING.out.summary_assignment) - .map{ meta, barcodes, hash_summary-> [meta, barcodes, hash_summary,[],[]] } + .join(HASH_DEMULTIPLEXING.out.summary_classification) + + ch_donor_match = HASH_DEMULTIPLEXING.out.summary_assignment ch_versions = ch_versions.mix(HASH_DEMULTIPLEXING.out.versions) } + + else if ( params.mode == 'rescue' ){ GENETIC_DEMULTIPLEXING( @@ -133,45 +142,111 @@ workflow HADGE { params.hash_tools.split(',') ) - ch_donor_match = ch_donor_match + JOIN_RESULTS_ASSIGNMENT( + GENETIC_DEMULTIPLEXING.out.summary_assignment + .join(HASH_DEMULTIPLEXING.out.summary_assignment) + .map{meta, gene_summary, hash_summary -> + [meta, [gene_summary,hash_summary]] + } + ) + + JOIN_RESULTS_CLASSIFICATION( + GENETIC_DEMULTIPLEXING.out.summary_classification + .join(HASH_DEMULTIPLEXING.out.summary_classification) + .map{meta, gene_summary, hash_summary -> + [meta, [gene_summary,hash_summary]] + } + ) + + ch_create_anndata_mudata = ch_create_anndata_mudata .join(GENETIC_DEMULTIPLEXING.out.summary_assignment) - .join(GENETIC_DEMULTIPLEXING.out.cell_genotype) + .join(GENETIC_DEMULTIPLEXING.out.summary_classification) .join(HASH_DEMULTIPLEXING.out.summary_assignment) + .join(HASH_DEMULTIPLEXING.out.summary_classification) - JOIN_RESULTS(ch_donor_match.map{ - meta, _barcodes, gene_summary, _cell_genotype, hash_summary -> - [meta, [gene_summary,hash_summary]] - }) + ch_donor_match = JOIN_RESULTS_ASSIGNMENT.out.csv - ch_donor_match = ch_donor_match - .join(JOIN_RESULTS.out.csv) - .map{ - meta, barcodes, _gene_summary, cell_genotype, _hash_summary, joined_summary -> - [meta, barcodes, joined_summary, cell_genotype, []] - } + if ( params.find_variants ){ + ch_find_variants = GENETIC_DEMULTIPLEXING.out.gt_cells + .join(GENETIC_DEMULTIPLEXING.out.vireo_filtered_variants) + } ch_versions = ch_versions.mix(GENETIC_DEMULTIPLEXING.out.versions) ch_versions = ch_versions.mix(HASH_DEMULTIPLEXING.out.versions) - ch_versions = ch_versions.mix(JOIN_RESULTS.out.versions) + ch_versions = ch_versions.mix(JOIN_RESULTS_ASSIGNMENT.out.versions) + ch_versions = ch_versions.mix(JOIN_RESULTS_CLASSIFICATION.out.versions) } else if ( params.mode == 'donor_match' ){ - ch_donor_match = ch_donor_match.map{ - meta, barcodes -> - [meta, barcodes, params.demultiplexing_result, params.celldata, params.vireo_parent_dir] + ch_donor_match = ch_preprocessed.map{ meta, _rna, _hto, _bam, _barcodes, _vcf -> [meta, params.demultiplexing_result] } + + if ( params.find_variants ){ + ch_find_variants = ch_preprocessed.map{ meta, _rna, _hto, _bam, _barcodes, _vcf -> + [meta, params.cell_genotype, params.vireo_filtered_variants] + } } + } + if (params.mode == 'genetic' | params.mode == 'hashing' | params.mode == 'rescue'){ + CREATE_ANNDATA_MUDATA( + ch_create_anndata_mudata.map { tuple -> + // hto can be null in genetic mode + if (params.mode == 'genetic'){ tuple.collect { it == null ? [] : it } } + else{ tuple } + } + ) } if (params.match_donor) { + DONOR_MATCH(ch_donor_match, params.match_donor_method1 ?: [], - params.match_donor_method2 ?: [], - params.findVariants, - params.variant_count, - params.variant_pct + params.match_donor_method2 ?: [] ) + // there only is a best_intersect_assignment_after_match output in donor_match and rescue mode to run FIND_VARIANTS + if ( (params.mode == 'donor_match' | params.mode == 'rescue') && params.find_variants ){ + + ch_find_variants = DONOR_MATCH.out.best_intersect_assignment_after_match + .join(ch_find_variants) + .join(ch_donor_match) + + FIND_VARIANTS( + ch_find_variants, + params.variant_count, + params.variant_pct + ) + + // subset gt_donors vcf with representative_variants + if ( params.subset_gt_donors ) { + ch_subset_gt_donors = FIND_VARIANTS.out.donor_specific_variants + .map { meta, subset_variants -> + tuple(meta, subset_variants, 'donor_specific') + } + .mix( + FIND_VARIANTS.out.vireo_variants + .map { meta, subset_variants -> + tuple(meta, subset_variants, 'vireo') + } + ) + + ch_subset_gt_donors = params.mode == 'rescue' + ? ch_subset_gt_donors + .combine(GENETIC_DEMULTIPLEXING.out.gt_donors, by: 0) + : ch_subset_gt_donors + .map { meta, variants, type -> + [ meta, variants, type, params.gt_donors ] + } + + ch_subset_gt_donors = ch_subset_gt_donors + .combine(DONOR_MATCH.out.best_donor_match, by: 0) + + SUBSET_GT_DONORS(ch_subset_gt_donors) + + ch_versions = ch_versions.mix(SUBSET_GT_DONORS.out.versions) + } + ch_versions = ch_versions.mix(FIND_VARIANTS.out.versions) + } ch_versions = ch_versions.mix(DONOR_MATCH.out.versions) } @@ -187,7 +262,6 @@ workflow HADGE { ) .set { ch_collated_versions } - // // MODULE: MultiQC // @@ -236,5 +310,5 @@ workflow HADGE { emit: multiqc_report = MULTIQC.out.report.toList() // channel: /path/to/multiqc_report.html - versions = ch_versions // channel: [ path(versions.yml) ] + versions = ch_versions // channel: [ path(versions.yml) ] } From 067f0a18b0bc6422c7665ac6b1e83b36a3bfb5de Mon Sep 17 00:00:00 2001 From: Nico Trummer Date: Sun, 22 Feb 2026 12:08:17 +0100 Subject: [PATCH 41/74] Template update 3.5.2 (#103) * Template update for nf-core/tools version 3.3.2 * Template update for nf-core/tools version 3.3.2 * Template update for nf-core/tools version 3.5.2 * Update multiqc --- .github/workflows/awsfulltest.yml | 2 +- .github/workflows/awstest.yml | 2 +- .github/workflows/download_pipeline.yml | 2 +- .github/workflows/fix_linting.yml | 2 +- .github/workflows/linting.yml | 6 +- .github/workflows/nf-test.yml | 16 +++-- .github/workflows/release-announcements.yml | 9 +-- .../workflows/template-version-comment.yml | 2 +- .nf-core.yml | 2 +- .prettierignore | 2 + README.md | 4 +- modules.json | 4 +- modules/nf-core/multiqc/environment.yml | 2 +- modules/nf-core/multiqc/main.nf | 44 +++++------- modules/nf-core/multiqc/meta.yml | 45 +++++++++--- .../multiqc/tests/custom_prefix.config | 5 ++ modules/nf-core/multiqc/tests/main.nf.test | 32 ++++++++- .../nf-core/multiqc/tests/main.nf.test.snap | 58 ++++++++++----- modules/nf-core/multiqc/tests/tags.yml | 2 + ro-crate-metadata.json | 27 ++++--- subworkflows/local/bam_qc/main.nf | 2 +- .../local/genetic_demultiplexing/main.nf | 16 ++--- .../local/hash_demultiplexing/main.nf | 24 +++---- .../local/utils_nfcore_hadge_pipeline/main.nf | 6 +- .../nf-core/utils_nfcore_pipeline/main.nf | 2 +- workflows/hadge.nf | 72 +++++++++++-------- 26 files changed, 245 insertions(+), 145 deletions(-) create mode 100644 modules/nf-core/multiqc/tests/custom_prefix.config create mode 100644 modules/nf-core/multiqc/tests/tags.yml diff --git a/.github/workflows/awsfulltest.yml b/.github/workflows/awsfulltest.yml index 3760e5fc..ce088dcb 100644 --- a/.github/workflows/awsfulltest.yml +++ b/.github/workflows/awsfulltest.yml @@ -40,7 +40,7 @@ jobs: } profiles: test_full - - uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4 + - uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5 with: name: Seqera Platform debug log file path: | diff --git a/.github/workflows/awstest.yml b/.github/workflows/awstest.yml index 49a85adc..5e50ddc1 100644 --- a/.github/workflows/awstest.yml +++ b/.github/workflows/awstest.yml @@ -25,7 +25,7 @@ jobs: } profiles: test - - uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4 + - uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5 with: name: Seqera Platform debug log file path: | diff --git a/.github/workflows/download_pipeline.yml b/.github/workflows/download_pipeline.yml index 6d94bcbf..45884ff9 100644 --- a/.github/workflows/download_pipeline.yml +++ b/.github/workflows/download_pipeline.yml @@ -127,7 +127,7 @@ jobs: fi - name: Upload Nextflow logfile for debugging purposes - uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4 + uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5 with: name: nextflow_logfile.txt path: .nextflow.log* diff --git a/.github/workflows/fix_linting.yml b/.github/workflows/fix_linting.yml index c3c5a317..038fdc92 100644 --- a/.github/workflows/fix_linting.yml +++ b/.github/workflows/fix_linting.yml @@ -13,7 +13,7 @@ jobs: runs-on: ubuntu-latest steps: # Use the @nf-core-bot token to check out so we can push later - - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5 + - uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5 with: token: ${{ secrets.nf_core_bot_auth_token }} diff --git a/.github/workflows/linting.yml b/.github/workflows/linting.yml index 30e66026..7a527a34 100644 --- a/.github/workflows/linting.yml +++ b/.github/workflows/linting.yml @@ -11,7 +11,7 @@ jobs: pre-commit: runs-on: ubuntu-latest steps: - - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5 + - uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5 - name: Set up Python 3.14 uses: actions/setup-python@e797f83bcb11b83ae66e0230d6156d7c80228e7c # v6 @@ -28,7 +28,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Check out pipeline code - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5 + uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5 - name: Install Nextflow uses: nf-core/setup-nextflow@v2 @@ -71,7 +71,7 @@ jobs: - name: Upload linting log file artifact if: ${{ always() }} - uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4 + uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5 with: name: linting-logs path: | diff --git a/.github/workflows/nf-test.yml b/.github/workflows/nf-test.yml index 7ce7dc4a..c98d76ec 100644 --- a/.github/workflows/nf-test.yml +++ b/.github/workflows/nf-test.yml @@ -27,7 +27,9 @@ env: jobs: nf-test-changes: name: nf-test-changes - runs-on: ubuntu-latest + runs-on: # use self-hosted runners + - runs-on=${{ github.run_id }}-nf-test-changes + - runner=4cpu-linux-x64 outputs: shard: ${{ steps.set-shards.outputs.shard }} total_shards: ${{ steps.set-shards.outputs.total_shards }} @@ -38,7 +40,7 @@ jobs: rm -rf ./* || true rm -rf ./.??* || true ls -la ./ - - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5 + - uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5 with: fetch-depth: 0 @@ -59,7 +61,9 @@ jobs: name: "${{ matrix.profile }} | ${{ matrix.NXF_VER }} | ${{ matrix.shard }}/${{ needs.nf-test-changes.outputs.total_shards }}" needs: [nf-test-changes] if: ${{ needs.nf-test-changes.outputs.total_shards != '0' }} - runs-on: ubuntu-latest + runs-on: # use self-hosted runners + - runs-on=${{ github.run_id }}-nf-test + - runner=4cpu-linux-x64 strategy: fail-fast: false matrix: @@ -81,7 +85,7 @@ jobs: TOTAL_SHARDS: ${{ needs.nf-test-changes.outputs.total_shards }} steps: - - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5 + - uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5 with: fetch-depth: 0 @@ -115,7 +119,9 @@ jobs: confirm-pass: needs: [nf-test] if: always() - runs-on: ubuntu-latest + runs-on: # use self-hosted runners + - runs-on=${{ github.run_id }}-confirm-pass + - runner=2cpu-linux-x64 steps: - name: One or more tests failed (excluding latest-everything) if: ${{ contains(needs.*.result, 'failure') }} diff --git a/.github/workflows/release-announcements.yml b/.github/workflows/release-announcements.yml index e64cebd6..431d3d44 100644 --- a/.github/workflows/release-announcements.yml +++ b/.github/workflows/release-announcements.yml @@ -15,10 +15,9 @@ jobs: echo "topics=$(curl -s https://nf-co.re/pipelines.json | jq -r '.remote_workflows[] | select(.full_name == "${{ github.repository }}") | .topics[]' | awk '{print "#"$0}' | tr '\n' ' ')" | sed 's/-//g' >> $GITHUB_OUTPUT - name: get description - id: get_topics + id: get_description run: | - echo "description=$(curl -s https://nf-co.re/pipelines.json | jq -r '.remote_workflows[] | select(.full_name == "${{ github.repository }}") | .description' >> $GITHUB_OUTPUT - + echo "description=$(curl -s https://nf-co.re/pipelines.json | jq -r '.remote_workflows[] | select(.full_name == "${{ github.repository }}") | .description')" >> $GITHUB_OUTPUT - uses: rzr/fediverse-action@master with: access-token: ${{ secrets.MASTODON_ACCESS_TOKEN }} @@ -27,9 +26,7 @@ jobs: # https://docs.github.com/en/developers/webhooks-and-events/webhooks/webhook-events-and-payloads#release message: | Pipeline release! ${{ github.repository }} v${{ github.event.release.tag_name }} - ${{ github.event.release.name }}! - - ${{ steps.get_topics.outputs.description }} - + ${{ steps.get_description.outputs.description }} Please see the changelog: ${{ github.event.release.html_url }} ${{ steps.get_topics.outputs.topics }} #nfcore #openscience #nextflow #bioinformatics diff --git a/.github/workflows/template-version-comment.yml b/.github/workflows/template-version-comment.yml index c5988af9..e8560fc7 100644 --- a/.github/workflows/template-version-comment.yml +++ b/.github/workflows/template-version-comment.yml @@ -9,7 +9,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Check out pipeline code - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5 + uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5 with: ref: ${{ github.event.pull_request.head.sha }} diff --git a/.nf-core.yml b/.nf-core.yml index 6bb5915e..3c5b1920 100644 --- a/.nf-core.yml +++ b/.nf-core.yml @@ -1,7 +1,7 @@ lint: files_unchanged: - .github/PULL_REQUEST_TEMPLATE.md -nf_core_version: 3.4.1 +nf_core_version: 3.5.2 repository_type: pipeline template: author: Fabiola Curion diff --git a/.prettierignore b/.prettierignore index 2255e3e3..dd749d43 100644 --- a/.prettierignore +++ b/.prettierignore @@ -12,3 +12,5 @@ testing* bin/ .nf-test/ ro-crate-metadata.json +modules/nf-core/ +subworkflows/nf-core/ diff --git a/README.md b/README.md index 9dd081e9..0f421e42 100644 --- a/README.md +++ b/README.md @@ -5,13 +5,13 @@ -[![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://github.com/codespaces/new/nf-core/hadge) +[![Open in GitHub Codespaces](https://img.shields.io/badge/Open_In_GitHub_Codespaces-black?labelColor=grey&logo=github)](https://github.com/codespaces/new/nf-core/hadge) [![GitHub Actions CI Status](https://github.com/nf-core/hadge/actions/workflows/nf-test.yml/badge.svg)](https://github.com/nf-core/hadge/actions/workflows/nf-test.yml) [![GitHub Actions Linting Status](https://github.com/nf-core/hadge/actions/workflows/linting.yml/badge.svg)](https://github.com/nf-core/hadge/actions/workflows/linting.yml)[![AWS CI](https://img.shields.io/badge/CI%20tests-full%20size-FF9900?labelColor=000000&logo=Amazon%20AWS)](https://nf-co.re/hadge/results)[![Cite with Zenodo](http://img.shields.io/badge/DOI-10.5281/zenodo.XXXXXXX-1073c8?labelColor=000000)](https://doi.org/10.5281/zenodo.XXXXXXX) [![nf-test](https://img.shields.io/badge/unit_tests-nf--test-337ab7.svg)](https://www.nf-test.com) [![Nextflow](https://img.shields.io/badge/version-%E2%89%A525.04.0-green?style=flat&logo=nextflow&logoColor=white&color=%230DC09D&link=https%3A%2F%2Fnextflow.io)](https://www.nextflow.io/) -[![nf-core template version](https://img.shields.io/badge/nf--core_template-3.4.1-green?style=flat&logo=nfcore&logoColor=white&color=%2324B064&link=https%3A%2F%2Fnf-co.re)](https://github.com/nf-core/tools/releases/tag/3.4.1) +[![nf-core template version](https://img.shields.io/badge/nf--core_template-3.5.2-green?style=flat&logo=nfcore&logoColor=white&color=%2324B064&link=https%3A%2F%2Fnf-co.re)](https://github.com/nf-core/tools/releases/tag/3.5.2) [![run with conda](http://img.shields.io/badge/run%20with-conda-3EB049?labelColor=000000&logo=anaconda)](https://docs.conda.io/en/latest/) [![run with docker](https://img.shields.io/badge/run%20with-docker-0db7ed?labelColor=000000&logo=docker)](https://www.docker.com/) [![run with singularity](https://img.shields.io/badge/run%20with-singularity-1d355c.svg?labelColor=000000)](https://sylabs.io/docs/) diff --git a/modules.json b/modules.json index f7f93ab4..1a3e926f 100644 --- a/modules.json +++ b/modules.json @@ -46,7 +46,7 @@ }, "multiqc": { "branch": "master", - "git_sha": "e10b76ca0c66213581bec2833e30d31f239dec0b", + "git_sha": "575e1a4b51a9bad7a8cd1316a88fb85684ef7c7b", "installed_by": ["modules"] }, "multiseqdemux": { @@ -123,7 +123,7 @@ }, "utils_nfcore_pipeline": { "branch": "master", - "git_sha": "05954dab2ff481bcb999f24455da29a5828af08d", + "git_sha": "271e7fc14eb1320364416d996fb077421f3faed2", "installed_by": ["subworkflows"] }, "utils_nfschema_plugin": { diff --git a/modules/nf-core/multiqc/environment.yml b/modules/nf-core/multiqc/environment.yml index dd513cbd..009874d4 100644 --- a/modules/nf-core/multiqc/environment.yml +++ b/modules/nf-core/multiqc/environment.yml @@ -4,4 +4,4 @@ channels: - conda-forge - bioconda dependencies: - - bioconda::multiqc=1.31 + - bioconda::multiqc=1.33 diff --git a/modules/nf-core/multiqc/main.nf b/modules/nf-core/multiqc/main.nf index 5288f5cc..3b0e975b 100644 --- a/modules/nf-core/multiqc/main.nf +++ b/modules/nf-core/multiqc/main.nf @@ -3,11 +3,11 @@ process MULTIQC { conda "${moduleDir}/environment.yml" container "${ workflow.containerEngine == 'singularity' && !task.ext.singularity_pull_docker_container ? - 'https://community-cr-prod.seqera.io/docker/registry/v2/blobs/sha256/ef/eff0eafe78d5f3b65a6639265a16b89fdca88d06d18894f90fcdb50142004329/data' : - 'community.wave.seqera.io/library/multiqc:1.31--1efbafd542a23882' }" + 'https://community-cr-prod.seqera.io/docker/registry/v2/blobs/sha256/34/34e733a9ae16a27e80fe00f863ea1479c96416017f24a907996126283e7ecd4d/data' : + 'community.wave.seqera.io/library/multiqc:1.33--ee7739d47738383b' }" input: - path multiqc_files, stageAs: "?/*" + path multiqc_files, stageAs: "?/*" path(multiqc_config) path(extra_multiqc_config) path(multiqc_logo) @@ -15,10 +15,11 @@ process MULTIQC { path(sample_names) output: - path "*multiqc_report.html", emit: report - path "*_data" , emit: data - path "*_plots" , optional:true, emit: plots - path "versions.yml" , emit: versions + path "*.html" , emit: report + path "*_data" , emit: data + path "*_plots" , optional:true, emit: plots + tuple val("${task.process}"), val('multiqc'), eval('multiqc --version | sed "s/.* //g"'), emit: versions + // MultiQC should not push its versions to the `versions` topic. Its input depends on the versions topic to be resolved thus outputting to the topic will let the pipeline hang forever when: task.ext.when == null || task.ext.when @@ -26,38 +27,29 @@ process MULTIQC { script: def args = task.ext.args ?: '' def prefix = task.ext.prefix ? "--filename ${task.ext.prefix}.html" : '' - def config = multiqc_config ? "--config $multiqc_config" : '' - def extra_config = extra_multiqc_config ? "--config $extra_multiqc_config" : '' + def config = multiqc_config ? "--config ${multiqc_config}" : '' + def extra_config = extra_multiqc_config ? "--config ${extra_multiqc_config}" : '' def logo = multiqc_logo ? "--cl-config 'custom_logo: \"${multiqc_logo}\"'" : '' def replace = replace_names ? "--replace-names ${replace_names}" : '' def samples = sample_names ? "--sample-names ${sample_names}" : '' """ multiqc \\ --force \\ - $args \\ - $config \\ - $prefix \\ - $extra_config \\ - $logo \\ - $replace \\ - $samples \\ + ${args} \\ + ${config} \\ + ${prefix} \\ + ${extra_config} \\ + ${logo} \\ + ${replace} \\ + ${samples} \\ . - - cat <<-END_VERSIONS > versions.yml - "${task.process}": - multiqc: \$( multiqc --version | sed -e "s/multiqc, version //g" ) - END_VERSIONS """ stub: """ mkdir multiqc_data + touch multiqc_data/.stub mkdir multiqc_plots touch multiqc_report.html - - cat <<-END_VERSIONS > versions.yml - "${task.process}": - multiqc: \$( multiqc --version | sed -e "s/multiqc, version //g" ) - END_VERSIONS """ } diff --git a/modules/nf-core/multiqc/meta.yml b/modules/nf-core/multiqc/meta.yml index ce30eb73..9fd34f37 100644 --- a/modules/nf-core/multiqc/meta.yml +++ b/modules/nf-core/multiqc/meta.yml @@ -57,10 +57,10 @@ input: - edam: http://edamontology.org/format_3475 # TSV output: report: - - "*multiqc_report.html": + - "*.html": type: file description: MultiQC report file - pattern: "multiqc_report.html" + pattern: ".html" ontologies: [] data: - "*_data": @@ -71,15 +71,18 @@ output: - "*_plots": type: file description: Plots created by MultiQC - pattern: "*_data" + pattern: "*_plots" ontologies: [] versions: - - versions.yml: - type: file - description: File containing software versions - pattern: "versions.yml" - ontologies: - - edam: http://edamontology.org/format_3750 # YAML + - - ${task.process}: + type: string + description: The process the versions were collected from + - multiqc: + type: string + description: The tool name + - multiqc --version | sed "s/.* //g": + type: eval + description: The expression to obtain the version of the tool authors: - "@abhi18av" - "@bunop" @@ -90,3 +93,27 @@ maintainers: - "@bunop" - "@drpatelh" - "@jfy133" +containers: + conda: + linux/amd64: + lock_file: https://wave.seqera.io/v1alpha1/builds/bd-ee7739d47738383b_1/condalock + linux/arm64: + lock_file: https://wave.seqera.io/v1alpha1/builds/bd-58d7dee710ab3aa8_1/condalock + docker: + linux/amd64: + build_id: bd-ee7739d47738383b_1 + name: community.wave.seqera.io/library/multiqc:1.33--ee7739d47738383b + scanId: sc-6ddec592dcadd583_4 + linux/arm64: + build_id: bd-58d7dee710ab3aa8_1 + name: community.wave.seqera.io/library/multiqc:1.33--58d7dee710ab3aa8 + scanId: sc-a04c42273e34c55c_2 + singularity: + linux/amd64: + build_id: bd-e3576ddf588fa00d_1 + https: https://community-cr-prod.seqera.io/docker/registry/v2/blobs/sha256/34/34e733a9ae16a27e80fe00f863ea1479c96416017f24a907996126283e7ecd4d/data + name: oras://community.wave.seqera.io/library/multiqc:1.33--e3576ddf588fa00d + linux/arm64: + build_id: bd-2537ca5f8445e3c2_1 + https: https://community-cr-prod.seqera.io/docker/registry/v2/blobs/sha256/78/78b89e91d89e9cc99ad5ade5be311f347838cb2acbfb4f13bc343b170be09ce4/data + name: oras://community.wave.seqera.io/library/multiqc:1.33--2537ca5f8445e3c2 diff --git a/modules/nf-core/multiqc/tests/custom_prefix.config b/modules/nf-core/multiqc/tests/custom_prefix.config new file mode 100644 index 00000000..b30b1358 --- /dev/null +++ b/modules/nf-core/multiqc/tests/custom_prefix.config @@ -0,0 +1,5 @@ +process { + withName: 'MULTIQC' { + ext.prefix = "custom_prefix" + } +} diff --git a/modules/nf-core/multiqc/tests/main.nf.test b/modules/nf-core/multiqc/tests/main.nf.test index 33316a7d..d1ae8b06 100644 --- a/modules/nf-core/multiqc/tests/main.nf.test +++ b/modules/nf-core/multiqc/tests/main.nf.test @@ -30,7 +30,33 @@ nextflow_process { { assert process.success }, { assert process.out.report[0] ==~ ".*/multiqc_report.html" }, { assert process.out.data[0] ==~ ".*/multiqc_data" }, - { assert snapshot(process.out.versions).match("multiqc_versions_single") } + { assert snapshot(process.out.findAll { key, val -> key.startsWith("versions")}).match() } + ) + } + + } + + test("sarscov2 single-end [fastqc] - custom prefix") { + config "./custom_prefix.config" + + when { + process { + """ + input[0] = Channel.of(file(params.modules_testdata_base_path + 'genomics/sarscov2/illumina/fastqc/test_fastqc.zip', checkIfExists: true)) + input[1] = [] + input[2] = [] + input[3] = [] + input[4] = [] + input[5] = [] + """ + } + } + + then { + assertAll( + { assert process.success }, + { assert process.out.report[0] ==~ ".*/custom_prefix.html" }, + { assert process.out.data[0] ==~ ".*/custom_prefix_data" } ) } @@ -56,7 +82,7 @@ nextflow_process { { assert process.success }, { assert process.out.report[0] ==~ ".*/multiqc_report.html" }, { assert process.out.data[0] ==~ ".*/multiqc_data" }, - { assert snapshot(process.out.versions).match("multiqc_versions_config") } + { assert snapshot(process.out.findAll { key, val -> key.startsWith("versions")}).match() } ) } } @@ -84,7 +110,7 @@ nextflow_process { { assert snapshot(process.out.report.collect { file(it).getName() } + process.out.data.collect { file(it).getName() } + process.out.plots.collect { file(it).getName() } + - process.out.versions ).match("multiqc_stub") } + process.out.findAll { key, val -> key.startsWith("versions")} ).match() } ) } diff --git a/modules/nf-core/multiqc/tests/main.nf.test.snap b/modules/nf-core/multiqc/tests/main.nf.test.snap index 17881d15..d72d35b7 100644 --- a/modules/nf-core/multiqc/tests/main.nf.test.snap +++ b/modules/nf-core/multiqc/tests/main.nf.test.snap @@ -1,41 +1,61 @@ { - "multiqc_versions_single": { + "sarscov2 single-end [fastqc]": { "content": [ - [ - "versions.yml:md5,8968b114a3e20756d8af2b80713bcc4f" - ] + { + "versions": [ + [ + "MULTIQC", + "multiqc", + "1.33" + ] + ] + } ], "meta": { - "nf-test": "0.9.2", - "nextflow": "25.04.6" + "nf-test": "0.9.3", + "nextflow": "25.10.2" }, - "timestamp": "2025-09-08T20:57:36.139055243" + "timestamp": "2025-12-09T10:10:43.020315838" }, - "multiqc_stub": { + "sarscov2 single-end [fastqc] - stub": { "content": [ [ "multiqc_report.html", "multiqc_data", "multiqc_plots", - "versions.yml:md5,8968b114a3e20756d8af2b80713bcc4f" + { + "versions": [ + [ + "MULTIQC", + "multiqc", + "1.33" + ] + ] + } ] ], "meta": { - "nf-test": "0.9.2", - "nextflow": "25.04.6" + "nf-test": "0.9.3", + "nextflow": "25.10.2" }, - "timestamp": "2025-09-08T20:59:15.142230631" + "timestamp": "2025-12-09T10:11:14.131950776" }, - "multiqc_versions_config": { + "sarscov2 single-end [fastqc] [config]": { "content": [ - [ - "versions.yml:md5,8968b114a3e20756d8af2b80713bcc4f" - ] + { + "versions": [ + [ + "MULTIQC", + "multiqc", + "1.33" + ] + ] + } ], "meta": { - "nf-test": "0.9.2", - "nextflow": "25.04.6" + "nf-test": "0.9.3", + "nextflow": "25.10.2" }, - "timestamp": "2025-09-08T20:58:29.629087066" + "timestamp": "2025-12-09T10:11:07.15692209" } } \ No newline at end of file diff --git a/modules/nf-core/multiqc/tests/tags.yml b/modules/nf-core/multiqc/tests/tags.yml new file mode 100644 index 00000000..bea6c0d3 --- /dev/null +++ b/modules/nf-core/multiqc/tests/tags.yml @@ -0,0 +1,2 @@ +multiqc: + - modules/nf-core/multiqc/** diff --git a/ro-crate-metadata.json b/ro-crate-metadata.json index 1e7e8f38..0f285955 100644 --- a/ro-crate-metadata.json +++ b/ro-crate-metadata.json @@ -22,8 +22,8 @@ "@id": "./", "@type": "Dataset", "creativeWorkStatus": "InProgress", - "datePublished": "2025-11-14T19:53:15+00:00", - "description": "

    \n \n \n \"nf-core/hadge\"\n \n

    \n\n[![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://github.com/codespaces/new/nf-core/hadge)\n[![GitHub Actions CI Status](https://github.com/nf-core/hadge/actions/workflows/nf-test.yml/badge.svg)](https://github.com/nf-core/hadge/actions/workflows/nf-test.yml)\n[![GitHub Actions Linting Status](https://github.com/nf-core/hadge/actions/workflows/linting.yml/badge.svg)](https://github.com/nf-core/hadge/actions/workflows/linting.yml)[![AWS CI](https://img.shields.io/badge/CI%20tests-full%20size-FF9900?labelColor=000000&logo=Amazon%20AWS)](https://nf-co.re/hadge/results)[![Cite with Zenodo](http://img.shields.io/badge/DOI-10.5281/zenodo.XXXXXXX-1073c8?labelColor=000000)](https://doi.org/10.5281/zenodo.XXXXXXX)\n[![nf-test](https://img.shields.io/badge/unit_tests-nf--test-337ab7.svg)](https://www.nf-test.com)\n\n[![Nextflow](https://img.shields.io/badge/version-%E2%89%A525.04.0-green?style=flat&logo=nextflow&logoColor=white&color=%230DC09D&link=https%3A%2F%2Fnextflow.io)](https://www.nextflow.io/)\n[![nf-core template version](https://img.shields.io/badge/nf--core_template-3.4.1-green?style=flat&logo=nfcore&logoColor=white&color=%2324B064&link=https%3A%2F%2Fnf-co.re)](https://github.com/nf-core/tools/releases/tag/3.4.1)\n[![run with conda](http://img.shields.io/badge/run%20with-conda-3EB049?labelColor=000000&logo=anaconda)](https://docs.conda.io/en/latest/)\n[![run with docker](https://img.shields.io/badge/run%20with-docker-0db7ed?labelColor=000000&logo=docker)](https://www.docker.com/)\n[![run with singularity](https://img.shields.io/badge/run%20with-singularity-1d355c.svg?labelColor=000000)](https://sylabs.io/docs/)\n[![Launch on Seqera Platform](https://img.shields.io/badge/Launch%20%F0%9F%9A%80-Seqera%20Platform-%234256e7)](https://cloud.seqera.io/launch?pipeline=https://github.com/nf-core/hadge)\n\n[![Get help on Slack](http://img.shields.io/badge/slack-nf--core%20%23hadge-4A154B?labelColor=000000&logo=slack)](https://nfcore.slack.com/channels/hadge)[![Follow on Bluesky](https://img.shields.io/badge/bluesky-%40nf__core-1185fe?labelColor=000000&logo=bluesky)](https://bsky.app/profile/nf-co.re)[![Follow on Mastodon](https://img.shields.io/badge/mastodon-nf__core-6364ff?labelColor=FFFFFF&logo=mastodon)](https://mstdn.science/@nf_core)[![Watch on YouTube](http://img.shields.io/badge/youtube-nf--core-FF0000?labelColor=000000&logo=youtube)](https://www.youtube.com/c/nf-core)\n\n## Introduction\n\n**nf-core/hadge** (**ha**shing **d**econvolution combined with **ge**notype information) is a bioinformatics pipeline that combines 11 methods to perform both hashing- and genotype-based deconvolution on single cell multiplexing data.\nIt takes a samplesheet with count matrices, BAM and VCF files as input, performs deconvolution with every method, joins all results and finally recovers previously discarded cells by combining the best performing methods (donor matching).\n\n![nf-core/hadge metro map](docs/images/pipeline.png)\n\n1. Untar matrices\n2. Extract hto names from matrix\n3. Perform genetic-based deconvolution\n 1. Get single cell genotype [`cellSNP`](https://github.com/single-cell-genetics/cellSNP)\n 2. [`vireo`](https://github.com/single-cell-genetics/vireo)\n 3. [`demuxlet`](https://github.com/statgen/popscle)\n 4. [`freemuxlet`](https://github.com/statgen/popscle)\n 5. [`souporcell`](https://github.com/wheaton5/souporcell)\n4. summarize assignments and classifications\n5. Perform hashing-based deconvolution\n 1. [`htodemux`](https://satijalab.org/seurat/articles/hashing_vignette)\n 2. [`multiseq`](https://satijalab.org/seurat/reference/multiseqdemux)\n 3. [`bff`](https://github.com/BimberLab/cellhashR)\n 4. [`demuxem`](https://demuxem.readthedocs.io/en/latest/)\n 5. [`gmm-demux`](https://github.com/CHPGenetics/GMM-demux)\n 6. [`hasheddrops`](https://github.com/MarioniLab/DropletUtils)\n 7. [`hashsolo`](https://scanpy.readthedocs.io/en/stable/generated/scanpy.external.pp.hashsolo.html)\n6. summarize assignments and classifications\n7. Join all results\n8. Donor match\n9. Find informative variants\n10. Create AnnData and Mudata objects\n11. [`MultiQC`](http://multiqc.info/)\n\n## Usage\n\n> [!NOTE]\n> If you are new to Nextflow and nf-core, please refer to [this page](https://nf-co.re/docs/usage/installation) on how to set-up Nextflow. Make sure to [test your setup](https://nf-co.re/docs/usage/introduction#how-to-run-a-pipeline) with `-profile test` before running the workflow on actual data. The profile `test` is used to test hadge's rescue mode, but you can also test the other modes with the profiles `test_genetic`, `test_hashing` and `test_donor_match`.\n\nFirst, prepare a samplesheet with your input data that looks as follows:\n\n`samplesheet.csv`:\n\n```csv\nsample,rna_matrix,hto_matrix,bam,vcf,n_samples,barcodes\nid1,rna.tar.gz,hto.tar.gz,chr21.bam,donor_genotype_chr21.vcf,2,barcodes.tsv\nid2,rna.tar.gz,hto.tar.gz,chr21.bam,donor_genotype_chr21.vcf,2,barcodes.tsv\nid3,rna.tar.gz,hto.tar.gz,chr21.bam,donor_genotype_chr21.vcf,2,barcodes.tsv\n```\n\nEach row contains data from a single-cell multiplexing experiment. The RNA-seq (`rna_matrix`) and hashing (`hto_matrix`) count matrices are provided in a 10x Genomics format and compressed as `.tar.gz`.\nGenetic deconvolution requires both the alignment file (`bam`) and a list of common SNPs (`vcf`). Users must specify the number of multiplexed donors (`n_samples`) and identify the target cells for deconvolution (`barcodes`).\n\nNow, you can run the pipeline using:\n\n```bash\nnextflow run nf-core/hadge \\\n -profile \\\n --input samplesheet.csv \\\n --outdir \\\n --mode rescue \\\n --hash_tools htodemux,hasheddrops,multiseq,gmm-demux,bff,hashsolo \\\n --genetic_tools demuxlet,freemuxlet,vireo,souporcell \\\n --fasta \n```\n\n> [!WARNING]\n> Please provide pipeline parameters via the CLI or Nextflow `-params-file` option. Custom config files including those provided by the `-c` Nextflow option can be used to provide any configuration _**except for parameters**_; see [docs](https://nf-co.re/docs/usage/getting_started/configuration#custom-configuration-files).\n\nFor more details and further functionality, please refer to the [usage documentation](https://nf-co.re/hadge/usage) and the [parameter documentation](https://nf-co.re/hadge/parameters).\n\n## Pipeline output\n\nTo see the results of an example test run with a full size dataset refer to the [results](https://nf-co.re/hadge/results) tab on the nf-core website pipeline page.\nFor more details about the output files and reports, please refer to the\n[output documentation](https://nf-co.re/hadge/output).\n\n## Credits\n\nnf-core/hadge was originally written by Fabiola Curion ([@bio-la](https://github.com/bio-la)), Xichen Wu ([@wxicu](https://github.com/wxicu)), Lukas Heumos ([@zethson](https://github.com/Zethson)) and Mariana Gonzales Andre ([@mari-ga](https://github.com/mari-ga)).\n\nWe thank the following people for their extensive assistance in the development of this pipeline:\n\n- [Luis Heinzlmeier](https://github.com/LuisHeinzlmeier)\n- [Nico Trummer](https://github.com/nictru)\n- [Seo Hyon Kim](https://github.com/seohyonkim)\n\n\n## Contributions and Support\n\nIf you would like to contribute to this pipeline, please see the [contributing guidelines](.github/CONTRIBUTING.md).\n\nFor further information or help, don't hesitate to get in touch on the [Slack `#hadge` channel](https://nfcore.slack.com/channels/hadge) (you can join with [this invite](https://nf-co.re/join/slack)).\n\n## Citations\n\n\n\n\n\n\nAn extensive list of references for the tools used by the pipeline can be found in the [`CITATIONS.md`](CITATIONS.md) file.\n\nYou can cite the `nf-core` publication as follows:\n\n> **The nf-core framework for community-curated bioinformatics pipelines.**\n>\n> Philip Ewels, Alexander Peltzer, Sven Fillinger, Harshil Patel, Johannes Alneberg, Andreas Wilm, Maxime Ulysse Garcia, Paolo Di Tommaso & Sven Nahnsen.\n>\n> _Nat Biotechnol._ 2020 Feb 13. doi: [10.1038/s41587-020-0439-x](https://dx.doi.org/10.1038/s41587-020-0439-x).\n", + "datePublished": "2026-02-15T09:55:13+00:00", + "description": "

    \n \n \n \"nf-core/hadge\"\n \n

    \n\n[![Open in GitHub Codespaces](https://img.shields.io/badge/Open_In_GitHub_Codespaces-black?labelColor=grey&logo=github)](https://github.com/codespaces/new/nf-core/hadge)\n[![GitHub Actions CI Status](https://github.com/nf-core/hadge/actions/workflows/nf-test.yml/badge.svg)](https://github.com/nf-core/hadge/actions/workflows/nf-test.yml)\n[![GitHub Actions Linting Status](https://github.com/nf-core/hadge/actions/workflows/linting.yml/badge.svg)](https://github.com/nf-core/hadge/actions/workflows/linting.yml)[![AWS CI](https://img.shields.io/badge/CI%20tests-full%20size-FF9900?labelColor=000000&logo=Amazon%20AWS)](https://nf-co.re/hadge/results)[![Cite with Zenodo](http://img.shields.io/badge/DOI-10.5281/zenodo.XXXXXXX-1073c8?labelColor=000000)](https://doi.org/10.5281/zenodo.XXXXXXX)\n[![nf-test](https://img.shields.io/badge/unit_tests-nf--test-337ab7.svg)](https://www.nf-test.com)\n\n[![Nextflow](https://img.shields.io/badge/version-%E2%89%A525.04.0-green?style=flat&logo=nextflow&logoColor=white&color=%230DC09D&link=https%3A%2F%2Fnextflow.io)](https://www.nextflow.io/)\n[![nf-core template version](https://img.shields.io/badge/nf--core_template-3.5.2-green?style=flat&logo=nfcore&logoColor=white&color=%2324B064&link=https%3A%2F%2Fnf-co.re)](https://github.com/nf-core/tools/releases/tag/3.5.2)\n[![run with conda](http://img.shields.io/badge/run%20with-conda-3EB049?labelColor=000000&logo=anaconda)](https://docs.conda.io/en/latest/)\n[![run with docker](https://img.shields.io/badge/run%20with-docker-0db7ed?labelColor=000000&logo=docker)](https://www.docker.com/)\n[![run with singularity](https://img.shields.io/badge/run%20with-singularity-1d355c.svg?labelColor=000000)](https://sylabs.io/docs/)\n[![Launch on Seqera Platform](https://img.shields.io/badge/Launch%20%F0%9F%9A%80-Seqera%20Platform-%234256e7)](https://cloud.seqera.io/launch?pipeline=https://github.com/nf-core/hadge)\n\n[![Get help on Slack](http://img.shields.io/badge/slack-nf--core%20%23hadge-4A154B?labelColor=000000&logo=slack)](https://nfcore.slack.com/channels/hadge)[![Follow on Bluesky](https://img.shields.io/badge/bluesky-%40nf__core-1185fe?labelColor=000000&logo=bluesky)](https://bsky.app/profile/nf-co.re)[![Follow on Mastodon](https://img.shields.io/badge/mastodon-nf__core-6364ff?labelColor=FFFFFF&logo=mastodon)](https://mstdn.science/@nf_core)[![Watch on YouTube](http://img.shields.io/badge/youtube-nf--core-FF0000?labelColor=000000&logo=youtube)](https://www.youtube.com/c/nf-core)\n\n## Introduction\n\n**nf-core/hadge** is a bioinformatics pipeline that ...\n\n\n\n\n2. Present QC for raw reads ([`MultiQC`](http://multiqc.info/))\n\n## Usage\n\n> [!NOTE]\n> If you are new to Nextflow and nf-core, please refer to [this page](https://nf-co.re/docs/usage/installation) on how to set-up Nextflow. Make sure to [test your setup](https://nf-co.re/docs/usage/introduction#how-to-run-a-pipeline) with `-profile test` before running the workflow on actual data.\n\n\n\nNow, you can run the pipeline using:\n\n\n\n```bash\nnextflow run nf-core/hadge \\\n -profile \\\n --input samplesheet.csv \\\n --outdir \n```\n\n> [!WARNING]\n> Please provide pipeline parameters via the CLI or Nextflow `-params-file` option. Custom config files including those provided by the `-c` Nextflow option can be used to provide any configuration _**except for parameters**_; see [docs](https://nf-co.re/docs/usage/getting_started/configuration#custom-configuration-files).\n\nFor more details and further functionality, please refer to the [usage documentation](https://nf-co.re/hadge/usage) and the [parameter documentation](https://nf-co.re/hadge/parameters).\n\n## Pipeline output\n\nTo see the results of an example test run with a full size dataset refer to the [results](https://nf-co.re/hadge/results) tab on the nf-core website pipeline page.\nFor more details about the output files and reports, please refer to the\n[output documentation](https://nf-co.re/hadge/output).\n\n## Credits\n\nnf-core/hadge was originally written by Fabiola Curion.\n\nWe thank the following people for their extensive assistance in the development of this pipeline:\n\n\n\n## Contributions and Support\n\nIf you would like to contribute to this pipeline, please see the [contributing guidelines](.github/CONTRIBUTING.md).\n\nFor further information or help, don't hesitate to get in touch on the [Slack `#hadge` channel](https://nfcore.slack.com/channels/hadge) (you can join with [this invite](https://nf-co.re/join/slack)).\n\n## Citations\n\n\n\n\n\n\nAn extensive list of references for the tools used by the pipeline can be found in the [`CITATIONS.md`](CITATIONS.md) file.\n\nYou can cite the `nf-core` publication as follows:\n\n> **The nf-core framework for community-curated bioinformatics pipelines.**\n>\n> Philip Ewels, Alexander Peltzer, Sven Fillinger, Harshil Patel, Johannes Alneberg, Andreas Wilm, Maxime Ulysse Garcia, Paolo Di Tommaso & Sven Nahnsen.\n>\n> _Nat Biotechnol._ 2020 Feb 13. doi: [10.1038/s41587-020-0439-x](https://dx.doi.org/10.1038/s41587-020-0439-x).\n", "hasPart": [ { "@id": "main.nf" @@ -99,7 +99,7 @@ }, "mentions": [ { - "@id": "#c8e976d2-368f-4e69-9d0b-23329bcac81d" + "@id": "#1d18b14c-d918-49cb-b91e-91fc45a4219e" } ], "name": "nf-core/hadge" @@ -127,11 +127,22 @@ "ComputationalWorkflow" ], "dateCreated": "", - "dateModified": "2025-11-14T20:53:15Z", + "dateModified": "2026-02-15T10:55:13Z", "dct:conformsTo": "https://bioschemas.org/profiles/ComputationalWorkflow/1.0-RELEASE/", "keywords": [ "nf-core", - "nextflow" + "nextflow", + "cell-hashing", + "deconvolution", + "demultiplexing", + "donor-assignment", + "donor-deconvolution", + "genetic-demultiplexing", + "genotype-deconvolution", + "hashing-deconvolution", + "hashtag-oligonucleotides", + "hto", + "single-cell" ], "license": [ "MIT" @@ -166,11 +177,11 @@ "version": "!>=25.04.0" }, { - "@id": "#c8e976d2-368f-4e69-9d0b-23329bcac81d", + "@id": "#1d18b14c-d918-49cb-b91e-91fc45a4219e", "@type": "TestSuite", "instance": [ { - "@id": "#64905bd3-87de-4353-9f61-8b940ce51a05" + "@id": "#2943e3c5-d2b0-4ca5-9d8a-5e951374a437" } ], "mainEntity": { @@ -179,7 +190,7 @@ "name": "Test suite for nf-core/hadge" }, { - "@id": "#64905bd3-87de-4353-9f61-8b940ce51a05", + "@id": "#2943e3c5-d2b0-4ca5-9d8a-5e951374a437", "@type": "TestInstance", "name": "GitHub Actions workflow for testing nf-core/hadge", "resource": "repos/nf-core/hadge/actions/workflows/nf-test.yml", diff --git a/subworkflows/local/bam_qc/main.nf b/subworkflows/local/bam_qc/main.nf index e6a1e8ac..35032ccb 100644 --- a/subworkflows/local/bam_qc/main.nf +++ b/subworkflows/local/bam_qc/main.nf @@ -9,7 +9,7 @@ workflow BAM_QC { main: - ch_versions = Channel.empty() + ch_versions = channel.empty() SAMTOOLS_VIEW(ch_bam.map { meta, bam -> [meta, bam, []] }, [[], []], [], 'bai') ch_versions = ch_versions.mix(SAMTOOLS_VIEW.out.versions) diff --git a/subworkflows/local/genetic_demultiplexing/main.nf b/subworkflows/local/genetic_demultiplexing/main.nf index b1fd2483..f3553579 100644 --- a/subworkflows/local/genetic_demultiplexing/main.nf +++ b/subworkflows/local/genetic_demultiplexing/main.nf @@ -18,14 +18,14 @@ workflow GENETIC_DEMULTIPLEXING { fasta // file: /path/to/genome.fasta main: - ch_versions = Channel.empty() - ch_vireo = Channel.empty() - ch_demuxlet = Channel.empty() - ch_freemuxlet = Channel.empty() - ch_souporcell = Channel.empty() - ch_gt_cells = Channel.empty() - ch_gt_donors = Channel.empty() - ch_vireo_filtered_variants = Channel.empty() + ch_versions = channel.empty() + ch_vireo = channel.empty() + ch_demuxlet = channel.empty() + ch_freemuxlet = channel.empty() + ch_souporcell = channel.empty() + ch_gt_cells = channel.empty() + ch_gt_donors = channel.empty() + ch_vireo_filtered_variants = channel.empty() ch_summary = ch_samplesheet.map{ meta, _bam, barcodes, _vcf -> [meta, barcodes] diff --git a/subworkflows/local/hash_demultiplexing/main.nf b/subworkflows/local/hash_demultiplexing/main.nf index cdd60a8d..06b2c351 100644 --- a/subworkflows/local/hash_demultiplexing/main.nf +++ b/subworkflows/local/hash_demultiplexing/main.nf @@ -19,18 +19,18 @@ workflow HASH_DEMULTIPLEXING { main: - ch_versions = Channel.empty() - - ch_htodemux_assignments = Channel.empty() - ch_htodemux_classifications = Channel.empty() - ch_multiseq = Channel.empty() - ch_bff = Channel.empty() - ch_demuxem = Channel.empty() - ch_gmmdemux_results = Channel.empty() - ch_gmmdemux_config = Channel.empty() - ch_hasheddrops_results = Channel.empty() - ch_hasheddrops_id_to_hash = Channel.empty() - ch_hashsolo = Channel.empty() + ch_versions = channel.empty() + + ch_htodemux_assignments = channel.empty() + ch_htodemux_classifications = channel.empty() + ch_multiseq = channel.empty() + ch_bff = channel.empty() + ch_demuxem = channel.empty() + ch_gmmdemux_results = channel.empty() + ch_gmmdemux_config = channel.empty() + ch_hasheddrops_results = channel.empty() + ch_hasheddrops_id_to_hash = channel.empty() + ch_hashsolo = channel.empty() if (methods.contains('htodemux') || methods.contains('multiseq')) { diff --git a/subworkflows/local/utils_nfcore_hadge_pipeline/main.nf b/subworkflows/local/utils_nfcore_hadge_pipeline/main.nf index 6f053e2f..e1b47db2 100644 --- a/subworkflows/local/utils_nfcore_hadge_pipeline/main.nf +++ b/subworkflows/local/utils_nfcore_hadge_pipeline/main.nf @@ -38,7 +38,7 @@ workflow PIPELINE_INITIALISATION { main: - ch_versions = Channel.empty() + ch_versions = channel.empty() // // Print version and exit if required and dump pipeline parameters to JSON file @@ -63,7 +63,7 @@ workflow PIPELINE_INITIALISATION { \033[0;35m nf-core/hadge ${workflow.manifest.version}\033[0m -\033[2m----------------------------------------------------\033[0m- """ - after_text = """${workflow.manifest.doi ? "\n* The pipeline\n" : ""}${workflow.manifest.doi.tokenize(",").collect { " https://doi.org/${it.trim().replace('https://doi.org/','')}"}.join("\n")}${workflow.manifest.doi ? "\n" : ""} + after_text = """${workflow.manifest.doi ? "\n* The pipeline\n" : ""}${workflow.manifest.doi.tokenize(",").collect { doi -> " https://doi.org/${doi.trim().replace('https://doi.org/','')}"}.join("\n")}${workflow.manifest.doi ? "\n" : ""} * The nf-core framework https://doi.org/10.1038/s41587-020-0439-x @@ -100,7 +100,7 @@ workflow PIPELINE_INITIALISATION { // Create channel from input file provided through params.input // - Channel.fromList(samplesheetToList(params.input, "${projectDir}/assets/schema_input.json")) + channel.fromList(samplesheetToList(params.input, "${projectDir}/assets/schema_input.json")) .map { samplesheet -> validateInputSamplesheet(samplesheet) } diff --git a/subworkflows/nf-core/utils_nfcore_pipeline/main.nf b/subworkflows/nf-core/utils_nfcore_pipeline/main.nf index bfd25876..2f30e9a4 100644 --- a/subworkflows/nf-core/utils_nfcore_pipeline/main.nf +++ b/subworkflows/nf-core/utils_nfcore_pipeline/main.nf @@ -98,7 +98,7 @@ def workflowVersionToYAML() { // Get channel of software versions used in pipeline in YAML format // def softwareVersionsToYAML(ch_versions) { - return ch_versions.unique().map { version -> processVersionsFromYAML(version) }.unique().mix(Channel.of(workflowVersionToYAML())) + return ch_versions.unique().map { version -> processVersionsFromYAML(version) }.unique().mix(channel.of(workflowVersionToYAML())) } // diff --git a/workflows/hadge.nf b/workflows/hadge.nf index 9d2bbbec..ebed87c1 100644 --- a/workflows/hadge.nf +++ b/workflows/hadge.nf @@ -33,11 +33,11 @@ workflow HADGE { main: - ch_versions = Channel.empty() - ch_donor_match = Channel.empty() - ch_find_variants = Channel.empty() - ch_subset_gt_donors = Channel.empty() - ch_multiqc_files = Channel.empty() + ch_versions = channel.empty() + ch_donor_match = channel.empty() + ch_find_variants = channel.empty() + ch_subset_gt_donors = channel.empty() + ch_multiqc_files = channel.empty() // ------------------------------ preprocessing start ------------------------------- // untar matrices @@ -253,7 +253,25 @@ workflow HADGE { // // Collate and save software versions // - softwareVersionsToYAML(ch_versions) + def topic_versions = channel.topic("versions") + .distinct() + .branch { entry -> + versions_file: entry instanceof Path + versions_tuple: true + } + + def topic_versions_string = topic_versions.versions_tuple + .map { process, tool, version -> + [ process[process.lastIndexOf(':')+1..-1], " ${tool}: ${version}" ] + } + .groupTuple(by:0) + .map { process, tool_versions -> + tool_versions.unique().sort() + "${process}:\n${tool_versions.join('\n')}" + } + + softwareVersionsToYAML(ch_versions.mix(topic_versions.versions_file)) + .mix(topic_versions_string) .collectFile( storeDir: "${params.outdir}/pipeline_info", name: 'nf_core_' + 'hadge_software_' + 'mqc_' + 'versions.yml', @@ -265,31 +283,25 @@ workflow HADGE { // // MODULE: MultiQC // - ch_multiqc_config = Channel.fromPath( - "${projectDir}/assets/multiqc_config.yml", - checkIfExists: true - ) - ch_multiqc_custom_config = params.multiqc_config - ? Channel.fromPath(params.multiqc_config, checkIfExists: true) - : Channel.empty() - ch_multiqc_logo = params.multiqc_logo - ? Channel.fromPath(params.multiqc_logo, checkIfExists: true) - : Channel.empty() - - summary_params = paramsSummaryMap( - workflow, - parameters_schema: "nextflow_schema.json" - ) - ch_workflow_summary = Channel.value(paramsSummaryMultiqc(summary_params)) + ch_multiqc_config = channel.fromPath( + "$projectDir/assets/multiqc_config.yml", checkIfExists: true) + ch_multiqc_custom_config = params.multiqc_config ? + channel.fromPath(params.multiqc_config, checkIfExists: true) : + channel.empty() + ch_multiqc_logo = params.multiqc_logo ? + channel.fromPath(params.multiqc_logo, checkIfExists: true) : + channel.empty() + + summary_params = paramsSummaryMap( + workflow, parameters_schema: "nextflow_schema.json") + ch_workflow_summary = channel.value(paramsSummaryMultiqc(summary_params)) ch_multiqc_files = ch_multiqc_files.mix( - ch_workflow_summary.collectFile(name: 'workflow_summary_mqc.yaml') - ) - ch_multiqc_custom_methods_description = params.multiqc_methods_description - ? file(params.multiqc_methods_description, checkIfExists: true) - : file("${projectDir}/assets/methods_description_template.yml", checkIfExists: true) - ch_methods_description = Channel.value( - methodsDescriptionText(ch_multiqc_custom_methods_description) - ) + ch_workflow_summary.collectFile(name: 'workflow_summary_mqc.yaml')) + ch_multiqc_custom_methods_description = params.multiqc_methods_description ? + file(params.multiqc_methods_description, checkIfExists: true) : + file("$projectDir/assets/methods_description_template.yml", checkIfExists: true) + ch_methods_description = channel.value( + methodsDescriptionText(ch_multiqc_custom_methods_description)) ch_multiqc_files = ch_multiqc_files.mix(ch_collated_versions) ch_multiqc_files = ch_multiqc_files.mix( From c70dc6a7421dd5c18bec52d5e2db3abed08d5526 Mon Sep 17 00:00:00 2001 From: LuisHeinzlmeier Date: Wed, 24 Jun 2026 13:50:09 +0200 Subject: [PATCH 42/74] Test with larger nf-core runners (#101) * add larger nf-core runners * add zenodo and citation * update citation * add missing bam files to docs * add random comment to trigger CI tests * remove nf-core TODO's * nf-core pipelines lint --fix rocrate_readme_sync * use BibTeX for citations * try another style for citations * try another style for citations (pre-commit) * change dropdown syntax to html * adjust syntax * add detailed docs for demuxlet * update format * update format 2 * Fix Numba caching error in container by setting NUMBA_CACHE_DIR * setup test_full * fix hash modules for full_test compatibility * remove demuxem TODOs * update tests and snapshots * current test setup * reduce computing/storage resources of test and test_full * closes #107 * update nextflow_schema.json * update docs * update test_full input * nf-core pipelines lint --fix rocrate_readme_sync * update snapshots * remove outdir from config files * update snapshots * increase volume of self-hosted runners * Remove aws.client.anonymous from global nextflow.config in concordance with template * fine tune vireo configurations * increase runner volume and update test snapshot with all tools * set plp as path input * use plp dir as input to fix aws error * new try * new try 2 * remove time constraint for souporcell * increase time for souporcell * update readme --------- Co-authored-by: Nico Trummer --- .github/workflows/nf-test.yml | 1 + README.md | 32 +- bin/update_snapshots.sh | 4 +- conf/test.config | 5 +- conf/test_donor_match.config | 8 +- conf/test_full.config | 35 +- conf/test_genetic.config | 1 - conf/test_hashing.config | 4 +- docs/usage.md | 62 +- main.nf | 3 - .../templates/create_anndata_mudata.py | 3 + .../mtxconvert/templates/convert.R | 14 - .../hash_summary/templates/hash_summary.py | 29 +- .../templates/htodemux_visualization.R | 6 +- modules/nf-core/popscle/demuxlet/main.nf | 4 +- nextflow.config | 6 - nextflow_schema.json | 101 +-- .../local/genetic_demultiplexing/main.nf | 2 +- tests/default.nf.test.snap | 716 ++++++++++++++---- tests/test_donor_match.nf.test.snap | 8 +- tests/test_genetic.nf.test.snap | 74 +- tests/test_hashing.nf.test.snap | 282 +++++-- 22 files changed, 1003 insertions(+), 397 deletions(-) diff --git a/.github/workflows/nf-test.yml b/.github/workflows/nf-test.yml index c98d76ec..ff6ef634 100644 --- a/.github/workflows/nf-test.yml +++ b/.github/workflows/nf-test.yml @@ -64,6 +64,7 @@ jobs: runs-on: # use self-hosted runners - runs-on=${{ github.run_id }}-nf-test - runner=4cpu-linux-x64 + - volume=80gb strategy: fail-fast: false matrix: diff --git a/README.md b/README.md index 0f421e42..6ceec537 100644 --- a/README.md +++ b/README.md @@ -7,7 +7,7 @@ [![Open in GitHub Codespaces](https://img.shields.io/badge/Open_In_GitHub_Codespaces-black?labelColor=grey&logo=github)](https://github.com/codespaces/new/nf-core/hadge) [![GitHub Actions CI Status](https://github.com/nf-core/hadge/actions/workflows/nf-test.yml/badge.svg)](https://github.com/nf-core/hadge/actions/workflows/nf-test.yml) -[![GitHub Actions Linting Status](https://github.com/nf-core/hadge/actions/workflows/linting.yml/badge.svg)](https://github.com/nf-core/hadge/actions/workflows/linting.yml)[![AWS CI](https://img.shields.io/badge/CI%20tests-full%20size-FF9900?labelColor=000000&logo=Amazon%20AWS)](https://nf-co.re/hadge/results)[![Cite with Zenodo](http://img.shields.io/badge/DOI-10.5281/zenodo.XXXXXXX-1073c8?labelColor=000000)](https://doi.org/10.5281/zenodo.XXXXXXX) +[![GitHub Actions Linting Status](https://github.com/nf-core/hadge/actions/workflows/linting.yml/badge.svg)](https://github.com/nf-core/hadge/actions/workflows/linting.yml)[![AWS CI](https://img.shields.io/badge/CI%20tests-full%20size-FF9900?labelColor=000000&logo=Amazon%20AWS)](https://nf-co.re/hadge/results)[![Cite with Zenodo](http://img.shields.io/badge/DOI-10.5281/zenodo.10634731-1073c8?labelColor=000000)](https://doi.org/10.5281/zenodo.10634731) [![nf-test](https://img.shields.io/badge/unit_tests-nf--test-337ab7.svg)](https://www.nf-test.com) [![Nextflow](https://img.shields.io/badge/version-%E2%89%A525.04.0-green?style=flat&logo=nextflow&logoColor=white&color=%230DC09D&link=https%3A%2F%2Fnextflow.io)](https://www.nextflow.io/) @@ -97,12 +97,11 @@ For more details about the output files and reports, please refer to the nf-core/hadge was originally written by Fabiola Curion ([@bio-la](https://github.com/bio-la)), Xichen Wu ([@wxicu](https://github.com/wxicu)), Lukas Heumos ([@zethson](https://github.com/Zethson)) and Mariana Gonzales Andre ([@mari-ga](https://github.com/mari-ga)). -We thank the following people for their extensive assistance in the development of this pipeline: +We thank the following people for rewriting the original pipeline within the nf-core framework: - [Luis Heinzlmeier](https://github.com/LuisHeinzlmeier) - [Nico Trummer](https://github.com/nictru) - [Seo Hyon Kim](https://github.com/seohyonkim) - ## Contributions and Support @@ -112,10 +111,31 @@ For further information or help, don't hesitate to get in touch on the [Slack `# ## Citations - - +If you use nf-core/hadge for your analysis, please cite it as follows: - +> **hadge: a comprehensive pipeline for donor deconvolution in single-cell studies.** +> +> Fabiola Curion, Xichen Wu, Lukas Heumos, Mariana Gonzales Andre, Lennard Halle, Melissa Grant-Peters, Charlotte Rich-Griffin, Hing-Yuen Yeung, Calliope A. Dendrou, Herbert B. Schiller & Fabian J. Theis. +> +> _Genome Biol._ 2024 Apr 26. doi: [10.1186/s13059-024-03249-z](https://doi.org/10.1186/s13059-024-03249-z). + +
    BibTeX + +```bibtex +@article{curion2024hadge, + title={hadge: a comprehensive pipeline for donor deconvolution in single-cell studies}, + author={Curion, Fabiola and Wu, Xichen and Heumos, Lukas and Andr{\'e}, Mylene Mariana Gonzales and Halle, Lennard and Ozols, Matiss and Grant-Peters, Melissa and Rich-Griffin, Charlotte and Yeung, Hing-Yuen and Dendrou, Calliope A and others}, + journal={Genome Biology}, + volume={25}, + number={1}, + pages={109}, + year={2024}, + publisher={Springer} +} + +``` + +
    An extensive list of references for the tools used by the pipeline can be found in the [`CITATIONS.md`](CITATIONS.md) file. diff --git a/bin/update_snapshots.sh b/bin/update_snapshots.sh index 7a3b3b93..ae105a2e 100755 --- a/bin/update_snapshots.sh +++ b/bin/update_snapshots.sh @@ -24,7 +24,7 @@ for test_file in "${test_files[@]}"; do test_profile="test" fi - command="nf-test test tests/${test_file}.nf.test --profile ${test_profile},docker --update-snapshot" + command="nf-test test tests/${test_file}.nf.test --profile ${test_profile},apptainer --update-snapshot" echo "Updating snapshot for: $test_file" echo "Running: ${command}" @@ -36,7 +36,7 @@ for test_file in "${test_files[@]}"; do # test if testing is consistent if [[ "$CHECK_CONSISTENCY" == "true" ]]; then echo "Re-running test to verify snapshot consistency for: $test_file" - command="nf-test test tests/${test_file}.nf.test --profile ${test_profile},docker" + command="nf-test test tests/${test_file}.nf.test --profile ${test_profile},apptainer" echo "Running: ${command}" eval "$command" echo "✓ Consistency check passed for: $test_file" diff --git a/conf/test.config b/conf/test.config index bbef78f3..fcea5aff 100644 --- a/conf/test.config +++ b/conf/test.config @@ -24,12 +24,11 @@ params { // Input data mode = 'rescue' - hash_tools = 'hasheddrops,bff,gmm-demux' - genetic_tools = 'freemuxlet,vireo,souporcell' + hash_tools = 'htodemux,hasheddrops,multiseq,demuxem,gmm-demux,bff,hashsolo' + genetic_tools = 'demuxlet,freemuxlet,vireo,souporcell' input = 'https://github.com/nf-core/test-datasets/raw/refs/heads/hadge/samplesheet/samplesheet_rescue.csv' genome = 'GRCh38' fasta = 'https://raw.githubusercontent.com/nf-core/test-datasets/modules/data/genomics/homo_sapiens/genome/chr21/sequence/genome.fasta' - bam_qc = true // all possible modules // hash_tools = 'htodemux,hasheddrops,multiseq,demuxem,gmm-demux,bff,hashsolo' diff --git a/conf/test_donor_match.config b/conf/test_donor_match.config index fe0bc2c9..40dad5c5 100644 --- a/conf/test_donor_match.config +++ b/conf/test_donor_match.config @@ -25,8 +25,8 @@ params { // Input data mode = 'donor_match' input = 'https://github.com/nf-core/test-datasets/raw/refs/heads/hadge/samplesheet/samplesheet_donor_match.csv' - demultiplexing_result = 'https://github.com/nf-core/test-datasets/raw/refs/heads/hadge/testdata/donor_match_assignment.csv' - vireo_filtered_variants = 'https://github.com/nf-core/test-datasets/raw/refs/heads/hadge/testdata/donor_match_filtered_variants.tsv' - cell_genotype = 'https://github.com/nf-core/test-datasets/raw/refs/heads/hadge/testdata/donor_match.cells.vcf.gz' - gt_donors = 'https://github.com/nf-core/test-datasets/raw/refs/heads/hadge/testdata/donor_match_GT_donors.vireo.vcf.gz' + demultiplexing_result = 'https://github.com/nf-core/test-datasets/raw/refs/heads/hadge/dataset_test/donor_match_assignment.csv' + vireo_filtered_variants = 'https://github.com/nf-core/test-datasets/raw/refs/heads/hadge/dataset_test/donor_match_filtered_variants.tsv' + cell_genotype = 'https://github.com/nf-core/test-datasets/raw/refs/heads/hadge/dataset_test/donor_match.cells.vcf.gz' + gt_donors = 'https://github.com/nf-core/test-datasets/raw/refs/heads/hadge/dataset_test/donor_match_GT_donors.vireo.vcf.gz' } diff --git a/conf/test_full.config b/conf/test_full.config index 2f9eedb2..afdeeaad 100644 --- a/conf/test_full.config +++ b/conf/test_full.config @@ -10,15 +10,36 @@ ---------------------------------------------------------------------------------------- */ +process { + resourceLimits = [ + cpus: 60, + memory: '360.GB', + time: '23.h' + ] + + withName: SOUPORCELL { + cpus = 60 + memory = 64.GB + time = 23.h + } + + withName: VIREO { + cpus = 4 + memory = 360.GB + time = 8.h + } +} + params { config_profile_name = 'Full test profile' config_profile_description = 'Full test dataset to check pipeline function' - // Input data for full size test - // TODO nf-core: Specify the paths to your full test data ( on nf-core/test-datasets or directly in repositories, e.g. SRA) - // TODO nf-core: Give any required params for the test so that command line flags are not needed - input = params.pipelines_testdata_base_path + 'viralrecon/samplesheet/samplesheet_full_illumina_amplicon.csv' - - // Genome references - genome = 'R64-1-1' + // Input data + mode = 'rescue' + hash_tools = 'htodemux,hasheddrops,multiseq,demuxem,gmm-demux,bff,hashsolo' + genetic_tools = 'demuxlet,freemuxlet,vireo,souporcell' + input = 'https://github.com/nf-core/test-datasets/raw/refs/heads/hadge/samplesheet/samplesheet_test_full.csv' + genome = 'GRCh38' + bam_qc = false + find_variants = false } diff --git a/conf/test_genetic.config b/conf/test_genetic.config index 646efc75..e0d755a9 100644 --- a/conf/test_genetic.config +++ b/conf/test_genetic.config @@ -28,5 +28,4 @@ params { input = 'https://github.com/nf-core/test-datasets/raw/refs/heads/hadge/samplesheet/samplesheet_genetic.csv' genome = 'GRCh38' fasta = 'https://raw.githubusercontent.com/nf-core/test-datasets/modules/data/genomics/homo_sapiens/genome/chr21/sequence/genome.fasta' - bam_qc = true } diff --git a/conf/test_hashing.config b/conf/test_hashing.config index ef30803a..b9d669e3 100644 --- a/conf/test_hashing.config +++ b/conf/test_hashing.config @@ -24,8 +24,6 @@ params { // Input data mode = 'hashing' - hash_tools = 'htodemux,hasheddrops,multiseq,gmm-demux,bff,hashsolo' + hash_tools = 'htodemux,hasheddrops,multiseq,demuxem,gmm-demux,bff,hashsolo' input = 'https://github.com/nf-core/test-datasets/raw/refs/heads/hadge/samplesheet/samplesheet_hashing.csv' - - // TODO demuxem: include demuxem to hash_tools if #81 is fixed } diff --git a/docs/usage.md b/docs/usage.md index e4d3c7c9..f90c78a8 100644 --- a/docs/usage.md +++ b/docs/usage.md @@ -41,8 +41,8 @@ Finally, it assigns SNPs to cells to determine donor identity but requires addit ```csv title="samplesheet.csv" sample,bam,vcf,n_samples,barcodes -id1,donor_genotype_chr21.vcf,2,barcodes.tsv -id2,donor_genotype_chr21.vcf,2,barcodes.tsv +id1,chr21.bam,donor_genotype_chr21.vcf,2,barcodes.tsv +id2,chr21.bam,donor_genotype_chr21.vcf,2,barcodes.tsv id3,chr21.bam,donor_genotype_chr21.vcf,2,barcodes.tsv ``` @@ -142,7 +142,7 @@ id3,rna.tar.gz,hto.tar.gz,chr21.bam,donor_genotype_chr21.vcf,2,barcodes.tsv | `rna_matrix` | Full path to the RNA-Seq count matrices provided in a 10x Genomics format and compressed as `.tar.gz`. | | `hto_matrix` | Full path to the hashing count matrices provided in a 10x Genomics format and compressed as `.tar.gz`. | | `bam` | Full path to the alignment file (`.bam`). | -| `vcf` | Full path to the list of common SNPs (`.vcf`). | +| `vcf` | Full path to common SNP genotypes vcf (`.vcf`). | | `n_samples` | The number of multiplexed donors. | | `barcodes` | Full path to the list of cell barcodes (e.g., `barcodes.tsv` from Cell Ranger) | @@ -155,21 +155,47 @@ id3,rna.tar.gz,hto.tar.gz,chr21.bam,donor_genotype_chr21.vcf,2,barcodes.tsv | hashing | ✅ | ✅ | ✅ | ❌ | ❌ | ❌ | ❌ | | donor_match | ✅ | ❌ | ❌ | ❌ | ❌ | ✅ | ❌ | -| Module | sample | rna_matrix | hto_matrix | bam | barcodes | n_samples | vcf | -| ----------- | :----: | :--------: | :--------: | :-: | :------: | :-------: | :-: | -| htodemux | ✅ | ✅ | ✅ | ❌ | ❌ | ❌ | ❌ | -| multiseq | ✅ | ✅ | ✅ | ❌ | ❌ | ❌ | ❌ | -| bff | ✅ | ❌ | ✅ | ❌ | ❌ | ❌ | ❌ | -| demuxem | ✅ | ✅ | ✅ | ❌ | ❌ | ❌ | ❌ | -| gmm-demux | ✅ | ❌ | ✅ | ❌ | ❌ | ❌ | ❌ | -| hasheddrops | ✅ | ✅\* | ✅ | ❌ | ❌ | ❌ | ❌ | -| hashsolo | ✅ | ❌ | ✅ | ❌ | ❌ | ❌ | ❌ | -| vireo | ✅ | ❌ | ❌ | ✅ | ✅ | ✅ | ✅ | -| demuxlet | ✅ | ❌ | ❌ | ✅ | ❌ | ❌ | ✅ | -| freemuxlet | ✅ | ❌ | ❌ | ✅ | ❌ | ✅ | ✅ | -| souporcell | ✅ | ❌ | ❌ | ✅ | ✅ | ✅ | ❌ | - -\* if `params.hasheddrops_runEmptyDrops` is true +| Module | sample | rna_matrix | hto_matrix | bam | barcodes | n_samples | vcf1 | +| ----------- | :----: | :------------: | :--------: | :-: | :------: | :-------: | :-------------: | +| htodemux | ✅ | ✅ | ✅ | ❌ | ❌ | ❌ | ❌ | +| multiseq | ✅ | ✅ | ✅ | ❌ | ❌ | ❌ | ❌ | +| bff | ✅ | ❌ | ✅ | ❌ | ❌ | ❌ | ❌ | +| demuxem | ✅ | ✅ | ✅ | ❌ | ❌ | ❌ | ❌ | +| gmm-demux | ✅ | ❌ | ✅ | ❌ | ❌ | ❌ | ❌ | +| hasheddrops | ✅ | ✅2 | ✅ | ❌ | ❌ | ❌ | ❌ | +| hashsolo | ✅ | ❌ | ✅ | ❌ | ❌ | ❌ | ❌ | +| vireo | ✅ | ❌ | ❌ | ✅ | ✅ | ✅ | ✅ | +| demuxlet | ✅ | ❌ | ❌ | ✅ | ❌ | ❌ | ✅3 | +| freemuxlet | ✅ | ❌ | ❌ | ✅ | ❌ | ✅ | ✅ | +| souporcell | ✅ | ❌ | ❌ | ✅ | ✅ | ✅ | ❌ | + +1 The requirements for the VCF file differ between genetic deconvolution methods. +Check out [Demuxafy](https://demultiplexing-doublet-detecting-docs.readthedocs.io/en/latest/DemultiplexingSoftwares.html) to find the right VCF file for the methods you want to use. +`POPSCLE_DSCPILEUP` (needed for `freemuxlet` and `demuxlet`) requires the VCF file to be sorted the same way as the BAM file. If you encounter an error due to this, consider using `picard SortVcf`. + +2 if `params.hasheddrops_runEmptyDrops` is true + +3 reference SNP genotypes for each individual ([demuxlet docs](https://demultiplexing-doublet-detecting-docs.readthedocs.io/en/latest/Demuxlet.html)) + +::: + +:::tip{collapse title="Recommendations for naming HTO-labels and barcodes"} + +1. Avoid single DNA base letters as suffixes + +- **Incorrect:** `HTO-A`, `HTO-C`, `HTO-G`, `HTO-T` +- **Reason:** The `BFF` module uses `cellhashR`'s `ProcessCountMatrix()`, which internally calls `SimplifyHtoNames()` and incorrectly strips single DNA base letters, collapsing `HTO-A`, `HTO-C`, `HTO-G` all to `HTO` and causing a crash. + +2. Avoid barcode sequences as part of the label + +- **Incorrect:** `HTO-1-ACTGTCTAACGG` +- **Reason:** `SimplifyHtoNames()` strips the barcode suffix in `BFF`, causing the same HTO to appear as `HTO-1` in `BFF` output but `HTO-1-ACTGTCTAACGG` in other methods, making cross-method comparison unreliable. + +3. Avoid using the same trailing suffixes on all barcodes + +- **Incorrect:** `AAACCCAAGAAACACT-1` (`-1` at all barcodes) +- **Reason:** In the `DEMUXEM` module, `pegasusio.read_input()` only removes the suffix from RNA barcodes, but not from HTO barcodes, which leads to a known issue (see [#21](https://github.com/lilab-bcb/demuxEM/issues/21)). + ::: An [example samplesheet](../assets/samplesheet.csv) has been provided with the pipeline. diff --git a/main.nf b/main.nf index bca1068e..2c8ef678 100644 --- a/main.nf +++ b/main.nf @@ -26,9 +26,6 @@ include { getGenomeAttribute } from './subworkflows/local/utils_nfcore_hadg ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ -// TODO nf-core: Remove this line if you don't need a FASTA file -// This is an example of how to use getGenomeAttribute() to fetch parameters -// from igenomes.config using `--genome` params.fasta = getGenomeAttribute('fasta') /* diff --git a/modules/local/create_anndata_mudata/templates/create_anndata_mudata.py b/modules/local/create_anndata_mudata/templates/create_anndata_mudata.py index 86ac15cf..f05b3881 100644 --- a/modules/local/create_anndata_mudata/templates/create_anndata_mudata.py +++ b/modules/local/create_anndata_mudata/templates/create_anndata_mudata.py @@ -1,5 +1,8 @@ #!/usr/bin/env python3 +import os +os.environ["NUMBA_CACHE_DIR"] = "./tmp/numba" + # versions import platform import yaml diff --git a/modules/local/dropletutils/mtxconvert/templates/convert.R b/modules/local/dropletutils/mtxconvert/templates/convert.R index 8d43487e..3cd31bab 100644 --- a/modules/local/dropletutils/mtxconvert/templates/convert.R +++ b/modules/local/dropletutils/mtxconvert/templates/convert.R @@ -4,7 +4,6 @@ library(DropletUtils) mtx_dir <- "${input_mtx_dir}" - sce <- read10xCounts(mtx_dir) # Read to SingleCellExperiment object print(sce) @@ -18,19 +17,6 @@ if ("${write_csv}" == "true") { write.csv(as.matrix(count_matrix), file = "${prefix}.csv", row.names = TRUE) } -# TODO demuxem: remove if demuxEM issue is solved (https://github.com/theislab/hadge/issues/81) -# Write to h5 file -# write10xCounts( -# path = "${prefix}.h5", -# x = counts(sce), -# barcodes = colData(sce)\$Barcode, -# gene.id = rownames(sce), -# gene.symbol = if (!is.null(rowData(sce)\$Symbol)) rowData(sce)\$Symbol else rownames(sce), -# gene.type = if (!is.null(rowData(sce)\$Type)) rowData(sce)\$Type else rep("Gene Expression", nrow(sce)), -# type = "HDF5", -# version = "3", # <-- ensures /matrix layout instead of /unknown -# overwrite = TRUE -# ) write10xCounts("${prefix}.h5", count_matrix, type = "HDF5") ################################################ diff --git a/modules/local/hash_summary/templates/hash_summary.py b/modules/local/hash_summary/templates/hash_summary.py index 59aaf726..a0d36789 100644 --- a/modules/local/hash_summary/templates/hash_summary.py +++ b/modules/local/hash_summary/templates/hash_summary.py @@ -140,17 +140,18 @@ def __init__(self): def demuxem(self, args: Arguments) -> Tuple[pd.DataFrame, pd.DataFrame]: data = io.read_input(str(args.demuxem)) - classification = data.obs["demux_type"].to_frame() - classification.reset_index(inplace=True) - classification.columns = ["Barcode", "demuxem"] - classification["demuxem"] = classification["demuxem"].cat.rename_categories( - {"unknown": args.negative_str} + df = data.obs[["assignment","demux_type"]].copy() + df.index.name = "Barcode" + df.reset_index(inplace=True) + df["demux_type"] = df["demux_type"].cat.rename_categories( + lambda x: args.negative_str if x == "unknown" else x ) - - # TODO demuxem: demuxem has more output barcodes than input barcodes metioned here: https://github.com/lilab-bcb/demuxEM/issues/20 - assignment = data.obs["assignment"].to_frame() - assignment.reset_index(inplace=True) - assignment.columns = ["Barcode", "demuxem"] + df["assignment"] = df["assignment"].cat.add_categories([args.negative_str, args.doublet_str]) + df.loc[df["demux_type"] == args.negative_str, "assignment"] = args.negative_str + df.loc[df["demux_type"] == args.doublet_str, "assignment"] = args.doublet_str + df["assignment"] = df["assignment"].cat.remove_unused_categories() + assignment = df[["Barcode", "assignment"]].rename(columns={"assignment": "demuxem"}) + classification = df[["Barcode", "demux_type"]].rename(columns={"demux_type": "demuxem"}) return assignment, classification @@ -440,9 +441,11 @@ def create_overview_table(dfs: List[pd.DataFrame]): classification_summary, classification, on="Barcode", how="left" ) - # TODO demuxem: update if demuxEM works (https://github.com/theislab/hadge/issues/81) - # .replace("", args.negative_str) - # maybe also in demuxem() + for df in [assignment_summary, classification_summary]: + for col in df.select_dtypes(["category"]): + if args.negative_str not in df[col].cat.categories: + df[col] = df[col].cat.add_categories(args.negative_str) + assignment_summary.fillna(args.negative_str).to_csv(args.assignment, index=False) classification_summary.fillna(args.negative_str).to_csv( args.classification, index=False diff --git a/modules/local/htodemux_visualization/templates/htodemux_visualization.R b/modules/local/htodemux_visualization/templates/htodemux_visualization.R index 4641c885..c9b634ab 100644 --- a/modules/local/htodemux_visualization/templates/htodemux_visualization.R +++ b/modules/local/htodemux_visualization/templates/htodemux_visualization.R @@ -94,7 +94,11 @@ if (vlnPlot) { # tSNE Plot if (tSNE) { print("Generating tSNE plot...") - hashtag.subset <- subset(hashtag, idents = tSNEIdents, invert = tSNEInvert) + if (tSNEIdents %in% levels(Idents(hashtag))) { + hashtag.subset <- subset(hashtag, idents = tSNEIdents, invert = tSNEInvert) + } else { + hashtag.subset <- hashtag + } DefaultAssay(hashtag.subset) <- assay hashtag.subset <- ScaleData(hashtag.subset, features = rownames(hashtag.subset), diff --git a/modules/nf-core/popscle/demuxlet/main.nf b/modules/nf-core/popscle/demuxlet/main.nf index 2e65c61e..ff6246a0 100644 --- a/modules/nf-core/popscle/demuxlet/main.nf +++ b/modules/nf-core/popscle/demuxlet/main.nf @@ -8,7 +8,7 @@ process POPSCLE_DEMUXLET { 'biocontainers/popscle:0.1beta--h2c78cec_0' }" input: - tuple val(meta), val(plp), path(bam), path(donor_genotype) + tuple val(meta), path(plp), path(bam), path(donor_genotype) output: tuple val(meta), path('*.best'), emit: demuxlet_result @@ -20,7 +20,7 @@ process POPSCLE_DEMUXLET { script: def args = task.ext.args ?: '' def prefix = task.ext.prefix ?: "${meta.id}" - def input = plp ? "--plp ${plp.toString() - '.plp.gz'}" : "--sam $bam" + def input = plp ? "--plp ${plp}/${prefix}" : "--sam $bam" def VERSION = '0.1' // WARN: Version information not provided by tool on CLI. Please update version string below when bumping container versions. """ diff --git a/nextflow.config b/nextflow.config index 305e1905..b3074dbe 100644 --- a/nextflow.config +++ b/nextflow.config @@ -46,10 +46,6 @@ params { // ======================== hasing paramters ======================== - // -------------------------- hash summary -------------------------- - generate_anndata = true - generate_mudata = true - // ------------------------------ bff ------------------------------- // inputs bff_methods = 'COMBINED' @@ -423,8 +419,6 @@ profiles { } } -// Set AWS client to anonymous when using the default igenomes_base -aws.client.anonymous = !params.igenomes_ignore && params.igenomes_base?.startsWith('s3://ngi-igenomes/igenomes/') ?: false // Load nf-core custom profiles from different institutions // If params.custom_config_base is set AND either the NXF_OFFLINE environment variable is not set or params.custom_config_base is a local path, the nfcore_custom.config file from the specified base path is included. diff --git a/nextflow_schema.json b/nextflow_schema.json index ec4ec154..6bcd2972 100644 --- a/nextflow_schema.json +++ b/nextflow_schema.json @@ -50,13 +50,13 @@ "type": "boolean", "description": "Perform BAM QC.", "fa_icon": "fas fa-chart-line", - "default": true + "default": true, + "help_text": "Consider setting this option to false, as it may consume major computational resources. Review the steps of `BAM_QC` to determine if it is necessary for your input data." }, "common_variants": { "type": "string", "description": "File with common variants. If provided, the BAM files will be filtered to only include reads that overlap with the common variants.", "fa_icon": "fas fa-dna", - "default": null, "pattern": "^\\S+\\.vcf(\\.gz)?$", "exists": true }, @@ -140,33 +140,28 @@ "type": ["string", "null"], "format": "file-path", "description": "Path to demultiplexing result CSV file (necessary only for donor_match mode).", - "default": null, "fa_icon": "fas fa-file-csv" }, "vireo_filtered_variants": { "type": ["string", "null"], "format": "file-path", "description": "Path to Vireo filtered variants file (necessary only for donor_match mode).", - "default": null, "fa_icon": "fas fa-file-code" }, "cell_genotype": { "type": ["string", "null"], "format": "file-path", "description": "Path to cell genotype file (necessary only for donor_match mode).", - "default": null, "fa_icon": "fas fa-dna" }, "match_donor_method1": { "type": ["string", "null"], "description": "First method to use for donor matching.", - "default": null, "fa_icon": "fas fa-list" }, "match_donor_method2": { "type": ["string", "null"], "description": "Second method to use for donor matching.", - "default": null, "fa_icon": "fas fa-list" }, "find_variants": { @@ -200,7 +195,6 @@ "type": ["string", "null"], "format": "file-path", "description": "Path to cell genotype file (necessary only for donor_match mode).", - "default": null, "fa_icon": "fas fa-dna" } } @@ -208,7 +202,7 @@ "demuxem_options": { "title": "demuxEM options", "type": "object", - "fa_icon": "fas fa-vial", + "fa_icon": "fas fa-tag", "description": "Options specific to the demuxEM tool for cell hashing demultiplexing.", "properties": { "demuxem_generate_diagnostic_plots": { @@ -220,7 +214,7 @@ "demuxem_alpha_on_samples": { "type": "number", "description": "The Dirichlet prior concentration parameter (alpha) on samples. An alpha value < 1.0 will make the prior sparse.", - "default": 0.0, + "default": 0, "minimum": 0, "maximum": 1, "fa_icon": "fas fa-sliders-h" @@ -242,7 +236,7 @@ "demuxem_min_signal_hashtag": { "type": "number", "description": "Any cell/nucleus with less than this count of hashtags from the signal will be marked as unknown.", - "default": 10.0, + "default": 10, "minimum": 0, "fa_icon": "fas fa-signal" }, @@ -263,14 +257,15 @@ "bff_options": { "title": "BFF options", "type": "object", - "fa_icon": "fas fa-bolt", + "fa_icon": "fas fa-tag", "description": "Options specific to the BFF cell hashing demultiplexing.", "properties": { "bff_methods": { "type": "string", "description": "Method(s) to use within BFF.", "default": "COMBINED", - "fa_icon": "fas fa-list" + "fa_icon": "fas fa-list", + "enum": ["COMBINED", "RAW", "CLUSTER"] }, "bff_preprocessing": { "type": "boolean", @@ -281,25 +276,21 @@ "bff_barcodeWhitelist": { "type": ["string", "null"], "description": "Path to barcode whitelist for preprocessing.", - "default": null, "fa_icon": "fas fa-file" }, "bff_cellbarcodeWhitelist": { "type": ["string", "null"], "description": "Path to cell barcode whitelist for GenerateCellHashingCalls().", - "default": null, "fa_icon": "fas fa-file" }, "bff_methodsForConsensus": { "type": ["string", "null"], "description": "Methods to use for consensus calling.", - "default": null, "fa_icon": "fas fa-layer-group" }, "bff_metricsFile": { "type": ["string", "null"], "description": "Optional metrics file path.", - "default": null, "fa_icon": "fas fa-file-alt" }, "bff_doTSNE": { @@ -316,13 +307,11 @@ "bff_perCellSaturation": { "type": ["number", "null"], "description": "Per-cell saturation value.", - "default": null, "fa_icon": "fas fa-tint" }, "bff_majorityConsensusThreshold": { "type": ["number", "null"], "description": "Majority consensus threshold.", - "default": null, "fa_icon": "fas fa-percentage" }, "bff_chemistry": { @@ -334,7 +323,6 @@ "bff_callerDisagreementThreshold": { "type": ["number", "null"], "description": "Threshold for caller disagreement.", - "default": null, "fa_icon": "fas fa-exclamation-triangle" } } @@ -342,19 +330,17 @@ "gmmdemux_options": { "title": "GMM-Demux options", "type": "object", - "fa_icon": "fas fa-layer-group", + "fa_icon": "fas fa-tag", "description": "Options specific to the GMM-Demux tool for cell hashing demultiplexing.", "properties": { "gmmdemux_hto_names": { "type": ["string", "null"], "description": "Comma separated list of HTO names, without whitespace. If null, hto_names are extracted from the input hto matrix from features.tsv.gz.", - "default": null, "fa_icon": "fas fa-file-alt" }, "gmmdemux_estimated_n_cells": { "type": ["integer", "null"], "description": "If specified, it will generate the statistic summary of the dataset, including MSM and SSM rates. This requires an estimated total number of cells in the assay as input.", - "default": null, "minimum": 1, "fa_icon": "fas fa-calculator" }, @@ -374,20 +360,17 @@ "type": ["string", "null"], "format": "file-path", "description": "Load a full classification report and skip the mtx folder as input. Requires a file path argument.", - "default": null, "fa_icon": "fas fa-file-import" }, "gmmdemux_examine": { "type": ["string", "null"], "format": "file-path", "description": "Provide the cell list. Requires a file path argument. Only executes if -u is set.", - "default": null, "fa_icon": "fas fa-file" }, "gmmdemux_extract": { "type": "string", "description": "Names of the HTO tag(s) to extract, separated by ','. Joint HTO samples are combined with '+', such as 'HTO_1+HTO_2'.", - "default": null, "fa_icon": "fas fa-filter" }, "gmmdemux_threshold": { @@ -411,7 +394,7 @@ "hashsolo_options": { "title": "Hashsolo options", "type": "object", - "fa_icon": "fas fa-hashtag", + "fa_icon": "fas fa-tag", "description": "Options specific to the Scanpy Hashsolo demultiplexing module.", "properties": { "hashsolo_cell_hashing_columns": { @@ -420,7 +403,6 @@ "type": "string" }, "description": "Groovy list (['hash_1', 'hash_2']) of .obs columns that contain cell hashing counts. Can be null if the data is in 10x Genomics format, as the columns are derived from the input.", - "default": null, "fa_icon": "fas fa-list" }, "hashsolo_priors": { @@ -435,20 +417,17 @@ "hashsolo_pre_existing_clusters": { "type": ["string", "null"], "description": "Column in cell_hashing_adata.obs for how to break up demultiplexing.", - "default": null, "fa_icon": "fas fa-columns" }, "hashsolo_clustering_data": { "type": ["string", "null"], "format": "directory-path", "description": "Input directory containing transcriptomic data in 10x mtx format.", - "default": null, "fa_icon": "fas fa-folder-open" }, "hashsolo_number_of_noise_barcodes": { "type": ["integer", "null"], "description": "Number of barcodes to use to create noise distribution.", - "default": null, "fa_icon": "fas fa-sort-numeric-up" }, "hashsolo_round_digits": { @@ -463,7 +442,7 @@ "htodemux_options": { "title": "HTODemux options", "type": "object", - "fa_icon": "fas fa-tags", + "fa_icon": "fas fa-tag", "description": "Options specific to the HTODemux tool for cell hashing demultiplexing.", "properties": { "htodemux_quantile": { @@ -517,7 +496,7 @@ "htodemux_visualization_options": { "title": "HTODemux visualization options", "type": "object", - "fa_icon": "fas fa-chart-bar", + "fa_icon": "fas fa-tag", "description": "Options specific to the HTODemux visualization tool for generating plots and visualizations.", "properties": { "htodemux_visualization_ridgePlot": { @@ -542,13 +521,11 @@ "htodemux_visualization_scatterFeat1": { "type": "string", "description": "Name of a Hash Tag Oligo (HTO) identifiers, usually defined in the `feature.tsv` of the hto matrix folder.", - "default": null, "fa_icon": "fas fa-tag" }, "htodemux_visualization_scatterFeat2": { "type": "string", "description": "Name of a Hash Tag Oligo (HTO) identifiers, usually defined in the `feature.tsv` of the hto matrix folder.", - "default": null, "fa_icon": "fas fa-tag" }, "htodemux_visualization_vlnPlot": { @@ -630,7 +607,7 @@ "multiseqdemux_options": { "title": "MultiSeqDemux options", "type": "object", - "fa_icon": "fas fa-list-ol", + "fa_icon": "fas fa-tag", "description": "Options specific to the MultiSeqDemux tool for cell hashing demultiplexing.", "properties": { "multiseqdemux_quantile": { @@ -689,7 +666,7 @@ "hasheddrops_options": { "title": "HashedDrops options", "type": "object", - "fa_icon": "fas fa-tint", + "fa_icon": "fas fa-tag", "description": "Options specific to the HashedDrops tool for cell hashing demultiplexing.", "properties": { "hasheddrops_lower": { @@ -721,7 +698,6 @@ "hasheddrops_byRank": { "type": ["integer", "null"], "description": "Alternative method for identifying empty droplets.", - "default": null, "fa_icon": "fas fa-sort-numeric-down" }, "hasheddrops_isCellFDR": { @@ -742,13 +718,11 @@ "hasheddrops_ignore": { "type": ["number", "null"], "description": "Lower bound for ignoring barcodes.", - "default": null, "fa_icon": "fas fa-ban" }, "hasheddrops_alpha": { "type": ["number", "null"], "description": "Scaling parameter for Dirichlet-multinomial sampling.", - "default": null, "fa_icon": "fas fa-adjust" }, "hasheddrops_ambient": { @@ -816,7 +790,6 @@ "type": "integer" }, "description": "An integer matrix specifying valid combinations of HTOs. Number of items in each row has to be the same.", - "default": null, "fa_icon": "fas fa-th-large" }, "hasheddrops_runEmptyDrops": { @@ -829,7 +802,7 @@ "preprocessing_options": { "title": "Preprocessing options", "type": "object", - "fa_icon": "fas fa-cogs", + "fa_icon": "fas fa-tag", "description": "Options for preprocessing data for HTODemux and MultiSeq demultiplexing.", "properties": { "preprocessing_sel_method": { @@ -876,26 +849,6 @@ } } }, - "hash_summary_options": { - "title": "Hash summary options", - "type": "object", - "fa_icon": "fas fa-list", - "description": "Options controlling hash summary and downstream exported formats.", - "properties": { - "generate_anndata": { - "type": "boolean", - "description": "Generate AnnData (.h5ad) outputs for hashing results.", - "fa_icon": "fas fa-database", - "default": true - }, - "generate_mudata": { - "type": "boolean", - "description": "Generate MuData outputs for hashing results.", - "fa_icon": "fas fa-layer-group", - "default": true - } - } - }, "cellsnp_options": { "title": "CellSNP-lite options", "type": "object", @@ -924,7 +877,7 @@ "cellsnp_minmaf": { "type": "number", "description": "Minimum minor allele frequency (MAF) for SNPs to be included in the output.", - "default": 0.0, + "default": 0, "minimum": 0, "maximum": 0.5, "fa_icon": "fas fa-percentage" @@ -970,7 +923,7 @@ "vireo_options": { "title": "Vireo options", "type": "object", - "fa_icon": "fas fa-users", + "fa_icon": "fas fa-dna", "description": "Options specific to the Vireo tool for donor demultiplexing from single-cell RNA-seq data.", "properties": { "vireo_genotag": { @@ -1045,7 +998,7 @@ "dsc_pileup_options": { "title": "DSC-Pileup options", "type": "object", - "fa_icon": "fas fa-layer-group", + "fa_icon": "fas fa-dna", "description": "Options specific to the DSC-Pileup tool for pileup generation from single-cell BAM files.", "properties": { "dsc_pileup_tag_group": { @@ -1121,7 +1074,7 @@ "demuxlet_options": { "title": "Demuxlet options", "type": "object", - "fa_icon": "fas fa-users-cog", + "fa_icon": "fas fa-dna", "description": "Options specific to the Demuxlet tool for genotype-based demultiplexing of single-cell RNA-seq data.", "properties": { "demuxlet_field": { @@ -1141,7 +1094,7 @@ "demuxlet_geno_error_coeff": { "type": "number", "description": "Slope of genotype error rate. [error] = [offset] + [1-offset]*[coeff]*[1-r2]", - "default": 0.0, + "default": 0, "minimum": 0, "maximum": 1, "fa_icon": "fas fa-chart-line" @@ -1186,7 +1139,7 @@ "freemuxlet_options": { "title": "Freemuxlet options", "type": "object", - "fa_icon": "fas fa-users", + "fa_icon": "fas fa-dna", "description": "Options specific to the Freemuxlet tool for reference-free genotype-based demultiplexing.", "properties": { "freemuxlet_doublet_prior": { @@ -1215,7 +1168,7 @@ "freemuxlet_frac_init_clust": { "type": "number", "description": "Fraction of droplets to be clustered in the very first round of initial clustering procedure.", - "default": 1.0, + "default": 1, "minimum": 0, "maximum": 1, "fa_icon": "fas fa-object-ungroup" @@ -1249,7 +1202,7 @@ "souporcell_options": { "title": "Souporcell options", "type": "object", - "fa_icon": "fas fa-users", + "fa_icon": "fas fa-dna", "description": "Options specific to the Souporcell tool for clustering mixed-genotype scRNAseq experiments by individual.", "properties": { "souporcell_ploidy": { @@ -1293,7 +1246,6 @@ "exists": true, "pattern": "^\\S+\\.vcf(\\.gz)?$", "description": "Common variant loci or known variant loci vcf, must be vs same reference fasta.", - "default": null, "fa_icon": "fas fa-file-code" }, "souporcell_known_genotypes": { @@ -1302,25 +1254,21 @@ "exists": true, "pattern": "^\\S+\\.vcf$", "description": "Known variants per clone in population vcf mode, must be .vcf right now we dont accept gzip or bcf sorry.", - "default": null, "fa_icon": "fas fa-dna" }, "souporcell_known_genotypes_sample_names": { "type": "string", "description": "Which samples in population vcf from known genotypes option represent the donors in your sample. Provide space-separated sample names for multiple donors.", - "default": null, "fa_icon": "fas fa-tag" }, "souporcell_skip_remap": { "type": "boolean", "description": "Don't remap with minimap2 (not recommended unless in conjunction with --common_variants).", - "default": false, "fa_icon": "fas fa-angle-double-right" }, "souporcell_ignore": { "type": "boolean", "description": "Set to True to ignore data error assertions.", - "default": false, "fa_icon": "fas fa-exclamation-triangle" } } @@ -1517,9 +1465,6 @@ { "$ref": "#/$defs/hasheddrops_options" }, - { - "$ref": "#/$defs/hash_summary_options" - }, { "$ref": "#/$defs/preprocessing_options" }, diff --git a/subworkflows/local/genetic_demultiplexing/main.nf b/subworkflows/local/genetic_demultiplexing/main.nf index f3553579..0b4e6d2d 100644 --- a/subworkflows/local/genetic_demultiplexing/main.nf +++ b/subworkflows/local/genetic_demultiplexing/main.nf @@ -93,7 +93,7 @@ workflow GENETIC_DEMULTIPLEXING { ch_versions = ch_versions.mix(POPSCLE_DSCPILEUP.out.versions) if (methods.contains('demuxlet')) { - ch_demuxlet_input = POPSCLE_DSCPILEUP.out.plp.join(ch_samplesheet).map { meta, plp, bam, _barcodes, vcf -> [meta, plp, bam, vcf] } + ch_demuxlet_input = POPSCLE_DSCPILEUP.out.directory.join(ch_samplesheet).map { meta, plp, bam, _barcodes, vcf -> [meta, plp, bam, vcf] } POPSCLE_DEMUXLET(ch_demuxlet_input) ch_demuxlet = ch_demuxlet.mix(POPSCLE_DEMUXLET.out.demuxlet_result) ch_versions = ch_versions.mix(POPSCLE_DEMUXLET.out.versions) diff --git a/tests/default.nf.test.snap b/tests/default.nf.test.snap index 0b39bae7..1834d911 100644 --- a/tests/default.nf.test.snap +++ b/tests/default.nf.test.snap @@ -10,6 +10,7 @@ "CELLSNP_MODEA": { "cellsnp": "1.2.3" }, + "DEMUXEM": "echo 0.1.7.post1", "DONOR_MATCH": { "r-base": "4.5.2", "r-data.table": "1.17.8", @@ -37,6 +38,12 @@ "r-seurat": "5.3.0", "dropletutils": "1.26.0" }, + "HASHSOLO": { + "matplotlib": "3.10.5", + "pandas": "2.3.1", + "python": "3.12.11", + "scanpy": "1.11.2" + }, "HASH_SUMMARY": { "numpy": "2.3.5", "pandas": "2.3.3", @@ -44,18 +51,42 @@ "python": "3.12.12", "scanpy": "1.11.5" }, + "HTODEMUX_VISUALIZATION": { + "r-base": "4.4.3", + "r-seurat": "5.3.0", + "r-ggplot2": "3.5.2" + }, "JOIN_RESULTS_ASSIGNMENT": { "csvtk": "0.31.0" }, "JOIN_RESULTS_CLASSIFICATION": { "csvtk": "0.31.0" }, + "MTXCONVERT_HTO": { + "r-base": "4.4.3", + "bioconductor-dropletutils": "1.26.0" + }, + "MTXCONVERT_RNA": { + "r-base": "4.4.3", + "bioconductor-dropletutils": "1.26.0" + }, + "MULTISEQDEMUX": { + "r-base": "4.4.3", + "r-seurat": "5.3.0" + }, + "POPSCLE_DEMUXLET": { + "popscle demuxlet": 0.1 + }, "POPSCLE_DSCPILEUP": { "popscle dsc-pileup": 0.1 }, "POPSCLE_FREEMUXLET": { "popscle": 0.1 }, + "PREPROCESSING_FOR_HTODEMUX_MULTISEQ": { + "r-base": "4.4.3", + "seurat": "5.3.0" + }, "SAMTOOLS_INDEX": { "samtools": 1.21 }, @@ -102,6 +133,12 @@ "donor_match/test1/freemuxlet_vs_bff_raw/test1_freemuxlet_vs_bff_raw_correlation_res.csv", "donor_match/test1/freemuxlet_vs_bff_raw/test1_freemuxlet_vs_bff_raw_donor_match.csv", "donor_match/test1/freemuxlet_vs_bff_raw/test1_freemuxlet_vs_bff_raw_intersect_assignment_after_match.csv", + "donor_match/test1/freemuxlet_vs_demuxem", + "donor_match/test1/freemuxlet_vs_demuxem/test1_freemuxlet_vs_demuxem_all_assignment_after_match.csv", + "donor_match/test1/freemuxlet_vs_demuxem/test1_freemuxlet_vs_demuxem_concordance_heatmap.png", + "donor_match/test1/freemuxlet_vs_demuxem/test1_freemuxlet_vs_demuxem_correlation_res.csv", + "donor_match/test1/freemuxlet_vs_demuxem/test1_freemuxlet_vs_demuxem_donor_match.csv", + "donor_match/test1/freemuxlet_vs_demuxem/test1_freemuxlet_vs_demuxem_intersect_assignment_after_match.csv", "donor_match/test1/freemuxlet_vs_gmmdemux", "donor_match/test1/freemuxlet_vs_gmmdemux/test1_freemuxlet_vs_gmmdemux_all_assignment_after_match.csv", "donor_match/test1/freemuxlet_vs_gmmdemux/test1_freemuxlet_vs_gmmdemux_concordance_heatmap.png", @@ -114,6 +151,24 @@ "donor_match/test1/freemuxlet_vs_hasheddrops/test1_freemuxlet_vs_hasheddrops_correlation_res.csv", "donor_match/test1/freemuxlet_vs_hasheddrops/test1_freemuxlet_vs_hasheddrops_donor_match.csv", "donor_match/test1/freemuxlet_vs_hasheddrops/test1_freemuxlet_vs_hasheddrops_intersect_assignment_after_match.csv", + "donor_match/test1/freemuxlet_vs_hashsolo", + "donor_match/test1/freemuxlet_vs_hashsolo/test1_freemuxlet_vs_hashsolo_all_assignment_after_match.csv", + "donor_match/test1/freemuxlet_vs_hashsolo/test1_freemuxlet_vs_hashsolo_concordance_heatmap.png", + "donor_match/test1/freemuxlet_vs_hashsolo/test1_freemuxlet_vs_hashsolo_correlation_res.csv", + "donor_match/test1/freemuxlet_vs_hashsolo/test1_freemuxlet_vs_hashsolo_donor_match.csv", + "donor_match/test1/freemuxlet_vs_hashsolo/test1_freemuxlet_vs_hashsolo_intersect_assignment_after_match.csv", + "donor_match/test1/freemuxlet_vs_htodemux", + "donor_match/test1/freemuxlet_vs_htodemux/test1_freemuxlet_vs_htodemux_all_assignment_after_match.csv", + "donor_match/test1/freemuxlet_vs_htodemux/test1_freemuxlet_vs_htodemux_concordance_heatmap.png", + "donor_match/test1/freemuxlet_vs_htodemux/test1_freemuxlet_vs_htodemux_correlation_res.csv", + "donor_match/test1/freemuxlet_vs_htodemux/test1_freemuxlet_vs_htodemux_donor_match.csv", + "donor_match/test1/freemuxlet_vs_htodemux/test1_freemuxlet_vs_htodemux_intersect_assignment_after_match.csv", + "donor_match/test1/freemuxlet_vs_multiseq", + "donor_match/test1/freemuxlet_vs_multiseq/test1_freemuxlet_vs_multiseq_all_assignment_after_match.csv", + "donor_match/test1/freemuxlet_vs_multiseq/test1_freemuxlet_vs_multiseq_concordance_heatmap.png", + "donor_match/test1/freemuxlet_vs_multiseq/test1_freemuxlet_vs_multiseq_correlation_res.csv", + "donor_match/test1/freemuxlet_vs_multiseq/test1_freemuxlet_vs_multiseq_donor_match.csv", + "donor_match/test1/freemuxlet_vs_multiseq/test1_freemuxlet_vs_multiseq_intersect_assignment_after_match.csv", "donor_match/test1/souporcell_vs_bff_consensuscall", "donor_match/test1/souporcell_vs_bff_consensuscall/test1_souporcell_vs_bff_consensuscall_all_assignment_after_match.csv", "donor_match/test1/souporcell_vs_bff_consensuscall/test1_souporcell_vs_bff_consensuscall_concordance_heatmap.png", @@ -126,6 +181,12 @@ "donor_match/test1/souporcell_vs_bff_raw/test1_souporcell_vs_bff_raw_correlation_res.csv", "donor_match/test1/souporcell_vs_bff_raw/test1_souporcell_vs_bff_raw_donor_match.csv", "donor_match/test1/souporcell_vs_bff_raw/test1_souporcell_vs_bff_raw_intersect_assignment_after_match.csv", + "donor_match/test1/souporcell_vs_demuxem", + "donor_match/test1/souporcell_vs_demuxem/test1_souporcell_vs_demuxem_all_assignment_after_match.csv", + "donor_match/test1/souporcell_vs_demuxem/test1_souporcell_vs_demuxem_concordance_heatmap.png", + "donor_match/test1/souporcell_vs_demuxem/test1_souporcell_vs_demuxem_correlation_res.csv", + "donor_match/test1/souporcell_vs_demuxem/test1_souporcell_vs_demuxem_donor_match.csv", + "donor_match/test1/souporcell_vs_demuxem/test1_souporcell_vs_demuxem_intersect_assignment_after_match.csv", "donor_match/test1/souporcell_vs_gmmdemux", "donor_match/test1/souporcell_vs_gmmdemux/test1_souporcell_vs_gmmdemux_all_assignment_after_match.csv", "donor_match/test1/souporcell_vs_gmmdemux/test1_souporcell_vs_gmmdemux_concordance_heatmap.png", @@ -138,6 +199,24 @@ "donor_match/test1/souporcell_vs_hasheddrops/test1_souporcell_vs_hasheddrops_correlation_res.csv", "donor_match/test1/souporcell_vs_hasheddrops/test1_souporcell_vs_hasheddrops_donor_match.csv", "donor_match/test1/souporcell_vs_hasheddrops/test1_souporcell_vs_hasheddrops_intersect_assignment_after_match.csv", + "donor_match/test1/souporcell_vs_hashsolo", + "donor_match/test1/souporcell_vs_hashsolo/test1_souporcell_vs_hashsolo_all_assignment_after_match.csv", + "donor_match/test1/souporcell_vs_hashsolo/test1_souporcell_vs_hashsolo_concordance_heatmap.png", + "donor_match/test1/souporcell_vs_hashsolo/test1_souporcell_vs_hashsolo_correlation_res.csv", + "donor_match/test1/souporcell_vs_hashsolo/test1_souporcell_vs_hashsolo_donor_match.csv", + "donor_match/test1/souporcell_vs_hashsolo/test1_souporcell_vs_hashsolo_intersect_assignment_after_match.csv", + "donor_match/test1/souporcell_vs_htodemux", + "donor_match/test1/souporcell_vs_htodemux/test1_souporcell_vs_htodemux_all_assignment_after_match.csv", + "donor_match/test1/souporcell_vs_htodemux/test1_souporcell_vs_htodemux_concordance_heatmap.png", + "donor_match/test1/souporcell_vs_htodemux/test1_souporcell_vs_htodemux_correlation_res.csv", + "donor_match/test1/souporcell_vs_htodemux/test1_souporcell_vs_htodemux_donor_match.csv", + "donor_match/test1/souporcell_vs_htodemux/test1_souporcell_vs_htodemux_intersect_assignment_after_match.csv", + "donor_match/test1/souporcell_vs_multiseq", + "donor_match/test1/souporcell_vs_multiseq/test1_souporcell_vs_multiseq_all_assignment_after_match.csv", + "donor_match/test1/souporcell_vs_multiseq/test1_souporcell_vs_multiseq_concordance_heatmap.png", + "donor_match/test1/souporcell_vs_multiseq/test1_souporcell_vs_multiseq_correlation_res.csv", + "donor_match/test1/souporcell_vs_multiseq/test1_souporcell_vs_multiseq_donor_match.csv", + "donor_match/test1/souporcell_vs_multiseq/test1_souporcell_vs_multiseq_intersect_assignment_after_match.csv", "donor_match/test1/test1_best_all_assignment_after_match.csv", "donor_match/test1/test1_best_donor_match.csv", "donor_match/test1/test1_best_intersect_assignment_after_match.csv", @@ -155,6 +234,12 @@ "donor_match/test2/freemuxlet_vs_bff_raw/test2_freemuxlet_vs_bff_raw_correlation_res.csv", "donor_match/test2/freemuxlet_vs_bff_raw/test2_freemuxlet_vs_bff_raw_donor_match.csv", "donor_match/test2/freemuxlet_vs_bff_raw/test2_freemuxlet_vs_bff_raw_intersect_assignment_after_match.csv", + "donor_match/test2/freemuxlet_vs_demuxem", + "donor_match/test2/freemuxlet_vs_demuxem/test2_freemuxlet_vs_demuxem_all_assignment_after_match.csv", + "donor_match/test2/freemuxlet_vs_demuxem/test2_freemuxlet_vs_demuxem_concordance_heatmap.png", + "donor_match/test2/freemuxlet_vs_demuxem/test2_freemuxlet_vs_demuxem_correlation_res.csv", + "donor_match/test2/freemuxlet_vs_demuxem/test2_freemuxlet_vs_demuxem_donor_match.csv", + "donor_match/test2/freemuxlet_vs_demuxem/test2_freemuxlet_vs_demuxem_intersect_assignment_after_match.csv", "donor_match/test2/freemuxlet_vs_gmmdemux", "donor_match/test2/freemuxlet_vs_gmmdemux/test2_freemuxlet_vs_gmmdemux_all_assignment_after_match.csv", "donor_match/test2/freemuxlet_vs_gmmdemux/test2_freemuxlet_vs_gmmdemux_concordance_heatmap.png", @@ -167,6 +252,24 @@ "donor_match/test2/freemuxlet_vs_hasheddrops/test2_freemuxlet_vs_hasheddrops_correlation_res.csv", "donor_match/test2/freemuxlet_vs_hasheddrops/test2_freemuxlet_vs_hasheddrops_donor_match.csv", "donor_match/test2/freemuxlet_vs_hasheddrops/test2_freemuxlet_vs_hasheddrops_intersect_assignment_after_match.csv", + "donor_match/test2/freemuxlet_vs_hashsolo", + "donor_match/test2/freemuxlet_vs_hashsolo/test2_freemuxlet_vs_hashsolo_all_assignment_after_match.csv", + "donor_match/test2/freemuxlet_vs_hashsolo/test2_freemuxlet_vs_hashsolo_concordance_heatmap.png", + "donor_match/test2/freemuxlet_vs_hashsolo/test2_freemuxlet_vs_hashsolo_correlation_res.csv", + "donor_match/test2/freemuxlet_vs_hashsolo/test2_freemuxlet_vs_hashsolo_donor_match.csv", + "donor_match/test2/freemuxlet_vs_hashsolo/test2_freemuxlet_vs_hashsolo_intersect_assignment_after_match.csv", + "donor_match/test2/freemuxlet_vs_htodemux", + "donor_match/test2/freemuxlet_vs_htodemux/test2_freemuxlet_vs_htodemux_all_assignment_after_match.csv", + "donor_match/test2/freemuxlet_vs_htodemux/test2_freemuxlet_vs_htodemux_concordance_heatmap.png", + "donor_match/test2/freemuxlet_vs_htodemux/test2_freemuxlet_vs_htodemux_correlation_res.csv", + "donor_match/test2/freemuxlet_vs_htodemux/test2_freemuxlet_vs_htodemux_donor_match.csv", + "donor_match/test2/freemuxlet_vs_htodemux/test2_freemuxlet_vs_htodemux_intersect_assignment_after_match.csv", + "donor_match/test2/freemuxlet_vs_multiseq", + "donor_match/test2/freemuxlet_vs_multiseq/test2_freemuxlet_vs_multiseq_all_assignment_after_match.csv", + "donor_match/test2/freemuxlet_vs_multiseq/test2_freemuxlet_vs_multiseq_concordance_heatmap.png", + "donor_match/test2/freemuxlet_vs_multiseq/test2_freemuxlet_vs_multiseq_correlation_res.csv", + "donor_match/test2/freemuxlet_vs_multiseq/test2_freemuxlet_vs_multiseq_donor_match.csv", + "donor_match/test2/freemuxlet_vs_multiseq/test2_freemuxlet_vs_multiseq_intersect_assignment_after_match.csv", "donor_match/test2/souporcell_vs_bff_consensuscall", "donor_match/test2/souporcell_vs_bff_consensuscall/test2_souporcell_vs_bff_consensuscall_all_assignment_after_match.csv", "donor_match/test2/souporcell_vs_bff_consensuscall/test2_souporcell_vs_bff_consensuscall_concordance_heatmap.png", @@ -179,6 +282,12 @@ "donor_match/test2/souporcell_vs_bff_raw/test2_souporcell_vs_bff_raw_correlation_res.csv", "donor_match/test2/souporcell_vs_bff_raw/test2_souporcell_vs_bff_raw_donor_match.csv", "donor_match/test2/souporcell_vs_bff_raw/test2_souporcell_vs_bff_raw_intersect_assignment_after_match.csv", + "donor_match/test2/souporcell_vs_demuxem", + "donor_match/test2/souporcell_vs_demuxem/test2_souporcell_vs_demuxem_all_assignment_after_match.csv", + "donor_match/test2/souporcell_vs_demuxem/test2_souporcell_vs_demuxem_concordance_heatmap.png", + "donor_match/test2/souporcell_vs_demuxem/test2_souporcell_vs_demuxem_correlation_res.csv", + "donor_match/test2/souporcell_vs_demuxem/test2_souporcell_vs_demuxem_donor_match.csv", + "donor_match/test2/souporcell_vs_demuxem/test2_souporcell_vs_demuxem_intersect_assignment_after_match.csv", "donor_match/test2/souporcell_vs_gmmdemux", "donor_match/test2/souporcell_vs_gmmdemux/test2_souporcell_vs_gmmdemux_all_assignment_after_match.csv", "donor_match/test2/souporcell_vs_gmmdemux/test2_souporcell_vs_gmmdemux_concordance_heatmap.png", @@ -191,6 +300,24 @@ "donor_match/test2/souporcell_vs_hasheddrops/test2_souporcell_vs_hasheddrops_correlation_res.csv", "donor_match/test2/souporcell_vs_hasheddrops/test2_souporcell_vs_hasheddrops_donor_match.csv", "donor_match/test2/souporcell_vs_hasheddrops/test2_souporcell_vs_hasheddrops_intersect_assignment_after_match.csv", + "donor_match/test2/souporcell_vs_hashsolo", + "donor_match/test2/souporcell_vs_hashsolo/test2_souporcell_vs_hashsolo_all_assignment_after_match.csv", + "donor_match/test2/souporcell_vs_hashsolo/test2_souporcell_vs_hashsolo_concordance_heatmap.png", + "donor_match/test2/souporcell_vs_hashsolo/test2_souporcell_vs_hashsolo_correlation_res.csv", + "donor_match/test2/souporcell_vs_hashsolo/test2_souporcell_vs_hashsolo_donor_match.csv", + "donor_match/test2/souporcell_vs_hashsolo/test2_souporcell_vs_hashsolo_intersect_assignment_after_match.csv", + "donor_match/test2/souporcell_vs_htodemux", + "donor_match/test2/souporcell_vs_htodemux/test2_souporcell_vs_htodemux_all_assignment_after_match.csv", + "donor_match/test2/souporcell_vs_htodemux/test2_souporcell_vs_htodemux_concordance_heatmap.png", + "donor_match/test2/souporcell_vs_htodemux/test2_souporcell_vs_htodemux_correlation_res.csv", + "donor_match/test2/souporcell_vs_htodemux/test2_souporcell_vs_htodemux_donor_match.csv", + "donor_match/test2/souporcell_vs_htodemux/test2_souporcell_vs_htodemux_intersect_assignment_after_match.csv", + "donor_match/test2/souporcell_vs_multiseq", + "donor_match/test2/souporcell_vs_multiseq/test2_souporcell_vs_multiseq_all_assignment_after_match.csv", + "donor_match/test2/souporcell_vs_multiseq/test2_souporcell_vs_multiseq_concordance_heatmap.png", + "donor_match/test2/souporcell_vs_multiseq/test2_souporcell_vs_multiseq_correlation_res.csv", + "donor_match/test2/souporcell_vs_multiseq/test2_souporcell_vs_multiseq_donor_match.csv", + "donor_match/test2/souporcell_vs_multiseq/test2_souporcell_vs_multiseq_intersect_assignment_after_match.csv", "donor_match/test2/test2_best_all_assignment_after_match.csv", "donor_match/test2/test2_best_donor_match.csv", "donor_match/test2/test2_best_intersect_assignment_after_match.csv", @@ -208,6 +335,12 @@ "donor_match/test3/freemuxlet_vs_bff_raw/test3_freemuxlet_vs_bff_raw_correlation_res.csv", "donor_match/test3/freemuxlet_vs_bff_raw/test3_freemuxlet_vs_bff_raw_donor_match.csv", "donor_match/test3/freemuxlet_vs_bff_raw/test3_freemuxlet_vs_bff_raw_intersect_assignment_after_match.csv", + "donor_match/test3/freemuxlet_vs_demuxem", + "donor_match/test3/freemuxlet_vs_demuxem/test3_freemuxlet_vs_demuxem_all_assignment_after_match.csv", + "donor_match/test3/freemuxlet_vs_demuxem/test3_freemuxlet_vs_demuxem_concordance_heatmap.png", + "donor_match/test3/freemuxlet_vs_demuxem/test3_freemuxlet_vs_demuxem_correlation_res.csv", + "donor_match/test3/freemuxlet_vs_demuxem/test3_freemuxlet_vs_demuxem_donor_match.csv", + "donor_match/test3/freemuxlet_vs_demuxem/test3_freemuxlet_vs_demuxem_intersect_assignment_after_match.csv", "donor_match/test3/freemuxlet_vs_gmmdemux", "donor_match/test3/freemuxlet_vs_gmmdemux/test3_freemuxlet_vs_gmmdemux_all_assignment_after_match.csv", "donor_match/test3/freemuxlet_vs_gmmdemux/test3_freemuxlet_vs_gmmdemux_concordance_heatmap.png", @@ -220,6 +353,24 @@ "donor_match/test3/freemuxlet_vs_hasheddrops/test3_freemuxlet_vs_hasheddrops_correlation_res.csv", "donor_match/test3/freemuxlet_vs_hasheddrops/test3_freemuxlet_vs_hasheddrops_donor_match.csv", "donor_match/test3/freemuxlet_vs_hasheddrops/test3_freemuxlet_vs_hasheddrops_intersect_assignment_after_match.csv", + "donor_match/test3/freemuxlet_vs_hashsolo", + "donor_match/test3/freemuxlet_vs_hashsolo/test3_freemuxlet_vs_hashsolo_all_assignment_after_match.csv", + "donor_match/test3/freemuxlet_vs_hashsolo/test3_freemuxlet_vs_hashsolo_concordance_heatmap.png", + "donor_match/test3/freemuxlet_vs_hashsolo/test3_freemuxlet_vs_hashsolo_correlation_res.csv", + "donor_match/test3/freemuxlet_vs_hashsolo/test3_freemuxlet_vs_hashsolo_donor_match.csv", + "donor_match/test3/freemuxlet_vs_hashsolo/test3_freemuxlet_vs_hashsolo_intersect_assignment_after_match.csv", + "donor_match/test3/freemuxlet_vs_htodemux", + "donor_match/test3/freemuxlet_vs_htodemux/test3_freemuxlet_vs_htodemux_all_assignment_after_match.csv", + "donor_match/test3/freemuxlet_vs_htodemux/test3_freemuxlet_vs_htodemux_concordance_heatmap.png", + "donor_match/test3/freemuxlet_vs_htodemux/test3_freemuxlet_vs_htodemux_correlation_res.csv", + "donor_match/test3/freemuxlet_vs_htodemux/test3_freemuxlet_vs_htodemux_donor_match.csv", + "donor_match/test3/freemuxlet_vs_htodemux/test3_freemuxlet_vs_htodemux_intersect_assignment_after_match.csv", + "donor_match/test3/freemuxlet_vs_multiseq", + "donor_match/test3/freemuxlet_vs_multiseq/test3_freemuxlet_vs_multiseq_all_assignment_after_match.csv", + "donor_match/test3/freemuxlet_vs_multiseq/test3_freemuxlet_vs_multiseq_concordance_heatmap.png", + "donor_match/test3/freemuxlet_vs_multiseq/test3_freemuxlet_vs_multiseq_correlation_res.csv", + "donor_match/test3/freemuxlet_vs_multiseq/test3_freemuxlet_vs_multiseq_donor_match.csv", + "donor_match/test3/freemuxlet_vs_multiseq/test3_freemuxlet_vs_multiseq_intersect_assignment_after_match.csv", "donor_match/test3/souporcell_vs_bff_consensuscall", "donor_match/test3/souporcell_vs_bff_consensuscall/test3_souporcell_vs_bff_consensuscall_all_assignment_after_match.csv", "donor_match/test3/souporcell_vs_bff_consensuscall/test3_souporcell_vs_bff_consensuscall_concordance_heatmap.png", @@ -232,6 +383,12 @@ "donor_match/test3/souporcell_vs_bff_raw/test3_souporcell_vs_bff_raw_correlation_res.csv", "donor_match/test3/souporcell_vs_bff_raw/test3_souporcell_vs_bff_raw_donor_match.csv", "donor_match/test3/souporcell_vs_bff_raw/test3_souporcell_vs_bff_raw_intersect_assignment_after_match.csv", + "donor_match/test3/souporcell_vs_demuxem", + "donor_match/test3/souporcell_vs_demuxem/test3_souporcell_vs_demuxem_all_assignment_after_match.csv", + "donor_match/test3/souporcell_vs_demuxem/test3_souporcell_vs_demuxem_concordance_heatmap.png", + "donor_match/test3/souporcell_vs_demuxem/test3_souporcell_vs_demuxem_correlation_res.csv", + "donor_match/test3/souporcell_vs_demuxem/test3_souporcell_vs_demuxem_donor_match.csv", + "donor_match/test3/souporcell_vs_demuxem/test3_souporcell_vs_demuxem_intersect_assignment_after_match.csv", "donor_match/test3/souporcell_vs_gmmdemux", "donor_match/test3/souporcell_vs_gmmdemux/test3_souporcell_vs_gmmdemux_all_assignment_after_match.csv", "donor_match/test3/souporcell_vs_gmmdemux/test3_souporcell_vs_gmmdemux_concordance_heatmap.png", @@ -244,6 +401,24 @@ "donor_match/test3/souporcell_vs_hasheddrops/test3_souporcell_vs_hasheddrops_correlation_res.csv", "donor_match/test3/souporcell_vs_hasheddrops/test3_souporcell_vs_hasheddrops_donor_match.csv", "donor_match/test3/souporcell_vs_hasheddrops/test3_souporcell_vs_hasheddrops_intersect_assignment_after_match.csv", + "donor_match/test3/souporcell_vs_hashsolo", + "donor_match/test3/souporcell_vs_hashsolo/test3_souporcell_vs_hashsolo_all_assignment_after_match.csv", + "donor_match/test3/souporcell_vs_hashsolo/test3_souporcell_vs_hashsolo_concordance_heatmap.png", + "donor_match/test3/souporcell_vs_hashsolo/test3_souporcell_vs_hashsolo_correlation_res.csv", + "donor_match/test3/souporcell_vs_hashsolo/test3_souporcell_vs_hashsolo_donor_match.csv", + "donor_match/test3/souporcell_vs_hashsolo/test3_souporcell_vs_hashsolo_intersect_assignment_after_match.csv", + "donor_match/test3/souporcell_vs_htodemux", + "donor_match/test3/souporcell_vs_htodemux/test3_souporcell_vs_htodemux_all_assignment_after_match.csv", + "donor_match/test3/souporcell_vs_htodemux/test3_souporcell_vs_htodemux_concordance_heatmap.png", + "donor_match/test3/souporcell_vs_htodemux/test3_souporcell_vs_htodemux_correlation_res.csv", + "donor_match/test3/souporcell_vs_htodemux/test3_souporcell_vs_htodemux_donor_match.csv", + "donor_match/test3/souporcell_vs_htodemux/test3_souporcell_vs_htodemux_intersect_assignment_after_match.csv", + "donor_match/test3/souporcell_vs_multiseq", + "donor_match/test3/souporcell_vs_multiseq/test3_souporcell_vs_multiseq_all_assignment_after_match.csv", + "donor_match/test3/souporcell_vs_multiseq/test3_souporcell_vs_multiseq_concordance_heatmap.png", + "donor_match/test3/souporcell_vs_multiseq/test3_souporcell_vs_multiseq_correlation_res.csv", + "donor_match/test3/souporcell_vs_multiseq/test3_souporcell_vs_multiseq_donor_match.csv", + "donor_match/test3/souporcell_vs_multiseq/test3_souporcell_vs_multiseq_intersect_assignment_after_match.csv", "donor_match/test3/test3_best_all_assignment_after_match.csv", "donor_match/test3/test3_best_donor_match.csv", "donor_match/test3/test3_best_intersect_assignment_after_match.csv", @@ -299,6 +474,13 @@ "find_variants/test3/test3_vireo_variants.csv", "genetic", "genetic/popscle", + "genetic/popscle/demuxlet", + "genetic/popscle/demuxlet/test1", + "genetic/popscle/demuxlet/test1/test1.best", + "genetic/popscle/demuxlet/test2", + "genetic/popscle/demuxlet/test2/test2.best", + "genetic/popscle/demuxlet/test3", + "genetic/popscle/demuxlet/test3/test3.best", "genetic/popscle/freemuxlet", "genetic/popscle/freemuxlet/test1", "genetic/popscle/freemuxlet/test1/test1.clust1.samples.gz", @@ -398,6 +580,28 @@ "hashing/bff/test3/test3_assignment_bff.csv", "hashing/bff/test3/test3_metrics_bff.csv", "hashing/bff/test3/test3_params_bff.csv", + "hashing/demuxem", + "hashing/demuxem/test1", + "hashing/demuxem/test1/test1.ambient_hashtag.hist.pdf", + "hashing/demuxem/test1/test1.background_probabilities.bar.pdf", + "hashing/demuxem/test1/test1.out.demuxEM.zarr.zip", + "hashing/demuxem/test1/test1.real_content.hist.pdf", + "hashing/demuxem/test1/test1.rna_demux.hist.pdf", + "hashing/demuxem/test1/test1_demux.zarr.zip", + "hashing/demuxem/test2", + "hashing/demuxem/test2/test2.ambient_hashtag.hist.pdf", + "hashing/demuxem/test2/test2.background_probabilities.bar.pdf", + "hashing/demuxem/test2/test2.out.demuxEM.zarr.zip", + "hashing/demuxem/test2/test2.real_content.hist.pdf", + "hashing/demuxem/test2/test2.rna_demux.hist.pdf", + "hashing/demuxem/test2/test2_demux.zarr.zip", + "hashing/demuxem/test3", + "hashing/demuxem/test3/test3.ambient_hashtag.hist.pdf", + "hashing/demuxem/test3/test3.background_probabilities.bar.pdf", + "hashing/demuxem/test3/test3.out.demuxEM.zarr.zip", + "hashing/demuxem/test3/test3.real_content.hist.pdf", + "hashing/demuxem/test3/test3.rna_demux.hist.pdf", + "hashing/demuxem/test3/test3_demux.zarr.zip", "hashing/gmm-demux", "hashing/gmm-demux/test1", "hashing/gmm-demux/test1/GMM_full.config", @@ -445,6 +649,69 @@ "hashing/hasheddrops/test3/test3_params_hasheddrops.csv", "hashing/hasheddrops/test3/test3_plot_hasheddrops.png", "hashing/hasheddrops/test3/test3_results_hasheddrops.csv", + "hashing/hashsolo", + "hashing/hashsolo/test1", + "hashing/hashsolo/test1/test1_assignment_hashsolo.csv", + "hashing/hashsolo/test1/test1_hashsolo.h5ad", + "hashing/hashsolo/test1/test1_params_hashsolo.csv", + "hashing/hashsolo/test2", + "hashing/hashsolo/test2/test2_assignment_hashsolo.csv", + "hashing/hashsolo/test2/test2_hashsolo.h5ad", + "hashing/hashsolo/test2/test2_params_hashsolo.csv", + "hashing/hashsolo/test3", + "hashing/hashsolo/test3/test3_assignment_hashsolo.csv", + "hashing/hashsolo/test3/test3_hashsolo.h5ad", + "hashing/hashsolo/test3/test3_params_hashsolo.csv", + "hashing/htodemux", + "hashing/htodemux/test1", + "hashing/htodemux/test1/test1_assignment_htodemux.csv", + "hashing/htodemux/test1/test1_classification_htodemux.csv", + "hashing/htodemux/test1/test1_htodemux.rds", + "hashing/htodemux/test1/test1_params_htodemux.csv", + "hashing/htodemux/test1/visualization", + "hashing/htodemux/test1/visualization/test1_featureScatter_htodemux.jpeg", + "hashing/htodemux/test1/visualization/test1_heatMap_htodemux.jpeg", + "hashing/htodemux/test1/visualization/test1_ridge_htodemux.jpeg", + "hashing/htodemux/test1/visualization/test1_tSNE_htodemux.jpeg", + "hashing/htodemux/test1/visualization/test1_violinPlot_htodemux.jpeg", + "hashing/htodemux/test1/visualization/test1_visual_params_htodemux.csv", + "hashing/htodemux/test2", + "hashing/htodemux/test2/test2_assignment_htodemux.csv", + "hashing/htodemux/test2/test2_classification_htodemux.csv", + "hashing/htodemux/test2/test2_htodemux.rds", + "hashing/htodemux/test2/test2_params_htodemux.csv", + "hashing/htodemux/test2/visualization", + "hashing/htodemux/test2/visualization/test2_featureScatter_htodemux.jpeg", + "hashing/htodemux/test2/visualization/test2_heatMap_htodemux.jpeg", + "hashing/htodemux/test2/visualization/test2_ridge_htodemux.jpeg", + "hashing/htodemux/test2/visualization/test2_tSNE_htodemux.jpeg", + "hashing/htodemux/test2/visualization/test2_violinPlot_htodemux.jpeg", + "hashing/htodemux/test2/visualization/test2_visual_params_htodemux.csv", + "hashing/htodemux/test3", + "hashing/htodemux/test3/test3_assignment_htodemux.csv", + "hashing/htodemux/test3/test3_classification_htodemux.csv", + "hashing/htodemux/test3/test3_htodemux.rds", + "hashing/htodemux/test3/test3_params_htodemux.csv", + "hashing/htodemux/test3/visualization", + "hashing/htodemux/test3/visualization/test3_featureScatter_htodemux.jpeg", + "hashing/htodemux/test3/visualization/test3_heatMap_htodemux.jpeg", + "hashing/htodemux/test3/visualization/test3_ridge_htodemux.jpeg", + "hashing/htodemux/test3/visualization/test3_tSNE_htodemux.jpeg", + "hashing/htodemux/test3/visualization/test3_violinPlot_htodemux.jpeg", + "hashing/htodemux/test3/visualization/test3_visual_params_htodemux.csv", + "hashing/multiseqdemux", + "hashing/multiseqdemux/test1", + "hashing/multiseqdemux/test1/test1_multiseqdemux.rds", + "hashing/multiseqdemux/test1/test1_params_multiseqdemux.csv", + "hashing/multiseqdemux/test1/test1_res_multiseqdemux.csv", + "hashing/multiseqdemux/test2", + "hashing/multiseqdemux/test2/test2_multiseqdemux.rds", + "hashing/multiseqdemux/test2/test2_params_multiseqdemux.csv", + "hashing/multiseqdemux/test2/test2_res_multiseqdemux.csv", + "hashing/multiseqdemux/test3", + "hashing/multiseqdemux/test3/test3_multiseqdemux.rds", + "hashing/multiseqdemux/test3/test3_params_multiseqdemux.csv", + "hashing/multiseqdemux/test3/test3_res_multiseqdemux.csv", "hashing/summary", "hashing/summary/test1", "hashing/summary/test1/test1_hashing_overview_assignment.csv", @@ -494,298 +761,463 @@ "summary/test3/test3_hashing.h5ad" ], [ - "test1_freemuxlet_vs_bff_consensuscall_all_assignment_after_match.csv:md5,e6c7237f7a927ab86368d9ecf28b807c", + "test1_freemuxlet_vs_bff_consensuscall_all_assignment_after_match.csv:md5,de2093a5ff281af46d45d7d4770338cf", "test1_freemuxlet_vs_bff_consensuscall_concordance_heatmap.png:md5,5c3e82573df24e781d4f422bc3b6925a", "test1_freemuxlet_vs_bff_consensuscall_correlation_res.csv:md5,5f2f2076175e313f10396b32fac4c26c", "test1_freemuxlet_vs_bff_consensuscall_donor_match.csv:md5,b92352374bf4b32f7e3a1b032a4c8b5f", - "test1_freemuxlet_vs_bff_consensuscall_intersect_assignment_after_match.csv:md5,3f611c4fb970117f9771ce05f4ec4d8d", - "test1_freemuxlet_vs_bff_raw_all_assignment_after_match.csv:md5,54406a297be20405919bd15348111ad8", + "test1_freemuxlet_vs_bff_consensuscall_intersect_assignment_after_match.csv:md5,a4df295c2689a2b3abfab4476e5a1dcb", + "test1_freemuxlet_vs_bff_raw_all_assignment_after_match.csv:md5,8c42806ae93d0bd0fc79864db6a79463", "test1_freemuxlet_vs_bff_raw_concordance_heatmap.png:md5,5c3e82573df24e781d4f422bc3b6925a", "test1_freemuxlet_vs_bff_raw_correlation_res.csv:md5,5f2f2076175e313f10396b32fac4c26c", "test1_freemuxlet_vs_bff_raw_donor_match.csv:md5,b92352374bf4b32f7e3a1b032a4c8b5f", - "test1_freemuxlet_vs_bff_raw_intersect_assignment_after_match.csv:md5,87b3ca9e977fe3df3536ba169566abed", - "test1_freemuxlet_vs_gmmdemux_all_assignment_after_match.csv:md5,e4ec8cd25409bdb7de568dcfd8ca3ad4", + "test1_freemuxlet_vs_bff_raw_intersect_assignment_after_match.csv:md5,6af3427166aab49c0136369311533d83", + "test1_freemuxlet_vs_demuxem_all_assignment_after_match.csv:md5,505b79ce4db1cef804bfeafb12f70a5d", + "test1_freemuxlet_vs_demuxem_concordance_heatmap.png:md5,360bc756be590ccb80d2add912d61e16", + "test1_freemuxlet_vs_demuxem_correlation_res.csv:md5,c880595fe0a83d46f1bcbb361c994828", + "test1_freemuxlet_vs_demuxem_donor_match.csv:md5,b92352374bf4b32f7e3a1b032a4c8b5f", + "test1_freemuxlet_vs_demuxem_intersect_assignment_after_match.csv:md5,edf378d87391a486b6635713e04fdbd9", + "test1_freemuxlet_vs_gmmdemux_all_assignment_after_match.csv:md5,473d87d9a65d8562e83d21067cfce536", "test1_freemuxlet_vs_gmmdemux_concordance_heatmap.png:md5,5c3e82573df24e781d4f422bc3b6925a", "test1_freemuxlet_vs_gmmdemux_correlation_res.csv:md5,5f2f2076175e313f10396b32fac4c26c", "test1_freemuxlet_vs_gmmdemux_donor_match.csv:md5,b92352374bf4b32f7e3a1b032a4c8b5f", - "test1_freemuxlet_vs_gmmdemux_intersect_assignment_after_match.csv:md5,f9092a34b0a2eef31daf452d89412f58", - "test1_freemuxlet_vs_hasheddrops_all_assignment_after_match.csv:md5,d777c2ffc8201579aa80123d42ca4c7f", + "test1_freemuxlet_vs_gmmdemux_intersect_assignment_after_match.csv:md5,d2f63d5a37e4a085fdd0ee97bfffe4f4", + "test1_freemuxlet_vs_hasheddrops_all_assignment_after_match.csv:md5,fb66dca65ba0ddaec50b78f4be1a662a", "test1_freemuxlet_vs_hasheddrops_concordance_heatmap.png:md5,5c3e82573df24e781d4f422bc3b6925a", "test1_freemuxlet_vs_hasheddrops_correlation_res.csv:md5,5f2f2076175e313f10396b32fac4c26c", "test1_freemuxlet_vs_hasheddrops_donor_match.csv:md5,b92352374bf4b32f7e3a1b032a4c8b5f", - "test1_freemuxlet_vs_hasheddrops_intersect_assignment_after_match.csv:md5,b3acbe48f60fc1d68dd8198950549812", - "test1_souporcell_vs_bff_consensuscall_all_assignment_after_match.csv:md5,4c61d15e4693b6bc03cd296e757b6498", + "test1_freemuxlet_vs_hasheddrops_intersect_assignment_after_match.csv:md5,b6e3daca5b12b963239eb9ff86ef7244", + "test1_freemuxlet_vs_hashsolo_all_assignment_after_match.csv:md5,8dbc642b118d82fe0dbb24631126d93b", + "test1_freemuxlet_vs_hashsolo_concordance_heatmap.png:md5,a6dd712948a85cf2f1660b4f6a07a5cb", + "test1_freemuxlet_vs_hashsolo_correlation_res.csv:md5,f6d61938702d82cca7a5ecbe4871a3fa", + "test1_freemuxlet_vs_hashsolo_donor_match.csv:md5,b92352374bf4b32f7e3a1b032a4c8b5f", + "test1_freemuxlet_vs_hashsolo_intersect_assignment_after_match.csv:md5,69a6c9f528d16e72d1a16927d65317fa", + "test1_freemuxlet_vs_htodemux_all_assignment_after_match.csv:md5,c9413355e0d822c0436fecf1706d0cfc", + "test1_freemuxlet_vs_htodemux_concordance_heatmap.png:md5,42ab19f0e287585a39d52817f85943f0", + "test1_freemuxlet_vs_htodemux_correlation_res.csv:md5,56e1f17bb5e0424e4840b2d89fe892d7", + "test1_freemuxlet_vs_htodemux_donor_match.csv:md5,b92352374bf4b32f7e3a1b032a4c8b5f", + "test1_freemuxlet_vs_htodemux_intersect_assignment_after_match.csv:md5,32cee037b091f9b1ca78ed88e11c75d4", + "test1_freemuxlet_vs_multiseq_all_assignment_after_match.csv:md5,cb8a579a6233cf227e4eb987fed88dc0", + "test1_freemuxlet_vs_multiseq_concordance_heatmap.png:md5,64bc44fb9aeeb1581ebbf708c8765b4d", + "test1_freemuxlet_vs_multiseq_correlation_res.csv:md5,8a690fde26636e0e4c44b41a156ae47a", + "test1_freemuxlet_vs_multiseq_donor_match.csv:md5,b92352374bf4b32f7e3a1b032a4c8b5f", + "test1_freemuxlet_vs_multiseq_intersect_assignment_after_match.csv:md5,bd1135d6b159a17c245742d6c4312219", + "test1_souporcell_vs_bff_consensuscall_all_assignment_after_match.csv:md5,0523d43db3b0b43d3c06642887b1e681", "test1_souporcell_vs_bff_consensuscall_concordance_heatmap.png:md5,d48df9ebb22dc1f2616511ad0405cdfd", "test1_souporcell_vs_bff_consensuscall_correlation_res.csv:md5,7e632ed518a1e6f02ed1ea25a28a4e45", "test1_souporcell_vs_bff_consensuscall_donor_match.csv:md5,d1feea9baad73b5bc106472a226cbbf2", - "test1_souporcell_vs_bff_consensuscall_intersect_assignment_after_match.csv:md5,17fe8deedeb704f60632733c9ec971ea", - "test1_souporcell_vs_bff_raw_all_assignment_after_match.csv:md5,f8e44703900103c804694041924df32a", + "test1_souporcell_vs_bff_consensuscall_intersect_assignment_after_match.csv:md5,d0069819b2b9722cc72ee2fae18e24d7", + "test1_souporcell_vs_bff_raw_all_assignment_after_match.csv:md5,3693453872310717f649a8da3538a8b2", "test1_souporcell_vs_bff_raw_concordance_heatmap.png:md5,d48df9ebb22dc1f2616511ad0405cdfd", "test1_souporcell_vs_bff_raw_correlation_res.csv:md5,7e632ed518a1e6f02ed1ea25a28a4e45", "test1_souporcell_vs_bff_raw_donor_match.csv:md5,d1feea9baad73b5bc106472a226cbbf2", - "test1_souporcell_vs_bff_raw_intersect_assignment_after_match.csv:md5,cc89c4263e95c7802152d5a84e5fda5a", - "test1_souporcell_vs_gmmdemux_all_assignment_after_match.csv:md5,5a9927b30847c3c3ff82f0cc5274076f", + "test1_souporcell_vs_bff_raw_intersect_assignment_after_match.csv:md5,9618d277186327f7092b4a838329c15c", + "test1_souporcell_vs_demuxem_all_assignment_after_match.csv:md5,02807a959d832624e2869d6c885a3676", + "test1_souporcell_vs_demuxem_concordance_heatmap.png:md5,8de7171a8fc0ec2f38e6f45e6c28cb03", + "test1_souporcell_vs_demuxem_correlation_res.csv:md5,24eca3fc8a0241e70b02055107683b91", + "test1_souporcell_vs_demuxem_donor_match.csv:md5,d1feea9baad73b5bc106472a226cbbf2", + "test1_souporcell_vs_demuxem_intersect_assignment_after_match.csv:md5,14280a2e649c89a8eb27f9c36798bc97", + "test1_souporcell_vs_gmmdemux_all_assignment_after_match.csv:md5,e163bc80fc846e43cca5d6281dc2bbc8", "test1_souporcell_vs_gmmdemux_concordance_heatmap.png:md5,d48df9ebb22dc1f2616511ad0405cdfd", "test1_souporcell_vs_gmmdemux_correlation_res.csv:md5,7e632ed518a1e6f02ed1ea25a28a4e45", "test1_souporcell_vs_gmmdemux_donor_match.csv:md5,d1feea9baad73b5bc106472a226cbbf2", - "test1_souporcell_vs_gmmdemux_intersect_assignment_after_match.csv:md5,d9cc57a14177d9c362b91dc923c123c8", - "test1_souporcell_vs_hasheddrops_all_assignment_after_match.csv:md5,8e5f886ef04c7d224861348ae470adc5", + "test1_souporcell_vs_gmmdemux_intersect_assignment_after_match.csv:md5,dd7403edeb5ba0e17da879a3ff310aba", + "test1_souporcell_vs_hasheddrops_all_assignment_after_match.csv:md5,2177729941ec7e0ec3e30490367ac648", "test1_souporcell_vs_hasheddrops_concordance_heatmap.png:md5,d48df9ebb22dc1f2616511ad0405cdfd", "test1_souporcell_vs_hasheddrops_correlation_res.csv:md5,7e632ed518a1e6f02ed1ea25a28a4e45", "test1_souporcell_vs_hasheddrops_donor_match.csv:md5,d1feea9baad73b5bc106472a226cbbf2", - "test1_souporcell_vs_hasheddrops_intersect_assignment_after_match.csv:md5,4d4e53deb9c6a9dd9ae9d6d89493d736", - "test1_best_all_assignment_after_match.csv:md5,4c61d15e4693b6bc03cd296e757b6498", + "test1_souporcell_vs_hasheddrops_intersect_assignment_after_match.csv:md5,595efa559856d1dfc7bae9d109478221", + "test1_souporcell_vs_hashsolo_all_assignment_after_match.csv:md5,89a1b529a2f3805193a3547771df15bc", + "test1_souporcell_vs_hashsolo_concordance_heatmap.png:md5,aafe59185e6eef45a25585a8c06505e3", + "test1_souporcell_vs_hashsolo_correlation_res.csv:md5,bdcc36b8fcabb311fa40d3d48e2b3080", + "test1_souporcell_vs_hashsolo_donor_match.csv:md5,d1feea9baad73b5bc106472a226cbbf2", + "test1_souporcell_vs_hashsolo_intersect_assignment_after_match.csv:md5,b9e34df8e9037c17fce729b661480b7f", + "test1_souporcell_vs_htodemux_all_assignment_after_match.csv:md5,986fb63a696867e670b14a7d3c299eeb", + "test1_souporcell_vs_htodemux_concordance_heatmap.png:md5,d48df9ebb22dc1f2616511ad0405cdfd", + "test1_souporcell_vs_htodemux_correlation_res.csv:md5,7e632ed518a1e6f02ed1ea25a28a4e45", + "test1_souporcell_vs_htodemux_donor_match.csv:md5,d1feea9baad73b5bc106472a226cbbf2", + "test1_souporcell_vs_htodemux_intersect_assignment_after_match.csv:md5,f9ac7bf74aea62b5cb9bd34f5fe002b2", + "test1_souporcell_vs_multiseq_all_assignment_after_match.csv:md5,71a33f1f8070410b7c016bb7234a89e2", + "test1_souporcell_vs_multiseq_concordance_heatmap.png:md5,987ae361340b8b77daecc726ffa6b9de", + "test1_souporcell_vs_multiseq_correlation_res.csv:md5,a5f9d7ebd675403b062cd0f4a59b1592", + "test1_souporcell_vs_multiseq_donor_match.csv:md5,d1feea9baad73b5bc106472a226cbbf2", + "test1_souporcell_vs_multiseq_intersect_assignment_after_match.csv:md5,a904d4236db50ef2d35fa4e0d608284f", + "test1_best_all_assignment_after_match.csv:md5,0523d43db3b0b43d3c06642887b1e681", "test1_best_donor_match.csv:md5,d1feea9baad73b5bc106472a226cbbf2", - "test1_best_intersect_assignment_after_match.csv:md5,17fe8deedeb704f60632733c9ec971ea", - "test1_score_record.csv:md5,19e6d7aa257ab96338c70bb1e69442b3", - "test2_freemuxlet_vs_bff_consensuscall_all_assignment_after_match.csv:md5,e6c7237f7a927ab86368d9ecf28b807c", + "test1_best_intersect_assignment_after_match.csv:md5,d0069819b2b9722cc72ee2fae18e24d7", + "test1_score_record.csv:md5,f85dc45d1e25a47aa1ce5df0f3652963", + "test2_freemuxlet_vs_bff_consensuscall_all_assignment_after_match.csv:md5,de2093a5ff281af46d45d7d4770338cf", "test2_freemuxlet_vs_bff_consensuscall_concordance_heatmap.png:md5,5c3e82573df24e781d4f422bc3b6925a", "test2_freemuxlet_vs_bff_consensuscall_correlation_res.csv:md5,5f2f2076175e313f10396b32fac4c26c", "test2_freemuxlet_vs_bff_consensuscall_donor_match.csv:md5,b92352374bf4b32f7e3a1b032a4c8b5f", - "test2_freemuxlet_vs_bff_consensuscall_intersect_assignment_after_match.csv:md5,3f611c4fb970117f9771ce05f4ec4d8d", - "test2_freemuxlet_vs_bff_raw_all_assignment_after_match.csv:md5,54406a297be20405919bd15348111ad8", + "test2_freemuxlet_vs_bff_consensuscall_intersect_assignment_after_match.csv:md5,a4df295c2689a2b3abfab4476e5a1dcb", + "test2_freemuxlet_vs_bff_raw_all_assignment_after_match.csv:md5,8c42806ae93d0bd0fc79864db6a79463", "test2_freemuxlet_vs_bff_raw_concordance_heatmap.png:md5,5c3e82573df24e781d4f422bc3b6925a", "test2_freemuxlet_vs_bff_raw_correlation_res.csv:md5,5f2f2076175e313f10396b32fac4c26c", "test2_freemuxlet_vs_bff_raw_donor_match.csv:md5,b92352374bf4b32f7e3a1b032a4c8b5f", - "test2_freemuxlet_vs_bff_raw_intersect_assignment_after_match.csv:md5,87b3ca9e977fe3df3536ba169566abed", - "test2_freemuxlet_vs_gmmdemux_all_assignment_after_match.csv:md5,e4ec8cd25409bdb7de568dcfd8ca3ad4", + "test2_freemuxlet_vs_bff_raw_intersect_assignment_after_match.csv:md5,6af3427166aab49c0136369311533d83", + "test2_freemuxlet_vs_demuxem_all_assignment_after_match.csv:md5,505b79ce4db1cef804bfeafb12f70a5d", + "test2_freemuxlet_vs_demuxem_concordance_heatmap.png:md5,360bc756be590ccb80d2add912d61e16", + "test2_freemuxlet_vs_demuxem_correlation_res.csv:md5,c880595fe0a83d46f1bcbb361c994828", + "test2_freemuxlet_vs_demuxem_donor_match.csv:md5,b92352374bf4b32f7e3a1b032a4c8b5f", + "test2_freemuxlet_vs_demuxem_intersect_assignment_after_match.csv:md5,edf378d87391a486b6635713e04fdbd9", + "test2_freemuxlet_vs_gmmdemux_all_assignment_after_match.csv:md5,473d87d9a65d8562e83d21067cfce536", "test2_freemuxlet_vs_gmmdemux_concordance_heatmap.png:md5,5c3e82573df24e781d4f422bc3b6925a", "test2_freemuxlet_vs_gmmdemux_correlation_res.csv:md5,5f2f2076175e313f10396b32fac4c26c", "test2_freemuxlet_vs_gmmdemux_donor_match.csv:md5,b92352374bf4b32f7e3a1b032a4c8b5f", - "test2_freemuxlet_vs_gmmdemux_intersect_assignment_after_match.csv:md5,f9092a34b0a2eef31daf452d89412f58", - "test2_freemuxlet_vs_hasheddrops_all_assignment_after_match.csv:md5,d777c2ffc8201579aa80123d42ca4c7f", + "test2_freemuxlet_vs_gmmdemux_intersect_assignment_after_match.csv:md5,d2f63d5a37e4a085fdd0ee97bfffe4f4", + "test2_freemuxlet_vs_hasheddrops_all_assignment_after_match.csv:md5,fb66dca65ba0ddaec50b78f4be1a662a", "test2_freemuxlet_vs_hasheddrops_concordance_heatmap.png:md5,5c3e82573df24e781d4f422bc3b6925a", "test2_freemuxlet_vs_hasheddrops_correlation_res.csv:md5,5f2f2076175e313f10396b32fac4c26c", "test2_freemuxlet_vs_hasheddrops_donor_match.csv:md5,b92352374bf4b32f7e3a1b032a4c8b5f", - "test2_freemuxlet_vs_hasheddrops_intersect_assignment_after_match.csv:md5,b3acbe48f60fc1d68dd8198950549812", - "test2_souporcell_vs_bff_consensuscall_all_assignment_after_match.csv:md5,4c61d15e4693b6bc03cd296e757b6498", + "test2_freemuxlet_vs_hasheddrops_intersect_assignment_after_match.csv:md5,b6e3daca5b12b963239eb9ff86ef7244", + "test2_freemuxlet_vs_hashsolo_all_assignment_after_match.csv:md5,8dbc642b118d82fe0dbb24631126d93b", + "test2_freemuxlet_vs_hashsolo_concordance_heatmap.png:md5,a6dd712948a85cf2f1660b4f6a07a5cb", + "test2_freemuxlet_vs_hashsolo_correlation_res.csv:md5,f6d61938702d82cca7a5ecbe4871a3fa", + "test2_freemuxlet_vs_hashsolo_donor_match.csv:md5,b92352374bf4b32f7e3a1b032a4c8b5f", + "test2_freemuxlet_vs_hashsolo_intersect_assignment_after_match.csv:md5,69a6c9f528d16e72d1a16927d65317fa", + "test2_freemuxlet_vs_htodemux_all_assignment_after_match.csv:md5,c9413355e0d822c0436fecf1706d0cfc", + "test2_freemuxlet_vs_htodemux_concordance_heatmap.png:md5,42ab19f0e287585a39d52817f85943f0", + "test2_freemuxlet_vs_htodemux_correlation_res.csv:md5,56e1f17bb5e0424e4840b2d89fe892d7", + "test2_freemuxlet_vs_htodemux_donor_match.csv:md5,b92352374bf4b32f7e3a1b032a4c8b5f", + "test2_freemuxlet_vs_htodemux_intersect_assignment_after_match.csv:md5,32cee037b091f9b1ca78ed88e11c75d4", + "test2_freemuxlet_vs_multiseq_all_assignment_after_match.csv:md5,cb8a579a6233cf227e4eb987fed88dc0", + "test2_freemuxlet_vs_multiseq_concordance_heatmap.png:md5,64bc44fb9aeeb1581ebbf708c8765b4d", + "test2_freemuxlet_vs_multiseq_correlation_res.csv:md5,8a690fde26636e0e4c44b41a156ae47a", + "test2_freemuxlet_vs_multiseq_donor_match.csv:md5,b92352374bf4b32f7e3a1b032a4c8b5f", + "test2_freemuxlet_vs_multiseq_intersect_assignment_after_match.csv:md5,bd1135d6b159a17c245742d6c4312219", + "test2_souporcell_vs_bff_consensuscall_all_assignment_after_match.csv:md5,0523d43db3b0b43d3c06642887b1e681", "test2_souporcell_vs_bff_consensuscall_concordance_heatmap.png:md5,d48df9ebb22dc1f2616511ad0405cdfd", "test2_souporcell_vs_bff_consensuscall_correlation_res.csv:md5,7e632ed518a1e6f02ed1ea25a28a4e45", "test2_souporcell_vs_bff_consensuscall_donor_match.csv:md5,d1feea9baad73b5bc106472a226cbbf2", - "test2_souporcell_vs_bff_consensuscall_intersect_assignment_after_match.csv:md5,17fe8deedeb704f60632733c9ec971ea", - "test2_souporcell_vs_bff_raw_all_assignment_after_match.csv:md5,f8e44703900103c804694041924df32a", + "test2_souporcell_vs_bff_consensuscall_intersect_assignment_after_match.csv:md5,d0069819b2b9722cc72ee2fae18e24d7", + "test2_souporcell_vs_bff_raw_all_assignment_after_match.csv:md5,3693453872310717f649a8da3538a8b2", "test2_souporcell_vs_bff_raw_concordance_heatmap.png:md5,d48df9ebb22dc1f2616511ad0405cdfd", "test2_souporcell_vs_bff_raw_correlation_res.csv:md5,7e632ed518a1e6f02ed1ea25a28a4e45", "test2_souporcell_vs_bff_raw_donor_match.csv:md5,d1feea9baad73b5bc106472a226cbbf2", - "test2_souporcell_vs_bff_raw_intersect_assignment_after_match.csv:md5,cc89c4263e95c7802152d5a84e5fda5a", - "test2_souporcell_vs_gmmdemux_all_assignment_after_match.csv:md5,5a9927b30847c3c3ff82f0cc5274076f", + "test2_souporcell_vs_bff_raw_intersect_assignment_after_match.csv:md5,9618d277186327f7092b4a838329c15c", + "test2_souporcell_vs_demuxem_all_assignment_after_match.csv:md5,02807a959d832624e2869d6c885a3676", + "test2_souporcell_vs_demuxem_concordance_heatmap.png:md5,8de7171a8fc0ec2f38e6f45e6c28cb03", + "test2_souporcell_vs_demuxem_correlation_res.csv:md5,24eca3fc8a0241e70b02055107683b91", + "test2_souporcell_vs_demuxem_donor_match.csv:md5,d1feea9baad73b5bc106472a226cbbf2", + "test2_souporcell_vs_demuxem_intersect_assignment_after_match.csv:md5,14280a2e649c89a8eb27f9c36798bc97", + "test2_souporcell_vs_gmmdemux_all_assignment_after_match.csv:md5,e163bc80fc846e43cca5d6281dc2bbc8", "test2_souporcell_vs_gmmdemux_concordance_heatmap.png:md5,d48df9ebb22dc1f2616511ad0405cdfd", "test2_souporcell_vs_gmmdemux_correlation_res.csv:md5,7e632ed518a1e6f02ed1ea25a28a4e45", "test2_souporcell_vs_gmmdemux_donor_match.csv:md5,d1feea9baad73b5bc106472a226cbbf2", - "test2_souporcell_vs_gmmdemux_intersect_assignment_after_match.csv:md5,d9cc57a14177d9c362b91dc923c123c8", - "test2_souporcell_vs_hasheddrops_all_assignment_after_match.csv:md5,8e5f886ef04c7d224861348ae470adc5", + "test2_souporcell_vs_gmmdemux_intersect_assignment_after_match.csv:md5,dd7403edeb5ba0e17da879a3ff310aba", + "test2_souporcell_vs_hasheddrops_all_assignment_after_match.csv:md5,2177729941ec7e0ec3e30490367ac648", "test2_souporcell_vs_hasheddrops_concordance_heatmap.png:md5,d48df9ebb22dc1f2616511ad0405cdfd", "test2_souporcell_vs_hasheddrops_correlation_res.csv:md5,7e632ed518a1e6f02ed1ea25a28a4e45", "test2_souporcell_vs_hasheddrops_donor_match.csv:md5,d1feea9baad73b5bc106472a226cbbf2", - "test2_souporcell_vs_hasheddrops_intersect_assignment_after_match.csv:md5,4d4e53deb9c6a9dd9ae9d6d89493d736", - "test2_best_all_assignment_after_match.csv:md5,4c61d15e4693b6bc03cd296e757b6498", + "test2_souporcell_vs_hasheddrops_intersect_assignment_after_match.csv:md5,595efa559856d1dfc7bae9d109478221", + "test2_souporcell_vs_hashsolo_all_assignment_after_match.csv:md5,89a1b529a2f3805193a3547771df15bc", + "test2_souporcell_vs_hashsolo_concordance_heatmap.png:md5,aafe59185e6eef45a25585a8c06505e3", + "test2_souporcell_vs_hashsolo_correlation_res.csv:md5,bdcc36b8fcabb311fa40d3d48e2b3080", + "test2_souporcell_vs_hashsolo_donor_match.csv:md5,d1feea9baad73b5bc106472a226cbbf2", + "test2_souporcell_vs_hashsolo_intersect_assignment_after_match.csv:md5,b9e34df8e9037c17fce729b661480b7f", + "test2_souporcell_vs_htodemux_all_assignment_after_match.csv:md5,986fb63a696867e670b14a7d3c299eeb", + "test2_souporcell_vs_htodemux_concordance_heatmap.png:md5,d48df9ebb22dc1f2616511ad0405cdfd", + "test2_souporcell_vs_htodemux_correlation_res.csv:md5,7e632ed518a1e6f02ed1ea25a28a4e45", + "test2_souporcell_vs_htodemux_donor_match.csv:md5,d1feea9baad73b5bc106472a226cbbf2", + "test2_souporcell_vs_htodemux_intersect_assignment_after_match.csv:md5,f9ac7bf74aea62b5cb9bd34f5fe002b2", + "test2_souporcell_vs_multiseq_all_assignment_after_match.csv:md5,71a33f1f8070410b7c016bb7234a89e2", + "test2_souporcell_vs_multiseq_concordance_heatmap.png:md5,987ae361340b8b77daecc726ffa6b9de", + "test2_souporcell_vs_multiseq_correlation_res.csv:md5,a5f9d7ebd675403b062cd0f4a59b1592", + "test2_souporcell_vs_multiseq_donor_match.csv:md5,d1feea9baad73b5bc106472a226cbbf2", + "test2_souporcell_vs_multiseq_intersect_assignment_after_match.csv:md5,a904d4236db50ef2d35fa4e0d608284f", + "test2_best_all_assignment_after_match.csv:md5,0523d43db3b0b43d3c06642887b1e681", "test2_best_donor_match.csv:md5,d1feea9baad73b5bc106472a226cbbf2", - "test2_best_intersect_assignment_after_match.csv:md5,17fe8deedeb704f60632733c9ec971ea", - "test2_score_record.csv:md5,19e6d7aa257ab96338c70bb1e69442b3", - "test3_freemuxlet_vs_bff_consensuscall_all_assignment_after_match.csv:md5,e6c7237f7a927ab86368d9ecf28b807c", + "test2_best_intersect_assignment_after_match.csv:md5,d0069819b2b9722cc72ee2fae18e24d7", + "test2_score_record.csv:md5,f85dc45d1e25a47aa1ce5df0f3652963", + "test3_freemuxlet_vs_bff_consensuscall_all_assignment_after_match.csv:md5,de2093a5ff281af46d45d7d4770338cf", "test3_freemuxlet_vs_bff_consensuscall_concordance_heatmap.png:md5,5c3e82573df24e781d4f422bc3b6925a", "test3_freemuxlet_vs_bff_consensuscall_correlation_res.csv:md5,5f2f2076175e313f10396b32fac4c26c", "test3_freemuxlet_vs_bff_consensuscall_donor_match.csv:md5,b92352374bf4b32f7e3a1b032a4c8b5f", - "test3_freemuxlet_vs_bff_consensuscall_intersect_assignment_after_match.csv:md5,3f611c4fb970117f9771ce05f4ec4d8d", - "test3_freemuxlet_vs_bff_raw_all_assignment_after_match.csv:md5,54406a297be20405919bd15348111ad8", + "test3_freemuxlet_vs_bff_consensuscall_intersect_assignment_after_match.csv:md5,a4df295c2689a2b3abfab4476e5a1dcb", + "test3_freemuxlet_vs_bff_raw_all_assignment_after_match.csv:md5,8c42806ae93d0bd0fc79864db6a79463", "test3_freemuxlet_vs_bff_raw_concordance_heatmap.png:md5,5c3e82573df24e781d4f422bc3b6925a", "test3_freemuxlet_vs_bff_raw_correlation_res.csv:md5,5f2f2076175e313f10396b32fac4c26c", "test3_freemuxlet_vs_bff_raw_donor_match.csv:md5,b92352374bf4b32f7e3a1b032a4c8b5f", - "test3_freemuxlet_vs_bff_raw_intersect_assignment_after_match.csv:md5,87b3ca9e977fe3df3536ba169566abed", - "test3_freemuxlet_vs_gmmdemux_all_assignment_after_match.csv:md5,e4ec8cd25409bdb7de568dcfd8ca3ad4", + "test3_freemuxlet_vs_bff_raw_intersect_assignment_after_match.csv:md5,6af3427166aab49c0136369311533d83", + "test3_freemuxlet_vs_demuxem_all_assignment_after_match.csv:md5,505b79ce4db1cef804bfeafb12f70a5d", + "test3_freemuxlet_vs_demuxem_concordance_heatmap.png:md5,360bc756be590ccb80d2add912d61e16", + "test3_freemuxlet_vs_demuxem_correlation_res.csv:md5,c880595fe0a83d46f1bcbb361c994828", + "test3_freemuxlet_vs_demuxem_donor_match.csv:md5,b92352374bf4b32f7e3a1b032a4c8b5f", + "test3_freemuxlet_vs_demuxem_intersect_assignment_after_match.csv:md5,edf378d87391a486b6635713e04fdbd9", + "test3_freemuxlet_vs_gmmdemux_all_assignment_after_match.csv:md5,473d87d9a65d8562e83d21067cfce536", "test3_freemuxlet_vs_gmmdemux_concordance_heatmap.png:md5,5c3e82573df24e781d4f422bc3b6925a", "test3_freemuxlet_vs_gmmdemux_correlation_res.csv:md5,5f2f2076175e313f10396b32fac4c26c", "test3_freemuxlet_vs_gmmdemux_donor_match.csv:md5,b92352374bf4b32f7e3a1b032a4c8b5f", - "test3_freemuxlet_vs_gmmdemux_intersect_assignment_after_match.csv:md5,f9092a34b0a2eef31daf452d89412f58", - "test3_freemuxlet_vs_hasheddrops_all_assignment_after_match.csv:md5,d777c2ffc8201579aa80123d42ca4c7f", + "test3_freemuxlet_vs_gmmdemux_intersect_assignment_after_match.csv:md5,d2f63d5a37e4a085fdd0ee97bfffe4f4", + "test3_freemuxlet_vs_hasheddrops_all_assignment_after_match.csv:md5,fb66dca65ba0ddaec50b78f4be1a662a", "test3_freemuxlet_vs_hasheddrops_concordance_heatmap.png:md5,5c3e82573df24e781d4f422bc3b6925a", "test3_freemuxlet_vs_hasheddrops_correlation_res.csv:md5,5f2f2076175e313f10396b32fac4c26c", "test3_freemuxlet_vs_hasheddrops_donor_match.csv:md5,b92352374bf4b32f7e3a1b032a4c8b5f", - "test3_freemuxlet_vs_hasheddrops_intersect_assignment_after_match.csv:md5,b3acbe48f60fc1d68dd8198950549812", - "test3_souporcell_vs_bff_consensuscall_all_assignment_after_match.csv:md5,4c61d15e4693b6bc03cd296e757b6498", + "test3_freemuxlet_vs_hasheddrops_intersect_assignment_after_match.csv:md5,b6e3daca5b12b963239eb9ff86ef7244", + "test3_freemuxlet_vs_hashsolo_all_assignment_after_match.csv:md5,8dbc642b118d82fe0dbb24631126d93b", + "test3_freemuxlet_vs_hashsolo_concordance_heatmap.png:md5,a6dd712948a85cf2f1660b4f6a07a5cb", + "test3_freemuxlet_vs_hashsolo_correlation_res.csv:md5,f6d61938702d82cca7a5ecbe4871a3fa", + "test3_freemuxlet_vs_hashsolo_donor_match.csv:md5,b92352374bf4b32f7e3a1b032a4c8b5f", + "test3_freemuxlet_vs_hashsolo_intersect_assignment_after_match.csv:md5,69a6c9f528d16e72d1a16927d65317fa", + "test3_freemuxlet_vs_htodemux_all_assignment_after_match.csv:md5,c9413355e0d822c0436fecf1706d0cfc", + "test3_freemuxlet_vs_htodemux_concordance_heatmap.png:md5,42ab19f0e287585a39d52817f85943f0", + "test3_freemuxlet_vs_htodemux_correlation_res.csv:md5,56e1f17bb5e0424e4840b2d89fe892d7", + "test3_freemuxlet_vs_htodemux_donor_match.csv:md5,b92352374bf4b32f7e3a1b032a4c8b5f", + "test3_freemuxlet_vs_htodemux_intersect_assignment_after_match.csv:md5,32cee037b091f9b1ca78ed88e11c75d4", + "test3_freemuxlet_vs_multiseq_all_assignment_after_match.csv:md5,cb8a579a6233cf227e4eb987fed88dc0", + "test3_freemuxlet_vs_multiseq_concordance_heatmap.png:md5,64bc44fb9aeeb1581ebbf708c8765b4d", + "test3_freemuxlet_vs_multiseq_correlation_res.csv:md5,8a690fde26636e0e4c44b41a156ae47a", + "test3_freemuxlet_vs_multiseq_donor_match.csv:md5,b92352374bf4b32f7e3a1b032a4c8b5f", + "test3_freemuxlet_vs_multiseq_intersect_assignment_after_match.csv:md5,bd1135d6b159a17c245742d6c4312219", + "test3_souporcell_vs_bff_consensuscall_all_assignment_after_match.csv:md5,0523d43db3b0b43d3c06642887b1e681", "test3_souporcell_vs_bff_consensuscall_concordance_heatmap.png:md5,d48df9ebb22dc1f2616511ad0405cdfd", "test3_souporcell_vs_bff_consensuscall_correlation_res.csv:md5,7e632ed518a1e6f02ed1ea25a28a4e45", "test3_souporcell_vs_bff_consensuscall_donor_match.csv:md5,d1feea9baad73b5bc106472a226cbbf2", - "test3_souporcell_vs_bff_consensuscall_intersect_assignment_after_match.csv:md5,17fe8deedeb704f60632733c9ec971ea", - "test3_souporcell_vs_bff_raw_all_assignment_after_match.csv:md5,f8e44703900103c804694041924df32a", + "test3_souporcell_vs_bff_consensuscall_intersect_assignment_after_match.csv:md5,d0069819b2b9722cc72ee2fae18e24d7", + "test3_souporcell_vs_bff_raw_all_assignment_after_match.csv:md5,3693453872310717f649a8da3538a8b2", "test3_souporcell_vs_bff_raw_concordance_heatmap.png:md5,d48df9ebb22dc1f2616511ad0405cdfd", "test3_souporcell_vs_bff_raw_correlation_res.csv:md5,7e632ed518a1e6f02ed1ea25a28a4e45", "test3_souporcell_vs_bff_raw_donor_match.csv:md5,d1feea9baad73b5bc106472a226cbbf2", - "test3_souporcell_vs_bff_raw_intersect_assignment_after_match.csv:md5,cc89c4263e95c7802152d5a84e5fda5a", - "test3_souporcell_vs_gmmdemux_all_assignment_after_match.csv:md5,5a9927b30847c3c3ff82f0cc5274076f", + "test3_souporcell_vs_bff_raw_intersect_assignment_after_match.csv:md5,9618d277186327f7092b4a838329c15c", + "test3_souporcell_vs_demuxem_all_assignment_after_match.csv:md5,02807a959d832624e2869d6c885a3676", + "test3_souporcell_vs_demuxem_concordance_heatmap.png:md5,8de7171a8fc0ec2f38e6f45e6c28cb03", + "test3_souporcell_vs_demuxem_correlation_res.csv:md5,24eca3fc8a0241e70b02055107683b91", + "test3_souporcell_vs_demuxem_donor_match.csv:md5,d1feea9baad73b5bc106472a226cbbf2", + "test3_souporcell_vs_demuxem_intersect_assignment_after_match.csv:md5,14280a2e649c89a8eb27f9c36798bc97", + "test3_souporcell_vs_gmmdemux_all_assignment_after_match.csv:md5,e163bc80fc846e43cca5d6281dc2bbc8", "test3_souporcell_vs_gmmdemux_concordance_heatmap.png:md5,d48df9ebb22dc1f2616511ad0405cdfd", "test3_souporcell_vs_gmmdemux_correlation_res.csv:md5,7e632ed518a1e6f02ed1ea25a28a4e45", "test3_souporcell_vs_gmmdemux_donor_match.csv:md5,d1feea9baad73b5bc106472a226cbbf2", - "test3_souporcell_vs_gmmdemux_intersect_assignment_after_match.csv:md5,d9cc57a14177d9c362b91dc923c123c8", - "test3_souporcell_vs_hasheddrops_all_assignment_after_match.csv:md5,8e5f886ef04c7d224861348ae470adc5", + "test3_souporcell_vs_gmmdemux_intersect_assignment_after_match.csv:md5,dd7403edeb5ba0e17da879a3ff310aba", + "test3_souporcell_vs_hasheddrops_all_assignment_after_match.csv:md5,2177729941ec7e0ec3e30490367ac648", "test3_souporcell_vs_hasheddrops_concordance_heatmap.png:md5,d48df9ebb22dc1f2616511ad0405cdfd", "test3_souporcell_vs_hasheddrops_correlation_res.csv:md5,7e632ed518a1e6f02ed1ea25a28a4e45", "test3_souporcell_vs_hasheddrops_donor_match.csv:md5,d1feea9baad73b5bc106472a226cbbf2", - "test3_souporcell_vs_hasheddrops_intersect_assignment_after_match.csv:md5,4d4e53deb9c6a9dd9ae9d6d89493d736", - "test3_best_all_assignment_after_match.csv:md5,4c61d15e4693b6bc03cd296e757b6498", + "test3_souporcell_vs_hasheddrops_intersect_assignment_after_match.csv:md5,595efa559856d1dfc7bae9d109478221", + "test3_souporcell_vs_hashsolo_all_assignment_after_match.csv:md5,89a1b529a2f3805193a3547771df15bc", + "test3_souporcell_vs_hashsolo_concordance_heatmap.png:md5,aafe59185e6eef45a25585a8c06505e3", + "test3_souporcell_vs_hashsolo_correlation_res.csv:md5,bdcc36b8fcabb311fa40d3d48e2b3080", + "test3_souporcell_vs_hashsolo_donor_match.csv:md5,d1feea9baad73b5bc106472a226cbbf2", + "test3_souporcell_vs_hashsolo_intersect_assignment_after_match.csv:md5,b9e34df8e9037c17fce729b661480b7f", + "test3_souporcell_vs_htodemux_all_assignment_after_match.csv:md5,986fb63a696867e670b14a7d3c299eeb", + "test3_souporcell_vs_htodemux_concordance_heatmap.png:md5,d48df9ebb22dc1f2616511ad0405cdfd", + "test3_souporcell_vs_htodemux_correlation_res.csv:md5,7e632ed518a1e6f02ed1ea25a28a4e45", + "test3_souporcell_vs_htodemux_donor_match.csv:md5,d1feea9baad73b5bc106472a226cbbf2", + "test3_souporcell_vs_htodemux_intersect_assignment_after_match.csv:md5,f9ac7bf74aea62b5cb9bd34f5fe002b2", + "test3_souporcell_vs_multiseq_all_assignment_after_match.csv:md5,71a33f1f8070410b7c016bb7234a89e2", + "test3_souporcell_vs_multiseq_concordance_heatmap.png:md5,987ae361340b8b77daecc726ffa6b9de", + "test3_souporcell_vs_multiseq_correlation_res.csv:md5,a5f9d7ebd675403b062cd0f4a59b1592", + "test3_souporcell_vs_multiseq_donor_match.csv:md5,d1feea9baad73b5bc106472a226cbbf2", + "test3_souporcell_vs_multiseq_intersect_assignment_after_match.csv:md5,a904d4236db50ef2d35fa4e0d608284f", + "test3_best_all_assignment_after_match.csv:md5,0523d43db3b0b43d3c06642887b1e681", "test3_best_donor_match.csv:md5,d1feea9baad73b5bc106472a226cbbf2", - "test3_best_intersect_assignment_after_match.csv:md5,17fe8deedeb704f60632733c9ec971ea", - "test3_score_record.csv:md5,19e6d7aa257ab96338c70bb1e69442b3", + "test3_best_intersect_assignment_after_match.csv:md5,d0069819b2b9722cc72ee2fae18e24d7", + "test3_score_record.csv:md5,f85dc45d1e25a47aa1ce5df0f3652963", "test1_hto-1_informative_variants.csv:md5,7ba6e70ac79adf74d2aa85f723b9f0d6", "test1_hto-1_matched_gt.csv:md5,bf3b7d684f30153fa603dbddde635583", - "test1_hto-1_unmatched_gt.csv:md5,8e52f5c81f4c467229a68b56390f7948", + "test1_hto-1_unmatched_gt.csv:md5,ed81dd2259b6f2eaf1c2e4b50988cf8a", "test1_hto-2_informative_variants.csv:md5,f641c48375833e592aa6f8c102ac1160", "test1_hto-2_matched_gt.csv:md5,fc8bdfc73587eaf63925f658b6fb1c5c", - "test1_hto-2_unmatched_gt.csv:md5,c3185e489df198d888b70e1d065d8265", + "test1_hto-2_unmatched_gt.csv:md5,56872af37baffd1e4689c20f92e4d1c0", "test1_all_representative_variants.csv:md5,93cf3fde5b1236b83abd53369b250f64", "test1_donor_specific_variants.csv:md5,bd137190cb87b1ad68a7283571e9a4b6", "test1_donor_specific_variants_upset.png:md5,5d0abe01aa07abd018d20abe8e2e99ab", "test1_vireo_variants.csv:md5,34bf574b4fe7c16db53a86f5cf6cdbf3", "test2_hto-1_informative_variants.csv:md5,7ba6e70ac79adf74d2aa85f723b9f0d6", "test2_hto-1_matched_gt.csv:md5,bf3b7d684f30153fa603dbddde635583", - "test2_hto-1_unmatched_gt.csv:md5,8e52f5c81f4c467229a68b56390f7948", + "test2_hto-1_unmatched_gt.csv:md5,ed81dd2259b6f2eaf1c2e4b50988cf8a", "test2_hto-2_informative_variants.csv:md5,f641c48375833e592aa6f8c102ac1160", "test2_hto-2_matched_gt.csv:md5,fc8bdfc73587eaf63925f658b6fb1c5c", - "test2_hto-2_unmatched_gt.csv:md5,c3185e489df198d888b70e1d065d8265", + "test2_hto-2_unmatched_gt.csv:md5,56872af37baffd1e4689c20f92e4d1c0", "test2_all_representative_variants.csv:md5,93cf3fde5b1236b83abd53369b250f64", "test2_donor_specific_variants.csv:md5,bd137190cb87b1ad68a7283571e9a4b6", "test2_donor_specific_variants_upset.png:md5,5d0abe01aa07abd018d20abe8e2e99ab", "test2_vireo_variants.csv:md5,34bf574b4fe7c16db53a86f5cf6cdbf3", "test3_hto-1_informative_variants.csv:md5,7ba6e70ac79adf74d2aa85f723b9f0d6", "test3_hto-1_matched_gt.csv:md5,bf3b7d684f30153fa603dbddde635583", - "test3_hto-1_unmatched_gt.csv:md5,8e52f5c81f4c467229a68b56390f7948", + "test3_hto-1_unmatched_gt.csv:md5,ed81dd2259b6f2eaf1c2e4b50988cf8a", "test3_hto-2_informative_variants.csv:md5,f641c48375833e592aa6f8c102ac1160", "test3_hto-2_matched_gt.csv:md5,fc8bdfc73587eaf63925f658b6fb1c5c", - "test3_hto-2_unmatched_gt.csv:md5,c3185e489df198d888b70e1d065d8265", + "test3_hto-2_unmatched_gt.csv:md5,56872af37baffd1e4689c20f92e4d1c0", "test3_all_representative_variants.csv:md5,93cf3fde5b1236b83abd53369b250f64", "test3_donor_specific_variants.csv:md5,bd137190cb87b1ad68a7283571e9a4b6", "test3_donor_specific_variants_upset.png:md5,5d0abe01aa07abd018d20abe8e2e99ab", "test3_vireo_variants.csv:md5,34bf574b4fe7c16db53a86f5cf6cdbf3", - "test1.clust1.samples.gz:md5,42f8c8ba281fccf6edaf6dbd30144210", - "test1.lmix:md5,d8be4c936ff56980a476d5ba82290b8e", - "test2.clust1.samples.gz:md5,42f8c8ba281fccf6edaf6dbd30144210", - "test2.lmix:md5,d8be4c936ff56980a476d5ba82290b8e", - "test3.clust1.samples.gz:md5,42f8c8ba281fccf6edaf6dbd30144210", - "test3.lmix:md5,d8be4c936ff56980a476d5ba82290b8e", - "clusters.tsv:md5,3443284fb0e0ace31b5da0f329568392", - "clusters.tsv:md5,3443284fb0e0ace31b5da0f329568392", - "clusters.tsv:md5,3443284fb0e0ace31b5da0f329568392", - "test1_genetic_overview_assignment.csv:md5,961503e69942d87a7887c4d3b52fd6a0", - "test1_genetic_overview_classification.csv:md5,1cbe450ce379ae815fffc44f230cfc63", - "test1_genetic_summary_assignment.csv:md5,e4c35f97306c75bf69d88cf6c39751a6", - "test1_genetic_summary_classification.csv:md5,615fa91b5f67289aa09c6116484d9bf0", - "test2_genetic_overview_assignment.csv:md5,961503e69942d87a7887c4d3b52fd6a0", - "test2_genetic_overview_classification.csv:md5,1cbe450ce379ae815fffc44f230cfc63", - "test2_genetic_summary_assignment.csv:md5,e4c35f97306c75bf69d88cf6c39751a6", - "test2_genetic_summary_classification.csv:md5,615fa91b5f67289aa09c6116484d9bf0", - "test3_genetic_overview_assignment.csv:md5,961503e69942d87a7887c4d3b52fd6a0", - "test3_genetic_overview_classification.csv:md5,1cbe450ce379ae815fffc44f230cfc63", - "test3_genetic_summary_assignment.csv:md5,e4c35f97306c75bf69d88cf6c39751a6", - "test3_genetic_summary_classification.csv:md5,615fa91b5f67289aa09c6116484d9bf0", + "test1.best:md5,237dadb1f53067fc7e3aad4336c6541b", + "test2.best:md5,237dadb1f53067fc7e3aad4336c6541b", + "test3.best:md5,237dadb1f53067fc7e3aad4336c6541b", + "test1.clust1.samples.gz:md5,d58f19c489213685176f03982dd8ea1e", + "test1.lmix:md5,39b16209e7c2238020024c5611ef10c5", + "test2.clust1.samples.gz:md5,d58f19c489213685176f03982dd8ea1e", + "test2.lmix:md5,39b16209e7c2238020024c5611ef10c5", + "test3.clust1.samples.gz:md5,d58f19c489213685176f03982dd8ea1e", + "test3.lmix:md5,39b16209e7c2238020024c5611ef10c5", + "clusters.tsv:md5,33ae22fc6e9cbb12b2d4a1d48b3da2e1", + "clusters.tsv:md5,33ae22fc6e9cbb12b2d4a1d48b3da2e1", + "clusters.tsv:md5,33ae22fc6e9cbb12b2d4a1d48b3da2e1", + "test1_genetic_overview_assignment.csv:md5,6e912c78c50b84deffdaf9fb5e8e3b20", + "test1_genetic_overview_classification.csv:md5,812a950b1eef4515079b2d7028b6658c", + "test1_genetic_summary_assignment.csv:md5,d99032c058450687415d441e474cef25", + "test1_genetic_summary_classification.csv:md5,fdfbf54c3c34746624fa6afdaead2f50", + "test2_genetic_overview_assignment.csv:md5,6e912c78c50b84deffdaf9fb5e8e3b20", + "test2_genetic_overview_classification.csv:md5,812a950b1eef4515079b2d7028b6658c", + "test2_genetic_summary_assignment.csv:md5,d99032c058450687415d441e474cef25", + "test2_genetic_summary_classification.csv:md5,fdfbf54c3c34746624fa6afdaead2f50", + "test3_genetic_overview_assignment.csv:md5,6e912c78c50b84deffdaf9fb5e8e3b20", + "test3_genetic_overview_classification.csv:md5,812a950b1eef4515079b2d7028b6658c", + "test3_genetic_summary_assignment.csv:md5,d99032c058450687415d441e474cef25", + "test3_genetic_summary_classification.csv:md5,fdfbf54c3c34746624fa6afdaead2f50", "test1.base.vcf.gz:md5,36eccb4bd3e2130f395985e09a66a60d", - "test1.cells.vcf.gz:md5,b28d8acaf17777c7cf9e2c7d01d93a66", - "test1.samples.tsv:md5,9e488782c1bcd63c37ee3d1c4c0a9217", + "test1.cells.vcf.gz:md5,4428343f7d9e3e310c14cfab8b9a57f1", + "test1.samples.tsv:md5,4ee35dd786d28bfad433c5c2b2a00e7a", "test1.tag.AD.mtx:md5,a76d9f63c6abdcab00f583fac269a98e", "test1.tag.DP.mtx:md5,6f8e54bbaa8ad9dcaa42c15eff1e1351", "test1.tag.OTH.mtx:md5,45e64ea3b3b8e8a7126690d9e775d501", "test1_GT_donors.vireo.vcf.gz:md5,60bfaaf61b8e7f5fc2bd0950e576c3e5", - "test1_donor_ids.tsv:md5,5dd2633a15d99a044d7f6706912ed5bc", + "test1_donor_ids.tsv:md5,a9ea6bc5851ecaff5a11abf54c5289cc", "test1_filtered_variants.tsv:md5,d37231135d9e6e330c03ed0647fcaf0e", - "test1_prob_doublet.tsv.gz:md5,3dae1d20888be460d02782eb776043bb", - "test1_prob_singlet.tsv.gz:md5,90a717e3bdb50090c72fa73a18330e38", + "test1_prob_doublet.tsv.gz:md5,68b4ec74b77aa58421d73df2dbc92e34", + "test1_prob_singlet.tsv.gz:md5,b22717700bc3f7e846f6f831466f0dfd", "test1_summary.tsv:md5,94b0ce40cc86bbe7fbacebf98e685ee7", "test2.base.vcf.gz:md5,36eccb4bd3e2130f395985e09a66a60d", - "test2.cells.vcf.gz:md5,b28d8acaf17777c7cf9e2c7d01d93a66", - "test2.samples.tsv:md5,9e488782c1bcd63c37ee3d1c4c0a9217", + "test2.cells.vcf.gz:md5,4428343f7d9e3e310c14cfab8b9a57f1", + "test2.samples.tsv:md5,4ee35dd786d28bfad433c5c2b2a00e7a", "test2.tag.AD.mtx:md5,a76d9f63c6abdcab00f583fac269a98e", "test2.tag.DP.mtx:md5,6f8e54bbaa8ad9dcaa42c15eff1e1351", "test2.tag.OTH.mtx:md5,45e64ea3b3b8e8a7126690d9e775d501", "test2_GT_donors.vireo.vcf.gz:md5,60bfaaf61b8e7f5fc2bd0950e576c3e5", - "test2_donor_ids.tsv:md5,5dd2633a15d99a044d7f6706912ed5bc", + "test2_donor_ids.tsv:md5,a9ea6bc5851ecaff5a11abf54c5289cc", "test2_filtered_variants.tsv:md5,d37231135d9e6e330c03ed0647fcaf0e", - "test2_prob_doublet.tsv.gz:md5,3dae1d20888be460d02782eb776043bb", - "test2_prob_singlet.tsv.gz:md5,90a717e3bdb50090c72fa73a18330e38", + "test2_prob_doublet.tsv.gz:md5,68b4ec74b77aa58421d73df2dbc92e34", + "test2_prob_singlet.tsv.gz:md5,b22717700bc3f7e846f6f831466f0dfd", "test2_summary.tsv:md5,94b0ce40cc86bbe7fbacebf98e685ee7", "test3.base.vcf.gz:md5,36eccb4bd3e2130f395985e09a66a60d", - "test3.cells.vcf.gz:md5,b28d8acaf17777c7cf9e2c7d01d93a66", - "test3.samples.tsv:md5,9e488782c1bcd63c37ee3d1c4c0a9217", + "test3.cells.vcf.gz:md5,4428343f7d9e3e310c14cfab8b9a57f1", + "test3.samples.tsv:md5,4ee35dd786d28bfad433c5c2b2a00e7a", "test3.tag.AD.mtx:md5,a76d9f63c6abdcab00f583fac269a98e", "test3.tag.DP.mtx:md5,6f8e54bbaa8ad9dcaa42c15eff1e1351", "test3.tag.OTH.mtx:md5,45e64ea3b3b8e8a7126690d9e775d501", "test3_GT_donors.vireo.vcf.gz:md5,60bfaaf61b8e7f5fc2bd0950e576c3e5", - "test3_donor_ids.tsv:md5,5dd2633a15d99a044d7f6706912ed5bc", + "test3_donor_ids.tsv:md5,a9ea6bc5851ecaff5a11abf54c5289cc", "test3_filtered_variants.tsv:md5,d37231135d9e6e330c03ed0647fcaf0e", - "test3_prob_doublet.tsv.gz:md5,3dae1d20888be460d02782eb776043bb", - "test3_prob_singlet.tsv.gz:md5,90a717e3bdb50090c72fa73a18330e38", + "test3_prob_doublet.tsv.gz:md5,68b4ec74b77aa58421d73df2dbc92e34", + "test3_prob_singlet.tsv.gz:md5,b22717700bc3f7e846f6f831466f0dfd", "test3_summary.tsv:md5,94b0ce40cc86bbe7fbacebf98e685ee7", - "test1_assignment_bff.csv:md5,82e9e12fec29f9eb0dcfbb1b6d8cc02a", + "test1_assignment_bff.csv:md5,a62d56a7a5b9b59765d2ae759773aca2", "test1_metrics_bff.csv:md5,1d7751cf035c9e736e4de5504ff87a6a", "test1_params_bff.csv:md5,dbb83e4e50779916ff4e368b2d05fe91", - "test2_assignment_bff.csv:md5,82e9e12fec29f9eb0dcfbb1b6d8cc02a", + "test2_assignment_bff.csv:md5,a62d56a7a5b9b59765d2ae759773aca2", "test2_metrics_bff.csv:md5,1d7751cf035c9e736e4de5504ff87a6a", "test2_params_bff.csv:md5,08758e268b309e4f3288069e3eab358a", - "test3_assignment_bff.csv:md5,82e9e12fec29f9eb0dcfbb1b6d8cc02a", + "test3_assignment_bff.csv:md5,a62d56a7a5b9b59765d2ae759773aca2", "test3_metrics_bff.csv:md5,1d7751cf035c9e736e4de5504ff87a6a", "test3_params_bff.csv:md5,882b80fd11a10a375e0abd5ae29f74b0", "GMM_full.config:md5,0a18f3d5729c4b6399ce4161609362b6", - "barcodes.tsv.gz:md5,e4cd9ce9929a07d6763304ec1bf64c23", + "barcodes.tsv.gz:md5,77a6cfcbb9cee3611b37d6309a8acaf7", "features.tsv.gz:md5,441a968b2c7ee68eae8388b04bee328d", "matrix.mtx.gz:md5,6f855b8bf68573d2e09139c29f9a2d41", "GMM_full.config:md5,0a18f3d5729c4b6399ce4161609362b6", - "barcodes.tsv.gz:md5,e4cd9ce9929a07d6763304ec1bf64c23", + "barcodes.tsv.gz:md5,77a6cfcbb9cee3611b37d6309a8acaf7", "features.tsv.gz:md5,441a968b2c7ee68eae8388b04bee328d", "matrix.mtx.gz:md5,6f855b8bf68573d2e09139c29f9a2d41", "GMM_full.config:md5,0a18f3d5729c4b6399ce4161609362b6", - "barcodes.tsv.gz:md5,e4cd9ce9929a07d6763304ec1bf64c23", + "barcodes.tsv.gz:md5,77a6cfcbb9cee3611b37d6309a8acaf7", "features.tsv.gz:md5,441a968b2c7ee68eae8388b04bee328d", "matrix.mtx.gz:md5,6f855b8bf68573d2e09139c29f9a2d41", "test1_emptyDrops.csv:md5,a227d42afbcc590b4e949075cde4a5b6", "test1_emptyDrops.png:md5,2d7113a76a494b2b2c18b77c4a664063", "test1_emptyDrops.rds:md5,bda2e88b1e3e8c8ea5a22e19e3ab243d", - "test1_hasheddrops.rds:md5,7bb71fa6f28e3fc97b20bbdeffbb81d2", + "test1_hasheddrops.rds:md5,53424c915d3926e46180be1408d28311", "test1_id_to_hash.csv:md5,e8f577103f3e0c17604f89c3bcda2ffb", "test1_params_hasheddrops.csv:md5,b253501914df23ef5901262e8a241a8d", "test1_plot_hasheddrops.png:md5,2d7113a76a494b2b2c18b77c4a664063", - "test1_results_hasheddrops.csv:md5,9614789e65fc843e6a11fa60686fe89a", + "test1_results_hasheddrops.csv:md5,230bcd615d3ac3d7ae8eb37ebe67f016", "test2_emptyDrops.csv:md5,a227d42afbcc590b4e949075cde4a5b6", "test2_emptyDrops.png:md5,2d7113a76a494b2b2c18b77c4a664063", "test2_emptyDrops.rds:md5,bda2e88b1e3e8c8ea5a22e19e3ab243d", - "test2_hasheddrops.rds:md5,7bb71fa6f28e3fc97b20bbdeffbb81d2", + "test2_hasheddrops.rds:md5,53424c915d3926e46180be1408d28311", "test2_id_to_hash.csv:md5,e8f577103f3e0c17604f89c3bcda2ffb", "test2_params_hasheddrops.csv:md5,3612264937e5c5962fefddc43afb595f", "test2_plot_hasheddrops.png:md5,2d7113a76a494b2b2c18b77c4a664063", - "test2_results_hasheddrops.csv:md5,9614789e65fc843e6a11fa60686fe89a", + "test2_results_hasheddrops.csv:md5,230bcd615d3ac3d7ae8eb37ebe67f016", "test3_emptyDrops.csv:md5,a227d42afbcc590b4e949075cde4a5b6", "test3_emptyDrops.png:md5,2d7113a76a494b2b2c18b77c4a664063", "test3_emptyDrops.rds:md5,bda2e88b1e3e8c8ea5a22e19e3ab243d", - "test3_hasheddrops.rds:md5,7bb71fa6f28e3fc97b20bbdeffbb81d2", + "test3_hasheddrops.rds:md5,53424c915d3926e46180be1408d28311", "test3_id_to_hash.csv:md5,e8f577103f3e0c17604f89c3bcda2ffb", "test3_params_hasheddrops.csv:md5,b15e94f887e3ada8f790ea18eb791d8b", "test3_plot_hasheddrops.png:md5,2d7113a76a494b2b2c18b77c4a664063", - "test3_results_hasheddrops.csv:md5,9614789e65fc843e6a11fa60686fe89a", - "test1_hashing_overview_assignment.csv:md5,8294c52a27689789c28a463429516b6d", - "test1_hashing_overview_classification.csv:md5,ea59642fe64b2f45954f4b088728380f", - "test1_hashing_summary_assignment.csv:md5,706c257beac2bae517d76731c5320989", - "test1_hashing_summary_classification.csv:md5,ab3df547c2e0319bd581c95432981c98", - "test2_hashing_overview_assignment.csv:md5,8294c52a27689789c28a463429516b6d", - "test2_hashing_overview_classification.csv:md5,ea59642fe64b2f45954f4b088728380f", - "test2_hashing_summary_assignment.csv:md5,706c257beac2bae517d76731c5320989", - "test2_hashing_summary_classification.csv:md5,ab3df547c2e0319bd581c95432981c98", - "test3_hashing_overview_assignment.csv:md5,8294c52a27689789c28a463429516b6d", - "test3_hashing_overview_classification.csv:md5,ea59642fe64b2f45954f4b088728380f", - "test3_hashing_summary_assignment.csv:md5,706c257beac2bae517d76731c5320989", - "test3_hashing_summary_classification.csv:md5,ab3df547c2e0319bd581c95432981c98", + "test3_results_hasheddrops.csv:md5,230bcd615d3ac3d7ae8eb37ebe67f016", + "test1_assignment_hashsolo.csv:md5,7763ee9e2ef20c353ab386f55f805b33", + "test1_hashsolo.h5ad:md5,41ba3e91669bb4d52a7eb214186d36ea", + "test1_params_hashsolo.csv:md5,e39b2354a531ff7c65a0d3d0ad500a81", + "test2_assignment_hashsolo.csv:md5,7763ee9e2ef20c353ab386f55f805b33", + "test2_hashsolo.h5ad:md5,41ba3e91669bb4d52a7eb214186d36ea", + "test2_params_hashsolo.csv:md5,581ecdaff2557e6402ae2acf90d4167f", + "test3_assignment_hashsolo.csv:md5,7763ee9e2ef20c353ab386f55f805b33", + "test3_hashsolo.h5ad:md5,41ba3e91669bb4d52a7eb214186d36ea", + "test3_params_hashsolo.csv:md5,4eac179a88b560899622d0de4cd5b079", + "test1_assignment_htodemux.csv:md5,7217cd9d30a3c6f7f69e8b4f8aaa679d", + "test1_classification_htodemux.csv:md5,e5b0d2af4de9bdc6682c120a78e53aa0", + "test1_params_htodemux.csv:md5,08b6c065b0c383e6be3c20787ec01d3e", + "test1_featureScatter_htodemux.jpeg:md5,c67268ac0ff18c485899b4ee58f8f948", + "test1_heatMap_htodemux.jpeg:md5,2ebdb027344db91ae89deaa3d1011993", + "test1_ridge_htodemux.jpeg:md5,8bbc209b475b437a80f853277daa4ce9", + "test1_tSNE_htodemux.jpeg:md5,470812a92d09b88ea6e33bcdadb13f11", + "test1_violinPlot_htodemux.jpeg:md5,ef866e13e18a31ab43370299245d33c7", + "test1_visual_params_htodemux.csv:md5,0a175b9868be2b4447867ab38705c11d", + "test2_assignment_htodemux.csv:md5,7217cd9d30a3c6f7f69e8b4f8aaa679d", + "test2_classification_htodemux.csv:md5,e5b0d2af4de9bdc6682c120a78e53aa0", + "test2_params_htodemux.csv:md5,484909d87cacd9c2b9cf60a3c853a5fd", + "test2_featureScatter_htodemux.jpeg:md5,c67268ac0ff18c485899b4ee58f8f948", + "test2_heatMap_htodemux.jpeg:md5,2ebdb027344db91ae89deaa3d1011993", + "test2_ridge_htodemux.jpeg:md5,8bbc209b475b437a80f853277daa4ce9", + "test2_tSNE_htodemux.jpeg:md5,470812a92d09b88ea6e33bcdadb13f11", + "test2_violinPlot_htodemux.jpeg:md5,ef866e13e18a31ab43370299245d33c7", + "test2_visual_params_htodemux.csv:md5,062e94620e3207810ddf2c65bf0d2914", + "test3_assignment_htodemux.csv:md5,7217cd9d30a3c6f7f69e8b4f8aaa679d", + "test3_classification_htodemux.csv:md5,e5b0d2af4de9bdc6682c120a78e53aa0", + "test3_params_htodemux.csv:md5,a0a3f900a632eea39057b038438a89b9", + "test3_featureScatter_htodemux.jpeg:md5,c67268ac0ff18c485899b4ee58f8f948", + "test3_heatMap_htodemux.jpeg:md5,2ebdb027344db91ae89deaa3d1011993", + "test3_ridge_htodemux.jpeg:md5,8bbc209b475b437a80f853277daa4ce9", + "test3_tSNE_htodemux.jpeg:md5,470812a92d09b88ea6e33bcdadb13f11", + "test3_violinPlot_htodemux.jpeg:md5,ef866e13e18a31ab43370299245d33c7", + "test3_visual_params_htodemux.csv:md5,b6f8f5593aeb86d846292f2f23fc518d", + "test1_params_multiseqdemux.csv:md5,60a096f79d78655870aef2af9130cf9d", + "test1_res_multiseqdemux.csv:md5,1173b7b90f66f0eb41d516a119ba52ee", + "test2_params_multiseqdemux.csv:md5,bb7b4009e6f709ebf35407b7849bfac3", + "test2_res_multiseqdemux.csv:md5,1173b7b90f66f0eb41d516a119ba52ee", + "test3_params_multiseqdemux.csv:md5,336308c45e52f92981173ecc7f295581", + "test3_res_multiseqdemux.csv:md5,1173b7b90f66f0eb41d516a119ba52ee", + "test1_hashing_overview_assignment.csv:md5,6fc8d4eed208650b52e9c5f426d995a1", + "test1_hashing_overview_classification.csv:md5,85f544d3dbf9377ca25af4f0720d9e9f", + "test1_hashing_summary_assignment.csv:md5,31a5bd36901a64e5908a38714b19f622", + "test1_hashing_summary_classification.csv:md5,c929da1c41e0adc0f3e120f27db00fb6", + "test2_hashing_overview_assignment.csv:md5,6fc8d4eed208650b52e9c5f426d995a1", + "test2_hashing_overview_classification.csv:md5,85f544d3dbf9377ca25af4f0720d9e9f", + "test2_hashing_summary_assignment.csv:md5,31a5bd36901a64e5908a38714b19f622", + "test2_hashing_summary_classification.csv:md5,c929da1c41e0adc0f3e120f27db00fb6", + "test3_hashing_overview_assignment.csv:md5,6fc8d4eed208650b52e9c5f426d995a1", + "test3_hashing_overview_classification.csv:md5,85f544d3dbf9377ca25af4f0720d9e9f", + "test3_hashing_summary_assignment.csv:md5,31a5bd36901a64e5908a38714b19f622", + "test3_hashing_summary_classification.csv:md5,c929da1c41e0adc0f3e120f27db00fb6", "multiqc_citations.txt:md5,4c806e63a283ec1b7e78cdae3a923d4f", - "test1_assignment.csv:md5,4d0eefc4298446469162230bc2752a44", - "test1_classification.csv:md5,793c660b1dbba5b1f5d6a87957df32fa", - "test1_genetic.h5ad:md5,c1cec22be88ca7440034dccdd1d21722", - "test1_genetic_and_hashing.h5mu:md5,bdef3ba1ff44ea3ed9cd8b4bdf0396f5", - "test1_hashing.h5ad:md5,9ad6217e86b5118e358ca5aaa5d4bff1", - "test2_assignment.csv:md5,4d0eefc4298446469162230bc2752a44", - "test2_classification.csv:md5,793c660b1dbba5b1f5d6a87957df32fa", - "test2_genetic.h5ad:md5,c1cec22be88ca7440034dccdd1d21722", - "test2_genetic_and_hashing.h5mu:md5,bdef3ba1ff44ea3ed9cd8b4bdf0396f5", - "test2_hashing.h5ad:md5,9ad6217e86b5118e358ca5aaa5d4bff1", - "test3_assignment.csv:md5,4d0eefc4298446469162230bc2752a44", - "test3_classification.csv:md5,793c660b1dbba5b1f5d6a87957df32fa", - "test3_genetic.h5ad:md5,c1cec22be88ca7440034dccdd1d21722", - "test3_genetic_and_hashing.h5mu:md5,bdef3ba1ff44ea3ed9cd8b4bdf0396f5", - "test3_hashing.h5ad:md5,9ad6217e86b5118e358ca5aaa5d4bff1" + "test1_assignment.csv:md5,3b7d6b6765ae4722b97375fa933b93c5", + "test1_classification.csv:md5,07c15ecad1ff8ea08163a0fabc84d3a9", + "test1_genetic.h5ad:md5,00ed3288cba12a0a4771913a14d5733e", + "test1_genetic_and_hashing.h5mu:md5,8269b93db6e7c57e027c0294a9cebe1a", + "test1_hashing.h5ad:md5,bca9eaccdeb92a5804a72889c67cddb3", + "test2_assignment.csv:md5,3b7d6b6765ae4722b97375fa933b93c5", + "test2_classification.csv:md5,07c15ecad1ff8ea08163a0fabc84d3a9", + "test2_genetic.h5ad:md5,00ed3288cba12a0a4771913a14d5733e", + "test2_genetic_and_hashing.h5mu:md5,8269b93db6e7c57e027c0294a9cebe1a", + "test2_hashing.h5ad:md5,bca9eaccdeb92a5804a72889c67cddb3", + "test3_assignment.csv:md5,3b7d6b6765ae4722b97375fa933b93c5", + "test3_classification.csv:md5,07c15ecad1ff8ea08163a0fabc84d3a9", + "test3_genetic.h5ad:md5,00ed3288cba12a0a4771913a14d5733e", + "test3_genetic_and_hashing.h5mu:md5,8269b93db6e7c57e027c0294a9cebe1a", + "test3_hashing.h5ad:md5,bca9eaccdeb92a5804a72889c67cddb3" ], [ "test1_donor_specific.vcf.gz:md5,8c51c5ed9e880089a8532e30a659adac", @@ -808,10 +1240,10 @@ "test3_GT_donors.vireo.vcf.gz:md5,b92d531491566870c247cf3a122b2dc1" ] ], + "timestamp": "2026-05-16T08:57:49.225710526", "meta": { - "nf-test": "0.9.3", - "nextflow": "25.10.3" - }, - "timestamp": "2026-02-01T16:47:19.348602535" + "nf-test": "0.9.4", + "nextflow": "25.10.4" + } } } \ No newline at end of file diff --git a/tests/test_donor_match.nf.test.snap b/tests/test_donor_match.nf.test.snap index b1f7f770..5ebbb8a5 100644 --- a/tests/test_donor_match.nf.test.snap +++ b/tests/test_donor_match.nf.test.snap @@ -419,10 +419,10 @@ "test3_vireo.vcf.gz:md5,6e4bbad3892e84cd1dd53c8fc0579977" ] ], + "timestamp": "2026-04-23T17:22:11.804693089", "meta": { - "nf-test": "0.9.2", - "nextflow": "25.10.2" - }, - "timestamp": "2026-02-01T15:54:40.941507" + "nf-test": "0.9.4", + "nextflow": "25.10.4" + } } } \ No newline at end of file diff --git a/tests/test_genetic.nf.test.snap b/tests/test_genetic.nf.test.snap index bf5ecda7..ac8e3ace 100644 --- a/tests/test_genetic.nf.test.snap +++ b/tests/test_genetic.nf.test.snap @@ -186,65 +186,65 @@ "test3_freemuxlet_vs_souporcell_concordance_heatmap.png:md5,dd2e92af065ab80b23767797978d05b6", "test3_freemuxlet_vs_souporcell_correlation_res.csv:md5,d21819b5db460f1535596d996af87976", "test3_freemuxlet_vs_souporcell_donor_match.csv:md5,c27bb85b84d4f2e33948b4232cd4169d", - "test1.best:md5,1afa27ec59d7ea6b37e1b9ffbc6007e8", - "test2.best:md5,1afa27ec59d7ea6b37e1b9ffbc6007e8", - "test3.best:md5,1afa27ec59d7ea6b37e1b9ffbc6007e8", - "test1.clust1.samples.gz:md5,42f8c8ba281fccf6edaf6dbd30144210", - "test1.lmix:md5,d8be4c936ff56980a476d5ba82290b8e", - "test2.clust1.samples.gz:md5,42f8c8ba281fccf6edaf6dbd30144210", - "test2.lmix:md5,d8be4c936ff56980a476d5ba82290b8e", - "test3.clust1.samples.gz:md5,42f8c8ba281fccf6edaf6dbd30144210", - "test3.lmix:md5,d8be4c936ff56980a476d5ba82290b8e", - "clusters.tsv:md5,3443284fb0e0ace31b5da0f329568392", - "clusters.tsv:md5,3443284fb0e0ace31b5da0f329568392", - "clusters.tsv:md5,3443284fb0e0ace31b5da0f329568392", + "test1.best:md5,237dadb1f53067fc7e3aad4336c6541b", + "test2.best:md5,237dadb1f53067fc7e3aad4336c6541b", + "test3.best:md5,237dadb1f53067fc7e3aad4336c6541b", + "test1.clust1.samples.gz:md5,d58f19c489213685176f03982dd8ea1e", + "test1.lmix:md5,39b16209e7c2238020024c5611ef10c5", + "test2.clust1.samples.gz:md5,d58f19c489213685176f03982dd8ea1e", + "test2.lmix:md5,39b16209e7c2238020024c5611ef10c5", + "test3.clust1.samples.gz:md5,d58f19c489213685176f03982dd8ea1e", + "test3.lmix:md5,39b16209e7c2238020024c5611ef10c5", + "clusters.tsv:md5,33ae22fc6e9cbb12b2d4a1d48b3da2e1", + "clusters.tsv:md5,33ae22fc6e9cbb12b2d4a1d48b3da2e1", + "clusters.tsv:md5,33ae22fc6e9cbb12b2d4a1d48b3da2e1", "test1_genetic_overview_assignment.csv:md5,6e912c78c50b84deffdaf9fb5e8e3b20", "test1_genetic_overview_classification.csv:md5,812a950b1eef4515079b2d7028b6658c", - "test1_genetic_summary_assignment.csv:md5,4a8a3b1c59ff015db2dba50eefde4f53", - "test1_genetic_summary_classification.csv:md5,938d7daf0ba588923fbc5b7c38bae90e", + "test1_genetic_summary_assignment.csv:md5,d99032c058450687415d441e474cef25", + "test1_genetic_summary_classification.csv:md5,fdfbf54c3c34746624fa6afdaead2f50", "test2_genetic_overview_assignment.csv:md5,6e912c78c50b84deffdaf9fb5e8e3b20", "test2_genetic_overview_classification.csv:md5,812a950b1eef4515079b2d7028b6658c", - "test2_genetic_summary_assignment.csv:md5,4a8a3b1c59ff015db2dba50eefde4f53", - "test2_genetic_summary_classification.csv:md5,938d7daf0ba588923fbc5b7c38bae90e", + "test2_genetic_summary_assignment.csv:md5,d99032c058450687415d441e474cef25", + "test2_genetic_summary_classification.csv:md5,fdfbf54c3c34746624fa6afdaead2f50", "test3_genetic_overview_assignment.csv:md5,6e912c78c50b84deffdaf9fb5e8e3b20", "test3_genetic_overview_classification.csv:md5,812a950b1eef4515079b2d7028b6658c", - "test3_genetic_summary_assignment.csv:md5,4a8a3b1c59ff015db2dba50eefde4f53", - "test3_genetic_summary_classification.csv:md5,938d7daf0ba588923fbc5b7c38bae90e", + "test3_genetic_summary_assignment.csv:md5,d99032c058450687415d441e474cef25", + "test3_genetic_summary_classification.csv:md5,fdfbf54c3c34746624fa6afdaead2f50", "test1.base.vcf.gz:md5,36eccb4bd3e2130f395985e09a66a60d", - "test1.cells.vcf.gz:md5,b28d8acaf17777c7cf9e2c7d01d93a66", - "test1.samples.tsv:md5,9e488782c1bcd63c37ee3d1c4c0a9217", + "test1.cells.vcf.gz:md5,4428343f7d9e3e310c14cfab8b9a57f1", + "test1.samples.tsv:md5,4ee35dd786d28bfad433c5c2b2a00e7a", "test1.tag.AD.mtx:md5,a76d9f63c6abdcab00f583fac269a98e", "test1.tag.DP.mtx:md5,6f8e54bbaa8ad9dcaa42c15eff1e1351", "test1.tag.OTH.mtx:md5,45e64ea3b3b8e8a7126690d9e775d501", "test1_GT_donors.vireo.vcf.gz:md5,60bfaaf61b8e7f5fc2bd0950e576c3e5", - "test1_donor_ids.tsv:md5,5dd2633a15d99a044d7f6706912ed5bc", + "test1_donor_ids.tsv:md5,a9ea6bc5851ecaff5a11abf54c5289cc", "test1_filtered_variants.tsv:md5,d37231135d9e6e330c03ed0647fcaf0e", - "test1_prob_doublet.tsv.gz:md5,3dae1d20888be460d02782eb776043bb", - "test1_prob_singlet.tsv.gz:md5,90a717e3bdb50090c72fa73a18330e38", + "test1_prob_doublet.tsv.gz:md5,68b4ec74b77aa58421d73df2dbc92e34", + "test1_prob_singlet.tsv.gz:md5,b22717700bc3f7e846f6f831466f0dfd", "test1_summary.tsv:md5,94b0ce40cc86bbe7fbacebf98e685ee7", "test2.base.vcf.gz:md5,36eccb4bd3e2130f395985e09a66a60d", - "test2.cells.vcf.gz:md5,b28d8acaf17777c7cf9e2c7d01d93a66", - "test2.samples.tsv:md5,9e488782c1bcd63c37ee3d1c4c0a9217", + "test2.cells.vcf.gz:md5,4428343f7d9e3e310c14cfab8b9a57f1", + "test2.samples.tsv:md5,4ee35dd786d28bfad433c5c2b2a00e7a", "test2.tag.AD.mtx:md5,a76d9f63c6abdcab00f583fac269a98e", "test2.tag.DP.mtx:md5,6f8e54bbaa8ad9dcaa42c15eff1e1351", "test2.tag.OTH.mtx:md5,45e64ea3b3b8e8a7126690d9e775d501", "test2_GT_donors.vireo.vcf.gz:md5,60bfaaf61b8e7f5fc2bd0950e576c3e5", - "test2_donor_ids.tsv:md5,5dd2633a15d99a044d7f6706912ed5bc", + "test2_donor_ids.tsv:md5,a9ea6bc5851ecaff5a11abf54c5289cc", "test2_filtered_variants.tsv:md5,d37231135d9e6e330c03ed0647fcaf0e", - "test2_prob_doublet.tsv.gz:md5,3dae1d20888be460d02782eb776043bb", - "test2_prob_singlet.tsv.gz:md5,90a717e3bdb50090c72fa73a18330e38", + "test2_prob_doublet.tsv.gz:md5,68b4ec74b77aa58421d73df2dbc92e34", + "test2_prob_singlet.tsv.gz:md5,b22717700bc3f7e846f6f831466f0dfd", "test2_summary.tsv:md5,94b0ce40cc86bbe7fbacebf98e685ee7", "test3.base.vcf.gz:md5,36eccb4bd3e2130f395985e09a66a60d", - "test3.cells.vcf.gz:md5,b28d8acaf17777c7cf9e2c7d01d93a66", - "test3.samples.tsv:md5,9e488782c1bcd63c37ee3d1c4c0a9217", + "test3.cells.vcf.gz:md5,4428343f7d9e3e310c14cfab8b9a57f1", + "test3.samples.tsv:md5,4ee35dd786d28bfad433c5c2b2a00e7a", "test3.tag.AD.mtx:md5,a76d9f63c6abdcab00f583fac269a98e", "test3.tag.DP.mtx:md5,6f8e54bbaa8ad9dcaa42c15eff1e1351", "test3.tag.OTH.mtx:md5,45e64ea3b3b8e8a7126690d9e775d501", "test3_GT_donors.vireo.vcf.gz:md5,60bfaaf61b8e7f5fc2bd0950e576c3e5", - "test3_donor_ids.tsv:md5,5dd2633a15d99a044d7f6706912ed5bc", + "test3_donor_ids.tsv:md5,a9ea6bc5851ecaff5a11abf54c5289cc", "test3_filtered_variants.tsv:md5,d37231135d9e6e330c03ed0647fcaf0e", - "test3_prob_doublet.tsv.gz:md5,3dae1d20888be460d02782eb776043bb", - "test3_prob_singlet.tsv.gz:md5,90a717e3bdb50090c72fa73a18330e38", + "test3_prob_doublet.tsv.gz:md5,68b4ec74b77aa58421d73df2dbc92e34", + "test3_prob_singlet.tsv.gz:md5,b22717700bc3f7e846f6f831466f0dfd", "test3_summary.tsv:md5,94b0ce40cc86bbe7fbacebf98e685ee7", "multiqc_citations.txt:md5,4c806e63a283ec1b7e78cdae3a923d4f" ], @@ -263,10 +263,10 @@ "test3_GT_donors.vireo.vcf.gz:md5,b92d531491566870c247cf3a122b2dc1" ] ], + "timestamp": "2026-04-23T17:26:19.929849031", "meta": { - "nf-test": "0.9.3", - "nextflow": "25.10.3" - }, - "timestamp": "2026-02-01T17:01:19.138557743" + "nf-test": "0.9.4", + "nextflow": "25.10.4" + } } } \ No newline at end of file diff --git a/tests/test_hashing.nf.test.snap b/tests/test_hashing.nf.test.snap index 45a930b9..6bf397a8 100644 --- a/tests/test_hashing.nf.test.snap +++ b/tests/test_hashing.nf.test.snap @@ -7,6 +7,7 @@ "r-seurat": "4.3.0.1", "cellhashR": "1.0.3" }, + "DEMUXEM": "echo 0.1.7.post1", "DONOR_MATCH": { "r-base": "4.5.2", "r-data.table": "1.17.8", @@ -39,6 +40,14 @@ "r-seurat": "5.3.0", "r-ggplot2": "3.5.2" }, + "MTXCONVERT_HTO": { + "r-base": "4.4.3", + "bioconductor-dropletutils": "1.26.0" + }, + "MTXCONVERT_RNA": { + "r-base": "4.4.3", + "bioconductor-dropletutils": "1.26.0" + }, "MULTISEQDEMUX": { "r-base": "4.4.3", "r-seurat": "5.3.0" @@ -64,6 +73,34 @@ "donor_match/test1/bff_raw_vs_bff_consensuscall/test1_bff_raw_vs_bff_consensuscall_concordance_heatmap.png", "donor_match/test1/bff_raw_vs_bff_consensuscall/test1_bff_raw_vs_bff_consensuscall_correlation_res.csv", "donor_match/test1/bff_raw_vs_bff_consensuscall/test1_bff_raw_vs_bff_consensuscall_donor_match.csv", + "donor_match/test1/demuxem_vs_bff_consensuscall", + "donor_match/test1/demuxem_vs_bff_consensuscall/test1_demuxem_vs_bff_consensuscall_concordance_heatmap.png", + "donor_match/test1/demuxem_vs_bff_consensuscall/test1_demuxem_vs_bff_consensuscall_correlation_res.csv", + "donor_match/test1/demuxem_vs_bff_consensuscall/test1_demuxem_vs_bff_consensuscall_donor_match.csv", + "donor_match/test1/demuxem_vs_bff_raw", + "donor_match/test1/demuxem_vs_bff_raw/test1_demuxem_vs_bff_raw_concordance_heatmap.png", + "donor_match/test1/demuxem_vs_bff_raw/test1_demuxem_vs_bff_raw_correlation_res.csv", + "donor_match/test1/demuxem_vs_bff_raw/test1_demuxem_vs_bff_raw_donor_match.csv", + "donor_match/test1/demuxem_vs_gmmdemux", + "donor_match/test1/demuxem_vs_gmmdemux/test1_demuxem_vs_gmmdemux_concordance_heatmap.png", + "donor_match/test1/demuxem_vs_gmmdemux/test1_demuxem_vs_gmmdemux_correlation_res.csv", + "donor_match/test1/demuxem_vs_gmmdemux/test1_demuxem_vs_gmmdemux_donor_match.csv", + "donor_match/test1/demuxem_vs_hasheddrops", + "donor_match/test1/demuxem_vs_hasheddrops/test1_demuxem_vs_hasheddrops_concordance_heatmap.png", + "donor_match/test1/demuxem_vs_hasheddrops/test1_demuxem_vs_hasheddrops_correlation_res.csv", + "donor_match/test1/demuxem_vs_hasheddrops/test1_demuxem_vs_hasheddrops_donor_match.csv", + "donor_match/test1/demuxem_vs_hashsolo", + "donor_match/test1/demuxem_vs_hashsolo/test1_demuxem_vs_hashsolo_concordance_heatmap.png", + "donor_match/test1/demuxem_vs_hashsolo/test1_demuxem_vs_hashsolo_correlation_res.csv", + "donor_match/test1/demuxem_vs_hashsolo/test1_demuxem_vs_hashsolo_donor_match.csv", + "donor_match/test1/demuxem_vs_htodemux", + "donor_match/test1/demuxem_vs_htodemux/test1_demuxem_vs_htodemux_concordance_heatmap.png", + "donor_match/test1/demuxem_vs_htodemux/test1_demuxem_vs_htodemux_correlation_res.csv", + "donor_match/test1/demuxem_vs_htodemux/test1_demuxem_vs_htodemux_donor_match.csv", + "donor_match/test1/demuxem_vs_multiseq", + "donor_match/test1/demuxem_vs_multiseq/test1_demuxem_vs_multiseq_concordance_heatmap.png", + "donor_match/test1/demuxem_vs_multiseq/test1_demuxem_vs_multiseq_correlation_res.csv", + "donor_match/test1/demuxem_vs_multiseq/test1_demuxem_vs_multiseq_donor_match.csv", "donor_match/test1/gmmdemux_vs_bff_consensuscall", "donor_match/test1/gmmdemux_vs_bff_consensuscall/test1_gmmdemux_vs_bff_consensuscall_concordance_heatmap.png", "donor_match/test1/gmmdemux_vs_bff_consensuscall/test1_gmmdemux_vs_bff_consensuscall_correlation_res.csv", @@ -149,6 +186,34 @@ "donor_match/test2/bff_raw_vs_bff_consensuscall/test2_bff_raw_vs_bff_consensuscall_concordance_heatmap.png", "donor_match/test2/bff_raw_vs_bff_consensuscall/test2_bff_raw_vs_bff_consensuscall_correlation_res.csv", "donor_match/test2/bff_raw_vs_bff_consensuscall/test2_bff_raw_vs_bff_consensuscall_donor_match.csv", + "donor_match/test2/demuxem_vs_bff_consensuscall", + "donor_match/test2/demuxem_vs_bff_consensuscall/test2_demuxem_vs_bff_consensuscall_concordance_heatmap.png", + "donor_match/test2/demuxem_vs_bff_consensuscall/test2_demuxem_vs_bff_consensuscall_correlation_res.csv", + "donor_match/test2/demuxem_vs_bff_consensuscall/test2_demuxem_vs_bff_consensuscall_donor_match.csv", + "donor_match/test2/demuxem_vs_bff_raw", + "donor_match/test2/demuxem_vs_bff_raw/test2_demuxem_vs_bff_raw_concordance_heatmap.png", + "donor_match/test2/demuxem_vs_bff_raw/test2_demuxem_vs_bff_raw_correlation_res.csv", + "donor_match/test2/demuxem_vs_bff_raw/test2_demuxem_vs_bff_raw_donor_match.csv", + "donor_match/test2/demuxem_vs_gmmdemux", + "donor_match/test2/demuxem_vs_gmmdemux/test2_demuxem_vs_gmmdemux_concordance_heatmap.png", + "donor_match/test2/demuxem_vs_gmmdemux/test2_demuxem_vs_gmmdemux_correlation_res.csv", + "donor_match/test2/demuxem_vs_gmmdemux/test2_demuxem_vs_gmmdemux_donor_match.csv", + "donor_match/test2/demuxem_vs_hasheddrops", + "donor_match/test2/demuxem_vs_hasheddrops/test2_demuxem_vs_hasheddrops_concordance_heatmap.png", + "donor_match/test2/demuxem_vs_hasheddrops/test2_demuxem_vs_hasheddrops_correlation_res.csv", + "donor_match/test2/demuxem_vs_hasheddrops/test2_demuxem_vs_hasheddrops_donor_match.csv", + "donor_match/test2/demuxem_vs_hashsolo", + "donor_match/test2/demuxem_vs_hashsolo/test2_demuxem_vs_hashsolo_concordance_heatmap.png", + "donor_match/test2/demuxem_vs_hashsolo/test2_demuxem_vs_hashsolo_correlation_res.csv", + "donor_match/test2/demuxem_vs_hashsolo/test2_demuxem_vs_hashsolo_donor_match.csv", + "donor_match/test2/demuxem_vs_htodemux", + "donor_match/test2/demuxem_vs_htodemux/test2_demuxem_vs_htodemux_concordance_heatmap.png", + "donor_match/test2/demuxem_vs_htodemux/test2_demuxem_vs_htodemux_correlation_res.csv", + "donor_match/test2/demuxem_vs_htodemux/test2_demuxem_vs_htodemux_donor_match.csv", + "donor_match/test2/demuxem_vs_multiseq", + "donor_match/test2/demuxem_vs_multiseq/test2_demuxem_vs_multiseq_concordance_heatmap.png", + "donor_match/test2/demuxem_vs_multiseq/test2_demuxem_vs_multiseq_correlation_res.csv", + "donor_match/test2/demuxem_vs_multiseq/test2_demuxem_vs_multiseq_donor_match.csv", "donor_match/test2/gmmdemux_vs_bff_consensuscall", "donor_match/test2/gmmdemux_vs_bff_consensuscall/test2_gmmdemux_vs_bff_consensuscall_concordance_heatmap.png", "donor_match/test2/gmmdemux_vs_bff_consensuscall/test2_gmmdemux_vs_bff_consensuscall_correlation_res.csv", @@ -234,6 +299,34 @@ "donor_match/test3/bff_raw_vs_bff_consensuscall/test3_bff_raw_vs_bff_consensuscall_concordance_heatmap.png", "donor_match/test3/bff_raw_vs_bff_consensuscall/test3_bff_raw_vs_bff_consensuscall_correlation_res.csv", "donor_match/test3/bff_raw_vs_bff_consensuscall/test3_bff_raw_vs_bff_consensuscall_donor_match.csv", + "donor_match/test3/demuxem_vs_bff_consensuscall", + "donor_match/test3/demuxem_vs_bff_consensuscall/test3_demuxem_vs_bff_consensuscall_concordance_heatmap.png", + "donor_match/test3/demuxem_vs_bff_consensuscall/test3_demuxem_vs_bff_consensuscall_correlation_res.csv", + "donor_match/test3/demuxem_vs_bff_consensuscall/test3_demuxem_vs_bff_consensuscall_donor_match.csv", + "donor_match/test3/demuxem_vs_bff_raw", + "donor_match/test3/demuxem_vs_bff_raw/test3_demuxem_vs_bff_raw_concordance_heatmap.png", + "donor_match/test3/demuxem_vs_bff_raw/test3_demuxem_vs_bff_raw_correlation_res.csv", + "donor_match/test3/demuxem_vs_bff_raw/test3_demuxem_vs_bff_raw_donor_match.csv", + "donor_match/test3/demuxem_vs_gmmdemux", + "donor_match/test3/demuxem_vs_gmmdemux/test3_demuxem_vs_gmmdemux_concordance_heatmap.png", + "donor_match/test3/demuxem_vs_gmmdemux/test3_demuxem_vs_gmmdemux_correlation_res.csv", + "donor_match/test3/demuxem_vs_gmmdemux/test3_demuxem_vs_gmmdemux_donor_match.csv", + "donor_match/test3/demuxem_vs_hasheddrops", + "donor_match/test3/demuxem_vs_hasheddrops/test3_demuxem_vs_hasheddrops_concordance_heatmap.png", + "donor_match/test3/demuxem_vs_hasheddrops/test3_demuxem_vs_hasheddrops_correlation_res.csv", + "donor_match/test3/demuxem_vs_hasheddrops/test3_demuxem_vs_hasheddrops_donor_match.csv", + "donor_match/test3/demuxem_vs_hashsolo", + "donor_match/test3/demuxem_vs_hashsolo/test3_demuxem_vs_hashsolo_concordance_heatmap.png", + "donor_match/test3/demuxem_vs_hashsolo/test3_demuxem_vs_hashsolo_correlation_res.csv", + "donor_match/test3/demuxem_vs_hashsolo/test3_demuxem_vs_hashsolo_donor_match.csv", + "donor_match/test3/demuxem_vs_htodemux", + "donor_match/test3/demuxem_vs_htodemux/test3_demuxem_vs_htodemux_concordance_heatmap.png", + "donor_match/test3/demuxem_vs_htodemux/test3_demuxem_vs_htodemux_correlation_res.csv", + "donor_match/test3/demuxem_vs_htodemux/test3_demuxem_vs_htodemux_donor_match.csv", + "donor_match/test3/demuxem_vs_multiseq", + "donor_match/test3/demuxem_vs_multiseq/test3_demuxem_vs_multiseq_concordance_heatmap.png", + "donor_match/test3/demuxem_vs_multiseq/test3_demuxem_vs_multiseq_correlation_res.csv", + "donor_match/test3/demuxem_vs_multiseq/test3_demuxem_vs_multiseq_donor_match.csv", "donor_match/test3/gmmdemux_vs_bff_consensuscall", "donor_match/test3/gmmdemux_vs_bff_consensuscall/test3_gmmdemux_vs_bff_consensuscall_concordance_heatmap.png", "donor_match/test3/gmmdemux_vs_bff_consensuscall/test3_gmmdemux_vs_bff_consensuscall_correlation_res.csv", @@ -328,6 +421,28 @@ "hashing/bff/test3/test3_assignment_bff.csv", "hashing/bff/test3/test3_metrics_bff.csv", "hashing/bff/test3/test3_params_bff.csv", + "hashing/demuxem", + "hashing/demuxem/test1", + "hashing/demuxem/test1/test1.ambient_hashtag.hist.pdf", + "hashing/demuxem/test1/test1.background_probabilities.bar.pdf", + "hashing/demuxem/test1/test1.out.demuxEM.zarr.zip", + "hashing/demuxem/test1/test1.real_content.hist.pdf", + "hashing/demuxem/test1/test1.rna_demux.hist.pdf", + "hashing/demuxem/test1/test1_demux.zarr.zip", + "hashing/demuxem/test2", + "hashing/demuxem/test2/test2.ambient_hashtag.hist.pdf", + "hashing/demuxem/test2/test2.background_probabilities.bar.pdf", + "hashing/demuxem/test2/test2.out.demuxEM.zarr.zip", + "hashing/demuxem/test2/test2.real_content.hist.pdf", + "hashing/demuxem/test2/test2.rna_demux.hist.pdf", + "hashing/demuxem/test2/test2_demux.zarr.zip", + "hashing/demuxem/test3", + "hashing/demuxem/test3/test3.ambient_hashtag.hist.pdf", + "hashing/demuxem/test3/test3.background_probabilities.bar.pdf", + "hashing/demuxem/test3/test3.out.demuxEM.zarr.zip", + "hashing/demuxem/test3/test3.real_content.hist.pdf", + "hashing/demuxem/test3/test3.rna_demux.hist.pdf", + "hashing/demuxem/test3/test3_demux.zarr.zip", "hashing/gmm-demux", "hashing/gmm-demux/test1", "hashing/gmm-demux/test1/GMM_full.config", @@ -484,6 +599,27 @@ "test1_bff_raw_vs_bff_consensuscall_concordance_heatmap.png:md5,70228780e63708c0255b9fefd17af97f", "test1_bff_raw_vs_bff_consensuscall_correlation_res.csv:md5,fd2c1ab13a371736d4ce2210fdbbb49a", "test1_bff_raw_vs_bff_consensuscall_donor_match.csv:md5,3dd18b42dded6bc972f54f877b62d744", + "test1_demuxem_vs_bff_consensuscall_concordance_heatmap.png:md5,70228780e63708c0255b9fefd17af97f", + "test1_demuxem_vs_bff_consensuscall_correlation_res.csv:md5,fd2c1ab13a371736d4ce2210fdbbb49a", + "test1_demuxem_vs_bff_consensuscall_donor_match.csv:md5,3dd18b42dded6bc972f54f877b62d744", + "test1_demuxem_vs_bff_raw_concordance_heatmap.png:md5,70228780e63708c0255b9fefd17af97f", + "test1_demuxem_vs_bff_raw_correlation_res.csv:md5,fd2c1ab13a371736d4ce2210fdbbb49a", + "test1_demuxem_vs_bff_raw_donor_match.csv:md5,3dd18b42dded6bc972f54f877b62d744", + "test1_demuxem_vs_gmmdemux_concordance_heatmap.png:md5,70228780e63708c0255b9fefd17af97f", + "test1_demuxem_vs_gmmdemux_correlation_res.csv:md5,fd2c1ab13a371736d4ce2210fdbbb49a", + "test1_demuxem_vs_gmmdemux_donor_match.csv:md5,3dd18b42dded6bc972f54f877b62d744", + "test1_demuxem_vs_hasheddrops_concordance_heatmap.png:md5,70228780e63708c0255b9fefd17af97f", + "test1_demuxem_vs_hasheddrops_correlation_res.csv:md5,fd2c1ab13a371736d4ce2210fdbbb49a", + "test1_demuxem_vs_hasheddrops_donor_match.csv:md5,3dd18b42dded6bc972f54f877b62d744", + "test1_demuxem_vs_hashsolo_concordance_heatmap.png:md5,70228780e63708c0255b9fefd17af97f", + "test1_demuxem_vs_hashsolo_correlation_res.csv:md5,fd2c1ab13a371736d4ce2210fdbbb49a", + "test1_demuxem_vs_hashsolo_donor_match.csv:md5,3dd18b42dded6bc972f54f877b62d744", + "test1_demuxem_vs_htodemux_concordance_heatmap.png:md5,70228780e63708c0255b9fefd17af97f", + "test1_demuxem_vs_htodemux_correlation_res.csv:md5,fd2c1ab13a371736d4ce2210fdbbb49a", + "test1_demuxem_vs_htodemux_donor_match.csv:md5,3dd18b42dded6bc972f54f877b62d744", + "test1_demuxem_vs_multiseq_concordance_heatmap.png:md5,70228780e63708c0255b9fefd17af97f", + "test1_demuxem_vs_multiseq_correlation_res.csv:md5,fd2c1ab13a371736d4ce2210fdbbb49a", + "test1_demuxem_vs_multiseq_donor_match.csv:md5,3dd18b42dded6bc972f54f877b62d744", "test1_gmmdemux_vs_bff_consensuscall_concordance_heatmap.png:md5,70228780e63708c0255b9fefd17af97f", "test1_gmmdemux_vs_bff_consensuscall_correlation_res.csv:md5,fd2c1ab13a371736d4ce2210fdbbb49a", "test1_gmmdemux_vs_bff_consensuscall_donor_match.csv:md5,3dd18b42dded6bc972f54f877b62d744", @@ -547,6 +683,27 @@ "test2_bff_raw_vs_bff_consensuscall_concordance_heatmap.png:md5,70228780e63708c0255b9fefd17af97f", "test2_bff_raw_vs_bff_consensuscall_correlation_res.csv:md5,fd2c1ab13a371736d4ce2210fdbbb49a", "test2_bff_raw_vs_bff_consensuscall_donor_match.csv:md5,3dd18b42dded6bc972f54f877b62d744", + "test2_demuxem_vs_bff_consensuscall_concordance_heatmap.png:md5,70228780e63708c0255b9fefd17af97f", + "test2_demuxem_vs_bff_consensuscall_correlation_res.csv:md5,fd2c1ab13a371736d4ce2210fdbbb49a", + "test2_demuxem_vs_bff_consensuscall_donor_match.csv:md5,3dd18b42dded6bc972f54f877b62d744", + "test2_demuxem_vs_bff_raw_concordance_heatmap.png:md5,70228780e63708c0255b9fefd17af97f", + "test2_demuxem_vs_bff_raw_correlation_res.csv:md5,fd2c1ab13a371736d4ce2210fdbbb49a", + "test2_demuxem_vs_bff_raw_donor_match.csv:md5,3dd18b42dded6bc972f54f877b62d744", + "test2_demuxem_vs_gmmdemux_concordance_heatmap.png:md5,70228780e63708c0255b9fefd17af97f", + "test2_demuxem_vs_gmmdemux_correlation_res.csv:md5,fd2c1ab13a371736d4ce2210fdbbb49a", + "test2_demuxem_vs_gmmdemux_donor_match.csv:md5,3dd18b42dded6bc972f54f877b62d744", + "test2_demuxem_vs_hasheddrops_concordance_heatmap.png:md5,70228780e63708c0255b9fefd17af97f", + "test2_demuxem_vs_hasheddrops_correlation_res.csv:md5,fd2c1ab13a371736d4ce2210fdbbb49a", + "test2_demuxem_vs_hasheddrops_donor_match.csv:md5,3dd18b42dded6bc972f54f877b62d744", + "test2_demuxem_vs_hashsolo_concordance_heatmap.png:md5,70228780e63708c0255b9fefd17af97f", + "test2_demuxem_vs_hashsolo_correlation_res.csv:md5,fd2c1ab13a371736d4ce2210fdbbb49a", + "test2_demuxem_vs_hashsolo_donor_match.csv:md5,3dd18b42dded6bc972f54f877b62d744", + "test2_demuxem_vs_htodemux_concordance_heatmap.png:md5,70228780e63708c0255b9fefd17af97f", + "test2_demuxem_vs_htodemux_correlation_res.csv:md5,fd2c1ab13a371736d4ce2210fdbbb49a", + "test2_demuxem_vs_htodemux_donor_match.csv:md5,3dd18b42dded6bc972f54f877b62d744", + "test2_demuxem_vs_multiseq_concordance_heatmap.png:md5,70228780e63708c0255b9fefd17af97f", + "test2_demuxem_vs_multiseq_correlation_res.csv:md5,fd2c1ab13a371736d4ce2210fdbbb49a", + "test2_demuxem_vs_multiseq_donor_match.csv:md5,3dd18b42dded6bc972f54f877b62d744", "test2_gmmdemux_vs_bff_consensuscall_concordance_heatmap.png:md5,70228780e63708c0255b9fefd17af97f", "test2_gmmdemux_vs_bff_consensuscall_correlation_res.csv:md5,fd2c1ab13a371736d4ce2210fdbbb49a", "test2_gmmdemux_vs_bff_consensuscall_donor_match.csv:md5,3dd18b42dded6bc972f54f877b62d744", @@ -610,6 +767,27 @@ "test3_bff_raw_vs_bff_consensuscall_concordance_heatmap.png:md5,70228780e63708c0255b9fefd17af97f", "test3_bff_raw_vs_bff_consensuscall_correlation_res.csv:md5,fd2c1ab13a371736d4ce2210fdbbb49a", "test3_bff_raw_vs_bff_consensuscall_donor_match.csv:md5,3dd18b42dded6bc972f54f877b62d744", + "test3_demuxem_vs_bff_consensuscall_concordance_heatmap.png:md5,70228780e63708c0255b9fefd17af97f", + "test3_demuxem_vs_bff_consensuscall_correlation_res.csv:md5,fd2c1ab13a371736d4ce2210fdbbb49a", + "test3_demuxem_vs_bff_consensuscall_donor_match.csv:md5,3dd18b42dded6bc972f54f877b62d744", + "test3_demuxem_vs_bff_raw_concordance_heatmap.png:md5,70228780e63708c0255b9fefd17af97f", + "test3_demuxem_vs_bff_raw_correlation_res.csv:md5,fd2c1ab13a371736d4ce2210fdbbb49a", + "test3_demuxem_vs_bff_raw_donor_match.csv:md5,3dd18b42dded6bc972f54f877b62d744", + "test3_demuxem_vs_gmmdemux_concordance_heatmap.png:md5,70228780e63708c0255b9fefd17af97f", + "test3_demuxem_vs_gmmdemux_correlation_res.csv:md5,fd2c1ab13a371736d4ce2210fdbbb49a", + "test3_demuxem_vs_gmmdemux_donor_match.csv:md5,3dd18b42dded6bc972f54f877b62d744", + "test3_demuxem_vs_hasheddrops_concordance_heatmap.png:md5,70228780e63708c0255b9fefd17af97f", + "test3_demuxem_vs_hasheddrops_correlation_res.csv:md5,fd2c1ab13a371736d4ce2210fdbbb49a", + "test3_demuxem_vs_hasheddrops_donor_match.csv:md5,3dd18b42dded6bc972f54f877b62d744", + "test3_demuxem_vs_hashsolo_concordance_heatmap.png:md5,70228780e63708c0255b9fefd17af97f", + "test3_demuxem_vs_hashsolo_correlation_res.csv:md5,fd2c1ab13a371736d4ce2210fdbbb49a", + "test3_demuxem_vs_hashsolo_donor_match.csv:md5,3dd18b42dded6bc972f54f877b62d744", + "test3_demuxem_vs_htodemux_concordance_heatmap.png:md5,70228780e63708c0255b9fefd17af97f", + "test3_demuxem_vs_htodemux_correlation_res.csv:md5,fd2c1ab13a371736d4ce2210fdbbb49a", + "test3_demuxem_vs_htodemux_donor_match.csv:md5,3dd18b42dded6bc972f54f877b62d744", + "test3_demuxem_vs_multiseq_concordance_heatmap.png:md5,70228780e63708c0255b9fefd17af97f", + "test3_demuxem_vs_multiseq_correlation_res.csv:md5,fd2c1ab13a371736d4ce2210fdbbb49a", + "test3_demuxem_vs_multiseq_donor_match.csv:md5,3dd18b42dded6bc972f54f877b62d744", "test3_gmmdemux_vs_bff_consensuscall_concordance_heatmap.png:md5,70228780e63708c0255b9fefd17af97f", "test3_gmmdemux_vs_bff_consensuscall_correlation_res.csv:md5,fd2c1ab13a371736d4ce2210fdbbb49a", "test3_gmmdemux_vs_bff_consensuscall_donor_match.csv:md5,3dd18b42dded6bc972f54f877b62d744", @@ -670,62 +848,62 @@ "test3_multiseq_vs_htodemux_concordance_heatmap.png:md5,70228780e63708c0255b9fefd17af97f", "test3_multiseq_vs_htodemux_correlation_res.csv:md5,fd2c1ab13a371736d4ce2210fdbbb49a", "test3_multiseq_vs_htodemux_donor_match.csv:md5,3dd18b42dded6bc972f54f877b62d744", - "test1_assignment_bff.csv:md5,82e9e12fec29f9eb0dcfbb1b6d8cc02a", + "test1_assignment_bff.csv:md5,a62d56a7a5b9b59765d2ae759773aca2", "test1_metrics_bff.csv:md5,1d7751cf035c9e736e4de5504ff87a6a", "test1_params_bff.csv:md5,dbb83e4e50779916ff4e368b2d05fe91", - "test2_assignment_bff.csv:md5,82e9e12fec29f9eb0dcfbb1b6d8cc02a", + "test2_assignment_bff.csv:md5,a62d56a7a5b9b59765d2ae759773aca2", "test2_metrics_bff.csv:md5,1d7751cf035c9e736e4de5504ff87a6a", "test2_params_bff.csv:md5,08758e268b309e4f3288069e3eab358a", - "test3_assignment_bff.csv:md5,82e9e12fec29f9eb0dcfbb1b6d8cc02a", + "test3_assignment_bff.csv:md5,a62d56a7a5b9b59765d2ae759773aca2", "test3_metrics_bff.csv:md5,1d7751cf035c9e736e4de5504ff87a6a", "test3_params_bff.csv:md5,882b80fd11a10a375e0abd5ae29f74b0", "GMM_full.config:md5,0a18f3d5729c4b6399ce4161609362b6", - "barcodes.tsv.gz:md5,e4cd9ce9929a07d6763304ec1bf64c23", + "barcodes.tsv.gz:md5,77a6cfcbb9cee3611b37d6309a8acaf7", "features.tsv.gz:md5,441a968b2c7ee68eae8388b04bee328d", "matrix.mtx.gz:md5,6f855b8bf68573d2e09139c29f9a2d41", "GMM_full.config:md5,0a18f3d5729c4b6399ce4161609362b6", - "barcodes.tsv.gz:md5,e4cd9ce9929a07d6763304ec1bf64c23", + "barcodes.tsv.gz:md5,77a6cfcbb9cee3611b37d6309a8acaf7", "features.tsv.gz:md5,441a968b2c7ee68eae8388b04bee328d", "matrix.mtx.gz:md5,6f855b8bf68573d2e09139c29f9a2d41", "GMM_full.config:md5,0a18f3d5729c4b6399ce4161609362b6", - "barcodes.tsv.gz:md5,e4cd9ce9929a07d6763304ec1bf64c23", + "barcodes.tsv.gz:md5,77a6cfcbb9cee3611b37d6309a8acaf7", "features.tsv.gz:md5,441a968b2c7ee68eae8388b04bee328d", "matrix.mtx.gz:md5,6f855b8bf68573d2e09139c29f9a2d41", "test1_emptyDrops.csv:md5,a227d42afbcc590b4e949075cde4a5b6", "test1_emptyDrops.png:md5,2d7113a76a494b2b2c18b77c4a664063", "test1_emptyDrops.rds:md5,bda2e88b1e3e8c8ea5a22e19e3ab243d", - "test1_hasheddrops.rds:md5,7bb71fa6f28e3fc97b20bbdeffbb81d2", + "test1_hasheddrops.rds:md5,53424c915d3926e46180be1408d28311", "test1_id_to_hash.csv:md5,e8f577103f3e0c17604f89c3bcda2ffb", "test1_params_hasheddrops.csv:md5,b253501914df23ef5901262e8a241a8d", "test1_plot_hasheddrops.png:md5,2d7113a76a494b2b2c18b77c4a664063", - "test1_results_hasheddrops.csv:md5,9614789e65fc843e6a11fa60686fe89a", + "test1_results_hasheddrops.csv:md5,230bcd615d3ac3d7ae8eb37ebe67f016", "test2_emptyDrops.csv:md5,a227d42afbcc590b4e949075cde4a5b6", "test2_emptyDrops.png:md5,2d7113a76a494b2b2c18b77c4a664063", "test2_emptyDrops.rds:md5,bda2e88b1e3e8c8ea5a22e19e3ab243d", - "test2_hasheddrops.rds:md5,7bb71fa6f28e3fc97b20bbdeffbb81d2", + "test2_hasheddrops.rds:md5,53424c915d3926e46180be1408d28311", "test2_id_to_hash.csv:md5,e8f577103f3e0c17604f89c3bcda2ffb", "test2_params_hasheddrops.csv:md5,3612264937e5c5962fefddc43afb595f", "test2_plot_hasheddrops.png:md5,2d7113a76a494b2b2c18b77c4a664063", - "test2_results_hasheddrops.csv:md5,9614789e65fc843e6a11fa60686fe89a", + "test2_results_hasheddrops.csv:md5,230bcd615d3ac3d7ae8eb37ebe67f016", "test3_emptyDrops.csv:md5,a227d42afbcc590b4e949075cde4a5b6", "test3_emptyDrops.png:md5,2d7113a76a494b2b2c18b77c4a664063", "test3_emptyDrops.rds:md5,bda2e88b1e3e8c8ea5a22e19e3ab243d", - "test3_hasheddrops.rds:md5,7bb71fa6f28e3fc97b20bbdeffbb81d2", + "test3_hasheddrops.rds:md5,53424c915d3926e46180be1408d28311", "test3_id_to_hash.csv:md5,e8f577103f3e0c17604f89c3bcda2ffb", "test3_params_hasheddrops.csv:md5,b15e94f887e3ada8f790ea18eb791d8b", "test3_plot_hasheddrops.png:md5,2d7113a76a494b2b2c18b77c4a664063", - "test3_results_hasheddrops.csv:md5,9614789e65fc843e6a11fa60686fe89a", - "test1_assignment_hashsolo.csv:md5,78cb7c0c26de18064e22a68dcf5e81fd", - "test1_hashsolo.h5ad:md5,b359b676ca95b91ba7397b56112ad1b1", + "test3_results_hasheddrops.csv:md5,230bcd615d3ac3d7ae8eb37ebe67f016", + "test1_assignment_hashsolo.csv:md5,7763ee9e2ef20c353ab386f55f805b33", + "test1_hashsolo.h5ad:md5,41ba3e91669bb4d52a7eb214186d36ea", "test1_params_hashsolo.csv:md5,e39b2354a531ff7c65a0d3d0ad500a81", - "test2_assignment_hashsolo.csv:md5,78cb7c0c26de18064e22a68dcf5e81fd", - "test2_hashsolo.h5ad:md5,b359b676ca95b91ba7397b56112ad1b1", + "test2_assignment_hashsolo.csv:md5,7763ee9e2ef20c353ab386f55f805b33", + "test2_hashsolo.h5ad:md5,41ba3e91669bb4d52a7eb214186d36ea", "test2_params_hashsolo.csv:md5,581ecdaff2557e6402ae2acf90d4167f", - "test3_assignment_hashsolo.csv:md5,78cb7c0c26de18064e22a68dcf5e81fd", - "test3_hashsolo.h5ad:md5,b359b676ca95b91ba7397b56112ad1b1", + "test3_assignment_hashsolo.csv:md5,7763ee9e2ef20c353ab386f55f805b33", + "test3_hashsolo.h5ad:md5,41ba3e91669bb4d52a7eb214186d36ea", "test3_params_hashsolo.csv:md5,4eac179a88b560899622d0de4cd5b079", - "test1_assignment_htodemux.csv:md5,33d72b27c3d0b0f7394af18a18490a5c", - "test1_classification_htodemux.csv:md5,886f595f148d2ccdd5a9134b8eb815cb", + "test1_assignment_htodemux.csv:md5,7217cd9d30a3c6f7f69e8b4f8aaa679d", + "test1_classification_htodemux.csv:md5,e5b0d2af4de9bdc6682c120a78e53aa0", "test1_params_htodemux.csv:md5,08b6c065b0c383e6be3c20787ec01d3e", "test1_featureScatter_htodemux.jpeg:md5,c67268ac0ff18c485899b4ee58f8f948", "test1_heatMap_htodemux.jpeg:md5,2ebdb027344db91ae89deaa3d1011993", @@ -733,8 +911,8 @@ "test1_tSNE_htodemux.jpeg:md5,470812a92d09b88ea6e33bcdadb13f11", "test1_violinPlot_htodemux.jpeg:md5,ef866e13e18a31ab43370299245d33c7", "test1_visual_params_htodemux.csv:md5,0a175b9868be2b4447867ab38705c11d", - "test2_assignment_htodemux.csv:md5,33d72b27c3d0b0f7394af18a18490a5c", - "test2_classification_htodemux.csv:md5,886f595f148d2ccdd5a9134b8eb815cb", + "test2_assignment_htodemux.csv:md5,7217cd9d30a3c6f7f69e8b4f8aaa679d", + "test2_classification_htodemux.csv:md5,e5b0d2af4de9bdc6682c120a78e53aa0", "test2_params_htodemux.csv:md5,484909d87cacd9c2b9cf60a3c853a5fd", "test2_featureScatter_htodemux.jpeg:md5,c67268ac0ff18c485899b4ee58f8f948", "test2_heatMap_htodemux.jpeg:md5,2ebdb027344db91ae89deaa3d1011993", @@ -742,8 +920,8 @@ "test2_tSNE_htodemux.jpeg:md5,470812a92d09b88ea6e33bcdadb13f11", "test2_violinPlot_htodemux.jpeg:md5,ef866e13e18a31ab43370299245d33c7", "test2_visual_params_htodemux.csv:md5,062e94620e3207810ddf2c65bf0d2914", - "test3_assignment_htodemux.csv:md5,33d72b27c3d0b0f7394af18a18490a5c", - "test3_classification_htodemux.csv:md5,886f595f148d2ccdd5a9134b8eb815cb", + "test3_assignment_htodemux.csv:md5,7217cd9d30a3c6f7f69e8b4f8aaa679d", + "test3_classification_htodemux.csv:md5,e5b0d2af4de9bdc6682c120a78e53aa0", "test3_params_htodemux.csv:md5,a0a3f900a632eea39057b038438a89b9", "test3_featureScatter_htodemux.jpeg:md5,c67268ac0ff18c485899b4ee58f8f948", "test3_heatMap_htodemux.jpeg:md5,2ebdb027344db91ae89deaa3d1011993", @@ -752,40 +930,40 @@ "test3_violinPlot_htodemux.jpeg:md5,ef866e13e18a31ab43370299245d33c7", "test3_visual_params_htodemux.csv:md5,b6f8f5593aeb86d846292f2f23fc518d", "test1_params_multiseqdemux.csv:md5,60a096f79d78655870aef2af9130cf9d", - "test1_res_multiseqdemux.csv:md5,9d4475b8c2778e7e361747dd521072e4", + "test1_res_multiseqdemux.csv:md5,1173b7b90f66f0eb41d516a119ba52ee", "test2_params_multiseqdemux.csv:md5,bb7b4009e6f709ebf35407b7849bfac3", - "test2_res_multiseqdemux.csv:md5,9d4475b8c2778e7e361747dd521072e4", + "test2_res_multiseqdemux.csv:md5,1173b7b90f66f0eb41d516a119ba52ee", "test3_params_multiseqdemux.csv:md5,336308c45e52f92981173ecc7f295581", - "test3_res_multiseqdemux.csv:md5,9d4475b8c2778e7e361747dd521072e4", - "test1_hashing_overview_assignment.csv:md5,d0356ab124e50b77f5d60a05a38ba585", - "test1_hashing_overview_classification.csv:md5,73e83a9cc51560a08c28357921d1baf9", - "test1_hashing_summary_assignment.csv:md5,53ccb1fc3a6b85d10ff2cf580e9dfbe3", - "test1_hashing_summary_classification.csv:md5,052d8aad0e934cb2384fae9b9f313e37", - "test2_hashing_overview_assignment.csv:md5,d0356ab124e50b77f5d60a05a38ba585", - "test2_hashing_overview_classification.csv:md5,73e83a9cc51560a08c28357921d1baf9", - "test2_hashing_summary_assignment.csv:md5,53ccb1fc3a6b85d10ff2cf580e9dfbe3", - "test2_hashing_summary_classification.csv:md5,052d8aad0e934cb2384fae9b9f313e37", - "test3_hashing_overview_assignment.csv:md5,d0356ab124e50b77f5d60a05a38ba585", - "test3_hashing_overview_classification.csv:md5,73e83a9cc51560a08c28357921d1baf9", - "test3_hashing_summary_assignment.csv:md5,53ccb1fc3a6b85d10ff2cf580e9dfbe3", - "test3_hashing_summary_classification.csv:md5,052d8aad0e934cb2384fae9b9f313e37", + "test3_res_multiseqdemux.csv:md5,1173b7b90f66f0eb41d516a119ba52ee", + "test1_hashing_overview_assignment.csv:md5,6fc8d4eed208650b52e9c5f426d995a1", + "test1_hashing_overview_classification.csv:md5,85f544d3dbf9377ca25af4f0720d9e9f", + "test1_hashing_summary_assignment.csv:md5,31a5bd36901a64e5908a38714b19f622", + "test1_hashing_summary_classification.csv:md5,c929da1c41e0adc0f3e120f27db00fb6", + "test2_hashing_overview_assignment.csv:md5,6fc8d4eed208650b52e9c5f426d995a1", + "test2_hashing_overview_classification.csv:md5,85f544d3dbf9377ca25af4f0720d9e9f", + "test2_hashing_summary_assignment.csv:md5,31a5bd36901a64e5908a38714b19f622", + "test2_hashing_summary_classification.csv:md5,c929da1c41e0adc0f3e120f27db00fb6", + "test3_hashing_overview_assignment.csv:md5,6fc8d4eed208650b52e9c5f426d995a1", + "test3_hashing_overview_classification.csv:md5,85f544d3dbf9377ca25af4f0720d9e9f", + "test3_hashing_summary_assignment.csv:md5,31a5bd36901a64e5908a38714b19f622", + "test3_hashing_summary_classification.csv:md5,c929da1c41e0adc0f3e120f27db00fb6", "multiqc_citations.txt:md5,4c806e63a283ec1b7e78cdae3a923d4f", - "test1_genetic.h5ad:md5,6493fa69aa7797d7530e5fdeb1e60048", - "test1_genetic_and_hashing.h5mu:md5,dc47ea1661289d1f61f7fa2d2dc4cf2d", - "test1_hashing.h5ad:md5,d3f3c7a57e654e70ce3c4cf40b99f227", - "test2_genetic.h5ad:md5,6493fa69aa7797d7530e5fdeb1e60048", - "test2_genetic_and_hashing.h5mu:md5,dc47ea1661289d1f61f7fa2d2dc4cf2d", - "test2_hashing.h5ad:md5,d3f3c7a57e654e70ce3c4cf40b99f227", - "test3_genetic.h5ad:md5,6493fa69aa7797d7530e5fdeb1e60048", - "test3_genetic_and_hashing.h5mu:md5,dc47ea1661289d1f61f7fa2d2dc4cf2d", - "test3_hashing.h5ad:md5,d3f3c7a57e654e70ce3c4cf40b99f227" + "test1_genetic.h5ad:md5,7f6429d2110a385c95ffe03d325ebcfd", + "test1_genetic_and_hashing.h5mu:md5,7085e41a05542950f90658a65417f002", + "test1_hashing.h5ad:md5,bca9eaccdeb92a5804a72889c67cddb3", + "test2_genetic.h5ad:md5,7f6429d2110a385c95ffe03d325ebcfd", + "test2_genetic_and_hashing.h5mu:md5,7085e41a05542950f90658a65417f002", + "test2_hashing.h5ad:md5,bca9eaccdeb92a5804a72889c67cddb3", + "test3_genetic.h5ad:md5,7f6429d2110a385c95ffe03d325ebcfd", + "test3_genetic_and_hashing.h5mu:md5,7085e41a05542950f90658a65417f002", + "test3_hashing.h5ad:md5,bca9eaccdeb92a5804a72889c67cddb3" ], "No VCF files" ], + "timestamp": "2026-04-23T17:28:30.528242288", "meta": { - "nf-test": "0.9.3", - "nextflow": "25.10.3" - }, - "timestamp": "2026-02-01T17:35:31.298514478" + "nf-test": "0.9.4", + "nextflow": "25.10.4" + } } } \ No newline at end of file From 70b9c1a52a862cf54efd03765b6af84b08dec668 Mon Sep 17 00:00:00 2001 From: nf-core bot Date: Thu, 25 Jun 2026 09:32:57 +0200 Subject: [PATCH 43/74] Important! Template update for nf-core/tools v4.0.2 (#109) * Template update for nf-core/tools version 3.3.2 * Template update for nf-core/tools version 3.3.2 * Template update for nf-core/tools version 4.0.0 * Template update for nf-core/tools version 4.0.2 * Fix nextflow linting errors * Update multiQC module * Fix nextflow lint warnings and remove redundant samplesheet input parameter. Use explicit closure parameters, prefix unused bindings, and drop dead HTODEMUX channel assignments. PIPELINE_INITIALISATION reads params.input directly, so the take input is no longer needed. * Use elvis operator for simplification * Update container config files --------- Co-authored-by: Nico Trummer --- .devcontainer/devcontainer.json | 1 + .github/CONTRIBUTING.md | 125 -- .github/PULL_REQUEST_TEMPLATE.md | 4 +- .github/actions/get-shards/action.yml | 2 +- .github/actions/nf-test/action.yml | 10 +- .github/workflows/awsfulltest.yml | 25 +- .github/workflows/awstest.yml | 4 +- .github/workflows/branch.yml | 2 +- .github/workflows/clean-up.yml | 2 +- .github/workflows/download_pipeline.yml | 16 +- .github/workflows/fix_linting.yml | 22 +- .github/workflows/linting.yml | 34 +- .github/workflows/linting_comment.yml | 4 +- .github/workflows/nf-test.yml | 8 +- .github/workflows/release-announcements.yml | 4 +- .../workflows/template-version-comment.yml | 6 +- .gitignore | 1 + .nf-core.yml | 2 +- .pre-commit-config.yaml | 16 +- .prettierignore | 2 - README.md | 8 +- assets/adaptivecard.json | 67 - assets/slackreport.json | 34 - conf/base.config | 2 +- conf/containers_conda_lock_files_amd64.config | 1 + conf/containers_conda_lock_files_arm64.config | 1 + conf/containers_docker_amd64.config | 1 + conf/containers_docker_arm64.config | 1 + .../containers_singularity_https_amd64.config | 1 + .../containers_singularity_https_arm64.config | 1 + conf/containers_singularity_oras_amd64.config | 1 + conf/containers_singularity_oras_arm64.config | 1 + docs/CONTRIBUTING.md | 185 ++ docs/usage.md | 8 +- main.nf | 8 +- modules.json | 6 +- modules/local/create_anndata_mudata/main.nf | 2 +- modules/local/extract_hashes/main.nf | 2 +- modules/local/gene_summary/main.nf | 2 +- modules/local/hash_summary/main.nf | 2 +- modules/local/htodemux_visualization/main.nf | 2 +- .../main.nf | 2 +- modules/nf-core/demuxem/main.nf | 2 +- .../linux_amd64-bd-c17fb751507e9dfc_1.txt | 1526 +++++++++++++++++ .../linux_arm64-bd-5c84a5000a226ab5_1.txt | 1476 ++++++++++++++++ modules/nf-core/multiqc/environment.yml | 2 +- modules/nf-core/multiqc/main.nf | 27 +- modules/nf-core/multiqc/meta.yml | 156 +- modules/nf-core/multiqc/tests/main.nf.test | 191 ++- .../nf-core/multiqc/tests/main.nf.test.snap | 411 ++++- modules/nf-core/multiqc/tests/nextflow.config | 1 + modules/nf-core/multiqc/tests/tags.yml | 2 - nextflow.config | 8 +- nextflow_schema.json | 8 - nf-test.config | 29 +- ro-crate-metadata.json | 32 +- .../local/genetic_demultiplexing/main.nf | 2 +- .../local/hash_demultiplexing/main.nf | 24 +- .../local/utils_nfcore_hadge_pipeline/main.nf | 22 +- .../nf-core/utils_nfcore_pipeline/main.nf | 66 +- .../utils_nfcore_pipeline/tests/main.nf.test | 29 + .../tests/main.nf.test.snap | 19 + .../nf-core/utils_nfschema_plugin/main.nf | 3 +- .../tests/nextflow.config | 2 +- tests/default.nf.test | 2 +- tests/nextflow.config | 2 +- workflows/hadge.nf | 74 +- 67 files changed, 4093 insertions(+), 651 deletions(-) delete mode 100644 .github/CONTRIBUTING.md delete mode 100644 assets/adaptivecard.json delete mode 100644 assets/slackreport.json create mode 100644 conf/containers_conda_lock_files_amd64.config create mode 100644 conf/containers_conda_lock_files_arm64.config create mode 100644 conf/containers_docker_amd64.config create mode 100644 conf/containers_docker_arm64.config create mode 100644 conf/containers_singularity_https_amd64.config create mode 100644 conf/containers_singularity_https_arm64.config create mode 100644 conf/containers_singularity_oras_amd64.config create mode 100644 conf/containers_singularity_oras_arm64.config create mode 100644 docs/CONTRIBUTING.md create mode 100644 modules/nf-core/multiqc/.conda-lock/linux_amd64-bd-c17fb751507e9dfc_1.txt create mode 100644 modules/nf-core/multiqc/.conda-lock/linux_arm64-bd-5c84a5000a226ab5_1.txt delete mode 100644 modules/nf-core/multiqc/tests/tags.yml create mode 100644 subworkflows/nf-core/utils_nfcore_pipeline/tests/main.nf.test create mode 100644 subworkflows/nf-core/utils_nfcore_pipeline/tests/main.nf.test.snap diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index 97c8c97f..237c9ed0 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -1,4 +1,5 @@ { + "$schema": "https://raw.githubusercontent.com/devcontainers/spec/main/schemas/devContainer.schema.json", "name": "nfcore", "image": "nfcore/devcontainer:latest", diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md deleted file mode 100644 index b56b839e..00000000 --- a/.github/CONTRIBUTING.md +++ /dev/null @@ -1,125 +0,0 @@ -# `nf-core/hadge`: Contributing Guidelines - -Hi there! -Many thanks for taking an interest in improving nf-core/hadge. - -We try to manage the required tasks for nf-core/hadge using GitHub issues, you probably came to this page when creating one. -Please use the pre-filled template to save time. - -However, don't be put off by this template - other more general issues and suggestions are welcome! -Contributions to the code are even more welcome ;) - -> [!NOTE] -> If you need help using or modifying nf-core/hadge then the best place to ask is on the nf-core Slack [#hadge](https://nfcore.slack.com/channels/hadge) channel ([join our Slack here](https://nf-co.re/join/slack)). - -## Contribution workflow - -If you'd like to write some code for nf-core/hadge, the standard workflow is as follows: - -1. Check that there isn't already an issue about your idea in the [nf-core/hadge issues](https://github.com/nf-core/hadge/issues) to avoid duplicating work. If there isn't one already, please create one so that others know you're working on this -2. [Fork](https://help.github.com/en/github/getting-started-with-github/fork-a-repo) the [nf-core/hadge repository](https://github.com/nf-core/hadge) to your GitHub account -3. Make the necessary changes / additions within your forked repository following [Pipeline conventions](#pipeline-contribution-conventions) -4. Use `nf-core pipelines schema build` and add any new parameters to the pipeline JSON schema (requires [nf-core tools](https://github.com/nf-core/tools) >= 1.10). -5. Submit a Pull Request against the `dev` branch and wait for the code to be reviewed and merged - -If you're not used to this workflow with git, you can start with some [docs from GitHub](https://help.github.com/en/github/collaborating-with-issues-and-pull-requests) or even their [excellent `git` resources](https://try.github.io/). - -## Tests - -You have the option to test your changes locally by running the pipeline. For receiving warnings about process selectors and other `debug` information, it is recommended to use the debug profile. Execute all the tests with the following command: - -```bash -nf-test test --profile debug,test,docker --verbose -``` - -When you create a pull request with changes, [GitHub Actions](https://github.com/features/actions) will run automatic tests. -Typically, pull-requests are only fully reviewed when these tests are passing, though of course we can help out before then. - -There are typically two types of tests that run: - -### Lint tests - -`nf-core` has a [set of guidelines](https://nf-co.re/developers/guidelines) which all pipelines must adhere to. -To enforce these and ensure that all pipelines stay in sync, we have developed a helper tool which runs checks on the pipeline code. This is in the [nf-core/tools repository](https://github.com/nf-core/tools) and once installed can be run locally with the `nf-core pipelines lint ` command. - -If any failures or warnings are encountered, please follow the listed URL for more documentation. - -### Pipeline tests - -Each `nf-core` pipeline should be set up with a minimal set of test-data. -`GitHub Actions` then runs the pipeline on this data to ensure that it exits successfully. -If there are any failures then the automated tests fail. -These tests are run both with the latest available version of `Nextflow` and also the minimum required version that is stated in the pipeline code. - -## Patch - -:warning: Only in the unlikely and regretful event of a release happening with a bug. - -- On your own fork, make a new branch `patch` based on `upstream/main` or `upstream/master`. -- Fix the bug, and bump version (X.Y.Z+1). -- Open a pull-request from `patch` to `main`/`master` with the changes. - -## Getting help - -For further information/help, please consult the [nf-core/hadge documentation](https://nf-co.re/hadge/usage) and don't hesitate to get in touch on the nf-core Slack [#hadge](https://nfcore.slack.com/channels/hadge) channel ([join our Slack here](https://nf-co.re/join/slack)). - -## Pipeline contribution conventions - -To make the `nf-core/hadge` code and processing logic more understandable for new contributors and to ensure quality, we semi-standardise the way the code and other contributions are written. - -### Adding a new step - -If you wish to contribute a new step, please use the following coding standards: - -1. Define the corresponding input channel into your new process from the expected previous process channel. -2. Write the process block (see below). -3. Define the output channel if needed (see below). -4. Add any new parameters to `nextflow.config` with a default (see below). -5. Add any new parameters to `nextflow_schema.json` with help text (via the `nf-core pipelines schema build` tool). -6. Add sanity checks and validation for all relevant parameters. -7. Perform local tests to validate that the new code works as expected. -8. If applicable, add a new test in the `tests` directory. -9. Update MultiQC config `assets/multiqc_config.yml` so relevant suffixes, file name clean up and module plots are in the appropriate order. If applicable, add a [MultiQC](https://https://multiqc.info/) module. -10. Add a description of the output files and if relevant any appropriate images from the MultiQC report to `docs/output.md`. - -### Default values - -Parameters should be initialised / defined with default values within the `params` scope in `nextflow.config`. - -Once there, use `nf-core pipelines schema build` to add to `nextflow_schema.json`. - -### Default processes resource requirements - -Sensible defaults for process resource requirements (CPUs / memory / time) for a process should be defined in `conf/base.config`. These should generally be specified generic with `withLabel:` selectors so they can be shared across multiple processes/steps of the pipeline. A nf-core standard set of labels that should be followed where possible can be seen in the [nf-core pipeline template](https://github.com/nf-core/tools/blob/main/nf_core/pipeline-template/conf/base.config), which has the default process as a single core-process, and then different levels of multi-core configurations for increasingly large memory requirements defined with standardised labels. - -The process resources can be passed on to the tool dynamically within the process with the `${task.cpus}` and `${task.memory}` variables in the `script:` block. - -### Naming schemes - -Please use the following naming schemes, to make it easy to understand what is going where. - -- initial process channel: `ch_output_from_` -- intermediate and terminal channels: `ch__for_` - -### Nextflow version bumping - -If you are using a new feature from core Nextflow, you may bump the minimum required version of nextflow in the pipeline with: `nf-core pipelines bump-version --nextflow . [min-nf-version]` - -### Images and figures - -For overview images and other documents we follow the nf-core [style guidelines and examples](https://nf-co.re/developers/design_guidelines). - -## GitHub Codespaces - -This repo includes a devcontainer configuration which will create a GitHub Codespaces for Nextflow development! This is an online developer environment that runs in your browser, complete with VSCode and a terminal. - -To get started: - -- Open the repo in [Codespaces](https://github.com/nf-core/hadge/codespaces) -- Tools installed - - nf-core - - Nextflow - -Devcontainer specs: - -- [DevContainer config](.devcontainer/devcontainer.json) diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index 4bf7ad0b..3c2642cc 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -8,14 +8,14 @@ These are the most common things requested on pull requests (PRs). Remember that PRs should be made against the dev branch, unless you're preparing a pipeline release. -Learn more about contributing: [CONTRIBUTING.md](https://github.com/nf-core/hadge/tree/master/.github/CONTRIBUTING.md) +Learn more about contributing: [CONTRIBUTING.md](https://github.com/nf-core/hadge/tree/main/docs/CONTRIBUTING.md) --> ## PR checklist - [ ] This comment contains a description of changes (with reason). - [ ] If you've fixed a bug or added code that should be tested, add tests! -- [ ] If you've added a new tool - have you followed the pipeline conventions in the [contribution docs](https://github.com/nf-core/hadge/tree/master/.github/CONTRIBUTING.md) +- [ ] If you've added a new tool - have you followed the pipeline conventions in the [contribution docs](https://github.com/nf-core/hadge/tree/main/docs/CONTRIBUTING.md) - [ ] If necessary, also make a PR on the nf-core/hadge _branch_ on the [nf-core/test-datasets](https://github.com/nf-core/test-datasets) repository. - [ ] Make sure your code lints (`nf-core pipelines lint`). - [ ] Ensure the test suite passes (`nextflow run . -profile test,docker --outdir `). diff --git a/.github/actions/get-shards/action.yml b/.github/actions/get-shards/action.yml index 34085279..e2833ee9 100644 --- a/.github/actions/get-shards/action.yml +++ b/.github/actions/get-shards/action.yml @@ -21,7 +21,7 @@ runs: using: "composite" steps: - name: Install nf-test - uses: nf-core/setup-nf-test@v1 + uses: nf-core/setup-nf-test@4069fbbaabe94c08faba4ad261bfa88225ba133f # v2 with: version: ${{ env.NFT_VER }} - name: Get number of shards diff --git a/.github/actions/nf-test/action.yml b/.github/actions/nf-test/action.yml index 3b9724c7..ad686e8e 100644 --- a/.github/actions/nf-test/action.yml +++ b/.github/actions/nf-test/action.yml @@ -20,24 +20,24 @@ runs: using: "composite" steps: - name: Setup Nextflow - uses: nf-core/setup-nextflow@v2 + uses: nf-core/setup-nextflow@b4ec1bc7c16a94435159de94a05253542fddf6ef # v3 with: version: "${{ env.NXF_VERSION }}" - name: Set up Python - uses: actions/setup-python@e797f83bcb11b83ae66e0230d6156d7c80228e7c # v6 + uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6 with: python-version: "3.14" - name: Install nf-test - uses: nf-core/setup-nf-test@v1 + uses: nf-core/setup-nf-test@4069fbbaabe94c08faba4ad261bfa88225ba133f # v2 with: version: "${{ env.NFT_VER }}" install-pdiff: true - name: Setup apptainer if: contains(inputs.profile, 'singularity') - uses: eWaterCycle/setup-apptainer@main + uses: eWaterCycle/setup-apptainer@3f706d898c9db585b1d741b4692e66755f3a1b40 # v2 - name: Set up Singularity if: contains(inputs.profile, 'singularity') @@ -48,7 +48,7 @@ runs: - name: Conda setup if: contains(inputs.profile, 'conda') - uses: conda-incubator/setup-miniconda@505e6394dae86d6a5c7fbb6e3fb8938e3e863830 # v3 + uses: conda-incubator/setup-miniconda@8ee1f361103df19b6f8c8655fd3967a8ecb162d5 # v4 with: auto-update-conda: true conda-solver: libmamba diff --git a/.github/workflows/awsfulltest.yml b/.github/workflows/awsfulltest.yml index ce088dcb..b277fb75 100644 --- a/.github/workflows/awsfulltest.yml +++ b/.github/workflows/awsfulltest.yml @@ -23,7 +23,7 @@ jobs: echo "revision=${{ (github.event_name == 'workflow_dispatch' || github.event_name == 'release') && github.sha || 'dev' }}" >> "$GITHUB_OUTPUT" - name: Launch workflow via Seqera Platform - uses: seqeralabs/action-tower-launch@v2 + uses: seqeralabs/action-tower-launch@51565b514bff1827cf34620de25d0055759f1fc9 # v2 # TODO nf-core: You can customise AWS full pipeline tests as required # Add full size test data (but still relatively small datasets for few samples) # on the `test_full.config` test runs with only one set of parameters @@ -33,14 +33,33 @@ jobs: compute_env: ${{ vars.TOWER_COMPUTE_ENV }} revision: ${{ steps.revision.outputs.revision }} workdir: s3://${{ vars.AWS_S3_BUCKET }}/work/hadge/work-${{ steps.revision.outputs.revision }} + nextflow_config: | + plugins { + id 'nf-slack@0.5.0' + } + slack { + enabled = true + bot { + token = '${{ secrets.NFSLACK_BOT_TOKEN }}' + channel = 'hadge' + } + onStart { + enabled = false + } + onComplete { + message = ':white_check_mark: *hadge/test_full* completed successfully! :tada:' + } + onError { + message = ':x: *hadge/test_full* failed :crying_cat_face:' + } + } parameters: | { - "hook_url": "${{ secrets.MEGATESTS_ALERTS_SLACK_HOOK_URL }}", "outdir": "s3://${{ vars.AWS_S3_BUCKET }}/hadge/results-${{ steps.revision.outputs.revision }}" } profiles: test_full - - uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5 + - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7 with: name: Seqera Platform debug log file path: | diff --git a/.github/workflows/awstest.yml b/.github/workflows/awstest.yml index 5e50ddc1..1b06d8fb 100644 --- a/.github/workflows/awstest.yml +++ b/.github/workflows/awstest.yml @@ -12,7 +12,7 @@ jobs: steps: # Launch workflow using Seqera Platform CLI tool action - name: Launch workflow via Seqera Platform - uses: seqeralabs/action-tower-launch@v2 + uses: seqeralabs/action-tower-launch@51565b514bff1827cf34620de25d0055759f1fc9 # v2 with: workspace_id: ${{ vars.TOWER_WORKSPACE_ID }} access_token: ${{ secrets.TOWER_ACCESS_TOKEN }} @@ -25,7 +25,7 @@ jobs: } profiles: test - - uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5 + - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7 with: name: Seqera Platform debug log file path: | diff --git a/.github/workflows/branch.yml b/.github/workflows/branch.yml index 41122fe6..4dcd0e56 100644 --- a/.github/workflows/branch.yml +++ b/.github/workflows/branch.yml @@ -21,7 +21,7 @@ jobs: # NOTE - this doesn't currently work if the PR is coming from a fork, due to limitations in GitHub actions secrets - name: Post PR comment if: failure() - uses: mshick/add-pr-comment@b8f338c590a895d50bcbfa6c5859251edc8952fc # v2 + uses: mshick/add-pr-comment@8e4927817251f1ff60c001f04568532b38e0b4a0 # v3 with: message: | ## This PR is against the `${{github.event.pull_request.base.ref}}` branch :x: diff --git a/.github/workflows/clean-up.yml b/.github/workflows/clean-up.yml index 6adb0fff..172de6f3 100644 --- a/.github/workflows/clean-up.yml +++ b/.github/workflows/clean-up.yml @@ -10,7 +10,7 @@ jobs: issues: write pull-requests: write steps: - - uses: actions/stale@5f858e3efba33a5ca4407a664cc011ad407f2008 # v10 + - uses: actions/stale@b5d41d4e1d5dceea10e7104786b73624c18a190f # v10 with: stale-issue-message: "This issue has been tagged as awaiting-changes or awaiting-feedback by an nf-core contributor. Remove stale label or add a comment otherwise this issue will be closed in 20 days." stale-pr-message: "This PR has been tagged as awaiting-changes or awaiting-feedback by an nf-core contributor. Remove stale label or add a comment if it is still useful." diff --git a/.github/workflows/download_pipeline.yml b/.github/workflows/download_pipeline.yml index 45884ff9..a7bf4fc2 100644 --- a/.github/workflows/download_pipeline.yml +++ b/.github/workflows/download_pipeline.yml @@ -38,13 +38,16 @@ jobs: runs-on: ubuntu-latest needs: configure steps: + - name: Check out pipeline code + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 + - name: Install Nextflow - uses: nf-core/setup-nextflow@v2 + uses: nf-core/setup-nextflow@b4ec1bc7c16a94435159de94a05253542fddf6ef # v3 - name: Disk space cleanup uses: jlumbroso/free-disk-space@54081f138730dfa15788a46383842cd2f914a1be # v1.3.1 - - uses: actions/setup-python@e797f83bcb11b83ae66e0230d6156d7c80228e7c # v6 + - uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6 with: python-version: "3.14" architecture: "x64" @@ -54,10 +57,15 @@ jobs: with: apptainer-version: 1.3.4 + - name: Read .nf-core.yml + id: read_yml + run: | + echo "nf_core_version=$(yq '.nf_core_version' ${{ github.workspace }}/.nf-core.yml)" >> "$GITHUB_OUTPUT" + - name: Install dependencies run: | python -m pip install --upgrade pip - pip install git+https://github.com/nf-core/tools.git + pip install nf-core==${{ steps.read_yml.outputs['nf_core_version'] }} - name: Make a cache directory for the container images run: | @@ -127,7 +135,7 @@ jobs: fi - name: Upload Nextflow logfile for debugging purposes - uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5 + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7 with: name: nextflow_logfile.txt path: .nextflow.log* diff --git a/.github/workflows/fix_linting.yml b/.github/workflows/fix_linting.yml index 038fdc92..44e4ae32 100644 --- a/.github/workflows/fix_linting.yml +++ b/.github/workflows/fix_linting.yml @@ -13,7 +13,7 @@ jobs: runs-on: ubuntu-latest steps: # Use the @nf-core-bot token to check out so we can push later - - uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 with: token: ${{ secrets.nf_core_bot_auth_token }} @@ -31,22 +31,18 @@ jobs: env: GITHUB_TOKEN: ${{ secrets.nf_core_bot_auth_token }} - # Install and run pre-commit - - uses: actions/setup-python@e797f83bcb11b83ae66e0230d6156d7c80228e7c # v6 - with: - python-version: "3.14" - - - name: Install pre-commit - run: pip install pre-commit + - name: Install Nextflow + uses: nf-core/setup-nextflow@b4ec1bc7c16a94435159de94a05253542fddf6ef # v3 - - name: Run pre-commit - id: pre-commit - run: pre-commit run --all-files + # Install and run prek + - name: Run prek + id: prek + uses: j178/prek-action@6ad80277337ad479fe43bd70701c3f7f8aa74db3 # v2 continue-on-error: true # indication that the linting has finished - name: react if linting finished succesfully - if: steps.pre-commit.outcome == 'success' + if: steps.prek.outcome == 'success' uses: peter-evans/create-or-update-comment@e8674b075228eee787fea43ef493e45ece1004c9 # v5 with: comment-id: ${{ github.event.comment.id }} @@ -54,7 +50,7 @@ jobs: - name: Commit & push changes id: commit-and-push - if: steps.pre-commit.outcome == 'failure' + if: steps.prek.outcome == 'failure' run: | git config user.email "core@nf-co.re" git config user.name "nf-core-bot" diff --git a/.github/workflows/linting.yml b/.github/workflows/linting.yml index 7a527a34..8738ffc9 100644 --- a/.github/workflows/linting.yml +++ b/.github/workflows/linting.yml @@ -11,33 +11,31 @@ jobs: pre-commit: runs-on: ubuntu-latest steps: - - uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 - - name: Set up Python 3.14 - uses: actions/setup-python@e797f83bcb11b83ae66e0230d6156d7c80228e7c # v6 - with: - python-version: "3.14" - - - name: Install pre-commit - run: pip install pre-commit + - name: Install Nextflow + uses: nf-core/setup-nextflow@b4ec1bc7c16a94435159de94a05253542fddf6ef # v3 - - name: Run pre-commit - run: pre-commit run --all-files + - name: Run prek + uses: j178/prek-action@6ad80277337ad479fe43bd70701c3f7f8aa74db3 # v2 nf-core: runs-on: ubuntu-latest steps: - name: Check out pipeline code - uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 - name: Install Nextflow - uses: nf-core/setup-nextflow@v2 + uses: nf-core/setup-nextflow@b4ec1bc7c16a94435159de94a05253542fddf6ef # v3 - - uses: actions/setup-python@e797f83bcb11b83ae66e0230d6156d7c80228e7c # v6 + - uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6 with: python-version: "3.14" architecture: "x64" + - name: Setup uv + uses: astral-sh/setup-uv@08807647e7069bb48b6ef5acd8ec9567f424441b # v8.1.0 + - name: read .nf-core.yml uses: pietrobolcato/action-read-yaml@9f13718d61111b69f30ab4ac683e67a56d254e1d # 1.1.0 id: read_yml @@ -45,12 +43,10 @@ jobs: config: ${{ github.workspace }}/.nf-core.yml - name: Install dependencies - run: | - python -m pip install --upgrade pip - pip install nf-core==${{ steps.read_yml.outputs['nf_core_version'] }} + run: uv tool install nf-core==${{ steps.read_yml.outputs['nf_core_version'] }} - name: Run nf-core pipelines lint - if: ${{ github.base_ref != 'master' }} + if: ${{ github.base_ref != 'master' || github.base_ref != 'main' }} env: GITHUB_COMMENTS_URL: ${{ github.event.pull_request.comments_url }} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} @@ -58,7 +54,7 @@ jobs: run: nf-core -l lint_log.txt pipelines lint --dir ${GITHUB_WORKSPACE} --markdown lint_results.md - name: Run nf-core pipelines lint --release - if: ${{ github.base_ref == 'master' }} + if: ${{ github.base_ref == 'master' || github.base_ref == 'main' }} env: GITHUB_COMMENTS_URL: ${{ github.event.pull_request.comments_url }} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} @@ -71,7 +67,7 @@ jobs: - name: Upload linting log file artifact if: ${{ always() }} - uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5 + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7 with: name: linting-logs path: | diff --git a/.github/workflows/linting_comment.yml b/.github/workflows/linting_comment.yml index e6e9bc26..5b0c24f7 100644 --- a/.github/workflows/linting_comment.yml +++ b/.github/workflows/linting_comment.yml @@ -11,7 +11,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Download lint results - uses: dawidd6/action-download-artifact@ac66b43f0e6a346234dd65d4d0c8fbb31cb316e5 # v11 + uses: dawidd6/action-download-artifact@b6e2e70617bc3265edd6dab6c906732b2f1ae151 # v21 with: workflow: linting.yml workflow_conclusion: completed @@ -21,7 +21,7 @@ jobs: run: echo "pr_number=$(cat linting-logs/PR_number.txt)" >> $GITHUB_OUTPUT - name: Post PR comment - uses: marocchino/sticky-pull-request-comment@773744901bac0e8cbb5a0dc842800d45e9b2b405 # v2 + uses: marocchino/sticky-pull-request-comment@70d2764d1a7d5d9560b100cbea0077fc8f633987 # v3 with: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} number: ${{ steps.pr_number.outputs.pr_number }} diff --git a/.github/workflows/nf-test.yml b/.github/workflows/nf-test.yml index ff6ef634..5e5b3e01 100644 --- a/.github/workflows/nf-test.yml +++ b/.github/workflows/nf-test.yml @@ -18,7 +18,7 @@ concurrency: env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - NFT_VER: "0.9.3" + NFT_VER: "0.9.4" NFT_WORKDIR: "~" NXF_ANSI_LOG: false NXF_SINGULARITY_CACHEDIR: ${{ github.workspace }}/.singularity @@ -40,7 +40,7 @@ jobs: rm -rf ./* || true rm -rf ./.??* || true ls -la ./ - - uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 with: fetch-depth: 0 @@ -79,14 +79,14 @@ jobs: - isMain: false profile: "singularity" NXF_VER: - - "25.04.0" + - "25.10.4" - "latest-everything" env: NXF_ANSI_LOG: false TOTAL_SHARDS: ${{ needs.nf-test-changes.outputs.total_shards }} steps: - - uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 with: fetch-depth: 0 diff --git a/.github/workflows/release-announcements.yml b/.github/workflows/release-announcements.yml index 431d3d44..78d5dbe0 100644 --- a/.github/workflows/release-announcements.yml +++ b/.github/workflows/release-announcements.yml @@ -18,7 +18,7 @@ jobs: id: get_description run: | echo "description=$(curl -s https://nf-co.re/pipelines.json | jq -r '.remote_workflows[] | select(.full_name == "${{ github.repository }}") | .description')" >> $GITHUB_OUTPUT - - uses: rzr/fediverse-action@master + - uses: rzr/fediverse-action@563159eb8d45f70ab6aaba36ed55cd037e51f441 # master with: access-token: ${{ secrets.MASTODON_ACCESS_TOKEN }} host: "mstdn.science" # custom host if not "mastodon.social" (default) @@ -34,7 +34,7 @@ jobs: bsky-post: runs-on: ubuntu-latest steps: - - uses: zentered/bluesky-post-action@6461056ea355ea43b977e149f7bf76aaa572e5e8 # v0.3.0 + - uses: zentered/bluesky-post-action@5a91cc2ad10a304a4e96c16182dbe4918710bcf6 # v0.4.0 with: post: | Pipeline release! ${{ github.repository }} v${{ github.event.release.tag_name }} - ${{ github.event.release.name }}! diff --git a/.github/workflows/template-version-comment.yml b/.github/workflows/template-version-comment.yml index e8560fc7..ea30827e 100644 --- a/.github/workflows/template-version-comment.yml +++ b/.github/workflows/template-version-comment.yml @@ -9,7 +9,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Check out pipeline code - uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 with: ref: ${{ github.event.pull_request.head.sha }} @@ -29,7 +29,7 @@ jobs: run: echo "OUTPUT=$(pip list --outdated | grep nf-core)" >> ${GITHUB_ENV} - name: Post nf-core template version comment - uses: mshick/add-pr-comment@b8f338c590a895d50bcbfa6c5859251edc8952fc # v2 + uses: mshick/add-pr-comment@8e4927817251f1ff60c001f04568532b38e0b4a0 # v3 if: | contains(env.OUTPUT, 'nf-core') with: @@ -42,5 +42,5 @@ jobs: > Your pipeline is using an old version of the nf-core template: ${{ steps.read_yml.outputs['nf_core_version'] }}. > Please update your pipeline to the latest version. > - > For more documentation on how to update your pipeline, please see the [nf-core documentation](https://github.com/nf-core/tools?tab=readme-ov-file#sync-a-pipeline-with-the-template) and [Synchronisation documentation](https://nf-co.re/docs/contributing/sync). + > For more documentation on how to update your pipeline, please see the [Synchronisation documentation](https://nf-co.re/docs/developing/template-syncs/overview). # diff --git a/.gitignore b/.gitignore index 23b0c7de..5516a066 100644 --- a/.gitignore +++ b/.gitignore @@ -7,4 +7,5 @@ testing/ testing* *.pyc null/ +.lineage/ .nf-test* diff --git a/.nf-core.yml b/.nf-core.yml index 3c5b1920..980280a9 100644 --- a/.nf-core.yml +++ b/.nf-core.yml @@ -1,7 +1,7 @@ lint: files_unchanged: - .github/PULL_REQUEST_TEMPLATE.md -nf_core_version: 3.5.2 +nf_core_version: 4.0.2 repository_type: pipeline template: author: Fabiola Curion diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index d06777a8..f51e1a28 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -4,7 +4,7 @@ repos: hooks: - id: prettier additional_dependencies: - - prettier@3.6.2 + - prettier@3.8.3 - repo: https://github.com/pre-commit/pre-commit-hooks rev: v6.0.0 hooks: @@ -13,15 +13,21 @@ repos: exclude: | (?x)^( .*ro-crate-metadata.json$| - modules/nf-core/.*| - subworkflows/nf-core/.*| + modules/(?!local/).*| + subworkflows/(?!local/).*| .*\.snap$ )$ - id: end-of-file-fixer exclude: | (?x)^( .*ro-crate-metadata.json$| - modules/nf-core/.*| - subworkflows/nf-core/.*| + modules/(?!local/).*| + subworkflows/(?!local/).*| .*\.snap$ )$ + - repo: https://github.com/seqeralabs/nf-lint-pre-commit + rev: v0.3.0 + hooks: + - id: nextflow-lint + files: '\.nf$|nextflow\.config$' + args: ["-output", "json"] diff --git a/.prettierignore b/.prettierignore index dd749d43..63cde500 100644 --- a/.prettierignore +++ b/.prettierignore @@ -1,6 +1,4 @@ email_template.html -adaptivecard.json -slackreport.json .nextflow* work/ data/ diff --git a/README.md b/README.md index 6ceec537..15b9906b 100644 --- a/README.md +++ b/README.md @@ -10,8 +10,8 @@ [![GitHub Actions Linting Status](https://github.com/nf-core/hadge/actions/workflows/linting.yml/badge.svg)](https://github.com/nf-core/hadge/actions/workflows/linting.yml)[![AWS CI](https://img.shields.io/badge/CI%20tests-full%20size-FF9900?labelColor=000000&logo=Amazon%20AWS)](https://nf-co.re/hadge/results)[![Cite with Zenodo](http://img.shields.io/badge/DOI-10.5281/zenodo.10634731-1073c8?labelColor=000000)](https://doi.org/10.5281/zenodo.10634731) [![nf-test](https://img.shields.io/badge/unit_tests-nf--test-337ab7.svg)](https://www.nf-test.com) -[![Nextflow](https://img.shields.io/badge/version-%E2%89%A525.04.0-green?style=flat&logo=nextflow&logoColor=white&color=%230DC09D&link=https%3A%2F%2Fnextflow.io)](https://www.nextflow.io/) -[![nf-core template version](https://img.shields.io/badge/nf--core_template-3.5.2-green?style=flat&logo=nfcore&logoColor=white&color=%2324B064&link=https%3A%2F%2Fnf-co.re)](https://github.com/nf-core/tools/releases/tag/3.5.2) +[![Nextflow](https://img.shields.io/badge/version-%E2%89%A525.10.4-green?style=flat&logo=nextflow&logoColor=white&color=%230DC09D&link=https%3A%2F%2Fnextflow.io)](https://www.nextflow.io/) +[![nf-core template version](https://img.shields.io/badge/nf--core_template-4.0.2-green?style=flat&logo=nfcore&logoColor=white&color=%2324B064&link=https%3A%2F%2Fnf-co.re)](https://github.com/nf-core/tools/releases/tag/4.0.2) [![run with conda](http://img.shields.io/badge/run%20with-conda-3EB049?labelColor=000000&logo=anaconda)](https://docs.conda.io/en/latest/) [![run with docker](https://img.shields.io/badge/run%20with-docker-0db7ed?labelColor=000000&logo=docker)](https://www.docker.com/) [![run with singularity](https://img.shields.io/badge/run%20with-singularity-1d355c.svg?labelColor=000000)](https://sylabs.io/docs/) @@ -83,7 +83,7 @@ nextflow run nf-core/hadge \ ``` > [!WARNING] -> Please provide pipeline parameters via the CLI or Nextflow `-params-file` option. Custom config files including those provided by the `-c` Nextflow option can be used to provide any configuration _**except for parameters**_; see [docs](https://nf-co.re/docs/usage/getting_started/configuration#custom-configuration-files). +> Please provide pipeline parameters via the CLI or Nextflow `-params-file` option. Custom config files including those provided by the `-c` Nextflow option can be used to provide any configuration _**except for parameters**_; see [docs](https://nf-co.re/docs/running/run-pipelines#using-parameter-files). For more details and further functionality, please refer to the [usage documentation](https://nf-co.re/hadge/usage) and the [parameter documentation](https://nf-co.re/hadge/parameters). @@ -105,7 +105,7 @@ We thank the following people for rewriting the original pipeline within the nf- ## Contributions and Support -If you would like to contribute to this pipeline, please see the [contributing guidelines](.github/CONTRIBUTING.md). +If you would like to contribute to this pipeline, please see the [contributing guidelines](docs/CONTRIBUTING.md). For further information or help, don't hesitate to get in touch on the [Slack `#hadge` channel](https://nfcore.slack.com/channels/hadge) (you can join with [this invite](https://nf-co.re/join/slack)). diff --git a/assets/adaptivecard.json b/assets/adaptivecard.json deleted file mode 100644 index 89fa06bf..00000000 --- a/assets/adaptivecard.json +++ /dev/null @@ -1,67 +0,0 @@ -{ - "type": "message", - "attachments": [ - { - "contentType": "application/vnd.microsoft.card.adaptive", - "contentUrl": null, - "content": { - "\$schema": "http://adaptivecards.io/schemas/adaptive-card.json", - "msteams": { - "width": "Full" - }, - "type": "AdaptiveCard", - "version": "1.2", - "body": [ - { - "type": "TextBlock", - "size": "Large", - "weight": "Bolder", - "color": "<% if (success) { %>Good<% } else { %>Attention<%} %>", - "text": "nf-core/hadge v${version} - ${runName}", - "wrap": true - }, - { - "type": "TextBlock", - "spacing": "None", - "text": "Completed at ${dateComplete} (duration: ${duration})", - "isSubtle": true, - "wrap": true - }, - { - "type": "TextBlock", - "text": "<% if (success) { %>Pipeline completed successfully!<% } else { %>Pipeline completed with errors. The full error message was: ${errorReport}.<% } %>", - "wrap": true - }, - { - "type": "TextBlock", - "text": "The command used to launch the workflow was as follows:", - "wrap": true - }, - { - "type": "TextBlock", - "text": "${commandLine}", - "isSubtle": true, - "wrap": true - } - ], - "actions": [ - { - "type": "Action.ShowCard", - "title": "Pipeline Configuration", - "card": { - "type": "AdaptiveCard", - "\$schema": "http://adaptivecards.io/schemas/adaptive-card.json", - "body": [ - { - "type": "FactSet", - "facts": [<% out << summary.collect{ k,v -> "{\"title\": \"$k\", \"value\" : \"$v\"}"}.join(",\n") %> - ] - } - ] - } - } - ] - } - } - ] -} diff --git a/assets/slackreport.json b/assets/slackreport.json deleted file mode 100644 index 1ec0ba80..00000000 --- a/assets/slackreport.json +++ /dev/null @@ -1,34 +0,0 @@ -{ - "attachments": [ - { - "fallback": "Plain-text summary of the attachment.", - "color": "<% if (success) { %>good<% } else { %>danger<%} %>", - "author_name": "nf-core/hadge ${version} - ${runName}", - "author_icon": "https://www.nextflow.io/docs/latest/_static/favicon.ico", - "text": "<% if (success) { %>Pipeline completed successfully!<% } else { %>Pipeline completed with errors<% } %>", - "fields": [ - { - "title": "Command used to launch the workflow", - "value": "```${commandLine}```", - "short": false - } - <% - if (!success) { %> - , - { - "title": "Full error message", - "value": "```${errorReport}```", - "short": false - }, - { - "title": "Pipeline configuration", - "value": "<% out << summary.collect{ k,v -> k == "hook_url" ? "_${k}_: (_hidden_)" : ( ( v.class.toString().contains('Path') || ( v.class.toString().contains('String') && v.contains('/') ) ) ? "_${k}_: `${v}`" : (v.class.toString().contains('DateTime') ? ("_${k}_: " + v.format(java.time.format.DateTimeFormatter.ofLocalizedDateTime(java.time.format.FormatStyle.MEDIUM))) : "_${k}_: ${v}") ) }.join(",\n") %>", - "short": false - } - <% } - %> - ], - "footer": "Completed at <% out << dateComplete.format(java.time.format.DateTimeFormatter.ofLocalizedDateTime(java.time.format.FormatStyle.MEDIUM)) %> (duration: ${duration})" - } - ] -} diff --git a/conf/base.config b/conf/base.config index 89b614c4..696ede13 100644 --- a/conf/base.config +++ b/conf/base.config @@ -15,7 +15,7 @@ process { memory = { 6.GB * task.attempt } time = { 4.h * task.attempt } - errorStrategy = { task.exitStatus in ((130..145) + 104 + 175) ? 'retry' : 'finish' } + errorStrategy = { task.exitStatus in ((130..145) + 104 + (175..177)) ? 'retry' : 'finish' } maxRetries = 1 maxErrors = '-1' diff --git a/conf/containers_conda_lock_files_amd64.config b/conf/containers_conda_lock_files_amd64.config new file mode 100644 index 00000000..01cc5458 --- /dev/null +++ b/conf/containers_conda_lock_files_amd64.config @@ -0,0 +1 @@ +process { withName: 'MULTIQC' { container = 'modules/nf-core/multiqc/.conda-lock/linux_amd64-bd-c17fb751507e9dfc_1.txt' } } diff --git a/conf/containers_conda_lock_files_arm64.config b/conf/containers_conda_lock_files_arm64.config new file mode 100644 index 00000000..6864e0b1 --- /dev/null +++ b/conf/containers_conda_lock_files_arm64.config @@ -0,0 +1 @@ +process { withName: 'MULTIQC' { container = 'modules/nf-core/multiqc/.conda-lock/linux_arm64-bd-5c84a5000a226ab5_1.txt' } } diff --git a/conf/containers_docker_amd64.config b/conf/containers_docker_amd64.config new file mode 100644 index 00000000..ea18c3f9 --- /dev/null +++ b/conf/containers_docker_amd64.config @@ -0,0 +1 @@ +process { withName: 'MULTIQC' { container = 'community.wave.seqera.io/library/multiqc:1.35--c17fb751507e9dfc' } } diff --git a/conf/containers_docker_arm64.config b/conf/containers_docker_arm64.config new file mode 100644 index 00000000..369f743a --- /dev/null +++ b/conf/containers_docker_arm64.config @@ -0,0 +1 @@ +process { withName: 'MULTIQC' { container = 'community.wave.seqera.io/library/multiqc:1.35--5c84a5000a226ab5' } } diff --git a/conf/containers_singularity_https_amd64.config b/conf/containers_singularity_https_amd64.config new file mode 100644 index 00000000..932fc7c0 --- /dev/null +++ b/conf/containers_singularity_https_amd64.config @@ -0,0 +1 @@ +process { withName: 'MULTIQC' { container = 'https://community-cr-prod.seqera.io/docker/registry/v2/blobs/sha256/c8/c8e346f4f6080eadf1253505e6ff09ef004454fc18e8d672006fd7b222cc412e/data' } } diff --git a/conf/containers_singularity_https_arm64.config b/conf/containers_singularity_https_arm64.config new file mode 100644 index 00000000..4f795323 --- /dev/null +++ b/conf/containers_singularity_https_arm64.config @@ -0,0 +1 @@ +process { withName: 'MULTIQC' { container = 'https://community-cr-prod.seqera.io/docker/registry/v2/blobs/sha256/e4/e48aa28aebc881254a499b24c3e1ce77b8df1b85a5432699ed6f72eb17ac7fb5/data' } } diff --git a/conf/containers_singularity_oras_amd64.config b/conf/containers_singularity_oras_amd64.config new file mode 100644 index 00000000..2fa065b3 --- /dev/null +++ b/conf/containers_singularity_oras_amd64.config @@ -0,0 +1 @@ +process { withName: 'MULTIQC' { container = 'oras://community.wave.seqera.io/library/multiqc:1.35--c680f2aea25ccec2' } } diff --git a/conf/containers_singularity_oras_arm64.config b/conf/containers_singularity_oras_arm64.config new file mode 100644 index 00000000..84f07924 --- /dev/null +++ b/conf/containers_singularity_oras_arm64.config @@ -0,0 +1 @@ +process { withName: 'MULTIQC' { container = 'oras://community.wave.seqera.io/library/multiqc:1.35--c0468833d65b2f81' } } diff --git a/docs/CONTRIBUTING.md b/docs/CONTRIBUTING.md new file mode 100644 index 00000000..6071ae9d --- /dev/null +++ b/docs/CONTRIBUTING.md @@ -0,0 +1,185 @@ +--- +title: Contributing +markdownPlugin: checklist +--- + +# `nf-core/hadge`: Contributing guidelines + +Hi there! +Thanks for taking an interest in improving nf-core/hadge. + +This page describes the recommended nf-core way to contribute to both nf-core/hadge and nf-core pipelines in general, including: + +- [General contribution guidelines](#general-contribution-guidelines): common procedures or guides across all nf-core pipelines. +- [Pipeline-specific contribution guidelines](#pipeline-specific-contribution-guidelines): procedures or guides specific to the development conventions of nf-core/hadge. + +> [!NOTE] +> If you need help using or modifying nf-core/hadge, ask on the nf-core Slack [#hadge](https://nfcore.slack.com/channels/hadge) channel ([join our Slack here](https://nf-co.re/join/slack)). + +## General contribution guidelines + +### Contribution quick start + +To contribute code to any nf-core pipeline: + +- [ ] Ensure you have Nextflow, nf-core tools, and nf-test installed. See the [nf-core/tools repository](https://github.com/nf-core/tools) for instructions. +- [ ] Check whether a GitHub [issue](https://github.com/nf-core/hadge/issues) about your idea already exists. If an issue does not exist, create one so that others are aware you are working on it. +- [ ] [Fork](https://help.github.com/en/github/getting-started-with-github/fork-a-repo) the [nf-core/hadge repository](https://github.com/nf-core/hadge) to your GitHub account. +- [ ] Create a branch on your forked repository and make your changes following [pipeline conventions](#pipeline-contribution-conventions) (if applicable). +- [ ] To fix major bugs, name your branch `patch` and follow the [patch release](#patch-release) process. +- [ ] Update relevant documentation within the `docs/` folder, use nf-core/tools to update `nextflow_schema.json`, and update `CITATIONS.md`. +- [ ] Run and/or update tests. See [Testing](#testing) for more information. +- [ ] [Lint](#lint-tests) your code with nf-core/tools. +- [ ] Submit a pull request (PR) against the `dev` branch and request a review. + +If you are not used to this workflow with Git, see the [GitHub documentation](https://help.github.com/en/github/collaborating-with-issues-and-pull-requests) or [Git resources](https://try.github.io/) for more information. + +## Use of AI and LLMs + +The nf-core stance on the use of AI and LLMs is that humans are still ultimately responsible for their submitted code, regardless of the tools they use. + +If you’re using AI tools, try to stick by these guidelines: + +- Keep PRs as small and focussed as possible +- Avoid any unnecessary changes, such as moving or refactoring code (unless that is the explicit intention of the PR) +- Review all generated code yourself before opening a PR, and ensure that you understand it +- Engage with the community review process and expect to make revisions + +For more detail, see the the [blog post](https://nf-co.re/blog/2026/statement-on-ai) for a statement from the nf-core/core team. + +### Getting help + +For further information and help, see the [nf-core/hadge documentation](https://nf-co.re/hadge/usage) or ask on the nf-core [#hadge](https://nfcore.slack.com/channels/hadge) Slack channel ([join our Slack here](https://nf-co.re/join/slack)). + +### GitHub Codespaces + +You can contribute to nf-core/hadge without installing a local development environment on your machine by using [GitHub Codespaces](https://github.com/codespaces). + +[GitHub Codespaces](https://github.com/codespaces) is an online developer environment that runs in your browser, complete with VS Code and a terminal. +Most nf-core repositories include a devcontainer configuration, which creates a GitHub Codespaces environment specifically for Nextflow development. +The environment includes pre-installed nf-core tools, Nextflow, and a few other helpful utilities via a Docker container. + +To get started, open the repository in [Codespaces](https://github.com/nf-core/hadge/codespaces). + +### Testing + +Once you have made your changes, run the pipeline with nf-test to test them locally. +For additional information, use the `--verbose` flag to view the Nextflow console log output. + +```bash +nf-test test --tag test --profile +docker --verbose +``` + +If you have added new functionality, ensure you update the test assertions in the `.nf.test` files in the `tests/` directory. +Update the snapshots with the following command: + +```bash +nf-test test --tag test --profile +docker --verbose --update-snapshots +``` + +When you create a pull request with changes, GitHub Actions will run automatic tests. +Pull requests are typically reviewed when these tests are passing. + +Two types of tests are typically run: + +#### Lint tests + +nf-core has a [set of guidelines](https://nf-co.re/docs/specifications/overview) which all pipelines must follow. +To enforce these, run linting with nf-core/tools: + +```bash +nf-core pipelines lint +``` + +If you encounter failures or warnings, follow the linked documentation printed to screen. +For more information about linting tests, see [nf-core/tools API documentation](https://nf-co.re/docs/nf-core-tools/api_reference/latest/pipeline_lint_tests/actions_awsfulltest). + +#### Pipeline tests + +Each nf-core pipeline should be set up with a minimal set of test data. +GitHub Actions runs the pipeline on this data to ensure it runs through and exits successfully. +If there are any failures then the automated tests fail. +These tests are run with the latest available version of Nextflow and the minimum required version specified in the pipeline code. + +### Patch release + +> [!WARNING] +> Only in the unlikely event of a release that contains a critical bug. + +- [ ] Create a new branch `patch` on your fork based on `upstream/main` or `upstream/master`. +- [ ] Fix the bug and use nf-core/tools to bump the version to the next semantic version, for example, `1.2.3` → `1.2.4`. +- [ ] Open a Pull Request from `patch` directly to `main`/`master` with the changes. + +### Pipeline contribution conventions + +nf-core semi-standardises how you write code and other contributions to make the nf-core/hadge code and processing logic more understandable for new contributors and to ensure quality. + +#### Add a new pipeline step + +To contribute a new step to the pipeline, follow the general nf-core coding procedure. +Please also refer to the [pipeline-specific contribution guidelines](#pipeline-specific-contribution-guidelines): + +- [ ] Define the corresponding [input channel](#channel-naming-schemes) into your new process from the expected previous process channel. +- [ ] Install a module with nf-core/tools, or write a local module (see [default processes resource requirements](#default-processes-resource-requirements)), and add it to the target `.nf`. +- [ ] Define the output channel if needed. Mix the version output channel into `ch_versions` and relevant files into `ch_multiqc`. +- [ ] Add new or updated parameters to `nextflow.config` with a [default value](#default-parameter-values). +- [ ] Add new or updated parameters and relevant help text to `nextflow_schema.json` with [nf-core/tools](#default-parameter-values). +- [ ] Add validation for relevant parameters to the pipeline utilisation section of `utils_nfcore_\_pipeline/main.nf` subworkflow. +- [ ] Perform local tests to validate that the new code works as expected. + - [ ] If applicable, add a new test in the `tests` directory. +- [ ] Update `usage.md`, `output.md`, and `citation.md` as appropriate. +- [ ] [Lint](lint) the code with nf-core/tools. +- [ ] Update any diagrams or pipeline images as necessary. +- [ ] Update MultiQC config `assets/multiqc_config.yml` so relevant suffixes, file name cleanup, and module plots are in the appropriate order. +- [ ] If applicable, create a [MultiQC](https://seqera.io/multiqc/) module. +- [ ] Add a description of the output files and, if relevant, images from the MultiQC report to `docs/output.md`. + +To update the minimum required Nextflow version, see the [Nextflow version bumping](#nextflow-version-bumping) section below. For more information about pipeline contributions, see [pipeline-specific contribution guidelines](#pipeline-specific-contribution-guidelines). + +#### Channel naming schemes + +Use the following naming schemes for channels to make the channel flow easier to understand: + +- Initial process channel: `ch_output_from_` +- Intermediate and terminal channels: `ch__for_` + +#### Default parameter values + +Parameters should be initialised and defined with default values within the `params` scope in `nextflow.config`. +They should also be documented in the pipeline JSON schema. + +To update `nextflow_schema.json`, run: + +```bash +nf-core pipelines schema build +``` + +The schema builder interface that loads in your browser should automatically update the defaults in the parameter documentation. + +#### Default processes resource requirements + +If you write a local module, specify a default set of resource requirements for the process. + +Sensible defaults for process resource requirements (CPUs, memory, time) should be defined in `conf/base.config`. +Specify these with generic `withLabel:` selectors, so they can be shared across multiple processes and steps of the pipeline. + +nf-core provides a set of standard labels that you should follow where possible, as seen in the [nf-core pipeline template](https://github.com/nf-core/tools/blob/main/nf_core/pipeline-template/conf/base.config). +These labels define resource defaults for single-core processes, modules that require a GPU, and different levels of multi-core configurations with increasing memory requirements. + +Values assigned within these labels can be dynamically passed to a tool using the the `${task.cpus}` and `${task.memory}` Nextflow variables in the `script:` block of a module (see an example in the [modules repository](https://github.com/nf-core/modules/blob/bd1b6a40f55933d94b8c9ca94ec8c1ea0eaf4b82/modules/nf-core/samtools/bam2fq/main.nf#L30)). + +#### Nextflow version bumping + +If you use a new feature from core Nextflow, bump the minimum required Nextflow version in the pipeline with: + +```bash +nf-core pipelines bump-version --nextflow . +``` + +#### Images and figures guidelines + +If you update images or graphics, follow the nf-core [style guidelines](https://nf-co.re/docs/community/brand/workflow-schematics). + +## Pipeline specific contribution guidelines + + diff --git a/docs/usage.md b/docs/usage.md index f90c78a8..d65ea4c6 100644 --- a/docs/usage.md +++ b/docs/usage.md @@ -224,7 +224,7 @@ If you wish to repeatedly use the same parameters for multiple runs, rather than Pipeline settings can be provided in a `yaml` or `json` file via `-params-file `. > [!WARNING] -> Do not use `-c ` to specify parameters as this will result in errors. Custom config files specified with `-c` must only be used for [tuning process resource specifications](https://nf-co.re/docs/usage/configuration#tuning-workflow-resources), other infrastructural tweaks (such as output directories), or module arguments (args). +> Do not use `-c ` to specify parameters as this will result in errors. Custom config files specified with `-c` must only be used for [tuning process resource specifications](https://nf-co.re/docs/running/run-pipelines#configuring-pipelines), other infrastructural tweaks (such as output directories), or module arguments (args). The above pipeline run specified with a params file in yaml format: @@ -321,19 +321,19 @@ Specify the path to a specific config file (this is a core Nextflow command). Se Whilst the default requirements set within the pipeline will hopefully work for most people and with most input data, you may find that you want to customise the compute resources that the pipeline requests. Each step in the pipeline has a default set of requirements for number of CPUs, memory and time. For most of the pipeline steps, if the job exits with any of the error codes specified [here](https://github.com/nf-core/rnaseq/blob/4c27ef5610c87db00c3c5a3eed10b1d161abf575/conf/base.config#L18) it will automatically be resubmitted with higher resources request (2 x original, then 3 x original). If it still fails after the third attempt then the pipeline execution is stopped. -To change the resource requests, please see the [max resources](https://nf-co.re/docs/usage/configuration#max-resources) and [tuning workflow resources](https://nf-co.re/docs/usage/configuration#tuning-workflow-resources) section of the nf-core website. +To change the resource requests, please see the [max resources](https://nf-co.re/docs/running/configuration/nextflow-for-your-system#set-max-resources) and [customise process resources](https://nf-co.re/docs/running/configuration/nextflow-for-your-system#customize-process-resources) section of the nf-core website. ### Custom Containers In some cases, you may wish to change the container or conda environment used by a pipeline steps for a particular tool. By default, nf-core pipelines use containers and software from the [biocontainers](https://biocontainers.pro/) or [bioconda](https://bioconda.github.io/) projects. However, in some cases the pipeline specified version maybe out of date. -To use a different container from the default container or conda environment specified in a pipeline, please see the [updating tool versions](https://nf-co.re/docs/usage/configuration#updating-tool-versions) section of the nf-core website. +To use a different container from the default container or conda environment specified in a pipeline, please see the [updating tool versions](https://nf-co.re/docs/running/configuration/nextflow-for-your-system#update-tool-versions) section of the nf-core website. ### Custom Tool Arguments A pipeline might not always support every possible argument or option of a particular tool used in pipeline. Fortunately, nf-core pipelines provide some freedom to users to insert additional parameters that the pipeline does not include by default. -To learn how to provide additional arguments to a particular tool of the pipeline, please see the [customising tool arguments](https://nf-co.re/docs/usage/configuration#customising-tool-arguments) section of the nf-core website. +To learn how to provide additional arguments to a particular tool of the pipeline, please see the [customising tool arguments](https://nf-co.re/docs/running/configuration/nextflow-for-your-system#modifying-tool-arguments) section of the nf-core website. ### nf-core/configs diff --git a/main.nf b/main.nf index 2c8ef678..228b45f0 100644 --- a/main.nf +++ b/main.nf @@ -50,7 +50,11 @@ workflow NFCORE_HADGE { // HADGE ( samplesheet, - fasta + fasta, + params.multiqc_config, + params.multiqc_logo, + params.multiqc_methods_description, + params.outdir ) emit: multiqc_report = HADGE.out.multiqc_report // channel: /path/to/multiqc_report.html @@ -73,7 +77,6 @@ workflow { params.monochrome_logs, args, params.outdir, - params.input, params.help, params.help_full, params.show_hidden @@ -95,7 +98,6 @@ workflow { params.plaintext_email, params.outdir, params.monochrome_logs, - params.hook_url, NFCORE_HADGE.out.multiqc_report ) } diff --git a/modules.json b/modules.json index 1a3e926f..883a91a6 100644 --- a/modules.json +++ b/modules.json @@ -46,7 +46,7 @@ }, "multiqc": { "branch": "master", - "git_sha": "575e1a4b51a9bad7a8cd1316a88fb85684ef7c7b", + "git_sha": "98403d15b0e50edae1f3fec5eae5e24982f1fade", "installed_by": ["modules"] }, "multiseqdemux": { @@ -123,12 +123,12 @@ }, "utils_nfcore_pipeline": { "branch": "master", - "git_sha": "271e7fc14eb1320364416d996fb077421f3faed2", + "git_sha": "a3fb7351b1fdb2b1de282b765816bbea190e86a8", "installed_by": ["subworkflows"] }, "utils_nfschema_plugin": { "branch": "master", - "git_sha": "4b406a74dc0449c0401ed87d5bfff4252fd277fd", + "git_sha": "fdc08b8b1ae74f56686ce21f7ea11ad11990ce57", "installed_by": ["subworkflows"] } } diff --git a/modules/local/create_anndata_mudata/main.nf b/modules/local/create_anndata_mudata/main.nf index 9976ad42..5b412bc4 100644 --- a/modules/local/create_anndata_mudata/main.nf +++ b/modules/local/create_anndata_mudata/main.nf @@ -31,7 +31,7 @@ process CREATE_ANNDATA_MUDATA { template 'create_anndata_mudata.py' stub: - def prefix = task.ext.prefix ?: "${meta.id}" + prefix = task.ext.prefix ?: "${meta.id}" """ touch ${prefix}_genetic.h5ad touch ${prefix}_hashing.h5ad diff --git a/modules/local/extract_hashes/main.nf b/modules/local/extract_hashes/main.nf index 1a756d44..38b8bce4 100644 --- a/modules/local/extract_hashes/main.nf +++ b/modules/local/extract_hashes/main.nf @@ -30,7 +30,7 @@ process EXTRACT_HASHES { """ stub: - def prefix = task.ext.prefix ?: "${meta.id}" + prefix = task.ext.prefix ?: "${meta.id}" """ touch ${prefix}_hashes.txt diff --git a/modules/local/gene_summary/main.nf b/modules/local/gene_summary/main.nf index aa8b2b76..b7d969ff 100644 --- a/modules/local/gene_summary/main.nf +++ b/modules/local/gene_summary/main.nf @@ -31,7 +31,7 @@ process GENE_SUMMARY { template 'gene_summary.py' stub: - def prefix = task.ext.prefix ?: "${meta.id}" + prefix = task.ext.prefix ?: "${meta.id}" """ touch ${prefix}_genetic_summary_assignment.csv touch ${prefix}_genetic_summary_classification.csv diff --git a/modules/local/hash_summary/main.nf b/modules/local/hash_summary/main.nf index 22eb8c8e..e2d33229 100644 --- a/modules/local/hash_summary/main.nf +++ b/modules/local/hash_summary/main.nf @@ -36,7 +36,7 @@ process HASH_SUMMARY { template 'hash_summary.py' stub: - def prefix = task.ext.prefix ?: "${meta.id}" + prefix = task.ext.prefix ?: "${meta.id}" """ touch ${prefix}_hashing_summary_assignment.csv touch ${prefix}_hashing_summary_classification.csv diff --git a/modules/local/htodemux_visualization/main.nf b/modules/local/htodemux_visualization/main.nf index 24817329..984448ed 100644 --- a/modules/local/htodemux_visualization/main.nf +++ b/modules/local/htodemux_visualization/main.nf @@ -56,7 +56,7 @@ process HTODEMUX_VISUALIZATION { template 'htodemux_visualization.R' stub: - def prefix = task.ext.prefix ?: "${meta.id}" + prefix = task.ext.prefix ?: "${meta.id}" """ touch ${prefix}_ridge_htodemux.jpeg touch ${prefix}_featureScatter_htodemux.jpeg diff --git a/modules/local/preprocessing_for_htodemux_multiseq/main.nf b/modules/local/preprocessing_for_htodemux_multiseq/main.nf index 3813b936..b9caa93b 100644 --- a/modules/local/preprocessing_for_htodemux_multiseq/main.nf +++ b/modules/local/preprocessing_for_htodemux_multiseq/main.nf @@ -35,7 +35,7 @@ process PREPROCESSING_FOR_HTODEMUX_MULTISEQ { template 'pre_processing.R' stub: - def prefix = task.ext.prefix ?: "${meta.id}" + prefix = task.ext.prefix ?: "${meta.id}" """ touch ${prefix}_preprocessed.rds touch ${prefix}_params_preprocessing.csv diff --git a/modules/nf-core/demuxem/main.nf b/modules/nf-core/demuxem/main.nf index 12e9e557..0b2e9e11 100644 --- a/modules/nf-core/demuxem/main.nf +++ b/modules/nf-core/demuxem/main.nf @@ -48,7 +48,7 @@ process DEMUXEM { """ stub: - def prefix = task.ext.prefix ?: "${meta.id}" + prefix = task.ext.prefix ?: "${meta.id}" """ export MPLCONFIGDIR=./tmp/mpl touch ${prefix}.out.demuxEM.zarr.zip diff --git a/modules/nf-core/multiqc/.conda-lock/linux_amd64-bd-c17fb751507e9dfc_1.txt b/modules/nf-core/multiqc/.conda-lock/linux_amd64-bd-c17fb751507e9dfc_1.txt new file mode 100644 index 00000000..2a91c22d --- /dev/null +++ b/modules/nf-core/multiqc/.conda-lock/linux_amd64-bd-c17fb751507e9dfc_1.txt @@ -0,0 +1,1526 @@ + +version: 6 +environments: +default: +channels: +- url: https://conda.anaconda.org/conda-forge/ +- url: https://conda.anaconda.org/bioconda/ +- url: https://conda.anaconda.org/bioconda/ +options: +pypi-prerelease-mode: if-necessary-or-explicit +packages: +linux-64: +- conda: https://conda.anaconda.org/conda-forge/linux-64/_openmp_mutex-4.5-20_gnu.conda +- conda: https://conda.anaconda.org/conda-forge/noarch/_python_abi3_support-1.0-hd8ed1ab_2.conda +- conda: https://conda.anaconda.org/conda-forge/noarch/annotated-types-0.7.0-pyhd8ed1ab_1.conda +- conda: https://conda.anaconda.org/conda-forge/noarch/attrs-26.1.0-pyhcf101f3_0.conda +- conda: https://conda.anaconda.org/conda-forge/noarch/backports.zstd-1.5.0-py314h680f03e_0.conda +- conda: https://conda.anaconda.org/conda-forge/linux-64/brotli-python-1.2.0-py314h3de4e8d_1.conda +- conda: https://conda.anaconda.org/conda-forge/linux-64/bzip2-1.0.8-hda65f42_9.conda +- conda: https://conda.anaconda.org/conda-forge/noarch/ca-certificates-2026.5.20-hbd8a1cb_0.conda +- conda: https://conda.anaconda.org/conda-forge/noarch/certifi-2026.5.20-pyhd8ed1ab_0.conda +- conda: https://conda.anaconda.org/conda-forge/noarch/charset-normalizer-3.4.7-pyhd8ed1ab_0.conda +- conda: https://conda.anaconda.org/conda-forge/noarch/click-8.4.0-pyhc90fa1f_0.conda +- conda: https://conda.anaconda.org/conda-forge/noarch/coloredlogs-15.0.1-pyhd8ed1ab_4.conda +- conda: https://conda.anaconda.org/conda-forge/noarch/colormath-3.0.0-pyhd8ed1ab_4.conda +- conda: https://conda.anaconda.org/conda-forge/noarch/cpython-3.14.5-py314hd8ed1ab_100.conda +- conda: https://conda.anaconda.org/conda-forge/linux-64/expat-2.8.1-hecca717_0.conda +- conda: https://conda.anaconda.org/conda-forge/noarch/font-ttf-dejavu-sans-mono-2.37-hab24e00_0.tar.bz2 +- conda: https://conda.anaconda.org/conda-forge/noarch/font-ttf-inconsolata-3.000-h77eed37_0.tar.bz2 +- conda: https://conda.anaconda.org/conda-forge/noarch/font-ttf-source-code-pro-2.038-h77eed37_0.tar.bz2 +- conda: https://conda.anaconda.org/conda-forge/noarch/font-ttf-ubuntu-0.83-h77eed37_3.conda +- conda: https://conda.anaconda.org/conda-forge/linux-64/fontconfig-2.18.0-h27c8c51_0.conda +- conda: https://conda.anaconda.org/conda-forge/noarch/fonts-conda-forge-1-hc364b38_1.conda +- conda: https://conda.anaconda.org/conda-forge/noarch/h2-4.3.0-pyhcf101f3_0.conda +- conda: https://conda.anaconda.org/conda-forge/noarch/hpack-4.1.0-pyhd8ed1ab_0.conda +- conda: https://conda.anaconda.org/conda-forge/noarch/humanfriendly-10.0-pyh707e725_8.conda +- conda: https://conda.anaconda.org/conda-forge/noarch/humanize-4.15.0-pyhd8ed1ab_0.conda +- conda: https://conda.anaconda.org/conda-forge/noarch/hyperframe-6.1.0-pyhd8ed1ab_0.conda +- conda: https://conda.anaconda.org/conda-forge/noarch/idna-3.15-pyhcf101f3_0.conda +- conda: https://conda.anaconda.org/conda-forge/noarch/importlib-metadata-9.0.0-pyhcf101f3_0.conda +- conda: https://conda.anaconda.org/conda-forge/noarch/jinja2-3.1.6-pyhcf101f3_1.conda +- conda: https://conda.anaconda.org/conda-forge/noarch/jsonschema-4.26.0-pyhcf101f3_0.conda +- conda: https://conda.anaconda.org/conda-forge/noarch/jsonschema-specifications-2025.9.1-pyhcf101f3_0.conda +- conda: https://conda.anaconda.org/conda-forge/linux-64/kaleido-core-0.2.1-h3644ca4_0.tar.bz2 +- conda: https://conda.anaconda.org/conda-forge/linux-64/lcms2-2.19.1-h0c24ade_0.conda +- conda: https://conda.anaconda.org/conda-forge/linux-64/ld_impl_linux-64-2.45.1-default_hbd61a6d_102.conda +- conda: https://conda.anaconda.org/conda-forge/linux-64/lerc-4.1.0-hdb68285_0.conda +- conda: https://conda.anaconda.org/conda-forge/linux-64/libblas-3.11.0-7_h4a7cf45_openblas.conda +- conda: https://conda.anaconda.org/conda-forge/linux-64/libcblas-3.11.0-7_h0358290_openblas.conda +- conda: https://conda.anaconda.org/conda-forge/linux-64/libdeflate-1.25-h17f619e_0.conda +- conda: https://conda.anaconda.org/conda-forge/linux-64/libexpat-2.8.1-hecca717_0.conda +- conda: https://conda.anaconda.org/conda-forge/linux-64/libffi-3.5.2-h3435931_0.conda +- conda: https://conda.anaconda.org/conda-forge/linux-64/libfreetype-2.14.3-ha770c72_0.conda +- conda: https://conda.anaconda.org/conda-forge/linux-64/libfreetype6-2.14.3-h73754d4_0.conda +- conda: https://conda.anaconda.org/conda-forge/linux-64/libgcc-15.2.0-he0feb66_19.conda +- conda: https://conda.anaconda.org/conda-forge/linux-64/libgcc-ng-15.2.0-h69a702a_19.conda +- conda: https://conda.anaconda.org/conda-forge/linux-64/libgfortran-15.2.0-h69a702a_19.conda +- conda: https://conda.anaconda.org/conda-forge/linux-64/libgfortran5-15.2.0-h68bc16d_19.conda +- conda: https://conda.anaconda.org/conda-forge/linux-64/libgomp-15.2.0-he0feb66_19.conda +- conda: https://conda.anaconda.org/conda-forge/linux-64/libjpeg-turbo-3.1.4.1-hb03c661_0.conda +- conda: https://conda.anaconda.org/conda-forge/linux-64/liblapack-3.11.0-7_h47877c9_openblas.conda +- conda: https://conda.anaconda.org/conda-forge/linux-64/liblzma-5.8.3-hb03c661_0.conda +- conda: https://conda.anaconda.org/conda-forge/linux-64/libmpdec-4.0.0-hb03c661_1.conda +- conda: https://conda.anaconda.org/conda-forge/linux-64/libopenblas-0.3.33-pthreads_h94d23a6_0.conda +- conda: https://conda.anaconda.org/conda-forge/linux-64/libpng-1.6.58-h421ea60_0.conda +- conda: https://conda.anaconda.org/conda-forge/linux-64/libsqlite-3.53.1-h0c1763c_0.conda +- conda: https://conda.anaconda.org/conda-forge/linux-64/libstdcxx-15.2.0-h934c35e_19.conda +- conda: https://conda.anaconda.org/conda-forge/linux-64/libtiff-4.7.1-h9d88235_1.conda +- conda: https://conda.anaconda.org/conda-forge/linux-64/libuuid-2.42.1-h5347b49_0.conda +- conda: https://conda.anaconda.org/conda-forge/linux-64/libwebp-base-1.6.0-hd42ef1d_0.conda +- conda: https://conda.anaconda.org/conda-forge/linux-64/libxcb-1.17.0-h8a09558_0.conda +- conda: https://conda.anaconda.org/conda-forge/linux-64/libzlib-1.3.2-h25fd6f3_2.conda +- conda: https://conda.anaconda.org/conda-forge/noarch/markdown-3.10.2-pyhcf101f3_0.conda +- conda: https://conda.anaconda.org/conda-forge/noarch/markdown-it-py-4.2.0-pyhd8ed1ab_0.conda +- conda: https://conda.anaconda.org/conda-forge/linux-64/markupsafe-3.0.3-py314h67df5f8_1.conda +- conda: https://conda.anaconda.org/conda-forge/linux-64/mathjax-2.7.7-ha770c72_3.tar.bz2 +- conda: https://conda.anaconda.org/conda-forge/noarch/mdurl-0.1.2-pyhd8ed1ab_1.conda +- conda: https://conda.anaconda.org/bioconda/noarch/multiqc-1.35-pyhdfd78af_1.conda +- conda: https://conda.anaconda.org/conda-forge/noarch/narwhals-2.21.2-pyhcf101f3_0.conda +- conda: https://conda.anaconda.org/conda-forge/noarch/natsort-8.4.0-pyhcf101f3_2.conda +- conda: https://conda.anaconda.org/conda-forge/linux-64/ncurses-6.6-hdb14827_0.conda +- conda: https://conda.anaconda.org/conda-forge/noarch/networkx-3.6.1-pyhcf101f3_0.conda +- conda: https://conda.anaconda.org/conda-forge/linux-64/nspr-4.38-h29cc59b_0.conda +- conda: https://conda.anaconda.org/conda-forge/linux-64/nss-3.118-h445c969_0.conda +- conda: https://conda.anaconda.org/conda-forge/linux-64/numpy-2.4.6-py314h2b28147_0.conda +- conda: https://conda.anaconda.org/conda-forge/linux-64/openjpeg-2.5.4-h55fea9a_0.conda +- conda: https://conda.anaconda.org/conda-forge/linux-64/openssl-3.6.2-h35e630c_0.conda +- conda: https://conda.anaconda.org/conda-forge/noarch/packaging-26.2-pyhc364b38_0.conda +- conda: https://conda.anaconda.org/conda-forge/linux-64/pillow-12.2.0-py314h8ec4b1a_0.conda +- conda: https://conda.anaconda.org/conda-forge/noarch/plotly-6.6.0-pyhd8ed1ab_0.conda +- conda: https://conda.anaconda.org/conda-forge/noarch/polars-1.41.0-pyh58ad624_0.conda +- conda: https://conda.anaconda.org/conda-forge/linux-64/polars-runtime-32-1.41.0-py310h49dadd8_0.conda +- conda: https://conda.anaconda.org/conda-forge/linux-64/polars-runtime-compat-1.41.0-py310hcbd6021_0.conda +- conda: https://conda.anaconda.org/conda-forge/linux-64/procps-ng-4.0.6-h18c060e_0.conda +- conda: https://conda.anaconda.org/conda-forge/linux-64/pthread-stubs-0.4-hb9d3cd8_1002.conda +- conda: https://conda.anaconda.org/conda-forge/noarch/pyaml-env-1.2.2-pyhd8ed1ab_0.conda +- conda: https://conda.anaconda.org/conda-forge/noarch/pydantic-2.13.4-pyhcf101f3_0.conda +- conda: https://conda.anaconda.org/conda-forge/linux-64/pydantic-core-2.46.4-py314h2e6c369_0.conda +- conda: https://conda.anaconda.org/conda-forge/noarch/pygments-2.20.0-pyhd8ed1ab_0.conda +- conda: https://conda.anaconda.org/conda-forge/noarch/pysocks-1.7.1-pyha55dd90_7.conda +- conda: https://conda.anaconda.org/conda-forge/linux-64/python-3.14.5-habeac84_100_cp314.conda +- conda: https://conda.anaconda.org/conda-forge/noarch/python-dotenv-1.2.2-pyhcf101f3_0.conda +- conda: https://conda.anaconda.org/conda-forge/noarch/python-gil-3.14.5-h4df99d1_100.conda +- conda: https://conda.anaconda.org/conda-forge/noarch/python-kaleido-0.2.1-pyhd8ed1ab_0.tar.bz2 +- conda: https://conda.anaconda.org/conda-forge/noarch/python_abi-3.14-8_cp314.conda +- conda: https://conda.anaconda.org/conda-forge/linux-64/pyyaml-6.0.3-py314h67df5f8_1.conda +- conda: https://conda.anaconda.org/conda-forge/linux-64/readline-8.3-h853b02a_0.conda +- conda: https://conda.anaconda.org/conda-forge/noarch/referencing-0.37.0-pyhcf101f3_0.conda +- conda: https://conda.anaconda.org/conda-forge/linux-64/regex-2026.5.9-py314h5bd0f2a_0.conda +- conda: https://conda.anaconda.org/conda-forge/noarch/requests-2.34.2-pyhcf101f3_0.conda +- conda: https://conda.anaconda.org/conda-forge/noarch/rich-15.0.0-pyhcf101f3_0.conda +- conda: https://conda.anaconda.org/conda-forge/noarch/rich-click-1.9.7-pyh8f84b5b_0.conda +- conda: https://conda.anaconda.org/conda-forge/linux-64/rpds-py-0.30.0-py314h2e6c369_0.conda +- conda: https://conda.anaconda.org/conda-forge/noarch/spectra-0.0.11-pyhd8ed1ab_2.conda +- conda: https://conda.anaconda.org/conda-forge/linux-64/sqlite-3.53.1-hbc0de68_0.conda +- conda: https://conda.anaconda.org/conda-forge/linux-64/tiktoken-0.12.0-py314h67fec18_3.conda +- conda: https://conda.anaconda.org/conda-forge/linux-64/tk-8.6.13-noxft_h366c992_103.conda +- conda: https://conda.anaconda.org/conda-forge/noarch/tqdm-4.67.3-pyh8f84b5b_0.conda +- conda: https://conda.anaconda.org/conda-forge/noarch/typeguard-4.5.2-pyhcf101f3_0.conda +- conda: https://conda.anaconda.org/conda-forge/noarch/typing-extensions-4.15.0-h396c80c_0.conda +- conda: https://conda.anaconda.org/conda-forge/noarch/typing-inspection-0.4.2-pyhcf101f3_2.conda +- conda: https://conda.anaconda.org/conda-forge/noarch/typing_extensions-4.15.0-pyhcf101f3_0.conda +- conda: https://conda.anaconda.org/conda-forge/noarch/tzdata-2025c-hc9c84f9_1.conda +- conda: https://conda.anaconda.org/conda-forge/noarch/urllib3-2.7.0-pyhd8ed1ab_0.conda +- conda: https://conda.anaconda.org/conda-forge/linux-64/xorg-libxau-1.0.12-hb03c661_1.conda +- conda: https://conda.anaconda.org/conda-forge/linux-64/xorg-libxdmcp-1.1.5-hb03c661_1.conda +- conda: https://conda.anaconda.org/conda-forge/linux-64/yaml-0.2.5-h280c20c_3.conda +- conda: https://conda.anaconda.org/conda-forge/noarch/zipp-4.1.0-pyhcf101f3_0.conda +- conda: https://conda.anaconda.org/conda-forge/linux-64/zlib-ng-2.3.3-hceb46e0_1.conda +- conda: https://conda.anaconda.org/conda-forge/linux-64/zstd-1.5.7-hb78ec9c_6.conda +packages: +- conda: https://conda.anaconda.org/conda-forge/linux-64/_openmp_mutex-4.5-20_gnu.conda +build_number: 20 +sha256: 1dd3fffd892081df9726d7eb7e0dea6198962ba775bd88842135a4ddb4deb3c9 +md5: a9f577daf3de00bca7c3c76c0ecbd1de +depends: +- __glibc >=2.17,<3.0.a0 +- libgomp >=7.5.0 +constrains: +- openmp_impl <0.0a0 +license: BSD-3-Clause +license_family: BSD +size: 28948 +timestamp: 1770939786096 +- conda: https://conda.anaconda.org/conda-forge/noarch/_python_abi3_support-1.0-hd8ed1ab_2.conda +sha256: a3967b937b9abf0f2a99f3173fa4630293979bd1644709d89580e7c62a544661 +md5: aaa2a381ccc56eac91d63b6c1240312f +depends: +- cpython +- python-gil +license: MIT +license_family: MIT +size: 8191 +timestamp: 1744137672556 +- conda: https://conda.anaconda.org/conda-forge/noarch/annotated-types-0.7.0-pyhd8ed1ab_1.conda +sha256: e0ea1ba78fbb64f17062601edda82097fcf815012cf52bb704150a2668110d48 +md5: 2934f256a8acfe48f6ebb4fce6cde29c +depends: +- python >=3.9 +- typing-extensions >=4.0.0 +license: MIT +license_family: MIT +size: 18074 +timestamp: 1733247158254 +- conda: https://conda.anaconda.org/conda-forge/noarch/attrs-26.1.0-pyhcf101f3_0.conda +sha256: 1b6124230bb4e571b1b9401537ecff575b7b109cc3a21ee019f65e083b8399ab +md5: c6b0543676ecb1fb2d7643941fe375f2 +depends: +- python >=3.10 +- python +license: MIT +license_family: MIT +size: 64927 +timestamp: 1773935801332 +- conda: https://conda.anaconda.org/conda-forge/noarch/backports.zstd-1.5.0-py314h680f03e_0.conda +noarch: generic +sha256: a1c97297e867776760489537bc5ae36fa83a154be30e3b79385a39ca4cb058fe +md5: 1133126d840e75287d83947be3fc3e71 +depends: +- python >=3.14 +license: BSD-3-Clause AND MIT AND EPL-2.0 +size: 7533 +timestamp: 1778594057496 +- conda: https://conda.anaconda.org/conda-forge/linux-64/brotli-python-1.2.0-py314h3de4e8d_1.conda +sha256: 3ad3500bff54a781c29f16ce1b288b36606e2189d0b0ef2f67036554f47f12b0 +md5: 8910d2c46f7e7b519129f486e0fe927a +depends: +- __glibc >=2.17,<3.0.a0 +- libgcc >=14 +- libstdcxx >=14 +- python >=3.14,<3.15.0a0 +- python_abi 3.14.* *_cp314 +constrains: +- libbrotlicommon 1.2.0 hb03c661_1 +license: MIT +license_family: MIT +size: 367376 +timestamp: 1764017265553 +- conda: https://conda.anaconda.org/conda-forge/linux-64/bzip2-1.0.8-hda65f42_9.conda +sha256: 0b75d45f0bba3e95dc693336fa51f40ea28c980131fec438afb7ce6118ed05f6 +md5: d2ffd7602c02f2b316fd921d39876885 +depends: +- __glibc >=2.17,<3.0.a0 +- libgcc >=14 +license: bzip2-1.0.6 +license_family: BSD +size: 260182 +timestamp: 1771350215188 +- conda: https://conda.anaconda.org/conda-forge/noarch/ca-certificates-2026.5.20-hbd8a1cb_0.conda +sha256: 9812a303a1395e1dafbd92e5bc8a1ff6013bcbba0a09c7f03a8d23e43560aa9b +md5: 489b8e97e666c93f68fdb35c3c9b957f +depends: +- __unix +license: ISC +size: 129868 +timestamp: 1779289852439 +- conda: https://conda.anaconda.org/conda-forge/noarch/certifi-2026.5.20-pyhd8ed1ab_0.conda +sha256: 645655a3510e38e625da136595f3f16f2130c3263630cc3bc8f60f619ddbe490 +md5: 9fefff2f745ea1cc2ef15211a20c054a +depends: +- python >=3.10 +license: ISC +size: 134201 +timestamp: 1779285131141 +- conda: https://conda.anaconda.org/conda-forge/noarch/charset-normalizer-3.4.7-pyhd8ed1ab_0.conda +sha256: 3f9483d62ce24ecd063f8a5a714448445dc8d9e201147c46699fc0033e824457 +md5: a9167b9571f3baa9d448faa2139d1089 +depends: +- python >=3.10 +license: MIT +license_family: MIT +size: 58872 +timestamp: 1775127203018 +- conda: https://conda.anaconda.org/conda-forge/noarch/click-8.4.0-pyhc90fa1f_0.conda +sha256: 99ab8ef815c4520cce3a7482c2513f377c14348206857661d84c76a55e030f97 +md5: 003767c47f1f0a474c4de268b57839c3 +depends: +- __unix +- python +- python >=3.10 +license: BSD-3-Clause +license_family: BSD +size: 104631 +timestamp: 1779108494556 +- conda: https://conda.anaconda.org/conda-forge/noarch/coloredlogs-15.0.1-pyhd8ed1ab_4.conda +sha256: 8021c76eeadbdd5784b881b165242db9449783e12ce26d6234060026fd6a8680 +md5: b866ff7007b934d564961066c8195983 +depends: +- humanfriendly >=9.1 +- python >=3.9 +license: MIT +license_family: MIT +size: 43758 +timestamp: 1733928076798 +- conda: https://conda.anaconda.org/conda-forge/noarch/colormath-3.0.0-pyhd8ed1ab_4.conda +sha256: 59c9e29800b483b390467f90e82b0da3a4fbf0612efe1c90813fca232780e160 +md5: 071cf7b0ce333c81718b054066c15102 +depends: +- networkx >=2.0 +- numpy +- python >=3.9 +license: BSD-3-Clause +license_family: BSD +size: 39326 +timestamp: 1735759976140 +- conda: https://conda.anaconda.org/conda-forge/noarch/cpython-3.14.5-py314hd8ed1ab_100.conda +noarch: generic +sha256: 777882d2685f368417f31bbe1b28f73687fc6c8f6a5768bda20ffeefa6b07f5b +md5: a749029ce5d0632a913db19d17f944ab +depends: +- python >=3.14,<3.15.0a0 +- python_abi * *_cp314 +license: Python-2.0 +size: 50212 +timestamp: 1779236682725 +- conda: https://conda.anaconda.org/conda-forge/linux-64/expat-2.8.1-hecca717_0.conda +sha256: 29a10599d56d93bd750914888ebe6822d47722070762b4647b34d12df9f4476e +md5: d0757fd84af06f065eba49d39af6c546 +depends: +- __glibc >=2.17,<3.0.a0 +- libexpat 2.8.1 hecca717_0 +- libgcc >=14 +license: MIT +license_family: MIT +size: 148238 +timestamp: 1779278694477 +- conda: https://conda.anaconda.org/conda-forge/noarch/font-ttf-dejavu-sans-mono-2.37-hab24e00_0.tar.bz2 +sha256: 58d7f40d2940dd0a8aa28651239adbf5613254df0f75789919c4e6762054403b +md5: 0c96522c6bdaed4b1566d11387caaf45 +license: BSD-3-Clause +license_family: BSD +size: 397370 +timestamp: 1566932522327 +- conda: https://conda.anaconda.org/conda-forge/noarch/font-ttf-inconsolata-3.000-h77eed37_0.tar.bz2 +sha256: c52a29fdac682c20d252facc50f01e7c2e7ceac52aa9817aaf0bb83f7559ec5c +md5: 34893075a5c9e55cdafac56607368fc6 +license: OFL-1.1 +license_family: Other +size: 96530 +timestamp: 1620479909603 +- conda: https://conda.anaconda.org/conda-forge/noarch/font-ttf-source-code-pro-2.038-h77eed37_0.tar.bz2 +sha256: 00925c8c055a2275614b4d983e1df637245e19058d79fc7dd1a93b8d9fb4b139 +md5: 4d59c254e01d9cde7957100457e2d5fb +license: OFL-1.1 +license_family: Other +size: 700814 +timestamp: 1620479612257 +- conda: https://conda.anaconda.org/conda-forge/noarch/font-ttf-ubuntu-0.83-h77eed37_3.conda +sha256: 2821ec1dc454bd8b9a31d0ed22a7ce22422c0aef163c59f49dfdf915d0f0ca14 +md5: 49023d73832ef61042f6a237cb2687e7 +license: LicenseRef-Ubuntu-Font-Licence-Version-1.0 +license_family: Other +size: 1620504 +timestamp: 1727511233259 +- conda: https://conda.anaconda.org/conda-forge/linux-64/fontconfig-2.18.0-h27c8c51_0.conda +sha256: e798086d8a65d55dc4c51f5746705639c9a5f2eeb0b8fc50e6152cfc0d69a4e8 +md5: 06965b2f9854d0b15e0443ee81fe83dc +depends: +- __glibc >=2.17,<3.0.a0 +- libexpat >=2.8.1,<3.0a0 +- libfreetype >=2.14.3 +- libfreetype6 >=2.14.3 +- libgcc >=14 +- libuuid >=2.42.1,<3.0a0 +- libzlib >=1.3.2,<2.0a0 +license: MIT +license_family: MIT +size: 280882 +timestamp: 1779421631622 +- conda: https://conda.anaconda.org/conda-forge/noarch/fonts-conda-forge-1-hc364b38_1.conda +sha256: 54eea8469786bc2291cc40bca5f46438d3e062a399e8f53f013b6a9f50e98333 +md5: a7970cd949a077b7cb9696379d338681 +depends: +- font-ttf-ubuntu +- font-ttf-inconsolata +- font-ttf-dejavu-sans-mono +- font-ttf-source-code-pro +license: BSD-3-Clause +license_family: BSD +size: 4059 +timestamp: 1762351264405 +- conda: https://conda.anaconda.org/conda-forge/noarch/h2-4.3.0-pyhcf101f3_0.conda +sha256: 84c64443368f84b600bfecc529a1194a3b14c3656ee2e832d15a20e0329b6da3 +md5: 164fc43f0b53b6e3a7bc7dce5e4f1dc9 +depends: +- python >=3.10 +- hyperframe >=6.1,<7 +- hpack >=4.1,<5 +- python +license: MIT +license_family: MIT +size: 95967 +timestamp: 1756364871835 +- conda: https://conda.anaconda.org/conda-forge/noarch/hpack-4.1.0-pyhd8ed1ab_0.conda +sha256: 6ad78a180576c706aabeb5b4c8ceb97c0cb25f1e112d76495bff23e3779948ba +md5: 0a802cb9888dd14eeefc611f05c40b6e +depends: +- python >=3.9 +license: MIT +license_family: MIT +size: 30731 +timestamp: 1737618390337 +- conda: https://conda.anaconda.org/conda-forge/noarch/humanfriendly-10.0-pyh707e725_8.conda +sha256: fa2071da7fab758c669e78227e6094f6b3608228740808a6de5d6bce83d9e52d +md5: 7fe569c10905402ed47024fc481bb371 +depends: +- __unix +- python >=3.9 +license: MIT +license_family: MIT +size: 73563 +timestamp: 1733928021866 +- conda: https://conda.anaconda.org/conda-forge/noarch/humanize-4.15.0-pyhd8ed1ab_0.conda +sha256: 6c4343b376d0b12a4c75ab992640970d36c933cad1fd924f6a1181fa91710e80 +md5: daddf757c3ecd6067b9af1df1f25d89e +depends: +- python >=3.10 +license: MIT +license_family: MIT +size: 67994 +timestamp: 1766267728652 +- conda: https://conda.anaconda.org/conda-forge/noarch/hyperframe-6.1.0-pyhd8ed1ab_0.conda +sha256: 77af6f5fe8b62ca07d09ac60127a30d9069fdc3c68d6b256754d0ffb1f7779f8 +md5: 8e6923fc12f1fe8f8c4e5c9f343256ac +depends: +- python >=3.9 +license: MIT +license_family: MIT +size: 17397 +timestamp: 1737618427549 +- conda: https://conda.anaconda.org/conda-forge/noarch/idna-3.15-pyhcf101f3_0.conda +sha256: 3d25f9f6f7ab3e1ce6429fc8c8aae0335cf446692e715068488536d220cc43de +md5: 1b9083b7f00609605d1483dbc6071a81 +depends: +- python >=3.10 +- python +license: BSD-3-Clause +license_family: BSD +size: 62642 +timestamp: 1779294335905 +- conda: https://conda.anaconda.org/conda-forge/noarch/importlib-metadata-9.0.0-pyhcf101f3_0.conda +sha256: 43e2a5497cad1598ff88a3e69f69bc88b7b8f141fa63c60eab5db296317318b8 +md5: ffc17e785d64e12fc311af9184221839 +depends: +- python >=3.10 +- zipp >=3.20 +- python +license: Apache-2.0 +size: 34766 +timestamp: 1779714582554 +- conda: https://conda.anaconda.org/conda-forge/noarch/jinja2-3.1.6-pyhcf101f3_1.conda +sha256: fc9ca7348a4f25fed2079f2153ecdcf5f9cf2a0bc36c4172420ca09e1849df7b +md5: 04558c96691bed63104678757beb4f8d +depends: +- markupsafe >=2.0 +- python >=3.10 +- python +license: BSD-3-Clause +license_family: BSD +size: 120685 +timestamp: 1764517220861 +- conda: https://conda.anaconda.org/conda-forge/noarch/jsonschema-4.26.0-pyhcf101f3_0.conda +sha256: db973a37d75db8e19b5f44bbbdaead0c68dde745407f281e2a7fe4db74ec51d7 +md5: ada41c863af263cc4c5fcbaff7c3e4dc +depends: +- attrs >=22.2.0 +- jsonschema-specifications >=2023.3.6 +- python >=3.10 +- referencing >=0.28.4 +- rpds-py >=0.25.0 +- python +license: MIT +license_family: MIT +size: 82356 +timestamp: 1767839954256 +- conda: https://conda.anaconda.org/conda-forge/noarch/jsonschema-specifications-2025.9.1-pyhcf101f3_0.conda +sha256: 0a4f3b132f0faca10c89fdf3b60e15abb62ded6fa80aebfc007d05965192aa04 +md5: 439cd0f567d697b20a8f45cb70a1005a +depends: +- python >=3.10 +- referencing >=0.31.0 +- python +license: MIT +license_family: MIT +size: 19236 +timestamp: 1757335715225 +- conda: https://conda.anaconda.org/conda-forge/linux-64/kaleido-core-0.2.1-h3644ca4_0.tar.bz2 +sha256: 7f243680ca03eba7457b7a48f93a9440ba8181a8eac20a3eb5ef165ab6c96664 +md5: b3723b235b0758abaae8c82ce4d80146 +depends: +- __glibc >=2.17,<3.0.a0 +- expat >=2.2.10,<3.0.0a0 +- fontconfig +- fonts-conda-forge +- libgcc-ng >=9.3.0 +- mathjax 2.7.* +- nspr >=4.29,<5.0a0 +- nss >=3.62,<4.0a0 +- sqlite >=3.34.0,<4.0a0 +license: MIT +license_family: MIT +size: 62099926 +timestamp: 1615199463039 +- conda: https://conda.anaconda.org/conda-forge/linux-64/lcms2-2.19.1-h0c24ade_0.conda +sha256: eb89c6c39f2f6a93db55723dbb2f6bba8c8e63e6312bf1abf13e6e9ff45849c8 +md5: f92f984b558e6e6204014b16d212b271 +depends: +- __glibc >=2.17,<3.0.a0 +- libgcc >=14 +- libjpeg-turbo >=3.1.4.1,<4.0a0 +- libtiff >=4.7.1,<4.8.0a0 +license: MIT +license_family: MIT +size: 251086 +timestamp: 1778079286384 +- conda: https://conda.anaconda.org/conda-forge/linux-64/ld_impl_linux-64-2.45.1-default_hbd61a6d_102.conda +sha256: 3d584956604909ff5df353767f3a2a2f60e07d070b328d109f30ac40cd62df6c +md5: 18335a698559cdbcd86150a48bf54ba6 +depends: +- __glibc >=2.17,<3.0.a0 +- zstd >=1.5.7,<1.6.0a0 +constrains: +- binutils_impl_linux-64 2.45.1 +license: GPL-3.0-only +license_family: GPL +size: 728002 +timestamp: 1774197446916 +- conda: https://conda.anaconda.org/conda-forge/linux-64/lerc-4.1.0-hdb68285_0.conda +sha256: f84cb54782f7e9cea95e810ea8fef186e0652d0fa73d3009914fa2c1262594e1 +md5: a752488c68f2e7c456bcbd8f16eec275 +depends: +- __glibc >=2.17,<3.0.a0 +- libgcc >=14 +- libstdcxx >=14 +license: Apache-2.0 +license_family: Apache +size: 261513 +timestamp: 1773113328888 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libblas-3.11.0-7_h4a7cf45_openblas.conda +build_number: 7 +sha256: 081c850f99bc355821fac9c6e3727d40b3f8ce3beb50a5437cf03726b611ff39 +md5: 955b44e8b00b7f7ef4ce0130cef12394 +depends: +- libopenblas >=0.3.33,<0.3.34.0a0 +- libopenblas >=0.3.33,<1.0a0 +constrains: +- libcblas 3.11.0 7*_openblas +- blas 2.307 openblas +- liblapack 3.11.0 7*_openblas +- liblapacke 3.11.0 7*_openblas +- mkl <2027 +license: BSD-3-Clause +license_family: BSD +size: 18716 +timestamp: 1778489854108 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libcblas-3.11.0-7_h0358290_openblas.conda +build_number: 7 +sha256: 956ae0bb1ec8b0c3663d75b151aceb0521b54e513bf97f621a035f9c87037970 +md5: 0675639dc24cb0032f199e7ff68e4633 +depends: +- libblas 3.11.0 7_h4a7cf45_openblas +constrains: +- liblapacke 3.11.0 7*_openblas +- blas 2.307 openblas +- liblapack 3.11.0 7*_openblas +license: BSD-3-Clause +license_family: BSD +size: 18675 +timestamp: 1778489861559 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libdeflate-1.25-h17f619e_0.conda +sha256: aa8e8c4be9a2e81610ddf574e05b64ee131fab5e0e3693210c9d6d2fba32c680 +md5: 6c77a605a7a689d17d4819c0f8ac9a00 +depends: +- __glibc >=2.17,<3.0.a0 +- libgcc >=14 +license: MIT +license_family: MIT +size: 73490 +timestamp: 1761979956660 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libexpat-2.8.1-hecca717_0.conda +sha256: 363018b25fdb5534c79783d912bd4b685a3547f4fc5996357ad548899b0ee8e7 +md5: 93764a5ca80616e9c10106cdaec92f74 +depends: +- __glibc >=2.17,<3.0.a0 +- libgcc >=14 +constrains: +- expat 2.8.1.* +license: MIT +license_family: MIT +size: 77294 +timestamp: 1779278686680 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libffi-3.5.2-h3435931_0.conda +sha256: 31f19b6a88ce40ebc0d5a992c131f57d919f73c0b92cd1617a5bec83f6e961e6 +md5: a360c33a5abe61c07959e449fa1453eb +depends: +- __glibc >=2.17,<3.0.a0 +- libgcc >=14 +license: MIT +license_family: MIT +size: 58592 +timestamp: 1769456073053 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libfreetype-2.14.3-ha770c72_0.conda +sha256: 38f014a7129e644636e46064ecd6b1945e729c2140e21d75bb476af39e692db2 +md5: e289f3d17880e44b633ba911d57a321b +depends: +- libfreetype6 >=2.14.3 +license: GPL-2.0-only OR FTL +size: 8049 +timestamp: 1774298163029 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libfreetype6-2.14.3-h73754d4_0.conda +sha256: 16f020f96da79db1863fcdd8f2b8f4f7d52f177dd4c58601e38e9182e91adf1d +md5: fb16b4b69e3f1dcfe79d80db8fd0c55d +depends: +- __glibc >=2.17,<3.0.a0 +- libgcc >=14 +- libpng >=1.6.55,<1.7.0a0 +- libzlib >=1.3.2,<2.0a0 +constrains: +- freetype >=2.14.3 +license: GPL-2.0-only OR FTL +size: 384575 +timestamp: 1774298162622 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libgcc-15.2.0-he0feb66_19.conda +sha256: 8e0a3b5e41272e5678499b5dfc4cddb673f9e935de01eb0767ce857001229f46 +md5: 57736f29cc2b0ec0b6c2952d3f101b6a +depends: +- __glibc >=2.17,<3.0.a0 +- _openmp_mutex >=4.5 +constrains: +- libgcc-ng ==15.2.0=*_19 +- libgomp 15.2.0 he0feb66_19 +license: GPL-3.0-only WITH GCC-exception-3.1 +license_family: GPL +size: 1041084 +timestamp: 1778269013026 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libgcc-ng-15.2.0-h69a702a_19.conda +sha256: 9dcf54adfaa5e861123c2da4f2f0451a685464ea7e5a41ad91cf67b31d658d98 +md5: 331ee9b72b9dff570d56b1302c5ab37d +depends: +- libgcc 15.2.0 he0feb66_19 +license: GPL-3.0-only WITH GCC-exception-3.1 +license_family: GPL +size: 27694 +timestamp: 1778269016987 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libgfortran-15.2.0-h69a702a_19.conda +sha256: 561a42758ef25b9ce308c4e2cf56daee4f06138385a17e29a492cd928e00be6f +md5: 42bf7eca1a951735fa06c0e3c0d5c8e6 +depends: +- libgfortran5 15.2.0 h68bc16d_19 +constrains: +- libgfortran-ng ==15.2.0=*_19 +license: GPL-3.0-only WITH GCC-exception-3.1 +license_family: GPL +size: 27655 +timestamp: 1778269042954 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libgfortran5-15.2.0-h68bc16d_19.conda +sha256: 057978bb69fea29ed715a9b98adf71015c31baecc4aeb2bfc20d4fd5d83579d4 +md5: 85072b0ad177c966294f129b7c04a2d5 +depends: +- __glibc >=2.17,<3.0.a0 +- libgcc >=15.2.0 +constrains: +- libgfortran 15.2.0 +license: GPL-3.0-only WITH GCC-exception-3.1 +license_family: GPL +size: 2483673 +timestamp: 1778269025089 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libgomp-15.2.0-he0feb66_19.conda +sha256: 5abe4ab9d93f6c9757d654f1969ae2267d4505315c1f2f8fe705fd60af084f1b +md5: faac990cb7aedc7f3a2224f2c9b0c26c +depends: +- __glibc >=2.17,<3.0.a0 +license: GPL-3.0-only WITH GCC-exception-3.1 +license_family: GPL +size: 603817 +timestamp: 1778268942614 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libjpeg-turbo-3.1.4.1-hb03c661_0.conda +sha256: 10056646c28115b174de81a44e23e3a0a3b95b5347d2e6c45cc6d49d35294256 +md5: 6178c6f2fb254558238ef4e6c56fb782 +depends: +- __glibc >=2.17,<3.0.a0 +- libgcc >=14 +constrains: +- jpeg <0.0.0a +license: IJG AND BSD-3-Clause AND Zlib +size: 633831 +timestamp: 1775962768273 +- conda: https://conda.anaconda.org/conda-forge/linux-64/liblapack-3.11.0-7_h47877c9_openblas.conda +build_number: 7 +sha256: 96962084921f197c9ad13fb7f8b324f2351d50ff3d8d962148751ad532f54a01 +md5: 6569b4f273740e25dc0dc7e3232c2a6c +depends: +- libblas 3.11.0 7_h4a7cf45_openblas +constrains: +- liblapacke 3.11.0 7*_openblas +- libcblas 3.11.0 7*_openblas +- blas 2.307 openblas +license: BSD-3-Clause +license_family: BSD +size: 18694 +timestamp: 1778489869038 +- conda: https://conda.anaconda.org/conda-forge/linux-64/liblzma-5.8.3-hb03c661_0.conda +sha256: ec30e52a3c1bf7d0425380a189d209a52baa03f22fb66dd3eb587acaa765bd6d +md5: b88d90cad08e6bc8ad540cb310a761fb +depends: +- __glibc >=2.17,<3.0.a0 +- libgcc >=14 +constrains: +- xz 5.8.3.* +license: 0BSD +size: 113478 +timestamp: 1775825492909 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libmpdec-4.0.0-hb03c661_1.conda +sha256: fe171ed5cf5959993d43ff72de7596e8ac2853e9021dec0344e583734f1e0843 +md5: 2c21e66f50753a083cbe6b80f38268fa +depends: +- __glibc >=2.17,<3.0.a0 +- libgcc >=14 +license: BSD-2-Clause +license_family: BSD +size: 92400 +timestamp: 1769482286018 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libopenblas-0.3.33-pthreads_h94d23a6_0.conda +sha256: 3d9aa85648e5e18a6d66db98b8c4317cc426721ad7a220aa86330d1ccedc8903 +md5: 2d3278b721e40468295ca755c3b84070 +depends: +- __glibc >=2.17,<3.0.a0 +- libgcc >=14 +- libgfortran +- libgfortran5 >=14.3.0 +constrains: +- openblas >=0.3.33,<0.3.34.0a0 +license: BSD-3-Clause +license_family: BSD +size: 5931919 +timestamp: 1776993658641 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libpng-1.6.58-h421ea60_0.conda +sha256: 377cfe037f3eeb3b1bf3ad333f724a64d32f315ee1958581fc671891d63d3f89 +md5: eba48a68a1a2b9d3c0d9511548db85db +depends: +- __glibc >=2.17,<3.0.a0 +- libgcc >=14 +- libzlib >=1.3.2,<2.0a0 +license: zlib-acknowledgement +size: 317729 +timestamp: 1776315175087 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libsqlite-3.53.1-h0c1763c_0.conda +sha256: 54cdcd3214313b62c2a8ee277e6f42150d9b748264c1b70d958bf735e420ef8d +md5: 7dc38adcbf71e6b38748e919e16e0dce +depends: +- __glibc >=2.17,<3.0.a0 +- libgcc >=14 +- libzlib >=1.3.2,<2.0a0 +license: blessing +size: 954962 +timestamp: 1777986471789 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libstdcxx-15.2.0-h934c35e_19.conda +sha256: dff1058c76ec6b8759e41cefa2508162d00e4a5e6721aa68ec3fd10094e702dc +md5: 5794b3bdc38177caf969dabd3af08549 +depends: +- __glibc >=2.17,<3.0.a0 +- libgcc 15.2.0 he0feb66_19 +constrains: +- libstdcxx-ng ==15.2.0=*_19 +license: GPL-3.0-only WITH GCC-exception-3.1 +license_family: GPL +size: 5852044 +timestamp: 1778269036376 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libtiff-4.7.1-h9d88235_1.conda +sha256: e5f8c38625aa6d567809733ae04bb71c161a42e44a9fa8227abe61fa5c60ebe0 +md5: cd5a90476766d53e901500df9215e927 +depends: +- __glibc >=2.17,<3.0.a0 +- lerc >=4.0.0,<5.0a0 +- libdeflate >=1.25,<1.26.0a0 +- libgcc >=14 +- libjpeg-turbo >=3.1.0,<4.0a0 +- liblzma >=5.8.1,<6.0a0 +- libstdcxx >=14 +- libwebp-base >=1.6.0,<2.0a0 +- libzlib >=1.3.1,<2.0a0 +- zstd >=1.5.7,<1.6.0a0 +license: HPND +size: 435273 +timestamp: 1762022005702 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libuuid-2.42.1-h5347b49_0.conda +sha256: 3f0edf1280e2f6684a986f821eaa3e123d2694a00b31b96ca0d4a4c12c129231 +md5: 7d0a66598195ef00b6efc55aefc7453b +depends: +- __glibc >=2.17,<3.0.a0 +- libgcc >=14 +license: BSD-3-Clause +license_family: BSD +size: 40163 +timestamp: 1779118517630 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libwebp-base-1.6.0-hd42ef1d_0.conda +sha256: 3aed21ab28eddffdaf7f804f49be7a7d701e8f0e46c856d801270b470820a37b +md5: aea31d2e5b1091feca96fcfe945c3cf9 +depends: +- __glibc >=2.17,<3.0.a0 +- libgcc >=14 +constrains: +- libwebp 1.6.0 +license: BSD-3-Clause +license_family: BSD +size: 429011 +timestamp: 1752159441324 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libxcb-1.17.0-h8a09558_0.conda +sha256: 666c0c431b23c6cec6e492840b176dde533d48b7e6fb8883f5071223433776aa +md5: 92ed62436b625154323d40d5f2f11dd7 +depends: +- __glibc >=2.17,<3.0.a0 +- libgcc >=13 +- pthread-stubs +- xorg-libxau >=1.0.11,<2.0a0 +- xorg-libxdmcp +license: MIT +license_family: MIT +size: 395888 +timestamp: 1727278577118 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libzlib-1.3.2-h25fd6f3_2.conda +sha256: 55044c403570f0dc26e6364de4dc5368e5f3fc7ff103e867c487e2b5ab2bcda9 +md5: d87ff7921124eccd67248aa483c23fec +depends: +- __glibc >=2.17,<3.0.a0 +constrains: +- zlib 1.3.2 *_2 +license: Zlib +license_family: Other +size: 63629 +timestamp: 1774072609062 +- conda: https://conda.anaconda.org/conda-forge/noarch/markdown-3.10.2-pyhcf101f3_0.conda +sha256: 20e0892592a3e7c683e3d66df704a9425d731486a97c34fc56af4da1106b2b6b +md5: ba0a9221ce1063f31692c07370d062f3 +depends: +- importlib-metadata >=4.4 +- python >=3.10 +- python +license: BSD-3-Clause +license_family: BSD +size: 85893 +timestamp: 1770694658918 +- conda: https://conda.anaconda.org/conda-forge/noarch/markdown-it-py-4.2.0-pyhd8ed1ab_0.conda +sha256: 0c4c35376fe920714390d46e4b8d31c876d65f18e1655899e0763ec25f2a902f +md5: 6d03368f2b2b0a5fb6839df53b2eb5e0 +depends: +- mdurl >=0.1,<1 +- python >=3.10 +license: MIT +license_family: MIT +size: 69017 +timestamp: 1778169663339 +- conda: https://conda.anaconda.org/conda-forge/linux-64/markupsafe-3.0.3-py314h67df5f8_1.conda +sha256: c279be85b59a62d5c52f5dd9a4cd43ebd08933809a8416c22c3131595607d4cf +md5: 9a17c4307d23318476d7fbf0fedc0cde +depends: +- __glibc >=2.17,<3.0.a0 +- libgcc >=14 +- python >=3.14,<3.15.0a0 +- python_abi 3.14.* *_cp314 +constrains: +- jinja2 >=3.0.0 +license: BSD-3-Clause +license_family: BSD +size: 27424 +timestamp: 1772445227915 +- conda: https://conda.anaconda.org/conda-forge/linux-64/mathjax-2.7.7-ha770c72_3.tar.bz2 +sha256: 02fef69bde69db264a12f21386612262f545b6e3e68d8f1ccec19f3eaae58edf +md5: 86e69bd82c2a2c6fd29f5ab7e02b3691 +license: Apache-2.0 +license_family: Apache +size: 22281629 +timestamp: 1662784498331 +- conda: https://conda.anaconda.org/conda-forge/noarch/mdurl-0.1.2-pyhd8ed1ab_1.conda +sha256: 78c1bbe1723449c52b7a9df1af2ee5f005209f67e40b6e1d3c7619127c43b1c7 +md5: 592132998493b3ff25fd7479396e8351 +depends: +- python >=3.9 +license: MIT +license_family: MIT +size: 14465 +timestamp: 1733255681319 +- conda: https://conda.anaconda.org/bioconda/noarch/multiqc-1.35-pyhdfd78af_1.conda +sha256: e86033aa55a9e915e2d0957e770bdb81e3feb26a227d1adb17f9d6c528da6a71 +md5: cdb20309681ba3ce8f52c110e214d4f3 +depends: +- click +- coloredlogs +- humanize +- importlib-metadata +- jinja2 >=3.0.0 +- jsonschema +- markdown +- natsort +- numpy +- packaging +- pillow >=10.2.0 +- plotly >=5.18 +- polars >=1.34.0 +- polars-runtime-compat >=1.34.0 +- pyaml-env +- pydantic >=2.7.1 +- python >=3.9,!=3.14.1 +- python-dotenv +- python-kaleido 0.2.1 +- pyyaml >=4 +- requests +- rich >=10 +- rich-click +- spectra >=0.0.10 +- tiktoken +- tqdm +- typeguard >=4 +license: GPL-3.0-or-later +license_family: GPL3 +size: 4282188 +timestamp: 1779465338806 +- conda: https://conda.anaconda.org/conda-forge/noarch/narwhals-2.21.2-pyhcf101f3_0.conda +sha256: 70f43d62450927d51673eecd8823e14f5b3cfebdb43cda1d502eba97162bab42 +md5: 6687827c332121727ce383919e1ec8c2 +depends: +- python >=3.10 +- python +license: MIT +license_family: MIT +size: 284323 +timestamp: 1778929680962 +- conda: https://conda.anaconda.org/conda-forge/noarch/natsort-8.4.0-pyhcf101f3_2.conda +sha256: aeb1548eb72e4f198e72f19d242fb695b35add2ac7b2c00e0d83687052867680 +md5: e941e85e273121222580723010bd4fa2 +depends: +- python >=3.9 +- python +license: MIT +license_family: MIT +size: 39262 +timestamp: 1770905275632 +- conda: https://conda.anaconda.org/conda-forge/linux-64/ncurses-6.6-hdb14827_0.conda +sha256: fc89f74bbe362fb29fa3c037697a89bec140b346a2469a90f7936d1d7ea4d8a3 +md5: fc21868a1a5aacc937e7a18747acb8a5 +depends: +- __glibc >=2.17,<3.0.a0 +- libgcc >=14 +license: X11 AND BSD-3-Clause +size: 918956 +timestamp: 1777422145199 +- conda: https://conda.anaconda.org/conda-forge/noarch/networkx-3.6.1-pyhcf101f3_0.conda +sha256: f6a82172afc50e54741f6f84527ef10424326611503c64e359e25a19a8e4c1c6 +md5: a2c1eeadae7a309daed9d62c96012a2b +depends: +- python >=3.11 +- python +constrains: +- numpy >=1.25 +- scipy >=1.11.2 +- matplotlib-base >=3.8 +- pandas >=2.0 +license: BSD-3-Clause +license_family: BSD +size: 1587439 +timestamp: 1765215107045 +- conda: https://conda.anaconda.org/conda-forge/linux-64/nspr-4.38-h29cc59b_0.conda +sha256: e3664264bd936c357523b55c71ed5a30263c6ba278d726a75b1eb112e6fb0b64 +md5: e235d5566c9cc8970eb2798dd4ecf62f +depends: +- __glibc >=2.17,<3.0.a0 +- libgcc >=14 +- libstdcxx >=14 +license: MPL-2.0 +license_family: MOZILLA +size: 228588 +timestamp: 1762348634537 +- conda: https://conda.anaconda.org/conda-forge/linux-64/nss-3.118-h445c969_0.conda +sha256: 44dd98ffeac859d84a6dcba79a2096193a42fc10b29b28a5115687a680dd6aea +md5: 567fbeed956c200c1db5782a424e58ee +depends: +- __glibc >=2.17,<3.0.a0 +- libgcc >=14 +- libsqlite >=3.51.0,<4.0a0 +- libstdcxx >=14 +- libzlib >=1.3.1,<2.0a0 +- nspr >=4.38,<5.0a0 +license: MPL-2.0 +license_family: MOZILLA +size: 2057773 +timestamp: 1763485556350 +- conda: https://conda.anaconda.org/conda-forge/linux-64/numpy-2.4.6-py314h2b28147_0.conda +sha256: bc61ae892973751a6b0e6ecea57ed6d7053224bddcb007165d6ceb1d7344ad47 +md5: f49b5f950379e0b97c35ca97682f7c6a +depends: +- python +- libstdcxx >=14 +- libgcc >=14 +- __glibc >=2.17,<3.0.a0 +- liblapack >=3.9.0,<4.0a0 +- python_abi 3.14.* *_cp314 +- libblas >=3.9.0,<4.0a0 +- libcblas >=3.9.0,<4.0a0 +constrains: +- numpy-base <0a0 +license: BSD-3-Clause +license_family: BSD +size: 8928909 +timestamp: 1779169198391 +- conda: https://conda.anaconda.org/conda-forge/linux-64/openjpeg-2.5.4-h55fea9a_0.conda +sha256: 3900f9f2dbbf4129cf3ad6acf4e4b6f7101390b53843591c53b00f034343bc4d +md5: 11b3379b191f63139e29c0d19dee24cd +depends: +- __glibc >=2.17,<3.0.a0 +- libgcc >=14 +- libpng >=1.6.50,<1.7.0a0 +- libstdcxx >=14 +- libtiff >=4.7.1,<4.8.0a0 +- libzlib >=1.3.1,<2.0a0 +license: BSD-2-Clause +license_family: BSD +size: 355400 +timestamp: 1758489294972 +- conda: https://conda.anaconda.org/conda-forge/linux-64/openssl-3.6.2-h35e630c_0.conda +sha256: c0ef482280e38c71a08ad6d71448194b719630345b0c9c60744a2010e8a8e0cb +md5: da1b85b6a87e141f5140bb9924cecab0 +depends: +- __glibc >=2.17,<3.0.a0 +- ca-certificates +- libgcc >=14 +license: Apache-2.0 +license_family: Apache +size: 3167099 +timestamp: 1775587756857 +- conda: https://conda.anaconda.org/conda-forge/noarch/packaging-26.2-pyhc364b38_0.conda +sha256: 3906abfb6511a3bb309e39b9b1b7bc38f50a723971de2395489fd1f379255890 +md5: 4c06a92e74452cfa53623a81592e8934 +depends: +- python >=3.8 +- python +license: Apache-2.0 +license_family: APACHE +size: 91574 +timestamp: 1777103621679 +- conda: https://conda.anaconda.org/conda-forge/linux-64/pillow-12.2.0-py314h8ec4b1a_0.conda +sha256: 123d8a7c16c88658b4f29e9f115a047598c941708dade74fbaff373a32dbec5e +md5: 76c4757c0ec9d11f969e8eb44899307b +depends: +- python +- libgcc >=14 +- __glibc >=2.17,<3.0.a0 +- libtiff >=4.7.1,<4.8.0a0 +- openjpeg >=2.5.4,<3.0a0 +- libxcb >=1.17.0,<2.0a0 +- libwebp-base >=1.6.0,<2.0a0 +- zlib-ng >=2.3.3,<2.4.0a0 +- libjpeg-turbo >=3.1.2,<4.0a0 +- python_abi 3.14.* *_cp314 +- libfreetype >=2.14.3 +- libfreetype6 >=2.14.3 +- lcms2 >=2.18,<3.0a0 +- tk >=8.6.13,<8.7.0a0 +license: HPND +size: 1082797 +timestamp: 1775060059882 +- conda: https://conda.anaconda.org/conda-forge/noarch/plotly-6.6.0-pyhd8ed1ab_0.conda +sha256: c418d325359fc7a0074cea7f081ef1bce26e114d2da8a0154c5d27ecc87a08e7 +md5: 3e9427ee186846052e81fadde8ebe96a +depends: +- narwhals >=1.15.1 +- packaging +- python >=3.10 +constrains: +- ipywidgets >=7.6 +license: MIT +license_family: MIT +size: 5251872 +timestamp: 1772628857717 +- conda: https://conda.anaconda.org/conda-forge/noarch/polars-1.41.0-pyh58ad624_0.conda +sha256: 70fc56877c4a095ee658d61924d8019768fbae4a48437058d181fc94b0a7c4d8 +md5: 25a883fed9f1f3f21ff317a3e7c92ac4 +depends: +- polars-runtime-32 ==1.41.0 +- python >=3.10 +- python +constrains: +- numpy >=1.16.0 +- pyarrow >=7.0.0 +- fastexcel >=0.9 +- openpyxl >=3.0.0 +- xlsx2csv >=0.8.0 +- connectorx >=0.3.2 +- deltalake >=1.0.0 +- pyiceberg >=0.7.1 +- altair >=5.4.0 +- great_tables >=0.8.0 +- polars-runtime-32 ==1.41.0 +- polars-runtime-64 ==1.41.0 +- polars-runtime-compat ==1.41.0 +license: MIT +size: 539656 +timestamp: 1779630790562 +- conda: https://conda.anaconda.org/conda-forge/linux-64/polars-runtime-32-1.41.0-py310h49dadd8_0.conda +noarch: python +sha256: e51ee3fe5259f2e115b2f78f8fbe3554e419c7c82b0c110878e12a5ff95ce3ab +md5: 7682765a1588e5ac887c99736d297c93 +depends: +- python +- __glibc >=2.17,<3.0.a0 +- libstdcxx >=14 +- libgcc >=14 +- _python_abi3_support 1.* +- cpython >=3.10 +constrains: +- __glibc >=2.17 +license: MIT +size: 42578921 +timestamp: 1779630790562 +- conda: https://conda.anaconda.org/conda-forge/linux-64/polars-runtime-compat-1.41.0-py310hcbd6021_0.conda +noarch: python +sha256: 29c3831c92394af11d9f7d04882dda9479ffbb76a3d36ba155d52159d67805fa +md5: cb0b620c9914a07a9022cb8b183ea9ee +depends: +- python +- libstdcxx >=14 +- libgcc >=14 +- __glibc >=2.17,<3.0.a0 +- _python_abi3_support 1.* +- cpython >=3.10 +constrains: +- __glibc >=2.17 +license: MIT +size: 41864944 +timestamp: 1779630722548 +- conda: https://conda.anaconda.org/conda-forge/linux-64/procps-ng-4.0.6-h18c060e_0.conda +sha256: 4ce2e1ee31a6217998f78c31ce7dc0a3e0557d9238b51d49dd20c52d467a126d +md5: f2c23a77b25efcad57d377b34bd84941 +depends: +- __glibc >=2.17,<3.0.a0 +- libgcc >=14 +- ncurses >=6.5,<7.0a0 +license: GPL-2.0-or-later AND LGPL-2.0-or-later +license_family: GPL +size: 593603 +timestamp: 1769710381284 +- conda: https://conda.anaconda.org/conda-forge/linux-64/pthread-stubs-0.4-hb9d3cd8_1002.conda +sha256: 9c88f8c64590e9567c6c80823f0328e58d3b1efb0e1c539c0315ceca764e0973 +md5: b3c17d95b5a10c6e64a21fa17573e70e +depends: +- __glibc >=2.17,<3.0.a0 +- libgcc >=13 +license: MIT +license_family: MIT +size: 8252 +timestamp: 1726802366959 +- conda: https://conda.anaconda.org/conda-forge/noarch/pyaml-env-1.2.2-pyhd8ed1ab_0.conda +sha256: 58994e0d2ea8584cb399546e6f6896d771995e6121d1a7b6a2c9948388358932 +md5: e17be1016bcc3516827b836cd3e4d9dc +depends: +- python >=3.9 +- pyyaml >=5.0,<=7.0 +license: MIT +license_family: MIT +size: 14645 +timestamp: 1736766960536 +- conda: https://conda.anaconda.org/conda-forge/noarch/pydantic-2.13.4-pyhcf101f3_0.conda +sha256: 69700e31165df070e9716315e042196aa92525dae5deb5107785847ab9f4189f +md5: 729843edafc0899b3348bd3f19525b9d +depends: +- typing-inspection >=0.4.2 +- typing_extensions >=4.14.1 +- python >=3.10 +- annotated-types >=0.6.0 +- pydantic-core ==2.46.4 +- python +license: MIT +license_family: MIT +size: 346511 +timestamp: 1778103405862 +- conda: https://conda.anaconda.org/conda-forge/linux-64/pydantic-core-2.46.4-py314h2e6c369_0.conda +sha256: 802e216c39f1359aed60823b6e11d8ccd812b0ae1c81ae5ac1c81f99446409ab +md5: 0c96993dbeadf3a277cf757b9f1c9412 +depends: +- python +- typing-extensions >=4.6.0,!=4.7.0 +- libgcc >=14 +- __glibc >=2.17,<3.0.a0 +- python_abi 3.14.* *_cp314 +constrains: +- __glibc >=2.17 +license: MIT +license_family: MIT +size: 1895020 +timestamp: 1778084229247 +- conda: https://conda.anaconda.org/conda-forge/noarch/pygments-2.20.0-pyhd8ed1ab_0.conda +sha256: cf70b2f5ad9ae472b71235e5c8a736c9316df3705746de419b59d442e8348e86 +md5: 16c18772b340887160c79a6acc022db0 +depends: +- python >=3.10 +license: BSD-2-Clause +license_family: BSD +size: 893031 +timestamp: 1774796815820 +- conda: https://conda.anaconda.org/conda-forge/noarch/pysocks-1.7.1-pyha55dd90_7.conda +sha256: ba3b032fa52709ce0d9fd388f63d330a026754587a2f461117cac9ab73d8d0d8 +md5: 461219d1a5bd61342293efa2c0c90eac +depends: +- __unix +- python >=3.9 +license: BSD-3-Clause +license_family: BSD +size: 21085 +timestamp: 1733217331982 +- conda: https://conda.anaconda.org/conda-forge/linux-64/python-3.14.5-habeac84_100_cp314.conda +build_number: 100 +sha256: 55eed9bf2a3f6e90311276f0834737fe7c2d9ec3e5e2e557507858df4c7521e6 +md5: da92e59ff92f2d5ede4f612af20f583f +depends: +- __glibc >=2.17,<3.0.a0 +- bzip2 >=1.0.8,<2.0a0 +- ld_impl_linux-64 >=2.36.1 +- libexpat >=2.8.0,<3.0a0 +- libffi >=3.5.2,<3.6.0a0 +- libgcc >=14 +- liblzma >=5.8.3,<6.0a0 +- libmpdec >=4.0.0,<5.0a0 +- libsqlite >=3.53.1,<4.0a0 +- libuuid >=2.42.1,<3.0a0 +- libzlib >=1.3.2,<2.0a0 +- ncurses >=6.6,<7.0a0 +- openssl >=3.5.6,<4.0a0 +- python_abi 3.14.* *_cp314 +- readline >=8.3,<9.0a0 +- tk >=8.6.13,<8.7.0a0 +- tzdata +- zstd >=1.5.7,<1.6.0a0 +license: Python-2.0 +size: 36745188 +timestamp: 1779236923603 +python_site_packages_path: lib/python3.14/site-packages +- conda: https://conda.anaconda.org/conda-forge/noarch/python-dotenv-1.2.2-pyhcf101f3_0.conda +sha256: 74e417a768f59f02a242c25e7db0aa796627b5bc8c818863b57786072aeb85e5 +md5: 130584ad9f3a513cdd71b1fdc1244e9c +depends: +- python >=3.10 +license: BSD-3-Clause +license_family: BSD +size: 27848 +timestamp: 1772388605021 +- conda: https://conda.anaconda.org/conda-forge/noarch/python-gil-3.14.5-h4df99d1_100.conda +sha256: 41dd7da285d71d519257fa7dacb1cae060d5ebfaa5f92cba5994899d2978e943 +md5: 41954747ba952ec4b01e16c2c9e8d8ff +depends: +- cpython 3.14.5.* +- python_abi * *_cp314 +license: Python-2.0 +size: 50212 +timestamp: 1779236703009 +- conda: https://conda.anaconda.org/conda-forge/noarch/python-kaleido-0.2.1-pyhd8ed1ab_0.tar.bz2 +sha256: e17bf63a30aec33432f1ead86e15e9febde9fc40a7f869c0e766be8d2db44170 +md5: 310259a5b03ff02289d7705f39e2b1d2 +depends: +- kaleido-core 0.2.1.* +- python >=3.5 +license: MIT +license_family: MIT +size: 18320 +timestamp: 1615204747600 +- conda: https://conda.anaconda.org/conda-forge/noarch/python_abi-3.14-8_cp314.conda +build_number: 8 +sha256: ad6d2e9ac39751cc0529dd1566a26751a0bf2542adb0c232533d32e176e21db5 +md5: 0539938c55b6b1a59b560e843ad864a4 +constrains: +- python 3.14.* *_cp314 +license: BSD-3-Clause +license_family: BSD +size: 6989 +timestamp: 1752805904792 +- conda: https://conda.anaconda.org/conda-forge/linux-64/pyyaml-6.0.3-py314h67df5f8_1.conda +sha256: b318fb070c7a1f89980ef124b80a0b5ccf3928143708a85e0053cde0169c699d +md5: 2035f68f96be30dc60a5dfd7452c7941 +depends: +- __glibc >=2.17,<3.0.a0 +- libgcc >=14 +- python >=3.14,<3.15.0a0 +- python_abi 3.14.* *_cp314 +- yaml >=0.2.5,<0.3.0a0 +license: MIT +license_family: MIT +size: 202391 +timestamp: 1770223462836 +- conda: https://conda.anaconda.org/conda-forge/linux-64/readline-8.3-h853b02a_0.conda +sha256: 12ffde5a6f958e285aa22c191ca01bbd3d6e710aa852e00618fa6ddc59149002 +md5: d7d95fc8287ea7bf33e0e7116d2b95ec +depends: +- __glibc >=2.17,<3.0.a0 +- libgcc >=14 +- ncurses >=6.5,<7.0a0 +license: GPL-3.0-only +license_family: GPL +size: 345073 +timestamp: 1765813471974 +- conda: https://conda.anaconda.org/conda-forge/noarch/referencing-0.37.0-pyhcf101f3_0.conda +sha256: 0577eedfb347ff94d0f2fa6c052c502989b028216996b45c7f21236f25864414 +md5: 870293df500ca7e18bedefa5838a22ab +depends: +- attrs >=22.2.0 +- python >=3.10 +- rpds-py >=0.7.0 +- typing_extensions >=4.4.0 +- python +license: MIT +license_family: MIT +size: 51788 +timestamp: 1760379115194 +- conda: https://conda.anaconda.org/conda-forge/linux-64/regex-2026.5.9-py314h5bd0f2a_0.conda +sha256: c7a4aca4977c15c82d053b06cbc676460974c1b25757cfeea8a9a2497ac911f8 +md5: 9dd235b6ac69a0198080dac39f9891aa +depends: +- __glibc >=2.17,<3.0.a0 +- libgcc >=14 +- python >=3.14,<3.15.0a0 +- python_abi 3.14.* *_cp314 +license: Apache-2.0 AND CNRI-Python +license_family: PSF +size: 413611 +timestamp: 1778374155646 +- conda: https://conda.anaconda.org/conda-forge/noarch/requests-2.34.2-pyhcf101f3_0.conda +sha256: 1715246b19c9f85ee022933b4845f2fc14ac9184981b7b7d9b728bec8e9588da +md5: 4a85203c1d80c1059086ae860836ffb9 +depends: +- python >=3.10 +- certifi >=2023.5.7 +- charset-normalizer >=2,<4 +- idna >=2.5,<4 +- urllib3 >=1.26,<3 +- python +constrains: +- chardet >=3.0.2,<8 +license: Apache-2.0 +license_family: APACHE +size: 68709 +timestamp: 1778851103479 +- conda: https://conda.anaconda.org/conda-forge/noarch/rich-15.0.0-pyhcf101f3_0.conda +sha256: 3d6ba2c0fcdac3196ba2f0615b4104e532525ffa1335b50a2878be5ff488814a +md5: 0242025a3c804966bf71aa04eee82f66 +depends: +- markdown-it-py >=2.2.0 +- pygments >=2.13.0,<3.0.0 +- python >=3.10 +- typing_extensions >=4.0.0,<5.0.0 +- python +license: MIT +license_family: MIT +size: 208577 +timestamp: 1775991661559 +- conda: https://conda.anaconda.org/conda-forge/noarch/rich-click-1.9.7-pyh8f84b5b_0.conda +sha256: aa3fcb167321bae51998de2e94d199109c9024f25a5a063cb1c28d8f1af33436 +md5: 0c20a8ebcddb24a45da89d5e917e6cb9 +depends: +- python >=3.10 +- rich >=12 +- click >=8 +- typing-extensions >=4 +- __unix +- python +license: MIT +license_family: MIT +size: 64356 +timestamp: 1769850479089 +- conda: https://conda.anaconda.org/conda-forge/linux-64/rpds-py-0.30.0-py314h2e6c369_0.conda +sha256: e53b0cbf3b324eaa03ca1fe1a688fdf4ab42cea9c25270b0a7307d8aaaa4f446 +md5: c1c368b5437b0d1a68f372ccf01cb133 +depends: +- python +- libgcc >=14 +- __glibc >=2.17,<3.0.a0 +- python_abi 3.14.* *_cp314 +constrains: +- __glibc >=2.17 +license: MIT +license_family: MIT +size: 376121 +timestamp: 1764543122774 +- conda: https://conda.anaconda.org/conda-forge/noarch/spectra-0.0.11-pyhd8ed1ab_2.conda +sha256: 7c65782d2511738e62c70462e89d65da4fa54d5a7e47c46667bcd27a59f81876 +md5: 472239e4eb7b5a84bb96b3ed7e3a596a +depends: +- colormath >=3.0.0 +- python >=3.9 +license: MIT +license_family: MIT +size: 22284 +timestamp: 1735770589188 +- conda: https://conda.anaconda.org/conda-forge/linux-64/sqlite-3.53.1-hbc0de68_0.conda +sha256: d167fa92781bcdcd3b9aaa6bb1cd50c5b108f6190c170098a118b5cf5df2f881 +md5: 8e0b8654ead18e50af552e54b5a08a61 +depends: +- __glibc >=2.17,<3.0.a0 +- libgcc >=14 +- libsqlite 3.53.1 h0c1763c_0 +- libzlib >=1.3.2,<2.0a0 +- ncurses >=6.6,<7.0a0 +- readline >=8.3,<9.0a0 +license: blessing +size: 205399 +timestamp: 1777986477546 +- conda: https://conda.anaconda.org/conda-forge/linux-64/tiktoken-0.12.0-py314h67fec18_3.conda +sha256: 7e395d67fd249d901beb1ae269057763c0d8c3ee5f7a348694bdb16d158a37d9 +md5: d705f9d8a1185a2b01cced191177a028 +depends: +- __glibc >=2.17,<3.0.a0 +- libgcc >=14 +- libstdcxx >=14 +- python >=3.14,<3.15.0a0 +- python_abi 3.14.* *_cp314 +- regex >=2022.1.18 +- requests >=2.26.0 +constrains: +- __glibc >=2.17 +license: MIT +license_family: MIT +size: 939648 +timestamp: 1764028306357 +- conda: https://conda.anaconda.org/conda-forge/linux-64/tk-8.6.13-noxft_h366c992_103.conda +sha256: cafeec44494f842ffeca27e9c8b0c27ed714f93ac77ddadc6aaf726b5554ebac +md5: cffd3bdd58090148f4cfcd831f4b26ab +depends: +- __glibc >=2.17,<3.0.a0 +- libgcc >=14 +- libzlib >=1.3.1,<2.0a0 +constrains: +- xorg-libx11 >=1.8.12,<2.0a0 +license: TCL +license_family: BSD +size: 3301196 +timestamp: 1769460227866 +- conda: https://conda.anaconda.org/conda-forge/noarch/tqdm-4.67.3-pyh8f84b5b_0.conda +sha256: 9ef8e47cf00e4d6dcc114eb32a1504cc18206300572ef14d76634ba29dfe1eb6 +md5: e5ce43272193b38c2e9037446c1d9206 +depends: +- python >=3.10 +- __unix +- python +license: MPL-2.0 and MIT +size: 94132 +timestamp: 1770153424136 +- conda: https://conda.anaconda.org/conda-forge/noarch/typeguard-4.5.2-pyhcf101f3_0.conda +sha256: 59d7851d32fddb5b510272e6557aa982edeb927d349648dac27f5bf01d18bb26 +md5: 4460f039b7dedf15f7df086446ca75ae +depends: +- typing_extensions >=4.14.0 +- python >=3.10 +- importlib-metadata >=3.6 +- python +constrains: +- pytest >=7 +license: MIT +license_family: MIT +size: 38297 +timestamp: 1778779291237 +- conda: https://conda.anaconda.org/conda-forge/noarch/typing-extensions-4.15.0-h396c80c_0.conda +sha256: 7c2df5721c742c2a47b2c8f960e718c930031663ac1174da67c1ed5999f7938c +md5: edd329d7d3a4ab45dcf905899a7a6115 +depends: +- typing_extensions ==4.15.0 pyhcf101f3_0 +license: PSF-2.0 +license_family: PSF +size: 91383 +timestamp: 1756220668932 +- conda: https://conda.anaconda.org/conda-forge/noarch/typing-inspection-0.4.2-pyhcf101f3_2.conda +sha256: 8b90d2f19f9458b8c58a55e1fcdc1d90c1603a847a47654d8a454549413ba60a +md5: 53f5409c5cfd6c5a66417d68e3f0a864 +depends: +- python >=3.10 +- typing_extensions >=4.12.0 +- python +license: MIT +license_family: MIT +size: 20935 +timestamp: 1777105465795 +- conda: https://conda.anaconda.org/conda-forge/noarch/typing_extensions-4.15.0-pyhcf101f3_0.conda +sha256: 032271135bca55aeb156cee361c81350c6f3fb203f57d024d7e5a1fc9ef18731 +md5: 0caa1af407ecff61170c9437a808404d +depends: +- python >=3.10 +- python +license: PSF-2.0 +license_family: PSF +size: 51692 +timestamp: 1756220668932 +- conda: https://conda.anaconda.org/conda-forge/noarch/tzdata-2025c-hc9c84f9_1.conda +sha256: 1d30098909076af33a35017eed6f2953af1c769e273a0626a04722ac4acaba3c +md5: ad659d0a2b3e47e38d829aa8cad2d610 +license: LicenseRef-Public-Domain +size: 119135 +timestamp: 1767016325805 +- conda: https://conda.anaconda.org/conda-forge/noarch/urllib3-2.7.0-pyhd8ed1ab_0.conda +sha256: feff959a816f7988a0893201aa9727bbb7ee1e9cec2c4f0428269b489eb93fb4 +md5: cbb88288f74dbe6ada1c6c7d0a97223e +depends: +- backports.zstd >=1.0.0 +- brotli-python >=1.2.0 +- h2 >=4,<5 +- pysocks >=1.5.6,<2.0,!=1.5.7 +- python >=3.10 +license: MIT +license_family: MIT +size: 103560 +timestamp: 1778188657149 +- conda: https://conda.anaconda.org/conda-forge/linux-64/xorg-libxau-1.0.12-hb03c661_1.conda +sha256: 6bc6ab7a90a5d8ac94c7e300cc10beb0500eeba4b99822768ca2f2ef356f731b +md5: b2895afaf55bf96a8c8282a2e47a5de0 +depends: +- __glibc >=2.17,<3.0.a0 +- libgcc >=14 +license: MIT +license_family: MIT +size: 15321 +timestamp: 1762976464266 +- conda: https://conda.anaconda.org/conda-forge/linux-64/xorg-libxdmcp-1.1.5-hb03c661_1.conda +sha256: 25d255fb2eef929d21ff660a0c687d38a6d2ccfbcbf0cc6aa738b12af6e9d142 +md5: 1dafce8548e38671bea82e3f5c6ce22f +depends: +- __glibc >=2.17,<3.0.a0 +- libgcc >=14 +license: MIT +license_family: MIT +size: 20591 +timestamp: 1762976546182 +- conda: https://conda.anaconda.org/conda-forge/linux-64/yaml-0.2.5-h280c20c_3.conda +sha256: 6d9ea2f731e284e9316d95fa61869fe7bbba33df7929f82693c121022810f4ad +md5: a77f85f77be52ff59391544bfe73390a +depends: +- libgcc >=14 +- __glibc >=2.17,<3.0.a0 +license: MIT +license_family: MIT +size: 85189 +timestamp: 1753484064210 +- conda: https://conda.anaconda.org/conda-forge/noarch/zipp-4.1.0-pyhcf101f3_0.conda +sha256: 210bd31c22bb88f5e2a167df24c95bb5f152b2ada7502f9b8c49d1f5366db423 +md5: ba3dcdc8584155c97c648ae9c044b7a3 +depends: +- python >=3.10 +- python +license: MIT +license_family: MIT +size: 24190 +timestamp: 1779159948016 +- conda: https://conda.anaconda.org/conda-forge/linux-64/zlib-ng-2.3.3-hceb46e0_1.conda +sha256: ea4e50c465d70236408cb0bfe0115609fd14db1adcd8bd30d8918e0291f8a75f +md5: 2aadb0d17215603a82a2a6b0afd9a4cb +depends: +- __glibc >=2.17,<3.0.a0 +- libgcc >=14 +- libstdcxx >=14 +license: Zlib +license_family: Other +size: 122618 +timestamp: 1770167931827 +- conda: https://conda.anaconda.org/conda-forge/linux-64/zstd-1.5.7-hb78ec9c_6.conda +sha256: 68f0206ca6e98fea941e5717cec780ed2873ffabc0e1ed34428c061e2c6268c7 +md5: 4a13eeac0b5c8e5b8ab496e6c4ddd829 +depends: +- __glibc >=2.17,<3.0.a0 +- libzlib >=1.3.1,<2.0a0 +license: BSD-3-Clause +license_family: BSD +size: 601375 +timestamp: 1764777111296 diff --git a/modules/nf-core/multiqc/.conda-lock/linux_arm64-bd-5c84a5000a226ab5_1.txt b/modules/nf-core/multiqc/.conda-lock/linux_arm64-bd-5c84a5000a226ab5_1.txt new file mode 100644 index 00000000..3d5b93db --- /dev/null +++ b/modules/nf-core/multiqc/.conda-lock/linux_arm64-bd-5c84a5000a226ab5_1.txt @@ -0,0 +1,1476 @@ + +version: 6 +environments: +default: +channels: +- url: https://conda.anaconda.org/conda-forge/ +- url: https://conda.anaconda.org/bioconda/ +- url: https://conda.anaconda.org/bioconda/ +options: +pypi-prerelease-mode: if-necessary-or-explicit +packages: +linux-aarch64: +- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/_openmp_mutex-4.5-20_gnu.conda +- conda: https://conda.anaconda.org/conda-forge/noarch/_python_abi3_support-1.0-hd8ed1ab_2.conda +- conda: https://conda.anaconda.org/conda-forge/noarch/annotated-types-0.7.0-pyhd8ed1ab_1.conda +- conda: https://conda.anaconda.org/conda-forge/noarch/attrs-26.1.0-pyhcf101f3_0.conda +- conda: https://conda.anaconda.org/conda-forge/noarch/backports.zstd-1.5.0-py314h680f03e_0.conda +- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/brotli-python-1.2.0-py314h352cb57_1.conda +- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/bzip2-1.0.8-h4777abc_9.conda +- conda: https://conda.anaconda.org/conda-forge/noarch/ca-certificates-2026.5.20-hbd8a1cb_0.conda +- conda: https://conda.anaconda.org/conda-forge/noarch/certifi-2026.5.20-pyhd8ed1ab_0.conda +- conda: https://conda.anaconda.org/conda-forge/noarch/charset-normalizer-3.4.7-pyhd8ed1ab_0.conda +- conda: https://conda.anaconda.org/conda-forge/noarch/click-8.4.0-pyhc90fa1f_0.conda +- conda: https://conda.anaconda.org/conda-forge/noarch/coloredlogs-15.0.1-pyhd8ed1ab_4.conda +- conda: https://conda.anaconda.org/conda-forge/noarch/colormath-3.0.0-pyhd8ed1ab_4.conda +- conda: https://conda.anaconda.org/conda-forge/noarch/cpython-3.14.5-py314hd8ed1ab_100.conda +- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/expat-2.8.1-hfae3067_0.conda +- conda: https://conda.anaconda.org/conda-forge/noarch/font-ttf-dejavu-sans-mono-2.37-hab24e00_0.tar.bz2 +- conda: https://conda.anaconda.org/conda-forge/noarch/font-ttf-inconsolata-3.000-h77eed37_0.tar.bz2 +- conda: https://conda.anaconda.org/conda-forge/noarch/font-ttf-source-code-pro-2.038-h77eed37_0.tar.bz2 +- conda: https://conda.anaconda.org/conda-forge/noarch/font-ttf-ubuntu-0.83-h77eed37_3.conda +- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/fontconfig-2.18.0-hba86a56_0.conda +- conda: https://conda.anaconda.org/conda-forge/noarch/fonts-conda-forge-1-hc364b38_1.conda +- conda: https://conda.anaconda.org/conda-forge/noarch/h2-4.3.0-pyhcf101f3_0.conda +- conda: https://conda.anaconda.org/conda-forge/noarch/hpack-4.1.0-pyhd8ed1ab_0.conda +- conda: https://conda.anaconda.org/conda-forge/noarch/humanfriendly-10.0-pyh707e725_8.conda +- conda: https://conda.anaconda.org/conda-forge/noarch/humanize-4.15.0-pyhd8ed1ab_0.conda +- conda: https://conda.anaconda.org/conda-forge/noarch/hyperframe-6.1.0-pyhd8ed1ab_0.conda +- conda: https://conda.anaconda.org/conda-forge/noarch/idna-3.15-pyhcf101f3_0.conda +- conda: https://conda.anaconda.org/conda-forge/noarch/importlib-metadata-9.0.0-pyhcf101f3_0.conda +- conda: https://conda.anaconda.org/conda-forge/noarch/jinja2-3.1.6-pyhcf101f3_1.conda +- conda: https://conda.anaconda.org/conda-forge/noarch/jsonschema-4.26.0-pyhcf101f3_0.conda +- conda: https://conda.anaconda.org/conda-forge/noarch/jsonschema-specifications-2025.9.1-pyhcf101f3_0.conda +- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/kaleido-core-0.2.1-he5a581e_0.tar.bz2 +- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/lcms2-2.19.1-h9d5b58d_0.conda +- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/ld_impl_linux-aarch64-2.45.1-default_h1979696_102.conda +- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/lerc-4.1.0-h52b7260_0.conda +- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/libblas-3.11.0-7_haddc8a3_openblas.conda +- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/libcblas-3.11.0-7_hd72aa62_openblas.conda +- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/libdeflate-1.25-h1af38f5_0.conda +- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/libexpat-2.8.1-hfae3067_0.conda +- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/libffi-3.5.2-h376a255_0.conda +- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/libfreetype-2.14.3-h8af1aa0_0.conda +- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/libfreetype6-2.14.3-hdae7a39_0.conda +- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/libgcc-15.2.0-h8acb6b2_19.conda +- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/libgcc-ng-15.2.0-he9431aa_19.conda +- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/libgfortran-15.2.0-he9431aa_19.conda +- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/libgfortran5-15.2.0-h1b7bec0_19.conda +- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/libgomp-15.2.0-h8acb6b2_19.conda +- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/libjpeg-turbo-3.1.4.1-he30d5cf_0.conda +- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/liblapack-3.11.0-7_h88aeb00_openblas.conda +- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/liblzma-5.8.3-he30d5cf_0.conda +- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/libmpdec-4.0.0-he30d5cf_1.conda +- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/libopenblas-0.3.33-pthreads_h9d3fd7e_0.conda +- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/libpng-1.6.58-h1abf092_0.conda +- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/libsqlite-3.53.1-h022381a_0.conda +- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/libstdcxx-15.2.0-hef695bb_19.conda +- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/libtiff-4.7.1-hdb009f0_1.conda +- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/libuuid-2.42.1-h1022ec0_0.conda +- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/libwebp-base-1.6.0-ha2e29f5_0.conda +- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/libxcb-1.17.0-h262b8f6_0.conda +- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/libzlib-1.3.2-hdc9db2a_2.conda +- conda: https://conda.anaconda.org/conda-forge/noarch/markdown-3.10.2-pyhcf101f3_0.conda +- conda: https://conda.anaconda.org/conda-forge/noarch/markdown-it-py-4.2.0-pyhd8ed1ab_0.conda +- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/markupsafe-3.0.3-py314hb76de3f_1.conda +- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/mathjax-2.7.7-h8af1aa0_3.tar.bz2 +- conda: https://conda.anaconda.org/conda-forge/noarch/mdurl-0.1.2-pyhd8ed1ab_1.conda +- conda: https://conda.anaconda.org/bioconda/noarch/multiqc-1.35-pyhdfd78af_1.conda +- conda: https://conda.anaconda.org/conda-forge/noarch/narwhals-2.21.2-pyhcf101f3_0.conda +- conda: https://conda.anaconda.org/conda-forge/noarch/natsort-8.4.0-pyhcf101f3_2.conda +- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/ncurses-6.6-hf8d1292_0.conda +- conda: https://conda.anaconda.org/conda-forge/noarch/networkx-3.6.1-pyhcf101f3_0.conda +- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/nspr-4.38-h3ad9384_0.conda +- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/nss-3.118-h544fa81_0.conda +- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/numpy-2.4.6-py314he1698a1_0.conda +- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/openjpeg-2.5.4-h5da879a_0.conda +- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/openssl-3.6.2-h546c87b_0.conda +- conda: https://conda.anaconda.org/conda-forge/noarch/packaging-26.2-pyhc364b38_0.conda +- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/pillow-12.2.0-py314hac3e5ec_0.conda +- conda: https://conda.anaconda.org/conda-forge/noarch/plotly-6.6.0-pyhd8ed1ab_0.conda +- conda: https://conda.anaconda.org/conda-forge/noarch/polars-1.41.0-pyh58ad624_0.conda +- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/polars-runtime-32-1.41.0-py310h32c7c23_0.conda +- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/polars-runtime-compat-1.41.0-py310hc0e61be_0.conda +- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/procps-ng-4.0.6-h1779866_0.conda +- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/pthread-stubs-0.4-h86ecc28_1002.conda +- conda: https://conda.anaconda.org/conda-forge/noarch/pyaml-env-1.2.2-pyhd8ed1ab_0.conda +- conda: https://conda.anaconda.org/conda-forge/noarch/pydantic-2.13.4-pyhcf101f3_0.conda +- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/pydantic-core-2.46.4-py314h451b6cc_0.conda +- conda: https://conda.anaconda.org/conda-forge/noarch/pygments-2.20.0-pyhd8ed1ab_0.conda +- conda: https://conda.anaconda.org/conda-forge/noarch/pysocks-1.7.1-pyha55dd90_7.conda +- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/python-3.14.5-hfd9ac0a_100_cp314.conda +- conda: https://conda.anaconda.org/conda-forge/noarch/python-dotenv-1.2.2-pyhcf101f3_0.conda +- conda: https://conda.anaconda.org/conda-forge/noarch/python-gil-3.14.5-h4df99d1_100.conda +- conda: https://conda.anaconda.org/conda-forge/noarch/python-kaleido-0.2.1-pyhd8ed1ab_0.tar.bz2 +- conda: https://conda.anaconda.org/conda-forge/noarch/python_abi-3.14-8_cp314.conda +- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/pyyaml-6.0.3-py314h807365f_1.conda +- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/readline-8.3-hb682ff5_0.conda +- conda: https://conda.anaconda.org/conda-forge/noarch/referencing-0.37.0-pyhcf101f3_0.conda +- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/regex-2026.5.9-py314h51f160d_0.conda +- conda: https://conda.anaconda.org/conda-forge/noarch/requests-2.34.2-pyhcf101f3_0.conda +- conda: https://conda.anaconda.org/conda-forge/noarch/rich-15.0.0-pyhcf101f3_0.conda +- conda: https://conda.anaconda.org/conda-forge/noarch/rich-click-1.9.7-pyh8f84b5b_0.conda +- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/rpds-py-0.30.0-py314h02b7a91_0.conda +- conda: https://conda.anaconda.org/conda-forge/noarch/spectra-0.0.11-pyhd8ed1ab_2.conda +- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/sqlite-3.53.1-he8854b5_0.conda +- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/tiktoken-0.12.0-py314h6a36e60_3.conda +- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/tk-8.6.13-noxft_h0dc03b3_103.conda +- conda: https://conda.anaconda.org/conda-forge/noarch/tqdm-4.67.3-pyh8f84b5b_0.conda +- conda: https://conda.anaconda.org/conda-forge/noarch/typeguard-4.5.2-pyhcf101f3_0.conda +- conda: https://conda.anaconda.org/conda-forge/noarch/typing-extensions-4.15.0-h396c80c_0.conda +- conda: https://conda.anaconda.org/conda-forge/noarch/typing-inspection-0.4.2-pyhcf101f3_2.conda +- conda: https://conda.anaconda.org/conda-forge/noarch/typing_extensions-4.15.0-pyhcf101f3_0.conda +- conda: https://conda.anaconda.org/conda-forge/noarch/tzdata-2025c-hc9c84f9_1.conda +- conda: https://conda.anaconda.org/conda-forge/noarch/urllib3-2.7.0-pyhd8ed1ab_0.conda +- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/xorg-libxau-1.0.12-he30d5cf_1.conda +- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/xorg-libxdmcp-1.1.5-he30d5cf_1.conda +- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/yaml-0.2.5-h80f16a2_3.conda +- conda: https://conda.anaconda.org/conda-forge/noarch/zipp-4.1.0-pyhcf101f3_0.conda +- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/zlib-ng-2.3.3-ha7cb516_1.conda +- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/zstd-1.5.7-h85ac4a6_6.conda +packages: +- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/_openmp_mutex-4.5-20_gnu.conda +build_number: 20 +sha256: a2527b1d81792a0ccd2c05850960df119c2b6d8f5fdec97f2db7d25dc23b1068 +md5: 468fd3bb9e1f671d36c2cbc677e56f1d +depends: +- libgomp >=7.5.0 +constrains: +- openmp_impl <0.0a0 +license: BSD-3-Clause +license_family: BSD +size: 28926 +timestamp: 1770939656741 +- conda: https://conda.anaconda.org/conda-forge/noarch/_python_abi3_support-1.0-hd8ed1ab_2.conda +sha256: a3967b937b9abf0f2a99f3173fa4630293979bd1644709d89580e7c62a544661 +md5: aaa2a381ccc56eac91d63b6c1240312f +depends: +- cpython +- python-gil +license: MIT +license_family: MIT +size: 8191 +timestamp: 1744137672556 +- conda: https://conda.anaconda.org/conda-forge/noarch/annotated-types-0.7.0-pyhd8ed1ab_1.conda +sha256: e0ea1ba78fbb64f17062601edda82097fcf815012cf52bb704150a2668110d48 +md5: 2934f256a8acfe48f6ebb4fce6cde29c +depends: +- python >=3.9 +- typing-extensions >=4.0.0 +license: MIT +license_family: MIT +size: 18074 +timestamp: 1733247158254 +- conda: https://conda.anaconda.org/conda-forge/noarch/attrs-26.1.0-pyhcf101f3_0.conda +sha256: 1b6124230bb4e571b1b9401537ecff575b7b109cc3a21ee019f65e083b8399ab +md5: c6b0543676ecb1fb2d7643941fe375f2 +depends: +- python >=3.10 +- python +license: MIT +license_family: MIT +size: 64927 +timestamp: 1773935801332 +- conda: https://conda.anaconda.org/conda-forge/noarch/backports.zstd-1.5.0-py314h680f03e_0.conda +noarch: generic +sha256: a1c97297e867776760489537bc5ae36fa83a154be30e3b79385a39ca4cb058fe +md5: 1133126d840e75287d83947be3fc3e71 +depends: +- python >=3.14 +license: BSD-3-Clause AND MIT AND EPL-2.0 +size: 7533 +timestamp: 1778594057496 +- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/brotli-python-1.2.0-py314h352cb57_1.conda +sha256: 5a5b0cdcd7ed89c6a8fb830924967f6314a2b71944bc1ebc2c105781ba97aa75 +md5: a1b5c571a0923a205d663d8678df4792 +depends: +- libgcc >=14 +- libstdcxx >=14 +- python >=3.14,<3.15.0a0 +- python >=3.14,<3.15.0a0 *_cp314 +- python_abi 3.14.* *_cp314 +constrains: +- libbrotlicommon 1.2.0 he30d5cf_1 +license: MIT +license_family: MIT +size: 373193 +timestamp: 1764017486851 +- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/bzip2-1.0.8-h4777abc_9.conda +sha256: b3495077889dde6bb370938e7db82be545c73e8589696ad0843a32221520ad4c +md5: 840d8fc0d7b3209be93080bc20e07f2d +depends: +- libgcc >=14 +license: bzip2-1.0.6 +license_family: BSD +size: 192412 +timestamp: 1771350241232 +- conda: https://conda.anaconda.org/conda-forge/noarch/ca-certificates-2026.5.20-hbd8a1cb_0.conda +sha256: 9812a303a1395e1dafbd92e5bc8a1ff6013bcbba0a09c7f03a8d23e43560aa9b +md5: 489b8e97e666c93f68fdb35c3c9b957f +depends: +- __unix +license: ISC +size: 129868 +timestamp: 1779289852439 +- conda: https://conda.anaconda.org/conda-forge/noarch/certifi-2026.5.20-pyhd8ed1ab_0.conda +sha256: 645655a3510e38e625da136595f3f16f2130c3263630cc3bc8f60f619ddbe490 +md5: 9fefff2f745ea1cc2ef15211a20c054a +depends: +- python >=3.10 +license: ISC +size: 134201 +timestamp: 1779285131141 +- conda: https://conda.anaconda.org/conda-forge/noarch/charset-normalizer-3.4.7-pyhd8ed1ab_0.conda +sha256: 3f9483d62ce24ecd063f8a5a714448445dc8d9e201147c46699fc0033e824457 +md5: a9167b9571f3baa9d448faa2139d1089 +depends: +- python >=3.10 +license: MIT +license_family: MIT +size: 58872 +timestamp: 1775127203018 +- conda: https://conda.anaconda.org/conda-forge/noarch/click-8.4.0-pyhc90fa1f_0.conda +sha256: 99ab8ef815c4520cce3a7482c2513f377c14348206857661d84c76a55e030f97 +md5: 003767c47f1f0a474c4de268b57839c3 +depends: +- __unix +- python +- python >=3.10 +license: BSD-3-Clause +license_family: BSD +size: 104631 +timestamp: 1779108494556 +- conda: https://conda.anaconda.org/conda-forge/noarch/coloredlogs-15.0.1-pyhd8ed1ab_4.conda +sha256: 8021c76eeadbdd5784b881b165242db9449783e12ce26d6234060026fd6a8680 +md5: b866ff7007b934d564961066c8195983 +depends: +- humanfriendly >=9.1 +- python >=3.9 +license: MIT +license_family: MIT +size: 43758 +timestamp: 1733928076798 +- conda: https://conda.anaconda.org/conda-forge/noarch/colormath-3.0.0-pyhd8ed1ab_4.conda +sha256: 59c9e29800b483b390467f90e82b0da3a4fbf0612efe1c90813fca232780e160 +md5: 071cf7b0ce333c81718b054066c15102 +depends: +- networkx >=2.0 +- numpy +- python >=3.9 +license: BSD-3-Clause +license_family: BSD +size: 39326 +timestamp: 1735759976140 +- conda: https://conda.anaconda.org/conda-forge/noarch/cpython-3.14.5-py314hd8ed1ab_100.conda +noarch: generic +sha256: 777882d2685f368417f31bbe1b28f73687fc6c8f6a5768bda20ffeefa6b07f5b +md5: a749029ce5d0632a913db19d17f944ab +depends: +- python >=3.14,<3.15.0a0 +- python_abi * *_cp314 +license: Python-2.0 +size: 50212 +timestamp: 1779236682725 +- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/expat-2.8.1-hfae3067_0.conda +sha256: a9cd5eb1700e11cc39acc36630a2d72a4e317943bd7c5695cd8804419f04ff42 +md5: 89f0247b3cea528d8ad1a6664a313153 +depends: +- libexpat 2.8.1 hfae3067_0 +- libgcc >=14 +license: MIT +license_family: MIT +size: 140114 +timestamp: 1779278679081 +- conda: https://conda.anaconda.org/conda-forge/noarch/font-ttf-dejavu-sans-mono-2.37-hab24e00_0.tar.bz2 +sha256: 58d7f40d2940dd0a8aa28651239adbf5613254df0f75789919c4e6762054403b +md5: 0c96522c6bdaed4b1566d11387caaf45 +license: BSD-3-Clause +license_family: BSD +size: 397370 +timestamp: 1566932522327 +- conda: https://conda.anaconda.org/conda-forge/noarch/font-ttf-inconsolata-3.000-h77eed37_0.tar.bz2 +sha256: c52a29fdac682c20d252facc50f01e7c2e7ceac52aa9817aaf0bb83f7559ec5c +md5: 34893075a5c9e55cdafac56607368fc6 +license: OFL-1.1 +license_family: Other +size: 96530 +timestamp: 1620479909603 +- conda: https://conda.anaconda.org/conda-forge/noarch/font-ttf-source-code-pro-2.038-h77eed37_0.tar.bz2 +sha256: 00925c8c055a2275614b4d983e1df637245e19058d79fc7dd1a93b8d9fb4b139 +md5: 4d59c254e01d9cde7957100457e2d5fb +license: OFL-1.1 +license_family: Other +size: 700814 +timestamp: 1620479612257 +- conda: https://conda.anaconda.org/conda-forge/noarch/font-ttf-ubuntu-0.83-h77eed37_3.conda +sha256: 2821ec1dc454bd8b9a31d0ed22a7ce22422c0aef163c59f49dfdf915d0f0ca14 +md5: 49023d73832ef61042f6a237cb2687e7 +license: LicenseRef-Ubuntu-Font-Licence-Version-1.0 +license_family: Other +size: 1620504 +timestamp: 1727511233259 +- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/fontconfig-2.18.0-hba86a56_0.conda +sha256: 1805f4ab3d9e1734a5a17abccc2cb0fdade51d4d5f29bdc410600ea0115ec050 +md5: b660d59a9d0fb3297327418624acaec3 +depends: +- libexpat >=2.8.1,<3.0a0 +- libfreetype >=2.14.3 +- libfreetype6 >=2.14.3 +- libgcc >=14 +- libuuid >=2.42.1,<3.0a0 +- libzlib >=1.3.2,<2.0a0 +license: MIT +license_family: MIT +size: 293348 +timestamp: 1779421661332 +- conda: https://conda.anaconda.org/conda-forge/noarch/fonts-conda-forge-1-hc364b38_1.conda +sha256: 54eea8469786bc2291cc40bca5f46438d3e062a399e8f53f013b6a9f50e98333 +md5: a7970cd949a077b7cb9696379d338681 +depends: +- font-ttf-ubuntu +- font-ttf-inconsolata +- font-ttf-dejavu-sans-mono +- font-ttf-source-code-pro +license: BSD-3-Clause +license_family: BSD +size: 4059 +timestamp: 1762351264405 +- conda: https://conda.anaconda.org/conda-forge/noarch/h2-4.3.0-pyhcf101f3_0.conda +sha256: 84c64443368f84b600bfecc529a1194a3b14c3656ee2e832d15a20e0329b6da3 +md5: 164fc43f0b53b6e3a7bc7dce5e4f1dc9 +depends: +- python >=3.10 +- hyperframe >=6.1,<7 +- hpack >=4.1,<5 +- python +license: MIT +license_family: MIT +size: 95967 +timestamp: 1756364871835 +- conda: https://conda.anaconda.org/conda-forge/noarch/hpack-4.1.0-pyhd8ed1ab_0.conda +sha256: 6ad78a180576c706aabeb5b4c8ceb97c0cb25f1e112d76495bff23e3779948ba +md5: 0a802cb9888dd14eeefc611f05c40b6e +depends: +- python >=3.9 +license: MIT +license_family: MIT +size: 30731 +timestamp: 1737618390337 +- conda: https://conda.anaconda.org/conda-forge/noarch/humanfriendly-10.0-pyh707e725_8.conda +sha256: fa2071da7fab758c669e78227e6094f6b3608228740808a6de5d6bce83d9e52d +md5: 7fe569c10905402ed47024fc481bb371 +depends: +- __unix +- python >=3.9 +license: MIT +license_family: MIT +size: 73563 +timestamp: 1733928021866 +- conda: https://conda.anaconda.org/conda-forge/noarch/humanize-4.15.0-pyhd8ed1ab_0.conda +sha256: 6c4343b376d0b12a4c75ab992640970d36c933cad1fd924f6a1181fa91710e80 +md5: daddf757c3ecd6067b9af1df1f25d89e +depends: +- python >=3.10 +license: MIT +license_family: MIT +size: 67994 +timestamp: 1766267728652 +- conda: https://conda.anaconda.org/conda-forge/noarch/hyperframe-6.1.0-pyhd8ed1ab_0.conda +sha256: 77af6f5fe8b62ca07d09ac60127a30d9069fdc3c68d6b256754d0ffb1f7779f8 +md5: 8e6923fc12f1fe8f8c4e5c9f343256ac +depends: +- python >=3.9 +license: MIT +license_family: MIT +size: 17397 +timestamp: 1737618427549 +- conda: https://conda.anaconda.org/conda-forge/noarch/idna-3.15-pyhcf101f3_0.conda +sha256: 3d25f9f6f7ab3e1ce6429fc8c8aae0335cf446692e715068488536d220cc43de +md5: 1b9083b7f00609605d1483dbc6071a81 +depends: +- python >=3.10 +- python +license: BSD-3-Clause +license_family: BSD +size: 62642 +timestamp: 1779294335905 +- conda: https://conda.anaconda.org/conda-forge/noarch/importlib-metadata-9.0.0-pyhcf101f3_0.conda +sha256: 43e2a5497cad1598ff88a3e69f69bc88b7b8f141fa63c60eab5db296317318b8 +md5: ffc17e785d64e12fc311af9184221839 +depends: +- python >=3.10 +- zipp >=3.20 +- python +license: Apache-2.0 +size: 34766 +timestamp: 1779714582554 +- conda: https://conda.anaconda.org/conda-forge/noarch/jinja2-3.1.6-pyhcf101f3_1.conda +sha256: fc9ca7348a4f25fed2079f2153ecdcf5f9cf2a0bc36c4172420ca09e1849df7b +md5: 04558c96691bed63104678757beb4f8d +depends: +- markupsafe >=2.0 +- python >=3.10 +- python +license: BSD-3-Clause +license_family: BSD +size: 120685 +timestamp: 1764517220861 +- conda: https://conda.anaconda.org/conda-forge/noarch/jsonschema-4.26.0-pyhcf101f3_0.conda +sha256: db973a37d75db8e19b5f44bbbdaead0c68dde745407f281e2a7fe4db74ec51d7 +md5: ada41c863af263cc4c5fcbaff7c3e4dc +depends: +- attrs >=22.2.0 +- jsonschema-specifications >=2023.3.6 +- python >=3.10 +- referencing >=0.28.4 +- rpds-py >=0.25.0 +- python +license: MIT +license_family: MIT +size: 82356 +timestamp: 1767839954256 +- conda: https://conda.anaconda.org/conda-forge/noarch/jsonschema-specifications-2025.9.1-pyhcf101f3_0.conda +sha256: 0a4f3b132f0faca10c89fdf3b60e15abb62ded6fa80aebfc007d05965192aa04 +md5: 439cd0f567d697b20a8f45cb70a1005a +depends: +- python >=3.10 +- referencing >=0.31.0 +- python +license: MIT +license_family: MIT +size: 19236 +timestamp: 1757335715225 +- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/kaleido-core-0.2.1-he5a581e_0.tar.bz2 +sha256: d3c7f4797566e6f983d16c2a87063a18e4b2d819a66230190a21584d70042755 +md5: 4f0d284f5d11e04277b552eb1c172c7f +depends: +- __glibc >=2.17,<3.0.a0 +- expat >=2.2.10,<3.0.0a0 +- fontconfig +- fonts-conda-forge +- libgcc-ng >=9.3.0 +- mathjax 2.7.* +- nspr >=4.29,<5.0a0 +- nss >=3.62,<4.0a0 +- sqlite >=3.34.0,<4.0a0 +license: MIT +license_family: MIT +size: 65750397 +timestamp: 1615199465742 +- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/lcms2-2.19.1-h9d5b58d_0.conda +sha256: 1e5f68e4b36a0e1a278c6dc026bc3d7775518a15832cbc9d7fc1c0e4c47784b1 +md5: b1f8bee3c53a6d2c103fb4a1ae44f5c4 +depends: +- libgcc >=14 +- libjpeg-turbo >=3.1.4.1,<4.0a0 +- libtiff >=4.7.1,<4.8.0a0 +license: MIT +license_family: MIT +size: 296899 +timestamp: 1778079402392 +- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/ld_impl_linux-aarch64-2.45.1-default_h1979696_102.conda +sha256: 7abd913d81a9bf00abb699e8987966baa2065f5132e37e815f92d90fc6bba530 +md5: a21644fc4a83da26452a718dc9468d5f +depends: +- zstd >=1.5.7,<1.6.0a0 +constrains: +- binutils_impl_linux-aarch64 2.45.1 +license: GPL-3.0-only +license_family: GPL +size: 875596 +timestamp: 1774197520746 +- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/lerc-4.1.0-h52b7260_0.conda +sha256: 8957fd460c1c132c8031f65fd5f56ec3807fd71b7cab2c5e2b0937b13404ab36 +md5: d13423b06447113a90b5b1366d4da171 +depends: +- libgcc >=14 +- libstdcxx >=14 +license: Apache-2.0 +license_family: Apache +size: 240444 +timestamp: 1773114901155 +- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/libblas-3.11.0-7_haddc8a3_openblas.conda +build_number: 7 +sha256: f27ba323c2f1e1731b5e880fe520f178f55047f25be94f77e649605b2343c066 +md5: e8d07b777f6ff1fab69665336561910b +depends: +- libopenblas >=0.3.33,<0.3.34.0a0 +- libopenblas >=0.3.33,<1.0a0 +constrains: +- liblapack 3.11.0 7*_openblas +- libcblas 3.11.0 7*_openblas +- mkl <2027 +- blas 2.307 openblas +- liblapacke 3.11.0 7*_openblas +license: BSD-3-Clause +license_family: BSD +size: 18696 +timestamp: 1778489796402 +- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/libcblas-3.11.0-7_hd72aa62_openblas.conda +build_number: 7 +sha256: c8f0192362966df0828419f042d6f94c079e5df00ad6bd05b5e84c12b42f8cc7 +md5: 90ac57b82c055faa9be25031864b7d8f +depends: +- libblas 3.11.0 7_haddc8a3_openblas +constrains: +- liblapack 3.11.0 7*_openblas +- blas 2.307 openblas +- liblapacke 3.11.0 7*_openblas +license: BSD-3-Clause +license_family: BSD +size: 18664 +timestamp: 1778489802790 +- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/libdeflate-1.25-h1af38f5_0.conda +sha256: 48814b73bd462da6eed2e697e30c060ae16af21e9fbed30d64feaf0aad9da392 +md5: a9138815598fe6b91a1d6782ca657b0c +depends: +- libgcc >=14 +license: MIT +license_family: MIT +size: 71117 +timestamp: 1761979776756 +- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/libexpat-2.8.1-hfae3067_0.conda +sha256: 1fc392b997c6ee2bd3226a7cd870d0edbcbb367e25f9f18dd4a7025fced6efc0 +md5: 513dd884361dfb8a554298ed69b58823 +depends: +- libgcc >=14 +constrains: +- expat 2.8.1.* +license: MIT +license_family: MIT +size: 77140 +timestamp: 1779278671302 +- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/libffi-3.5.2-h376a255_0.conda +sha256: 3df4c539449aabc3443bbe8c492c01d401eea894603087fca2917aa4e1c2dea9 +md5: 2f364feefb6a7c00423e80dcb12db62a +depends: +- libgcc >=14 +license: MIT +license_family: MIT +size: 55952 +timestamp: 1769456078358 +- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/libfreetype-2.14.3-h8af1aa0_0.conda +sha256: 752e4f66283d7deb4c6fd47d88df644d8daa2aaa825a54f3bf350a625190192a +md5: a229e22d4d8814a07702b0919d8e6701 +depends: +- libfreetype6 >=2.14.3 +license: GPL-2.0-only OR FTL +size: 8125 +timestamp: 1774301094057 +- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/libfreetype6-2.14.3-hdae7a39_0.conda +sha256: 8e6b27fe4eec4c2fa7b7769a21973734c8dba1de80086fb0213e58375ac09f4c +md5: b99ed99e42dafb27889483b3098cace7 +depends: +- libgcc >=14 +- libpng >=1.6.55,<1.7.0a0 +- libzlib >=1.3.2,<2.0a0 +constrains: +- freetype >=2.14.3 +license: GPL-2.0-only OR FTL +size: 422941 +timestamp: 1774301093473 +- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/libgcc-15.2.0-h8acb6b2_19.conda +sha256: 4592b096e553f67799ae70d4b6167eeda3ec74587d68c7aecbf4e7b1df136681 +md5: f35b3f52d0a2ec4ffe3c89ba135cdb9a +depends: +- _openmp_mutex >=4.5 +constrains: +- libgomp 15.2.0 h8acb6b2_19 +- libgcc-ng ==15.2.0=*_19 +license: GPL-3.0-only WITH GCC-exception-3.1 +license_family: GPL +size: 622462 +timestamp: 1778268755949 +- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/libgcc-ng-15.2.0-he9431aa_19.conda +sha256: 1137f93f477f56199ded24117430045a0c02cbe8b10031beac3b9ad2138539d3 +md5: 770cf892e5530f43e63cadc673e85653 +depends: +- libgcc 15.2.0 h8acb6b2_19 +license: GPL-3.0-only WITH GCC-exception-3.1 +license_family: GPL +size: 27738 +timestamp: 1778268759211 +- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/libgfortran-15.2.0-he9431aa_19.conda +sha256: e5ad94be72634233510b33ba792a3339921bd468f0b8bc6961ea05eded251d9b +md5: c7a5b5decf969ead5ecada83654164cf +depends: +- libgfortran5 15.2.0 h1b7bec0_19 +constrains: +- libgfortran-ng ==15.2.0=*_19 +license: GPL-3.0-only WITH GCC-exception-3.1 +license_family: GPL +size: 27728 +timestamp: 1778268784621 +- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/libgfortran5-15.2.0-h1b7bec0_19.conda +sha256: af8e9bdcaa77f133a8ee4c1ef57ef564d9c45aa262abf9f5ef9b50eb99d96407 +md5: 779dbb494de6d3d6477cab52eb34285a +depends: +- libgcc >=15.2.0 +constrains: +- libgfortran 15.2.0 +license: GPL-3.0-only WITH GCC-exception-3.1 +license_family: GPL +size: 1487244 +timestamp: 1778268767295 +- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/libgomp-15.2.0-h8acb6b2_19.conda +sha256: 2370ef0ffcbae5bede3c4bf136add4abc257245eb91f724c99bb4a43116c5a83 +md5: c5e8a379c4a2ec2aea4ba22758c001d9 +license: GPL-3.0-only WITH GCC-exception-3.1 +license_family: GPL +size: 587387 +timestamp: 1778268674393 +- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/libjpeg-turbo-3.1.4.1-he30d5cf_0.conda +sha256: e97ec2af5f09f8f6ea8ecd550055c95ae80fae22015fcfadaa94eafe025c9ccc +md5: a85ba48648f6868016f2741fd9170250 +depends: +- libgcc >=14 +constrains: +- jpeg <0.0.0a +license: IJG AND BSD-3-Clause AND Zlib +size: 693143 +timestamp: 1775962625956 +- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/liblapack-3.11.0-7_h88aeb00_openblas.conda +build_number: 7 +sha256: 20b38a0156200ac65f597bf0a93914c565435f2cc58b1042581854231a99ac35 +md5: 5899cbd743cc74fd655c1ed2af7120f3 +depends: +- libblas 3.11.0 7_haddc8a3_openblas +constrains: +- libcblas 3.11.0 7*_openblas +- blas 2.307 openblas +- liblapacke 3.11.0 7*_openblas +license: BSD-3-Clause +license_family: BSD +size: 18685 +timestamp: 1778489809140 +- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/liblzma-5.8.3-he30d5cf_0.conda +sha256: d61962b9cd54c3554361550203c64d5b65b71e3058a285b66e4b04b9769f0a5c +md5: 76298a9e6d71ee6e832a8d0d7373b261 +depends: +- libgcc >=14 +constrains: +- xz 5.8.3.* +license: 0BSD +size: 126102 +timestamp: 1775828008518 +- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/libmpdec-4.0.0-he30d5cf_1.conda +sha256: 57c0dd12d506e84541c4e877898bd2a59cca141df493d34036f18b2751e0a453 +md5: 7b9813e885482e3ccb1fa212b86d7fd0 +depends: +- libgcc >=14 +license: BSD-2-Clause +license_family: BSD +size: 114056 +timestamp: 1769482343003 +- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/libopenblas-0.3.33-pthreads_h9d3fd7e_0.conda +sha256: b018ecfb05e75a8eea3f21f6b5c5c2a54b5178bdcf19e2e2df2735740214a8c8 +md5: 58a66cd95e9692f08abe89f55a6f3f12 +depends: +- libgcc >=14 +- libgfortran +- libgfortran5 >=14.3.0 +constrains: +- openblas >=0.3.33,<0.3.34.0a0 +license: BSD-3-Clause +license_family: BSD +size: 5121336 +timestamp: 1776993423004 +- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/libpng-1.6.58-h1abf092_0.conda +sha256: 483eaa53da40a6a3e558709d9f7b1ca388735364ae21a1ba58cf942514649c92 +md5: f51503ac45a4888bce71af9027a2ecc9 +depends: +- libgcc >=14 +- libzlib >=1.3.2,<2.0a0 +license: zlib-acknowledgement +size: 341202 +timestamp: 1776315188425 +- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/libsqlite-3.53.1-h022381a_0.conda +sha256: ad03b7d8e4d08001f0df88ee7a56108bb35bae4795a42b9a04cc1abfa822bd07 +md5: 2ec1119217d8f0d086e9a62f3cb0e5ea +depends: +- libgcc >=14 +- libzlib >=1.3.2,<2.0a0 +license: blessing +size: 955361 +timestamp: 1777986487553 +- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/libstdcxx-15.2.0-hef695bb_19.conda +sha256: 1dadc45e599f510dd5f97141dddcdbb9844d9f1430c1f3a38075cf1c58f87b4e +md5: 543fbc8d71f2a0baf04cf88ce96cb8bb +depends: +- libgcc 15.2.0 h8acb6b2_19 +constrains: +- libstdcxx-ng ==15.2.0=*_19 +license: GPL-3.0-only WITH GCC-exception-3.1 +license_family: GPL +size: 5546559 +timestamp: 1778268777463 +- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/libtiff-4.7.1-hdb009f0_1.conda +sha256: 7ff79470db39e803e21b8185bc8f19c460666d5557b1378d1b1e857d929c6b39 +md5: 8c6fd84f9c87ac00636007c6131e457d +depends: +- lerc >=4.0.0,<5.0a0 +- libdeflate >=1.25,<1.26.0a0 +- libgcc >=14 +- libjpeg-turbo >=3.1.0,<4.0a0 +- liblzma >=5.8.1,<6.0a0 +- libstdcxx >=14 +- libwebp-base >=1.6.0,<2.0a0 +- libzlib >=1.3.1,<2.0a0 +- zstd >=1.5.7,<1.6.0a0 +license: HPND +size: 488407 +timestamp: 1762022048105 +- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/libuuid-2.42.1-h1022ec0_0.conda +sha256: 1628839b062e98b2192857d4da8496ac9ac6b0dbb77aa040c34efc9192c440ee +md5: 0f42f9fedd2a32d798de95a7f65c456f +depends: +- libgcc >=14 +license: BSD-3-Clause +license_family: BSD +size: 43453 +timestamp: 1779118526838 +- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/libwebp-base-1.6.0-ha2e29f5_0.conda +sha256: b03700a1f741554e8e5712f9b06dd67e76f5301292958cd3cb1ac8c6fdd9ed25 +md5: 24e92d0942c799db387f5c9d7b81f1af +depends: +- libgcc >=14 +constrains: +- libwebp 1.6.0 +license: BSD-3-Clause +license_family: BSD +size: 359496 +timestamp: 1752160685488 +- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/libxcb-1.17.0-h262b8f6_0.conda +sha256: 461cab3d5650ac6db73a367de5c8eca50363966e862dcf60181d693236b1ae7b +md5: cd14ee5cca2464a425b1dbfc24d90db2 +depends: +- libgcc >=13 +- pthread-stubs +- xorg-libxau >=1.0.11,<2.0a0 +- xorg-libxdmcp +license: MIT +license_family: MIT +size: 397493 +timestamp: 1727280745441 +- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/libzlib-1.3.2-hdc9db2a_2.conda +sha256: eb111e32e5a7313a5bf799c7fb2419051fa2fe7eff74769fac8d5a448b309f7f +md5: 502006882cf5461adced436e410046d1 +constrains: +- zlib 1.3.2 *_2 +license: Zlib +license_family: Other +size: 69833 +timestamp: 1774072605429 +- conda: https://conda.anaconda.org/conda-forge/noarch/markdown-3.10.2-pyhcf101f3_0.conda +sha256: 20e0892592a3e7c683e3d66df704a9425d731486a97c34fc56af4da1106b2b6b +md5: ba0a9221ce1063f31692c07370d062f3 +depends: +- importlib-metadata >=4.4 +- python >=3.10 +- python +license: BSD-3-Clause +license_family: BSD +size: 85893 +timestamp: 1770694658918 +- conda: https://conda.anaconda.org/conda-forge/noarch/markdown-it-py-4.2.0-pyhd8ed1ab_0.conda +sha256: 0c4c35376fe920714390d46e4b8d31c876d65f18e1655899e0763ec25f2a902f +md5: 6d03368f2b2b0a5fb6839df53b2eb5e0 +depends: +- mdurl >=0.1,<1 +- python >=3.10 +license: MIT +license_family: MIT +size: 69017 +timestamp: 1778169663339 +- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/markupsafe-3.0.3-py314hb76de3f_1.conda +sha256: 383c188496d13a55658c06e61e7d4cdff2c9f9d5a0648769fca8250bece7e0ef +md5: e5de3c36dd548b35ff2a8aa49208dcb3 +depends: +- libgcc >=14 +- python >=3.14,<3.15.0a0 +- python_abi 3.14.* *_cp314 +constrains: +- jinja2 >=3.0.0 +license: BSD-3-Clause +license_family: BSD +size: 27913 +timestamp: 1772446407659 +- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/mathjax-2.7.7-h8af1aa0_3.tar.bz2 +sha256: 8fd4c79d6eda3d4cba73783114305a53a154ada4d1e334d4e02cb3521429599b +md5: 7b08314a6867a9d5648a1c3265e9eb8e +license: Apache-2.0 +license_family: Apache +size: 22257008 +timestamp: 1662784555011 +- conda: https://conda.anaconda.org/conda-forge/noarch/mdurl-0.1.2-pyhd8ed1ab_1.conda +sha256: 78c1bbe1723449c52b7a9df1af2ee5f005209f67e40b6e1d3c7619127c43b1c7 +md5: 592132998493b3ff25fd7479396e8351 +depends: +- python >=3.9 +license: MIT +license_family: MIT +size: 14465 +timestamp: 1733255681319 +- conda: https://conda.anaconda.org/bioconda/noarch/multiqc-1.35-pyhdfd78af_1.conda +sha256: e86033aa55a9e915e2d0957e770bdb81e3feb26a227d1adb17f9d6c528da6a71 +md5: cdb20309681ba3ce8f52c110e214d4f3 +depends: +- click +- coloredlogs +- humanize +- importlib-metadata +- jinja2 >=3.0.0 +- jsonschema +- markdown +- natsort +- numpy +- packaging +- pillow >=10.2.0 +- plotly >=5.18 +- polars >=1.34.0 +- polars-runtime-compat >=1.34.0 +- pyaml-env +- pydantic >=2.7.1 +- python >=3.9,!=3.14.1 +- python-dotenv +- python-kaleido 0.2.1 +- pyyaml >=4 +- requests +- rich >=10 +- rich-click +- spectra >=0.0.10 +- tiktoken +- tqdm +- typeguard >=4 +license: GPL-3.0-or-later +license_family: GPL3 +size: 4282188 +timestamp: 1779465338806 +- conda: https://conda.anaconda.org/conda-forge/noarch/narwhals-2.21.2-pyhcf101f3_0.conda +sha256: 70f43d62450927d51673eecd8823e14f5b3cfebdb43cda1d502eba97162bab42 +md5: 6687827c332121727ce383919e1ec8c2 +depends: +- python >=3.10 +- python +license: MIT +license_family: MIT +size: 284323 +timestamp: 1778929680962 +- conda: https://conda.anaconda.org/conda-forge/noarch/natsort-8.4.0-pyhcf101f3_2.conda +sha256: aeb1548eb72e4f198e72f19d242fb695b35add2ac7b2c00e0d83687052867680 +md5: e941e85e273121222580723010bd4fa2 +depends: +- python >=3.9 +- python +license: MIT +license_family: MIT +size: 39262 +timestamp: 1770905275632 +- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/ncurses-6.6-hf8d1292_0.conda +sha256: 369db85c5cd8d99dde364ce70725d76511d9c8199e5b820c740414091bf5bcca +md5: b2a43456aa56fe80c2477a5094899eff +depends: +- libgcc >=14 +license: X11 AND BSD-3-Clause +size: 960036 +timestamp: 1777422174534 +- conda: https://conda.anaconda.org/conda-forge/noarch/networkx-3.6.1-pyhcf101f3_0.conda +sha256: f6a82172afc50e54741f6f84527ef10424326611503c64e359e25a19a8e4c1c6 +md5: a2c1eeadae7a309daed9d62c96012a2b +depends: +- python >=3.11 +- python +constrains: +- numpy >=1.25 +- scipy >=1.11.2 +- matplotlib-base >=3.8 +- pandas >=2.0 +license: BSD-3-Clause +license_family: BSD +size: 1587439 +timestamp: 1765215107045 +- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/nspr-4.38-h3ad9384_0.conda +sha256: 78a06e89285fef242e272998b292c1e621e3ee3dd4fba62ec014e503c7ec118f +md5: 6dd4f07147774bf720075a210f8026b9 +depends: +- libgcc >=14 +- libstdcxx >=14 +license: MPL-2.0 +license_family: MOZILLA +size: 235140 +timestamp: 1762350120355 +- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/nss-3.118-h544fa81_0.conda +sha256: 48942696889367ffd448f8dccfc080fb7e130b9938a4a3b6b20ef8e6af856463 +md5: 4540f9570d12db2150f42ba036154552 +depends: +- libgcc >=14 +- libsqlite >=3.51.0,<4.0a0 +- libstdcxx >=14 +- libzlib >=1.3.1,<2.0a0 +- nspr >=4.38,<5.0a0 +license: MPL-2.0 +license_family: MOZILLA +size: 2061869 +timestamp: 1763490303490 +- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/numpy-2.4.6-py314he1698a1_0.conda +sha256: 04af718b911f8a3a0095481c7e283aa081a175fe626eccbc2c5644bcb2aba9a1 +md5: 8b173772deea177b45d2a133b509b3f7 +depends: +- python +- libstdcxx >=14 +- libgcc >=14 +- python_abi 3.14.* *_cp314 +- libblas >=3.9.0,<4.0a0 +- liblapack >=3.9.0,<4.0a0 +- libcblas >=3.9.0,<4.0a0 +constrains: +- numpy-base <0a0 +license: BSD-3-Clause +license_family: BSD +size: 8002900 +timestamp: 1779169206742 +- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/openjpeg-2.5.4-h5da879a_0.conda +sha256: bd1bc8bdde5e6c5cbac42d462b939694e40b59be6d0698f668515908640c77b8 +md5: cea962410e327262346d48d01f05936c +depends: +- libgcc >=14 +- libpng >=1.6.50,<1.7.0a0 +- libstdcxx >=14 +- libtiff >=4.7.1,<4.8.0a0 +- libzlib >=1.3.1,<2.0a0 +license: BSD-2-Clause +license_family: BSD +size: 392636 +timestamp: 1758489353577 +- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/openssl-3.6.2-h546c87b_0.conda +sha256: 348cb74c1530ac241215d047ef65d134cf797af935c97a68655319362b7e6a01 +md5: 3b129669089e4d6a5c6871dbb4669b99 +depends: +- ca-certificates +- libgcc >=14 +license: Apache-2.0 +license_family: Apache +size: 3706406 +timestamp: 1775589602258 +- conda: https://conda.anaconda.org/conda-forge/noarch/packaging-26.2-pyhc364b38_0.conda +sha256: 3906abfb6511a3bb309e39b9b1b7bc38f50a723971de2395489fd1f379255890 +md5: 4c06a92e74452cfa53623a81592e8934 +depends: +- python >=3.8 +- python +license: Apache-2.0 +license_family: APACHE +size: 91574 +timestamp: 1777103621679 +- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/pillow-12.2.0-py314hac3e5ec_0.conda +sha256: 96b26c2657275ffe84ab510edf0865e21999d791485d12794edd4a71b837beb6 +md5: 87d58d103b47c4a8567b3d7666647684 +depends: +- python +- libgcc >=14 +- python 3.14.* *_cp314 +- openjpeg >=2.5.4,<3.0a0 +- libxcb >=1.17.0,<2.0a0 +- libwebp-base >=1.6.0,<2.0a0 +- zlib-ng >=2.3.3,<2.4.0a0 +- python_abi 3.14.* *_cp314 +- lcms2 >=2.18,<3.0a0 +- tk >=8.6.13,<8.7.0a0 +- libtiff >=4.7.1,<4.8.0a0 +- libjpeg-turbo >=3.1.2,<4.0a0 +- libfreetype >=2.14.3 +- libfreetype6 >=2.14.3 +license: HPND +size: 1062080 +timestamp: 1775060067775 +- conda: https://conda.anaconda.org/conda-forge/noarch/plotly-6.6.0-pyhd8ed1ab_0.conda +sha256: c418d325359fc7a0074cea7f081ef1bce26e114d2da8a0154c5d27ecc87a08e7 +md5: 3e9427ee186846052e81fadde8ebe96a +depends: +- narwhals >=1.15.1 +- packaging +- python >=3.10 +constrains: +- ipywidgets >=7.6 +license: MIT +license_family: MIT +size: 5251872 +timestamp: 1772628857717 +- conda: https://conda.anaconda.org/conda-forge/noarch/polars-1.41.0-pyh58ad624_0.conda +sha256: 70fc56877c4a095ee658d61924d8019768fbae4a48437058d181fc94b0a7c4d8 +md5: 25a883fed9f1f3f21ff317a3e7c92ac4 +depends: +- polars-runtime-32 ==1.41.0 +- python >=3.10 +- python +constrains: +- numpy >=1.16.0 +- pyarrow >=7.0.0 +- fastexcel >=0.9 +- openpyxl >=3.0.0 +- xlsx2csv >=0.8.0 +- connectorx >=0.3.2 +- deltalake >=1.0.0 +- pyiceberg >=0.7.1 +- altair >=5.4.0 +- great_tables >=0.8.0 +- polars-runtime-32 ==1.41.0 +- polars-runtime-64 ==1.41.0 +- polars-runtime-compat ==1.41.0 +license: MIT +size: 539656 +timestamp: 1779630790562 +- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/polars-runtime-32-1.41.0-py310h32c7c23_0.conda +noarch: python +sha256: d903b774ec09189e164207328aac157eee82fed8cc5c9ace46aeb5d1c15cb5b3 +md5: 8c08c506ed1ea8ce0ca37af5e918c58d +depends: +- python +- libgcc >=14 +- libstdcxx >=14 +- _python_abi3_support 1.* +- cpython >=3.10 +constrains: +- __glibc >=2.17 +license: MIT +size: 38704429 +timestamp: 1779630794932 +- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/polars-runtime-compat-1.41.0-py310hc0e61be_0.conda +noarch: python +sha256: 101696adff43a654146376c62ef9611bf7946b95fa46f604fe247d77eefc6267 +md5: 65b73e4260677ee5162bdbb252e28e06 +depends: +- python +- libstdcxx >=14 +- libgcc >=14 +- _python_abi3_support 1.* +- cpython >=3.10 +constrains: +- __glibc >=2.17 +license: MIT +size: 38651498 +timestamp: 1779630714016 +- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/procps-ng-4.0.6-h1779866_0.conda +sha256: e9cbcbc94e151ada3d6dc365380aaaf591f65012c16d9a2abaea4b9b90adc402 +md5: ab7288cc39545556d1bc5e71ab2df9a9 +depends: +- libgcc >=14 +- ncurses >=6.5,<7.0a0 +license: GPL-2.0-or-later AND LGPL-2.0-or-later +license_family: GPL +size: 636733 +timestamp: 1769712412683 +- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/pthread-stubs-0.4-h86ecc28_1002.conda +sha256: 977dfb0cb3935d748521dd80262fe7169ab82920afd38ed14b7fee2ea5ec01ba +md5: bb5a90c93e3bac3d5690acf76b4a6386 +depends: +- libgcc >=13 +license: MIT +license_family: MIT +size: 8342 +timestamp: 1726803319942 +- conda: https://conda.anaconda.org/conda-forge/noarch/pyaml-env-1.2.2-pyhd8ed1ab_0.conda +sha256: 58994e0d2ea8584cb399546e6f6896d771995e6121d1a7b6a2c9948388358932 +md5: e17be1016bcc3516827b836cd3e4d9dc +depends: +- python >=3.9 +- pyyaml >=5.0,<=7.0 +license: MIT +license_family: MIT +size: 14645 +timestamp: 1736766960536 +- conda: https://conda.anaconda.org/conda-forge/noarch/pydantic-2.13.4-pyhcf101f3_0.conda +sha256: 69700e31165df070e9716315e042196aa92525dae5deb5107785847ab9f4189f +md5: 729843edafc0899b3348bd3f19525b9d +depends: +- typing-inspection >=0.4.2 +- typing_extensions >=4.14.1 +- python >=3.10 +- annotated-types >=0.6.0 +- pydantic-core ==2.46.4 +- python +license: MIT +license_family: MIT +size: 346511 +timestamp: 1778103405862 +- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/pydantic-core-2.46.4-py314h451b6cc_0.conda +sha256: 1a7c6b18e404c13c4d959888ecb48a9ed9de0e41be2872932b83a35278088df0 +md5: 9c3ace6aba6df14b943256095ac1281e +depends: +- python +- typing-extensions >=4.6.0,!=4.7.0 +- libgcc >=14 +- python 3.14.* *_cp314 +- python_abi 3.14.* *_cp314 +constrains: +- __glibc >=2.17 +license: MIT +license_family: MIT +size: 1780773 +timestamp: 1778084251775 +- conda: https://conda.anaconda.org/conda-forge/noarch/pygments-2.20.0-pyhd8ed1ab_0.conda +sha256: cf70b2f5ad9ae472b71235e5c8a736c9316df3705746de419b59d442e8348e86 +md5: 16c18772b340887160c79a6acc022db0 +depends: +- python >=3.10 +license: BSD-2-Clause +license_family: BSD +size: 893031 +timestamp: 1774796815820 +- conda: https://conda.anaconda.org/conda-forge/noarch/pysocks-1.7.1-pyha55dd90_7.conda +sha256: ba3b032fa52709ce0d9fd388f63d330a026754587a2f461117cac9ab73d8d0d8 +md5: 461219d1a5bd61342293efa2c0c90eac +depends: +- __unix +- python >=3.9 +license: BSD-3-Clause +license_family: BSD +size: 21085 +timestamp: 1733217331982 +- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/python-3.14.5-hfd9ac0a_100_cp314.conda +build_number: 100 +sha256: d37bad5447365346166c72950ea8f49689aa49cecc1b0623d00458427627b8df +md5: d956e09feb806f5974675ce92ad81d45 +depends: +- bzip2 >=1.0.8,<2.0a0 +- ld_impl_linux-aarch64 >=2.36.1 +- libexpat >=2.8.0,<3.0a0 +- libffi >=3.5.2,<3.6.0a0 +- libgcc >=14 +- liblzma >=5.8.3,<6.0a0 +- libmpdec >=4.0.0,<5.0a0 +- libsqlite >=3.53.1,<4.0a0 +- libuuid >=2.42.1,<3.0a0 +- libzlib >=1.3.2,<2.0a0 +- ncurses >=6.6,<7.0a0 +- openssl >=3.5.6,<4.0a0 +- python_abi 3.14.* *_cp314 +- readline >=8.3,<9.0a0 +- tk >=8.6.13,<8.7.0a0 +- tzdata +- zstd >=1.5.7,<1.6.0a0 +license: Python-2.0 +size: 37510439 +timestamp: 1779236267040 +python_site_packages_path: lib/python3.14/site-packages +- conda: https://conda.anaconda.org/conda-forge/noarch/python-dotenv-1.2.2-pyhcf101f3_0.conda +sha256: 74e417a768f59f02a242c25e7db0aa796627b5bc8c818863b57786072aeb85e5 +md5: 130584ad9f3a513cdd71b1fdc1244e9c +depends: +- python >=3.10 +license: BSD-3-Clause +license_family: BSD +size: 27848 +timestamp: 1772388605021 +- conda: https://conda.anaconda.org/conda-forge/noarch/python-gil-3.14.5-h4df99d1_100.conda +sha256: 41dd7da285d71d519257fa7dacb1cae060d5ebfaa5f92cba5994899d2978e943 +md5: 41954747ba952ec4b01e16c2c9e8d8ff +depends: +- cpython 3.14.5.* +- python_abi * *_cp314 +license: Python-2.0 +size: 50212 +timestamp: 1779236703009 +- conda: https://conda.anaconda.org/conda-forge/noarch/python-kaleido-0.2.1-pyhd8ed1ab_0.tar.bz2 +sha256: e17bf63a30aec33432f1ead86e15e9febde9fc40a7f869c0e766be8d2db44170 +md5: 310259a5b03ff02289d7705f39e2b1d2 +depends: +- kaleido-core 0.2.1.* +- python >=3.5 +license: MIT +license_family: MIT +size: 18320 +timestamp: 1615204747600 +- conda: https://conda.anaconda.org/conda-forge/noarch/python_abi-3.14-8_cp314.conda +build_number: 8 +sha256: ad6d2e9ac39751cc0529dd1566a26751a0bf2542adb0c232533d32e176e21db5 +md5: 0539938c55b6b1a59b560e843ad864a4 +constrains: +- python 3.14.* *_cp314 +license: BSD-3-Clause +license_family: BSD +size: 6989 +timestamp: 1752805904792 +- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/pyyaml-6.0.3-py314h807365f_1.conda +sha256: 496b5e65dfdd0aaaaa5de0dcaaf3bceea00fcb4398acf152f89e567c82ec1046 +md5: 9ae2c92975118058bd720e9ba2bb7c58 +depends: +- libgcc >=14 +- python >=3.14,<3.15.0a0 +- python >=3.14,<3.15.0a0 *_cp314 +- python_abi 3.14.* *_cp314 +- yaml >=0.2.5,<0.3.0a0 +license: MIT +license_family: MIT +size: 195678 +timestamp: 1770223441816 +- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/readline-8.3-hb682ff5_0.conda +sha256: fe695f9d215e9a2e3dd0ca7f56435ab4df24f5504b83865e3d295df36e88d216 +md5: 3d49cad61f829f4f0e0611547a9cda12 +depends: +- libgcc >=14 +- ncurses >=6.5,<7.0a0 +license: GPL-3.0-only +license_family: GPL +size: 357597 +timestamp: 1765815673644 +- conda: https://conda.anaconda.org/conda-forge/noarch/referencing-0.37.0-pyhcf101f3_0.conda +sha256: 0577eedfb347ff94d0f2fa6c052c502989b028216996b45c7f21236f25864414 +md5: 870293df500ca7e18bedefa5838a22ab +depends: +- attrs >=22.2.0 +- python >=3.10 +- rpds-py >=0.7.0 +- typing_extensions >=4.4.0 +- python +license: MIT +license_family: MIT +size: 51788 +timestamp: 1760379115194 +- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/regex-2026.5.9-py314h51f160d_0.conda +sha256: 05ef55f09f31eabd0a205f6b065e13fc746675f41924620977692ef0ffe5aad8 +md5: 34ed7bc9febeca70f55b757ca09c354d +depends: +- libgcc >=14 +- python >=3.14,<3.15.0a0 +- python >=3.14,<3.15.0a0 *_cp314 +- python_abi 3.14.* *_cp314 +license: Apache-2.0 AND CNRI-Python +license_family: PSF +size: 409780 +timestamp: 1778374195988 +- conda: https://conda.anaconda.org/conda-forge/noarch/requests-2.34.2-pyhcf101f3_0.conda +sha256: 1715246b19c9f85ee022933b4845f2fc14ac9184981b7b7d9b728bec8e9588da +md5: 4a85203c1d80c1059086ae860836ffb9 +depends: +- python >=3.10 +- certifi >=2023.5.7 +- charset-normalizer >=2,<4 +- idna >=2.5,<4 +- urllib3 >=1.26,<3 +- python +constrains: +- chardet >=3.0.2,<8 +license: Apache-2.0 +license_family: APACHE +size: 68709 +timestamp: 1778851103479 +- conda: https://conda.anaconda.org/conda-forge/noarch/rich-15.0.0-pyhcf101f3_0.conda +sha256: 3d6ba2c0fcdac3196ba2f0615b4104e532525ffa1335b50a2878be5ff488814a +md5: 0242025a3c804966bf71aa04eee82f66 +depends: +- markdown-it-py >=2.2.0 +- pygments >=2.13.0,<3.0.0 +- python >=3.10 +- typing_extensions >=4.0.0,<5.0.0 +- python +license: MIT +license_family: MIT +size: 208577 +timestamp: 1775991661559 +- conda: https://conda.anaconda.org/conda-forge/noarch/rich-click-1.9.7-pyh8f84b5b_0.conda +sha256: aa3fcb167321bae51998de2e94d199109c9024f25a5a063cb1c28d8f1af33436 +md5: 0c20a8ebcddb24a45da89d5e917e6cb9 +depends: +- python >=3.10 +- rich >=12 +- click >=8 +- typing-extensions >=4 +- __unix +- python +license: MIT +license_family: MIT +size: 64356 +timestamp: 1769850479089 +- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/rpds-py-0.30.0-py314h02b7a91_0.conda +sha256: a587240f16eac7c6a80f9585cef679cd1cb9a287b8dfcdd36dcef1f7e7db15dc +md5: e7f6ed9e60043bb5cbcc527764897f0d +depends: +- python +- libgcc >=14 +- python_abi 3.14.* *_cp314 +constrains: +- __glibc >=2.17 +license: MIT +license_family: MIT +size: 376332 +timestamp: 1764543345455 +- conda: https://conda.anaconda.org/conda-forge/noarch/spectra-0.0.11-pyhd8ed1ab_2.conda +sha256: 7c65782d2511738e62c70462e89d65da4fa54d5a7e47c46667bcd27a59f81876 +md5: 472239e4eb7b5a84bb96b3ed7e3a596a +depends: +- colormath >=3.0.0 +- python >=3.9 +license: MIT +license_family: MIT +size: 22284 +timestamp: 1735770589188 +- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/sqlite-3.53.1-he8854b5_0.conda +sha256: 27467e4bfb0681546f149718c33b806fec078185fbaa6a4d17d440bc8f56185c +md5: 46009bdca2315a99e0a3a7d0ba1af3b9 +depends: +- libgcc >=14 +- libsqlite 3.53.1 h022381a_0 +- libzlib >=1.3.2,<2.0a0 +- ncurses >=6.6,<7.0a0 +- readline >=8.3,<9.0a0 +license: blessing +size: 209964 +timestamp: 1777986493350 +- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/tiktoken-0.12.0-py314h6a36e60_3.conda +sha256: c1da41c79262b27efa168407cfecc47b20270e5fc071a8307f95a2c85fb94170 +md5: 55bf7b559202236157b14323b40f19e6 +depends: +- libgcc >=14 +- libstdcxx >=14 +- python >=3.14,<3.15.0a0 +- python_abi 3.14.* *_cp314 +- regex >=2022.1.18 +- requests >=2.26.0 +constrains: +- __glibc >=2.17 +license: MIT +license_family: MIT +size: 914402 +timestamp: 1764030357702 +- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/tk-8.6.13-noxft_h0dc03b3_103.conda +sha256: e25c314b52764219f842b41aea2c98a059f06437392268f09b03561e4f6e5309 +md5: 7fc6affb9b01e567d2ef1d05b84aa6ed +depends: +- libgcc >=14 +- libzlib >=1.3.1,<2.0a0 +constrains: +- xorg-libx11 >=1.8.12,<2.0a0 +license: TCL +license_family: BSD +size: 3368666 +timestamp: 1769464148928 +- conda: https://conda.anaconda.org/conda-forge/noarch/tqdm-4.67.3-pyh8f84b5b_0.conda +sha256: 9ef8e47cf00e4d6dcc114eb32a1504cc18206300572ef14d76634ba29dfe1eb6 +md5: e5ce43272193b38c2e9037446c1d9206 +depends: +- python >=3.10 +- __unix +- python +license: MPL-2.0 and MIT +size: 94132 +timestamp: 1770153424136 +- conda: https://conda.anaconda.org/conda-forge/noarch/typeguard-4.5.2-pyhcf101f3_0.conda +sha256: 59d7851d32fddb5b510272e6557aa982edeb927d349648dac27f5bf01d18bb26 +md5: 4460f039b7dedf15f7df086446ca75ae +depends: +- typing_extensions >=4.14.0 +- python >=3.10 +- importlib-metadata >=3.6 +- python +constrains: +- pytest >=7 +license: MIT +license_family: MIT +size: 38297 +timestamp: 1778779291237 +- conda: https://conda.anaconda.org/conda-forge/noarch/typing-extensions-4.15.0-h396c80c_0.conda +sha256: 7c2df5721c742c2a47b2c8f960e718c930031663ac1174da67c1ed5999f7938c +md5: edd329d7d3a4ab45dcf905899a7a6115 +depends: +- typing_extensions ==4.15.0 pyhcf101f3_0 +license: PSF-2.0 +license_family: PSF +size: 91383 +timestamp: 1756220668932 +- conda: https://conda.anaconda.org/conda-forge/noarch/typing-inspection-0.4.2-pyhcf101f3_2.conda +sha256: 8b90d2f19f9458b8c58a55e1fcdc1d90c1603a847a47654d8a454549413ba60a +md5: 53f5409c5cfd6c5a66417d68e3f0a864 +depends: +- python >=3.10 +- typing_extensions >=4.12.0 +- python +license: MIT +license_family: MIT +size: 20935 +timestamp: 1777105465795 +- conda: https://conda.anaconda.org/conda-forge/noarch/typing_extensions-4.15.0-pyhcf101f3_0.conda +sha256: 032271135bca55aeb156cee361c81350c6f3fb203f57d024d7e5a1fc9ef18731 +md5: 0caa1af407ecff61170c9437a808404d +depends: +- python >=3.10 +- python +license: PSF-2.0 +license_family: PSF +size: 51692 +timestamp: 1756220668932 +- conda: https://conda.anaconda.org/conda-forge/noarch/tzdata-2025c-hc9c84f9_1.conda +sha256: 1d30098909076af33a35017eed6f2953af1c769e273a0626a04722ac4acaba3c +md5: ad659d0a2b3e47e38d829aa8cad2d610 +license: LicenseRef-Public-Domain +size: 119135 +timestamp: 1767016325805 +- conda: https://conda.anaconda.org/conda-forge/noarch/urllib3-2.7.0-pyhd8ed1ab_0.conda +sha256: feff959a816f7988a0893201aa9727bbb7ee1e9cec2c4f0428269b489eb93fb4 +md5: cbb88288f74dbe6ada1c6c7d0a97223e +depends: +- backports.zstd >=1.0.0 +- brotli-python >=1.2.0 +- h2 >=4,<5 +- pysocks >=1.5.6,<2.0,!=1.5.7 +- python >=3.10 +license: MIT +license_family: MIT +size: 103560 +timestamp: 1778188657149 +- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/xorg-libxau-1.0.12-he30d5cf_1.conda +sha256: e9f6e931feeb2f40e1fdbafe41d3b665f1ab6cb39c5880a1fcf9f79a3f3c84a5 +md5: 1c246e1105000c3660558459e2fd6d43 +depends: +- libgcc >=14 +license: MIT +license_family: MIT +size: 16317 +timestamp: 1762977521691 +- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/xorg-libxdmcp-1.1.5-he30d5cf_1.conda +sha256: 128d72f36bcc8d2b4cdbec07507542e437c7d67f677b7d77b71ed9eeac7d6df1 +md5: bff06dcde4a707339d66d45d96ceb2e2 +depends: +- libgcc >=14 +license: MIT +license_family: MIT +size: 21039 +timestamp: 1762979038025 +- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/yaml-0.2.5-h80f16a2_3.conda +sha256: 66265e943f32ce02396ad214e27cb35f5b0490b3bd4f064446390f9d67fa5d88 +md5: 032d8030e4a24fe1f72c74423a46fb88 +depends: +- libgcc >=14 +license: MIT +license_family: MIT +size: 88088 +timestamp: 1753484092643 +- conda: https://conda.anaconda.org/conda-forge/noarch/zipp-4.1.0-pyhcf101f3_0.conda +sha256: 210bd31c22bb88f5e2a167df24c95bb5f152b2ada7502f9b8c49d1f5366db423 +md5: ba3dcdc8584155c97c648ae9c044b7a3 +depends: +- python >=3.10 +- python +license: MIT +license_family: MIT +size: 24190 +timestamp: 1779159948016 +- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/zlib-ng-2.3.3-ha7cb516_1.conda +sha256: 638a3a41a4fbfed52d3c60c8ef5a3693b3f12a5b1a3f58fa29f5698d0a0702e2 +md5: f731af71c723065d91b4c01bb822641b +depends: +- libgcc >=14 +- libstdcxx >=14 +license: Zlib +license_family: Other +size: 121046 +timestamp: 1770167944449 +- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/zstd-1.5.7-h85ac4a6_6.conda +sha256: 569990cf12e46f9df540275146da567d9c618c1e9c7a0bc9d9cfefadaed20b75 +md5: c3655f82dcea2aa179b291e7099c1fcc +depends: +- libzlib >=1.3.1,<2.0a0 +license: BSD-3-Clause +license_family: BSD +size: 614429 +timestamp: 1764777145593 diff --git a/modules/nf-core/multiqc/environment.yml b/modules/nf-core/multiqc/environment.yml index 009874d4..7a970e2b 100644 --- a/modules/nf-core/multiqc/environment.yml +++ b/modules/nf-core/multiqc/environment.yml @@ -4,4 +4,4 @@ channels: - conda-forge - bioconda dependencies: - - bioconda::multiqc=1.33 + - bioconda::multiqc=1.35 diff --git a/modules/nf-core/multiqc/main.nf b/modules/nf-core/multiqc/main.nf index 3b0e975b..c4bc715e 100644 --- a/modules/nf-core/multiqc/main.nf +++ b/modules/nf-core/multiqc/main.nf @@ -1,25 +1,21 @@ process MULTIQC { + tag "${meta.id}" label 'process_single' conda "${moduleDir}/environment.yml" - container "${ workflow.containerEngine == 'singularity' && !task.ext.singularity_pull_docker_container ? - 'https://community-cr-prod.seqera.io/docker/registry/v2/blobs/sha256/34/34e733a9ae16a27e80fe00f863ea1479c96416017f24a907996126283e7ecd4d/data' : - 'community.wave.seqera.io/library/multiqc:1.33--ee7739d47738383b' }" + container "${workflow.containerEngine in ['singularity', 'apptainer'] && !task.ext.singularity_pull_docker_container + ? 'https://community-cr-prod.seqera.io/docker/registry/v2/blobs/sha256/c8/c8e346f4f6080eadf1253505e6ff09ef004454fc18e8d672006fd7b222cc412e/data' + : 'community.wave.seqera.io/library/multiqc:1.35--c17fb751507e9dfc'}" input: - path multiqc_files, stageAs: "?/*" - path(multiqc_config) - path(extra_multiqc_config) - path(multiqc_logo) - path(replace_names) - path(sample_names) + tuple val(meta), path(multiqc_files, stageAs: "?/*"), path(multiqc_config, stageAs: "?/*"), path(multiqc_logo), path(replace_names), path(sample_names) output: - path "*.html" , emit: report - path "*_data" , emit: data - path "*_plots" , optional:true, emit: plots - tuple val("${task.process}"), val('multiqc'), eval('multiqc --version | sed "s/.* //g"'), emit: versions + tuple val(meta), path("*.html"), emit: report + tuple val(meta), path("*_data"), emit: data + tuple val(meta), path("*_plots"), emit: plots, optional: true // MultiQC should not push its versions to the `versions` topic. Its input depends on the versions topic to be resolved thus outputting to the topic will let the pipeline hang forever + tuple val("${task.process}"), val('multiqc'), eval('multiqc --version | sed "s/.* //g"'), emit: versions when: task.ext.when == null || task.ext.when @@ -27,8 +23,7 @@ process MULTIQC { script: def args = task.ext.args ?: '' def prefix = task.ext.prefix ? "--filename ${task.ext.prefix}.html" : '' - def config = multiqc_config ? "--config ${multiqc_config}" : '' - def extra_config = extra_multiqc_config ? "--config ${extra_multiqc_config}" : '' + def config = multiqc_config ? multiqc_config instanceof List ? "--config ${multiqc_config.join(' --config ')}" : "--config ${multiqc_config}" : "" def logo = multiqc_logo ? "--cl-config 'custom_logo: \"${multiqc_logo}\"'" : '' def replace = replace_names ? "--replace-names ${replace_names}" : '' def samples = sample_names ? "--sample-names ${sample_names}" : '' @@ -38,7 +33,6 @@ process MULTIQC { ${args} \\ ${config} \\ ${prefix} \\ - ${extra_config} \\ ${logo} \\ ${replace} \\ ${samples} \\ @@ -50,6 +44,7 @@ process MULTIQC { mkdir multiqc_data touch multiqc_data/.stub mkdir multiqc_plots + touch multiqc_plots/.stub touch multiqc_report.html """ } diff --git a/modules/nf-core/multiqc/meta.yml b/modules/nf-core/multiqc/meta.yml index 9fd34f37..27ce18d8 100644 --- a/modules/nf-core/multiqc/meta.yml +++ b/modules/nf-core/multiqc/meta.yml @@ -1,6 +1,6 @@ name: multiqc -description: Aggregate results from bioinformatics analyses across many samples into - a single report +description: Aggregate results from bioinformatics analyses across many samples + into a single report keywords: - QC - bioinformatics tools @@ -12,67 +12,81 @@ tools: It's a general use tool, perfect for summarising the output from numerous bioinformatics tools. homepage: https://multiqc.info/ documentation: https://multiqc.info/docs/ - licence: ["GPL-3.0-or-later"] + licence: + - "GPL-3.0-or-later" identifier: biotools:multiqc input: - - multiqc_files: - type: file - description: | - List of reports / files recognised by MultiQC, for example the html and zip output of FastQC - ontologies: [] - - multiqc_config: - type: file - description: Optional config yml for MultiQC - pattern: "*.{yml,yaml}" - ontologies: - - edam: http://edamontology.org/format_3750 # YAML - - extra_multiqc_config: - type: file - description: Second optional config yml for MultiQC. Will override common sections - in multiqc_config. - pattern: "*.{yml,yaml}" - ontologies: - - edam: http://edamontology.org/format_3750 # YAML - - multiqc_logo: - type: file - description: Optional logo file for MultiQC - pattern: "*.{png}" - ontologies: [] - - replace_names: - type: file - description: | - Optional two-column sample renaming file. First column a set of - patterns, second column a set of corresponding replacements. Passed via - MultiQC's `--replace-names` option. - pattern: "*.{tsv}" - ontologies: - - edam: http://edamontology.org/format_3475 # TSV - - sample_names: - type: file - description: | - Optional TSV file with headers, passed to the MultiQC --sample_names - argument. - pattern: "*.{tsv}" - ontologies: - - edam: http://edamontology.org/format_3475 # TSV -output: - report: - - "*.html": + - - meta: + type: map + description: | + Groovy Map containing sample information + e.g. [ id:'sample1', single_end:false ] + - multiqc_files: type: file - description: MultiQC report file - pattern: ".html" + description: | + List of reports / files recognised by MultiQC, for example the html and zip output of FastQC ontologies: [] - data: - - "*_data": - type: directory - description: MultiQC data dir - pattern: "multiqc_data" - plots: - - "*_plots": + - multiqc_config: type: file - description: Plots created by MultiQC - pattern: "*_plots" + description: Optional config yml for MultiQC + pattern: "*.{yml,yaml}" + ontologies: + - edam: http://edamontology.org/format_3750 + - multiqc_logo: + type: file + description: Optional logo file for MultiQC + pattern: "*.{png}" ontologies: [] + - replace_names: + type: file + description: | + Optional two-column sample renaming file. First column a set of + patterns, second column a set of corresponding replacements. Passed via + MultiQC's `--replace-names` option. + pattern: "*.{tsv}" + ontologies: + - edam: http://edamontology.org/format_3475 + - sample_names: + type: file + description: | + Optional TSV file with headers, passed to the MultiQC --sample_names + argument. + pattern: "*.{tsv}" + ontologies: + - edam: http://edamontology.org/format_3475 +output: + report: + - - meta: + type: map + description: | + Groovy Map containing sample information + e.g. [ id:'sample1', single_end:false ] + - "*.html": + type: file + description: MultiQC report file + pattern: ".html" + ontologies: [] + data: + - - meta: + type: map + description: | + Groovy Map containing sample information + e.g. [ id:'sample1', single_end:false ] + - "*_data": + type: directory + description: MultiQC data dir + pattern: "multiqc_data" + plots: + - - meta: + type: map + description: | + Groovy Map containing sample information + e.g. [ id:'sample1', single_end:false ] + - "*_plots": + type: file + description: Plots created by MultiQC + pattern: "*_plots" + ontologies: [] versions: - - ${task.process}: type: string @@ -96,24 +110,24 @@ maintainers: containers: conda: linux/amd64: - lock_file: https://wave.seqera.io/v1alpha1/builds/bd-ee7739d47738383b_1/condalock + lock_file: modules/nf-core/multiqc/.conda-lock/linux_amd64-bd-c17fb751507e9dfc_1.txt linux/arm64: - lock_file: https://wave.seqera.io/v1alpha1/builds/bd-58d7dee710ab3aa8_1/condalock + lock_file: modules/nf-core/multiqc/.conda-lock/linux_arm64-bd-5c84a5000a226ab5_1.txt docker: linux/amd64: - build_id: bd-ee7739d47738383b_1 - name: community.wave.seqera.io/library/multiqc:1.33--ee7739d47738383b - scanId: sc-6ddec592dcadd583_4 + name: community.wave.seqera.io/library/multiqc:1.35--c17fb751507e9dfc + build_id: bd-c17fb751507e9dfc_1 + scan_id: sc-3b1b3932f9846892_1 linux/arm64: - build_id: bd-58d7dee710ab3aa8_1 - name: community.wave.seqera.io/library/multiqc:1.33--58d7dee710ab3aa8 - scanId: sc-a04c42273e34c55c_2 + name: community.wave.seqera.io/library/multiqc:1.35--5c84a5000a226ab5 + build_id: bd-5c84a5000a226ab5_1 + scan_id: sc-0d39df41e9737bbd_1 singularity: linux/amd64: - build_id: bd-e3576ddf588fa00d_1 - https: https://community-cr-prod.seqera.io/docker/registry/v2/blobs/sha256/34/34e733a9ae16a27e80fe00f863ea1479c96416017f24a907996126283e7ecd4d/data - name: oras://community.wave.seqera.io/library/multiqc:1.33--e3576ddf588fa00d + name: oras://community.wave.seqera.io/library/multiqc:1.35--c680f2aea25ccec2 + build_id: bd-c680f2aea25ccec2_1 + https: https://community-cr-prod.seqera.io/docker/registry/v2/blobs/sha256/c8/c8e346f4f6080eadf1253505e6ff09ef004454fc18e8d672006fd7b222cc412e/data linux/arm64: - build_id: bd-2537ca5f8445e3c2_1 - https: https://community-cr-prod.seqera.io/docker/registry/v2/blobs/sha256/78/78b89e91d89e9cc99ad5ade5be311f347838cb2acbfb4f13bc343b170be09ce4/data - name: oras://community.wave.seqera.io/library/multiqc:1.33--2537ca5f8445e3c2 + name: oras://community.wave.seqera.io/library/multiqc:1.35--c0468833d65b2f81 + build_id: bd-c0468833d65b2f81_1 + https: https://community-cr-prod.seqera.io/docker/registry/v2/blobs/sha256/e4/e48aa28aebc881254a499b24c3e1ce77b8df1b85a5432699ed6f72eb17ac7fb5/data diff --git a/modules/nf-core/multiqc/tests/main.nf.test b/modules/nf-core/multiqc/tests/main.nf.test index d1ae8b06..4cbdb95d 100644 --- a/modules/nf-core/multiqc/tests/main.nf.test +++ b/modules/nf-core/multiqc/tests/main.nf.test @@ -15,25 +15,41 @@ nextflow_process { when { process { """ - input[0] = Channel.of(file(params.modules_testdata_base_path + 'genomics/sarscov2/illumina/fastqc/test_fastqc.zip', checkIfExists: true)) - input[1] = [] - input[2] = [] - input[3] = [] - input[4] = [] - input[5] = [] + input[0] = channel.of([ + [ id: 'FASTQC' ], + file(params.modules_testdata_base_path + 'genomics/sarscov2/illumina/fastqc/test_fastqc.zip', checkIfExists: true), + [], + [], + [], + [] + ]) """ } } then { - assertAll( - { assert process.success }, - { assert process.out.report[0] ==~ ".*/multiqc_report.html" }, - { assert process.out.data[0] ==~ ".*/multiqc_data" }, - { assert snapshot(process.out.findAll { key, val -> key.startsWith("versions")}).match() } - ) + assert process.success + assert snapshot( + sanitizeOutput(process.out).collectEntries { key, val -> + if (key == "data") { + return [key, val.collect { [path(it[1]).list().collect { file(it.toString()).name }] }] + } + else if (key == "plots") { + return [key, val.collect { [ + "pdf", + path("${it[1]}/pdf").list().collect { file(it.toString()).name }, + "png", + path("${it[1]}/png").list().collect { file(it.toString()).name }, + "svg", + path("${it[1]}/svg").list().collect { file(it.toString()).name }] }] + } + else if (key == "report") { + return [key, file(val[0][1].toString()).name] + } + return [key, val] + } + ).match() } - } test("sarscov2 single-end [fastqc] - custom prefix") { @@ -42,24 +58,41 @@ nextflow_process { when { process { """ - input[0] = Channel.of(file(params.modules_testdata_base_path + 'genomics/sarscov2/illumina/fastqc/test_fastqc.zip', checkIfExists: true)) - input[1] = [] - input[2] = [] - input[3] = [] - input[4] = [] - input[5] = [] + input[0] = channel.of([ + [ id: 'FASTQC' ], + file(params.modules_testdata_base_path + 'genomics/sarscov2/illumina/fastqc/test_fastqc.zip', checkIfExists: true), + [], + [], + [], + [] + ]) """ } } then { - assertAll( - { assert process.success }, - { assert process.out.report[0] ==~ ".*/custom_prefix.html" }, - { assert process.out.data[0] ==~ ".*/custom_prefix_data" } - ) + assert process.success + assert snapshot( + sanitizeOutput(process.out).collectEntries { key, val -> + if (key == "data") { + return [key, val.collect { [path(it[1]).list().collect { file(it.toString()).name }] }] + } + else if (key == "plots") { + return [key, val.collect { [ + "pdf", + path("${it[1]}/pdf").list().collect { file(it.toString()).name }, + "png", + path("${it[1]}/png").list().collect { file(it.toString()).name }, + "svg", + path("${it[1]}/svg").list().collect { file(it.toString()).name }] }] + } + else if (key == "report") { + return [key, file(val[0][1].toString()).name] + } + return [key, val] + } + ).match() } - } test("sarscov2 single-end [fastqc] [config]") { @@ -67,23 +100,85 @@ nextflow_process { when { process { """ - input[0] = Channel.of(file(params.modules_testdata_base_path + 'genomics/sarscov2/illumina/fastqc/test_fastqc.zip', checkIfExists: true)) - input[1] = Channel.of(file("https://github.com/nf-core/tools/raw/dev/nf_core/pipeline-template/assets/multiqc_config.yml", checkIfExists: true)) - input[2] = [] - input[3] = [] - input[4] = [] - input[5] = [] + input[0] = channel.of([ + [ id: 'FASTQC' ], + file(params.modules_testdata_base_path + 'genomics/sarscov2/illumina/fastqc/test_fastqc.zip', checkIfExists: true), + file("https://raw.githubusercontent.com/nf-core/seqinspector/1.0.0/assets/multiqc_config.yml", checkIfExists: true), + [], + [], + [] + ]) """ } } then { - assertAll( - { assert process.success }, - { assert process.out.report[0] ==~ ".*/multiqc_report.html" }, - { assert process.out.data[0] ==~ ".*/multiqc_data" }, - { assert snapshot(process.out.findAll { key, val -> key.startsWith("versions")}).match() } - ) + assert process.success + assert snapshot( + sanitizeOutput(process.out).collectEntries { key, val -> + if (key == "data") { + return [key, val.collect { [path(it[1]).list().collect { file(it.toString()).name }] }] + } + else if (key == "plots") { + return [key, val.collect { [ + "pdf", + path("${it[1]}/pdf").list().collect { file(it.toString()).name }, + "png", + path("${it[1]}/png").list().collect { file(it.toString()).name }, + "svg", + path("${it[1]}/svg").list().collect { file(it.toString()).name }] }] + } + else if (key == "report") { + return [key, file(val[0][1].toString()).name] + } + return [key, val] + } + ).match() + } + } + + test("sarscov2 single-end [fastqc] [multiple configs]") { + + when { + process { + """ + input[0] = channel.of([ + [ id: 'FASTQC' ], + file(params.modules_testdata_base_path + 'genomics/sarscov2/illumina/fastqc/test_fastqc.zip', checkIfExists: true), + [ + file("https://raw.githubusercontent.com/nf-core/seqinspector/1.0.0/assets/multiqc_config.yml", checkIfExists: true), + file("https://raw.githubusercontent.com/nf-core/seqinspector/1.0.0/assets/multiqc_config.yml", checkIfExists: true) + ], + [], + [], + [] + ]) + """ + } + } + + then { + assert process.success + assert snapshot( + sanitizeOutput(process.out).collectEntries { key, val -> + if (key == "data") { + return [key, val.collect { [path(it[1]).list().collect { file(it.toString()).name }] }] + } + else if (key == "plots") { + return [key, val.collect { [ + "pdf", + path("${it[1]}/pdf").list().collect { file(it.toString()).name }, + "png", + path("${it[1]}/png").list().collect { file(it.toString()).name }, + "svg", + path("${it[1]}/svg").list().collect { file(it.toString()).name }] }] + } + else if (key == "report") { + return [key, file(val[0][1].toString()).name] + } + return [key, val] + } + ).match() } } @@ -94,25 +189,23 @@ nextflow_process { when { process { """ - input[0] = Channel.of(file(params.modules_testdata_base_path + 'genomics/sarscov2/illumina/fastqc/test_fastqc.zip', checkIfExists: true)) - input[1] = [] - input[2] = [] - input[3] = [] - input[4] = [] - input[5] = [] + input[0] = channel.of([ + [ id: 'FASTQC' ], + file(params.modules_testdata_base_path + 'genomics/sarscov2/illumina/fastqc/test_fastqc.zip', checkIfExists: true), + [], + [], + [], + [] + ]) """ } } then { + assert process.success assertAll( - { assert process.success }, - { assert snapshot(process.out.report.collect { file(it).getName() } + - process.out.data.collect { file(it).getName() } + - process.out.plots.collect { file(it).getName() } + - process.out.findAll { key, val -> key.startsWith("versions")} ).match() } + { assert snapshot(sanitizeOutput(process.out)).match() } ) } - } } diff --git a/modules/nf-core/multiqc/tests/main.nf.test.snap b/modules/nf-core/multiqc/tests/main.nf.test.snap index d72d35b7..44899216 100644 --- a/modules/nf-core/multiqc/tests/main.nf.test.snap +++ b/modules/nf-core/multiqc/tests/main.nf.test.snap @@ -1,61 +1,422 @@ { + "sarscov2 single-end [fastqc] [multiple configs]": { + "content": [ + { + "data": [ + [ + [ + "fastqc-status-check-heatmap.txt", + "fastqc_overrepresented_sequences_plot.txt", + "fastqc_per_base_n_content_plot.txt", + "fastqc_per_base_sequence_quality_plot.txt", + "fastqc_per_sequence_gc_content_plot_Counts.txt", + "fastqc_per_sequence_gc_content_plot_Percentages.txt", + "fastqc_per_sequence_quality_scores_plot.txt", + "fastqc_sequence_counts_plot.txt", + "fastqc_sequence_duplication_levels_plot.txt", + "fastqc_sequence_length_distribution_plot.txt", + "fastqc_top_overrepresented_sequences_table.txt", + "llms-full.txt", + "multiqc.log", + "multiqc.parquet", + "multiqc_citations.txt", + "multiqc_data.json", + "multiqc_fastqc.txt", + "multiqc_general_stats.txt", + "multiqc_sources.txt" + ] + ] + ], + "plots": [ + [ + "pdf", + [ + "fastqc-status-check-heatmap.pdf", + "fastqc_overrepresented_sequences_plot.pdf", + "fastqc_per_base_n_content_plot.pdf", + "fastqc_per_base_sequence_quality_plot.pdf", + "fastqc_per_sequence_gc_content_plot_Counts.pdf", + "fastqc_per_sequence_gc_content_plot_Percentages.pdf", + "fastqc_per_sequence_quality_scores_plot.pdf", + "fastqc_sequence_counts_plot-cnt.pdf", + "fastqc_sequence_counts_plot-pct.pdf", + "fastqc_sequence_duplication_levels_plot.pdf", + "fastqc_sequence_length_distribution_plot.pdf", + "fastqc_top_overrepresented_sequences_table.pdf" + ], + "png", + [ + "fastqc-status-check-heatmap.png", + "fastqc_overrepresented_sequences_plot.png", + "fastqc_per_base_n_content_plot.png", + "fastqc_per_base_sequence_quality_plot.png", + "fastqc_per_sequence_gc_content_plot_Counts.png", + "fastqc_per_sequence_gc_content_plot_Percentages.png", + "fastqc_per_sequence_quality_scores_plot.png", + "fastqc_sequence_counts_plot-cnt.png", + "fastqc_sequence_counts_plot-pct.png", + "fastqc_sequence_duplication_levels_plot.png", + "fastqc_sequence_length_distribution_plot.png", + "fastqc_top_overrepresented_sequences_table.png" + ], + "svg", + [ + "fastqc-status-check-heatmap.svg", + "fastqc_overrepresented_sequences_plot.svg", + "fastqc_per_base_n_content_plot.svg", + "fastqc_per_base_sequence_quality_plot.svg", + "fastqc_per_sequence_gc_content_plot_Counts.svg", + "fastqc_per_sequence_gc_content_plot_Percentages.svg", + "fastqc_per_sequence_quality_scores_plot.svg", + "fastqc_sequence_counts_plot-cnt.svg", + "fastqc_sequence_counts_plot-pct.svg", + "fastqc_sequence_duplication_levels_plot.svg", + "fastqc_sequence_length_distribution_plot.svg", + "fastqc_top_overrepresented_sequences_table.svg" + ] + ] + ], + "report": "multiqc_report.html", + "versions": [ + [ + "MULTIQC", + "multiqc", + "1.35" + ] + ] + } + ], + "timestamp": "2026-03-17T16:15:42.577775492", + "meta": { + "nf-test": "0.9.4", + "nextflow": "25.10.4" + } + }, "sarscov2 single-end [fastqc]": { "content": [ { + "data": [ + [ + [ + "fastqc-status-check-heatmap.txt", + "fastqc_overrepresented_sequences_plot.txt", + "fastqc_per_base_n_content_plot.txt", + "fastqc_per_base_sequence_quality_plot.txt", + "fastqc_per_sequence_gc_content_plot_Counts.txt", + "fastqc_per_sequence_gc_content_plot_Percentages.txt", + "fastqc_per_sequence_quality_scores_plot.txt", + "fastqc_sequence_counts_plot.txt", + "fastqc_sequence_duplication_levels_plot.txt", + "fastqc_sequence_length_distribution_plot.txt", + "fastqc_top_overrepresented_sequences_table.txt", + "llms-full.txt", + "multiqc.log", + "multiqc.parquet", + "multiqc_citations.txt", + "multiqc_data.json", + "multiqc_fastqc.txt", + "multiqc_general_stats.txt", + "multiqc_software_versions.txt", + "multiqc_sources.txt" + ] + ] + ], + "plots": [ + [ + "pdf", + [ + "fastqc-status-check-heatmap.pdf", + "fastqc_overrepresented_sequences_plot.pdf", + "fastqc_per_base_n_content_plot.pdf", + "fastqc_per_base_sequence_quality_plot.pdf", + "fastqc_per_sequence_gc_content_plot_Counts.pdf", + "fastqc_per_sequence_gc_content_plot_Percentages.pdf", + "fastqc_per_sequence_quality_scores_plot.pdf", + "fastqc_sequence_counts_plot-cnt.pdf", + "fastqc_sequence_counts_plot-pct.pdf", + "fastqc_sequence_duplication_levels_plot.pdf", + "fastqc_sequence_length_distribution_plot.pdf", + "fastqc_top_overrepresented_sequences_table.pdf" + ], + "png", + [ + "fastqc-status-check-heatmap.png", + "fastqc_overrepresented_sequences_plot.png", + "fastqc_per_base_n_content_plot.png", + "fastqc_per_base_sequence_quality_plot.png", + "fastqc_per_sequence_gc_content_plot_Counts.png", + "fastqc_per_sequence_gc_content_plot_Percentages.png", + "fastqc_per_sequence_quality_scores_plot.png", + "fastqc_sequence_counts_plot-cnt.png", + "fastqc_sequence_counts_plot-pct.png", + "fastqc_sequence_duplication_levels_plot.png", + "fastqc_sequence_length_distribution_plot.png", + "fastqc_top_overrepresented_sequences_table.png" + ], + "svg", + [ + "fastqc-status-check-heatmap.svg", + "fastqc_overrepresented_sequences_plot.svg", + "fastqc_per_base_n_content_plot.svg", + "fastqc_per_base_sequence_quality_plot.svg", + "fastqc_per_sequence_gc_content_plot_Counts.svg", + "fastqc_per_sequence_gc_content_plot_Percentages.svg", + "fastqc_per_sequence_quality_scores_plot.svg", + "fastqc_sequence_counts_plot-cnt.svg", + "fastqc_sequence_counts_plot-pct.svg", + "fastqc_sequence_duplication_levels_plot.svg", + "fastqc_sequence_length_distribution_plot.svg", + "fastqc_top_overrepresented_sequences_table.svg" + ] + ] + ], + "report": "multiqc_report.html", "versions": [ [ "MULTIQC", "multiqc", - "1.33" + "1.35" ] ] } ], + "timestamp": "2026-03-17T16:21:17.072841555", "meta": { - "nf-test": "0.9.3", - "nextflow": "25.10.2" - }, - "timestamp": "2025-12-09T10:10:43.020315838" + "nf-test": "0.9.4", + "nextflow": "25.10.4" + } }, "sarscov2 single-end [fastqc] - stub": { "content": [ - [ - "multiqc_report.html", - "multiqc_data", - "multiqc_plots", - { - "versions": [ + { + "data": [ + [ + { + "id": "FASTQC" + }, + [ + ".stub:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ] + ], + "plots": [ + [ + { + "id": "FASTQC" + }, [ - "MULTIQC", - "multiqc", - "1.33" + ".stub:md5,d41d8cd98f00b204e9800998ecf8427e" ] ] - } - ] + ], + "report": [ + [ + { + "id": "FASTQC" + }, + "multiqc_report.html:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "versions": [ + [ + "MULTIQC", + "multiqc", + "1.35" + ] + ] + } ], + "timestamp": "2026-02-26T15:14:39.789193051", "meta": { - "nf-test": "0.9.3", - "nextflow": "25.10.2" - }, - "timestamp": "2025-12-09T10:11:14.131950776" + "nf-test": "0.9.4", + "nextflow": "25.10.4" + } }, "sarscov2 single-end [fastqc] [config]": { "content": [ { + "data": [ + [ + [ + "fastqc-status-check-heatmap.txt", + "fastqc_overrepresented_sequences_plot.txt", + "fastqc_per_base_n_content_plot.txt", + "fastqc_per_base_sequence_quality_plot.txt", + "fastqc_per_sequence_gc_content_plot_Counts.txt", + "fastqc_per_sequence_gc_content_plot_Percentages.txt", + "fastqc_per_sequence_quality_scores_plot.txt", + "fastqc_sequence_counts_plot.txt", + "fastqc_sequence_duplication_levels_plot.txt", + "fastqc_sequence_length_distribution_plot.txt", + "fastqc_top_overrepresented_sequences_table.txt", + "llms-full.txt", + "multiqc.log", + "multiqc.parquet", + "multiqc_citations.txt", + "multiqc_data.json", + "multiqc_fastqc.txt", + "multiqc_general_stats.txt", + "multiqc_sources.txt" + ] + ] + ], + "plots": [ + [ + "pdf", + [ + "fastqc-status-check-heatmap.pdf", + "fastqc_overrepresented_sequences_plot.pdf", + "fastqc_per_base_n_content_plot.pdf", + "fastqc_per_base_sequence_quality_plot.pdf", + "fastqc_per_sequence_gc_content_plot_Counts.pdf", + "fastqc_per_sequence_gc_content_plot_Percentages.pdf", + "fastqc_per_sequence_quality_scores_plot.pdf", + "fastqc_sequence_counts_plot-cnt.pdf", + "fastqc_sequence_counts_plot-pct.pdf", + "fastqc_sequence_duplication_levels_plot.pdf", + "fastqc_sequence_length_distribution_plot.pdf", + "fastqc_top_overrepresented_sequences_table.pdf" + ], + "png", + [ + "fastqc-status-check-heatmap.png", + "fastqc_overrepresented_sequences_plot.png", + "fastqc_per_base_n_content_plot.png", + "fastqc_per_base_sequence_quality_plot.png", + "fastqc_per_sequence_gc_content_plot_Counts.png", + "fastqc_per_sequence_gc_content_plot_Percentages.png", + "fastqc_per_sequence_quality_scores_plot.png", + "fastqc_sequence_counts_plot-cnt.png", + "fastqc_sequence_counts_plot-pct.png", + "fastqc_sequence_duplication_levels_plot.png", + "fastqc_sequence_length_distribution_plot.png", + "fastqc_top_overrepresented_sequences_table.png" + ], + "svg", + [ + "fastqc-status-check-heatmap.svg", + "fastqc_overrepresented_sequences_plot.svg", + "fastqc_per_base_n_content_plot.svg", + "fastqc_per_base_sequence_quality_plot.svg", + "fastqc_per_sequence_gc_content_plot_Counts.svg", + "fastqc_per_sequence_gc_content_plot_Percentages.svg", + "fastqc_per_sequence_quality_scores_plot.svg", + "fastqc_sequence_counts_plot-cnt.svg", + "fastqc_sequence_counts_plot-pct.svg", + "fastqc_sequence_duplication_levels_plot.svg", + "fastqc_sequence_length_distribution_plot.svg", + "fastqc_top_overrepresented_sequences_table.svg" + ] + ] + ], + "report": "multiqc_report.html", + "versions": [ + [ + "MULTIQC", + "multiqc", + "1.35" + ] + ] + } + ], + "timestamp": "2026-03-17T16:15:30.372239611", + "meta": { + "nf-test": "0.9.4", + "nextflow": "25.10.4" + } + }, + "sarscov2 single-end [fastqc] - custom prefix": { + "content": [ + { + "data": [ + [ + [ + "fastqc-status-check-heatmap.txt", + "fastqc_overrepresented_sequences_plot.txt", + "fastqc_per_base_n_content_plot.txt", + "fastqc_per_base_sequence_quality_plot.txt", + "fastqc_per_sequence_gc_content_plot_Counts.txt", + "fastqc_per_sequence_gc_content_plot_Percentages.txt", + "fastqc_per_sequence_quality_scores_plot.txt", + "fastqc_sequence_counts_plot.txt", + "fastqc_sequence_duplication_levels_plot.txt", + "fastqc_sequence_length_distribution_plot.txt", + "fastqc_top_overrepresented_sequences_table.txt", + "llms-full.txt", + "multiqc.log", + "multiqc.parquet", + "multiqc_citations.txt", + "multiqc_data.json", + "multiqc_fastqc.txt", + "multiqc_general_stats.txt", + "multiqc_software_versions.txt", + "multiqc_sources.txt" + ] + ] + ], + "plots": [ + [ + "pdf", + [ + "fastqc-status-check-heatmap.pdf", + "fastqc_overrepresented_sequences_plot.pdf", + "fastqc_per_base_n_content_plot.pdf", + "fastqc_per_base_sequence_quality_plot.pdf", + "fastqc_per_sequence_gc_content_plot_Counts.pdf", + "fastqc_per_sequence_gc_content_plot_Percentages.pdf", + "fastqc_per_sequence_quality_scores_plot.pdf", + "fastqc_sequence_counts_plot-cnt.pdf", + "fastqc_sequence_counts_plot-pct.pdf", + "fastqc_sequence_duplication_levels_plot.pdf", + "fastqc_sequence_length_distribution_plot.pdf", + "fastqc_top_overrepresented_sequences_table.pdf" + ], + "png", + [ + "fastqc-status-check-heatmap.png", + "fastqc_overrepresented_sequences_plot.png", + "fastqc_per_base_n_content_plot.png", + "fastqc_per_base_sequence_quality_plot.png", + "fastqc_per_sequence_gc_content_plot_Counts.png", + "fastqc_per_sequence_gc_content_plot_Percentages.png", + "fastqc_per_sequence_quality_scores_plot.png", + "fastqc_sequence_counts_plot-cnt.png", + "fastqc_sequence_counts_plot-pct.png", + "fastqc_sequence_duplication_levels_plot.png", + "fastqc_sequence_length_distribution_plot.png", + "fastqc_top_overrepresented_sequences_table.png" + ], + "svg", + [ + "fastqc-status-check-heatmap.svg", + "fastqc_overrepresented_sequences_plot.svg", + "fastqc_per_base_n_content_plot.svg", + "fastqc_per_base_sequence_quality_plot.svg", + "fastqc_per_sequence_gc_content_plot_Counts.svg", + "fastqc_per_sequence_gc_content_plot_Percentages.svg", + "fastqc_per_sequence_quality_scores_plot.svg", + "fastqc_sequence_counts_plot-cnt.svg", + "fastqc_sequence_counts_plot-pct.svg", + "fastqc_sequence_duplication_levels_plot.svg", + "fastqc_sequence_length_distribution_plot.svg", + "fastqc_top_overrepresented_sequences_table.svg" + ] + ] + ], + "report": "custom_prefix.html", "versions": [ [ "MULTIQC", "multiqc", - "1.33" + "1.35" ] ] } ], + "timestamp": "2026-03-17T16:15:18.189023981", "meta": { - "nf-test": "0.9.3", - "nextflow": "25.10.2" - }, - "timestamp": "2025-12-09T10:11:07.15692209" + "nf-test": "0.9.4", + "nextflow": "25.10.4" + } } } \ No newline at end of file diff --git a/modules/nf-core/multiqc/tests/nextflow.config b/modules/nf-core/multiqc/tests/nextflow.config index c537a6a3..374dfef2 100644 --- a/modules/nf-core/multiqc/tests/nextflow.config +++ b/modules/nf-core/multiqc/tests/nextflow.config @@ -1,5 +1,6 @@ process { withName: 'MULTIQC' { ext.prefix = null + ext.args = '-p' } } diff --git a/modules/nf-core/multiqc/tests/tags.yml b/modules/nf-core/multiqc/tests/tags.yml deleted file mode 100644 index bea6c0d3..00000000 --- a/modules/nf-core/multiqc/tests/tags.yml +++ /dev/null @@ -1,2 +0,0 @@ -multiqc: - - modules/nf-core/multiqc/** diff --git a/nextflow.config b/nextflow.config index b3074dbe..31fcd706 100644 --- a/nextflow.config +++ b/nextflow.config @@ -268,7 +268,6 @@ params { email_on_fail = null plaintext_email = false monochrome_logs = false - hook_url = System.getenv('HOOK_URL') help = false help_full = false show_hidden = false @@ -289,6 +288,10 @@ params { validate_params = true } +// Backwards compatibility for publishDir syntax +outputDir = params.outdir +workflow.output.mode = params.publish_dir_mode + // Load base.config by default for all pipelines includeConfig 'conf/base.config' @@ -547,7 +550,7 @@ manifest { description = """Comprehensive pipeline for donor demultiplexing in single cell""" mainScript = 'main.nf' defaultBranch = 'main' - nextflowVersion = '!>=25.04.0' + nextflowVersion = '!>=25.10.4' version = '1.0.0dev' doi = '10.1101/2023.07.23.550061' } @@ -561,6 +564,5 @@ validation { defaultIgnoreParams = ["genomes"] monochromeLogs = params.monochrome_logs } - // Load modules.config for DSL2 module specific options includeConfig 'conf/modules.config' diff --git a/nextflow_schema.json b/nextflow_schema.json index 6bcd2972..457d4eb2 100644 --- a/nextflow_schema.json +++ b/nextflow_schema.json @@ -116,7 +116,6 @@ }, "igenomes_base": { "type": "string", - "format": "directory-path", "description": "The base path to the igenomes reference files", "fa_icon": "fas fa-ban", "hidden": true, @@ -1371,13 +1370,6 @@ "fa_icon": "fas fa-palette", "hidden": true }, - "hook_url": { - "type": "string", - "description": "Incoming hook URL for messaging service", - "fa_icon": "fas fa-people-group", - "help_text": "Incoming hook URL for messaging service. Currently, MS Teams and Slack are supported.", - "hidden": true - }, "multiqc_config": { "type": "string", "format": "file-path", diff --git a/nf-test.config b/nf-test.config index 8e869e2a..c5fa4192 100644 --- a/nf-test.config +++ b/nf-test.config @@ -1,21 +1,38 @@ config { // location for all nf-test tests - testsDir "." + testsDir = "." // nf-test directory including temporary files for each test - workDir System.getenv("NFT_WORKDIR") ?: ".nf-test" + workDir = System.getenv("NFT_WORKDIR") ?: ".nf-test" // location of an optional nextflow.config file specific for executing tests - configFile "tests/nextflow.config" + configFile = "tests/nextflow.config" // ignore tests coming from the nf-core/modules repo - ignore 'modules/nf-core/**/tests/*', 'subworkflows/nf-core/**/tests/*' + ignore = [ + 'modules/nf-core/**/tests/*', + 'subworkflows/nf-core/**/tests/*', + ] // run all test with defined profile(s) from the main nextflow.config - profile "test" + profile = "test" // list of filenames or patterns that should be trigger a full test run - triggers 'nextflow.config', 'nf-test.config', 'conf/test.config', 'conf/test_genetic.config', 'conf/test_hashing.config', 'conf/test_donor_match.config', 'tests/nextflow.config', 'tests/.nftignore' + triggers = [ + '.github/actions/nf-test/action.yml', + '.github/workflows/nf-test.yml', + 'assets/schema_input.json', + 'bin/*', + 'conf/test.config', + 'conf/test_genetic.config', + 'conf/test_hashing.config', + 'conf/test_donor_match.config', + 'nextflow.config', + 'nextflow_schema.json', + 'nf-test.config', + 'tests/.nftignore', + 'tests/nextflow.config', + ] // load the necessary plugins plugins { diff --git a/ro-crate-metadata.json b/ro-crate-metadata.json index 0f285955..a3e32dc0 100644 --- a/ro-crate-metadata.json +++ b/ro-crate-metadata.json @@ -1,6 +1,6 @@ { "@context": [ - "https://w3id.org/ro/crate/1.1/context", + "https://w3id.org/ro/crate/1.2/context", { "GithubService": "https://w3id.org/ro/terms/test#GithubService", "JenkinsService": "https://w3id.org/ro/terms/test#JenkinsService", @@ -22,8 +22,8 @@ "@id": "./", "@type": "Dataset", "creativeWorkStatus": "InProgress", - "datePublished": "2026-02-15T09:55:13+00:00", - "description": "

    \n \n \n \"nf-core/hadge\"\n \n

    \n\n[![Open in GitHub Codespaces](https://img.shields.io/badge/Open_In_GitHub_Codespaces-black?labelColor=grey&logo=github)](https://github.com/codespaces/new/nf-core/hadge)\n[![GitHub Actions CI Status](https://github.com/nf-core/hadge/actions/workflows/nf-test.yml/badge.svg)](https://github.com/nf-core/hadge/actions/workflows/nf-test.yml)\n[![GitHub Actions Linting Status](https://github.com/nf-core/hadge/actions/workflows/linting.yml/badge.svg)](https://github.com/nf-core/hadge/actions/workflows/linting.yml)[![AWS CI](https://img.shields.io/badge/CI%20tests-full%20size-FF9900?labelColor=000000&logo=Amazon%20AWS)](https://nf-co.re/hadge/results)[![Cite with Zenodo](http://img.shields.io/badge/DOI-10.5281/zenodo.XXXXXXX-1073c8?labelColor=000000)](https://doi.org/10.5281/zenodo.XXXXXXX)\n[![nf-test](https://img.shields.io/badge/unit_tests-nf--test-337ab7.svg)](https://www.nf-test.com)\n\n[![Nextflow](https://img.shields.io/badge/version-%E2%89%A525.04.0-green?style=flat&logo=nextflow&logoColor=white&color=%230DC09D&link=https%3A%2F%2Fnextflow.io)](https://www.nextflow.io/)\n[![nf-core template version](https://img.shields.io/badge/nf--core_template-3.5.2-green?style=flat&logo=nfcore&logoColor=white&color=%2324B064&link=https%3A%2F%2Fnf-co.re)](https://github.com/nf-core/tools/releases/tag/3.5.2)\n[![run with conda](http://img.shields.io/badge/run%20with-conda-3EB049?labelColor=000000&logo=anaconda)](https://docs.conda.io/en/latest/)\n[![run with docker](https://img.shields.io/badge/run%20with-docker-0db7ed?labelColor=000000&logo=docker)](https://www.docker.com/)\n[![run with singularity](https://img.shields.io/badge/run%20with-singularity-1d355c.svg?labelColor=000000)](https://sylabs.io/docs/)\n[![Launch on Seqera Platform](https://img.shields.io/badge/Launch%20%F0%9F%9A%80-Seqera%20Platform-%234256e7)](https://cloud.seqera.io/launch?pipeline=https://github.com/nf-core/hadge)\n\n[![Get help on Slack](http://img.shields.io/badge/slack-nf--core%20%23hadge-4A154B?labelColor=000000&logo=slack)](https://nfcore.slack.com/channels/hadge)[![Follow on Bluesky](https://img.shields.io/badge/bluesky-%40nf__core-1185fe?labelColor=000000&logo=bluesky)](https://bsky.app/profile/nf-co.re)[![Follow on Mastodon](https://img.shields.io/badge/mastodon-nf__core-6364ff?labelColor=FFFFFF&logo=mastodon)](https://mstdn.science/@nf_core)[![Watch on YouTube](http://img.shields.io/badge/youtube-nf--core-FF0000?labelColor=000000&logo=youtube)](https://www.youtube.com/c/nf-core)\n\n## Introduction\n\n**nf-core/hadge** is a bioinformatics pipeline that ...\n\n\n\n\n2. Present QC for raw reads ([`MultiQC`](http://multiqc.info/))\n\n## Usage\n\n> [!NOTE]\n> If you are new to Nextflow and nf-core, please refer to [this page](https://nf-co.re/docs/usage/installation) on how to set-up Nextflow. Make sure to [test your setup](https://nf-co.re/docs/usage/introduction#how-to-run-a-pipeline) with `-profile test` before running the workflow on actual data.\n\n\n\nNow, you can run the pipeline using:\n\n\n\n```bash\nnextflow run nf-core/hadge \\\n -profile \\\n --input samplesheet.csv \\\n --outdir \n```\n\n> [!WARNING]\n> Please provide pipeline parameters via the CLI or Nextflow `-params-file` option. Custom config files including those provided by the `-c` Nextflow option can be used to provide any configuration _**except for parameters**_; see [docs](https://nf-co.re/docs/usage/getting_started/configuration#custom-configuration-files).\n\nFor more details and further functionality, please refer to the [usage documentation](https://nf-co.re/hadge/usage) and the [parameter documentation](https://nf-co.re/hadge/parameters).\n\n## Pipeline output\n\nTo see the results of an example test run with a full size dataset refer to the [results](https://nf-co.re/hadge/results) tab on the nf-core website pipeline page.\nFor more details about the output files and reports, please refer to the\n[output documentation](https://nf-co.re/hadge/output).\n\n## Credits\n\nnf-core/hadge was originally written by Fabiola Curion.\n\nWe thank the following people for their extensive assistance in the development of this pipeline:\n\n\n\n## Contributions and Support\n\nIf you would like to contribute to this pipeline, please see the [contributing guidelines](.github/CONTRIBUTING.md).\n\nFor further information or help, don't hesitate to get in touch on the [Slack `#hadge` channel](https://nfcore.slack.com/channels/hadge) (you can join with [this invite](https://nf-co.re/join/slack)).\n\n## Citations\n\n\n\n\n\n\nAn extensive list of references for the tools used by the pipeline can be found in the [`CITATIONS.md`](CITATIONS.md) file.\n\nYou can cite the `nf-core` publication as follows:\n\n> **The nf-core framework for community-curated bioinformatics pipelines.**\n>\n> Philip Ewels, Alexander Peltzer, Sven Fillinger, Harshil Patel, Johannes Alneberg, Andreas Wilm, Maxime Ulysse Garcia, Paolo Di Tommaso & Sven Nahnsen.\n>\n> _Nat Biotechnol._ 2020 Feb 13. doi: [10.1038/s41587-020-0439-x](https://dx.doi.org/10.1038/s41587-020-0439-x).\n", + "datePublished": "2026-04-30T13:33:34+00:00", + "description": "

    \n \n \n \"nf-core/hadge\"\n \n

    \n\n[![Open in GitHub Codespaces](https://img.shields.io/badge/Open_In_GitHub_Codespaces-black?labelColor=grey&logo=github)](https://github.com/codespaces/new/nf-core/hadge)\n[![GitHub Actions CI Status](https://github.com/nf-core/hadge/actions/workflows/nf-test.yml/badge.svg)](https://github.com/nf-core/hadge/actions/workflows/nf-test.yml)\n[![GitHub Actions Linting Status](https://github.com/nf-core/hadge/actions/workflows/linting.yml/badge.svg)](https://github.com/nf-core/hadge/actions/workflows/linting.yml)[![AWS CI](https://img.shields.io/badge/CI%20tests-full%20size-FF9900?labelColor=000000&logo=Amazon%20AWS)](https://nf-co.re/hadge/results)[![Cite with Zenodo](http://img.shields.io/badge/DOI-10.5281/zenodo.10634731-1073c8?labelColor=000000)](https://doi.org/10.5281/zenodo.10634731)\n[![nf-test](https://img.shields.io/badge/unit_tests-nf--test-337ab7.svg)](https://www.nf-test.com)\n\n[![Nextflow](https://img.shields.io/badge/version-%E2%89%A525.10.4-green?style=flat&logo=nextflow&logoColor=white&color=%230DC09D&link=https%3A%2F%2Fnextflow.io)](https://www.nextflow.io/)\n[![nf-core template version](https://img.shields.io/badge/nf--core_template-4.0.2-green?style=flat&logo=nfcore&logoColor=white&color=%2324B064&link=https%3A%2F%2Fnf-co.re)](https://github.com/nf-core/tools/releases/tag/4.0.2)\n[![run with conda](http://img.shields.io/badge/run%20with-conda-3EB049?labelColor=000000&logo=anaconda)](https://docs.conda.io/en/latest/)\n[![run with docker](https://img.shields.io/badge/run%20with-docker-0db7ed?labelColor=000000&logo=docker)](https://www.docker.com/)\n[![run with singularity](https://img.shields.io/badge/run%20with-singularity-1d355c.svg?labelColor=000000)](https://sylabs.io/docs/)\n[![Launch on Seqera Platform](https://img.shields.io/badge/Launch%20%F0%9F%9A%80-Seqera%20Platform-%234256e7)](https://cloud.seqera.io/launch?pipeline=https://github.com/nf-core/hadge)\n\n[![Get help on Slack](http://img.shields.io/badge/slack-nf--core%20%23hadge-4A154B?labelColor=000000&logo=slack)](https://nfcore.slack.com/channels/hadge)[![Follow on Bluesky](https://img.shields.io/badge/bluesky-%40nf__core-1185fe?labelColor=000000&logo=bluesky)](https://bsky.app/profile/nf-co.re)[![Follow on Mastodon](https://img.shields.io/badge/mastodon-nf__core-6364ff?labelColor=FFFFFF&logo=mastodon)](https://mstdn.science/@nf_core)[![Watch on YouTube](http://img.shields.io/badge/youtube-nf--core-FF0000?labelColor=000000&logo=youtube)](https://www.youtube.com/c/nf-core)\n\n## Introduction\n\n**nf-core/hadge** (**ha**shing **d**econvolution combined with **ge**notype information) is a bioinformatics pipeline that combines 11 methods to perform both hashing- and genotype-based deconvolution on single cell multiplexing data.\nIt takes a samplesheet with count matrices, BAM and VCF files as input, performs deconvolution with every method, joins all results and finally recovers previously discarded cells by combining the best performing methods (donor matching).\n\n![nf-core/hadge metro map](docs/images/pipeline.png)\n\n1. Untar matrices\n2. Extract hto names from matrix\n3. Perform genetic-based deconvolution\n 1. Get single cell genotype [`cellSNP`](https://github.com/single-cell-genetics/cellSNP)\n 2. [`vireo`](https://github.com/single-cell-genetics/vireo)\n 3. [`demuxlet`](https://github.com/statgen/popscle)\n 4. [`freemuxlet`](https://github.com/statgen/popscle)\n 5. [`souporcell`](https://github.com/wheaton5/souporcell)\n4. summarize assignments and classifications\n5. Perform hashing-based deconvolution\n 1. [`htodemux`](https://satijalab.org/seurat/articles/hashing_vignette)\n 2. [`multiseq`](https://satijalab.org/seurat/reference/multiseqdemux)\n 3. [`bff`](https://github.com/BimberLab/cellhashR)\n 4. [`demuxem`](https://demuxem.readthedocs.io/en/latest/)\n 5. [`gmm-demux`](https://github.com/CHPGenetics/GMM-demux)\n 6. [`hasheddrops`](https://github.com/MarioniLab/DropletUtils)\n 7. [`hashsolo`](https://scanpy.readthedocs.io/en/stable/generated/scanpy.external.pp.hashsolo.html)\n6. summarize assignments and classifications\n7. Join all results\n8. Donor match\n9. Find informative variants\n10. Create AnnData and Mudata objects\n11. [`MultiQC`](http://multiqc.info/)\n\n## Usage\n\n> [!NOTE]\n> If you are new to Nextflow and nf-core, please refer to [this page](https://nf-co.re/docs/usage/installation) on how to set-up Nextflow. Make sure to [test your setup](https://nf-co.re/docs/usage/introduction#how-to-run-a-pipeline) with `-profile test` before running the workflow on actual data. The profile `test` is used to test hadge's rescue mode, but you can also test the other modes with the profiles `test_genetic`, `test_hashing` and `test_donor_match`.\n\nFirst, prepare a samplesheet with your input data that looks as follows:\n\n`samplesheet.csv`:\n\n```csv\nsample,rna_matrix,hto_matrix,bam,vcf,n_samples,barcodes\nid1,rna.tar.gz,hto.tar.gz,chr21.bam,donor_genotype_chr21.vcf,2,barcodes.tsv\nid2,rna.tar.gz,hto.tar.gz,chr21.bam,donor_genotype_chr21.vcf,2,barcodes.tsv\nid3,rna.tar.gz,hto.tar.gz,chr21.bam,donor_genotype_chr21.vcf,2,barcodes.tsv\n```\n\nEach row contains data from a single-cell multiplexing experiment. The RNA-seq (`rna_matrix`) and hashing (`hto_matrix`) count matrices are provided in a 10x Genomics format and compressed as `.tar.gz`.\nGenetic deconvolution requires both the alignment file (`bam`) and a list of common SNPs (`vcf`). Users must specify the number of multiplexed donors (`n_samples`) and identify the target cells for deconvolution (`barcodes`).\n\nNow, you can run the pipeline using:\n\n```bash\nnextflow run nf-core/hadge \\\n -profile \\\n --input samplesheet.csv \\\n --outdir \\\n --mode rescue \\\n --hash_tools htodemux,hasheddrops,multiseq,gmm-demux,bff,hashsolo \\\n --genetic_tools demuxlet,freemuxlet,vireo,souporcell \\\n --fasta \n```\n\n> [!WARNING]\n> Please provide pipeline parameters via the CLI or Nextflow `-params-file` option. Custom config files including those provided by the `-c` Nextflow option can be used to provide any configuration _**except for parameters**_; see [docs](https://nf-co.re/docs/running/run-pipelines#using-parameter-files).\n\nFor more details and further functionality, please refer to the [usage documentation](https://nf-co.re/hadge/usage) and the [parameter documentation](https://nf-co.re/hadge/parameters).\n\n## Pipeline output\n\nTo see the results of an example test run with a full size dataset refer to the [results](https://nf-co.re/hadge/results) tab on the nf-core website pipeline page.\nFor more details about the output files and reports, please refer to the\n[output documentation](https://nf-co.re/hadge/output).\n\n## Credits\n\nnf-core/hadge was originally written by Fabiola Curion ([@bio-la](https://github.com/bio-la)), Xichen Wu ([@wxicu](https://github.com/wxicu)), Lukas Heumos ([@zethson](https://github.com/Zethson)) and Mariana Gonzales Andre ([@mari-ga](https://github.com/mari-ga)).\n\nWe thank the following people for rewriting the original pipeline within the nf-core framework:\n\n- [Luis Heinzlmeier](https://github.com/LuisHeinzlmeier)\n- [Nico Trummer](https://github.com/nictru)\n- [Seo Hyon Kim](https://github.com/seohyonkim)\n\n## Contributions and Support\n\nIf you would like to contribute to this pipeline, please see the [contributing guidelines](docs/CONTRIBUTING.md).\n\nFor further information or help, don't hesitate to get in touch on the [Slack `#hadge` channel](https://nfcore.slack.com/channels/hadge) (you can join with [this invite](https://nf-co.re/join/slack)).\n\n## Citations\n\nIf you use nf-core/hadge for your analysis, please cite it as follows:\n\n> **hadge: a comprehensive pipeline for donor deconvolution in single-cell studies.**\n>\n> Fabiola Curion, Xichen Wu, Lukas Heumos, Mariana Gonzales Andre, Lennard Halle, Melissa Grant-Peters, Charlotte Rich-Griffin, Hing-Yuen Yeung, Calliope A. Dendrou, Herbert B. Schiller & Fabian J. Theis.\n>\n> _Genome Biol._ 2024 Apr 26. doi: [10.1186/s13059-024-03249-z](https://doi.org/10.1186/s13059-024-03249-z).\n\n
    BibTeX\n\n```bibtex\n@article{curion2024hadge,\n title={hadge: a comprehensive pipeline for donor deconvolution in single-cell studies},\n author={Curion, Fabiola and Wu, Xichen and Heumos, Lukas and Andr{\\'e}, Mylene Mariana Gonzales and Halle, Lennard and Ozols, Matiss and Grant-Peters, Melissa and Rich-Griffin, Charlotte and Yeung, Hing-Yuen and Dendrou, Calliope A and others},\n journal={Genome Biology},\n volume={25},\n number={1},\n pages={109},\n year={2024},\n publisher={Springer}\n}\n\n```\n\n
    \n\nAn extensive list of references for the tools used by the pipeline can be found in the [`CITATIONS.md`](CITATIONS.md) file.\n\nYou can cite the `nf-core` publication as follows:\n\n> **The nf-core framework for community-curated bioinformatics pipelines.**\n>\n> Philip Ewels, Alexander Peltzer, Sven Fillinger, Harshil Patel, Johannes Alneberg, Andreas Wilm, Maxime Ulysse Garcia, Paolo Di Tommaso & Sven Nahnsen.\n>\n> _Nat Biotechnol._ 2020 Feb 13. doi: [10.1038/s41587-020-0439-x](https://dx.doi.org/10.1038/s41587-020-0439-x).\n", "hasPart": [ { "@id": "main.nf" @@ -99,7 +99,7 @@ }, "mentions": [ { - "@id": "#1d18b14c-d918-49cb-b91e-91fc45a4219e" + "@id": "#3c4b4c5e-a5c9-4173-bc2a-7f712cb38689" } ], "name": "nf-core/hadge" @@ -112,7 +112,7 @@ }, "conformsTo": [ { - "@id": "https://w3id.org/ro/crate/1.1" + "@id": "https://w3id.org/ro/crate/1.2" }, { "@id": "https://w3id.org/workflowhub/workflow-ro-crate/1.0" @@ -126,8 +126,13 @@ "SoftwareSourceCode", "ComputationalWorkflow" ], + "contributor": [ + { + "@id": "https://orcid.org/0000-0003-2502-8803" + } + ], "dateCreated": "", - "dateModified": "2026-02-15T10:55:13Z", + "dateModified": "2026-04-30T13:33:34Z", "dct:conformsTo": "https://bioschemas.org/profiles/ComputationalWorkflow/1.0-RELEASE/", "keywords": [ "nf-core", @@ -174,14 +179,14 @@ "url": { "@id": "https://www.nextflow.io/" }, - "version": "!>=25.04.0" + "version": "!>=25.10.4" }, { - "@id": "#1d18b14c-d918-49cb-b91e-91fc45a4219e", + "@id": "#3c4b4c5e-a5c9-4173-bc2a-7f712cb38689", "@type": "TestSuite", "instance": [ { - "@id": "#2943e3c5-d2b0-4ca5-9d8a-5e951374a437" + "@id": "#27279eed-019d-4b26-822b-99b1c1009922" } ], "mainEntity": { @@ -190,7 +195,7 @@ "name": "Test suite for nf-core/hadge" }, { - "@id": "#2943e3c5-d2b0-4ca5-9d8a-5e951374a437", + "@id": "#27279eed-019d-4b26-822b-99b1c1009922", "@type": "TestInstance", "name": "GitHub Actions workflow for testing nf-core/hadge", "resource": "repos/nf-core/hadge/actions/workflows/nf-test.yml", @@ -317,6 +322,11 @@ "@type": "Organization", "name": "nf-core", "url": "https://nf-co.re/" + }, + { + "@id": "https://orcid.org/0000-0003-2502-8803", + "@type": "Person", + "name": "Fabiola Curion" } ] -} \ No newline at end of file +} diff --git a/subworkflows/local/genetic_demultiplexing/main.nf b/subworkflows/local/genetic_demultiplexing/main.nf index 0b4e6d2d..059abe60 100644 --- a/subworkflows/local/genetic_demultiplexing/main.nf +++ b/subworkflows/local/genetic_demultiplexing/main.nf @@ -131,7 +131,7 @@ workflow GENETIC_DEMULTIPLEXING { .join(ch_demuxlet, remainder: true) .join(ch_freemuxlet, remainder: true) .join(ch_souporcell, remainder: true) - .map { tuple -> tuple.collect { it == null ? [] : it } } + .map { tuple -> tuple.collect { item -> item == null ? [] : item } } GENE_SUMMARY(ch_summary) diff --git a/subworkflows/local/hash_demultiplexing/main.nf b/subworkflows/local/hash_demultiplexing/main.nf index 06b2c351..fd1827b7 100644 --- a/subworkflows/local/hash_demultiplexing/main.nf +++ b/subworkflows/local/hash_demultiplexing/main.nf @@ -48,16 +48,6 @@ workflow HASH_DEMULTIPLEXING { PREPROCESSING_FOR_HTODEMUX_MULTISEQ.out.seurat_object.map { meta, seurat_object -> [meta, seurat_object, "HTO"] } ) - ch_assignments = HTODEMUX.out.assignment - .map { meta, assignment -> - [meta, [result: assignment, method: 'htodemux_assignment']] - } - - ch_classifications = HTODEMUX.out.classification - .map { meta, classification -> - [meta, [result: classification, method: 'htodemux_classification']] - } - ch_htodemux_assignments = ch_htodemux_assignments.mix(HTODEMUX.out.assignment) ch_htodemux_classifications = ch_htodemux_classifications.mix(HTODEMUX.out.classification) @@ -111,8 +101,8 @@ workflow HASH_DEMULTIPLEXING { ch_gmmdemux_input = ch_samplesheet.map { meta, _rna, hto -> [ meta, hto, - params.gmmdemux_hto_names ? params.gmmdemux_hto_names : meta.hto_names, - params.gmmdemux_estimated_n_cells ? gmmdemux_estimated_n_cells : [], + params.gmmdemux_hto_names ?: meta.hto_names, + params.gmmdemux_estimated_n_cells ?: [], ] } @@ -120,8 +110,8 @@ workflow HASH_DEMULTIPLEXING { ch_gmmdemux_input, params.gmmdemux_type_report, params.gmmdemux_summary_report, - params.gmmdemux_skip ? params.gmmdemux_skip : [], - params.gmmdemux_examine ? params.gmmdemux_examine : [] + params.gmmdemux_skip ?: [], + params.gmmdemux_examine ?: [] ) ch_versions = ch_versions.mix(GMMDEMUX.out.versions) @@ -150,7 +140,7 @@ workflow HASH_DEMULTIPLEXING { ch_samplesheet.map {meta, _rna, hto -> [ meta, hto, - params.hashsolo_cell_hashing_columns ? params.hashsolo_cell_hashing_columns : [] + params.hashsolo_cell_hashing_columns ?: [] ] } ) @@ -159,7 +149,7 @@ workflow HASH_DEMULTIPLEXING { ch_versions = ch_versions.mix(HASHSOLO.out.versions) } - ch_summary = ch_samplesheet.map { meta, rna, hto -> [meta,hto] } + ch_summary = ch_samplesheet.map { meta, _rna, hto -> [meta,hto] } .join(ch_htodemux_assignments, remainder: true) .join(ch_htodemux_classifications, remainder: true) .join(ch_multiseq, remainder: true) @@ -170,7 +160,7 @@ workflow HASH_DEMULTIPLEXING { .join(ch_hasheddrops_results, remainder: true) .join(ch_hasheddrops_id_to_hash, remainder: true) .join(ch_hashsolo, remainder: true) - .map { tuple -> tuple.collect { it == null ? [] : it } } + .map { tuple -> tuple.collect { item -> item == null ? [] : item } } // Empty inputs solved as recommended here: // https://nf-co.re/docs/guidelines/components/modules#optional-inputs diff --git a/subworkflows/local/utils_nfcore_hadge_pipeline/main.nf b/subworkflows/local/utils_nfcore_hadge_pipeline/main.nf index e1b47db2..375d7bce 100644 --- a/subworkflows/local/utils_nfcore_hadge_pipeline/main.nf +++ b/subworkflows/local/utils_nfcore_hadge_pipeline/main.nf @@ -14,7 +14,6 @@ include { samplesheetToList } from 'plugin/nf-schema' include { paramsHelp } from 'plugin/nf-schema' include { completionEmail } from '../../nf-core/utils_nfcore_pipeline' include { completionSummary } from '../../nf-core/utils_nfcore_pipeline' -include { imNotification } from '../../nf-core/utils_nfcore_pipeline' include { UTILS_NFCORE_PIPELINE } from '../../nf-core/utils_nfcore_pipeline' include { UTILS_NEXTFLOW_PIPELINE } from '../../nf-core/utils_nextflow_pipeline' @@ -31,7 +30,6 @@ workflow PIPELINE_INITIALISATION { monochrome_logs // boolean: Do not use coloured log outputs nextflow_cli_args // array: List of positional nextflow CLI args outdir // string: The output directory where the results will be saved - input // string: Path to input samplesheet help // boolean: Display help message and exit help_full // boolean: Show the full help message show_hidden // boolean: Show hidden parameters in the help message @@ -53,6 +51,9 @@ workflow PIPELINE_INITIALISATION { // // Validate parameters and generate parameter summary to stdout // + + def before_text = "" + def after_text = "" before_text = """ -\033[2m----------------------------------------------------\033[0m- \033[0;32m,--.\033[0;30m/\033[0;32m,-.\033[0m @@ -68,8 +69,12 @@ workflow PIPELINE_INITIALISATION { https://doi.org/10.1038/s41587-020-0439-x * Software dependencies - https://github.com/nf-core/hadge/blob/master/CITATIONS.md + https://github.com/nf-core/hadge/blob/main/CITATIONS.md """ + if (monochrome_logs) { + before_text = before_text.replaceAll(/\033\[[0-9;]*m/, '') + } + command = "nextflow run ${workflow.manifest.name} -profile --input samplesheet.csv --outdir " UTILS_NFSCHEMA_PLUGIN ( @@ -124,7 +129,6 @@ workflow PIPELINE_COMPLETION { plaintext_email // boolean: Send plain-text email instead of HTML outdir // path: Path to output directory where results will be published monochrome_logs // boolean: Disable ANSI colour codes in log output - hook_url // string: hook URL for notifications multiqc_report // string: Path to MultiQC report main: @@ -148,13 +152,11 @@ workflow PIPELINE_COMPLETION { } completionSummary(monochrome_logs) - if (hook_url) { - imNotification(summary_params, hook_url) - } + } workflow.onError { - log.error("Pipeline failed. Please refer to troubleshooting docs: https://nf-co.re/docs/usage/troubleshooting") + log.error "Pipeline failed. Please refer to troubleshooting docs for common issues: https://nf-co.re/docs/running/troubleshooting" } } @@ -214,8 +216,8 @@ def validateInputParameters() { // def validateHtoNames(Map meta){ - if(meta.hto_names.split(",").any { it.contains('_') }){ - def bad = meta.hto_names.split(",").findAll { it.contains('_') }.join(', ') + if(meta.hto_names.split(",").any { name -> name.contains('_') }){ + def bad = meta.hto_names.split(",").findAll { name -> name.contains('_') }.join(', ') throw new IllegalArgumentException( "Running hadge with the methods htodemux or multiseq does not allow to use underscores ('_') in HTO names. Both tools require a SeuratObject as input, which will replace '_' with '-' leading to ambiguous or misleading assignment summaries. Please remove underscores ('_') from: ${bad}" ) diff --git a/subworkflows/nf-core/utils_nfcore_pipeline/main.nf b/subworkflows/nf-core/utils_nfcore_pipeline/main.nf index 2f30e9a4..afca5439 100644 --- a/subworkflows/nf-core/utils_nfcore_pipeline/main.nf +++ b/subworkflows/nf-core/utils_nfcore_pipeline/main.nf @@ -17,7 +17,7 @@ workflow UTILS_NFCORE_PIPELINE { checkProfileProvided(nextflow_cli_args) emit: - valid_config + valid_config = valid_config } /* @@ -353,67 +353,3 @@ def completionSummary(monochrome_logs=true) { log.info("-${colors.purple}[${workflow.manifest.name}]${colors.red} Pipeline completed with errors${colors.reset}-") } } - -// -// Construct and send a notification to a web server as JSON e.g. Microsoft Teams and Slack -// -def imNotification(summary_params, hook_url) { - def summary = [:] - summary_params - .keySet() - .sort() - .each { group -> - summary << summary_params[group] - } - - def misc_fields = [:] - misc_fields['start'] = workflow.start - misc_fields['complete'] = workflow.complete - misc_fields['scriptfile'] = workflow.scriptFile - misc_fields['scriptid'] = workflow.scriptId - if (workflow.repository) { - misc_fields['repository'] = workflow.repository - } - if (workflow.commitId) { - misc_fields['commitid'] = workflow.commitId - } - if (workflow.revision) { - misc_fields['revision'] = workflow.revision - } - misc_fields['nxf_version'] = workflow.nextflow.version - misc_fields['nxf_build'] = workflow.nextflow.build - misc_fields['nxf_timestamp'] = workflow.nextflow.timestamp - - def msg_fields = [:] - msg_fields['version'] = getWorkflowVersion() - msg_fields['runName'] = workflow.runName - msg_fields['success'] = workflow.success - msg_fields['dateComplete'] = workflow.complete - msg_fields['duration'] = workflow.duration - msg_fields['exitStatus'] = workflow.exitStatus - msg_fields['errorMessage'] = (workflow.errorMessage ?: 'None') - msg_fields['errorReport'] = (workflow.errorReport ?: 'None') - msg_fields['commandLine'] = workflow.commandLine.replaceFirst(/ +--hook_url +[^ ]+/, "") - msg_fields['projectDir'] = workflow.projectDir - msg_fields['summary'] = summary << misc_fields - - // Render the JSON template - def engine = new groovy.text.GStringTemplateEngine() - // Different JSON depending on the service provider - // Defaults to "Adaptive Cards" (https://adaptivecards.io), except Slack which has its own format - def json_path = hook_url.contains("hooks.slack.com") ? "slackreport.json" : "adaptivecard.json" - def hf = new File("${workflow.projectDir}/assets/${json_path}") - def json_template = engine.createTemplate(hf).make(msg_fields) - def json_message = json_template.toString() - - // POST - def post = new URL(hook_url).openConnection() - post.setRequestMethod("POST") - post.setDoOutput(true) - post.setRequestProperty("Content-Type", "application/json") - post.getOutputStream().write(json_message.getBytes("UTF-8")) - def postRC = post.getResponseCode() - if (!postRC.equals(200)) { - log.warn(post.getErrorStream().getText()) - } -} diff --git a/subworkflows/nf-core/utils_nfcore_pipeline/tests/main.nf.test b/subworkflows/nf-core/utils_nfcore_pipeline/tests/main.nf.test new file mode 100644 index 00000000..8940d32d --- /dev/null +++ b/subworkflows/nf-core/utils_nfcore_pipeline/tests/main.nf.test @@ -0,0 +1,29 @@ +nextflow_workflow { + + name "Test Workflow UTILS_NFCORE_PIPELINE" + script "../main.nf" + config "subworkflows/nf-core/utils_nfcore_pipeline/tests/nextflow.config" + workflow "UTILS_NFCORE_PIPELINE" + tag "subworkflows" + tag "subworkflows_nfcore" + tag "utils_nfcore_pipeline" + tag "subworkflows/utils_nfcore_pipeline" + + test("Should run without failures") { + + when { + workflow { + """ + input[0] = [] + """ + } + } + + then { + assertAll( + { assert workflow.success }, + { assert snapshot(workflow.out).match() } + ) + } + } +} diff --git a/subworkflows/nf-core/utils_nfcore_pipeline/tests/main.nf.test.snap b/subworkflows/nf-core/utils_nfcore_pipeline/tests/main.nf.test.snap new file mode 100644 index 00000000..859d1030 --- /dev/null +++ b/subworkflows/nf-core/utils_nfcore_pipeline/tests/main.nf.test.snap @@ -0,0 +1,19 @@ +{ + "Should run without failures": { + "content": [ + { + "0": [ + true + ], + "valid_config": [ + true + ] + } + ], + "meta": { + "nf-test": "0.8.4", + "nextflow": "23.10.1" + }, + "timestamp": "2024-02-28T12:03:25.726491" + } +} \ No newline at end of file diff --git a/subworkflows/nf-core/utils_nfschema_plugin/main.nf b/subworkflows/nf-core/utils_nfschema_plugin/main.nf index ee4738c8..1df8b76f 100644 --- a/subworkflows/nf-core/utils_nfschema_plugin/main.nf +++ b/subworkflows/nf-core/utils_nfschema_plugin/main.nf @@ -38,7 +38,7 @@ workflow UTILS_NFSCHEMA_PLUGIN { } log.info paramsHelp( help_options, - params.help instanceof String ? params.help : "", + (params.help instanceof String && params.help != "true") ? params.help : "", ) exit 0 } @@ -71,4 +71,3 @@ workflow UTILS_NFSCHEMA_PLUGIN { emit: dummy_emit = true } - diff --git a/subworkflows/nf-core/utils_nfschema_plugin/tests/nextflow.config b/subworkflows/nf-core/utils_nfschema_plugin/tests/nextflow.config index 8d8c7371..f6537cc3 100644 --- a/subworkflows/nf-core/utils_nfschema_plugin/tests/nextflow.config +++ b/subworkflows/nf-core/utils_nfschema_plugin/tests/nextflow.config @@ -1,5 +1,5 @@ plugins { - id "nf-schema@2.5.1" + id "nf-schema@2.6.1" } validation { diff --git a/tests/default.nf.test b/tests/default.nf.test index 14da0d05..117744ef 100644 --- a/tests/default.nf.test +++ b/tests/default.nf.test @@ -22,7 +22,7 @@ nextflow_pipeline { def vcf_files = getAllFilesFromDir(params.outdir, include: ['**/*.vcf{,.gz}'], ignore: ['genetic/souporcell/*/*/cluster_genotypes.vcf']) assertAll( - { assert workflow.success}, + { assert workflow.success }, { assert snapshot( // pipeline versions.yml file for multiqc from which Nextflow version is removed because we test pipelines on multiple Nextflow versions removeNextflowVersion("$outputDir/pipeline_info/nf_core_hadge_software_mqc_versions.yml"), diff --git a/tests/nextflow.config b/tests/nextflow.config index 3851c0c4..ce8a0d7e 100644 --- a/tests/nextflow.config +++ b/tests/nextflow.config @@ -8,7 +8,7 @@ // Or any resources requirements params { modules_testdata_base_path = 'https://raw.githubusercontent.com/nf-core/test-datasets/modules/data/' - pipelines_testdata_base_path = 'https://raw.githubusercontent.com/nf-core/test-datasets/refs/heads/hadge' + pipelines_testdata_base_path = 'https://raw.githubusercontent.com/nf-core/test-datasets/refs/heads/hadge/' } aws.client.anonymous = true // fixes S3 access issues on self-hosted runners diff --git a/workflows/hadge.nf b/workflows/hadge.nf index ebed87c1..f6b224d5 100644 --- a/workflows/hadge.nf +++ b/workflows/hadge.nf @@ -30,6 +30,10 @@ workflow HADGE { take: ch_samplesheet // channel: samplesheet read in from --input fasta // file: /path/to/genome.fasta + multiqc_config + multiqc_logo + multiqc_methods_description + outdir main: @@ -81,7 +85,7 @@ workflow HADGE { } // create channels for deconvolution tools - ch_genetic = ch_preprocessed.map { meta, rna, _hto, bam, barcodes, vcf -> [meta, bam, barcodes, vcf] } + ch_genetic = ch_preprocessed.map { meta, _rna, _hto, bam, barcodes, vcf -> [meta, bam, barcodes, vcf] } ch_hashing = ch_preprocessed.map { meta, rna, hto, _bam, _barcodes, _vcf -> [meta, rna, hto] } ch_create_anndata_mudata = ch_preprocessed.map { meta, rna, hto, _bam, _barcodes, _vcf -> [meta, rna, hto] } @@ -191,7 +195,7 @@ workflow HADGE { CREATE_ANNDATA_MUDATA( ch_create_anndata_mudata.map { tuple -> // hto can be null in genetic mode - if (params.mode == 'genetic'){ tuple.collect { it == null ? [] : it } } + if (params.mode == 'genetic'){ tuple.collect { item -> item == null ? [] : item } } else{ tuple } } ) @@ -270,57 +274,41 @@ workflow HADGE { "${process}:\n${tool_versions.join('\n')}" } - softwareVersionsToYAML(ch_versions.mix(topic_versions.versions_file)) + def ch_collated_versions = softwareVersionsToYAML(ch_versions.mix(topic_versions.versions_file)) .mix(topic_versions_string) .collectFile( - storeDir: "${params.outdir}/pipeline_info", - name: 'nf_core_' + 'hadge_software_' + 'mqc_' + 'versions.yml', + storeDir: "${outdir}/pipeline_info", + name: 'nf_core_' + 'hadge_software_' + 'mqc_' + 'versions.yml', sort: true, - newLine: true, + newLine: true ) - .set { ch_collated_versions } // // MODULE: MultiQC // - ch_multiqc_config = channel.fromPath( - "$projectDir/assets/multiqc_config.yml", checkIfExists: true) - ch_multiqc_custom_config = params.multiqc_config ? - channel.fromPath(params.multiqc_config, checkIfExists: true) : - channel.empty() - ch_multiqc_logo = params.multiqc_logo ? - channel.fromPath(params.multiqc_logo, checkIfExists: true) : - channel.empty() - - summary_params = paramsSummaryMap( - workflow, parameters_schema: "nextflow_schema.json") - ch_workflow_summary = channel.value(paramsSummaryMultiqc(summary_params)) - ch_multiqc_files = ch_multiqc_files.mix( - ch_workflow_summary.collectFile(name: 'workflow_summary_mqc.yaml')) - ch_multiqc_custom_methods_description = params.multiqc_methods_description ? - file(params.multiqc_methods_description, checkIfExists: true) : - file("$projectDir/assets/methods_description_template.yml", checkIfExists: true) - ch_methods_description = channel.value( - methodsDescriptionText(ch_multiqc_custom_methods_description)) - ch_multiqc_files = ch_multiqc_files.mix(ch_collated_versions) - ch_multiqc_files = ch_multiqc_files.mix( - ch_methods_description.collectFile( - name: 'methods_description_mqc.yaml', - sort: true, - ) - ) - + def ch_summary_params = paramsSummaryMap(workflow, parameters_schema: "nextflow_schema.json") + def ch_workflow_summary = channel.value(paramsSummaryMultiqc(ch_summary_params)) + ch_multiqc_files = ch_multiqc_files.mix(ch_workflow_summary.collectFile(name: 'workflow_summary_mqc.yaml')) + def ch_multiqc_custom_methods_description = multiqc_methods_description + ? file(multiqc_methods_description, checkIfExists: true) + : file("${projectDir}/assets/methods_description_template.yml", checkIfExists: true) + def ch_methods_description = channel.value(methodsDescriptionText(ch_multiqc_custom_methods_description)) + ch_multiqc_files = ch_multiqc_files.mix(ch_methods_description.collectFile(name: 'methods_description_mqc.yaml', sort: true)) MULTIQC( - ch_multiqc_files.collect(), - ch_multiqc_config.toList(), - ch_multiqc_custom_config.toList(), - ch_multiqc_logo.toList(), - [], - [], + ch_multiqc_files.flatten().collect().map { files -> + [ + [id: 'hadge'], + files, + multiqc_config + ? file(multiqc_config, checkIfExists: true) + : file("${projectDir}/assets/multiqc_config.yml", checkIfExists: true), + multiqc_logo ? file(multiqc_logo, checkIfExists: true) : [], + [], + [], + ] + } ) - - emit: - multiqc_report = MULTIQC.out.report.toList() // channel: /path/to/multiqc_report.html + emit:multiqc_report = MULTIQC.out.report.map { _meta, report -> [report] }.toList() // channel: /path/to/multiqc_report.html versions = ch_versions // channel: [ path(versions.yml) ] } From 8ce9cda7af6ba1dacca387c45ff311501b658929 Mon Sep 17 00:00:00 2001 From: Nico Trummer Date: Tue, 30 Jun 2026 10:01:23 +0200 Subject: [PATCH 44/74] Update modules and use topic channels for version capture where applicable (#110) * Update nf-core modules and fix bam_qc for new samtools API. Refresh installed modules from nf-core/modules, adapt BAM_QC to the updated samtools view/sort inputs, and regenerate module patch files. Drop obsolete hasheddrops and multiseqdemux patches that no longer differ from upstream. * Drop manual version mixing for nf-core topic outputs. Rely on the versions topic collected before MultiQC instead of passing samtools, umitools, untar, csvtk, bff, and hasheddrops versions through subworkflows. * Emit local module versions via topic and keep patched nf-core mixing. Local modules publish to the versions topic so subworkflows no longer mix their outputs manually, while patched nf-core modules without topic support continue to use traditional ch_versions plumbing. * Restore Harshil alignment on local module emit statements. Re-align output emit columns after adding topic: versions during the topics migration. * Fix compatibility issues * Simplify hto null branching * Fix hto branch logic while preserving dev hash channel behaviour. Use a side-channel branch so ch_hto stays intact for joins, and mix is_null rows with EXTRACT_HASHES hashes rather than not_null hto paths. * Update nf-test snapshots for topic-based version collection. Refresh pipeline software versions YAML after local modules publish to the versions topic. * Ignore non-deterministic tSNE plots in nf-test snapshots. tSNE htodemux JPEGs vary across CI and local Docker runs; exclude them from stable-path md5 checks. --- modules.json | 44 +- modules/local/create_anndata_mudata/main.nf | 2 +- modules/local/donor_match/main.nf | 2 +- modules/local/dropletutils/mtxconvert/main.nf | 10 +- modules/local/extract_hashes/main.nf | 1 + modules/local/filter_bam/main.nf | 2 +- modules/local/find_variants/main.nf | 2 +- modules/local/gene_summary/main.nf | 2 +- modules/local/hash_summary/main.nf | 2 +- modules/local/htodemux_visualization/main.nf | 2 +- .../main.nf | 2 +- modules/local/subset_gt_donors/main.nf | 2 +- modules/nf-core/bff/main.nf | 6 +- modules/nf-core/bff/meta.yml | 42 +- modules/nf-core/bff/templates/bff.R | 2 - modules/nf-core/bff/tests/main.nf.test.snap | 54 +- .../nf-core/cellsnp/modea/cellsnp-modea.diff | 32 +- modules/nf-core/cellsnp/modea/meta.yml | 104 +- .../nf-core/cellsnp/modea/tests/main.nf.test | 20 +- .../cellsnp/modea/tests/main.nf.test.snap | 95 +- modules/nf-core/csvtk/join/environment.yml | 4 +- modules/nf-core/csvtk/join/main.nf | 16 +- modules/nf-core/csvtk/join/meta.yml | 33 +- modules/nf-core/csvtk/join/tests/main.nf.test | 8 +- .../csvtk/join/tests/main.nf.test.snap | 50 +- modules/nf-core/demuxem/demuxem.diff | 50 +- modules/nf-core/demuxem/environment.yml | 3 + modules/nf-core/demuxem/meta.yml | 74 +- modules/nf-core/demuxem/tests/main.nf.test | 23 +- .../nf-core/demuxem/tests/main.nf.test.snap | 94 +- modules/nf-core/gmmdemux/environment.yml | 5 + modules/nf-core/gmmdemux/gmmdemux.diff | 62 +- modules/nf-core/gmmdemux/meta.yml | 112 +- modules/nf-core/gmmdemux/tests/main.nf.test | 14 +- .../nf-core/gmmdemux/tests/main.nf.test.snap | 14 +- .../nf-core/gmmdemux/tests/nextflow.config | 2 +- modules/nf-core/hasheddrops/hasheddrops.diff | 28 - modules/nf-core/hasheddrops/main.nf | 8 +- modules/nf-core/hasheddrops/meta.yml | 20 +- .../nf-core/hasheddrops/tests/main.nf.test | 95 +- .../hasheddrops/tests/main.nf.test.snap | 164 +-- modules/nf-core/htodemux/main.nf | 7 +- modules/nf-core/htodemux/meta.yml | 18 +- modules/nf-core/htodemux/templates/HTODemux.R | 2 +- modules/nf-core/htodemux/tests/main.nf.test | 14 +- .../nf-core/htodemux/tests/main.nf.test.snap | 102 +- modules/nf-core/multiseqdemux/main.nf | 2 +- .../nf-core/multiseqdemux/multiseqdemux.diff | 19 - modules/nf-core/popscle/demuxlet/meta.yml | 19 +- .../popscle/demuxlet/popscle-demuxlet.diff | 29 +- .../popscle/demuxlet/tests/main.nf.test | 16 +- modules/nf-core/popscle/dscpileup/meta.yml | 42 +- .../popscle/dscpileup/popscle-dscpileup.diff | 19 +- .../popscle/dscpileup/tests/main.nf.test | 2 +- modules/nf-core/popscle/freemuxlet/main.nf | 12 +- modules/nf-core/popscle/freemuxlet/meta.yml | 44 +- .../popscle/freemuxlet/tests/main.nf.test | 26 +- .../nf-core/samtools/index/environment.yml | 6 +- modules/nf-core/samtools/index/main.nf | 33 +- modules/nf-core/samtools/index/meta.yml | 61 +- .../samtools/index/tests/csi.nextflow.config | 1 - .../nf-core/samtools/index/tests/main.nf.test | 29 +- .../samtools/index/tests/main.nf.test.snap | 278 ++-- modules/nf-core/samtools/sort/environment.yml | 6 +- modules/nf-core/samtools/sort/main.nf | 115 +- modules/nf-core/samtools/sort/meta.yml | 76 +- .../nf-core/samtools/sort/tests/main.nf.test | 250 +++- .../samtools/sort/tests/main.nf.test.snap | 409 +++--- .../samtools/sort/tests/nextflow.config | 4 +- .../samtools/sort/tests/nextflow_cram.config | 5 +- .../samtools/sort/tests/nextflow_sam.config | 7 + modules/nf-core/samtools/view/environment.yml | 5 +- modules/nf-core/samtools/view/main.nf | 105 +- modules/nf-core/samtools/view/meta.yml | 110 +- .../nf-core/samtools/view/tests/bam.config | 3 - .../samtools/view/tests/bam_index.config | 3 - .../samtools/view/tests/cram_index.config | 3 - .../nf-core/samtools/view/tests/main.nf.test | 385 ++--- .../samtools/view/tests/main.nf.test.snap | 1272 +++++++++-------- .../samtools/view/tests/nextflow.config | 5 + modules/nf-core/scanpy/hashsolo/main.nf | 2 +- .../scanpy/hashsolo/templates/hashsolo.py | 35 +- modules/nf-core/souporcell/main.nf | 2 +- .../nf-core/umitools/dedup/environment.yml | 12 +- modules/nf-core/umitools/dedup/main.nf | 23 +- modules/nf-core/umitools/dedup/meta.yml | 65 +- .../nf-core/umitools/dedup/tests/main.nf.test | 6 +- .../umitools/dedup/tests/main.nf.test.snap | 54 +- modules/nf-core/untar/main.nf | 13 +- modules/nf-core/untar/meta.yml | 43 +- modules/nf-core/untar/tests/main.nf.test | 20 +- modules/nf-core/untar/tests/main.nf.test.snap | 176 +-- modules/nf-core/vireo/main.nf | 4 +- modules/nf-core/vireo/tests/main.nf.test | 2 +- subworkflows/local/bam_qc/main.nf | 21 +- .../local/genetic_demultiplexing/main.nf | 8 +- .../local/hash_demultiplexing/main.nf | 8 - .../local/utils_nfcore_hadge_pipeline/main.nf | 3 - tests/.nftignore | 1 + tests/default.nf.test.snap | 109 +- tests/test_genetic.nf.test.snap | 87 +- tests/test_hashing.nf.test.snap | 25 +- workflows/hadge.nf | 17 +- 103 files changed, 2897 insertions(+), 2690 deletions(-) delete mode 100644 modules/nf-core/hasheddrops/hasheddrops.diff delete mode 100644 modules/nf-core/multiseqdemux/multiseqdemux.diff create mode 100644 modules/nf-core/samtools/sort/tests/nextflow_sam.config delete mode 100644 modules/nf-core/samtools/view/tests/bam.config delete mode 100644 modules/nf-core/samtools/view/tests/bam_index.config delete mode 100644 modules/nf-core/samtools/view/tests/cram_index.config create mode 100644 modules/nf-core/samtools/view/tests/nextflow.config diff --git a/modules.json b/modules.json index 883a91a6..94436547 100644 --- a/modules.json +++ b/modules.json @@ -7,41 +7,40 @@ "nf-core": { "bff": { "branch": "master", - "git_sha": "4c2012fe04a7850c9e222ca5e81c8220b94027de", + "git_sha": "6d46786420b4d7bc88eba026eb389c0c5535d120", "installed_by": ["modules"] }, "cellsnp/modea": { "branch": "master", - "git_sha": "05954dab2ff481bcb999f24455da29a5828af08d", + "git_sha": "6d46786420b4d7bc88eba026eb389c0c5535d120", "installed_by": ["modules"], "patch": "modules/nf-core/cellsnp/modea/cellsnp-modea.diff" }, "csvtk/join": { "branch": "master", - "git_sha": "41dfa3f7c0ffabb96a6a813fe321c6d1cc5b6e46", + "git_sha": "2f74af306499775ab48db1ed4077d2fa5aecf9c4", "installed_by": ["modules"] }, "demuxem": { "branch": "master", - "git_sha": "05954dab2ff481bcb999f24455da29a5828af08d", + "git_sha": "6d46786420b4d7bc88eba026eb389c0c5535d120", "installed_by": ["modules"], "patch": "modules/nf-core/demuxem/demuxem.diff" }, "gmmdemux": { "branch": "master", - "git_sha": "05954dab2ff481bcb999f24455da29a5828af08d", + "git_sha": "6303e9a20c7060f3f834d7eb147b27799c710dc3", "installed_by": ["modules"], "patch": "modules/nf-core/gmmdemux/gmmdemux.diff" }, "hasheddrops": { "branch": "master", - "git_sha": "0cb99468d3666ac3babf10b270720e1b99319f90", - "installed_by": ["modules"], - "patch": "modules/nf-core/hasheddrops/hasheddrops.diff" + "git_sha": "8e89b068261ea1a0c941c3b89543c7e27e0f7451", + "installed_by": ["modules"] }, "htodemux": { "branch": "master", - "git_sha": "6d8e4eb4f0e2790646d0466d1073db95610883f0", + "git_sha": "0699518f4bfa7ba1b29dc755bc982157385d8aa2", "installed_by": ["modules"] }, "multiqc": { @@ -51,65 +50,64 @@ }, "multiseqdemux": { "branch": "master", - "git_sha": "6d8e4eb4f0e2790646d0466d1073db95610883f0", - "installed_by": ["modules"], - "patch": "modules/nf-core/multiseqdemux/multiseqdemux.diff" + "git_sha": "6d46786420b4d7bc88eba026eb389c0c5535d120", + "installed_by": ["modules"] }, "popscle/demuxlet": { "branch": "master", - "git_sha": "05954dab2ff481bcb999f24455da29a5828af08d", + "git_sha": "6d46786420b4d7bc88eba026eb389c0c5535d120", "installed_by": ["modules"], "patch": "modules/nf-core/popscle/demuxlet/popscle-demuxlet.diff" }, "popscle/dscpileup": { "branch": "master", - "git_sha": "05954dab2ff481bcb999f24455da29a5828af08d", + "git_sha": "6d46786420b4d7bc88eba026eb389c0c5535d120", "installed_by": ["modules"], "patch": "modules/nf-core/popscle/dscpileup/popscle-dscpileup.diff" }, "popscle/freemuxlet": { "branch": "master", - "git_sha": "05954dab2ff481bcb999f24455da29a5828af08d", + "git_sha": "b73338eca19d798eb0ef6ed112ef4d27a30d2871", "installed_by": ["modules"] }, "samtools/index": { "branch": "master", - "git_sha": "d090922b1a4b80ba283186459ababf8e308abcbb", + "git_sha": "6d46786420b4d7bc88eba026eb389c0c5535d120", "installed_by": ["modules"] }, "samtools/sort": { "branch": "master", - "git_sha": "05954dab2ff481bcb999f24455da29a5828af08d", + "git_sha": "6d46786420b4d7bc88eba026eb389c0c5535d120", "installed_by": ["modules"] }, "samtools/view": { "branch": "master", - "git_sha": "d090922b1a4b80ba283186459ababf8e308abcbb", + "git_sha": "6d46786420b4d7bc88eba026eb389c0c5535d120", "installed_by": ["modules"] }, "scanpy/hashsolo": { "branch": "master", - "git_sha": "2b474484e0f4c130392c4d67225e15614732e2a8", + "git_sha": "6d46786420b4d7bc88eba026eb389c0c5535d120", "installed_by": ["modules"] }, "souporcell": { "branch": "master", - "git_sha": "6998ba9049ad0d544c243e2f785f94f8e9f8356b", + "git_sha": "6d46786420b4d7bc88eba026eb389c0c5535d120", "installed_by": ["modules"] }, "umitools/dedup": { "branch": "master", - "git_sha": "05954dab2ff481bcb999f24455da29a5828af08d", + "git_sha": "6d46786420b4d7bc88eba026eb389c0c5535d120", "installed_by": ["modules"] }, "untar": { "branch": "master", - "git_sha": "05954dab2ff481bcb999f24455da29a5828af08d", + "git_sha": "6d46786420b4d7bc88eba026eb389c0c5535d120", "installed_by": ["modules"] }, "vireo": { "branch": "master", - "git_sha": "adfd5ba7915e96ccf41ccc0b4be859c398d0eb95", + "git_sha": "6d46786420b4d7bc88eba026eb389c0c5535d120", "installed_by": ["modules"] } } diff --git a/modules/local/create_anndata_mudata/main.nf b/modules/local/create_anndata_mudata/main.nf index 5b412bc4..64109c3f 100644 --- a/modules/local/create_anndata_mudata/main.nf +++ b/modules/local/create_anndata_mudata/main.nf @@ -20,7 +20,7 @@ process CREATE_ANNDATA_MUDATA { tuple val(meta), path("*_genetic.h5ad") , emit: h5ad_genetic, optional: true tuple val(meta), path("*_hashing.h5ad") , emit: h5ad_hashing, optional: true tuple val(meta), path("*_genetic_and_hashing.h5mu"), emit: h5mu , optional: true - path "versions.yml" , emit: versions + path "versions.yml" , emit: versions, topic: versions when: task.ext.when == null || task.ext.when diff --git a/modules/local/donor_match/main.nf b/modules/local/donor_match/main.nf index f014c13a..ad484f65 100644 --- a/modules/local/donor_match/main.nf +++ b/modules/local/donor_match/main.nf @@ -26,7 +26,7 @@ process DONOR_MATCH { tuple val(meta), path("*/*_vs_*correlation_res.csv") , emit: correlation tuple val(meta), path("*/*_vs_*donor_match.csv") , emit: donor_match tuple val(meta), path("*/*_vs_*concordance_heatmap.png") , emit: concordance_heatmap - path "versions.yml" , emit: versions + path "versions.yml" , emit: versions, topic: versions when: task.ext.when == null || task.ext.when diff --git a/modules/local/dropletutils/mtxconvert/main.nf b/modules/local/dropletutils/mtxconvert/main.nf index 21a3401b..c4fac691 100644 --- a/modules/local/dropletutils/mtxconvert/main.nf +++ b/modules/local/dropletutils/mtxconvert/main.nf @@ -13,8 +13,8 @@ process DROPLETUTILS_MTXCONVERT { output: tuple val(meta), path("*.csv"), emit: csv, optional: true - tuple val(meta), path("*.h5"), emit: h5 - path "versions.yml", emit: versions + tuple val(meta), path("*.h5") , emit: h5 + path "versions.yml" , emit: versions, topic: versions script: prefix = task.ext.prefix ?: "${meta.id}" @@ -27,5 +27,11 @@ process DROPLETUTILS_MTXCONVERT { touch ${prefix}.csv fi touch ${prefix}.h5 + + cat <<-END_VERSIONS > versions.yml + "${task.process}": + r-base: \$(Rscript -e "cat(paste(R.version[['major']], R.version[['minor']], sep='.'))") + bioconductor-dropletutils: \$(Rscript -e "library(DropletUtils); cat(as.character(packageVersion('DropletUtils')))") + END_VERSIONS """ } diff --git a/modules/local/extract_hashes/main.nf b/modules/local/extract_hashes/main.nf index 38b8bce4..c5a49ae2 100644 --- a/modules/local/extract_hashes/main.nf +++ b/modules/local/extract_hashes/main.nf @@ -12,6 +12,7 @@ process EXTRACT_HASHES { output: tuple val(meta), path("*_hashes.txt"), emit: hashes + path "versions.yml" , emit: versions, topic: versions when: task.ext.when == null || task.ext.when diff --git a/modules/local/filter_bam/main.nf b/modules/local/filter_bam/main.nf index 8da90e3e..ba1411dd 100644 --- a/modules/local/filter_bam/main.nf +++ b/modules/local/filter_bam/main.nf @@ -13,7 +13,7 @@ process FILTER_BAM { output: tuple val(meta), path("${prefix}.bam"), emit: bam - path 'versions.yml', emit: versions + path 'versions.yml' , emit: versions, topic: versions script: prefix = task.ext.prefix ?: "${meta.id}" diff --git a/modules/local/find_variants/main.nf b/modules/local/find_variants/main.nf index 134fa4bc..2b32b382 100644 --- a/modules/local/find_variants/main.nf +++ b/modules/local/find_variants/main.nf @@ -20,7 +20,7 @@ process FIND_VARIANTS { tuple val(meta), path("*_donor_specific_variants_upset.png"), emit: donor_specific_variants_upset tuple val(meta), path("*_donor_specific_variants.csv") , emit: donor_specific_variants tuple val(meta), path("*_vireo_variants.csv") , emit: vireo_variants, optional: true - path "versions.yml" , emit: versions + path "versions.yml" , emit: versions, topic: versions when: task.ext.when == null || task.ext.when diff --git a/modules/local/gene_summary/main.nf b/modules/local/gene_summary/main.nf index b7d969ff..529f7358 100644 --- a/modules/local/gene_summary/main.nf +++ b/modules/local/gene_summary/main.nf @@ -20,7 +20,7 @@ process GENE_SUMMARY { tuple val(meta), path("*_genetic_summary_classification.csv") , emit: classification tuple val(meta), path("*_genetic_overview_assignment.csv") , emit: overview_assignment tuple val(meta), path("*_genetic_overview_classification.csv"), emit: overview_classification - path "versions.yml" , emit: versions + path "versions.yml" , emit: versions, topic: versions when: task.ext.when == null || task.ext.when diff --git a/modules/local/hash_summary/main.nf b/modules/local/hash_summary/main.nf index e2d33229..a739afc4 100644 --- a/modules/local/hash_summary/main.nf +++ b/modules/local/hash_summary/main.nf @@ -24,7 +24,7 @@ process HASH_SUMMARY { tuple val(meta), path("*_hashing_summary_classification.csv") , emit: classification tuple val(meta), path("*_hashing_overview_assignment.csv") , emit: overview_assignment tuple val(meta), path("*_hashing_overview_classification.csv"), emit: overview_classification - path "versions.yml" , emit: versions + path "versions.yml" , emit: versions, topic: versions when: task.ext.when == null || task.ext.when diff --git a/modules/local/htodemux_visualization/main.nf b/modules/local/htodemux_visualization/main.nf index 984448ed..d096a957 100644 --- a/modules/local/htodemux_visualization/main.nf +++ b/modules/local/htodemux_visualization/main.nf @@ -17,7 +17,7 @@ process HTODEMUX_VISUALIZATION { tuple val(meta), path("*_tSNE_htodemux.jpeg") , emit: tsne_plot , optional: true tuple val(meta), path("*_heatMap_htodemux.jpeg") , emit: heatmap_plot , optional: true tuple val(meta), path("*_visual_params_htodemux.csv") , emit: params - path "versions.yml" , emit: versions + path "versions.yml" , emit: versions, topic: versions when: task.ext.when == null || task.ext.when diff --git a/modules/local/preprocessing_for_htodemux_multiseq/main.nf b/modules/local/preprocessing_for_htodemux_multiseq/main.nf index b9caa93b..197606ec 100644 --- a/modules/local/preprocessing_for_htodemux_multiseq/main.nf +++ b/modules/local/preprocessing_for_htodemux_multiseq/main.nf @@ -14,7 +14,7 @@ process PREPROCESSING_FOR_HTODEMUX_MULTISEQ { output: tuple val(meta), path("*_preprocessed.rds") , emit: seurat_object tuple val(meta), path("*_params_preprocessing.csv"), emit: params - path "versions.yml" , emit: versions + path "versions.yml" , emit: versions, topic: versions when: task.ext.when == null || task.ext.when diff --git a/modules/local/subset_gt_donors/main.nf b/modules/local/subset_gt_donors/main.nf index 484b8617..14987069 100644 --- a/modules/local/subset_gt_donors/main.nf +++ b/modules/local/subset_gt_donors/main.nf @@ -12,7 +12,7 @@ process SUBSET_GT_DONORS { output: tuple val(meta), path("*_${output_basename}.vcf.gz"), emit: donor_subset_vcf - path "versions.yml" , emit: versions + path "versions.yml" , emit: versions, topic: versions when: task.ext.when == null || task.ext.when diff --git a/modules/nf-core/bff/main.nf b/modules/nf-core/bff/main.nf index 8d86008c..530c597b 100644 --- a/modules/nf-core/bff/main.nf +++ b/modules/nf-core/bff/main.nf @@ -3,8 +3,8 @@ process BFF { label 'process_medium' conda "${moduleDir}/environment.yml" - container "${ workflow.containerEngine == 'singularity' && !task.ext.singularity_pull_docker_container ? - 'oras://community.wave.seqera.io/library/bioconductor-cellhashr_r-seurat:1c94360d8ed188c4': + container "${ workflow.containerEngine in ['singularity', 'apptainer'] && !task.ext.singularity_pull_docker_container ? + 'https://community-cr-prod.seqera.io/docker/registry/v2/blobs/sha256/21/21d3acad5fd1818f00b1c267fcc8c8be88c930cf31e74fb6b5ce444f96827604/data': 'community.wave.seqera.io/library/bioconductor-cellhashr_r-seurat:25c4bc76749af5ac' }" input: @@ -14,7 +14,7 @@ process BFF { tuple val(meta), path("*_assignment_bff.csv"), emit: assignment tuple val(meta), path("*_metrics_bff.csv") , emit: metrics tuple val(meta), path("*_params_bff.csv") , emit: params - path "versions.yml" , emit: versions + path "versions.yml" , emit: versions_bff, topic: versions when: task.ext.when == null || task.ext.when diff --git a/modules/nf-core/bff/meta.yml b/modules/nf-core/bff/meta.yml index 4396a4b1..5084bafe 100644 --- a/modules/nf-core/bff/meta.yml +++ b/modules/nf-core/bff/meta.yml @@ -1,5 +1,3 @@ ---- -# yaml-language-server: $schema=https://raw.githubusercontent.com/nf-core/modules/master/modules/meta-schema.json name: "bff" description: Generating cell hashing calls from a matrix of count data. keywords: @@ -8,14 +6,20 @@ keywords: - single-cell tools: - "bff": - description: "A toolkit for quality control, analysis, and exploration of single cell RNA sequencing data. 'Seurat' aims to enable users to identify and interpret sources of heterogeneity from single cell transcriptomic measurements, and to integrate diverse types of single cell data. See Satija R, Farrell J, Gennert D, et al (2015) , Macosko E, Basu A, Satija R, et al (2015) , and Butler A and Satija R (2017) for more details." + description: "A toolkit for quality control, analysis, and exploration of single + cell RNA sequencing data. 'Seurat' aims to enable users to identify and interpret + sources of heterogeneity from single cell transcriptomic measurements, and to + integrate diverse types of single cell data. See Satija R, Farrell J, Gennert + D, et al (2015) , Macosko E, Basu A, Satija R, et al (2015) + , and Butler A and Satija R (2017) + for more details." homepage: "https://rdrr.io/github/BimberLab/cellhashR/man/GenerateCellHashingCalls.html" documentation: "https://rdrr.io/github/BimberLab/cellhashR/man/GenerateCellHashingCalls.html" tool_dev_url: "https://github.com/BimberLab/cellhashR" doi: "10.5281/zenodo.6402477" - licence: ["GPL-3"] + licence: + - "GPL-3" identifier: "" - input: - - meta: type: map @@ -27,7 +31,7 @@ input: description: | Directory that contains the HTO matrix in a 10X format. ontologies: - - edam: "http://edamontology.org/data_3917" # count matrix + - edam: "http://edamontology.org/data_3917" - methods: type: string description: | @@ -36,7 +40,6 @@ input: type: string description: | Decides whether the HTO matrix should undergo a preprocessing step ('TRUE') or not ('FALSE'). - output: assignment: - - meta: @@ -44,39 +47,54 @@ output: description: | Groovy Map containing sample information e.g. `[ id:'sample1' ]` + ontologies: [] - "*_assignment_bff.csv": type: file description: Contains the assignment results of demultiplexing. pattern: "assignment_bff.csv" - + ontologies: + - edam: http://edamontology.org/format_3752 metrics: - - meta: type: file description: | Groovy Map containing sample information e.g. `[ id:'sample1' ]` + ontologies: [] - "*_metrics_bff.csv": type: file description: Summary metrics will be written to this file. pattern: "_metrics_bff.csv" - + ontologies: + - edam: http://edamontology.org/format_3752 params: - - meta: - type: map + type: file description: | Groovy Map containing sample information e.g. `[ id:'sample1' ]` + ontologies: [] - "*_params_bff.csv": type: file description: The used parameters to call HTODemux in the R-Script. pattern: "params_htodemux.csv" - + ontologies: + - edam: http://edamontology.org/format_3752 + versions_bff: + - versions.yml: + type: file + description: File containing software versions. + pattern: "versions.yml" + ontologies: + - edam: http://edamontology.org/format_3750 +topics: versions: - versions.yml: type: file description: File containing software versions. pattern: "versions.yml" - + ontologies: + - edam: http://edamontology.org/format_3750 authors: - "@LuisHeinzlmeier" maintainers: diff --git a/modules/nf-core/bff/templates/bff.R b/modules/nf-core/bff/templates/bff.R index 9609f977..1f4d2b0d 100644 --- a/modules/nf-core/bff/templates/bff.R +++ b/modules/nf-core/bff/templates/bff.R @@ -85,8 +85,6 @@ for ( ao in names(args_opt)){ } } -print(opt) - # Set individual variables for backward compatibility and cleaner code hto_matrix <- opt\$hto_matrix methods <- opt\$methods diff --git a/modules/nf-core/bff/tests/main.nf.test.snap b/modules/nf-core/bff/tests/main.nf.test.snap index 97a32d09..d4c336be 100644 --- a/modules/nf-core/bff/tests/main.nf.test.snap +++ b/modules/nf-core/bff/tests/main.nf.test.snap @@ -53,16 +53,16 @@ "test_params_bff.csv:md5,a8885b3d8aaa64b3350137e91856ca3b" ] ], - "versions": [ + "versions_bff": [ "versions.yml:md5,71a50af83aebf56493d64a87f468a6f2" ] } ], + "timestamp": "2026-03-12T11:34:02.383760239", "meta": { - "nf-test": "0.9.2", - "nextflow": "25.04.3" - }, - "timestamp": "2025-08-07T09:36:16.222081" + "nf-test": "0.9.4", + "nextflow": "25.10.4" + } }, "hto_matrix - combined - preprocessing - stub": { "content": [ @@ -118,16 +118,16 @@ "test_params_bff.csv:md5,d41d8cd98f00b204e9800998ecf8427e" ] ], - "versions": [ + "versions_bff": [ "versions.yml:md5,71a50af83aebf56493d64a87f468a6f2" ] } ], + "timestamp": "2026-03-12T11:37:21.721930221", "meta": { - "nf-test": "0.9.2", - "nextflow": "25.04.3" - }, - "timestamp": "2025-06-17T23:03:24.350098" + "nf-test": "0.9.4", + "nextflow": "25.10.4" + } }, "hto_matrix - combined - no preprocessing": { "content": [ @@ -183,16 +183,16 @@ "test_params_bff.csv:md5,a8885b3d8aaa64b3350137e91856ca3b" ] ], - "versions": [ + "versions_bff": [ "versions.yml:md5,71a50af83aebf56493d64a87f468a6f2" ] } ], + "timestamp": "2026-03-12T11:36:35.514874397", "meta": { - "nf-test": "0.9.2", - "nextflow": "25.04.3" - }, - "timestamp": "2025-08-07T09:36:51.751444" + "nf-test": "0.9.4", + "nextflow": "25.10.4" + } }, "hto_matrix - combined - preprocessing - test ext.args": { "content": [ @@ -248,16 +248,16 @@ "test_params_bff.csv:md5,fbc8cbbe0c24be222cb2a0ea6261db99" ] ], - "versions": [ + "versions_bff": [ "versions.yml:md5,71a50af83aebf56493d64a87f468a6f2" ] } ], + "timestamp": "2026-03-12T11:34:57.218260117", "meta": { - "nf-test": "0.9.2", - "nextflow": "25.04.3" - }, - "timestamp": "2025-08-07T09:48:46.370263" + "nf-test": "0.9.4", + "nextflow": "25.10.4" + } }, "hto_matrix - raw - preprocessing": { "content": [ @@ -313,16 +313,16 @@ "test_params_bff.csv:md5,1e3c54b1dc9786e9e21c7baf2247ddef" ] ], - "versions": [ + "versions_bff": [ "versions.yml:md5,71a50af83aebf56493d64a87f468a6f2" ] } ], + "timestamp": "2026-03-12T11:35:48.222040827", "meta": { - "nf-test": "0.9.2", - "nextflow": "25.04.3" - }, - "timestamp": "2025-08-07T09:36:35.771859" + "nf-test": "0.9.4", + "nextflow": "25.10.4" + } }, "hto_matrix - combined - preprocessing - with ext.args": { "content": [ @@ -383,10 +383,10 @@ ] } ], + "timestamp": "2025-08-07T09:47:18.772948", "meta": { "nf-test": "0.9.2", "nextflow": "25.04.3" - }, - "timestamp": "2025-08-07T09:47:18.772948" + } } } \ No newline at end of file diff --git a/modules/nf-core/cellsnp/modea/cellsnp-modea.diff b/modules/nf-core/cellsnp/modea/cellsnp-modea.diff index b3342128..0377546a 100644 --- a/modules/nf-core/cellsnp/modea/cellsnp-modea.diff +++ b/modules/nf-core/cellsnp/modea/cellsnp-modea.diff @@ -1,17 +1,19 @@ Changes in component 'nf-core/cellsnp/modea' +'modules/nf-core/cellsnp/modea/environment.yml' is unchanged +'modules/nf-core/cellsnp/modea/meta.yml' is unchanged Changes in 'cellsnp/modea/main.nf': --- modules/nf-core/cellsnp/modea/main.nf +++ modules/nf-core/cellsnp/modea/main.nf -@@ -1,42 +1,42 @@ +@@ -1,58 +1,68 @@ process CELLSNP_MODEA { - tag "$meta.id" + tag "${meta.id}" label 'process_medium' conda "${moduleDir}/environment.yml" -- container "${ workflow.containerEngine == 'singularity' && !task.ext.singularity_pull_docker_container ? +- container "${ workflow.containerEngine in ['singularity', 'apptainer'] && !task.ext.singularity_pull_docker_container ? - 'https://depot.galaxyproject.org/singularity/cellsnp-lite:1.2.3--h6141fd1_2' : -- 'biocontainers/cellsnp-lite:1.2.3--h6141fd1_2' }" +- 'quay.io/biocontainers/cellsnp-lite:1.2.3--h6141fd1_2' }" + container "${workflow.containerEngine == 'singularity' && !task.ext.singularity_pull_docker_container + ? 'https://depot.galaxyproject.org/singularity/cellsnp-lite:1.2.3--h6141fd1_2' + : 'biocontainers/cellsnp-lite:1.2.3--h6141fd1_2'}" @@ -26,7 +28,7 @@ Changes in 'cellsnp/modea/main.nf': - tuple val(meta), path('*.tag.AD.mtx') , emit: allele_depth - tuple val(meta), path('*.tag.DP.mtx') , emit: depth_coverage - tuple val(meta), path('*.tag.OTH.mtx') , emit: depth_other -- path 'versions.yml' , emit: versions +- tuple val("${task.process}"), val("cellsnp"), eval("cellsnp-lite --v | cut -f2 -d ' '"), emit: versions_cellsnp, topic: versions + tuple val(meta), path('*.base.vcf.gz'), emit: base + tuple val(meta), path('*.cells.vcf.gz'), emit: cell, optional: true + tuple val(meta), path('*.samples.tsv'), emit: sample @@ -65,7 +67,16 @@ Changes in 'cellsnp/modea/main.nf': mv cellSNP.cells.vcf.gz ${prefix}.cells.vcf.gz fi mv cellSNP.tag.AD.mtx ${prefix}.tag.AD.mtx -@@ -53,7 +53,7 @@ + mv cellSNP.tag.DP.mtx ${prefix}.tag.DP.mtx + mv cellSNP.tag.OTH.mtx ${prefix}.tag.OTH.mtx + mv cellSNP.samples.tsv ${prefix}.samples.tsv ++ ++ cat <<-END_VERSIONS > versions.yml ++ "${task.process}": ++ cellsnp: \$(cellsnp-lite --v | awk '{print \$2}') ++ END_VERSIONS + """ + stub: def prefix = task.ext.prefix ?: "${meta.id}" """ @@ -74,9 +85,16 @@ Changes in 'cellsnp/modea/main.nf': echo "" | gzip > ${prefix}.base.vcf.gz touch ${prefix}.samples.tsv touch ${prefix}.tag.AD.mtx + touch ${prefix}.tag.DP.mtx + touch ${prefix}.tag.OTH.mtx ++ ++ cat <<-END_VERSIONS > versions.yml ++ "${task.process}": ++ cellsnp: \$(cellsnp-lite --v | awk '{print \$2}') ++ END_VERSIONS + """ + } -'modules/nf-core/cellsnp/modea/environment.yml' is unchanged -'modules/nf-core/cellsnp/modea/meta.yml' is unchanged 'modules/nf-core/cellsnp/modea/tests/main.nf.test.snap' is unchanged 'modules/nf-core/cellsnp/modea/tests/main.nf.test' is unchanged ************************************************************ diff --git a/modules/nf-core/cellsnp/modea/meta.yml b/modules/nf-core/cellsnp/modea/meta.yml index 1b7fac56..db12ddc4 100644 --- a/modules/nf-core/cellsnp/modea/meta.yml +++ b/modules/nf-core/cellsnp/modea/meta.yml @@ -1,9 +1,8 @@ -# yaml-language-server: $schema=https://raw.githubusercontent.com/nf-core/modules/master/modules/meta-schema.json name: "cellsnp_modea" -description: Cellsnp-lite is a C/C++ tool for efficient genotyping bi-allelic SNPs - on single cells. You can use the mode A of cellsnp-lite after read alignment to - obtain the snp x cell pileup UMI or read count matrices for each alleles of given - or detected SNPs for droplet based single cell data. +description: Cellsnp-lite is a C/C++ tool for efficient genotyping bi-allelic + SNPs on single cells. You can use the mode A of cellsnp-lite after read + alignment to obtain the snp x cell pileup UMI or read count matrices for each + alleles of given or detected SNPs for droplet based single cell data. keywords: - genotyping - single cell @@ -16,9 +15,9 @@ tools: documentation: "https://cellsnp-lite.readthedocs.io" tool_dev_url: "https://github.com/single-cell-genetics/cellsnp-lite" doi: "10.1093/bioinformatics/btab358" - licence: ["Apache-2.0"] + licence: + - "Apache-2.0" identifier: "" - input: - - meta: type: map @@ -29,43 +28,53 @@ input: type: file description: A single BAM/SAM/CRAM file, e.g., from CellRanger. pattern: "*.{bam,cram,sam}" + ontologies: [] - bai: type: file description: The index of the BAM/CRAM file. pattern: "*.{bai,crai}" + ontologies: [] - region_vcf: type: file - description: A optional vcf file listing all candidate SNPs for genotyping. + description: A optional vcf file listing all candidate SNPs for + genotyping. pattern: "*.{vcf, vcf.gz}" + ontologies: [] - barcode: type: file description: A plain file listing all effective cell barcodes. pattern: "*.tsv" + ontologies: + - edam: http://edamontology.org/format_3475 # TSV output: - - base: - - meta: + base: + - - meta: type: map description: | Groovy Map containing sample information e.g. `[ id:'sample1', single_end:false ]` - "*.base.vcf.gz": type: file - description: A VCF file listing genotyped SNPs and aggregated AD & DP information - (without GT). + description: A VCF file listing genotyped SNPs and aggregated AD & DP + information (without GT). pattern: "*.base.vcf.gz" - - cell: - - meta: + ontologies: + - edam: http://edamontology.org/format_3989 # GZIP format + cell: + - - meta: type: map description: | Groovy Map containing sample information e.g. `[ id:'sample1', single_end:false ]` - "*.cells.vcf.gz": type: file - description: A VCF file listing genotyped SNPs and aggregated AD & DP information - & genotype (GT) information for each cell or sample. + description: A VCF file listing genotyped SNPs and aggregated AD & DP + information & genotype (GT) information for each cell or sample. pattern: "*.cells.vcf.gz" - - sample: - - meta: + ontologies: + - edam: http://edamontology.org/format_3989 # GZIP format + sample: + - - meta: type: map description: | Groovy Map containing sample information @@ -74,44 +83,69 @@ output: type: file description: A TSV file listing cell barcodes or sample IDs. pattern: "*.tsv" - - allele_depth: - - meta: + ontologies: + - edam: http://edamontology.org/format_3475 # TSV + allele_depth: + - - meta: type: map description: | Groovy Map containing sample information e.g. `[ id:'sample1', single_end:false ]` - "*.tag.AD.mtx": type: file - description: A file in “Matrix Market exchange formats”, containing the allele - depths of the alternative (ALT) alleles. + description: A file in “Matrix Market exchange formats”, containing the + allele depths of the alternative (ALT) alleles. pattern: "*.tag.AD.mtx" - - depth_coverage: - - meta: + ontologies: + - edam: http://edamontology.org/format_3916 # MTX + depth_coverage: + - - meta: type: map description: | Groovy Map containing sample information e.g. `[ id:'sample1', single_end:false ]` - "*.tag.DP.mtx": type: file - description: A file in “Matrix Market exchange formats”, containing the sum - of allele depths of the reference and alternative alleles (REF + ALT). + description: A file in “Matrix Market exchange formats”, containing the + sum of allele depths of the reference and alternative alleles (REF + + ALT). pattern: "*.tag.DP.mtx" - - depth_other: - - meta: + ontologies: + - edam: http://edamontology.org/format_3916 # MTX + depth_other: + - - meta: type: map description: | Groovy Map containing sample information e.g. `[ id:'sample1', single_end:false ]` - "*.tag.OTH.mtx": type: file - description: A file in “Matrix Market exchange formats”, containing the sum - of allele depths of all the alleles other than REF and ALT. + description: A file in “Matrix Market exchange formats”, containing the + sum of allele depths of all the alleles other than REF and ALT. pattern: "*.tag.OTH.mtx" - - versions: - - versions.yml: - type: file - description: File containing software versions - pattern: "versions.yml" + ontologies: + - edam: http://edamontology.org/format_3916 # MTX + versions_cellsnp: + - - ${task.process}: + type: string + description: The name of the process + - cellsnp: + type: string + description: The name of the tool + - cellsnp-lite --v | cut -f2 -d ' ': + type: eval + description: The expression to obtain the version of the tool +topics: + versions: + - - ${task.process}: + type: string + description: The name of the process + - cellsnp: + type: string + description: The name of the tool + - cellsnp-lite --v | cut -f2 -d ' ': + type: eval + description: The expression to obtain the version of the tool authors: - "@wxicu" maintainers: diff --git a/modules/nf-core/cellsnp/modea/tests/main.nf.test b/modules/nf-core/cellsnp/modea/tests/main.nf.test index 8f67d265..24f9e344 100644 --- a/modules/nf-core/cellsnp/modea/tests/main.nf.test +++ b/modules/nf-core/cellsnp/modea/tests/main.nf.test @@ -12,7 +12,7 @@ nextflow_process { tag "samtools/index" test("genotyping") { - setup { + setup { run("SAMTOOLS_INDEX") { script "../../../samtools/index/main.nf" process { @@ -27,10 +27,10 @@ nextflow_process { when { process { - """ - - input[0] = SAMTOOLS_INDEX.out.bai.collect{ meta, bai -> bai }.map{ - bai -> [[ id: 'sample1'], + """ + + input[0] = SAMTOOLS_INDEX.out.index.collect{ meta, bai -> bai }.map{ + bai -> [[ id: 'sample1'], file(params.modules_testdata_base_path + '/genomics/homo_sapiens/demultiplexing/chr21.bam', checkIfExists: true), bai, file(params.modules_testdata_base_path + '/genomics/homo_sapiens/demultiplexing/donor_genotype_chr21.vcf', checkIfExists: true), @@ -42,14 +42,14 @@ nextflow_process { then { assertAll( { assert process.success }, - { assert snapshot(process.out.versions).match("versions") }, { assert path(process.out.base.get(0).get(1)).exists() }, { assert snapshot( process.out.sample, process.out.allele_depth, process.out.depth_coverage, - process.out.depth_other).match() - } + process.out.depth_other, + process.out.findAll { key, val -> key.startsWith('versions') } + ).match()} ) } @@ -62,7 +62,7 @@ nextflow_process { when { process { """ - + input[0] = [ [ id:'sample1'], file(params.modules_testdata_base_path + '/genomics/homo_sapiens/demultiplexing/chr21.bam', checkIfExists: true), @@ -77,7 +77,7 @@ nextflow_process { then { assertAll( { assert process.success }, - { assert snapshot(process.out).match("stub")}) + { assert snapshot(sanitizeOutput(process.out)).match()}) } } diff --git a/modules/nf-core/cellsnp/modea/tests/main.nf.test.snap b/modules/nf-core/cellsnp/modea/tests/main.nf.test.snap index 07997e3f..461b59d5 100644 --- a/modules/nf-core/cellsnp/modea/tests/main.nf.test.snap +++ b/modules/nf-core/cellsnp/modea/tests/main.nf.test.snap @@ -1,16 +1,4 @@ { - "versions": { - "content": [ - [ - "versions.yml:md5,965121af3dc48657c2128c404589fa6b" - ] - ], - "meta": { - "nf-test": "0.8.4", - "nextflow": "23.10.1" - }, - "timestamp": "2024-05-08T22:02:32.670061197" - }, "genotyping": { "content": [ [ @@ -44,63 +32,26 @@ }, "sample1.tag.OTH.mtx:md5,1e3429950c59edec58a80a9b4ecda552" ] - ] + ], + { + "versions_cellsnp": [ + [ + "CELLSNP_MODEA", + "cellsnp", + "1.2.3" + ] + ] + } ], + "timestamp": "2026-03-12T13:32:05.51576453", "meta": { - "nf-test": "0.8.4", - "nextflow": "23.10.1" - }, - "timestamp": "2024-05-08T22:02:32.689525045" + "nf-test": "0.9.4", + "nextflow": "25.10.4" + } }, - "stub": { + "genotyping - stub": { "content": [ { - "0": [ - [ - { - "id": "sample1" - }, - "sample1.base.vcf.gz:md5,68b329da9893e34099c7d8ad5cb9c940" - ] - ], - "1": [ - - ], - "2": [ - [ - { - "id": "sample1" - }, - "sample1.samples.tsv:md5,d41d8cd98f00b204e9800998ecf8427e" - ] - ], - "3": [ - [ - { - "id": "sample1" - }, - "sample1.tag.AD.mtx:md5,d41d8cd98f00b204e9800998ecf8427e" - ] - ], - "4": [ - [ - { - "id": "sample1" - }, - "sample1.tag.DP.mtx:md5,d41d8cd98f00b204e9800998ecf8427e" - ] - ], - "5": [ - [ - { - "id": "sample1" - }, - "sample1.tag.OTH.mtx:md5,d41d8cd98f00b204e9800998ecf8427e" - ] - ], - "6": [ - "versions.yml:md5,965121af3dc48657c2128c404589fa6b" - ], "allele_depth": [ [ { @@ -144,15 +95,19 @@ "sample1.samples.tsv:md5,d41d8cd98f00b204e9800998ecf8427e" ] ], - "versions": [ - "versions.yml:md5,965121af3dc48657c2128c404589fa6b" + "versions_cellsnp": [ + [ + "CELLSNP_MODEA", + "cellsnp", + "1.2.3" + ] ] } ], + "timestamp": "2026-03-12T13:32:13.617963529", "meta": { - "nf-test": "0.8.4", - "nextflow": "23.10.1" - }, - "timestamp": "2024-05-08T22:02:39.591564384" + "nf-test": "0.9.4", + "nextflow": "25.10.4" + } } } \ No newline at end of file diff --git a/modules/nf-core/csvtk/join/environment.yml b/modules/nf-core/csvtk/join/environment.yml index 47679f18..69d5f5e8 100644 --- a/modules/nf-core/csvtk/join/environment.yml +++ b/modules/nf-core/csvtk/join/environment.yml @@ -1,8 +1,8 @@ --- # yaml-language-server: $schema=https://raw.githubusercontent.com/nf-core/modules/master/modules/environment-schema.json channels: - - bioconda - conda-forge + - bioconda dependencies: - - bioconda::csvtk=0.31.0 + - conda-forge::csvtk=0.37.0 diff --git a/modules/nf-core/csvtk/join/main.nf b/modules/nf-core/csvtk/join/main.nf index 0bd6b2a5..fe50c10a 100644 --- a/modules/nf-core/csvtk/join/main.nf +++ b/modules/nf-core/csvtk/join/main.nf @@ -4,15 +4,15 @@ process CSVTK_JOIN { conda "${moduleDir}/environment.yml" container "${ workflow.containerEngine == 'singularity' && !task.ext.singularity_pull_docker_container ? - 'https://depot.galaxyproject.org/singularity/csvtk:0.31.0--h9ee0642_0': - 'biocontainers/csvtk:0.31.0--h9ee0642_0' }" + 'https://community-cr-prod.seqera.io/docker/registry/v2/blobs/sha256/91/917edb71b915f07fa2838c20e3c731181d3d315cbf8a9bfead41412d2b4ae062/data': + 'community.wave.seqera.io/library/csvtk:0.37.0--113625988dd3285d' }" input: tuple val(meta), path(csv) output: tuple val(meta), path("${prefix}.${out_extension}"), emit: csv - path "versions.yml" , emit: versions + tuple val("${task.process}"), val('csvtk'), eval("csvtk version | sed -e 's/csvtk v//g'"), emit: versions_csvtk, topic: versions when: task.ext.when == null || task.ext.when @@ -28,11 +28,6 @@ process CSVTK_JOIN { --num-cpus $task.cpus \\ --out-file ${prefix}.${out_extension} \\ $csv - - cat <<-END_VERSIONS > versions.yml - "${task.process}": - csvtk: \$(echo \$( csvtk version | sed -e "s/csvtk v//g" )) - END_VERSIONS """ stub: @@ -40,10 +35,5 @@ process CSVTK_JOIN { out_extension = args.contains('--out-delimiter "\t"') || args.contains('-D "\t"') || args.contains("-D \$'\t'") ? "tsv" : "csv" """ touch ${prefix}.${out_extension} - - cat <<-END_VERSIONS > versions.yml - "${task.process}": - csvtk: \$(echo \$( csvtk version | sed -e "s/csvtk v//g" )) - END_VERSIONS """ } diff --git a/modules/nf-core/csvtk/join/meta.yml b/modules/nf-core/csvtk/join/meta.yml index ca86aca1..1917afd8 100644 --- a/modules/nf-core/csvtk/join/meta.yml +++ b/modules/nf-core/csvtk/join/meta.yml @@ -1,6 +1,6 @@ name: csvtk_join -description: Join two or more CSV (or TSV) tables by selected fields into a single - table +description: Join two or more CSV (or TSV) tables by selected fields into a + single table keywords: - join - tsv @@ -11,7 +11,8 @@ tools: homepage: http://bioinf.shenwei.me/csvtk documentation: http://bioinf.shenwei.me/csvtk tool_dev_url: https://github.com/shenwei356/csvtk - licence: ["MIT"] + licence: + - "MIT" identifier: "" input: - - meta: @@ -40,13 +41,27 @@ output: ontologies: - edam: http://edamontology.org/format_3752 # CSV - edam: http://edamontology.org/format_3475 # TSV + versions_csvtk: + - - ${task.process}: + type: string + description: The name of the process + - csvtk: + type: string + description: The name of the tool + - csvtk version | sed -e 's/csvtk v//g': + type: eval + description: The expression to obtain the version of the tool +topics: versions: - - versions.yml: - type: file - description: File containing software versions - pattern: "version.yml" - ontologies: - - edam: http://edamontology.org/format_3750 # YAML + - - ${task.process}: + type: string + description: The name of the process + - csvtk: + type: string + description: The name of the tool + - csvtk version | sed -e 's/csvtk v//g': + type: eval + description: The expression to obtain the version of the tool authors: - "@anoronh4" maintainers: diff --git a/modules/nf-core/csvtk/join/tests/main.nf.test b/modules/nf-core/csvtk/join/tests/main.nf.test index 3cf178c4..ce7a7b51 100644 --- a/modules/nf-core/csvtk/join/tests/main.nf.test +++ b/modules/nf-core/csvtk/join/tests/main.nf.test @@ -15,7 +15,7 @@ nextflow_process { process { """ input[0] = [ - [ id:'test' ], // meta map + [ id:'test' ], [ file("https://github.com/nf-core/test-datasets/raw/bacass/bacass_hybrid.csv", checkIfExists: true), file("https://github.com/nf-core/test-datasets/raw/bacass/bacass_short.csv", checkIfExists: true), @@ -28,7 +28,7 @@ nextflow_process { then { assertAll( { assert process.success }, - { assert snapshot(process.out).match() } + { assert snapshot(sanitizeOutput(process.out)).match() } ) } @@ -42,7 +42,7 @@ nextflow_process { process { """ input[0] = [ - [ id:'test' ], // meta map + [ id:'test' ], [ file("https://github.com/nf-core/test-datasets/raw/bacass/bacass_hybrid.csv", checkIfExists: true), file("https://github.com/nf-core/test-datasets/raw/bacass/bacass_short.csv", checkIfExists: true), @@ -55,7 +55,7 @@ nextflow_process { then { assertAll( { assert process.success }, - { assert snapshot(process.out).match() } + { assert snapshot(sanitizeOutput(process.out)).match() } ) } diff --git a/modules/nf-core/csvtk/join/tests/main.nf.test.snap b/modules/nf-core/csvtk/join/tests/main.nf.test.snap index 8ba7b861..78c71ec7 100644 --- a/modules/nf-core/csvtk/join/tests/main.nf.test.snap +++ b/modules/nf-core/csvtk/join/tests/main.nf.test.snap @@ -2,7 +2,7 @@ "join - csv": { "content": [ { - "0": [ + "csv": [ [ { "id": "test" @@ -10,32 +10,25 @@ "test.csv:md5,d0ad82ca096c7e05eb9f9a04194c9e30" ] ], - "1": [ - "versions.yml:md5,b80d80628bb39bba336cff32fe502aac" - ], - "csv": [ + "versions_csvtk": [ [ - { - "id": "test" - }, - "test.csv:md5,d0ad82ca096c7e05eb9f9a04194c9e30" + "CSVTK_JOIN", + "csvtk", + "0.37.0" ] - ], - "versions": [ - "versions.yml:md5,b80d80628bb39bba336cff32fe502aac" ] } ], + "timestamp": "2026-04-30T14:26:03.585129", "meta": { - "nf-test": "0.9.2", - "nextflow": "24.10.3" - }, - "timestamp": "2025-01-02T06:18:42.09571517" + "nf-test": "0.9.4", + "nextflow": "26.04.0" + } }, "join - csv - stub": { "content": [ { - "0": [ + "csv": [ [ { "id": "test" @@ -43,26 +36,19 @@ "test.csv:md5,d41d8cd98f00b204e9800998ecf8427e" ] ], - "1": [ - "versions.yml:md5,b80d80628bb39bba336cff32fe502aac" - ], - "csv": [ + "versions_csvtk": [ [ - { - "id": "test" - }, - "test.csv:md5,d41d8cd98f00b204e9800998ecf8427e" + "CSVTK_JOIN", + "csvtk", + "0.37.0" ] - ], - "versions": [ - "versions.yml:md5,b80d80628bb39bba336cff32fe502aac" ] } ], + "timestamp": "2026-04-30T14:26:09.818285", "meta": { - "nf-test": "0.9.2", - "nextflow": "24.10.3" - }, - "timestamp": "2025-01-02T06:19:00.2453934" + "nf-test": "0.9.4", + "nextflow": "26.04.0" + } } } \ No newline at end of file diff --git a/modules/nf-core/demuxem/demuxem.diff b/modules/nf-core/demuxem/demuxem.diff index 9cb369d4..c9cfdeee 100644 --- a/modules/nf-core/demuxem/demuxem.diff +++ b/modules/nf-core/demuxem/demuxem.diff @@ -1,34 +1,35 @@ Changes in component 'nf-core/demuxem' 'modules/nf-core/demuxem/environment.yml' is unchanged +'modules/nf-core/demuxem/nextflow.config' is unchanged +'modules/nf-core/demuxem/meta.yml' is unchanged Changes in 'demuxem/main.nf': --- modules/nf-core/demuxem/main.nf +++ modules/nf-core/demuxem/main.nf -@@ -1,47 +1,56 @@ +@@ -1,47 +1,62 @@ process DEMUXEM { - tag "$meta.id" + tag "${meta.id}" label 'process_low' conda "${moduleDir}/environment.yml" -- container "${ workflow.containerEngine == 'singularity' && !task.ext.singularity_pull_docker_container ? +- container "${ workflow.containerEngine in ['singularity', 'apptainer'] && !task.ext.singularity_pull_docker_container ? - 'https://depot.galaxyproject.org/singularity/demuxem:0.1.7.post1--pyhdfd78af_0' : -- 'biocontainers/demuxem:0.1.7.post1--pyhdfd78af_0' }" +- 'quay.io/biocontainers/demuxem:0.1.7.post1--pyhdfd78af_0' }" + container "${workflow.containerEngine == 'singularity' && !task.ext.singularity_pull_docker_container + ? 'https://community-cr-prod.seqera.io/docker/registry/v2/blobs/sha256/0d/0d3f96aaa8437bfa1654570e1d2b84749f1ac14d68f97978acc19b3757af7f55/data' + : 'community.wave.seqera.io/library/demuxem:0.1.7.post1--5ac55376ad7cb80e'}" + input: tuple val(meta), path(input_raw_gene_bc_matrices_h5), path(input_hto_csv_file) -- val output_name - val generate_gender_plot + val gender_genes val genome val generate_diagnostic_plots -+ + output: - tuple val(meta), path("*_demux.zarr.zip"), emit: zarr - tuple val(meta), path("*.out.demuxEM.zarr.zip"), emit: out_zarr -- path "versions.yml" , emit: versions +- tuple val("${task.process}"), val('demuxEM'), eval("demuxEM --version"), topic: versions, emit: versions_demuxem + tuple val(meta), path("${prefix}_demux.zarr.zip"), emit: zarr + tuple val(meta), path("${prefix}.out.demuxEM.zarr.zip"), emit: out_zarr + tuple val(meta), path("${prefix}.ambient_hashtag.hist.pdf"), emit: ambient_hashtag_hist, optional: true @@ -52,46 +53,45 @@ Changes in 'demuxem/main.nf': + def genome_args = genome ? "--genome ${genome}" : "" + def diagnostic_plots = generate_diagnostic_plots ? "--generate-diagnostic-plots" : "" """ -- demuxEM $input_raw_gene_bc_matrices_h5 \\ -- $input_hto_csv_file $output_name \\ -- $args \\ -- $generateGenderPlot\\ -- $genome_file\\ -- $diagnostic_plots +- demuxEM \\ +- ${args} \\ +- ${generateGenderPlot}\\ +- ${genome_file}\\ + export MPLCONFIGDIR=./tmp/mpl + + demuxEM ${input_raw_gene_bc_matrices_h5} ${input_hto_csv_file} ${prefix} \\ + -p ${task.cpus} \\ + ${generateGenderPlot} \\ + ${genome_args} \\ -+ ${diagnostic_plots} \\ + ${diagnostic_plots} \\ +- ${input_raw_gene_bc_matrices_h5} \\ +- ${input_hto_csv_file} \\ +- ${prefix} + ${args} + - cat <<-END_VERSIONS > versions.yml -- "${task.process}":g -- echo \$(demuxEM --version 2>&1) ++ cat <<-END_VERSIONS > versions.yml + "${task.process}": + echo \$(demuxEM --version 2>&1) - END_VERSIONS ++ END_VERSIONS """ stub: - def prefix = task.ext.prefix ?: "${meta.id}" +- def prefix = task.ext.prefix ?: "${meta.id}" ++ prefix = task.ext.prefix ?: "${meta.id}" """ + export MPLCONFIGDIR=./tmp/mpl touch ${prefix}.out.demuxEM.zarr.zip touch ${prefix}_demux.zarr.zip - -@@ -50,5 +59,4 @@ - echo \$(demuxEM --version 2>&1) - END_VERSIONS ++ ++ cat <<-END_VERSIONS > versions.yml ++ "${task.process}": ++ echo \$(demuxEM --version 2>&1) ++ END_VERSIONS """ - } -'modules/nf-core/demuxem/meta.yml' is unchanged -'modules/nf-core/demuxem/nextflow.config' is unchanged 'modules/nf-core/demuxem/tests/main.nf.test.snap' is unchanged -'modules/nf-core/demuxem/tests/main.nf.test' is unchanged 'modules/nf-core/demuxem/tests/nextflow.config' is unchanged +'modules/nf-core/demuxem/tests/main.nf.test' is unchanged ************************************************************ diff --git a/modules/nf-core/demuxem/environment.yml b/modules/nf-core/demuxem/environment.yml index db39832a..251235bf 100644 --- a/modules/nf-core/demuxem/environment.yml +++ b/modules/nf-core/demuxem/environment.yml @@ -5,3 +5,6 @@ channels: - bioconda dependencies: - bioconda::demuxem=0.1.7.post1 + - conda-forge::python=3.10 + - conda-forge::numpy=1.26 + - conda-forge::pandas=2.2 diff --git a/modules/nf-core/demuxem/meta.yml b/modules/nf-core/demuxem/meta.yml index 2fb43f57..9ef751f3 100644 --- a/modules/nf-core/demuxem/meta.yml +++ b/modules/nf-core/demuxem/meta.yml @@ -1,6 +1,6 @@ name: "demuxem" -description: Demultiplexing cell nucleus hashing data, using the estimated antibody - background probability. +description: Demultiplexing cell nucleus hashing data, using the estimated + antibody background probability. keywords: - demultiplexing - hashing-based deconvoltion @@ -13,9 +13,9 @@ tools: documentation: "https://demuxEM.readthedocs.io" tool_dev_url: "https://github.com/lilab-bcb/pegasus/tree/master" doi: "10.1038/s41467-019-10756-2" - licence: ["BSD-3-clause"] + licence: + - "BSD-3-clause" identifier: "" - input: - - meta: type: map @@ -32,25 +32,21 @@ input: description: | Path to file containing input HTO (antibody tag) count matrix in CSV format. pattern: "*.{csv}" - - - output_name: - type: string - description: | - Output name. All outputs will use it as the prefix. - - - generate_gender_plot: - type: string - description: | - Generate violin plots using gender-specific genes (e.g. Xist). It is a comma-separated list of gene names. - - - genome: - type: string - description: | - Reference genome name. If not provided, the tools infers it from the expression matrix file - - - generate_diagnostic_plots: - type: string - description: | - Generate diagnostic plots, including the background/signal between HTO counts, estimated background probabilities, HTO distributions. + - generate_gender_plot: + type: string + description: | + Generate violin plots using gender-specific genes (e.g. Xist). It is a comma-separated list of gene names. + - genome: + type: string + description: | + Reference genome name. If not provided, the tools infers it from the expression matrix file + - generate_diagnostic_plots: + type: string + description: | + Generate diagnostic plots, including the background/signal between HTO counts, estimated background probabilities, HTO distributions. output: - - zarr: - - meta: + zarr: + - - meta: type: map description: | Groovy Map containing sample information @@ -60,8 +56,10 @@ output: description: | RNA expression matrix with demultiplexed sample identities in Zarr format. pattern: "*_demux.zarr.zip" - - out_zarr: - - meta: + ontologies: + - edam: http://edamontology.org/format_3987 # ZIP format + out_zarr: + - - meta: type: map description: | Groovy Map containing sample information @@ -71,11 +69,29 @@ output: description: | DemuxEM-calculated results in Zarr format, containing two datasets, one for HTO and one for RNA. pattern: "*.out.demuxEM.zarr.zip" - - versions: - - versions.yml: - type: file - description: File containing software versions - pattern: "versions.yml" + ontologies: + - edam: http://edamontology.org/format_3987 # ZIP format + versions_demuxem: + - - ${task.process}: + type: string + description: The name of the process + - demuxEM: + type: string + description: The name of the tool + - demuxEM --version: + type: eval + description: The expression to obtain the version of the tool +topics: + versions: + - - ${task.process}: + type: string + description: The name of the process + - demuxEM: + type: string + description: The name of the tool + - demuxEM --version: + type: eval + description: The expression to obtain the version of the tool authors: - "@mari-ga" - "@maxozo" diff --git a/modules/nf-core/demuxem/tests/main.nf.test b/modules/nf-core/demuxem/tests/main.nf.test index 49b75f70..657d5c0d 100644 --- a/modules/nf-core/demuxem/tests/main.nf.test +++ b/modules/nf-core/demuxem/tests/main.nf.test @@ -15,16 +15,15 @@ nextflow_process { when { process { """ - + input[0] = [ [ id:'sample1'], file(params.modules_testdata_base_path + "/genomics/homo_sapiens/10xgenomics/cellranger/hashing_demultiplexing/438-21-raw_feature_bc_matrix.h5",checkIfExists: true), file(params.modules_testdata_base_path + "/genomics/homo_sapiens/10xgenomics/cellranger/hashing_demultiplexing/438_21_raw_HTO.csv",checkIfExists: true) ] - input[1] = "results" + input[1] = "" input[2] = "" input[3] = "" - input[4] = "" """ } } @@ -32,9 +31,11 @@ nextflow_process { then { assertAll( { assert process.success }, - { assert path(process.out.zarr.get(0).get(1)).exists() }, - { assert path(process.out.out_zarr.get(0).get(1)).exists() }, - + { assert snapshot( + file(process.out.zarr[0][1]).name, + file(process.out.out_zarr[0][1]).name, + process.out.findAll { key, val -> key.startsWith('versions') } + ).match() } ) } @@ -47,16 +48,15 @@ nextflow_process { when { process { """ - + input[0] = [ [ id:'sample1'], file(params.modules_testdata_base_path + "/genomics/homo_sapiens/10xgenomics/cellranger/hashing_demultiplexing/438-21-raw_feature_bc_matrix.h5",checkIfExists: true), file(params.modules_testdata_base_path + "/genomics/homo_sapiens/10xgenomics/cellranger/hashing_demultiplexing/438_21_raw_HTO.csv",checkIfExists: true) ] - input[1] = "results" + input[1] = "" input[2] = "" - input[3] = "" - input[4] = "True" + input[3] = "True" """ } } @@ -64,8 +64,7 @@ nextflow_process { then { assertAll( { assert process.success }, - { assert path(process.out.zarr.get(0).get(1)).exists() }, - { assert path(process.out.out_zarr.get(0).get(1)).exists() }, + { assert snapshot(sanitizeOutput(process.out)).match() } ) } diff --git a/modules/nf-core/demuxem/tests/main.nf.test.snap b/modules/nf-core/demuxem/tests/main.nf.test.snap index 55b5b72b..32e9992d 100644 --- a/modules/nf-core/demuxem/tests/main.nf.test.snap +++ b/modules/nf-core/demuxem/tests/main.nf.test.snap @@ -2,83 +2,55 @@ "Standard_Multiome - h5 - csv - stub": { "content": [ { - "0": [ - - ], - "1": [ - - ], - "2": [ - - ], "out_zarr": [ - - ], - "versions": [ - + [ + { + "id": "sample1" + }, + "sample1.out.demuxEM.zarr.zip:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "versions_demuxem": [ + [ + "DEMUXEM", + "demuxEM", + "0.1.7.post1" + ] ], "zarr": [ - + [ + { + "id": "sample1" + }, + "sample1_demux.zarr.zip:md5,d41d8cd98f00b204e9800998ecf8427e" + ] ] } ], "meta": { - "nf-test": "0.8.4", - "nextflow": "23.10.1" + "nf-test": "0.9.3", + "nextflow": "25.10.3" }, - "timestamp": "2024-04-18T11:53:38.668726" - }, - "versions": { - "content": [ - [ - - ] - ], - "meta": { - "nf-test": "0.8.4", - "nextflow": "23.10.1" - }, - "timestamp": "2024-04-18T11:35:55.502389" + "timestamp": "2026-02-12T10:56:24.718965234" }, "Standard_Multiome - h5 - csv": { "content": [ + "sample1_demux.zarr.zip", + "sample1.out.demuxEM.zarr.zip", { - "0": [ - - ], - "1": [ - - ], - "2": [ - - ], - "out_zarr": [ - - ], - "versions": [ - - ], - "zarr": [ - + "versions_demuxem": [ + [ + "DEMUXEM", + "demuxEM", + "0.1.7.post1" + ] ] } ], "meta": { - "nf-test": "0.8.4", - "nextflow": "23.10.1" - }, - "timestamp": "2024-04-18T11:35:55.532153" - }, - "stub-versions": { - "content": [ - [ - - ] - ], - "meta": { - "nf-test": "0.8.4", - "nextflow": "23.10.1" + "nf-test": "0.9.3", + "nextflow": "25.10.3" }, - "timestamp": "2024-04-18T11:53:38.65021" + "timestamp": "2026-02-12T12:29:24.704098521" } } \ No newline at end of file diff --git a/modules/nf-core/gmmdemux/environment.yml b/modules/nf-core/gmmdemux/environment.yml index 1ddb2fa2..7b94206f 100644 --- a/modules/nf-core/gmmdemux/environment.yml +++ b/modules/nf-core/gmmdemux/environment.yml @@ -5,3 +5,8 @@ channels: - bioconda dependencies: - bioconda::gmm-demux=0.2.2.3 + - conda-forge::numpy=1.26.4 + - conda-forge::pandas=2.2.1 + - conda-forge::python=3.12.2 + - conda-forge::scikit-learn=1.4.1 + - conda-forge::scipy=1.12.0 diff --git a/modules/nf-core/gmmdemux/gmmdemux.diff b/modules/nf-core/gmmdemux/gmmdemux.diff index fdd35963..f1832907 100644 --- a/modules/nf-core/gmmdemux/gmmdemux.diff +++ b/modules/nf-core/gmmdemux/gmmdemux.diff @@ -1,8 +1,10 @@ Changes in component 'nf-core/gmmdemux' +'modules/nf-core/gmmdemux/environment.yml' is unchanged +'modules/nf-core/gmmdemux/meta.yml' is unchanged Changes in 'gmmdemux/main.nf': --- modules/nf-core/gmmdemux/main.nf +++ modules/nf-core/gmmdemux/main.nf -@@ -1,63 +1,65 @@ +@@ -1,66 +1,74 @@ - process GMMDEMUX { - tag "$meta.id" @@ -10,9 +12,9 @@ Changes in 'gmmdemux/main.nf': label 'process_low' conda "${moduleDir}/environment.yml" -- container "${ workflow.containerEngine == 'singularity' && !task.ext.singularity_pull_docker_container ? +- container "${ workflow.containerEngine in ['singularity', 'apptainer'] && !task.ext.singularity_pull_docker_container ? - 'https://depot.galaxyproject.org/singularity/gmm-demux:0.2.2.3--pyh7cba7a3_0': -- 'biocontainers/gmm-demux:0.2.2.3--pyh7cba7a3_0' }" +- 'quay.io/biocontainers/gmm-demux:0.2.2.3--pyh7cba7a3_0' }" + container "${workflow.containerEngine == 'singularity' && !task.ext.singularity_pull_docker_container + ? 'https://depot.galaxyproject.org/singularity/gmm-demux:0.2.2.3--pyh7cba7a3_0' + : 'biocontainers/gmm-demux:0.2.2.3--pyh7cba7a3_0'}" @@ -34,26 +36,27 @@ Changes in 'gmmdemux/main.nf': - tuple val(meta), path("features.tsv.gz" ), emit: features - tuple val(meta), path("GMM_*.csv" ), emit: classification_report - tuple val(meta), path("GMM_*.config" ), emit: config_report +- tuple val(meta), path("summary_report_*.txt") , emit: summary_report, optional: true +- // WARN: Version information not provided by tool on CLI. Please update version string below when bumping container versions. +- tuple val("${task.process}"), val('GMM-Demux'), val("0.2.2.3") , emit: versions_gmmdemux, topic: versions + tuple val(meta), path("barcodes.tsv.gz"), emit: barcodes + tuple val(meta), path("matrix.mtx.gz"), emit: matrix + tuple val(meta), path("features.tsv.gz"), emit: features + tuple val(meta), path("GMM_*.csv"), emit: classification_report + tuple val(meta), path("GMM_*.config"), emit: config_report - tuple val(meta), path("summary_report_*.txt"), emit: summary_report, optional: true -- path "versions.yml" , emit: versions ++ tuple val(meta), path("summary_report_*.txt"), emit: summary_report, optional: true + path "versions.yml", emit: versions when: task.ext.when == null || task.ext.when script: -- def args = task.ext.args ?: '' -- def prefix = task.ext.prefix ?: "${meta.id}" -- def skip = skip ? "--skip $skip" : "" -- def examine_cells = examine ? "--examine $examine" : "" -- def VERSION = '0.2.2.3' // WARN: Version information not provided by tool on CLI. Please update version string below when bumping container versions. -- def type_report = type_report ? "-f ." : "-s ." -- def summary_rep = summary_report ? "-r ${prefix}_summary_report.txt" : "" +- def args = task.ext.args ?: '' +- def prefix = task.ext.prefix ?: "${meta.id}" +- def skip_opt = skip ? "--skip $skip" : "" +- def examine_cells = examine ? "--examine $examine" : "" +- def type_report_opt = type_report ? "-f ." : "-s ." +- def summary_rep = summary_report ? "-r ${prefix}_summary_report.txt" : "" + // Docs: https://gmm-demux.readthedocs.io/en/latest/usage.html#command-line-tools + def args = task.ext.args ?: '' + def prefix = task.ext.prefix ?: "${meta.id}" @@ -70,9 +73,9 @@ Changes in 'gmmdemux/main.nf': fi - GMM-demux $args \\ -- $type_report \\ +- $type_report_opt \\ - $summary_rep \\ -- $skip \\ +- $skip_opt \\ - $examine_cells \\ - $hto_matrix \\ - $hto_names \\ @@ -85,33 +88,34 @@ Changes in 'gmmdemux/main.nf': + ${examine_arg} \\ + -o . \\ + ${args} - - cat <<-END_VERSIONS > versions.yml - "${task.process}": -- GMM-Demux: $VERSION ++ ++ cat <<-END_VERSIONS > versions.yml ++ "${task.process}": + GMM-Demux: ${VERSION} - END_VERSIONS ++ END_VERSIONS """ stub: - def VERSION = '0.2.2.3' - def prefix = task.ext.prefix ?: "${meta.id}" -+ def prefix = task.ext.prefix ?: "${meta.id}" ++ def VERSION = '0.2.2.3' """ +- if [[ ${summary_report} == true ]]; then +- touch ${prefix}_summary_report.txt +- fi +- echo "" | gzip > barcodes.tsv.gz echo "" | gzip > features.tsv.gz -@@ -67,7 +69,7 @@ - - cat <<-END_VERSIONS > versions.yml - "${task.process}": -- GMM-Demux: $VERSION + echo "" | gzip > matrix.mtx.gz + touch GMM_full.config + touch GMM_full.csv ++ ++ cat <<-END_VERSIONS > versions.yml ++ "${task.process}": + GMM-Demux: ${VERSION} - END_VERSIONS ++ END_VERSIONS """ } -'modules/nf-core/gmmdemux/environment.yml' is unchanged -'modules/nf-core/gmmdemux/meta.yml' is unchanged 'modules/nf-core/gmmdemux/tests/main.nf.test.snap' is unchanged 'modules/nf-core/gmmdemux/tests/nextflow.config' is unchanged 'modules/nf-core/gmmdemux/tests/main.nf.test' is unchanged diff --git a/modules/nf-core/gmmdemux/meta.yml b/modules/nf-core/gmmdemux/meta.yml index cca69326..b3223c4a 100644 --- a/modules/nf-core/gmmdemux/meta.yml +++ b/modules/nf-core/gmmdemux/meta.yml @@ -1,6 +1,4 @@ -# yaml-language-server: $schema=https://raw.githubusercontent.com/nf-core/modules/master/modules/meta-schema.json name: "gmmdemux" - description: GMM-Demux is a Gaussian-Mixture-Model-based software for processing sample barcoding data (cell hashing and MULTI-seq). keywords: @@ -15,9 +13,9 @@ tools: documentation: "https://github.com/CHPGenetics/GMM-Demux" tool_dev_url: "https://github.com/CHPGenetics/GMM-demux" doi: "10.1186/s13059-020-02084-2" - licence: ["MIT"] + licence: + - "MIT" identifier: "" - input: - - meta: type: map @@ -28,84 +26,97 @@ input: type: file description: path to matrix from cell hashing data, the tool receives either CSV files or TSV, type must be specified using parameters + ontologies: [] - hto_names: type: string description: | Comma separated list of HTO names, without whitespace - - - type_report: - type: boolean - description: | - If true, full classification report is generated, otherwise the simplified classification report. - - - summary_report: - type: boolean - description: | - If true, summary report is generated. - - - skip: - type: file - description: | - Load a full classification report and skip the mtx folder as input. Require a path argument. - - - examine: - type: file - description: | - Provide the cell list. Requires a file argument. Only executes if -u is set. + - type_report: + type: boolean + description: | + If true, full classification report is generated, otherwise the simplified classification report. + - summary_report: + type: boolean + description: | + If true, summary report is generated. + - skip: + type: file + description: | + Load a full classification report and skip the mtx folder as input. Require a path argument. + ontologies: [] + - examine: + type: file + description: | + Provide the cell list. Requires a file argument. Only executes if -u is set. + ontologies: [] output: - - barcodes: - - meta: + barcodes: + - - meta: type: map description: | Groovy Map containing sample information e.g. `[ id:'sample1' ]` - - 'barcodes.tsv.gz" ': + - barcodes.tsv.gz: type: file description: | barcodes tsv file with removed cell-hashing-identifiable multiplets pattern: "barcodes.tsv.gz" - - matrix: - - meta: + ontologies: + - edam: http://edamontology.org/format_3989 + matrix: + - - meta: type: map description: | Groovy Map containing sample information e.g. `[ id:'sample1' ]` - - 'matrix.mtx.gz" ': + - matrix.mtx.gz: type: file description: | matrix mtx.tsv file with removed cell-hashing-identifiable multiplets pattern: "matrix.mtx.gz" - - features: - - meta: + ontologies: + - edam: http://edamontology.org/format_3989 # GZIP + features: + - - meta: type: map description: | Groovy Map containing sample information e.g. `[ id:'sample1' ]` - - 'features.tsv.gz" ': + - features.tsv.gz: type: file description: | features tsv file with removed cell-hashing-identifiable multiplets pattern: "features.tsv.gz" - - classification_report: - - meta: + ontologies: + - edam: http://edamontology.org/format_3989 + classification_report: + - - meta: type: map description: | Groovy Map containing sample information e.g. `[ id:'sample1' ]` - - 'GMM_*.csv" ': + - GMM_*.csv: type: file description: | full or simplified classification report pattern: "GMM_*.csv" - - config_report: - - meta: + ontologies: + - edam: http://edamontology.org/format_3752 # CSV + config_report: + - - meta: type: map description: | Groovy Map containing sample information e.g. `[ id:'sample1' ]` - - 'GMM_*.config" ': + - GMM_*.config: type: file description: | Configuration report mapping the results obtained by the tool to the respective names of the HTOs in the classification report. pattern: "GMM_*.csv" - - summary_report: - - meta: + ontologies: + - edam: http://edamontology.org/format_3752 # CSV + summary_report: + - - meta: type: map description: | Groovy Map containing sample information @@ -115,11 +126,28 @@ output: description: | summary report, optional output pattern: "test/summary_report_*.txt" - - versions: - - versions.yml: - type: file - description: File containing software versions - pattern: "versions.yml" + ontologies: [] + versions_gmmdemux: + - - ${task.process}: + type: string + description: The name of the process + - GMM-Demux: + type: string + description: The name of the tool + - 0.2.2.3: + type: string + description: The expression to obtain the version of the tool +topics: + versions: + - - ${task.process}: + type: string + description: The name of the process + - GMM-Demux: + type: string + description: The name of the tool + - 0.2.2.3: + type: string + description: The expression to obtain the version of the tool authors: - "@mari-ga" - "@maxozo" diff --git a/modules/nf-core/gmmdemux/tests/main.nf.test b/modules/nf-core/gmmdemux/tests/main.nf.test index 674a7ce0..34b96aad 100644 --- a/modules/nf-core/gmmdemux/tests/main.nf.test +++ b/modules/nf-core/gmmdemux/tests/main.nf.test @@ -10,17 +10,17 @@ nextflow_process { tag "gmmdemux" tag "untar" - + test("Standard_Multiome - 10x mtx - simple") { setup { run("UNTAR") { script "modules/nf-core/untar/main.nf" process { """ - input[0] = Channel.of([ [],file(params.modules_testdata_base_path + "/genomics/homo_sapiens/10xgenomics/cellranger/hashing_demultiplexing/hto.tar.gz", checkIfExists: true) ]) + input[0] = Channel.of([ [],file(params.modules_testdata_base_path + "/genomics/homo_sapiens/10xgenomics/cellranger/hashing_demultiplexing/hto.tar.gz", checkIfExists: true) ]) """ } - } + } } when { process { @@ -53,10 +53,10 @@ nextflow_process { script "modules/nf-core/untar/main.nf" process { """ - input[0] = Channel.of([ [],file(params.modules_testdata_base_path + "/genomics/homo_sapiens/10xgenomics/cellranger/hashing_demultiplexing/hto.tar.gz", checkIfExists: true) ]) + input[0] = Channel.of([ [],file(params.modules_testdata_base_path + "/genomics/homo_sapiens/10xgenomics/cellranger/hashing_demultiplexing/hto.tar.gz", checkIfExists: true) ]) """ } - } + } } when { process { @@ -89,9 +89,9 @@ nextflow_process { when { process { """ - + input[0] = [ - [ id:'test'], + [ id:'test'], file(params.modules_testdata_base_path + "/genomics/homo_sapiens/10xgenomics/cellranger/hashing_demultiplexing/hto.tar.gz", checkIfExists: true), "MS-11,MS-12" ] diff --git a/modules/nf-core/gmmdemux/tests/main.nf.test.snap b/modules/nf-core/gmmdemux/tests/main.nf.test.snap index 7fc5f03c..1ed7e095 100644 --- a/modules/nf-core/gmmdemux/tests/main.nf.test.snap +++ b/modules/nf-core/gmmdemux/tests/main.nf.test.snap @@ -46,7 +46,11 @@ ], "6": [ - "versions.yml:md5,da37b016c394b4e8cc16afb83e6f4941" + [ + "GMMDEMUX", + "GMM-Demux", + "0.2.2.3" + ] ], "barcodes": [ [ @@ -91,8 +95,12 @@ "summary_report": [ ], - "versions": [ - "versions.yml:md5,da37b016c394b4e8cc16afb83e6f4941" + "versions_gmmdemux": [ + [ + "GMMDEMUX", + "GMM-Demux", + "0.2.2.3" + ] ] } ], diff --git a/modules/nf-core/gmmdemux/tests/nextflow.config b/modules/nf-core/gmmdemux/tests/nextflow.config index 66649e7e..fe988ee0 100644 --- a/modules/nf-core/gmmdemux/tests/nextflow.config +++ b/modules/nf-core/gmmdemux/tests/nextflow.config @@ -9,7 +9,7 @@ params{ } process { - + withName: GMMDEMUX { ext.args = "-t ${params.threshold} --summary ${params.num_cells} -a ${params.ambiguous} " } diff --git a/modules/nf-core/hasheddrops/hasheddrops.diff b/modules/nf-core/hasheddrops/hasheddrops.diff deleted file mode 100644 index dcc5bc79..00000000 --- a/modules/nf-core/hasheddrops/hasheddrops.diff +++ /dev/null @@ -1,28 +0,0 @@ -Changes in component 'nf-core/hasheddrops' -'modules/nf-core/hasheddrops/main.nf' is unchanged -'modules/nf-core/hasheddrops/environment.yml' is unchanged -Changes in 'hasheddrops/meta.yml': ---- modules/nf-core/hasheddrops/meta.yml -+++ modules/nf-core/hasheddrops/meta.yml -@@ -109,7 +109,7 @@ - - "*_plot_hasheddrops.png": - type: file - description: | -- HashedDrops plot -+ HashedDrops plot - pattern: "_plot_hasheddrops.png" - - - params: -@@ -128,7 +128,6 @@ - type: file - description: File containing software versions. - -- - authors: - - "@LuisHeinzlmeier" - maintainers: - -'modules/nf-core/hasheddrops/templates/HashedDrops.R' is unchanged -'modules/nf-core/hasheddrops/tests/main.nf.test.snap' is unchanged -'modules/nf-core/hasheddrops/tests/main.nf.test' is unchanged -************************************************************ diff --git a/modules/nf-core/hasheddrops/main.nf b/modules/nf-core/hasheddrops/main.nf index 0b45a00f..5125b86c 100644 --- a/modules/nf-core/hasheddrops/main.nf +++ b/modules/nf-core/hasheddrops/main.nf @@ -3,7 +3,7 @@ process HASHEDDROPS { label 'process_low' conda "${moduleDir}/environment.yml" - container "${ workflow.containerEngine == 'singularity' && !task.ext.singularity_pull_docker_container ? + container "${ workflow.containerEngine in ['singularity', 'apptainer'] && !task.ext.singularity_pull_docker_container ? 'https://community-cr-prod.seqera.io/docker/registry/v2/blobs/sha256/43/431b27926fac88d6334ee3e8f63479f69a1a69340b305a05b70bc84083d301aa/data': 'community.wave.seqera.io/library/bioconductor-dropletutils_r-seurat:e1dff3a0fb7c5920' }" @@ -19,7 +19,7 @@ process HASHEDDROPS { tuple val(meta), path("*_hasheddrops.rds") , emit: rds tuple val(meta), path("*_plot_hasheddrops.png") , emit: plot tuple val(meta), path("*_params_hasheddrops.csv") , emit: params - path "versions.yml" , emit: versions + path "versions.yml" , emit: versions, topic: versions when: task.ext.when == null || task.ext.when @@ -42,8 +42,8 @@ process HASHEDDROPS { cat <<-END_VERSIONS > versions.yml "${task.process}": r-base: \$(Rscript -e "cat(strsplit(R.version[['version.string']], ' ')[[1]][3])") - r-seurat: \$(Rscript -e "library(Seurat); cat(as.character(packageVersion('Seurat')))") - dropletutils: \$(Rscript -e "library(DropletUtils); cat(as.character(packageVersion('DropletUtils')))") + r-seurat: \$(Rscript -e "cat(as.character(packageVersion('Seurat')))") + dropletutils: \$(Rscript -e "cat(as.character(packageVersion('DropletUtils')))") END_VERSIONS """ } diff --git a/modules/nf-core/hasheddrops/meta.yml b/modules/nf-core/hasheddrops/meta.yml index 43a33ce9..9198597f 100644 --- a/modules/nf-core/hasheddrops/meta.yml +++ b/modules/nf-core/hasheddrops/meta.yml @@ -1,4 +1,3 @@ -# yaml-language-server: $schema=https://raw.githubusercontent.com/nf-core/modules/master/modules/meta-schema.json name: "hasheddrops" description: Generating cell hashing calls from a matrix of count data. keywords: @@ -14,9 +13,9 @@ tools: documentation: "https://rdrr.io/github/MarioniLab/DropletUtils/man/hashedDrops.html" tool_dev_url: "https://github.com/MarioniLab/DropletUtils" doi: "10.18129/B9.bioc.DropletUtils" - licence: ["GPL-3"] + licence: + - "GPL-3" identifier: "" - input: - - meta: type: map @@ -144,9 +143,18 @@ output: versions: - versions.yml: type: file - description: File containing software versions. - - ontologies: [] + description: File containing software versions + pattern: "versions.yml" + ontologies: + - edam: "http://edamontology.org/format_3750" # YAML +topics: + versions: + - versions.yml: + type: file + description: File containing software versions + pattern: "versions.yml" + ontologies: + - edam: "http://edamontology.org/format_3750" # YAML authors: - "@LuisHeinzlmeier" maintainers: diff --git a/modules/nf-core/hasheddrops/tests/main.nf.test b/modules/nf-core/hasheddrops/tests/main.nf.test index e785b50b..34ff55f0 100644 --- a/modules/nf-core/hasheddrops/tests/main.nf.test +++ b/modules/nf-core/hasheddrops/tests/main.nf.test @@ -9,63 +9,30 @@ nextflow_process { tag "hasheddrops" tag "untar" - test("hto_matrix - runEmptyDrops:false - rna_matrix") { - - setup { - run("UNTAR") { - script "modules/nf-core/untar/main.nf" - process { - """ - input[0] = Channel.of( - [ [id: 'hto'], file(params.modules_testdata_base_path + "/genomics/homo_sapiens/10xgenomics/cellranger/hashing_demultiplexing/hto.tar.gz") ], - [ [id: 'rna'], file(params.modules_testdata_base_path + "/genomics/homo_sapiens/10xgenomics/cellranger/hashing_demultiplexing/rna.tar.gz") ] - ) - """ - } - } - } - when { + setup { + run("UNTAR") { + script "modules/nf-core/untar/main.nf" process { """ - input[0] = UNTAR.out.untar.filter{ meta, _f -> meta.id == 'hto' } - .combine(UNTAR.out.untar.filter{ meta, _f -> meta.id == 'rna' }) - .map{ _meta_hto, hto, _meta_rna, rna -> [[id: 'test'], hto, "FALSE", rna] } + input[0] = channel.of( + [ [id: 'hto'], file(params.modules_testdata_base_path + "/genomics/homo_sapiens/10xgenomics/cellranger/hashing_demultiplexing/hto.tar.gz") ], + [ [id: 'rna'], file(params.modules_testdata_base_path + "/genomics/homo_sapiens/10xgenomics/cellranger/hashing_demultiplexing/rna.tar.gz") ] + ) """ } } - - then { - assertAll( - { assert process.success }, - { assert snapshot(process.out).match() } - ) - } - } -/* - test("hto_matrix - runEmptyDrops:true - rna_matrix") { - setup { - run("UNTAR") { - script "modules/nf-core/untar/main.nf" - process { - """ - input[0] = Channel.of( - [ [id: 'hto'], file(params.modules_testdata_base_path + "/genomics/homo_sapiens/10xgenomics/cellranger/hashing_demultiplexing/hto.tar.gz") ], - [ [id: 'rna'], file(params.modules_testdata_base_path + "/genomics/homo_sapiens/10xgenomics/cellranger/hashing_demultiplexing/rna.tar.gz") ] - ) - """ - } - } - } + test("hto_matrix - runEmptyDrops:false - rna_matrix") { when { process { """ - input[0] = UNTAR.out.untar.filter{ meta, _f -> meta.id == 'hto' } - .combine(UNTAR.out.untar.filter{ meta, _f -> meta.id == 'rna' }) - .map{ _meta_hto, hto, _meta_rna, rna -> [[id: 'test'], hto, "TRUE", rna] } + input[0] = UNTAR.out.untar + .filter{ meta, _f -> meta.id == 'hto' } + .combine(UNTAR.out.untar.filter{ meta, _f -> meta.id == 'rna' }) + .map{ _meta_hto, hto, _meta_rna, rna -> [[id: 'test'], hto, "FALSE", rna] } """ } } @@ -73,36 +40,23 @@ nextflow_process { then { assertAll( { assert process.success }, - { assert snapshot(process.out).match() } + { assert snapshot( + sanitizeOutput(process.out), + path(process.out.versions[0]).yaml + ).match() } ) } - } -*/ - test("hto_matrix - runEmptyDrops:true - rna_matrix - stub") { + test("hto_matrix - runEmptyDrops:true - rna_matrix - stub") { options "-stub" - - setup { - run("UNTAR") { - script "modules/nf-core/untar/main.nf" - process { - """ - input[0] = Channel.of( - [ [id: 'hto'], file(params.modules_testdata_base_path + "/genomics/homo_sapiens/10xgenomics/cellranger/hashing_demultiplexing/hto.tar.gz") ], - [ [id: 'rna'], file(params.modules_testdata_base_path + "/genomics/homo_sapiens/10xgenomics/cellranger/hashing_demultiplexing/rna.tar.gz") ] - ) - """ - } - } - } - when { process { """ - input[0] = UNTAR.out.untar.filter{ meta, _f -> meta.id == 'hto' } - .combine(UNTAR.out.untar.filter{ meta, _f -> meta.id == 'rna' }) - .map{ _meta_hto, hto, _meta_rna, rna -> [[id: 'test'], hto, "TRUE", rna] } + input[0] = UNTAR.out.untar + .filter{ meta, _f -> meta.id == 'hto' } + .combine(UNTAR.out.untar.filter{ meta, _f -> meta.id == 'rna' }) + .map{ _meta_hto, hto, _meta_rna, rna -> [[id: 'test'], hto, "TRUE", rna] } """ } } @@ -110,11 +64,12 @@ nextflow_process { then { assertAll( { assert process.success }, - { assert snapshot(process.out).match() } + { assert snapshot( + sanitizeOutput(process.out), + path(process.out.versions[0]).yaml + ).match() } ) } - } } - diff --git a/modules/nf-core/hasheddrops/tests/main.nf.test.snap b/modules/nf-core/hasheddrops/tests/main.nf.test.snap index b75e8f16..ff1c04cb 100644 --- a/modules/nf-core/hasheddrops/tests/main.nf.test.snap +++ b/modules/nf-core/hasheddrops/tests/main.nf.test.snap @@ -2,73 +2,6 @@ "hto_matrix - runEmptyDrops:true - rna_matrix - stub": { "content": [ { - "0": [ - [ - { - "id": "test" - }, - "test_emptyDrops.png:md5,d41d8cd98f00b204e9800998ecf8427e" - ] - ], - "1": [ - [ - { - "id": "test" - }, - "test_emptyDrops.csv:md5,d41d8cd98f00b204e9800998ecf8427e" - ] - ], - "2": [ - [ - { - "id": "test" - }, - "test_emptyDrops.rds:md5,d41d8cd98f00b204e9800998ecf8427e" - ] - ], - "3": [ - [ - { - "id": "test" - }, - "test_results_hasheddrops.csv:md5,d41d8cd98f00b204e9800998ecf8427e" - ] - ], - "4": [ - [ - { - "id": "test" - }, - "test_id_to_hash.csv:md5,d41d8cd98f00b204e9800998ecf8427e" - ] - ], - "5": [ - [ - { - "id": "test" - }, - "test_hasheddrops.rds:md5,d41d8cd98f00b204e9800998ecf8427e" - ] - ], - "6": [ - [ - { - "id": "test" - }, - "test_plot_hasheddrops.png:md5,d41d8cd98f00b204e9800998ecf8427e" - ] - ], - "7": [ - [ - { - "id": "test" - }, - "test_params_hasheddrops.csv:md5,d41d8cd98f00b204e9800998ecf8427e" - ] - ], - "8": [ - "versions.yml:md5,82ab6a6b5ac26c697551e18b1526daa5" - ], "empty_drops_csv": [ [ { @@ -136,84 +69,24 @@ "versions": [ "versions.yml:md5,82ab6a6b5ac26c697551e18b1526daa5" ] + }, + { + "HASHEDDROPS": { + "r-base": "4.4.3", + "r-seurat": "5.3.0", + "dropletutils": "1.26.0" + } } ], + "timestamp": "2026-05-18T06:10:36.325409261", "meta": { - "nf-test": "0.9.2", - "nextflow": "25.04.3" - }, - "timestamp": "2025-08-11T11:41:43.705133" + "nf-test": "0.9.5", + "nextflow": "26.04.0" + } }, "hto_matrix - runEmptyDrops:false - rna_matrix": { "content": [ { - "0": [ - [ - { - "id": "test" - }, - "test_emptyDrops.png:md5,2d7113a76a494b2b2c18b77c4a664063" - ] - ], - "1": [ - [ - { - "id": "test" - }, - "test_emptyDrops.csv:md5,a227d42afbcc590b4e949075cde4a5b6" - ] - ], - "2": [ - [ - { - "id": "test" - }, - "test_emptyDrops.rds:md5,bda2e88b1e3e8c8ea5a22e19e3ab243d" - ] - ], - "3": [ - [ - { - "id": "test" - }, - "test_results_hasheddrops.csv:md5,d0f2af6d9bdb47a80bfbf08a85493d2e" - ] - ], - "4": [ - [ - { - "id": "test" - }, - "test_id_to_hash.csv:md5,a015ca1178177038db21a97fa2070874" - ] - ], - "5": [ - [ - { - "id": "test" - }, - "test_hasheddrops.rds:md5,31ba0ecae1d94080bccf81cdbaa5afe8" - ] - ], - "6": [ - [ - { - "id": "test" - }, - "test_plot_hasheddrops.png:md5,2d7113a76a494b2b2c18b77c4a664063" - ] - ], - "7": [ - [ - { - "id": "test" - }, - "test_params_hasheddrops.csv:md5,144fd97597ea5df4ad7bdddf2f6fabac" - ] - ], - "8": [ - "versions.yml:md5,82ab6a6b5ac26c697551e18b1526daa5" - ], "empty_drops_csv": [ [ { @@ -281,12 +154,19 @@ "versions": [ "versions.yml:md5,82ab6a6b5ac26c697551e18b1526daa5" ] + }, + { + "HASHEDDROPS": { + "r-base": "4.4.3", + "r-seurat": "5.3.0", + "dropletutils": "1.26.0" + } } ], + "timestamp": "2026-05-18T06:08:00.591235299", "meta": { - "nf-test": "0.9.2", - "nextflow": "25.04.6" - }, - "timestamp": "2025-08-11T15:04:02.201418256" + "nf-test": "0.9.5", + "nextflow": "26.04.0" + } } } \ No newline at end of file diff --git a/modules/nf-core/htodemux/main.nf b/modules/nf-core/htodemux/main.nf index 77a66434..76045eb2 100644 --- a/modules/nf-core/htodemux/main.nf +++ b/modules/nf-core/htodemux/main.nf @@ -3,7 +3,7 @@ process HTODEMUX { label 'process_low' conda "${moduleDir}/environment.yml" - container "${ workflow.containerEngine == 'singularity' && !task.ext.singularity_pull_docker_container ? + container "${ workflow.containerEngine in ['singularity', 'apptainer'] && !task.ext.singularity_pull_docker_container ? 'https://community-cr-prod.seqera.io/docker/registry/v2/blobs/sha256/f9/f96b7927142847485eff858170a4cfd2d3924fb4f09de7043dd6677ac6acd09e/data': 'community.wave.seqera.io/library/r-seurat_r-seuratobject:b11306d1bdc82827' }" @@ -15,7 +15,7 @@ process HTODEMUX { tuple val(meta), path("*_assignment_htodemux.csv") , emit: assignment tuple val(meta), path("*_classification_htodemux.csv"), emit: classification tuple val(meta), path("*_htodemux.rds") , emit: rds - path "versions.yml" , emit: versions + path "versions.yml", emit: versions, topic: versions when: task.ext.when == null || task.ext.when @@ -33,7 +33,8 @@ process HTODEMUX { cat <<-END_VERSIONS > versions.yml "${task.process}": - htodemux: \$(htodemux --version) + r-base: \$(Rscript -e "cat(strsplit(R.version[['version.string']], ' ')[[1]][3])") + r-seurat: \$(Rscript -e "cat(as.character(packageVersion('Seurat')))") END_VERSIONS """ } diff --git a/modules/nf-core/htodemux/meta.yml b/modules/nf-core/htodemux/meta.yml index accc7367..362d6a52 100644 --- a/modules/nf-core/htodemux/meta.yml +++ b/modules/nf-core/htodemux/meta.yml @@ -1,4 +1,3 @@ -# yaml-language-server: $schema=https://raw.githubusercontent.com/nf-core/modules/master/modules/meta-schema.json name: "htodemux" description: Demultiplex samples based on data from cell hashing. keywords: @@ -13,9 +12,9 @@ tools: documentation: "https://satijalab.org/seurat/reference/htodemux" tool_dev_url: "https://github.com/satijalab/seurat" doi: "10.1186/s13059-018-1603-1" - licence: ["MIT"] + licence: + - "MIT" identifier: "" - input: - - meta: type: map @@ -42,7 +41,6 @@ output: type: file description: The used parameters to call HTODemux in the R-Script. pattern: "params_htodemux.csv" - ontologies: - edam: http://edamontology.org/format_3752 # CSV assignment: @@ -55,7 +53,6 @@ output: type: file description: Assignment results. pattern: "assignment_htodemux.csv" - ontologies: - edam: http://edamontology.org/format_3752 # CSV classification: @@ -68,7 +65,6 @@ output: type: file description: Classification results. pattern: "classification_htodemux.csv" - ontologies: - edam: http://edamontology.org/format_3752 # CSV rds: @@ -81,14 +77,20 @@ output: type: file description: SeuratObject saved as RDS. pattern: "htodemux.rds" - ontologies: [] versions: - versions.yml: type: file description: File containing software versions pattern: "versions.yml" - + ontologies: + - edam: http://edamontology.org/format_3750 # YAML +topics: + versions: + - versions.yml: + type: file + description: File containing software versions + pattern: "versions.yml" ontologies: - edam: http://edamontology.org/format_3750 # YAML authors: diff --git a/modules/nf-core/htodemux/templates/HTODemux.R b/modules/nf-core/htodemux/templates/HTODemux.R index b66976fe..790bb8fe 100755 --- a/modules/nf-core/htodemux/templates/HTODemux.R +++ b/modules/nf-core/htodemux/templates/HTODemux.R @@ -160,7 +160,7 @@ writeLines( c( '"${task.process}":', paste(' r-base:', r.version), - paste(' seurat:', seurat.version) + paste(' r-seurat:', seurat.version) ), 'versions.yml') diff --git a/modules/nf-core/htodemux/tests/main.nf.test b/modules/nf-core/htodemux/tests/main.nf.test index d42f4f83..811eb715 100644 --- a/modules/nf-core/htodemux/tests/main.nf.test +++ b/modules/nf-core/htodemux/tests/main.nf.test @@ -14,7 +14,7 @@ nextflow_process { process { """ input[0] = [ - [ id:'test'], // meta map + [ id:'test'], file(params.modules_testdata_base_path + '/genomics/homo_sapiens/10xgenomics/cellranger/hashing_demultiplexing/htodemux.rds', checkIfExists: true), "HTO" ] @@ -25,7 +25,10 @@ nextflow_process { then { assertAll( { assert process.success }, - { assert snapshot(process.out).match() } + { assert snapshot( + sanitizeOutput(process.out), + path(process.out.versions[0]).yaml + ).match() } ) } @@ -39,7 +42,7 @@ nextflow_process { process { """ input[0] = [ - [ id:'test'], // meta map + [ id:'test'], file(params.modules_testdata_base_path + '/genomics/homo_sapiens/10xgenomics/cellranger/hashing_demultiplexing/htodemux.rds', checkIfExists: true), "HTO" ] @@ -50,7 +53,10 @@ nextflow_process { then { assertAll( { assert process.success }, - { assert snapshot(process.out).match() } + { assert snapshot( + sanitizeOutput(process.out), + path(process.out.versions[0]).yaml + ).match() } ) } diff --git a/modules/nf-core/htodemux/tests/main.nf.test.snap b/modules/nf-core/htodemux/tests/main.nf.test.snap index f3be1eab..62c64df8 100644 --- a/modules/nf-core/htodemux/tests/main.nf.test.snap +++ b/modules/nf-core/htodemux/tests/main.nf.test.snap @@ -2,41 +2,6 @@ "seuratObject - rds": { "content": [ { - "0": [ - [ - { - "id": "test" - }, - "test_params_htodemux.csv:md5,d01208cd0d0b76548cc71de04e76f3d2" - ] - ], - "1": [ - [ - { - "id": "test" - }, - "test_assignment_htodemux.csv:md5,33d72b27c3d0b0f7394af18a18490a5c" - ] - ], - "2": [ - [ - { - "id": "test" - }, - "test_classification_htodemux.csv:md5,886f595f148d2ccdd5a9134b8eb815cb" - ] - ], - "3": [ - [ - { - "id": "test" - }, - "test_htodemux.rds:md5,ec43d2d0217c3a87529f1eb4d0e05ffb" - ] - ], - "4": [ - "versions.yml:md5,11469ff7805ab3e88363f2ececd7f4a3" - ], "assignment": [ [ { @@ -70,54 +35,25 @@ ] ], "versions": [ - "versions.yml:md5,11469ff7805ab3e88363f2ececd7f4a3" + "versions.yml:md5,c34406eb377dbda4e62a36a1fb78350b" ] + }, + { + "HTODEMUX": { + "r-base": "4.4.3", + "r-seurat": "5.3.0" + } } ], + "timestamp": "2026-05-27T16:21:53.353849501", "meta": { - "nf-test": "0.9.2", - "nextflow": "25.04.3" - }, - "timestamp": "2025-07-09T15:06:44.480302427" + "nf-test": "0.9.5", + "nextflow": "26.03.2" + } }, "seuratObject - rds - stub": { "content": [ { - "0": [ - [ - { - "id": "test" - }, - "test_params_htodemux.csv:md5,d41d8cd98f00b204e9800998ecf8427e" - ] - ], - "1": [ - [ - { - "id": "test" - }, - "test_assignment_htodemux.csv:md5,d41d8cd98f00b204e9800998ecf8427e" - ] - ], - "2": [ - [ - { - "id": "test" - }, - "test_classification_htodemux.csv:md5,d41d8cd98f00b204e9800998ecf8427e" - ] - ], - "3": [ - [ - { - "id": "test" - }, - "test_htodemux.rds:md5,d41d8cd98f00b204e9800998ecf8427e" - ] - ], - "4": [ - "versions.yml:md5,6ebabe2043aa21ba7c47cb07abaabba9" - ], "assignment": [ [ { @@ -151,14 +87,20 @@ ] ], "versions": [ - "versions.yml:md5,6ebabe2043aa21ba7c47cb07abaabba9" + "versions.yml:md5,c34406eb377dbda4e62a36a1fb78350b" ] + }, + { + "HTODEMUX": { + "r-base": "4.4.3", + "r-seurat": "5.3.0" + } } ], + "timestamp": "2026-05-27T16:22:22.136057404", "meta": { - "nf-test": "0.9.2", - "nextflow": "25.04.3" - }, - "timestamp": "2025-07-09T15:06:53.10583219" + "nf-test": "0.9.5", + "nextflow": "26.03.2" + } } } \ No newline at end of file diff --git a/modules/nf-core/multiseqdemux/main.nf b/modules/nf-core/multiseqdemux/main.nf index 0d33a745..d1cc2699 100644 --- a/modules/nf-core/multiseqdemux/main.nf +++ b/modules/nf-core/multiseqdemux/main.nf @@ -3,7 +3,7 @@ process MULTISEQDEMUX { label 'process_low' conda "${moduleDir}/environment.yml" - container "${ workflow.containerEngine == 'singularity' && !task.ext.singularity_pull_docker_container ? + container "${ workflow.containerEngine in ['singularity', 'apptainer'] && !task.ext.singularity_pull_docker_container ? 'https://community-cr-prod.seqera.io/docker/registry/v2/blobs/sha256/f9/f96b7927142847485eff858170a4cfd2d3924fb4f09de7043dd6677ac6acd09e/data': 'community.wave.seqera.io/library/r-seurat_r-seuratobject:b11306d1bdc82827' }" diff --git a/modules/nf-core/multiseqdemux/multiseqdemux.diff b/modules/nf-core/multiseqdemux/multiseqdemux.diff deleted file mode 100644 index 7cea8040..00000000 --- a/modules/nf-core/multiseqdemux/multiseqdemux.diff +++ /dev/null @@ -1,19 +0,0 @@ -Changes in component 'nf-core/multiseqdemux' -'modules/nf-core/multiseqdemux/main.nf' is unchanged -'modules/nf-core/multiseqdemux/environment.yml' is unchanged -Changes in 'multiseqdemux/meta.yml': ---- modules/nf-core/multiseqdemux/meta.yml -+++ modules/nf-core/multiseqdemux/meta.yml -@@ -32,7 +32,6 @@ - Name of the Hashtag assay, usually called "HTO" by default. Use the custom name if the assay has been named differently. - - output: -- - - params: - - meta: - type: map - -'modules/nf-core/multiseqdemux/templates/MultiSeqDemux.R' is unchanged -'modules/nf-core/multiseqdemux/tests/main.nf.test.snap' is unchanged -'modules/nf-core/multiseqdemux/tests/main.nf.test' is unchanged -************************************************************ diff --git a/modules/nf-core/popscle/demuxlet/meta.yml b/modules/nf-core/popscle/demuxlet/meta.yml index 1a8129ae..25804bf7 100644 --- a/modules/nf-core/popscle/demuxlet/meta.yml +++ b/modules/nf-core/popscle/demuxlet/meta.yml @@ -34,15 +34,17 @@ input: description: Input SAM/BAM/CRAM file without running popscle/dsc_pileup, must be sorted by coordinates and indexed. pattern: "*.{bam,cram,sam}" + ontologies: [] - donor_genotype: type: file description: Input VCF/BCF file, containing the individual genotypes (GT), posterior probability (GP), or genotype likelihood (PL) to assign each barcode to a specific sample (or a pair of samples) in the VCF file. pattern: "*.{vcf,bcf}" + ontologies: [] output: - - demuxlet_result: - - meta: + demuxlet_result: + - - meta: type: map description: | Groovy Map containing sample information @@ -52,11 +54,14 @@ output: description: Result of demuxlet containing the best guess of the sample identity, with detailed statistics to reach to the best guess. pattern: "*.best" - - versions: - - versions.yml: - type: file - description: File containing software versions - pattern: "versions.yml" + ontologies: [] + versions: + - versions.yml: + type: file + description: File containing software versions + pattern: "versions.yml" + ontologies: + - edam: http://edamontology.org/format_3750 # YAML authors: - "@mari-ga" - "@maxozo" diff --git a/modules/nf-core/popscle/demuxlet/popscle-demuxlet.diff b/modules/nf-core/popscle/demuxlet/popscle-demuxlet.diff index 7a1f9427..fa133d9c 100644 --- a/modules/nf-core/popscle/demuxlet/popscle-demuxlet.diff +++ b/modules/nf-core/popscle/demuxlet/popscle-demuxlet.diff @@ -1,13 +1,22 @@ Changes in component 'nf-core/popscle/demuxlet' +'modules/nf-core/popscle/demuxlet/environment.yml' is unchanged +'modules/nf-core/popscle/demuxlet/meta.yml' is unchanged Changes in 'popscle/demuxlet/main.nf': --- modules/nf-core/popscle/demuxlet/main.nf +++ modules/nf-core/popscle/demuxlet/main.nf -@@ -8,7 +8,7 @@ - 'biocontainers/popscle:0.1beta--h2c78cec_0' }" +@@ -3,12 +3,12 @@ + label 'process_medium' + + conda "${moduleDir}/environment.yml" +- container "${ workflow.containerEngine in ['singularity', 'apptainer'] && !task.ext.singularity_pull_docker_container ? ++ container "${ workflow.containerEngine == 'singularity' && !task.ext.singularity_pull_docker_container ? + 'https://depot.galaxyproject.org/singularity/popscle:0.1beta--h2c78cec_0' : +- 'quay.io/biocontainers/popscle:0.1beta--h2c78cec_0' }" ++ 'biocontainers/popscle:0.1beta--h2c78cec_0' }" input: - tuple val(meta), val(plp_prefix), path(bam), path(donor_genotype) -+ tuple val(meta), val(plp), path(bam), path(donor_genotype) ++ tuple val(meta), path(plp), path(bam), path(donor_genotype) output: tuple val(meta), path('*.best'), emit: demuxlet_result @@ -16,23 +25,11 @@ Changes in 'popscle/demuxlet/main.nf': def args = task.ext.args ?: '' def prefix = task.ext.prefix ?: "${meta.id}" - def input = plp_prefix ? "--plp ${plp_prefix}" : "--sam $bam" -+ def input = plp ? "--plp ${plp.toString() - '.plp.gz'}" : "--sam $bam" ++ def input = plp ? "--plp ${plp}/${prefix}" : "--sam $bam" def VERSION = '0.1' // WARN: Version information not provided by tool on CLI. Please update version string below when bumping container versions. """ -@@ -37,9 +37,7 @@ - """ - - stub: -- def args = task.ext.args ?: '' - def prefix = task.ext.prefix ?: "${meta.id}" -- def input = plp_prefix ? "--plp ${plp_prefix}" : "--sam $bam" - def VERSION = '0.1' // WARN: Version information not provided by tool on CLI. Please update version string below when bumping container versions. - """ - touch ${prefix}.best -'modules/nf-core/popscle/demuxlet/environment.yml' is unchanged -'modules/nf-core/popscle/demuxlet/meta.yml' is unchanged 'modules/nf-core/popscle/demuxlet/tests/main.nf.test.snap' is unchanged 'modules/nf-core/popscle/demuxlet/tests/nextflow.config' is unchanged 'modules/nf-core/popscle/demuxlet/tests/main.nf.test' is unchanged diff --git a/modules/nf-core/popscle/demuxlet/tests/main.nf.test b/modules/nf-core/popscle/demuxlet/tests/main.nf.test index 7a12a0c2..98a9e085 100644 --- a/modules/nf-core/popscle/demuxlet/tests/main.nf.test +++ b/modules/nf-core/popscle/demuxlet/tests/main.nf.test @@ -14,7 +14,7 @@ nextflow_process { test("demultiplexing - bam") { when { process { - """ + """ input[0] = [ [ id:'sample1'], [], @@ -36,7 +36,7 @@ nextflow_process { } test ("demultiplexing - bam - pileup"){ - setup { + setup { run("POPSCLE_DSCPILEUP") { script "../../dscpileup/main.nf" process { @@ -54,10 +54,10 @@ nextflow_process { when { process { """ - input[0] = POPSCLE_DSCPILEUP.out.plp.collect{ meta, plp -> plp }.map{ - plp -> [[ id: 'sample1'], - plp[0].toString() - '.plp.gz', - [], + input[0] = POPSCLE_DSCPILEUP.out.plp.collect{ meta, plp -> plp }.map{ + plp -> [[ id: 'sample1'], + plp[0].toString() - '.plp.gz', + [], file(params.modules_testdata_base_path + '/genomics/homo_sapiens/demultiplexing/donor_genotype_chr21.vcf', checkIfExists: true)]} """ } @@ -70,7 +70,7 @@ nextflow_process { { assert path(process.out.demuxlet_result.get(0).get(1)).exists() }, ) } - + } test("demultiplexing - bam - stub") { @@ -79,7 +79,7 @@ nextflow_process { when { process { - """ + """ input[0] = [ [ id:'sample1' ], [], diff --git a/modules/nf-core/popscle/dscpileup/meta.yml b/modules/nf-core/popscle/dscpileup/meta.yml index 3c95caa5..d43dc341 100644 --- a/modules/nf-core/popscle/dscpileup/meta.yml +++ b/modules/nf-core/popscle/dscpileup/meta.yml @@ -31,14 +31,16 @@ input: description: Input SAM/BAM/CRAM file produced by the standard 10x sequencing platform, or any other barcoded single cell RNA-seq. pattern: "*.{bam,cram,sam}" + ontologies: [] - vcf: type: file description: Input VCF/BCF file files containing (AC) and (AN) from referenced population (e.g. 1000g). pattern: "*.{vcf,bcf}" + ontologies: [] output: - - cel: - - meta: + cel: + - - meta: type: map description: | Groovy Map containing sample information @@ -48,8 +50,10 @@ output: description: Contains the relation between numerated barcode ID and barcode and the number of SNP and number of UMI for each barcoded droplet. pattern: "*.cel.gz" - - plp: - - meta: + ontologies: + - edam: http://edamontology.org/format_3989 # GZIP format + plp: + - - meta: type: map description: | Groovy Map containing sample information @@ -59,19 +63,23 @@ output: description: Contains the overlapping SNP and the corresponding read and base quality for each barcode ID. pattern: "*.plp.gz" - - var: - - meta: + ontologies: + - edam: http://edamontology.org/format_3989 # GZIP format + var: + - - meta: type: map description: | Groovy Map containing sample information e.g. `[ id:'sample1']` - "*.var.gz": type: file - description: Contains the position, reference allele and allele frequency for - each SNP. + description: Contains the position, reference allele and allele frequency + for each SNP. pattern: "*.var.gz" - - umi: - - meta: + ontologies: + - edam: http://edamontology.org/format_3989 # GZIP format + umi: + - - meta: type: map description: | Groovy Map containing sample information @@ -80,11 +88,15 @@ output: type: file description: Contains the position covered by each umi. pattern: "*.umi.gz" - - versions: - - versions.yml: - type: file - description: File containing software versions - pattern: "versions.yml" + ontologies: + - edam: http://edamontology.org/format_3989 # GZIP format + versions: + - versions.yml: + type: file + description: File containing software versions + pattern: "versions.yml" + ontologies: + - edam: http://edamontology.org/format_3750 # YAML authors: - "@mari-ga" - "@maxozo" diff --git a/modules/nf-core/popscle/dscpileup/popscle-dscpileup.diff b/modules/nf-core/popscle/dscpileup/popscle-dscpileup.diff index 989cde17..d20e4268 100644 --- a/modules/nf-core/popscle/dscpileup/popscle-dscpileup.diff +++ b/modules/nf-core/popscle/dscpileup/popscle-dscpileup.diff @@ -1,17 +1,19 @@ Changes in component 'nf-core/popscle/dscpileup' +'modules/nf-core/popscle/dscpileup/environment.yml' is unchanged +'modules/nf-core/popscle/dscpileup/meta.yml' is unchanged Changes in 'popscle/dscpileup/main.nf': --- modules/nf-core/popscle/dscpileup/main.nf +++ modules/nf-core/popscle/dscpileup/main.nf -@@ -1,57 +1,61 @@ +@@ -1,56 +1,61 @@ process POPSCLE_DSCPILEUP { - tag "$meta.id" + tag "${meta.id}" label 'process_medium' conda "${moduleDir}/environment.yml" -- container "${ workflow.containerEngine == 'singularity' && !task.ext.singularity_pull_docker_container ? +- container "${ workflow.containerEngine in ['singularity', 'apptainer'] && !task.ext.singularity_pull_docker_container ? - 'https://depot.galaxyproject.org/singularity/popscle:0.1beta--h2c78cec_0' : -- 'biocontainers/popscle:0.1beta--h2c78cec_0' }" +- 'quay.io/biocontainers/popscle:0.1beta--h2c78cec_0' }" + container "${workflow.containerEngine == 'singularity' && !task.ext.singularity_pull_docker_container + ? 'https://depot.galaxyproject.org/singularity/popscle:0.1beta--h2c78cec_0' + : 'biocontainers/popscle:0.1beta--h2c78cec_0'}" @@ -64,7 +66,6 @@ Changes in 'popscle/dscpileup/main.nf': """ stub: -- def args = task.ext.args ?: '' - def prefix = task.ext.prefix ?: "${meta.id}" - def VERSION = '0.1' // WARN: Version information not provided by tool on CLI. Please update version string below when bumping container versions. + prefix = task.ext.prefix ?: "${meta.id}" @@ -72,10 +73,10 @@ Changes in 'popscle/dscpileup/main.nf': + // WARN: Version information not provided by tool on CLI. Please update version string below when bumping container versions. """ -- touch ${prefix}.cel.gz -- touch ${prefix}.var.gz -- touch ${prefix}.plp.gz -- touch ${prefix}.umi.gz +- echo "" | gzip > ${prefix}.cel.gz +- echo "" | gzip > ${prefix}.var.gz +- echo "" | gzip > ${prefix}.plp.gz +- echo "" | gzip > ${prefix}.umi.gz + mkdir -p ${prefix} + touch ${prefix}/${prefix}.cel.gz + touch ${prefix}/${prefix}.var.gz @@ -90,8 +91,6 @@ Changes in 'popscle/dscpileup/main.nf': """ } -'modules/nf-core/popscle/dscpileup/environment.yml' is unchanged -'modules/nf-core/popscle/dscpileup/meta.yml' is unchanged 'modules/nf-core/popscle/dscpileup/tests/main.nf.test.snap' is unchanged 'modules/nf-core/popscle/dscpileup/tests/main.nf.test' is unchanged ************************************************************ diff --git a/modules/nf-core/popscle/dscpileup/tests/main.nf.test b/modules/nf-core/popscle/dscpileup/tests/main.nf.test index 7c3334b3..3af11552 100644 --- a/modules/nf-core/popscle/dscpileup/tests/main.nf.test +++ b/modules/nf-core/popscle/dscpileup/tests/main.nf.test @@ -42,7 +42,7 @@ nextflow_process { when { process { - """ + """ input[0] = [ [ id:'sample1' ], // meta map file(params.modules_testdata_base_path + '/genomics/homo_sapiens/demultiplexing/chr21.bam', checkIfExists: true), diff --git a/modules/nf-core/popscle/freemuxlet/main.nf b/modules/nf-core/popscle/freemuxlet/main.nf index d94d36a1..2e10918b 100644 --- a/modules/nf-core/popscle/freemuxlet/main.nf +++ b/modules/nf-core/popscle/freemuxlet/main.nf @@ -3,9 +3,9 @@ process POPSCLE_FREEMUXLET { label 'process_high' conda "${moduleDir}/environment.yml" - container "${ workflow.containerEngine == 'singularity' && !task.ext.singularity_pull_docker_container ? + container "${ workflow.containerEngine in ['singularity', 'apptainer'] && !task.ext.singularity_pull_docker_container ? 'https://depot.galaxyproject.org/singularity/popscle:0.1beta--h2c78cec_0' : - 'biocontainers/popscle:0.1beta--h2c78cec_0' }" + 'quay.io/biocontainers/popscle:0.1beta--h2c78cec_0' }" input: tuple val(meta), path(plp), val(n_sample) @@ -44,13 +44,13 @@ process POPSCLE_FREEMUXLET { def VERSION = '0.1' // WARN: Version information not provided by tool on CLI. Please update version string below when bumping container versions. """ - touch ${prefix}.clust1.samples.gz - touch ${prefix}.clust1.vcf.gz + echo | gzip > ${prefix}.clust1.samples.gz + echo | gzip > ${prefix}.clust1.vcf.gz touch ${prefix}.lmix if [[ "$args" == *"--aux-files"* ]]; then - touch ${prefix}.clust0.samples.gz - touch ${prefix}.clust0.vcf.gz + echo | gzip > ${prefix}.clust0.samples.gz + echo | gzip > ${prefix}.clust0.vcf.gz fi cat <<-END_VERSIONS > versions.yml diff --git a/modules/nf-core/popscle/freemuxlet/meta.yml b/modules/nf-core/popscle/freemuxlet/meta.yml index ff28e1dc..33db6154 100644 --- a/modules/nf-core/popscle/freemuxlet/meta.yml +++ b/modules/nf-core/popscle/freemuxlet/meta.yml @@ -33,8 +33,8 @@ input: type: integer description: Number of samples multiplexed together. output: - - result: - - meta: + result: + - - meta: type: map description: | Groovy Map containing sample information @@ -44,8 +44,10 @@ output: description: Output file contains the best guess of the sample identity, with detailed statistics to reach to the best guess. pattern: "*.clust1.samples.gz" - - vcf: - - meta: + ontologies: + - edam: http://edamontology.org/format_3989 # GZIP format + vcf: + - - meta: type: map description: | Groovy Map containing sample information @@ -54,8 +56,10 @@ output: type: file description: Output vcf file for each sample inferred and clustered from freemuxlet. pattern: "*.clust1.vcf.gz" - - lmix: - - meta: + ontologies: + - edam: http://edamontology.org/format_3989 # GZIP format + lmix: + - - meta: type: map description: | Groovy Map containing sample information @@ -64,8 +68,9 @@ output: type: file description: Output file contains basic statistics for each barcode. pattern: "*.lmix" - - singlet_result: - - meta: + ontologies: [] + singlet_result: + - - meta: type: map description: | Groovy Map containing sample information @@ -73,10 +78,13 @@ output: - "*.clust0.samples.gz": type: file description: Optional output file contains the best sample identity assuming - all droplets are singlets when writing auxiliary output files is turned on. + all droplets are singlets when writing auxiliary output files is turned + on. pattern: "*.clust0.samples.gz" - - singlet_vcf: - - meta: + ontologies: + - edam: http://edamontology.org/format_3989 # GZIP format + singlet_vcf: + - - meta: type: map description: | Groovy Map containing sample information @@ -87,11 +95,15 @@ output: from freemuxlet assuming all droplets are singlets when writing auxiliary output files is turned on. pattern: "*.clust0.vcf.gz" - - versions: - - versions.yml: - type: file - description: File containing software versions - pattern: "versions.yml" + ontologies: + - edam: http://edamontology.org/format_3989 # GZIP format + versions: + - versions.yml: + type: file + description: File containing software versions + pattern: "versions.yml" + ontologies: + - edam: http://edamontology.org/format_3750 # YAML authors: - "@wxicu" maintainers: diff --git a/modules/nf-core/popscle/freemuxlet/tests/main.nf.test b/modules/nf-core/popscle/freemuxlet/tests/main.nf.test index baf642e5..162d1327 100644 --- a/modules/nf-core/popscle/freemuxlet/tests/main.nf.test +++ b/modules/nf-core/popscle/freemuxlet/tests/main.nf.test @@ -11,7 +11,7 @@ nextflow_process { tag "popscle/freemuxlet" test("demultiplexing") { - setup { + setup { run("POPSCLE_DSCPILEUP") { script "../../dscpileup/main.nf" process { @@ -29,8 +29,8 @@ nextflow_process { when { process { """ - input[0] = POPSCLE_DSCPILEUP.out.plp.collect{ meta, plp -> plp }.map{ - plp -> [[ id: 'sample1'], + input[0] = POPSCLE_DSCPILEUP.out.plp.collect{ meta, plp -> plp }.map{ + plp -> [[ id: 'sample1'], plp[0].getParent(), 2 ]} """ @@ -52,7 +52,7 @@ nextflow_process { test("demultiplexing - auxiliary - files") { config "./nextflow.config" - setup { + setup { run("POPSCLE_DSCPILEUP") { script "../../dscpileup/main.nf" process { @@ -70,8 +70,8 @@ nextflow_process { when { process { """ - input[0] = POPSCLE_DSCPILEUP.out.plp.collect{ meta, plp -> plp }.map{ - plp -> [[ id: 'sample1'], + input[0] = POPSCLE_DSCPILEUP.out.plp.collect{ meta, plp -> plp }.map{ + plp -> [[ id: 'sample1'], plp[0].getParent(), 2 ]} """ @@ -96,8 +96,8 @@ nextflow_process { test("demultiplexing - stub") { options "-stub" - - setup { + + setup { run("POPSCLE_DSCPILEUP") { script "../../dscpileup/main.nf" process { @@ -115,8 +115,8 @@ nextflow_process { when { process { """ - input[0] = POPSCLE_DSCPILEUP.out.plp.collect{ meta, plp -> plp }.map{ - plp -> [[ id: 'sample1'], + input[0] = POPSCLE_DSCPILEUP.out.plp.collect{ meta, plp -> plp }.map{ + plp -> [[ id: 'sample1'], plp[0].toString() - '.plp.gz', 2 ]} """ @@ -140,7 +140,7 @@ nextflow_process { options "-stub" config "./nextflow.config" - setup { + setup { run("POPSCLE_DSCPILEUP") { script "../../dscpileup/main.nf" process { @@ -158,8 +158,8 @@ nextflow_process { when { process { """ - input[0] = POPSCLE_DSCPILEUP.out.plp.collect{ meta, plp -> plp }.map{ - plp -> [[ id: 'sample1'], + input[0] = POPSCLE_DSCPILEUP.out.plp.collect{ meta, plp -> plp }.map{ + plp -> [[ id: 'sample1'], plp[0].toString() - '.plp.gz', 2 ]} """ diff --git a/modules/nf-core/samtools/index/environment.yml b/modules/nf-core/samtools/index/environment.yml index 62054fc9..946bb362 100644 --- a/modules/nf-core/samtools/index/environment.yml +++ b/modules/nf-core/samtools/index/environment.yml @@ -4,5 +4,7 @@ channels: - conda-forge - bioconda dependencies: - - bioconda::htslib=1.21 - - bioconda::samtools=1.21 + # renovate: datasource=conda depName=bioconda/htslib + - bioconda::htslib=1.23.1 + # renovate: datasource=conda depName=bioconda/samtools + - bioconda::samtools=1.23.1 diff --git a/modules/nf-core/samtools/index/main.nf b/modules/nf-core/samtools/index/main.nf index 7019a72e..5b5756b1 100644 --- a/modules/nf-core/samtools/index/main.nf +++ b/modules/nf-core/samtools/index/main.nf @@ -1,20 +1,18 @@ process SAMTOOLS_INDEX { - tag "$meta.id" + tag "${meta.id}" label 'process_low' conda "${moduleDir}/environment.yml" - container "${ workflow.containerEngine == 'singularity' && !task.ext.singularity_pull_docker_container ? - 'https://depot.galaxyproject.org/singularity/samtools:1.21--h50ea8bc_0' : - 'biocontainers/samtools:1.21--h50ea8bc_0' }" + container "${workflow.containerEngine in ['singularity', 'apptainer'] && !task.ext.singularity_pull_docker_container + ? 'https://community-cr-prod.seqera.io/docker/registry/v2/blobs/sha256/8c/8c5d2818c8b9f58e1fba77ce219fdaf32087ae53e857c4a496402978af26e78c/data' + : 'community.wave.seqera.io/library/htslib_samtools:1.23.1--5b6bb4ede7e612e5'}" input: tuple val(meta), path(input) output: - tuple val(meta), path("*.bai") , optional:true, emit: bai - tuple val(meta), path("*.csi") , optional:true, emit: csi - tuple val(meta), path("*.crai"), optional:true, emit: crai - path "versions.yml" , emit: versions + tuple val(meta), path("*.{bai,csi,crai}"), emit: index + tuple val("${task.process}"), val('samtools'), eval("samtools version | sed '1!d;s/.* //'"), emit: versions_samtools, topic: versions when: task.ext.when == null || task.ext.when @@ -25,25 +23,16 @@ process SAMTOOLS_INDEX { samtools \\ index \\ -@ ${task.cpus} \\ - $args \\ - $input - - cat <<-END_VERSIONS > versions.yml - "${task.process}": - samtools: \$(echo \$(samtools --version 2>&1) | sed 's/^.*samtools //; s/Using.*\$//') - END_VERSIONS + ${args} \\ + ${input} """ stub: def args = task.ext.args ?: '' - def extension = file(input).getExtension() == 'cram' ? - "crai" : args.contains("-c") ? "csi" : "bai" + def extension = file(input).getExtension() == 'cram' + ? "crai" + : args.contains("-c") ? "csi" : "bai" """ touch ${input}.${extension} - - cat <<-END_VERSIONS > versions.yml - "${task.process}": - samtools: \$(echo \$(samtools --version 2>&1) | sed 's/^.*samtools //; s/Using.*\$//') - END_VERSIONS """ } diff --git a/modules/nf-core/samtools/index/meta.yml b/modules/nf-core/samtools/index/meta.yml index db8df0d5..d4938bcd 100644 --- a/modules/nf-core/samtools/index/meta.yml +++ b/modules/nf-core/samtools/index/meta.yml @@ -14,7 +14,8 @@ tools: homepage: http://www.htslib.org/ documentation: http://www.htslib.org/doc/samtools.html doi: 10.1093/bioinformatics/btp352 - licence: ["MIT"] + licence: + - "MIT" identifier: biotools:samtools input: - - meta: @@ -25,47 +26,45 @@ input: - input: type: file description: input file + ontologies: [] output: - - bai: - - meta: + index: + - - meta: type: map description: | Groovy Map containing sample information e.g. [ id:'test', single_end:false ] - - "*.bai": + - "*.{bai,csi,crai}": type: file description: BAM/CRAM/SAM index file - pattern: "*.{bai,crai,sai}" - - csi: - - meta: - type: map - description: | - Groovy Map containing sample information - e.g. [ id:'test', single_end:false ] - - "*.csi": - type: file - description: CSI index file - pattern: "*.{csi}" - - crai: - - meta: - type: map - description: | - Groovy Map containing sample information - e.g. [ id:'test', single_end:false ] - - "*.crai": - type: file - description: BAM/CRAM/SAM index file - pattern: "*.{bai,crai,sai}" - - versions: - - versions.yml: - type: file - description: File containing software versions - pattern: "versions.yml" + pattern: "*.{bai,csi,crai}" + ontologies: [] + versions_samtools: + - - ${task.process}: + type: string + description: The name of the process + - samtools: + type: string + description: The name of the tool + - samtools version | sed '1!d;s/.* //': + type: eval + description: The expression to obtain the version of the tool +topics: + versions: + - - ${task.process}: + type: string + description: The name of the process + - samtools: + type: string + description: The name of the tool + - samtools version | sed '1!d;s/.* //': + type: eval + description: The expression to obtain the version of the tool authors: - "@drpatelh" - "@ewels" - "@maxulysse" maintainers: - - "@drpatelh" - "@ewels" - "@maxulysse" + - "@matthdsm" diff --git a/modules/nf-core/samtools/index/tests/csi.nextflow.config b/modules/nf-core/samtools/index/tests/csi.nextflow.config index 0ed260ef..4af6d82c 100644 --- a/modules/nf-core/samtools/index/tests/csi.nextflow.config +++ b/modules/nf-core/samtools/index/tests/csi.nextflow.config @@ -3,5 +3,4 @@ process { withName: SAMTOOLS_INDEX { ext.args = '-c' } - } diff --git a/modules/nf-core/samtools/index/tests/main.nf.test b/modules/nf-core/samtools/index/tests/main.nf.test index ca34fb5c..f9d39222 100644 --- a/modules/nf-core/samtools/index/tests/main.nf.test +++ b/modules/nf-core/samtools/index/tests/main.nf.test @@ -23,7 +23,10 @@ nextflow_process { then { assertAll ( { assert process.success }, - { assert snapshot(process.out).match() } + { assert snapshot( + process.out.index, + process.out.findAll { key, val -> key.startsWith('versions') } + ).match() } ) } } @@ -43,7 +46,10 @@ nextflow_process { then { assertAll ( { assert process.success }, - { assert snapshot(process.out).match() } + { assert snapshot( + process.out.index, + process.out.findAll { key, val -> key.startsWith('versions') } + ).match() } ) } } @@ -66,8 +72,8 @@ nextflow_process { assertAll ( { assert process.success }, { assert snapshot( - file(process.out.csi[0][1]).name, - process.out.versions + file(process.out.index[0][1]).name, + process.out.findAll { key, val -> key.startsWith('versions') } ).match() } ) } @@ -89,7 +95,10 @@ nextflow_process { then { assertAll ( { assert process.success }, - { assert snapshot(process.out).match() } + { assert snapshot( + process.out.index, + process.out.findAll { key, val -> key.startsWith('versions') } + ).match() } ) } } @@ -110,7 +119,10 @@ nextflow_process { then { assertAll ( { assert process.success }, - { assert snapshot(process.out).match() } + { assert snapshot( + process.out.index, + process.out.findAll { key, val -> key.startsWith('versions') } + ).match() } ) } } @@ -133,7 +145,10 @@ nextflow_process { then { assertAll ( { assert process.success }, - { assert snapshot(process.out).match() } + { assert snapshot( + process.out.index, + process.out.findAll { key, val -> key.startsWith('versions') } + ).match() } ) } } diff --git a/modules/nf-core/samtools/index/tests/main.nf.test.snap b/modules/nf-core/samtools/index/tests/main.nf.test.snap index 72d65e81..337dec77 100644 --- a/modules/nf-core/samtools/index/tests/main.nf.test.snap +++ b/modules/nf-core/samtools/index/tests/main.nf.test.snap @@ -1,250 +1,156 @@ { "csi - stub": { "content": [ + [ + [ + { + "id": "test", + "single_end": false + }, + "test.paired_end.sorted.bam.csi:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], { - "0": [ - - ], - "1": [ - [ - { - "id": "test", - "single_end": false - }, - "test.paired_end.sorted.bam.csi:md5,d41d8cd98f00b204e9800998ecf8427e" - ] - ], - "2": [ - - ], - "3": [ - "versions.yml:md5,5e09a6fdf76de396728f877193d72315" - ], - "bai": [ - - ], - "crai": [ - - ], - "csi": [ + "versions_samtools": [ [ - { - "id": "test", - "single_end": false - }, - "test.paired_end.sorted.bam.csi:md5,d41d8cd98f00b204e9800998ecf8427e" + "SAMTOOLS_INDEX", + "samtools", + "1.23.1" ] - ], - "versions": [ - "versions.yml:md5,5e09a6fdf76de396728f877193d72315" ] } ], "meta": { - "nf-test": "0.9.0", - "nextflow": "24.04.4" + "nf-test": "0.9.3", + "nextflow": "25.10.4" }, - "timestamp": "2024-09-16T08:21:25.261127166" + "timestamp": "2026-03-19T09:00:39.171613" }, "crai - stub": { "content": [ + [ + [ + { + "id": "test", + "single_end": false + }, + "test.paired_end.recalibrated.sorted.cram.crai:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], { - "0": [ - - ], - "1": [ - - ], - "2": [ + "versions_samtools": [ [ - { - "id": "test", - "single_end": false - }, - "test.paired_end.recalibrated.sorted.cram.crai:md5,d41d8cd98f00b204e9800998ecf8427e" + "SAMTOOLS_INDEX", + "samtools", + "1.23.1" ] - ], - "3": [ - "versions.yml:md5,5e09a6fdf76de396728f877193d72315" - ], - "bai": [ - - ], - "crai": [ - [ - { - "id": "test", - "single_end": false - }, - "test.paired_end.recalibrated.sorted.cram.crai:md5,d41d8cd98f00b204e9800998ecf8427e" - ] - ], - "csi": [ - - ], - "versions": [ - "versions.yml:md5,5e09a6fdf76de396728f877193d72315" ] } ], "meta": { - "nf-test": "0.9.0", - "nextflow": "24.04.4" + "nf-test": "0.9.3", + "nextflow": "25.10.4" }, - "timestamp": "2024-09-16T08:21:12.653194876" + "timestamp": "2026-03-19T09:00:32.838795" }, "bai - stub": { "content": [ + [ + [ + { + "id": "test", + "single_end": false + }, + "test.paired_end.sorted.bam.bai:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], { - "0": [ + "versions_samtools": [ [ - { - "id": "test", - "single_end": false - }, - "test.paired_end.sorted.bam.bai:md5,d41d8cd98f00b204e9800998ecf8427e" + "SAMTOOLS_INDEX", + "samtools", + "1.23.1" ] - ], - "1": [ - - ], - "2": [ - - ], - "3": [ - "versions.yml:md5,5e09a6fdf76de396728f877193d72315" - ], - "bai": [ - [ - { - "id": "test", - "single_end": false - }, - "test.paired_end.sorted.bam.bai:md5,d41d8cd98f00b204e9800998ecf8427e" - ] - ], - "crai": [ - - ], - "csi": [ - - ], - "versions": [ - "versions.yml:md5,5e09a6fdf76de396728f877193d72315" ] } ], "meta": { - "nf-test": "0.9.0", - "nextflow": "24.04.4" + "nf-test": "0.9.3", + "nextflow": "25.10.4" }, - "timestamp": "2024-09-16T08:21:01.854932651" + "timestamp": "2026-03-19T09:00:25.255379" }, "csi": { "content": [ "test.paired_end.sorted.bam.csi", - [ - "versions.yml:md5,5e09a6fdf76de396728f877193d72315" - ] + { + "versions_samtools": [ + [ + "SAMTOOLS_INDEX", + "samtools", + "1.23.1" + ] + ] + } ], "meta": { - "nf-test": "0.9.0", - "nextflow": "24.04.4" + "nf-test": "0.9.3", + "nextflow": "25.10.4" }, - "timestamp": "2024-09-16T08:20:51.485364222" + "timestamp": "2026-03-19T09:00:18.414839" }, "crai": { "content": [ + [ + [ + { + "id": "test", + "single_end": false + }, + "test.paired_end.recalibrated.sorted.cram.crai:md5,14bc3bd5c89cacc8f4541f9062429029" + ] + ], { - "0": [ - - ], - "1": [ - - ], - "2": [ + "versions_samtools": [ [ - { - "id": "test", - "single_end": false - }, - "test.paired_end.recalibrated.sorted.cram.crai:md5,14bc3bd5c89cacc8f4541f9062429029" + "SAMTOOLS_INDEX", + "samtools", + "1.23.1" ] - ], - "3": [ - "versions.yml:md5,5e09a6fdf76de396728f877193d72315" - ], - "bai": [ - - ], - "crai": [ - [ - { - "id": "test", - "single_end": false - }, - "test.paired_end.recalibrated.sorted.cram.crai:md5,14bc3bd5c89cacc8f4541f9062429029" - ] - ], - "csi": [ - - ], - "versions": [ - "versions.yml:md5,5e09a6fdf76de396728f877193d72315" ] } ], "meta": { - "nf-test": "0.9.0", - "nextflow": "24.04.4" + "nf-test": "0.9.3", + "nextflow": "25.10.4" }, - "timestamp": "2024-09-16T08:20:40.518873972" + "timestamp": "2026-03-19T09:00:13.571297" }, "bai": { "content": [ + [ + [ + { + "id": "test", + "single_end": false + }, + "test.paired_end.sorted.bam.bai:md5,704c10dd1326482448ca3073fdebc2f4" + ] + ], { - "0": [ - [ - { - "id": "test", - "single_end": false - }, - "test.paired_end.sorted.bam.bai:md5,704c10dd1326482448ca3073fdebc2f4" - ] - ], - "1": [ - - ], - "2": [ - - ], - "3": [ - "versions.yml:md5,5e09a6fdf76de396728f877193d72315" - ], - "bai": [ + "versions_samtools": [ [ - { - "id": "test", - "single_end": false - }, - "test.paired_end.sorted.bam.bai:md5,704c10dd1326482448ca3073fdebc2f4" + "SAMTOOLS_INDEX", + "samtools", + "1.23.1" ] - ], - "crai": [ - - ], - "csi": [ - - ], - "versions": [ - "versions.yml:md5,5e09a6fdf76de396728f877193d72315" ] } ], "meta": { - "nf-test": "0.9.0", - "nextflow": "24.04.4" + "nf-test": "0.9.3", + "nextflow": "25.10.4" }, - "timestamp": "2024-09-16T08:20:21.184050361" + "timestamp": "2026-03-19T09:00:06.767362" } } \ No newline at end of file diff --git a/modules/nf-core/samtools/sort/environment.yml b/modules/nf-core/samtools/sort/environment.yml index 62054fc9..946bb362 100644 --- a/modules/nf-core/samtools/sort/environment.yml +++ b/modules/nf-core/samtools/sort/environment.yml @@ -4,5 +4,7 @@ channels: - conda-forge - bioconda dependencies: - - bioconda::htslib=1.21 - - bioconda::samtools=1.21 + # renovate: datasource=conda depName=bioconda/htslib + - bioconda::htslib=1.23.1 + # renovate: datasource=conda depName=bioconda/samtools + - bioconda::samtools=1.23.1 diff --git a/modules/nf-core/samtools/sort/main.nf b/modules/nf-core/samtools/sort/main.nf index caf3c61a..e75ce915 100644 --- a/modules/nf-core/samtools/sort/main.nf +++ b/modules/nf-core/samtools/sort/main.nf @@ -1,72 +1,97 @@ process SAMTOOLS_SORT { - tag "$meta.id" + tag "${meta.id}" label 'process_medium' conda "${moduleDir}/environment.yml" - container "${ workflow.containerEngine == 'singularity' && !task.ext.singularity_pull_docker_container ? - 'https://depot.galaxyproject.org/singularity/samtools:1.21--h50ea8bc_0' : - 'biocontainers/samtools:1.21--h50ea8bc_0' }" + container "${workflow.containerEngine in ['singularity', 'apptainer'] && !task.ext.singularity_pull_docker_container + ? 'https://community-cr-prod.seqera.io/docker/registry/v2/blobs/sha256/8c/8c5d2818c8b9f58e1fba77ce219fdaf32087ae53e857c4a496402978af26e78c/data' + : 'community.wave.seqera.io/library/htslib_samtools:1.23.1--5b6bb4ede7e612e5'}" input: - tuple val(meta) , path(bam) - tuple val(meta2), path(fasta) + tuple val(meta), path(bam) + tuple val(meta2), path(fasta), path(fai) + val index_format output: - tuple val(meta), path("*.bam"), emit: bam, optional: true - tuple val(meta), path("*.cram"), emit: cram, optional: true - tuple val(meta), path("*.crai"), emit: crai, optional: true - tuple val(meta), path("*.csi"), emit: csi, optional: true - path "versions.yml", emit: versions + tuple val(meta), path("${prefix}.bam"), emit: bam, optional: true + tuple val(meta), path("${prefix}.cram"), emit: cram, optional: true + tuple val(meta), path("${prefix}.sam"), emit: sam, optional: true + tuple val(meta), path("${prefix}.${extension}.{crai,csi,bai}"), emit: index, optional: true + tuple val("${task.process}"), val('samtools'), eval("samtools version | sed '1!d;s/.* //'"), topic: versions, emit: versions_samtools when: task.ext.when == null || task.ext.when script: def args = task.ext.args ?: '' - def prefix = task.ext.prefix ?: "${meta.id}" - def extension = args.contains("--output-fmt sam") ? "sam" : - args.contains("--output-fmt cram") ? "cram" : - "bam" + prefix = task.ext.prefix ?: "${meta.id}" + extension = args.contains("--output-fmt sam") + ? "sam" + : args.contains("--output-fmt cram") + ? "cram" + : "bam" def reference = fasta ? "--reference ${fasta}" : "" - if ("$bam" == "${prefix}.bam") error "Input and output names are the same, use \"task.ext.prefix\" to disambiguate!" + //setting default values + def write_index = "" + def output_file = "${prefix}.${extension}" + + // Update if index is requested + if (index_format != '' && index_format) { + write_index = "--write-index" + output_file = "${prefix}.${extension}##idx##${prefix}.${extension}.${index_format}" + } + def is_sam = (bam instanceof List ? bam[0] : bam).name.endsWith('.sam') + if (index_format) { + if (!index_format.matches('bai|csi|crai')) { + error("Index format not one of bai, csi, crai.") + } + else if (extension == "sam") { + error("Indexing not compatible with SAM output") + } + } + if ("${bam}" == "${prefix}.bam") { + error("Input and output names are the same, use \"task.ext.prefix\" to disambiguate!") + } + if ("${bam}" == "${prefix}.bam") { + error("Input and output names are the same, use \"task.ext.prefix\" to disambiguate!") + } + + def input_source = is_sam ? "${bam}" : "-" + def pre_command = is_sam ? "" : "samtools cat ${bam} | " """ - samtools cat \\ - ${bam} \\ - | \\ - samtools sort \\ - $args \\ + ${pre_command}samtools sort \\ + ${args} \\ -T ${prefix} \\ - --threads $task.cpus \\ + --threads ${task.cpus} \\ ${reference} \\ - -o ${prefix}.${extension} \\ - - - - cat <<-END_VERSIONS > versions.yml - "${task.process}": - samtools: \$(echo \$(samtools --version 2>&1) | sed 's/^.*samtools //; s/Using.*\$//') - END_VERSIONS + -o ${output_file} \\ + ${write_index} \\ + ${input_source} """ stub: def args = task.ext.args ?: '' - def prefix = task.ext.prefix ?: "${meta.id}" - def extension = args.contains("--output-fmt sam") ? "sam" : - args.contains("--output-fmt cram") ? "cram" : - "bam" + prefix = task.ext.prefix ?: "${meta.id}" + extension = args.contains("--output-fmt sam") + ? "sam" + : args.contains("--output-fmt cram") + ? "cram" + : "bam" + + if (index_format) { + if (!index_format.matches('bai|csi|crai')) { + error("Index format not one of bai, csi, crai.") + } + else if (extension == "sam") { + error("Indexing not compatible with SAM output") + } + } + + index = index_format ? "touch ${prefix}.${extension}.${index_format}" : "" + """ touch ${prefix}.${extension} - if [ "${extension}" == "bam" ]; - then - touch ${prefix}.${extension}.csi - elif [ "${extension}" == "cram" ]; - then - touch ${prefix}.${extension}.crai - fi - - cat <<-END_VERSIONS > versions.yml - "${task.process}": - samtools: \$(echo \$(samtools --version 2>&1) | sed 's/^.*samtools //; s/Using.*\$//') - END_VERSIONS + ${index} """ } diff --git a/modules/nf-core/samtools/sort/meta.yml b/modules/nf-core/samtools/sort/meta.yml index a9dbec5a..0447a95e 100644 --- a/modules/nf-core/samtools/sort/meta.yml +++ b/modules/nf-core/samtools/sort/meta.yml @@ -26,6 +26,7 @@ input: type: file description: BAM/CRAM/SAM file(s) pattern: "*.{bam,cram,sam}" + ontologies: [] - - meta2: type: map description: | @@ -36,52 +37,85 @@ input: description: Reference genome FASTA file pattern: "*.{fa,fasta,fna}" optional: true + ontologies: [] + - fai: + type: file + description: Reference genome FASTA index file + pattern: "*.{fai}" + optional: true + ontologies: [] + - index_format: + type: string + description: Index format to use (optional) + pattern: "bai|csi|crai" output: - - bam: - - meta: + bam: + - - meta: type: map description: | Groovy Map containing sample information e.g. [ id:'test', single_end:false ] - - "*.bam": + - "${prefix}.bam": type: file description: Sorted BAM file pattern: "*.{bam}" - - cram: - - meta: + ontologies: [] + cram: + - - meta: type: map description: | Groovy Map containing sample information e.g. [ id:'test', single_end:false ] - - "*.cram": + - "${prefix}.cram": type: file description: Sorted CRAM file pattern: "*.{cram}" - - crai: - - meta: + ontologies: [] + sam: + - - meta: type: map description: | Groovy Map containing sample information e.g. [ id:'test', single_end:false ] - - "*.crai": + - "${prefix}.sam": type: file - description: CRAM index file (optional) - pattern: "*.crai" - - csi: - - meta: + description: Sorted SAM file + pattern: "*.{sam}" + ontologies: [] + index: + - - meta: type: map description: | Groovy Map containing sample information e.g. [ id:'test', single_end:false ] - - "*.csi": + - "${prefix}.${extension}.{crai,csi,bai}": type: file - description: BAM index file (optional) - pattern: "*.csi" - - versions: - - versions.yml: - type: file - description: File containing software versions - pattern: "versions.yml" + description: CRAM index file (optional) + pattern: "*.{crai,csi,bai}" + ontologies: [] + versions_samtools: + - - ${task.process}: + type: string + description: The process the versions were collected from + - samtools: + type: string + description: The tool name + - "samtools version | sed '1!d;s/.* //'": + type: string + description: The command used to generate the version of the tool + +topics: + versions: + - - ${task.process}: + type: string + description: The process the versions were collected from + - samtools: + type: string + description: The tool name + - "samtools version | sed '1!d;s/.* //'": + type: string + description: The command used to generate the version of the tool + authors: - "@drpatelh" - "@ewels" diff --git a/modules/nf-core/samtools/sort/tests/main.nf.test b/modules/nf-core/samtools/sort/tests/main.nf.test index b05e6691..b60edf8c 100644 --- a/modules/nf-core/samtools/sort/tests/main.nf.test +++ b/modules/nf-core/samtools/sort/tests/main.nf.test @@ -8,7 +8,7 @@ nextflow_process { tag "samtools" tag "samtools/sort" - test("bam") { + test("bam_no_index") { config "./nextflow.config" @@ -21,8 +21,10 @@ nextflow_process { ]) input[1] = Channel.of([ [ id:'fasta' ], // meta map - file(params.modules_testdata_base_path + 'genomics/sarscov2/genome/genome.fasta', checkIfExists: true) + file(params.modules_testdata_base_path + 'genomics/sarscov2/genome/genome.fasta', checkIfExists: true), + file(params.modules_testdata_base_path + 'genomics/sarscov2/genome/genome.fasta.fai', checkIfExists: true) ]) + input[2] = '' """ } } @@ -32,8 +34,74 @@ nextflow_process { { assert process.success }, { assert snapshot( process.out.bam, - process.out.csi.collect { it.collect { it instanceof Map ? it : file(it).name } }, - process.out.versions + process.out.index, + process.out.findAll { key, val -> key.startsWith("versions") } + ).match()} + ) + } + } + + test("bam_bai_index") { + + config "./nextflow.config" + + when { + process { + """ + input[0] = Channel.of([ + [ id:'test', single_end:false ], // meta map + file(params.modules_testdata_base_path + 'genomics/sarscov2/illumina/bam/test.paired_end.bam', checkIfExists: true) + ]) + input[1] = Channel.of([ + [ id:'fasta' ], // meta map + file(params.modules_testdata_base_path + 'genomics/sarscov2/genome/genome.fasta', checkIfExists: true), + file(params.modules_testdata_base_path + 'genomics/sarscov2/genome/genome.fasta.fai', checkIfExists: true) + ]) + input[2] = 'bai' + """ + } + } + + then { + assertAll ( + { assert process.success }, + { assert snapshot( + process.out.bam, + process.out.index, + process.out.findAll { key, val -> key.startsWith("versions") } + ).match()} + ) + } + } + + test("bam_csi_index") { + + config "./nextflow.config" + + when { + process { + """ + input[0] = Channel.of([ + [ id:'test', single_end:false ], // meta map + file(params.modules_testdata_base_path + 'genomics/sarscov2/illumina/bam/test.paired_end.bam', checkIfExists: true) + ]) + input[1] = Channel.of([ + [ id:'fasta' ], // meta map + file(params.modules_testdata_base_path + 'genomics/sarscov2/genome/genome.fasta', checkIfExists: true), + file(params.modules_testdata_base_path + 'genomics/sarscov2/genome/genome.fasta.fai', checkIfExists: true) + ]) + input[2] = 'csi' + """ + } + } + + then { + assertAll ( + { assert process.success }, + { assert snapshot( + process.out.bam, + process.out.index, + process.out.findAll { key, val -> key.startsWith("versions") } ).match()} ) } @@ -55,8 +123,10 @@ nextflow_process { ]) input[1] = Channel.of([ [ id:'fasta' ], // meta map - file(params.modules_testdata_base_path + 'genomics/homo_sapiens/genome/genome.fasta', checkIfExists: true) + file(params.modules_testdata_base_path + 'genomics/homo_sapiens/genome/genome.fasta', checkIfExists: true), + file(params.modules_testdata_base_path + 'genomics/homo_sapiens/genome/genome.fasta.fai', checkIfExists: true) ]) + input[2] = '' """ } } @@ -66,8 +136,80 @@ nextflow_process { { assert process.success }, { assert snapshot( process.out.bam, - process.out.csi.collect { it.collect { it instanceof Map ? it : file(it).name } }, - process.out.versions + process.out.index.collect { it.collect { it instanceof Map ? it : file(it).name } }, + process.out.findAll { key, val -> key.startsWith("versions") } + ).match()} + ) + } + } + + test("multiple bam bai index") { + + config "./nextflow.config" + + when { + process { + """ + input[0] = Channel.of([ + [ id:'test', single_end:false ], // meta map + [ + file(params.modules_testdata_base_path + 'genomics/homo_sapiens/illumina/bam/test.paired_end.sorted.bam', checkIfExists: true), + file(params.modules_testdata_base_path + 'genomics/homo_sapiens/illumina/bam/test2.paired_end.sorted.bam', checkIfExists: true) + ] + ]) + input[1] = Channel.of([ + [ id:'fasta' ], // meta map + file(params.modules_testdata_base_path + 'genomics/homo_sapiens/genome/genome.fasta', checkIfExists: true), + file(params.modules_testdata_base_path + 'genomics/homo_sapiens/genome/genome.fasta.fai', checkIfExists: true) + ]) + input[2] = 'bai' + """ + } + } + + then { + assertAll ( + { assert process.success }, + { assert snapshot( + process.out.bam, + process.out.index.collect { it.collect { it instanceof Map ? it : file(it).name } }, + process.out.findAll { key, val -> key.startsWith("versions") } + ).match()} + ) + } + } + + test("multiple bam csi index") { + + config "./nextflow.config" + + when { + process { + """ + input[0] = Channel.of([ + [ id:'test', single_end:false ], // meta map + [ + file(params.modules_testdata_base_path + 'genomics/homo_sapiens/illumina/bam/test.paired_end.sorted.bam', checkIfExists: true), + file(params.modules_testdata_base_path + 'genomics/homo_sapiens/illumina/bam/test2.paired_end.sorted.bam', checkIfExists: true) + ] + ]) + input[1] = Channel.of([ + [ id:'fasta' ], // meta map + file(params.modules_testdata_base_path + 'genomics/homo_sapiens/genome/genome.fasta', checkIfExists: true), + file(params.modules_testdata_base_path + 'genomics/homo_sapiens/genome/genome.fasta.fai', checkIfExists: true) + ]) + input[2] = 'csi' + """ + } + } + + then { + assertAll ( + { assert process.success }, + { assert snapshot( + process.out.bam, + process.out.index.collect { it.collect { it instanceof Map ? it : file(it).name } }, + process.out.findAll { key, val -> key.startsWith("versions") } ).match()} ) } @@ -86,8 +228,10 @@ nextflow_process { ]) input[1] = Channel.of([ [ id:'fasta' ], // meta map - file(params.modules_testdata_base_path + 'genomics/homo_sapiens/genome/genome.fasta', checkIfExists: true) + file(params.modules_testdata_base_path + 'genomics/homo_sapiens/genome/genome.fasta', checkIfExists: true), + file(params.modules_testdata_base_path + 'genomics/homo_sapiens/genome/genome.fasta.fai', checkIfExists: true) ]) + input[2] = '' """ } } @@ -97,8 +241,8 @@ nextflow_process { { assert process.success }, { assert snapshot( process.out.cram.collect { it.collect { it instanceof Map ? it : file(it).name } }, - process.out.crai.collect { it.collect { it instanceof Map ? it : file(it).name } }, - process.out.versions + process.out.index.collect { it.collect { it instanceof Map ? it : file(it).name } }, + process.out.findAll { key, val -> key.startsWith("versions") } ).match()} ) } @@ -118,8 +262,10 @@ nextflow_process { ]) input[1] = Channel.of([ [ id:'fasta' ], // meta map - file(params.modules_testdata_base_path + 'genomics/sarscov2/genome/genome.fasta', checkIfExists: true) + file(params.modules_testdata_base_path + 'genomics/sarscov2/genome/genome.fasta', checkIfExists: true), + file(params.modules_testdata_base_path + 'genomics/sarscov2/genome/genome.fasta.fai', checkIfExists: true) ]) + input[2] = '' """ } } @@ -127,7 +273,7 @@ nextflow_process { then { assertAll ( { assert process.success }, - { assert snapshot(process.out).match() } + { assert snapshot(process.out.findAll { key, val -> key.startsWith("versions") }).match() } ) } } @@ -148,8 +294,10 @@ nextflow_process { ]) input[1] = Channel.of([ [ id:'fasta' ], // meta map - file(params.modules_testdata_base_path + 'genomics/homo_sapiens/genome/genome.fasta', checkIfExists: true) + file(params.modules_testdata_base_path + 'genomics/homo_sapiens/genome/genome.fasta', checkIfExists: true), + file(params.modules_testdata_base_path + 'genomics/homo_sapiens/genome/genome.fasta.fai', checkIfExists: true) ]) + input[2] = '' """ } } @@ -157,7 +305,7 @@ nextflow_process { then { assertAll ( { assert process.success }, - { assert snapshot(process.out).match() } + { assert snapshot(process.out.findAll { key, val -> key.startsWith("versions") }).match() } ) } } @@ -176,8 +324,10 @@ nextflow_process { ]) input[1] = Channel.of([ [ id:'fasta' ], // meta map - file(params.modules_testdata_base_path + 'genomics/homo_sapiens/genome/genome.fasta', checkIfExists: true) + file(params.modules_testdata_base_path + 'genomics/homo_sapiens/genome/genome.fasta', checkIfExists: true), + file(params.modules_testdata_base_path + 'genomics/homo_sapiens/genome/genome.fasta.fai', checkIfExists: true) ]) + input[2] = '' """ } } @@ -185,7 +335,75 @@ nextflow_process { then { assertAll ( { assert process.success }, - { assert snapshot(process.out).match() } + { assert snapshot(process.out.findAll { key, val -> key.startsWith("versions") }).match() } + ) + } + } + + test("multi_sam") { + + config "./nextflow_sam.config" + + when { + process { + """ + input[0] = Channel.of([ + [ id:'test', single_end:false ], // meta map + file(params.modules_testdata_base_path + 'genomics/homo_sapiens/pairtools/mock.sam', checkIfExists: true) + ]) + input[1] = Channel.of([ + [ id:'fasta' ], // meta map + file(params.modules_testdata_base_path + 'genomics/homo_sapiens/genome/genome.fasta', checkIfExists: true), + file(params.modules_testdata_base_path + 'genomics/homo_sapiens/genome/genome.fasta.fai', checkIfExists: true) + ]) + input[2] = '' + """ + } + } + + then { + assertAll ( + { assert process.success }, + { assert snapshot( + process.out.bam.collect { it.collect { it instanceof Map ? it : file(it).name } }, + process.out.index.collect { it.collect { it instanceof Map ? it : file(it).name } }, + process.out.findAll { key, val -> key.startsWith("versions") } + ).match()} + ) + } + } + + + test("sam") { + + config "./nextflow_sam.config" + + when { + process { + """ + input[0] = Channel.of([ + [ id:'test', single_end:false ], // meta map + file(params.modules_testdata_base_path + 'genomics/homo_sapiens/pairtools/mock.sam', checkIfExists: true), + file(params.modules_testdata_base_path + 'genomics/homo_sapiens/pairtools/mock.sam', checkIfExists: true) + ]) + input[1] = Channel.of([ + [ id:'fasta' ], // meta map + file(params.modules_testdata_base_path + 'genomics/homo_sapiens/genome/genome.fasta', checkIfExists: true), + file(params.modules_testdata_base_path + 'genomics/homo_sapiens/genome/genome.fasta.fai', checkIfExists: true) + ]) + input[2] = '' + """ + } + } + + then { + assertAll ( + { assert process.success }, + { assert snapshot( + process.out.bam.collect { it.collect { it instanceof Map ? it : file(it).name } }, + process.out.index.collect { it.collect { it instanceof Map ? it : file(it).name } }, + process.out.findAll { key, val -> key.startsWith("versions") } + ).match()} ) } } diff --git a/modules/nf-core/samtools/sort/tests/main.nf.test.snap b/modules/nf-core/samtools/sort/tests/main.nf.test.snap index 469891fe..5ce05c3c 100644 --- a/modules/nf-core/samtools/sort/tests/main.nf.test.snap +++ b/modules/nf-core/samtools/sort/tests/main.nf.test.snap @@ -19,147 +19,233 @@ "test.sorted.cram.crai" ] ], - [ - "versions.yml:md5,2659b187d681241451539d4c53500b9f" - ] + { + "versions_samtools": [ + [ + "SAMTOOLS_SORT", + "samtools", + "1.23.1" + ] + ] + } ], "meta": { - "nf-test": "0.9.0", - "nextflow": "24.04.4" + "nf-test": "0.9.3", + "nextflow": "25.10.4" }, - "timestamp": "2024-09-16T08:49:58.207549273" + "timestamp": "2026-03-19T09:04:36.491063" }, - "bam - stub": { + "bam_csi_index": { "content": [ + [ + [ + { + "id": "test", + "single_end": false + }, + "test.sorted.bam:md5,53aea06779611856bc481c60beabecaa" + ] + ], + [ + [ + { + "id": "test", + "single_end": false + }, + "test.sorted.bam.csi:md5,f77a4adb3dde616d7eafd28db2ed147c" + ] + ], { - "0": [ + "versions_samtools": [ [ - { - "id": "test", - "single_end": false - }, - "test.sorted.bam:md5,d41d8cd98f00b204e9800998ecf8427e" + "SAMTOOLS_SORT", + "samtools", + "1.23.1" ] - ], - "1": [ - - ], - "2": [ - - ], - "3": [ + ] + } + ], + "meta": { + "nf-test": "0.9.3", + "nextflow": "25.10.4" + }, + "timestamp": "2026-03-19T09:04:14.341977" + }, + "bam - stub": { + "content": [ + { + "versions_samtools": [ [ - { - "id": "test", - "single_end": false - }, - "test.sorted.bam.csi:md5,d41d8cd98f00b204e9800998ecf8427e" + "SAMTOOLS_SORT", + "samtools", + "1.23.1" ] - ], - "4": [ - "versions.yml:md5,2659b187d681241451539d4c53500b9f" - ], - "bam": [ + ] + } + ], + "meta": { + "nf-test": "0.9.3", + "nextflow": "25.10.4" + }, + "timestamp": "2026-03-19T09:04:43.558376" + }, + "multiple bam bai index": { + "content": [ + [ + [ + { + "id": "test", + "single_end": false + }, + "test.sorted.bam:md5,a15f775c655d4a3b080812a8aae84d34" + ] + ], + [ + [ + { + "id": "test", + "single_end": false + }, + "test.sorted.bam.bai" + ] + ], + { + "versions_samtools": [ [ - { - "id": "test", - "single_end": false - }, - "test.sorted.bam:md5,d41d8cd98f00b204e9800998ecf8427e" + "SAMTOOLS_SORT", + "samtools", + "1.23.1" ] - ], - "crai": [ - - ], - "cram": [ - - ], - "csi": [ + ] + } + ], + "meta": { + "nf-test": "0.9.3", + "nextflow": "25.10.4" + }, + "timestamp": "2026-03-19T09:04:25.647565" + }, + "cram - stub": { + "content": [ + { + "versions_samtools": [ [ - { - "id": "test", - "single_end": false - }, - "test.sorted.bam.csi:md5,d41d8cd98f00b204e9800998ecf8427e" + "SAMTOOLS_SORT", + "samtools", + "1.23.1" ] - ], - "versions": [ - "versions.yml:md5,2659b187d681241451539d4c53500b9f" ] } ], "meta": { - "nf-test": "0.9.0", - "nextflow": "24.04.4" + "nf-test": "0.9.3", + "nextflow": "25.10.4" }, - "timestamp": "2024-09-16T08:50:08.630951018" + "timestamp": "2026-03-19T09:04:54.684578" }, - "cram - stub": { + "multiple bam": { "content": [ + [ + [ + { + "id": "test", + "single_end": false + }, + "test.sorted.bam:md5,f4343475d9ed2c261f31e1e49d67c1b6" + ] + ], + [ + + ], { - "0": [ - - ], - "1": [ + "versions_samtools": [ [ - { - "id": "test", - "single_end": false - }, - "test.sorted.cram:md5,d41d8cd98f00b204e9800998ecf8427e" + "SAMTOOLS_SORT", + "samtools", + "1.23.1" ] - ], - "2": [ + ] + } + ], + "meta": { + "nf-test": "0.9.3", + "nextflow": "25.10.4" + }, + "timestamp": "2026-03-19T09:04:20.2368" + }, + "multiple bam - stub": { + "content": [ + { + "versions_samtools": [ [ - { - "id": "test", - "single_end": false - }, - "test.sorted.cram.crai:md5,d41d8cd98f00b204e9800998ecf8427e" + "SAMTOOLS_SORT", + "samtools", + "1.23.1" ] - ], - "3": [ - - ], - "4": [ - "versions.yml:md5,2659b187d681241451539d4c53500b9f" - ], - "bam": [ - - ], - "crai": [ + ] + } + ], + "meta": { + "nf-test": "0.9.3", + "nextflow": "25.10.4" + }, + "timestamp": "2026-03-19T09:04:48.874947" + }, + "bam_no_index": { + "content": [ + [ + [ + { + "id": "test", + "single_end": false + }, + "test.sorted.bam:md5,9277ba4bea590ae1b84e6ab06d11d79b" + ] + ], + [ + + ], + { + "versions_samtools": [ [ - { - "id": "test", - "single_end": false - }, - "test.sorted.cram.crai:md5,d41d8cd98f00b204e9800998ecf8427e" + "SAMTOOLS_SORT", + "samtools", + "1.23.1" ] - ], - "cram": [ + ] + } + ], + "meta": { + "nf-test": "0.9.3", + "nextflow": "25.10.4" + }, + "timestamp": "2026-03-19T09:04:03.721646" + }, + "multi_sam": { + "content": [ + [ + + ], + [ + + ], + { + "versions_samtools": [ [ - { - "id": "test", - "single_end": false - }, - "test.sorted.cram:md5,d41d8cd98f00b204e9800998ecf8427e" + "SAMTOOLS_SORT", + "samtools", + "1.23.1" ] - ], - "csi": [ - - ], - "versions": [ - "versions.yml:md5,2659b187d681241451539d4c53500b9f" ] } ], "meta": { - "nf-test": "0.9.0", - "nextflow": "24.04.4" + "nf-test": "0.9.3", + "nextflow": "25.10.4" }, - "timestamp": "2024-09-16T08:50:19.061912443" + "timestamp": "2026-03-19T09:05:00.624092" }, - "multiple bam": { + "multiple bam csi index": { "content": [ [ [ @@ -167,7 +253,7 @@ "id": "test", "single_end": false }, - "test.sorted.bam:md5,8a16ba90c7d294cbb4c33ac0f7127a12" + "test.sorted.bam:md5,f168809dc154156c40548c06d0f46791" ] ], [ @@ -179,82 +265,47 @@ "test.sorted.bam.csi" ] ], - [ - "versions.yml:md5,2659b187d681241451539d4c53500b9f" - ] + { + "versions_samtools": [ + [ + "SAMTOOLS_SORT", + "samtools", + "1.23.1" + ] + ] + } ], "meta": { - "nf-test": "0.9.0", - "nextflow": "24.09.0" + "nf-test": "0.9.3", + "nextflow": "25.10.4" }, - "timestamp": "2024-10-08T11:59:55.479443" + "timestamp": "2026-03-19T09:04:31.11865" }, - "multiple bam - stub": { + "sam": { "content": [ + [ + + ], + [ + + ], { - "0": [ + "versions_samtools": [ [ - { - "id": "test", - "single_end": false - }, - "test.sorted.bam:md5,8a16ba90c7d294cbb4c33ac0f7127a12" + "SAMTOOLS_SORT", + "samtools", + "1.23.1" ] - ], - "1": [ - - ], - "2": [ - - ], - "3": [ - [ - { - "id": "test", - "single_end": false - }, - "test.sorted.bam.csi:md5,d185916eaff9afeb4d0aeab3310371f9" - ] - ], - "4": [ - "versions.yml:md5,2659b187d681241451539d4c53500b9f" - ], - "bam": [ - [ - { - "id": "test", - "single_end": false - }, - "test.sorted.bam:md5,8a16ba90c7d294cbb4c33ac0f7127a12" - ] - ], - "crai": [ - - ], - "cram": [ - - ], - "csi": [ - [ - { - "id": "test", - "single_end": false - }, - "test.sorted.bam.csi:md5,d185916eaff9afeb4d0aeab3310371f9" - ] - ], - "versions": [ - "versions.yml:md5,2659b187d681241451539d4c53500b9f" ] } ], "meta": { - "nf-test": "0.9.0", - "nextflow": "24.09.0" + "nf-test": "0.9.3", + "nextflow": "25.10.4" }, - "timestamp": "2024-10-08T11:36:13.781404" + "timestamp": "2026-03-19T09:05:06.309319" }, - "bam": { + "bam_bai_index": { "content": [ [ [ @@ -262,7 +313,7 @@ "id": "test", "single_end": false }, - "test.sorted.bam:md5,34aa85e86abefe637f7a4a9887f016fc" + "test.sorted.bam:md5,2ca2d7f2368251d3f06f84afa49865a5" ] ], [ @@ -271,17 +322,23 @@ "id": "test", "single_end": false }, - "test.sorted.bam.csi" + "test.sorted.bam.bai:md5,66dca3dc2e314029035799f6f44f60d1" ] ], - [ - "versions.yml:md5,2659b187d681241451539d4c53500b9f" - ] + { + "versions_samtools": [ + [ + "SAMTOOLS_SORT", + "samtools", + "1.23.1" + ] + ] + } ], "meta": { - "nf-test": "0.9.0", - "nextflow": "24.09.0" + "nf-test": "0.9.3", + "nextflow": "25.10.4" }, - "timestamp": "2024-10-08T11:59:46.372244" + "timestamp": "2026-03-19T09:04:09.147615" } } \ No newline at end of file diff --git a/modules/nf-core/samtools/sort/tests/nextflow.config b/modules/nf-core/samtools/sort/tests/nextflow.config index f642771f..ca694caa 100644 --- a/modules/nf-core/samtools/sort/tests/nextflow.config +++ b/modules/nf-core/samtools/sort/tests/nextflow.config @@ -1,8 +1,6 @@ process { withName: SAMTOOLS_SORT { - ext.prefix = { "${meta.id}.sorted" } - ext.args = "--write-index" + ext.prefix = { "${meta.id}.sorted" } } - } diff --git a/modules/nf-core/samtools/sort/tests/nextflow_cram.config b/modules/nf-core/samtools/sort/tests/nextflow_cram.config index 3a8c0188..8ebc9d91 100644 --- a/modules/nf-core/samtools/sort/tests/nextflow_cram.config +++ b/modules/nf-core/samtools/sort/tests/nextflow_cram.config @@ -1,8 +1,7 @@ process { withName: SAMTOOLS_SORT { - ext.prefix = { "${meta.id}.sorted" } - ext.args = "--write-index --output-fmt cram" + ext.prefix = { "${meta.id}.sorted" } + ext.args = "--write-index --output-fmt cram" } - } diff --git a/modules/nf-core/samtools/sort/tests/nextflow_sam.config b/modules/nf-core/samtools/sort/tests/nextflow_sam.config new file mode 100644 index 00000000..29ee6a88 --- /dev/null +++ b/modules/nf-core/samtools/sort/tests/nextflow_sam.config @@ -0,0 +1,7 @@ +process { + + withName: SAMTOOLS_SORT { + ext.prefix = { "${meta.id}.sorted" } + ext.args = "--output-fmt sam" + } +} diff --git a/modules/nf-core/samtools/view/environment.yml b/modules/nf-core/samtools/view/environment.yml index 8cae5712..946bb362 100644 --- a/modules/nf-core/samtools/view/environment.yml +++ b/modules/nf-core/samtools/view/environment.yml @@ -5,5 +5,6 @@ channels: - bioconda dependencies: # renovate: datasource=conda depName=bioconda/htslib - - bioconda::htslib=1.21 - - bioconda::samtools=1.21 + - bioconda::htslib=1.23.1 + # renovate: datasource=conda depName=bioconda/samtools + - bioconda::samtools=1.23.1 diff --git a/modules/nf-core/samtools/view/main.nf b/modules/nf-core/samtools/view/main.nf index c34cab91..1823daab 100644 --- a/modules/nf-core/samtools/view/main.nf +++ b/modules/nf-core/samtools/view/main.nf @@ -1,28 +1,29 @@ process SAMTOOLS_VIEW { - tag "$meta.id" + tag "${meta.id}" label 'process_low' conda "${moduleDir}/environment.yml" - container "${ workflow.containerEngine == 'singularity' && !task.ext.singularity_pull_docker_container ? - 'https://depot.galaxyproject.org/singularity/samtools:1.21--h50ea8bc_0' : - 'biocontainers/samtools:1.21--h50ea8bc_0' }" + container "${workflow.containerEngine in ['singularity', 'apptainer'] && !task.ext.singularity_pull_docker_container + ? 'https://community-cr-prod.seqera.io/docker/registry/v2/blobs/sha256/8c/8c5d2818c8b9f58e1fba77ce219fdaf32087ae53e857c4a496402978af26e78c/data' + : 'community.wave.seqera.io/library/htslib_samtools:1.23.1--5b6bb4ede7e612e5'}" input: tuple val(meta), path(input), path(index) - tuple val(meta2), path(fasta) - path qname + tuple val(meta2), path(fasta), path(fai) + tuple val(meta3), path(qname) + tuple val(meta4), path(bed) val index_format output: - tuple val(meta), path("${prefix}.bam"), emit: bam, optional: true - tuple val(meta), path("${prefix}.cram"), emit: cram, optional: true - tuple val(meta), path("${prefix}.sam"), emit: sam, optional: true - tuple val(meta), path("${prefix}.${file_type}.bai"), emit: bai, optional: true - tuple val(meta), path("${prefix}.${file_type}.csi"), emit: csi, optional: true - tuple val(meta), path("${prefix}.${file_type}.crai"), emit: crai, optional: true - tuple val(meta), path("${prefix}.unselected.${file_type}"), emit: unselected, optional: true - tuple val(meta), path("${prefix}.unselected.${file_type}.{csi,crai}"), emit: unselected_index, optional: true - path "versions.yml", emit: versions + tuple val(meta), path("${prefix}.bam"), emit: bam, optional: true + tuple val(meta), path("${prefix}.cram"), emit: cram, optional: true + tuple val(meta), path("${prefix}.sam"), emit: sam, optional: true + tuple val(meta), path("${prefix}.${file_type}.bai"), emit: bai, optional: true + tuple val(meta), path("${prefix}.${file_type}.csi"), emit: csi, optional: true + tuple val(meta), path("${prefix}.${file_type}.crai"), emit: crai, optional: true + tuple val(meta), path("${prefix}.unselected.${file_type}"), emit: unselected, optional: true + tuple val(meta), path("${prefix}.unselected.${file_type}.{csi,crai}"), emit: unselected_index, optional: true + tuple val("${task.process}"), val('samtools'), eval('samtools version | sed "1!d;s/.* //"'), emit: versions_samtools, topic: versions when: task.ext.when == null || task.ext.when @@ -32,62 +33,71 @@ process SAMTOOLS_VIEW { def args2 = task.ext.args2 ?: '' prefix = task.ext.prefix ?: "${meta.id}" def reference = fasta ? "--reference ${fasta}" : "" - file_type = args.contains("--output-fmt sam") ? "sam" : - args.contains("--output-fmt bam") ? "bam" : - args.contains("--output-fmt cram") ? "cram" : - input.getExtension() + file_type = args.contains("--output-fmt sam") + ? "sam" + : args.contains("--output-fmt bam") + ? "bam" + : args.contains("--output-fmt cram") + ? "cram" + : input.getExtension() output_file = index_format ? "${prefix}.${file_type}##idx##${prefix}.${file_type}.${index_format} --write-index" : "${prefix}.${file_type}" // Can't choose index type of unselected file - readnames = qname ? "--qname-file ${qname} --output-unselected ${prefix}.unselected.${file_type}": "" + readnames = qname ? "--qname-file ${qname} --output-unselected ${prefix}.unselected.${file_type}" : "" + def bedfile = bed ? "-L ${bed}" : "" - if ("$input" == "${prefix}.${file_type}") error "Input and output names are the same, use \"task.ext.prefix\" to disambiguate!" + if ("${input}" == "${prefix}.${file_type}") { + error("Input and output names are the same, use \"task.ext.prefix\" to disambiguate!") + } if (index_format) { if (!index_format.matches('bai|csi|crai')) { - error "Index format not one of bai, csi, crai." - } else if (file_type == "sam") { - error "Indexing not compatible with SAM output" + error("Index format not one of bai, csi, crai.") + } + else if (file_type == "sam") { + error("Indexing not compatible with SAM output") } } """ # Note: --threads value represents *additional* CPUs to allocate (total CPUs = 1 + --threads). samtools \\ view \\ - --threads ${task.cpus-1} \\ + --threads ${task.cpus - 1} \\ ${reference} \\ ${readnames} \\ - $args \\ + ${bedfile} \\ + ${args} \\ -o ${output_file} \\ - $input \\ - $args2 - - cat <<-END_VERSIONS > versions.yml - "${task.process}": - samtools: \$(echo \$(samtools --version 2>&1) | sed 's/^.*samtools //; s/Using.*\$//') - END_VERSIONS + ${input} \\ + ${args2} """ stub: def args = task.ext.args ?: '' prefix = task.ext.prefix ?: "${meta.id}" - file_type = args.contains("--output-fmt sam") ? "sam" : - args.contains("--output-fmt bam") ? "bam" : - args.contains("--output-fmt cram") ? "cram" : - input.getExtension() - default_index_format = - file_type == "bam" ? "csi" : - file_type == "cram" ? "crai" : "" - index = index_format ? "touch ${prefix}.${file_type}.${index_format}" : args.contains("--write-index") ? "touch ${prefix}.${file_type}.${default_index_format}" : "" + file_type = args.contains("--output-fmt sam") + ? "sam" + : args.contains("--output-fmt bam") + ? "bam" + : args.contains("--output-fmt cram") + ? "cram" + : input.getExtension() + default_index_format = file_type == "bam" + ? "csi" + : file_type == "cram" ? "crai" : "" + index = index_format ? "touch ${prefix}.${file_type}.${index_format}" : args.contains("--write-index") ? "touch ${prefix}.${file_type}.${default_index_format}" : "" unselected = qname ? "touch ${prefix}.unselected.${file_type}" : "" // Can't choose index type of unselected file unselected_index = qname && (args.contains("--write-index") || index_format) ? "touch ${prefix}.unselected.${file_type}.${default_index_format}" : "" - if ("$input" == "${prefix}.${file_type}") error "Input and output names are the same, use \"task.ext.prefix\" to disambiguate!" + if ("${input}" == "${prefix}.${file_type}") { + error("Input and output names are the same, use \"task.ext.prefix\" to disambiguate!") + } if (index_format) { if (!index_format.matches('bai|csi|crai')) { - error "Index format not one of bai, csi, crai." - } else if (file_type == "sam") { - error "Indexing not compatible with SAM output." + error("Index format not one of bai, csi, crai.") + } + else if (file_type == "sam") { + error("Indexing not compatible with SAM output.") } } """ @@ -95,10 +105,5 @@ process SAMTOOLS_VIEW { ${index} ${unselected} ${unselected_index} - - cat <<-END_VERSIONS > versions.yml - "${task.process}": - samtools: \$(echo \$(samtools --version 2>&1) | sed 's/^.*samtools //; s/Using.*\$//') - END_VERSIONS """ } diff --git a/modules/nf-core/samtools/view/meta.yml b/modules/nf-core/samtools/view/meta.yml index 28c268a6..51e31834 100644 --- a/modules/nf-core/samtools/view/meta.yml +++ b/modules/nf-core/samtools/view/meta.yml @@ -26,30 +26,57 @@ input: type: file description: BAM/CRAM/SAM file pattern: "*.{bam,cram,sam}" + ontologies: [] - index: type: file description: BAM.BAI/BAM.CSI/CRAM.CRAI file (optional) pattern: "*.{.bai,.csi,.crai}" + ontologies: [] - - meta2: type: map description: | Groovy Map containing reference information - e.g. [ id:'test' ] + e.g. [ id:'genome' ] - fasta: type: file - description: Reference file the CRAM was created with (optional) + description: Fasta reference file pattern: "*.{fasta,fa}" - - - qname: + ontologies: + - edam: http://edamontology.org/format_1929 # FASTA + - fai: + type: file + description: Fasta reference file index + pattern: "*.{fai}" + ontologies: + - edam: http://edamontology.org/format_3326 # Index + - - meta3: + type: map + description: | + Groovy Map containing sample information + e.g. [ id:'test', single_end:false ] + - qname: type: file description: Optional file with read names to output only select alignments pattern: "*.{txt,list}" - - - index_format: - type: string - description: Index format, used together with ext.args = '--write-index' - pattern: "bai|csi|crai" + ontologies: [] + - - meta4: + type: map + description: | + Groovy Map containing sample information + e.g. [ id:'test', single_end:false ] + - bed: + type: file + description: Optional BED file for filtering alignments by genomic region (-L) + pattern: "*.{bed}" + ontologies: + - edam: http://edamontology.org/format_3003 # BED + - index_format: + type: string + description: Index format, used together with ext.args = '--write-index' + pattern: "bai|csi|crai" output: - - bam: - - meta: + bam: + - - meta: type: map description: | Groovy Map containing sample information @@ -58,8 +85,9 @@ output: type: file description: optional filtered/converted BAM file pattern: "*.{bam}" - - cram: - - meta: + ontologies: [] + cram: + - - meta: type: map description: | Groovy Map containing sample information @@ -68,8 +96,9 @@ output: type: file description: optional filtered/converted CRAM file pattern: "*.{cram}" - - sam: - - meta: + ontologies: [] + sam: + - - meta: type: map description: | Groovy Map containing sample information @@ -78,8 +107,9 @@ output: type: file description: optional filtered/converted SAM file pattern: "*.{sam}" - - bai: - - meta: + ontologies: [] + bai: + - - meta: type: map description: | Groovy Map containing sample information @@ -88,8 +118,9 @@ output: type: file description: optional BAM file index pattern: "*.{bai}" - - csi: - - meta: + ontologies: [] + csi: + - - meta: type: map description: | Groovy Map containing sample information @@ -98,8 +129,9 @@ output: type: file description: optional tabix BAM file index pattern: "*.{csi}" - - crai: - - meta: + ontologies: [] + crai: + - - meta: type: map description: | Groovy Map containing sample information @@ -108,8 +140,9 @@ output: type: file description: optional CRAM file index pattern: "*.{crai}" - - unselected: - - meta: + ontologies: [] + unselected: + - - meta: type: map description: | Groovy Map containing sample information @@ -118,8 +151,9 @@ output: type: file description: optional file with unselected alignments pattern: "*.unselected.{bam,cram,sam}" - - unselected_index: - - meta: + ontologies: [] + unselected_index: + - - meta: type: map description: | Groovy Map containing sample information @@ -128,11 +162,30 @@ output: type: file description: index for the "unselected" file pattern: "*.unselected.{csi,crai}" - - versions: - - versions.yml: - type: file - description: File containing software versions - pattern: "versions.yml" + ontologies: [] + versions_samtools: + - - ${task.process}: + type: string + description: Name of the process + - samtools: + type: string + description: Name of the tool + - samtools version | sed "1!d;s/.* //": + type: eval + description: The expression to obtain the version of the tool + +topics: + versions: + - - ${task.process}: + type: string + description: Name of the process + - samtools: + type: string + description: Name of the tool + - samtools version | sed "1!d;s/.* //": + type: eval + description: The expression to obtain the version of the tool + authors: - "@drpatelh" - "@joseespinosa" @@ -143,3 +196,4 @@ maintainers: - "@joseespinosa" - "@FriederikeHanssen" - "@priyanka-surana" + - "@matthdsm" diff --git a/modules/nf-core/samtools/view/tests/bam.config b/modules/nf-core/samtools/view/tests/bam.config deleted file mode 100644 index c10d1081..00000000 --- a/modules/nf-core/samtools/view/tests/bam.config +++ /dev/null @@ -1,3 +0,0 @@ -process { - ext.args = "--output-fmt bam" -} \ No newline at end of file diff --git a/modules/nf-core/samtools/view/tests/bam_index.config b/modules/nf-core/samtools/view/tests/bam_index.config deleted file mode 100644 index 771ae033..00000000 --- a/modules/nf-core/samtools/view/tests/bam_index.config +++ /dev/null @@ -1,3 +0,0 @@ -process { - ext.args = "--output-fmt bam --write-index" -} \ No newline at end of file diff --git a/modules/nf-core/samtools/view/tests/cram_index.config b/modules/nf-core/samtools/view/tests/cram_index.config deleted file mode 100644 index ed87c334..00000000 --- a/modules/nf-core/samtools/view/tests/cram_index.config +++ /dev/null @@ -1,3 +0,0 @@ -process { - ext.args = "--output-fmt cram --write-index" -} diff --git a/modules/nf-core/samtools/view/tests/main.nf.test b/modules/nf-core/samtools/view/tests/main.nf.test index d8551dd8..12faf1f1 100644 --- a/modules/nf-core/samtools/view/tests/main.nf.test +++ b/modules/nf-core/samtools/view/tests/main.nf.test @@ -2,6 +2,7 @@ nextflow_process { name "Test Process SAMTOOLS_VIEW" script "../main.nf" + config "./nextflow.config" process "SAMTOOLS_VIEW" tag "modules" @@ -10,18 +11,21 @@ nextflow_process { tag "samtools/view" test("bam") { - when { + params { + samtools_args = "" + } process { """ - input[0] = Channel.of([ - [ id:'test', single_end:false ], // meta map + input[0] = [ + [ id:'test' ], file(params.modules_testdata_base_path + 'genomics/sarscov2/illumina/bam/test.paired_end.bam', checkIfExists: true), [] - ]) - input[1] = [[],[]] - input[2] = [] - input[3] = [] + ] + input[1] = [[],[],[]] + input[2] = [[], []] + input[3] = [[], []] + input[4] = [] """ } } @@ -29,32 +33,27 @@ nextflow_process { then { assertAll( { assert process.success }, - { assert snapshot(file(process.out.bam[0][1]).name).match("bam_bam") }, - { assert snapshot(process.out.bai).match("bam_bai") }, - { assert snapshot(process.out.crai).match("bam_crai") }, - { assert snapshot(process.out.cram).match("bam_cram") }, - { assert snapshot(process.out.csi).match("bam_csi") }, - { assert snapshot(process.out.sam).match("bam_sam") }, - { assert snapshot(process.out.versions).match("bam_versions") } + { assert snapshot(sanitizeOutput(process.out, unstableKeys: ["bam"])).match()} ) } } test("bam_csi_index") { - - config "./bam_index.config" - when { + params { + samtools_args = "--output-fmt bam --write-index" + } process { """ - input[0] = Channel.of([ - [ id:'test', single_end:false ], // meta map + input[0] = [ + [ id:'test' ], file(params.modules_testdata_base_path + 'genomics/sarscov2/illumina/bam/test.paired_end.sorted.bam', checkIfExists: true), [] - ]) - input[1] = [[],[]] - input[2] = [] - input[3] = 'csi' + ] + input[1] = [[],[],[]] + input[2] = [[], []] + input[3] = [[], []] + input[4] = 'csi' """ } } @@ -62,30 +61,27 @@ nextflow_process { then { assertAll( { assert process.success }, - { assert snapshot( - file(process.out.bam[0][1]).name, - file(process.out.csi[0][1]).name, - process.out.versions).match() - } + { assert snapshot(sanitizeOutput(process.out, unstableKeys: ["bam", "csi"])).match()} ) } } test("bam_bai_index") { - - config "./bam_index.config" - when { + params { + samtools_args = "--output-fmt bam --write-index" + } process { """ - input[0] = Channel.of([ - [ id:'test', single_end:false ], // meta map + input[0] = [ + [ id:'test' ], file(params.modules_testdata_base_path + 'genomics/sarscov2/illumina/bam/test.paired_end.sorted.bam', checkIfExists: true), [] - ]) - input[1] = [[],[]] - input[2] = [] - input[3] = 'bai' + ] + input[1] = [[],[],[]] + input[2] = [[], []] + input[3] = [[], []] + input[4] = 'bai' """ } } @@ -93,30 +89,29 @@ nextflow_process { then { assertAll( { assert process.success }, - { assert snapshot( - file(process.out.bam[0][1]).name, - file(process.out.bai[0][1]).name, - process.out.versions).match() } + { assert snapshot(sanitizeOutput(process.out, unstableKeys: ["bam", "bai"])).match()} ) } } test("bam_bai_index_unselected") { - - config "./bam_index.config" - when { + params { + samtools_args = "--output-fmt bam --write-index" + } process { """ - input[0] = Channel.of([ - [ id:'test', single_end:false ], // meta map + input[0] = [ + [ id:'test' ], file(params.modules_testdata_base_path + 'genomics/sarscov2/illumina/bam/test.paired_end.sorted.bam', checkIfExists: true), [] - ]) - input[1] = [[],[]] + ] + input[1] = [[],[],[]] input[2] = Channel.of('testN:1') .collectFile(name: 'selected_reads.txt') - input[3] = 'bai' + .map { file -> [[:], file] } + input[3] = [[], []] + input[4] = 'bai' """ } } @@ -124,33 +119,29 @@ nextflow_process { then { assertAll( { assert process.success }, - { assert snapshot( - file(process.out.bam[0][1]).name, - file(process.out.bai[0][1]).name, - file(process.out.unselected[0][1]).name, - file(process.out.unselected_index[0][1]).name, - process.out.versions).match() - } + { assert snapshot(sanitizeOutput(process.out, unstableKeys: ["bam", "bai", "unselected", "unselected_index"])).match()} ) } } test("cram_crai_index_unselected") { - - config "./cram_index.config" - when { + params { + samtools_args = "--output-fmt cram --write-index" + } process { """ - input[0] = Channel.of([ - [ id:'test', single_end:false ], // meta map + input[0] = [ + [ id:'test' ], file(params.modules_testdata_base_path + 'genomics/sarscov2/illumina/bam/test.paired_end.sorted.bam', checkIfExists: true), [] - ]) - input[1] = [[],[]] + ] + input[1] = [[],[],[]] input[2] = Channel.of('testN:1') .collectFile(name: 'selected_reads.txt') - input[3] = 'crai' + .map { file -> [[:], file] } + input[3] = [[], []] + input[4] = 'crai' """ } } @@ -158,33 +149,31 @@ nextflow_process { then { assertAll( { assert process.success }, - { assert snapshot( - file(process.out.cram[0][1]).name, - file(process.out.crai[0][1]).name, - file(process.out.unselected[0][1]).name, - file(process.out.unselected_index[0][1]).name, - process.out.versions).match() - } + { assert snapshot(sanitizeOutput(process.out, unstableKeys: ["bam", "bai", "unselected", "unselected_index", "crai"])).match()} ) } } test("cram") { - when { + params { + samtools_args = "" + } process { """ - input[0] = Channel.of([ - [ id:'test', single_end:false ], // meta map + input[0] = [ + [ id:'test' ], file(params.modules_testdata_base_path + 'genomics/homo_sapiens/illumina/cram/test.paired_end.sorted.cram', checkIfExists: true), file(params.modules_testdata_base_path + 'genomics/homo_sapiens/illumina/cram/test.paired_end.sorted.cram.crai', checkIfExists: true) - ]) - input[1] = Channel.of([ - [ id:'genome' ], // meta map - file(params.modules_testdata_base_path + 'genomics/homo_sapiens/genome/genome.fasta', checkIfExists: true) - ]) - input[2] = [] - input[3] = [] + ] + input[1] = [ + [ id:'genome' ], + file(params.modules_testdata_base_path + 'genomics/homo_sapiens/genome/genome.fasta', checkIfExists: true), + file(params.modules_testdata_base_path + 'genomics/homo_sapiens/genome/genome.fasta.fai', checkIfExists: true) + ] + input[2] = [[], []] + input[3] = [[], []] + input[4] = [] """ } } @@ -192,35 +181,31 @@ nextflow_process { then { assertAll( { assert process.success }, - { assert snapshot(file(process.out.cram[0][1]).name).match("cram_cram") }, - { assert snapshot(process.out.bai).match("cram_bai") }, - { assert snapshot(process.out.bam).match("cram_bam") }, - { assert snapshot(process.out.crai).match("cram_crai") }, - { assert snapshot(process.out.csi).match("cram_csi") }, - { assert snapshot(process.out.sam).match("cram_sam") }, - { assert snapshot(process.out.versions).match("cram_versions") } + { assert snapshot(sanitizeOutput(process.out, unstableKeys: ["cram"])).match()} ) } } test("cram_to_bam") { - - config "./bam.config" - when { + params { + samtools_args = "--output-fmt bam" + } process { """ - input[0] = Channel.of([ - [ id:'test', single_end:false ], // meta map + input[0] = [ + [ id:'test' ], file(params.modules_testdata_base_path + 'genomics/homo_sapiens/illumina/cram/test.paired_end.sorted.cram', checkIfExists: true), [] - ]) - input[1] = Channel.of([ - [ id:'genome' ], // meta map - file(params.modules_testdata_base_path + 'genomics/homo_sapiens/genome/genome.fasta', checkIfExists: true) - ]) - input[2] = [] - input[3] = [] + ] + input[1] = [ + [ id:'genome' ], + file(params.modules_testdata_base_path + 'genomics/homo_sapiens/genome/genome.fasta', checkIfExists: true), + file(params.modules_testdata_base_path + 'genomics/homo_sapiens/genome/genome.fasta.fai', checkIfExists: true) + ] + input[2] = [[], []] + input[3] = [[], []] + input[4] = [] """ } } @@ -228,35 +213,31 @@ nextflow_process { then { assertAll( { assert process.success }, - { assert snapshot(file(process.out.bam[0][1]).name).match("cram_to_bam_bam") }, - { assert snapshot(process.out.bai).match("cram_to_bam_bai") }, - { assert snapshot(process.out.crai).match("cram_to_bam_crai") }, - { assert snapshot(process.out.cram).match("cram_to_bam_cram") }, - { assert snapshot(process.out.csi).match("cram_to_bam_csi") }, - { assert snapshot(process.out.sam).match("cram_to_bam_sam") }, - { assert snapshot(process.out.versions).match("cram_to_bam_versions") } + { assert snapshot(sanitizeOutput(process.out, unstableKeys: ["bam"])).match()} ) } } test("cram_to_bam_index") { - - config "./bam_index.config" - when { + params { + samtools_args = "--output-fmt bam --write-index" + } process { """ - input[0] = Channel.of([ - [ id:'test', single_end:false ], // meta map + input[0] = [ + [ id:'test' ], file(params.modules_testdata_base_path + 'genomics/homo_sapiens/illumina/cram/test.paired_end.sorted.cram', checkIfExists: true), [] - ]) - input[1] = Channel.of([ - [ id:'genome' ], // meta map - file(params.modules_testdata_base_path + 'genomics/homo_sapiens/genome/genome.fasta', checkIfExists: true) - ]) - input[2] = [] - input[3] = [] + ] + input[1] = [ + [ id:'genome' ], + file(params.modules_testdata_base_path + 'genomics/homo_sapiens/genome/genome.fasta', checkIfExists: true), + file(params.modules_testdata_base_path + 'genomics/homo_sapiens/genome/genome.fasta.fai', checkIfExists: true) + ] + input[2] = [[], []] + input[3] = [[], []] + input[4] = [] """ } } @@ -264,35 +245,64 @@ nextflow_process { then { assertAll( { assert process.success }, - { assert snapshot(file(process.out.bam[0][1]).name).match("cram_to_bam_index_bam") }, - { assert snapshot(file(process.out.csi[0][1]).name).match("cram_to_bam_index_csi") }, - { assert snapshot(process.out.bai).match("cram_to_bam_index_bai") }, - { assert snapshot(process.out.crai).match("cram_to_bam_index_crai") }, - { assert snapshot(process.out.cram).match("cram_to_bam_index_cram") }, - { assert snapshot(process.out.sam).match("cram_to_bam_index_sam") }, - { assert snapshot(process.out.versions).match("cram_to_bam_index_versions") } + { assert snapshot(sanitizeOutput(process.out, unstableKeys: ["bam", "csi"])).match()} ) } } test("cram_to_bam_index_qname") { + when { + params { + samtools_args = "--output-fmt bam --write-index" + } + process { + """ + input[0] = [ + [ id:'test' ], + file(params.modules_testdata_base_path + 'genomics/homo_sapiens/illumina/cram/test.paired_end.sorted.cram', checkIfExists: true), + [] + ] + input[1] = [ + [ id:'genome' ], + file(params.modules_testdata_base_path + 'genomics/homo_sapiens/genome/genome.fasta', checkIfExists: true), + file(params.modules_testdata_base_path + 'genomics/homo_sapiens/genome/genome.fasta.fai', checkIfExists: true) + ] + input[2] = Channel.of("testN:2817", "testN:2814") + .collectFile(name: "readnames.list", newLine: true) + .map { file -> [[:], file] } + input[3] = [[], []] + input[4] = [] + """ + } + } - config "./bam_index.config" + then { + assertAll( + { assert process.success }, + { assert snapshot(sanitizeOutput(process.out, unstableKeys: ["bam", "bai", "unselected", "unselected_index"])).match()} + ) + } + } + test("bam_with_bed") { when { + params { + samtools_args = "--output-fmt bam" + } process { """ - input[0] = Channel.of([ - [ id:'test', single_end:false ], // meta map - file(params.modules_testdata_base_path + 'genomics/homo_sapiens/illumina/cram/test.paired_end.sorted.cram', checkIfExists: true), + input[0] = [ + [ id:'test' ], + file(params.modules_testdata_base_path + 'genomics/sarscov2/illumina/bam/test.paired_end.sorted.bam', checkIfExists: true), [] - ]) - input[1] = Channel.of([ - [ id:'genome' ], // meta map - file(params.modules_testdata_base_path + 'genomics/homo_sapiens/genome/genome.fasta', checkIfExists: true) - ]) - input[2] = Channel.of("testN:2817", "testN:2814").collectFile(name: "readnames.list", newLine: true) - input[3] = [] + ] + input[1] = [[],[],[]] + input[2] = [[], []] + input[3] = [ + [:], + file(params.modules_testdata_base_path + 'genomics/sarscov2/genome/bed/test.bed', checkIfExists: true) + ] + input[4] = [] """ } } @@ -300,15 +310,7 @@ nextflow_process { then { assertAll( { assert process.success }, - { assert snapshot(file(process.out.bam[0][1]).name).match("cram_to_bam_index_qname_bam") }, - { assert snapshot(file(process.out.csi[0][1]).name).match("cram_to_bam_index_qname_csi") }, - { assert snapshot(process.out.bai).match("cram_to_bam_index_qname_bai") }, - { assert snapshot(process.out.crai).match("cram_to_bam_index_qname_crai") }, - { assert snapshot(process.out.cram).match("cram_to_bam_index_qname_cram") }, - { assert snapshot(process.out.sam).match("cram_to_bam_index_qname_sam") }, - { assert snapshot(file(process.out.unselected[0][1]).name).match("cram_to_bam_index_qname_unselected") }, - { assert snapshot(file(process.out.unselected_index[0][1]).name).match("cram_to_bam_index_qname_unselected_csi") }, - { assert snapshot(process.out.versions).match("cram_to_bam_index_qname_versions") } + { assert snapshot(sanitizeOutput(process.out, unstableKeys: ["bam"])).match()} ) } } @@ -316,19 +318,22 @@ nextflow_process { test("bam_stub") { options "-stub" - config "./bam_index.config" when { + params { + samtools_args = "--output-fmt bam --write-index" + } process { """ - input[0] = Channel.of([ - [ id:'test', single_end:false ], // meta map + input[0] = [ + [ id:'test' ], file(params.modules_testdata_base_path + 'genomics/sarscov2/illumina/bam/test.paired_end.bam', checkIfExists: true), [] - ]) - input[1] = [[],[]] - input[2] = [] - input[3] = [] + ] + input[1] = [[],[],[]] + input[2] = [[], []] + input[3] = [[], []] + input[4] = [] """ } } @@ -336,13 +341,7 @@ nextflow_process { then { assertAll( { assert process.success }, - { assert snapshot(file(process.out.bam[0][1]).name).match("bam_stub_bam") }, - { assert snapshot(file(process.out.csi[0][1]).name).match("bam_stub_csi") }, - { assert snapshot(process.out.bai).match("bam_stub_bai") }, - { assert snapshot(process.out.crai).match("bam_stub_crai") }, - { assert snapshot(process.out.cram).match("bam_stub_cram") }, - { assert snapshot(process.out.sam).match("bam_stub_sam") }, - { assert snapshot(process.out.versions).match("bam_stub_versions") } + { assert snapshot(sanitizeOutput(process.out, unstableKeys: ["bam", "csi"])).match()} ) } } @@ -350,19 +349,22 @@ nextflow_process { test("bam_csi_index - stub") { options "-stub" - config "./bam_index.config" when { + params { + samtools_args = "--output-fmt bam --write-index" + } process { """ - input[0] = Channel.of([ - [ id:'test', single_end:false ], // meta map + input[0] = [ + [ id:'test' ], file(params.modules_testdata_base_path + 'genomics/sarscov2/illumina/bam/test.paired_end.sorted.bam', checkIfExists: true), [] - ]) - input[1] = [[],[]] - input[2] = [] - input[3] = 'csi' + ] + input[1] = [[],[],[]] + input[2] = [[], []] + input[3] = [[], []] + input[4] = 'csi' """ } } @@ -378,19 +380,22 @@ nextflow_process { test("bam_bai_index - stub") { options "-stub" - config "./bam_index.config" when { + params { + samtools_args = "--output-fmt bam --write-index" + } process { """ - input[0] = Channel.of([ - [ id:'test', single_end:false ], // meta map + input[0] = [ + [ id:'test' ], file(params.modules_testdata_base_path + 'genomics/sarscov2/illumina/bam/test.paired_end.sorted.bam', checkIfExists: true), [] - ]) - input[1] = [[],[]] - input[2] = [] - input[3] = 'bai' + ] + input[1] = [[],[],[]] + input[2] = [[], []] + input[3] = [[], []] + input[4] = 'bai' """ } } @@ -406,20 +411,24 @@ nextflow_process { test("bam_bai_index_uselected - stub") { options "-stub" - config "./bam_index.config" when { + params { + samtools_args = "--output-fmt bam --write-index" + } process { """ - input[0] = Channel.of([ - [ id:'test', single_end:false ], // meta map + input[0] = [ + [ id:'test' ], file(params.modules_testdata_base_path + 'genomics/sarscov2/illumina/bam/test.paired_end.sorted.bam', checkIfExists: true), [] - ]) - input[1] = [[],[]] + ] + input[1] = [[],[],[]] input[2] = Channel.of('testN:1') .collectFile(name: 'selected_reads.txt') - input[3] = 'bai' + .map { file -> [[:], file] } + input[3] = [[], []] + input[4] = 'bai' """ } } @@ -435,20 +444,24 @@ nextflow_process { test("cram_crai_index_unselected - stub") { options "-stub" - config "./cram_index.config" when { + params { + samtools_args = "--output-fmt cram --write-index" + } process { """ - input[0] = Channel.of([ - [ id:'test', single_end:false ], // meta map + input[0] = [ + [ id:'test' ], file(params.modules_testdata_base_path + 'genomics/sarscov2/illumina/bam/test.paired_end.sorted.bam', checkIfExists: true), [] - ]) - input[1] = [[],[]] + ] + input[1] = [[],[],[]] input[2] = Channel.of('testN:1') .collectFile(name: 'selected_reads.txt') - input[3] = 'crai' + .map { file -> [[:], file] } + input[3] = [[], []] + input[4] = 'crai' """ } } diff --git a/modules/nf-core/samtools/view/tests/main.nf.test.snap b/modules/nf-core/samtools/view/tests/main.nf.test.snap index 1cb793f2..697c918d 100644 --- a/modules/nf-core/samtools/view/tests/main.nf.test.snap +++ b/modules/nf-core/samtools/view/tests/main.nf.test.snap @@ -1,139 +1,324 @@ { - "bam_bam": { + "cram_to_bam_index": { "content": [ - "test.bam" - ], - "meta": { - "nf-test": "0.8.4", - "nextflow": "23.04.3" - }, - "timestamp": "2024-02-12T19:37:51.256068" - }, - "bam_stub_bam": { - "content": [ - "test.bam" - ], - "meta": { - "nf-test": "0.8.4", - "nextflow": "23.04.3" - }, - "timestamp": "2024-02-12T19:38:32.065301" - }, - "bam_bai": { - "content": [ - [ - - ] - ], - "meta": { - "nf-test": "0.8.4", - "nextflow": "23.04.3" - }, - "timestamp": "2024-02-12T19:37:51.258578" - }, - "bam_stub_bai": { - "content": [ - [ - - ] - ], - "meta": { - "nf-test": "0.8.4", - "nextflow": "23.04.3" - }, - "timestamp": "2024-02-12T19:38:32.071284" - }, - "bam_stub_versions": { - "content": [ - [ - "versions.yml:md5,176db5ec46b965219604bcdbb3ef9e07" - ] - ], - "meta": { - "nf-test": "0.9.2", - "nextflow": "24.10.3" - }, - "timestamp": "2025-02-14T07:43:43.6526401" - }, - "cram_to_bam_index_qname_csi": { - "content": [ - "test.bam.csi" - ], - "meta": { - "nf-test": "0.8.4", - "nextflow": "23.04.3" - }, - "timestamp": "2024-02-12T19:38:23.325496" - }, - "cram_to_bam_index_qname_unselected_csi": { - "content": [ - "test.unselected.bam.csi" - ], - "meta": { - "nf-test": "0.8.4", - "nextflow": "23.04.3" - }, - "timestamp": "2024-02-12T19:38:23.328458" - }, - "bam_csi": { - "content": [ - [ - - ] + { + "bai": [ + + ], + "bam": [ + [ + { + "id": "test" + }, + "test.bam" + ] + ], + "crai": [ + + ], + "cram": [ + + ], + "csi": [ + [ + { + "id": "test" + }, + "test.bam.csi" + ] + ], + "sam": [ + + ], + "unselected": [ + + ], + "unselected_index": [ + + ], + "versions_samtools": [ + [ + "SAMTOOLS_VIEW", + "samtools", + "1.23.1" + ] + ] + } ], + "timestamp": "2026-03-19T09:06:44.475595", "meta": { - "nf-test": "0.8.4", - "nextflow": "23.04.3" - }, - "timestamp": "2024-02-12T19:37:51.262882" + "nf-test": "0.9.3", + "nextflow": "25.10.4" + } }, - "cram_to_bam_index_bam": { + "bam_csi_index - stub": { "content": [ - "test.bam" + { + "0": [ + [ + { + "id": "test" + }, + "test.bam:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "1": [ + + ], + "2": [ + + ], + "3": [ + + ], + "4": [ + [ + { + "id": "test" + }, + "test.bam.csi:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "5": [ + + ], + "6": [ + + ], + "7": [ + + ], + "8": [ + [ + "SAMTOOLS_VIEW", + "samtools", + "1.23.1" + ] + ], + "bai": [ + + ], + "bam": [ + [ + { + "id": "test" + }, + "test.bam:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "crai": [ + + ], + "cram": [ + + ], + "csi": [ + [ + { + "id": "test" + }, + "test.bam.csi:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "sam": [ + + ], + "unselected": [ + + ], + "unselected_index": [ + + ], + "versions_samtools": [ + [ + "SAMTOOLS_VIEW", + "samtools", + "1.23.1" + ] + ] + } ], + "timestamp": "2026-03-19T09:07:04.977559", "meta": { - "nf-test": "0.8.4", - "nextflow": "23.04.3" - }, - "timestamp": "2024-02-12T19:38:12.95456" + "nf-test": "0.9.3", + "nextflow": "25.10.4" + } }, - "cram_to_bam_index_versions": { + "bam_csi_index": { "content": [ - [ - "versions.yml:md5,176db5ec46b965219604bcdbb3ef9e07" - ] + { + "bai": [ + + ], + "bam": [ + [ + { + "id": "test" + }, + "test.bam" + ] + ], + "crai": [ + + ], + "cram": [ + + ], + "csi": [ + [ + { + "id": "test" + }, + "test.bam.csi" + ] + ], + "sam": [ + + ], + "unselected": [ + + ], + "unselected_index": [ + + ], + "versions_samtools": [ + [ + "SAMTOOLS_VIEW", + "samtools", + "1.23.1" + ] + ] + } ], + "timestamp": "2026-03-19T09:06:07.203393", "meta": { - "nf-test": "0.9.0", - "nextflow": "24.04.4" - }, - "timestamp": "2024-09-16T09:25:14.475388399" + "nf-test": "0.9.3", + "nextflow": "25.10.4" + } }, - "bam_csi_index": { + "cram_to_bam_index_qname": { "content": [ - "test.bam", - "test.bam.csi", - [ - "versions.yml:md5,176db5ec46b965219604bcdbb3ef9e07" - ] + { + "bai": [ + + ], + "bam": [ + [ + { + "id": "test" + }, + "test.bam" + ] + ], + "crai": [ + + ], + "cram": [ + + ], + "csi": [ + [ + { + "id": "test" + }, + "test.bam.csi:md5,15d725bced7ececd45b4312b2af99a6b" + ] + ], + "sam": [ + + ], + "unselected": [ + [ + { + "id": "test" + }, + "test.unselected.bam" + ] + ], + "unselected_index": [ + [ + { + "id": "test" + }, + "test.unselected.bam.csi" + ] + ], + "versions_samtools": [ + [ + "SAMTOOLS_VIEW", + "samtools", + "1.23.1" + ] + ] + } ], + "timestamp": "2026-03-19T09:06:52.242422", "meta": { - "nf-test": "0.9.2", - "nextflow": "24.10.3" - }, - "timestamp": "2025-02-14T07:45:19.718077276" + "nf-test": "0.9.3", + "nextflow": "25.10.4" + } }, - "bam_versions": { + "bam_bai_index_unselected": { "content": [ - [ - "versions.yml:md5,176db5ec46b965219604bcdbb3ef9e07" - ] + { + "bai": [ + [ + { + "id": "test" + }, + "test.bam.bai" + ] + ], + "bam": [ + [ + { + "id": "test" + }, + "test.bam" + ] + ], + "crai": [ + + ], + "cram": [ + + ], + "csi": [ + + ], + "sam": [ + + ], + "unselected": [ + [ + { + "id": "test" + }, + "test.unselected.bam" + ] + ], + "unselected_index": [ + [ + { + "id": "test" + }, + "test.unselected.bam.csi" + ] + ], + "versions_samtools": [ + [ + "SAMTOOLS_VIEW", + "samtools", + "1.23.1" + ] + ] + } ], + "timestamp": "2026-03-19T09:06:18.267421", "meta": { - "nf-test": "0.9.2", - "nextflow": "24.10.3" - }, - "timestamp": "2025-02-13T16:13:00.739468586" + "nf-test": "0.9.3", + "nextflow": "25.10.4" + } }, "cram_crai_index_unselected - stub": { "content": [ @@ -144,8 +329,7 @@ "1": [ [ { - "id": "test", - "single_end": false + "id": "test" }, "test.cram:md5,d41d8cd98f00b204e9800998ecf8427e" ] @@ -162,8 +346,7 @@ "5": [ [ { - "id": "test", - "single_end": false + "id": "test" }, "test.cram.crai:md5,d41d8cd98f00b204e9800998ecf8427e" ] @@ -171,8 +354,7 @@ "6": [ [ { - "id": "test", - "single_end": false + "id": "test" }, "test.unselected.cram:md5,d41d8cd98f00b204e9800998ecf8427e" ] @@ -180,14 +362,17 @@ "7": [ [ { - "id": "test", - "single_end": false + "id": "test" }, "test.unselected.cram.crai:md5,d41d8cd98f00b204e9800998ecf8427e" ] ], "8": [ - "versions.yml:md5,176db5ec46b965219604bcdbb3ef9e07" + [ + "SAMTOOLS_VIEW", + "samtools", + "1.23.1" + ] ], "bai": [ @@ -198,8 +383,7 @@ "crai": [ [ { - "id": "test", - "single_end": false + "id": "test" }, "test.cram.crai:md5,d41d8cd98f00b204e9800998ecf8427e" ] @@ -207,8 +391,7 @@ "cram": [ [ { - "id": "test", - "single_end": false + "id": "test" }, "test.cram:md5,d41d8cd98f00b204e9800998ecf8427e" ] @@ -222,8 +405,7 @@ "unselected": [ [ { - "id": "test", - "single_end": false + "id": "test" }, "test.unselected.cram:md5,d41d8cd98f00b204e9800998ecf8427e" ] @@ -231,147 +413,137 @@ "unselected_index": [ [ { - "id": "test", - "single_end": false + "id": "test" }, "test.unselected.cram.crai:md5,d41d8cd98f00b204e9800998ecf8427e" ] ], - "versions": [ - "versions.yml:md5,176db5ec46b965219604bcdbb3ef9e07" + "versions_samtools": [ + [ + "SAMTOOLS_VIEW", + "samtools", + "1.23.1" + ] ] } ], + "timestamp": "2026-03-19T09:07:27.864649", "meta": { - "nf-test": "0.9.2", - "nextflow": "24.10.3" - }, - "timestamp": "2025-02-14T07:47:20.903462221" - }, - "bam_crai": { - "content": [ - [ - - ] - ], - "meta": { - "nf-test": "0.8.4", - "nextflow": "23.04.3" - }, - "timestamp": "2024-02-12T19:37:51.259774" + "nf-test": "0.9.3", + "nextflow": "25.10.4" + } }, - "bam_cram": { - "content": [ - [ - - ] - ], - "meta": { - "nf-test": "0.8.4", - "nextflow": "23.04.3" - }, - "timestamp": "2024-02-12T19:37:51.261287" - }, - "cram_sam": { - "content": [ - [ - - ] - ], - "meta": { - "nf-test": "0.8.4", - "nextflow": "23.04.3" - }, - "timestamp": "2024-02-12T19:37:56.502625" - }, - "cram_versions": { - "content": [ - [ - "versions.yml:md5,176db5ec46b965219604bcdbb3ef9e07" - ] - ], - "meta": { - "nf-test": "0.9.2", - "nextflow": "24.10.3" - }, - "timestamp": "2025-02-13T16:33:28.319991831" - }, - "cram_to_bam_index_bai": { - "content": [ - [ - - ] - ], - "meta": { - "nf-test": "0.8.4", - "nextflow": "23.04.3" - }, - "timestamp": "2024-02-12T19:38:12.962863" - }, - "cram_to_bam_index_qname_sam": { - "content": [ - [ - - ] - ], - "meta": { - "nf-test": "0.8.4", - "nextflow": "23.04.3" - }, - "timestamp": "2024-02-12T19:38:23.337634" - }, - "bam_csi_index - stub": { + "bam": { "content": [ { - "0": [ + "bai": [ + + ], + "bam": [ [ { - "id": "test", - "single_end": false + "id": "test" }, - "test.bam:md5,d41d8cd98f00b204e9800998ecf8427e" + "test.bam" ] ], - "1": [ + "crai": [ ], - "2": [ + "cram": [ ], - "3": [ + "csi": [ ], - "4": [ + "sam": [ + + ], + "unselected": [ + + ], + "unselected_index": [ + + ], + "versions_samtools": [ + [ + "SAMTOOLS_VIEW", + "samtools", + "1.23.1" + ] + ] + } + ], + "timestamp": "2026-03-19T09:06:01.906355", + "meta": { + "nf-test": "0.9.3", + "nextflow": "25.10.4" + } + }, + "bam_bai_index": { + "content": [ + { + "bai": [ [ { - "id": "test", - "single_end": false + "id": "test" }, - "test.bam.csi:md5,d41d8cd98f00b204e9800998ecf8427e" + "test.bam.bai" ] ], - "5": [ + "bam": [ + [ + { + "id": "test" + }, + "test.bam" + ] + ], + "crai": [ ], - "6": [ + "cram": [ ], - "7": [ + "csi": [ ], - "8": [ - "versions.yml:md5,176db5ec46b965219604bcdbb3ef9e07" + "sam": [ + + ], + "unselected": [ + + ], + "unselected_index": [ + ], + "versions_samtools": [ + [ + "SAMTOOLS_VIEW", + "samtools", + "1.23.1" + ] + ] + } + ], + "timestamp": "2026-03-19T09:06:12.483533", + "meta": { + "nf-test": "0.9.3", + "nextflow": "25.10.4" + } + }, + "cram_to_bam": { + "content": [ + { "bai": [ ], "bam": [ [ { - "id": "test", - "single_end": false + "id": "test" }, - "test.bam:md5,d41d8cd98f00b204e9800998ecf8427e" + "test.bam" ] ], "crai": [ @@ -381,13 +553,7 @@ ], "csi": [ - [ - { - "id": "test", - "single_end": false - }, - "test.bam.csi:md5,d41d8cd98f00b204e9800998ecf8427e" - ] + ], "sam": [ @@ -398,52 +564,20 @@ "unselected_index": [ ], - "versions": [ - "versions.yml:md5,176db5ec46b965219604bcdbb3ef9e07" + "versions_samtools": [ + [ + "SAMTOOLS_VIEW", + "samtools", + "1.23.1" + ] ] } ], + "timestamp": "2026-03-19T09:06:37.592685", "meta": { - "nf-test": "0.9.2", - "nextflow": "24.10.3" - }, - "timestamp": "2025-02-14T07:46:52.477256747" - }, - "cram_to_bam_index_csi": { - "content": [ - "test.bam.csi" - ], - "meta": { - "nf-test": "0.8.4", - "nextflow": "23.04.3" - }, - "timestamp": "2024-02-12T19:38:12.958617" - }, - "bam_bai_index": { - "content": [ - "test.bam", - "test.bam.bai", - [ - "versions.yml:md5,176db5ec46b965219604bcdbb3ef9e07" - ] - ], - "meta": { - "nf-test": "0.9.2", - "nextflow": "24.10.3" - }, - "timestamp": "2025-02-14T07:45:29.205677197" - }, - "cram_to_bam_index_cram": { - "content": [ - [ - - ] - ], - "meta": { - "nf-test": "0.8.4", - "nextflow": "23.04.3" - }, - "timestamp": "2024-02-12T19:38:12.972288" + "nf-test": "0.9.3", + "nextflow": "25.10.4" + } }, "bam_bai_index - stub": { "content": [ @@ -451,8 +585,7 @@ "0": [ [ { - "id": "test", - "single_end": false + "id": "test" }, "test.bam:md5,d41d8cd98f00b204e9800998ecf8427e" ] @@ -466,8 +599,7 @@ "3": [ [ { - "id": "test", - "single_end": false + "id": "test" }, "test.bam.bai:md5,d41d8cd98f00b204e9800998ecf8427e" ] @@ -485,13 +617,16 @@ ], "8": [ - "versions.yml:md5,176db5ec46b965219604bcdbb3ef9e07" + [ + "SAMTOOLS_VIEW", + "samtools", + "1.23.1" + ] ], "bai": [ [ { - "id": "test", - "single_end": false + "id": "test" }, "test.bam.bai:md5,d41d8cd98f00b204e9800998ecf8427e" ] @@ -499,8 +634,7 @@ "bam": [ [ { - "id": "test", - "single_end": false + "id": "test" }, "test.bam:md5,d41d8cd98f00b204e9800998ecf8427e" ] @@ -523,306 +657,67 @@ "unselected_index": [ ], - "versions": [ - "versions.yml:md5,176db5ec46b965219604bcdbb3ef9e07" + "versions_samtools": [ + [ + "SAMTOOLS_VIEW", + "samtools", + "1.23.1" + ] ] } ], + "timestamp": "2026-03-19T09:07:12.214174", "meta": { - "nf-test": "0.9.2", - "nextflow": "24.10.3" - }, - "timestamp": "2025-02-14T07:51:10.220507926" - }, - "cram_to_bam_sam": { - "content": [ - [ - - ] - ], - "meta": { - "nf-test": "0.8.4", - "nextflow": "23.04.3" - }, - "timestamp": "2024-02-12T19:38:04.999247" - }, - "cram_to_bam_index_sam": { - "content": [ - [ - - ] - ], - "meta": { - "nf-test": "0.8.4", - "nextflow": "23.04.3" - }, - "timestamp": "2024-02-12T19:38:12.976457" - }, - "cram_crai": { - "content": [ - [ - - ] - ], - "meta": { - "nf-test": "0.8.4", - "nextflow": "23.04.3" - }, - "timestamp": "2024-02-12T19:37:56.497581" - }, - "cram_csi": { - "content": [ - [ - - ] - ], - "meta": { - "nf-test": "0.8.4", - "nextflow": "23.04.3" - }, - "timestamp": "2024-02-12T19:37:56.50038" - }, - "cram_to_bam_cram": { - "content": [ - [ - - ] - ], - "meta": { - "nf-test": "0.8.4", - "nextflow": "23.04.3" - }, - "timestamp": "2024-02-12T19:38:04.992239" - }, - "bam_stub_sam": { - "content": [ - [ - - ] - ], - "meta": { - "nf-test": "0.8.4", - "nextflow": "23.04.3" - }, - "timestamp": "2024-02-12T19:38:32.079529" - }, - "cram_cram": { - "content": [ - "test.cram" - ], - "meta": { - "nf-test": "0.8.4", - "nextflow": "23.04.3" - }, - "timestamp": "2024-02-12T19:37:56.490286" - }, - "cram_to_bam_crai": { - "content": [ - [ - - ] - ], - "meta": { - "nf-test": "0.8.4", - "nextflow": "23.04.3" - }, - "timestamp": "2024-02-12T19:38:04.989247" - }, - "cram_to_bam_index_crai": { - "content": [ - [ - - ] - ], - "meta": { - "nf-test": "0.8.4", - "nextflow": "23.04.3" - }, - "timestamp": "2024-02-12T19:38:12.967681" - }, - "cram_crai_index_unselected": { - "content": [ - "test.cram", - "test.cram.crai", - "test.unselected.cram", - "test.unselected.cram.crai", - [ - "versions.yml:md5,176db5ec46b965219604bcdbb3ef9e07" - ] - ], - "meta": { - "nf-test": "0.9.2", - "nextflow": "24.10.3" - }, - "timestamp": "2025-02-14T07:45:48.461930073" - }, - "cram_to_bam_index_qname_versions": { - "content": [ - [ - "versions.yml:md5,176db5ec46b965219604bcdbb3ef9e07" - ] - ], - "meta": { - "nf-test": "0.9.0", - "nextflow": "24.04.4" - }, - "timestamp": "2024-09-16T09:25:51.953436682" - }, - "cram_to_bam_bam": { - "content": [ - "test.bam" - ], - "meta": { - "nf-test": "0.8.4", - "nextflow": "23.04.3" - }, - "timestamp": "2024-02-12T19:38:04.982361" - }, - "cram_to_bam_bai": { - "content": [ - [ - - ] - ], - "meta": { - "nf-test": "0.8.4", - "nextflow": "23.04.3" - }, - "timestamp": "2024-02-12T19:38:04.98601" - }, - "cram_to_bam_versions": { - "content": [ - [ - "versions.yml:md5,176db5ec46b965219604bcdbb3ef9e07" - ] - ], - "meta": { - "nf-test": "0.9.2", - "nextflow": "24.10.3" - }, - "timestamp": "2025-02-13T16:33:39.363718229" - }, - "cram_bam": { - "content": [ - [ - - ] - ], - "meta": { - "nf-test": "0.8.4", - "nextflow": "23.04.3" - }, - "timestamp": "2024-02-12T19:37:56.495512" - }, - "bam_stub_cram": { - "content": [ - [ - - ] - ], - "meta": { - "nf-test": "0.8.4", - "nextflow": "23.04.3" - }, - "timestamp": "2024-02-12T19:38:32.076908" - }, - "cram_to_bam_index_qname_bai": { - "content": [ - [ - - ] - ], - "meta": { - "nf-test": "0.8.4", - "nextflow": "23.04.3" - }, - "timestamp": "2024-02-12T19:38:23.328458" - }, - "cram_to_bam_index_qname_crai": { - "content": [ - [ - - ] - ], - "meta": { - "nf-test": "0.8.4", - "nextflow": "23.04.3" - }, - "timestamp": "2024-02-12T19:38:23.330789" - }, - "cram_bai": { - "content": [ - [ - - ] - ], - "meta": { - "nf-test": "0.8.4", - "nextflow": "23.04.3" - }, - "timestamp": "2024-02-12T19:37:56.493129" - }, - "bam_stub_crai": { - "content": [ - [ - - ] - ], - "meta": { - "nf-test": "0.8.4", - "nextflow": "23.04.3" - }, - "timestamp": "2024-02-12T19:38:32.074313" - }, - "cram_to_bam_index_qname_bam": { - "content": [ - "test.bam" - ], - "meta": { - "nf-test": "0.8.4", - "nextflow": "23.04.3" - }, - "timestamp": "2024-02-12T19:38:23.322874" - }, - "bam_bai_index_unselected": { - "content": [ - "test.bam", - "test.bam.bai", - "test.unselected.bam", - "test.unselected.bam.csi", - [ - "versions.yml:md5,176db5ec46b965219604bcdbb3ef9e07" - ] - ], - "meta": { - "nf-test": "0.9.2", - "nextflow": "24.10.3" - }, - "timestamp": "2025-02-14T07:45:38.993014707" - }, - "cram_to_bam_index_qname_cram": { - "content": [ - [ - - ] - ], - "meta": { - "nf-test": "0.8.4", - "nextflow": "23.04.3" - }, - "timestamp": "2024-02-12T19:38:23.333248" + "nf-test": "0.9.3", + "nextflow": "25.10.4" + } }, - "cram_to_bam_csi": { + "cram": { "content": [ - [ - - ] + { + "bai": [ + + ], + "bam": [ + + ], + "crai": [ + + ], + "cram": [ + [ + { + "id": "test" + }, + "test.cram" + ] + ], + "csi": [ + + ], + "sam": [ + + ], + "unselected": [ + + ], + "unselected_index": [ + + ], + "versions_samtools": [ + [ + "SAMTOOLS_VIEW", + "samtools", + "1.23.1" + ] + ] + } ], + "timestamp": "2026-03-19T09:06:29.867409", "meta": { - "nf-test": "0.8.4", - "nextflow": "23.04.3" - }, - "timestamp": "2024-02-12T19:38:04.995454" + "nf-test": "0.9.3", + "nextflow": "25.10.4" + } }, "bam_bai_index_uselected - stub": { "content": [ @@ -830,8 +725,7 @@ "0": [ [ { - "id": "test", - "single_end": false + "id": "test" }, "test.bam:md5,d41d8cd98f00b204e9800998ecf8427e" ] @@ -845,8 +739,7 @@ "3": [ [ { - "id": "test", - "single_end": false + "id": "test" }, "test.bam.bai:md5,d41d8cd98f00b204e9800998ecf8427e" ] @@ -860,8 +753,7 @@ "6": [ [ { - "id": "test", - "single_end": false + "id": "test" }, "test.unselected.bam:md5,d41d8cd98f00b204e9800998ecf8427e" ] @@ -869,20 +761,22 @@ "7": [ [ { - "id": "test", - "single_end": false + "id": "test" }, "test.unselected.bam.csi:md5,d41d8cd98f00b204e9800998ecf8427e" ] ], "8": [ - "versions.yml:md5,176db5ec46b965219604bcdbb3ef9e07" + [ + "SAMTOOLS_VIEW", + "samtools", + "1.23.1" + ] ], "bai": [ [ { - "id": "test", - "single_end": false + "id": "test" }, "test.bam.bai:md5,d41d8cd98f00b204e9800998ecf8427e" ] @@ -890,8 +784,7 @@ "bam": [ [ { - "id": "test", - "single_end": false + "id": "test" }, "test.bam:md5,d41d8cd98f00b204e9800998ecf8427e" ] @@ -911,8 +804,7 @@ "unselected": [ [ { - "id": "test", - "single_end": false + "id": "test" }, "test.unselected.bam:md5,d41d8cd98f00b204e9800998ecf8427e" ] @@ -920,53 +812,185 @@ "unselected_index": [ [ { - "id": "test", - "single_end": false + "id": "test" }, "test.unselected.bam.csi:md5,d41d8cd98f00b204e9800998ecf8427e" ] ], - "versions": [ - "versions.yml:md5,176db5ec46b965219604bcdbb3ef9e07" + "versions_samtools": [ + [ + "SAMTOOLS_VIEW", + "samtools", + "1.23.1" + ] ] } ], + "timestamp": "2026-03-19T09:07:21.14353", "meta": { - "nf-test": "0.9.2", - "nextflow": "24.10.3" - }, - "timestamp": "2025-02-14T07:51:24.947216832" + "nf-test": "0.9.3", + "nextflow": "25.10.4" + } }, - "cram_to_bam_index_qname_unselected": { + "bam_with_bed": { "content": [ - "test.unselected.bam" + { + "bai": [ + + ], + "bam": [ + [ + { + "id": "test" + }, + "test.bam" + ] + ], + "crai": [ + + ], + "cram": [ + + ], + "csi": [ + + ], + "sam": [ + + ], + "unselected": [ + + ], + "unselected_index": [ + + ], + "versions_samtools": [ + [ + "SAMTOOLS_VIEW", + "samtools", + "1.23.1" + ] + ] + } ], + "timestamp": "2026-03-25T12:13:59.0656", "meta": { - "nf-test": "0.8.4", - "nextflow": "23.04.3" - }, - "timestamp": "2024-02-12T19:38:23.322874" + "nf-test": "0.9.4", + "nextflow": "25.10.4" + } }, - "bam_sam": { + "cram_crai_index_unselected": { "content": [ - [ - - ] + { + "bai": [ + + ], + "bam": [ + + ], + "crai": [ + [ + { + "id": "test" + }, + "test.cram.crai" + ] + ], + "cram": [ + [ + { + "id": "test" + }, + "test.cram:md5,924e27301c54060128cc259f5f83e74f" + ] + ], + "csi": [ + + ], + "sam": [ + + ], + "unselected": [ + [ + { + "id": "test" + }, + "test.unselected.cram" + ] + ], + "unselected_index": [ + [ + { + "id": "test" + }, + "test.unselected.cram.crai" + ] + ], + "versions_samtools": [ + [ + "SAMTOOLS_VIEW", + "samtools", + "1.23.1" + ] + ] + } ], + "timestamp": "2026-03-19T09:06:23.90307", "meta": { - "nf-test": "0.8.4", - "nextflow": "23.04.3" - }, - "timestamp": "2024-02-12T19:37:51.264651" + "nf-test": "0.9.3", + "nextflow": "25.10.4" + } }, - "bam_stub_csi": { + "bam_stub": { "content": [ - "test.bam.csi" + { + "bai": [ + + ], + "bam": [ + [ + { + "id": "test" + }, + "test.bam" + ] + ], + "crai": [ + + ], + "cram": [ + + ], + "csi": [ + [ + { + "id": "test" + }, + "test.bam.csi" + ] + ], + "sam": [ + + ], + "unselected": [ + + ], + "unselected_index": [ + + ], + "versions_samtools": [ + [ + "SAMTOOLS_VIEW", + "samtools", + "1.23.1" + ] + ] + } ], + "timestamp": "2026-03-19T09:06:58.641723", "meta": { - "nf-test": "0.8.4", - "nextflow": "23.04.3" - }, - "timestamp": "2024-02-12T19:38:32.068596" + "nf-test": "0.9.3", + "nextflow": "25.10.4" + } } } \ No newline at end of file diff --git a/modules/nf-core/samtools/view/tests/nextflow.config b/modules/nf-core/samtools/view/tests/nextflow.config new file mode 100644 index 00000000..7d11b502 --- /dev/null +++ b/modules/nf-core/samtools/view/tests/nextflow.config @@ -0,0 +1,5 @@ +process { + withName: SAMTOOLS_VIEW { + ext.args = { params.samtools_args } + } +} diff --git a/modules/nf-core/scanpy/hashsolo/main.nf b/modules/nf-core/scanpy/hashsolo/main.nf index 5b12c734..cf7d915b 100644 --- a/modules/nf-core/scanpy/hashsolo/main.nf +++ b/modules/nf-core/scanpy/hashsolo/main.nf @@ -3,7 +3,7 @@ process SCANPY_HASHSOLO { label 'process_medium' conda "${moduleDir}/environment.yml" - container "${ workflow.containerEngine == 'singularity' && !task.ext.singularity_pull_docker_container ? + container "${ workflow.containerEngine in ['singularity', 'apptainer'] && !task.ext.singularity_pull_docker_container ? 'https://community-cr-prod.seqera.io/docker/registry/v2/blobs/sha256/a0/a06fe0be02e1a82be29f415a554cfd3ad9d921cd63e71ebce3b141a723564426/data': 'community.wave.seqera.io/library/matplotlib_pandas_python_pyyaml_scanpy:bf7b7ef27120d15c' }" diff --git a/modules/nf-core/scanpy/hashsolo/templates/hashsolo.py b/modules/nf-core/scanpy/hashsolo/templates/hashsolo.py index 2db087b4..c566858b 100644 --- a/modules/nf-core/scanpy/hashsolo/templates/hashsolo.py +++ b/modules/nf-core/scanpy/hashsolo/templates/hashsolo.py @@ -1,19 +1,20 @@ #!/usr/bin/env python3 +import argparse import os import platform -import yaml import shlex -import argparse + +import yaml os.environ["MPLCONFIGDIR"] = "./tmp/mpl" os.environ["NUMBA_CACHE_DIR"] = "./tmp/numba" +import matplotlib +import pandas as pd import scanpy as sc import scanpy.external as sce -import pandas as pd -import matplotlib -import matplotlib.pyplot as plt + class Arguments: # adopted from mygene module (Suzanne Jin) @@ -22,9 +23,6 @@ class Arguments: """ def __init__(self) -> None: - - - self.data = "$data" self.use_10x = True @@ -34,12 +32,12 @@ def __init__(self) -> None: cell_hashing_columns = "${cell_hashing_columns.join(' ')}".split() self.cell_hashing_columns = [str(x) for x in cell_hashing_columns] - self.prefix = "$task.ext.prefix" if "$task.ext.prefix" != "null" else "$meta.id" + self.prefix = "$task.ext.prefix" if "$task.ext.prefix" != "null" else "$meta.id" self.path_assignment = self.prefix + "_assignment_hashsolo.csv" # self.path_plot = self.prefix + "_hashsolo.jpg" - self.path_h5ad = self.prefix + "_hashsolo.h5ad" - self.path_params = self.prefix + "_params_hashsolo.csv" + self.path_h5ad = self.prefix + "_hashsolo.h5ad" + self.path_params = self.prefix + "_params_hashsolo.csv" self.parse_ext_args("$task.ext.args") def parse_ext_args(self, args_string: str) -> None: @@ -62,8 +60,7 @@ def parse_ext_args(self, args_string: str) -> None: help="""List of priors for each hypothesis: NEGATIVE = prior for negative hypothesis SINGLET = prior for singlet hypothesis - DOUBLET = prior for doublet hypothesis""" - , + DOUBLET = prior for doublet hypothesis""", default=[0.01, 0.8, 0.19], ) @@ -120,8 +117,8 @@ def print_args(self) -> None: for attr in vars(self): print(f"{attr}: {getattr(self, attr)}") -if __name__ == "__main__": +if __name__ == "__main__": # ------------------------------ parse and print arguments ------------------------------ args = Arguments() args.print_args() @@ -135,7 +132,7 @@ def print_args(self) -> None: # Convert sparse matrix to dense array if needed, otherwise use as-is cell_hashing_data.X.toarray() if hasattr(cell_hashing_data.X, "toarray") else cell_hashing_data.X, index=cell_hashing_data.obs_names, - columns=cell_hashing_data.var_names + columns=cell_hashing_data.var_names, ) args.cell_hashing_columns = list(cell_hashing_data.obs.columns) else: @@ -145,7 +142,6 @@ def print_args(self) -> None: # This edge case issue may be fixed in future versions: https://github.com/calico/solo/issues/91 args.number_of_noise_barcodes = 1 - # -------------------------------------- call hashsolo ----------------------------------- if args.clustering_data is not None: trans_data = sc.read_10x_mtx(args.clustering_data) @@ -167,7 +163,6 @@ def print_args(self) -> None: number_of_noise_barcodes=args.number_of_noise_barcodes, ) - # ------------------------------------- save results ------------------------------------- # Round numeric values if requested @@ -193,10 +188,10 @@ def print_args(self) -> None: versions = { "${task.process}": { - "python" : platform.python_version(), - "scanpy" : sc.__version__, + "python": platform.python_version(), + "scanpy": sc.__version__, "matplotlib": matplotlib.__version__, - "pandas" : pd.__version__, + "pandas": pd.__version__, } } diff --git a/modules/nf-core/souporcell/main.nf b/modules/nf-core/souporcell/main.nf index 8c990d98..6a7306d9 100644 --- a/modules/nf-core/souporcell/main.nf +++ b/modules/nf-core/souporcell/main.nf @@ -3,7 +3,7 @@ process SOUPORCELL { label 'process_medium' conda "${moduleDir}/environment.yml" - container "${ workflow.containerEngine == 'singularity' && !task.ext.singularity_pull_docker_container ? + container "${ workflow.containerEngine in ['singularity', 'apptainer'] && !task.ext.singularity_pull_docker_container ? 'https://community-cr-prod.seqera.io/docker/registry/v2/blobs/sha256/9a/9a69c552c52aa5b3636a7a596f9406b2ec3e165809ccd58a012b9ea285ba6ecd/data' : 'community.wave.seqera.io/library/souporcell_gxx:f648658dde2cdd53' }" diff --git a/modules/nf-core/umitools/dedup/environment.yml b/modules/nf-core/umitools/dedup/environment.yml index e5721f1e..4d05188b 100644 --- a/modules/nf-core/umitools/dedup/environment.yml +++ b/modules/nf-core/umitools/dedup/environment.yml @@ -4,4 +4,14 @@ channels: - conda-forge - bioconda dependencies: - - bioconda::umi_tools=1.1.5 + # renovate: datasource=conda depName=bioconda/umi_tools + - bioconda::umi_tools=1.1.6 + - conda-forge::future=0.18.3 + - conda-forge::matplotlib=3.8.2 + - conda-forge::numpy=1.26.4 + - conda-forge::pandas=2.2.0 + - conda-forge::python=3.9.18 + - conda-forge::regex=2023.12.25 + - conda-forge::scipy=1.12.0 + - conda-forge::pybktree=1.1 + - bioconda::pysam=0.22.0 diff --git a/modules/nf-core/umitools/dedup/main.nf b/modules/nf-core/umitools/dedup/main.nf index 1e2a2aae..13c43ca0 100644 --- a/modules/nf-core/umitools/dedup/main.nf +++ b/modules/nf-core/umitools/dedup/main.nf @@ -3,9 +3,9 @@ process UMITOOLS_DEDUP { label "process_medium" conda "${moduleDir}/environment.yml" - container "${ workflow.containerEngine == 'singularity' && !task.ext.singularity_pull_docker_container ? - 'https://depot.galaxyproject.org/singularity/umi_tools:1.1.5--py39hf95cd2a_0' : - 'biocontainers/umi_tools:1.1.5--py39hf95cd2a_0' }" + container "${ workflow.containerEngine in ['singularity', 'apptainer'] && !task.ext.singularity_pull_docker_container ? + 'https://community-cr-prod.seqera.io/docker/registry/v2/blobs/sha256/32/32476f0107d72dbd2210a4e56b2873abde07300025cc11052680475509d2db81/data' : + 'community.wave.seqera.io/library/umi_tools_future_matplotlib_numpy_pruned:1ee668bafc8c9f81' }" input: tuple val(meta), path(bam), path(bai) @@ -17,7 +17,7 @@ process UMITOOLS_DEDUP { tuple val(meta), path("*edit_distance.tsv"), optional:true, emit: tsv_edit_distance tuple val(meta), path("*per_umi.tsv") , optional:true, emit: tsv_per_umi tuple val(meta), path("*per_position.tsv") , optional:true, emit: tsv_umi_per_position - path "versions.yml" , emit: versions + tuple val("${task.process}"), val('umitools'), eval("umi_tools --version | sed 's/UMI-tools version: //'"), emit: versions_umitools, topic: versions when: task.ext.when == null || task.ext.when @@ -31,7 +31,10 @@ process UMITOOLS_DEDUP { if (!(args ==~ /.*--random-seed.*/)) {args += " --random-seed=100"} """ - PYTHONHASHSEED=0 umi_tools \\ + #Prevent matplotlib from using /tmp + mkdir .tmp && chmod 777 .tmp + + MPLCONFIGDIR=.tmp TMPDIR=.tmp PYTHONHASHSEED=0 umi_tools \\ dedup \\ -I $bam \\ -S ${prefix}.bam \\ @@ -39,11 +42,6 @@ process UMITOOLS_DEDUP { $stats \\ $paired \\ $args - - cat <<-END_VERSIONS > versions.yml - "${task.process}": - umitools: \$( umi_tools --version | sed '/version:/!d; s/.*: //' ) - END_VERSIONS """ stub: @@ -54,10 +52,5 @@ process UMITOOLS_DEDUP { touch ${prefix}_edit_distance.tsv touch ${prefix}_per_umi.tsv touch ${prefix}_per_position.tsv - - cat <<-END_VERSIONS > versions.yml - "${task.process}": - umitools: \$( umi_tools --version | sed '/version:/!d; s/.*: //' ) - END_VERSIONS """ } diff --git a/modules/nf-core/umitools/dedup/meta.yml b/modules/nf-core/umitools/dedup/meta.yml index 6cbd8411..1dc45598 100644 --- a/modules/nf-core/umitools/dedup/meta.yml +++ b/modules/nf-core/umitools/dedup/meta.yml @@ -25,18 +25,20 @@ input: description: | BAM file containing reads to be deduplicated via UMIs. pattern: "*.{bam}" + ontologies: [] - bai: type: file description: | BAM index files corresponding to the input BAM file. pattern: "*.{bai}" - - - get_output_stats: - type: boolean - description: | - Whether or not to generate output stats. + ontologies: [] + - get_output_stats: + type: boolean + description: | + Whether or not to generate output stats. output: - - bam: - - meta: + bam: + - - meta: type: map description: | Groovy Map containing sample information @@ -45,8 +47,9 @@ output: type: file description: BAM file with deduplicated UMIs. pattern: "*.{bam}" - - log: - - meta: + ontologies: [] + log: + - - meta: type: map description: | Groovy Map containing sample information @@ -55,8 +58,9 @@ output: type: file description: File with logging information pattern: "*.{log}" - - tsv_edit_distance: - - meta: + ontologies: [] + tsv_edit_distance: + - - meta: type: map description: | Groovy Map containing sample information @@ -66,8 +70,10 @@ output: description: Reports the (binned) average edit distance between the UMIs at each position. pattern: "*edit_distance.tsv" - - tsv_per_umi: - - meta: + ontologies: + - edam: http://edamontology.org/format_3475 # TSV + tsv_per_umi: + - - meta: type: map description: | Groovy Map containing sample information @@ -76,8 +82,10 @@ output: type: file description: UMI-level summary statistics. pattern: "*per_umi.tsv" - - tsv_umi_per_position: - - meta: + ontologies: + - edam: http://edamontology.org/format_3475 # TSV + tsv_umi_per_position: + - - meta: type: map description: | Groovy Map containing sample information @@ -86,11 +94,30 @@ output: type: file description: Tabulates the counts for unique combinations of UMI and position. pattern: "*per_position.tsv" - - versions: - - versions.yml: - type: file - description: File containing software versions - pattern: "versions.yml" + ontologies: + - edam: http://edamontology.org/format_3475 # TSV + versions_umitools: + - - ${task.process}: + type: string + description: The name of the process + - umitools: + type: string + description: The name of the tool + - "umi_tools --version | sed 's/UMI-tools version: //'": + type: eval + description: The expression to obtain the version of the tool + +topics: + versions: + - - ${task.process}: + type: string + description: The name of the process + - umitools: + type: string + description: The name of the tool + - "umi_tools --version | sed 's/UMI-tools version: //'": + type: eval + description: The expression to obtain the version of the tool authors: - "@drpatelh" - "@grst" diff --git a/modules/nf-core/umitools/dedup/tests/main.nf.test b/modules/nf-core/umitools/dedup/tests/main.nf.test index f00a8cbe..dd7096bf 100644 --- a/modules/nf-core/umitools/dedup/tests/main.nf.test +++ b/modules/nf-core/umitools/dedup/tests/main.nf.test @@ -33,7 +33,7 @@ nextflow_process { { assert path("${process.out.log[0][1]}").exists() }, { assert snapshot( bam(process.out.bam[0][1]).getSamLinesMD5(), - process.out.versions).match() } + process.out.findAll { key, val -> key.startsWith('versions') }).match() } ) } } @@ -62,7 +62,7 @@ nextflow_process { { assert path("${process.out.log[0][1]}").exists() }, { assert snapshot( bam(process.out.bam[0][1]).getSamLinesMD5(), - process.out.versions).match() } + process.out.findAll { key, val -> key.startsWith('versions') }).match() } ) } } @@ -94,7 +94,7 @@ nextflow_process { process.out.tsv_edit_distance, process.out.tsv_per_umi, process.out.tsv_umi_per_position, - process.out.versions).match() } + process.out.findAll { key, val -> key.startsWith('versions') }).match() } ) } } diff --git a/modules/nf-core/umitools/dedup/tests/main.nf.test.snap b/modules/nf-core/umitools/dedup/tests/main.nf.test.snap index 04b81692..19f0dfe9 100644 --- a/modules/nf-core/umitools/dedup/tests/main.nf.test.snap +++ b/modules/nf-core/umitools/dedup/tests/main.nf.test.snap @@ -65,15 +65,21 @@ "test.dedup_per_umi_per_position.tsv:md5,2e1a12e6f720510880068deddeefe063" ] ], - [ - "versions.yml:md5,e2f5146464c09bf7ae98c85ea5410e50" - ] + { + "versions_umitools": [ + [ + "UMITOOLS_DEDUP", + "umitools", + "1.1.6" + ] + ] + } ], "meta": { - "nf-test": "0.8.4", - "nextflow": "24.10.1" + "nf-test": "0.9.3", + "nextflow": "25.10.2" }, - "timestamp": "2024-11-25T17:25:28.939957" + "timestamp": "2026-02-02T17:10:09.998378" }, "se - no stats - stub": { "content": [ @@ -96,27 +102,39 @@ "se - no stats": { "content": [ "9158ea6e7a0e54819e25cbac5fbc5cc0", - [ - "versions.yml:md5,e2f5146464c09bf7ae98c85ea5410e50" - ] + { + "versions_umitools": [ + [ + "UMITOOLS_DEDUP", + "umitools", + "1.1.6" + ] + ] + } ], "meta": { - "nf-test": "0.8.4", - "nextflow": "24.10.1" + "nf-test": "0.9.3", + "nextflow": "25.10.2" }, - "timestamp": "2024-11-23T09:06:54.373171" + "timestamp": "2026-02-02T17:08:30.01886" }, "pe - no stats": { "content": [ "b7be15ac7aae194b04bdbb56f3534495", - [ - "versions.yml:md5,e2f5146464c09bf7ae98c85ea5410e50" - ] + { + "versions_umitools": [ + [ + "UMITOOLS_DEDUP", + "umitools", + "1.1.6" + ] + ] + } ], "meta": { - "nf-test": "0.8.4", - "nextflow": "24.10.1" + "nf-test": "0.9.3", + "nextflow": "25.10.2" }, - "timestamp": "2024-11-25T17:24:51.423637" + "timestamp": "2026-02-02T17:09:22.650959" } } \ No newline at end of file diff --git a/modules/nf-core/untar/main.nf b/modules/nf-core/untar/main.nf index e712ebe6..bf2c056c 100644 --- a/modules/nf-core/untar/main.nf +++ b/modules/nf-core/untar/main.nf @@ -3,7 +3,7 @@ process UNTAR { label 'process_single' conda "${moduleDir}/environment.yml" - container "${workflow.containerEngine == 'singularity' && !task.ext.singularity_pull_docker_container + container "${workflow.containerEngine in ['singularity', 'apptainer'] && !task.ext.singularity_pull_docker_container ? 'https://community-cr-prod.seqera.io/docker/registry/v2/blobs/sha256/52/52ccce28d2ab928ab862e25aae26314d69c8e38bd41ca9431c67ef05221348aa/data' : 'community.wave.seqera.io/library/coreutils_grep_gzip_lbzip2_pruned:838ba80435a629f8'}" @@ -12,7 +12,7 @@ process UNTAR { output: tuple val(meta), path("${prefix}"), emit: untar - path "versions.yml", emit: versions + tuple val("${task.process}"), val('untar'), eval('tar --version 2>&1 | head -1 | sed "s/tar (GNU tar) //; s/ Copyright.*//"'), emit: versions_untar, topic: versions when: task.ext.when == null || task.ext.when @@ -43,10 +43,6 @@ process UNTAR { ${args2} fi - cat <<-END_VERSIONS > versions.yml - "${task.process}": - untar: \$(echo \$(tar --version 2>&1) | sed 's/^.*(GNU tar) //; s/ Copyright.*\$//') - END_VERSIONS """ stub: @@ -75,10 +71,5 @@ process UNTAR { fi done fi - - cat <<-END_VERSIONS > versions.yml - "${task.process}": - untar: \$(echo \$(tar --version 2>&1) | sed 's/^.*(GNU tar) //; s/ Copyright.*\$//') - END_VERSIONS """ } diff --git a/modules/nf-core/untar/meta.yml b/modules/nf-core/untar/meta.yml index 3a37bb35..571d8078 100644 --- a/modules/nf-core/untar/meta.yml +++ b/modules/nf-core/untar/meta.yml @@ -1,5 +1,5 @@ name: untar -description: Extract files. +description: Extract files from tar, tar.gz, tar.bz2, tar.xz archives keywords: - untar - uncompress @@ -7,7 +7,7 @@ keywords: tools: - untar: description: | - Extract tar.gz files. + Extract tar, tar.gz, tar.bz2, tar.xz files. documentation: https://www.gnu.org/software/tar/manual/ licence: ["GPL-3.0-or-later"] identifier: "" @@ -19,11 +19,14 @@ input: e.g. [ id:'test', single_end:false ] - archive: type: file - description: File to be untar - pattern: "*.{tar}.{gz}" + description: File to be untarred + pattern: "*.{tar,tar.gz,tar.bz2,tar.xz}" + ontologies: + - edam: http://edamontology.org/format_3981 # TAR format + - edam: http://edamontology.org/format_3989 # GZIP format output: - - untar: - - meta: + untar: + - - meta: type: map description: | Groovy Map containing sample information @@ -35,11 +38,29 @@ output: Groovy Map containing sample information e.g. [ id:'test', single_end:false ] pattern: "*/" - - versions: - - versions.yml: - type: file - description: File containing software versions - pattern: "versions.yml" + versions_untar: + - - ${task.process}: + type: string + description: The name of the process + - untar: + type: string + description: The name of the tool + - tar --version 2>&1 | head -1 | sed "s/tar (GNU tar) //; s/ Copyright.*//": + type: eval + description: The expression to obtain the version of the tool + +topics: + versions: + - - ${task.process}: + type: string + description: The name of the process + - untar: + type: string + description: The name of the tool + - tar --version 2>&1 | head -1 | sed "s/tar (GNU tar) //; s/ Copyright.*//": + type: eval + description: The expression to obtain the version of the tool + authors: - "@joseespinosa" - "@drpatelh" diff --git a/modules/nf-core/untar/tests/main.nf.test b/modules/nf-core/untar/tests/main.nf.test index c957517a..fde8db16 100644 --- a/modules/nf-core/untar/tests/main.nf.test +++ b/modules/nf-core/untar/tests/main.nf.test @@ -20,7 +20,10 @@ nextflow_process { then { assertAll ( { assert process.success }, - { assert snapshot(process.out).match() }, + { assert snapshot( + process.out.untar, + process.out.findAll { key, val -> key.startsWith('versions') } + ).match() }, ) } } @@ -38,7 +41,10 @@ nextflow_process { then { assertAll ( { assert process.success }, - { assert snapshot(process.out).match() }, + { assert snapshot( + process.out.untar, + process.out.findAll { key, val -> key.startsWith('versions') } + ).match() }, ) } } @@ -58,7 +64,10 @@ nextflow_process { then { assertAll ( { assert process.success }, - { assert snapshot(process.out).match() }, + { assert snapshot( + process.out.untar, + process.out.findAll { key, val -> key.startsWith('versions') } + ).match() }, ) } } @@ -78,7 +87,10 @@ nextflow_process { then { assertAll ( { assert process.success }, - { assert snapshot(process.out).match() }, + { assert snapshot( + process.out.untar, + process.out.findAll { key, val -> key.startsWith('versions') } + ).match() }, ) } } diff --git a/modules/nf-core/untar/tests/main.nf.test.snap b/modules/nf-core/untar/tests/main.nf.test.snap index ceb91b79..51a414dd 100644 --- a/modules/nf-core/untar/tests/main.nf.test.snap +++ b/modules/nf-core/untar/tests/main.nf.test.snap @@ -1,158 +1,118 @@ { "test_untar_onlyfiles": { "content": [ - { - "0": [ + [ + [ + [ + + ], [ - [ - - ], - [ - "hello.txt:md5,e59ff97941044f85df5297e1c302d260" - ] + "hello.txt:md5,e59ff97941044f85df5297e1c302d260" ] - ], - "1": [ - "versions.yml:md5,6063247258c56fd271d076bb04dd7536" - ], - "untar": [ + ] + ], + { + "versions_untar": [ [ - [ - - ], - [ - "hello.txt:md5,e59ff97941044f85df5297e1c302d260" - ] + "UNTAR", + "untar", + "1.34" ] - ], - "versions": [ - "versions.yml:md5,6063247258c56fd271d076bb04dd7536" ] } ], "meta": { - "nf-test": "0.8.4", - "nextflow": "24.04.3" + "nf-test": "0.9.3", + "nextflow": "25.10.2" }, - "timestamp": "2024-07-10T12:04:28.231047" + "timestamp": "2026-01-28T17:49:32.000491" }, "test_untar_onlyfiles - stub": { "content": [ - { - "0": [ + [ + [ + [ + + ], [ - [ - - ], - [ - "hello.txt:md5,d41d8cd98f00b204e9800998ecf8427e" - ] + "hello.txt:md5,d41d8cd98f00b204e9800998ecf8427e" ] - ], - "1": [ - "versions.yml:md5,6063247258c56fd271d076bb04dd7536" - ], - "untar": [ + ] + ], + { + "versions_untar": [ [ - [ - - ], - [ - "hello.txt:md5,d41d8cd98f00b204e9800998ecf8427e" - ] + "UNTAR", + "untar", + "1.34" ] - ], - "versions": [ - "versions.yml:md5,6063247258c56fd271d076bb04dd7536" ] } ], "meta": { - "nf-test": "0.8.4", - "nextflow": "24.04.3" + "nf-test": "0.9.3", + "nextflow": "25.10.2" }, - "timestamp": "2024-07-10T12:04:45.773103" + "timestamp": "2026-01-28T17:49:58.812479" }, "test_untar - stub": { "content": [ - { - "0": [ + [ + [ + [ + + ], [ - [ - - ], - [ - "hash.k2d:md5,d41d8cd98f00b204e9800998ecf8427e", - "opts.k2d:md5,d41d8cd98f00b204e9800998ecf8427e", - "taxo.k2d:md5,d41d8cd98f00b204e9800998ecf8427e" - ] + "hash.k2d:md5,d41d8cd98f00b204e9800998ecf8427e", + "opts.k2d:md5,d41d8cd98f00b204e9800998ecf8427e", + "taxo.k2d:md5,d41d8cd98f00b204e9800998ecf8427e" ] - ], - "1": [ - "versions.yml:md5,6063247258c56fd271d076bb04dd7536" - ], - "untar": [ + ] + ], + { + "versions_untar": [ [ - [ - - ], - [ - "hash.k2d:md5,d41d8cd98f00b204e9800998ecf8427e", - "opts.k2d:md5,d41d8cd98f00b204e9800998ecf8427e", - "taxo.k2d:md5,d41d8cd98f00b204e9800998ecf8427e" - ] + "UNTAR", + "untar", + "1.34" ] - ], - "versions": [ - "versions.yml:md5,6063247258c56fd271d076bb04dd7536" ] } ], "meta": { - "nf-test": "0.8.4", - "nextflow": "24.04.3" + "nf-test": "0.9.3", + "nextflow": "25.10.2" }, - "timestamp": "2024-07-10T12:04:36.777441" + "timestamp": "2026-01-28T17:49:48.119456" }, "test_untar": { "content": [ - { - "0": [ + [ + [ + [ + + ], [ - [ - - ], - [ - "hash.k2d:md5,8b8598468f54a7087c203ad0190555d9", - "opts.k2d:md5,a033d00cf6759407010b21700938f543", - "taxo.k2d:md5,094d5891cdccf2f1468088855c214b2c" - ] + "hash.k2d:md5,8b8598468f54a7087c203ad0190555d9", + "opts.k2d:md5,a033d00cf6759407010b21700938f543", + "taxo.k2d:md5,094d5891cdccf2f1468088855c214b2c" ] - ], - "1": [ - "versions.yml:md5,6063247258c56fd271d076bb04dd7536" - ], - "untar": [ + ] + ], + { + "versions_untar": [ [ - [ - - ], - [ - "hash.k2d:md5,8b8598468f54a7087c203ad0190555d9", - "opts.k2d:md5,a033d00cf6759407010b21700938f543", - "taxo.k2d:md5,094d5891cdccf2f1468088855c214b2c" - ] + "UNTAR", + "untar", + "1.34" ] - ], - "versions": [ - "versions.yml:md5,6063247258c56fd271d076bb04dd7536" ] } ], "meta": { - "nf-test": "0.8.4", - "nextflow": "24.04.3" + "nf-test": "0.9.3", + "nextflow": "25.10.2" }, - "timestamp": "2024-07-10T12:04:19.377674" + "timestamp": "2026-01-28T17:49:17.252494" } } \ No newline at end of file diff --git a/modules/nf-core/vireo/main.nf b/modules/nf-core/vireo/main.nf index 8c4034ec..0ebe730b 100644 --- a/modules/nf-core/vireo/main.nf +++ b/modules/nf-core/vireo/main.nf @@ -3,9 +3,9 @@ process VIREO { label 'process_medium' conda "${moduleDir}/environment.yml" - container "${ workflow.containerEngine == 'singularity' && !task.ext.singularity_pull_docker_container ? + container "${ workflow.containerEngine in ['singularity', 'apptainer'] && !task.ext.singularity_pull_docker_container ? 'https://depot.galaxyproject.org/singularity/vireosnp:0.5.8--pyh7cba7a3_0' : - 'biocontainers/vireosnp:0.5.8--pyh7cba7a3_0' }" + 'quay.io/biocontainers/vireosnp:0.5.8--pyh7cba7a3_0' }" input: tuple val(meta), path(cell_data), val(n_donor), path(donor_file), path(vartrix_data) diff --git a/modules/nf-core/vireo/tests/main.nf.test b/modules/nf-core/vireo/tests/main.nf.test index 047fea38..707d36df 100644 --- a/modules/nf-core/vireo/tests/main.nf.test +++ b/modules/nf-core/vireo/tests/main.nf.test @@ -28,7 +28,7 @@ nextflow_process { script "../../cellsnp/modea/main.nf" process { """ - input[0] = SAMTOOLS_INDEX.out.bai.collect{ meta, bai -> bai }.map{ + input[0] = SAMTOOLS_INDEX.out.index.collect{ meta, bai -> bai }.map{ bai -> [[id: 'sample1'], file(params.modules_testdata_base_path + '/genomics/homo_sapiens/demultiplexing/chr21.bam', checkIfExists: true), bai, diff --git a/subworkflows/local/bam_qc/main.nf b/subworkflows/local/bam_qc/main.nf index 35032ccb..23650206 100644 --- a/subworkflows/local/bam_qc/main.nf +++ b/subworkflows/local/bam_qc/main.nf @@ -9,21 +9,20 @@ workflow BAM_QC { main: - ch_versions = channel.empty() - - SAMTOOLS_VIEW(ch_bam.map { meta, bam -> [meta, bam, []] }, [[], []], [], 'bai') - ch_versions = ch_versions.mix(SAMTOOLS_VIEW.out.versions) + SAMTOOLS_VIEW( + ch_bam.map { meta, bam -> [meta, bam, []] }, + [[], [], []], + [[], []], + [[], []], + 'bai' + ) SAMTOOLS_INDEX(SAMTOOLS_VIEW.out.bam) - ch_versions = ch_versions.mix(SAMTOOLS_INDEX.out.versions) - UMITOOLS_DEDUP(SAMTOOLS_VIEW.out.bam.join(SAMTOOLS_INDEX.out.bai), true) - ch_versions = ch_versions.mix(UMITOOLS_DEDUP.out.versions) + UMITOOLS_DEDUP(SAMTOOLS_VIEW.out.bam.join(SAMTOOLS_INDEX.out.index), true) - SAMTOOLS_SORT(UMITOOLS_DEDUP.out.bam, [[], []]) - ch_versions = ch_versions.mix(SAMTOOLS_SORT.out.versions) + SAMTOOLS_SORT(UMITOOLS_DEDUP.out.bam, [[], [], []], '') emit: - bam = SAMTOOLS_SORT.out.bam - versions = ch_versions // channel: [ versions.yml ] + bam = SAMTOOLS_SORT.out.bam } diff --git a/subworkflows/local/genetic_demultiplexing/main.nf b/subworkflows/local/genetic_demultiplexing/main.nf index 059abe60..64798655 100644 --- a/subworkflows/local/genetic_demultiplexing/main.nf +++ b/subworkflows/local/genetic_demultiplexing/main.nf @@ -37,7 +37,6 @@ workflow GENETIC_DEMULTIPLEXING { if (bam_qc) { BAM_QC(ch_samplesheet.map { meta, bam, _barcodes, _vcf -> [meta, bam] }) - ch_versions = ch_versions.mix(BAM_QC.out.versions) ch_samplesheet = ch_samplesheet .join(BAM_QC.out.bam) @@ -55,8 +54,6 @@ workflow GENETIC_DEMULTIPLEXING { }, common_variants, ) - ch_versions = ch_versions.mix(FILTER_BAM.out.versions) - ch_samplesheet = ch_samplesheet .join(FILTER_BAM.out.bam) .map { meta, _bam, barcodes, vcf, new_bam -> [meta, new_bam, barcodes, vcf] } @@ -66,10 +63,9 @@ workflow GENETIC_DEMULTIPLEXING { if ( params.find_variants | methods.contains('vireo')){ SAMTOOLS_INDEX(ch_samplesheet.map { meta, bam, _barcodes, _vcf -> [meta, bam] }) - ch_versions = ch_versions.mix(SAMTOOLS_INDEX.out.versions) CELLSNP_MODEA( - ch_samplesheet.join(SAMTOOLS_INDEX.out.bai).map { meta, bam, barcodes, vcf, bai -> [meta, bam, bai, vcf, barcodes] } + ch_samplesheet.join(SAMTOOLS_INDEX.out.index).map { meta, bam, barcodes, vcf, bai -> [meta, bam, bai, vcf, barcodes] } ) ch_gt_cells = ch_gt_cells.mix(CELLSNP_MODEA.out.cell) @@ -135,8 +131,6 @@ workflow GENETIC_DEMULTIPLEXING { GENE_SUMMARY(ch_summary) - ch_versions = ch_versions.mix(GENE_SUMMARY.out.versions) - emit: summary_assignment = GENE_SUMMARY.out.assignment diff --git a/subworkflows/local/hash_demultiplexing/main.nf b/subworkflows/local/hash_demultiplexing/main.nf index fd1827b7..87329148 100644 --- a/subworkflows/local/hash_demultiplexing/main.nf +++ b/subworkflows/local/hash_demultiplexing/main.nf @@ -41,7 +41,6 @@ workflow HASH_DEMULTIPLEXING { PREPROCESSING_FOR_HTODEMUX_MULTISEQ( ch_samplesheet ) - ch_versions = ch_versions.mix(PREPROCESSING_FOR_HTODEMUX_MULTISEQ.out.versions) if (methods.contains('htodemux')) { HTODEMUX( @@ -54,7 +53,6 @@ workflow HASH_DEMULTIPLEXING { HTODEMUX_VISUALIZATION( HTODEMUX.out.rds.map { meta, seurat_object -> [meta, seurat_object, "HTO"] } ) - ch_versions = ch_versions.mix(HTODEMUX_VISUALIZATION.out.versions) } if (methods.contains('multiseq')) { MULTISEQDEMUX( @@ -74,16 +72,13 @@ workflow HASH_DEMULTIPLEXING { if (methods.contains('bff')) { BFF(ch_samplesheet.map { meta, _rna, hto -> [meta,hto,params.bff_methods,params.bff_preprocessing]}) ch_bff = ch_bff.mix(BFF.out.assignment) - ch_versions = ch_versions.mix(BFF.out.versions) } if (methods.contains('demuxem')) { MTXCONVERT_RNA(ch_samplesheet.map { meta, rna, _hto -> [meta, rna] }, false) - ch_versions = ch_versions.mix(MTXCONVERT_RNA.out.versions) MTXCONVERT_HTO(ch_samplesheet.map { meta, _rna, hto -> [meta, hto] }, true) - ch_versions = ch_versions.mix(MTXCONVERT_HTO.out.versions) DEMUXEM( MTXCONVERT_RNA.out.h5.join(MTXCONVERT_HTO.out.csv), @@ -132,7 +127,6 @@ workflow HASH_DEMULTIPLEXING { ch_hasheddrops_results = ch_hasheddrops_results.mix(HASHEDDROPS.out.results) ch_hasheddrops_id_to_hash = ch_hasheddrops_id_to_hash.mix(HASHEDDROPS.out.id_to_hash) - ch_versions = ch_versions.mix(HASHEDDROPS.out.versions) } if (methods.contains('hashsolo')) { @@ -166,8 +160,6 @@ workflow HASH_DEMULTIPLEXING { HASH_SUMMARY(ch_summary,params.bff_methods) - ch_versions = ch_versions.mix(HASH_SUMMARY.out.versions) - emit: summary_assignment = HASH_SUMMARY.out.assignment summary_classification = HASH_SUMMARY.out.classification diff --git a/subworkflows/local/utils_nfcore_hadge_pipeline/main.nf b/subworkflows/local/utils_nfcore_hadge_pipeline/main.nf index 375d7bce..e29d5e30 100644 --- a/subworkflows/local/utils_nfcore_hadge_pipeline/main.nf +++ b/subworkflows/local/utils_nfcore_hadge_pipeline/main.nf @@ -36,8 +36,6 @@ workflow PIPELINE_INITIALISATION { main: - ch_versions = channel.empty() - // // Print version and exit if required and dump pipeline parameters to JSON file // @@ -113,7 +111,6 @@ workflow PIPELINE_INITIALISATION { emit: samplesheet = ch_samplesheet - versions = ch_versions } /* diff --git a/tests/.nftignore b/tests/.nftignore index 82efe04c..b1751523 100644 --- a/tests/.nftignore +++ b/tests/.nftignore @@ -13,6 +13,7 @@ hashing/demuxem/** hashing/gmm-demux/*/GMM_full.csv hashing/htodemux/*/*_htodemux.rds hashing/multiseqdemux/*/*_multiseqdemux.rds +hashing/htodemux/*/visualization/*_tSNE_htodemux.jpeg hashing/preprocessing/*/*_preprocessed.rds genetic/popscle/freemuxlet/*/*.clust1.vcf.gz genetic/souporcell/*/*/ambient_rna.txt diff --git a/tests/default.nf.test.snap b/tests/default.nf.test.snap index 1834d911..ad1c1117 100644 --- a/tests/default.nf.test.snap +++ b/tests/default.nf.test.snap @@ -10,6 +10,13 @@ "CELLSNP_MODEA": { "cellsnp": "1.2.3" }, + "CREATE_ANNDATA_MUDATA": { + "anndata": "0.12.7", + "mudata": "0.3.2", + "pandas": "2.3.3", + "python": "3.14.2", + "scanpy": "1.11.5" + }, "DEMUXEM": "echo 0.1.7.post1", "DONOR_MATCH": { "r-base": "4.5.2", @@ -17,6 +24,11 @@ "r-pheatmap": "1.0.13", "r-tidyverse": "2.0.0" }, + "EXTRACT_HASHES": { + "gzip": 1.14, + "gawk": "5.3.1", + "coreutils": 9.5 + }, "FIND_VARIANTS": { "r-base": "4.5.2", "r-complexupset": "1.3.3", @@ -51,16 +63,20 @@ "python": "3.12.12", "scanpy": "1.11.5" }, + "HTODEMUX": { + "r-base": "4.4.3", + "r-seurat": "5.3.0" + }, "HTODEMUX_VISUALIZATION": { "r-base": "4.4.3", "r-seurat": "5.3.0", "r-ggplot2": "3.5.2" }, "JOIN_RESULTS_ASSIGNMENT": { - "csvtk": "0.31.0" + "csvtk": "0.37.0" }, "JOIN_RESULTS_CLASSIFICATION": { - "csvtk": "0.31.0" + "csvtk": "0.37.0" }, "MTXCONVERT_HTO": { "r-base": "4.4.3", @@ -88,13 +104,13 @@ "seurat": "5.3.0" }, "SAMTOOLS_INDEX": { - "samtools": 1.21 + "samtools": "1.23.1" }, "SAMTOOLS_SORT": { - "samtools": 1.21 + "samtools": "1.23.1" }, "SAMTOOLS_VIEW": { - "samtools": 1.21 + "samtools": "1.23.1" }, "SOUPORCELL": { "souporcell": 2.5 @@ -103,7 +119,7 @@ "bcftools": 1.22 }, "UMITOOLS_DEDUP": { - "umitools": "1.1.5" + "umitools": "1.1.6" }, "UNTAR_HTO": { "untar": 1.34 @@ -1052,9 +1068,9 @@ "test2.lmix:md5,39b16209e7c2238020024c5611ef10c5", "test3.clust1.samples.gz:md5,d58f19c489213685176f03982dd8ea1e", "test3.lmix:md5,39b16209e7c2238020024c5611ef10c5", - "clusters.tsv:md5,33ae22fc6e9cbb12b2d4a1d48b3da2e1", - "clusters.tsv:md5,33ae22fc6e9cbb12b2d4a1d48b3da2e1", - "clusters.tsv:md5,33ae22fc6e9cbb12b2d4a1d48b3da2e1", + "clusters.tsv:md5,408fb8c0433868d68c5463dee1ec8a3c", + "clusters.tsv:md5,408fb8c0433868d68c5463dee1ec8a3c", + "clusters.tsv:md5,408fb8c0433868d68c5463dee1ec8a3c", "test1_genetic_overview_assignment.csv:md5,6e912c78c50b84deffdaf9fb5e8e3b20", "test1_genetic_overview_classification.csv:md5,812a950b1eef4515079b2d7028b6658c", "test1_genetic_summary_assignment.csv:md5,d99032c058450687415d441e474cef25", @@ -1067,41 +1083,41 @@ "test3_genetic_overview_classification.csv:md5,812a950b1eef4515079b2d7028b6658c", "test3_genetic_summary_assignment.csv:md5,d99032c058450687415d441e474cef25", "test3_genetic_summary_classification.csv:md5,fdfbf54c3c34746624fa6afdaead2f50", - "test1.base.vcf.gz:md5,36eccb4bd3e2130f395985e09a66a60d", - "test1.cells.vcf.gz:md5,4428343f7d9e3e310c14cfab8b9a57f1", + "test1.base.vcf.gz:md5,c381d7e92163710dfca2306d0d9b73f1", + "test1.cells.vcf.gz:md5,f2237f599444ca39fa202b2789dc5c36", "test1.samples.tsv:md5,4ee35dd786d28bfad433c5c2b2a00e7a", - "test1.tag.AD.mtx:md5,a76d9f63c6abdcab00f583fac269a98e", - "test1.tag.DP.mtx:md5,6f8e54bbaa8ad9dcaa42c15eff1e1351", + "test1.tag.AD.mtx:md5,59641a7fccbd20da984734c9a6a62d17", + "test1.tag.DP.mtx:md5,dcf051758a2f21372ce273b1ccc7fc90", "test1.tag.OTH.mtx:md5,45e64ea3b3b8e8a7126690d9e775d501", - "test1_GT_donors.vireo.vcf.gz:md5,60bfaaf61b8e7f5fc2bd0950e576c3e5", - "test1_donor_ids.tsv:md5,a9ea6bc5851ecaff5a11abf54c5289cc", + "test1_GT_donors.vireo.vcf.gz:md5,8f7ab71e3b0aa2635d4b5d1349d221d0", + "test1_donor_ids.tsv:md5,3f425382307931a725fa3031401dbdf7", "test1_filtered_variants.tsv:md5,d37231135d9e6e330c03ed0647fcaf0e", "test1_prob_doublet.tsv.gz:md5,68b4ec74b77aa58421d73df2dbc92e34", - "test1_prob_singlet.tsv.gz:md5,b22717700bc3f7e846f6f831466f0dfd", + "test1_prob_singlet.tsv.gz:md5,5ea247ca9d0483389a730340f3f34d0d", "test1_summary.tsv:md5,94b0ce40cc86bbe7fbacebf98e685ee7", - "test2.base.vcf.gz:md5,36eccb4bd3e2130f395985e09a66a60d", - "test2.cells.vcf.gz:md5,4428343f7d9e3e310c14cfab8b9a57f1", + "test2.base.vcf.gz:md5,c381d7e92163710dfca2306d0d9b73f1", + "test2.cells.vcf.gz:md5,f2237f599444ca39fa202b2789dc5c36", "test2.samples.tsv:md5,4ee35dd786d28bfad433c5c2b2a00e7a", - "test2.tag.AD.mtx:md5,a76d9f63c6abdcab00f583fac269a98e", - "test2.tag.DP.mtx:md5,6f8e54bbaa8ad9dcaa42c15eff1e1351", + "test2.tag.AD.mtx:md5,59641a7fccbd20da984734c9a6a62d17", + "test2.tag.DP.mtx:md5,dcf051758a2f21372ce273b1ccc7fc90", "test2.tag.OTH.mtx:md5,45e64ea3b3b8e8a7126690d9e775d501", - "test2_GT_donors.vireo.vcf.gz:md5,60bfaaf61b8e7f5fc2bd0950e576c3e5", - "test2_donor_ids.tsv:md5,a9ea6bc5851ecaff5a11abf54c5289cc", + "test2_GT_donors.vireo.vcf.gz:md5,8f7ab71e3b0aa2635d4b5d1349d221d0", + "test2_donor_ids.tsv:md5,3f425382307931a725fa3031401dbdf7", "test2_filtered_variants.tsv:md5,d37231135d9e6e330c03ed0647fcaf0e", "test2_prob_doublet.tsv.gz:md5,68b4ec74b77aa58421d73df2dbc92e34", - "test2_prob_singlet.tsv.gz:md5,b22717700bc3f7e846f6f831466f0dfd", + "test2_prob_singlet.tsv.gz:md5,5ea247ca9d0483389a730340f3f34d0d", "test2_summary.tsv:md5,94b0ce40cc86bbe7fbacebf98e685ee7", - "test3.base.vcf.gz:md5,36eccb4bd3e2130f395985e09a66a60d", - "test3.cells.vcf.gz:md5,4428343f7d9e3e310c14cfab8b9a57f1", + "test3.base.vcf.gz:md5,c381d7e92163710dfca2306d0d9b73f1", + "test3.cells.vcf.gz:md5,f2237f599444ca39fa202b2789dc5c36", "test3.samples.tsv:md5,4ee35dd786d28bfad433c5c2b2a00e7a", - "test3.tag.AD.mtx:md5,a76d9f63c6abdcab00f583fac269a98e", - "test3.tag.DP.mtx:md5,6f8e54bbaa8ad9dcaa42c15eff1e1351", + "test3.tag.AD.mtx:md5,59641a7fccbd20da984734c9a6a62d17", + "test3.tag.DP.mtx:md5,dcf051758a2f21372ce273b1ccc7fc90", "test3.tag.OTH.mtx:md5,45e64ea3b3b8e8a7126690d9e775d501", - "test3_GT_donors.vireo.vcf.gz:md5,60bfaaf61b8e7f5fc2bd0950e576c3e5", - "test3_donor_ids.tsv:md5,a9ea6bc5851ecaff5a11abf54c5289cc", + "test3_GT_donors.vireo.vcf.gz:md5,8f7ab71e3b0aa2635d4b5d1349d221d0", + "test3_donor_ids.tsv:md5,3f425382307931a725fa3031401dbdf7", "test3_filtered_variants.tsv:md5,d37231135d9e6e330c03ed0647fcaf0e", "test3_prob_doublet.tsv.gz:md5,68b4ec74b77aa58421d73df2dbc92e34", - "test3_prob_singlet.tsv.gz:md5,b22717700bc3f7e846f6f831466f0dfd", + "test3_prob_singlet.tsv.gz:md5,5ea247ca9d0483389a730340f3f34d0d", "test3_summary.tsv:md5,94b0ce40cc86bbe7fbacebf98e685ee7", "test1_assignment_bff.csv:md5,a62d56a7a5b9b59765d2ae759773aca2", "test1_metrics_bff.csv:md5,1d7751cf035c9e736e4de5504ff87a6a", @@ -1163,7 +1179,6 @@ "test1_featureScatter_htodemux.jpeg:md5,c67268ac0ff18c485899b4ee58f8f948", "test1_heatMap_htodemux.jpeg:md5,2ebdb027344db91ae89deaa3d1011993", "test1_ridge_htodemux.jpeg:md5,8bbc209b475b437a80f853277daa4ce9", - "test1_tSNE_htodemux.jpeg:md5,470812a92d09b88ea6e33bcdadb13f11", "test1_violinPlot_htodemux.jpeg:md5,ef866e13e18a31ab43370299245d33c7", "test1_visual_params_htodemux.csv:md5,0a175b9868be2b4447867ab38705c11d", "test2_assignment_htodemux.csv:md5,7217cd9d30a3c6f7f69e8b4f8aaa679d", @@ -1172,7 +1187,6 @@ "test2_featureScatter_htodemux.jpeg:md5,c67268ac0ff18c485899b4ee58f8f948", "test2_heatMap_htodemux.jpeg:md5,2ebdb027344db91ae89deaa3d1011993", "test2_ridge_htodemux.jpeg:md5,8bbc209b475b437a80f853277daa4ce9", - "test2_tSNE_htodemux.jpeg:md5,470812a92d09b88ea6e33bcdadb13f11", "test2_violinPlot_htodemux.jpeg:md5,ef866e13e18a31ab43370299245d33c7", "test2_visual_params_htodemux.csv:md5,062e94620e3207810ddf2c65bf0d2914", "test3_assignment_htodemux.csv:md5,7217cd9d30a3c6f7f69e8b4f8aaa679d", @@ -1181,7 +1195,6 @@ "test3_featureScatter_htodemux.jpeg:md5,c67268ac0ff18c485899b4ee58f8f948", "test3_heatMap_htodemux.jpeg:md5,2ebdb027344db91ae89deaa3d1011993", "test3_ridge_htodemux.jpeg:md5,8bbc209b475b437a80f853277daa4ce9", - "test3_tSNE_htodemux.jpeg:md5,470812a92d09b88ea6e33bcdadb13f11", "test3_violinPlot_htodemux.jpeg:md5,ef866e13e18a31ab43370299245d33c7", "test3_visual_params_htodemux.csv:md5,b6f8f5593aeb86d846292f2f23fc518d", "test1_params_multiseqdemux.csv:md5,60a096f79d78655870aef2af9130cf9d", @@ -1220,30 +1233,30 @@ "test3_hashing.h5ad:md5,bca9eaccdeb92a5804a72889c67cddb3" ], [ - "test1_donor_specific.vcf.gz:md5,8c51c5ed9e880089a8532e30a659adac", + "test1_donor_specific.vcf.gz:md5,e84ad86464a6096f9c4c33858cdc52d", "test1_vireo.vcf.gz:md5,6e4bbad3892e84cd1dd53c8fc0579977", - "test2_donor_specific.vcf.gz:md5,8c51c5ed9e880089a8532e30a659adac", + "test2_donor_specific.vcf.gz:md5,e84ad86464a6096f9c4c33858cdc52d", "test2_vireo.vcf.gz:md5,6e4bbad3892e84cd1dd53c8fc0579977", - "test3_donor_specific.vcf.gz:md5,8c51c5ed9e880089a8532e30a659adac", + "test3_donor_specific.vcf.gz:md5,e84ad86464a6096f9c4c33858cdc52d", "test3_vireo.vcf.gz:md5,6e4bbad3892e84cd1dd53c8fc0579977", "test1.clust1.vcf.gz:md5,8de6d22ca83fcc331f3d49d59973e5ac", "test2.clust1.vcf.gz:md5,8de6d22ca83fcc331f3d49d59973e5ac", "test3.clust1.vcf.gz:md5,8de6d22ca83fcc331f3d49d59973e5ac", - "test1.base.vcf.gz:md5,cb2af96582fa4efe089ec4e0b3f1644c", - "test1.cells.vcf.gz:md5,349e2d3b9abb1e346f0cebb621b9d7f4", - "test1_GT_donors.vireo.vcf.gz:md5,b92d531491566870c247cf3a122b2dc1", - "test2.base.vcf.gz:md5,cb2af96582fa4efe089ec4e0b3f1644c", - "test2.cells.vcf.gz:md5,349e2d3b9abb1e346f0cebb621b9d7f4", - "test2_GT_donors.vireo.vcf.gz:md5,b92d531491566870c247cf3a122b2dc1", - "test3.base.vcf.gz:md5,cb2af96582fa4efe089ec4e0b3f1644c", - "test3.cells.vcf.gz:md5,349e2d3b9abb1e346f0cebb621b9d7f4", - "test3_GT_donors.vireo.vcf.gz:md5,b92d531491566870c247cf3a122b2dc1" + "test1.base.vcf.gz:md5,6a426c9fc04cd6c52023d12c3e7b294", + "test1.cells.vcf.gz:md5,df70201204c42838109e76955418f112", + "test1_GT_donors.vireo.vcf.gz:md5,921ce18ca982b86589477432fce4e3e6", + "test2.base.vcf.gz:md5,6a426c9fc04cd6c52023d12c3e7b294", + "test2.cells.vcf.gz:md5,df70201204c42838109e76955418f112", + "test2_GT_donors.vireo.vcf.gz:md5,921ce18ca982b86589477432fce4e3e6", + "test3.base.vcf.gz:md5,6a426c9fc04cd6c52023d12c3e7b294", + "test3.cells.vcf.gz:md5,df70201204c42838109e76955418f112", + "test3_GT_donors.vireo.vcf.gz:md5,921ce18ca982b86589477432fce4e3e6" ] ], - "timestamp": "2026-05-16T08:57:49.225710526", + "timestamp": "2026-06-25T13:53:35.37241", "meta": { - "nf-test": "0.9.4", - "nextflow": "25.10.4" + "nf-test": "0.9.5", + "nextflow": "26.04.3" } } } \ No newline at end of file diff --git a/tests/test_genetic.nf.test.snap b/tests/test_genetic.nf.test.snap index ac8e3ace..1dcf8fac 100644 --- a/tests/test_genetic.nf.test.snap +++ b/tests/test_genetic.nf.test.snap @@ -5,6 +5,13 @@ "CELLSNP_MODEA": { "cellsnp": "1.2.3" }, + "CREATE_ANNDATA_MUDATA": { + "anndata": "0.12.7", + "mudata": "0.3.2", + "pandas": "2.3.3", + "python": "3.14.2", + "scanpy": "1.11.5" + }, "DONOR_MATCH": { "r-base": "4.5.2", "r-data.table": "1.17.8", @@ -27,19 +34,19 @@ "popscle": 0.1 }, "SAMTOOLS_INDEX": { - "samtools": 1.21 + "samtools": "1.23.1" }, "SAMTOOLS_SORT": { - "samtools": 1.21 + "samtools": "1.23.1" }, "SAMTOOLS_VIEW": { - "samtools": 1.21 + "samtools": "1.23.1" }, "SOUPORCELL": { "souporcell": 2.5 }, "UMITOOLS_DEDUP": { - "umitools": "1.1.5" + "umitools": "1.1.6" }, "VIREO": { "vireo": "v0.5.8" @@ -195,9 +202,9 @@ "test2.lmix:md5,39b16209e7c2238020024c5611ef10c5", "test3.clust1.samples.gz:md5,d58f19c489213685176f03982dd8ea1e", "test3.lmix:md5,39b16209e7c2238020024c5611ef10c5", - "clusters.tsv:md5,33ae22fc6e9cbb12b2d4a1d48b3da2e1", - "clusters.tsv:md5,33ae22fc6e9cbb12b2d4a1d48b3da2e1", - "clusters.tsv:md5,33ae22fc6e9cbb12b2d4a1d48b3da2e1", + "clusters.tsv:md5,408fb8c0433868d68c5463dee1ec8a3c", + "clusters.tsv:md5,408fb8c0433868d68c5463dee1ec8a3c", + "clusters.tsv:md5,408fb8c0433868d68c5463dee1ec8a3c", "test1_genetic_overview_assignment.csv:md5,6e912c78c50b84deffdaf9fb5e8e3b20", "test1_genetic_overview_classification.csv:md5,812a950b1eef4515079b2d7028b6658c", "test1_genetic_summary_assignment.csv:md5,d99032c058450687415d441e474cef25", @@ -210,41 +217,41 @@ "test3_genetic_overview_classification.csv:md5,812a950b1eef4515079b2d7028b6658c", "test3_genetic_summary_assignment.csv:md5,d99032c058450687415d441e474cef25", "test3_genetic_summary_classification.csv:md5,fdfbf54c3c34746624fa6afdaead2f50", - "test1.base.vcf.gz:md5,36eccb4bd3e2130f395985e09a66a60d", - "test1.cells.vcf.gz:md5,4428343f7d9e3e310c14cfab8b9a57f1", + "test1.base.vcf.gz:md5,c381d7e92163710dfca2306d0d9b73f1", + "test1.cells.vcf.gz:md5,f2237f599444ca39fa202b2789dc5c36", "test1.samples.tsv:md5,4ee35dd786d28bfad433c5c2b2a00e7a", - "test1.tag.AD.mtx:md5,a76d9f63c6abdcab00f583fac269a98e", - "test1.tag.DP.mtx:md5,6f8e54bbaa8ad9dcaa42c15eff1e1351", + "test1.tag.AD.mtx:md5,59641a7fccbd20da984734c9a6a62d17", + "test1.tag.DP.mtx:md5,dcf051758a2f21372ce273b1ccc7fc90", "test1.tag.OTH.mtx:md5,45e64ea3b3b8e8a7126690d9e775d501", - "test1_GT_donors.vireo.vcf.gz:md5,60bfaaf61b8e7f5fc2bd0950e576c3e5", - "test1_donor_ids.tsv:md5,a9ea6bc5851ecaff5a11abf54c5289cc", + "test1_GT_donors.vireo.vcf.gz:md5,8f7ab71e3b0aa2635d4b5d1349d221d0", + "test1_donor_ids.tsv:md5,3f425382307931a725fa3031401dbdf7", "test1_filtered_variants.tsv:md5,d37231135d9e6e330c03ed0647fcaf0e", "test1_prob_doublet.tsv.gz:md5,68b4ec74b77aa58421d73df2dbc92e34", - "test1_prob_singlet.tsv.gz:md5,b22717700bc3f7e846f6f831466f0dfd", + "test1_prob_singlet.tsv.gz:md5,5ea247ca9d0483389a730340f3f34d0d", "test1_summary.tsv:md5,94b0ce40cc86bbe7fbacebf98e685ee7", - "test2.base.vcf.gz:md5,36eccb4bd3e2130f395985e09a66a60d", - "test2.cells.vcf.gz:md5,4428343f7d9e3e310c14cfab8b9a57f1", + "test2.base.vcf.gz:md5,c381d7e92163710dfca2306d0d9b73f1", + "test2.cells.vcf.gz:md5,f2237f599444ca39fa202b2789dc5c36", "test2.samples.tsv:md5,4ee35dd786d28bfad433c5c2b2a00e7a", - "test2.tag.AD.mtx:md5,a76d9f63c6abdcab00f583fac269a98e", - "test2.tag.DP.mtx:md5,6f8e54bbaa8ad9dcaa42c15eff1e1351", + "test2.tag.AD.mtx:md5,59641a7fccbd20da984734c9a6a62d17", + "test2.tag.DP.mtx:md5,dcf051758a2f21372ce273b1ccc7fc90", "test2.tag.OTH.mtx:md5,45e64ea3b3b8e8a7126690d9e775d501", - "test2_GT_donors.vireo.vcf.gz:md5,60bfaaf61b8e7f5fc2bd0950e576c3e5", - "test2_donor_ids.tsv:md5,a9ea6bc5851ecaff5a11abf54c5289cc", + "test2_GT_donors.vireo.vcf.gz:md5,8f7ab71e3b0aa2635d4b5d1349d221d0", + "test2_donor_ids.tsv:md5,3f425382307931a725fa3031401dbdf7", "test2_filtered_variants.tsv:md5,d37231135d9e6e330c03ed0647fcaf0e", "test2_prob_doublet.tsv.gz:md5,68b4ec74b77aa58421d73df2dbc92e34", - "test2_prob_singlet.tsv.gz:md5,b22717700bc3f7e846f6f831466f0dfd", + "test2_prob_singlet.tsv.gz:md5,5ea247ca9d0483389a730340f3f34d0d", "test2_summary.tsv:md5,94b0ce40cc86bbe7fbacebf98e685ee7", - "test3.base.vcf.gz:md5,36eccb4bd3e2130f395985e09a66a60d", - "test3.cells.vcf.gz:md5,4428343f7d9e3e310c14cfab8b9a57f1", + "test3.base.vcf.gz:md5,c381d7e92163710dfca2306d0d9b73f1", + "test3.cells.vcf.gz:md5,f2237f599444ca39fa202b2789dc5c36", "test3.samples.tsv:md5,4ee35dd786d28bfad433c5c2b2a00e7a", - "test3.tag.AD.mtx:md5,a76d9f63c6abdcab00f583fac269a98e", - "test3.tag.DP.mtx:md5,6f8e54bbaa8ad9dcaa42c15eff1e1351", + "test3.tag.AD.mtx:md5,59641a7fccbd20da984734c9a6a62d17", + "test3.tag.DP.mtx:md5,dcf051758a2f21372ce273b1ccc7fc90", "test3.tag.OTH.mtx:md5,45e64ea3b3b8e8a7126690d9e775d501", - "test3_GT_donors.vireo.vcf.gz:md5,60bfaaf61b8e7f5fc2bd0950e576c3e5", - "test3_donor_ids.tsv:md5,a9ea6bc5851ecaff5a11abf54c5289cc", + "test3_GT_donors.vireo.vcf.gz:md5,8f7ab71e3b0aa2635d4b5d1349d221d0", + "test3_donor_ids.tsv:md5,3f425382307931a725fa3031401dbdf7", "test3_filtered_variants.tsv:md5,d37231135d9e6e330c03ed0647fcaf0e", "test3_prob_doublet.tsv.gz:md5,68b4ec74b77aa58421d73df2dbc92e34", - "test3_prob_singlet.tsv.gz:md5,b22717700bc3f7e846f6f831466f0dfd", + "test3_prob_singlet.tsv.gz:md5,5ea247ca9d0483389a730340f3f34d0d", "test3_summary.tsv:md5,94b0ce40cc86bbe7fbacebf98e685ee7", "multiqc_citations.txt:md5,4c806e63a283ec1b7e78cdae3a923d4f" ], @@ -252,21 +259,21 @@ "test1.clust1.vcf.gz:md5,8de6d22ca83fcc331f3d49d59973e5ac", "test2.clust1.vcf.gz:md5,8de6d22ca83fcc331f3d49d59973e5ac", "test3.clust1.vcf.gz:md5,8de6d22ca83fcc331f3d49d59973e5ac", - "test1.base.vcf.gz:md5,cb2af96582fa4efe089ec4e0b3f1644c", - "test1.cells.vcf.gz:md5,349e2d3b9abb1e346f0cebb621b9d7f4", - "test1_GT_donors.vireo.vcf.gz:md5,b92d531491566870c247cf3a122b2dc1", - "test2.base.vcf.gz:md5,cb2af96582fa4efe089ec4e0b3f1644c", - "test2.cells.vcf.gz:md5,349e2d3b9abb1e346f0cebb621b9d7f4", - "test2_GT_donors.vireo.vcf.gz:md5,b92d531491566870c247cf3a122b2dc1", - "test3.base.vcf.gz:md5,cb2af96582fa4efe089ec4e0b3f1644c", - "test3.cells.vcf.gz:md5,349e2d3b9abb1e346f0cebb621b9d7f4", - "test3_GT_donors.vireo.vcf.gz:md5,b92d531491566870c247cf3a122b2dc1" + "test1.base.vcf.gz:md5,6a426c9fc04cd6c52023d12c3e7b294", + "test1.cells.vcf.gz:md5,df70201204c42838109e76955418f112", + "test1_GT_donors.vireo.vcf.gz:md5,921ce18ca982b86589477432fce4e3e6", + "test2.base.vcf.gz:md5,6a426c9fc04cd6c52023d12c3e7b294", + "test2.cells.vcf.gz:md5,df70201204c42838109e76955418f112", + "test2_GT_donors.vireo.vcf.gz:md5,921ce18ca982b86589477432fce4e3e6", + "test3.base.vcf.gz:md5,6a426c9fc04cd6c52023d12c3e7b294", + "test3.cells.vcf.gz:md5,df70201204c42838109e76955418f112", + "test3_GT_donors.vireo.vcf.gz:md5,921ce18ca982b86589477432fce4e3e6" ] ], - "timestamp": "2026-04-23T17:26:19.929849031", + "timestamp": "2026-06-25T12:16:23.520507", "meta": { - "nf-test": "0.9.4", - "nextflow": "25.10.4" + "nf-test": "0.9.5", + "nextflow": "26.04.3" } } } \ No newline at end of file diff --git a/tests/test_hashing.nf.test.snap b/tests/test_hashing.nf.test.snap index 6bf397a8..23c96294 100644 --- a/tests/test_hashing.nf.test.snap +++ b/tests/test_hashing.nf.test.snap @@ -7,6 +7,13 @@ "r-seurat": "4.3.0.1", "cellhashR": "1.0.3" }, + "CREATE_ANNDATA_MUDATA": { + "anndata": "0.12.7", + "mudata": "0.3.2", + "pandas": "2.3.3", + "python": "3.14.2", + "scanpy": "1.11.5" + }, "DEMUXEM": "echo 0.1.7.post1", "DONOR_MATCH": { "r-base": "4.5.2", @@ -14,6 +21,11 @@ "r-pheatmap": "1.0.13", "r-tidyverse": "2.0.0" }, + "EXTRACT_HASHES": { + "gzip": 1.14, + "gawk": "5.3.1", + "coreutils": 9.5 + }, "GMMDEMUX": { "GMM-Demux": "0.2.2.3" }, @@ -35,6 +47,10 @@ "python": "3.12.12", "scanpy": "1.11.5" }, + "HTODEMUX": { + "r-base": "4.4.3", + "r-seurat": "5.3.0" + }, "HTODEMUX_VISUALIZATION": { "r-base": "4.4.3", "r-seurat": "5.3.0", @@ -908,7 +924,6 @@ "test1_featureScatter_htodemux.jpeg:md5,c67268ac0ff18c485899b4ee58f8f948", "test1_heatMap_htodemux.jpeg:md5,2ebdb027344db91ae89deaa3d1011993", "test1_ridge_htodemux.jpeg:md5,8bbc209b475b437a80f853277daa4ce9", - "test1_tSNE_htodemux.jpeg:md5,470812a92d09b88ea6e33bcdadb13f11", "test1_violinPlot_htodemux.jpeg:md5,ef866e13e18a31ab43370299245d33c7", "test1_visual_params_htodemux.csv:md5,0a175b9868be2b4447867ab38705c11d", "test2_assignment_htodemux.csv:md5,7217cd9d30a3c6f7f69e8b4f8aaa679d", @@ -917,7 +932,6 @@ "test2_featureScatter_htodemux.jpeg:md5,c67268ac0ff18c485899b4ee58f8f948", "test2_heatMap_htodemux.jpeg:md5,2ebdb027344db91ae89deaa3d1011993", "test2_ridge_htodemux.jpeg:md5,8bbc209b475b437a80f853277daa4ce9", - "test2_tSNE_htodemux.jpeg:md5,470812a92d09b88ea6e33bcdadb13f11", "test2_violinPlot_htodemux.jpeg:md5,ef866e13e18a31ab43370299245d33c7", "test2_visual_params_htodemux.csv:md5,062e94620e3207810ddf2c65bf0d2914", "test3_assignment_htodemux.csv:md5,7217cd9d30a3c6f7f69e8b4f8aaa679d", @@ -926,7 +940,6 @@ "test3_featureScatter_htodemux.jpeg:md5,c67268ac0ff18c485899b4ee58f8f948", "test3_heatMap_htodemux.jpeg:md5,2ebdb027344db91ae89deaa3d1011993", "test3_ridge_htodemux.jpeg:md5,8bbc209b475b437a80f853277daa4ce9", - "test3_tSNE_htodemux.jpeg:md5,470812a92d09b88ea6e33bcdadb13f11", "test3_violinPlot_htodemux.jpeg:md5,ef866e13e18a31ab43370299245d33c7", "test3_visual_params_htodemux.csv:md5,b6f8f5593aeb86d846292f2f23fc518d", "test1_params_multiseqdemux.csv:md5,60a096f79d78655870aef2af9130cf9d", @@ -960,10 +973,10 @@ ], "No VCF files" ], - "timestamp": "2026-04-23T17:28:30.528242288", + "timestamp": "2026-06-25T14:03:38.851764", "meta": { - "nf-test": "0.9.4", - "nextflow": "25.10.4" + "nf-test": "0.9.5", + "nextflow": "26.04.3" } } } \ No newline at end of file diff --git a/workflows/hadge.nf b/workflows/hadge.nf index f6b224d5..a3f04e33 100644 --- a/workflows/hadge.nf +++ b/workflows/hadge.nf @@ -59,18 +59,19 @@ workflow HADGE { } UNTAR_RNA(ch_rna.tar) - ch_versions = ch_versions.mix(UNTAR_RNA.out.versions) UNTAR_HTO(ch_hto.tar) - ch_versions = ch_versions.mix(UNTAR_HTO.out.versions) ch_rna = ch_rna.directory.mix(UNTAR_RNA.out.untar) ch_hto = ch_hto.directory.mix(UNTAR_HTO.out.untar) // extract hto names (hto can be null in genetic or donor_match mode) - ch_hashes_non_null = EXTRACT_HASHES(ch_hto.filter { _meta, hto -> hto != null }) - ch_hashes_null = ch_hto.filter { _meta, hto -> hto == null } - ch_hashes = ch_hashes_non_null.mix(ch_hashes_null) + ch_hto_by_presence = ch_hto.branch { _meta, hto -> + is_null: hto == null + not_null: true + } + EXTRACT_HASHES(ch_hto_by_presence.not_null) + ch_hashes = ch_hto_by_presence.is_null.mix(EXTRACT_HASHES.out.hashes) // join preprocessed channels ch_remaining_input = ch_samplesheet.map { meta, _rna, _hto, bam, barcodes, vcf -> [meta, bam, barcodes, vcf] } @@ -177,8 +178,6 @@ workflow HADGE { ch_versions = ch_versions.mix(GENETIC_DEMULTIPLEXING.out.versions) ch_versions = ch_versions.mix(HASH_DEMULTIPLEXING.out.versions) - ch_versions = ch_versions.mix(JOIN_RESULTS_ASSIGNMENT.out.versions) - ch_versions = ch_versions.mix(JOIN_RESULTS_CLASSIFICATION.out.versions) } else if ( params.mode == 'donor_match' ){ @@ -246,12 +245,8 @@ workflow HADGE { .combine(DONOR_MATCH.out.best_donor_match, by: 0) SUBSET_GT_DONORS(ch_subset_gt_donors) - - ch_versions = ch_versions.mix(SUBSET_GT_DONORS.out.versions) } - ch_versions = ch_versions.mix(FIND_VARIANTS.out.versions) } - ch_versions = ch_versions.mix(DONOR_MATCH.out.versions) } // From 20357ab632fea4a898d8a686a179d37b6bed70ee Mon Sep 17 00:00:00 2001 From: Nico Trummer Date: Tue, 30 Jun 2026 13:35:59 +0200 Subject: [PATCH 45/74] Fix BFF meta map types and record updated module SHA. (#111) Correct meta output types in bff/meta.yml after the nf-core module update and sync modules.json. --- modules.json | 2 +- modules/nf-core/bff/meta.yml | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/modules.json b/modules.json index 94436547..a6348cf3 100644 --- a/modules.json +++ b/modules.json @@ -7,7 +7,7 @@ "nf-core": { "bff": { "branch": "master", - "git_sha": "6d46786420b4d7bc88eba026eb389c0c5535d120", + "git_sha": "5549501d6db037a4872d207635ce329878714408", "installed_by": ["modules"] }, "cellsnp/modea": { diff --git a/modules/nf-core/bff/meta.yml b/modules/nf-core/bff/meta.yml index 5084bafe..87fcc58e 100644 --- a/modules/nf-core/bff/meta.yml +++ b/modules/nf-core/bff/meta.yml @@ -43,7 +43,7 @@ input: output: assignment: - - meta: - type: file + type: map description: | Groovy Map containing sample information e.g. `[ id:'sample1' ]` @@ -56,7 +56,7 @@ output: - edam: http://edamontology.org/format_3752 metrics: - - meta: - type: file + type: map description: | Groovy Map containing sample information e.g. `[ id:'sample1' ]` @@ -69,7 +69,7 @@ output: - edam: http://edamontology.org/format_3752 params: - - meta: - type: file + type: map description: | Groovy Map containing sample information e.g. `[ id:'sample1' ]` From 1edc561f5904886a7880fb6521b53f0cd289e9af Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matthias=20H=C3=B6rtenhuber?= Date: Fri, 24 Jul 2026 16:30:36 +0200 Subject: [PATCH 46/74] Remove vulnerable PR-comment artifact pattern (#113) Co-authored-by: nf-core-bot --- .github/workflows/branch.yml | 60 +++++++++----- .github/workflows/linting.yml | 18 ++++ .github/workflows/linting_comment.yml | 28 ------- .github/workflows/nf-test.yml | 57 +++++++++++++ .github/workflows/pr-comment.yml | 82 +++++++++++++++++++ .../workflows/template-version-comment.yml | 56 ++++++++----- .nf-core.yml | 4 + 7 files changed, 234 insertions(+), 71 deletions(-) delete mode 100644 .github/workflows/linting_comment.yml create mode 100644 .github/workflows/pr-comment.yml diff --git a/.github/workflows/branch.yml b/.github/workflows/branch.yml index 4dcd0e56..5188ebe3 100644 --- a/.github/workflows/branch.yml +++ b/.github/workflows/branch.yml @@ -2,11 +2,13 @@ name: nf-core branch protection # This workflow is triggered on PRs to `main`/`master` branch on the repository # It fails when someone tries to make a PR against the nf-core `main`/`master` branch instead of `dev` on: - pull_request_target: + pull_request: branches: - main - master +permissions: {} + jobs: test: runs-on: ubuntu-latest @@ -14,33 +16,47 @@ jobs: # PRs to the nf-core repo main/master branch are only ok if coming from the nf-core repo `dev` or any `patch` branches - name: Check PRs if: github.repository == 'nf-core/hadge' + env: + HEAD_REPO: ${{ github.event.pull_request.head.repo.full_name }} run: | - { [[ ${{github.event.pull_request.head.repo.full_name }} == nf-core/hadge ]] && [[ $GITHUB_HEAD_REF == "dev" ]]; } || [[ $GITHUB_HEAD_REF == "patch" ]] + { [[ "$HEAD_REPO" == nf-core/hadge ]] && [[ $GITHUB_HEAD_REF == "dev" ]]; } || [[ $GITHUB_HEAD_REF == "patch" ]] - # If the above check failed, post a comment on the PR explaining the failure - # NOTE - this doesn't currently work if the PR is coming from a fork, due to limitations in GitHub actions secrets - - name: Post PR comment + # If the above check failed, build a comment to be posted by the shared poster workflow + - name: Build PR comment if: failure() - uses: mshick/add-pr-comment@8e4927817251f1ff60c001f04568532b38e0b4a0 # v3 - with: - message: | - ## This PR is against the `${{github.event.pull_request.base.ref}}` branch :x: + env: + PR_NUMBER: ${{ github.event.pull_request.number }} + BASE_REF: ${{ github.event.pull_request.base.ref }} + HEAD_REPO: ${{ github.event.pull_request.head.repo.full_name }} + PR_USER: ${{ github.event.pull_request.user.login }} + run: | + mkdir -p pr-comment + echo "$PR_NUMBER" > pr-comment/pr_number.txt + echo "branch" > pr-comment/header.txt + cat > pr-comment/comment.md < pr-comment/pr_number.txt + echo "lint" > pr-comment/header.txt + [ -f lint_results.md ] && cp lint_results.md pr-comment/comment.md || true + + - name: Upload PR comment artifact + if: ${{ always() }} + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7 + with: + name: pr-comment + path: pr-comment/ diff --git a/.github/workflows/linting_comment.yml b/.github/workflows/linting_comment.yml deleted file mode 100644 index 5b0c24f7..00000000 --- a/.github/workflows/linting_comment.yml +++ /dev/null @@ -1,28 +0,0 @@ -name: nf-core linting comment -# This workflow is triggered after the linting action is complete -# It posts an automated comment to the PR, even if the PR is coming from a fork - -on: - workflow_run: - workflows: ["nf-core linting"] - -jobs: - test: - runs-on: ubuntu-latest - steps: - - name: Download lint results - uses: dawidd6/action-download-artifact@b6e2e70617bc3265edd6dab6c906732b2f1ae151 # v21 - with: - workflow: linting.yml - workflow_conclusion: completed - - - name: Get PR number - id: pr_number - run: echo "pr_number=$(cat linting-logs/PR_number.txt)" >> $GITHUB_OUTPUT - - - name: Post PR comment - uses: marocchino/sticky-pull-request-comment@70d2764d1a7d5d9560b100cbea0077fc8f633987 # v3 - with: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - number: ${{ steps.pr_number.outputs.pr_number }} - path: linting-logs/lint_results.md diff --git a/.github/workflows/nf-test.yml b/.github/workflows/nf-test.yml index 5e5b3e01..166831c7 100644 --- a/.github/workflows/nf-test.yml +++ b/.github/workflows/nf-test.yml @@ -117,6 +117,22 @@ jobs: fi fi + # continue-on-error keeps latest-everything from failing the job, so it never shows up in + # `needs.nf-test.result` downstream and CI stays green. Surface it via a PR comment instead; + # other NXF_VER failures already fail the job/CI directly, so no comment is needed for those. + - name: Prepare PR comment fragment + if: ${{ always() && steps.run_nf_test.outcome == 'failure' && matrix.NXF_VER == 'latest-everything' }} + run: | + mkdir -p pr-comment-fragment + echo "* ❌ \`${{ matrix.profile }}\` | \`${{ matrix.NXF_VER }}\` | Shard ${{ matrix.shard }}/${{ env.TOTAL_SHARDS }}" > pr-comment-fragment/fragment.md + + - name: Upload PR comment fragment + if: ${{ always() && steps.run_nf_test.outcome == 'failure' && matrix.NXF_VER == 'latest-everything' }} + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7 + with: + name: pr-comment-fragment-${{ strategy.job-index }} + path: pr-comment-fragment/ + confirm-pass: needs: [nf-test] if: always() @@ -143,3 +159,44 @@ jobs: echo "DEBUG: toJSON(needs) = ${{ toJSON(needs) }}" echo "DEBUG: toJSON(needs.*.result) = ${{ toJSON(needs.*.result) }}" echo "::endgroup::" + + - name: Download PR comment fragments + if: ${{ always() }} + uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 + continue-on-error: true + with: + pattern: pr-comment-fragment-* + path: pr-comment-fragments + merge-multiple: true + + # Build a comment for the shared pr-comment.yml poster to publish on the PR. + # Based on the fragments above (not needs.*.result) so non-blocking failures are still reported. + - name: Prepare PR comment + if: ${{ always() }} + env: + PR_NUMBER: ${{ github.event.pull_request.number }} + RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} + run: | + mkdir -p pr-comment + echo "$PR_NUMBER" > pr-comment/pr_number.txt + echo "nf-test" > pr-comment/header.txt + if [ -d pr-comment-fragments ] && [ -n "$(ls -A pr-comment-fragments)" ]; then + { + echo "## ❌ nf-test failed with latest Nextflow version" + echo "" + echo "> [!NOTE]" + echo "> Tests with Nextflow's latest version failed but it will not cause a CI workflow failure." + echo "> Please check if the failure is expected with newer (edge-)releases of Nextflow or if it needs fixing." + echo "" + cat pr-comment-fragments/*.md + echo "" + echo "See the [full run](${RUN_URL}) for details." + } > pr-comment/comment.md + fi + + - name: Upload PR comment artifact + if: ${{ always() }} + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7 + with: + name: pr-comment + path: pr-comment/ diff --git a/.github/workflows/pr-comment.yml b/.github/workflows/pr-comment.yml new file mode 100644 index 00000000..ab7b59dd --- /dev/null +++ b/.github/workflows/pr-comment.yml @@ -0,0 +1,82 @@ +name: Post PR comment +# Shared, privileged comment poster. +# +# This is the single workflow that runs with a write token. It is triggered +# after any of the listed "producer" workflows complete on a pull request. +# Each producer runs untrusted PR code (if any) with a read-only token and +# uploads a `pr-comment` artifact describing the comment to post; this workflow +# only ever reads that plain-text artifact, so no PR code is executed here. +# +# Artifact contract (uploaded by producers under the name `pr-comment`): +# pr_number.txt - the pull request number +# header.txt - sticky-comment identifier (keeps comment types separate) +# comment.md - the Markdown body (omit the file to post nothing) + +on: + workflow_run: + workflows: + - "nf-core linting" + - "nf-core template version comment" + - "nf-core branch protection" + - "Run nf-test" + +permissions: + actions: read + contents: read + pull-requests: write + +jobs: + post-comment: + runs-on: ubuntu-latest + if: github.event.workflow_run.event == 'pull_request' + steps: + - name: Download PR comment artifact + uses: dawidd6/action-download-artifact@b6e2e70617bc3265edd6dab6c906732b2f1ae151 # v21 + with: + run_id: ${{ github.event.workflow_run.id }} + name: pr-comment + path: pr-comment + if_no_artifact_found: ignore + + - name: Read comment metadata + id: meta + run: | + echo "::group::Downloaded pr-comment contents" + ls -la pr-comment 2>/dev/null || echo "No pr-comment/ directory was downloaded." + echo "::endgroup::" + + if [ ! -d pr-comment ]; then + echo "No pr-comment artifact found; nothing to post." + exit 0 + fi + + if [ ! -f pr-comment/comment.md ]; then + echo "Artifact present but no comment.md; nothing to post." + exit 0 + fi + + pr_number=$(cat pr-comment/pr_number.txt) + header=$(cat pr-comment/header.txt) + echo "Found comment.md (header='$header', pr_number='$pr_number')." + + # Guard against anything unexpected ending up in the PR number. + case "$pr_number" in + ''|*[!0-9]*) + echo "Invalid PR number: '$pr_number'" + exit 1 + ;; + esac + + echo "pr_number=$pr_number" >> "$GITHUB_OUTPUT" + echo "header=$header" >> "$GITHUB_OUTPUT" + echo "post=true" >> "$GITHUB_OUTPUT" + echo "Will post comment to PR #${pr_number}." + + - name: Post PR comment + if: steps.meta.outputs.post == 'true' + uses: marocchino/sticky-pull-request-comment@5770ad5eb8f42dd2c4f34da00c94c5381e49af88 # v3.0.5 + with: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + number: ${{ steps.meta.outputs.pr_number }} + header: ${{ steps.meta.outputs.header }} + path: pr-comment/comment.md diff --git a/.github/workflows/template-version-comment.yml b/.github/workflows/template-version-comment.yml index ea30827e..ee102f71 100644 --- a/.github/workflows/template-version-comment.yml +++ b/.github/workflows/template-version-comment.yml @@ -2,10 +2,13 @@ name: nf-core template version comment # This workflow is triggered on PRs to check if the pipeline template version matches the latest nf-core version. # It posts a comment to the PR, even if it comes from a fork. -on: pull_request_target +on: + pull_request: + +permissions: {} jobs: - template_version: + check_template_version: runs-on: ubuntu-latest steps: - name: Check out pipeline code @@ -22,25 +25,36 @@ jobs: - name: Install nf-core run: | python -m pip install --upgrade pip - pip install nf-core==${{ steps.read_yml.outputs['nf_core_version'] }} + pip install nf-core + + - name: Build PR comment if template is outdated + # The fork-controlled version is passed via the environment and only ever + # used as quoted shell data (never interpolated into a command), so it + # cannot be used for script injection. + env: + PR_VERSION: ${{ steps.read_yml.outputs['nf_core_version'] }} + PR_NUMBER: ${{ github.event.pull_request.number }} + run: | + mkdir -p pr-comment + echo "$PR_NUMBER" > pr-comment/pr_number.txt + echo "template-version" > pr-comment/header.txt + + latest_version=$(nf-core --version | grep -oE '[0-9]+\.[0-9]+\.[0-9]+' | head -n1) - - name: Check nf-core outdated - id: nf_core_outdated - run: echo "OUTPUT=$(pip list --outdated | grep nf-core)" >> ${GITHUB_ENV} + if [ -n "$PR_VERSION" ] && [ -n "$latest_version" ] && [ "$PR_VERSION" != "$latest_version" ]; then + cat > pr-comment/comment.md < [!WARNING] + > Newer version of the nf-core template is available. + > + > Your pipeline is using an old version of the nf-core template: ${PR_VERSION}. + > Please update your pipeline to the latest version. + > + > For more documentation on how to update your pipeline, please see the [Synchronisation documentation](https://nf-co.re/docs/developing/template-syncs/overview). + EOF + fi - - name: Post nf-core template version comment - uses: mshick/add-pr-comment@8e4927817251f1ff60c001f04568532b38e0b4a0 # v3 - if: | - contains(env.OUTPUT, 'nf-core') + - name: Upload PR comment artifact + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7 with: - repo-token: ${{ secrets.NF_CORE_BOT_AUTH_TOKEN }} - allow-repeats: false - message: | - > [!WARNING] - > Newer version of the nf-core template is available. - > - > Your pipeline is using an old version of the nf-core template: ${{ steps.read_yml.outputs['nf_core_version'] }}. - > Please update your pipeline to the latest version. - > - > For more documentation on how to update your pipeline, please see the [Synchronisation documentation](https://nf-co.re/docs/developing/template-syncs/overview). - # + name: pr-comment + path: pr-comment/ diff --git a/.nf-core.yml b/.nf-core.yml index 980280a9..782df743 100644 --- a/.nf-core.yml +++ b/.nf-core.yml @@ -1,6 +1,10 @@ lint: files_unchanged: - .github/PULL_REQUEST_TEMPLATE.md + - .github/workflows/branch.yml + - .github/workflows/linting.yml + files_exist: + - .github/workflows/linting_comment.yml nf_core_version: 4.0.2 repository_type: pipeline template: From 442174b85a2f358d988166a044871797ebdb0643 Mon Sep 17 00:00:00 2001 From: nf-core bot Date: Wed, 19 Aug 2026 12:55:28 +0200 Subject: [PATCH 47/74] Important! Template update for nf-core/tools v4.0.3 (#112) * Template update for nf-core/tools version 3.3.2 * Template update for nf-core/tools version 3.3.2 * Template update for nf-core/tools version 4.0.0 * Template update for nf-core/tools version 4.0.2 * Template update for nf-core/tools version 4.0.3 * resolve missing merge conflicts * fix linting errors * update mulitqc * nf-core pipelines lint --dir /workspaces/hadge --fix container_configs * Fix conda container config file linting * Align linting workflow * use stable_name in test * use input instead of params.input --------- Co-authored-by: Nico Trummer Co-authored-by: LuisHeinzlmeier --- .github/actions/nf-test/action.yml | 2 +- .github/workflows/release-announcements.yml | 2 +- .gitignore | 1 - .nf-core.yml | 6 +----- README.md | 2 +- main.nf | 1 + ro-crate-metadata.json | 16 ++++++++-------- .../local/utils_nfcore_hadge_pipeline/main.nf | 5 ++++- 8 files changed, 17 insertions(+), 18 deletions(-) diff --git a/.github/actions/nf-test/action.yml b/.github/actions/nf-test/action.yml index ad686e8e..945c56f0 100644 --- a/.github/actions/nf-test/action.yml +++ b/.github/actions/nf-test/action.yml @@ -37,7 +37,7 @@ runs: - name: Setup apptainer if: contains(inputs.profile, 'singularity') - uses: eWaterCycle/setup-apptainer@3f706d898c9db585b1d741b4692e66755f3a1b40 # v2 + uses: eWaterCycle/setup-apptainer@4bb22c52d4f63406c49e94c804632975787312b3 # v2.0.0 - name: Set up Singularity if: contains(inputs.profile, 'singularity') diff --git a/.github/workflows/release-announcements.yml b/.github/workflows/release-announcements.yml index 78d5dbe0..4974f44f 100644 --- a/.github/workflows/release-announcements.yml +++ b/.github/workflows/release-announcements.yml @@ -18,7 +18,7 @@ jobs: id: get_description run: | echo "description=$(curl -s https://nf-co.re/pipelines.json | jq -r '.remote_workflows[] | select(.full_name == "${{ github.repository }}") | .description')" >> $GITHUB_OUTPUT - - uses: rzr/fediverse-action@563159eb8d45f70ab6aaba36ed55cd037e51f441 # master + - uses: rzr/fediverse-action@66c2cbb5b1997666b0e28d597631b6a4f09a2719 # v0.0.6 with: access-token: ${{ secrets.MASTODON_ACCESS_TOKEN }} host: "mstdn.science" # custom host if not "mastodon.social" (default) diff --git a/.gitignore b/.gitignore index 5516a066..cc2b1a77 100644 --- a/.gitignore +++ b/.gitignore @@ -8,4 +8,3 @@ testing* *.pyc null/ .lineage/ -.nf-test* diff --git a/.nf-core.yml b/.nf-core.yml index 782df743..89b3522d 100644 --- a/.nf-core.yml +++ b/.nf-core.yml @@ -1,11 +1,7 @@ lint: files_unchanged: - .github/PULL_REQUEST_TEMPLATE.md - - .github/workflows/branch.yml - - .github/workflows/linting.yml - files_exist: - - .github/workflows/linting_comment.yml -nf_core_version: 4.0.2 +nf_core_version: 4.0.3 repository_type: pipeline template: author: Fabiola Curion diff --git a/README.md b/README.md index 15b9906b..0c3c60fa 100644 --- a/README.md +++ b/README.md @@ -11,7 +11,7 @@ [![nf-test](https://img.shields.io/badge/unit_tests-nf--test-337ab7.svg)](https://www.nf-test.com) [![Nextflow](https://img.shields.io/badge/version-%E2%89%A525.10.4-green?style=flat&logo=nextflow&logoColor=white&color=%230DC09D&link=https%3A%2F%2Fnextflow.io)](https://www.nextflow.io/) -[![nf-core template version](https://img.shields.io/badge/nf--core_template-4.0.2-green?style=flat&logo=nfcore&logoColor=white&color=%2324B064&link=https%3A%2F%2Fnf-co.re)](https://github.com/nf-core/tools/releases/tag/4.0.2) +[![nf-core template version](https://img.shields.io/badge/nf--core_template-4.0.3-green?style=flat&logo=nfcore&logoColor=white&color=%2324B064&link=https%3A%2F%2Fnf-co.re)](https://github.com/nf-core/tools/releases/tag/4.0.3) [![run with conda](http://img.shields.io/badge/run%20with-conda-3EB049?labelColor=000000&logo=anaconda)](https://docs.conda.io/en/latest/) [![run with docker](https://img.shields.io/badge/run%20with-docker-0db7ed?labelColor=000000&logo=docker)](https://www.docker.com/) [![run with singularity](https://img.shields.io/badge/run%20with-singularity-1d355c.svg?labelColor=000000)](https://sylabs.io/docs/) diff --git a/main.nf b/main.nf index 228b45f0..a55f5984 100644 --- a/main.nf +++ b/main.nf @@ -77,6 +77,7 @@ workflow { params.monochrome_logs, args, params.outdir, + params.input, params.help, params.help_full, params.show_hidden diff --git a/ro-crate-metadata.json b/ro-crate-metadata.json index a3e32dc0..af6aa23c 100644 --- a/ro-crate-metadata.json +++ b/ro-crate-metadata.json @@ -22,8 +22,8 @@ "@id": "./", "@type": "Dataset", "creativeWorkStatus": "InProgress", - "datePublished": "2026-04-30T13:33:34+00:00", - "description": "

    \n \n \n \"nf-core/hadge\"\n \n

    \n\n[![Open in GitHub Codespaces](https://img.shields.io/badge/Open_In_GitHub_Codespaces-black?labelColor=grey&logo=github)](https://github.com/codespaces/new/nf-core/hadge)\n[![GitHub Actions CI Status](https://github.com/nf-core/hadge/actions/workflows/nf-test.yml/badge.svg)](https://github.com/nf-core/hadge/actions/workflows/nf-test.yml)\n[![GitHub Actions Linting Status](https://github.com/nf-core/hadge/actions/workflows/linting.yml/badge.svg)](https://github.com/nf-core/hadge/actions/workflows/linting.yml)[![AWS CI](https://img.shields.io/badge/CI%20tests-full%20size-FF9900?labelColor=000000&logo=Amazon%20AWS)](https://nf-co.re/hadge/results)[![Cite with Zenodo](http://img.shields.io/badge/DOI-10.5281/zenodo.10634731-1073c8?labelColor=000000)](https://doi.org/10.5281/zenodo.10634731)\n[![nf-test](https://img.shields.io/badge/unit_tests-nf--test-337ab7.svg)](https://www.nf-test.com)\n\n[![Nextflow](https://img.shields.io/badge/version-%E2%89%A525.10.4-green?style=flat&logo=nextflow&logoColor=white&color=%230DC09D&link=https%3A%2F%2Fnextflow.io)](https://www.nextflow.io/)\n[![nf-core template version](https://img.shields.io/badge/nf--core_template-4.0.2-green?style=flat&logo=nfcore&logoColor=white&color=%2324B064&link=https%3A%2F%2Fnf-co.re)](https://github.com/nf-core/tools/releases/tag/4.0.2)\n[![run with conda](http://img.shields.io/badge/run%20with-conda-3EB049?labelColor=000000&logo=anaconda)](https://docs.conda.io/en/latest/)\n[![run with docker](https://img.shields.io/badge/run%20with-docker-0db7ed?labelColor=000000&logo=docker)](https://www.docker.com/)\n[![run with singularity](https://img.shields.io/badge/run%20with-singularity-1d355c.svg?labelColor=000000)](https://sylabs.io/docs/)\n[![Launch on Seqera Platform](https://img.shields.io/badge/Launch%20%F0%9F%9A%80-Seqera%20Platform-%234256e7)](https://cloud.seqera.io/launch?pipeline=https://github.com/nf-core/hadge)\n\n[![Get help on Slack](http://img.shields.io/badge/slack-nf--core%20%23hadge-4A154B?labelColor=000000&logo=slack)](https://nfcore.slack.com/channels/hadge)[![Follow on Bluesky](https://img.shields.io/badge/bluesky-%40nf__core-1185fe?labelColor=000000&logo=bluesky)](https://bsky.app/profile/nf-co.re)[![Follow on Mastodon](https://img.shields.io/badge/mastodon-nf__core-6364ff?labelColor=FFFFFF&logo=mastodon)](https://mstdn.science/@nf_core)[![Watch on YouTube](http://img.shields.io/badge/youtube-nf--core-FF0000?labelColor=000000&logo=youtube)](https://www.youtube.com/c/nf-core)\n\n## Introduction\n\n**nf-core/hadge** (**ha**shing **d**econvolution combined with **ge**notype information) is a bioinformatics pipeline that combines 11 methods to perform both hashing- and genotype-based deconvolution on single cell multiplexing data.\nIt takes a samplesheet with count matrices, BAM and VCF files as input, performs deconvolution with every method, joins all results and finally recovers previously discarded cells by combining the best performing methods (donor matching).\n\n![nf-core/hadge metro map](docs/images/pipeline.png)\n\n1. Untar matrices\n2. Extract hto names from matrix\n3. Perform genetic-based deconvolution\n 1. Get single cell genotype [`cellSNP`](https://github.com/single-cell-genetics/cellSNP)\n 2. [`vireo`](https://github.com/single-cell-genetics/vireo)\n 3. [`demuxlet`](https://github.com/statgen/popscle)\n 4. [`freemuxlet`](https://github.com/statgen/popscle)\n 5. [`souporcell`](https://github.com/wheaton5/souporcell)\n4. summarize assignments and classifications\n5. Perform hashing-based deconvolution\n 1. [`htodemux`](https://satijalab.org/seurat/articles/hashing_vignette)\n 2. [`multiseq`](https://satijalab.org/seurat/reference/multiseqdemux)\n 3. [`bff`](https://github.com/BimberLab/cellhashR)\n 4. [`demuxem`](https://demuxem.readthedocs.io/en/latest/)\n 5. [`gmm-demux`](https://github.com/CHPGenetics/GMM-demux)\n 6. [`hasheddrops`](https://github.com/MarioniLab/DropletUtils)\n 7. [`hashsolo`](https://scanpy.readthedocs.io/en/stable/generated/scanpy.external.pp.hashsolo.html)\n6. summarize assignments and classifications\n7. Join all results\n8. Donor match\n9. Find informative variants\n10. Create AnnData and Mudata objects\n11. [`MultiQC`](http://multiqc.info/)\n\n## Usage\n\n> [!NOTE]\n> If you are new to Nextflow and nf-core, please refer to [this page](https://nf-co.re/docs/usage/installation) on how to set-up Nextflow. Make sure to [test your setup](https://nf-co.re/docs/usage/introduction#how-to-run-a-pipeline) with `-profile test` before running the workflow on actual data. The profile `test` is used to test hadge's rescue mode, but you can also test the other modes with the profiles `test_genetic`, `test_hashing` and `test_donor_match`.\n\nFirst, prepare a samplesheet with your input data that looks as follows:\n\n`samplesheet.csv`:\n\n```csv\nsample,rna_matrix,hto_matrix,bam,vcf,n_samples,barcodes\nid1,rna.tar.gz,hto.tar.gz,chr21.bam,donor_genotype_chr21.vcf,2,barcodes.tsv\nid2,rna.tar.gz,hto.tar.gz,chr21.bam,donor_genotype_chr21.vcf,2,barcodes.tsv\nid3,rna.tar.gz,hto.tar.gz,chr21.bam,donor_genotype_chr21.vcf,2,barcodes.tsv\n```\n\nEach row contains data from a single-cell multiplexing experiment. The RNA-seq (`rna_matrix`) and hashing (`hto_matrix`) count matrices are provided in a 10x Genomics format and compressed as `.tar.gz`.\nGenetic deconvolution requires both the alignment file (`bam`) and a list of common SNPs (`vcf`). Users must specify the number of multiplexed donors (`n_samples`) and identify the target cells for deconvolution (`barcodes`).\n\nNow, you can run the pipeline using:\n\n```bash\nnextflow run nf-core/hadge \\\n -profile \\\n --input samplesheet.csv \\\n --outdir \\\n --mode rescue \\\n --hash_tools htodemux,hasheddrops,multiseq,gmm-demux,bff,hashsolo \\\n --genetic_tools demuxlet,freemuxlet,vireo,souporcell \\\n --fasta \n```\n\n> [!WARNING]\n> Please provide pipeline parameters via the CLI or Nextflow `-params-file` option. Custom config files including those provided by the `-c` Nextflow option can be used to provide any configuration _**except for parameters**_; see [docs](https://nf-co.re/docs/running/run-pipelines#using-parameter-files).\n\nFor more details and further functionality, please refer to the [usage documentation](https://nf-co.re/hadge/usage) and the [parameter documentation](https://nf-co.re/hadge/parameters).\n\n## Pipeline output\n\nTo see the results of an example test run with a full size dataset refer to the [results](https://nf-co.re/hadge/results) tab on the nf-core website pipeline page.\nFor more details about the output files and reports, please refer to the\n[output documentation](https://nf-co.re/hadge/output).\n\n## Credits\n\nnf-core/hadge was originally written by Fabiola Curion ([@bio-la](https://github.com/bio-la)), Xichen Wu ([@wxicu](https://github.com/wxicu)), Lukas Heumos ([@zethson](https://github.com/Zethson)) and Mariana Gonzales Andre ([@mari-ga](https://github.com/mari-ga)).\n\nWe thank the following people for rewriting the original pipeline within the nf-core framework:\n\n- [Luis Heinzlmeier](https://github.com/LuisHeinzlmeier)\n- [Nico Trummer](https://github.com/nictru)\n- [Seo Hyon Kim](https://github.com/seohyonkim)\n\n## Contributions and Support\n\nIf you would like to contribute to this pipeline, please see the [contributing guidelines](docs/CONTRIBUTING.md).\n\nFor further information or help, don't hesitate to get in touch on the [Slack `#hadge` channel](https://nfcore.slack.com/channels/hadge) (you can join with [this invite](https://nf-co.re/join/slack)).\n\n## Citations\n\nIf you use nf-core/hadge for your analysis, please cite it as follows:\n\n> **hadge: a comprehensive pipeline for donor deconvolution in single-cell studies.**\n>\n> Fabiola Curion, Xichen Wu, Lukas Heumos, Mariana Gonzales Andre, Lennard Halle, Melissa Grant-Peters, Charlotte Rich-Griffin, Hing-Yuen Yeung, Calliope A. Dendrou, Herbert B. Schiller & Fabian J. Theis.\n>\n> _Genome Biol._ 2024 Apr 26. doi: [10.1186/s13059-024-03249-z](https://doi.org/10.1186/s13059-024-03249-z).\n\n
    BibTeX\n\n```bibtex\n@article{curion2024hadge,\n title={hadge: a comprehensive pipeline for donor deconvolution in single-cell studies},\n author={Curion, Fabiola and Wu, Xichen and Heumos, Lukas and Andr{\\'e}, Mylene Mariana Gonzales and Halle, Lennard and Ozols, Matiss and Grant-Peters, Melissa and Rich-Griffin, Charlotte and Yeung, Hing-Yuen and Dendrou, Calliope A and others},\n journal={Genome Biology},\n volume={25},\n number={1},\n pages={109},\n year={2024},\n publisher={Springer}\n}\n\n```\n\n
    \n\nAn extensive list of references for the tools used by the pipeline can be found in the [`CITATIONS.md`](CITATIONS.md) file.\n\nYou can cite the `nf-core` publication as follows:\n\n> **The nf-core framework for community-curated bioinformatics pipelines.**\n>\n> Philip Ewels, Alexander Peltzer, Sven Fillinger, Harshil Patel, Johannes Alneberg, Andreas Wilm, Maxime Ulysse Garcia, Paolo Di Tommaso & Sven Nahnsen.\n>\n> _Nat Biotechnol._ 2020 Feb 13. doi: [10.1038/s41587-020-0439-x](https://dx.doi.org/10.1038/s41587-020-0439-x).\n", + "datePublished": "2026-07-24T12:35:51+00:00", + "description": "

    \n \n \n \"nf-core/hadge\"\n \n

    \n\n[![Open in GitHub Codespaces](https://img.shields.io/badge/Open_In_GitHub_Codespaces-black?labelColor=grey&logo=github)](https://github.com/codespaces/new/nf-core/hadge)\n[![GitHub Actions CI Status](https://github.com/nf-core/hadge/actions/workflows/nf-test.yml/badge.svg)](https://github.com/nf-core/hadge/actions/workflows/nf-test.yml)\n[![GitHub Actions Linting Status](https://github.com/nf-core/hadge/actions/workflows/linting.yml/badge.svg)](https://github.com/nf-core/hadge/actions/workflows/linting.yml)[![AWS CI](https://img.shields.io/badge/CI%20tests-full%20size-FF9900?labelColor=000000&logo=Amazon%20AWS)](https://nf-co.re/hadge/results)[![Cite with Zenodo](http://img.shields.io/badge/DOI-10.5281/zenodo.10634731-1073c8?labelColor=000000)](https://doi.org/10.5281/zenodo.10634731)\n[![nf-test](https://img.shields.io/badge/unit_tests-nf--test-337ab7.svg)](https://www.nf-test.com)\n\n[![Nextflow](https://img.shields.io/badge/version-%E2%89%A525.10.4-green?style=flat&logo=nextflow&logoColor=white&color=%230DC09D&link=https%3A%2F%2Fnextflow.io)](https://www.nextflow.io/)\n[![nf-core template version](https://img.shields.io/badge/nf--core_template-4.0.3-green?style=flat&logo=nfcore&logoColor=white&color=%2324B064&link=https%3A%2F%2Fnf-co.re)](https://github.com/nf-core/tools/releases/tag/4.0.3)\n[![run with conda](http://img.shields.io/badge/run%20with-conda-3EB049?labelColor=000000&logo=anaconda)](https://docs.conda.io/en/latest/)\n[![run with docker](https://img.shields.io/badge/run%20with-docker-0db7ed?labelColor=000000&logo=docker)](https://www.docker.com/)\n[![run with singularity](https://img.shields.io/badge/run%20with-singularity-1d355c.svg?labelColor=000000)](https://sylabs.io/docs/)\n[![Launch on Seqera Platform](https://img.shields.io/badge/Launch%20%F0%9F%9A%80-Seqera%20Platform-%234256e7)](https://cloud.seqera.io/launch?pipeline=https://github.com/nf-core/hadge)\n\n[![Get help on Slack](http://img.shields.io/badge/slack-nf--core%20%23hadge-4A154B?labelColor=000000&logo=slack)](https://nfcore.slack.com/channels/hadge)[![Follow on Bluesky](https://img.shields.io/badge/bluesky-%40nf__core-1185fe?labelColor=000000&logo=bluesky)](https://bsky.app/profile/nf-co.re)[![Follow on Mastodon](https://img.shields.io/badge/mastodon-nf__core-6364ff?labelColor=FFFFFF&logo=mastodon)](https://mstdn.science/@nf_core)[![Watch on YouTube](http://img.shields.io/badge/youtube-nf--core-FF0000?labelColor=000000&logo=youtube)](https://www.youtube.com/c/nf-core)\n\n## Introduction\n\n**nf-core/hadge** (**ha**shing **d**econvolution combined with **ge**notype information) is a bioinformatics pipeline that combines 11 methods to perform both hashing- and genotype-based deconvolution on single cell multiplexing data.\nIt takes a samplesheet with count matrices, BAM and VCF files as input, performs deconvolution with every method, joins all results and finally recovers previously discarded cells by combining the best performing methods (donor matching).\n\n![nf-core/hadge metro map](docs/images/pipeline.png)\n\n1. Untar matrices\n2. Extract hto names from matrix\n3. Perform genetic-based deconvolution\n 1. Get single cell genotype [`cellSNP`](https://github.com/single-cell-genetics/cellSNP)\n 2. [`vireo`](https://github.com/single-cell-genetics/vireo)\n 3. [`demuxlet`](https://github.com/statgen/popscle)\n 4. [`freemuxlet`](https://github.com/statgen/popscle)\n 5. [`souporcell`](https://github.com/wheaton5/souporcell)\n4. summarize assignments and classifications\n5. Perform hashing-based deconvolution\n 1. [`htodemux`](https://satijalab.org/seurat/articles/hashing_vignette)\n 2. [`multiseq`](https://satijalab.org/seurat/reference/multiseqdemux)\n 3. [`bff`](https://github.com/BimberLab/cellhashR)\n 4. [`demuxem`](https://demuxem.readthedocs.io/en/latest/)\n 5. [`gmm-demux`](https://github.com/CHPGenetics/GMM-demux)\n 6. [`hasheddrops`](https://github.com/MarioniLab/DropletUtils)\n 7. [`hashsolo`](https://scanpy.readthedocs.io/en/stable/generated/scanpy.external.pp.hashsolo.html)\n6. summarize assignments and classifications\n7. Join all results\n8. Donor match\n9. Find informative variants\n10. Create AnnData and Mudata objects\n11. [`MultiQC`](http://multiqc.info/)\n\n## Usage\n\n> [!NOTE]\n> If you are new to Nextflow and nf-core, please refer to [this page](https://nf-co.re/docs/usage/installation) on how to set-up Nextflow. Make sure to [test your setup](https://nf-co.re/docs/usage/introduction#how-to-run-a-pipeline) with `-profile test` before running the workflow on actual data. The profile `test` is used to test hadge's rescue mode, but you can also test the other modes with the profiles `test_genetic`, `test_hashing` and `test_donor_match`.\n\nFirst, prepare a samplesheet with your input data that looks as follows:\n\n`samplesheet.csv`:\n\n```csv\nsample,rna_matrix,hto_matrix,bam,vcf,n_samples,barcodes\nid1,rna.tar.gz,hto.tar.gz,chr21.bam,donor_genotype_chr21.vcf,2,barcodes.tsv\nid2,rna.tar.gz,hto.tar.gz,chr21.bam,donor_genotype_chr21.vcf,2,barcodes.tsv\nid3,rna.tar.gz,hto.tar.gz,chr21.bam,donor_genotype_chr21.vcf,2,barcodes.tsv\n```\n\nEach row contains data from a single-cell multiplexing experiment. The RNA-seq (`rna_matrix`) and hashing (`hto_matrix`) count matrices are provided in a 10x Genomics format and compressed as `.tar.gz`.\nGenetic deconvolution requires both the alignment file (`bam`) and a list of common SNPs (`vcf`). Users must specify the number of multiplexed donors (`n_samples`) and identify the target cells for deconvolution (`barcodes`).\n\nNow, you can run the pipeline using:\n\n```bash\nnextflow run nf-core/hadge \\\n -profile \\\n --input samplesheet.csv \\\n --outdir \\\n --mode rescue \\\n --hash_tools htodemux,hasheddrops,multiseq,gmm-demux,bff,hashsolo \\\n --genetic_tools demuxlet,freemuxlet,vireo,souporcell \\\n --fasta \n```\n\n> [!WARNING]\n> Please provide pipeline parameters via the CLI or Nextflow `-params-file` option. Custom config files including those provided by the `-c` Nextflow option can be used to provide any configuration _**except for parameters**_; see [docs](https://nf-co.re/docs/running/run-pipelines#using-parameter-files).\n\nFor more details and further functionality, please refer to the [usage documentation](https://nf-co.re/hadge/usage) and the [parameter documentation](https://nf-co.re/hadge/parameters).\n\n## Pipeline output\n\nTo see the results of an example test run with a full size dataset refer to the [results](https://nf-co.re/hadge/results) tab on the nf-core website pipeline page.\nFor more details about the output files and reports, please refer to the\n[output documentation](https://nf-co.re/hadge/output).\n\n## Credits\n\nnf-core/hadge was originally written by Fabiola Curion ([@bio-la](https://github.com/bio-la)), Xichen Wu ([@wxicu](https://github.com/wxicu)), Lukas Heumos ([@zethson](https://github.com/Zethson)) and Mariana Gonzales Andre ([@mari-ga](https://github.com/mari-ga)).\n\nWe thank the following people for rewriting the original pipeline within the nf-core framework:\n\n- [Luis Heinzlmeier](https://github.com/LuisHeinzlmeier)\n- [Nico Trummer](https://github.com/nictru)\n- [Seo Hyon Kim](https://github.com/seohyonkim)\n\n## Contributions and Support\n\nIf you would like to contribute to this pipeline, please see the [contributing guidelines](docs/CONTRIBUTING.md).\n\nFor further information or help, don't hesitate to get in touch on the [Slack `#hadge` channel](https://nfcore.slack.com/channels/hadge) (you can join with [this invite](https://nf-co.re/join/slack)).\n\n## Citations\n\nIf you use nf-core/hadge for your analysis, please cite it as follows:\n\n> **hadge: a comprehensive pipeline for donor deconvolution in single-cell studies.**\n>\n> Fabiola Curion, Xichen Wu, Lukas Heumos, Mariana Gonzales Andre, Lennard Halle, Melissa Grant-Peters, Charlotte Rich-Griffin, Hing-Yuen Yeung, Calliope A. Dendrou, Herbert B. Schiller & Fabian J. Theis.\n>\n> _Genome Biol._ 2024 Apr 26. doi: [10.1186/s13059-024-03249-z](https://doi.org/10.1186/s13059-024-03249-z).\n\n
    BibTeX\n\n```bibtex\n@article{curion2024hadge,\n title={hadge: a comprehensive pipeline for donor deconvolution in single-cell studies},\n author={Curion, Fabiola and Wu, Xichen and Heumos, Lukas and Andr{\\'e}, Mylene Mariana Gonzales and Halle, Lennard and Ozols, Matiss and Grant-Peters, Melissa and Rich-Griffin, Charlotte and Yeung, Hing-Yuen and Dendrou, Calliope A and others},\n journal={Genome Biology},\n volume={25},\n number={1},\n pages={109},\n year={2024},\n publisher={Springer}\n}\n\n```\n\n
    \n\nAn extensive list of references for the tools used by the pipeline can be found in the [`CITATIONS.md`](CITATIONS.md) file.\n\nYou can cite the `nf-core` publication as follows:\n\n> **The nf-core framework for community-curated bioinformatics pipelines.**\n>\n> Philip Ewels, Alexander Peltzer, Sven Fillinger, Harshil Patel, Johannes Alneberg, Andreas Wilm, Maxime Ulysse Garcia, Paolo Di Tommaso & Sven Nahnsen.\n>\n> _Nat Biotechnol._ 2020 Feb 13. doi: [10.1038/s41587-020-0439-x](https://dx.doi.org/10.1038/s41587-020-0439-x).\n", "hasPart": [ { "@id": "main.nf" @@ -99,7 +99,7 @@ }, "mentions": [ { - "@id": "#3c4b4c5e-a5c9-4173-bc2a-7f712cb38689" + "@id": "#a37f9932-cf4a-41df-aab3-21643d744aff" } ], "name": "nf-core/hadge" @@ -132,7 +132,7 @@ } ], "dateCreated": "", - "dateModified": "2026-04-30T13:33:34Z", + "dateModified": "2026-07-24T12:35:51Z", "dct:conformsTo": "https://bioschemas.org/profiles/ComputationalWorkflow/1.0-RELEASE/", "keywords": [ "nf-core", @@ -182,11 +182,11 @@ "version": "!>=25.10.4" }, { - "@id": "#3c4b4c5e-a5c9-4173-bc2a-7f712cb38689", + "@id": "#a37f9932-cf4a-41df-aab3-21643d744aff", "@type": "TestSuite", "instance": [ { - "@id": "#27279eed-019d-4b26-822b-99b1c1009922" + "@id": "#615cd548-c282-4db1-b570-94ab024a941b" } ], "mainEntity": { @@ -195,7 +195,7 @@ "name": "Test suite for nf-core/hadge" }, { - "@id": "#27279eed-019d-4b26-822b-99b1c1009922", + "@id": "#615cd548-c282-4db1-b570-94ab024a941b", "@type": "TestInstance", "name": "GitHub Actions workflow for testing nf-core/hadge", "resource": "repos/nf-core/hadge/actions/workflows/nf-test.yml", @@ -329,4 +329,4 @@ "name": "Fabiola Curion" } ] -} +} \ No newline at end of file diff --git a/subworkflows/local/utils_nfcore_hadge_pipeline/main.nf b/subworkflows/local/utils_nfcore_hadge_pipeline/main.nf index e29d5e30..a0074b28 100644 --- a/subworkflows/local/utils_nfcore_hadge_pipeline/main.nf +++ b/subworkflows/local/utils_nfcore_hadge_pipeline/main.nf @@ -30,12 +30,15 @@ workflow PIPELINE_INITIALISATION { monochrome_logs // boolean: Do not use coloured log outputs nextflow_cli_args // array: List of positional nextflow CLI args outdir // string: The output directory where the results will be saved + input // string: Path to input samplesheet help // boolean: Display help message and exit help_full // boolean: Show the full help message show_hidden // boolean: Show hidden parameters in the help message main: + ch_versions = channel.empty() + // // Print version and exit if required and dump pipeline parameters to JSON file // @@ -103,7 +106,7 @@ workflow PIPELINE_INITIALISATION { // Create channel from input file provided through params.input // - channel.fromList(samplesheetToList(params.input, "${projectDir}/assets/schema_input.json")) + channel.fromList(samplesheetToList(input, "${projectDir}/assets/schema_input.json")) .map { samplesheet -> validateInputSamplesheet(samplesheet) } From 403600441e2f90105e62993ccbb2d2195330ce6c Mon Sep 17 00:00:00 2001 From: LuisHeinzlmeier Date: Sun, 23 Aug 2026 19:57:07 +0200 Subject: [PATCH 48/74] Prepare Release PR (#114) * bump-version * update changelog * resolve lint errors/warnings * udpate snapshots --- .github/workflows/awsfulltest.yml | 3 - .nf-core.yml | 4 +- CHANGELOG.md | 12 ++-- assets/multiqc_config.yml | 4 +- conf/base.config | 7 -- nextflow.config | 4 +- ro-crate-metadata.json | 108 +++++++++++++++++++++++++--- tests/default.nf.test.snap | 6 +- tests/nextflow.config | 2 - tests/test_donor_match.nf.test.snap | 8 +-- tests/test_genetic.nf.test.snap | 6 +- tests/test_hashing.nf.test.snap | 2 +- 12 files changed, 118 insertions(+), 48 deletions(-) diff --git a/.github/workflows/awsfulltest.yml b/.github/workflows/awsfulltest.yml index b277fb75..77c37bed 100644 --- a/.github/workflows/awsfulltest.yml +++ b/.github/workflows/awsfulltest.yml @@ -24,9 +24,6 @@ jobs: - name: Launch workflow via Seqera Platform uses: seqeralabs/action-tower-launch@51565b514bff1827cf34620de25d0055759f1fc9 # v2 - # TODO nf-core: You can customise AWS full pipeline tests as required - # Add full size test data (but still relatively small datasets for few samples) - # on the `test_full.config` test runs with only one set of parameters with: workspace_id: ${{ vars.TOWER_WORKSPACE_ID }} access_token: ${{ secrets.TOWER_ACCESS_TOKEN }} diff --git a/.nf-core.yml b/.nf-core.yml index 89b3522d..1c519097 100644 --- a/.nf-core.yml +++ b/.nf-core.yml @@ -1,6 +1,8 @@ lint: files_unchanged: - .github/PULL_REQUEST_TEMPLATE.md + - assets/nf-core-hadge_logo_light.png + - docs/images/nf-core-hadge_logo_light.png nf_core_version: 4.0.3 repository_type: pipeline template: @@ -13,4 +15,4 @@ template: outdir: . skip_features: - fastqc - version: 1.0.0dev + version: 1.0.0 diff --git a/CHANGELOG.md b/CHANGELOG.md index 73a9b5f5..ae31b7ba 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,14 +3,10 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). -## v1.0.0dev - [date] +## v1.0.0 - [2026-08-23] -Initial release of nf-core/hadge, created with the [nf-core](https://nf-co.re/) template. - -### `Added` +First stable release of nf-core/hadge, combining 11 hashing- and genotype-based deconvolution methods (cellSNP, vireo, demuxlet, freemuxlet, souporcell, htodemux, multiseq, bff, demuxem, gmm-demux, hasheddrops, hashsolo) with donor matching to recover discarded cells. -### `Fixed` +## v1.0.0dev - [2025-06-13] -### `Dependencies` - -### `Deprecated` +Initial release of nf-core/hadge, created with the [nf-core](https://nf-co.re/) template. diff --git a/assets/multiqc_config.yml b/assets/multiqc_config.yml index 045f069a..70efd688 100644 --- a/assets/multiqc_config.yml +++ b/assets/multiqc_config.yml @@ -1,7 +1,5 @@ report_comment: > - This report has been generated by the
    nf-core/hadge - analysis pipeline. For information about how to interpret these results, please see the - documentation. + This report has been generated by the nf-core/hadge analysis pipeline. For information about how to interpret these results, please see the documentation. report_section_order: "nf-core-hadge-methods-description": order: -1000 diff --git a/conf/base.config b/conf/base.config index 696ede13..765a01b3 100644 --- a/conf/base.config +++ b/conf/base.config @@ -19,13 +19,6 @@ process { maxRetries = 1 maxErrors = '-1' - // Process-specific resource requirements - // NOTE - Please try and reuse the labels below as much as possible. - // These labels are used and recognised by default in DSL2 files hosted on nf-core/modules. - // If possible, it would be nice to keep the same label naming convention when - // adding in your local modules too. - // TODO nf-core: Customise requirements for specific processes. - // See https://www.nextflow.io/docs/latest/config.html#config-process-selectors withLabel:process_single { cpus = { 1 } memory = { 6.GB * task.attempt } diff --git a/nextflow.config b/nextflow.config index 31fcd706..c9fd9abc 100644 --- a/nextflow.config +++ b/nextflow.config @@ -431,7 +431,7 @@ includeConfig params.custom_config_base && (!System.getenv('NXF_OFFLINE') || !pa // Load nf-core/hadge custom profiles from different institutions. // TODO nf-core: Optionally, you can add a pipeline-specific nf-core config at https://github.com/nf-core/configs -// includeConfig params.custom_config_base && (!System.getenv('NXF_OFFLINE') || !params.custom_config_base.startsWith('http')) ? "${params.custom_config_base}/pipeline/hadge.config" : "/dev/null" +includeConfig params.custom_config_base && (!System.getenv('NXF_OFFLINE') || !params.custom_config_base.startsWith('http')) ? "${params.custom_config_base}/pipeline/hadge.config" : "/dev/null" // Set default registry for Apptainer, Docker, Podman, Charliecloud and Singularity independent of -profile // Will not be used unless Apptainer / Docker / Podman / Charliecloud / Singularity are enabled @@ -551,7 +551,7 @@ manifest { mainScript = 'main.nf' defaultBranch = 'main' nextflowVersion = '!>=25.10.4' - version = '1.0.0dev' + version = '1.0.0' doi = '10.1101/2023.07.23.550061' } diff --git a/ro-crate-metadata.json b/ro-crate-metadata.json index af6aa23c..92e59293 100644 --- a/ro-crate-metadata.json +++ b/ro-crate-metadata.json @@ -21,8 +21,8 @@ { "@id": "./", "@type": "Dataset", - "creativeWorkStatus": "InProgress", - "datePublished": "2026-07-24T12:35:51+00:00", + "creativeWorkStatus": "Stable", + "datePublished": "2026-08-23T06:06:53+00:00", "description": "

    \n \n \n \"nf-core/hadge\"\n \n

    \n\n[![Open in GitHub Codespaces](https://img.shields.io/badge/Open_In_GitHub_Codespaces-black?labelColor=grey&logo=github)](https://github.com/codespaces/new/nf-core/hadge)\n[![GitHub Actions CI Status](https://github.com/nf-core/hadge/actions/workflows/nf-test.yml/badge.svg)](https://github.com/nf-core/hadge/actions/workflows/nf-test.yml)\n[![GitHub Actions Linting Status](https://github.com/nf-core/hadge/actions/workflows/linting.yml/badge.svg)](https://github.com/nf-core/hadge/actions/workflows/linting.yml)[![AWS CI](https://img.shields.io/badge/CI%20tests-full%20size-FF9900?labelColor=000000&logo=Amazon%20AWS)](https://nf-co.re/hadge/results)[![Cite with Zenodo](http://img.shields.io/badge/DOI-10.5281/zenodo.10634731-1073c8?labelColor=000000)](https://doi.org/10.5281/zenodo.10634731)\n[![nf-test](https://img.shields.io/badge/unit_tests-nf--test-337ab7.svg)](https://www.nf-test.com)\n\n[![Nextflow](https://img.shields.io/badge/version-%E2%89%A525.10.4-green?style=flat&logo=nextflow&logoColor=white&color=%230DC09D&link=https%3A%2F%2Fnextflow.io)](https://www.nextflow.io/)\n[![nf-core template version](https://img.shields.io/badge/nf--core_template-4.0.3-green?style=flat&logo=nfcore&logoColor=white&color=%2324B064&link=https%3A%2F%2Fnf-co.re)](https://github.com/nf-core/tools/releases/tag/4.0.3)\n[![run with conda](http://img.shields.io/badge/run%20with-conda-3EB049?labelColor=000000&logo=anaconda)](https://docs.conda.io/en/latest/)\n[![run with docker](https://img.shields.io/badge/run%20with-docker-0db7ed?labelColor=000000&logo=docker)](https://www.docker.com/)\n[![run with singularity](https://img.shields.io/badge/run%20with-singularity-1d355c.svg?labelColor=000000)](https://sylabs.io/docs/)\n[![Launch on Seqera Platform](https://img.shields.io/badge/Launch%20%F0%9F%9A%80-Seqera%20Platform-%234256e7)](https://cloud.seqera.io/launch?pipeline=https://github.com/nf-core/hadge)\n\n[![Get help on Slack](http://img.shields.io/badge/slack-nf--core%20%23hadge-4A154B?labelColor=000000&logo=slack)](https://nfcore.slack.com/channels/hadge)[![Follow on Bluesky](https://img.shields.io/badge/bluesky-%40nf__core-1185fe?labelColor=000000&logo=bluesky)](https://bsky.app/profile/nf-co.re)[![Follow on Mastodon](https://img.shields.io/badge/mastodon-nf__core-6364ff?labelColor=FFFFFF&logo=mastodon)](https://mstdn.science/@nf_core)[![Watch on YouTube](http://img.shields.io/badge/youtube-nf--core-FF0000?labelColor=000000&logo=youtube)](https://www.youtube.com/c/nf-core)\n\n## Introduction\n\n**nf-core/hadge** (**ha**shing **d**econvolution combined with **ge**notype information) is a bioinformatics pipeline that combines 11 methods to perform both hashing- and genotype-based deconvolution on single cell multiplexing data.\nIt takes a samplesheet with count matrices, BAM and VCF files as input, performs deconvolution with every method, joins all results and finally recovers previously discarded cells by combining the best performing methods (donor matching).\n\n![nf-core/hadge metro map](docs/images/pipeline.png)\n\n1. Untar matrices\n2. Extract hto names from matrix\n3. Perform genetic-based deconvolution\n 1. Get single cell genotype [`cellSNP`](https://github.com/single-cell-genetics/cellSNP)\n 2. [`vireo`](https://github.com/single-cell-genetics/vireo)\n 3. [`demuxlet`](https://github.com/statgen/popscle)\n 4. [`freemuxlet`](https://github.com/statgen/popscle)\n 5. [`souporcell`](https://github.com/wheaton5/souporcell)\n4. summarize assignments and classifications\n5. Perform hashing-based deconvolution\n 1. [`htodemux`](https://satijalab.org/seurat/articles/hashing_vignette)\n 2. [`multiseq`](https://satijalab.org/seurat/reference/multiseqdemux)\n 3. [`bff`](https://github.com/BimberLab/cellhashR)\n 4. [`demuxem`](https://demuxem.readthedocs.io/en/latest/)\n 5. [`gmm-demux`](https://github.com/CHPGenetics/GMM-demux)\n 6. [`hasheddrops`](https://github.com/MarioniLab/DropletUtils)\n 7. [`hashsolo`](https://scanpy.readthedocs.io/en/stable/generated/scanpy.external.pp.hashsolo.html)\n6. summarize assignments and classifications\n7. Join all results\n8. Donor match\n9. Find informative variants\n10. Create AnnData and Mudata objects\n11. [`MultiQC`](http://multiqc.info/)\n\n## Usage\n\n> [!NOTE]\n> If you are new to Nextflow and nf-core, please refer to [this page](https://nf-co.re/docs/usage/installation) on how to set-up Nextflow. Make sure to [test your setup](https://nf-co.re/docs/usage/introduction#how-to-run-a-pipeline) with `-profile test` before running the workflow on actual data. The profile `test` is used to test hadge's rescue mode, but you can also test the other modes with the profiles `test_genetic`, `test_hashing` and `test_donor_match`.\n\nFirst, prepare a samplesheet with your input data that looks as follows:\n\n`samplesheet.csv`:\n\n```csv\nsample,rna_matrix,hto_matrix,bam,vcf,n_samples,barcodes\nid1,rna.tar.gz,hto.tar.gz,chr21.bam,donor_genotype_chr21.vcf,2,barcodes.tsv\nid2,rna.tar.gz,hto.tar.gz,chr21.bam,donor_genotype_chr21.vcf,2,barcodes.tsv\nid3,rna.tar.gz,hto.tar.gz,chr21.bam,donor_genotype_chr21.vcf,2,barcodes.tsv\n```\n\nEach row contains data from a single-cell multiplexing experiment. The RNA-seq (`rna_matrix`) and hashing (`hto_matrix`) count matrices are provided in a 10x Genomics format and compressed as `.tar.gz`.\nGenetic deconvolution requires both the alignment file (`bam`) and a list of common SNPs (`vcf`). Users must specify the number of multiplexed donors (`n_samples`) and identify the target cells for deconvolution (`barcodes`).\n\nNow, you can run the pipeline using:\n\n```bash\nnextflow run nf-core/hadge \\\n -profile \\\n --input samplesheet.csv \\\n --outdir \\\n --mode rescue \\\n --hash_tools htodemux,hasheddrops,multiseq,gmm-demux,bff,hashsolo \\\n --genetic_tools demuxlet,freemuxlet,vireo,souporcell \\\n --fasta \n```\n\n> [!WARNING]\n> Please provide pipeline parameters via the CLI or Nextflow `-params-file` option. Custom config files including those provided by the `-c` Nextflow option can be used to provide any configuration _**except for parameters**_; see [docs](https://nf-co.re/docs/running/run-pipelines#using-parameter-files).\n\nFor more details and further functionality, please refer to the [usage documentation](https://nf-co.re/hadge/usage) and the [parameter documentation](https://nf-co.re/hadge/parameters).\n\n## Pipeline output\n\nTo see the results of an example test run with a full size dataset refer to the [results](https://nf-co.re/hadge/results) tab on the nf-core website pipeline page.\nFor more details about the output files and reports, please refer to the\n[output documentation](https://nf-co.re/hadge/output).\n\n## Credits\n\nnf-core/hadge was originally written by Fabiola Curion ([@bio-la](https://github.com/bio-la)), Xichen Wu ([@wxicu](https://github.com/wxicu)), Lukas Heumos ([@zethson](https://github.com/Zethson)) and Mariana Gonzales Andre ([@mari-ga](https://github.com/mari-ga)).\n\nWe thank the following people for rewriting the original pipeline within the nf-core framework:\n\n- [Luis Heinzlmeier](https://github.com/LuisHeinzlmeier)\n- [Nico Trummer](https://github.com/nictru)\n- [Seo Hyon Kim](https://github.com/seohyonkim)\n\n## Contributions and Support\n\nIf you would like to contribute to this pipeline, please see the [contributing guidelines](docs/CONTRIBUTING.md).\n\nFor further information or help, don't hesitate to get in touch on the [Slack `#hadge` channel](https://nfcore.slack.com/channels/hadge) (you can join with [this invite](https://nf-co.re/join/slack)).\n\n## Citations\n\nIf you use nf-core/hadge for your analysis, please cite it as follows:\n\n> **hadge: a comprehensive pipeline for donor deconvolution in single-cell studies.**\n>\n> Fabiola Curion, Xichen Wu, Lukas Heumos, Mariana Gonzales Andre, Lennard Halle, Melissa Grant-Peters, Charlotte Rich-Griffin, Hing-Yuen Yeung, Calliope A. Dendrou, Herbert B. Schiller & Fabian J. Theis.\n>\n> _Genome Biol._ 2024 Apr 26. doi: [10.1186/s13059-024-03249-z](https://doi.org/10.1186/s13059-024-03249-z).\n\n
    BibTeX\n\n```bibtex\n@article{curion2024hadge,\n title={hadge: a comprehensive pipeline for donor deconvolution in single-cell studies},\n author={Curion, Fabiola and Wu, Xichen and Heumos, Lukas and Andr{\\'e}, Mylene Mariana Gonzales and Halle, Lennard and Ozols, Matiss and Grant-Peters, Melissa and Rich-Griffin, Charlotte and Yeung, Hing-Yuen and Dendrou, Calliope A and others},\n journal={Genome Biology},\n volume={25},\n number={1},\n pages={109},\n year={2024},\n publisher={Springer}\n}\n\n```\n\n
    \n\nAn extensive list of references for the tools used by the pipeline can be found in the [`CITATIONS.md`](CITATIONS.md) file.\n\nYou can cite the `nf-core` publication as follows:\n\n> **The nf-core framework for community-curated bioinformatics pipelines.**\n>\n> Philip Ewels, Alexander Peltzer, Sven Fillinger, Harshil Patel, Johannes Alneberg, Andreas Wilm, Maxime Ulysse Garcia, Paolo Di Tommaso & Sven Nahnsen.\n>\n> _Nat Biotechnol._ 2020 Feb 13. doi: [10.1038/s41587-020-0439-x](https://dx.doi.org/10.1038/s41587-020-0439-x).\n", "hasPart": [ { @@ -31,6 +31,9 @@ { "@id": "assets/" }, + { + "@id": "bin/" + }, { "@id": "conf/" }, @@ -43,6 +46,9 @@ { "@id": "modules/" }, + { + "@id": "modules/local/" + }, { "@id": "modules/nf-core/" }, @@ -99,7 +105,7 @@ }, "mentions": [ { - "@id": "#a37f9932-cf4a-41df-aab3-21643d744aff" + "@id": "#c12ab60a-4a5c-4265-9dfd-d6c3f5d74dd5" } ], "name": "nf-core/hadge" @@ -126,13 +132,27 @@ "SoftwareSourceCode", "ComputationalWorkflow" ], - "contributor": [ + "author": [ { "@id": "https://orcid.org/0000-0003-2502-8803" + }, + { + "@id": "https://orcid.org/0000-0002-8937-3457" + }, + { + "@id": "https://orcid.org/0009-0008-2168-4508" + }, + { + "@id": "https://orcid.org/0000-0002-9846-0130" + } + ], + "contributor": [ + { + "@id": "https://orcid.org/0009-0007-3062-4681" } ], "dateCreated": "", - "dateModified": "2026-07-24T12:35:51Z", + "dateModified": "2026-08-23T08:06:53Z", "dct:conformsTo": "https://bioschemas.org/profiles/ComputationalWorkflow/1.0-RELEASE/", "keywords": [ "nf-core", @@ -152,6 +172,14 @@ "license": [ "MIT" ], + "maintainer": [ + { + "@id": "https://orcid.org/0009-0002-3261-4472" + }, + { + "@id": "https://orcid.org/0000-0002-4639-0935" + } + ], "name": [ "nf-core/hadge" ], @@ -163,10 +191,10 @@ }, "url": [ "https://github.com/nf-core/hadge", - "https://nf-co.re/hadge/dev/" + "https://nf-co.re/hadge/1.0.0/" ], "version": [ - "1.0.0dev" + "1.0.0" ] }, { @@ -182,11 +210,11 @@ "version": "!>=25.10.4" }, { - "@id": "#a37f9932-cf4a-41df-aab3-21643d744aff", + "@id": "#c12ab60a-4a5c-4265-9dfd-d6c3f5d74dd5", "@type": "TestSuite", "instance": [ { - "@id": "#615cd548-c282-4db1-b570-94ab024a941b" + "@id": "#675c4503-8368-4bf2-85dc-f7db6cdcdb0a" } ], "mainEntity": { @@ -195,7 +223,7 @@ "name": "Test suite for nf-core/hadge" }, { - "@id": "#615cd548-c282-4db1-b570-94ab024a941b", + "@id": "#675c4503-8368-4bf2-85dc-f7db6cdcdb0a", "@type": "TestInstance", "name": "GitHub Actions workflow for testing nf-core/hadge", "resource": "repos/nf-core/hadge/actions/workflows/nf-test.yml", @@ -217,6 +245,11 @@ "@type": "Dataset", "description": "Additional files" }, + { + "@id": "bin/", + "@type": "Dataset", + "description": "Scripts that must be callable from a pipeline process" + }, { "@id": "conf/", "@type": "Dataset", @@ -237,6 +270,11 @@ "@type": "Dataset", "description": "Modules used by the pipeline" }, + { + "@id": "modules/local/", + "@type": "Dataset", + "description": "Pipeline-specific modules" + }, { "@id": "modules/nf-core/", "@type": "Dataset", @@ -326,7 +364,55 @@ { "@id": "https://orcid.org/0000-0003-2502-8803", "@type": "Person", - "name": "Fabiola Curion" + "affiliation": "Institute of Computational Biology, Helmholtz Zentrum München, Munich, Germany", + "name": "Fabiola Curion", + "url": "https://github.com/bio-la" + }, + { + "@id": "https://orcid.org/0000-0002-8937-3457", + "@type": "Person", + "affiliation": "Institute of Computational Biology, Helmholtz Zentrum München, Munich, Germany", + "email": "lukas.heumos@helmholtz-munich.de", + "name": "Lukas Heumos", + "url": "https://github.com/zethson" + }, + { + "@id": "https://orcid.org/0009-0002-3261-4472", + "@type": "Person", + "affiliation": "Institute of Computational Biology, Helmholtz Zentrum München, Munich, Germany", + "email": "luis.heinzlmeier@helmholtz-munich.de", + "name": "Luis Heinzlmeier", + "url": "https://github.com/LuisHeinzlmeier" + }, + { + "@id": "https://orcid.org/0000-0002-4639-0935", + "@type": "Person", + "affiliation": "Technical University of Munich", + "email": "nico.trummer@tum.de", + "name": "Nico Trummer", + "url": "https://github.com/nictru" + }, + { + "@id": "https://orcid.org/0009-0008-2168-4508", + "@type": "Person", + "affiliation": "Institute of Computational Biology, Helmholtz Zentrum München, Munich, Germany", + "name": "Xichen Wu", + "url": "https://github.com/wxicu" + }, + { + "@id": "https://orcid.org/0000-0002-9846-0130", + "@type": "Person", + "affiliation": "Technical University of Munich", + "name": "Mylène Mariana Gonzales André", + "url": "https://github.com/mari-ga" + }, + { + "@id": "https://orcid.org/0009-0007-3062-4681", + "@type": "Person", + "affiliation": "Institute of Computational Biology, Helmholtz Zentrum München, Munich, Germany", + "email": "seohyon.l.kim@gmail.com", + "name": "Seo Hyon Kim", + "url": "https://github.com/seohyonkim" } ] } \ No newline at end of file diff --git a/tests/default.nf.test.snap b/tests/default.nf.test.snap index ad1c1117..1d98f50d 100644 --- a/tests/default.nf.test.snap +++ b/tests/default.nf.test.snap @@ -131,7 +131,7 @@ "vireo": "v0.5.8" }, "Workflow": { - "nf-core/hadge": "v1.0.0dev" + "nf-core/hadge": "v1.0.0" } }, [ @@ -1253,10 +1253,10 @@ "test3_GT_donors.vireo.vcf.gz:md5,921ce18ca982b86589477432fce4e3e6" ] ], - "timestamp": "2026-06-25T13:53:35.37241", + "timestamp": "2026-08-23T07:37:43.847132463", "meta": { "nf-test": "0.9.5", - "nextflow": "26.04.3" + "nextflow": "26.04.6" } } } \ No newline at end of file diff --git a/tests/nextflow.config b/tests/nextflow.config index ce8a0d7e..4c1f85e4 100644 --- a/tests/nextflow.config +++ b/tests/nextflow.config @@ -4,8 +4,6 @@ ======================================================================================== */ -// TODO nf-core: Specify any additional parameters here -// Or any resources requirements params { modules_testdata_base_path = 'https://raw.githubusercontent.com/nf-core/test-datasets/modules/data/' pipelines_testdata_base_path = 'https://raw.githubusercontent.com/nf-core/test-datasets/refs/heads/hadge/' diff --git a/tests/test_donor_match.nf.test.snap b/tests/test_donor_match.nf.test.snap index 5ebbb8a5..72b3fd94 100644 --- a/tests/test_donor_match.nf.test.snap +++ b/tests/test_donor_match.nf.test.snap @@ -19,7 +19,7 @@ "bcftools": 1.22 }, "Workflow": { - "nf-core/hadge": "v1.0.0dev" + "nf-core/hadge": "v1.0.0" } }, [ @@ -419,10 +419,10 @@ "test3_vireo.vcf.gz:md5,6e4bbad3892e84cd1dd53c8fc0579977" ] ], - "timestamp": "2026-04-23T17:22:11.804693089", + "timestamp": "2026-08-23T07:39:06.342397056", "meta": { - "nf-test": "0.9.4", - "nextflow": "25.10.4" + "nf-test": "0.9.5", + "nextflow": "26.04.6" } } } \ No newline at end of file diff --git a/tests/test_genetic.nf.test.snap b/tests/test_genetic.nf.test.snap index 1dcf8fac..436daf64 100644 --- a/tests/test_genetic.nf.test.snap +++ b/tests/test_genetic.nf.test.snap @@ -52,7 +52,7 @@ "vireo": "v0.5.8" }, "Workflow": { - "nf-core/hadge": "v1.0.0dev" + "nf-core/hadge": "v1.0.0" } }, [ @@ -270,10 +270,10 @@ "test3_GT_donors.vireo.vcf.gz:md5,921ce18ca982b86589477432fce4e3e6" ] ], - "timestamp": "2026-06-25T12:16:23.520507", + "timestamp": "2026-08-23T07:47:07.819251449", "meta": { "nf-test": "0.9.5", - "nextflow": "26.04.3" + "nextflow": "26.04.6" } } } \ No newline at end of file diff --git a/tests/test_hashing.nf.test.snap b/tests/test_hashing.nf.test.snap index 23c96294..15fbe1b5 100644 --- a/tests/test_hashing.nf.test.snap +++ b/tests/test_hashing.nf.test.snap @@ -79,7 +79,7 @@ "untar": 1.34 }, "Workflow": { - "nf-core/hadge": "v1.0.0dev" + "nf-core/hadge": "v1.0.0" } }, [ From 880f816fdb934bce1e710768568778d28a0ff2d0 Mon Sep 17 00:00:00 2001 From: LuisHeinzlmeier Date: Sun, 30 Aug 2026 17:34:32 +0200 Subject: [PATCH 49/74] update conda envs --- modules/local/htodemux_visualization/environment.yml | 1 + .../local/preprocessing_for_htodemux_multiseq/environment.yml | 3 ++- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/modules/local/htodemux_visualization/environment.yml b/modules/local/htodemux_visualization/environment.yml index 977429a6..a38a6f89 100644 --- a/modules/local/htodemux_visualization/environment.yml +++ b/modules/local/htodemux_visualization/environment.yml @@ -4,3 +4,4 @@ channels: dependencies: - conda-forge::r-ggplot2=3.5.2 - conda-forge::r-seurat=5.3.0 + - conda-forge::r-seuratobject=5.1.0 diff --git a/modules/local/preprocessing_for_htodemux_multiseq/environment.yml b/modules/local/preprocessing_for_htodemux_multiseq/environment.yml index 12cf993b..b099cc0c 100644 --- a/modules/local/preprocessing_for_htodemux_multiseq/environment.yml +++ b/modules/local/preprocessing_for_htodemux_multiseq/environment.yml @@ -2,4 +2,5 @@ channels: - conda-forge - bioconda dependencies: - - conda-forge::r-seurat=5.3.0 + - "conda-forge::r-seurat=5.3.0" + - "conda-forge::r-seuratobject=5.1.0" From dabb774b0d36270d8656986c22a86565662823fc Mon Sep 17 00:00:00 2001 From: LuisHeinzlmeier Date: Sun, 30 Aug 2026 18:10:01 +0200 Subject: [PATCH 50/74] bumped SHA to fix similarity issue in CI --- .github/actions/nf-test/action.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/actions/nf-test/action.yml b/.github/actions/nf-test/action.yml index 945c56f0..01433870 100644 --- a/.github/actions/nf-test/action.yml +++ b/.github/actions/nf-test/action.yml @@ -37,7 +37,7 @@ runs: - name: Setup apptainer if: contains(inputs.profile, 'singularity') - uses: eWaterCycle/setup-apptainer@4bb22c52d4f63406c49e94c804632975787312b3 # v2.0.0 + uses: eWaterCycle/setup-apptainer@3f706d898c9db585b1d741b4692e66755f3a1b40 # v2.0.0 - name: Set up Singularity if: contains(inputs.profile, 'singularity') From 7f8051815c5424043f504321ed1111d7a52e97b5 Mon Sep 17 00:00:00 2001 From: LuisHeinzlmeier Date: Mon, 31 Aug 2026 20:35:51 +0000 Subject: [PATCH 51/74] update containers --- modules/local/htodemux_visualization/main.nf | 4 ++-- .../local/preprocessing_for_htodemux_multiseq/environment.yml | 4 ++-- modules/local/preprocessing_for_htodemux_multiseq/main.nf | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/modules/local/htodemux_visualization/main.nf b/modules/local/htodemux_visualization/main.nf index d096a957..6316e7a0 100644 --- a/modules/local/htodemux_visualization/main.nf +++ b/modules/local/htodemux_visualization/main.nf @@ -4,8 +4,8 @@ process HTODEMUX_VISUALIZATION { conda "${moduleDir}/environment.yml" container "${ workflow.containerEngine == 'singularity' && !task.ext.singularity_pull_docker_container ? - 'oras://community.wave.seqera.io/library/r-ggplot2_r-seurat:dac8c905972b98df': - 'community.wave.seqera.io/library/r-ggplot2_r-seurat:eefd54806320eae0' }" + 'https://community-cr-prod.seqera.io/docker/registry/v2/blobs/sha256/8e/8e9a749bb2e2bfac7f1a80d9565eb75fd1ca5bc036eb87a04964fb58f4ac0138/data': + 'community.wave.seqera.io/library/r-ggplot2_r-seurat_r-seuratobject:e12b56f2caede4ba' }" input: tuple val(meta), path(seurat_object), val(assay) diff --git a/modules/local/preprocessing_for_htodemux_multiseq/environment.yml b/modules/local/preprocessing_for_htodemux_multiseq/environment.yml index b099cc0c..d51e0097 100644 --- a/modules/local/preprocessing_for_htodemux_multiseq/environment.yml +++ b/modules/local/preprocessing_for_htodemux_multiseq/environment.yml @@ -2,5 +2,5 @@ channels: - conda-forge - bioconda dependencies: - - "conda-forge::r-seurat=5.3.0" - - "conda-forge::r-seuratobject=5.1.0" + - conda-forge::r-seurat=5.3.0 + - conda-forge::r-seuratobject=5.1.0 diff --git a/modules/local/preprocessing_for_htodemux_multiseq/main.nf b/modules/local/preprocessing_for_htodemux_multiseq/main.nf index 197606ec..071f56eb 100644 --- a/modules/local/preprocessing_for_htodemux_multiseq/main.nf +++ b/modules/local/preprocessing_for_htodemux_multiseq/main.nf @@ -4,8 +4,8 @@ process PREPROCESSING_FOR_HTODEMUX_MULTISEQ { conda "${moduleDir}/environment.yml" container "${ workflow.containerEngine == 'singularity' && !task.ext.singularity_pull_docker_container ? - 'oras://community.wave.seqera.io/library/r-seurat:5.3.0--00f94834f5eea080': - 'community.wave.seqera.io/library/r-seurat:5.3.0--eeb977835038859a' }" + 'https://community-cr-prod.seqera.io/docker/registry/v2/blobs/sha256/6b/6b43d3afc47ad5e5d99bc97980d409e377f6b0595cb588b121076e67dca71d39/data': + 'community.wave.seqera.io/library/r-seurat_r-seuratobject:c1b3e7a7276bda09' }" input: From b72a6592f356076ac56ddeed182d974399a480f8 Mon Sep 17 00:00:00 2001 From: LuisHeinzlmeier Date: Tue, 1 Sep 2026 09:35:30 +0200 Subject: [PATCH 52/74] add missing versions --- modules/local/create_anndata_mudata/environment.yml | 1 + modules/local/donor_match/environment.yml | 1 + modules/local/find_variants/environment.yml | 1 + modules/local/gene_summary/environment.yml | 1 + modules/local/hash_summary/environment.yml | 1 + 5 files changed, 5 insertions(+) diff --git a/modules/local/create_anndata_mudata/environment.yml b/modules/local/create_anndata_mudata/environment.yml index e1185d62..2c8fe6c2 100644 --- a/modules/local/create_anndata_mudata/environment.yml +++ b/modules/local/create_anndata_mudata/environment.yml @@ -2,6 +2,7 @@ channels: - conda-forge - bioconda dependencies: + - conda-forge::python=3.14.2 - conda-forge::anndata=0.12.7 - conda-forge::mudata=0.3.2 - conda-forge::pandas=2.3.3 diff --git a/modules/local/donor_match/environment.yml b/modules/local/donor_match/environment.yml index 834a490c..6d58f6dd 100644 --- a/modules/local/donor_match/environment.yml +++ b/modules/local/donor_match/environment.yml @@ -2,6 +2,7 @@ channels: - conda-forge - bioconda dependencies: + - conda-forge::r-base=4.5.2 - conda-forge::r-data.table=1.17.8 - conda-forge::r-pheatmap=1.0.13 - conda-forge::r-tidyverse=2.0.0 diff --git a/modules/local/find_variants/environment.yml b/modules/local/find_variants/environment.yml index f1ced77a..57592cdb 100644 --- a/modules/local/find_variants/environment.yml +++ b/modules/local/find_variants/environment.yml @@ -2,6 +2,7 @@ channels: - conda-forge - bioconda dependencies: + - conda-forge::r-base=4.5.2 - conda-forge::r-complexupset=1.3.3 - conda-forge::r-data.table=1.17.8 - conda-forge::r-tidyverse=2.0.0 diff --git a/modules/local/gene_summary/environment.yml b/modules/local/gene_summary/environment.yml index d166cb22..a97c2ea4 100644 --- a/modules/local/gene_summary/environment.yml +++ b/modules/local/gene_summary/environment.yml @@ -2,6 +2,7 @@ channels: - conda-forge - bioconda dependencies: + - conda-forge::python=3.14.2 - conda-forge::numpy=2.3.5 - conda-forge::pandas=2.3.3 - conda-forge::pyyaml=6.0.3 diff --git a/modules/local/hash_summary/environment.yml b/modules/local/hash_summary/environment.yml index 43927119..8f0f374b 100644 --- a/modules/local/hash_summary/environment.yml +++ b/modules/local/hash_summary/environment.yml @@ -2,6 +2,7 @@ channels: - conda-forge - bioconda dependencies: + - conda-forge::python=3.12.12 - bioconda::pegasusio=0.10.0 - conda-forge::numpy=2.3.5 - conda-forge::pandas=2.3.3 From 1ea2adeb035047a9aca4ff3c9fcfd4ea0e57c3e8 Mon Sep 17 00:00:00 2001 From: LuisHeinzlmeier Date: Tue, 1 Sep 2026 09:46:13 +0200 Subject: [PATCH 53/74] exclude png for testing --- tests/.nftignore | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/.nftignore b/tests/.nftignore index b1751523..c5cfe464 100644 --- a/tests/.nftignore +++ b/tests/.nftignore @@ -19,3 +19,4 @@ genetic/popscle/freemuxlet/*/*.clust1.vcf.gz genetic/souporcell/*/*/ambient_rna.txt genetic/souporcell/*/*/cluster_genotypes.vcf find_variants/*/subset_gt_donors/*.vcf.gz +**/*.{png} From c8133dc7574eb4995fe9501740a0170af8326737 Mon Sep 17 00:00:00 2001 From: LuisHeinzlmeier Date: Tue, 1 Sep 2026 12:21:22 +0200 Subject: [PATCH 54/74] remove png and jpeg from shapshots --- tests/.nftignore | 2 +- tests/default.nf.test.snap | 141 ------------------- tests/test_donor_match.nf.test.snap | 54 -------- tests/test_genetic.nf.test.snap | 6 - tests/test_hashing.nf.test.snap | 207 ---------------------------- 5 files changed, 1 insertion(+), 409 deletions(-) diff --git a/tests/.nftignore b/tests/.nftignore index c5cfe464..22a7ad43 100644 --- a/tests/.nftignore +++ b/tests/.nftignore @@ -19,4 +19,4 @@ genetic/popscle/freemuxlet/*/*.clust1.vcf.gz genetic/souporcell/*/*/ambient_rna.txt genetic/souporcell/*/*/cluster_genotypes.vcf find_variants/*/subset_gt_donors/*.vcf.gz -**/*.{png} +**/*.{png,jpeg} diff --git a/tests/default.nf.test.snap b/tests/default.nf.test.snap index 1d98f50d..b821e893 100644 --- a/tests/default.nf.test.snap +++ b/tests/default.nf.test.snap @@ -139,97 +139,81 @@ "donor_match/test1", "donor_match/test1/freemuxlet_vs_bff_consensuscall", "donor_match/test1/freemuxlet_vs_bff_consensuscall/test1_freemuxlet_vs_bff_consensuscall_all_assignment_after_match.csv", - "donor_match/test1/freemuxlet_vs_bff_consensuscall/test1_freemuxlet_vs_bff_consensuscall_concordance_heatmap.png", "donor_match/test1/freemuxlet_vs_bff_consensuscall/test1_freemuxlet_vs_bff_consensuscall_correlation_res.csv", "donor_match/test1/freemuxlet_vs_bff_consensuscall/test1_freemuxlet_vs_bff_consensuscall_donor_match.csv", "donor_match/test1/freemuxlet_vs_bff_consensuscall/test1_freemuxlet_vs_bff_consensuscall_intersect_assignment_after_match.csv", "donor_match/test1/freemuxlet_vs_bff_raw", "donor_match/test1/freemuxlet_vs_bff_raw/test1_freemuxlet_vs_bff_raw_all_assignment_after_match.csv", - "donor_match/test1/freemuxlet_vs_bff_raw/test1_freemuxlet_vs_bff_raw_concordance_heatmap.png", "donor_match/test1/freemuxlet_vs_bff_raw/test1_freemuxlet_vs_bff_raw_correlation_res.csv", "donor_match/test1/freemuxlet_vs_bff_raw/test1_freemuxlet_vs_bff_raw_donor_match.csv", "donor_match/test1/freemuxlet_vs_bff_raw/test1_freemuxlet_vs_bff_raw_intersect_assignment_after_match.csv", "donor_match/test1/freemuxlet_vs_demuxem", "donor_match/test1/freemuxlet_vs_demuxem/test1_freemuxlet_vs_demuxem_all_assignment_after_match.csv", - "donor_match/test1/freemuxlet_vs_demuxem/test1_freemuxlet_vs_demuxem_concordance_heatmap.png", "donor_match/test1/freemuxlet_vs_demuxem/test1_freemuxlet_vs_demuxem_correlation_res.csv", "donor_match/test1/freemuxlet_vs_demuxem/test1_freemuxlet_vs_demuxem_donor_match.csv", "donor_match/test1/freemuxlet_vs_demuxem/test1_freemuxlet_vs_demuxem_intersect_assignment_after_match.csv", "donor_match/test1/freemuxlet_vs_gmmdemux", "donor_match/test1/freemuxlet_vs_gmmdemux/test1_freemuxlet_vs_gmmdemux_all_assignment_after_match.csv", - "donor_match/test1/freemuxlet_vs_gmmdemux/test1_freemuxlet_vs_gmmdemux_concordance_heatmap.png", "donor_match/test1/freemuxlet_vs_gmmdemux/test1_freemuxlet_vs_gmmdemux_correlation_res.csv", "donor_match/test1/freemuxlet_vs_gmmdemux/test1_freemuxlet_vs_gmmdemux_donor_match.csv", "donor_match/test1/freemuxlet_vs_gmmdemux/test1_freemuxlet_vs_gmmdemux_intersect_assignment_after_match.csv", "donor_match/test1/freemuxlet_vs_hasheddrops", "donor_match/test1/freemuxlet_vs_hasheddrops/test1_freemuxlet_vs_hasheddrops_all_assignment_after_match.csv", - "donor_match/test1/freemuxlet_vs_hasheddrops/test1_freemuxlet_vs_hasheddrops_concordance_heatmap.png", "donor_match/test1/freemuxlet_vs_hasheddrops/test1_freemuxlet_vs_hasheddrops_correlation_res.csv", "donor_match/test1/freemuxlet_vs_hasheddrops/test1_freemuxlet_vs_hasheddrops_donor_match.csv", "donor_match/test1/freemuxlet_vs_hasheddrops/test1_freemuxlet_vs_hasheddrops_intersect_assignment_after_match.csv", "donor_match/test1/freemuxlet_vs_hashsolo", "donor_match/test1/freemuxlet_vs_hashsolo/test1_freemuxlet_vs_hashsolo_all_assignment_after_match.csv", - "donor_match/test1/freemuxlet_vs_hashsolo/test1_freemuxlet_vs_hashsolo_concordance_heatmap.png", "donor_match/test1/freemuxlet_vs_hashsolo/test1_freemuxlet_vs_hashsolo_correlation_res.csv", "donor_match/test1/freemuxlet_vs_hashsolo/test1_freemuxlet_vs_hashsolo_donor_match.csv", "donor_match/test1/freemuxlet_vs_hashsolo/test1_freemuxlet_vs_hashsolo_intersect_assignment_after_match.csv", "donor_match/test1/freemuxlet_vs_htodemux", "donor_match/test1/freemuxlet_vs_htodemux/test1_freemuxlet_vs_htodemux_all_assignment_after_match.csv", - "donor_match/test1/freemuxlet_vs_htodemux/test1_freemuxlet_vs_htodemux_concordance_heatmap.png", "donor_match/test1/freemuxlet_vs_htodemux/test1_freemuxlet_vs_htodemux_correlation_res.csv", "donor_match/test1/freemuxlet_vs_htodemux/test1_freemuxlet_vs_htodemux_donor_match.csv", "donor_match/test1/freemuxlet_vs_htodemux/test1_freemuxlet_vs_htodemux_intersect_assignment_after_match.csv", "donor_match/test1/freemuxlet_vs_multiseq", "donor_match/test1/freemuxlet_vs_multiseq/test1_freemuxlet_vs_multiseq_all_assignment_after_match.csv", - "donor_match/test1/freemuxlet_vs_multiseq/test1_freemuxlet_vs_multiseq_concordance_heatmap.png", "donor_match/test1/freemuxlet_vs_multiseq/test1_freemuxlet_vs_multiseq_correlation_res.csv", "donor_match/test1/freemuxlet_vs_multiseq/test1_freemuxlet_vs_multiseq_donor_match.csv", "donor_match/test1/freemuxlet_vs_multiseq/test1_freemuxlet_vs_multiseq_intersect_assignment_after_match.csv", "donor_match/test1/souporcell_vs_bff_consensuscall", "donor_match/test1/souporcell_vs_bff_consensuscall/test1_souporcell_vs_bff_consensuscall_all_assignment_after_match.csv", - "donor_match/test1/souporcell_vs_bff_consensuscall/test1_souporcell_vs_bff_consensuscall_concordance_heatmap.png", "donor_match/test1/souporcell_vs_bff_consensuscall/test1_souporcell_vs_bff_consensuscall_correlation_res.csv", "donor_match/test1/souporcell_vs_bff_consensuscall/test1_souporcell_vs_bff_consensuscall_donor_match.csv", "donor_match/test1/souporcell_vs_bff_consensuscall/test1_souporcell_vs_bff_consensuscall_intersect_assignment_after_match.csv", "donor_match/test1/souporcell_vs_bff_raw", "donor_match/test1/souporcell_vs_bff_raw/test1_souporcell_vs_bff_raw_all_assignment_after_match.csv", - "donor_match/test1/souporcell_vs_bff_raw/test1_souporcell_vs_bff_raw_concordance_heatmap.png", "donor_match/test1/souporcell_vs_bff_raw/test1_souporcell_vs_bff_raw_correlation_res.csv", "donor_match/test1/souporcell_vs_bff_raw/test1_souporcell_vs_bff_raw_donor_match.csv", "donor_match/test1/souporcell_vs_bff_raw/test1_souporcell_vs_bff_raw_intersect_assignment_after_match.csv", "donor_match/test1/souporcell_vs_demuxem", "donor_match/test1/souporcell_vs_demuxem/test1_souporcell_vs_demuxem_all_assignment_after_match.csv", - "donor_match/test1/souporcell_vs_demuxem/test1_souporcell_vs_demuxem_concordance_heatmap.png", "donor_match/test1/souporcell_vs_demuxem/test1_souporcell_vs_demuxem_correlation_res.csv", "donor_match/test1/souporcell_vs_demuxem/test1_souporcell_vs_demuxem_donor_match.csv", "donor_match/test1/souporcell_vs_demuxem/test1_souporcell_vs_demuxem_intersect_assignment_after_match.csv", "donor_match/test1/souporcell_vs_gmmdemux", "donor_match/test1/souporcell_vs_gmmdemux/test1_souporcell_vs_gmmdemux_all_assignment_after_match.csv", - "donor_match/test1/souporcell_vs_gmmdemux/test1_souporcell_vs_gmmdemux_concordance_heatmap.png", "donor_match/test1/souporcell_vs_gmmdemux/test1_souporcell_vs_gmmdemux_correlation_res.csv", "donor_match/test1/souporcell_vs_gmmdemux/test1_souporcell_vs_gmmdemux_donor_match.csv", "donor_match/test1/souporcell_vs_gmmdemux/test1_souporcell_vs_gmmdemux_intersect_assignment_after_match.csv", "donor_match/test1/souporcell_vs_hasheddrops", "donor_match/test1/souporcell_vs_hasheddrops/test1_souporcell_vs_hasheddrops_all_assignment_after_match.csv", - "donor_match/test1/souporcell_vs_hasheddrops/test1_souporcell_vs_hasheddrops_concordance_heatmap.png", "donor_match/test1/souporcell_vs_hasheddrops/test1_souporcell_vs_hasheddrops_correlation_res.csv", "donor_match/test1/souporcell_vs_hasheddrops/test1_souporcell_vs_hasheddrops_donor_match.csv", "donor_match/test1/souporcell_vs_hasheddrops/test1_souporcell_vs_hasheddrops_intersect_assignment_after_match.csv", "donor_match/test1/souporcell_vs_hashsolo", "donor_match/test1/souporcell_vs_hashsolo/test1_souporcell_vs_hashsolo_all_assignment_after_match.csv", - "donor_match/test1/souporcell_vs_hashsolo/test1_souporcell_vs_hashsolo_concordance_heatmap.png", "donor_match/test1/souporcell_vs_hashsolo/test1_souporcell_vs_hashsolo_correlation_res.csv", "donor_match/test1/souporcell_vs_hashsolo/test1_souporcell_vs_hashsolo_donor_match.csv", "donor_match/test1/souporcell_vs_hashsolo/test1_souporcell_vs_hashsolo_intersect_assignment_after_match.csv", "donor_match/test1/souporcell_vs_htodemux", "donor_match/test1/souporcell_vs_htodemux/test1_souporcell_vs_htodemux_all_assignment_after_match.csv", - "donor_match/test1/souporcell_vs_htodemux/test1_souporcell_vs_htodemux_concordance_heatmap.png", "donor_match/test1/souporcell_vs_htodemux/test1_souporcell_vs_htodemux_correlation_res.csv", "donor_match/test1/souporcell_vs_htodemux/test1_souporcell_vs_htodemux_donor_match.csv", "donor_match/test1/souporcell_vs_htodemux/test1_souporcell_vs_htodemux_intersect_assignment_after_match.csv", "donor_match/test1/souporcell_vs_multiseq", "donor_match/test1/souporcell_vs_multiseq/test1_souporcell_vs_multiseq_all_assignment_after_match.csv", - "donor_match/test1/souporcell_vs_multiseq/test1_souporcell_vs_multiseq_concordance_heatmap.png", "donor_match/test1/souporcell_vs_multiseq/test1_souporcell_vs_multiseq_correlation_res.csv", "donor_match/test1/souporcell_vs_multiseq/test1_souporcell_vs_multiseq_donor_match.csv", "donor_match/test1/souporcell_vs_multiseq/test1_souporcell_vs_multiseq_intersect_assignment_after_match.csv", @@ -240,97 +224,81 @@ "donor_match/test2", "donor_match/test2/freemuxlet_vs_bff_consensuscall", "donor_match/test2/freemuxlet_vs_bff_consensuscall/test2_freemuxlet_vs_bff_consensuscall_all_assignment_after_match.csv", - "donor_match/test2/freemuxlet_vs_bff_consensuscall/test2_freemuxlet_vs_bff_consensuscall_concordance_heatmap.png", "donor_match/test2/freemuxlet_vs_bff_consensuscall/test2_freemuxlet_vs_bff_consensuscall_correlation_res.csv", "donor_match/test2/freemuxlet_vs_bff_consensuscall/test2_freemuxlet_vs_bff_consensuscall_donor_match.csv", "donor_match/test2/freemuxlet_vs_bff_consensuscall/test2_freemuxlet_vs_bff_consensuscall_intersect_assignment_after_match.csv", "donor_match/test2/freemuxlet_vs_bff_raw", "donor_match/test2/freemuxlet_vs_bff_raw/test2_freemuxlet_vs_bff_raw_all_assignment_after_match.csv", - "donor_match/test2/freemuxlet_vs_bff_raw/test2_freemuxlet_vs_bff_raw_concordance_heatmap.png", "donor_match/test2/freemuxlet_vs_bff_raw/test2_freemuxlet_vs_bff_raw_correlation_res.csv", "donor_match/test2/freemuxlet_vs_bff_raw/test2_freemuxlet_vs_bff_raw_donor_match.csv", "donor_match/test2/freemuxlet_vs_bff_raw/test2_freemuxlet_vs_bff_raw_intersect_assignment_after_match.csv", "donor_match/test2/freemuxlet_vs_demuxem", "donor_match/test2/freemuxlet_vs_demuxem/test2_freemuxlet_vs_demuxem_all_assignment_after_match.csv", - "donor_match/test2/freemuxlet_vs_demuxem/test2_freemuxlet_vs_demuxem_concordance_heatmap.png", "donor_match/test2/freemuxlet_vs_demuxem/test2_freemuxlet_vs_demuxem_correlation_res.csv", "donor_match/test2/freemuxlet_vs_demuxem/test2_freemuxlet_vs_demuxem_donor_match.csv", "donor_match/test2/freemuxlet_vs_demuxem/test2_freemuxlet_vs_demuxem_intersect_assignment_after_match.csv", "donor_match/test2/freemuxlet_vs_gmmdemux", "donor_match/test2/freemuxlet_vs_gmmdemux/test2_freemuxlet_vs_gmmdemux_all_assignment_after_match.csv", - "donor_match/test2/freemuxlet_vs_gmmdemux/test2_freemuxlet_vs_gmmdemux_concordance_heatmap.png", "donor_match/test2/freemuxlet_vs_gmmdemux/test2_freemuxlet_vs_gmmdemux_correlation_res.csv", "donor_match/test2/freemuxlet_vs_gmmdemux/test2_freemuxlet_vs_gmmdemux_donor_match.csv", "donor_match/test2/freemuxlet_vs_gmmdemux/test2_freemuxlet_vs_gmmdemux_intersect_assignment_after_match.csv", "donor_match/test2/freemuxlet_vs_hasheddrops", "donor_match/test2/freemuxlet_vs_hasheddrops/test2_freemuxlet_vs_hasheddrops_all_assignment_after_match.csv", - "donor_match/test2/freemuxlet_vs_hasheddrops/test2_freemuxlet_vs_hasheddrops_concordance_heatmap.png", "donor_match/test2/freemuxlet_vs_hasheddrops/test2_freemuxlet_vs_hasheddrops_correlation_res.csv", "donor_match/test2/freemuxlet_vs_hasheddrops/test2_freemuxlet_vs_hasheddrops_donor_match.csv", "donor_match/test2/freemuxlet_vs_hasheddrops/test2_freemuxlet_vs_hasheddrops_intersect_assignment_after_match.csv", "donor_match/test2/freemuxlet_vs_hashsolo", "donor_match/test2/freemuxlet_vs_hashsolo/test2_freemuxlet_vs_hashsolo_all_assignment_after_match.csv", - "donor_match/test2/freemuxlet_vs_hashsolo/test2_freemuxlet_vs_hashsolo_concordance_heatmap.png", "donor_match/test2/freemuxlet_vs_hashsolo/test2_freemuxlet_vs_hashsolo_correlation_res.csv", "donor_match/test2/freemuxlet_vs_hashsolo/test2_freemuxlet_vs_hashsolo_donor_match.csv", "donor_match/test2/freemuxlet_vs_hashsolo/test2_freemuxlet_vs_hashsolo_intersect_assignment_after_match.csv", "donor_match/test2/freemuxlet_vs_htodemux", "donor_match/test2/freemuxlet_vs_htodemux/test2_freemuxlet_vs_htodemux_all_assignment_after_match.csv", - "donor_match/test2/freemuxlet_vs_htodemux/test2_freemuxlet_vs_htodemux_concordance_heatmap.png", "donor_match/test2/freemuxlet_vs_htodemux/test2_freemuxlet_vs_htodemux_correlation_res.csv", "donor_match/test2/freemuxlet_vs_htodemux/test2_freemuxlet_vs_htodemux_donor_match.csv", "donor_match/test2/freemuxlet_vs_htodemux/test2_freemuxlet_vs_htodemux_intersect_assignment_after_match.csv", "donor_match/test2/freemuxlet_vs_multiseq", "donor_match/test2/freemuxlet_vs_multiseq/test2_freemuxlet_vs_multiseq_all_assignment_after_match.csv", - "donor_match/test2/freemuxlet_vs_multiseq/test2_freemuxlet_vs_multiseq_concordance_heatmap.png", "donor_match/test2/freemuxlet_vs_multiseq/test2_freemuxlet_vs_multiseq_correlation_res.csv", "donor_match/test2/freemuxlet_vs_multiseq/test2_freemuxlet_vs_multiseq_donor_match.csv", "donor_match/test2/freemuxlet_vs_multiseq/test2_freemuxlet_vs_multiseq_intersect_assignment_after_match.csv", "donor_match/test2/souporcell_vs_bff_consensuscall", "donor_match/test2/souporcell_vs_bff_consensuscall/test2_souporcell_vs_bff_consensuscall_all_assignment_after_match.csv", - "donor_match/test2/souporcell_vs_bff_consensuscall/test2_souporcell_vs_bff_consensuscall_concordance_heatmap.png", "donor_match/test2/souporcell_vs_bff_consensuscall/test2_souporcell_vs_bff_consensuscall_correlation_res.csv", "donor_match/test2/souporcell_vs_bff_consensuscall/test2_souporcell_vs_bff_consensuscall_donor_match.csv", "donor_match/test2/souporcell_vs_bff_consensuscall/test2_souporcell_vs_bff_consensuscall_intersect_assignment_after_match.csv", "donor_match/test2/souporcell_vs_bff_raw", "donor_match/test2/souporcell_vs_bff_raw/test2_souporcell_vs_bff_raw_all_assignment_after_match.csv", - "donor_match/test2/souporcell_vs_bff_raw/test2_souporcell_vs_bff_raw_concordance_heatmap.png", "donor_match/test2/souporcell_vs_bff_raw/test2_souporcell_vs_bff_raw_correlation_res.csv", "donor_match/test2/souporcell_vs_bff_raw/test2_souporcell_vs_bff_raw_donor_match.csv", "donor_match/test2/souporcell_vs_bff_raw/test2_souporcell_vs_bff_raw_intersect_assignment_after_match.csv", "donor_match/test2/souporcell_vs_demuxem", "donor_match/test2/souporcell_vs_demuxem/test2_souporcell_vs_demuxem_all_assignment_after_match.csv", - "donor_match/test2/souporcell_vs_demuxem/test2_souporcell_vs_demuxem_concordance_heatmap.png", "donor_match/test2/souporcell_vs_demuxem/test2_souporcell_vs_demuxem_correlation_res.csv", "donor_match/test2/souporcell_vs_demuxem/test2_souporcell_vs_demuxem_donor_match.csv", "donor_match/test2/souporcell_vs_demuxem/test2_souporcell_vs_demuxem_intersect_assignment_after_match.csv", "donor_match/test2/souporcell_vs_gmmdemux", "donor_match/test2/souporcell_vs_gmmdemux/test2_souporcell_vs_gmmdemux_all_assignment_after_match.csv", - "donor_match/test2/souporcell_vs_gmmdemux/test2_souporcell_vs_gmmdemux_concordance_heatmap.png", "donor_match/test2/souporcell_vs_gmmdemux/test2_souporcell_vs_gmmdemux_correlation_res.csv", "donor_match/test2/souporcell_vs_gmmdemux/test2_souporcell_vs_gmmdemux_donor_match.csv", "donor_match/test2/souporcell_vs_gmmdemux/test2_souporcell_vs_gmmdemux_intersect_assignment_after_match.csv", "donor_match/test2/souporcell_vs_hasheddrops", "donor_match/test2/souporcell_vs_hasheddrops/test2_souporcell_vs_hasheddrops_all_assignment_after_match.csv", - "donor_match/test2/souporcell_vs_hasheddrops/test2_souporcell_vs_hasheddrops_concordance_heatmap.png", "donor_match/test2/souporcell_vs_hasheddrops/test2_souporcell_vs_hasheddrops_correlation_res.csv", "donor_match/test2/souporcell_vs_hasheddrops/test2_souporcell_vs_hasheddrops_donor_match.csv", "donor_match/test2/souporcell_vs_hasheddrops/test2_souporcell_vs_hasheddrops_intersect_assignment_after_match.csv", "donor_match/test2/souporcell_vs_hashsolo", "donor_match/test2/souporcell_vs_hashsolo/test2_souporcell_vs_hashsolo_all_assignment_after_match.csv", - "donor_match/test2/souporcell_vs_hashsolo/test2_souporcell_vs_hashsolo_concordance_heatmap.png", "donor_match/test2/souporcell_vs_hashsolo/test2_souporcell_vs_hashsolo_correlation_res.csv", "donor_match/test2/souporcell_vs_hashsolo/test2_souporcell_vs_hashsolo_donor_match.csv", "donor_match/test2/souporcell_vs_hashsolo/test2_souporcell_vs_hashsolo_intersect_assignment_after_match.csv", "donor_match/test2/souporcell_vs_htodemux", "donor_match/test2/souporcell_vs_htodemux/test2_souporcell_vs_htodemux_all_assignment_after_match.csv", - "donor_match/test2/souporcell_vs_htodemux/test2_souporcell_vs_htodemux_concordance_heatmap.png", "donor_match/test2/souporcell_vs_htodemux/test2_souporcell_vs_htodemux_correlation_res.csv", "donor_match/test2/souporcell_vs_htodemux/test2_souporcell_vs_htodemux_donor_match.csv", "donor_match/test2/souporcell_vs_htodemux/test2_souporcell_vs_htodemux_intersect_assignment_after_match.csv", "donor_match/test2/souporcell_vs_multiseq", "donor_match/test2/souporcell_vs_multiseq/test2_souporcell_vs_multiseq_all_assignment_after_match.csv", - "donor_match/test2/souporcell_vs_multiseq/test2_souporcell_vs_multiseq_concordance_heatmap.png", "donor_match/test2/souporcell_vs_multiseq/test2_souporcell_vs_multiseq_correlation_res.csv", "donor_match/test2/souporcell_vs_multiseq/test2_souporcell_vs_multiseq_donor_match.csv", "donor_match/test2/souporcell_vs_multiseq/test2_souporcell_vs_multiseq_intersect_assignment_after_match.csv", @@ -341,97 +309,81 @@ "donor_match/test3", "donor_match/test3/freemuxlet_vs_bff_consensuscall", "donor_match/test3/freemuxlet_vs_bff_consensuscall/test3_freemuxlet_vs_bff_consensuscall_all_assignment_after_match.csv", - "donor_match/test3/freemuxlet_vs_bff_consensuscall/test3_freemuxlet_vs_bff_consensuscall_concordance_heatmap.png", "donor_match/test3/freemuxlet_vs_bff_consensuscall/test3_freemuxlet_vs_bff_consensuscall_correlation_res.csv", "donor_match/test3/freemuxlet_vs_bff_consensuscall/test3_freemuxlet_vs_bff_consensuscall_donor_match.csv", "donor_match/test3/freemuxlet_vs_bff_consensuscall/test3_freemuxlet_vs_bff_consensuscall_intersect_assignment_after_match.csv", "donor_match/test3/freemuxlet_vs_bff_raw", "donor_match/test3/freemuxlet_vs_bff_raw/test3_freemuxlet_vs_bff_raw_all_assignment_after_match.csv", - "donor_match/test3/freemuxlet_vs_bff_raw/test3_freemuxlet_vs_bff_raw_concordance_heatmap.png", "donor_match/test3/freemuxlet_vs_bff_raw/test3_freemuxlet_vs_bff_raw_correlation_res.csv", "donor_match/test3/freemuxlet_vs_bff_raw/test3_freemuxlet_vs_bff_raw_donor_match.csv", "donor_match/test3/freemuxlet_vs_bff_raw/test3_freemuxlet_vs_bff_raw_intersect_assignment_after_match.csv", "donor_match/test3/freemuxlet_vs_demuxem", "donor_match/test3/freemuxlet_vs_demuxem/test3_freemuxlet_vs_demuxem_all_assignment_after_match.csv", - "donor_match/test3/freemuxlet_vs_demuxem/test3_freemuxlet_vs_demuxem_concordance_heatmap.png", "donor_match/test3/freemuxlet_vs_demuxem/test3_freemuxlet_vs_demuxem_correlation_res.csv", "donor_match/test3/freemuxlet_vs_demuxem/test3_freemuxlet_vs_demuxem_donor_match.csv", "donor_match/test3/freemuxlet_vs_demuxem/test3_freemuxlet_vs_demuxem_intersect_assignment_after_match.csv", "donor_match/test3/freemuxlet_vs_gmmdemux", "donor_match/test3/freemuxlet_vs_gmmdemux/test3_freemuxlet_vs_gmmdemux_all_assignment_after_match.csv", - "donor_match/test3/freemuxlet_vs_gmmdemux/test3_freemuxlet_vs_gmmdemux_concordance_heatmap.png", "donor_match/test3/freemuxlet_vs_gmmdemux/test3_freemuxlet_vs_gmmdemux_correlation_res.csv", "donor_match/test3/freemuxlet_vs_gmmdemux/test3_freemuxlet_vs_gmmdemux_donor_match.csv", "donor_match/test3/freemuxlet_vs_gmmdemux/test3_freemuxlet_vs_gmmdemux_intersect_assignment_after_match.csv", "donor_match/test3/freemuxlet_vs_hasheddrops", "donor_match/test3/freemuxlet_vs_hasheddrops/test3_freemuxlet_vs_hasheddrops_all_assignment_after_match.csv", - "donor_match/test3/freemuxlet_vs_hasheddrops/test3_freemuxlet_vs_hasheddrops_concordance_heatmap.png", "donor_match/test3/freemuxlet_vs_hasheddrops/test3_freemuxlet_vs_hasheddrops_correlation_res.csv", "donor_match/test3/freemuxlet_vs_hasheddrops/test3_freemuxlet_vs_hasheddrops_donor_match.csv", "donor_match/test3/freemuxlet_vs_hasheddrops/test3_freemuxlet_vs_hasheddrops_intersect_assignment_after_match.csv", "donor_match/test3/freemuxlet_vs_hashsolo", "donor_match/test3/freemuxlet_vs_hashsolo/test3_freemuxlet_vs_hashsolo_all_assignment_after_match.csv", - "donor_match/test3/freemuxlet_vs_hashsolo/test3_freemuxlet_vs_hashsolo_concordance_heatmap.png", "donor_match/test3/freemuxlet_vs_hashsolo/test3_freemuxlet_vs_hashsolo_correlation_res.csv", "donor_match/test3/freemuxlet_vs_hashsolo/test3_freemuxlet_vs_hashsolo_donor_match.csv", "donor_match/test3/freemuxlet_vs_hashsolo/test3_freemuxlet_vs_hashsolo_intersect_assignment_after_match.csv", "donor_match/test3/freemuxlet_vs_htodemux", "donor_match/test3/freemuxlet_vs_htodemux/test3_freemuxlet_vs_htodemux_all_assignment_after_match.csv", - "donor_match/test3/freemuxlet_vs_htodemux/test3_freemuxlet_vs_htodemux_concordance_heatmap.png", "donor_match/test3/freemuxlet_vs_htodemux/test3_freemuxlet_vs_htodemux_correlation_res.csv", "donor_match/test3/freemuxlet_vs_htodemux/test3_freemuxlet_vs_htodemux_donor_match.csv", "donor_match/test3/freemuxlet_vs_htodemux/test3_freemuxlet_vs_htodemux_intersect_assignment_after_match.csv", "donor_match/test3/freemuxlet_vs_multiseq", "donor_match/test3/freemuxlet_vs_multiseq/test3_freemuxlet_vs_multiseq_all_assignment_after_match.csv", - "donor_match/test3/freemuxlet_vs_multiseq/test3_freemuxlet_vs_multiseq_concordance_heatmap.png", "donor_match/test3/freemuxlet_vs_multiseq/test3_freemuxlet_vs_multiseq_correlation_res.csv", "donor_match/test3/freemuxlet_vs_multiseq/test3_freemuxlet_vs_multiseq_donor_match.csv", "donor_match/test3/freemuxlet_vs_multiseq/test3_freemuxlet_vs_multiseq_intersect_assignment_after_match.csv", "donor_match/test3/souporcell_vs_bff_consensuscall", "donor_match/test3/souporcell_vs_bff_consensuscall/test3_souporcell_vs_bff_consensuscall_all_assignment_after_match.csv", - "donor_match/test3/souporcell_vs_bff_consensuscall/test3_souporcell_vs_bff_consensuscall_concordance_heatmap.png", "donor_match/test3/souporcell_vs_bff_consensuscall/test3_souporcell_vs_bff_consensuscall_correlation_res.csv", "donor_match/test3/souporcell_vs_bff_consensuscall/test3_souporcell_vs_bff_consensuscall_donor_match.csv", "donor_match/test3/souporcell_vs_bff_consensuscall/test3_souporcell_vs_bff_consensuscall_intersect_assignment_after_match.csv", "donor_match/test3/souporcell_vs_bff_raw", "donor_match/test3/souporcell_vs_bff_raw/test3_souporcell_vs_bff_raw_all_assignment_after_match.csv", - "donor_match/test3/souporcell_vs_bff_raw/test3_souporcell_vs_bff_raw_concordance_heatmap.png", "donor_match/test3/souporcell_vs_bff_raw/test3_souporcell_vs_bff_raw_correlation_res.csv", "donor_match/test3/souporcell_vs_bff_raw/test3_souporcell_vs_bff_raw_donor_match.csv", "donor_match/test3/souporcell_vs_bff_raw/test3_souporcell_vs_bff_raw_intersect_assignment_after_match.csv", "donor_match/test3/souporcell_vs_demuxem", "donor_match/test3/souporcell_vs_demuxem/test3_souporcell_vs_demuxem_all_assignment_after_match.csv", - "donor_match/test3/souporcell_vs_demuxem/test3_souporcell_vs_demuxem_concordance_heatmap.png", "donor_match/test3/souporcell_vs_demuxem/test3_souporcell_vs_demuxem_correlation_res.csv", "donor_match/test3/souporcell_vs_demuxem/test3_souporcell_vs_demuxem_donor_match.csv", "donor_match/test3/souporcell_vs_demuxem/test3_souporcell_vs_demuxem_intersect_assignment_after_match.csv", "donor_match/test3/souporcell_vs_gmmdemux", "donor_match/test3/souporcell_vs_gmmdemux/test3_souporcell_vs_gmmdemux_all_assignment_after_match.csv", - "donor_match/test3/souporcell_vs_gmmdemux/test3_souporcell_vs_gmmdemux_concordance_heatmap.png", "donor_match/test3/souporcell_vs_gmmdemux/test3_souporcell_vs_gmmdemux_correlation_res.csv", "donor_match/test3/souporcell_vs_gmmdemux/test3_souporcell_vs_gmmdemux_donor_match.csv", "donor_match/test3/souporcell_vs_gmmdemux/test3_souporcell_vs_gmmdemux_intersect_assignment_after_match.csv", "donor_match/test3/souporcell_vs_hasheddrops", "donor_match/test3/souporcell_vs_hasheddrops/test3_souporcell_vs_hasheddrops_all_assignment_after_match.csv", - "donor_match/test3/souporcell_vs_hasheddrops/test3_souporcell_vs_hasheddrops_concordance_heatmap.png", "donor_match/test3/souporcell_vs_hasheddrops/test3_souporcell_vs_hasheddrops_correlation_res.csv", "donor_match/test3/souporcell_vs_hasheddrops/test3_souporcell_vs_hasheddrops_donor_match.csv", "donor_match/test3/souporcell_vs_hasheddrops/test3_souporcell_vs_hasheddrops_intersect_assignment_after_match.csv", "donor_match/test3/souporcell_vs_hashsolo", "donor_match/test3/souporcell_vs_hashsolo/test3_souporcell_vs_hashsolo_all_assignment_after_match.csv", - "donor_match/test3/souporcell_vs_hashsolo/test3_souporcell_vs_hashsolo_concordance_heatmap.png", "donor_match/test3/souporcell_vs_hashsolo/test3_souporcell_vs_hashsolo_correlation_res.csv", "donor_match/test3/souporcell_vs_hashsolo/test3_souporcell_vs_hashsolo_donor_match.csv", "donor_match/test3/souporcell_vs_hashsolo/test3_souporcell_vs_hashsolo_intersect_assignment_after_match.csv", "donor_match/test3/souporcell_vs_htodemux", "donor_match/test3/souporcell_vs_htodemux/test3_souporcell_vs_htodemux_all_assignment_after_match.csv", - "donor_match/test3/souporcell_vs_htodemux/test3_souporcell_vs_htodemux_concordance_heatmap.png", "donor_match/test3/souporcell_vs_htodemux/test3_souporcell_vs_htodemux_correlation_res.csv", "donor_match/test3/souporcell_vs_htodemux/test3_souporcell_vs_htodemux_donor_match.csv", "donor_match/test3/souporcell_vs_htodemux/test3_souporcell_vs_htodemux_intersect_assignment_after_match.csv", "donor_match/test3/souporcell_vs_multiseq", "donor_match/test3/souporcell_vs_multiseq/test3_souporcell_vs_multiseq_all_assignment_after_match.csv", - "donor_match/test3/souporcell_vs_multiseq/test3_souporcell_vs_multiseq_concordance_heatmap.png", "donor_match/test3/souporcell_vs_multiseq/test3_souporcell_vs_multiseq_correlation_res.csv", "donor_match/test3/souporcell_vs_multiseq/test3_souporcell_vs_multiseq_donor_match.csv", "donor_match/test3/souporcell_vs_multiseq/test3_souporcell_vs_multiseq_intersect_assignment_after_match.csv", @@ -454,7 +406,6 @@ "find_variants/test1/subset_gt_donors/test1_vireo.vcf.gz", "find_variants/test1/test1_all_representative_variants.csv", "find_variants/test1/test1_donor_specific_variants.csv", - "find_variants/test1/test1_donor_specific_variants_upset.png", "find_variants/test1/test1_vireo_variants.csv", "find_variants/test2", "find_variants/test2/hto-1", @@ -470,7 +421,6 @@ "find_variants/test2/subset_gt_donors/test2_vireo.vcf.gz", "find_variants/test2/test2_all_representative_variants.csv", "find_variants/test2/test2_donor_specific_variants.csv", - "find_variants/test2/test2_donor_specific_variants_upset.png", "find_variants/test2/test2_vireo_variants.csv", "find_variants/test3", "find_variants/test3/hto-1", @@ -486,7 +436,6 @@ "find_variants/test3/subset_gt_donors/test3_vireo.vcf.gz", "find_variants/test3/test3_all_representative_variants.csv", "find_variants/test3/test3_donor_specific_variants.csv", - "find_variants/test3/test3_donor_specific_variants_upset.png", "find_variants/test3/test3_vireo_variants.csv", "genetic", "genetic/popscle", @@ -640,30 +589,24 @@ "hashing/hasheddrops", "hashing/hasheddrops/test1", "hashing/hasheddrops/test1/test1_emptyDrops.csv", - "hashing/hasheddrops/test1/test1_emptyDrops.png", "hashing/hasheddrops/test1/test1_emptyDrops.rds", "hashing/hasheddrops/test1/test1_hasheddrops.rds", "hashing/hasheddrops/test1/test1_id_to_hash.csv", "hashing/hasheddrops/test1/test1_params_hasheddrops.csv", - "hashing/hasheddrops/test1/test1_plot_hasheddrops.png", "hashing/hasheddrops/test1/test1_results_hasheddrops.csv", "hashing/hasheddrops/test2", "hashing/hasheddrops/test2/test2_emptyDrops.csv", - "hashing/hasheddrops/test2/test2_emptyDrops.png", "hashing/hasheddrops/test2/test2_emptyDrops.rds", "hashing/hasheddrops/test2/test2_hasheddrops.rds", "hashing/hasheddrops/test2/test2_id_to_hash.csv", "hashing/hasheddrops/test2/test2_params_hasheddrops.csv", - "hashing/hasheddrops/test2/test2_plot_hasheddrops.png", "hashing/hasheddrops/test2/test2_results_hasheddrops.csv", "hashing/hasheddrops/test3", "hashing/hasheddrops/test3/test3_emptyDrops.csv", - "hashing/hasheddrops/test3/test3_emptyDrops.png", "hashing/hasheddrops/test3/test3_emptyDrops.rds", "hashing/hasheddrops/test3/test3_hasheddrops.rds", "hashing/hasheddrops/test3/test3_id_to_hash.csv", "hashing/hasheddrops/test3/test3_params_hasheddrops.csv", - "hashing/hasheddrops/test3/test3_plot_hasheddrops.png", "hashing/hasheddrops/test3/test3_results_hasheddrops.csv", "hashing/hashsolo", "hashing/hashsolo/test1", @@ -685,11 +628,6 @@ "hashing/htodemux/test1/test1_htodemux.rds", "hashing/htodemux/test1/test1_params_htodemux.csv", "hashing/htodemux/test1/visualization", - "hashing/htodemux/test1/visualization/test1_featureScatter_htodemux.jpeg", - "hashing/htodemux/test1/visualization/test1_heatMap_htodemux.jpeg", - "hashing/htodemux/test1/visualization/test1_ridge_htodemux.jpeg", - "hashing/htodemux/test1/visualization/test1_tSNE_htodemux.jpeg", - "hashing/htodemux/test1/visualization/test1_violinPlot_htodemux.jpeg", "hashing/htodemux/test1/visualization/test1_visual_params_htodemux.csv", "hashing/htodemux/test2", "hashing/htodemux/test2/test2_assignment_htodemux.csv", @@ -697,11 +635,6 @@ "hashing/htodemux/test2/test2_htodemux.rds", "hashing/htodemux/test2/test2_params_htodemux.csv", "hashing/htodemux/test2/visualization", - "hashing/htodemux/test2/visualization/test2_featureScatter_htodemux.jpeg", - "hashing/htodemux/test2/visualization/test2_heatMap_htodemux.jpeg", - "hashing/htodemux/test2/visualization/test2_ridge_htodemux.jpeg", - "hashing/htodemux/test2/visualization/test2_tSNE_htodemux.jpeg", - "hashing/htodemux/test2/visualization/test2_violinPlot_htodemux.jpeg", "hashing/htodemux/test2/visualization/test2_visual_params_htodemux.csv", "hashing/htodemux/test3", "hashing/htodemux/test3/test3_assignment_htodemux.csv", @@ -709,11 +642,6 @@ "hashing/htodemux/test3/test3_htodemux.rds", "hashing/htodemux/test3/test3_params_htodemux.csv", "hashing/htodemux/test3/visualization", - "hashing/htodemux/test3/visualization/test3_featureScatter_htodemux.jpeg", - "hashing/htodemux/test3/visualization/test3_heatMap_htodemux.jpeg", - "hashing/htodemux/test3/visualization/test3_ridge_htodemux.jpeg", - "hashing/htodemux/test3/visualization/test3_tSNE_htodemux.jpeg", - "hashing/htodemux/test3/visualization/test3_violinPlot_htodemux.jpeg", "hashing/htodemux/test3/visualization/test3_visual_params_htodemux.csv", "hashing/multiseqdemux", "hashing/multiseqdemux/test1", @@ -778,82 +706,66 @@ ], [ "test1_freemuxlet_vs_bff_consensuscall_all_assignment_after_match.csv:md5,de2093a5ff281af46d45d7d4770338cf", - "test1_freemuxlet_vs_bff_consensuscall_concordance_heatmap.png:md5,5c3e82573df24e781d4f422bc3b6925a", "test1_freemuxlet_vs_bff_consensuscall_correlation_res.csv:md5,5f2f2076175e313f10396b32fac4c26c", "test1_freemuxlet_vs_bff_consensuscall_donor_match.csv:md5,b92352374bf4b32f7e3a1b032a4c8b5f", "test1_freemuxlet_vs_bff_consensuscall_intersect_assignment_after_match.csv:md5,a4df295c2689a2b3abfab4476e5a1dcb", "test1_freemuxlet_vs_bff_raw_all_assignment_after_match.csv:md5,8c42806ae93d0bd0fc79864db6a79463", - "test1_freemuxlet_vs_bff_raw_concordance_heatmap.png:md5,5c3e82573df24e781d4f422bc3b6925a", "test1_freemuxlet_vs_bff_raw_correlation_res.csv:md5,5f2f2076175e313f10396b32fac4c26c", "test1_freemuxlet_vs_bff_raw_donor_match.csv:md5,b92352374bf4b32f7e3a1b032a4c8b5f", "test1_freemuxlet_vs_bff_raw_intersect_assignment_after_match.csv:md5,6af3427166aab49c0136369311533d83", "test1_freemuxlet_vs_demuxem_all_assignment_after_match.csv:md5,505b79ce4db1cef804bfeafb12f70a5d", - "test1_freemuxlet_vs_demuxem_concordance_heatmap.png:md5,360bc756be590ccb80d2add912d61e16", "test1_freemuxlet_vs_demuxem_correlation_res.csv:md5,c880595fe0a83d46f1bcbb361c994828", "test1_freemuxlet_vs_demuxem_donor_match.csv:md5,b92352374bf4b32f7e3a1b032a4c8b5f", "test1_freemuxlet_vs_demuxem_intersect_assignment_after_match.csv:md5,edf378d87391a486b6635713e04fdbd9", "test1_freemuxlet_vs_gmmdemux_all_assignment_after_match.csv:md5,473d87d9a65d8562e83d21067cfce536", - "test1_freemuxlet_vs_gmmdemux_concordance_heatmap.png:md5,5c3e82573df24e781d4f422bc3b6925a", "test1_freemuxlet_vs_gmmdemux_correlation_res.csv:md5,5f2f2076175e313f10396b32fac4c26c", "test1_freemuxlet_vs_gmmdemux_donor_match.csv:md5,b92352374bf4b32f7e3a1b032a4c8b5f", "test1_freemuxlet_vs_gmmdemux_intersect_assignment_after_match.csv:md5,d2f63d5a37e4a085fdd0ee97bfffe4f4", "test1_freemuxlet_vs_hasheddrops_all_assignment_after_match.csv:md5,fb66dca65ba0ddaec50b78f4be1a662a", - "test1_freemuxlet_vs_hasheddrops_concordance_heatmap.png:md5,5c3e82573df24e781d4f422bc3b6925a", "test1_freemuxlet_vs_hasheddrops_correlation_res.csv:md5,5f2f2076175e313f10396b32fac4c26c", "test1_freemuxlet_vs_hasheddrops_donor_match.csv:md5,b92352374bf4b32f7e3a1b032a4c8b5f", "test1_freemuxlet_vs_hasheddrops_intersect_assignment_after_match.csv:md5,b6e3daca5b12b963239eb9ff86ef7244", "test1_freemuxlet_vs_hashsolo_all_assignment_after_match.csv:md5,8dbc642b118d82fe0dbb24631126d93b", - "test1_freemuxlet_vs_hashsolo_concordance_heatmap.png:md5,a6dd712948a85cf2f1660b4f6a07a5cb", "test1_freemuxlet_vs_hashsolo_correlation_res.csv:md5,f6d61938702d82cca7a5ecbe4871a3fa", "test1_freemuxlet_vs_hashsolo_donor_match.csv:md5,b92352374bf4b32f7e3a1b032a4c8b5f", "test1_freemuxlet_vs_hashsolo_intersect_assignment_after_match.csv:md5,69a6c9f528d16e72d1a16927d65317fa", "test1_freemuxlet_vs_htodemux_all_assignment_after_match.csv:md5,c9413355e0d822c0436fecf1706d0cfc", - "test1_freemuxlet_vs_htodemux_concordance_heatmap.png:md5,42ab19f0e287585a39d52817f85943f0", "test1_freemuxlet_vs_htodemux_correlation_res.csv:md5,56e1f17bb5e0424e4840b2d89fe892d7", "test1_freemuxlet_vs_htodemux_donor_match.csv:md5,b92352374bf4b32f7e3a1b032a4c8b5f", "test1_freemuxlet_vs_htodemux_intersect_assignment_after_match.csv:md5,32cee037b091f9b1ca78ed88e11c75d4", "test1_freemuxlet_vs_multiseq_all_assignment_after_match.csv:md5,cb8a579a6233cf227e4eb987fed88dc0", - "test1_freemuxlet_vs_multiseq_concordance_heatmap.png:md5,64bc44fb9aeeb1581ebbf708c8765b4d", "test1_freemuxlet_vs_multiseq_correlation_res.csv:md5,8a690fde26636e0e4c44b41a156ae47a", "test1_freemuxlet_vs_multiseq_donor_match.csv:md5,b92352374bf4b32f7e3a1b032a4c8b5f", "test1_freemuxlet_vs_multiseq_intersect_assignment_after_match.csv:md5,bd1135d6b159a17c245742d6c4312219", "test1_souporcell_vs_bff_consensuscall_all_assignment_after_match.csv:md5,0523d43db3b0b43d3c06642887b1e681", - "test1_souporcell_vs_bff_consensuscall_concordance_heatmap.png:md5,d48df9ebb22dc1f2616511ad0405cdfd", "test1_souporcell_vs_bff_consensuscall_correlation_res.csv:md5,7e632ed518a1e6f02ed1ea25a28a4e45", "test1_souporcell_vs_bff_consensuscall_donor_match.csv:md5,d1feea9baad73b5bc106472a226cbbf2", "test1_souporcell_vs_bff_consensuscall_intersect_assignment_after_match.csv:md5,d0069819b2b9722cc72ee2fae18e24d7", "test1_souporcell_vs_bff_raw_all_assignment_after_match.csv:md5,3693453872310717f649a8da3538a8b2", - "test1_souporcell_vs_bff_raw_concordance_heatmap.png:md5,d48df9ebb22dc1f2616511ad0405cdfd", "test1_souporcell_vs_bff_raw_correlation_res.csv:md5,7e632ed518a1e6f02ed1ea25a28a4e45", "test1_souporcell_vs_bff_raw_donor_match.csv:md5,d1feea9baad73b5bc106472a226cbbf2", "test1_souporcell_vs_bff_raw_intersect_assignment_after_match.csv:md5,9618d277186327f7092b4a838329c15c", "test1_souporcell_vs_demuxem_all_assignment_after_match.csv:md5,02807a959d832624e2869d6c885a3676", - "test1_souporcell_vs_demuxem_concordance_heatmap.png:md5,8de7171a8fc0ec2f38e6f45e6c28cb03", "test1_souporcell_vs_demuxem_correlation_res.csv:md5,24eca3fc8a0241e70b02055107683b91", "test1_souporcell_vs_demuxem_donor_match.csv:md5,d1feea9baad73b5bc106472a226cbbf2", "test1_souporcell_vs_demuxem_intersect_assignment_after_match.csv:md5,14280a2e649c89a8eb27f9c36798bc97", "test1_souporcell_vs_gmmdemux_all_assignment_after_match.csv:md5,e163bc80fc846e43cca5d6281dc2bbc8", - "test1_souporcell_vs_gmmdemux_concordance_heatmap.png:md5,d48df9ebb22dc1f2616511ad0405cdfd", "test1_souporcell_vs_gmmdemux_correlation_res.csv:md5,7e632ed518a1e6f02ed1ea25a28a4e45", "test1_souporcell_vs_gmmdemux_donor_match.csv:md5,d1feea9baad73b5bc106472a226cbbf2", "test1_souporcell_vs_gmmdemux_intersect_assignment_after_match.csv:md5,dd7403edeb5ba0e17da879a3ff310aba", "test1_souporcell_vs_hasheddrops_all_assignment_after_match.csv:md5,2177729941ec7e0ec3e30490367ac648", - "test1_souporcell_vs_hasheddrops_concordance_heatmap.png:md5,d48df9ebb22dc1f2616511ad0405cdfd", "test1_souporcell_vs_hasheddrops_correlation_res.csv:md5,7e632ed518a1e6f02ed1ea25a28a4e45", "test1_souporcell_vs_hasheddrops_donor_match.csv:md5,d1feea9baad73b5bc106472a226cbbf2", "test1_souporcell_vs_hasheddrops_intersect_assignment_after_match.csv:md5,595efa559856d1dfc7bae9d109478221", "test1_souporcell_vs_hashsolo_all_assignment_after_match.csv:md5,89a1b529a2f3805193a3547771df15bc", - "test1_souporcell_vs_hashsolo_concordance_heatmap.png:md5,aafe59185e6eef45a25585a8c06505e3", "test1_souporcell_vs_hashsolo_correlation_res.csv:md5,bdcc36b8fcabb311fa40d3d48e2b3080", "test1_souporcell_vs_hashsolo_donor_match.csv:md5,d1feea9baad73b5bc106472a226cbbf2", "test1_souporcell_vs_hashsolo_intersect_assignment_after_match.csv:md5,b9e34df8e9037c17fce729b661480b7f", "test1_souporcell_vs_htodemux_all_assignment_after_match.csv:md5,986fb63a696867e670b14a7d3c299eeb", - "test1_souporcell_vs_htodemux_concordance_heatmap.png:md5,d48df9ebb22dc1f2616511ad0405cdfd", "test1_souporcell_vs_htodemux_correlation_res.csv:md5,7e632ed518a1e6f02ed1ea25a28a4e45", "test1_souporcell_vs_htodemux_donor_match.csv:md5,d1feea9baad73b5bc106472a226cbbf2", "test1_souporcell_vs_htodemux_intersect_assignment_after_match.csv:md5,f9ac7bf74aea62b5cb9bd34f5fe002b2", "test1_souporcell_vs_multiseq_all_assignment_after_match.csv:md5,71a33f1f8070410b7c016bb7234a89e2", - "test1_souporcell_vs_multiseq_concordance_heatmap.png:md5,987ae361340b8b77daecc726ffa6b9de", "test1_souporcell_vs_multiseq_correlation_res.csv:md5,a5f9d7ebd675403b062cd0f4a59b1592", "test1_souporcell_vs_multiseq_donor_match.csv:md5,d1feea9baad73b5bc106472a226cbbf2", "test1_souporcell_vs_multiseq_intersect_assignment_after_match.csv:md5,a904d4236db50ef2d35fa4e0d608284f", @@ -862,82 +774,66 @@ "test1_best_intersect_assignment_after_match.csv:md5,d0069819b2b9722cc72ee2fae18e24d7", "test1_score_record.csv:md5,f85dc45d1e25a47aa1ce5df0f3652963", "test2_freemuxlet_vs_bff_consensuscall_all_assignment_after_match.csv:md5,de2093a5ff281af46d45d7d4770338cf", - "test2_freemuxlet_vs_bff_consensuscall_concordance_heatmap.png:md5,5c3e82573df24e781d4f422bc3b6925a", "test2_freemuxlet_vs_bff_consensuscall_correlation_res.csv:md5,5f2f2076175e313f10396b32fac4c26c", "test2_freemuxlet_vs_bff_consensuscall_donor_match.csv:md5,b92352374bf4b32f7e3a1b032a4c8b5f", "test2_freemuxlet_vs_bff_consensuscall_intersect_assignment_after_match.csv:md5,a4df295c2689a2b3abfab4476e5a1dcb", "test2_freemuxlet_vs_bff_raw_all_assignment_after_match.csv:md5,8c42806ae93d0bd0fc79864db6a79463", - "test2_freemuxlet_vs_bff_raw_concordance_heatmap.png:md5,5c3e82573df24e781d4f422bc3b6925a", "test2_freemuxlet_vs_bff_raw_correlation_res.csv:md5,5f2f2076175e313f10396b32fac4c26c", "test2_freemuxlet_vs_bff_raw_donor_match.csv:md5,b92352374bf4b32f7e3a1b032a4c8b5f", "test2_freemuxlet_vs_bff_raw_intersect_assignment_after_match.csv:md5,6af3427166aab49c0136369311533d83", "test2_freemuxlet_vs_demuxem_all_assignment_after_match.csv:md5,505b79ce4db1cef804bfeafb12f70a5d", - "test2_freemuxlet_vs_demuxem_concordance_heatmap.png:md5,360bc756be590ccb80d2add912d61e16", "test2_freemuxlet_vs_demuxem_correlation_res.csv:md5,c880595fe0a83d46f1bcbb361c994828", "test2_freemuxlet_vs_demuxem_donor_match.csv:md5,b92352374bf4b32f7e3a1b032a4c8b5f", "test2_freemuxlet_vs_demuxem_intersect_assignment_after_match.csv:md5,edf378d87391a486b6635713e04fdbd9", "test2_freemuxlet_vs_gmmdemux_all_assignment_after_match.csv:md5,473d87d9a65d8562e83d21067cfce536", - "test2_freemuxlet_vs_gmmdemux_concordance_heatmap.png:md5,5c3e82573df24e781d4f422bc3b6925a", "test2_freemuxlet_vs_gmmdemux_correlation_res.csv:md5,5f2f2076175e313f10396b32fac4c26c", "test2_freemuxlet_vs_gmmdemux_donor_match.csv:md5,b92352374bf4b32f7e3a1b032a4c8b5f", "test2_freemuxlet_vs_gmmdemux_intersect_assignment_after_match.csv:md5,d2f63d5a37e4a085fdd0ee97bfffe4f4", "test2_freemuxlet_vs_hasheddrops_all_assignment_after_match.csv:md5,fb66dca65ba0ddaec50b78f4be1a662a", - "test2_freemuxlet_vs_hasheddrops_concordance_heatmap.png:md5,5c3e82573df24e781d4f422bc3b6925a", "test2_freemuxlet_vs_hasheddrops_correlation_res.csv:md5,5f2f2076175e313f10396b32fac4c26c", "test2_freemuxlet_vs_hasheddrops_donor_match.csv:md5,b92352374bf4b32f7e3a1b032a4c8b5f", "test2_freemuxlet_vs_hasheddrops_intersect_assignment_after_match.csv:md5,b6e3daca5b12b963239eb9ff86ef7244", "test2_freemuxlet_vs_hashsolo_all_assignment_after_match.csv:md5,8dbc642b118d82fe0dbb24631126d93b", - "test2_freemuxlet_vs_hashsolo_concordance_heatmap.png:md5,a6dd712948a85cf2f1660b4f6a07a5cb", "test2_freemuxlet_vs_hashsolo_correlation_res.csv:md5,f6d61938702d82cca7a5ecbe4871a3fa", "test2_freemuxlet_vs_hashsolo_donor_match.csv:md5,b92352374bf4b32f7e3a1b032a4c8b5f", "test2_freemuxlet_vs_hashsolo_intersect_assignment_after_match.csv:md5,69a6c9f528d16e72d1a16927d65317fa", "test2_freemuxlet_vs_htodemux_all_assignment_after_match.csv:md5,c9413355e0d822c0436fecf1706d0cfc", - "test2_freemuxlet_vs_htodemux_concordance_heatmap.png:md5,42ab19f0e287585a39d52817f85943f0", "test2_freemuxlet_vs_htodemux_correlation_res.csv:md5,56e1f17bb5e0424e4840b2d89fe892d7", "test2_freemuxlet_vs_htodemux_donor_match.csv:md5,b92352374bf4b32f7e3a1b032a4c8b5f", "test2_freemuxlet_vs_htodemux_intersect_assignment_after_match.csv:md5,32cee037b091f9b1ca78ed88e11c75d4", "test2_freemuxlet_vs_multiseq_all_assignment_after_match.csv:md5,cb8a579a6233cf227e4eb987fed88dc0", - "test2_freemuxlet_vs_multiseq_concordance_heatmap.png:md5,64bc44fb9aeeb1581ebbf708c8765b4d", "test2_freemuxlet_vs_multiseq_correlation_res.csv:md5,8a690fde26636e0e4c44b41a156ae47a", "test2_freemuxlet_vs_multiseq_donor_match.csv:md5,b92352374bf4b32f7e3a1b032a4c8b5f", "test2_freemuxlet_vs_multiseq_intersect_assignment_after_match.csv:md5,bd1135d6b159a17c245742d6c4312219", "test2_souporcell_vs_bff_consensuscall_all_assignment_after_match.csv:md5,0523d43db3b0b43d3c06642887b1e681", - "test2_souporcell_vs_bff_consensuscall_concordance_heatmap.png:md5,d48df9ebb22dc1f2616511ad0405cdfd", "test2_souporcell_vs_bff_consensuscall_correlation_res.csv:md5,7e632ed518a1e6f02ed1ea25a28a4e45", "test2_souporcell_vs_bff_consensuscall_donor_match.csv:md5,d1feea9baad73b5bc106472a226cbbf2", "test2_souporcell_vs_bff_consensuscall_intersect_assignment_after_match.csv:md5,d0069819b2b9722cc72ee2fae18e24d7", "test2_souporcell_vs_bff_raw_all_assignment_after_match.csv:md5,3693453872310717f649a8da3538a8b2", - "test2_souporcell_vs_bff_raw_concordance_heatmap.png:md5,d48df9ebb22dc1f2616511ad0405cdfd", "test2_souporcell_vs_bff_raw_correlation_res.csv:md5,7e632ed518a1e6f02ed1ea25a28a4e45", "test2_souporcell_vs_bff_raw_donor_match.csv:md5,d1feea9baad73b5bc106472a226cbbf2", "test2_souporcell_vs_bff_raw_intersect_assignment_after_match.csv:md5,9618d277186327f7092b4a838329c15c", "test2_souporcell_vs_demuxem_all_assignment_after_match.csv:md5,02807a959d832624e2869d6c885a3676", - "test2_souporcell_vs_demuxem_concordance_heatmap.png:md5,8de7171a8fc0ec2f38e6f45e6c28cb03", "test2_souporcell_vs_demuxem_correlation_res.csv:md5,24eca3fc8a0241e70b02055107683b91", "test2_souporcell_vs_demuxem_donor_match.csv:md5,d1feea9baad73b5bc106472a226cbbf2", "test2_souporcell_vs_demuxem_intersect_assignment_after_match.csv:md5,14280a2e649c89a8eb27f9c36798bc97", "test2_souporcell_vs_gmmdemux_all_assignment_after_match.csv:md5,e163bc80fc846e43cca5d6281dc2bbc8", - "test2_souporcell_vs_gmmdemux_concordance_heatmap.png:md5,d48df9ebb22dc1f2616511ad0405cdfd", "test2_souporcell_vs_gmmdemux_correlation_res.csv:md5,7e632ed518a1e6f02ed1ea25a28a4e45", "test2_souporcell_vs_gmmdemux_donor_match.csv:md5,d1feea9baad73b5bc106472a226cbbf2", "test2_souporcell_vs_gmmdemux_intersect_assignment_after_match.csv:md5,dd7403edeb5ba0e17da879a3ff310aba", "test2_souporcell_vs_hasheddrops_all_assignment_after_match.csv:md5,2177729941ec7e0ec3e30490367ac648", - "test2_souporcell_vs_hasheddrops_concordance_heatmap.png:md5,d48df9ebb22dc1f2616511ad0405cdfd", "test2_souporcell_vs_hasheddrops_correlation_res.csv:md5,7e632ed518a1e6f02ed1ea25a28a4e45", "test2_souporcell_vs_hasheddrops_donor_match.csv:md5,d1feea9baad73b5bc106472a226cbbf2", "test2_souporcell_vs_hasheddrops_intersect_assignment_after_match.csv:md5,595efa559856d1dfc7bae9d109478221", "test2_souporcell_vs_hashsolo_all_assignment_after_match.csv:md5,89a1b529a2f3805193a3547771df15bc", - "test2_souporcell_vs_hashsolo_concordance_heatmap.png:md5,aafe59185e6eef45a25585a8c06505e3", "test2_souporcell_vs_hashsolo_correlation_res.csv:md5,bdcc36b8fcabb311fa40d3d48e2b3080", "test2_souporcell_vs_hashsolo_donor_match.csv:md5,d1feea9baad73b5bc106472a226cbbf2", "test2_souporcell_vs_hashsolo_intersect_assignment_after_match.csv:md5,b9e34df8e9037c17fce729b661480b7f", "test2_souporcell_vs_htodemux_all_assignment_after_match.csv:md5,986fb63a696867e670b14a7d3c299eeb", - "test2_souporcell_vs_htodemux_concordance_heatmap.png:md5,d48df9ebb22dc1f2616511ad0405cdfd", "test2_souporcell_vs_htodemux_correlation_res.csv:md5,7e632ed518a1e6f02ed1ea25a28a4e45", "test2_souporcell_vs_htodemux_donor_match.csv:md5,d1feea9baad73b5bc106472a226cbbf2", "test2_souporcell_vs_htodemux_intersect_assignment_after_match.csv:md5,f9ac7bf74aea62b5cb9bd34f5fe002b2", "test2_souporcell_vs_multiseq_all_assignment_after_match.csv:md5,71a33f1f8070410b7c016bb7234a89e2", - "test2_souporcell_vs_multiseq_concordance_heatmap.png:md5,987ae361340b8b77daecc726ffa6b9de", "test2_souporcell_vs_multiseq_correlation_res.csv:md5,a5f9d7ebd675403b062cd0f4a59b1592", "test2_souporcell_vs_multiseq_donor_match.csv:md5,d1feea9baad73b5bc106472a226cbbf2", "test2_souporcell_vs_multiseq_intersect_assignment_after_match.csv:md5,a904d4236db50ef2d35fa4e0d608284f", @@ -946,82 +842,66 @@ "test2_best_intersect_assignment_after_match.csv:md5,d0069819b2b9722cc72ee2fae18e24d7", "test2_score_record.csv:md5,f85dc45d1e25a47aa1ce5df0f3652963", "test3_freemuxlet_vs_bff_consensuscall_all_assignment_after_match.csv:md5,de2093a5ff281af46d45d7d4770338cf", - "test3_freemuxlet_vs_bff_consensuscall_concordance_heatmap.png:md5,5c3e82573df24e781d4f422bc3b6925a", "test3_freemuxlet_vs_bff_consensuscall_correlation_res.csv:md5,5f2f2076175e313f10396b32fac4c26c", "test3_freemuxlet_vs_bff_consensuscall_donor_match.csv:md5,b92352374bf4b32f7e3a1b032a4c8b5f", "test3_freemuxlet_vs_bff_consensuscall_intersect_assignment_after_match.csv:md5,a4df295c2689a2b3abfab4476e5a1dcb", "test3_freemuxlet_vs_bff_raw_all_assignment_after_match.csv:md5,8c42806ae93d0bd0fc79864db6a79463", - "test3_freemuxlet_vs_bff_raw_concordance_heatmap.png:md5,5c3e82573df24e781d4f422bc3b6925a", "test3_freemuxlet_vs_bff_raw_correlation_res.csv:md5,5f2f2076175e313f10396b32fac4c26c", "test3_freemuxlet_vs_bff_raw_donor_match.csv:md5,b92352374bf4b32f7e3a1b032a4c8b5f", "test3_freemuxlet_vs_bff_raw_intersect_assignment_after_match.csv:md5,6af3427166aab49c0136369311533d83", "test3_freemuxlet_vs_demuxem_all_assignment_after_match.csv:md5,505b79ce4db1cef804bfeafb12f70a5d", - "test3_freemuxlet_vs_demuxem_concordance_heatmap.png:md5,360bc756be590ccb80d2add912d61e16", "test3_freemuxlet_vs_demuxem_correlation_res.csv:md5,c880595fe0a83d46f1bcbb361c994828", "test3_freemuxlet_vs_demuxem_donor_match.csv:md5,b92352374bf4b32f7e3a1b032a4c8b5f", "test3_freemuxlet_vs_demuxem_intersect_assignment_after_match.csv:md5,edf378d87391a486b6635713e04fdbd9", "test3_freemuxlet_vs_gmmdemux_all_assignment_after_match.csv:md5,473d87d9a65d8562e83d21067cfce536", - "test3_freemuxlet_vs_gmmdemux_concordance_heatmap.png:md5,5c3e82573df24e781d4f422bc3b6925a", "test3_freemuxlet_vs_gmmdemux_correlation_res.csv:md5,5f2f2076175e313f10396b32fac4c26c", "test3_freemuxlet_vs_gmmdemux_donor_match.csv:md5,b92352374bf4b32f7e3a1b032a4c8b5f", "test3_freemuxlet_vs_gmmdemux_intersect_assignment_after_match.csv:md5,d2f63d5a37e4a085fdd0ee97bfffe4f4", "test3_freemuxlet_vs_hasheddrops_all_assignment_after_match.csv:md5,fb66dca65ba0ddaec50b78f4be1a662a", - "test3_freemuxlet_vs_hasheddrops_concordance_heatmap.png:md5,5c3e82573df24e781d4f422bc3b6925a", "test3_freemuxlet_vs_hasheddrops_correlation_res.csv:md5,5f2f2076175e313f10396b32fac4c26c", "test3_freemuxlet_vs_hasheddrops_donor_match.csv:md5,b92352374bf4b32f7e3a1b032a4c8b5f", "test3_freemuxlet_vs_hasheddrops_intersect_assignment_after_match.csv:md5,b6e3daca5b12b963239eb9ff86ef7244", "test3_freemuxlet_vs_hashsolo_all_assignment_after_match.csv:md5,8dbc642b118d82fe0dbb24631126d93b", - "test3_freemuxlet_vs_hashsolo_concordance_heatmap.png:md5,a6dd712948a85cf2f1660b4f6a07a5cb", "test3_freemuxlet_vs_hashsolo_correlation_res.csv:md5,f6d61938702d82cca7a5ecbe4871a3fa", "test3_freemuxlet_vs_hashsolo_donor_match.csv:md5,b92352374bf4b32f7e3a1b032a4c8b5f", "test3_freemuxlet_vs_hashsolo_intersect_assignment_after_match.csv:md5,69a6c9f528d16e72d1a16927d65317fa", "test3_freemuxlet_vs_htodemux_all_assignment_after_match.csv:md5,c9413355e0d822c0436fecf1706d0cfc", - "test3_freemuxlet_vs_htodemux_concordance_heatmap.png:md5,42ab19f0e287585a39d52817f85943f0", "test3_freemuxlet_vs_htodemux_correlation_res.csv:md5,56e1f17bb5e0424e4840b2d89fe892d7", "test3_freemuxlet_vs_htodemux_donor_match.csv:md5,b92352374bf4b32f7e3a1b032a4c8b5f", "test3_freemuxlet_vs_htodemux_intersect_assignment_after_match.csv:md5,32cee037b091f9b1ca78ed88e11c75d4", "test3_freemuxlet_vs_multiseq_all_assignment_after_match.csv:md5,cb8a579a6233cf227e4eb987fed88dc0", - "test3_freemuxlet_vs_multiseq_concordance_heatmap.png:md5,64bc44fb9aeeb1581ebbf708c8765b4d", "test3_freemuxlet_vs_multiseq_correlation_res.csv:md5,8a690fde26636e0e4c44b41a156ae47a", "test3_freemuxlet_vs_multiseq_donor_match.csv:md5,b92352374bf4b32f7e3a1b032a4c8b5f", "test3_freemuxlet_vs_multiseq_intersect_assignment_after_match.csv:md5,bd1135d6b159a17c245742d6c4312219", "test3_souporcell_vs_bff_consensuscall_all_assignment_after_match.csv:md5,0523d43db3b0b43d3c06642887b1e681", - "test3_souporcell_vs_bff_consensuscall_concordance_heatmap.png:md5,d48df9ebb22dc1f2616511ad0405cdfd", "test3_souporcell_vs_bff_consensuscall_correlation_res.csv:md5,7e632ed518a1e6f02ed1ea25a28a4e45", "test3_souporcell_vs_bff_consensuscall_donor_match.csv:md5,d1feea9baad73b5bc106472a226cbbf2", "test3_souporcell_vs_bff_consensuscall_intersect_assignment_after_match.csv:md5,d0069819b2b9722cc72ee2fae18e24d7", "test3_souporcell_vs_bff_raw_all_assignment_after_match.csv:md5,3693453872310717f649a8da3538a8b2", - "test3_souporcell_vs_bff_raw_concordance_heatmap.png:md5,d48df9ebb22dc1f2616511ad0405cdfd", "test3_souporcell_vs_bff_raw_correlation_res.csv:md5,7e632ed518a1e6f02ed1ea25a28a4e45", "test3_souporcell_vs_bff_raw_donor_match.csv:md5,d1feea9baad73b5bc106472a226cbbf2", "test3_souporcell_vs_bff_raw_intersect_assignment_after_match.csv:md5,9618d277186327f7092b4a838329c15c", "test3_souporcell_vs_demuxem_all_assignment_after_match.csv:md5,02807a959d832624e2869d6c885a3676", - "test3_souporcell_vs_demuxem_concordance_heatmap.png:md5,8de7171a8fc0ec2f38e6f45e6c28cb03", "test3_souporcell_vs_demuxem_correlation_res.csv:md5,24eca3fc8a0241e70b02055107683b91", "test3_souporcell_vs_demuxem_donor_match.csv:md5,d1feea9baad73b5bc106472a226cbbf2", "test3_souporcell_vs_demuxem_intersect_assignment_after_match.csv:md5,14280a2e649c89a8eb27f9c36798bc97", "test3_souporcell_vs_gmmdemux_all_assignment_after_match.csv:md5,e163bc80fc846e43cca5d6281dc2bbc8", - "test3_souporcell_vs_gmmdemux_concordance_heatmap.png:md5,d48df9ebb22dc1f2616511ad0405cdfd", "test3_souporcell_vs_gmmdemux_correlation_res.csv:md5,7e632ed518a1e6f02ed1ea25a28a4e45", "test3_souporcell_vs_gmmdemux_donor_match.csv:md5,d1feea9baad73b5bc106472a226cbbf2", "test3_souporcell_vs_gmmdemux_intersect_assignment_after_match.csv:md5,dd7403edeb5ba0e17da879a3ff310aba", "test3_souporcell_vs_hasheddrops_all_assignment_after_match.csv:md5,2177729941ec7e0ec3e30490367ac648", - "test3_souporcell_vs_hasheddrops_concordance_heatmap.png:md5,d48df9ebb22dc1f2616511ad0405cdfd", "test3_souporcell_vs_hasheddrops_correlation_res.csv:md5,7e632ed518a1e6f02ed1ea25a28a4e45", "test3_souporcell_vs_hasheddrops_donor_match.csv:md5,d1feea9baad73b5bc106472a226cbbf2", "test3_souporcell_vs_hasheddrops_intersect_assignment_after_match.csv:md5,595efa559856d1dfc7bae9d109478221", "test3_souporcell_vs_hashsolo_all_assignment_after_match.csv:md5,89a1b529a2f3805193a3547771df15bc", - "test3_souporcell_vs_hashsolo_concordance_heatmap.png:md5,aafe59185e6eef45a25585a8c06505e3", "test3_souporcell_vs_hashsolo_correlation_res.csv:md5,bdcc36b8fcabb311fa40d3d48e2b3080", "test3_souporcell_vs_hashsolo_donor_match.csv:md5,d1feea9baad73b5bc106472a226cbbf2", "test3_souporcell_vs_hashsolo_intersect_assignment_after_match.csv:md5,b9e34df8e9037c17fce729b661480b7f", "test3_souporcell_vs_htodemux_all_assignment_after_match.csv:md5,986fb63a696867e670b14a7d3c299eeb", - "test3_souporcell_vs_htodemux_concordance_heatmap.png:md5,d48df9ebb22dc1f2616511ad0405cdfd", "test3_souporcell_vs_htodemux_correlation_res.csv:md5,7e632ed518a1e6f02ed1ea25a28a4e45", "test3_souporcell_vs_htodemux_donor_match.csv:md5,d1feea9baad73b5bc106472a226cbbf2", "test3_souporcell_vs_htodemux_intersect_assignment_after_match.csv:md5,f9ac7bf74aea62b5cb9bd34f5fe002b2", "test3_souporcell_vs_multiseq_all_assignment_after_match.csv:md5,71a33f1f8070410b7c016bb7234a89e2", - "test3_souporcell_vs_multiseq_concordance_heatmap.png:md5,987ae361340b8b77daecc726ffa6b9de", "test3_souporcell_vs_multiseq_correlation_res.csv:md5,a5f9d7ebd675403b062cd0f4a59b1592", "test3_souporcell_vs_multiseq_donor_match.csv:md5,d1feea9baad73b5bc106472a226cbbf2", "test3_souporcell_vs_multiseq_intersect_assignment_after_match.csv:md5,a904d4236db50ef2d35fa4e0d608284f", @@ -1037,7 +917,6 @@ "test1_hto-2_unmatched_gt.csv:md5,56872af37baffd1e4689c20f92e4d1c0", "test1_all_representative_variants.csv:md5,93cf3fde5b1236b83abd53369b250f64", "test1_donor_specific_variants.csv:md5,bd137190cb87b1ad68a7283571e9a4b6", - "test1_donor_specific_variants_upset.png:md5,5d0abe01aa07abd018d20abe8e2e99ab", "test1_vireo_variants.csv:md5,34bf574b4fe7c16db53a86f5cf6cdbf3", "test2_hto-1_informative_variants.csv:md5,7ba6e70ac79adf74d2aa85f723b9f0d6", "test2_hto-1_matched_gt.csv:md5,bf3b7d684f30153fa603dbddde635583", @@ -1047,7 +926,6 @@ "test2_hto-2_unmatched_gt.csv:md5,56872af37baffd1e4689c20f92e4d1c0", "test2_all_representative_variants.csv:md5,93cf3fde5b1236b83abd53369b250f64", "test2_donor_specific_variants.csv:md5,bd137190cb87b1ad68a7283571e9a4b6", - "test2_donor_specific_variants_upset.png:md5,5d0abe01aa07abd018d20abe8e2e99ab", "test2_vireo_variants.csv:md5,34bf574b4fe7c16db53a86f5cf6cdbf3", "test3_hto-1_informative_variants.csv:md5,7ba6e70ac79adf74d2aa85f723b9f0d6", "test3_hto-1_matched_gt.csv:md5,bf3b7d684f30153fa603dbddde635583", @@ -1057,7 +935,6 @@ "test3_hto-2_unmatched_gt.csv:md5,56872af37baffd1e4689c20f92e4d1c0", "test3_all_representative_variants.csv:md5,93cf3fde5b1236b83abd53369b250f64", "test3_donor_specific_variants.csv:md5,bd137190cb87b1ad68a7283571e9a4b6", - "test3_donor_specific_variants_upset.png:md5,5d0abe01aa07abd018d20abe8e2e99ab", "test3_vireo_variants.csv:md5,34bf574b4fe7c16db53a86f5cf6cdbf3", "test1.best:md5,237dadb1f53067fc7e3aad4336c6541b", "test2.best:md5,237dadb1f53067fc7e3aad4336c6541b", @@ -1141,28 +1018,22 @@ "features.tsv.gz:md5,441a968b2c7ee68eae8388b04bee328d", "matrix.mtx.gz:md5,6f855b8bf68573d2e09139c29f9a2d41", "test1_emptyDrops.csv:md5,a227d42afbcc590b4e949075cde4a5b6", - "test1_emptyDrops.png:md5,2d7113a76a494b2b2c18b77c4a664063", "test1_emptyDrops.rds:md5,bda2e88b1e3e8c8ea5a22e19e3ab243d", "test1_hasheddrops.rds:md5,53424c915d3926e46180be1408d28311", "test1_id_to_hash.csv:md5,e8f577103f3e0c17604f89c3bcda2ffb", "test1_params_hasheddrops.csv:md5,b253501914df23ef5901262e8a241a8d", - "test1_plot_hasheddrops.png:md5,2d7113a76a494b2b2c18b77c4a664063", "test1_results_hasheddrops.csv:md5,230bcd615d3ac3d7ae8eb37ebe67f016", "test2_emptyDrops.csv:md5,a227d42afbcc590b4e949075cde4a5b6", - "test2_emptyDrops.png:md5,2d7113a76a494b2b2c18b77c4a664063", "test2_emptyDrops.rds:md5,bda2e88b1e3e8c8ea5a22e19e3ab243d", "test2_hasheddrops.rds:md5,53424c915d3926e46180be1408d28311", "test2_id_to_hash.csv:md5,e8f577103f3e0c17604f89c3bcda2ffb", "test2_params_hasheddrops.csv:md5,3612264937e5c5962fefddc43afb595f", - "test2_plot_hasheddrops.png:md5,2d7113a76a494b2b2c18b77c4a664063", "test2_results_hasheddrops.csv:md5,230bcd615d3ac3d7ae8eb37ebe67f016", "test3_emptyDrops.csv:md5,a227d42afbcc590b4e949075cde4a5b6", - "test3_emptyDrops.png:md5,2d7113a76a494b2b2c18b77c4a664063", "test3_emptyDrops.rds:md5,bda2e88b1e3e8c8ea5a22e19e3ab243d", "test3_hasheddrops.rds:md5,53424c915d3926e46180be1408d28311", "test3_id_to_hash.csv:md5,e8f577103f3e0c17604f89c3bcda2ffb", "test3_params_hasheddrops.csv:md5,b15e94f887e3ada8f790ea18eb791d8b", - "test3_plot_hasheddrops.png:md5,2d7113a76a494b2b2c18b77c4a664063", "test3_results_hasheddrops.csv:md5,230bcd615d3ac3d7ae8eb37ebe67f016", "test1_assignment_hashsolo.csv:md5,7763ee9e2ef20c353ab386f55f805b33", "test1_hashsolo.h5ad:md5,41ba3e91669bb4d52a7eb214186d36ea", @@ -1176,26 +1047,14 @@ "test1_assignment_htodemux.csv:md5,7217cd9d30a3c6f7f69e8b4f8aaa679d", "test1_classification_htodemux.csv:md5,e5b0d2af4de9bdc6682c120a78e53aa0", "test1_params_htodemux.csv:md5,08b6c065b0c383e6be3c20787ec01d3e", - "test1_featureScatter_htodemux.jpeg:md5,c67268ac0ff18c485899b4ee58f8f948", - "test1_heatMap_htodemux.jpeg:md5,2ebdb027344db91ae89deaa3d1011993", - "test1_ridge_htodemux.jpeg:md5,8bbc209b475b437a80f853277daa4ce9", - "test1_violinPlot_htodemux.jpeg:md5,ef866e13e18a31ab43370299245d33c7", "test1_visual_params_htodemux.csv:md5,0a175b9868be2b4447867ab38705c11d", "test2_assignment_htodemux.csv:md5,7217cd9d30a3c6f7f69e8b4f8aaa679d", "test2_classification_htodemux.csv:md5,e5b0d2af4de9bdc6682c120a78e53aa0", "test2_params_htodemux.csv:md5,484909d87cacd9c2b9cf60a3c853a5fd", - "test2_featureScatter_htodemux.jpeg:md5,c67268ac0ff18c485899b4ee58f8f948", - "test2_heatMap_htodemux.jpeg:md5,2ebdb027344db91ae89deaa3d1011993", - "test2_ridge_htodemux.jpeg:md5,8bbc209b475b437a80f853277daa4ce9", - "test2_violinPlot_htodemux.jpeg:md5,ef866e13e18a31ab43370299245d33c7", "test2_visual_params_htodemux.csv:md5,062e94620e3207810ddf2c65bf0d2914", "test3_assignment_htodemux.csv:md5,7217cd9d30a3c6f7f69e8b4f8aaa679d", "test3_classification_htodemux.csv:md5,e5b0d2af4de9bdc6682c120a78e53aa0", "test3_params_htodemux.csv:md5,a0a3f900a632eea39057b038438a89b9", - "test3_featureScatter_htodemux.jpeg:md5,c67268ac0ff18c485899b4ee58f8f948", - "test3_heatMap_htodemux.jpeg:md5,2ebdb027344db91ae89deaa3d1011993", - "test3_ridge_htodemux.jpeg:md5,8bbc209b475b437a80f853277daa4ce9", - "test3_violinPlot_htodemux.jpeg:md5,ef866e13e18a31ab43370299245d33c7", "test3_visual_params_htodemux.csv:md5,b6f8f5593aeb86d846292f2f23fc518d", "test1_params_multiseqdemux.csv:md5,60a096f79d78655870aef2af9130cf9d", "test1_res_multiseqdemux.csv:md5,1173b7b90f66f0eb41d516a119ba52ee", diff --git a/tests/test_donor_match.nf.test.snap b/tests/test_donor_match.nf.test.snap index 72b3fd94..d3eda9ff 100644 --- a/tests/test_donor_match.nf.test.snap +++ b/tests/test_donor_match.nf.test.snap @@ -27,49 +27,41 @@ "donor_match/test1", "donor_match/test1/freemuxlet_vs_bff_consensuscall", "donor_match/test1/freemuxlet_vs_bff_consensuscall/test1_freemuxlet_vs_bff_consensuscall_all_assignment_after_match.csv", - "donor_match/test1/freemuxlet_vs_bff_consensuscall/test1_freemuxlet_vs_bff_consensuscall_concordance_heatmap.png", "donor_match/test1/freemuxlet_vs_bff_consensuscall/test1_freemuxlet_vs_bff_consensuscall_correlation_res.csv", "donor_match/test1/freemuxlet_vs_bff_consensuscall/test1_freemuxlet_vs_bff_consensuscall_donor_match.csv", "donor_match/test1/freemuxlet_vs_bff_consensuscall/test1_freemuxlet_vs_bff_consensuscall_intersect_assignment_after_match.csv", "donor_match/test1/freemuxlet_vs_bff_raw", "donor_match/test1/freemuxlet_vs_bff_raw/test1_freemuxlet_vs_bff_raw_all_assignment_after_match.csv", - "donor_match/test1/freemuxlet_vs_bff_raw/test1_freemuxlet_vs_bff_raw_concordance_heatmap.png", "donor_match/test1/freemuxlet_vs_bff_raw/test1_freemuxlet_vs_bff_raw_correlation_res.csv", "donor_match/test1/freemuxlet_vs_bff_raw/test1_freemuxlet_vs_bff_raw_donor_match.csv", "donor_match/test1/freemuxlet_vs_bff_raw/test1_freemuxlet_vs_bff_raw_intersect_assignment_after_match.csv", "donor_match/test1/freemuxlet_vs_gmmdemux", "donor_match/test1/freemuxlet_vs_gmmdemux/test1_freemuxlet_vs_gmmdemux_all_assignment_after_match.csv", - "donor_match/test1/freemuxlet_vs_gmmdemux/test1_freemuxlet_vs_gmmdemux_concordance_heatmap.png", "donor_match/test1/freemuxlet_vs_gmmdemux/test1_freemuxlet_vs_gmmdemux_correlation_res.csv", "donor_match/test1/freemuxlet_vs_gmmdemux/test1_freemuxlet_vs_gmmdemux_donor_match.csv", "donor_match/test1/freemuxlet_vs_gmmdemux/test1_freemuxlet_vs_gmmdemux_intersect_assignment_after_match.csv", "donor_match/test1/freemuxlet_vs_hasheddrops", "donor_match/test1/freemuxlet_vs_hasheddrops/test1_freemuxlet_vs_hasheddrops_all_assignment_after_match.csv", - "donor_match/test1/freemuxlet_vs_hasheddrops/test1_freemuxlet_vs_hasheddrops_concordance_heatmap.png", "donor_match/test1/freemuxlet_vs_hasheddrops/test1_freemuxlet_vs_hasheddrops_correlation_res.csv", "donor_match/test1/freemuxlet_vs_hasheddrops/test1_freemuxlet_vs_hasheddrops_donor_match.csv", "donor_match/test1/freemuxlet_vs_hasheddrops/test1_freemuxlet_vs_hasheddrops_intersect_assignment_after_match.csv", "donor_match/test1/souporcell_vs_bff_consensuscall", "donor_match/test1/souporcell_vs_bff_consensuscall/test1_souporcell_vs_bff_consensuscall_all_assignment_after_match.csv", - "donor_match/test1/souporcell_vs_bff_consensuscall/test1_souporcell_vs_bff_consensuscall_concordance_heatmap.png", "donor_match/test1/souporcell_vs_bff_consensuscall/test1_souporcell_vs_bff_consensuscall_correlation_res.csv", "donor_match/test1/souporcell_vs_bff_consensuscall/test1_souporcell_vs_bff_consensuscall_donor_match.csv", "donor_match/test1/souporcell_vs_bff_consensuscall/test1_souporcell_vs_bff_consensuscall_intersect_assignment_after_match.csv", "donor_match/test1/souporcell_vs_bff_raw", "donor_match/test1/souporcell_vs_bff_raw/test1_souporcell_vs_bff_raw_all_assignment_after_match.csv", - "donor_match/test1/souporcell_vs_bff_raw/test1_souporcell_vs_bff_raw_concordance_heatmap.png", "donor_match/test1/souporcell_vs_bff_raw/test1_souporcell_vs_bff_raw_correlation_res.csv", "donor_match/test1/souporcell_vs_bff_raw/test1_souporcell_vs_bff_raw_donor_match.csv", "donor_match/test1/souporcell_vs_bff_raw/test1_souporcell_vs_bff_raw_intersect_assignment_after_match.csv", "donor_match/test1/souporcell_vs_gmmdemux", "donor_match/test1/souporcell_vs_gmmdemux/test1_souporcell_vs_gmmdemux_all_assignment_after_match.csv", - "donor_match/test1/souporcell_vs_gmmdemux/test1_souporcell_vs_gmmdemux_concordance_heatmap.png", "donor_match/test1/souporcell_vs_gmmdemux/test1_souporcell_vs_gmmdemux_correlation_res.csv", "donor_match/test1/souporcell_vs_gmmdemux/test1_souporcell_vs_gmmdemux_donor_match.csv", "donor_match/test1/souporcell_vs_gmmdemux/test1_souporcell_vs_gmmdemux_intersect_assignment_after_match.csv", "donor_match/test1/souporcell_vs_hasheddrops", "donor_match/test1/souporcell_vs_hasheddrops/test1_souporcell_vs_hasheddrops_all_assignment_after_match.csv", - "donor_match/test1/souporcell_vs_hasheddrops/test1_souporcell_vs_hasheddrops_concordance_heatmap.png", "donor_match/test1/souporcell_vs_hasheddrops/test1_souporcell_vs_hasheddrops_correlation_res.csv", "donor_match/test1/souporcell_vs_hasheddrops/test1_souporcell_vs_hasheddrops_donor_match.csv", "donor_match/test1/souporcell_vs_hasheddrops/test1_souporcell_vs_hasheddrops_intersect_assignment_after_match.csv", @@ -80,49 +72,41 @@ "donor_match/test2", "donor_match/test2/freemuxlet_vs_bff_consensuscall", "donor_match/test2/freemuxlet_vs_bff_consensuscall/test2_freemuxlet_vs_bff_consensuscall_all_assignment_after_match.csv", - "donor_match/test2/freemuxlet_vs_bff_consensuscall/test2_freemuxlet_vs_bff_consensuscall_concordance_heatmap.png", "donor_match/test2/freemuxlet_vs_bff_consensuscall/test2_freemuxlet_vs_bff_consensuscall_correlation_res.csv", "donor_match/test2/freemuxlet_vs_bff_consensuscall/test2_freemuxlet_vs_bff_consensuscall_donor_match.csv", "donor_match/test2/freemuxlet_vs_bff_consensuscall/test2_freemuxlet_vs_bff_consensuscall_intersect_assignment_after_match.csv", "donor_match/test2/freemuxlet_vs_bff_raw", "donor_match/test2/freemuxlet_vs_bff_raw/test2_freemuxlet_vs_bff_raw_all_assignment_after_match.csv", - "donor_match/test2/freemuxlet_vs_bff_raw/test2_freemuxlet_vs_bff_raw_concordance_heatmap.png", "donor_match/test2/freemuxlet_vs_bff_raw/test2_freemuxlet_vs_bff_raw_correlation_res.csv", "donor_match/test2/freemuxlet_vs_bff_raw/test2_freemuxlet_vs_bff_raw_donor_match.csv", "donor_match/test2/freemuxlet_vs_bff_raw/test2_freemuxlet_vs_bff_raw_intersect_assignment_after_match.csv", "donor_match/test2/freemuxlet_vs_gmmdemux", "donor_match/test2/freemuxlet_vs_gmmdemux/test2_freemuxlet_vs_gmmdemux_all_assignment_after_match.csv", - "donor_match/test2/freemuxlet_vs_gmmdemux/test2_freemuxlet_vs_gmmdemux_concordance_heatmap.png", "donor_match/test2/freemuxlet_vs_gmmdemux/test2_freemuxlet_vs_gmmdemux_correlation_res.csv", "donor_match/test2/freemuxlet_vs_gmmdemux/test2_freemuxlet_vs_gmmdemux_donor_match.csv", "donor_match/test2/freemuxlet_vs_gmmdemux/test2_freemuxlet_vs_gmmdemux_intersect_assignment_after_match.csv", "donor_match/test2/freemuxlet_vs_hasheddrops", "donor_match/test2/freemuxlet_vs_hasheddrops/test2_freemuxlet_vs_hasheddrops_all_assignment_after_match.csv", - "donor_match/test2/freemuxlet_vs_hasheddrops/test2_freemuxlet_vs_hasheddrops_concordance_heatmap.png", "donor_match/test2/freemuxlet_vs_hasheddrops/test2_freemuxlet_vs_hasheddrops_correlation_res.csv", "donor_match/test2/freemuxlet_vs_hasheddrops/test2_freemuxlet_vs_hasheddrops_donor_match.csv", "donor_match/test2/freemuxlet_vs_hasheddrops/test2_freemuxlet_vs_hasheddrops_intersect_assignment_after_match.csv", "donor_match/test2/souporcell_vs_bff_consensuscall", "donor_match/test2/souporcell_vs_bff_consensuscall/test2_souporcell_vs_bff_consensuscall_all_assignment_after_match.csv", - "donor_match/test2/souporcell_vs_bff_consensuscall/test2_souporcell_vs_bff_consensuscall_concordance_heatmap.png", "donor_match/test2/souporcell_vs_bff_consensuscall/test2_souporcell_vs_bff_consensuscall_correlation_res.csv", "donor_match/test2/souporcell_vs_bff_consensuscall/test2_souporcell_vs_bff_consensuscall_donor_match.csv", "donor_match/test2/souporcell_vs_bff_consensuscall/test2_souporcell_vs_bff_consensuscall_intersect_assignment_after_match.csv", "donor_match/test2/souporcell_vs_bff_raw", "donor_match/test2/souporcell_vs_bff_raw/test2_souporcell_vs_bff_raw_all_assignment_after_match.csv", - "donor_match/test2/souporcell_vs_bff_raw/test2_souporcell_vs_bff_raw_concordance_heatmap.png", "donor_match/test2/souporcell_vs_bff_raw/test2_souporcell_vs_bff_raw_correlation_res.csv", "donor_match/test2/souporcell_vs_bff_raw/test2_souporcell_vs_bff_raw_donor_match.csv", "donor_match/test2/souporcell_vs_bff_raw/test2_souporcell_vs_bff_raw_intersect_assignment_after_match.csv", "donor_match/test2/souporcell_vs_gmmdemux", "donor_match/test2/souporcell_vs_gmmdemux/test2_souporcell_vs_gmmdemux_all_assignment_after_match.csv", - "donor_match/test2/souporcell_vs_gmmdemux/test2_souporcell_vs_gmmdemux_concordance_heatmap.png", "donor_match/test2/souporcell_vs_gmmdemux/test2_souporcell_vs_gmmdemux_correlation_res.csv", "donor_match/test2/souporcell_vs_gmmdemux/test2_souporcell_vs_gmmdemux_donor_match.csv", "donor_match/test2/souporcell_vs_gmmdemux/test2_souporcell_vs_gmmdemux_intersect_assignment_after_match.csv", "donor_match/test2/souporcell_vs_hasheddrops", "donor_match/test2/souporcell_vs_hasheddrops/test2_souporcell_vs_hasheddrops_all_assignment_after_match.csv", - "donor_match/test2/souporcell_vs_hasheddrops/test2_souporcell_vs_hasheddrops_concordance_heatmap.png", "donor_match/test2/souporcell_vs_hasheddrops/test2_souporcell_vs_hasheddrops_correlation_res.csv", "donor_match/test2/souporcell_vs_hasheddrops/test2_souporcell_vs_hasheddrops_donor_match.csv", "donor_match/test2/souporcell_vs_hasheddrops/test2_souporcell_vs_hasheddrops_intersect_assignment_after_match.csv", @@ -133,49 +117,41 @@ "donor_match/test3", "donor_match/test3/freemuxlet_vs_bff_consensuscall", "donor_match/test3/freemuxlet_vs_bff_consensuscall/test3_freemuxlet_vs_bff_consensuscall_all_assignment_after_match.csv", - "donor_match/test3/freemuxlet_vs_bff_consensuscall/test3_freemuxlet_vs_bff_consensuscall_concordance_heatmap.png", "donor_match/test3/freemuxlet_vs_bff_consensuscall/test3_freemuxlet_vs_bff_consensuscall_correlation_res.csv", "donor_match/test3/freemuxlet_vs_bff_consensuscall/test3_freemuxlet_vs_bff_consensuscall_donor_match.csv", "donor_match/test3/freemuxlet_vs_bff_consensuscall/test3_freemuxlet_vs_bff_consensuscall_intersect_assignment_after_match.csv", "donor_match/test3/freemuxlet_vs_bff_raw", "donor_match/test3/freemuxlet_vs_bff_raw/test3_freemuxlet_vs_bff_raw_all_assignment_after_match.csv", - "donor_match/test3/freemuxlet_vs_bff_raw/test3_freemuxlet_vs_bff_raw_concordance_heatmap.png", "donor_match/test3/freemuxlet_vs_bff_raw/test3_freemuxlet_vs_bff_raw_correlation_res.csv", "donor_match/test3/freemuxlet_vs_bff_raw/test3_freemuxlet_vs_bff_raw_donor_match.csv", "donor_match/test3/freemuxlet_vs_bff_raw/test3_freemuxlet_vs_bff_raw_intersect_assignment_after_match.csv", "donor_match/test3/freemuxlet_vs_gmmdemux", "donor_match/test3/freemuxlet_vs_gmmdemux/test3_freemuxlet_vs_gmmdemux_all_assignment_after_match.csv", - "donor_match/test3/freemuxlet_vs_gmmdemux/test3_freemuxlet_vs_gmmdemux_concordance_heatmap.png", "donor_match/test3/freemuxlet_vs_gmmdemux/test3_freemuxlet_vs_gmmdemux_correlation_res.csv", "donor_match/test3/freemuxlet_vs_gmmdemux/test3_freemuxlet_vs_gmmdemux_donor_match.csv", "donor_match/test3/freemuxlet_vs_gmmdemux/test3_freemuxlet_vs_gmmdemux_intersect_assignment_after_match.csv", "donor_match/test3/freemuxlet_vs_hasheddrops", "donor_match/test3/freemuxlet_vs_hasheddrops/test3_freemuxlet_vs_hasheddrops_all_assignment_after_match.csv", - "donor_match/test3/freemuxlet_vs_hasheddrops/test3_freemuxlet_vs_hasheddrops_concordance_heatmap.png", "donor_match/test3/freemuxlet_vs_hasheddrops/test3_freemuxlet_vs_hasheddrops_correlation_res.csv", "donor_match/test3/freemuxlet_vs_hasheddrops/test3_freemuxlet_vs_hasheddrops_donor_match.csv", "donor_match/test3/freemuxlet_vs_hasheddrops/test3_freemuxlet_vs_hasheddrops_intersect_assignment_after_match.csv", "donor_match/test3/souporcell_vs_bff_consensuscall", "donor_match/test3/souporcell_vs_bff_consensuscall/test3_souporcell_vs_bff_consensuscall_all_assignment_after_match.csv", - "donor_match/test3/souporcell_vs_bff_consensuscall/test3_souporcell_vs_bff_consensuscall_concordance_heatmap.png", "donor_match/test3/souporcell_vs_bff_consensuscall/test3_souporcell_vs_bff_consensuscall_correlation_res.csv", "donor_match/test3/souporcell_vs_bff_consensuscall/test3_souporcell_vs_bff_consensuscall_donor_match.csv", "donor_match/test3/souporcell_vs_bff_consensuscall/test3_souporcell_vs_bff_consensuscall_intersect_assignment_after_match.csv", "donor_match/test3/souporcell_vs_bff_raw", "donor_match/test3/souporcell_vs_bff_raw/test3_souporcell_vs_bff_raw_all_assignment_after_match.csv", - "donor_match/test3/souporcell_vs_bff_raw/test3_souporcell_vs_bff_raw_concordance_heatmap.png", "donor_match/test3/souporcell_vs_bff_raw/test3_souporcell_vs_bff_raw_correlation_res.csv", "donor_match/test3/souporcell_vs_bff_raw/test3_souporcell_vs_bff_raw_donor_match.csv", "donor_match/test3/souporcell_vs_bff_raw/test3_souporcell_vs_bff_raw_intersect_assignment_after_match.csv", "donor_match/test3/souporcell_vs_gmmdemux", "donor_match/test3/souporcell_vs_gmmdemux/test3_souporcell_vs_gmmdemux_all_assignment_after_match.csv", - "donor_match/test3/souporcell_vs_gmmdemux/test3_souporcell_vs_gmmdemux_concordance_heatmap.png", "donor_match/test3/souporcell_vs_gmmdemux/test3_souporcell_vs_gmmdemux_correlation_res.csv", "donor_match/test3/souporcell_vs_gmmdemux/test3_souporcell_vs_gmmdemux_donor_match.csv", "donor_match/test3/souporcell_vs_gmmdemux/test3_souporcell_vs_gmmdemux_intersect_assignment_after_match.csv", "donor_match/test3/souporcell_vs_hasheddrops", "donor_match/test3/souporcell_vs_hasheddrops/test3_souporcell_vs_hasheddrops_all_assignment_after_match.csv", - "donor_match/test3/souporcell_vs_hasheddrops/test3_souporcell_vs_hasheddrops_concordance_heatmap.png", "donor_match/test3/souporcell_vs_hasheddrops/test3_souporcell_vs_hasheddrops_correlation_res.csv", "donor_match/test3/souporcell_vs_hasheddrops/test3_souporcell_vs_hasheddrops_donor_match.csv", "donor_match/test3/souporcell_vs_hasheddrops/test3_souporcell_vs_hasheddrops_intersect_assignment_after_match.csv", @@ -198,7 +174,6 @@ "find_variants/test1/subset_gt_donors/test1_vireo.vcf.gz", "find_variants/test1/test1_all_representative_variants.csv", "find_variants/test1/test1_donor_specific_variants.csv", - "find_variants/test1/test1_donor_specific_variants_upset.png", "find_variants/test1/test1_vireo_variants.csv", "find_variants/test2", "find_variants/test2/hto-1", @@ -214,7 +189,6 @@ "find_variants/test2/subset_gt_donors/test2_vireo.vcf.gz", "find_variants/test2/test2_all_representative_variants.csv", "find_variants/test2/test2_donor_specific_variants.csv", - "find_variants/test2/test2_donor_specific_variants_upset.png", "find_variants/test2/test2_vireo_variants.csv", "find_variants/test3", "find_variants/test3/hto-1", @@ -230,7 +204,6 @@ "find_variants/test3/subset_gt_donors/test3_vireo.vcf.gz", "find_variants/test3/test3_all_representative_variants.csv", "find_variants/test3/test3_donor_specific_variants.csv", - "find_variants/test3/test3_donor_specific_variants_upset.png", "find_variants/test3/test3_vireo_variants.csv", "multiqc", "multiqc/multiqc_data", @@ -247,42 +220,34 @@ ], [ "test1_freemuxlet_vs_bff_consensuscall_all_assignment_after_match.csv:md5,c5552086e271152756067aa99ab77fa1", - "test1_freemuxlet_vs_bff_consensuscall_concordance_heatmap.png:md5,5c3e82573df24e781d4f422bc3b6925a", "test1_freemuxlet_vs_bff_consensuscall_correlation_res.csv:md5,5f2f2076175e313f10396b32fac4c26c", "test1_freemuxlet_vs_bff_consensuscall_donor_match.csv:md5,b92352374bf4b32f7e3a1b032a4c8b5f", "test1_freemuxlet_vs_bff_consensuscall_intersect_assignment_after_match.csv:md5,7827c80fbdb30275350dfea4e6ee3a46", "test1_freemuxlet_vs_bff_raw_all_assignment_after_match.csv:md5,f64080fd544ffd711634e06719c2a574", - "test1_freemuxlet_vs_bff_raw_concordance_heatmap.png:md5,5c3e82573df24e781d4f422bc3b6925a", "test1_freemuxlet_vs_bff_raw_correlation_res.csv:md5,5f2f2076175e313f10396b32fac4c26c", "test1_freemuxlet_vs_bff_raw_donor_match.csv:md5,b92352374bf4b32f7e3a1b032a4c8b5f", "test1_freemuxlet_vs_bff_raw_intersect_assignment_after_match.csv:md5,51b6c078396c57902e610e8497b52ec6", "test1_freemuxlet_vs_gmmdemux_all_assignment_after_match.csv:md5,843151ffa9d30820ade39c0fbe3a5150", - "test1_freemuxlet_vs_gmmdemux_concordance_heatmap.png:md5,5c3e82573df24e781d4f422bc3b6925a", "test1_freemuxlet_vs_gmmdemux_correlation_res.csv:md5,5f2f2076175e313f10396b32fac4c26c", "test1_freemuxlet_vs_gmmdemux_donor_match.csv:md5,b92352374bf4b32f7e3a1b032a4c8b5f", "test1_freemuxlet_vs_gmmdemux_intersect_assignment_after_match.csv:md5,e55c2902ef5d84a00e0df8c814d0b572", "test1_freemuxlet_vs_hasheddrops_all_assignment_after_match.csv:md5,52d09d2ed176e113c88b8325edd9cb79", - "test1_freemuxlet_vs_hasheddrops_concordance_heatmap.png:md5,5c3e82573df24e781d4f422bc3b6925a", "test1_freemuxlet_vs_hasheddrops_correlation_res.csv:md5,5f2f2076175e313f10396b32fac4c26c", "test1_freemuxlet_vs_hasheddrops_donor_match.csv:md5,b92352374bf4b32f7e3a1b032a4c8b5f", "test1_freemuxlet_vs_hasheddrops_intersect_assignment_after_match.csv:md5,78e11f774a11260199c5262b551c4618", "test1_souporcell_vs_bff_consensuscall_all_assignment_after_match.csv:md5,0453e72b042219598602022c78f500b0", - "test1_souporcell_vs_bff_consensuscall_concordance_heatmap.png:md5,d48df9ebb22dc1f2616511ad0405cdfd", "test1_souporcell_vs_bff_consensuscall_correlation_res.csv:md5,7e632ed518a1e6f02ed1ea25a28a4e45", "test1_souporcell_vs_bff_consensuscall_donor_match.csv:md5,d1feea9baad73b5bc106472a226cbbf2", "test1_souporcell_vs_bff_consensuscall_intersect_assignment_after_match.csv:md5,2f98b7e021001d4093324dad82177d9d", "test1_souporcell_vs_bff_raw_all_assignment_after_match.csv:md5,9667ceb1476d7ef9155042a2bfea590a", - "test1_souporcell_vs_bff_raw_concordance_heatmap.png:md5,d48df9ebb22dc1f2616511ad0405cdfd", "test1_souporcell_vs_bff_raw_correlation_res.csv:md5,7e632ed518a1e6f02ed1ea25a28a4e45", "test1_souporcell_vs_bff_raw_donor_match.csv:md5,d1feea9baad73b5bc106472a226cbbf2", "test1_souporcell_vs_bff_raw_intersect_assignment_after_match.csv:md5,245e79ea38948063f671d8553afd2d55", "test1_souporcell_vs_gmmdemux_all_assignment_after_match.csv:md5,ab32c63ef3a5e5258b399aa2a193ffe4", - "test1_souporcell_vs_gmmdemux_concordance_heatmap.png:md5,d48df9ebb22dc1f2616511ad0405cdfd", "test1_souporcell_vs_gmmdemux_correlation_res.csv:md5,7e632ed518a1e6f02ed1ea25a28a4e45", "test1_souporcell_vs_gmmdemux_donor_match.csv:md5,d1feea9baad73b5bc106472a226cbbf2", "test1_souporcell_vs_gmmdemux_intersect_assignment_after_match.csv:md5,e10f4fc3d56187b82fefa95b1a6ddfca", "test1_souporcell_vs_hasheddrops_all_assignment_after_match.csv:md5,cc8f74509dc0a10c488c2a1e5e064644", - "test1_souporcell_vs_hasheddrops_concordance_heatmap.png:md5,d48df9ebb22dc1f2616511ad0405cdfd", "test1_souporcell_vs_hasheddrops_correlation_res.csv:md5,7e632ed518a1e6f02ed1ea25a28a4e45", "test1_souporcell_vs_hasheddrops_donor_match.csv:md5,d1feea9baad73b5bc106472a226cbbf2", "test1_souporcell_vs_hasheddrops_intersect_assignment_after_match.csv:md5,f31968c6aa78e9d8d4c43b99035f2cf3", @@ -291,42 +256,34 @@ "test1_best_intersect_assignment_after_match.csv:md5,2f98b7e021001d4093324dad82177d9d", "test1_score_record.csv:md5,19e6d7aa257ab96338c70bb1e69442b3", "test2_freemuxlet_vs_bff_consensuscall_all_assignment_after_match.csv:md5,c5552086e271152756067aa99ab77fa1", - "test2_freemuxlet_vs_bff_consensuscall_concordance_heatmap.png:md5,5c3e82573df24e781d4f422bc3b6925a", "test2_freemuxlet_vs_bff_consensuscall_correlation_res.csv:md5,5f2f2076175e313f10396b32fac4c26c", "test2_freemuxlet_vs_bff_consensuscall_donor_match.csv:md5,b92352374bf4b32f7e3a1b032a4c8b5f", "test2_freemuxlet_vs_bff_consensuscall_intersect_assignment_after_match.csv:md5,7827c80fbdb30275350dfea4e6ee3a46", "test2_freemuxlet_vs_bff_raw_all_assignment_after_match.csv:md5,f64080fd544ffd711634e06719c2a574", - "test2_freemuxlet_vs_bff_raw_concordance_heatmap.png:md5,5c3e82573df24e781d4f422bc3b6925a", "test2_freemuxlet_vs_bff_raw_correlation_res.csv:md5,5f2f2076175e313f10396b32fac4c26c", "test2_freemuxlet_vs_bff_raw_donor_match.csv:md5,b92352374bf4b32f7e3a1b032a4c8b5f", "test2_freemuxlet_vs_bff_raw_intersect_assignment_after_match.csv:md5,51b6c078396c57902e610e8497b52ec6", "test2_freemuxlet_vs_gmmdemux_all_assignment_after_match.csv:md5,843151ffa9d30820ade39c0fbe3a5150", - "test2_freemuxlet_vs_gmmdemux_concordance_heatmap.png:md5,5c3e82573df24e781d4f422bc3b6925a", "test2_freemuxlet_vs_gmmdemux_correlation_res.csv:md5,5f2f2076175e313f10396b32fac4c26c", "test2_freemuxlet_vs_gmmdemux_donor_match.csv:md5,b92352374bf4b32f7e3a1b032a4c8b5f", "test2_freemuxlet_vs_gmmdemux_intersect_assignment_after_match.csv:md5,e55c2902ef5d84a00e0df8c814d0b572", "test2_freemuxlet_vs_hasheddrops_all_assignment_after_match.csv:md5,52d09d2ed176e113c88b8325edd9cb79", - "test2_freemuxlet_vs_hasheddrops_concordance_heatmap.png:md5,5c3e82573df24e781d4f422bc3b6925a", "test2_freemuxlet_vs_hasheddrops_correlation_res.csv:md5,5f2f2076175e313f10396b32fac4c26c", "test2_freemuxlet_vs_hasheddrops_donor_match.csv:md5,b92352374bf4b32f7e3a1b032a4c8b5f", "test2_freemuxlet_vs_hasheddrops_intersect_assignment_after_match.csv:md5,78e11f774a11260199c5262b551c4618", "test2_souporcell_vs_bff_consensuscall_all_assignment_after_match.csv:md5,0453e72b042219598602022c78f500b0", - "test2_souporcell_vs_bff_consensuscall_concordance_heatmap.png:md5,d48df9ebb22dc1f2616511ad0405cdfd", "test2_souporcell_vs_bff_consensuscall_correlation_res.csv:md5,7e632ed518a1e6f02ed1ea25a28a4e45", "test2_souporcell_vs_bff_consensuscall_donor_match.csv:md5,d1feea9baad73b5bc106472a226cbbf2", "test2_souporcell_vs_bff_consensuscall_intersect_assignment_after_match.csv:md5,2f98b7e021001d4093324dad82177d9d", "test2_souporcell_vs_bff_raw_all_assignment_after_match.csv:md5,9667ceb1476d7ef9155042a2bfea590a", - "test2_souporcell_vs_bff_raw_concordance_heatmap.png:md5,d48df9ebb22dc1f2616511ad0405cdfd", "test2_souporcell_vs_bff_raw_correlation_res.csv:md5,7e632ed518a1e6f02ed1ea25a28a4e45", "test2_souporcell_vs_bff_raw_donor_match.csv:md5,d1feea9baad73b5bc106472a226cbbf2", "test2_souporcell_vs_bff_raw_intersect_assignment_after_match.csv:md5,245e79ea38948063f671d8553afd2d55", "test2_souporcell_vs_gmmdemux_all_assignment_after_match.csv:md5,ab32c63ef3a5e5258b399aa2a193ffe4", - "test2_souporcell_vs_gmmdemux_concordance_heatmap.png:md5,d48df9ebb22dc1f2616511ad0405cdfd", "test2_souporcell_vs_gmmdemux_correlation_res.csv:md5,7e632ed518a1e6f02ed1ea25a28a4e45", "test2_souporcell_vs_gmmdemux_donor_match.csv:md5,d1feea9baad73b5bc106472a226cbbf2", "test2_souporcell_vs_gmmdemux_intersect_assignment_after_match.csv:md5,e10f4fc3d56187b82fefa95b1a6ddfca", "test2_souporcell_vs_hasheddrops_all_assignment_after_match.csv:md5,cc8f74509dc0a10c488c2a1e5e064644", - "test2_souporcell_vs_hasheddrops_concordance_heatmap.png:md5,d48df9ebb22dc1f2616511ad0405cdfd", "test2_souporcell_vs_hasheddrops_correlation_res.csv:md5,7e632ed518a1e6f02ed1ea25a28a4e45", "test2_souporcell_vs_hasheddrops_donor_match.csv:md5,d1feea9baad73b5bc106472a226cbbf2", "test2_souporcell_vs_hasheddrops_intersect_assignment_after_match.csv:md5,f31968c6aa78e9d8d4c43b99035f2cf3", @@ -335,42 +292,34 @@ "test2_best_intersect_assignment_after_match.csv:md5,2f98b7e021001d4093324dad82177d9d", "test2_score_record.csv:md5,19e6d7aa257ab96338c70bb1e69442b3", "test3_freemuxlet_vs_bff_consensuscall_all_assignment_after_match.csv:md5,c5552086e271152756067aa99ab77fa1", - "test3_freemuxlet_vs_bff_consensuscall_concordance_heatmap.png:md5,5c3e82573df24e781d4f422bc3b6925a", "test3_freemuxlet_vs_bff_consensuscall_correlation_res.csv:md5,5f2f2076175e313f10396b32fac4c26c", "test3_freemuxlet_vs_bff_consensuscall_donor_match.csv:md5,b92352374bf4b32f7e3a1b032a4c8b5f", "test3_freemuxlet_vs_bff_consensuscall_intersect_assignment_after_match.csv:md5,7827c80fbdb30275350dfea4e6ee3a46", "test3_freemuxlet_vs_bff_raw_all_assignment_after_match.csv:md5,f64080fd544ffd711634e06719c2a574", - "test3_freemuxlet_vs_bff_raw_concordance_heatmap.png:md5,5c3e82573df24e781d4f422bc3b6925a", "test3_freemuxlet_vs_bff_raw_correlation_res.csv:md5,5f2f2076175e313f10396b32fac4c26c", "test3_freemuxlet_vs_bff_raw_donor_match.csv:md5,b92352374bf4b32f7e3a1b032a4c8b5f", "test3_freemuxlet_vs_bff_raw_intersect_assignment_after_match.csv:md5,51b6c078396c57902e610e8497b52ec6", "test3_freemuxlet_vs_gmmdemux_all_assignment_after_match.csv:md5,843151ffa9d30820ade39c0fbe3a5150", - "test3_freemuxlet_vs_gmmdemux_concordance_heatmap.png:md5,5c3e82573df24e781d4f422bc3b6925a", "test3_freemuxlet_vs_gmmdemux_correlation_res.csv:md5,5f2f2076175e313f10396b32fac4c26c", "test3_freemuxlet_vs_gmmdemux_donor_match.csv:md5,b92352374bf4b32f7e3a1b032a4c8b5f", "test3_freemuxlet_vs_gmmdemux_intersect_assignment_after_match.csv:md5,e55c2902ef5d84a00e0df8c814d0b572", "test3_freemuxlet_vs_hasheddrops_all_assignment_after_match.csv:md5,52d09d2ed176e113c88b8325edd9cb79", - "test3_freemuxlet_vs_hasheddrops_concordance_heatmap.png:md5,5c3e82573df24e781d4f422bc3b6925a", "test3_freemuxlet_vs_hasheddrops_correlation_res.csv:md5,5f2f2076175e313f10396b32fac4c26c", "test3_freemuxlet_vs_hasheddrops_donor_match.csv:md5,b92352374bf4b32f7e3a1b032a4c8b5f", "test3_freemuxlet_vs_hasheddrops_intersect_assignment_after_match.csv:md5,78e11f774a11260199c5262b551c4618", "test3_souporcell_vs_bff_consensuscall_all_assignment_after_match.csv:md5,0453e72b042219598602022c78f500b0", - "test3_souporcell_vs_bff_consensuscall_concordance_heatmap.png:md5,d48df9ebb22dc1f2616511ad0405cdfd", "test3_souporcell_vs_bff_consensuscall_correlation_res.csv:md5,7e632ed518a1e6f02ed1ea25a28a4e45", "test3_souporcell_vs_bff_consensuscall_donor_match.csv:md5,d1feea9baad73b5bc106472a226cbbf2", "test3_souporcell_vs_bff_consensuscall_intersect_assignment_after_match.csv:md5,2f98b7e021001d4093324dad82177d9d", "test3_souporcell_vs_bff_raw_all_assignment_after_match.csv:md5,9667ceb1476d7ef9155042a2bfea590a", - "test3_souporcell_vs_bff_raw_concordance_heatmap.png:md5,d48df9ebb22dc1f2616511ad0405cdfd", "test3_souporcell_vs_bff_raw_correlation_res.csv:md5,7e632ed518a1e6f02ed1ea25a28a4e45", "test3_souporcell_vs_bff_raw_donor_match.csv:md5,d1feea9baad73b5bc106472a226cbbf2", "test3_souporcell_vs_bff_raw_intersect_assignment_after_match.csv:md5,245e79ea38948063f671d8553afd2d55", "test3_souporcell_vs_gmmdemux_all_assignment_after_match.csv:md5,ab32c63ef3a5e5258b399aa2a193ffe4", - "test3_souporcell_vs_gmmdemux_concordance_heatmap.png:md5,d48df9ebb22dc1f2616511ad0405cdfd", "test3_souporcell_vs_gmmdemux_correlation_res.csv:md5,7e632ed518a1e6f02ed1ea25a28a4e45", "test3_souporcell_vs_gmmdemux_donor_match.csv:md5,d1feea9baad73b5bc106472a226cbbf2", "test3_souporcell_vs_gmmdemux_intersect_assignment_after_match.csv:md5,e10f4fc3d56187b82fefa95b1a6ddfca", "test3_souporcell_vs_hasheddrops_all_assignment_after_match.csv:md5,cc8f74509dc0a10c488c2a1e5e064644", - "test3_souporcell_vs_hasheddrops_concordance_heatmap.png:md5,d48df9ebb22dc1f2616511ad0405cdfd", "test3_souporcell_vs_hasheddrops_correlation_res.csv:md5,7e632ed518a1e6f02ed1ea25a28a4e45", "test3_souporcell_vs_hasheddrops_donor_match.csv:md5,d1feea9baad73b5bc106472a226cbbf2", "test3_souporcell_vs_hasheddrops_intersect_assignment_after_match.csv:md5,f31968c6aa78e9d8d4c43b99035f2cf3", @@ -386,7 +335,6 @@ "test1_hto-2_unmatched_gt.csv:md5,e53bfe5050c7197a53acec1f30f2daec", "test1_all_representative_variants.csv:md5,93cf3fde5b1236b83abd53369b250f64", "test1_donor_specific_variants.csv:md5,bd137190cb87b1ad68a7283571e9a4b6", - "test1_donor_specific_variants_upset.png:md5,5d0abe01aa07abd018d20abe8e2e99ab", "test1_vireo_variants.csv:md5,34bf574b4fe7c16db53a86f5cf6cdbf3", "test2_hto-1_informative_variants.csv:md5,7ba6e70ac79adf74d2aa85f723b9f0d6", "test2_hto-1_matched_gt.csv:md5,bf3b7d684f30153fa603dbddde635583", @@ -396,7 +344,6 @@ "test2_hto-2_unmatched_gt.csv:md5,e53bfe5050c7197a53acec1f30f2daec", "test2_all_representative_variants.csv:md5,93cf3fde5b1236b83abd53369b250f64", "test2_donor_specific_variants.csv:md5,bd137190cb87b1ad68a7283571e9a4b6", - "test2_donor_specific_variants_upset.png:md5,5d0abe01aa07abd018d20abe8e2e99ab", "test2_vireo_variants.csv:md5,34bf574b4fe7c16db53a86f5cf6cdbf3", "test3_hto-1_informative_variants.csv:md5,7ba6e70ac79adf74d2aa85f723b9f0d6", "test3_hto-1_matched_gt.csv:md5,bf3b7d684f30153fa603dbddde635583", @@ -406,7 +353,6 @@ "test3_hto-2_unmatched_gt.csv:md5,e53bfe5050c7197a53acec1f30f2daec", "test3_all_representative_variants.csv:md5,93cf3fde5b1236b83abd53369b250f64", "test3_donor_specific_variants.csv:md5,bd137190cb87b1ad68a7283571e9a4b6", - "test3_donor_specific_variants_upset.png:md5,5d0abe01aa07abd018d20abe8e2e99ab", "test3_vireo_variants.csv:md5,34bf574b4fe7c16db53a86f5cf6cdbf3", "multiqc_citations.txt:md5,4c806e63a283ec1b7e78cdae3a923d4f" ], diff --git a/tests/test_genetic.nf.test.snap b/tests/test_genetic.nf.test.snap index 436daf64..4dd2bd2f 100644 --- a/tests/test_genetic.nf.test.snap +++ b/tests/test_genetic.nf.test.snap @@ -59,17 +59,14 @@ "donor_match", "donor_match/test1", "donor_match/test1/freemuxlet_vs_souporcell", - "donor_match/test1/freemuxlet_vs_souporcell/test1_freemuxlet_vs_souporcell_concordance_heatmap.png", "donor_match/test1/freemuxlet_vs_souporcell/test1_freemuxlet_vs_souporcell_correlation_res.csv", "donor_match/test1/freemuxlet_vs_souporcell/test1_freemuxlet_vs_souporcell_donor_match.csv", "donor_match/test2", "donor_match/test2/freemuxlet_vs_souporcell", - "donor_match/test2/freemuxlet_vs_souporcell/test2_freemuxlet_vs_souporcell_concordance_heatmap.png", "donor_match/test2/freemuxlet_vs_souporcell/test2_freemuxlet_vs_souporcell_correlation_res.csv", "donor_match/test2/freemuxlet_vs_souporcell/test2_freemuxlet_vs_souporcell_donor_match.csv", "donor_match/test3", "donor_match/test3/freemuxlet_vs_souporcell", - "donor_match/test3/freemuxlet_vs_souporcell/test3_freemuxlet_vs_souporcell_concordance_heatmap.png", "donor_match/test3/freemuxlet_vs_souporcell/test3_freemuxlet_vs_souporcell_correlation_res.csv", "donor_match/test3/freemuxlet_vs_souporcell/test3_freemuxlet_vs_souporcell_donor_match.csv", "genetic", @@ -184,13 +181,10 @@ "summary/test3" ], [ - "test1_freemuxlet_vs_souporcell_concordance_heatmap.png:md5,dd2e92af065ab80b23767797978d05b6", "test1_freemuxlet_vs_souporcell_correlation_res.csv:md5,d21819b5db460f1535596d996af87976", "test1_freemuxlet_vs_souporcell_donor_match.csv:md5,c27bb85b84d4f2e33948b4232cd4169d", - "test2_freemuxlet_vs_souporcell_concordance_heatmap.png:md5,dd2e92af065ab80b23767797978d05b6", "test2_freemuxlet_vs_souporcell_correlation_res.csv:md5,d21819b5db460f1535596d996af87976", "test2_freemuxlet_vs_souporcell_donor_match.csv:md5,c27bb85b84d4f2e33948b4232cd4169d", - "test3_freemuxlet_vs_souporcell_concordance_heatmap.png:md5,dd2e92af065ab80b23767797978d05b6", "test3_freemuxlet_vs_souporcell_correlation_res.csv:md5,d21819b5db460f1535596d996af87976", "test3_freemuxlet_vs_souporcell_donor_match.csv:md5,c27bb85b84d4f2e33948b4232cd4169d", "test1.best:md5,237dadb1f53067fc7e3aad4336c6541b", diff --git a/tests/test_hashing.nf.test.snap b/tests/test_hashing.nf.test.snap index 15fbe1b5..ccb489b4 100644 --- a/tests/test_hashing.nf.test.snap +++ b/tests/test_hashing.nf.test.snap @@ -86,341 +86,257 @@ "donor_match", "donor_match/test1", "donor_match/test1/bff_raw_vs_bff_consensuscall", - "donor_match/test1/bff_raw_vs_bff_consensuscall/test1_bff_raw_vs_bff_consensuscall_concordance_heatmap.png", "donor_match/test1/bff_raw_vs_bff_consensuscall/test1_bff_raw_vs_bff_consensuscall_correlation_res.csv", "donor_match/test1/bff_raw_vs_bff_consensuscall/test1_bff_raw_vs_bff_consensuscall_donor_match.csv", "donor_match/test1/demuxem_vs_bff_consensuscall", - "donor_match/test1/demuxem_vs_bff_consensuscall/test1_demuxem_vs_bff_consensuscall_concordance_heatmap.png", "donor_match/test1/demuxem_vs_bff_consensuscall/test1_demuxem_vs_bff_consensuscall_correlation_res.csv", "donor_match/test1/demuxem_vs_bff_consensuscall/test1_demuxem_vs_bff_consensuscall_donor_match.csv", "donor_match/test1/demuxem_vs_bff_raw", - "donor_match/test1/demuxem_vs_bff_raw/test1_demuxem_vs_bff_raw_concordance_heatmap.png", "donor_match/test1/demuxem_vs_bff_raw/test1_demuxem_vs_bff_raw_correlation_res.csv", "donor_match/test1/demuxem_vs_bff_raw/test1_demuxem_vs_bff_raw_donor_match.csv", "donor_match/test1/demuxem_vs_gmmdemux", - "donor_match/test1/demuxem_vs_gmmdemux/test1_demuxem_vs_gmmdemux_concordance_heatmap.png", "donor_match/test1/demuxem_vs_gmmdemux/test1_demuxem_vs_gmmdemux_correlation_res.csv", "donor_match/test1/demuxem_vs_gmmdemux/test1_demuxem_vs_gmmdemux_donor_match.csv", "donor_match/test1/demuxem_vs_hasheddrops", - "donor_match/test1/demuxem_vs_hasheddrops/test1_demuxem_vs_hasheddrops_concordance_heatmap.png", "donor_match/test1/demuxem_vs_hasheddrops/test1_demuxem_vs_hasheddrops_correlation_res.csv", "donor_match/test1/demuxem_vs_hasheddrops/test1_demuxem_vs_hasheddrops_donor_match.csv", "donor_match/test1/demuxem_vs_hashsolo", - "donor_match/test1/demuxem_vs_hashsolo/test1_demuxem_vs_hashsolo_concordance_heatmap.png", "donor_match/test1/demuxem_vs_hashsolo/test1_demuxem_vs_hashsolo_correlation_res.csv", "donor_match/test1/demuxem_vs_hashsolo/test1_demuxem_vs_hashsolo_donor_match.csv", "donor_match/test1/demuxem_vs_htodemux", - "donor_match/test1/demuxem_vs_htodemux/test1_demuxem_vs_htodemux_concordance_heatmap.png", "donor_match/test1/demuxem_vs_htodemux/test1_demuxem_vs_htodemux_correlation_res.csv", "donor_match/test1/demuxem_vs_htodemux/test1_demuxem_vs_htodemux_donor_match.csv", "donor_match/test1/demuxem_vs_multiseq", - "donor_match/test1/demuxem_vs_multiseq/test1_demuxem_vs_multiseq_concordance_heatmap.png", "donor_match/test1/demuxem_vs_multiseq/test1_demuxem_vs_multiseq_correlation_res.csv", "donor_match/test1/demuxem_vs_multiseq/test1_demuxem_vs_multiseq_donor_match.csv", "donor_match/test1/gmmdemux_vs_bff_consensuscall", - "donor_match/test1/gmmdemux_vs_bff_consensuscall/test1_gmmdemux_vs_bff_consensuscall_concordance_heatmap.png", "donor_match/test1/gmmdemux_vs_bff_consensuscall/test1_gmmdemux_vs_bff_consensuscall_correlation_res.csv", "donor_match/test1/gmmdemux_vs_bff_consensuscall/test1_gmmdemux_vs_bff_consensuscall_donor_match.csv", "donor_match/test1/gmmdemux_vs_bff_raw", - "donor_match/test1/gmmdemux_vs_bff_raw/test1_gmmdemux_vs_bff_raw_concordance_heatmap.png", "donor_match/test1/gmmdemux_vs_bff_raw/test1_gmmdemux_vs_bff_raw_correlation_res.csv", "donor_match/test1/gmmdemux_vs_bff_raw/test1_gmmdemux_vs_bff_raw_donor_match.csv", "donor_match/test1/hasheddrops_vs_bff_consensuscall", - "donor_match/test1/hasheddrops_vs_bff_consensuscall/test1_hasheddrops_vs_bff_consensuscall_concordance_heatmap.png", "donor_match/test1/hasheddrops_vs_bff_consensuscall/test1_hasheddrops_vs_bff_consensuscall_correlation_res.csv", "donor_match/test1/hasheddrops_vs_bff_consensuscall/test1_hasheddrops_vs_bff_consensuscall_donor_match.csv", "donor_match/test1/hasheddrops_vs_bff_raw", - "donor_match/test1/hasheddrops_vs_bff_raw/test1_hasheddrops_vs_bff_raw_concordance_heatmap.png", "donor_match/test1/hasheddrops_vs_bff_raw/test1_hasheddrops_vs_bff_raw_correlation_res.csv", "donor_match/test1/hasheddrops_vs_bff_raw/test1_hasheddrops_vs_bff_raw_donor_match.csv", "donor_match/test1/hasheddrops_vs_gmmdemux", - "donor_match/test1/hasheddrops_vs_gmmdemux/test1_hasheddrops_vs_gmmdemux_concordance_heatmap.png", "donor_match/test1/hasheddrops_vs_gmmdemux/test1_hasheddrops_vs_gmmdemux_correlation_res.csv", "donor_match/test1/hasheddrops_vs_gmmdemux/test1_hasheddrops_vs_gmmdemux_donor_match.csv", "donor_match/test1/hasheddrops_vs_htodemux", - "donor_match/test1/hasheddrops_vs_htodemux/test1_hasheddrops_vs_htodemux_concordance_heatmap.png", "donor_match/test1/hasheddrops_vs_htodemux/test1_hasheddrops_vs_htodemux_correlation_res.csv", "donor_match/test1/hasheddrops_vs_htodemux/test1_hasheddrops_vs_htodemux_donor_match.csv", "donor_match/test1/hasheddrops_vs_multiseq", - "donor_match/test1/hasheddrops_vs_multiseq/test1_hasheddrops_vs_multiseq_concordance_heatmap.png", "donor_match/test1/hasheddrops_vs_multiseq/test1_hasheddrops_vs_multiseq_correlation_res.csv", "donor_match/test1/hasheddrops_vs_multiseq/test1_hasheddrops_vs_multiseq_donor_match.csv", "donor_match/test1/hashsolo_vs_bff_consensuscall", - "donor_match/test1/hashsolo_vs_bff_consensuscall/test1_hashsolo_vs_bff_consensuscall_concordance_heatmap.png", "donor_match/test1/hashsolo_vs_bff_consensuscall/test1_hashsolo_vs_bff_consensuscall_correlation_res.csv", "donor_match/test1/hashsolo_vs_bff_consensuscall/test1_hashsolo_vs_bff_consensuscall_donor_match.csv", "donor_match/test1/hashsolo_vs_bff_raw", - "donor_match/test1/hashsolo_vs_bff_raw/test1_hashsolo_vs_bff_raw_concordance_heatmap.png", "donor_match/test1/hashsolo_vs_bff_raw/test1_hashsolo_vs_bff_raw_correlation_res.csv", "donor_match/test1/hashsolo_vs_bff_raw/test1_hashsolo_vs_bff_raw_donor_match.csv", "donor_match/test1/hashsolo_vs_gmmdemux", - "donor_match/test1/hashsolo_vs_gmmdemux/test1_hashsolo_vs_gmmdemux_concordance_heatmap.png", "donor_match/test1/hashsolo_vs_gmmdemux/test1_hashsolo_vs_gmmdemux_correlation_res.csv", "donor_match/test1/hashsolo_vs_gmmdemux/test1_hashsolo_vs_gmmdemux_donor_match.csv", "donor_match/test1/hashsolo_vs_hasheddrops", - "donor_match/test1/hashsolo_vs_hasheddrops/test1_hashsolo_vs_hasheddrops_concordance_heatmap.png", "donor_match/test1/hashsolo_vs_hasheddrops/test1_hashsolo_vs_hasheddrops_correlation_res.csv", "donor_match/test1/hashsolo_vs_hasheddrops/test1_hashsolo_vs_hasheddrops_donor_match.csv", "donor_match/test1/hashsolo_vs_htodemux", - "donor_match/test1/hashsolo_vs_htodemux/test1_hashsolo_vs_htodemux_concordance_heatmap.png", "donor_match/test1/hashsolo_vs_htodemux/test1_hashsolo_vs_htodemux_correlation_res.csv", "donor_match/test1/hashsolo_vs_htodemux/test1_hashsolo_vs_htodemux_donor_match.csv", "donor_match/test1/hashsolo_vs_multiseq", - "donor_match/test1/hashsolo_vs_multiseq/test1_hashsolo_vs_multiseq_concordance_heatmap.png", "donor_match/test1/hashsolo_vs_multiseq/test1_hashsolo_vs_multiseq_correlation_res.csv", "donor_match/test1/hashsolo_vs_multiseq/test1_hashsolo_vs_multiseq_donor_match.csv", "donor_match/test1/htodemux_vs_bff_consensuscall", - "donor_match/test1/htodemux_vs_bff_consensuscall/test1_htodemux_vs_bff_consensuscall_concordance_heatmap.png", "donor_match/test1/htodemux_vs_bff_consensuscall/test1_htodemux_vs_bff_consensuscall_correlation_res.csv", "donor_match/test1/htodemux_vs_bff_consensuscall/test1_htodemux_vs_bff_consensuscall_donor_match.csv", "donor_match/test1/htodemux_vs_bff_raw", - "donor_match/test1/htodemux_vs_bff_raw/test1_htodemux_vs_bff_raw_concordance_heatmap.png", "donor_match/test1/htodemux_vs_bff_raw/test1_htodemux_vs_bff_raw_correlation_res.csv", "donor_match/test1/htodemux_vs_bff_raw/test1_htodemux_vs_bff_raw_donor_match.csv", "donor_match/test1/htodemux_vs_gmmdemux", - "donor_match/test1/htodemux_vs_gmmdemux/test1_htodemux_vs_gmmdemux_concordance_heatmap.png", "donor_match/test1/htodemux_vs_gmmdemux/test1_htodemux_vs_gmmdemux_correlation_res.csv", "donor_match/test1/htodemux_vs_gmmdemux/test1_htodemux_vs_gmmdemux_donor_match.csv", "donor_match/test1/multiseq_vs_bff_consensuscall", - "donor_match/test1/multiseq_vs_bff_consensuscall/test1_multiseq_vs_bff_consensuscall_concordance_heatmap.png", "donor_match/test1/multiseq_vs_bff_consensuscall/test1_multiseq_vs_bff_consensuscall_correlation_res.csv", "donor_match/test1/multiseq_vs_bff_consensuscall/test1_multiseq_vs_bff_consensuscall_donor_match.csv", "donor_match/test1/multiseq_vs_bff_raw", - "donor_match/test1/multiseq_vs_bff_raw/test1_multiseq_vs_bff_raw_concordance_heatmap.png", "donor_match/test1/multiseq_vs_bff_raw/test1_multiseq_vs_bff_raw_correlation_res.csv", "donor_match/test1/multiseq_vs_bff_raw/test1_multiseq_vs_bff_raw_donor_match.csv", "donor_match/test1/multiseq_vs_gmmdemux", - "donor_match/test1/multiseq_vs_gmmdemux/test1_multiseq_vs_gmmdemux_concordance_heatmap.png", "donor_match/test1/multiseq_vs_gmmdemux/test1_multiseq_vs_gmmdemux_correlation_res.csv", "donor_match/test1/multiseq_vs_gmmdemux/test1_multiseq_vs_gmmdemux_donor_match.csv", "donor_match/test1/multiseq_vs_htodemux", - "donor_match/test1/multiseq_vs_htodemux/test1_multiseq_vs_htodemux_concordance_heatmap.png", "donor_match/test1/multiseq_vs_htodemux/test1_multiseq_vs_htodemux_correlation_res.csv", "donor_match/test1/multiseq_vs_htodemux/test1_multiseq_vs_htodemux_donor_match.csv", "donor_match/test2", "donor_match/test2/bff_raw_vs_bff_consensuscall", - "donor_match/test2/bff_raw_vs_bff_consensuscall/test2_bff_raw_vs_bff_consensuscall_concordance_heatmap.png", "donor_match/test2/bff_raw_vs_bff_consensuscall/test2_bff_raw_vs_bff_consensuscall_correlation_res.csv", "donor_match/test2/bff_raw_vs_bff_consensuscall/test2_bff_raw_vs_bff_consensuscall_donor_match.csv", "donor_match/test2/demuxem_vs_bff_consensuscall", - "donor_match/test2/demuxem_vs_bff_consensuscall/test2_demuxem_vs_bff_consensuscall_concordance_heatmap.png", "donor_match/test2/demuxem_vs_bff_consensuscall/test2_demuxem_vs_bff_consensuscall_correlation_res.csv", "donor_match/test2/demuxem_vs_bff_consensuscall/test2_demuxem_vs_bff_consensuscall_donor_match.csv", "donor_match/test2/demuxem_vs_bff_raw", - "donor_match/test2/demuxem_vs_bff_raw/test2_demuxem_vs_bff_raw_concordance_heatmap.png", "donor_match/test2/demuxem_vs_bff_raw/test2_demuxem_vs_bff_raw_correlation_res.csv", "donor_match/test2/demuxem_vs_bff_raw/test2_demuxem_vs_bff_raw_donor_match.csv", "donor_match/test2/demuxem_vs_gmmdemux", - "donor_match/test2/demuxem_vs_gmmdemux/test2_demuxem_vs_gmmdemux_concordance_heatmap.png", "donor_match/test2/demuxem_vs_gmmdemux/test2_demuxem_vs_gmmdemux_correlation_res.csv", "donor_match/test2/demuxem_vs_gmmdemux/test2_demuxem_vs_gmmdemux_donor_match.csv", "donor_match/test2/demuxem_vs_hasheddrops", - "donor_match/test2/demuxem_vs_hasheddrops/test2_demuxem_vs_hasheddrops_concordance_heatmap.png", "donor_match/test2/demuxem_vs_hasheddrops/test2_demuxem_vs_hasheddrops_correlation_res.csv", "donor_match/test2/demuxem_vs_hasheddrops/test2_demuxem_vs_hasheddrops_donor_match.csv", "donor_match/test2/demuxem_vs_hashsolo", - "donor_match/test2/demuxem_vs_hashsolo/test2_demuxem_vs_hashsolo_concordance_heatmap.png", "donor_match/test2/demuxem_vs_hashsolo/test2_demuxem_vs_hashsolo_correlation_res.csv", "donor_match/test2/demuxem_vs_hashsolo/test2_demuxem_vs_hashsolo_donor_match.csv", "donor_match/test2/demuxem_vs_htodemux", - "donor_match/test2/demuxem_vs_htodemux/test2_demuxem_vs_htodemux_concordance_heatmap.png", "donor_match/test2/demuxem_vs_htodemux/test2_demuxem_vs_htodemux_correlation_res.csv", "donor_match/test2/demuxem_vs_htodemux/test2_demuxem_vs_htodemux_donor_match.csv", "donor_match/test2/demuxem_vs_multiseq", - "donor_match/test2/demuxem_vs_multiseq/test2_demuxem_vs_multiseq_concordance_heatmap.png", "donor_match/test2/demuxem_vs_multiseq/test2_demuxem_vs_multiseq_correlation_res.csv", "donor_match/test2/demuxem_vs_multiseq/test2_demuxem_vs_multiseq_donor_match.csv", "donor_match/test2/gmmdemux_vs_bff_consensuscall", - "donor_match/test2/gmmdemux_vs_bff_consensuscall/test2_gmmdemux_vs_bff_consensuscall_concordance_heatmap.png", "donor_match/test2/gmmdemux_vs_bff_consensuscall/test2_gmmdemux_vs_bff_consensuscall_correlation_res.csv", "donor_match/test2/gmmdemux_vs_bff_consensuscall/test2_gmmdemux_vs_bff_consensuscall_donor_match.csv", "donor_match/test2/gmmdemux_vs_bff_raw", - "donor_match/test2/gmmdemux_vs_bff_raw/test2_gmmdemux_vs_bff_raw_concordance_heatmap.png", "donor_match/test2/gmmdemux_vs_bff_raw/test2_gmmdemux_vs_bff_raw_correlation_res.csv", "donor_match/test2/gmmdemux_vs_bff_raw/test2_gmmdemux_vs_bff_raw_donor_match.csv", "donor_match/test2/hasheddrops_vs_bff_consensuscall", - "donor_match/test2/hasheddrops_vs_bff_consensuscall/test2_hasheddrops_vs_bff_consensuscall_concordance_heatmap.png", "donor_match/test2/hasheddrops_vs_bff_consensuscall/test2_hasheddrops_vs_bff_consensuscall_correlation_res.csv", "donor_match/test2/hasheddrops_vs_bff_consensuscall/test2_hasheddrops_vs_bff_consensuscall_donor_match.csv", "donor_match/test2/hasheddrops_vs_bff_raw", - "donor_match/test2/hasheddrops_vs_bff_raw/test2_hasheddrops_vs_bff_raw_concordance_heatmap.png", "donor_match/test2/hasheddrops_vs_bff_raw/test2_hasheddrops_vs_bff_raw_correlation_res.csv", "donor_match/test2/hasheddrops_vs_bff_raw/test2_hasheddrops_vs_bff_raw_donor_match.csv", "donor_match/test2/hasheddrops_vs_gmmdemux", - "donor_match/test2/hasheddrops_vs_gmmdemux/test2_hasheddrops_vs_gmmdemux_concordance_heatmap.png", "donor_match/test2/hasheddrops_vs_gmmdemux/test2_hasheddrops_vs_gmmdemux_correlation_res.csv", "donor_match/test2/hasheddrops_vs_gmmdemux/test2_hasheddrops_vs_gmmdemux_donor_match.csv", "donor_match/test2/hasheddrops_vs_htodemux", - "donor_match/test2/hasheddrops_vs_htodemux/test2_hasheddrops_vs_htodemux_concordance_heatmap.png", "donor_match/test2/hasheddrops_vs_htodemux/test2_hasheddrops_vs_htodemux_correlation_res.csv", "donor_match/test2/hasheddrops_vs_htodemux/test2_hasheddrops_vs_htodemux_donor_match.csv", "donor_match/test2/hasheddrops_vs_multiseq", - "donor_match/test2/hasheddrops_vs_multiseq/test2_hasheddrops_vs_multiseq_concordance_heatmap.png", "donor_match/test2/hasheddrops_vs_multiseq/test2_hasheddrops_vs_multiseq_correlation_res.csv", "donor_match/test2/hasheddrops_vs_multiseq/test2_hasheddrops_vs_multiseq_donor_match.csv", "donor_match/test2/hashsolo_vs_bff_consensuscall", - "donor_match/test2/hashsolo_vs_bff_consensuscall/test2_hashsolo_vs_bff_consensuscall_concordance_heatmap.png", "donor_match/test2/hashsolo_vs_bff_consensuscall/test2_hashsolo_vs_bff_consensuscall_correlation_res.csv", "donor_match/test2/hashsolo_vs_bff_consensuscall/test2_hashsolo_vs_bff_consensuscall_donor_match.csv", "donor_match/test2/hashsolo_vs_bff_raw", - "donor_match/test2/hashsolo_vs_bff_raw/test2_hashsolo_vs_bff_raw_concordance_heatmap.png", "donor_match/test2/hashsolo_vs_bff_raw/test2_hashsolo_vs_bff_raw_correlation_res.csv", "donor_match/test2/hashsolo_vs_bff_raw/test2_hashsolo_vs_bff_raw_donor_match.csv", "donor_match/test2/hashsolo_vs_gmmdemux", - "donor_match/test2/hashsolo_vs_gmmdemux/test2_hashsolo_vs_gmmdemux_concordance_heatmap.png", "donor_match/test2/hashsolo_vs_gmmdemux/test2_hashsolo_vs_gmmdemux_correlation_res.csv", "donor_match/test2/hashsolo_vs_gmmdemux/test2_hashsolo_vs_gmmdemux_donor_match.csv", "donor_match/test2/hashsolo_vs_hasheddrops", - "donor_match/test2/hashsolo_vs_hasheddrops/test2_hashsolo_vs_hasheddrops_concordance_heatmap.png", "donor_match/test2/hashsolo_vs_hasheddrops/test2_hashsolo_vs_hasheddrops_correlation_res.csv", "donor_match/test2/hashsolo_vs_hasheddrops/test2_hashsolo_vs_hasheddrops_donor_match.csv", "donor_match/test2/hashsolo_vs_htodemux", - "donor_match/test2/hashsolo_vs_htodemux/test2_hashsolo_vs_htodemux_concordance_heatmap.png", "donor_match/test2/hashsolo_vs_htodemux/test2_hashsolo_vs_htodemux_correlation_res.csv", "donor_match/test2/hashsolo_vs_htodemux/test2_hashsolo_vs_htodemux_donor_match.csv", "donor_match/test2/hashsolo_vs_multiseq", - "donor_match/test2/hashsolo_vs_multiseq/test2_hashsolo_vs_multiseq_concordance_heatmap.png", "donor_match/test2/hashsolo_vs_multiseq/test2_hashsolo_vs_multiseq_correlation_res.csv", "donor_match/test2/hashsolo_vs_multiseq/test2_hashsolo_vs_multiseq_donor_match.csv", "donor_match/test2/htodemux_vs_bff_consensuscall", - "donor_match/test2/htodemux_vs_bff_consensuscall/test2_htodemux_vs_bff_consensuscall_concordance_heatmap.png", "donor_match/test2/htodemux_vs_bff_consensuscall/test2_htodemux_vs_bff_consensuscall_correlation_res.csv", "donor_match/test2/htodemux_vs_bff_consensuscall/test2_htodemux_vs_bff_consensuscall_donor_match.csv", "donor_match/test2/htodemux_vs_bff_raw", - "donor_match/test2/htodemux_vs_bff_raw/test2_htodemux_vs_bff_raw_concordance_heatmap.png", "donor_match/test2/htodemux_vs_bff_raw/test2_htodemux_vs_bff_raw_correlation_res.csv", "donor_match/test2/htodemux_vs_bff_raw/test2_htodemux_vs_bff_raw_donor_match.csv", "donor_match/test2/htodemux_vs_gmmdemux", - "donor_match/test2/htodemux_vs_gmmdemux/test2_htodemux_vs_gmmdemux_concordance_heatmap.png", "donor_match/test2/htodemux_vs_gmmdemux/test2_htodemux_vs_gmmdemux_correlation_res.csv", "donor_match/test2/htodemux_vs_gmmdemux/test2_htodemux_vs_gmmdemux_donor_match.csv", "donor_match/test2/multiseq_vs_bff_consensuscall", - "donor_match/test2/multiseq_vs_bff_consensuscall/test2_multiseq_vs_bff_consensuscall_concordance_heatmap.png", "donor_match/test2/multiseq_vs_bff_consensuscall/test2_multiseq_vs_bff_consensuscall_correlation_res.csv", "donor_match/test2/multiseq_vs_bff_consensuscall/test2_multiseq_vs_bff_consensuscall_donor_match.csv", "donor_match/test2/multiseq_vs_bff_raw", - "donor_match/test2/multiseq_vs_bff_raw/test2_multiseq_vs_bff_raw_concordance_heatmap.png", "donor_match/test2/multiseq_vs_bff_raw/test2_multiseq_vs_bff_raw_correlation_res.csv", "donor_match/test2/multiseq_vs_bff_raw/test2_multiseq_vs_bff_raw_donor_match.csv", "donor_match/test2/multiseq_vs_gmmdemux", - "donor_match/test2/multiseq_vs_gmmdemux/test2_multiseq_vs_gmmdemux_concordance_heatmap.png", "donor_match/test2/multiseq_vs_gmmdemux/test2_multiseq_vs_gmmdemux_correlation_res.csv", "donor_match/test2/multiseq_vs_gmmdemux/test2_multiseq_vs_gmmdemux_donor_match.csv", "donor_match/test2/multiseq_vs_htodemux", - "donor_match/test2/multiseq_vs_htodemux/test2_multiseq_vs_htodemux_concordance_heatmap.png", "donor_match/test2/multiseq_vs_htodemux/test2_multiseq_vs_htodemux_correlation_res.csv", "donor_match/test2/multiseq_vs_htodemux/test2_multiseq_vs_htodemux_donor_match.csv", "donor_match/test3", "donor_match/test3/bff_raw_vs_bff_consensuscall", - "donor_match/test3/bff_raw_vs_bff_consensuscall/test3_bff_raw_vs_bff_consensuscall_concordance_heatmap.png", "donor_match/test3/bff_raw_vs_bff_consensuscall/test3_bff_raw_vs_bff_consensuscall_correlation_res.csv", "donor_match/test3/bff_raw_vs_bff_consensuscall/test3_bff_raw_vs_bff_consensuscall_donor_match.csv", "donor_match/test3/demuxem_vs_bff_consensuscall", - "donor_match/test3/demuxem_vs_bff_consensuscall/test3_demuxem_vs_bff_consensuscall_concordance_heatmap.png", "donor_match/test3/demuxem_vs_bff_consensuscall/test3_demuxem_vs_bff_consensuscall_correlation_res.csv", "donor_match/test3/demuxem_vs_bff_consensuscall/test3_demuxem_vs_bff_consensuscall_donor_match.csv", "donor_match/test3/demuxem_vs_bff_raw", - "donor_match/test3/demuxem_vs_bff_raw/test3_demuxem_vs_bff_raw_concordance_heatmap.png", "donor_match/test3/demuxem_vs_bff_raw/test3_demuxem_vs_bff_raw_correlation_res.csv", "donor_match/test3/demuxem_vs_bff_raw/test3_demuxem_vs_bff_raw_donor_match.csv", "donor_match/test3/demuxem_vs_gmmdemux", - "donor_match/test3/demuxem_vs_gmmdemux/test3_demuxem_vs_gmmdemux_concordance_heatmap.png", "donor_match/test3/demuxem_vs_gmmdemux/test3_demuxem_vs_gmmdemux_correlation_res.csv", "donor_match/test3/demuxem_vs_gmmdemux/test3_demuxem_vs_gmmdemux_donor_match.csv", "donor_match/test3/demuxem_vs_hasheddrops", - "donor_match/test3/demuxem_vs_hasheddrops/test3_demuxem_vs_hasheddrops_concordance_heatmap.png", "donor_match/test3/demuxem_vs_hasheddrops/test3_demuxem_vs_hasheddrops_correlation_res.csv", "donor_match/test3/demuxem_vs_hasheddrops/test3_demuxem_vs_hasheddrops_donor_match.csv", "donor_match/test3/demuxem_vs_hashsolo", - "donor_match/test3/demuxem_vs_hashsolo/test3_demuxem_vs_hashsolo_concordance_heatmap.png", "donor_match/test3/demuxem_vs_hashsolo/test3_demuxem_vs_hashsolo_correlation_res.csv", "donor_match/test3/demuxem_vs_hashsolo/test3_demuxem_vs_hashsolo_donor_match.csv", "donor_match/test3/demuxem_vs_htodemux", - "donor_match/test3/demuxem_vs_htodemux/test3_demuxem_vs_htodemux_concordance_heatmap.png", "donor_match/test3/demuxem_vs_htodemux/test3_demuxem_vs_htodemux_correlation_res.csv", "donor_match/test3/demuxem_vs_htodemux/test3_demuxem_vs_htodemux_donor_match.csv", "donor_match/test3/demuxem_vs_multiseq", - "donor_match/test3/demuxem_vs_multiseq/test3_demuxem_vs_multiseq_concordance_heatmap.png", "donor_match/test3/demuxem_vs_multiseq/test3_demuxem_vs_multiseq_correlation_res.csv", "donor_match/test3/demuxem_vs_multiseq/test3_demuxem_vs_multiseq_donor_match.csv", "donor_match/test3/gmmdemux_vs_bff_consensuscall", - "donor_match/test3/gmmdemux_vs_bff_consensuscall/test3_gmmdemux_vs_bff_consensuscall_concordance_heatmap.png", "donor_match/test3/gmmdemux_vs_bff_consensuscall/test3_gmmdemux_vs_bff_consensuscall_correlation_res.csv", "donor_match/test3/gmmdemux_vs_bff_consensuscall/test3_gmmdemux_vs_bff_consensuscall_donor_match.csv", "donor_match/test3/gmmdemux_vs_bff_raw", - "donor_match/test3/gmmdemux_vs_bff_raw/test3_gmmdemux_vs_bff_raw_concordance_heatmap.png", "donor_match/test3/gmmdemux_vs_bff_raw/test3_gmmdemux_vs_bff_raw_correlation_res.csv", "donor_match/test3/gmmdemux_vs_bff_raw/test3_gmmdemux_vs_bff_raw_donor_match.csv", "donor_match/test3/hasheddrops_vs_bff_consensuscall", - "donor_match/test3/hasheddrops_vs_bff_consensuscall/test3_hasheddrops_vs_bff_consensuscall_concordance_heatmap.png", "donor_match/test3/hasheddrops_vs_bff_consensuscall/test3_hasheddrops_vs_bff_consensuscall_correlation_res.csv", "donor_match/test3/hasheddrops_vs_bff_consensuscall/test3_hasheddrops_vs_bff_consensuscall_donor_match.csv", "donor_match/test3/hasheddrops_vs_bff_raw", - "donor_match/test3/hasheddrops_vs_bff_raw/test3_hasheddrops_vs_bff_raw_concordance_heatmap.png", "donor_match/test3/hasheddrops_vs_bff_raw/test3_hasheddrops_vs_bff_raw_correlation_res.csv", "donor_match/test3/hasheddrops_vs_bff_raw/test3_hasheddrops_vs_bff_raw_donor_match.csv", "donor_match/test3/hasheddrops_vs_gmmdemux", - "donor_match/test3/hasheddrops_vs_gmmdemux/test3_hasheddrops_vs_gmmdemux_concordance_heatmap.png", "donor_match/test3/hasheddrops_vs_gmmdemux/test3_hasheddrops_vs_gmmdemux_correlation_res.csv", "donor_match/test3/hasheddrops_vs_gmmdemux/test3_hasheddrops_vs_gmmdemux_donor_match.csv", "donor_match/test3/hasheddrops_vs_htodemux", - "donor_match/test3/hasheddrops_vs_htodemux/test3_hasheddrops_vs_htodemux_concordance_heatmap.png", "donor_match/test3/hasheddrops_vs_htodemux/test3_hasheddrops_vs_htodemux_correlation_res.csv", "donor_match/test3/hasheddrops_vs_htodemux/test3_hasheddrops_vs_htodemux_donor_match.csv", "donor_match/test3/hasheddrops_vs_multiseq", - "donor_match/test3/hasheddrops_vs_multiseq/test3_hasheddrops_vs_multiseq_concordance_heatmap.png", "donor_match/test3/hasheddrops_vs_multiseq/test3_hasheddrops_vs_multiseq_correlation_res.csv", "donor_match/test3/hasheddrops_vs_multiseq/test3_hasheddrops_vs_multiseq_donor_match.csv", "donor_match/test3/hashsolo_vs_bff_consensuscall", - "donor_match/test3/hashsolo_vs_bff_consensuscall/test3_hashsolo_vs_bff_consensuscall_concordance_heatmap.png", "donor_match/test3/hashsolo_vs_bff_consensuscall/test3_hashsolo_vs_bff_consensuscall_correlation_res.csv", "donor_match/test3/hashsolo_vs_bff_consensuscall/test3_hashsolo_vs_bff_consensuscall_donor_match.csv", "donor_match/test3/hashsolo_vs_bff_raw", - "donor_match/test3/hashsolo_vs_bff_raw/test3_hashsolo_vs_bff_raw_concordance_heatmap.png", "donor_match/test3/hashsolo_vs_bff_raw/test3_hashsolo_vs_bff_raw_correlation_res.csv", "donor_match/test3/hashsolo_vs_bff_raw/test3_hashsolo_vs_bff_raw_donor_match.csv", "donor_match/test3/hashsolo_vs_gmmdemux", - "donor_match/test3/hashsolo_vs_gmmdemux/test3_hashsolo_vs_gmmdemux_concordance_heatmap.png", "donor_match/test3/hashsolo_vs_gmmdemux/test3_hashsolo_vs_gmmdemux_correlation_res.csv", "donor_match/test3/hashsolo_vs_gmmdemux/test3_hashsolo_vs_gmmdemux_donor_match.csv", "donor_match/test3/hashsolo_vs_hasheddrops", - "donor_match/test3/hashsolo_vs_hasheddrops/test3_hashsolo_vs_hasheddrops_concordance_heatmap.png", "donor_match/test3/hashsolo_vs_hasheddrops/test3_hashsolo_vs_hasheddrops_correlation_res.csv", "donor_match/test3/hashsolo_vs_hasheddrops/test3_hashsolo_vs_hasheddrops_donor_match.csv", "donor_match/test3/hashsolo_vs_htodemux", - "donor_match/test3/hashsolo_vs_htodemux/test3_hashsolo_vs_htodemux_concordance_heatmap.png", "donor_match/test3/hashsolo_vs_htodemux/test3_hashsolo_vs_htodemux_correlation_res.csv", "donor_match/test3/hashsolo_vs_htodemux/test3_hashsolo_vs_htodemux_donor_match.csv", "donor_match/test3/hashsolo_vs_multiseq", - "donor_match/test3/hashsolo_vs_multiseq/test3_hashsolo_vs_multiseq_concordance_heatmap.png", "donor_match/test3/hashsolo_vs_multiseq/test3_hashsolo_vs_multiseq_correlation_res.csv", "donor_match/test3/hashsolo_vs_multiseq/test3_hashsolo_vs_multiseq_donor_match.csv", "donor_match/test3/htodemux_vs_bff_consensuscall", - "donor_match/test3/htodemux_vs_bff_consensuscall/test3_htodemux_vs_bff_consensuscall_concordance_heatmap.png", "donor_match/test3/htodemux_vs_bff_consensuscall/test3_htodemux_vs_bff_consensuscall_correlation_res.csv", "donor_match/test3/htodemux_vs_bff_consensuscall/test3_htodemux_vs_bff_consensuscall_donor_match.csv", "donor_match/test3/htodemux_vs_bff_raw", - "donor_match/test3/htodemux_vs_bff_raw/test3_htodemux_vs_bff_raw_concordance_heatmap.png", "donor_match/test3/htodemux_vs_bff_raw/test3_htodemux_vs_bff_raw_correlation_res.csv", "donor_match/test3/htodemux_vs_bff_raw/test3_htodemux_vs_bff_raw_donor_match.csv", "donor_match/test3/htodemux_vs_gmmdemux", - "donor_match/test3/htodemux_vs_gmmdemux/test3_htodemux_vs_gmmdemux_concordance_heatmap.png", "donor_match/test3/htodemux_vs_gmmdemux/test3_htodemux_vs_gmmdemux_correlation_res.csv", "donor_match/test3/htodemux_vs_gmmdemux/test3_htodemux_vs_gmmdemux_donor_match.csv", "donor_match/test3/multiseq_vs_bff_consensuscall", - "donor_match/test3/multiseq_vs_bff_consensuscall/test3_multiseq_vs_bff_consensuscall_concordance_heatmap.png", "donor_match/test3/multiseq_vs_bff_consensuscall/test3_multiseq_vs_bff_consensuscall_correlation_res.csv", "donor_match/test3/multiseq_vs_bff_consensuscall/test3_multiseq_vs_bff_consensuscall_donor_match.csv", "donor_match/test3/multiseq_vs_bff_raw", - "donor_match/test3/multiseq_vs_bff_raw/test3_multiseq_vs_bff_raw_concordance_heatmap.png", "donor_match/test3/multiseq_vs_bff_raw/test3_multiseq_vs_bff_raw_correlation_res.csv", "donor_match/test3/multiseq_vs_bff_raw/test3_multiseq_vs_bff_raw_donor_match.csv", "donor_match/test3/multiseq_vs_gmmdemux", - "donor_match/test3/multiseq_vs_gmmdemux/test3_multiseq_vs_gmmdemux_concordance_heatmap.png", "donor_match/test3/multiseq_vs_gmmdemux/test3_multiseq_vs_gmmdemux_correlation_res.csv", "donor_match/test3/multiseq_vs_gmmdemux/test3_multiseq_vs_gmmdemux_donor_match.csv", "donor_match/test3/multiseq_vs_htodemux", - "donor_match/test3/multiseq_vs_htodemux/test3_multiseq_vs_htodemux_concordance_heatmap.png", "donor_match/test3/multiseq_vs_htodemux/test3_multiseq_vs_htodemux_correlation_res.csv", "donor_match/test3/multiseq_vs_htodemux/test3_multiseq_vs_htodemux_donor_match.csv", "hashing", @@ -481,30 +397,24 @@ "hashing/hasheddrops", "hashing/hasheddrops/test1", "hashing/hasheddrops/test1/test1_emptyDrops.csv", - "hashing/hasheddrops/test1/test1_emptyDrops.png", "hashing/hasheddrops/test1/test1_emptyDrops.rds", "hashing/hasheddrops/test1/test1_hasheddrops.rds", "hashing/hasheddrops/test1/test1_id_to_hash.csv", "hashing/hasheddrops/test1/test1_params_hasheddrops.csv", - "hashing/hasheddrops/test1/test1_plot_hasheddrops.png", "hashing/hasheddrops/test1/test1_results_hasheddrops.csv", "hashing/hasheddrops/test2", "hashing/hasheddrops/test2/test2_emptyDrops.csv", - "hashing/hasheddrops/test2/test2_emptyDrops.png", "hashing/hasheddrops/test2/test2_emptyDrops.rds", "hashing/hasheddrops/test2/test2_hasheddrops.rds", "hashing/hasheddrops/test2/test2_id_to_hash.csv", "hashing/hasheddrops/test2/test2_params_hasheddrops.csv", - "hashing/hasheddrops/test2/test2_plot_hasheddrops.png", "hashing/hasheddrops/test2/test2_results_hasheddrops.csv", "hashing/hasheddrops/test3", "hashing/hasheddrops/test3/test3_emptyDrops.csv", - "hashing/hasheddrops/test3/test3_emptyDrops.png", "hashing/hasheddrops/test3/test3_emptyDrops.rds", "hashing/hasheddrops/test3/test3_hasheddrops.rds", "hashing/hasheddrops/test3/test3_id_to_hash.csv", "hashing/hasheddrops/test3/test3_params_hasheddrops.csv", - "hashing/hasheddrops/test3/test3_plot_hasheddrops.png", "hashing/hasheddrops/test3/test3_results_hasheddrops.csv", "hashing/hashsolo", "hashing/hashsolo/test1", @@ -526,11 +436,6 @@ "hashing/htodemux/test1/test1_htodemux.rds", "hashing/htodemux/test1/test1_params_htodemux.csv", "hashing/htodemux/test1/visualization", - "hashing/htodemux/test1/visualization/test1_featureScatter_htodemux.jpeg", - "hashing/htodemux/test1/visualization/test1_heatMap_htodemux.jpeg", - "hashing/htodemux/test1/visualization/test1_ridge_htodemux.jpeg", - "hashing/htodemux/test1/visualization/test1_tSNE_htodemux.jpeg", - "hashing/htodemux/test1/visualization/test1_violinPlot_htodemux.jpeg", "hashing/htodemux/test1/visualization/test1_visual_params_htodemux.csv", "hashing/htodemux/test2", "hashing/htodemux/test2/test2_assignment_htodemux.csv", @@ -538,11 +443,6 @@ "hashing/htodemux/test2/test2_htodemux.rds", "hashing/htodemux/test2/test2_params_htodemux.csv", "hashing/htodemux/test2/visualization", - "hashing/htodemux/test2/visualization/test2_featureScatter_htodemux.jpeg", - "hashing/htodemux/test2/visualization/test2_heatMap_htodemux.jpeg", - "hashing/htodemux/test2/visualization/test2_ridge_htodemux.jpeg", - "hashing/htodemux/test2/visualization/test2_tSNE_htodemux.jpeg", - "hashing/htodemux/test2/visualization/test2_violinPlot_htodemux.jpeg", "hashing/htodemux/test2/visualization/test2_visual_params_htodemux.csv", "hashing/htodemux/test3", "hashing/htodemux/test3/test3_assignment_htodemux.csv", @@ -550,11 +450,6 @@ "hashing/htodemux/test3/test3_htodemux.rds", "hashing/htodemux/test3/test3_params_htodemux.csv", "hashing/htodemux/test3/visualization", - "hashing/htodemux/test3/visualization/test3_featureScatter_htodemux.jpeg", - "hashing/htodemux/test3/visualization/test3_heatMap_htodemux.jpeg", - "hashing/htodemux/test3/visualization/test3_ridge_htodemux.jpeg", - "hashing/htodemux/test3/visualization/test3_tSNE_htodemux.jpeg", - "hashing/htodemux/test3/visualization/test3_violinPlot_htodemux.jpeg", "hashing/htodemux/test3/visualization/test3_visual_params_htodemux.csv", "hashing/multiseqdemux", "hashing/multiseqdemux/test1", @@ -612,256 +507,172 @@ "summary/test3/test3_hashing.h5ad" ], [ - "test1_bff_raw_vs_bff_consensuscall_concordance_heatmap.png:md5,70228780e63708c0255b9fefd17af97f", "test1_bff_raw_vs_bff_consensuscall_correlation_res.csv:md5,fd2c1ab13a371736d4ce2210fdbbb49a", "test1_bff_raw_vs_bff_consensuscall_donor_match.csv:md5,3dd18b42dded6bc972f54f877b62d744", - "test1_demuxem_vs_bff_consensuscall_concordance_heatmap.png:md5,70228780e63708c0255b9fefd17af97f", "test1_demuxem_vs_bff_consensuscall_correlation_res.csv:md5,fd2c1ab13a371736d4ce2210fdbbb49a", "test1_demuxem_vs_bff_consensuscall_donor_match.csv:md5,3dd18b42dded6bc972f54f877b62d744", - "test1_demuxem_vs_bff_raw_concordance_heatmap.png:md5,70228780e63708c0255b9fefd17af97f", "test1_demuxem_vs_bff_raw_correlation_res.csv:md5,fd2c1ab13a371736d4ce2210fdbbb49a", "test1_demuxem_vs_bff_raw_donor_match.csv:md5,3dd18b42dded6bc972f54f877b62d744", - "test1_demuxem_vs_gmmdemux_concordance_heatmap.png:md5,70228780e63708c0255b9fefd17af97f", "test1_demuxem_vs_gmmdemux_correlation_res.csv:md5,fd2c1ab13a371736d4ce2210fdbbb49a", "test1_demuxem_vs_gmmdemux_donor_match.csv:md5,3dd18b42dded6bc972f54f877b62d744", - "test1_demuxem_vs_hasheddrops_concordance_heatmap.png:md5,70228780e63708c0255b9fefd17af97f", "test1_demuxem_vs_hasheddrops_correlation_res.csv:md5,fd2c1ab13a371736d4ce2210fdbbb49a", "test1_demuxem_vs_hasheddrops_donor_match.csv:md5,3dd18b42dded6bc972f54f877b62d744", - "test1_demuxem_vs_hashsolo_concordance_heatmap.png:md5,70228780e63708c0255b9fefd17af97f", "test1_demuxem_vs_hashsolo_correlation_res.csv:md5,fd2c1ab13a371736d4ce2210fdbbb49a", "test1_demuxem_vs_hashsolo_donor_match.csv:md5,3dd18b42dded6bc972f54f877b62d744", - "test1_demuxem_vs_htodemux_concordance_heatmap.png:md5,70228780e63708c0255b9fefd17af97f", "test1_demuxem_vs_htodemux_correlation_res.csv:md5,fd2c1ab13a371736d4ce2210fdbbb49a", "test1_demuxem_vs_htodemux_donor_match.csv:md5,3dd18b42dded6bc972f54f877b62d744", - "test1_demuxem_vs_multiseq_concordance_heatmap.png:md5,70228780e63708c0255b9fefd17af97f", "test1_demuxem_vs_multiseq_correlation_res.csv:md5,fd2c1ab13a371736d4ce2210fdbbb49a", "test1_demuxem_vs_multiseq_donor_match.csv:md5,3dd18b42dded6bc972f54f877b62d744", - "test1_gmmdemux_vs_bff_consensuscall_concordance_heatmap.png:md5,70228780e63708c0255b9fefd17af97f", "test1_gmmdemux_vs_bff_consensuscall_correlation_res.csv:md5,fd2c1ab13a371736d4ce2210fdbbb49a", "test1_gmmdemux_vs_bff_consensuscall_donor_match.csv:md5,3dd18b42dded6bc972f54f877b62d744", - "test1_gmmdemux_vs_bff_raw_concordance_heatmap.png:md5,70228780e63708c0255b9fefd17af97f", "test1_gmmdemux_vs_bff_raw_correlation_res.csv:md5,fd2c1ab13a371736d4ce2210fdbbb49a", "test1_gmmdemux_vs_bff_raw_donor_match.csv:md5,3dd18b42dded6bc972f54f877b62d744", - "test1_hasheddrops_vs_bff_consensuscall_concordance_heatmap.png:md5,70228780e63708c0255b9fefd17af97f", "test1_hasheddrops_vs_bff_consensuscall_correlation_res.csv:md5,fd2c1ab13a371736d4ce2210fdbbb49a", "test1_hasheddrops_vs_bff_consensuscall_donor_match.csv:md5,3dd18b42dded6bc972f54f877b62d744", - "test1_hasheddrops_vs_bff_raw_concordance_heatmap.png:md5,70228780e63708c0255b9fefd17af97f", "test1_hasheddrops_vs_bff_raw_correlation_res.csv:md5,fd2c1ab13a371736d4ce2210fdbbb49a", "test1_hasheddrops_vs_bff_raw_donor_match.csv:md5,3dd18b42dded6bc972f54f877b62d744", - "test1_hasheddrops_vs_gmmdemux_concordance_heatmap.png:md5,70228780e63708c0255b9fefd17af97f", "test1_hasheddrops_vs_gmmdemux_correlation_res.csv:md5,fd2c1ab13a371736d4ce2210fdbbb49a", "test1_hasheddrops_vs_gmmdemux_donor_match.csv:md5,3dd18b42dded6bc972f54f877b62d744", - "test1_hasheddrops_vs_htodemux_concordance_heatmap.png:md5,70228780e63708c0255b9fefd17af97f", "test1_hasheddrops_vs_htodemux_correlation_res.csv:md5,fd2c1ab13a371736d4ce2210fdbbb49a", "test1_hasheddrops_vs_htodemux_donor_match.csv:md5,3dd18b42dded6bc972f54f877b62d744", - "test1_hasheddrops_vs_multiseq_concordance_heatmap.png:md5,70228780e63708c0255b9fefd17af97f", "test1_hasheddrops_vs_multiseq_correlation_res.csv:md5,fd2c1ab13a371736d4ce2210fdbbb49a", "test1_hasheddrops_vs_multiseq_donor_match.csv:md5,3dd18b42dded6bc972f54f877b62d744", - "test1_hashsolo_vs_bff_consensuscall_concordance_heatmap.png:md5,70228780e63708c0255b9fefd17af97f", "test1_hashsolo_vs_bff_consensuscall_correlation_res.csv:md5,fd2c1ab13a371736d4ce2210fdbbb49a", "test1_hashsolo_vs_bff_consensuscall_donor_match.csv:md5,3dd18b42dded6bc972f54f877b62d744", - "test1_hashsolo_vs_bff_raw_concordance_heatmap.png:md5,70228780e63708c0255b9fefd17af97f", "test1_hashsolo_vs_bff_raw_correlation_res.csv:md5,fd2c1ab13a371736d4ce2210fdbbb49a", "test1_hashsolo_vs_bff_raw_donor_match.csv:md5,3dd18b42dded6bc972f54f877b62d744", - "test1_hashsolo_vs_gmmdemux_concordance_heatmap.png:md5,70228780e63708c0255b9fefd17af97f", "test1_hashsolo_vs_gmmdemux_correlation_res.csv:md5,fd2c1ab13a371736d4ce2210fdbbb49a", "test1_hashsolo_vs_gmmdemux_donor_match.csv:md5,3dd18b42dded6bc972f54f877b62d744", - "test1_hashsolo_vs_hasheddrops_concordance_heatmap.png:md5,70228780e63708c0255b9fefd17af97f", "test1_hashsolo_vs_hasheddrops_correlation_res.csv:md5,fd2c1ab13a371736d4ce2210fdbbb49a", "test1_hashsolo_vs_hasheddrops_donor_match.csv:md5,3dd18b42dded6bc972f54f877b62d744", - "test1_hashsolo_vs_htodemux_concordance_heatmap.png:md5,70228780e63708c0255b9fefd17af97f", "test1_hashsolo_vs_htodemux_correlation_res.csv:md5,fd2c1ab13a371736d4ce2210fdbbb49a", "test1_hashsolo_vs_htodemux_donor_match.csv:md5,3dd18b42dded6bc972f54f877b62d744", - "test1_hashsolo_vs_multiseq_concordance_heatmap.png:md5,70228780e63708c0255b9fefd17af97f", "test1_hashsolo_vs_multiseq_correlation_res.csv:md5,fd2c1ab13a371736d4ce2210fdbbb49a", "test1_hashsolo_vs_multiseq_donor_match.csv:md5,3dd18b42dded6bc972f54f877b62d744", - "test1_htodemux_vs_bff_consensuscall_concordance_heatmap.png:md5,70228780e63708c0255b9fefd17af97f", "test1_htodemux_vs_bff_consensuscall_correlation_res.csv:md5,fd2c1ab13a371736d4ce2210fdbbb49a", "test1_htodemux_vs_bff_consensuscall_donor_match.csv:md5,3dd18b42dded6bc972f54f877b62d744", - "test1_htodemux_vs_bff_raw_concordance_heatmap.png:md5,70228780e63708c0255b9fefd17af97f", "test1_htodemux_vs_bff_raw_correlation_res.csv:md5,fd2c1ab13a371736d4ce2210fdbbb49a", "test1_htodemux_vs_bff_raw_donor_match.csv:md5,3dd18b42dded6bc972f54f877b62d744", - "test1_htodemux_vs_gmmdemux_concordance_heatmap.png:md5,70228780e63708c0255b9fefd17af97f", "test1_htodemux_vs_gmmdemux_correlation_res.csv:md5,fd2c1ab13a371736d4ce2210fdbbb49a", "test1_htodemux_vs_gmmdemux_donor_match.csv:md5,3dd18b42dded6bc972f54f877b62d744", - "test1_multiseq_vs_bff_consensuscall_concordance_heatmap.png:md5,70228780e63708c0255b9fefd17af97f", "test1_multiseq_vs_bff_consensuscall_correlation_res.csv:md5,fd2c1ab13a371736d4ce2210fdbbb49a", "test1_multiseq_vs_bff_consensuscall_donor_match.csv:md5,3dd18b42dded6bc972f54f877b62d744", - "test1_multiseq_vs_bff_raw_concordance_heatmap.png:md5,70228780e63708c0255b9fefd17af97f", "test1_multiseq_vs_bff_raw_correlation_res.csv:md5,fd2c1ab13a371736d4ce2210fdbbb49a", "test1_multiseq_vs_bff_raw_donor_match.csv:md5,3dd18b42dded6bc972f54f877b62d744", - "test1_multiseq_vs_gmmdemux_concordance_heatmap.png:md5,70228780e63708c0255b9fefd17af97f", "test1_multiseq_vs_gmmdemux_correlation_res.csv:md5,fd2c1ab13a371736d4ce2210fdbbb49a", "test1_multiseq_vs_gmmdemux_donor_match.csv:md5,3dd18b42dded6bc972f54f877b62d744", - "test1_multiseq_vs_htodemux_concordance_heatmap.png:md5,70228780e63708c0255b9fefd17af97f", "test1_multiseq_vs_htodemux_correlation_res.csv:md5,fd2c1ab13a371736d4ce2210fdbbb49a", "test1_multiseq_vs_htodemux_donor_match.csv:md5,3dd18b42dded6bc972f54f877b62d744", - "test2_bff_raw_vs_bff_consensuscall_concordance_heatmap.png:md5,70228780e63708c0255b9fefd17af97f", "test2_bff_raw_vs_bff_consensuscall_correlation_res.csv:md5,fd2c1ab13a371736d4ce2210fdbbb49a", "test2_bff_raw_vs_bff_consensuscall_donor_match.csv:md5,3dd18b42dded6bc972f54f877b62d744", - "test2_demuxem_vs_bff_consensuscall_concordance_heatmap.png:md5,70228780e63708c0255b9fefd17af97f", "test2_demuxem_vs_bff_consensuscall_correlation_res.csv:md5,fd2c1ab13a371736d4ce2210fdbbb49a", "test2_demuxem_vs_bff_consensuscall_donor_match.csv:md5,3dd18b42dded6bc972f54f877b62d744", - "test2_demuxem_vs_bff_raw_concordance_heatmap.png:md5,70228780e63708c0255b9fefd17af97f", "test2_demuxem_vs_bff_raw_correlation_res.csv:md5,fd2c1ab13a371736d4ce2210fdbbb49a", "test2_demuxem_vs_bff_raw_donor_match.csv:md5,3dd18b42dded6bc972f54f877b62d744", - "test2_demuxem_vs_gmmdemux_concordance_heatmap.png:md5,70228780e63708c0255b9fefd17af97f", "test2_demuxem_vs_gmmdemux_correlation_res.csv:md5,fd2c1ab13a371736d4ce2210fdbbb49a", "test2_demuxem_vs_gmmdemux_donor_match.csv:md5,3dd18b42dded6bc972f54f877b62d744", - "test2_demuxem_vs_hasheddrops_concordance_heatmap.png:md5,70228780e63708c0255b9fefd17af97f", "test2_demuxem_vs_hasheddrops_correlation_res.csv:md5,fd2c1ab13a371736d4ce2210fdbbb49a", "test2_demuxem_vs_hasheddrops_donor_match.csv:md5,3dd18b42dded6bc972f54f877b62d744", - "test2_demuxem_vs_hashsolo_concordance_heatmap.png:md5,70228780e63708c0255b9fefd17af97f", "test2_demuxem_vs_hashsolo_correlation_res.csv:md5,fd2c1ab13a371736d4ce2210fdbbb49a", "test2_demuxem_vs_hashsolo_donor_match.csv:md5,3dd18b42dded6bc972f54f877b62d744", - "test2_demuxem_vs_htodemux_concordance_heatmap.png:md5,70228780e63708c0255b9fefd17af97f", "test2_demuxem_vs_htodemux_correlation_res.csv:md5,fd2c1ab13a371736d4ce2210fdbbb49a", "test2_demuxem_vs_htodemux_donor_match.csv:md5,3dd18b42dded6bc972f54f877b62d744", - "test2_demuxem_vs_multiseq_concordance_heatmap.png:md5,70228780e63708c0255b9fefd17af97f", "test2_demuxem_vs_multiseq_correlation_res.csv:md5,fd2c1ab13a371736d4ce2210fdbbb49a", "test2_demuxem_vs_multiseq_donor_match.csv:md5,3dd18b42dded6bc972f54f877b62d744", - "test2_gmmdemux_vs_bff_consensuscall_concordance_heatmap.png:md5,70228780e63708c0255b9fefd17af97f", "test2_gmmdemux_vs_bff_consensuscall_correlation_res.csv:md5,fd2c1ab13a371736d4ce2210fdbbb49a", "test2_gmmdemux_vs_bff_consensuscall_donor_match.csv:md5,3dd18b42dded6bc972f54f877b62d744", - "test2_gmmdemux_vs_bff_raw_concordance_heatmap.png:md5,70228780e63708c0255b9fefd17af97f", "test2_gmmdemux_vs_bff_raw_correlation_res.csv:md5,fd2c1ab13a371736d4ce2210fdbbb49a", "test2_gmmdemux_vs_bff_raw_donor_match.csv:md5,3dd18b42dded6bc972f54f877b62d744", - "test2_hasheddrops_vs_bff_consensuscall_concordance_heatmap.png:md5,70228780e63708c0255b9fefd17af97f", "test2_hasheddrops_vs_bff_consensuscall_correlation_res.csv:md5,fd2c1ab13a371736d4ce2210fdbbb49a", "test2_hasheddrops_vs_bff_consensuscall_donor_match.csv:md5,3dd18b42dded6bc972f54f877b62d744", - "test2_hasheddrops_vs_bff_raw_concordance_heatmap.png:md5,70228780e63708c0255b9fefd17af97f", "test2_hasheddrops_vs_bff_raw_correlation_res.csv:md5,fd2c1ab13a371736d4ce2210fdbbb49a", "test2_hasheddrops_vs_bff_raw_donor_match.csv:md5,3dd18b42dded6bc972f54f877b62d744", - "test2_hasheddrops_vs_gmmdemux_concordance_heatmap.png:md5,70228780e63708c0255b9fefd17af97f", "test2_hasheddrops_vs_gmmdemux_correlation_res.csv:md5,fd2c1ab13a371736d4ce2210fdbbb49a", "test2_hasheddrops_vs_gmmdemux_donor_match.csv:md5,3dd18b42dded6bc972f54f877b62d744", - "test2_hasheddrops_vs_htodemux_concordance_heatmap.png:md5,70228780e63708c0255b9fefd17af97f", "test2_hasheddrops_vs_htodemux_correlation_res.csv:md5,fd2c1ab13a371736d4ce2210fdbbb49a", "test2_hasheddrops_vs_htodemux_donor_match.csv:md5,3dd18b42dded6bc972f54f877b62d744", - "test2_hasheddrops_vs_multiseq_concordance_heatmap.png:md5,70228780e63708c0255b9fefd17af97f", "test2_hasheddrops_vs_multiseq_correlation_res.csv:md5,fd2c1ab13a371736d4ce2210fdbbb49a", "test2_hasheddrops_vs_multiseq_donor_match.csv:md5,3dd18b42dded6bc972f54f877b62d744", - "test2_hashsolo_vs_bff_consensuscall_concordance_heatmap.png:md5,70228780e63708c0255b9fefd17af97f", "test2_hashsolo_vs_bff_consensuscall_correlation_res.csv:md5,fd2c1ab13a371736d4ce2210fdbbb49a", "test2_hashsolo_vs_bff_consensuscall_donor_match.csv:md5,3dd18b42dded6bc972f54f877b62d744", - "test2_hashsolo_vs_bff_raw_concordance_heatmap.png:md5,70228780e63708c0255b9fefd17af97f", "test2_hashsolo_vs_bff_raw_correlation_res.csv:md5,fd2c1ab13a371736d4ce2210fdbbb49a", "test2_hashsolo_vs_bff_raw_donor_match.csv:md5,3dd18b42dded6bc972f54f877b62d744", - "test2_hashsolo_vs_gmmdemux_concordance_heatmap.png:md5,70228780e63708c0255b9fefd17af97f", "test2_hashsolo_vs_gmmdemux_correlation_res.csv:md5,fd2c1ab13a371736d4ce2210fdbbb49a", "test2_hashsolo_vs_gmmdemux_donor_match.csv:md5,3dd18b42dded6bc972f54f877b62d744", - "test2_hashsolo_vs_hasheddrops_concordance_heatmap.png:md5,70228780e63708c0255b9fefd17af97f", "test2_hashsolo_vs_hasheddrops_correlation_res.csv:md5,fd2c1ab13a371736d4ce2210fdbbb49a", "test2_hashsolo_vs_hasheddrops_donor_match.csv:md5,3dd18b42dded6bc972f54f877b62d744", - "test2_hashsolo_vs_htodemux_concordance_heatmap.png:md5,70228780e63708c0255b9fefd17af97f", "test2_hashsolo_vs_htodemux_correlation_res.csv:md5,fd2c1ab13a371736d4ce2210fdbbb49a", "test2_hashsolo_vs_htodemux_donor_match.csv:md5,3dd18b42dded6bc972f54f877b62d744", - "test2_hashsolo_vs_multiseq_concordance_heatmap.png:md5,70228780e63708c0255b9fefd17af97f", "test2_hashsolo_vs_multiseq_correlation_res.csv:md5,fd2c1ab13a371736d4ce2210fdbbb49a", "test2_hashsolo_vs_multiseq_donor_match.csv:md5,3dd18b42dded6bc972f54f877b62d744", - "test2_htodemux_vs_bff_consensuscall_concordance_heatmap.png:md5,70228780e63708c0255b9fefd17af97f", "test2_htodemux_vs_bff_consensuscall_correlation_res.csv:md5,fd2c1ab13a371736d4ce2210fdbbb49a", "test2_htodemux_vs_bff_consensuscall_donor_match.csv:md5,3dd18b42dded6bc972f54f877b62d744", - "test2_htodemux_vs_bff_raw_concordance_heatmap.png:md5,70228780e63708c0255b9fefd17af97f", "test2_htodemux_vs_bff_raw_correlation_res.csv:md5,fd2c1ab13a371736d4ce2210fdbbb49a", "test2_htodemux_vs_bff_raw_donor_match.csv:md5,3dd18b42dded6bc972f54f877b62d744", - "test2_htodemux_vs_gmmdemux_concordance_heatmap.png:md5,70228780e63708c0255b9fefd17af97f", "test2_htodemux_vs_gmmdemux_correlation_res.csv:md5,fd2c1ab13a371736d4ce2210fdbbb49a", "test2_htodemux_vs_gmmdemux_donor_match.csv:md5,3dd18b42dded6bc972f54f877b62d744", - "test2_multiseq_vs_bff_consensuscall_concordance_heatmap.png:md5,70228780e63708c0255b9fefd17af97f", "test2_multiseq_vs_bff_consensuscall_correlation_res.csv:md5,fd2c1ab13a371736d4ce2210fdbbb49a", "test2_multiseq_vs_bff_consensuscall_donor_match.csv:md5,3dd18b42dded6bc972f54f877b62d744", - "test2_multiseq_vs_bff_raw_concordance_heatmap.png:md5,70228780e63708c0255b9fefd17af97f", "test2_multiseq_vs_bff_raw_correlation_res.csv:md5,fd2c1ab13a371736d4ce2210fdbbb49a", "test2_multiseq_vs_bff_raw_donor_match.csv:md5,3dd18b42dded6bc972f54f877b62d744", - "test2_multiseq_vs_gmmdemux_concordance_heatmap.png:md5,70228780e63708c0255b9fefd17af97f", "test2_multiseq_vs_gmmdemux_correlation_res.csv:md5,fd2c1ab13a371736d4ce2210fdbbb49a", "test2_multiseq_vs_gmmdemux_donor_match.csv:md5,3dd18b42dded6bc972f54f877b62d744", - "test2_multiseq_vs_htodemux_concordance_heatmap.png:md5,70228780e63708c0255b9fefd17af97f", "test2_multiseq_vs_htodemux_correlation_res.csv:md5,fd2c1ab13a371736d4ce2210fdbbb49a", "test2_multiseq_vs_htodemux_donor_match.csv:md5,3dd18b42dded6bc972f54f877b62d744", - "test3_bff_raw_vs_bff_consensuscall_concordance_heatmap.png:md5,70228780e63708c0255b9fefd17af97f", "test3_bff_raw_vs_bff_consensuscall_correlation_res.csv:md5,fd2c1ab13a371736d4ce2210fdbbb49a", "test3_bff_raw_vs_bff_consensuscall_donor_match.csv:md5,3dd18b42dded6bc972f54f877b62d744", - "test3_demuxem_vs_bff_consensuscall_concordance_heatmap.png:md5,70228780e63708c0255b9fefd17af97f", "test3_demuxem_vs_bff_consensuscall_correlation_res.csv:md5,fd2c1ab13a371736d4ce2210fdbbb49a", "test3_demuxem_vs_bff_consensuscall_donor_match.csv:md5,3dd18b42dded6bc972f54f877b62d744", - "test3_demuxem_vs_bff_raw_concordance_heatmap.png:md5,70228780e63708c0255b9fefd17af97f", "test3_demuxem_vs_bff_raw_correlation_res.csv:md5,fd2c1ab13a371736d4ce2210fdbbb49a", "test3_demuxem_vs_bff_raw_donor_match.csv:md5,3dd18b42dded6bc972f54f877b62d744", - "test3_demuxem_vs_gmmdemux_concordance_heatmap.png:md5,70228780e63708c0255b9fefd17af97f", "test3_demuxem_vs_gmmdemux_correlation_res.csv:md5,fd2c1ab13a371736d4ce2210fdbbb49a", "test3_demuxem_vs_gmmdemux_donor_match.csv:md5,3dd18b42dded6bc972f54f877b62d744", - "test3_demuxem_vs_hasheddrops_concordance_heatmap.png:md5,70228780e63708c0255b9fefd17af97f", "test3_demuxem_vs_hasheddrops_correlation_res.csv:md5,fd2c1ab13a371736d4ce2210fdbbb49a", "test3_demuxem_vs_hasheddrops_donor_match.csv:md5,3dd18b42dded6bc972f54f877b62d744", - "test3_demuxem_vs_hashsolo_concordance_heatmap.png:md5,70228780e63708c0255b9fefd17af97f", "test3_demuxem_vs_hashsolo_correlation_res.csv:md5,fd2c1ab13a371736d4ce2210fdbbb49a", "test3_demuxem_vs_hashsolo_donor_match.csv:md5,3dd18b42dded6bc972f54f877b62d744", - "test3_demuxem_vs_htodemux_concordance_heatmap.png:md5,70228780e63708c0255b9fefd17af97f", "test3_demuxem_vs_htodemux_correlation_res.csv:md5,fd2c1ab13a371736d4ce2210fdbbb49a", "test3_demuxem_vs_htodemux_donor_match.csv:md5,3dd18b42dded6bc972f54f877b62d744", - "test3_demuxem_vs_multiseq_concordance_heatmap.png:md5,70228780e63708c0255b9fefd17af97f", "test3_demuxem_vs_multiseq_correlation_res.csv:md5,fd2c1ab13a371736d4ce2210fdbbb49a", "test3_demuxem_vs_multiseq_donor_match.csv:md5,3dd18b42dded6bc972f54f877b62d744", - "test3_gmmdemux_vs_bff_consensuscall_concordance_heatmap.png:md5,70228780e63708c0255b9fefd17af97f", "test3_gmmdemux_vs_bff_consensuscall_correlation_res.csv:md5,fd2c1ab13a371736d4ce2210fdbbb49a", "test3_gmmdemux_vs_bff_consensuscall_donor_match.csv:md5,3dd18b42dded6bc972f54f877b62d744", - "test3_gmmdemux_vs_bff_raw_concordance_heatmap.png:md5,70228780e63708c0255b9fefd17af97f", "test3_gmmdemux_vs_bff_raw_correlation_res.csv:md5,fd2c1ab13a371736d4ce2210fdbbb49a", "test3_gmmdemux_vs_bff_raw_donor_match.csv:md5,3dd18b42dded6bc972f54f877b62d744", - "test3_hasheddrops_vs_bff_consensuscall_concordance_heatmap.png:md5,70228780e63708c0255b9fefd17af97f", "test3_hasheddrops_vs_bff_consensuscall_correlation_res.csv:md5,fd2c1ab13a371736d4ce2210fdbbb49a", "test3_hasheddrops_vs_bff_consensuscall_donor_match.csv:md5,3dd18b42dded6bc972f54f877b62d744", - "test3_hasheddrops_vs_bff_raw_concordance_heatmap.png:md5,70228780e63708c0255b9fefd17af97f", "test3_hasheddrops_vs_bff_raw_correlation_res.csv:md5,fd2c1ab13a371736d4ce2210fdbbb49a", "test3_hasheddrops_vs_bff_raw_donor_match.csv:md5,3dd18b42dded6bc972f54f877b62d744", - "test3_hasheddrops_vs_gmmdemux_concordance_heatmap.png:md5,70228780e63708c0255b9fefd17af97f", "test3_hasheddrops_vs_gmmdemux_correlation_res.csv:md5,fd2c1ab13a371736d4ce2210fdbbb49a", "test3_hasheddrops_vs_gmmdemux_donor_match.csv:md5,3dd18b42dded6bc972f54f877b62d744", - "test3_hasheddrops_vs_htodemux_concordance_heatmap.png:md5,70228780e63708c0255b9fefd17af97f", "test3_hasheddrops_vs_htodemux_correlation_res.csv:md5,fd2c1ab13a371736d4ce2210fdbbb49a", "test3_hasheddrops_vs_htodemux_donor_match.csv:md5,3dd18b42dded6bc972f54f877b62d744", - "test3_hasheddrops_vs_multiseq_concordance_heatmap.png:md5,70228780e63708c0255b9fefd17af97f", "test3_hasheddrops_vs_multiseq_correlation_res.csv:md5,fd2c1ab13a371736d4ce2210fdbbb49a", "test3_hasheddrops_vs_multiseq_donor_match.csv:md5,3dd18b42dded6bc972f54f877b62d744", - "test3_hashsolo_vs_bff_consensuscall_concordance_heatmap.png:md5,70228780e63708c0255b9fefd17af97f", "test3_hashsolo_vs_bff_consensuscall_correlation_res.csv:md5,fd2c1ab13a371736d4ce2210fdbbb49a", "test3_hashsolo_vs_bff_consensuscall_donor_match.csv:md5,3dd18b42dded6bc972f54f877b62d744", - "test3_hashsolo_vs_bff_raw_concordance_heatmap.png:md5,70228780e63708c0255b9fefd17af97f", "test3_hashsolo_vs_bff_raw_correlation_res.csv:md5,fd2c1ab13a371736d4ce2210fdbbb49a", "test3_hashsolo_vs_bff_raw_donor_match.csv:md5,3dd18b42dded6bc972f54f877b62d744", - "test3_hashsolo_vs_gmmdemux_concordance_heatmap.png:md5,70228780e63708c0255b9fefd17af97f", "test3_hashsolo_vs_gmmdemux_correlation_res.csv:md5,fd2c1ab13a371736d4ce2210fdbbb49a", "test3_hashsolo_vs_gmmdemux_donor_match.csv:md5,3dd18b42dded6bc972f54f877b62d744", - "test3_hashsolo_vs_hasheddrops_concordance_heatmap.png:md5,70228780e63708c0255b9fefd17af97f", "test3_hashsolo_vs_hasheddrops_correlation_res.csv:md5,fd2c1ab13a371736d4ce2210fdbbb49a", "test3_hashsolo_vs_hasheddrops_donor_match.csv:md5,3dd18b42dded6bc972f54f877b62d744", - "test3_hashsolo_vs_htodemux_concordance_heatmap.png:md5,70228780e63708c0255b9fefd17af97f", "test3_hashsolo_vs_htodemux_correlation_res.csv:md5,fd2c1ab13a371736d4ce2210fdbbb49a", "test3_hashsolo_vs_htodemux_donor_match.csv:md5,3dd18b42dded6bc972f54f877b62d744", - "test3_hashsolo_vs_multiseq_concordance_heatmap.png:md5,70228780e63708c0255b9fefd17af97f", "test3_hashsolo_vs_multiseq_correlation_res.csv:md5,fd2c1ab13a371736d4ce2210fdbbb49a", "test3_hashsolo_vs_multiseq_donor_match.csv:md5,3dd18b42dded6bc972f54f877b62d744", - "test3_htodemux_vs_bff_consensuscall_concordance_heatmap.png:md5,70228780e63708c0255b9fefd17af97f", "test3_htodemux_vs_bff_consensuscall_correlation_res.csv:md5,fd2c1ab13a371736d4ce2210fdbbb49a", "test3_htodemux_vs_bff_consensuscall_donor_match.csv:md5,3dd18b42dded6bc972f54f877b62d744", - "test3_htodemux_vs_bff_raw_concordance_heatmap.png:md5,70228780e63708c0255b9fefd17af97f", "test3_htodemux_vs_bff_raw_correlation_res.csv:md5,fd2c1ab13a371736d4ce2210fdbbb49a", "test3_htodemux_vs_bff_raw_donor_match.csv:md5,3dd18b42dded6bc972f54f877b62d744", - "test3_htodemux_vs_gmmdemux_concordance_heatmap.png:md5,70228780e63708c0255b9fefd17af97f", "test3_htodemux_vs_gmmdemux_correlation_res.csv:md5,fd2c1ab13a371736d4ce2210fdbbb49a", "test3_htodemux_vs_gmmdemux_donor_match.csv:md5,3dd18b42dded6bc972f54f877b62d744", - "test3_multiseq_vs_bff_consensuscall_concordance_heatmap.png:md5,70228780e63708c0255b9fefd17af97f", "test3_multiseq_vs_bff_consensuscall_correlation_res.csv:md5,fd2c1ab13a371736d4ce2210fdbbb49a", "test3_multiseq_vs_bff_consensuscall_donor_match.csv:md5,3dd18b42dded6bc972f54f877b62d744", - "test3_multiseq_vs_bff_raw_concordance_heatmap.png:md5,70228780e63708c0255b9fefd17af97f", "test3_multiseq_vs_bff_raw_correlation_res.csv:md5,fd2c1ab13a371736d4ce2210fdbbb49a", "test3_multiseq_vs_bff_raw_donor_match.csv:md5,3dd18b42dded6bc972f54f877b62d744", - "test3_multiseq_vs_gmmdemux_concordance_heatmap.png:md5,70228780e63708c0255b9fefd17af97f", "test3_multiseq_vs_gmmdemux_correlation_res.csv:md5,fd2c1ab13a371736d4ce2210fdbbb49a", "test3_multiseq_vs_gmmdemux_donor_match.csv:md5,3dd18b42dded6bc972f54f877b62d744", - "test3_multiseq_vs_htodemux_concordance_heatmap.png:md5,70228780e63708c0255b9fefd17af97f", "test3_multiseq_vs_htodemux_correlation_res.csv:md5,fd2c1ab13a371736d4ce2210fdbbb49a", "test3_multiseq_vs_htodemux_donor_match.csv:md5,3dd18b42dded6bc972f54f877b62d744", "test1_assignment_bff.csv:md5,a62d56a7a5b9b59765d2ae759773aca2", @@ -886,28 +697,22 @@ "features.tsv.gz:md5,441a968b2c7ee68eae8388b04bee328d", "matrix.mtx.gz:md5,6f855b8bf68573d2e09139c29f9a2d41", "test1_emptyDrops.csv:md5,a227d42afbcc590b4e949075cde4a5b6", - "test1_emptyDrops.png:md5,2d7113a76a494b2b2c18b77c4a664063", "test1_emptyDrops.rds:md5,bda2e88b1e3e8c8ea5a22e19e3ab243d", "test1_hasheddrops.rds:md5,53424c915d3926e46180be1408d28311", "test1_id_to_hash.csv:md5,e8f577103f3e0c17604f89c3bcda2ffb", "test1_params_hasheddrops.csv:md5,b253501914df23ef5901262e8a241a8d", - "test1_plot_hasheddrops.png:md5,2d7113a76a494b2b2c18b77c4a664063", "test1_results_hasheddrops.csv:md5,230bcd615d3ac3d7ae8eb37ebe67f016", "test2_emptyDrops.csv:md5,a227d42afbcc590b4e949075cde4a5b6", - "test2_emptyDrops.png:md5,2d7113a76a494b2b2c18b77c4a664063", "test2_emptyDrops.rds:md5,bda2e88b1e3e8c8ea5a22e19e3ab243d", "test2_hasheddrops.rds:md5,53424c915d3926e46180be1408d28311", "test2_id_to_hash.csv:md5,e8f577103f3e0c17604f89c3bcda2ffb", "test2_params_hasheddrops.csv:md5,3612264937e5c5962fefddc43afb595f", - "test2_plot_hasheddrops.png:md5,2d7113a76a494b2b2c18b77c4a664063", "test2_results_hasheddrops.csv:md5,230bcd615d3ac3d7ae8eb37ebe67f016", "test3_emptyDrops.csv:md5,a227d42afbcc590b4e949075cde4a5b6", - "test3_emptyDrops.png:md5,2d7113a76a494b2b2c18b77c4a664063", "test3_emptyDrops.rds:md5,bda2e88b1e3e8c8ea5a22e19e3ab243d", "test3_hasheddrops.rds:md5,53424c915d3926e46180be1408d28311", "test3_id_to_hash.csv:md5,e8f577103f3e0c17604f89c3bcda2ffb", "test3_params_hasheddrops.csv:md5,b15e94f887e3ada8f790ea18eb791d8b", - "test3_plot_hasheddrops.png:md5,2d7113a76a494b2b2c18b77c4a664063", "test3_results_hasheddrops.csv:md5,230bcd615d3ac3d7ae8eb37ebe67f016", "test1_assignment_hashsolo.csv:md5,7763ee9e2ef20c353ab386f55f805b33", "test1_hashsolo.h5ad:md5,41ba3e91669bb4d52a7eb214186d36ea", @@ -921,26 +726,14 @@ "test1_assignment_htodemux.csv:md5,7217cd9d30a3c6f7f69e8b4f8aaa679d", "test1_classification_htodemux.csv:md5,e5b0d2af4de9bdc6682c120a78e53aa0", "test1_params_htodemux.csv:md5,08b6c065b0c383e6be3c20787ec01d3e", - "test1_featureScatter_htodemux.jpeg:md5,c67268ac0ff18c485899b4ee58f8f948", - "test1_heatMap_htodemux.jpeg:md5,2ebdb027344db91ae89deaa3d1011993", - "test1_ridge_htodemux.jpeg:md5,8bbc209b475b437a80f853277daa4ce9", - "test1_violinPlot_htodemux.jpeg:md5,ef866e13e18a31ab43370299245d33c7", "test1_visual_params_htodemux.csv:md5,0a175b9868be2b4447867ab38705c11d", "test2_assignment_htodemux.csv:md5,7217cd9d30a3c6f7f69e8b4f8aaa679d", "test2_classification_htodemux.csv:md5,e5b0d2af4de9bdc6682c120a78e53aa0", "test2_params_htodemux.csv:md5,484909d87cacd9c2b9cf60a3c853a5fd", - "test2_featureScatter_htodemux.jpeg:md5,c67268ac0ff18c485899b4ee58f8f948", - "test2_heatMap_htodemux.jpeg:md5,2ebdb027344db91ae89deaa3d1011993", - "test2_ridge_htodemux.jpeg:md5,8bbc209b475b437a80f853277daa4ce9", - "test2_violinPlot_htodemux.jpeg:md5,ef866e13e18a31ab43370299245d33c7", "test2_visual_params_htodemux.csv:md5,062e94620e3207810ddf2c65bf0d2914", "test3_assignment_htodemux.csv:md5,7217cd9d30a3c6f7f69e8b4f8aaa679d", "test3_classification_htodemux.csv:md5,e5b0d2af4de9bdc6682c120a78e53aa0", "test3_params_htodemux.csv:md5,a0a3f900a632eea39057b038438a89b9", - "test3_featureScatter_htodemux.jpeg:md5,c67268ac0ff18c485899b4ee58f8f948", - "test3_heatMap_htodemux.jpeg:md5,2ebdb027344db91ae89deaa3d1011993", - "test3_ridge_htodemux.jpeg:md5,8bbc209b475b437a80f853277daa4ce9", - "test3_violinPlot_htodemux.jpeg:md5,ef866e13e18a31ab43370299245d33c7", "test3_visual_params_htodemux.csv:md5,b6f8f5593aeb86d846292f2f23fc518d", "test1_params_multiseqdemux.csv:md5,60a096f79d78655870aef2af9130cf9d", "test1_res_multiseqdemux.csv:md5,1173b7b90f66f0eb41d516a119ba52ee", From e0480c4e938ac2172547440e368ad481676b0aee Mon Sep 17 00:00:00 2001 From: LuisHeinzlmeier Date: Sat, 5 Sep 2026 19:30:04 +0000 Subject: [PATCH 55/74] update donor_match snapshot --- tests/test_donor_match.nf.test.snap | 29 ++++++++++++++++++++++++++++- 1 file changed, 28 insertions(+), 1 deletion(-) diff --git a/tests/test_donor_match.nf.test.snap b/tests/test_donor_match.nf.test.snap index d3eda9ff..277cdbdb 100644 --- a/tests/test_donor_match.nf.test.snap +++ b/tests/test_donor_match.nf.test.snap @@ -27,41 +27,49 @@ "donor_match/test1", "donor_match/test1/freemuxlet_vs_bff_consensuscall", "donor_match/test1/freemuxlet_vs_bff_consensuscall/test1_freemuxlet_vs_bff_consensuscall_all_assignment_after_match.csv", + "donor_match/test1/freemuxlet_vs_bff_consensuscall/test1_freemuxlet_vs_bff_consensuscall_concordance_heatmap.png", "donor_match/test1/freemuxlet_vs_bff_consensuscall/test1_freemuxlet_vs_bff_consensuscall_correlation_res.csv", "donor_match/test1/freemuxlet_vs_bff_consensuscall/test1_freemuxlet_vs_bff_consensuscall_donor_match.csv", "donor_match/test1/freemuxlet_vs_bff_consensuscall/test1_freemuxlet_vs_bff_consensuscall_intersect_assignment_after_match.csv", "donor_match/test1/freemuxlet_vs_bff_raw", "donor_match/test1/freemuxlet_vs_bff_raw/test1_freemuxlet_vs_bff_raw_all_assignment_after_match.csv", + "donor_match/test1/freemuxlet_vs_bff_raw/test1_freemuxlet_vs_bff_raw_concordance_heatmap.png", "donor_match/test1/freemuxlet_vs_bff_raw/test1_freemuxlet_vs_bff_raw_correlation_res.csv", "donor_match/test1/freemuxlet_vs_bff_raw/test1_freemuxlet_vs_bff_raw_donor_match.csv", "donor_match/test1/freemuxlet_vs_bff_raw/test1_freemuxlet_vs_bff_raw_intersect_assignment_after_match.csv", "donor_match/test1/freemuxlet_vs_gmmdemux", "donor_match/test1/freemuxlet_vs_gmmdemux/test1_freemuxlet_vs_gmmdemux_all_assignment_after_match.csv", + "donor_match/test1/freemuxlet_vs_gmmdemux/test1_freemuxlet_vs_gmmdemux_concordance_heatmap.png", "donor_match/test1/freemuxlet_vs_gmmdemux/test1_freemuxlet_vs_gmmdemux_correlation_res.csv", "donor_match/test1/freemuxlet_vs_gmmdemux/test1_freemuxlet_vs_gmmdemux_donor_match.csv", "donor_match/test1/freemuxlet_vs_gmmdemux/test1_freemuxlet_vs_gmmdemux_intersect_assignment_after_match.csv", "donor_match/test1/freemuxlet_vs_hasheddrops", "donor_match/test1/freemuxlet_vs_hasheddrops/test1_freemuxlet_vs_hasheddrops_all_assignment_after_match.csv", + "donor_match/test1/freemuxlet_vs_hasheddrops/test1_freemuxlet_vs_hasheddrops_concordance_heatmap.png", "donor_match/test1/freemuxlet_vs_hasheddrops/test1_freemuxlet_vs_hasheddrops_correlation_res.csv", "donor_match/test1/freemuxlet_vs_hasheddrops/test1_freemuxlet_vs_hasheddrops_donor_match.csv", "donor_match/test1/freemuxlet_vs_hasheddrops/test1_freemuxlet_vs_hasheddrops_intersect_assignment_after_match.csv", "donor_match/test1/souporcell_vs_bff_consensuscall", "donor_match/test1/souporcell_vs_bff_consensuscall/test1_souporcell_vs_bff_consensuscall_all_assignment_after_match.csv", + "donor_match/test1/souporcell_vs_bff_consensuscall/test1_souporcell_vs_bff_consensuscall_concordance_heatmap.png", "donor_match/test1/souporcell_vs_bff_consensuscall/test1_souporcell_vs_bff_consensuscall_correlation_res.csv", "donor_match/test1/souporcell_vs_bff_consensuscall/test1_souporcell_vs_bff_consensuscall_donor_match.csv", "donor_match/test1/souporcell_vs_bff_consensuscall/test1_souporcell_vs_bff_consensuscall_intersect_assignment_after_match.csv", "donor_match/test1/souporcell_vs_bff_raw", "donor_match/test1/souporcell_vs_bff_raw/test1_souporcell_vs_bff_raw_all_assignment_after_match.csv", + "donor_match/test1/souporcell_vs_bff_raw/test1_souporcell_vs_bff_raw_concordance_heatmap.png", "donor_match/test1/souporcell_vs_bff_raw/test1_souporcell_vs_bff_raw_correlation_res.csv", "donor_match/test1/souporcell_vs_bff_raw/test1_souporcell_vs_bff_raw_donor_match.csv", "donor_match/test1/souporcell_vs_bff_raw/test1_souporcell_vs_bff_raw_intersect_assignment_after_match.csv", "donor_match/test1/souporcell_vs_gmmdemux", "donor_match/test1/souporcell_vs_gmmdemux/test1_souporcell_vs_gmmdemux_all_assignment_after_match.csv", + "donor_match/test1/souporcell_vs_gmmdemux/test1_souporcell_vs_gmmdemux_concordance_heatmap.png", "donor_match/test1/souporcell_vs_gmmdemux/test1_souporcell_vs_gmmdemux_correlation_res.csv", "donor_match/test1/souporcell_vs_gmmdemux/test1_souporcell_vs_gmmdemux_donor_match.csv", "donor_match/test1/souporcell_vs_gmmdemux/test1_souporcell_vs_gmmdemux_intersect_assignment_after_match.csv", "donor_match/test1/souporcell_vs_hasheddrops", "donor_match/test1/souporcell_vs_hasheddrops/test1_souporcell_vs_hasheddrops_all_assignment_after_match.csv", + "donor_match/test1/souporcell_vs_hasheddrops/test1_souporcell_vs_hasheddrops_concordance_heatmap.png", "donor_match/test1/souporcell_vs_hasheddrops/test1_souporcell_vs_hasheddrops_correlation_res.csv", "donor_match/test1/souporcell_vs_hasheddrops/test1_souporcell_vs_hasheddrops_donor_match.csv", "donor_match/test1/souporcell_vs_hasheddrops/test1_souporcell_vs_hasheddrops_intersect_assignment_after_match.csv", @@ -72,41 +80,49 @@ "donor_match/test2", "donor_match/test2/freemuxlet_vs_bff_consensuscall", "donor_match/test2/freemuxlet_vs_bff_consensuscall/test2_freemuxlet_vs_bff_consensuscall_all_assignment_after_match.csv", + "donor_match/test2/freemuxlet_vs_bff_consensuscall/test2_freemuxlet_vs_bff_consensuscall_concordance_heatmap.png", "donor_match/test2/freemuxlet_vs_bff_consensuscall/test2_freemuxlet_vs_bff_consensuscall_correlation_res.csv", "donor_match/test2/freemuxlet_vs_bff_consensuscall/test2_freemuxlet_vs_bff_consensuscall_donor_match.csv", "donor_match/test2/freemuxlet_vs_bff_consensuscall/test2_freemuxlet_vs_bff_consensuscall_intersect_assignment_after_match.csv", "donor_match/test2/freemuxlet_vs_bff_raw", "donor_match/test2/freemuxlet_vs_bff_raw/test2_freemuxlet_vs_bff_raw_all_assignment_after_match.csv", + "donor_match/test2/freemuxlet_vs_bff_raw/test2_freemuxlet_vs_bff_raw_concordance_heatmap.png", "donor_match/test2/freemuxlet_vs_bff_raw/test2_freemuxlet_vs_bff_raw_correlation_res.csv", "donor_match/test2/freemuxlet_vs_bff_raw/test2_freemuxlet_vs_bff_raw_donor_match.csv", "donor_match/test2/freemuxlet_vs_bff_raw/test2_freemuxlet_vs_bff_raw_intersect_assignment_after_match.csv", "donor_match/test2/freemuxlet_vs_gmmdemux", "donor_match/test2/freemuxlet_vs_gmmdemux/test2_freemuxlet_vs_gmmdemux_all_assignment_after_match.csv", + "donor_match/test2/freemuxlet_vs_gmmdemux/test2_freemuxlet_vs_gmmdemux_concordance_heatmap.png", "donor_match/test2/freemuxlet_vs_gmmdemux/test2_freemuxlet_vs_gmmdemux_correlation_res.csv", "donor_match/test2/freemuxlet_vs_gmmdemux/test2_freemuxlet_vs_gmmdemux_donor_match.csv", "donor_match/test2/freemuxlet_vs_gmmdemux/test2_freemuxlet_vs_gmmdemux_intersect_assignment_after_match.csv", "donor_match/test2/freemuxlet_vs_hasheddrops", "donor_match/test2/freemuxlet_vs_hasheddrops/test2_freemuxlet_vs_hasheddrops_all_assignment_after_match.csv", + "donor_match/test2/freemuxlet_vs_hasheddrops/test2_freemuxlet_vs_hasheddrops_concordance_heatmap.png", "donor_match/test2/freemuxlet_vs_hasheddrops/test2_freemuxlet_vs_hasheddrops_correlation_res.csv", "donor_match/test2/freemuxlet_vs_hasheddrops/test2_freemuxlet_vs_hasheddrops_donor_match.csv", "donor_match/test2/freemuxlet_vs_hasheddrops/test2_freemuxlet_vs_hasheddrops_intersect_assignment_after_match.csv", "donor_match/test2/souporcell_vs_bff_consensuscall", "donor_match/test2/souporcell_vs_bff_consensuscall/test2_souporcell_vs_bff_consensuscall_all_assignment_after_match.csv", + "donor_match/test2/souporcell_vs_bff_consensuscall/test2_souporcell_vs_bff_consensuscall_concordance_heatmap.png", "donor_match/test2/souporcell_vs_bff_consensuscall/test2_souporcell_vs_bff_consensuscall_correlation_res.csv", "donor_match/test2/souporcell_vs_bff_consensuscall/test2_souporcell_vs_bff_consensuscall_donor_match.csv", "donor_match/test2/souporcell_vs_bff_consensuscall/test2_souporcell_vs_bff_consensuscall_intersect_assignment_after_match.csv", "donor_match/test2/souporcell_vs_bff_raw", "donor_match/test2/souporcell_vs_bff_raw/test2_souporcell_vs_bff_raw_all_assignment_after_match.csv", + "donor_match/test2/souporcell_vs_bff_raw/test2_souporcell_vs_bff_raw_concordance_heatmap.png", "donor_match/test2/souporcell_vs_bff_raw/test2_souporcell_vs_bff_raw_correlation_res.csv", "donor_match/test2/souporcell_vs_bff_raw/test2_souporcell_vs_bff_raw_donor_match.csv", "donor_match/test2/souporcell_vs_bff_raw/test2_souporcell_vs_bff_raw_intersect_assignment_after_match.csv", "donor_match/test2/souporcell_vs_gmmdemux", "donor_match/test2/souporcell_vs_gmmdemux/test2_souporcell_vs_gmmdemux_all_assignment_after_match.csv", + "donor_match/test2/souporcell_vs_gmmdemux/test2_souporcell_vs_gmmdemux_concordance_heatmap.png", "donor_match/test2/souporcell_vs_gmmdemux/test2_souporcell_vs_gmmdemux_correlation_res.csv", "donor_match/test2/souporcell_vs_gmmdemux/test2_souporcell_vs_gmmdemux_donor_match.csv", "donor_match/test2/souporcell_vs_gmmdemux/test2_souporcell_vs_gmmdemux_intersect_assignment_after_match.csv", "donor_match/test2/souporcell_vs_hasheddrops", "donor_match/test2/souporcell_vs_hasheddrops/test2_souporcell_vs_hasheddrops_all_assignment_after_match.csv", + "donor_match/test2/souporcell_vs_hasheddrops/test2_souporcell_vs_hasheddrops_concordance_heatmap.png", "donor_match/test2/souporcell_vs_hasheddrops/test2_souporcell_vs_hasheddrops_correlation_res.csv", "donor_match/test2/souporcell_vs_hasheddrops/test2_souporcell_vs_hasheddrops_donor_match.csv", "donor_match/test2/souporcell_vs_hasheddrops/test2_souporcell_vs_hasheddrops_intersect_assignment_after_match.csv", @@ -117,41 +133,49 @@ "donor_match/test3", "donor_match/test3/freemuxlet_vs_bff_consensuscall", "donor_match/test3/freemuxlet_vs_bff_consensuscall/test3_freemuxlet_vs_bff_consensuscall_all_assignment_after_match.csv", + "donor_match/test3/freemuxlet_vs_bff_consensuscall/test3_freemuxlet_vs_bff_consensuscall_concordance_heatmap.png", "donor_match/test3/freemuxlet_vs_bff_consensuscall/test3_freemuxlet_vs_bff_consensuscall_correlation_res.csv", "donor_match/test3/freemuxlet_vs_bff_consensuscall/test3_freemuxlet_vs_bff_consensuscall_donor_match.csv", "donor_match/test3/freemuxlet_vs_bff_consensuscall/test3_freemuxlet_vs_bff_consensuscall_intersect_assignment_after_match.csv", "donor_match/test3/freemuxlet_vs_bff_raw", "donor_match/test3/freemuxlet_vs_bff_raw/test3_freemuxlet_vs_bff_raw_all_assignment_after_match.csv", + "donor_match/test3/freemuxlet_vs_bff_raw/test3_freemuxlet_vs_bff_raw_concordance_heatmap.png", "donor_match/test3/freemuxlet_vs_bff_raw/test3_freemuxlet_vs_bff_raw_correlation_res.csv", "donor_match/test3/freemuxlet_vs_bff_raw/test3_freemuxlet_vs_bff_raw_donor_match.csv", "donor_match/test3/freemuxlet_vs_bff_raw/test3_freemuxlet_vs_bff_raw_intersect_assignment_after_match.csv", "donor_match/test3/freemuxlet_vs_gmmdemux", "donor_match/test3/freemuxlet_vs_gmmdemux/test3_freemuxlet_vs_gmmdemux_all_assignment_after_match.csv", + "donor_match/test3/freemuxlet_vs_gmmdemux/test3_freemuxlet_vs_gmmdemux_concordance_heatmap.png", "donor_match/test3/freemuxlet_vs_gmmdemux/test3_freemuxlet_vs_gmmdemux_correlation_res.csv", "donor_match/test3/freemuxlet_vs_gmmdemux/test3_freemuxlet_vs_gmmdemux_donor_match.csv", "donor_match/test3/freemuxlet_vs_gmmdemux/test3_freemuxlet_vs_gmmdemux_intersect_assignment_after_match.csv", "donor_match/test3/freemuxlet_vs_hasheddrops", "donor_match/test3/freemuxlet_vs_hasheddrops/test3_freemuxlet_vs_hasheddrops_all_assignment_after_match.csv", + "donor_match/test3/freemuxlet_vs_hasheddrops/test3_freemuxlet_vs_hasheddrops_concordance_heatmap.png", "donor_match/test3/freemuxlet_vs_hasheddrops/test3_freemuxlet_vs_hasheddrops_correlation_res.csv", "donor_match/test3/freemuxlet_vs_hasheddrops/test3_freemuxlet_vs_hasheddrops_donor_match.csv", "donor_match/test3/freemuxlet_vs_hasheddrops/test3_freemuxlet_vs_hasheddrops_intersect_assignment_after_match.csv", "donor_match/test3/souporcell_vs_bff_consensuscall", "donor_match/test3/souporcell_vs_bff_consensuscall/test3_souporcell_vs_bff_consensuscall_all_assignment_after_match.csv", + "donor_match/test3/souporcell_vs_bff_consensuscall/test3_souporcell_vs_bff_consensuscall_concordance_heatmap.png", "donor_match/test3/souporcell_vs_bff_consensuscall/test3_souporcell_vs_bff_consensuscall_correlation_res.csv", "donor_match/test3/souporcell_vs_bff_consensuscall/test3_souporcell_vs_bff_consensuscall_donor_match.csv", "donor_match/test3/souporcell_vs_bff_consensuscall/test3_souporcell_vs_bff_consensuscall_intersect_assignment_after_match.csv", "donor_match/test3/souporcell_vs_bff_raw", "donor_match/test3/souporcell_vs_bff_raw/test3_souporcell_vs_bff_raw_all_assignment_after_match.csv", + "donor_match/test3/souporcell_vs_bff_raw/test3_souporcell_vs_bff_raw_concordance_heatmap.png", "donor_match/test3/souporcell_vs_bff_raw/test3_souporcell_vs_bff_raw_correlation_res.csv", "donor_match/test3/souporcell_vs_bff_raw/test3_souporcell_vs_bff_raw_donor_match.csv", "donor_match/test3/souporcell_vs_bff_raw/test3_souporcell_vs_bff_raw_intersect_assignment_after_match.csv", "donor_match/test3/souporcell_vs_gmmdemux", "donor_match/test3/souporcell_vs_gmmdemux/test3_souporcell_vs_gmmdemux_all_assignment_after_match.csv", + "donor_match/test3/souporcell_vs_gmmdemux/test3_souporcell_vs_gmmdemux_concordance_heatmap.png", "donor_match/test3/souporcell_vs_gmmdemux/test3_souporcell_vs_gmmdemux_correlation_res.csv", "donor_match/test3/souporcell_vs_gmmdemux/test3_souporcell_vs_gmmdemux_donor_match.csv", "donor_match/test3/souporcell_vs_gmmdemux/test3_souporcell_vs_gmmdemux_intersect_assignment_after_match.csv", "donor_match/test3/souporcell_vs_hasheddrops", "donor_match/test3/souporcell_vs_hasheddrops/test3_souporcell_vs_hasheddrops_all_assignment_after_match.csv", + "donor_match/test3/souporcell_vs_hasheddrops/test3_souporcell_vs_hasheddrops_concordance_heatmap.png", "donor_match/test3/souporcell_vs_hasheddrops/test3_souporcell_vs_hasheddrops_correlation_res.csv", "donor_match/test3/souporcell_vs_hasheddrops/test3_souporcell_vs_hasheddrops_donor_match.csv", "donor_match/test3/souporcell_vs_hasheddrops/test3_souporcell_vs_hasheddrops_intersect_assignment_after_match.csv", @@ -174,6 +198,7 @@ "find_variants/test1/subset_gt_donors/test1_vireo.vcf.gz", "find_variants/test1/test1_all_representative_variants.csv", "find_variants/test1/test1_donor_specific_variants.csv", + "find_variants/test1/test1_donor_specific_variants_upset.png", "find_variants/test1/test1_vireo_variants.csv", "find_variants/test2", "find_variants/test2/hto-1", @@ -189,6 +214,7 @@ "find_variants/test2/subset_gt_donors/test2_vireo.vcf.gz", "find_variants/test2/test2_all_representative_variants.csv", "find_variants/test2/test2_donor_specific_variants.csv", + "find_variants/test2/test2_donor_specific_variants_upset.png", "find_variants/test2/test2_vireo_variants.csv", "find_variants/test3", "find_variants/test3/hto-1", @@ -204,6 +230,7 @@ "find_variants/test3/subset_gt_donors/test3_vireo.vcf.gz", "find_variants/test3/test3_all_representative_variants.csv", "find_variants/test3/test3_donor_specific_variants.csv", + "find_variants/test3/test3_donor_specific_variants_upset.png", "find_variants/test3/test3_vireo_variants.csv", "multiqc", "multiqc/multiqc_data", @@ -365,7 +392,7 @@ "test3_vireo.vcf.gz:md5,6e4bbad3892e84cd1dd53c8fc0579977" ] ], - "timestamp": "2026-08-23T07:39:06.342397056", + "timestamp": "2026-09-05T18:17:35.976579932", "meta": { "nf-test": "0.9.5", "nextflow": "26.04.6" From fa0c1e2788710d2df8204eec6dd38819540cc4c5 Mon Sep 17 00:00:00 2001 From: LuisHeinzlmeier Date: Sat, 5 Sep 2026 21:56:41 +0000 Subject: [PATCH 56/74] update test --- tests/default.nf.test.snap | 74 +++++++++++++++++++++++++++++++++++++- 1 file changed, 73 insertions(+), 1 deletion(-) diff --git a/tests/default.nf.test.snap b/tests/default.nf.test.snap index b821e893..c681c94c 100644 --- a/tests/default.nf.test.snap +++ b/tests/default.nf.test.snap @@ -139,81 +139,97 @@ "donor_match/test1", "donor_match/test1/freemuxlet_vs_bff_consensuscall", "donor_match/test1/freemuxlet_vs_bff_consensuscall/test1_freemuxlet_vs_bff_consensuscall_all_assignment_after_match.csv", + "donor_match/test1/freemuxlet_vs_bff_consensuscall/test1_freemuxlet_vs_bff_consensuscall_concordance_heatmap.png", "donor_match/test1/freemuxlet_vs_bff_consensuscall/test1_freemuxlet_vs_bff_consensuscall_correlation_res.csv", "donor_match/test1/freemuxlet_vs_bff_consensuscall/test1_freemuxlet_vs_bff_consensuscall_donor_match.csv", "donor_match/test1/freemuxlet_vs_bff_consensuscall/test1_freemuxlet_vs_bff_consensuscall_intersect_assignment_after_match.csv", "donor_match/test1/freemuxlet_vs_bff_raw", "donor_match/test1/freemuxlet_vs_bff_raw/test1_freemuxlet_vs_bff_raw_all_assignment_after_match.csv", + "donor_match/test1/freemuxlet_vs_bff_raw/test1_freemuxlet_vs_bff_raw_concordance_heatmap.png", "donor_match/test1/freemuxlet_vs_bff_raw/test1_freemuxlet_vs_bff_raw_correlation_res.csv", "donor_match/test1/freemuxlet_vs_bff_raw/test1_freemuxlet_vs_bff_raw_donor_match.csv", "donor_match/test1/freemuxlet_vs_bff_raw/test1_freemuxlet_vs_bff_raw_intersect_assignment_after_match.csv", "donor_match/test1/freemuxlet_vs_demuxem", "donor_match/test1/freemuxlet_vs_demuxem/test1_freemuxlet_vs_demuxem_all_assignment_after_match.csv", + "donor_match/test1/freemuxlet_vs_demuxem/test1_freemuxlet_vs_demuxem_concordance_heatmap.png", "donor_match/test1/freemuxlet_vs_demuxem/test1_freemuxlet_vs_demuxem_correlation_res.csv", "donor_match/test1/freemuxlet_vs_demuxem/test1_freemuxlet_vs_demuxem_donor_match.csv", "donor_match/test1/freemuxlet_vs_demuxem/test1_freemuxlet_vs_demuxem_intersect_assignment_after_match.csv", "donor_match/test1/freemuxlet_vs_gmmdemux", "donor_match/test1/freemuxlet_vs_gmmdemux/test1_freemuxlet_vs_gmmdemux_all_assignment_after_match.csv", + "donor_match/test1/freemuxlet_vs_gmmdemux/test1_freemuxlet_vs_gmmdemux_concordance_heatmap.png", "donor_match/test1/freemuxlet_vs_gmmdemux/test1_freemuxlet_vs_gmmdemux_correlation_res.csv", "donor_match/test1/freemuxlet_vs_gmmdemux/test1_freemuxlet_vs_gmmdemux_donor_match.csv", "donor_match/test1/freemuxlet_vs_gmmdemux/test1_freemuxlet_vs_gmmdemux_intersect_assignment_after_match.csv", "donor_match/test1/freemuxlet_vs_hasheddrops", "donor_match/test1/freemuxlet_vs_hasheddrops/test1_freemuxlet_vs_hasheddrops_all_assignment_after_match.csv", + "donor_match/test1/freemuxlet_vs_hasheddrops/test1_freemuxlet_vs_hasheddrops_concordance_heatmap.png", "donor_match/test1/freemuxlet_vs_hasheddrops/test1_freemuxlet_vs_hasheddrops_correlation_res.csv", "donor_match/test1/freemuxlet_vs_hasheddrops/test1_freemuxlet_vs_hasheddrops_donor_match.csv", "donor_match/test1/freemuxlet_vs_hasheddrops/test1_freemuxlet_vs_hasheddrops_intersect_assignment_after_match.csv", "donor_match/test1/freemuxlet_vs_hashsolo", "donor_match/test1/freemuxlet_vs_hashsolo/test1_freemuxlet_vs_hashsolo_all_assignment_after_match.csv", + "donor_match/test1/freemuxlet_vs_hashsolo/test1_freemuxlet_vs_hashsolo_concordance_heatmap.png", "donor_match/test1/freemuxlet_vs_hashsolo/test1_freemuxlet_vs_hashsolo_correlation_res.csv", "donor_match/test1/freemuxlet_vs_hashsolo/test1_freemuxlet_vs_hashsolo_donor_match.csv", "donor_match/test1/freemuxlet_vs_hashsolo/test1_freemuxlet_vs_hashsolo_intersect_assignment_after_match.csv", "donor_match/test1/freemuxlet_vs_htodemux", "donor_match/test1/freemuxlet_vs_htodemux/test1_freemuxlet_vs_htodemux_all_assignment_after_match.csv", + "donor_match/test1/freemuxlet_vs_htodemux/test1_freemuxlet_vs_htodemux_concordance_heatmap.png", "donor_match/test1/freemuxlet_vs_htodemux/test1_freemuxlet_vs_htodemux_correlation_res.csv", "donor_match/test1/freemuxlet_vs_htodemux/test1_freemuxlet_vs_htodemux_donor_match.csv", "donor_match/test1/freemuxlet_vs_htodemux/test1_freemuxlet_vs_htodemux_intersect_assignment_after_match.csv", "donor_match/test1/freemuxlet_vs_multiseq", "donor_match/test1/freemuxlet_vs_multiseq/test1_freemuxlet_vs_multiseq_all_assignment_after_match.csv", + "donor_match/test1/freemuxlet_vs_multiseq/test1_freemuxlet_vs_multiseq_concordance_heatmap.png", "donor_match/test1/freemuxlet_vs_multiseq/test1_freemuxlet_vs_multiseq_correlation_res.csv", "donor_match/test1/freemuxlet_vs_multiseq/test1_freemuxlet_vs_multiseq_donor_match.csv", "donor_match/test1/freemuxlet_vs_multiseq/test1_freemuxlet_vs_multiseq_intersect_assignment_after_match.csv", "donor_match/test1/souporcell_vs_bff_consensuscall", "donor_match/test1/souporcell_vs_bff_consensuscall/test1_souporcell_vs_bff_consensuscall_all_assignment_after_match.csv", + "donor_match/test1/souporcell_vs_bff_consensuscall/test1_souporcell_vs_bff_consensuscall_concordance_heatmap.png", "donor_match/test1/souporcell_vs_bff_consensuscall/test1_souporcell_vs_bff_consensuscall_correlation_res.csv", "donor_match/test1/souporcell_vs_bff_consensuscall/test1_souporcell_vs_bff_consensuscall_donor_match.csv", "donor_match/test1/souporcell_vs_bff_consensuscall/test1_souporcell_vs_bff_consensuscall_intersect_assignment_after_match.csv", "donor_match/test1/souporcell_vs_bff_raw", "donor_match/test1/souporcell_vs_bff_raw/test1_souporcell_vs_bff_raw_all_assignment_after_match.csv", + "donor_match/test1/souporcell_vs_bff_raw/test1_souporcell_vs_bff_raw_concordance_heatmap.png", "donor_match/test1/souporcell_vs_bff_raw/test1_souporcell_vs_bff_raw_correlation_res.csv", "donor_match/test1/souporcell_vs_bff_raw/test1_souporcell_vs_bff_raw_donor_match.csv", "donor_match/test1/souporcell_vs_bff_raw/test1_souporcell_vs_bff_raw_intersect_assignment_after_match.csv", "donor_match/test1/souporcell_vs_demuxem", "donor_match/test1/souporcell_vs_demuxem/test1_souporcell_vs_demuxem_all_assignment_after_match.csv", + "donor_match/test1/souporcell_vs_demuxem/test1_souporcell_vs_demuxem_concordance_heatmap.png", "donor_match/test1/souporcell_vs_demuxem/test1_souporcell_vs_demuxem_correlation_res.csv", "donor_match/test1/souporcell_vs_demuxem/test1_souporcell_vs_demuxem_donor_match.csv", "donor_match/test1/souporcell_vs_demuxem/test1_souporcell_vs_demuxem_intersect_assignment_after_match.csv", "donor_match/test1/souporcell_vs_gmmdemux", "donor_match/test1/souporcell_vs_gmmdemux/test1_souporcell_vs_gmmdemux_all_assignment_after_match.csv", + "donor_match/test1/souporcell_vs_gmmdemux/test1_souporcell_vs_gmmdemux_concordance_heatmap.png", "donor_match/test1/souporcell_vs_gmmdemux/test1_souporcell_vs_gmmdemux_correlation_res.csv", "donor_match/test1/souporcell_vs_gmmdemux/test1_souporcell_vs_gmmdemux_donor_match.csv", "donor_match/test1/souporcell_vs_gmmdemux/test1_souporcell_vs_gmmdemux_intersect_assignment_after_match.csv", "donor_match/test1/souporcell_vs_hasheddrops", "donor_match/test1/souporcell_vs_hasheddrops/test1_souporcell_vs_hasheddrops_all_assignment_after_match.csv", + "donor_match/test1/souporcell_vs_hasheddrops/test1_souporcell_vs_hasheddrops_concordance_heatmap.png", "donor_match/test1/souporcell_vs_hasheddrops/test1_souporcell_vs_hasheddrops_correlation_res.csv", "donor_match/test1/souporcell_vs_hasheddrops/test1_souporcell_vs_hasheddrops_donor_match.csv", "donor_match/test1/souporcell_vs_hasheddrops/test1_souporcell_vs_hasheddrops_intersect_assignment_after_match.csv", "donor_match/test1/souporcell_vs_hashsolo", "donor_match/test1/souporcell_vs_hashsolo/test1_souporcell_vs_hashsolo_all_assignment_after_match.csv", + "donor_match/test1/souporcell_vs_hashsolo/test1_souporcell_vs_hashsolo_concordance_heatmap.png", "donor_match/test1/souporcell_vs_hashsolo/test1_souporcell_vs_hashsolo_correlation_res.csv", "donor_match/test1/souporcell_vs_hashsolo/test1_souporcell_vs_hashsolo_donor_match.csv", "donor_match/test1/souporcell_vs_hashsolo/test1_souporcell_vs_hashsolo_intersect_assignment_after_match.csv", "donor_match/test1/souporcell_vs_htodemux", "donor_match/test1/souporcell_vs_htodemux/test1_souporcell_vs_htodemux_all_assignment_after_match.csv", + "donor_match/test1/souporcell_vs_htodemux/test1_souporcell_vs_htodemux_concordance_heatmap.png", "donor_match/test1/souporcell_vs_htodemux/test1_souporcell_vs_htodemux_correlation_res.csv", "donor_match/test1/souporcell_vs_htodemux/test1_souporcell_vs_htodemux_donor_match.csv", "donor_match/test1/souporcell_vs_htodemux/test1_souporcell_vs_htodemux_intersect_assignment_after_match.csv", "donor_match/test1/souporcell_vs_multiseq", "donor_match/test1/souporcell_vs_multiseq/test1_souporcell_vs_multiseq_all_assignment_after_match.csv", + "donor_match/test1/souporcell_vs_multiseq/test1_souporcell_vs_multiseq_concordance_heatmap.png", "donor_match/test1/souporcell_vs_multiseq/test1_souporcell_vs_multiseq_correlation_res.csv", "donor_match/test1/souporcell_vs_multiseq/test1_souporcell_vs_multiseq_donor_match.csv", "donor_match/test1/souporcell_vs_multiseq/test1_souporcell_vs_multiseq_intersect_assignment_after_match.csv", @@ -224,81 +240,97 @@ "donor_match/test2", "donor_match/test2/freemuxlet_vs_bff_consensuscall", "donor_match/test2/freemuxlet_vs_bff_consensuscall/test2_freemuxlet_vs_bff_consensuscall_all_assignment_after_match.csv", + "donor_match/test2/freemuxlet_vs_bff_consensuscall/test2_freemuxlet_vs_bff_consensuscall_concordance_heatmap.png", "donor_match/test2/freemuxlet_vs_bff_consensuscall/test2_freemuxlet_vs_bff_consensuscall_correlation_res.csv", "donor_match/test2/freemuxlet_vs_bff_consensuscall/test2_freemuxlet_vs_bff_consensuscall_donor_match.csv", "donor_match/test2/freemuxlet_vs_bff_consensuscall/test2_freemuxlet_vs_bff_consensuscall_intersect_assignment_after_match.csv", "donor_match/test2/freemuxlet_vs_bff_raw", "donor_match/test2/freemuxlet_vs_bff_raw/test2_freemuxlet_vs_bff_raw_all_assignment_after_match.csv", + "donor_match/test2/freemuxlet_vs_bff_raw/test2_freemuxlet_vs_bff_raw_concordance_heatmap.png", "donor_match/test2/freemuxlet_vs_bff_raw/test2_freemuxlet_vs_bff_raw_correlation_res.csv", "donor_match/test2/freemuxlet_vs_bff_raw/test2_freemuxlet_vs_bff_raw_donor_match.csv", "donor_match/test2/freemuxlet_vs_bff_raw/test2_freemuxlet_vs_bff_raw_intersect_assignment_after_match.csv", "donor_match/test2/freemuxlet_vs_demuxem", "donor_match/test2/freemuxlet_vs_demuxem/test2_freemuxlet_vs_demuxem_all_assignment_after_match.csv", + "donor_match/test2/freemuxlet_vs_demuxem/test2_freemuxlet_vs_demuxem_concordance_heatmap.png", "donor_match/test2/freemuxlet_vs_demuxem/test2_freemuxlet_vs_demuxem_correlation_res.csv", "donor_match/test2/freemuxlet_vs_demuxem/test2_freemuxlet_vs_demuxem_donor_match.csv", "donor_match/test2/freemuxlet_vs_demuxem/test2_freemuxlet_vs_demuxem_intersect_assignment_after_match.csv", "donor_match/test2/freemuxlet_vs_gmmdemux", "donor_match/test2/freemuxlet_vs_gmmdemux/test2_freemuxlet_vs_gmmdemux_all_assignment_after_match.csv", + "donor_match/test2/freemuxlet_vs_gmmdemux/test2_freemuxlet_vs_gmmdemux_concordance_heatmap.png", "donor_match/test2/freemuxlet_vs_gmmdemux/test2_freemuxlet_vs_gmmdemux_correlation_res.csv", "donor_match/test2/freemuxlet_vs_gmmdemux/test2_freemuxlet_vs_gmmdemux_donor_match.csv", "donor_match/test2/freemuxlet_vs_gmmdemux/test2_freemuxlet_vs_gmmdemux_intersect_assignment_after_match.csv", "donor_match/test2/freemuxlet_vs_hasheddrops", "donor_match/test2/freemuxlet_vs_hasheddrops/test2_freemuxlet_vs_hasheddrops_all_assignment_after_match.csv", + "donor_match/test2/freemuxlet_vs_hasheddrops/test2_freemuxlet_vs_hasheddrops_concordance_heatmap.png", "donor_match/test2/freemuxlet_vs_hasheddrops/test2_freemuxlet_vs_hasheddrops_correlation_res.csv", "donor_match/test2/freemuxlet_vs_hasheddrops/test2_freemuxlet_vs_hasheddrops_donor_match.csv", "donor_match/test2/freemuxlet_vs_hasheddrops/test2_freemuxlet_vs_hasheddrops_intersect_assignment_after_match.csv", "donor_match/test2/freemuxlet_vs_hashsolo", "donor_match/test2/freemuxlet_vs_hashsolo/test2_freemuxlet_vs_hashsolo_all_assignment_after_match.csv", + "donor_match/test2/freemuxlet_vs_hashsolo/test2_freemuxlet_vs_hashsolo_concordance_heatmap.png", "donor_match/test2/freemuxlet_vs_hashsolo/test2_freemuxlet_vs_hashsolo_correlation_res.csv", "donor_match/test2/freemuxlet_vs_hashsolo/test2_freemuxlet_vs_hashsolo_donor_match.csv", "donor_match/test2/freemuxlet_vs_hashsolo/test2_freemuxlet_vs_hashsolo_intersect_assignment_after_match.csv", "donor_match/test2/freemuxlet_vs_htodemux", "donor_match/test2/freemuxlet_vs_htodemux/test2_freemuxlet_vs_htodemux_all_assignment_after_match.csv", + "donor_match/test2/freemuxlet_vs_htodemux/test2_freemuxlet_vs_htodemux_concordance_heatmap.png", "donor_match/test2/freemuxlet_vs_htodemux/test2_freemuxlet_vs_htodemux_correlation_res.csv", "donor_match/test2/freemuxlet_vs_htodemux/test2_freemuxlet_vs_htodemux_donor_match.csv", "donor_match/test2/freemuxlet_vs_htodemux/test2_freemuxlet_vs_htodemux_intersect_assignment_after_match.csv", "donor_match/test2/freemuxlet_vs_multiseq", "donor_match/test2/freemuxlet_vs_multiseq/test2_freemuxlet_vs_multiseq_all_assignment_after_match.csv", + "donor_match/test2/freemuxlet_vs_multiseq/test2_freemuxlet_vs_multiseq_concordance_heatmap.png", "donor_match/test2/freemuxlet_vs_multiseq/test2_freemuxlet_vs_multiseq_correlation_res.csv", "donor_match/test2/freemuxlet_vs_multiseq/test2_freemuxlet_vs_multiseq_donor_match.csv", "donor_match/test2/freemuxlet_vs_multiseq/test2_freemuxlet_vs_multiseq_intersect_assignment_after_match.csv", "donor_match/test2/souporcell_vs_bff_consensuscall", "donor_match/test2/souporcell_vs_bff_consensuscall/test2_souporcell_vs_bff_consensuscall_all_assignment_after_match.csv", + "donor_match/test2/souporcell_vs_bff_consensuscall/test2_souporcell_vs_bff_consensuscall_concordance_heatmap.png", "donor_match/test2/souporcell_vs_bff_consensuscall/test2_souporcell_vs_bff_consensuscall_correlation_res.csv", "donor_match/test2/souporcell_vs_bff_consensuscall/test2_souporcell_vs_bff_consensuscall_donor_match.csv", "donor_match/test2/souporcell_vs_bff_consensuscall/test2_souporcell_vs_bff_consensuscall_intersect_assignment_after_match.csv", "donor_match/test2/souporcell_vs_bff_raw", "donor_match/test2/souporcell_vs_bff_raw/test2_souporcell_vs_bff_raw_all_assignment_after_match.csv", + "donor_match/test2/souporcell_vs_bff_raw/test2_souporcell_vs_bff_raw_concordance_heatmap.png", "donor_match/test2/souporcell_vs_bff_raw/test2_souporcell_vs_bff_raw_correlation_res.csv", "donor_match/test2/souporcell_vs_bff_raw/test2_souporcell_vs_bff_raw_donor_match.csv", "donor_match/test2/souporcell_vs_bff_raw/test2_souporcell_vs_bff_raw_intersect_assignment_after_match.csv", "donor_match/test2/souporcell_vs_demuxem", "donor_match/test2/souporcell_vs_demuxem/test2_souporcell_vs_demuxem_all_assignment_after_match.csv", + "donor_match/test2/souporcell_vs_demuxem/test2_souporcell_vs_demuxem_concordance_heatmap.png", "donor_match/test2/souporcell_vs_demuxem/test2_souporcell_vs_demuxem_correlation_res.csv", "donor_match/test2/souporcell_vs_demuxem/test2_souporcell_vs_demuxem_donor_match.csv", "donor_match/test2/souporcell_vs_demuxem/test2_souporcell_vs_demuxem_intersect_assignment_after_match.csv", "donor_match/test2/souporcell_vs_gmmdemux", "donor_match/test2/souporcell_vs_gmmdemux/test2_souporcell_vs_gmmdemux_all_assignment_after_match.csv", + "donor_match/test2/souporcell_vs_gmmdemux/test2_souporcell_vs_gmmdemux_concordance_heatmap.png", "donor_match/test2/souporcell_vs_gmmdemux/test2_souporcell_vs_gmmdemux_correlation_res.csv", "donor_match/test2/souporcell_vs_gmmdemux/test2_souporcell_vs_gmmdemux_donor_match.csv", "donor_match/test2/souporcell_vs_gmmdemux/test2_souporcell_vs_gmmdemux_intersect_assignment_after_match.csv", "donor_match/test2/souporcell_vs_hasheddrops", "donor_match/test2/souporcell_vs_hasheddrops/test2_souporcell_vs_hasheddrops_all_assignment_after_match.csv", + "donor_match/test2/souporcell_vs_hasheddrops/test2_souporcell_vs_hasheddrops_concordance_heatmap.png", "donor_match/test2/souporcell_vs_hasheddrops/test2_souporcell_vs_hasheddrops_correlation_res.csv", "donor_match/test2/souporcell_vs_hasheddrops/test2_souporcell_vs_hasheddrops_donor_match.csv", "donor_match/test2/souporcell_vs_hasheddrops/test2_souporcell_vs_hasheddrops_intersect_assignment_after_match.csv", "donor_match/test2/souporcell_vs_hashsolo", "donor_match/test2/souporcell_vs_hashsolo/test2_souporcell_vs_hashsolo_all_assignment_after_match.csv", + "donor_match/test2/souporcell_vs_hashsolo/test2_souporcell_vs_hashsolo_concordance_heatmap.png", "donor_match/test2/souporcell_vs_hashsolo/test2_souporcell_vs_hashsolo_correlation_res.csv", "donor_match/test2/souporcell_vs_hashsolo/test2_souporcell_vs_hashsolo_donor_match.csv", "donor_match/test2/souporcell_vs_hashsolo/test2_souporcell_vs_hashsolo_intersect_assignment_after_match.csv", "donor_match/test2/souporcell_vs_htodemux", "donor_match/test2/souporcell_vs_htodemux/test2_souporcell_vs_htodemux_all_assignment_after_match.csv", + "donor_match/test2/souporcell_vs_htodemux/test2_souporcell_vs_htodemux_concordance_heatmap.png", "donor_match/test2/souporcell_vs_htodemux/test2_souporcell_vs_htodemux_correlation_res.csv", "donor_match/test2/souporcell_vs_htodemux/test2_souporcell_vs_htodemux_donor_match.csv", "donor_match/test2/souporcell_vs_htodemux/test2_souporcell_vs_htodemux_intersect_assignment_after_match.csv", "donor_match/test2/souporcell_vs_multiseq", "donor_match/test2/souporcell_vs_multiseq/test2_souporcell_vs_multiseq_all_assignment_after_match.csv", + "donor_match/test2/souporcell_vs_multiseq/test2_souporcell_vs_multiseq_concordance_heatmap.png", "donor_match/test2/souporcell_vs_multiseq/test2_souporcell_vs_multiseq_correlation_res.csv", "donor_match/test2/souporcell_vs_multiseq/test2_souporcell_vs_multiseq_donor_match.csv", "donor_match/test2/souporcell_vs_multiseq/test2_souporcell_vs_multiseq_intersect_assignment_after_match.csv", @@ -309,81 +341,97 @@ "donor_match/test3", "donor_match/test3/freemuxlet_vs_bff_consensuscall", "donor_match/test3/freemuxlet_vs_bff_consensuscall/test3_freemuxlet_vs_bff_consensuscall_all_assignment_after_match.csv", + "donor_match/test3/freemuxlet_vs_bff_consensuscall/test3_freemuxlet_vs_bff_consensuscall_concordance_heatmap.png", "donor_match/test3/freemuxlet_vs_bff_consensuscall/test3_freemuxlet_vs_bff_consensuscall_correlation_res.csv", "donor_match/test3/freemuxlet_vs_bff_consensuscall/test3_freemuxlet_vs_bff_consensuscall_donor_match.csv", "donor_match/test3/freemuxlet_vs_bff_consensuscall/test3_freemuxlet_vs_bff_consensuscall_intersect_assignment_after_match.csv", "donor_match/test3/freemuxlet_vs_bff_raw", "donor_match/test3/freemuxlet_vs_bff_raw/test3_freemuxlet_vs_bff_raw_all_assignment_after_match.csv", + "donor_match/test3/freemuxlet_vs_bff_raw/test3_freemuxlet_vs_bff_raw_concordance_heatmap.png", "donor_match/test3/freemuxlet_vs_bff_raw/test3_freemuxlet_vs_bff_raw_correlation_res.csv", "donor_match/test3/freemuxlet_vs_bff_raw/test3_freemuxlet_vs_bff_raw_donor_match.csv", "donor_match/test3/freemuxlet_vs_bff_raw/test3_freemuxlet_vs_bff_raw_intersect_assignment_after_match.csv", "donor_match/test3/freemuxlet_vs_demuxem", "donor_match/test3/freemuxlet_vs_demuxem/test3_freemuxlet_vs_demuxem_all_assignment_after_match.csv", + "donor_match/test3/freemuxlet_vs_demuxem/test3_freemuxlet_vs_demuxem_concordance_heatmap.png", "donor_match/test3/freemuxlet_vs_demuxem/test3_freemuxlet_vs_demuxem_correlation_res.csv", "donor_match/test3/freemuxlet_vs_demuxem/test3_freemuxlet_vs_demuxem_donor_match.csv", "donor_match/test3/freemuxlet_vs_demuxem/test3_freemuxlet_vs_demuxem_intersect_assignment_after_match.csv", "donor_match/test3/freemuxlet_vs_gmmdemux", "donor_match/test3/freemuxlet_vs_gmmdemux/test3_freemuxlet_vs_gmmdemux_all_assignment_after_match.csv", + "donor_match/test3/freemuxlet_vs_gmmdemux/test3_freemuxlet_vs_gmmdemux_concordance_heatmap.png", "donor_match/test3/freemuxlet_vs_gmmdemux/test3_freemuxlet_vs_gmmdemux_correlation_res.csv", "donor_match/test3/freemuxlet_vs_gmmdemux/test3_freemuxlet_vs_gmmdemux_donor_match.csv", "donor_match/test3/freemuxlet_vs_gmmdemux/test3_freemuxlet_vs_gmmdemux_intersect_assignment_after_match.csv", "donor_match/test3/freemuxlet_vs_hasheddrops", "donor_match/test3/freemuxlet_vs_hasheddrops/test3_freemuxlet_vs_hasheddrops_all_assignment_after_match.csv", + "donor_match/test3/freemuxlet_vs_hasheddrops/test3_freemuxlet_vs_hasheddrops_concordance_heatmap.png", "donor_match/test3/freemuxlet_vs_hasheddrops/test3_freemuxlet_vs_hasheddrops_correlation_res.csv", "donor_match/test3/freemuxlet_vs_hasheddrops/test3_freemuxlet_vs_hasheddrops_donor_match.csv", "donor_match/test3/freemuxlet_vs_hasheddrops/test3_freemuxlet_vs_hasheddrops_intersect_assignment_after_match.csv", "donor_match/test3/freemuxlet_vs_hashsolo", "donor_match/test3/freemuxlet_vs_hashsolo/test3_freemuxlet_vs_hashsolo_all_assignment_after_match.csv", + "donor_match/test3/freemuxlet_vs_hashsolo/test3_freemuxlet_vs_hashsolo_concordance_heatmap.png", "donor_match/test3/freemuxlet_vs_hashsolo/test3_freemuxlet_vs_hashsolo_correlation_res.csv", "donor_match/test3/freemuxlet_vs_hashsolo/test3_freemuxlet_vs_hashsolo_donor_match.csv", "donor_match/test3/freemuxlet_vs_hashsolo/test3_freemuxlet_vs_hashsolo_intersect_assignment_after_match.csv", "donor_match/test3/freemuxlet_vs_htodemux", "donor_match/test3/freemuxlet_vs_htodemux/test3_freemuxlet_vs_htodemux_all_assignment_after_match.csv", + "donor_match/test3/freemuxlet_vs_htodemux/test3_freemuxlet_vs_htodemux_concordance_heatmap.png", "donor_match/test3/freemuxlet_vs_htodemux/test3_freemuxlet_vs_htodemux_correlation_res.csv", "donor_match/test3/freemuxlet_vs_htodemux/test3_freemuxlet_vs_htodemux_donor_match.csv", "donor_match/test3/freemuxlet_vs_htodemux/test3_freemuxlet_vs_htodemux_intersect_assignment_after_match.csv", "donor_match/test3/freemuxlet_vs_multiseq", "donor_match/test3/freemuxlet_vs_multiseq/test3_freemuxlet_vs_multiseq_all_assignment_after_match.csv", + "donor_match/test3/freemuxlet_vs_multiseq/test3_freemuxlet_vs_multiseq_concordance_heatmap.png", "donor_match/test3/freemuxlet_vs_multiseq/test3_freemuxlet_vs_multiseq_correlation_res.csv", "donor_match/test3/freemuxlet_vs_multiseq/test3_freemuxlet_vs_multiseq_donor_match.csv", "donor_match/test3/freemuxlet_vs_multiseq/test3_freemuxlet_vs_multiseq_intersect_assignment_after_match.csv", "donor_match/test3/souporcell_vs_bff_consensuscall", "donor_match/test3/souporcell_vs_bff_consensuscall/test3_souporcell_vs_bff_consensuscall_all_assignment_after_match.csv", + "donor_match/test3/souporcell_vs_bff_consensuscall/test3_souporcell_vs_bff_consensuscall_concordance_heatmap.png", "donor_match/test3/souporcell_vs_bff_consensuscall/test3_souporcell_vs_bff_consensuscall_correlation_res.csv", "donor_match/test3/souporcell_vs_bff_consensuscall/test3_souporcell_vs_bff_consensuscall_donor_match.csv", "donor_match/test3/souporcell_vs_bff_consensuscall/test3_souporcell_vs_bff_consensuscall_intersect_assignment_after_match.csv", "donor_match/test3/souporcell_vs_bff_raw", "donor_match/test3/souporcell_vs_bff_raw/test3_souporcell_vs_bff_raw_all_assignment_after_match.csv", + "donor_match/test3/souporcell_vs_bff_raw/test3_souporcell_vs_bff_raw_concordance_heatmap.png", "donor_match/test3/souporcell_vs_bff_raw/test3_souporcell_vs_bff_raw_correlation_res.csv", "donor_match/test3/souporcell_vs_bff_raw/test3_souporcell_vs_bff_raw_donor_match.csv", "donor_match/test3/souporcell_vs_bff_raw/test3_souporcell_vs_bff_raw_intersect_assignment_after_match.csv", "donor_match/test3/souporcell_vs_demuxem", "donor_match/test3/souporcell_vs_demuxem/test3_souporcell_vs_demuxem_all_assignment_after_match.csv", + "donor_match/test3/souporcell_vs_demuxem/test3_souporcell_vs_demuxem_concordance_heatmap.png", "donor_match/test3/souporcell_vs_demuxem/test3_souporcell_vs_demuxem_correlation_res.csv", "donor_match/test3/souporcell_vs_demuxem/test3_souporcell_vs_demuxem_donor_match.csv", "donor_match/test3/souporcell_vs_demuxem/test3_souporcell_vs_demuxem_intersect_assignment_after_match.csv", "donor_match/test3/souporcell_vs_gmmdemux", "donor_match/test3/souporcell_vs_gmmdemux/test3_souporcell_vs_gmmdemux_all_assignment_after_match.csv", + "donor_match/test3/souporcell_vs_gmmdemux/test3_souporcell_vs_gmmdemux_concordance_heatmap.png", "donor_match/test3/souporcell_vs_gmmdemux/test3_souporcell_vs_gmmdemux_correlation_res.csv", "donor_match/test3/souporcell_vs_gmmdemux/test3_souporcell_vs_gmmdemux_donor_match.csv", "donor_match/test3/souporcell_vs_gmmdemux/test3_souporcell_vs_gmmdemux_intersect_assignment_after_match.csv", "donor_match/test3/souporcell_vs_hasheddrops", "donor_match/test3/souporcell_vs_hasheddrops/test3_souporcell_vs_hasheddrops_all_assignment_after_match.csv", + "donor_match/test3/souporcell_vs_hasheddrops/test3_souporcell_vs_hasheddrops_concordance_heatmap.png", "donor_match/test3/souporcell_vs_hasheddrops/test3_souporcell_vs_hasheddrops_correlation_res.csv", "donor_match/test3/souporcell_vs_hasheddrops/test3_souporcell_vs_hasheddrops_donor_match.csv", "donor_match/test3/souporcell_vs_hasheddrops/test3_souporcell_vs_hasheddrops_intersect_assignment_after_match.csv", "donor_match/test3/souporcell_vs_hashsolo", "donor_match/test3/souporcell_vs_hashsolo/test3_souporcell_vs_hashsolo_all_assignment_after_match.csv", + "donor_match/test3/souporcell_vs_hashsolo/test3_souporcell_vs_hashsolo_concordance_heatmap.png", "donor_match/test3/souporcell_vs_hashsolo/test3_souporcell_vs_hashsolo_correlation_res.csv", "donor_match/test3/souporcell_vs_hashsolo/test3_souporcell_vs_hashsolo_donor_match.csv", "donor_match/test3/souporcell_vs_hashsolo/test3_souporcell_vs_hashsolo_intersect_assignment_after_match.csv", "donor_match/test3/souporcell_vs_htodemux", "donor_match/test3/souporcell_vs_htodemux/test3_souporcell_vs_htodemux_all_assignment_after_match.csv", + "donor_match/test3/souporcell_vs_htodemux/test3_souporcell_vs_htodemux_concordance_heatmap.png", "donor_match/test3/souporcell_vs_htodemux/test3_souporcell_vs_htodemux_correlation_res.csv", "donor_match/test3/souporcell_vs_htodemux/test3_souporcell_vs_htodemux_donor_match.csv", "donor_match/test3/souporcell_vs_htodemux/test3_souporcell_vs_htodemux_intersect_assignment_after_match.csv", "donor_match/test3/souporcell_vs_multiseq", "donor_match/test3/souporcell_vs_multiseq/test3_souporcell_vs_multiseq_all_assignment_after_match.csv", + "donor_match/test3/souporcell_vs_multiseq/test3_souporcell_vs_multiseq_concordance_heatmap.png", "donor_match/test3/souporcell_vs_multiseq/test3_souporcell_vs_multiseq_correlation_res.csv", "donor_match/test3/souporcell_vs_multiseq/test3_souporcell_vs_multiseq_donor_match.csv", "donor_match/test3/souporcell_vs_multiseq/test3_souporcell_vs_multiseq_intersect_assignment_after_match.csv", @@ -406,6 +454,7 @@ "find_variants/test1/subset_gt_donors/test1_vireo.vcf.gz", "find_variants/test1/test1_all_representative_variants.csv", "find_variants/test1/test1_donor_specific_variants.csv", + "find_variants/test1/test1_donor_specific_variants_upset.png", "find_variants/test1/test1_vireo_variants.csv", "find_variants/test2", "find_variants/test2/hto-1", @@ -421,6 +470,7 @@ "find_variants/test2/subset_gt_donors/test2_vireo.vcf.gz", "find_variants/test2/test2_all_representative_variants.csv", "find_variants/test2/test2_donor_specific_variants.csv", + "find_variants/test2/test2_donor_specific_variants_upset.png", "find_variants/test2/test2_vireo_variants.csv", "find_variants/test3", "find_variants/test3/hto-1", @@ -436,6 +486,7 @@ "find_variants/test3/subset_gt_donors/test3_vireo.vcf.gz", "find_variants/test3/test3_all_representative_variants.csv", "find_variants/test3/test3_donor_specific_variants.csv", + "find_variants/test3/test3_donor_specific_variants_upset.png", "find_variants/test3/test3_vireo_variants.csv", "genetic", "genetic/popscle", @@ -589,24 +640,30 @@ "hashing/hasheddrops", "hashing/hasheddrops/test1", "hashing/hasheddrops/test1/test1_emptyDrops.csv", + "hashing/hasheddrops/test1/test1_emptyDrops.png", "hashing/hasheddrops/test1/test1_emptyDrops.rds", "hashing/hasheddrops/test1/test1_hasheddrops.rds", "hashing/hasheddrops/test1/test1_id_to_hash.csv", "hashing/hasheddrops/test1/test1_params_hasheddrops.csv", + "hashing/hasheddrops/test1/test1_plot_hasheddrops.png", "hashing/hasheddrops/test1/test1_results_hasheddrops.csv", "hashing/hasheddrops/test2", "hashing/hasheddrops/test2/test2_emptyDrops.csv", + "hashing/hasheddrops/test2/test2_emptyDrops.png", "hashing/hasheddrops/test2/test2_emptyDrops.rds", "hashing/hasheddrops/test2/test2_hasheddrops.rds", "hashing/hasheddrops/test2/test2_id_to_hash.csv", "hashing/hasheddrops/test2/test2_params_hasheddrops.csv", + "hashing/hasheddrops/test2/test2_plot_hasheddrops.png", "hashing/hasheddrops/test2/test2_results_hasheddrops.csv", "hashing/hasheddrops/test3", "hashing/hasheddrops/test3/test3_emptyDrops.csv", + "hashing/hasheddrops/test3/test3_emptyDrops.png", "hashing/hasheddrops/test3/test3_emptyDrops.rds", "hashing/hasheddrops/test3/test3_hasheddrops.rds", "hashing/hasheddrops/test3/test3_id_to_hash.csv", "hashing/hasheddrops/test3/test3_params_hasheddrops.csv", + "hashing/hasheddrops/test3/test3_plot_hasheddrops.png", "hashing/hasheddrops/test3/test3_results_hasheddrops.csv", "hashing/hashsolo", "hashing/hashsolo/test1", @@ -628,6 +685,11 @@ "hashing/htodemux/test1/test1_htodemux.rds", "hashing/htodemux/test1/test1_params_htodemux.csv", "hashing/htodemux/test1/visualization", + "hashing/htodemux/test1/visualization/test1_featureScatter_htodemux.jpeg", + "hashing/htodemux/test1/visualization/test1_heatMap_htodemux.jpeg", + "hashing/htodemux/test1/visualization/test1_ridge_htodemux.jpeg", + "hashing/htodemux/test1/visualization/test1_tSNE_htodemux.jpeg", + "hashing/htodemux/test1/visualization/test1_violinPlot_htodemux.jpeg", "hashing/htodemux/test1/visualization/test1_visual_params_htodemux.csv", "hashing/htodemux/test2", "hashing/htodemux/test2/test2_assignment_htodemux.csv", @@ -635,6 +697,11 @@ "hashing/htodemux/test2/test2_htodemux.rds", "hashing/htodemux/test2/test2_params_htodemux.csv", "hashing/htodemux/test2/visualization", + "hashing/htodemux/test2/visualization/test2_featureScatter_htodemux.jpeg", + "hashing/htodemux/test2/visualization/test2_heatMap_htodemux.jpeg", + "hashing/htodemux/test2/visualization/test2_ridge_htodemux.jpeg", + "hashing/htodemux/test2/visualization/test2_tSNE_htodemux.jpeg", + "hashing/htodemux/test2/visualization/test2_violinPlot_htodemux.jpeg", "hashing/htodemux/test2/visualization/test2_visual_params_htodemux.csv", "hashing/htodemux/test3", "hashing/htodemux/test3/test3_assignment_htodemux.csv", @@ -642,6 +709,11 @@ "hashing/htodemux/test3/test3_htodemux.rds", "hashing/htodemux/test3/test3_params_htodemux.csv", "hashing/htodemux/test3/visualization", + "hashing/htodemux/test3/visualization/test3_featureScatter_htodemux.jpeg", + "hashing/htodemux/test3/visualization/test3_heatMap_htodemux.jpeg", + "hashing/htodemux/test3/visualization/test3_ridge_htodemux.jpeg", + "hashing/htodemux/test3/visualization/test3_tSNE_htodemux.jpeg", + "hashing/htodemux/test3/visualization/test3_violinPlot_htodemux.jpeg", "hashing/htodemux/test3/visualization/test3_visual_params_htodemux.csv", "hashing/multiseqdemux", "hashing/multiseqdemux/test1", @@ -1112,7 +1184,7 @@ "test3_GT_donors.vireo.vcf.gz:md5,921ce18ca982b86589477432fce4e3e6" ] ], - "timestamp": "2026-08-23T07:37:43.847132463", + "timestamp": "2026-09-05T21:24:42.704033458", "meta": { "nf-test": "0.9.5", "nextflow": "26.04.6" From b81dcd2fd21f2a876538154c77e143cbb846c33a Mon Sep 17 00:00:00 2001 From: LuisHeinzlmeier Date: Sat, 12 Sep 2026 19:06:28 +0000 Subject: [PATCH 57/74] update remaining snapshots --- tests/test_genetic.nf.test.snap | 5 +- tests/test_hashing.nf.test.snap | 109 +++++++++++++++++++++++++++++++- 2 files changed, 111 insertions(+), 3 deletions(-) diff --git a/tests/test_genetic.nf.test.snap b/tests/test_genetic.nf.test.snap index 4dd2bd2f..7aba3c66 100644 --- a/tests/test_genetic.nf.test.snap +++ b/tests/test_genetic.nf.test.snap @@ -59,14 +59,17 @@ "donor_match", "donor_match/test1", "donor_match/test1/freemuxlet_vs_souporcell", + "donor_match/test1/freemuxlet_vs_souporcell/test1_freemuxlet_vs_souporcell_concordance_heatmap.png", "donor_match/test1/freemuxlet_vs_souporcell/test1_freemuxlet_vs_souporcell_correlation_res.csv", "donor_match/test1/freemuxlet_vs_souporcell/test1_freemuxlet_vs_souporcell_donor_match.csv", "donor_match/test2", "donor_match/test2/freemuxlet_vs_souporcell", + "donor_match/test2/freemuxlet_vs_souporcell/test2_freemuxlet_vs_souporcell_concordance_heatmap.png", "donor_match/test2/freemuxlet_vs_souporcell/test2_freemuxlet_vs_souporcell_correlation_res.csv", "donor_match/test2/freemuxlet_vs_souporcell/test2_freemuxlet_vs_souporcell_donor_match.csv", "donor_match/test3", "donor_match/test3/freemuxlet_vs_souporcell", + "donor_match/test3/freemuxlet_vs_souporcell/test3_freemuxlet_vs_souporcell_concordance_heatmap.png", "donor_match/test3/freemuxlet_vs_souporcell/test3_freemuxlet_vs_souporcell_correlation_res.csv", "donor_match/test3/freemuxlet_vs_souporcell/test3_freemuxlet_vs_souporcell_donor_match.csv", "genetic", @@ -264,7 +267,7 @@ "test3_GT_donors.vireo.vcf.gz:md5,921ce18ca982b86589477432fce4e3e6" ] ], - "timestamp": "2026-08-23T07:47:07.819251449", + "timestamp": "2026-09-12T16:58:01.78457062", "meta": { "nf-test": "0.9.5", "nextflow": "26.04.6" diff --git a/tests/test_hashing.nf.test.snap b/tests/test_hashing.nf.test.snap index ccb489b4..eaca1b0f 100644 --- a/tests/test_hashing.nf.test.snap +++ b/tests/test_hashing.nf.test.snap @@ -86,257 +86,341 @@ "donor_match", "donor_match/test1", "donor_match/test1/bff_raw_vs_bff_consensuscall", + "donor_match/test1/bff_raw_vs_bff_consensuscall/test1_bff_raw_vs_bff_consensuscall_concordance_heatmap.png", "donor_match/test1/bff_raw_vs_bff_consensuscall/test1_bff_raw_vs_bff_consensuscall_correlation_res.csv", "donor_match/test1/bff_raw_vs_bff_consensuscall/test1_bff_raw_vs_bff_consensuscall_donor_match.csv", "donor_match/test1/demuxem_vs_bff_consensuscall", + "donor_match/test1/demuxem_vs_bff_consensuscall/test1_demuxem_vs_bff_consensuscall_concordance_heatmap.png", "donor_match/test1/demuxem_vs_bff_consensuscall/test1_demuxem_vs_bff_consensuscall_correlation_res.csv", "donor_match/test1/demuxem_vs_bff_consensuscall/test1_demuxem_vs_bff_consensuscall_donor_match.csv", "donor_match/test1/demuxem_vs_bff_raw", + "donor_match/test1/demuxem_vs_bff_raw/test1_demuxem_vs_bff_raw_concordance_heatmap.png", "donor_match/test1/demuxem_vs_bff_raw/test1_demuxem_vs_bff_raw_correlation_res.csv", "donor_match/test1/demuxem_vs_bff_raw/test1_demuxem_vs_bff_raw_donor_match.csv", "donor_match/test1/demuxem_vs_gmmdemux", + "donor_match/test1/demuxem_vs_gmmdemux/test1_demuxem_vs_gmmdemux_concordance_heatmap.png", "donor_match/test1/demuxem_vs_gmmdemux/test1_demuxem_vs_gmmdemux_correlation_res.csv", "donor_match/test1/demuxem_vs_gmmdemux/test1_demuxem_vs_gmmdemux_donor_match.csv", "donor_match/test1/demuxem_vs_hasheddrops", + "donor_match/test1/demuxem_vs_hasheddrops/test1_demuxem_vs_hasheddrops_concordance_heatmap.png", "donor_match/test1/demuxem_vs_hasheddrops/test1_demuxem_vs_hasheddrops_correlation_res.csv", "donor_match/test1/demuxem_vs_hasheddrops/test1_demuxem_vs_hasheddrops_donor_match.csv", "donor_match/test1/demuxem_vs_hashsolo", + "donor_match/test1/demuxem_vs_hashsolo/test1_demuxem_vs_hashsolo_concordance_heatmap.png", "donor_match/test1/demuxem_vs_hashsolo/test1_demuxem_vs_hashsolo_correlation_res.csv", "donor_match/test1/demuxem_vs_hashsolo/test1_demuxem_vs_hashsolo_donor_match.csv", "donor_match/test1/demuxem_vs_htodemux", + "donor_match/test1/demuxem_vs_htodemux/test1_demuxem_vs_htodemux_concordance_heatmap.png", "donor_match/test1/demuxem_vs_htodemux/test1_demuxem_vs_htodemux_correlation_res.csv", "donor_match/test1/demuxem_vs_htodemux/test1_demuxem_vs_htodemux_donor_match.csv", "donor_match/test1/demuxem_vs_multiseq", + "donor_match/test1/demuxem_vs_multiseq/test1_demuxem_vs_multiseq_concordance_heatmap.png", "donor_match/test1/demuxem_vs_multiseq/test1_demuxem_vs_multiseq_correlation_res.csv", "donor_match/test1/demuxem_vs_multiseq/test1_demuxem_vs_multiseq_donor_match.csv", "donor_match/test1/gmmdemux_vs_bff_consensuscall", + "donor_match/test1/gmmdemux_vs_bff_consensuscall/test1_gmmdemux_vs_bff_consensuscall_concordance_heatmap.png", "donor_match/test1/gmmdemux_vs_bff_consensuscall/test1_gmmdemux_vs_bff_consensuscall_correlation_res.csv", "donor_match/test1/gmmdemux_vs_bff_consensuscall/test1_gmmdemux_vs_bff_consensuscall_donor_match.csv", "donor_match/test1/gmmdemux_vs_bff_raw", + "donor_match/test1/gmmdemux_vs_bff_raw/test1_gmmdemux_vs_bff_raw_concordance_heatmap.png", "donor_match/test1/gmmdemux_vs_bff_raw/test1_gmmdemux_vs_bff_raw_correlation_res.csv", "donor_match/test1/gmmdemux_vs_bff_raw/test1_gmmdemux_vs_bff_raw_donor_match.csv", "donor_match/test1/hasheddrops_vs_bff_consensuscall", + "donor_match/test1/hasheddrops_vs_bff_consensuscall/test1_hasheddrops_vs_bff_consensuscall_concordance_heatmap.png", "donor_match/test1/hasheddrops_vs_bff_consensuscall/test1_hasheddrops_vs_bff_consensuscall_correlation_res.csv", "donor_match/test1/hasheddrops_vs_bff_consensuscall/test1_hasheddrops_vs_bff_consensuscall_donor_match.csv", "donor_match/test1/hasheddrops_vs_bff_raw", + "donor_match/test1/hasheddrops_vs_bff_raw/test1_hasheddrops_vs_bff_raw_concordance_heatmap.png", "donor_match/test1/hasheddrops_vs_bff_raw/test1_hasheddrops_vs_bff_raw_correlation_res.csv", "donor_match/test1/hasheddrops_vs_bff_raw/test1_hasheddrops_vs_bff_raw_donor_match.csv", "donor_match/test1/hasheddrops_vs_gmmdemux", + "donor_match/test1/hasheddrops_vs_gmmdemux/test1_hasheddrops_vs_gmmdemux_concordance_heatmap.png", "donor_match/test1/hasheddrops_vs_gmmdemux/test1_hasheddrops_vs_gmmdemux_correlation_res.csv", "donor_match/test1/hasheddrops_vs_gmmdemux/test1_hasheddrops_vs_gmmdemux_donor_match.csv", "donor_match/test1/hasheddrops_vs_htodemux", + "donor_match/test1/hasheddrops_vs_htodemux/test1_hasheddrops_vs_htodemux_concordance_heatmap.png", "donor_match/test1/hasheddrops_vs_htodemux/test1_hasheddrops_vs_htodemux_correlation_res.csv", "donor_match/test1/hasheddrops_vs_htodemux/test1_hasheddrops_vs_htodemux_donor_match.csv", "donor_match/test1/hasheddrops_vs_multiseq", + "donor_match/test1/hasheddrops_vs_multiseq/test1_hasheddrops_vs_multiseq_concordance_heatmap.png", "donor_match/test1/hasheddrops_vs_multiseq/test1_hasheddrops_vs_multiseq_correlation_res.csv", "donor_match/test1/hasheddrops_vs_multiseq/test1_hasheddrops_vs_multiseq_donor_match.csv", "donor_match/test1/hashsolo_vs_bff_consensuscall", + "donor_match/test1/hashsolo_vs_bff_consensuscall/test1_hashsolo_vs_bff_consensuscall_concordance_heatmap.png", "donor_match/test1/hashsolo_vs_bff_consensuscall/test1_hashsolo_vs_bff_consensuscall_correlation_res.csv", "donor_match/test1/hashsolo_vs_bff_consensuscall/test1_hashsolo_vs_bff_consensuscall_donor_match.csv", "donor_match/test1/hashsolo_vs_bff_raw", + "donor_match/test1/hashsolo_vs_bff_raw/test1_hashsolo_vs_bff_raw_concordance_heatmap.png", "donor_match/test1/hashsolo_vs_bff_raw/test1_hashsolo_vs_bff_raw_correlation_res.csv", "donor_match/test1/hashsolo_vs_bff_raw/test1_hashsolo_vs_bff_raw_donor_match.csv", "donor_match/test1/hashsolo_vs_gmmdemux", + "donor_match/test1/hashsolo_vs_gmmdemux/test1_hashsolo_vs_gmmdemux_concordance_heatmap.png", "donor_match/test1/hashsolo_vs_gmmdemux/test1_hashsolo_vs_gmmdemux_correlation_res.csv", "donor_match/test1/hashsolo_vs_gmmdemux/test1_hashsolo_vs_gmmdemux_donor_match.csv", "donor_match/test1/hashsolo_vs_hasheddrops", + "donor_match/test1/hashsolo_vs_hasheddrops/test1_hashsolo_vs_hasheddrops_concordance_heatmap.png", "donor_match/test1/hashsolo_vs_hasheddrops/test1_hashsolo_vs_hasheddrops_correlation_res.csv", "donor_match/test1/hashsolo_vs_hasheddrops/test1_hashsolo_vs_hasheddrops_donor_match.csv", "donor_match/test1/hashsolo_vs_htodemux", + "donor_match/test1/hashsolo_vs_htodemux/test1_hashsolo_vs_htodemux_concordance_heatmap.png", "donor_match/test1/hashsolo_vs_htodemux/test1_hashsolo_vs_htodemux_correlation_res.csv", "donor_match/test1/hashsolo_vs_htodemux/test1_hashsolo_vs_htodemux_donor_match.csv", "donor_match/test1/hashsolo_vs_multiseq", + "donor_match/test1/hashsolo_vs_multiseq/test1_hashsolo_vs_multiseq_concordance_heatmap.png", "donor_match/test1/hashsolo_vs_multiseq/test1_hashsolo_vs_multiseq_correlation_res.csv", "donor_match/test1/hashsolo_vs_multiseq/test1_hashsolo_vs_multiseq_donor_match.csv", "donor_match/test1/htodemux_vs_bff_consensuscall", + "donor_match/test1/htodemux_vs_bff_consensuscall/test1_htodemux_vs_bff_consensuscall_concordance_heatmap.png", "donor_match/test1/htodemux_vs_bff_consensuscall/test1_htodemux_vs_bff_consensuscall_correlation_res.csv", "donor_match/test1/htodemux_vs_bff_consensuscall/test1_htodemux_vs_bff_consensuscall_donor_match.csv", "donor_match/test1/htodemux_vs_bff_raw", + "donor_match/test1/htodemux_vs_bff_raw/test1_htodemux_vs_bff_raw_concordance_heatmap.png", "donor_match/test1/htodemux_vs_bff_raw/test1_htodemux_vs_bff_raw_correlation_res.csv", "donor_match/test1/htodemux_vs_bff_raw/test1_htodemux_vs_bff_raw_donor_match.csv", "donor_match/test1/htodemux_vs_gmmdemux", + "donor_match/test1/htodemux_vs_gmmdemux/test1_htodemux_vs_gmmdemux_concordance_heatmap.png", "donor_match/test1/htodemux_vs_gmmdemux/test1_htodemux_vs_gmmdemux_correlation_res.csv", "donor_match/test1/htodemux_vs_gmmdemux/test1_htodemux_vs_gmmdemux_donor_match.csv", "donor_match/test1/multiseq_vs_bff_consensuscall", + "donor_match/test1/multiseq_vs_bff_consensuscall/test1_multiseq_vs_bff_consensuscall_concordance_heatmap.png", "donor_match/test1/multiseq_vs_bff_consensuscall/test1_multiseq_vs_bff_consensuscall_correlation_res.csv", "donor_match/test1/multiseq_vs_bff_consensuscall/test1_multiseq_vs_bff_consensuscall_donor_match.csv", "donor_match/test1/multiseq_vs_bff_raw", + "donor_match/test1/multiseq_vs_bff_raw/test1_multiseq_vs_bff_raw_concordance_heatmap.png", "donor_match/test1/multiseq_vs_bff_raw/test1_multiseq_vs_bff_raw_correlation_res.csv", "donor_match/test1/multiseq_vs_bff_raw/test1_multiseq_vs_bff_raw_donor_match.csv", "donor_match/test1/multiseq_vs_gmmdemux", + "donor_match/test1/multiseq_vs_gmmdemux/test1_multiseq_vs_gmmdemux_concordance_heatmap.png", "donor_match/test1/multiseq_vs_gmmdemux/test1_multiseq_vs_gmmdemux_correlation_res.csv", "donor_match/test1/multiseq_vs_gmmdemux/test1_multiseq_vs_gmmdemux_donor_match.csv", "donor_match/test1/multiseq_vs_htodemux", + "donor_match/test1/multiseq_vs_htodemux/test1_multiseq_vs_htodemux_concordance_heatmap.png", "donor_match/test1/multiseq_vs_htodemux/test1_multiseq_vs_htodemux_correlation_res.csv", "donor_match/test1/multiseq_vs_htodemux/test1_multiseq_vs_htodemux_donor_match.csv", "donor_match/test2", "donor_match/test2/bff_raw_vs_bff_consensuscall", + "donor_match/test2/bff_raw_vs_bff_consensuscall/test2_bff_raw_vs_bff_consensuscall_concordance_heatmap.png", "donor_match/test2/bff_raw_vs_bff_consensuscall/test2_bff_raw_vs_bff_consensuscall_correlation_res.csv", "donor_match/test2/bff_raw_vs_bff_consensuscall/test2_bff_raw_vs_bff_consensuscall_donor_match.csv", "donor_match/test2/demuxem_vs_bff_consensuscall", + "donor_match/test2/demuxem_vs_bff_consensuscall/test2_demuxem_vs_bff_consensuscall_concordance_heatmap.png", "donor_match/test2/demuxem_vs_bff_consensuscall/test2_demuxem_vs_bff_consensuscall_correlation_res.csv", "donor_match/test2/demuxem_vs_bff_consensuscall/test2_demuxem_vs_bff_consensuscall_donor_match.csv", "donor_match/test2/demuxem_vs_bff_raw", + "donor_match/test2/demuxem_vs_bff_raw/test2_demuxem_vs_bff_raw_concordance_heatmap.png", "donor_match/test2/demuxem_vs_bff_raw/test2_demuxem_vs_bff_raw_correlation_res.csv", "donor_match/test2/demuxem_vs_bff_raw/test2_demuxem_vs_bff_raw_donor_match.csv", "donor_match/test2/demuxem_vs_gmmdemux", + "donor_match/test2/demuxem_vs_gmmdemux/test2_demuxem_vs_gmmdemux_concordance_heatmap.png", "donor_match/test2/demuxem_vs_gmmdemux/test2_demuxem_vs_gmmdemux_correlation_res.csv", "donor_match/test2/demuxem_vs_gmmdemux/test2_demuxem_vs_gmmdemux_donor_match.csv", "donor_match/test2/demuxem_vs_hasheddrops", + "donor_match/test2/demuxem_vs_hasheddrops/test2_demuxem_vs_hasheddrops_concordance_heatmap.png", "donor_match/test2/demuxem_vs_hasheddrops/test2_demuxem_vs_hasheddrops_correlation_res.csv", "donor_match/test2/demuxem_vs_hasheddrops/test2_demuxem_vs_hasheddrops_donor_match.csv", "donor_match/test2/demuxem_vs_hashsolo", + "donor_match/test2/demuxem_vs_hashsolo/test2_demuxem_vs_hashsolo_concordance_heatmap.png", "donor_match/test2/demuxem_vs_hashsolo/test2_demuxem_vs_hashsolo_correlation_res.csv", "donor_match/test2/demuxem_vs_hashsolo/test2_demuxem_vs_hashsolo_donor_match.csv", "donor_match/test2/demuxem_vs_htodemux", + "donor_match/test2/demuxem_vs_htodemux/test2_demuxem_vs_htodemux_concordance_heatmap.png", "donor_match/test2/demuxem_vs_htodemux/test2_demuxem_vs_htodemux_correlation_res.csv", "donor_match/test2/demuxem_vs_htodemux/test2_demuxem_vs_htodemux_donor_match.csv", "donor_match/test2/demuxem_vs_multiseq", + "donor_match/test2/demuxem_vs_multiseq/test2_demuxem_vs_multiseq_concordance_heatmap.png", "donor_match/test2/demuxem_vs_multiseq/test2_demuxem_vs_multiseq_correlation_res.csv", "donor_match/test2/demuxem_vs_multiseq/test2_demuxem_vs_multiseq_donor_match.csv", "donor_match/test2/gmmdemux_vs_bff_consensuscall", + "donor_match/test2/gmmdemux_vs_bff_consensuscall/test2_gmmdemux_vs_bff_consensuscall_concordance_heatmap.png", "donor_match/test2/gmmdemux_vs_bff_consensuscall/test2_gmmdemux_vs_bff_consensuscall_correlation_res.csv", "donor_match/test2/gmmdemux_vs_bff_consensuscall/test2_gmmdemux_vs_bff_consensuscall_donor_match.csv", "donor_match/test2/gmmdemux_vs_bff_raw", + "donor_match/test2/gmmdemux_vs_bff_raw/test2_gmmdemux_vs_bff_raw_concordance_heatmap.png", "donor_match/test2/gmmdemux_vs_bff_raw/test2_gmmdemux_vs_bff_raw_correlation_res.csv", "donor_match/test2/gmmdemux_vs_bff_raw/test2_gmmdemux_vs_bff_raw_donor_match.csv", "donor_match/test2/hasheddrops_vs_bff_consensuscall", + "donor_match/test2/hasheddrops_vs_bff_consensuscall/test2_hasheddrops_vs_bff_consensuscall_concordance_heatmap.png", "donor_match/test2/hasheddrops_vs_bff_consensuscall/test2_hasheddrops_vs_bff_consensuscall_correlation_res.csv", "donor_match/test2/hasheddrops_vs_bff_consensuscall/test2_hasheddrops_vs_bff_consensuscall_donor_match.csv", "donor_match/test2/hasheddrops_vs_bff_raw", + "donor_match/test2/hasheddrops_vs_bff_raw/test2_hasheddrops_vs_bff_raw_concordance_heatmap.png", "donor_match/test2/hasheddrops_vs_bff_raw/test2_hasheddrops_vs_bff_raw_correlation_res.csv", "donor_match/test2/hasheddrops_vs_bff_raw/test2_hasheddrops_vs_bff_raw_donor_match.csv", "donor_match/test2/hasheddrops_vs_gmmdemux", + "donor_match/test2/hasheddrops_vs_gmmdemux/test2_hasheddrops_vs_gmmdemux_concordance_heatmap.png", "donor_match/test2/hasheddrops_vs_gmmdemux/test2_hasheddrops_vs_gmmdemux_correlation_res.csv", "donor_match/test2/hasheddrops_vs_gmmdemux/test2_hasheddrops_vs_gmmdemux_donor_match.csv", "donor_match/test2/hasheddrops_vs_htodemux", + "donor_match/test2/hasheddrops_vs_htodemux/test2_hasheddrops_vs_htodemux_concordance_heatmap.png", "donor_match/test2/hasheddrops_vs_htodemux/test2_hasheddrops_vs_htodemux_correlation_res.csv", "donor_match/test2/hasheddrops_vs_htodemux/test2_hasheddrops_vs_htodemux_donor_match.csv", "donor_match/test2/hasheddrops_vs_multiseq", + "donor_match/test2/hasheddrops_vs_multiseq/test2_hasheddrops_vs_multiseq_concordance_heatmap.png", "donor_match/test2/hasheddrops_vs_multiseq/test2_hasheddrops_vs_multiseq_correlation_res.csv", "donor_match/test2/hasheddrops_vs_multiseq/test2_hasheddrops_vs_multiseq_donor_match.csv", "donor_match/test2/hashsolo_vs_bff_consensuscall", + "donor_match/test2/hashsolo_vs_bff_consensuscall/test2_hashsolo_vs_bff_consensuscall_concordance_heatmap.png", "donor_match/test2/hashsolo_vs_bff_consensuscall/test2_hashsolo_vs_bff_consensuscall_correlation_res.csv", "donor_match/test2/hashsolo_vs_bff_consensuscall/test2_hashsolo_vs_bff_consensuscall_donor_match.csv", "donor_match/test2/hashsolo_vs_bff_raw", + "donor_match/test2/hashsolo_vs_bff_raw/test2_hashsolo_vs_bff_raw_concordance_heatmap.png", "donor_match/test2/hashsolo_vs_bff_raw/test2_hashsolo_vs_bff_raw_correlation_res.csv", "donor_match/test2/hashsolo_vs_bff_raw/test2_hashsolo_vs_bff_raw_donor_match.csv", "donor_match/test2/hashsolo_vs_gmmdemux", + "donor_match/test2/hashsolo_vs_gmmdemux/test2_hashsolo_vs_gmmdemux_concordance_heatmap.png", "donor_match/test2/hashsolo_vs_gmmdemux/test2_hashsolo_vs_gmmdemux_correlation_res.csv", "donor_match/test2/hashsolo_vs_gmmdemux/test2_hashsolo_vs_gmmdemux_donor_match.csv", "donor_match/test2/hashsolo_vs_hasheddrops", + "donor_match/test2/hashsolo_vs_hasheddrops/test2_hashsolo_vs_hasheddrops_concordance_heatmap.png", "donor_match/test2/hashsolo_vs_hasheddrops/test2_hashsolo_vs_hasheddrops_correlation_res.csv", "donor_match/test2/hashsolo_vs_hasheddrops/test2_hashsolo_vs_hasheddrops_donor_match.csv", "donor_match/test2/hashsolo_vs_htodemux", + "donor_match/test2/hashsolo_vs_htodemux/test2_hashsolo_vs_htodemux_concordance_heatmap.png", "donor_match/test2/hashsolo_vs_htodemux/test2_hashsolo_vs_htodemux_correlation_res.csv", "donor_match/test2/hashsolo_vs_htodemux/test2_hashsolo_vs_htodemux_donor_match.csv", "donor_match/test2/hashsolo_vs_multiseq", + "donor_match/test2/hashsolo_vs_multiseq/test2_hashsolo_vs_multiseq_concordance_heatmap.png", "donor_match/test2/hashsolo_vs_multiseq/test2_hashsolo_vs_multiseq_correlation_res.csv", "donor_match/test2/hashsolo_vs_multiseq/test2_hashsolo_vs_multiseq_donor_match.csv", "donor_match/test2/htodemux_vs_bff_consensuscall", + "donor_match/test2/htodemux_vs_bff_consensuscall/test2_htodemux_vs_bff_consensuscall_concordance_heatmap.png", "donor_match/test2/htodemux_vs_bff_consensuscall/test2_htodemux_vs_bff_consensuscall_correlation_res.csv", "donor_match/test2/htodemux_vs_bff_consensuscall/test2_htodemux_vs_bff_consensuscall_donor_match.csv", "donor_match/test2/htodemux_vs_bff_raw", + "donor_match/test2/htodemux_vs_bff_raw/test2_htodemux_vs_bff_raw_concordance_heatmap.png", "donor_match/test2/htodemux_vs_bff_raw/test2_htodemux_vs_bff_raw_correlation_res.csv", "donor_match/test2/htodemux_vs_bff_raw/test2_htodemux_vs_bff_raw_donor_match.csv", "donor_match/test2/htodemux_vs_gmmdemux", + "donor_match/test2/htodemux_vs_gmmdemux/test2_htodemux_vs_gmmdemux_concordance_heatmap.png", "donor_match/test2/htodemux_vs_gmmdemux/test2_htodemux_vs_gmmdemux_correlation_res.csv", "donor_match/test2/htodemux_vs_gmmdemux/test2_htodemux_vs_gmmdemux_donor_match.csv", "donor_match/test2/multiseq_vs_bff_consensuscall", + "donor_match/test2/multiseq_vs_bff_consensuscall/test2_multiseq_vs_bff_consensuscall_concordance_heatmap.png", "donor_match/test2/multiseq_vs_bff_consensuscall/test2_multiseq_vs_bff_consensuscall_correlation_res.csv", "donor_match/test2/multiseq_vs_bff_consensuscall/test2_multiseq_vs_bff_consensuscall_donor_match.csv", "donor_match/test2/multiseq_vs_bff_raw", + "donor_match/test2/multiseq_vs_bff_raw/test2_multiseq_vs_bff_raw_concordance_heatmap.png", "donor_match/test2/multiseq_vs_bff_raw/test2_multiseq_vs_bff_raw_correlation_res.csv", "donor_match/test2/multiseq_vs_bff_raw/test2_multiseq_vs_bff_raw_donor_match.csv", "donor_match/test2/multiseq_vs_gmmdemux", + "donor_match/test2/multiseq_vs_gmmdemux/test2_multiseq_vs_gmmdemux_concordance_heatmap.png", "donor_match/test2/multiseq_vs_gmmdemux/test2_multiseq_vs_gmmdemux_correlation_res.csv", "donor_match/test2/multiseq_vs_gmmdemux/test2_multiseq_vs_gmmdemux_donor_match.csv", "donor_match/test2/multiseq_vs_htodemux", + "donor_match/test2/multiseq_vs_htodemux/test2_multiseq_vs_htodemux_concordance_heatmap.png", "donor_match/test2/multiseq_vs_htodemux/test2_multiseq_vs_htodemux_correlation_res.csv", "donor_match/test2/multiseq_vs_htodemux/test2_multiseq_vs_htodemux_donor_match.csv", "donor_match/test3", "donor_match/test3/bff_raw_vs_bff_consensuscall", + "donor_match/test3/bff_raw_vs_bff_consensuscall/test3_bff_raw_vs_bff_consensuscall_concordance_heatmap.png", "donor_match/test3/bff_raw_vs_bff_consensuscall/test3_bff_raw_vs_bff_consensuscall_correlation_res.csv", "donor_match/test3/bff_raw_vs_bff_consensuscall/test3_bff_raw_vs_bff_consensuscall_donor_match.csv", "donor_match/test3/demuxem_vs_bff_consensuscall", + "donor_match/test3/demuxem_vs_bff_consensuscall/test3_demuxem_vs_bff_consensuscall_concordance_heatmap.png", "donor_match/test3/demuxem_vs_bff_consensuscall/test3_demuxem_vs_bff_consensuscall_correlation_res.csv", "donor_match/test3/demuxem_vs_bff_consensuscall/test3_demuxem_vs_bff_consensuscall_donor_match.csv", "donor_match/test3/demuxem_vs_bff_raw", + "donor_match/test3/demuxem_vs_bff_raw/test3_demuxem_vs_bff_raw_concordance_heatmap.png", "donor_match/test3/demuxem_vs_bff_raw/test3_demuxem_vs_bff_raw_correlation_res.csv", "donor_match/test3/demuxem_vs_bff_raw/test3_demuxem_vs_bff_raw_donor_match.csv", "donor_match/test3/demuxem_vs_gmmdemux", + "donor_match/test3/demuxem_vs_gmmdemux/test3_demuxem_vs_gmmdemux_concordance_heatmap.png", "donor_match/test3/demuxem_vs_gmmdemux/test3_demuxem_vs_gmmdemux_correlation_res.csv", "donor_match/test3/demuxem_vs_gmmdemux/test3_demuxem_vs_gmmdemux_donor_match.csv", "donor_match/test3/demuxem_vs_hasheddrops", + "donor_match/test3/demuxem_vs_hasheddrops/test3_demuxem_vs_hasheddrops_concordance_heatmap.png", "donor_match/test3/demuxem_vs_hasheddrops/test3_demuxem_vs_hasheddrops_correlation_res.csv", "donor_match/test3/demuxem_vs_hasheddrops/test3_demuxem_vs_hasheddrops_donor_match.csv", "donor_match/test3/demuxem_vs_hashsolo", + "donor_match/test3/demuxem_vs_hashsolo/test3_demuxem_vs_hashsolo_concordance_heatmap.png", "donor_match/test3/demuxem_vs_hashsolo/test3_demuxem_vs_hashsolo_correlation_res.csv", "donor_match/test3/demuxem_vs_hashsolo/test3_demuxem_vs_hashsolo_donor_match.csv", "donor_match/test3/demuxem_vs_htodemux", + "donor_match/test3/demuxem_vs_htodemux/test3_demuxem_vs_htodemux_concordance_heatmap.png", "donor_match/test3/demuxem_vs_htodemux/test3_demuxem_vs_htodemux_correlation_res.csv", "donor_match/test3/demuxem_vs_htodemux/test3_demuxem_vs_htodemux_donor_match.csv", "donor_match/test3/demuxem_vs_multiseq", + "donor_match/test3/demuxem_vs_multiseq/test3_demuxem_vs_multiseq_concordance_heatmap.png", "donor_match/test3/demuxem_vs_multiseq/test3_demuxem_vs_multiseq_correlation_res.csv", "donor_match/test3/demuxem_vs_multiseq/test3_demuxem_vs_multiseq_donor_match.csv", "donor_match/test3/gmmdemux_vs_bff_consensuscall", + "donor_match/test3/gmmdemux_vs_bff_consensuscall/test3_gmmdemux_vs_bff_consensuscall_concordance_heatmap.png", "donor_match/test3/gmmdemux_vs_bff_consensuscall/test3_gmmdemux_vs_bff_consensuscall_correlation_res.csv", "donor_match/test3/gmmdemux_vs_bff_consensuscall/test3_gmmdemux_vs_bff_consensuscall_donor_match.csv", "donor_match/test3/gmmdemux_vs_bff_raw", + "donor_match/test3/gmmdemux_vs_bff_raw/test3_gmmdemux_vs_bff_raw_concordance_heatmap.png", "donor_match/test3/gmmdemux_vs_bff_raw/test3_gmmdemux_vs_bff_raw_correlation_res.csv", "donor_match/test3/gmmdemux_vs_bff_raw/test3_gmmdemux_vs_bff_raw_donor_match.csv", "donor_match/test3/hasheddrops_vs_bff_consensuscall", + "donor_match/test3/hasheddrops_vs_bff_consensuscall/test3_hasheddrops_vs_bff_consensuscall_concordance_heatmap.png", "donor_match/test3/hasheddrops_vs_bff_consensuscall/test3_hasheddrops_vs_bff_consensuscall_correlation_res.csv", "donor_match/test3/hasheddrops_vs_bff_consensuscall/test3_hasheddrops_vs_bff_consensuscall_donor_match.csv", "donor_match/test3/hasheddrops_vs_bff_raw", + "donor_match/test3/hasheddrops_vs_bff_raw/test3_hasheddrops_vs_bff_raw_concordance_heatmap.png", "donor_match/test3/hasheddrops_vs_bff_raw/test3_hasheddrops_vs_bff_raw_correlation_res.csv", "donor_match/test3/hasheddrops_vs_bff_raw/test3_hasheddrops_vs_bff_raw_donor_match.csv", "donor_match/test3/hasheddrops_vs_gmmdemux", + "donor_match/test3/hasheddrops_vs_gmmdemux/test3_hasheddrops_vs_gmmdemux_concordance_heatmap.png", "donor_match/test3/hasheddrops_vs_gmmdemux/test3_hasheddrops_vs_gmmdemux_correlation_res.csv", "donor_match/test3/hasheddrops_vs_gmmdemux/test3_hasheddrops_vs_gmmdemux_donor_match.csv", "donor_match/test3/hasheddrops_vs_htodemux", + "donor_match/test3/hasheddrops_vs_htodemux/test3_hasheddrops_vs_htodemux_concordance_heatmap.png", "donor_match/test3/hasheddrops_vs_htodemux/test3_hasheddrops_vs_htodemux_correlation_res.csv", "donor_match/test3/hasheddrops_vs_htodemux/test3_hasheddrops_vs_htodemux_donor_match.csv", "donor_match/test3/hasheddrops_vs_multiseq", + "donor_match/test3/hasheddrops_vs_multiseq/test3_hasheddrops_vs_multiseq_concordance_heatmap.png", "donor_match/test3/hasheddrops_vs_multiseq/test3_hasheddrops_vs_multiseq_correlation_res.csv", "donor_match/test3/hasheddrops_vs_multiseq/test3_hasheddrops_vs_multiseq_donor_match.csv", "donor_match/test3/hashsolo_vs_bff_consensuscall", + "donor_match/test3/hashsolo_vs_bff_consensuscall/test3_hashsolo_vs_bff_consensuscall_concordance_heatmap.png", "donor_match/test3/hashsolo_vs_bff_consensuscall/test3_hashsolo_vs_bff_consensuscall_correlation_res.csv", "donor_match/test3/hashsolo_vs_bff_consensuscall/test3_hashsolo_vs_bff_consensuscall_donor_match.csv", "donor_match/test3/hashsolo_vs_bff_raw", + "donor_match/test3/hashsolo_vs_bff_raw/test3_hashsolo_vs_bff_raw_concordance_heatmap.png", "donor_match/test3/hashsolo_vs_bff_raw/test3_hashsolo_vs_bff_raw_correlation_res.csv", "donor_match/test3/hashsolo_vs_bff_raw/test3_hashsolo_vs_bff_raw_donor_match.csv", "donor_match/test3/hashsolo_vs_gmmdemux", + "donor_match/test3/hashsolo_vs_gmmdemux/test3_hashsolo_vs_gmmdemux_concordance_heatmap.png", "donor_match/test3/hashsolo_vs_gmmdemux/test3_hashsolo_vs_gmmdemux_correlation_res.csv", "donor_match/test3/hashsolo_vs_gmmdemux/test3_hashsolo_vs_gmmdemux_donor_match.csv", "donor_match/test3/hashsolo_vs_hasheddrops", + "donor_match/test3/hashsolo_vs_hasheddrops/test3_hashsolo_vs_hasheddrops_concordance_heatmap.png", "donor_match/test3/hashsolo_vs_hasheddrops/test3_hashsolo_vs_hasheddrops_correlation_res.csv", "donor_match/test3/hashsolo_vs_hasheddrops/test3_hashsolo_vs_hasheddrops_donor_match.csv", "donor_match/test3/hashsolo_vs_htodemux", + "donor_match/test3/hashsolo_vs_htodemux/test3_hashsolo_vs_htodemux_concordance_heatmap.png", "donor_match/test3/hashsolo_vs_htodemux/test3_hashsolo_vs_htodemux_correlation_res.csv", "donor_match/test3/hashsolo_vs_htodemux/test3_hashsolo_vs_htodemux_donor_match.csv", "donor_match/test3/hashsolo_vs_multiseq", + "donor_match/test3/hashsolo_vs_multiseq/test3_hashsolo_vs_multiseq_concordance_heatmap.png", "donor_match/test3/hashsolo_vs_multiseq/test3_hashsolo_vs_multiseq_correlation_res.csv", "donor_match/test3/hashsolo_vs_multiseq/test3_hashsolo_vs_multiseq_donor_match.csv", "donor_match/test3/htodemux_vs_bff_consensuscall", + "donor_match/test3/htodemux_vs_bff_consensuscall/test3_htodemux_vs_bff_consensuscall_concordance_heatmap.png", "donor_match/test3/htodemux_vs_bff_consensuscall/test3_htodemux_vs_bff_consensuscall_correlation_res.csv", "donor_match/test3/htodemux_vs_bff_consensuscall/test3_htodemux_vs_bff_consensuscall_donor_match.csv", "donor_match/test3/htodemux_vs_bff_raw", + "donor_match/test3/htodemux_vs_bff_raw/test3_htodemux_vs_bff_raw_concordance_heatmap.png", "donor_match/test3/htodemux_vs_bff_raw/test3_htodemux_vs_bff_raw_correlation_res.csv", "donor_match/test3/htodemux_vs_bff_raw/test3_htodemux_vs_bff_raw_donor_match.csv", "donor_match/test3/htodemux_vs_gmmdemux", + "donor_match/test3/htodemux_vs_gmmdemux/test3_htodemux_vs_gmmdemux_concordance_heatmap.png", "donor_match/test3/htodemux_vs_gmmdemux/test3_htodemux_vs_gmmdemux_correlation_res.csv", "donor_match/test3/htodemux_vs_gmmdemux/test3_htodemux_vs_gmmdemux_donor_match.csv", "donor_match/test3/multiseq_vs_bff_consensuscall", + "donor_match/test3/multiseq_vs_bff_consensuscall/test3_multiseq_vs_bff_consensuscall_concordance_heatmap.png", "donor_match/test3/multiseq_vs_bff_consensuscall/test3_multiseq_vs_bff_consensuscall_correlation_res.csv", "donor_match/test3/multiseq_vs_bff_consensuscall/test3_multiseq_vs_bff_consensuscall_donor_match.csv", "donor_match/test3/multiseq_vs_bff_raw", + "donor_match/test3/multiseq_vs_bff_raw/test3_multiseq_vs_bff_raw_concordance_heatmap.png", "donor_match/test3/multiseq_vs_bff_raw/test3_multiseq_vs_bff_raw_correlation_res.csv", "donor_match/test3/multiseq_vs_bff_raw/test3_multiseq_vs_bff_raw_donor_match.csv", "donor_match/test3/multiseq_vs_gmmdemux", + "donor_match/test3/multiseq_vs_gmmdemux/test3_multiseq_vs_gmmdemux_concordance_heatmap.png", "donor_match/test3/multiseq_vs_gmmdemux/test3_multiseq_vs_gmmdemux_correlation_res.csv", "donor_match/test3/multiseq_vs_gmmdemux/test3_multiseq_vs_gmmdemux_donor_match.csv", "donor_match/test3/multiseq_vs_htodemux", + "donor_match/test3/multiseq_vs_htodemux/test3_multiseq_vs_htodemux_concordance_heatmap.png", "donor_match/test3/multiseq_vs_htodemux/test3_multiseq_vs_htodemux_correlation_res.csv", "donor_match/test3/multiseq_vs_htodemux/test3_multiseq_vs_htodemux_donor_match.csv", "hashing", @@ -397,24 +481,30 @@ "hashing/hasheddrops", "hashing/hasheddrops/test1", "hashing/hasheddrops/test1/test1_emptyDrops.csv", + "hashing/hasheddrops/test1/test1_emptyDrops.png", "hashing/hasheddrops/test1/test1_emptyDrops.rds", "hashing/hasheddrops/test1/test1_hasheddrops.rds", "hashing/hasheddrops/test1/test1_id_to_hash.csv", "hashing/hasheddrops/test1/test1_params_hasheddrops.csv", + "hashing/hasheddrops/test1/test1_plot_hasheddrops.png", "hashing/hasheddrops/test1/test1_results_hasheddrops.csv", "hashing/hasheddrops/test2", "hashing/hasheddrops/test2/test2_emptyDrops.csv", + "hashing/hasheddrops/test2/test2_emptyDrops.png", "hashing/hasheddrops/test2/test2_emptyDrops.rds", "hashing/hasheddrops/test2/test2_hasheddrops.rds", "hashing/hasheddrops/test2/test2_id_to_hash.csv", "hashing/hasheddrops/test2/test2_params_hasheddrops.csv", + "hashing/hasheddrops/test2/test2_plot_hasheddrops.png", "hashing/hasheddrops/test2/test2_results_hasheddrops.csv", "hashing/hasheddrops/test3", "hashing/hasheddrops/test3/test3_emptyDrops.csv", + "hashing/hasheddrops/test3/test3_emptyDrops.png", "hashing/hasheddrops/test3/test3_emptyDrops.rds", "hashing/hasheddrops/test3/test3_hasheddrops.rds", "hashing/hasheddrops/test3/test3_id_to_hash.csv", "hashing/hasheddrops/test3/test3_params_hasheddrops.csv", + "hashing/hasheddrops/test3/test3_plot_hasheddrops.png", "hashing/hasheddrops/test3/test3_results_hasheddrops.csv", "hashing/hashsolo", "hashing/hashsolo/test1", @@ -436,6 +526,11 @@ "hashing/htodemux/test1/test1_htodemux.rds", "hashing/htodemux/test1/test1_params_htodemux.csv", "hashing/htodemux/test1/visualization", + "hashing/htodemux/test1/visualization/test1_featureScatter_htodemux.jpeg", + "hashing/htodemux/test1/visualization/test1_heatMap_htodemux.jpeg", + "hashing/htodemux/test1/visualization/test1_ridge_htodemux.jpeg", + "hashing/htodemux/test1/visualization/test1_tSNE_htodemux.jpeg", + "hashing/htodemux/test1/visualization/test1_violinPlot_htodemux.jpeg", "hashing/htodemux/test1/visualization/test1_visual_params_htodemux.csv", "hashing/htodemux/test2", "hashing/htodemux/test2/test2_assignment_htodemux.csv", @@ -443,6 +538,11 @@ "hashing/htodemux/test2/test2_htodemux.rds", "hashing/htodemux/test2/test2_params_htodemux.csv", "hashing/htodemux/test2/visualization", + "hashing/htodemux/test2/visualization/test2_featureScatter_htodemux.jpeg", + "hashing/htodemux/test2/visualization/test2_heatMap_htodemux.jpeg", + "hashing/htodemux/test2/visualization/test2_ridge_htodemux.jpeg", + "hashing/htodemux/test2/visualization/test2_tSNE_htodemux.jpeg", + "hashing/htodemux/test2/visualization/test2_violinPlot_htodemux.jpeg", "hashing/htodemux/test2/visualization/test2_visual_params_htodemux.csv", "hashing/htodemux/test3", "hashing/htodemux/test3/test3_assignment_htodemux.csv", @@ -450,6 +550,11 @@ "hashing/htodemux/test3/test3_htodemux.rds", "hashing/htodemux/test3/test3_params_htodemux.csv", "hashing/htodemux/test3/visualization", + "hashing/htodemux/test3/visualization/test3_featureScatter_htodemux.jpeg", + "hashing/htodemux/test3/visualization/test3_heatMap_htodemux.jpeg", + "hashing/htodemux/test3/visualization/test3_ridge_htodemux.jpeg", + "hashing/htodemux/test3/visualization/test3_tSNE_htodemux.jpeg", + "hashing/htodemux/test3/visualization/test3_violinPlot_htodemux.jpeg", "hashing/htodemux/test3/visualization/test3_visual_params_htodemux.csv", "hashing/multiseqdemux", "hashing/multiseqdemux/test1", @@ -766,10 +871,10 @@ ], "No VCF files" ], - "timestamp": "2026-06-25T14:03:38.851764", + "timestamp": "2026-09-12T16:38:17.799784922", "meta": { "nf-test": "0.9.5", - "nextflow": "26.04.3" + "nextflow": "26.04.6" } } } \ No newline at end of file From 5d2de44ee4f876c1b6c91e13208577950ec2b170 Mon Sep 17 00:00:00 2001 From: LuisHeinzlmeier Date: Sun, 13 Sep 2026 10:42:27 +0200 Subject: [PATCH 58/74] fix test_hashing --- tests/.nftignore | 1 + tests/default.nf.test.snap | 3 --- tests/test_hashing.nf.test.snap | 3 --- 3 files changed, 1 insertion(+), 6 deletions(-) diff --git a/tests/.nftignore b/tests/.nftignore index 22a7ad43..b07adb15 100644 --- a/tests/.nftignore +++ b/tests/.nftignore @@ -15,6 +15,7 @@ hashing/htodemux/*/*_htodemux.rds hashing/multiseqdemux/*/*_multiseqdemux.rds hashing/htodemux/*/visualization/*_tSNE_htodemux.jpeg hashing/preprocessing/*/*_preprocessed.rds +hashing/hashsolo/*/*_hashsolo.h5ad genetic/popscle/freemuxlet/*/*.clust1.vcf.gz genetic/souporcell/*/*/ambient_rna.txt genetic/souporcell/*/*/cluster_genotypes.vcf diff --git a/tests/default.nf.test.snap b/tests/default.nf.test.snap index c681c94c..169bab14 100644 --- a/tests/default.nf.test.snap +++ b/tests/default.nf.test.snap @@ -1108,13 +1108,10 @@ "test3_params_hasheddrops.csv:md5,b15e94f887e3ada8f790ea18eb791d8b", "test3_results_hasheddrops.csv:md5,230bcd615d3ac3d7ae8eb37ebe67f016", "test1_assignment_hashsolo.csv:md5,7763ee9e2ef20c353ab386f55f805b33", - "test1_hashsolo.h5ad:md5,41ba3e91669bb4d52a7eb214186d36ea", "test1_params_hashsolo.csv:md5,e39b2354a531ff7c65a0d3d0ad500a81", "test2_assignment_hashsolo.csv:md5,7763ee9e2ef20c353ab386f55f805b33", - "test2_hashsolo.h5ad:md5,41ba3e91669bb4d52a7eb214186d36ea", "test2_params_hashsolo.csv:md5,581ecdaff2557e6402ae2acf90d4167f", "test3_assignment_hashsolo.csv:md5,7763ee9e2ef20c353ab386f55f805b33", - "test3_hashsolo.h5ad:md5,41ba3e91669bb4d52a7eb214186d36ea", "test3_params_hashsolo.csv:md5,4eac179a88b560899622d0de4cd5b079", "test1_assignment_htodemux.csv:md5,7217cd9d30a3c6f7f69e8b4f8aaa679d", "test1_classification_htodemux.csv:md5,e5b0d2af4de9bdc6682c120a78e53aa0", diff --git a/tests/test_hashing.nf.test.snap b/tests/test_hashing.nf.test.snap index eaca1b0f..97bb5400 100644 --- a/tests/test_hashing.nf.test.snap +++ b/tests/test_hashing.nf.test.snap @@ -820,13 +820,10 @@ "test3_params_hasheddrops.csv:md5,b15e94f887e3ada8f790ea18eb791d8b", "test3_results_hasheddrops.csv:md5,230bcd615d3ac3d7ae8eb37ebe67f016", "test1_assignment_hashsolo.csv:md5,7763ee9e2ef20c353ab386f55f805b33", - "test1_hashsolo.h5ad:md5,41ba3e91669bb4d52a7eb214186d36ea", "test1_params_hashsolo.csv:md5,e39b2354a531ff7c65a0d3d0ad500a81", "test2_assignment_hashsolo.csv:md5,7763ee9e2ef20c353ab386f55f805b33", - "test2_hashsolo.h5ad:md5,41ba3e91669bb4d52a7eb214186d36ea", "test2_params_hashsolo.csv:md5,581ecdaff2557e6402ae2acf90d4167f", "test3_assignment_hashsolo.csv:md5,7763ee9e2ef20c353ab386f55f805b33", - "test3_hashsolo.h5ad:md5,41ba3e91669bb4d52a7eb214186d36ea", "test3_params_hashsolo.csv:md5,4eac179a88b560899622d0de4cd5b079", "test1_assignment_htodemux.csv:md5,7217cd9d30a3c6f7f69e8b4f8aaa679d", "test1_classification_htodemux.csv:md5,e5b0d2af4de9bdc6682c120a78e53aa0", From 48082eb8a37e5f8a232ab31789239c9724cbd688 Mon Sep 17 00:00:00 2001 From: LuisHeinzlmeier Date: Sun, 13 Sep 2026 11:13:58 +0200 Subject: [PATCH 59/74] fix test_genetic --- tests/.nftignore | 1 + tests/default.nf.test.snap | 3 --- tests/test_genetic.nf.test.snap | 3 --- 3 files changed, 1 insertion(+), 6 deletions(-) diff --git a/tests/.nftignore b/tests/.nftignore index b07adb15..2f8e7ce9 100644 --- a/tests/.nftignore +++ b/tests/.nftignore @@ -17,6 +17,7 @@ hashing/htodemux/*/visualization/*_tSNE_htodemux.jpeg hashing/preprocessing/*/*_preprocessed.rds hashing/hashsolo/*/*_hashsolo.h5ad genetic/popscle/freemuxlet/*/*.clust1.vcf.gz +genetic/vireo/*/*.base.vcf.gz genetic/souporcell/*/*/ambient_rna.txt genetic/souporcell/*/*/cluster_genotypes.vcf find_variants/*/subset_gt_donors/*.vcf.gz diff --git a/tests/default.nf.test.snap b/tests/default.nf.test.snap index 169bab14..4766a28d 100644 --- a/tests/default.nf.test.snap +++ b/tests/default.nf.test.snap @@ -1032,7 +1032,6 @@ "test3_genetic_overview_classification.csv:md5,812a950b1eef4515079b2d7028b6658c", "test3_genetic_summary_assignment.csv:md5,d99032c058450687415d441e474cef25", "test3_genetic_summary_classification.csv:md5,fdfbf54c3c34746624fa6afdaead2f50", - "test1.base.vcf.gz:md5,c381d7e92163710dfca2306d0d9b73f1", "test1.cells.vcf.gz:md5,f2237f599444ca39fa202b2789dc5c36", "test1.samples.tsv:md5,4ee35dd786d28bfad433c5c2b2a00e7a", "test1.tag.AD.mtx:md5,59641a7fccbd20da984734c9a6a62d17", @@ -1044,7 +1043,6 @@ "test1_prob_doublet.tsv.gz:md5,68b4ec74b77aa58421d73df2dbc92e34", "test1_prob_singlet.tsv.gz:md5,5ea247ca9d0483389a730340f3f34d0d", "test1_summary.tsv:md5,94b0ce40cc86bbe7fbacebf98e685ee7", - "test2.base.vcf.gz:md5,c381d7e92163710dfca2306d0d9b73f1", "test2.cells.vcf.gz:md5,f2237f599444ca39fa202b2789dc5c36", "test2.samples.tsv:md5,4ee35dd786d28bfad433c5c2b2a00e7a", "test2.tag.AD.mtx:md5,59641a7fccbd20da984734c9a6a62d17", @@ -1056,7 +1054,6 @@ "test2_prob_doublet.tsv.gz:md5,68b4ec74b77aa58421d73df2dbc92e34", "test2_prob_singlet.tsv.gz:md5,5ea247ca9d0483389a730340f3f34d0d", "test2_summary.tsv:md5,94b0ce40cc86bbe7fbacebf98e685ee7", - "test3.base.vcf.gz:md5,c381d7e92163710dfca2306d0d9b73f1", "test3.cells.vcf.gz:md5,f2237f599444ca39fa202b2789dc5c36", "test3.samples.tsv:md5,4ee35dd786d28bfad433c5c2b2a00e7a", "test3.tag.AD.mtx:md5,59641a7fccbd20da984734c9a6a62d17", diff --git a/tests/test_genetic.nf.test.snap b/tests/test_genetic.nf.test.snap index 7aba3c66..b0172ccf 100644 --- a/tests/test_genetic.nf.test.snap +++ b/tests/test_genetic.nf.test.snap @@ -214,7 +214,6 @@ "test3_genetic_overview_classification.csv:md5,812a950b1eef4515079b2d7028b6658c", "test3_genetic_summary_assignment.csv:md5,d99032c058450687415d441e474cef25", "test3_genetic_summary_classification.csv:md5,fdfbf54c3c34746624fa6afdaead2f50", - "test1.base.vcf.gz:md5,c381d7e92163710dfca2306d0d9b73f1", "test1.cells.vcf.gz:md5,f2237f599444ca39fa202b2789dc5c36", "test1.samples.tsv:md5,4ee35dd786d28bfad433c5c2b2a00e7a", "test1.tag.AD.mtx:md5,59641a7fccbd20da984734c9a6a62d17", @@ -226,7 +225,6 @@ "test1_prob_doublet.tsv.gz:md5,68b4ec74b77aa58421d73df2dbc92e34", "test1_prob_singlet.tsv.gz:md5,5ea247ca9d0483389a730340f3f34d0d", "test1_summary.tsv:md5,94b0ce40cc86bbe7fbacebf98e685ee7", - "test2.base.vcf.gz:md5,c381d7e92163710dfca2306d0d9b73f1", "test2.cells.vcf.gz:md5,f2237f599444ca39fa202b2789dc5c36", "test2.samples.tsv:md5,4ee35dd786d28bfad433c5c2b2a00e7a", "test2.tag.AD.mtx:md5,59641a7fccbd20da984734c9a6a62d17", @@ -238,7 +236,6 @@ "test2_prob_doublet.tsv.gz:md5,68b4ec74b77aa58421d73df2dbc92e34", "test2_prob_singlet.tsv.gz:md5,5ea247ca9d0483389a730340f3f34d0d", "test2_summary.tsv:md5,94b0ce40cc86bbe7fbacebf98e685ee7", - "test3.base.vcf.gz:md5,c381d7e92163710dfca2306d0d9b73f1", "test3.cells.vcf.gz:md5,f2237f599444ca39fa202b2789dc5c36", "test3.samples.tsv:md5,4ee35dd786d28bfad433c5c2b2a00e7a", "test3.tag.AD.mtx:md5,59641a7fccbd20da984734c9a6a62d17", From b2a8459b134839f4d235fbfff9852cbd0ab1eabe Mon Sep 17 00:00:00 2001 From: LuisHeinzlmeier Date: Mon, 21 Sep 2026 13:21:41 +0200 Subject: [PATCH 60/74] =?UTF-8?q?use=20||=C2=A0instead=20of=20|?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- subworkflows/local/genetic_demultiplexing/main.nf | 2 +- workflows/hadge.nf | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/subworkflows/local/genetic_demultiplexing/main.nf b/subworkflows/local/genetic_demultiplexing/main.nf index 64798655..a82e5378 100644 --- a/subworkflows/local/genetic_demultiplexing/main.nf +++ b/subworkflows/local/genetic_demultiplexing/main.nf @@ -61,7 +61,7 @@ workflow GENETIC_DEMULTIPLEXING { - if ( params.find_variants | methods.contains('vireo')){ + if ( params.find_variants || methods.contains('vireo')){ SAMTOOLS_INDEX(ch_samplesheet.map { meta, bam, _barcodes, _vcf -> [meta, bam] }) CELLSNP_MODEA( diff --git a/workflows/hadge.nf b/workflows/hadge.nf index a3f04e33..d6ea5732 100644 --- a/workflows/hadge.nf +++ b/workflows/hadge.nf @@ -190,7 +190,7 @@ workflow HADGE { } } - if (params.mode == 'genetic' | params.mode == 'hashing' | params.mode == 'rescue'){ + if (params.mode == 'genetic' || params.mode == 'hashing' || params.mode == 'rescue'){ CREATE_ANNDATA_MUDATA( ch_create_anndata_mudata.map { tuple -> // hto can be null in genetic mode @@ -208,7 +208,7 @@ workflow HADGE { ) // there only is a best_intersect_assignment_after_match output in donor_match and rescue mode to run FIND_VARIANTS - if ( (params.mode == 'donor_match' | params.mode == 'rescue') && params.find_variants ){ + if ( (params.mode == 'donor_match' || params.mode == 'rescue') && params.find_variants ){ ch_find_variants = DONOR_MATCH.out.best_intersect_assignment_after_match .join(ch_find_variants) From 3c379c350c73b9c4e903600da6c947c0bb168095 Mon Sep 17 00:00:00 2001 From: LuisHeinzlmeier Date: Mon, 21 Sep 2026 13:41:45 +0200 Subject: [PATCH 61/74] add meta.yml files to local modules using claude --- modules/local/create_anndata_mudata/meta.yml | 149 +++++++++++++ modules/local/donor_match/meta.yml | 202 ++++++++++++++++++ .../local/dropletutils/mtxconvert/meta.yml | 79 +++++++ modules/local/extract_hashes/meta.yml | 74 +++++++ modules/local/filter_bam/meta.yml | 93 ++++++++ modules/local/find_variants/meta.yml | 193 +++++++++++++++++ modules/local/gene_summary/meta.yml | 146 +++++++++++++ modules/local/hash_summary/meta.yml | 194 +++++++++++++++++ modules/local/htodemux_visualization/meta.yml | 143 +++++++++++++ .../meta.yml | 81 +++++++ modules/local/subset_gt_donors/meta.yml | 79 +++++++ 11 files changed, 1433 insertions(+) create mode 100644 modules/local/create_anndata_mudata/meta.yml create mode 100644 modules/local/donor_match/meta.yml create mode 100644 modules/local/dropletutils/mtxconvert/meta.yml create mode 100644 modules/local/extract_hashes/meta.yml create mode 100644 modules/local/filter_bam/meta.yml create mode 100644 modules/local/find_variants/meta.yml create mode 100644 modules/local/gene_summary/meta.yml create mode 100644 modules/local/hash_summary/meta.yml create mode 100644 modules/local/htodemux_visualization/meta.yml create mode 100644 modules/local/preprocessing_for_htodemux_multiseq/meta.yml create mode 100644 modules/local/subset_gt_donors/meta.yml diff --git a/modules/local/create_anndata_mudata/meta.yml b/modules/local/create_anndata_mudata/meta.yml new file mode 100644 index 00000000..b0aa1895 --- /dev/null +++ b/modules/local/create_anndata_mudata/meta.yml @@ -0,0 +1,149 @@ +name: create_anndata_mudata +description: Create AnnData objects and a MuData object from the RNA and HTO matrices and + annotate them with the genetic and hashing demultiplexing summaries. +keywords: + - demultiplexing + - anndata + - mudata + - single-cell +tools: + - scanpy: + description: Scalable toolkit for analyzing single-cell gene expression data built jointly + with anndata. + homepage: https://scanpy.readthedocs.io + documentation: https://scanpy.readthedocs.io/en/stable/ + tool_dev_url: https://github.com/scverse/scanpy + doi: 10.1186/s13059-017-1382-0 + licence: + - BSD-3-Clause + identifier: "" + - anndata: + description: Annotated data matrices for single-cell data, with support for HDF5-backed + storage. + homepage: https://anndata.readthedocs.io + documentation: https://anndata.readthedocs.io/en/stable/ + tool_dev_url: https://github.com/scverse/anndata + doi: 10.21105/joss.04371 + licence: + - BSD-3-Clause + identifier: "" + - mudata: + description: Multimodal annotated data, a container for multiple AnnData objects. + homepage: https://mudata.readthedocs.io + documentation: https://mudata.readthedocs.io/en/stable/ + tool_dev_url: https://github.com/scverse/mudata + doi: 10.1186/s13059-021-02577-8 + licence: + - BSD-3-Clause + identifier: "" + - pandas: + description: Python library providing data structures and data analysis tools. + homepage: https://pandas.pydata.org + documentation: https://pandas.pydata.org/docs/ + tool_dev_url: https://github.com/pandas-dev/pandas + licence: + - BSD-3-Clause + identifier: "" +input: + - - meta: + type: map + description: | + Groovy Map containing sample information + e.g. `[ id:'sample1' ]` + - rna_matrix: + type: directory + description: Directory containing the RNA count matrix in 10X format. Pass an empty + list to skip the RNA modality. + ontologies: + - edam: http://edamontology.org/data_3917 + - hto_matrix: + type: directory + description: Directory containing the HTO count matrix in 10X format. Pass an empty + list to skip the hashing modality. + ontologies: + - edam: http://edamontology.org/data_3917 + - genetic_summary_assignment: + type: file + description: CSV file with the assignment of each barcode by the genetic demultiplexing + methods (output of `GENE_SUMMARY`). Pass an empty list if not available. + ontologies: + - edam: http://edamontology.org/format_3752 + - genetic_summary_classification: + type: file + description: CSV file with the singlet/doublet/negative classification of each barcode + by the genetic demultiplexing methods (output of `GENE_SUMMARY`). Pass an empty + list if not available. + ontologies: + - edam: http://edamontology.org/format_3752 + - hashing_summary_assignment: + type: file + description: CSV file with the assignment of each barcode by the hashing-based demultiplexing + methods (output of `HASH_SUMMARY`). Pass an empty list if not available. + ontologies: + - edam: http://edamontology.org/format_3752 + - hashing_summary_classification: + type: file + description: CSV file with the singlet/doublet/negative classification of each barcode + by the hashing-based demultiplexing methods (output of `HASH_SUMMARY`). Pass an + empty list if not available. + ontologies: + - edam: http://edamontology.org/format_3752 +output: + h5ad_genetic: + - - meta: + type: map + description: | + Groovy Map containing sample information + e.g. `[ id:'sample1' ]` + ontologies: [] + - "*_genetic.h5ad": + type: file + description: AnnData object of the RNA matrix annotated with the genetic demultiplexing + results. Only created if the RNA matrix is provided. + pattern: "*_genetic.h5ad" + ontologies: [] + h5ad_hashing: + - - meta: + type: map + description: | + Groovy Map containing sample information + e.g. `[ id:'sample1' ]` + ontologies: [] + - "*_hashing.h5ad": + type: file + description: AnnData object of the HTO matrix annotated with the hashing demultiplexing + results. Only created if the HTO matrix is provided. + pattern: "*_hashing.h5ad" + ontologies: [] + h5mu: + - - meta: + type: map + description: | + Groovy Map containing sample information + e.g. `[ id:'sample1' ]` + ontologies: [] + - "*_genetic_and_hashing.h5mu": + type: file + description: MuData object combining the `rna` and `hto` AnnData objects. Only created + if both matrices are provided. + pattern: "*_genetic_and_hashing.h5mu" + ontologies: [] + versions: + - versions.yml: + type: file + description: File containing software versions + pattern: versions.yml + ontologies: + - edam: http://edamontology.org/format_3750 +topics: + versions: + - versions.yml: + type: file + description: File containing software versions + pattern: versions.yml + ontologies: + - edam: http://edamontology.org/format_3750 +authors: + - "@LuisHeinzlmeier" +maintainers: + - "@LuisHeinzlmeier" diff --git a/modules/local/donor_match/meta.yml b/modules/local/donor_match/meta.yml new file mode 100644 index 00000000..c07b6dea --- /dev/null +++ b/modules/local/donor_match/meta.yml @@ -0,0 +1,202 @@ +name: donor_match +description: Match the donors of genetic demultiplexing methods to the hashtags of hashing-based + demultiplexing methods and compare the assignments of the methods. +keywords: + - demultiplexing + - donor matching + - genetic deconvolution + - hashing-based deconvolution +tools: + - tidyverse: + description: Collection of R packages for data science, sharing a common design philosophy. + homepage: https://www.tidyverse.org + documentation: https://tidyverse.tidyverse.org + tool_dev_url: https://github.com/tidyverse/tidyverse + doi: 10.21105/joss.01686 + licence: + - MIT + identifier: "" + - data.table: + description: Extension of R's data.frame for fast aggregation of large data. + homepage: https://rdatatable.gitlab.io/data.table + documentation: https://rdatatable.gitlab.io/data.table/ + tool_dev_url: https://github.com/Rdatatable/data.table + licence: + - MPL-2.0 + identifier: "" + - pheatmap: + description: R package for drawing pretty heatmaps. + homepage: https://cran.r-project.org/package=pheatmap + documentation: https://cran.r-project.org/package=pheatmap + tool_dev_url: https://github.com/raivokolde/pheatmap + licence: + - GPL-2.0-only + identifier: "" +input: + - - meta: + type: map + description: | + Groovy Map containing sample information + e.g. `[ id:'sample1' ]` + - demultiplexing_result: + type: file + description: CSV file with the combined assignment of each barcode (`Barcode` column) + by the genetic and hashing demultiplexing methods (output of `GENE_SUMMARY` and + `HASH_SUMMARY`). + ontologies: + - edam: http://edamontology.org/format_3752 + - match_donor_method1: + type: string + description: Name of the first demultiplexing method to be compared. If it is `null`/empty, + all combinations of genetic and hashing methods are compared. + - match_donor_method2: + type: string + description: Name of the second demultiplexing method to be compared. If it is `null`/empty, + all combinations of genetic and hashing methods are compared. +output: + best_donor_match: + - - meta: + type: map + description: | + Groovy Map containing sample information + e.g. `[ id:'sample1' ]` + ontologies: [] + - "*_best_donor_match.csv": + type: file + description: Donor matching (genetic donor to hashtag) of the best scoring combination + of a genetic and a hashing method. Only created if both a genetic and a hashing + method were compared. + pattern: "*_best_donor_match.csv" + ontologies: + - edam: http://edamontology.org/format_3752 + best_all_assignment_after_match: + - - meta: + type: map + description: | + Groovy Map containing sample information + e.g. `[ id:'sample1' ]` + ontologies: [] + - "*_best_all_assignment_after_match.csv": + type: file + description: Assignments of all cells after donor matching for the best scoring + method combination. Only created if both a genetic and a hashing method were compared. + pattern: "*_best_all_assignment_after_match.csv" + ontologies: + - edam: http://edamontology.org/format_3752 + best_intersect_assignment_after_match: + - - meta: + type: map + description: | + Groovy Map containing sample information + e.g. `[ id:'sample1' ]` + ontologies: [] + - "*_best_intersect_assignment_after_match.csv": + type: file + description: Assignments of the cells that both methods of the best scoring combination + agree on being singlets, after donor matching. Only created if both a genetic + and a hashing method were compared. + pattern: "*_best_intersect_assignment_after_match.csv" + ontologies: + - edam: http://edamontology.org/format_3752 + score_record: + - - meta: + type: map + description: | + Groovy Map containing sample information + e.g. `[ id:'sample1' ]` + ontologies: [] + - "*_score_record.csv": + type: file + description: Matching score of every compared method combination. Only created if + both a genetic and a hashing method were compared. + pattern: "*_score_record.csv" + ontologies: + - edam: http://edamontology.org/format_3752 + assignment_after_match: + - - meta: + type: map + description: | + Groovy Map containing sample information + e.g. `[ id:'sample1' ]` + ontologies: [] + - "*/*_vs_*all_assignment_after_match.csv": + type: file + description: Assignments of all cells after donor matching for each compared method + pair. + pattern: "*/*_vs_*all_assignment_after_match.csv" + ontologies: + - edam: http://edamontology.org/format_3752 + assignment_intersect_match: + - - meta: + type: map + description: | + Groovy Map containing sample information + e.g. `[ id:'sample1' ]` + ontologies: [] + - "*/*_vs_*intersect_assignment_after_match.csv": + type: file + description: Assignments of the cells that both methods of a pair agree on being + singlets, after donor matching, for each compared method pair. + pattern: "*/*_vs_*intersect_assignment_after_match.csv" + ontologies: + - edam: http://edamontology.org/format_3752 + correlation: + - - meta: + type: map + description: | + Groovy Map containing sample information + e.g. `[ id:'sample1' ]` + ontologies: [] + - "*/*_vs_*correlation_res.csv": + type: file + description: Correlation between the donors of both methods for each compared method + pair. + pattern: "*/*_vs_*correlation_res.csv" + ontologies: + - edam: http://edamontology.org/format_3752 + donor_match: + - - meta: + type: map + description: | + Groovy Map containing sample information + e.g. `[ id:'sample1' ]` + ontologies: [] + - "*/*_vs_*donor_match.csv": + type: file + description: Donor matching between the two methods for each compared method pair. + pattern: "*/*_vs_*donor_match.csv" + ontologies: + - edam: http://edamontology.org/format_3752 + concordance_heatmap: + - - meta: + type: map + description: | + Groovy Map containing sample information + e.g. `[ id:'sample1' ]` + ontologies: [] + - "*/*_vs_*concordance_heatmap.png": + type: file + description: Heatmap of the concordance between the two methods for each compared + method pair. + pattern: "*/*_vs_*concordance_heatmap.png" + ontologies: + - edam: http://edamontology.org/format_3603 + versions: + - versions.yml: + type: file + description: File containing software versions + pattern: versions.yml + ontologies: + - edam: http://edamontology.org/format_3750 +topics: + versions: + - versions.yml: + type: file + description: File containing software versions + pattern: versions.yml + ontologies: + - edam: http://edamontology.org/format_3750 +authors: + - "@LuisHeinzlmeier" +maintainers: + - "@LuisHeinzlmeier" diff --git a/modules/local/dropletutils/mtxconvert/meta.yml b/modules/local/dropletutils/mtxconvert/meta.yml new file mode 100644 index 00000000..e259f309 --- /dev/null +++ b/modules/local/dropletutils/mtxconvert/meta.yml @@ -0,0 +1,79 @@ +name: dropletutils_mtxconvert +description: Convert a 10X-format count matrix into an HDF5 file and optionally into a CSV + file using DropletUtils. +keywords: + - single-cell + - matrix + - conversion + - 10x +tools: + - dropletutils: + description: Bioconductor package with utilities for handling droplet-based single-cell + RNA-seq data. + homepage: https://bioconductor.org/packages/DropletUtils/ + documentation: https://bioconductor.org/packages/release/bioc/html/DropletUtils.html + tool_dev_url: https://github.com/MarioniLab/DropletUtils + doi: 10.1186/s13059-019-1662-y + licence: + - GPL-3.0-only + identifier: "" +input: + - - meta: + type: map + description: | + Groovy Map containing sample information + e.g. `[ id:'sample1' ]` + - input_mtx_dir: + type: directory + description: Directory containing the count matrix in 10X format. + ontologies: + - edam: http://edamontology.org/data_3917 + - write_csv: + type: boolean + description: Whether a dense CSV file should be written in addition to the HDF5 file. +output: + csv: + - - meta: + type: map + description: | + Groovy Map containing sample information + e.g. `[ id:'sample1' ]` + ontologies: [] + - "*.csv": + type: file + description: Dense count matrix as CSV file. Only created if `write_csv` is true. + pattern: "*.csv" + ontologies: + - edam: http://edamontology.org/format_3752 + h5: + - - meta: + type: map + description: | + Groovy Map containing sample information + e.g. `[ id:'sample1' ]` + ontologies: [] + - "*.h5": + type: file + description: Count matrix as 10X HDF5 file. + pattern: "*.h5" + ontologies: + - edam: http://edamontology.org/format_3590 + versions: + - versions.yml: + type: file + description: File containing software versions + pattern: versions.yml + ontologies: + - edam: http://edamontology.org/format_3750 +topics: + versions: + - versions.yml: + type: file + description: File containing software versions + pattern: versions.yml + ontologies: + - edam: http://edamontology.org/format_3750 +authors: + - "@LuisHeinzlmeier" +maintainers: + - "@LuisHeinzlmeier" diff --git a/modules/local/extract_hashes/meta.yml b/modules/local/extract_hashes/meta.yml new file mode 100644 index 00000000..59b28467 --- /dev/null +++ b/modules/local/extract_hashes/meta.yml @@ -0,0 +1,74 @@ +name: extract_hashes +description: Extract the names of the hashtags from the features file of a 10X-format HTO + matrix. +keywords: + - demultiplexing + - hashing-based deconvolution + - hashtag + - single-cell +tools: + - coreutils: + description: GNU core utilities for file, shell and text manipulation. + homepage: https://www.gnu.org/software/coreutils/ + documentation: https://www.gnu.org/software/coreutils/manual/ + licence: + - GPL-3.0-or-later + identifier: "" + - gawk: + description: GNU implementation of the AWK text processing language. + homepage: https://www.gnu.org/software/gawk/ + documentation: https://www.gnu.org/software/gawk/manual/ + licence: + - GPL-3.0-or-later + identifier: "" + - gzip: + description: GNU file compression and decompression utility. + homepage: https://www.gnu.org/software/gzip/ + documentation: https://www.gnu.org/software/gzip/manual/ + licence: + - GPL-3.0-or-later + identifier: "" +input: + - - meta: + type: map + description: | + Groovy Map containing sample information + e.g. `[ id:'sample1' ]` + - hto_dir: + type: directory + description: Directory containing the HTO count matrix in 10X format, including `features.tsv.gz`. + ontologies: + - edam: http://edamontology.org/data_3917 +output: + hashes: + - - meta: + type: map + description: | + Groovy Map containing sample information + e.g. `[ id:'sample1' ]` + ontologies: [] + - "*_hashes.txt": + type: file + description: Text file with the comma-separated names of the hashtags. + pattern: "*_hashes.txt" + ontologies: + - edam: http://edamontology.org/format_2330 + versions: + - versions.yml: + type: file + description: File containing software versions + pattern: versions.yml + ontologies: + - edam: http://edamontology.org/format_3750 +topics: + versions: + - versions.yml: + type: file + description: File containing software versions + pattern: versions.yml + ontologies: + - edam: http://edamontology.org/format_3750 +authors: + - "@LuisHeinzlmeier" +maintainers: + - "@LuisHeinzlmeier" diff --git a/modules/local/filter_bam/meta.yml b/modules/local/filter_bam/meta.yml new file mode 100644 index 00000000..2b4f7af0 --- /dev/null +++ b/modules/local/filter_bam/meta.yml @@ -0,0 +1,93 @@ +name: filter_bam +description: Filter a BAM file to the reads of the given barcodes overlapping the given variants, + as preparation for popscle pileup. +keywords: + - demultiplexing + - genetic deconvolution + - bam + - filter +tools: + - bcftools: + description: Utilities for variant calling and manipulating VCFs and BCFs. + homepage: https://samtools.github.io/bcftools/ + documentation: https://samtools.github.io/bcftools/bcftools.html + tool_dev_url: https://github.com/samtools/bcftools + doi: 10.1093/gigascience/giab008 + licence: + - MIT + - GPL + identifier: "" + - bedtools: + description: Toolset for genome arithmetic on genomic intervals. + homepage: https://bedtools.readthedocs.io + documentation: https://bedtools.readthedocs.io/en/latest/ + tool_dev_url: https://github.com/arq5x/bedtools2 + doi: 10.1093/bioinformatics/btq033 + licence: + - GPL-2.0-only + identifier: "" + - samtools: + description: Tools for dealing with SAM, BAM and CRAM files. + homepage: http://www.htslib.org/ + documentation: http://www.htslib.org/doc/samtools.html + tool_dev_url: https://github.com/samtools/samtools + doi: 10.1093/bioinformatics/btp352 + licence: + - MIT + identifier: "" +input: + - - meta: + type: map + description: | + Groovy Map containing sample information + e.g. `[ id:'sample1' ]` + - bam: + type: file + description: Aligned reads of the single-cell experiment. + ontologies: + - edam: http://edamontology.org/format_2572 + - barcodes: + type: file + description: File with the cell barcodes to keep, one barcode per line. + ontologies: + - edam: http://edamontology.org/format_2330 + - vcf: + type: file + description: VCF file with the variants to restrict the reads to. It is sorted before + filtering. + ontologies: + - edam: http://edamontology.org/format_3016 +output: + bam: + - - meta: + type: map + description: | + Groovy Map containing sample information + e.g. `[ id:'sample1' ]` + ontologies: [] + - ${prefix}.bam: + type: file + description: Filtered BAM file. Its name must differ from the name of the input + BAM file (set `task.ext.prefix` to disambiguate). + pattern: "*.bam" + ontologies: + - edam: http://edamontology.org/format_2572 + versions: + - versions.yml: + type: file + description: File containing software versions + pattern: versions.yml + ontologies: + - edam: http://edamontology.org/format_3750 +topics: + versions: + - versions.yml: + type: file + description: File containing software versions + pattern: versions.yml + ontologies: + - edam: http://edamontology.org/format_3750 +authors: + - "@LuisHeinzlmeier" +maintainers: + - "@LuisHeinzlmeier" diff --git a/modules/local/find_variants/meta.yml b/modules/local/find_variants/meta.yml new file mode 100644 index 00000000..514d9894 --- /dev/null +++ b/modules/local/find_variants/meta.yml @@ -0,0 +1,193 @@ +name: find_variants +description: Find representative and donor-specific variants from the cell genotypes of a + genetic demultiplexing method, based on the donor-matched assignments. +keywords: + - demultiplexing + - genetic deconvolution + - variants + - donor matching +tools: + - tidyverse: + description: Collection of R packages for data science, sharing a common design philosophy. + homepage: https://www.tidyverse.org + documentation: https://tidyverse.tidyverse.org + tool_dev_url: https://github.com/tidyverse/tidyverse + doi: 10.21105/joss.01686 + licence: + - MIT + identifier: "" + - data.table: + description: Extension of R's data.frame for fast aggregation of large data. + homepage: https://rdatatable.gitlab.io/data.table + documentation: https://rdatatable.gitlab.io/data.table/ + tool_dev_url: https://github.com/Rdatatable/data.table + licence: + - MPL-2.0 + identifier: "" + - ComplexUpset: + description: R package for creating UpSet plots with ggplot2. + homepage: https://krassowski.github.io/complex-upset/ + documentation: https://krassowski.github.io/complex-upset/ + tool_dev_url: https://github.com/krassowski/complex-upset + doi: 10.5281/zenodo.3700590 + licence: + - MIT + identifier: "" + - vcfR: + description: R package to read, manipulate and visualize variant call format (VCF) data. + homepage: https://knausb.github.io/vcfR_documentation/ + documentation: https://knausb.github.io/vcfR_documentation/ + tool_dev_url: https://github.com/knausb/vcfR + doi: 10.1111/1755-0998.12549 + licence: + - GPL-3.0-or-later + identifier: "" +input: + - - meta: + type: map + description: | + Groovy Map containing sample information + e.g. `[ id:'sample1' ]` + - best_intersect_assignment_after_match: + type: file + description: CSV file with the assignments of the cells that both methods of the best + method combination agree on being singlets, after donor matching (output of `DONOR_MATCH`). + ontologies: + - edam: http://edamontology.org/format_3752 + - cell_genotype: + type: file + description: VCF file with the genotype of each cell (e.g. `cellSNP.cells.vcf`). Pass + an empty list to skip the genotype analysis. + ontologies: + - edam: http://edamontology.org/format_3016 + - variants_vireo: + type: file + description: Variants file of vireo. Pass an empty list to skip the vireo variants + analysis. + ontologies: + - edam: http://edamontology.org/format_3475 + - demultiplexing_result: + type: file + description: CSV file with the combined assignment of each barcode. It is only used + to obtain all barcodes. + ontologies: + - edam: http://edamontology.org/format_3752 + - variant_count: + type: integer + description: Minimum number of cells a variant has to be found in to be considered informative. + - variant_pct: + type: float + description: Minimum fraction of cells in which a variant must be found in one genotype + state to be considered informative (between 0.5 and 1). +output: + matched_gt: + - - meta: + type: map + description: | + Groovy Map containing sample information + e.g. `[ id:'sample1' ]` + ontologies: [] + - "*/*_matched_gt.csv": + type: file + description: Genotypes of the variants for the cells matched to a donor. + pattern: "*/*_matched_gt.csv" + ontologies: + - edam: http://edamontology.org/format_3752 + unmatched_gt: + - - meta: + type: map + description: | + Groovy Map containing sample information + e.g. `[ id:'sample1' ]` + ontologies: [] + - "*/*_unmatched_gt.csv": + type: file + description: Genotypes of the variants for the cells not matched to a donor. + pattern: "*/*_unmatched_gt.csv" + ontologies: + - edam: http://edamontology.org/format_3752 + informative_variants: + - - meta: + type: map + description: | + Groovy Map containing sample information + e.g. `[ id:'sample1' ]` + ontologies: [] + - "*/*_informative_variants.csv": + type: file + description: Variants that are informative for the donor assignment. + pattern: "*/*_informative_variants.csv" + ontologies: + - edam: http://edamontology.org/format_3752 + all_representative_variants: + - - meta: + type: map + description: | + Groovy Map containing sample information + e.g. `[ id:'sample1' ]` + ontologies: [] + - "*_all_representative_variants.csv": + type: file + description: All representative variants across the donors. + pattern: "*_all_representative_variants.csv" + ontologies: + - edam: http://edamontology.org/format_3752 + donor_specific_variants_upset: + - - meta: + type: map + description: | + Groovy Map containing sample information + e.g. `[ id:'sample1' ]` + ontologies: [] + - "*_donor_specific_variants_upset.png": + type: file + description: UpSet plot of the variants shared between the donors. + pattern: "*_donor_specific_variants_upset.png" + ontologies: + - edam: http://edamontology.org/format_3603 + donor_specific_variants: + - - meta: + type: map + description: | + Groovy Map containing sample information + e.g. `[ id:'sample1' ]` + ontologies: [] + - "*_donor_specific_variants.csv": + type: file + description: Representative variants that are specific to one donor. + pattern: "*_donor_specific_variants.csv" + ontologies: + - edam: http://edamontology.org/format_3752 + vireo_variants: + - - meta: + type: map + description: | + Groovy Map containing sample information + e.g. `[ id:'sample1' ]` + ontologies: [] + - "*_vireo_variants.csv": + type: file + description: Representative variants of the vireo variants file. Only created if + the vireo variants file is provided. + pattern: "*_vireo_variants.csv" + ontologies: + - edam: http://edamontology.org/format_3752 + versions: + - versions.yml: + type: file + description: File containing software versions + pattern: versions.yml + ontologies: + - edam: http://edamontology.org/format_3750 +topics: + versions: + - versions.yml: + type: file + description: File containing software versions + pattern: versions.yml + ontologies: + - edam: http://edamontology.org/format_3750 +authors: + - "@LuisHeinzlmeier" +maintainers: + - "@LuisHeinzlmeier" diff --git a/modules/local/gene_summary/meta.yml b/modules/local/gene_summary/meta.yml new file mode 100644 index 00000000..d402f81a --- /dev/null +++ b/modules/local/gene_summary/meta.yml @@ -0,0 +1,146 @@ +name: gene_summary +description: Summarise the results of the genetic demultiplexing methods (vireo, demuxlet, + freemuxlet, souporcell) into a unified assignment and classification table. +keywords: + - demultiplexing + - genetic deconvolution + - summary + - single-cell +tools: + - scanpy: + description: Scalable toolkit for analyzing single-cell gene expression data built jointly + with anndata. + homepage: https://scanpy.readthedocs.io + documentation: https://scanpy.readthedocs.io/en/stable/ + tool_dev_url: https://github.com/scverse/scanpy + doi: 10.1186/s13059-017-1382-0 + licence: + - BSD-3-Clause + identifier: "" + - pandas: + description: Python library providing data structures and data analysis tools. + homepage: https://pandas.pydata.org + documentation: https://pandas.pydata.org/docs/ + tool_dev_url: https://github.com/pandas-dev/pandas + licence: + - BSD-3-Clause + identifier: "" + - numpy: + description: Fundamental package for scientific computing with Python. + homepage: https://numpy.org + documentation: https://numpy.org/doc/stable/ + tool_dev_url: https://github.com/numpy/numpy + doi: 10.1038/s41586-020-2649-2 + licence: + - BSD-3-Clause + identifier: "" +input: + - - meta: + type: map + description: | + Groovy Map containing sample information + e.g. `[ id:'sample1' ]` + - barcodes: + type: file + description: File with the cell barcodes, one barcode per line. + ontologies: + - edam: http://edamontology.org/format_3475 + - vireo: + type: file + description: Donor assignment table of vireo (`donor_ids.tsv`). Pass an empty list + if vireo was not run. + ontologies: + - edam: http://edamontology.org/format_3475 + - demuxlet: + type: file + description: Result file of demuxlet (`.best`). Pass an empty list if demuxlet was + not run. + ontologies: + - edam: http://edamontology.org/format_3475 + - freemuxlet: + type: file + description: Result file of freemuxlet (`.clust1.samples`). Pass an empty list if + freemuxlet was not run. + ontologies: + - edam: http://edamontology.org/format_3475 + - souporcell: + type: file + description: Cluster assignment table of souporcell (`clusters.tsv`). Pass an empty + list if souporcell was not run. + ontologies: + - edam: http://edamontology.org/format_3475 +output: + assignment: + - - meta: + type: map + description: | + Groovy Map containing sample information + e.g. `[ id:'sample1' ]` + ontologies: [] + - "*_genetic_summary_assignment.csv": + type: file + description: Donor assignment of each barcode by each genetic demultiplexing method. + pattern: "*_genetic_summary_assignment.csv" + ontologies: + - edam: http://edamontology.org/format_3752 + classification: + - - meta: + type: map + description: | + Groovy Map containing sample information + e.g. `[ id:'sample1' ]` + ontologies: [] + - "*_genetic_summary_classification.csv": + type: file + description: Singlet/doublet/negative classification of each barcode by each genetic + demultiplexing method. + pattern: "*_genetic_summary_classification.csv" + ontologies: + - edam: http://edamontology.org/format_3752 + overview_assignment: + - - meta: + type: map + description: | + Groovy Map containing sample information + e.g. `[ id:'sample1' ]` + ontologies: [] + - "*_genetic_overview_assignment.csv": + type: file + description: Overview of the number of cells assigned to each donor by each genetic + demultiplexing method. + pattern: "*_genetic_overview_assignment.csv" + ontologies: + - edam: http://edamontology.org/format_3752 + overview_classification: + - - meta: + type: map + description: | + Groovy Map containing sample information + e.g. `[ id:'sample1' ]` + ontologies: [] + - "*_genetic_overview_classification.csv": + type: file + description: Overview of the number of singlets, doublets and negatives called by + each genetic demultiplexing method. + pattern: "*_genetic_overview_classification.csv" + ontologies: + - edam: http://edamontology.org/format_3752 + versions: + - versions.yml: + type: file + description: File containing software versions + pattern: versions.yml + ontologies: + - edam: http://edamontology.org/format_3750 +topics: + versions: + - versions.yml: + type: file + description: File containing software versions + pattern: versions.yml + ontologies: + - edam: http://edamontology.org/format_3750 +authors: + - "@LuisHeinzlmeier" +maintainers: + - "@LuisHeinzlmeier" diff --git a/modules/local/hash_summary/meta.yml b/modules/local/hash_summary/meta.yml new file mode 100644 index 00000000..d300cac8 --- /dev/null +++ b/modules/local/hash_summary/meta.yml @@ -0,0 +1,194 @@ +name: hash_summary +description: Summarise the results of the hashing-based demultiplexing methods (HTODemux, + MULTIseqDemux, BFF, demuxEM, GMM-Demux, hashedDrops, HashSolo) into a unified assignment + and classification table. +keywords: + - demultiplexing + - hashing-based deconvolution + - summary + - single-cell +tools: + - scanpy: + description: Scalable toolkit for analyzing single-cell gene expression data built jointly + with anndata. + homepage: https://scanpy.readthedocs.io + documentation: https://scanpy.readthedocs.io/en/stable/ + tool_dev_url: https://github.com/scverse/scanpy + doi: 10.1186/s13059-017-1382-0 + licence: + - BSD-3-Clause + identifier: "" + - pandas: + description: Python library providing data structures and data analysis tools. + homepage: https://pandas.pydata.org + documentation: https://pandas.pydata.org/docs/ + tool_dev_url: https://github.com/pandas-dev/pandas + licence: + - BSD-3-Clause + identifier: "" + - numpy: + description: Fundamental package for scientific computing with Python. + homepage: https://numpy.org + documentation: https://numpy.org/doc/stable/ + tool_dev_url: https://github.com/numpy/numpy + doi: 10.1038/s41586-020-2649-2 + licence: + - BSD-3-Clause + identifier: "" + - pegasusio: + description: Python package for reading, writing and manipulating single-cell and single-nucleus + genomics data. + homepage: https://pegasusio.readthedocs.io + documentation: https://pegasusio.readthedocs.io/en/latest/ + tool_dev_url: https://github.com/lilab-bcb/pegasusio + doi: 10.1038/s41592-020-0905-x + licence: + - BSD-3-Clause + identifier: "" +input: + - - meta: + type: map + description: | + Groovy Map containing sample information + e.g. `[ id:'sample1' ]` + - hto_matrix: + type: directory + description: Directory containing the HTO count matrix in 10X format. + ontologies: + - edam: http://edamontology.org/data_3917 + - htodemux_assignments: + type: file + description: Assignment results of HTODemux. Pass an empty list if the method was + not run. + ontologies: + - edam: http://edamontology.org/format_3752 + - htodemux_classification: + type: file + description: Classification results of HTODemux. Pass an empty list if the method + was not run. + ontologies: + - edam: http://edamontology.org/format_3752 + - multiseq: + type: file + description: Assignment results of MULTIseqDemux. Pass an empty list if the method + was not run. + ontologies: + - edam: http://edamontology.org/format_3752 + - bff: + type: file + description: Assignment results of BFF. Pass an empty list if the method was not run. + ontologies: + - edam: http://edamontology.org/format_3752 + - demuxem: + type: file + description: Result of demuxEM. Pass an empty list if the method was not run. + ontologies: [] + - gmmdemux_results: + type: file + description: Classification results of GMM-Demux. Pass an empty list if the method + was not run. + ontologies: + - edam: http://edamontology.org/format_3752 + - gmmdemux_config: + type: file + description: Configuration file of GMM-Demux mapping the cluster to the hashtags. + Pass an empty list if the method was not run. + ontologies: + - edam: http://edamontology.org/format_3752 + - hasheddrops_results: + type: file + description: Assignment results of hashedDrops. Pass an empty list if the method was + not run. + ontologies: + - edam: http://edamontology.org/format_3752 + - hasheddrops_id_to_hash: + type: file + description: Table mapping the hashedDrops ids to the hashtag names. Pass an empty + list if the method was not run. + ontologies: + - edam: http://edamontology.org/format_3752 + - hashsolo: + type: file + description: Assignment results of HashSolo. Pass an empty list if the method was + not run. + ontologies: + - edam: http://edamontology.org/format_3752 + - bff_methods: + type: string + description: "Which BFF call methods to summarise: `RAW`, `CLUSTER` or `COMBINED`." +output: + assignment: + - - meta: + type: map + description: | + Groovy Map containing sample information + e.g. `[ id:'sample1' ]` + ontologies: [] + - "*_hashing_summary_assignment.csv": + type: file + description: Hashtag assignment of each barcode by each hashing-based demultiplexing + method. + pattern: "*_hashing_summary_assignment.csv" + ontologies: + - edam: http://edamontology.org/format_3752 + classification: + - - meta: + type: map + description: | + Groovy Map containing sample information + e.g. `[ id:'sample1' ]` + ontologies: [] + - "*_hashing_summary_classification.csv": + type: file + description: Singlet/doublet/negative classification of each barcode by each hashing-based + demultiplexing method. + pattern: "*_hashing_summary_classification.csv" + ontologies: + - edam: http://edamontology.org/format_3752 + overview_assignment: + - - meta: + type: map + description: | + Groovy Map containing sample information + e.g. `[ id:'sample1' ]` + ontologies: [] + - "*_hashing_overview_assignment.csv": + type: file + description: Overview of the number of cells assigned to each hashtag by each hashing-based + demultiplexing method. + pattern: "*_hashing_overview_assignment.csv" + ontologies: + - edam: http://edamontology.org/format_3752 + overview_classification: + - - meta: + type: map + description: | + Groovy Map containing sample information + e.g. `[ id:'sample1' ]` + ontologies: [] + - "*_hashing_overview_classification.csv": + type: file + description: Overview of the number of singlets, doublets and negatives called by + each hashing-based demultiplexing method. + pattern: "*_hashing_overview_classification.csv" + ontologies: + - edam: http://edamontology.org/format_3752 + versions: + - versions.yml: + type: file + description: File containing software versions + pattern: versions.yml + ontologies: + - edam: http://edamontology.org/format_3750 +topics: + versions: + - versions.yml: + type: file + description: File containing software versions + pattern: versions.yml + ontologies: + - edam: http://edamontology.org/format_3750 +authors: + - "@LuisHeinzlmeier" +maintainers: + - "@LuisHeinzlmeier" diff --git a/modules/local/htodemux_visualization/meta.yml b/modules/local/htodemux_visualization/meta.yml new file mode 100644 index 00000000..ad7763eb --- /dev/null +++ b/modules/local/htodemux_visualization/meta.yml @@ -0,0 +1,143 @@ +name: htodemux_visualization +description: Visualise the results of HTODemux (ridge plot, feature scatter, violin plot, + tSNE and heatmap) from a Seurat object. +keywords: + - demultiplexing + - hashing-based deconvolution + - visualization + - single-cell +tools: + - seurat: + description: R toolkit for quality control, analysis and exploration of single-cell + RNA-seq data, including multimodal data. + homepage: https://satijalab.org/seurat/ + documentation: https://satijalab.org/seurat/reference/ + tool_dev_url: https://github.com/satijalab/seurat + doi: 10.1016/j.cell.2021.04.048 + licence: + - MIT + identifier: "" + - ggplot2: + description: R system for declaratively creating graphics based on The Grammar of Graphics. + homepage: https://ggplot2.tidyverse.org + documentation: https://ggplot2.tidyverse.org/reference/ + tool_dev_url: https://github.com/tidyverse/ggplot2 + licence: + - MIT + identifier: "" +input: + - - meta: + type: map + description: | + Groovy Map containing sample information + e.g. `[ id:'sample1' ]` + - seurat_object: + type: file + description: RDS file containing the Seurat object on which HTODemux was run. + ontologies: [] + - assay: + type: string + description: Name of the hashtag assay in the Seurat object, usually `HTO`. +output: + ridge_plot: + - - meta: + type: map + description: | + Groovy Map containing sample information + e.g. `[ id:'sample1' ]` + ontologies: [] + - "*_ridge_htodemux.jpeg": + type: file + description: Ridge plot of the HTO expression per hashtag. Only created if `task.ext.ridgePlot` + is true. + pattern: "*_ridge_htodemux.jpeg" + ontologies: + - edam: http://edamontology.org/format_3579 + feature_scatter: + - - meta: + type: map + description: | + Groovy Map containing sample information + e.g. `[ id:'sample1' ]` + ontologies: [] + - "*_featureScatter_htodemux.jpeg": + type: file + description: Scatter plot of two features. Only created if `task.ext.featureScatter` + is true. + pattern: "*_featureScatter_htodemux.jpeg" + ontologies: + - edam: http://edamontology.org/format_3579 + violin_plot: + - - meta: + type: map + description: | + Groovy Map containing sample information + e.g. `[ id:'sample1' ]` + ontologies: [] + - "*_violinPlot_htodemux.jpeg": + type: file + description: Violin plot of a feature (e.g. `nCount_RNA`) per HTODemux classification. + Only created if `task.ext.vlnPlot` is true. + pattern: "*_violinPlot_htodemux.jpeg" + ontologies: + - edam: http://edamontology.org/format_3579 + tsne_plot: + - - meta: + type: map + description: | + Groovy Map containing sample information + e.g. `[ id:'sample1' ]` + ontologies: [] + - "*_tSNE_htodemux.jpeg": + type: file + description: tSNE embedding of the HTO data. Only created if `task.ext.tSNE` is + true. + pattern: "*_tSNE_htodemux.jpeg" + ontologies: + - edam: http://edamontology.org/format_3579 + heatmap_plot: + - - meta: + type: map + description: | + Groovy Map containing sample information + e.g. `[ id:'sample1' ]` + ontologies: [] + - "*_heatMap_htodemux.jpeg": + type: file + description: Heatmap of the HTO signal across cells. Only created if `task.ext.heatMap` + is true. + pattern: "*_heatMap_htodemux.jpeg" + ontologies: + - edam: http://edamontology.org/format_3579 + params: + - - meta: + type: map + description: | + Groovy Map containing sample information + e.g. `[ id:'sample1' ]` + ontologies: [] + - "*_visual_params_htodemux.csv": + type: file + description: The parameters used for the visualisation. + pattern: "*_visual_params_htodemux.csv" + ontologies: + - edam: http://edamontology.org/format_3752 + versions: + - versions.yml: + type: file + description: File containing software versions + pattern: versions.yml + ontologies: + - edam: http://edamontology.org/format_3750 +topics: + versions: + - versions.yml: + type: file + description: File containing software versions + pattern: versions.yml + ontologies: + - edam: http://edamontology.org/format_3750 +authors: + - "@LuisHeinzlmeier" +maintainers: + - "@LuisHeinzlmeier" diff --git a/modules/local/preprocessing_for_htodemux_multiseq/meta.yml b/modules/local/preprocessing_for_htodemux_multiseq/meta.yml new file mode 100644 index 00000000..910891e4 --- /dev/null +++ b/modules/local/preprocessing_for_htodemux_multiseq/meta.yml @@ -0,0 +1,81 @@ +name: preprocessing_for_htodemux_multiseq +description: Create a Seurat object from the RNA and HTO matrices and normalise the HTO data + as preprocessing for HTODemux and MULTIseqDemux. +keywords: + - demultiplexing + - hashing-based deconvolution + - preprocessing + - single-cell +tools: + - seurat: + description: R toolkit for quality control, analysis and exploration of single-cell + RNA-seq data, including multimodal data. + homepage: https://satijalab.org/seurat/ + documentation: https://satijalab.org/seurat/reference/ + tool_dev_url: https://github.com/satijalab/seurat + doi: 10.1016/j.cell.2021.04.048 + licence: + - MIT + identifier: "" +input: + - - meta: + type: map + description: | + Groovy Map containing sample information + e.g. `[ id:'sample1' ]` + - rna_matrix: + type: directory + description: Directory containing the RNA count matrix in 10X format. + ontologies: + - edam: http://edamontology.org/data_3917 + - hto_matrix: + type: directory + description: Directory containing the HTO count matrix in 10X format. + ontologies: + - edam: http://edamontology.org/data_3917 +output: + seurat_object: + - - meta: + type: map + description: | + Groovy Map containing sample information + e.g. `[ id:'sample1' ]` + ontologies: [] + - "*_preprocessed.rds": + type: file + description: RDS file containing the preprocessed Seurat object with the HTO assay + added and normalised. + pattern: "*_preprocessed.rds" + ontologies: [] + params: + - - meta: + type: map + description: | + Groovy Map containing sample information + e.g. `[ id:'sample1' ]` + ontologies: [] + - "*_params_preprocessing.csv": + type: file + description: The parameters used for the preprocessing. + pattern: "*_params_preprocessing.csv" + ontologies: + - edam: http://edamontology.org/format_3752 + versions: + - versions.yml: + type: file + description: File containing software versions + pattern: versions.yml + ontologies: + - edam: http://edamontology.org/format_3750 +topics: + versions: + - versions.yml: + type: file + description: File containing software versions + pattern: versions.yml + ontologies: + - edam: http://edamontology.org/format_3750 +authors: + - "@LuisHeinzlmeier" +maintainers: + - "@LuisHeinzlmeier" diff --git a/modules/local/subset_gt_donors/meta.yml b/modules/local/subset_gt_donors/meta.yml new file mode 100644 index 00000000..44c2e657 --- /dev/null +++ b/modules/local/subset_gt_donors/meta.yml @@ -0,0 +1,79 @@ +name: subset_gt_donors +description: Subset a genotype VCF of donors to the given variants and rename its samples + to the matched donors. +keywords: + - demultiplexing + - genetic deconvolution + - vcf + - subset +tools: + - bcftools: + description: Utilities for variant calling and manipulating VCFs and BCFs. + homepage: https://samtools.github.io/bcftools/ + documentation: https://samtools.github.io/bcftools/bcftools.html + tool_dev_url: https://github.com/samtools/bcftools + doi: 10.1093/gigascience/giab008 + licence: + - MIT + - GPL + identifier: "" +input: + - - meta: + type: map + description: | + Groovy Map containing sample information + e.g. `[ id:'sample1' ]` + - subset_variants: + type: file + description: File with the variants (regions) to keep, as used by `bcftools filter + -R`. + ontologies: + - edam: http://edamontology.org/format_3475 + - output_basename: + type: string + description: Basename of the output file, which is appended to the prefix. + - gt_donors_vcf: + type: file + description: VCF file with the genotypes of the donors. It is sorted before subsetting. + ontologies: + - edam: http://edamontology.org/format_3016 + - donor_match: + type: file + description: File with the new sample names (one per line), used to rename the samples + of the VCF (see `bcftools reheader --samples`). + ontologies: + - edam: http://edamontology.org/format_2330 +output: + donor_subset_vcf: + - - meta: + type: map + description: | + Groovy Map containing sample information + e.g. `[ id:'sample1' ]` + ontologies: [] + - "*_${output_basename}.vcf.gz": + type: file + description: Compressed VCF file with the subsetted variants and the samples renamed + to the matched donors. + pattern: "*.vcf.gz" + ontologies: + - edam: http://edamontology.org/format_3016 + versions: + - versions.yml: + type: file + description: File containing software versions + pattern: versions.yml + ontologies: + - edam: http://edamontology.org/format_3750 +topics: + versions: + - versions.yml: + type: file + description: File containing software versions + pattern: versions.yml + ontologies: + - edam: http://edamontology.org/format_3750 +authors: + - "@LuisHeinzlmeier" +maintainers: + - "@LuisHeinzlmeier" From bb5b839346e73b97e494a0b61022d1e634bf7c10 Mon Sep 17 00:00:00 2001 From: LuisHeinzlmeier Date: Mon, 21 Sep 2026 15:51:19 +0200 Subject: [PATCH 62/74] update resource usage of VIREO and SOUPORCELL --- conf/modules.config | 8 ++++++++ conf/test_full.config | 12 ------------ docs/usage.md | 11 +++++++++++ 3 files changed, 19 insertions(+), 12 deletions(-) diff --git a/conf/modules.config b/conf/modules.config index c16a576d..9875bec2 100644 --- a/conf/modules.config +++ b/conf/modules.config @@ -150,6 +150,10 @@ process { } withName: VIREO { + // Real-life dataset (AWS): peak RSS 255 GB, ~2 cores used, 10 min. Memory scales with dataset size (cells x variants) + cpus = { 4 * task.attempt } + memory = { 320.GB * task.attempt } + time = { 4.h * task.attempt } ext.args = { [ "--genoTag", @@ -265,6 +269,10 @@ process { } withName: SOUPORCELL { + // Real-life dataset (AWS, 60 threads): peak RSS 51 GB, 11 h runtime, ~4.7 cores used on average. + cpus = 60 + memory = { 64.GB * task.attempt } + time = { 24.h * task.attempt } ext.args = { [ "-p", diff --git a/conf/test_full.config b/conf/test_full.config index afdeeaad..a8b9045f 100644 --- a/conf/test_full.config +++ b/conf/test_full.config @@ -16,18 +16,6 @@ process { memory: '360.GB', time: '23.h' ] - - withName: SOUPORCELL { - cpus = 60 - memory = 64.GB - time = 23.h - } - - withName: VIREO { - cpus = 4 - memory = 360.GB - time = 8.h - } } params { diff --git a/docs/usage.md b/docs/usage.md index d65ea4c6..716f3f78 100644 --- a/docs/usage.md +++ b/docs/usage.md @@ -323,6 +323,17 @@ Whilst the default requirements set within the pipeline will hopefully work for To change the resource requests, please see the [max resources](https://nf-co.re/docs/running/configuration/nextflow-for-your-system#set-max-resources) and [customise process resources](https://nf-co.re/docs/running/configuration/nextflow-for-your-system#customize-process-resources) section of the nf-core website. +#### SOUPORCELL and VIREO + +`SOUPORCELL` and `VIREO` have dedicated resource requests in [`conf/modules.config`](../conf/modules.config) because they need far more than the generic `process_medium` label on real-life data. The defaults are based on a `test_full` run on AWS (dataset: see [issue #106](https://github.com/nf-core/hadge/issues/106)): + +| Process | Requested (CPUs, memory, time) | Duration | CPU usage | Peak RSS | Peak VMEM | +| ------------ | ------------------------------ | --------- | --------- | -------- | --------- | +| `SOUPORCELL` | 60, 64 GB, 24 h | 11 h 3 m | 467% | 50.6 GB | 70.6 GB | +| `VIREO` | 4, 320 GB, 4 h | 14 m 38 s | 197% | 255.5 GB | 299.4 GB | + +If they do not fit your system or data, cap them with [max resources](https://nf-co.re/docs/running/configuration/nextflow-for-your-system#set-max-resources) or override them with `-c`, e.g. `withName: SOUPORCELL { cpus = 16; time = { 48.h * task.attempt } }`. Note that SOUPORCELL gets slower with fewer CPUs, so increase `time` when lowering `cpus`. + ### Custom Containers In some cases, you may wish to change the container or conda environment used by a pipeline steps for a particular tool. By default, nf-core pipelines use containers and software from the [biocontainers](https://biocontainers.pro/) or [bioconda](https://bioconda.github.io/) projects. However, in some cases the pipeline specified version maybe out of date. From 4ae3290ba195356608d61bd7a63a2cb23d91f94d Mon Sep 17 00:00:00 2001 From: LuisHeinzlmeier Date: Mon, 21 Sep 2026 16:04:44 +0200 Subject: [PATCH 63/74] restore apptainer in container directive of local modules --- modules/local/create_anndata_mudata/main.nf | 2 +- modules/local/donor_match/main.nf | 2 +- modules/local/dropletutils/mtxconvert/main.nf | 2 +- modules/local/extract_hashes/main.nf | 2 +- modules/local/filter_bam/main.nf | 2 +- modules/local/find_variants/main.nf | 2 +- modules/local/gene_summary/main.nf | 2 +- modules/local/hash_summary/main.nf | 2 +- modules/local/htodemux_visualization/main.nf | 2 +- modules/local/preprocessing_for_htodemux_multiseq/main.nf | 2 +- modules/local/subset_gt_donors/main.nf | 2 +- 11 files changed, 11 insertions(+), 11 deletions(-) diff --git a/modules/local/create_anndata_mudata/main.nf b/modules/local/create_anndata_mudata/main.nf index 64109c3f..92e67511 100644 --- a/modules/local/create_anndata_mudata/main.nf +++ b/modules/local/create_anndata_mudata/main.nf @@ -3,7 +3,7 @@ process CREATE_ANNDATA_MUDATA { label 'process_low' conda "${moduleDir}/environment.yml" - container "${ workflow.containerEngine == 'singularity' && !task.ext.singularity_pull_docker_container ? + container "${ workflow.containerEngine in ['singularity', 'apptainer'] && !task.ext.singularity_pull_docker_container ? 'https://community-cr-prod.seqera.io/docker/registry/v2/blobs/sha256/31/31c261a4a1ed9c3b409457fe778a363fb941152f7307bfa76cb4c42d44235ddf/data': 'community.wave.seqera.io/library/anndata_mudata_pandas_pyyaml_scanpy:e96a91e210372525' }" diff --git a/modules/local/donor_match/main.nf b/modules/local/donor_match/main.nf index ad484f65..52280186 100644 --- a/modules/local/donor_match/main.nf +++ b/modules/local/donor_match/main.nf @@ -3,7 +3,7 @@ process DONOR_MATCH { label 'process_low' conda "${moduleDir}/environment.yml" - container "${ workflow.containerEngine == 'singularity' && !task.ext.singularity_pull_docker_container ? + container "${ workflow.containerEngine in ['singularity', 'apptainer'] && !task.ext.singularity_pull_docker_container ? 'https://community-cr-prod.seqera.io/docker/registry/v2/blobs/sha256/d9/d9138b380ca73daad0b5ad74a10b46324ca4f676efdf199f9dc9cb9145a4590c/data': 'community.wave.seqera.io/library/r-data.table_r-pheatmap_r-tidyverse:ac2dbc33f827dbb9' }" diff --git a/modules/local/dropletutils/mtxconvert/main.nf b/modules/local/dropletutils/mtxconvert/main.nf index c4fac691..0adbf769 100644 --- a/modules/local/dropletutils/mtxconvert/main.nf +++ b/modules/local/dropletutils/mtxconvert/main.nf @@ -3,7 +3,7 @@ process DROPLETUTILS_MTXCONVERT { label 'process_low' conda "${moduleDir}/environment.yml" - container "${workflow.containerEngine == 'singularity' && !task.ext.singularity_pull_docker_container + container "${workflow.containerEngine in ['singularity', 'apptainer'] && !task.ext.singularity_pull_docker_container ? 'https://community-cr-prod.seqera.io/docker/registry/v2/blobs/sha256/c9/c9f81df3cdd03c86a8133f74c0deb78719798c061895e4d9dd454f05e82ff93e/data' : 'community.wave.seqera.io/library/bioconductor-dropletutils:1.26.0--35a578ac06f1c531'}" diff --git a/modules/local/extract_hashes/main.nf b/modules/local/extract_hashes/main.nf index c5a49ae2..fe117c5e 100644 --- a/modules/local/extract_hashes/main.nf +++ b/modules/local/extract_hashes/main.nf @@ -3,7 +3,7 @@ process EXTRACT_HASHES { label 'process_low' conda "${moduleDir}/environment.yml" - container "${workflow.containerEngine == 'singularity' && !task.ext.singularity_pull_docker_container + container "${workflow.containerEngine in ['singularity', 'apptainer'] && !task.ext.singularity_pull_docker_container ? 'https://community-cr-prod.seqera.io/docker/registry/v2/blobs/sha256/74/749b3cf99e0a33f46d2b49ab60d6e408ce467476c0ec86da1775e73ac4b5ba7b/data' : 'community.wave.seqera.io/library/coreutils_gawk_gzip:3d2dde6df78e314a'}" diff --git a/modules/local/filter_bam/main.nf b/modules/local/filter_bam/main.nf index ba1411dd..59fb74ca 100644 --- a/modules/local/filter_bam/main.nf +++ b/modules/local/filter_bam/main.nf @@ -3,7 +3,7 @@ process FILTER_BAM { label 'process_low' conda "${moduleDir}/environment.yml" - container "${workflow.containerEngine == 'singularity' && !task.ext.singularity_pull_docker_container + container "${workflow.containerEngine in ['singularity', 'apptainer'] && !task.ext.singularity_pull_docker_container ? 'https://community-cr-prod.seqera.io/docker/registry/v2/blobs/sha256/f4/f46d4b6a720d442979b57330a319a25863231b2c70c80348f7b1d1d7d422b1f6/data' : 'community.wave.seqera.io/library/bcftools_bedtools_samtools:f1acc4ec7fbdba9e'}" diff --git a/modules/local/find_variants/main.nf b/modules/local/find_variants/main.nf index 2b32b382..bd146485 100644 --- a/modules/local/find_variants/main.nf +++ b/modules/local/find_variants/main.nf @@ -3,7 +3,7 @@ process FIND_VARIANTS { label 'process_low' conda "${moduleDir}/environment.yml" - container "${ workflow.containerEngine == 'singularity' && !task.ext.singularity_pull_docker_container ? + container "${ workflow.containerEngine in ['singularity', 'apptainer'] && !task.ext.singularity_pull_docker_container ? 'https://community-cr-prod.seqera.io/docker/registry/v2/blobs/sha256/75/755e81f7b523df9db3a6f574fdb876ddfc1e1faf1e912260b99bca773f6dba2d/data': 'community.wave.seqera.io/library/r-complexupset_r-data.table_r-tidyverse_r-vcfr:87602a1274fab432' }" diff --git a/modules/local/gene_summary/main.nf b/modules/local/gene_summary/main.nf index 529f7358..eae73502 100644 --- a/modules/local/gene_summary/main.nf +++ b/modules/local/gene_summary/main.nf @@ -3,7 +3,7 @@ process GENE_SUMMARY { label 'process_low' conda "${moduleDir}/environment.yml" - container "${ workflow.containerEngine == 'singularity' && !task.ext.singularity_pull_docker_container ? + container "${ workflow.containerEngine in ['singularity', 'apptainer'] && !task.ext.singularity_pull_docker_container ? 'https://community-cr-prod.seqera.io/docker/registry/v2/blobs/sha256/6d/6d63210b90bdadc321e15610f40c337ab08fa724719b7d4be0785944a86755fb/data': 'community.wave.seqera.io/library/numpy_pandas_pyyaml_scanpy:d959777f7735763f' }" diff --git a/modules/local/hash_summary/main.nf b/modules/local/hash_summary/main.nf index a739afc4..d7c363f2 100644 --- a/modules/local/hash_summary/main.nf +++ b/modules/local/hash_summary/main.nf @@ -3,7 +3,7 @@ process HASH_SUMMARY { label 'process_low' conda "${moduleDir}/environment.yml" - container "${ workflow.containerEngine == 'singularity' && !task.ext.singularity_pull_docker_container ? + container "${ workflow.containerEngine in ['singularity', 'apptainer'] && !task.ext.singularity_pull_docker_container ? 'https://community-cr-prod.seqera.io/docker/registry/v2/blobs/sha256/cb/cb8601e2171467026ea36c22328a15eb25025bbe686ff1a0ea04ab407c735aee/data': 'community.wave.seqera.io/library/pegasusio_numpy_pandas_pyyaml_scanpy:e16c3756496aa20c' }" diff --git a/modules/local/htodemux_visualization/main.nf b/modules/local/htodemux_visualization/main.nf index 6316e7a0..1ec806fc 100644 --- a/modules/local/htodemux_visualization/main.nf +++ b/modules/local/htodemux_visualization/main.nf @@ -3,7 +3,7 @@ process HTODEMUX_VISUALIZATION { label 'process_low' conda "${moduleDir}/environment.yml" - container "${ workflow.containerEngine == 'singularity' && !task.ext.singularity_pull_docker_container ? + container "${ workflow.containerEngine in ['singularity', 'apptainer'] && !task.ext.singularity_pull_docker_container ? 'https://community-cr-prod.seqera.io/docker/registry/v2/blobs/sha256/8e/8e9a749bb2e2bfac7f1a80d9565eb75fd1ca5bc036eb87a04964fb58f4ac0138/data': 'community.wave.seqera.io/library/r-ggplot2_r-seurat_r-seuratobject:e12b56f2caede4ba' }" diff --git a/modules/local/preprocessing_for_htodemux_multiseq/main.nf b/modules/local/preprocessing_for_htodemux_multiseq/main.nf index 071f56eb..c749e529 100644 --- a/modules/local/preprocessing_for_htodemux_multiseq/main.nf +++ b/modules/local/preprocessing_for_htodemux_multiseq/main.nf @@ -3,7 +3,7 @@ process PREPROCESSING_FOR_HTODEMUX_MULTISEQ { label 'process_low' conda "${moduleDir}/environment.yml" - container "${ workflow.containerEngine == 'singularity' && !task.ext.singularity_pull_docker_container ? + container "${ workflow.containerEngine in ['singularity', 'apptainer'] && !task.ext.singularity_pull_docker_container ? 'https://community-cr-prod.seqera.io/docker/registry/v2/blobs/sha256/6b/6b43d3afc47ad5e5d99bc97980d409e377f6b0595cb588b121076e67dca71d39/data': 'community.wave.seqera.io/library/r-seurat_r-seuratobject:c1b3e7a7276bda09' }" diff --git a/modules/local/subset_gt_donors/main.nf b/modules/local/subset_gt_donors/main.nf index 14987069..2510093c 100644 --- a/modules/local/subset_gt_donors/main.nf +++ b/modules/local/subset_gt_donors/main.nf @@ -3,7 +3,7 @@ process SUBSET_GT_DONORS { label 'process_low' conda "${moduleDir}/environment.yml" - container "${ workflow.containerEngine == 'singularity' && !task.ext.singularity_pull_docker_container ? + container "${ workflow.containerEngine in ['singularity', 'apptainer'] && !task.ext.singularity_pull_docker_container ? 'https://community-cr-prod.seqera.io/docker/registry/v2/blobs/sha256/4e/4eb863d427b6e327abab4be2112d17760f86947eeaf3a214e1450bc680f14a49/data': 'community.wave.seqera.io/library/bcftools:1.22--a51ee80717c2467e' }" From a6531c113b2cea614ab7f7eaad7ac4b902914a30 Mon Sep 17 00:00:00 2001 From: LuisHeinzlmeier Date: Mon, 21 Sep 2026 16:21:05 +0200 Subject: [PATCH 64/74] restore apptainer in container directive of patched nf-core modules --- modules/nf-core/cellsnp/modea/cellsnp-modea.diff | 2 +- modules/nf-core/cellsnp/modea/main.nf | 2 +- modules/nf-core/demuxem/demuxem.diff | 2 +- modules/nf-core/demuxem/main.nf | 2 +- modules/nf-core/gmmdemux/gmmdemux.diff | 5 +++-- modules/nf-core/gmmdemux/main.nf | 2 +- modules/nf-core/popscle/demuxlet/main.nf | 2 +- modules/nf-core/popscle/demuxlet/popscle-demuxlet.diff | 7 ++----- modules/nf-core/popscle/dscpileup/main.nf | 2 +- modules/nf-core/popscle/dscpileup/popscle-dscpileup.diff | 2 +- 10 files changed, 13 insertions(+), 15 deletions(-) diff --git a/modules/nf-core/cellsnp/modea/cellsnp-modea.diff b/modules/nf-core/cellsnp/modea/cellsnp-modea.diff index 0377546a..97b38ed9 100644 --- a/modules/nf-core/cellsnp/modea/cellsnp-modea.diff +++ b/modules/nf-core/cellsnp/modea/cellsnp-modea.diff @@ -14,7 +14,7 @@ Changes in 'cellsnp/modea/main.nf': - container "${ workflow.containerEngine in ['singularity', 'apptainer'] && !task.ext.singularity_pull_docker_container ? - 'https://depot.galaxyproject.org/singularity/cellsnp-lite:1.2.3--h6141fd1_2' : - 'quay.io/biocontainers/cellsnp-lite:1.2.3--h6141fd1_2' }" -+ container "${workflow.containerEngine == 'singularity' && !task.ext.singularity_pull_docker_container ++ container "${workflow.containerEngine in ['singularity', 'apptainer'] && !task.ext.singularity_pull_docker_container + ? 'https://depot.galaxyproject.org/singularity/cellsnp-lite:1.2.3--h6141fd1_2' + : 'biocontainers/cellsnp-lite:1.2.3--h6141fd1_2'}" diff --git a/modules/nf-core/cellsnp/modea/main.nf b/modules/nf-core/cellsnp/modea/main.nf index 6188ad77..6d3d0044 100644 --- a/modules/nf-core/cellsnp/modea/main.nf +++ b/modules/nf-core/cellsnp/modea/main.nf @@ -3,7 +3,7 @@ process CELLSNP_MODEA { label 'process_medium' conda "${moduleDir}/environment.yml" - container "${workflow.containerEngine == 'singularity' && !task.ext.singularity_pull_docker_container + container "${workflow.containerEngine in ['singularity', 'apptainer'] && !task.ext.singularity_pull_docker_container ? 'https://depot.galaxyproject.org/singularity/cellsnp-lite:1.2.3--h6141fd1_2' : 'biocontainers/cellsnp-lite:1.2.3--h6141fd1_2'}" diff --git a/modules/nf-core/demuxem/demuxem.diff b/modules/nf-core/demuxem/demuxem.diff index c9cfdeee..3aefdf07 100644 --- a/modules/nf-core/demuxem/demuxem.diff +++ b/modules/nf-core/demuxem/demuxem.diff @@ -15,7 +15,7 @@ Changes in 'demuxem/main.nf': - container "${ workflow.containerEngine in ['singularity', 'apptainer'] && !task.ext.singularity_pull_docker_container ? - 'https://depot.galaxyproject.org/singularity/demuxem:0.1.7.post1--pyhdfd78af_0' : - 'quay.io/biocontainers/demuxem:0.1.7.post1--pyhdfd78af_0' }" -+ container "${workflow.containerEngine == 'singularity' && !task.ext.singularity_pull_docker_container ++ container "${workflow.containerEngine in ['singularity', 'apptainer'] && !task.ext.singularity_pull_docker_container + ? 'https://community-cr-prod.seqera.io/docker/registry/v2/blobs/sha256/0d/0d3f96aaa8437bfa1654570e1d2b84749f1ac14d68f97978acc19b3757af7f55/data' + : 'community.wave.seqera.io/library/demuxem:0.1.7.post1--5ac55376ad7cb80e'}" + diff --git a/modules/nf-core/demuxem/main.nf b/modules/nf-core/demuxem/main.nf index 0b2e9e11..9a27256d 100644 --- a/modules/nf-core/demuxem/main.nf +++ b/modules/nf-core/demuxem/main.nf @@ -3,7 +3,7 @@ process DEMUXEM { label 'process_low' conda "${moduleDir}/environment.yml" - container "${workflow.containerEngine == 'singularity' && !task.ext.singularity_pull_docker_container + container "${workflow.containerEngine in ['singularity', 'apptainer'] && !task.ext.singularity_pull_docker_container ? 'https://community-cr-prod.seqera.io/docker/registry/v2/blobs/sha256/0d/0d3f96aaa8437bfa1654570e1d2b84749f1ac14d68f97978acc19b3757af7f55/data' : 'community.wave.seqera.io/library/demuxem:0.1.7.post1--5ac55376ad7cb80e'}" diff --git a/modules/nf-core/gmmdemux/gmmdemux.diff b/modules/nf-core/gmmdemux/gmmdemux.diff index f1832907..4f10bdb0 100644 --- a/modules/nf-core/gmmdemux/gmmdemux.diff +++ b/modules/nf-core/gmmdemux/gmmdemux.diff @@ -4,7 +4,7 @@ Changes in component 'nf-core/gmmdemux' Changes in 'gmmdemux/main.nf': --- modules/nf-core/gmmdemux/main.nf +++ modules/nf-core/gmmdemux/main.nf -@@ -1,66 +1,74 @@ +@@ -1,66 +1,75 @@ - process GMMDEMUX { - tag "$meta.id" @@ -15,7 +15,7 @@ Changes in 'gmmdemux/main.nf': - container "${ workflow.containerEngine in ['singularity', 'apptainer'] && !task.ext.singularity_pull_docker_container ? - 'https://depot.galaxyproject.org/singularity/gmm-demux:0.2.2.3--pyh7cba7a3_0': - 'quay.io/biocontainers/gmm-demux:0.2.2.3--pyh7cba7a3_0' }" -+ container "${workflow.containerEngine == 'singularity' && !task.ext.singularity_pull_docker_container ++ container "${workflow.containerEngine in ['singularity', 'apptainer'] && !task.ext.singularity_pull_docker_container + ? 'https://depot.galaxyproject.org/singularity/gmm-demux:0.2.2.3--pyh7cba7a3_0' + : 'biocontainers/gmm-demux:0.2.2.3--pyh7cba7a3_0'}" @@ -98,6 +98,7 @@ Changes in 'gmmdemux/main.nf': stub: - def prefix = task.ext.prefix ?: "${meta.id}" + def VERSION = '0.2.2.3' ++ def prefix = task.ext.prefix ?: "${meta.id}" """ - if [[ ${summary_report} == true ]]; then - touch ${prefix}_summary_report.txt diff --git a/modules/nf-core/gmmdemux/main.nf b/modules/nf-core/gmmdemux/main.nf index 51b1fcd3..66f44e1e 100644 --- a/modules/nf-core/gmmdemux/main.nf +++ b/modules/nf-core/gmmdemux/main.nf @@ -3,7 +3,7 @@ process GMMDEMUX { label 'process_low' conda "${moduleDir}/environment.yml" - container "${workflow.containerEngine == 'singularity' && !task.ext.singularity_pull_docker_container + container "${workflow.containerEngine in ['singularity', 'apptainer'] && !task.ext.singularity_pull_docker_container ? 'https://depot.galaxyproject.org/singularity/gmm-demux:0.2.2.3--pyh7cba7a3_0' : 'biocontainers/gmm-demux:0.2.2.3--pyh7cba7a3_0'}" diff --git a/modules/nf-core/popscle/demuxlet/main.nf b/modules/nf-core/popscle/demuxlet/main.nf index ff6246a0..3d997ea8 100644 --- a/modules/nf-core/popscle/demuxlet/main.nf +++ b/modules/nf-core/popscle/demuxlet/main.nf @@ -3,7 +3,7 @@ process POPSCLE_DEMUXLET { label 'process_medium' conda "${moduleDir}/environment.yml" - container "${ workflow.containerEngine == 'singularity' && !task.ext.singularity_pull_docker_container ? + container "${ workflow.containerEngine in ['singularity', 'apptainer'] && !task.ext.singularity_pull_docker_container ? 'https://depot.galaxyproject.org/singularity/popscle:0.1beta--h2c78cec_0' : 'biocontainers/popscle:0.1beta--h2c78cec_0' }" diff --git a/modules/nf-core/popscle/demuxlet/popscle-demuxlet.diff b/modules/nf-core/popscle/demuxlet/popscle-demuxlet.diff index fa133d9c..4e3cb9c4 100644 --- a/modules/nf-core/popscle/demuxlet/popscle-demuxlet.diff +++ b/modules/nf-core/popscle/demuxlet/popscle-demuxlet.diff @@ -4,12 +4,9 @@ Changes in component 'nf-core/popscle/demuxlet' Changes in 'popscle/demuxlet/main.nf': --- modules/nf-core/popscle/demuxlet/main.nf +++ modules/nf-core/popscle/demuxlet/main.nf -@@ -3,12 +3,12 @@ - label 'process_medium' - +@@ -5,10 +5,10 @@ conda "${moduleDir}/environment.yml" -- container "${ workflow.containerEngine in ['singularity', 'apptainer'] && !task.ext.singularity_pull_docker_container ? -+ container "${ workflow.containerEngine == 'singularity' && !task.ext.singularity_pull_docker_container ? + container "${ workflow.containerEngine in ['singularity', 'apptainer'] && !task.ext.singularity_pull_docker_container ? 'https://depot.galaxyproject.org/singularity/popscle:0.1beta--h2c78cec_0' : - 'quay.io/biocontainers/popscle:0.1beta--h2c78cec_0' }" + 'biocontainers/popscle:0.1beta--h2c78cec_0' }" diff --git a/modules/nf-core/popscle/dscpileup/main.nf b/modules/nf-core/popscle/dscpileup/main.nf index f4cde4b1..590d9dee 100644 --- a/modules/nf-core/popscle/dscpileup/main.nf +++ b/modules/nf-core/popscle/dscpileup/main.nf @@ -3,7 +3,7 @@ process POPSCLE_DSCPILEUP { label 'process_medium' conda "${moduleDir}/environment.yml" - container "${workflow.containerEngine == 'singularity' && !task.ext.singularity_pull_docker_container + container "${workflow.containerEngine in ['singularity', 'apptainer'] && !task.ext.singularity_pull_docker_container ? 'https://depot.galaxyproject.org/singularity/popscle:0.1beta--h2c78cec_0' : 'biocontainers/popscle:0.1beta--h2c78cec_0'}" diff --git a/modules/nf-core/popscle/dscpileup/popscle-dscpileup.diff b/modules/nf-core/popscle/dscpileup/popscle-dscpileup.diff index d20e4268..d83feebb 100644 --- a/modules/nf-core/popscle/dscpileup/popscle-dscpileup.diff +++ b/modules/nf-core/popscle/dscpileup/popscle-dscpileup.diff @@ -14,7 +14,7 @@ Changes in 'popscle/dscpileup/main.nf': - container "${ workflow.containerEngine in ['singularity', 'apptainer'] && !task.ext.singularity_pull_docker_container ? - 'https://depot.galaxyproject.org/singularity/popscle:0.1beta--h2c78cec_0' : - 'quay.io/biocontainers/popscle:0.1beta--h2c78cec_0' }" -+ container "${workflow.containerEngine == 'singularity' && !task.ext.singularity_pull_docker_container ++ container "${workflow.containerEngine in ['singularity', 'apptainer'] && !task.ext.singularity_pull_docker_container + ? 'https://depot.galaxyproject.org/singularity/popscle:0.1beta--h2c78cec_0' + : 'biocontainers/popscle:0.1beta--h2c78cec_0'}" From d1e8a61c425b6124d707c01e701a4e549ff9c822 Mon Sep 17 00:00:00 2001 From: LuisHeinzlmeier Date: Mon, 21 Sep 2026 16:27:03 +0200 Subject: [PATCH 65/74] add stub and when to FILTER_BAM --- modules/local/filter_bam/main.nf | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/modules/local/filter_bam/main.nf b/modules/local/filter_bam/main.nf index 59fb74ca..3531eb71 100644 --- a/modules/local/filter_bam/main.nf +++ b/modules/local/filter_bam/main.nf @@ -15,6 +15,9 @@ process FILTER_BAM { tuple val(meta), path("${prefix}.bam"), emit: bam path 'versions.yml' , emit: versions, topic: versions + when: + task.ext.when == null || task.ext.when + script: prefix = task.ext.prefix ?: "${meta.id}" if ("${bam}" == "${prefix}.bam") { @@ -31,4 +34,17 @@ process FILTER_BAM { samtools: \$(echo \$(samtools --version 2>&1) | sed 's/^.*samtools //; s/Using.*\$//') END_VERSIONS """ + + stub: + prefix = task.ext.prefix ?: "${meta.id}" + """ + touch ${prefix}.bam + + cat <<-END_VERSIONS > versions.yml + "${task.process}": + bcftools: \$(bcftools --version 2>&1 | head -n1 | sed 's/^.*bcftools //; s/ .*\$//') + bedtools: \$(bedtools --version | sed -e "s/bedtools v//g") + samtools: \$(echo \$(samtools --version 2>&1) | sed 's/^.*samtools //; s/Using.*\$//') + END_VERSIONS + """ } From 0f4e45a563e1c431ff39ee67fd7f53a47d1cec6d Mon Sep 17 00:00:00 2001 From: LuisHeinzlmeier Date: Sun, 27 Sep 2026 18:27:36 +0200 Subject: [PATCH 66/74] first fixes --- CHANGELOG.md | 2 +- assets/schema_input.json | 8 ++++++-- docs/usage.md | 10 +++++----- modules/local/find_variants/main.nf | 6 +++--- subworkflows/local/genetic_demultiplexing/main.nf | 2 +- 5 files changed, 16 insertions(+), 12 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ae31b7ba..e1ac44f6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,7 +5,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## v1.0.0 - [2026-08-23] -First stable release of nf-core/hadge, combining 11 hashing- and genotype-based deconvolution methods (cellSNP, vireo, demuxlet, freemuxlet, souporcell, htodemux, multiseq, bff, demuxem, gmm-demux, hasheddrops, hashsolo) with donor matching to recover discarded cells. +First stable release of nf-core/hadge, combining 11 hashing- and genotype-based deconvolution methods (vireo, demuxlet, freemuxlet, souporcell, htodemux, multiseq, bff, demuxem, gmm-demux, hasheddrops, hashsolo) with donor matching to recover discarded cells. ## v1.0.0dev - [2025-06-13] diff --git a/assets/schema_input.json b/assets/schema_input.json index 1b03be15..ead33970 100644 --- a/assets/schema_input.json +++ b/assets/schema_input.json @@ -17,13 +17,17 @@ "type": "string", "default": null, "pattern": "^\\S+$", - "errorMessage": "RNA matrix must be provided and cannot contain spaces" + "format": "path", + "exists": true, + "errorMessage": "HTO matrix must be provided and cannot contain spaces" }, "hto_matrix": { "type": "string", "default": null, "pattern": "^\\S+$", - "errorMessage": "HTO matrix must be provided and cannot contain spaces" + "format": "path", + "exists": true, + "errorMessage": "RNA matrix must be provided and cannot contain spaces" }, "bam": { "type": "string", diff --git a/docs/usage.md b/docs/usage.md index 716f3f78..7a47c18b 100644 --- a/docs/usage.md +++ b/docs/usage.md @@ -105,11 +105,11 @@ nextflow run nf-core/hadge \ -profile \ --input samplesheet.csv \ --outdir \ - --mode donor_match - --demultiplexing_result \ - --vireo_filtered_variants \ - --cell_genotype \ - --gt_donors \ + --mode donor_match \ + --demultiplexing_result \ + --vireo_filtered_variants \ + --cell_genotype \ + --gt_donors ``` ## Samplesheet input diff --git a/modules/local/find_variants/main.nf b/modules/local/find_variants/main.nf index bd146485..b7c243b7 100644 --- a/modules/local/find_variants/main.nf +++ b/modules/local/find_variants/main.nf @@ -34,10 +34,10 @@ process FIND_VARIANTS { """ mkdir -p hto1 - touch ${prefix}_all_representative_variant.csv + touch ${prefix}_all_representative_variants.csv touch ${prefix}_donor_specific_variants_upset.png - touch ${prefix}_donor_specific_representative_variants.csv - touch ${prefix}_vireo_representative_variants.csv + touch ${prefix}_donor_specific_variants.csv + touch ${prefix}_vireo_variants.csv touch hto1/${prefix}_hto1_matched_gt.csv touch hto1/${prefix}_hto1_unmatched_gt.csv touch hto1/${prefix}_hto1_informative_variants.csv diff --git a/subworkflows/local/genetic_demultiplexing/main.nf b/subworkflows/local/genetic_demultiplexing/main.nf index a82e5378..4a79efc3 100644 --- a/subworkflows/local/genetic_demultiplexing/main.nf +++ b/subworkflows/local/genetic_demultiplexing/main.nf @@ -52,7 +52,7 @@ workflow GENETIC_DEMULTIPLEXING { barcodes, ] }, - common_variants, + file(common_variants, checkIfExists: true), ) ch_samplesheet = ch_samplesheet .join(FILTER_BAM.out.bam) From f149c8dffb34dde7a04bba86cd07c726ca9ca62c Mon Sep 17 00:00:00 2001 From: LuisHeinzlmeier Date: Sun, 27 Sep 2026 18:43:01 +0200 Subject: [PATCH 67/74] make vireo filtered variants optional for FIND_VARIANTS --- docs/usage.md | 3 ++- nextflow_schema.json | 2 +- subworkflows/local/utils_nfcore_hadge_pipeline/main.nf | 5 +++-- workflows/hadge.nf | 5 +++-- 4 files changed, 9 insertions(+), 6 deletions(-) diff --git a/docs/usage.md b/docs/usage.md index 7a47c18b..b015a347 100644 --- a/docs/usage.md +++ b/docs/usage.md @@ -90,7 +90,8 @@ nextflow run nf-core/hadge \ ### The donor match mode This mode utilizes the donor matching component from the rescue mode, but requires manual input for several stages. -To run all steps of donor matching, you must provide the demultiplexing results, filtered variants, and both cell and donor genotypes. +To run all steps of donor matching, you must provide the demultiplexing results and both cell and donor genotypes. +The vireo filtered variants (`--vireo_filtered_variants`) are optional and only needed to additionally subset the donor genotypes by the variants used by vireo. For detailed specifications on these input parameters, refer to the [parameter documentation](https://nf-co.re/hadge/parameters). ```csv title="samplesheet.csv" diff --git a/nextflow_schema.json b/nextflow_schema.json index 457d4eb2..77ed8245 100644 --- a/nextflow_schema.json +++ b/nextflow_schema.json @@ -144,7 +144,7 @@ "vireo_filtered_variants": { "type": ["string", "null"], "format": "file-path", - "description": "Path to Vireo filtered variants file (necessary only for donor_match mode).", + "description": "Path to Vireo filtered variants file (optional, only used in donor_match mode).", "fa_icon": "fas fa-file-code" }, "cell_genotype": { diff --git a/subworkflows/local/utils_nfcore_hadge_pipeline/main.nf b/subworkflows/local/utils_nfcore_hadge_pipeline/main.nf index a0074b28..2b8c74f4 100644 --- a/subworkflows/local/utils_nfcore_hadge_pipeline/main.nf +++ b/subworkflows/local/utils_nfcore_hadge_pipeline/main.nf @@ -191,8 +191,9 @@ def validateInputParameters() { if ( params.mode == 'donor_match' ){ checkParams('demultiplexing_result', 'DONOR_MATCH', 'donor_match', true) if ( params.find_variants ){ - ['cell_genotype', 'vireo_filtered_variants'].each { p -> - checkParams(p, 'FIND_VARIANTS', 'donor_match', true) + checkParams('cell_genotype', 'FIND_VARIANTS', 'donor_match', true) + if ( params.vireo_filtered_variants ){ + checkParams('vireo_filtered_variants', 'FIND_VARIANTS', 'donor_match', true) } } } diff --git a/workflows/hadge.nf b/workflows/hadge.nf index d6ea5732..4fa6d165 100644 --- a/workflows/hadge.nf +++ b/workflows/hadge.nf @@ -173,7 +173,8 @@ workflow HADGE { if ( params.find_variants ){ ch_find_variants = GENETIC_DEMULTIPLEXING.out.gt_cells - .join(GENETIC_DEMULTIPLEXING.out.vireo_filtered_variants) + .join(GENETIC_DEMULTIPLEXING.out.vireo_filtered_variants, remainder: true) + .map { meta, cell_genotype, vireo_variants -> [meta, cell_genotype, vireo_variants ?: []] } } ch_versions = ch_versions.mix(GENETIC_DEMULTIPLEXING.out.versions) @@ -185,7 +186,7 @@ workflow HADGE { if ( params.find_variants ){ ch_find_variants = ch_preprocessed.map{ meta, _rna, _hto, _bam, _barcodes, _vcf -> - [meta, params.cell_genotype, params.vireo_filtered_variants] + [meta, params.cell_genotype, params.vireo_filtered_variants ?: []] } } } From 93c30bbfea8e2cb10a297c9727be8864eb268f6e Mon Sep 17 00:00:00 2001 From: LuisHeinzlmeier Date: Sun, 27 Sep 2026 18:58:11 +0200 Subject: [PATCH 68/74] only run cellSNP when vireo or FIND_VARIANTS uses its output --- subworkflows/local/genetic_demultiplexing/main.nf | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/subworkflows/local/genetic_demultiplexing/main.nf b/subworkflows/local/genetic_demultiplexing/main.nf index 4a79efc3..0aa45358 100644 --- a/subworkflows/local/genetic_demultiplexing/main.nf +++ b/subworkflows/local/genetic_demultiplexing/main.nf @@ -61,7 +61,7 @@ workflow GENETIC_DEMULTIPLEXING { - if ( params.find_variants || methods.contains('vireo')){ + if ( methods.contains('vireo') || (params.mode == 'rescue' && params.match_donor && params.find_variants) ) { SAMTOOLS_INDEX(ch_samplesheet.map { meta, bam, _barcodes, _vcf -> [meta, bam] }) CELLSNP_MODEA( From a87f709df3dcd8167d7a98c86177e07fb417f341 Mon Sep 17 00:00:00 2001 From: LuisHeinzlmeier Date: Sun, 27 Sep 2026 19:01:31 +0200 Subject: [PATCH 69/74] update docs --- docs/usage.md | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/docs/usage.md b/docs/usage.md index b015a347..ff54b651 100644 --- a/docs/usage.md +++ b/docs/usage.md @@ -137,15 +137,15 @@ id2,rna.tar.gz,hto.tar.gz,chr21.bam,donor_genotype_chr21.vcf,2,barcodes.tsv id3,rna.tar.gz,hto.tar.gz,chr21.bam,donor_genotype_chr21.vcf,2,barcodes.tsv ``` -| Column | Description | -| ------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `sample` | Custom sample name. This entry will be identical for multiple sequencing libraries/runs from the same sample. Spaces in sample names are automatically converted to underscores (`_`). | -| `rna_matrix` | Full path to the RNA-Seq count matrices provided in a 10x Genomics format and compressed as `.tar.gz`. | -| `hto_matrix` | Full path to the hashing count matrices provided in a 10x Genomics format and compressed as `.tar.gz`. | -| `bam` | Full path to the alignment file (`.bam`). | -| `vcf` | Full path to common SNP genotypes vcf (`.vcf`). | -| `n_samples` | The number of multiplexed donors. | -| `barcodes` | Full path to the list of cell barcodes (e.g., `barcodes.tsv` from Cell Ranger) | +| Column | Description | +| ------------ | ------------------------------------------------------------------------------------------------------ | +| `sample` | Custom sample name. Must be unique within the samplesheet and cannot contain spaces. | +| `rna_matrix` | Full path to the RNA-Seq count matrices provided in a 10x Genomics format and compressed as `.tar.gz`. | +| `hto_matrix` | Full path to the hashing count matrices provided in a 10x Genomics format and compressed as `.tar.gz`. | +| `bam` | Full path to the alignment file (`.bam`). | +| `vcf` | Full path to common SNP genotypes vcf (`.vcf`). | +| `n_samples` | The number of multiplexed donors. | +| `barcodes` | Full path to the list of cell barcodes (e.g., `barcodes.tsv` from Cell Ranger) | :::tip{collapse title="Samplesheet Input Requirements by Module"} From 858bf39533f8d2707c4325eef4a5abf02ec4af88 Mon Sep 17 00:00:00 2001 From: LuisHeinzlmeier Date: Sun, 27 Sep 2026 19:45:24 +0200 Subject: [PATCH 70/74] only allow one samplesheet row in donor_match mode --- subworkflows/local/utils_nfcore_hadge_pipeline/main.nf | 8 +++++++- workflows/hadge.nf | 2 +- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/subworkflows/local/utils_nfcore_hadge_pipeline/main.nf b/subworkflows/local/utils_nfcore_hadge_pipeline/main.nf index 2b8c74f4..e8ce43ba 100644 --- a/subworkflows/local/utils_nfcore_hadge_pipeline/main.nf +++ b/subworkflows/local/utils_nfcore_hadge_pipeline/main.nf @@ -106,7 +106,13 @@ workflow PIPELINE_INITIALISATION { // Create channel from input file provided through params.input // - channel.fromList(samplesheetToList(input, "${projectDir}/assets/schema_input.json")) + def samplesheet_list = samplesheetToList(input, "${projectDir}/assets/schema_input.json") + + if (params.mode == 'donor_match' && samplesheet_list.size() > 1) { + error "Mode 'donor_match' supports only one samplesheet row, but ${samplesheet_list.size()} were provided." + } + + channel.fromList(samplesheet_list) .map { samplesheet -> validateInputSamplesheet(samplesheet) } diff --git a/workflows/hadge.nf b/workflows/hadge.nf index 4fa6d165..e3e0f3fc 100644 --- a/workflows/hadge.nf +++ b/workflows/hadge.nf @@ -182,7 +182,7 @@ workflow HADGE { } else if ( params.mode == 'donor_match' ){ - ch_donor_match = ch_preprocessed.map{ meta, _rna, _hto, _bam, _barcodes, _vcf -> [meta, params.demultiplexing_result] } + ch_donor_match = ch_preprocessed.map{ meta, _rna, _hto, _bam, _barcodes, _vcf -> [meta, file(params.demultiplexing_result, checkIfExists: true)] } if ( params.find_variants ){ ch_find_variants = ch_preprocessed.map{ meta, _rna, _hto, _bam, _barcodes, _vcf -> From 4d112212de9f10e33adce66503d443b53b3612a3 Mon Sep 17 00:00:00 2001 From: LuisHeinzlmeier Date: Sun, 27 Sep 2026 20:00:36 +0200 Subject: [PATCH 71/74] add checkIfExists --- workflows/hadge.nf | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/workflows/hadge.nf b/workflows/hadge.nf index e3e0f3fc..6090a229 100644 --- a/workflows/hadge.nf +++ b/workflows/hadge.nf @@ -186,7 +186,7 @@ workflow HADGE { if ( params.find_variants ){ ch_find_variants = ch_preprocessed.map{ meta, _rna, _hto, _bam, _barcodes, _vcf -> - [meta, params.cell_genotype, params.vireo_filtered_variants ?: []] + [meta, file(params.cell_genotype, checkIfExists: true), params.vireo_filtered_variants ? file(params.vireo_filtered_variants, checkIfExists: true) : []] } } } @@ -239,7 +239,7 @@ workflow HADGE { .combine(GENETIC_DEMULTIPLEXING.out.gt_donors, by: 0) : ch_subset_gt_donors .map { meta, variants, type -> - [ meta, variants, type, params.gt_donors ] + [ meta, variants, type, file(params.gt_donors, checkIfExists: true) ] } ch_subset_gt_donors = ch_subset_gt_donors From fabfbfc4273ad9b488a672b4fcf1bc9f53d6ee8b Mon Sep 17 00:00:00 2001 From: LuisHeinzlmeier Date: Sun, 27 Sep 2026 20:45:54 +0200 Subject: [PATCH 72/74] fine tune resources of vireo and souporcell --- conf/modules.config | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/conf/modules.config b/conf/modules.config index 9875bec2..1982c0ec 100644 --- a/conf/modules.config +++ b/conf/modules.config @@ -150,10 +150,11 @@ process { } withName: VIREO { - // Real-life dataset (AWS): peak RSS 255 GB, ~2 cores used, 10 min. Memory scales with dataset size (cells x variants) - cpus = { 4 * task.attempt } - memory = { 320.GB * task.attempt } - time = { 4.h * task.attempt } + // Real-life test_full dataset (AWS): peak RSS 255 GB, ~2 cores used, 10 min. Memory scales with dataset size (cells x variants) + cpus = { 4 * task.attempt } + memory = { (workflow.profile.contains('test_full') ? 360.GB : 90.GB) * task.attempt } + time = { 4.h * task.attempt } + maxRetries = 2 ext.args = { [ "--genoTag", @@ -269,10 +270,11 @@ process { } withName: SOUPORCELL { - // Real-life dataset (AWS, 60 threads): peak RSS 51 GB, 11 h runtime, ~4.7 cores used on average. - cpus = 60 - memory = { 64.GB * task.attempt } - time = { 24.h * task.attempt } + // Real-life test_full dataset (AWS, 60 threads): peak RSS 51 GB, 11 h runtime, ~4.7 cores used on average. + cpus = { (workflow.profile.contains('test_full') ? 60 : 12) * task.attempt } + memory = { 64.GB * task.attempt } + time = { 24.h * task.attempt } + maxRetries = 2 ext.args = { [ "-p", From cb0ba6e7d6ed2fe98ff7186e30e11d592875f0cd Mon Sep 17 00:00:00 2001 From: LuisHeinzlmeier Date: Sun, 27 Sep 2026 22:11:43 +0200 Subject: [PATCH 73/74] add mqc plot --- docs/output.md | 2 + .../local/genetic_demultiplexing/main.nf | 1 + .../local/hash_demultiplexing/main.nf | 1 + workflows/hadge.nf | 46 ++++++++++++++++++- 4 files changed, 48 insertions(+), 2 deletions(-) diff --git a/docs/output.md b/docs/output.md index 8063269c..09042b78 100644 --- a/docs/output.md +++ b/docs/output.md @@ -148,6 +148,8 @@ The pipeline is built using [Nextflow](https://www.nextflow.io/) and processes d [MultiQC](http://multiqc.info) is a visualization tool that generates a single HTML report summarising all samples in your project. Most of the pipeline QC results are visualised in the report and further statistics are available in the report data directory. +The **Cell classification** section shows a stacked bar plot with the number of barcodes classified as singlet, doublet or negative by each deconvolution method for every sample. It is built from the `*_(genetic|hashing)_overview_classification.csv` tables and helps to spot methods or samples with unusually high doublet or negative rates. + Results generated by MultiQC collate pipeline QC from supported tools e.g. FastQC. The pipeline has special steps which also allow the software versions to be reported in the MultiQC output for future traceability. For more information about how to use MultiQC reports, see . ### Pipeline information diff --git a/subworkflows/local/genetic_demultiplexing/main.nf b/subworkflows/local/genetic_demultiplexing/main.nf index 0aa45358..8b0c8a6f 100644 --- a/subworkflows/local/genetic_demultiplexing/main.nf +++ b/subworkflows/local/genetic_demultiplexing/main.nf @@ -135,6 +135,7 @@ workflow GENETIC_DEMULTIPLEXING { emit: summary_assignment = GENE_SUMMARY.out.assignment summary_classification = GENE_SUMMARY.out.classification + overview_classification = GENE_SUMMARY.out.overview_classification vireo_filtered_variants = ch_vireo_filtered_variants gt_cells = ch_gt_cells gt_donors = ch_gt_donors diff --git a/subworkflows/local/hash_demultiplexing/main.nf b/subworkflows/local/hash_demultiplexing/main.nf index 87329148..fa5e95af 100644 --- a/subworkflows/local/hash_demultiplexing/main.nf +++ b/subworkflows/local/hash_demultiplexing/main.nf @@ -163,5 +163,6 @@ workflow HASH_DEMULTIPLEXING { emit: summary_assignment = HASH_SUMMARY.out.assignment summary_classification = HASH_SUMMARY.out.classification + overview_classification = HASH_SUMMARY.out.overview_classification versions = ch_versions // channel: [ versions.yml ] } diff --git a/workflows/hadge.nf b/workflows/hadge.nf index 6090a229..d91b8a9c 100644 --- a/workflows/hadge.nf +++ b/workflows/hadge.nf @@ -42,19 +42,20 @@ workflow HADGE { ch_find_variants = channel.empty() ch_subset_gt_donors = channel.empty() ch_multiqc_files = channel.empty() + ch_overview_classification = channel.empty() // ------------------------------ preprocessing start ------------------------------- // untar matrices ch_rna = ch_samplesheet.map { meta, rna, _hto, _bam, _barcodes, _vcf -> [meta, rna] } .branch { _meta, rna -> - tar: rna != null && rna.endsWith('.tar.gz') + tar: rna != null && rna.name.endsWith('.tar.gz') directory: true } ch_hto = ch_samplesheet.map { meta, _rna, hto, _bam, _barcodes, _vcf -> [meta, hto] } .branch { _meta, hto -> - tar: hto != null && hto.endsWith('.tar.gz') + tar: hto != null && hto.name.endsWith('.tar.gz') directory: true } @@ -111,6 +112,8 @@ workflow HADGE { ch_donor_match = GENETIC_DEMULTIPLEXING.out.summary_assignment + ch_overview_classification = GENETIC_DEMULTIPLEXING.out.overview_classification + ch_versions = ch_versions.mix(GENETIC_DEMULTIPLEXING.out.versions) } @@ -128,6 +131,8 @@ workflow HADGE { ch_donor_match = HASH_DEMULTIPLEXING.out.summary_assignment + ch_overview_classification = HASH_DEMULTIPLEXING.out.overview_classification + ch_versions = ch_versions.mix(HASH_DEMULTIPLEXING.out.versions) } @@ -171,6 +176,9 @@ workflow HADGE { ch_donor_match = JOIN_RESULTS_ASSIGNMENT.out.csv + ch_overview_classification = GENETIC_DEMULTIPLEXING.out.overview_classification + .mix(HASH_DEMULTIPLEXING.out.overview_classification) + if ( params.find_variants ){ ch_find_variants = GENETIC_DEMULTIPLEXING.out.gt_cells .join(GENETIC_DEMULTIPLEXING.out.vireo_filtered_variants, remainder: true) @@ -279,10 +287,44 @@ workflow HADGE { newLine: true ) + // + // Collate singlet/doublet/negative counts per sample and method for MultiQC + // + def ch_classification_mqc = ch_overview_classification + .splitCsv(header: true, elem: 1) + .map { meta, row -> + "${meta.id}_${row.method}\t${row.singlet ?: 0}\t${row.doublet ?: 0}\t${row.negative ?: 0}" + } + .collectFile( + name: 'classification_mqc.tsv', + seed: [ + "# id: 'hadge_classification'", + "# section_name: 'Cell classification'", + "# description: 'Number of barcodes classified as singlet, doublet or negative by each deconvolution method.'", + "# plot_type: 'bargraph'", + "# pconfig:", + "# id: 'hadge_classification_plot'", + "# title: 'nf-core/hadge: Cell classification per method'", + "# ylab: 'Number of barcodes'", + "# cpswitch_counts_label: 'Number of barcodes'", + "# categories:", + "# singlet:", + "# name: 'Singlet'", + "# doublet:", + "# name: 'Doublet'", + "# negative:", + "# name: 'Negative'", + "Sample\tsinglet\tdoublet\tnegative", + ].join('\n'), + newLine: true, + sort: true + ) + // // MODULE: MultiQC // ch_multiqc_files = ch_multiqc_files.mix(ch_collated_versions) + ch_multiqc_files = ch_multiqc_files.mix(ch_classification_mqc) def ch_summary_params = paramsSummaryMap(workflow, parameters_schema: "nextflow_schema.json") def ch_workflow_summary = channel.value(paramsSummaryMultiqc(ch_summary_params)) ch_multiqc_files = ch_multiqc_files.mix(ch_workflow_summary.collectFile(name: 'workflow_summary_mqc.yaml')) From fedeeb3ef358fb7cf576eae6f20b97f95ef920d3 Mon Sep 17 00:00:00 2001 From: Luis Heinzlmeier Date: Sun, 27 Sep 2026 22:48:56 +0200 Subject: [PATCH 74/74] update snapshots --- tests/default.nf.test.snap | 18 ++- tests/test_donor_match.nf.test.snap | 240 +--------------------------- tests/test_genetic.nf.test.snap | 20 ++- tests/test_hashing.nf.test.snap | 18 ++- 4 files changed, 50 insertions(+), 246 deletions(-) diff --git a/tests/default.nf.test.snap b/tests/default.nf.test.snap index 4766a28d..eae3b395 100644 --- a/tests/default.nf.test.snap +++ b/tests/default.nf.test.snap @@ -751,8 +751,19 @@ "multiqc/multiqc_data/multiqc.parquet", "multiqc/multiqc_data/multiqc_citations.txt", "multiqc/multiqc_data/multiqc_data.json", + "multiqc/multiqc_data/multiqc_hadge_classification_plot.txt", "multiqc/multiqc_data/multiqc_software_versions.txt", "multiqc/multiqc_data/multiqc_sources.txt", + "multiqc/multiqc_plots", + "multiqc/multiqc_plots/pdf", + "multiqc/multiqc_plots/pdf/hadge_classification_plot-cnt.pdf", + "multiqc/multiqc_plots/pdf/hadge_classification_plot-pct.pdf", + "multiqc/multiqc_plots/png", + "multiqc/multiqc_plots/png/hadge_classification_plot-cnt.png", + "multiqc/multiqc_plots/png/hadge_classification_plot-pct.png", + "multiqc/multiqc_plots/svg", + "multiqc/multiqc_plots/svg/hadge_classification_plot-cnt.svg", + "multiqc/multiqc_plots/svg/hadge_classification_plot-pct.svg", "multiqc/multiqc_report.html", "pipeline_info", "pipeline_info/nf_core_hadge_software_mqc_versions.yml", @@ -1141,6 +1152,7 @@ "test3_hashing_summary_assignment.csv:md5,31a5bd36901a64e5908a38714b19f622", "test3_hashing_summary_classification.csv:md5,c929da1c41e0adc0f3e120f27db00fb6", "multiqc_citations.txt:md5,4c806e63a283ec1b7e78cdae3a923d4f", + "multiqc_hadge_classification_plot.txt:md5,ff546217b09443cf656c358a6a9a6760", "test1_assignment.csv:md5,3b7d6b6765ae4722b97375fa933b93c5", "test1_classification.csv:md5,07c15ecad1ff8ea08163a0fabc84d3a9", "test1_genetic.h5ad:md5,00ed3288cba12a0a4771913a14d5733e", @@ -1178,10 +1190,10 @@ "test3_GT_donors.vireo.vcf.gz:md5,921ce18ca982b86589477432fce4e3e6" ] ], - "timestamp": "2026-09-05T21:24:42.704033458", + "timestamp": "2026-09-27T22:25:43.69782722", "meta": { - "nf-test": "0.9.5", - "nextflow": "26.04.6" + "nf-test": "0.9.4", + "nextflow": "25.10.4" } } } \ No newline at end of file diff --git a/tests/test_donor_match.nf.test.snap b/tests/test_donor_match.nf.test.snap index 277cdbdb..de17e3c3 100644 --- a/tests/test_donor_match.nf.test.snap +++ b/tests/test_donor_match.nf.test.snap @@ -77,112 +77,6 @@ "donor_match/test1/test1_best_donor_match.csv", "donor_match/test1/test1_best_intersect_assignment_after_match.csv", "donor_match/test1/test1_score_record.csv", - "donor_match/test2", - "donor_match/test2/freemuxlet_vs_bff_consensuscall", - "donor_match/test2/freemuxlet_vs_bff_consensuscall/test2_freemuxlet_vs_bff_consensuscall_all_assignment_after_match.csv", - "donor_match/test2/freemuxlet_vs_bff_consensuscall/test2_freemuxlet_vs_bff_consensuscall_concordance_heatmap.png", - "donor_match/test2/freemuxlet_vs_bff_consensuscall/test2_freemuxlet_vs_bff_consensuscall_correlation_res.csv", - "donor_match/test2/freemuxlet_vs_bff_consensuscall/test2_freemuxlet_vs_bff_consensuscall_donor_match.csv", - "donor_match/test2/freemuxlet_vs_bff_consensuscall/test2_freemuxlet_vs_bff_consensuscall_intersect_assignment_after_match.csv", - "donor_match/test2/freemuxlet_vs_bff_raw", - "donor_match/test2/freemuxlet_vs_bff_raw/test2_freemuxlet_vs_bff_raw_all_assignment_after_match.csv", - "donor_match/test2/freemuxlet_vs_bff_raw/test2_freemuxlet_vs_bff_raw_concordance_heatmap.png", - "donor_match/test2/freemuxlet_vs_bff_raw/test2_freemuxlet_vs_bff_raw_correlation_res.csv", - "donor_match/test2/freemuxlet_vs_bff_raw/test2_freemuxlet_vs_bff_raw_donor_match.csv", - "donor_match/test2/freemuxlet_vs_bff_raw/test2_freemuxlet_vs_bff_raw_intersect_assignment_after_match.csv", - "donor_match/test2/freemuxlet_vs_gmmdemux", - "donor_match/test2/freemuxlet_vs_gmmdemux/test2_freemuxlet_vs_gmmdemux_all_assignment_after_match.csv", - "donor_match/test2/freemuxlet_vs_gmmdemux/test2_freemuxlet_vs_gmmdemux_concordance_heatmap.png", - "donor_match/test2/freemuxlet_vs_gmmdemux/test2_freemuxlet_vs_gmmdemux_correlation_res.csv", - "donor_match/test2/freemuxlet_vs_gmmdemux/test2_freemuxlet_vs_gmmdemux_donor_match.csv", - "donor_match/test2/freemuxlet_vs_gmmdemux/test2_freemuxlet_vs_gmmdemux_intersect_assignment_after_match.csv", - "donor_match/test2/freemuxlet_vs_hasheddrops", - "donor_match/test2/freemuxlet_vs_hasheddrops/test2_freemuxlet_vs_hasheddrops_all_assignment_after_match.csv", - "donor_match/test2/freemuxlet_vs_hasheddrops/test2_freemuxlet_vs_hasheddrops_concordance_heatmap.png", - "donor_match/test2/freemuxlet_vs_hasheddrops/test2_freemuxlet_vs_hasheddrops_correlation_res.csv", - "donor_match/test2/freemuxlet_vs_hasheddrops/test2_freemuxlet_vs_hasheddrops_donor_match.csv", - "donor_match/test2/freemuxlet_vs_hasheddrops/test2_freemuxlet_vs_hasheddrops_intersect_assignment_after_match.csv", - "donor_match/test2/souporcell_vs_bff_consensuscall", - "donor_match/test2/souporcell_vs_bff_consensuscall/test2_souporcell_vs_bff_consensuscall_all_assignment_after_match.csv", - "donor_match/test2/souporcell_vs_bff_consensuscall/test2_souporcell_vs_bff_consensuscall_concordance_heatmap.png", - "donor_match/test2/souporcell_vs_bff_consensuscall/test2_souporcell_vs_bff_consensuscall_correlation_res.csv", - "donor_match/test2/souporcell_vs_bff_consensuscall/test2_souporcell_vs_bff_consensuscall_donor_match.csv", - "donor_match/test2/souporcell_vs_bff_consensuscall/test2_souporcell_vs_bff_consensuscall_intersect_assignment_after_match.csv", - "donor_match/test2/souporcell_vs_bff_raw", - "donor_match/test2/souporcell_vs_bff_raw/test2_souporcell_vs_bff_raw_all_assignment_after_match.csv", - "donor_match/test2/souporcell_vs_bff_raw/test2_souporcell_vs_bff_raw_concordance_heatmap.png", - "donor_match/test2/souporcell_vs_bff_raw/test2_souporcell_vs_bff_raw_correlation_res.csv", - "donor_match/test2/souporcell_vs_bff_raw/test2_souporcell_vs_bff_raw_donor_match.csv", - "donor_match/test2/souporcell_vs_bff_raw/test2_souporcell_vs_bff_raw_intersect_assignment_after_match.csv", - "donor_match/test2/souporcell_vs_gmmdemux", - "donor_match/test2/souporcell_vs_gmmdemux/test2_souporcell_vs_gmmdemux_all_assignment_after_match.csv", - "donor_match/test2/souporcell_vs_gmmdemux/test2_souporcell_vs_gmmdemux_concordance_heatmap.png", - "donor_match/test2/souporcell_vs_gmmdemux/test2_souporcell_vs_gmmdemux_correlation_res.csv", - "donor_match/test2/souporcell_vs_gmmdemux/test2_souporcell_vs_gmmdemux_donor_match.csv", - "donor_match/test2/souporcell_vs_gmmdemux/test2_souporcell_vs_gmmdemux_intersect_assignment_after_match.csv", - "donor_match/test2/souporcell_vs_hasheddrops", - "donor_match/test2/souporcell_vs_hasheddrops/test2_souporcell_vs_hasheddrops_all_assignment_after_match.csv", - "donor_match/test2/souporcell_vs_hasheddrops/test2_souporcell_vs_hasheddrops_concordance_heatmap.png", - "donor_match/test2/souporcell_vs_hasheddrops/test2_souporcell_vs_hasheddrops_correlation_res.csv", - "donor_match/test2/souporcell_vs_hasheddrops/test2_souporcell_vs_hasheddrops_donor_match.csv", - "donor_match/test2/souporcell_vs_hasheddrops/test2_souporcell_vs_hasheddrops_intersect_assignment_after_match.csv", - "donor_match/test2/test2_best_all_assignment_after_match.csv", - "donor_match/test2/test2_best_donor_match.csv", - "donor_match/test2/test2_best_intersect_assignment_after_match.csv", - "donor_match/test2/test2_score_record.csv", - "donor_match/test3", - "donor_match/test3/freemuxlet_vs_bff_consensuscall", - "donor_match/test3/freemuxlet_vs_bff_consensuscall/test3_freemuxlet_vs_bff_consensuscall_all_assignment_after_match.csv", - "donor_match/test3/freemuxlet_vs_bff_consensuscall/test3_freemuxlet_vs_bff_consensuscall_concordance_heatmap.png", - "donor_match/test3/freemuxlet_vs_bff_consensuscall/test3_freemuxlet_vs_bff_consensuscall_correlation_res.csv", - "donor_match/test3/freemuxlet_vs_bff_consensuscall/test3_freemuxlet_vs_bff_consensuscall_donor_match.csv", - "donor_match/test3/freemuxlet_vs_bff_consensuscall/test3_freemuxlet_vs_bff_consensuscall_intersect_assignment_after_match.csv", - "donor_match/test3/freemuxlet_vs_bff_raw", - "donor_match/test3/freemuxlet_vs_bff_raw/test3_freemuxlet_vs_bff_raw_all_assignment_after_match.csv", - "donor_match/test3/freemuxlet_vs_bff_raw/test3_freemuxlet_vs_bff_raw_concordance_heatmap.png", - "donor_match/test3/freemuxlet_vs_bff_raw/test3_freemuxlet_vs_bff_raw_correlation_res.csv", - "donor_match/test3/freemuxlet_vs_bff_raw/test3_freemuxlet_vs_bff_raw_donor_match.csv", - "donor_match/test3/freemuxlet_vs_bff_raw/test3_freemuxlet_vs_bff_raw_intersect_assignment_after_match.csv", - "donor_match/test3/freemuxlet_vs_gmmdemux", - "donor_match/test3/freemuxlet_vs_gmmdemux/test3_freemuxlet_vs_gmmdemux_all_assignment_after_match.csv", - "donor_match/test3/freemuxlet_vs_gmmdemux/test3_freemuxlet_vs_gmmdemux_concordance_heatmap.png", - "donor_match/test3/freemuxlet_vs_gmmdemux/test3_freemuxlet_vs_gmmdemux_correlation_res.csv", - "donor_match/test3/freemuxlet_vs_gmmdemux/test3_freemuxlet_vs_gmmdemux_donor_match.csv", - "donor_match/test3/freemuxlet_vs_gmmdemux/test3_freemuxlet_vs_gmmdemux_intersect_assignment_after_match.csv", - "donor_match/test3/freemuxlet_vs_hasheddrops", - "donor_match/test3/freemuxlet_vs_hasheddrops/test3_freemuxlet_vs_hasheddrops_all_assignment_after_match.csv", - "donor_match/test3/freemuxlet_vs_hasheddrops/test3_freemuxlet_vs_hasheddrops_concordance_heatmap.png", - "donor_match/test3/freemuxlet_vs_hasheddrops/test3_freemuxlet_vs_hasheddrops_correlation_res.csv", - "donor_match/test3/freemuxlet_vs_hasheddrops/test3_freemuxlet_vs_hasheddrops_donor_match.csv", - "donor_match/test3/freemuxlet_vs_hasheddrops/test3_freemuxlet_vs_hasheddrops_intersect_assignment_after_match.csv", - "donor_match/test3/souporcell_vs_bff_consensuscall", - "donor_match/test3/souporcell_vs_bff_consensuscall/test3_souporcell_vs_bff_consensuscall_all_assignment_after_match.csv", - "donor_match/test3/souporcell_vs_bff_consensuscall/test3_souporcell_vs_bff_consensuscall_concordance_heatmap.png", - "donor_match/test3/souporcell_vs_bff_consensuscall/test3_souporcell_vs_bff_consensuscall_correlation_res.csv", - "donor_match/test3/souporcell_vs_bff_consensuscall/test3_souporcell_vs_bff_consensuscall_donor_match.csv", - "donor_match/test3/souporcell_vs_bff_consensuscall/test3_souporcell_vs_bff_consensuscall_intersect_assignment_after_match.csv", - "donor_match/test3/souporcell_vs_bff_raw", - "donor_match/test3/souporcell_vs_bff_raw/test3_souporcell_vs_bff_raw_all_assignment_after_match.csv", - "donor_match/test3/souporcell_vs_bff_raw/test3_souporcell_vs_bff_raw_concordance_heatmap.png", - "donor_match/test3/souporcell_vs_bff_raw/test3_souporcell_vs_bff_raw_correlation_res.csv", - "donor_match/test3/souporcell_vs_bff_raw/test3_souporcell_vs_bff_raw_donor_match.csv", - "donor_match/test3/souporcell_vs_bff_raw/test3_souporcell_vs_bff_raw_intersect_assignment_after_match.csv", - "donor_match/test3/souporcell_vs_gmmdemux", - "donor_match/test3/souporcell_vs_gmmdemux/test3_souporcell_vs_gmmdemux_all_assignment_after_match.csv", - "donor_match/test3/souporcell_vs_gmmdemux/test3_souporcell_vs_gmmdemux_concordance_heatmap.png", - "donor_match/test3/souporcell_vs_gmmdemux/test3_souporcell_vs_gmmdemux_correlation_res.csv", - "donor_match/test3/souporcell_vs_gmmdemux/test3_souporcell_vs_gmmdemux_donor_match.csv", - "donor_match/test3/souporcell_vs_gmmdemux/test3_souporcell_vs_gmmdemux_intersect_assignment_after_match.csv", - "donor_match/test3/souporcell_vs_hasheddrops", - "donor_match/test3/souporcell_vs_hasheddrops/test3_souporcell_vs_hasheddrops_all_assignment_after_match.csv", - "donor_match/test3/souporcell_vs_hasheddrops/test3_souporcell_vs_hasheddrops_concordance_heatmap.png", - "donor_match/test3/souporcell_vs_hasheddrops/test3_souporcell_vs_hasheddrops_correlation_res.csv", - "donor_match/test3/souporcell_vs_hasheddrops/test3_souporcell_vs_hasheddrops_donor_match.csv", - "donor_match/test3/souporcell_vs_hasheddrops/test3_souporcell_vs_hasheddrops_intersect_assignment_after_match.csv", - "donor_match/test3/test3_best_all_assignment_after_match.csv", - "donor_match/test3/test3_best_donor_match.csv", - "donor_match/test3/test3_best_intersect_assignment_after_match.csv", - "donor_match/test3/test3_score_record.csv", "find_variants", "find_variants/test1", "find_variants/test1/hto-1", @@ -200,38 +94,6 @@ "find_variants/test1/test1_donor_specific_variants.csv", "find_variants/test1/test1_donor_specific_variants_upset.png", "find_variants/test1/test1_vireo_variants.csv", - "find_variants/test2", - "find_variants/test2/hto-1", - "find_variants/test2/hto-1/test2_hto-1_informative_variants.csv", - "find_variants/test2/hto-1/test2_hto-1_matched_gt.csv", - "find_variants/test2/hto-1/test2_hto-1_unmatched_gt.csv", - "find_variants/test2/hto-2", - "find_variants/test2/hto-2/test2_hto-2_informative_variants.csv", - "find_variants/test2/hto-2/test2_hto-2_matched_gt.csv", - "find_variants/test2/hto-2/test2_hto-2_unmatched_gt.csv", - "find_variants/test2/subset_gt_donors", - "find_variants/test2/subset_gt_donors/test2_donor_specific.vcf.gz", - "find_variants/test2/subset_gt_donors/test2_vireo.vcf.gz", - "find_variants/test2/test2_all_representative_variants.csv", - "find_variants/test2/test2_donor_specific_variants.csv", - "find_variants/test2/test2_donor_specific_variants_upset.png", - "find_variants/test2/test2_vireo_variants.csv", - "find_variants/test3", - "find_variants/test3/hto-1", - "find_variants/test3/hto-1/test3_hto-1_informative_variants.csv", - "find_variants/test3/hto-1/test3_hto-1_matched_gt.csv", - "find_variants/test3/hto-1/test3_hto-1_unmatched_gt.csv", - "find_variants/test3/hto-2", - "find_variants/test3/hto-2/test3_hto-2_informative_variants.csv", - "find_variants/test3/hto-2/test3_hto-2_matched_gt.csv", - "find_variants/test3/hto-2/test3_hto-2_unmatched_gt.csv", - "find_variants/test3/subset_gt_donors", - "find_variants/test3/subset_gt_donors/test3_donor_specific.vcf.gz", - "find_variants/test3/subset_gt_donors/test3_vireo.vcf.gz", - "find_variants/test3/test3_all_representative_variants.csv", - "find_variants/test3/test3_donor_specific_variants.csv", - "find_variants/test3/test3_donor_specific_variants_upset.png", - "find_variants/test3/test3_vireo_variants.csv", "multiqc", "multiqc/multiqc_data", "multiqc/multiqc_data/llms-full.txt", @@ -282,78 +144,6 @@ "test1_best_donor_match.csv:md5,d1feea9baad73b5bc106472a226cbbf2", "test1_best_intersect_assignment_after_match.csv:md5,2f98b7e021001d4093324dad82177d9d", "test1_score_record.csv:md5,19e6d7aa257ab96338c70bb1e69442b3", - "test2_freemuxlet_vs_bff_consensuscall_all_assignment_after_match.csv:md5,c5552086e271152756067aa99ab77fa1", - "test2_freemuxlet_vs_bff_consensuscall_correlation_res.csv:md5,5f2f2076175e313f10396b32fac4c26c", - "test2_freemuxlet_vs_bff_consensuscall_donor_match.csv:md5,b92352374bf4b32f7e3a1b032a4c8b5f", - "test2_freemuxlet_vs_bff_consensuscall_intersect_assignment_after_match.csv:md5,7827c80fbdb30275350dfea4e6ee3a46", - "test2_freemuxlet_vs_bff_raw_all_assignment_after_match.csv:md5,f64080fd544ffd711634e06719c2a574", - "test2_freemuxlet_vs_bff_raw_correlation_res.csv:md5,5f2f2076175e313f10396b32fac4c26c", - "test2_freemuxlet_vs_bff_raw_donor_match.csv:md5,b92352374bf4b32f7e3a1b032a4c8b5f", - "test2_freemuxlet_vs_bff_raw_intersect_assignment_after_match.csv:md5,51b6c078396c57902e610e8497b52ec6", - "test2_freemuxlet_vs_gmmdemux_all_assignment_after_match.csv:md5,843151ffa9d30820ade39c0fbe3a5150", - "test2_freemuxlet_vs_gmmdemux_correlation_res.csv:md5,5f2f2076175e313f10396b32fac4c26c", - "test2_freemuxlet_vs_gmmdemux_donor_match.csv:md5,b92352374bf4b32f7e3a1b032a4c8b5f", - "test2_freemuxlet_vs_gmmdemux_intersect_assignment_after_match.csv:md5,e55c2902ef5d84a00e0df8c814d0b572", - "test2_freemuxlet_vs_hasheddrops_all_assignment_after_match.csv:md5,52d09d2ed176e113c88b8325edd9cb79", - "test2_freemuxlet_vs_hasheddrops_correlation_res.csv:md5,5f2f2076175e313f10396b32fac4c26c", - "test2_freemuxlet_vs_hasheddrops_donor_match.csv:md5,b92352374bf4b32f7e3a1b032a4c8b5f", - "test2_freemuxlet_vs_hasheddrops_intersect_assignment_after_match.csv:md5,78e11f774a11260199c5262b551c4618", - "test2_souporcell_vs_bff_consensuscall_all_assignment_after_match.csv:md5,0453e72b042219598602022c78f500b0", - "test2_souporcell_vs_bff_consensuscall_correlation_res.csv:md5,7e632ed518a1e6f02ed1ea25a28a4e45", - "test2_souporcell_vs_bff_consensuscall_donor_match.csv:md5,d1feea9baad73b5bc106472a226cbbf2", - "test2_souporcell_vs_bff_consensuscall_intersect_assignment_after_match.csv:md5,2f98b7e021001d4093324dad82177d9d", - "test2_souporcell_vs_bff_raw_all_assignment_after_match.csv:md5,9667ceb1476d7ef9155042a2bfea590a", - "test2_souporcell_vs_bff_raw_correlation_res.csv:md5,7e632ed518a1e6f02ed1ea25a28a4e45", - "test2_souporcell_vs_bff_raw_donor_match.csv:md5,d1feea9baad73b5bc106472a226cbbf2", - "test2_souporcell_vs_bff_raw_intersect_assignment_after_match.csv:md5,245e79ea38948063f671d8553afd2d55", - "test2_souporcell_vs_gmmdemux_all_assignment_after_match.csv:md5,ab32c63ef3a5e5258b399aa2a193ffe4", - "test2_souporcell_vs_gmmdemux_correlation_res.csv:md5,7e632ed518a1e6f02ed1ea25a28a4e45", - "test2_souporcell_vs_gmmdemux_donor_match.csv:md5,d1feea9baad73b5bc106472a226cbbf2", - "test2_souporcell_vs_gmmdemux_intersect_assignment_after_match.csv:md5,e10f4fc3d56187b82fefa95b1a6ddfca", - "test2_souporcell_vs_hasheddrops_all_assignment_after_match.csv:md5,cc8f74509dc0a10c488c2a1e5e064644", - "test2_souporcell_vs_hasheddrops_correlation_res.csv:md5,7e632ed518a1e6f02ed1ea25a28a4e45", - "test2_souporcell_vs_hasheddrops_donor_match.csv:md5,d1feea9baad73b5bc106472a226cbbf2", - "test2_souporcell_vs_hasheddrops_intersect_assignment_after_match.csv:md5,f31968c6aa78e9d8d4c43b99035f2cf3", - "test2_best_all_assignment_after_match.csv:md5,0453e72b042219598602022c78f500b0", - "test2_best_donor_match.csv:md5,d1feea9baad73b5bc106472a226cbbf2", - "test2_best_intersect_assignment_after_match.csv:md5,2f98b7e021001d4093324dad82177d9d", - "test2_score_record.csv:md5,19e6d7aa257ab96338c70bb1e69442b3", - "test3_freemuxlet_vs_bff_consensuscall_all_assignment_after_match.csv:md5,c5552086e271152756067aa99ab77fa1", - "test3_freemuxlet_vs_bff_consensuscall_correlation_res.csv:md5,5f2f2076175e313f10396b32fac4c26c", - "test3_freemuxlet_vs_bff_consensuscall_donor_match.csv:md5,b92352374bf4b32f7e3a1b032a4c8b5f", - "test3_freemuxlet_vs_bff_consensuscall_intersect_assignment_after_match.csv:md5,7827c80fbdb30275350dfea4e6ee3a46", - "test3_freemuxlet_vs_bff_raw_all_assignment_after_match.csv:md5,f64080fd544ffd711634e06719c2a574", - "test3_freemuxlet_vs_bff_raw_correlation_res.csv:md5,5f2f2076175e313f10396b32fac4c26c", - "test3_freemuxlet_vs_bff_raw_donor_match.csv:md5,b92352374bf4b32f7e3a1b032a4c8b5f", - "test3_freemuxlet_vs_bff_raw_intersect_assignment_after_match.csv:md5,51b6c078396c57902e610e8497b52ec6", - "test3_freemuxlet_vs_gmmdemux_all_assignment_after_match.csv:md5,843151ffa9d30820ade39c0fbe3a5150", - "test3_freemuxlet_vs_gmmdemux_correlation_res.csv:md5,5f2f2076175e313f10396b32fac4c26c", - "test3_freemuxlet_vs_gmmdemux_donor_match.csv:md5,b92352374bf4b32f7e3a1b032a4c8b5f", - "test3_freemuxlet_vs_gmmdemux_intersect_assignment_after_match.csv:md5,e55c2902ef5d84a00e0df8c814d0b572", - "test3_freemuxlet_vs_hasheddrops_all_assignment_after_match.csv:md5,52d09d2ed176e113c88b8325edd9cb79", - "test3_freemuxlet_vs_hasheddrops_correlation_res.csv:md5,5f2f2076175e313f10396b32fac4c26c", - "test3_freemuxlet_vs_hasheddrops_donor_match.csv:md5,b92352374bf4b32f7e3a1b032a4c8b5f", - "test3_freemuxlet_vs_hasheddrops_intersect_assignment_after_match.csv:md5,78e11f774a11260199c5262b551c4618", - "test3_souporcell_vs_bff_consensuscall_all_assignment_after_match.csv:md5,0453e72b042219598602022c78f500b0", - "test3_souporcell_vs_bff_consensuscall_correlation_res.csv:md5,7e632ed518a1e6f02ed1ea25a28a4e45", - "test3_souporcell_vs_bff_consensuscall_donor_match.csv:md5,d1feea9baad73b5bc106472a226cbbf2", - "test3_souporcell_vs_bff_consensuscall_intersect_assignment_after_match.csv:md5,2f98b7e021001d4093324dad82177d9d", - "test3_souporcell_vs_bff_raw_all_assignment_after_match.csv:md5,9667ceb1476d7ef9155042a2bfea590a", - "test3_souporcell_vs_bff_raw_correlation_res.csv:md5,7e632ed518a1e6f02ed1ea25a28a4e45", - "test3_souporcell_vs_bff_raw_donor_match.csv:md5,d1feea9baad73b5bc106472a226cbbf2", - "test3_souporcell_vs_bff_raw_intersect_assignment_after_match.csv:md5,245e79ea38948063f671d8553afd2d55", - "test3_souporcell_vs_gmmdemux_all_assignment_after_match.csv:md5,ab32c63ef3a5e5258b399aa2a193ffe4", - "test3_souporcell_vs_gmmdemux_correlation_res.csv:md5,7e632ed518a1e6f02ed1ea25a28a4e45", - "test3_souporcell_vs_gmmdemux_donor_match.csv:md5,d1feea9baad73b5bc106472a226cbbf2", - "test3_souporcell_vs_gmmdemux_intersect_assignment_after_match.csv:md5,e10f4fc3d56187b82fefa95b1a6ddfca", - "test3_souporcell_vs_hasheddrops_all_assignment_after_match.csv:md5,cc8f74509dc0a10c488c2a1e5e064644", - "test3_souporcell_vs_hasheddrops_correlation_res.csv:md5,7e632ed518a1e6f02ed1ea25a28a4e45", - "test3_souporcell_vs_hasheddrops_donor_match.csv:md5,d1feea9baad73b5bc106472a226cbbf2", - "test3_souporcell_vs_hasheddrops_intersect_assignment_after_match.csv:md5,f31968c6aa78e9d8d4c43b99035f2cf3", - "test3_best_all_assignment_after_match.csv:md5,0453e72b042219598602022c78f500b0", - "test3_best_donor_match.csv:md5,d1feea9baad73b5bc106472a226cbbf2", - "test3_best_intersect_assignment_after_match.csv:md5,2f98b7e021001d4093324dad82177d9d", - "test3_score_record.csv:md5,19e6d7aa257ab96338c70bb1e69442b3", "test1_hto-1_informative_variants.csv:md5,7ba6e70ac79adf74d2aa85f723b9f0d6", "test1_hto-1_matched_gt.csv:md5,bf3b7d684f30153fa603dbddde635583", "test1_hto-1_unmatched_gt.csv:md5,964ec72473742d51e7e20788bbaa412c", @@ -363,39 +153,17 @@ "test1_all_representative_variants.csv:md5,93cf3fde5b1236b83abd53369b250f64", "test1_donor_specific_variants.csv:md5,bd137190cb87b1ad68a7283571e9a4b6", "test1_vireo_variants.csv:md5,34bf574b4fe7c16db53a86f5cf6cdbf3", - "test2_hto-1_informative_variants.csv:md5,7ba6e70ac79adf74d2aa85f723b9f0d6", - "test2_hto-1_matched_gt.csv:md5,bf3b7d684f30153fa603dbddde635583", - "test2_hto-1_unmatched_gt.csv:md5,964ec72473742d51e7e20788bbaa412c", - "test2_hto-2_informative_variants.csv:md5,f641c48375833e592aa6f8c102ac1160", - "test2_hto-2_matched_gt.csv:md5,fc8bdfc73587eaf63925f658b6fb1c5c", - "test2_hto-2_unmatched_gt.csv:md5,e53bfe5050c7197a53acec1f30f2daec", - "test2_all_representative_variants.csv:md5,93cf3fde5b1236b83abd53369b250f64", - "test2_donor_specific_variants.csv:md5,bd137190cb87b1ad68a7283571e9a4b6", - "test2_vireo_variants.csv:md5,34bf574b4fe7c16db53a86f5cf6cdbf3", - "test3_hto-1_informative_variants.csv:md5,7ba6e70ac79adf74d2aa85f723b9f0d6", - "test3_hto-1_matched_gt.csv:md5,bf3b7d684f30153fa603dbddde635583", - "test3_hto-1_unmatched_gt.csv:md5,964ec72473742d51e7e20788bbaa412c", - "test3_hto-2_informative_variants.csv:md5,f641c48375833e592aa6f8c102ac1160", - "test3_hto-2_matched_gt.csv:md5,fc8bdfc73587eaf63925f658b6fb1c5c", - "test3_hto-2_unmatched_gt.csv:md5,e53bfe5050c7197a53acec1f30f2daec", - "test3_all_representative_variants.csv:md5,93cf3fde5b1236b83abd53369b250f64", - "test3_donor_specific_variants.csv:md5,bd137190cb87b1ad68a7283571e9a4b6", - "test3_vireo_variants.csv:md5,34bf574b4fe7c16db53a86f5cf6cdbf3", "multiqc_citations.txt:md5,4c806e63a283ec1b7e78cdae3a923d4f" ], [ "test1_donor_specific.vcf.gz:md5,8c51c5ed9e880089a8532e30a659adac", - "test1_vireo.vcf.gz:md5,6e4bbad3892e84cd1dd53c8fc0579977", - "test2_donor_specific.vcf.gz:md5,8c51c5ed9e880089a8532e30a659adac", - "test2_vireo.vcf.gz:md5,6e4bbad3892e84cd1dd53c8fc0579977", - "test3_donor_specific.vcf.gz:md5,8c51c5ed9e880089a8532e30a659adac", - "test3_vireo.vcf.gz:md5,6e4bbad3892e84cd1dd53c8fc0579977" + "test1_vireo.vcf.gz:md5,6e4bbad3892e84cd1dd53c8fc0579977" ] ], - "timestamp": "2026-09-05T18:17:35.976579932", + "timestamp": "2026-09-27T22:30:55.805619165", "meta": { - "nf-test": "0.9.5", - "nextflow": "26.04.6" + "nf-test": "0.9.4", + "nextflow": "25.10.4" } } } \ No newline at end of file diff --git a/tests/test_genetic.nf.test.snap b/tests/test_genetic.nf.test.snap index b0172ccf..e6347de4 100644 --- a/tests/test_genetic.nf.test.snap +++ b/tests/test_genetic.nf.test.snap @@ -173,8 +173,19 @@ "multiqc/multiqc_data/multiqc.parquet", "multiqc/multiqc_data/multiqc_citations.txt", "multiqc/multiqc_data/multiqc_data.json", + "multiqc/multiqc_data/multiqc_hadge_classification_plot.txt", "multiqc/multiqc_data/multiqc_software_versions.txt", "multiqc/multiqc_data/multiqc_sources.txt", + "multiqc/multiqc_plots", + "multiqc/multiqc_plots/pdf", + "multiqc/multiqc_plots/pdf/hadge_classification_plot-cnt.pdf", + "multiqc/multiqc_plots/pdf/hadge_classification_plot-pct.pdf", + "multiqc/multiqc_plots/png", + "multiqc/multiqc_plots/png/hadge_classification_plot-cnt.png", + "multiqc/multiqc_plots/png/hadge_classification_plot-pct.png", + "multiqc/multiqc_plots/svg", + "multiqc/multiqc_plots/svg/hadge_classification_plot-cnt.svg", + "multiqc/multiqc_plots/svg/hadge_classification_plot-pct.svg", "multiqc/multiqc_report.html", "pipeline_info", "pipeline_info/nf_core_hadge_software_mqc_versions.yml", @@ -247,7 +258,8 @@ "test3_prob_doublet.tsv.gz:md5,68b4ec74b77aa58421d73df2dbc92e34", "test3_prob_singlet.tsv.gz:md5,5ea247ca9d0483389a730340f3f34d0d", "test3_summary.tsv:md5,94b0ce40cc86bbe7fbacebf98e685ee7", - "multiqc_citations.txt:md5,4c806e63a283ec1b7e78cdae3a923d4f" + "multiqc_citations.txt:md5,4c806e63a283ec1b7e78cdae3a923d4f", + "multiqc_hadge_classification_plot.txt:md5,6d6978e110e626d2e9253e9aa3cf3b8e" ], [ "test1.clust1.vcf.gz:md5,8de6d22ca83fcc331f3d49d59973e5ac", @@ -264,10 +276,10 @@ "test3_GT_donors.vireo.vcf.gz:md5,921ce18ca982b86589477432fce4e3e6" ] ], - "timestamp": "2026-09-12T16:58:01.78457062", + "timestamp": "2026-09-27T22:34:54.96234196", "meta": { - "nf-test": "0.9.5", - "nextflow": "26.04.6" + "nf-test": "0.9.4", + "nextflow": "25.10.4" } } } \ No newline at end of file diff --git a/tests/test_hashing.nf.test.snap b/tests/test_hashing.nf.test.snap index 97bb5400..b33f2304 100644 --- a/tests/test_hashing.nf.test.snap +++ b/tests/test_hashing.nf.test.snap @@ -592,8 +592,19 @@ "multiqc/multiqc_data/multiqc.parquet", "multiqc/multiqc_data/multiqc_citations.txt", "multiqc/multiqc_data/multiqc_data.json", + "multiqc/multiqc_data/multiqc_hadge_classification_plot.txt", "multiqc/multiqc_data/multiqc_software_versions.txt", "multiqc/multiqc_data/multiqc_sources.txt", + "multiqc/multiqc_plots", + "multiqc/multiqc_plots/pdf", + "multiqc/multiqc_plots/pdf/hadge_classification_plot-cnt.pdf", + "multiqc/multiqc_plots/pdf/hadge_classification_plot-pct.pdf", + "multiqc/multiqc_plots/png", + "multiqc/multiqc_plots/png/hadge_classification_plot-cnt.png", + "multiqc/multiqc_plots/png/hadge_classification_plot-pct.png", + "multiqc/multiqc_plots/svg", + "multiqc/multiqc_plots/svg/hadge_classification_plot-cnt.svg", + "multiqc/multiqc_plots/svg/hadge_classification_plot-pct.svg", "multiqc/multiqc_report.html", "pipeline_info", "pipeline_info/nf_core_hadge_software_mqc_versions.yml", @@ -856,6 +867,7 @@ "test3_hashing_summary_assignment.csv:md5,31a5bd36901a64e5908a38714b19f622", "test3_hashing_summary_classification.csv:md5,c929da1c41e0adc0f3e120f27db00fb6", "multiqc_citations.txt:md5,4c806e63a283ec1b7e78cdae3a923d4f", + "multiqc_hadge_classification_plot.txt:md5,8f3ea4948667ea3263c03114729f507b", "test1_genetic.h5ad:md5,7f6429d2110a385c95ffe03d325ebcfd", "test1_genetic_and_hashing.h5mu:md5,7085e41a05542950f90658a65417f002", "test1_hashing.h5ad:md5,bca9eaccdeb92a5804a72889c67cddb3", @@ -868,10 +880,10 @@ ], "No VCF files" ], - "timestamp": "2026-09-12T16:38:17.799784922", + "timestamp": "2026-09-27T22:40:09.65888401", "meta": { - "nf-test": "0.9.5", - "nextflow": "26.04.6" + "nf-test": "0.9.4", + "nextflow": "25.10.4" } } } \ No newline at end of file