From e4eea9d698490f7a6086b341fbd464a32716ff80 Mon Sep 17 00:00:00 2001 From: Niklas Schandry Date: Thu, 22 May 2025 15:49:10 +0200 Subject: [PATCH 001/162] add prefix to singularity container for report --- modules/local/report/main.nf | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/local/report/main.nf b/modules/local/report/main.nf index 85ba0a14..a519ed29 100644 --- a/modules/local/report/main.nf +++ b/modules/local/report/main.nf @@ -3,7 +3,7 @@ process REPORT { label 'process_low' conda "${moduleDir}/environment.yml" container "${workflow.containerEngine == 'singularity' && !task.ext.singularity_pull_docker_container - ? 'community.wave.seqera.io/library/quarto_r-gt_r-plotly_r-quarto_pruned:6e20dd9b9b77f359' + ? 'https://community.wave.seqera.io/library/quarto_r-gt_r-plotly_r-quarto_pruned:6e20dd9b9b77f359' : 'community.wave.seqera.io/library/quarto_r-gt_r-plotly_r-quarto_pruned:be4a8863b7b76cf7'}" /* wave builds: https://wave.seqera.io/view/builds/bd-6e20dd9b9b77f359_1 singularity From 4e98303d4a8adc037fa469e27a5c4ce95a340f9a Mon Sep 17 00:00:00 2001 From: Niklas Schandry Date: Fri, 11 Jul 2025 11:48:46 +0200 Subject: [PATCH 002/162] maxime comments --- CHANGELOG.md | 2 +- conf/old.modules.config | 597 ---------------------------------------- conf/test.config | 8 +- nextflow.config | 3 +- 4 files changed, 3 insertions(+), 607 deletions(-) delete mode 100644 conf/old.modules.config diff --git a/CHANGELOG.md b/CHANGELOG.md index 02c6831d..b7af38b8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,7 +3,7 @@ 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.1.0 'Brass Pigeon' - [2025-05-08] +## v1.1.0 'Brass Pigeon' - [2025-07-11] ### `Added` diff --git a/conf/old.modules.config b/conf/old.modules.config deleted file mode 100644 index 6c0c27b2..00000000 --- a/conf/old.modules.config +++ /dev/null @@ -1,597 +0,0 @@ -/* -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - Config file for defining DSL2 per module options and publishing paths -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - Available keys to override module options: - ext.args = Additional arguments appended to command in module. - ext.args2 = Second set of arguments appended to command in module (multi-tool modules). - ext.args3 = Third set of arguments appended to command in module (multi-tool modules). - ext.prefix = File name prefix for output files. ----------------------------------------------------------------------------------------- -*/ - -process { - // General catch-all - publishDir = [ - path: { "${params.outdir}/${meta.id}/${task.process.tokenize(':')[-1].tokenize('_')[0].toLowerCase()}" }, - mode: params.publish_dir_mode, - saveAs: { filename -> filename.equals('versions.yml') ? null : filename } - ] - /* - ---------- - Reads in - ONT - ---------- - */ - // nanoq: local module; QC - withName: NANOQ { - publishDir = [ - path: { "${params.outdir}/${meta.id}/QC/nanoq" }, - mode: params.publish_dir_mode, - saveAs: { filename -> filename.equals('versions.yml') ? null : filename } - ] - } - // only local module - withName: COLLECT { - publishDir = [ - path: { "${params.outdir}/${meta.id}/reads/collect" }, - mode: params.publish_dir_mode, - saveAs: { filename -> filename.equals('versions.yml') ? null : filename } - ] - } - // porechop: nf-core module - withName: PORECHOP { - publishDir = [ - path: { "${params.outdir}/${meta.id}/reads/porechop" }, - mode: params.publish_dir_mode, - saveAs: { filename -> filename.equals('versions.yml') ? null : filename } - ] - } - // Genomescope / jellyfish: local modules - withName: COUNT { - publishDir = [ - path: { "${params.outdir}/${meta.id}/reads/genomescope/jellyfish/count/" }, - mode: params.publish_dir_mode, - saveAs: { filename -> filename.equals('versions.yml') ? null : filename } - ] - } - withName: DUMP { - publishDir = [ - path: { "${params.outdir}/${meta.id}/reads/genomescope/jellyfish/dump/" }, - mode: params.publish_dir_mode, - saveAs: { filename -> filename.equals('versions.yml') ? null : filename } - ] - } - withName: STATS { - publishDir = [ - path: { "${params.outdir}/${meta.id}/reads/genomescope/jellyfish/stats/" }, - mode: params.publish_dir_mode, - saveAs: { filename -> filename.equals('versions.yml') ? null : filename } - ] - } - withName: HISTO { - publishDir = [ - path: { "${params.outdir}/${meta.id}/reads/genomescope/jellyfish/histo/" }, - mode: params.publish_dir_mode, - saveAs: { filename -> filename.equals('versions.yml') ? null : filename } - ] - } - withName: GENOMESCOPE { - publishDir = [ - path: { "${params.outdir}/${meta.id}/reads/genomescope/genomescope/" }, - mode: params.publish_dir_mode, - saveAs: { filename -> filename.equals('versions.yml') ? null : filename } - ] - } - /* - ---------- - Reads in - HiFi - all nf-core - ---------- - */ - withName: LIMA { - publishDir = [ - path: { "${params.outdir}/${meta.id}/reads/lima/" }, - mode: params.publish_dir_mode, - saveAs: { filename -> filename.equals('versions.yml') ? null : filename } - ] - } - withName: TO_FASTQ { - publishDir = [ - path: { "${params.outdir}/${meta.id}/reads/lima/fastq/" }, - mode: params.publish_dir_mode, - saveAs: { filename -> filename.equals('versions.yml') ? null : filename } - ] - } - /* - ---------- - Reads in - Short reads - all nf-core - ---------- - */ - withName: TRIMGALORE { - publishDir = [ - path: { "${params.outdir}/${meta.id}/reads/trimgalore" }, - mode: params.publish_dir_mode, - saveAs: { filename -> filename.equals('versions.yml') ? null : filename } - ] - } - withName: MERYL_COUNT { - publishDir = [ - path: { "${params.outdir}/${meta.id}/reads/meryl/count/" }, - mode: params.publish_dir_mode, - saveAs: { filename -> filename.equals('versions.yml') ? null : filename } - ] - } - withName: MERYL_UNIONSUM { - publishDir = [ - path: { "${params.outdir}/${meta.id}/reads/meryl/unionsum/" }, - mode: params.publish_dir_mode, - saveAs: { filename -> filename.equals('versions.yml') ? null : filename } - ] - } - /* - ---------- - ASSEMBLY - ---------- - */ - // FLYE: nf-core - withName: FLYE { - ext.args = { - [ - meta.genome_size ? "--genome-size ${meta.genome_size}" : '', - params.flye_args - ].join(" ").trim() - } - publishDir = [ - path: { "${params.outdir}/${meta.id}/assembly/flye/" }, - mode: params.publish_dir_mode, - saveAs: { filename -> filename.equals('versions.yml') ? null : filename } - ] - } - // HIFIASM: - /* updated nf-core module */ - - withName: HIFIASM { - ext.args = { [ params.hifiasm_args ].join(" ").trim() } - publishDir = [ - path: { "${params.outdir}/${meta.id}/assembly/hifiasm/" }, - mode: params.publish_dir_mode, - saveAs: { filename -> filename.equals('versions.yml') ? null : filename } - ] - } - - withName: HIFIASM_ONT { - ext.args = { [ params.hifiasm_args, "--ont" ].join(" ").trim() } - publishDir = [ - path: { "${params.outdir}/${meta.id}/assembly/hifiasm_ont/" }, - mode: params.publish_dir_mode, - saveAs: { filename -> filename.equals('versions.yml') ? null : filename } - ] - } - withName: GFA_2_FA { - publishDir = [ - path: { "${params.outdir}/${meta.id}/assembly/hifiasm/fasta" }, - mode: params.publish_dir_mode, - saveAs: { filename -> filename.equals('versions.yml') ? null : filename } - ] - } - withName: GFA_2_FA_HIFI { - publishDir = [ - path: { "${params.outdir}/${meta.id}/assembly/hifiasm/fasta" }, - mode: params.publish_dir_mode, - saveAs: { filename -> filename.equals('versions.yml') ? null : filename } - ] - } - withName: GFA_2_FA_ONT { - publishDir = [ - path: { "${params.outdir}/${meta.id}/assembly/hifiasm_ont/fasta" }, - mode: params.publish_dir_mode, - saveAs: { filename -> filename.equals('versions.yml') ? null : filename } - ] - } - withName: '.*ASSEMBLE:.*RAGTAG_PATCH' { - publishDir = [ - path: { "${params.outdir}/${meta.id}/assembly/ragtag/" }, - mode: params.publish_dir_mode, - saveAs: { filename -> filename.equals('versions.yml') ? null : filename } - ] - ext.prefix = { "${meta.id}_assembly_patch" } - } - - /* - ---------- - Polishing - ---------- - */ - // Medaka: local; nf-core module cant deal with gzipped input - withName: MEDAKA { - ext.args1 = { } // args mini_align - ext.args2 = { [params.medaka_model ? "--model ${params.medaka_model}" : ''].join(" ").trim() } // args medaka_inference - ext.args3 = { } // args medaka sequence - ext.prefix = { "${meta.id}_medaka" } - publishDir = [ - path: { "${params.outdir}/${meta.id}/polish/medaka" }, - mode: params.publish_dir_mode, - saveAs: { filename -> filename.equals('versions.yml') ? null : filename } - ] - } - // Pilon: nf-core - withName: PILON { - ext.prefix = { "${meta.id}_pilon" } - publishDir = [ - path: { "${params.outdir}/${meta.id}/polish/pilon" }, - mode: params.publish_dir_mode, - saveAs: { filename -> filename.equals('versions.yml') ? null : filename } - ] - } - - /* - ---------- - Scaffolding - ---------- - */ - // RagTag - withName: '.*SCAFFOLD:.*RAGTAG_SCAFFOLD' { - publishDir = [ - path: { "${params.outdir}/${meta.id}/scaffold/ragtag/" }, - mode: params.publish_dir_mode, - saveAs: { filename -> filename.equals('versions.yml') ? null : filename } - ] - ext.prefix = { "${meta.id}_ragtag" } - ext.args = [ - "-C", - "-u", - "-r", - "-w" - ].join(" ").trim() - } - - withName: LINKS { - publishDir = [ - path: { "${params.outdir}/${meta.id}/scaffold/links/" }, - mode: params.publish_dir_mode, - saveAs: { filename -> filename.equals('versions.yml') ? null : filename } - ] - ext.prefix = { "${meta.id}_links" } - ext.args = ["-t 40,200", "-d 500,2000,5000"].join(" ").trim() - } - - // No nf-core module yet. - withName: LONGSTITCH { - publishDir = [ - path: { "${params.outdir}/${meta.id}/scaffold/longstitch/" }, - mode: params.publish_dir_mode, - saveAs: { filename -> filename.equals('versions.yml') ? null : filename } - ] - ext.prefix = { "${meta.id}_longstitch" } - } - /* - -------- - Annotations - liftoff nf-core module - -------- - gff file goes with fasta file - */ - - withName: '.*ASSEMBLE:.*LIFTOFF' { - publishDir = [ - path: { "${params.outdir}/${meta.id}/assembly/" }, - mode: params.publish_dir_mode, - saveAs: { filename -> filename.equals('versions.yml') ? null : filename } - ] - ext.prefix = { "${meta.id}_assembly" } - } - - withName: '.*PILON:.*LIFTOFF' { - publishDir = [ - path: { "${params.outdir}/${meta.id}/polish/pilon/" }, - mode: params.publish_dir_mode, - saveAs: { filename -> filename.equals('versions.yml') ? null : filename } - ] - ext.prefix = { "${meta.id}_pilon" } - } - - withName: '.*MEDAKA:.*LIFTOFF' { - publishDir = [ - path: { "${params.outdir}/${meta.id}/polish/medaka" }, - mode: params.publish_dir_mode, - saveAs: { filename -> filename.equals('versions.yml') ? null : filename } - ] - ext.prefix = { "${meta.id}_medaka" } - } - - withName: '.*RAGTAG:.*LIFTOFF' { - publishDir = [ - path: { "${params.outdir}/${meta.id}/scaffold/ragtag/" }, - mode: params.publish_dir_mode, - saveAs: { filename -> filename.equals('versions.yml') ? null : filename } - ] - ext.prefix = { "${meta.id}_ragtag" } - } - - withName: '.*LONGSTITCH:.*LIFTOFF' { - publishDir = [ - path: { "${params.outdir}/${meta.id}/scaffold/longstitch" }, - mode: params.publish_dir_mode, - saveAs: { filename -> filename.equals('versions.yml') ? null : filename } - ] - ext.prefix = { "${meta.id}_longstitch" } - } - - withName: '.*LINKS:.*LIFTOFF' { - publishDir = [ - path: { "${params.outdir}/${meta.id}/scaffold/links" }, - mode: params.publish_dir_mode, - saveAs: { filename -> filename.equals('versions.yml') ? null : filename } - ] - ext.prefix = { "${meta.id}_links" } - } - - /* - ---------- - QC - ---------- - */ - - // BUSCO: nf-core - withName: '.*:ASSEMBLE:.*:BUSCO' { - ext.prefix = { "${meta.id}_assembly-${lineage}" } - publishDir = [ - path: { "${params.outdir}/${meta.id}/QC/BUSCO/" }, - mode: params.publish_dir_mode, - pattern: "*{-busco,_summary}*", - saveAs: { filename -> filename.equals('versions.yml') ? null : filename } - ] - } - withName: '.*PILON:.*:BUSCO' { - ext.prefix = { "${meta.id}_pilon-${lineage}" } - publishDir = [ - path: { "${params.outdir}/${meta.id}/QC/BUSCO/" }, - mode: params.publish_dir_mode, - pattern: "*{-busco,_summary}*", - saveAs: { filename -> filename.equals('versions.yml') ? null : filename } - ] - } - withName: '.*MEDAKA:.*:BUSCO' { - ext.prefix = { "${meta.id}_medaka-${lineage}" } - publishDir = [ - path: { "${params.outdir}/${meta.id}/QC/BUSCO/" }, - mode: params.publish_dir_mode, - pattern: "*{-busco,_summary}*", - saveAs: { filename -> filename.equals('versions.yml') ? null : filename } - ] - } - withName: '.*LINKS:.*:BUSCO' { - ext.prefix = { "${meta.id}_links-${lineage}" } - publishDir = [ - path: { "${params.outdir}/${meta.id}/QC/BUSCO/" }, - mode: params.publish_dir_mode, - pattern: "*{-busco,_summary}*", - saveAs: { filename -> filename.equals('versions.yml') ? null : filename } - ] - } - withName: '.*LONGSTITCH:.*:BUSCO' { - ext.prefix = { "${meta.id}_longstitch-${lineage}" } - publishDir = [ - path: { "${params.outdir}/${meta.id}/QC/BUSCO/" }, - mode: params.publish_dir_mode, - pattern: "*{-busco,_summary}*", - saveAs: { filename -> filename.equals('versions.yml') ? null : filename } - ] - } - // avoid catching ragtag from ont_on_hifi assembly - withName: '.*:SCAFFOLD:.*RAGTAG:.*:BUSCO' { - ext.prefix = { "${meta.id}_ragtag-${lineage}" } - publishDir = [ - path: { "${params.outdir}/${meta.id}/QC/BUSCO/" }, - mode: params.publish_dir_mode, - pattern: "*{-busco,_summary}*", - saveAs: { filename -> filename.equals('versions.yml') ? null : filename } - ] - } - - // QUAST: Prefer to keep the local module since it can deal with the inputs I have - withName: '.*:ASSEMBLE:.*:QUAST' { - ext.prefix = { "${meta.id}_assembly" } - publishDir = [ - path: { "${params.outdir}/${meta.id}/QC/QUAST" }, - mode: params.publish_dir_mode, - saveAs: { filename -> filename.equals('versions.yml') ? null : filename } - ] - } - withName: '.*PILON:.*:QUAST' { - ext.prefix = { "${meta.id}_pilon" } - publishDir = [ - path: { "${params.outdir}/${meta.id}/QC/QUAST" }, - mode: params.publish_dir_mode, - saveAs: { filename -> filename.equals('versions.yml') ? null : filename } - ] - } - withName: '.*MEDAKA:.*:QUAST' { - ext.prefix = { "${meta.id}_medaka" } - publishDir = [ - path: { "${params.outdir}/${meta.id}/QC/QUAST" }, - mode: params.publish_dir_mode, - saveAs: { filename -> filename.equals('versions.yml') ? null : filename } - ] - } - withName: '.*LINKS:.*:QUAST' { - ext.prefix = { "${meta.id}_links" } - publishDir = [ - path: { "${params.outdir}/${meta.id}/QC/QUAST/" }, - mode: params.publish_dir_mode, - saveAs: { filename -> filename.equals('versions.yml') ? null : filename } - ] - } - withName: '.*LONGSTITCH:.*:QUAST' { - ext.prefix = { "${meta.id}_longstitch" } - publishDir = [ - path: { "${params.outdir}/${meta.id}/QC/QUAST/" }, - mode: params.publish_dir_mode, - saveAs: { filename -> filename.equals('versions.yml') ? null : filename } - ] - } - // avoid catching ragtag from ont_on_hifi assembly - withName: '.*:SCAFFOLD:.*RAGTAG:.*:QUAST' { - ext.prefix = { "${meta.id}_ragtag" } - publishDir = [ - path: { "${params.outdir}/${meta.id}/QC/QUAST/" }, - mode: params.publish_dir_mode, - saveAs: { filename -> filename.equals('versions.yml') ? null : filename } - ] - } - - // MERQURY: nf-core - withName: '.*:ASSEMBLE:.*:MERQURY' { - ext.prefix = { "${meta.id}_assembly" } - publishDir = [ - path: { "${params.outdir}/${meta.id}/QC/merqury/" }, - mode: params.publish_dir_mode, - saveAs: { filename -> filename.equals('versions.yml') ? null : filename } - ] - } - withName: '.*PILON:.*:MERQURY' { - ext.prefix = { "${meta.id}_pilon" } - publishDir = [ - path: { "${params.outdir}/${meta.id}/QC/merqury/" }, - mode: params.publish_dir_mode, - saveAs: { filename -> filename.equals('versions.yml') ? null : filename } - ] - } - withName: '.*MEDAKA:.*:MERQURY' { - ext.prefix = { "${meta.id}_medaka" } - publishDir = [ - path: { "${params.outdir}/${meta.id}/QC/merqury/" }, - mode: params.publish_dir_mode, - saveAs: { filename -> filename.equals('versions.yml') ? null : filename } - ] - } - withName: '.*LINKS:.*:MERQURY' { - ext.prefix = { "${meta.id}_links" } - publishDir = [ - path: { "${params.outdir}/${meta.id}/QC/merqury/" }, - mode: params.publish_dir_mode, - saveAs: { filename -> filename.equals('versions.yml') ? null : filename } - ] - } - withName: '.*LONGSTITCH:.*:MERQURY' { - ext.prefix = { "${meta.id}_longstitch" } - publishDir = [ - path: { "${params.outdir}/${meta.id}/QC/merqury/" }, - mode: params.publish_dir_mode, - saveAs: { filename -> filename.equals('versions.yml') ? null : filename } - ] - } - // avoid catching ragtag from ont_on_hifi assembly - withName: '.*:SCAFFOLD:.*RAGTAG:.*:MERQURY' { - ext.prefix = { "${meta.id}_ragtag" } - publishDir = [ - path: { "${params.outdir}/${meta.id}/QC/merqury/" }, - mode: params.publish_dir_mode, - saveAs: { filename -> filename.equals('versions.yml') ? null : filename } - ] - } - // Refence - withName: '.*MAP_TO_REF.*' { - ext.prefix = { "${meta.id}_to_reference" } - publishDir = [ - path: { "${params.outdir}/${meta.id}/QC/alignments/reference/" }, - mode: params.publish_dir_mode, - saveAs: { filename -> filename.equals('versions.yml') ? null : filename } - ] - ext.args = { - (params.hifi && params.ont) ? (params.qc_reads == 'ONT' ? "-ax map-ont" : "-ax map-hifi") : (params.ont) ? "-ax map-ont" : "-ax map-hifi" - } - } - // Assembly mappings - withName: '.*ASSEMBLE:.*MAP_TO_ASSEMBLY.*' { - ext.prefix = { "${meta.id}_assembly" } - publishDir = [ - path: { "${params.outdir}/${meta.id}/QC/alignments/" }, - mode: params.publish_dir_mode, - saveAs: { filename -> filename.equals('versions.yml') ? null : filename } - ] - ext.args = { - (params.hifi && params.ont) ? (params.qc_reads == 'ONT' ? "-ax map-ont" : "-ax map-hifi") : (params.ont) ? "-ax map-ont" : "-ax map-hifi" - } - } - withName: '.*MEDAKA:.*MAP_TO_ASSEMBLY.*' { - ext.prefix = { "${meta.id}_medaka" } - publishDir = [ - path: { "${params.outdir}/${meta.id}/QC/alignments/" }, - mode: params.publish_dir_mode, - saveAs: { filename -> filename.equals('versions.yml') ? null : filename } - ] - ext.args = { - (params.hifi && params.ont) ? (params.qc_reads == 'ONT' ? "-ax map-ont" : "-ax map-hifi") : (params.ont) ? "-ax map-ont" : "-ax map-hifi" - } - } - withName: '.*PILON:.*MAP_TO_ASSEMBLY.*' { - ext.prefix = { "${meta.id}_pilon" } - publishDir = [ - path: { "${params.outdir}/${meta.id}/QC/alignments/" }, - mode: params.publish_dir_mode, - saveAs: { filename -> filename.equals('versions.yml') ? null : filename } - ] - ext.args = { - (params.hifi && params.ont) ? (params.qc_reads == 'ONT' ? "-ax map-ont" : "-ax map-hifi") : (params.ont) ? "-ax map-ont" : "-ax map-hifi" - } - } - withName: '.*LONGSTITCH:.*MAP_TO_ASSEMBLY.*' { - ext.prefix = { "${meta.id}_longstitch" } - publishDir = [ - path: { "${params.outdir}/${meta.id}/QC/alignments/" }, - mode: params.publish_dir_mode, - saveAs: { filename -> filename.equals('versions.yml') ? null : filename } - ] - ext.args = { - (params.hifi && params.ont) ? (params.qc_reads == 'ONT' ? "-ax map-ont" : "-ax map-hifi") : (params.ont) ? "-ax map-ont" : "-ax map-hifi" - } - } - withName: '.*LINKS:.*MAP_TO_ASSEMBLY.*' { - ext.prefix = { "${meta.id}_links" } - publishDir = [ - path: { "${params.outdir}/${meta.id}/QC/alignments/" }, - mode: params.publish_dir_mode, - saveAs: { filename -> filename.equals('versions.yml') ? null : filename } - ] - ext.args = { - (params.hifi && params.ont) ? (params.qc_reads == 'ONT' ? "-ax map-ont" : "-ax map-hifi") : (params.ont) ? "-ax map-ont" : "-ax map-hifi" - } - } - withName: '.*RAGTAG:.*MAP_TO_ASSEMBLY.*' { - ext.prefix = { "${meta.id}_ragtag" } - publishDir = [ - path: { "${params.outdir}/${meta.id}/QC/alignments/" }, - mode: params.publish_dir_mode, - saveAs: { filename -> filename.equals('versions.yml') ? null : filename } - ] - ext.args = { - (params.hifi && params.ont) ? (params.qc_reads == 'ONT' ? "-ax map-ont" : "-ax map-hifi") : (params.ont) ? "-ax map-ont" : "-ax map-hifi" - } - } - // Pilon mapping - withName: '.*PILON:MAP_SR.*' { - publishDir = [ - path: { "${params.outdir}/${meta.id}/QC/alignments/shortreads/" }, - mode: params.publish_dir_mode, - saveAs: { filename -> filename.equals('versions.yml') ? null : filename } - ] - ext.prefix = { "${meta.id}_shortreads" } - ext.args = { "-ax sr " } - } - /* - -------- - Report - */ - withName: REPORT { - publishDir = [ - path: { "${params.outdir}/report/" }, - mode: params.publish_dir_mode, - saveAs: { filename -> filename.equals('versions.yml') ? null : filename } - ] - } -} diff --git a/conf/test.config b/conf/test.config index b8875c0f..be3dc0e5 100644 --- a/conf/test.config +++ b/conf/test.config @@ -17,13 +17,7 @@ process { time: '1.h' ] } -/* -process { - withName: 'HIFIASM.*' { - memory = '15.GB' - } -} -*/ + params { config_profile_name = 'Test profile' config_profile_description = 'Minimal test dataset to check pipeline function' diff --git a/nextflow.config b/nextflow.config index 17bb4d91..45e4f254 100644 --- a/nextflow.config +++ b/nextflow.config @@ -239,8 +239,7 @@ includeConfig params.custom_config_base && (!System.getenv('NXF_OFFLINE') || !pa // Load nf-core/genomeassembler custom profiles from different institutions. -includeConfig !System.getenv('NXF_OFFLINE') && params.custom_config_base ? "${params.custom_config_base}/pipeline/genomeassembler.config" : "/dev/null" - +includeConfig params.custom_config_base && (!System.getenv('NXF_OFFLINE') || !params.custom_config_base.startsWith('http')) ? "${params.custom_config_base}/pipeline/genomeassembler.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 // Set to your registry if you have a mirror of containers From 376973f040ef2836a17796ae2d62e19bd74cb76e Mon Sep 17 00:00:00 2001 From: Niklas Schandry Date: Mon, 21 Jul 2025 09:56:45 +0200 Subject: [PATCH 003/162] comments Daniel Lundin --- modules/local/collect_reads/main.nf | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/local/collect_reads/main.nf b/modules/local/collect_reads/main.nf index ee38cb3b..4dbdc87a 100644 --- a/modules/local/collect_reads/main.nf +++ b/modules/local/collect_reads/main.nf @@ -18,7 +18,7 @@ process COLLECT_READS { def prefix = task.ext.prefix ?: "${meta.id}" """ - zcat ${reads} | gzip > ${prefix}_all_reads.fq.gz + cat ${reads} > ${prefix}_all_reads.fq.gz cat <<-END_VERSIONS > versions.yml "${task.process}": gzip: \$(echo \$(gzip --version | head -n1 | sed 's/gzip //')) @@ -28,7 +28,7 @@ process COLLECT_READS { stub: def prefix = task.ext.prefix ?: "${meta.id}" """ - touch ${prefix}_all_reads.fq.gz + touch ${prefix}_all_reads.fq; gzip ${prefix}_all_reads.fq cat <<-END_VERSIONS > versions.yml "${task.process}": gzip: \$(echo \$(gzip --version | head -n1 | sed 's/gzip //')) From a8e9afcf9da9839a213c2babf4687527bddebb3e Mon Sep 17 00:00:00 2001 From: Niklas Schandry Date: Mon, 21 Jul 2025 09:57:17 +0200 Subject: [PATCH 004/162] update release date --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b7af38b8..5de49257 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,7 +3,7 @@ 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.1.0 'Brass Pigeon' - [2025-07-11] +## v1.1.0 'Brass Pigeon' - [2025-07-21] ### `Added` From 5c5b2d2e99373a617f34a31799a569b6052aa902 Mon Sep 17 00:00:00 2001 From: Niklas Schandry Date: Mon, 21 Jul 2025 10:18:12 +0200 Subject: [PATCH 005/162] Revert "comments Daniel Lundin" This reverts commit 376973f040ef2836a17796ae2d62e19bd74cb76e. --- modules/local/collect_reads/main.nf | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/local/collect_reads/main.nf b/modules/local/collect_reads/main.nf index 4dbdc87a..ee38cb3b 100644 --- a/modules/local/collect_reads/main.nf +++ b/modules/local/collect_reads/main.nf @@ -18,7 +18,7 @@ process COLLECT_READS { def prefix = task.ext.prefix ?: "${meta.id}" """ - cat ${reads} > ${prefix}_all_reads.fq.gz + zcat ${reads} | gzip > ${prefix}_all_reads.fq.gz cat <<-END_VERSIONS > versions.yml "${task.process}": gzip: \$(echo \$(gzip --version | head -n1 | sed 's/gzip //')) @@ -28,7 +28,7 @@ process COLLECT_READS { stub: def prefix = task.ext.prefix ?: "${meta.id}" """ - touch ${prefix}_all_reads.fq; gzip ${prefix}_all_reads.fq + touch ${prefix}_all_reads.fq.gz cat <<-END_VERSIONS > versions.yml "${task.process}": gzip: \$(echo \$(gzip --version | head -n1 | sed 's/gzip //')) From 484c1d2938ef39a59083fc79f8b8ec09d3b0f98f Mon Sep 17 00:00:00 2001 From: Niklas Schandry Date: Fri, 27 Jun 2025 13:43:03 +0200 Subject: [PATCH 006/162] prepare pipeline initialization for sample-wise parameterization --- .../main.nf | 75 ++++++++++++------- 1 file changed, 46 insertions(+), 29 deletions(-) diff --git a/subworkflows/local/utils_nfcore_genomeassembler_pipeline/main.nf b/subworkflows/local/utils_nfcore_genomeassembler_pipeline/main.nf index 29aee94e..0770101a 100644 --- a/subworkflows/local/utils_nfcore_genomeassembler_pipeline/main.nf +++ b/subworkflows/local/utils_nfcore_genomeassembler_pipeline/main.nf @@ -98,43 +98,60 @@ workflow PIPELINE_INITIALISATION { Channel.empty().set { ch_refs } Channel.fromPath(params.input) .splitCsv(header: true) - .map { it -> [meta: [id: it.sample], ontreads: it.ontreads, hifireads: it.hifireads, ref_fasta: it.ref_fasta, ref_gff: it.ref_gff, shortread_F: it.shortread_F, shortread_R: it.shortread_R, paired: it.paired] } + .map { it -> + [ + meta: [id: it.sample], + ontreads: it.ontreads, + hifireads: it.hifireads, + // new in refactor-assemblers + strategy: it.strategy, + assembler1: it.assembler1, + assembler2: it.assembler2, + scaffolding: it.scaffolding_order, + genome_size: it.genome_size, + // old + ref_fasta: it.ref_fasta, + ref_gff: it.ref_gff, + shortread_F: it.shortread_F, + shortread_R: it.shortread_R, + paired: it.paired + ] } .set { ch_samplesheet } if (params.use_ref) { ch_samplesheet .map { it -> [it.meta, file(it.ref_fasta, checkIfExists: true)] } .set { ch_refs } } - if (params.lift_annotations) { - ch_samplesheet - .map { it -> [it.meta, file(it.ref_gff, checkIfExists: true)] } - } - // check for assembler / read combination - def hifi_only = params.hifi && !params.ont ? true : false - if (!params.skip_assembly) { - if (params.assembler == "flye") { - if (params.hifi) { - if (!hifi_only) { - error('Cannot combine hifi and ont reads with flye') - } - } - } - } - // check for QC reads - if (params.hifi && params.ont) { - if (!params.qc_reads) { - error("Please specify which reads should be used for qc: 'ONT' or 'HIFI'") + + // Define valid hybrid assemblers + + def hybrid_assemblers = ["hifiasm"] + + // sample-level checks + + ch_samplesheet + .map { + it -> + // Check if assembler can do hybrid + (it.strategy == "hybrid" && !hybrid_assemblers.contains(it.assembler1)) + ? [ println("Please confirm samplesheet: [sample: $it.meta.id]: Hybrid assembly can only be performed with $hybrid_assemblers"), "invalid" ] + : null + // Check if qc reads are specified for hybrid assemblies + (it.strategy == "hybrid" && !params.qc_reads) + ? [ println("Please confirm samplesheet: [sample: $it.meta.id]: Please specify which reads should be used for qc: '--qc_reads': 'ONT' or 'HIFI'") , "invalid" ] + : null + // Check if genome_size is given with --scaffold_longstitch + (params.scaffold_longstitch && !it.genome_size && !(it.ont_reads && params.jellyfish)) + ? [ println("Please confirm samplesheet: [sample: $it.meta.id]: --scaffold_longstitch requires genome-size. Either provide genome-size estimate, or estimate from ONT reads with --jellyfish"), "invalid" ] + : null } - } - // Make sure that genome_size is provided or estimated when using scaffold_longstitch - if (params.scaffold_longstitch) { - // If genomesize is not provided, and if ONT is not used in combination with jellyfish - // Throw an error - if (!params.genome_size && (!params.ont && !params.jellyfish)) { - error("Scaffolding with longstitch requires genome size.\n Either provide a genome size with --genome_size or estimate from ONT reads using jellyfish and genomescope") + .collect() + // error if >0 samples failed a check above + .subscribe { + it -> it.contains("invalid") + ? error("Invalid combination in samplesheet") + : null } - } - emit: samplesheet = ch_samplesheet From ae122c62642c169134e861c09bc75627b2a290c3 Mon Sep 17 00:00:00 2001 From: Niklas Schandry Date: Fri, 27 Jun 2025 16:54:21 +0200 Subject: [PATCH 007/162] refactor assemble and assemble subworkflows for sample-wise parameterization --- .gitignore | 1 + subworkflows/local/assemble/main.nf | 274 ++++++++++-------- subworkflows/local/hifi/main.nf | 8 +- subworkflows/local/jellyfish/main.nf | 52 +++- subworkflows/local/liftoff/main.nf | 16 +- subworkflows/local/ont/main.nf | 14 +- subworkflows/local/prepare_hifi/main.nf | 20 +- subworkflows/local/prepare_ont/chop/main.nf | 28 +- .../local/prepare_ont/collect/main.nf | 5 + .../local/prepare_ont/run_nanoq/main.nf | 11 +- subworkflows/local/prepare_shortreads/main.nf | 16 +- subworkflows/local/qc/main.nf | 31 +- subworkflows/local/qc/quast/main.nf | 23 +- .../main.nf | 41 ++- workflows/genomeassembler.nf | 56 ++-- 15 files changed, 385 insertions(+), 211 deletions(-) diff --git a/.gitignore b/.gitignore index f232546a..11ebb3ef 100644 --- a/.gitignore +++ b/.gitignore @@ -9,3 +9,4 @@ testing* null/ .nf-test/ .nf-test.log +NOTE.md diff --git a/subworkflows/local/assemble/main.nf b/subworkflows/local/assemble/main.nf index 1030beb9..e4082535 100644 --- a/subworkflows/local/assemble/main.nf +++ b/subworkflows/local/assemble/main.nf @@ -11,10 +11,7 @@ include { QC } from '../qc/main' workflow ASSEMBLE { take: - ont_reads // meta, reads - hifi_reads // meta, reads - ch_input - genome_size + ch_main meryl_kmers main: @@ -29,7 +26,7 @@ workflow ASSEMBLE { Channel.empty().set { ch_versions } if (params.use_ref) { - ch_input + ch_main .map { row -> [row.meta, row.ref_fasta] } .set { ch_refs } } @@ -37,121 +34,144 @@ workflow ASSEMBLE { if (params.skip_assembly) { // Sample sheet layout when skipping assembly // sample,ontreads,assembly,ref_fasta,ref_gff - ch_input + ch_main .map { row -> [row.meta, row.assembly] } .set { ch_assembly } } if (!params.skip_assembly) { - def hifi_only = params.hifi && !params.ont ? true : false - // Define inputs for flye - if (params.assembler == "flye") { - if (params.hifi) { - hifi_reads - .map { it -> [it[0], it[1]] } - .set { flye_inputs } + + ch_main + .branch { it -> + hifiasm: (it.strategy == "single" && it.assembler1 == "hifiasm") + || (it.strategy == "scaffold" && (it.assembler1 == "hifiasm" || it.assembler2 == "hifiasm")) + || (it.strategy == "hybrid" && it.assembler1 == "hifiasm") + hifiasm_ont: (it.strategy == "single" && it.assembler1 == "hifiasm" && it.ontreads) + flye: (it.strategy == "single" && it.assembler1 == "flye") || (it.strategy == "scaffold" && (it.assembler1 == "flye")) } - if (params.ont) { - ont_reads.set { flye_inputs } + .set { ch_main_branched } + + // Assembly flye branch + + ch_main_branched.flye + .multiMap { + it -> + reads: [ + [ + meta: it.meta, + genome_size: it.genome_size + ], + it.ontreads ?: it.hifireads, + ] + mode: it.ontreads ? "nano-hq" : "--pacbio-hifi" } - // Run flye - flye_inputs - .join(genome_size) - .map { meta, reads, genomesize -> [meta +[ genome_size: genomesize ], reads] } - .set { flye_inputs } - FLYE(flye_inputs, params.flye_mode) - FLYE.out.fasta.map { meta, assembly -> [meta - meta.subMap('genome_size'), assembly] }.set { ch_assembly } + .set { flye_inputs } + + FLYE(flye_inputs.reads, flye_inputs.mode) + ch_versions = ch_versions.mix(FLYE.out.versions) - } - if (params.assembler == "hifiasm") { - // HiFi and ONT reads in ultralong mode - if (params.hifi && params.ont) { - hifi_reads - .join(ont_reads) - .set { hifiasm_inputs } - HIFIASM(hifiasm_inputs, [[], [], []], [[], [], []], [[], []]) - GFA_2_FA_HIFI(HIFIASM.out.processed_unitigs) - GFA_2_FA_HIFI.out.contigs_fasta.set { ch_assembly } - - ch_versions = ch_versions.mix(HIFIASM.out.versions).mix(GFA_2_FA_HIFI.out.versions) - } - // ONT reads only - if (!params.hifi && params.ont) { - ont_reads - .map { meta, ontreads -> [meta, ontreads, []] } - .set { hifiasm_inputs } - HIFIASM_ONT(hifiasm_inputs, [[], [], []], [[], [], []], [[], []]) - GFA_2_FA_ONT(HIFIASM_ONT.out.processed_unitigs) - GFA_2_FA_ONT.out.contigs_fasta.set { ch_assembly } - ch_versions = ch_versions.mix(HIFIASM_ONT.out.versions).mix(GFA_2_FA_ONT.out.versions) - } - // HiFI reads only - if (params.hifi && !params.ont) { - hifi_reads - .map { meta, ontreads -> [meta, ontreads, []] } - .set { hifiasm_inputs } - HIFIASM(hifiasm_inputs, [[], [], []], [[], [], []], [[], []]) - GFA_2_FA_HIFI(HIFIASM.out.processed_unitigs) - GFA_2_FA_HIFI.out.contigs_fasta.set { ch_assembly } + // Assembly hifiasm branch - ch_versions = ch_versions.mix(HIFIASM.out.versions).mix(GFA_2_FA_HIFI.out.versions) - } - } - if (params.assembler == "flye_on_hifiasm" | params.assembler == "hifiasm_on_hifiasm") { - // Run hifiasm - hifi_reads - .map { meta, hifireads -> [meta, hifireads, []] } + ch_main_branched.hifiasm + .map { it -> [ it.meta, it.hifireads, it.ontreads ?: [] ] } .set { hifiasm_inputs } + HIFIASM(hifiasm_inputs, [[], [], []], [[], [], []], [[], []]) GFA_2_FA_HIFI(HIFIASM.out.processed_unitigs) ch_versions = ch_versions.mix(HIFIASM.out.versions).mix(GFA_2_FA_HIFI.out.versions) - if(params.assembler == "flye_on_hifiasm") { - // Run flye - ont_reads - .join(genome_size) - .map { meta, reads, genomesize -> [[id: meta.id, genome_size: genomesize], reads]} - .set { flye_inputs } + // Assemble hifiasm_ont branch + ch_main_branched.hifiasm_ont + .map { it -> [it.meta, it.ontreads, []] } + .set { hifiasm_ont_inputs } + + HIFIASM_ONT(hifiasm_ont_inputs, [[], [], []], [[], [], []], [[], []]) + + GFA_2_FA_ONT(HIFIASM_ONT.out.processed_unitigs) + + ch_versions = ch_versions.mix(HIFIASM_ONT.out.versions).mix(GFA_2_FA_ONT.out.versions) + + // Create a channel containing all assemblies in a "wide" format + ch_main + .map { it -> [meta: it.meta] } + .join(FLYE.out.fasta + .map { it -> [meta: it[0], flye_assembly: it[1]] }) + .join(GFA_2_FA_HIFI.out.contigs_fasta + .map { it -> [meta: it[0], hifiasm_hifi_assembly: it[1]]}) + .join(GFA_2_FA_ONT.out.contigs_fasta + .map { it -> [meta: it[0], hifiasm_ont_assembly: it[1]]}) + .set { ch_assemblies } + + // Now figure out which of the wide assemblies goes into which generic assembly slot + ch_main + .join { ch_assemblies } + // The extra columns are joined and removed via submap + .map { + it ->å + it + .subMap('flye_assembly') + .subMap('hifiasm_hifi_assembly') + .subMap('hifiasm_ont_assembly') + + [ + assembly: it.strategy == "single" || it.strategy == "hybrid" ? + (it.flye_assembly ?: + it.hifiasm_hifi_assembly ?: + it.hifiasm_ont_assembly) : + null, + // remaining case is "scaffold" + // by definition assembly1 == ONT in "scaffold" + assembly1: it.strategy != "scaffold" ? + null : + it.assembler1 == "flye" ? + (it.flye_assembly) : + (it.hifiasm_ont_assembly), + // assembly2 only exists if the strategy is "scaffold" + assembly2: it.strategy != "scaffold" ? + null : + // by definition assembly2 == hifi in "scaffold" + it.assembler2 == "flye" ? + (it.flye_assembly) : + (it.hifiasm_hifi_assembly) + ] + } + // This should return the unbranched main channel + .set { ch_main } - FLYE(flye_inputs, params.flye_mode) - FLYE.out.fasta - .map { meta, assembly -> [[id: meta.id], assembly] } - .join( - GFA_2_FA_HIFI.out.contigs_fasta - ) - .multiMap { meta, flye_fasta, hifiasm_fasta -> - target: [meta, flye_fasta] - query: [meta, hifiasm_fasta] - } - .set { ragtag_in } - ch_versions = ch_versions.mix(FLYE.out.versions) - } - if(params.assembler == "hifiasm_on_hifiasm") { - // Run hifiasm --ont - ont_reads - .map { meta, ontreads -> [meta, ontreads, []] } - .set { hifiasm_inputs } - HIFIASM_ONT(hifiasm_inputs,[[], [], []], [[], [], []], [[], []]) - GFA_2_FA_ONT(HIFIASM_ONT.out.processed_unitigs) - GFA_2_FA_ONT.out.contigs_fasta - .join( - GFA_2_FA_HIFI.out.contigs_fasta - ) - .multiMap { meta, ont_assembly, hifi_assembly -> - target: [meta, ont_assembly] - query: [meta, hifi_assembly] - } - .set { ragtag_in } - ch_versions = ch_versions.mix(HIFIASM_ONT.out.versions).mix(GFA_2_FA_ONT.out.versions) - } + ch_main + .filter { it -> + it.strategy == "scaffold" + } + .multiMap { + it -> + target: [ + it.meta, + it.assembly_scaffolding == "ont_on_hifi" ? (it.assembly1) : (it.assembly2) + ] + query: [ + it.meta, + it.assembly_scaffolding == "ont_on_hifi" ? (it.assembly2) : (it.assembly1) + ] + } + .set { ragtag_in } RAGTAG_PATCH(ragtag_in.target, ragtag_in.query, [[], []], [[], []] ) - // takes: meta, assembly (ont), reference (hifi) - RAGTAG_PATCH.out.patch_fasta.set { ch_assembly } + ch_versions = ch_versions.mix(RAGTAG_PATCH.out.versions) - } + + ch_main + .join( + RAGTAG_PATCH.out.patch_fasta + .map { it -> [meta: it[0], assembly_patched: it[1]] } + ) + .map { it -> + it.subMap("assembly_patched") + + [ + assembly: it.strategy == "scaffold" ? + (it.assembly_patched) : + (it.assembly) + ]} } /* Prepare alignments @@ -159,56 +179,54 @@ workflow ASSEMBLE { if (params.skip_alignments) { // Sample sheet layout when skipping assembly and mapping // sample,ontreads,assembly,ref_fasta,ref_gff,assembly_bam,assembly_bai,ref_bam - ch_input + ch_main .map { row -> [row.meta, row.ref_bam] } .set { ch_ref_bam } - ch_input + ch_main .map { row -> [row.meta, row.assembly_bam] } .set { ch_assembly_bam } } else { Channel.empty().set { ch_ref_bam } - if (params.assembler == "flye") { - flye_inputs - .map { meta, reads -> [[id: meta.id], reads] } - .set { longreads } - } - if (params.assembler == "hifiasm" || params.assembler == "flye_on_hifiasm" || params.assembler == "hifiasm_on_hifiasm") { - hifiasm_inputs - .map { meta, long_reads, _ultralong -> [meta, long_reads] } - .set { longreads } - // When using either hifiasm_ont or flye_on_hifiasm, both reads are available, which should be used for qc? - if (params.hifi && params.ont) { - if (params.qc_reads == 'ONT') { - ont_reads - .map { it -> [it[0], it[1]] } - .set { longreads } - } - if (params.qc_reads == 'HIFI') { - hifi_reads - .map { it -> [it[0], it[1]] } - .set { longreads } - } + + ch_main + .map { it -> + [ + meta: it.meta, + longreads: params.qc_reads == "ont" ? (it.ontreads) : (it.hifireads) + ] } - } + if (params.quast) { if (params.use_ref) { MAP_TO_REF(longreads, ch_refs) - - MAP_TO_REF.out.ch_aln_to_ref_bam.set { ch_ref_bam } + ch_main + .join( + MAP_TO_REF.out.ch_aln_to_ref_bam + .map { it -> [meta: it[0], ref_map_bam: it[1]] } + ) + .set { ch_main } + } else { + ch_main + .join( + ch_main + .map { it -> [meta: it.meta, ref_map_bam: []] } + ) + .set { ch_main } } } } /* QC on initial assembly */ - QC(ch_input, longreads, ch_assembly, ch_ref_bam, meryl_kmers) + QC( ch_main, + meryl_kmers) ch_versions = ch_versions.mix(QC.out.versions) if (params.lift_annotations) { - RUN_LIFTOFF(ch_assembly, ch_input) + RUN_LIFTOFF(ch_main) ch_versions = ch_versions.mix(RUN_LIFTOFF.out.versions) } diff --git a/subworkflows/local/hifi/main.nf b/subworkflows/local/hifi/main.nf index ea2fd032..d67486aa 100644 --- a/subworkflows/local/hifi/main.nf +++ b/subworkflows/local/hifi/main.nf @@ -3,20 +3,18 @@ include { PREPARE_HIFI } from '../prepare_hifi/main' workflow HIFI { take: - inputs + main_in main: Channel.empty().set { ch_versions } - PREPARE_HIFI(inputs) - - PREPARE_HIFI.out.hifireads.set { hifi_reads } + PREPARE_HIFI(main_in) ch_versions.mix(PREPARE_HIFI.out.versions) versions = ch_versions emit: - hifi_reads + main_out = PREPARE_HIFI.out.main_out versions } diff --git a/subworkflows/local/jellyfish/main.nf b/subworkflows/local/jellyfish/main.nf index e1257b58..3ca19b5d 100644 --- a/subworkflows/local/jellyfish/main.nf +++ b/subworkflows/local/jellyfish/main.nf @@ -6,12 +6,20 @@ include { GENOMESCOPE } from '../../../modules/local/genomescope/main' workflow JELLYFISH { take: - samples // id, fasta + inputs nanoq_out main: Channel.empty().set { genomescope_in } Channel.empty().set { ch_versions } + inputs.map { + it -> + [ + meta: it.meta, + reads: it.ontreads + ] + } + .set { samples } COUNT(samples) COUNT.out.kmers.set { kmers } @@ -26,11 +34,31 @@ workflow JELLYFISH { ch_versions = ch_versions.mix(HISTO.out.versions) if (!params.read_length == null) { - HISTO.out.histo.map { it -> [it[0], it[1], params.kmer_length, params.read_length] }.set { genomescope_in } + HISTO.out.histo + .map { + it -> + [ + it[0], + it[1], + params.kmer_length, + params.read_length + ] + } + .set { genomescope_in } } if (params.read_length == null) { - HISTO.out.histo.map { it -> [it[0], it[1], params.kmer_length] }.join(nanoq_out).set { genomescope_in } + HISTO.out.histo + .map { + it -> + [ + it[0], + it[1], + params.kmer_length + ] + } + .join(nanoq_out) + .set { genomescope_in } } GENOMESCOPE(genomescope_in) @@ -41,7 +69,21 @@ workflow JELLYFISH { ch_versions = ch_versions.mix(STATS.out.versions) - GENOMESCOPE.out.estimated_hap_len.set { hap_len } + inputs + .map { + it -> it.subMap('genome_size') + } + .join( + GENOMESCOPE.out.estimated_hap_len + .map { + it -> + [ + meta: it[0], + genome_size: it[1] + ] + } + ) + .set { outputs } GENOMESCOPE.out.summary.set { genomescope_summary } @@ -50,7 +92,7 @@ workflow JELLYFISH { versions = ch_versions emit: - hap_len + outputs genomescope_summary genomescope_plot versions diff --git a/subworkflows/local/liftoff/main.nf b/subworkflows/local/liftoff/main.nf index 7d2fd024..56e6e6b4 100644 --- a/subworkflows/local/liftoff/main.nf +++ b/subworkflows/local/liftoff/main.nf @@ -2,15 +2,19 @@ include { LIFTOFF } from '../../../modules/nf-core/liftoff/main' workflow RUN_LIFTOFF { take: - assembly - inputs + ch_main main: Channel.empty().set { ch_versions } - assembly - .join( - inputs.map { row -> [row.meta, row.ref_fasta, row.ref_gff] } - ) + ch_main + .map { it -> + [ + it.meta, + it.assembly, + it.ref_fasta, + it.ref_gff + ] + } .set { liftoff_in } LIFTOFF(liftoff_in, []) diff --git a/subworkflows/local/ont/main.nf b/subworkflows/local/ont/main.nf index 5f079840..7573f60f 100644 --- a/subworkflows/local/ont/main.nf +++ b/subworkflows/local/ont/main.nf @@ -3,8 +3,7 @@ include { JELLYFISH } from '../jellyfish/main' workflow ONT { take: - input_channel - genome_size + main_in main: Channel.empty().set { ch_versions } @@ -12,9 +11,9 @@ workflow ONT { .tap { genomescope_summary } .tap { genomescope_plot } - PREPARE_ONT(input_channel) + PREPARE_ONT(main_in) - PREPARE_ONT.out.trimmed.set { ont_reads } + PREPARE_ONT.out.trimmed.set { main_out } PREPARE_ONT.out.nanoq_report.set { nanoq_report } @@ -24,9 +23,7 @@ workflow ONT { if (params.jellyfish) { JELLYFISH(PREPARE_ONT.out.trimmed, PREPARE_ONT.out.med_len) - if (params.genome_size == null) { - JELLYFISH.out.hap_len.set { genome_size } - } + JELLYFISH.out.outputs.set { output_channel } JELLYFISH.out.genomescope_summary.set { genomescope_summary } JELLYFISH.out.genomescope_plot.set { genomescope_plot } ch_versions = ch_versions.mix(JELLYFISH.out.versions) @@ -35,8 +32,7 @@ workflow ONT { versions = ch_versions emit: - ont_reads - genome_size + main_out nanoq_report nanoq_stats genomescope_plot diff --git a/subworkflows/local/prepare_hifi/main.nf b/subworkflows/local/prepare_hifi/main.nf index 3eda0a32..af509b5f 100644 --- a/subworkflows/local/prepare_hifi/main.nf +++ b/subworkflows/local/prepare_hifi/main.nf @@ -3,25 +3,37 @@ include { SAMTOOLS_FASTQ as TO_FASTQ } from '../../../modules/nf-core/samtools/f workflow PREPARE_HIFI { take: - inputs + main_in main: Channel.empty().set { ch_versions } - inputs + main_in .map { it -> [it.meta, it.hifireads] } .set { hifireads } + if (params.lima) { if (!params.pacbio_primers) { error('Trimming with lima requires a file containing primers (--pacbio_primers)') } LIMA(hifireads, params.pacbio_primers) TO_FASTQ(LIMA.out.bam, false) - TO_FASTQ.out.set { hifireads } + main_in + .map { it -> it.subMap('hifireads') } + .join( + TO_FASTQ.out.fastq.map { + it -> + [ + meta: it[0], + hifireads: it[1] + ] + } + ) + .set { main_out } ch_versions.mix(LIMA.out.versions).mix(TO_FASTQ.out.versions) } versions = ch_versions emit: - hifireads + main_out versions } diff --git a/subworkflows/local/prepare_ont/chop/main.nf b/subworkflows/local/prepare_ont/chop/main.nf index 0fa31419..632e2411 100644 --- a/subworkflows/local/prepare_ont/chop/main.nf +++ b/subworkflows/local/prepare_ont/chop/main.nf @@ -2,19 +2,41 @@ include { PORECHOP_PORECHOP as PORECHOP } from '../../../../modules/nf-core/pore workflow CHOP { take: - in_reads + input main: Channel.empty().set { chopped_reads } Channel.empty().set { ch_versions } if (params.porechop) { + input.map { + it -> + [ + meta: it.meta, + reads: it.ontreads + ] + } + .set { in_reads } PORECHOP(in_reads) - PORECHOP.out.reads.set { chopped_reads } + input.map { + it -> + it.subMap('ontreads') + } + .join( + PORECHOP.out.reads + .map { it -> + [ + meta: it[0], + ont_reads: it[1] + ] + } + ) + .set { chopped_reads } + ch_versions.mix(PORECHOP.out.versions) } else { - in_reads.set { chopped_reads } + input.set { chopped_reads } } versions = ch_versions diff --git a/subworkflows/local/prepare_ont/collect/main.nf b/subworkflows/local/prepare_ont/collect/main.nf index aea3fdec..e3739242 100644 --- a/subworkflows/local/prepare_ont/collect/main.nf +++ b/subworkflows/local/prepare_ont/collect/main.nf @@ -18,6 +18,11 @@ workflow COLLECT { } versions = ch_versions + ch_input + .map { it -> it.submap('ontreads') } + .join(reads.map { it -> [meta: it[0], ontreads:it[1]] } ) + .set { reads } + emit: reads versions diff --git a/subworkflows/local/prepare_ont/run_nanoq/main.nf b/subworkflows/local/prepare_ont/run_nanoq/main.nf index 4f78c859..3e6b91ad 100644 --- a/subworkflows/local/prepare_ont/run_nanoq/main.nf +++ b/subworkflows/local/prepare_ont/run_nanoq/main.nf @@ -2,11 +2,18 @@ include { NANOQ } from '../../../../modules/local/nanoq/main' workflow RUN_NANOQ { take: - in_reads + inputs main: Channel.empty().set { versions } - + inputs.map { + it -> + [ + meta: it.meta, + ontreads: it.ontreads + ] + } + .set { in_reads } NANOQ(in_reads) NANOQ.out.report.set { report } diff --git a/subworkflows/local/prepare_shortreads/main.nf b/subworkflows/local/prepare_shortreads/main.nf index 78641add..4ada74fa 100644 --- a/subworkflows/local/prepare_shortreads/main.nf +++ b/subworkflows/local/prepare_shortreads/main.nf @@ -4,12 +4,12 @@ include { MERYL_UNIONSUM } from '../../../modules/nf-core/meryl/unionsum/main' workflow PREPARE_SHORTREADS { take: - input_channel + main_in main: Channel.empty().set { ch_versions } - input_channel + main_in .map { create_shortread_channel(it) } .set { shortreads } @@ -18,14 +18,24 @@ workflow PREPARE_SHORTREADS { TRIMGALORE.out.reads.set { shortreads } ch_versions = ch_versions.mix(TRIMGALORE.out.versions) } + MERYL_COUNT(shortreads.map { it -> [it[0], it[1]] }, params.meryl_k) MERYL_UNIONSUM(MERYL_COUNT.out.meryl_db, params.meryl_k) MERYL_UNIONSUM.out.meryl_db.set { meryl_kmers } + main_in + .map { + it -> it.subMap('shortread_F', 'shortread_R', 'paired') + } + .join( + shortreads.map { it -> [meta: [id: it[0].id], shortreads: it[1]]} + ) + .set { main_out } + versions = ch_versions.mix(MERYL_COUNT.out.versions).mix(MERYL_UNIONSUM.out.versions) emit: - shortreads + main_out meryl_kmers versions } diff --git a/subworkflows/local/qc/main.nf b/subworkflows/local/qc/main.nf index bba6b31d..f33baa86 100644 --- a/subworkflows/local/qc/main.nf +++ b/subworkflows/local/qc/main.nf @@ -5,10 +5,7 @@ include { MERQURY_QC } from './merqury/main.nf' workflow QC { take: - inputs - in_reads - scaffolds - aln_to_ref + ch_main meryl_kmers main: @@ -16,15 +13,33 @@ workflow QC { Channel.empty().set { quast_out } Channel.empty().set { busco_out } Channel.empty().set { merqury_report_files } - Channel.empty().set { map_to_assembly } + + ch_main + .map { it -> [meta: it.meta, scaffolds: it.assembly] } + .set { scaffolds } + + + ch_main + .map { it -> + [ + meta: it.meta, + longreads: params.qc_reads == "ont" ? (it.ontreads) : (it.hifireads) + ] + } + .set { reads } if (params.quast) { - MAP_TO_ASSEMBLY(in_reads, scaffolds) - MAP_TO_ASSEMBLY.out.aln_to_assembly_bam.set { map_to_assembly } + MAP_TO_ASSEMBLY(reads, scaffolds) + ch_main + .join( + MAP_TO_ASSEMBLY.out.aln_to_assembly_bam + .map { it -> [meta: it[0], assembly_map_bam: it[1]] } + ) + .set { ch_main } ch_versions = ch_versions.mix(MAP_TO_ASSEMBLY.out.versions) } - RUN_QUAST(scaffolds, inputs, aln_to_ref, map_to_assembly) + RUN_QUAST(ch_main) RUN_QUAST.out.quast_tsv.set { quast_out } ch_versions = ch_versions.mix(RUN_QUAST.out.versions) diff --git a/subworkflows/local/qc/quast/main.nf b/subworkflows/local/qc/quast/main.nf index bf7a4566..a31dc2c4 100644 --- a/subworkflows/local/qc/quast/main.nf +++ b/subworkflows/local/qc/quast/main.nf @@ -2,10 +2,7 @@ include { QUAST } from '../../../../modules/local/quast/main' workflow RUN_QUAST { take: - assembly - inputs - aln_to_ref - aln_to_assembly + ch_main main: Channel.empty().set { versions } @@ -17,14 +14,18 @@ workflow RUN_QUAST { Channel.empty().set { quast_tsv } if (params.quast) { - inputs - .map { row -> [row.meta, row.ref_fasta, row.ref_gff] } - .set { inputs_references } + ch_main + .map { it -> + [ + it.meta, + it.assembly, + it.ref_fasta, + [], + it.reference_map_bam, + it.assembly_map_bam + ] - assembly - .join(inputs_references) - .join(aln_to_ref) - .join(aln_to_assembly) + } .set { quast_in } /* * Run QUAST diff --git a/subworkflows/local/utils_nfcore_genomeassembler_pipeline/main.nf b/subworkflows/local/utils_nfcore_genomeassembler_pipeline/main.nf index 0770101a..a6c91521 100644 --- a/subworkflows/local/utils_nfcore_genomeassembler_pipeline/main.nf +++ b/subworkflows/local/utils_nfcore_genomeassembler_pipeline/main.nf @@ -108,8 +108,16 @@ workflow PIPELINE_INITIALISATION { assembler1: it.assembler1, assembler2: it.assembler2, scaffolding: it.scaffolding_order, - genome_size: it.genome_size, - // old + genome_size: it.genome_size ?: params.genome_size, + assembler1_args: it.assembler1_args ?: + (it.assembler1 == "hifiasm") ? params.hifiasm_args : + (it.assembler1 == "flye") ? params.flye_args : + null, + assembler2_args: it.assembler2_args ?: + (it.assembler2 == "hifiasm") ? params.hifiasm_args : + (it.assembler2 == "flye") ? params.flye_args : + null, + // not new ref_fasta: it.ref_fasta, ref_gff: it.ref_gff, shortread_F: it.shortread_F, @@ -128,21 +136,42 @@ workflow PIPELINE_INITIALISATION { def hybrid_assemblers = ["hifiasm"] // sample-level checks - + // if a check fails, map returns a list that prints what fails, and contains "invalid" + // error is raised by subscribe if there is more than one "invalid" ch_samplesheet .map { it -> // Check if assembler can do hybrid + (it.strategy == "single" && it.ont_reads && it.hifi_reads) + ? + [ + println("Please confirm samplesheet: [sample: $it.meta.id]: Stragety is $it.strategy, but both types of reads are provided."), + "invalid" + ] + : null + // Check if assembler can do hybrid (it.strategy == "hybrid" && !hybrid_assemblers.contains(it.assembler1)) - ? [ println("Please confirm samplesheet: [sample: $it.meta.id]: Hybrid assembly can only be performed with $hybrid_assemblers"), "invalid" ] + ? + [ + println("Please confirm samplesheet: [sample: $it.meta.id]: Hybrid assembly can only be performed with $hybrid_assemblers"), + "invalid" + ] : null // Check if qc reads are specified for hybrid assemblies (it.strategy == "hybrid" && !params.qc_reads) - ? [ println("Please confirm samplesheet: [sample: $it.meta.id]: Please specify which reads should be used for qc: '--qc_reads': 'ONT' or 'HIFI'") , "invalid" ] + ? + [ + println("Please confirm samplesheet: [sample: $it.meta.id]: Please specify which reads should be used for qc: '--qc_reads': 'ONT' or 'HIFI'"), + "invalid" + ] : null // Check if genome_size is given with --scaffold_longstitch (params.scaffold_longstitch && !it.genome_size && !(it.ont_reads && params.jellyfish)) - ? [ println("Please confirm samplesheet: [sample: $it.meta.id]: --scaffold_longstitch requires genome-size. Either provide genome-size estimate, or estimate from ONT reads with --jellyfish"), "invalid" ] + ? + [ + println("Please confirm samplesheet: [sample: $it.meta.id]: --scaffold_longstitch requires genome-size. Either provide genome-size estimate, or estimate from ONT reads with --jellyfish"), + "invalid" + ] : null } .collect() diff --git a/workflows/genomeassembler.nf b/workflows/genomeassembler.nf index 48b2d6db..e27ddaf2 100644 --- a/workflows/genomeassembler.nf +++ b/workflows/genomeassembler.nf @@ -41,10 +41,33 @@ workflow GENOMEASSEMBLER { main: // Initialize empty channels + ch_input.set { ch_main } + /* + This is the "main" channel, it contains all sample-wise information. + This channel should be the main input of all subworkflows, + and the subworkflows should make relevant changes / updates to the map. + This channel should stay a map to allow key-based modifications in subworkflows. + The keys are defined in subworkflows/local/utils_nfcore_genomeassembler/main.nf : + + meta: [id: string], + ontreads: path, + hifireads: path, + strategy: string, + assembler1: string, + assembler2: string, + scaffolding: string, + genome_size: integer, + assembler1_args: string, + assembler2_args: string, + ref_fasta: path, + ref_gff: path, + shortread_F: path, + shortread_R: path, + paired: bool + + */ Channel.empty().set { ch_ref_bam } Channel.empty().set { ch_polished_genome } - Channel.empty().set { ch_ont_reads } - Channel.empty().set { ch_hifi_reads } Channel.empty().set { ch_shortreads } Channel.empty().set { meryl_kmers } Channel.empty().set { genome_size } @@ -59,14 +82,7 @@ workflow GENOMEASSEMBLER { .tap { busco_files } .map { it -> [it[0], it[1], it[1], it[1], it[1]] } .tap { merqury_files } - /* - ============= - Some checks - ============= - */ - if (!params.ont && !params.hifi) { - error('At least one of params.ont, params.hifi needs to be true.') - } + /* ============= Prepare reads @@ -76,22 +92,20 @@ workflow GENOMEASSEMBLER { Short reads */ if (params.short_reads) { - PREPARE_SHORTREADS(ch_input) - PREPARE_SHORTREADS.out.shortreads.set { ch_shortreads } + PREPARE_SHORTREADS(ch_main) + PREPARE_SHORTREADS.out.main_out.set { ch_main } + // This changes ch_main shortreads_F and _R become one tuple, paired is gone. PREPARE_SHORTREADS.out.meryl_kmers.set { meryl_kmers } ch_versions = ch_versions.mix(PREPARE_SHORTREADS.out.versions) } - ch_input.map { it -> [it.meta, params.genome_size] } - .set { genome_size } - /* ONT reads */ if (params.ont) { - ONT(ch_input, genome_size) - ONT.out.genome_size.set { genome_size } - ONT.out.ont_reads.set { ch_ont_reads } + ONT(ch_main) + + ONT.out.main_out.set { ch_main } ONT.out.nanoq_report .concat( @@ -115,8 +129,8 @@ workflow GENOMEASSEMBLER { HIFI reads */ if (params.hifi) { - HIFI(ch_input) - HIFI.out.hifi_reads.set { ch_hifi_reads } + HIFI(ch_main) + HIFI.out.main_out.set { ch_main } ch_versions = ch_versions.mix(HIFI.out.versions) } @@ -125,7 +139,7 @@ workflow GENOMEASSEMBLER { Assembly */ - ASSEMBLE(ch_ont_reads, ch_hifi_reads, ch_input, genome_size, meryl_kmers) + ASSEMBLE( ch_main, meryl_kmers) ASSEMBLE.out.assembly.set { ch_polished_genome } ASSEMBLE.out.ref_bam.set { ch_ref_bam } ASSEMBLE.out.longreads.set { ch_longreads } From 539f3093dcfc113337ff6221387edaf8073cd260 Mon Sep 17 00:00:00 2001 From: Niklas Schandry Date: Tue, 1 Jul 2025 09:40:10 +0200 Subject: [PATCH 008/162] checks, typos --- subworkflows/local/assemble/main.nf | 2 +- .../main.nf | 20 ++++++++++++------- 2 files changed, 14 insertions(+), 8 deletions(-) diff --git a/subworkflows/local/assemble/main.nf b/subworkflows/local/assemble/main.nf index e4082535..360497fd 100644 --- a/subworkflows/local/assemble/main.nf +++ b/subworkflows/local/assemble/main.nf @@ -109,7 +109,7 @@ workflow ASSEMBLE { .join { ch_assemblies } // The extra columns are joined and removed via submap .map { - it ->å + it -> it .subMap('flye_assembly') .subMap('hifiasm_hifi_assembly') diff --git a/subworkflows/local/utils_nfcore_genomeassembler_pipeline/main.nf b/subworkflows/local/utils_nfcore_genomeassembler_pipeline/main.nf index a6c91521..ef902e6c 100644 --- a/subworkflows/local/utils_nfcore_genomeassembler_pipeline/main.nf +++ b/subworkflows/local/utils_nfcore_genomeassembler_pipeline/main.nf @@ -105,8 +105,14 @@ workflow PIPELINE_INITIALISATION { hifireads: it.hifireads, // new in refactor-assemblers strategy: it.strategy, - assembler1: it.assembler1, - assembler2: it.assembler2, + assembler1: it.assembler1 ?: + ["hifiasm","flye"].contains(params.assembler) ? params.assembler : + params.assembler == "flye_on_hifiasm" ? "flye" : + params.assembler == "hifiasm_on_hifiasm" ? "hifiasm" + : null, + assembler2: it.assembler2 ?: + ["hifiasm_on_hifiasm","flye_on_hifiasm"].contains(params.assembler) ? "hifiasm" : + null, scaffolding: it.scaffolding_order, genome_size: it.genome_size ?: params.genome_size, assembler1_args: it.assembler1_args ?: @@ -118,11 +124,11 @@ workflow PIPELINE_INITIALISATION { (it.assembler2 == "flye") ? params.flye_args : null, // not new - ref_fasta: it.ref_fasta, - ref_gff: it.ref_gff, - shortread_F: it.shortread_F, - shortread_R: it.shortread_R, - paired: it.paired + ref_fasta: it.ref_fasta ?: params.ref_fasta, + ref_gff: it.ref_gff ?: params.ref_gff, + shortread_F: it.shortread_F ?: params.shortread_F, + shortread_R: it.shortread_R ?: params.shortread_R, + paired: it.paired ?: params.paired ] } .set { ch_samplesheet } if (params.use_ref) { From f2e0ca70099d5695b06e4d54655fd4ffcbddb2d4 Mon Sep 17 00:00:00 2001 From: nf-core bot Date: Tue, 1 Jul 2025 09:36:28 +0200 Subject: [PATCH 009/162] Important! Template update for nf-core/tools v3.3.1 (#164) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Template update for nf-core/tools version 3.2.1 * Template update for nf-core/tools version 3.3.1 * merge template 3.3.1 - fix linting * update pre-commit * merge template 3.3.1 - fix linting * pre-commit config? * pre-commit config? * reinstall links * try larger runner * smaller run, disable bloom filter for hifiasm test * updated test snapshot * updated test snapshot * update nftignore * update nftignore * update nftignore * update nftignore * update nftignore * update nftignore * update nftignore * update nftignore * update nftignore * Update .github/actions/nf-test/action.yml Co-authored-by: Matthias Hörtenhuber * Update docs/output.md Co-authored-by: Matthias Hörtenhuber * remove .nf-test.log --------- Co-authored-by: Niklas Schandry Co-authored-by: Matthias Hörtenhuber --- .github/workflows/fix_linting.yml | 6 ++ .gitignore | 1 - nextflow.config | 6 -- nf-test.config | 2 +- schema.md | 145 ++++++++++++++++++++++++++++++ tests/default.nf.test.snap | 6 +- 6 files changed, 155 insertions(+), 11 deletions(-) create mode 100644 schema.md diff --git a/.github/workflows/fix_linting.yml b/.github/workflows/fix_linting.yml index ed185515..53dde2f3 100644 --- a/.github/workflows/fix_linting.yml +++ b/.github/workflows/fix_linting.yml @@ -32,9 +32,15 @@ jobs: GITHUB_TOKEN: ${{ secrets.nf_core_bot_auth_token }} # Install and run pre-commit +<<<<<<< HEAD - uses: actions/setup-python@e797f83bcb11b83ae66e0230d6156d7c80228e7c # v6 with: python-version: "3.14" +======= + - uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5 + with: + python-version: "3.13" +>>>>>>> 533baa7 (Important! Template update for nf-core/tools v3.3.1 (#164)) - name: Install pre-commit run: pip install pre-commit diff --git a/.gitignore b/.gitignore index 11ebb3ef..f232546a 100644 --- a/.gitignore +++ b/.gitignore @@ -9,4 +9,3 @@ testing* null/ .nf-test/ .nf-test.log -NOTE.md diff --git a/nextflow.config b/nextflow.config index d20e92b5..a86bd8b1 100644 --- a/nextflow.config +++ b/nextflow.config @@ -242,12 +242,6 @@ profiles { // Load nf-core/genomeassembler custom profiles from different institutions. includeConfig params.custom_config_base && (!System.getenv('NXF_OFFLINE') || !params.custom_config_base.startsWith('http')) ? "${params.custom_config_base}/nfcore_custom.config" : "/dev/null" -// 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. -// Load nf-core/genomeassembler custom profiles from different institutions. -includeConfig params.custom_config_base && (!System.getenv('NXF_OFFLINE') || !params.custom_config_base.startsWith('http')) ? "${params.custom_config_base}/nfcore_custom.config" : "/dev/null" - // Load nf-core/genomeassembler custom profiles from different institutions. includeConfig params.custom_config_base && (!System.getenv('NXF_OFFLINE') || !params.custom_config_base.startsWith('http')) ? "${params.custom_config_base}/pipeline/genomeassembler.config" : "/dev/null" diff --git a/nf-test.config b/nf-test.config index 3a1fff59..c9fb4e90 100644 --- a/nf-test.config +++ b/nf-test.config @@ -1,4 +1,4 @@ -config { + config { // location for all nf-test tests testsDir "." diff --git a/schema.md b/schema.md new file mode 100644 index 00000000..378031bb --- /dev/null +++ b/schema.md @@ -0,0 +1,145 @@ +# nf-core/genomeassembler pipeline parameters + +Assemble genomes from long ONT or pacbio HiFi reads + +## Input/output options + +Define where the pipeline should find input data and save output data. + +| Parameter | Description | Type | Default | Required | Hidden | +| ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------- | ------- | -------- | ------ | +| `input` | Path to comma-separated file containing information about the samples in the experiment.
HelpYou 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/genomeassembler/usage#samplesheet-input).
| `string` | | True | | +| `outdir` | The output directory where the results will be saved. You have to use absolute paths to storage on Cloud infrastructure. | `string` | | True | | +| `email` | Email address for completion summary.
HelpSet this parameter to your e-mail address to get a summary e-mail with details of the run sent to you when the workflow exits. If set in your user config file | +| (`~/.nextflow/config`) then you don't need to specify this on the command line for every run.
| `string` | | | | + +## Institutional config options + +Parameters used to describe centralised config profiles. These should not be edited. + +| Parameter | Description | Type | Default | Required | Hidden | +| ------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------- | ------- | -------- | ------ | +| `custom_config_version` | Git commit id for Institutional configs. | `string` | master | | True | +| `custom_config_base` | Base directory for Institutional configs.
HelpIf you're running offline, Nextflow will not be able to fetch the institutional config files from the internet. If you don't need them, then this is not | +| a problem. If you do need them, you should download the files from the repo and tell Nextflow where to find them with this parameter.
| `string` | https://raw.githubusercontent.com/nf-core/configs/master | | True | +| `config_profile_name` | Institutional config name. | `string` | | | True | +| `config_profile_description` | Institutional config description. | `string` | | | True | +| `config_profile_contact` | Institutional config contact information. | `string` | | | True | +| `config_profile_url` | Institutional config URL link. | `string` | | | True | + +## Generic options + +Less common options for the pipeline, typically set in a config file. + +| Parameter | Description | Type | Default | Required | Hidden | +| --------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------- | -------------------------------------------------------- | -------- | ------ | +| `version` | Display version and exit. | `boolean` | | | True | +| `publish_dir_mode` | Method used to save pipeline results to output directory.
HelpThe Nextflow `publishDir` option specifies which intermediate files should be saved to the output directory. This option tells the pipeline | +| what method should be used to move these files. See [Nextflow docs](https://www.nextflow.io/docs/latest/process.html#publishdir) for details.
| `string` | copy | | True | +| `email_on_fail` | Email address for completion summary, only when pipeline fails.
HelpAn email address to send a summary email to when the pipeline is completed - ONLY sent if the pipeline does not exit | +| successfully.
| `string` | | | True | +| `plaintext_email` | Send plain-text email instead of HTML. | `boolean` | | | True | +| `monochrome_logs` | Do not use coloured log outputs. | `boolean` | | | True | +| `hook_url` | Incoming hook URL for messaging service
HelpIncoming hook URL for messaging service. Currently, MS Teams and Slack are supported.
| `string` | | | True | +| `validate_params` | Boolean whether to validate parameters against the schema at runtime | `boolean` | True | | True | +| `pipelines_testdata_base_path` | Base URL or local path to location of pipeline test dataset files | `string` | https://raw.githubusercontent.com/nf-core/test-datasets/ | | True | + +## Reference Parameters + +Options controlling pipeline behavior + +| Parameter | Description | Type | Default | Required | Hidden | +| ----------- | ------------------------------------------ | --------- | ------- | -------- | ------ | +| `ref_fasta` | Path to reference genome seqeunce (fasta) | `string` | | | | +| `ref_gff` | Path to reference genome annotations (gff) | `string` | | | | +| `use_ref` | use reference genome | `boolean` | | | True | + +## Assembly options + +Options controlling assembly + +| Parameter | Description | Type | Default | Required | Hidden | +| ------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------- | ----------- | -------- | ------ | +| `strategy` | Assembly strategy to use. Valid choices are `'single'`, `'hybrid'` and `'scaffold'` | `string` | single | | | +| `assembler` | Assembler to use. Valid choices depend on strategy; for single either `flye` or `hifiasm`, hybrid can be done with `hifiasm` and for scaffolded assembly provide the names of the assemblers separated with an underscore. The first assembler will | +| be used for ONT reads, the second for HiFi reads. | `string` | hifiasm | | | +| `assembly_scaffolding_order` | When strategy is "scaffold", which assembly should be scaffolded onto which? | `string` | ont_on_hifi | | | +| `genome_size` | expected genome size, optional | `string` | | | | +| `flye_mode` | flye mode | `string` | --nano-hq | | | +| `flye_args` | additional args for flye | `string` | | | | +| `hifiasm_args` | Extra arguments passed to `hifiasm` | `string` | "" | | | + +## Long-read preprocessing + +| Parameter | Description | Type | Default | Required | Hidden | +| --------------------- | -------------------------------------------------------- | --------- | ------- | -------- | ------ | +| `ontreads` | Path to ONT reads | `string` | | | | +| `ont_collect` | Collect ONT reads from several files? | `boolean` | | | | +| `ont_trim` | Trim ont reads with fastplong? | `boolean` | | | | +| `ont_adapters` | Adaptors for ONT read-trimming | `string` | [] | | | +| `ont_fastplong_args` | Additional args to be passed to fastplong for ONT reads | `string` | "" | | | +| `hifireads` | Path to HiFi reads | `string` | | | | +| `hifi_trim` | Trim HiFi reads with fastplonng | `boolean` | | | | +| `hifi_adapters` | Adaptors for HiFi read-trimming | `string` | [] | | | +| `hifi_fastplong_args` | Additional args to be passed to fastplong for HiFi reads | `string` | "" | | | +| `jellyfish` | Run jellyfish and genomescope (recommended) | `boolean` | | | | +| `jellyfish_k` | Value of k used during k-mer analysis with jellyfish | `integer` | 21 | | | +| `dump` | dump jellyfish output | `boolean` | | | | + +## Polishing options + +Polishing options + +| Parameter | Description | Type | Default | Required | Hidden | +| --------------- | ------------------------------------------------ | --------- | ------- | -------- | ------ | +| `polish_pilon` | Polish assembly with pilon? Requires short reads | `boolean` | | | | +| `polish_medaka` | Polish assembly with medaka (ONT only) | `boolean` | | | | +| `medaka_model` | model to use with medaka | `string` | "" | | | + +## Scaffolding options + +Scaffolding options + +| Parameter | Description | Type | Default | Required | Hidden | +| --------------------- | ------------------------------------------ | --------- | ------- | -------- | ------ | +| `scaffold_longstitch` | Scaffold with longstitch? | `boolean` | | | | +| `scaffold_links` | Scaffolding with links? | `boolean` | | | | +| `scaffold_ragtag` | Scaffold with ragtag (requires reference)? | `boolean` | | | | + +## QC options + +Options for QC tools + +| Parameter | Description | Type | Default | Required | Hidden | +| ------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------- | --------- | ----------------- | -------- | ------ | +| `merqury` | Run merqury (if short reads are provided) | `boolean` | True | | | +| `qc_reads` | Long reads that should be used for QC when both ONT and HiFi reads are provided. Options are `'ont'` or `'hifi'` | `string` | ont | | | +| `busco` | Run BUSCO? | `boolean` | | | | +| `busco_db` | Path to busco db (optional) | `string` | | | | +| `busco_lineage` | Busco lineage to use | `string` | brassicales_odb10 | | | +| `quast` | Run quast | `boolean` | | | | +| `ref_map_bam` | A mapping (bam) of reads mapped to the reference can be provided for QC. If provided alignment to reference fasta will not run | `string` | | | | +| `assembly` | Can be used to proved existing assembly will skip assembly and perform downstream steps including qc | `string` | | | | +| `assembly_map_bam` | A mapping (bam) of reads mapped to the provided assembly can be specified for QC. If provided alignment to the provided assembly fasta will not run | `string` | | | | + +## Annotations options + +Options controlling annotation liftover + +| Parameter | Description | Type | Default | Required | Hidden | +| ------------------ | ----------------------------------------- | --------- | ------- | -------- | ------ | +| `lift_annotations` | Lift-over annotations (requires ref_gff)? | `boolean` | True | | | + +## Short read options + +Options for short reads + +| Parameter | Description | Type | Default | Required | Hidden | +| ----------------- | ------------------------------- | --------- | ------- | -------- | ------ | +| `use_short_reads` | Use short reads? | `boolean` | | | | +| `shortread_trim` | Trim short reads? | `boolean` | | | | +| `meryl_k` | kmer length for meryl / merqury | `integer` | 21 | | | +| `shortread_F` | Path to forward short reads | `string` | | | | +| `shortread_R` | Path to reverse short reads | `string` | | | | +| `paired` | Are shortreads paired? | `string` | | | | diff --git a/tests/default.nf.test.snap b/tests/default.nf.test.snap index 73ab05f2..e4bfbb0b 100644 --- a/tests/default.nf.test.snap +++ b/tests/default.nf.test.snap @@ -7,7 +7,7 @@ "flye": "2.9.5-b1801" }, "GFA_2_FA_HIFI": { - "awk": "mawk 1.3.4", + "awk": "1.3.4", "gzip": 1.13 }, "HIFIASM": { @@ -82,8 +82,8 @@ ], "meta": { "nf-test": "0.9.2", - "nextflow": "24.10.5" + "nextflow": "24.10.3" }, - "timestamp": "2025-07-02T11:25:42.487154678" + "timestamp": "2025-06-26T16:28:52.273158206" } } \ No newline at end of file From 5579db4f4accdbcfb63b1d2961401011b34e6a94 Mon Sep 17 00:00:00 2001 From: Niklas Schandry Date: Fri, 27 Jun 2025 16:54:21 +0200 Subject: [PATCH 010/162] refactor assemble and assemble subworkflows for sample-wise parameterization --- subworkflows/local/assemble/main.nf | 35 ++++++++++++++++++++++++++++- 1 file changed, 34 insertions(+), 1 deletion(-) diff --git a/subworkflows/local/assemble/main.nf b/subworkflows/local/assemble/main.nf index 360497fd..a605550d 100644 --- a/subworkflows/local/assemble/main.nf +++ b/subworkflows/local/assemble/main.nf @@ -12,6 +12,7 @@ include { QC } from '../qc/main' workflow ASSEMBLE { take: ch_main + ch_main meryl_kmers main: @@ -26,6 +27,7 @@ workflow ASSEMBLE { Channel.empty().set { ch_versions } if (params.use_ref) { + ch_main ch_main .map { row -> [row.meta, row.ref_fasta] } .set { ch_refs } @@ -34,12 +36,42 @@ workflow ASSEMBLE { if (params.skip_assembly) { // Sample sheet layout when skipping assembly // sample,ontreads,assembly,ref_fasta,ref_gff + ch_main ch_main .map { row -> [row.meta, row.assembly] } .set { ch_assembly } } if (!params.skip_assembly) { + ch_main + .branch { it -> + hifiasm: (it.strategy == "single" && it.assembler1 == "hifiasm") + || (it.strategy == "scaffold" && (it.assembler1 == "hifiasm" || it.assembler2 == "hifiasm")) + || (it.strategy == "hybrid" && it.assembler1 == "hifiasm") + hifiasm_ont: (it.strategy == "single" && it.assembler1 == "hifiasm" && it.ontreads) + flye: (it.strategy == "single" && it.assembler1 == "flye") || (it.strategy == "scaffold" && (it.assembler1 == "flye")) + } + .set { ch_main_branched } + + // Assembly flye branch + + ch_main_branched.flye + .multiMap { + it -> + reads: [ + [ + meta: it.meta, + genome_size: it.genome_size + ], + it.ontreads ?: it.hifireads, + ] + mode: it.ontreads ? "nano-hq" : "--pacbio-hifi" + } + .set { flye_inputs } + + FLYE(flye_inputs.reads, flye_inputs.mode) + + ch_main .branch { it -> hifiasm: (it.strategy == "single" && it.assembler1 == "hifiasm") @@ -76,6 +108,7 @@ workflow ASSEMBLE { .map { it -> [ it.meta, it.hifireads, it.ontreads ?: [] ] } .set { hifiasm_inputs } + HIFIASM(hifiasm_inputs, [[], [], []], [[], [], []], [[], []]) GFA_2_FA_HIFI(HIFIASM.out.processed_unitigs) @@ -109,7 +142,7 @@ workflow ASSEMBLE { .join { ch_assemblies } // The extra columns are joined and removed via submap .map { - it -> + it ->å it .subMap('flye_assembly') .subMap('hifiasm_hifi_assembly') From 39b5d3842cb52a06e2472815b4a30eadc5b19bcc Mon Sep 17 00:00:00 2001 From: Niklas Schandry Date: Wed, 2 Jul 2025 16:28:24 +0200 Subject: [PATCH 011/162] intermediate WIP commit --- subworkflows/local/assemble/main.nf | 96 +++++++++++------------------ subworkflows/local/qc/main.nf | 26 ++++---- workflows/genomeassembler.nf | 4 +- 3 files changed, 53 insertions(+), 73 deletions(-) diff --git a/subworkflows/local/assemble/main.nf b/subworkflows/local/assemble/main.nf index a605550d..4d37c1a1 100644 --- a/subworkflows/local/assemble/main.nf +++ b/subworkflows/local/assemble/main.nf @@ -12,15 +12,11 @@ include { QC } from '../qc/main' workflow ASSEMBLE { take: ch_main - ch_main meryl_kmers main: // Empty channels Channel.empty().set { ch_refs } - Channel.empty().set { ch_ref_bam } - Channel.empty().set { ch_assembly_bam } - Channel.empty().set { ch_assembly } Channel.empty().set { flye_inputs } Channel.empty().set { hifiasm_inputs } Channel.empty().set { longreads } @@ -45,11 +41,11 @@ workflow ASSEMBLE { ch_main .branch { it -> - hifiasm: (it.strategy == "single" && it.assembler1 == "hifiasm") + hifiasm: ((it.strategy == "single" && it.assembler1 == "hifiasm") || (it.strategy == "scaffold" && (it.assembler1 == "hifiasm" || it.assembler2 == "hifiasm")) - || (it.strategy == "hybrid" && it.assembler1 == "hifiasm") - hifiasm_ont: (it.strategy == "single" && it.assembler1 == "hifiasm" && it.ontreads) - flye: (it.strategy == "single" && it.assembler1 == "flye") || (it.strategy == "scaffold" && (it.assembler1 == "flye")) + || (it.strategy == "hybrid" && it.assembler1 == "hifiasm")) ? it : null + hifiasm_ont: (it.strategy == "single" && it.assembler1 == "hifiasm" && it.ontreads) ? it : null + flye: ((it.strategy == "single" && it.assembler1 == "flye") || (it.strategy == "scaffold" && (it.assembler1 == "flye"))) ? it : null } .set { ch_main_branched } @@ -60,37 +56,8 @@ workflow ASSEMBLE { it -> reads: [ [ - meta: it.meta, - genome_size: it.genome_size - ], - it.ontreads ?: it.hifireads, - ] - mode: it.ontreads ? "nano-hq" : "--pacbio-hifi" - } - .set { flye_inputs } - - FLYE(flye_inputs.reads, flye_inputs.mode) - - - ch_main - .branch { it -> - hifiasm: (it.strategy == "single" && it.assembler1 == "hifiasm") - || (it.strategy == "scaffold" && (it.assembler1 == "hifiasm" || it.assembler2 == "hifiasm")) - || (it.strategy == "hybrid" && it.assembler1 == "hifiasm") - hifiasm_ont: (it.strategy == "single" && it.assembler1 == "hifiasm" && it.ontreads) - flye: (it.strategy == "single" && it.assembler1 == "flye") || (it.strategy == "scaffold" && (it.assembler1 == "flye")) - } - .set { ch_main_branched } - - // Assembly flye branch - - ch_main_branched.flye - .multiMap { - it -> - reads: [ - [ - meta: it.meta, - genome_size: it.genome_size + it.meta, + it.genome_size ], it.ontreads ?: it.hifireads, ] @@ -116,6 +83,7 @@ workflow ASSEMBLE { ch_versions = ch_versions.mix(HIFIASM.out.versions).mix(GFA_2_FA_HIFI.out.versions) // Assemble hifiasm_ont branch + ch_main_branched.hifiasm_ont .map { it -> [it.meta, it.ontreads, []] } .set { hifiasm_ont_inputs } @@ -128,21 +96,27 @@ workflow ASSEMBLE { // Create a channel containing all assemblies in a "wide" format ch_main - .map { it -> [meta: it.meta] } + .map { it -> [it.meta] } .join(FLYE.out.fasta - .map { it -> [meta: it[0], flye_assembly: it[1]] }) + .map { it -> [it[0], it[1]] }) .join(GFA_2_FA_HIFI.out.contigs_fasta - .map { it -> [meta: it[0], hifiasm_hifi_assembly: it[1]]}) + .map { it -> [it[0], it[1]]}) .join(GFA_2_FA_ONT.out.contigs_fasta - .map { it -> [meta: it[0], hifiasm_ont_assembly: it[1]]}) + .map { it -> [it[0], it[1]]}) + .map { it -> [meta: it[0], flye_assembly: it[1], hifiasm_hifi_assembly: it[2], hifiasm_ont_assembly: it[3]] } .set { ch_assemblies } // Now figure out which of the wide assemblies goes into which generic assembly slot ch_main - .join { ch_assemblies } + // Turn map into list for joining + .map { it -> it.collect { entry -> [ entry.value, entry ] } } + .join { ch_assemblies + .map { it -> it.collect { entry -> [ entry.value, entry ] } } + //TODO: figure out how to go back to a map + .map { it -> it.eachWithIndex()} // The extra columns are joined and removed via submap .map { - it ->å + it -> it .subMap('flye_assembly') .subMap('hifiasm_hifi_assembly') @@ -209,18 +183,11 @@ workflow ASSEMBLE { /* Prepare alignments */ - if (params.skip_alignments) { - // Sample sheet layout when skipping assembly and mapping - // sample,ontreads,assembly,ref_fasta,ref_gff,assembly_bam,assembly_bai,ref_bam - ch_main - .map { row -> [row.meta, row.ref_bam] } - .set { ch_ref_bam } - ch_main - .map { row -> [row.meta, row.assembly_bam] } - .set { ch_assembly_bam } - } - else { + // Sample sheet layout when skipping assembly and mapping + // sample,ontreads,assembly,ref_fasta,ref_gff,assembly_map_bam,ref_map_bam + + if (!params.skip_alignments) { Channel.empty().set { ch_ref_bam } ch_main @@ -230,6 +197,7 @@ workflow ASSEMBLE { longreads: params.qc_reads == "ont" ? (it.ontreads) : (it.hifireads) ] } + .set { longreads } if (params.quast) { @@ -238,7 +206,7 @@ workflow ASSEMBLE { ch_main .join( MAP_TO_REF.out.ch_aln_to_ref_bam - .map { it -> [meta: it[0], ref_map_bam: it[1]] } + .map { it -> [meta: it.meta, ref_map_bam: it[1]] } ) .set { ch_main } } else { @@ -254,8 +222,15 @@ workflow ASSEMBLE { /* QC on initial assembly */ - QC( ch_main, - meryl_kmers) + + // scaffolds to QC need to be defined here + ch_main + .map { it -> [meta: it.meta, scaffolds: it.assembly] } + .set { scaffolds } + + + QC(ch_main, scaffolds, meryl_kmers) + ch_versions = ch_versions.mix(QC.out.versions) if (params.lift_annotations) { @@ -264,9 +239,10 @@ workflow ASSEMBLE { } emit: + ch_main + qc_reads = longreads assembly = ch_assembly ref_bam = ch_ref_bam - longreads assembly_quast_reports = QC.out.quast_out assembly_busco_reports = QC.out.busco_out assembly_merqury_reports = QC.out.merqury_report_files diff --git a/subworkflows/local/qc/main.nf b/subworkflows/local/qc/main.nf index f33baa86..fa2bc2bc 100644 --- a/subworkflows/local/qc/main.nf +++ b/subworkflows/local/qc/main.nf @@ -5,8 +5,9 @@ include { MERQURY_QC } from './merqury/main.nf' workflow QC { take: - ch_main - meryl_kmers + ch_main // pipeline main + scaffolds // scaffolds to run qc on + meryl_kmers // short-read kmers main: Channel.empty().set { ch_versions } @@ -14,11 +15,6 @@ workflow QC { Channel.empty().set { busco_out } Channel.empty().set { merqury_report_files } - ch_main - .map { it -> [meta: it.meta, scaffolds: it.assembly] } - .set { scaffolds } - - ch_main .map { it -> [ @@ -28,18 +24,18 @@ workflow QC { } .set { reads } - if (params.quast) { + if (params.quast && !params.skip_alignments) { MAP_TO_ASSEMBLY(reads, scaffolds) ch_main .join( MAP_TO_ASSEMBLY.out.aln_to_assembly_bam .map { it -> [meta: it[0], assembly_map_bam: it[1]] } ) - .set { ch_main } + .set { ch_main_assembly_mapped } ch_versions = ch_versions.mix(MAP_TO_ASSEMBLY.out.versions) } - RUN_QUAST(ch_main) + RUN_QUAST(ch_main_assembly_mapped) RUN_QUAST.out.quast_tsv.set { quast_out } ch_versions = ch_versions.mix(RUN_QUAST.out.versions) @@ -50,7 +46,14 @@ workflow QC { ch_versions = ch_versions.mix(RUN_BUSCO.out.versions) if (params.short_reads) { - MERQURY_QC(scaffolds, meryl_kmers) + scaffolds + .join(meryl_kmers) + .multiMap { it -> + scaffolds: [it[0], it[1]] + kmers: [it[0], it[2]] + } + .set { merqury_in } + MERQURY_QC(merqury_in.scaffolds, merqury_in._kmers) MERQURY_QC.out.stats .join( MERQURY_QC.out.spectra_asm_hist @@ -69,6 +72,7 @@ workflow QC { versions = ch_versions emit: + ch_main // QC does not (and should not) modify ch_main but returns the input. quast_out busco_out merqury_report_files diff --git a/workflows/genomeassembler.nf b/workflows/genomeassembler.nf index e27ddaf2..314e91c9 100644 --- a/workflows/genomeassembler.nf +++ b/workflows/genomeassembler.nf @@ -46,7 +46,7 @@ workflow GENOMEASSEMBLER { This is the "main" channel, it contains all sample-wise information. This channel should be the main input of all subworkflows, and the subworkflows should make relevant changes / updates to the map. - This channel should stay a map to allow key-based modifications in subworkflows. + This channel should stay a map (!!) to allow key-based modifications in subworkflows. The keys are defined in subworkflows/local/utils_nfcore_genomeassembler/main.nf : meta: [id: string], @@ -139,7 +139,7 @@ workflow GENOMEASSEMBLER { Assembly */ - ASSEMBLE( ch_main, meryl_kmers) + ASSEMBLE(ch_main, meryl_kmers) ASSEMBLE.out.assembly.set { ch_polished_genome } ASSEMBLE.out.ref_bam.set { ch_ref_bam } ASSEMBLE.out.longreads.set { ch_longreads } From 97d2d137f49cbf57cf768c170417532ac6e61757 Mon Sep 17 00:00:00 2001 From: Niklas Schandry Date: Fri, 4 Jul 2025 14:41:17 +0200 Subject: [PATCH 012/162] WIP commit --- subworkflows/local/assemble/main.nf | 404 +++++++++--------- subworkflows/local/jellyfish/main.nf | 15 +- subworkflows/local/liftoff/main.nf | 12 +- subworkflows/local/ont/main.nf | 26 +- subworkflows/local/polishing/main.nf | 83 ++-- .../polishing/medaka/polish_medaka/main.nf | 69 ++- .../polishing/pilon/polish_pilon/main.nf | 61 ++- .../local/polishing/pilon/run_pilon/main.nf | 12 +- subworkflows/local/prepare_hifi/main.nf | 77 +++- subworkflows/local/prepare_ont/chop/main.nf | 2 +- .../local/prepare_ont/collect/main.nf | 9 +- subworkflows/local/prepare_ont/main.nf | 79 +++- subworkflows/local/prepare_shortreads/main.nf | 72 +++- subworkflows/local/qc/busco/main.nf | 28 +- subworkflows/local/qc/main.nf | 130 ++++-- subworkflows/local/qc/quast/main.nf | 25 +- subworkflows/local/scaffolding/main.nf | 7 +- .../main.nf | 58 ++- workflows/genomeassembler.nf | 85 ++-- 19 files changed, 811 insertions(+), 443 deletions(-) diff --git a/subworkflows/local/assemble/main.nf b/subworkflows/local/assemble/main.nf index 4d37c1a1..3f72fb1f 100644 --- a/subworkflows/local/assemble/main.nf +++ b/subworkflows/local/assemble/main.nf @@ -16,216 +16,227 @@ workflow ASSEMBLE { main: // Empty channels - Channel.empty().set { ch_refs } Channel.empty().set { flye_inputs } Channel.empty().set { hifiasm_inputs } - Channel.empty().set { longreads } Channel.empty().set { ch_versions } - if (params.use_ref) { - ch_main - ch_main - .map { row -> [row.meta, row.ref_fasta] } - .set { ch_refs } - } - - if (params.skip_assembly) { - // Sample sheet layout when skipping assembly - // sample,ontreads,assembly,ref_fasta,ref_gff - ch_main - ch_main - .map { row -> [row.meta, row.assembly] } - .set { ch_assembly } - } - if (!params.skip_assembly) { - - ch_main - .branch { it -> - hifiasm: ((it.strategy == "single" && it.assembler1 == "hifiasm") - || (it.strategy == "scaffold" && (it.assembler1 == "hifiasm" || it.assembler2 == "hifiasm")) - || (it.strategy == "hybrid" && it.assembler1 == "hifiasm")) ? it : null - hifiasm_ont: (it.strategy == "single" && it.assembler1 == "hifiasm" && it.ontreads) ? it : null - flye: ((it.strategy == "single" && it.assembler1 == "flye") || (it.strategy == "scaffold" && (it.assembler1 == "flye"))) ? it : null - } - .set { ch_main_branched } + ch_main + .branch { + it -> + to_assemble: !it.assembly + no_assemble: it.assembly + } + .set { + ch_main_branched + } + + ch_main_branched + .to_assemble + .branch { it -> + hifiasm: ((it.strategy == "single" && it.assembler1 == "hifiasm") + || (it.strategy == "scaffold" && (it.assembler1 == "hifiasm" || it.assembler2 == "hifiasm")) + || (it.strategy == "hybrid" && it.assembler1 == "hifiasm")) + hifiasm_ont: (it.strategy == "single" && it.assembler1 == "hifiasm" && it.ontreads) + flye: ((it.strategy == "single" && it.assembler1 == "flye") || (it.strategy == "scaffold" && (it.assembler1 == "flye"))) + } + .set { ch_main_assemble } // Assembly flye branch - ch_main_branched.flye - .multiMap { - it -> - reads: [ + ch_main_assemble + .flye + .multiMap { + it -> + reads: [ + [ + it.meta, + it.genome_size + ], + it.ontreads ?: it.hifireads, + ] + mode: it.flye_mode ?: it.ontreads ? "nano-hq" : "--pacbio-hifi" + } + .set { flye_inputs } + FLYE(flye_inputs.reads, flye_inputs.mode) + ch_versions = ch_versions.mix(FLYE.out.versions) + // Assembly hifiasm branch + ch_main_assemble.hifiasm + .map { it -> [ it.meta, it.hifireads, it.ontreads ?: [] ] } + .set { hifiasm_inputs } + HIFIASM(hifiasm_inputs, [[], [], []], [[], [], []], [[], []]) + GFA_2_FA_HIFI(HIFIASM.out.processed_unitigs) + ch_versions = ch_versions.mix(HIFIASM.out.versions).mix(GFA_2_FA_HIFI.out.versions) + // Assemble hifiasm_ont branch + ch_main_assemble.hifiasm_ont + .map { it -> [it.meta, it.ontreads, []] } + .set { hifiasm_ont_inputs } + HIFIASM_ONT(hifiasm_ont_inputs, [[], [], []], [[], [], []], [[], []]) + GFA_2_FA_ONT(HIFIASM_ONT.out.processed_unitigs) + ch_versions = ch_versions.mix(HIFIASM_ONT.out.versions).mix(GFA_2_FA_ONT.out.versions) + // Create a channel containing all assemblies in a "wide" format + ch_main_branched.to_assemble + .map { it -> [it.meta] } + .join(FLYE.out.fasta) + .join(GFA_2_FA_HIFI.out.contigs_fasta) + .join(GFA_2_FA_ONT.out.contigs_fasta) + .map { it -> [meta: it[0], flye_assembly: it[1], hifiasm_hifi_assembly: it[2], hifiasm_ont_assembly: it[3]] } + .set { ch_assemblies } + // Now figure out which of the wide assemblies goes into which generic assembly slot + ch_main_branched.to_assemble + // Turn map into list for joining: + // each tuple in the list contains the value(s) and the original map + // I think this should also work without the entry.value, keeping the map in the tuple + // but it would required a different collect strategy that seems more involved? + .map { it -> it.collect { entry -> [ entry.value, entry ] } } + .join { ch_assemblies + .map { it -> it.collect { entry -> [ entry.value, entry ] } } } + // After joining re-create the maps from the stored map + .map { it -> it.collect { _entry, map -> [ (map.key): map.value ] }.collectEntries() } + // The extra columns are joined and removed via submap + .map { it -> + it - it.subMap('flye_assembly', 'hifiasm_hifi_assembly', 'hifiasm_ont_assembly') + [ - it.meta, - it.genome_size - ], - it.ontreads ?: it.hifireads, - ] - mode: it.ontreads ? "nano-hq" : "--pacbio-hifi" + assembly: it.strategy == "single" || it.strategy == "hybrid" ? + (it.flye_assembly ?: + it.hifiasm_hifi_assembly ?: + it.hifiasm_ont_assembly) : + null, + // remaining case is "scaffold" + // by definition assembly1 == ONT in "scaffold" + assembly1: it.strategy != "scaffold" ? + null : + it.assembler1 == "flye" ? + (it.flye_assembly) : + (it.hifiasm_ont_assembly), + // assembly2 only exists if the strategy is "scaffold" + assembly2: it.strategy != "scaffold" ? + null : + // by definition assembly2 == hifi in "scaffold" + it.assembler2 == "flye" ? + (it.flye_assembly) : + (it.hifiasm_hifi_assembly) + ] } - .set { flye_inputs } + // This should return the to_assemble branch + .set { ch_main_assembled } - FLYE(flye_inputs.reads, flye_inputs.mode) - - ch_versions = ch_versions.mix(FLYE.out.versions) - - // Assembly hifiasm branch - - ch_main_branched.hifiasm - .map { it -> [ it.meta, it.hifireads, it.ontreads ?: [] ] } - .set { hifiasm_inputs } - - - HIFIASM(hifiasm_inputs, [[], [], []], [[], [], []], [[], []]) - - GFA_2_FA_HIFI(HIFIASM.out.processed_unitigs) - - ch_versions = ch_versions.mix(HIFIASM.out.versions).mix(GFA_2_FA_HIFI.out.versions) - - // Assemble hifiasm_ont branch - - ch_main_branched.hifiasm_ont - .map { it -> [it.meta, it.ontreads, []] } - .set { hifiasm_ont_inputs } - - HIFIASM_ONT(hifiasm_ont_inputs, [[], [], []], [[], [], []], [[], []]) - - GFA_2_FA_ONT(HIFIASM_ONT.out.processed_unitigs) + // branch to scaffold those assemblies that need it + ch_main_assembled + .branch { it -> + scaffold: it.strategy == "scaffold" + no_scaffold: it.strategy != "scaffold" + } + .set { ch_assembled_branch_scaffold } - ch_versions = ch_versions.mix(HIFIASM_ONT.out.versions).mix(GFA_2_FA_ONT.out.versions) + ch_assembled_branch_scaffold.scaffold + .multiMap { + it -> + target: [ + it.meta, + it.assembly_scaffolding == "ont_on_hifi" ? (it.assembly1) : (it.assembly2) + ] + query: [ + it.meta, + it.assembly_scaffolding == "ont_on_hifi" ? (it.assembly2) : (it.assembly1) + ] + } + .set { ragtag_in } + + RAGTAG_PATCH(ragtag_in.target, ragtag_in.query, [[], []], [[], []] ) + + // Add the scaffolded assemblies to the scaffold branch and mix with unscaffolded branch + // recreates ch_main_assembled (unbranched) + ch_assembled_branch_scaffold + .scaffold + .map { it -> it.collect { entry -> [ entry.value, entry ] } } + .join( + RAGTAG_PATCH.out.patch_fasta + .map { it -> [meta: it[0], assembly_patched: it[1]] } + .map { it -> it.collect { entry -> [ entry.value, entry ] } } + ) + .map { it -> it.collect { _entry, map -> [ (map.key): map.value ] }.collectEntries() } + .map { it -> + it - it.subMap("assembly_patched") + + [ + assembly: it.strategy == "scaffold" ? + (it.assembly_patched) : + (it.assembly) + ] + } + .mix(ch_assembled_branch_scaffold.no_scaffold) + .set { ch_main_assembled } - // Create a channel containing all assemblies in a "wide" format - ch_main - .map { it -> [it.meta] } - .join(FLYE.out.fasta - .map { it -> [it[0], it[1]] }) - .join(GFA_2_FA_HIFI.out.contigs_fasta - .map { it -> [it[0], it[1]]}) - .join(GFA_2_FA_ONT.out.contigs_fasta - .map { it -> [it[0], it[1]]}) - .map { it -> [meta: it[0], flye_assembly: it[1], hifiasm_hifi_assembly: it[2], hifiasm_ont_assembly: it[3]] } - .set { ch_assemblies } - // Now figure out which of the wide assemblies goes into which generic assembly slot - ch_main - // Turn map into list for joining - .map { it -> it.collect { entry -> [ entry.value, entry ] } } - .join { ch_assemblies - .map { it -> it.collect { entry -> [ entry.value, entry ] } } - //TODO: figure out how to go back to a map - .map { it -> it.eachWithIndex()} - // The extra columns are joined and removed via submap - .map { - it -> - it - .subMap('flye_assembly') - .subMap('hifiasm_hifi_assembly') - .subMap('hifiasm_ont_assembly') + - [ - assembly: it.strategy == "single" || it.strategy == "hybrid" ? - (it.flye_assembly ?: - it.hifiasm_hifi_assembly ?: - it.hifiasm_ont_assembly) : - null, - // remaining case is "scaffold" - // by definition assembly1 == ONT in "scaffold" - assembly1: it.strategy != "scaffold" ? - null : - it.assembler1 == "flye" ? - (it.flye_assembly) : - (it.hifiasm_ont_assembly), - // assembly2 only exists if the strategy is "scaffold" - assembly2: it.strategy != "scaffold" ? - null : - // by definition assembly2 == hifi in "scaffold" - it.assembler2 == "flye" ? - (it.flye_assembly) : - (it.hifiasm_hifi_assembly) - ] - } - // This should return the unbranched main channel - .set { ch_main } - - ch_main - .filter { it -> - it.strategy == "scaffold" - } - .multiMap { - it -> - target: [ - it.meta, - it.assembly_scaffolding == "ont_on_hifi" ? (it.assembly1) : (it.assembly2) - ] - query: [ - it.meta, - it.assembly_scaffolding == "ont_on_hifi" ? (it.assembly2) : (it.assembly1) - ] - } - .set { ragtag_in } - - RAGTAG_PATCH(ragtag_in.target, ragtag_in.query, [[], []], [[], []] ) - - ch_versions = ch_versions.mix(RAGTAG_PATCH.out.versions) - - ch_main - .join( - RAGTAG_PATCH.out.patch_fasta - .map { it -> [meta: it[0], assembly_patched: it[1]] } - ) - .map { it -> - it.subMap("assembly_patched") + - [ - assembly: it.strategy == "scaffold" ? - (it.assembly_patched) : - (it.assembly) - ]} - } + ch_versions = ch_versions.mix(RAGTAG_PATCH.out.versions) /* Prepare alignments */ - // Sample sheet layout when skipping assembly and mapping - // sample,ontreads,assembly,ref_fasta,ref_gff,assembly_map_bam,ref_map_bam + ch_main_branched.no_assemble + .mix( ch_main_assembled ) + .set { ch_main } - if (!params.skip_alignments) { - Channel.empty().set { ch_ref_bam } + ch_main + .branch { + it -> + quast: it.quast + no_quast: !it.quast + } + // Note that this channel is set here but the quast branch is further used + .tap { ch_main_quast_branch } + .quast + .branch { + it -> + use_ref: it.use_ref + no_use_ref: !it.use_ref + } + .set { + ch_quast_branched + } - ch_main - .map { it -> - [ - meta: it.meta, - longreads: params.qc_reads == "ont" ? (it.ontreads) : (it.hifireads) - ] - } - .set { longreads } - - - if (params.quast) { - if (params.use_ref) { - MAP_TO_REF(longreads, ch_refs) - ch_main - .join( - MAP_TO_REF.out.ch_aln_to_ref_bam - .map { it -> [meta: it.meta, ref_map_bam: it[1]] } - ) - .set { ch_main } - } else { - ch_main - .join( - ch_main - .map { it -> [meta: it.meta, ref_map_bam: []] } - ) - .set { ch_main } - } + ch_quast_branched + .use_ref + .branch { it -> + to_map: !it.ref_map_bam + dont_map: it.ref_map_bam } - } + .set { ch_ref_mapping_branched } + + ch_ref_mapping_branched + .to_map + .multiMap { + it -> + reads: [it.meta, it.qc_reads] + ref: [it.meta, it.ref_fasta] + } + .set {map_to_ref_in} + + MAP_TO_REF(map_to_ref_in.reads, map_to_ref_in.ref) + + ch_quast_branched + .use_ref + .to_map + .map { it -> it - it.subMap["ref_map_bam"] } + .map { it -> it.collect { entry -> [ entry.value, entry ] } } + .join( + MAP_TO_REF.out.ch_aln_to_ref_bam + .map { it -> [meta: it[0], ref_map_bam: it[1]] } + .map { it -> it.collect { entry -> [ entry.value, entry ] } } + ) + .map { it -> it.collect { _entry, map -> [ (map.key): map.value ] }.collectEntries() } + .mix(ch_quast_branched.use_ref.dont_map) + .mix(ch_quast_branched.no_use_ref) + // above recreates ch_main_quast_branch.quast + .mix(ch_main_quast_branch.no_quast) + .set { ch_main } + /* QC on initial assembly */ // scaffolds to QC need to be defined here ch_main - .map { it -> [meta: it.meta, scaffolds: it.assembly] } + .map { it -> [it.meta, it.assembly] } .set { scaffolds } @@ -233,16 +244,27 @@ workflow ASSEMBLE { ch_versions = ch_versions.mix(QC.out.versions) - if (params.lift_annotations) { - RUN_LIFTOFF(ch_main) - ch_versions = ch_versions.mix(RUN_LIFTOFF.out.versions) - } + ch_main + .filter { + it -> it.lift_annotations + } + .map { it -> + [ + it.meta, + it.assembly, + it.ref_fasta, + it.ref_gff + ] + } + .set { liftoff_in } + + RUN_LIFTOFF(liftoff_in) + + ch_versions = ch_versions.mix(RUN_LIFTOFF.out.versions) + emit: ch_main - qc_reads = longreads - assembly = ch_assembly - ref_bam = ch_ref_bam assembly_quast_reports = QC.out.quast_out assembly_busco_reports = QC.out.busco_out assembly_merqury_reports = QC.out.merqury_report_files diff --git a/subworkflows/local/jellyfish/main.nf b/subworkflows/local/jellyfish/main.nf index 3ca19b5d..7ef247eb 100644 --- a/subworkflows/local/jellyfish/main.nf +++ b/subworkflows/local/jellyfish/main.nf @@ -15,8 +15,8 @@ workflow JELLYFISH { inputs.map { it -> [ - meta: it.meta, - reads: it.ontreads + it.meta, + it.ontreads ] } .set { samples } @@ -71,7 +71,10 @@ workflow JELLYFISH { inputs .map { - it -> it.subMap('genome_size') + it -> it - it.subMap('genome_size') + } + .map { + it -> it.collect { entry -> [ entry.value, entry ] } } .join( GENOMESCOPE.out.estimated_hap_len @@ -82,7 +85,11 @@ workflow JELLYFISH { genome_size: it[1] ] } + .map { + it -> it.collect { entry -> [ entry.value, entry ] } + } ) + .map { it -> it.collect { _entry, map -> [ (map.key): map.value ] }.collectEntries() } .set { outputs } GENOMESCOPE.out.summary.set { genomescope_summary } @@ -92,7 +99,7 @@ workflow JELLYFISH { versions = ch_versions emit: - outputs + main_out = outputs genomescope_summary genomescope_plot versions diff --git a/subworkflows/local/liftoff/main.nf b/subworkflows/local/liftoff/main.nf index 56e6e6b4..8e04024d 100644 --- a/subworkflows/local/liftoff/main.nf +++ b/subworkflows/local/liftoff/main.nf @@ -2,20 +2,10 @@ include { LIFTOFF } from '../../../modules/nf-core/liftoff/main' workflow RUN_LIFTOFF { take: - ch_main + liftoff_in main: Channel.empty().set { ch_versions } - ch_main - .map { it -> - [ - it.meta, - it.assembly, - it.ref_fasta, - it.ref_gff - ] - } - .set { liftoff_in } LIFTOFF(liftoff_in, []) diff --git a/subworkflows/local/ont/main.nf b/subworkflows/local/ont/main.nf index 7573f60f..bcd2f6ac 100644 --- a/subworkflows/local/ont/main.nf +++ b/subworkflows/local/ont/main.nf @@ -13,7 +13,7 @@ workflow ONT { PREPARE_ONT(main_in) - PREPARE_ONT.out.trimmed.set { main_out } + PREPARE_ONT.out.main_out.set { ch_main } PREPARE_ONT.out.nanoq_report.set { nanoq_report } @@ -21,13 +21,23 @@ workflow ONT { ch_versions = ch_versions.mix(PREPARE_ONT.out.versions) - if (params.jellyfish) { - JELLYFISH(PREPARE_ONT.out.trimmed, PREPARE_ONT.out.med_len) - JELLYFISH.out.outputs.set { output_channel } - JELLYFISH.out.genomescope_summary.set { genomescope_summary } - JELLYFISH.out.genomescope_plot.set { genomescope_plot } - ch_versions = ch_versions.mix(JELLYFISH.out.versions) - } + ch_main + .branch { + it -> + jellyfish: it.ont_jellyfish + no_jelly: !it.ont_jellyfish + } + .set { ch_main_jellyfish_branched } + + JELLYFISH(ch_main_jellyfish_branched.jellyfish, PREPARE_ONT.out.med_len) + + ch_main_jellyfish_branched.no_jelly + .mix( JELLYFISH.out.outputs ) + .set { main_out } + + JELLYFISH.out.genomescope_summary.set { genomescope_summary } + JELLYFISH.out.genomescope_plot.set { genomescope_plot } + ch_versions = ch_versions.mix(JELLYFISH.out.versions) versions = ch_versions diff --git a/subworkflows/local/polishing/main.nf b/subworkflows/local/polishing/main.nf index 1c5a4dd9..e931f158 100644 --- a/subworkflows/local/polishing/main.nf +++ b/subworkflows/local/polishing/main.nf @@ -3,12 +3,7 @@ include { POLISH_PILON } from './pilon/polish_pilon/main' workflow POLISH { take: - inputs - ch_ont_reads - ch_longreads - ch_shortreads - ch_polished_genome - reference_bam + ch_main meryl_kmers main: @@ -18,62 +13,68 @@ workflow POLISH { Channel.empty().set { polish_quast_reports } Channel.empty().set { polish_merqury_reports } - if (params.polish_medaka) { - - if (params.hifiasm_ont) { - error('Medaka should not be used on ONT-HiFi hybrid assemblies') - } - if (params.hifi && !params.ont) { - error('Medaka should not be used on HiFi assemblies') + ch_main + .branch { it -> + medaka: it.polish_medaka + no_medaka: !it.polish_medaka } + .set { ch_main } - POLISH_MEDAKA(inputs, ch_ont_reads, ch_polished_genome, reference_bam, meryl_kmers) + POLISH_MEDAKA(ch_main.medaka, meryl_kmers) - POLISH_MEDAKA.out.polished_assembly.set { ch_polished_genome } + POLISH_MEDAKA.out.ch_main + .mix { ch_main.no_medaka } + .set { ch_main } - POLISH_MEDAKA.out.busco_out.set { polish_busco_reports } + POLISH_MEDAKA.out.busco_out.set { polish_busco_reports } - POLISH_MEDAKA.out.quast_out.set { polish_quast_reports } + POLISH_MEDAKA.out.quast_out.set { polish_quast_reports } - POLISH_MEDAKA.out.merqury_report_files.set { polish_merqury_reports } + POLISH_MEDAKA.out.merqury_report_files.set { polish_merqury_reports } - ch_versions = ch_versions.mix(POLISH_MEDAKA.out.versions) - } + ch_versions = ch_versions.mix(POLISH_MEDAKA.out.versions) /* Polishing with short reads using pilon */ - if (params.polish_pilon) { - POLISH_PILON(inputs, ch_shortreads, ch_longreads, ch_polished_genome, reference_bam, meryl_kmers) + ch_main + .branch { + it -> + pilon: it.polish_pilon + no_pilon: !it.polish_pilon + } + .set { ch_main } - POLISH_PILON.out.pilon_polished.set { ch_polished_genome } + POLISH_PILON(ch_main.polish_pilon, meryl_kmers) - polish_busco_reports - .concat( - POLISH_PILON.out.busco_out - ) - .set { polish_busco_reports } + ch_main.no_pilon.mix(POLISH_PILON.out.ch_main) + .set { ch_main } - polish_quast_reports - .concat( - POLISH_PILON.out.quast_out - ) - .set { polish_quast_reports } + polish_busco_reports + .concat( + POLISH_PILON.out.busco_out + ) + .set { polish_busco_reports } - polish_merqury_reports - .concat( - POLISH_PILON.out.merqury_report_files - ) - .set { polish_merqury_reports } + polish_quast_reports + .concat( + POLISH_PILON.out.quast_out + ) + .set { polish_quast_reports } + + polish_merqury_reports + .concat( + POLISH_PILON.out.merqury_report_files + ) + .set { polish_merqury_reports } - ch_versions = ch_versions.mix(POLISH_PILON.out.versions) - } + ch_versions = ch_versions.mix(POLISH_PILON.out.versions) versions = ch_versions emit: - ch_polished_genome + ch_main polish_busco_reports polish_quast_reports polish_merqury_reports diff --git a/subworkflows/local/polishing/medaka/polish_medaka/main.nf b/subworkflows/local/polishing/medaka/polish_medaka/main.nf index e4d459fa..fdef01f1 100644 --- a/subworkflows/local/polishing/medaka/polish_medaka/main.nf +++ b/subworkflows/local/polishing/medaka/polish_medaka/main.nf @@ -4,36 +4,77 @@ include { RUN_LIFTOFF } from '../../../liftoff/main' workflow POLISH_MEDAKA { take: - ch_input - in_reads - assembly - ch_aln_to_ref + ch_main meryl_kmers main: Channel.empty().set { ch_versions } - Channel.empty().set { quast_out } - Channel.empty().set { busco_out } - Channel.empty().set { merqury_report_files } - RUN_MEDAKA(in_reads, assembly) + ch_main + .filter { + it -> it.polish.medaka + } + .multiMap { + it -> + reads: [it.meta, it.ontreads] + reference: [it.meta, it.assembly] + } + .set { ch_medaka_in } + + RUN_MEDAKA(ch_medaka_in.reads, ch_medaka_in.reference) + RUN_MEDAKA.out.medaka_out.set { polished_assembly } + polished_assembly + .map { it -> [meta: it[0], polished_medaka: it[1]]} + + ch_main + .map { it -> it.collect { entry -> [ entry.value, entry ] } } + .join { polished_assembly + .map { it -> it.collect { entry -> [ entry.value, entry ] } } } + // After joining re-create the maps from the stored map + .map { it -> it.collect { _entry, map -> [ (map.key): map.value ] }.collectEntries() } + .map { it -> it - it.subMap["polished_medaka"] + [polished: [medaka: it.polished.medaka ]]} + .set { ch_medaka_out } + + ch_main + .filter { it -> !it.polish_medaka } + .mix(ch_medaka_out) + .set { ch_main } + ch_versions = ch_versions.mix(RUN_MEDAKA.out.versions) - QC(ch_input, in_reads, polished_assembly, ch_aln_to_ref, meryl_kmers) + QC( + ch_medaka_out.map { it -> it - it.subMap["assembly_map_bam"] }, + polished_assembly, + meryl_kmers + ) + ch_versions = ch_versions.mix(QC.out.versions) - if (params.lift_annotations) { - RUN_LIFTOFF(polished_assembly, ch_input) - ch_versions = ch_versions.mix(RUN_LIFTOFF.out.versions) - } + ch_medaka_out + .filter { + it -> it.lift_annotations + } + .map { it -> + [ + it.meta, + it.polished.medaka, + it.ref_fasta, + it.ref_gff + ] + } + .set { liftoff_in } + + RUN_LIFTOFF(liftoff_in) + + ch_versions = ch_versions.mix(RUN_LIFTOFF.out.versions) versions = ch_versions emit: - polished_assembly + ch_main quast_out = QC.out.quast_out busco_out = QC.out.busco_out merqury_report_files = QC.out.merqury_report_files diff --git a/subworkflows/local/polishing/pilon/polish_pilon/main.nf b/subworkflows/local/polishing/pilon/polish_pilon/main.nf index 3b7df47a..5cf6e73e 100644 --- a/subworkflows/local/polishing/pilon/polish_pilon/main.nf +++ b/subworkflows/local/polishing/pilon/polish_pilon/main.nf @@ -5,39 +5,72 @@ include { QC } from '../../../qc/main.nf' workflow POLISH_PILON { take: - ch_input - shortreads - in_reads - assembly - ch_aln_to_ref + ch_main meryl_kmers main: Channel.empty().set { ch_versions } - MAP_SR(shortreads, assembly) + ch_main.branch { + it -> + shortreads: [it.meta, it.shortreads] + assembly: [ + it.meta, + it.polish == "medaka+pilon" ? it.polished.medaka : it.assembly + ] + } + .set { map_sr_in } + + MAP_SR(map_sr_in.shortreads, map_sr_in.assembly) ch_versions = ch_versions.mix(MAP_SR.out.versions) - RUN_PILON(assembly, MAP_SR.out.aln_to_assembly_bam_bai) + RUN_PILON(map_sr_in.assembly, MAP_SR.out.aln_to_assembly_bam_bai) + + RUN_PILON.out.improved_assembly + .set { pilon_polished } - RUN_PILON.out.improved_assembly.set { pilon_polished } + ch_main + .map { it -> it - it.subMap("polished") + [polished_medaka: it.polished.medaka ?: null] } + .map { it -> it.collect { entry -> [ entry.value, entry ] } } + .join(pilon_polished + .map { it -> [ meta: it[0], polished_pilon: it[1] ] } + .map { it -> it.collect { entry -> [ entry.value, entry ] } } + ) + .map { it -> it.collect { _entry, map -> [ (map.key): map.value ] }.collectEntries() } + .map { it -> (it.polish_medaka ? + (it - it.subMap("polished_medaka", "polished_pilon")) : + (it - it.subMap("polished_pilon"))) + + [polished: [medaka: it.polished_medaka, pilon: it.polished_pilon]] + } + .set { ch_main } ch_versions = ch_versions.mix(RUN_PILON.out.versions) - QC(ch_input, in_reads, pilon_polished, ch_aln_to_ref, meryl_kmers) + QC(ch_main.map { it -> it - it.submap["assembly_map_bam"]}, pilon_polished, meryl_kmers) ch_versions = ch_versions.mix(QC.out.versions) - if (params.lift_annotations) { - RUN_LIFTOFF(pilon_polished, ch_input) - ch_versions = ch_versions.mix(RUN_LIFTOFF.out.versions) - } + ch_main + .filter { + it -> it.lift_annotations + } + .map { it -> + [ + it.meta, + it.polished.pilon, + it.ref_fasta, + it.ref_gff + ] + } + .set { liftoff_in } + + RUN_LIFTOFF(liftoff_in) versions = ch_versions emit: - pilon_polished + ch_main quast_out = QC.out.quast_out busco_out = QC.out.busco_out merqury_report_files = QC.out.merqury_report_files diff --git a/subworkflows/local/polishing/pilon/run_pilon/main.nf b/subworkflows/local/polishing/pilon/run_pilon/main.nf index e2c29efb..762d55ad 100644 --- a/subworkflows/local/polishing/pilon/run_pilon/main.nf +++ b/subworkflows/local/polishing/pilon/run_pilon/main.nf @@ -6,15 +6,23 @@ workflow RUN_PILON { aln_to_assembly_bam_bai main: + assembly_in .join(aln_to_assembly_bam_bai) + .multiMap { + meta, assembly, bam, bai -> + assembly: [meta, assembly] + bam_bai: [meta, bam, bai] + } .set { pilon_in } + PILON( - pilon_in.map { meta, assembly, _bam, _bai -> [meta, assembly] }, - pilon_in.map { meta, _assembly, bam, bai -> [meta, bam, bai] }, + pilon_in.assembly, + pilon_in.bam_bai, "bam", ) versions = PILON.out.versions + improved_assembly = PILON.out.improved_assembly emit: diff --git a/subworkflows/local/prepare_hifi/main.nf b/subworkflows/local/prepare_hifi/main.nf index af509b5f..93ef1605 100644 --- a/subworkflows/local/prepare_hifi/main.nf +++ b/subworkflows/local/prepare_hifi/main.nf @@ -7,31 +7,64 @@ workflow PREPARE_HIFI { main: Channel.empty().set { ch_versions } + main_in - .map { it -> [it.meta, it.hifireads] } - .set { hifireads } + .branch { + hifi: it.hifireads + no_hifi: !it.hifireads + } + .set {ch_main_hifi_branched } + - if (params.lima) { - if (!params.pacbio_primers) { - error('Trimming with lima requires a file containing primers (--pacbio_primers)') + ch_main_hifi_branched + .hifi + .branch { + lima: it.hifi_trim + no_lima: !it.hifi_trim } - LIMA(hifireads, params.pacbio_primers) - TO_FASTQ(LIMA.out.bam, false) - main_in - .map { it -> it.subMap('hifireads') } - .join( - TO_FASTQ.out.fastq.map { - it -> - [ - meta: it[0], - hifireads: it[1] - ] - } - ) - .set { main_out } - ch_versions.mix(LIMA.out.versions).mix(TO_FASTQ.out.versions) - } - versions = ch_versions + .set { ch_hifi_trim_branched } + + + // lima channel goes through lima and to_fastq + ch_hifi_trim_branched + .lima + .multiMap { + it -> + reads: [it.meta, it.hifireads] + primers: [it.meta, it.hifi_primers] + } + .set { ch_lima_in } + + LIMA(ch_lima_in.reads, ch_lima_in.primers ) + TO_FASTQ(LIMA.out.bam, false) + + // no_lima is mixed with lima outputs + ch_hifi_trim_branched + .no_lima + .mix( + // lima inputs are joined to lima outputs + ch_hifi_trim_branched + .lima + .map { it -> it - it.subMap('hifireads') } + .map { it -> it.collect { entry -> [ entry.value, entry ] } } + .join( + TO_FASTQ.out.fastq + .map { + it -> + [ + meta: it[0], + hifireads: it[1] + ] + } + .map { it -> it.collect { entry -> [ entry.value, entry ] } } + ) + .map { it -> it.collect { _entry, map -> [ (map.key): map.value ] }.collectEntries() } + ) + // this contains all hifi samples, mix back with no_hifi and set to main_out + .mix(ch_main_hifi_branched.no_hifi) + .set { main_out } + + versions = ch_versions.mix(LIMA.out.versions).mix(TO_FASTQ.out.versions) emit: main_out diff --git a/subworkflows/local/prepare_ont/chop/main.nf b/subworkflows/local/prepare_ont/chop/main.nf index 632e2411..43ef40e5 100644 --- a/subworkflows/local/prepare_ont/chop/main.nf +++ b/subworkflows/local/prepare_ont/chop/main.nf @@ -20,7 +20,7 @@ workflow CHOP { PORECHOP(in_reads) input.map { it -> - it.subMap('ontreads') + it - it.subMap('ontreads') } .join( PORECHOP.out.reads diff --git a/subworkflows/local/prepare_ont/collect/main.nf b/subworkflows/local/prepare_ont/collect/main.nf index e3739242..52ecdde6 100644 --- a/subworkflows/local/prepare_ont/collect/main.nf +++ b/subworkflows/local/prepare_ont/collect/main.nf @@ -19,8 +19,13 @@ workflow COLLECT { versions = ch_versions ch_input - .map { it -> it.submap('ontreads') } - .join(reads.map { it -> [meta: it[0], ontreads:it[1]] } ) + .map { it -> it - it.submap('ontreads') } + .map { it -> it.collect { entry -> [ entry.value, entry ] } } + .join(reads + .map { it -> [meta: it[0], ontreads:it[1]] } + .map { it -> it.collect { entry -> [ entry.value, entry ] } } + ) + .map { it -> it.collect { _entry, map -> [ (map.key): map.value ] }.collectEntries() } .set { reads } emit: diff --git a/subworkflows/local/prepare_ont/main.nf b/subworkflows/local/prepare_ont/main.nf index 52c8522e..c4f22125 100644 --- a/subworkflows/local/prepare_ont/main.nf +++ b/subworkflows/local/prepare_ont/main.nf @@ -4,18 +4,85 @@ include { RUN_NANOQ } from './run_nanoq/main' workflow PREPARE_ONT { take: - inputs + ch_main main: Channel.empty().set { ch_versions } - COLLECT(inputs) + ch_main + .branch { + it -> + ont: it.ontreads != null + no_ont: !it.ontreads + } + .set { ch_ont } - CHOP(COLLECT.out.reads) + ch_ont + .branch { + it -> + to_collect: it.ont_collect + no_collect: !it.ont_collect + } + .set { ch_ont_collect_branched } - CHOP.out.chopped_reads.set { trimmed } + ch_ont_collect_branched + .to_collect + .map { it -> [it.meta, it.ontreads] } + .set { collect_in } - RUN_NANOQ(trimmed) + COLLECT(collect_in) + + COLLECT.out.reads + .map { it -> [meta: it[0], ontreads: it[1]] } + .map { it -> it.collect { entry -> [ entry.value, entry ] } } + .set { ch_collected_reads } + + ch_ont_collect_branched + .to_collect + .map { it -> it - it.subMap["ontreads"] } + .map { it -> it.collect { entry -> [ entry.value, entry ] } } + .join(ch_collected_reads) + .map { it -> it.collect { _entry, map -> [ (map.key): map.value ] }.collectEntries() } + .mix(ch_ont_collect_branched.no_collect) + .set { ch_collected } + + ch_collected + .branch { + chop: it.ont_trim + no_chop: !it.ont_trim + } + .set { ch_ont_chop_branched } + + ch_ont_chop_branched + .chop + .map { it -> [it.meta, it.ontreads]} + .set { chop_in } + + CHOP(chop_in) + + CHOP.out.chopped_reads + .map { it -> [meta: it[0], ontreads: it[1]] } + .map { it -> it.collect { entry -> [ entry.value, entry ] } } + .set { ch_chopped_reads } + + ch_ont_chop_branched + .chop + .map { it -> it - it.subMap("ontreads") } + .map { it -> it.collect { entry -> [ entry.value, entry ] } } + .join( ch_chopped_reads ) + .map { it -> it.collect { _entry, map -> [ (map.key): map.value ] }.collectEntries() } + .mix(ch_ont_chop_branched.no_chop) + .set { ch_chopped } + + ch_chopped + .map { it -> [it.meta, it.ontreads] } + .set {ch_nanoq_in} + + ch_chopped + .mix(ch_ont.no_ont) + .set { main_out } + + RUN_NANOQ(ch_nanoq_in) RUN_NANOQ.out.median_length.set { med_len } @@ -26,7 +93,7 @@ workflow PREPARE_ONT { versions = ch_versions.mix(COLLECT.out.versions).mix(CHOP.out.versions).mix(RUN_NANOQ.out.versions) emit: - trimmed + main_out med_len nanoq_report nanoq_stats diff --git a/subworkflows/local/prepare_shortreads/main.nf b/subworkflows/local/prepare_shortreads/main.nf index 4ada74fa..dd2173d2 100644 --- a/subworkflows/local/prepare_shortreads/main.nf +++ b/subworkflows/local/prepare_shortreads/main.nf @@ -10,26 +10,74 @@ workflow PREPARE_SHORTREADS { Channel.empty().set { ch_versions } main_in + .branch { + it -> + shortreads: it.shortreads_F + no_shortreads: !it.shortread_F + } + .set { main_branched } + + + main_branched + .shortreads .map { create_shortread_channel(it) } .set { shortreads } - if (params.trim_short_reads) { - TRIMGALORE(shortreads) - TRIMGALORE.out.reads.set { shortreads } - ch_versions = ch_versions.mix(TRIMGALORE.out.versions) - } - - MERYL_COUNT(shortreads.map { it -> [it[0], it[1]] }, params.meryl_k) - MERYL_UNIONSUM(MERYL_COUNT.out.meryl_db, params.meryl_k) - MERYL_UNIONSUM.out.meryl_db.set { meryl_kmers } + // use modified shortread channel - main_in + main_branched + .shortreads + .map { + it -> it - it.subMap('shortread_F', 'shortread_R', 'paired') + } .map { - it -> it.subMap('shortread_F', 'shortread_R', 'paired') + it -> it.collect { entry -> [ entry.value, entry ] } + } + .join( + shortreads + .map { it -> [meta: [id: it[0].id], shortreads: it[1]]} + .map { it -> it.collect { entry -> [ entry.value, entry ] } } + ) + .map { it -> it.collect { _entry, map -> [ (map.key): map.value ] }.collectEntries() } + .set { shortreads } + + // shortread trimming + + shortreads + .branch { + it -> + trim: it.shortreads_trim + no_trim: !it.shortreads_trim } + .set { shortreads } + + TRIMGALORE(shortreads.trim) + + // unite branched: + // add trimmed reads to trim channel, then mix with shortreads.no_trim + + shortreads.trim + .map { it -> it - it.subMap["shortreads"] } + .map { it -> it.collect { entry -> [ entry.value, entry ] } } .join( - shortreads.map { it -> [meta: [id: it[0].id], shortreads: it[1]]} + TRIMGALORE.out.reads + .map { it -> [meta: [id: it[0].id], shortreads: it[1]]} + .map { it -> it.collect { entry -> [ entry.value, entry ] } } ) + .map { it -> it.collect { _entry, map -> [ (map.key): map.value ] }.collectEntries() } + .mix( shortreads.no_trim ) + .set { shortreads } + + ch_versions = ch_versions.mix(TRIMGALORE.out.versions) + + MERYL_COUNT(shortreads.map { it -> [ it.meta, it.shortreads ] }, params.meryl_k) + MERYL_UNIONSUM(MERYL_COUNT.out.meryl_db, params.meryl_k) + MERYL_UNIONSUM.out.meryl_db.set { meryl_kmers } + + // put shortreads back together with samples without shortreads + main_branched.no_shortreads + .map { it -> it - it.subMap["shortread_F","shortread_R", "paired"] + [shorteads: null] } + .mix(shortreads) .set { main_out } versions = ch_versions.mix(MERYL_COUNT.out.versions).mix(MERYL_UNIONSUM.out.versions) diff --git a/subworkflows/local/qc/busco/main.nf b/subworkflows/local/qc/busco/main.nf index 93d93c5e..74f511b0 100644 --- a/subworkflows/local/qc/busco/main.nf +++ b/subworkflows/local/qc/busco/main.nf @@ -2,7 +2,7 @@ include { BUSCO_BUSCO as BUSCO } from '../../../../modules/nf-core/busco/busco/m workflow RUN_BUSCO { take: - assembly + ch_main main: Channel.empty().set { versions } @@ -10,13 +10,25 @@ workflow RUN_BUSCO { Channel.empty().set { short_summary_txt } Channel.empty().set { short_summary_json } - if (params.busco) { - BUSCO(assembly, 'genome', params.busco_lineage, params.busco_db ? file(params.busco_db, checkIfExists: true) : [], [], true) - BUSCO.out.batch_summary.set { batch_summary } - BUSCO.out.short_summaries_txt.set { short_summary_txt } - BUSCO.out.short_summaries_json.set { short_summary_json } - BUSCO.out.versions.set { versions } - } + ch_main + .filter { + it -> it.busco + } + .multiMap { it -> + fasta: [ + it.meta, + it.qc_target + ] + busco_lineage: it.busco_lineage + busco_db: it.busco_db ? file(it.busco_db, checkIfExists: true) : [] + } + .set { busco_in } + + BUSCO(busco_in.fasta, 'genome', busco_in.busco_lineage, busco_in.busco_db , [], true) + BUSCO.out.batch_summary.set { batch_summary } + BUSCO.out.short_summaries_txt.set { short_summary_txt } + BUSCO.out.short_summaries_json.set { short_summary_json } + BUSCO.out.versions.set { versions } emit: batch_summary diff --git a/subworkflows/local/qc/main.nf b/subworkflows/local/qc/main.nf index fa2bc2bc..5ab0553b 100644 --- a/subworkflows/local/qc/main.nf +++ b/subworkflows/local/qc/main.nf @@ -16,65 +16,105 @@ workflow QC { Channel.empty().set { merqury_report_files } ch_main - .map { it -> - [ - meta: it.meta, - longreads: params.qc_reads == "ont" ? (it.ontreads) : (it.hifireads) - ] + .branch { + it -> + shortread: it.use_short_reads + no_shortread: !it.use_short_reads } - .set { reads } - - if (params.quast && !params.skip_alignments) { - MAP_TO_ASSEMBLY(reads, scaffolds) - ch_main - .join( - MAP_TO_ASSEMBLY.out.aln_to_assembly_bam - .map { it -> [meta: it[0], assembly_map_bam: it[1]] } - ) - .set { ch_main_assembly_mapped } - ch_versions = ch_versions.mix(MAP_TO_ASSEMBLY.out.versions) - } - - RUN_QUAST(ch_main_assembly_mapped) + .set { ch_shortread_branched } + + ch_shortread_branched + .shortread + .map { it -> [it.meta] } + .join(scaffolds) + .join(meryl_kmers) + .multiMap { it -> + scaffolds: [it[0], it[1]] + kmers: [it[0], it[2]] + } + .set { merqury_in } + + MERQURY_QC(merqury_in.scaffolds, merqury_in._kmers) + + // Make sure that Polish and Scaffold main channels do not contain assembly_map_bam + + ch_main + .branch { + it -> + map_to_assembly: it.quast && !it.assembly_map_bam + no_map_to_assembly: !it.quast || (it.quast && it.assembly_map_bam) + } + .set { ch_map_branched } + + ch_map_branched + .map_to_assembly + .map { + it -> [it.meta, it.qc_reads] + } + .join(scaffolds) + .multiMap { + meta, reads, target_scaffolds -> + reads: [meta, reads] + scaffolds: [meta, target_scaffolds] + } + .set { map_assembly_in } + + MAP_TO_ASSEMBLY(map_assembly_in.reads, map_assembly_in.scaffolds) + + // create main channel with mappings + ch_map_branched + .map_to_assembly + .map { it -> it - it.subMap("assembly_map_bam") } + .map { it -> it.collect { entry -> [ entry.value, entry ] } } + .join( + MAP_TO_ASSEMBLY.out.aln_to_assembly_bam + .map { it -> [meta: it[0], assembly_map_bam: it[1]] } + .map { it -> it.collect { entry -> [ entry.value, entry ] } } + ) + .map { it -> it.collect { _entry, map -> [ (map.key): map.value ] }.collectEntries() } + .mix { ch_map_branched.no_map_to_assembly } + .map { it -> it.collect { entry -> [ entry.value, entry ] } } + .join( + scaffolds + .map { + it -> [meta: it[0], qc_target: it[1] ] + } + .map { it -> it.collect { entry -> [ entry.value, entry ] } } + ) + .map { it -> it.collect { _entry, map -> [ (map.key): map.value ] }.collectEntries() } + .set { ch_qc } + + + ch_versions = ch_versions.mix(MAP_TO_ASSEMBLY.out.versions) + + RUN_QUAST(ch_qc) RUN_QUAST.out.quast_tsv.set { quast_out } ch_versions = ch_versions.mix(RUN_QUAST.out.versions) - RUN_BUSCO(scaffolds) + RUN_BUSCO(ch_qc) RUN_BUSCO.out.batch_summary.set { busco_out } ch_versions = ch_versions.mix(RUN_BUSCO.out.versions) - if (params.short_reads) { - scaffolds - .join(meryl_kmers) - .multiMap { it -> - scaffolds: [it[0], it[1]] - kmers: [it[0], it[2]] - } - .set { merqury_in } - MERQURY_QC(merqury_in.scaffolds, merqury_in._kmers) - MERQURY_QC.out.stats - .join( - MERQURY_QC.out.spectra_asm_hist - ) - .join( - MERQURY_QC.out.spectra_cn_hist - ) - .join( - MERQURY_QC.out.assembly_qv - ) - .set { merqury_report_files } - - ch_versions = ch_versions.mix(MERQURY_QC.out.versions) - } + MERQURY_QC.out.stats + .join( + MERQURY_QC.out.spectra_asm_hist + ) + .join( + MERQURY_QC.out.spectra_cn_hist + ) + .join( + MERQURY_QC.out.assembly_qv + ) + .set { merqury_report_files } - versions = ch_versions + ch_versions = ch_versions.mix(MERQURY_QC.out.versions) emit: ch_main // QC does not (and should not) modify ch_main but returns the input. quast_out busco_out merqury_report_files - versions + versions = ch_versions } diff --git a/subworkflows/local/qc/quast/main.nf b/subworkflows/local/qc/quast/main.nf index a31dc2c4..4394f3d9 100644 --- a/subworkflows/local/qc/quast/main.nf +++ b/subworkflows/local/qc/quast/main.nf @@ -13,28 +13,29 @@ workflow RUN_QUAST { Channel.empty().set { quast_results } Channel.empty().set { quast_tsv } - if (params.quast) { - ch_main - .map { it -> - [ + ch_main + .filter { + it -> it.quast + } + .multiMap { it -> + quast_in: [ it.meta, - it.assembly, + it.qc_target, it.ref_fasta, [], it.reference_map_bam, it.assembly_map_bam ] - + use_ref: it.use_ref } - .set { quast_in } + .set { quast_in } /* * Run QUAST */ - QUAST(quast_in, params.use_ref, false) - QUAST.out.results.set { quast_results } - QUAST.out.tsv.set { quast_tsv } - QUAST.out.versions.set { versions } - } + QUAST(quast_in.quast_in, quast_in.use_ref, false) + QUAST.out.results.set { quast_results } + QUAST.out.tsv.set { quast_tsv } + QUAST.out.versions.set { versions } emit: quast_results diff --git a/subworkflows/local/scaffolding/main.nf b/subworkflows/local/scaffolding/main.nf index d816ff87..70dbbd23 100644 --- a/subworkflows/local/scaffolding/main.nf +++ b/subworkflows/local/scaffolding/main.nf @@ -4,13 +4,8 @@ include { RUN_RAGTAG } from './ragtag/main' workflow SCAFFOLD { take: - inputs - in_reads - assembly - references - ch_aln_to_ref + ch_main meryl_kmers - genome_size main: Channel.empty().set { ch_versions } diff --git a/subworkflows/local/utils_nfcore_genomeassembler_pipeline/main.nf b/subworkflows/local/utils_nfcore_genomeassembler_pipeline/main.nf index ef902e6c..fa11b0f1 100644 --- a/subworkflows/local/utils_nfcore_genomeassembler_pipeline/main.nf +++ b/subworkflows/local/utils_nfcore_genomeassembler_pipeline/main.nf @@ -101,10 +101,10 @@ workflow PIPELINE_INITIALISATION { .map { it -> [ meta: [id: it.sample], - ontreads: it.ontreads, - hifireads: it.hifireads, + ontreads: it.ontreads ?: null, + hifireads: it.hifireads ?: null, // new in refactor-assemblers - strategy: it.strategy, + strategy: it.strategy ?: params.strategy, assembler1: it.assembler1 ?: ["hifiasm","flye"].contains(params.assembler) ? params.assembler : params.assembler == "flye_on_hifiasm" ? "flye" : @@ -123,12 +123,48 @@ workflow PIPELINE_INITIALISATION { (it.assembler2 == "hifiasm") ? params.hifiasm_args : (it.assembler2 == "flye") ? params.flye_args : null, + polish: it.polish ?: + (params.polish_medaka && params.polish_pilon) ? "medaka+pilon" : + (params.polish_medaka) ? "medaka" : + (params.polish_pilon) ? "pilon" : + null, + ont_collect: it.ont_collect ?: params.collect_reads, + ont_trim: it.ont_trim ?: params.porechop, + ont_jellyfish: it.ont_jellyfish ?: params.jellyfish, + hifi_trim: it.hifi_trim ?: params.lima, + hifi_primers: it.hifi_primers ?: params.pacbio_primers, + polish_medaka: it.polish_medaka ?: params.polish_medaka, + medaka_model: it.medaka_model ?: params.medaka_model, + polish_pilon: it.polish_pilon ?: params.polish_pilon, + scaffold_longstitch: it.scaffold_longstitch ?: params.scaffold_longstitch, + scaffold_links: it.scaffold_longstitch ?: params.scaffold_links, + scaffold_ragtag: it.scaffold_longstitch ?: params.scaffold_ragtag, + use_ref: it.use_ref ?: params.use_ref ?: it.ref_fasta ? true : false, + flye_mode: it.flye_mode ?: params.flye_mode, + // assembly already provided? + assembly: it.assembly ?: null, + // ref mapping provided? + ref_map_bam: it.ref_map_bam ?: null, + // assembly mapping provided + assembly_map_bam: it.assembly_map_bam ?: null, + + // reads for qc + qc_reads: it.qc_reads ?: params.qc_reads ?: "ont", + qc_reads_path: it.qc_reads == "ont" ? (it.ontreads) : (it.hifireads), + quast: it.quast ?: params.quast, + busco: it.busco ?: params.busco, + busco_lineage: it.busco_lineage ?: params.busco_lineage, + busco_db: it.busco_db ?: params.busco_db, + lift_annotations: it.lift_annotations ?: params.lift_annotations, // not new ref_fasta: it.ref_fasta ?: params.ref_fasta, ref_gff: it.ref_gff ?: params.ref_gff, shortread_F: it.shortread_F ?: params.shortread_F, shortread_R: it.shortread_R ?: params.shortread_R, - paired: it.paired ?: params.paired + paired: it.paired ?: params.paired, + // new: + use_short_reads: it.use_short_reads ?: params.short_reads ?: it.shortread_F ? true : false, + shortread_trim: it.shortread_trim ?: params.trim_short_reads ] } .set { ch_samplesheet } if (params.use_ref) { @@ -147,7 +183,15 @@ workflow PIPELINE_INITIALISATION { ch_samplesheet .map { it -> - // Check if assembler can do hybrid + // Check if primers for lima are provided + (it.hifi_trim && !it.hifi_primers) + ? + [ + println("Please confirm samplesheet: [sample: $it.meta.id]: Please provide the primers used for pacbio sequencing to trim with lima."), + "invalid" + ] + : null + // Check if reads and strategy match (it.strategy == "single" && it.ont_reads && it.hifi_reads) ? [ @@ -172,10 +216,10 @@ workflow PIPELINE_INITIALISATION { ] : null // Check if genome_size is given with --scaffold_longstitch - (params.scaffold_longstitch && !it.genome_size && !(it.ont_reads && params.jellyfish)) + (it.scaffold_longstitch && !it.genome_size && !(it.ont_reads && params.jellyfish)) ? [ - println("Please confirm samplesheet: [sample: $it.meta.id]: --scaffold_longstitch requires genome-size. Either provide genome-size estimate, or estimate from ONT reads with --jellyfish"), + println("Please confirm samplesheet: [sample: $it.meta.id]: scaffolding with longstitch requires genome-size. Either provide genome-size estimate, or estimate from ONT reads with --jellyfish"), "invalid" ] : null diff --git a/workflows/genomeassembler.nf b/workflows/genomeassembler.nf index 314e91c9..f79a176b 100644 --- a/workflows/genomeassembler.nf +++ b/workflows/genomeassembler.nf @@ -91,65 +91,76 @@ workflow GENOMEASSEMBLER { /* Short reads */ - if (params.short_reads) { - PREPARE_SHORTREADS(ch_main) - PREPARE_SHORTREADS.out.main_out.set { ch_main } - // This changes ch_main shortreads_F and _R become one tuple, paired is gone. - PREPARE_SHORTREADS.out.meryl_kmers.set { meryl_kmers } - ch_versions = ch_versions.mix(PREPARE_SHORTREADS.out.versions) - } + + // adapted to sample-logic + PREPARE_SHORTREADS(ch_main) + // This changes ch_main shortreads_F and _R become one tuple, paired is gone. + PREPARE_SHORTREADS.out.main_out.set { ch_main } + PREPARE_SHORTREADS.out.meryl_kmers.set { meryl_kmers } + + ch_versions = ch_versions.mix(PREPARE_SHORTREADS.out.versions) /* ONT reads */ - if (params.ont) { - ONT(ch_main) - ONT.out.main_out.set { ch_main } + // adapted to sample-logic + ONT(ch_main) - ONT.out.nanoq_report - .concat( - ONT.out.nanoq_stats - ) - .collect { it -> it[1] } - .set { nanoq_files } - ONT.out.genomescope_summary - .concat( - ONT.out.genomescope_plot - ) - .unique() - .collect { it -> it[1] } - .set { genomescope_files } + ONT.out.main_out.set { ch_main } - ch_versions = ch_versions.mix(ONT.out.versions) - } + ONT.out.nanoq_report + .concat( + ONT.out.nanoq_stats + ) + .collect { it -> it[1] } + .set { nanoq_files } + + ONT.out.genomescope_summary + .concat( + ONT.out.genomescope_plot + ) + .unique() + .collect { it -> it[1] } + .set { genomescope_files } + + ch_versions = ch_versions.mix(ONT.out.versions) /* HIFI reads */ - if (params.hifi) { - HIFI(ch_main) - HIFI.out.main_out.set { ch_main } - ch_versions = ch_versions.mix(HIFI.out.versions) - } + // adapted to sample-logic + + HIFI(ch_main) + + HIFI.out.main_out.set { ch_main } + ch_versions = ch_versions.mix(HIFI.out.versions) /* Assembly */ - + // This pipeline is named genomeassembler, so everything goes into assemble + // even it might not actually be assembled. ASSEMBLE(ch_main, meryl_kmers) - ASSEMBLE.out.assembly.set { ch_polished_genome } - ASSEMBLE.out.ref_bam.set { ch_ref_bam } - ASSEMBLE.out.longreads.set { ch_longreads } + + ASSEMBLE.out.ch_main.set { ch_main } + ch_versions = ch_versions.mix(ASSEMBLE.out.versions) /* Polishing */ - - POLISH(ch_input, ch_ont_reads, ch_longreads, ch_shortreads, ch_polished_genome, ch_ref_bam, meryl_kmers) - POLISH.out.ch_polished_genome.set { ch_polished_genome } + ch_main + .branch { + it -> + polish: it.polish_medaka || it.polish_pilon + no_polish: !it.polish_medaka && !it.polish_pilon + } + POLISH(ch_main.polish, meryl_kmers) + ch_main.no_polish + .mix(POLISH.out.ch_main) + .set { ch_main } ch_versions = ch_versions.mix(POLISH.out.versions) From 6b1c1d75de1922be3717422c8696c467f0010730 Mon Sep 17 00:00:00 2001 From: Niklas Schandry Date: Fri, 4 Jul 2025 15:37:59 +0200 Subject: [PATCH 013/162] first attemp done --- .../polishing/medaka/polish_medaka/main.nf | 11 ++- .../polishing/pilon/polish_pilon/main.nf | 4 +- subworkflows/local/scaffolding/links/main.nf | 58 +++++++---- .../local/scaffolding/longstitch/main.nf | 60 ++++++++---- subworkflows/local/scaffolding/main.nf | 94 ++++++++++++++---- subworkflows/local/scaffolding/ragtag/main.nf | 58 ++++++----- workflows/genomeassembler.nf | 97 ++++++++++++++----- 7 files changed, 267 insertions(+), 115 deletions(-) diff --git a/subworkflows/local/polishing/medaka/polish_medaka/main.nf b/subworkflows/local/polishing/medaka/polish_medaka/main.nf index fdef01f1..a9a1f719 100644 --- a/subworkflows/local/polishing/medaka/polish_medaka/main.nf +++ b/subworkflows/local/polishing/medaka/polish_medaka/main.nf @@ -15,10 +15,10 @@ workflow POLISH_MEDAKA { it -> it.polish.medaka } .multiMap { - it -> - reads: [it.meta, it.ontreads] - reference: [it.meta, it.assembly] - } + it -> + reads: [it.meta, it.ontreads] + reference: [it.meta, it.assembly] + } .set { ch_medaka_in } RUN_MEDAKA(ch_medaka_in.reads, ch_medaka_in.reference) @@ -31,7 +31,8 @@ workflow POLISH_MEDAKA { ch_main .map { it -> it.collect { entry -> [ entry.value, entry ] } } .join { polished_assembly - .map { it -> it.collect { entry -> [ entry.value, entry ] } } } + .map { it -> it.collect { entry -> [ entry.value, entry ] } } + } // After joining re-create the maps from the stored map .map { it -> it.collect { _entry, map -> [ (map.key): map.value ] }.collectEntries() } .map { it -> it - it.subMap["polished_medaka"] + [polished: [medaka: it.polished.medaka ]]} diff --git a/subworkflows/local/polishing/pilon/polish_pilon/main.nf b/subworkflows/local/polishing/pilon/polish_pilon/main.nf index 5cf6e73e..9743802d 100644 --- a/subworkflows/local/polishing/pilon/polish_pilon/main.nf +++ b/subworkflows/local/polishing/pilon/polish_pilon/main.nf @@ -67,12 +67,12 @@ workflow POLISH_PILON { RUN_LIFTOFF(liftoff_in) - versions = ch_versions + ch_versions = ch_versions.mix(RUN_LIFTOFF.out.versions) emit: ch_main quast_out = QC.out.quast_out busco_out = QC.out.busco_out merqury_report_files = QC.out.merqury_report_files - versions + versions = ch_versions } diff --git a/subworkflows/local/scaffolding/links/main.nf b/subworkflows/local/scaffolding/links/main.nf index 4493e4c4..be524eb6 100644 --- a/subworkflows/local/scaffolding/links/main.nf +++ b/subworkflows/local/scaffolding/links/main.nf @@ -4,44 +4,60 @@ include { RUN_LIFTOFF } from '../../liftoff/main' workflow RUN_LINKS { take: - inputs - in_reads - assembly - _references - ch_aln_to_ref + ch_main meryl_kmers main: Channel.empty().set { ch_versions } - assembly - .join(in_reads) - .multiMap { meta, assembly_fa, reads -> - assembly: [meta, assembly_fa] - reads: [meta, reads] - } + ch_main + .multiMap { + assembly: [it.meta, it.polish.pilon ?: it.polish.medaka ?: it.assembly] + reads: [it.meta, it.qc_reads] + } .set { links_in } LINKS(links_in.assembly, links_in.reads) - LINKS.out.scaffolds_fasta.set { scaffolds } + LINKS.out.scaffolds_fasta + .set { scaffolds } + + ch_main + .map { it -> it.collect { entry -> [ entry.value, entry ] } } + .join( + scaffolds + .map { it -> [meta: it[0], scaffolds_links: it[1]] } + .map { it -> it.collect { entry -> [ entry.value, entry ] } } + ) + .map { it -> it.collect { _entry, map -> [ (map.key): map.value ] }.collectEntries() } + .set { ch_main } ch_versions = ch_versions.mix(LINKS.out.versions) - QC(inputs, in_reads, scaffolds, ch_aln_to_ref, meryl_kmers) + QC(ch_main.map { it -> it - it.submap["assembly_map_bam"]}, scaffolds, meryl_kmers) ch_versions = ch_versions.mix(QC.out.versions) - if (params.lift_annotations) { - RUN_LIFTOFF(scaffolds, inputs) - ch_versions = ch_versions.mix(RUN_LIFTOFF.out.versions) - } - - versions = ch_versions + ch_main + .filter { + it -> it.lift_annotations + } + .map { it -> + [ + it.meta, + it.scaffolds_links, + it.ref_fasta, + it.ref_gff + ] + } + .set { liftoff_in } + + RUN_LIFTOFF(liftoff_in) + ch_versions = ch_versions.mix(RUN_LIFTOFF.out.versions) emit: - scaffolds + ch_main quast_out = QC.out.quast_out busco_out = QC.out.busco_out merqury_report_files = QC.out.merqury_report_files - versions + versions = ch_versions } diff --git a/subworkflows/local/scaffolding/longstitch/main.nf b/subworkflows/local/scaffolding/longstitch/main.nf index 8756225d..046c3fb4 100644 --- a/subworkflows/local/scaffolding/longstitch/main.nf +++ b/subworkflows/local/scaffolding/longstitch/main.nf @@ -4,42 +4,66 @@ include { RUN_LIFTOFF } from '../../liftoff/main' workflow RUN_LONGSTITCH { take: - inputs - in_reads - assembly - _references - ch_aln_to_ref + ch_main meryl_kmers - genome_size main: Channel.empty().set { ch_versions } - assembly - .join(in_reads) - .join(genome_size) + ch_main + .map { + it -> + [ + it.meta, + it.polish.pilon ?: it.polish.medaka ?: it.assembly, + it.qc_reads, + it.genome_size + ] + } .set { longstitch_in } + LONGSTITCH(longstitch_in) - LONGSTITCH.out.ntlLinks_arks_scaffolds.set { scaffolds } + LONGSTITCH.out.ntlLinks_arks_scaffolds + .set { scaffolds } + + ch_main + .map { it -> it.collect { entry -> [ entry.value, entry ] } } + .join( + scaffolds + .map { it -> [meta: it[0], scaffolds_longstitch: it[1]] } + .map { it -> it.collect { entry -> [ entry.value, entry ] } } + ) + .map { it -> it.collect { _entry, map -> [ (map.key): map.value ] }.collectEntries() } + .set { ch_main } ch_versions = ch_versions.mix(LONGSTITCH.out.versions) - QC(inputs, in_reads, scaffolds, ch_aln_to_ref, meryl_kmers) + QC(ch_main.map { it -> it - it.submap["assembly_map_bam"]}, scaffolds, meryl_kmers) ch_versions = ch_versions.mix(QC.out.versions) - if (params.lift_annotations) { - RUN_LIFTOFF(LONGSTITCH.out.ntlLinks_arks_scaffolds, inputs) - ch_versions = ch_versions.mix(RUN_LIFTOFF.out.versions) - } + ch_main + .filter { + it -> it.lift_annotations + } + .map { it -> + [ + it.meta, + it.scaffolds_links, + it.ref_fasta, + it.ref_gff + ] + } + .set { liftoff_in } - versions = ch_versions + RUN_LIFTOFF(liftoff_in) + ch_versions = ch_versions.mix(RUN_LIFTOFF.out.versions) emit: - scaffolds + ch_main quast_out = QC.out.quast_out busco_out = QC.out.busco_out merqury_report_files = QC.out.merqury_report_files - versions + versions = ch_versions } diff --git a/subworkflows/local/scaffolding/main.nf b/subworkflows/local/scaffolding/main.nf index 70dbbd23..5196ecb6 100644 --- a/subworkflows/local/scaffolding/main.nf +++ b/subworkflows/local/scaffolding/main.nf @@ -19,32 +19,83 @@ workflow SCAFFOLD { Channel.empty().set { ragtag_quast } Channel.empty().set { ragtag_merqury } - if (params.scaffold_links) { - RUN_LINKS(inputs, in_reads, assembly, references, ch_aln_to_ref, meryl_kmers) - RUN_LINKS.out.busco_out.set { links_busco } - RUN_LINKS.out.quast_out.set { links_quast } - RUN_LINKS.out.merqury_report_files.set { links_merqury } + // There is no support for scaffolding of scaffolded scaffolds. + // But it is possible that one sample is scaffolded with different tools. + // Therefore main is filtered, instead of branched. - ch_versions = ch_versions.mix(RUN_LINKS.out.versions) - } - if (params.scaffold_longstitch) { - RUN_LONGSTITCH(inputs, in_reads, assembly, references, ch_aln_to_ref, meryl_kmers, genome_size) - RUN_LONGSTITCH.out.busco_out.set { longstitch_busco } - RUN_LONGSTITCH.out.quast_out.set { longstitch_quast } - RUN_LONGSTITCH.out.merqury_report_files.set { longstitch_merqury } + ch_main + .filter { + it -> it.scaffold_links + } + .set { links_in } + + RUN_LINKS(links_in, meryl_kmers) + + RUN_LINKS.out.ch_main + .map { it -> it.subMap("meta", "scaffolds_links")} + .map { it -> it.collect { entry -> [ entry.value, entry ] } } + .set { links_out } + + ch_main + .filter { + it -> it.scaffold_longstitch + } + .set { longstitch_in } + + RUN_LONGSTITCH(longstitch_in, meryl_kmers) + RUN_LONGSTITCH.out.ch_main + .map { it -> it.subMap("meta", "scaffolds_longstitch")} + .map { it -> it.collect { entry -> [ entry.value, entry ] } } + .set { longstitch_out } - ch_versions = ch_versions.mix(RUN_LONGSTITCH.out.versions) - } + ch_main + .filter { + it -> it.scaffold_ragag + } + .set { ragtag_in } - if (params.scaffold_ragtag) { - RUN_RAGTAG(inputs, in_reads, assembly, references, ch_aln_to_ref, meryl_kmers) - RUN_RAGTAG.out.busco_out.set { ragtag_busco } - RUN_RAGTAG.out.quast_out.set { ragtag_quast } - RUN_RAGTAG.out.merqury_report_files.set { ragtag_merqury } + RUN_RAGTAG(ragtag_in, meryl_kmers) + RUN_RAGTAG.out.ch_main + .map { it -> it.subMap("meta","scaffolds_ragtag")} + .map { it -> it.collect { entry -> [ entry.value, entry ] } } + .set { ragtag_out } - ch_versions = ch_versions.mix(RUN_RAGTAG.out.versions) - } + ch_main + .map { it -> it.collect { entry -> [ entry.value, entry ] } } + .join(links_out) + .join(longstitch_out) + .join(ragtag_out) + .map { it -> it.collect { _entry, map -> [ (map.key): map.value ] }.collectEntries() } + .map { + it -> it - it.subMap("scaffolds_links","scaffolds_longstitch", "scaffolds_ragtag") + + [ + scaffolds: [ + links: it.scaffold_links ?: null, + longstitch: it.scaffold_longstitch ?: null, + ragtag: it.scaffolds_ragtag ?: null + ] + ] + } + .set { ch_main } + + + RUN_LINKS.out.busco_out.set { links_busco } + RUN_LINKS.out.quast_out.set { links_quast } + RUN_LINKS.out.merqury_report_files.set { links_merqury } + + ch_versions = ch_versions.mix(RUN_LINKS.out.versions) + + RUN_LONGSTITCH.out.busco_out.set { longstitch_busco } + RUN_LONGSTITCH.out.quast_out.set { longstitch_quast } + RUN_LONGSTITCH.out.merqury_report_files.set { longstitch_merqury } + ch_versions = ch_versions.mix(RUN_LONGSTITCH.out.versions) + + RUN_RAGTAG.out.busco_out.set { ragtag_busco } + RUN_RAGTAG.out.quast_out.set { ragtag_quast } + RUN_RAGTAG.out.merqury_report_files.set { ragtag_merqury } + + ch_versions = ch_versions.mix(RUN_RAGTAG.out.versions) links_busco .concat(longstitch_busco) @@ -64,6 +115,7 @@ workflow SCAFFOLD { versions = ch_versions emit: + ch_main scaffold_busco_reports scaffold_quast_reports scaffold_merqury_reports diff --git a/subworkflows/local/scaffolding/ragtag/main.nf b/subworkflows/local/scaffolding/ragtag/main.nf index 518afb87..fe9cc0bd 100644 --- a/subworkflows/local/scaffolding/ragtag/main.nf +++ b/subworkflows/local/scaffolding/ragtag/main.nf @@ -5,48 +5,58 @@ include { RUN_LIFTOFF } from '../../liftoff/main' workflow RUN_RAGTAG { take: - inputs - in_reads - assembly - references - ch_aln_to_ref + ch_main meryl_kmers main: Channel.empty().set { ch_versions } - assembly - .join(references) - .multiMap { meta, assembly_fasta, reference_fasta -> - assembly: [meta, assembly_fasta] - reference: [meta, reference_fasta] + ch_main + .multiMap { it -> + assembly: [it.meta, it.polish.pilon ?: it.polish.medaka ?: it.assembly] + reference: [it.meta, it.ref_fasta] } .set { ragtag_in } RAGTAG_SCAFFOLD(ragtag_in.assembly, ragtag_in.reference, [[], []], [[], [], []]) - RAGTAG_SCAFFOLD.out.corrected_assembly.set { ragtag_scaffold_fasta } + RAGTAG_SCAFFOLD.out.corrected_assembly.set { scaffolds } - RAGTAG_SCAFFOLD.out.corrected_agp.set { ragtag_scaffold_agp } + ch_main + .map { it -> it.collect { entry -> [ entry.value, entry ] } } + .join( + scaffolds + .map { it -> [meta: it[0], scaffolds_ragtag: it[1]] } + .map { it -> it.collect { entry -> [ entry.value, entry ] } } + ) + .map { it -> it.collect { _entry, map -> [ (map.key): map.value ] }.collectEntries() } + .set { ch_main } - ch_versions = ch_versions.mix(RAGTAG_SCAFFOLD.out.versions) - - QC(inputs, in_reads, ragtag_scaffold_fasta, ch_aln_to_ref, meryl_kmers) + QC(ch_main.map { it -> it - it.submap["assembly_map_bam"]}, scaffolds, meryl_kmers) ch_versions = ch_versions.mix(QC.out.versions) - if (params.lift_annotations) { - RUN_LIFTOFF(RAGTAG_SCAFFOLD.out.corrected_assembly, inputs) - ch_versions = ch_versions.mix(RUN_LIFTOFF.out.versions) - } - - versions = ch_versions + ch_main + .filter { + it -> it.lift_annotations + } + .map { it -> + [ + it.meta, + it.scaffolds_ragtag, + it.ref_fasta, + it.ref_gff + ] + } + .set { liftoff_in } + + RUN_LIFTOFF(liftoff_in) + ch_versions = ch_versions.mix(RUN_LIFTOFF.out.versions) emit: - ragtag_scaffold_fasta - ragtag_scaffold_agp + ch_main quast_out = QC.out.quast_out busco_out = QC.out.busco_out merqury_report_files = QC.out.merqury_report_files - versions + versions = ch_versions } diff --git a/workflows/genomeassembler.nf b/workflows/genomeassembler.nf index f79a176b..bd1d4d69 100644 --- a/workflows/genomeassembler.nf +++ b/workflows/genomeassembler.nf @@ -22,7 +22,6 @@ include { ASSEMBLE } from '../subworkflows/local/assemble/main' // Polishing include { POLISH } from '../subworkflows/local/polishing/main' - // Scaffolding include { SCAFFOLD } from '../subworkflows/local/scaffolding/main' // reporting @@ -37,41 +36,73 @@ include { REPORT } from '../modules/local/report/main' workflow GENOMEASSEMBLER { take: ch_input - ch_refs main: // Initialize empty channels ch_input.set { ch_main } + /* This is the "main" channel, it contains all sample-wise information. This channel should be the main input of all subworkflows, and the subworkflows should make relevant changes / updates to the map. This channel should stay a map (!!) to allow key-based modifications in subworkflows. - The keys are defined in subworkflows/local/utils_nfcore_genomeassembler/main.nf : - - meta: [id: string], - ontreads: path, - hifireads: path, - strategy: string, - assembler1: string, - assembler2: string, - scaffolding: string, - genome_size: integer, - assembler1_args: string, - assembler2_args: string, - ref_fasta: path, - ref_gff: path, - shortread_F: path, - shortread_R: path, - paired: bool + The keys are defined in subworkflows/local/utils_nfcore_genomeassembler/main.nf + Here is a list of keys and their types that come in : + + meta: [id: string], + ontreads: path, + hifireads: path, + strategy: string, + assembler1: string, + assembler2: string, + scaffolding: string, + genome_size: integer, + assembler1_args: string, + assembler2_args: string, + ref_fasta: path, + ref_gff: path, + shortread_F: path, + shortread_R: path, + paired: bool + ont_collect: bool, + ont_trim: bool, + ont_jellyfish: bool, + hifi_trim: bool, + hifi_primers: path, + polish_medaka: bool, + medaka_model: string, + polish_pilon: bool, + scaffold_longstitch: bool, + scaffold_links: bool, + scaffold_ragtag: bool, + use_ref: bool, + flye_mode: string, + // assembly already provided? + assembly: path, + // ref mapping provided? + ref_map_bam: path, + // assembly mapping provided + assembly_map_bam: path, + // reads for qc + qc_reads: string ["ont","hifi"], + qc_reads_path: path, + quast: bool, + busco: bool, + busco_lineage: string, + busco_db: path, + lift_annotations: bool, + // short read options + shortread_F: path, + shortread_R: path, + paired: bool, + use_short_reads: bool, + shortread_trim: bool */ - Channel.empty().set { ch_ref_bam } - Channel.empty().set { ch_polished_genome } - Channel.empty().set { ch_shortreads } + Channel.empty().set { meryl_kmers } - Channel.empty().set { genome_size } Channel.empty().set { ch_versions } + // Initialize channels for QC report collection Channel .of([]) @@ -157,17 +188,35 @@ workflow GENOMEASSEMBLER { polish: it.polish_medaka || it.polish_pilon no_polish: !it.polish_medaka && !it.polish_pilon } + .set { ch_main } POLISH(ch_main.polish, meryl_kmers) + ch_main.no_polish .mix(POLISH.out.ch_main) .set { ch_main } ch_versions = ch_versions.mix(POLISH.out.versions) + ch_main + .branch { + scaffold: it.scaffold_links || it.scaffold_longstitch || it.scaffold_ragtag + no_scaffold: !it.scaffold_links && !it.scaffold_longstitch && !it.scaffold_ragtag + } + .set { + ch_main + } /* Scaffolding */ - SCAFFOLD(ch_input, ch_longreads, ch_polished_genome, ch_refs, ch_ref_bam, meryl_kmers, genome_size) + SCAFFOLD(ch_main.scaffold, meryl_kmers) + + // Recreate ch_main, even though it is not used since there are no later steps._report + + ch_main + .no_scaffold + .mix(SCAFFOLD.out.ch_main) + .set { ch_main } + ch_versions = ch_versions.mix(SCAFFOLD.out.versions) From daee6ee8951036f95aceb12600c87148c9fd9ae1 Mon Sep 17 00:00:00 2001 From: Niklas Schandry Date: Mon, 7 Jul 2025 10:26:57 +0200 Subject: [PATCH 014/162] bugfix commit 1 --- main.nf | 8 +-- nextflow.config | 6 +- subworkflows/local/assemble/main.nf | 64 +++++++++++-------- subworkflows/local/jellyfish/main.nf | 47 +++++--------- subworkflows/local/ont/main.nf | 8 +-- subworkflows/local/polishing/main.nf | 4 +- .../polishing/medaka/polish_medaka/main.nf | 4 +- subworkflows/local/prepare_ont/chop/main.nf | 35 +++------- .../local/prepare_ont/collect/main.nf | 12 ++-- subworkflows/local/prepare_ont/main.nf | 39 ++++++----- .../local/prepare_ont/run_nanoq/main.nf | 5 +- subworkflows/local/prepare_shortreads/main.nf | 27 ++------ subworkflows/local/qc/main.nf | 4 +- .../main.nf | 21 +++--- workflows/genomeassembler.nf | 55 ++++++++++------ 15 files changed, 158 insertions(+), 181 deletions(-) diff --git a/main.nf b/main.nf index 9bd1bab0..396744a2 100644 --- a/main.nf +++ b/main.nf @@ -31,7 +31,6 @@ workflow NFCORE_GENOMEASSEMBLER { take: samplesheet // channel: samplesheet read in from --input - refs main: @@ -39,8 +38,7 @@ workflow NFCORE_GENOMEASSEMBLER { // WORKFLOW: Run pipeline // GENOMEASSEMBLER ( - samplesheet, - refs + samplesheet ) } /* @@ -70,9 +68,11 @@ workflow { // // WORKFLOW: Run main workflow // + NFCORE_GENOMEASSEMBLER ( - PIPELINE_INITIALISATION.out.samplesheet, PIPELINE_INITIALISATION.out.refs + PIPELINE_INITIALISATION.out.samplesheet ) + // // SUBWORKFLOW: Run completion tasks // diff --git a/nextflow.config b/nextflow.config index a86bd8b1..b6408a19 100644 --- a/nextflow.config +++ b/nextflow.config @@ -47,7 +47,7 @@ params { skip_assembly = false // Intended for QC-oriented (re)-runs, assemblies are provided // -- ONT ont = false // ont reads available? - collect = false // collect ONT reads into a single file + collect_reads = false // collect ONT reads into a single file porechop = false // run porechop on ONT read_length = null // avg read length, can be estimated from reads // -- Jellyfish (ONT reads only) -- @@ -81,11 +81,11 @@ params { meryl_k = 21 // k for meryl merqury = true // -- QC : Busco - busco = true // run busco + busco = false // run busco busco_db = '' // path to busco db busco_lineage = "brassicales_odb10" // busco lineage // -- QC: QUAST - quast = true // run quast + quast = false // run quast qc_reads = "ONT" // if both ONT and HiFi reads are available, which should be used for QC alignments // -- SCAFFOLDING scaffold_links = false // Scaffold with LINKS diff --git a/subworkflows/local/assemble/main.nf b/subworkflows/local/assemble/main.nf index 3f72fb1f..2be25ee9 100644 --- a/subworkflows/local/assemble/main.nf +++ b/subworkflows/local/assemble/main.nf @@ -57,24 +57,36 @@ workflow ASSEMBLE { mode: it.flye_mode ?: it.ontreads ? "nano-hq" : "--pacbio-hifi" } .set { flye_inputs } + FLYE(flye_inputs.reads, flye_inputs.mode) + ch_versions = ch_versions.mix(FLYE.out.versions) + // Assembly hifiasm branch ch_main_assemble.hifiasm .map { it -> [ it.meta, it.hifireads, it.ontreads ?: [] ] } .set { hifiasm_inputs } + HIFIASM(hifiasm_inputs, [[], [], []], [[], [], []], [[], []]) + GFA_2_FA_HIFI(HIFIASM.out.processed_unitigs) + ch_versions = ch_versions.mix(HIFIASM.out.versions).mix(GFA_2_FA_HIFI.out.versions) // Assemble hifiasm_ont branch + ch_main_assemble.hifiasm_ont .map { it -> [it.meta, it.ontreads, []] } .set { hifiasm_ont_inputs } + HIFIASM_ONT(hifiasm_ont_inputs, [[], [], []], [[], [], []], [[], []]) + GFA_2_FA_ONT(HIFIASM_ONT.out.processed_unitigs) + ch_versions = ch_versions.mix(HIFIASM_ONT.out.versions).mix(GFA_2_FA_ONT.out.versions) + // Create a channel containing all assemblies in a "wide" format - ch_main_branched.to_assemble + ch_main_branched + .to_assemble .map { it -> [it.meta] } .join(FLYE.out.fasta) .join(GFA_2_FA_HIFI.out.contigs_fasta) @@ -88,8 +100,9 @@ workflow ASSEMBLE { // I think this should also work without the entry.value, keeping the map in the tuple // but it would required a different collect strategy that seems more involved? .map { it -> it.collect { entry -> [ entry.value, entry ] } } - .join { ch_assemblies - .map { it -> it.collect { entry -> [ entry.value, entry ] } } } + .join(ch_assemblies + .map { it -> it.collect { entry -> [ entry.value, entry ] } } + ) // After joining re-create the maps from the stored map .map { it -> it.collect { _entry, map -> [ (map.key): map.value ] }.collectEntries() } // The extra columns are joined and removed via submap @@ -133,11 +146,11 @@ workflow ASSEMBLE { it -> target: [ it.meta, - it.assembly_scaffolding == "ont_on_hifi" ? (it.assembly1) : (it.assembly2) + it.assembly_scaffolding_order == "ont_on_hifi" ? (it.assembly1) : (it.assembly2) ] query: [ it.meta, - it.assembly_scaffolding == "ont_on_hifi" ? (it.assembly2) : (it.assembly1) + it.assembly_scaffolding_order == "ont_on_hifi" ? (it.assembly2) : (it.assembly1) ] } .set { ragtag_in } @@ -168,27 +181,27 @@ workflow ASSEMBLE { ch_versions = ch_versions.mix(RAGTAG_PATCH.out.versions) - /* - Prepare alignments - */ - ch_main_branched.no_assemble + ch_main_branched + .no_assemble .mix( ch_main_assembled ) - .set { ch_main } + .set { ch_main_to_mapping } - ch_main + ch_main_to_mapping .branch { it -> quast: it.quast no_quast: !it.quast } // Note that this channel is set here but the quast branch is further used - .tap { ch_main_quast_branch } + .set { ch_main_quast_branch } + + ch_main_quast_branch .quast .branch { it -> - use_ref: it.use_ref - no_use_ref: !it.use_ref + use_ref: it.use_ref + no_use_ref: !it.use_ref } .set { ch_quast_branched @@ -213,8 +226,7 @@ workflow ASSEMBLE { MAP_TO_REF(map_to_ref_in.reads, map_to_ref_in.ref) - ch_quast_branched - .use_ref + ch_ref_mapping_branched .to_map .map { it -> it - it.subMap["ref_map_bam"] } .map { it -> it.collect { entry -> [ entry.value, entry ] } } @@ -224,27 +236,27 @@ workflow ASSEMBLE { .map { it -> it.collect { entry -> [ entry.value, entry ] } } ) .map { it -> it.collect { _entry, map -> [ (map.key): map.value ] }.collectEntries() } - .mix(ch_quast_branched.use_ref.dont_map) + .mix(ch_ref_mapping_branched.dont_map) .mix(ch_quast_branched.no_use_ref) // above recreates ch_main_quast_branch.quast .mix(ch_main_quast_branch.no_quast) - .set { ch_main } + .set { ch_main_to_qc } + + + //QC on initial assembly - /* - QC on initial assembly - */ // scaffolds to QC need to be defined here - ch_main + ch_main_to_qc .map { it -> [it.meta, it.assembly] } .set { scaffolds } - QC(ch_main, scaffolds, meryl_kmers) + QC(ch_main_to_qc, scaffolds, meryl_kmers) ch_versions = ch_versions.mix(QC.out.versions) - ch_main + ch_main_to_qc .filter { it -> it.lift_annotations } @@ -259,12 +271,10 @@ workflow ASSEMBLE { .set { liftoff_in } RUN_LIFTOFF(liftoff_in) - ch_versions = ch_versions.mix(RUN_LIFTOFF.out.versions) - emit: - ch_main + ch_main = ch_main_to_qc assembly_quast_reports = QC.out.quast_out assembly_busco_reports = QC.out.busco_out assembly_merqury_reports = QC.out.merqury_report_files diff --git a/subworkflows/local/jellyfish/main.nf b/subworkflows/local/jellyfish/main.nf index 7ef247eb..15061760 100644 --- a/subworkflows/local/jellyfish/main.nf +++ b/subworkflows/local/jellyfish/main.nf @@ -6,13 +6,12 @@ include { GENOMESCOPE } from '../../../modules/local/genomescope/main' workflow JELLYFISH { take: - inputs - nanoq_out + ch_main main: Channel.empty().set { genomescope_in } Channel.empty().set { ch_versions } - inputs.map { + ch_main.map { it -> [ it.meta, @@ -20,6 +19,8 @@ workflow JELLYFISH { ] } .set { samples } + + samples.view { it -> "JELLYFISH SAMPLES: $it"} COUNT(samples) COUNT.out.kmers.set { kmers } @@ -33,33 +34,19 @@ workflow JELLYFISH { HISTO(kmers) ch_versions = ch_versions.mix(HISTO.out.versions) - if (!params.read_length == null) { - HISTO.out.histo - .map { - it -> - [ - it[0], - it[1], - params.kmer_length, - params.read_length - ] - } - .set { genomescope_in } - } - - if (params.read_length == null) { - HISTO.out.histo - .map { - it -> - [ - it[0], - it[1], - params.kmer_length - ] - } - .join(nanoq_out) + HISTO.out.histo + .join( + ch_main + .map { it -> + [ + it.meta, + it.ont_jellyfish_k, + it.ont_read_length + ] + } + ) .set { genomescope_in } - } + GENOMESCOPE(genomescope_in) @@ -69,7 +56,7 @@ workflow JELLYFISH { ch_versions = ch_versions.mix(STATS.out.versions) - inputs + ch_main .map { it -> it - it.subMap('genome_size') } diff --git a/subworkflows/local/ont/main.nf b/subworkflows/local/ont/main.nf index bcd2f6ac..034e714b 100644 --- a/subworkflows/local/ont/main.nf +++ b/subworkflows/local/ont/main.nf @@ -13,7 +13,7 @@ workflow ONT { PREPARE_ONT(main_in) - PREPARE_ONT.out.main_out.set { ch_main } + PREPARE_ONT.out.main_out.set { ch_main_prepared } PREPARE_ONT.out.nanoq_report.set { nanoq_report } @@ -21,7 +21,7 @@ workflow ONT { ch_versions = ch_versions.mix(PREPARE_ONT.out.versions) - ch_main + ch_main_prepared .branch { it -> jellyfish: it.ont_jellyfish @@ -29,10 +29,10 @@ workflow ONT { } .set { ch_main_jellyfish_branched } - JELLYFISH(ch_main_jellyfish_branched.jellyfish, PREPARE_ONT.out.med_len) + JELLYFISH(ch_main_jellyfish_branched.jellyfish) ch_main_jellyfish_branched.no_jelly - .mix( JELLYFISH.out.outputs ) + .mix( JELLYFISH.out.main_out ) .set { main_out } JELLYFISH.out.genomescope_summary.set { genomescope_summary } diff --git a/subworkflows/local/polishing/main.nf b/subworkflows/local/polishing/main.nf index e931f158..73e5a8b6 100644 --- a/subworkflows/local/polishing/main.nf +++ b/subworkflows/local/polishing/main.nf @@ -23,7 +23,7 @@ workflow POLISH { POLISH_MEDAKA(ch_main.medaka, meryl_kmers) POLISH_MEDAKA.out.ch_main - .mix { ch_main.no_medaka } + .mix(ch_main.no_medaka) .set { ch_main } POLISH_MEDAKA.out.busco_out.set { polish_busco_reports } @@ -46,7 +46,7 @@ workflow POLISH { } .set { ch_main } - POLISH_PILON(ch_main.polish_pilon, meryl_kmers) + POLISH_PILON(ch_main.pilon, meryl_kmers) ch_main.no_pilon.mix(POLISH_PILON.out.ch_main) .set { ch_main } diff --git a/subworkflows/local/polishing/medaka/polish_medaka/main.nf b/subworkflows/local/polishing/medaka/polish_medaka/main.nf index a9a1f719..675ef7ec 100644 --- a/subworkflows/local/polishing/medaka/polish_medaka/main.nf +++ b/subworkflows/local/polishing/medaka/polish_medaka/main.nf @@ -30,9 +30,9 @@ workflow POLISH_MEDAKA { ch_main .map { it -> it.collect { entry -> [ entry.value, entry ] } } - .join { polished_assembly + .join( polished_assembly .map { it -> it.collect { entry -> [ entry.value, entry ] } } - } + ) // After joining re-create the maps from the stored map .map { it -> it.collect { _entry, map -> [ (map.key): map.value ] }.collectEntries() } .map { it -> it - it.subMap["polished_medaka"] + [polished: [medaka: it.polished.medaka ]]} diff --git a/subworkflows/local/prepare_ont/chop/main.nf b/subworkflows/local/prepare_ont/chop/main.nf index 43ef40e5..cf77d8d5 100644 --- a/subworkflows/local/prepare_ont/chop/main.nf +++ b/subworkflows/local/prepare_ont/chop/main.nf @@ -8,36 +8,17 @@ workflow CHOP { Channel.empty().set { chopped_reads } Channel.empty().set { ch_versions } - if (params.porechop) { - input.map { - it -> - [ - meta: it.meta, - reads: it.ontreads - ] - } - .set { in_reads } - PORECHOP(in_reads) - input.map { - it -> - it - it.subMap('ontreads') + PORECHOP(input) + + input.map { + it -> [ it[0] ] + } - .join( - PORECHOP.out.reads - .map { it -> - [ - meta: it[0], - ont_reads: it[1] - ] - } - ) + .join(PORECHOP.out.reads) .set { chopped_reads } - ch_versions.mix(PORECHOP.out.versions) - } - else { - input.set { chopped_reads } - } + ch_versions.mix(PORECHOP.out.versions) + versions = ch_versions emit: diff --git a/subworkflows/local/prepare_ont/collect/main.nf b/subworkflows/local/prepare_ont/collect/main.nf index 52ecdde6..abc597d7 100644 --- a/subworkflows/local/prepare_ont/collect/main.nf +++ b/subworkflows/local/prepare_ont/collect/main.nf @@ -8,14 +8,16 @@ workflow COLLECT { Channel.empty().set { ch_versions } ch_input + .filter { + it -> it.ont_collect + } .map { row -> [row.meta, row.ontreads] } .set { reads } - if (params.collect) { - COLLECT_READS(reads) - COLLECT_READS.out.combined_reads.set { reads } - ch_versions.mix(COLLECT_READS.out.versions) - } + COLLECT_READS(reads) + COLLECT_READS.out.combined_reads.set { reads } + ch_versions.mix(COLLECT_READS.out.versions) + versions = ch_versions ch_input diff --git a/subworkflows/local/prepare_ont/main.nf b/subworkflows/local/prepare_ont/main.nf index c4f22125..581ef905 100644 --- a/subworkflows/local/prepare_ont/main.nf +++ b/subworkflows/local/prepare_ont/main.nf @@ -18,6 +18,7 @@ workflow PREPARE_ONT { .set { ch_ont } ch_ont + .ont .branch { it -> to_collect: it.ont_collect @@ -60,41 +61,37 @@ workflow PREPARE_ONT { CHOP(chop_in) - CHOP.out.chopped_reads - .map { it -> [meta: it[0], ontreads: it[1]] } - .map { it -> it.collect { entry -> [ entry.value, entry ] } } - .set { ch_chopped_reads } + ch_ont_chop_branched.no_chop.view { it -> "UNCHOPPED: $it"} - ch_ont_chop_branched - .chop - .map { it -> it - it.subMap("ontreads") } - .map { it -> it.collect { entry -> [ entry.value, entry ] } } - .join( ch_chopped_reads ) - .map { it -> it.collect { _entry, map -> [ (map.key): map.value ] }.collectEntries() } - .mix(ch_ont_chop_branched.no_chop) - .set { ch_chopped } - - ch_chopped - .map { it -> [it.meta, it.ontreads] } + CHOP.out.chopped_reads + .mix(ch_ont_chop_branched + .no_chop + .map { it -> [meta: it.meta, ontreads: it.ontreads] } ) .set {ch_nanoq_in} - ch_chopped - .mix(ch_ont.no_ont) - .set { main_out } - RUN_NANOQ(ch_nanoq_in) - RUN_NANOQ.out.median_length.set { med_len } + RUN_NANOQ.out.median_length + .map { it -> [meta: it[0], ont_read_length: it[1]] } + .map { it -> it.collect { entry -> [ entry.value, entry ] } }.set { med_len } RUN_NANOQ.out.report.set { nanoq_report } RUN_NANOQ.out.stats.set { nanoq_stats } + ch_ont + .ont + .map { it -> it - it.subMap("ontreads", "ont_read_length") } + .map { it -> it.collect { entry -> [ entry.value, entry ] } } + .join( med_len ) + .map { it -> it.collect { _entry, map -> [ (map.key): map.value ] }.collectEntries() } + .mix(ch_ont.no_ont) + .set { main_out } + versions = ch_versions.mix(COLLECT.out.versions).mix(CHOP.out.versions).mix(RUN_NANOQ.out.versions) emit: main_out - med_len nanoq_report nanoq_stats versions diff --git a/subworkflows/local/prepare_ont/run_nanoq/main.nf b/subworkflows/local/prepare_ont/run_nanoq/main.nf index 3e6b91ad..f6273dfb 100644 --- a/subworkflows/local/prepare_ont/run_nanoq/main.nf +++ b/subworkflows/local/prepare_ont/run_nanoq/main.nf @@ -13,7 +13,10 @@ workflow RUN_NANOQ { ontreads: it.ontreads ] } - .set { in_reads } + .set { in_reads } + + in_reads.view {it -> "NANOQ INPUT: $it"} + NANOQ(in_reads) NANOQ.out.report.set { report } diff --git a/subworkflows/local/prepare_shortreads/main.nf b/subworkflows/local/prepare_shortreads/main.nf index dd2173d2..e8b3f35d 100644 --- a/subworkflows/local/prepare_shortreads/main.nf +++ b/subworkflows/local/prepare_shortreads/main.nf @@ -4,29 +4,21 @@ include { MERYL_UNIONSUM } from '../../../modules/nf-core/meryl/unionsum/main' workflow PREPARE_SHORTREADS { take: - main_in + shortreads_in main: Channel.empty().set { ch_versions } - main_in - .branch { - it -> - shortreads: it.shortreads_F - no_shortreads: !it.shortread_F - } - .set { main_branched } - + //shortreads_in.view { it -> "Shortread input: $it"} - main_branched - .shortreads + shortreads_in .map { create_shortread_channel(it) } .set { shortreads } + // use modified shortread channel - main_branched - .shortreads + shortreads_in .map { it -> it - it.subMap('shortread_F', 'shortread_R', 'paired') } @@ -42,6 +34,7 @@ workflow PREPARE_SHORTREADS { .set { shortreads } // shortread trimming + //shortreads.view { it -> "shortreads: $it" } shortreads .branch { @@ -74,16 +67,10 @@ workflow PREPARE_SHORTREADS { MERYL_UNIONSUM(MERYL_COUNT.out.meryl_db, params.meryl_k) MERYL_UNIONSUM.out.meryl_db.set { meryl_kmers } - // put shortreads back together with samples without shortreads - main_branched.no_shortreads - .map { it -> it - it.subMap["shortread_F","shortread_R", "paired"] + [shorteads: null] } - .mix(shortreads) - .set { main_out } - versions = ch_versions.mix(MERYL_COUNT.out.versions).mix(MERYL_UNIONSUM.out.versions) emit: - main_out + shortreads meryl_kmers versions } diff --git a/subworkflows/local/qc/main.nf b/subworkflows/local/qc/main.nf index 5ab0553b..a40f647c 100644 --- a/subworkflows/local/qc/main.nf +++ b/subworkflows/local/qc/main.nf @@ -34,7 +34,7 @@ workflow QC { } .set { merqury_in } - MERQURY_QC(merqury_in.scaffolds, merqury_in._kmers) + MERQURY_QC(merqury_in.scaffolds, merqury_in.kmers) // Make sure that Polish and Scaffold main channels do not contain assembly_map_bam @@ -72,7 +72,7 @@ workflow QC { .map { it -> it.collect { entry -> [ entry.value, entry ] } } ) .map { it -> it.collect { _entry, map -> [ (map.key): map.value ] }.collectEntries() } - .mix { ch_map_branched.no_map_to_assembly } + .mix(ch_map_branched.no_map_to_assembly) .map { it -> it.collect { entry -> [ entry.value, entry ] } } .join( scaffolds diff --git a/subworkflows/local/utils_nfcore_genomeassembler_pipeline/main.nf b/subworkflows/local/utils_nfcore_genomeassembler_pipeline/main.nf index fa11b0f1..8fc97ff5 100644 --- a/subworkflows/local/utils_nfcore_genomeassembler_pipeline/main.nf +++ b/subworkflows/local/utils_nfcore_genomeassembler_pipeline/main.nf @@ -95,7 +95,6 @@ workflow PIPELINE_INITIALISATION { // Create channel from input file provided through params.input // - Channel.empty().set { ch_refs } Channel.fromPath(params.input) .splitCsv(header: true) .map { it -> @@ -113,7 +112,7 @@ workflow PIPELINE_INITIALISATION { assembler2: it.assembler2 ?: ["hifiasm_on_hifiasm","flye_on_hifiasm"].contains(params.assembler) ? "hifiasm" : null, - scaffolding: it.scaffolding_order, + assembly_scaffolding_order: it.assembly_scaffolding_order, genome_size: it.genome_size ?: params.genome_size, assembler1_args: it.assembler1_args ?: (it.assembler1 == "hifiasm") ? params.hifiasm_args : @@ -130,7 +129,9 @@ workflow PIPELINE_INITIALISATION { null, ont_collect: it.ont_collect ?: params.collect_reads, ont_trim: it.ont_trim ?: params.porechop, - ont_jellyfish: it.ont_jellyfish ?: params.jellyfish, + ont_jellyfish: it.ont_jellyfish ?: (params.jellyfish && it.ontreads), + ont_jellyfish_k: it.ont_jellyfish_k ?: params.kmer_length, + ont_read_length: it.ont_read_length ?: params.read_length, hifi_trim: it.hifi_trim ?: params.lima, hifi_primers: it.hifi_primers ?: params.pacbio_primers, polish_medaka: it.polish_medaka ?: params.polish_medaka, @@ -151,8 +152,8 @@ workflow PIPELINE_INITIALISATION { // reads for qc qc_reads: it.qc_reads ?: params.qc_reads ?: "ont", qc_reads_path: it.qc_reads == "ont" ? (it.ontreads) : (it.hifireads), - quast: it.quast ?: params.quast, - busco: it.busco ?: params.busco, + quast: it.quast ?: params.quast ?: false, + busco: it.busco ?: params.busco ?: false, busco_lineage: it.busco_lineage ?: params.busco_lineage, busco_db: it.busco_db ?: params.busco_db, lift_annotations: it.lift_annotations ?: params.lift_annotations, @@ -167,11 +168,6 @@ workflow PIPELINE_INITIALISATION { shortread_trim: it.shortread_trim ?: params.trim_short_reads ] } .set { ch_samplesheet } - if (params.use_ref) { - ch_samplesheet - .map { it -> [it.meta, file(it.ref_fasta, checkIfExists: true)] } - .set { ch_refs } - } // Define valid hybrid assemblers @@ -192,7 +188,7 @@ workflow PIPELINE_INITIALISATION { ] : null // Check if reads and strategy match - (it.strategy == "single" && it.ont_reads && it.hifi_reads) + (it.strategy == "single" && it.ontreads && it.hifireads) ? [ println("Please confirm samplesheet: [sample: $it.meta.id]: Stragety is $it.strategy, but both types of reads are provided."), @@ -216,7 +212,7 @@ workflow PIPELINE_INITIALISATION { ] : null // Check if genome_size is given with --scaffold_longstitch - (it.scaffold_longstitch && !it.genome_size && !(it.ont_reads && params.jellyfish)) + (it.scaffold_longstitch && !it.genome_size && !(it.ontreads && params.jellyfish)) ? [ println("Please confirm samplesheet: [sample: $it.meta.id]: scaffolding with longstitch requires genome-size. Either provide genome-size estimate, or estimate from ONT reads with --jellyfish"), @@ -234,7 +230,6 @@ workflow PIPELINE_INITIALISATION { emit: samplesheet = ch_samplesheet - refs = ch_refs versions = ch_versions } diff --git a/workflows/genomeassembler.nf b/workflows/genomeassembler.nf index bd1d4d69..c89f4c78 100644 --- a/workflows/genomeassembler.nf +++ b/workflows/genomeassembler.nf @@ -97,10 +97,10 @@ workflow GENOMEASSEMBLER { paired: bool, use_short_reads: bool, shortread_trim: bool - */ Channel.empty().set { meryl_kmers } + Channel.empty().set { ch_versions } // Initialize channels for QC report collection @@ -113,7 +113,6 @@ workflow GENOMEASSEMBLER { .tap { busco_files } .map { it -> [it[0], it[1], it[1], it[1], it[1]] } .tap { merqury_files } - /* ============= Prepare reads @@ -122,11 +121,26 @@ workflow GENOMEASSEMBLER { /* Short reads */ + ch_main + .filter { + it -> it.shortread_F ? true : false + } + .set { shortreads } + shortreads.view(it -> "SHORTREADS: $it") // adapted to sample-logic - PREPARE_SHORTREADS(ch_main) + PREPARE_SHORTREADS(shortreads) // This changes ch_main shortreads_F and _R become one tuple, paired is gone. - PREPARE_SHORTREADS.out.main_out.set { ch_main } + + // put shortreads back together with samples without shortreads + ch_main + .filter { + it -> !it.shortread_F ? true : false + } + .map { it -> it - it.subMap["shortread_F","shortread_R", "paired"] + [shorteads: null] } + .mix(PREPARE_SHORTREADS.out.shortreads) + .set { ch_main_shortreaded } + PREPARE_SHORTREADS.out.meryl_kmers.set { meryl_kmers } ch_versions = ch_versions.mix(PREPARE_SHORTREADS.out.versions) @@ -136,9 +150,9 @@ workflow GENOMEASSEMBLER { */ // adapted to sample-logic - ONT(ch_main) + ONT(ch_main_shortreaded) - ONT.out.main_out.set { ch_main } + ONT.out.main_out.set { ch_main_onted } ONT.out.nanoq_report .concat( @@ -164,9 +178,9 @@ workflow GENOMEASSEMBLER { // adapted to sample-logic - HIFI(ch_main) + HIFI(ch_main_onted) - HIFI.out.main_out.set { ch_main } + HIFI.out.main_out.set { ch_main_prepared } ch_versions = ch_versions.mix(HIFI.out.versions) /* @@ -174,48 +188,49 @@ workflow GENOMEASSEMBLER { */ // This pipeline is named genomeassembler, so everything goes into assemble // even it might not actually be assembled. - ASSEMBLE(ch_main, meryl_kmers) + ASSEMBLE(ch_main_prepared, meryl_kmers) - ASSEMBLE.out.ch_main.set { ch_main } + ASSEMBLE.out.ch_main.set { ch_main_assembled } ch_versions = ch_versions.mix(ASSEMBLE.out.versions) /* Polishing */ - ch_main + ch_main_assembled .branch { it -> polish: it.polish_medaka || it.polish_pilon no_polish: !it.polish_medaka && !it.polish_pilon } - .set { ch_main } - POLISH(ch_main.polish, meryl_kmers) + .set { ch_main_assembled } - ch_main.no_polish + POLISH(ch_main_assembled.polish, meryl_kmers) + + ch_main_assembled.no_polish .mix(POLISH.out.ch_main) - .set { ch_main } + .set { ch_main_polished } ch_versions = ch_versions.mix(POLISH.out.versions) - ch_main + ch_main_polished .branch { scaffold: it.scaffold_links || it.scaffold_longstitch || it.scaffold_ragtag no_scaffold: !it.scaffold_links && !it.scaffold_longstitch && !it.scaffold_ragtag } .set { - ch_main + ch_main_polished } /* Scaffolding */ - SCAFFOLD(ch_main.scaffold, meryl_kmers) + SCAFFOLD(ch_main_polished.scaffold, meryl_kmers) // Recreate ch_main, even though it is not used since there are no later steps._report - ch_main + ch_main_polished .no_scaffold .mix(SCAFFOLD.out.ch_main) - .set { ch_main } + .set { ch_main_scaffolded } ch_versions = ch_versions.mix(SCAFFOLD.out.versions) From 2f95c5e4dd1239a9be66b08eb39eb497d91d3923 Mon Sep 17 00:00:00 2001 From: Niklas Schandry Date: Mon, 7 Jul 2025 11:12:58 +0200 Subject: [PATCH 015/162] bugfix commit 2 --- subworkflows/local/ont/main.nf | 3 +++ subworkflows/local/prepare_ont/main.nf | 8 +++++--- subworkflows/local/prepare_ont/run_nanoq/main.nf | 2 -- .../local/utils_nfcore_genomeassembler_pipeline/main.nf | 2 +- workflows/genomeassembler.nf | 1 - 5 files changed, 9 insertions(+), 7 deletions(-) diff --git a/subworkflows/local/ont/main.nf b/subworkflows/local/ont/main.nf index 034e714b..84035ff0 100644 --- a/subworkflows/local/ont/main.nf +++ b/subworkflows/local/ont/main.nf @@ -21,12 +21,15 @@ workflow ONT { ch_versions = ch_versions.mix(PREPARE_ONT.out.versions) + //ch_main_prepared.view { it -> "PREPARED: $it"} + ch_main_prepared .branch { it -> jellyfish: it.ont_jellyfish no_jelly: !it.ont_jellyfish } + .set { ch_main_jellyfish_branched } JELLYFISH(ch_main_jellyfish_branched.jellyfish) diff --git a/subworkflows/local/prepare_ont/main.nf b/subworkflows/local/prepare_ont/main.nf index 581ef905..4e36a5b0 100644 --- a/subworkflows/local/prepare_ont/main.nf +++ b/subworkflows/local/prepare_ont/main.nf @@ -61,8 +61,6 @@ workflow PREPARE_ONT { CHOP(chop_in) - ch_ont_chop_branched.no_chop.view { it -> "UNCHOPPED: $it"} - CHOP.out.chopped_reads .mix(ch_ont_chop_branched .no_chop @@ -73,7 +71,8 @@ workflow PREPARE_ONT { RUN_NANOQ.out.median_length .map { it -> [meta: it[0], ont_read_length: it[1]] } - .map { it -> it.collect { entry -> [ entry.value, entry ] } }.set { med_len } + .map { it -> it.collect { entry -> [ entry.value, entry ] } } + .set { med_len } RUN_NANOQ.out.report.set { nanoq_report } @@ -84,6 +83,9 @@ workflow PREPARE_ONT { .map { it -> it - it.subMap("ontreads", "ont_read_length") } .map { it -> it.collect { entry -> [ entry.value, entry ] } } .join( med_len ) + .join( ch_nanoq_in + .map { it -> it.collect { entry -> [ entry.value, entry ] } } + ) .map { it -> it.collect { _entry, map -> [ (map.key): map.value ] }.collectEntries() } .mix(ch_ont.no_ont) .set { main_out } diff --git a/subworkflows/local/prepare_ont/run_nanoq/main.nf b/subworkflows/local/prepare_ont/run_nanoq/main.nf index f6273dfb..50d5dc80 100644 --- a/subworkflows/local/prepare_ont/run_nanoq/main.nf +++ b/subworkflows/local/prepare_ont/run_nanoq/main.nf @@ -15,8 +15,6 @@ workflow RUN_NANOQ { } .set { in_reads } - in_reads.view {it -> "NANOQ INPUT: $it"} - NANOQ(in_reads) NANOQ.out.report.set { report } diff --git a/subworkflows/local/utils_nfcore_genomeassembler_pipeline/main.nf b/subworkflows/local/utils_nfcore_genomeassembler_pipeline/main.nf index 8fc97ff5..dce27b35 100644 --- a/subworkflows/local/utils_nfcore_genomeassembler_pipeline/main.nf +++ b/subworkflows/local/utils_nfcore_genomeassembler_pipeline/main.nf @@ -150,7 +150,7 @@ workflow PIPELINE_INITIALISATION { assembly_map_bam: it.assembly_map_bam ?: null, // reads for qc - qc_reads: it.qc_reads ?: params.qc_reads ?: "ont", + qc_reads: ((it.qc_reads == "ont" || params.qc_reads == "ont") && it.ontreads) ? "ont" : "hifi", qc_reads_path: it.qc_reads == "ont" ? (it.ontreads) : (it.hifireads), quast: it.quast ?: params.quast ?: false, busco: it.busco ?: params.busco ?: false, diff --git a/workflows/genomeassembler.nf b/workflows/genomeassembler.nf index c89f4c78..1045a96c 100644 --- a/workflows/genomeassembler.nf +++ b/workflows/genomeassembler.nf @@ -127,7 +127,6 @@ workflow GENOMEASSEMBLER { } .set { shortreads } - shortreads.view(it -> "SHORTREADS: $it") // adapted to sample-logic PREPARE_SHORTREADS(shortreads) // This changes ch_main shortreads_F and _R become one tuple, paired is gone. From faddbdb06024081085da282beb3c337db843d1f8 Mon Sep 17 00:00:00 2001 From: Niklas Schandry Date: Mon, 7 Jul 2025 13:28:32 +0200 Subject: [PATCH 016/162] bugfix commit 3 --- subworkflows/local/assemble/main.nf | 16 ++++++++++------ subworkflows/local/jellyfish/main.nf | 2 -- 2 files changed, 10 insertions(+), 8 deletions(-) diff --git a/subworkflows/local/assemble/main.nf b/subworkflows/local/assemble/main.nf index 2be25ee9..61ed1938 100644 --- a/subworkflows/local/assemble/main.nf +++ b/subworkflows/local/assemble/main.nf @@ -33,11 +33,11 @@ workflow ASSEMBLE { ch_main_branched .to_assemble .branch { it -> - hifiasm: ((it.strategy == "single" && it.assembler1 == "hifiasm") + hifiasm: (it.strategy == "single" && it.assembler1 == "hifiasm" && !it.ontreads) || (it.strategy == "scaffold" && (it.assembler1 == "hifiasm" || it.assembler2 == "hifiasm")) - || (it.strategy == "hybrid" && it.assembler1 == "hifiasm")) + || (it.strategy == "hybrid" && it.assembler1 == "hifiasm") hifiasm_ont: (it.strategy == "single" && it.assembler1 == "hifiasm" && it.ontreads) - flye: ((it.strategy == "single" && it.assembler1 == "flye") || (it.strategy == "scaffold" && (it.assembler1 == "flye"))) + flye: (it.strategy == "single" && it.assembler1 == "flye") || (it.strategy == "scaffold" && it.assembler1 == "flye") } .set { ch_main_assemble } @@ -49,8 +49,8 @@ workflow ASSEMBLE { it -> reads: [ [ - it.meta, - it.genome_size + id: it.meta.id, + genome_size: it.genome_size ], it.ontreads ?: it.hifireads, ] @@ -67,6 +67,7 @@ workflow ASSEMBLE { .map { it -> [ it.meta, it.hifireads, it.ontreads ?: [] ] } .set { hifiasm_inputs } + hifiasm_inputs.view { it -> "HIFIASM: $it"} HIFIASM(hifiasm_inputs, [[], [], []], [[], [], []], [[], []]) GFA_2_FA_HIFI(HIFIASM.out.processed_unitigs) @@ -78,6 +79,8 @@ workflow ASSEMBLE { .map { it -> [it.meta, it.ontreads, []] } .set { hifiasm_ont_inputs } + hifiasm_ont_inputs.view { it -> "HIFIASM_ONT: $it"} + HIFIASM_ONT(hifiasm_ont_inputs, [[], [], []], [[], [], []], [[], []]) GFA_2_FA_ONT(HIFIASM_ONT.out.processed_unitigs) @@ -88,7 +91,8 @@ workflow ASSEMBLE { ch_main_branched .to_assemble .map { it -> [it.meta] } - .join(FLYE.out.fasta) + //FLYE meta map contains id and genomesize + .join(FLYE.out.fasta.map { meta, assembly -> [[meta.id], assembly ] }) .join(GFA_2_FA_HIFI.out.contigs_fasta) .join(GFA_2_FA_ONT.out.contigs_fasta) .map { it -> [meta: it[0], flye_assembly: it[1], hifiasm_hifi_assembly: it[2], hifiasm_ont_assembly: it[3]] } diff --git a/subworkflows/local/jellyfish/main.nf b/subworkflows/local/jellyfish/main.nf index 15061760..71a72c94 100644 --- a/subworkflows/local/jellyfish/main.nf +++ b/subworkflows/local/jellyfish/main.nf @@ -20,7 +20,6 @@ workflow JELLYFISH { } .set { samples } - samples.view { it -> "JELLYFISH SAMPLES: $it"} COUNT(samples) COUNT.out.kmers.set { kmers } @@ -47,7 +46,6 @@ workflow JELLYFISH { ) .set { genomescope_in } - GENOMESCOPE(genomescope_in) ch_versions = ch_versions.mix(GENOMESCOPE.out.versions) From d428ddb363f2b8880443f7692b9e59b60b9038d9 Mon Sep 17 00:00:00 2001 From: Niklas Schandry Date: Mon, 7 Jul 2025 14:21:13 +0200 Subject: [PATCH 017/162] report modifications 1; add groups --- modules/local/report/main.nf | 41 +++++++++---------- subworkflows/local/assemble/main.nf | 3 -- .../main.nf | 3 ++ workflows/genomeassembler.nf | 11 ++++- 4 files changed, 33 insertions(+), 25 deletions(-) diff --git a/modules/local/report/main.nf b/modules/local/report/main.nf index 841e61b4..3fedbd2c 100644 --- a/modules/local/report/main.nf +++ b/modules/local/report/main.nf @@ -18,6 +18,7 @@ process REPORT { path busco_files, stageAs: "data/busco/*" path meryl_files, stageAs: "data/merqury/*" path versions, stageAs: "software_versions.yml" + val groups output: tuple path("report.html"), path("report_files/*"), emit: report_html @@ -31,38 +32,36 @@ process REPORT { script: def report_profile = "--profile base" - if (params.ont) { + def report_params = '' + if (nanoq_files) { report_profile = report_profile << ",nanoq" + report_params = report_params << ' -P nanoq:true' } - if (params.quast) { + if (quast_files) { report_profile = report_profile << ",quast" + report_params = report_params << ' -P quast:true ' } - if (params.busco) { + if (busco_files) { report_profile = report_profile << ",busco" + report_params = report_params << ' -P busco:true' } - if (params.jellyfish) { + if (jelly_files) { report_profile = report_profile << ",jellyfish" + report_params = report_params << ' -P jellyfish:true' } - if (params.merqury) { + if (meryl_files) { report_profile = report_profile << ",merqury" + report_params = report_params << ' -P merqury:true' } - def report_params = '' - if (params.ont) { - report_params = report_params << ' -P nanoq:true' - } - if (params.quast) { - report_params = report_params << ' -P quast:true ' - } - if (params.busco) { - report_params = report_params << ' -P busco:true' - } - if (params.jellyfish) { - report_params = report_params << ' -P jellyfish:true' - } - if (params.merqury) { - report_params = report_params << ' -P merqury:true' - } + + def yamlBuilder = new groovy.yaml.YamlBuilder() + yamlBuilder(groups) + def yaml_content = yamlBuilder.toString().tokenize('\n').join("\n ") """ + cat <<- END_YAML_GROUPS > groups.yml + ${yaml_content} + END_YAML_GROUPS + export HOME="\$PWD" quarto render report.qmd \\ ${report_profile} \\ diff --git a/subworkflows/local/assemble/main.nf b/subworkflows/local/assemble/main.nf index 61ed1938..91cc4b69 100644 --- a/subworkflows/local/assemble/main.nf +++ b/subworkflows/local/assemble/main.nf @@ -67,7 +67,6 @@ workflow ASSEMBLE { .map { it -> [ it.meta, it.hifireads, it.ontreads ?: [] ] } .set { hifiasm_inputs } - hifiasm_inputs.view { it -> "HIFIASM: $it"} HIFIASM(hifiasm_inputs, [[], [], []], [[], [], []], [[], []]) GFA_2_FA_HIFI(HIFIASM.out.processed_unitigs) @@ -79,8 +78,6 @@ workflow ASSEMBLE { .map { it -> [it.meta, it.ontreads, []] } .set { hifiasm_ont_inputs } - hifiasm_ont_inputs.view { it -> "HIFIASM_ONT: $it"} - HIFIASM_ONT(hifiasm_ont_inputs, [[], [], []], [[], [], []], [[], []]) GFA_2_FA_ONT(HIFIASM_ONT.out.processed_unitigs) diff --git a/subworkflows/local/utils_nfcore_genomeassembler_pipeline/main.nf b/subworkflows/local/utils_nfcore_genomeassembler_pipeline/main.nf index dce27b35..fe1436ce 100644 --- a/subworkflows/local/utils_nfcore_genomeassembler_pipeline/main.nf +++ b/subworkflows/local/utils_nfcore_genomeassembler_pipeline/main.nf @@ -100,6 +100,9 @@ workflow PIPELINE_INITIALISATION { .map { it -> [ meta: [id: it.sample], + // new in refactor-assemblies + group: it.group ?: "none", + // old ontreads: it.ontreads ?: null, hifireads: it.hifireads ?: null, // new in refactor-assemblers diff --git a/workflows/genomeassembler.nf b/workflows/genomeassembler.nf index 1045a96c..e422ec3b 100644 --- a/workflows/genomeassembler.nf +++ b/workflows/genomeassembler.nf @@ -297,7 +297,16 @@ workflow GENOMEASSEMBLER { merqury_files = Channel.of([]) } - REPORT(report_files, report_functions, nanoq_files, genomescope_files, quast_files, busco_files, merqury_files, Channel.fromPath("${params.outdir}/pipeline_info/nf_core_pipeline_software_versions.yml")) + REPORT( report_files, + report_functions, + nanoq_files, + genomescope_files, + quast_files, + busco_files, + merqury_files, + Channel.fromPath("${params.outdir}/pipeline_info/nf_core_pipeline_software_versions.yml"), + ch_main.map { it -> [sample: [id: it.meta.id, group: it.group]]}.collect() + ) // // Collate and save software versions From cbe5eea31b6177b3d5f567ca127032abc0234bc2 Mon Sep 17 00:00:00 2001 From: Niklas Schandry Date: Tue, 8 Jul 2025 15:12:55 +0200 Subject: [PATCH 018/162] WIP commit --- docs/usage.md | 47 ++++++++++++++----- nextflow.config | 47 +++++++++++-------- subworkflows/local/assemble/main.nf | 2 +- .../main.nf | 27 ++++++----- 4 files changed, 78 insertions(+), 45 deletions(-) diff --git a/docs/usage.md b/docs/usage.md index 178148b4..c9b69753 100644 --- a/docs/usage.md +++ b/docs/usage.md @@ -11,19 +11,42 @@ This pipeline can perform assembly, polishing, scaffolding and annotation lift-o ![Pipeline metromap](images/genomeassembler.light.png) -### Pre-set profiles +Since it is often difficult to know which tool, or assembly strategy will perform best on a dataset, `nf-core/genomeassembler` can also be used to compare outcomes of different approaches in one run. +To compare different samples, a column named `group` is required, which should contain the same value for all samples that should be compared to each other. -To ease configuration, there are a couple of pre-defined profiles for various combinations of read sources and assemblers (named readtype_assembler) +## Parameterization -| ONT | HiFI  | Assembly-strategy  | Profile name | -| --- | ----- | ---------------------------------------------------------------------- | ---------------------------- | -| Yes | No  | flye | `ont_flye` | -| No | Yes  | flye | `hifi_flye` | -| Yes | No | hifiasm | `ont_hifiasm` | -| No | Yes  | hifiasm | `hifi_hifiasm` | -| Yes | Yes  | hifiasm --ul | `hifiont_hifiasm` | -| Yes | Yes  | Scaffolding of ONT assemblies (flye) onto HiFi assemblies (hifiasm) | `hifiont_flye_on_hifiasm` | -| Yes | Yes  | Scaffolding of ONT assemblies (hifiasm) onto HiFi assemblies (hifiasm) | `hifiont_hifiasm_on_hifiasm` | +Parameters for this pipeline can either be supplied **globally**, e.g: + +- via `--paramname value`, +- or in a config with `params { paramname = value }`, +- or a yaml with: + +```yaml +--- +params: + - paramname: "value" +``` + +or as **sample parameters**, by adding a _correctly named_ column to the samplesheet. In the above example this would be a column named `paramname`. + +Sample parameters take priority over global parameters, if both are provided the sample-specific parameter will be used for that sample. + +> [!NOTE] +> The parameter names will be used in subsequent sections. Since all parameters can be provided per-sample or pipeline wide, no examples will be given. + +## Choice of assembly-strategy and assembler + +Assembly strategy is controlled via `strategy` (either pipeline parameter or sample-setting), and assembler(s) used are chosen via `assembler` (either pipeline parameter or sample-setting) +`nf-core/genomeassembler` currently supports the following assembly strategies: + +- single: Use a single assembler for a single type of read. The assembler should be provided via `assembler` and can be `hifiasm` or `flye`. +- hybrid: Use a single assembler for a combined assembly of ONT and HiFi reads. The assembler should be provided via `assembler`. Only `hifiasm` supports hybrid assembly. +- scaffold: Assemble ONT reads and HiFi indepently and scaffold one assembly onto the other. `assembler` should be: "flye_hifiasm" or "hifiasm_hifiasm" (ONT_HiFi). When running in "scaffold" mode, `assembly_scaffolding_order` can be used to control which assembly gets scaffolded onto which, the default being "ont_on_hifi" where ONT assembly is scaffolded onto HifI assembly. + +Assembler specific arguments can be provided, for the assembler via `hifiasm_args` or `flye_args`, or with more fine-grained control via `assembler1_args` and `assembler2_args`. +`assembler1_args` controls the parameters for the assembler in `single` and `hybrid` strategies, or for the assembler used of ONT reads when using `scaffold`. `assembler2_args` can be used to pass arguments to the assembler used for HiFi reads in `scaffold` mode. +`assembler[1,2]_args` can only be set via samplesheet. ## Samplesheet input @@ -38,7 +61,7 @@ You will need to create a samplesheet with information about the samples you wou The largest samplesheet format is: ```csv title="samplesheet.csv" -sample,ontreads,hifireads,ref_fasta,ref_gff,shortread_F,shortread_R,paired +sample,ontreads,hifireads Sample1,/path/reads/sample1ont.fq.gz,/path/reads/sample1hifi.fq.gz,/path/references/ref.fa,/path/references/ref.gff,/path/reads/sample1_r1.fq.gz,/path/reads/sample1_r2.fq.gz,true ``` diff --git a/nextflow.config b/nextflow.config index b6408a19..e127a473 100644 --- a/nextflow.config +++ b/nextflow.config @@ -42,34 +42,43 @@ params { // Pipeline params input = '' // input file outdir = null // outdir - use_ref = true // use a reference genome (requires fasta + gff) - skip_alignments = false // Intended for QC-oriented (re)-runs, alignments (to ref) are provided - skip_assembly = false // Intended for QC-oriented (re)-runs, assemblies are provided + // Assembly strategy + strategy = null // assembly_strategy + + // == Read QC and trimming == // -- ONT - ont = false // ont reads available? - collect_reads = false // collect ONT reads into a single file - porechop = false // run porechop on ONT - read_length = null // avg read length, can be estimated from reads + ont_collect = false // collect ONT reads into a single file + ont_trim = false // run porechop on ONT // -- Jellyfish (ONT reads only) -- - jellyfish = true // run jellyfish - dump = false // dump output - kmer_length = 21 // kmer length + ont_jellyfish = false + ont_jellyfish_k = 21 + read_length = null // avg read length, can be estimated from reads + dump = false // dump jellyfish output // -- HiFi -- - hifi = false // HiFi reads available? - lima = false // run lima on HiFi reads? - pacbio_primers = null // if lima, then this needs to be a path to a list of primers - // -- ASSEMBLY + hifi_trim = false // run lima on HiFi reads? + hifi_primers = null // if lima, then this needs to be a path to a list of primers + // -- Short read -- + use_short_reads = false // short reads available? + trim_short_reads = true // trim short reads? + shortread_F = null + shortread_R = null + paired = null + // == ASSEMBLY == assembler = "flye" // assembler to use // -- Assembly: Flye -- genome_size = null // genomesize, optional, can be estimated from ONT reads flye_mode = '--nano-hq' // flye mode flye_args = "" // extra flye args // -- Assembly: hifiasm -- - hifiasm_ont = false // combine hifi and ONT with hifiasm --ul? hifiasm_args = "" // extra hifiasm args - // -- Short read -- - short_reads = false // short reads available? - trim_short_reads = true // trim short reads? + // QC Oriented extra options + assembly = null + ref_map_bam = null + assembly_map_bam = null + // == Reference == + use_ref = false // use a reference genome (requires fasta + gff) + ref_fasta = null + ref_gff = null // -- POLISHING // -- Polish: medaka polish_medaka = false // run medaka @@ -86,7 +95,7 @@ params { busco_lineage = "brassicales_odb10" // busco lineage // -- QC: QUAST quast = false // run quast - qc_reads = "ONT" // if both ONT and HiFi reads are available, which should be used for QC alignments + qc_reads = "ont" // if both ONT and HiFi reads are available, which should be used for QC alignments // -- SCAFFOLDING scaffold_links = false // Scaffold with LINKS scaffold_longstitch = false // Scaffold with Longstitch diff --git a/subworkflows/local/assemble/main.nf b/subworkflows/local/assemble/main.nf index 91cc4b69..d0e7556d 100644 --- a/subworkflows/local/assemble/main.nf +++ b/subworkflows/local/assemble/main.nf @@ -89,7 +89,7 @@ workflow ASSEMBLE { .to_assemble .map { it -> [it.meta] } //FLYE meta map contains id and genomesize - .join(FLYE.out.fasta.map { meta, assembly -> [[meta.id], assembly ] }) + .join(FLYE.out.fasta.map { meta, assembly -> [[id: meta.id], assembly ] }) .join(GFA_2_FA_HIFI.out.contigs_fasta) .join(GFA_2_FA_ONT.out.contigs_fasta) .map { it -> [meta: it[0], flye_assembly: it[1], hifiasm_hifi_assembly: it[2], hifiasm_ont_assembly: it[3]] } diff --git a/subworkflows/local/utils_nfcore_genomeassembler_pipeline/main.nf b/subworkflows/local/utils_nfcore_genomeassembler_pipeline/main.nf index fe1436ce..6ac598bb 100644 --- a/subworkflows/local/utils_nfcore_genomeassembler_pipeline/main.nf +++ b/subworkflows/local/utils_nfcore_genomeassembler_pipeline/main.nf @@ -102,20 +102,20 @@ workflow PIPELINE_INITIALISATION { meta: [id: it.sample], // new in refactor-assemblies group: it.group ?: "none", - // old ontreads: it.ontreads ?: null, hifireads: it.hifireads ?: null, // new in refactor-assemblers strategy: it.strategy ?: params.strategy, assembler1: it.assembler1 ?: + ["hifiasm","flye"].contains(it.assembler) ? it.assembler : ["hifiasm","flye"].contains(params.assembler) ? params.assembler : - params.assembler == "flye_on_hifiasm" ? "flye" : - params.assembler == "hifiasm_on_hifiasm" ? "hifiasm" + params.assembler == "flye_hifiasm" ? "flye" : + params.assembler == "hifiasm_hifiasm" ? "hifiasm" : null, assembler2: it.assembler2 ?: ["hifiasm_on_hifiasm","flye_on_hifiasm"].contains(params.assembler) ? "hifiasm" : null, - assembly_scaffolding_order: it.assembly_scaffolding_order, + assembly_scaffolding_order: it.assembly_scaffolding_order ?: params.assembly_scaffolding_order ?: "ont_on_hifi", genome_size: it.genome_size ?: params.genome_size, assembler1_args: it.assembler1_args ?: (it.assembler1 == "hifiasm") ? params.hifiasm_args : @@ -130,13 +130,13 @@ workflow PIPELINE_INITIALISATION { (params.polish_medaka) ? "medaka" : (params.polish_pilon) ? "pilon" : null, - ont_collect: it.ont_collect ?: params.collect_reads, - ont_trim: it.ont_trim ?: params.porechop, - ont_jellyfish: it.ont_jellyfish ?: (params.jellyfish && it.ontreads), + ont_collect: it.ont_collect ?: params.ont_collect, + ont_trim: it.ont_trim ?: params.ont_trim, + ont_jellyfish: it.ont_jellyfish ?: (params.ont_jellyfish && it.ontreads), ont_jellyfish_k: it.ont_jellyfish_k ?: params.kmer_length, ont_read_length: it.ont_read_length ?: params.read_length, - hifi_trim: it.hifi_trim ?: params.lima, - hifi_primers: it.hifi_primers ?: params.pacbio_primers, + hifi_trim: it.hifi_trim ?: params.hifi_trim, + hifi_primers: it.hifi_primers ?: params.hifi_primers, polish_medaka: it.polish_medaka ?: params.polish_medaka, medaka_model: it.medaka_model ?: params.medaka_model, polish_pilon: it.polish_pilon ?: params.polish_pilon, @@ -144,6 +144,9 @@ workflow PIPELINE_INITIALISATION { scaffold_links: it.scaffold_longstitch ?: params.scaffold_links, scaffold_ragtag: it.scaffold_longstitch ?: params.scaffold_ragtag, use_ref: it.use_ref ?: params.use_ref ?: it.ref_fasta ? true : false, + // not new + ref_fasta: it.ref_fasta ?: params.ref_fasta, + ref_gff: it.ref_gff ?: params.ref_gff, flye_mode: it.flye_mode ?: params.flye_mode, // assembly already provided? assembly: it.assembly ?: null, @@ -160,14 +163,12 @@ workflow PIPELINE_INITIALISATION { busco_lineage: it.busco_lineage ?: params.busco_lineage, busco_db: it.busco_db ?: params.busco_db, lift_annotations: it.lift_annotations ?: params.lift_annotations, - // not new - ref_fasta: it.ref_fasta ?: params.ref_fasta, - ref_gff: it.ref_gff ?: params.ref_gff, + shortread_F: it.shortread_F ?: params.shortread_F, shortread_R: it.shortread_R ?: params.shortread_R, paired: it.paired ?: params.paired, // new: - use_short_reads: it.use_short_reads ?: params.short_reads ?: it.shortread_F ? true : false, + use_short_reads: it.use_short_reads ?: params.use_short_reads ?: it.shortread_F ? true : false, shortread_trim: it.shortread_trim ?: params.trim_short_reads ] } .set { ch_samplesheet } From d24eb8eca7bbd8f35ec9818fe8033676462e54d7 Mon Sep 17 00:00:00 2001 From: Niklas Schandry Date: Tue, 8 Jul 2025 19:09:49 +0200 Subject: [PATCH 019/162] WIP commit --- nextflow_schema.json | 6 +- subworkflows/local/assemble/main.nf | 283 ++++++++++++------ .../main.nf | 22 +- 3 files changed, 211 insertions(+), 100 deletions(-) diff --git a/nextflow_schema.json b/nextflow_schema.json index 636070bc..530a99f5 100644 --- a/nextflow_schema.json +++ b/nextflow_schema.json @@ -350,9 +350,9 @@ }, "qc_reads": { "type": "string", - "description": "Long reads that should be used for QC when both ONT and HiFi reads are provided. Options are `'ONT'` or `'HIFI'`", - "enum": ["ONT", "HIFI"], - "default": "ONT" + "description": "Long reads that should be used for QC when both ONT and HiFi reads are provided. Options are `'ont'` or `'hifi'`", + "enum": ["ont", "hifi"], + "default": "ont" }, "busco": { "type": "boolean", diff --git a/subworkflows/local/assemble/main.nf b/subworkflows/local/assemble/main.nf index d0e7556d..65c0f961 100644 --- a/subworkflows/local/assemble/main.nf +++ b/subworkflows/local/assemble/main.nf @@ -16,8 +16,6 @@ workflow ASSEMBLE { main: // Empty channels - Channel.empty().set { flye_inputs } - Channel.empty().set { hifiasm_inputs } Channel.empty().set { ch_versions } ch_main @@ -31,20 +29,29 @@ workflow ASSEMBLE { } ch_main_branched - .to_assemble - .branch { it -> - hifiasm: (it.strategy == "single" && it.assembler1 == "hifiasm" && !it.ontreads) - || (it.strategy == "scaffold" && (it.assembler1 == "hifiasm" || it.assembler2 == "hifiasm")) - || (it.strategy == "hybrid" && it.assembler1 == "hifiasm") - hifiasm_ont: (it.strategy == "single" && it.assembler1 == "hifiasm" && it.ontreads) - flye: (it.strategy == "single" && it.assembler1 == "flye") || (it.strategy == "scaffold" && it.assembler1 == "flye") - } - .set { ch_main_assemble } - + .to_assemble + .branch { it -> + single: it.strategy == "single" + hybrid: it.strategy == "hybrid" + scaffold: it.strategy == "scaffold" + } + .set { ch_main_assemble_branched } + + + ch_main_assemble_branched.scaffold.view { it -> "BRANCHED SCAFFOLD: $it"} + // Flye inputs: + ch_main_assemble_branched + .single + .filter { it -> it.assembler1 == "flye" } + .mix( + ch_main_assemble_branched + .scaffold + .filter { it -> it.assembler1 == "flye" || it.assembler2 == "flye"} + ) + .set { ch_main_assemble_flye } // Assembly flye branch - - ch_main_assemble - .flye + ch_main_assemble_flye.view { it -> "ASSEMBLE_FLYE: $it"} + ch_main_assemble_flye .multiMap { it -> reads: [ @@ -52,97 +59,198 @@ workflow ASSEMBLE { id: it.meta.id, genome_size: it.genome_size ], - it.ontreads ?: it.hifireads, + it.assembler1 == "flye" ? it.ontreads : (it.assembler2 == "flye" ? it.hifireads : null), ] - mode: it.flye_mode ?: it.ontreads ? "nano-hq" : "--pacbio-hifi" + mode: it.flye_mode ?: it.assembler1 == "flye" ? "--nano-hq" : "--pacbio-hifi" } .set { flye_inputs } - FLYE(flye_inputs.reads, flye_inputs.mode) + FLYE(flye_inputs.reads, flye_inputs.mode) - ch_versions = ch_versions.mix(FLYE.out.versions) + ch_versions = ch_versions.mix(FLYE.out.versions) - // Assembly hifiasm branch - ch_main_assemble.hifiasm - .map { it -> [ it.meta, it.hifireads, it.ontreads ?: [] ] } - .set { hifiasm_inputs } - HIFIASM(hifiasm_inputs, [[], [], []], [[], [], []], [[], []]) + // Hifiasm: everything that is not ONT + ch_main_assemble_branched + .single + .filter { it -> it.assembler1 == "hifiasm" && !it.ontreads } + .mix( + ch_main_assemble_branched + .hybrid + .filter { it -> it.assembler1 == "hifiasm" } + ) + .mix(ch_main_assemble_branched + .scaffold + .filter { it -> it.assembler2 == "hifiasm" } + // the samples for scaffolding should not have ONT reads, otherwise hifiasm will run in --ul mode + .map { it -> it - it.subMap("ontreads") } + ) + .set { ch_main_assemble_hifi_hifiasm } + + HIFIASM(ch_main_assemble_hifi_hifiasm.map { it -> [ it.meta, it.hifireads, it.ontreads ?: [] ] }, + [[], [], []], + [[], [], []], + [[], []]) GFA_2_FA_HIFI(HIFIASM.out.processed_unitigs) ch_versions = ch_versions.mix(HIFIASM.out.versions).mix(GFA_2_FA_HIFI.out.versions) + + // Assemble hifiasm_ont branch + ch_main_assemble_branched + .single + .filter { it -> it.assembler1 == "hifiasm" && it.ontreads } + .mix(ch_main_assemble_branched + .scaffold + .filter { it -> it.assembler1 == "hifiasm" } + ) + .set { ch_main_assemble_ont_hifiasm } - ch_main_assemble.hifiasm_ont - .map { it -> [it.meta, it.ontreads, []] } - .set { hifiasm_ont_inputs } - HIFIASM_ONT(hifiasm_ont_inputs, [[], [], []], [[], [], []], [[], []]) + HIFIASM_ONT(ch_main_assemble_ont_hifiasm.map { it -> [ it.meta, it.ontreads, [] ] }, [[], [], []], [[], [], []], [[], []]) GFA_2_FA_ONT(HIFIASM_ONT.out.processed_unitigs) ch_versions = ch_versions.mix(HIFIASM_ONT.out.versions).mix(GFA_2_FA_ONT.out.versions) - // Create a channel containing all assemblies in a "wide" format - ch_main_branched - .to_assemble - .map { it -> [it.meta] } - //FLYE meta map contains id and genomesize - .join(FLYE.out.fasta.map { meta, assembly -> [[id: meta.id], assembly ] }) - .join(GFA_2_FA_HIFI.out.contigs_fasta) - .join(GFA_2_FA_ONT.out.contigs_fasta) - .map { it -> [meta: it[0], flye_assembly: it[1], hifiasm_hifi_assembly: it[2], hifiasm_ont_assembly: it[3]] } - .set { ch_assemblies } - // Now figure out which of the wide assemblies goes into which generic assembly slot - ch_main_branched.to_assemble - // Turn map into list for joining: - // each tuple in the list contains the value(s) and the original map - // I think this should also work without the entry.value, keeping the map in the tuple - // but it would required a different collect strategy that seems more involved? + + // Now, the individual assemblies need to be correctly added into the main channel. + // This should be done per-strategy I think + // join assembler outputs back to assembler inputs and determine correct placement of the assembly. + // Flye: + ch_main_assemble_flye + // Convert to list for join .map { it -> it.collect { entry -> [ entry.value, entry ] } } - .join(ch_assemblies - .map { it -> it.collect { entry -> [ entry.value, entry ] } } + .join( FLYE.out.fasta + .map { meta, assembly -> [meta: [id: meta.id], flye_assembly: assembly ] } + .map { it -> it.collect { entry -> [ entry.value, entry ] } } ) // After joining re-create the maps from the stored map .map { it -> it.collect { _entry, map -> [ (map.key): map.value ] }.collectEntries() } - // The extra columns are joined and removed via submap - .map { it -> - it - it.subMap('flye_assembly', 'hifiasm_hifi_assembly', 'hifiasm_ont_assembly') + + .map { it -> it - it.subMap("flye_assembly") + [ - assembly: it.strategy == "single" || it.strategy == "hybrid" ? - (it.flye_assembly ?: - it.hifiasm_hifi_assembly ?: - it.hifiasm_ont_assembly) : - null, - // remaining case is "scaffold" - // by definition assembly1 == ONT in "scaffold" - assembly1: it.strategy != "scaffold" ? - null : - it.assembler1 == "flye" ? - (it.flye_assembly) : - (it.hifiasm_ont_assembly), - // assembly2 only exists if the strategy is "scaffold" - assembly2: it.strategy != "scaffold" ? - null : - // by definition assembly2 == hifi in "scaffold" - it.assembler2 == "flye" ? - (it.flye_assembly) : - (it.hifiasm_hifi_assembly) + assembly: it.strategy == "single" ? it.flye_assembly : null, + assembly1: it.assembler1 == "flye" ? it.flye_assembly : null, + assembly2: it.assembler2 == "flye" ? it.flye_assembly : null, ] } - // This should return the to_assemble branch - .set { ch_main_assembled } + .set { flye_assemblies } - // branch to scaffold those assemblies that need it - ch_main_assembled - .branch { it -> - scaffold: it.strategy == "scaffold" - no_scaffold: it.strategy != "scaffold" + ch_main_assemble_hifi_hifiasm + // Convert to list for join + .map { it -> it.collect { entry -> [ entry.value, entry ] } } + .join( GFA_2_FA_HIFI.out.contigs_fasta + .map { meta, assembly -> [meta: meta, hifiasm_assembly: assembly ] } + .map { it -> it.collect { entry -> [ entry.value, entry ] } } + ) + // After joining re-create the maps from the stored map + .map { it -> it.collect { _entry, map -> [ (map.key): map.value ] }.collectEntries() } + .map { + it -> it -it.subMap("hifiasm_assembly") + + [ + assembly: (it.strategy == "single" || it.strategy == "hybrid") && it.assembler1 == "hifiasm" ? it.hifiasm_assembly : null, + assembly1: it.strategy == "scaffold" && it.assembler1 == "hifiasm" ? it.hifiasm_assembly : null, + assembly2: it.strategy == "scaffold" && it.assembler2 == "hifiasm" ? it.hifiasm_assembly : null + ] } - .set { ch_assembled_branch_scaffold } + .set { hifiasm_hifi_assemblies } + - ch_assembled_branch_scaffold.scaffold + ch_main_assemble_ont_hifiasm + .map { it -> it.collect { entry -> [ entry.value, entry ] } } + .join( GFA_2_FA_ONT.out.contigs_fasta + .map { meta, assembly -> [meta: meta, hifiasm_assembly: assembly ] } + .map { it -> it.collect { entry -> [ entry.value, entry ] } } + ) + // After joining re-create the maps from the stored map + .map { it -> it.collect { _entry, map -> [ (map.key): map.value ] }.collectEntries() } + .map { + it -> it -it.subMap("hifiasm_assembly") + + [ + assembly: (it.strategy == "single" || it.strategy == "hybrid") && it.assembler1 == "hifiasm" ? it.hifiasm_assembly : null, + assembly1: it.strategy == "scaffold" && it.assembler1 == "hifiasm" ? it.hifiasm_assembly : null, + assembly2: it.strategy == "scaffold" && it.assembler2 == "hifiasm" ? it.hifiasm_assembly : null + ] + } + .set { hifiasm_ont_assemblies } + + // The single and hybrid channels can be mixed and forwarded. + // The scaffold channel needs to be joined separately. + flye_assemblies + .filter { it -> ["single","hybrid"].contains(it.strategy) } + .mix( + hifiasm_hifi_assemblies + .filter { it -> ["single","hybrid"].contains(it.strategy) } + ) + .mix( + hifiasm_ont_assemblies + .filter { it -> ["single","hybrid"].contains(it.strategy) } + ) + .set { ch_assemblies_no_scaffold } + + // This leaves the scaffold strategy. + // scaffolds can be: FLYE-HIFIASM, FLYE-FLYE, HIFIASM-HIFIASM HIFIASM-FLYE or + flye_assemblies + // Flye-hifiasm + .filter { it -> it.strategy == "scaffold" && it.assembler1 == "flye" && it.assembler2 == "hifiasm" } + .map { it -> it.collect { entry -> [ entry.value, entry ] } } + .join( hifiasm_hifi_assemblies + .filter{ it -> it.strategy == "scaffold" && it.assembler1 == "flye" && it.assembler2 == "hifiasm" } + .map { it -> [ meta: it.meta, hifiasm_assembly: it.assembly2 ] } + .map { it -> it.collect { entry -> [ entry.value, entry ] } } + ) + .map { it -> it.collect { _entry, map -> [ (map.key): map.value ] }.collectEntries() } + .map { it -> it - it.subMap("hifiasm_assembly","assembly2") + [assembly2: it.hifiasm_assembly] } + .set{ scaffold_flye_hifiasm } + + // flye-flye + flye_assemblies + .filter { it -> it.strategy == "scaffold" && it.assembler1 == "flye" && it.assembler2 == "flye" } + .map { it -> it.collect { entry -> [ entry.value, entry ] } } + .join( flye_assemblies + .filter{ it -> it.strategy == "scaffold" && it.assembler1 == "flye" && it.assembler2 == "flye" } + .map { it -> [ meta: it.meta, flye_assembly: it.assembly2 ] } + .map { it -> it.collect { entry -> [ entry.value, entry ] } } + ) + .map { it -> it.collect { _entry, map -> [ (map.key): map.value ] }.collectEntries() } + .map { it -> it - it.subMap("flye_assembly","assembly2") + [assembly2: it.flye_assembly] } + .set{ scaffold_flye_flye } + + // hifiasm_flye + hifiasm_ont_assemblies + .filter { it -> it.strategy == "scaffold" && it.assembler1 == "hifiasm" && it.assembler2 == "flye" } + .map { it -> it.collect { entry -> [ entry.value, entry ] } } + .join( flye_assemblies + .filter{ it -> it.strategy == "scaffold" && it.assembler1 == "hifiasm" && it.assembler2 == "flye" } + .map { it -> [ meta: it.meta, flye_assembly: it.assembly2 ] } + .map { it -> it.collect { entry -> [ entry.value, entry ] } } + ) + .map { it -> it.collect { _entry, map -> [ (map.key): map.value ] }.collectEntries() } + .map { it -> it - it.subMap("flye_assembly","assembly2") + [assembly2: it.flye_assembly] } + .set{ scaffold_hifiasm_flye } + + // hifiasm_hifiasm + hifiasm_ont_assemblies + .filter { it -> it.strategy == "scaffold" && it.assembler1 == "hifiasm" && it.assembler2 == "flye" } + .map { it -> it.collect { entry -> [ entry.value, entry ] } } + .join( hifiasm_hifi_assemblies + .filter{ it -> it.strategy == "scaffold" && it.assembler1 == "hifiasm" && it.assembler2 == "flye" } + .map { it -> [ meta: it.meta, hifiasm_assembly: it.assembly2 ] } + .map { it -> it.collect { entry -> [ entry.value, entry ] } } + ) + .map { it -> it.collect { _entry, map -> [ (map.key): map.value ] }.collectEntries() } + .map { it -> it - it.subMap("hifiasm_assembly","assembly2") + [assembly2: it.hifiasm_assembly] } + .set{ scaffold_hifiasm_hifiasm } + + // branch to scaffold those assemblies that need it + scaffold_flye_hifiasm + .mix(scaffold_flye_flye) + .mix(scaffold_hifiasm_flye) + .mix(scaffold_hifiasm_hifiasm) + .set { ch_to_scaffold } + + ch_to_scaffold .multiMap { it -> target: [ @@ -158,26 +266,19 @@ workflow ASSEMBLE { RAGTAG_PATCH(ragtag_in.target, ragtag_in.query, [[], []], [[], []] ) - // Add the scaffolded assemblies to the scaffold branch and mix with unscaffolded branch - // recreates ch_main_assembled (unbranched) - ch_assembled_branch_scaffold - .scaffold + ch_to_scaffold + .map { it -> it - it.subMap("assembly") } .map { it -> it.collect { entry -> [ entry.value, entry ] } } .join( RAGTAG_PATCH.out.patch_fasta - .map { it -> [meta: it[0], assembly_patched: it[1]] } + .map { it -> [meta: it[0], assembly: it[1]] } .map { it -> it.collect { entry -> [ entry.value, entry ] } } ) .map { it -> it.collect { _entry, map -> [ (map.key): map.value ] }.collectEntries() } - .map { it -> - it - it.subMap("assembly_patched") + - [ - assembly: it.strategy == "scaffold" ? - (it.assembly_patched) : - (it.assembly) - ] - } - .mix(ch_assembled_branch_scaffold.no_scaffold) + .set { ch_assemblies_scaffold } + + ch_assemblies_no_scaffold + .mix(ch_assemblies_scaffold) .set { ch_main_assembled } diff --git a/subworkflows/local/utils_nfcore_genomeassembler_pipeline/main.nf b/subworkflows/local/utils_nfcore_genomeassembler_pipeline/main.nf index 6ac598bb..49ded409 100644 --- a/subworkflows/local/utils_nfcore_genomeassembler_pipeline/main.nf +++ b/subworkflows/local/utils_nfcore_genomeassembler_pipeline/main.nf @@ -106,14 +106,16 @@ workflow PIPELINE_INITIALISATION { hifireads: it.hifireads ?: null, // new in refactor-assemblers strategy: it.strategy ?: params.strategy, + assembler: it.assembler ?: params.assembler, assembler1: it.assembler1 ?: - ["hifiasm","flye"].contains(it.assembler) ? it.assembler : - ["hifiasm","flye"].contains(params.assembler) ? params.assembler : - params.assembler == "flye_hifiasm" ? "flye" : - params.assembler == "hifiasm_hifiasm" ? "hifiasm" - : null, + it.assembler == "hifiasm" || it.assembler == "flye" ? it.assembler : + params.assembler == "hifiasm" || params.assembler == "flye" ? params.assembler : + it.assembler.contains("_") ? it.assembler.tokenize("_")[0] : + params.assembler.contains("_") ? it.assembler.tokenize("_")[0] : + null, assembler2: it.assembler2 ?: - ["hifiasm_on_hifiasm","flye_on_hifiasm"].contains(params.assembler) ? "hifiasm" : + it.assembler.contains("_") ? it.assembler.tokenize("_")[1] : + params.assembler.contains("_") ? it.assembler.tokenize("_")[1] : null, assembly_scaffolding_order: it.assembly_scaffolding_order ?: params.assembly_scaffolding_order ?: "ont_on_hifi", genome_size: it.genome_size ?: params.genome_size, @@ -183,6 +185,14 @@ workflow PIPELINE_INITIALISATION { ch_samplesheet .map { it -> + // Check if assembler1 was set + (it.assembler1 && !it.assembly) + ? + [ + println("Please confirm samplesheet: [sample: $it.meta.id]: assembler1 could not be set and no assembly was provided."), + "invalid" + ] + : null // Check if primers for lima are provided (it.hifi_trim && !it.hifi_primers) ? From cd198cd03f4ddbfbab2e6a9ebc4e4a2180ba249a Mon Sep 17 00:00:00 2001 From: Niklas Schandry Date: Wed, 9 Jul 2025 11:51:34 +0200 Subject: [PATCH 020/162] bugfix commit, update configs for sample-wise args --- conf/modules/QC/alignments.config | 14 +++---- conf/modules/assembly.config | 6 +-- docs/usage.md | 8 ++-- nextflow.config | 10 +++-- subworkflows/local/assemble/main.nf | 37 +++++++++---------- .../local/mapping/map_to_assembly/main.nf | 19 +++++----- subworkflows/local/mapping/map_to_ref/main.nf | 24 ++++++------ .../polishing/medaka/polish_medaka/main.nf | 4 +- .../polishing/pilon/polish_pilon/main.nf | 2 +- subworkflows/local/qc/main.nf | 9 ++--- subworkflows/local/qc/quast/main.nf | 12 +++--- subworkflows/local/scaffolding/links/main.nf | 8 ++-- .../local/scaffolding/longstitch/main.nf | 8 ++-- subworkflows/local/scaffolding/ragtag/main.nf | 6 +-- .../main.nf | 37 ++++++++++--------- 15 files changed, 105 insertions(+), 99 deletions(-) diff --git a/conf/modules/QC/alignments.config b/conf/modules/QC/alignments.config index f9536164..e14f103d 100644 --- a/conf/modules/QC/alignments.config +++ b/conf/modules/QC/alignments.config @@ -8,7 +8,7 @@ process { saveAs: { filename -> filename.equals('versions.yml') ? null : filename } ] ext.args = { - (params.hifi && params.ont) ? (params.qc_reads == 'ONT' ? "-ax lr:hq" : "-ax map-hifi") : (params.ont) ? "-ax lr:hq" : "-ax map-hifi" + (meta.qc_reads == 'ont' ? "-ax lr:hq" : "-ax map-hifi") } } // Assembly mappings @@ -20,7 +20,7 @@ process { saveAs: { filename -> filename.equals('versions.yml') ? null : filename } ] ext.args = { - (params.hifi && params.ont) ? (params.qc_reads == 'ONT' ? "-ax lr:hq" : "-ax map-hifi") : (params.ont) ? "-ax lr:hq" : "-ax map-hifi" + (meta.qc_reads == 'ont' ? "-ax lr:hq" : "-ax map-hifi") } } withName: '.*MEDAKA:.*MAP_TO_ASSEMBLY.*' { @@ -31,7 +31,7 @@ process { saveAs: { filename -> filename.equals('versions.yml') ? null : filename } ] ext.args = { - (params.hifi && params.ont) ? (params.qc_reads == 'ONT' ? "-ax lr:hq" : "-ax map-hifi") : (params.ont) ? "-ax lr:hq" : "-ax map-hifi" + (meta.qc_reads == 'ont' ? "-ax lr:hq" : "-ax map-hifi") } } withName: '.*PILON:.*MAP_TO_ASSEMBLY.*' { @@ -42,7 +42,7 @@ process { saveAs: { filename -> filename.equals('versions.yml') ? null : filename } ] ext.args = { - (params.hifi && params.ont) ? (params.qc_reads == 'ONT' ? "-ax lr:hq" : "-ax map-hifi") : (params.ont) ? "-ax lr:hq" : "-ax map-hifi" + (meta.qc_reads == 'ont' ? "-ax lr:hq" : "-ax map-hifi") } } withName: '.*LONGSTITCH:.*MAP_TO_ASSEMBLY.*' { @@ -53,7 +53,7 @@ process { saveAs: { filename -> filename.equals('versions.yml') ? null : filename } ] ext.args = { - (params.hifi && params.ont) ? (params.qc_reads == 'ONT' ? "-ax lr:hq" : "-ax map-hifi") : (params.ont) ? "-ax lr:hq" : "-ax map-hifi" + (meta.qc_reads == 'ont' ? "-ax lr:hq" : "-ax map-hifi") } } withName: '.*LINKS:.*MAP_TO_ASSEMBLY.*' { @@ -64,7 +64,7 @@ process { saveAs: { filename -> filename.equals('versions.yml') ? null : filename } ] ext.args = { - (params.hifi && params.ont) ? (params.qc_reads == 'ONT' ? "-ax lr:hq" : "-ax map-hifi") : (params.ont) ? "-ax lr:hq" : "-ax map-hifi" + (meta.qc_reads == 'ont' ? "-ax lr:hq" : "-ax map-hifi") } } withName: '.*RAGTAG:.*MAP_TO_ASSEMBLY.*' { @@ -75,7 +75,7 @@ process { saveAs: { filename -> filename.equals('versions.yml') ? null : filename } ] ext.args = { - (params.hifi && params.ont) ? (params.qc_reads == 'ONT' ? "-ax lr:hq" : "-ax map-hifi") : (params.ont) ? "-ax lr:hq" : "-ax map-hifi" + (meta.qc_reads == 'ont' ? "-ax lr:hq" : "-ax map-hifi") } } } diff --git a/conf/modules/assembly.config b/conf/modules/assembly.config index eb18c7ef..b2c964b2 100644 --- a/conf/modules/assembly.config +++ b/conf/modules/assembly.config @@ -3,7 +3,7 @@ process { ext.args = { [ meta.genome_size ? "--genome-size ${meta.genome_size}" : '', - params.flye_args + meta.flye_args ].join(" ").trim() } publishDir = [ @@ -13,7 +13,7 @@ process { ] } withName: HIFIASM { - ext.args = { [ params.hifiasm_args ].join(" ").trim() } + ext.args = { [ meta.hifiasm_args ].join(" ").trim() } publishDir = [ path: { "${params.outdir}/${meta.id}/assembly/hifiasm/" }, mode: params.publish_dir_mode, @@ -21,7 +21,7 @@ process { ] } withName: HIFIASM_ONT { - ext.args = { [ params.hifiasm_args, "--ont" ].join(" ").trim() } + ext.args = { [ meta.hifiasm_args, "--ont" ].join(" ").trim() } publishDir = [ path: { "${params.outdir}/${meta.id}/assembly/hifiasm_ont/" }, mode: params.publish_dir_mode, diff --git a/docs/usage.md b/docs/usage.md index c9b69753..c48a0fec 100644 --- a/docs/usage.md +++ b/docs/usage.md @@ -40,11 +40,11 @@ Sample parameters take priority over global parameters, if both are provided the Assembly strategy is controlled via `strategy` (either pipeline parameter or sample-setting), and assembler(s) used are chosen via `assembler` (either pipeline parameter or sample-setting) `nf-core/genomeassembler` currently supports the following assembly strategies: -- single: Use a single assembler for a single type of read. The assembler should be provided via `assembler` and can be `hifiasm` or `flye`. -- hybrid: Use a single assembler for a combined assembly of ONT and HiFi reads. The assembler should be provided via `assembler`. Only `hifiasm` supports hybrid assembly. -- scaffold: Assemble ONT reads and HiFi indepently and scaffold one assembly onto the other. `assembler` should be: "flye_hifiasm" or "hifiasm_hifiasm" (ONT_HiFi). When running in "scaffold" mode, `assembly_scaffolding_order` can be used to control which assembly gets scaffolded onto which, the default being "ont_on_hifi" where ONT assembly is scaffolded onto HifI assembly. +- single (default): Use a single assembler for a single type of read. The assembler should be provided via `assembler` and can be `hifiasm` (default) or `flye`. +- hybrid: Use a single assembler for a combined assembly of ONT and HiFi reads. The assembler should be provided via `assembler`. Currently, only `hifiasm` supports hybrid assembly. +- scaffold: Assemble ONT reads and HiFi indepently and scaffold one assembly onto the other. `assembler` has to be provided as "ont_hifi" and could for example be: "flye_hifiasm" to assemble ont reads with `flye` and hifi reads with `hifiasm` or "hifiasm_hifiasm" to assemble both ont and hifi reads indepently with `hifiasm`. When running in "scaffold" mode, `assembly_scaffolding_order` can be used to control which assembly gets scaffolded onto which, the default being "ont_on_hifi" where ONT assembly is scaffolded onto HifI assembly. -Assembler specific arguments can be provided, for the assembler via `hifiasm_args` or `flye_args`, or with more fine-grained control via `assembler1_args` and `assembler2_args`. +Assembler specific arguments can be provided for the assembler via `hifiasm_args` or `flye_args`, or with more fine-grained control via `assembler1_args` and `assembler2_args` for scaffolding. `assembler1_args` controls the parameters for the assembler in `single` and `hybrid` strategies, or for the assembler used of ONT reads when using `scaffold`. `assembler2_args` can be used to pass arguments to the assembler used for HiFi reads in `scaffold` mode. `assembler[1,2]_args` can only be set via samplesheet. diff --git a/nextflow.config b/nextflow.config index e127a473..982ff79b 100644 --- a/nextflow.config +++ b/nextflow.config @@ -43,8 +43,9 @@ params { input = '' // input file outdir = null // outdir // Assembly strategy - strategy = null // assembly_strategy - + strategy = "single" // assembly_strategy + ontreads = null + hifireads = null // == Read QC and trimming == // -- ONT ont_collect = false // collect ONT reads into a single file @@ -59,12 +60,13 @@ params { hifi_primers = null // if lima, then this needs to be a path to a list of primers // -- Short read -- use_short_reads = false // short reads available? - trim_short_reads = true // trim short reads? + trim_short_reads = false // trim short reads? shortread_F = null shortread_R = null paired = null // == ASSEMBLY == - assembler = "flye" // assembler to use + assembler = "hifiasm" // assembler to use + assembly_scaffolding_order = "ont_on_hifi" // -- Assembly: Flye -- genome_size = null // genomesize, optional, can be estimated from ONT reads flye_mode = '--nano-hq' // flye mode diff --git a/subworkflows/local/assemble/main.nf b/subworkflows/local/assemble/main.nf index 65c0f961..13d454cc 100644 --- a/subworkflows/local/assemble/main.nf +++ b/subworkflows/local/assemble/main.nf @@ -37,8 +37,6 @@ workflow ASSEMBLE { } .set { ch_main_assemble_branched } - - ch_main_assemble_branched.scaffold.view { it -> "BRANCHED SCAFFOLD: $it"} // Flye inputs: ch_main_assemble_branched .single @@ -46,18 +44,19 @@ workflow ASSEMBLE { .mix( ch_main_assemble_branched .scaffold - .filter { it -> it.assembler1 == "flye" || it.assembler2 == "flye"} + .filter { it -> it.assembler1 == "flye" || it.assembler2 == "flye" } ) .set { ch_main_assemble_flye } - // Assembly flye branch - ch_main_assemble_flye.view { it -> "ASSEMBLE_FLYE: $it"} + + // Assembly flye branch ch_main_assemble_flye .multiMap { it -> reads: [ [ id: it.meta.id, - genome_size: it.genome_size + genome_size: it.genome_size, + flye_args: it.fyle_args ], it.assembler1 == "flye" ? it.ontreads : (it.assembler2 == "flye" ? it.hifireads : null), ] @@ -87,12 +86,12 @@ workflow ASSEMBLE { ) .set { ch_main_assemble_hifi_hifiasm } - HIFIASM(ch_main_assemble_hifi_hifiasm.map { it -> [ it.meta, it.hifireads, it.ontreads ?: [] ] }, + HIFIASM(ch_main_assemble_hifi_hifiasm.map { it -> [ [id: it.meta.id, hifiasm_args: it.hifiasm_args], it.hifireads, it.ontreads ?: [] ] }, [[], [], []], [[], [], []], [[], []]) - GFA_2_FA_HIFI(HIFIASM.out.processed_unitigs) + GFA_2_FA_HIFI( HIFIASM.out.processed_unitigs.map { meta, fasta -> [[id: meta.id], fasta] } ) ch_versions = ch_versions.mix(HIFIASM.out.versions).mix(GFA_2_FA_HIFI.out.versions) @@ -108,9 +107,9 @@ workflow ASSEMBLE { .set { ch_main_assemble_ont_hifiasm } - HIFIASM_ONT(ch_main_assemble_ont_hifiasm.map { it -> [ it.meta, it.ontreads, [] ] }, [[], [], []], [[], [], []], [[], []]) + HIFIASM_ONT(ch_main_assemble_ont_hifiasm.map { it -> [ [id: it.meta.id, hifiasm_args: it.hifiasm_args], it.ontreads, [] ] }, [[], [], []], [[], [], []], [[], []]) - GFA_2_FA_ONT(HIFIASM_ONT.out.processed_unitigs) + GFA_2_FA_ONT( HIFIASM_ONT.out.processed_unitigs.map { meta, fasta -> [[id: meta.id], fasta] } ) ch_versions = ch_versions.mix(HIFIASM_ONT.out.versions).mix(GFA_2_FA_ONT.out.versions) @@ -232,10 +231,10 @@ workflow ASSEMBLE { // hifiasm_hifiasm hifiasm_ont_assemblies - .filter { it -> it.strategy == "scaffold" && it.assembler1 == "hifiasm" && it.assembler2 == "flye" } + .filter { it -> it.strategy == "scaffold" && it.assembler1 == "hifiasm" && it.assembler2 == "hifiasm" } .map { it -> it.collect { entry -> [ entry.value, entry ] } } .join( hifiasm_hifi_assemblies - .filter{ it -> it.strategy == "scaffold" && it.assembler1 == "hifiasm" && it.assembler2 == "flye" } + .filter{ it -> it.strategy == "scaffold" && it.assembler1 == "hifiasm" && it.assembler2 == "hifiasm" } .map { it -> [ meta: it.meta, hifiasm_assembly: it.assembly2 ] } .map { it -> it.collect { entry -> [ entry.value, entry ] } } ) @@ -289,6 +288,8 @@ workflow ASSEMBLE { .mix( ch_main_assembled ) .set { ch_main_to_mapping } + //ch_main_to_mapping.view { it -> "TO MAPPING: $it"} + ch_main_to_mapping .branch { it -> @@ -319,18 +320,17 @@ workflow ASSEMBLE { ch_ref_mapping_branched .to_map - .multiMap { + .map { it -> - reads: [it.meta, it.qc_reads] - ref: [it.meta, it.ref_fasta] + [ [id: it.meta.id, qc_reads: it.qc_reads], it.qc_reads_path, it.ref_fasta ] } - .set {map_to_ref_in} + .set { map_to_ref_in } - MAP_TO_REF(map_to_ref_in.reads, map_to_ref_in.ref) + MAP_TO_REF(map_to_ref_in) // returns meta: [id] ch_ref_mapping_branched .to_map - .map { it -> it - it.subMap["ref_map_bam"] } + .map { it -> it - it.subMap("ref_map_bam") } .map { it -> it.collect { entry -> [ entry.value, entry ] } } .join( MAP_TO_REF.out.ch_aln_to_ref_bam @@ -353,7 +353,6 @@ workflow ASSEMBLE { .map { it -> [it.meta, it.assembly] } .set { scaffolds } - QC(ch_main_to_qc, scaffolds, meryl_kmers) ch_versions = ch_versions.mix(QC.out.versions) diff --git a/subworkflows/local/mapping/map_to_assembly/main.nf b/subworkflows/local/mapping/map_to_assembly/main.nf index 2489a8ed..a4444755 100644 --- a/subworkflows/local/mapping/map_to_assembly/main.nf +++ b/subworkflows/local/mapping/map_to_assembly/main.nf @@ -3,23 +3,24 @@ include { BAM_STATS_SAMTOOLS as BAM_STATS } from '../../../nf-core/bam_stats_sam workflow MAP_TO_ASSEMBLY { take: - in_reads - genome_assembly + map_assembly // meta: [id, qc_reads], reads, refs main: Channel.empty().set { ch_versions } // map reads to assembly - in_reads - .join(genome_assembly) - .set { map_assembly } ALIGN(map_assembly, true, 'bai', false, false) - ALIGN.out.bam.set { aln_to_assembly_bam } - ALIGN.out.index.set { aln_to_assembly_bai } + ALIGN.out.bam + .map {meta, bam -> [ [id: meta.id], bam ]} + .set { aln_to_assembly_bam } + + ALIGN.out.index + .map {meta, bai -> [ [id: meta.id], bai ]} + .set { aln_to_assembly_bai } map_assembly - .map { meta, _reads, fasta -> [meta, fasta] } + .map { meta, _reads, fasta -> [[id: meta.id], fasta] } .set { ch_fasta } aln_to_assembly_bam @@ -31,6 +32,6 @@ workflow MAP_TO_ASSEMBLY { versions = ch_versions.mix(ALIGN.out.versions).mix(BAM_STATS.out.versions) emit: - aln_to_assembly_bam + aln_to_assembly_bam // [id], bam versions } diff --git a/subworkflows/local/mapping/map_to_ref/main.nf b/subworkflows/local/mapping/map_to_ref/main.nf index 2293e4b8..ec177f42 100644 --- a/subworkflows/local/mapping/map_to_ref/main.nf +++ b/subworkflows/local/mapping/map_to_ref/main.nf @@ -3,26 +3,28 @@ include { BAM_STATS_SAMTOOLS as BAM_STATS } from '../../../nf-core/bam_stats_sam workflow MAP_TO_REF { take: - in_reads - ch_refs + ch_map_ref // meta: [id, qc_reads], reads, refs main: Channel.empty().set { ch_versions } + // Map reads to reference - in_reads - .join(ch_refs) - .set { ch_map_ref_in } + ALIGN(ch_map_ref, true, 'bai', false, false) - ALIGN(ch_map_ref_in, true, 'bai', false, false) + ALIGN.out.bam + .map { meta, bam -> [ [id: meta.id], bam ] } + .set { ch_aln_to_ref_bam } - ALIGN.out.bam.set { ch_aln_to_ref_bam } + ALIGN.out.index + .map {meta, bai -> [ [id: meta.id], bai ]} + .set { aln_to_ref_bai } ch_aln_to_ref_bam - .join(ALIGN.out.index) + .join(aln_to_ref_bai) .set { ch_aln_to_ref_bam_bai } - ch_map_ref_in - .map { meta, _reads, fasta -> [meta, fasta] } + ch_map_ref + .map { meta, _reads, fasta -> [[id: meta.id], fasta] } .set { ch_fasta } BAM_STATS(ch_aln_to_ref_bam_bai, ch_fasta) @@ -30,6 +32,6 @@ workflow MAP_TO_REF { versions = ch_versions.mix(ALIGN.out.versions).mix(BAM_STATS.out.versions) emit: - ch_aln_to_ref_bam + ch_aln_to_ref_bam // [id], bam versions } diff --git a/subworkflows/local/polishing/medaka/polish_medaka/main.nf b/subworkflows/local/polishing/medaka/polish_medaka/main.nf index 675ef7ec..37485a70 100644 --- a/subworkflows/local/polishing/medaka/polish_medaka/main.nf +++ b/subworkflows/local/polishing/medaka/polish_medaka/main.nf @@ -35,7 +35,7 @@ workflow POLISH_MEDAKA { ) // After joining re-create the maps from the stored map .map { it -> it.collect { _entry, map -> [ (map.key): map.value ] }.collectEntries() } - .map { it -> it - it.subMap["polished_medaka"] + [polished: [medaka: it.polished.medaka ]]} + .map { it -> it - it.subMap("polished_medaka") + [polished: [medaka: it.polished.medaka ]]} .set { ch_medaka_out } ch_main @@ -46,7 +46,7 @@ workflow POLISH_MEDAKA { ch_versions = ch_versions.mix(RUN_MEDAKA.out.versions) QC( - ch_medaka_out.map { it -> it - it.subMap["assembly_map_bam"] }, + ch_medaka_out.map { it -> it - it.subMap("assembly_map_bam") + [assembly_map_bam: null] }, polished_assembly, meryl_kmers ) diff --git a/subworkflows/local/polishing/pilon/polish_pilon/main.nf b/subworkflows/local/polishing/pilon/polish_pilon/main.nf index 9743802d..a4017a00 100644 --- a/subworkflows/local/polishing/pilon/polish_pilon/main.nf +++ b/subworkflows/local/polishing/pilon/polish_pilon/main.nf @@ -47,7 +47,7 @@ workflow POLISH_PILON { ch_versions = ch_versions.mix(RUN_PILON.out.versions) - QC(ch_main.map { it -> it - it.submap["assembly_map_bam"]}, pilon_polished, meryl_kmers) + QC(ch_main.map { it -> it - it.submap("assembly_map_bam") + [assembly_map_bam: null]}, pilon_polished, meryl_kmers) ch_versions = ch_versions.mix(QC.out.versions) diff --git a/subworkflows/local/qc/main.nf b/subworkflows/local/qc/main.nf index a40f647c..80b8fedb 100644 --- a/subworkflows/local/qc/main.nf +++ b/subworkflows/local/qc/main.nf @@ -49,17 +49,16 @@ workflow QC { ch_map_branched .map_to_assembly .map { - it -> [it.meta, it.qc_reads] + it -> [ it.meta, it.qc_reads_path, it.qc_reads ] } .join(scaffolds) .multiMap { - meta, reads, target_scaffolds -> - reads: [meta, reads] - scaffolds: [meta, target_scaffolds] + meta, reads, qc_reads, target_scaffolds -> + reads: [[id: meta.id, qc_reads:qc_reads], reads, target_scaffolds] } .set { map_assembly_in } - MAP_TO_ASSEMBLY(map_assembly_in.reads, map_assembly_in.scaffolds) + MAP_TO_ASSEMBLY(map_assembly_in) // create main channel with mappings ch_map_branched diff --git a/subworkflows/local/qc/quast/main.nf b/subworkflows/local/qc/quast/main.nf index 4394f3d9..3ac79855 100644 --- a/subworkflows/local/qc/quast/main.nf +++ b/subworkflows/local/qc/quast/main.nf @@ -21,17 +21,19 @@ workflow RUN_QUAST { quast_in: [ it.meta, it.qc_target, - it.ref_fasta, - [], - it.reference_map_bam, + it.ref_fasta ?: [], + it.ref_gff?: [], + it.ref_map_bam ?: [], it.assembly_map_bam ] use_ref: it.use_ref } .set { quast_in } + + quast_in.quast_in.view { it -> "QUAST_IN: $it"} /* - * Run QUAST - */ + * Run QUAST + */ QUAST(quast_in.quast_in, quast_in.use_ref, false) QUAST.out.results.set { quast_results } QUAST.out.tsv.set { quast_tsv } diff --git a/subworkflows/local/scaffolding/links/main.nf b/subworkflows/local/scaffolding/links/main.nf index be524eb6..841ba933 100644 --- a/subworkflows/local/scaffolding/links/main.nf +++ b/subworkflows/local/scaffolding/links/main.nf @@ -13,7 +13,7 @@ workflow RUN_LINKS { ch_main .multiMap { assembly: [it.meta, it.polish.pilon ?: it.polish.medaka ?: it.assembly] - reads: [it.meta, it.qc_reads] + reads: [it.meta, it.qc_reads_path] } .set { links_in } @@ -29,15 +29,15 @@ workflow RUN_LINKS { .map { it -> it.collect { entry -> [ entry.value, entry ] } } ) .map { it -> it.collect { _entry, map -> [ (map.key): map.value ] }.collectEntries() } - .set { ch_main } + .set { ch_main_scaffolded } ch_versions = ch_versions.mix(LINKS.out.versions) - QC(ch_main.map { it -> it - it.submap["assembly_map_bam"]}, scaffolds, meryl_kmers) + QC(ch_main_scaffolded.map { it -> it - it.subMap("assembly_map_bam") + [assembly_map_bam: null] }, scaffolds, meryl_kmers) ch_versions = ch_versions.mix(QC.out.versions) - ch_main + ch_main_scaffolded .filter { it -> it.lift_annotations } diff --git a/subworkflows/local/scaffolding/longstitch/main.nf b/subworkflows/local/scaffolding/longstitch/main.nf index 046c3fb4..1fcde79a 100644 --- a/subworkflows/local/scaffolding/longstitch/main.nf +++ b/subworkflows/local/scaffolding/longstitch/main.nf @@ -16,7 +16,7 @@ workflow RUN_LONGSTITCH { [ it.meta, it.polish.pilon ?: it.polish.medaka ?: it.assembly, - it.qc_reads, + it.qc_reads_path, it.genome_size ] } @@ -35,15 +35,15 @@ workflow RUN_LONGSTITCH { .map { it -> it.collect { entry -> [ entry.value, entry ] } } ) .map { it -> it.collect { _entry, map -> [ (map.key): map.value ] }.collectEntries() } - .set { ch_main } + .set { ch_main_scaffolded } ch_versions = ch_versions.mix(LONGSTITCH.out.versions) - QC(ch_main.map { it -> it - it.submap["assembly_map_bam"]}, scaffolds, meryl_kmers) + QC(ch_main_scaffolded.map { it -> it - it.subMap("assembly_map_bam") + [assembly_map_bam: null] }, scaffolds, meryl_kmers) ch_versions = ch_versions.mix(QC.out.versions) - ch_main + ch_main_scaffolded .filter { it -> it.lift_annotations } diff --git a/subworkflows/local/scaffolding/ragtag/main.nf b/subworkflows/local/scaffolding/ragtag/main.nf index fe9cc0bd..28fe90e0 100644 --- a/subworkflows/local/scaffolding/ragtag/main.nf +++ b/subworkflows/local/scaffolding/ragtag/main.nf @@ -30,13 +30,13 @@ workflow RUN_RAGTAG { .map { it -> it.collect { entry -> [ entry.value, entry ] } } ) .map { it -> it.collect { _entry, map -> [ (map.key): map.value ] }.collectEntries() } - .set { ch_main } + .set { ch_main_scaffolded } - QC(ch_main.map { it -> it - it.submap["assembly_map_bam"]}, scaffolds, meryl_kmers) + QC(ch_main_scaffolded.map { it -> it - it.subMap("assembly_map_bam") + [assembly_map_bam: null] }, scaffolds, meryl_kmers) ch_versions = ch_versions.mix(QC.out.versions) - ch_main + ch_main_scaffolded .filter { it -> it.lift_annotations } diff --git a/subworkflows/local/utils_nfcore_genomeassembler_pipeline/main.nf b/subworkflows/local/utils_nfcore_genomeassembler_pipeline/main.nf index 49ded409..0bdbb9b3 100644 --- a/subworkflows/local/utils_nfcore_genomeassembler_pipeline/main.nf +++ b/subworkflows/local/utils_nfcore_genomeassembler_pipeline/main.nf @@ -102,15 +102,15 @@ workflow PIPELINE_INITIALISATION { meta: [id: it.sample], // new in refactor-assemblies group: it.group ?: "none", - ontreads: it.ontreads ?: null, - hifireads: it.hifireads ?: null, + ontreads: it.ontreads ?: params.ontreads, + hifireads: it.hifireads ?: params.hifireads, // new in refactor-assemblers strategy: it.strategy ?: params.strategy, assembler: it.assembler ?: params.assembler, assembler1: it.assembler1 ?: it.assembler == "hifiasm" || it.assembler == "flye" ? it.assembler : - params.assembler == "hifiasm" || params.assembler == "flye" ? params.assembler : it.assembler.contains("_") ? it.assembler.tokenize("_")[0] : + params.assembler == "hifiasm" || params.assembler == "flye" ? params.assembler : params.assembler.contains("_") ? it.assembler.tokenize("_")[0] : null, assembler2: it.assembler2 ?: @@ -135,7 +135,7 @@ workflow PIPELINE_INITIALISATION { ont_collect: it.ont_collect ?: params.ont_collect, ont_trim: it.ont_trim ?: params.ont_trim, ont_jellyfish: it.ont_jellyfish ?: (params.ont_jellyfish && it.ontreads), - ont_jellyfish_k: it.ont_jellyfish_k ?: params.kmer_length, + ont_jellyfish_k: it.ont_jellyfish_k ?: params.ont_jellyfish_k, ont_read_length: it.ont_read_length ?: params.read_length, hifi_trim: it.hifi_trim ?: params.hifi_trim, hifi_primers: it.hifi_primers ?: params.hifi_primers, @@ -151,17 +151,17 @@ workflow PIPELINE_INITIALISATION { ref_gff: it.ref_gff ?: params.ref_gff, flye_mode: it.flye_mode ?: params.flye_mode, // assembly already provided? - assembly: it.assembly ?: null, + assembly: it.assembly ?: "", // ref mapping provided? - ref_map_bam: it.ref_map_bam ?: null, + ref_map_bam: it.ref_map_bam ?: params.ref_map_bam ?: null, // assembly mapping provided - assembly_map_bam: it.assembly_map_bam ?: null, + assembly_map_bam: it.assembly_map_bam ?: params.ref_map_bam ?: null, // reads for qc qc_reads: ((it.qc_reads == "ont" || params.qc_reads == "ont") && it.ontreads) ? "ont" : "hifi", - qc_reads_path: it.qc_reads == "ont" ? (it.ontreads) : (it.hifireads), - quast: it.quast ?: params.quast ?: false, - busco: it.busco ?: params.busco ?: false, + qc_reads_path: ((it.qc_reads == "ont" || params.qc_reads == "ont") && it.ontreads) ? (it.ontreads) : (it.hifireads), + quast: it.quast ?: params.quast, + busco: it.busco ?: params.busco, busco_lineage: it.busco_lineage ?: params.busco_lineage, busco_db: it.busco_db ?: params.busco_db, lift_annotations: it.lift_annotations ?: params.lift_annotations, @@ -186,13 +186,13 @@ workflow PIPELINE_INITIALISATION { .map { it -> // Check if assembler1 was set - (it.assembler1 && !it.assembly) + [(!it.assembler1 && !it.assembly) ? [ println("Please confirm samplesheet: [sample: $it.meta.id]: assembler1 could not be set and no assembly was provided."), "invalid" ] - : null + : null, // Check if primers for lima are provided (it.hifi_trim && !it.hifi_primers) ? @@ -200,7 +200,7 @@ workflow PIPELINE_INITIALISATION { println("Please confirm samplesheet: [sample: $it.meta.id]: Please provide the primers used for pacbio sequencing to trim with lima."), "invalid" ] - : null + : null, // Check if reads and strategy match (it.strategy == "single" && it.ontreads && it.hifireads) ? @@ -208,7 +208,7 @@ workflow PIPELINE_INITIALISATION { println("Please confirm samplesheet: [sample: $it.meta.id]: Stragety is $it.strategy, but both types of reads are provided."), "invalid" ] - : null + : null, // Check if assembler can do hybrid (it.strategy == "hybrid" && !hybrid_assemblers.contains(it.assembler1)) ? @@ -216,15 +216,15 @@ workflow PIPELINE_INITIALISATION { println("Please confirm samplesheet: [sample: $it.meta.id]: Hybrid assembly can only be performed with $hybrid_assemblers"), "invalid" ] - : null + : null, // Check if qc reads are specified for hybrid assemblies (it.strategy == "hybrid" && !params.qc_reads) ? [ - println("Please confirm samplesheet: [sample: $it.meta.id]: Please specify which reads should be used for qc: '--qc_reads': 'ONT' or 'HIFI'"), + println("Please confirm samplesheet: [sample: $it.meta.id]: Please specify which reads should be used for qc: '--qc_reads': 'ont' or 'hifi'"), "invalid" ] - : null + : null, // Check if genome_size is given with --scaffold_longstitch (it.scaffold_longstitch && !it.genome_size && !(it.ontreads && params.jellyfish)) ? @@ -232,7 +232,8 @@ workflow PIPELINE_INITIALISATION { println("Please confirm samplesheet: [sample: $it.meta.id]: scaffolding with longstitch requires genome-size. Either provide genome-size estimate, or estimate from ONT reads with --jellyfish"), "invalid" ] - : null + : null, + ] } .collect() // error if >0 samples failed a check above From 896617b220b12734a3d6d4e09a794b36cb06298e Mon Sep 17 00:00:00 2001 From: Niklas Schandry Date: Wed, 9 Jul 2025 13:23:13 +0200 Subject: [PATCH 021/162] running up until including medaka --- subworkflows/local/polishing/main.nf | 28 +++++++++---------- .../polishing/medaka/polish_medaka/main.nf | 12 ++++---- .../polishing/pilon/polish_pilon/main.nf | 5 ++-- subworkflows/local/qc/quast/main.nf | 4 +-- .../main.nf | 6 ++-- 5 files changed, 26 insertions(+), 29 deletions(-) diff --git a/subworkflows/local/polishing/main.nf b/subworkflows/local/polishing/main.nf index 73e5a8b6..ed723173 100644 --- a/subworkflows/local/polishing/main.nf +++ b/subworkflows/local/polishing/main.nf @@ -15,16 +15,16 @@ workflow POLISH { ch_main .branch { it -> - medaka: it.polish_medaka - no_medaka: !it.polish_medaka + medaka: ["medaka","medaka+pilon"].contains(it.polish) + no_medaka: !["medaka","medaka+pilon"].contains(it.polish) } - .set { ch_main } + .set { ch_main_polish } - POLISH_MEDAKA(ch_main.medaka, meryl_kmers) + POLISH_MEDAKA(ch_main_polish.medaka, meryl_kmers) POLISH_MEDAKA.out.ch_main - .mix(ch_main.no_medaka) - .set { ch_main } + .mix(ch_main_polish.no_medaka) + .set { ch_main_polish_pilon } POLISH_MEDAKA.out.busco_out.set { polish_busco_reports } @@ -38,18 +38,18 @@ workflow POLISH { Polishing with short reads using pilon */ - ch_main + ch_main_polish_pilon .branch { it -> - pilon: it.polish_pilon - no_pilon: !it.polish_pilon + pilon: ["pilon","medaka+pilon"].contains(it.polish) + no_pilon: !["pilon","medaka+pilon"].contains(it.polish) } - .set { ch_main } + .set { ch_main_polish_pilon_in } - POLISH_PILON(ch_main.pilon, meryl_kmers) + POLISH_PILON(ch_main_polish_pilon_in.pilon, meryl_kmers) - ch_main.no_pilon.mix(POLISH_PILON.out.ch_main) - .set { ch_main } + ch_main_polish_pilon_in.no_pilon.mix(POLISH_PILON.out.ch_main) + .set { ch_out } polish_busco_reports .concat( @@ -74,7 +74,7 @@ workflow POLISH { versions = ch_versions emit: - ch_main + ch_main = ch_out polish_busco_reports polish_quast_reports polish_merqury_reports diff --git a/subworkflows/local/polishing/medaka/polish_medaka/main.nf b/subworkflows/local/polishing/medaka/polish_medaka/main.nf index 37485a70..d2344a2a 100644 --- a/subworkflows/local/polishing/medaka/polish_medaka/main.nf +++ b/subworkflows/local/polishing/medaka/polish_medaka/main.nf @@ -12,7 +12,7 @@ workflow POLISH_MEDAKA { ch_main .filter { - it -> it.polish.medaka + it -> it.polish_medaka } .multiMap { it -> @@ -25,23 +25,21 @@ workflow POLISH_MEDAKA { RUN_MEDAKA.out.medaka_out.set { polished_assembly } - polished_assembly - .map { it -> [meta: it[0], polished_medaka: it[1]]} - ch_main .map { it -> it.collect { entry -> [ entry.value, entry ] } } .join( polished_assembly + .map { it -> [meta: it[0], polished_medaka: it[1]]} .map { it -> it.collect { entry -> [ entry.value, entry ] } } ) // After joining re-create the maps from the stored map .map { it -> it.collect { _entry, map -> [ (map.key): map.value ] }.collectEntries() } - .map { it -> it - it.subMap("polished_medaka") + [polished: [medaka: it.polished.medaka ]]} + .map { it -> it - it.subMap("polished_medaka") + [polished: [medaka: it.polished_medaka ]]} .set { ch_medaka_out } ch_main .filter { it -> !it.polish_medaka } .mix(ch_medaka_out) - .set { ch_main } + .set { ch_main_out } ch_versions = ch_versions.mix(RUN_MEDAKA.out.versions) @@ -75,7 +73,7 @@ workflow POLISH_MEDAKA { versions = ch_versions emit: - ch_main + ch_main = ch_main_out quast_out = QC.out.quast_out busco_out = QC.out.busco_out merqury_report_files = QC.out.merqury_report_files diff --git a/subworkflows/local/polishing/pilon/polish_pilon/main.nf b/subworkflows/local/polishing/pilon/polish_pilon/main.nf index a4017a00..9e06a728 100644 --- a/subworkflows/local/polishing/pilon/polish_pilon/main.nf +++ b/subworkflows/local/polishing/pilon/polish_pilon/main.nf @@ -31,14 +31,15 @@ workflow POLISH_PILON { .set { pilon_polished } ch_main - .map { it -> it - it.subMap("polished") + [polished_medaka: it.polished.medaka ?: null] } + .map { it -> it - it.subMap("polished") + [polished_medaka: it.polished ? it.polished.medaka : null] } .map { it -> it.collect { entry -> [ entry.value, entry ] } } .join(pilon_polished .map { it -> [ meta: it[0], polished_pilon: it[1] ] } .map { it -> it.collect { entry -> [ entry.value, entry ] } } ) .map { it -> it.collect { _entry, map -> [ (map.key): map.value ] }.collectEntries() } - .map { it -> (it.polish_medaka ? + .map { it -> ( + ["medaka+pilon"].contains(it.polish) ? (it - it.subMap("polished_medaka", "polished_pilon")) : (it - it.subMap("polished_pilon"))) + [polished: [medaka: it.polished_medaka, pilon: it.polished_pilon]] diff --git a/subworkflows/local/qc/quast/main.nf b/subworkflows/local/qc/quast/main.nf index 3ac79855..cc152de8 100644 --- a/subworkflows/local/qc/quast/main.nf +++ b/subworkflows/local/qc/quast/main.nf @@ -29,9 +29,7 @@ workflow RUN_QUAST { use_ref: it.use_ref } .set { quast_in } - - quast_in.quast_in.view { it -> "QUAST_IN: $it"} - /* + /* * Run QUAST */ QUAST(quast_in.quast_in, quast_in.use_ref, false) diff --git a/subworkflows/local/utils_nfcore_genomeassembler_pipeline/main.nf b/subworkflows/local/utils_nfcore_genomeassembler_pipeline/main.nf index 0bdbb9b3..63a2abd9 100644 --- a/subworkflows/local/utils_nfcore_genomeassembler_pipeline/main.nf +++ b/subworkflows/local/utils_nfcore_genomeassembler_pipeline/main.nf @@ -128,9 +128,9 @@ workflow PIPELINE_INITIALISATION { (it.assembler2 == "flye") ? params.flye_args : null, polish: it.polish ?: - (params.polish_medaka && params.polish_pilon) ? "medaka+pilon" : - (params.polish_medaka) ? "medaka" : - (params.polish_pilon) ? "pilon" : + (params.polish_medaka && params.polish_pilon && (it.ontreads || params.ontreads)) ? "medaka+pilon" : + (params.polish_medaka && (it.ontreads || params.ontreads)) ? "medaka" : + (params.polish_pilon && (it.shortread_F || params.shortread_F)) ? "pilon" : null, ont_collect: it.ont_collect ?: params.ont_collect, ont_trim: it.ont_trim ?: params.ont_trim, From 54fdedc7839409665d637e04b3fe530dcee18249 Mon Sep 17 00:00:00 2001 From: Niklas Schandry Date: Wed, 9 Jul 2025 13:46:33 +0200 Subject: [PATCH 022/162] running up until including pilon --- .../polishing/pilon/polish_pilon/main.nf | 35 +++++++++++-------- 1 file changed, 20 insertions(+), 15 deletions(-) diff --git a/subworkflows/local/polishing/pilon/polish_pilon/main.nf b/subworkflows/local/polishing/pilon/polish_pilon/main.nf index 9e06a728..defc0252 100644 --- a/subworkflows/local/polishing/pilon/polish_pilon/main.nf +++ b/subworkflows/local/polishing/pilon/polish_pilon/main.nf @@ -11,18 +11,21 @@ workflow POLISH_PILON { main: Channel.empty().set { ch_versions } - ch_main.branch { - it -> - shortreads: [it.meta, it.shortreads] - assembly: [ - it.meta, - it.polish == "medaka+pilon" ? it.polished.medaka : it.assembly - ] - } - .set { map_sr_in } + ch_main + .multiMap { + it -> + shortreads: [it.meta, it.shortreads] + assembly: [ + it.meta, + it.polish == "medaka+pilon" ? it.polished.medaka : it.assembly + ] + } + .set { map_sr_in } MAP_SR(map_sr_in.shortreads, map_sr_in.assembly) + MAP_SR.out.aln_to_assembly_bam_bai.view { it -> "SR MAPPED: $it"} + ch_versions = ch_versions.mix(MAP_SR.out.versions) RUN_PILON(map_sr_in.assembly, MAP_SR.out.aln_to_assembly_bam_bai) @@ -31,24 +34,26 @@ workflow POLISH_PILON { .set { pilon_polished } ch_main - .map { it -> it - it.subMap("polished") + [polished_medaka: it.polished ? it.polished.medaka : null] } + .map { it -> it - it.subMap("polished") + [polished_medaka: it.polish == "medaka+pilon" ? it.polished.medaka : null] } .map { it -> it.collect { entry -> [ entry.value, entry ] } } - .join(pilon_polished - .map { it -> [ meta: it[0], polished_pilon: it[1] ] } - .map { it -> it.collect { entry -> [ entry.value, entry ] } } + .join( + pilon_polished + .map { it -> [ meta: it[0], polished_pilon: it[1] ] } + .map { it -> it.collect { entry -> [ entry.value, entry ] } } ) .map { it -> it.collect { _entry, map -> [ (map.key): map.value ] }.collectEntries() } .map { it -> ( ["medaka+pilon"].contains(it.polish) ? (it - it.subMap("polished_medaka", "polished_pilon")) : - (it - it.subMap("polished_pilon"))) + + (it - it.subMap("polished_pilon")) + ) + [polished: [medaka: it.polished_medaka, pilon: it.polished_pilon]] } .set { ch_main } ch_versions = ch_versions.mix(RUN_PILON.out.versions) - QC(ch_main.map { it -> it - it.submap("assembly_map_bam") + [assembly_map_bam: null]}, pilon_polished, meryl_kmers) + QC(ch_main.map { it -> it - it.submap("assembly_map_bam") + [assembly_map_bam: null] }, pilon_polished, meryl_kmers) ch_versions = ch_versions.mix(QC.out.versions) From a66f51c0b45b96854292d79dd496524b9e01c160 Mon Sep 17 00:00:00 2001 From: Niklas Schandry Date: Wed, 9 Jul 2025 13:47:24 +0200 Subject: [PATCH 023/162] running up until including pilon (fixed) --- subworkflows/local/polishing/pilon/polish_pilon/main.nf | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/subworkflows/local/polishing/pilon/polish_pilon/main.nf b/subworkflows/local/polishing/pilon/polish_pilon/main.nf index defc0252..0aa1c5e8 100644 --- a/subworkflows/local/polishing/pilon/polish_pilon/main.nf +++ b/subworkflows/local/polishing/pilon/polish_pilon/main.nf @@ -24,8 +24,6 @@ workflow POLISH_PILON { MAP_SR(map_sr_in.shortreads, map_sr_in.assembly) - MAP_SR.out.aln_to_assembly_bam_bai.view { it -> "SR MAPPED: $it"} - ch_versions = ch_versions.mix(MAP_SR.out.versions) RUN_PILON(map_sr_in.assembly, MAP_SR.out.aln_to_assembly_bam_bai) @@ -53,7 +51,7 @@ workflow POLISH_PILON { ch_versions = ch_versions.mix(RUN_PILON.out.versions) - QC(ch_main.map { it -> it - it.submap("assembly_map_bam") + [assembly_map_bam: null] }, pilon_polished, meryl_kmers) + QC(ch_main.map { it -> it - it.subMap("assembly_map_bam") + [assembly_map_bam: null] }, pilon_polished, meryl_kmers) ch_versions = ch_versions.mix(QC.out.versions) From cd5c8eb753074aa83bebee292f17725b06b2b583 Mon Sep 17 00:00:00 2001 From: Niklas Schandry Date: Wed, 9 Jul 2025 14:51:12 +0200 Subject: [PATCH 024/162] running up until report, schema update --- modules/local/jellyfish/count/main.nf | 2 +- nextflow_schema.json | 228 ++++++++++-------- subworkflows/local/jellyfish/main.nf | 4 +- subworkflows/local/scaffolding/links/main.nf | 2 +- .../local/scaffolding/longstitch/main.nf | 4 +- subworkflows/local/scaffolding/main.nf | 1 - subworkflows/local/scaffolding/ragtag/main.nf | 6 +- .../main.nf | 3 +- 8 files changed, 138 insertions(+), 112 deletions(-) diff --git a/modules/local/jellyfish/count/main.nf b/modules/local/jellyfish/count/main.nf index 841f4a38..3a3c62e2 100644 --- a/modules/local/jellyfish/count/main.nf +++ b/modules/local/jellyfish/count/main.nf @@ -25,7 +25,7 @@ process COUNT { cp ${fasta} ${fasta.baseName}.fasta fi jellyfish count \\ - -m ${params.kmer_length} \\ + -m ${meta.ont_jellyfish_k} \\ -s 140M \\ -C \\ -t ${task.cpus} ${fasta.baseName}.fasta diff --git a/nextflow_schema.json b/nextflow_schema.json index 530a99f5..5eff5d0c 100644 --- a/nextflow_schema.json +++ b/nextflow_schema.json @@ -151,103 +151,44 @@ } } }, - "general_parameters": { - "title": "General parameters", + "reference_parameters": { + "title": "Reference Parameters", "type": "object", "description": "Options controlling pipeline behavior", "default": "", "properties": { - "use_ref": { - "type": "boolean", - "description": "use reference genome", - "default": true + "ref_fasta": { + "type": "string", + "description": "Path to reference genome seqeunce (fasta)" }, - "skip_assembly": { - "type": "boolean", - "description": "skip assembly steps", - "help_text": "Skip assembly and perform only qc. Requires 'assembly' column in the samplesheet" + "ref_gff": { + "type": "string", + "description": "Path to reference genome annotations (gff)" }, - "skip_alignments": { + "use_ref": { "type": "boolean", - "description": "skip alignments during qc" + "description": "use reference genome", + "hidden": true } }, "fa_icon": "fas fa-bacon" }, - "ont_options": { - "title": "ONT options", - "type": "object", - "description": "Options for ONT reads", - "default": "", - "properties": { - "ont": { - "type": "boolean", - "description": "ONT reads available?" - }, - "collect": { - "type": "boolean", - "description": "collect ONT reads into a single file" - }, - "porechop": { - "type": "boolean", - "description": "run porechop on ONT reads" - }, - "read_length": { - "type": "integer", - "description": "read length for genomescope (ONT only)", - "minimum": 1 - }, - "jellyfish": { - "type": "boolean", - "description": "run jellyfish on ONT reads to compute k-mer distribution and estimate genome size", - "default": true - }, - "dump": { - "type": "boolean", - "description": "dump jellyfish output" - }, - "kmer_length": { - "type": "integer", - "description": "kmer length to be used for jellyfish", - "default": 21, - "minimum": 1 - } - } - }, - "hifi_options": { - "title": "HiFi options", - "type": "object", - "description": "Options for HiFi reads", - "default": "", - "properties": { - "hifi": { - "type": "boolean", - "description": "HiFi reads available?" - }, - "lima": { - "type": "boolean", - "description": "run lima on HiFi reads?" - }, - "pacbio_primers": { - "type": "string", - "format": "file-path", - "exists": true, - "pattern": "^\\S+\\.fn?a(sta)?$", - "description": "file containing pacbio primers for trimming with lima" - } - } - }, "assembly_options": { "title": "Assembly options", "type": "object", "description": "Options controlling assembly", "default": "", "properties": { + "strategy": { + "type": "string", + "default": "single", + "description": "Assembly strategy to use. Valid choices are `'single'`, `'hybrid'` and `'scaffold'`" + }, "assembler": { "type": "string", "description": "Assembler to use. Valid choices are: `'hifiasm'`, `'flye'`, `'flye_on_hifiasm'` or `hifiasm_on_hifiasm`. `flye_on_hifiasm` will scaffold flye assembly (ont) on hifiasm (hifi) assembly using ragtag. `hifiasm_on_hifiasm` will scaffold hifiasm (ont) onto hifiasm (HiFi) using ragtag", "enum": ["flye", "hifiasm", "flye_on_hifiasm", "hifiasm_on_hifiasm"], - "default": "flye" + "default": "hifiasm" }, "genome_size": { "type": "integer", @@ -264,36 +205,52 @@ "type": "string", "description": "additional args for flye" }, - "hifiasm_ont": { - "type": "boolean", - "description": "Use hifi and ONT reads with `hifiasm --ul`" - }, "hifiasm_args": { "type": "string", "description": "Extra arguments passed to `hifiasm`" + }, + "assembly_scaffolding_order": { + "type": "string", + "default": "ont_on_hifi", + "description": "When strategy is \"scaffold\", which assembly should be scaffolded onto which?" } } }, - "short_read_options": { - "title": "Short read options", + "ont_read_options": { + "title": "ONT read options", "type": "object", - "description": "Options for short reads", + "description": "Options for ONT reads", "default": "", "properties": { - "short_reads": { + "ontreads": { + "type": "string", + "description": "Path to ONT reads" + }, + "ont_trim": { "type": "boolean", - "description": "Short reads available?" + "description": "Trim ont reads with porechop?" }, - "trim_short_reads": { + "ont_jellyfish": { "type": "boolean", - "description": "trim short reads with trimgalore", - "default": true + "description": "Run jellyfish on ONT reads (k-mer analysis)" }, - "meryl_k": { + "ont_jellyfish_k": { "type": "integer", - "description": "kmer length for meryl / merqury", "default": 21, + "description": "k-mer size for jellyfish" + }, + "read_length": { + "type": "integer", + "description": "read length for genomescope (ONT only)", "minimum": 1 + }, + "dump": { + "type": "boolean", + "description": "dump jellyfish output" + }, + "ont_collect": { + "type": "boolean", + "description": "Collect ONT reads from several files?" } } }, @@ -337,6 +294,26 @@ } } }, + "hifi_read_options": { + "title": "HiFi read options", + "type": "object", + "description": "Options for HiFi reads", + "default": "", + "properties": { + "hifireads": { + "type": "string", + "description": "Path to HiFi reads" + }, + "hifi_trim": { + "type": "boolean", + "description": "Trim HiFi reads with lima" + }, + "hifi_primers": { + "type": "string", + "description": "Primers to use with lima" + } + } + }, "qc_options": { "title": "QC options", "type": "object", @@ -346,7 +323,7 @@ "merqury": { "type": "boolean", "default": true, - "description": "Run merqury" + "description": "Run merqury (if short reads are provided)" }, "qc_reads": { "type": "string", @@ -356,8 +333,7 @@ }, "busco": { "type": "boolean", - "description": "Run BUSCO?", - "default": true + "description": "Run BUSCO?" }, "busco_db": { "type": "string", @@ -371,8 +347,19 @@ }, "quast": { "type": "boolean", - "description": "Run quast", - "default": true + "description": "Run quast" + }, + "ref_map_bam": { + "type": "string", + "description": "A mapping (bam) of reads mapped to the reference can be provided for QC. If provided alignment to reference fasta will not run" + }, + "assembly": { + "type": "string", + "description": "Can be used to proved existing assembly will skip assembly and perform downstream steps including qc" + }, + "assembly_map_bam": { + "type": "string", + "description": "A mapping (bam) of reads mapped to the provided assembly can be specified for QC. If provided alignment to the provided assembly fasta will not run" } } }, @@ -388,6 +375,41 @@ "default": true } } + }, + "short_read_options": { + "title": "Short read options", + "type": "object", + "description": "Options for short reads", + "default": "", + "properties": { + "use_short_reads": { + "type": "boolean", + "description": "Use short reads?", + "hidden": true + }, + "trim_short_reads": { + "type": "boolean", + "description": "trim short reads with trimgalore" + }, + "meryl_k": { + "type": "integer", + "description": "kmer length for meryl / merqury", + "default": 21, + "minimum": 1 + }, + "shortread_F": { + "type": "string", + "description": "Path to forward short reads" + }, + "shortread_R": { + "type": "string", + "description": "Path to reverse short reads" + }, + "paired": { + "type": "string", + "description": "Are shortreads paired?" + } + } } }, "allOf": [ @@ -401,19 +423,13 @@ "$ref": "#/$defs/generic_options" }, { - "$ref": "#/$defs/general_parameters" - }, - { - "$ref": "#/$defs/ont_options" - }, - { - "$ref": "#/$defs/hifi_options" + "$ref": "#/$defs/reference_parameters" }, { "$ref": "#/$defs/assembly_options" }, { - "$ref": "#/$defs/short_read_options" + "$ref": "#/$defs/ont_read_options" }, { "$ref": "#/$defs/polishing_options" @@ -421,11 +437,17 @@ { "$ref": "#/$defs/scaffolding_options" }, + { + "$ref": "#/$defs/hifi_read_options" + }, { "$ref": "#/$defs/qc_options" }, { "$ref": "#/$defs/annotations_options" + }, + { + "$ref": "#/$defs/short_read_options" } ] } diff --git a/subworkflows/local/jellyfish/main.nf b/subworkflows/local/jellyfish/main.nf index 71a72c94..fa4e451e 100644 --- a/subworkflows/local/jellyfish/main.nf +++ b/subworkflows/local/jellyfish/main.nf @@ -14,14 +14,14 @@ workflow JELLYFISH { ch_main.map { it -> [ - it.meta, + [id: it.meta.id, ont_jellyfish_k: it.ont_jellyfish_k], it.ontreads ] } .set { samples } COUNT(samples) - COUNT.out.kmers.set { kmers } + COUNT.out.kmers.map {meta, counts -> [[id: meta.id], counts]}.set { kmers } ch_versions = ch_versions.mix(COUNT.out.versions) diff --git a/subworkflows/local/scaffolding/links/main.nf b/subworkflows/local/scaffolding/links/main.nf index 841ba933..9aef834d 100644 --- a/subworkflows/local/scaffolding/links/main.nf +++ b/subworkflows/local/scaffolding/links/main.nf @@ -12,7 +12,7 @@ workflow RUN_LINKS { ch_main .multiMap { - assembly: [it.meta, it.polish.pilon ?: it.polish.medaka ?: it.assembly] + assembly: [it.meta, it.polished ? (it.polished.pilon ?: it.polished.medaka) : it.assembly] reads: [it.meta, it.qc_reads_path] } .set { links_in } diff --git a/subworkflows/local/scaffolding/longstitch/main.nf b/subworkflows/local/scaffolding/longstitch/main.nf index 1fcde79a..53ede958 100644 --- a/subworkflows/local/scaffolding/longstitch/main.nf +++ b/subworkflows/local/scaffolding/longstitch/main.nf @@ -15,7 +15,7 @@ workflow RUN_LONGSTITCH { it -> [ it.meta, - it.polish.pilon ?: it.polish.medaka ?: it.assembly, + it.polished ? (it.polished.pilon ?: it.polished.medaka) : it.assembly, it.qc_reads_path, it.genome_size ] @@ -50,7 +50,7 @@ workflow RUN_LONGSTITCH { .map { it -> [ it.meta, - it.scaffolds_links, + it.scaffolds_longstitch, it.ref_fasta, it.ref_gff ] diff --git a/subworkflows/local/scaffolding/main.nf b/subworkflows/local/scaffolding/main.nf index 5196ecb6..4cb5a538 100644 --- a/subworkflows/local/scaffolding/main.nf +++ b/subworkflows/local/scaffolding/main.nf @@ -31,7 +31,6 @@ workflow SCAFFOLD { .set { links_in } RUN_LINKS(links_in, meryl_kmers) - RUN_LINKS.out.ch_main .map { it -> it.subMap("meta", "scaffolds_links")} .map { it -> it.collect { entry -> [ entry.value, entry ] } } diff --git a/subworkflows/local/scaffolding/ragtag/main.nf b/subworkflows/local/scaffolding/ragtag/main.nf index 28fe90e0..366084d2 100644 --- a/subworkflows/local/scaffolding/ragtag/main.nf +++ b/subworkflows/local/scaffolding/ragtag/main.nf @@ -13,7 +13,11 @@ workflow RUN_RAGTAG { ch_main .multiMap { it -> - assembly: [it.meta, it.polish.pilon ?: it.polish.medaka ?: it.assembly] + assembly: + [ + it.meta, + it.polished ? (it.polished.pilon ?: it.polished.medaka) : it.assembly + ] reference: [it.meta, it.ref_fasta] } .set { ragtag_in } diff --git a/subworkflows/local/utils_nfcore_genomeassembler_pipeline/main.nf b/subworkflows/local/utils_nfcore_genomeassembler_pipeline/main.nf index 63a2abd9..3839b715 100644 --- a/subworkflows/local/utils_nfcore_genomeassembler_pipeline/main.nf +++ b/subworkflows/local/utils_nfcore_genomeassembler_pipeline/main.nf @@ -226,7 +226,7 @@ workflow PIPELINE_INITIALISATION { ] : null, // Check if genome_size is given with --scaffold_longstitch - (it.scaffold_longstitch && !it.genome_size && !(it.ontreads && params.jellyfish)) + (it.scaffold_longstitch && !it.genome_size && !(it.ontreads && params.ont_jellyfish)) ? [ println("Please confirm samplesheet: [sample: $it.meta.id]: scaffolding with longstitch requires genome-size. Either provide genome-size estimate, or estimate from ONT reads with --jellyfish"), @@ -235,6 +235,7 @@ workflow PIPELINE_INITIALISATION { : null, ] } + .map { it -> it.collect() } .collect() // error if >0 samples failed a check above .subscribe { From 7ce8e7bf1eb9f82aff6f46c6d59220c4eca94d03 Mon Sep 17 00:00:00 2001 From: Niklas Schandry Date: Wed, 9 Jul 2025 14:58:52 +0200 Subject: [PATCH 025/162] docs update --- docs/usage.md | 156 ++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 150 insertions(+), 6 deletions(-) diff --git a/docs/usage.md b/docs/usage.md index c48a0fec..c29a18d1 100644 --- a/docs/usage.md +++ b/docs/usage.md @@ -35,6 +35,8 @@ Sample parameters take priority over global parameters, if both are provided the > [!NOTE] > The parameter names will be used in subsequent sections. Since all parameters can be provided per-sample or pipeline wide, no examples will be given. +The list of all parameters that can be provided globally or per sample is at the [end of this page](#sample-parameters) + ## Choice of assembly-strategy and assembler Assembly strategy is controlled via `strategy` (either pipeline parameter or sample-setting), and assembler(s) used are chosen via `assembler` (either pipeline parameter or sample-setting) @@ -45,7 +47,7 @@ Assembly strategy is controlled via `strategy` (either pipeline parameter or sam - scaffold: Assemble ONT reads and HiFi indepently and scaffold one assembly onto the other. `assembler` has to be provided as "ont_hifi" and could for example be: "flye_hifiasm" to assemble ont reads with `flye` and hifi reads with `hifiasm` or "hifiasm_hifiasm" to assemble both ont and hifi reads indepently with `hifiasm`. When running in "scaffold" mode, `assembly_scaffolding_order` can be used to control which assembly gets scaffolded onto which, the default being "ont_on_hifi" where ONT assembly is scaffolded onto HifI assembly. Assembler specific arguments can be provided for the assembler via `hifiasm_args` or `flye_args`, or with more fine-grained control via `assembler1_args` and `assembler2_args` for scaffolding. -`assembler1_args` controls the parameters for the assembler in `single` and `hybrid` strategies, or for the assembler used of ONT reads when using `scaffold`. `assembler2_args` can be used to pass arguments to the assembler used for HiFi reads in `scaffold` mode. +`assembler1_args` controls the parameters for the assembler in `single` and `hybrid` strategies, or for the assembler used for ONT reads when using `scaffold`. `assembler2_args` can be used to pass arguments to the assembler used for HiFi reads in `scaffold` mode. `assembler[1,2]_args` can only be set via samplesheet. ## Samplesheet input @@ -66,7 +68,8 @@ Sample1,/path/reads/sample1ont.fq.gz,/path/reads/sample1hifi.fq.gz,/path/referen ``` The samplesheet _must_ contain a column name `sample` [string]. -Further columns _can_ be: + +Further commonly used columns _can_ be: - `ontreads` [path] for long reads produced with oxford nanopore sequencers - `hifireads` [path] for long reads produced with pacbio sequencers in "HiFi" mode @@ -78,13 +81,11 @@ Further columns _can_ be: - `shortread_R`: shortread reverse file (paired end) - `paired`: [true/false] true if the reads are paired end, false if they are single-end. The `shortreads_R` column should exist if `paired` is `false` but can be empty. +A list of all possible columns can be found at the [end of this page](#sample-parameters) + > [!INFO] > It is strongly recommended to provide all paths as absolute paths -### Multiple runs of the same sample - -For ONT reads, a glob pattern can be provided, matching files will be concatenated into a single file if `--collect` is used. Generally we recommend to provide all reads in a single file. - ## Running the pipeline The typical command for running the pipeline is as follows: @@ -244,3 +245,146 @@ We recommend adding the following line to your environment to limit this (typica ```bash NXF_OPTS='-Xms1g -Xmx4g' ``` + +# nf-core/genomeassembler pipeline parameters + +Assemble genomes from long ONT or pacbio HiFi reads + +## Global parameters + +### Input/output options + +Define where the pipeline should find input data and save output data. + +| Parameter | Description | Type | Default | Required | Hidden | +| ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | -------- | ------- | -------- | ------ | +| `input` | Path to comma-separated file containing information about the samples in the experiment.
HelpYou 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/genomeassembler/usage#samplesheet-input).
| `string` | | True | | +| `outdir` | The output directory where the results will be saved. You have to use absolute paths to storage on Cloud infrastructure. | `string` | | True | | +| `email` | Email address for completion summary.
HelpSet this parameter to your e-mail address to get a summary e-mail with details of the run sent to you when the workflow exits. If set in your user config file | +| (`~/.nextflow/config`) then you don't need to specify this on the command line for every run.
| `string` | | | | + +### Generic options + +Less common options for the pipeline, typically set in a config file. + +| Parameter | Description | Type | Default | Required | Hidden | +| ---------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------- | -------------------------------------------------------- | -------- | ------ | +| `version` | Display version and exit. | `boolean` | | | True | +| `publish_dir_mode` | Method used to save pipeline results to output directory.
HelpThe Nextflow `publishDir` option specifies which intermediate files should be saved to the output directory. This option tells the pipeline what | +| method should be used to move these files. See [Nextflow docs](https://www.nextflow.io/docs/latest/process.html#publishdir) for details.
| `string` | copy | | True | +| `email_on_fail` | Email address for completion summary, only when pipeline fails.
HelpAn email address to send a summary email to when the pipeline is completed - ONLY sent if the pipeline does not exit | +| successfully.
| `string` | | | True | +| `plaintext_email` | Send plain-text email instead of HTML. | `boolean` | | | True | +| `monochrome_logs` | Do not use coloured log outputs. | `boolean` | | | True | +| `hook_url` | Incoming hook URL for messaging service
HelpIncoming hook URL for messaging service. Currently, MS Teams and Slack are supported.
| `string` | | | True | +| `validate_params` | Boolean whether to validate parameters against the schema at runtime | `boolean` | True | | True | +| `pipelines_testdata_base_path` | Base URL or local path to location of pipeline test dataset files | `string` | https://raw.githubusercontent.com/nf-core/test-datasets/ | | True | + +## Sample Parameters + +### Reference Parameters + +Options controlling pipeline behavior + +| Parameter | Description | Type | Default | Required | Hidden | +| ----------- | ------------------------------------------ | --------- | ------- | -------- | ------ | +| `ref_fasta` | Path to reference genome seqeunce (fasta) | `string` | | | | +| `ref_gff` | Path to reference genome annotations (gff) | `string` | | | | +| `use_ref` | use reference genome | `boolean` | | | True | + +### Assembly options + +Options controlling assembly + +| Parameter | Description | Type | Default | Required | Hidden | +| -------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | --------- | ----------- | -------- | ------ | +| `strategy` | Assembly strategy to use. Valid choices are `'single'`, `'hybrid'` and `'scaffold'` | `string` | single | | | +| `assembler` | Assembler to use. Valid choices are: `'hifiasm'`, `'flye'`, `'flye_on_hifiasm'` or `hifiasm_on_hifiasm`. `flye_on_hifiasm` will scaffold flye assembly (ont) on hifiasm (hifi) assembly using ragtag. `hifiasm_on_hifiasm` will scaffold hifiasm (ont) | +| onto hifiasm (HiFi) using ragtag | `string` | hifiasm | | | +| `genome_size` | expected genome size, optional | `integer` | | | | +| `flye_mode` | flye mode | `string` | --nano-hq | | | +| `flye_args` | additional args for flye | `string` | | | | +| `hifiasm_args` | Extra arguments passed to `hifiasm` | `string` | | | | +| `assembly_scaffolding_order` | When strategy is "scaffold", which assembly should be scaffolded onto which? | `string` | ont_on_hifi | | | + +### ONT read options + +Options for ONT reads + +| Parameter | Description | Type | Default | Required | Hidden | +| ----------------- | ------------------------------------------- | --------- | ------- | -------- | ------ | +| `ontreads` | Path to ONT reads | `string` | | | | +| `ont_trim` | Trim ont reads with porechop? | `boolean` | | | | +| `ont_jellyfish` | Run jellyfish on ONT reads (k-mer analysis) | `boolean` | | | | +| `ont_jellyfish_k` | k-mer size for jellyfish | `integer` | 21 | | | +| `read_length` | read length for genomescope (ONT only) | `integer` | | | | +| `dump` | dump jellyfish output | `boolean` | | | | +| `ont_collect` | Collect ONT reads from several files? | `boolean` | | | | + +### Polishing options + +Polishing options + +| Parameter | Description | Type | Default | Required | Hidden | +| --------------- | ------------------------------------------------ | --------- | ------- | -------- | ------ | +| `polish_pilon` | Polish assembly with pilon? Requires short reads | `boolean` | | | | +| `polish_medaka` | Polish assembly with medaka (ONT only) | `boolean` | | | | +| `medaka_model` | model to use with medaka | `string` | | | | + +### Scaffolding options + +Scaffolding options + +| Parameter | Description | Type | Default | Required | Hidden | +| --------------------- | ------------------------------------------ | --------- | ------- | -------- | ------ | +| `scaffold_longstitch` | Scaffold with longstitch? | `boolean` | | | | +| `scaffold_links` | Scaffolding with links? | `boolean` | | | | +| `scaffold_ragtag` | Scaffold with ragtag (requires reference)? | `boolean` | | | | + +### HiFi read options + +Options for HiFi reads + +| Parameter | Description | Type | Default | Required | Hidden | +| -------------- | ------------------------- | --------- | ------- | -------- | ------ | +| `hifireads` | Path to HiFi reads | `string` | | | | +| `hifi_trim` | Trim HiFi reads with lima | `boolean` | | | | +| `hifi_primers` | Primers to use with lima | `string` | | | | + +### QC options + +Options for QC tools + +| Parameter | Description | Type | Default | Required | Hidden | +| ------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------- | --------- | ----------------- | -------- | ------ | +| `merqury` | Run merqury (if short reads are provided) | `boolean` | True | | | +| `qc_reads` | Long reads that should be used for QC when both ONT and HiFi reads are provided. Options are `'ont'` or `'hifi'` | `string` | ont | | | +| `busco` | Run BUSCO? | `boolean` | | | | +| `busco_db` | Path to busco db (optional) | `string` | | | | +| `busco_lineage` | Busco lineage to use | `string` | brassicales_odb10 | | | +| `quast` | Run quast | `boolean` | | | | +| `ref_map_bam` | A mapping (bam) of reads mapped to the reference can be provided for QC. If provided alignment to reference fasta will not run | `string` | | | | +| `assembly` | Can be used to proved existing assembly will skip assembly and perform downstream steps including qc | `string` | | | | +| `assembly_map_bam` | A mapping (bam) of reads mapped to the provided assembly can be specified for QC. If provided alignment to the provided assembly fasta will not run | `string` | | | | + +### Annotations options + +Options controlling annotation liftover + +| Parameter | Description | Type | Default | Required | Hidden | +| ------------------ | ------------------------------------------- | --------- | ------- | -------- | ------ | +| `lift_annotations` | Lift-over annotations (requires reference)? | `boolean` | True | | | + +### Short read options + +Options for short reads + +| Parameter | Description | Type | Default | Required | Hidden | +| ------------------ | -------------------------------- | --------- | ------- | -------- | ------ | +| `use_short_reads` | Use short reads? | `boolean` | | | True | +| `trim_short_reads` | trim short reads with trimgalore | `boolean` | | | | +| `meryl_k` | kmer length for meryl / merqury | `integer` | 21 | | | +| `shortread_F` | Path to forward short reads | `string` | | | | +| `shortread_R` | Path to reverse short reads | `string` | | | | +| `paired` | Are shortreads paired? | `string` | | | | From a4758371e200258436bc6231795e49422677941f Mon Sep 17 00:00:00 2001 From: Niklas Schandry Date: Wed, 9 Jul 2025 15:07:55 +0200 Subject: [PATCH 026/162] working pipeline (no report --- subworkflows/local/prepare_hifi/main.nf | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/subworkflows/local/prepare_hifi/main.nf b/subworkflows/local/prepare_hifi/main.nf index 93ef1605..312684d6 100644 --- a/subworkflows/local/prepare_hifi/main.nf +++ b/subworkflows/local/prepare_hifi/main.nf @@ -31,7 +31,7 @@ workflow PREPARE_HIFI { .multiMap { it -> reads: [it.meta, it.hifireads] - primers: [it.meta, it.hifi_primers] + primers: it.hifi_primers } .set { ch_lima_in } From b789420a4365649a892e0b5ef92ecbb0a483a786 Mon Sep 17 00:00:00 2001 From: Niklas Schandry Date: Thu, 10 Jul 2025 14:57:24 +0200 Subject: [PATCH 027/162] adding additional control for merqury, nanoq, for reporting; WIP --- assets/report/functions/plot_merqury.R | 171 +++--- assets/report/report.qmd | 513 +++++++++--------- docs/usage.md | 1 + modules/local/report/main.nf | 8 +- nextflow.config | 1 + subworkflows/local/prepare_ont/main.nf | 9 + subworkflows/local/prepare_shortreads/main.nf | 10 +- subworkflows/local/qc/main.nf | 1 + .../main.nf | 3 +- workflows/genomeassembler.nf | 20 +- 10 files changed, 398 insertions(+), 339 deletions(-) diff --git a/assets/report/functions/plot_merqury.R b/assets/report/functions/plot_merqury.R index 55bc78b9..e57607e1 100644 --- a/assets/report/functions/plot_merqury.R +++ b/assets/report/functions/plot_merqury.R @@ -1,93 +1,92 @@ -plot_merqury_stats <- function(data, samplename) { - data %>% - filter(sample == paste(samplename)) %>% - ggplot(aes(x = stage, y = assembly * 100 / total)) + - geom_line(aes(group = sample)) + - geom_point(size = 7, color = "black", fill = "white", pch = 21) + - labs( - y = "k-mer completeness [%]", - x = "Stage", - color = "k-mers copy number", - fill = "k-mers copy number", - title = glue::glue("k-mer completeness {samplename} assemblies") - ) +plot_merqury_stats <- function(data, groupname) { + data %>% + filter(group == paste(groupname)) %>% + ggplot(aes(x = stage, y = assembly*100/total, color = sample, fill = sample)) + + geom_line(aes(group = sample)) + + geom_point(size = 7, color = "black", pch=21) + + labs( + y = "k-mer completeness [%]", + x = "Stage", + color = "k-mers copy number", + fill = "k-mers copy number", + title = glue::glue("k-mer completeness {groupname} assemblies") + ) } -plot_merqury_multiplicity <- function(data, samplename) { - y_max <- data %>% - filter(sample == paste(samplename), Assembly != "read-only") %$% - max(Count) - x_max <- data %>% - filter(sample == paste(samplename), Assembly != "read-only") %$% - quantile(Count, .95) - data %>% - filter(sample == paste(samplename)) %>% - mutate(Assembly = case_when(Assembly == "read-only" ~ "Reads", TRUE ~ "Assembly")) %>% - ggplot(aes(x = kmer_multiplicity, y = Count)) + - geom_line(aes(color = Assembly)) + - # geom_area(aes(fill = Assembly), alpha = 0.15,stat = "identity") + - facet_grid(~stage) + - coord_cartesian( - xlim = c(0, x_max * 1.1), - ylim = c(0, y_max * 1.05), - expand = TRUE, - default = FALSE, - clip = "on" - ) + - labs( - x = "kmer multiplicity", - y = "Count", - color = "k-mers from", - fill = "k-mers from", - title = glue::glue("k-mer multiplicity across {samplename} assemblies") - ) + - theme(legend.position = "bottom") + - color_scale_plots + - fill_scale_plots +plot_merqury_multiplicity <- function(data, groupname) { + y_max <- data %>% + filter(group == paste(groupname), Assembly != "read-only") %$% + max(Count) + x_max <- data %>% + filter(group == paste(groupname), Assembly != "read-only") %$% + quantile(Count, .95) + data %>% + filter(group == paste(groupname)) %>% + mutate(Assembly = case_when(Assembly == "read-only" ~ "Reads", TRUE ~ "Assembly")) %>% + ggplot(aes(x = kmer_multiplicity, y = Count)) + + geom_line(aes(color = Assembly)) + + #geom_area(aes(fill = Assembly), alpha = 0.15,stat = "identity") + + facet_grid(sample ~ stage) + + coord_cartesian( + xlim = c(0, x_max * 1.1), + ylim = c(0, y_max * 1.05), + expand = TRUE, + default = FALSE, + clip = "on" + ) + + labs( + x = "kmer multiplicity", + y = "Count", + color = "k-mers from", + fill = "k-mers from", + title = glue::glue("k-mer multiplicity across {groupname} assemblies") + ) + + theme(legend.position = "bottom") + + color_scale_plots + + fill_scale_plots } -plot_merqury_copynumber <- function(data, samplename) { - y_max <- data %>% - filter(sample == paste(samplename), Copies != "read-only") %$% - max(Count) - x_max <- data %>% - filter(sample == paste(samplename), Copies != "read-only") %$% - quantile(Count, .965) - data %>% - filter(sample == paste(samplename)) %>% - ggplot(aes(x = kmer_multiplicity, y = Count)) + - geom_line(aes(color = Copies)) + - # geom_area(aes(fill = Copies), alpha = 0.15, stat = "identity") + - facet_grid(~stage) + - coord_cartesian( - xlim = c(0, x_max * 1.1), - ylim = c(0, y_max * 1.05), - expand = TRUE, - default = FALSE, - clip = "on" - ) + - labs( - x = "kmer multiplicity", - y = "Count", - color = "k-mers copy number", - fill = "k-mers copy number", - title = glue::glue("k-mer copy number across {samplename} assemblies") - ) + - theme(legend.position = "bottom") + - color_scale_plots + - fill_scale_plots +plot_merqury_copynumber <- function(data, groupname) { + y_max <- data %>% + filter(group == paste(groupname), Copies != "read-only") %$% + max(Count) + x_max <- data %>% + filter(group == paste(groupname), Copies != "read-only") %$% + quantile(Count, .965) + data %>% + filter(group == paste(groupname)) %>% + ggplot(aes(x = kmer_multiplicity, y = Count)) + + geom_line(aes(color = Copies)) + + #geom_area(aes(fill = Copies), alpha = 0.15, stat = "identity") + + facet_grid(sample ~ stage) + + coord_cartesian( + xlim = c(0, x_max * 1.1), + ylim = c(0, y_max * 1.05), + expand = TRUE, + default = FALSE, + clip = "on" + ) + + labs( + x = "kmer multiplicity", + y = "Count", + color = "k-mers copy number", + fill = "k-mers copy number", + title = glue::glue("k-mer copy number across {groupname} assemblies") + ) + + theme(legend.position = "bottom") + + color_scale_plots + + fill_scale_plots } -plot_merqury_qv <- function(data, samplename) { - data %>% - filter(sample == paste(samplename)) %>% - ggplot(aes(x = stage, y = QV)) + - geom_line(aes(group = sample)) + - geom_point( - pch = 21, - color = "black", - fill = "white", - size = 5 - ) + - labs(y = "QV", x = "Stage", title = "QV across assembly stages") +plot_merqury_qv <- function(data, groupname) { + data %>% + filter(group == paste(groupname)) %>% + ggplot(aes(x = stage, y = QV, color = sample, fill = sample)) + + geom_line(aes(group = sample)) + + geom_point( + pch = 21, + color = "black", + size = 5 + ) + + labs(y = "QV", x = "Stage", title = "QV across assembly stages") } diff --git a/assets/report/report.qmd b/assets/report/report.qmd index 16cb578c..b91db08c 100644 --- a/assets/report/report.qmd +++ b/assets/report/report.qmd @@ -1,17 +1,16 @@ --- title: "nf-core/genomeassembler report" -author: "" format: dashboard editor: source nav-buttons: - icon: github - href: https://github.com/nf-core/genomeassembler params: - nanoq: false - busco: false - quast: false - jellyfish: false - merqury: false + nanoq: true + busco: true + quast: true + jellyfish: true + merqury: true --- ```{r load libraries and functions} @@ -23,21 +22,43 @@ params: library(tidyverse) library(magrittr) library(plotly) +#library(gt) # Load functions -list.files("functions", full.names = T, pattern = ".R") %>% +list.files("functions", + full.names = T, + pattern = ".R") %>% map(\(x) source(x)) # Set default ggplot theme theme_set(theme_bw(base_size = 14, base_family = "Arial")) theme_update(strip.background = element_blank(), - axis.text.x = element_text(angle = 70, hjust = 1)) + axis.text.x = element_text(angle = 70, hjust = 1)) ## Colors, these come from the khroma package ("muted") ### For <=9 stages: -color_scale_plots <- scale_color_manual(values = c("#CC6677", "#332288", "#DDCC77", "#117733", "#88CCEE", "#882255", "#44AA99", "#999933", "#AA4499"), na.value = "#DDDDDD") -fill_scale_plots <- scale_fill_manual(values = c("#CC6677", "#332288", "#DDCC77", "#117733", "#88CCEE", "#882255", "#44AA99", "#999933", "#AA4499"), na.value = "#DDDDDD") +colors_9 <- c( + "#CC6677", + "#332288", + "#DDCC77", + "#117733", + "#88CCEE", + "#882255", + "#44AA99", + "#999933", + "#AA4499" +) +color_scale_plots <- scale_color_manual(values = colors_9, na.value = "#DDDDDD") +fill_scale_plots <- scale_fill_manual(values = colors_9, na.value = "#DDDDDD") + + # Base directory containing reports -data_base = "data/" +data_base <- "data/" + +# groups +groups <- yaml::read_yaml("examples/yml/example.yml") %>% + map_dfr(\(row) + data.frame(sample = pluck(row, 1, "id"), group = pluck(row, 1, "group"))) %>% + mutate(group = case_when(group %in% c("","null") ~ sample, TRUE ~ group)) ``` # About @@ -60,7 +81,8 @@ nanoq was not included in the pipeline run, no ONT reads were included. nanoq_reports <- list.files(paste0(data_base, "nanoq"), pattern = "report.json", full.names = T) %>% - map_dfr(\(x) read_nanoq(x)) + map_dfr(\(x) read_nanoq(x)) %>% + left_join(groups, by = join_by(sample)) ``` ```{r} @@ -71,12 +93,12 @@ nanoq_reports <- list.files(paste0(data_base, "nanoq"), # This is an rmd chunk in plain text. dir.create("nanoq_files") -for (i in 1:length(unique(nanoq_reports$sample))) { +for (i in 1:length(unique(nanoq_reports$group))) { paste0('```{r}\n #| title: "Nanoq read statistics" p <- nanoq_reports %>% filter(stat %in% c("Median Length", "Longest", "Median Quality","Bases")) %>% - filter(sample == "', unique(nanoq_reports$sample)[i], '") %>% + filter(group == "', unique(nanoq_reports$group)[i], '") %>% mutate(stat=fct_relevel(stat,c("Bases","Longest","Median Length","Median Quality"))) %>% ggplot(aes(x = sample, y = val)) + geom_line() + @@ -90,7 +112,7 @@ paste0('```{r}\n legend.title = element_blank(), panel.grid.minor = element_blank()) ggplotly(p)\n```') %>% - write_lines(glue::glue("nanoq_files/_{ unique(nanoq_reports$sample)[i] }_nanoq.Rmd")) + write_lines(glue::glue("nanoq_files/_{ unique(nanoq_reports$group)[i] }_nanoq.Rmd")) } ``` @@ -102,36 +124,36 @@ paste0('```{r}\n #| eval: !expr params$nanoq #| results: asis -# This loop creates one tab per sample. +# This loop creates one tab per group ## Each tab contains 3 valueboxes ## Below the valueboxes, the sample-specific plot code generated above is inserted -for (i in 1:length(unique(nanoq_reports$sample))) { - cat(paste0('## ', unique(nanoq_reports$sample)[i], '\n\n'), +for (i in 1:length(unique(nanoq_reports$group))) { + cat(paste0('## ', unique(nanoq_reports$group)[i], '\n\n'), paste0('### { width = 30% }', '\n\n'), paste0('::: {.valuebox icon="magic" color="primary" title="Total bases sequenced"}','\n'), paste0(nanoq_reports %>% filter(stat == "Bases") %>% - filter(sample == unique(nanoq_reports$sample)[i],) %$% + filter(group == unique(nanoq_reports$group)[i],) %$% sum(val) %>% format(scientific=-1,trim=T, digits = 3, drop0trailing=T),'\n'), paste0(':::', '\n\n'), paste0('::: {.valuebox icon="collection" color="secondary" title="Number of reads"}', '\n'), paste0(nanoq_reports %>% filter(stat == "N Reads") %>% - filter(sample == unique(nanoq_reports$sample)[i]) %$% - min(val) %>% + filter(group == unique(nanoq_reports$group)[i]) %$% + sum(val) %>% paste(" bases"), '\n'), paste0(':::', '\n\n'), paste0('::: {.valuebox icon="chevron-double-up" color="success" title="Longest read"}', '\n'), paste0(nanoq_reports %>% filter(stat == "Longest") %>% - filter(sample == unique(nanoq_reports$sample)[i]) %$% + filter(group == unique(nanoq_reports$group)[i]) %$% max(val) %>% paste0(" bases"),'\n'), paste0(':::', '\n\n'), paste0('### ', '\n\n'), - knitr::knit_child(glue::glue('nanoq_files/_{ unique(nanoq_reports$sample)[i] }_nanoq.Rmd'), + knitr::knit_child(glue::glue('nanoq_files/_{ unique(nanoq_reports$group)[i] }_nanoq.Rmd'), envir = globalenv(), quiet = TRUE), paste0('\n\n'), @@ -167,19 +189,18 @@ quast_stats <- list.files(paste0(data_base, "quast"), full.names = T) %>% map_dfr(\(x) { read_quast_report(x) %>% - mutate(sample = str_extract(x %>% basename(), - ".+?(?=_[assembly|links|longstitch|ragtag|medaka|pilon])"), + mutate( + sample = str_extract(x %>% basename(), + ".+?(?=_[assemble|polish|run])"), stage = case_when( str_detect(x, "_ragtag") ~ "RagTag", str_detect(x, "_medaka") ~ "medaka", str_detect(x, "_pilon") ~ "pilon", str_detect(x, "_longstitch") ~ "longstitch", str_detect(x, "_links") ~ "LINKS", - str_detect(x, "assembly") ~ "Assembly", - TRUE ~ "Unknown") - ) - } - ) + str_detect(x, "assemble") ~ "Assembly", + TRUE ~ "Unknown")) }) %>% + left_join(groups, by = join_by(sample)) ``` ```{r quast write length plots} @@ -188,10 +209,10 @@ quast_stats <- list.files(paste0(data_base, "quast"), # This creates code that will generate the length plot based on the contents of the quast report. dir.create("quast_files") dir.create("quast_files/length") -for (i in 1:length(unique(quast_stats$sample))) { +for (i in 1:length(unique(quast_stats$group))) { paste0('```{r}\n p <- quast_stats %>% - filter(sample == "', unique(quast_stats$sample)[i], '") %>% + filter(group == "', unique(quast_stats$group)[i], '") %>% filter(str_detect(stat, "[L].*[59]0")) %>% mutate(stat = fct_relevel(stat, "L50","L90","LG50","LG90")) %>% ggplot(aes(x=stat, y=value)) + @@ -205,7 +226,7 @@ paste0('```{r}\n labs(title = "QUAST: L(G) 50 and 90") + theme(panel.border = element_rect(fill = NA)) ggplotly(p) \n```') %>% - write_lines(glue::glue("quast_files/length/_{ unique(quast_stats$sample)[i] }_quast.Rmd")) + write_lines(glue::glue("quast_files/length/_{ unique(quast_stats$group)[i] }_quast.Rmd")) } ``` @@ -215,10 +236,10 @@ paste0('```{r}\n # This creates code that will generate the contig plots based on the contents of the quast report. dir.create("quast_files/contigs") -for (i in 1:length(unique(quast_stats$sample))) { +for (i in 1:length(unique(quast_stats$group))) { paste0('```{r}\n p <- quast_stats %>% - filter(sample == "', unique(quast_stats$sample)[i], '") %>% + filter(group == "', unique(quast_stats$group)[i], '") %>% filter(str_detect(stat, "# contigs \\\\(")) %>% filter(!str_detect(stat, ">= 0")) %>% mutate(stat = stat %>% str_remove_all("# contigs ") %>% str_remove_all("[()]") %>% fct_inorder()) %>% @@ -235,7 +256,7 @@ paste0('```{r}\n labs(title = "QUAST: Number of contigs by size") ggplotly(p) p <- quast_stats %>% - filter(sample == "', unique(quast_stats$sample)[i], '") %>% + filter(group == "', unique(quast_stats$group)[i], '") %>% filter(str_detect(stat, "Total length")) %>% filter(!str_detect(stat, ">= 0")) %>% mutate(stat = stat %>% str_remove_all("Total length ") %>% str_remove_all("[()]") %>% fct_inorder()) %>% @@ -266,7 +287,7 @@ ggplotly(p) ) ggplotly(p) \n```') %>% - write_lines(glue::glue("quast_files/contigs/_{ unique(quast_stats$sample)[i] }_quast.Rmd")) + write_lines(glue::glue("quast_files/contigs/_{ unique(quast_stats$group)[i] }_quast.Rmd")) } ``` @@ -278,27 +299,28 @@ ggplotly(p) # Per sample there are 3 value boxes # Below the value boxes there are two plots, one showing the length and one showing the contig statistics -for (i in 1:length(unique(quast_stats$sample))) { - cat(paste0('## ', unique(quast_stats$sample)[i], '\n'), +for (i in 1:length(unique(quast_stats$group))) { + cat(paste0('## ', unique(quast_stats$group)[i], '\n'), paste0('### { width=30% }\n\n'), - paste0('::: {.valuebox icon="arrow-up-right-circle" color="primary" title="Total length"}\n'), + paste0('::: {.valuebox icon="arrow-up-right-circle" color="primary" title="Longest length"}\n'), quast_stats %>% - filter(sample == unique(quast_stats$sample)[i]) %>% - filter(stat == "Total length (>= 0 bp)") %$% - max(value) %>% - format( + filter(group == unique(quast_stats$group)[i]) %>% + filter(stat == "Total length (>= 0 bp)") %>% + filter(value == max(value)) %$% + paste( + format(value, scientific = -1, trim = T, digits = 3, drop0trailing = T - ) %>% + ), "in sample: ",sample, sep = " ") %>% paste("bp"), paste0('\n'), paste0(':::'), paste0('\n\n'), - paste0('::: {.valuebox icon="percent" color="success" title="GC Content"}\n'), + paste0('::: {.valuebox icon="percent" color="success" title="Average GC Content"}\n'), quast_stats %>% - filter(sample == unique(quast_stats$sample)[i]) %>% + filter(group == unique(quast_stats$group)[i]) %>% filter(stat == "GC (%)") %$% mean(value) %>% round(2) %>% @@ -308,11 +330,11 @@ for (i in 1:length(unique(quast_stats$sample))) { paste0('\n\n'), paste0('::: {.valuebox icon="emoji-heart-eyes" color="info" title="Lowest L90"}\n'), quast_stats %>% - filter(sample == unique(quast_stats$sample)[i]) %>% + filter(group == unique(quast_stats$group)[i]) %>% filter(stat == "L90") %>% filter(value == min(value)) %>% unique() %$% - glue::glue("{unique(value)}, at stage(s): {paste(stage, collapse = ', ')}"), + glue::glue("{unique(value)}, in sample(s) {unique(sample)} at stage(s): {paste(stage, collapse = ', ')}"), paste0('\n'), paste0(':::'), paste0('\n\n'), @@ -320,36 +342,41 @@ for (i in 1:length(unique(quast_stats$sample))) { paste0('\n\n'), paste0('#### Tables \n\n'), quast_stats %>% - filter(sample == unique(quast_stats$sample)[i]) %>% - dplyr::select(sample, stage, stat, value) %>% - pivot_wider(names_from = "stat", values_from = "value") %>% - #knitr::kable(format = 'html', caption = glue::glue('QUAST statistics')) - gt::gt() %>% - gt::cols_nanoplot(columns = starts_with("# contigs ("), - new_col_name = "Contigs_by_size", - new_col_label = gt::md("*# Contigs by size*")) %>% - gt::cols_nanoplot(columns = starts_with("Total length ("), - new_col_name = "Total_length", - new_col_label = gt::md("*Total length*")) %>% - gt::tab_footnote( - footnote = "Breaks are: contigs >= 0, 1kb, 5kb, 10kb, 25kb, 50kb", - locations = gt::cells_column_labels(columns = c(Contigs_by_size, Total_length))) %>% - gt::cols_align(align = "center", - columns = c(Contigs_by_size, Total_length)) %>% - gt::cols_move(Contigs_by_size, "Largest contig") %>% - gt::cols_move(Total_length, "Total length") %>% - gt::as_raw_html(), + filter(group == unique(quast_stats$group)[i]) %>% + dplyr::select(sample, stage, stat, value) %>% + pivot_wider(names_from = "stat", values_from = "value",id_cols = c(sample, stage)) %>% + dplyr::arrange(factor(stage, levels = c("Assembly","medaka", "pilon","links","longstitch","ragtag")), sample) %>% + gt::gt() %>% + gt::cols_nanoplot(columns = starts_with("# contigs ("), + new_col_name = "Contigs_by_size", + new_col_label = gt::md("*# Contigs by size*")) %>% + gt::cols_nanoplot(columns = starts_with("Total length ("), + new_col_name = "Total_length", + new_col_label = gt::md("*Total length*")) %>% + gt::tab_footnote( + footnote = "Breaks are: contigs >= 0, 1kb, 5kb, 10kb, 25kb, 50kb", + locations = gt::cells_column_labels(columns = c(Contigs_by_size, Total_length))) %>% + gt::cols_align(align = "center", columns = c(Contigs_by_size, Total_length)) %>% + gt::cols_move(Contigs_by_size, "Largest contig") %>% + gt::cols_move(Total_length, "Total length") %>% + gt::as_raw_html() + , paste0('\n\n'), - paste0('#### Plots \n\n'), - knitr::knit_child(glue::glue('quast_files/length/_{ unique(quast_stats$sample)[i] }_quast.Rmd'), + paste0('#### Plots { orientation="columns" }'), + paste0('\n\n'), + paste0('#####'), + paste0('\n\n'), + knitr::knit_child(glue::glue('quast_files/length/_{ unique(quast_stats$group)[i] }_quast.Rmd'), envir = globalenv(), quiet = TRUE), - paste0('\n\n\n'), - knitr::knit_child(glue::glue('quast_files/contigs/_{ unique(quast_stats$sample)[i] }_quast.Rmd'), + paste0('\n\n'), + paste0('#####'), + paste0('\n\n'), + knitr::knit_child(glue::glue('quast_files/contigs/_{ unique(quast_stats$group)[i] }_quast.Rmd'), envir = globalenv(), quiet = TRUE), paste0('\n\n\n'), - sep = "") + sep = "") } ``` @@ -378,7 +405,8 @@ BUSCO was not included in the pipeline run. busco_reports <- list.files(paste0(data_base, "busco"), full.names = T, pattern = "batch_summary") %>% - map_dfr(\(x) read_busco_batch(x)) + map_dfr(\(x) read_busco_batch(x)) %>% + left_join(groups, by = join_by(sample)) ``` @@ -389,10 +417,10 @@ busco_reports <- list.files(paste0(data_base, "busco"), dir.create("busco_files") dir.create("busco_files/orthologs") -for (i in 1:length(unique(busco_reports$sample))) { +for (i in 1:length(unique(busco_reports$group))) { paste0('```{r}\n p <- busco_reports %>% - filter(sample == "', unique(busco_reports$sample)[i], '") %>% + filter(group == "', unique(busco_reports$group)[i], '") %>% filter(Var %in% c("Complete","Single","Duplicated","Fragmented")) %>% ggplot(aes(y = value, x = Var)) + geom_point( @@ -416,7 +444,7 @@ paste0('```{r}\n ) ggplotly(p) \n```') %>% - write_lines(glue::glue("busco_files/orthologs/_{ unique(busco_reports$sample)[i] }_orthologs.Rmd")) + write_lines(glue::glue("busco_files/orthologs/_{ unique(busco_reports$group)[i] }_orthologs.Rmd")) } ``` @@ -428,77 +456,67 @@ BUSCO assess assembly quality based on the presence / absence of expected single #| eval: !expr params$busco #| results: asis # -# This generates the tab-page for each sample -# Per sample there are 3 value boxes +# This generates the tab-page for each group +# Per group there are 3 value boxes # Below the value boxes there are one plots, showing the BUSCO statistics -for (i in 1:length(unique(busco_reports$sample))) { - cur_sample <- unique(busco_reports$sample)[i] +for (i in 1:length(unique(busco_reports$group))) { + cur_group <- unique(busco_reports$group)[i] # The BUSCO valueboxes contain information on which stage of the assembly had the highest quality, this requires some variables. - completeness_val <- busco_reports %>% - filter(sample == cur_sample) %>% + completenes <- busco_reports %>% + filter(group == cur_group) %>% filter(Var == "Complete") %>% - filter(value == max(value)) %$% - value %>% + filter(value == max(value)) %>% + dplyr::select(sample, stage, value) %>% unique() - completeness_stage <- busco_reports %>% - filter(sample == cur_sample) %>% - filter(Var == "Complete") %>% - filter(value == max(value)) %$% - stage - frag_val <- busco_reports %>% - filter(sample == cur_sample) %>% + fragmented <- busco_reports %>% + filter(group == cur_group) %>% filter(Var == "Fragmented") %>% - filter(value == max(value)) %$% - value %>% + filter(value == max(value)) %>% + dplyr::select(sample, stage ,value) %>% unique() - frag_stage <- busco_reports %>% - filter(sample == cur_sample) %>% - filter(Var == "Fragmented") %>% - filter(value == max(value)) %$% - stage - missing_val <- busco_reports %>% - filter(sample == cur_sample) %>% + missing <- busco_reports %>% + filter(group == cur_group) %>% filter(Var == "Missing") %>% - filter(value == max(value)) %$% - value %>% + filter(value == max(value)) %>% + dplyr::select(sample, stage ,value) %>% unique() - missing_stage <- busco_reports %>% - filter(sample == cur_sample) %>% - filter(Var == "Missing") %>% - filter(value == max(value)) %$% - stage - cat(paste('## ', unique(busco_reports$sample)[i]), + cat(paste('## ', unique(busco_reports$group)[i]), paste0('\n\n'), paste0('### {.fill} \n\n'), - paste0('::: {.valuebox icon="percent" color="success" title="Max. BUSCO Completeness" }\n'), + paste0('::: {.valuebox icon="percent" color="success" title="Max. BUSCO Completenes" }\n'), paste0('\n'), - glue::glue("{unique(completeness_val)}%,\nat stage(s): {paste(unique(completeness_stage), collapse = ', ')}"), + glue::glue("{unique(completenes$value)}%,\n at: {paste(completenes$sample, completenes$stage, collapse = ', ')}"), paste0('\n'), paste0(':::'), paste0('\n'), paste0('::: {.valuebox icon="heartbreak" color="warning" title="Max. BUSCO Fragmented"}\n'), - glue::glue("{unique(frag_val)}%,\nat stage(s): {paste(unique(frag_stage), collapse = ', ')}"), + glue::glue("{unique(fragmented$value)}%,\n at: {paste(fragmented$sample, fragmented$stage, collapse = ', ')}"), paste0('\n'), paste0('\n'), paste0(':::'), paste0('\n'), paste0('::: {.valuebox icon="person-walking" color="danger" title="Max. BUSCOs Missing"}\n'), - glue::glue("{unique(missing_val)}%, at stage(s): {paste(unique(missing_stage), collapse = ', ')}"), + glue::glue("{unique(missing$value)}%,\n at: {paste(missing$sample, missing$stage, collapse = ', ')}"), paste0('\n'), paste0(':::'), paste0('\n\n'), paste('###'), paste0('\n\n'), + # paste('#### Tables'), + paste0('\n\n'), busco_reports %>% - filter(sample == cur_sample) %>% + filter(group == cur_group) %>% dplyr::select(sample, stage, Var, value) %>% mutate(Var = str_replace_all(Var, "_", " ")) %>% - pivot_wider(names_from = "Var", values_from = "value") %>% + pivot_wider(names_from = "Var", values_from = "value", id_cols = c(sample,stage)) %>% + dplyr::arrange(factor(stage, levels = c("Assembly","medaka", "pilon","links","longstitch","ragtag")), sample) %>% gt::gt() %>% gt::as_raw_html(), paste0('\n\n'), - knitr::knit_child(glue::glue('busco_files/orthologs/_{ unique(busco_reports$sample)[i] }_orthologs.Rmd'), + # paste('#### Plots'), + # paste0('\n\n'), + knitr::knit_child(glue::glue('busco_files/orthologs/_{ unique(busco_reports$group)[i] }_orthologs.Rmd'), envir = globalenv(), quiet = TRUE), paste0('\n\n\n'), @@ -520,47 +538,6 @@ unlink("busco_files/orthologs", recursive = T) write_csv(busco_reports,"busco_files/reports.csv") ``` -# genomescope - -::: {.content-visible unless-profile="jellyfish"} -jellyfish / genomescope was not included in the pipeline run. -::: - -```{r} -#| eval: !expr params$jellyfish -#| message: false -#| echo: false -#| output: false -#| warning: false -# Parse the genomescope statistics -genomescope_out <- list.files(paste0(data_base, "genomescope"), full.names = T, pattern = "genomescope.txt") %>% - map_dfr(\(x) read_genomescope(x)) -``` - -::: {.content-visible when-profile="jellyfish"} -Jellyfish and genomescope are used to infer genome size from the initial ONT reads. - -```{r} -#| eval: !expr params$jellyfish -#| output: asis -# Since genomescope produces plots, I am simply including those here instead of recreating them, the proper QC for kmers comes with merqury. -img_files <- list.files(paste0(data_base,"genomescope"), full.names = T, pattern = "plot.png") -dir.create("genomescope_files") -for (file in img_files) { - file.copy(from = file, - to = paste0("genomescope_files/", file %>% basename(), sep ="")) - -} -img_files <- list.files("genomescope_files", full.names = T, pattern = "plot.png") - -cat(":::{.panel-tabset}\n", - glue::glue('## <% basename(), ".+?(?=_plot.png)")>>\n ![](<>){width=50% fig-align="centre"}\n\n\n', .open = "<<", .close = ">>"), - ":::\n", - sep = "" -) -``` -::: - # merqury ::: {.content-visible unless-profile="merqury"} @@ -577,97 +554,91 @@ meryl and merqury were not included in the pipeline run. merqury_stats <- list.files(paste0(data_base, "merqury"), full.names = T, pattern = "stats") %>% lapply(\(x) { read_tsv(x, col_names = c("sample_stage","all","assembly","total","percent"), show_col_types = FALSE) %>% - mutate(sample = str_extract(x %>% basename(), - ".+?(?=_[assembly|links|longstitch|ragtag|medaka|pilon])"), + mutate( sample = str_extract(x %>% basename(), + ".+?(?=_\\.assembly|\\-assemble|links|longstitch|ragtag|medaka|pilon|\\-run|\\-polish)"), stage = case_when( str_detect(x, "_ragtag") ~ "RagTag", str_detect(x, "_medaka") ~ "medaka", str_detect(x, "_pilon") ~ "pilon", str_detect(x, "_longstitch") ~ "longstitch", str_detect(x, "_links") ~ "LINKS", - str_detect(x, "assembly") ~ "Assembly", - TRUE ~ "Unknown" - ) - ) - } - ) %>% - bind_rows() + str_detect(x, "assemble") ~ "Assembly", + TRUE ~ "Unknown")) }) %>% + bind_rows() %>% + left_join(groups, by = join_by(sample)) # This parses the assembly stats merqury_asm_hists <- list.files(paste0(data_base, "/merqury"), full.names = T, pattern = "asm.hist") %>% lapply(\(x) { read_tsv(x, col_names = T, show_col_types = FALSE) %>% mutate( sample = str_extract(x %>% basename(), - ".+?(?=_[assembly|links|longstitch|ragtag|medaka|pilon])"), + ".+?(?=_\\.assembly|\\-assemble|links|longstitch|ragtag|medaka|pilon|\\-run|\\-polish)"), stage = case_when( - str_detect(x, "_ragtag") ~ "RagTag", - str_detect(x, "_medaka") ~ "medaka", - str_detect(x, "_pilon") ~ "pilon", - str_detect(x, "_longstitch") ~ "longstitch", - str_detect(x, "_links") ~ "LINKS", - str_detect(x, "assembly") ~ "Assembly", - TRUE ~ "Unknown"), + str_detect(x, "_ragtag") ~ "RagTag", + str_detect(x, "_medaka") ~ "medaka", + str_detect(x, "_pilon") ~ "pilon", + str_detect(x, "_longstitch") ~ "longstitch", + str_detect(x, "_links") ~ "LINKS", + str_detect(x, "assemble") ~ "Assembly", + TRUE ~ "Unknown"), Assembly = as.factor(Assembly), stage = as.factor(stage), sample = as.factor(sample), kmer_multiplicity = as.integer(kmer_multiplicity), - Count = as.integer(Count) - ) - } - ) %>% - bind_rows() + Count = as.integer(Count)) + }) %>% + bind_rows() %>% + left_join(groups, by = join_by(sample)) # This parses the copy number file merqury_cn_hists <- list.files(paste0(data_base, "merqury"), full.names = T, pattern = "cn.hist") %>% lapply(\(x) { read_tsv(x, col_names = T, show_col_types = FALSE) %>% mutate( sample = str_extract(x %>% basename(), - ".+?(?=_[assembly|links|longstitch|ragtag|medaka|pilon])"), + ".+?(?=_\\.assembly|\\-assemble|links|longstitch|ragtag|medaka|pilon|\\-run|\\-polish)"), stage = case_when( str_detect(x, "_ragtag") ~ "RagTag", str_detect(x, "_medaka") ~ "medaka", str_detect(x, "_pilon") ~ "pilon", str_detect(x, "_longstitch") ~ "longstitch", str_detect(x, "_links") ~ "LINKS", - str_detect(x, "assembly") ~ "Assembly", + str_detect(x, "assemble") ~ "Assembly", TRUE ~ "Unknown"), Copies = as.factor(Copies), stage = as.factor(stage), sample = as.factor(sample), kmer_multiplicity = as.integer(kmer_multiplicity), - Count = as.integer(Count) - ) - } - ) %>% - bind_rows() + Count = as.integer(Count)) + }) %>% + bind_rows() %>% + left_join(groups, by = join_by(sample)) # This parses the qv file -merqury_qv <- - list.files(paste0(data_base, "merqury"), full.names = T, pattern = ".qv") %>% +merqury_qv <- list.files(paste0(data_base, "merqury"), full.names = T, pattern = ".qv") %>% lapply(\(x) { read_tsv(x, col_names = c("Assembly", "kmers_assembly_unique", "kmers_assembly_shared", "QV", "error_rate"), show_col_types = FALSE) %>% mutate( sample = str_extract(x %>% basename(), - ".+?(?=_[assembly|links|longstitch|ragtag|medaka|pilon])"), + ".+?(?=_\\.assembly|\\-assemble|links|longstitch|ragtag|medaka|pilon|\\-run|\\-polish)"), stage = case_when( str_detect(x, "_ragtag") ~ "RagTag", str_detect(x, "_medaka") ~ "medaka", str_detect(x, "_pilon") ~ "pilon", str_detect(x, "_longstitch") ~ "longstitch", str_detect(x, "_links") ~ "LINKS", - str_detect(x, "assembly") ~ "Assembly", + str_detect(x, "assemble") ~ "Assembly", TRUE ~ "Unknown"), stage = as.factor(stage), sample = as.factor(sample), kmers_assembly_shared = as.integer(kmers_assembly_shared), kmers_assembly_unique = as.integer(kmers_assembly_unique), QV = as.double(QV), - error_rate = as.double(error_rate) - ) + error_rate = as.double(error_rate)) } ) %>% - bind_rows() + bind_rows() %>% + left_join(groups, by = join_by(sample)) dir.create("merqury_files") ``` @@ -676,13 +647,13 @@ dir.create("merqury_files") #| include: false # This generates QV-plots from merqury; the plot function is stuffed into plot_merqury dir.create("merqury_files/qv_plots/") -for (i in 1:length(unique(merqury_qv$sample))) { - cur_sample <- unique(merqury_qv$sample)[i] +for (i in 1:length(unique(merqury_qv$group))) { + cur_group <- unique(merqury_qv$group)[i] paste0('```{r} p <- merqury_qv %>% - plot_merqury_qv("', cur_sample,'") + plot_merqury_qv("', cur_group,'") ggplotly(p)\n```') %>% - write_lines(glue::glue("merqury_files/qv_plots/_{ cur_sample }_qv_plt.Rmd")) + write_lines(glue::glue("merqury_files/qv_plots/_{ cur_group }_qv_plt.Rmd")) } ``` @@ -692,13 +663,13 @@ ggplotly(p)\n```') %>% # This generates stat-plots from merqury; the plot function is stuffed into plot_merqury dir.create("merqury_files/stat_plots/") -for (i in 1:length(unique(merqury_stats$sample))) { - cur_sample <- unique(merqury_stats$sample)[i] +for (i in 1:length(unique(merqury_stats$group))) { + cur_group <- unique(merqury_stats$group)[i] paste0('```{r} p <- merqury_stats %>% - plot_merqury_stats("', cur_sample,'") + plot_merqury_stats("', cur_group,'") ggplotly(p)\n```') %>% - write_lines(glue::glue("merqury_files/stat_plots/_{ cur_sample }_completeness_plt.Rmd")) + write_lines(glue::glue("merqury_files/stat_plots/_{ cur_group }_completeness_plt.Rmd")) } ``` @@ -708,13 +679,13 @@ ggplotly(p)\n```') %>% # This generates assembly plots from merqury; the plot function is stuffed into plot_merqury dir.create("merqury_files/asm_plots/") -for (i in 1:length(unique(merqury_asm_hists$sample))) { - cur_sample <- unique(merqury_asm_hists$sample)[i] +for (i in 1:length(unique(merqury_asm_hists$group))) { + cur_group <- unique(merqury_asm_hists$group)[i] paste0('```{r} p <- merqury_asm_hists %>% - plot_merqury_multiplicity("', cur_sample,'") + plot_merqury_multiplicity("', cur_group,'") ggplotly(p)\n```') %>% - write_lines(glue::glue("merqury_files/asm_plots/_{ cur_sample }_asm_plt.Rmd")) + write_lines(glue::glue("merqury_files/asm_plots/_{ cur_group }_asm_plt.Rmd")) } ``` @@ -724,13 +695,13 @@ ggplotly(p)\n```') %>% # This generates copy-number from merqury; the plot function is stuffed into plot_merqury dir.create("merqury_files/cn_plots/") -for (i in 1:length(unique(merqury_cn_hists$sample))) { - cur_sample <- unique(merqury_cn_hists$sample)[i] +for (i in 1:length(unique(merqury_cn_hists$group))) { + cur_group <- unique(merqury_cn_hists$group)[i] paste0('```{r} p <- merqury_cn_hists %>% - plot_merqury_copynumber("', cur_sample,'") + plot_merqury_copynumber("', cur_group,'") ggplotly(p)\n```') %>% - write_lines(glue::glue("merqury_files/cn_plots/_{ cur_sample }_cn_plt.Rmd")) + write_lines(glue::glue("merqury_files/cn_plots/_{ cur_group }_cn_plt.Rmd")) } ``` @@ -746,55 +717,52 @@ merqury compares k-mer spectra between assemblies and short read libraries to as # Below the value boxes there is a tabset of plots, each tab contains one of the plot-types produced above. # Those are: Completeness, k-mer specatr, QV and CN -for (i in 1:length(unique(merqury_stats$sample))) { - cur_sample <- unique(merqury_stats$sample)[i] +for (i in 1:length(unique(merqury_stats$group))) { + cur_group <- unique(merqury_stats$group)[i] highest_val <- merqury_stats %>% - filter(sample == cur_sample) %>% + filter(group == cur_group) %>% filter(percent == max(percent)) %$% percent %>% unique() highest_stage <- merqury_stats %>% - filter(sample == cur_sample) %>% - filter(percent == highest_val) %$% - stage %>% - unique() + filter(group == cur_group) %>% + filter(percent == highest_val) %>% + dplyr::select(sample, stage, percent) lowest_val <- merqury_stats %>% - filter(sample == cur_sample) %>% + filter(group == cur_group) %>% filter(percent == min(percent)) %$% percent %>% unique() lowest_stage <- merqury_stats %>% - filter(sample == cur_sample) %>% - filter(percent == lowest_val) %$% - stage %>% - unique() + filter(group == cur_group) %>% + filter(percent == lowest_val) %>% + dplyr::select(sample, stage, percent) highest_qv <- merqury_qv %>% - filter(sample == cur_sample) %>% + filter(group == cur_group) %>% filter(QV == max(QV)) %$% QV %>% unique() highest_qv_stage <- merqury_qv %>% - filter(sample == cur_sample) %>% - filter(QV == max(QV)) %$% - stage %>% - unique() + filter(group == cur_group) %>% + filter(QV == max(QV)) %>% + dplyr::select(sample, stage, QV) - cat(paste('## ', cur_sample), + cat(paste('## ', cur_group), paste0('\n\n'), paste0('### Valueboxes'), paste0('\n\n'), - paste0('::: {.valuebox icon="exclude" color="primary" title="Merqury QV" }\n'), - glue::glue("QV: {unique(highest_qv) %>% round(2)}, at stage(s): {paste(highest_qv_stage, collapse = ', ')}"), + paste0('::: {.valuebox icon="exclude" color="primary" title="Max. merqury QV" }\n'), + glue::glue("QV: {unique(highest_qv) %>% round(2)}, at: {paste(highest_qv_stage$sample, highest_qv_stage$stage, collapse = ', ')}"), paste0('\n'), paste0(':::'), paste0('\n\n'), paste0('::: {.valuebox icon="percent" color="success" title="Highest k-mer completeness" }\n'), - glue::glue("{unique(highest_val) %>% round(2)}%, at stage(s): {paste(highest_stage, collapse = ', ')}"), + glue::glue("{unique(highest_val) %>% round(2)}%, at stage(s): {paste(highest_stage$sample, highest_stage$stage, collapse = ', ')}"), paste0('\n'), paste0(':::'), paste0('\n\n'), paste0('::: {.valuebox icon="heartbreak" color="warning" title="Lowest k-mer completeness" }\n'), - glue::glue("{unique(lowest_val) %>% round(2)}%, at stage(s): {paste(lowest_stage, collapse = ', ')}"), + glue::glue("{unique(lowest_val) %>% round(2)}%, at stage(s): {paste(lowest_stage$sample, lowest_stage$stage, collapse = ', ')}"), paste0('\n'), paste0(':::'), paste0('\n\n'), @@ -803,7 +771,7 @@ for (i in 1:length(unique(merqury_stats$sample))) { paste0('\n\n'), paste0('#### Completeness \n'), paste0('\n'), - knitr::knit_child(glue::glue('merqury_files/stat_plots/_{ cur_sample }_completeness_plt.Rmd'), + knitr::knit_child(glue::glue('merqury_files/stat_plots/_{ cur_group }_completeness_plt.Rmd'), envir = globalenv(), quiet = TRUE), paste0('\n'), @@ -811,19 +779,19 @@ for (i in 1:length(unique(merqury_stats$sample))) { paste0('\n'), paste0('QV is defined as:\n', expression(10*-log10(error_rate))), paste0('\n'), - knitr::knit_child(glue::glue('merqury_files/qv_plots/_{ cur_sample }_qv_plt.Rmd'), + knitr::knit_child(glue::glue('merqury_files/qv_plots/_{ cur_group }_qv_plt.Rmd'), envir = globalenv(), quiet = TRUE), paste0('\n'), paste0('#### Spectra \n'), paste0('\n'), - knitr::knit_child(glue::glue('merqury_files/asm_plots/_{ cur_sample }_asm_plt.Rmd'), + knitr::knit_child(glue::glue('merqury_files/asm_plots/_{ cur_group }_asm_plt.Rmd'), envir = globalenv(), quiet = TRUE), paste0('\n'), paste0('#### Copy Number \n'), paste0('\n'), - knitr::knit_child(glue::glue('merqury_files/cn_plots/_{ cur_sample }_cn_plt.Rmd'), + knitr::knit_child(glue::glue('merqury_files/cn_plots/_{ cur_group }_cn_plt.Rmd'), envir = globalenv(), quiet = TRUE), paste0('\n\n\n'), @@ -841,6 +809,61 @@ unlink("merqury_files/asm_plots") unlink("merqury_files") ``` +# genomescope + +::: {.content-visible unless-profile="jellyfish"} +jellyfish / genomescope was not included in the pipeline run. +::: + +```{r} +#| eval: !expr params$jellyfish +#| message: false +#| echo: false +#| output: false +#| warning: false +# Parse the genomescope statistics +genomescope_out <- list.files(paste0(data_base, "genomescope"), full.names = T, pattern = "genomescope.txt") %>% + map_dfr(\(x) read_genomescope(x)) %>% + left_join(groups, by = join_by(sample)) +``` + +::: {.content-visible when-profile="jellyfish"} + +Below are the genomescope estimates based on the provided ONT reads: + +```{r} +#| eval: !expr params$jellyfish +#| output: asis +# Since genomescope produces plots, I am simply including those here instead of recreating them, the proper QC for kmers comes with merqury. +img_files <- list.files(paste0(data_base,"genomescope"), full.names = T, pattern = "plot.png") +dir.create("genomescope_files") +for (file in img_files) { + file.copy(from = file, + to = paste0("genomescope_files/", file %>% basename(), sep ="")) + +} + +img_files <- data.frame(file = list.files("genomescope_files/", full.names = T, pattern = "plot.png")) %>% + mutate(sample = str_extract(file %>% basename(), ".+?(?=_plot.png)")) %>% + left_join(groups, join_by(sample)) + + +cat(":::{.panel-tabset}\n\n") +for(grp in unique(img_files$group)) { + cat(glue::glue('## {grp}\n\n\n')) + cat(glue::glue('![](<% filter(group == grp) %$% file>>){width=50% fig-align="centre"}\n\n\n', + .open = "<<", + .close = ">>")) + } +cat(":::\n") +``` +::: +```{r} +glue::glue('## <>\n (<% filter(group == grp) %$% file %>% lapply(function(file) paste("![]", file, "{width=50% fig-align=centre}\n")) %>% unlist>>)\n\n\n', + .open = "<<", + .close = ">>") +``` + # Software versions The pipeline was run using the following software versions: @@ -848,15 +871,15 @@ The pipeline was run using the following software versions: ```{r} versions <- yaml::read_yaml("software_versions.yml") lapply(1:length(versions), \(process) { - proc = versions[[process]] - proc_name = names(versions[process]) - tools <- lapply(1:length(proc), \(tool) { - tool_name = proc[tool] %>% names - tool_version = proc[[tool]] %>% as.character() - return(tibble(Process = proc_name,Tool = tool_name, Version = tool_version)) - }) %>% + proc = versions[[process]] + proc_name = names(versions[process]) + tools <- lapply(1:length(proc), \(tool) { + tool_name = proc[tool] %>% names + tool_version = proc[[tool]] %>% as.character() + return(tibble(Process = proc_name,Tool = tool_name, Version = tool_version)) + }) %>% bind_rows() - }) %>% -bind_rows() %>% -knitr::kable() +}) %>% + bind_rows() %>% + knitr::kable() ``` diff --git a/docs/usage.md b/docs/usage.md index c29a18d1..f4092f74 100644 --- a/docs/usage.md +++ b/docs/usage.md @@ -71,6 +71,7 @@ The samplesheet _must_ contain a column name `sample` [string]. Further commonly used columns _can_ be: +- `group` [string] to group different samples in the report to facilitate comparisons. - `ontreads` [path] for long reads produced with oxford nanopore sequencers - `hifireads` [path] for long reads produced with pacbio sequencers in "HiFi" mode - Reference information: diff --git a/modules/local/report/main.nf b/modules/local/report/main.nf index 3fedbd2c..3c024a05 100644 --- a/modules/local/report/main.nf +++ b/modules/local/report/main.nf @@ -54,12 +54,12 @@ process REPORT { report_params = report_params << ' -P merqury:true' } - def yamlBuilder = new groovy.yaml.YamlBuilder() - yamlBuilder(groups) - def yaml_content = yamlBuilder.toString().tokenize('\n').join("\n ") + def groupBuilder = new groovy.yaml.YamlBuilder() + groupBuilder(groups) + def group_content = groupBuilder.toString().tokenize('\n').join("\n ") """ cat <<- END_YAML_GROUPS > groups.yml - ${yaml_content} + ${group_content} END_YAML_GROUPS export HOME="\$PWD" diff --git a/nextflow.config b/nextflow.config index 982ff79b..8925f19b 100644 --- a/nextflow.config +++ b/nextflow.config @@ -50,6 +50,7 @@ params { // -- ONT ont_collect = false // collect ONT reads into a single file ont_trim = false // run porechop on ONT + ont_nanoq = false // run nanoq // -- Jellyfish (ONT reads only) -- ont_jellyfish = false ont_jellyfish_k = 21 diff --git a/subworkflows/local/prepare_ont/main.nf b/subworkflows/local/prepare_ont/main.nf index 4e36a5b0..a33eb342 100644 --- a/subworkflows/local/prepare_ont/main.nf +++ b/subworkflows/local/prepare_ont/main.nf @@ -62,8 +62,15 @@ workflow PREPARE_ONT { CHOP(chop_in) CHOP.out.chopped_reads + .join(ch_ont_chop_branched + .chop + .map { it -> [it.meta, it.ont_nanoq] } + ) + .filter { it -> it[2] == true } + .map { it -> [meta: it[0], ontreads: it[1]]} .mix(ch_ont_chop_branched .no_chop + .filter { it -> it.ont_nanoq } .map { it -> [meta: it.meta, ontreads: it.ontreads] } ) .set {ch_nanoq_in} @@ -80,6 +87,7 @@ workflow PREPARE_ONT { ch_ont .ont + .filter { it -> it.ont_nanoq } .map { it -> it - it.subMap("ontreads", "ont_read_length") } .map { it -> it.collect { entry -> [ entry.value, entry ] } } .join( med_len ) @@ -88,6 +96,7 @@ workflow PREPARE_ONT { ) .map { it -> it.collect { _entry, map -> [ (map.key): map.value ] }.collectEntries() } .mix(ch_ont.no_ont) + .mix(ch_ont.ont.filter { it -> !it.nanoq }) .set { main_out } versions = ch_versions.mix(COLLECT.out.versions).mix(CHOP.out.versions).mix(RUN_NANOQ.out.versions) diff --git a/subworkflows/local/prepare_shortreads/main.nf b/subworkflows/local/prepare_shortreads/main.nf index e8b3f35d..f10a30b8 100644 --- a/subworkflows/local/prepare_shortreads/main.nf +++ b/subworkflows/local/prepare_shortreads/main.nf @@ -62,8 +62,14 @@ workflow PREPARE_SHORTREADS { .set { shortreads } ch_versions = ch_versions.mix(TRIMGALORE.out.versions) - - MERYL_COUNT(shortreads.map { it -> [ it.meta, it.shortreads ] }, params.meryl_k) + shortreads + .filter { it -> it.merqury } + .multiMap { it -> + reads: [ it.meta, it.shortreads ] + kmer_size: it.meryl_k + } + .set { meryl_in } + MERYL_COUNT(meryl_in.reads, meryl_in.kmer_size) MERYL_UNIONSUM(MERYL_COUNT.out.meryl_db, params.meryl_k) MERYL_UNIONSUM.out.meryl_db.set { meryl_kmers } diff --git a/subworkflows/local/qc/main.nf b/subworkflows/local/qc/main.nf index 80b8fedb..6efaa0b2 100644 --- a/subworkflows/local/qc/main.nf +++ b/subworkflows/local/qc/main.nf @@ -25,6 +25,7 @@ workflow QC { ch_shortread_branched .shortread + .filter { it -> it.merqury } .map { it -> [it.meta] } .join(scaffolds) .join(meryl_kmers) diff --git a/subworkflows/local/utils_nfcore_genomeassembler_pipeline/main.nf b/subworkflows/local/utils_nfcore_genomeassembler_pipeline/main.nf index 3839b715..37c7be4a 100644 --- a/subworkflows/local/utils_nfcore_genomeassembler_pipeline/main.nf +++ b/subworkflows/local/utils_nfcore_genomeassembler_pipeline/main.nf @@ -134,6 +134,7 @@ workflow PIPELINE_INITIALISATION { null, ont_collect: it.ont_collect ?: params.ont_collect, ont_trim: it.ont_trim ?: params.ont_trim, + ont_nanoq: it.ont_nanoq ?: params.ont_nanoq, ont_jellyfish: it.ont_jellyfish ?: (params.ont_jellyfish && it.ontreads), ont_jellyfish_k: it.ont_jellyfish_k ?: params.ont_jellyfish_k, ont_read_length: it.ont_read_length ?: params.read_length, @@ -164,8 +165,8 @@ workflow PIPELINE_INITIALISATION { busco: it.busco ?: params.busco, busco_lineage: it.busco_lineage ?: params.busco_lineage, busco_db: it.busco_db ?: params.busco_db, + merqury: it.merqury ?: params.merqury, lift_annotations: it.lift_annotations ?: params.lift_annotations, - shortread_F: it.shortread_F ?: params.shortread_F, shortread_R: it.shortread_R ?: params.shortread_R, paired: it.paired ?: params.paired, diff --git a/workflows/genomeassembler.nf b/workflows/genomeassembler.nf index e422ec3b..833e6ac9 100644 --- a/workflows/genomeassembler.nf +++ b/workflows/genomeassembler.nf @@ -113,6 +113,7 @@ workflow GENOMEASSEMBLER { .tap { busco_files } .map { it -> [it[0], it[1], it[1], it[1], it[1]] } .tap { merqury_files } + /* ============= Prepare reads @@ -294,9 +295,26 @@ workflow GENOMEASSEMBLER { .set { report_functions } if(!params.merqury) { - merqury_files = Channel.of([]) + } + ch_main + .collect { it -> it.quast ?: null } + .map { it -> it.any { it2 -> it2 == true ?: false } } + .set { quast_val } + ch_main + .collect { it -> it.busco ?: null } + .map { it -> it.any { it2 -> it2 == true ?: false } } + .set { busco_val } + ch_main + .collect { it -> it.ont_jellyfish ?: null } + .map { it -> it.any { it2 -> it2 == true ?: false } } + .set { jelly_val } + ch_main + .collect { it -> it.merqury ?: null } + .map { it -> it.any { it2 -> it2 == true ?: false } } + .set { merqury_val } + REPORT( report_files, report_functions, nanoq_files, From 3f26436edab3b5f730a391c92a6ae7574d473f85 Mon Sep 17 00:00:00 2001 From: Niklas Schandry Date: Fri, 27 Jun 2025 16:54:21 +0200 Subject: [PATCH 028/162] refactor assemble and assemble subworkflows for sample-wise parameterization --- subworkflows/local/prepare_shortreads/main.nf | 11 ++++++++++- .../utils_nfcore_genomeassembler_pipeline/main.nf | 8 ++++++++ 2 files changed, 18 insertions(+), 1 deletion(-) diff --git a/subworkflows/local/prepare_shortreads/main.nf b/subworkflows/local/prepare_shortreads/main.nf index f10a30b8..a714bfd5 100644 --- a/subworkflows/local/prepare_shortreads/main.nf +++ b/subworkflows/local/prepare_shortreads/main.nf @@ -73,10 +73,19 @@ workflow PREPARE_SHORTREADS { MERYL_UNIONSUM(MERYL_COUNT.out.meryl_db, params.meryl_k) MERYL_UNIONSUM.out.meryl_db.set { meryl_kmers } + shortreads_in + .map { + it -> it.subMap('shortread_F', 'shortread_R', 'paired') + } + .join( + shortreads.map { it -> [meta: [id: it[0].id], shortreads: it[1]]} + ) + .set { main_out } + versions = ch_versions.mix(MERYL_COUNT.out.versions).mix(MERYL_UNIONSUM.out.versions) emit: - shortreads + main_out meryl_kmers versions } diff --git a/subworkflows/local/utils_nfcore_genomeassembler_pipeline/main.nf b/subworkflows/local/utils_nfcore_genomeassembler_pipeline/main.nf index 37c7be4a..23ada56f 100644 --- a/subworkflows/local/utils_nfcore_genomeassembler_pipeline/main.nf +++ b/subworkflows/local/utils_nfcore_genomeassembler_pipeline/main.nf @@ -211,6 +211,14 @@ workflow PIPELINE_INITIALISATION { ] : null, // Check if assembler can do hybrid + (it.strategy == "single" && it.ont_reads && it.hifi_reads) + ? + [ + println("Please confirm samplesheet: [sample: $it.meta.id]: Stragety is $it.strategy, but both types of reads are provided."), + "invalid" + ] + : null, + // Check if assembler can do hybrid (it.strategy == "hybrid" && !hybrid_assemblers.contains(it.assembler1)) ? [ From 0069ed245863fdc38677d37c26db5523fe5f1812 Mon Sep 17 00:00:00 2001 From: nf-core bot Date: Tue, 1 Jul 2025 09:36:28 +0200 Subject: [PATCH 029/162] Important! Template update for nf-core/tools v3.3.1 (#164) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Template update for nf-core/tools version 3.2.1 * Template update for nf-core/tools version 3.3.1 * merge template 3.3.1 - fix linting * update pre-commit * merge template 3.3.1 - fix linting * pre-commit config? * pre-commit config? * reinstall links * try larger runner * smaller run, disable bloom filter for hifiasm test * updated test snapshot * updated test snapshot * update nftignore * update nftignore * update nftignore * update nftignore * update nftignore * update nftignore * update nftignore * update nftignore * update nftignore * Update .github/actions/nf-test/action.yml Co-authored-by: Matthias Hörtenhuber * Update docs/output.md Co-authored-by: Matthias Hörtenhuber * remove .nf-test.log --------- Co-authored-by: Niklas Schandry Co-authored-by: Matthias Hörtenhuber --- .github/CONTRIBUTING.md | 1 + .github/actions/nf-test/action.yml | 2 + .github/workflows/linting_comment.yml | 2 +- .github/workflows/nf-test.yml | 43 ++++++++++----------- .github/workflows/release-announcements.yml | 2 +- 5 files changed, 25 insertions(+), 25 deletions(-) diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md index e01c8db1..1f9ed4d6 100644 --- a/.github/CONTRIBUTING.md +++ b/.github/CONTRIBUTING.md @@ -79,6 +79,7 @@ If you wish to contribute a new step, please use the following coding standards: 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. Add a description of the output files and if relevant any appropriate images from the MultiQC report to `docs/output.md`. ### Default values diff --git a/.github/actions/nf-test/action.yml b/.github/actions/nf-test/action.yml index 3b9724c7..9cd66b34 100644 --- a/.github/actions/nf-test/action.yml +++ b/.github/actions/nf-test/action.yml @@ -56,6 +56,8 @@ runs: channel-priority: strict 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: diff --git a/.github/workflows/linting_comment.yml b/.github/workflows/linting_comment.yml index e6e9bc26..3f43d741 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@4c1e823582f43b179e2cbb49c3eade4e41f992e2 # v10 with: workflow: linting.yml workflow_conclusion: completed diff --git a/.github/workflows/nf-test.yml b/.github/workflows/nf-test.yml index c98d76ec..0854baf7 100644 --- a/.github/workflows/nf-test.yml +++ b/.github/workflows/nf-test.yml @@ -1,5 +1,12 @@ name: Run nf-test on: + push: + paths-ignore: + - "docs/**" + - "**/meta.yml" + - "**/*.md" + - "**/*.png" + - "**/*.svg" pull_request: paths-ignore: - "docs/**" @@ -28,8 +35,8 @@ 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=$-nf-test-changes + - runner4cpu-linux-x64 outputs: shard: ${{ steps.set-shards.outputs.shard }} total_shards: ${{ steps.set-shards.outputs.total_shards }} @@ -62,7 +69,7 @@ jobs: 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 + - runs-on=$-nf-test - runner=4cpu-linux-x64 strategy: fail-fast: false @@ -90,9 +97,7 @@ 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 }} NXF_VERSION: ${{ matrix.NXF_VER }} @@ -100,30 +105,14 @@ jobs: 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: # use self-hosted runners - - runs-on=${{ github.run_id }}-confirm-pass + - runs-on=$-confirm-pass - runner=2cpu-linux-x64 steps: - - name: One or more tests failed (excluding latest-everything) + - name: One or more tests failed if: ${{ contains(needs.*.result, 'failure') }} run: exit 1 @@ -142,3 +131,11 @@ 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 431d3d44..ab43c710 100644 --- a/.github/workflows/release-announcements.yml +++ b/.github/workflows/release-announcements.yml @@ -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@4aa83560bb3eac05dbad1e5f221ee339118abdd2 # v0.2.0 with: post: | Pipeline release! ${{ github.repository }} v${{ github.event.release.tag_name }} - ${{ github.event.release.name }}! From 9468b09ee96b34611315e73fecef3b319095e472 Mon Sep 17 00:00:00 2001 From: Niklas Schandry Date: Fri, 11 Jul 2025 11:07:56 +0200 Subject: [PATCH 030/162] params.md --- params.md | 152 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 152 insertions(+) create mode 100644 params.md diff --git a/params.md b/params.md new file mode 100644 index 00000000..c29f09a3 --- /dev/null +++ b/params.md @@ -0,0 +1,152 @@ +# nf-core/genomeassembler pipeline parameters + +Assemble genomes from long ONT or pacbio HiFi reads + +## Input/output options + +Define where the pipeline should find input data and save output data. + +| Parameter | Description | Type | Default | Required | Hidden | +| ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | -------- | ------- | -------- | ------ | +| `input` | Path to comma-separated file containing information about the samples in the experiment.
HelpYou 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/genomeassembler/usage#samplesheet-input).
| `string` | | True | | +| `outdir` | The output directory where the results will be saved. You have to use absolute paths to storage on Cloud infrastructure. | `string` | | True | | +| `email` | Email address for completion summary.
HelpSet this parameter to your e-mail address to get a summary e-mail with details of the run sent to you when the workflow exits. If set in your user config file | +| (`~/.nextflow/config`) then you don't need to specify this on the command line for every run.
| `string` | | | | + +## Institutional config options + +Parameters used to describe centralised config profiles. These should not be edited. + +| Parameter | Description | Type | Default | Required | Hidden | +| ----------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------- | ------- | -------- | ------ | +| `custom_config_version` | Git commit id for Institutional configs. | `string` | master | | True | +| `custom_config_base` | Base directory for Institutional configs.
HelpIf you're running offline, Nextflow will not be able to fetch the institutional config files from the internet. If you don't need them, then this is not a | +| problem. If you do need them, you should download the files from the repo and tell Nextflow where to find them with this parameter.
| `string` | https://raw.githubusercontent.com/nf-core/configs/master | | True | +| `config_profile_name` | Institutional config name. | `string` | | | True | +| `config_profile_description` | Institutional config description. | `string` | | | True | +| `config_profile_contact` | Institutional config contact information. | `string` | | | True | +| `config_profile_url` | Institutional config URL link. | `string` | | | True | + +## Generic options + +Less common options for the pipeline, typically set in a config file. + +| Parameter | Description | Type | Default | Required | Hidden | +| ---------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------- | -------------------------------------------------------- | -------- | ------ | +| `version` | Display version and exit. | `boolean` | | | True | +| `publish_dir_mode` | Method used to save pipeline results to output directory.
HelpThe Nextflow `publishDir` option specifies which intermediate files should be saved to the output directory. This option tells the pipeline what | +| method should be used to move these files. See [Nextflow docs](https://www.nextflow.io/docs/latest/process.html#publishdir) for details.
| `string` | copy | | True | +| `email_on_fail` | Email address for completion summary, only when pipeline fails.
HelpAn email address to send a summary email to when the pipeline is completed - ONLY sent if the pipeline does not exit | +| successfully.
| `string` | | | True | +| `plaintext_email` | Send plain-text email instead of HTML. | `boolean` | | | True | +| `monochrome_logs` | Do not use coloured log outputs. | `boolean` | | | True | +| `hook_url` | Incoming hook URL for messaging service
HelpIncoming hook URL for messaging service. Currently, MS Teams and Slack are supported.
| `string` | | | True | +| `validate_params` | Boolean whether to validate parameters against the schema at runtime | `boolean` | True | | True | +| `pipelines_testdata_base_path` | Base URL or local path to location of pipeline test dataset files | `string` | https://raw.githubusercontent.com/nf-core/test-datasets/ | | True | + +## Reference Parameters + +Options controlling pipeline behavior + +| Parameter | Description | Type | Default | Required | Hidden | +| ----------- | ------------------------------------------ | --------- | ------- | -------- | ------ | +| `ref_fasta` | Path to reference genome seqeunce (fasta) | `string` | | | | +| `ref_gff` | Path to reference genome annotations (gff) | `string` | | | | +| `use_ref` | use reference genome | `boolean` | | | True | + +## Assembly options + +Options controlling assembly + +| Parameter | Description | Type | Default | Required | Hidden | +| -------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | --------- | ----------- | -------- | ------ | +| `strategy` | Assembly strategy to use. Valid choices are `'single'`, `'hybrid'` and `'scaffold'` | `string` | single | | | +| `assembler` | Assembler to use. Valid choices are: `'hifiasm'`, `'flye'`, `'flye_on_hifiasm'` or `hifiasm_on_hifiasm`. `flye_on_hifiasm` will scaffold flye assembly (ont) on hifiasm (hifi) assembly using ragtag. `hifiasm_on_hifiasm` will scaffold hifiasm (ont) | +| onto hifiasm (HiFi) using ragtag | `string` | hifiasm | | | +| `genome_size` | expected genome size, optional | `integer` | | | | +| `flye_mode` | flye mode | `string` | --nano-hq | | | +| `flye_args` | additional args for flye | `string` | | | | +| `hifiasm_args` | Extra arguments passed to `hifiasm` | `string` | | | | +| `assembly_scaffolding_order` | When strategy is "scaffold", which assembly should be scaffolded onto which? | `string` | ont_on_hifi | | | + +## ONT read options + +Options for ONT reads + +| Parameter | Description | Type | Default | Required | Hidden | +| ----------------- | ------------------------------------------- | --------- | ------- | -------- | ------ | +| `ontreads` | Path to ONT reads | `string` | | | | +| `ont_trim` | Trim ont reads with porechop? | `boolean` | | | | +| `ont_jellyfish` | Run jellyfish on ONT reads (k-mer analysis) | `boolean` | | | | +| `ont_jellyfish_k` | k-mer size for jellyfish | `integer` | 21 | | | +| `read_length` | read length for genomescope (ONT only) | `integer` | | | | +| `dump` | dump jellyfish output | `boolean` | | | | +| `ont_collect` | Collect ONT reads from several files? | `boolean` | | | | + +## Polishing options + +Polishing options + +| Parameter | Description | Type | Default | Required | Hidden | +| --------------- | ------------------------------------------------ | --------- | ------- | -------- | ------ | +| `polish_pilon` | Polish assembly with pilon? Requires short reads | `boolean` | | | | +| `polish_medaka` | Polish assembly with medaka (ONT only) | `boolean` | | | | +| `medaka_model` | model to use with medaka | `string` | | | | + +## Scaffolding options + +Scaffolding options + +| Parameter | Description | Type | Default | Required | Hidden | +| --------------------- | ------------------------------------------ | --------- | ------- | -------- | ------ | +| `scaffold_longstitch` | Scaffold with longstitch? | `boolean` | | | | +| `scaffold_links` | Scaffolding with links? | `boolean` | | | | +| `scaffold_ragtag` | Scaffold with ragtag (requires reference)? | `boolean` | | | | + +## HiFi read options + +Options for HiFi reads + +| Parameter | Description | Type | Default | Required | Hidden | +| -------------- | ------------------------- | --------- | ------- | -------- | ------ | +| `hifireads` | Path to HiFi reads | `string` | | | | +| `hifi_trim` | Trim HiFi reads with lima | `boolean` | | | | +| `hifi_primers` | Primers to use with lima | `string` | | | | + +## QC options + +Options for QC tools + +| Parameter | Description | Type | Default | Required | Hidden | +| ------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------- | --------- | ----------------- | -------- | ------ | +| `merqury` | Run merqury (if short reads are provided) | `boolean` | True | | | +| `qc_reads` | Long reads that should be used for QC when both ONT and HiFi reads are provided. Options are `'ont'` or `'hifi'` | `string` | ont | | | +| `busco` | Run BUSCO? | `boolean` | | | | +| `busco_db` | Path to busco db (optional) | `string` | | | | +| `busco_lineage` | Busco lineage to use | `string` | brassicales_odb10 | | | +| `quast` | Run quast | `boolean` | | | | +| `ref_map_bam` | A mapping (bam) of reads mapped to the reference can be provided for QC. If provided alignment to reference fasta will not run | `string` | | | | +| `assembly` | Can be used to proved existing assembly will skip assembly and perform downstream steps including qc | `string` | | | | +| `assembly_map_bam` | A mapping (bam) of reads mapped to the provided assembly can be specified for QC. If provided alignment to the provided assembly fasta will not run | `string` | | | | + +## Annotations options + +Options controlling annotation liftover + +| Parameter | Description | Type | Default | Required | Hidden | +| ------------------ | ------------------------------------------- | --------- | ------- | -------- | ------ | +| `lift_annotations` | Lift-over annotations (requires reference)? | `boolean` | True | | | + +## Short read options + +Options for short reads + +| Parameter | Description | Type | Default | Required | Hidden | +| ------------------ | -------------------------------- | --------- | ------- | -------- | ------ | +| `use_short_reads` | Use short reads? | `boolean` | | | True | +| `trim_short_reads` | trim short reads with trimgalore | `boolean` | | | | +| `meryl_k` | kmer length for meryl / merqury | `integer` | 21 | | | +| `shortread_F` | Path to forward short reads | `string` | | | | +| `shortread_R` | Path to reverse short reads | `string` | | | | +| `paired` | Are shortreads paired? | `string` | | | | From 52708b6c486e212780c0b8e0c965709a27ee3406 Mon Sep 17 00:00:00 2001 From: Niklas Schandry Date: Fri, 11 Jul 2025 16:00:04 +0200 Subject: [PATCH 031/162] less blocking --- nextflow.config | 9 +- subworkflows/local/assemble/main.nf | 2 +- subworkflows/local/prepare_ont/chop/main.nf | 6 +- subworkflows/local/prepare_ont/main.nf | 69 +++++----- .../local/prepare_ont/run_nanoq/main.nf | 12 +- subworkflows/local/prepare_shortreads/main.nf | 19 ++- .../main.nf | 3 +- workflows/genomeassembler.nf | 124 ++++++++++++------ 8 files changed, 143 insertions(+), 101 deletions(-) diff --git a/nextflow.config b/nextflow.config index 8925f19b..e71638aa 100644 --- a/nextflow.config +++ b/nextflow.config @@ -61,7 +61,7 @@ params { hifi_primers = null // if lima, then this needs to be a path to a list of primers // -- Short read -- use_short_reads = false // short reads available? - trim_short_reads = false // trim short reads? + shortread_trim = false // trim short reads? shortread_F = null shortread_R = null paired = null @@ -239,13 +239,6 @@ profiles { test { includeConfig 'conf/test.config' } test_full { includeConfig 'conf/test_full.config' } - hifi_flye { includeConfig 'configs/hifi_flye.config' } // Hifi-reads with flye - hifi_hifiasm { includeConfig 'configs/hifi_hifiasm.config' } // hifi-reads with hifiasm - ont_flye { includeConfig 'configs/ont_flye.config' } // ont-reads with flye - ont_hifiasm { includeConfig 'configs/ont_hifiasm.config' } // ont-reads with hifiasm - hifiont_hifiasm { includeConfig 'configs/hifi_ont_hifiasm_ul.config' } // ont and hifi reads with hifiasm --ul - hifiont_flye_on_hifiasm { includeConfig 'configs/hifi_ont_flye_on_hifiasm.config' } // ont and hifi reads. ONT via flye, Hifi via hifiasm, scaffold flye on hifiasm - hifiont_hifiasm_on_hifiasm { includeConfig 'configs/hifi_ont_hifiasm_on_hifiasm.config' } // ont and hifi reads. ONT via hifiasm, Hifi via hifiasm, scaffold ONT on HiFi } // Load nf-core custom profiles from different institutions diff --git a/subworkflows/local/assemble/main.nf b/subworkflows/local/assemble/main.nf index 13d454cc..bd0a8de5 100644 --- a/subworkflows/local/assemble/main.nf +++ b/subworkflows/local/assemble/main.nf @@ -107,7 +107,7 @@ workflow ASSEMBLE { .set { ch_main_assemble_ont_hifiasm } - HIFIASM_ONT(ch_main_assemble_ont_hifiasm.map { it -> [ [id: it.meta.id, hifiasm_args: it.hifiasm_args], it.ontreads, [] ] }, [[], [], []], [[], [], []], [[], []]) + HIFIASM_ONT(ch_main_assemble_ont_hifiasm.map { it -> [ [id: it.meta.id, hifiasm_args: it.hifiasm_args], [], it.ontreads ] }, [[], [], []], [[], [], []], [[], []]) GFA_2_FA_ONT( HIFIASM_ONT.out.processed_unitigs.map { meta, fasta -> [[id: meta.id], fasta] } ) diff --git a/subworkflows/local/prepare_ont/chop/main.nf b/subworkflows/local/prepare_ont/chop/main.nf index cf77d8d5..64fa2c24 100644 --- a/subworkflows/local/prepare_ont/chop/main.nf +++ b/subworkflows/local/prepare_ont/chop/main.nf @@ -10,11 +10,7 @@ workflow CHOP { PORECHOP(input) - input.map { - it -> [ it[0] ] - - } - .join(PORECHOP.out.reads) + PORECHOP.out.reads .set { chopped_reads } ch_versions.mix(PORECHOP.out.versions) diff --git a/subworkflows/local/prepare_ont/main.nf b/subworkflows/local/prepare_ont/main.nf index a33eb342..3eecaed7 100644 --- a/subworkflows/local/prepare_ont/main.nf +++ b/subworkflows/local/prepare_ont/main.nf @@ -10,15 +10,6 @@ workflow PREPARE_ONT { Channel.empty().set { ch_versions } ch_main - .branch { - it -> - ont: it.ontreads != null - no_ont: !it.ontreads - } - .set { ch_ont } - - ch_ont - .ont .branch { it -> to_collect: it.ont_collect @@ -40,13 +31,15 @@ workflow PREPARE_ONT { ch_ont_collect_branched .to_collect - .map { it -> it - it.subMap["ontreads"] } + .map { it -> it - it.subMap("ontreads") } .map { it -> it.collect { entry -> [ entry.value, entry ] } } .join(ch_collected_reads) .map { it -> it.collect { _entry, map -> [ (map.key): map.value ] }.collectEntries() } .mix(ch_ont_collect_branched.no_collect) .set { ch_collected } + // ch_collected is the same samples as the input channel + ch_collected .branch { chop: it.ont_trim @@ -55,23 +48,40 @@ workflow PREPARE_ONT { .set { ch_ont_chop_branched } ch_ont_chop_branched - .chop - .map { it -> [it.meta, it.ontreads]} - .set { chop_in } + .chop + .map { it -> [it.meta, it.ontreads]} + .set { chop_in } CHOP(chop_in) - CHOP.out.chopped_reads - .join(ch_ont_chop_branched + ch_ont_chop_branched .chop - .map { it -> [it.meta, it.ont_nanoq] } - ) - .filter { it -> it[2] == true } - .map { it -> [meta: it[0], ontreads: it[1]]} - .mix(ch_ont_chop_branched - .no_chop - .filter { it -> it.ont_nanoq } - .map { it -> [meta: it.meta, ontreads: it.ontreads] } ) + .map { it -> it - it.subMap("ontreads")} + .map { it -> it.collect { entry -> [ entry.value, entry ] } } + .join( + CHOP + .out + .chopped_reads + .map { meta, reads -> [meta: meta, ontreads: reads] } + .map { it -> it.collect { entry -> [ entry.value, entry ] } } + ) + .map { it -> it.collect { _entry, map -> [ (map.key): map.value ] }.collectEntries() } + .mix( + ch_ont_chop_branched + .no_chop + ) + .set { ch_ont_chopped } + + ch_ont_chopped + .branch { + nanoq: it.ont_nanoq + no_nanoq: !it.ont_nanoq + } + .set { ch_ont_chopped_branched } + + ch_ont_chopped_branched + .nanoq + .map { it -> [it.meta, it.ontreads] } .set {ch_nanoq_in} RUN_NANOQ(ch_nanoq_in) @@ -85,18 +95,13 @@ workflow PREPARE_ONT { RUN_NANOQ.out.stats.set { nanoq_stats } - ch_ont - .ont - .filter { it -> it.ont_nanoq } - .map { it -> it - it.subMap("ontreads", "ont_read_length") } + ch_ont_chopped_branched + .nanoq + .map { it -> it - it.subMap("ont_read_length") } .map { it -> it.collect { entry -> [ entry.value, entry ] } } .join( med_len ) - .join( ch_nanoq_in - .map { it -> it.collect { entry -> [ entry.value, entry ] } } - ) .map { it -> it.collect { _entry, map -> [ (map.key): map.value ] }.collectEntries() } - .mix(ch_ont.no_ont) - .mix(ch_ont.ont.filter { it -> !it.nanoq }) + .mix(ch_ont_chopped_branched.no_nanoq) .set { main_out } versions = ch_versions.mix(COLLECT.out.versions).mix(CHOP.out.versions).mix(RUN_NANOQ.out.versions) diff --git a/subworkflows/local/prepare_ont/run_nanoq/main.nf b/subworkflows/local/prepare_ont/run_nanoq/main.nf index 50d5dc80..8ee7d81c 100644 --- a/subworkflows/local/prepare_ont/run_nanoq/main.nf +++ b/subworkflows/local/prepare_ont/run_nanoq/main.nf @@ -6,16 +6,8 @@ workflow RUN_NANOQ { main: Channel.empty().set { versions } - inputs.map { - it -> - [ - meta: it.meta, - ontreads: it.ontreads - ] - } - .set { in_reads } - - NANOQ(in_reads) + + NANOQ(inputs) NANOQ.out.report.set { report } diff --git a/subworkflows/local/prepare_shortreads/main.nf b/subworkflows/local/prepare_shortreads/main.nf index a714bfd5..e694a0eb 100644 --- a/subworkflows/local/prepare_shortreads/main.nf +++ b/subworkflows/local/prepare_shortreads/main.nf @@ -39,18 +39,19 @@ workflow PREPARE_SHORTREADS { shortreads .branch { it -> - trim: it.shortreads_trim - no_trim: !it.shortreads_trim + trim: it.shortread_trim + no_trim: !it.shortread_trim } .set { shortreads } - TRIMGALORE(shortreads.trim) + TRIMGALORE(shortreads.trim.map { it -> [it.meta, it.shortreads] }) // unite branched: // add trimmed reads to trim channel, then mix with shortreads.no_trim - shortreads.trim - .map { it -> it - it.subMap["shortreads"] } + shortreads + .trim + .map { it -> it - it.subMap("shortreads") } .map { it -> it.collect { entry -> [ entry.value, entry ] } } .join( TRIMGALORE.out.reads @@ -75,11 +76,15 @@ workflow PREPARE_SHORTREADS { shortreads_in .map { - it -> it.subMap('shortread_F', 'shortread_R', 'paired') + it -> it - it.subMap('shortread_F', 'shortread_R', 'paired') } + .map { it -> it.collect { entry -> [ entry.value, entry ] } } .join( - shortreads.map { it -> [meta: [id: it[0].id], shortreads: it[1]]} + shortreads + .map { it -> [meta: [id: it.meta.id], shortreads: it.shortreads]} + .map { it -> it.collect { entry -> [ entry.value, entry ] } } ) + .map { it -> it.collect { _entry, map -> [ (map.key): map.value ] }.collectEntries() } .set { main_out } versions = ch_versions.mix(MERYL_COUNT.out.versions).mix(MERYL_UNIONSUM.out.versions) diff --git a/subworkflows/local/utils_nfcore_genomeassembler_pipeline/main.nf b/subworkflows/local/utils_nfcore_genomeassembler_pipeline/main.nf index 23ada56f..a8d16620 100644 --- a/subworkflows/local/utils_nfcore_genomeassembler_pipeline/main.nf +++ b/subworkflows/local/utils_nfcore_genomeassembler_pipeline/main.nf @@ -165,6 +165,7 @@ workflow PIPELINE_INITIALISATION { busco: it.busco ?: params.busco, busco_lineage: it.busco_lineage ?: params.busco_lineage, busco_db: it.busco_db ?: params.busco_db, + meryl_k: it.meryl_k ?: params.meryl_k, merqury: it.merqury ?: params.merqury, lift_annotations: it.lift_annotations ?: params.lift_annotations, shortread_F: it.shortread_F ?: params.shortread_F, @@ -172,7 +173,7 @@ workflow PIPELINE_INITIALISATION { paired: it.paired ?: params.paired, // new: use_short_reads: it.use_short_reads ?: params.use_short_reads ?: it.shortread_F ? true : false, - shortread_trim: it.shortread_trim ?: params.trim_short_reads + shortread_trim: it.shortread_trim ?: params.shortread_trim ] } .set { ch_samplesheet } diff --git a/workflows/genomeassembler.nf b/workflows/genomeassembler.nf index 833e6ac9..61862d86 100644 --- a/workflows/genomeassembler.nf +++ b/workflows/genomeassembler.nf @@ -124,65 +124,97 @@ workflow GENOMEASSEMBLER { */ ch_main .filter { - it -> it.shortread_F ? true : false + it -> (it.shortread_F && it.use_short_reads) ? true : false } .set { shortreads } + ch_main + .filter { + it -> (it.ontreads) ? true : false + } + .set { ontreads } + + ch_main + .filter { + it -> (it.hifireads) ? true : false + } + .set { hifireads } + // adapted to sample-logic PREPARE_SHORTREADS(shortreads) + + PREPARE_SHORTREADS.out.meryl_kmers.set { meryl_kmers } // This changes ch_main shortreads_F and _R become one tuple, paired is gone. // put shortreads back together with samples without shortreads + ch_main .filter { it -> !it.shortread_F ? true : false } - .map { it -> it - it.subMap["shortread_F","shortread_R", "paired"] + [shorteads: null] } - .mix(PREPARE_SHORTREADS.out.shortreads) + .map { it -> it - it.subMap("shortread_F","shortread_R", "paired") + [shorteads: null] } + .mix(PREPARE_SHORTREADS.out.main_out) .set { ch_main_shortreaded } - PREPARE_SHORTREADS.out.meryl_kmers.set { meryl_kmers } + ONT(ontreads) + ONT.out.main_out.set { ch_main_ont_prepped } - ch_versions = ch_versions.mix(PREPARE_SHORTREADS.out.versions) + HIFI(hifireads) + HIFI.out.main_out.set { ch_main_hifi_prepped } - /* - ONT reads - */ + // rebuild the main channel from shortread out, ont out and hifi out. + // This will block entry into assemble, and it should. - // adapted to sample-logic - ONT(ch_main_shortreaded) - ONT.out.main_out.set { ch_main_onted } + // ch_main_shortreaded contains all samples + // add ONT outputs to mixed shortread output - ONT.out.nanoq_report - .concat( - ONT.out.nanoq_stats - ) - .collect { it -> it[1] } - .set { nanoq_files } - - ONT.out.genomescope_summary - .concat( - ONT.out.genomescope_plot + ch_main_shortreaded + .filter { + it -> it.ontreads ? true : false + } + .map { it -> it.subMap("meta","shortreads")} + .map { it -> it.collect { entry -> [ entry.value, entry ] } } + .join( ch_main_ont_prepped + .map { it -> it - it.subMap("shortreads") } + .map { it -> it.collect { entry -> [ entry.value, entry ] } } + ) + // After joining re-create the maps from the stored map + .map { it -> it.collect { _entry, map -> [ (map.key): map.value ] }.collectEntries() } + // mix back in those samples where nothing was done to the ont reads + .mix(ch_main_shortreaded + .filter { + it -> it.ontreads ? false : true + } ) - .unique() - .collect { it -> it[1] } - .set { genomescope_files } - - ch_versions = ch_versions.mix(ONT.out.versions) - - - /* - HIFI reads - */ - - // adapted to sample-logic + .set { + ch_main_sr_ont + } - HIFI(ch_main_onted) + // Add prepared hifi-reads: - HIFI.out.main_out.set { ch_main_prepared } + ch_main_sr_ont + .filter { + it -> it.hifireads ? true : false + } + .map { it -> it.subMap("meta","shortreads","ontreads")} + .map { it -> it.collect { entry -> [ entry.value, entry ] } } + .join( ch_main_hifi_prepped + .map { it -> it - it.subMap("shortreads","ontreads") } + .map { it -> it.collect { entry -> [ entry.value, entry ] } } + ) + // After joining re-create the maps from the stored map + .map { it -> it.collect { _entry, map -> [ (map.key): map.value ] }.collectEntries() } + // mix back in those samples where nothing was done to the hifireads reads + .mix(ch_main_sr_ont + .filter { + it -> it.hifireads ? false : true + } + ) + .set { + ch_main_prepared + } - ch_versions = ch_versions.mix(HIFI.out.versions) /* Assembly */ @@ -232,8 +264,26 @@ workflow GENOMEASSEMBLER { .mix(SCAFFOLD.out.ch_main) .set { ch_main_scaffolded } + ONT.out.nanoq_report + .concat( + ONT.out.nanoq_stats + ) + .collect { it -> it[1] } + .set { nanoq_files } + + ONT.out.genomescope_summary + .concat( + ONT.out.genomescope_plot + ) + .unique() + .collect { it -> it[1] } + .set { genomescope_files } + + ch_versions = ch_versions.mix(PREPARE_SHORTREADS.out.versions).mix(ONT.out.versions).mix(HIFI.out.versions).mix(ASSEMBLE.out.versions).mix(POLISH.out.versions).mix(SCAFFOLD.out.versions) + + ch_versions = ch_versions + - ch_versions = ch_versions.mix(SCAFFOLD.out.versions) /* Report From 8b4b15795db8a22acfbbb0ce32ecdfc908a17512 Mon Sep 17 00:00:00 2001 From: Niklas Schandry Date: Mon, 21 Jul 2025 09:51:55 +0200 Subject: [PATCH 032/162] wip commit --- modules/local/jellyfish/count/main.nf | 2 +- nextflow.config | 8 +- subworkflows/local/assemble/main.nf | 25 ++-- .../local/{ => prepare}/jellyfish/main.nf | 18 +-- subworkflows/local/prepare/main.nf | 132 ++++++++++++++++++ .../local/{ => prepare}/prepare_hifi/main.nf | 4 +- .../{ => prepare}/prepare_ont/chop/main.nf | 2 +- .../{ => prepare}/prepare_ont/collect/main.nf | 2 +- .../local/{ => prepare}/prepare_ont/main.nf | 0 .../prepare_ont/run_nanoq/main.nf | 2 +- .../{ => prepare}/prepare_shortreads/main.nf | 19 ++- .../local/scaffolding/longstitch/main.nf | 11 +- .../main.nf | 6 +- workflows/genomeassembler.nf | 8 ++ 14 files changed, 202 insertions(+), 37 deletions(-) rename subworkflows/local/{ => prepare}/jellyfish/main.nf (77%) create mode 100644 subworkflows/local/prepare/main.nf rename subworkflows/local/{ => prepare}/prepare_hifi/main.nf (92%) rename subworkflows/local/{ => prepare}/prepare_ont/chop/main.nf (75%) rename subworkflows/local/{ => prepare}/prepare_ont/collect/main.nf (91%) rename subworkflows/local/{ => prepare}/prepare_ont/main.nf (100%) rename subworkflows/local/{ => prepare}/prepare_ont/run_nanoq/main.nf (83%) rename subworkflows/local/{ => prepare}/prepare_shortreads/main.nf (82%) diff --git a/modules/local/jellyfish/count/main.nf b/modules/local/jellyfish/count/main.nf index 3a3c62e2..3aebbf94 100644 --- a/modules/local/jellyfish/count/main.nf +++ b/modules/local/jellyfish/count/main.nf @@ -25,7 +25,7 @@ process COUNT { cp ${fasta} ${fasta.baseName}.fasta fi jellyfish count \\ - -m ${meta.ont_jellyfish_k} \\ + -m ${meta.jellyfish_k} \\ -s 140M \\ -C \\ -t ${task.cpus} ${fasta.baseName}.fasta diff --git a/nextflow.config b/nextflow.config index e71638aa..f8aebf81 100644 --- a/nextflow.config +++ b/nextflow.config @@ -51,10 +51,10 @@ params { ont_collect = false // collect ONT reads into a single file ont_trim = false // run porechop on ONT ont_nanoq = false // run nanoq - // -- Jellyfish (ONT reads only) -- - ont_jellyfish = false - ont_jellyfish_k = 21 - read_length = null // avg read length, can be estimated from reads + // -- Jellyfish (qc reads) -- + jellyfish = false + jellyfish_k = 21 + qc_read_length = null // avg read length, can be estimated from reads dump = false // dump jellyfish output // -- HiFi -- hifi_trim = false // run lima on HiFi reads? diff --git a/subworkflows/local/assemble/main.nf b/subworkflows/local/assemble/main.nf index bd0a8de5..c5e69749 100644 --- a/subworkflows/local/assemble/main.nf +++ b/subworkflows/local/assemble/main.nf @@ -40,7 +40,7 @@ workflow ASSEMBLE { // Flye inputs: ch_main_assemble_branched .single - .filter { it -> it.assembler1 == "flye" } + .filter { it -> it.assembler1 == "flye" || it.assembler2 == "flye" } .mix( ch_main_assemble_branched .scaffold @@ -56,11 +56,11 @@ workflow ASSEMBLE { [ id: it.meta.id, genome_size: it.genome_size, - flye_args: it.fyle_args + flye_args: it.flye_args ?: "" ], - it.assembler1 == "flye" ? it.ontreads : (it.assembler2 == "flye" ? it.hifireads : null), + it.assembler1 == "flye" ? it.ontreads : (it.assembler2 == "flye" ? it.hifireads : []), ] - mode: it.flye_mode ?: it.assembler1 == "flye" ? "--nano-hq" : "--pacbio-hifi" + mode: it.assembler1 == "flye" ? "--nano-hq" : "--pacbio-hifi" } .set { flye_inputs } @@ -86,10 +86,17 @@ workflow ASSEMBLE { ) .set { ch_main_assemble_hifi_hifiasm } - HIFIASM(ch_main_assemble_hifi_hifiasm.map { it -> [ [id: it.meta.id, hifiasm_args: it.hifiasm_args], it.hifireads, it.ontreads ?: [] ] }, - [[], [], []], - [[], [], []], - [[], []]) + HIFIASM(ch_main_assemble_hifi_hifiasm + .map { + it -> [ + [id: it.meta.id, hifiasm_args: it.hifiasm_args ?: ""], + it.hifireads, + (it.stragtegy == "hybrid" && it.ontreads) ? it.ontreads : [] + ] + }, + [[], [], []], + [[], [], []], + [[], []]) GFA_2_FA_HIFI( HIFIASM.out.processed_unitigs.map { meta, fasta -> [[id: meta.id], fasta] } ) @@ -107,7 +114,7 @@ workflow ASSEMBLE { .set { ch_main_assemble_ont_hifiasm } - HIFIASM_ONT(ch_main_assemble_ont_hifiasm.map { it -> [ [id: it.meta.id, hifiasm_args: it.hifiasm_args], [], it.ontreads ] }, [[], [], []], [[], [], []], [[], []]) + HIFIASM_ONT(ch_main_assemble_ont_hifiasm.map { it -> [ [id: it.meta.id, hifiasm_args: it.hifiasm_args ?: ""], it.ontreads, [] ] }, [[], [], []], [[], [], []], [[], []]) GFA_2_FA_ONT( HIFIASM_ONT.out.processed_unitigs.map { meta, fasta -> [[id: meta.id], fasta] } ) diff --git a/subworkflows/local/jellyfish/main.nf b/subworkflows/local/prepare/jellyfish/main.nf similarity index 77% rename from subworkflows/local/jellyfish/main.nf rename to subworkflows/local/prepare/jellyfish/main.nf index fa4e451e..43576431 100644 --- a/subworkflows/local/jellyfish/main.nf +++ b/subworkflows/local/prepare/jellyfish/main.nf @@ -1,8 +1,8 @@ -include { COUNT } from '../../../modules/local/jellyfish/count/main' -include { DUMP } from '../../../modules/local/jellyfish/dump/main' -include { HISTO } from '../../../modules/local/jellyfish/histo/main' -include { STATS } from '../../../modules/local/jellyfish/stats/main' -include { GENOMESCOPE } from '../../../modules/local/genomescope/main' +include { COUNT } from '../../../../modules/local/jellyfish/count/main' +include { DUMP } from '../../../../modules/local/jellyfish/dump/main' +include { HISTO } from '../../../../modules/local/jellyfish/histo/main' +include { STATS } from '../../../../modules/local/jellyfish/stats/main' +include { GENOMESCOPE } from '../../../../modules/local/genomescope/main' workflow JELLYFISH { take: @@ -14,8 +14,8 @@ workflow JELLYFISH { ch_main.map { it -> [ - [id: it.meta.id, ont_jellyfish_k: it.ont_jellyfish_k], - it.ontreads + [id: it.meta.id, jellyfish_k: it.jellyfish_k], + it.qc_reads_path ] } .set { samples } @@ -39,8 +39,8 @@ workflow JELLYFISH { .map { it -> [ it.meta, - it.ont_jellyfish_k, - it.ont_read_length + it.jellyfish_k, + it.qc_read_length ] } ) diff --git a/subworkflows/local/prepare/main.nf b/subworkflows/local/prepare/main.nf new file mode 100644 index 00000000..71a26373 --- /dev/null +++ b/subworkflows/local/prepare/main.nf @@ -0,0 +1,132 @@ +include { PREPARE_ONT as ONT } from 'prepare_ont/main' +include { PREPARE_HIFI as HIFI } from 'prepare_hifi/main' +include { PREPARE_SHORTREADS as SHORTREADS } from 'prepare_shortreads/main' + +workflow PREPARE { + take: ch_main + + main: + ch_main + .filter { + it -> (it.shortread_F && it.use_short_reads) ? true : false + } + .set { shortreads } + + ch_main + .filter { + it -> (it.ontreads) ? true : false + } + .set { ontreads } + + ch_main + .filter { + it -> (it.hifireads) ? true : false + } + .set { hifireads } + + + // adapted to sample-logic + SHORTREADS(shortreads) + + SHORTREADS.out.meryl_kmers.set { meryl_kmers } + // This changes ch_main shortreads_F and _R become one tuple, paired is gone. + + // put shortreads back together with samples without shortreads + + ch_main + .filter { + it -> !it.shortread_F ? true : false + } + .map { it -> it - it.subMap("shortread_F","shortread_R", "paired") + [shorteads: null] } + .mix(SHORTREADS.out.main_out) + .set { ch_main_shortreaded } + + + /* + TODO: + A current limitation is that jellyfish / genomescope are only in ONT. + Further refactoring is probably necessary, PREPARE should be split into shortread + and longread, and QC-reads should be used to prepare the jellyfish / genomescope + + */ + ONT(ontreads) + + ONT.out.main_out.set { ch_main_ont_prepped } + + ONT.out.nanoq_report.set { nanoq_report } + + ONT.out.nanoq_stats.set { nanoq_stats } + + ONT.out.main_out.set { ch_main_ont_prepped } + + HIFI(hifireads) + + HIFI.out.main_out.set { ch_main_hifi_prepped } + + ch_main_shortreaded + .filter { + it -> it.ontreads ? true : false + } + .map { it -> it.subMap("meta","shortreads")} + .map { it -> it.collect { entry -> [ entry.value, entry ] } } + .join( ch_main_ont_prepped + .map { it -> it - it.subMap("shortreads") } + .map { it -> it.collect { entry -> [ entry.value, entry ] } } + ) + // After joining re-create the maps from the stored map + .map { it -> it.collect { _entry, map -> [ (map.key): map.value ] }.collectEntries() } + // mix back in those samples where nothing was done to the ont reads + .mix(ch_main_shortreaded + .filter { + it -> it.ontreads ? false : true + } + ) + .set { + ch_main_sr_ont + } + + // Add prepared hifi-reads: + + ch_main_sr_ont + .filter { + it -> it.hifireads ? true : false + } + .map { it -> it.subMap("meta","shortreads","ontreads")} + .map { it -> it.collect { entry -> [ entry.value, entry ] } } + .join( ch_main_hifi_prepped + .map { it -> it - it.subMap("shortreads","ontreads") } + .map { it -> it.collect { entry -> [ entry.value, entry ] } } + ) + // After joining re-create the maps from the stored map + .map { it -> it.collect { _entry, map -> [ (map.key): map.value ] }.collectEntries() } + // mix back in those samples where nothing was done to the hifireads reads + .mix(ch_main_sr_ont + .filter { + it -> it.hifireads ? false : true + } + ) + .set { + ch_main_prepared + } + + ch_main_prepared + .branch { + it -> + jellyfish: it.jellyfish + no_jelly: !it.jellyfish + } + + .set { ch_main_jellyfish_branched } + + JELLYFISH(ch_main_jellyfish_branched.jellyfish) + + ch_main_jellyfish_branched.no_jelly + .mix( JELLYFISH.out.main_out ) + .set { main_out } + + JELLYFISH.out.genomescope_summary.set { genomescope_summary } + JELLYFISH.out.genomescope_plot.set { genomescope_plot } + ch_versions = ch_versions.mix(JELLYFISH.out.versions) + + versions = ch_versions +} diff --git a/subworkflows/local/prepare_hifi/main.nf b/subworkflows/local/prepare/prepare_hifi/main.nf similarity index 92% rename from subworkflows/local/prepare_hifi/main.nf rename to subworkflows/local/prepare/prepare_hifi/main.nf index 312684d6..bb3e6ff5 100644 --- a/subworkflows/local/prepare_hifi/main.nf +++ b/subworkflows/local/prepare/prepare_hifi/main.nf @@ -1,5 +1,5 @@ -include { LIMA } from '../../../modules/nf-core/lima/main' -include { SAMTOOLS_FASTQ as TO_FASTQ } from '../../../modules/nf-core/samtools/fastq/main' +include { LIMA } from '../../../../modules/nf-core/lima/main' +include { SAMTOOLS_FASTQ as TO_FASTQ } from '../../../../modules/nf-core/samtools/fastq/main' workflow PREPARE_HIFI { take: diff --git a/subworkflows/local/prepare_ont/chop/main.nf b/subworkflows/local/prepare/prepare_ont/chop/main.nf similarity index 75% rename from subworkflows/local/prepare_ont/chop/main.nf rename to subworkflows/local/prepare/prepare_ont/chop/main.nf index 64fa2c24..9183cbb3 100644 --- a/subworkflows/local/prepare_ont/chop/main.nf +++ b/subworkflows/local/prepare/prepare_ont/chop/main.nf @@ -1,4 +1,4 @@ -include { PORECHOP_PORECHOP as PORECHOP } from '../../../../modules/nf-core/porechop/porechop/main' +include { PORECHOP_PORECHOP as PORECHOP } from '../../../../../modules/nf-core/porechop/porechop/main' workflow CHOP { take: diff --git a/subworkflows/local/prepare_ont/collect/main.nf b/subworkflows/local/prepare/prepare_ont/collect/main.nf similarity index 91% rename from subworkflows/local/prepare_ont/collect/main.nf rename to subworkflows/local/prepare/prepare_ont/collect/main.nf index abc597d7..a3e3e155 100644 --- a/subworkflows/local/prepare_ont/collect/main.nf +++ b/subworkflows/local/prepare/prepare_ont/collect/main.nf @@ -1,4 +1,4 @@ -include { COLLECT_READS } from '../../../../modules/local/collect_reads/main' +include { COLLECT_READS } from '../../../../../modules/local/collect_reads/main' workflow COLLECT { take: diff --git a/subworkflows/local/prepare_ont/main.nf b/subworkflows/local/prepare/prepare_ont/main.nf similarity index 100% rename from subworkflows/local/prepare_ont/main.nf rename to subworkflows/local/prepare/prepare_ont/main.nf diff --git a/subworkflows/local/prepare_ont/run_nanoq/main.nf b/subworkflows/local/prepare/prepare_ont/run_nanoq/main.nf similarity index 83% rename from subworkflows/local/prepare_ont/run_nanoq/main.nf rename to subworkflows/local/prepare/prepare_ont/run_nanoq/main.nf index 8ee7d81c..06e35d2c 100644 --- a/subworkflows/local/prepare_ont/run_nanoq/main.nf +++ b/subworkflows/local/prepare/prepare_ont/run_nanoq/main.nf @@ -1,4 +1,4 @@ -include { NANOQ } from '../../../../modules/local/nanoq/main' +include { NANOQ } from '../../../../../modules/local/nanoq/main' workflow RUN_NANOQ { take: diff --git a/subworkflows/local/prepare_shortreads/main.nf b/subworkflows/local/prepare/prepare_shortreads/main.nf similarity index 82% rename from subworkflows/local/prepare_shortreads/main.nf rename to subworkflows/local/prepare/prepare_shortreads/main.nf index e694a0eb..4292d6e2 100644 --- a/subworkflows/local/prepare_shortreads/main.nf +++ b/subworkflows/local/prepare/prepare_shortreads/main.nf @@ -1,6 +1,6 @@ -include { TRIMGALORE } from '../../../modules/nf-core/trimgalore/main' -include { MERYL_COUNT } from '../../../modules/nf-core/meryl/count/main' -include { MERYL_UNIONSUM } from '../../../modules/nf-core/meryl/unionsum/main' +include { TRIMGALORE } from '../../../../modules/nf-core/trimgalore/main' +include { MERYL_COUNT } from '../../../../modules/nf-core/meryl/count/main' +include { MERYL_UNIONSUM } from '../../../../modules/nf-core/meryl/unionsum/main' workflow PREPARE_SHORTREADS { take: @@ -9,8 +9,17 @@ workflow PREPARE_SHORTREADS { main: Channel.empty().set { ch_versions } - //shortreads_in.view { it -> "Shortread input: $it"} - + /* + TODO: + Grouped preparations + Generally, I expect that a group will contain the same set of input. + To reduce redundant work on the inputs that belong one group, in all + prepare_* subworkflows groups will be used as meta.id, if a group is + set. After the preparations are done, results are joined back to all + members of the group. This needs to account for sample level setting + of additional args. For shortreads no args can be set at the sample- + level, so here everything group only. + */ shortreads_in .map { create_shortread_channel(it) } .set { shortreads } diff --git a/subworkflows/local/scaffolding/longstitch/main.nf b/subworkflows/local/scaffolding/longstitch/main.nf index 53ede958..43778071 100644 --- a/subworkflows/local/scaffolding/longstitch/main.nf +++ b/subworkflows/local/scaffolding/longstitch/main.nf @@ -7,6 +7,15 @@ workflow RUN_LONGSTITCH { ch_main meryl_kmers + /* + TODO: + Longstitch needs genomesize. For ONT reads that is estimated if not provided. + For hifireads it needs to be provided. Currently, not checks for that.. + Depending on the reads used, longmap needs to be changed. + This should probably be done via args / config, but needs a way to do this per-sample. + Probably passing in additional information via meta is the way to go for this. + */ + main: Channel.empty().set { ch_versions } @@ -17,7 +26,7 @@ workflow RUN_LONGSTITCH { it.meta, it.polished ? (it.polished.pilon ?: it.polished.medaka) : it.assembly, it.qc_reads_path, - it.genome_size + it.genome_size ?: params.genome_size ] } .set { longstitch_in } diff --git a/subworkflows/local/utils_nfcore_genomeassembler_pipeline/main.nf b/subworkflows/local/utils_nfcore_genomeassembler_pipeline/main.nf index a8d16620..a95e6d07 100644 --- a/subworkflows/local/utils_nfcore_genomeassembler_pipeline/main.nf +++ b/subworkflows/local/utils_nfcore_genomeassembler_pipeline/main.nf @@ -135,9 +135,9 @@ workflow PIPELINE_INITIALISATION { ont_collect: it.ont_collect ?: params.ont_collect, ont_trim: it.ont_trim ?: params.ont_trim, ont_nanoq: it.ont_nanoq ?: params.ont_nanoq, - ont_jellyfish: it.ont_jellyfish ?: (params.ont_jellyfish && it.ontreads), - ont_jellyfish_k: it.ont_jellyfish_k ?: params.ont_jellyfish_k, - ont_read_length: it.ont_read_length ?: params.read_length, + jellyfish: it.jellyfish ?: params.jellyfish, + jellyfish_k: it.ont_jellyfish_k ?: params.jellyfish_k, + qc_read_length: it.qc_read_length ?: params.qc_read_length, hifi_trim: it.hifi_trim ?: params.hifi_trim, hifi_primers: it.hifi_primers ?: params.hifi_primers, polish_medaka: it.polish_medaka ?: params.polish_medaka, diff --git a/workflows/genomeassembler.nf b/workflows/genomeassembler.nf index 61862d86..d098d4fb 100644 --- a/workflows/genomeassembler.nf +++ b/workflows/genomeassembler.nf @@ -156,6 +156,14 @@ workflow GENOMEASSEMBLER { .mix(PREPARE_SHORTREADS.out.main_out) .set { ch_main_shortreaded } + + /* + TODO: + A current limitation is that jellyfish / genomescope are only in ONT. + Further refactoring is probably necessary, PREPARE should be split into shortread + and longread, and QC-reads should be used to prepare the jellyfish / genomescope + + */ ONT(ontreads) ONT.out.main_out.set { ch_main_ont_prepped } From 14c129ff30b9aa7e4b24b71fc8b2171245ed120d Mon Sep 17 00:00:00 2001 From: Niklas Schandry Date: Tue, 29 Jul 2025 10:26:55 +0200 Subject: [PATCH 033/162] use groups in prepare --- nextflow.config | 2 +- subworkflows/local/prepare/jellyfish/main.nf | 3 +- subworkflows/local/prepare/main.nf | 91 ++++++++++++--- .../local/prepare/prepare_hifi/main.nf | 45 ++++++-- .../local/prepare/prepare_ont/main.nf | 101 ++++++++++++++-- .../local/prepare/prepare_shortreads/main.nf | 107 ++++++++++++----- workflows/genomeassembler.nf | 108 +----------------- 7 files changed, 292 insertions(+), 165 deletions(-) diff --git a/nextflow.config b/nextflow.config index f8aebf81..4fdb5d07 100644 --- a/nextflow.config +++ b/nextflow.config @@ -54,7 +54,7 @@ params { // -- Jellyfish (qc reads) -- jellyfish = false jellyfish_k = 21 - qc_read_length = null // avg read length, can be estimated from reads + qc_read_length = 20000 // avg read length, can be estimated from reads, used for genomescope and not very critical. dump = false // dump jellyfish output // -- HiFi -- hifi_trim = false // run lima on HiFi reads? diff --git a/subworkflows/local/prepare/jellyfish/main.nf b/subworkflows/local/prepare/jellyfish/main.nf index 43576431..d0c08723 100644 --- a/subworkflows/local/prepare/jellyfish/main.nf +++ b/subworkflows/local/prepare/jellyfish/main.nf @@ -11,6 +11,7 @@ workflow JELLYFISH { main: Channel.empty().set { genomescope_in } Channel.empty().set { ch_versions } + ch_main.map { it -> [ @@ -40,7 +41,7 @@ workflow JELLYFISH { [ it.meta, it.jellyfish_k, - it.qc_read_length + (it.qc_reads == "ONT" && it.ont_read_length) ? it.ont_read_length : it.qc_read_length ] } ) diff --git a/subworkflows/local/prepare/main.nf b/subworkflows/local/prepare/main.nf index 71a26373..60f53593 100644 --- a/subworkflows/local/prepare/main.nf +++ b/subworkflows/local/prepare/main.nf @@ -1,8 +1,70 @@ include { PREPARE_ONT as ONT } from 'prepare_ont/main' include { PREPARE_HIFI as HIFI } from 'prepare_hifi/main' include { PREPARE_SHORTREADS as SHORTREADS } from 'prepare_shortreads/main' +include { JELLYFISH } from 'jellyfish/main' workflow PREPARE { + /* + Grouped preparations + + Generally, I expect that a group will contain the same set of input. + To reduce redundant work on the inputs that belong one group, in all + prepare_* subworkflows groups will be used as meta.id, if a group is + set. After the preparations are done, results are joined back to all + members of the group. This needs to account for sample level setting + of additional args. For preparation no arg can be set at the sample- + level, so here everything group only. + + The pattern for grouping/ungrouping and mixing samples is: + + Grouping: + channel_grouped is a map that contains at least meta, group and path + within one group the path is expected to be the same for all members + + channel_grouped + .filter { it -> it.group } + .map { it -> [it.meta, it.group, it.path] } + .groupTuple(by: 1) + .map { + it -> + [ + [id: it[1], ids: it[0].id.collect().join("+")], + it[2].unique()[0] + ] + } + .mix( + groups + .filter { it -> !it.group } + .map { + it -> [ it.meta, it.path ] + } + ) + .set { collected_groups } + + This produces one channel that contains meta and path ready to go in + a process. + + For a process that again returns [meta, path] split group in samples + and merge with ungrouped samples: + + PROCESS(collected_groups) + + PROCESS.out + .filter { it -> it[0].ids } + .flatMap { it -> + it[0].ids + .tokenize("+") + .collect { sample -> [ meta: [ id: sample ], path: it[1] ] } + } + .mix(PROCESS.out + .filter { it -> !it[0].ids } + .map { + it -> [ meta: [ id: it[0].id ], path: it[1] ] + } + ) + .set { process_output } + */ + take: ch_main main: @@ -26,9 +88,11 @@ workflow PREPARE { // adapted to sample-logic + SHORTREADS(shortreads) SHORTREADS.out.meryl_kmers.set { meryl_kmers } + // This changes ch_main shortreads_F and _R become one tuple, paired is gone. // put shortreads back together with samples without shortreads @@ -42,23 +106,10 @@ workflow PREPARE { .set { ch_main_shortreaded } - /* - TODO: - A current limitation is that jellyfish / genomescope are only in ONT. - Further refactoring is probably necessary, PREPARE should be split into shortread - and longread, and QC-reads should be used to prepare the jellyfish / genomescope - - */ ONT(ontreads) ONT.out.main_out.set { ch_main_ont_prepped } - ONT.out.nanoq_report.set { nanoq_report } - - ONT.out.nanoq_stats.set { nanoq_stats } - - ONT.out.main_out.set { ch_main_ont_prepped } - HIFI(hifireads) HIFI.out.main_out.set { ch_main_hifi_prepped } @@ -126,7 +177,17 @@ workflow PREPARE { JELLYFISH.out.genomescope_summary.set { genomescope_summary } JELLYFISH.out.genomescope_plot.set { genomescope_plot } - ch_versions = ch_versions.mix(JELLYFISH.out.versions) - versions = ch_versions + SHORTREADS.out.versions + .mix(ONT.out.versions) + .mix(HIFI.out.versions) + .mix(JELLYFISH.out.versions) + .set { versions } + + emit: + ch_main = main_out + meryl_kmers + versions + genomescope_summary + genomescope_plot } diff --git a/subworkflows/local/prepare/prepare_hifi/main.nf b/subworkflows/local/prepare/prepare_hifi/main.nf index bb3e6ff5..4ff72808 100644 --- a/subworkflows/local/prepare/prepare_hifi/main.nf +++ b/subworkflows/local/prepare/prepare_hifi/main.nf @@ -28,6 +28,25 @@ workflow PREPARE_HIFI { // lima channel goes through lima and to_fastq ch_hifi_trim_branched .lima + .filter { it -> it.group } + .map { it -> [it.meta, it.group, it.hifireads, it.hifi_primers] } + .groupTuple(by: 1) + .map { + it -> + [ + meta: [id: it[1], ids: it[0].id.collect().join("+")], + hifireads: it[2].unique()[0], + hifi_primers: it[3].unique()[0] + ] + } + .mix( + ch_hifi_trim_branched + .lima + .filter { it -> !it.group } + .map { + it -> it.subMap("meta","hifireads","hifi_primers") + } + ) .multiMap { it -> reads: [it.meta, it.hifireads] @@ -38,6 +57,23 @@ workflow PREPARE_HIFI { LIMA(ch_lima_in.reads, ch_lima_in.primers ) TO_FASTQ(LIMA.out.bam, false) + TO_FASTQ + .out + .filter { it -> it[0].ids } + .flatMap { it -> + it[0].ids + .tokenize("+") + .collect { sample -> [ meta: [ id: sample ], hifireads: it[1] ] } + } + .mix(TO_FASTQESS.out + .filter { it -> !it[0].ids } + .map { + it -> [ meta: [ id: it[0].id ], hifireads: it[1] ] + } + ) + .set { to_fastq_out } + + // no_lima is mixed with lima outputs ch_hifi_trim_branched .no_lima @@ -48,14 +84,7 @@ workflow PREPARE_HIFI { .map { it -> it - it.subMap('hifireads') } .map { it -> it.collect { entry -> [ entry.value, entry ] } } .join( - TO_FASTQ.out.fastq - .map { - it -> - [ - meta: it[0], - hifireads: it[1] - ] - } + to_fastq_out .map { it -> it.collect { entry -> [ entry.value, entry ] } } ) .map { it -> it.collect { _entry, map -> [ (map.key): map.value ] }.collectEntries() } diff --git a/subworkflows/local/prepare/prepare_ont/main.nf b/subworkflows/local/prepare/prepare_ont/main.nf index 3eecaed7..978ff047 100644 --- a/subworkflows/local/prepare/prepare_ont/main.nf +++ b/subworkflows/local/prepare/prepare_ont/main.nf @@ -19,13 +19,41 @@ workflow PREPARE_ONT { ch_ont_collect_branched .to_collect - .map { it -> [it.meta, it.ontreads] } + .filter { it -> it.group } + .map { it -> [it.meta, it.group, it.ontreads] } + .groupTuple(by: 1) + .map { + it -> + [ + [id: it[1], ids: it[0].id.collect().join("+")], + it[2].unique()[0] + ] + } + .mix( + ch_ont_collect_branched + .to_collect + .filter { it -> !it.group } + .map { + it -> [ it.meta, it.ontreads ] + } + ) .set { collect_in } COLLECT(collect_in) COLLECT.out.reads - .map { it -> [meta: it[0], ontreads: it[1]] } + .filter { it -> it[0].ids } + .flatMap { it -> + it[0].ids + .tokenize("+") + .collect { sample -> [ meta: [ id: sample ], ontreads: it[1] ] } + } + .mix(COLLECT.out + .filter { it -> !it[0].ids } + .map { + it -> [ meta: [ it[0].id ], ontreads: it[1] ] + } + ) .map { it -> it.collect { entry -> [ entry.value, entry ] } } .set { ch_collected_reads } @@ -49,7 +77,25 @@ workflow PREPARE_ONT { ch_ont_chop_branched .chop - .map { it -> [it.meta, it.ontreads]} + .filter { it -> it.group } + .map { it -> [it.meta, it.group, it.ontreads] } + .groupTuple(by: 1) + .map { + it -> + [ + [id: it[1], ids: it[0].id.collect().join("+")], + it[2].unique()[0] + ] + } + .mix( + ch_ont_chop_branched + .chop + .filter { it -> !it.group } + .map { + it -> [ it.meta, it.ontreads ] + } + ) + .map { it -> [ it.meta, it.ontreads ] } .set { chop_in } CHOP(chop_in) @@ -62,7 +108,20 @@ workflow PREPARE_ONT { CHOP .out .chopped_reads - .map { meta, reads -> [meta: meta, ontreads: reads] } + .filter { it -> it[0].ids } + .flatMap { it -> + it[0].ids + .tokenize("+") + .collect { sample -> [ meta: [ id: sample ], ontreads: it[1] ] } + } + .mix(CHOP + .out + .chopped_reads + .filter { it -> !it[0].ids } + .map { + it -> [ meta: [ it[0].id ], ontreads: it[1] ] + } + ) .map { it -> it.collect { entry -> [ entry.value, entry ] } } ) .map { it -> it.collect { _entry, map -> [ (map.key): map.value ] }.collectEntries() } @@ -81,13 +140,41 @@ workflow PREPARE_ONT { ch_ont_chopped_branched .nanoq - .map { it -> [it.meta, it.ontreads] } - .set {ch_nanoq_in} + .filter { it -> it.group } + .map { it -> [it.meta, it.group, it.ontreads] } + .groupTuple(by: 1) + .map { + it -> + [ + [id: it[1], ids: it[0].id.collect().join("+")], + it[2].unique()[0] + ] + } + .mix( + ch_ont_chopped_branched + .nanoq + .filter { it -> !it.group } + .map { + it -> [ it.meta, it.ontreads ] + } + ) + .map { it -> [ it.meta, it.ontreads ] } + .set { ch_nanoq_in } RUN_NANOQ(ch_nanoq_in) RUN_NANOQ.out.median_length - .map { it -> [meta: it[0], ont_read_length: it[1]] } + .filter { it -> it[0].ids } + .flatMap { it -> + it[0].ids + .tokenize("+") + .collect { sample -> [ meta: [ id: sample ], ont_read_length: it[1] ] } + } + .mix( + RUN_NANOQ.out.median_length + .filter { it -> !it[0].ids } + .map { it -> [meta: [ id: it[0].id ], ont_read_length: it[1]] } + ) .map { it -> it.collect { entry -> [ entry.value, entry ] } } .set { med_len } diff --git a/subworkflows/local/prepare/prepare_shortreads/main.nf b/subworkflows/local/prepare/prepare_shortreads/main.nf index 4292d6e2..9f8ef31e 100644 --- a/subworkflows/local/prepare/prepare_shortreads/main.nf +++ b/subworkflows/local/prepare/prepare_shortreads/main.nf @@ -9,24 +9,10 @@ workflow PREPARE_SHORTREADS { main: Channel.empty().set { ch_versions } - /* - TODO: - Grouped preparations - Generally, I expect that a group will contain the same set of input. - To reduce redundant work on the inputs that belong one group, in all - prepare_* subworkflows groups will be used as meta.id, if a group is - set. After the preparations are done, results are joined back to all - members of the group. This needs to account for sample level setting - of additional args. For shortreads no args can be set at the sample- - level, so here everything group only. - */ shortreads_in .map { create_shortread_channel(it) } .set { shortreads } - - // use modified shortread channel - shortreads_in .map { it -> it - it.subMap('shortread_F', 'shortread_R', 'paired') @@ -53,35 +39,98 @@ workflow PREPARE_SHORTREADS { } .set { shortreads } - TRIMGALORE(shortreads.trim.map { it -> [it.meta, it.shortreads] }) + shortreads + .trim + .filter { it -> it.group } + .map { it -> [it.meta, it.group, it.shortreads] } + // Create a group + .groupTuple(by: 1) + .map { + it -> + [ + [ id: it[1], ids: it[0].id.collect().join("+") ], + it[2].unique()[0] + ] + } + .mix(shortreads.trim + .filter { it -> !it.group } + .map { + it -> [ it.meta, it.shortreads ] + } + ) + .set { trimgalore_in } + + TRIMGALORE(trimgalore_in) + + TRIMGALORE.out.reads + .filter { it -> it[0].ids } + .flatMap { it -> + it[0].ids.tokenize("+").collect { + sample -> [meta: [ id: sample ], shortreads: it[1] ] + } + } + .mix( + TRIMGALORE.out.reads + .filter { it -> !it[0].ids } + .map { it -> [ meta: [ id: it[0].id ], shortreads: it[1] ] } + ) + .map { it -> it.collect { entry -> [ entry.value, entry ] } } + .set { trimmed_reads } // unite branched: // add trimmed reads to trim channel, then mix with shortreads.no_trim - shortreads - .trim + shortreads.trim .map { it -> it - it.subMap("shortreads") } .map { it -> it.collect { entry -> [ entry.value, entry ] } } - .join( - TRIMGALORE.out.reads - .map { it -> [meta: [id: it[0].id], shortreads: it[1]]} - .map { it -> it.collect { entry -> [ entry.value, entry ] } } - ) + .join( trimmed_reads ) .map { it -> it.collect { _entry, map -> [ (map.key): map.value ] }.collectEntries() } .mix( shortreads.no_trim ) .set { shortreads } ch_versions = ch_versions.mix(TRIMGALORE.out.versions) + shortreads - .filter { it -> it.merqury } - .multiMap { it -> - reads: [ it.meta, it.shortreads ] - kmer_size: it.meryl_k - } - .set { meryl_in } + .filter { it -> it.merqury } + .filter { it -> it.group } + .map { it -> [it.meta, it.group, it.shortreads, it.meryl_k] } + // Create a group + .groupTuple(by: 1) + .map { + it -> [ + meta: [ id: it[1], ids: it[0].id.collect().join("+") ], + shortreads: it[2].unique()[0], + meryl_k: it[3].unique()[0] + ] + } + .mix(shortreads + .filter { it -> it.merqury } + .filter { it -> !it.group } + .map { it -> [meta: it.meta, shortreads: it.shortreads, meryl_k: it.meryl_k]} + ) + .multiMap { it -> + reads: [ it.meta, it.shortreads ] + kmer_size: it.meryl_k + } + .set { meryl_in } + MERYL_COUNT(meryl_in.reads, meryl_in.kmer_size) + MERYL_UNIONSUM(MERYL_COUNT.out.meryl_db, params.meryl_k) - MERYL_UNIONSUM.out.meryl_db.set { meryl_kmers } + + MERYL_UNIONSUM.out.meryl_db + .filter { it -> it[0].ids } + .flatMap { it -> + it[0].ids + .tokenize("+") + .collect { sample -> [ [ id: sample ], it[1] ] } + } + .mix(MERYL_UNIONSUM.out.meryl_db + .filter { it -> !it[0].ids } + .map { + it -> [ [ id: it[0].id ], it[1] ] + } + ).set { meryl_kmers } shortreads_in .map { diff --git a/workflows/genomeassembler.nf b/workflows/genomeassembler.nf index d098d4fb..ed7a438b 100644 --- a/workflows/genomeassembler.nf +++ b/workflows/genomeassembler.nf @@ -8,9 +8,7 @@ include { paramsSummaryMultiqc } from '../subworkflows/nf-core/utils_nfcore_pipe include { softwareVersionsToYAML } from '../subworkflows/nf-core/utils_nfcore_pipeline' include { methodsDescriptionText } from '../subworkflows/local/utils_nfcore_genomeassembler_pipeline' // Read preparation -include { PREPARE_ONT } from '../subworkflows/local/prepare_ont/main' -include { PREPARE_HIFI } from '../subworkflows/local/prepare_hifi/main' -include { PREPARE_SHORTREADS } from '../subworkflows/local/prepare_shortreads/main' +include { PREPARE } from '../subworkflows/local/prepare/main' // Read checks include { ONT } from '../subworkflows/local/ont/main' @@ -119,109 +117,11 @@ workflow GENOMEASSEMBLER { Prepare reads ============= */ - /* - Short reads - */ - ch_main - .filter { - it -> (it.shortread_F && it.use_short_reads) ? true : false - } - .set { shortreads } - - ch_main - .filter { - it -> (it.ontreads) ? true : false - } - .set { ontreads } - - ch_main - .filter { - it -> (it.hifireads) ? true : false - } - .set { hifireads } - - // adapted to sample-logic - PREPARE_SHORTREADS(shortreads) - - PREPARE_SHORTREADS.out.meryl_kmers.set { meryl_kmers } - // This changes ch_main shortreads_F and _R become one tuple, paired is gone. - - // put shortreads back together with samples without shortreads - - ch_main - .filter { - it -> !it.shortread_F ? true : false - } - .map { it -> it - it.subMap("shortread_F","shortread_R", "paired") + [shorteads: null] } - .mix(PREPARE_SHORTREADS.out.main_out) - .set { ch_main_shortreaded } - + PREPARE(ch_main) - /* - TODO: - A current limitation is that jellyfish / genomescope are only in ONT. - Further refactoring is probably necessary, PREPARE should be split into shortread - and longread, and QC-reads should be used to prepare the jellyfish / genomescope - - */ - ONT(ontreads) - ONT.out.main_out.set { ch_main_ont_prepped } - - HIFI(hifireads) - HIFI.out.main_out.set { ch_main_hifi_prepped } - - // rebuild the main channel from shortread out, ont out and hifi out. - // This will block entry into assemble, and it should. + PREPARE.out.ch_main.set { ch_main_prepared } - - // ch_main_shortreaded contains all samples - // add ONT outputs to mixed shortread output - - ch_main_shortreaded - .filter { - it -> it.ontreads ? true : false - } - .map { it -> it.subMap("meta","shortreads")} - .map { it -> it.collect { entry -> [ entry.value, entry ] } } - .join( ch_main_ont_prepped - .map { it -> it - it.subMap("shortreads") } - .map { it -> it.collect { entry -> [ entry.value, entry ] } } - ) - // After joining re-create the maps from the stored map - .map { it -> it.collect { _entry, map -> [ (map.key): map.value ] }.collectEntries() } - // mix back in those samples where nothing was done to the ont reads - .mix(ch_main_shortreaded - .filter { - it -> it.ontreads ? false : true - } - ) - .set { - ch_main_sr_ont - } - - // Add prepared hifi-reads: - - ch_main_sr_ont - .filter { - it -> it.hifireads ? true : false - } - .map { it -> it.subMap("meta","shortreads","ontreads")} - .map { it -> it.collect { entry -> [ entry.value, entry ] } } - .join( ch_main_hifi_prepped - .map { it -> it - it.subMap("shortreads","ontreads") } - .map { it -> it.collect { entry -> [ entry.value, entry ] } } - ) - // After joining re-create the maps from the stored map - .map { it -> it.collect { _entry, map -> [ (map.key): map.value ] }.collectEntries() } - // mix back in those samples where nothing was done to the hifireads reads - .mix(ch_main_sr_ont - .filter { - it -> it.hifireads ? false : true - } - ) - .set { - ch_main_prepared - } + PREPARE.out.meryl_kmers.set { meryl_kmers } /* Assembly From e52f31d16eebc9256d7e35e9ffd0649d17edb745 Mon Sep 17 00:00:00 2001 From: Niklas Schandry Date: Tue, 29 Jul 2025 10:51:16 +0200 Subject: [PATCH 034/162] cleanup --- subworkflows/local/hifi/main.nf | 20 ------- subworkflows/local/ont/main.nf | 54 ----------------- subworkflows/local/prepare/main.nf | 10 ++-- .../local/prepare/prepare_hifi/main.nf | 3 +- .../local/prepare/prepare_ont/main.nf | 10 ++-- workflows/genomeassembler.nf | 60 +++++++++---------- 6 files changed, 43 insertions(+), 114 deletions(-) delete mode 100644 subworkflows/local/hifi/main.nf delete mode 100644 subworkflows/local/ont/main.nf diff --git a/subworkflows/local/hifi/main.nf b/subworkflows/local/hifi/main.nf deleted file mode 100644 index d67486aa..00000000 --- a/subworkflows/local/hifi/main.nf +++ /dev/null @@ -1,20 +0,0 @@ -include { PREPARE_HIFI } from '../prepare_hifi/main' - - -workflow HIFI { - take: - main_in - - main: - Channel.empty().set { ch_versions } - - PREPARE_HIFI(main_in) - - ch_versions.mix(PREPARE_HIFI.out.versions) - - versions = ch_versions - - emit: - main_out = PREPARE_HIFI.out.main_out - versions -} diff --git a/subworkflows/local/ont/main.nf b/subworkflows/local/ont/main.nf deleted file mode 100644 index 84035ff0..00000000 --- a/subworkflows/local/ont/main.nf +++ /dev/null @@ -1,54 +0,0 @@ -include { PREPARE_ONT } from '../prepare_ont/main' -include { JELLYFISH } from '../jellyfish/main' - -workflow ONT { - take: - main_in - - main: - Channel.empty().set { ch_versions } - Channel.of([[],[]]) - .tap { genomescope_summary } - .tap { genomescope_plot } - - PREPARE_ONT(main_in) - - PREPARE_ONT.out.main_out.set { ch_main_prepared } - - PREPARE_ONT.out.nanoq_report.set { nanoq_report } - - PREPARE_ONT.out.nanoq_stats.set { nanoq_stats } - - ch_versions = ch_versions.mix(PREPARE_ONT.out.versions) - - //ch_main_prepared.view { it -> "PREPARED: $it"} - - ch_main_prepared - .branch { - it -> - jellyfish: it.ont_jellyfish - no_jelly: !it.ont_jellyfish - } - - .set { ch_main_jellyfish_branched } - - JELLYFISH(ch_main_jellyfish_branched.jellyfish) - - ch_main_jellyfish_branched.no_jelly - .mix( JELLYFISH.out.main_out ) - .set { main_out } - - JELLYFISH.out.genomescope_summary.set { genomescope_summary } - JELLYFISH.out.genomescope_plot.set { genomescope_plot } - ch_versions = ch_versions.mix(JELLYFISH.out.versions) - - versions = ch_versions - - emit: - main_out - nanoq_report - nanoq_stats - genomescope_plot - genomescope_summary - versions -} diff --git a/subworkflows/local/prepare/main.nf b/subworkflows/local/prepare/main.nf index 60f53593..db60ab7a 100644 --- a/subworkflows/local/prepare/main.nf +++ b/subworkflows/local/prepare/main.nf @@ -1,7 +1,7 @@ -include { PREPARE_ONT as ONT } from 'prepare_ont/main' -include { PREPARE_HIFI as HIFI } from 'prepare_hifi/main' -include { PREPARE_SHORTREADS as SHORTREADS } from 'prepare_shortreads/main' -include { JELLYFISH } from 'jellyfish/main' +include { PREPARE_ONT as ONT } from './prepare_ont/main' +include { PREPARE_HIFI as HIFI } from './prepare_hifi/main' +include { PREPARE_SHORTREADS as SHORTREADS } from './prepare_shortreads/main' +include { JELLYFISH } from './jellyfish/main' workflow PREPARE { /* @@ -190,4 +190,6 @@ workflow PREPARE { versions genomescope_summary genomescope_plot + nanoq_report = ONT.out.nanoq_report + nanoq_stats = ONT.out.nanoq_stats } diff --git a/subworkflows/local/prepare/prepare_hifi/main.nf b/subworkflows/local/prepare/prepare_hifi/main.nf index 4ff72808..402bd369 100644 --- a/subworkflows/local/prepare/prepare_hifi/main.nf +++ b/subworkflows/local/prepare/prepare_hifi/main.nf @@ -59,13 +59,14 @@ workflow PREPARE_HIFI { TO_FASTQ .out + .fastq .filter { it -> it[0].ids } .flatMap { it -> it[0].ids .tokenize("+") .collect { sample -> [ meta: [ id: sample ], hifireads: it[1] ] } } - .mix(TO_FASTQESS.out + .mix(TO_FASTQ.out.fastq .filter { it -> !it[0].ids } .map { it -> [ meta: [ id: it[0].id ], hifireads: it[1] ] diff --git a/subworkflows/local/prepare/prepare_ont/main.nf b/subworkflows/local/prepare/prepare_ont/main.nf index 978ff047..9b6b9b33 100644 --- a/subworkflows/local/prepare/prepare_ont/main.nf +++ b/subworkflows/local/prepare/prepare_ont/main.nf @@ -48,11 +48,11 @@ workflow PREPARE_ONT { .tokenize("+") .collect { sample -> [ meta: [ id: sample ], ontreads: it[1] ] } } - .mix(COLLECT.out - .filter { it -> !it[0].ids } - .map { - it -> [ meta: [ it[0].id ], ontreads: it[1] ] - } + .mix(COLLECT.out.reads + .filter { it -> !it[0].ids } + .map { + it -> [ meta: [ it[0].id ], ontreads: it[1] ] + } ) .map { it -> it.collect { entry -> [ entry.value, entry ] } } .set { ch_collected_reads } diff --git a/workflows/genomeassembler.nf b/workflows/genomeassembler.nf index ed7a438b..f58f4d50 100644 --- a/workflows/genomeassembler.nf +++ b/workflows/genomeassembler.nf @@ -3,31 +3,29 @@ IMPORT MODULES / SUBWORKFLOWS / FUNCTIONS ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ -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_genomeassembler_pipeline' -// Read preparation -include { PREPARE } from '../subworkflows/local/prepare/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_genomeassembler_pipeline' -// Read checks -include { ONT } from '../subworkflows/local/ont/main' -include { HIFI } from '../subworkflows/local/hifi/main' +// Read preparation +include { PREPARE } from '../subworkflows/local/prepare/main' // Assembly -include { ASSEMBLE } from '../subworkflows/local/assemble/main' +include { ASSEMBLE } from '../subworkflows/local/assemble/main' // Polishing -include { POLISH } from '../subworkflows/local/polishing/main' +include { POLISH } from '../subworkflows/local/polishing/main' // Scaffolding -include { SCAFFOLD } from '../subworkflows/local/scaffolding/main' +include { SCAFFOLD } from '../subworkflows/local/scaffolding/main' + // reporting -include { REPORT } from '../modules/local/report/main' +include { REPORT } from '../modules/local/report/main' /* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - RUN MAIN WORKFLOW + MAIN WORKFLOW ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ @@ -40,12 +38,19 @@ workflow GENOMEASSEMBLER { ch_input.set { ch_main } /* - This is the "main" channel, it contains all sample-wise information. - This channel should be the main input of all subworkflows, - and the subworkflows should make relevant changes / updates to the map. - This channel should stay a map (!!) to allow key-based modifications in subworkflows. - The keys are defined in subworkflows/local/utils_nfcore_genomeassembler/main.nf - Here is a list of keys and their types that come in : + + The "main" channel, contains all sample-wise information. + This channel should be the main input of all subworkflows + and the subworkflows should make changes to this map. The + main channel should stay a map whenever possible and this + main channel reflects all pipeline parameters. + I will make use of the meta map to pass additional infor- + mation into processes. This is neccessary to provide fine + control for parameterization of processes. This is passed + via ext.args to the process and fetched from meta. + + The keys are defined in + ./subworkflows/local/utils_nfcore_genomeassembler/main.nf meta: [id: string], ontreads: path, @@ -75,13 +80,9 @@ workflow GENOMEASSEMBLER { scaffold_ragtag: bool, use_ref: bool, flye_mode: string, - // assembly already provided? assembly: path, - // ref mapping provided? ref_map_bam: path, - // assembly mapping provided assembly_map_bam: path, - // reads for qc qc_reads: string ["ont","hifi"], qc_reads_path: path, quast: bool, @@ -89,7 +90,6 @@ workflow GENOMEASSEMBLER { busco_lineage: string, busco_db: path, lift_annotations: bool, - // short read options shortread_F: path, shortread_R: path, paired: bool, @@ -172,22 +172,22 @@ workflow GENOMEASSEMBLER { .mix(SCAFFOLD.out.ch_main) .set { ch_main_scaffolded } - ONT.out.nanoq_report + PREPARE.out.nanoq_report .concat( - ONT.out.nanoq_stats + PREPARE.out.nanoq_stats ) .collect { it -> it[1] } .set { nanoq_files } - ONT.out.genomescope_summary + PREPARE.out.genomescope_summary .concat( - ONT.out.genomescope_plot + PREPARE.out.genomescope_plot ) .unique() .collect { it -> it[1] } .set { genomescope_files } - ch_versions = ch_versions.mix(PREPARE_SHORTREADS.out.versions).mix(ONT.out.versions).mix(HIFI.out.versions).mix(ASSEMBLE.out.versions).mix(POLISH.out.versions).mix(SCAFFOLD.out.versions) + ch_versions = ch_versions.mix(PREPARE.out.versions).mix(ASSEMBLE.out.versions).mix(POLISH.out.versions).mix(SCAFFOLD.out.versions) ch_versions = ch_versions From eba981cc233f1734760860e8c7b8e7148c226124 Mon Sep 17 00:00:00 2001 From: Niklas Schandry Date: Thu, 7 Aug 2025 14:59:46 +0200 Subject: [PATCH 035/162] switch to fastplong --- conf/modules/hifi-prep.config | 10 +- modules.json | 14 +- .../{fastqc => fastplong}/environment.yml | 2 +- modules/nf-core/fastplong/main.nf | 73 + modules/nf-core/fastplong/meta.yml | 114 ++ modules/nf-core/fastplong/tests/main.nf.test | 70 + .../nf-core/fastplong/tests/main.nf.test.snap | 130 ++ modules/nf-core/fastqc/main.nf | 64 - modules/nf-core/fastqc/meta.yml | 72 - modules/nf-core/fastqc/tests/main.nf.test | 309 ---- .../nf-core/fastqc/tests/main.nf.test.snap | 392 ----- modules/nf-core/lima/environment.yml | 8 - modules/nf-core/lima/main.nf | 82 - modules/nf-core/lima/meta.yml | 174 -- modules/nf-core/lima/tests/main.nf.test | 263 --- modules/nf-core/lima/tests/main.nf.test.snap | 1486 ----------------- modules/nf-core/lima/tests/nextflow.config | 7 - .../nf-core/porechop/porechop/environment.yml | 8 - modules/nf-core/porechop/porechop/main.nf | 49 - modules/nf-core/porechop/porechop/meta.yml | 71 - .../porechop/porechop/tests/main.nf.test | 62 - .../porechop/porechop/tests/main.nf.test.snap | 88 - .../porechop/porechop/tests/nextflow.config | 9 - nextflow.config | 11 +- subworkflows/local/prepare/jellyfish/main.nf | 5 +- subworkflows/local/prepare/main.nf | 57 +- .../local/prepare/prepare_hifi/main.nf | 110 +- .../local/prepare/prepare_ont/chop/main.nf | 23 - .../local/prepare/prepare_ont/main.nf | 180 +- .../prepare/prepare_ont/run_nanoq/main.nf | 25 - .../main.nf | 12 +- workflows/genomeassembler.nf | 4 +- 32 files changed, 589 insertions(+), 3395 deletions(-) rename modules/nf-core/{fastqc => fastplong}/environment.yml (84%) create mode 100644 modules/nf-core/fastplong/main.nf create mode 100644 modules/nf-core/fastplong/meta.yml create mode 100644 modules/nf-core/fastplong/tests/main.nf.test create mode 100644 modules/nf-core/fastplong/tests/main.nf.test.snap delete mode 100644 modules/nf-core/fastqc/main.nf delete mode 100644 modules/nf-core/fastqc/meta.yml delete mode 100644 modules/nf-core/fastqc/tests/main.nf.test delete mode 100644 modules/nf-core/fastqc/tests/main.nf.test.snap delete mode 100644 modules/nf-core/lima/environment.yml delete mode 100644 modules/nf-core/lima/main.nf delete mode 100644 modules/nf-core/lima/meta.yml delete mode 100644 modules/nf-core/lima/tests/main.nf.test delete mode 100644 modules/nf-core/lima/tests/main.nf.test.snap delete mode 100644 modules/nf-core/lima/tests/nextflow.config delete mode 100644 modules/nf-core/porechop/porechop/environment.yml delete mode 100644 modules/nf-core/porechop/porechop/main.nf delete mode 100644 modules/nf-core/porechop/porechop/meta.yml delete mode 100644 modules/nf-core/porechop/porechop/tests/main.nf.test delete mode 100644 modules/nf-core/porechop/porechop/tests/main.nf.test.snap delete mode 100644 modules/nf-core/porechop/porechop/tests/nextflow.config delete mode 100644 subworkflows/local/prepare/prepare_ont/chop/main.nf delete mode 100644 subworkflows/local/prepare/prepare_ont/run_nanoq/main.nf diff --git a/conf/modules/hifi-prep.config b/conf/modules/hifi-prep.config index ec84a420..45985889 100644 --- a/conf/modules/hifi-prep.config +++ b/conf/modules/hifi-prep.config @@ -1,10 +1,16 @@ process { - withName: LIMA { + withName: FASTPLONG_HIFI { publishDir = [ - path: { "${params.outdir}/${meta.id}/reads/lima/" }, + path: { "${params.outdir}/${meta.id}/reads/fastplong/hifi/" }, mode: params.publish_dir_mode, saveAs: { filename -> filename.equals('versions.yml') ? null : filename } ] + ext.args = { + [ + meta.trim ? "-A" : '', + meta.hifi_fastplong_args + ].join(" ").trim() + } } withName: TO_FASTQ { publishDir = [ diff --git a/modules.json b/modules.json index c1eff73d..6a7b4a51 100644 --- a/modules.json +++ b/modules.json @@ -10,9 +10,9 @@ "git_sha": "05954dab2ff481bcb999f24455da29a5828af08d", "installed_by": ["modules"] }, - "fastqc": { + "fastplong": { "branch": "master", - "git_sha": "41dfa3f7c0ffabb96a6a813fe321c6d1cc5b6e46", + "git_sha": "88869fced9dc0c56043eff2dc748a5f47c57495d", "installed_by": ["modules"] }, "flye": { @@ -31,11 +31,6 @@ "installed_by": ["modules"], "patch": "modules/nf-core/liftoff/liftoff.diff" }, - "lima": { - "branch": "master", - "git_sha": "05954dab2ff481bcb999f24455da29a5828af08d", - "installed_by": ["modules"] - }, "links": { "branch": "master", "git_sha": "bd049fd0244ed914f2d10bed580b49fb44eba914", @@ -68,11 +63,6 @@ "git_sha": "05954dab2ff481bcb999f24455da29a5828af08d", "installed_by": ["modules"] }, - "porechop/porechop": { - "branch": "master", - "git_sha": "dbf496251becaa54933305bb494b880253a84ee6", - "installed_by": ["modules"] - }, "ragtag/patch": { "branch": "master", "git_sha": "62775d90df7565c82bd4ceedca70149529820cff", diff --git a/modules/nf-core/fastqc/environment.yml b/modules/nf-core/fastplong/environment.yml similarity index 84% rename from modules/nf-core/fastqc/environment.yml rename to modules/nf-core/fastplong/environment.yml index f9f54ee9..9fb22e54 100644 --- a/modules/nf-core/fastqc/environment.yml +++ b/modules/nf-core/fastplong/environment.yml @@ -4,4 +4,4 @@ channels: - conda-forge - bioconda dependencies: - - bioconda::fastqc=0.12.1 + - "bioconda::fastplong=0.3.0" diff --git a/modules/nf-core/fastplong/main.nf b/modules/nf-core/fastplong/main.nf new file mode 100644 index 00000000..1c324e14 --- /dev/null +++ b/modules/nf-core/fastplong/main.nf @@ -0,0 +1,73 @@ +process FASTPLONG { + 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/fastplong:0.3.0--h224cc79_0': + 'biocontainers/fastplong:0.3.0--h224cc79_0' }" + + input: + tuple val(meta), path(reads) + path adapter_fasta + val discard_trimmed_pass + val save_trimmed_fail + + output: + tuple val(meta), path('*.fastplong.fastq.gz') , optional:true, emit: reads + tuple val(meta), path('*.json') , emit: json + tuple val(meta), path('*.html') , emit: html + tuple val(meta), path('*.log') , emit: log + tuple val(meta), path('*.fail.fastq.gz') , optional:true, emit: reads_fail + 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 adapter_list = adapter_fasta ? "--adapter_fasta ${adapter_fasta}" : "" + def fail_fastq = save_trimmed_fail ? "--failed_out ${prefix}.fail.fastq.gz" : '' + def output_file = discard_trimmed_pass ? '' : "--out ${prefix}.fastplong.fastq.gz" + def report_title = task.ext.report_title ?: "${prefix}_fastplong_report" + """ + fastplong \\ + --in ${prefix}.fastq.gz \\ + $output_file \\ + --json ${prefix}.fastplong.json \\ + --html ${prefix}.fastplong.html \\ + $adapter_list \\ + $fail_fastq \\ + --thread $task.cpus \\ + --report_title $report_title\\ + $args \\ + 2> >(tee ${prefix}.fastplong.log >&2) + + + cat <<-END_VERSIONS > versions.yml + "${task.process}": + fastplong: \$(fastplong --version 2>&1 | sed -e "s/fastplong //g") + END_VERSIONS + """ + + stub: + def args = task.ext.args ?: '' + def prefix = task.ext.prefix ?: "${meta.id}" + def touch_reads = discard_trimmed_pass ? "" : "echo '' | gzip > ${prefix}.fastplong.fastq.gz" + def touch_fail = save_trimmed_fail ? "echo '' | gzip > ${prefix}.fail.fastq.gz" : "" + """ + echo $args + + $touch_reads + $touch_fail + touch ${prefix}.fastplong.json + touch ${prefix}.fastplong.html + touch ${prefix}.fastplong.log + + cat <<-END_VERSIONS > versions.yml + "${task.process}": + fastplong: \$(fastplong --version 2>&1 | sed -e "s/fastplong //g") + END_VERSIONS + """ +} diff --git a/modules/nf-core/fastplong/meta.yml b/modules/nf-core/fastplong/meta.yml new file mode 100644 index 00000000..d48ce4df --- /dev/null +++ b/modules/nf-core/fastplong/meta.yml @@ -0,0 +1,114 @@ +# yaml-language-server: $schema=https://raw.githubusercontent.com/nf-core/modules/master/modules/meta-schema.json +name: "fastplong" +description: Perform adapter/quality trimming and QC on long sequencing reads (ONT, + PacBio, etc.) +keywords: + - trimming + - quality control + - fastq + - long reads +tools: + - "fastplong": + description: "Ultra-fast preprocessing and quality control for long-read sequencing + data." + homepage: "https://github.com/OpenGene/fastplong/blob/v0.3.0/README.md" + documentation: "https://github.com/OpenGene/fastplong/blob/v0.3.0/README.md" + tool_dev_url: "https://github.com/OpenGene/fastplong" + doi: 10.1002/imt2.107 + licence: ["MIT"] + identifier: biotools:fastplong + +input: + - - meta: + type: map + description: | + Groovy Map containing sample information. Use 'single_end: true' for single-end reads. + e.g. [ id:'test', single_end:true ] + - reads: + type: file + description: | + Input FASTQ file. Gzip-compressed files are supported. + pattern: "*.{fastq.gz,fastq}" + ontologies: + - edam: http://edamontology.org/format_1930 # FASTQ + - adapter_fasta: + type: file + description: | + Optional FASTA file containing adapter sequences to trim. + pattern: "*.{fasta,fa,fna}" + ontologies: [] + - discard_trimmed_pass: + type: boolean + description: | + If true, no reads that pass trimming thresholds will be written. Only reports will be generated. + - save_trimmed_fail: + type: boolean + description: | + If true, reads that fail filtering will be saved to a file ending in `*.fail.fastq.gz`. + +output: + reads: + - - meta: + type: map + description: Sample information map + - "*.fastplong.fastq.gz": + type: file + description: Trimmed and filtered reads + pattern: "*fastplong.fastq.gz" + + ontologies: + - edam: http://edamontology.org/format_3989 # GZIP format + json: + - - meta: + type: map + description: Sample information map + - "*.json": + type: file + description: QC report in JSON format + pattern: "*.json" + + ontologies: + - edam: http://edamontology.org/format_3464 # JSON + html: + - - meta: + type: map + description: Sample information map + - "*.html": + type: file + description: QC report in HTML format + pattern: "*.html" + + ontologies: [] + log: + - - meta: + type: map + description: Sample information map + - "*.log": + type: file + description: Log file generated during trimming + pattern: "*.log" + + ontologies: [] + reads_fail: + - - meta: + type: map + description: Sample information map + - "*.fail.fastq.gz": + type: file + description: Reads that failed quality/trimming filters + pattern: "*fail.fastq.gz" + + 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: + - "@Lupphes" +maintainers: + - "@Lupphes" diff --git a/modules/nf-core/fastplong/tests/main.nf.test b/modules/nf-core/fastplong/tests/main.nf.test new file mode 100644 index 00000000..091901ec --- /dev/null +++ b/modules/nf-core/fastplong/tests/main.nf.test @@ -0,0 +1,70 @@ +nextflow_process { + + name "Test Process FASTPLONG" + script "../main.nf" + process "FASTPLONG" + + tag "modules" + tag "modules_nfcore" + tag "fastplong" + + + test("test_fastplong - pacbio") { + + when { + + process { + """ + input[0] = Channel.of([ + [ id:'alz.ccs', single_end:true ], + [ file(params.modules_testdata_base_path + 'genomics/homo_sapiens/pacbio/fastq/alz.ccs.fastq.gz', checkIfExists: true), ] + ]) + input[1] = [] + input[2] = false + input[3] = false + """ + } + } + + then { + assertAll( + { assert process.success }, + { assert path(process.out.html.get(0).get(1)).getText().contains("total bases:272.128000 K") }, + { assert path(process.out.log.get(0).get(1)).getText().contains("reads passed filter: 100") }, + { assert snapshot( + process.out.json, + process.out.reads, + process.out.reads_fail, + process.out.versions).match() } + ) + } + } + + test("test_fastplong - pacbio - stub") { + + options "-stub" + + when { + process { + """ + input[0] = Channel.of([ + [ id:'test', single_end:true ], + [ file(params.modules_testdata_base_path + 'genomics/homo_sapiens/pacbio/fastq/alz.ccs.fastq.gz', checkIfExists: true), ] + ]) + input[1] = [] + input[2] = false + input[3] = false + """ + } + } + + then { + assertAll( + { assert process.success }, + { assert snapshot(process.out).match() } + ) + } + + } + +} \ No newline at end of file diff --git a/modules/nf-core/fastplong/tests/main.nf.test.snap b/modules/nf-core/fastplong/tests/main.nf.test.snap new file mode 100644 index 00000000..daf08610 --- /dev/null +++ b/modules/nf-core/fastplong/tests/main.nf.test.snap @@ -0,0 +1,130 @@ +{ + "test_fastplong - pacbio": { + "content": [ + [ + [ + { + "id": "alz.ccs", + "single_end": true + }, + "alz.ccs.fastplong.json:md5,2bead4f1ec7984bf16de054f610befd7" + ] + ], + [ + [ + { + "id": "alz.ccs", + "single_end": true + }, + "alz.ccs.fastplong.fastq.gz:md5,23a6d8f301d0fd4d2da21f86ff0afac6" + ] + ], + [ + + ], + [ + "versions.yml:md5,1eec92ed637b6807443de07b646dcd07" + ] + ], + "meta": { + "nf-test": "0.9.2", + "nextflow": "24.10.5" + }, + "timestamp": "2025-08-07T11:21:12.065994812" + }, + "test_fastplong - pacbio - stub": { + "content": [ + { + "0": [ + [ + { + "id": "test", + "single_end": true + }, + "test.fastplong.fastq.gz:md5,68b329da9893e34099c7d8ad5cb9c940" + ] + ], + "1": [ + [ + { + "id": "test", + "single_end": true + }, + "test.fastplong.json:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "2": [ + [ + { + "id": "test", + "single_end": true + }, + "test.fastplong.html:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "3": [ + [ + { + "id": "test", + "single_end": true + }, + "test.fastplong.log:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "4": [ + + ], + "5": [ + "versions.yml:md5,1eec92ed637b6807443de07b646dcd07" + ], + "html": [ + [ + { + "id": "test", + "single_end": true + }, + "test.fastplong.html:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "json": [ + [ + { + "id": "test", + "single_end": true + }, + "test.fastplong.json:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "log": [ + [ + { + "id": "test", + "single_end": true + }, + "test.fastplong.log:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "reads": [ + [ + { + "id": "test", + "single_end": true + }, + "test.fastplong.fastq.gz:md5,68b329da9893e34099c7d8ad5cb9c940" + ] + ], + "reads_fail": [ + + ], + "versions": [ + "versions.yml:md5,1eec92ed637b6807443de07b646dcd07" + ] + } + ], + "meta": { + "nf-test": "0.9.2", + "nextflow": "24.10.5" + }, + "timestamp": "2025-08-07T00:26:16.322516864" + } +} \ No newline at end of file diff --git a/modules/nf-core/fastqc/main.nf b/modules/nf-core/fastqc/main.nf deleted file mode 100644 index 23e16634..00000000 --- a/modules/nf-core/fastqc/main.nf +++ /dev/null @@ -1,64 +0,0 @@ -process FASTQC { - 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/fastqc:0.12.1--hdfd78af_0' : - 'biocontainers/fastqc:0.12.1--hdfd78af_0' }" - - input: - tuple val(meta), path(reads) - - output: - tuple val(meta), path("*.html"), emit: html - tuple val(meta), path("*.zip") , emit: zip - 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}" - // Make list of old name and new name pairs to use for renaming in the bash while loop - def old_new_pairs = reads instanceof Path || reads.size() == 1 ? [[ reads, "${prefix}.${reads.extension}" ]] : reads.withIndex().collect { entry, index -> [ entry, "${prefix}_${index + 1}.${entry.extension}" ] } - def rename_to = old_new_pairs*.join(' ').join(' ') - def renamed_files = old_new_pairs.collect{ _old_name, new_name -> new_name }.join(' ') - - // The total amount of allocated RAM by FastQC is equal to the number of threads defined (--threads) time the amount of RAM defined (--memory) - // https://github.com/s-andrews/FastQC/blob/1faeea0412093224d7f6a07f777fad60a5650795/fastqc#L211-L222 - // Dividing the task.memory by task.cpu allows to stick to requested amount of RAM in the label - def memory_in_mb = task.memory ? task.memory.toUnit('MB') / task.cpus : null - // FastQC memory value allowed range (100 - 10000) - def fastqc_memory = memory_in_mb > 10000 ? 10000 : (memory_in_mb < 100 ? 100 : memory_in_mb) - - """ - printf "%s %s\\n" ${rename_to} | while read old_name new_name; do - [ -f "\${new_name}" ] || ln -s \$old_name \$new_name - done - - fastqc \\ - ${args} \\ - --threads ${task.cpus} \\ - --memory ${fastqc_memory} \\ - ${renamed_files} - - cat <<-END_VERSIONS > versions.yml - "${task.process}": - fastqc: \$( fastqc --version | sed '/FastQC v/!d; s/.*v//' ) - END_VERSIONS - """ - - stub: - def prefix = task.ext.prefix ?: "${meta.id}" - """ - touch ${prefix}.html - touch ${prefix}.zip - - cat <<-END_VERSIONS > versions.yml - "${task.process}": - fastqc: \$( fastqc --version | sed '/FastQC v/!d; s/.*v//' ) - END_VERSIONS - """ -} diff --git a/modules/nf-core/fastqc/meta.yml b/modules/nf-core/fastqc/meta.yml deleted file mode 100644 index c8d9d025..00000000 --- a/modules/nf-core/fastqc/meta.yml +++ /dev/null @@ -1,72 +0,0 @@ -name: fastqc -description: Run FastQC on sequenced reads -keywords: - - quality control - - qc - - adapters - - fastq -tools: - - fastqc: - description: | - FastQC gives general quality metrics about your reads. - It provides information about the quality score distribution - across your reads, the per base sequence content (%A/C/G/T). - - You get information about adapter contamination and other - overrepresented sequences. - homepage: https://www.bioinformatics.babraham.ac.uk/projects/fastqc/ - documentation: https://www.bioinformatics.babraham.ac.uk/projects/fastqc/Help/ - licence: ["GPL-2.0-only"] - identifier: biotools:fastqc -input: - - - meta: - type: map - description: | - Groovy Map containing sample information - e.g. [ id:'test', single_end:false ] - - reads: - type: file - description: | - List of input FastQ files of size 1 and 2 for single-end and paired-end data, - respectively. - ontologies: [] -output: - html: - - - meta: - type: map - description: | - Groovy Map containing sample information - e.g. [ id:'test', single_end:false ] - - "*.html": - type: file - description: FastQC report - pattern: "*_{fastqc.html}" - ontologies: [] - zip: - - - meta: - type: map - description: | - Groovy Map containing sample information - e.g. [ id:'test', single_end:false ] - - "*.zip": - type: file - description: FastQC report archive - pattern: "*_{fastqc.zip}" - ontologies: [] - versions: - - versions.yml: - type: file - description: File containing software versions - pattern: "versions.yml" - ontologies: - - edam: http://edamontology.org/format_3750 # YAML -authors: - - "@drpatelh" - - "@grst" - - "@ewels" - - "@FelixKrueger" -maintainers: - - "@drpatelh" - - "@grst" - - "@ewels" - - "@FelixKrueger" diff --git a/modules/nf-core/fastqc/tests/main.nf.test b/modules/nf-core/fastqc/tests/main.nf.test deleted file mode 100644 index e9d79a07..00000000 --- a/modules/nf-core/fastqc/tests/main.nf.test +++ /dev/null @@ -1,309 +0,0 @@ -nextflow_process { - - name "Test Process FASTQC" - script "../main.nf" - process "FASTQC" - - tag "modules" - tag "modules_nfcore" - tag "fastqc" - - test("sarscov2 single-end [fastq]") { - - when { - process { - """ - input[0] = Channel.of([ - [ id: 'test', single_end:true ], - [ file(params.modules_testdata_base_path + 'genomics/sarscov2/illumina/fastq/test_1.fastq.gz', checkIfExists: true) ] - ]) - """ - } - } - - then { - assertAll ( - { assert process.success }, - // NOTE The report contains the date inside it, which means that the md5sum is stable per day, but not longer than that. So you can't md5sum it. - // looks like this:
Mon 2 Oct 2023
test.gz
- // https://github.com/nf-core/modules/pull/3903#issuecomment-1743620039 - { assert process.out.html[0][1] ==~ ".*/test_fastqc.html" }, - { assert process.out.zip[0][1] ==~ ".*/test_fastqc.zip" }, - { assert path(process.out.html[0][1]).text.contains("File typeConventional base calls") }, - { assert snapshot(process.out.versions).match() } - ) - } - } - - test("sarscov2 paired-end [fastq]") { - - when { - process { - """ - input[0] = Channel.of([ - [id: 'test', single_end: false], // meta map - [ file(params.modules_testdata_base_path + 'genomics/sarscov2/illumina/fastq/test_1.fastq.gz', checkIfExists: true), - file(params.modules_testdata_base_path + 'genomics/sarscov2/illumina/fastq/test_2.fastq.gz', checkIfExists: true) ] - ]) - """ - } - } - - then { - assertAll ( - { assert process.success }, - { assert process.out.html[0][1][0] ==~ ".*/test_1_fastqc.html" }, - { assert process.out.html[0][1][1] ==~ ".*/test_2_fastqc.html" }, - { assert process.out.zip[0][1][0] ==~ ".*/test_1_fastqc.zip" }, - { assert process.out.zip[0][1][1] ==~ ".*/test_2_fastqc.zip" }, - { assert path(process.out.html[0][1][0]).text.contains("File typeConventional base calls") }, - { assert path(process.out.html[0][1][1]).text.contains("File typeConventional base calls") }, - { assert snapshot(process.out.versions).match() } - ) - } - } - - test("sarscov2 interleaved [fastq]") { - - when { - process { - """ - input[0] = Channel.of([ - [id: 'test', single_end: false], // meta map - file(params.modules_testdata_base_path + 'genomics/sarscov2/illumina/fastq/test_interleaved.fastq.gz', checkIfExists: true) - ]) - """ - } - } - - then { - assertAll ( - { assert process.success }, - { assert process.out.html[0][1] ==~ ".*/test_fastqc.html" }, - { assert process.out.zip[0][1] ==~ ".*/test_fastqc.zip" }, - { assert path(process.out.html[0][1]).text.contains("File typeConventional base calls") }, - { assert snapshot(process.out.versions).match() } - ) - } - } - - test("sarscov2 paired-end [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.sorted.bam', checkIfExists: true) - ]) - """ - } - } - - then { - assertAll ( - { assert process.success }, - { assert process.out.html[0][1] ==~ ".*/test_fastqc.html" }, - { assert process.out.zip[0][1] ==~ ".*/test_fastqc.zip" }, - { assert path(process.out.html[0][1]).text.contains("File typeConventional base calls") }, - { assert snapshot(process.out.versions).match() } - ) - } - } - - test("sarscov2 multiple [fastq]") { - - when { - process { - """ - input[0] = Channel.of([ - [id: 'test', single_end: false], // meta map - [ file(params.modules_testdata_base_path + 'genomics/sarscov2/illumina/fastq/test_1.fastq.gz', checkIfExists: true), - file(params.modules_testdata_base_path + 'genomics/sarscov2/illumina/fastq/test_2.fastq.gz', checkIfExists: true), - file(params.modules_testdata_base_path + 'genomics/sarscov2/illumina/fastq/test2_1.fastq.gz', checkIfExists: true), - file(params.modules_testdata_base_path + 'genomics/sarscov2/illumina/fastq/test2_2.fastq.gz', checkIfExists: true) ] - ]) - """ - } - } - - then { - assertAll ( - { assert process.success }, - { assert process.out.html[0][1][0] ==~ ".*/test_1_fastqc.html" }, - { assert process.out.html[0][1][1] ==~ ".*/test_2_fastqc.html" }, - { assert process.out.html[0][1][2] ==~ ".*/test_3_fastqc.html" }, - { assert process.out.html[0][1][3] ==~ ".*/test_4_fastqc.html" }, - { assert process.out.zip[0][1][0] ==~ ".*/test_1_fastqc.zip" }, - { assert process.out.zip[0][1][1] ==~ ".*/test_2_fastqc.zip" }, - { assert process.out.zip[0][1][2] ==~ ".*/test_3_fastqc.zip" }, - { assert process.out.zip[0][1][3] ==~ ".*/test_4_fastqc.zip" }, - { assert path(process.out.html[0][1][0]).text.contains("File typeConventional base calls") }, - { assert path(process.out.html[0][1][1]).text.contains("File typeConventional base calls") }, - { assert path(process.out.html[0][1][2]).text.contains("File typeConventional base calls") }, - { assert path(process.out.html[0][1][3]).text.contains("File typeConventional base calls") }, - { assert snapshot(process.out.versions).match() } - ) - } - } - - test("sarscov2 custom_prefix") { - - when { - process { - """ - input[0] = Channel.of([ - [ id:'mysample', single_end:true ], // meta map - file(params.modules_testdata_base_path + 'genomics/sarscov2/illumina/fastq/test_1.fastq.gz', checkIfExists: true) - ]) - """ - } - } - - then { - assertAll ( - { assert process.success }, - { assert process.out.html[0][1] ==~ ".*/mysample_fastqc.html" }, - { assert process.out.zip[0][1] ==~ ".*/mysample_fastqc.zip" }, - { assert path(process.out.html[0][1]).text.contains("File typeConventional base calls") }, - { assert snapshot(process.out.versions).match() } - ) - } - } - - test("sarscov2 single-end [fastq] - stub") { - - options "-stub" - when { - process { - """ - input[0] = Channel.of([ - [ id: 'test', single_end:true ], - [ file(params.modules_testdata_base_path + 'genomics/sarscov2/illumina/fastq/test_1.fastq.gz', checkIfExists: true) ] - ]) - """ - } - } - - then { - assertAll ( - { assert process.success }, - { assert snapshot(process.out).match() } - ) - } - } - - test("sarscov2 paired-end [fastq] - 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/fastq/test_1.fastq.gz', checkIfExists: true), - file(params.modules_testdata_base_path + 'genomics/sarscov2/illumina/fastq/test_2.fastq.gz', checkIfExists: true) ] - ]) - """ - } - } - - then { - assertAll ( - { assert process.success }, - { assert snapshot(process.out).match() } - ) - } - } - - test("sarscov2 interleaved [fastq] - 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/fastq/test_interleaved.fastq.gz', checkIfExists: true) - ]) - """ - } - } - - then { - assertAll ( - { assert process.success }, - { assert snapshot(process.out).match() } - ) - } - } - - test("sarscov2 paired-end [bam] - 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("sarscov2 multiple [fastq] - 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/fastq/test_1.fastq.gz', checkIfExists: true), - file(params.modules_testdata_base_path + 'genomics/sarscov2/illumina/fastq/test_2.fastq.gz', checkIfExists: true), - file(params.modules_testdata_base_path + 'genomics/sarscov2/illumina/fastq/test2_1.fastq.gz', checkIfExists: true), - file(params.modules_testdata_base_path + 'genomics/sarscov2/illumina/fastq/test2_2.fastq.gz', checkIfExists: true) ] - ]) - """ - } - } - - then { - assertAll ( - { assert process.success }, - { assert snapshot(process.out).match() } - ) - } - } - - test("sarscov2 custom_prefix - stub") { - - options "-stub" - when { - process { - """ - input[0] = Channel.of([ - [ id:'mysample', single_end:true ], // meta map - file(params.modules_testdata_base_path + 'genomics/sarscov2/illumina/fastq/test_1.fastq.gz', checkIfExists: true) - ]) - """ - } - } - - then { - assertAll ( - { assert process.success }, - { assert snapshot(process.out).match() } - ) - } - } -} diff --git a/modules/nf-core/fastqc/tests/main.nf.test.snap b/modules/nf-core/fastqc/tests/main.nf.test.snap deleted file mode 100644 index d5db3092..00000000 --- a/modules/nf-core/fastqc/tests/main.nf.test.snap +++ /dev/null @@ -1,392 +0,0 @@ -{ - "sarscov2 custom_prefix": { - "content": [ - [ - "versions.yml:md5,e1cc25ca8af856014824abd842e93978" - ] - ], - "meta": { - "nf-test": "0.9.0", - "nextflow": "24.04.3" - }, - "timestamp": "2024-07-22T11:02:16.374038" - }, - "sarscov2 single-end [fastq] - stub": { - "content": [ - { - "0": [ - [ - { - "id": "test", - "single_end": true - }, - "test.html:md5,d41d8cd98f00b204e9800998ecf8427e" - ] - ], - "1": [ - [ - { - "id": "test", - "single_end": true - }, - "test.zip:md5,d41d8cd98f00b204e9800998ecf8427e" - ] - ], - "2": [ - "versions.yml:md5,e1cc25ca8af856014824abd842e93978" - ], - "html": [ - [ - { - "id": "test", - "single_end": true - }, - "test.html:md5,d41d8cd98f00b204e9800998ecf8427e" - ] - ], - "versions": [ - "versions.yml:md5,e1cc25ca8af856014824abd842e93978" - ], - "zip": [ - [ - { - "id": "test", - "single_end": true - }, - "test.zip:md5,d41d8cd98f00b204e9800998ecf8427e" - ] - ] - } - ], - "meta": { - "nf-test": "0.9.0", - "nextflow": "24.04.3" - }, - "timestamp": "2024-07-22T11:02:24.993809" - }, - "sarscov2 custom_prefix - stub": { - "content": [ - { - "0": [ - [ - { - "id": "mysample", - "single_end": true - }, - "mysample.html:md5,d41d8cd98f00b204e9800998ecf8427e" - ] - ], - "1": [ - [ - { - "id": "mysample", - "single_end": true - }, - "mysample.zip:md5,d41d8cd98f00b204e9800998ecf8427e" - ] - ], - "2": [ - "versions.yml:md5,e1cc25ca8af856014824abd842e93978" - ], - "html": [ - [ - { - "id": "mysample", - "single_end": true - }, - "mysample.html:md5,d41d8cd98f00b204e9800998ecf8427e" - ] - ], - "versions": [ - "versions.yml:md5,e1cc25ca8af856014824abd842e93978" - ], - "zip": [ - [ - { - "id": "mysample", - "single_end": true - }, - "mysample.zip:md5,d41d8cd98f00b204e9800998ecf8427e" - ] - ] - } - ], - "meta": { - "nf-test": "0.9.0", - "nextflow": "24.04.3" - }, - "timestamp": "2024-07-22T11:03:10.93942" - }, - "sarscov2 interleaved [fastq]": { - "content": [ - [ - "versions.yml:md5,e1cc25ca8af856014824abd842e93978" - ] - ], - "meta": { - "nf-test": "0.9.0", - "nextflow": "24.04.3" - }, - "timestamp": "2024-07-22T11:01:42.355718" - }, - "sarscov2 paired-end [bam]": { - "content": [ - [ - "versions.yml:md5,e1cc25ca8af856014824abd842e93978" - ] - ], - "meta": { - "nf-test": "0.9.0", - "nextflow": "24.04.3" - }, - "timestamp": "2024-07-22T11:01:53.276274" - }, - "sarscov2 multiple [fastq]": { - "content": [ - [ - "versions.yml:md5,e1cc25ca8af856014824abd842e93978" - ] - ], - "meta": { - "nf-test": "0.9.0", - "nextflow": "24.04.3" - }, - "timestamp": "2024-07-22T11:02:05.527626" - }, - "sarscov2 paired-end [fastq]": { - "content": [ - [ - "versions.yml:md5,e1cc25ca8af856014824abd842e93978" - ] - ], - "meta": { - "nf-test": "0.9.0", - "nextflow": "24.04.3" - }, - "timestamp": "2024-07-22T11:01:31.188871" - }, - "sarscov2 paired-end [fastq] - stub": { - "content": [ - { - "0": [ - [ - { - "id": "test", - "single_end": false - }, - "test.html:md5,d41d8cd98f00b204e9800998ecf8427e" - ] - ], - "1": [ - [ - { - "id": "test", - "single_end": false - }, - "test.zip:md5,d41d8cd98f00b204e9800998ecf8427e" - ] - ], - "2": [ - "versions.yml:md5,e1cc25ca8af856014824abd842e93978" - ], - "html": [ - [ - { - "id": "test", - "single_end": false - }, - "test.html:md5,d41d8cd98f00b204e9800998ecf8427e" - ] - ], - "versions": [ - "versions.yml:md5,e1cc25ca8af856014824abd842e93978" - ], - "zip": [ - [ - { - "id": "test", - "single_end": false - }, - "test.zip:md5,d41d8cd98f00b204e9800998ecf8427e" - ] - ] - } - ], - "meta": { - "nf-test": "0.9.0", - "nextflow": "24.04.3" - }, - "timestamp": "2024-07-22T11:02:34.273566" - }, - "sarscov2 multiple [fastq] - stub": { - "content": [ - { - "0": [ - [ - { - "id": "test", - "single_end": false - }, - "test.html:md5,d41d8cd98f00b204e9800998ecf8427e" - ] - ], - "1": [ - [ - { - "id": "test", - "single_end": false - }, - "test.zip:md5,d41d8cd98f00b204e9800998ecf8427e" - ] - ], - "2": [ - "versions.yml:md5,e1cc25ca8af856014824abd842e93978" - ], - "html": [ - [ - { - "id": "test", - "single_end": false - }, - "test.html:md5,d41d8cd98f00b204e9800998ecf8427e" - ] - ], - "versions": [ - "versions.yml:md5,e1cc25ca8af856014824abd842e93978" - ], - "zip": [ - [ - { - "id": "test", - "single_end": false - }, - "test.zip:md5,d41d8cd98f00b204e9800998ecf8427e" - ] - ] - } - ], - "meta": { - "nf-test": "0.9.0", - "nextflow": "24.04.3" - }, - "timestamp": "2024-07-22T11:03:02.304411" - }, - "sarscov2 single-end [fastq]": { - "content": [ - [ - "versions.yml:md5,e1cc25ca8af856014824abd842e93978" - ] - ], - "meta": { - "nf-test": "0.9.0", - "nextflow": "24.04.3" - }, - "timestamp": "2024-07-22T11:01:19.095607" - }, - "sarscov2 interleaved [fastq] - stub": { - "content": [ - { - "0": [ - [ - { - "id": "test", - "single_end": false - }, - "test.html:md5,d41d8cd98f00b204e9800998ecf8427e" - ] - ], - "1": [ - [ - { - "id": "test", - "single_end": false - }, - "test.zip:md5,d41d8cd98f00b204e9800998ecf8427e" - ] - ], - "2": [ - "versions.yml:md5,e1cc25ca8af856014824abd842e93978" - ], - "html": [ - [ - { - "id": "test", - "single_end": false - }, - "test.html:md5,d41d8cd98f00b204e9800998ecf8427e" - ] - ], - "versions": [ - "versions.yml:md5,e1cc25ca8af856014824abd842e93978" - ], - "zip": [ - [ - { - "id": "test", - "single_end": false - }, - "test.zip:md5,d41d8cd98f00b204e9800998ecf8427e" - ] - ] - } - ], - "meta": { - "nf-test": "0.9.0", - "nextflow": "24.04.3" - }, - "timestamp": "2024-07-22T11:02:44.640184" - }, - "sarscov2 paired-end [bam] - stub": { - "content": [ - { - "0": [ - [ - { - "id": "test", - "single_end": false - }, - "test.html:md5,d41d8cd98f00b204e9800998ecf8427e" - ] - ], - "1": [ - [ - { - "id": "test", - "single_end": false - }, - "test.zip:md5,d41d8cd98f00b204e9800998ecf8427e" - ] - ], - "2": [ - "versions.yml:md5,e1cc25ca8af856014824abd842e93978" - ], - "html": [ - [ - { - "id": "test", - "single_end": false - }, - "test.html:md5,d41d8cd98f00b204e9800998ecf8427e" - ] - ], - "versions": [ - "versions.yml:md5,e1cc25ca8af856014824abd842e93978" - ], - "zip": [ - [ - { - "id": "test", - "single_end": false - }, - "test.zip:md5,d41d8cd98f00b204e9800998ecf8427e" - ] - ] - } - ], - "meta": { - "nf-test": "0.9.0", - "nextflow": "24.04.3" - }, - "timestamp": "2024-07-22T11:02:53.550742" - } -} \ No newline at end of file diff --git a/modules/nf-core/lima/environment.yml b/modules/nf-core/lima/environment.yml deleted file mode 100644 index 2e56e30d..00000000 --- a/modules/nf-core/lima/environment.yml +++ /dev/null @@ -1,8 +0,0 @@ ---- -# yaml-language-server: $schema=https://raw.githubusercontent.com/nf-core/modules/master/modules/environment-schema.json -channels: - - conda-forge - - bioconda - -dependencies: - - bioconda::lima=2.12.0 diff --git a/modules/nf-core/lima/main.nf b/modules/nf-core/lima/main.nf deleted file mode 100644 index e5b334b1..00000000 --- a/modules/nf-core/lima/main.nf +++ /dev/null @@ -1,82 +0,0 @@ -process LIMA { - 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/lima:2.12.0--h9ee0642_1' : - 'biocontainers/lima:2.12.0--h9ee0642_1' }" - - input: - tuple val(meta), path(ccs) - path primers - - output: - tuple val(meta), path("*.counts") , emit: counts - tuple val(meta), path("*.report") , emit: report - tuple val(meta), path("*.summary"), emit: summary - path "versions.yml" , emit: versions - - tuple val(meta), path("*.bam") , optional: true, emit: bam - tuple val(meta), path("*.bam.pbi") , optional: true, emit: pbi - tuple val(meta), path("*.{fa, fasta}") , optional: true, emit: fasta - tuple val(meta), path("*.{fa.gz, fasta.gz}"), optional: true, emit: fastagz - tuple val(meta), path("*.fastq") , optional: true, emit: fastq - tuple val(meta), path("*.fastq.gz") , optional: true, emit: fastqgz - tuple val(meta), path("*.xml") , optional: true, emit: xml - tuple val(meta), path("*.json") , optional: true, emit: json - tuple val(meta), path("*.clips") , optional: true, emit: clips - tuple val(meta), path("*.guess") , optional: true, emit: guess - - when: - task.ext.when == null || task.ext.when - - script: - def args = task.ext.args ?: '' - def prefix = task.ext.prefix ?: "${meta.id}" - if( "$ccs" == "${prefix}.bam" ) error "Input and output names are the same, set prefix in module configuration" - if( "$ccs" == "${prefix}.fasta" ) error "Input and output names are the same, set prefix in module configuration" - if( "$ccs" == "${prefix}.fasta.gz" ) error "Input and output names are the same, set prefix in module configuration" - if( "$ccs" == "${prefix}.fastq" ) error "Input and output names are the same, set prefix in module configuration" - if( "$ccs" == "${prefix}.fastq.gz" ) error "Input and output names are the same, set prefix in module configuration" - - """ - OUT_EXT="" - - if [[ $ccs =~ bam\$ ]]; then - OUT_EXT="bam" - elif [[ $ccs =~ fasta\$ ]]; then - OUT_EXT="fasta" - elif [[ $ccs =~ fasta.gz\$ ]]; then - OUT_EXT="fasta.gz" - elif [[ $ccs =~ fastq\$ ]]; then - OUT_EXT="fastq" - elif [[ $ccs =~ fastq.gz\$ ]]; then - OUT_EXT="fastq.gz" - fi - - lima \\ - $ccs \\ - $primers \\ - $prefix.\$OUT_EXT \\ - -j $task.cpus \\ - $args - - cat <<-END_VERSIONS > versions.yml - "${task.process}": - lima: \$( lima --version | head -n1 | sed 's/lima //g' | sed 's/ (.\\+//g' ) - END_VERSIONS - """ - - stub: - """ - touch dummy.counts - touch dummy.report - touch dummy.summary - - cat <<-END_VERSIONS > versions.yml - "${task.process}": - lima: \$( lima --version | head -n1 | sed 's/lima //g' | sed 's/ (.\\+//g' ) - END_VERSIONS - """ -} diff --git a/modules/nf-core/lima/meta.yml b/modules/nf-core/lima/meta.yml deleted file mode 100644 index f22973f0..00000000 --- a/modules/nf-core/lima/meta.yml +++ /dev/null @@ -1,174 +0,0 @@ -name: lima -description: lima - The PacBio Barcode Demultiplexer and Primer Remover -keywords: - - isoseq - - ccs - - primer - - pacbio - - barcode -tools: - - lima: - description: lima - The PacBio Barcode Demultiplexer and Primer Remover - homepage: https://github.com/PacificBiosciences/pbbioconda - documentation: https://lima.how/ - tool_dev_url: https://github.com/pacificbiosciences/barcoding/ - licence: ["BSD-3-Clause-Clear"] - identifier: "" -input: - - - meta: - type: map - description: | - Groovy Map containing sample information - e.g. [ id:'test' ] - - ccs: - type: file - description: A BAM or fasta or fasta.gz or fastq or fastq.gz file of subreads - or ccs - pattern: "*.{bam,fasta,fasta.gz,fastq,fastq.gz}" - - - primers: - type: file - description: Fasta file, sequences of primers - pattern: "*.fasta" -output: - - counts: - - meta: - type: map - description: | - Groovy Map containing sample information - e.g. [ id:'test' ] - - "*.counts": - type: file - description: A tabulated file of describing pairs of primers - pattern: "*.counts" - - report: - - meta: - type: map - description: | - Groovy Map containing sample information - e.g. [ id:'test' ] - - "*.report": - type: file - description: A tab-separated file about each ZMW, unfiltered - pattern: "*.report" - - summary: - - meta: - type: map - description: | - Groovy Map containing sample information - e.g. [ id:'test' ] - - "*.summary": - type: file - description: This file shows how many ZMWs have been filtered, how ZMWs many - are same/different, and how many reads have been filtered. - pattern: "*.summary" - - versions: - - versions.yml: - type: file - description: File containing software versions - pattern: "versions.yml" - - bam: - - meta: - type: map - description: | - Groovy Map containing sample information - e.g. [ id:'test' ] - - "*.bam": - type: file - description: A bam file of ccs purged of primers - pattern: "*.bam" - - pbi: - - meta: - type: map - description: | - Groovy Map containing sample information - e.g. [ id:'test' ] - - "*.bam.pbi": - type: file - description: Pacbio index file of ccs purged of primers - pattern: "*.bam" - - fasta: - - meta: - type: map - description: | - Groovy Map containing sample information - e.g. [ id:'test' ] - - "*.{fa, fasta}": - type: file - description: A fasta file of ccs purged of primers. - pattern: "*.fa" - - fastagz: - - meta: - type: map - description: | - Groovy Map containing sample information - e.g. [ id:'test' ] - - "*.{fa.gz, fasta.gz}": - type: file - description: A fasta.gz file of ccs purged of primers. - pattern: "*.fasta.gz" - - fastq: - - meta: - type: map - description: | - Groovy Map containing sample information - e.g. [ id:'test' ] - - "*.fastq": - type: file - description: A fastq file of ccs purged of primers. - pattern: "*.fastq" - - fastqgz: - - meta: - type: map - description: | - Groovy Map containing sample information - e.g. [ id:'test' ] - - "*.fastq.gz": - type: file - description: A fastq.gz file of ccs purged of primers. - pattern: "*.fastq.gz" - - xml: - - meta: - type: map - description: | - Groovy Map containing sample information - e.g. [ id:'test' ] - - "*.xml": - type: file - description: An XML file representing a set of a particular sequence data type - such as subreads, references or aligned subreads. - pattern: "*.xml" - - json: - - meta: - type: map - description: | - Groovy Map containing sample information - e.g. [ id:'test' ] - - "*.json": - type: file - description: A metadata json file - pattern: "*.json" - - clips: - - meta: - type: map - description: | - Groovy Map containing sample information - e.g. [ id:'test' ] - - "*.clips": - type: file - description: A fasta file of clipped primers - pattern: "*.clips" - - guess: - - meta: - type: map - description: | - Groovy Map containing sample information - e.g. [ id:'test' ] - - "*.guess": - type: file - description: A second tabulated file of describing pairs of primers (no doc - available) - pattern: "*.guess" -authors: - - "@sguizard" -maintainers: - - "@sguizard" diff --git a/modules/nf-core/lima/tests/main.nf.test b/modules/nf-core/lima/tests/main.nf.test deleted file mode 100644 index f5cb6b86..00000000 --- a/modules/nf-core/lima/tests/main.nf.test +++ /dev/null @@ -1,263 +0,0 @@ -nextflow_process { - - name "Test Process LIMA" - script "../main.nf" - config "./nextflow.config" - process "LIMA" - - tag "modules" - tag "modules_nfcore" - tag "lima" - - test("LIMA - Primer Removal - Input => bam") { - - when { - process { - """ - input[0] = [ - [ id:'test' ], // meta map - file(params.modules_testdata_base_path + 'genomics/homo_sapiens/pacbio/bam/alz.ccs.bam', checkIfExists: true), - ] - input[1] = [ file(params.modules_testdata_base_path + 'genomics/homo_sapiens/pacbio/fasta/primers.fasta', checkIfExists: true) ] - """ - } - } - - then { - assertAll( - { assert process.success }, - { assert snapshot(process.out.counts).match("counts") }, - { assert snapshot(process.out.report).match("report") }, - { assert snapshot(process.out.summary).match("summary") }, - { assert snapshot(process.out.versions).match("versions") }, - { assert snapshot(process.out.bam).match("bam") }, - { assert snapshot(process.out.pbi).match("pbi") }, - { assert snapshot(process.out.fasta).match("fasta") }, - { assert snapshot(process.out.fastagz).match("fastagz") }, - { assert snapshot(process.out.fastq).match("fastq") }, - { assert snapshot(process.out.fastqgz).match("fastqgz") }, - { assert snapshot(process.out.clips).match("clips") }, - { assert snapshot(process.out.guess).match("guess") } - ) - } - - } - - test("LIMA - Primer Removal - Input => fa") { - - when { - process { - """ - input[0] = [ - [ id:'test' ], // meta map - file(params.modules_testdata_base_path + 'genomics/homo_sapiens/pacbio/fasta/alz.ccs.fasta', checkIfExists: true), - ] - input[1] = [ file(params.modules_testdata_base_path + 'genomics/homo_sapiens/pacbio/fasta/primers.fasta', checkIfExists: true) ] - """ - } - } - - then { - assertAll( - { assert process.success }, - { assert snapshot(process.out).match() } - ) - } - - } - - test("LIMA - Primer Removal - Input => fa.gz") { - - when { - process { - """ - input[0] = [ - [ id:'test' ], // meta map - file(params.modules_testdata_base_path + 'genomics/homo_sapiens/pacbio/fasta/alz.ccs.fasta.gz', checkIfExists: true), - ] - input[1] = [ file(params.modules_testdata_base_path + 'genomics/homo_sapiens/pacbio/fasta/primers.fasta', checkIfExists: true) ] - """ - } - } - - then { - assertAll( - { assert process.success }, - { assert snapshot(process.out).match() } - ) - } - - } - - test("LIMA - Primer Removal - Input => fq") { - - when { - process { - """ - input[0] = [ - [ id:'test' ], // meta map - file(params.modules_testdata_base_path + 'genomics/homo_sapiens/pacbio/fastq/alz.ccs.fastq', checkIfExists: true), - ] - input[1] = [ file(params.modules_testdata_base_path + 'genomics/homo_sapiens/pacbio/fasta/primers.fasta', checkIfExists: true) ] - """ - } - } - - then { - assertAll( - { assert process.success }, - { assert snapshot(process.out).match() } - ) - } - - } - - test("LIMA - Primer Removal - Input => fq.gz") { - - when { - process { - """ - input[0] = [ - [ id:'test' ], // meta map - file(params.modules_testdata_base_path + 'genomics/homo_sapiens/pacbio/fastq/alz.ccs.fastq.gz', checkIfExists: true), - ] - input[1] = [ file(params.modules_testdata_base_path + 'genomics/homo_sapiens/pacbio/fasta/primers.fasta', checkIfExists: true) ] - """ - } - } - - then { - assertAll( - { assert process.success }, - { assert snapshot(process.out).match() } - ) - } - - } - - test("LIMA - Primer Removal - Input => bam - stub") { - - options "-stub" - - when { - process { - """ - input[0] = [ - [ id:'test' ], // meta map - file(params.modules_testdata_base_path + 'genomics/homo_sapiens/pacbio/bam/alz.ccs.bam', checkIfExists: true), - ] - input[1] = [ file(params.modules_testdata_base_path + 'genomics/homo_sapiens/pacbio/fasta/primers.fasta', checkIfExists: true) ] - """ - } - } - - then { - assertAll( - { assert process.success }, - { assert snapshot(process.out).match() } - ) - } - - } - - test("LIMA - Primer Removal - Input => fa - stub") { - - options "-stub" - - when { - process { - """ - input[0] = [ - [ id:'test' ], // meta map - file(params.modules_testdata_base_path + 'genomics/homo_sapiens/pacbio/fasta/alz.ccs.fasta', checkIfExists: true), - ] - input[1] = [ file(params.modules_testdata_base_path + 'genomics/homo_sapiens/pacbio/fasta/primers.fasta', checkIfExists: true) ] - """ - } - } - - then { - assertAll( - { assert process.success }, - { assert snapshot(process.out).match() } - ) - } - - } - - test("LIMA - Primer Removal - Input => fa.gz - stub") { - - options "-stub" - - when { - process { - """ - input[0] = [ - [ id:'test' ], // meta map - file(params.modules_testdata_base_path + 'genomics/homo_sapiens/pacbio/fasta/alz.ccs.fasta.gz', checkIfExists: true), - ] - input[1] = [ file(params.modules_testdata_base_path + 'genomics/homo_sapiens/pacbio/fasta/primers.fasta', checkIfExists: true) ] - """ - } - } - - then { - assertAll( - { assert process.success }, - { assert snapshot(process.out).match() } - ) - } - - } - - test("LIMA - Primer Removal - Input => fq - stub") { - - options "-stub" - - when { - process { - """ - input[0] = [ - [ id:'test' ], // meta map - file(params.modules_testdata_base_path + 'genomics/homo_sapiens/pacbio/fastq/alz.ccs.fastq', checkIfExists: true), - ] - input[1] = [ file(params.modules_testdata_base_path + 'genomics/homo_sapiens/pacbio/fasta/primers.fasta', checkIfExists: true) ] - """ - } - } - - then { - assertAll( - { assert process.success }, - { assert snapshot(process.out).match() } - ) - } - - } - - test("LIMA - Primer Removal - Input => fq.gz - stub") { - - options "-stub" - - when { - process { - """ - input[0] = [ - [ id:'test' ], // meta map - file(params.modules_testdata_base_path + 'genomics/homo_sapiens/pacbio/fastq/alz.ccs.fastq.gz', checkIfExists: true), - ] - input[1] = [ file(params.modules_testdata_base_path + 'genomics/homo_sapiens/pacbio/fasta/primers.fasta', checkIfExists: true) ] - """ - } - } - - then { - assertAll( - { assert process.success }, - { assert snapshot(process.out).match() } - ) - } - - } - -} \ No newline at end of file diff --git a/modules/nf-core/lima/tests/main.nf.test.snap b/modules/nf-core/lima/tests/main.nf.test.snap deleted file mode 100644 index 334b6936..00000000 --- a/modules/nf-core/lima/tests/main.nf.test.snap +++ /dev/null @@ -1,1486 +0,0 @@ -{ - "summary": { - "content": [ - [ - [ - { - "id": "test" - }, - "test.fl.lima.summary:md5,bcbcaaaca418bdeb91141c81715ca420" - ] - ] - ], - "meta": { - "nf-test": "0.8.4", - "nextflow": "23.10.1" - }, - "timestamp": "2024-01-17T11:35:00.704932" - }, - "fastqgz": { - "content": [ - [ - - ] - ], - "meta": { - "nf-test": "0.8.4", - "nextflow": "23.10.1" - }, - "timestamp": "2024-01-17T11:35:01.98256" - }, - "counts": { - "content": [ - [ - [ - { - "id": "test" - }, - "test.fl.lima.counts:md5,842c6a23ca2de504ced4538ad5111da1" - ] - ] - ], - "meta": { - "nf-test": "0.8.4", - "nextflow": "23.10.1" - }, - "timestamp": "2024-01-17T11:35:00.491188" - }, - "clips": { - "content": [ - [ - [ - { - "id": "test" - }, - "test.fl.lima.clips:md5,fa03bc75bd78b2648a139fd67c69208f" - ] - ] - ], - "meta": { - "nf-test": "0.8.4", - "nextflow": "23.10.1" - }, - "timestamp": "2024-01-17T11:35:02.19417" - }, - "LIMA - Primer Removal - Input => fq.gz - stub": { - "content": [ - { - "0": [ - [ - { - "id": "test" - }, - "dummy.counts:md5,d41d8cd98f00b204e9800998ecf8427e" - ] - ], - "1": [ - [ - { - "id": "test" - }, - "dummy.report:md5,d41d8cd98f00b204e9800998ecf8427e" - ] - ], - "10": [ - - ], - "11": [ - - ], - "12": [ - - ], - "13": [ - - ], - "2": [ - [ - { - "id": "test" - }, - "dummy.summary:md5,d41d8cd98f00b204e9800998ecf8427e" - ] - ], - "3": [ - "versions.yml:md5,3253df3f697bdcd8cceee60e0b4ebdaf" - ], - "4": [ - - ], - "5": [ - - ], - "6": [ - - ], - "7": [ - - ], - "8": [ - - ], - "9": [ - - ], - "bam": [ - - ], - "clips": [ - - ], - "counts": [ - [ - { - "id": "test" - }, - "dummy.counts:md5,d41d8cd98f00b204e9800998ecf8427e" - ] - ], - "fasta": [ - - ], - "fastagz": [ - - ], - "fastq": [ - - ], - "fastqgz": [ - - ], - "guess": [ - - ], - "json": [ - - ], - "pbi": [ - - ], - "report": [ - [ - { - "id": "test" - }, - "dummy.report:md5,d41d8cd98f00b204e9800998ecf8427e" - ] - ], - "summary": [ - [ - { - "id": "test" - }, - "dummy.summary:md5,d41d8cd98f00b204e9800998ecf8427e" - ] - ], - "versions": [ - "versions.yml:md5,3253df3f697bdcd8cceee60e0b4ebdaf" - ], - "xml": [ - - ] - } - ], - "meta": { - "nf-test": "0.9.0", - "nextflow": "24.10.3" - }, - "timestamp": "2025-01-13T13:17:04.481318" - }, - "LIMA - Primer Removal - Input => fq.gz": { - "content": [ - { - "0": [ - [ - { - "id": "test" - }, - "test.fl.lima.counts:md5,767b687e6eda7b24cd0e577f527eb2f0" - ] - ], - "1": [ - [ - { - "id": "test" - }, - "test.fl.lima.report:md5,ad2a9b1eeb4cda4a1f69ef4b7520b5fd" - ] - ], - "10": [ - - ], - "11": [ - - ], - "12": [ - [ - { - "id": "test" - }, - "test.fl.lima.clips:md5,5c16ef8122f6f1798acc30eb8a30828c" - ] - ], - "13": [ - [ - { - "id": "test" - }, - "test.fl.lima.guess:md5,31b988aab6bda84867e704b9edd8a763" - ] - ], - "2": [ - [ - { - "id": "test" - }, - "test.fl.lima.summary:md5,e91d3c386aaf4effa63f33ee2eb7da2a" - ] - ], - "3": [ - "versions.yml:md5,3253df3f697bdcd8cceee60e0b4ebdaf" - ], - "4": [ - [ - { - "id": "test" - }, - "test.fl.NEB_5p--NEB_Clontech_3p.bam:md5,c5d3d376ca7ffc32ef5cbabcc9850804" - ] - ], - "5": [ - [ - { - "id": "test" - }, - "test.fl.NEB_5p--NEB_Clontech_3p.bam.pbi:md5,d1d6a2f961b9fb3d29837555706c59eb" - ] - ], - "6": [ - - ], - "7": [ - - ], - "8": [ - - ], - "9": [ - [ - { - "id": "test" - }, - "test.fl.NEB_5p--NEB_Clontech_3p.fastq.gz:md5,ef395f689c5566f501e300bb83d7a5f2" - ] - ], - "bam": [ - [ - { - "id": "test" - }, - "test.fl.NEB_5p--NEB_Clontech_3p.bam:md5,c5d3d376ca7ffc32ef5cbabcc9850804" - ] - ], - "clips": [ - [ - { - "id": "test" - }, - "test.fl.lima.clips:md5,5c16ef8122f6f1798acc30eb8a30828c" - ] - ], - "counts": [ - [ - { - "id": "test" - }, - "test.fl.lima.counts:md5,767b687e6eda7b24cd0e577f527eb2f0" - ] - ], - "fasta": [ - - ], - "fastagz": [ - - ], - "fastq": [ - - ], - "fastqgz": [ - [ - { - "id": "test" - }, - "test.fl.NEB_5p--NEB_Clontech_3p.fastq.gz:md5,ef395f689c5566f501e300bb83d7a5f2" - ] - ], - "guess": [ - [ - { - "id": "test" - }, - "test.fl.lima.guess:md5,31b988aab6bda84867e704b9edd8a763" - ] - ], - "json": [ - - ], - "pbi": [ - [ - { - "id": "test" - }, - "test.fl.NEB_5p--NEB_Clontech_3p.bam.pbi:md5,d1d6a2f961b9fb3d29837555706c59eb" - ] - ], - "report": [ - [ - { - "id": "test" - }, - "test.fl.lima.report:md5,ad2a9b1eeb4cda4a1f69ef4b7520b5fd" - ] - ], - "summary": [ - [ - { - "id": "test" - }, - "test.fl.lima.summary:md5,e91d3c386aaf4effa63f33ee2eb7da2a" - ] - ], - "versions": [ - "versions.yml:md5,3253df3f697bdcd8cceee60e0b4ebdaf" - ], - "xml": [ - - ] - } - ], - "meta": { - "nf-test": "0.9.0", - "nextflow": "24.10.3" - }, - "timestamp": "2025-01-13T13:16:41.110353" - }, - "LIMA - Primer Removal - Input => fq": { - "content": [ - { - "0": [ - [ - { - "id": "test" - }, - "test.fl.lima.counts:md5,767b687e6eda7b24cd0e577f527eb2f0" - ] - ], - "1": [ - [ - { - "id": "test" - }, - "test.fl.lima.report:md5,ad2a9b1eeb4cda4a1f69ef4b7520b5fd" - ] - ], - "10": [ - - ], - "11": [ - - ], - "12": [ - [ - { - "id": "test" - }, - "test.fl.lima.clips:md5,5c16ef8122f6f1798acc30eb8a30828c" - ] - ], - "13": [ - [ - { - "id": "test" - }, - "test.fl.lima.guess:md5,31b988aab6bda84867e704b9edd8a763" - ] - ], - "2": [ - [ - { - "id": "test" - }, - "test.fl.lima.summary:md5,e91d3c386aaf4effa63f33ee2eb7da2a" - ] - ], - "3": [ - "versions.yml:md5,3253df3f697bdcd8cceee60e0b4ebdaf" - ], - "4": [ - [ - { - "id": "test" - }, - "test.fl.NEB_5p--NEB_Clontech_3p.bam:md5,c5d3d376ca7ffc32ef5cbabcc9850804" - ] - ], - "5": [ - [ - { - "id": "test" - }, - "test.fl.NEB_5p--NEB_Clontech_3p.bam.pbi:md5,d1d6a2f961b9fb3d29837555706c59eb" - ] - ], - "6": [ - - ], - "7": [ - - ], - "8": [ - [ - { - "id": "test" - }, - "test.fl.NEB_5p--NEB_Clontech_3p.fastq:md5,ef395f689c5566f501e300bb83d7a5f2" - ] - ], - "9": [ - - ], - "bam": [ - [ - { - "id": "test" - }, - "test.fl.NEB_5p--NEB_Clontech_3p.bam:md5,c5d3d376ca7ffc32ef5cbabcc9850804" - ] - ], - "clips": [ - [ - { - "id": "test" - }, - "test.fl.lima.clips:md5,5c16ef8122f6f1798acc30eb8a30828c" - ] - ], - "counts": [ - [ - { - "id": "test" - }, - "test.fl.lima.counts:md5,767b687e6eda7b24cd0e577f527eb2f0" - ] - ], - "fasta": [ - - ], - "fastagz": [ - - ], - "fastq": [ - [ - { - "id": "test" - }, - "test.fl.NEB_5p--NEB_Clontech_3p.fastq:md5,ef395f689c5566f501e300bb83d7a5f2" - ] - ], - "fastqgz": [ - - ], - "guess": [ - [ - { - "id": "test" - }, - "test.fl.lima.guess:md5,31b988aab6bda84867e704b9edd8a763" - ] - ], - "json": [ - - ], - "pbi": [ - [ - { - "id": "test" - }, - "test.fl.NEB_5p--NEB_Clontech_3p.bam.pbi:md5,d1d6a2f961b9fb3d29837555706c59eb" - ] - ], - "report": [ - [ - { - "id": "test" - }, - "test.fl.lima.report:md5,ad2a9b1eeb4cda4a1f69ef4b7520b5fd" - ] - ], - "summary": [ - [ - { - "id": "test" - }, - "test.fl.lima.summary:md5,e91d3c386aaf4effa63f33ee2eb7da2a" - ] - ], - "versions": [ - "versions.yml:md5,3253df3f697bdcd8cceee60e0b4ebdaf" - ], - "xml": [ - - ] - } - ], - "meta": { - "nf-test": "0.9.0", - "nextflow": "24.10.3" - }, - "timestamp": "2025-01-13T13:16:31.41132" - }, - "LIMA - Primer Removal - Input => fa.gz - stub": { - "content": [ - { - "0": [ - [ - { - "id": "test" - }, - "dummy.counts:md5,d41d8cd98f00b204e9800998ecf8427e" - ] - ], - "1": [ - [ - { - "id": "test" - }, - "dummy.report:md5,d41d8cd98f00b204e9800998ecf8427e" - ] - ], - "10": [ - - ], - "11": [ - - ], - "12": [ - - ], - "13": [ - - ], - "2": [ - [ - { - "id": "test" - }, - "dummy.summary:md5,d41d8cd98f00b204e9800998ecf8427e" - ] - ], - "3": [ - "versions.yml:md5,3253df3f697bdcd8cceee60e0b4ebdaf" - ], - "4": [ - - ], - "5": [ - - ], - "6": [ - - ], - "7": [ - - ], - "8": [ - - ], - "9": [ - - ], - "bam": [ - - ], - "clips": [ - - ], - "counts": [ - [ - { - "id": "test" - }, - "dummy.counts:md5,d41d8cd98f00b204e9800998ecf8427e" - ] - ], - "fasta": [ - - ], - "fastagz": [ - - ], - "fastq": [ - - ], - "fastqgz": [ - - ], - "guess": [ - - ], - "json": [ - - ], - "pbi": [ - - ], - "report": [ - [ - { - "id": "test" - }, - "dummy.report:md5,d41d8cd98f00b204e9800998ecf8427e" - ] - ], - "summary": [ - [ - { - "id": "test" - }, - "dummy.summary:md5,d41d8cd98f00b204e9800998ecf8427e" - ] - ], - "versions": [ - "versions.yml:md5,3253df3f697bdcd8cceee60e0b4ebdaf" - ], - "xml": [ - - ] - } - ], - "meta": { - "nf-test": "0.9.0", - "nextflow": "24.10.3" - }, - "timestamp": "2025-01-13T13:16:55.396572" - }, - "fasta": { - "content": [ - [ - - ] - ], - "meta": { - "nf-test": "0.8.4", - "nextflow": "23.10.1" - }, - "timestamp": "2024-01-17T11:35:01.262691" - }, - "LIMA - Primer Removal - Input => fq - stub": { - "content": [ - { - "0": [ - [ - { - "id": "test" - }, - "dummy.counts:md5,d41d8cd98f00b204e9800998ecf8427e" - ] - ], - "1": [ - [ - { - "id": "test" - }, - "dummy.report:md5,d41d8cd98f00b204e9800998ecf8427e" - ] - ], - "10": [ - - ], - "11": [ - - ], - "12": [ - - ], - "13": [ - - ], - "2": [ - [ - { - "id": "test" - }, - "dummy.summary:md5,d41d8cd98f00b204e9800998ecf8427e" - ] - ], - "3": [ - "versions.yml:md5,3253df3f697bdcd8cceee60e0b4ebdaf" - ], - "4": [ - - ], - "5": [ - - ], - "6": [ - - ], - "7": [ - - ], - "8": [ - - ], - "9": [ - - ], - "bam": [ - - ], - "clips": [ - - ], - "counts": [ - [ - { - "id": "test" - }, - "dummy.counts:md5,d41d8cd98f00b204e9800998ecf8427e" - ] - ], - "fasta": [ - - ], - "fastagz": [ - - ], - "fastq": [ - - ], - "fastqgz": [ - - ], - "guess": [ - - ], - "json": [ - - ], - "pbi": [ - - ], - "report": [ - [ - { - "id": "test" - }, - "dummy.report:md5,d41d8cd98f00b204e9800998ecf8427e" - ] - ], - "summary": [ - [ - { - "id": "test" - }, - "dummy.summary:md5,d41d8cd98f00b204e9800998ecf8427e" - ] - ], - "versions": [ - "versions.yml:md5,3253df3f697bdcd8cceee60e0b4ebdaf" - ], - "xml": [ - - ] - } - ], - "meta": { - "nf-test": "0.9.0", - "nextflow": "24.10.3" - }, - "timestamp": "2025-01-13T13:16:59.983156" - }, - "bam": { - "content": [ - [ - [ - { - "id": "test" - }, - "test.fl.NEB_5p--NEB_Clontech_3p.bam:md5,59b04f200c309b0a60a3f182d22f6910" - ] - ] - ], - "meta": { - "nf-test": "0.9.0", - "nextflow": "24.10.3" - }, - "timestamp": "2025-01-13T13:16:01.243385" - }, - "LIMA - Primer Removal - Input => bam - stub": { - "content": [ - { - "0": [ - [ - { - "id": "test" - }, - "dummy.counts:md5,d41d8cd98f00b204e9800998ecf8427e" - ] - ], - "1": [ - [ - { - "id": "test" - }, - "dummy.report:md5,d41d8cd98f00b204e9800998ecf8427e" - ] - ], - "10": [ - - ], - "11": [ - - ], - "12": [ - - ], - "13": [ - - ], - "2": [ - [ - { - "id": "test" - }, - "dummy.summary:md5,d41d8cd98f00b204e9800998ecf8427e" - ] - ], - "3": [ - "versions.yml:md5,3253df3f697bdcd8cceee60e0b4ebdaf" - ], - "4": [ - - ], - "5": [ - - ], - "6": [ - - ], - "7": [ - - ], - "8": [ - - ], - "9": [ - - ], - "bam": [ - - ], - "clips": [ - - ], - "counts": [ - [ - { - "id": "test" - }, - "dummy.counts:md5,d41d8cd98f00b204e9800998ecf8427e" - ] - ], - "fasta": [ - - ], - "fastagz": [ - - ], - "fastq": [ - - ], - "fastqgz": [ - - ], - "guess": [ - - ], - "json": [ - - ], - "pbi": [ - - ], - "report": [ - [ - { - "id": "test" - }, - "dummy.report:md5,d41d8cd98f00b204e9800998ecf8427e" - ] - ], - "summary": [ - [ - { - "id": "test" - }, - "dummy.summary:md5,d41d8cd98f00b204e9800998ecf8427e" - ] - ], - "versions": [ - "versions.yml:md5,3253df3f697bdcd8cceee60e0b4ebdaf" - ], - "xml": [ - - ] - } - ], - "meta": { - "nf-test": "0.9.0", - "nextflow": "24.10.3" - }, - "timestamp": "2025-01-13T13:16:46.316856" - }, - "LIMA - Primer Removal - Input => fa - stub": { - "content": [ - { - "0": [ - [ - { - "id": "test" - }, - "dummy.counts:md5,d41d8cd98f00b204e9800998ecf8427e" - ] - ], - "1": [ - [ - { - "id": "test" - }, - "dummy.report:md5,d41d8cd98f00b204e9800998ecf8427e" - ] - ], - "10": [ - - ], - "11": [ - - ], - "12": [ - - ], - "13": [ - - ], - "2": [ - [ - { - "id": "test" - }, - "dummy.summary:md5,d41d8cd98f00b204e9800998ecf8427e" - ] - ], - "3": [ - "versions.yml:md5,3253df3f697bdcd8cceee60e0b4ebdaf" - ], - "4": [ - - ], - "5": [ - - ], - "6": [ - - ], - "7": [ - - ], - "8": [ - - ], - "9": [ - - ], - "bam": [ - - ], - "clips": [ - - ], - "counts": [ - [ - { - "id": "test" - }, - "dummy.counts:md5,d41d8cd98f00b204e9800998ecf8427e" - ] - ], - "fasta": [ - - ], - "fastagz": [ - - ], - "fastq": [ - - ], - "fastqgz": [ - - ], - "guess": [ - - ], - "json": [ - - ], - "pbi": [ - - ], - "report": [ - [ - { - "id": "test" - }, - "dummy.report:md5,d41d8cd98f00b204e9800998ecf8427e" - ] - ], - "summary": [ - [ - { - "id": "test" - }, - "dummy.summary:md5,d41d8cd98f00b204e9800998ecf8427e" - ] - ], - "versions": [ - "versions.yml:md5,3253df3f697bdcd8cceee60e0b4ebdaf" - ], - "xml": [ - - ] - } - ], - "meta": { - "nf-test": "0.9.0", - "nextflow": "24.10.3" - }, - "timestamp": "2025-01-13T13:16:50.996309" - }, - "fastagz": { - "content": [ - [ - - ] - ], - "meta": { - "nf-test": "0.8.4", - "nextflow": "23.10.1" - }, - "timestamp": "2024-01-17T11:35:01.493258" - }, - "versions": { - "content": [ - [ - "versions.yml:md5,3253df3f697bdcd8cceee60e0b4ebdaf" - ] - ], - "meta": { - "nf-test": "0.9.0", - "nextflow": "24.10.3" - }, - "timestamp": "2025-01-13T13:16:01.136138" - }, - "guess": { - "content": [ - [ - [ - { - "id": "test" - }, - "test.fl.lima.guess:md5,d3675af3ca8a908ee9e3c231668392d3" - ] - ] - ], - "meta": { - "nf-test": "0.8.4", - "nextflow": "23.10.1" - }, - "timestamp": "2024-01-17T11:35:02.396472" - }, - "pbi": { - "content": [ - [ - [ - { - "id": "test" - }, - "test.fl.NEB_5p--NEB_Clontech_3p.bam.pbi:md5,851cf26eb54e4399cba5241db969dc0c" - ] - ] - ], - "meta": { - "nf-test": "0.9.0", - "nextflow": "24.10.3" - }, - "timestamp": "2025-01-13T13:16:01.343034" - }, - "report": { - "content": [ - [ - [ - { - "id": "test" - }, - "test.fl.lima.report:md5,dc073985322ae0a003ccc7e0fa4db5e6" - ] - ] - ], - "meta": { - "nf-test": "0.8.4", - "nextflow": "23.10.1" - }, - "timestamp": "2024-01-17T11:35:00.626772" - }, - "LIMA - Primer Removal - Input => fa": { - "content": [ - { - "0": [ - [ - { - "id": "test" - }, - "test.fl.lima.counts:md5,a4ceaa408be334eaa711577e95f8730e" - ] - ], - "1": [ - [ - { - "id": "test" - }, - "test.fl.lima.report:md5,bd4a8bde17471563cf91aab4c787911d" - ] - ], - "10": [ - - ], - "11": [ - - ], - "12": [ - [ - { - "id": "test" - }, - "test.fl.lima.clips:md5,1012bc8874a14836f291bac48e8482a4" - ] - ], - "13": [ - [ - { - "id": "test" - }, - "test.fl.lima.guess:md5,651e5f2b438b8ceadb3e06a2177e1818" - ] - ], - "2": [ - [ - { - "id": "test" - }, - "test.fl.lima.summary:md5,03be2311ba4afb878d8e547ab38c11eb" - ] - ], - "3": [ - "versions.yml:md5,3253df3f697bdcd8cceee60e0b4ebdaf" - ], - "4": [ - [ - { - "id": "test" - }, - "test.fl.NEB_5p--NEB_Clontech_3p.bam:md5,c5d3d376ca7ffc32ef5cbabcc9850804" - ] - ], - "5": [ - [ - { - "id": "test" - }, - "test.fl.NEB_5p--NEB_Clontech_3p.bam.pbi:md5,d1d6a2f961b9fb3d29837555706c59eb" - ] - ], - "6": [ - - ], - "7": [ - - ], - "8": [ - - ], - "9": [ - - ], - "bam": [ - [ - { - "id": "test" - }, - "test.fl.NEB_5p--NEB_Clontech_3p.bam:md5,c5d3d376ca7ffc32ef5cbabcc9850804" - ] - ], - "clips": [ - [ - { - "id": "test" - }, - "test.fl.lima.clips:md5,1012bc8874a14836f291bac48e8482a4" - ] - ], - "counts": [ - [ - { - "id": "test" - }, - "test.fl.lima.counts:md5,a4ceaa408be334eaa711577e95f8730e" - ] - ], - "fasta": [ - - ], - "fastagz": [ - - ], - "fastq": [ - - ], - "fastqgz": [ - - ], - "guess": [ - [ - { - "id": "test" - }, - "test.fl.lima.guess:md5,651e5f2b438b8ceadb3e06a2177e1818" - ] - ], - "json": [ - - ], - "pbi": [ - [ - { - "id": "test" - }, - "test.fl.NEB_5p--NEB_Clontech_3p.bam.pbi:md5,d1d6a2f961b9fb3d29837555706c59eb" - ] - ], - "report": [ - [ - { - "id": "test" - }, - "test.fl.lima.report:md5,bd4a8bde17471563cf91aab4c787911d" - ] - ], - "summary": [ - [ - { - "id": "test" - }, - "test.fl.lima.summary:md5,03be2311ba4afb878d8e547ab38c11eb" - ] - ], - "versions": [ - "versions.yml:md5,3253df3f697bdcd8cceee60e0b4ebdaf" - ], - "xml": [ - - ] - } - ], - "meta": { - "nf-test": "0.9.0", - "nextflow": "24.10.3" - }, - "timestamp": "2025-01-13T13:16:11.510737" - }, - "fastq": { - "content": [ - [ - - ] - ], - "meta": { - "nf-test": "0.8.4", - "nextflow": "23.10.1" - }, - "timestamp": "2024-01-17T11:35:01.752773" - }, - "LIMA - Primer Removal - Input => fa.gz": { - "content": [ - { - "0": [ - [ - { - "id": "test" - }, - "test.fl.lima.counts:md5,a4ceaa408be334eaa711577e95f8730e" - ] - ], - "1": [ - [ - { - "id": "test" - }, - "test.fl.lima.report:md5,bd4a8bde17471563cf91aab4c787911d" - ] - ], - "10": [ - - ], - "11": [ - - ], - "12": [ - [ - { - "id": "test" - }, - "test.fl.lima.clips:md5,1012bc8874a14836f291bac48e8482a4" - ] - ], - "13": [ - [ - { - "id": "test" - }, - "test.fl.lima.guess:md5,651e5f2b438b8ceadb3e06a2177e1818" - ] - ], - "2": [ - [ - { - "id": "test" - }, - "test.fl.lima.summary:md5,03be2311ba4afb878d8e547ab38c11eb" - ] - ], - "3": [ - "versions.yml:md5,3253df3f697bdcd8cceee60e0b4ebdaf" - ], - "4": [ - [ - { - "id": "test" - }, - "test.fl.NEB_5p--NEB_Clontech_3p.bam:md5,c5d3d376ca7ffc32ef5cbabcc9850804" - ] - ], - "5": [ - [ - { - "id": "test" - }, - "test.fl.NEB_5p--NEB_Clontech_3p.bam.pbi:md5,d1d6a2f961b9fb3d29837555706c59eb" - ] - ], - "6": [ - - ], - "7": [ - - ], - "8": [ - - ], - "9": [ - - ], - "bam": [ - [ - { - "id": "test" - }, - "test.fl.NEB_5p--NEB_Clontech_3p.bam:md5,c5d3d376ca7ffc32ef5cbabcc9850804" - ] - ], - "clips": [ - [ - { - "id": "test" - }, - "test.fl.lima.clips:md5,1012bc8874a14836f291bac48e8482a4" - ] - ], - "counts": [ - [ - { - "id": "test" - }, - "test.fl.lima.counts:md5,a4ceaa408be334eaa711577e95f8730e" - ] - ], - "fasta": [ - - ], - "fastagz": [ - - ], - "fastq": [ - - ], - "fastqgz": [ - - ], - "guess": [ - [ - { - "id": "test" - }, - "test.fl.lima.guess:md5,651e5f2b438b8ceadb3e06a2177e1818" - ] - ], - "json": [ - - ], - "pbi": [ - [ - { - "id": "test" - }, - "test.fl.NEB_5p--NEB_Clontech_3p.bam.pbi:md5,d1d6a2f961b9fb3d29837555706c59eb" - ] - ], - "report": [ - [ - { - "id": "test" - }, - "test.fl.lima.report:md5,bd4a8bde17471563cf91aab4c787911d" - ] - ], - "summary": [ - [ - { - "id": "test" - }, - "test.fl.lima.summary:md5,03be2311ba4afb878d8e547ab38c11eb" - ] - ], - "versions": [ - "versions.yml:md5,3253df3f697bdcd8cceee60e0b4ebdaf" - ], - "xml": [ - - ] - } - ], - "meta": { - "nf-test": "0.9.0", - "nextflow": "24.10.3" - }, - "timestamp": "2025-01-13T13:16:21.321812" - } -} \ No newline at end of file diff --git a/modules/nf-core/lima/tests/nextflow.config b/modules/nf-core/lima/tests/nextflow.config deleted file mode 100644 index ac259b70..00000000 --- a/modules/nf-core/lima/tests/nextflow.config +++ /dev/null @@ -1,7 +0,0 @@ -process { - - publishDir = { "${params.outdir}/${task.process.tokenize(':')[-1].tokenize('_')[0].toLowerCase()}" } - ext.args = '--isoseq --peek-guess' - ext.prefix = { "${meta.id}.fl" } - -} diff --git a/modules/nf-core/porechop/porechop/environment.yml b/modules/nf-core/porechop/porechop/environment.yml deleted file mode 100644 index 109cf8bd..00000000 --- a/modules/nf-core/porechop/porechop/environment.yml +++ /dev/null @@ -1,8 +0,0 @@ ---- -# yaml-language-server: $schema=https://raw.githubusercontent.com/nf-core/modules/master/modules/environment-schema.json -channels: - - conda-forge - - bioconda -dependencies: - - bioconda::porechop=0.2.4 - - conda-forge::pigz=2.8 diff --git a/modules/nf-core/porechop/porechop/main.nf b/modules/nf-core/porechop/porechop/main.nf deleted file mode 100644 index 34daf3e8..00000000 --- a/modules/nf-core/porechop/porechop/main.nf +++ /dev/null @@ -1,49 +0,0 @@ -process PORECHOP_PORECHOP { - 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/2b/2bce1f10c51906a66c4c4d3a7485394f67e304177192ad1cce6cf586a3a18bae/data' : - 'community.wave.seqera.io/library/porechop_pigz:d1655e5b5bad786c' }" - - - input: - tuple val(meta), path(reads) - - output: - tuple val(meta), path("*.fastq.gz"), emit: reads - tuple val(meta), path("*.log") , emit: log - 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}" - """ - porechop \\ - -i $reads \\ - -t $task.cpus \\ - $args \\ - -o ${prefix}.fastq.gz \\ - > ${prefix}.log - cat <<-END_VERSIONS > versions.yml - "${task.process}": - porechop: \$( porechop --version ) - END_VERSIONS - """ - - stub: - def prefix = task.ext.prefix ?: "${meta.id}" - """ - touch ${prefix}.fastq - gzip ${prefix}.fastq - touch ${prefix}.log - cat <<-END_VERSIONS > versions.yml - "${task.process}": - porechop: \$( porechop --version ) - END_VERSIONS - """ -} diff --git a/modules/nf-core/porechop/porechop/meta.yml b/modules/nf-core/porechop/porechop/meta.yml deleted file mode 100644 index 9e61c054..00000000 --- a/modules/nf-core/porechop/porechop/meta.yml +++ /dev/null @@ -1,71 +0,0 @@ -name: "porechop_porechop" -description: Adapter removal and demultiplexing of Oxford Nanopore reads -keywords: - - adapter - - nanopore - - demultiplexing -tools: - - porechop: - description: Adapter removal and demultiplexing of Oxford Nanopore reads - homepage: "https://github.com/rrwick/Porechop" - documentation: "https://github.com/rrwick/Porechop" - tool_dev_url: "https://github.com/rrwick/Porechop" - doi: "10.1099/mgen.0.000132" - licence: ["GPL v3"] - identifier: "" -input: - - - meta: - type: map - description: | - Groovy Map containing sample information - e.g. [ id:'test', single_end:false ] - - reads: - type: file - description: fastq/fastq.gz file - pattern: "*.{fastq,fastq.gz,fq,fq.gz}" -output: - - reads: - - meta: - type: map - description: | - Groovy Map containing sample information - e.g. [ id:'test', single_end:false ] - - "*.fastq.gz": - type: file - description: Demultiplexed and/or adapter-trimmed fastq.gz file - pattern: "*.{fastq.gz}" - - log: - - meta: - type: map - description: | - Groovy Map containing sample information - e.g. [ id:'test', single_end:false ] - - "*.log": - type: file - description: Log file containing stdout information - pattern: "*.log" - - versions: - - versions.yml: - type: file - description: File containing software versions - pattern: "versions.yml" -authors: - - "@ggabernet" - - "@jasmezz" - - "@d4straub" - - "@LaurenceKuhl" - - "@SusiJo" - - "@jonasscheid" - - "@jonoave" - - "@GokceOGUZ" - - "@jfy133" -maintainers: - - "@ggabernet" - - "@jasmezz" - - "@d4straub" - - "@LaurenceKuhl" - - "@SusiJo" - - "@jonasscheid" - - "@jonoave" - - "@GokceOGUZ" - - "@jfy133" diff --git a/modules/nf-core/porechop/porechop/tests/main.nf.test b/modules/nf-core/porechop/porechop/tests/main.nf.test deleted file mode 100644 index ed3f6986..00000000 --- a/modules/nf-core/porechop/porechop/tests/main.nf.test +++ /dev/null @@ -1,62 +0,0 @@ -nextflow_process { - - name "Test Process PORECHOP_PORECHOP" - script "../main.nf" - process "PORECHOP_PORECHOP" - config "./nextflow.config" - - tag "modules" - tag "modules_nfcore" - tag "porechop" - tag "porechop/porechop" - - test("sarscov2 - nanopore - fastq") { - - when { - process { - """ - input[0] = [ - [ id:'test', single_end:true ], - file(params.modules_testdata_base_path + 'genomics/sarscov2/nanopore/fastq/test.fastq.gz', checkIfExists: true) - ] - """ - } - } - - then { - assertAll( - { assert process.success }, - { assert snapshot(process.out.reads).match("reads") }, - { assert snapshot(process.out.versions).match("versions") }, - // complete log is not stable. These first lines should be stable - { assert snapshot(path(process.out.log.get(0).get(1)).readLines()[0..7]).match("log")} - ) - } - - } - - - test("stub") { - options "-stub" - - when { - process { - """ - input[0] = [ [ id:'test', single_end:true ], - [] - ] - """ - } - } - - then { - assertAll( - { assert process.success }, - { assert snapshot(process.out).match() } - ) - } - - } - - -} diff --git a/modules/nf-core/porechop/porechop/tests/main.nf.test.snap b/modules/nf-core/porechop/porechop/tests/main.nf.test.snap deleted file mode 100644 index cf544d2d..00000000 --- a/modules/nf-core/porechop/porechop/tests/main.nf.test.snap +++ /dev/null @@ -1,88 +0,0 @@ -{ - "versions": { - "content": [ - [ - "versions.yml:md5,712c0753b56d0fb530092dfb5bdf2e5c" - ] - ], - "timestamp": "2023-12-18T07:47:16.83444" - }, - "log": { - "content": [ - [ - "", - "\u001b[1m\u001b[4mLoading reads\u001b[0m", - "test.fastq.gz", - "100 reads loaded", - "", - "", - "\u001b[1m\u001b[4mLooking for known adapter sets\u001b[0m", - "" - ] - ], - "timestamp": "2023-12-18T07:47:16.853899" - }, - "reads": { - "content": [ - [ - [ - { - "id": "test", - "single_end": true - }, - "test_porechop.fastq.gz:md5,886fdb859fb50e0dddd35007bcff043e" - ] - ] - ], - "timestamp": "2023-12-18T07:47:16.811393" - }, - "stub": { - "content": [ - { - "0": [ - [ - { - "id": "test", - "single_end": true - }, - "test_porechop.fastq.gz:md5,d41d8cd98f00b204e9800998ecf8427e" - ] - ], - "1": [ - [ - { - "id": "test", - "single_end": true - }, - "test_porechop.log:md5,d41d8cd98f00b204e9800998ecf8427e" - ] - ], - "2": [ - "versions.yml:md5,712c0753b56d0fb530092dfb5bdf2e5c" - ], - "log": [ - [ - { - "id": "test", - "single_end": true - }, - "test_porechop.log:md5,d41d8cd98f00b204e9800998ecf8427e" - ] - ], - "reads": [ - [ - { - "id": "test", - "single_end": true - }, - "test_porechop.fastq.gz:md5,d41d8cd98f00b204e9800998ecf8427e" - ] - ], - "versions": [ - "versions.yml:md5,712c0753b56d0fb530092dfb5bdf2e5c" - ] - } - ], - "timestamp": "2023-12-18T07:47:37.814949" - } -} \ No newline at end of file diff --git a/modules/nf-core/porechop/porechop/tests/nextflow.config b/modules/nf-core/porechop/porechop/tests/nextflow.config deleted file mode 100644 index a9ecf7b6..00000000 --- a/modules/nf-core/porechop/porechop/tests/nextflow.config +++ /dev/null @@ -1,9 +0,0 @@ -process { - - - withName: PORECHOP_PORECHOP { - ext.args = '' - ext.prefix = { "${meta.id}_porechop" } - } - -} diff --git a/nextflow.config b/nextflow.config index 4fdb5d07..2218f6d1 100644 --- a/nextflow.config +++ b/nextflow.config @@ -49,16 +49,17 @@ params { // == Read QC and trimming == // -- ONT ont_collect = false // collect ONT reads into a single file - ont_trim = false // run porechop on ONT - ont_nanoq = false // run nanoq + ont_trim = false /// run fastplong trimming on ONT reads? + ont_adapters = [] // list of adapters for fastplong + ont_fastplong_args = null // args for fastplong // -- Jellyfish (qc reads) -- jellyfish = false jellyfish_k = 21 - qc_read_length = 20000 // avg read length, can be estimated from reads, used for genomescope and not very critical. dump = false // dump jellyfish output // -- HiFi -- - hifi_trim = false // run lima on HiFi reads? - hifi_primers = null // if lima, then this needs to be a path to a list of primers + hifi_trim = false // run fastplong trimming on HiFi reads? + hifi_adapters = [] // list of adapters for fastplong + hifi_fastplong_args = null // args for fastplong // -- Short read -- use_short_reads = false // short reads available? shortread_trim = false // trim short reads? diff --git a/subworkflows/local/prepare/jellyfish/main.nf b/subworkflows/local/prepare/jellyfish/main.nf index d0c08723..8f437b4a 100644 --- a/subworkflows/local/prepare/jellyfish/main.nf +++ b/subworkflows/local/prepare/jellyfish/main.nf @@ -16,7 +16,8 @@ workflow JELLYFISH { it -> [ [id: it.meta.id, jellyfish_k: it.jellyfish_k], - it.qc_reads_path + it.qc_reads_path, + it.qc_read_mean ] } .set { samples } @@ -41,7 +42,7 @@ workflow JELLYFISH { [ it.meta, it.jellyfish_k, - (it.qc_reads == "ONT" && it.ont_read_length) ? it.ont_read_length : it.qc_read_length + it.qc_read_length ] } ) diff --git a/subworkflows/local/prepare/main.nf b/subworkflows/local/prepare/main.nf index db60ab7a..b13d957e 100644 --- a/subworkflows/local/prepare/main.nf +++ b/subworkflows/local/prepare/main.nf @@ -4,6 +4,8 @@ include { PREPARE_SHORTREADS as SHORTREADS } from './prepare_shortreads/main' include { JELLYFISH } from './jellyfish/main' workflow PREPARE { + // TODO: Switch to fastp and fastplong. + /* Grouped preparations @@ -142,11 +144,11 @@ workflow PREPARE { .filter { it -> it.hifireads ? true : false } - .map { it -> it.subMap("meta","shortreads","ontreads")} + .map { it -> it.subMap("meta", "shortreads", "ontreads")} .map { it -> it.collect { entry -> [ entry.value, entry ] } } - .join( ch_main_hifi_prepped - .map { it -> it - it.subMap("shortreads","ontreads") } - .map { it -> it.collect { entry -> [ entry.value, entry ] } } + .join( ch_main_hifi_prepped + .map { it -> it - it.subMap("shortreads","ontreads") } + .map { it -> it.collect { entry -> [ entry.value, entry ] } } ) // After joining re-create the maps from the stored map .map { it -> it.collect { _entry, map -> [ (map.key): map.value ] }.collectEntries() } @@ -166,10 +168,45 @@ workflow PREPARE { jellyfish: it.jellyfish no_jelly: !it.jellyfish } + .set { ch_main_jellyfish_branched } - .set { ch_main_jellyfish_branched } + // Get average read length of the QC reads from fastplong json report + def slurp = new groovy.json.JsonSlurper() + + ch_main_prepared + .filter(it.qc_reads == "ONT") + .map { it -> it.collect { entry -> [ entry.value, entry ] } } + .join(ONT.out.fastplong_ont_reports + .map { it -> [ meta: it[0], fastplong_json: it[1] ]} + .map { it -> it.collect { entry -> [ entry.value, entry ] } } + ) + .mix( + ch_main_prepared + .filter(it.qc_reads == "HIFI") + .map { it -> it.collect { entry -> [ entry.value, entry ] } } + .join(HIFI.out.fastplong_hifi_reports + .map { it -> [ meta: it[0], fastplong_json: it[1] ]} + .map { it -> it.collect { entry -> [ entry.value, entry ] } } + ) + ) + .map { it -> it.collect { _entry, map -> [ (map.key): map.value ] }.collectEntries() } + .map { it -> it + + [ + qc_read_mean: slurp.parse(it.fastplong_json).summary.after_filtering.read_mean_length ?: + slurp.parse(it.fastplong_json).summary.before_filtering.read_mean_length + ] + - it.subMap("fastplong_json") + } + .branch { + it -> + jelly: it.jellyfish + no_jelly: !it.jellyfish + } + .set { ch_main_jellyfish_branched } - JELLYFISH(ch_main_jellyfish_branched.jellyfish) + + + JELLYFISH(ch_main_jellyfish_branched.jelly) ch_main_jellyfish_branched.no_jelly .mix( JELLYFISH.out.main_out ) @@ -185,11 +222,11 @@ workflow PREPARE { .set { versions } emit: - ch_main = main_out + ch_main = main_out meryl_kmers - versions + nanoq_stats = ONT.out.nanoq_stats + nanoq_report = ONT.out.nanoq_report genomescope_summary genomescope_plot - nanoq_report = ONT.out.nanoq_report - nanoq_stats = ONT.out.nanoq_stats + versions } diff --git a/subworkflows/local/prepare/prepare_hifi/main.nf b/subworkflows/local/prepare/prepare_hifi/main.nf index 402bd369..66c5ebd9 100644 --- a/subworkflows/local/prepare/prepare_hifi/main.nf +++ b/subworkflows/local/prepare/prepare_hifi/main.nf @@ -1,102 +1,104 @@ -include { LIMA } from '../../../../modules/nf-core/lima/main' -include { SAMTOOLS_FASTQ as TO_FASTQ } from '../../../../modules/nf-core/samtools/fastq/main' +include { FASTPLONG as FASTPLONG_HIFI } from '../../../../modules/nf-core/fastplong/main' workflow PREPARE_HIFI { take: - main_in + main_in // should contain only samples with hifireads main: Channel.empty().set { ch_versions } main_in - .branch { - hifi: it.hifireads - no_hifi: !it.hifireads - } - .set {ch_main_hifi_branched } - - - ch_main_hifi_branched - .hifi - .branch { - lima: it.hifi_trim - no_lima: !it.hifi_trim - } - .set { ch_hifi_trim_branched } - - - // lima channel goes through lima and to_fastq - ch_hifi_trim_branched - .lima .filter { it -> it.group } - .map { it -> [it.meta, it.group, it.hifireads, it.hifi_primers] } + .map { it -> [it.meta, it.group, it.hifi_trim, it.hifireads, it.hifi_adapters, it.hifi_fastplong_args] } .groupTuple(by: 1) .map { it -> [ - meta: [id: it[1], ids: it[0].id.collect().join("+")], - hifireads: it[2].unique()[0], - hifi_primers: it[3].unique()[0] + meta: [ + id: it[1], ids: it[0].id.collect().join("+"), + trim: it[2].unique()[0], + hifi_fastplong_args: it[5].unique()[0] + ], + hifireads: it[3].unique()[0], + hifi_adapters: it[4].unique()[0] ] } .mix( - ch_hifi_trim_branched - .lima + main_in .filter { it -> !it.group } .map { - it -> it.subMap("meta","hifireads","hifi_primers") + it -> + [ + meta: [ + id: it.meta.id, + trim: it.hifi_trim, + hifi_fastplong_args: it.hifi_fastplong_args + ], + hifireads: it.hifireads, + hifi_adapters: it.hifi_adapters, + ] } ) .multiMap { it -> reads: [it.meta, it.hifireads] - primers: it.hifi_primers + adapters: it.hifi_adapters } - .set { ch_lima_in } + .set { ch_fastplong_in } - LIMA(ch_lima_in.reads, ch_lima_in.primers ) - TO_FASTQ(LIMA.out.bam, false) + FASTPLONG_HIFI(ch_fastplong_in.reads, ch_fastplong_in.adapters, false, false ) - TO_FASTQ + FASTPLONG_HIFI .out - .fastq + .reads .filter { it -> it[0].ids } .flatMap { it -> it[0].ids .tokenize("+") .collect { sample -> [ meta: [ id: sample ], hifireads: it[1] ] } } - .mix(TO_FASTQ.out.fastq + .mix(FASTPLONG_HIFI.out.reads .filter { it -> !it[0].ids } .map { it -> [ meta: [ id: it[0].id ], hifireads: it[1] ] } ) - .set { to_fastq_out } + .set { fastplong_reads_out } + FASTPLONG_HIFI + .out + .json + .filter { it -> it[0].ids } + .flatMap { it -> + it[0].ids + .tokenize("+") + .collect { sample -> [ [ id: sample ], it[1] ] } + } + .mix(FASTPLONG_HIFI.out.json + .filter { it -> !it[0].ids } + .map { + it -> [ [ id: it[0].id ], it[1] ] + } + ) + .set { fastplong_json_out } - // no_lima is mixed with lima outputs - ch_hifi_trim_branched - .no_lima - .mix( - // lima inputs are joined to lima outputs - ch_hifi_trim_branched - .lima - .map { it -> it - it.subMap('hifireads') } + // inputs are joined to outputs + main_in + .map { it -> it - it.subMap('hifireads') } + .map { it -> it.collect { entry -> [ entry.value, entry ] } } + .join( + fastplong_reads_out .map { it -> it.collect { entry -> [ entry.value, entry ] } } - .join( - to_fastq_out - .map { it -> it.collect { entry -> [ entry.value, entry ] } } - ) - .map { it -> it.collect { _entry, map -> [ (map.key): map.value ] }.collectEntries() } - ) - // this contains all hifi samples, mix back with no_hifi and set to main_out - .mix(ch_main_hifi_branched.no_hifi) + ) + .map { it -> it.collect { _entry, map -> [ (map.key): map.value ] }.collectEntries() } .set { main_out } - versions = ch_versions.mix(LIMA.out.versions).mix(TO_FASTQ.out.versions) + + + versions = ch_versions.mix(FASTPLONG_HIFI.out.versions) emit: main_out + fastplong_hifi_reports = fastplong_json_out versions } diff --git a/subworkflows/local/prepare/prepare_ont/chop/main.nf b/subworkflows/local/prepare/prepare_ont/chop/main.nf deleted file mode 100644 index 9183cbb3..00000000 --- a/subworkflows/local/prepare/prepare_ont/chop/main.nf +++ /dev/null @@ -1,23 +0,0 @@ -include { PORECHOP_PORECHOP as PORECHOP } from '../../../../../modules/nf-core/porechop/porechop/main' - -workflow CHOP { - take: - input - - main: - Channel.empty().set { chopped_reads } - Channel.empty().set { ch_versions } - - PORECHOP(input) - - PORECHOP.out.reads - .set { chopped_reads } - - ch_versions.mix(PORECHOP.out.versions) - - versions = ch_versions - - emit: - chopped_reads - versions -} diff --git a/subworkflows/local/prepare/prepare_ont/main.nf b/subworkflows/local/prepare/prepare_ont/main.nf index 9b6b9b33..62ce90ff 100644 --- a/subworkflows/local/prepare/prepare_ont/main.nf +++ b/subworkflows/local/prepare/prepare_ont/main.nf @@ -1,10 +1,10 @@ -include { CHOP } from './chop/main' +include { FASTPLONG as FASTPLONG_ONT } from '../../../../modules/nf-core/fastplong/main' include { COLLECT } from './collect/main' -include { RUN_NANOQ } from './run_nanoq/main' + workflow PREPARE_ONT { take: - ch_main + ch_main // should contain only samples with ontreads main: Channel.empty().set { ch_versions } @@ -12,12 +12,12 @@ workflow PREPARE_ONT { ch_main .branch { it -> - to_collect: it.ont_collect - no_collect: !it.ont_collect + to_collect: it.ont_collect + no_collect: !it.ont_collect } - .set { ch_ont_collect_branched } + .set { ch_main_collect_branched } - ch_ont_collect_branched + ch_main_collect_branched .to_collect .filter { it -> it.group } .map { it -> [it.meta, it.group, it.ontreads] } @@ -30,7 +30,7 @@ workflow PREPARE_ONT { ] } .mix( - ch_ont_collect_branched + ch_main_collect_branched .to_collect .filter { it -> !it.group } .map { @@ -57,145 +57,105 @@ workflow PREPARE_ONT { .map { it -> it.collect { entry -> [ entry.value, entry ] } } .set { ch_collected_reads } - ch_ont_collect_branched + ch_main_collect_branched .to_collect .map { it -> it - it.subMap("ontreads") } .map { it -> it.collect { entry -> [ entry.value, entry ] } } .join(ch_collected_reads) .map { it -> it.collect { _entry, map -> [ (map.key): map.value ] }.collectEntries() } - .mix(ch_ont_collect_branched.no_collect) + .mix(ch_main_collect_branched.no_collect) .set { ch_collected } // ch_collected is the same samples as the input channel - ch_collected - .branch { - chop: it.ont_trim - no_chop: !it.ont_trim - } - .set { ch_ont_chop_branched } - - ch_ont_chop_branched - .chop - .filter { it -> it.group } - .map { it -> [it.meta, it.group, it.ontreads] } + .filter { it -> it.group } + .map { it -> [it.meta, it.group, it.ont_trim, it.ontreads, it.ont_adaptors, it.ont_fastplong_args] } .groupTuple(by: 1) .map { it -> [ - [id: it[1], ids: it[0].id.collect().join("+")], - it[2].unique()[0] + meta: [ + id: it[1], ids: it[0].id.collect().join("+"), + trim: it[2].unique()[0], + ont_fastplong_args: it[5].unique()[0] + ], + ontreads: it[3].unique()[0], + ont_adaptors: it[4].unique()[0] ] } .mix( - ch_ont_chop_branched - .chop + ch_collected .filter { it -> !it.group } .map { - it -> [ it.meta, it.ontreads ] + it -> + [ + meta: [ + id: it.meta.id, + trim: it.ont_trim, + ont_fastplong_args: it.ont_fastplong_args + ], + ontreads: it.ontreads, + ont_adaptors: it.ont_adaptors, + ] } ) - .map { it -> [ it.meta, it.ontreads ] } - .set { chop_in } - - CHOP(chop_in) - - ch_ont_chop_branched - .chop - .map { it -> it - it.subMap("ontreads")} - .map { it -> it.collect { entry -> [ entry.value, entry ] } } - .join( - CHOP - .out - .chopped_reads - .filter { it -> it[0].ids } - .flatMap { it -> - it[0].ids - .tokenize("+") - .collect { sample -> [ meta: [ id: sample ], ontreads: it[1] ] } - } - .mix(CHOP - .out - .chopped_reads - .filter { it -> !it[0].ids } - .map { - it -> [ meta: [ it[0].id ], ontreads: it[1] ] - } - ) - .map { it -> it.collect { entry -> [ entry.value, entry ] } } - ) - .map { it -> it.collect { _entry, map -> [ (map.key): map.value ] }.collectEntries() } - .mix( - ch_ont_chop_branched - .no_chop - ) - .set { ch_ont_chopped } - - ch_ont_chopped - .branch { - nanoq: it.ont_nanoq - no_nanoq: !it.ont_nanoq - } - .set { ch_ont_chopped_branched } - - ch_ont_chopped_branched - .nanoq - .filter { it -> it.group } - .map { it -> [it.meta, it.group, it.ontreads] } - .groupTuple(by: 1) - .map { + .multiMap { it -> - [ - [id: it[1], ids: it[0].id.collect().join("+")], - it[2].unique()[0] - ] + reads: [it.meta, it.ontreads] + adapters: it.ont_adapters } - .mix( - ch_ont_chopped_branched - .nanoq - .filter { it -> !it.group } - .map { - it -> [ it.meta, it.ontreads ] - } - ) - .map { it -> [ it.meta, it.ontreads ] } - .set { ch_nanoq_in } + .set { ch_fastplong_in } - RUN_NANOQ(ch_nanoq_in) + FASTPLONG_ONT(ch_fastplong_in.reads, ch_fastplong_in.adapters, false, false) - RUN_NANOQ.out.median_length + FASTPLONG_ONT + .out + .reads .filter { it -> it[0].ids } .flatMap { it -> it[0].ids .tokenize("+") - .collect { sample -> [ meta: [ id: sample ], ont_read_length: it[1] ] } - } - .mix( - RUN_NANOQ.out.median_length + .collect { sample -> [ meta: [ id: sample ], ontreads: it[1] ] } + } + .mix(FASTPLONG_ONT.out.reads .filter { it -> !it[0].ids } - .map { it -> [meta: [ id: it[0].id ], ont_read_length: it[1]] } - ) - .map { it -> it.collect { entry -> [ entry.value, entry ] } } - .set { med_len } - - RUN_NANOQ.out.report.set { nanoq_report } + .map { + it -> [ meta: [ id: it[0].id ], ontreads: it[1] ] + } + ) + .set { fastplong_reads_out } - RUN_NANOQ.out.stats.set { nanoq_stats } + FASTPLONG_ONT + .out + .json + .filter { it -> it[0].ids } + .flatMap { it -> + it[0].ids + .tokenize("+") + .collect { sample -> [ [ id: sample ], it[1] ] } + } + .mix(FASTPLONG_ONT.out.json + .filter { it -> !it[0].ids } + .map { + it -> [ [ id: it[0].id ], it[1] ] + } + ) + .set { fastplong_json_out } - ch_ont_chopped_branched - .nanoq - .map { it -> it - it.subMap("ont_read_length") } + ch_collected + .map { it -> it - it.subMap('ontreads') } .map { it -> it.collect { entry -> [ entry.value, entry ] } } - .join( med_len ) + .join( + fastplong_reads_out + .map { it -> it.collect { entry -> [ entry.value, entry ] } } + ) .map { it -> it.collect { _entry, map -> [ (map.key): map.value ] }.collectEntries() } - .mix(ch_ont_chopped_branched.no_nanoq) .set { main_out } - versions = ch_versions.mix(COLLECT.out.versions).mix(CHOP.out.versions).mix(RUN_NANOQ.out.versions) + versions = ch_versions.mix(COLLECT.out.versions).mix(FASTPLONG_ONT.out.versions) emit: main_out - nanoq_report - nanoq_stats + fastplong_ont_reports = fastplong_json_out versions } diff --git a/subworkflows/local/prepare/prepare_ont/run_nanoq/main.nf b/subworkflows/local/prepare/prepare_ont/run_nanoq/main.nf deleted file mode 100644 index 06e35d2c..00000000 --- a/subworkflows/local/prepare/prepare_ont/run_nanoq/main.nf +++ /dev/null @@ -1,25 +0,0 @@ -include { NANOQ } from '../../../../../modules/local/nanoq/main' - -workflow RUN_NANOQ { - take: - inputs - - main: - Channel.empty().set { versions } - - NANOQ(inputs) - - NANOQ.out.report.set { report } - - NANOQ.out.stats.set { stats } - - NANOQ.out.median_length.set { median_length } - - NANOQ.out.versions.set { versions } - - emit: - report - stats - median_length - versions -} diff --git a/subworkflows/local/utils_nfcore_genomeassembler_pipeline/main.nf b/subworkflows/local/utils_nfcore_genomeassembler_pipeline/main.nf index a95e6d07..a0500538 100644 --- a/subworkflows/local/utils_nfcore_genomeassembler_pipeline/main.nf +++ b/subworkflows/local/utils_nfcore_genomeassembler_pipeline/main.nf @@ -101,7 +101,7 @@ workflow PIPELINE_INITIALISATION { [ meta: [id: it.sample], // new in refactor-assemblies - group: it.group ?: "none", + group: it.group ?: null, ontreads: it.ontreads ?: params.ontreads, hifireads: it.hifireads ?: params.hifireads, // new in refactor-assemblers @@ -134,12 +134,13 @@ workflow PIPELINE_INITIALISATION { null, ont_collect: it.ont_collect ?: params.ont_collect, ont_trim: it.ont_trim ?: params.ont_trim, - ont_nanoq: it.ont_nanoq ?: params.ont_nanoq, + ont_adapters: it.ont_adapters ?: params.ont_adapters, + ont_fastplong_args: it.ont_fastplong_args ?: params.ont_fastplong_args, jellyfish: it.jellyfish ?: params.jellyfish, jellyfish_k: it.ont_jellyfish_k ?: params.jellyfish_k, - qc_read_length: it.qc_read_length ?: params.qc_read_length, hifi_trim: it.hifi_trim ?: params.hifi_trim, - hifi_primers: it.hifi_primers ?: params.hifi_primers, + hifi_adapters: it.hifi_adapters ?: params.hifi_adapters, + hifi_fastplong_args: it.hifi_fastplong_args ?: params.hifi_fastplong_args, polish_medaka: it.polish_medaka ?: params.polish_medaka, medaka_model: it.medaka_model ?: params.medaka_model, polish_pilon: it.polish_pilon ?: params.polish_pilon, @@ -157,7 +158,6 @@ workflow PIPELINE_INITIALISATION { ref_map_bam: it.ref_map_bam ?: params.ref_map_bam ?: null, // assembly mapping provided assembly_map_bam: it.assembly_map_bam ?: params.ref_map_bam ?: null, - // reads for qc qc_reads: ((it.qc_reads == "ont" || params.qc_reads == "ont") && it.ontreads) ? "ont" : "hifi", qc_reads_path: ((it.qc_reads == "ont" || params.qc_reads == "ont") && it.ontreads) ? (it.ontreads) : (it.hifireads), @@ -236,7 +236,7 @@ workflow PIPELINE_INITIALISATION { ] : null, // Check if genome_size is given with --scaffold_longstitch - (it.scaffold_longstitch && !it.genome_size && !(it.ontreads && params.ont_jellyfish)) + (it.scaffold_longstitch && !it.genome_size && !(it.ontreads && params.jellyfish)) ? [ println("Please confirm samplesheet: [sample: $it.meta.id]: scaffolding with longstitch requires genome-size. Either provide genome-size estimate, or estimate from ONT reads with --jellyfish"), diff --git a/workflows/genomeassembler.nf b/workflows/genomeassembler.nf index f58f4d50..acb821cd 100644 --- a/workflows/genomeassembler.nf +++ b/workflows/genomeassembler.nf @@ -97,6 +97,8 @@ workflow GENOMEASSEMBLER { shortread_trim: bool */ + // TODO: Currently the pipeline is losing everything with hifireads somewhere. + Channel.empty().set { meryl_kmers } Channel.empty().set { ch_versions } @@ -105,7 +107,7 @@ workflow GENOMEASSEMBLER { Channel .of([]) .tap { quast_files } - .tap { nanoq_files } + .tap { fastplong_files } .tap { genomescope_files } .map { it -> ["dummy", it] } .tap { busco_files } From 93d3dd23550396732c4623917a5feffe3017aa24 Mon Sep 17 00:00:00 2001 From: Niklas Schandry Date: Thu, 7 Aug 2025 16:56:41 +0200 Subject: [PATCH 036/162] fastplong report --- assets/report/functions/read_fastplong.R | 0 assets/report/report.qmd | 145 ++++++++++++----------- conf/modules/hifi-prep.config | 1 + conf/modules/ont-prep.config | 18 +-- modules/local/nanoq/environment.yml | 7 -- modules/local/nanoq/main.nf | 43 ------- modules/local/report/main.nf | 53 +++++++-- modules/nf-core/fastqc/main.nf | 64 ++++++++++ subworkflows/local/prepare/main.nf | 16 +-- workflows/genomeassembler.nf | 15 +-- 10 files changed, 205 insertions(+), 157 deletions(-) create mode 100644 assets/report/functions/read_fastplong.R delete mode 100644 modules/local/nanoq/environment.yml delete mode 100644 modules/local/nanoq/main.nf create mode 100644 modules/nf-core/fastqc/main.nf diff --git a/assets/report/functions/read_fastplong.R b/assets/report/functions/read_fastplong.R new file mode 100644 index 00000000..e69de29b diff --git a/assets/report/report.qmd b/assets/report/report.qmd index b91db08c..3273fcec 100644 --- a/assets/report/report.qmd +++ b/assets/report/report.qmd @@ -6,11 +6,11 @@ nav-buttons: - icon: github - href: https://github.com/nf-core/genomeassembler params: - nanoq: true + fastplong: false busco: true quast: true - jellyfish: true - merqury: true + jellyfish: false + merqury: false --- ```{r load libraries and functions} @@ -24,86 +24,71 @@ library(magrittr) library(plotly) #library(gt) # Load functions -list.files("functions", - full.names = T, - pattern = ".R") %>% +list.files("functions", full.names = T, pattern = ".R") %>% map(\(x) source(x)) # Set default ggplot theme theme_set(theme_bw(base_size = 14, base_family = "Arial")) theme_update(strip.background = element_blank(), - axis.text.x = element_text(angle = 70, hjust = 1)) + axis.text.x = element_text(angle = 70, hjust = 1)) ## Colors, these come from the khroma package ("muted") ### For <=9 stages: -colors_9 <- c( - "#CC6677", - "#332288", - "#DDCC77", - "#117733", - "#88CCEE", - "#882255", - "#44AA99", - "#999933", - "#AA4499" -) -color_scale_plots <- scale_color_manual(values = colors_9, na.value = "#DDDDDD") -fill_scale_plots <- scale_fill_manual(values = colors_9, na.value = "#DDDDDD") - - +color_scale_plots <- scale_color_manual(values = c("#CC6677", "#332288", "#DDCC77", "#117733", "#88CCEE", "#882255", "#44AA99", "#999933", "#AA4499"), na.value = "#DDDDDD") +fill_scale_plots <- scale_fill_manual(values = c("#CC6677", "#332288", "#DDCC77", "#117733", "#88CCEE", "#882255", "#44AA99", "#999933", "#AA4499"), na.value = "#DDDDDD") # Base directory containing reports data_base <- "data/" - -# groups groups <- yaml::read_yaml("examples/yml/example.yml") %>% - map_dfr(\(row) - data.frame(sample = pluck(row, 1, "id"), group = pluck(row, 1, "group"))) %>% - mutate(group = case_when(group %in% c("","null") ~ sample, TRUE ~ group)) + map_dfr(\(row) + data.frame( + id = pluck(row,1,"id"), + group = pluck(row,1,"group") + ) + ) ``` + # About This report displays the main information gathered from various QC steps. -# nanoq {.tabset} +# fastplong {.tabset} -::: {.content-visible unless-profile="nanoq"} -nanoq was not included in the pipeline run, no ONT reads were included. +::: {.content-visible unless-profile="fastplong"} +fastplong was not included in the pipeline run. ::: -```{r nanoq read inputs} -#| eval: !expr params$nanoq +```{r fastplong read inputs} +#| eval: !expr params$fastplong #| include: false #| message: false #| output: false -# Parse nanoq reports into table -nanoq_reports <- list.files(paste0(data_base, "nanoq"), - pattern = "report.json", +# Parse fastplong reports into table +# Note that for these reports the sample name is the group +fastplong_reports <- list.files(paste0(data_base, "fastplong"), + pattern = ".json", full.names = T) %>% - map_dfr(\(x) read_nanoq(x)) %>% - left_join(groups, by = join_by(sample)) + map_dfr(\(x) read_fastplong(x)) ``` ```{r} -#| eval: !expr params$nanoq +#| eval: !expr params$fastplong #| include: false -# For each sample, we create one plot chunk that will be saved into nanoq files +# For each sample, we create one plot chunk that will be saved into fastplong files # This is an rmd chunk in plain text. -dir.create("nanoq_files") -for (i in 1:length(unique(nanoq_reports$group))) { +dir.create("fastplong_files") +for (i in 1:length(unique(fastplong_reports$sample))) { paste0('```{r}\n - #| title: "Nanoq read statistics" - p <- nanoq_reports %>% - filter(stat %in% c("Median Length", "Longest", "Median Quality","Bases")) %>% - filter(group == "', unique(nanoq_reports$group)[i], '") %>% - mutate(stat=fct_relevel(stat,c("Bases","Longest","Median Length","Median Quality"))) %>% - ggplot(aes(x = sample, y = val)) + + #| title: "fastplong read statistics" + p <- fastplong_reports %>% + filter(group == "', unique(fastplong_reports$sample)[i], '") %>% + ggplot(aes(x = sample, y = value)) + geom_line() + geom_point(size = 5, pch=21, aes(fill=stage)) + - facet_wrap(~stat, scales = "free_y", ncol=2) + + facet_wrap(read_type~stat, scales = "free_y", ncol=2) + fill_scale_plots + scale_y_continuous(labels = function(x) format(x,scientific=-1,trim=T, digits = 3, drop0trailing=T), n.breaks = 4) + theme(axis.title.x = element_blank(), @@ -112,48 +97,69 @@ paste0('```{r}\n legend.title = element_blank(), panel.grid.minor = element_blank()) ggplotly(p)\n```') %>% - write_lines(glue::glue("nanoq_files/_{ unique(nanoq_reports$group)[i] }_nanoq.Rmd")) + write_lines(glue::glue("fastplong_files/_{ unique(fastplong_reports$sample)[i] }_fastplong.Rmd")) } ``` -::: {.content-visible when-profile="nanoq"} +::: {.content-visible when-profile="fastplong"} ::: {.panel-tabset .flow} -```{r nanoq add subplots} -#| eval: !expr params$nanoq +```{r fastplong add subplots} +#| eval: !expr params$fastplong #| results: asis # This loop creates one tab per group ## Each tab contains 3 valueboxes ## Below the valueboxes, the sample-specific plot code generated above is inserted -for (i in 1:length(unique(nanoq_reports$group))) { - cat(paste0('## ', unique(nanoq_reports$group)[i], '\n\n'), +for (i in 1:length(unique(fastplong_reports$sample))) { + cat(paste0('## ', unique(fastplong_reports$sample)[i], '\n\n'), paste0('### { width = 30% }', '\n\n'), paste0('::: {.valuebox icon="magic" color="primary" title="Total bases sequenced"}','\n'), - paste0(nanoq_reports %>% - filter(stat == "Bases") %>% - filter(group == unique(nanoq_reports$group)[i],) %$% + paste0(fastplong_reports %>% + filter(stat == "Total Bases", stage == "After Filtering") %>% + filter(sample == unique(fastplong_reports$sample)[i],) %$% sum(val) %>% format(scientific=-1,trim=T, digits = 3, drop0trailing=T),'\n'), paste0(':::', '\n\n'), paste0('::: {.valuebox icon="collection" color="secondary" title="Number of reads"}', '\n'), - paste0(nanoq_reports %>% - filter(stat == "N Reads") %>% - filter(group == unique(nanoq_reports$group)[i]) %$% + paste0(fastplong_reports %>% + filter(stat == "Total Reads", stage == "After Filtering") %>% + filter(sample == unique(fastplong_reports$sample)[i]) %$% sum(val) %>% paste(" bases"), '\n'), paste0(':::', '\n\n'), - paste0('::: {.valuebox icon="chevron-double-up" color="success" title="Longest read"}', '\n'), - paste0(nanoq_reports %>% - filter(stat == "Longest") %>% - filter(group == unique(nanoq_reports$group)[i]) %$% - max(val) %>% - paste0(" bases"),'\n'), + paste0('::: {.valuebox icon="chevron-double-up" color="success" title="Q30 rate"}', '\n'), + paste0(fastplong_reports %>% + filter(stat == "Q30 Rate", stage == "After Filtering") %>% + filter(sample == unique(fastplong_reports$sample)[i]) %$% + {value*100} %>% + round(1) %>% + paste0(" %"), + '\n'), paste0(':::', '\n\n'), + paste0('\n\n'), + paste0('### {.tabset}'), + paste0('\n\n'), + paste0('#### Tables \n\n'), + fastplong_reports %>% + filter(sample == unique(fastplong_reports$sample)[i]) %>% + pivot_wider(id_cols = c("sample","read_type","stat"),names_from = "stage") %>% + gt::gt() %>% + gt::cols_label( + sample = "Group", + read_type = "Read Type", + stat = "Metric" + ) %>% + gt::fmt_number(suffixing = TRUE, n_sigfig = 3, rows = ! (stat %>% str_detect("Rate"))) %>% + gt::fmt_percent(rows = stat %>% str_detect("Rate")) %>% + gt::as_raw_html() + , + paste0('\n\n'), + paste0('#### Plots'), paste0('### ', '\n\n'), - knitr::knit_child(glue::glue('nanoq_files/_{ unique(nanoq_reports$group)[i] }_nanoq.Rmd'), + knitr::knit_child(glue::glue('fastplong_files/_{ unique(fastplong_reports$sample)[i] }_fastplong.Rmd'), envir = globalenv(), quiet = TRUE), paste0('\n\n'), @@ -163,15 +169,16 @@ for (i in 1:length(unique(nanoq_reports$group))) { ``` ```{r} -#| eval: !expr params$nanoq +#| eval: !expr params$fastplong # Clean up the intermediate files -unlink("nanoq_files", recursive = T) +unlink("fastplong_files", recursive = T) ``` ::: ::: + # QUAST {.tabset} ::: {.content-visible unless-profile="quast"} diff --git a/conf/modules/hifi-prep.config b/conf/modules/hifi-prep.config index 45985889..a65796ed 100644 --- a/conf/modules/hifi-prep.config +++ b/conf/modules/hifi-prep.config @@ -11,6 +11,7 @@ process { meta.hifi_fastplong_args ].join(" ").trim() } + ext.prefix = { "${meta.id}_hifi" } } withName: TO_FASTQ { publishDir = [ diff --git a/conf/modules/ont-prep.config b/conf/modules/ont-prep.config index 6b824273..e92e67c3 100644 --- a/conf/modules/ont-prep.config +++ b/conf/modules/ont-prep.config @@ -1,11 +1,4 @@ process { - withName: NANOQ { - publishDir = [ - path: { "${params.outdir}/${meta.id}/QC/nanoq" }, - mode: params.publish_dir_mode, - saveAs: { filename -> filename.equals('versions.yml') ? null : filename } - ] - } withName: COLLECT { publishDir = [ path: { "${params.outdir}/${meta.id}/reads/collect" }, @@ -13,11 +6,18 @@ process { saveAs: { filename -> filename.equals('versions.yml') ? null : filename } ] } - withName: PORECHOP { + withName: FASTPLONG_ONT { publishDir = [ - path: { "${params.outdir}/${meta.id}/reads/porechop" }, + path: { "${params.outdir}/${meta.id}/reads/fastplong/ont/" }, mode: params.publish_dir_mode, saveAs: { filename -> filename.equals('versions.yml') ? null : filename } ] + ext.args = { + [ + meta.trim ? "-A" : '', + meta.ont_fastplong_args + ].join(" ").trim() + } + ext.prefix = { "${meta.id}_ont" } } } diff --git a/modules/local/nanoq/environment.yml b/modules/local/nanoq/environment.yml deleted file mode 100644 index 1a95d24e..00000000 --- a/modules/local/nanoq/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: - - "bioconda::nanoq=0.10.0" diff --git a/modules/local/nanoq/main.nf b/modules/local/nanoq/main.nf deleted file mode 100644 index 50d75135..00000000 --- a/modules/local/nanoq/main.nf +++ /dev/null @@ -1,43 +0,0 @@ -process NANOQ { - 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/nanoq:0.10.0--h031d066_2' - : 'biocontainers/nanoq:0.10.0--h031d066_2'}" - - input: - tuple val(meta), path(reads) - - output: - tuple val(meta), path("*_report.json"), emit: report - tuple val(meta), path("*_stats.json"), emit: stats - tuple val(meta), env(median), emit: median_length - path "versions.yml", emit: versions - - script: - def prefix = task.ext.prefix ?: "${meta.id}" - """ - nanoq -i ${reads} -j -r ${prefix}_report.json -s -H -vvv > ${prefix}_stats.json - median=\$(cat ${prefix}_report.json | grep -o '"median_length":[0-9]*' | grep -o '[0-9]*') - - cat <<-END_VERSIONS > versions.yml - "${task.process}": - nanoq: \$(nanoq -V | sed 's/nanoq //') - END_VERSIONS - """ - - stub: - def prefix = task.ext.prefix ?: "${meta.id}" - """ - touch ${prefix}_report.json - touch ${prefix}_stats.json - median=1 - - cat <<-END_VERSIONS > versions.yml - "${task.process}": - nanoq: \$(nanoq -V | sed 's/nanoq //') - END_VERSIONS - """ -} diff --git a/modules/local/report/main.nf b/modules/local/report/main.nf index 3c024a05..ee21952e 100644 --- a/modules/local/report/main.nf +++ b/modules/local/report/main.nf @@ -10,14 +10,14 @@ process REPORT { https://wave.seqera.io/view/builds/bd-be4a8863b7b76cf7_1 docker */ input: - path qmdir_files, stageAs: "*" - path funct_files, stageAs: "functions/*" - path nanoq_files, stageAs: "data/nanoq/*" - path jelly_files, stageAs: "data/genomescope/*" - path quast_files, stageAs: "data/quast/*" - path busco_files, stageAs: "data/busco/*" - path meryl_files, stageAs: "data/merqury/*" - path versions, stageAs: "software_versions.yml" + path qmdir_files, stageAs: "*" + path funct_files, stageAs: "functions/*" + path fastplong_files, stageAs: "data/fastplong/*" + path jelly_files, stageAs: "data/genomescope/*" + path quast_files, stageAs: "data/quast/*" + path busco_files, stageAs: "data/busco/*" + path meryl_files, stageAs: "data/merqury/*" + path versions, stageAs: "software_versions.yml" val groups output: @@ -33,9 +33,9 @@ process REPORT { script: def report_profile = "--profile base" def report_params = '' - if (nanoq_files) { - report_profile = report_profile << ",nanoq" - report_params = report_params << ' -P nanoq:true' + if (fastplong_files) { + report_profile = report_profile << ",fastplong" + report_params = report_params << ' -P fastplong:true' } if (quast_files) { report_profile = report_profile << ",quast" @@ -95,3 +95,34 @@ process REPORT { END_VERSIONS """ } +library(magrittr) +library(tidyjson) +library(dplyr) +library(readr) + +# Read a nanoq json report +read_fastplong <- function(file) { + sample_name <- file %>% str_extract('(?<=fastplong/).+?(?=_(ont|hifi)\\.fastplong\\.json)') + read_type <- file %>% + str_extract('(?<=fastplong/).*') %>% + str_extract('_(ont|hifi)\\.') %>% + str_remove_all("_|\\.") + read_json(file) %>% + enter_object("summary") %>% + tidyjson::spread_all() %>% + as_tibble() %>% + select(-document.id,-fastplong_version) %>% + pivot_longer(everything(), + names_to = c("stage", "stat"), + names_pattern = "(.*)\\.(.*)") %>% + mutate(sample = sample_name) %>% + mutate( + stat = stat %>% + str_replace_all("_", " ") %>% + str_to_title(), + stage = stage %>% + str_replace_all("_", " ") %>% + str_to_title(), + read_type = read_type + ) +} diff --git a/modules/nf-core/fastqc/main.nf b/modules/nf-core/fastqc/main.nf new file mode 100644 index 00000000..23e16634 --- /dev/null +++ b/modules/nf-core/fastqc/main.nf @@ -0,0 +1,64 @@ +process FASTQC { + 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/fastqc:0.12.1--hdfd78af_0' : + 'biocontainers/fastqc:0.12.1--hdfd78af_0' }" + + input: + tuple val(meta), path(reads) + + output: + tuple val(meta), path("*.html"), emit: html + tuple val(meta), path("*.zip") , emit: zip + 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}" + // Make list of old name and new name pairs to use for renaming in the bash while loop + def old_new_pairs = reads instanceof Path || reads.size() == 1 ? [[ reads, "${prefix}.${reads.extension}" ]] : reads.withIndex().collect { entry, index -> [ entry, "${prefix}_${index + 1}.${entry.extension}" ] } + def rename_to = old_new_pairs*.join(' ').join(' ') + def renamed_files = old_new_pairs.collect{ _old_name, new_name -> new_name }.join(' ') + + // The total amount of allocated RAM by FastQC is equal to the number of threads defined (--threads) time the amount of RAM defined (--memory) + // https://github.com/s-andrews/FastQC/blob/1faeea0412093224d7f6a07f777fad60a5650795/fastqc#L211-L222 + // Dividing the task.memory by task.cpu allows to stick to requested amount of RAM in the label + def memory_in_mb = task.memory ? task.memory.toUnit('MB') / task.cpus : null + // FastQC memory value allowed range (100 - 10000) + def fastqc_memory = memory_in_mb > 10000 ? 10000 : (memory_in_mb < 100 ? 100 : memory_in_mb) + + """ + printf "%s %s\\n" ${rename_to} | while read old_name new_name; do + [ -f "\${new_name}" ] || ln -s \$old_name \$new_name + done + + fastqc \\ + ${args} \\ + --threads ${task.cpus} \\ + --memory ${fastqc_memory} \\ + ${renamed_files} + + cat <<-END_VERSIONS > versions.yml + "${task.process}": + fastqc: \$( fastqc --version | sed '/FastQC v/!d; s/.*v//' ) + END_VERSIONS + """ + + stub: + def prefix = task.ext.prefix ?: "${meta.id}" + """ + touch ${prefix}.html + touch ${prefix}.zip + + cat <<-END_VERSIONS > versions.yml + "${task.process}": + fastqc: \$( fastqc --version | sed '/FastQC v/!d; s/.*v//' ) + END_VERSIONS + """ +} diff --git a/subworkflows/local/prepare/main.nf b/subworkflows/local/prepare/main.nf index b13d957e..0f46b151 100644 --- a/subworkflows/local/prepare/main.nf +++ b/subworkflows/local/prepare/main.nf @@ -174,7 +174,7 @@ workflow PREPARE { def slurp = new groovy.json.JsonSlurper() ch_main_prepared - .filter(it.qc_reads == "ONT") + .filter(it.qc_reads.toLower() == "ont") .map { it -> it.collect { entry -> [ entry.value, entry ] } } .join(ONT.out.fastplong_ont_reports .map { it -> [ meta: it[0], fastplong_json: it[1] ]} @@ -182,7 +182,7 @@ workflow PREPARE { ) .mix( ch_main_prepared - .filter(it.qc_reads == "HIFI") + .filter(it.qc_reads.toLower() == "hifi") .map { it -> it.collect { entry -> [ entry.value, entry ] } } .join(HIFI.out.fastplong_hifi_reports .map { it -> [ meta: it[0], fastplong_json: it[1] ]} @@ -190,12 +190,13 @@ workflow PREPARE { ) ) .map { it -> it.collect { _entry, map -> [ (map.key): map.value ] }.collectEntries() } - .map { it -> it + + .map { it -> + it + [ qc_read_mean: slurp.parse(it.fastplong_json).summary.after_filtering.read_mean_length ?: slurp.parse(it.fastplong_json).summary.before_filtering.read_mean_length - ] - - it.subMap("fastplong_json") + ] - + it.subMap("fastplong_json") } .branch { it -> @@ -221,11 +222,12 @@ workflow PREPARE { .mix(JELLYFISH.out.versions) .set { versions } + fastplong_json_reports = HIFI.out.fastplong_hifi_reports.mix(ONT.out.fastplong_ont_reports) + emit: ch_main = main_out + fastplong_json_reports meryl_kmers - nanoq_stats = ONT.out.nanoq_stats - nanoq_report = ONT.out.nanoq_report genomescope_summary genomescope_plot versions diff --git a/workflows/genomeassembler.nf b/workflows/genomeassembler.nf index acb821cd..17db0ee8 100644 --- a/workflows/genomeassembler.nf +++ b/workflows/genomeassembler.nf @@ -107,7 +107,7 @@ workflow GENOMEASSEMBLER { Channel .of([]) .tap { quast_files } - .tap { fastplong_files } + .tap { fastplong_reports } .tap { genomescope_files } .map { it -> ["dummy", it] } .tap { busco_files } @@ -174,12 +174,9 @@ workflow GENOMEASSEMBLER { .mix(SCAFFOLD.out.ch_main) .set { ch_main_scaffolded } - PREPARE.out.nanoq_report - .concat( - PREPARE.out.nanoq_stats - ) + PREPARE.out.fastplong_json_reports .collect { it -> it[1] } - .set { nanoq_files } + .set { fasplong_jsons } PREPARE.out.genomescope_summary .concat( @@ -254,10 +251,6 @@ workflow GENOMEASSEMBLER { .collect() .set { report_functions } - if(!params.merqury) { - - } - ch_main .collect { it -> it.quast ?: null } .map { it -> it.any { it2 -> it2 == true ?: false } } @@ -277,7 +270,7 @@ workflow GENOMEASSEMBLER { REPORT( report_files, report_functions, - nanoq_files, + fasplong_jsons, genomescope_files, quast_files, busco_files, From e745fd198ed45cd5e965e054dd248c4b74f33112 Mon Sep 17 00:00:00 2001 From: Niklas Schandry Date: Fri, 8 Aug 2025 10:30:36 +0200 Subject: [PATCH 037/162] bugfixes and schema update --- modules.json | 6 ++ modules/local/report/main.nf | 31 ------- modules/nf-core/fastplong/fastplong.diff | 19 ++++ modules/nf-core/fastplong/main.nf | 2 +- nextflow_schema.json | 87 +++++++++---------- subworkflows/local/prepare/jellyfish/main.nf | 5 +- subworkflows/local/prepare/main.nf | 7 +- .../local/prepare/prepare_hifi/main.nf | 2 +- .../local/prepare/prepare_ont/main.nf | 2 +- .../main.nf | 2 +- 10 files changed, 76 insertions(+), 87 deletions(-) create mode 100644 modules/nf-core/fastplong/fastplong.diff diff --git a/modules.json b/modules.json index 6a7b4a51..06cce6b2 100644 --- a/modules.json +++ b/modules.json @@ -13,6 +13,12 @@ "fastplong": { "branch": "master", "git_sha": "88869fced9dc0c56043eff2dc748a5f47c57495d", + "installed_by": ["modules"], + "patch": "modules/nf-core/fastplong/fastplong.diff" + }, + "fastqc": { + "branch": "master", + "git_sha": "41dfa3f7c0ffabb96a6a813fe321c6d1cc5b6e46", "installed_by": ["modules"] }, "flye": { diff --git a/modules/local/report/main.nf b/modules/local/report/main.nf index ee21952e..51db1887 100644 --- a/modules/local/report/main.nf +++ b/modules/local/report/main.nf @@ -95,34 +95,3 @@ process REPORT { END_VERSIONS """ } -library(magrittr) -library(tidyjson) -library(dplyr) -library(readr) - -# Read a nanoq json report -read_fastplong <- function(file) { - sample_name <- file %>% str_extract('(?<=fastplong/).+?(?=_(ont|hifi)\\.fastplong\\.json)') - read_type <- file %>% - str_extract('(?<=fastplong/).*') %>% - str_extract('_(ont|hifi)\\.') %>% - str_remove_all("_|\\.") - read_json(file) %>% - enter_object("summary") %>% - tidyjson::spread_all() %>% - as_tibble() %>% - select(-document.id,-fastplong_version) %>% - pivot_longer(everything(), - names_to = c("stage", "stat"), - names_pattern = "(.*)\\.(.*)") %>% - mutate(sample = sample_name) %>% - mutate( - stat = stat %>% - str_replace_all("_", " ") %>% - str_to_title(), - stage = stage %>% - str_replace_all("_", " ") %>% - str_to_title(), - read_type = read_type - ) -} diff --git a/modules/nf-core/fastplong/fastplong.diff b/modules/nf-core/fastplong/fastplong.diff new file mode 100644 index 00000000..68a80481 --- /dev/null +++ b/modules/nf-core/fastplong/fastplong.diff @@ -0,0 +1,19 @@ +Changes in component 'nf-core/fastplong' +'modules/nf-core/fastplong/environment.yml' is unchanged +'modules/nf-core/fastplong/meta.yml' is unchanged +Changes in 'fastplong/main.nf': +--- modules/nf-core/fastplong/main.nf ++++ modules/nf-core/fastplong/main.nf +@@ -33,7 +33,7 @@ + def report_title = task.ext.report_title ?: "${prefix}_fastplong_report" + """ + fastplong \\ +- --in ${prefix}.fastq.gz \\ ++ --in ${reads} \\ + $output_file \\ + --json ${prefix}.fastplong.json \\ + --html ${prefix}.fastplong.html \\ + +'modules/nf-core/fastplong/tests/main.nf.test' is unchanged +'modules/nf-core/fastplong/tests/main.nf.test.snap' is unchanged +************************************************************ diff --git a/modules/nf-core/fastplong/main.nf b/modules/nf-core/fastplong/main.nf index 1c324e14..b672c113 100644 --- a/modules/nf-core/fastplong/main.nf +++ b/modules/nf-core/fastplong/main.nf @@ -33,7 +33,7 @@ process FASTPLONG { def report_title = task.ext.report_title ?: "${prefix}_fastplong_report" """ fastplong \\ - --in ${prefix}.fastq.gz \\ + --in ${reads} \\ $output_file \\ --json ${prefix}.fastplong.json \\ --html ${prefix}.fastplong.html \\ diff --git a/nextflow_schema.json b/nextflow_schema.json index 5eff5d0c..c4543342 100644 --- a/nextflow_schema.json +++ b/nextflow_schema.json @@ -216,41 +216,62 @@ } } }, - "ont_read_options": { - "title": "ONT read options", + "long_read_preprocessing": { + "title": "Long-read preprocessing", "type": "object", - "description": "Options for ONT reads", + "description": "", "default": "", "properties": { "ontreads": { "type": "string", "description": "Path to ONT reads" }, + "ont_collect": { + "type": "boolean", + "description": "Collect ONT reads from several files?" + }, "ont_trim": { "type": "boolean", - "description": "Trim ont reads with porechop?" + "description": "Trim ont reads with fastplong?" + }, + "ont_adapters": { + "type": "string", + "default": "[]", + "description": "Adaptors for ONT read-trimming" + }, + "ont_fastplong_args": { + "type": "string", + "description": "Additional args to be passed to fastplong for ONT reads" + }, + "hifireads": { + "type": "string", + "description": "Path to HiFi reads" + }, + "hifi_trim": { + "type": "boolean", + "description": "Trim HiFi reads with fastplonng" + }, + "hifi_adapters": { + "type": "string", + "default": "[]", + "description": "Adaptors for HiFi read-trimming" }, - "ont_jellyfish": { + "hifi_fastplong_args": { + "type": "string", + "description": "Additional args to be passed to fastplong for HiFi reads" + }, + "jellyfish": { "type": "boolean", - "description": "Run jellyfish on ONT reads (k-mer analysis)" + "description": "Run jellyfish and genomescope (recommended)" }, - "ont_jellyfish_k": { + "jellyfish_k": { "type": "integer", "default": 21, - "description": "k-mer size for jellyfish" - }, - "read_length": { - "type": "integer", - "description": "read length for genomescope (ONT only)", - "minimum": 1 + "description": "Value of k used during k-mer analysis with jellyfish" }, "dump": { "type": "boolean", "description": "dump jellyfish output" - }, - "ont_collect": { - "type": "boolean", - "description": "Collect ONT reads from several files?" } } }, @@ -294,26 +315,6 @@ } } }, - "hifi_read_options": { - "title": "HiFi read options", - "type": "object", - "description": "Options for HiFi reads", - "default": "", - "properties": { - "hifireads": { - "type": "string", - "description": "Path to HiFi reads" - }, - "hifi_trim": { - "type": "boolean", - "description": "Trim HiFi reads with lima" - }, - "hifi_primers": { - "type": "string", - "description": "Primers to use with lima" - } - } - }, "qc_options": { "title": "QC options", "type": "object", @@ -384,12 +385,11 @@ "properties": { "use_short_reads": { "type": "boolean", - "description": "Use short reads?", - "hidden": true + "description": "Use short reads?" }, - "trim_short_reads": { + "shortread_trim": { "type": "boolean", - "description": "trim short reads with trimgalore" + "description": "Trim short reads?" }, "meryl_k": { "type": "integer", @@ -429,7 +429,7 @@ "$ref": "#/$defs/assembly_options" }, { - "$ref": "#/$defs/ont_read_options" + "$ref": "#/$defs/long_read_preprocessing" }, { "$ref": "#/$defs/polishing_options" @@ -437,9 +437,6 @@ { "$ref": "#/$defs/scaffolding_options" }, - { - "$ref": "#/$defs/hifi_read_options" - }, { "$ref": "#/$defs/qc_options" }, diff --git a/subworkflows/local/prepare/jellyfish/main.nf b/subworkflows/local/prepare/jellyfish/main.nf index 8f437b4a..b863a7fe 100644 --- a/subworkflows/local/prepare/jellyfish/main.nf +++ b/subworkflows/local/prepare/jellyfish/main.nf @@ -16,8 +16,7 @@ workflow JELLYFISH { it -> [ [id: it.meta.id, jellyfish_k: it.jellyfish_k], - it.qc_reads_path, - it.qc_read_mean + it.qc_reads_path ] } .set { samples } @@ -42,7 +41,7 @@ workflow JELLYFISH { [ it.meta, it.jellyfish_k, - it.qc_read_length + it.qc_read_mean ] } ) diff --git a/subworkflows/local/prepare/main.nf b/subworkflows/local/prepare/main.nf index 0f46b151..3524ca8c 100644 --- a/subworkflows/local/prepare/main.nf +++ b/subworkflows/local/prepare/main.nf @@ -174,7 +174,7 @@ workflow PREPARE { def slurp = new groovy.json.JsonSlurper() ch_main_prepared - .filter(it.qc_reads.toLower() == "ont") + .filter { it.qc_reads.toLowerCase() == "ont" } .map { it -> it.collect { entry -> [ entry.value, entry ] } } .join(ONT.out.fastplong_ont_reports .map { it -> [ meta: it[0], fastplong_json: it[1] ]} @@ -182,7 +182,7 @@ workflow PREPARE { ) .mix( ch_main_prepared - .filter(it.qc_reads.toLower() == "hifi") + .filter { it.qc_reads.toLowerCase() == "hifi" } .map { it -> it.collect { entry -> [ entry.value, entry ] } } .join(HIFI.out.fastplong_hifi_reports .map { it -> [ meta: it[0], fastplong_json: it[1] ]} @@ -205,8 +205,6 @@ workflow PREPARE { } .set { ch_main_jellyfish_branched } - - JELLYFISH(ch_main_jellyfish_branched.jelly) ch_main_jellyfish_branched.no_jelly @@ -214,6 +212,7 @@ workflow PREPARE { .set { main_out } JELLYFISH.out.genomescope_summary.set { genomescope_summary } + JELLYFISH.out.genomescope_plot.set { genomescope_plot } SHORTREADS.out.versions diff --git a/subworkflows/local/prepare/prepare_hifi/main.nf b/subworkflows/local/prepare/prepare_hifi/main.nf index 66c5ebd9..6deb0ab7 100644 --- a/subworkflows/local/prepare/prepare_hifi/main.nf +++ b/subworkflows/local/prepare/prepare_hifi/main.nf @@ -42,7 +42,7 @@ workflow PREPARE_HIFI { .multiMap { it -> reads: [it.meta, it.hifireads] - adapters: it.hifi_adapters + adapters: it.hifi_adapters ?: [] } .set { ch_fastplong_in } diff --git a/subworkflows/local/prepare/prepare_ont/main.nf b/subworkflows/local/prepare/prepare_ont/main.nf index 62ce90ff..b3ccafdd 100644 --- a/subworkflows/local/prepare/prepare_ont/main.nf +++ b/subworkflows/local/prepare/prepare_ont/main.nf @@ -102,7 +102,7 @@ workflow PREPARE_ONT { .multiMap { it -> reads: [it.meta, it.ontreads] - adapters: it.ont_adapters + adapters: it.ont_adapters ?: [] } .set { ch_fastplong_in } diff --git a/subworkflows/local/utils_nfcore_genomeassembler_pipeline/main.nf b/subworkflows/local/utils_nfcore_genomeassembler_pipeline/main.nf index a0500538..a654171f 100644 --- a/subworkflows/local/utils_nfcore_genomeassembler_pipeline/main.nf +++ b/subworkflows/local/utils_nfcore_genomeassembler_pipeline/main.nf @@ -236,7 +236,7 @@ workflow PIPELINE_INITIALISATION { ] : null, // Check if genome_size is given with --scaffold_longstitch - (it.scaffold_longstitch && !it.genome_size && !(it.ontreads && params.jellyfish)) + (it.scaffold_longstitch && !it.genome_size && !(params.jellyfish || it.jellyfish)) ? [ println("Please confirm samplesheet: [sample: $it.meta.id]: scaffolding with longstitch requires genome-size. Either provide genome-size estimate, or estimate from ONT reads with --jellyfish"), From 406e2e996885e9ab653e4393b8d50a43a081e1a6 Mon Sep 17 00:00:00 2001 From: Niklas Schandry Date: Tue, 26 Aug 2025 13:46:13 +0200 Subject: [PATCH 038/162] report updating --- assets/report/functions/read_fastplong.R | 31 +++++ docs/usage.md | 161 ++++++++++++----------- params.md | 133 +++++++++---------- 3 files changed, 176 insertions(+), 149 deletions(-) diff --git a/assets/report/functions/read_fastplong.R b/assets/report/functions/read_fastplong.R index e69de29b..475cba44 100644 --- a/assets/report/functions/read_fastplong.R +++ b/assets/report/functions/read_fastplong.R @@ -0,0 +1,31 @@ +library(magrittr) +library(tidyjson) +library(dplyr) +library(readr) + +# Read a nanoq json report +read_fastplong <- function(file) { + sample_name <- file %>% str_extract('(?<=fastplong/).+?(?=_(ont|hifi)\\.fastplong\\.json)') + read_type <- file %>% + str_extract('(?<=fastplong/).*') %>% + str_extract('_(ont|hifi)\\.') %>% + str_remove_all("_|\\.") + read_json(file) %>% + enter_object("summary") %>% + tidyjson::spread_all() %>% + as_tibble() %>% + select(-document.id,-fastplong_version) %>% + pivot_longer(everything(), + names_to = c("stage", "stat"), + names_pattern = "(.*)\\.(.*)") %>% + mutate(sample = sample_name) %>% + mutate( + stat = stat %>% + str_replace_all("_", " ") %>% + str_to_title(), + stage = stage %>% + str_replace_all("_", " ") %>% + str_to_title(), + read_type = read_type + ) +} diff --git a/docs/usage.md b/docs/usage.md index f4092f74..770eafb0 100644 --- a/docs/usage.md +++ b/docs/usage.md @@ -251,40 +251,19 @@ NXF_OPTS='-Xms1g -Xmx4g' Assemble genomes from long ONT or pacbio HiFi reads -## Global parameters - -### Input/output options +## Input/output options Define where the pipeline should find input data and save output data. -| Parameter | Description | Type | Default | Required | Hidden | -| ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | -------- | ------- | -------- | ------ | -| `input` | Path to comma-separated file containing information about the samples in the experiment.
HelpYou 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/genomeassembler/usage#samplesheet-input).
| `string` | | True | | -| `outdir` | The output directory where the results will be saved. You have to use absolute paths to storage on Cloud infrastructure. | `string` | | True | | -| `email` | Email address for completion summary.
HelpSet this parameter to your e-mail address to get a summary e-mail with details of the run sent to you when the workflow exits. If set in your user config file | -| (`~/.nextflow/config`) then you don't need to specify this on the command line for every run.
| `string` | | | | - -### Generic options - -Less common options for the pipeline, typically set in a config file. - -| Parameter | Description | Type | Default | Required | Hidden | -| ---------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------- | -------------------------------------------------------- | -------- | ------ | -| `version` | Display version and exit. | `boolean` | | | True | -| `publish_dir_mode` | Method used to save pipeline results to output directory.
HelpThe Nextflow `publishDir` option specifies which intermediate files should be saved to the output directory. This option tells the pipeline what | -| method should be used to move these files. See [Nextflow docs](https://www.nextflow.io/docs/latest/process.html#publishdir) for details.
| `string` | copy | | True | -| `email_on_fail` | Email address for completion summary, only when pipeline fails.
HelpAn email address to send a summary email to when the pipeline is completed - ONLY sent if the pipeline does not exit | -| successfully.
| `string` | | | True | -| `plaintext_email` | Send plain-text email instead of HTML. | `boolean` | | | True | -| `monochrome_logs` | Do not use coloured log outputs. | `boolean` | | | True | -| `hook_url` | Incoming hook URL for messaging service
HelpIncoming hook URL for messaging service. Currently, MS Teams and Slack are supported.
| `string` | | | True | -| `validate_params` | Boolean whether to validate parameters against the schema at runtime | `boolean` | True | | True | -| `pipelines_testdata_base_path` | Base URL or local path to location of pipeline test dataset files | `string` | https://raw.githubusercontent.com/nf-core/test-datasets/ | | True | - -## Sample Parameters +| Parameter | Description | Type | Default | Required | Hidden | +| ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------- | ------- | -------- | ------ | +| `input` | Path to comma-separated file containing information about the samples in the experiment.
HelpYou 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/genomeassembler/usage#samplesheet-input).
| `string` | | True | | +| `outdir` | The output directory where the results will be saved. You have to use absolute paths to storage on Cloud infrastructure. | `string` | | True | | +| `email` | Email address for completion summary.
HelpSet this parameter to your e-mail address to get a summary e-mail with details of the run sent to you when the workflow exits. If set in your user config file | +| (`~/.nextflow/config`) then you don't need to specify this on the command line for every run.
| `string` | | | | -### Reference Parameters +## Reference Parameters Options controlling pipeline behavior @@ -294,36 +273,39 @@ Options controlling pipeline behavior | `ref_gff` | Path to reference genome annotations (gff) | `string` | | | | | `use_ref` | use reference genome | `boolean` | | | True | -### Assembly options +## Assembly options Options controlling assembly -| Parameter | Description | Type | Default | Required | Hidden | -| -------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | --------- | ----------- | -------- | ------ | -| `strategy` | Assembly strategy to use. Valid choices are `'single'`, `'hybrid'` and `'scaffold'` | `string` | single | | | -| `assembler` | Assembler to use. Valid choices are: `'hifiasm'`, `'flye'`, `'flye_on_hifiasm'` or `hifiasm_on_hifiasm`. `flye_on_hifiasm` will scaffold flye assembly (ont) on hifiasm (hifi) assembly using ragtag. `hifiasm_on_hifiasm` will scaffold hifiasm (ont) | -| onto hifiasm (HiFi) using ragtag | `string` | hifiasm | | | -| `genome_size` | expected genome size, optional | `integer` | | | | -| `flye_mode` | flye mode | `string` | --nano-hq | | | -| `flye_args` | additional args for flye | `string` | | | | -| `hifiasm_args` | Extra arguments passed to `hifiasm` | `string` | | | | -| `assembly_scaffolding_order` | When strategy is "scaffold", which assembly should be scaffolded onto which? | `string` | ont_on_hifi | | | - -### ONT read options - -Options for ONT reads - -| Parameter | Description | Type | Default | Required | Hidden | -| ----------------- | ------------------------------------------- | --------- | ------- | -------- | ------ | -| `ontreads` | Path to ONT reads | `string` | | | | -| `ont_trim` | Trim ont reads with porechop? | `boolean` | | | | -| `ont_jellyfish` | Run jellyfish on ONT reads (k-mer analysis) | `boolean` | | | | -| `ont_jellyfish_k` | k-mer size for jellyfish | `integer` | 21 | | | -| `read_length` | read length for genomescope (ONT only) | `integer` | | | | -| `dump` | dump jellyfish output | `boolean` | | | | -| `ont_collect` | Collect ONT reads from several files? | `boolean` | | | | - -### Polishing options +| Parameter | Description | Type | Default | Required | Hidden | +| -------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | --------- | ----------- | -------- | ------ | +| `strategy` | Assembly strategy to use. Valid choices are `'single'`, `'hybrid'` and `'scaffold'` | `string` | single | | | +| `assembler` | Assembler to use. Valid choices are: `'hifiasm'`, `'flye'`, `'flye_on_hifiasm'` or `hifiasm_on_hifiasm`. `flye_on_hifiasm` will scaffold flye assembly (ont) on hifiasm (hifi) assembly using ragtag. `hifiasm_on_hifiasm` will scaffold hifiasm | +| (ont) onto hifiasm (HiFi) using ragtag | `string` | hifiasm | | | +| `genome_size` | expected genome size, optional | `integer` | | | | +| `flye_mode` | flye mode | `string` | --nano-hq | | | +| `flye_args` | additional args for flye | `string` | | | | +| `hifiasm_args` | Extra arguments passed to `hifiasm` | `string` | | | | +| `assembly_scaffolding_order` | When strategy is "scaffold", which assembly should be scaffolded onto which? | `string` | ont_on_hifi | | | + +## Long-read preprocessing + +| Parameter | Description | Type | Default | Required | Hidden | +| --------------------- | -------------------------------------------------------- | --------- | ------- | -------- | ------ | +| `ontreads` | Path to ONT reads | `string` | | | | +| `ont_collect` | Collect ONT reads from several files? | `boolean` | | | | +| `ont_trim` | Trim ont reads with fastplong? | `boolean` | | | | +| `ont_adapters` | Adaptors for ONT read-trimming | `string` | [] | | | +| `ont_fastplong_args` | Additional args to be passed to fastplong for ONT reads | `string` | | | | +| `hifireads` | Path to HiFi reads | `string` | | | | +| `hifi_trim` | Trim HiFi reads with fastplonng | `boolean` | | | | +| `hifi_adapters` | Adaptors for HiFi read-trimming | `string` | [] | | | +| `hifi_fastplong_args` | Additional args to be passed to fastplong for HiFi reads | `string` | | | | +| `jellyfish` | Run jellyfish and genomescope (recommended) | `boolean` | | | | +| `jellyfish_k` | Value of k used during k-mer analysis with jellyfish | `integer` | 21 | | | +| `dump` | dump jellyfish output | `boolean` | | | | + +## Polishing options Polishing options @@ -333,7 +315,7 @@ Polishing options | `polish_medaka` | Polish assembly with medaka (ONT only) | `boolean` | | | | | `medaka_model` | model to use with medaka | `string` | | | | -### Scaffolding options +## Scaffolding options Scaffolding options @@ -343,17 +325,7 @@ Scaffolding options | `scaffold_links` | Scaffolding with links? | `boolean` | | | | | `scaffold_ragtag` | Scaffold with ragtag (requires reference)? | `boolean` | | | | -### HiFi read options - -Options for HiFi reads - -| Parameter | Description | Type | Default | Required | Hidden | -| -------------- | ------------------------- | --------- | ------- | -------- | ------ | -| `hifireads` | Path to HiFi reads | `string` | | | | -| `hifi_trim` | Trim HiFi reads with lima | `boolean` | | | | -| `hifi_primers` | Primers to use with lima | `string` | | | | - -### QC options +## QC options Options for QC tools @@ -369,7 +341,7 @@ Options for QC tools | `assembly` | Can be used to proved existing assembly will skip assembly and perform downstream steps including qc | `string` | | | | | `assembly_map_bam` | A mapping (bam) of reads mapped to the provided assembly can be specified for QC. If provided alignment to the provided assembly fasta will not run | `string` | | | | -### Annotations options +## Annotations options Options controlling annotation liftover @@ -377,15 +349,46 @@ Options controlling annotation liftover | ------------------ | ------------------------------------------- | --------- | ------- | -------- | ------ | | `lift_annotations` | Lift-over annotations (requires reference)? | `boolean` | True | | | -### Short read options +## Short read options Options for short reads -| Parameter | Description | Type | Default | Required | Hidden | -| ------------------ | -------------------------------- | --------- | ------- | -------- | ------ | -| `use_short_reads` | Use short reads? | `boolean` | | | True | -| `trim_short_reads` | trim short reads with trimgalore | `boolean` | | | | -| `meryl_k` | kmer length for meryl / merqury | `integer` | 21 | | | -| `shortread_F` | Path to forward short reads | `string` | | | | -| `shortread_R` | Path to reverse short reads | `string` | | | | -| `paired` | Are shortreads paired? | `string` | | | | +| Parameter | Description | Type | Default | Required | Hidden | +| ----------------- | ------------------------------- | --------- | ------- | -------- | ------ | +| `use_short_reads` | Use short reads? | `boolean` | | | | +| `shortread_trim` | Trim short reads? | `boolean` | | | | +| `meryl_k` | kmer length for meryl / merqury | `integer` | 21 | | | +| `shortread_F` | Path to forward short reads | `string` | | | | +| `shortread_R` | Path to reverse short reads | `string` | | | | +| `paired` | Are shortreads paired? | `string` | | | | + +## Institutional config options + +Parameters used to describe centralised config profiles. These should not be edited. + +| Parameter | Description | Type | Default | Required | Hidden | +| ------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------- | ------- | -------- | ------ | +| `custom_config_version` | Git commit id for Institutional configs. | `string` | master | | True | +| `custom_config_base` | Base directory for Institutional configs.
HelpIf you're running offline, Nextflow will not be able to fetch the institutional config files from the internet. If you don't need them, then this is not | +| a problem. If you do need them, you should download the files from the repo and tell Nextflow where to find them with this parameter.
| `string` | https://raw.githubusercontent.com/nf-core/configs/master | | True | +| `config_profile_name` | Institutional config name. | `string` | | | True | +| `config_profile_description` | Institutional config description. | `string` | | | True | +| `config_profile_contact` | Institutional config contact information. | `string` | | | True | +| `config_profile_url` | Institutional config URL link. | `string` | | | True | + +## Generic options + +Less common options for the pipeline, typically set in a config file. + +| Parameter | Description | Type | Default | Required | Hidden | +| --------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------- | -------------------------------------------------------- | -------- | ------ | +| `version` | Display version and exit. | `boolean` | | | True | +| `publish_dir_mode` | Method used to save pipeline results to output directory.
HelpThe Nextflow `publishDir` option specifies which intermediate files should be saved to the output directory. This option tells the pipeline | +| what method should be used to move these files. See [Nextflow docs](https://www.nextflow.io/docs/latest/process.html#publishdir) for details.
| `string` | copy | | True | +| `email_on_fail` | Email address for completion summary, only when pipeline fails.
HelpAn email address to send a summary email to when the pipeline is completed - ONLY sent if the pipeline does not exit | +| successfully.
| `string` | | | True | +| `plaintext_email` | Send plain-text email instead of HTML. | `boolean` | | | True | +| `monochrome_logs` | Do not use coloured log outputs. | `boolean` | | | True | +| `hook_url` | Incoming hook URL for messaging service
HelpIncoming hook URL for messaging service. Currently, MS Teams and Slack are supported.
| `string` | | | True | +| `validate_params` | Boolean whether to validate parameters against the schema at runtime | `boolean` | True | | True | +| `pipelines_testdata_base_path` | Base URL or local path to location of pipeline test dataset files | `string` | https://raw.githubusercontent.com/nf-core/test-datasets/ | | True | diff --git a/params.md b/params.md index c29f09a3..febf1f10 100644 --- a/params.md +++ b/params.md @@ -6,44 +6,44 @@ Assemble genomes from long ONT or pacbio HiFi reads Define where the pipeline should find input data and save output data. -| Parameter | Description | Type | Default | Required | Hidden | -| ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | -------- | ------- | -------- | ------ | -| `input` | Path to comma-separated file containing information about the samples in the experiment.
HelpYou 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/genomeassembler/usage#samplesheet-input).
| `string` | | True | | -| `outdir` | The output directory where the results will be saved. You have to use absolute paths to storage on Cloud infrastructure. | `string` | | True | | -| `email` | Email address for completion summary.
HelpSet this parameter to your e-mail address to get a summary e-mail with details of the run sent to you when the workflow exits. If set in your user config file | -| (`~/.nextflow/config`) then you don't need to specify this on the command line for every run.
| `string` | | | | +| Parameter | Description | Type | Default | Required | Hidden | +| ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------- | ------- | -------- | ------ | +| `input` | Path to comma-separated file containing information about the samples in the experiment.
HelpYou 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/genomeassembler/usage#samplesheet-input).
| `string` | | True | | +| `outdir` | The output directory where the results will be saved. You have to use absolute paths to storage on Cloud infrastructure. | `string` | | True | | +| `email` | Email address for completion summary.
HelpSet this parameter to your e-mail address to get a summary e-mail with details of the run sent to you when the workflow exits. If set in your user config file | +| (`~/.nextflow/config`) then you don't need to specify this on the command line for every run.
| `string` | | | | ## Institutional config options Parameters used to describe centralised config profiles. These should not be edited. -| Parameter | Description | Type | Default | Required | Hidden | -| ----------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------- | ------- | -------- | ------ | -| `custom_config_version` | Git commit id for Institutional configs. | `string` | master | | True | -| `custom_config_base` | Base directory for Institutional configs.
HelpIf you're running offline, Nextflow will not be able to fetch the institutional config files from the internet. If you don't need them, then this is not a | -| problem. If you do need them, you should download the files from the repo and tell Nextflow where to find them with this parameter.
| `string` | https://raw.githubusercontent.com/nf-core/configs/master | | True | -| `config_profile_name` | Institutional config name. | `string` | | | True | -| `config_profile_description` | Institutional config description. | `string` | | | True | -| `config_profile_contact` | Institutional config contact information. | `string` | | | True | -| `config_profile_url` | Institutional config URL link. | `string` | | | True | +| Parameter | Description | Type | Default | Required | Hidden | +| ------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------- | ------- | -------- | ------ | +| `custom_config_version` | Git commit id for Institutional configs. | `string` | master | | True | +| `custom_config_base` | Base directory for Institutional configs.
HelpIf you're running offline, Nextflow will not be able to fetch the institutional config files from the internet. If you don't need them, then this is not | +| a problem. If you do need them, you should download the files from the repo and tell Nextflow where to find them with this parameter.
| `string` | https://raw.githubusercontent.com/nf-core/configs/master | | True | +| `config_profile_name` | Institutional config name. | `string` | | | True | +| `config_profile_description` | Institutional config description. | `string` | | | True | +| `config_profile_contact` | Institutional config contact information. | `string` | | | True | +| `config_profile_url` | Institutional config URL link. | `string` | | | True | ## Generic options Less common options for the pipeline, typically set in a config file. -| Parameter | Description | Type | Default | Required | Hidden | -| ---------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------- | -------------------------------------------------------- | -------- | ------ | -| `version` | Display version and exit. | `boolean` | | | True | -| `publish_dir_mode` | Method used to save pipeline results to output directory.
HelpThe Nextflow `publishDir` option specifies which intermediate files should be saved to the output directory. This option tells the pipeline what | -| method should be used to move these files. See [Nextflow docs](https://www.nextflow.io/docs/latest/process.html#publishdir) for details.
| `string` | copy | | True | -| `email_on_fail` | Email address for completion summary, only when pipeline fails.
HelpAn email address to send a summary email to when the pipeline is completed - ONLY sent if the pipeline does not exit | -| successfully.
| `string` | | | True | -| `plaintext_email` | Send plain-text email instead of HTML. | `boolean` | | | True | -| `monochrome_logs` | Do not use coloured log outputs. | `boolean` | | | True | -| `hook_url` | Incoming hook URL for messaging service
HelpIncoming hook URL for messaging service. Currently, MS Teams and Slack are supported.
| `string` | | | True | -| `validate_params` | Boolean whether to validate parameters against the schema at runtime | `boolean` | True | | True | -| `pipelines_testdata_base_path` | Base URL or local path to location of pipeline test dataset files | `string` | https://raw.githubusercontent.com/nf-core/test-datasets/ | | True | +| Parameter | Description | Type | Default | Required | Hidden | +| --------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------- | -------------------------------------------------------- | -------- | ------ | +| `version` | Display version and exit. | `boolean` | | | True | +| `publish_dir_mode` | Method used to save pipeline results to output directory.
HelpThe Nextflow `publishDir` option specifies which intermediate files should be saved to the output directory. This option tells the pipeline | +| what method should be used to move these files. See [Nextflow docs](https://www.nextflow.io/docs/latest/process.html#publishdir) for details.
| `string` | copy | | True | +| `email_on_fail` | Email address for completion summary, only when pipeline fails.
HelpAn email address to send a summary email to when the pipeline is completed - ONLY sent if the pipeline does not exit | +| successfully.
| `string` | | | True | +| `plaintext_email` | Send plain-text email instead of HTML. | `boolean` | | | True | +| `monochrome_logs` | Do not use coloured log outputs. | `boolean` | | | True | +| `hook_url` | Incoming hook URL for messaging service
HelpIncoming hook URL for messaging service. Currently, MS Teams and Slack are supported.
| `string` | | | True | +| `validate_params` | Boolean whether to validate parameters against the schema at runtime | `boolean` | True | | True | +| `pipelines_testdata_base_path` | Base URL or local path to location of pipeline test dataset files | `string` | https://raw.githubusercontent.com/nf-core/test-datasets/ | | True | ## Reference Parameters @@ -59,30 +59,33 @@ Options controlling pipeline behavior Options controlling assembly -| Parameter | Description | Type | Default | Required | Hidden | -| -------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | --------- | ----------- | -------- | ------ | -| `strategy` | Assembly strategy to use. Valid choices are `'single'`, `'hybrid'` and `'scaffold'` | `string` | single | | | -| `assembler` | Assembler to use. Valid choices are: `'hifiasm'`, `'flye'`, `'flye_on_hifiasm'` or `hifiasm_on_hifiasm`. `flye_on_hifiasm` will scaffold flye assembly (ont) on hifiasm (hifi) assembly using ragtag. `hifiasm_on_hifiasm` will scaffold hifiasm (ont) | -| onto hifiasm (HiFi) using ragtag | `string` | hifiasm | | | -| `genome_size` | expected genome size, optional | `integer` | | | | -| `flye_mode` | flye mode | `string` | --nano-hq | | | -| `flye_args` | additional args for flye | `string` | | | | -| `hifiasm_args` | Extra arguments passed to `hifiasm` | `string` | | | | -| `assembly_scaffolding_order` | When strategy is "scaffold", which assembly should be scaffolded onto which? | `string` | ont_on_hifi | | | - -## ONT read options - -Options for ONT reads - -| Parameter | Description | Type | Default | Required | Hidden | -| ----------------- | ------------------------------------------- | --------- | ------- | -------- | ------ | -| `ontreads` | Path to ONT reads | `string` | | | | -| `ont_trim` | Trim ont reads with porechop? | `boolean` | | | | -| `ont_jellyfish` | Run jellyfish on ONT reads (k-mer analysis) | `boolean` | | | | -| `ont_jellyfish_k` | k-mer size for jellyfish | `integer` | 21 | | | -| `read_length` | read length for genomescope (ONT only) | `integer` | | | | -| `dump` | dump jellyfish output | `boolean` | | | | -| `ont_collect` | Collect ONT reads from several files? | `boolean` | | | | +| Parameter | Description | Type | Default | Required | Hidden | +| -------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | --------- | ----------- | -------- | ------ | +| `strategy` | Assembly strategy to use. Valid choices are `'single'`, `'hybrid'` and `'scaffold'` | `string` | single | | | +| `assembler` | Assembler to use. Valid choices are: `'hifiasm'`, `'flye'`, `'flye_on_hifiasm'` or `hifiasm_on_hifiasm`. `flye_on_hifiasm` will scaffold flye assembly (ont) on hifiasm (hifi) assembly using ragtag. `hifiasm_on_hifiasm` will scaffold hifiasm | +| (ont) onto hifiasm (HiFi) using ragtag | `string` | hifiasm | | | +| `genome_size` | expected genome size, optional | `integer` | | | | +| `flye_mode` | flye mode | `string` | --nano-hq | | | +| `flye_args` | additional args for flye | `string` | | | | +| `hifiasm_args` | Extra arguments passed to `hifiasm` | `string` | | | | +| `assembly_scaffolding_order` | When strategy is "scaffold", which assembly should be scaffolded onto which? | `string` | ont_on_hifi | | | + +## Long-read preprocessing + +| Parameter | Description | Type | Default | Required | Hidden | +| --------------------- | -------------------------------------------------------- | --------- | ------- | -------- | ------ | +| `ontreads` | Path to ONT reads | `string` | | | | +| `ont_collect` | Collect ONT reads from several files? | `boolean` | | | | +| `ont_trim` | Trim ont reads with fastplong? | `boolean` | | | | +| `ont_adapters` | Adaptors for ONT read-trimming | `string` | [] | | | +| `ont_fastplong_args` | Additional args to be passed to fastplong for ONT reads | `string` | | | | +| `hifireads` | Path to HiFi reads | `string` | | | | +| `hifi_trim` | Trim HiFi reads with fastplonng | `boolean` | | | | +| `hifi_adapters` | Adaptors for HiFi read-trimming | `string` | [] | | | +| `hifi_fastplong_args` | Additional args to be passed to fastplong for HiFi reads | `string` | | | | +| `jellyfish` | Run jellyfish and genomescope (recommended) | `boolean` | | | | +| `jellyfish_k` | Value of k used during k-mer analysis with jellyfish | `integer` | 21 | | | +| `dump` | dump jellyfish output | `boolean` | | | | ## Polishing options @@ -104,16 +107,6 @@ Scaffolding options | `scaffold_links` | Scaffolding with links? | `boolean` | | | | | `scaffold_ragtag` | Scaffold with ragtag (requires reference)? | `boolean` | | | | -## HiFi read options - -Options for HiFi reads - -| Parameter | Description | Type | Default | Required | Hidden | -| -------------- | ------------------------- | --------- | ------- | -------- | ------ | -| `hifireads` | Path to HiFi reads | `string` | | | | -| `hifi_trim` | Trim HiFi reads with lima | `boolean` | | | | -| `hifi_primers` | Primers to use with lima | `string` | | | | - ## QC options Options for QC tools @@ -142,11 +135,11 @@ Options controlling annotation liftover Options for short reads -| Parameter | Description | Type | Default | Required | Hidden | -| ------------------ | -------------------------------- | --------- | ------- | -------- | ------ | -| `use_short_reads` | Use short reads? | `boolean` | | | True | -| `trim_short_reads` | trim short reads with trimgalore | `boolean` | | | | -| `meryl_k` | kmer length for meryl / merqury | `integer` | 21 | | | -| `shortread_F` | Path to forward short reads | `string` | | | | -| `shortread_R` | Path to reverse short reads | `string` | | | | -| `paired` | Are shortreads paired? | `string` | | | | +| Parameter | Description | Type | Default | Required | Hidden | +| ----------------- | ------------------------------- | --------- | ------- | -------- | ------ | +| `use_short_reads` | Use short reads? | `boolean` | | | | +| `shortread_trim` | Trim short reads? | `boolean` | | | | +| `meryl_k` | kmer length for meryl / merqury | `integer` | 21 | | | +| `shortread_F` | Path to forward short reads | `string` | | | | +| `shortread_R` | Path to reverse short reads | `string` | | | | +| `paired` | Are shortreads paired? | `string` | | | | From 2dae4812e98ebd19731dcbddba558e72174d0ee0 Mon Sep 17 00:00:00 2001 From: Niklas Schandry Date: Thu, 28 Aug 2025 09:27:29 +0200 Subject: [PATCH 039/162] finish refactoring, update report for fastplong --- assets/report/report.qmd | 13 ++++++------ modules.json | 3 ++- modules/nf-core/links/links.diff | 21 +++++++++++++++++++ modules/nf-core/links/main.nf | 3 ++- subworkflows/local/scaffolding/main.nf | 2 +- .../main.nf | 2 +- 6 files changed, 34 insertions(+), 10 deletions(-) create mode 100644 modules/nf-core/links/links.diff diff --git a/assets/report/report.qmd b/assets/report/report.qmd index 3273fcec..822a541b 100644 --- a/assets/report/report.qmd +++ b/assets/report/report.qmd @@ -38,11 +38,11 @@ color_scale_plots <- scale_color_manual(values = c("#CC6677", "#332288", "#DDCC7 fill_scale_plots <- scale_fill_manual(values = c("#CC6677", "#332288", "#DDCC77", "#117733", "#88CCEE", "#882255", "#44AA99", "#999933", "#AA4499"), na.value = "#DDDDDD") # Base directory containing reports data_base <- "data/" -groups <- yaml::read_yaml("examples/yml/example.yml") %>% +groups <- yaml::read_yaml("groups.yml") %>% map_dfr(\(row) data.frame( - id = pluck(row,1,"id"), - group = pluck(row,1,"group") + sample = pluck(row, 1, "id"), + group = pluck(row, 1, "group", .default = "null") ) ) ``` @@ -69,7 +69,8 @@ fastplong was not included in the pipeline run. fastplong_reports <- list.files(paste0(data_base, "fastplong"), pattern = ".json", full.names = T) %>% - map_dfr(\(x) read_fastplong(x)) + map_dfr(\(x) read_fastplong(x)) %>% + left_join(groups, by = join_by(sample)) ``` ```{r} @@ -120,14 +121,14 @@ for (i in 1:length(unique(fastplong_reports$sample))) { paste0(fastplong_reports %>% filter(stat == "Total Bases", stage == "After Filtering") %>% filter(sample == unique(fastplong_reports$sample)[i],) %$% - sum(val) %>% + sum(value) %>% format(scientific=-1,trim=T, digits = 3, drop0trailing=T),'\n'), paste0(':::', '\n\n'), paste0('::: {.valuebox icon="collection" color="secondary" title="Number of reads"}', '\n'), paste0(fastplong_reports %>% filter(stat == "Total Reads", stage == "After Filtering") %>% filter(sample == unique(fastplong_reports$sample)[i]) %$% - sum(val) %>% + sum(value) %>% paste(" bases"), '\n'), paste0(':::', '\n\n'), paste0('::: {.valuebox icon="chevron-double-up" color="success" title="Q30 rate"}', '\n'), diff --git a/modules.json b/modules.json index 06cce6b2..5d7acd16 100644 --- a/modules.json +++ b/modules.json @@ -40,7 +40,8 @@ "links": { "branch": "master", "git_sha": "bd049fd0244ed914f2d10bed580b49fb44eba914", - "installed_by": ["modules"] + "installed_by": ["modules"], + "patch": "modules/nf-core/links/links.diff" }, "merqury/merqury": { "branch": "master", diff --git a/modules/nf-core/links/links.diff b/modules/nf-core/links/links.diff new file mode 100644 index 00000000..1386c88a --- /dev/null +++ b/modules/nf-core/links/links.diff @@ -0,0 +1,21 @@ +Changes in component 'nf-core/links' +'modules/nf-core/links/environment.yml' is unchanged +'modules/nf-core/links/meta.yml' is unchanged +Changes in 'links/main.nf': +--- modules/nf-core/links/main.nf ++++ modules/nf-core/links/main.nf +@@ -30,7 +30,8 @@ + script: + def prefix = task.ext.prefix ?: "${meta.id}" + // Currently LINKS does not support more than 4 threads +- def nthreads = "${task.cpus}" < 4 ? "${task.cpus}" : 4 ++ def ncpu = "${task.cpus}".toInteger() ++ def nthreads = ncpu < 4 ? ncpu : 4 + def args = task.ext.args ?: "" + """ + if [[ ${assembly} == *.gz ]]; + +'modules/nf-core/links/tests/nextflow.config' is unchanged +'modules/nf-core/links/tests/main.nf.test' is unchanged +'modules/nf-core/links/tests/main.nf.test.snap' is unchanged +************************************************************ diff --git a/modules/nf-core/links/main.nf b/modules/nf-core/links/main.nf index c55bc661..3e3ee19c 100644 --- a/modules/nf-core/links/main.nf +++ b/modules/nf-core/links/main.nf @@ -30,7 +30,8 @@ process LINKS { script: def prefix = task.ext.prefix ?: "${meta.id}" // Currently LINKS does not support more than 4 threads - def nthreads = "${task.cpus}" < 4 ? "${task.cpus}" : 4 + def ncpu = "${task.cpus}".toInteger() + def nthreads = ncpu < 4 ? ncpu : 4 def args = task.ext.args ?: "" """ if [[ ${assembly} == *.gz ]]; diff --git a/subworkflows/local/scaffolding/main.nf b/subworkflows/local/scaffolding/main.nf index 4cb5a538..0194dfae 100644 --- a/subworkflows/local/scaffolding/main.nf +++ b/subworkflows/local/scaffolding/main.nf @@ -50,7 +50,7 @@ workflow SCAFFOLD { ch_main .filter { - it -> it.scaffold_ragag + it -> it.scaffold_ragtag } .set { ragtag_in } diff --git a/subworkflows/local/utils_nfcore_genomeassembler_pipeline/main.nf b/subworkflows/local/utils_nfcore_genomeassembler_pipeline/main.nf index a654171f..591390b5 100644 --- a/subworkflows/local/utils_nfcore_genomeassembler_pipeline/main.nf +++ b/subworkflows/local/utils_nfcore_genomeassembler_pipeline/main.nf @@ -146,7 +146,7 @@ workflow PIPELINE_INITIALISATION { polish_pilon: it.polish_pilon ?: params.polish_pilon, scaffold_longstitch: it.scaffold_longstitch ?: params.scaffold_longstitch, scaffold_links: it.scaffold_longstitch ?: params.scaffold_links, - scaffold_ragtag: it.scaffold_longstitch ?: params.scaffold_ragtag, + scaffold_ragtag: it.scaffold_ragtag ?: params.scaffold_ragtag, use_ref: it.use_ref ?: params.use_ref ?: it.ref_fasta ? true : false, // not new ref_fasta: it.ref_fasta ?: params.ref_fasta, From 8508e827f3c74120c0c590424664ba314b197a16 Mon Sep 17 00:00:00 2001 From: Niklas Schandry Date: Thu, 28 Aug 2025 09:40:04 +0200 Subject: [PATCH 040/162] report fixes --- assets/report/functions/read_busco.R | 150 +++++++++++++-------------- assets/report/report.qmd | 50 +++++---- 2 files changed, 101 insertions(+), 99 deletions(-) diff --git a/assets/report/functions/read_busco.R b/assets/report/functions/read_busco.R index 11834f74..fc5a3ae3 100644 --- a/assets/report/functions/read_busco.R +++ b/assets/report/functions/read_busco.R @@ -1,85 +1,81 @@ # read busco short summary tsv read_busco_report <- function(file) { - assembly <- read_lines(file, - skip = 2L, - n_max = 1L + assembly <- read_lines(file, + skip = 2L, + n_max = 1L) %>% + str_extract('(?<=input_seqs/).+?(?=\\.fa)') + bind_rows( + read_tsv( + file, + skip = 9L, + col_names = c("empty", "count", "variable"), + trim_ws = T, + n_max = 6L, + col_select = c(2, 3), + show_col_types = FALSE ) %>% - str_extract("(?<=input_seqs/).+?(?=\\.fa)") - bind_rows( - read_tsv( - file, - skip = 9L, - col_names = c("empty", "count", "variable"), - trim_ws = T, - n_max = 6L, - col_select = c(2, 3), - show_col_types = FALSE - ) %>% - magrittr::set_colnames(c("count", "stat")) %>% - mutate( - percent = 100 * count / count[stat == "Total BUSCO groups searched"], - percent = round(percent, 3) - ), - read_tsv( - file, - skip = 17L, - col_names = c("empty", "count", "variable"), - trim_ws = T, - n_max = 6L, - col_select = c(2, 3), - show_col_types = FALSE - ) %>% - magrittr::set_colnames(c("count", "stat")) %>% - dplyr::filter(!stat == "Percent gaps") %>% - mutate( - count = case_when( - str_detect(count, "MB") ~ count %>% str_extract("[0-9]+") %>% as.double() %>% - { - . * 1e6 - }, - str_detect(count, "KB") ~ count %>% str_extract("[0-9]+") %>% as.double() %>% - { - . * 1e3 - }, - TRUE ~ count %>% str_extract("[0-9]+") %>% as.double() - ), - percent = NA_real_ - ) - ) %>% - mutate(assembly = assembly) %>% - mutate( - BUSCO = stat %>% - str_extract("\\(.\\)") %>% - fct_relevel(c("(C)", "(S)", "(D)", "(F)", "(M)")), - BUSCO = case_when( - str_detect(BUSCO, "(C)") ~ "Complete", - str_detect(BUSCO, "(S)") ~ "Single Copy", - str_detect(BUSCO, "(D)") ~ "Duplicated", - str_detect(BUSCO, "(F)") ~ "Fragmented", - str_detect(BUSCO, "(M)") ~ "Missing", - ) - ) + magrittr::set_colnames(c("count", "stat")) %>% + mutate( + percent = 100 * count / count[stat == "Total BUSCO groups searched"], + percent = round(percent, 3) + ), + read_tsv( + file, + skip = 17L, + col_names = c("empty", "count", "variable"), + trim_ws = T, + n_max = 6L, + col_select = c(2, 3), + show_col_types = FALSE + ) %>% + magrittr::set_colnames(c("count", "stat")) %>% + dplyr::filter(!stat == "Percent gaps") %>% + mutate( + count = case_when( + str_detect(count, "MB") ~ count %>% str_extract("[0-9]+") %>% as.double() %>% { + . * 1e6 + }, + str_detect(count, "KB") ~ count %>% str_extract("[0-9]+") %>% as.double() %>% { + . * 1e3 + }, + TRUE ~ count %>% str_extract("[0-9]+") %>% as.double() + ), + percent = NA_real_ + ) + ) %>% + mutate(assembly = assembly) %>% + mutate( + BUSCO = stat %>% + str_extract("\\(.\\)") %>% + fct_relevel(c("(C)", "(S)", "(D)", "(F)", "(M)")), + BUSCO = case_when( + str_detect(BUSCO, "(C)") ~ "Complete", + str_detect(BUSCO, "(S)") ~ "Single Copy", + str_detect(BUSCO, "(D)") ~ "Duplicated", + str_detect(BUSCO, "(F)") ~ "Fragmented", + str_detect(BUSCO, "(M)") ~ "Missing", + ) + ) } ## Read busco batch summary -read_busco_batch <- \(x) { - read_tsv(x, show_col_types = F) %>% - set_colnames(colnames(.) %>% str_replace_all(" ", "_")) %>% - mutate( - sample = str_extract(x %>% basename(), - ".+?(?=_[assembly|links|longstitch|ragtag|medaka|pilon])"), - stage = case_when( - str_detect(x, "_ragtag") ~ "RagTag", - str_detect(x, "_medaka") ~ "medaka", - str_detect(x, "_pilon") ~ "pilon", - str_detect(x, "_longstitch") ~ "longstitch", - str_detect(x, "_links") ~ "LINKS", - str_detect(x, "assembly") ~ "Assembly", - TRUE ~ "Unknown"), - Percent_gaps = Percent_gaps %>% str_remove("%") %>% as.numeric() - ) %>% - dplyr::select(-Dataset) %>% - pivot_longer(Complete:Number_of_scaffolds, names_to = "Var") -} +read_busco_batch <- \(x) {read_tsv(x, show_col_types = F) %>% + set_colnames(colnames(.) %>% str_replace_all(" ", "_")) %>% + mutate( + # Get sample name by matching filename to samples in groups, reverse sort by length to hopefully catch + # the correct name first in case there is partial overlap between sample names. + sample = basename(x) %>% + str_extract(groups$sample[rev(order(nchar(groups$sample)))] %>% paste(collapse = "|")),, + stage = case_when( + str_detect(x, "ragtag") ~ "RagTag", + str_detect(x, "medaka") ~ "medaka", + str_detect(x, "pilon") ~ "pilon", + str_detect(x, "longstitch") ~ "longstitch", + str_detect(x, "links") ~ "LINKS", + str_detect(x, "assembl[ey]") ~ "Assembly", + ), + Percent_gaps = Percent_gaps %>% str_remove("%") %>% as.numeric) %>% + dplyr::select(-Dataset) %>% + pivot_longer(Complete:Number_of_scaffolds, names_to = "Var")} diff --git a/assets/report/report.qmd b/assets/report/report.qmd index 822a541b..b093edaf 100644 --- a/assets/report/report.qmd +++ b/assets/report/report.qmd @@ -44,7 +44,8 @@ groups <- yaml::read_yaml("groups.yml") %>% sample = pluck(row, 1, "id"), group = pluck(row, 1, "group", .default = "null") ) - ) + ) %>% + mutate(group = case_when(group == "null" ~ sample, TRUE ~ group)) ``` @@ -81,15 +82,14 @@ fastplong_reports <- list.files(paste0(data_base, "fastplong"), # This is an rmd chunk in plain text. dir.create("fastplong_files") -for (i in 1:length(unique(fastplong_reports$sample))) { +for (i in 1:length(unique(fastplong_reports$group))) { paste0('```{r}\n #| title: "fastplong read statistics" p <- fastplong_reports %>% - filter(group == "', unique(fastplong_reports$sample)[i], '") %>% + filter(group == "', unique(fastplong_reports$group)[i], '") %>% ggplot(aes(x = sample, y = value)) + - geom_line() + - geom_point(size = 5, pch=21, aes(fill=stage)) + - facet_wrap(read_type~stat, scales = "free_y", ncol=2) + + geom_point(size = 5, pch=21, aes(fill=stage), position = position_dodge(width = 0.5)) + + facet_wrap(read_type~stat, scales = "free_y", ncol=2, , labeller = label_context) + fill_scale_plots + scale_y_continuous(labels = function(x) format(x,scientific=-1,trim=T, digits = 3, drop0trailing=T), n.breaks = 4) + theme(axis.title.x = element_blank(), @@ -198,16 +198,19 @@ quast_stats <- list.files(paste0(data_base, "quast"), map_dfr(\(x) { read_quast_report(x) %>% mutate( - sample = str_extract(x %>% basename(), - ".+?(?=_[assemble|polish|run])"), + # Get sample name by matching filename to samples in groups, reverse sort by length to hopefully catch + # the correct name first in case there is partial overlap between sample names. + sample = basename(x) %>% + str_extract(groups$sample[rev(order(nchar(groups$sample)))] %>% paste(collapse = "|")), stage = case_when( str_detect(x, "_ragtag") ~ "RagTag", str_detect(x, "_medaka") ~ "medaka", str_detect(x, "_pilon") ~ "pilon", str_detect(x, "_longstitch") ~ "longstitch", str_detect(x, "_links") ~ "LINKS", - str_detect(x, "assemble") ~ "Assembly", - TRUE ~ "Unknown")) }) %>% + str_detect(x, "assembl[ey]") ~ "Assembly", + TRUE ~ "Unknown")) + }) %>% left_join(groups, by = join_by(sample)) ``` @@ -562,15 +565,18 @@ meryl and merqury were not included in the pipeline run. merqury_stats <- list.files(paste0(data_base, "merqury"), full.names = T, pattern = "stats") %>% lapply(\(x) { read_tsv(x, col_names = c("sample_stage","all","assembly","total","percent"), show_col_types = FALSE) %>% - mutate( sample = str_extract(x %>% basename(), - ".+?(?=_\\.assembly|\\-assemble|links|longstitch|ragtag|medaka|pilon|\\-run|\\-polish)"), + # Get sample name by matching filename to samples in groups, reverse sort by length to hopefully catch + # the correct name first in case there is partial overlap between sample names. + mutate( + sample = basename(x) %>% + str_extract(groups$sample[rev(order(nchar(groups$sample)))] %>% paste(collapse = "|")), stage = case_when( str_detect(x, "_ragtag") ~ "RagTag", str_detect(x, "_medaka") ~ "medaka", str_detect(x, "_pilon") ~ "pilon", str_detect(x, "_longstitch") ~ "longstitch", str_detect(x, "_links") ~ "LINKS", - str_detect(x, "assemble") ~ "Assembly", + str_detect(x, "assembl[ey]") ~ "Assembly", TRUE ~ "Unknown")) }) %>% bind_rows() %>% left_join(groups, by = join_by(sample)) @@ -579,15 +585,15 @@ merqury_asm_hists <- list.files(paste0(data_base, "/merqury"), full.names = T, p lapply(\(x) { read_tsv(x, col_names = T, show_col_types = FALSE) %>% mutate( - sample = str_extract(x %>% basename(), - ".+?(?=_\\.assembly|\\-assemble|links|longstitch|ragtag|medaka|pilon|\\-run|\\-polish)"), + sample = basename(x) %>% + str_extract(groups$sample[rev(order(nchar(groups$sample)))] %>% paste(collapse = "|")), stage = case_when( str_detect(x, "_ragtag") ~ "RagTag", str_detect(x, "_medaka") ~ "medaka", str_detect(x, "_pilon") ~ "pilon", str_detect(x, "_longstitch") ~ "longstitch", str_detect(x, "_links") ~ "LINKS", - str_detect(x, "assemble") ~ "Assembly", + str_detect(x, "assembl[ey]") ~ "Assembly", TRUE ~ "Unknown"), Assembly = as.factor(Assembly), stage = as.factor(stage), @@ -602,15 +608,15 @@ merqury_cn_hists <- list.files(paste0(data_base, "merqury"), full.names = T, pat lapply(\(x) { read_tsv(x, col_names = T, show_col_types = FALSE) %>% mutate( - sample = str_extract(x %>% basename(), - ".+?(?=_\\.assembly|\\-assemble|links|longstitch|ragtag|medaka|pilon|\\-run|\\-polish)"), + sample = basename(x) %>% + str_extract(groups$sample[rev(order(nchar(groups$sample)))] %>% paste(collapse = "|")), stage = case_when( str_detect(x, "_ragtag") ~ "RagTag", str_detect(x, "_medaka") ~ "medaka", str_detect(x, "_pilon") ~ "pilon", str_detect(x, "_longstitch") ~ "longstitch", str_detect(x, "_links") ~ "LINKS", - str_detect(x, "assemble") ~ "Assembly", + str_detect(x, "assembl[ey]") ~ "Assembly", TRUE ~ "Unknown"), Copies = as.factor(Copies), stage = as.factor(stage), @@ -627,15 +633,15 @@ merqury_qv <- list.files(paste0(data_base, "merqury"), full.names = T, pattern = col_names = c("Assembly", "kmers_assembly_unique", "kmers_assembly_shared", "QV", "error_rate"), show_col_types = FALSE) %>% mutate( - sample = str_extract(x %>% basename(), - ".+?(?=_\\.assembly|\\-assemble|links|longstitch|ragtag|medaka|pilon|\\-run|\\-polish)"), + sample = basename(x) %>% + str_extract(groups$sample[rev(order(nchar(groups$sample)))] %>% paste(collapse = "|")), stage = case_when( str_detect(x, "_ragtag") ~ "RagTag", str_detect(x, "_medaka") ~ "medaka", str_detect(x, "_pilon") ~ "pilon", str_detect(x, "_longstitch") ~ "longstitch", str_detect(x, "_links") ~ "LINKS", - str_detect(x, "assemble") ~ "Assembly", + str_detect(x, "assembl[ey]") ~ "Assembly", TRUE ~ "Unknown"), stage = as.factor(stage), sample = as.factor(sample), From f7125b6ae230549e83b37d7d69dc0472fd8c988f Mon Sep 17 00:00:00 2001 From: Niklas Schandry Date: Fri, 27 Jun 2025 16:54:21 +0200 Subject: [PATCH 041/162] refactor assemble and assemble subworkflows for sample-wise parameterization --- .gitignore | 1 - .nf-test.log | 18 ++++ subworkflows/local/hifi/main.nf | 20 ++++ subworkflows/local/jellyfish/main.nf | 99 +++++++++++++++++++ subworkflows/local/liftoff/main.nf | 17 ++++ subworkflows/local/ont/main.nf | 41 ++++++++ subworkflows/local/prepare_hifi/main.nf | 39 ++++++++ subworkflows/local/prepare_ont/chop/main.nf | 46 +++++++++ .../local/prepare_ont/collect/main.nf | 29 ++++++ .../local/prepare_ont/run_nanoq/main.nf | 32 ++++++ subworkflows/local/prepare_shortreads/main.nf | 65 ++++++++++++ subworkflows/local/qc/main.nf | 5 +- .../main.nf | 8 ++ 13 files changed, 416 insertions(+), 4 deletions(-) create mode 100644 .nf-test.log create mode 100644 subworkflows/local/hifi/main.nf create mode 100644 subworkflows/local/jellyfish/main.nf create mode 100644 subworkflows/local/ont/main.nf create mode 100644 subworkflows/local/prepare_hifi/main.nf create mode 100644 subworkflows/local/prepare_ont/chop/main.nf create mode 100644 subworkflows/local/prepare_ont/collect/main.nf create mode 100644 subworkflows/local/prepare_ont/run_nanoq/main.nf create mode 100644 subworkflows/local/prepare_shortreads/main.nf diff --git a/.gitignore b/.gitignore index f232546a..1cf207c5 100644 --- a/.gitignore +++ b/.gitignore @@ -8,4 +8,3 @@ testing* *.pyc null/ .nf-test/ -.nf-test.log diff --git a/.nf-test.log b/.nf-test.log new file mode 100644 index 00000000..830c9b67 --- /dev/null +++ b/.nf-test.log @@ -0,0 +1,18 @@ +Jul-02 11:19:22.019 [main] INFO com.askimed.nf.test.App - nf-test 0.9.2 +Jul-02 11:19:22.043 [main] INFO com.askimed.nf.test.App - Arguments: [test, --profile, conda,test, --update-snapshot] +Jul-02 11:19:23.308 [main] INFO com.askimed.nf.test.App - Nextflow Version: 24.10.5 +Jul-02 11:19:23.312 [main] INFO com.askimed.nf.test.commands.RunTestsCommand - Load config from file /dss/dsshome1/lxc0A/ra34bin/genomeassembler/nf-test.config... +Jul-02 11:19:36.250 [main] INFO com.askimed.nf.test.lang.dependencies.DependencyResolver - Loaded 46 files from directory /dss/dsshome1/lxc0A/ra34bin/genomeassembler in 12.313 sec +Jul-02 11:19:36.256 [main] INFO com.askimed.nf.test.lang.dependencies.DependencyResolver - Found 1 files containing tests. +Jul-02 11:19:36.256 [main] DEBUG com.askimed.nf.test.lang.dependencies.DependencyResolver - Found files: [/dss/dsshome1/lxc0A/ra34bin/genomeassembler/tests/default.nf.test] +Jul-02 11:19:36.489 [main] INFO com.askimed.nf.test.commands.RunTestsCommand - Found 1 tests to execute. +Jul-02 11:19:36.491 [main] INFO com.askimed.nf.test.core.TestExecutionEngine - Started test plan +Jul-02 11:19:36.491 [main] INFO com.askimed.nf.test.core.TestExecutionEngine - Running testsuite 'Test pipeline' from file '/dss/dsshome1/lxc0A/ra34bin/genomeassembler/tests/default.nf.test'. +Jul-02 11:19:36.491 [main] INFO com.askimed.nf.test.core.TestExecutionEngine - Run test 'c85cb7f4: -profile test'. type: com.askimed.nf.test.lang.pipeline.PipelineTest +Jul-02 11:25:42.268 [main] DEBUG com.askimed.nf.test.lang.extensions.SnapshotFile - Load snapshots from file '/dss/dsshome1/lxc0A/ra34bin/genomeassembler/tests/default.nf.test.snap' +Jul-02 11:25:42.486 [main] DEBUG com.askimed.nf.test.lang.extensions.Snapshot - Snapshots '-profile test' do not match. Update snapshots flag set. +Jul-02 11:25:42.487 [main] DEBUG com.askimed.nf.test.lang.extensions.SnapshotFile - Updated snapshot '-profile test' +Jul-02 11:25:42.678 [main] DEBUG com.askimed.nf.test.lang.extensions.SnapshotFile - Wrote snapshots to file '/dss/dsshome1/lxc0A/ra34bin/genomeassembler/tests/default.nf.test.snap' +Jul-02 11:25:42.679 [main] INFO com.askimed.nf.test.core.TestExecutionEngine - Test 'c85cb7f4: -profile test' finished. status: PASSED +Jul-02 11:25:42.684 [main] INFO com.askimed.nf.test.core.TestExecutionEngine - Testsuite 'Test pipeline' finished. snapshot file: true, skipped tests: false, failed tests: false +Jul-02 11:25:42.688 [main] INFO com.askimed.nf.test.core.TestExecutionEngine - Executed 1 tests. 0 tests failed. Done! diff --git a/subworkflows/local/hifi/main.nf b/subworkflows/local/hifi/main.nf new file mode 100644 index 00000000..d67486aa --- /dev/null +++ b/subworkflows/local/hifi/main.nf @@ -0,0 +1,20 @@ +include { PREPARE_HIFI } from '../prepare_hifi/main' + + +workflow HIFI { + take: + main_in + + main: + Channel.empty().set { ch_versions } + + PREPARE_HIFI(main_in) + + ch_versions.mix(PREPARE_HIFI.out.versions) + + versions = ch_versions + + emit: + main_out = PREPARE_HIFI.out.main_out + versions +} diff --git a/subworkflows/local/jellyfish/main.nf b/subworkflows/local/jellyfish/main.nf new file mode 100644 index 00000000..3ca19b5d --- /dev/null +++ b/subworkflows/local/jellyfish/main.nf @@ -0,0 +1,99 @@ +include { COUNT } from '../../../modules/local/jellyfish/count/main' +include { DUMP } from '../../../modules/local/jellyfish/dump/main' +include { HISTO } from '../../../modules/local/jellyfish/histo/main' +include { STATS } from '../../../modules/local/jellyfish/stats/main' +include { GENOMESCOPE } from '../../../modules/local/genomescope/main' + +workflow JELLYFISH { + take: + inputs + nanoq_out + + main: + Channel.empty().set { genomescope_in } + Channel.empty().set { ch_versions } + inputs.map { + it -> + [ + meta: it.meta, + reads: it.ontreads + ] + } + .set { samples } + COUNT(samples) + COUNT.out.kmers.set { kmers } + + ch_versions = ch_versions.mix(COUNT.out.versions) + + if (params.dump) { + DUMP(kmers) + ch_versions = ch_versions.mix(DUMP.out.versions) + } + + HISTO(kmers) + ch_versions = ch_versions.mix(HISTO.out.versions) + + if (!params.read_length == null) { + HISTO.out.histo + .map { + it -> + [ + it[0], + it[1], + params.kmer_length, + params.read_length + ] + } + .set { genomescope_in } + } + + if (params.read_length == null) { + HISTO.out.histo + .map { + it -> + [ + it[0], + it[1], + params.kmer_length + ] + } + .join(nanoq_out) + .set { genomescope_in } + } + + GENOMESCOPE(genomescope_in) + + ch_versions = ch_versions.mix(GENOMESCOPE.out.versions) + + STATS(kmers) + + ch_versions = ch_versions.mix(STATS.out.versions) + + inputs + .map { + it -> it.subMap('genome_size') + } + .join( + GENOMESCOPE.out.estimated_hap_len + .map { + it -> + [ + meta: it[0], + genome_size: it[1] + ] + } + ) + .set { outputs } + + GENOMESCOPE.out.summary.set { genomescope_summary } + + GENOMESCOPE.out.plot.set { genomescope_plot } + + versions = ch_versions + + emit: + outputs + genomescope_summary + genomescope_plot + versions +} diff --git a/subworkflows/local/liftoff/main.nf b/subworkflows/local/liftoff/main.nf index 8e04024d..edbc7fdd 100644 --- a/subworkflows/local/liftoff/main.nf +++ b/subworkflows/local/liftoff/main.nf @@ -2,10 +2,27 @@ include { LIFTOFF } from '../../../modules/nf-core/liftoff/main' workflow RUN_LIFTOFF { take: +<<<<<<< HEAD liftoff_in main: Channel.empty().set { ch_versions } +======= + ch_main + + main: + Channel.empty().set { ch_versions } + ch_main + .map { it -> + [ + it.meta, + it.assembly, + it.ref_fasta, + it.ref_gff + ] + } + .set { liftoff_in } +>>>>>>> 096fc93 (refactor assemble and assemble subworkflows for sample-wise parameterization) LIFTOFF(liftoff_in, []) diff --git a/subworkflows/local/ont/main.nf b/subworkflows/local/ont/main.nf new file mode 100644 index 00000000..7573f60f --- /dev/null +++ b/subworkflows/local/ont/main.nf @@ -0,0 +1,41 @@ +include { PREPARE_ONT } from '../prepare_ont/main' +include { JELLYFISH } from '../jellyfish/main' + +workflow ONT { + take: + main_in + + main: + Channel.empty().set { ch_versions } + Channel.of([[],[]]) + .tap { genomescope_summary } + .tap { genomescope_plot } + + PREPARE_ONT(main_in) + + PREPARE_ONT.out.trimmed.set { main_out } + + PREPARE_ONT.out.nanoq_report.set { nanoq_report } + + PREPARE_ONT.out.nanoq_stats.set { nanoq_stats } + + ch_versions = ch_versions.mix(PREPARE_ONT.out.versions) + + if (params.jellyfish) { + JELLYFISH(PREPARE_ONT.out.trimmed, PREPARE_ONT.out.med_len) + JELLYFISH.out.outputs.set { output_channel } + JELLYFISH.out.genomescope_summary.set { genomescope_summary } + JELLYFISH.out.genomescope_plot.set { genomescope_plot } + ch_versions = ch_versions.mix(JELLYFISH.out.versions) + } + + versions = ch_versions + + emit: + main_out + nanoq_report + nanoq_stats + genomescope_plot + genomescope_summary + versions +} diff --git a/subworkflows/local/prepare_hifi/main.nf b/subworkflows/local/prepare_hifi/main.nf new file mode 100644 index 00000000..af509b5f --- /dev/null +++ b/subworkflows/local/prepare_hifi/main.nf @@ -0,0 +1,39 @@ +include { LIMA } from '../../../modules/nf-core/lima/main' +include { SAMTOOLS_FASTQ as TO_FASTQ } from '../../../modules/nf-core/samtools/fastq/main' + +workflow PREPARE_HIFI { + take: + main_in + + main: + Channel.empty().set { ch_versions } + main_in + .map { it -> [it.meta, it.hifireads] } + .set { hifireads } + + if (params.lima) { + if (!params.pacbio_primers) { + error('Trimming with lima requires a file containing primers (--pacbio_primers)') + } + LIMA(hifireads, params.pacbio_primers) + TO_FASTQ(LIMA.out.bam, false) + main_in + .map { it -> it.subMap('hifireads') } + .join( + TO_FASTQ.out.fastq.map { + it -> + [ + meta: it[0], + hifireads: it[1] + ] + } + ) + .set { main_out } + ch_versions.mix(LIMA.out.versions).mix(TO_FASTQ.out.versions) + } + versions = ch_versions + + emit: + main_out + versions +} diff --git a/subworkflows/local/prepare_ont/chop/main.nf b/subworkflows/local/prepare_ont/chop/main.nf new file mode 100644 index 00000000..632e2411 --- /dev/null +++ b/subworkflows/local/prepare_ont/chop/main.nf @@ -0,0 +1,46 @@ +include { PORECHOP_PORECHOP as PORECHOP } from '../../../../modules/nf-core/porechop/porechop/main' + +workflow CHOP { + take: + input + + main: + Channel.empty().set { chopped_reads } + Channel.empty().set { ch_versions } + + if (params.porechop) { + input.map { + it -> + [ + meta: it.meta, + reads: it.ontreads + ] + } + .set { in_reads } + PORECHOP(in_reads) + input.map { + it -> + it.subMap('ontreads') + } + .join( + PORECHOP.out.reads + .map { it -> + [ + meta: it[0], + ont_reads: it[1] + ] + } + ) + .set { chopped_reads } + + ch_versions.mix(PORECHOP.out.versions) + } + else { + input.set { chopped_reads } + } + versions = ch_versions + + emit: + chopped_reads + versions +} diff --git a/subworkflows/local/prepare_ont/collect/main.nf b/subworkflows/local/prepare_ont/collect/main.nf new file mode 100644 index 00000000..e3739242 --- /dev/null +++ b/subworkflows/local/prepare_ont/collect/main.nf @@ -0,0 +1,29 @@ +include { COLLECT_READS } from '../../../../modules/local/collect_reads/main' + +workflow COLLECT { + take: + ch_input + + main: + Channel.empty().set { ch_versions } + + ch_input + .map { row -> [row.meta, row.ontreads] } + .set { reads } + + if (params.collect) { + COLLECT_READS(reads) + COLLECT_READS.out.combined_reads.set { reads } + ch_versions.mix(COLLECT_READS.out.versions) + } + versions = ch_versions + + ch_input + .map { it -> it.submap('ontreads') } + .join(reads.map { it -> [meta: it[0], ontreads:it[1]] } ) + .set { reads } + + emit: + reads + versions +} diff --git a/subworkflows/local/prepare_ont/run_nanoq/main.nf b/subworkflows/local/prepare_ont/run_nanoq/main.nf new file mode 100644 index 00000000..3e6b91ad --- /dev/null +++ b/subworkflows/local/prepare_ont/run_nanoq/main.nf @@ -0,0 +1,32 @@ +include { NANOQ } from '../../../../modules/local/nanoq/main' + +workflow RUN_NANOQ { + take: + inputs + + main: + Channel.empty().set { versions } + inputs.map { + it -> + [ + meta: it.meta, + ontreads: it.ontreads + ] + } + .set { in_reads } + NANOQ(in_reads) + + NANOQ.out.report.set { report } + + NANOQ.out.stats.set { stats } + + NANOQ.out.median_length.set { median_length } + + NANOQ.out.versions.set { versions } + + emit: + report + stats + median_length + versions +} diff --git a/subworkflows/local/prepare_shortreads/main.nf b/subworkflows/local/prepare_shortreads/main.nf new file mode 100644 index 00000000..4ada74fa --- /dev/null +++ b/subworkflows/local/prepare_shortreads/main.nf @@ -0,0 +1,65 @@ +include { TRIMGALORE } from '../../../modules/nf-core/trimgalore/main' +include { MERYL_COUNT } from '../../../modules/nf-core/meryl/count/main' +include { MERYL_UNIONSUM } from '../../../modules/nf-core/meryl/unionsum/main' + +workflow PREPARE_SHORTREADS { + take: + main_in + + main: + Channel.empty().set { ch_versions } + + main_in + .map { create_shortread_channel(it) } + .set { shortreads } + + if (params.trim_short_reads) { + TRIMGALORE(shortreads) + TRIMGALORE.out.reads.set { shortreads } + ch_versions = ch_versions.mix(TRIMGALORE.out.versions) + } + + MERYL_COUNT(shortreads.map { it -> [it[0], it[1]] }, params.meryl_k) + MERYL_UNIONSUM(MERYL_COUNT.out.meryl_db, params.meryl_k) + MERYL_UNIONSUM.out.meryl_db.set { meryl_kmers } + + main_in + .map { + it -> it.subMap('shortread_F', 'shortread_R', 'paired') + } + .join( + shortreads.map { it -> [meta: [id: it[0].id], shortreads: it[1]]} + ) + .set { main_out } + + versions = ch_versions.mix(MERYL_COUNT.out.versions).mix(MERYL_UNIONSUM.out.versions) + + emit: + main_out + meryl_kmers + versions +} + +def create_shortread_channel(row) { + // create meta map + def meta = [:] + meta.id = row.meta.id + meta.paired = row.paired.toBoolean() + meta.single_end = !meta.paired + + // add path(s) of the fastq file(s) to the meta map + def shortreads = [] + if (!file(row.shortread_F).exists()) { + exit(1, "ERROR: shortread_F fastq file does not exist!\n${row.shortread_F}") + } + if (!meta.paired) { + shortreads = [meta, [file(row.shortread_F)]] + } + else { + if (!file(row.shortread_R).exists()) { + exit(1, "ERROR: shortread_R fastq file does not exist!\n${row.shortread_R}") + } + shortreads = [meta, [file(row.shortread_F), file(row.shortread_R)]] + } + return shortreads +} diff --git a/subworkflows/local/qc/main.nf b/subworkflows/local/qc/main.nf index 6efaa0b2..0fa33495 100644 --- a/subworkflows/local/qc/main.nf +++ b/subworkflows/local/qc/main.nf @@ -5,9 +5,8 @@ include { MERQURY_QC } from './merqury/main.nf' workflow QC { take: - ch_main // pipeline main - scaffolds // scaffolds to run qc on - meryl_kmers // short-read kmers + ch_main + meryl_kmers main: Channel.empty().set { ch_versions } diff --git a/subworkflows/local/utils_nfcore_genomeassembler_pipeline/main.nf b/subworkflows/local/utils_nfcore_genomeassembler_pipeline/main.nf index 591390b5..59db28fb 100644 --- a/subworkflows/local/utils_nfcore_genomeassembler_pipeline/main.nf +++ b/subworkflows/local/utils_nfcore_genomeassembler_pipeline/main.nf @@ -220,6 +220,14 @@ workflow PIPELINE_INITIALISATION { ] : null, // Check if assembler can do hybrid + (it.strategy == "single" && it.ont_reads && it.hifi_reads) + ? + [ + println("Please confirm samplesheet: [sample: $it.meta.id]: Stragety is $it.strategy, but both types of reads are provided."), + "invalid" + ] + : null, + // Check if assembler can do hybrid (it.strategy == "hybrid" && !hybrid_assemblers.contains(it.assembler1)) ? [ From 69e94a9c6d182ba61f9b5cffe1393ff48eb50b63 Mon Sep 17 00:00:00 2001 From: nf-core bot Date: Tue, 1 Jul 2025 09:36:28 +0200 Subject: [PATCH 042/162] Important! Template update for nf-core/tools v3.3.1 (#164) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Template update for nf-core/tools version 3.2.1 * Template update for nf-core/tools version 3.3.1 * merge template 3.3.1 - fix linting * update pre-commit * merge template 3.3.1 - fix linting * pre-commit config? * pre-commit config? * reinstall links * try larger runner * smaller run, disable bloom filter for hifiasm test * updated test snapshot * updated test snapshot * update nftignore * update nftignore * update nftignore * update nftignore * update nftignore * update nftignore * update nftignore * update nftignore * update nftignore * Update .github/actions/nf-test/action.yml Co-authored-by: Matthias Hörtenhuber * Update docs/output.md Co-authored-by: Matthias Hörtenhuber * remove .nf-test.log --------- Co-authored-by: Niklas Schandry Co-authored-by: Matthias Hörtenhuber --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 1cf207c5..f232546a 100644 --- a/.gitignore +++ b/.gitignore @@ -8,3 +8,4 @@ testing* *.pyc null/ .nf-test/ +.nf-test.log From 3b3948eb554bf90b267216f8eec5ad876734ffd9 Mon Sep 17 00:00:00 2001 From: Niklas Schandry Date: Fri, 11 Jul 2025 16:00:04 +0200 Subject: [PATCH 043/162] less blocking --- subworkflows/local/prepare_ont/chop/main.nf | 26 +--- subworkflows/local/prepare_ont/main.nf | 114 ++++++++++++++++++ subworkflows/local/prepare_shortreads/main.nf | 69 +++++++++-- workflows/genomeassembler.nf | 109 +++++++++++++++-- 4 files changed, 279 insertions(+), 39 deletions(-) create mode 100644 subworkflows/local/prepare_ont/main.nf diff --git a/subworkflows/local/prepare_ont/chop/main.nf b/subworkflows/local/prepare_ont/chop/main.nf index 632e2411..4fca60ab 100644 --- a/subworkflows/local/prepare_ont/chop/main.nf +++ b/subworkflows/local/prepare_ont/chop/main.nf @@ -8,29 +8,9 @@ workflow CHOP { Channel.empty().set { chopped_reads } Channel.empty().set { ch_versions } - if (params.porechop) { - input.map { - it -> - [ - meta: it.meta, - reads: it.ontreads - ] - } - .set { in_reads } - PORECHOP(in_reads) - input.map { - it -> - it.subMap('ontreads') - } - .join( - PORECHOP.out.reads - .map { it -> - [ - meta: it[0], - ont_reads: it[1] - ] - } - ) + PORECHOP(input) + + PORECHOP.out.reads .set { chopped_reads } ch_versions.mix(PORECHOP.out.versions) diff --git a/subworkflows/local/prepare_ont/main.nf b/subworkflows/local/prepare_ont/main.nf new file mode 100644 index 00000000..3eecaed7 --- /dev/null +++ b/subworkflows/local/prepare_ont/main.nf @@ -0,0 +1,114 @@ +include { CHOP } from './chop/main' +include { COLLECT } from './collect/main' +include { RUN_NANOQ } from './run_nanoq/main' + +workflow PREPARE_ONT { + take: + ch_main + + main: + Channel.empty().set { ch_versions } + + ch_main + .branch { + it -> + to_collect: it.ont_collect + no_collect: !it.ont_collect + } + .set { ch_ont_collect_branched } + + ch_ont_collect_branched + .to_collect + .map { it -> [it.meta, it.ontreads] } + .set { collect_in } + + COLLECT(collect_in) + + COLLECT.out.reads + .map { it -> [meta: it[0], ontreads: it[1]] } + .map { it -> it.collect { entry -> [ entry.value, entry ] } } + .set { ch_collected_reads } + + ch_ont_collect_branched + .to_collect + .map { it -> it - it.subMap("ontreads") } + .map { it -> it.collect { entry -> [ entry.value, entry ] } } + .join(ch_collected_reads) + .map { it -> it.collect { _entry, map -> [ (map.key): map.value ] }.collectEntries() } + .mix(ch_ont_collect_branched.no_collect) + .set { ch_collected } + + // ch_collected is the same samples as the input channel + + ch_collected + .branch { + chop: it.ont_trim + no_chop: !it.ont_trim + } + .set { ch_ont_chop_branched } + + ch_ont_chop_branched + .chop + .map { it -> [it.meta, it.ontreads]} + .set { chop_in } + + CHOP(chop_in) + + ch_ont_chop_branched + .chop + .map { it -> it - it.subMap("ontreads")} + .map { it -> it.collect { entry -> [ entry.value, entry ] } } + .join( + CHOP + .out + .chopped_reads + .map { meta, reads -> [meta: meta, ontreads: reads] } + .map { it -> it.collect { entry -> [ entry.value, entry ] } } + ) + .map { it -> it.collect { _entry, map -> [ (map.key): map.value ] }.collectEntries() } + .mix( + ch_ont_chop_branched + .no_chop + ) + .set { ch_ont_chopped } + + ch_ont_chopped + .branch { + nanoq: it.ont_nanoq + no_nanoq: !it.ont_nanoq + } + .set { ch_ont_chopped_branched } + + ch_ont_chopped_branched + .nanoq + .map { it -> [it.meta, it.ontreads] } + .set {ch_nanoq_in} + + RUN_NANOQ(ch_nanoq_in) + + RUN_NANOQ.out.median_length + .map { it -> [meta: it[0], ont_read_length: it[1]] } + .map { it -> it.collect { entry -> [ entry.value, entry ] } } + .set { med_len } + + RUN_NANOQ.out.report.set { nanoq_report } + + RUN_NANOQ.out.stats.set { nanoq_stats } + + ch_ont_chopped_branched + .nanoq + .map { it -> it - it.subMap("ont_read_length") } + .map { it -> it.collect { entry -> [ entry.value, entry ] } } + .join( med_len ) + .map { it -> it.collect { _entry, map -> [ (map.key): map.value ] }.collectEntries() } + .mix(ch_ont_chopped_branched.no_nanoq) + .set { main_out } + + versions = ch_versions.mix(COLLECT.out.versions).mix(CHOP.out.versions).mix(RUN_NANOQ.out.versions) + + emit: + main_out + nanoq_report + nanoq_stats + versions +} diff --git a/subworkflows/local/prepare_shortreads/main.nf b/subworkflows/local/prepare_shortreads/main.nf index 4ada74fa..67d430bd 100644 --- a/subworkflows/local/prepare_shortreads/main.nf +++ b/subworkflows/local/prepare_shortreads/main.nf @@ -13,23 +13,76 @@ workflow PREPARE_SHORTREADS { .map { create_shortread_channel(it) } .set { shortreads } - if (params.trim_short_reads) { - TRIMGALORE(shortreads) - TRIMGALORE.out.reads.set { shortreads } - ch_versions = ch_versions.mix(TRIMGALORE.out.versions) - } - MERYL_COUNT(shortreads.map { it -> [it[0], it[1]] }, params.meryl_k) + // use modified shortread channel + + shortreads_in + .map { + it -> it - it.subMap('shortread_F', 'shortread_R', 'paired') + } + .map { + it -> it.collect { entry -> [ entry.value, entry ] } + } + .join( + shortreads + .map { it -> [meta: [id: it[0].id], shortreads: it[1]]} + .map { it -> it.collect { entry -> [ entry.value, entry ] } } + ) + .map { it -> it.collect { _entry, map -> [ (map.key): map.value ] }.collectEntries() } + .set { shortreads } + + // shortread trimming + //shortreads.view { it -> "shortreads: $it" } + + shortreads + .branch { + it -> + trim: it.shortread_trim + no_trim: !it.shortread_trim + } + .set { shortreads } + + TRIMGALORE(shortreads.trim.map { it -> [it.meta, it.shortreads] }) + + // unite branched: + // add trimmed reads to trim channel, then mix with shortreads.no_trim + + shortreads + .trim + .map { it -> it - it.subMap("shortreads") } + .map { it -> it.collect { entry -> [ entry.value, entry ] } } + .join( + TRIMGALORE.out.reads + .map { it -> [meta: [id: it[0].id], shortreads: it[1]]} + .map { it -> it.collect { entry -> [ entry.value, entry ] } } + ) + .map { it -> it.collect { _entry, map -> [ (map.key): map.value ] }.collectEntries() } + .mix( shortreads.no_trim ) + .set { shortreads } + + ch_versions = ch_versions.mix(TRIMGALORE.out.versions) + shortreads + .filter { it -> it.merqury } + .multiMap { it -> + reads: [ it.meta, it.shortreads ] + kmer_size: it.meryl_k + } + .set { meryl_in } + MERYL_COUNT(meryl_in.reads, meryl_in.kmer_size) MERYL_UNIONSUM(MERYL_COUNT.out.meryl_db, params.meryl_k) MERYL_UNIONSUM.out.meryl_db.set { meryl_kmers } main_in .map { - it -> it.subMap('shortread_F', 'shortread_R', 'paired') + it -> it - it.subMap('shortread_F', 'shortread_R', 'paired') } + .map { it -> it.collect { entry -> [ entry.value, entry ] } } .join( - shortreads.map { it -> [meta: [id: it[0].id], shortreads: it[1]]} + shortreads + .map { it -> [meta: [id: it.meta.id], shortreads: it.shortreads]} + .map { it -> it.collect { entry -> [ entry.value, entry ] } } ) + .map { it -> it.collect { _entry, map -> [ (map.key): map.value ] }.collectEntries() } .set { main_out } versions = ch_versions.mix(MERYL_COUNT.out.versions).mix(MERYL_UNIONSUM.out.versions) diff --git a/workflows/genomeassembler.nf b/workflows/genomeassembler.nf index 17db0ee8..b30b9e76 100644 --- a/workflows/genomeassembler.nf +++ b/workflows/genomeassembler.nf @@ -119,11 +119,101 @@ workflow GENOMEASSEMBLER { Prepare reads ============= */ - PREPARE(ch_main) + /* + Short reads + */ + ch_main + .filter { + it -> (it.shortread_F && it.use_short_reads) ? true : false + } + .set { shortreads } + + ch_main + .filter { + it -> (it.ontreads) ? true : false + } + .set { ontreads } + + ch_main + .filter { + it -> (it.hifireads) ? true : false + } + .set { hifireads } + + // adapted to sample-logic + PREPARE_SHORTREADS(shortreads) + + PREPARE_SHORTREADS.out.meryl_kmers.set { meryl_kmers } + // This changes ch_main shortreads_F and _R become one tuple, paired is gone. + + // put shortreads back together with samples without shortreads + + ch_main + .filter { + it -> !it.shortread_F ? true : false + } + .map { it -> it - it.subMap("shortread_F","shortread_R", "paired") + [shorteads: null] } + .mix(PREPARE_SHORTREADS.out.main_out) + .set { ch_main_shortreaded } - PREPARE.out.ch_main.set { ch_main_prepared } + ONT(ontreads) + ONT.out.main_out.set { ch_main_ont_prepped } - PREPARE.out.meryl_kmers.set { meryl_kmers } + HIFI(hifireads) + HIFI.out.main_out.set { ch_main_hifi_prepped } + + // rebuild the main channel from shortread out, ont out and hifi out. + // This will block entry into assemble, and it should. + + + // ch_main_shortreaded contains all samples + // add ONT outputs to mixed shortread output + + ch_main_shortreaded + .filter { + it -> it.ontreads ? true : false + } + .map { it -> it.subMap("meta","shortreads")} + .map { it -> it.collect { entry -> [ entry.value, entry ] } } + .join( ch_main_ont_prepped + .map { it -> it - it.subMap("shortreads") } + .map { it -> it.collect { entry -> [ entry.value, entry ] } } + ) + // After joining re-create the maps from the stored map + .map { it -> it.collect { _entry, map -> [ (map.key): map.value ] }.collectEntries() } + // mix back in those samples where nothing was done to the ont reads + .mix(ch_main_shortreaded + .filter { + it -> it.ontreads ? false : true + } + ) + .set { + ch_main_sr_ont + } + + // Add prepared hifi-reads: + + ch_main_sr_ont + .filter { + it -> it.hifireads ? true : false + } + .map { it -> it.subMap("meta","shortreads","ontreads")} + .map { it -> it.collect { entry -> [ entry.value, entry ] } } + .join( ch_main_hifi_prepped + .map { it -> it - it.subMap("shortreads","ontreads") } + .map { it -> it.collect { entry -> [ entry.value, entry ] } } + ) + // After joining re-create the maps from the stored map + .map { it -> it.collect { _entry, map -> [ (map.key): map.value ] }.collectEntries() } + // mix back in those samples where nothing was done to the hifireads reads + .mix(ch_main_sr_ont + .filter { + it -> it.hifireads ? false : true + } + ) + .set { + ch_main_prepared + } /* Assembly @@ -174,19 +264,22 @@ workflow GENOMEASSEMBLER { .mix(SCAFFOLD.out.ch_main) .set { ch_main_scaffolded } - PREPARE.out.fastplong_json_reports + ONT.out.nanoq_report + .concat( + ONT.out.nanoq_stats + ) .collect { it -> it[1] } - .set { fasplong_jsons } + .set { nanoq_files } - PREPARE.out.genomescope_summary + ONT.out.genomescope_summary .concat( - PREPARE.out.genomescope_plot + ONT.out.genomescope_plot ) .unique() .collect { it -> it[1] } .set { genomescope_files } - ch_versions = ch_versions.mix(PREPARE.out.versions).mix(ASSEMBLE.out.versions).mix(POLISH.out.versions).mix(SCAFFOLD.out.versions) + ch_versions = ch_versions.mix(PREPARE_SHORTREADS.out.versions).mix(ONT.out.versions).mix(HIFI.out.versions).mix(ASSEMBLE.out.versions).mix(POLISH.out.versions).mix(SCAFFOLD.out.versions) ch_versions = ch_versions From c3e2ae373ea6f47b2c6756f1c0e148f0db33e222 Mon Sep 17 00:00:00 2001 From: Niklas Schandry Date: Thu, 26 Jun 2025 14:02:37 +0200 Subject: [PATCH 044/162] Improve reference input check (#166) * add prefix to singularity container for report * add files exist check for references, closes #165 --- subworkflows/local/utils_nfcore_genomeassembler_pipeline/main.nf | 1 + 1 file changed, 1 insertion(+) diff --git a/subworkflows/local/utils_nfcore_genomeassembler_pipeline/main.nf b/subworkflows/local/utils_nfcore_genomeassembler_pipeline/main.nf index 59db28fb..ea06a2ae 100644 --- a/subworkflows/local/utils_nfcore_genomeassembler_pipeline/main.nf +++ b/subworkflows/local/utils_nfcore_genomeassembler_pipeline/main.nf @@ -262,6 +262,7 @@ workflow PIPELINE_INITIALISATION { : null } + emit: samplesheet = ch_samplesheet versions = ch_versions From d7f0d522f56880b45fe748a28c585cdc4d0594b5 Mon Sep 17 00:00:00 2001 From: nf-core bot Date: Tue, 1 Jul 2025 09:36:28 +0200 Subject: [PATCH 045/162] Important! Template update for nf-core/tools v3.3.1 (#164) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Template update for nf-core/tools version 3.2.1 * Template update for nf-core/tools version 3.3.1 * merge template 3.3.1 - fix linting * update pre-commit * merge template 3.3.1 - fix linting * pre-commit config? * pre-commit config? * reinstall links * try larger runner * smaller run, disable bloom filter for hifiasm test * updated test snapshot * updated test snapshot * update nftignore * update nftignore * update nftignore * update nftignore * update nftignore * update nftignore * update nftignore * update nftignore * update nftignore * Update .github/actions/nf-test/action.yml Co-authored-by: Matthias Hörtenhuber * Update docs/output.md Co-authored-by: Matthias Hörtenhuber * remove .nf-test.log --------- Co-authored-by: Niklas Schandry Co-authored-by: Matthias Hörtenhuber --- tests/default.nf.test.snap | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/default.nf.test.snap b/tests/default.nf.test.snap index e4bfbb0b..1b8cfae3 100644 --- a/tests/default.nf.test.snap +++ b/tests/default.nf.test.snap @@ -7,7 +7,7 @@ "flye": "2.9.5-b1801" }, "GFA_2_FA_HIFI": { - "awk": "1.3.4", + "awk": "mawk 1.3.4", "gzip": 1.13 }, "HIFIASM": { From 47d656e28b02c514da0463391f0d8cfa8e567993 Mon Sep 17 00:00:00 2001 From: Niklas Schandry Date: Fri, 27 Jun 2025 13:43:03 +0200 Subject: [PATCH 046/162] prepare pipeline initialization for sample-wise parameterization --- NOTE.md | 51 +++++++++++++++++++ .../main.nf | 29 +++++++++++ 2 files changed, 80 insertions(+) create mode 100644 NOTE.md diff --git a/NOTE.md b/NOTE.md new file mode 100644 index 00000000..1fd8b7be --- /dev/null +++ b/NOTE.md @@ -0,0 +1,51 @@ +branch refactor-assemblers: + +The idea is to move from pipepline / run level choice of assemblers towards a per-sample level choice. +Most obviously this will facilitate comparisons between assembly strategies for the same reads across assemblers from one run, which is probably an important use-case. +Still, pipeline-level settings should ne propagated (but sample-levels should take priority) to also make one-size-fits-all runs possible. + +Things to do: + + - Modify sample sheet + - Single assembler (ONT or HIFI) + - Hybrid assembler (ONT and HIFI) + - Assembler scaffolding (ONT assembler, HIFI assembler) + - Columns to add: + -> strategy: + - "single" + - "hybrid" + - "scaffold" -> Assembler 1 will do ONT, assembler 2 will do HiFi. + -> assembler1 (required for single and hybrid): ["flye","hifiasm"] + -> assembler1_args (optional; can be global via params.assembler1_args) + -> assembler2 (required for scaffold): ["flye","hifiasm"] + -> assembler2_args (optional; can be global via params.assembler2_args) + -> assembly_scaffolding (required for scaffold): ["ont_on_hifi", "hifi_on_ont"] + -> QC_reads: ["ont", "hifi"] + -> genome_size: genome_size + -> polish: "medaka", "pilon", "medaka+pilon" (both polish initial assembly), "medaka-pilon" (first medaka then pilon) + + - Params to remove: + - ont + - hifi + - genome_size + + - Refactor pipeline to carry this information. + + - Probably should make use of channel branching to achieve this reasonably + +# The main channel + +This channel is of map type. This means that it is not suitable for many things, but positional retrieval of entries from a channel that is not static seems like a way to create problems. +This means that for joining, some dropping of k/v pairs and back-and-forth between map and list is required. +The main channel is intially defined from the sample-sheet, but extended / modified throughout the pipeline run, it contains a number of entries. +The main chnanel goes into each subworkflow and comes out of each subworkflow. +Conditional execution within subworkflows is done via branching of the main channel, +processing of the branch that is worked on, and mixing back of modified main channel with the untouched branch. +The subworkflow emits ch_main into the workflow. +To enter into processes (!) input (list) channels are created via (multi)mapping. + +This means that there is no conditional execution of subworkflows, but everything goes into all subworkflows, but may come out untouched. + +# Reporting + +Generally, reporting works as before, but an additional diff --git a/subworkflows/local/utils_nfcore_genomeassembler_pipeline/main.nf b/subworkflows/local/utils_nfcore_genomeassembler_pipeline/main.nf index ea06a2ae..d19d3c1f 100644 --- a/subworkflows/local/utils_nfcore_genomeassembler_pipeline/main.nf +++ b/subworkflows/local/utils_nfcore_genomeassembler_pipeline/main.nf @@ -262,6 +262,35 @@ workflow PIPELINE_INITIALISATION { : null } + // Define valid hybrid assemblers + + def hybrid_assemblers = ["hifiasm"] + + // sample-level checks + + ch_samplesheet + .map { + it -> + // Check if assembler can do hybrid + (it.strategy == "hybrid" && !hybrid_assemblers.contains(it.assembler1)) + ? [ println("Please confirm samplesheet: [sample: $it.meta.id]: Hybrid assembly can only be performed with $hybrid_assemblers"), "invalid" ] + : null + // Check if qc reads are specified for hybrid assemblies + (it.strategy == "hybrid" && !params.qc_reads) + ? [ println("Please confirm samplesheet: [sample: $it.meta.id]: Please specify which reads should be used for qc: '--qc_reads': 'ONT' or 'HIFI'") , "invalid" ] + : null + // Check if genome_size is given with --scaffold_longstitch + (params.scaffold_longstitch && !it.genome_size && !(it.ont_reads && params.jellyfish)) + ? [ println("Please confirm samplesheet: [sample: $it.meta.id]: --scaffold_longstitch requires genome-size. Either provide genome-size estimate, or estimate from ONT reads with --jellyfish"), "invalid" ] + : null + } + .collect() + // error if >0 samples failed a check above + .subscribe { + it -> it.contains("invalid") + ? error("Invalid combination in samplesheet") + : null + } emit: samplesheet = ch_samplesheet From 419eed6baf3ccad94a257e8e2376491497ec61a1 Mon Sep 17 00:00:00 2001 From: Niklas Schandry Date: Fri, 27 Jun 2025 16:54:21 +0200 Subject: [PATCH 047/162] refactor assemble and assemble subworkflows for sample-wise parameterization --- .gitignore | 1 - subworkflows/local/assemble/main.nf | 15 ++++++++++ subworkflows/local/liftoff/main.nf | 7 ----- .../main.nf | 29 +++++++++++++++--- workflows/genomeassembler.nf | 30 +++++++++++++++++++ 5 files changed, 70 insertions(+), 12 deletions(-) diff --git a/.gitignore b/.gitignore index f232546a..1cf207c5 100644 --- a/.gitignore +++ b/.gitignore @@ -8,4 +8,3 @@ testing* *.pyc null/ .nf-test/ -.nf-test.log diff --git a/subworkflows/local/assemble/main.nf b/subworkflows/local/assemble/main.nf index c5e69749..34277a56 100644 --- a/subworkflows/local/assemble/main.nf +++ b/subworkflows/local/assemble/main.nf @@ -62,6 +62,7 @@ workflow ASSEMBLE { ] mode: it.assembler1 == "flye" ? "--nano-hq" : "--pacbio-hifi" } +<<<<<<< HEAD .set { flye_inputs } FLYE(flye_inputs.reads, flye_inputs.mode) @@ -380,6 +381,20 @@ workflow ASSEMBLE { RUN_LIFTOFF(liftoff_in) ch_versions = ch_versions.mix(RUN_LIFTOFF.out.versions) +======= + } + /* + QC on initial assembly + */ + QC( ch_main, + meryl_kmers) + ch_versions = ch_versions.mix(QC.out.versions) + + if (params.lift_annotations) { + RUN_LIFTOFF(ch_main) + ch_versions = ch_versions.mix(RUN_LIFTOFF.out.versions) + } +>>>>>>> d2e37c9 (refactor assemble and assemble subworkflows for sample-wise parameterization) emit: ch_main = ch_main_to_qc diff --git a/subworkflows/local/liftoff/main.nf b/subworkflows/local/liftoff/main.nf index edbc7fdd..df4de343 100644 --- a/subworkflows/local/liftoff/main.nf +++ b/subworkflows/local/liftoff/main.nf @@ -2,14 +2,8 @@ include { LIFTOFF } from '../../../modules/nf-core/liftoff/main' workflow RUN_LIFTOFF { take: -<<<<<<< HEAD liftoff_in - main: - Channel.empty().set { ch_versions } -======= - ch_main - main: Channel.empty().set { ch_versions } ch_main @@ -22,7 +16,6 @@ workflow RUN_LIFTOFF { ] } .set { liftoff_in } ->>>>>>> 096fc93 (refactor assemble and assemble subworkflows for sample-wise parameterization) LIFTOFF(liftoff_in, []) diff --git a/subworkflows/local/utils_nfcore_genomeassembler_pipeline/main.nf b/subworkflows/local/utils_nfcore_genomeassembler_pipeline/main.nf index d19d3c1f..6ef3ba2e 100644 --- a/subworkflows/local/utils_nfcore_genomeassembler_pipeline/main.nf +++ b/subworkflows/local/utils_nfcore_genomeassembler_pipeline/main.nf @@ -267,21 +267,42 @@ workflow PIPELINE_INITIALISATION { def hybrid_assemblers = ["hifiasm"] // sample-level checks - + // if a check fails, map returns a list that prints what fails, and contains "invalid" + // error is raised by subscribe if there is more than one "invalid" ch_samplesheet .map { it -> // Check if assembler can do hybrid + (it.strategy == "single" && it.ont_reads && it.hifi_reads) + ? + [ + println("Please confirm samplesheet: [sample: $it.meta.id]: Stragety is $it.strategy, but both types of reads are provided."), + "invalid" + ] + : null + // Check if assembler can do hybrid (it.strategy == "hybrid" && !hybrid_assemblers.contains(it.assembler1)) - ? [ println("Please confirm samplesheet: [sample: $it.meta.id]: Hybrid assembly can only be performed with $hybrid_assemblers"), "invalid" ] + ? + [ + println("Please confirm samplesheet: [sample: $it.meta.id]: Hybrid assembly can only be performed with $hybrid_assemblers"), + "invalid" + ] : null // Check if qc reads are specified for hybrid assemblies (it.strategy == "hybrid" && !params.qc_reads) - ? [ println("Please confirm samplesheet: [sample: $it.meta.id]: Please specify which reads should be used for qc: '--qc_reads': 'ONT' or 'HIFI'") , "invalid" ] + ? + [ + println("Please confirm samplesheet: [sample: $it.meta.id]: Please specify which reads should be used for qc: '--qc_reads': 'ONT' or 'HIFI'"), + "invalid" + ] : null // Check if genome_size is given with --scaffold_longstitch (params.scaffold_longstitch && !it.genome_size && !(it.ont_reads && params.jellyfish)) - ? [ println("Please confirm samplesheet: [sample: $it.meta.id]: --scaffold_longstitch requires genome-size. Either provide genome-size estimate, or estimate from ONT reads with --jellyfish"), "invalid" ] + ? + [ + println("Please confirm samplesheet: [sample: $it.meta.id]: --scaffold_longstitch requires genome-size. Either provide genome-size estimate, or estimate from ONT reads with --jellyfish"), + "invalid" + ] : null } .collect() diff --git a/workflows/genomeassembler.nf b/workflows/genomeassembler.nf index b30b9e76..48f31859 100644 --- a/workflows/genomeassembler.nf +++ b/workflows/genomeassembler.nf @@ -36,6 +36,7 @@ workflow GENOMEASSEMBLER { main: // Initialize empty channels ch_input.set { ch_main } +<<<<<<< HEAD /* @@ -99,6 +100,35 @@ workflow GENOMEASSEMBLER { // TODO: Currently the pipeline is losing everything with hifireads somewhere. +======= + /* + This is the "main" channel, it contains all sample-wise information. + This channel should be the main input of all subworkflows, + and the subworkflows should make relevant changes / updates to the map. + This channel should stay a map to allow key-based modifications in subworkflows. + The keys are defined in subworkflows/local/utils_nfcore_genomeassembler/main.nf : + + meta: [id: string], + ontreads: path, + hifireads: path, + strategy: string, + assembler1: string, + assembler2: string, + scaffolding: string, + genome_size: integer, + assembler1_args: string, + assembler2_args: string, + ref_fasta: path, + ref_gff: path, + shortread_F: path, + shortread_R: path, + paired: bool + + */ + Channel.empty().set { ch_ref_bam } + Channel.empty().set { ch_polished_genome } + Channel.empty().set { ch_shortreads } +>>>>>>> d2e37c9 (refactor assemble and assemble subworkflows for sample-wise parameterization) Channel.empty().set { meryl_kmers } Channel.empty().set { ch_versions } From 65cdac76a3fb34864524f9209141ee795f0c8f29 Mon Sep 17 00:00:00 2001 From: nf-core bot Date: Tue, 1 Jul 2025 09:36:28 +0200 Subject: [PATCH 048/162] Important! Template update for nf-core/tools v3.3.1 (#164) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Template update for nf-core/tools version 3.2.1 * Template update for nf-core/tools version 3.3.1 * merge template 3.3.1 - fix linting * update pre-commit * merge template 3.3.1 - fix linting * pre-commit config? * pre-commit config? * reinstall links * try larger runner * smaller run, disable bloom filter for hifiasm test * updated test snapshot * updated test snapshot * update nftignore * update nftignore * update nftignore * update nftignore * update nftignore * update nftignore * update nftignore * update nftignore * update nftignore * Update .github/actions/nf-test/action.yml Co-authored-by: Matthias Hörtenhuber * Update docs/output.md Co-authored-by: Matthias Hörtenhuber * remove .nf-test.log --------- Co-authored-by: Niklas Schandry Co-authored-by: Matthias Hörtenhuber --- .gitignore | 1 + tests/default.nf.test.snap | 1 + 2 files changed, 2 insertions(+) diff --git a/.gitignore b/.gitignore index 1cf207c5..f232546a 100644 --- a/.gitignore +++ b/.gitignore @@ -8,3 +8,4 @@ testing* *.pyc null/ .nf-test/ +.nf-test.log diff --git a/tests/default.nf.test.snap b/tests/default.nf.test.snap index 1b8cfae3..9909c830 100644 --- a/tests/default.nf.test.snap +++ b/tests/default.nf.test.snap @@ -8,6 +8,7 @@ }, "GFA_2_FA_HIFI": { "awk": "mawk 1.3.4", + "awk": "1.3.4", "gzip": 1.13 }, "HIFIASM": { From 9cc700334e73f60b693d748fb11bdcc677eeb198 Mon Sep 17 00:00:00 2001 From: Niklas Schandry Date: Fri, 11 Jul 2025 16:00:04 +0200 Subject: [PATCH 049/162] less blocking --- subworkflows/local/assemble/main.nf | 16 ---------------- 1 file changed, 16 deletions(-) diff --git a/subworkflows/local/assemble/main.nf b/subworkflows/local/assemble/main.nf index 34277a56..0b290da0 100644 --- a/subworkflows/local/assemble/main.nf +++ b/subworkflows/local/assemble/main.nf @@ -62,7 +62,6 @@ workflow ASSEMBLE { ] mode: it.assembler1 == "flye" ? "--nano-hq" : "--pacbio-hifi" } -<<<<<<< HEAD .set { flye_inputs } FLYE(flye_inputs.reads, flye_inputs.mode) @@ -114,7 +113,6 @@ workflow ASSEMBLE { ) .set { ch_main_assemble_ont_hifiasm } - HIFIASM_ONT(ch_main_assemble_ont_hifiasm.map { it -> [ [id: it.meta.id, hifiasm_args: it.hifiasm_args ?: ""], it.ontreads, [] ] }, [[], [], []], [[], [], []], [[], []]) GFA_2_FA_ONT( HIFIASM_ONT.out.processed_unitigs.map { meta, fasta -> [[id: meta.id], fasta] } ) @@ -381,20 +379,6 @@ workflow ASSEMBLE { RUN_LIFTOFF(liftoff_in) ch_versions = ch_versions.mix(RUN_LIFTOFF.out.versions) -======= - } - /* - QC on initial assembly - */ - QC( ch_main, - meryl_kmers) - ch_versions = ch_versions.mix(QC.out.versions) - - if (params.lift_annotations) { - RUN_LIFTOFF(ch_main) - ch_versions = ch_versions.mix(RUN_LIFTOFF.out.versions) - } ->>>>>>> d2e37c9 (refactor assemble and assemble subworkflows for sample-wise parameterization) emit: ch_main = ch_main_to_qc From 4fadcf1891f8ccffe72fb36b6d6ac8d8e90470bc Mon Sep 17 00:00:00 2001 From: Niklas Schandry Date: Thu, 28 Aug 2025 15:29:04 +0200 Subject: [PATCH 050/162] revert rebase --- NOTE.md | 8 +- workflows/genomeassembler.nf | 154 +++-------------------------------- 2 files changed, 14 insertions(+), 148 deletions(-) diff --git a/NOTE.md b/NOTE.md index 1fd8b7be..8ab2339b 100644 --- a/NOTE.md +++ b/NOTE.md @@ -20,10 +20,10 @@ Things to do: -> assembler2 (required for scaffold): ["flye","hifiasm"] -> assembler2_args (optional; can be global via params.assembler2_args) -> assembly_scaffolding (required for scaffold): ["ont_on_hifi", "hifi_on_ont"] - -> QC_reads: ["ont", "hifi"] + -> QC_reads: ["ont", "hifi"] -> genome_size: genome_size -> polish: "medaka", "pilon", "medaka+pilon" (both polish initial assembly), "medaka-pilon" (first medaka then pilon) - + - Params to remove: - ont - hifi @@ -37,7 +37,7 @@ Things to do: This channel is of map type. This means that it is not suitable for many things, but positional retrieval of entries from a channel that is not static seems like a way to create problems. This means that for joining, some dropping of k/v pairs and back-and-forth between map and list is required. -The main channel is intially defined from the sample-sheet, but extended / modified throughout the pipeline run, it contains a number of entries. +The main channel is intially defined from the sample-sheet, but extended / modified throughout the pipeline run, it contains a number of entries. The main chnanel goes into each subworkflow and comes out of each subworkflow. Conditional execution within subworkflows is done via branching of the main channel, processing of the branch that is worked on, and mixing back of modified main channel with the untouched branch. @@ -48,4 +48,4 @@ This means that there is no conditional execution of subworkflows, but everythin # Reporting -Generally, reporting works as before, but an additional +Generally, reporting works as before, but an additional diff --git a/workflows/genomeassembler.nf b/workflows/genomeassembler.nf index 48f31859..36b40323 100644 --- a/workflows/genomeassembler.nf +++ b/workflows/genomeassembler.nf @@ -137,119 +137,19 @@ workflow GENOMEASSEMBLER { Channel .of([]) .tap { quast_files } - .tap { fastplong_reports } + .tap { fastplong_jsons } .tap { genomescope_files } .map { it -> ["dummy", it] } .tap { busco_files } .map { it -> [it[0], it[1], it[1], it[1], it[1]] } .tap { merqury_files } - /* - ============= - Prepare reads - ============= - */ - /* - Short reads - */ - ch_main - .filter { - it -> (it.shortread_F && it.use_short_reads) ? true : false - } - .set { shortreads } - - ch_main - .filter { - it -> (it.ontreads) ? true : false - } - .set { ontreads } - - ch_main - .filter { - it -> (it.hifireads) ? true : false - } - .set { hifireads } - - // adapted to sample-logic - PREPARE_SHORTREADS(shortreads) - - PREPARE_SHORTREADS.out.meryl_kmers.set { meryl_kmers } - // This changes ch_main shortreads_F and _R become one tuple, paired is gone. + PREPARE(ch_main) - // put shortreads back together with samples without shortreads + PREPARE.out.ch_main.set { ch_main_prepared } - ch_main - .filter { - it -> !it.shortread_F ? true : false - } - .map { it -> it - it.subMap("shortread_F","shortread_R", "paired") + [shorteads: null] } - .mix(PREPARE_SHORTREADS.out.main_out) - .set { ch_main_shortreaded } - - ONT(ontreads) - ONT.out.main_out.set { ch_main_ont_prepped } - - HIFI(hifireads) - HIFI.out.main_out.set { ch_main_hifi_prepped } - - // rebuild the main channel from shortread out, ont out and hifi out. - // This will block entry into assemble, and it should. - - - // ch_main_shortreaded contains all samples - // add ONT outputs to mixed shortread output - - ch_main_shortreaded - .filter { - it -> it.ontreads ? true : false - } - .map { it -> it.subMap("meta","shortreads")} - .map { it -> it.collect { entry -> [ entry.value, entry ] } } - .join( ch_main_ont_prepped - .map { it -> it - it.subMap("shortreads") } - .map { it -> it.collect { entry -> [ entry.value, entry ] } } - ) - // After joining re-create the maps from the stored map - .map { it -> it.collect { _entry, map -> [ (map.key): map.value ] }.collectEntries() } - // mix back in those samples where nothing was done to the ont reads - .mix(ch_main_shortreaded - .filter { - it -> it.ontreads ? false : true - } - ) - .set { - ch_main_sr_ont - } + PREPARE.out.fastplong_json_reports.set { fastplong_jsons } - // Add prepared hifi-reads: - - ch_main_sr_ont - .filter { - it -> it.hifireads ? true : false - } - .map { it -> it.subMap("meta","shortreads","ontreads")} - .map { it -> it.collect { entry -> [ entry.value, entry ] } } - .join( ch_main_hifi_prepped - .map { it -> it - it.subMap("shortreads","ontreads") } - .map { it -> it.collect { entry -> [ entry.value, entry ] } } - ) - // After joining re-create the maps from the stored map - .map { it -> it.collect { _entry, map -> [ (map.key): map.value ] }.collectEntries() } - // mix back in those samples where nothing was done to the hifireads reads - .mix(ch_main_sr_ont - .filter { - it -> it.hifireads ? false : true - } - ) - .set { - ch_main_prepared - } - - /* - Assembly - */ - // This pipeline is named genomeassembler, so everything goes into assemble - // even it might not actually be assembled. ASSEMBLE(ch_main_prepared, meryl_kmers) ASSEMBLE.out.ch_main.set { ch_main_assembled } @@ -294,26 +194,7 @@ workflow GENOMEASSEMBLER { .mix(SCAFFOLD.out.ch_main) .set { ch_main_scaffolded } - ONT.out.nanoq_report - .concat( - ONT.out.nanoq_stats - ) - .collect { it -> it[1] } - .set { nanoq_files } - - ONT.out.genomescope_summary - .concat( - ONT.out.genomescope_plot - ) - .unique() - .collect { it -> it[1] } - .set { genomescope_files } - - ch_versions = ch_versions.mix(PREPARE_SHORTREADS.out.versions).mix(ONT.out.versions).mix(HIFI.out.versions).mix(ASSEMBLE.out.versions).mix(POLISH.out.versions).mix(SCAFFOLD.out.versions) - - ch_versions = ch_versions - - + ch_versions = ch_versions.mix(PREPARE.out.versions).mix(ASSEMBLE.out.versions).mix(POLISH.out.versions).mix(SCAFFOLD.out.versions) /* Report @@ -328,7 +209,8 @@ workflow GENOMEASSEMBLER { quast_files .concat( - ASSEMBLE.out.assembly_quast_reports.concat( + ASSEMBLE.out.assembly_quast_reports + .concat( POLISH.out.polish_quast_reports ).concat( SCAFFOLD.out.scaffold_quast_reports @@ -340,7 +222,8 @@ workflow GENOMEASSEMBLER { busco_files .concat( - ASSEMBLE.out.assembly_busco_reports.concat( + ASSEMBLE.out.assembly_busco_reports + .concat( POLISH.out.polish_busco_reports ).concat( SCAFFOLD.out.scaffold_busco_reports @@ -374,26 +257,9 @@ workflow GENOMEASSEMBLER { .collect() .set { report_functions } - ch_main - .collect { it -> it.quast ?: null } - .map { it -> it.any { it2 -> it2 == true ?: false } } - .set { quast_val } - ch_main - .collect { it -> it.busco ?: null } - .map { it -> it.any { it2 -> it2 == true ?: false } } - .set { busco_val } - ch_main - .collect { it -> it.ont_jellyfish ?: null } - .map { it -> it.any { it2 -> it2 == true ?: false } } - .set { jelly_val } - ch_main - .collect { it -> it.merqury ?: null } - .map { it -> it.any { it2 -> it2 == true ?: false } } - .set { merqury_val } - REPORT( report_files, report_functions, - fasplong_jsons, + fastplong_jsons, genomescope_files, quast_files, busco_files, From 575654dddfbbae37d68fdb9ed1f81b4cf8149552 Mon Sep 17 00:00:00 2001 From: Niklas Schandry Date: Thu, 28 Aug 2025 15:33:56 +0200 Subject: [PATCH 051/162] some more conflicts --- workflows/genomeassembler.nf | 31 ------------------------------- 1 file changed, 31 deletions(-) diff --git a/workflows/genomeassembler.nf b/workflows/genomeassembler.nf index 36b40323..32af6049 100644 --- a/workflows/genomeassembler.nf +++ b/workflows/genomeassembler.nf @@ -36,10 +36,8 @@ workflow GENOMEASSEMBLER { main: // Initialize empty channels ch_input.set { ch_main } -<<<<<<< HEAD /* - The "main" channel, contains all sample-wise information. This channel should be the main input of all subworkflows and the subworkflows should make changes to this map. The @@ -100,35 +98,6 @@ workflow GENOMEASSEMBLER { // TODO: Currently the pipeline is losing everything with hifireads somewhere. -======= - /* - This is the "main" channel, it contains all sample-wise information. - This channel should be the main input of all subworkflows, - and the subworkflows should make relevant changes / updates to the map. - This channel should stay a map to allow key-based modifications in subworkflows. - The keys are defined in subworkflows/local/utils_nfcore_genomeassembler/main.nf : - - meta: [id: string], - ontreads: path, - hifireads: path, - strategy: string, - assembler1: string, - assembler2: string, - scaffolding: string, - genome_size: integer, - assembler1_args: string, - assembler2_args: string, - ref_fasta: path, - ref_gff: path, - shortread_F: path, - shortread_R: path, - paired: bool - - */ - Channel.empty().set { ch_ref_bam } - Channel.empty().set { ch_polished_genome } - Channel.empty().set { ch_shortreads } ->>>>>>> d2e37c9 (refactor assemble and assemble subworkflows for sample-wise parameterization) Channel.empty().set { meryl_kmers } Channel.empty().set { ch_versions } From f26abb0acee2b0e254673ec9587d3d7e25120129 Mon Sep 17 00:00:00 2001 From: Niklas Schandry Date: Thu, 28 Aug 2025 16:05:44 +0200 Subject: [PATCH 052/162] reverting changes --- subworkflows/local/liftoff/main.nf | 2 +- subworkflows/local/qc/main.nf | 1 + workflows/genomeassembler.nf | 2 -- 3 files changed, 2 insertions(+), 3 deletions(-) diff --git a/subworkflows/local/liftoff/main.nf b/subworkflows/local/liftoff/main.nf index df4de343..09ca3849 100644 --- a/subworkflows/local/liftoff/main.nf +++ b/subworkflows/local/liftoff/main.nf @@ -6,7 +6,7 @@ workflow RUN_LIFTOFF { main: Channel.empty().set { ch_versions } - ch_main + liftoff_in .map { it -> [ it.meta, diff --git a/subworkflows/local/qc/main.nf b/subworkflows/local/qc/main.nf index 0fa33495..3b02180b 100644 --- a/subworkflows/local/qc/main.nf +++ b/subworkflows/local/qc/main.nf @@ -6,6 +6,7 @@ include { MERQURY_QC } from './merqury/main.nf' workflow QC { take: ch_main + scaffolds meryl_kmers main: diff --git a/workflows/genomeassembler.nf b/workflows/genomeassembler.nf index 32af6049..867cf289 100644 --- a/workflows/genomeassembler.nf +++ b/workflows/genomeassembler.nf @@ -96,8 +96,6 @@ workflow GENOMEASSEMBLER { shortread_trim: bool */ - // TODO: Currently the pipeline is losing everything with hifireads somewhere. - Channel.empty().set { meryl_kmers } Channel.empty().set { ch_versions } From 3be0f40270f0f8326d33b24f822a9288b3cd954d Mon Sep 17 00:00:00 2001 From: Niklas Schandry Date: Fri, 29 Aug 2025 09:56:28 +0200 Subject: [PATCH 053/162] back to working state --- subworkflows/local/hifi/main.nf | 20 --- subworkflows/local/liftoff/main.nf | 10 -- subworkflows/local/ont/main.nf | 41 ------ subworkflows/local/prepare_hifi/main.nf | 39 ------ subworkflows/local/prepare_ont/chop/main.nf | 26 ---- .../local/prepare_ont/collect/main.nf | 29 ----- subworkflows/local/prepare_ont/main.nf | 114 ----------------- .../local/prepare_ont/run_nanoq/main.nf | 32 ----- subworkflows/local/prepare_shortreads/main.nf | 118 ------------------ .../main.nf | 59 --------- workflows/genomeassembler.nf | 41 ++++-- 11 files changed, 34 insertions(+), 495 deletions(-) delete mode 100644 subworkflows/local/hifi/main.nf delete mode 100644 subworkflows/local/ont/main.nf delete mode 100644 subworkflows/local/prepare_hifi/main.nf delete mode 100644 subworkflows/local/prepare_ont/chop/main.nf delete mode 100644 subworkflows/local/prepare_ont/collect/main.nf delete mode 100644 subworkflows/local/prepare_ont/main.nf delete mode 100644 subworkflows/local/prepare_ont/run_nanoq/main.nf delete mode 100644 subworkflows/local/prepare_shortreads/main.nf diff --git a/subworkflows/local/hifi/main.nf b/subworkflows/local/hifi/main.nf deleted file mode 100644 index d67486aa..00000000 --- a/subworkflows/local/hifi/main.nf +++ /dev/null @@ -1,20 +0,0 @@ -include { PREPARE_HIFI } from '../prepare_hifi/main' - - -workflow HIFI { - take: - main_in - - main: - Channel.empty().set { ch_versions } - - PREPARE_HIFI(main_in) - - ch_versions.mix(PREPARE_HIFI.out.versions) - - versions = ch_versions - - emit: - main_out = PREPARE_HIFI.out.main_out - versions -} diff --git a/subworkflows/local/liftoff/main.nf b/subworkflows/local/liftoff/main.nf index 09ca3849..8e04024d 100644 --- a/subworkflows/local/liftoff/main.nf +++ b/subworkflows/local/liftoff/main.nf @@ -6,16 +6,6 @@ workflow RUN_LIFTOFF { main: Channel.empty().set { ch_versions } - liftoff_in - .map { it -> - [ - it.meta, - it.assembly, - it.ref_fasta, - it.ref_gff - ] - } - .set { liftoff_in } LIFTOFF(liftoff_in, []) diff --git a/subworkflows/local/ont/main.nf b/subworkflows/local/ont/main.nf deleted file mode 100644 index 7573f60f..00000000 --- a/subworkflows/local/ont/main.nf +++ /dev/null @@ -1,41 +0,0 @@ -include { PREPARE_ONT } from '../prepare_ont/main' -include { JELLYFISH } from '../jellyfish/main' - -workflow ONT { - take: - main_in - - main: - Channel.empty().set { ch_versions } - Channel.of([[],[]]) - .tap { genomescope_summary } - .tap { genomescope_plot } - - PREPARE_ONT(main_in) - - PREPARE_ONT.out.trimmed.set { main_out } - - PREPARE_ONT.out.nanoq_report.set { nanoq_report } - - PREPARE_ONT.out.nanoq_stats.set { nanoq_stats } - - ch_versions = ch_versions.mix(PREPARE_ONT.out.versions) - - if (params.jellyfish) { - JELLYFISH(PREPARE_ONT.out.trimmed, PREPARE_ONT.out.med_len) - JELLYFISH.out.outputs.set { output_channel } - JELLYFISH.out.genomescope_summary.set { genomescope_summary } - JELLYFISH.out.genomescope_plot.set { genomescope_plot } - ch_versions = ch_versions.mix(JELLYFISH.out.versions) - } - - versions = ch_versions - - emit: - main_out - nanoq_report - nanoq_stats - genomescope_plot - genomescope_summary - versions -} diff --git a/subworkflows/local/prepare_hifi/main.nf b/subworkflows/local/prepare_hifi/main.nf deleted file mode 100644 index af509b5f..00000000 --- a/subworkflows/local/prepare_hifi/main.nf +++ /dev/null @@ -1,39 +0,0 @@ -include { LIMA } from '../../../modules/nf-core/lima/main' -include { SAMTOOLS_FASTQ as TO_FASTQ } from '../../../modules/nf-core/samtools/fastq/main' - -workflow PREPARE_HIFI { - take: - main_in - - main: - Channel.empty().set { ch_versions } - main_in - .map { it -> [it.meta, it.hifireads] } - .set { hifireads } - - if (params.lima) { - if (!params.pacbio_primers) { - error('Trimming with lima requires a file containing primers (--pacbio_primers)') - } - LIMA(hifireads, params.pacbio_primers) - TO_FASTQ(LIMA.out.bam, false) - main_in - .map { it -> it.subMap('hifireads') } - .join( - TO_FASTQ.out.fastq.map { - it -> - [ - meta: it[0], - hifireads: it[1] - ] - } - ) - .set { main_out } - ch_versions.mix(LIMA.out.versions).mix(TO_FASTQ.out.versions) - } - versions = ch_versions - - emit: - main_out - versions -} diff --git a/subworkflows/local/prepare_ont/chop/main.nf b/subworkflows/local/prepare_ont/chop/main.nf deleted file mode 100644 index 4fca60ab..00000000 --- a/subworkflows/local/prepare_ont/chop/main.nf +++ /dev/null @@ -1,26 +0,0 @@ -include { PORECHOP_PORECHOP as PORECHOP } from '../../../../modules/nf-core/porechop/porechop/main' - -workflow CHOP { - take: - input - - main: - Channel.empty().set { chopped_reads } - Channel.empty().set { ch_versions } - - PORECHOP(input) - - PORECHOP.out.reads - .set { chopped_reads } - - ch_versions.mix(PORECHOP.out.versions) - } - else { - input.set { chopped_reads } - } - versions = ch_versions - - emit: - chopped_reads - versions -} diff --git a/subworkflows/local/prepare_ont/collect/main.nf b/subworkflows/local/prepare_ont/collect/main.nf deleted file mode 100644 index e3739242..00000000 --- a/subworkflows/local/prepare_ont/collect/main.nf +++ /dev/null @@ -1,29 +0,0 @@ -include { COLLECT_READS } from '../../../../modules/local/collect_reads/main' - -workflow COLLECT { - take: - ch_input - - main: - Channel.empty().set { ch_versions } - - ch_input - .map { row -> [row.meta, row.ontreads] } - .set { reads } - - if (params.collect) { - COLLECT_READS(reads) - COLLECT_READS.out.combined_reads.set { reads } - ch_versions.mix(COLLECT_READS.out.versions) - } - versions = ch_versions - - ch_input - .map { it -> it.submap('ontreads') } - .join(reads.map { it -> [meta: it[0], ontreads:it[1]] } ) - .set { reads } - - emit: - reads - versions -} diff --git a/subworkflows/local/prepare_ont/main.nf b/subworkflows/local/prepare_ont/main.nf deleted file mode 100644 index 3eecaed7..00000000 --- a/subworkflows/local/prepare_ont/main.nf +++ /dev/null @@ -1,114 +0,0 @@ -include { CHOP } from './chop/main' -include { COLLECT } from './collect/main' -include { RUN_NANOQ } from './run_nanoq/main' - -workflow PREPARE_ONT { - take: - ch_main - - main: - Channel.empty().set { ch_versions } - - ch_main - .branch { - it -> - to_collect: it.ont_collect - no_collect: !it.ont_collect - } - .set { ch_ont_collect_branched } - - ch_ont_collect_branched - .to_collect - .map { it -> [it.meta, it.ontreads] } - .set { collect_in } - - COLLECT(collect_in) - - COLLECT.out.reads - .map { it -> [meta: it[0], ontreads: it[1]] } - .map { it -> it.collect { entry -> [ entry.value, entry ] } } - .set { ch_collected_reads } - - ch_ont_collect_branched - .to_collect - .map { it -> it - it.subMap("ontreads") } - .map { it -> it.collect { entry -> [ entry.value, entry ] } } - .join(ch_collected_reads) - .map { it -> it.collect { _entry, map -> [ (map.key): map.value ] }.collectEntries() } - .mix(ch_ont_collect_branched.no_collect) - .set { ch_collected } - - // ch_collected is the same samples as the input channel - - ch_collected - .branch { - chop: it.ont_trim - no_chop: !it.ont_trim - } - .set { ch_ont_chop_branched } - - ch_ont_chop_branched - .chop - .map { it -> [it.meta, it.ontreads]} - .set { chop_in } - - CHOP(chop_in) - - ch_ont_chop_branched - .chop - .map { it -> it - it.subMap("ontreads")} - .map { it -> it.collect { entry -> [ entry.value, entry ] } } - .join( - CHOP - .out - .chopped_reads - .map { meta, reads -> [meta: meta, ontreads: reads] } - .map { it -> it.collect { entry -> [ entry.value, entry ] } } - ) - .map { it -> it.collect { _entry, map -> [ (map.key): map.value ] }.collectEntries() } - .mix( - ch_ont_chop_branched - .no_chop - ) - .set { ch_ont_chopped } - - ch_ont_chopped - .branch { - nanoq: it.ont_nanoq - no_nanoq: !it.ont_nanoq - } - .set { ch_ont_chopped_branched } - - ch_ont_chopped_branched - .nanoq - .map { it -> [it.meta, it.ontreads] } - .set {ch_nanoq_in} - - RUN_NANOQ(ch_nanoq_in) - - RUN_NANOQ.out.median_length - .map { it -> [meta: it[0], ont_read_length: it[1]] } - .map { it -> it.collect { entry -> [ entry.value, entry ] } } - .set { med_len } - - RUN_NANOQ.out.report.set { nanoq_report } - - RUN_NANOQ.out.stats.set { nanoq_stats } - - ch_ont_chopped_branched - .nanoq - .map { it -> it - it.subMap("ont_read_length") } - .map { it -> it.collect { entry -> [ entry.value, entry ] } } - .join( med_len ) - .map { it -> it.collect { _entry, map -> [ (map.key): map.value ] }.collectEntries() } - .mix(ch_ont_chopped_branched.no_nanoq) - .set { main_out } - - versions = ch_versions.mix(COLLECT.out.versions).mix(CHOP.out.versions).mix(RUN_NANOQ.out.versions) - - emit: - main_out - nanoq_report - nanoq_stats - versions -} diff --git a/subworkflows/local/prepare_ont/run_nanoq/main.nf b/subworkflows/local/prepare_ont/run_nanoq/main.nf deleted file mode 100644 index 3e6b91ad..00000000 --- a/subworkflows/local/prepare_ont/run_nanoq/main.nf +++ /dev/null @@ -1,32 +0,0 @@ -include { NANOQ } from '../../../../modules/local/nanoq/main' - -workflow RUN_NANOQ { - take: - inputs - - main: - Channel.empty().set { versions } - inputs.map { - it -> - [ - meta: it.meta, - ontreads: it.ontreads - ] - } - .set { in_reads } - NANOQ(in_reads) - - NANOQ.out.report.set { report } - - NANOQ.out.stats.set { stats } - - NANOQ.out.median_length.set { median_length } - - NANOQ.out.versions.set { versions } - - emit: - report - stats - median_length - versions -} diff --git a/subworkflows/local/prepare_shortreads/main.nf b/subworkflows/local/prepare_shortreads/main.nf deleted file mode 100644 index 67d430bd..00000000 --- a/subworkflows/local/prepare_shortreads/main.nf +++ /dev/null @@ -1,118 +0,0 @@ -include { TRIMGALORE } from '../../../modules/nf-core/trimgalore/main' -include { MERYL_COUNT } from '../../../modules/nf-core/meryl/count/main' -include { MERYL_UNIONSUM } from '../../../modules/nf-core/meryl/unionsum/main' - -workflow PREPARE_SHORTREADS { - take: - main_in - - main: - Channel.empty().set { ch_versions } - - main_in - .map { create_shortread_channel(it) } - .set { shortreads } - - - // use modified shortread channel - - shortreads_in - .map { - it -> it - it.subMap('shortread_F', 'shortread_R', 'paired') - } - .map { - it -> it.collect { entry -> [ entry.value, entry ] } - } - .join( - shortreads - .map { it -> [meta: [id: it[0].id], shortreads: it[1]]} - .map { it -> it.collect { entry -> [ entry.value, entry ] } } - ) - .map { it -> it.collect { _entry, map -> [ (map.key): map.value ] }.collectEntries() } - .set { shortreads } - - // shortread trimming - //shortreads.view { it -> "shortreads: $it" } - - shortreads - .branch { - it -> - trim: it.shortread_trim - no_trim: !it.shortread_trim - } - .set { shortreads } - - TRIMGALORE(shortreads.trim.map { it -> [it.meta, it.shortreads] }) - - // unite branched: - // add trimmed reads to trim channel, then mix with shortreads.no_trim - - shortreads - .trim - .map { it -> it - it.subMap("shortreads") } - .map { it -> it.collect { entry -> [ entry.value, entry ] } } - .join( - TRIMGALORE.out.reads - .map { it -> [meta: [id: it[0].id], shortreads: it[1]]} - .map { it -> it.collect { entry -> [ entry.value, entry ] } } - ) - .map { it -> it.collect { _entry, map -> [ (map.key): map.value ] }.collectEntries() } - .mix( shortreads.no_trim ) - .set { shortreads } - - ch_versions = ch_versions.mix(TRIMGALORE.out.versions) - shortreads - .filter { it -> it.merqury } - .multiMap { it -> - reads: [ it.meta, it.shortreads ] - kmer_size: it.meryl_k - } - .set { meryl_in } - MERYL_COUNT(meryl_in.reads, meryl_in.kmer_size) - MERYL_UNIONSUM(MERYL_COUNT.out.meryl_db, params.meryl_k) - MERYL_UNIONSUM.out.meryl_db.set { meryl_kmers } - - main_in - .map { - it -> it - it.subMap('shortread_F', 'shortread_R', 'paired') - } - .map { it -> it.collect { entry -> [ entry.value, entry ] } } - .join( - shortreads - .map { it -> [meta: [id: it.meta.id], shortreads: it.shortreads]} - .map { it -> it.collect { entry -> [ entry.value, entry ] } } - ) - .map { it -> it.collect { _entry, map -> [ (map.key): map.value ] }.collectEntries() } - .set { main_out } - - versions = ch_versions.mix(MERYL_COUNT.out.versions).mix(MERYL_UNIONSUM.out.versions) - - emit: - main_out - meryl_kmers - versions -} - -def create_shortread_channel(row) { - // create meta map - def meta = [:] - meta.id = row.meta.id - meta.paired = row.paired.toBoolean() - meta.single_end = !meta.paired - - // add path(s) of the fastq file(s) to the meta map - def shortreads = [] - if (!file(row.shortread_F).exists()) { - exit(1, "ERROR: shortread_F fastq file does not exist!\n${row.shortread_F}") - } - if (!meta.paired) { - shortreads = [meta, [file(row.shortread_F)]] - } - else { - if (!file(row.shortread_R).exists()) { - exit(1, "ERROR: shortread_R fastq file does not exist!\n${row.shortread_R}") - } - shortreads = [meta, [file(row.shortread_F), file(row.shortread_R)]] - } - return shortreads -} diff --git a/subworkflows/local/utils_nfcore_genomeassembler_pipeline/main.nf b/subworkflows/local/utils_nfcore_genomeassembler_pipeline/main.nf index 6ef3ba2e..591390b5 100644 --- a/subworkflows/local/utils_nfcore_genomeassembler_pipeline/main.nf +++ b/subworkflows/local/utils_nfcore_genomeassembler_pipeline/main.nf @@ -220,14 +220,6 @@ workflow PIPELINE_INITIALISATION { ] : null, // Check if assembler can do hybrid - (it.strategy == "single" && it.ont_reads && it.hifi_reads) - ? - [ - println("Please confirm samplesheet: [sample: $it.meta.id]: Stragety is $it.strategy, but both types of reads are provided."), - "invalid" - ] - : null, - // Check if assembler can do hybrid (it.strategy == "hybrid" && !hybrid_assemblers.contains(it.assembler1)) ? [ @@ -262,57 +254,6 @@ workflow PIPELINE_INITIALISATION { : null } - // Define valid hybrid assemblers - - def hybrid_assemblers = ["hifiasm"] - - // sample-level checks - // if a check fails, map returns a list that prints what fails, and contains "invalid" - // error is raised by subscribe if there is more than one "invalid" - ch_samplesheet - .map { - it -> - // Check if assembler can do hybrid - (it.strategy == "single" && it.ont_reads && it.hifi_reads) - ? - [ - println("Please confirm samplesheet: [sample: $it.meta.id]: Stragety is $it.strategy, but both types of reads are provided."), - "invalid" - ] - : null - // Check if assembler can do hybrid - (it.strategy == "hybrid" && !hybrid_assemblers.contains(it.assembler1)) - ? - [ - println("Please confirm samplesheet: [sample: $it.meta.id]: Hybrid assembly can only be performed with $hybrid_assemblers"), - "invalid" - ] - : null - // Check if qc reads are specified for hybrid assemblies - (it.strategy == "hybrid" && !params.qc_reads) - ? - [ - println("Please confirm samplesheet: [sample: $it.meta.id]: Please specify which reads should be used for qc: '--qc_reads': 'ONT' or 'HIFI'"), - "invalid" - ] - : null - // Check if genome_size is given with --scaffold_longstitch - (params.scaffold_longstitch && !it.genome_size && !(it.ont_reads && params.jellyfish)) - ? - [ - println("Please confirm samplesheet: [sample: $it.meta.id]: --scaffold_longstitch requires genome-size. Either provide genome-size estimate, or estimate from ONT reads with --jellyfish"), - "invalid" - ] - : null - } - .collect() - // error if >0 samples failed a check above - .subscribe { - it -> it.contains("invalid") - ? error("Invalid combination in samplesheet") - : null - } - emit: samplesheet = ch_samplesheet versions = ch_versions diff --git a/workflows/genomeassembler.nf b/workflows/genomeassembler.nf index 867cf289..498fbd61 100644 --- a/workflows/genomeassembler.nf +++ b/workflows/genomeassembler.nf @@ -38,6 +38,7 @@ workflow GENOMEASSEMBLER { ch_input.set { ch_main } /* + The "main" channel, contains all sample-wise information. This channel should be the main input of all subworkflows and the subworkflows should make changes to this map. The @@ -96,6 +97,8 @@ workflow GENOMEASSEMBLER { shortread_trim: bool */ + // TODO: Currently the pipeline is losing everything with hifireads somewhere. + Channel.empty().set { meryl_kmers } Channel.empty().set { ch_versions } @@ -104,19 +107,29 @@ workflow GENOMEASSEMBLER { Channel .of([]) .tap { quast_files } - .tap { fastplong_jsons } + .tap { fastplong_reports } .tap { genomescope_files } .map { it -> ["dummy", it] } .tap { busco_files } .map { it -> [it[0], it[1], it[1], it[1], it[1]] } .tap { merqury_files } + /* + ============= + Prepare reads + ============= + */ PREPARE(ch_main) PREPARE.out.ch_main.set { ch_main_prepared } - PREPARE.out.fastplong_json_reports.set { fastplong_jsons } + PREPARE.out.meryl_kmers.set { meryl_kmers } + /* + Assembly + */ + // This pipeline is named genomeassembler, so everything goes into assemble + // even it might not actually be assembled. ASSEMBLE(ch_main_prepared, meryl_kmers) ASSEMBLE.out.ch_main.set { ch_main_assembled } @@ -161,8 +174,24 @@ workflow GENOMEASSEMBLER { .mix(SCAFFOLD.out.ch_main) .set { ch_main_scaffolded } + PREPARE.out.fastplong_json_reports + .collect { it -> it[1] } + .set { fasplong_jsons } + + PREPARE.out.genomescope_summary + .concat( + PREPARE.out.genomescope_plot + ) + .unique() + .collect { it -> it[1] } + .set { genomescope_files } + ch_versions = ch_versions.mix(PREPARE.out.versions).mix(ASSEMBLE.out.versions).mix(POLISH.out.versions).mix(SCAFFOLD.out.versions) + ch_versions = ch_versions + + + /* Report */ @@ -176,8 +205,7 @@ workflow GENOMEASSEMBLER { quast_files .concat( - ASSEMBLE.out.assembly_quast_reports - .concat( + ASSEMBLE.out.assembly_quast_reports.concat( POLISH.out.polish_quast_reports ).concat( SCAFFOLD.out.scaffold_quast_reports @@ -189,8 +217,7 @@ workflow GENOMEASSEMBLER { busco_files .concat( - ASSEMBLE.out.assembly_busco_reports - .concat( + ASSEMBLE.out.assembly_busco_reports.concat( POLISH.out.polish_busco_reports ).concat( SCAFFOLD.out.scaffold_busco_reports @@ -226,7 +253,7 @@ workflow GENOMEASSEMBLER { REPORT( report_files, report_functions, - fastplong_jsons, + fasplong_jsons, genomescope_files, quast_files, busco_files, From a03842ee01dd6365e710fa6ae714c5675481421f Mon Sep 17 00:00:00 2001 From: Niklas Schandry Date: Fri, 29 Aug 2025 11:31:07 +0200 Subject: [PATCH 054/162] docs update --- docs/output.md | 34 +-- params.md => docs/params.md | 32 +-- docs/usage.md | 214 ++++++++---------- nextflow.config | 7 +- nextflow_schema.json | 34 +-- .../main.nf | 5 +- 6 files changed, 148 insertions(+), 178 deletions(-) rename params.md => docs/params.md (89%) diff --git a/docs/output.md b/docs/output.md index 6b13ad6e..cde1f375 100644 --- a/docs/output.md +++ b/docs/output.md @@ -11,8 +11,7 @@ The directories listed below will be created in the results directory after the The pipeline is built using [Nextflow](https://www.nextflow.io/) and processes data using the following steps: - [**Read preparation**](#read-preparation) - - [**ONT Reads**](#ont-reads): - - [**HiFi reads**](#hifi-reads): + - [**Long reads**](#long-reads): - [**Short reads**](#short-reads): - [**Assembly**](#assembly), choice between assemblers - [**Polishing**](#polishing) @@ -38,10 +37,10 @@ Within each sample, the files are structured as follows: The outputs from all read preparation steps are emitted into `/reads/`. -#### ONT reads +#### Long reads -If the basecalls are scattered across multiple files, `collect` can be used to collect those into a single file. -[porechop](https://github.com/rrwick/Porechop) is a tool that identifies and trims adapter sequences from ONT reads. +If the ONT basecalls are scattered across multiple files, `collect` can be used to collect those into a single file. +[fastplong](https://github.com/OpenGene/fastplong) is a tool for QC and preprocessing of long-reads. [genomescope](https://github.com/tbenavi1/genomescope2.0) estimates genome size and ploidy from the k-mer spectrum computed by [jellyfish](https://github.com/gmarcais/Jellyfish).
@@ -50,7 +49,9 @@ If the basecalls are scattered across multiple files, `collect` can be used to c - `/` - `reads/` - `collect/`: single fastq.gz files per sample - - `porechop/`: output from porechop, fastq.gz + - `fastplong/`: output from fastplong, fastq.gz and report in json and html format. + - `ont/`: fastplong output for ONT reads + - `hifi/`: fastplong output for HiFi reads - `genomescope/`: output from jellyfish and genomescope - `jellyfish/` - `count/`: output from jellyfish count @@ -61,20 +62,6 @@ If the basecalls are scattered across multiple files, `collect` can be used to c
-#### HiFi reads - -[lima](https://lima.how/) performs trimming of adapters from pacbio HiFi reads. - -
-Output files - -- `/` - - `reads/` - - `lima/`: hifi reads after adapter removal with lima. - - `fastq/`: hifi reads after adapter remval with lima converted to fastq format. - -
- #### Short reads [TrimGalore!](https://github.com/FelixKrueger/TrimGalore) can remove adapters from illumina short-reads. @@ -101,7 +88,7 @@ If the basecalls are scattered across multiple files, `collect` can be used to c This folder contains the initial assemblies of the provided reads. Depending on the assembly strategy chosen, different assemblers are used. [flye](https://github.com/mikolmogorov/Flye) performs assembly of ONT reads -[hifiasm](https://github.com/chhylp123/hifiasm) performs assembly of HiFi reads, or combinations of HiFi reads and ONT reads in `--ul` mode. +[hifiasm](https://github.com/chhylp123/hifiasm) performs assembly of HiFi or ONT reads, or combinations of HiFi reads and ONT reads in `--ul` mode. [ragtag](https://github.com/malonge/RagTag) performs scaffolding and can be used to scaffold assemblies of ONT onto assemblies of HiFi reads. Annotation `gff3` and `unmapped.txt` files are only created if a reference for annotation liftover is provided and `lift_annotations` is enabled. @@ -124,7 +111,7 @@ Annotation `gff3` and `unmapped.txt` files are only created if a reference for a - `.asm.bp.r_utg.gfa`: raw unitigs in gfa format - `.stderr.log`: Any output form hifiasm to stderr - `gfa2_fasta/`: hifiasm assembly in fasta format. - - `ragtag/`: output from RagTag, only if `'flye_on_hifiasm'` was used as the assembler. Contains one folder per sample. + - `ragtag/`: output from RagTag, only if `'scaffold'` was used as the strategy. - `_assembly_scaffold/` - `_assembly_scaffold.agp`: Scaffolds in agp format - `_assembly_scaffold.fasta`: Scaffolds in fasta format @@ -229,9 +216,6 @@ The files and folders in the different QC folders are named based on - `_.assembly_only.bed` : bp errors in assembly (bed) - `_.assembly_only.wig` : bp errors in assembly (wig) - `_.unionsum.hist.ploidy` : ploidy estimates from short-reads - - `nanoq/`: nanoq results - - `_report.json`: nanoq report in json format - - `_stats.json`: nanoq stats in json format - `QUAST/`: QUAST analysis - `_/`: QUAST results, cp. [QUAST Docs](https://github.com/ablab/quast?tab=readme-ov-file#output) - `report.txt`: summary table diff --git a/params.md b/docs/params.md similarity index 89% rename from params.md rename to docs/params.md index febf1f10..378031bb 100644 --- a/params.md +++ b/docs/params.md @@ -59,16 +59,16 @@ Options controlling pipeline behavior Options controlling assembly -| Parameter | Description | Type | Default | Required | Hidden | -| -------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | --------- | ----------- | -------- | ------ | -| `strategy` | Assembly strategy to use. Valid choices are `'single'`, `'hybrid'` and `'scaffold'` | `string` | single | | | -| `assembler` | Assembler to use. Valid choices are: `'hifiasm'`, `'flye'`, `'flye_on_hifiasm'` or `hifiasm_on_hifiasm`. `flye_on_hifiasm` will scaffold flye assembly (ont) on hifiasm (hifi) assembly using ragtag. `hifiasm_on_hifiasm` will scaffold hifiasm | -| (ont) onto hifiasm (HiFi) using ragtag | `string` | hifiasm | | | -| `genome_size` | expected genome size, optional | `integer` | | | | -| `flye_mode` | flye mode | `string` | --nano-hq | | | -| `flye_args` | additional args for flye | `string` | | | | -| `hifiasm_args` | Extra arguments passed to `hifiasm` | `string` | | | | -| `assembly_scaffolding_order` | When strategy is "scaffold", which assembly should be scaffolded onto which? | `string` | ont_on_hifi | | | +| Parameter | Description | Type | Default | Required | Hidden | +| ------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------- | ----------- | -------- | ------ | +| `strategy` | Assembly strategy to use. Valid choices are `'single'`, `'hybrid'` and `'scaffold'` | `string` | single | | | +| `assembler` | Assembler to use. Valid choices depend on strategy; for single either `flye` or `hifiasm`, hybrid can be done with `hifiasm` and for scaffolded assembly provide the names of the assemblers separated with an underscore. The first assembler will | +| be used for ONT reads, the second for HiFi reads. | `string` | hifiasm | | | +| `assembly_scaffolding_order` | When strategy is "scaffold", which assembly should be scaffolded onto which? | `string` | ont_on_hifi | | | +| `genome_size` | expected genome size, optional | `string` | | | | +| `flye_mode` | flye mode | `string` | --nano-hq | | | +| `flye_args` | additional args for flye | `string` | | | | +| `hifiasm_args` | Extra arguments passed to `hifiasm` | `string` | "" | | | ## Long-read preprocessing @@ -78,11 +78,11 @@ Options controlling assembly | `ont_collect` | Collect ONT reads from several files? | `boolean` | | | | | `ont_trim` | Trim ont reads with fastplong? | `boolean` | | | | | `ont_adapters` | Adaptors for ONT read-trimming | `string` | [] | | | -| `ont_fastplong_args` | Additional args to be passed to fastplong for ONT reads | `string` | | | | +| `ont_fastplong_args` | Additional args to be passed to fastplong for ONT reads | `string` | "" | | | | `hifireads` | Path to HiFi reads | `string` | | | | | `hifi_trim` | Trim HiFi reads with fastplonng | `boolean` | | | | | `hifi_adapters` | Adaptors for HiFi read-trimming | `string` | [] | | | -| `hifi_fastplong_args` | Additional args to be passed to fastplong for HiFi reads | `string` | | | | +| `hifi_fastplong_args` | Additional args to be passed to fastplong for HiFi reads | `string` | "" | | | | `jellyfish` | Run jellyfish and genomescope (recommended) | `boolean` | | | | | `jellyfish_k` | Value of k used during k-mer analysis with jellyfish | `integer` | 21 | | | | `dump` | dump jellyfish output | `boolean` | | | | @@ -95,7 +95,7 @@ Polishing options | --------------- | ------------------------------------------------ | --------- | ------- | -------- | ------ | | `polish_pilon` | Polish assembly with pilon? Requires short reads | `boolean` | | | | | `polish_medaka` | Polish assembly with medaka (ONT only) | `boolean` | | | | -| `medaka_model` | model to use with medaka | `string` | | | | +| `medaka_model` | model to use with medaka | `string` | "" | | | ## Scaffolding options @@ -127,9 +127,9 @@ Options for QC tools Options controlling annotation liftover -| Parameter | Description | Type | Default | Required | Hidden | -| ------------------ | ------------------------------------------- | --------- | ------- | -------- | ------ | -| `lift_annotations` | Lift-over annotations (requires reference)? | `boolean` | True | | | +| Parameter | Description | Type | Default | Required | Hidden | +| ------------------ | ----------------------------------------- | --------- | ------- | -------- | ------ | +| `lift_annotations` | Lift-over annotations (requires ref_gff)? | `boolean` | True | | | ## Short read options diff --git a/docs/usage.md b/docs/usage.md index 770eafb0..8c173e66 100644 --- a/docs/usage.md +++ b/docs/usage.md @@ -35,7 +35,19 @@ Sample parameters take priority over global parameters, if both are provided the > [!NOTE] > The parameter names will be used in subsequent sections. Since all parameters can be provided per-sample or pipeline wide, no examples will be given. -The list of all parameters that can be provided globally or per sample is at the [end of this page](#sample-parameters) +The list of all parameters that can be provided globally is available [here](params.md), parameters that can be set per sample are provided at the [end of this page](#sample-parameters). + +## Samples and grouping + +This pipeline is intended to support two main use-cases: + +- either a larger set of samples is assembled using a shared set of parameters and settings set mostly via params, +- or a single, or few samples, are assembled using different strategies, typically with the goal of comparing strategies to identify the best approach for a given dataset. + +In the second case, it is likely that several samples will use the same inputs (i.e. reads). Such samples can be put into one group, by assigning them the same value in the group column of the samplesheet, and for these samples pre-processing of reads will only be done once per group, instead of once per sample. This can be used to avoid unneccessary redundant work on the same set of inputs and will only affect preprocessing and reporting, where these samples will be displayed together. + +> [!WARNING] +> Grouping should **never** be used for samples that use different input files. ## Choice of assembly-strategy and assembler @@ -44,11 +56,11 @@ Assembly strategy is controlled via `strategy` (either pipeline parameter or sam - single (default): Use a single assembler for a single type of read. The assembler should be provided via `assembler` and can be `hifiasm` (default) or `flye`. - hybrid: Use a single assembler for a combined assembly of ONT and HiFi reads. The assembler should be provided via `assembler`. Currently, only `hifiasm` supports hybrid assembly. -- scaffold: Assemble ONT reads and HiFi indepently and scaffold one assembly onto the other. `assembler` has to be provided as "ont_hifi" and could for example be: "flye_hifiasm" to assemble ont reads with `flye` and hifi reads with `hifiasm` or "hifiasm_hifiasm" to assemble both ont and hifi reads indepently with `hifiasm`. When running in "scaffold" mode, `assembly_scaffolding_order` can be used to control which assembly gets scaffolded onto which, the default being "ont_on_hifi" where ONT assembly is scaffolded onto HifI assembly. +- scaffold: Assemble ONT reads and HiFi indepently and scaffold one assembly onto the other. `assembler` has to be provided as `"ontAssembler_hifiAssembler"` and could for example be: "`flye_hifiasm"` to assemble ont reads with `flye` and HiFi reads with `hifiasm` or "hifiasm_hifiasm" to assemble both ont and hifi reads indepently with `hifiasm`. When running in "scaffold" mode, `assembly_scaffolding_order` can be used to control which assembly gets scaffolded onto which, the default being "ont_on_hifi" where ONT assembly is scaffolded onto HifI assembly. Assembler specific arguments can be provided for the assembler via `hifiasm_args` or `flye_args`, or with more fine-grained control via `assembler1_args` and `assembler2_args` for scaffolding. `assembler1_args` controls the parameters for the assembler in `single` and `hybrid` strategies, or for the assembler used for ONT reads when using `scaffold`. `assembler2_args` can be used to pass arguments to the assembler used for HiFi reads in `scaffold` mode. -`assembler[1,2]_args` can only be set via samplesheet. +`assembler[1,2]_args` can only be set via the samplesheet and are not available as global pipeline parameters. ## Samplesheet input @@ -60,15 +72,14 @@ You will need to create a samplesheet with information about the samples you wou ### Samplesheet layout -The largest samplesheet format is: +The samplesheet _must_ contain a column name `sample` [string]. The most barebone samplesheet format is: ```csv title="samplesheet.csv" -sample,ontreads,hifireads -Sample1,/path/reads/sample1ont.fq.gz,/path/reads/sample1hifi.fq.gz,/path/references/ref.fa,/path/references/ref.gff,/path/reads/sample1_r1.fq.gz,/path/reads/sample1_r2.fq.gz,true +sample +Sample1 +Sample2 ``` -The samplesheet _must_ contain a column name `sample` [string]. - Further commonly used columns _can_ be: - `group` [string] to group different samples in the report to facilitate comparisons. @@ -82,7 +93,7 @@ Further commonly used columns _can_ be: - `shortread_R`: shortread reverse file (paired end) - `paired`: [true/false] true if the reads are paired end, false if they are single-end. The `shortreads_R` column should exist if `paired` is `false` but can be empty. -A list of all possible columns can be found at the [end of this page](#sample-parameters) +But samplesheets can grow more complex if a range of strategies should be compared in a single pipeline run. A list of all possible columns can be found at the [end of this page](#sample-parameters) > [!INFO] > It is strongly recommended to provide all paths as absolute paths @@ -247,148 +258,115 @@ We recommend adding the following line to your environment to limit this (typica NXF_OPTS='-Xms1g -Xmx4g' ``` -# nf-core/genomeassembler pipeline parameters +# Sample Parameters -Assemble genomes from long ONT or pacbio HiFi reads +This section lists all possible parameters that can be set per sample. +If parameters are not provided, they are inherited from the pipeline parameters; see params for default settings. -## Input/output options +## Sample information -Define where the pipeline should find input data and save output data. - -| Parameter | Description | Type | Default | Required | Hidden | -| ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------- | ------- | -------- | ------ | -| `input` | Path to comma-separated file containing information about the samples in the experiment.
HelpYou 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/genomeassembler/usage#samplesheet-input).
| `string` | | True | | -| `outdir` | The output directory where the results will be saved. You have to use absolute paths to storage on Cloud infrastructure. | `string` | | True | | -| `email` | Email address for completion summary.
HelpSet this parameter to your e-mail address to get a summary e-mail with details of the run sent to you when the workflow exits. If set in your user config file | -| (`~/.nextflow/config`) then you don't need to specify this on the command line for every run.
| `string` | | | | +| Parameter | Description | Type | +| --------- | ------------ | -------- | +| `sample` | Sample name | `string` | +| `group` | Sample group | `string` | ## Reference Parameters Options controlling pipeline behavior -| Parameter | Description | Type | Default | Required | Hidden | -| ----------- | ------------------------------------------ | --------- | ------- | -------- | ------ | -| `ref_fasta` | Path to reference genome seqeunce (fasta) | `string` | | | | -| `ref_gff` | Path to reference genome annotations (gff) | `string` | | | | -| `use_ref` | use reference genome | `boolean` | | | True | +| Parameter | Description | Type | +| ----------- | ------------------------------------------ | --------- | +| `ref_fasta` | Path to reference genome seqeunce (fasta) | `string` | +| `ref_gff` | Path to reference genome annotations (gff) | `string` | +| `use_ref` | Use reference genome | `boolean` | ## Assembly options Options controlling assembly -| Parameter | Description | Type | Default | Required | Hidden | -| -------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | --------- | ----------- | -------- | ------ | -| `strategy` | Assembly strategy to use. Valid choices are `'single'`, `'hybrid'` and `'scaffold'` | `string` | single | | | -| `assembler` | Assembler to use. Valid choices are: `'hifiasm'`, `'flye'`, `'flye_on_hifiasm'` or `hifiasm_on_hifiasm`. `flye_on_hifiasm` will scaffold flye assembly (ont) on hifiasm (hifi) assembly using ragtag. `hifiasm_on_hifiasm` will scaffold hifiasm | -| (ont) onto hifiasm (HiFi) using ragtag | `string` | hifiasm | | | -| `genome_size` | expected genome size, optional | `integer` | | | | -| `flye_mode` | flye mode | `string` | --nano-hq | | | -| `flye_args` | additional args for flye | `string` | | | | -| `hifiasm_args` | Extra arguments passed to `hifiasm` | `string` | | | | -| `assembly_scaffolding_order` | When strategy is "scaffold", which assembly should be scaffolded onto which? | `string` | ont_on_hifi | | | +| Parameter | Description | Type | +| ------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------- | +| `strategy` | Assembly strategy to use. Valid choices are `'single'`, `'hybrid'` and `'scaffold'` | `string` | +| `assembler` | Assembler to use. Valid choices depend on strategy; for single either `flye` or `hifiasm`, hybrid can be done with `hifiasm` and for scaffolded assembly provide the names of the assemblers separated with an underscore. The first assembler will | +| be used for ONT reads, the second for HiFi reads. | `string` | +| `assembly_scaffolding_order` | When strategy is "scaffold", which assembly should be scaffolded onto which? | `string` | +| `genome_size` | expected genome size, optional | `string` | +| `flye_mode` | flye mode | `string` | +| `flye_args` | additional args for flye | `string` | +| `hifiasm_args` | Extra arguments passed to `hifiasm` | `string` | +| `assembler1_args` | Extra arguments passed to assembler1; assembling ONT reads in `scaffold` strategy | `string` | +| `assembler2_args` | Extra arguments passed to assembler2; assembling HiFi reads in `scaffold` strategy | `string` | ## Long-read preprocessing -| Parameter | Description | Type | Default | Required | Hidden | -| --------------------- | -------------------------------------------------------- | --------- | ------- | -------- | ------ | -| `ontreads` | Path to ONT reads | `string` | | | | -| `ont_collect` | Collect ONT reads from several files? | `boolean` | | | | -| `ont_trim` | Trim ont reads with fastplong? | `boolean` | | | | -| `ont_adapters` | Adaptors for ONT read-trimming | `string` | [] | | | -| `ont_fastplong_args` | Additional args to be passed to fastplong for ONT reads | `string` | | | | -| `hifireads` | Path to HiFi reads | `string` | | | | -| `hifi_trim` | Trim HiFi reads with fastplonng | `boolean` | | | | -| `hifi_adapters` | Adaptors for HiFi read-trimming | `string` | [] | | | -| `hifi_fastplong_args` | Additional args to be passed to fastplong for HiFi reads | `string` | | | | -| `jellyfish` | Run jellyfish and genomescope (recommended) | `boolean` | | | | -| `jellyfish_k` | Value of k used during k-mer analysis with jellyfish | `integer` | 21 | | | -| `dump` | dump jellyfish output | `boolean` | | | | +| Parameter | Description | Type | +| --------------------- | --------------------------------------------------------- | --------- | --- | +| `ontreads` | Path to ONT reads | `string` | +| `ont_collect` | Collect ONT reads from several files? | `boolean` | +| `ont_trim` | Trim ont reads with `fastplong`? | `boolean` | +| `ont_adapters` | Adaptors for ONT read-trimming | `string` | +| `ont_fastplong_args` | Additional args to be passed to `fastplong` for ONT reads | `string` | +| `hifireads` | Path to HiFi reads | `string` | +| `hifi_trim` | Trim HiFi reads with `fastplong` | `boolean` | +| `hifi_adapters` | Adaptors for HiFi read-trimming | `string` | +| `hifi_fastplong_args` | Additional args to be passed to fastplong for HiFi reads | `string` | +| `jellyfish` | Run jellyfish and genomescope (recommended) | `boolean` | +| `jellyfish_k` | Value of k used during k-mer analysis with jellyfish | `integer` | 21 | +| `dump` | dump jellyfish output | `boolean` | + +## Short read options + +Options for short reads + +| Parameter | Description | Type | +| ----------------- | ------------------------------- | --------- | +| `use_short_reads` | Use short reads? | `boolean` | +| `shortread_trim` | Trim short reads? | `boolean` | +| `meryl_k` | kmer length for meryl / merqury | `integer` | +| `shortread_F` | Path to forward short reads | `string` | +| `shortread_R` | Path to reverse short reads | `string` | +| `paired` | Are shortreads paired? | `string` | ## Polishing options Polishing options -| Parameter | Description | Type | Default | Required | Hidden | -| --------------- | ------------------------------------------------ | --------- | ------- | -------- | ------ | -| `polish_pilon` | Polish assembly with pilon? Requires short reads | `boolean` | | | | -| `polish_medaka` | Polish assembly with medaka (ONT only) | `boolean` | | | | -| `medaka_model` | model to use with medaka | `string` | | | | +| Parameter | Description | Type | +| --------------- | ------------------------------------------------ | --------- | +| `polish_pilon` | Polish assembly with pilon? Requires short reads | `boolean` | +| `polish_medaka` | Polish assembly with medaka (ONT only) | `boolean` | +| `medaka_model` | model to use with medaka | `string` | ## Scaffolding options Scaffolding options -| Parameter | Description | Type | Default | Required | Hidden | -| --------------------- | ------------------------------------------ | --------- | ------- | -------- | ------ | -| `scaffold_longstitch` | Scaffold with longstitch? | `boolean` | | | | -| `scaffold_links` | Scaffolding with links? | `boolean` | | | | -| `scaffold_ragtag` | Scaffold with ragtag (requires reference)? | `boolean` | | | | +| Parameter | Description | Type | +| --------------------- | ------------------------------------------ | --------- | +| `scaffold_longstitch` | Scaffold with longstitch? | `boolean` | +| `scaffold_links` | Scaffolding with links? | `boolean` | +| `scaffold_ragtag` | Scaffold with ragtag (requires reference)? | `boolean` | ## QC options Options for QC tools -| Parameter | Description | Type | Default | Required | Hidden | -| ------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------- | --------- | ----------------- | -------- | ------ | -| `merqury` | Run merqury (if short reads are provided) | `boolean` | True | | | -| `qc_reads` | Long reads that should be used for QC when both ONT and HiFi reads are provided. Options are `'ont'` or `'hifi'` | `string` | ont | | | -| `busco` | Run BUSCO? | `boolean` | | | | -| `busco_db` | Path to busco db (optional) | `string` | | | | -| `busco_lineage` | Busco lineage to use | `string` | brassicales_odb10 | | | -| `quast` | Run quast | `boolean` | | | | -| `ref_map_bam` | A mapping (bam) of reads mapped to the reference can be provided for QC. If provided alignment to reference fasta will not run | `string` | | | | -| `assembly` | Can be used to proved existing assembly will skip assembly and perform downstream steps including qc | `string` | | | | -| `assembly_map_bam` | A mapping (bam) of reads mapped to the provided assembly can be specified for QC. If provided alignment to the provided assembly fasta will not run | `string` | | | | +| Parameter | Description | Type | +| ------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------- | --------- | +| `merqury` | Run merqury (if short reads are provided) | `boolean` | +| `qc_reads` | Long reads that should be used for QC when both ONT and HiFi reads are provided. Options are `'ont'` or `'hifi'` | +| `busco` | Run BUSCO? | `boolean` | +| `busco_db` | Path to busco db (optional) | `string` | +| `busco_lineage` | Busco lineage to use | `string` | +| `quast` | Run quast | `boolean` | +| `ref_map_bam` | A mapping (bam) of reads mapped to the reference can be provided for QC. If provided alignment to reference fasta will not run | `string` | +| `assembly` | Can be used to proved existing assembly will skip assembly and perform downstream steps including qc | `string` | +| `assembly_map_bam` | A mapping (bam) of reads mapped to the provided assembly can be specified for QC. If provided alignment to the provided assembly fasta will not run | `string` | ## Annotations options Options controlling annotation liftover -| Parameter | Description | Type | Default | Required | Hidden | -| ------------------ | ------------------------------------------- | --------- | ------- | -------- | ------ | -| `lift_annotations` | Lift-over annotations (requires reference)? | `boolean` | True | | | - -## Short read options - -Options for short reads - -| Parameter | Description | Type | Default | Required | Hidden | -| ----------------- | ------------------------------- | --------- | ------- | -------- | ------ | -| `use_short_reads` | Use short reads? | `boolean` | | | | -| `shortread_trim` | Trim short reads? | `boolean` | | | | -| `meryl_k` | kmer length for meryl / merqury | `integer` | 21 | | | -| `shortread_F` | Path to forward short reads | `string` | | | | -| `shortread_R` | Path to reverse short reads | `string` | | | | -| `paired` | Are shortreads paired? | `string` | | | | - -## Institutional config options - -Parameters used to describe centralised config profiles. These should not be edited. - -| Parameter | Description | Type | Default | Required | Hidden | -| ------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------- | ------- | -------- | ------ | -| `custom_config_version` | Git commit id for Institutional configs. | `string` | master | | True | -| `custom_config_base` | Base directory for Institutional configs.
HelpIf you're running offline, Nextflow will not be able to fetch the institutional config files from the internet. If you don't need them, then this is not | -| a problem. If you do need them, you should download the files from the repo and tell Nextflow where to find them with this parameter.
| `string` | https://raw.githubusercontent.com/nf-core/configs/master | | True | -| `config_profile_name` | Institutional config name. | `string` | | | True | -| `config_profile_description` | Institutional config description. | `string` | | | True | -| `config_profile_contact` | Institutional config contact information. | `string` | | | True | -| `config_profile_url` | Institutional config URL link. | `string` | | | True | - -## Generic options - -Less common options for the pipeline, typically set in a config file. - -| Parameter | Description | Type | Default | Required | Hidden | -| --------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------- | -------------------------------------------------------- | -------- | ------ | -| `version` | Display version and exit. | `boolean` | | | True | -| `publish_dir_mode` | Method used to save pipeline results to output directory.
HelpThe Nextflow `publishDir` option specifies which intermediate files should be saved to the output directory. This option tells the pipeline | -| what method should be used to move these files. See [Nextflow docs](https://www.nextflow.io/docs/latest/process.html#publishdir) for details.
| `string` | copy | | True | -| `email_on_fail` | Email address for completion summary, only when pipeline fails.
HelpAn email address to send a summary email to when the pipeline is completed - ONLY sent if the pipeline does not exit | -| successfully.
| `string` | | | True | -| `plaintext_email` | Send plain-text email instead of HTML. | `boolean` | | | True | -| `monochrome_logs` | Do not use coloured log outputs. | `boolean` | | | True | -| `hook_url` | Incoming hook URL for messaging service
HelpIncoming hook URL for messaging service. Currently, MS Teams and Slack are supported.
| `string` | | | True | -| `validate_params` | Boolean whether to validate parameters against the schema at runtime | `boolean` | True | | True | -| `pipelines_testdata_base_path` | Base URL or local path to location of pipeline test dataset files | `string` | https://raw.githubusercontent.com/nf-core/test-datasets/ | | True | +| Parameter | Description | Type | +| ------------------ | ----------------------------------------- | --------- | +| `lift_annotations` | Lift-over annotations (requires ref_gff)? | `boolean` | diff --git a/nextflow.config b/nextflow.config index 2218f6d1..9dda278b 100644 --- a/nextflow.config +++ b/nextflow.config @@ -44,10 +44,12 @@ params { outdir = null // outdir // Assembly strategy strategy = "single" // assembly_strategy + assembler = null ontreads = null hifireads = null // == Read QC and trimming == // -- ONT + ontreads = null ont_collect = false // collect ONT reads into a single file ont_trim = false /// run fastplong trimming on ONT reads? ont_adapters = [] // list of adapters for fastplong @@ -57,14 +59,15 @@ params { jellyfish_k = 21 dump = false // dump jellyfish output // -- HiFi -- + hifireads = null hifi_trim = false // run fastplong trimming on HiFi reads? hifi_adapters = [] // list of adapters for fastplong hifi_fastplong_args = null // args for fastplong // -- Short read -- use_short_reads = false // short reads available? shortread_trim = false // trim short reads? - shortread_F = null - shortread_R = null + shortread_F = null // fwd shortreads + shortread_R = null // rev shortreads paired = null // == ASSEMBLY == assembler = "hifiasm" // assembler to use diff --git a/nextflow_schema.json b/nextflow_schema.json index c4543342..f74eb37a 100644 --- a/nextflow_schema.json +++ b/nextflow_schema.json @@ -186,14 +186,19 @@ }, "assembler": { "type": "string", - "description": "Assembler to use. Valid choices are: `'hifiasm'`, `'flye'`, `'flye_on_hifiasm'` or `hifiasm_on_hifiasm`. `flye_on_hifiasm` will scaffold flye assembly (ont) on hifiasm (hifi) assembly using ragtag. `hifiasm_on_hifiasm` will scaffold hifiasm (ont) onto hifiasm (HiFi) using ragtag", - "enum": ["flye", "hifiasm", "flye_on_hifiasm", "hifiasm_on_hifiasm"], + "description": "Assembler to use. Valid choices depend on strategy; for single either `flye` or `hifiasm`, hybrid can be done with `hifiasm` and for scaffolded assembly provide the names of the assemblers separated with an underscore. The first assembler will be used for ONT reads, the second for HiFi reads.", + "enum": ["flye", "hifiasm", "flye_hifiasm", "hifiasm_hifiasm", "flye_flye", "hifiasm_flye"], "default": "hifiasm" }, + "assembly_scaffolding_order": { + "type": "string", + "default": "ont_on_hifi", + "description": "When strategy is \"scaffold\", which assembly should be scaffolded onto which?", + "enum": ["ont_on_hifi", "hifi_on_ont"] + }, "genome_size": { - "type": "integer", - "description": "expected genome size, optional", - "minimum": 1 + "type": "string", + "description": "expected genome size, optional" }, "flye_mode": { "type": "string", @@ -207,12 +212,8 @@ }, "hifiasm_args": { "type": "string", - "description": "Extra arguments passed to `hifiasm`" - }, - "assembly_scaffolding_order": { - "type": "string", - "default": "ont_on_hifi", - "description": "When strategy is \"scaffold\", which assembly should be scaffolded onto which?" + "description": "Extra arguments passed to `hifiasm`", + "default": "\"\"" } } }, @@ -241,7 +242,8 @@ }, "ont_fastplong_args": { "type": "string", - "description": "Additional args to be passed to fastplong for ONT reads" + "description": "Additional args to be passed to fastplong for ONT reads", + "default": "\"\"" }, "hifireads": { "type": "string", @@ -258,7 +260,8 @@ }, "hifi_fastplong_args": { "type": "string", - "description": "Additional args to be passed to fastplong for HiFi reads" + "description": "Additional args to be passed to fastplong for HiFi reads", + "default": "\"\"" }, "jellyfish": { "type": "boolean", @@ -291,7 +294,8 @@ }, "medaka_model": { "type": "string", - "description": "model to use with medaka" + "description": "model to use with medaka", + "default": "\"\"" } } }, @@ -372,7 +376,7 @@ "properties": { "lift_annotations": { "type": "boolean", - "description": "Lift-over annotations (requires reference)?", + "description": "Lift-over annotations (requires ref_gff)?", "default": true } } diff --git a/subworkflows/local/utils_nfcore_genomeassembler_pipeline/main.nf b/subworkflows/local/utils_nfcore_genomeassembler_pipeline/main.nf index 591390b5..94bf7ff8 100644 --- a/subworkflows/local/utils_nfcore_genomeassembler_pipeline/main.nf +++ b/subworkflows/local/utils_nfcore_genomeassembler_pipeline/main.nf @@ -167,14 +167,15 @@ workflow PIPELINE_INITIALISATION { busco_db: it.busco_db ?: params.busco_db, meryl_k: it.meryl_k ?: params.meryl_k, merqury: it.merqury ?: params.merqury, - lift_annotations: it.lift_annotations ?: params.lift_annotations, + lift_annotations: (it.ref_gff || params.ref_gff) ? (it.lift_annotations ?: params.lift_annotations) : false, shortread_F: it.shortread_F ?: params.shortread_F, shortread_R: it.shortread_R ?: params.shortread_R, paired: it.paired ?: params.paired, // new: use_short_reads: it.use_short_reads ?: params.use_short_reads ?: it.shortread_F ? true : false, shortread_trim: it.shortread_trim ?: params.shortread_trim - ] } + ] + } .set { ch_samplesheet } // Define valid hybrid assemblers From 1f6f2f9e00c317f9326fff9afc8c6f71907814f3 Mon Sep 17 00:00:00 2001 From: Niklas Schandry Date: Fri, 29 Aug 2025 14:27:46 +0200 Subject: [PATCH 055/162] add check if longreads should go into fastplong --- .nf-core.yml | 2 +- .nf-test.log | 34 ++++----- CHANGELOG.md | 28 +++++++ conf/test.config | 14 ++-- nextflow.config | 2 +- ro-crate-metadata.json | 18 ++--- .../local/prepare/prepare_hifi/main.nf | 8 +- .../local/prepare/prepare_ont/main.nf | 6 +- tests/default.nf.test.snap | 75 ++----------------- 9 files changed, 76 insertions(+), 111 deletions(-) diff --git a/.nf-core.yml b/.nf-core.yml index 1e279baf..18079628 100644 --- a/.nf-core.yml +++ b/.nf-core.yml @@ -20,4 +20,4 @@ template: skip_features: - multiqc - igenomes - version: 1.1.0 + version: 2.0.0dev diff --git a/.nf-test.log b/.nf-test.log index 830c9b67..d393bec4 100644 --- a/.nf-test.log +++ b/.nf-test.log @@ -1,18 +1,16 @@ -Jul-02 11:19:22.019 [main] INFO com.askimed.nf.test.App - nf-test 0.9.2 -Jul-02 11:19:22.043 [main] INFO com.askimed.nf.test.App - Arguments: [test, --profile, conda,test, --update-snapshot] -Jul-02 11:19:23.308 [main] INFO com.askimed.nf.test.App - Nextflow Version: 24.10.5 -Jul-02 11:19:23.312 [main] INFO com.askimed.nf.test.commands.RunTestsCommand - Load config from file /dss/dsshome1/lxc0A/ra34bin/genomeassembler/nf-test.config... -Jul-02 11:19:36.250 [main] INFO com.askimed.nf.test.lang.dependencies.DependencyResolver - Loaded 46 files from directory /dss/dsshome1/lxc0A/ra34bin/genomeassembler in 12.313 sec -Jul-02 11:19:36.256 [main] INFO com.askimed.nf.test.lang.dependencies.DependencyResolver - Found 1 files containing tests. -Jul-02 11:19:36.256 [main] DEBUG com.askimed.nf.test.lang.dependencies.DependencyResolver - Found files: [/dss/dsshome1/lxc0A/ra34bin/genomeassembler/tests/default.nf.test] -Jul-02 11:19:36.489 [main] INFO com.askimed.nf.test.commands.RunTestsCommand - Found 1 tests to execute. -Jul-02 11:19:36.491 [main] INFO com.askimed.nf.test.core.TestExecutionEngine - Started test plan -Jul-02 11:19:36.491 [main] INFO com.askimed.nf.test.core.TestExecutionEngine - Running testsuite 'Test pipeline' from file '/dss/dsshome1/lxc0A/ra34bin/genomeassembler/tests/default.nf.test'. -Jul-02 11:19:36.491 [main] INFO com.askimed.nf.test.core.TestExecutionEngine - Run test 'c85cb7f4: -profile test'. type: com.askimed.nf.test.lang.pipeline.PipelineTest -Jul-02 11:25:42.268 [main] DEBUG com.askimed.nf.test.lang.extensions.SnapshotFile - Load snapshots from file '/dss/dsshome1/lxc0A/ra34bin/genomeassembler/tests/default.nf.test.snap' -Jul-02 11:25:42.486 [main] DEBUG com.askimed.nf.test.lang.extensions.Snapshot - Snapshots '-profile test' do not match. Update snapshots flag set. -Jul-02 11:25:42.487 [main] DEBUG com.askimed.nf.test.lang.extensions.SnapshotFile - Updated snapshot '-profile test' -Jul-02 11:25:42.678 [main] DEBUG com.askimed.nf.test.lang.extensions.SnapshotFile - Wrote snapshots to file '/dss/dsshome1/lxc0A/ra34bin/genomeassembler/tests/default.nf.test.snap' -Jul-02 11:25:42.679 [main] INFO com.askimed.nf.test.core.TestExecutionEngine - Test 'c85cb7f4: -profile test' finished. status: PASSED -Jul-02 11:25:42.684 [main] INFO com.askimed.nf.test.core.TestExecutionEngine - Testsuite 'Test pipeline' finished. snapshot file: true, skipped tests: false, failed tests: false -Jul-02 11:25:42.688 [main] INFO com.askimed.nf.test.core.TestExecutionEngine - Executed 1 tests. 0 tests failed. Done! +Aug-29 14:25:56.353 [main] INFO com.askimed.nf.test.App - nf-test 0.9.2 +Aug-29 14:25:56.382 [main] INFO com.askimed.nf.test.App - Arguments: [test, --profile, test, --update-snapshot] +Aug-29 14:25:58.091 [main] INFO com.askimed.nf.test.App - Nextflow Version: 25.04.6 +Aug-29 14:25:58.094 [main] INFO com.askimed.nf.test.commands.RunTestsCommand - Load config from file /dss/dsshome1/lxc0A/ra34bin/genomeassembler/nf-test.config... +Aug-29 14:26:00.412 [main] INFO com.askimed.nf.test.lang.dependencies.DependencyResolver - Loaded 43 files from directory /dss/dsshome1/lxc0A/ra34bin/genomeassembler in 1.658 sec +Aug-29 14:26:00.414 [main] INFO com.askimed.nf.test.lang.dependencies.DependencyResolver - Found 1 files containing tests. +Aug-29 14:26:00.414 [main] DEBUG com.askimed.nf.test.lang.dependencies.DependencyResolver - Found files: [/dss/dsshome1/lxc0A/ra34bin/genomeassembler/tests/default.nf.test] +Aug-29 14:26:00.631 [main] INFO com.askimed.nf.test.commands.RunTestsCommand - Found 1 tests to execute. +Aug-29 14:26:00.632 [main] INFO com.askimed.nf.test.core.TestExecutionEngine - Started test plan +Aug-29 14:26:00.632 [main] INFO com.askimed.nf.test.core.TestExecutionEngine - Running testsuite 'Test pipeline' from file '/dss/dsshome1/lxc0A/ra34bin/genomeassembler/tests/default.nf.test'. +Aug-29 14:26:00.632 [main] INFO com.askimed.nf.test.core.TestExecutionEngine - Run test 'c85cb7f4: -profile test'. type: com.askimed.nf.test.lang.pipeline.PipelineTest +Aug-29 14:26:22.763 [main] DEBUG com.askimed.nf.test.lang.extensions.SnapshotFile - Load snapshots from file '/dss/dsshome1/lxc0A/ra34bin/genomeassembler/tests/default.nf.test.snap' +Aug-29 14:26:22.776 [main] DEBUG com.askimed.nf.test.lang.extensions.Snapshot - Snapshots '-profile test' match. +Aug-29 14:26:22.777 [main] INFO com.askimed.nf.test.core.TestExecutionEngine - Test 'c85cb7f4: -profile test' finished. status: PASSED +Aug-29 14:26:22.779 [main] INFO com.askimed.nf.test.core.TestExecutionEngine - Testsuite 'Test pipeline' finished. snapshot file: true, skipped tests: false, failed tests: false +Aug-29 14:26:22.779 [main] INFO com.askimed.nf.test.core.TestExecutionEngine - Executed 1 tests. 0 tests failed. Done! diff --git a/CHANGELOG.md b/CHANGELOG.md index 5de49257..30067654 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,32 @@ 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). +## v2.0.0 'Saffron Vulture' - [2025-xx-xx] + +v2.0.0 of this pipeline is a (almost complete) refactor of the pipeline to facilitate sample-level parameteristation. Besides, it contains some additional changes: + +### `Added` + +- fastplong for long-read trimming and qc +- migration to nf-test +- increased flexibility of the scaffolding strategy +- added option to group samples +- + +### `Fixed` + +### `Dependencies` + +- `fastplong` + +### `Deprecated` + +The following tools are no longer used: + +- `nanoq` +- `porechop` +- `lima` + ## v1.1.0 'Brass Pigeon' - [2025-07-21] ### `Added` @@ -72,3 +98,5 @@ Initial release of nf-core/genomeassembler, created with the [nf-core](https://n ### `Dependencies` ### `Deprecated` + +Codenames for v1.x are various types of metallic pigeons, v2.x are vultures of different colors. diff --git a/conf/test.config b/conf/test.config index be3dc0e5..35891c1e 100644 --- a/conf/test.config +++ b/conf/test.config @@ -21,13 +21,11 @@ process { params { config_profile_name = 'Test profile' config_profile_description = 'Minimal test dataset to check pipeline function' - input = 'https://raw.githubusercontent.com/nf-core/test-datasets/genomeassembler/samplesheet/test_samplesheet.csv' - quast = false - busco = false - jellyfish = false - genome_size = 2000000 - hifi = true - ont = true - assembler = "flye_on_hifiasm" + input = 'https://raw.githubusercontent.com/nf-core/test-datasets/genomeassembler/samplesheet/test_samplesheet_v2.csv' + ontreads = 'https://raw.githubusercontent.com/nf-core/test-datasets/genomeassembler/A_thaliana_Col-0_2mb/ONT-Col-0_test_data.fastq.gz' + hifireads = 'https://raw.githubusercontent.com/nf-core/test-datasets/genomeassembler/A_thaliana_Col-0_2mb/HiFi-Col-0_test_data.fastq.gz' + ref_fasta = 'https://raw.githubusercontent.com/nf-core/test-datasets/genomeassembler/A_thaliana_Col-0_2mb/Col-CEN_v1.2.Chr1_5MB-7MB.fasta.gz' + ref_gff = 'https://raw.githubusercontent.com/nf-core/test-datasets/genomeassembler/A_thaliana_Col-0_2mb/Col-CEN_v1.2_genes_araport11.Chr1_5MB-7MB.gff3.gz' + genome_size = "2000000" hifiasm_args = "-f 0" } diff --git a/nextflow.config b/nextflow.config index 9dda278b..3586ac81 100644 --- a/nextflow.config +++ b/nextflow.config @@ -325,7 +325,7 @@ manifest { mainScript = 'main.nf' defaultBranch = 'master' nextflowVersion = '!>=25.04.0' - version = '1.1.0' + version = '2.0.0dev' doi = '10.5281/zenodo.14986998' } diff --git a/ro-crate-metadata.json b/ro-crate-metadata.json index c87dc35b..30bcdcd1 100644 --- a/ro-crate-metadata.json +++ b/ro-crate-metadata.json @@ -133,14 +133,14 @@ "ComputationalWorkflow" ], "creator": [ - { - "@id": "https://orcid.org/0000-0003-3099-7860" - }, { "@id": "https://orcid.org/0000-0002-7860-3560" }, { "@id": "https://orcid.org/0000-0003-1675-0677" + }, + { + "@id": "https://orcid.org/0000-0003-3099-7860" } ], "dateCreated": "", @@ -322,12 +322,6 @@ "name": "nf-core", "url": "https://nf-co.re/" }, - { - "@id": "https://orcid.org/0000-0003-3099-7860", - "@type": "Person", - "email": "niklas@bio.lmu.de", - "name": "Niklas Schandry" - }, { "@id": "https://orcid.org/0000-0002-7860-3560", "@type": "Person", @@ -339,6 +333,12 @@ "@type": "Person", "email": "mahesh.binzer-panchal@nbis.se", "name": "Mahesh Binzer-Panchal" + }, + { + "@id": "https://orcid.org/0000-0003-3099-7860", + "@type": "Person", + "email": "niklas@bio.lmu.de", + "name": "Niklas Schandry" } ] } \ No newline at end of file diff --git a/subworkflows/local/prepare/prepare_hifi/main.nf b/subworkflows/local/prepare/prepare_hifi/main.nf index 6deb0ab7..3a7269c7 100644 --- a/subworkflows/local/prepare/prepare_hifi/main.nf +++ b/subworkflows/local/prepare/prepare_hifi/main.nf @@ -8,7 +8,8 @@ workflow PREPARE_HIFI { Channel.empty().set { ch_versions } main_in - .filter { it -> it.group } + .filter { it -> it.hifi_trim} + .filter { it -> it.group } .map { it -> [it.meta, it.group, it.hifi_trim, it.hifireads, it.hifi_adapters, it.hifi_fastplong_args] } .groupTuple(by: 1) .map { @@ -25,6 +26,7 @@ workflow PREPARE_HIFI { } .mix( main_in + .filter { it -> it.hifi_trim} .filter { it -> !it.group } .map { it -> @@ -84,6 +86,7 @@ workflow PREPARE_HIFI { // inputs are joined to outputs main_in + .filter { it -> it.hifi_trim } .map { it -> it - it.subMap('hifireads') } .map { it -> it.collect { entry -> [ entry.value, entry ] } } .join( @@ -91,10 +94,9 @@ workflow PREPARE_HIFI { .map { it -> it.collect { entry -> [ entry.value, entry ] } } ) .map { it -> it.collect { _entry, map -> [ (map.key): map.value ] }.collectEntries() } + .mix(main_in.filter { it -> it.hifi_trim }) .set { main_out } - - versions = ch_versions.mix(FASTPLONG_HIFI.out.versions) emit: diff --git a/subworkflows/local/prepare/prepare_ont/main.nf b/subworkflows/local/prepare/prepare_ont/main.nf index b3ccafdd..ae3b6818 100644 --- a/subworkflows/local/prepare/prepare_ont/main.nf +++ b/subworkflows/local/prepare/prepare_ont/main.nf @@ -68,7 +68,8 @@ workflow PREPARE_ONT { // ch_collected is the same samples as the input channel ch_collected - .filter { it -> it.group } + .filter { it -> it.ont_trim} + .filter { it -> it.group } .map { it -> [it.meta, it.group, it.ont_trim, it.ontreads, it.ont_adaptors, it.ont_fastplong_args] } .groupTuple(by: 1) .map { @@ -85,6 +86,7 @@ workflow PREPARE_ONT { } .mix( ch_collected + .filter { it -> it.ont_trim} .filter { it -> !it.group } .map { it -> @@ -143,6 +145,7 @@ workflow PREPARE_ONT { .set { fastplong_json_out } ch_collected + .filter { it -> it.ont_trim } .map { it -> it - it.subMap('ontreads') } .map { it -> it.collect { entry -> [ entry.value, entry ] } } .join( @@ -150,6 +153,7 @@ workflow PREPARE_ONT { .map { it -> it.collect { entry -> [ entry.value, entry ] } } ) .map { it -> it.collect { _entry, map -> [ (map.key): map.value ] }.collectEntries() } + .mix(ch_collected.filter { it -> !it.ont_trim }) .set { main_out } versions = ch_versions.mix(COLLECT.out.versions).mix(FASTPLONG_ONT.out.versions) diff --git a/tests/default.nf.test.snap b/tests/default.nf.test.snap index 9909c830..fa487b04 100644 --- a/tests/default.nf.test.snap +++ b/tests/default.nf.test.snap @@ -1,90 +1,25 @@ { "-profile test": { "content": [ - 6, + 0, { - "FLYE": { - "flye": "2.9.5-b1801" - }, - "GFA_2_FA_HIFI": { - "awk": "mawk 1.3.4", - "awk": "1.3.4", - "gzip": 1.13 - }, - "HIFIASM": { - "hifiasm": "0.25.0-r726" - }, - "LIFTOFF": { - "liftoff": "v1.6.3" - }, - "NANOQ": { - "nanoq": "0.10.0" - }, - "RAGTAG_PATCH": { - "ragtag": "2.1.0" - }, "Workflow": { - "nf-core/genomeassembler": "v1.1.0" + "nf-core/genomeassembler": "v2.0.0dev" } }, [ - "Col-0_2MB", - "Col-0_2MB/QC", - "Col-0_2MB/QC/nanoq", - "Col-0_2MB/QC/nanoq/Col-0_2MB_report.json", - "Col-0_2MB/QC/nanoq/Col-0_2MB_stats.json", - "Col-0_2MB/assembly", - "Col-0_2MB/assembly/Col-0_2MB_assembly.gff3", - "Col-0_2MB/assembly/Col-0_2MB_assembly.unmapped.txt", - "Col-0_2MB/assembly/flye", - "Col-0_2MB/assembly/flye/Col-0_2MB.assembly.fasta.gz", - "Col-0_2MB/assembly/flye/Col-0_2MB.assembly_graph.gfa.gz", - "Col-0_2MB/assembly/flye/Col-0_2MB.assembly_graph.gv.gz", - "Col-0_2MB/assembly/flye/Col-0_2MB.assembly_info.txt", - "Col-0_2MB/assembly/flye/Col-0_2MB.flye.log", - "Col-0_2MB/assembly/flye/Col-0_2MB.params.json", - "Col-0_2MB/assembly/hifiasm", - "Col-0_2MB/assembly/hifiasm/Col-0_2MB.bp.hap1.p_ctg.gfa", - "Col-0_2MB/assembly/hifiasm/Col-0_2MB.bp.hap2.p_ctg.gfa", - "Col-0_2MB/assembly/hifiasm/Col-0_2MB.bp.p_ctg.gfa", - "Col-0_2MB/assembly/hifiasm/Col-0_2MB.bp.p_utg.gfa", - "Col-0_2MB/assembly/hifiasm/Col-0_2MB.bp.r_utg.gfa", - "Col-0_2MB/assembly/hifiasm/Col-0_2MB.ec.bin", - "Col-0_2MB/assembly/hifiasm/Col-0_2MB.ovlp.reverse.bin", - "Col-0_2MB/assembly/hifiasm/Col-0_2MB.ovlp.source.bin", - "Col-0_2MB/assembly/hifiasm/Col-0_2MB.stderr.log", - "Col-0_2MB/assembly/hifiasm/fasta", - "Col-0_2MB/assembly/hifiasm/fasta/Col-0_2MB.bp.p_utg.fa.gz", - "Col-0_2MB/assembly/ragtag", - "Col-0_2MB/assembly/ragtag/Col-0_2MB_assembly_patch.comps.fasta", - "Col-0_2MB/assembly/ragtag/Col-0_2MB_assembly_patch.ctg.agp", - "Col-0_2MB/assembly/ragtag/Col-0_2MB_assembly_patch.ctg.fasta", - "Col-0_2MB/assembly/ragtag/Col-0_2MB_assembly_patch.patch.agp", - "Col-0_2MB/assembly/ragtag/Col-0_2MB_assembly_patch.patch.err", - "Col-0_2MB/assembly/ragtag/Col-0_2MB_assembly_patch.patch.fasta", - "Col-0_2MB/assembly/ragtag/Col-0_2MB_assembly_patch.rename.agp", - "Col-0_2MB/assembly/ragtag/Col-0_2MB_assembly_patch.rename.fasta", "pipeline_info", "pipeline_info/nf_core_genomeassembler_software_versions.yml", "pipeline_info/nf_core_pipeline_software_versions.yml" ], [ - "Col-0_2MB_report.json:md5,25d7ae5780b2f565cb46df7c9e09388a", - "Col-0_2MB_stats.json:md5,d41d8cd98f00b204e9800998ecf8427e", - "Col-0_2MB.params.json:md5,afa91c041bce5e190f4a699d11b69db6", - "Col-0_2MB.bp.hap1.p_ctg.gfa:md5,46ee70869884ad585165bd48081414e9", - "Col-0_2MB.bp.hap2.p_ctg.gfa:md5,7792865547989d6d284f640425c4e36c", - "Col-0_2MB.bp.p_ctg.gfa:md5,8fe65466d76815ffe1663ff6d8f2e8d1", - "Col-0_2MB.bp.p_utg.gfa:md5,ba2c77ebdb2ad3e6060f5574e890c6eb", - "Col-0_2MB.bp.r_utg.gfa:md5,ba2c77ebdb2ad3e6060f5574e890c6eb", - "Col-0_2MB.bp.p_utg.fa.gz:md5,812a3a16dc68bb409deb69f0aef7e6a8", - "Col-0_2MB_assembly_patch.patch.err:md5,d41d8cd98f00b204e9800998ecf8427e" + ] ], "meta": { "nf-test": "0.9.2", - "nextflow": "24.10.3" + "nextflow": "25.04.6" }, - "timestamp": "2025-06-26T16:28:52.273158206" + "timestamp": "2025-08-29T14:07:14.788806405" } } \ No newline at end of file From 3e528efe6b05d5f1b90854dff83e77f9cee1df12 Mon Sep 17 00:00:00 2001 From: Niklas Schandry Date: Thu, 4 Sep 2025 15:16:18 +0200 Subject: [PATCH 056/162] bug collection --- .nf-test.log | 32 +- subworkflows/local/assemble/main.nf | 391 +++++++++--------- subworkflows/local/prepare/jellyfish/main.nf | 78 +++- subworkflows/local/prepare/main.nf | 11 +- .../local/prepare/prepare_hifi/main.nf | 7 +- .../local/prepare/prepare_ont/main.nf | 7 +- 6 files changed, 288 insertions(+), 238 deletions(-) diff --git a/.nf-test.log b/.nf-test.log index d393bec4..4d19adc8 100644 --- a/.nf-test.log +++ b/.nf-test.log @@ -1,16 +1,16 @@ -Aug-29 14:25:56.353 [main] INFO com.askimed.nf.test.App - nf-test 0.9.2 -Aug-29 14:25:56.382 [main] INFO com.askimed.nf.test.App - Arguments: [test, --profile, test, --update-snapshot] -Aug-29 14:25:58.091 [main] INFO com.askimed.nf.test.App - Nextflow Version: 25.04.6 -Aug-29 14:25:58.094 [main] INFO com.askimed.nf.test.commands.RunTestsCommand - Load config from file /dss/dsshome1/lxc0A/ra34bin/genomeassembler/nf-test.config... -Aug-29 14:26:00.412 [main] INFO com.askimed.nf.test.lang.dependencies.DependencyResolver - Loaded 43 files from directory /dss/dsshome1/lxc0A/ra34bin/genomeassembler in 1.658 sec -Aug-29 14:26:00.414 [main] INFO com.askimed.nf.test.lang.dependencies.DependencyResolver - Found 1 files containing tests. -Aug-29 14:26:00.414 [main] DEBUG com.askimed.nf.test.lang.dependencies.DependencyResolver - Found files: [/dss/dsshome1/lxc0A/ra34bin/genomeassembler/tests/default.nf.test] -Aug-29 14:26:00.631 [main] INFO com.askimed.nf.test.commands.RunTestsCommand - Found 1 tests to execute. -Aug-29 14:26:00.632 [main] INFO com.askimed.nf.test.core.TestExecutionEngine - Started test plan -Aug-29 14:26:00.632 [main] INFO com.askimed.nf.test.core.TestExecutionEngine - Running testsuite 'Test pipeline' from file '/dss/dsshome1/lxc0A/ra34bin/genomeassembler/tests/default.nf.test'. -Aug-29 14:26:00.632 [main] INFO com.askimed.nf.test.core.TestExecutionEngine - Run test 'c85cb7f4: -profile test'. type: com.askimed.nf.test.lang.pipeline.PipelineTest -Aug-29 14:26:22.763 [main] DEBUG com.askimed.nf.test.lang.extensions.SnapshotFile - Load snapshots from file '/dss/dsshome1/lxc0A/ra34bin/genomeassembler/tests/default.nf.test.snap' -Aug-29 14:26:22.776 [main] DEBUG com.askimed.nf.test.lang.extensions.Snapshot - Snapshots '-profile test' match. -Aug-29 14:26:22.777 [main] INFO com.askimed.nf.test.core.TestExecutionEngine - Test 'c85cb7f4: -profile test' finished. status: PASSED -Aug-29 14:26:22.779 [main] INFO com.askimed.nf.test.core.TestExecutionEngine - Testsuite 'Test pipeline' finished. snapshot file: true, skipped tests: false, failed tests: false -Aug-29 14:26:22.779 [main] INFO com.askimed.nf.test.core.TestExecutionEngine - Executed 1 tests. 0 tests failed. Done! +Sep-01 10:36:09.700 [main] INFO com.askimed.nf.test.App - nf-test 0.9.2 +Sep-01 10:36:09.727 [main] INFO com.askimed.nf.test.App - Arguments: [test, --profile=+conda] +Sep-01 10:36:11.061 [main] INFO com.askimed.nf.test.App - Nextflow Version: 24.10.5 +Sep-01 10:36:11.065 [main] INFO com.askimed.nf.test.commands.RunTestsCommand - Load config from file /dss/dsshome1/lxc0A/ra34bin/genomeassembler/nf-test.config... +Sep-01 10:36:13.124 [main] INFO com.askimed.nf.test.lang.dependencies.DependencyResolver - Loaded 43 files from directory /dss/dsshome1/lxc0A/ra34bin/genomeassembler in 1.486 sec +Sep-01 10:36:13.126 [main] INFO com.askimed.nf.test.lang.dependencies.DependencyResolver - Found 1 files containing tests. +Sep-01 10:36:13.126 [main] DEBUG com.askimed.nf.test.lang.dependencies.DependencyResolver - Found files: [/dss/dsshome1/lxc0A/ra34bin/genomeassembler/tests/default.nf.test] +Sep-01 10:36:13.370 [main] INFO com.askimed.nf.test.commands.RunTestsCommand - Found 1 tests to execute. +Sep-01 10:36:13.372 [main] INFO com.askimed.nf.test.core.TestExecutionEngine - Started test plan +Sep-01 10:36:13.372 [main] INFO com.askimed.nf.test.core.TestExecutionEngine - Running testsuite 'Test pipeline' from file '/dss/dsshome1/lxc0A/ra34bin/genomeassembler/tests/default.nf.test'. +Sep-01 10:36:13.373 [main] INFO com.askimed.nf.test.core.TestExecutionEngine - Run test 'c85cb7f4: -profile test'. type: com.askimed.nf.test.lang.pipeline.PipelineTest +Sep-01 10:36:37.842 [main] DEBUG com.askimed.nf.test.lang.extensions.SnapshotFile - Load snapshots from file '/dss/dsshome1/lxc0A/ra34bin/genomeassembler/tests/default.nf.test.snap' +Sep-01 10:36:37.863 [main] DEBUG com.askimed.nf.test.lang.extensions.Snapshot - Snapshots '-profile test' match. +Sep-01 10:36:37.863 [main] INFO com.askimed.nf.test.core.TestExecutionEngine - Test 'c85cb7f4: -profile test' finished. status: PASSED +Sep-01 10:36:37.866 [main] INFO com.askimed.nf.test.core.TestExecutionEngine - Testsuite 'Test pipeline' finished. snapshot file: true, skipped tests: false, failed tests: false +Sep-01 10:36:37.866 [main] INFO com.askimed.nf.test.core.TestExecutionEngine - Executed 1 tests. 0 tests failed. Done! diff --git a/subworkflows/local/assemble/main.nf b/subworkflows/local/assemble/main.nf index 0b290da0..eef3af09 100644 --- a/subworkflows/local/assemble/main.nf +++ b/subworkflows/local/assemble/main.nf @@ -18,6 +18,8 @@ workflow ASSEMBLE { // Empty channels Channel.empty().set { ch_versions } + + ch_main.dump(tag: "Assemble - Inputs") ch_main .branch { it -> @@ -37,10 +39,20 @@ workflow ASSEMBLE { } .set { ch_main_assemble_branched } + ch_main_assemble_branched + .single + .dump(tag: "Assemble: Branched: Single") + ch_main_assemble_branched + .hybrid + .dump(tag: "Assemble: Branched: Hybrid") + ch_main_assemble_branched + .scaffold + .dump(tag: "Assemble: Branched: scaffold") + // Flye inputs: ch_main_assemble_branched .single - .filter { it -> it.assembler1 == "flye" || it.assembler2 == "flye" } + .filter { it -> it.assembler1 == "flye" } .mix( ch_main_assemble_branched .scaffold @@ -60,15 +72,16 @@ workflow ASSEMBLE { ], it.assembler1 == "flye" ? it.ontreads : (it.assembler2 == "flye" ? it.hifireads : []), ] - mode: it.assembler1 == "flye" ? "--nano-hq" : "--pacbio-hifi" + mode: it.ontreads ? "--nano-hq" : "--pacbio-hifi" } .set { flye_inputs } + flye_inputs.reads.dump(tag: "Assemble: Flye inputs") + FLYE(flye_inputs.reads, flye_inputs.mode) ch_versions = ch_versions.mix(FLYE.out.versions) - // Hifiasm: everything that is not ONT ch_main_assemble_branched .single @@ -86,206 +99,214 @@ workflow ASSEMBLE { ) .set { ch_main_assemble_hifi_hifiasm } - HIFIASM(ch_main_assemble_hifi_hifiasm - .map { - it -> [ - [id: it.meta.id, hifiasm_args: it.hifiasm_args ?: ""], - it.hifireads, - (it.stragtegy == "hybrid" && it.ontreads) ? it.ontreads : [] - ] - }, - [[], [], []], - [[], [], []], - [[], []]) + ch_main_assemble_hifi_hifiasm.dump(tag: "Assemble: hifiasm HIFI inputs") - GFA_2_FA_HIFI( HIFIASM.out.processed_unitigs.map { meta, fasta -> [[id: meta.id], fasta] } ) + HIFIASM(ch_main_assemble_hifi_hifiasm + .map { + it -> [ + [id: it.meta.id, hifiasm_args: it.hifiasm_args ?: ""], + it.hifireads, + (it.stragtegy == "hybrid" && it.ontreads) ? it.ontreads : [] + ] + }, + [[], [], []], + [[], [], []], + [[], []]) - ch_versions = ch_versions.mix(HIFIASM.out.versions).mix(GFA_2_FA_HIFI.out.versions) + GFA_2_FA_HIFI( HIFIASM.out.processed_unitigs.map { meta, fasta -> [[id: meta.id], fasta] } ) + ch_versions = ch_versions.mix(HIFIASM.out.versions).mix(GFA_2_FA_HIFI.out.versions) - // Assemble hifiasm_ont branch - ch_main_assemble_branched - .single - .filter { it -> it.assembler1 == "hifiasm" && it.ontreads } - .mix(ch_main_assemble_branched - .scaffold - .filter { it -> it.assembler1 == "hifiasm" } - ) - .set { ch_main_assemble_ont_hifiasm } + // Assemble hifiasm_ont branch + ch_main_assemble_branched + .single + .filter { it -> it.assembler1 == "hifiasm" && it.ontreads } + .mix(ch_main_assemble_branched + .scaffold + .filter { it -> it.assembler1 == "hifiasm" } + ) + .set { ch_main_assemble_ont_hifiasm } - HIFIASM_ONT(ch_main_assemble_ont_hifiasm.map { it -> [ [id: it.meta.id, hifiasm_args: it.hifiasm_args ?: ""], it.ontreads, [] ] }, [[], [], []], [[], [], []], [[], []]) + ch_main_assemble_ont_hifiasm.dump(tag: "Assemble: hifiasm ONT inputs") - GFA_2_FA_ONT( HIFIASM_ONT.out.processed_unitigs.map { meta, fasta -> [[id: meta.id], fasta] } ) + HIFIASM_ONT(ch_main_assemble_ont_hifiasm.map { it -> [ [id: it.meta.id, hifiasm_args: it.hifiasm_args ?: ""], it.ontreads, [] ] }, [[], [], []], [[], [], []], [[], []]) - ch_versions = ch_versions.mix(HIFIASM_ONT.out.versions).mix(GFA_2_FA_ONT.out.versions) + GFA_2_FA_ONT( HIFIASM_ONT.out.processed_unitigs.map { meta, fasta -> [[id: meta.id], fasta] } ) + ch_versions = ch_versions.mix(HIFIASM_ONT.out.versions).mix(GFA_2_FA_ONT.out.versions) - // Now, the individual assemblies need to be correctly added into the main channel. - // This should be done per-strategy I think - // join assembler outputs back to assembler inputs and determine correct placement of the assembly. - // Flye: - ch_main_assemble_flye - // Convert to list for join - .map { it -> it.collect { entry -> [ entry.value, entry ] } } - .join( FLYE.out.fasta - .map { meta, assembly -> [meta: [id: meta.id], flye_assembly: assembly ] } - .map { it -> it.collect { entry -> [ entry.value, entry ] } } - ) - // After joining re-create the maps from the stored map - .map { it -> it.collect { _entry, map -> [ (map.key): map.value ] }.collectEntries() } - .map { it -> it - it.subMap("flye_assembly") + - [ - assembly: it.strategy == "single" ? it.flye_assembly : null, - assembly1: it.assembler1 == "flye" ? it.flye_assembly : null, - assembly2: it.assembler2 == "flye" ? it.flye_assembly : null, - ] - } - .set { flye_assemblies } - - ch_main_assemble_hifi_hifiasm - // Convert to list for join - .map { it -> it.collect { entry -> [ entry.value, entry ] } } - .join( GFA_2_FA_HIFI.out.contigs_fasta - .map { meta, assembly -> [meta: meta, hifiasm_assembly: assembly ] } - .map { it -> it.collect { entry -> [ entry.value, entry ] } } - ) - // After joining re-create the maps from the stored map - .map { it -> it.collect { _entry, map -> [ (map.key): map.value ] }.collectEntries() } - .map { - it -> it -it.subMap("hifiasm_assembly") + + + // Now, the individual assemblies need to be correctly added into the main channel. + // This should be done per-strategy I think + // join assembler outputs back to assembler inputs and determine correct placement of the assembly. + // Flye: + ch_main_assemble_flye + // Convert to list for join + .map { it -> it.collect { entry -> [ entry.value, entry ] } } + .join( FLYE.out.fasta + .map { meta, assembly -> [meta: [id: meta.id], flye_assembly: assembly ] } + .map { it -> it.collect { entry -> [ entry.value, entry ] } } + ) + // After joining re-create the maps from the stored map + .map { it -> it.collect { _entry, map -> [ (map.key): map.value ] }.collectEntries() } + .map { it -> it - it.subMap("flye_assembly") + [ - assembly: (it.strategy == "single" || it.strategy == "hybrid") && it.assembler1 == "hifiasm" ? it.hifiasm_assembly : null, - assembly1: it.strategy == "scaffold" && it.assembler1 == "hifiasm" ? it.hifiasm_assembly : null, - assembly2: it.strategy == "scaffold" && it.assembler2 == "hifiasm" ? it.hifiasm_assembly : null + assembly: it.strategy == "single" ? it.flye_assembly : null, + assembly1: it.assembler1 == "flye" ? it.flye_assembly : null, + assembly2: it.assembler2 == "flye" ? it.flye_assembly : null, ] - } - .set { hifiasm_hifi_assemblies } + } + .set { flye_assemblies } + flye_assemblies.dump(tag: "Assemble: Flye assemblies") + + ch_main_assemble_hifi_hifiasm + // Convert to list for join + .map { it -> it.collect { entry -> [ entry.value, entry ] } } + .join( GFA_2_FA_HIFI.out.contigs_fasta + .map { meta, assembly -> [meta: meta, hifiasm_assembly: assembly ] } + .map { it -> it.collect { entry -> [ entry.value, entry ] } } + ) + // After joining re-create the maps from the stored map + .map { it -> it.collect { _entry, map -> [ (map.key): map.value ] }.collectEntries() } + .map { + it -> it -it.subMap("hifiasm_assembly") + + [ + assembly: (it.strategy == "single" || it.strategy == "hybrid") && it.assembler1 == "hifiasm" ? it.hifiasm_assembly : null, + assembly1: it.strategy == "scaffold" && it.assembler1 == "hifiasm" ? it.hifiasm_assembly : null, + assembly2: it.strategy == "scaffold" && it.assembler2 == "hifiasm" ? it.hifiasm_assembly : null + ] + } + .set { hifiasm_hifi_assemblies } + hifiasm_hifi_assemblies.dump(tag: "Assemble: hifiasm HIFI assemblies") - ch_main_assemble_ont_hifiasm - .map { it -> it.collect { entry -> [ entry.value, entry ] } } - .join( GFA_2_FA_ONT.out.contigs_fasta - .map { meta, assembly -> [meta: meta, hifiasm_assembly: assembly ] } - .map { it -> it.collect { entry -> [ entry.value, entry ] } } - ) - // After joining re-create the maps from the stored map - .map { it -> it.collect { _entry, map -> [ (map.key): map.value ] }.collectEntries() } - .map { - it -> it -it.subMap("hifiasm_assembly") + - [ - assembly: (it.strategy == "single" || it.strategy == "hybrid") && it.assembler1 == "hifiasm" ? it.hifiasm_assembly : null, - assembly1: it.strategy == "scaffold" && it.assembler1 == "hifiasm" ? it.hifiasm_assembly : null, - assembly2: it.strategy == "scaffold" && it.assembler2 == "hifiasm" ? it.hifiasm_assembly : null + ch_main_assemble_ont_hifiasm + .map { it -> it.collect { entry -> [ entry.value, entry ] } } + .join( GFA_2_FA_ONT.out.contigs_fasta + .map { meta, assembly -> [meta: meta, hifiasm_assembly: assembly ] } + .map { it -> it.collect { entry -> [ entry.value, entry ] } } + ) + // After joining re-create the maps from the stored map + .map { it -> it.collect { _entry, map -> [ (map.key): map.value ] }.collectEntries() } + .map { + it -> it -it.subMap("hifiasm_assembly") + + [ + assembly: (it.strategy == "single" || it.strategy == "hybrid") && it.assembler1 == "hifiasm" ? it.hifiasm_assembly : null, + assembly1: it.strategy == "scaffold" && it.assembler1 == "hifiasm" ? it.hifiasm_assembly : null, + assembly2: it.strategy == "scaffold" && it.assembler2 == "hifiasm" ? it.hifiasm_assembly : null + ] + } + .set { hifiasm_ont_assemblies } + + hifiasm_ont_assemblies.dump(tag: "Assemble: hifiasm HIFI assemblies") + // The single and hybrid channels can be mixed and forwarded. + // The scaffold channel needs to be joined separately. + flye_assemblies + .filter { it -> ["single","hybrid"].contains(it.strategy) } + .mix( + hifiasm_hifi_assemblies + .filter { it -> ["single","hybrid"].contains(it.strategy) } + ) + .mix( + hifiasm_ont_assemblies + .filter { it -> ["single","hybrid"].contains(it.strategy) } + ) + .set { ch_assemblies_no_scaffold } + + ch_assemblies_no_scaffold.dump(tag: "Assemble: Assemblies without scaffolding") + // This leaves the scaffold strategy. + // scaffolds can be: FLYE-HIFIASM, FLYE-FLYE, HIFIASM-HIFIASM HIFIASM-FLYE or + flye_assemblies + // Flye-hifiasm + .filter { it -> it.strategy == "scaffold" && it.assembler1 == "flye" && it.assembler2 == "hifiasm" } + .map { it -> it.collect { entry -> [ entry.value, entry ] } } + .join( hifiasm_hifi_assemblies + .filter{ it -> it.strategy == "scaffold" && it.assembler1 == "flye" && it.assembler2 == "hifiasm" } + .map { it -> [ meta: it.meta, hifiasm_assembly: it.assembly2 ] } + .map { it -> it.collect { entry -> [ entry.value, entry ] } } + ) + .map { it -> it.collect { _entry, map -> [ (map.key): map.value ] }.collectEntries() } + .map { it -> it - it.subMap("hifiasm_assembly","assembly2") + [assembly2: it.hifiasm_assembly] } + .set{ scaffold_flye_hifiasm } + // flye-flye + flye_assemblies + .filter { it -> it.strategy == "scaffold" && it.assembler1 == "flye" && it.assembler2 == "flye" } + .map { it -> it.collect { entry -> [ entry.value, entry ] } } + .join( flye_assemblies + .filter{ it -> it.strategy == "scaffold" && it.assembler1 == "flye" && it.assembler2 == "flye" } + .map { it -> [ meta: it.meta, flye_assembly: it.assembly2 ] } + .map { it -> it.collect { entry -> [ entry.value, entry ] } } + ) + .map { it -> it.collect { _entry, map -> [ (map.key): map.value ] }.collectEntries() } + .map { it -> it - it.subMap("flye_assembly","assembly2") + [assembly2: it.flye_assembly] } + .set{ scaffold_flye_flye } + // hifiasm_flye + hifiasm_ont_assemblies + .filter { it -> it.strategy == "scaffold" && it.assembler1 == "hifiasm" && it.assembler2 == "flye" } + .map { it -> it.collect { entry -> [ entry.value, entry ] } } + .join( flye_assemblies + .filter{ it -> it.strategy == "scaffold" && it.assembler1 == "hifiasm" && it.assembler2 == "flye" } + .map { it -> [ meta: it.meta, flye_assembly: it.assembly2 ] } + .map { it -> it.collect { entry -> [ entry.value, entry ] } } + ) + .map { it -> it.collect { _entry, map -> [ (map.key): map.value ] }.collectEntries() } + .map { it -> it - it.subMap("flye_assembly","assembly2") + [assembly2: it.flye_assembly] } + .set{ scaffold_hifiasm_flye } + // hifiasm_hifiasm + hifiasm_ont_assemblies + .filter { it -> it.strategy == "scaffold" && it.assembler1 == "hifiasm" && it.assembler2 == "hifiasm" } + .map { it -> it.collect { entry -> [ entry.value, entry ] } } + .join( hifiasm_hifi_assemblies + .filter{ it -> it.strategy == "scaffold" && it.assembler1 == "hifiasm" && it.assembler2 == "hifiasm" } + .map { it -> [ meta: it.meta, hifiasm_assembly: it.assembly2 ] } + .map { it -> it.collect { entry -> [ entry.value, entry ] } } + ) + .map { it -> it.collect { _entry, map -> [ (map.key): map.value ] }.collectEntries() } + .map { it -> it - it.subMap("hifiasm_assembly","assembly2") + [assembly2: it.hifiasm_assembly] } + .set{ scaffold_hifiasm_hifiasm } + // branch to scaffold those assemblies that need it + scaffold_flye_hifiasm + .mix(scaffold_flye_flye) + .mix(scaffold_hifiasm_flye) + .mix(scaffold_hifiasm_hifiasm) + .set { ch_to_scaffold } + + ch_to_scaffold.dump(tag: "Assemble: Assemblies with scaffolding - inputs") + + ch_to_scaffold + .multiMap { + it -> + target: [ + it.meta, + it.assembly_scaffolding_order == "ont_on_hifi" ? (it.assembly1) : (it.assembly2) ] - } - .set { hifiasm_ont_assemblies } + query: [ + it.meta, + it.assembly_scaffolding_order == "ont_on_hifi" ? (it.assembly2) : (it.assembly1) + ] + } + .set { ragtag_in } - // The single and hybrid channels can be mixed and forwarded. - // The scaffold channel needs to be joined separately. - flye_assemblies - .filter { it -> ["single","hybrid"].contains(it.strategy) } - .mix( - hifiasm_hifi_assemblies - .filter { it -> ["single","hybrid"].contains(it.strategy) } - ) - .mix( - hifiasm_ont_assemblies - .filter { it -> ["single","hybrid"].contains(it.strategy) } - ) - .set { ch_assemblies_no_scaffold } - - // This leaves the scaffold strategy. - // scaffolds can be: FLYE-HIFIASM, FLYE-FLYE, HIFIASM-HIFIASM HIFIASM-FLYE or - flye_assemblies - // Flye-hifiasm - .filter { it -> it.strategy == "scaffold" && it.assembler1 == "flye" && it.assembler2 == "hifiasm" } - .map { it -> it.collect { entry -> [ entry.value, entry ] } } - .join( hifiasm_hifi_assemblies - .filter{ it -> it.strategy == "scaffold" && it.assembler1 == "flye" && it.assembler2 == "hifiasm" } - .map { it -> [ meta: it.meta, hifiasm_assembly: it.assembly2 ] } - .map { it -> it.collect { entry -> [ entry.value, entry ] } } - ) - .map { it -> it.collect { _entry, map -> [ (map.key): map.value ] }.collectEntries() } - .map { it -> it - it.subMap("hifiasm_assembly","assembly2") + [assembly2: it.hifiasm_assembly] } - .set{ scaffold_flye_hifiasm } - - // flye-flye - flye_assemblies - .filter { it -> it.strategy == "scaffold" && it.assembler1 == "flye" && it.assembler2 == "flye" } - .map { it -> it.collect { entry -> [ entry.value, entry ] } } - .join( flye_assemblies - .filter{ it -> it.strategy == "scaffold" && it.assembler1 == "flye" && it.assembler2 == "flye" } - .map { it -> [ meta: it.meta, flye_assembly: it.assembly2 ] } - .map { it -> it.collect { entry -> [ entry.value, entry ] } } - ) - .map { it -> it.collect { _entry, map -> [ (map.key): map.value ] }.collectEntries() } - .map { it -> it - it.subMap("flye_assembly","assembly2") + [assembly2: it.flye_assembly] } - .set{ scaffold_flye_flye } - - // hifiasm_flye - hifiasm_ont_assemblies - .filter { it -> it.strategy == "scaffold" && it.assembler1 == "hifiasm" && it.assembler2 == "flye" } - .map { it -> it.collect { entry -> [ entry.value, entry ] } } - .join( flye_assemblies - .filter{ it -> it.strategy == "scaffold" && it.assembler1 == "hifiasm" && it.assembler2 == "flye" } - .map { it -> [ meta: it.meta, flye_assembly: it.assembly2 ] } - .map { it -> it.collect { entry -> [ entry.value, entry ] } } - ) - .map { it -> it.collect { _entry, map -> [ (map.key): map.value ] }.collectEntries() } - .map { it -> it - it.subMap("flye_assembly","assembly2") + [assembly2: it.flye_assembly] } - .set{ scaffold_hifiasm_flye } - - // hifiasm_hifiasm - hifiasm_ont_assemblies - .filter { it -> it.strategy == "scaffold" && it.assembler1 == "hifiasm" && it.assembler2 == "hifiasm" } - .map { it -> it.collect { entry -> [ entry.value, entry ] } } - .join( hifiasm_hifi_assemblies - .filter{ it -> it.strategy == "scaffold" && it.assembler1 == "hifiasm" && it.assembler2 == "hifiasm" } - .map { it -> [ meta: it.meta, hifiasm_assembly: it.assembly2 ] } - .map { it -> it.collect { entry -> [ entry.value, entry ] } } - ) - .map { it -> it.collect { _entry, map -> [ (map.key): map.value ] }.collectEntries() } - .map { it -> it - it.subMap("hifiasm_assembly","assembly2") + [assembly2: it.hifiasm_assembly] } - .set{ scaffold_hifiasm_hifiasm } - - // branch to scaffold those assemblies that need it - scaffold_flye_hifiasm - .mix(scaffold_flye_flye) - .mix(scaffold_hifiasm_flye) - .mix(scaffold_hifiasm_hifiasm) - .set { ch_to_scaffold } - - ch_to_scaffold - .multiMap { - it -> - target: [ - it.meta, - it.assembly_scaffolding_order == "ont_on_hifi" ? (it.assembly1) : (it.assembly2) - ] - query: [ - it.meta, - it.assembly_scaffolding_order == "ont_on_hifi" ? (it.assembly2) : (it.assembly1) - ] - } - .set { ragtag_in } + RAGTAG_PATCH(ragtag_in.target, ragtag_in.query, [[], []], [[], []] ) + ch_to_scaffold + .map { it -> it - it.subMap("assembly") } + .map { it -> it.collect { entry -> [ entry.value, entry ] } } + .join( + RAGTAG_PATCH.out.patch_fasta + .map { it -> [meta: it[0], assembly: it[1]] } + .map { it -> it.collect { entry -> [ entry.value, entry ] } } + ) + .map { it -> it.collect { _entry, map -> [ (map.key): map.value ] }.collectEntries() } + .set { ch_assemblies_scaffold } - RAGTAG_PATCH(ragtag_in.target, ragtag_in.query, [[], []], [[], []] ) + ch_assemblies_scaffold.dump(tag: "Assemble: Assemblies with scaffolding - outputs") - ch_to_scaffold - .map { it -> it - it.subMap("assembly") } - .map { it -> it.collect { entry -> [ entry.value, entry ] } } - .join( - RAGTAG_PATCH.out.patch_fasta - .map { it -> [meta: it[0], assembly: it[1]] } - .map { it -> it.collect { entry -> [ entry.value, entry ] } } - ) - .map { it -> it.collect { _entry, map -> [ (map.key): map.value ] }.collectEntries() } - .set { ch_assemblies_scaffold } - ch_assemblies_no_scaffold - .mix(ch_assemblies_scaffold) - .set { ch_main_assembled } + ch_assemblies_no_scaffold + .mix(ch_assemblies_scaffold) + .set { ch_main_assembled } + ch_main_assembled.dump(tag: "Assemble: Assembled") ch_versions = ch_versions.mix(RAGTAG_PATCH.out.versions) @@ -294,7 +315,7 @@ workflow ASSEMBLE { .mix( ch_main_assembled ) .set { ch_main_to_mapping } - //ch_main_to_mapping.view { it -> "TO MAPPING: $it"} + ch_main_to_mapping.dump(tag: "Assemble: TO MAPPING") ch_main_to_mapping .branch { diff --git a/subworkflows/local/prepare/jellyfish/main.nf b/subworkflows/local/prepare/jellyfish/main.nf index b863a7fe..7af9d67f 100644 --- a/subworkflows/local/prepare/jellyfish/main.nf +++ b/subworkflows/local/prepare/jellyfish/main.nf @@ -12,17 +12,41 @@ workflow JELLYFISH { Channel.empty().set { genomescope_in } Channel.empty().set { ch_versions } - ch_main.map { - it -> - [ - [id: it.meta.id, jellyfish_k: it.jellyfish_k], - it.qc_reads_path - ] + ch_main + .filter { it -> it.group } + .map { it -> [it.meta, it.group, it.jellyfish_k, it.qc_reads_path, it.qc_read_mean] } + .groupTuple(by: 1) + .map { + it -> + [ + meta: [ + id: it[1], + ids: it[0].id.collect().join("+"), + jellyfish_k: it[2].unique()[0], + qc_read_mean: it[4].unique()[0] + ], + qc_reads_path: it[3].unique()[0] + ] } - .set { samples } + .mix( + ch_main + .filter { it -> !it.group } + .map { + it -> + [ + meta: [ + id: it.meta.id, + jellyfish_k: it.jellyfish_k, + qc_read_mean: it.qc_read_mean + ], + qc_reads_path: it.qc_reads_path + ] + } + ) + .set { samples } COUNT(samples) - COUNT.out.kmers.map {meta, counts -> [[id: meta.id], counts]}.set { kmers } + COUNT.out.kmers.set { kmers } ch_versions = ch_versions.mix(COUNT.out.versions) @@ -36,24 +60,24 @@ workflow JELLYFISH { HISTO.out.histo .join( - ch_main + samples .map { it -> [ it.meta, - it.jellyfish_k, - it.qc_read_mean + it.meta.jellyfish_k, + it.meta.qc_read_mean ] } ) .set { genomescope_in } - GENOMESCOPE(genomescope_in) - - ch_versions = ch_versions.mix(GENOMESCOPE.out.versions) - STATS(kmers) - ch_versions = ch_versions.mix(STATS.out.versions) + GENOMESCOPE(genomescope_in) + + ch_versions = ch_versions + .mix(GENOMESCOPE.out.versions) + .mix(STATS.out.versions) ch_main .map { @@ -64,12 +88,24 @@ workflow JELLYFISH { } .join( GENOMESCOPE.out.estimated_hap_len + .filter { it -> it[0].ids } + .flatMap { it -> + it[0].ids + .tokenize("+") + .collect { sample -> [ [ id: sample ], it[1] ] } + } + .mix(GENOMESCOPE.out.estimated_hap_len + .filter { it -> !it[0].ids } .map { - it -> - [ - meta: it[0], - genome_size: it[1] - ] + it -> [ [ id: it[0].id ], it[1] ] + } + ) + .map { + it -> + [ + meta: it[0], + genome_size: it[1] + ] } .map { it -> it.collect { entry -> [ entry.value, entry ] } diff --git a/subworkflows/local/prepare/main.nf b/subworkflows/local/prepare/main.nf index 3524ca8c..77c0ab38 100644 --- a/subworkflows/local/prepare/main.nf +++ b/subworkflows/local/prepare/main.nf @@ -162,14 +162,6 @@ workflow PREPARE { ch_main_prepared } - ch_main_prepared - .branch { - it -> - jellyfish: it.jellyfish - no_jelly: !it.jellyfish - } - .set { ch_main_jellyfish_branched } - // Get average read length of the QC reads from fastplong json report def slurp = new groovy.json.JsonSlurper() @@ -205,12 +197,15 @@ workflow PREPARE { } .set { ch_main_jellyfish_branched } + // TODO: Jellyfish is currently not grouped JELLYFISH(ch_main_jellyfish_branched.jelly) ch_main_jellyfish_branched.no_jelly .mix( JELLYFISH.out.main_out ) .set { main_out } + main_out.dump(tag: "Prepare: Combined outputs") + JELLYFISH.out.genomescope_summary.set { genomescope_summary } JELLYFISH.out.genomescope_plot.set { genomescope_plot } diff --git a/subworkflows/local/prepare/prepare_hifi/main.nf b/subworkflows/local/prepare/prepare_hifi/main.nf index 3a7269c7..5f21062b 100644 --- a/subworkflows/local/prepare/prepare_hifi/main.nf +++ b/subworkflows/local/prepare/prepare_hifi/main.nf @@ -7,8 +7,8 @@ workflow PREPARE_HIFI { main: Channel.empty().set { ch_versions } + main_in.dump(tag: "Prepare-HIFI input") main_in - .filter { it -> it.hifi_trim} .filter { it -> it.group } .map { it -> [it.meta, it.group, it.hifi_trim, it.hifireads, it.hifi_adapters, it.hifi_fastplong_args] } .groupTuple(by: 1) @@ -26,7 +26,6 @@ workflow PREPARE_HIFI { } .mix( main_in - .filter { it -> it.hifi_trim} .filter { it -> !it.group } .map { it -> @@ -86,7 +85,6 @@ workflow PREPARE_HIFI { // inputs are joined to outputs main_in - .filter { it -> it.hifi_trim } .map { it -> it - it.subMap('hifireads') } .map { it -> it.collect { entry -> [ entry.value, entry ] } } .join( @@ -94,9 +92,10 @@ workflow PREPARE_HIFI { .map { it -> it.collect { entry -> [ entry.value, entry ] } } ) .map { it -> it.collect { _entry, map -> [ (map.key): map.value ] }.collectEntries() } - .mix(main_in.filter { it -> it.hifi_trim }) .set { main_out } + main_out.dump(tag: "Prepare-HIFI output") + versions = ch_versions.mix(FASTPLONG_HIFI.out.versions) emit: diff --git a/subworkflows/local/prepare/prepare_ont/main.nf b/subworkflows/local/prepare/prepare_ont/main.nf index ae3b6818..0aa215b4 100644 --- a/subworkflows/local/prepare/prepare_ont/main.nf +++ b/subworkflows/local/prepare/prepare_ont/main.nf @@ -9,6 +9,7 @@ workflow PREPARE_ONT { main: Channel.empty().set { ch_versions } + ch_main.dump(tag: "Prepare-ONT input") ch_main .branch { it -> @@ -68,7 +69,6 @@ workflow PREPARE_ONT { // ch_collected is the same samples as the input channel ch_collected - .filter { it -> it.ont_trim} .filter { it -> it.group } .map { it -> [it.meta, it.group, it.ont_trim, it.ontreads, it.ont_adaptors, it.ont_fastplong_args] } .groupTuple(by: 1) @@ -86,7 +86,6 @@ workflow PREPARE_ONT { } .mix( ch_collected - .filter { it -> it.ont_trim} .filter { it -> !it.group } .map { it -> @@ -145,7 +144,6 @@ workflow PREPARE_ONT { .set { fastplong_json_out } ch_collected - .filter { it -> it.ont_trim } .map { it -> it - it.subMap('ontreads') } .map { it -> it.collect { entry -> [ entry.value, entry ] } } .join( @@ -153,11 +151,12 @@ workflow PREPARE_ONT { .map { it -> it.collect { entry -> [ entry.value, entry ] } } ) .map { it -> it.collect { _entry, map -> [ (map.key): map.value ] }.collectEntries() } - .mix(ch_collected.filter { it -> !it.ont_trim }) .set { main_out } versions = ch_versions.mix(COLLECT.out.versions).mix(FASTPLONG_ONT.out.versions) + main_out.dump(tag: "Prepare-ONT output") + emit: main_out fastplong_ont_reports = fastplong_json_out From 67802f4002a11e10f34a0a67df12fd6809e303ed Mon Sep 17 00:00:00 2001 From: Niklas Schandry Date: Thu, 4 Sep 2025 16:22:33 +0200 Subject: [PATCH 057/162] remove jellyfish dump --- subworkflows/local/prepare/jellyfish/main.nf | 1 - 1 file changed, 1 deletion(-) diff --git a/subworkflows/local/prepare/jellyfish/main.nf b/subworkflows/local/prepare/jellyfish/main.nf index 7af9d67f..bf9d7422 100644 --- a/subworkflows/local/prepare/jellyfish/main.nf +++ b/subworkflows/local/prepare/jellyfish/main.nf @@ -1,5 +1,4 @@ include { COUNT } from '../../../../modules/local/jellyfish/count/main' -include { DUMP } from '../../../../modules/local/jellyfish/dump/main' include { HISTO } from '../../../../modules/local/jellyfish/histo/main' include { STATS } from '../../../../modules/local/jellyfish/stats/main' include { GENOMESCOPE } from '../../../../modules/local/genomescope/main' From efb4dba1264e3e0a876418bbb714fa412839703b Mon Sep 17 00:00:00 2001 From: Niklas Schandry Date: Thu, 11 Sep 2025 09:50:37 +0200 Subject: [PATCH 058/162] report grouping fixes --- assets/report/functions/read_fastplong.R | 4 +-- assets/report/report.qmd | 36 ++++++------------------ workflows/genomeassembler.nf | 6 +++- 3 files changed, 16 insertions(+), 30 deletions(-) diff --git a/assets/report/functions/read_fastplong.R b/assets/report/functions/read_fastplong.R index 475cba44..1ad50a42 100644 --- a/assets/report/functions/read_fastplong.R +++ b/assets/report/functions/read_fastplong.R @@ -5,7 +5,7 @@ library(readr) # Read a nanoq json report read_fastplong <- function(file) { - sample_name <- file %>% str_extract('(?<=fastplong/).+?(?=_(ont|hifi)\\.fastplong\\.json)') + group_name <- file %>% str_extract('(?<=fastplong/).+?(?=_(ont|hifi)\\.fastplong\\.json)') read_type <- file %>% str_extract('(?<=fastplong/).*') %>% str_extract('_(ont|hifi)\\.') %>% @@ -18,7 +18,7 @@ read_fastplong <- function(file) { pivot_longer(everything(), names_to = c("stage", "stat"), names_pattern = "(.*)\\.(.*)") %>% - mutate(sample = sample_name) %>% + mutate(group = group_name) %>% mutate( stat = stat %>% str_replace_all("_", " ") %>% diff --git a/assets/report/report.qmd b/assets/report/report.qmd index b093edaf..6beb8f37 100644 --- a/assets/report/report.qmd +++ b/assets/report/report.qmd @@ -71,7 +71,7 @@ fastplong_reports <- list.files(paste0(data_base, "fastplong"), pattern = ".json", full.names = T) %>% map_dfr(\(x) read_fastplong(x)) %>% - left_join(groups, by = join_by(sample)) + left_join(groups, by = join_by(group)) ``` ```{r} @@ -88,8 +88,9 @@ paste0('```{r}\n p <- fastplong_reports %>% filter(group == "', unique(fastplong_reports$group)[i], '") %>% ggplot(aes(x = sample, y = value)) + - geom_point(size = 5, pch=21, aes(fill=stage), position = position_dodge(width = 0.5)) + - facet_wrap(read_type~stat, scales = "free_y", ncol=2, , labeller = label_context) + + geom_line() + + geom_point(size = 5, pch=21, aes(fill=stage)) + + facet_wrap(read_type~stat, scales = "free_y", ncol=2) + fill_scale_plots + scale_y_continuous(labels = function(x) format(x,scientific=-1,trim=T, digits = 3, drop0trailing=T), n.breaks = 4) + theme(axis.title.x = element_blank(), @@ -114,51 +115,32 @@ paste0('```{r}\n ## Each tab contains 3 valueboxes ## Below the valueboxes, the sample-specific plot code generated above is inserted -for (i in 1:length(unique(fastplong_reports$sample))) { - cat(paste0('## ', unique(fastplong_reports$sample)[i], '\n\n'), +for (i in 1:length(unique(fastplong_reports$group))) { + cat(paste0('## ', unique(fastplong_reports$group)[i], '\n\n'), paste0('### { width = 30% }', '\n\n'), paste0('::: {.valuebox icon="magic" color="primary" title="Total bases sequenced"}','\n'), paste0(fastplong_reports %>% filter(stat == "Total Bases", stage == "After Filtering") %>% - filter(sample == unique(fastplong_reports$sample)[i],) %$% + filter(sample == unique(fastplong_reports$group)[i]) %$% sum(value) %>% format(scientific=-1,trim=T, digits = 3, drop0trailing=T),'\n'), paste0(':::', '\n\n'), paste0('::: {.valuebox icon="collection" color="secondary" title="Number of reads"}', '\n'), paste0(fastplong_reports %>% filter(stat == "Total Reads", stage == "After Filtering") %>% - filter(sample == unique(fastplong_reports$sample)[i]) %$% + filter(sample == unique(fastplong_reports$group)[i]) %$% sum(value) %>% paste(" bases"), '\n'), paste0(':::', '\n\n'), paste0('::: {.valuebox icon="chevron-double-up" color="success" title="Q30 rate"}', '\n'), paste0(fastplong_reports %>% filter(stat == "Q30 Rate", stage == "After Filtering") %>% - filter(sample == unique(fastplong_reports$sample)[i]) %$% + filter(sample == unique(fastplong_reports$group)[i]) %$% {value*100} %>% round(1) %>% paste0(" %"), '\n'), paste0(':::', '\n\n'), - paste0('\n\n'), - paste0('### {.tabset}'), - paste0('\n\n'), - paste0('#### Tables \n\n'), - fastplong_reports %>% - filter(sample == unique(fastplong_reports$sample)[i]) %>% - pivot_wider(id_cols = c("sample","read_type","stat"),names_from = "stage") %>% - gt::gt() %>% - gt::cols_label( - sample = "Group", - read_type = "Read Type", - stat = "Metric" - ) %>% - gt::fmt_number(suffixing = TRUE, n_sigfig = 3, rows = ! (stat %>% str_detect("Rate"))) %>% - gt::fmt_percent(rows = stat %>% str_detect("Rate")) %>% - gt::as_raw_html() - , - paste0('\n\n'), - paste0('#### Plots'), paste0('### ', '\n\n'), knitr::knit_child(glue::glue('fastplong_files/_{ unique(fastplong_reports$sample)[i] }_fastplong.Rmd'), envir = globalenv(), diff --git a/workflows/genomeassembler.nf b/workflows/genomeassembler.nf index 498fbd61..8196d34a 100644 --- a/workflows/genomeassembler.nf +++ b/workflows/genomeassembler.nf @@ -175,7 +175,9 @@ workflow GENOMEASSEMBLER { .set { ch_main_scaffolded } PREPARE.out.fastplong_json_reports - .collect { it -> it[1] } + .map { it -> it[1] } + .unique() + .collect() .set { fasplong_jsons } PREPARE.out.genomescope_summary @@ -251,6 +253,8 @@ workflow GENOMEASSEMBLER { .collect() .set { report_functions } + //fasplong_jsons.view { it -> "UNQIE JSONS: $it"} + REPORT( report_files, report_functions, fasplong_jsons, From e324ac92cac0043ac89c62a74061afd06424975b Mon Sep 17 00:00:00 2001 From: Niklas Schandry Date: Thu, 11 Sep 2025 11:26:04 +0200 Subject: [PATCH 059/162] no more plotly for merqury plots --- assets/report/report.qmd | 31 +++++++++++++++---------------- 1 file changed, 15 insertions(+), 16 deletions(-) diff --git a/assets/report/report.qmd b/assets/report/report.qmd index 6beb8f37..abd76ec5 100644 --- a/assets/report/report.qmd +++ b/assets/report/report.qmd @@ -547,9 +547,8 @@ meryl and merqury were not included in the pipeline run. merqury_stats <- list.files(paste0(data_base, "merqury"), full.names = T, pattern = "stats") %>% lapply(\(x) { read_tsv(x, col_names = c("sample_stage","all","assembly","total","percent"), show_col_types = FALSE) %>% - # Get sample name by matching filename to samples in groups, reverse sort by length to hopefully catch + mutate( # Get sample name by matching filename to samples in groups, reverse sort by length to hopefully catch # the correct name first in case there is partial overlap between sample names. - mutate( sample = basename(x) %>% str_extract(groups$sample[rev(order(nchar(groups$sample)))] %>% paste(collapse = "|")), stage = case_when( @@ -558,7 +557,7 @@ merqury_stats <- list.files(paste0(data_base, "merqury"), full.names = T, patter str_detect(x, "_pilon") ~ "pilon", str_detect(x, "_longstitch") ~ "longstitch", str_detect(x, "_links") ~ "LINKS", - str_detect(x, "assembl[ey]") ~ "Assembly", + str_detect(x, "assembl[ey]") ~ "Assembly" TRUE ~ "Unknown")) }) %>% bind_rows() %>% left_join(groups, by = join_by(sample)) @@ -567,15 +566,15 @@ merqury_asm_hists <- list.files(paste0(data_base, "/merqury"), full.names = T, p lapply(\(x) { read_tsv(x, col_names = T, show_col_types = FALSE) %>% mutate( - sample = basename(x) %>% - str_extract(groups$sample[rev(order(nchar(groups$sample)))] %>% paste(collapse = "|")), + sample = str_extract(x %>% basename(), + ".+?(?=_\\.assembly|\\-assemble|links|longstitch|ragtag|medaka|pilon|\\-run|\\-polish)"), stage = case_when( str_detect(x, "_ragtag") ~ "RagTag", str_detect(x, "_medaka") ~ "medaka", str_detect(x, "_pilon") ~ "pilon", str_detect(x, "_longstitch") ~ "longstitch", str_detect(x, "_links") ~ "LINKS", - str_detect(x, "assembl[ey]") ~ "Assembly", + str_detect(x, "assembl[ey]") ~ "Assembly" TRUE ~ "Unknown"), Assembly = as.factor(Assembly), stage = as.factor(stage), @@ -590,15 +589,15 @@ merqury_cn_hists <- list.files(paste0(data_base, "merqury"), full.names = T, pat lapply(\(x) { read_tsv(x, col_names = T, show_col_types = FALSE) %>% mutate( - sample = basename(x) %>% - str_extract(groups$sample[rev(order(nchar(groups$sample)))] %>% paste(collapse = "|")), + sample = str_extract(x %>% basename(), + ".+?(?=_\\.assembly|\\-assemble|links|longstitch|ragtag|medaka|pilon|\\-run|\\-polish)"), stage = case_when( str_detect(x, "_ragtag") ~ "RagTag", str_detect(x, "_medaka") ~ "medaka", str_detect(x, "_pilon") ~ "pilon", str_detect(x, "_longstitch") ~ "longstitch", str_detect(x, "_links") ~ "LINKS", - str_detect(x, "assembl[ey]") ~ "Assembly", + str_detect(x, "assembl[ey]") ~ "Assembly" TRUE ~ "Unknown"), Copies = as.factor(Copies), stage = as.factor(stage), @@ -615,15 +614,15 @@ merqury_qv <- list.files(paste0(data_base, "merqury"), full.names = T, pattern = col_names = c("Assembly", "kmers_assembly_unique", "kmers_assembly_shared", "QV", "error_rate"), show_col_types = FALSE) %>% mutate( - sample = basename(x) %>% - str_extract(groups$sample[rev(order(nchar(groups$sample)))] %>% paste(collapse = "|")), + sample = str_extract(x %>% basename(), + ".+?(?=_\\.assembly|\\-assemble|links|longstitch|ragtag|medaka|pilon|\\-run|\\-polish)"), stage = case_when( str_detect(x, "_ragtag") ~ "RagTag", str_detect(x, "_medaka") ~ "medaka", str_detect(x, "_pilon") ~ "pilon", str_detect(x, "_longstitch") ~ "longstitch", str_detect(x, "_links") ~ "LINKS", - str_detect(x, "assembl[ey]") ~ "Assembly", + str_detect(x, "assembl[ey]") ~ "Assembly" TRUE ~ "Unknown"), stage = as.factor(stage), sample = as.factor(sample), @@ -648,7 +647,7 @@ for (i in 1:length(unique(merqury_qv$group))) { paste0('```{r} p <- merqury_qv %>% plot_merqury_qv("', cur_group,'") -ggplotly(p)\n```') %>% +print(p)\n```') %>% write_lines(glue::glue("merqury_files/qv_plots/_{ cur_group }_qv_plt.Rmd")) } ``` @@ -664,7 +663,7 @@ for (i in 1:length(unique(merqury_stats$group))) { paste0('```{r} p <- merqury_stats %>% plot_merqury_stats("', cur_group,'") -ggplotly(p)\n```') %>% +print(p)\n```') %>% write_lines(glue::glue("merqury_files/stat_plots/_{ cur_group }_completeness_plt.Rmd")) } ``` @@ -680,7 +679,7 @@ for (i in 1:length(unique(merqury_asm_hists$group))) { paste0('```{r} p <- merqury_asm_hists %>% plot_merqury_multiplicity("', cur_group,'") -ggplotly(p)\n```') %>% +print(p)\n```') %>% write_lines(glue::glue("merqury_files/asm_plots/_{ cur_group }_asm_plt.Rmd")) } ``` @@ -696,7 +695,7 @@ for (i in 1:length(unique(merqury_cn_hists$group))) { paste0('```{r} p <- merqury_cn_hists %>% plot_merqury_copynumber("', cur_group,'") -ggplotly(p)\n```') %>% +print(p)\n```') %>% write_lines(glue::glue("merqury_files/cn_plots/_{ cur_group }_cn_plt.Rmd")) } ``` From 79ecc1ee9b444f84592928890aa6aa5584b8fe85 Mon Sep 17 00:00:00 2001 From: Niklas Schandry Date: Fri, 19 Sep 2025 15:43:47 +0200 Subject: [PATCH 060/162] fix report grouping --- .nf-test.log | 16 ---------------- assets/report/report.qmd | 25 ++++++++++++------------- 2 files changed, 12 insertions(+), 29 deletions(-) delete mode 100644 .nf-test.log diff --git a/.nf-test.log b/.nf-test.log deleted file mode 100644 index 4d19adc8..00000000 --- a/.nf-test.log +++ /dev/null @@ -1,16 +0,0 @@ -Sep-01 10:36:09.700 [main] INFO com.askimed.nf.test.App - nf-test 0.9.2 -Sep-01 10:36:09.727 [main] INFO com.askimed.nf.test.App - Arguments: [test, --profile=+conda] -Sep-01 10:36:11.061 [main] INFO com.askimed.nf.test.App - Nextflow Version: 24.10.5 -Sep-01 10:36:11.065 [main] INFO com.askimed.nf.test.commands.RunTestsCommand - Load config from file /dss/dsshome1/lxc0A/ra34bin/genomeassembler/nf-test.config... -Sep-01 10:36:13.124 [main] INFO com.askimed.nf.test.lang.dependencies.DependencyResolver - Loaded 43 files from directory /dss/dsshome1/lxc0A/ra34bin/genomeassembler in 1.486 sec -Sep-01 10:36:13.126 [main] INFO com.askimed.nf.test.lang.dependencies.DependencyResolver - Found 1 files containing tests. -Sep-01 10:36:13.126 [main] DEBUG com.askimed.nf.test.lang.dependencies.DependencyResolver - Found files: [/dss/dsshome1/lxc0A/ra34bin/genomeassembler/tests/default.nf.test] -Sep-01 10:36:13.370 [main] INFO com.askimed.nf.test.commands.RunTestsCommand - Found 1 tests to execute. -Sep-01 10:36:13.372 [main] INFO com.askimed.nf.test.core.TestExecutionEngine - Started test plan -Sep-01 10:36:13.372 [main] INFO com.askimed.nf.test.core.TestExecutionEngine - Running testsuite 'Test pipeline' from file '/dss/dsshome1/lxc0A/ra34bin/genomeassembler/tests/default.nf.test'. -Sep-01 10:36:13.373 [main] INFO com.askimed.nf.test.core.TestExecutionEngine - Run test 'c85cb7f4: -profile test'. type: com.askimed.nf.test.lang.pipeline.PipelineTest -Sep-01 10:36:37.842 [main] DEBUG com.askimed.nf.test.lang.extensions.SnapshotFile - Load snapshots from file '/dss/dsshome1/lxc0A/ra34bin/genomeassembler/tests/default.nf.test.snap' -Sep-01 10:36:37.863 [main] DEBUG com.askimed.nf.test.lang.extensions.Snapshot - Snapshots '-profile test' match. -Sep-01 10:36:37.863 [main] INFO com.askimed.nf.test.core.TestExecutionEngine - Test 'c85cb7f4: -profile test' finished. status: PASSED -Sep-01 10:36:37.866 [main] INFO com.askimed.nf.test.core.TestExecutionEngine - Testsuite 'Test pipeline' finished. snapshot file: true, skipped tests: false, failed tests: false -Sep-01 10:36:37.866 [main] INFO com.askimed.nf.test.core.TestExecutionEngine - Executed 1 tests. 0 tests failed. Done! diff --git a/assets/report/report.qmd b/assets/report/report.qmd index abd76ec5..e81d9d00 100644 --- a/assets/report/report.qmd +++ b/assets/report/report.qmd @@ -557,7 +557,7 @@ merqury_stats <- list.files(paste0(data_base, "merqury"), full.names = T, patter str_detect(x, "_pilon") ~ "pilon", str_detect(x, "_longstitch") ~ "longstitch", str_detect(x, "_links") ~ "LINKS", - str_detect(x, "assembl[ey]") ~ "Assembly" + str_detect(x, "assembl[ey]") ~ "Assembly", TRUE ~ "Unknown")) }) %>% bind_rows() %>% left_join(groups, by = join_by(sample)) @@ -566,15 +566,15 @@ merqury_asm_hists <- list.files(paste0(data_base, "/merqury"), full.names = T, p lapply(\(x) { read_tsv(x, col_names = T, show_col_types = FALSE) %>% mutate( - sample = str_extract(x %>% basename(), - ".+?(?=_\\.assembly|\\-assemble|links|longstitch|ragtag|medaka|pilon|\\-run|\\-polish)"), + sample = basename(x) %>% + str_extract(groups$sample[rev(order(nchar(groups$sample)))] %>% paste(collapse = "|")), stage = case_when( str_detect(x, "_ragtag") ~ "RagTag", str_detect(x, "_medaka") ~ "medaka", str_detect(x, "_pilon") ~ "pilon", str_detect(x, "_longstitch") ~ "longstitch", str_detect(x, "_links") ~ "LINKS", - str_detect(x, "assembl[ey]") ~ "Assembly" + str_detect(x, "assembl[ey]") ~ "Assembly", TRUE ~ "Unknown"), Assembly = as.factor(Assembly), stage = as.factor(stage), @@ -589,15 +589,15 @@ merqury_cn_hists <- list.files(paste0(data_base, "merqury"), full.names = T, pat lapply(\(x) { read_tsv(x, col_names = T, show_col_types = FALSE) %>% mutate( - sample = str_extract(x %>% basename(), - ".+?(?=_\\.assembly|\\-assemble|links|longstitch|ragtag|medaka|pilon|\\-run|\\-polish)"), + sample = basename(x) %>% + str_extract(groups$sample[rev(order(nchar(groups$sample)))] %>% paste(collapse = "|")), stage = case_when( str_detect(x, "_ragtag") ~ "RagTag", str_detect(x, "_medaka") ~ "medaka", str_detect(x, "_pilon") ~ "pilon", str_detect(x, "_longstitch") ~ "longstitch", str_detect(x, "_links") ~ "LINKS", - str_detect(x, "assembl[ey]") ~ "Assembly" + str_detect(x, "assembl[ey]") ~ "Assembly", TRUE ~ "Unknown"), Copies = as.factor(Copies), stage = as.factor(stage), @@ -614,15 +614,15 @@ merqury_qv <- list.files(paste0(data_base, "merqury"), full.names = T, pattern = col_names = c("Assembly", "kmers_assembly_unique", "kmers_assembly_shared", "QV", "error_rate"), show_col_types = FALSE) %>% mutate( - sample = str_extract(x %>% basename(), - ".+?(?=_\\.assembly|\\-assemble|links|longstitch|ragtag|medaka|pilon|\\-run|\\-polish)"), + sample = basename(x) %>% + str_extract(groups$sample[rev(order(nchar(groups$sample)))] %>% paste(collapse = "|")), stage = case_when( str_detect(x, "_ragtag") ~ "RagTag", str_detect(x, "_medaka") ~ "medaka", str_detect(x, "_pilon") ~ "pilon", str_detect(x, "_longstitch") ~ "longstitch", str_detect(x, "_links") ~ "LINKS", - str_detect(x, "assembl[ey]") ~ "Assembly" + str_detect(x, "assembl[ey]") ~ "Assembly", TRUE ~ "Unknown"), stage = as.factor(stage), sample = as.factor(sample), @@ -824,7 +824,7 @@ genomescope_out <- list.files(paste0(data_base, "genomescope"), full.names = T, ::: {.content-visible when-profile="jellyfish"} -Below are the genomescope estimates based on the provided ONT reads: +Below are the genomescope estimates based on the provided QC reads: ```{r} #| eval: !expr params$jellyfish @@ -839,8 +839,7 @@ for (file in img_files) { } img_files <- data.frame(file = list.files("genomescope_files/", full.names = T, pattern = "plot.png")) %>% - mutate(sample = str_extract(file %>% basename(), ".+?(?=_plot.png)")) %>% - left_join(groups, join_by(sample)) + mutate(group = str_extract(file %>% basename(), ".+?(?=_plot.png)")) cat(":::{.panel-tabset}\n\n") From 3e45625cbf9e3111b768b73708252c71baf66ede Mon Sep 17 00:00:00 2001 From: Niklas Schandry Date: Fri, 19 Sep 2025 15:44:12 +0200 Subject: [PATCH 061/162] add comments to main and assemble workflow --- subworkflows/local/assemble/main.nf | 94 ++++++++++++++++++++++++----- workflows/genomeassembler.nf | 63 ++++++++++++++++++- 2 files changed, 140 insertions(+), 17 deletions(-) diff --git a/subworkflows/local/assemble/main.nf b/subworkflows/local/assemble/main.nf index eef3af09..a8b0eeaa 100644 --- a/subworkflows/local/assemble/main.nf +++ b/subworkflows/local/assemble/main.nf @@ -18,7 +18,9 @@ workflow ASSEMBLE { // Empty channels Channel.empty().set { ch_versions } - + /* + Samples are split into those that need assembly, and those that will not be assembled (i.e. assemblies are provided) + */ ch_main.dump(tag: "Assemble - Inputs") ch_main .branch { @@ -29,7 +31,14 @@ workflow ASSEMBLE { .set { ch_main_branched } - + /* + There are three assembly strategies: + - Single: Using a single assembler with one type of reads + - Hybrid: Using a single assembler with both types of read in one run (only hifiasm --ul) + - Scaffold: Separately assembling ONT and HiFi reads, and then scaffolding one onto the other + + Each sample can only have one strategy, and branching happens here. + */ ch_main_branched .to_assemble .branch { it -> @@ -49,11 +58,17 @@ workflow ASSEMBLE { .scaffold .dump(tag: "Assemble: Branched: scaffold") - // Flye inputs: + /* + Inputs for flye assembler: + - Samples with single strategy, where the assembler is flye + - Samples from the scaffold strategy where either (or both) assembler is flye + */ ch_main_assemble_branched .single .filter { it -> it.assembler1 == "flye" } .mix( + // Does this actually work correctly? What happens to samples where assembler1 and assembler2 are flye? + // TODO: May need fixing, think those need to be mixed in individually so they actually are assembled twice ch_main_assemble_branched .scaffold .filter { it -> it.assembler1 == "flye" || it.assembler2 == "flye" } @@ -61,6 +76,7 @@ workflow ASSEMBLE { .set { ch_main_assemble_flye } // Assembly flye branch + // Extra args per sample are stored in the meta map, so is the estimated / expected genome size ch_main_assemble_flye .multiMap { it -> @@ -70,19 +86,27 @@ workflow ASSEMBLE { genome_size: it.genome_size, flye_args: it.flye_args ?: "" ], + // Reads are matched based on assembler + // Does this actually work correctly? What happens to samples where assembler1 and assembler2 are flye? + // TODO: May need fixing it.assembler1 == "flye" ? it.ontreads : (it.assembler2 == "flye" ? it.hifireads : []), ] - mode: it.ontreads ? "--nano-hq" : "--pacbio-hifi" + mode: it.assembler1 == "flye" ? "--nano-hq" : "--pacbio-hifi" } .set { flye_inputs } flye_inputs.reads.dump(tag: "Assemble: Flye inputs") + // Run through flye FLYE(flye_inputs.reads, flye_inputs.mode) ch_versions = ch_versions.mix(FLYE.out.versions) - // Hifiasm: everything that is not ONT + /* Hifiasm: everything that is not hifiasm-ONT + Single branch with hifiasm as assembler and no ont reads (only hifireads) + Hybrid assembly + Scaffold samples where assembler2 (hifi assembler) is hifiasm + */ ch_main_assemble_branched .single .filter { it -> it.assembler1 == "hifiasm" && !it.ontreads } @@ -101,11 +125,15 @@ workflow ASSEMBLE { ch_main_assemble_hifi_hifiasm.dump(tag: "Assemble: hifiasm HIFI inputs") + + HIFIASM(ch_main_assemble_hifi_hifiasm .map { it -> [ + // Put sample-level args into meta map [id: it.meta.id, hifiasm_args: it.hifiasm_args ?: ""], it.hifireads, + // for hybrid samples include ONT reads in 3rd slot of first input (see hifiasm module) (it.stragtegy == "hybrid" && it.ontreads) ? it.ontreads : [] ] }, @@ -113,11 +141,16 @@ workflow ASSEMBLE { [[], [], []], [[], []]) + // hifiasm produces GFA files, convert to fasta & restore meta map with id only GFA_2_FA_HIFI( HIFIASM.out.processed_unitigs.map { meta, fasta -> [[id: meta.id], fasta] } ) ch_versions = ch_versions.mix(HIFIASM.out.versions).mix(GFA_2_FA_HIFI.out.versions) - // Assemble hifiasm_ont branch + /* + Assemble hifiasm_ont branch: + Single branch with hifiasm and only ont reads + Scaffold samples where assembler1 (ont assembler) is hifiasm + */ ch_main_assemble_branched .single .filter { it -> it.assembler1 == "hifiasm" && it.ontreads } @@ -139,6 +172,7 @@ workflow ASSEMBLE { // Now, the individual assemblies need to be correctly added into the main channel. // This should be done per-strategy I think // join assembler outputs back to assembler inputs and determine correct placement of the assembly. + // Flye: ch_main_assemble_flye // Convert to list for join @@ -149,6 +183,8 @@ workflow ASSEMBLE { ) // After joining re-create the maps from the stored map .map { it -> it.collect { _entry, map -> [ (map.key): map.value ] }.collectEntries() } + // The flye_assembly has to be placed into the correct slot + // TODO: Rethink if this works for flye/flye scaffolds?! .map { it -> it - it.subMap("flye_assembly") + [ assembly: it.strategy == "single" ? it.flye_assembly : null, @@ -159,6 +195,7 @@ workflow ASSEMBLE { .set { flye_assemblies } flye_assemblies.dump(tag: "Assemble: Flye assemblies") + // Join hifiasm hifi assemblies back to main channel ch_main_assemble_hifi_hifiasm // Convert to list for join .map { it -> it.collect { entry -> [ entry.value, entry ] } } @@ -168,18 +205,23 @@ workflow ASSEMBLE { ) // After joining re-create the maps from the stored map .map { it -> it.collect { _entry, map -> [ (map.key): map.value ] }.collectEntries() } + // On the proper map, place the hifiasm assembly into the correct position .map { - it -> it -it.subMap("hifiasm_assembly") + + // remove hifiasm_assembly entry from joined result + it -> it - it.subMap("hifiasm_assembly") + + // stick what was in hifiasm_assembly into the correct key [ assembly: (it.strategy == "single" || it.strategy == "hybrid") && it.assembler1 == "hifiasm" ? it.hifiasm_assembly : null, + // I think below case dose not exist in this channel since it is only hifiasm (assembler2) assemblies? assembly1: it.strategy == "scaffold" && it.assembler1 == "hifiasm" ? it.hifiasm_assembly : null, assembly2: it.strategy == "scaffold" && it.assembler2 == "hifiasm" ? it.hifiasm_assembly : null ] } .set { hifiasm_hifi_assemblies } - hifiasm_hifi_assemblies.dump(tag: "Assemble: hifiasm HIFI assemblies") + hifiasm_hifi_assemblies.dump(tag: "Assemble: hifiasm HIFI assemblies") + // Join hifiasm ONT assemblies back to main channel ch_main_assemble_ont_hifiasm .map { it -> it.collect { entry -> [ entry.value, entry ] } } .join( GFA_2_FA_ONT.out.contigs_fasta @@ -192,6 +234,7 @@ workflow ASSEMBLE { it -> it -it.subMap("hifiasm_assembly") + [ assembly: (it.strategy == "single" || it.strategy == "hybrid") && it.assembler1 == "hifiasm" ? it.hifiasm_assembly : null, + // I think below case dose not exist in this channel since it is only ont (assembler1) assemblies? assembly1: it.strategy == "scaffold" && it.assembler1 == "hifiasm" ? it.hifiasm_assembly : null, assembly2: it.strategy == "scaffold" && it.assembler2 == "hifiasm" ? it.hifiasm_assembly : null ] @@ -199,6 +242,7 @@ workflow ASSEMBLE { .set { hifiasm_ont_assemblies } hifiasm_ont_assemblies.dump(tag: "Assemble: hifiasm HIFI assemblies") + // The single and hybrid channels can be mixed and forwarded. // The scaffold channel needs to be joined separately. flye_assemblies @@ -214,8 +258,10 @@ workflow ASSEMBLE { .set { ch_assemblies_no_scaffold } ch_assemblies_no_scaffold.dump(tag: "Assemble: Assemblies without scaffolding") + // This leaves the scaffold strategy. // scaffolds can be: FLYE-HIFIASM, FLYE-FLYE, HIFIASM-HIFIASM HIFIASM-FLYE or + flye_assemblies // Flye-hifiasm .filter { it -> it.strategy == "scaffold" && it.assembler1 == "flye" && it.assembler2 == "hifiasm" } @@ -228,6 +274,7 @@ workflow ASSEMBLE { .map { it -> it.collect { _entry, map -> [ (map.key): map.value ] }.collectEntries() } .map { it -> it - it.subMap("hifiasm_assembly","assembly2") + [assembly2: it.hifiasm_assembly] } .set{ scaffold_flye_hifiasm } + // flye-flye flye_assemblies .filter { it -> it.strategy == "scaffold" && it.assembler1 == "flye" && it.assembler2 == "flye" } @@ -238,8 +285,9 @@ workflow ASSEMBLE { .map { it -> it.collect { entry -> [ entry.value, entry ] } } ) .map { it -> it.collect { _entry, map -> [ (map.key): map.value ] }.collectEntries() } - .map { it -> it - it.subMap("flye_assembly","assembly2") + [assembly2: it.flye_assembly] } + .map { it -> it - it.subMap("flye_assembly", "assembly2") + [assembly2: it.flye_assembly] } .set{ scaffold_flye_flye } + // hifiasm_flye hifiasm_ont_assemblies .filter { it -> it.strategy == "scaffold" && it.assembler1 == "hifiasm" && it.assembler2 == "flye" } @@ -250,8 +298,9 @@ workflow ASSEMBLE { .map { it -> it.collect { entry -> [ entry.value, entry ] } } ) .map { it -> it.collect { _entry, map -> [ (map.key): map.value ] }.collectEntries() } - .map { it -> it - it.subMap("flye_assembly","assembly2") + [assembly2: it.flye_assembly] } + .map { it -> it - it.subMap("flye_assembly", "assembly2") + [assembly2: it.flye_assembly] } .set{ scaffold_hifiasm_flye } + // hifiasm_hifiasm hifiasm_ont_assemblies .filter { it -> it.strategy == "scaffold" && it.assembler1 == "hifiasm" && it.assembler2 == "hifiasm" } @@ -264,7 +313,9 @@ workflow ASSEMBLE { .map { it -> it.collect { _entry, map -> [ (map.key): map.value ] }.collectEntries() } .map { it -> it - it.subMap("hifiasm_assembly","assembly2") + [assembly2: it.hifiasm_assembly] } .set{ scaffold_hifiasm_hifiasm } + // branch to scaffold those assemblies that need it + scaffold_flye_hifiasm .mix(scaffold_flye_flye) .mix(scaffold_hifiasm_flye) @@ -273,6 +324,9 @@ workflow ASSEMBLE { ch_to_scaffold.dump(tag: "Assemble: Assemblies with scaffolding - inputs") + // For scaffolding, depeding on which strategy we used, the correct assembly needs to go into either target or query: + // assembly1 is always ONT, assembly2 is always HiFi + ch_to_scaffold .multiMap { it -> @@ -287,7 +341,10 @@ workflow ASSEMBLE { } .set { ragtag_in } + // Scaffold with PATCH RAGTAG_PATCH(ragtag_in.target, ragtag_in.query, [[], []], [[], []] ) + + // Update inputs ch_to_scaffold .map { it -> it - it.subMap("assembly") } .map { it -> it.collect { entry -> [ entry.value, entry ] } } @@ -301,7 +358,7 @@ workflow ASSEMBLE { ch_assemblies_scaffold.dump(tag: "Assemble: Assemblies with scaffolding - outputs") - + // Mix everything assembled back togehter ch_assemblies_no_scaffold .mix(ch_assemblies_scaffold) .set { ch_main_assembled } @@ -310,6 +367,7 @@ workflow ASSEMBLE { ch_versions = ch_versions.mix(RAGTAG_PATCH.out.versions) + // Mix with whatever was not destined for assembly ch_main_branched .no_assemble .mix( ch_main_assembled ) @@ -317,6 +375,7 @@ workflow ASSEMBLE { ch_main_to_mapping.dump(tag: "Assemble: TO MAPPING") + ch_main_to_mapping .branch { it -> @@ -326,6 +385,7 @@ workflow ASSEMBLE { // Note that this channel is set here but the quast branch is further used .set { ch_main_quast_branch } + // If QUAST should run, and we need an alignment to reference, this is created here ch_main_quast_branch .quast .branch { @@ -336,7 +396,7 @@ workflow ASSEMBLE { .set { ch_quast_branched } - + // It is actually only created if no bam file is provided ch_quast_branched .use_ref .branch { it -> @@ -345,6 +405,7 @@ workflow ASSEMBLE { } .set { ch_ref_mapping_branched } + // Use the QC reads and map them to ref ch_ref_mapping_branched .to_map .map { @@ -353,14 +414,17 @@ workflow ASSEMBLE { } .set { map_to_ref_in } - MAP_TO_REF(map_to_ref_in) // returns meta: [id] + MAP_TO_REF(map_to_ref_in) // returns meta: [ id: ] + // Add the ref mapping to the large main channel ch_ref_mapping_branched .to_map .map { it -> it - it.subMap("ref_map_bam") } .map { it -> it.collect { entry -> [ entry.value, entry ] } } .join( MAP_TO_REF.out.ch_aln_to_ref_bam + // Note that this is a normal list channel and needs to become a map before conversion back to list and joining + // Otherwise the map cannot be regenerated later .map { it -> [meta: it[0], ref_map_bam: it[1]] } .map { it -> it.collect { entry -> [ entry.value, entry ] } } ) @@ -374,8 +438,7 @@ workflow ASSEMBLE { //QC on initial assembly - - // scaffolds to QC need to be defined here + // scaffolds to QC need to be defined here, this is what is in the assembly slot ch_main_to_qc .map { it -> [it.meta, it.assembly] } .set { scaffolds } @@ -384,6 +447,7 @@ workflow ASSEMBLE { ch_versions = ch_versions.mix(QC.out.versions) + // If annotation liftover on the initial assembly is desired, it happens here. ch_main_to_qc .filter { it -> it.lift_annotations diff --git a/workflows/genomeassembler.nf b/workflows/genomeassembler.nf index 8196d34a..6f97f49c 100644 --- a/workflows/genomeassembler.nf +++ b/workflows/genomeassembler.nf @@ -95,9 +95,68 @@ workflow GENOMEASSEMBLER { paired: bool, use_short_reads: bool, shortread_trim: bool - */ - // TODO: Currently the pipeline is losing everything with hifireads somewhere. + + + =========== + JOINS + =========== + + Since this channel needs to stay a map so I can pull out the correct elements, joining is difficult: + Nextflow's join operator only works on list-typed channels, but the channels here are maps. + For this reason, there are some confuding map operations involved where each map-element is converted to a list, + containing the value and the previous map. The whole channel is turned into a list this way: + + Something like + + [ + meta: [id: something1], + somepath: "/path" + ] + + becomes + + [ + [id: something, meta: [id: something]], + ["path", somepath: "path"] + ] + + This can be joined to + + [ + [id: something, meta: [id: something]], + ["different_path", otherpath: "path"] + ] + + This makes it possible to join on the first element (the one containing meta): + + [ + [id: something, meta: [id: something]], + ["path", somepath: "path"], + ["different_path", otherpath: "different_path"] + ] + + After joining, the map is recreated from the second list element, to create: + + [ + meta: [id: something], + somepath: "path", + otherpath: "different_path" + ] + + This is sadly a somewhat frequent pattern in this pipeline and + it is done like this: + + map_channel_1 + // Convert to list for join + .map { it -> it.collect { entry -> [ entry.value, entry ] } } + .join( map_channel_2 + // Convert to list for join + .map { it -> it.collect { entry -> [ entry.value, entry ] } } + ) + // After joining re-create the maps from the stored map + .map { it -> it.collect { _entry, map -> [ (map.key): map.value ] }.collectEntries() } + */ Channel.empty().set { meryl_kmers } From f89fe8f2a81c172d884f4dedc282c314adabf72c Mon Sep 17 00:00:00 2001 From: Niklas Schandry Date: Wed, 1 Oct 2025 11:21:18 +0200 Subject: [PATCH 062/162] fix flye-flye scaffolding code --- subworkflows/local/assemble/main.nf | 72 ++++++++++++++++++++--------- 1 file changed, 51 insertions(+), 21 deletions(-) diff --git a/subworkflows/local/assemble/main.nf b/subworkflows/local/assemble/main.nf index a8b0eeaa..1d65a671 100644 --- a/subworkflows/local/assemble/main.nf +++ b/subworkflows/local/assemble/main.nf @@ -1,4 +1,5 @@ -include { FLYE } from '../../../modules/nf-core/flye/main' +include { FLYE as FLYE_ONT} from '../../../modules/nf-core/flye/main' +include { FLYE as FLYE_HIFI} from '../../../modules/nf-core/flye/main' include { HIFIASM } from '../../../modules/nf-core/hifiasm/main' include { HIFIASM as HIFIASM_ONT } from '../../../modules/nf-core/hifiasm/main' include { GFA_2_FA as GFA_2_FA_HIFI } from '../../../modules/local/gfa2fa/main' @@ -67,17 +68,42 @@ workflow ASSEMBLE { .single .filter { it -> it.assembler1 == "flye" } .mix( - // Does this actually work correctly? What happens to samples where assembler1 and assembler2 are flye? - // TODO: May need fixing, think those need to be mixed in individually so they actually are assembled twice + // Add in the scaffolding samples where flye is used for ONT ch_main_assemble_branched .scaffold - .filter { it -> it.assembler1 == "flye" || it.assembler2 == "flye" } + .filter { it -> it.assembler1 == "flye" } + // Add in the scaffolding samples where is used for HiFi + .mix( + ch_main_assemble_branched + .scaffold + .filter { it -> it.assembler2 == "flye" } + ) ) .set { ch_main_assemble_flye } // Assembly flye branch // Extra args per sample are stored in the meta map, so is the estimated / expected genome size + // The inputs are created once for ONT and once for HiFi + ch_main_assemble_flye + .filter { it -> it.assembler1 == "flye" && it.ontreads } + .multiMap { + it -> + reads: [ + [ + id: it.meta.id, + genome_size: it.genome_size, + flye_args: it.flye_args ?: "" + ], + // Reads are matched based on assembler + it.assembler1 == "flye" ? it.ontreads : null, + ] + mode: it.assembler1 == "flye" ? "--nano-hq" : null + } + .set { flye_ont_inputs } + // These are the hifi samples ch_main_assemble_flye + // Those where the hifi assembler is flye, or where there is only one assembler and hifireads + .filter { it -> it.assembler2 == "flye" || (it.strategy == "single" && it.assembler1 == "flye" && it.hifireads)} .multiMap { it -> reads: [ @@ -87,20 +113,21 @@ workflow ASSEMBLE { flye_args: it.flye_args ?: "" ], // Reads are matched based on assembler - // Does this actually work correctly? What happens to samples where assembler1 and assembler2 are flye? - // TODO: May need fixing - it.assembler1 == "flye" ? it.ontreads : (it.assembler2 == "flye" ? it.hifireads : []), + it.assembler2 == "flye" ? it.hifireads : null, ] - mode: it.assembler1 == "flye" ? "--nano-hq" : "--pacbio-hifi" + mode: it.assembler2 == "flye" ? "--pacbio-hifi" : null } - .set { flye_inputs } + .set { flye_hifi_inputs } - flye_inputs.reads.dump(tag: "Assemble: Flye inputs") + flye_ont_inputs.reads.dump(tag: "Assemble: Flye-ONT inputs") + flye_hifi_inputs.reads.dump(tag: "Assemble: Flye-HIFI inputs") // Run through flye - FLYE(flye_inputs.reads, flye_inputs.mode) + FLYE_ONT(flye_ont_inputs.reads, flye_ont_inputs.mode) + FLYE_HIFI(flye_hifi_inputs.reads, flye_hifi_inputs.mode) + - ch_versions = ch_versions.mix(FLYE.out.versions) + ch_versions = ch_versions.mix(FLYE_ONT.out.versions).mix(FLYE_HIFI.out.versions) /* Hifiasm: everything that is not hifiasm-ONT Single branch with hifiasm as assembler and no ont reads (only hifireads) @@ -177,19 +204,22 @@ workflow ASSEMBLE { ch_main_assemble_flye // Convert to list for join .map { it -> it.collect { entry -> [ entry.value, entry ] } } - .join( FLYE.out.fasta - .map { meta, assembly -> [meta: [id: meta.id], flye_assembly: assembly ] } + .join( FLYE_ONT.out.fasta + .map { meta, assembly -> [meta: [id: meta.id], flye_ont_assembly: assembly ] } + .map { it -> it.collect { entry -> [ entry.value, entry ] } } + ) + .join( FLYE_HIFI.out.fasta + .map { meta, assembly -> [meta: [id: meta.id], flye_hifi_assembly: assembly ] } .map { it -> it.collect { entry -> [ entry.value, entry ] } } ) // After joining re-create the maps from the stored map .map { it -> it.collect { _entry, map -> [ (map.key): map.value ] }.collectEntries() } - // The flye_assembly has to be placed into the correct slot - // TODO: Rethink if this works for flye/flye scaffolds?! - .map { it -> it - it.subMap("flye_assembly") + + // The flye_ont|hifi_assembly has to be placed into the correct slot + .map { it -> it - it.subMap("flye_ont_assembly") - it.subMap("flye_hifi_assembly") + [ - assembly: it.strategy == "single" ? it.flye_assembly : null, - assembly1: it.assembler1 == "flye" ? it.flye_assembly : null, - assembly2: it.assembler2 == "flye" ? it.flye_assembly : null, + assembly: it.strategy == "single" && it.ontreads ? it.flye_ont_assembly : it.flye_hifi_assembly, + assembly1: it.assembler1 == "flye" ? it.flye_ont_assembly : null, + assembly2: it.assembler2 == "flye" ? it.flye_hifi_assembly : null, ] } .set { flye_assemblies } @@ -396,7 +426,7 @@ workflow ASSEMBLE { .set { ch_quast_branched } - // It is actually only created if no bam file is provided + // Alignment is actually only created if no bam file is provided ch_quast_branched .use_ref .branch { it -> From 16f2f222d59cfb100b16ff9cdb88b5b807dc9272 Mon Sep 17 00:00:00 2001 From: Niklas Schandry Date: Thu, 23 Oct 2025 10:12:36 +0200 Subject: [PATCH 063/162] update report format, refactor document, document format --- assets/report/_brand.yml | 38 + assets/report/functions/plot_merqury.R | 18 +- assets/report/functions/read_busco.R | 28 +- assets/report/functions/read_fastplong.R | 4 +- assets/report/functions/read_genomescope.R | 81 +- assets/report/functions/read_nanoq.R | 47 +- assets/report/functions/read_quast.R | 38 +- assets/report/nf-core-logo-square.png | Bin 0 -> 24893 bytes assets/report/nf-core-logo.png | Bin 0 -> 84414 bytes assets/report/report.qmd | 830 ++++----------------- assets/report/scripts/_busco.R | 43 ++ assets/report/scripts/_busco_page.R | 33 + assets/report/scripts/_fastplong_page.R | 41 + assets/report/scripts/_genomescope_page.R | 21 + assets/report/scripts/_merqury.R | 149 ++++ assets/report/scripts/_merqury_page.R | 41 + assets/report/scripts/_quast.R | 156 ++++ assets/report/scripts/_quast_page.R | 131 ++++ 18 files changed, 901 insertions(+), 798 deletions(-) create mode 100644 assets/report/_brand.yml create mode 100644 assets/report/nf-core-logo-square.png create mode 100644 assets/report/nf-core-logo.png create mode 100644 assets/report/scripts/_busco.R create mode 100644 assets/report/scripts/_busco_page.R create mode 100644 assets/report/scripts/_fastplong_page.R create mode 100644 assets/report/scripts/_genomescope_page.R create mode 100644 assets/report/scripts/_merqury.R create mode 100644 assets/report/scripts/_merqury_page.R create mode 100644 assets/report/scripts/_quast.R create mode 100644 assets/report/scripts/_quast_page.R diff --git a/assets/report/_brand.yml b/assets/report/_brand.yml new file mode 100644 index 00000000..40b25dac --- /dev/null +++ b/assets/report/_brand.yml @@ -0,0 +1,38 @@ +color: + palette: + green: "#24B064" + brown: "#3F2B29" + yellow: "#ECDC86" + white: "#f9f9f9" + gray-dark: "#343a40" + gray-100: "#f8f9fa" + gray-200: "#e9ecef" + gray-300: "#dee2e6" + gray-400: "#ced4da" + gray-500: "#adb5bd" + gray-600: "#6c757d" + gray-700: "#495057" + gray-800: "#343a40" + gray-900: "#212529" + background: white + foreground: gray-dark + primary: green + secondary: yellow + tertiary: brown + light: "#f8f9fa" + dark: "#212529" + +logo: + images: + banner: "nf-core-logo.png" + apple: "nf-core-logo-square.png" + small: apple + medium: banner + large: banner + +typography: + fonts: + - family: Maven Pro + source: google + base: Maven Pro + headings: Maven Pro diff --git a/assets/report/functions/plot_merqury.R b/assets/report/functions/plot_merqury.R index e57607e1..d212db90 100644 --- a/assets/report/functions/plot_merqury.R +++ b/assets/report/functions/plot_merqury.R @@ -10,7 +10,9 @@ plot_merqury_stats <- function(data, groupname) { color = "k-mers copy number", fill = "k-mers copy number", title = glue::glue("k-mer completeness {groupname} assemblies") - ) + ) + + fill_scale_plots + + color_scale_plots } plot_merqury_multiplicity <- function(data, groupname) { @@ -19,16 +21,15 @@ plot_merqury_multiplicity <- function(data, groupname) { max(Count) x_max <- data %>% filter(group == paste(groupname), Assembly != "read-only") %$% - quantile(Count, .95) + quantile(Count, .9) data %>% filter(group == paste(groupname)) %>% mutate(Assembly = case_when(Assembly == "read-only" ~ "Reads", TRUE ~ "Assembly")) %>% ggplot(aes(x = kmer_multiplicity, y = Count)) + geom_line(aes(color = Assembly)) + - #geom_area(aes(fill = Assembly), alpha = 0.15,stat = "identity") + facet_grid(sample ~ stage) + coord_cartesian( - xlim = c(0, x_max * 1.1), + xlim = c(0, x_max * 1.05), ylim = c(0, y_max * 1.05), expand = TRUE, default = FALSE, @@ -52,15 +53,14 @@ plot_merqury_copynumber <- function(data, groupname) { max(Count) x_max <- data %>% filter(group == paste(groupname), Copies != "read-only") %$% - quantile(Count, .965) + quantile(Count, .9) data %>% filter(group == paste(groupname)) %>% ggplot(aes(x = kmer_multiplicity, y = Count)) + geom_line(aes(color = Copies)) + - #geom_area(aes(fill = Copies), alpha = 0.15, stat = "identity") + facet_grid(sample ~ stage) + coord_cartesian( - xlim = c(0, x_max * 1.1), + xlim = c(0, x_max * 1.05), ylim = c(0, y_max * 1.05), expand = TRUE, default = FALSE, @@ -88,5 +88,7 @@ plot_merqury_qv <- function(data, groupname) { color = "black", size = 5 ) + - labs(y = "QV", x = "Stage", title = "QV across assembly stages") + labs(y = "QV", x = "Stage", title = "QV across assembly stages") + + fill_scale_plots + + color_scale_plots } diff --git a/assets/report/functions/read_busco.R b/assets/report/functions/read_busco.R index fc5a3ae3..a214604c 100644 --- a/assets/report/functions/read_busco.R +++ b/assets/report/functions/read_busco.R @@ -2,8 +2,8 @@ read_busco_report <- function(file) { assembly <- read_lines(file, - skip = 2L, - n_max = 1L) %>% + skip = 2L, + n_max = 1L) %>% str_extract('(?<=input_seqs/).+?(?=\\.fa)') bind_rows( read_tsv( @@ -64,18 +64,18 @@ read_busco_report <- function(file) { read_busco_batch <- \(x) {read_tsv(x, show_col_types = F) %>% set_colnames(colnames(.) %>% str_replace_all(" ", "_")) %>% mutate( - # Get sample name by matching filename to samples in groups, reverse sort by length to hopefully catch - # the correct name first in case there is partial overlap between sample names. - sample = basename(x) %>% + # Get sample name by matching filename to samples in groups, reverse sort by length to hopefully catch + # the correct name first in case there is partial overlap between sample names. + sample = basename(x) %>% str_extract(groups$sample[rev(order(nchar(groups$sample)))] %>% paste(collapse = "|")),, - stage = case_when( - str_detect(x, "ragtag") ~ "RagTag", - str_detect(x, "medaka") ~ "medaka", - str_detect(x, "pilon") ~ "pilon", - str_detect(x, "longstitch") ~ "longstitch", - str_detect(x, "links") ~ "LINKS", - str_detect(x, "assembl[ey]") ~ "Assembly", - ), - Percent_gaps = Percent_gaps %>% str_remove("%") %>% as.numeric) %>% + stage = case_when( + str_detect(x, "ragtag") ~ "RagTag", + str_detect(x, "medaka") ~ "medaka", + str_detect(x, "pilon") ~ "pilon", + str_detect(x, "longstitch") ~ "longstitch", + str_detect(x, "links") ~ "LINKS", + str_detect(x, "assembl[ey]") ~ "Assembly", + ), + Percent_gaps = Percent_gaps %>% str_remove("%") %>% as.numeric) %>% dplyr::select(-Dataset) %>% pivot_longer(Complete:Number_of_scaffolds, names_to = "Var")} diff --git a/assets/report/functions/read_fastplong.R b/assets/report/functions/read_fastplong.R index 1ad50a42..3979a32b 100644 --- a/assets/report/functions/read_fastplong.R +++ b/assets/report/functions/read_fastplong.R @@ -9,7 +9,9 @@ read_fastplong <- function(file) { read_type <- file %>% str_extract('(?<=fastplong/).*') %>% str_extract('_(ont|hifi)\\.') %>% - str_remove_all("_|\\.") + str_remove_all("_|\\.") |> + str_replace("ont", "ONT") |> + str_replace("hifi", "PB HiFi") read_json(file) %>% enter_object("summary") %>% tidyjson::spread_all() %>% diff --git a/assets/report/functions/read_genomescope.R b/assets/report/functions/read_genomescope.R index 40745392..babcb9e4 100644 --- a/assets/report/functions/read_genomescope.R +++ b/assets/report/functions/read_genomescope.R @@ -1,43 +1,44 @@ +## WIP read_genomescope <- function(path) { - rbind( - read_table(path, skip = 3, n_max = 1) %>% dplyr::select(-X4), - read_table( - path, - skip = 5, - n_max = 3, - col_names = c( - "property", - "property2", - "property3", - "min", - "bp", - "max", - "bp2" - ) - ) %>% - dplyr::select(-starts_with("bp")) %>% - mutate(property = glue::glue("{property} {property2} {property3}")) %>% - dplyr::select(-property2, -property3), - read_table( - path, - skip = 8, - n_max = 1, - c("property", "property2", "min", "max") - ) %>% - mutate(property = glue::glue("{property} {property2}")) %>% - dplyr::select(-property2), - read_table( - path, - skip = 9, - n_max = 1, - c("property", "property2", "property3", "min", "max") - ) %>% - mutate(property = glue::glue("{property} {property2} {property3}")) %>% - dplyr::select(-property2, -property3) + rbind( + read_table(path, skip = 3, n_max = 1) %>% dplyr::select(-X4), + read_table( + path, + skip = 5, + n_max = 3, + col_names = c( + "property", + "property2", + "property3", + "min", + "bp", + "max", + "bp2" + ) ) %>% - mutate( - min = str_extract(min, "[0-9\\.]+") %>% as.numeric(), - max = str_extract(max, "[0-9\\.]+") %>% as.numeric() - ) %>% - mutate(sample = str_extract(path %>% basename(), ".+?(?=_genomescope)")) + dplyr::select(-starts_with("bp")) %>% + mutate(property = glue::glue("{property} {property2} {property3}")) %>% + dplyr::select(-property2, -property3), + read_table( + path, + skip = 8, + n_max = 1, + c("property", "property2", "min", "max") + ) %>% + mutate(property = glue::glue("{property} {property2}")) %>% + dplyr::select(-property2), + read_table( + path, + skip = 9, + n_max = 1, + c("property", "property2", "property3", "min", "max") + ) %>% + mutate(property = glue::glue("{property} {property2} {property3}")) %>% + dplyr::select(-property2, -property3) + ) %>% + mutate( + min = str_extract(min, "[0-9\\.]+") %>% as.numeric(), + max = str_extract(max, "[0-9\\.]+") %>% as.numeric() + ) %>% + mutate(sample = str_extract(path %>% basename(), ".+?(?=_genomescope)")) } diff --git a/assets/report/functions/read_nanoq.R b/assets/report/functions/read_nanoq.R index 3dcee647..e8e9dfd7 100644 --- a/assets/report/functions/read_nanoq.R +++ b/assets/report/functions/read_nanoq.R @@ -1,28 +1,29 @@ +library(magrittr) library(tidyjson) +library(dplyr) +library(readr) # Read a nanoq json report read_nanoq <- function(file) { - bind_rows( - read_json(file) %>% - tidyjson::spread_all() %>% - as_tibble() %>% - select(-document.id) %>% - pivot_longer(everything(), values_to = "val", names_to = "stat"), - read_json(file) %>% - enter_object(top_lengths) %>% - gather_array() %>% - unnest(cols = c(..JSON)) %>% - mutate(stat = "longest") %>% - dplyr::select(4, val = 3) - ) %>% - mutate( - sample = str_extract(file, "(?<=nanoq/).+?(?=_report)"), - stage = "Reads" - ) %>% - mutate( - stat = stat %>% - str_replace_all("_", " ") %>% - str_to_title() %>% - str_replace("Reads", "N Reads") - ) + bind_rows( + read_json(file) %>% + tidyjson::spread_all() %>% + as_tibble() %>% + select(-document.id) %>% + pivot_longer(everything(), values_to = "val", names_to = "stat"), + read_json(file) %>% + enter_object(top_lengths) %>% + gather_array() %>% + unnest(cols = c(..JSON)) %>% + mutate(stat = "longest") %>% + dplyr::select(4, val = 3) + ) %>% + mutate(sample = str_extract(file, '(?<=nanoq/).+?(?=_report)'), + stage = "Reads") %>% + mutate( + stat = stat %>% + str_replace_all("_", " ") %>% + str_to_title() %>% + str_replace("Reads", "N Reads") + ) } diff --git a/assets/report/functions/read_quast.R b/assets/report/functions/read_quast.R index a6cdc24e..13d87397 100644 --- a/assets/report/functions/read_quast.R +++ b/assets/report/functions/read_quast.R @@ -1,21 +1,21 @@ read_quast_report <- function(file) { - assembly <- read_tsv( - file, - skip = 0L, - col_names = c("stat", "value"), - trim_ws = T, - n_max = 1L, - show_col_types = FALSE - ) %$% - value - read_tsv( - file, - skip = 1L, - col_names = c("stat", "value"), - col_types = "cd", - trim_ws = T, - n_max = 36L, - show_col_types = FALSE - ) %>% - mutate(assembly = assembly) + assembly = read_tsv( + file, + skip = 0L, + col_names = c("stat", "value"), + trim_ws = T, + n_max = 1L, + show_col_types = FALSE + ) %$% + value + read_tsv( + file, + skip = 1L, + col_names = c("stat", "value"), + col_types = "cd", + trim_ws = T, + n_max = 36L, + show_col_types = FALSE + ) %>% + mutate(assembly = assembly) } diff --git a/assets/report/nf-core-logo-square.png b/assets/report/nf-core-logo-square.png new file mode 100644 index 0000000000000000000000000000000000000000..1731363780f990a3d4f85acde6468b8570f2957f GIT binary patch literal 24893 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 QhDJ`FFf%ODC;rd>0vMg=hX4Qo literal 0 HcmV?d00001 diff --git a/assets/report/nf-core-logo.png b/assets/report/nf-core-logo.png new file mode 100644 index 0000000000000000000000000000000000000000..91ddb58d8ada2682b8edb9528c09479f248e3010 GIT binary patch literal 84414 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 +list.files("functions", + full.names = T, + pattern = ".R") |> map(\(x) source(x)) # Set default ggplot theme theme_set(theme_bw(base_size = 14, - base_family = "Arial")) + base_family = gt::google_font("Maven Pro"))) theme_update(strip.background = element_blank(), - axis.text.x = element_text(angle = 70, hjust = 1)) + axis.text.x = element_text(angle = 70, hjust = 1)) ## Colors, these come from the khroma package ("muted") ### For <=9 stages: -color_scale_plots <- scale_color_manual(values = c("#CC6677", "#332288", "#DDCC77", "#117733", "#88CCEE", "#882255", "#44AA99", "#999933", "#AA4499"), na.value = "#DDDDDD") -fill_scale_plots <- scale_fill_manual(values = c("#CC6677", "#332288", "#DDCC77", "#117733", "#88CCEE", "#882255", "#44AA99", "#999933", "#AA4499"), na.value = "#DDDDDD") +colors_9 <- c( + "#CC6677", + "#332288", + "#DDCC77", + "#117733", + "#88CCEE", + "#882255", + "#44AA99", + "#999933", + "#AA4499" +) +color_scale_plots <- scale_color_manual(values = colors_9, na.value = "#DDDDDD") +fill_scale_plots <- scale_fill_manual(values = colors_9, na.value = "#DDDDDD") + + # Base directory containing reports data_base <- "data/" -groups <- yaml::read_yaml("groups.yml") %>% - map_dfr(\(row) - data.frame( - sample = pluck(row, 1, "id"), - group = pluck(row, 1, "group", .default = "null") - ) - ) %>% - mutate(group = case_when(group == "null" ~ sample, TRUE ~ group)) -``` +# groups +groups <- yaml::read_yaml("groups.yml") |> + map_dfr(\(row) + data.frame(sample = pluck(row, 1, "id"), group = pluck(row, 1, "group", .default = "null"))) |> + mutate(group = case_when(group %in% c("","null") ~ sample, TRUE ~ group)) +``` # About This report displays the main information gathered from various QC steps. -# fastplong {.tabset} +These may include: + + - Read Quality control: + - [fastplong](https://github.com/OpenGene/fastplong): QC, adaptor removal, barcode removal and trimming for long reads + - [fastp](https://github.com/OpenGene/fastp) QC, adaptor removal, barcode removal and trimming for short reads + - Assembly Quality control for each stage + - [QUAST (QUality ASsessment Tool)](https://github.com/ablab/quast): Computes various statistics of assemblies + - [BUSCO](https://busco.ezlab.org/): BUSCO provides a quantitative assessment of the completeness in terms of expected gene content of a genome assembly, transcriptome, or annotated gene set. + - [merqury](https://github.com/marbl/merqury): k-mer spectrum based analysis of genomes, completeness, quality and ploidy + - [genomescope](https://github.com/schatzlab/genomescope): genome analysis from unassembled reads + +# Reads + +## fastplong {.tabset} ::: {.content-visible unless-profile="fastplong"} -fastplong was not included in the pipeline run. +[fastplong](https://github.com/OpenGene/fastplong) was not included in the pipeline run. ::: ```{r fastplong read inputs} @@ -69,469 +102,126 @@ fastplong was not included in the pipeline run. # Note that for these reports the sample name is the group fastplong_reports <- list.files(paste0(data_base, "fastplong"), pattern = ".json", - full.names = T) %>% - map_dfr(\(x) read_fastplong(x)) %>% + full.names = T) |> + map_dfr(\(x) read_fastplong(x)) |> left_join(groups, by = join_by(group)) ``` -```{r} -#| eval: !expr params$fastplong -#| include: false +:::: {.content-visible when-profile="fastplong" } -# For each sample, we create one plot chunk that will be saved into fastplong files -# This is an rmd chunk in plain text. - -dir.create("fastplong_files") -for (i in 1:length(unique(fastplong_reports$group))) { -paste0('```{r}\n - #| title: "fastplong read statistics" - p <- fastplong_reports %>% - filter(group == "', unique(fastplong_reports$group)[i], '") %>% - ggplot(aes(x = sample, y = value)) + - geom_line() + - geom_point(size = 5, pch=21, aes(fill=stage)) + - facet_wrap(read_type~stat, scales = "free_y", ncol=2) + - fill_scale_plots + - scale_y_continuous(labels = function(x) format(x,scientific=-1,trim=T, digits = 3, drop0trailing=T), n.breaks = 4) + - theme(axis.title.x = element_blank(), - axis.title.y = element_blank(), - legend.position = "none", - legend.title = element_blank(), - panel.grid.minor = element_blank()) - ggplotly(p)\n```') %>% - write_lines(glue::glue("fastplong_files/_{ unique(fastplong_reports$sample)[i] }_fastplong.Rmd")) -} -``` - -::: {.content-visible when-profile="fastplong"} +[fastplong](https://github.com/OpenGene/fastplong) is a tool for trimming and quality control of long reads. If groups were provided in the sample sheet, the results are reported for each group, otherwise for each sample. -::: {.panel-tabset .flow} +::: {.panel-tabset} ```{r fastplong add subplots} #| eval: !expr params$fastplong #| results: asis -# This loop creates one tab per group -## Each tab contains 3 valueboxes -## Below the valueboxes, the sample-specific plot code generated above is inserted - -for (i in 1:length(unique(fastplong_reports$group))) { - cat(paste0('## ', unique(fastplong_reports$group)[i], '\n\n'), - paste0('### { width = 30% }', '\n\n'), - paste0('::: {.valuebox icon="magic" color="primary" title="Total bases sequenced"}','\n'), - paste0(fastplong_reports %>% - filter(stat == "Total Bases", stage == "After Filtering") %>% - filter(sample == unique(fastplong_reports$group)[i]) %$% - sum(value) %>% - format(scientific=-1,trim=T, digits = 3, drop0trailing=T),'\n'), - paste0(':::', '\n\n'), - paste0('::: {.valuebox icon="collection" color="secondary" title="Number of reads"}', '\n'), - paste0(fastplong_reports %>% - filter(stat == "Total Reads", stage == "After Filtering") %>% - filter(sample == unique(fastplong_reports$group)[i]) %$% - sum(value) %>% - paste(" bases"), '\n'), - paste0(':::', '\n\n'), - paste0('::: {.valuebox icon="chevron-double-up" color="success" title="Q30 rate"}', '\n'), - paste0(fastplong_reports %>% - filter(stat == "Q30 Rate", stage == "After Filtering") %>% - filter(sample == unique(fastplong_reports$group)[i]) %$% - {value*100} %>% - round(1) %>% - paste0(" %"), - '\n'), - paste0(':::', '\n\n'), - paste0('### ', '\n\n'), - knitr::knit_child(glue::glue('fastplong_files/_{ unique(fastplong_reports$sample)[i] }_fastplong.Rmd'), - envir = globalenv(), - quiet = TRUE), - paste0('\n\n'), - sep = "" - ) -} +source("scripts/_fastplong_page.R") ``` +::: + +:::: + + ```{r} #| eval: !expr params$fastplong +#| include: false # Clean up the intermediate files unlink("fastplong_files", recursive = T) ``` -::: - -::: - +# Assemblies -# QUAST {.tabset} +## QUAST {.tabset} ::: {.content-visible unless-profile="quast"} QUAST was not included in the pipeline run. ::: -::: {.content-visible when-profile="quast"} -QUAST reports assembly statistics, taking into account the reference, if provided. +::: {.content-visible when-profile="quast" .panel-tabset .flow} -```{r message = F} -#| eval: !expr params$quast -# This chunk parses the quast reports from data/quast -quast_stats <- list.files(paste0(data_base, "quast"), - pattern = "report.tsv", - full.names = T) %>% - map_dfr(\(x) { - read_quast_report(x) %>% - mutate( - # Get sample name by matching filename to samples in groups, reverse sort by length to hopefully catch - # the correct name first in case there is partial overlap between sample names. - sample = basename(x) %>% - str_extract(groups$sample[rev(order(nchar(groups$sample)))] %>% paste(collapse = "|")), - stage = case_when( - str_detect(x, "_ragtag") ~ "RagTag", - str_detect(x, "_medaka") ~ "medaka", - str_detect(x, "_pilon") ~ "pilon", - str_detect(x, "_longstitch") ~ "longstitch", - str_detect(x, "_links") ~ "LINKS", - str_detect(x, "assembl[ey]") ~ "Assembly", - TRUE ~ "Unknown")) - }) %>% - left_join(groups, by = join_by(sample)) -``` +QUAST reports assembly statistics, taking into account the reference, if provided. -```{r quast write length plots} +```{r prepare quast, message = F} #| eval: !expr params$quast #| include: false -# This creates code that will generate the length plot based on the contents of the quast report. -dir.create("quast_files") -dir.create("quast_files/length") -for (i in 1:length(unique(quast_stats$group))) { -paste0('```{r}\n - p <- quast_stats %>% - filter(group == "', unique(quast_stats$group)[i], '") %>% - filter(str_detect(stat, "[L].*[59]0")) %>% - mutate(stat = fct_relevel(stat, "L50","L90","LG50","LG90")) %>% - ggplot(aes(x=stat, y=value)) + - geom_point(aes(fill = stage), - size = 5, - pch = 21, - alpha = 0.8, - position = position_dodge(width = 0.4)) + - facet_wrap(~ sample, scales = "free_y") + - fill_scale_plots + - labs(title = "QUAST: L(G) 50 and 90") + - theme(panel.border = element_rect(fill = NA)) - ggplotly(p) \n```') %>% - write_lines(glue::glue("quast_files/length/_{ unique(quast_stats$group)[i] }_quast.Rmd")) -} +#| warning: false +# This script prepares things required to render the page +source("scripts/_quast.R") ``` -```{r quast contig plots} -#| eval: !expr params$quast -#| include: false -# This creates code that will generate the contig plots based on the contents of the quast report. - -dir.create("quast_files/contigs") -for (i in 1:length(unique(quast_stats$group))) { -paste0('```{r}\n - p <- quast_stats %>% - filter(group == "', unique(quast_stats$group)[i], '") %>% - filter(str_detect(stat, "# contigs \\\\(")) %>% - filter(!str_detect(stat, ">= 0")) %>% - mutate(stat = stat %>% str_remove_all("# contigs ") %>% str_remove_all("[()]") %>% fct_inorder()) %>% - ggplot(aes(x=stat, y=value)) + - geom_point(aes(fill = stage), - size = 5, - pch = 21, - alpha = 0.8, - position = position_dodge(width = 0.4)) + - facet_wrap(~ sample, scales = "free_y") + - fill_scale_plots + - theme(axis.title.x = element_blank(), - axis.title.y = element_blank()) + - labs(title = "QUAST: Number of contigs by size") -ggplotly(p) - p <- quast_stats %>% - filter(group == "', unique(quast_stats$group)[i], '") %>% - filter(str_detect(stat, "Total length")) %>% - filter(!str_detect(stat, ">= 0")) %>% - mutate(stat = stat %>% str_remove_all("Total length ") %>% str_remove_all("[()]") %>% fct_inorder()) %>% - ggplot(aes(x = stat, y = value)) + - geom_point( - aes(fill = stage), - size = 5, - pch = 21, - height = 0, - width = 0.2, - alpha = 0.8, - position = position_dodge(width = 0.4) - ) + - facet_wrap( ~ sample, scales = "free_y") + - fill_scale_plots + - theme(axis.title.x = element_blank(), - axis.title.y = element_blank()) + - labs(title = "QUAST: Aggregated length") + - scale_y_continuous( - labels = function(x) - format( - x, - scientific = -1, - trim = T, - digits = 3, - drop0trailing = T - ) - ) -ggplotly(p) - \n```') %>% - write_lines(glue::glue("quast_files/contigs/_{ unique(quast_stats$group)[i] }_quast.Rmd")) -} -``` +:::: {.panel-tabset .flow} -::: {.panel-tabset .flow} -```{r quast add length subplots} +```{r quast page} #| eval: !expr params$quast #| results: asis -# This generates the tab-page for each sample -# Per sample there are 3 value boxes -# Below the value boxes there are two plots, one showing the length and one showing the contig statistics - -for (i in 1:length(unique(quast_stats$group))) { - cat(paste0('## ', unique(quast_stats$group)[i], '\n'), - paste0('### { width=30% }\n\n'), - paste0('::: {.valuebox icon="arrow-up-right-circle" color="primary" title="Longest length"}\n'), - quast_stats %>% - filter(group == unique(quast_stats$group)[i]) %>% - filter(stat == "Total length (>= 0 bp)") %>% - filter(value == max(value)) %$% - paste( - format(value, - scientific = -1, - trim = T, - digits = 3, - drop0trailing = T - ), "in sample: ",sample, sep = " ") %>% - paste("bp"), - paste0('\n'), - paste0(':::'), - paste0('\n\n'), - paste0('::: {.valuebox icon="percent" color="success" title="Average GC Content"}\n'), - quast_stats %>% - filter(group == unique(quast_stats$group)[i]) %>% - filter(stat == "GC (%)") %$% - mean(value) %>% - round(2) %>% - paste(" %"), - paste0('\n'), - paste0(':::'), - paste0('\n\n'), - paste0('::: {.valuebox icon="emoji-heart-eyes" color="info" title="Lowest L90"}\n'), - quast_stats %>% - filter(group == unique(quast_stats$group)[i]) %>% - filter(stat == "L90") %>% - filter(value == min(value)) %>% - unique() %$% - glue::glue("{unique(value)}, in sample(s) {unique(sample)} at stage(s): {paste(stage, collapse = ', ')}"), - paste0('\n'), - paste0(':::'), - paste0('\n\n'), - paste0('### {.tabset}'), - paste0('\n\n'), - paste0('#### Tables \n\n'), - quast_stats %>% - filter(group == unique(quast_stats$group)[i]) %>% - dplyr::select(sample, stage, stat, value) %>% - pivot_wider(names_from = "stat", values_from = "value",id_cols = c(sample, stage)) %>% - dplyr::arrange(factor(stage, levels = c("Assembly","medaka", "pilon","links","longstitch","ragtag")), sample) %>% - gt::gt() %>% - gt::cols_nanoplot(columns = starts_with("# contigs ("), - new_col_name = "Contigs_by_size", - new_col_label = gt::md("*# Contigs by size*")) %>% - gt::cols_nanoplot(columns = starts_with("Total length ("), - new_col_name = "Total_length", - new_col_label = gt::md("*Total length*")) %>% - gt::tab_footnote( - footnote = "Breaks are: contigs >= 0, 1kb, 5kb, 10kb, 25kb, 50kb", - locations = gt::cells_column_labels(columns = c(Contigs_by_size, Total_length))) %>% - gt::cols_align(align = "center", columns = c(Contigs_by_size, Total_length)) %>% - gt::cols_move(Contigs_by_size, "Largest contig") %>% - gt::cols_move(Total_length, "Total length") %>% - gt::as_raw_html() - , - paste0('\n\n'), - paste0('#### Plots { orientation="columns" }'), - paste0('\n\n'), - paste0('#####'), - paste0('\n\n'), - knitr::knit_child(glue::glue('quast_files/length/_{ unique(quast_stats$group)[i] }_quast.Rmd'), - envir = globalenv(), - quiet = TRUE), - paste0('\n\n'), - paste0('#####'), - paste0('\n\n'), - knitr::knit_child(glue::glue('quast_files/contigs/_{ unique(quast_stats$group)[i] }_quast.Rmd'), - envir = globalenv(), - quiet = TRUE), - paste0('\n\n\n'), - sep = "") -} +# This produces the contents of the page. +source("scripts/_quast_page.R") ``` ```{r unlink quast} #| eval: !expr params$quast +#| include: false # Remove temporary files, write out collected report (mainly for debugging) unlink("quast_files/contigs", recursive = T) unlink("quast_files/length", recursive = T) +unlink("quast_files/NL_plots", recursive = T) write_csv(quast_stats,"quast_files/reports.csv") ``` -::: + +:::: + ::: -# BUSCO +## BUSCO ::: {.content-visible unless-profile="busco"} + BUSCO was not included in the pipeline run. + ::: -```{r} +::: {.content-visible when-profile="busco"} + +BUSCO assess assembly quality based on the presence / absence of expected single-copy orthologs. + +```{r busco data} #| eval: !expr params$busco #| warning: false #| message: false #| echo: false # Parse the reports from busco -busco_reports <- list.files(paste0(data_base, "busco"), - full.names = T, - pattern = "batch_summary") %>% - map_dfr(\(x) read_busco_batch(x)) %>% - left_join(groups, by = join_by(sample)) -``` - - -```{r} -#| eval: !expr params$busco -#| include: false -# This creates code that will generate the plots based on BUSCO results - -dir.create("busco_files") -dir.create("busco_files/orthologs") -for (i in 1:length(unique(busco_reports$group))) { -paste0('```{r}\n - p <- busco_reports %>% - filter(group == "', unique(busco_reports$group)[i], '") %>% - filter(Var %in% c("Complete","Single","Duplicated","Fragmented")) %>% - ggplot(aes(y = value, x = Var)) + - geom_point( - aes(fill = stage), - size = 6, - pch = 21, - height = 0, - alpha = 0.8, - position = position_dodge(width = 0.4) - ) + - facet_wrap( ~ sample, nrow = 3) + - fill_scale_plots + - labs( y = "% of Single Copy Orthologs", - title = "BUSCO: Conserved Orthologs") + - coord_cartesian(clip = "on") + - theme( - panel.border = element_rect(fill = NA), - legend.position = "bottom", - axis.title.y = element_text(angle = 90), - axis.title.x = element_blank() - ) - ggplotly(p) - \n```') %>% - write_lines(glue::glue("busco_files/orthologs/_{ unique(busco_reports$group)[i] }_orthologs.Rmd")) -} +source("scripts/_busco.R") ``` -::: {.content-visible when-profile="busco"} -BUSCO assess assembly quality based on the presence / absence of expected single-copy orthologs. +:::: {.panel-tabset .flow} -::: {.panel-tabset .flow} -```{r busco orthologs add subplots and valueboxes} +```{r busco page} #| eval: !expr params$busco #| results: asis # # This generates the tab-page for each group -# Per group there are 3 value boxes -# Below the value boxes there are one plots, showing the BUSCO statistics - -for (i in 1:length(unique(busco_reports$group))) { - cur_group <- unique(busco_reports$group)[i] - # The BUSCO valueboxes contain information on which stage of the assembly had the highest quality, this requires some variables. - completenes <- busco_reports %>% - filter(group == cur_group) %>% - filter(Var == "Complete") %>% - filter(value == max(value)) %>% - dplyr::select(sample, stage, value) %>% - unique() - fragmented <- busco_reports %>% - filter(group == cur_group) %>% - filter(Var == "Fragmented") %>% - filter(value == max(value)) %>% - dplyr::select(sample, stage ,value) %>% - unique() - missing <- busco_reports %>% - filter(group == cur_group) %>% - filter(Var == "Missing") %>% - filter(value == max(value)) %>% - dplyr::select(sample, stage ,value) %>% - unique() - cat(paste('## ', unique(busco_reports$group)[i]), - paste0('\n\n'), - paste0('### {.fill} \n\n'), - paste0('::: {.valuebox icon="percent" color="success" title="Max. BUSCO Completenes" }\n'), - paste0('\n'), - glue::glue("{unique(completenes$value)}%,\n at: {paste(completenes$sample, completenes$stage, collapse = ', ')}"), - paste0('\n'), - paste0(':::'), - paste0('\n'), - paste0('::: {.valuebox icon="heartbreak" color="warning" title="Max. BUSCO Fragmented"}\n'), - glue::glue("{unique(fragmented$value)}%,\n at: {paste(fragmented$sample, fragmented$stage, collapse = ', ')}"), - paste0('\n'), - paste0('\n'), - paste0(':::'), - paste0('\n'), - paste0('::: {.valuebox icon="person-walking" color="danger" title="Max. BUSCOs Missing"}\n'), - glue::glue("{unique(missing$value)}%,\n at: {paste(missing$sample, missing$stage, collapse = ', ')}"), - paste0('\n'), - paste0(':::'), - paste0('\n\n'), - paste('###'), - paste0('\n\n'), - # paste('#### Tables'), - paste0('\n\n'), - busco_reports %>% - filter(group == cur_group) %>% - dplyr::select(sample, stage, Var, value) %>% - mutate(Var = str_replace_all(Var, "_", " ")) %>% - pivot_wider(names_from = "Var", values_from = "value", id_cols = c(sample,stage)) %>% - dplyr::arrange(factor(stage, levels = c("Assembly","medaka", "pilon","links","longstitch","ragtag")), sample) %>% - gt::gt() %>% - gt::as_raw_html(), - paste0('\n\n'), - # paste('#### Plots'), - # paste0('\n\n'), - knitr::knit_child(glue::glue('busco_files/orthologs/_{ unique(busco_reports$group)[i] }_orthologs.Rmd'), - envir = globalenv(), - quiet = TRUE), - paste0('\n\n\n'), - sep = "") -} + +source("scripts/_busco_page.R") ``` -::: + +:::: + ::: -```{r} +```{r include = F} #| eval: !expr params$busco # Delete temporary files unlink("busco_files/orthologs", recursive = T) -``` -```{r} #| eval: !expr params$busco # Export large report table, mainly for debugging write_csv(busco_reports,"busco_files/reports.csv") ``` -# merqury +## merqury ::: {.content-visible unless-profile="merqury"} meryl and merqury were not included in the pipeline run. @@ -543,260 +233,26 @@ meryl and merqury were not included in the pipeline run. #| message: false #| output: false -# Here the merqury stats are parsed and the assembly stage is extracted -merqury_stats <- list.files(paste0(data_base, "merqury"), full.names = T, pattern = "stats") %>% - lapply(\(x) { - read_tsv(x, col_names = c("sample_stage","all","assembly","total","percent"), show_col_types = FALSE) %>% - mutate( # Get sample name by matching filename to samples in groups, reverse sort by length to hopefully catch - # the correct name first in case there is partial overlap between sample names. - sample = basename(x) %>% - str_extract(groups$sample[rev(order(nchar(groups$sample)))] %>% paste(collapse = "|")), - stage = case_when( - str_detect(x, "_ragtag") ~ "RagTag", - str_detect(x, "_medaka") ~ "medaka", - str_detect(x, "_pilon") ~ "pilon", - str_detect(x, "_longstitch") ~ "longstitch", - str_detect(x, "_links") ~ "LINKS", - str_detect(x, "assembl[ey]") ~ "Assembly", - TRUE ~ "Unknown")) }) %>% - bind_rows() %>% - left_join(groups, by = join_by(sample)) -# This parses the assembly stats -merqury_asm_hists <- list.files(paste0(data_base, "/merqury"), full.names = T, pattern = "asm.hist") %>% - lapply(\(x) { - read_tsv(x, col_names = T, show_col_types = FALSE) %>% - mutate( - sample = basename(x) %>% - str_extract(groups$sample[rev(order(nchar(groups$sample)))] %>% paste(collapse = "|")), - stage = case_when( - str_detect(x, "_ragtag") ~ "RagTag", - str_detect(x, "_medaka") ~ "medaka", - str_detect(x, "_pilon") ~ "pilon", - str_detect(x, "_longstitch") ~ "longstitch", - str_detect(x, "_links") ~ "LINKS", - str_detect(x, "assembl[ey]") ~ "Assembly", - TRUE ~ "Unknown"), - Assembly = as.factor(Assembly), - stage = as.factor(stage), - sample = as.factor(sample), - kmer_multiplicity = as.integer(kmer_multiplicity), - Count = as.integer(Count)) - }) %>% - bind_rows() %>% - left_join(groups, by = join_by(sample)) -# This parses the copy number file -merqury_cn_hists <- list.files(paste0(data_base, "merqury"), full.names = T, pattern = "cn.hist") %>% - lapply(\(x) { - read_tsv(x, col_names = T, show_col_types = FALSE) %>% - mutate( - sample = basename(x) %>% - str_extract(groups$sample[rev(order(nchar(groups$sample)))] %>% paste(collapse = "|")), - stage = case_when( - str_detect(x, "_ragtag") ~ "RagTag", - str_detect(x, "_medaka") ~ "medaka", - str_detect(x, "_pilon") ~ "pilon", - str_detect(x, "_longstitch") ~ "longstitch", - str_detect(x, "_links") ~ "LINKS", - str_detect(x, "assembl[ey]") ~ "Assembly", - TRUE ~ "Unknown"), - Copies = as.factor(Copies), - stage = as.factor(stage), - sample = as.factor(sample), - kmer_multiplicity = as.integer(kmer_multiplicity), - Count = as.integer(Count)) - }) %>% - bind_rows() %>% - left_join(groups, by = join_by(sample)) -# This parses the qv file -merqury_qv <- list.files(paste0(data_base, "merqury"), full.names = T, pattern = ".qv") %>% - lapply(\(x) { - read_tsv(x, - col_names = c("Assembly", "kmers_assembly_unique", "kmers_assembly_shared", "QV", "error_rate"), - show_col_types = FALSE) %>% - mutate( - sample = basename(x) %>% - str_extract(groups$sample[rev(order(nchar(groups$sample)))] %>% paste(collapse = "|")), - stage = case_when( - str_detect(x, "_ragtag") ~ "RagTag", - str_detect(x, "_medaka") ~ "medaka", - str_detect(x, "_pilon") ~ "pilon", - str_detect(x, "_longstitch") ~ "longstitch", - str_detect(x, "_links") ~ "LINKS", - str_detect(x, "assembl[ey]") ~ "Assembly", - TRUE ~ "Unknown"), - stage = as.factor(stage), - sample = as.factor(sample), - kmers_assembly_shared = as.integer(kmers_assembly_shared), - kmers_assembly_unique = as.integer(kmers_assembly_unique), - QV = as.double(QV), - error_rate = as.double(error_rate)) - } - ) %>% - bind_rows() %>% - left_join(groups, by = join_by(sample)) -dir.create("merqury_files") -``` - -```{r merqury qv} -#| eval: !expr params$merqury -#| include: false -# This generates QV-plots from merqury; the plot function is stuffed into plot_merqury -dir.create("merqury_files/qv_plots/") -for (i in 1:length(unique(merqury_qv$group))) { - cur_group <- unique(merqury_qv$group)[i] - paste0('```{r} -p <- merqury_qv %>% - plot_merqury_qv("', cur_group,'") -print(p)\n```') %>% - write_lines(glue::glue("merqury_files/qv_plots/_{ cur_group }_qv_plt.Rmd")) -} -``` - -```{r merqury completeness} -#| eval: !expr params$merqury -#| include: false -# This generates stat-plots from merqury; the plot function is stuffed into plot_merqury - -dir.create("merqury_files/stat_plots/") -for (i in 1:length(unique(merqury_stats$group))) { - cur_group <- unique(merqury_stats$group)[i] - paste0('```{r} -p <- merqury_stats %>% - plot_merqury_stats("', cur_group,'") -print(p)\n```') %>% - write_lines(glue::glue("merqury_files/stat_plots/_{ cur_group }_completeness_plt.Rmd")) -} -``` - -```{r merqury asm} -#| eval: !expr params$merqury -#| include: false -# This generates assembly plots from merqury; the plot function is stuffed into plot_merqury - -dir.create("merqury_files/asm_plots/") -for (i in 1:length(unique(merqury_asm_hists$group))) { - cur_group <- unique(merqury_asm_hists$group)[i] - paste0('```{r} -p <- merqury_asm_hists %>% - plot_merqury_multiplicity("', cur_group,'") -print(p)\n```') %>% - write_lines(glue::glue("merqury_files/asm_plots/_{ cur_group }_asm_plt.Rmd")) -} -``` - -```{r merqury cn} -#| eval: !expr params$merqury -#| include: false -# This generates copy-number from merqury; the plot function is stuffed into plot_merqury - -dir.create("merqury_files/cn_plots/") -for (i in 1:length(unique(merqury_cn_hists$group))) { - cur_group <- unique(merqury_cn_hists$group)[i] - paste0('```{r} -p <- merqury_cn_hists %>% - plot_merqury_copynumber("', cur_group,'") -print(p)\n```') %>% - write_lines(glue::glue("merqury_files/cn_plots/_{ cur_group }_cn_plt.Rmd")) -} +source("scripts/_merqury.R") ``` ::: {.content-visible when-profile="merqury"} + merqury compares k-mer spectra between assemblies and short read libraries to assess assembly quality and completeness. -::: {.panel-tabset .flow} +:::: {.panel-tabset .flow} + ```{r merqury add plots and valueboxes} #| eval: !expr params$merqury #| results: asis -# This generates the tab-page for each sample -# Per sample there are 3 value boxes -# Below the value boxes there is a tabset of plots, each tab contains one of the plot-types produced above. -# Those are: Completeness, k-mer specatr, QV and CN - -for (i in 1:length(unique(merqury_stats$group))) { - cur_group <- unique(merqury_stats$group)[i] - highest_val <- merqury_stats %>% - filter(group == cur_group) %>% - filter(percent == max(percent)) %$% - percent %>% - unique() - highest_stage <- merqury_stats %>% - filter(group == cur_group) %>% - filter(percent == highest_val) %>% - dplyr::select(sample, stage, percent) - lowest_val <- merqury_stats %>% - filter(group == cur_group) %>% - filter(percent == min(percent)) %$% - percent %>% - unique() - lowest_stage <- merqury_stats %>% - filter(group == cur_group) %>% - filter(percent == lowest_val) %>% - dplyr::select(sample, stage, percent) - highest_qv <- merqury_qv %>% - filter(group == cur_group) %>% - filter(QV == max(QV)) %$% - QV %>% - unique() - highest_qv_stage <- merqury_qv %>% - filter(group == cur_group) %>% - filter(QV == max(QV)) %>% - dplyr::select(sample, stage, QV) - - cat(paste('## ', cur_group), - paste0('\n\n'), - paste0('### Valueboxes'), - paste0('\n\n'), - paste0('::: {.valuebox icon="exclude" color="primary" title="Max. merqury QV" }\n'), - glue::glue("QV: {unique(highest_qv) %>% round(2)}, at: {paste(highest_qv_stage$sample, highest_qv_stage$stage, collapse = ', ')}"), - paste0('\n'), - paste0(':::'), - paste0('\n\n'), - paste0('::: {.valuebox icon="percent" color="success" title="Highest k-mer completeness" }\n'), - glue::glue("{unique(highest_val) %>% round(2)}%, at stage(s): {paste(highest_stage$sample, highest_stage$stage, collapse = ', ')}"), - paste0('\n'), - paste0(':::'), - paste0('\n\n'), - paste0('::: {.valuebox icon="heartbreak" color="warning" title="Lowest k-mer completeness" }\n'), - glue::glue("{unique(lowest_val) %>% round(2)}%, at stage(s): {paste(lowest_stage$sample, lowest_stage$stage, collapse = ', ')}"), - paste0('\n'), - paste0(':::'), - paste0('\n\n'), - paste0('\n\n'), - paste0('### Plots { .tabset }'), - paste0('\n\n'), - paste0('#### Completeness \n'), - paste0('\n'), - knitr::knit_child(glue::glue('merqury_files/stat_plots/_{ cur_group }_completeness_plt.Rmd'), - envir = globalenv(), - quiet = TRUE), - paste0('\n'), - paste0('#### QV \n'), - paste0('\n'), - paste0('QV is defined as:\n', expression(10*-log10(error_rate))), - paste0('\n'), - knitr::knit_child(glue::glue('merqury_files/qv_plots/_{ cur_group }_qv_plt.Rmd'), - envir = globalenv(), - quiet = TRUE), - paste0('\n'), - paste0('#### Spectra \n'), - paste0('\n'), - knitr::knit_child(glue::glue('merqury_files/asm_plots/_{ cur_group }_asm_plt.Rmd'), - envir = globalenv(), - quiet = TRUE), - paste0('\n'), - paste0('#### Copy Number \n'), - paste0('\n'), - knitr::knit_child(glue::glue('merqury_files/cn_plots/_{ cur_group }_cn_plt.Rmd'), - envir = globalenv(), - quiet = TRUE), - paste0('\n\n\n'), - sep = "") -} +source("scripts/_merqury_page.R") ``` -::: + +:::: + ::: -```{r} +```{r include = F} #| eval: !expr params$merqury # Delete files. unlink("merqury_files/cn_plots") @@ -804,7 +260,7 @@ unlink("merqury_files/asm_plots") unlink("merqury_files") ``` -# genomescope +## genomescope ::: {.content-visible unless-profile="jellyfish"} jellyfish / genomescope was not included in the pipeline run. @@ -817,8 +273,8 @@ jellyfish / genomescope was not included in the pipeline run. #| output: false #| warning: false # Parse the genomescope statistics -genomescope_out <- list.files(paste0(data_base, "genomescope"), full.names = T, pattern = "genomescope.txt") %>% - map_dfr(\(x) read_genomescope(x)) %>% +genomescope_out <- list.files(paste0(data_base, "genomescope"), full.names = T, pattern = "genomescope.txt") |> + map_dfr(\(x) read_genomescope(x)) |> left_join(groups, by = join_by(sample)) ``` @@ -826,54 +282,42 @@ genomescope_out <- list.files(paste0(data_base, "genomescope"), full.names = T, Below are the genomescope estimates based on the provided QC reads: -```{r} +```{r echo = F} #| eval: !expr params$jellyfish #| output: asis -# Since genomescope produces plots, I am simply including those here instead of recreating them, the proper QC for kmers comes with merqury. -img_files <- list.files(paste0(data_base,"genomescope"), full.names = T, pattern = "plot.png") -dir.create("genomescope_files") -for (file in img_files) { - file.copy(from = file, - to = paste0("genomescope_files/", file %>% basename(), sep ="")) - -} - -img_files <- data.frame(file = list.files("genomescope_files/", full.names = T, pattern = "plot.png")) %>% - mutate(group = str_extract(file %>% basename(), ".+?(?=_plot.png)")) - - -cat(":::{.panel-tabset}\n\n") -for(grp in unique(img_files$group)) { - cat(glue::glue('## {grp}\n\n\n')) - cat(glue::glue('![](<% filter(group == grp) %$% file>>){width=50% fig-align="centre"}\n\n\n', - .open = "<<", - .close = ">>")) - } -cat(":::\n") +source("scripts/_genomescope_page.R") ``` + ::: -```{r} -glue::glue('## <>\n (<% filter(group == grp) %$% file %>% lapply(function(file) paste("![]", file, "{width=50% fig-align=centre}\n")) %>% unlist>>)\n\n\n', - .open = "<<", - .close = ">>") -``` + # Software versions The pipeline was run using the following software versions: ```{r} +#| message: false +#| echo: false +#| output: asis versions <- yaml::read_yaml("software_versions.yml") lapply(1:length(versions), \(process) { proc = versions[[process]] proc_name = names(versions[process]) tools <- lapply(1:length(proc), \(tool) { - tool_name = proc[tool] %>% names - tool_version = proc[[tool]] %>% as.character() + tool_name = proc[tool] |> names() + tool_version = proc[[tool]] |> as.character() return(tibble(Process = proc_name,Tool = tool_name, Version = tool_version)) - }) %>% + }) |> bind_rows() -}) %>% - bind_rows() %>% - knitr::kable() +}) |> + bind_rows() |> + gt::gt() |> + gt::fmt_auto() |> + gt::opt_stylize(color = "gray") |> + gt::opt_table_font( + font = list( + gt::google_font(name = "Maven Pro"), + "rounded-sans" + )) |> + gt::as_raw_html() ``` diff --git a/assets/report/scripts/_busco.R b/assets/report/scripts/_busco.R new file mode 100644 index 00000000..09fb97ce --- /dev/null +++ b/assets/report/scripts/_busco.R @@ -0,0 +1,43 @@ +# Parse BUSCO reports + +busco_reports <- list.files(paste0(data_base, "busco"), + full.names = T, + pattern = "batch_summary") |> + map_dfr(\(x) read_busco_batch(x)) |> + left_join(groups, by = join_by(sample)) + +dir.create("busco_files") +dir.create("busco_files/orthologs") + +# Create BUSCO plot +for (i in 1:length(unique(busco_reports$group))) { + cur_group <- unique(busco_reports$group)[i] + group_size <- busco_reports |> filter(group == cur_group) |> _$sample |> unique() |> length() + plt_height <- case_when(group_size < 5 ~ 7, TRUE ~ group_size+3) + paste0('```{r echo = F, fig.height = ',plt_height,'} + p <- busco_reports |> + filter(group == "', unique(busco_reports$group)[i], '") |> + filter(Var %in% c("Complete","Single","Duplicated","Fragmented")) |> + ggplot(aes(y = value, x = Var)) + + geom_point( + aes(fill = stage), + size = 6, + pch = 21, + alpha = 0.8, + position = position_dodge(width = 0.4) + ) + + facet_wrap( ~ sample, nrow = 3) + + fill_scale_plots + + labs( y = "% of Single Copy Orthologs", + title = "BUSCO: Conserved Orthologs") + + coord_cartesian(clip = "on") + + theme( + panel.border = element_rect(fill = NA), + legend.position = "bottom", + axis.title.y = element_text(angle = 90), + axis.title.x = element_blank() + ) + ggplotly(p) + \n```') |> + write_lines(glue::glue("busco_files/orthologs/_{ unique(busco_reports$group)[i] }_orthologs.Rmd")) +} diff --git a/assets/report/scripts/_busco_page.R b/assets/report/scripts/_busco_page.R new file mode 100644 index 00000000..ef672a42 --- /dev/null +++ b/assets/report/scripts/_busco_page.R @@ -0,0 +1,33 @@ +# This generates a tab-page for each sample + +for (i in 1:length(unique(busco_reports$group))) { + cur_group <- unique(busco_reports$group)[i] + cat( + paste0('### ', cur_group, '\n\n'), + paste0('::: {.panel-tabset} \n\n'), + paste0('#### Tabular \n\n'), + busco_reports |> + filter(group == cur_group) |> + dplyr::select(sample, stage, Var, value) |> + mutate(Var = str_replace_all(Var, "_", " ") |> str_replace_all("percent", "(%)")) |> + pivot_wider(names_from = "Var", values_from = "value", id_cols = c(sample,stage)) |> + dplyr::arrange(factor(stage, levels = c("Assembly","medaka", "pilon","links","longstitch","ragtag")), sample) |> + gt::gt() |> + gt::fmt_auto() |> + gt::opt_stylize(color = "gray") |> + gt::opt_table_font( + font = list( + gt::google_font(name = "Maven Pro"), + "rounded-sans" + )) |> + gt::as_raw_html(), + paste0('\n\n'), + paste('#### Plot'), + paste0('\n\n'), + knitr::knit_child(glue::glue('busco_files/orthologs/_{ unique(busco_reports$group)[i] }_orthologs.Rmd'), + envir = globalenv(), + quiet = TRUE), + paste0('\n\n\n'), + paste0(':::\n\n'), + sep = "") +} diff --git a/assets/report/scripts/_fastplong_page.R b/assets/report/scripts/_fastplong_page.R new file mode 100644 index 00000000..297bdba0 --- /dev/null +++ b/assets/report/scripts/_fastplong_page.R @@ -0,0 +1,41 @@ +# This loop creates one tab per group +for (i in 1:length(unique(fastplong_reports$group))) { + cat(paste0('### ', unique(fastplong_reports$group)[i] , '\n\n'), + paste0('\n\n'), + paste0('Read filtering and QC results for ', unique(fastplong_reports$group)[i]), + paste0('\n\n'), + fastplong_reports |> + filter(group == unique(fastplong_reports$group)[i]) |> + dplyr::select(-sample) |> + unique() |> + pivot_wider(id_cols = c("stat","group","read_type"), names_from = stage, values_from = value) |> + mutate(Filtered = `Before Filtering` - `After Filtering` |> round(), + Filtered = case_when(!str_detect(stat, "Rate|Length|Content") ~ Filtered, + TRUE ~ NA_real_), + Filtered_Perc = Filtered / `Before Filtering`) |> + dplyr::select(-group) |> + dplyr::arrange( + stat |> fct_relevel( + "Total Reads", + "Total Bases", + "Read Mean Length", + "Q30 Rate", + "Q20 Rate", + "Q30 Bases", + "Q20 Bases" + ), + read_type + ) |> + gt::gt() |> + gt::cols_label(stat = "", read_type = "Read Type", Filtered_Perc = "% filtered") |> + gt::fmt_auto() |> + gt::fmt_percent(Filtered_Perc) |> + gt::tab_footnote( + footnote = "Due to read splitting it is possible that the number of reads after filtering is larger than before.", + locations = gt::cells_column_labels(columns = c(Filtered))) |> + gt::opt_stylize(color = "gray") |> + gt::as_raw_html(), + paste0('\n\n'), + sep = "" + ) +} diff --git a/assets/report/scripts/_genomescope_page.R b/assets/report/scripts/_genomescope_page.R new file mode 100644 index 00000000..73bb61b6 --- /dev/null +++ b/assets/report/scripts/_genomescope_page.R @@ -0,0 +1,21 @@ +# Since genomescope produces plots, I am simply including those here instead of recreating them, the proper QC for kmers comes with merqury. +img_files <- list.files(paste0(data_base,"genomescope"), full.names = T, pattern = "plot.png") +dir.create("genomescope_files") +for (file in img_files) { + file.copy(from = file, + to = paste0("genomescope_files/", file |> basename(), sep ="")) + +} + +img_files <- data.frame(file = list.files("genomescope_files/", full.names = T, pattern = "plot.png")) |> + mutate(group = str_extract(file |> basename(), ".+?(?=_plot.png)")) + + +cat(":::{.panel-tabset}\n\n") +for(grp in unique(img_files$group)) { + cat(glue::glue('## {grp}\n\n\n')) + cat(glue::glue('![](< filter(group == grp) %$% file>>){fig-align="centre"}\n\n\n', + .open = "<<", + .close = ">>")) +} +cat(":::\n") diff --git a/assets/report/scripts/_merqury.R b/assets/report/scripts/_merqury.R new file mode 100644 index 00000000..12209ef6 --- /dev/null +++ b/assets/report/scripts/_merqury.R @@ -0,0 +1,149 @@ +# Here the merqury stats are parsed and the assembly stage is extracted +merqury_stats <- list.files(paste0(data_base, "merqury"), full.names = T, pattern = "stats") |> + lapply(\(x) { + read_tsv(x, col_names = c("sample_stage","all","assembly","total","percent"), show_col_types = FALSE) |> + mutate( # Get sample name by matching filename to samples in groups, reverse sort by length to hopefully catch + # the correct name first in case there is partial overlap between sample names. + sample = basename(x) |> + str_extract(groups$sample[rev(order(nchar(groups$sample)))] |> paste(collapse = "|")), + stage = case_when( + str_detect(x, "_ragtag") ~ "RagTag", + str_detect(x, "_medaka") ~ "medaka", + str_detect(x, "_pilon") ~ "pilon", + str_detect(x, "_longstitch") ~ "longstitch", + str_detect(x, "_links") ~ "LINKS", + str_detect(x, "assembl[ey]") ~ "Assembly", + TRUE ~ "Unknown")) }) |> + bind_rows() |> + left_join(groups, by = join_by(sample)) + +# This parses the assembly stats +merqury_asm_hists <- list.files(paste0(data_base, "/merqury"), full.names = T, pattern = "asm.hist") |> + lapply(\(x) { + read_tsv(x, col_names = T, show_col_types = FALSE) |> + mutate( + sample = str_extract(x |> basename(), + groups$sample[rev(order(nchar(groups$sample)))] |> paste(collapse = "|")), + stage = case_when( + str_detect(x, "_ragtag") ~ "RagTag", + str_detect(x, "_medaka") ~ "medaka", + str_detect(x, "_pilon") ~ "pilon", + str_detect(x, "_longstitch") ~ "longstitch", + str_detect(x, "_links") ~ "LINKS", + str_detect(x, "assembl[ey]") ~ "Assembly", + TRUE ~ "Unknown"), + Assembly = as.factor(Assembly), + stage = as.factor(stage), + sample = as.factor(sample), + kmer_multiplicity = as.integer(kmer_multiplicity), + Count = as.integer(Count)) + }) |> + bind_rows() |> + left_join(groups, by = join_by(sample)) + +# This parses the copy number file +merqury_cn_hists <- list.files(paste0(data_base, "merqury"), full.names = T, pattern = "cn.hist") |> + lapply(\(x) { + read_tsv(x, col_names = T, show_col_types = FALSE) |> + mutate( + sample = str_extract(x |> basename(), + groups$sample[rev(order(nchar(groups$sample)))] |> paste(collapse = "|")), + stage = case_when( + str_detect(x, "_ragtag") ~ "RagTag", + str_detect(x, "_medaka") ~ "medaka", + str_detect(x, "_pilon") ~ "pilon", + str_detect(x, "_longstitch") ~ "longstitch", + str_detect(x, "_links") ~ "LINKS", + str_detect(x, "assembl[ey]") ~ "Assembly", + TRUE ~ "Unknown"), + Copies = as.factor(Copies), + stage = as.factor(stage), + sample = as.factor(sample), + kmer_multiplicity = as.integer(kmer_multiplicity), + Count = as.integer(Count)) + }) |> + bind_rows() |> + left_join(groups, by = join_by(sample)) + +# This parses the qv file +merqury_qv <- list.files(paste0(data_base, "merqury"), full.names = T, pattern = ".qv") |> + lapply(\(x) { + read_tsv(x, + col_names = c("Assembly", "kmers_assembly_unique", "kmers_assembly_shared", "QV", "error_rate"), + show_col_types = FALSE) |> + mutate( + sample = str_extract(x |> basename(), + groups$sample[rev(order(nchar(groups$sample)))] |> paste(collapse = "|")), + stage = case_when( + str_detect(x, "_ragtag") ~ "RagTag", + str_detect(x, "_medaka") ~ "medaka", + str_detect(x, "_pilon") ~ "pilon", + str_detect(x, "_longstitch") ~ "longstitch", + str_detect(x, "_links") ~ "LINKS", + str_detect(x, "assembl[ey]") ~ "Assembly", + TRUE ~ "Unknown"), + stage = as.factor(stage), + sample = as.factor(sample), + kmers_assembly_shared = as.integer(kmers_assembly_shared), + kmers_assembly_unique = as.integer(kmers_assembly_unique), + QV = as.double(QV), + error_rate = as.double(error_rate)) + } + ) |> + bind_rows() |> + left_join(groups, by = join_by(sample)) +dir.create("merqury_files") + +# This generates QV-plots from merqury; the plot function is stuffed into plot_merqury +dir.create("merqury_files/qv_plots/") +for (i in 1:length(unique(merqury_qv$group))) { + cur_group <- unique(merqury_qv$group)[i] + group_size <- merqury_qv |> filter(group == cur_group) |> _$sample |> unique() |> length() + plt_height <- case_when(group_size < 5 ~ 7, TRUE ~ group_size+3) + cur_group <- unique(merqury_qv$group)[i] + paste0('```{r echo = F, fig.height = ',plt_height,'} +p <- merqury_qv |> + plot_merqury_qv("', cur_group,'") +ggplotly(p)\n```') |> + write_lines(glue::glue("merqury_files/qv_plots/_{ cur_group }_qv_plt.Rmd")) +} +# This generates stat-plots from merqury; the plot function is stuffed into plot_merqury + +dir.create("merqury_files/stat_plots/") +for (i in 1:length(unique(merqury_stats$group))) { + cur_group <- unique(merqury_stats$group)[i] + group_size <- merqury_stats |> filter(group == cur_group) |> _$sample |> unique() |> length() + plt_height <- case_when(group_size < 5 ~ 7, TRUE ~ group_size+3) + cur_group <- unique(merqury_stats$group)[i] + paste0('```{r echo = F, fig.height = ',plt_height,'} +p <- merqury_stats |> + plot_merqury_stats("', cur_group,'") +ggplotly(p)\n```') |> + write_lines(glue::glue("merqury_files/stat_plots/_{ cur_group }_completeness_plt.Rmd")) +} + +# This generates assembly plots from merqury; the plot function is stuffed into plot_merqury + +dir.create("merqury_files/asm_plots/") +for (i in 1:length(unique(merqury_asm_hists$group))) { + cur_group <- unique(merqury_asm_hists$group)[i] + group_size <- merqury_asm_hists |> filter(group == cur_group) |> _$sample |> unique() |> length() + plt_height <- case_when(group_size < 5 ~ 7, TRUE ~ group_size+3) + paste0('```{r echo = F, fig.height = ',plt_height,'} +p <- merqury_asm_hists |> + plot_merqury_multiplicity("', cur_group,'") +print(p)\n```') |> + write_lines(glue::glue("merqury_files/asm_plots/_{ cur_group }_asm_plt.Rmd")) +} + +dir.create("merqury_files/cn_plots/") +for (i in 1:length(unique(merqury_cn_hists$group))) { + cur_group <- unique(merqury_cn_hists$group)[i] + group_size <- merqury_cn_hists |> filter(group == cur_group) |> _$sample |> unique() |> length() + plt_height <- case_when(group_size < 5 ~ 7, TRUE ~ group_size+3) + paste0('```{r echo = F, fig.height = ',plt_height,'} +p <- merqury_cn_hists |> + plot_merqury_copynumber("', cur_group,'") +print(p)\n```') |> + write_lines(glue::glue("merqury_files/cn_plots/_{ cur_group }_cn_plt.Rmd")) +} diff --git a/assets/report/scripts/_merqury_page.R b/assets/report/scripts/_merqury_page.R new file mode 100644 index 00000000..4a753393 --- /dev/null +++ b/assets/report/scripts/_merqury_page.R @@ -0,0 +1,41 @@ +# This generates the tab-page for each sample +# Per sample there are 3 value boxes +# Below the value boxes there is a tabset of plots, each tab contains one of the plot-types produced above. +# Those are: Completeness, k-mer specatr, QV and CN + +for (i in 1:length(unique(merqury_stats$group))) { + cur_group <- unique(merqury_stats$group)[i] + cat( + paste0('### ', cur_group, '\n\n'), + paste0('merqury creates assembly statistics, through comparisons of the k-mer spectrum of short-reads to the k-mer spectrum of an assembly.\n\n'), + paste0('::: {.panel-tabset} \n\n'), + paste0('#### Completeness \n'), + paste0('\n'), + knitr::knit_child(glue::glue('merqury_files/stat_plots/_{ cur_group }_completeness_plt.Rmd'), + envir = globalenv(), + quiet = TRUE), + paste0('\n'), + paste0('#### QV \n'), + paste0('\n'), + paste0('QV is defined as:\n', expression(10*-log10(error_rate))), + paste0('\n'), + knitr::knit_child(glue::glue('merqury_files/qv_plots/_{ cur_group }_qv_plt.Rmd'), + envir = globalenv(), + quiet = TRUE), + paste0('\n'), + paste0('#### Spectra \n'), + paste0('\n'), + knitr::knit_child(glue::glue('merqury_files/asm_plots/_{ cur_group }_asm_plt.Rmd'), + envir = globalenv(), + quiet = TRUE), + paste0('\n'), + paste0('#### Copy Number \n'), + paste0('\n'), + knitr::knit_child(glue::glue('merqury_files/cn_plots/_{ cur_group }_cn_plt.Rmd'), + envir = globalenv(), + quiet = TRUE), + paste0('\n\n\n'), + paste0(':::'), + paste0('\n\n\n'), + sep = "") +} diff --git a/assets/report/scripts/_quast.R b/assets/report/scripts/_quast.R new file mode 100644 index 00000000..6b8a27e2 --- /dev/null +++ b/assets/report/scripts/_quast.R @@ -0,0 +1,156 @@ +# This file parses QUAST outputs and creates the templates for plotting. + +# Parse the quast reports from data/quast +quast_stats <- list.files(paste0(data_base, "quast"), + pattern = "report.tsv", + full.names = T) |> + map_dfr(\(x) { + read_quast_report(x) |> + mutate( + # Get sample name by matching filename to samples in groups, reverse sort by length to hopefully catch + # the correct name first in case there is partial overlap between sample names. + sample = basename(x) |> + str_extract(groups$sample[rev(order(nchar(groups$sample)))] |> paste(collapse = "|")), + stage = case_when( + str_detect(x, "_ragtag") ~ "RagTag", + str_detect(x, "_medaka") ~ "medaka", + str_detect(x, "_pilon") ~ "pilon", + str_detect(x, "_longstitch") ~ "longstitch", + str_detect(x, "_links") ~ "LINKS", + str_detect(x, "assembl[ey]") ~ "Assembly", + TRUE ~ "Unknown")) }) |> + left_join(groups, by = join_by(sample)) |> + mutate(stage = stage |> fct_relevel("Assembly", "medaka", "pilon", "longstitch", "LINKS", "RagTag") ) |> + dplyr::arrange(sample, stage) + +# This creates code that will generate the length plot based on the contents of the quast report. +dir.create("quast_files") +dir.create("quast_files/length") +for (i in 1:length(unique(quast_stats$group))) { + cur_group <- unique(quast_stats$group)[i] + group_size <- quast_stats |> filter(group == cur_group) |> _$sample |> unique() |> length() + plt_height <- case_when(group_size < 5 ~ 7, TRUE ~ group_size+3) + paste0('```{r echo = F, fig.height = ',plt_height,'} + quast_stats |> + filter(group == "', unique(quast_stats$group)[i], '") |> + filter(str_detect(stat, "[L].*[59]0")) |> + mutate(stat = fct_relevel(stat, "L50","L90","LG50","LG90")) |> + ggplot(aes(x = stat, y = value)) + + geom_point( + aes(fill = stage), + size = 5, + pch = 21, + height = 0, + width = 0.2, + alpha = 0.8, + position = position_dodge(width = 0.4) + ) + + facet_wrap(~ sample, scales = "free_y") + + fill_scale_plots + + theme_bw(base_size = 14) + + theme( + axis.title.x = element_blank(), + strip.background = element_blank(), + legend.position = "bottom", + axis.text.x = element_text(angle = 60, hjust = 1) + ) + + scale_y_continuous( + labels = function(x) + format( + x, + scientific = -1, + trim = T, + digits = 3, + drop0trailing = T + ) + ) + + labs(y = "Aggregated length of contigs in bin")\n```') |> + write_lines(glue::glue("quast_files/length/_{ unique(quast_stats$group)[i] }_quast.Rmd")) +} + +dir.create("quast_files/contigs") +for (i in 1:length(unique(quast_stats$group))) { + cur_group <- unique(quast_stats$group)[i] + group_size <- quast_stats |> filter(group == cur_group) |> _$sample |> unique() |> length() + plt_height <- case_when(group_size < 5 ~ 7, TRUE ~ group_size+3) + paste0('```{r echo = F, fig.height = ',plt_height,'} + quast_stats |> + filter(group == "', unique(quast_stats$group)[i], '") |> + filter(str_detect(stat, "# contigs \\\\(")) |> + filter(!str_detect(stat, ">= 0")) |> + mutate(stat = stat |> str_remove_all("# contigs ") |> str_remove_all("[()]") |> fct_inorder()) |> + ggplot(aes(x = stat, y = value)) + + geom_point( + aes(fill = stage), + size = 5, + pch = 21, + alpha = 0.8, + position = position_dodge(width = 0.4) + ) + + facet_wrap(~ sample, scales = "free_y") + + fill_scale_plots + + theme_bw(base_size = 14) + + theme( + axis.title.x = element_blank(), + axis.title.y = element_blank(), + strip.background = element_blank(), + legend.position = "bottom", + axis.text.x = element_text(angle = 60, hjust = 1) + ) + \n```') |> + write_lines(glue::glue("quast_files/contigs/_{ unique(quast_stats$group)[i] }_quast.Rmd")) +} + +# This creates code that will generate the contig plots based on the contents of the quast report. +dir.create("quast_files/NL_plots") +for (i in 1:length(unique(quast_stats$group))) { + cur_group <- unique(quast_stats$group)[i] + group_size <- quast_stats |> filter(group == cur_group) |> _$sample |> unique() |> length() + plt_height <- case_when(group_size < 5 ~ 7, TRUE ~ group_size+3) + paste0('```{r echo = F, fig.height = ',plt_height,'} + quast_stats |> + filter(group == "', unique(quast_stats$group)[i], '") |> + filter(str_detect(stat, "[N].*[59]0")) |> + ggplot(aes(y = stat, x = value)) + + geom_point( + aes(fill = stage), + size = 5, + pch = 21, + alpha = 0.8, + position = position_dodge(width = 0.4) + ) + + facet_wrap(~sample, scales = "free") + + theme_bw(base_size = 14) + + theme( + axis.title = element_blank(), + strip.background = element_blank(), + legend.position = "bottom", + axis.text.x = element_text(angle = 60, hjust = 1) + ) + + fill_scale_plots + \n```') |> + write_lines(glue::glue("quast_files/NL_plots/_{ unique(quast_stats$group)[i] }_N_quast.Rmd")) + paste0('```{r echo = F, fig.height = ',plt_height,'} + quast_stats |> + filter(group == "', unique(quast_stats$group)[i], '") |> + filter(str_detect(stat, "[L].*[59]0")) |> + ggplot(aes(y = stat, x = value)) + + geom_point( + aes(fill = stage), + size = 5, + pch = 21, + alpha = 0.8, + position = position_dodge(width = 0.4) + ) + + facet_wrap(~sample, scales = "free") + + theme_bw(base_size = 14) + + theme( + axis.title = element_blank(), + strip.background = element_blank(), + legend.position = "bottom", + axis.text.x = element_text(angle = 60, hjust = 1) + ) + + fill_scale_plots + \n```') |> + write_lines(glue::glue("quast_files/NL_plots/_{ unique(quast_stats$group)[i] }_L_quast.Rmd")) +} diff --git a/assets/report/scripts/_quast_page.R b/assets/report/scripts/_quast_page.R new file mode 100644 index 00000000..23b688da --- /dev/null +++ b/assets/report/scripts/_quast_page.R @@ -0,0 +1,131 @@ +# This generates a tab-page for each sample + +for (i in 1:length(unique(quast_stats$group))) { + cat(paste0('### ', unique(quast_stats$group)[i], '\n\n'), + paste0('::: {.panel-tabset} \n\n'), + paste0('#### Tabular \n\n'), + paste0('::::: {.panel-tabset} \n\n'), + paste0('##### Overview \n\n'), + quast_stats |> + filter(group == unique(quast_stats$group)[i]) |> + dplyr::select(sample, stage, stat, value) |> + pivot_wider(names_from = "stat", values_from = "value",id_cols = c(sample, stage)) |> + dplyr::arrange(stage, sample) |> + dplyr::select( + sample, + stage, + `# contigs`, + `Largest contig`, + starts_with("# contigs ("), + `Total length`, + `Reference length` , + starts_with("Total length ("), + `GC (%)` + ) |> + gt::gt() |> + gt::cols_nanoplot(columns = starts_with("# contigs ("), + new_col_name = "Contigs_by_size", + new_col_label = gt::md("*# Contigs by size*")) |> + gt::cols_nanoplot(columns = starts_with("Total length ("), + new_col_name = "Total_length", + new_col_label = gt::md("*Total length*")) |> + gt::tab_footnote( + footnote = "Breaks are: contigs >= 0, 1kb, 5kb, 10kb, 25kb, 50kb", + locations = gt::cells_column_labels(columns = c(Contigs_by_size, Total_length))) |> + gt::cols_align(align = "center", columns = c(Contigs_by_size, Total_length)) |> + gt::cols_move(Contigs_by_size, "Largest contig") |> + gt::cols_move(Total_length, "Total length") |> + gt::fmt_auto() |> + gt::fmt_scientific(columns = c("Largest contig", "Total length", "Reference length")) |> + gt::opt_stylize(color = "gray") |> + gt::opt_table_font( + font = list( + gt::google_font(name = "Maven Pro"), + "rounded-sans" + )) |> + gt::as_raw_html() + , + paste0('\n\n'), + paste0('##### N/L 50/90 \n\n'), + paste0('N50: length of a contig, such that all the contigs of at least the same length together cover at least 50% of the assembly.
N90: same as N50 but the contigs cover 90% of the assembly.
NG 50/90: Similar to N50/90, but measures coverage of the reference.
L measures the number of contigs required to cover 50 (or 90) % of the assembly length.
LG measures the number of contigs to cover the given percentage of the reference.\n\n'), + quast_stats |> + filter(group == unique(quast_stats$group)[i]) |> + filter(str_detect(stat, "[NLG].*[59]0")) |> + dplyr::select(sample, stage, stat, value) |> + pivot_wider(names_from = "stat", values_from = "value",id_cols = c(sample, stage)) |> + dplyr::arrange(stage, sample) |> + gt::gt() |> + gt::fmt_auto() |> + gt::fmt_scientific(columns = starts_with("N")) |> + gt::opt_stylize(color = "gray") |> + gt::opt_table_font( + font = list( + gt::google_font(name = "Maven Pro"), + "rounded-sans" + )) |> + gt::as_raw_html() + , + paste0('\n\n'), + paste0('##### Comparison to ref \n\n'), + quast_stats |> + filter(group == unique(quast_stats$group)[i]) |> + filter( + stat %in% c( + "Reference mapped (%)", + "Reference properly paired (%)", + "Reference avg. coverage depth", + "Reference coverage >= 1x (%)", + "# misassemblies", + "# misassembled contigs", + "Misassembled contigs length", + "# local misassemblies" + ) + ) |> + dplyr::select(sample, stage, stat, value) |> + pivot_wider( + names_from = "stat", + values_from = "value", + id_cols = c(sample, stage) + ) |> + dplyr::arrange(stage, sample) |> + gt::gt() |> + gt::fmt_auto() |> + gt::opt_stylize(color = "gray") |> + gt::opt_table_font( + font = list( + gt::google_font(name = "Maven Pro"), + "rounded-sans" + )) |> + gt::as_raw_html(), + paste0(':::::'), # tables tabset + paste0('\n\n'), + paste0('#### Visual'), + paste0('\n\n'), + paste0('::::: {.panel-tabset} \n\n'), + paste0('\n\n'), + paste0('##### Contigs by size\n'), + knitr::knit_child(glue::glue('quast_files/contigs/_{ unique(quast_stats$group)[i] }_quast.Rmd'), + envir = globalenv(), + quiet = TRUE), + paste0('\n\n'), + paste0('##### N 50 / 90\n'), + paste0('\n\n'), + paste0('N50: length of a contig, such that all the contigs of at least the same length together cover at least 50% of the assembly.
N90: same as N50 but the contigs cover 90% of the assembly.
NG 50/90: Similar to N50/90, but measures coverage of the reference.\n\n'), + knitr::knit_child(glue::glue('quast_files/NL_plots/_{ unique(quast_stats$group)[i] }_N_quast.Rmd'), + envir = globalenv(), + quiet = TRUE), + paste0('\n\n'), + paste0('##### L 50 / 90'), + paste0('\n\n'), + paste0('L measures the number of contigs required to cover 50 (or 90) % of the assembly length, LG measures the number of contigs to cover the given percentage of the reference.\n\n'), + knitr::knit_child(glue::glue('quast_files/NL_plots/_{ unique(quast_stats$group)[i] }_L_quast.Rmd'), + envir = globalenv(), + quiet = TRUE), + paste0('\n\n'), + paste0('\n\n'), + paste0(':::::'), # plots tabset + paste0('\n\n'), + paste0(':::'), # group tabsets + paste0('\n\n'), + sep = "") +} From 26aacd40a7948d51856faeb7cf1f4ce2f566d79d Mon Sep 17 00:00:00 2001 From: Niklas Schandry Date: Thu, 23 Oct 2025 10:20:31 +0200 Subject: [PATCH 064/162] update assemble subworkflow: split flye assembly process by read type --- subworkflows/local/assemble/main.nf | 1 + 1 file changed, 1 insertion(+) diff --git a/subworkflows/local/assemble/main.nf b/subworkflows/local/assemble/main.nf index 1d65a671..d34836ad 100644 --- a/subworkflows/local/assemble/main.nf +++ b/subworkflows/local/assemble/main.nf @@ -223,6 +223,7 @@ workflow ASSEMBLE { ] } .set { flye_assemblies } + flye_assemblies.dump(tag: "Assemble: Flye assemblies") // Join hifiasm hifi assemblies back to main channel From 71d8300c901c18f574a05544baf59861fdf18cae Mon Sep 17 00:00:00 2001 From: Niklas Schandry Date: Thu, 23 Oct 2025 10:25:20 +0200 Subject: [PATCH 065/162] remove schema.md --- schema.md | 145 ------------------------------------------------------ 1 file changed, 145 deletions(-) delete mode 100644 schema.md diff --git a/schema.md b/schema.md deleted file mode 100644 index 378031bb..00000000 --- a/schema.md +++ /dev/null @@ -1,145 +0,0 @@ -# nf-core/genomeassembler pipeline parameters - -Assemble genomes from long ONT or pacbio HiFi reads - -## Input/output options - -Define where the pipeline should find input data and save output data. - -| Parameter | Description | Type | Default | Required | Hidden | -| ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------- | ------- | -------- | ------ | -| `input` | Path to comma-separated file containing information about the samples in the experiment.
HelpYou 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/genomeassembler/usage#samplesheet-input).
| `string` | | True | | -| `outdir` | The output directory where the results will be saved. You have to use absolute paths to storage on Cloud infrastructure. | `string` | | True | | -| `email` | Email address for completion summary.
HelpSet this parameter to your e-mail address to get a summary e-mail with details of the run sent to you when the workflow exits. If set in your user config file | -| (`~/.nextflow/config`) then you don't need to specify this on the command line for every run.
| `string` | | | | - -## Institutional config options - -Parameters used to describe centralised config profiles. These should not be edited. - -| Parameter | Description | Type | Default | Required | Hidden | -| ------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------- | ------- | -------- | ------ | -| `custom_config_version` | Git commit id for Institutional configs. | `string` | master | | True | -| `custom_config_base` | Base directory for Institutional configs.
HelpIf you're running offline, Nextflow will not be able to fetch the institutional config files from the internet. If you don't need them, then this is not | -| a problem. If you do need them, you should download the files from the repo and tell Nextflow where to find them with this parameter.
| `string` | https://raw.githubusercontent.com/nf-core/configs/master | | True | -| `config_profile_name` | Institutional config name. | `string` | | | True | -| `config_profile_description` | Institutional config description. | `string` | | | True | -| `config_profile_contact` | Institutional config contact information. | `string` | | | True | -| `config_profile_url` | Institutional config URL link. | `string` | | | True | - -## Generic options - -Less common options for the pipeline, typically set in a config file. - -| Parameter | Description | Type | Default | Required | Hidden | -| --------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------- | -------------------------------------------------------- | -------- | ------ | -| `version` | Display version and exit. | `boolean` | | | True | -| `publish_dir_mode` | Method used to save pipeline results to output directory.
HelpThe Nextflow `publishDir` option specifies which intermediate files should be saved to the output directory. This option tells the pipeline | -| what method should be used to move these files. See [Nextflow docs](https://www.nextflow.io/docs/latest/process.html#publishdir) for details.
| `string` | copy | | True | -| `email_on_fail` | Email address for completion summary, only when pipeline fails.
HelpAn email address to send a summary email to when the pipeline is completed - ONLY sent if the pipeline does not exit | -| successfully.
| `string` | | | True | -| `plaintext_email` | Send plain-text email instead of HTML. | `boolean` | | | True | -| `monochrome_logs` | Do not use coloured log outputs. | `boolean` | | | True | -| `hook_url` | Incoming hook URL for messaging service
HelpIncoming hook URL for messaging service. Currently, MS Teams and Slack are supported.
| `string` | | | True | -| `validate_params` | Boolean whether to validate parameters against the schema at runtime | `boolean` | True | | True | -| `pipelines_testdata_base_path` | Base URL or local path to location of pipeline test dataset files | `string` | https://raw.githubusercontent.com/nf-core/test-datasets/ | | True | - -## Reference Parameters - -Options controlling pipeline behavior - -| Parameter | Description | Type | Default | Required | Hidden | -| ----------- | ------------------------------------------ | --------- | ------- | -------- | ------ | -| `ref_fasta` | Path to reference genome seqeunce (fasta) | `string` | | | | -| `ref_gff` | Path to reference genome annotations (gff) | `string` | | | | -| `use_ref` | use reference genome | `boolean` | | | True | - -## Assembly options - -Options controlling assembly - -| Parameter | Description | Type | Default | Required | Hidden | -| ------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------- | ----------- | -------- | ------ | -| `strategy` | Assembly strategy to use. Valid choices are `'single'`, `'hybrid'` and `'scaffold'` | `string` | single | | | -| `assembler` | Assembler to use. Valid choices depend on strategy; for single either `flye` or `hifiasm`, hybrid can be done with `hifiasm` and for scaffolded assembly provide the names of the assemblers separated with an underscore. The first assembler will | -| be used for ONT reads, the second for HiFi reads. | `string` | hifiasm | | | -| `assembly_scaffolding_order` | When strategy is "scaffold", which assembly should be scaffolded onto which? | `string` | ont_on_hifi | | | -| `genome_size` | expected genome size, optional | `string` | | | | -| `flye_mode` | flye mode | `string` | --nano-hq | | | -| `flye_args` | additional args for flye | `string` | | | | -| `hifiasm_args` | Extra arguments passed to `hifiasm` | `string` | "" | | | - -## Long-read preprocessing - -| Parameter | Description | Type | Default | Required | Hidden | -| --------------------- | -------------------------------------------------------- | --------- | ------- | -------- | ------ | -| `ontreads` | Path to ONT reads | `string` | | | | -| `ont_collect` | Collect ONT reads from several files? | `boolean` | | | | -| `ont_trim` | Trim ont reads with fastplong? | `boolean` | | | | -| `ont_adapters` | Adaptors for ONT read-trimming | `string` | [] | | | -| `ont_fastplong_args` | Additional args to be passed to fastplong for ONT reads | `string` | "" | | | -| `hifireads` | Path to HiFi reads | `string` | | | | -| `hifi_trim` | Trim HiFi reads with fastplonng | `boolean` | | | | -| `hifi_adapters` | Adaptors for HiFi read-trimming | `string` | [] | | | -| `hifi_fastplong_args` | Additional args to be passed to fastplong for HiFi reads | `string` | "" | | | -| `jellyfish` | Run jellyfish and genomescope (recommended) | `boolean` | | | | -| `jellyfish_k` | Value of k used during k-mer analysis with jellyfish | `integer` | 21 | | | -| `dump` | dump jellyfish output | `boolean` | | | | - -## Polishing options - -Polishing options - -| Parameter | Description | Type | Default | Required | Hidden | -| --------------- | ------------------------------------------------ | --------- | ------- | -------- | ------ | -| `polish_pilon` | Polish assembly with pilon? Requires short reads | `boolean` | | | | -| `polish_medaka` | Polish assembly with medaka (ONT only) | `boolean` | | | | -| `medaka_model` | model to use with medaka | `string` | "" | | | - -## Scaffolding options - -Scaffolding options - -| Parameter | Description | Type | Default | Required | Hidden | -| --------------------- | ------------------------------------------ | --------- | ------- | -------- | ------ | -| `scaffold_longstitch` | Scaffold with longstitch? | `boolean` | | | | -| `scaffold_links` | Scaffolding with links? | `boolean` | | | | -| `scaffold_ragtag` | Scaffold with ragtag (requires reference)? | `boolean` | | | | - -## QC options - -Options for QC tools - -| Parameter | Description | Type | Default | Required | Hidden | -| ------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------- | --------- | ----------------- | -------- | ------ | -| `merqury` | Run merqury (if short reads are provided) | `boolean` | True | | | -| `qc_reads` | Long reads that should be used for QC when both ONT and HiFi reads are provided. Options are `'ont'` or `'hifi'` | `string` | ont | | | -| `busco` | Run BUSCO? | `boolean` | | | | -| `busco_db` | Path to busco db (optional) | `string` | | | | -| `busco_lineage` | Busco lineage to use | `string` | brassicales_odb10 | | | -| `quast` | Run quast | `boolean` | | | | -| `ref_map_bam` | A mapping (bam) of reads mapped to the reference can be provided for QC. If provided alignment to reference fasta will not run | `string` | | | | -| `assembly` | Can be used to proved existing assembly will skip assembly and perform downstream steps including qc | `string` | | | | -| `assembly_map_bam` | A mapping (bam) of reads mapped to the provided assembly can be specified for QC. If provided alignment to the provided assembly fasta will not run | `string` | | | | - -## Annotations options - -Options controlling annotation liftover - -| Parameter | Description | Type | Default | Required | Hidden | -| ------------------ | ----------------------------------------- | --------- | ------- | -------- | ------ | -| `lift_annotations` | Lift-over annotations (requires ref_gff)? | `boolean` | True | | | - -## Short read options - -Options for short reads - -| Parameter | Description | Type | Default | Required | Hidden | -| ----------------- | ------------------------------- | --------- | ------- | -------- | ------ | -| `use_short_reads` | Use short reads? | `boolean` | | | | -| `shortread_trim` | Trim short reads? | `boolean` | | | | -| `meryl_k` | kmer length for meryl / merqury | `integer` | 21 | | | -| `shortread_F` | Path to forward short reads | `string` | | | | -| `shortread_R` | Path to reverse short reads | `string` | | | | -| `paired` | Are shortreads paired? | `string` | | | | From 7cf82b4cbd4e6a55b2a1735cc134fe2aafeae85a Mon Sep 17 00:00:00 2001 From: Niklas Schandry Date: Thu, 23 Oct 2025 16:02:52 +0200 Subject: [PATCH 066/162] minor cleanup --- subworkflows/local/assemble/main.nf | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/subworkflows/local/assemble/main.nf b/subworkflows/local/assemble/main.nf index d34836ad..57567838 100644 --- a/subworkflows/local/assemble/main.nf +++ b/subworkflows/local/assemble/main.nf @@ -152,8 +152,6 @@ workflow ASSEMBLE { ch_main_assemble_hifi_hifiasm.dump(tag: "Assemble: hifiasm HIFI inputs") - - HIFIASM(ch_main_assemble_hifi_hifiasm .map { it -> [ @@ -176,7 +174,7 @@ workflow ASSEMBLE { /* Assemble hifiasm_ont branch: Single branch with hifiasm and only ont reads - Scaffold samples where assembler1 (ont assembler) is hifiasm + Scaffold branch where assembler1 (ont assembler) is hifiasm */ ch_main_assemble_branched .single From acffbdcd2ff1ca781b8bf4333232978a995d5696 Mon Sep 17 00:00:00 2001 From: Niklas Schandry Date: Mon, 24 Nov 2025 10:14:27 +0100 Subject: [PATCH 067/162] remove param from quast module --- modules/local/quast/main.nf | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/local/quast/main.nf b/modules/local/quast/main.nf index 118c1b3a..dd3b9d13 100644 --- a/modules/local/quast/main.nf +++ b/modules/local/quast/main.nf @@ -25,7 +25,7 @@ process QUAST { def prefix = task.ext.prefix ?: "${meta.id}" def features = use_gff ? "--features ${gff}" : '' def reference = use_fasta ? "-r ${fasta}" : '' - def reference_bam = params.use_ref ? "--ref-bam ${ref_bam}" : '' + def reference_bam = ref_bam ? "--ref-bam ${ref_bam}" : '' """ quast.py \\ From 1c8c7d0b4473a596c8e4a0accb3307722c090632 Mon Sep 17 00:00:00 2001 From: Niklas Schandry Date: Mon, 24 Nov 2025 10:59:38 +0100 Subject: [PATCH 068/162] switch to fastp for shortread trimming --- modules.json | 5 + modules/nf-core/fastp/environment.yml | 8 + modules/nf-core/fastp/main.nf | 124 ++ modules/nf-core/fastp/meta.yml | 127 ++ modules/nf-core/fastp/tests/main.nf.test | 661 +++++++++ modules/nf-core/fastp/tests/main.nf.test.snap | 1250 +++++++++++++++++ .../fastp/tests/nextflow.interleaved.config | 5 + .../fastp/tests/nextflow.save_failed.config | 5 + .../local/prepare/prepare_shortreads/main.nf | 16 +- 9 files changed, 2193 insertions(+), 8 deletions(-) create mode 100644 modules/nf-core/fastp/environment.yml create mode 100644 modules/nf-core/fastp/main.nf create mode 100644 modules/nf-core/fastp/meta.yml create mode 100644 modules/nf-core/fastp/tests/main.nf.test create mode 100644 modules/nf-core/fastp/tests/main.nf.test.snap create mode 100644 modules/nf-core/fastp/tests/nextflow.interleaved.config create mode 100644 modules/nf-core/fastp/tests/nextflow.save_failed.config diff --git a/modules.json b/modules.json index 5d7acd16..0fff4232 100644 --- a/modules.json +++ b/modules.json @@ -10,6 +10,11 @@ "git_sha": "05954dab2ff481bcb999f24455da29a5828af08d", "installed_by": ["modules"] }, + "fastp": { + "branch": "master", + "git_sha": "d9ec4ef289ad39b8a662a7a12be50409b11df84b", + "installed_by": ["modules"] + }, "fastplong": { "branch": "master", "git_sha": "88869fced9dc0c56043eff2dc748a5f47c57495d", diff --git a/modules/nf-core/fastp/environment.yml b/modules/nf-core/fastp/environment.yml new file mode 100644 index 00000000..0c36eed2 --- /dev/null +++ b/modules/nf-core/fastp/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: + # renovate: datasource=conda depName=bioconda/fastp + - bioconda::fastp=1.0.1 diff --git a/modules/nf-core/fastp/main.nf b/modules/nf-core/fastp/main.nf new file mode 100644 index 00000000..85013f5d --- /dev/null +++ b/modules/nf-core/fastp/main.nf @@ -0,0 +1,124 @@ +process FASTP { + 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/52/527b18847a97451091dba07a886b24f17f742a861f9f6c9a6bfb79d4f1f3bf9d/data' : + 'community.wave.seqera.io/library/fastp:1.0.1--c8b87fe62dcc103c' }" + + input: + tuple val(meta), path(reads), path(adapter_fasta) + val discard_trimmed_pass + val save_trimmed_fail + val save_merged + + output: + tuple val(meta), path('*.fastp.fastq.gz') , optional:true, emit: reads + tuple val(meta), path('*.json') , emit: json + tuple val(meta), path('*.html') , emit: html + tuple val(meta), path('*.log') , emit: log + tuple val(meta), path('*.fail.fastq.gz') , optional:true, emit: reads_fail + tuple val(meta), path('*.merged.fastq.gz'), optional:true, emit: reads_merged + 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 adapter_list = adapter_fasta ? "--adapter_fasta ${adapter_fasta}" : "" + def fail_fastq = save_trimmed_fail && meta.single_end ? "--failed_out ${prefix}.fail.fastq.gz" : save_trimmed_fail && !meta.single_end ? "--failed_out ${prefix}.paired.fail.fastq.gz --unpaired1 ${prefix}_1.fail.fastq.gz --unpaired2 ${prefix}_2.fail.fastq.gz" : '' + def out_fq1 = discard_trimmed_pass ?: ( meta.single_end ? "--out1 ${prefix}.fastp.fastq.gz" : "--out1 ${prefix}_1.fastp.fastq.gz" ) + def out_fq2 = discard_trimmed_pass ?: "--out2 ${prefix}_2.fastp.fastq.gz" + // Added soft-links to original fastqs for consistent naming in MultiQC + // Use single ended for interleaved. Add --interleaved_in in config. + if ( task.ext.args?.contains('--interleaved_in') ) { + """ + [ ! -f ${prefix}.fastq.gz ] && ln -sf $reads ${prefix}.fastq.gz + + fastp \\ + --stdout \\ + --in1 ${prefix}.fastq.gz \\ + --thread $task.cpus \\ + --json ${prefix}.fastp.json \\ + --html ${prefix}.fastp.html \\ + $adapter_list \\ + $fail_fastq \\ + $args \\ + 2>| >(tee ${prefix}.fastp.log >&2) \\ + | gzip -c > ${prefix}.fastp.fastq.gz + + cat <<-END_VERSIONS > versions.yml + "${task.process}": + fastp: \$(fastp --version 2>&1 | sed -e "s/fastp //g") + END_VERSIONS + """ + } else if (meta.single_end) { + """ + [ ! -f ${prefix}.fastq.gz ] && ln -sf $reads ${prefix}.fastq.gz + + fastp \\ + --in1 ${prefix}.fastq.gz \\ + $out_fq1 \\ + --thread $task.cpus \\ + --json ${prefix}.fastp.json \\ + --html ${prefix}.fastp.html \\ + $adapter_list \\ + $fail_fastq \\ + $args \\ + 2>| >(tee ${prefix}.fastp.log >&2) + + cat <<-END_VERSIONS > versions.yml + "${task.process}": + fastp: \$(fastp --version 2>&1 | sed -e "s/fastp //g") + END_VERSIONS + """ + } else { + def merge_fastq = save_merged ? "-m --merged_out ${prefix}.merged.fastq.gz" : '' + """ + [ ! -f ${prefix}_1.fastq.gz ] && ln -sf ${reads[0]} ${prefix}_1.fastq.gz + [ ! -f ${prefix}_2.fastq.gz ] && ln -sf ${reads[1]} ${prefix}_2.fastq.gz + fastp \\ + --in1 ${prefix}_1.fastq.gz \\ + --in2 ${prefix}_2.fastq.gz \\ + $out_fq1 \\ + $out_fq2 \\ + --json ${prefix}.fastp.json \\ + --html ${prefix}.fastp.html \\ + $adapter_list \\ + $fail_fastq \\ + $merge_fastq \\ + --thread $task.cpus \\ + --detect_adapter_for_pe \\ + $args \\ + 2>| >(tee ${prefix}.fastp.log >&2) + + cat <<-END_VERSIONS > versions.yml + "${task.process}": + fastp: \$(fastp --version 2>&1 | sed -e "s/fastp //g") + END_VERSIONS + """ + } + + stub: + def prefix = task.ext.prefix ?: "${meta.id}" + def is_single_output = task.ext.args?.contains('--interleaved_in') || meta.single_end + def touch_reads = (discard_trimmed_pass) ? "" : (is_single_output) ? "echo '' | gzip > ${prefix}.fastp.fastq.gz" : "echo '' | gzip > ${prefix}_1.fastp.fastq.gz ; echo '' | gzip > ${prefix}_2.fastp.fastq.gz" + def touch_merged = (!is_single_output && save_merged) ? "echo '' | gzip > ${prefix}.merged.fastq.gz" : "" + def touch_fail_fastq = (!save_trimmed_fail) ? "" : meta.single_end ? "echo '' | gzip > ${prefix}.fail.fastq.gz" : "echo '' | gzip > ${prefix}.paired.fail.fastq.gz ; echo '' | gzip > ${prefix}_1.fail.fastq.gz ; echo '' | gzip > ${prefix}_2.fail.fastq.gz" + """ + $touch_reads + $touch_fail_fastq + $touch_merged + touch "${prefix}.fastp.json" + touch "${prefix}.fastp.html" + touch "${prefix}.fastp.log" + + cat <<-END_VERSIONS > versions.yml + "${task.process}": + fastp: \$(fastp --version 2>&1 | sed -e "s/fastp //g") + END_VERSIONS + """ +} diff --git a/modules/nf-core/fastp/meta.yml b/modules/nf-core/fastp/meta.yml new file mode 100644 index 00000000..324025fe --- /dev/null +++ b/modules/nf-core/fastp/meta.yml @@ -0,0 +1,127 @@ +name: fastp +description: Perform adapter/quality trimming on sequencing reads +keywords: + - trimming + - quality control + - fastq +tools: + - fastp: + description: | + A tool designed to provide fast all-in-one preprocessing for FastQ files. This tool is developed in C++ with multithreading supported to afford high performance. + documentation: https://github.com/OpenGene/fastp + doi: 10.1093/bioinformatics/bty560 + licence: ["MIT"] + identifier: biotools:fastp +input: + - - meta: + type: map + description: | + Groovy Map containing sample information. Use 'single_end: true' to specify single ended or interleaved FASTQs. Use 'single_end: false' for paired-end reads. + e.g. [ id:'test', single_end:false ] + - reads: + type: file + description: | + List of input FastQ files of size 1 and 2 for single-end and paired-end data, + respectively. If you wish to run interleaved paired-end data, supply as single-end data + but with `--interleaved_in` in your `modules.conf`'s `ext.args` for the module. + ontologies: [] + - adapter_fasta: + type: file + description: File in FASTA format containing possible adapters to remove. + pattern: "*.{fasta,fna,fas,fa}" + ontologies: [] + - discard_trimmed_pass: + type: boolean + description: | + Specify true to not write any reads that pass trimming thresholds. + This can be used to use fastp for the output report only. + - save_trimmed_fail: + type: boolean + description: Specify true to save files that failed to pass trimming thresholds + ending in `*.fail.fastq.gz` + - save_merged: + type: boolean + description: Specify true to save all merged reads to a file ending in `*.merged.fastq.gz` +output: + reads: + - - meta: + type: map + description: | + Groovy Map containing sample information + e.g. [ id:'test', single_end:false ] + - "*.fastp.fastq.gz": + type: file + description: The trimmed/modified/unmerged fastq reads + pattern: "*fastp.fastq.gz" + ontologies: + - edam: http://edamontology.org/format_3989 # GZIP format + json: + - - meta: + type: map + description: | + Groovy Map containing sample information + e.g. [ id:'test', single_end:false ] + - "*.json": + type: file + description: Results in JSON format + pattern: "*.json" + ontologies: + - edam: http://edamontology.org/format_3464 # JSON + html: + - - meta: + type: map + description: | + Groovy Map containing sample information + e.g. [ id:'test', single_end:false ] + - "*.html": + type: file + description: Results in HTML format + pattern: "*.html" + ontologies: [] + log: + - - meta: + type: map + description: | + Groovy Map containing sample information + e.g. [ id:'test', single_end:false ] + - "*.log": + type: file + description: fastq log file + pattern: "*.log" + ontologies: [] + reads_fail: + - - meta: + type: map + description: | + Groovy Map containing sample information + e.g. [ id:'test', single_end:false ] + - "*.fail.fastq.gz": + type: file + description: Reads the failed the preprocessing + pattern: "*fail.fastq.gz" + ontologies: + - edam: http://edamontology.org/format_3989 # GZIP format + reads_merged: + - - meta: + type: map + description: | + Groovy Map containing sample information + e.g. [ id:'test', single_end:false ] + - "*.merged.fastq.gz": + type: file + description: Reads that were successfully merged + pattern: "*.{merged.fastq.gz}" + ontologies: [] + versions: + - versions.yml: + type: file + description: File containing software versions + pattern: "versions.yml" + ontologies: + - edam: http://edamontology.org/format_3750 # YAML +authors: + - "@drpatelh" + - "@kevinmenden" +maintainers: + - "@drpatelh" + - "@kevinmenden" diff --git a/modules/nf-core/fastp/tests/main.nf.test b/modules/nf-core/fastp/tests/main.nf.test new file mode 100644 index 00000000..5125705c --- /dev/null +++ b/modules/nf-core/fastp/tests/main.nf.test @@ -0,0 +1,661 @@ +nextflow_process { + + name "Test Process FASTP" + script "../main.nf" + process "FASTP" + tag "modules" + tag "modules_nfcore" + tag "fastp" + + test("test_fastp_single_end") { + + when { + + process { + """ + adapter_fasta = [] // empty list for no adapter file! + discard_trimmed_pass = false + save_trimmed_fail = false + save_merged = false + + input[0] = Channel.of([ + [ id:'test', single_end:true ], + [ file(params.modules_testdata_base_path + 'genomics/sarscov2/illumina/fastq/test_1.fastq.gz', checkIfExists: true) ], + adapter_fasta + ]) + input[1] = discard_trimmed_pass + input[2] = save_trimmed_fail + input[3] = save_merged + """ + } + } + + then { + assertAll( + { assert process.success }, + { assert path(process.out.html.get(0).get(1)).getText().contains("single end (151 cycles)") }, + { assert path(process.out.log.get(0).get(1)).getText().contains("reads passed filter: 99") }, + { assert snapshot( + process.out.reads, + process.out.reads_fail, + process.out.reads_merged, + process.out.versions).match() + } + ) + } + } + + test("test_fastp_paired_end") { + + when { + + process { + """ + adapter_fasta = [] + discard_trimmed_pass = false + save_trimmed_fail = false + save_merged = false + + input[0] = Channel.of([ + [ id:'test', single_end:false ], // meta map + [ + file(params.modules_testdata_base_path + 'genomics/sarscov2/illumina/fastq/test_1.fastq.gz', checkIfExists: true), + file(params.modules_testdata_base_path + 'genomics/sarscov2/illumina/fastq/test_2.fastq.gz', checkIfExists: true) + ], + adapter_fasta + ]) + input[1] = discard_trimmed_pass + input[2] = save_trimmed_fail + input[3] = save_merged + """ + } + } + + then { + assertAll( + { assert process.success }, + { assert path(process.out.html.get(0).get(1)).getText().contains("The input has little adapter percentage (~0.000000%), probably it's trimmed before.") }, + { assert path(process.out.log.get(0).get(1)).getText().contains("Q30 bases: 12281(88.3716%)") }, + { assert snapshot( + process.out.reads, + process.out.reads_fail, + process.out.reads_merged, + process.out.versions).match() } + ) + } + } + + test("fastp test_fastp_interleaved") { + + config './nextflow.interleaved.config' + when { + process { + """ + adapter_fasta = [] + discard_trimmed_pass = false + save_trimmed_fail = false + save_merged = false + + input[0] = Channel.of([ + [ id:'test', single_end:true ], // meta map + [ file(params.modules_testdata_base_path + 'genomics/sarscov2/illumina/fastq/test_interleaved.fastq.gz', checkIfExists: true) ], + adapter_fasta + ]) + input[1] = discard_trimmed_pass + input[2] = save_trimmed_fail + input[3] = save_merged + """ + } + } + + then { + assertAll( + { assert process.success }, + { assert path(process.out.html.get(0).get(1)).getText().contains("paired end (151 cycles + 151 cycles)") }, + { assert path(process.out.log.get(0).get(1)).getText().contains("reads passed filter: 162") }, + { assert process.out.reads_fail == [] }, + { assert process.out.reads_merged == [] }, + { assert snapshot( + process.out.reads, + process.out.versions).match() } + ) + } + } + + test("test_fastp_single_end_trim_fail") { + + when { + + process { + """ + adapter_fasta = [] + discard_trimmed_pass = false + save_trimmed_fail = true + save_merged = false + + input[0] = Channel.of([ + [ id:'test', single_end:true ], // meta map + [ file(params.modules_testdata_base_path + 'genomics/sarscov2/illumina/fastq/test_1.fastq.gz', checkIfExists: true) ], + adapter_fasta + ]) + input[1] = discard_trimmed_pass + input[2] = save_trimmed_fail + input[3] = save_merged + """ + } + } + + then { + assertAll( + { assert process.success }, + { assert path(process.out.html.get(0).get(1)).getText().contains("single end (151 cycles)") }, + { assert path(process.out.log.get(0).get(1)).getText().contains("reads passed filter: 99") }, + { assert snapshot( + process.out.reads, + process.out.reads_fail, + process.out.reads_merged, + process.out.versions).match() } + ) + } + } + + test("test_fastp_paired_end_trim_fail") { + + config './nextflow.save_failed.config' + when { + process { + """ + adapter_fasta = [] + discard_trimmed_pass = false + save_trimmed_fail = true + save_merged = false + + input[0] = Channel.of([ + [ id:'test', single_end:false ], // meta map + [ + file(params.modules_testdata_base_path + 'genomics/sarscov2/illumina/fastq/test_1.fastq.gz', checkIfExists: true), + file(params.modules_testdata_base_path + 'genomics/sarscov2/illumina/fastq/test_2.fastq.gz', checkIfExists: true) + ], + adapter_fasta + ]) + input[1] = discard_trimmed_pass + input[2] = save_trimmed_fail + input[3] = save_merged + """ + } + } + + then { + assertAll( + { assert process.success }, + { assert path(process.out.html.get(0).get(1)).getText().contains("The input has little adapter percentage (~0.000000%), probably it's trimmed before.") }, + { assert path(process.out.log.get(0).get(1)).getText().contains("reads passed filter: 162") }, + { assert snapshot( + process.out.reads, + process.out.reads_fail, + process.out.reads_merged, + process.out.versions).match() } + ) + } + } + + test("test_fastp_paired_end_merged") { + + when { + process { + """ + adapter_fasta = [] + discard_trimmed_pass = false + save_trimmed_fail = false + save_merged = true + + input[0] = Channel.of([ + [ id:'test', single_end:false ], // meta map + [ + file(params.modules_testdata_base_path + 'genomics/sarscov2/illumina/fastq/test_1.fastq.gz', checkIfExists: true), + file(params.modules_testdata_base_path + 'genomics/sarscov2/illumina/fastq/test_2.fastq.gz', checkIfExists: true) + ], + adapter_fasta + ]) + input[1] = discard_trimmed_pass + input[2] = save_trimmed_fail + input[3] = save_merged + """ + } + } + + then { + assertAll( + { assert process.success }, + { assert path(process.out.html.get(0).get(1)).getText().contains("The input has little adapter percentage (~0.000000%), probably it's trimmed before.") }, + { assert path(process.out.log.get(0).get(1)).getText().contains("total reads: 75") }, + { assert snapshot( + process.out.reads, + process.out.reads_fail, + process.out.reads_merged, + process.out.versions).match() }, + ) + } + } + + test("test_fastp_paired_end_merged_adapterlist") { + + when { + process { + """ + adapter_fasta = file(params.modules_testdata_base_path + 'delete_me/fastp/adapters.fasta', checkIfExists: true) + discard_trimmed_pass = false + save_trimmed_fail = false + save_merged = true + + input[0] = Channel.of([ + [ id:'test', single_end:false ], // meta map + [ + file(params.modules_testdata_base_path + 'genomics/sarscov2/illumina/fastq/test_1.fastq.gz', checkIfExists: true), + file(params.modules_testdata_base_path + 'genomics/sarscov2/illumina/fastq/test_2.fastq.gz', checkIfExists: true) + ], + adapter_fasta + ]) + input[1] = false + input[2] = false + input[3] = true + """ + } + } + + then { + assertAll( + { assert process.success }, + { assert path(process.out.html.get(0).get(1)).getText().contains("
") }, + { assert path(process.out.log.get(0).get(1)).getText().contains("total bases: 13683") }, + { assert snapshot( + process.out.reads, + process.out.reads_fail, + process.out.reads_merged, + process.out.versions).match() } + ) + } + } + + test("test_fastp_single_end_qc_only") { + + when { + process { + """ + adapter_fasta = [] + discard_trimmed_pass = true + save_trimmed_fail = false + save_merged = false + + input[0] = Channel.of([ + [ id:'test', single_end:true ], + [ file(params.modules_testdata_base_path + 'genomics/sarscov2/illumina/fastq/test_1.fastq.gz', checkIfExists: true) ], + adapter_fasta + ]) + + input[1] = discard_trimmed_pass + input[2] = save_trimmed_fail + input[3] = save_merged + """ + } + } + + then { + assertAll( + { assert process.success }, + { assert path(process.out.html.get(0).get(1)).getText().contains("single end (151 cycles)") }, + { assert path(process.out.log.get(0).get(1)).getText().contains("reads passed filter: 99") }, + { assert snapshot( + process.out.reads, + process.out.reads, + process.out.reads_fail, + process.out.reads_fail, + process.out.reads_merged, + process.out.reads_merged, + process.out.versions).match() } + ) + } + } + + test("test_fastp_paired_end_qc_only") { + + when { + process { + """ + adapter_fasta = [] + discard_trimmed_pass = true + save_trimmed_fail = false + save_merged = false + + input[0] = Channel.of([ + [ id:'test', single_end:false ], // meta map + [ + file(params.modules_testdata_base_path + 'genomics/sarscov2/illumina/fastq/test_1.fastq.gz', checkIfExists: true), + file(params.modules_testdata_base_path + 'genomics/sarscov2/illumina/fastq/test_2.fastq.gz', checkIfExists: true) + ], + adapter_fasta + ]) + input[1] = discard_trimmed_pass + input[2] = save_trimmed_fail + input[3] = save_merged + """ + } + } + + then { + assertAll( + { assert process.success }, + { assert path(process.out.html.get(0).get(1)).getText().contains("The input has little adapter percentage (~0.000000%), probably it's trimmed before.") }, + { assert path(process.out.log.get(0).get(1)).getText().contains("Q30 bases: 12281(88.3716%)") }, + { assert snapshot( + process.out.reads, + process.out.reads, + process.out.reads_fail, + process.out.reads_fail, + process.out.reads_merged, + process.out.reads_merged, + process.out.versions).match() } + ) + } + } + + test("test_fastp_single_end - stub") { + + options "-stub" + + when { + + process { + """ + adapter_fasta = [] + discard_trimmed_pass = false + save_trimmed_fail = false + save_merged = false + + input[0] = Channel.of([ + [ id:'test', single_end:true ], + [ file(params.modules_testdata_base_path + 'genomics/sarscov2/illumina/fastq/test_1.fastq.gz', checkIfExists: true) ], + adapter_fasta + ]) + input[1] = discard_trimmed_pass + input[2] = save_trimmed_fail + input[3] = save_merged + """ + } + } + + then { + assertAll( + { assert process.success }, + { assert snapshot(process.out).match() } + ) + } + } + + test("test_fastp_paired_end - stub") { + + options "-stub" + + when { + + process { + """ + adapter_fasta = [] + discard_trimmed_pass = false + save_trimmed_fail = false + save_merged = false + + input[0] = Channel.of([ + [ id:'test', single_end:false ], // meta map + [ file(params.modules_testdata_base_path + 'genomics/sarscov2/illumina/fastq/test_1.fastq.gz', checkIfExists: true), + file(params.modules_testdata_base_path + 'genomics/sarscov2/illumina/fastq/test_2.fastq.gz', checkIfExists: true) ], + adapter_fasta + ]) + input[1] = discard_trimmed_pass + input[2] = save_trimmed_fail + input[3] = save_merged + """ + } + } + + then { + assertAll( + { assert process.success }, + { assert snapshot(process.out).match() } + ) + } + } + + test("fastp - stub test_fastp_interleaved") { + + options "-stub" + + config './nextflow.interleaved.config' + when { + process { + """ + adapter_fasta = [] + discard_trimmed_pass = false + save_trimmed_fail = false + save_merged = false + + input[0] = Channel.of([ + [ id:'test', single_end:true ], // meta map + [ file(params.modules_testdata_base_path + 'genomics/sarscov2/illumina/fastq/test_interleaved.fastq.gz', checkIfExists: true) ], + adapter_fasta + ]) + input[1] = discard_trimmed_pass + input[2] = save_trimmed_fail + input[3] = save_merged + """ + } + } + + then { + assertAll( + { assert process.success }, + { assert snapshot(process.out).match() } + ) + } + } + + test("test_fastp_single_end_trim_fail - stub") { + + options "-stub" + + when { + + process { + """ + adapter_fasta = [] + discard_trimmed_pass = false + save_trimmed_fail = true + save_merged = false + + input[0] = Channel.of([ + [ id:'test', single_end:true ], // meta map + [ file(params.modules_testdata_base_path + 'genomics/sarscov2/illumina/fastq/test_1.fastq.gz', checkIfExists: true) ], + adapter_fasta + ]) + input[1] = discard_trimmed_pass + input[2] = save_trimmed_fail + input[3] = save_merged + """ + } + } + + then { + assertAll( + { assert process.success }, + { assert snapshot(process.out).match() } + ) + } + } + + test("test_fastp_paired_end_trim_fail - stub") { + + options "-stub" + + config './nextflow.save_failed.config' + when { + process { + """ + adapter_fasta = [] + discard_trimmed_pass = false + save_trimmed_fail = true + save_merged = false + + input[0] = Channel.of([ + [ id:'test', single_end:false ], // meta map + [ file(params.modules_testdata_base_path + 'genomics/sarscov2/illumina/fastq/test_1.fastq.gz', checkIfExists: true), + file(params.modules_testdata_base_path + 'genomics/sarscov2/illumina/fastq/test_2.fastq.gz', checkIfExists: true)], + adapter_fasta + ]) + input[1] = discard_trimmed_pass + input[2] = save_trimmed_fail + input[3] = save_merged + """ + } + } + + then { + assertAll( + { assert process.success }, + { assert snapshot(process.out).match() } + ) + } + } + + test("test_fastp_paired_end_merged - stub") { + + options "-stub" + + when { + process { + """ + adapter_fasta = [] + discard_trimmed_pass = false + save_trimmed_fail = false + save_merged = true + + input[0] = Channel.of([ + [ id:'test', single_end:false ], // meta map + [ file(params.modules_testdata_base_path + 'genomics/sarscov2/illumina/fastq/test_1.fastq.gz', checkIfExists: true), + file(params.modules_testdata_base_path + 'genomics/sarscov2/illumina/fastq/test_2.fastq.gz', checkIfExists: true) ], + adapter_fasta + ]) + input[1] = discard_trimmed_pass + input[2] = save_trimmed_fail + input[3] = save_merged + """ + } + } + + then { + assertAll( + { assert process.success }, + { assert snapshot(process.out).match() } + ) + } + } + + test("test_fastp_paired_end_merged_adapterlist - stub") { + + options "-stub" + + when { + process { + """ + adapter_fasta = file(params.modules_testdata_base_path + 'delete_me/fastp/adapters.fasta', checkIfExists: true) + discard_trimmed_pass = false + save_trimmed_fail = false + save_merged = true + + input[0] = Channel.of([ + [ id:'test', single_end:false ], // meta map + [ + file(params.modules_testdata_base_path + 'genomics/sarscov2/illumina/fastq/test_1.fastq.gz', checkIfExists: true), + file(params.modules_testdata_base_path + 'genomics/sarscov2/illumina/fastq/test_2.fastq.gz', checkIfExists: true) + ], + adapter_fasta + ]) + input[1] = discard_trimmed_pass + input[2] = save_trimmed_fail + input[3] = save_merged + """ + } + } + + then { + assertAll( + { assert process.success }, + { assert snapshot(process.out).match() } + ) + } + } + + test("test_fastp_single_end_qc_only - stub") { + + options "-stub" + + when { + process { + """ + adapter_fasta = [] + discard_trimmed_pass = true + save_trimmed_fail = false + save_merged = false + + input[0] = Channel.of([ + [ id:'test', single_end:true ], + [ file(params.modules_testdata_base_path + 'genomics/sarscov2/illumina/fastq/test_1.fastq.gz', checkIfExists: true) ], + adapter_fasta + ]) + input[1] = discard_trimmed_pass + input[2] = save_trimmed_fail + input[3] = save_merged + """ + } + } + + then { + assertAll( + { assert process.success }, + { assert snapshot(process.out).match() } + ) + } + } + + test("test_fastp_paired_end_qc_only - stub") { + + options "-stub" + + when { + process { + """ + adapter_fasta = [] + discard_trimmed_pass = true + save_trimmed_fail = false + save_merged = false + + input[0] = Channel.of([ + [ id:'test', single_end:false ], // meta map + [ file(params.modules_testdata_base_path + 'genomics/sarscov2/illumina/fastq/test_1.fastq.gz', checkIfExists: true), + file(params.modules_testdata_base_path + 'genomics/sarscov2/illumina/fastq/test_2.fastq.gz', checkIfExists: true) ], + adapter_fasta + ]) + input[1] = discard_trimmed_pass + input[2] = save_trimmed_fail + input[3] = save_merged + """ + } + } + + then { + assertAll( + { assert process.success }, + { assert snapshot(process.out).match() } + ) + } + } +} diff --git a/modules/nf-core/fastp/tests/main.nf.test.snap b/modules/nf-core/fastp/tests/main.nf.test.snap new file mode 100644 index 00000000..a30c680d --- /dev/null +++ b/modules/nf-core/fastp/tests/main.nf.test.snap @@ -0,0 +1,1250 @@ +{ + "test_fastp_single_end_qc_only - stub": { + "content": [ + { + "0": [ + + ], + "1": [ + [ + { + "id": "test", + "single_end": true + }, + "test.fastp.json:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "2": [ + [ + { + "id": "test", + "single_end": true + }, + "test.fastp.html:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "3": [ + [ + { + "id": "test", + "single_end": true + }, + "test.fastp.log:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "4": [ + + ], + "5": [ + + ], + "6": [ + "versions.yml:md5,c4974822658d02533e660fae343f281b" + ], + "html": [ + [ + { + "id": "test", + "single_end": true + }, + "test.fastp.html:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "json": [ + [ + { + "id": "test", + "single_end": true + }, + "test.fastp.json:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "log": [ + [ + { + "id": "test", + "single_end": true + }, + "test.fastp.log:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "reads": [ + + ], + "reads_fail": [ + + ], + "reads_merged": [ + + ], + "versions": [ + "versions.yml:md5,c4974822658d02533e660fae343f281b" + ] + } + ], + "meta": { + "nf-test": "0.9.2", + "nextflow": "25.04.6" + }, + "timestamp": "2025-09-11T09:55:42.073182" + }, + "test_fastp_paired_end": { + "content": [ + [ + [ + { + "id": "test", + "single_end": false + }, + [ + "test_1.fastp.fastq.gz:md5,67b2bbae47f073e05a97a9c2edce23c7", + "test_2.fastp.fastq.gz:md5,25cbdca08e2083dbd4f0502de6b62f39" + ] + ] + ], + [ + + ], + [ + + ], + [ + "versions.yml:md5,c4974822658d02533e660fae343f281b" + ] + ], + "meta": { + "nf-test": "0.9.2", + "nextflow": "25.04.6" + }, + "timestamp": "2025-09-19T16:23:12.436191" + }, + "test_fastp_paired_end_merged_adapterlist": { + "content": [ + [ + [ + { + "id": "test", + "single_end": false + }, + [ + "test_1.fastp.fastq.gz:md5,54b726a55e992a869fd3fa778afe1672", + "test_2.fastp.fastq.gz:md5,29d3b33b869f7b63417b8ff07bb128ba" + ] + ] + ], + [ + + ], + [ + [ + { + "id": "test", + "single_end": false + }, + "test.merged.fastq.gz:md5,c873bb1ab3fa859dcc47306465e749d5" + ] + ], + [ + "versions.yml:md5,c4974822658d02533e660fae343f281b" + ] + ], + "meta": { + "nf-test": "0.9.2", + "nextflow": "25.04.6" + }, + "timestamp": "2025-09-19T16:23:32.267735" + }, + "test_fastp_single_end_qc_only": { + "content": [ + [ + + ], + [ + + ], + [ + + ], + [ + + ], + [ + + ], + [ + + ], + [ + "versions.yml:md5,c4974822658d02533e660fae343f281b" + ] + ], + "meta": { + "nf-test": "0.9.2", + "nextflow": "25.04.6" + }, + "timestamp": "2025-09-19T16:23:36.149003" + }, + "test_fastp_paired_end_trim_fail": { + "content": [ + [ + [ + { + "id": "test", + "single_end": false + }, + [ + "test_1.fastp.fastq.gz:md5,6ff32a64c5188b9a9192be1398c262c7", + "test_2.fastp.fastq.gz:md5,db0cb7c9977e94ac2b4b446ebd017a8a" + ] + ] + ], + [ + [ + { + "id": "test", + "single_end": false + }, + [ + "test.paired.fail.fastq.gz:md5,409b687c734cedd7a1fec14d316e1366", + "test_1.fail.fastq.gz:md5,4f273cf3159c13f79e8ffae12f5661f6", + "test_2.fail.fastq.gz:md5,f97b9edefb5649aab661fbc9e71fc995" + ] + ] + ], + [ + + ], + [ + "versions.yml:md5,c4974822658d02533e660fae343f281b" + ] + ], + "meta": { + "nf-test": "0.9.2", + "nextflow": "25.04.6" + }, + "timestamp": "2025-09-19T16:23:24.23891" + }, + "fastp - stub test_fastp_interleaved": { + "content": [ + { + "0": [ + [ + { + "id": "test", + "single_end": true + }, + "test.fastp.fastq.gz:md5,68b329da9893e34099c7d8ad5cb9c940" + ] + ], + "1": [ + [ + { + "id": "test", + "single_end": true + }, + "test.fastp.json:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "2": [ + [ + { + "id": "test", + "single_end": true + }, + "test.fastp.html:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "3": [ + [ + { + "id": "test", + "single_end": true + }, + "test.fastp.log:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "4": [ + + ], + "5": [ + + ], + "6": [ + "versions.yml:md5,c4974822658d02533e660fae343f281b" + ], + "html": [ + [ + { + "id": "test", + "single_end": true + }, + "test.fastp.html:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "json": [ + [ + { + "id": "test", + "single_end": true + }, + "test.fastp.json:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "log": [ + [ + { + "id": "test", + "single_end": true + }, + "test.fastp.log:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "reads": [ + [ + { + "id": "test", + "single_end": true + }, + "test.fastp.fastq.gz:md5,68b329da9893e34099c7d8ad5cb9c940" + ] + ], + "reads_fail": [ + + ], + "reads_merged": [ + + ], + "versions": [ + "versions.yml:md5,c4974822658d02533e660fae343f281b" + ] + } + ], + "meta": { + "nf-test": "0.9.2", + "nextflow": "25.04.6" + }, + "timestamp": "2025-09-11T09:55:19.47199" + }, + "test_fastp_single_end - stub": { + "content": [ + { + "0": [ + [ + { + "id": "test", + "single_end": true + }, + "test.fastp.fastq.gz:md5,68b329da9893e34099c7d8ad5cb9c940" + ] + ], + "1": [ + [ + { + "id": "test", + "single_end": true + }, + "test.fastp.json:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "2": [ + [ + { + "id": "test", + "single_end": true + }, + "test.fastp.html:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "3": [ + [ + { + "id": "test", + "single_end": true + }, + "test.fastp.log:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "4": [ + + ], + "5": [ + + ], + "6": [ + "versions.yml:md5,c4974822658d02533e660fae343f281b" + ], + "html": [ + [ + { + "id": "test", + "single_end": true + }, + "test.fastp.html:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "json": [ + [ + { + "id": "test", + "single_end": true + }, + "test.fastp.json:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "log": [ + [ + { + "id": "test", + "single_end": true + }, + "test.fastp.log:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "reads": [ + [ + { + "id": "test", + "single_end": true + }, + "test.fastp.fastq.gz:md5,68b329da9893e34099c7d8ad5cb9c940" + ] + ], + "reads_fail": [ + + ], + "reads_merged": [ + + ], + "versions": [ + "versions.yml:md5,c4974822658d02533e660fae343f281b" + ] + } + ], + "meta": { + "nf-test": "0.9.2", + "nextflow": "25.04.6" + }, + "timestamp": "2025-09-11T09:55:09.617001" + }, + "test_fastp_paired_end_merged_adapterlist - stub": { + "content": [ + { + "0": [ + [ + { + "id": "test", + "single_end": false + }, + [ + "test_1.fastp.fastq.gz:md5,68b329da9893e34099c7d8ad5cb9c940", + "test_2.fastp.fastq.gz:md5,68b329da9893e34099c7d8ad5cb9c940" + ] + ] + ], + "1": [ + [ + { + "id": "test", + "single_end": false + }, + "test.fastp.json:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "2": [ + [ + { + "id": "test", + "single_end": false + }, + "test.fastp.html:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "3": [ + [ + { + "id": "test", + "single_end": false + }, + "test.fastp.log:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "4": [ + + ], + "5": [ + [ + { + "id": "test", + "single_end": false + }, + "test.merged.fastq.gz:md5,68b329da9893e34099c7d8ad5cb9c940" + ] + ], + "6": [ + "versions.yml:md5,c4974822658d02533e660fae343f281b" + ], + "html": [ + [ + { + "id": "test", + "single_end": false + }, + "test.fastp.html:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "json": [ + [ + { + "id": "test", + "single_end": false + }, + "test.fastp.json:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "log": [ + [ + { + "id": "test", + "single_end": false + }, + "test.fastp.log:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "reads": [ + [ + { + "id": "test", + "single_end": false + }, + [ + "test_1.fastp.fastq.gz:md5,68b329da9893e34099c7d8ad5cb9c940", + "test_2.fastp.fastq.gz:md5,68b329da9893e34099c7d8ad5cb9c940" + ] + ] + ], + "reads_fail": [ + + ], + "reads_merged": [ + [ + { + "id": "test", + "single_end": false + }, + "test.merged.fastq.gz:md5,68b329da9893e34099c7d8ad5cb9c940" + ] + ], + "versions": [ + "versions.yml:md5,c4974822658d02533e660fae343f281b" + ] + } + ], + "meta": { + "nf-test": "0.9.2", + "nextflow": "25.04.6" + }, + "timestamp": "2025-09-11T09:55:37.413738" + }, + "test_fastp_paired_end_merged - stub": { + "content": [ + { + "0": [ + [ + { + "id": "test", + "single_end": false + }, + [ + "test_1.fastp.fastq.gz:md5,68b329da9893e34099c7d8ad5cb9c940", + "test_2.fastp.fastq.gz:md5,68b329da9893e34099c7d8ad5cb9c940" + ] + ] + ], + "1": [ + [ + { + "id": "test", + "single_end": false + }, + "test.fastp.json:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "2": [ + [ + { + "id": "test", + "single_end": false + }, + "test.fastp.html:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "3": [ + [ + { + "id": "test", + "single_end": false + }, + "test.fastp.log:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "4": [ + + ], + "5": [ + [ + { + "id": "test", + "single_end": false + }, + "test.merged.fastq.gz:md5,68b329da9893e34099c7d8ad5cb9c940" + ] + ], + "6": [ + "versions.yml:md5,c4974822658d02533e660fae343f281b" + ], + "html": [ + [ + { + "id": "test", + "single_end": false + }, + "test.fastp.html:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "json": [ + [ + { + "id": "test", + "single_end": false + }, + "test.fastp.json:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "log": [ + [ + { + "id": "test", + "single_end": false + }, + "test.fastp.log:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "reads": [ + [ + { + "id": "test", + "single_end": false + }, + [ + "test_1.fastp.fastq.gz:md5,68b329da9893e34099c7d8ad5cb9c940", + "test_2.fastp.fastq.gz:md5,68b329da9893e34099c7d8ad5cb9c940" + ] + ] + ], + "reads_fail": [ + + ], + "reads_merged": [ + [ + { + "id": "test", + "single_end": false + }, + "test.merged.fastq.gz:md5,68b329da9893e34099c7d8ad5cb9c940" + ] + ], + "versions": [ + "versions.yml:md5,c4974822658d02533e660fae343f281b" + ] + } + ], + "meta": { + "nf-test": "0.9.2", + "nextflow": "25.04.6" + }, + "timestamp": "2025-09-11T09:55:32.965652" + }, + "test_fastp_paired_end_merged": { + "content": [ + [ + [ + { + "id": "test", + "single_end": false + }, + [ + "test_1.fastp.fastq.gz:md5,54b726a55e992a869fd3fa778afe1672", + "test_2.fastp.fastq.gz:md5,29d3b33b869f7b63417b8ff07bb128ba" + ] + ] + ], + [ + + ], + [ + [ + { + "id": "test", + "single_end": false + }, + "test.merged.fastq.gz:md5,c873bb1ab3fa859dcc47306465e749d5" + ] + ], + [ + "versions.yml:md5,c4974822658d02533e660fae343f281b" + ] + ], + "meta": { + "nf-test": "0.9.2", + "nextflow": "25.04.6" + }, + "timestamp": "2025-09-19T16:23:28.074624" + }, + "test_fastp_paired_end - stub": { + "content": [ + { + "0": [ + [ + { + "id": "test", + "single_end": false + }, + [ + "test_1.fastp.fastq.gz:md5,68b329da9893e34099c7d8ad5cb9c940", + "test_2.fastp.fastq.gz:md5,68b329da9893e34099c7d8ad5cb9c940" + ] + ] + ], + "1": [ + [ + { + "id": "test", + "single_end": false + }, + "test.fastp.json:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "2": [ + [ + { + "id": "test", + "single_end": false + }, + "test.fastp.html:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "3": [ + [ + { + "id": "test", + "single_end": false + }, + "test.fastp.log:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "4": [ + + ], + "5": [ + + ], + "6": [ + "versions.yml:md5,c4974822658d02533e660fae343f281b" + ], + "html": [ + [ + { + "id": "test", + "single_end": false + }, + "test.fastp.html:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "json": [ + [ + { + "id": "test", + "single_end": false + }, + "test.fastp.json:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "log": [ + [ + { + "id": "test", + "single_end": false + }, + "test.fastp.log:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "reads": [ + [ + { + "id": "test", + "single_end": false + }, + [ + "test_1.fastp.fastq.gz:md5,68b329da9893e34099c7d8ad5cb9c940", + "test_2.fastp.fastq.gz:md5,68b329da9893e34099c7d8ad5cb9c940" + ] + ] + ], + "reads_fail": [ + + ], + "reads_merged": [ + + ], + "versions": [ + "versions.yml:md5,c4974822658d02533e660fae343f281b" + ] + } + ], + "meta": { + "nf-test": "0.9.2", + "nextflow": "25.04.6" + }, + "timestamp": "2025-09-11T09:55:14.414258" + }, + "test_fastp_single_end": { + "content": [ + [ + [ + { + "id": "test", + "single_end": true + }, + "test.fastp.fastq.gz:md5,67b2bbae47f073e05a97a9c2edce23c7" + ] + ], + [ + + ], + [ + + ], + [ + "versions.yml:md5,c4974822658d02533e660fae343f281b" + ] + ], + "meta": { + "nf-test": "0.9.2", + "nextflow": "25.04.6" + }, + "timestamp": "2025-09-19T16:23:08.469846" + }, + "test_fastp_single_end_trim_fail - stub": { + "content": [ + { + "0": [ + [ + { + "id": "test", + "single_end": true + }, + "test.fastp.fastq.gz:md5,68b329da9893e34099c7d8ad5cb9c940" + ] + ], + "1": [ + [ + { + "id": "test", + "single_end": true + }, + "test.fastp.json:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "2": [ + [ + { + "id": "test", + "single_end": true + }, + "test.fastp.html:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "3": [ + [ + { + "id": "test", + "single_end": true + }, + "test.fastp.log:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "4": [ + [ + { + "id": "test", + "single_end": true + }, + "test.fail.fastq.gz:md5,68b329da9893e34099c7d8ad5cb9c940" + ] + ], + "5": [ + + ], + "6": [ + "versions.yml:md5,c4974822658d02533e660fae343f281b" + ], + "html": [ + [ + { + "id": "test", + "single_end": true + }, + "test.fastp.html:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "json": [ + [ + { + "id": "test", + "single_end": true + }, + "test.fastp.json:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "log": [ + [ + { + "id": "test", + "single_end": true + }, + "test.fastp.log:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "reads": [ + [ + { + "id": "test", + "single_end": true + }, + "test.fastp.fastq.gz:md5,68b329da9893e34099c7d8ad5cb9c940" + ] + ], + "reads_fail": [ + [ + { + "id": "test", + "single_end": true + }, + "test.fail.fastq.gz:md5,68b329da9893e34099c7d8ad5cb9c940" + ] + ], + "reads_merged": [ + + ], + "versions": [ + "versions.yml:md5,c4974822658d02533e660fae343f281b" + ] + } + ], + "meta": { + "nf-test": "0.9.2", + "nextflow": "25.04.6" + }, + "timestamp": "2025-09-11T09:55:23.871395" + }, + "test_fastp_paired_end_trim_fail - stub": { + "content": [ + { + "0": [ + [ + { + "id": "test", + "single_end": false + }, + [ + "test_1.fastp.fastq.gz:md5,68b329da9893e34099c7d8ad5cb9c940", + "test_2.fastp.fastq.gz:md5,68b329da9893e34099c7d8ad5cb9c940" + ] + ] + ], + "1": [ + [ + { + "id": "test", + "single_end": false + }, + "test.fastp.json:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "2": [ + [ + { + "id": "test", + "single_end": false + }, + "test.fastp.html:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "3": [ + [ + { + "id": "test", + "single_end": false + }, + "test.fastp.log:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "4": [ + [ + { + "id": "test", + "single_end": false + }, + [ + "test.paired.fail.fastq.gz:md5,68b329da9893e34099c7d8ad5cb9c940", + "test_1.fail.fastq.gz:md5,68b329da9893e34099c7d8ad5cb9c940", + "test_2.fail.fastq.gz:md5,68b329da9893e34099c7d8ad5cb9c940" + ] + ] + ], + "5": [ + + ], + "6": [ + "versions.yml:md5,c4974822658d02533e660fae343f281b" + ], + "html": [ + [ + { + "id": "test", + "single_end": false + }, + "test.fastp.html:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "json": [ + [ + { + "id": "test", + "single_end": false + }, + "test.fastp.json:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "log": [ + [ + { + "id": "test", + "single_end": false + }, + "test.fastp.log:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "reads": [ + [ + { + "id": "test", + "single_end": false + }, + [ + "test_1.fastp.fastq.gz:md5,68b329da9893e34099c7d8ad5cb9c940", + "test_2.fastp.fastq.gz:md5,68b329da9893e34099c7d8ad5cb9c940" + ] + ] + ], + "reads_fail": [ + [ + { + "id": "test", + "single_end": false + }, + [ + "test.paired.fail.fastq.gz:md5,68b329da9893e34099c7d8ad5cb9c940", + "test_1.fail.fastq.gz:md5,68b329da9893e34099c7d8ad5cb9c940", + "test_2.fail.fastq.gz:md5,68b329da9893e34099c7d8ad5cb9c940" + ] + ] + ], + "reads_merged": [ + + ], + "versions": [ + "versions.yml:md5,c4974822658d02533e660fae343f281b" + ] + } + ], + "meta": { + "nf-test": "0.9.2", + "nextflow": "25.04.6" + }, + "timestamp": "2025-09-11T09:55:28.399328" + }, + "fastp test_fastp_interleaved": { + "content": [ + [ + [ + { + "id": "test", + "single_end": true + }, + "test.fastp.fastq.gz:md5,217d62dc13a23e92513a1bd8e1bcea39" + ] + ], + [ + "versions.yml:md5,c4974822658d02533e660fae343f281b" + ] + ], + "meta": { + "nf-test": "0.9.2", + "nextflow": "25.04.6" + }, + "timestamp": "2025-09-19T16:23:16.479494" + }, + "test_fastp_single_end_trim_fail": { + "content": [ + [ + [ + { + "id": "test", + "single_end": true + }, + "test.fastp.fastq.gz:md5,67b2bbae47f073e05a97a9c2edce23c7" + ] + ], + [ + [ + { + "id": "test", + "single_end": true + }, + "test.fail.fastq.gz:md5,3e4aaadb66a5b8fc9b881bf39c227abd" + ] + ], + [ + + ], + [ + "versions.yml:md5,c4974822658d02533e660fae343f281b" + ] + ], + "meta": { + "nf-test": "0.9.2", + "nextflow": "25.04.6" + }, + "timestamp": "2025-09-19T16:23:20.299076" + }, + "test_fastp_paired_end_qc_only": { + "content": [ + [ + + ], + [ + + ], + [ + + ], + [ + + ], + [ + + ], + [ + + ], + [ + "versions.yml:md5,c4974822658d02533e660fae343f281b" + ] + ], + "meta": { + "nf-test": "0.9.2", + "nextflow": "25.04.6" + }, + "timestamp": "2025-09-19T16:23:40.113724" + }, + "test_fastp_paired_end_qc_only - stub": { + "content": [ + { + "0": [ + + ], + "1": [ + [ + { + "id": "test", + "single_end": false + }, + "test.fastp.json:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "2": [ + [ + { + "id": "test", + "single_end": false + }, + "test.fastp.html:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "3": [ + [ + { + "id": "test", + "single_end": false + }, + "test.fastp.log:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "4": [ + + ], + "5": [ + + ], + "6": [ + "versions.yml:md5,c4974822658d02533e660fae343f281b" + ], + "html": [ + [ + { + "id": "test", + "single_end": false + }, + "test.fastp.html:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "json": [ + [ + { + "id": "test", + "single_end": false + }, + "test.fastp.json:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "log": [ + [ + { + "id": "test", + "single_end": false + }, + "test.fastp.log:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "reads": [ + + ], + "reads_fail": [ + + ], + "reads_merged": [ + + ], + "versions": [ + "versions.yml:md5,c4974822658d02533e660fae343f281b" + ] + } + ], + "meta": { + "nf-test": "0.9.2", + "nextflow": "25.04.6" + }, + "timestamp": "2025-09-11T09:55:46.696419" + } +} \ No newline at end of file diff --git a/modules/nf-core/fastp/tests/nextflow.interleaved.config b/modules/nf-core/fastp/tests/nextflow.interleaved.config new file mode 100644 index 00000000..4be8dbd2 --- /dev/null +++ b/modules/nf-core/fastp/tests/nextflow.interleaved.config @@ -0,0 +1,5 @@ +process { + withName: FASTP { + ext.args = "--interleaved_in -e 30" + } +} diff --git a/modules/nf-core/fastp/tests/nextflow.save_failed.config b/modules/nf-core/fastp/tests/nextflow.save_failed.config new file mode 100644 index 00000000..53b61b0c --- /dev/null +++ b/modules/nf-core/fastp/tests/nextflow.save_failed.config @@ -0,0 +1,5 @@ +process { + withName: FASTP { + ext.args = "-e 30" + } +} diff --git a/subworkflows/local/prepare/prepare_shortreads/main.nf b/subworkflows/local/prepare/prepare_shortreads/main.nf index 9f8ef31e..a16c9690 100644 --- a/subworkflows/local/prepare/prepare_shortreads/main.nf +++ b/subworkflows/local/prepare/prepare_shortreads/main.nf @@ -1,4 +1,4 @@ -include { TRIMGALORE } from '../../../../modules/nf-core/trimgalore/main' +include { FASTP } from '../../../../modules/nf-core/fastp/main' include { MERYL_COUNT } from '../../../../modules/nf-core/meryl/count/main' include { MERYL_UNIONSUM } from '../../../../modules/nf-core/meryl/unionsum/main' @@ -7,7 +7,7 @@ workflow PREPARE_SHORTREADS { shortreads_in main: - Channel.empty().set { ch_versions } + channel.empty().set { ch_versions } shortreads_in .map { create_shortread_channel(it) } @@ -55,14 +55,14 @@ workflow PREPARE_SHORTREADS { .mix(shortreads.trim .filter { it -> !it.group } .map { - it -> [ it.meta, it.shortreads ] + it -> [ it.meta, it.shortreads, [] ] } ) - .set { trimgalore_in } + .set { trim_in } - TRIMGALORE(trimgalore_in) + FASTP(trim_in) - TRIMGALORE.out.reads + FASTP.out.reads .filter { it -> it[0].ids } .flatMap { it -> it[0].ids.tokenize("+").collect { @@ -70,7 +70,7 @@ workflow PREPARE_SHORTREADS { } } .mix( - TRIMGALORE.out.reads + FASTP.out.reads .filter { it -> !it[0].ids } .map { it -> [ meta: [ id: it[0].id ], shortreads: it[1] ] } ) @@ -88,7 +88,7 @@ workflow PREPARE_SHORTREADS { .mix( shortreads.no_trim ) .set { shortreads } - ch_versions = ch_versions.mix(TRIMGALORE.out.versions) + ch_versions = ch_versions.mix(FASTP.out.versions) shortreads .filter { it -> it.merqury } From 723e86512f5f96a28862f0f23490f841fc5a5e5b Mon Sep 17 00:00:00 2001 From: Niklas Schandry Date: Mon, 24 Nov 2025 11:00:49 +0100 Subject: [PATCH 069/162] small update quast SW --- subworkflows/local/qc/quast/main.nf | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/subworkflows/local/qc/quast/main.nf b/subworkflows/local/qc/quast/main.nf index cc152de8..56bf886b 100644 --- a/subworkflows/local/qc/quast/main.nf +++ b/subworkflows/local/qc/quast/main.nf @@ -5,13 +5,13 @@ workflow RUN_QUAST { ch_main main: - Channel.empty().set { versions } + channel.empty().set { versions } /* prepare for quast: * This makes use of the input channel to obtain the reference and reference annotations * See quast module for details */ - Channel.empty().set { quast_results } - Channel.empty().set { quast_tsv } + channel.empty().set { quast_results } + channel.empty().set { quast_tsv } ch_main .filter { @@ -22,7 +22,7 @@ workflow RUN_QUAST { it.meta, it.qc_target, it.ref_fasta ?: [], - it.ref_gff?: [], + it.ref_gff ?: [], it.ref_map_bam ?: [], it.assembly_map_bam ] From 0cc1bae3165300020d620c3b731140dfc36cee88 Mon Sep 17 00:00:00 2001 From: Niklas Schandry Date: Wed, 26 Nov 2025 13:42:13 +0100 Subject: [PATCH 070/162] Bulk change: Channel.emtpy -> channel.empty --- .gitignore | 1 + subworkflows/local/assemble/main.nf | 2 +- subworkflows/local/bam_sort_stat/main.nf | 2 +- subworkflows/local/jellyfish/main.nf | 4 ++-- subworkflows/local/liftoff/main.nf | 2 +- subworkflows/local/mapping/map_sr/main.nf | 2 +- .../local/mapping/map_to_assembly/main.nf | 2 +- subworkflows/local/mapping/map_to_ref/main.nf | 2 +- subworkflows/local/polishing/main.nf | 8 ++++---- .../polishing/medaka/polish_medaka/main.nf | 2 +- .../polishing/pilon/polish_pilon/main.nf | 2 +- subworkflows/local/prepare/jellyfish/main.nf | 4 ++-- .../local/prepare/prepare_hifi/main.nf | 2 +- .../local/prepare/prepare_ont/collect/main.nf | 2 +- .../local/prepare/prepare_ont/main.nf | 2 +- subworkflows/local/qc/busco/main.nf | 8 ++++---- subworkflows/local/qc/main.nf | 8 ++++---- subworkflows/local/qc/merqury/main.nf | 2 +- subworkflows/local/scaffolding/links/main.nf | 2 +- .../local/scaffolding/longstitch/main.nf | 2 +- subworkflows/local/scaffolding/main.nf | 20 +++++++++---------- subworkflows/local/scaffolding/ragtag/main.nf | 2 +- workflows/genomeassembler.nf | 4 ++-- 23 files changed, 44 insertions(+), 43 deletions(-) diff --git a/.gitignore b/.gitignore index f232546a..ecdcfbc3 100644 --- a/.gitignore +++ b/.gitignore @@ -9,3 +9,4 @@ testing* null/ .nf-test/ .nf-test.log +schema.md diff --git a/subworkflows/local/assemble/main.nf b/subworkflows/local/assemble/main.nf index 57567838..f672d8e7 100644 --- a/subworkflows/local/assemble/main.nf +++ b/subworkflows/local/assemble/main.nf @@ -17,7 +17,7 @@ workflow ASSEMBLE { main: // Empty channels - Channel.empty().set { ch_versions } + channel.empty().set { ch_versions } /* Samples are split into those that need assembly, and those that will not be assembled (i.e. assemblies are provided) diff --git a/subworkflows/local/bam_sort_stat/main.nf b/subworkflows/local/bam_sort_stat/main.nf index 89f8cdf4..eebbe1f1 100644 --- a/subworkflows/local/bam_sort_stat/main.nf +++ b/subworkflows/local/bam_sort_stat/main.nf @@ -17,7 +17,7 @@ workflow BAM_INDEX_STATS_SAMTOOLS { fasta main: - Channel.empty().set { ch_versions } + channel.empty().set { ch_versions } SAMTOOLS_INDEX(bam) diff --git a/subworkflows/local/jellyfish/main.nf b/subworkflows/local/jellyfish/main.nf index 3ca19b5d..c5c063c7 100644 --- a/subworkflows/local/jellyfish/main.nf +++ b/subworkflows/local/jellyfish/main.nf @@ -10,8 +10,8 @@ workflow JELLYFISH { nanoq_out main: - Channel.empty().set { genomescope_in } - Channel.empty().set { ch_versions } + channel.empty().set { genomescope_in } + channel.empty().set { ch_versions } inputs.map { it -> [ diff --git a/subworkflows/local/liftoff/main.nf b/subworkflows/local/liftoff/main.nf index 8e04024d..780c3239 100644 --- a/subworkflows/local/liftoff/main.nf +++ b/subworkflows/local/liftoff/main.nf @@ -5,7 +5,7 @@ workflow RUN_LIFTOFF { liftoff_in main: - Channel.empty().set { ch_versions } + channel.empty().set { ch_versions } LIFTOFF(liftoff_in, []) diff --git a/subworkflows/local/mapping/map_sr/main.nf b/subworkflows/local/mapping/map_sr/main.nf index 838d7454..655c4002 100644 --- a/subworkflows/local/mapping/map_sr/main.nf +++ b/subworkflows/local/mapping/map_sr/main.nf @@ -7,7 +7,7 @@ workflow MAP_SR { genome_assembly main: - Channel.empty().set { ch_versions } + channel.empty().set { ch_versions } // map reads to assembly in_reads .map { meta, reads -> [[id: meta.id], reads] } diff --git a/subworkflows/local/mapping/map_to_assembly/main.nf b/subworkflows/local/mapping/map_to_assembly/main.nf index a4444755..1c9f0937 100644 --- a/subworkflows/local/mapping/map_to_assembly/main.nf +++ b/subworkflows/local/mapping/map_to_assembly/main.nf @@ -6,7 +6,7 @@ workflow MAP_TO_ASSEMBLY { map_assembly // meta: [id, qc_reads], reads, refs main: - Channel.empty().set { ch_versions } + channel.empty().set { ch_versions } // map reads to assembly ALIGN(map_assembly, true, 'bai', false, false) diff --git a/subworkflows/local/mapping/map_to_ref/main.nf b/subworkflows/local/mapping/map_to_ref/main.nf index ec177f42..4b77a518 100644 --- a/subworkflows/local/mapping/map_to_ref/main.nf +++ b/subworkflows/local/mapping/map_to_ref/main.nf @@ -6,7 +6,7 @@ workflow MAP_TO_REF { ch_map_ref // meta: [id, qc_reads], reads, refs main: - Channel.empty().set { ch_versions } + channel.empty().set { ch_versions } // Map reads to reference ALIGN(ch_map_ref, true, 'bai', false, false) diff --git a/subworkflows/local/polishing/main.nf b/subworkflows/local/polishing/main.nf index ed723173..faefe82f 100644 --- a/subworkflows/local/polishing/main.nf +++ b/subworkflows/local/polishing/main.nf @@ -8,10 +8,10 @@ workflow POLISH { main: - Channel.empty().set { ch_versions } - Channel.empty().set { polish_busco_reports } - Channel.empty().set { polish_quast_reports } - Channel.empty().set { polish_merqury_reports } + channel.empty().set { ch_versions } + channel.empty().set { polish_busco_reports } + channel.empty().set { polish_quast_reports } + channel.empty().set { polish_merqury_reports } ch_main .branch { it -> diff --git a/subworkflows/local/polishing/medaka/polish_medaka/main.nf b/subworkflows/local/polishing/medaka/polish_medaka/main.nf index d2344a2a..84fc7254 100644 --- a/subworkflows/local/polishing/medaka/polish_medaka/main.nf +++ b/subworkflows/local/polishing/medaka/polish_medaka/main.nf @@ -8,7 +8,7 @@ workflow POLISH_MEDAKA { meryl_kmers main: - Channel.empty().set { ch_versions } + channel.empty().set { ch_versions } ch_main .filter { diff --git a/subworkflows/local/polishing/pilon/polish_pilon/main.nf b/subworkflows/local/polishing/pilon/polish_pilon/main.nf index 0aa1c5e8..86ae05c4 100644 --- a/subworkflows/local/polishing/pilon/polish_pilon/main.nf +++ b/subworkflows/local/polishing/pilon/polish_pilon/main.nf @@ -9,7 +9,7 @@ workflow POLISH_PILON { meryl_kmers main: - Channel.empty().set { ch_versions } + channel.empty().set { ch_versions } ch_main .multiMap { diff --git a/subworkflows/local/prepare/jellyfish/main.nf b/subworkflows/local/prepare/jellyfish/main.nf index bf9d7422..d3cb3655 100644 --- a/subworkflows/local/prepare/jellyfish/main.nf +++ b/subworkflows/local/prepare/jellyfish/main.nf @@ -8,8 +8,8 @@ workflow JELLYFISH { ch_main main: - Channel.empty().set { genomescope_in } - Channel.empty().set { ch_versions } + channel.empty().set { genomescope_in } + channel.empty().set { ch_versions } ch_main .filter { it -> it.group } diff --git a/subworkflows/local/prepare/prepare_hifi/main.nf b/subworkflows/local/prepare/prepare_hifi/main.nf index 5f21062b..edbc1b07 100644 --- a/subworkflows/local/prepare/prepare_hifi/main.nf +++ b/subworkflows/local/prepare/prepare_hifi/main.nf @@ -5,7 +5,7 @@ workflow PREPARE_HIFI { main_in // should contain only samples with hifireads main: - Channel.empty().set { ch_versions } + channel.empty().set { ch_versions } main_in.dump(tag: "Prepare-HIFI input") main_in diff --git a/subworkflows/local/prepare/prepare_ont/collect/main.nf b/subworkflows/local/prepare/prepare_ont/collect/main.nf index a3e3e155..5fa5da24 100644 --- a/subworkflows/local/prepare/prepare_ont/collect/main.nf +++ b/subworkflows/local/prepare/prepare_ont/collect/main.nf @@ -5,7 +5,7 @@ workflow COLLECT { ch_input main: - Channel.empty().set { ch_versions } + channel.empty().set { ch_versions } ch_input .filter { diff --git a/subworkflows/local/prepare/prepare_ont/main.nf b/subworkflows/local/prepare/prepare_ont/main.nf index 0aa215b4..bafe6a1a 100644 --- a/subworkflows/local/prepare/prepare_ont/main.nf +++ b/subworkflows/local/prepare/prepare_ont/main.nf @@ -7,7 +7,7 @@ workflow PREPARE_ONT { ch_main // should contain only samples with ontreads main: - Channel.empty().set { ch_versions } + channel.empty().set { ch_versions } ch_main.dump(tag: "Prepare-ONT input") ch_main diff --git a/subworkflows/local/qc/busco/main.nf b/subworkflows/local/qc/busco/main.nf index 74f511b0..fcc3ddd1 100644 --- a/subworkflows/local/qc/busco/main.nf +++ b/subworkflows/local/qc/busco/main.nf @@ -5,10 +5,10 @@ workflow RUN_BUSCO { ch_main main: - Channel.empty().set { versions } - Channel.empty().set { batch_summary } - Channel.empty().set { short_summary_txt } - Channel.empty().set { short_summary_json } + channel.empty().set { versions } + channel.empty().set { batch_summary } + channel.empty().set { short_summary_txt } + channel.empty().set { short_summary_json } ch_main .filter { diff --git a/subworkflows/local/qc/main.nf b/subworkflows/local/qc/main.nf index 3b02180b..4ad425bd 100644 --- a/subworkflows/local/qc/main.nf +++ b/subworkflows/local/qc/main.nf @@ -10,10 +10,10 @@ workflow QC { meryl_kmers main: - Channel.empty().set { ch_versions } - Channel.empty().set { quast_out } - Channel.empty().set { busco_out } - Channel.empty().set { merqury_report_files } + channel.empty().set { ch_versions } + channel.empty().set { quast_out } + channel.empty().set { busco_out } + channel.empty().set { merqury_report_files } ch_main .branch { diff --git a/subworkflows/local/qc/merqury/main.nf b/subworkflows/local/qc/merqury/main.nf index 8ee300d3..a5eda05b 100644 --- a/subworkflows/local/qc/merqury/main.nf +++ b/subworkflows/local/qc/merqury/main.nf @@ -6,7 +6,7 @@ workflow MERQURY_QC { meryl_out main: - Channel.empty().set { versions } + channel.empty().set { versions } assembly.map { meta, _assembly -> [meta.id, []] }.set { stats } assembly.map { meta, _assembly -> [meta.id, []] }.set { spectra_asm_hist } assembly.map { meta, _assembly -> [meta.id, []] }.set { spectra_cn_hist } diff --git a/subworkflows/local/scaffolding/links/main.nf b/subworkflows/local/scaffolding/links/main.nf index 9aef834d..b6ed944b 100644 --- a/subworkflows/local/scaffolding/links/main.nf +++ b/subworkflows/local/scaffolding/links/main.nf @@ -8,7 +8,7 @@ workflow RUN_LINKS { meryl_kmers main: - Channel.empty().set { ch_versions } + channel.empty().set { ch_versions } ch_main .multiMap { diff --git a/subworkflows/local/scaffolding/longstitch/main.nf b/subworkflows/local/scaffolding/longstitch/main.nf index 43778071..b0a8a238 100644 --- a/subworkflows/local/scaffolding/longstitch/main.nf +++ b/subworkflows/local/scaffolding/longstitch/main.nf @@ -17,7 +17,7 @@ workflow RUN_LONGSTITCH { */ main: - Channel.empty().set { ch_versions } + channel.empty().set { ch_versions } ch_main .map { diff --git a/subworkflows/local/scaffolding/main.nf b/subworkflows/local/scaffolding/main.nf index 0194dfae..26470652 100644 --- a/subworkflows/local/scaffolding/main.nf +++ b/subworkflows/local/scaffolding/main.nf @@ -8,16 +8,16 @@ workflow SCAFFOLD { meryl_kmers main: - Channel.empty().set { ch_versions } - Channel.empty().set { links_busco } - Channel.empty().set { links_quast } - Channel.empty().set { links_merqury } - Channel.empty().set { longstitch_busco } - Channel.empty().set { longstitch_quast } - Channel.empty().set { longstitch_merqury } - Channel.empty().set { ragtag_busco } - Channel.empty().set { ragtag_quast } - Channel.empty().set { ragtag_merqury } + channel.empty().set { ch_versions } + channel.empty().set { links_busco } + channel.empty().set { links_quast } + channel.empty().set { links_merqury } + channel.empty().set { longstitch_busco } + channel.empty().set { longstitch_quast } + channel.empty().set { longstitch_merqury } + channel.empty().set { ragtag_busco } + channel.empty().set { ragtag_quast } + channel.empty().set { ragtag_merqury } // There is no support for scaffolding of scaffolded scaffolds. // But it is possible that one sample is scaffolded with different tools. diff --git a/subworkflows/local/scaffolding/ragtag/main.nf b/subworkflows/local/scaffolding/ragtag/main.nf index 366084d2..3238c6c2 100644 --- a/subworkflows/local/scaffolding/ragtag/main.nf +++ b/subworkflows/local/scaffolding/ragtag/main.nf @@ -9,7 +9,7 @@ workflow RUN_RAGTAG { meryl_kmers main: - Channel.empty().set { ch_versions } + channel.empty().set { ch_versions } ch_main .multiMap { it -> diff --git a/workflows/genomeassembler.nf b/workflows/genomeassembler.nf index 6f97f49c..92338344 100644 --- a/workflows/genomeassembler.nf +++ b/workflows/genomeassembler.nf @@ -158,9 +158,9 @@ workflow GENOMEASSEMBLER { .map { it -> it.collect { _entry, map -> [ (map.key): map.value ] }.collectEntries() } */ - Channel.empty().set { meryl_kmers } + channel.empty().set { meryl_kmers } - Channel.empty().set { ch_versions } + channel.empty().set { ch_versions } // Initialize channels for QC report collection Channel From 886cecb0e40006c5ed1a6e035abcd341d4b3909a Mon Sep 17 00:00:00 2001 From: Niklas Schandry Date: Wed, 26 Nov 2025 14:19:00 +0100 Subject: [PATCH 071/162] switch to fastp, cleanup QC sw --- CHANGELOG.md | 6 +- nextflow.config | 1 - subworkflows/local/assemble/main.nf | 66 +++++++++++++------ subworkflows/local/prepare/jellyfish/main.nf | 5 -- subworkflows/local/prepare/main.nf | 2 +- .../local/prepare/prepare_shortreads/main.nf | 1 + subworkflows/local/qc/main.nf | 17 +++-- 7 files changed, 65 insertions(+), 33 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 30067654..1e805589 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,7 +13,7 @@ v2.0.0 of this pipeline is a (almost complete) refactor of the pipeline to facil - migration to nf-test - increased flexibility of the scaffolding strategy - added option to group samples -- +- fastp for short-read trimming and qc ### `Fixed` @@ -29,6 +29,10 @@ The following tools are no longer used: - `porechop` - `lima` +The following param is no longer implemented: + +- `dump`, used to dump jellyfish output. + ## v1.1.0 'Brass Pigeon' - [2025-07-21] ### `Added` diff --git a/nextflow.config b/nextflow.config index 3586ac81..040ea564 100644 --- a/nextflow.config +++ b/nextflow.config @@ -57,7 +57,6 @@ params { // -- Jellyfish (qc reads) -- jellyfish = false jellyfish_k = 21 - dump = false // dump jellyfish output // -- HiFi -- hifireads = null hifi_trim = false // run fastplong trimming on HiFi reads? diff --git a/subworkflows/local/assemble/main.nf b/subworkflows/local/assemble/main.nf index f672d8e7..c21f0df1 100644 --- a/subworkflows/local/assemble/main.nf +++ b/subworkflows/local/assemble/main.nf @@ -59,11 +59,18 @@ workflow ASSEMBLE { .scaffold .dump(tag: "Assemble: Branched: scaffold") + + /* + ========================= + FLYE ASSEMBLER + ========================= + */ /* Inputs for flye assembler: - Samples with single strategy, where the assembler is flye - Samples from the scaffold strategy where either (or both) assembler is flye */ + ch_main_assemble_branched .single .filter { it -> it.assembler1 == "flye" } @@ -72,7 +79,7 @@ workflow ASSEMBLE { ch_main_assemble_branched .scaffold .filter { it -> it.assembler1 == "flye" } - // Add in the scaffolding samples where is used for HiFi + // Add in the scaffolding samples where flye is used for HiFi .mix( ch_main_assemble_branched .scaffold @@ -94,12 +101,12 @@ workflow ASSEMBLE { genome_size: it.genome_size, flye_args: it.flye_args ?: "" ], - // Reads are matched based on assembler - it.assembler1 == "flye" ? it.ontreads : null, + it.ontreads ?: [], ] mode: it.assembler1 == "flye" ? "--nano-hq" : null } .set { flye_ont_inputs } + // These are the hifi samples ch_main_assemble_flye // Those where the hifi assembler is flye, or where there is only one assembler and hifireads @@ -112,8 +119,7 @@ workflow ASSEMBLE { genome_size: it.genome_size, flye_args: it.flye_args ?: "" ], - // Reads are matched based on assembler - it.assembler2 == "flye" ? it.hifireads : null, + it.hifireads ?: [], ] mode: it.assembler2 == "flye" ? "--pacbio-hifi" : null } @@ -129,10 +135,15 @@ workflow ASSEMBLE { ch_versions = ch_versions.mix(FLYE_ONT.out.versions).mix(FLYE_HIFI.out.versions) + /* + ========================= + HIFIASM ASSEMBLER + ========================= + */ /* Hifiasm: everything that is not hifiasm-ONT - Single branch with hifiasm as assembler and no ont reads (only hifireads) - Hybrid assembly - Scaffold samples where assembler2 (hifi assembler) is hifiasm + - Single branch with hifiasm as assembler and no ont reads (only hifireads) + - Hybrid assembly + - Scaffold samples where assembler2 (hifi assembler) is hifiasm */ ch_main_assemble_branched .single @@ -172,10 +183,12 @@ workflow ASSEMBLE { ch_versions = ch_versions.mix(HIFIASM.out.versions).mix(GFA_2_FA_HIFI.out.versions) /* + hifiasm with ONLY ont reads. Assemble hifiasm_ont branch: Single branch with hifiasm and only ont reads Scaffold branch where assembler1 (ont assembler) is hifiasm */ + ch_main_assemble_branched .single .filter { it -> it.assembler1 == "hifiasm" && it.ontreads } @@ -195,7 +208,7 @@ workflow ASSEMBLE { // Now, the individual assemblies need to be correctly added into the main channel. - // This should be done per-strategy I think + // This should be done per-strategy // join assembler outputs back to assembler inputs and determine correct placement of the assembly. // Flye: @@ -234,7 +247,7 @@ workflow ASSEMBLE { ) // After joining re-create the maps from the stored map .map { it -> it.collect { _entry, map -> [ (map.key): map.value ] }.collectEntries() } - // On the proper map, place the hifiasm assembly into the correct position + // On the map, place the hifiasm assembly into the correct position .map { // remove hifiasm_assembly entry from joined result it -> it - it.subMap("hifiasm_assembly") + @@ -263,14 +276,19 @@ workflow ASSEMBLE { it -> it -it.subMap("hifiasm_assembly") + [ assembly: (it.strategy == "single" || it.strategy == "hybrid") && it.assembler1 == "hifiasm" ? it.hifiasm_assembly : null, - // I think below case dose not exist in this channel since it is only ont (assembler1) assemblies? assembly1: it.strategy == "scaffold" && it.assembler1 == "hifiasm" ? it.hifiasm_assembly : null, assembly2: it.strategy == "scaffold" && it.assembler2 == "hifiasm" ? it.hifiasm_assembly : null ] } .set { hifiasm_ont_assemblies } - hifiasm_ont_assemblies.dump(tag: "Assemble: hifiasm HIFI assemblies") + hifiasm_ont_assemblies.dump(tag: "Assemble: hifiasm ONT assemblies") + + /* + ========================= + SCAFFOLDING + ========================= + */ // The single and hybrid channels can be mixed and forwarded. // The scaffold channel needs to be joined separately. @@ -288,8 +306,14 @@ workflow ASSEMBLE { ch_assemblies_no_scaffold.dump(tag: "Assemble: Assemblies without scaffolding") + + /* + ------------------- + Prepare Scaffolding + ------------------- + */ // This leaves the scaffold strategy. - // scaffolds can be: FLYE-HIFIASM, FLYE-FLYE, HIFIASM-HIFIASM HIFIASM-FLYE or + // scaffolds can be: FLYE-HIFIASM, FLYE-FLYE, HIFIASM-HIFIASM or HIFIASM-FLYE flye_assemblies // Flye-hifiasm @@ -301,7 +325,7 @@ workflow ASSEMBLE { .map { it -> it.collect { entry -> [ entry.value, entry ] } } ) .map { it -> it.collect { _entry, map -> [ (map.key): map.value ] }.collectEntries() } - .map { it -> it - it.subMap("hifiasm_assembly","assembly2") + [assembly2: it.hifiasm_assembly] } + .map { it -> it - it.subMap("hifiasm_assembly","assembly2") + [ assembly2: it.hifiasm_assembly ] } .set{ scaffold_flye_hifiasm } // flye-flye @@ -314,7 +338,7 @@ workflow ASSEMBLE { .map { it -> it.collect { entry -> [ entry.value, entry ] } } ) .map { it -> it.collect { _entry, map -> [ (map.key): map.value ] }.collectEntries() } - .map { it -> it - it.subMap("flye_assembly", "assembly2") + [assembly2: it.flye_assembly] } + .map { it -> it - it.subMap("flye_assembly", "assembly2") + [ assembly2: it.flye_assembly ] } .set{ scaffold_flye_flye } // hifiasm_flye @@ -327,7 +351,7 @@ workflow ASSEMBLE { .map { it -> it.collect { entry -> [ entry.value, entry ] } } ) .map { it -> it.collect { _entry, map -> [ (map.key): map.value ] }.collectEntries() } - .map { it -> it - it.subMap("flye_assembly", "assembly2") + [assembly2: it.flye_assembly] } + .map { it -> it - it.subMap("flye_assembly", "assembly2") + [ assembly2: it.flye_assembly ] } .set{ scaffold_hifiasm_flye } // hifiasm_hifiasm @@ -340,7 +364,7 @@ workflow ASSEMBLE { .map { it -> it.collect { entry -> [ entry.value, entry ] } } ) .map { it -> it.collect { _entry, map -> [ (map.key): map.value ] }.collectEntries() } - .map { it -> it - it.subMap("hifiasm_assembly","assembly2") + [assembly2: it.hifiasm_assembly] } + .map { it -> it - it.subMap("hifiasm_assembly","assembly2") + [ assembly2: it.hifiasm_assembly ] } .set{ scaffold_hifiasm_hifiasm } // branch to scaffold those assemblies that need it @@ -353,7 +377,7 @@ workflow ASSEMBLE { ch_to_scaffold.dump(tag: "Assemble: Assemblies with scaffolding - inputs") - // For scaffolding, depeding on which strategy we used, the correct assembly needs to go into either target or query: + // For scaffolding, depeding on which strategy used, the correct assembly needs to go into either target or query: // assembly1 is always ONT, assembly2 is always HiFi ch_to_scaffold @@ -404,6 +428,7 @@ workflow ASSEMBLE { ch_main_to_mapping.dump(tag: "Assemble: TO MAPPING") + // QUAST is the only QC tool that requires mapping ch_main_to_mapping .branch { @@ -411,7 +436,7 @@ workflow ASSEMBLE { quast: it.quast no_quast: !it.quast } - // Note that this channel is set here but the quast branch is further used + // Note that this channel is set here but only the quast branch is further used .set { ch_main_quast_branch } // If QUAST should run, and we need an alignment to reference, this is created here @@ -425,6 +450,7 @@ workflow ASSEMBLE { .set { ch_quast_branched } + // Alignment is actually only created if no bam file is provided ch_quast_branched .use_ref @@ -453,7 +479,7 @@ workflow ASSEMBLE { .join( MAP_TO_REF.out.ch_aln_to_ref_bam // Note that this is a normal list channel and needs to become a map before conversion back to list and joining - // Otherwise the map cannot be regenerated later + // otherwise, the map cannot be regenerated later .map { it -> [meta: it[0], ref_map_bam: it[1]] } .map { it -> it.collect { entry -> [ entry.value, entry ] } } ) diff --git a/subworkflows/local/prepare/jellyfish/main.nf b/subworkflows/local/prepare/jellyfish/main.nf index d3cb3655..d2658e74 100644 --- a/subworkflows/local/prepare/jellyfish/main.nf +++ b/subworkflows/local/prepare/jellyfish/main.nf @@ -49,11 +49,6 @@ workflow JELLYFISH { ch_versions = ch_versions.mix(COUNT.out.versions) - if (params.dump) { - DUMP(kmers) - ch_versions = ch_versions.mix(DUMP.out.versions) - } - HISTO(kmers) ch_versions = ch_versions.mix(HISTO.out.versions) diff --git a/subworkflows/local/prepare/main.nf b/subworkflows/local/prepare/main.nf index 77c0ab38..cfc10a5a 100644 --- a/subworkflows/local/prepare/main.nf +++ b/subworkflows/local/prepare/main.nf @@ -197,7 +197,6 @@ workflow PREPARE { } .set { ch_main_jellyfish_branched } - // TODO: Jellyfish is currently not grouped JELLYFISH(ch_main_jellyfish_branched.jelly) ch_main_jellyfish_branched.no_jelly @@ -221,6 +220,7 @@ workflow PREPARE { emit: ch_main = main_out fastplong_json_reports + fastp_json_reports = SHORTREADS.out.fastp_json meryl_kmers genomescope_summary genomescope_plot diff --git a/subworkflows/local/prepare/prepare_shortreads/main.nf b/subworkflows/local/prepare/prepare_shortreads/main.nf index a16c9690..bea7d700 100644 --- a/subworkflows/local/prepare/prepare_shortreads/main.nf +++ b/subworkflows/local/prepare/prepare_shortreads/main.nf @@ -151,6 +151,7 @@ workflow PREPARE_SHORTREADS { main_out meryl_kmers versions + fastp_json = FASTP.out.json } def create_shortread_channel(row) { diff --git a/subworkflows/local/qc/main.nf b/subworkflows/local/qc/main.nf index 4ad425bd..0cc9ff13 100644 --- a/subworkflows/local/qc/main.nf +++ b/subworkflows/local/qc/main.nf @@ -29,9 +29,9 @@ workflow QC { .map { it -> [it.meta] } .join(scaffolds) .join(meryl_kmers) - .multiMap { it -> - scaffolds: [it[0], it[1]] - kmers: [it[0], it[2]] + .multiMap { meta, scaffs, kmers -> + scaffolds: [ meta, scaffs ] + kmers: [ meta, kmers ] } .set { merqury_in } @@ -53,9 +53,16 @@ workflow QC { it -> [ it.meta, it.qc_reads_path, it.qc_reads ] } .join(scaffolds) - .multiMap { + .map { meta, reads, qc_reads, target_scaffolds -> - reads: [[id: meta.id, qc_reads:qc_reads], reads, target_scaffolds] + [ + [ + id: meta.id, + qc_reads:qc_reads + ], + reads, + target_scaffolds + ] } .set { map_assembly_in } From bcc5fe341264ff3b9c81d8e01642b0989183c8c1 Mon Sep 17 00:00:00 2001 From: Niklas Schandry Date: Wed, 26 Nov 2025 14:19:26 +0100 Subject: [PATCH 072/162] remove old config presets --- configs/hifi_flye.config | 8 -------- configs/hifi_hifiasm.config | 7 ------- configs/hifi_ont_flye_on_hifiasm.config | 12 ------------ configs/hifi_ont_hifiasm_on_hifiasm.config | 12 ------------ configs/hifi_ont_hifiasm_ul.config | 8 -------- configs/ont_flye.config | 7 ------- configs/ont_hifiasm.config | 7 ------- 7 files changed, 61 deletions(-) delete mode 100644 configs/hifi_flye.config delete mode 100644 configs/hifi_hifiasm.config delete mode 100644 configs/hifi_ont_flye_on_hifiasm.config delete mode 100644 configs/hifi_ont_hifiasm_on_hifiasm.config delete mode 100644 configs/hifi_ont_hifiasm_ul.config delete mode 100644 configs/ont_flye.config delete mode 100644 configs/ont_hifiasm.config diff --git a/configs/hifi_flye.config b/configs/hifi_flye.config deleted file mode 100644 index 7b5a3b10..00000000 --- a/configs/hifi_flye.config +++ /dev/null @@ -1,8 +0,0 @@ -// Use this config to assemble HIFI reads with FLYE in --pacbio-hifi mode - -params { - assembler = 'flye' - flye_mode = '--pacbio-hifi' - hifi = true - ont = false -} diff --git a/configs/hifi_hifiasm.config b/configs/hifi_hifiasm.config deleted file mode 100644 index 1b7558a2..00000000 --- a/configs/hifi_hifiasm.config +++ /dev/null @@ -1,7 +0,0 @@ -// Use this config to assemble HIFI reads with hifiasm - -params { - assembler = 'hifiasm' - hifi = true - ont = false -} diff --git a/configs/hifi_ont_flye_on_hifiasm.config b/configs/hifi_ont_flye_on_hifiasm.config deleted file mode 100644 index 092e3a23..00000000 --- a/configs/hifi_ont_flye_on_hifiasm.config +++ /dev/null @@ -1,12 +0,0 @@ -/* - Use this config to: - assemble HIFI reads with hifiasm - assemble ONT reads with flye - scaffold the flye assembly onto the hifiasm assembly -*/ - -params { - hifi = true - ont = true - assembler = "flye_on_hifiasm" -} diff --git a/configs/hifi_ont_hifiasm_on_hifiasm.config b/configs/hifi_ont_hifiasm_on_hifiasm.config deleted file mode 100644 index 9e548e42..00000000 --- a/configs/hifi_ont_hifiasm_on_hifiasm.config +++ /dev/null @@ -1,12 +0,0 @@ -/* - Use this config to: - assemble HIFI reads with hifiasm - assemble ONT reads with hifiasm --ont - scaffold the ONT assembly onto the HiFi assembly -*/ - -params { - hifi = true - ont = true - assembler = "hifiasm_on_hifiasm" -} diff --git a/configs/hifi_ont_hifiasm_ul.config b/configs/hifi_ont_hifiasm_ul.config deleted file mode 100644 index 4c48a6ba..00000000 --- a/configs/hifi_ont_hifiasm_ul.config +++ /dev/null @@ -1,8 +0,0 @@ -// Use this config to assemble HIFI and ONT reads with hifiasm in --ul mode - -params { - assembler = 'hifiasm' - hifi = true - ont = true - hifiasm_ont = true -} diff --git a/configs/ont_flye.config b/configs/ont_flye.config deleted file mode 100644 index 18b1ff6a..00000000 --- a/configs/ont_flye.config +++ /dev/null @@ -1,7 +0,0 @@ -// Use this config to assemble ONT reads with flye - -params { - assembler = 'flye' - hifi = false - ont = true -} diff --git a/configs/ont_hifiasm.config b/configs/ont_hifiasm.config deleted file mode 100644 index 9835cd88..00000000 --- a/configs/ont_hifiasm.config +++ /dev/null @@ -1,7 +0,0 @@ -// Use this config to assemble ONT reads with hifiasm using --ont - -params { - assembler = 'hifiasm' - hifi = false - ont = true -} From b10a79146b3c4837798c73844c35a218315febdb Mon Sep 17 00:00:00 2001 From: Niklas Schandry Date: Wed, 26 Nov 2025 15:09:36 +0100 Subject: [PATCH 073/162] remove ci.yml --- conf/test_full.config | 14 -------------- 1 file changed, 14 deletions(-) diff --git a/conf/test_full.config b/conf/test_full.config index 8dd4fee9..47fff452 100644 --- a/conf/test_full.config +++ b/conf/test_full.config @@ -15,18 +15,4 @@ params { config_profile_description = 'Full test dataset to check pipeline function' input = 'https://raw.githubusercontent.com/nf-core/test-datasets/genomeassembler/samplesheet/full_test_samplesheet.csv' - ont = true - hifi = true - quast = true - busco = true // needs DB - jellyfish = true - genome_size = 2000000 - assembler = "flye_on_hifiasm" - polish_medaka = true - polish_pilon = true - scaffold_links = true - scaffold_longstitch = true - scaffold_ragtag = true - short_reads = true - merqury = true } From 99cfebfbb8c59d9cf95986df1fbb2b027f70a862 Mon Sep 17 00:00:00 2001 From: Niklas Schandry Date: Tue, 9 Dec 2025 10:00:52 +0100 Subject: [PATCH 074/162] bulk module update --- modules.json | 44 +-- modules/nf-core/busco/busco/environment.yml | 4 +- modules/nf-core/busco/busco/main.nf | 60 ++-- modules/nf-core/busco/busco/meta.yml | 117 ++++--- .../nf-core/busco/busco/tests/main.nf.test | 80 +---- .../busco/busco/tests/main.nf.test.snap | 156 ++++------ modules/nf-core/fastplong/tests/main.nf.test | 2 +- modules/nf-core/flye/meta.yml | 56 ++-- modules/nf-core/hifiasm/main.nf | 2 +- modules/nf-core/hifiasm/meta.yml | 68 +++-- modules/nf-core/hifiasm/tests/main.nf.test | 2 +- modules/nf-core/liftoff/meta.yml | 57 ++-- modules/nf-core/liftoff/tests/main.nf.test | 4 +- modules/nf-core/links/main.nf | 2 +- modules/nf-core/links/meta.yml | 87 +++--- modules/nf-core/links/tests/main.nf.test | 6 +- modules/nf-core/merqury/merqury/meta.yml | 94 +++--- .../merqury/merqury/tests/main.nf.test | 2 +- modules/nf-core/meryl/count/meta.yml | 23 +- modules/nf-core/meryl/unionsum/meta.yml | 22 +- modules/nf-core/minimap2/align/meta.yml | 57 ++-- .../nf-core/minimap2/align/tests/main.nf.test | 2 +- modules/nf-core/pilon/meta.yml | 62 ++-- modules/nf-core/ragtag/patch/main.nf | 10 +- modules/nf-core/ragtag/patch/meta.yml | 61 ++-- modules/nf-core/ragtag/scaffold/main.nf | 2 +- modules/nf-core/ragtag/scaffold/meta.yml | 32 +- .../ragtag/scaffold/tests/main.nf.test | 4 +- .../nf-core/samtools/fastq/environment.yml | 6 +- modules/nf-core/samtools/fastq/main.nf | 7 +- modules/nf-core/samtools/fastq/meta.yml | 46 +-- .../samtools/fastq/tests/main.nf.test.snap | 32 +- .../nf-core/samtools/flagstat/environment.yml | 6 +- modules/nf-core/samtools/flagstat/main.nf | 18 +- modules/nf-core/samtools/flagstat/meta.yml | 19 +- .../samtools/flagstat/tests/main.nf.test.snap | 24 +- .../nf-core/samtools/idxstats/environment.yml | 6 +- modules/nf-core/samtools/idxstats/main.nf | 5 +- modules/nf-core/samtools/idxstats/meta.yml | 19 +- .../samtools/idxstats/tests/main.nf.test.snap | 20 +- .../nf-core/samtools/index/environment.yml | 6 +- modules/nf-core/samtools/index/main.nf | 6 +- modules/nf-core/samtools/index/meta.yml | 28 +- .../samtools/index/tests/main.nf.test.snap | 58 ++-- modules/nf-core/samtools/sort/environment.yml | 6 +- modules/nf-core/samtools/sort/main.nf | 60 ++-- modules/nf-core/samtools/sort/meta.yml | 67 +++- .../nf-core/samtools/sort/tests/main.nf.test | 144 ++++++++- .../samtools/sort/tests/main.nf.test.snap | 288 +++++++++++++----- .../samtools/sort/tests/nextflow.config | 1 - .../nf-core/samtools/stats/environment.yml | 6 +- modules/nf-core/samtools/stats/main.nf | 4 +- modules/nf-core/samtools/stats/meta.yml | 20 +- .../samtools/stats/tests/main.nf.test.snap | 48 +-- modules/nf-core/trimgalore/meta.yml | 36 +-- modules/nf-core/trimgalore/tests/main.nf.test | 2 +- .../local/prepare/prepare_shortreads/main.nf | 2 +- .../nf-core/bam_sort_stats_samtools/main.nf | 2 +- .../tests/main.nf.test.snap | 88 +++--- .../tests/main.nf.test.snap | 108 +++---- 60 files changed, 1369 insertions(+), 937 deletions(-) diff --git a/modules.json b/modules.json index 0fff4232..2dc69151 100644 --- a/modules.json +++ b/modules.json @@ -7,7 +7,7 @@ "nf-core": { "busco/busco": { "branch": "master", - "git_sha": "05954dab2ff481bcb999f24455da29a5828af08d", + "git_sha": "e753770db613ce014b3c4bc94f6cba443427b726", "installed_by": ["modules"] }, "fastp": { @@ -17,7 +17,7 @@ }, "fastplong": { "branch": "master", - "git_sha": "88869fced9dc0c56043eff2dc748a5f47c57495d", + "git_sha": "e753770db613ce014b3c4bc94f6cba443427b726", "installed_by": ["modules"], "patch": "modules/nf-core/fastplong/fastplong.diff" }, @@ -28,96 +28,96 @@ }, "flye": { "branch": "master", - "git_sha": "05954dab2ff481bcb999f24455da29a5828af08d", + "git_sha": "41dfa3f7c0ffabb96a6a813fe321c6d1cc5b6e46", "installed_by": ["modules"] }, "hifiasm": { "branch": "master", - "git_sha": "c457b50bf9187031f65b0fb090dc022e8814c729", + "git_sha": "e753770db613ce014b3c4bc94f6cba443427b726", "installed_by": ["modules"] }, "liftoff": { "branch": "master", - "git_sha": "81880787133db07d9b4c1febd152c090eb8325dc", + "git_sha": "e753770db613ce014b3c4bc94f6cba443427b726", "installed_by": ["modules"], "patch": "modules/nf-core/liftoff/liftoff.diff" }, "links": { "branch": "master", - "git_sha": "bd049fd0244ed914f2d10bed580b49fb44eba914", + "git_sha": "e753770db613ce014b3c4bc94f6cba443427b726", "installed_by": ["modules"], "patch": "modules/nf-core/links/links.diff" }, "merqury/merqury": { "branch": "master", - "git_sha": "05954dab2ff481bcb999f24455da29a5828af08d", + "git_sha": "e753770db613ce014b3c4bc94f6cba443427b726", "installed_by": ["modules"], "patch": "modules/nf-core/merqury/merqury/merqury-merqury.diff" }, "meryl/count": { "branch": "master", - "git_sha": "05954dab2ff481bcb999f24455da29a5828af08d", + "git_sha": "41dfa3f7c0ffabb96a6a813fe321c6d1cc5b6e46", "installed_by": ["modules"] }, "meryl/unionsum": { "branch": "master", - "git_sha": "05954dab2ff481bcb999f24455da29a5828af08d", + "git_sha": "41dfa3f7c0ffabb96a6a813fe321c6d1cc5b6e46", "installed_by": ["modules"] }, "minimap2/align": { "branch": "master", - "git_sha": "a532706a19b3d83f14b1d48a6a815ed33eb48b0c", + "git_sha": "e753770db613ce014b3c4bc94f6cba443427b726", "installed_by": ["modules"], "patch": "modules/nf-core/minimap2/align/minimap2-align.diff" }, "pilon": { "branch": "master", - "git_sha": "05954dab2ff481bcb999f24455da29a5828af08d", + "git_sha": "41dfa3f7c0ffabb96a6a813fe321c6d1cc5b6e46", "installed_by": ["modules"] }, "ragtag/patch": { "branch": "master", - "git_sha": "62775d90df7565c82bd4ceedca70149529820cff", + "git_sha": "41dfa3f7c0ffabb96a6a813fe321c6d1cc5b6e46", "installed_by": ["modules"] }, "ragtag/scaffold": { "branch": "master", - "git_sha": "7d163aded9221aef68d8c11cb7a04354a232d89c", + "git_sha": "e753770db613ce014b3c4bc94f6cba443427b726", "installed_by": ["modules"] }, "samtools/fastq": { "branch": "master", - "git_sha": "05954dab2ff481bcb999f24455da29a5828af08d", + "git_sha": "c8be52dba1166c678e74cda9c3a3c221635c8bb1", "installed_by": ["modules"] }, "samtools/flagstat": { "branch": "master", - "git_sha": "05954dab2ff481bcb999f24455da29a5828af08d", + "git_sha": "e334e12a1e985adc5ffc3fc78a68be1de711de45", "installed_by": ["bam_stats_samtools", "modules"] }, "samtools/idxstats": { "branch": "master", - "git_sha": "05954dab2ff481bcb999f24455da29a5828af08d", + "git_sha": "c8be52dba1166c678e74cda9c3a3c221635c8bb1", "installed_by": ["bam_stats_samtools", "modules"] }, "samtools/index": { "branch": "master", - "git_sha": "05954dab2ff481bcb999f24455da29a5828af08d", + "git_sha": "c8be52dba1166c678e74cda9c3a3c221635c8bb1", "installed_by": ["bam_sort_stats_samtools", "modules"] }, "samtools/sort": { "branch": "master", - "git_sha": "05954dab2ff481bcb999f24455da29a5828af08d", + "git_sha": "e753770db613ce014b3c4bc94f6cba443427b726", "installed_by": ["bam_sort_stats_samtools", "modules"] }, "samtools/stats": { "branch": "master", - "git_sha": "05954dab2ff481bcb999f24455da29a5828af08d", + "git_sha": "c8be52dba1166c678e74cda9c3a3c221635c8bb1", "installed_by": ["bam_stats_samtools", "modules"] }, "trimgalore": { "branch": "master", - "git_sha": "05954dab2ff481bcb999f24455da29a5828af08d", + "git_sha": "e753770db613ce014b3c4bc94f6cba443427b726", "installed_by": ["modules"] } } @@ -126,12 +126,12 @@ "nf-core": { "bam_sort_stats_samtools": { "branch": "master", - "git_sha": "05954dab2ff481bcb999f24455da29a5828af08d", + "git_sha": "c8be52dba1166c678e74cda9c3a3c221635c8bb1", "installed_by": ["subworkflows"] }, "bam_stats_samtools": { "branch": "master", - "git_sha": "05954dab2ff481bcb999f24455da29a5828af08d", + "git_sha": "e334e12a1e985adc5ffc3fc78a68be1de711de45", "installed_by": ["bam_sort_stats_samtools", "subworkflows"] }, "utils_nextflow_pipeline": { diff --git a/modules/nf-core/busco/busco/environment.yml b/modules/nf-core/busco/busco/environment.yml index ba8a40c0..861982d0 100644 --- a/modules/nf-core/busco/busco/environment.yml +++ b/modules/nf-core/busco/busco/environment.yml @@ -3,7 +3,5 @@ channels: - conda-forge - bioconda - dependencies: - - bioconda::busco=5.8.3 - - bioconda::sepp=4.5.5 + - bioconda::busco=6.0.0 diff --git a/modules/nf-core/busco/busco/main.nf b/modules/nf-core/busco/busco/main.nf index 05ac4295..ee4cb266 100644 --- a/modules/nf-core/busco/busco/main.nf +++ b/modules/nf-core/busco/busco/main.nf @@ -4,33 +4,39 @@ process BUSCO_BUSCO { 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/c6/c607f319867d96a38c8502f751458aa78bbd18fe4c7c4fa6b9d8350e6ba11ebe/data' - : 'community.wave.seqera.io/library/busco_sepp:f2dbc18a2f7a5b64'}" + ? 'https://community-cr-prod.seqera.io/docker/registry/v2/blobs/sha256/41/4137d65ab5b90d2ae4fa9d3e0e8294ddccc287e53ca653bb3c63b8fdb03e882f/data' + : 'community.wave.seqera.io/library/busco:6.0.0--a9a1426105f81165'}" + // Note: one test had to be disabled when switching to Busco 6.0.0, cf https://github.com/nf-core/modules/pull/8781/files + // Try to restore it when upgrading Busco to a later version input: - tuple val(meta), path(fasta, stageAs:'tmp_input/*') - val mode // Required: One of genome, proteins, or transcriptome - val lineage // Required: lineage for checking against, or "auto/auto_prok/auto_euk" for enabling auto-lineage - path busco_lineages_path // Recommended: BUSCO lineages file - downloads if not set - path config_file // Optional: BUSCO configuration file - val clean_intermediates // Optional: Remove intermediate files + tuple val(meta), path(fasta, stageAs: 'tmp_input/*') + // Required: One of genome, proteins, or transcriptome + val mode + // Required: lineage for checking against, or "auto/auto_prok/auto_euk" for enabling auto-lineage + val lineage + // Recommended: BUSCO lineages file - downloads if not set + path busco_lineages_path + // Optional: BUSCO configuration file + path config_file + val clean_intermediates output: - tuple val(meta), path("*-busco.batch_summary.txt") , emit: batch_summary - tuple val(meta), path("short_summary.*.txt") , emit: short_summaries_txt , optional: true - tuple val(meta), path("short_summary.*.json") , emit: short_summaries_json, optional: true - tuple val(meta), path("*-busco.log") , emit: log , optional: true - tuple val(meta), path("*-busco/*/run_*/full_table.tsv") , emit: full_table , optional: true - tuple val(meta), path("*-busco/*/run_*/missing_busco_list.tsv") , emit: missing_busco_list , optional: true - tuple val(meta), path("*-busco/*/run_*/single_copy_proteins.faa") , emit: single_copy_proteins, optional: true - tuple val(meta), path("*-busco/*/run_*/busco_sequences") , emit: seq_dir , optional: true - tuple val(meta), path("*-busco/*/translated_proteins") , emit: translated_dir , optional: true - tuple val(meta), path("*-busco") , emit: busco_dir - tuple val(meta), path("busco_downloads/lineages/*") , emit: downloaded_lineages , optional: true - tuple val(meta), path("*-busco/*/run_*/busco_sequences/single_copy_busco_sequences/*.faa"), emit: single_copy_faa , optional: true - tuple val(meta), path("*-busco/*/run_*/busco_sequences/single_copy_busco_sequences/*.fna"), emit: single_copy_fna , optional: true + tuple val(meta), path("*-busco.batch_summary.txt"), emit: batch_summary + tuple val(meta), path("short_summary.*.txt"), emit: short_summaries_txt, optional: true + tuple val(meta), path("short_summary.*.json"), emit: short_summaries_json, optional: true + tuple val(meta), path("*-busco.log"), emit: log, optional: true + tuple val(meta), path("*-busco/*/run_*/full_table.tsv"), emit: full_table, optional: true + tuple val(meta), path("*-busco/*/run_*/missing_busco_list.tsv"), emit: missing_busco_list, optional: true + tuple val(meta), path("*-busco/*/run_*/single_copy_proteins.faa"), emit: single_copy_proteins, optional: true + tuple val(meta), path("*-busco/*/run_*/busco_sequences"), emit: seq_dir, optional: true + tuple val(meta), path("*-busco/*/translated_proteins"), emit: translated_dir, optional: true + tuple val(meta), path("*-busco"), emit: busco_dir + tuple val(meta), path("busco_downloads/lineages/*"), emit: downloaded_lineages, optional: true + tuple val(meta), path("*-busco/*/run_*/busco_sequences/single_copy_busco_sequences/*.faa"), emit: single_copy_faa, optional: true + tuple val(meta), path("*-busco/*/run_*/busco_sequences/single_copy_busco_sequences/*.fna"), emit: single_copy_fna, optional: true - path "versions.yml" , emit: versions + path "versions.yml", emit: versions when: task.ext.when == null || task.ext.when @@ -104,9 +110,15 @@ process BUSCO_BUSCO { mv ${prefix}-busco/*/short_summary.*.{json,txt} . || echo "Short summaries were not available: No genes were found." mv ${prefix}-busco/logs/busco.log ${prefix}-busco.log + if grep 'Run failed; check logs' ${prefix}-busco.batch_summary.txt > /dev/null + then + echo "Busco run failed" + exit 1 + fi + cat <<-END_VERSIONS > versions.yml "${task.process}": - busco: \$( busco --version 2>&1 | sed 's/^BUSCO //' ) + busco: \$( busco --version 2> /dev/null | sed 's/BUSCO //g' ) END_VERSIONS """ @@ -119,7 +131,7 @@ process BUSCO_BUSCO { cat <<-END_VERSIONS > versions.yml "${task.process}": - busco: \$( busco --version 2>&1 | sed 's/^BUSCO //' ) + busco: \$( busco --version 2> /dev/null | sed 's/BUSCO //g' ) END_VERSIONS """ } diff --git a/modules/nf-core/busco/busco/meta.yml b/modules/nf-core/busco/busco/meta.yml index 0222e490..281e3db0 100644 --- a/modules/nf-core/busco/busco/meta.yml +++ b/modules/nf-core/busco/busco/meta.yml @@ -26,26 +26,28 @@ input: type: file description: Nucleic or amino acid sequence file in FASTA format. pattern: "*.{fasta,fna,fa,fasta.gz,fna.gz,fa.gz}" - - - mode: - type: string - description: The mode to run Busco in. One of genome, proteins, or transcriptome - pattern: "{genome,proteins,transcriptome}" - - - lineage: - type: string - description: The BUSCO lineage to use, or "auto", "auto_prok" or "auto_euk" - to automatically select lineage - - - busco_lineages_path: - type: directory - description: Path to local BUSCO lineages directory. - - - config_file: - type: file - description: Path to BUSCO config file. - - - clean_intermediates: - type: boolean - description: Flag to remove intermediate files. + ontologies: [] + - mode: + type: string + description: The mode to run Busco in. One of genome, proteins, or transcriptome + pattern: "{genome,proteins,transcriptome}" + - lineage: + type: string + description: The BUSCO lineage to use, or "auto", "auto_prok" or "auto_euk" to + automatically select lineage + - busco_lineages_path: + type: directory + description: Path to local BUSCO lineages directory. + - config_file: + type: file + description: Path to BUSCO config file. + ontologies: [] + - clean_intermediates: + type: boolean + description: Flag to remove intermediate files. output: - - batch_summary: - - meta: + batch_summary: + - - meta: type: map description: | Groovy Map containing sample information @@ -54,8 +56,9 @@ output: type: file description: Summary of all sequence files analyzed pattern: "*-busco.batch_summary.txt" - - short_summaries_txt: - - meta: + ontologies: [] + short_summaries_txt: + - - meta: type: map description: | Groovy Map containing sample information @@ -64,8 +67,9 @@ output: type: file description: Short Busco summary in plain text format pattern: "short_summary.*.txt" - - short_summaries_json: - - meta: + ontologies: [] + short_summaries_json: + - - meta: type: map description: | Groovy Map containing sample information @@ -74,8 +78,10 @@ output: type: file description: Short Busco summary in JSON format pattern: "short_summary.*.json" - - log: - - meta: + ontologies: + - edam: http://edamontology.org/format_3464 # JSON + log: + - - meta: type: map description: | Groovy Map containing sample information @@ -84,8 +90,9 @@ output: type: file description: BUSCO main log pattern: "*-busco.log" - - full_table: - - meta: + ontologies: [] + full_table: + - - meta: type: map description: | Groovy Map containing sample information @@ -94,8 +101,10 @@ output: type: file description: Full BUSCO results table pattern: "full_table.tsv" - - missing_busco_list: - - meta: + ontologies: + - edam: http://edamontology.org/format_3475 # TSV + missing_busco_list: + - - meta: type: map description: | Groovy Map containing sample information @@ -104,8 +113,10 @@ output: type: file description: List of missing BUSCOs pattern: "missing_busco_list.tsv" - - single_copy_proteins: - - meta: + ontologies: + - edam: http://edamontology.org/format_3475 # TSV + single_copy_proteins: + - - meta: type: map description: | Groovy Map containing sample information @@ -114,8 +125,9 @@ output: type: file description: Fasta file of single copy proteins (transcriptome mode) pattern: "single_copy_proteins.faa" - - seq_dir: - - meta: + ontologies: [] + seq_dir: + - - meta: type: map description: | Groovy Map containing sample information @@ -124,8 +136,8 @@ output: type: directory description: BUSCO sequence directory pattern: "busco_sequences" - - translated_dir: - - meta: + translated_dir: + - - meta: type: map description: | Groovy Map containing sample information @@ -135,8 +147,8 @@ output: description: Six frame translations of each transcript made by the transcriptome mode pattern: "translated_dir" - - busco_dir: - - meta: + busco_dir: + - - meta: type: map description: | Groovy Map containing sample information @@ -145,18 +157,19 @@ output: type: directory description: BUSCO lineage specific output pattern: "*-busco" - - downloaded_lineages: - - meta: + downloaded_lineages: + - - meta: type: map description: | Groovy Map containing sample information e.g. [ id:'test' ] - - "busco_downloads/lineages/*": + - busco_downloads/lineages/*: type: directory - description: Lineages downloaded by BUSCO when running the analysis, for example bacteria_odb12 + description: Lineages downloaded by BUSCO when running the analysis, for example + bacteria_odb12 pattern: "busco_downloads/lineages/*" - - single_copy_faa: - - meta: + single_copy_faa: + - - meta: type: map description: | Groovy Map containing sample information @@ -165,8 +178,9 @@ output: type: file description: Single copy .faa sequence files pattern: "*-busco/*/run_*/busco_sequences/single_copy_busco_sequences/*.faa" - - single_copy_fna: - - meta: + ontologies: [] + single_copy_fna: + - - meta: type: map description: | Groovy Map containing sample information @@ -175,11 +189,14 @@ output: type: file description: Single copy .fna sequence files pattern: "*-busco/*/run_*/busco_sequences/single_copy_busco_sequences/*.fna" - - 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: - "@priyanka-surana" - "@charles-plessy" diff --git a/modules/nf-core/busco/busco/tests/main.nf.test b/modules/nf-core/busco/busco/tests/main.nf.test index 411ceb86..5610ed93 100644 --- a/modules/nf-core/busco/busco/tests/main.nf.test +++ b/modules/nf-core/busco/busco/tests/main.nf.test @@ -24,7 +24,7 @@ nextflow_process { file(params.modules_testdata_base_path + 'genomics/prokaryotes/bacteroides_fragilis/genome/genome.fna.gz', checkIfExists: true) ] input[1] = 'genome' - input[2] = 'bacteria_odb12' // Launch with 'auto' to use --auto-lineage, and specified lineages // 'auto' removed from test due to memory issues + input[2] = 'bacteria_odb10' // Launch with 'auto' to use --auto-lineage, and specified lineages // 'auto' removed from test due to memory issues input[3] = [] // Download busco lineage input[4] = [] // No config input[5] = false // Clean intermediates @@ -92,7 +92,7 @@ nextflow_process { ] ] input[1] = 'genome' - input[2] = 'bacteria_odb12' + input[2] = 'bacteria_odb10' input[3] = [] input[4] = [] input[5] = false @@ -163,72 +163,6 @@ nextflow_process { } - test("test_busco_eukaryote_metaeuk") { - - config './nextflow.config' - - when { - params { - busco_args = '--tar --metaeuk' - } - process { - """ - input[0] = [ - [ id:'test' ], // meta map - file(params.modules_testdata_base_path + 'genomics/homo_sapiens/genome/genome.fasta', checkIfExists: true) - ] - input[1] = 'genome' - input[2] = 'eukaryota_odb10' - input[3] = [] - input[4] = [] - input[5] = false - """ - } - } - - then { - assert process.success - - with(path(process.out.short_summaries_txt[0][1]).text) { - assert contains('BUSCO version') - assert contains('The lineage dataset is') - assert contains('BUSCO was run in mode') - assert contains('Complete BUSCOs') - assert contains('Missing BUSCOs') - assert contains('Dependencies and versions') - } - - with(path(process.out.short_summaries_json[0][1]).text) { - assert contains('one_line_summary') - assert contains('mode') - assert contains('dataset') - } - - assert snapshot( - process.out.batch_summary[0][1], - process.out.full_table[0][1], - process.out.missing_busco_list[0][1], - process.out.versions[0] - ).match() - - with(file(process.out.seq_dir[0][1]).listFiles().collect { it.name }) { - assert contains('single_copy_busco_sequences.tar.gz') - assert contains('multi_copy_busco_sequences.tar.gz') - assert contains('fragmented_busco_sequences.tar.gz') - } - - with(path(process.out.log[0][1]).text) { - assert contains('DEBUG:busco.run_BUSCO') - assert contains('Results from dataset') - assert contains('how to cite BUSCO') - - } - - assert process.out.single_copy_proteins == [] - assert process.out.translated_dir == [] - } - - } test("test_busco_eukaryote_augustus") { @@ -292,7 +226,7 @@ nextflow_process { file(params.modules_testdata_base_path + 'genomics/prokaryotes/candidatus_portiera_aleyrodidarum/genome/proteome.fasta', checkIfExists: true) ] input[1] = 'proteins' - input[2] = 'bacteria_odb12' + input[2] = 'bacteria_odb10' input[3] = [] input[4] = [] input[5] = false @@ -358,7 +292,7 @@ nextflow_process { file(params.modules_testdata_base_path + 'genomics/prokaryotes/bacteroides_fragilis/illumina/fasta/test1.contigs.fa.gz', checkIfExists: true) ] input[1] = 'transcriptome' - input[2] = 'bacteria_odb12' + input[2] = 'bacteria_odb10' input[3] = [] input[4] = [] input[5] = false @@ -423,7 +357,7 @@ nextflow_process { file(params.modules_testdata_base_path + 'genomics/prokaryotes/bacteroides_fragilis/genome/genome.fna.gz', checkIfExists: true) ] input[1] = 'genome' - input[2] = 'bacteria_odb12' + input[2] = 'bacteria_odb10' input[3] = [] input[4] = [] input[5] = true @@ -467,7 +401,7 @@ nextflow_process { file(params.modules_testdata_base_path + 'genomics/prokaryotes/bacteroides_fragilis/genome/genome.fna.gz', checkIfExists: true) ] input[1] = 'genome' - input[2] = 'bacteria_odb12' + input[2] = 'bacteria_odb10' input[3] = [] input[4] = [] input[5] = false @@ -485,4 +419,4 @@ nextflow_process { ) } } -} \ No newline at end of file +} diff --git a/modules/nf-core/busco/busco/tests/main.nf.test.snap b/modules/nf-core/busco/busco/tests/main.nf.test.snap index 1026524b..5de40123 100644 --- a/modules/nf-core/busco/busco/tests/main.nf.test.snap +++ b/modules/nf-core/busco/busco/tests/main.nf.test.snap @@ -6,157 +6,123 @@ { "id": "test" }, - "test-bacteria_odb12-busco.batch_summary.txt:md5,d41d8cd98f00b204e9800998ecf8427e" + "test-bacteria_odb10-busco.batch_summary.txt:md5,d41d8cd98f00b204e9800998ecf8427e" ] ], [ - "versions.yml:md5,0046a4b8575cbc3635f2a9ee616fd840" + "versions.yml:md5,d3cecb346ce389a471bd041a53617d05" ] ], "meta": { "nf-test": "0.9.2", - "nextflow": "24.10.3" + "nextflow": "25.04.6" }, - "timestamp": "2025-03-12T10:50:57.218573431" + "timestamp": "2025-07-21T16:11:16.371060201" }, "test_busco_eukaryote_augustus": { "content": [ "test-eukaryota_odb10-busco.batch_summary.txt:md5,3ea3bdc423a461dae514d816bdc61c89", - "versions.yml:md5,0046a4b8575cbc3635f2a9ee616fd840" + "versions.yml:md5,d3cecb346ce389a471bd041a53617d05" ], "meta": { "nf-test": "0.9.2", - "nextflow": "24.10.3" + "nextflow": "25.04.6" }, - "timestamp": "2025-03-12T10:44:25.359421247" + "timestamp": "2025-07-21T16:09:47.906365972" }, "test_busco_genome_single_fasta": { "content": [ - "test-bacteria_odb12-busco.batch_summary.txt:md5,e3e503e1540b633d95c273c465945740", - "full_table.tsv:md5,086f2ecdc90d47745c828c9b25357039", - "missing_busco_list.tsv:md5,9919aee2da9d30a3985aede354850a46", - "versions.yml:md5,0046a4b8575cbc3635f2a9ee616fd840" + "test-bacteria_odb10-busco.batch_summary.txt:md5,12e911830d66bab6dbf3523ac4392597", + "full_table.tsv:md5,660e2f556ca6efa97f0c2a8cebd94786", + "missing_busco_list.tsv:md5,0e08587f4dc65d9226a31433c1f9ba25", + "versions.yml:md5,d3cecb346ce389a471bd041a53617d05" ], "meta": { "nf-test": "0.9.2", - "nextflow": "24.10.3" + "nextflow": "25.04.6" }, - "timestamp": "2025-03-12T10:41:46.251404188" + "timestamp": "2025-07-21T16:08:41.497678114" }, "test_busco_genome_multi_fasta": { "content": [ [ - "full_table.tsv:md5,5a6bf59055e2040e74797a1e36c8e374", - "full_table.tsv:md5,086f2ecdc90d47745c828c9b25357039" + "full_table.tsv:md5,26b1d35d975593834acb4d4a91e225a1", + "full_table.tsv:md5,660e2f556ca6efa97f0c2a8cebd94786" ], [ - "missing_busco_list.tsv:md5,a55eee6869fad9176d812e59886232fb", - "missing_busco_list.tsv:md5,9919aee2da9d30a3985aede354850a46" + "missing_busco_list.tsv:md5,5dcdc7707035904a7d467ca1026b399a", + "missing_busco_list.tsv:md5,0e08587f4dc65d9226a31433c1f9ba25" ], - "versions.yml:md5,0046a4b8575cbc3635f2a9ee616fd840" + "versions.yml:md5,d3cecb346ce389a471bd041a53617d05" ], "meta": { "nf-test": "0.9.2", - "nextflow": "24.10.3" + "nextflow": "25.04.6" }, - "timestamp": "2025-03-12T10:42:28.126899794" - }, - "test_busco_eukaryote_metaeuk": { - "content": [ - "test-eukaryota_odb10-busco.batch_summary.txt:md5,ff6d8277e452a83ce9456bbee666feb6", - "full_table.tsv:md5,cfb55ab2ce590d2def51926324691aa8", - "missing_busco_list.tsv:md5,77e3d4503b2c13db0d611723fc83ab7e", - "versions.yml:md5,0046a4b8575cbc3635f2a9ee616fd840" - ], - "meta": { - "nf-test": "0.9.2", - "nextflow": "24.10.3" - }, - "timestamp": "2025-03-12T10:43:59.997031348" + "timestamp": "2025-07-21T16:09:25.578789984" }, "test_busco_cleanup": { "content": [ - "test-bacteria_odb12-busco.batch_summary.txt:md5,e3e503e1540b633d95c273c465945740", - "full_table.tsv:md5,086f2ecdc90d47745c828c9b25357039", - "missing_busco_list.tsv:md5,9919aee2da9d30a3985aede354850a46", - "versions.yml:md5,0046a4b8575cbc3635f2a9ee616fd840" + "test-bacteria_odb10-busco.batch_summary.txt:md5,12e911830d66bab6dbf3523ac4392597", + "full_table.tsv:md5,660e2f556ca6efa97f0c2a8cebd94786", + "missing_busco_list.tsv:md5,0e08587f4dc65d9226a31433c1f9ba25", + "versions.yml:md5,d3cecb346ce389a471bd041a53617d05" ], "meta": { "nf-test": "0.9.2", - "nextflow": "24.10.3" + "nextflow": "25.04.6" }, - "timestamp": "2025-03-12T10:50:48.928173488" + "timestamp": "2025-07-21T16:11:08.495786376" }, "test_busco_transcriptome": { "content": [ - "test-bacteria_odb12-busco.batch_summary.txt:md5,6cd69d8a66b5f8b7fd4a9de758e7a739", - "full_table.tsv:md5,4efc19f8d2cc7ea9e73425f09cb3ed97", - "missing_busco_list.tsv:md5,55f0322d494e5c165508712be63062bf", + "test-bacteria_odb10-busco.batch_summary.txt:md5,8734b3f379c4c0928e5dd4ea1873dc64", + "full_table.tsv:md5,645b65b725fd8b30ff6808e0ac671a73", + "missing_busco_list.tsv:md5,b1cc1c22d484439ac128af2290d7d9dd", [ - "9767721at2.faa:md5,1731738ca153959391f8302fd5a3679f", - "9778364at2.faa:md5,7a19a6b6696ae53efce30457b4dd1ab2", - "9782003at2.faa:md5,65d2a613c903852681981f8e8427dc70", - "9790352at2.faa:md5,5e18cfb68122dff7a61c5517246223fc", - "9791908at2.faa:md5,707ef4501f93a6e0dc217e037f26da54", - "9793681at2.faa:md5,e361d654145e70f06c386e75ad90f943", - "9800696at2.faa:md5,9e2f431e4aada7bdc2c317747105b874", - "9801107at2.faa:md5,83933b1426fc9abfe8891c49838cd02f", - "9801213at2.faa:md5,ec340354a86728189c3d1a294c0ccbad", - "9801753at2.faa:md5,39c09bd8a831c90aab44ded14c56d0e6", - "9802065at2.faa:md5,8361fa013dc1cd29af938c9d5ffebfe4", - "9802219at2.faa:md5,9e23aed07790f460da634f7f6132e73d", - "9802304at2.faa:md5,86b259197441716075f3d3d18f8743ba", - "9802309at2.faa:md5,b4b4613e9b69baa9274140c1b26cc27b", - "9802672at2.faa:md5,6c6d592c2fbb0d7a4e5e1f47a15644f0", - "9803420at2.faa:md5,eec6f7189ce9a596ed6ead06f2229c8a", - "9803541at2.faa:md5,132954cc7bfcb1c1fe9da105867c4b78", - "9803667at2.faa:md5,ec31d499f6b523cb081af6a3284a5a5c", - "9803773at2.faa:md5,efbe4c35075dd8c871827d4e5ac72922", - "9804006at2.faa:md5,fca5b560714ba37be0be3e2597f74c5a", - "9804243at2.faa:md5,3280570e4357fb4daedaea8a066dbf0b", - "9804478at2.faa:md5,98c2cfd8f089812a41a1e66fea630b2d", - "9804933at2.faa:md5,de648025c49061c614c77e7c9ce7ab62", - "9805026at2.faa:md5,eea9da88f3cd718514493d6890bf7660", - "9806637at2.faa:md5,c8a9e0c37a8aeb1fd44db64fd93aa3e1", - "9806651at2.faa:md5,f5abacf8930d78c81fdeb0c91c8681a7", - "9807064at2.faa:md5,1167d5c4c044b4eb82fac5d1955e7130", - "9807233at2.faa:md5,7c8adb6556a7f9a0244e7c7e5f75f20d", - "9807240at2.faa:md5,2eff2de1ab83b22f3234a529a44e22bb", - "9807458at2.faa:md5,bee695d260b2b7f8980a636fed6aa0c0", - "9808036at2.faa:md5,797ca476d2c7820151fec98d2815d6cb", - "9808348at2.faa:md5,4e8573a5d287e01aa4f5de8b48feaa42", - "9808936at2.faa:md5,30333f3f62f8e3d0ea6f6544d49572c6", - "9809052at2.faa:md5,0590efbf94fce0ad212513dcb2e8176f", - "9809084at2.faa:md5,37e6214b4204dc31858e2ef2bad5db4a", - "9809356at2.faa:md5,e18c1d5a4931a25baf7dbd1a40c417dc", - "9809796at2.faa:md5,857aac8a22c00472bfc9add7fde94c5c", - "9810191at2.faa:md5,72b63933bb045b680e0635eb03915cc0", - "9811804at2.faa:md5,da341c24e763a949d16432bb052af321", - "9812272at2.faa:md5,7a54f872dd8243c6814852d40cf1bfc0", - "9812943at2.faa:md5,149da17f067cdce328a73f6364a95b26", - "9813375at2.faa:md5,49835b9f3188434c771a840b628b07f6", - "9814755at2.faa:md5,9b4c4648d250c2e6d04acb78f9cf6df0" + "1024388at2.faa:md5,797d603d262a6595a112e25b73e878b0", + "1054741at2.faa:md5,cd4b928cba6b19b4437746ba507e7195", + "1093223at2.faa:md5,df9549708e5ffcfaee6a74dd70a0e5dc", + "1151822at2.faa:md5,12726afc1cdc40c13392e1596e93df3a", + "143460at2.faa:md5,d887431fd988a5556a523440f02d9594", + "1491686at2.faa:md5,d03362d19979b27306c192f1c74a84e5", + "1504821at2.faa:md5,4f5f6e5c57bac0092c1d85ded73d7e67", + "1574817at2.faa:md5,1153e55998c2929eacad2aed7d08d248", + "1592033at2.faa:md5,bb7a59e5f3a57ba12d10dabf4c77ab57", + "1623045at2.faa:md5,8fe38155feb1802beb97ef7714837bf5", + "1661836at2.faa:md5,6c6d592c2fbb0d7a4e5e1f47a15644f0", + "1674344at2.faa:md5,bb41b44e53565a54cadf0b780532fe08", + "1698718at2.faa:md5,f233860000028eb00329aa85236c71e5", + "1990650at2.faa:md5,34a2d29c5f8b6253159ddb7a43fa1829", + "223233at2.faa:md5,dec6705c7846c989296e73942f953cbc", + "402899at2.faa:md5,acc0f271f9a586d2ce1ee41669b22999", + "505485at2.faa:md5,aa0391f8fa5d9bd19b30d844d5a99845", + "665824at2.faa:md5,47f8ad43b6a6078206feb48c2e552793", + "776861at2.faa:md5,f8b90c13f7c6be828dea3bb920195e3d", + "874197at2.faa:md5,8d22a35a768debe6f376fc695d233a69", + "932854at2.faa:md5,2eff2de1ab83b22f3234a529a44e22bb", + "95696at2.faa:md5,247bfd1aef432f7b5456307768e9149c" ], - "single_copy_proteins.faa:md5,14124def13668c6d9b0d589207754b31", - "versions.yml:md5,0046a4b8575cbc3635f2a9ee616fd840" + "single_copy_proteins.faa:md5,73e2c5d6a9b0f01f2deea3cc5f21b764", + "versions.yml:md5,d3cecb346ce389a471bd041a53617d05" ], "meta": { "nf-test": "0.9.2", - "nextflow": "24.10.3" + "nextflow": "25.04.6" }, - "timestamp": "2025-03-12T10:45:08.029718703" + "timestamp": "2025-07-21T16:10:28.783205973" }, "test_busco_protein": { "content": [ - "test-bacteria_odb12-busco.batch_summary.txt:md5,44d4cdebd61a3c8e8981ddf1829f83b3", - "full_table.tsv:md5,350f9b1b6c37cfcf41be84e93ef41931", - "missing_busco_list.tsv:md5,a55eee6869fad9176d812e59886232fb", - "versions.yml:md5,0046a4b8575cbc3635f2a9ee616fd840" + "test-bacteria_odb10-busco.batch_summary.txt:md5,942dbb2d8ff26240860a794213db14a8", + "full_table.tsv:md5,4db33686f2755a09fdc9521ca89411bc", + "missing_busco_list.tsv:md5,5dcdc7707035904a7d467ca1026b399a", + "versions.yml:md5,d3cecb346ce389a471bd041a53617d05" ], "meta": { "nf-test": "0.9.2", - "nextflow": "24.10.3" + "nextflow": "25.04.6" }, - "timestamp": "2025-03-12T10:44:44.094048564" + "timestamp": "2025-07-21T16:10:05.674445797" } } \ No newline at end of file diff --git a/modules/nf-core/fastplong/tests/main.nf.test b/modules/nf-core/fastplong/tests/main.nf.test index 091901ec..5bcf1081 100644 --- a/modules/nf-core/fastplong/tests/main.nf.test +++ b/modules/nf-core/fastplong/tests/main.nf.test @@ -67,4 +67,4 @@ nextflow_process { } -} \ No newline at end of file +} diff --git a/modules/nf-core/flye/meta.yml b/modules/nf-core/flye/meta.yml index 1e33c275..8e338b6c 100644 --- a/modules/nf-core/flye/meta.yml +++ b/modules/nf-core/flye/meta.yml @@ -27,13 +27,15 @@ input: description: Input reads from Oxford Nanopore or PacBio data in FASTA/FASTQ format. pattern: "*.{fasta,fastq,fasta.gz,fastq.gz,fa,fq,fa.gz,fq.gz}" - - - mode: - type: string - description: Flye mode depending on the input data (source and error rate) - pattern: "--pacbio-raw|--pacbio-corr|--pacbio-hifi|--nano-raw|--nano-corr|--nano-hq" + ontologies: + - edam: http://edamontology.org/format_1930 # FASTQ + - mode: + type: string + description: Flye mode depending on the input data (source and error rate) + pattern: "--pacbio-raw|--pacbio-corr|--pacbio-hifi|--nano-raw|--nano-corr|--nano-hq" output: - - fasta: - - meta: + fasta: + - - meta: type: map description: | Groovy Map containing sample information @@ -42,8 +44,10 @@ output: type: file description: Assembled FASTA file pattern: "*.fasta.gz" - - gfa: - - meta: + ontologies: + - edam: http://edamontology.org/format_3989 # GZIP format + gfa: + - - meta: type: map description: | Groovy Map containing sample information @@ -52,8 +56,10 @@ output: type: file description: Repeat graph in gfa format pattern: "*.gfa.gz" - - gv: - - meta: + ontologies: + - edam: http://edamontology.org/format_3989 # GZIP format + gv: + - - meta: type: map description: | Groovy Map containing sample information @@ -62,8 +68,10 @@ output: type: file description: Repeat graph in gv format pattern: "*.gv.gz" - - txt: - - meta: + ontologies: + - edam: http://edamontology.org/format_3989 # GZIP format + txt: + - - meta: type: map description: | Groovy Map containing sample information @@ -72,8 +80,9 @@ output: type: file description: Extra information and statistics about resulting contigs pattern: "*.txt" - - log: - - meta: + ontologies: [] + log: + - - meta: type: map description: | Groovy Map containing sample information @@ -82,8 +91,9 @@ output: type: file description: Flye log file pattern: "*.log" - - json: - - meta: + ontologies: [] + json: + - - meta: type: map description: | Groovy Map containing sample information @@ -92,11 +102,15 @@ output: type: file description: Flye parameters pattern: "*.json" - - versions: - - versions.yml: - type: file - description: File containing software versions - pattern: "versions.yml" + ontologies: + - edam: http://edamontology.org/format_3464 # JSON + versions: + - versions.yml: + type: file + description: File containing software versions + pattern: "versions.yml" + ontologies: + - edam: http://edamontology.org/format_3750 # YAML authors: - "@mirpedrol" maintainers: diff --git a/modules/nf-core/hifiasm/main.nf b/modules/nf-core/hifiasm/main.nf index 7330920e..df295b70 100644 --- a/modules/nf-core/hifiasm/main.nf +++ b/modules/nf-core/hifiasm/main.nf @@ -67,7 +67,7 @@ process HIFIASM { ${ultralong} \\ -o ${prefix} \\ ${long_reads_sorted} \\ - 2> >( tee ${prefix}.stderr.log >&2 ) + 2>| >( tee ${prefix}.stderr.log >&2 ) if [ -f ${prefix}.ec.fa ]; then gzip ${prefix}.ec.fa diff --git a/modules/nf-core/hifiasm/meta.yml b/modules/nf-core/hifiasm/meta.yml index fcd211db..272a0828 100644 --- a/modules/nf-core/hifiasm/meta.yml +++ b/modules/nf-core/hifiasm/meta.yml @@ -65,8 +65,8 @@ input: description: bin files produced during a previous Hifiasm run ontologies: [] output: - - raw_unitigs: - - meta: + raw_unitigs: + - - meta: type: map description: | Groovy Map containing sample information @@ -75,8 +75,10 @@ output: type: file description: Raw unitigs pattern: "*.r_utg.gfa" - - bin_files: - - meta: + ontologies: + - edam: http://edamontology.org/format_3975 # GFA 1 + bin_files: + - - meta: type: map description: | Groovy Map containing sample information @@ -91,8 +93,9 @@ output: initial results, which are the most computationally-expensive steps. pattern: "*.bin" - - processed_unitigs: - - meta: + ontologies: [] + processed_unitigs: + - - meta: type: map description: | Groovy Map containing sample information @@ -101,8 +104,10 @@ output: type: file description: Processed unitigs pattern: "*.p_utg.gfa" - - primary_contigs: - - meta: + ontologies: + - edam: http://edamontology.org/format_3975 # GFA 1 + primary_contigs: + - - meta: type: map description: | Groovy Map containing sample information @@ -111,8 +116,9 @@ output: type: file description: Contigs representing the primary assembly pattern: "${prefix}.{p_ctg,bp.p_ctg,hic.p_ctg}.gfa" - - alternate_contigs: - - meta: + ontologies: [] + alternate_contigs: + - - meta: type: map description: | Groovy Map containing sample information @@ -121,13 +127,13 @@ output: type: file description: Contigs representing the alternative assembly pattern: "${prefix}.{a_ctg,hic.a_ctg}.gfa" - - hap1_contigs: - - meta: + ontologies: [] + hap1_contigs: + - - meta: type: map description: | Groovy Map containing sample information e.g. [ id:'test', single_end:false ] - pattern: "${prefix}.*.hap1.p_ctg.gfa" - ${prefix}.*.hap1.p_ctg.gfa: type: file description: | @@ -138,13 +144,13 @@ output: between contigs. In trio mode, they are fully phased paternal contigs all originating from a single parental haplotype. pattern: "${prefix}.*.hap1.p_ctg.gfa" - - hap2_contigs: - - meta: + ontologies: [] + hap2_contigs: + - - meta: type: map description: | Groovy Map containing sample information e.g. [ id:'test', single_end:false ] - pattern: "${prefix}.*.hap2.p_ctg.gfa" - ${prefix}.*.hap2.p_ctg.gfa: type: file description: | @@ -155,8 +161,9 @@ output: between contigs. In trio mode, they are fully phased paternal contigs all originating from a single parental haplotype. pattern: "${prefix}.*.hap2.p_ctg.gfa" - - corrected_reads: - - meta: + ontologies: [] + corrected_reads: + - - meta: type: map description: | Groovy Map containing sample information @@ -167,8 +174,10 @@ output: If option --write-ec specified, a gzipped fasta file containing the error corrected reads produced by the hifiasm error correction module pattern: "*.ec.fa.gz" - - read_overlaps: - - meta: + ontologies: + - edam: http://edamontology.org/format_3989 # GZIP format + read_overlaps: + - - meta: type: map description: | Groovy Map containing sample information @@ -179,23 +188,26 @@ output: If option --write-paf specified, a gzipped paf file describing the overlaps among all error-corrected reads pattern: "*.ovlp.paf.gz" - - log: - - meta: + ontologies: + - edam: http://edamontology.org/format_3989 # GZIP format + log: + - - meta: type: map description: | Groovy Map containing sample information e.g. [ id:'test', single_end:false ] - pattern: "*.stderr.log" - ${prefix}.stderr.log: type: file description: Stderr log pattern: "*.stderr.log" - - 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: - "@sidorov-si" - "@scorreard" diff --git a/modules/nf-core/hifiasm/tests/main.nf.test b/modules/nf-core/hifiasm/tests/main.nf.test index 53edf404..ceda17b9 100644 --- a/modules/nf-core/hifiasm/tests/main.nf.test +++ b/modules/nf-core/hifiasm/tests/main.nf.test @@ -455,4 +455,4 @@ nextflow_process { ) } } -} \ No newline at end of file +} diff --git a/modules/nf-core/liftoff/meta.yml b/modules/nf-core/liftoff/meta.yml index 7d809f7f..984ccb0c 100644 --- a/modules/nf-core/liftoff/meta.yml +++ b/modules/nf-core/liftoff/meta.yml @@ -30,22 +30,26 @@ input: type: file description: Target assembly in fasta format (can be gzipped) pattern: "*.{fa,fa.gz,fasta,fasta.gz,fas,fas.gz,fsa,fsa.gz}" - - - ref_fa: - type: file - description: Reference assembly in fasta format (can be gzipped) - pattern: "*.{fa,fa.gz,fasta,fasta.gz,fas,fas.gz,fsa,fsa.gz}" - - - ref_annotation: - type: file - description: Reference assembly annotations in gtf or gff3 format - pattern: "*.{gtf,gff3}" - - - ref_db: - type: file - description: | - Name of feature database; if not specified, the -g argument must - be provided and a database will be built automatically + ontologies: [] + - ref_fa: + type: file + description: Reference assembly in fasta format (can be gzipped) + pattern: "*.{fa,fa.gz,fasta,fasta.gz,fas,fas.gz,fsa,fsa.gz}" + ontologies: [] + - ref_annotation: + type: file + description: Reference assembly annotations in gtf or gff3 format + pattern: "*.{gtf,gff3}" + ontologies: [] + - ref_db: + type: file + description: | + Name of feature database; if not specified, the -g argument must + be provided and a database will be built automatically + ontologies: [] output: - - gff3: - - meta: + gff3: + - - meta: type: map description: | Groovy Map containing sample information @@ -54,8 +58,9 @@ output: type: file description: Lifted annotations for the target assembly in gff3 format pattern: "*.gff3" - - polished_gff3: - - meta: + ontologies: [] + polished_gff3: + - - meta: type: map description: | Groovy Map containing sample information @@ -64,8 +69,9 @@ output: type: file description: Polished lifted annotations for the target assembly in gff3 format pattern: "*.polished.gff3" - - unmapped_txt: - - meta: + ontologies: [] + unmapped_txt: + - - meta: type: map description: | Groovy Map containing sample information @@ -74,11 +80,14 @@ output: type: file description: List of unmapped reference annotations pattern: "*.unmapped.txt" - - 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: - "@GallVp" maintainers: diff --git a/modules/nf-core/liftoff/tests/main.nf.test b/modules/nf-core/liftoff/tests/main.nf.test index 2a17cc81..161f925b 100644 --- a/modules/nf-core/liftoff/tests/main.nf.test +++ b/modules/nf-core/liftoff/tests/main.nf.test @@ -52,8 +52,8 @@ nextflow_process { [ id:'test' ], file(params.modules_testdata_base_path + 'genomics/homo_sapiens/genome/chr21/sequence/genome.fasta', checkIfExists: true) ] - input[1] = [ - file(params.modules_testdata_base_path + 'genomics/homo_sapiens/genome/chr1/genome.fasta.gz', checkIfExists: true) + input[1] = [ + file(params.modules_testdata_base_path + 'genomics/homo_sapiens/genome/chr1/genome.fasta.gz', checkIfExists: true) ] input[2] = [ file(params.modules_testdata_base_path + 'genomics/homo_sapiens/genome/chr1/genome.gtf', checkIfExists: true) diff --git a/modules/nf-core/links/main.nf b/modules/nf-core/links/main.nf index 3e3ee19c..39c50db4 100644 --- a/modules/nf-core/links/main.nf +++ b/modules/nf-core/links/main.nf @@ -44,7 +44,7 @@ process LINKS { for read_file in ${reads}; do if [[ \$read_file == *.gz ]]; - then + then gzip -dc \$read_file > \$(basename \$read_file .gz) echo \$(basename \$read_file .gz) >> readfile.fof else diff --git a/modules/nf-core/links/meta.yml b/modules/nf-core/links/meta.yml index 852cf2bb..288e1bd7 100644 --- a/modules/nf-core/links/meta.yml +++ b/modules/nf-core/links/meta.yml @@ -1,10 +1,9 @@ ---- name: "links" description: | LINKS is a genomics application for scaffolding genome assemblies with long reads, - such as those produced by Oxford Nanopore Technologies Ltd. - It can be used to scaffold high-quality draft genome assemblies with any long sequences - (eg. ONT reads, PacBio reads, other draft genomes, etc). + such as those produced by Oxford Nanopore Technologies Ltd. + It can be used to scaffold high-quality draft genome assemblies with any long sequences + (eg. ONT reads, PacBio reads, other draft genomes, etc). It is also used to scaffold contig pairs linked by ARCS/ARKS. This module is for LINKS >=2.0.0 and does not support MPET input. keywords: @@ -31,6 +30,7 @@ input: type: file description: (Multi-)fasta file containing the draft assembly pattern: "*.{fa,fasta,fa.gz,fasta.gz}" + ontologies: [] - - meta2: type: map description: | @@ -41,9 +41,11 @@ input: description: fastq file(s) containing the long reads to be used for scaffolding pattern: "*.{fq,fastq,fq.gz,fastq.gz}" + ontologies: + - edam: http://edamontology.org/format_1930 # FASTQ output: - - log: - - meta: + log: + - - meta: type: map description: | Groovy Map containing sample information @@ -52,8 +54,9 @@ output: type: file description: text file; Logs execution time / errors / pairing stats. pattern: "*.log" - - pairing_distribution: - - meta: + ontologies: [] + pairing_distribution: + - - meta: type: map description: | Groovy Map containing sample information @@ -66,8 +69,10 @@ output: within the same contig. 2nd column is the number of pairs at that distance. pattern: "*.pairing_distribution.csv" - - pairing_issues: - - meta: + ontologies: + - edam: http://edamontology.org/format_3752 # CSV + pairing_issues: + - - meta: type: map description: | Groovy Map containing sample information @@ -78,8 +83,9 @@ output: text file; Lists all pairing issues encountered between contig pairs and illogical/out-of-bounds pairing. pattern: "*.pairing_issues" - - scaffolds_csv: - - meta: + ontologies: [] + scaffolds_csv: + - - meta: type: map description: | Groovy Map containing sample information @@ -88,8 +94,9 @@ output: type: file description: comma-separated file; containing the new scaffold(s) pattern: "*.scaffolds" - - scaffolds_fasta: - - meta: + ontologies: [] + scaffolds_fasta: + - - meta: type: map description: | Groovy Map containing sample information @@ -98,8 +105,9 @@ output: type: file description: fasta file of the new scaffold sequence pattern: "*.scaffolds.fa" - - bloom: - - meta: + ontologies: [] + bloom: + - - meta: type: map description: | Groovy Map containing sample information @@ -110,8 +118,9 @@ output: Bloom filter created by shredding the -f input into k-mers of size -k pattern: "*.bloom" - - scaffolds_graph: - - meta: + ontologies: [] + scaffolds_graph: + - - meta: type: map description: | Groovy Map containing sample information @@ -119,11 +128,12 @@ output: - "*.gv": type: file description: | - scaffold graph (for visualizing merges), can be rendered + scaffold graph (for visualizing merges), can be rendered in neato, graphviz, etc pattern: "*.gv" - - assembly_correspondence: - - meta: + ontologies: [] + assembly_correspondence: + - - meta: type: map description: | Groovy Map containing sample information @@ -135,18 +145,23 @@ output: contig ID, original_name, #linking kmer pairs, links ratio, gap or overlap pattern: "*.assembly_correspondence.tsv" - - simplepair_checkpoint: - - meta: + ontologies: + - edam: http://edamontology.org/format_3475 # TSV + simplepair_checkpoint: + - - meta: type: map description: | Groovy Map containing sample information e.g. `[ id:'sample1']` - "*.simplepair_checkpoint.tsv": type: file - description: checkpoint file, contains info to rebuild datastructure for .gv graph + description: checkpoint file, contains info to rebuild datastructure for .gv + graph pattern: "*.simplepair_checkpoint.tsv" - - tigpair_checkpoint: - - meta: + ontologies: + - edam: http://edamontology.org/format_3475 # TSV + tigpair_checkpoint: + - - meta: type: map description: | Groovy Map containing sample information @@ -154,21 +169,25 @@ output: - "*.tigpair_checkpoint.tsv": type: file description: | - if -b BASNAME.tigpair_checkpoint.tsv is present, + if -b BASNAME.tigpair_checkpoint.tsv is present, LINKS will skip the kmer pair extraction and contig pairing stages. Delete this file to force LINKS to start at the beginning. - This file can be used to: - 1) quickly test parameters (-l min. links / -a min. links ratio), + This file can be used to: + 1) quickly test parameters (-l min. links / -a min. links ratio), 2) quickly recover from crash, 3) explore very large kmer spaces, 4) scaffold with output of ARCS pattern: "*.tigpair_checkpoint.tsv" - - versions: - - "versions.yml": - type: file - description: File containing software versions - pattern: "versions.yml" + 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: - "@nschan" maintainers: diff --git a/modules/nf-core/links/tests/main.nf.test b/modules/nf-core/links/tests/main.nf.test index bbffb1dd..c045b4aa 100644 --- a/modules/nf-core/links/tests/main.nf.test +++ b/modules/nf-core/links/tests/main.nf.test @@ -21,7 +21,7 @@ nextflow_process { file(params.modules_testdata_base_path + 'genomics/sarscov2/illumina/fasta/contigs.fasta', checkIfExists: true), ] input[1] = [ - [ id:'test'], + [ id:'test'], file(params.modules_testdata_base_path + 'genomics/sarscov2/genome/genome.fasta', checkIfExists: true), ] """ @@ -62,7 +62,7 @@ nextflow_process { file(params.modules_testdata_base_path + 'genomics/sarscov2/illumina/fasta/scaffolds.fasta', checkIfExists: true), ] input[1] = [ - [ id:'test'], + [ id:'test'], file(params.modules_testdata_base_path + 'genomics/sarscov2/illumina/fasta/contigs.fasta', checkIfExists: true), ] """ @@ -104,7 +104,7 @@ nextflow_process { file(params.modules_testdata_base_path + 'genomics/sarscov2/genome/genome.fasta', checkIfExists: true), ] input[1] = [ - [ id:'test'], + [ id:'test'], file(params.modules_testdata_base_path + 'genomics/sarscov2/illumina/fasta/contigs.fasta', checkIfExists: true), ] """ diff --git a/modules/nf-core/merqury/merqury/meta.yml b/modules/nf-core/merqury/merqury/meta.yml index 7e8d875a..9e72531b 100644 --- a/modules/nf-core/merqury/merqury/meta.yml +++ b/modules/nf-core/merqury/merqury/meta.yml @@ -20,12 +20,14 @@ input: - meryl_db: type: file description: "Meryl read database" + ontologies: [] - assembly: type: file description: FASTA assembly file + ontologies: [] output: - - assembly_only_kmers_bed: - - meta: + assembly_only_kmers_bed: + - - meta: type: map description: | Groovy Map containing sample information @@ -35,8 +37,9 @@ output: description: "The positions of the k-mers found only in an assembly for further investigation in .bed" pattern: "*_only.bed" - - assembly_only_kmers_wig: - - meta: + ontologies: [] + assembly_only_kmers_wig: + - - meta: type: map description: | Groovy Map containing sample information @@ -46,8 +49,9 @@ output: description: "The positions of the k-mers found only in an assembly for further investigation in .wig" pattern: "*_only.wig" - - stats: - - meta: + ontologies: [] + stats: + - - meta: type: map description: | Groovy Map containing sample information @@ -56,8 +60,9 @@ output: type: file description: Assembly statistics file pattern: "*.completeness.stats" - - dist_hist: - - meta: + ontologies: [] + dist_hist: + - - meta: type: map description: | Groovy Map containing sample information @@ -66,8 +71,9 @@ output: type: file description: Histogram pattern: "*.dist_only.hist" - - spectra_cn_fl_png: - - meta: + ontologies: [] + spectra_cn_fl_png: + - - meta: type: map description: | Groovy Map containing sample information @@ -76,8 +82,9 @@ output: type: file description: "Unstacked copy number spectra filled plot in PNG format" pattern: "*.spectra-cn.fl.png" - - spectra_cn_hist: - - meta: + ontologies: [] + spectra_cn_hist: + - - meta: type: map description: | Groovy Map containing sample information @@ -86,8 +93,9 @@ output: type: file description: "Copy number spectra histogram" pattern: "*.spectra-cn.hist" - - spectra_cn_ln_png: - - meta: + ontologies: [] + spectra_cn_ln_png: + - - meta: type: map description: | Groovy Map containing sample information @@ -96,8 +104,9 @@ output: type: file description: "Unstacked copy number spectra line plot in PNG format" pattern: "*.spectra-cn.ln.png" - - spectra_cn_st_png: - - meta: + ontologies: [] + spectra_cn_st_png: + - - meta: type: map description: | Groovy Map containing sample information @@ -106,8 +115,9 @@ output: type: file description: "Stacked copy number spectra line plot in PNG format" pattern: "*.spectra-cn.st.png" - - spectra_asm_fl_png: - - meta: + ontologies: [] + spectra_asm_fl_png: + - - meta: type: map description: | Groovy Map containing sample information @@ -116,8 +126,9 @@ output: type: file description: "Unstacked assembly spectra filled plot in PNG format" pattern: "*.spectra-asm.fl.png" - - spectra_asm_hist: - - meta: + ontologies: [] + spectra_asm_hist: + - - meta: type: map description: | Groovy Map containing sample information @@ -126,8 +137,9 @@ output: type: file description: "Assembly spectra histogram" pattern: "*.spectra-asm.hist" - - spectra_asm_ln_png: - - meta: + ontologies: [] + spectra_asm_ln_png: + - - meta: type: map description: | Groovy Map containing sample information @@ -136,8 +148,9 @@ output: type: file description: "Unstacked assembly spectra line plot in PNG format" pattern: "*.spectra-asm.ln.png" - - spectra_asm_st_png: - - meta: + ontologies: [] + spectra_asm_st_png: + - - meta: type: map description: | Groovy Map containing sample information @@ -146,8 +159,9 @@ output: type: file description: "Stacked assembly spectra line plot in PNG format" pattern: "*.spectra-asm.st.png" - - assembly_qv: - - meta: + ontologies: [] + assembly_qv: + - - meta: type: map description: | Groovy Map containing sample information @@ -156,8 +170,9 @@ output: type: file description: "Assembly consensus quality estimation" pattern: "*.qv" - - scaffold_qv: - - meta: + ontologies: [] + scaffold_qv: + - - meta: type: map description: | Groovy Map containing sample information @@ -166,8 +181,9 @@ output: type: file description: "Scaffold consensus quality estimation" pattern: "*.qv" - - read_ploidy: - - meta: + ontologies: [] + read_ploidy: + - - meta: type: map description: | Groovy Map containing sample information @@ -176,8 +192,9 @@ output: type: file description: "Ploidy estimate from read k-mer database" pattern: "*.hist.ploidy" - - hapmers_blob_png: - - meta: + ontologies: [] + hapmers_blob_png: + - - meta: type: map description: | Groovy Map containing sample information @@ -186,11 +203,14 @@ output: type: file description: "Hap-mer blob plot" pattern: "*.hapmers.blob.png" - - 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: - "@mahesh-panchal" maintainers: diff --git a/modules/nf-core/merqury/merqury/tests/main.nf.test b/modules/nf-core/merqury/merqury/tests/main.nf.test index 46a07c02..f20031fa 100644 --- a/modules/nf-core/merqury/merqury/tests/main.nf.test +++ b/modules/nf-core/merqury/merqury/tests/main.nf.test @@ -186,4 +186,4 @@ nextflow_process { } -} \ No newline at end of file +} diff --git a/modules/nf-core/meryl/count/meta.yml b/modules/nf-core/meryl/count/meta.yml index a110a610..0cb2a294 100644 --- a/modules/nf-core/meryl/count/meta.yml +++ b/modules/nf-core/meryl/count/meta.yml @@ -23,12 +23,13 @@ input: description: | List of input FastQ files of size 1 and 2 for single-end and paired-end data, respectively. - - - kvalue: - type: integer - description: An integer value of k to use as the k-mer value. + ontologies: [] + - kvalue: + type: integer + description: An integer value of k to use as the k-mer value. output: - - meryl_db: - - meta: + meryl_db: + - - meta: type: map description: | Groovy Map containing sample information @@ -37,11 +38,13 @@ output: type: directory description: A Meryl k-mer database pattern: "*.meryl" - - versions: - - versions.yml: - type: file - description: File containing software versions - pattern: "versions.yml" + versions: + - versions.yml: + type: file + description: File containing software versions + pattern: "versions.yml" + ontologies: + - edam: http://edamontology.org/format_3750 # YAML authors: - "@mahesh-panchal" maintainers: diff --git a/modules/nf-core/meryl/unionsum/meta.yml b/modules/nf-core/meryl/unionsum/meta.yml index e9e13051..ce056955 100644 --- a/modules/nf-core/meryl/unionsum/meta.yml +++ b/modules/nf-core/meryl/unionsum/meta.yml @@ -21,12 +21,12 @@ input: - meryl_dbs: type: directory description: Meryl k-mer databases - - - kvalue: - type: integer - description: An integer value of k to use as the k-mer value. + - kvalue: + type: integer + description: An integer value of k to use as the k-mer value. output: - - meryl_db: - - meta: + meryl_db: + - - meta: type: map description: | Groovy Map containing sample information @@ -35,11 +35,13 @@ output: type: directory description: A Meryl k-mer database that is the union sum of the input databases pattern: "*.unionsum.meryl" - - versions: - - versions.yml: - type: file - description: File containing software versions - pattern: "versions.yml" + versions: + - versions.yml: + type: file + description: File containing software versions + pattern: "versions.yml" + ontologies: + - edam: http://edamontology.org/format_3750 # YAML authors: - "@mahesh-panchal" maintainers: diff --git a/modules/nf-core/minimap2/align/meta.yml b/modules/nf-core/minimap2/align/meta.yml index a4cfc891..b501526e 100644 --- a/modules/nf-core/minimap2/align/meta.yml +++ b/modules/nf-core/minimap2/align/meta.yml @@ -26,6 +26,7 @@ input: description: | List of input FASTA or FASTQ files of size 1 and 2 for single-end and paired-end data, respectively. + ontologies: [] - - meta2: type: map description: | @@ -35,23 +36,24 @@ input: type: file description: | Reference database in FASTA format. - - - bam_format: - type: boolean - description: Specify that output should be in BAM format - - - bam_index_extension: - type: string - description: BAM alignment index extension (e.g. "bai") - - - cigar_paf_format: - type: boolean - description: Specify that output CIGAR should be in PAF format - - - cigar_bam: - type: boolean - description: | - Write CIGAR with >65535 ops at the CG tag. This is recommended when - doing XYZ (https://github.com/lh3/minimap2#working-with-65535-cigar-operations) + ontologies: [] + - bam_format: + type: boolean + description: Specify that output should be in BAM format + - bam_index_extension: + type: string + description: BAM alignment index extension (e.g. "bai") + - cigar_paf_format: + type: boolean + description: Specify that output CIGAR should be in PAF format + - cigar_bam: + type: boolean + description: | + Write CIGAR with >65535 ops at the CG tag. This is recommended when + doing XYZ (https://github.com/lh3/minimap2#working-with-65535-cigar-operations) output: - - paf: - - meta: + paf: + - - meta: type: map description: | Groovy Map containing sample information @@ -60,8 +62,9 @@ output: type: file description: Alignment in PAF format pattern: "*.paf" - - bam: - - meta: + ontologies: [] + bam: + - - meta: type: map description: | Groovy Map containing sample information @@ -70,8 +73,9 @@ output: type: file description: Alignment in BAM format pattern: "*.bam" - - index: - - meta: + ontologies: [] + index: + - - meta: type: map description: | Groovy Map containing sample information @@ -80,11 +84,14 @@ output: type: file description: BAM alignment index pattern: "*.bam.*" - - 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: - "@heuermh" - "@sofstam" diff --git a/modules/nf-core/minimap2/align/tests/main.nf.test b/modules/nf-core/minimap2/align/tests/main.nf.test index 4072c171..65061404 100644 --- a/modules/nf-core/minimap2/align/tests/main.nf.test +++ b/modules/nf-core/minimap2/align/tests/main.nf.test @@ -438,4 +438,4 @@ nextflow_process { } -} \ No newline at end of file +} diff --git a/modules/nf-core/pilon/meta.yml b/modules/nf-core/pilon/meta.yml index 38d9006d..87142587 100644 --- a/modules/nf-core/pilon/meta.yml +++ b/modules/nf-core/pilon/meta.yml @@ -25,6 +25,7 @@ input: type: file description: FASTA of the input genome pattern: "*.{fasta}" + ontologies: [] - - meta2: type: map description: | @@ -34,22 +35,24 @@ input: type: file description: BAM file of reads aligned to the input genome pattern: "*.{bam}" + ontologies: [] - bai: type: file description: BAI file (BAM index) of BAM reads aligned to the input genome pattern: "*.{bai}" - - - pilon_mode: - type: string - description: Indicates the type of bam file used (frags for paired-end sequencing - of DNA fragments, such as Illumina paired-end reads of fragment size <1000bp, - jumps for paired sequencing data of larger insert size, such as Illumina mate - pair libraries, typically of insert size >1000bp, unpaired for unpaired sequencing - reads, bam will automatically classify the BAM as one of the three types above - (version 1.17 and higher). - enum: ["frags", "jumps", "unpaired", "bam"] + ontologies: [] + - pilon_mode: + type: string + description: Indicates the type of bam file used (frags for paired-end sequencing + of DNA fragments, such as Illumina paired-end reads of fragment size <1000bp, + jumps for paired sequencing data of larger insert size, such as Illumina mate + pair libraries, typically of insert size >1000bp, unpaired for unpaired sequencing + reads, bam will automatically classify the BAM as one of the three types above + (version 1.17 and higher). + enum: ["frags", "jumps", "unpaired", "bam"] output: - - improved_assembly: - - meta: + improved_assembly: + - - meta: type: map description: | Groovy Map containing sample information @@ -58,8 +61,9 @@ output: type: file description: fasta file, improved assembly pattern: "*.{fasta}" - - vcf: - - meta: + ontologies: [] + vcf: + - - meta: type: map description: | Groovy Map containing sample information @@ -68,19 +72,21 @@ output: type: file description: Pilon variant output pattern: "*.{vcf}" - - change_record: - - meta: + ontologies: [] + change_record: + - - meta: type: map description: | Groovy Map containing sample information e.g. [ id:'test', single_end:false ] - "*.change": type: file - description: file containing a space-delimited record of every change made in - the assembly as instructed by the --fix option + description: file containing a space-delimited record of every change made + in the assembly as instructed by the --fix option pattern: "*.{change}" - - tracks_bed: - - meta: + ontologies: [] + tracks_bed: + - - meta: type: map description: | Groovy Map containing sample information @@ -90,8 +96,9 @@ output: description: files that may be viewed in genome browsers such as IGV, GenomeView, and other applications that support these formats pattern: "*.{bed}" - - tracks_wig: - - meta: + ontologies: [] + tracks_wig: + - - meta: type: map description: | Groovy Map containing sample information @@ -101,11 +108,14 @@ output: description: files that may be viewed in genome browsers such as IGV, GenomeView, and other applications that support these formats pattern: "*.{wig}" - - 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: - "@scorreard" maintainers: diff --git a/modules/nf-core/ragtag/patch/main.nf b/modules/nf-core/ragtag/patch/main.nf index 4e8cf455..e6e4712b 100644 --- a/modules/nf-core/ragtag/patch/main.nf +++ b/modules/nf-core/ragtag/patch/main.nf @@ -8,11 +8,11 @@ process RAGTAG_PATCH { : 'biocontainers/ragtag:2.1.0--pyhb7b1952_0'}" input: - tuple val(meta), path(target, name: 'target/*') + tuple val(meta), path(target, name: 'target/*') tuple val(meta2), path(query, name: 'query/*') tuple val(meta3), path(exclude) tuple val(meta4), path(skip) - + output: tuple val(meta), path("*.patch.fasta"), emit: patch_fasta tuple val(meta), path("*.patch.agp"), emit: patch_agp @@ -56,7 +56,7 @@ process RAGTAG_PATCH { ${arg_exclude} \\ ${arg_skip} \\ ${args} \\ - 2> >( tee ${prefix}.stderr.log >&2 ) \\ + 2>| >( tee ${prefix}.stderr.log >&2 ) \\ | tee ${prefix}.stdout.log kill -TERM "\$tailpid" @@ -76,7 +76,7 @@ process RAGTAG_PATCH { mv ${prefix}/ragtag.patch.err ${prefix}.patch.err # Move the assembly files from prefix folder, and add prefix for alignment_file in \$(ls ${prefix}/ragtag.patch.asm.*); - do + do mv "\$alignment_file" "\${alignment_file/${prefix}\\//${prefix}_}" done @@ -101,7 +101,7 @@ process RAGTAG_PATCH { touch ${prefix}.rename.fasta touch ${prefix}.ragtag.patch.asm.1 touch ${prefix}.patch.err - + cat <<-END_VERSIONS > versions.yml ragtag: \$(echo \$(ragtag.py -v | sed 's/v//')) END_VERSIONS diff --git a/modules/nf-core/ragtag/patch/meta.yml b/modules/nf-core/ragtag/patch/meta.yml index d74ee3d2..9cad9c91 100644 --- a/modules/nf-core/ragtag/patch/meta.yml +++ b/modules/nf-core/ragtag/patch/meta.yml @@ -26,6 +26,7 @@ input: type: file description: Target assembly pattern: "*.{fasta,fasta.gz}" + ontologies: [] - - meta2: type: map description: | @@ -35,6 +36,7 @@ input: type: file description: Query assembly pattern: "*.{fasta,fasta.gz}" + ontologies: [] - - meta3: type: map description: | @@ -44,6 +46,7 @@ input: type: file description: list of target sequences to ignore pattern: "*.txt" + ontologies: [] - - meta4: type: map description: | @@ -53,9 +56,10 @@ input: type: file description: list of query sequences to ignore pattern: "*.txt" + ontologies: [] output: - - patch_fasta: - - meta: + patch_fasta: + - - meta: type: map description: | Groovy Map containing sample information @@ -64,8 +68,9 @@ output: type: file description: FASTA file containing the patched assembly pattern: "*.patch.fasta" - - patch_agp: - - meta: + ontologies: [] + patch_agp: + - - meta: type: map description: | Groovy Map containing sample information @@ -74,8 +79,9 @@ output: type: file description: AGP file defining how ragtag.patch.fasta is built pattern: "*.patch.agp" - - patch_components_fasta: - - meta: + ontologies: [] + patch_components_fasta: + - - meta: type: map description: | Groovy Map containing sample information @@ -85,8 +91,9 @@ output: description: The split target assembly and the renamed query assembly combined into one FASTA file. This file contains all components in ragtag.patch.agp pattern: "*.comps.fasta" - - assembly_alignments: - - meta: + ontologies: [] + assembly_alignments: + - - meta: type: map description: | Groovy Map containing sample information @@ -95,8 +102,9 @@ output: type: file description: Assembly alignment files pattern: "*.ragtag.patch.asm.*" - - target_splits_agp: - - meta: + ontologies: [] + target_splits_agp: + - - meta: type: map description: | Groovy Map containing sample information @@ -105,8 +113,9 @@ output: type: file description: An AGP file defining how the target assembly was split at gaps pattern: "*.ctg.agp" - - target_splits_fasta: - - meta: + ontologies: [] + target_splits_fasta: + - - meta: type: map description: | Groovy Map containing sample information @@ -115,8 +124,9 @@ output: type: file description: FASTA file containing the target assembly split at gaps pattern: "*.ctg.fasta" - - qry_rename_agp: - - meta: + ontologies: [] + qry_rename_agp: + - - meta: type: map description: | Groovy Map containing sample information @@ -125,8 +135,9 @@ output: type: file description: An AGP file defining the new names for query sequences pattern: "*.rename.agp" - - qry_rename_fasta: - - meta: + ontologies: [] + qry_rename_fasta: + - - meta: type: map description: | Groovy Map containing sample information @@ -135,8 +146,9 @@ output: type: file description: A FASTA file with the original query sequence, but with new names pattern: "*.rename.fasta" - - stderr: - - meta: + ontologies: [] + stderr: + - - meta: type: map description: | Groovy Map containing sample information @@ -145,11 +157,14 @@ output: type: file description: Standard error logging for all external RagTag commands pattern: "*.patch.err" - - 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: - "@nschan" maintainers: diff --git a/modules/nf-core/ragtag/scaffold/main.nf b/modules/nf-core/ragtag/scaffold/main.nf index c3930c12..b258a0aa 100644 --- a/modules/nf-core/ragtag/scaffold/main.nf +++ b/modules/nf-core/ragtag/scaffold/main.nf @@ -51,7 +51,7 @@ process RAGTAG_SCAFFOLD { ${arg_skip} \\ ${arg_hard_skip} \\ ${args} \\ - 2> >( tee ${prefix}.stderr.log >&2 ) \\ + 2>| >( tee ${prefix}.stderr.log >&2 ) \\ | tee ${prefix}.stdout.log mv ${prefix}/ragtag.scaffold.fasta ${prefix}.fasta diff --git a/modules/nf-core/ragtag/scaffold/meta.yml b/modules/nf-core/ragtag/scaffold/meta.yml index 62eb0e49..6b5d55a8 100644 --- a/modules/nf-core/ragtag/scaffold/meta.yml +++ b/modules/nf-core/ragtag/scaffold/meta.yml @@ -31,6 +31,7 @@ input: type: file description: Assembly to be scaffolded pattern: "*.{fasta,fasta.gz,fa,fa.gz}" + ontologies: [] - - meta2: type: map description: | @@ -40,6 +41,7 @@ input: type: file description: Reference assembly pattern: "*.{fasta,fasta.gz,fa,fa.gz}" + ontologies: [] - - meta3: type: map description: | @@ -49,6 +51,7 @@ input: type: file description: list of target sequences to ignore pattern: "*.txt" + ontologies: [] - - meta4: type: map description: | @@ -58,14 +61,16 @@ input: type: file description: list of query sequences to leave unplaced pattern: "*.txt" + ontologies: [] - hard_skip: type: file description: list of query headers to leave unplaced and exclude from 'chr0' ('-C') pattern: "*.txt" + ontologies: [] output: - - corrected_assembly: - - meta: + corrected_assembly: + - - meta: type: map description: | Groovy Map containing sample information @@ -74,8 +79,9 @@ output: type: file description: FASTA file containing the patched assembly pattern: "*.fasta" - - corrected_agp: - - meta: + ontologies: [] + corrected_agp: + - - meta: type: map description: | Groovy Map containing sample information @@ -84,8 +90,9 @@ output: type: file description: agp file defining how corrected_assembly is built pattern: "*.agp" - - corrected_stats: - - meta: + ontologies: [] + corrected_stats: + - - meta: type: map description: | Groovy Map containing sample information @@ -95,11 +102,14 @@ output: description: Statistics on the scaffold pattern: "*.stats" - - 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: - "@nschan" maintainers: diff --git a/modules/nf-core/ragtag/scaffold/tests/main.nf.test b/modules/nf-core/ragtag/scaffold/tests/main.nf.test index 51b42642..84e6b09e 100644 --- a/modules/nf-core/ragtag/scaffold/tests/main.nf.test +++ b/modules/nf-core/ragtag/scaffold/tests/main.nf.test @@ -30,7 +30,7 @@ nextflow_process { [], [], [] - ] + ] """ } } @@ -66,7 +66,7 @@ nextflow_process { [], [], [] - ] + ] """ } } diff --git a/modules/nf-core/samtools/fastq/environment.yml b/modules/nf-core/samtools/fastq/environment.yml index 62054fc9..89e12a64 100644 --- a/modules/nf-core/samtools/fastq/environment.yml +++ b/modules/nf-core/samtools/fastq/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.22.1 + # renovate: datasource=conda depName=bioconda/samtools + - bioconda::samtools=1.22.1 diff --git a/modules/nf-core/samtools/fastq/main.nf b/modules/nf-core/samtools/fastq/main.nf index 696d668f..d2a8a750 100644 --- a/modules/nf-core/samtools/fastq/main.nf +++ b/modules/nf-core/samtools/fastq/main.nf @@ -4,8 +4,8 @@ process SAMTOOLS_FASTQ { 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' }" + 'https://depot.galaxyproject.org/singularity/samtools:1.22.1--h96c455f_0' : + 'biocontainers/samtools:1.22.1--h96c455f_0' }" input: tuple val(meta), path(input) @@ -28,6 +28,7 @@ process SAMTOOLS_FASTQ { meta.single_end ? "-1 ${prefix}_1.fastq.gz -s ${prefix}_singleton.fastq.gz" : "-1 ${prefix}_1.fastq.gz -2 ${prefix}_2.fastq.gz -s ${prefix}_singleton.fastq.gz" """ + # Note: --threads value represents *additional* CPUs to allocate (total CPUs = 1 + --threads). samtools \\ fastq \\ $args \\ @@ -50,7 +51,7 @@ process SAMTOOLS_FASTQ { """ ${output} echo | gzip > ${prefix}_other.fastq.gz - + cat <<-END_VERSIONS > versions.yml "${task.process}": samtools: \$(echo \$(samtools --version 2>&1) | sed 's/^.*samtools //; s/Using.*\$//') diff --git a/modules/nf-core/samtools/fastq/meta.yml b/modules/nf-core/samtools/fastq/meta.yml index c15a0b6f..9a5bd42f 100644 --- a/modules/nf-core/samtools/fastq/meta.yml +++ b/modules/nf-core/samtools/fastq/meta.yml @@ -26,23 +26,25 @@ input: type: file description: BAM/CRAM/SAM file pattern: "*.{bam,cram,sam}" - - - interleave: - type: boolean - description: Set true for interleaved fastq file + ontologies: [] + - interleave: + type: boolean + description: Set true for interleaved fastq file output: - - fastq: - - meta: + fastq: + - - meta: type: map description: | Groovy Map containing sample information e.g. [ id:'test', single_end:false ] - "*_{1,2}.fastq.gz": type: file - description: Compressed FASTQ file(s) with reads with either the READ1 or READ2 - flag set in separate files. + description: Compressed FASTQ file(s) with reads with either the READ1 or + READ2 flag set in separate files. pattern: "*_{1,2}.fastq.gz" - - interleaved: - - meta: + ontologies: [] + interleaved: + - - meta: type: map description: | Groovy Map containing sample information @@ -52,8 +54,10 @@ output: description: Compressed FASTQ file with reads with either the READ1 or READ2 flag set in a combined file. Needs collated input file. pattern: "*_interleaved.fastq.gz" - - singleton: - - meta: + ontologies: + - edam: http://edamontology.org/format_3989 # GZIP format + singleton: + - - meta: type: map description: | Groovy Map containing sample information @@ -62,8 +66,10 @@ output: type: file description: Compressed FASTQ file with singleton reads pattern: "*_singleton.fastq.gz" - - other: - - meta: + ontologies: + - edam: http://edamontology.org/format_3989 # GZIP format + other: + - - meta: type: map description: | Groovy Map containing sample information @@ -73,11 +79,15 @@ output: description: Compressed FASTQ file with reads with either both READ1 and READ2 flags set or unset pattern: "*_other.fastq.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: - "@priyanka-surana" - "@suzannejin" diff --git a/modules/nf-core/samtools/fastq/tests/main.nf.test.snap b/modules/nf-core/samtools/fastq/tests/main.nf.test.snap index ff63f9ae..590b886b 100644 --- a/modules/nf-core/samtools/fastq/tests/main.nf.test.snap +++ b/modules/nf-core/samtools/fastq/tests/main.nf.test.snap @@ -64,7 +64,7 @@ ] ], "4": [ - "versions.yml:md5,11e074d69900de5a7dfdbe1fb4e789fd" + "versions.yml:md5,391b9e4150ae63ea50d15781b61712c7" ], "fastq": [ [ @@ -100,15 +100,15 @@ ] ], "versions": [ - "versions.yml:md5,11e074d69900de5a7dfdbe1fb4e789fd" + "versions.yml:md5,391b9e4150ae63ea50d15781b61712c7" ] } ], "meta": { "nf-test": "0.9.2", - "nextflow": "24.10.3" + "nextflow": "25.04.6" }, - "timestamp": "2025-03-05T12:50:58.986886415" + "timestamp": "2025-09-10T13:18:13.675438" }, "bam_fastq": { "content": [ @@ -176,26 +176,26 @@ "bam_versions": { "content": [ [ - "versions.yml:md5,11e074d69900de5a7dfdbe1fb4e789fd" + "versions.yml:md5,391b9e4150ae63ea50d15781b61712c7" ] ], "meta": { - "nf-test": "0.9.0", - "nextflow": "24.04.4" + "nf-test": "0.9.2", + "nextflow": "25.04.6" }, - "timestamp": "2024-09-16T08:00:41.44921616" + "timestamp": "2025-09-10T13:18:05.023407" }, "bam_verinterleave_sions": { "content": [ [ - "versions.yml:md5,11e074d69900de5a7dfdbe1fb4e789fd" + "versions.yml:md5,391b9e4150ae63ea50d15781b61712c7" ] ], "meta": { - "nf-test": "0.9.0", - "nextflow": "24.04.4" + "nf-test": "0.9.2", + "nextflow": "25.04.6" }, - "timestamp": "2024-09-16T08:00:56.47781168" + "timestamp": "2025-09-10T13:18:09.490066" }, "bam_singleton": { "content": [ @@ -247,7 +247,7 @@ ] ], "4": [ - "versions.yml:md5,11e074d69900de5a7dfdbe1fb4e789fd" + "versions.yml:md5,391b9e4150ae63ea50d15781b61712c7" ], "fastq": [ @@ -274,14 +274,14 @@ ], "versions": [ - "versions.yml:md5,11e074d69900de5a7dfdbe1fb4e789fd" + "versions.yml:md5,391b9e4150ae63ea50d15781b61712c7" ] } ], "meta": { "nf-test": "0.9.2", - "nextflow": "24.10.3" + "nextflow": "25.04.6" }, - "timestamp": "2025-03-05T12:51:10.155471004" + "timestamp": "2025-09-10T13:18:17.613509" } } \ No newline at end of file diff --git a/modules/nf-core/samtools/flagstat/environment.yml b/modules/nf-core/samtools/flagstat/environment.yml index 62054fc9..89e12a64 100644 --- a/modules/nf-core/samtools/flagstat/environment.yml +++ b/modules/nf-core/samtools/flagstat/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.22.1 + # renovate: datasource=conda depName=bioconda/samtools + - bioconda::samtools=1.22.1 diff --git a/modules/nf-core/samtools/flagstat/main.nf b/modules/nf-core/samtools/flagstat/main.nf index c23f3a5c..f148f56b 100644 --- a/modules/nf-core/samtools/flagstat/main.nf +++ b/modules/nf-core/samtools/flagstat/main.nf @@ -4,8 +4,8 @@ process SAMTOOLS_FLAGSTAT { 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' }" + 'https://depot.galaxyproject.org/singularity/samtools:1.22.1--h96c455f_0' : + 'biocontainers/samtools:1.22.1--h96c455f_0' }" input: tuple val(meta), path(bam), path(bai) @@ -35,7 +35,19 @@ process SAMTOOLS_FLAGSTAT { stub: def prefix = task.ext.prefix ?: "${meta.id}" """ - touch ${prefix}.flagstat + cat <<-END_FLAGSTAT > ${prefix}.flagstat + 1000000 + 0 in total (QC-passed reads + QC-failed reads) + 0 + 0 secondary + 0 + 0 supplementary + 0 + 0 duplicates + 900000 + 0 mapped (90.00% : N/A) + 1000000 + 0 paired in sequencing + 500000 + 0 read1 + 500000 + 0 read2 + 800000 + 0 properly paired (80.00% : N/A) + 850000 + 0 with mate mapped to a different chr + 50000 + 0 with mate mapped to a different chr (mapQ>=5) + END_FLAGSTAT cat <<-END_VERSIONS > versions.yml "${task.process}": diff --git a/modules/nf-core/samtools/flagstat/meta.yml b/modules/nf-core/samtools/flagstat/meta.yml index cdc4c254..ebbc15f2 100644 --- a/modules/nf-core/samtools/flagstat/meta.yml +++ b/modules/nf-core/samtools/flagstat/meta.yml @@ -29,13 +29,15 @@ input: type: file description: BAM/CRAM/SAM file pattern: "*.{bam,cram,sam}" + ontologies: [] - bai: type: file description: Index for BAM/CRAM/SAM file pattern: "*.{bai,crai,sai}" + ontologies: [] output: - - flagstat: - - meta: + flagstat: + - - meta: type: map description: | Groovy Map containing sample information @@ -44,11 +46,14 @@ output: type: file description: File containing samtools flagstat output pattern: "*.{flagstat}" - - 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: - "@drpatelh" maintainers: diff --git a/modules/nf-core/samtools/flagstat/tests/main.nf.test.snap b/modules/nf-core/samtools/flagstat/tests/main.nf.test.snap index 04c3852b..0a0a9b15 100644 --- a/modules/nf-core/samtools/flagstat/tests/main.nf.test.snap +++ b/modules/nf-core/samtools/flagstat/tests/main.nf.test.snap @@ -8,11 +8,11 @@ "id": "test", "single_end": false }, - "test.flagstat:md5,d41d8cd98f00b204e9800998ecf8427e" + "test.flagstat:md5,67394650dbae96d1a4fcc70484822159" ] ], "1": [ - "versions.yml:md5,108a155f2d4a99f50bf3176904208d27" + "versions.yml:md5,bdc0bfb2b0542580e7cd65e80d8570bc" ], "flagstat": [ [ @@ -20,19 +20,19 @@ "id": "test", "single_end": false }, - "test.flagstat:md5,d41d8cd98f00b204e9800998ecf8427e" + "test.flagstat:md5,67394650dbae96d1a4fcc70484822159" ] ], "versions": [ - "versions.yml:md5,108a155f2d4a99f50bf3176904208d27" + "versions.yml:md5,bdc0bfb2b0542580e7cd65e80d8570bc" ] } ], "meta": { - "nf-test": "0.9.0", - "nextflow": "24.04.4" + "nf-test": "0.9.2", + "nextflow": "25.04.6" }, - "timestamp": "2024-09-16T08:02:58.866491759" + "timestamp": "2025-09-15T15:02:00.813612" }, "BAM": { "content": [ @@ -47,7 +47,7 @@ ] ], "1": [ - "versions.yml:md5,108a155f2d4a99f50bf3176904208d27" + "versions.yml:md5,bdc0bfb2b0542580e7cd65e80d8570bc" ], "flagstat": [ [ @@ -59,14 +59,14 @@ ] ], "versions": [ - "versions.yml:md5,108a155f2d4a99f50bf3176904208d27" + "versions.yml:md5,bdc0bfb2b0542580e7cd65e80d8570bc" ] } ], "meta": { - "nf-test": "0.9.0", - "nextflow": "24.04.4" + "nf-test": "0.9.2", + "nextflow": "25.04.6" }, - "timestamp": "2024-09-16T08:02:47.383332837" + "timestamp": "2025-09-15T15:01:55.232954" } } \ No newline at end of file diff --git a/modules/nf-core/samtools/idxstats/environment.yml b/modules/nf-core/samtools/idxstats/environment.yml index 62054fc9..89e12a64 100644 --- a/modules/nf-core/samtools/idxstats/environment.yml +++ b/modules/nf-core/samtools/idxstats/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.22.1 + # renovate: datasource=conda depName=bioconda/samtools + - bioconda::samtools=1.22.1 diff --git a/modules/nf-core/samtools/idxstats/main.nf b/modules/nf-core/samtools/idxstats/main.nf index e2bb6b20..9181a1a5 100644 --- a/modules/nf-core/samtools/idxstats/main.nf +++ b/modules/nf-core/samtools/idxstats/main.nf @@ -4,8 +4,8 @@ process SAMTOOLS_IDXSTATS { 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' }" + 'https://depot.galaxyproject.org/singularity/samtools:1.22.1--h96c455f_0' : + 'biocontainers/samtools:1.22.1--h96c455f_0' }" input: tuple val(meta), path(bam), path(bai) @@ -21,6 +21,7 @@ process SAMTOOLS_IDXSTATS { def prefix = task.ext.prefix ?: "${meta.id}" """ + # Note: --threads value represents *additional* CPUs to allocate (total CPUs = 1 + --threads). samtools \\ idxstats \\ --threads ${task.cpus-1} \\ diff --git a/modules/nf-core/samtools/idxstats/meta.yml b/modules/nf-core/samtools/idxstats/meta.yml index f0a6bcb2..96d42746 100644 --- a/modules/nf-core/samtools/idxstats/meta.yml +++ b/modules/nf-core/samtools/idxstats/meta.yml @@ -29,13 +29,15 @@ input: type: file description: BAM/CRAM/SAM file pattern: "*.{bam,cram,sam}" + ontologies: [] - bai: type: file description: Index for BAM/CRAM/SAM file pattern: "*.{bai,crai,sai}" + ontologies: [] output: - - idxstats: - - meta: + idxstats: + - - meta: type: map description: | Groovy Map containing sample information @@ -44,11 +46,14 @@ output: type: file description: File containing samtools idxstats output pattern: "*.{idxstats}" - - 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: - "@drpatelh" maintainers: diff --git a/modules/nf-core/samtools/idxstats/tests/main.nf.test.snap b/modules/nf-core/samtools/idxstats/tests/main.nf.test.snap index 2cc89a3b..d3e785e0 100644 --- a/modules/nf-core/samtools/idxstats/tests/main.nf.test.snap +++ b/modules/nf-core/samtools/idxstats/tests/main.nf.test.snap @@ -12,7 +12,7 @@ ] ], "1": [ - "versions.yml:md5,c8d7394830c3c1e5be150589571534fb" + "versions.yml:md5,6da44e5235401559cea62052bdc0197b" ], "idxstats": [ [ @@ -24,15 +24,15 @@ ] ], "versions": [ - "versions.yml:md5,c8d7394830c3c1e5be150589571534fb" + "versions.yml:md5,6da44e5235401559cea62052bdc0197b" ] } ], "meta": { - "nf-test": "0.9.0", - "nextflow": "24.04.4" + "nf-test": "0.9.2", + "nextflow": "25.04.6" }, - "timestamp": "2024-09-16T08:11:56.466856235" + "timestamp": "2025-09-10T13:47:35.796569" }, "bam": { "content": [ @@ -47,7 +47,7 @@ ] ], "1": [ - "versions.yml:md5,c8d7394830c3c1e5be150589571534fb" + "versions.yml:md5,6da44e5235401559cea62052bdc0197b" ], "idxstats": [ [ @@ -59,14 +59,14 @@ ] ], "versions": [ - "versions.yml:md5,c8d7394830c3c1e5be150589571534fb" + "versions.yml:md5,6da44e5235401559cea62052bdc0197b" ] } ], "meta": { - "nf-test": "0.9.0", - "nextflow": "24.04.4" + "nf-test": "0.9.2", + "nextflow": "25.04.6" }, - "timestamp": "2024-09-16T08:11:46.311550359" + "timestamp": "2025-09-10T13:47:31.86415" } } \ No newline at end of file diff --git a/modules/nf-core/samtools/index/environment.yml b/modules/nf-core/samtools/index/environment.yml index 62054fc9..89e12a64 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.22.1 + # renovate: datasource=conda depName=bioconda/samtools + - bioconda::samtools=1.22.1 diff --git a/modules/nf-core/samtools/index/main.nf b/modules/nf-core/samtools/index/main.nf index 31175610..a77ad821 100644 --- a/modules/nf-core/samtools/index/main.nf +++ b/modules/nf-core/samtools/index/main.nf @@ -4,8 +4,8 @@ process SAMTOOLS_INDEX { 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' }" + 'https://depot.galaxyproject.org/singularity/samtools:1.22.1--h96c455f_0' : + 'biocontainers/samtools:1.22.1--h96c455f_0' }" input: tuple val(meta), path(input) @@ -24,7 +24,7 @@ process SAMTOOLS_INDEX { """ samtools \\ index \\ - -@ ${task.cpus-1} \\ + -@ ${task.cpus} \\ $args \\ $input diff --git a/modules/nf-core/samtools/index/meta.yml b/modules/nf-core/samtools/index/meta.yml index db8df0d5..1bed6bca 100644 --- a/modules/nf-core/samtools/index/meta.yml +++ b/modules/nf-core/samtools/index/meta.yml @@ -25,9 +25,10 @@ input: - input: type: file description: input file + ontologies: [] output: - - bai: - - meta: + bai: + - - meta: type: map description: | Groovy Map containing sample information @@ -36,8 +37,9 @@ output: type: file description: BAM/CRAM/SAM index file pattern: "*.{bai,crai,sai}" - - csi: - - meta: + ontologies: [] + csi: + - - meta: type: map description: | Groovy Map containing sample information @@ -46,8 +48,9 @@ output: type: file description: CSI index file pattern: "*.{csi}" - - crai: - - meta: + ontologies: [] + crai: + - - meta: type: map description: | Groovy Map containing sample information @@ -56,11 +59,14 @@ output: 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" + ontologies: [] + versions: + - versions.yml: + type: file + description: File containing software versions + pattern: "versions.yml" + ontologies: + - edam: http://edamontology.org/format_3750 # YAML authors: - "@drpatelh" - "@ewels" 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..3836c6bf 100644 --- a/modules/nf-core/samtools/index/tests/main.nf.test.snap +++ b/modules/nf-core/samtools/index/tests/main.nf.test.snap @@ -18,7 +18,7 @@ ], "3": [ - "versions.yml:md5,5e09a6fdf76de396728f877193d72315" + "versions.yml:md5,b8717818c91b07de87c2a5590bad02e6" ], "bai": [ @@ -36,15 +36,15 @@ ] ], "versions": [ - "versions.yml:md5,5e09a6fdf76de396728f877193d72315" + "versions.yml:md5,b8717818c91b07de87c2a5590bad02e6" ] } ], "meta": { - "nf-test": "0.9.0", - "nextflow": "24.04.4" + "nf-test": "0.9.2", + "nextflow": "25.04.6" }, - "timestamp": "2024-09-16T08:21:25.261127166" + "timestamp": "2025-09-10T14:13:38.25787" }, "crai - stub": { "content": [ @@ -65,7 +65,7 @@ ] ], "3": [ - "versions.yml:md5,5e09a6fdf76de396728f877193d72315" + "versions.yml:md5,b8717818c91b07de87c2a5590bad02e6" ], "bai": [ @@ -83,15 +83,15 @@ ], "versions": [ - "versions.yml:md5,5e09a6fdf76de396728f877193d72315" + "versions.yml:md5,b8717818c91b07de87c2a5590bad02e6" ] } ], "meta": { - "nf-test": "0.9.0", - "nextflow": "24.04.4" + "nf-test": "0.9.2", + "nextflow": "25.04.6" }, - "timestamp": "2024-09-16T08:21:12.653194876" + "timestamp": "2025-09-10T14:13:34.496412" }, "bai - stub": { "content": [ @@ -112,7 +112,7 @@ ], "3": [ - "versions.yml:md5,5e09a6fdf76de396728f877193d72315" + "versions.yml:md5,b8717818c91b07de87c2a5590bad02e6" ], "bai": [ [ @@ -130,28 +130,28 @@ ], "versions": [ - "versions.yml:md5,5e09a6fdf76de396728f877193d72315" + "versions.yml:md5,b8717818c91b07de87c2a5590bad02e6" ] } ], "meta": { - "nf-test": "0.9.0", - "nextflow": "24.04.4" + "nf-test": "0.9.2", + "nextflow": "25.04.6" }, - "timestamp": "2024-09-16T08:21:01.854932651" + "timestamp": "2025-09-10T14:13:25.934431" }, "csi": { "content": [ "test.paired_end.sorted.bam.csi", [ - "versions.yml:md5,5e09a6fdf76de396728f877193d72315" + "versions.yml:md5,b8717818c91b07de87c2a5590bad02e6" ] ], "meta": { - "nf-test": "0.9.0", - "nextflow": "24.04.4" + "nf-test": "0.9.2", + "nextflow": "25.04.6" }, - "timestamp": "2024-09-16T08:20:51.485364222" + "timestamp": "2025-09-10T14:13:22.262088" }, "crai": { "content": [ @@ -172,7 +172,7 @@ ] ], "3": [ - "versions.yml:md5,5e09a6fdf76de396728f877193d72315" + "versions.yml:md5,b8717818c91b07de87c2a5590bad02e6" ], "bai": [ @@ -190,15 +190,15 @@ ], "versions": [ - "versions.yml:md5,5e09a6fdf76de396728f877193d72315" + "versions.yml:md5,b8717818c91b07de87c2a5590bad02e6" ] } ], "meta": { - "nf-test": "0.9.0", - "nextflow": "24.04.4" + "nf-test": "0.9.2", + "nextflow": "25.04.6" }, - "timestamp": "2024-09-16T08:20:40.518873972" + "timestamp": "2025-09-10T14:13:18.191664" }, "bai": { "content": [ @@ -219,7 +219,7 @@ ], "3": [ - "versions.yml:md5,5e09a6fdf76de396728f877193d72315" + "versions.yml:md5,b8717818c91b07de87c2a5590bad02e6" ], "bai": [ [ @@ -237,14 +237,14 @@ ], "versions": [ - "versions.yml:md5,5e09a6fdf76de396728f877193d72315" + "versions.yml:md5,b8717818c91b07de87c2a5590bad02e6" ] } ], "meta": { - "nf-test": "0.9.0", - "nextflow": "24.04.4" + "nf-test": "0.9.2", + "nextflow": "25.04.6" }, - "timestamp": "2024-09-16T08:20:21.184050361" + "timestamp": "2025-09-10T14:13:08.51539" } } \ 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..89e12a64 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.22.1 + # renovate: datasource=conda depName=bioconda/samtools + - bioconda::samtools=1.22.1 diff --git a/modules/nf-core/samtools/sort/main.nf b/modules/nf-core/samtools/sort/main.nf index caf3c61a..d4bd5a32 100644 --- a/modules/nf-core/samtools/sort/main.nf +++ b/modules/nf-core/samtools/sort/main.nf @@ -4,30 +4,41 @@ process SAMTOOLS_SORT { 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' }" + 'https://depot.galaxyproject.org/singularity/samtools:1.22.1--h96c455f_0' : + 'biocontainers/samtools:1.22.1--h96c455f_0' }" input: tuple val(meta) , path(bam) tuple val(meta2), path(fasta) + 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"), emit: crai, optional: true + tuple val(meta), path("${prefix}.${extension}.csi"), emit: csi, optional: true + tuple val(meta), path("${prefix}.${extension}.bai"), emit: bai, 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" + 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}" : "" + output_file = index_format ? "${prefix}.${extension}##idx##${prefix}.${extension}.${index_format} --write-index" : "${prefix}.${extension}" + 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!" """ @@ -39,7 +50,7 @@ process SAMTOOLS_SORT { -T ${prefix} \\ --threads $task.cpus \\ ${reference} \\ - -o ${prefix}.${extension} \\ + -o ${output_file} \\ - cat <<-END_VERSIONS > versions.yml @@ -50,19 +61,22 @@ process SAMTOOLS_SORT { 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 + ${index} cat <<-END_VERSIONS > versions.yml "${task.process}": diff --git a/modules/nf-core/samtools/sort/meta.yml b/modules/nf-core/samtools/sort/meta.yml index a9dbec5a..4c4010bb 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,86 @@ input: description: Reference genome FASTA file pattern: "*.{fa,fasta,fna}" 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: Sorted SAM file + pattern: "*.{sam}" + ontologies: [] + + crai: + - - meta: + type: map + description: | + Groovy Map containing sample information + e.g. [ id:'test', single_end:false ] + - "${prefix}.${extension}.crai": type: file description: CRAM index file (optional) pattern: "*.crai" - - csi: - - meta: + ontologies: [] + csi: + - - meta: type: map description: | Groovy Map containing sample information e.g. [ id:'test', single_end:false ] - - "*.csi": + - "${prefix}.${extension}.csi": type: file description: BAM index file (optional) pattern: "*.csi" - - versions: - - versions.yml: + ontologies: [] + bai: + - - meta: + type: map + description: | + Groovy Map containing sample information + e.g. [ id:'test', single_end:false ] + - "${prefix}.${extension}.bai": type: file - description: File containing software versions - pattern: "versions.yml" + description: BAM index file (optional) + pattern: "*.bai" + ontologies: [] + versions: + - versions.yml: + type: file + description: File containing software versions + pattern: "versions.yml" + ontologies: + - edam: http://edamontology.org/format_3750 # YAML 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..ff069190 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" @@ -23,6 +23,7 @@ nextflow_process { [ id:'fasta' ], // meta map file(params.modules_testdata_base_path + 'genomics/sarscov2/genome/genome.fasta', checkIfExists: true) ]) + input[2] = '' """ } } @@ -32,7 +33,71 @@ 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.bai, + process.out.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) + ]) + input[2] = 'bai' + """ + } + } + + then { + assertAll ( + { assert process.success }, + { assert snapshot( + process.out.bam, + process.out.bai, + process.out.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) + ]) + input[2] = 'csi' + """ + } + } + + then { + assertAll ( + { assert process.success }, + { assert snapshot( + process.out.bam, + process.out.csi, process.out.versions ).match()} ) @@ -57,6 +122,77 @@ nextflow_process { [ id:'fasta' ], // meta map file(params.modules_testdata_base_path + 'genomics/homo_sapiens/genome/genome.fasta', checkIfExists: true) ]) + input[2] = '' + """ + } + } + + 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 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) + ]) + input[2] = 'bai' + """ + } + } + + then { + assertAll ( + { assert process.success }, + { assert snapshot( + process.out.bam, + process.out.bai.collect { it.collect { it instanceof Map ? it : file(it).name } }, + process.out.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) + ]) + input[2] = 'csi' """ } } @@ -88,6 +224,7 @@ nextflow_process { [ id:'fasta' ], // meta map file(params.modules_testdata_base_path + 'genomics/homo_sapiens/genome/genome.fasta', checkIfExists: true) ]) + input[2] = '' """ } } @@ -120,6 +257,7 @@ nextflow_process { [ id:'fasta' ], // meta map file(params.modules_testdata_base_path + 'genomics/sarscov2/genome/genome.fasta', checkIfExists: true) ]) + input[2] = '' """ } } @@ -150,6 +288,7 @@ nextflow_process { [ id:'fasta' ], // meta map file(params.modules_testdata_base_path + 'genomics/homo_sapiens/genome/genome.fasta', checkIfExists: true) ]) + input[2] = '' """ } } @@ -178,6 +317,7 @@ nextflow_process { [ id:'fasta' ], // meta map file(params.modules_testdata_base_path + 'genomics/homo_sapiens/genome/genome.fasta', checkIfExists: true) ]) + input[2] = '' """ } } 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..473e1745 100644 --- a/modules/nf-core/samtools/sort/tests/main.nf.test.snap +++ b/modules/nf-core/samtools/sort/tests/main.nf.test.snap @@ -20,14 +20,14 @@ ] ], [ - "versions.yml:md5,2659b187d681241451539d4c53500b9f" + "versions.yml:md5,18e8b3709b62aa2ba61966672eee91b2" ] ], "meta": { - "nf-test": "0.9.0", - "nextflow": "24.04.4" + "nf-test": "0.9.2", + "nextflow": "25.04.6" }, - "timestamp": "2024-09-16T08:49:58.207549273" + "timestamp": "2025-09-10T14:43:31.395604" }, "bam - stub": { "content": [ @@ -48,16 +48,19 @@ ], "3": [ - [ - { - "id": "test", - "single_end": false - }, - "test.sorted.bam.csi:md5,d41d8cd98f00b204e9800998ecf8427e" - ] + ], "4": [ - "versions.yml:md5,2659b187d681241451539d4c53500b9f" + + ], + "5": [ + + ], + "6": [ + "versions.yml:md5,18e8b3709b62aa2ba61966672eee91b2" + ], + "bai": [ + ], "bam": [ [ @@ -75,24 +78,81 @@ ], "csi": [ - [ - { - "id": "test", - "single_end": false - }, - "test.sorted.bam.csi:md5,d41d8cd98f00b204e9800998ecf8427e" - ] + + ], + "sam": [ + ], "versions": [ - "versions.yml:md5,2659b187d681241451539d4c53500b9f" + "versions.yml:md5,18e8b3709b62aa2ba61966672eee91b2" ] } ], "meta": { - "nf-test": "0.9.0", - "nextflow": "24.04.4" + "nf-test": "0.9.2", + "nextflow": "25.04.6" + }, + "timestamp": "2025-09-10T14:43:37.387063" + }, + "bam_csi_index": { + "content": [ + [ + [ + { + "id": "test", + "single_end": false + }, + "test.sorted.bam:md5,72ca1dff5344a5e5e6b892fe5f6b134d" + ] + ], + [ + [ + { + "id": "test", + "single_end": false + }, + "test.sorted.bam.csi:md5,01394e702c729cb478df914ffaf9f7f8" + ] + ], + [ + "versions.yml:md5,18e8b3709b62aa2ba61966672eee91b2" + ] + ], + "meta": { + "nf-test": "0.9.2", + "nextflow": "25.04.6" }, - "timestamp": "2024-09-16T08:50:08.630951018" + "timestamp": "2025-09-10T14:43:06.976036" + }, + "multiple bam bai index": { + "content": [ + [ + [ + { + "id": "test", + "single_end": false + }, + "test.sorted.bam:md5,3ffa2affc29f0aa6e7b36dded84625fe" + ] + ], + [ + [ + { + "id": "test", + "single_end": false + }, + "test.sorted.bam.bai" + ] + ], + [ + "versions.yml:md5,18e8b3709b62aa2ba61966672eee91b2" + ] + ], + "meta": { + "nf-test": "0.9.2", + "nextflow": "25.04.6" + }, + "timestamp": "2025-09-10T14:43:18.72271" }, "cram - stub": { "content": [ @@ -110,31 +170,28 @@ ] ], "2": [ - [ - { - "id": "test", - "single_end": false - }, - "test.sorted.cram.crai:md5,d41d8cd98f00b204e9800998ecf8427e" - ] + ], "3": [ ], "4": [ - "versions.yml:md5,2659b187d681241451539d4c53500b9f" + + ], + "5": [ + + ], + "6": [ + "versions.yml:md5,18e8b3709b62aa2ba61966672eee91b2" + ], + "bai": [ + ], "bam": [ ], "crai": [ - [ - { - "id": "test", - "single_end": false - }, - "test.sorted.cram.crai:md5,d41d8cd98f00b204e9800998ecf8427e" - ] + ], "cram": [ [ @@ -147,17 +204,20 @@ ], "csi": [ + ], + "sam": [ + ], "versions": [ - "versions.yml:md5,2659b187d681241451539d4c53500b9f" + "versions.yml:md5,18e8b3709b62aa2ba61966672eee91b2" ] } ], "meta": { - "nf-test": "0.9.0", - "nextflow": "24.04.4" + "nf-test": "0.9.2", + "nextflow": "25.04.6" }, - "timestamp": "2024-09-16T08:50:19.061912443" + "timestamp": "2025-09-10T14:43:48.734367" }, "multiple bam": { "content": [ @@ -167,27 +227,21 @@ "id": "test", "single_end": false }, - "test.sorted.bam:md5,8a16ba90c7d294cbb4c33ac0f7127a12" + "test.sorted.bam:md5,cd4eb0077f25e9cff395366b8883dd1f" ] ], [ - [ - { - "id": "test", - "single_end": false - }, - "test.sorted.bam.csi" - ] + ], [ - "versions.yml:md5,2659b187d681241451539d4c53500b9f" + "versions.yml:md5,18e8b3709b62aa2ba61966672eee91b2" ] ], "meta": { - "nf-test": "0.9.0", - "nextflow": "24.09.0" + "nf-test": "0.9.2", + "nextflow": "25.04.6" }, - "timestamp": "2024-10-08T11:59:55.479443" + "timestamp": "2025-09-10T14:43:12.989244" }, "multiple bam - stub": { "content": [ @@ -198,7 +252,7 @@ "id": "test", "single_end": false }, - "test.sorted.bam:md5,8a16ba90c7d294cbb4c33ac0f7127a12" + "test.sorted.bam:md5,cd4eb0077f25e9cff395366b8883dd1f" ] ], "1": [ @@ -208,16 +262,19 @@ ], "3": [ - [ - { - "id": "test", - "single_end": false - }, - "test.sorted.bam.csi:md5,d185916eaff9afeb4d0aeab3310371f9" - ] + ], "4": [ - "versions.yml:md5,2659b187d681241451539d4c53500b9f" + + ], + "5": [ + + ], + "6": [ + "versions.yml:md5,18e8b3709b62aa2ba61966672eee91b2" + ], + "bai": [ + ], "bam": [ [ @@ -225,7 +282,7 @@ "id": "test", "single_end": false }, - "test.sorted.bam:md5,8a16ba90c7d294cbb4c33ac0f7127a12" + "test.sorted.bam:md5,cd4eb0077f25e9cff395366b8883dd1f" ] ], "crai": [ @@ -235,24 +292,75 @@ ], "csi": [ - [ - { - "id": "test", - "single_end": false - }, - "test.sorted.bam.csi:md5,d185916eaff9afeb4d0aeab3310371f9" - ] + + ], + "sam": [ + ], "versions": [ - "versions.yml:md5,2659b187d681241451539d4c53500b9f" + "versions.yml:md5,18e8b3709b62aa2ba61966672eee91b2" ] } ], "meta": { - "nf-test": "0.9.0", - "nextflow": "24.09.0" + "nf-test": "0.9.2", + "nextflow": "25.04.6" + }, + "timestamp": "2025-09-10T14:43:43.196638" + }, + "bam_no_index": { + "content": [ + [ + [ + { + "id": "test", + "single_end": false + }, + "test.sorted.bam:md5,26b27d1f9bcb61c25da21b562349784e" + ] + ], + [ + + ], + [ + "versions.yml:md5,18e8b3709b62aa2ba61966672eee91b2" + ] + ], + "meta": { + "nf-test": "0.9.2", + "nextflow": "25.04.6" + }, + "timestamp": "2025-09-10T14:42:54.926504" + }, + "multiple bam csi index": { + "content": [ + [ + [ + { + "id": "test", + "single_end": false + }, + "test.sorted.bam:md5,295503ba5342531a3310c33ad0efbc22" + ] + ], + [ + [ + { + "id": "test", + "single_end": false + }, + "test.sorted.bam.csi" + ] + ], + [ + "versions.yml:md5,18e8b3709b62aa2ba61966672eee91b2" + ] + ], + "meta": { + "nf-test": "0.9.2", + "nextflow": "25.04.6" }, - "timestamp": "2024-10-08T11:36:13.781404" + "timestamp": "2025-09-10T14:43:25.059178" }, "bam": { "content": [ @@ -283,5 +391,35 @@ "nextflow": "24.09.0" }, "timestamp": "2024-10-08T11:59:46.372244" + }, + "bam_bai_index": { + "content": [ + [ + [ + { + "id": "test", + "single_end": false + }, + "test.sorted.bam:md5,cae7564cb83bb4a5911205bf94124b54" + ] + ], + [ + [ + { + "id": "test", + "single_end": false + }, + "test.sorted.bam.bai:md5,50dd467c169545a4d5d1f709f7e986e0" + ] + ], + [ + "versions.yml:md5,18e8b3709b62aa2ba61966672eee91b2" + ] + ], + "meta": { + "nf-test": "0.9.2", + "nextflow": "25.04.6" + }, + "timestamp": "2025-09-10T14:43:00.82974" } } \ 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..723f62b2 100644 --- a/modules/nf-core/samtools/sort/tests/nextflow.config +++ b/modules/nf-core/samtools/sort/tests/nextflow.config @@ -2,7 +2,6 @@ process { withName: SAMTOOLS_SORT { ext.prefix = { "${meta.id}.sorted" } - ext.args = "--write-index" } } diff --git a/modules/nf-core/samtools/stats/environment.yml b/modules/nf-core/samtools/stats/environment.yml index 62054fc9..89e12a64 100644 --- a/modules/nf-core/samtools/stats/environment.yml +++ b/modules/nf-core/samtools/stats/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.22.1 + # renovate: datasource=conda depName=bioconda/samtools + - bioconda::samtools=1.22.1 diff --git a/modules/nf-core/samtools/stats/main.nf b/modules/nf-core/samtools/stats/main.nf index 4443948b..c06ad3bf 100644 --- a/modules/nf-core/samtools/stats/main.nf +++ b/modules/nf-core/samtools/stats/main.nf @@ -4,8 +4,8 @@ process SAMTOOLS_STATS { 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' }" + 'https://depot.galaxyproject.org/singularity/samtools:1.22.1--h96c455f_0' : + 'biocontainers/samtools:1.22.1--h96c455f_0' }" input: tuple val(meta), path(input), path(input_index) diff --git a/modules/nf-core/samtools/stats/meta.yml b/modules/nf-core/samtools/stats/meta.yml index 77b020f7..6dc51885 100644 --- a/modules/nf-core/samtools/stats/meta.yml +++ b/modules/nf-core/samtools/stats/meta.yml @@ -27,10 +27,12 @@ input: type: file description: BAM/CRAM file from alignment pattern: "*.{bam,cram}" + ontologies: [] - input_index: type: file description: BAI/CRAI file from alignment pattern: "*.{bai,crai}" + ontologies: [] - - meta2: type: map description: | @@ -40,9 +42,10 @@ input: type: file description: Reference file the CRAM was created with (optional) pattern: "*.{fasta,fa}" + ontologies: [] output: - - stats: - - meta: + stats: + - - meta: type: map description: | Groovy Map containing sample information @@ -51,11 +54,14 @@ output: type: file description: File containing samtools stats output pattern: "*.{stats}" - - 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: - "@drpatelh" - "@FriederikeHanssen" diff --git a/modules/nf-core/samtools/stats/tests/main.nf.test.snap b/modules/nf-core/samtools/stats/tests/main.nf.test.snap index df507be7..a451c04e 100644 --- a/modules/nf-core/samtools/stats/tests/main.nf.test.snap +++ b/modules/nf-core/samtools/stats/tests/main.nf.test.snap @@ -8,11 +8,11 @@ "id": "test", "single_end": false }, - "test.stats:md5,a27fe55e49a341f92379bb20a65c6a06" + "test.stats:md5,f4aec6c41b73d34ac2fc6b3253aa39ba" ] ], "1": [ - "versions.yml:md5,15b91d8c0e0440332e0fe4df80957043" + "versions.yml:md5,7668882f411d0f6356f14a1b8b56fa3c" ], "stats": [ [ @@ -20,19 +20,19 @@ "id": "test", "single_end": false }, - "test.stats:md5,a27fe55e49a341f92379bb20a65c6a06" + "test.stats:md5,f4aec6c41b73d34ac2fc6b3253aa39ba" ] ], "versions": [ - "versions.yml:md5,15b91d8c0e0440332e0fe4df80957043" + "versions.yml:md5,7668882f411d0f6356f14a1b8b56fa3c" ] } ], "meta": { - "nf-test": "0.9.0", - "nextflow": "24.04.4" + "nf-test": "0.9.2", + "nextflow": "25.04.6" }, - "timestamp": "2024-09-16T09:29:16.767396182" + "timestamp": "2025-09-10T15:05:52.878044" }, "bam - stub": { "content": [ @@ -47,7 +47,7 @@ ] ], "1": [ - "versions.yml:md5,15b91d8c0e0440332e0fe4df80957043" + "versions.yml:md5,7668882f411d0f6356f14a1b8b56fa3c" ], "stats": [ [ @@ -59,15 +59,15 @@ ] ], "versions": [ - "versions.yml:md5,15b91d8c0e0440332e0fe4df80957043" + "versions.yml:md5,7668882f411d0f6356f14a1b8b56fa3c" ] } ], "meta": { - "nf-test": "0.9.0", - "nextflow": "24.04.4" + "nf-test": "0.9.2", + "nextflow": "25.04.6" }, - "timestamp": "2024-09-16T09:29:29.721580274" + "timestamp": "2025-09-10T15:05:56.722672" }, "cram - stub": { "content": [ @@ -82,7 +82,7 @@ ] ], "1": [ - "versions.yml:md5,15b91d8c0e0440332e0fe4df80957043" + "versions.yml:md5,7668882f411d0f6356f14a1b8b56fa3c" ], "stats": [ [ @@ -94,15 +94,15 @@ ] ], "versions": [ - "versions.yml:md5,15b91d8c0e0440332e0fe4df80957043" + "versions.yml:md5,7668882f411d0f6356f14a1b8b56fa3c" ] } ], "meta": { - "nf-test": "0.9.0", - "nextflow": "24.04.4" + "nf-test": "0.9.2", + "nextflow": "25.04.6" }, - "timestamp": "2024-09-16T09:29:53.567964304" + "timestamp": "2025-09-10T15:06:13.766719" }, "bam": { "content": [ @@ -113,11 +113,11 @@ "id": "test", "single_end": false }, - "test.stats:md5,d53a2584376d78942839e9933a34d11b" + "test.stats:md5,41ba8ad30ddb598dadb177a54c222ab9" ] ], "1": [ - "versions.yml:md5,15b91d8c0e0440332e0fe4df80957043" + "versions.yml:md5,7668882f411d0f6356f14a1b8b56fa3c" ], "stats": [ [ @@ -125,18 +125,18 @@ "id": "test", "single_end": false }, - "test.stats:md5,d53a2584376d78942839e9933a34d11b" + "test.stats:md5,41ba8ad30ddb598dadb177a54c222ab9" ] ], "versions": [ - "versions.yml:md5,15b91d8c0e0440332e0fe4df80957043" + "versions.yml:md5,7668882f411d0f6356f14a1b8b56fa3c" ] } ], "meta": { - "nf-test": "0.9.0", - "nextflow": "24.04.4" + "nf-test": "0.9.2", + "nextflow": "25.04.6" }, - "timestamp": "2024-09-16T09:28:50.73610604" + "timestamp": "2025-09-10T15:05:30.301153" } } \ No newline at end of file diff --git a/modules/nf-core/trimgalore/meta.yml b/modules/nf-core/trimgalore/meta.yml index bd793635..43ac961c 100644 --- a/modules/nf-core/trimgalore/meta.yml +++ b/modules/nf-core/trimgalore/meta.yml @@ -26,9 +26,10 @@ input: description: | List of input FastQ files of size 1 and 2 for single-end and paired-end data, respectively. + ontologies: [] output: - - reads: - - meta: + reads: + - - meta: type: map description: | Groovy Map containing sample information @@ -39,21 +40,20 @@ output: Groovy Map containing sample information e.g. [ id:'test', single_end:false ] pattern: "*{3prime,5prime,trimmed,val}{,_1,_2}.fq.gz" - - log: - - meta: + log: + - - meta: type: map description: | Groovy Map containing sample information e.g. [ id:'test', single_end:false ] - pattern: "*_{report.txt}" - "*report.txt": type: map description: | Groovy Map containing sample information e.g. [ id:'test', single_end:false ] pattern: "*_{report.txt}" - - unpaired: - - meta: + unpaired: + - - meta: type: map description: | Groovy Map containing sample information @@ -64,37 +64,37 @@ output: Groovy Map containing sample information e.g. [ id:'test', single_end:false ] pattern: "*unpaired*.fq.gz" - - html: - - meta: + html: + - - meta: type: map description: | Groovy Map containing sample information e.g. [ id:'test', single_end:false ] - pattern: "*_{fastqc.html}" - "*.html": type: map description: | Groovy Map containing sample information e.g. [ id:'test', single_end:false ] pattern: "*_{fastqc.html}" - - zip: - - meta: + zip: + - - meta: type: map description: | Groovy Map containing sample information e.g. [ id:'test', single_end:false ] - pattern: "*_{fastqc.zip}" - "*.zip": type: map description: | Groovy Map containing sample information e.g. [ id:'test', single_end:false ] pattern: "*_{fastqc.zip}" - - versions: - - versions.yml: - type: file - description: File containing software versions - pattern: "versions.yml" + versions: + - versions.yml: + type: file + description: File containing software versions + pattern: "versions.yml" + ontologies: + - edam: http://edamontology.org/format_3750 # YAML authors: - "@drpatelh" - "@ewels" diff --git a/modules/nf-core/trimgalore/tests/main.nf.test b/modules/nf-core/trimgalore/tests/main.nf.test index c01672c4..e2f11e03 100644 --- a/modules/nf-core/trimgalore/tests/main.nf.test +++ b/modules/nf-core/trimgalore/tests/main.nf.test @@ -185,4 +185,4 @@ nextflow_process { ) } } -} \ No newline at end of file +} diff --git a/subworkflows/local/prepare/prepare_shortreads/main.nf b/subworkflows/local/prepare/prepare_shortreads/main.nf index bea7d700..d61ef2dc 100644 --- a/subworkflows/local/prepare/prepare_shortreads/main.nf +++ b/subworkflows/local/prepare/prepare_shortreads/main.nf @@ -60,7 +60,7 @@ workflow PREPARE_SHORTREADS { ) .set { trim_in } - FASTP(trim_in) + FASTP(trim_in, false, false, false) FASTP.out.reads .filter { it -> it[0].ids } diff --git a/subworkflows/nf-core/bam_sort_stats_samtools/main.nf b/subworkflows/nf-core/bam_sort_stats_samtools/main.nf index b716375b..46072636 100644 --- a/subworkflows/nf-core/bam_sort_stats_samtools/main.nf +++ b/subworkflows/nf-core/bam_sort_stats_samtools/main.nf @@ -15,7 +15,7 @@ workflow BAM_SORT_STATS_SAMTOOLS { ch_versions = Channel.empty() - SAMTOOLS_SORT ( ch_bam, ch_fasta ) + SAMTOOLS_SORT ( ch_bam, ch_fasta, '' ) ch_versions = ch_versions.mix(SAMTOOLS_SORT.out.versions.first()) SAMTOOLS_INDEX ( SAMTOOLS_SORT.out.bam ) diff --git a/subworkflows/nf-core/bam_sort_stats_samtools/tests/main.nf.test.snap b/subworkflows/nf-core/bam_sort_stats_samtools/tests/main.nf.test.snap index c3c9a049..1cc7fa3d 100644 --- a/subworkflows/nf-core/bam_sort_stats_samtools/tests/main.nf.test.snap +++ b/subworkflows/nf-core/bam_sort_stats_samtools/tests/main.nf.test.snap @@ -25,22 +25,22 @@ "id": "test", "single_end": false }, - "test.stats:md5,2fe0f3a7a1f07906061c1dadb62e0d05" + "test.stats:md5,1101fe711c4a389fdb5c4a1532107d1f" ] ], [ - "versions.yml:md5,032c89015461d597fcc5a5331b619d0a", - "versions.yml:md5,416c5e4a374c61167db999b0e400e3cf", - "versions.yml:md5,721391fd94c417808516480c9451c6fd", - "versions.yml:md5,9e12386b91a2977d23292754e3bcb522", - "versions.yml:md5,c294c162aeb09862cc5e55b602647452" + "versions.yml:md5,199b33608db0a9457645d070ab24b71b", + "versions.yml:md5,54f02345c3a7699f9272e6ef9ce916c5", + "versions.yml:md5,6a93080732801bacb21c3acbe13858a5", + "versions.yml:md5,8f2a377b0149cf437f0730d293b62c81", + "versions.yml:md5,de3b0ae7c3ac4188662d57fd3219e312" ] ], "meta": { - "nf-test": "0.9.0", - "nextflow": "24.04.4" + "nf-test": "0.9.2", + "nextflow": "25.04.6" }, - "timestamp": "2024-09-16T08:26:24.36986488" + "timestamp": "2025-09-10T13:37:01.228228" }, "test_bam_sort_stats_samtools_paired_end": { "content": [ @@ -68,22 +68,22 @@ "id": "test", "single_end": false }, - "test.stats:md5,ba007b13981dad548358c7c957d41e12" + "test.stats:md5,f26c554c244ee86c89d62ebed509fd95" ] ], [ - "versions.yml:md5,032c89015461d597fcc5a5331b619d0a", - "versions.yml:md5,416c5e4a374c61167db999b0e400e3cf", - "versions.yml:md5,721391fd94c417808516480c9451c6fd", - "versions.yml:md5,9e12386b91a2977d23292754e3bcb522", - "versions.yml:md5,c294c162aeb09862cc5e55b602647452" + "versions.yml:md5,199b33608db0a9457645d070ab24b71b", + "versions.yml:md5,54f02345c3a7699f9272e6ef9ce916c5", + "versions.yml:md5,6a93080732801bacb21c3acbe13858a5", + "versions.yml:md5,8f2a377b0149cf437f0730d293b62c81", + "versions.yml:md5,de3b0ae7c3ac4188662d57fd3219e312" ] ], "meta": { - "nf-test": "0.9.0", - "nextflow": "24.04.4" + "nf-test": "0.9.2", + "nextflow": "25.04.6" }, - "timestamp": "2024-09-16T08:26:38.683996037" + "timestamp": "2025-09-10T13:37:08.591748" }, "test_bam_sort_stats_samtools_single_end - stub": { "content": [ @@ -137,11 +137,11 @@ ] ], "6": [ - "versions.yml:md5,032c89015461d597fcc5a5331b619d0a", - "versions.yml:md5,416c5e4a374c61167db999b0e400e3cf", - "versions.yml:md5,721391fd94c417808516480c9451c6fd", - "versions.yml:md5,9e12386b91a2977d23292754e3bcb522", - "versions.yml:md5,c294c162aeb09862cc5e55b602647452" + "versions.yml:md5,199b33608db0a9457645d070ab24b71b", + "versions.yml:md5,54f02345c3a7699f9272e6ef9ce916c5", + "versions.yml:md5,6a93080732801bacb21c3acbe13858a5", + "versions.yml:md5,8f2a377b0149cf437f0730d293b62c81", + "versions.yml:md5,de3b0ae7c3ac4188662d57fd3219e312" ], "bai": [ [ @@ -192,19 +192,19 @@ ] ], "versions": [ - "versions.yml:md5,032c89015461d597fcc5a5331b619d0a", - "versions.yml:md5,416c5e4a374c61167db999b0e400e3cf", - "versions.yml:md5,721391fd94c417808516480c9451c6fd", - "versions.yml:md5,9e12386b91a2977d23292754e3bcb522", - "versions.yml:md5,c294c162aeb09862cc5e55b602647452" + "versions.yml:md5,199b33608db0a9457645d070ab24b71b", + "versions.yml:md5,54f02345c3a7699f9272e6ef9ce916c5", + "versions.yml:md5,6a93080732801bacb21c3acbe13858a5", + "versions.yml:md5,8f2a377b0149cf437f0730d293b62c81", + "versions.yml:md5,de3b0ae7c3ac4188662d57fd3219e312" ] } ], "meta": { - "nf-test": "0.9.0", - "nextflow": "24.04.4" + "nf-test": "0.9.2", + "nextflow": "25.04.6" }, - "timestamp": "2024-09-16T08:07:18.896460047" + "timestamp": "2025-09-10T13:37:15.315753" }, "test_bam_sort_stats_samtools_paired_end - stub": { "content": [ @@ -258,11 +258,11 @@ ] ], "6": [ - "versions.yml:md5,032c89015461d597fcc5a5331b619d0a", - "versions.yml:md5,416c5e4a374c61167db999b0e400e3cf", - "versions.yml:md5,721391fd94c417808516480c9451c6fd", - "versions.yml:md5,9e12386b91a2977d23292754e3bcb522", - "versions.yml:md5,c294c162aeb09862cc5e55b602647452" + "versions.yml:md5,199b33608db0a9457645d070ab24b71b", + "versions.yml:md5,54f02345c3a7699f9272e6ef9ce916c5", + "versions.yml:md5,6a93080732801bacb21c3acbe13858a5", + "versions.yml:md5,8f2a377b0149cf437f0730d293b62c81", + "versions.yml:md5,de3b0ae7c3ac4188662d57fd3219e312" ], "bai": [ [ @@ -313,18 +313,18 @@ ] ], "versions": [ - "versions.yml:md5,032c89015461d597fcc5a5331b619d0a", - "versions.yml:md5,416c5e4a374c61167db999b0e400e3cf", - "versions.yml:md5,721391fd94c417808516480c9451c6fd", - "versions.yml:md5,9e12386b91a2977d23292754e3bcb522", - "versions.yml:md5,c294c162aeb09862cc5e55b602647452" + "versions.yml:md5,199b33608db0a9457645d070ab24b71b", + "versions.yml:md5,54f02345c3a7699f9272e6ef9ce916c5", + "versions.yml:md5,6a93080732801bacb21c3acbe13858a5", + "versions.yml:md5,8f2a377b0149cf437f0730d293b62c81", + "versions.yml:md5,de3b0ae7c3ac4188662d57fd3219e312" ] } ], "meta": { - "nf-test": "0.9.0", - "nextflow": "24.04.4" + "nf-test": "0.9.2", + "nextflow": "25.04.6" }, - "timestamp": "2024-09-16T08:07:39.028688324" + "timestamp": "2025-09-10T13:37:22.163708" } } \ No newline at end of file diff --git a/subworkflows/nf-core/bam_stats_samtools/tests/main.nf.test.snap b/subworkflows/nf-core/bam_stats_samtools/tests/main.nf.test.snap index 8ca22526..dadc71a7 100644 --- a/subworkflows/nf-core/bam_stats_samtools/tests/main.nf.test.snap +++ b/subworkflows/nf-core/bam_stats_samtools/tests/main.nf.test.snap @@ -17,7 +17,7 @@ "id": "test", "single_end": true }, - "test.flagstat:md5,d41d8cd98f00b204e9800998ecf8427e" + "test.flagstat:md5,67394650dbae96d1a4fcc70484822159" ] ], "2": [ @@ -30,9 +30,9 @@ ] ], "3": [ - "versions.yml:md5,73c55059ed478cd2f9cd93dd3185da3a", - "versions.yml:md5,80d8653e01575b3c381d87073f672fb5", - "versions.yml:md5,cb889532237a2f3d813978ac14a12d51" + "versions.yml:md5,088c14fc7d21fa2e662860d7cbf9a181", + "versions.yml:md5,ade6457ea5ae73a41c505bb22681d0fa", + "versions.yml:md5,c7826ee705b3af5245db8d9cf64ed520" ], "flagstat": [ [ @@ -40,7 +40,7 @@ "id": "test", "single_end": true }, - "test.flagstat:md5,d41d8cd98f00b204e9800998ecf8427e" + "test.flagstat:md5,67394650dbae96d1a4fcc70484822159" ] ], "idxstats": [ @@ -62,17 +62,17 @@ ] ], "versions": [ - "versions.yml:md5,73c55059ed478cd2f9cd93dd3185da3a", - "versions.yml:md5,80d8653e01575b3c381d87073f672fb5", - "versions.yml:md5,cb889532237a2f3d813978ac14a12d51" + "versions.yml:md5,088c14fc7d21fa2e662860d7cbf9a181", + "versions.yml:md5,ade6457ea5ae73a41c505bb22681d0fa", + "versions.yml:md5,c7826ee705b3af5245db8d9cf64ed520" ] } ], "meta": { - "nf-test": "0.9.0", - "nextflow": "24.04.4" + "nf-test": "0.9.2", + "nextflow": "25.04.6" }, - "timestamp": "2024-09-16T08:08:35.660286921" + "timestamp": "2025-09-15T15:20:35.417926" }, "test_bam_stats_samtools_single_end - stub": { "content": [ @@ -92,7 +92,7 @@ "id": "test", "single_end": true }, - "test.flagstat:md5,d41d8cd98f00b204e9800998ecf8427e" + "test.flagstat:md5,67394650dbae96d1a4fcc70484822159" ] ], "2": [ @@ -105,9 +105,9 @@ ] ], "3": [ - "versions.yml:md5,73c55059ed478cd2f9cd93dd3185da3a", - "versions.yml:md5,80d8653e01575b3c381d87073f672fb5", - "versions.yml:md5,cb889532237a2f3d813978ac14a12d51" + "versions.yml:md5,088c14fc7d21fa2e662860d7cbf9a181", + "versions.yml:md5,ade6457ea5ae73a41c505bb22681d0fa", + "versions.yml:md5,c7826ee705b3af5245db8d9cf64ed520" ], "flagstat": [ [ @@ -115,7 +115,7 @@ "id": "test", "single_end": true }, - "test.flagstat:md5,d41d8cd98f00b204e9800998ecf8427e" + "test.flagstat:md5,67394650dbae96d1a4fcc70484822159" ] ], "idxstats": [ @@ -137,17 +137,17 @@ ] ], "versions": [ - "versions.yml:md5,73c55059ed478cd2f9cd93dd3185da3a", - "versions.yml:md5,80d8653e01575b3c381d87073f672fb5", - "versions.yml:md5,cb889532237a2f3d813978ac14a12d51" + "versions.yml:md5,088c14fc7d21fa2e662860d7cbf9a181", + "versions.yml:md5,ade6457ea5ae73a41c505bb22681d0fa", + "versions.yml:md5,c7826ee705b3af5245db8d9cf64ed520" ] } ], "meta": { - "nf-test": "0.9.0", - "nextflow": "24.04.4" + "nf-test": "0.9.2", + "nextflow": "25.04.6" }, - "timestamp": "2024-09-16T08:08:24.220305512" + "timestamp": "2025-09-15T15:20:25.439222" }, "test_bam_stats_samtools_paired_end_cram - stub": { "content": [ @@ -167,7 +167,7 @@ "id": "test", "single_end": false }, - "test.flagstat:md5,d41d8cd98f00b204e9800998ecf8427e" + "test.flagstat:md5,67394650dbae96d1a4fcc70484822159" ] ], "2": [ @@ -180,9 +180,9 @@ ] ], "3": [ - "versions.yml:md5,73c55059ed478cd2f9cd93dd3185da3a", - "versions.yml:md5,80d8653e01575b3c381d87073f672fb5", - "versions.yml:md5,cb889532237a2f3d813978ac14a12d51" + "versions.yml:md5,088c14fc7d21fa2e662860d7cbf9a181", + "versions.yml:md5,ade6457ea5ae73a41c505bb22681d0fa", + "versions.yml:md5,c7826ee705b3af5245db8d9cf64ed520" ], "flagstat": [ [ @@ -190,7 +190,7 @@ "id": "test", "single_end": false }, - "test.flagstat:md5,d41d8cd98f00b204e9800998ecf8427e" + "test.flagstat:md5,67394650dbae96d1a4fcc70484822159" ] ], "idxstats": [ @@ -212,17 +212,17 @@ ] ], "versions": [ - "versions.yml:md5,73c55059ed478cd2f9cd93dd3185da3a", - "versions.yml:md5,80d8653e01575b3c381d87073f672fb5", - "versions.yml:md5,cb889532237a2f3d813978ac14a12d51" + "versions.yml:md5,088c14fc7d21fa2e662860d7cbf9a181", + "versions.yml:md5,ade6457ea5ae73a41c505bb22681d0fa", + "versions.yml:md5,c7826ee705b3af5245db8d9cf64ed520" ] } ], "meta": { - "nf-test": "0.9.0", - "nextflow": "24.04.4" + "nf-test": "0.9.2", + "nextflow": "25.04.6" }, - "timestamp": "2024-09-16T08:08:54.206770141" + "timestamp": "2025-09-15T15:20:46.02738" }, "test_bam_stats_samtools_single_end": { "content": [ @@ -250,20 +250,20 @@ "id": "test", "single_end": true }, - "test.stats:md5,291bb2393ec947140d12d42c2795b222" + "test.stats:md5,7a05a22bdb17e8df6e8c2d100ff09a31" ] ], [ - "versions.yml:md5,73c55059ed478cd2f9cd93dd3185da3a", - "versions.yml:md5,80d8653e01575b3c381d87073f672fb5", - "versions.yml:md5,cb889532237a2f3d813978ac14a12d51" + "versions.yml:md5,088c14fc7d21fa2e662860d7cbf9a181", + "versions.yml:md5,ade6457ea5ae73a41c505bb22681d0fa", + "versions.yml:md5,c7826ee705b3af5245db8d9cf64ed520" ] ], "meta": { - "nf-test": "0.9.0", - "nextflow": "24.04.4" + "nf-test": "0.9.2", + "nextflow": "25.04.6" }, - "timestamp": "2024-09-16T08:07:49.731645858" + "timestamp": "2025-09-15T15:20:00.064679" }, "test_bam_stats_samtools_paired_end": { "content": [ @@ -291,20 +291,20 @@ "id": "test", "single_end": true }, - "test.stats:md5,8140d69cdedd77570ca1d7618a744e16" + "test.stats:md5,a391612b5ef5b181e854ccaad8c8a068" ] ], [ - "versions.yml:md5,73c55059ed478cd2f9cd93dd3185da3a", - "versions.yml:md5,80d8653e01575b3c381d87073f672fb5", - "versions.yml:md5,cb889532237a2f3d813978ac14a12d51" + "versions.yml:md5,088c14fc7d21fa2e662860d7cbf9a181", + "versions.yml:md5,ade6457ea5ae73a41c505bb22681d0fa", + "versions.yml:md5,c7826ee705b3af5245db8d9cf64ed520" ] ], "meta": { - "nf-test": "0.9.0", - "nextflow": "24.04.4" + "nf-test": "0.9.2", + "nextflow": "25.04.6" }, - "timestamp": "2024-09-16T08:08:01.421996172" + "timestamp": "2025-09-15T15:20:08.141202" }, "test_bam_stats_samtools_paired_end_cram": { "content": [ @@ -332,19 +332,19 @@ "id": "test", "single_end": false }, - "test.stats:md5,1622856127bafd6cdbadee9cd64ec9b7" + "test.stats:md5,2b0e31ab01b867a6ff312023ae03838d" ] ], [ - "versions.yml:md5,73c55059ed478cd2f9cd93dd3185da3a", - "versions.yml:md5,80d8653e01575b3c381d87073f672fb5", - "versions.yml:md5,cb889532237a2f3d813978ac14a12d51" + "versions.yml:md5,088c14fc7d21fa2e662860d7cbf9a181", + "versions.yml:md5,ade6457ea5ae73a41c505bb22681d0fa", + "versions.yml:md5,c7826ee705b3af5245db8d9cf64ed520" ] ], "meta": { - "nf-test": "0.9.0", - "nextflow": "24.04.4" + "nf-test": "0.9.2", + "nextflow": "25.04.6" }, - "timestamp": "2024-09-16T08:08:12.640915756" + "timestamp": "2025-09-15T15:20:16.06318" } } \ No newline at end of file From 3e77ece6a4b3c96eebfc0872d50c004216f1c782 Mon Sep 17 00:00:00 2001 From: Niklas Schandry Date: Tue, 9 Dec 2025 10:39:17 +0100 Subject: [PATCH 075/162] fastp config, remove trimgalore --- conf/modules.config | 2 +- .../{trimgalore.config => fastp.config} | 4 ++-- subworkflows/local/prepare/main.nf | 6 +++--- .../local/prepare/prepare_shortreads/main.nf | 2 +- workflows/genomeassembler.nf | 19 +++++++++---------- 5 files changed, 16 insertions(+), 17 deletions(-) rename conf/modules/{trimgalore.config => fastp.config} (64%) diff --git a/conf/modules.config b/conf/modules.config index efacbbdd..e94bbf69 100644 --- a/conf/modules.config +++ b/conf/modules.config @@ -22,7 +22,7 @@ process { // Read preparation includeConfig 'modules/ont-prep.config' includeConfig 'modules/hifi-prep.config' -includeConfig 'modules/trimgalore.config' +includeConfig 'modules/fastp.config' // Assembly includeConfig 'modules/assembly.config' diff --git a/conf/modules/trimgalore.config b/conf/modules/fastp.config similarity index 64% rename from conf/modules/trimgalore.config rename to conf/modules/fastp.config index dc899e99..58abec2f 100644 --- a/conf/modules/trimgalore.config +++ b/conf/modules/fastp.config @@ -1,7 +1,7 @@ process { - withName: TRIMGALORE { + withName: FASTP { publishDir = [ - path: { "${params.outdir}/${meta.id}/reads/trimgalore" }, + path: { "${params.outdir}/${meta.id}/reads/fastp" }, mode: params.publish_dir_mode, saveAs: { filename -> filename.equals('versions.yml') ? null : filename } ] diff --git a/subworkflows/local/prepare/main.nf b/subworkflows/local/prepare/main.nf index cfc10a5a..7119eb89 100644 --- a/subworkflows/local/prepare/main.nf +++ b/subworkflows/local/prepare/main.nf @@ -4,7 +4,7 @@ include { PREPARE_SHORTREADS as SHORTREADS } from './prepare_shortreads/main' include { JELLYFISH } from './jellyfish/main' workflow PREPARE { - // TODO: Switch to fastp and fastplong. + // TODO: Switch to fastp. /* Grouped preparations @@ -166,7 +166,7 @@ workflow PREPARE { def slurp = new groovy.json.JsonSlurper() ch_main_prepared - .filter { it.qc_reads.toLowerCase() == "ont" } + .filter { it -> it.qc_reads.toLowerCase() == "ont" } .map { it -> it.collect { entry -> [ entry.value, entry ] } } .join(ONT.out.fastplong_ont_reports .map { it -> [ meta: it[0], fastplong_json: it[1] ]} @@ -174,7 +174,7 @@ workflow PREPARE { ) .mix( ch_main_prepared - .filter { it.qc_reads.toLowerCase() == "hifi" } + .filter { it -> it.qc_reads.toLowerCase() == "hifi" } .map { it -> it.collect { entry -> [ entry.value, entry ] } } .join(HIFI.out.fastplong_hifi_reports .map { it -> [ meta: it[0], fastplong_json: it[1] ]} diff --git a/subworkflows/local/prepare/prepare_shortreads/main.nf b/subworkflows/local/prepare/prepare_shortreads/main.nf index d61ef2dc..568d7159 100644 --- a/subworkflows/local/prepare/prepare_shortreads/main.nf +++ b/subworkflows/local/prepare/prepare_shortreads/main.nf @@ -10,7 +10,7 @@ workflow PREPARE_SHORTREADS { channel.empty().set { ch_versions } shortreads_in - .map { create_shortread_channel(it) } + .map { it -> create_shortread_channel(it) } .set { shortreads } shortreads_in diff --git a/workflows/genomeassembler.nf b/workflows/genomeassembler.nf index 92338344..390d593d 100644 --- a/workflows/genomeassembler.nf +++ b/workflows/genomeassembler.nf @@ -118,14 +118,14 @@ workflow GENOMEASSEMBLER { [ [id: something, meta: [id: something]], - ["path", somepath: "path"] + ["/path", somepath: "/path"] ] This can be joined to [ [id: something, meta: [id: something]], - ["different_path", otherpath: "path"] + ["different_path", otherpath: "different_path"] ] This makes it possible to join on the first element (the one containing meta): @@ -144,7 +144,7 @@ workflow GENOMEASSEMBLER { otherpath: "different_path" ] - This is sadly a somewhat frequent pattern in this pipeline and + This is (sadly) a somewhat frequent pattern in this pipeline and it is done like this: map_channel_1 @@ -157,16 +157,15 @@ workflow GENOMEASSEMBLER { // After joining re-create the maps from the stored map .map { it -> it.collect { _entry, map -> [ (map.key): map.value ] }.collectEntries() } */ - channel.empty().set { meryl_kmers } channel.empty().set { ch_versions } // Initialize channels for QC report collection - Channel + channel .of([]) .tap { quast_files } - .tap { fastplong_reports } + .tap { fastplong_jsons } .tap { genomescope_files } .map { it -> ["dummy", it] } .tap { busco_files } @@ -214,7 +213,7 @@ workflow GENOMEASSEMBLER { ch_versions = ch_versions.mix(POLISH.out.versions) ch_main_polished - .branch { + .branch { it -> scaffold: it.scaffold_links || it.scaffold_longstitch || it.scaffold_ragtag no_scaffold: !it.scaffold_links && !it.scaffold_longstitch && !it.scaffold_ragtag } @@ -302,12 +301,12 @@ workflow GENOMEASSEMBLER { .collect() .set { merqury_files } - Channel + channel .fromPath("${projectDir}/assets/report/*") .collect() .set { report_files } // Report files - Channel + channel .fromPath("${projectDir}/assets/report/functions/*") .collect() .set { report_functions } @@ -321,7 +320,7 @@ workflow GENOMEASSEMBLER { quast_files, busco_files, merqury_files, - Channel.fromPath("${params.outdir}/pipeline_info/nf_core_pipeline_software_versions.yml"), + channel.fromPath("${params.outdir}/pipeline_info/nf_core_pipeline_software_versions.yml"), ch_main.map { it -> [sample: [id: it.meta.id, group: it.group]]}.collect() ) From 18f303fb078f4d7cee4300d2467d3104467b2941 Mon Sep 17 00:00:00 2001 From: Niklas Schandry Date: Tue, 9 Dec 2025 10:52:05 +0100 Subject: [PATCH 076/162] update rocrate --- nextflow.config | 3 +-- ro-crate-metadata.json | 16 ++++++++-------- 2 files changed, 9 insertions(+), 10 deletions(-) diff --git a/nextflow.config b/nextflow.config index 040ea564..2cbd8a4b 100644 --- a/nextflow.config +++ b/nextflow.config @@ -119,8 +119,6 @@ profiles { process.beforeScript = 'echo $HOSTNAME' cleanup = false nextflow.enable.configProcessNamesValidation = true - dumpHashes = true - } conda { conda.enabled = true @@ -289,6 +287,7 @@ process.shell = [ nextflow.enable.configProcessNamesValidation = false def trace_timestamp = new java.util.Date().format( 'yyyy-MM-dd_HH-mm-ss') + timeline { enabled = true file = "${params.outdir}/pipeline_info/execution_timeline_${trace_timestamp}.html" diff --git a/ro-crate-metadata.json b/ro-crate-metadata.json index 30bcdcd1..ad17825c 100644 --- a/ro-crate-metadata.json +++ b/ro-crate-metadata.json @@ -134,13 +134,13 @@ ], "creator": [ { - "@id": "https://orcid.org/0000-0002-7860-3560" + "@id": "https://orcid.org/0000-0003-3099-7860" }, { "@id": "https://orcid.org/0000-0003-1675-0677" }, { - "@id": "https://orcid.org/0000-0003-3099-7860" + "@id": "https://orcid.org/0000-0002-7860-3560" } ], "dateCreated": "", @@ -323,10 +323,10 @@ "url": "https://nf-co.re/" }, { - "@id": "https://orcid.org/0000-0002-7860-3560", + "@id": "https://orcid.org/0000-0003-3099-7860", "@type": "Person", - "email": "mm49@sanger.ac.uk", - "name": "Matthieu Muffato" + "email": "niklas@bio.lmu.de", + "name": "Niklas Schandry" }, { "@id": "https://orcid.org/0000-0003-1675-0677", @@ -335,10 +335,10 @@ "name": "Mahesh Binzer-Panchal" }, { - "@id": "https://orcid.org/0000-0003-3099-7860", + "@id": "https://orcid.org/0000-0002-7860-3560", "@type": "Person", - "email": "niklas@bio.lmu.de", - "name": "Niklas Schandry" + "email": "mm49@sanger.ac.uk", + "name": "Matthieu Muffato" } ] } \ No newline at end of file From 36c3f54c3f7b382f5e286716ae28928b6888a28c Mon Sep 17 00:00:00 2001 From: Niklas Schandry Date: Tue, 9 Dec 2025 13:20:29 +0100 Subject: [PATCH 077/162] improve assembler filtering logic --- subworkflows/local/assemble/main.nf | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/subworkflows/local/assemble/main.nf b/subworkflows/local/assemble/main.nf index c21f0df1..a23f7f98 100644 --- a/subworkflows/local/assemble/main.nf +++ b/subworkflows/local/assemble/main.nf @@ -109,8 +109,8 @@ workflow ASSEMBLE { // These are the hifi samples ch_main_assemble_flye - // Those where the hifi assembler is flye, or where there is only one assembler and hifireads - .filter { it -> it.assembler2 == "flye" || (it.strategy == "single" && it.assembler1 == "flye" && it.hifireads)} + // Those where the hifi assembler is flye, or where there is only one assembler and only hifireads + .filter { it -> it.assembler2 == "flye" || (it.strategy == "single" && it.hifireads && !it.ontreads)} .multiMap { it -> reads: [ From 1697bdea2892e3e5f3e0c3635238677c240b5e30 Mon Sep 17 00:00:00 2001 From: Niklas Schandry Date: Tue, 9 Dec 2025 13:32:46 +0100 Subject: [PATCH 078/162] update CHANGELONG --- CHANGELOG.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1e805589..769ac5e0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,7 +5,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## v2.0.0 'Saffron Vulture' - [2025-xx-xx] -v2.0.0 of this pipeline is a (almost complete) refactor of the pipeline to facilitate sample-level parameteristation. Besides, it contains some additional changes: +v2.0.0 of this pipeline is a large refactor of the pipeline to facilitate sample-level parameteristation. This allows to either parameterise the _pipeline_ using `params`, or parameterise _samples_ via the `input` samplesheet. In case both types of parameterisations are used, sample parameters will overwrite the pipeline parameters for that sample. +In addition, v2.0.0 contains some these changes: ### `Added` From 7df7552f905ec51cbfecd238587749dff750c2a5 Mon Sep 17 00:00:00 2001 From: Niklas Schandry Date: Tue, 9 Dec 2025 13:34:12 +0100 Subject: [PATCH 079/162] fix validation during pipeline initialisation --- .../main.nf | 26 ++++--------------- 1 file changed, 5 insertions(+), 21 deletions(-) diff --git a/subworkflows/local/utils_nfcore_genomeassembler_pipeline/main.nf b/subworkflows/local/utils_nfcore_genomeassembler_pipeline/main.nf index 94bf7ff8..005f526e 100644 --- a/subworkflows/local/utils_nfcore_genomeassembler_pipeline/main.nf +++ b/subworkflows/local/utils_nfcore_genomeassembler_pipeline/main.nf @@ -95,7 +95,7 @@ workflow PIPELINE_INITIALISATION { // Create channel from input file provided through params.input // - Channel.fromPath(params.input) + channel.fromPath(params.input) .splitCsv(header: true) .map { it -> [ @@ -196,27 +196,11 @@ workflow PIPELINE_INITIALISATION { "invalid" ] : null, - // Check if primers for lima are provided - (it.hifi_trim && !it.hifi_primers) - ? - [ - println("Please confirm samplesheet: [sample: $it.meta.id]: Please provide the primers used for pacbio sequencing to trim with lima."), - "invalid" - ] - : null, // Check if reads and strategy match (it.strategy == "single" && it.ontreads && it.hifireads) ? [ - println("Please confirm samplesheet: [sample: $it.meta.id]: Stragety is $it.strategy, but both types of reads are provided."), - "invalid" - ] - : null, - // Check if assembler can do hybrid - (it.strategy == "single" && it.ont_reads && it.hifi_reads) - ? - [ - println("Please confirm samplesheet: [sample: $it.meta.id]: Stragety is $it.strategy, but both types of reads are provided."), + println("Please confirm samplesheet: [sample: $it.meta.id]: Strategy is $it.strategy, but both types of reads are provided."), "invalid" ] : null, @@ -240,7 +224,7 @@ workflow PIPELINE_INITIALISATION { (it.scaffold_longstitch && !it.genome_size && !(params.jellyfish || it.jellyfish)) ? [ - println("Please confirm samplesheet: [sample: $it.meta.id]: scaffolding with longstitch requires genome-size. Either provide genome-size estimate, or estimate from ONT reads with --jellyfish"), + println("Please confirm samplesheet: [sample: $it.meta.id]: scaffolding with longstitch requires genome-size. Either provide genome-size estimate, or estimate from reads with --jellyfish"), "invalid" ] : null, @@ -248,10 +232,10 @@ workflow PIPELINE_INITIALISATION { } .map { it -> it.collect() } .collect() - // error if >0 samples failed a check above + // warn if >0 samples failed a check above .subscribe { it -> it.contains("invalid") - ? error("Invalid combination in samplesheet") + ? log.warn("Invalid combination in samplesheet") : null } From bc6ab4a930a8ad5fe30df9304b5ad40ecae09c04 Mon Sep 17 00:00:00 2001 From: Niklas Schandry Date: Tue, 9 Dec 2025 14:52:40 +0100 Subject: [PATCH 080/162] update qc read path after fastplong --- subworkflows/local/prepare/main.nf | 3 +++ 1 file changed, 3 insertions(+) diff --git a/subworkflows/local/prepare/main.nf b/subworkflows/local/prepare/main.nf index 7119eb89..55129348 100644 --- a/subworkflows/local/prepare/main.nf +++ b/subworkflows/local/prepare/main.nf @@ -199,8 +199,11 @@ workflow PREPARE { JELLYFISH(ch_main_jellyfish_branched.jelly) + ch_main_jellyfish_branched.no_jelly .mix( JELLYFISH.out.main_out ) + // At this stage, make sure that qc_read_path for downstream qc is using the prepared reads. + .map { it -> it - it.subMap("qc_read_path") + [qc_read_path: it.qc_reads.toLowerCase() == "ont" ? it.ontreads : it.hifireads] } .set { main_out } main_out.dump(tag: "Prepare: Combined outputs") From 926bf410d0dae5ce2a94367a0f5eb051b46ab8f2 Mon Sep 17 00:00:00 2001 From: Niklas Schandry Date: Tue, 9 Dec 2025 14:53:35 +0100 Subject: [PATCH 081/162] fix scaffolding inputs --- subworkflows/local/scaffolding/links/main.nf | 9 ++++++--- subworkflows/local/scaffolding/longstitch/main.nf | 1 + subworkflows/local/scaffolding/ragtag/main.nf | 4 ++++ 3 files changed, 11 insertions(+), 3 deletions(-) diff --git a/subworkflows/local/scaffolding/links/main.nf b/subworkflows/local/scaffolding/links/main.nf index b6ed944b..e6816067 100644 --- a/subworkflows/local/scaffolding/links/main.nf +++ b/subworkflows/local/scaffolding/links/main.nf @@ -11,12 +11,15 @@ workflow RUN_LINKS { channel.empty().set { ch_versions } ch_main - .multiMap { - assembly: [it.meta, it.polished ? (it.polished.pilon ?: it.polished.medaka) : it.assembly] - reads: [it.meta, it.qc_reads_path] + .multiMap { it -> + assembly: [it.meta, it.polished ? (it.polished.pilon ?: it.polished.medaka) : it.assembly] + reads: [it.meta, it.qc_reads_path] } .set { links_in } + links_in.assembly.dump(tag: "SCAFFOLD: LINKS: Assembly inputs") + links_in.reads.dump(tag: "SCAFFOLD: LINKS: Read inputs") + LINKS(links_in.assembly, links_in.reads) LINKS.out.scaffolds_fasta .set { scaffolds } diff --git a/subworkflows/local/scaffolding/longstitch/main.nf b/subworkflows/local/scaffolding/longstitch/main.nf index b0a8a238..fb3f7051 100644 --- a/subworkflows/local/scaffolding/longstitch/main.nf +++ b/subworkflows/local/scaffolding/longstitch/main.nf @@ -31,6 +31,7 @@ workflow RUN_LONGSTITCH { } .set { longstitch_in } + longstitch_in.dump(tag: "SCAFFOLD: LONGSTITCH: inputs") LONGSTITCH(longstitch_in) LONGSTITCH.out.ntlLinks_arks_scaffolds diff --git a/subworkflows/local/scaffolding/ragtag/main.nf b/subworkflows/local/scaffolding/ragtag/main.nf index 3238c6c2..ada0a604 100644 --- a/subworkflows/local/scaffolding/ragtag/main.nf +++ b/subworkflows/local/scaffolding/ragtag/main.nf @@ -22,6 +22,10 @@ workflow RUN_RAGTAG { } .set { ragtag_in } + ragtag_in.assembly.dump(tag: "SCAFFOLD: RAGTAG: Assembly inputs") + ragtag_in.reference.dump(tag: "SCAFFOLD: RAGTAG: Reference inputs") + + RAGTAG_SCAFFOLD(ragtag_in.assembly, ragtag_in.reference, [[], []], [[], [], []]) RAGTAG_SCAFFOLD.out.corrected_assembly.set { scaffolds } From 92711ed67aa4d188df370c7fa87b6537a046dd36 Mon Sep 17 00:00:00 2001 From: Niklas Schandry Date: Tue, 9 Dec 2025 14:54:38 +0100 Subject: [PATCH 082/162] update qc_read_path management during input --- .../local/utils_nfcore_genomeassembler_pipeline/main.nf | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/subworkflows/local/utils_nfcore_genomeassembler_pipeline/main.nf b/subworkflows/local/utils_nfcore_genomeassembler_pipeline/main.nf index 005f526e..c422f682 100644 --- a/subworkflows/local/utils_nfcore_genomeassembler_pipeline/main.nf +++ b/subworkflows/local/utils_nfcore_genomeassembler_pipeline/main.nf @@ -160,7 +160,7 @@ workflow PIPELINE_INITIALISATION { assembly_map_bam: it.assembly_map_bam ?: params.ref_map_bam ?: null, // reads for qc qc_reads: ((it.qc_reads == "ont" || params.qc_reads == "ont") && it.ontreads) ? "ont" : "hifi", - qc_reads_path: ((it.qc_reads == "ont" || params.qc_reads == "ont") && it.ontreads) ? (it.ontreads) : (it.hifireads), + qc_reads_path: ((it.qc_reads == "ont" || params.qc_reads == "ont") && it.ontreads) ? (it.ontreads ?: params.ontreads) : (it.hifireads ?: params.hifireads), quast: it.quast ?: params.quast, busco: it.busco ?: params.busco, busco_lineage: it.busco_lineage ?: params.busco_lineage, From 71bdee6462bbdf9d487a3d49e34337e6c0d4a5f0 Mon Sep 17 00:00:00 2001 From: Niklas Schandry Date: Tue, 9 Dec 2025 15:50:44 +0100 Subject: [PATCH 083/162] fix links setting --- .../local/utils_nfcore_genomeassembler_pipeline/main.nf | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/subworkflows/local/utils_nfcore_genomeassembler_pipeline/main.nf b/subworkflows/local/utils_nfcore_genomeassembler_pipeline/main.nf index c422f682..3a69195e 100644 --- a/subworkflows/local/utils_nfcore_genomeassembler_pipeline/main.nf +++ b/subworkflows/local/utils_nfcore_genomeassembler_pipeline/main.nf @@ -145,7 +145,7 @@ workflow PIPELINE_INITIALISATION { medaka_model: it.medaka_model ?: params.medaka_model, polish_pilon: it.polish_pilon ?: params.polish_pilon, scaffold_longstitch: it.scaffold_longstitch ?: params.scaffold_longstitch, - scaffold_links: it.scaffold_longstitch ?: params.scaffold_links, + scaffold_links: it.scaffold_links ?: params.scaffold_links, scaffold_ragtag: it.scaffold_ragtag ?: params.scaffold_ragtag, use_ref: it.use_ref ?: params.use_ref ?: it.ref_fasta ? true : false, // not new From c12bb986406152d27a6f7be41f92049a938fec7e Mon Sep 17 00:00:00 2001 From: Niklas Schandry Date: Wed, 10 Dec 2025 08:55:35 +0100 Subject: [PATCH 084/162] test snapshot --- conf/test_full.config | 9 ++ tests/default.nf.test.snap | 320 ++++++++++++++++++++++++++++++++++++- 2 files changed, 324 insertions(+), 5 deletions(-) diff --git a/conf/test_full.config b/conf/test_full.config index 47fff452..83a6435d 100644 --- a/conf/test_full.config +++ b/conf/test_full.config @@ -15,4 +15,13 @@ params { config_profile_description = 'Full test dataset to check pipeline function' input = 'https://raw.githubusercontent.com/nf-core/test-datasets/genomeassembler/samplesheet/full_test_samplesheet.csv' + ontreads = 's3://nf-core-awsmegatests/genomeassembler/input/Col_0.ONT.porechopped.fastq.gz' + hifireads = 's3://nf-core-awsmegatests/genomeassembler/input/Col_0.hifi_reads.fastq.gz' + ref_fasta = 'http://raw.githubusercontent.com/schatzlab/Col-CEN/main/v1.2/Col-CEN_v1.2.fasta.gz' + ref_gff = 'http://raw.githubusercontent.com/schatzlab/Col-CEN/main/v1.2/Col-CEN_v1.2_genes.araport11.gff3.gz' + shortread_F = + quast = true + busco = true + jellyfish = true + use_ref = true } diff --git a/tests/default.nf.test.snap b/tests/default.nf.test.snap index fa487b04..a3f8f2bb 100644 --- a/tests/default.nf.test.snap +++ b/tests/default.nf.test.snap @@ -1,8 +1,40 @@ { "-profile test": { "content": [ - 0, + 39, { + "FASTPLONG_HIFI": { + "fastplong": "0.3.0" + }, + "FASTPLONG_ONT": { + "fastplong": "0.3.0" + }, + "FLYE_HIFI": { + "flye": "2.9.5-b1801" + }, + "FLYE_ONT": { + "flye": "2.9.5-b1801" + }, + "GFA_2_FA_HIFI": { + "awk": "mawk 1.3.4", + "gzip": 1.13 + }, + "GFA_2_FA_ONT": { + "awk": "mawk 1.3.4", + "gzip": 1.13 + }, + "HIFIASM": { + "hifiasm": "0.25.0-r726" + }, + "HIFIASM_ONT": { + "hifiasm": "0.25.0-r726" + }, + "LIFTOFF": { + "liftoff": "v1.6.3" + }, + "LONGSTITCH": { + "LongStitch": "1.0.5" + }, "Workflow": { "nf-core/genomeassembler": "v2.0.0dev" } @@ -10,16 +42,294 @@ [ "pipeline_info", "pipeline_info/nf_core_genomeassembler_software_versions.yml", - "pipeline_info/nf_core_pipeline_software_versions.yml" + "pipeline_info/nf_core_pipeline_software_versions.yml", + "test_flye_hifiasm", + "test_flye_hifiasm/assembly", + "test_flye_hifiasm/assembly/flye", + "test_flye_hifiasm/assembly/flye/test_flye_hifiasm.assembly.fasta.gz", + "test_flye_hifiasm/assembly/flye/test_flye_hifiasm.assembly_graph.gfa.gz", + "test_flye_hifiasm/assembly/flye/test_flye_hifiasm.assembly_graph.gv.gz", + "test_flye_hifiasm/assembly/flye/test_flye_hifiasm.assembly_info.txt", + "test_flye_hifiasm/assembly/flye/test_flye_hifiasm.flye.log", + "test_flye_hifiasm/assembly/flye/test_flye_hifiasm.params.json", + "test_flye_hifiasm/assembly/hifiasm", + "test_flye_hifiasm/assembly/hifiasm/fasta", + "test_flye_hifiasm/assembly/hifiasm/fasta/test_flye_hifiasm.bp.p_utg.fa.gz", + "test_flye_hifiasm/assembly/hifiasm/test_flye_hifiasm.bp.hap1.p_ctg.gfa", + "test_flye_hifiasm/assembly/hifiasm/test_flye_hifiasm.bp.hap2.p_ctg.gfa", + "test_flye_hifiasm/assembly/hifiasm/test_flye_hifiasm.bp.p_ctg.gfa", + "test_flye_hifiasm/assembly/hifiasm/test_flye_hifiasm.bp.p_utg.gfa", + "test_flye_hifiasm/assembly/hifiasm/test_flye_hifiasm.bp.r_utg.gfa", + "test_flye_hifiasm/assembly/hifiasm/test_flye_hifiasm.ec.bin", + "test_flye_hifiasm/assembly/hifiasm/test_flye_hifiasm.ovlp.reverse.bin", + "test_flye_hifiasm/assembly/hifiasm/test_flye_hifiasm.ovlp.source.bin", + "test_flye_hifiasm/assembly/hifiasm/test_flye_hifiasm.stderr.log", + "test_flye_hifiasm/reads", + "test_flye_hifiasm/reads/fastplong", + "test_flye_hifiasm/reads/fastplong/hifi", + "test_flye_hifiasm/reads/fastplong/hifi/test_flye_hifiasm_hifi.fastplong.fastq.gz", + "test_flye_hifiasm/reads/fastplong/hifi/test_flye_hifiasm_hifi.fastplong.html", + "test_flye_hifiasm/reads/fastplong/hifi/test_flye_hifiasm_hifi.fastplong.json", + "test_flye_hifiasm/reads/fastplong/hifi/test_flye_hifiasm_hifi.fastplong.log", + "test_flye_hifiasm/reads/fastplong/ont", + "test_flye_hifiasm/reads/fastplong/ont/test_flye_hifiasm_ont.fastplong.fastq.gz", + "test_flye_hifiasm/reads/fastplong/ont/test_flye_hifiasm_ont.fastplong.html", + "test_flye_hifiasm/reads/fastplong/ont/test_flye_hifiasm_ont.fastplong.json", + "test_flye_hifiasm/reads/fastplong/ont/test_flye_hifiasm_ont.fastplong.log", + "test_hifi_flye", + "test_hifi_flye/assembly", + "test_hifi_flye/assembly/hifiasm_ont", + "test_hifi_flye/assembly/hifiasm_ont/fasta", + "test_hifi_flye/assembly/hifiasm_ont/fasta/test_hifi_flye.bp.p_utg.fa.gz", + "test_hifi_flye/assembly/hifiasm_ont/test_hifi_flye.bp.hap1.p_ctg.gfa", + "test_hifi_flye/assembly/hifiasm_ont/test_hifi_flye.bp.hap2.p_ctg.gfa", + "test_hifi_flye/assembly/hifiasm_ont/test_hifi_flye.bp.p_ctg.gfa", + "test_hifi_flye/assembly/hifiasm_ont/test_hifi_flye.bp.p_utg.gfa", + "test_hifi_flye/assembly/hifiasm_ont/test_hifi_flye.bp.r_utg.gfa", + "test_hifi_flye/assembly/hifiasm_ont/test_hifi_flye.ec.bin", + "test_hifi_flye/assembly/hifiasm_ont/test_hifi_flye.ovlp.reverse.bin", + "test_hifi_flye/assembly/hifiasm_ont/test_hifi_flye.ovlp.source.bin", + "test_hifi_flye/assembly/hifiasm_ont/test_hifi_flye.stderr.log", + "test_hifi_flye/assembly/test_hifi_flye_assembly.gff3", + "test_hifi_flye/assembly/test_hifi_flye_assembly.unmapped.txt", + "test_hifi_flye/reads", + "test_hifi_flye/reads/fastplong", + "test_hifi_flye/reads/fastplong/hifi", + "test_hifi_flye/reads/fastplong/hifi/test_hifi_flye_hifi.fastplong.fastq.gz", + "test_hifi_flye/reads/fastplong/hifi/test_hifi_flye_hifi.fastplong.html", + "test_hifi_flye/reads/fastplong/hifi/test_hifi_flye_hifi.fastplong.json", + "test_hifi_flye/reads/fastplong/hifi/test_hifi_flye_hifi.fastplong.log", + "test_hifi_flye/reads/fastplong/ont", + "test_hifi_flye/reads/fastplong/ont/test_hifi_flye_ont.fastplong.fastq.gz", + "test_hifi_flye/reads/fastplong/ont/test_hifi_flye_ont.fastplong.html", + "test_hifi_flye/reads/fastplong/ont/test_hifi_flye_ont.fastplong.json", + "test_hifi_flye/reads/fastplong/ont/test_hifi_flye_ont.fastplong.log", + "test_hifi_flye/scaffold", + "test_hifi_flye/scaffold/ragtag", + "test_hifi_flye/scaffold/ragtag/test_hifi_flye_ragtag.agp", + "test_hifi_flye/scaffold/ragtag/test_hifi_flye_ragtag.fasta", + "test_hifi_flye/scaffold/ragtag/test_hifi_flye_ragtag.gff3", + "test_hifi_flye/scaffold/ragtag/test_hifi_flye_ragtag.stats", + "test_hifi_flye/scaffold/ragtag/test_hifi_flye_ragtag.unmapped.txt", + "test_hifi_hifiasm", + "test_hifi_hifiasm/assembly", + "test_hifi_hifiasm/assembly/hifiasm_ont", + "test_hifi_hifiasm/assembly/hifiasm_ont/fasta", + "test_hifi_hifiasm/assembly/hifiasm_ont/fasta/test_hifi_hifiasm.bp.p_utg.fa.gz", + "test_hifi_hifiasm/assembly/hifiasm_ont/test_hifi_hifiasm.bp.hap1.p_ctg.gfa", + "test_hifi_hifiasm/assembly/hifiasm_ont/test_hifi_hifiasm.bp.hap2.p_ctg.gfa", + "test_hifi_hifiasm/assembly/hifiasm_ont/test_hifi_hifiasm.bp.p_ctg.gfa", + "test_hifi_hifiasm/assembly/hifiasm_ont/test_hifi_hifiasm.bp.p_utg.gfa", + "test_hifi_hifiasm/assembly/hifiasm_ont/test_hifi_hifiasm.bp.r_utg.gfa", + "test_hifi_hifiasm/assembly/hifiasm_ont/test_hifi_hifiasm.ec.bin", + "test_hifi_hifiasm/assembly/hifiasm_ont/test_hifi_hifiasm.ovlp.reverse.bin", + "test_hifi_hifiasm/assembly/hifiasm_ont/test_hifi_hifiasm.ovlp.source.bin", + "test_hifi_hifiasm/assembly/hifiasm_ont/test_hifi_hifiasm.stderr.log", + "test_hifi_hifiasm/assembly/test_hifi_hifiasm_assembly.gff3", + "test_hifi_hifiasm/assembly/test_hifi_hifiasm_assembly.unmapped.txt", + "test_hifi_hifiasm/reads", + "test_hifi_hifiasm/reads/fastplong", + "test_hifi_hifiasm/reads/fastplong/hifi", + "test_hifi_hifiasm/reads/fastplong/hifi/test_hifi_hifiasm_hifi.fastplong.fastq.gz", + "test_hifi_hifiasm/reads/fastplong/hifi/test_hifi_hifiasm_hifi.fastplong.html", + "test_hifi_hifiasm/reads/fastplong/hifi/test_hifi_hifiasm_hifi.fastplong.json", + "test_hifi_hifiasm/reads/fastplong/hifi/test_hifi_hifiasm_hifi.fastplong.log", + "test_hifi_hifiasm/reads/fastplong/ont", + "test_hifi_hifiasm/reads/fastplong/ont/test_hifi_hifiasm_ont.fastplong.fastq.gz", + "test_hifi_hifiasm/reads/fastplong/ont/test_hifi_hifiasm_ont.fastplong.html", + "test_hifi_hifiasm/reads/fastplong/ont/test_hifi_hifiasm_ont.fastplong.json", + "test_hifi_hifiasm/reads/fastplong/ont/test_hifi_hifiasm_ont.fastplong.log", + "test_hifiasm_flye", + "test_hifiasm_flye/assembly", + "test_hifiasm_flye/assembly/flye", + "test_hifiasm_flye/assembly/flye/test_hifiasm_flye.assembly.fasta.gz", + "test_hifiasm_flye/assembly/flye/test_hifiasm_flye.assembly_graph.gfa.gz", + "test_hifiasm_flye/assembly/flye/test_hifiasm_flye.assembly_graph.gv.gz", + "test_hifiasm_flye/assembly/flye/test_hifiasm_flye.assembly_info.txt", + "test_hifiasm_flye/assembly/flye/test_hifiasm_flye.flye.log", + "test_hifiasm_flye/assembly/flye/test_hifiasm_flye.params.json", + "test_hifiasm_flye/assembly/hifiasm_ont", + "test_hifiasm_flye/assembly/hifiasm_ont/fasta", + "test_hifiasm_flye/assembly/hifiasm_ont/fasta/test_hifiasm_flye.bp.p_utg.fa.gz", + "test_hifiasm_flye/assembly/hifiasm_ont/test_hifiasm_flye.bp.hap1.p_ctg.gfa", + "test_hifiasm_flye/assembly/hifiasm_ont/test_hifiasm_flye.bp.hap2.p_ctg.gfa", + "test_hifiasm_flye/assembly/hifiasm_ont/test_hifiasm_flye.bp.p_ctg.gfa", + "test_hifiasm_flye/assembly/hifiasm_ont/test_hifiasm_flye.bp.p_utg.gfa", + "test_hifiasm_flye/assembly/hifiasm_ont/test_hifiasm_flye.bp.r_utg.gfa", + "test_hifiasm_flye/assembly/hifiasm_ont/test_hifiasm_flye.ec.bin", + "test_hifiasm_flye/assembly/hifiasm_ont/test_hifiasm_flye.ovlp.reverse.bin", + "test_hifiasm_flye/assembly/hifiasm_ont/test_hifiasm_flye.ovlp.source.bin", + "test_hifiasm_flye/assembly/hifiasm_ont/test_hifiasm_flye.stderr.log", + "test_hifiasm_flye/reads", + "test_hifiasm_flye/reads/fastplong", + "test_hifiasm_flye/reads/fastplong/hifi", + "test_hifiasm_flye/reads/fastplong/hifi/test_hifiasm_flye_hifi.fastplong.fastq.gz", + "test_hifiasm_flye/reads/fastplong/hifi/test_hifiasm_flye_hifi.fastplong.html", + "test_hifiasm_flye/reads/fastplong/hifi/test_hifiasm_flye_hifi.fastplong.json", + "test_hifiasm_flye/reads/fastplong/hifi/test_hifiasm_flye_hifi.fastplong.log", + "test_hifiasm_flye/reads/fastplong/ont", + "test_hifiasm_flye/reads/fastplong/ont/test_hifiasm_flye_ont.fastplong.fastq.gz", + "test_hifiasm_flye/reads/fastplong/ont/test_hifiasm_flye_ont.fastplong.html", + "test_hifiasm_flye/reads/fastplong/ont/test_hifiasm_flye_ont.fastplong.json", + "test_hifiasm_flye/reads/fastplong/ont/test_hifiasm_flye_ont.fastplong.log", + "test_hifiasm_ul", + "test_hifiasm_ul/assembly", + "test_hifiasm_ul/assembly/hifiasm", + "test_hifiasm_ul/assembly/hifiasm/fasta", + "test_hifiasm_ul/assembly/hifiasm/fasta/test_hifiasm_ul.bp.p_utg.fa.gz", + "test_hifiasm_ul/assembly/hifiasm/test_hifiasm_ul.bp.hap1.p_ctg.gfa", + "test_hifiasm_ul/assembly/hifiasm/test_hifiasm_ul.bp.hap2.p_ctg.gfa", + "test_hifiasm_ul/assembly/hifiasm/test_hifiasm_ul.bp.p_ctg.gfa", + "test_hifiasm_ul/assembly/hifiasm/test_hifiasm_ul.bp.p_utg.gfa", + "test_hifiasm_ul/assembly/hifiasm/test_hifiasm_ul.bp.r_utg.gfa", + "test_hifiasm_ul/assembly/hifiasm/test_hifiasm_ul.ec.bin", + "test_hifiasm_ul/assembly/hifiasm/test_hifiasm_ul.ovlp.reverse.bin", + "test_hifiasm_ul/assembly/hifiasm/test_hifiasm_ul.ovlp.source.bin", + "test_hifiasm_ul/assembly/hifiasm/test_hifiasm_ul.stderr.log", + "test_hifiasm_ul/assembly/test_hifiasm_ul_assembly.gff3", + "test_hifiasm_ul/assembly/test_hifiasm_ul_assembly.unmapped.txt", + "test_hifiasm_ul/reads", + "test_hifiasm_ul/reads/fastplong", + "test_hifiasm_ul/reads/fastplong/hifi", + "test_hifiasm_ul/reads/fastplong/hifi/test_hifiasm_ul_hifi.fastplong.fastq.gz", + "test_hifiasm_ul/reads/fastplong/hifi/test_hifiasm_ul_hifi.fastplong.html", + "test_hifiasm_ul/reads/fastplong/hifi/test_hifiasm_ul_hifi.fastplong.json", + "test_hifiasm_ul/reads/fastplong/hifi/test_hifiasm_ul_hifi.fastplong.log", + "test_hifiasm_ul/reads/fastplong/ont", + "test_hifiasm_ul/reads/fastplong/ont/test_hifiasm_ul_ont.fastplong.fastq.gz", + "test_hifiasm_ul/reads/fastplong/ont/test_hifiasm_ul_ont.fastplong.html", + "test_hifiasm_ul/reads/fastplong/ont/test_hifiasm_ul_ont.fastplong.json", + "test_hifiasm_ul/reads/fastplong/ont/test_hifiasm_ul_ont.fastplong.log", + "test_hifiasm_ul/scaffold", + "test_hifiasm_ul/scaffold/longstitch", + "test_hifiasm_ul/scaffold/longstitch/test_hifiasm_ul_longstitch.gff3", + "test_hifiasm_ul/scaffold/longstitch/test_hifiasm_ul_longstitch.tigmint-ntLink-arks.fa", + "test_hifiasm_ul/scaffold/longstitch/test_hifiasm_ul_longstitch.tigmint-ntLink.fa", + "test_hifiasm_ul/scaffold/longstitch/test_hifiasm_ul_longstitch.unmapped.txt", + "test_ont_flye", + "test_ont_flye/assembly", + "test_ont_flye/assembly/flye", + "test_ont_flye/assembly/flye/test_ont_flye.assembly.fasta.gz", + "test_ont_flye/assembly/flye/test_ont_flye.assembly_graph.gfa.gz", + "test_ont_flye/assembly/flye/test_ont_flye.assembly_graph.gv.gz", + "test_ont_flye/assembly/flye/test_ont_flye.assembly_info.txt", + "test_ont_flye/assembly/flye/test_ont_flye.flye.log", + "test_ont_flye/assembly/flye/test_ont_flye.params.json", + "test_ont_flye/reads", + "test_ont_flye/reads/fastplong", + "test_ont_flye/reads/fastplong/hifi", + "test_ont_flye/reads/fastplong/hifi/test_ont_flye_hifi.fastplong.fastq.gz", + "test_ont_flye/reads/fastplong/hifi/test_ont_flye_hifi.fastplong.html", + "test_ont_flye/reads/fastplong/hifi/test_ont_flye_hifi.fastplong.json", + "test_ont_flye/reads/fastplong/hifi/test_ont_flye_hifi.fastplong.log", + "test_ont_flye/reads/fastplong/ont", + "test_ont_flye/reads/fastplong/ont/test_ont_flye_ont.fastplong.fastq.gz", + "test_ont_flye/reads/fastplong/ont/test_ont_flye_ont.fastplong.html", + "test_ont_flye/reads/fastplong/ont/test_ont_flye_ont.fastplong.json", + "test_ont_flye/reads/fastplong/ont/test_ont_flye_ont.fastplong.log", + "test_ont_hifiasm", + "test_ont_hifiasm/assembly", + "test_ont_hifiasm/assembly/hifiasm_ont", + "test_ont_hifiasm/assembly/hifiasm_ont/fasta", + "test_ont_hifiasm/assembly/hifiasm_ont/fasta/test_ont_hifiasm.bp.p_utg.fa.gz", + "test_ont_hifiasm/assembly/hifiasm_ont/test_ont_hifiasm.bp.hap1.p_ctg.gfa", + "test_ont_hifiasm/assembly/hifiasm_ont/test_ont_hifiasm.bp.hap2.p_ctg.gfa", + "test_ont_hifiasm/assembly/hifiasm_ont/test_ont_hifiasm.bp.p_ctg.gfa", + "test_ont_hifiasm/assembly/hifiasm_ont/test_ont_hifiasm.bp.p_utg.gfa", + "test_ont_hifiasm/assembly/hifiasm_ont/test_ont_hifiasm.bp.r_utg.gfa", + "test_ont_hifiasm/assembly/hifiasm_ont/test_ont_hifiasm.ec.bin", + "test_ont_hifiasm/assembly/hifiasm_ont/test_ont_hifiasm.ovlp.reverse.bin", + "test_ont_hifiasm/assembly/hifiasm_ont/test_ont_hifiasm.ovlp.source.bin", + "test_ont_hifiasm/assembly/hifiasm_ont/test_ont_hifiasm.stderr.log", + "test_ont_hifiasm/assembly/test_ont_hifiasm_assembly.gff3", + "test_ont_hifiasm/assembly/test_ont_hifiasm_assembly.unmapped.txt", + "test_ont_hifiasm/reads", + "test_ont_hifiasm/reads/fastplong", + "test_ont_hifiasm/reads/fastplong/hifi", + "test_ont_hifiasm/reads/fastplong/hifi/test_ont_hifiasm_hifi.fastplong.fastq.gz", + "test_ont_hifiasm/reads/fastplong/hifi/test_ont_hifiasm_hifi.fastplong.html", + "test_ont_hifiasm/reads/fastplong/hifi/test_ont_hifiasm_hifi.fastplong.json", + "test_ont_hifiasm/reads/fastplong/hifi/test_ont_hifiasm_hifi.fastplong.log", + "test_ont_hifiasm/reads/fastplong/ont", + "test_ont_hifiasm/reads/fastplong/ont/test_ont_hifiasm_ont.fastplong.fastq.gz", + "test_ont_hifiasm/reads/fastplong/ont/test_ont_hifiasm_ont.fastplong.html", + "test_ont_hifiasm/reads/fastplong/ont/test_ont_hifiasm_ont.fastplong.json", + "test_ont_hifiasm/reads/fastplong/ont/test_ont_hifiasm_ont.fastplong.log", + "test_ont_hifiasm/scaffold", + "test_ont_hifiasm/scaffold/ragtag", + "test_ont_hifiasm/scaffold/ragtag/test_ont_hifiasm_ragtag.agp", + "test_ont_hifiasm/scaffold/ragtag/test_ont_hifiasm_ragtag.fasta", + "test_ont_hifiasm/scaffold/ragtag/test_ont_hifiasm_ragtag.gff3", + "test_ont_hifiasm/scaffold/ragtag/test_ont_hifiasm_ragtag.stats", + "test_ont_hifiasm/scaffold/ragtag/test_ont_hifiasm_ragtag.unmapped.txt" ], [ - + "test_flye_hifiasm.params.json:md5,afa91c041bce5e190f4a699d11b69db6", + "test_flye_hifiasm.bp.hap1.p_ctg.gfa:md5,46ee70869884ad585165bd48081414e9", + "test_flye_hifiasm.bp.hap2.p_ctg.gfa:md5,7792865547989d6d284f640425c4e36c", + "test_flye_hifiasm.bp.p_ctg.gfa:md5,8fe65466d76815ffe1663ff6d8f2e8d1", + "test_flye_hifiasm.bp.p_utg.gfa:md5,ba2c77ebdb2ad3e6060f5574e890c6eb", + "test_flye_hifiasm.bp.r_utg.gfa:md5,ba2c77ebdb2ad3e6060f5574e890c6eb", + "test_flye_hifiasm_hifi.fastplong.json:md5,fa79b411112c8176d9b400595a6cdc62", + "test_flye_hifiasm_ont.fastplong.json:md5,87b24dfdd4b0a7a1e743e34948751dfe", + "test_hifi_flye.bp.hap1.p_ctg.gfa:md5,c9a084903ea872a7ac28f3bbd5eee8f3", + "test_hifi_flye.bp.hap2.p_ctg.gfa:md5,d41d8cd98f00b204e9800998ecf8427e", + "test_hifi_flye.bp.p_ctg.gfa:md5,d78f7a647429b254d9d46aab4d1306a0", + "test_hifi_flye.bp.p_utg.gfa:md5,e2b6621386edda1636dfb84ddd676ea8", + "test_hifi_flye.bp.r_utg.gfa:md5,e2b6621386edda1636dfb84ddd676ea8", + "test_hifi_flye_assembly.unmapped.txt:md5,e99c39c4afff7b433fb3973359dae6a4", + "test_hifi_flye_hifi.fastplong.json:md5,36158364b20e75a0bfbc8e3f45d69d2a", + "test_hifi_flye_ont.fastplong.json:md5,eab2fc3332839ce008753c4e00aca912", + "test_hifi_flye_ragtag.fasta:md5,51f2975257b49657716d2ca7cc6d5fe2", + "test_hifi_flye_ragtag.stats:md5,466273b499384b2c781c83dc583b8c99", + "test_hifi_flye_ragtag.unmapped.txt:md5,e99c39c4afff7b433fb3973359dae6a4", + "test_hifi_hifiasm.bp.hap1.p_ctg.gfa:md5,c9a084903ea872a7ac28f3bbd5eee8f3", + "test_hifi_hifiasm.bp.hap2.p_ctg.gfa:md5,d41d8cd98f00b204e9800998ecf8427e", + "test_hifi_hifiasm.bp.p_ctg.gfa:md5,d78f7a647429b254d9d46aab4d1306a0", + "test_hifi_hifiasm.bp.p_utg.gfa:md5,e2b6621386edda1636dfb84ddd676ea8", + "test_hifi_hifiasm.bp.r_utg.gfa:md5,e2b6621386edda1636dfb84ddd676ea8", + "test_hifi_hifiasm_assembly.unmapped.txt:md5,e99c39c4afff7b433fb3973359dae6a4", + "test_hifi_hifiasm_hifi.fastplong.json:md5,ccca90d161aa7612409b275d38f12e23", + "test_hifi_hifiasm_ont.fastplong.json:md5,f83e4be65172652e188ba7afb8ba9219", + "test_hifiasm_flye.params.json:md5,54b576cb6d4d27656878a7fd3657bde9", + "test_hifiasm_flye.bp.hap1.p_ctg.gfa:md5,c9a084903ea872a7ac28f3bbd5eee8f3", + "test_hifiasm_flye.bp.hap2.p_ctg.gfa:md5,d41d8cd98f00b204e9800998ecf8427e", + "test_hifiasm_flye.bp.p_ctg.gfa:md5,d78f7a647429b254d9d46aab4d1306a0", + "test_hifiasm_flye.bp.p_utg.gfa:md5,e2b6621386edda1636dfb84ddd676ea8", + "test_hifiasm_flye.bp.r_utg.gfa:md5,e2b6621386edda1636dfb84ddd676ea8", + "test_hifiasm_flye_hifi.fastplong.json:md5,fafb25091859d8f815f1c12e1ce3c20a", + "test_hifiasm_flye_ont.fastplong.json:md5,ef9b8a8aae9d5c28f48245eea51542a9", + "test_hifiasm_ul.bp.hap1.p_ctg.gfa:md5,46ee70869884ad585165bd48081414e9", + "test_hifiasm_ul.bp.hap2.p_ctg.gfa:md5,7792865547989d6d284f640425c4e36c", + "test_hifiasm_ul.bp.p_ctg.gfa:md5,8fe65466d76815ffe1663ff6d8f2e8d1", + "test_hifiasm_ul.bp.p_utg.gfa:md5,ba2c77ebdb2ad3e6060f5574e890c6eb", + "test_hifiasm_ul.bp.r_utg.gfa:md5,ba2c77ebdb2ad3e6060f5574e890c6eb", + "test_hifiasm_ul_assembly.unmapped.txt:md5,d41d8cd98f00b204e9800998ecf8427e", + "test_hifiasm_ul_hifi.fastplong.json:md5,f63fad76bc219fe1875237c8becc9d40", + "test_hifiasm_ul_ont.fastplong.json:md5,628c20cd4d3fc93f6d7f9cbdf57049fd", + "test_hifiasm_ul_longstitch.tigmint-ntLink-arks.fa:md5,ff3182fa3e39281c2b8550c9137d9442", + "test_hifiasm_ul_longstitch.tigmint-ntLink.fa:md5,c983ebdc067bea70c625dd9857733b68", + "test_hifiasm_ul_longstitch.unmapped.txt:md5,d41d8cd98f00b204e9800998ecf8427e", + "test_ont_flye.params.json:md5,afa91c041bce5e190f4a699d11b69db6", + "test_ont_flye_hifi.fastplong.json:md5,0578aa3e71c029417c34910ef1da88ce", + "test_ont_flye_ont.fastplong.json:md5,161921438b26ce26127efdbd13a59ea9", + "test_ont_hifiasm.bp.hap1.p_ctg.gfa:md5,c9a084903ea872a7ac28f3bbd5eee8f3", + "test_ont_hifiasm.bp.hap2.p_ctg.gfa:md5,d41d8cd98f00b204e9800998ecf8427e", + "test_ont_hifiasm.bp.p_ctg.gfa:md5,d78f7a647429b254d9d46aab4d1306a0", + "test_ont_hifiasm.bp.p_utg.gfa:md5,e2b6621386edda1636dfb84ddd676ea8", + "test_ont_hifiasm.bp.r_utg.gfa:md5,e2b6621386edda1636dfb84ddd676ea8", + "test_ont_hifiasm_assembly.unmapped.txt:md5,e99c39c4afff7b433fb3973359dae6a4", + "test_ont_hifiasm_hifi.fastplong.json:md5,a033e6890ae430fe64b4740e3dc8fe91", + "test_ont_hifiasm_ont.fastplong.json:md5,a6de185eb4dc104fd811be325ae27fa1", + "test_ont_hifiasm_ragtag.fasta:md5,51f2975257b49657716d2ca7cc6d5fe2", + "test_ont_hifiasm_ragtag.stats:md5,466273b499384b2c781c83dc583b8c99", + "test_ont_hifiasm_ragtag.unmapped.txt:md5,e99c39c4afff7b433fb3973359dae6a4" ] ], "meta": { "nf-test": "0.9.2", - "nextflow": "25.04.6" + "nextflow": "25.04.8" }, - "timestamp": "2025-08-29T14:07:14.788806405" + "timestamp": "2025-12-09T16:35:35.479027188" } } \ No newline at end of file From 803b66fe2ef7f472f54e4a698dd20e06a29b8caa Mon Sep 17 00:00:00 2001 From: Niklas Schandry Date: Wed, 10 Dec 2025 08:56:26 +0100 Subject: [PATCH 085/162] remove deprecated params from config --- nextflow.config | 1 - 1 file changed, 1 deletion(-) diff --git a/nextflow.config b/nextflow.config index 2cbd8a4b..3bbd4837 100644 --- a/nextflow.config +++ b/nextflow.config @@ -60,7 +60,6 @@ params { // -- HiFi -- hifireads = null hifi_trim = false // run fastplong trimming on HiFi reads? - hifi_adapters = [] // list of adapters for fastplong hifi_fastplong_args = null // args for fastplong // -- Short read -- use_short_reads = false // short reads available? From 52c5aaf8185523d40f41163ac47cb496876a65fd Mon Sep 17 00:00:00 2001 From: Niklas Schandry Date: Wed, 10 Dec 2025 14:50:24 +0100 Subject: [PATCH 086/162] paths for reads in full_config --- conf/test_full.config | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/conf/test_full.config b/conf/test_full.config index 83a6435d..31cca9c8 100644 --- a/conf/test_full.config +++ b/conf/test_full.config @@ -14,14 +14,15 @@ params { config_profile_name = 'Full test profile' config_profile_description = 'Full test dataset to check pipeline function' - input = 'https://raw.githubusercontent.com/nf-core/test-datasets/genomeassembler/samplesheet/full_test_samplesheet.csv' - ontreads = 's3://nf-core-awsmegatests/genomeassembler/input/Col_0.ONT.porechopped.fastq.gz' - hifireads = 's3://nf-core-awsmegatests/genomeassembler/input/Col_0.hifi_reads.fastq.gz' - ref_fasta = 'http://raw.githubusercontent.com/schatzlab/Col-CEN/main/v1.2/Col-CEN_v1.2.fasta.gz' - ref_gff = 'http://raw.githubusercontent.com/schatzlab/Col-CEN/main/v1.2/Col-CEN_v1.2_genes.araport11.gff3.gz' - shortread_F = - quast = true - busco = true - jellyfish = true - use_ref = true + input = 'https://raw.githubusercontent.com/nf-core/test-datasets/genomeassembler/samplesheet/full_test_samplesheet.csv' + ontreads = 's3://nf-core-awsmegatests/genomeassembler/input/Col_0.ONT.porechopped.fastq.gz' + hifireads = 's3://nf-core-awsmegatests/genomeassembler/input/Col_0.hifi_reads.fastq.gz' + shortread_F = 's3://nf-core-awsmegatests/genomeassembler/input/SRR1604937_1.fastq.gz' + shortread_R = 's3://nf-core-awsmegatests/genomeassembler/input/SRR1604937_2.fastq.gz' + ref_gff = 'http://raw.githubusercontent.com/schatzlab/Col-CEN/main/v1.2/Col-CEN_v1.2_genes.araport11.gff3.gz' + ref_fasta = 'http://raw.githubusercontent.com/schatzlab/Col-CEN/main/v1.2/Col-CEN_v1.2.fasta.gz' + quast = true + busco = true + jellyfish = true + use_ref = true } From 161cc63d455bb8f7f3683ba3b17a4dd463d6c131 Mon Sep 17 00:00:00 2001 From: Niklas Schandry Date: Wed, 10 Dec 2025 14:50:46 +0100 Subject: [PATCH 087/162] update nftignore --- tests/.nftignore | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/tests/.nftignore b/tests/.nftignore index e8128b21..7839efd2 100644 --- a/tests/.nftignore +++ b/tests/.nftignore @@ -1,6 +1,9 @@ .DS_Store fastqc/*_fastqc.{html,zip} pipeline_info/*.{html,json,txt,yml} -*/*/*/*.{log,bin,gz,gff3,fasta,agp} -*/*/*.{log,bin,gz,gff3,txt} +*/*/*/*/*.{log,bin,gff3,agp,html,gz} +*/*/*/*.{log,bin,gff3,agp,html,gz} +*/*/*.{log,bin,gz,gff3,agp,html,gz} +*/*.{log,bin,gz,gff3,agp,html,gz} +*.{log,bin,gz,gff3,agp,html,gz} */*/*/*.assembly_info.txt From 048421ceff39a2ffd4e9bc435d6b04d3a369dd9a Mon Sep 17 00:00:00 2001 From: Niklas Schandry Date: Thu, 11 Dec 2025 09:36:03 +0100 Subject: [PATCH 088/162] empty adaptors for fastp --- subworkflows/local/prepare/prepare_shortreads/main.nf | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/subworkflows/local/prepare/prepare_shortreads/main.nf b/subworkflows/local/prepare/prepare_shortreads/main.nf index 568d7159..d260db9a 100644 --- a/subworkflows/local/prepare/prepare_shortreads/main.nf +++ b/subworkflows/local/prepare/prepare_shortreads/main.nf @@ -49,7 +49,8 @@ workflow PREPARE_SHORTREADS { it -> [ [ id: it[1], ids: it[0].id.collect().join("+") ], - it[2].unique()[0] + it[2].unique()[0], + [] ] } .mix(shortreads.trim From c9251a9a5deb1507803e3801393c80b4d2441190 Mon Sep 17 00:00:00 2001 From: Niklas Schandry Date: Thu, 11 Dec 2025 09:36:28 +0100 Subject: [PATCH 089/162] improve short-read logic --- .../local/utils_nfcore_genomeassembler_pipeline/main.nf | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/subworkflows/local/utils_nfcore_genomeassembler_pipeline/main.nf b/subworkflows/local/utils_nfcore_genomeassembler_pipeline/main.nf index 3a69195e..f966f05e 100644 --- a/subworkflows/local/utils_nfcore_genomeassembler_pipeline/main.nf +++ b/subworkflows/local/utils_nfcore_genomeassembler_pipeline/main.nf @@ -170,9 +170,9 @@ workflow PIPELINE_INITIALISATION { lift_annotations: (it.ref_gff || params.ref_gff) ? (it.lift_annotations ?: params.lift_annotations) : false, shortread_F: it.shortread_F ?: params.shortread_F, shortread_R: it.shortread_R ?: params.shortread_R, - paired: it.paired ?: params.paired, + paired: it.paired ?: params.paired ?: ((it.shortread_F || params.shortread_F) && (it.shortread_R || params.shortread_R)) ? true : false, // new: - use_short_reads: it.use_short_reads ?: params.use_short_reads ?: it.shortread_F ? true : false, + use_short_reads: it.use_short_reads ?: params.use_short_reads ?: params.shortread_F ? true : (it.shortread_F ? true : false), shortread_trim: it.shortread_trim ?: params.shortread_trim ] } From 5fb4b72101a64cd24c0aff12c1e74c51303111a7 Mon Sep 17 00:00:00 2001 From: Niklas Schandry Date: Thu, 11 Dec 2025 09:36:52 +0100 Subject: [PATCH 090/162] wip full_test --- conf/test_full.config | 33 ++++++++++++++++++++++----------- 1 file changed, 22 insertions(+), 11 deletions(-) diff --git a/conf/test_full.config b/conf/test_full.config index 31cca9c8..47c4ef5c 100644 --- a/conf/test_full.config +++ b/conf/test_full.config @@ -10,19 +10,30 @@ ---------------------------------------------------------------------------------------- */ +/* +Notes on this test: + There are 9 samples in the samplesheet. + Of these, 4 are "single" assembler strategy, and these are grouped + During read-prep this group becomes 1 sample + All prep steps should be 6 processes total (5 ungrouped + group) + These should be visible as a group in the report + +*/ + params { config_profile_name = 'Full test profile' config_profile_description = 'Full test dataset to check pipeline function' - input = 'https://raw.githubusercontent.com/nf-core/test-datasets/genomeassembler/samplesheet/full_test_samplesheet.csv' - ontreads = 's3://nf-core-awsmegatests/genomeassembler/input/Col_0.ONT.porechopped.fastq.gz' - hifireads = 's3://nf-core-awsmegatests/genomeassembler/input/Col_0.hifi_reads.fastq.gz' - shortread_F = 's3://nf-core-awsmegatests/genomeassembler/input/SRR1604937_1.fastq.gz' - shortread_R = 's3://nf-core-awsmegatests/genomeassembler/input/SRR1604937_2.fastq.gz' - ref_gff = 'http://raw.githubusercontent.com/schatzlab/Col-CEN/main/v1.2/Col-CEN_v1.2_genes.araport11.gff3.gz' - ref_fasta = 'http://raw.githubusercontent.com/schatzlab/Col-CEN/main/v1.2/Col-CEN_v1.2.fasta.gz' - quast = true - busco = true - jellyfish = true - use_ref = true + input = 'https://raw.githubusercontent.com/nf-core/test-datasets/genomeassembler/samplesheet/full_test_samplesheet.csv' + ontreads = 's3://nf-core-awsmegatests/genomeassembler/input/Col_0.ONT.porechopped.fastq.gz' + hifireads = 's3://nf-core-awsmegatests/genomeassembler/input/Col_0.hifi_reads.fastq.gz' + shortread_F = 's3://nf-core-awsmegatests/genomeassembler/input/SRR1604937_1.fastq.gz' + shortread_R = 's3://nf-core-awsmegatests/genomeassembler/input/SRR1604937_2.fastq.gz' + ref_gff = 'http://raw.githubusercontent.com/schatzlab/Col-CEN/main/v1.2/Col-CEN_v1.2_genes.araport11.gff3.gz' + ref_fasta = 'http://raw.githubusercontent.com/schatzlab/Col-CEN/main/v1.2/Col-CEN_v1.2.fasta.gz' + quast = true + busco = true + jellyfish = true + use_ref = true + shortread_trim = true } From 9471bc39e7c1ea545109ac60134b7e7f7c621cf0 Mon Sep 17 00:00:00 2001 From: Niklas Schandry Date: Thu, 11 Dec 2025 09:50:10 +0100 Subject: [PATCH 091/162] remove fastp TODO --- subworkflows/local/prepare/main.nf | 2 -- 1 file changed, 2 deletions(-) diff --git a/subworkflows/local/prepare/main.nf b/subworkflows/local/prepare/main.nf index 55129348..7513654e 100644 --- a/subworkflows/local/prepare/main.nf +++ b/subworkflows/local/prepare/main.nf @@ -4,8 +4,6 @@ include { PREPARE_SHORTREADS as SHORTREADS } from './prepare_shortreads/main' include { JELLYFISH } from './jellyfish/main' workflow PREPARE { - // TODO: Switch to fastp. - /* Grouped preparations From a7de4d014f22b2a12c4fc0985adc30a76cb4f332 Mon Sep 17 00:00:00 2001 From: Niklas Schandry Date: Tue, 16 Dec 2025 14:36:00 +0100 Subject: [PATCH 092/162] improve sample parameterisation --- subworkflows/local/assemble/main.nf | 11 +- .../main.nf | 126 ++++++++++++------ workflows/genomeassembler.nf | 5 + 3 files changed, 100 insertions(+), 42 deletions(-) diff --git a/subworkflows/local/assemble/main.nf b/subworkflows/local/assemble/main.nf index a23f7f98..de350fe7 100644 --- a/subworkflows/local/assemble/main.nf +++ b/subworkflows/local/assemble/main.nf @@ -110,7 +110,7 @@ workflow ASSEMBLE { // These are the hifi samples ch_main_assemble_flye // Those where the hifi assembler is flye, or where there is only one assembler and only hifireads - .filter { it -> it.assembler2 == "flye" || (it.strategy == "single" && it.hifireads && !it.ontreads)} + .filter { it -> it.assembler2 == "flye" && it.hifireads || (it.strategy == "single" && it.hifireads && !it.ontreads && it.assembler == "flye")} .multiMap { it -> reads: [ @@ -147,11 +147,11 @@ workflow ASSEMBLE { */ ch_main_assemble_branched .single - .filter { it -> it.assembler1 == "hifiasm" && !it.ontreads } + .filter { it -> it.assembler2 == "hifiasm" } .mix( ch_main_assemble_branched .hybrid - .filter { it -> it.assembler1 == "hifiasm" } + .filter { it -> it.assembler2 == "hifiasm" } ) .mix(ch_main_assemble_branched .scaffold @@ -331,6 +331,9 @@ workflow ASSEMBLE { // flye-flye flye_assemblies .filter { it -> it.strategy == "scaffold" && it.assembler1 == "flye" && it.assembler2 == "flye" } + /* + Below is uneccessary, this is handled already when flye_assemblies is created. + -------------------------------------------- .map { it -> it.collect { entry -> [ entry.value, entry ] } } .join( flye_assemblies .filter{ it -> it.strategy == "scaffold" && it.assembler1 == "flye" && it.assembler2 == "flye" } @@ -339,6 +342,8 @@ workflow ASSEMBLE { ) .map { it -> it.collect { _entry, map -> [ (map.key): map.value ] }.collectEntries() } .map { it -> it - it.subMap("flye_assembly", "assembly2") + [ assembly2: it.flye_assembly ] } + -------------------------------------------- + */ .set{ scaffold_flye_flye } // hifiasm_flye diff --git a/subworkflows/local/utils_nfcore_genomeassembler_pipeline/main.nf b/subworkflows/local/utils_nfcore_genomeassembler_pipeline/main.nf index f966f05e..b5b8e341 100644 --- a/subworkflows/local/utils_nfcore_genomeassembler_pipeline/main.nf +++ b/subworkflows/local/utils_nfcore_genomeassembler_pipeline/main.nf @@ -98,40 +98,67 @@ workflow PIPELINE_INITIALISATION { channel.fromPath(params.input) .splitCsv(header: true) .map { it -> + /* + This is a somewhat crucial step, where the samplesheet and params are used to determine per-sample parameters. + Some variables that with more complex logic are handled below + */ + def strategy = it.strategy ?: params.strategy + + def ontreads = it.ontreads ?: params.ontreads + + def hifireads = it.hifireads ?: params.hifireads + + def assembler = it.assembler ?: params.assembler + + def assembler1 = it.assembler1 ?: + params.assembler1 ?: + (strategy == "single" && ontreads && !hifireads) ? assembler : + (strategy == "hybrid" && assembler == "hifiasm") ? assembler : + assembler.contains("_") ? assembler.tokenize("_")[0] : + null + + def assembler2 = it.assembler2 ?: + params.assembler2 ?: + (strategy == "single" && !ontreads && hifireads) ? assembler : + assembler.contains("_") ? assembler.tokenize("_")[1] : + null + + def polish = it.polish ?: + (params.polish_medaka && params.polish_pilon && ontreads) ? "medaka+pilon" : + (params.polish_medaka && ontreads) ? "medaka" : + (params.polish_pilon && (it.shortread_F || params.shortread_F)) ? "pilon" : + null + + // Hard exit here if assembler cannot be determined. + strategy == "single" && ontreads && hifireads && !(assembler1 || assembler2) ? + error(""" + [$it.sample]: Strategy is 'single', but ONT and HiFi reads are provided. + Please unambigiously define either 'assembler1' for ONT or 'assembler2' for Hifi + """) : null + [ meta: [id: it.sample], // new in refactor-assemblies group: it.group ?: null, - ontreads: it.ontreads ?: params.ontreads, - hifireads: it.hifireads ?: params.hifireads, + ontreads: ontreads, + hifireads: hifireads, // new in refactor-assemblers - strategy: it.strategy ?: params.strategy, - assembler: it.assembler ?: params.assembler, - assembler1: it.assembler1 ?: - it.assembler == "hifiasm" || it.assembler == "flye" ? it.assembler : - it.assembler.contains("_") ? it.assembler.tokenize("_")[0] : - params.assembler == "hifiasm" || params.assembler == "flye" ? params.assembler : - params.assembler.contains("_") ? it.assembler.tokenize("_")[0] : - null, - assembler2: it.assembler2 ?: - it.assembler.contains("_") ? it.assembler.tokenize("_")[1] : - params.assembler.contains("_") ? it.assembler.tokenize("_")[1] : - null, + strategy: strategy, + // The "assembler" value is mainly to ease input, all actual workflow logic should use assembler1/2 + assembler: assembler, + // Assembler1: ONT, Assembler2: HiFi + assembler1: assembler1, + assembler2: assembler2, assembly_scaffolding_order: it.assembly_scaffolding_order ?: params.assembly_scaffolding_order ?: "ont_on_hifi", - genome_size: it.genome_size ?: params.genome_size, - assembler1_args: it.assembler1_args ?: - (it.assembler1 == "hifiasm") ? params.hifiasm_args : - (it.assembler1 == "flye") ? params.flye_args : + assembler1_args: it.assembler1_args ?: params.assembler1_args ?: + (assembler1 == "hifiasm") ? params.hifiasm_args : + (assembler1 == "flye") ? params.flye_args : null, - assembler2_args: it.assembler2_args ?: - (it.assembler2 == "hifiasm") ? params.hifiasm_args : - (it.assembler2 == "flye") ? params.flye_args : - null, - polish: it.polish ?: - (params.polish_medaka && params.polish_pilon && (it.ontreads || params.ontreads)) ? "medaka+pilon" : - (params.polish_medaka && (it.ontreads || params.ontreads)) ? "medaka" : - (params.polish_pilon && (it.shortread_F || params.shortread_F)) ? "pilon" : + assembler2_args: it.assembler2_args ?: params.assembler2_args ?: + (assembler2 == "hifiasm") ? params.hifiasm_args : + (assembler2 == "flye") ? params.flye_args : null, + polish: polish, ont_collect: it.ont_collect ?: params.ont_collect, ont_trim: it.ont_trim ?: params.ont_trim, ont_adapters: it.ont_adapters ?: params.ont_adapters, @@ -141,26 +168,25 @@ workflow PIPELINE_INITIALISATION { hifi_trim: it.hifi_trim ?: params.hifi_trim, hifi_adapters: it.hifi_adapters ?: params.hifi_adapters, hifi_fastplong_args: it.hifi_fastplong_args ?: params.hifi_fastplong_args, - polish_medaka: it.polish_medaka ?: params.polish_medaka, medaka_model: it.medaka_model ?: params.medaka_model, - polish_pilon: it.polish_pilon ?: params.polish_pilon, scaffold_longstitch: it.scaffold_longstitch ?: params.scaffold_longstitch, scaffold_links: it.scaffold_links ?: params.scaffold_links, scaffold_ragtag: it.scaffold_ragtag ?: params.scaffold_ragtag, use_ref: it.use_ref ?: params.use_ref ?: it.ref_fasta ? true : false, // not new + genome_size: it.genome_size ?: params.genome_size, ref_fasta: it.ref_fasta ?: params.ref_fasta, ref_gff: it.ref_gff ?: params.ref_gff, flye_mode: it.flye_mode ?: params.flye_mode, // assembly already provided? - assembly: it.assembly ?: "", + assembly: it.assembly ?: params.assembly ?: null, // ref mapping provided? ref_map_bam: it.ref_map_bam ?: params.ref_map_bam ?: null, // assembly mapping provided assembly_map_bam: it.assembly_map_bam ?: params.ref_map_bam ?: null, // reads for qc - qc_reads: ((it.qc_reads == "ont" || params.qc_reads == "ont") && it.ontreads) ? "ont" : "hifi", - qc_reads_path: ((it.qc_reads == "ont" || params.qc_reads == "ont") && it.ontreads) ? (it.ontreads ?: params.ontreads) : (it.hifireads ?: params.hifireads), + qc_reads: ((it.qc_reads == "ont" || params.qc_reads == "ont") && ontreads) ? "ont" : "hifi", + qc_reads_path: ((it.qc_reads == "ont" || params.qc_reads == "ont") && ontreads) ? ontreads : hifireads, quast: it.quast ?: params.quast, busco: it.busco ?: params.busco, busco_lineage: it.busco_lineage ?: params.busco_lineage, @@ -181,23 +207,45 @@ workflow PIPELINE_INITIALISATION { // Define valid hybrid assemblers def hybrid_assemblers = ["hifiasm"] - + ch_samplesheet.dump(tag: "PARSED INPUTS:") // sample-level checks // if a check fails, map returns a list that prints what fails, and contains "invalid" // error is raised by subscribe if there is more than one "invalid" ch_samplesheet .map { it -> - // Check if assembler1 was set - [(!it.assembler1 && !it.assembly) + [ + // Check if assembler1 was set + (it.ontreads && !it.assembler1 && !it.assembly) ? - [ - println("Please confirm samplesheet: [sample: $it.meta.id]: assembler1 could not be set and no assembly was provided."), - "invalid" - ] +( // Check if assembler2 was set + (it.hifireads && it.assembler2 && it.stragegy == "single") + ? + null + : + [ + println("Please confirm samplesheet: [sample: $it.meta.id]: assembler1 could not be set and no assembly was provided."), + "invalid" + ] + ) + : null, + // Check if assembler2 was set + (it.hifireads && !it.assembler2 && !it.assembly) + ? + ( + // Check if assembler1 was set + (it.ontreads && it.assembler1 && it.stragegy == "single") + ? + null + : + [ + println("Please confirm samplesheet: [sample: $it.meta.id]: assembler2 could not be set and no assembly was provided."), + "invalid" + ] + ) : null, // Check if reads and strategy match - (it.strategy == "single" && it.ontreads && it.hifireads) + (it.strategy == "single" && it.ontreads && it.hifireads) ? [ println("Please confirm samplesheet: [sample: $it.meta.id]: Strategy is $it.strategy, but both types of reads are provided."), diff --git a/workflows/genomeassembler.nf b/workflows/genomeassembler.nf index 390d593d..bd95757f 100644 --- a/workflows/genomeassembler.nf +++ b/workflows/genomeassembler.nf @@ -310,11 +310,16 @@ workflow GENOMEASSEMBLER { .fromPath("${projectDir}/assets/report/functions/*") .collect() .set { report_functions } + channel + .fromPath("${projectDir}/assets/report/scripts/*") + .collect() + .set { report_scripts } //fasplong_jsons.view { it -> "UNQIE JSONS: $it"} REPORT( report_files, report_functions, + report_scripts, fasplong_jsons, genomescope_files, quast_files, From 3b842d5aeff2dc1d7839f820be30112068cf8857 Mon Sep 17 00:00:00 2001 From: Niklas Schandry Date: Tue, 13 Jan 2026 15:23:41 +0100 Subject: [PATCH 093/162] update param defaults, schema --- nextflow.config | 20 ++++++++++++-------- nextflow_schema.json | 28 ++++++++++++++++++++-------- 2 files changed, 32 insertions(+), 16 deletions(-) diff --git a/nextflow.config b/nextflow.config index 3bbd4837..68e396e6 100644 --- a/nextflow.config +++ b/nextflow.config @@ -45,22 +45,26 @@ params { // Assembly strategy strategy = "single" // assembly_strategy assembler = null - ontreads = null - hifireads = null + assembler1 = null + assembler2 = null + assembler1_args = '' + assembler2_args = '' + flye_mode = '--nano-hq' // == Read QC and trimming == // -- ONT ontreads = null ont_collect = false // collect ONT reads into a single file ont_trim = false /// run fastplong trimming on ONT reads? ont_adapters = [] // list of adapters for fastplong - ont_fastplong_args = null // args for fastplong + ont_fastplong_args = '' // args for fastplong // -- Jellyfish (qc reads) -- jellyfish = false jellyfish_k = 21 // -- HiFi -- hifireads = null + hifi_adapters = [] hifi_trim = false // run fastplong trimming on HiFi reads? - hifi_fastplong_args = null // args for fastplong + hifi_fastplong_args = '' // args for fastplong // -- Short read -- use_short_reads = false // short reads available? shortread_trim = false // trim short reads? @@ -72,10 +76,10 @@ params { assembly_scaffolding_order = "ont_on_hifi" // -- Assembly: Flye -- genome_size = null // genomesize, optional, can be estimated from ONT reads - flye_mode = '--nano-hq' // flye mode + // DEPRECATED: flye_mode = '--nano-hq' flye_args = "" // extra flye args // -- Assembly: hifiasm -- - hifiasm_args = "" // extra hifiasm args + hifiasm_args = '' // extra hifiasm args // QC Oriented extra options assembly = null ref_map_bam = null @@ -87,7 +91,7 @@ params { // -- POLISHING // -- Polish: medaka polish_medaka = false // run medaka - medaka_model = "" // model for medaka, if empty medaka will guess + medaka_model = '' // model for medaka, if empty medaka will guess // -- Polish: pilon polish_pilon = false // run pilon // -- QC -- @@ -97,7 +101,7 @@ params { // -- QC : Busco busco = false // run busco busco_db = '' // path to busco db - busco_lineage = "brassicales_odb10" // busco lineage + busco_lineage = "auto_euk" // busco lineage // -- QC: QUAST quast = false // run quast qc_reads = "ont" // if both ONT and HiFi reads are available, which should be used for QC alignments diff --git a/nextflow_schema.json b/nextflow_schema.json index f74eb37a..13d88581 100644 --- a/nextflow_schema.json +++ b/nextflow_schema.json @@ -186,7 +186,7 @@ }, "assembler": { "type": "string", - "description": "Assembler to use. Valid choices depend on strategy; for single either `flye` or `hifiasm`, hybrid can be done with `hifiasm` and for scaffolded assembly provide the names of the assemblers separated with an underscore. The first assembler will be used for ONT reads, the second for HiFi reads.", + "description": "Assembler to use. Valid choices depend on strategy; for single either `flye` or `hifiasm`, hybrid can be done with `hifiasm` and for scaffolded assembly provide the names of the assemblers separated with an underscore. The first assembler will be used for ONT reads, the second for HiFi reads. (see below, asembler1 and assembler2)", "enum": ["flye", "hifiasm", "flye_hifiasm", "hifiasm_hifiasm", "flye_flye", "hifiasm_flye"], "default": "hifiasm" }, @@ -214,6 +214,22 @@ "type": "string", "description": "Extra arguments passed to `hifiasm`", "default": "\"\"" + }, + "assembler1": { + "type": "string", + "description": "Assembler1 assembles ONT reads. This option is mainly useful when building more complex samplesheets" + }, + "assembler1_args": { + "type": "string", + "description": "Arguments to be passed to assembler1 (ONT)" + }, + "assembler2": { + "type": "string", + "description": "Assembler2 assembles HiFi reads. This option is mainly useful when building more complex samplesheets" + }, + "assembler2_args": { + "type": "string", + "description": "Arguments to be passed to assembler2 (HiFi)" } } }, @@ -271,10 +287,6 @@ "type": "integer", "default": 21, "description": "Value of k used during k-mer analysis with jellyfish" - }, - "dump": { - "type": "boolean", - "description": "dump jellyfish output" } } }, @@ -348,7 +360,7 @@ "busco_lineage": { "type": "string", "description": "Busco lineage to use", - "default": "brassicales_odb10" + "default": "auto_euk" }, "quast": { "type": "boolean", @@ -356,7 +368,7 @@ }, "ref_map_bam": { "type": "string", - "description": "A mapping (bam) of reads mapped to the reference can be provided for QC. If provided alignment to reference fasta will not run" + "description": "A mapping (bam) of reads mapped to the reference can be provided for QC. If provided, alignment to reference fasta will not run." }, "assembly": { "type": "string", @@ -364,7 +376,7 @@ }, "assembly_map_bam": { "type": "string", - "description": "A mapping (bam) of reads mapped to the provided assembly can be specified for QC. If provided alignment to the provided assembly fasta will not run" + "description": "A mapping (bam) of reads mapped to the provided assembly can be specified for QC. If provided, alignment to the provided assembly fasta will not run" } } }, From 20133209e4404f5aa9a598e9c2da4c8d909e6801 Mon Sep 17 00:00:00 2001 From: Niklas Schandry Date: Tue, 13 Jan 2026 15:24:17 +0100 Subject: [PATCH 094/162] improve channel creation --- .../main.nf | 90 ++++++++++--------- 1 file changed, 47 insertions(+), 43 deletions(-) diff --git a/subworkflows/local/utils_nfcore_genomeassembler_pipeline/main.nf b/subworkflows/local/utils_nfcore_genomeassembler_pipeline/main.nf index b5b8e341..4afff361 100644 --- a/subworkflows/local/utils_nfcore_genomeassembler_pipeline/main.nf +++ b/subworkflows/local/utils_nfcore_genomeassembler_pipeline/main.nf @@ -97,45 +97,47 @@ workflow PIPELINE_INITIALISATION { channel.fromPath(params.input) .splitCsv(header: true) - .map { it -> /* This is a somewhat crucial step, where the samplesheet and params are used to determine per-sample parameters. - Some variables that with more complex logic are handled below */ - def strategy = it.strategy ?: params.strategy - - def ontreads = it.ontreads ?: params.ontreads - - def hifireads = it.hifireads ?: params.hifireads - - def assembler = it.assembler ?: params.assembler - - def assembler1 = it.assembler1 ?: - params.assembler1 ?: - (strategy == "single" && ontreads && !hifireads) ? assembler : - (strategy == "hybrid" && assembler == "hifiasm") ? assembler : - assembler.contains("_") ? assembler.tokenize("_")[0] : - null - - def assembler2 = it.assembler2 ?: - params.assembler2 ?: - (strategy == "single" && !ontreads && hifireads) ? assembler : - assembler.contains("_") ? assembler.tokenize("_")[1] : - null - - def polish = it.polish ?: - (params.polish_medaka && params.polish_pilon && ontreads) ? "medaka+pilon" : - (params.polish_medaka && ontreads) ? "medaka" : - (params.polish_pilon && (it.shortread_F || params.shortread_F)) ? "pilon" : - null - - // Hard exit here if assembler cannot be determined. - strategy == "single" && ontreads && hifireads && !(assembler1 || assembler2) ? - error(""" - [$it.sample]: Strategy is 'single', but ONT and HiFi reads are provided. - Please unambigiously define either 'assembler1' for ONT or 'assembler2' for Hifi - """) : null - + .map { it -> + def strategy = it.strategy ?: params.strategy + + def ontreads = it.ontreads ?: params.ontreads + + def hifireads = it.hifireads ?: params.hifireads + + def assembler = it.assembler ?: params.assembler + + def assembler1 = it.assembler1 ?: + params.assembler1 ?: + (strategy == "single" && ontreads && !hifireads) ? assembler : + (strategy == "hybrid" && assembler == "hifiasm") ? assembler : + assembler.contains("_") ? assembler.tokenize("_")[0] : + null + + def assembler2 = it.assembler2 ?: + params.assembler2 ?: + (strategy == "single" && !ontreads && hifireads) ? assembler : + assembler.contains("_") ? assembler.tokenize("_")[1] : + null + + def polish = it.polish ?: + (params.polish_medaka && params.polish_pilon && ontreads) ? "medaka+pilon" : + (params.polish_medaka && ontreads) ? "medaka" : + (params.polish_pilon && (it.shortread_F || params.shortread_F)) ? "pilon" : + null + + // Hard exit here if assembler cannot be determined. + strategy == "single" && ontreads && hifireads && !(assembler1 || assembler2) ? + error( + """ + [$it.sample]: Strategy is 'single', but ONT and HiFi reads are provided. + Please unambigiously define either 'assembler1' for ONT or 'assembler2' for HiFi + """ + ) : + null + // Build the map [ meta: [id: it.sample], // new in refactor-assemblies @@ -144,19 +146,20 @@ workflow PIPELINE_INITIALISATION { hifireads: hifireads, // new in refactor-assemblers strategy: strategy, - // The "assembler" value is mainly to ease input, all actual workflow logic should use assembler1/2 + // The "assembler" value is mainly to ease input, all actual workflow logic should use assembler1/2. + // Could still be useful for debugging. assembler: assembler, // Assembler1: ONT, Assembler2: HiFi assembler1: assembler1, assembler2: assembler2, assembly_scaffolding_order: it.assembly_scaffolding_order ?: params.assembly_scaffolding_order ?: "ont_on_hifi", assembler1_args: it.assembler1_args ?: params.assembler1_args ?: - (assembler1 == "hifiasm") ? params.hifiasm_args : - (assembler1 == "flye") ? params.flye_args : + (assembler1 == "hifiasm") ? (it.hifiasm_args ?: params.hifiasm_args) : + (assembler1 == "flye") ? (it.flye_args ?: params.flye_args) : null, assembler2_args: it.assembler2_args ?: params.assembler2_args ?: - (assembler2 == "hifiasm") ? params.hifiasm_args : - (assembler2 == "flye") ? params.flye_args : + (assembler2 == "hifiasm") ? (it.hifiasm_args ?: params.hifiasm_args) : + (assembler2 == "flye") ? (it.flye_args ?: params.flye_args) : null, polish: polish, ont_collect: it.ont_collect ?: params.ont_collect, @@ -172,7 +175,7 @@ workflow PIPELINE_INITIALISATION { scaffold_longstitch: it.scaffold_longstitch ?: params.scaffold_longstitch, scaffold_links: it.scaffold_links ?: params.scaffold_links, scaffold_ragtag: it.scaffold_ragtag ?: params.scaffold_ragtag, - use_ref: it.use_ref ?: params.use_ref ?: it.ref_fasta ? true : false, + use_ref: it.use_ref ?: params.use_ref, // not new genome_size: it.genome_size ?: params.genome_size, ref_fasta: it.ref_fasta ?: params.ref_fasta, @@ -218,7 +221,8 @@ workflow PIPELINE_INITIALISATION { // Check if assembler1 was set (it.ontreads && !it.assembler1 && !it.assembly) ? -( // Check if assembler2 was set + ( + // Check if assembler2 was set (it.hifireads && it.assembler2 && it.stragegy == "single") ? null From 35ff8f0de108fc3c931bc477e78e37d012412e56 Mon Sep 17 00:00:00 2001 From: Niklas Schandry Date: Tue, 13 Jan 2026 15:24:30 +0100 Subject: [PATCH 095/162] new test config --- conf/test_full_local.config | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) create mode 100644 conf/test_full_local.config diff --git a/conf/test_full_local.config b/conf/test_full_local.config new file mode 100644 index 00000000..64f403f5 --- /dev/null +++ b/conf/test_full_local.config @@ -0,0 +1,29 @@ +/* +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + Nextflow config file for running full-size tests +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + Defines input files and everything required to run a full size pipeline test. + + Use as follows: + nextflow run nf-core/genomeassembler -profile test_full, --outdir + +---------------------------------------------------------------------------------------- +*/ + +params { + config_profile_name = 'Full test profile' + config_profile_description = 'Full test dataset to check pipeline function' + + input = 'https://raw.githubusercontent.com/nf-core/test-datasets/genomeassembler/samplesheet/full_test_samplesheet.csv' + ontreads = '/dss/dsslegfs01/pn73so/pn73so-dss-0002/genomeassembler_test_data/Col_0.ONT.porechopped.fastq.gz' + hifireads = '/dss/dsslegfs01/pn73so/pn73so-dss-0002/genomeassembler_test_data/Col_0.hifi_reads.fastq.gz' + shortread_F = '/dss/dsslegfs01/pn73so/pn73so-dss-0002/genomeassembler_test_data/SRR1604937_1.fastq.gz' + shortread_R = '/dss/dsslegfs01/pn73so/pn73so-dss-0002/genomeassembler_test_data/SRR1604937_2.fastq.gz' + ref_gff = 'http://raw.githubusercontent.com/schatzlab/Col-CEN/main/v1.2/Col-CEN_v1.2_genes.araport11.gff3.gz' + ref_fasta = 'http://raw.githubusercontent.com/schatzlab/Col-CEN/main/v1.2/Col-CEN_v1.2.fasta.gz' + quast = true + busco = true + jellyfish = true + use_ref = true + trim_short_reads = true +} From 0f8d1694df74b71597a71c3c536e4c90341a3cf2 Mon Sep 17 00:00:00 2001 From: Niklas Schandry Date: Tue, 13 Jan 2026 15:24:52 +0100 Subject: [PATCH 096/162] report update --- modules/local/report/main.nf | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/local/report/main.nf b/modules/local/report/main.nf index 51db1887..10bec57c 100644 --- a/modules/local/report/main.nf +++ b/modules/local/report/main.nf @@ -12,6 +12,7 @@ process REPORT { input: path qmdir_files, stageAs: "*" path funct_files, stageAs: "functions/*" + path script_files, stageAs: "scripts/*" path fastplong_files, stageAs: "data/fastplong/*" path jelly_files, stageAs: "data/genomescope/*" path quast_files, stageAs: "data/quast/*" @@ -65,8 +66,7 @@ process REPORT { export HOME="\$PWD" quarto render report.qmd \\ ${report_profile} \\ - ${report_params} \\ - --to dashboard + ${report_params} cat <<-END_VERSIONS > versions.yml "${task.process}": From 5e6924a5f581c8655a462ae9c41723f1832568f8 Mon Sep 17 00:00:00 2001 From: Niklas Schandry Date: Tue, 13 Jan 2026 15:25:29 +0100 Subject: [PATCH 097/162] fix bug where samples that had flye in assembler1 and assembler2 were duplicated --- subworkflows/local/assemble/main.nf | 35 +++++++------------- subworkflows/local/scaffolding/links/main.nf | 2 +- subworkflows/local/scaffolding/main.nf | 1 - 3 files changed, 13 insertions(+), 25 deletions(-) diff --git a/subworkflows/local/assemble/main.nf b/subworkflows/local/assemble/main.nf index de350fe7..90f82f5d 100644 --- a/subworkflows/local/assemble/main.nf +++ b/subworkflows/local/assemble/main.nf @@ -75,16 +75,10 @@ workflow ASSEMBLE { .single .filter { it -> it.assembler1 == "flye" } .mix( - // Add in the scaffolding samples where flye is used for ONT + // Add in the scaffolding samples where flye is used ch_main_assemble_branched .scaffold - .filter { it -> it.assembler1 == "flye" } - // Add in the scaffolding samples where flye is used for HiFi - .mix( - ch_main_assemble_branched - .scaffold - .filter { it -> it.assembler2 == "flye" } - ) + .filter { it -> it.assembler1 == "flye" || it.assembler2 == "flye" } ) .set { ch_main_assemble_flye } @@ -253,8 +247,8 @@ workflow ASSEMBLE { it -> it - it.subMap("hifiasm_assembly") + // stick what was in hifiasm_assembly into the correct key [ - assembly: (it.strategy == "single" || it.strategy == "hybrid") && it.assembler1 == "hifiasm" ? it.hifiasm_assembly : null, - // I think below case dose not exist in this channel since it is only hifiasm (assembler2) assemblies? + assembly: (it.strategy == "single" && it.assembler2 == "hifiasm") || (it.strategy == "hybrid" && it.assembler1 == "hifiasm") ? it.hifiasm_assembly : null, + // I think below case does not exist in this channel since it is only hifiasm (assembler2) assemblies? assembly1: it.strategy == "scaffold" && it.assembler1 == "hifiasm" ? it.hifiasm_assembly : null, assembly2: it.strategy == "scaffold" && it.assembler2 == "hifiasm" ? it.hifiasm_assembly : null ] @@ -332,17 +326,7 @@ workflow ASSEMBLE { flye_assemblies .filter { it -> it.strategy == "scaffold" && it.assembler1 == "flye" && it.assembler2 == "flye" } /* - Below is uneccessary, this is handled already when flye_assemblies is created. - -------------------------------------------- - .map { it -> it.collect { entry -> [ entry.value, entry ] } } - .join( flye_assemblies - .filter{ it -> it.strategy == "scaffold" && it.assembler1 == "flye" && it.assembler2 == "flye" } - .map { it -> [ meta: it.meta, flye_assembly: it.assembly2 ] } - .map { it -> it.collect { entry -> [ entry.value, entry ] } } - ) - .map { it -> it.collect { _entry, map -> [ (map.key): map.value ] }.collectEntries() } - .map { it -> it - it.subMap("flye_assembly", "assembly2") + [ assembly2: it.flye_assembly ] } - -------------------------------------------- + No joins neccessary, this is handled already when flye_assemblies is created. */ .set{ scaffold_flye_flye } @@ -399,6 +383,8 @@ workflow ASSEMBLE { } .set { ragtag_in } + ragtag_in.target.dump(tag: "ASSEMBLE: SCAFFOLD: RAGTAG_PATCH INPUT: TARGET") + ragtag_in.query.dump( tag: "ASSEMBLE: SCAFFOLD: RAGTAG_PATCH INPUT: QUERY") // Scaffold with PATCH RAGTAG_PATCH(ragtag_in.target, ragtag_in.query, [[], []], [[], []] ) @@ -419,6 +405,9 @@ workflow ASSEMBLE { // Mix everything assembled back togehter ch_assemblies_no_scaffold .mix(ch_assemblies_scaffold) + .map { + it -> it - it.subMap("assembly","assembly1", "assembly2") + [assembly: it.assembly ?: it.assembly1 ?: it.assembly2] + } .set { ch_main_assembled } ch_main_assembled.dump(tag: "Assemble: Assembled") @@ -495,7 +484,7 @@ workflow ASSEMBLE { .mix(ch_main_quast_branch.no_quast) .set { ch_main_to_qc } - + ch_main_to_qc.dump(tag: "ASSEMBLE: QC INPUT") //QC on initial assembly // scaffolds to QC need to be defined here, this is what is in the assembly slot @@ -521,7 +510,7 @@ workflow ASSEMBLE { ] } .set { liftoff_in } - + liftoff_in.dump(tag: "ASSEMBLE: LIFTOFF: INPUT") RUN_LIFTOFF(liftoff_in) ch_versions = ch_versions.mix(RUN_LIFTOFF.out.versions) diff --git a/subworkflows/local/scaffolding/links/main.nf b/subworkflows/local/scaffolding/links/main.nf index e6816067..5611d628 100644 --- a/subworkflows/local/scaffolding/links/main.nf +++ b/subworkflows/local/scaffolding/links/main.nf @@ -9,7 +9,7 @@ workflow RUN_LINKS { main: channel.empty().set { ch_versions } - + ch_main.dump(tag: "SCAFFOLD: LINKS: WORKFLOW inputs") ch_main .multiMap { it -> assembly: [it.meta, it.polished ? (it.polished.pilon ?: it.polished.medaka) : it.assembly] diff --git a/subworkflows/local/scaffolding/main.nf b/subworkflows/local/scaffolding/main.nf index 26470652..60be6356 100644 --- a/subworkflows/local/scaffolding/main.nf +++ b/subworkflows/local/scaffolding/main.nf @@ -23,7 +23,6 @@ workflow SCAFFOLD { // But it is possible that one sample is scaffolded with different tools. // Therefore main is filtered, instead of branched. - ch_main .filter { it -> it.scaffold_links From ecc759c3787622ef3792c7a9237025ee1a6f3990 Mon Sep 17 00:00:00 2001 From: Niklas Schandry Date: Tue, 13 Jan 2026 15:29:44 +0100 Subject: [PATCH 098/162] fix typo in channel name --- workflows/genomeassembler.nf | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/workflows/genomeassembler.nf b/workflows/genomeassembler.nf index bd95757f..eed1e38f 100644 --- a/workflows/genomeassembler.nf +++ b/workflows/genomeassembler.nf @@ -236,7 +236,7 @@ workflow GENOMEASSEMBLER { .map { it -> it[1] } .unique() .collect() - .set { fasplong_jsons } + .set { fastplong_jsons } PREPARE.out.genomescope_summary .concat( @@ -315,12 +315,12 @@ workflow GENOMEASSEMBLER { .collect() .set { report_scripts } - //fasplong_jsons.view { it -> "UNQIE JSONS: $it"} + //fastplong_jsons.view { it -> "UNQIE JSONS: $it"} REPORT( report_files, report_functions, report_scripts, - fasplong_jsons, + fastplong_jsons, genomescope_files, quast_files, busco_files, From f8499fd1eaa9d75e1914331fd50285a47b46bd33 Mon Sep 17 00:00:00 2001 From: Niklas Schandry Date: Thu, 15 Jan 2026 10:45:38 +0100 Subject: [PATCH 099/162] add busco lineage for test-data --- conf/test_full.config | 1 + 1 file changed, 1 insertion(+) diff --git a/conf/test_full.config b/conf/test_full.config index 47c4ef5c..166158ed 100644 --- a/conf/test_full.config +++ b/conf/test_full.config @@ -36,4 +36,5 @@ params { jellyfish = true use_ref = true shortread_trim = true + busco_lineage = "brassicales_odb12" } From 403e1bb59b669fc7ee1030165cc197b7406ec2e4 Mon Sep 17 00:00:00 2001 From: Niklas Schandry Date: Thu, 15 Jan 2026 10:46:26 +0100 Subject: [PATCH 100/162] fix bug where flye samples were not joined back correctly; mix channels before joining instead of doing two joins. --- subworkflows/local/assemble/main.nf | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/subworkflows/local/assemble/main.nf b/subworkflows/local/assemble/main.nf index 90f82f5d..41969b6b 100644 --- a/subworkflows/local/assemble/main.nf +++ b/subworkflows/local/assemble/main.nf @@ -211,10 +211,10 @@ workflow ASSEMBLE { .map { it -> it.collect { entry -> [ entry.value, entry ] } } .join( FLYE_ONT.out.fasta .map { meta, assembly -> [meta: [id: meta.id], flye_ont_assembly: assembly ] } - .map { it -> it.collect { entry -> [ entry.value, entry ] } } - ) - .join( FLYE_HIFI.out.fasta - .map { meta, assembly -> [meta: [id: meta.id], flye_hifi_assembly: assembly ] } + .mix( + FLYE_HIFI.out.fasta + .map { meta, assembly -> [meta: [id: meta.id], flye_hifi_assembly: assembly ] } + ) .map { it -> it.collect { entry -> [ entry.value, entry ] } } ) // After joining re-create the maps from the stored map From 6799326cc796986fc6ca582834179d61bfdf5650 Mon Sep 17 00:00:00 2001 From: Niklas Schandry Date: Thu, 15 Jan 2026 10:47:37 +0100 Subject: [PATCH 101/162] indentation - good, bad and ugly? --- workflows/genomeassembler.nf | 26 ++++++++++++++++---------- 1 file changed, 16 insertions(+), 10 deletions(-) diff --git a/workflows/genomeassembler.nf b/workflows/genomeassembler.nf index eed1e38f..b9864de1 100644 --- a/workflows/genomeassembler.nf +++ b/workflows/genomeassembler.nf @@ -225,7 +225,7 @@ workflow GENOMEASSEMBLER { */ SCAFFOLD(ch_main_polished.scaffold, meryl_kmers) - // Recreate ch_main, even though it is not used since there are no later steps._report + // Recreate ch_main, even though it is not used since there are no later steps. ch_main_polished .no_scaffold @@ -264,10 +264,12 @@ workflow GENOMEASSEMBLER { ) quast_files - .concat( - ASSEMBLE.out.assembly_quast_reports.concat( + .mix( + ASSEMBLE.out.assembly_quast_reports + .mix( POLISH.out.polish_quast_reports - ).concat( + ) + .mix( SCAFFOLD.out.scaffold_quast_reports ) ) @@ -276,10 +278,12 @@ workflow GENOMEASSEMBLER { .set { quast_files } busco_files - .concat( - ASSEMBLE.out.assembly_busco_reports.concat( + .mix( + ASSEMBLE.out.assembly_busco_reports + .mix( POLISH.out.polish_busco_reports - ).concat( + ) + .mix( SCAFFOLD.out.scaffold_busco_reports ) ) @@ -288,10 +292,12 @@ workflow GENOMEASSEMBLER { .set { busco_files } merqury_files - .concat( - ASSEMBLE.out.assembly_merqury_reports.concat( + .mix( + ASSEMBLE.out.assembly_merqury_reports + .mix( POLISH.out.polish_merqury_reports - ).concat( + ) + .mix( SCAFFOLD.out.scaffold_merqury_reports ) ) From a03f2f4a0e2089fc9c9ff283e4e691142ec078a1 Mon Sep 17 00:00:00 2001 From: Niklas Schandry Date: Fri, 23 Jan 2026 13:03:21 +0100 Subject: [PATCH 102/162] move all sample information into meta, remove joins that are now obsolete --- docs/usage.md | 8 +- nextflow.config | 8 +- nextflow_schema.json | 18 +- subworkflows/local/assemble/main.nf | 398 ++++++++++-------- .../local/mapping/map_to_assembly/main.nf | 4 +- subworkflows/local/mapping/map_to_ref/main.nf | 8 +- subworkflows/local/polishing/main.nf | 8 +- .../polishing/medaka/polish_medaka/main.nf | 26 +- .../polishing/pilon/polish_pilon/main.nf | 34 +- subworkflows/local/prepare/jellyfish/main.nf | 77 ++-- subworkflows/local/prepare/main.nf | 114 +++-- .../local/prepare/prepare_hifi/main.nf | 72 ++-- .../local/prepare/prepare_ont/main.nf | 118 +++--- .../local/prepare/prepare_shortreads/main.nf | 108 ++--- subworkflows/local/qc/busco/main.nf | 6 +- subworkflows/local/qc/main.nf | 52 +-- subworkflows/local/qc/quast/main.nf | 14 +- subworkflows/local/scaffolding/links/main.nf | 19 +- .../local/scaffolding/longstitch/main.nf | 30 +- subworkflows/local/scaffolding/main.nf | 4 +- subworkflows/local/scaffolding/ragtag/main.nf | 26 +- .../main.nf | 186 ++++---- workflows/genomeassembler.nf | 98 ++--- 23 files changed, 695 insertions(+), 741 deletions(-) diff --git a/docs/usage.md b/docs/usage.md index 8c173e66..300c4029 100644 --- a/docs/usage.md +++ b/docs/usage.md @@ -58,8 +58,8 @@ Assembly strategy is controlled via `strategy` (either pipeline parameter or sam - hybrid: Use a single assembler for a combined assembly of ONT and HiFi reads. The assembler should be provided via `assembler`. Currently, only `hifiasm` supports hybrid assembly. - scaffold: Assemble ONT reads and HiFi indepently and scaffold one assembly onto the other. `assembler` has to be provided as `"ontAssembler_hifiAssembler"` and could for example be: "`flye_hifiasm"` to assemble ont reads with `flye` and HiFi reads with `hifiasm` or "hifiasm_hifiasm" to assemble both ont and hifi reads indepently with `hifiasm`. When running in "scaffold" mode, `assembly_scaffolding_order` can be used to control which assembly gets scaffolded onto which, the default being "ont_on_hifi" where ONT assembly is scaffolded onto HifI assembly. -Assembler specific arguments can be provided for the assembler via `hifiasm_args` or `flye_args`, or with more fine-grained control via `assembler1_args` and `assembler2_args` for scaffolding. -`assembler1_args` controls the parameters for the assembler in `single` and `hybrid` strategies, or for the assembler used for ONT reads when using `scaffold`. `assembler2_args` can be used to pass arguments to the assembler used for HiFi reads in `scaffold` mode. +Assembler specific arguments can be provided for the assembler via `hifiasm_args` or `flye_args`, or with more fine-grained control via `assembler_ont_args` and `assembler_hifi_args` for scaffolding. +`assembler_ont_args` controls the parameters for the assembler in `single` and `hybrid` strategies, or for the assembler used for ONT reads when using `scaffold`. `assembler_hifi_args` can be used to pass arguments to the assembler used for HiFi reads in `scaffold` mode. `assembler[1,2]_args` can only be set via the samplesheet and are not available as global pipeline parameters. ## Samplesheet input @@ -294,8 +294,8 @@ Options controlling assembly | `flye_mode` | flye mode | `string` | | `flye_args` | additional args for flye | `string` | | `hifiasm_args` | Extra arguments passed to `hifiasm` | `string` | -| `assembler1_args` | Extra arguments passed to assembler1; assembling ONT reads in `scaffold` strategy | `string` | -| `assembler2_args` | Extra arguments passed to assembler2; assembling HiFi reads in `scaffold` strategy | `string` | +| `assembler_ont_args` | Extra arguments passed to assembler_ont; assembling ONT reads in `scaffold` strategy | `string` | +| `assembler_hifi_args` | Extra arguments passed to assembler_hifi; assembling HiFi reads in `scaffold` strategy | `string` | ## Long-read preprocessing diff --git a/nextflow.config b/nextflow.config index 68e396e6..9a36930c 100644 --- a/nextflow.config +++ b/nextflow.config @@ -45,10 +45,10 @@ params { // Assembly strategy strategy = "single" // assembly_strategy assembler = null - assembler1 = null - assembler2 = null - assembler1_args = '' - assembler2_args = '' + assembler_ont = null + assembler_hifi = null + assembler_ont_args = '' + assembler_hifi_args = '' flye_mode = '--nano-hq' // == Read QC and trimming == // -- ONT diff --git a/nextflow_schema.json b/nextflow_schema.json index 13d88581..ff434686 100644 --- a/nextflow_schema.json +++ b/nextflow_schema.json @@ -186,7 +186,7 @@ }, "assembler": { "type": "string", - "description": "Assembler to use. Valid choices depend on strategy; for single either `flye` or `hifiasm`, hybrid can be done with `hifiasm` and for scaffolded assembly provide the names of the assemblers separated with an underscore. The first assembler will be used for ONT reads, the second for HiFi reads. (see below, asembler1 and assembler2)", + "description": "Assembler to use. Valid choices depend on strategy; for single either `flye` or `hifiasm`, hybrid can be done with `hifiasm` and for scaffolded assembly provide the names of the assemblers separated with an underscore. The first assembler will be used for ONT reads, the second for HiFi reads. (see below, asembler1 and assembler_hifi)", "enum": ["flye", "hifiasm", "flye_hifiasm", "hifiasm_hifiasm", "flye_flye", "hifiasm_flye"], "default": "hifiasm" }, @@ -215,21 +215,21 @@ "description": "Extra arguments passed to `hifiasm`", "default": "\"\"" }, - "assembler1": { + "assembler_ont": { "type": "string", - "description": "Assembler1 assembles ONT reads. This option is mainly useful when building more complex samplesheets" + "description": "assembler_ont assembles ONT reads. This option is mainly useful when building more complex samplesheets" }, - "assembler1_args": { + "assembler_ont_args": { "type": "string", - "description": "Arguments to be passed to assembler1 (ONT)" + "description": "Arguments to be passed to assembler_ont (ONT)" }, - "assembler2": { + "assembler_hifi": { "type": "string", - "description": "Assembler2 assembles HiFi reads. This option is mainly useful when building more complex samplesheets" + "description": "assembler_hifi assembles HiFi reads. This option is mainly useful when building more complex samplesheets" }, - "assembler2_args": { + "assembler_hifi_args": { "type": "string", - "description": "Arguments to be passed to assembler2 (HiFi)" + "description": "Arguments to be passed to assembler_hifi (HiFi)" } } }, diff --git a/subworkflows/local/assemble/main.nf b/subworkflows/local/assemble/main.nf index 41969b6b..038f0541 100644 --- a/subworkflows/local/assemble/main.nf +++ b/subworkflows/local/assemble/main.nf @@ -26,8 +26,8 @@ workflow ASSEMBLE { ch_main .branch { it -> - to_assemble: !it.assembly - no_assemble: it.assembly + to_assemble: !it.meta.assembly + no_assemble: it.meta.assembly } .set { ch_main_branched @@ -43,9 +43,9 @@ workflow ASSEMBLE { ch_main_branched .to_assemble .branch { it -> - single: it.strategy == "single" - hybrid: it.strategy == "hybrid" - scaffold: it.strategy == "scaffold" + single: it.meta.strategy == "single" + hybrid: it.meta.strategy == "hybrid" + scaffold: it.meta.strategy == "scaffold" } .set { ch_main_assemble_branched } @@ -59,7 +59,6 @@ workflow ASSEMBLE { .scaffold .dump(tag: "Assemble: Branched: scaffold") - /* ========================= FLYE ASSEMBLER @@ -73,12 +72,12 @@ workflow ASSEMBLE { ch_main_assemble_branched .single - .filter { it -> it.assembler1 == "flye" } + .filter { it -> it.meta.assembler_ont == "flye" } .mix( // Add in the scaffolding samples where flye is used ch_main_assemble_branched .scaffold - .filter { it -> it.assembler1 == "flye" || it.assembler2 == "flye" } + .filter { it -> it.meta.assembler_ont == "flye" || it.meta.assembler_hifi == "flye" } ) .set { ch_main_assemble_flye } @@ -86,36 +85,36 @@ workflow ASSEMBLE { // Extra args per sample are stored in the meta map, so is the estimated / expected genome size // The inputs are created once for ONT and once for HiFi ch_main_assemble_flye - .filter { it -> it.assembler1 == "flye" && it.ontreads } + .filter { it -> it.meta.assembler_ont == "flye" && it.meta.ontreads } .multiMap { it -> reads: [ - [ - id: it.meta.id, - genome_size: it.genome_size, - flye_args: it.flye_args ?: "" - ], - it.ontreads ?: [], + it.meta, + it.meta.ontreads ?: [], ] - mode: it.assembler1 == "flye" ? "--nano-hq" : null + mode: it.meta.assembler_ont == "flye" ? "--nano-hq" : null } .set { flye_ont_inputs } // These are the hifi samples ch_main_assemble_flye // Those where the hifi assembler is flye, or where there is only one assembler and only hifireads - .filter { it -> it.assembler2 == "flye" && it.hifireads || (it.strategy == "single" && it.hifireads && !it.ontreads && it.assembler == "flye")} + .filter { it -> + it.meta.assembler_hifi == "flye" && it.meta.hifireads || + ( + it.meta.strategy == "single" && + it.meta.hifireads && + !it.meta.ontreads && + it.meta.assembler == "flye" + ) + } .multiMap { it -> reads: [ - [ - id: it.meta.id, - genome_size: it.genome_size, - flye_args: it.flye_args ?: "" - ], - it.hifireads ?: [], + it.meta, + it.meta.hifireads ?: [], ] - mode: it.assembler2 == "flye" ? "--pacbio-hifi" : null + mode: it.meta.assembler_hifi == "flye" ? "--pacbio-hifi" : null } .set { flye_hifi_inputs } @@ -126,7 +125,6 @@ workflow ASSEMBLE { FLYE_ONT(flye_ont_inputs.reads, flye_ont_inputs.mode) FLYE_HIFI(flye_hifi_inputs.reads, flye_hifi_inputs.mode) - ch_versions = ch_versions.mix(FLYE_ONT.out.versions).mix(FLYE_HIFI.out.versions) /* @@ -137,21 +135,27 @@ workflow ASSEMBLE { /* Hifiasm: everything that is not hifiasm-ONT - Single branch with hifiasm as assembler and no ont reads (only hifireads) - Hybrid assembly - - Scaffold samples where assembler2 (hifi assembler) is hifiasm + - Scaffold samples where assembler_hifi (hifi assembler) is hifiasm */ ch_main_assemble_branched .single - .filter { it -> it.assembler2 == "hifiasm" } + .filter { + it -> it.meta.assembler_hifi == "hifiasm" + } .mix( ch_main_assemble_branched .hybrid - .filter { it -> it.assembler2 == "hifiasm" } + .filter { + it -> it.meta.assembler_hifi == "hifiasm" + } ) .mix(ch_main_assemble_branched .scaffold - .filter { it -> it.assembler2 == "hifiasm" } + .filter { + it -> it.meta.assembler_hifi == "hifiasm" + } // the samples for scaffolding should not have ONT reads, otherwise hifiasm will run in --ul mode - .map { it -> it - it.subMap("ontreads") } + .map { it -> [meta: it.meta - it.meta.subMap("ontreads")] } ) .set { ch_main_assemble_hifi_hifiasm } @@ -160,19 +164,18 @@ workflow ASSEMBLE { HIFIASM(ch_main_assemble_hifi_hifiasm .map { it -> [ - // Put sample-level args into meta map - [id: it.meta.id, hifiasm_args: it.hifiasm_args ?: ""], - it.hifireads, + it.meta, + it.meta.hifireads, // for hybrid samples include ONT reads in 3rd slot of first input (see hifiasm module) - (it.stragtegy == "hybrid" && it.ontreads) ? it.ontreads : [] + (it.meta.strategy == "hybrid" && it.meta.ontreads) ? it.meta.ontreads : [] ] }, [[], [], []], [[], [], []], [[], []]) - // hifiasm produces GFA files, convert to fasta & restore meta map with id only - GFA_2_FA_HIFI( HIFIASM.out.processed_unitigs.map { meta, fasta -> [[id: meta.id], fasta] } ) + // hifiasm produces GFA files + GFA_2_FA_HIFI( HIFIASM.out.processed_unitigs) ch_versions = ch_versions.mix(HIFIASM.out.versions).mix(GFA_2_FA_HIFI.out.versions) @@ -180,98 +183,67 @@ workflow ASSEMBLE { hifiasm with ONLY ont reads. Assemble hifiasm_ont branch: Single branch with hifiasm and only ont reads - Scaffold branch where assembler1 (ont assembler) is hifiasm + Scaffold branch where assembler_ont (ont assembler) is hifiasm */ ch_main_assemble_branched .single - .filter { it -> it.assembler1 == "hifiasm" && it.ontreads } + .filter { it -> it.meta.assembler_ont == "hifiasm" && it.meta.ontreads } .mix(ch_main_assemble_branched .scaffold - .filter { it -> it.assembler1 == "hifiasm" } + .filter { it -> it.meta.assembler_ont == "hifiasm" } ) .set { ch_main_assemble_ont_hifiasm } ch_main_assemble_ont_hifiasm.dump(tag: "Assemble: hifiasm ONT inputs") - HIFIASM_ONT(ch_main_assemble_ont_hifiasm.map { it -> [ [id: it.meta.id, hifiasm_args: it.hifiasm_args ?: ""], it.ontreads, [] ] }, [[], [], []], [[], [], []], [[], []]) + HIFIASM_ONT(ch_main_assemble_ont_hifiasm.map { it -> [ it.meta, it.meta.ontreads, [] ] }, [[], [], []], [[], [], []], [[], []]) - GFA_2_FA_ONT( HIFIASM_ONT.out.processed_unitigs.map { meta, fasta -> [[id: meta.id], fasta] } ) + GFA_2_FA_ONT( HIFIASM_ONT.out.processed_unitigs) ch_versions = ch_versions.mix(HIFIASM_ONT.out.versions).mix(GFA_2_FA_ONT.out.versions) - - // Now, the individual assemblies need to be correctly added into the main channel. - // This should be done per-strategy - // join assembler outputs back to assembler inputs and determine correct placement of the assembly. - // Flye: - ch_main_assemble_flye - // Convert to list for join - .map { it -> it.collect { entry -> [ entry.value, entry ] } } - .join( FLYE_ONT.out.fasta - .map { meta, assembly -> [meta: [id: meta.id], flye_ont_assembly: assembly ] } - .mix( - FLYE_HIFI.out.fasta - .map { meta, assembly -> [meta: [id: meta.id], flye_hifi_assembly: assembly ] } - ) - .map { it -> it.collect { entry -> [ entry.value, entry ] } } - ) - // After joining re-create the maps from the stored map - .map { it -> it.collect { _entry, map -> [ (map.key): map.value ] }.collectEntries() } - // The flye_ont|hifi_assembly has to be placed into the correct slot - .map { it -> it - it.subMap("flye_ont_assembly") - it.subMap("flye_hifi_assembly") + - [ - assembly: it.strategy == "single" && it.ontreads ? it.flye_ont_assembly : it.flye_hifi_assembly, - assembly1: it.assembler1 == "flye" ? it.flye_ont_assembly : null, - assembly2: it.assembler2 == "flye" ? it.flye_hifi_assembly : null, - ] + FLYE_ONT.out.fasta + .filter { + meta, _fasta -> meta.strategy != "scaffold" } + .map { meta_old, assembly -> [meta: meta_old + [ assembly: assembly ] ] } + .mix( + FLYE_HIFI.out.fasta + .filter { + meta, _fasta -> meta.strategy != "scaffold" + } + .map { meta_old, assembly -> [meta: meta_old + [ assembly: assembly ] ] } + ) .set { flye_assemblies } flye_assemblies.dump(tag: "Assemble: Flye assemblies") - // Join hifiasm hifi assemblies back to main channel - ch_main_assemble_hifi_hifiasm - // Convert to list for join - .map { it -> it.collect { entry -> [ entry.value, entry ] } } - .join( GFA_2_FA_HIFI.out.contigs_fasta - .map { meta, assembly -> [meta: meta, hifiasm_assembly: assembly ] } - .map { it -> it.collect { entry -> [ entry.value, entry ] } } - ) - // After joining re-create the maps from the stored map - .map { it -> it.collect { _entry, map -> [ (map.key): map.value ] }.collectEntries() } - // On the map, place the hifiasm assembly into the correct position - .map { - // remove hifiasm_assembly entry from joined result - it -> it - it.subMap("hifiasm_assembly") + - // stick what was in hifiasm_assembly into the correct key + // regernerate meta maps + GFA_2_FA_HIFI.out.contigs_fasta + .filter { it -> it[0].strategy != "scaffold" } + .map { meta_old, assembly -> + [ + meta: meta_old + + // stick assembly into the correct key [ - assembly: (it.strategy == "single" && it.assembler2 == "hifiasm") || (it.strategy == "hybrid" && it.assembler1 == "hifiasm") ? it.hifiasm_assembly : null, - // I think below case does not exist in this channel since it is only hifiasm (assembler2) assemblies? - assembly1: it.strategy == "scaffold" && it.assembler1 == "hifiasm" ? it.hifiasm_assembly : null, - assembly2: it.strategy == "scaffold" && it.assembler2 == "hifiasm" ? it.hifiasm_assembly : null + assembly: (meta_old.strategy == "single" && meta_old.assembler_hifi == "hifiasm") || (meta_old.strategy == "hybrid" && meta_old.assembler_ont == "hifiasm") ? assembly : null, ] + ] } .set { hifiasm_hifi_assemblies } hifiasm_hifi_assemblies.dump(tag: "Assemble: hifiasm HIFI assemblies") - // Join hifiasm ONT assemblies back to main channel - ch_main_assemble_ont_hifiasm - .map { it -> it.collect { entry -> [ entry.value, entry ] } } - .join( GFA_2_FA_ONT.out.contigs_fasta - .map { meta, assembly -> [meta: meta, hifiasm_assembly: assembly ] } - .map { it -> it.collect { entry -> [ entry.value, entry ] } } - ) - // After joining re-create the maps from the stored map - .map { it -> it.collect { _entry, map -> [ (map.key): map.value ] }.collectEntries() } - .map { - it -> it -it.subMap("hifiasm_assembly") + + GFA_2_FA_ONT.out.contigs_fasta + .filter { meta, _fasta -> meta.strategy != "scaffold" } + .map { meta, assembly -> [ - assembly: (it.strategy == "single" || it.strategy == "hybrid") && it.assembler1 == "hifiasm" ? it.hifiasm_assembly : null, - assembly1: it.strategy == "scaffold" && it.assembler1 == "hifiasm" ? it.hifiasm_assembly : null, - assembly2: it.strategy == "scaffold" && it.assembler2 == "hifiasm" ? it.hifiasm_assembly : null + meta: meta + + [ + assembly: assembly + ] ] } .set { hifiasm_ont_assemblies } @@ -285,17 +257,10 @@ workflow ASSEMBLE { */ // The single and hybrid channels can be mixed and forwarded. - // The scaffold channel needs to be joined separately. + flye_assemblies - .filter { it -> ["single","hybrid"].contains(it.strategy) } - .mix( - hifiasm_hifi_assemblies - .filter { it -> ["single","hybrid"].contains(it.strategy) } - ) - .mix( - hifiasm_ont_assemblies - .filter { it -> ["single","hybrid"].contains(it.strategy) } - ) + .mix(hifiasm_hifi_assemblies) + .mix(hifiasm_ont_assemblies) .set { ch_assemblies_no_scaffold } ch_assemblies_no_scaffold.dump(tag: "Assemble: Assemblies without scaffolding") @@ -308,52 +273,139 @@ workflow ASSEMBLE { */ // This leaves the scaffold strategy. // scaffolds can be: FLYE-HIFIASM, FLYE-FLYE, HIFIASM-HIFIASM or HIFIASM-FLYE + // The above is (ONT-HIFI) - flye_assemblies + FLYE_ONT.out.fasta // Flye-hifiasm - .filter { it -> it.strategy == "scaffold" && it.assembler1 == "flye" && it.assembler2 == "hifiasm" } - .map { it -> it.collect { entry -> [ entry.value, entry ] } } - .join( hifiasm_hifi_assemblies - .filter{ it -> it.strategy == "scaffold" && it.assembler1 == "flye" && it.assembler2 == "hifiasm" } - .map { it -> [ meta: it.meta, hifiasm_assembly: it.assembly2 ] } - .map { it -> it.collect { entry -> [ entry.value, entry ] } } + .filter { meta, _fasta -> + meta.strategy == "scaffold" && + meta.assembler_ont == "flye" && + meta.assembler_hifi == "hifiasm" + } + .map { meta, fasta -> [meta.id, meta, fasta] } + .join( + GFA_2_FA_HIFI + .out + .contigs_fasta + .filter { meta, _fasta -> + meta.strategy == "scaffold" && + meta.assembler_ont == "flye" && + meta.assembler_hifi == "hifiasm" + } + .map { meta, fasta -> [ meta.id, fasta ] } ) - .map { it -> it.collect { _entry, map -> [ (map.key): map.value ] }.collectEntries() } - .map { it -> it - it.subMap("hifiasm_assembly","assembly2") + [ assembly2: it.hifiasm_assembly ] } + .map { _id, meta_old, assembly_flye, assembly_hifiasm -> + [ + meta: meta_old - + meta_old.subMap("hifiasm_assembly", "assembly_hifi", "assembly_ont", "flye_assembly") + + [ + assembly_ont: assembly_flye, + assembly_hifi: assembly_hifiasm + ] + ] + } .set{ scaffold_flye_hifiasm } // flye-flye - flye_assemblies - .filter { it -> it.strategy == "scaffold" && it.assembler1 == "flye" && it.assembler2 == "flye" } - /* - No joins neccessary, this is handled already when flye_assemblies is created. - */ - .set{ scaffold_flye_flye } + FLYE_ONT.out.fasta + .filter { + meta, _fasta -> meta.strategy == "scaffold" && meta.assembler_ont == "flye" & meta.assembler_hifi == "flye" + } + .map { + meta, fasta -> [meta.id, meta, fasta] // id, meta, ont assembly + } + .join( + FLYE_HIFI.out.fasta + .filter { + meta, _fasta -> meta.strategy == "scaffold" && meta.assembler_ont == "flye" & meta.assembler_hifi == "flye" + } + .map { + meta, fasta -> [ meta.id, fasta ] // id, hifi assembly + }, + ) + .map { _id, meta, ont_assembly, hifi_assembly -> + [ + meta: meta + + [ + assembly_ont: ont_assembly, + assembly_hifi: hifi_assembly + ] + ] + } + .set { scaffold_flye_flye } // hifiasm_flye - hifiasm_ont_assemblies - .filter { it -> it.strategy == "scaffold" && it.assembler1 == "hifiasm" && it.assembler2 == "flye" } - .map { it -> it.collect { entry -> [ entry.value, entry ] } } - .join( flye_assemblies - .filter{ it -> it.strategy == "scaffold" && it.assembler1 == "hifiasm" && it.assembler2 == "flye" } - .map { it -> [ meta: it.meta, flye_assembly: it.assembly2 ] } - .map { it -> it.collect { entry -> [ entry.value, entry ] } } + GFA_2_FA_ONT.out.contigs_fasta + .filter { + meta, _assembly -> meta.strategy == "scaffold" && + meta.assembler_ont == "hifiasm" && + meta.assembler_hifi == "flye" + } + .map { + meta, assembly -> + [ + meta.id, + meta, + assembly + ] + } + .join( + FLYE_HIFI.out.fasta + .filter{ meta, _fasta -> meta.strategy == "scaffold" && meta.assembler_ont == "hifiasm" && meta.assembler_hifi == "flye" } + .map { meta, fasta -> [ meta.id, fasta ] } ) - .map { it -> it.collect { _entry, map -> [ (map.key): map.value ] }.collectEntries() } - .map { it -> it - it.subMap("flye_assembly", "assembly2") + [ assembly2: it.flye_assembly ] } + .map { + _id, meta, hifiasm_ont_assembly, flye_hifi_assembly -> + [ + meta: meta + + [ + assembly_ont: hifiasm_ont_assembly, + assembly_hifi: flye_hifi_assembly + ] + ] + } .set{ scaffold_hifiasm_flye } // hifiasm_hifiasm - hifiasm_ont_assemblies - .filter { it -> it.strategy == "scaffold" && it.assembler1 == "hifiasm" && it.assembler2 == "hifiasm" } - .map { it -> it.collect { entry -> [ entry.value, entry ] } } - .join( hifiasm_hifi_assemblies - .filter{ it -> it.strategy == "scaffold" && it.assembler1 == "hifiasm" && it.assembler2 == "hifiasm" } - .map { it -> [ meta: it.meta, hifiasm_assembly: it.assembly2 ] } - .map { it -> it.collect { entry -> [ entry.value, entry ] } } + GFA_2_FA_ONT.out.contigs_fasta + .filter { + meta, _assembly -> meta.strategy == "scaffold" && + meta.assembler_ont == "hifiasm" && + meta.assembler_hifi == "hifiasm" + } + .map { + meta, assembly -> + [ + meta.id, + meta, + assembly + ] + } + .join( + GFA_2_FA_HIFI.out.contigs_fasta + .filter { + meta, _assembly -> meta.strategy == "scaffold" && + meta.assembler_ont == "hifiasm" && + meta.assembler_hifi == "hifiasm" + } + .map { + meta, assembly -> + [ + meta.id, + assembly + ] + } ) - .map { it -> it.collect { _entry, map -> [ (map.key): map.value ] }.collectEntries() } - .map { it -> it - it.subMap("hifiasm_assembly","assembly2") + [ assembly2: it.hifiasm_assembly ] } + .map { + _id, meta, assembly_ont, assembly_hifi -> + [ + meta: meta + + [ + assembly_ont: assembly_ont, + assembly_hifi: assembly_hifi + ] + ] + } .set{ scaffold_hifiasm_hifiasm } // branch to scaffold those assemblies that need it @@ -367,18 +419,18 @@ workflow ASSEMBLE { ch_to_scaffold.dump(tag: "Assemble: Assemblies with scaffolding - inputs") // For scaffolding, depeding on which strategy used, the correct assembly needs to go into either target or query: - // assembly1 is always ONT, assembly2 is always HiFi + // assembly_ont is always ONT, assembly_hifi is always HiFi ch_to_scaffold .multiMap { it -> target: [ it.meta, - it.assembly_scaffolding_order == "ont_on_hifi" ? (it.assembly1) : (it.assembly2) + it.meta.assembly_scaffolding_order == "ont_on_hifi" ? (it.meta.assembly_ont) : (it.meta.assembly_hifi) ] query: [ it.meta, - it.assembly_scaffolding_order == "ont_on_hifi" ? (it.assembly2) : (it.assembly1) + it.meta.assembly_scaffolding_order == "ont_on_hifi" ? (it.meta.assembly_hifi) : (it.meta.assembly_ont) ] } .set { ragtag_in } @@ -388,16 +440,9 @@ workflow ASSEMBLE { // Scaffold with PATCH RAGTAG_PATCH(ragtag_in.target, ragtag_in.query, [[], []], [[], []] ) - // Update inputs - ch_to_scaffold - .map { it -> it - it.subMap("assembly") } - .map { it -> it.collect { entry -> [ entry.value, entry ] } } - .join( - RAGTAG_PATCH.out.patch_fasta - .map { it -> [meta: it[0], assembly: it[1]] } - .map { it -> it.collect { entry -> [ entry.value, entry ] } } - ) - .map { it -> it.collect { _entry, map -> [ (map.key): map.value ] }.collectEntries() } + // Update meta + RAGTAG_PATCH.out.patch_fasta + .map { meta, patched -> [meta: meta + [assembly: patched] ] } .set { ch_assemblies_scaffold } ch_assemblies_scaffold.dump(tag: "Assemble: Assemblies with scaffolding - outputs") @@ -405,9 +450,6 @@ workflow ASSEMBLE { // Mix everything assembled back togehter ch_assemblies_no_scaffold .mix(ch_assemblies_scaffold) - .map { - it -> it - it.subMap("assembly","assembly1", "assembly2") + [assembly: it.assembly ?: it.assembly1 ?: it.assembly2] - } .set { ch_main_assembled } ch_main_assembled.dump(tag: "Assemble: Assembled") @@ -427,8 +469,8 @@ workflow ASSEMBLE { ch_main_to_mapping .branch { it -> - quast: it.quast - no_quast: !it.quast + quast: it.meta.quast + no_quast: !it.meta.quast } // Note that this channel is set here but only the quast branch is further used .set { ch_main_quast_branch } @@ -438,8 +480,8 @@ workflow ASSEMBLE { .quast .branch { it -> - use_ref: it.use_ref - no_use_ref: !it.use_ref + use_ref: it.meta.use_ref + no_use_ref: !it.meta.use_ref } .set { ch_quast_branched @@ -449,8 +491,8 @@ workflow ASSEMBLE { ch_quast_branched .use_ref .branch { it -> - to_map: !it.ref_map_bam - dont_map: it.ref_map_bam + to_map: !it.meta.ref_map_bam + dont_map: it.meta.ref_map_bam } .set { ch_ref_mapping_branched } @@ -459,25 +501,15 @@ workflow ASSEMBLE { .to_map .map { it -> - [ [id: it.meta.id, qc_reads: it.qc_reads], it.qc_reads_path, it.ref_fasta ] + [ [it.meta], it.meta.qc_reads_path, it.meta.ref_fasta ] } .set { map_to_ref_in } - MAP_TO_REF(map_to_ref_in) // returns meta: [ id: ] + MAP_TO_REF(map_to_ref_in) // Add the ref mapping to the large main channel - ch_ref_mapping_branched - .to_map - .map { it -> it - it.subMap("ref_map_bam") } - .map { it -> it.collect { entry -> [ entry.value, entry ] } } - .join( - MAP_TO_REF.out.ch_aln_to_ref_bam - // Note that this is a normal list channel and needs to become a map before conversion back to list and joining - // otherwise, the map cannot be regenerated later - .map { it -> [meta: it[0], ref_map_bam: it[1]] } - .map { it -> it.collect { entry -> [ entry.value, entry ] } } - ) - .map { it -> it.collect { _entry, map -> [ (map.key): map.value ] }.collectEntries() } + MAP_TO_REF.out.ch_aln_to_ref_bam + .map { meta, bam -> [ meta: meta + [ref_map_bam: bam] ] } .mix(ch_ref_mapping_branched.dont_map) .mix(ch_quast_branched.no_use_ref) // above recreates ch_main_quast_branch.quast @@ -489,7 +521,7 @@ workflow ASSEMBLE { // scaffolds to QC need to be defined here, this is what is in the assembly slot ch_main_to_qc - .map { it -> [it.meta, it.assembly] } + .map { it -> [it.meta.id, it.meta.assembly] } .set { scaffolds } QC(ch_main_to_qc, scaffolds, meryl_kmers) @@ -499,14 +531,14 @@ workflow ASSEMBLE { // If annotation liftover on the initial assembly is desired, it happens here. ch_main_to_qc .filter { - it -> it.lift_annotations + it -> it.meta.lift_annotations } .map { it -> [ it.meta, - it.assembly, - it.ref_fasta, - it.ref_gff + it.meta.assembly, + it.meta.ref_fasta, + it.meta.ref_gff ] } .set { liftoff_in } diff --git a/subworkflows/local/mapping/map_to_assembly/main.nf b/subworkflows/local/mapping/map_to_assembly/main.nf index 1c9f0937..62e127f7 100644 --- a/subworkflows/local/mapping/map_to_assembly/main.nf +++ b/subworkflows/local/mapping/map_to_assembly/main.nf @@ -12,15 +12,13 @@ workflow MAP_TO_ASSEMBLY { ALIGN(map_assembly, true, 'bai', false, false) ALIGN.out.bam - .map {meta, bam -> [ [id: meta.id], bam ]} .set { aln_to_assembly_bam } ALIGN.out.index - .map {meta, bai -> [ [id: meta.id], bai ]} .set { aln_to_assembly_bai } map_assembly - .map { meta, _reads, fasta -> [[id: meta.id], fasta] } + .map { meta, _reads, fasta -> [meta, fasta] } .set { ch_fasta } aln_to_assembly_bam diff --git a/subworkflows/local/mapping/map_to_ref/main.nf b/subworkflows/local/mapping/map_to_ref/main.nf index 4b77a518..dec632cb 100644 --- a/subworkflows/local/mapping/map_to_ref/main.nf +++ b/subworkflows/local/mapping/map_to_ref/main.nf @@ -3,7 +3,7 @@ include { BAM_STATS_SAMTOOLS as BAM_STATS } from '../../../nf-core/bam_stats_sam workflow MAP_TO_REF { take: - ch_map_ref // meta: [id, qc_reads], reads, refs + ch_map_ref // meta, reads, refs main: channel.empty().set { ch_versions } @@ -12,11 +12,9 @@ workflow MAP_TO_REF { ALIGN(ch_map_ref, true, 'bai', false, false) ALIGN.out.bam - .map { meta, bam -> [ [id: meta.id], bam ] } .set { ch_aln_to_ref_bam } ALIGN.out.index - .map {meta, bai -> [ [id: meta.id], bai ]} .set { aln_to_ref_bai } ch_aln_to_ref_bam @@ -24,7 +22,7 @@ workflow MAP_TO_REF { .set { ch_aln_to_ref_bam_bai } ch_map_ref - .map { meta, _reads, fasta -> [[id: meta.id], fasta] } + .map { meta, _reads, fasta -> [[meta], fasta] } .set { ch_fasta } BAM_STATS(ch_aln_to_ref_bam_bai, ch_fasta) @@ -32,6 +30,6 @@ workflow MAP_TO_REF { versions = ch_versions.mix(ALIGN.out.versions).mix(BAM_STATS.out.versions) emit: - ch_aln_to_ref_bam // [id], bam + ch_aln_to_ref_bam // meta, bam versions } diff --git a/subworkflows/local/polishing/main.nf b/subworkflows/local/polishing/main.nf index faefe82f..44d0eeff 100644 --- a/subworkflows/local/polishing/main.nf +++ b/subworkflows/local/polishing/main.nf @@ -15,8 +15,8 @@ workflow POLISH { ch_main .branch { it -> - medaka: ["medaka","medaka+pilon"].contains(it.polish) - no_medaka: !["medaka","medaka+pilon"].contains(it.polish) + medaka: ["medaka","medaka+pilon"].contains(it.meta.polish) + no_medaka: !["medaka","medaka+pilon"].contains(it.meta.polish) } .set { ch_main_polish } @@ -41,8 +41,8 @@ workflow POLISH { ch_main_polish_pilon .branch { it -> - pilon: ["pilon","medaka+pilon"].contains(it.polish) - no_pilon: !["pilon","medaka+pilon"].contains(it.polish) + pilon: ["pilon","medaka+pilon"].contains(it.meta.polish) + no_pilon: !["pilon","medaka+pilon"].contains(it.meta.polish) } .set { ch_main_polish_pilon_in } diff --git a/subworkflows/local/polishing/medaka/polish_medaka/main.nf b/subworkflows/local/polishing/medaka/polish_medaka/main.nf index 84fc7254..75eb45b8 100644 --- a/subworkflows/local/polishing/medaka/polish_medaka/main.nf +++ b/subworkflows/local/polishing/medaka/polish_medaka/main.nf @@ -12,12 +12,12 @@ workflow POLISH_MEDAKA { ch_main .filter { - it -> it.polish_medaka + it -> it.meta.polish_medaka } .multiMap { it -> - reads: [it.meta, it.ontreads] - reference: [it.meta, it.assembly] + reads: [it.meta, it.meta.ontreads] + reference: [it.meta, it.meta.assembly] } .set { ch_medaka_in } @@ -25,15 +25,9 @@ workflow POLISH_MEDAKA { RUN_MEDAKA.out.medaka_out.set { polished_assembly } - ch_main - .map { it -> it.collect { entry -> [ entry.value, entry ] } } - .join( polished_assembly - .map { it -> [meta: it[0], polished_medaka: it[1]]} - .map { it -> it.collect { entry -> [ entry.value, entry ] } } - ) + polished_assembly + .map { meta, polished_medaka -> [meta: meta + [ polished: [polished_medaka: polished_medaka ] ] ]} // After joining re-create the maps from the stored map - .map { it -> it.collect { _entry, map -> [ (map.key): map.value ] }.collectEntries() } - .map { it -> it - it.subMap("polished_medaka") + [polished: [medaka: it.polished_medaka ]]} .set { ch_medaka_out } ch_main @@ -44,8 +38,8 @@ workflow POLISH_MEDAKA { ch_versions = ch_versions.mix(RUN_MEDAKA.out.versions) QC( - ch_medaka_out.map { it -> it - it.subMap("assembly_map_bam") + [assembly_map_bam: null] }, - polished_assembly, + ch_medaka_out.map { it -> [meta: it.meta - it.meta.subMap("assembly_map_bam") + [ assembly_map_bam: null] ] }, + polished_assembly.map { meta, polished -> [meta.id, polished] }, meryl_kmers ) @@ -59,9 +53,9 @@ workflow POLISH_MEDAKA { .map { it -> [ it.meta, - it.polished.medaka, - it.ref_fasta, - it.ref_gff + it.meta.polished.medaka, + it.meta.ref_fasta, + it.meta.ref_gff ] } .set { liftoff_in } diff --git a/subworkflows/local/polishing/pilon/polish_pilon/main.nf b/subworkflows/local/polishing/pilon/polish_pilon/main.nf index 86ae05c4..8547f252 100644 --- a/subworkflows/local/polishing/pilon/polish_pilon/main.nf +++ b/subworkflows/local/polishing/pilon/polish_pilon/main.nf @@ -14,10 +14,10 @@ workflow POLISH_PILON { ch_main .multiMap { it -> - shortreads: [it.meta, it.shortreads] + shortreads: [it.meta, it.meta.shortreads] assembly: [ it.meta, - it.polish == "medaka+pilon" ? it.polished.medaka : it.assembly + it.meta.polish == "medaka+pilon" ? it.meta.polished.medaka : it.meta.assembly ] } .set { map_sr_in } @@ -31,27 +31,19 @@ workflow POLISH_PILON { RUN_PILON.out.improved_assembly .set { pilon_polished } - ch_main - .map { it -> it - it.subMap("polished") + [polished_medaka: it.polish == "medaka+pilon" ? it.polished.medaka : null] } - .map { it -> it.collect { entry -> [ entry.value, entry ] } } - .join( - pilon_polished - .map { it -> [ meta: it[0], polished_pilon: it[1] ] } - .map { it -> it.collect { entry -> [ entry.value, entry ] } } - ) - .map { it -> it.collect { _entry, map -> [ (map.key): map.value ] }.collectEntries() } - .map { it -> ( - ["medaka+pilon"].contains(it.polish) ? - (it - it.subMap("polished_medaka", "polished_pilon")) : - (it - it.subMap("polished_pilon")) - ) + - [polished: [medaka: it.polished_medaka, pilon: it.polished_pilon]] + pilon_polished + .map { meta, polished_pilon -> [ meta: meta, polished_pilon: polished_pilon ] } + .map { it -> [ meta: it.meta + + [ polished: [it.polished_pilon] ] + ] } .set { ch_main } ch_versions = ch_versions.mix(RUN_PILON.out.versions) - QC(ch_main.map { it -> it - it.subMap("assembly_map_bam") + [assembly_map_bam: null] }, pilon_polished, meryl_kmers) + QC(ch_main.map { it -> [meta: it.meta - it.meta.subMap("assembly_map_bam") + [assembly_map_bam: null] ]}, + pilon_polished.map {meta, polished -> [meta.id, polished ]}, + meryl_kmers) ch_versions = ch_versions.mix(QC.out.versions) @@ -62,9 +54,9 @@ workflow POLISH_PILON { .map { it -> [ it.meta, - it.polished.pilon, - it.ref_fasta, - it.ref_gff + it.meta.polished.pilon, + it.meta.ref_fasta, + it.meta.ref_gff ] } .set { liftoff_in } diff --git a/subworkflows/local/prepare/jellyfish/main.nf b/subworkflows/local/prepare/jellyfish/main.nf index d2658e74..61ee849a 100644 --- a/subworkflows/local/prepare/jellyfish/main.nf +++ b/subworkflows/local/prepare/jellyfish/main.nf @@ -12,15 +12,23 @@ workflow JELLYFISH { channel.empty().set { ch_versions } ch_main - .filter { it -> it.group } - .map { it -> [it.meta, it.group, it.jellyfish_k, it.qc_reads_path, it.qc_read_mean] } + .filter { it -> it.meta.group } + .map { it -> + [ + it.meta, + it.meta.group, + it.meta.jellyfish_k, + it.meta.qc_reads_path, + it.meta.qc_read_mean + ] + } .groupTuple(by: 1) .map { it -> [ meta: [ id: it[1], - ids: it[0].id.collect().join("+"), + metas: it[0], jellyfish_k: it[2].unique()[0], qc_read_mean: it[4].unique()[0] ], @@ -33,12 +41,8 @@ workflow JELLYFISH { .map { it -> [ - meta: [ - id: it.meta.id, - jellyfish_k: it.jellyfish_k, - qc_read_mean: it.qc_read_mean - ], - qc_reads_path: it.qc_reads_path + meta: it.meta, + qc_reads_path: it.meta.qc_reads_path ] } ) @@ -53,16 +57,14 @@ workflow JELLYFISH { ch_versions = ch_versions.mix(HISTO.out.versions) HISTO.out.histo - .join( - samples - .map { it -> + .map { meta, hist -> [ - it.meta, - it.meta.jellyfish_k, - it.meta.qc_read_mean + meta, + meta.jellyfish_k, + meta.qc_read_mean, + hist ] - } - ) + } .set { genomescope_in } STATS(kmers) @@ -73,41 +75,22 @@ workflow JELLYFISH { .mix(GENOMESCOPE.out.versions) .mix(STATS.out.versions) - ch_main - .map { - it -> it - it.subMap('genome_size') - } - .map { - it -> it.collect { entry -> [ entry.value, entry ] } + GENOMESCOPE.out.estimated_hap_len + .filter { it -> it[0].metas } + .flatMap { it -> + it[0].metas + .collect { meta -> [ meta: meta + [ genome_size: it[1] ] ] } } - .join( - GENOMESCOPE.out.estimated_hap_len - .filter { it -> it[0].ids } - .flatMap { it -> - it[0].ids - .tokenize("+") - .collect { sample -> [ [ id: sample ], it[1] ] } - } - .mix(GENOMESCOPE.out.estimated_hap_len - .filter { it -> !it[0].ids } - .map { - it -> [ [ id: it[0].id ], it[1] ] - } - ) + .mix(GENOMESCOPE.out.estimated_hap_len + .filter { it -> !it[0].ids } .map { - it -> - [ - meta: it[0], - genome_size: it[1] - ] - } - .map { - it -> it.collect { entry -> [ entry.value, entry ] } - } + it -> [ meta: it[0] + [ genome_size: it[1] ] ] + } ) - .map { it -> it.collect { _entry, map -> [ (map.key): map.value ] }.collectEntries() } .set { outputs } + outputs.dump(tag: "Jellyfish outputs") + GENOMESCOPE.out.summary.set { genomescope_summary } GENOMESCOPE.out.plot.set { genomescope_plot } diff --git a/subworkflows/local/prepare/main.nf b/subworkflows/local/prepare/main.nf index 7513654e..ecaa5184 100644 --- a/subworkflows/local/prepare/main.nf +++ b/subworkflows/local/prepare/main.nf @@ -70,19 +70,19 @@ workflow PREPARE { main: ch_main .filter { - it -> (it.shortread_F && it.use_short_reads) ? true : false + it -> (it.meta.shortread_F && it.meta.use_short_reads) ? true : false } .set { shortreads } ch_main .filter { - it -> (it.ontreads) ? true : false + it -> (it.meta.ontreads) ? true : false } .set { ontreads } ch_main .filter { - it -> (it.hifireads) ? true : false + it -> (it.meta.hifireads) ? true : false } .set { hifireads } @@ -99,9 +99,9 @@ workflow PREPARE { ch_main .filter { - it -> !it.shortread_F ? true : false + it -> !it.meta.shortread_F ? true : false } - .map { it -> it - it.subMap("shortread_F","shortread_R", "paired") + [shorteads: null] } + .map { it -> it.meta - it.meta.subMap("shortread_F","shortread_R", "paired") + [shorteads: null] } .mix(SHORTREADS.out.main_out) .set { ch_main_shortreaded } @@ -110,26 +110,32 @@ workflow PREPARE { ONT.out.main_out.set { ch_main_ont_prepped } + // Continue here with switching to meta + HIFI(hifireads) HIFI.out.main_out.set { ch_main_hifi_prepped } ch_main_shortreaded + // ADD ONT READS .filter { it -> it.ontreads ? true : false } - .map { it -> it.subMap("meta","shortreads")} - .map { it -> it.collect { entry -> [ entry.value, entry ] } } - .join( ch_main_ont_prepped - .map { it -> it - it.subMap("shortreads") } - .map { it -> it.collect { entry -> [ entry.value, entry ] } } + .map { it -> [it.meta.id, it.meta - it.meta.subMap("ontreads")]} + .join( + ch_main_ont_prepped + .map { it -> [it.meta.id, it.meta.ontreads] } ) - // After joining re-create the maps from the stored map - .map { it -> it.collect { _entry, map -> [ (map.key): map.value ] }.collectEntries() } + // After joining re-create the maps from the stored map + .map { _id, meta_old, ont_reads -> + [ + meta: meta_old + [ontreads: ont_reads] + ] + } // mix back in those samples where nothing was done to the ont reads .mix(ch_main_shortreaded .filter { - it -> it.ontreads ? false : true + it -> it.meta.ontreads ? false : true } ) .set { @@ -142,14 +148,17 @@ workflow PREPARE { .filter { it -> it.hifireads ? true : false } - .map { it -> it.subMap("meta", "shortreads", "ontreads")} - .map { it -> it.collect { entry -> [ entry.value, entry ] } } - .join( ch_main_hifi_prepped - .map { it -> it - it.subMap("shortreads","ontreads") } - .map { it -> it.collect { entry -> [ entry.value, entry ] } } + .map { it -> [it.meta.id, it.meta - it.meta.subMap("hifireads")]} + .join( + ch_main_hifi_prepped + .map { it -> [it.meta.id, it.meta.hifireads] } ) // After joining re-create the maps from the stored map - .map { it -> it.collect { _entry, map -> [ (map.key): map.value ] }.collectEntries() } + .map { _id, meta_old, hifi_reads -> + [ + meta: meta_old + [hifireads: hifi_reads] + ] + } // mix back in those samples where nothing was done to the hifireads reads .mix(ch_main_sr_ont .filter { @@ -164,34 +173,53 @@ workflow PREPARE { def slurp = new groovy.json.JsonSlurper() ch_main_prepared - .filter { it -> it.qc_reads.toLowerCase() == "ont" } - .map { it -> it.collect { entry -> [ entry.value, entry ] } } - .join(ONT.out.fastplong_ont_reports - .map { it -> [ meta: it[0], fastplong_json: it[1] ]} - .map { it -> it.collect { entry -> [ entry.value, entry ] } } - ) + .filter { it -> it.meta.qc_reads.toLowerCase() == "ont" } + .map { it -> + [ + it.meta.id, + it.meta - it.meta.subMap("fastplong_json") + ] + } + .join( + ONT.out.fastplong_ont_reports + .map { it -> [ it[0].id, it[1] ]} + ) + .map { + _id, meta_old, json -> [meta: meta_old + [fastplong_json: json]] + } .mix( ch_main_prepared .filter { it -> it.qc_reads.toLowerCase() == "hifi" } - .map { it -> it.collect { entry -> [ entry.value, entry ] } } - .join(HIFI.out.fastplong_hifi_reports - .map { it -> [ meta: it[0], fastplong_json: it[1] ]} - .map { it -> it.collect { entry -> [ entry.value, entry ] } } + .map { + it -> [ + it.meta.id, it.meta - it.meta.subMap("fastplong_json")]} + .join( + HIFI.out.fastplong_hifi_reports + .map { it -> [ it[0].id, it[1] ]} ) + .map { + _id, meta_old, json -> [meta: meta_old + [fastplong_json: json]] + } ) - .map { it -> it.collect { _entry, map -> [ (map.key): map.value ] }.collectEntries() } .map { it -> - it + [ - qc_read_mean: slurp.parse(it.fastplong_json).summary.after_filtering.read_mean_length ?: - slurp.parse(it.fastplong_json).summary.before_filtering.read_mean_length - ] - - it.subMap("fastplong_json") + meta: it.meta + + [ + qc_read_mean: slurp.parse(it.meta.fastplong_json) + .summary + .after_filtering + .read_mean_length ?: + slurp.parse(it.meta.fastplong_json) + .summary + .before_filtering + .read_mean_length + ] + ] } .branch { it -> - jelly: it.jellyfish - no_jelly: !it.jellyfish + jelly: it.meta.jellyfish + no_jelly: !it.meta.jellyfish } .set { ch_main_jellyfish_branched } @@ -201,7 +229,17 @@ workflow PREPARE { ch_main_jellyfish_branched.no_jelly .mix( JELLYFISH.out.main_out ) // At this stage, make sure that qc_read_path for downstream qc is using the prepared reads. - .map { it -> it - it.subMap("qc_read_path") + [qc_read_path: it.qc_reads.toLowerCase() == "ont" ? it.ontreads : it.hifireads] } + .map { it -> + [ + meta: it.meta - + it.meta.subMap("qc_read_path") + + [ + qc_read_path: it.meta.qc_reads.toLowerCase() == "ont" ? + it.meta.ontreads : + it.meta.hifireads + ] + ] + } .set { main_out } main_out.dump(tag: "Prepare: Combined outputs") diff --git a/subworkflows/local/prepare/prepare_hifi/main.nf b/subworkflows/local/prepare/prepare_hifi/main.nf index edbc1b07..ffc1700d 100644 --- a/subworkflows/local/prepare/prepare_hifi/main.nf +++ b/subworkflows/local/prepare/prepare_hifi/main.nf @@ -8,35 +8,33 @@ workflow PREPARE_HIFI { channel.empty().set { ch_versions } main_in.dump(tag: "Prepare-HIFI input") + main_in - .filter { it -> it.group } - .map { it -> [it.meta, it.group, it.hifi_trim, it.hifireads, it.hifi_adapters, it.hifi_fastplong_args] } + .filter { it -> it.meta.group } + .map { it -> [it.meta, it.meta.group, it.meta.hifi_trim, it.meta.hifireads, it.meta.hifi_adapters, it.meta.hifi_fastplong_args] } .groupTuple(by: 1) .map { it -> [ meta: [ - id: it[1], ids: it[0].id.collect().join("+"), - trim: it[2].unique()[0], - hifi_fastplong_args: it[5].unique()[0] + id: it[1], + metas: it[0], + trim: it[2][0], // These go in via config + hifi_fastplong_args: it[5][0] ], - hifireads: it[3].unique()[0], - hifi_adapters: it[4].unique()[0] + hifireads: it[3][0], + hifi_adapters: it[4][0] ] } .mix( main_in - .filter { it -> !it.group } + .filter { it -> !it.meta.group } .map { it -> [ - meta: [ - id: it.meta.id, - trim: it.hifi_trim, - hifi_fastplong_args: it.hifi_fastplong_args - ], - hifireads: it.hifireads, - hifi_adapters: it.hifi_adapters, + meta: it.meta, + hifireads: it.meta.hifireads, + hifi_adapters: it.meta.hifi_adapters, ] } ) @@ -47,21 +45,22 @@ workflow PREPARE_HIFI { } .set { ch_fastplong_in } + ch_fastplong_in.reads.dump(tag: "HiFI fastplong reads in") + FASTPLONG_HIFI(ch_fastplong_in.reads, ch_fastplong_in.adapters, false, false ) FASTPLONG_HIFI .out .reads - .filter { it -> it[0].ids } - .flatMap { it -> - it[0].ids - .tokenize("+") - .collect { sample -> [ meta: [ id: sample ], hifireads: it[1] ] } - } + .filter { it -> it[0].metas } + .flatMap { it -> // it looks like [meta, output_path] + it[0].metas + .collect { meta -> [ meta: meta.clone() + [hifireads: it[1]] ] } + } .mix(FASTPLONG_HIFI.out.reads - .filter { it -> !it[0].ids } + .filter { it -> !it[0].metas } .map { - it -> [ meta: [ id: it[0].id ], hifireads: it[1] ] + it -> [ meta: it[0].clone() + [ hifireads: it[1] ] ] } ) .set { fastplong_reads_out } @@ -69,37 +68,22 @@ workflow PREPARE_HIFI { FASTPLONG_HIFI .out .json - .filter { it -> it[0].ids } + .filter { it -> it[0].metas } .flatMap { it -> - it[0].ids - .tokenize("+") - .collect { sample -> [ [ id: sample ], it[1] ] } + it[0].metas + .collect { meta -> [ meta.clone(), it[1] ] } } .mix(FASTPLONG_HIFI.out.json .filter { it -> !it[0].ids } - .map { - it -> [ [ id: it[0].id ], it[1] ] - } ) .set { fastplong_json_out } - // inputs are joined to outputs - main_in - .map { it -> it - it.subMap('hifireads') } - .map { it -> it.collect { entry -> [ entry.value, entry ] } } - .join( - fastplong_reads_out - .map { it -> it.collect { entry -> [ entry.value, entry ] } } - ) - .map { it -> it.collect { _entry, map -> [ (map.key): map.value ] }.collectEntries() } - .set { main_out } - - main_out.dump(tag: "Prepare-HIFI output") + fastplong_reads_out.dump(tag: "Prepare-HIFI output") versions = ch_versions.mix(FASTPLONG_HIFI.out.versions) emit: - main_out - fastplong_hifi_reports = fastplong_json_out + main_out = fastplong_reads_out + fastplong_hifi_reports = fastplong_json_out versions } diff --git a/subworkflows/local/prepare/prepare_ont/main.nf b/subworkflows/local/prepare/prepare_ont/main.nf index bafe6a1a..12bcccf8 100644 --- a/subworkflows/local/prepare/prepare_ont/main.nf +++ b/subworkflows/local/prepare/prepare_ont/main.nf @@ -13,29 +13,32 @@ workflow PREPARE_ONT { ch_main .branch { it -> - to_collect: it.ont_collect - no_collect: !it.ont_collect + to_collect: it.meta.ont_collect + no_collect: !it.meta.ont_collect } .set { ch_main_collect_branched } ch_main_collect_branched .to_collect - .filter { it -> it.group } - .map { it -> [it.meta, it.group, it.ontreads] } + .filter { it -> it.meta.group } + .map { it -> [it.meta, it.meta.group, it.meta.ontreads] } .groupTuple(by: 1) .map { it -> [ - [id: it[1], ids: it[0].id.collect().join("+")], - it[2].unique()[0] + [ + id: it[1], // the group + metas: it[0] + ], + it[2].unique()[0] // Ontreads ] } .mix( ch_main_collect_branched .to_collect - .filter { it -> !it.group } + .filter { it -> !it.meta.group } .map { - it -> [ it.meta, it.ontreads ] + it -> [ it.meta, it.meta.ontreads ] } ) .set { collect_in } @@ -43,45 +46,44 @@ workflow PREPARE_ONT { COLLECT(collect_in) COLLECT.out.reads - .filter { it -> it[0].ids } - .flatMap { it -> - it[0].ids - .tokenize("+") - .collect { sample -> [ meta: [ id: sample ], ontreads: it[1] ] } - } - .mix(COLLECT.out.reads + .filter { it -> it[0].metas } + .flatMap { it -> // it looks like [meta, output_path] + it[0].metas + .collect { meta -> [ meta: meta + [ontreads: it[1]] ] } + } + .mix( + COLLECT.out.reads .filter { it -> !it[0].ids } .map { - it -> [ meta: [ it[0].id ], ontreads: it[1] ] + it -> [ meta: it[0] + [ontreads: it[1]] ] } ) - .map { it -> it.collect { entry -> [ entry.value, entry ] } } .set { ch_collected_reads } - ch_main_collect_branched - .to_collect - .map { it -> it - it.subMap("ontreads") } - .map { it -> it.collect { entry -> [ entry.value, entry ] } } - .join(ch_collected_reads) - .map { it -> it.collect { _entry, map -> [ (map.key): map.value ] }.collectEntries() } + ch_collected_reads.dump(tag: "Collected ONT reads") + + ch_collected_reads .mix(ch_main_collect_branched.no_collect) .set { ch_collected } + ch_collected.dump(tag: "Collected reads mixed with uncollected.") + // ch_collected is the same samples as the input channel ch_collected .filter { it -> it.group } - .map { it -> [it.meta, it.group, it.ont_trim, it.ontreads, it.ont_adaptors, it.ont_fastplong_args] } + .map { it -> [it.meta, it.meta.group, it.meta.ont_trim, it.meta.ontreads, it.meta.ont_adaptors, it.meta.ont_fastplong_args] } .groupTuple(by: 1) .map { it -> [ meta: [ - id: it[1], ids: it[0].id.collect().join("+"), - trim: it[2].unique()[0], - ont_fastplong_args: it[5].unique()[0] + id: it[1], + metas: it[0], + trim: it[2][0], + ont_fastplong_args: it[5][0] ], - ontreads: it[3].unique()[0], - ont_adaptors: it[4].unique()[0] + ontreads: it[3][0], + ont_adaptors: it[4][0] ] } .mix( @@ -90,13 +92,9 @@ workflow PREPARE_ONT { .map { it -> [ - meta: [ - id: it.meta.id, - trim: it.ont_trim, - ont_fastplong_args: it.ont_fastplong_args - ], - ontreads: it.ontreads, - ont_adaptors: it.ont_adaptors, + meta: it.meta, + ontreads: it.meta.ontreads, + ont_adaptors: it.meta.ont_adaptors, ] } ) @@ -112,16 +110,15 @@ workflow PREPARE_ONT { FASTPLONG_ONT .out .reads - .filter { it -> it[0].ids } - .flatMap { it -> - it[0].ids - .tokenize("+") - .collect { sample -> [ meta: [ id: sample ], ontreads: it[1] ] } - } + .filter { it -> it[0].metas } + .flatMap { it -> // it looks like [meta, output_path] + it[0].metas + .collect { meta -> [ meta: meta + [ontreads: it[1]] ] } + } .mix(FASTPLONG_ONT.out.reads .filter { it -> !it[0].ids } .map { - it -> [ meta: [ id: it[0].id ], ontreads: it[1] ] + it -> [ meta: [ id: it[0] ] + [ ontreads: it[1] ] ] } ) .set { fastplong_reads_out } @@ -129,36 +126,23 @@ workflow PREPARE_ONT { FASTPLONG_ONT .out .json - .filter { it -> it[0].ids } - .flatMap { it -> - it[0].ids - .tokenize("+") - .collect { sample -> [ [ id: sample ], it[1] ] } - } - .mix(FASTPLONG_ONT.out.json - .filter { it -> !it[0].ids } - .map { - it -> [ [ id: it[0].id ], it[1] ] - } + .filter { it -> it[0].metas } + .flatMap { it -> // it looks like [meta, output_path] + it[0].metas + .collect { meta -> [ meta, + it[1] ] } + } + .mix( + FASTPLONG_ONT.out.json + .filter { it -> !it[0].metas } ) .set { fastplong_json_out } - ch_collected - .map { it -> it - it.subMap('ontreads') } - .map { it -> it.collect { entry -> [ entry.value, entry ] } } - .join( - fastplong_reads_out - .map { it -> it.collect { entry -> [ entry.value, entry ] } } - ) - .map { it -> it.collect { _entry, map -> [ (map.key): map.value ] }.collectEntries() } - .set { main_out } - - versions = ch_versions.mix(COLLECT.out.versions).mix(FASTPLONG_ONT.out.versions) + versions = ch_versions.mix(COLLECT.out.versions).mix(FASTPLONG_ONT.out.versions) - main_out.dump(tag: "Prepare-ONT output") + fastplong_reads_out.dump(tag: "Prepare-ONT output") emit: - main_out + main_out = fastplong_reads_out fastplong_ont_reports = fastplong_json_out versions } diff --git a/subworkflows/local/prepare/prepare_shortreads/main.nf b/subworkflows/local/prepare/prepare_shortreads/main.nf index d260db9a..aa1ff301 100644 --- a/subworkflows/local/prepare/prepare_shortreads/main.nf +++ b/subworkflows/local/prepare/prepare_shortreads/main.nf @@ -10,96 +10,83 @@ workflow PREPARE_SHORTREADS { channel.empty().set { ch_versions } shortreads_in - .map { it -> create_shortread_channel(it) } + .map { it -> create_shortread_channel(it.meta) } // See modified function below, adds shortreads to meta .set { shortreads } - shortreads_in - .map { - it -> it - it.subMap('shortread_F', 'shortread_R', 'paired') - } - .map { - it -> it.collect { entry -> [ entry.value, entry ] } - } - .join( - shortreads - .map { it -> [meta: [id: it[0].id], shortreads: it[1]]} - .map { it -> it.collect { entry -> [ entry.value, entry ] } } - ) - .map { it -> it.collect { _entry, map -> [ (map.key): map.value ] }.collectEntries() } - .set { shortreads } + shortreads.dump(tag: "shortread channel") // shortread trimming - //shortreads.view { it -> "shortreads: $it" } shortreads .branch { it -> - trim: it.shortread_trim - no_trim: !it.shortread_trim + trim: it.meta.shortread_trim + no_trim: !it.meta.shortread_trim } .set { shortreads } + //shortreads.dump(tag: "Shortreads branched") + shortreads .trim - .filter { it -> it.group } - .map { it -> [it.meta, it.group, it.shortreads] } - // Create a group + .filter { it -> it.meta.group } + .map {it -> [it.meta, it.meta.group]} .groupTuple(by: 1) .map { it -> [ - [ id: it[1], ids: it[0].id.collect().join("+") ], - it[2].unique()[0], + [ + id: it[1], // the group + metas: it[0] + ], + it[0].shortreads[0], // Pull path from meta [] ] } .mix(shortreads.trim .filter { it -> !it.group } .map { - it -> [ it.meta, it.shortreads, [] ] + it -> [ it.meta, it.meta.shortreads, [] ] } ) .set { trim_in } + trim_in.dump(tag: "Trim in") + FASTP(trim_in, false, false, false) FASTP.out.reads - .filter { it -> it[0].ids } - .flatMap { it -> - it[0].ids.tokenize("+").collect { - sample -> [meta: [ id: sample ], shortreads: it[1] ] - } + .filter { it -> it[0].metas } + .flatMap { it -> // looks like [meta <[id, metas]>, output_path] + it[0].metas + .collect { meta -> [ meta: meta + [ shortreads: it[1] ] ] } } .mix( FASTP.out.reads .filter { it -> !it[0].ids } - .map { it -> [ meta: [ id: it[0].id ], shortreads: it[1] ] } + .map { it -> [ meta: it[0] + [ shortreads: it[1] ] ] } ) - .map { it -> it.collect { entry -> [ entry.value, entry ] } } .set { trimmed_reads } + trimmed_reads.dump(tag: "Trim out") // unite branched: // add trimmed reads to trim channel, then mix with shortreads.no_trim - shortreads.trim - .map { it -> it - it.subMap("shortreads") } - .map { it -> it.collect { entry -> [ entry.value, entry ] } } - .join( trimmed_reads ) - .map { it -> it.collect { _entry, map -> [ (map.key): map.value ] }.collectEntries() } + trimmed_reads .mix( shortreads.no_trim ) .set { shortreads } ch_versions = ch_versions.mix(FASTP.out.versions) shortreads - .filter { it -> it.merqury } - .filter { it -> it.group } - .map { it -> [it.meta, it.group, it.shortreads, it.meryl_k] } + .filter { it -> it.meta.merqury } + .filter { it -> it.meta.group } + .map { it -> [it.meta, it.meta.group, it.meta.shortreads, it.meta.meryl_k] } // Create a group .groupTuple(by: 1) .map { it -> [ - meta: [ id: it[1], ids: it[0].id.collect().join("+") ], + meta: [ id: it[1], metas: it[0] ], shortreads: it[2].unique()[0], meryl_k: it[3].unique()[0] ] @@ -107,7 +94,7 @@ workflow PREPARE_SHORTREADS { .mix(shortreads .filter { it -> it.merqury } .filter { it -> !it.group } - .map { it -> [meta: it.meta, shortreads: it.shortreads, meryl_k: it.meryl_k]} + .map { it -> [meta: it.meta, shortreads: it.meta.shortreads, meryl_k: it.meta.meryl_k]} ) .multiMap { it -> reads: [ it.meta, it.shortreads ] @@ -120,45 +107,32 @@ workflow PREPARE_SHORTREADS { MERYL_UNIONSUM(MERYL_COUNT.out.meryl_db, params.meryl_k) MERYL_UNIONSUM.out.meryl_db - .filter { it -> it[0].ids } - .flatMap { it -> - it[0].ids - .tokenize("+") - .collect { sample -> [ [ id: sample ], it[1] ] } - } + .filter { it -> it[0].metas } + .flatMap { it -> // looks like [meta <[id, metas]>, output_path] + it[0].metas + .collect { meta -> [ meta, it[1] ] } + } .mix(MERYL_UNIONSUM.out.meryl_db .filter { it -> !it[0].ids } .map { - it -> [ [ id: it[0].id ], it[1] ] + it -> [ it[0], it[1] ] } ).set { meryl_kmers } - shortreads_in - .map { - it -> it - it.subMap('shortread_F', 'shortread_R', 'paired') - } - .map { it -> it.collect { entry -> [ entry.value, entry ] } } - .join( - shortreads - .map { it -> [meta: [id: it.meta.id], shortreads: it.shortreads]} - .map { it -> it.collect { entry -> [ entry.value, entry ] } } - ) - .map { it -> it.collect { _entry, map -> [ (map.key): map.value ] }.collectEntries() } - .set { main_out } + versions = ch_versions.mix(MERYL_COUNT.out.versions).mix(MERYL_UNIONSUM.out.versions) emit: - main_out + main_out = shortreads meryl_kmers versions - fastp_json = FASTP.out.json + fastp_json = FASTP.out.json } -def create_shortread_channel(row) { +def create_shortread_channel(row) { // This function expects a meta map as input // create meta map - def meta = [:] - meta.id = row.meta.id + def meta = row meta.paired = row.paired.toBoolean() meta.single_end = !meta.paired @@ -168,13 +142,13 @@ def create_shortread_channel(row) { exit(1, "ERROR: shortread_F fastq file does not exist!\n${row.shortread_F}") } if (!meta.paired) { - shortreads = [meta, [file(row.shortread_F)]] + shortreads = [meta: meta + [shortreads: [file(row.shortread_F)]]] } else { if (!file(row.shortread_R).exists()) { exit(1, "ERROR: shortread_R fastq file does not exist!\n${row.shortread_R}") } - shortreads = [meta, [file(row.shortread_F), file(row.shortread_R)]] + shortreads = [ meta: meta + [shortreads: [file(row.shortread_F), file(row.shortread_R)]] ] } return shortreads } diff --git a/subworkflows/local/qc/busco/main.nf b/subworkflows/local/qc/busco/main.nf index fcc3ddd1..65fcfc7a 100644 --- a/subworkflows/local/qc/busco/main.nf +++ b/subworkflows/local/qc/busco/main.nf @@ -17,10 +17,10 @@ workflow RUN_BUSCO { .multiMap { it -> fasta: [ it.meta, - it.qc_target + it.meta.qc_target ] - busco_lineage: it.busco_lineage - busco_db: it.busco_db ? file(it.busco_db, checkIfExists: true) : [] + busco_lineage: it.meta.busco_lineage + busco_db: it.meta.busco_db ? file(it.meta.busco_db, checkIfExists: true) : [] } .set { busco_in } diff --git a/subworkflows/local/qc/main.nf b/subworkflows/local/qc/main.nf index 0cc9ff13..9773d4d7 100644 --- a/subworkflows/local/qc/main.nf +++ b/subworkflows/local/qc/main.nf @@ -18,18 +18,18 @@ workflow QC { ch_main .branch { it -> - shortread: it.use_short_reads - no_shortread: !it.use_short_reads + shortread: it.meta.use_short_reads + no_shortread: !it.meta.use_short_reads } .set { ch_shortread_branched } ch_shortread_branched .shortread - .filter { it -> it.merqury } - .map { it -> [it.meta] } + .filter { it -> it.meta.merqury } + .map { it -> [it.meta.id, it.meta] } .join(scaffolds) .join(meryl_kmers) - .multiMap { meta, scaffs, kmers -> + .multiMap { _id, meta, scaffs, kmers -> scaffolds: [ meta, scaffs ] kmers: [ meta, kmers ] } @@ -42,53 +42,37 @@ workflow QC { ch_main .branch { it -> - map_to_assembly: it.quast && !it.assembly_map_bam - no_map_to_assembly: !it.quast || (it.quast && it.assembly_map_bam) + map_to_assembly: it.meta.quast && !it.meta.assembly_map_bam + no_map_to_assembly: !it.meta.quast || (it.meta.quast && it.meta.assembly_map_bam) } .set { ch_map_branched } ch_map_branched .map_to_assembly .map { - it -> [ it.meta, it.qc_reads_path, it.qc_reads ] + it -> [ it.meta.id, it.meta ] } .join(scaffolds) .map { - meta, reads, qc_reads, target_scaffolds -> + _id, meta, target_scaffolds -> [ - [ - id: meta.id, - qc_reads:qc_reads - ], - reads, + meta, + meta.qc_reads_path, target_scaffolds ] } .set { map_assembly_in } - MAP_TO_ASSEMBLY(map_assembly_in) + MAP_TO_ASSEMBLY(map_assembly_in) // create main channel with mappings - ch_map_branched - .map_to_assembly - .map { it -> it - it.subMap("assembly_map_bam") } - .map { it -> it.collect { entry -> [ entry.value, entry ] } } - .join( - MAP_TO_ASSEMBLY.out.aln_to_assembly_bam - .map { it -> [meta: it[0], assembly_map_bam: it[1]] } - .map { it -> it.collect { entry -> [ entry.value, entry ] } } - ) - .map { it -> it.collect { _entry, map -> [ (map.key): map.value ] }.collectEntries() } + MAP_TO_ASSEMBLY.out.aln_to_assembly_bam + .map { meta, assembly_map_bam -> + [ + meta: meta + [ assembly_map_bam: assembly_map_bam ] + ] + } .mix(ch_map_branched.no_map_to_assembly) - .map { it -> it.collect { entry -> [ entry.value, entry ] } } - .join( - scaffolds - .map { - it -> [meta: it[0], qc_target: it[1] ] - } - .map { it -> it.collect { entry -> [ entry.value, entry ] } } - ) - .map { it -> it.collect { _entry, map -> [ (map.key): map.value ] }.collectEntries() } .set { ch_qc } diff --git a/subworkflows/local/qc/quast/main.nf b/subworkflows/local/qc/quast/main.nf index 56bf886b..4e63a97d 100644 --- a/subworkflows/local/qc/quast/main.nf +++ b/subworkflows/local/qc/quast/main.nf @@ -15,18 +15,18 @@ workflow RUN_QUAST { ch_main .filter { - it -> it.quast + it -> it.meta.quast } .multiMap { it -> quast_in: [ it.meta, - it.qc_target, - it.ref_fasta ?: [], - it.ref_gff ?: [], - it.ref_map_bam ?: [], - it.assembly_map_bam + it.meta.qc_target, + it.meta.ref_fasta ?: [], + it.meta.ref_gff ?: [], + it.meta.ref_map_bam ?: [], + it.meta.assembly_map_bam ] - use_ref: it.use_ref + use_ref: it.meta.use_ref } .set { quast_in } /* diff --git a/subworkflows/local/scaffolding/links/main.nf b/subworkflows/local/scaffolding/links/main.nf index 5611d628..defdc2a0 100644 --- a/subworkflows/local/scaffolding/links/main.nf +++ b/subworkflows/local/scaffolding/links/main.nf @@ -12,8 +12,8 @@ workflow RUN_LINKS { ch_main.dump(tag: "SCAFFOLD: LINKS: WORKFLOW inputs") ch_main .multiMap { it -> - assembly: [it.meta, it.polished ? (it.polished.pilon ?: it.polished.medaka) : it.assembly] - reads: [it.meta, it.qc_reads_path] + assembly: [it.meta, it.meta.polished ? (it.meta.polished.pilon ?: it.meta.polished.medaka) : it.meta.assembly] + reads: [it.meta, it.meta.qc_reads_path] } .set { links_in } @@ -22,21 +22,14 @@ workflow RUN_LINKS { LINKS(links_in.assembly, links_in.reads) LINKS.out.scaffolds_fasta - .set { scaffolds } - - ch_main - .map { it -> it.collect { entry -> [ entry.value, entry ] } } - .join( - scaffolds - .map { it -> [meta: it[0], scaffolds_links: it[1]] } - .map { it -> it.collect { entry -> [ entry.value, entry ] } } - ) - .map { it -> it.collect { _entry, map -> [ (map.key): map.value ] }.collectEntries() } + .map { meta, scaff_links -> [meta: meta + [scaffolds_links: scaff_links] ] } .set { ch_main_scaffolded } ch_versions = ch_versions.mix(LINKS.out.versions) - QC(ch_main_scaffolded.map { it -> it - it.subMap("assembly_map_bam") + [assembly_map_bam: null] }, scaffolds, meryl_kmers) + QC(ch_main_scaffolded.map { it -> [meta: it.meta - it.meta.subMap("assembly_map_bam") + [assembly_map_bam: null] ]}, + LINKS.out.scaffolds_fasta.map { meta, scaffold -> [meta.id, scaffold]}, + meryl_kmers) ch_versions = ch_versions.mix(QC.out.versions) diff --git a/subworkflows/local/scaffolding/longstitch/main.nf b/subworkflows/local/scaffolding/longstitch/main.nf index fb3f7051..693a04f8 100644 --- a/subworkflows/local/scaffolding/longstitch/main.nf +++ b/subworkflows/local/scaffolding/longstitch/main.nf @@ -24,45 +24,39 @@ workflow RUN_LONGSTITCH { it -> [ it.meta, - it.polished ? (it.polished.pilon ?: it.polished.medaka) : it.assembly, - it.qc_reads_path, - it.genome_size ?: params.genome_size + it.meta.polished ? (it.polished.pilon ?: it.polished.medaka) : it.assembly, + it.meta.qc_reads_path, + it.meta.genome_size ] } .set { longstitch_in } longstitch_in.dump(tag: "SCAFFOLD: LONGSTITCH: inputs") + LONGSTITCH(longstitch_in) LONGSTITCH.out.ntlLinks_arks_scaffolds - .set { scaffolds } - - ch_main - .map { it -> it.collect { entry -> [ entry.value, entry ] } } - .join( - scaffolds - .map { it -> [meta: it[0], scaffolds_longstitch: it[1]] } - .map { it -> it.collect { entry -> [ entry.value, entry ] } } - ) - .map { it -> it.collect { _entry, map -> [ (map.key): map.value ] }.collectEntries() } + .map { meta, scaff_longst -> [meta: meta + [scaffolds_longstitch: scaff_longst] ] } .set { ch_main_scaffolded } ch_versions = ch_versions.mix(LONGSTITCH.out.versions) - QC(ch_main_scaffolded.map { it -> it - it.subMap("assembly_map_bam") + [assembly_map_bam: null] }, scaffolds, meryl_kmers) + QC(ch_main_scaffolded.map { it -> [ meta: it.meta - it.meta.subMap("assembly_map_bam") + [assembly_map_bam: null] ] }, + LONGSTITCH.out.ntlLinks_arks_scaffolds.map { meta, scaffold -> [meta.id, scaffold]}, + meryl_kmers) ch_versions = ch_versions.mix(QC.out.versions) ch_main_scaffolded .filter { - it -> it.lift_annotations + it -> it.meta.lift_annotations } .map { it -> [ it.meta, - it.scaffolds_longstitch, - it.ref_fasta, - it.ref_gff + it.meta.scaffolds_longstitch, + it.meta.ref_fasta, + it.meta.ref_gff ] } .set { liftoff_in } diff --git a/subworkflows/local/scaffolding/main.nf b/subworkflows/local/scaffolding/main.nf index 60be6356..9f93241a 100644 --- a/subworkflows/local/scaffolding/main.nf +++ b/subworkflows/local/scaffolding/main.nf @@ -25,14 +25,12 @@ workflow SCAFFOLD { ch_main .filter { - it -> it.scaffold_links + it -> it.meta.scaffold_links } .set { links_in } RUN_LINKS(links_in, meryl_kmers) RUN_LINKS.out.ch_main - .map { it -> it.subMap("meta", "scaffolds_links")} - .map { it -> it.collect { entry -> [ entry.value, entry ] } } .set { links_out } ch_main diff --git a/subworkflows/local/scaffolding/ragtag/main.nf b/subworkflows/local/scaffolding/ragtag/main.nf index ada0a604..0cd58b10 100644 --- a/subworkflows/local/scaffolding/ragtag/main.nf +++ b/subworkflows/local/scaffolding/ragtag/main.nf @@ -16,9 +16,9 @@ workflow RUN_RAGTAG { assembly: [ it.meta, - it.polished ? (it.polished.pilon ?: it.polished.medaka) : it.assembly + it.meta.polished ? (it.meta.polished.pilon ?: it.meta.polished.medaka) : it.meta.assembly ] - reference: [it.meta, it.ref_fasta] + reference: [it.meta, it.meta.ref_fasta] } .set { ragtag_in } @@ -28,19 +28,13 @@ workflow RUN_RAGTAG { RAGTAG_SCAFFOLD(ragtag_in.assembly, ragtag_in.reference, [[], []], [[], [], []]) - RAGTAG_SCAFFOLD.out.corrected_assembly.set { scaffolds } - - ch_main - .map { it -> it.collect { entry -> [ entry.value, entry ] } } - .join( - scaffolds - .map { it -> [meta: it[0], scaffolds_ragtag: it[1]] } - .map { it -> it.collect { entry -> [ entry.value, entry ] } } - ) - .map { it -> it.collect { _entry, map -> [ (map.key): map.value ] }.collectEntries() } + RAGTAG_SCAFFOLD.out.corrected_assembly + .map { meta, corrected -> [meta: meta + [ scaffolds_ragtag: corrected] ] } .set { ch_main_scaffolded } - QC(ch_main_scaffolded.map { it -> it - it.subMap("assembly_map_bam") + [assembly_map_bam: null] }, scaffolds, meryl_kmers) + QC(ch_main_scaffolded.map { it -> [meta: it.meta - it.meta.subMap("assembly_map_bam") + [assembly_map_bam: null] ] }, + RAGTAG_SCAFFOLD.out.corrected_assembly.map { meta, corrected -> [ meta.id, corrected ] }, + meryl_kmers) ch_versions = ch_versions.mix(QC.out.versions) @@ -51,9 +45,9 @@ workflow RUN_RAGTAG { .map { it -> [ it.meta, - it.scaffolds_ragtag, - it.ref_fasta, - it.ref_gff + it.meta.scaffolds_ragtag, + it.meta.ref_fasta, + it.meta.ref_gff ] } .set { liftoff_in } diff --git a/subworkflows/local/utils_nfcore_genomeassembler_pipeline/main.nf b/subworkflows/local/utils_nfcore_genomeassembler_pipeline/main.nf index 4afff361..83c085aa 100644 --- a/subworkflows/local/utils_nfcore_genomeassembler_pipeline/main.nf +++ b/subworkflows/local/utils_nfcore_genomeassembler_pipeline/main.nf @@ -107,17 +107,23 @@ workflow PIPELINE_INITIALISATION { def hifireads = it.hifireads ?: params.hifireads - def assembler = it.assembler ?: params.assembler - - def assembler1 = it.assembler1 ?: - params.assembler1 ?: + def assembler = it.strategy == "single" ? + ( it.assembler ?: + (it.ontreads && it.assembler_ont) ? it.assembler_ont : + (it.hifireads && it.assembler_hifi) ? it.assembler_hifi : + params.assembler + ) : + params.assembler + + def assembler_ont = it.assembler_ont ?: + params.assembler_ont ?: (strategy == "single" && ontreads && !hifireads) ? assembler : (strategy == "hybrid" && assembler == "hifiasm") ? assembler : assembler.contains("_") ? assembler.tokenize("_")[0] : null - def assembler2 = it.assembler2 ?: - params.assembler2 ?: + def assembler_hifi = it.assembler_hifi ?: + params.assembler_hifi ?: (strategy == "single" && !ontreads && hifireads) ? assembler : assembler.contains("_") ? assembler.tokenize("_")[1] : null @@ -129,81 +135,85 @@ workflow PIPELINE_INITIALISATION { null // Hard exit here if assembler cannot be determined. - strategy == "single" && ontreads && hifireads && !(assembler1 || assembler2) ? + /* + strategy == "single" && ontreads && hifireads && !(assembler_ont || assembler_hifi) ? error( """ [$it.sample]: Strategy is 'single', but ONT and HiFi reads are provided. - Please unambigiously define either 'assembler1' for ONT or 'assembler2' for HiFi + Please unambigiously define either 'assembler_ont' for ONT or 'assembler_hifi' for HiFi """ ) : null - // Build the map + */ + // Build the map. Everything goes into meta. [ - meta: [id: it.sample], - // new in refactor-assemblies - group: it.group ?: null, - ontreads: ontreads, - hifireads: hifireads, - // new in refactor-assemblers - strategy: strategy, - // The "assembler" value is mainly to ease input, all actual workflow logic should use assembler1/2. - // Could still be useful for debugging. - assembler: assembler, - // Assembler1: ONT, Assembler2: HiFi - assembler1: assembler1, - assembler2: assembler2, - assembly_scaffolding_order: it.assembly_scaffolding_order ?: params.assembly_scaffolding_order ?: "ont_on_hifi", - assembler1_args: it.assembler1_args ?: params.assembler1_args ?: - (assembler1 == "hifiasm") ? (it.hifiasm_args ?: params.hifiasm_args) : - (assembler1 == "flye") ? (it.flye_args ?: params.flye_args) : - null, - assembler2_args: it.assembler2_args ?: params.assembler2_args ?: - (assembler2 == "hifiasm") ? (it.hifiasm_args ?: params.hifiasm_args) : - (assembler2 == "flye") ? (it.flye_args ?: params.flye_args) : - null, - polish: polish, - ont_collect: it.ont_collect ?: params.ont_collect, - ont_trim: it.ont_trim ?: params.ont_trim, - ont_adapters: it.ont_adapters ?: params.ont_adapters, - ont_fastplong_args: it.ont_fastplong_args ?: params.ont_fastplong_args, - jellyfish: it.jellyfish ?: params.jellyfish, - jellyfish_k: it.ont_jellyfish_k ?: params.jellyfish_k, - hifi_trim: it.hifi_trim ?: params.hifi_trim, - hifi_adapters: it.hifi_adapters ?: params.hifi_adapters, - hifi_fastplong_args: it.hifi_fastplong_args ?: params.hifi_fastplong_args, - medaka_model: it.medaka_model ?: params.medaka_model, - scaffold_longstitch: it.scaffold_longstitch ?: params.scaffold_longstitch, - scaffold_links: it.scaffold_links ?: params.scaffold_links, - scaffold_ragtag: it.scaffold_ragtag ?: params.scaffold_ragtag, - use_ref: it.use_ref ?: params.use_ref, - // not new - genome_size: it.genome_size ?: params.genome_size, - ref_fasta: it.ref_fasta ?: params.ref_fasta, - ref_gff: it.ref_gff ?: params.ref_gff, - flye_mode: it.flye_mode ?: params.flye_mode, - // assembly already provided? - assembly: it.assembly ?: params.assembly ?: null, - // ref mapping provided? - ref_map_bam: it.ref_map_bam ?: params.ref_map_bam ?: null, - // assembly mapping provided - assembly_map_bam: it.assembly_map_bam ?: params.ref_map_bam ?: null, - // reads for qc - qc_reads: ((it.qc_reads == "ont" || params.qc_reads == "ont") && ontreads) ? "ont" : "hifi", - qc_reads_path: ((it.qc_reads == "ont" || params.qc_reads == "ont") && ontreads) ? ontreads : hifireads, - quast: it.quast ?: params.quast, - busco: it.busco ?: params.busco, - busco_lineage: it.busco_lineage ?: params.busco_lineage, - busco_db: it.busco_db ?: params.busco_db, - meryl_k: it.meryl_k ?: params.meryl_k, - merqury: it.merqury ?: params.merqury, - lift_annotations: (it.ref_gff || params.ref_gff) ? (it.lift_annotations ?: params.lift_annotations) : false, - shortread_F: it.shortread_F ?: params.shortread_F, - shortread_R: it.shortread_R ?: params.shortread_R, - paired: it.paired ?: params.paired ?: ((it.shortread_F || params.shortread_F) && (it.shortread_R || params.shortread_R)) ? true : false, - // new: - use_short_reads: it.use_short_reads ?: params.use_short_reads ?: params.shortread_F ? true : (it.shortread_F ? true : false), - shortread_trim: it.shortread_trim ?: params.shortread_trim + meta: [ + id: it.sample, + // new in refactor-assemblies + group: it.group ?: null, + ontreads: ontreads, + hifireads: hifireads, + // new in refactor-assemblers + strategy: strategy, + // The "assembler" value is mainly to ease input, all actual workflow logic should use assembler_ont/2. + // Could still be useful for debugging. + assembler: assembler, + // assembler_ont: ONT, assembler_hifi: HiFi + assembler_ont: assembler_ont, + assembler_hifi: assembler_hifi, + assembly_scaffolding_order: it.assembly_scaffolding_order ?: params.assembly_scaffolding_order ?: "ont_on_hifi", + assembler_ont_args: it.assembler_ont_args ?: params.assembler_ont_args ?: + (assembler_ont == "hifiasm") ? (it.hifiasm_args ?: params.hifiasm_args) : + (assembler_ont == "flye") ? (it.flye_args ?: params.flye_args) : + null, + assembler_hifi_args: it.assembler_hifi_args ?: params.assembler_hifi_args ?: + (assembler_hifi == "hifiasm") ? (it.hifiasm_args ?: params.hifiasm_args) : + (assembler_hifi == "flye") ? (it.flye_args ?: params.flye_args) : + null, + polish: polish, + ont_collect: it.ont_collect ?: params.ont_collect, + ont_trim: it.ont_trim ?: params.ont_trim, + ont_adapters: it.ont_adapters ?: params.ont_adapters, + ont_fastplong_args: it.ont_fastplong_args ?: params.ont_fastplong_args, + jellyfish: it.jellyfish ?: params.jellyfish, + jellyfish_k: it.ont_jellyfish_k ?: params.jellyfish_k, + hifi_trim: it.hifi_trim ?: params.hifi_trim, + hifi_adapters: it.hifi_adapters ?: params.hifi_adapters, + hifi_fastplong_args: it.hifi_fastplong_args ?: params.hifi_fastplong_args, + medaka_model: it.medaka_model ?: params.medaka_model, + scaffold_longstitch: it.scaffold_longstitch ?: params.scaffold_longstitch, + scaffold_links: it.scaffold_links ?: params.scaffold_links, + scaffold_ragtag: it.scaffold_ragtag ?: params.scaffold_ragtag, + use_ref: it.use_ref ?: params.use_ref, + // not new + genome_size: it.genome_size ?: params.genome_size, + ref_fasta: it.ref_fasta ?: params.ref_fasta, + ref_gff: it.ref_gff ?: params.ref_gff, + flye_mode: it.flye_mode ?: params.flye_mode, + // assembly already provided? + assembly: it.assembly ?: params.assembly ?: null, + // ref mapping provided? + ref_map_bam: it.ref_map_bam ?: params.ref_map_bam ?: null, + // assembly mapping provided + assembly_map_bam: it.assembly_map_bam ?: params.ref_map_bam ?: null, + // reads for qc + qc_reads: ((it.qc_reads == "ont" || params.qc_reads == "ont") && ontreads) ? "ont" : "hifi", + qc_reads_path: ((it.qc_reads == "ont" || params.qc_reads == "ont") && ontreads) ? ontreads : hifireads, + quast: it.quast ?: params.quast, + busco: it.busco ?: params.busco, + busco_lineage: it.busco_lineage ?: params.busco_lineage, + busco_db: it.busco_db ?: params.busco_db, + meryl_k: it.meryl_k ?: params.meryl_k, + merqury: it.merqury ?: params.merqury, + lift_annotations: (it.ref_gff || params.ref_gff) ? (it.lift_annotations ?: params.lift_annotations) : false, + shortread_F: it.shortread_F ?: params.shortread_F, + shortread_R: it.shortread_R ?: params.shortread_R, + paired: it.paired ?: params.paired ?: ((it.shortread_F || params.shortread_F) && (it.shortread_R || params.shortread_R)) ? true : false, + // new: + use_short_reads: it.use_short_reads ?: params.use_short_reads ?: params.shortread_F ? true : (it.shortread_F ? true : false), + shortread_trim: it.shortread_trim ?: params.shortread_trim ] + ] } .set { ch_samplesheet } @@ -218,46 +228,46 @@ workflow PIPELINE_INITIALISATION { .map { it -> [ - // Check if assembler1 was set - (it.ontreads && !it.assembler1 && !it.assembly) + // Check if assembler_ont was set + (it.meta.ontreads && !it.meta.assembler_ont && !it.meta.assembly) ? ( - // Check if assembler2 was set - (it.hifireads && it.assembler2 && it.stragegy == "single") + // Check if assembler_hifi was set + (it.meta.hifireads && it.meta.assembler_hifi && it.meta.stragegy == "single") ? null : [ - println("Please confirm samplesheet: [sample: $it.meta.id]: assembler1 could not be set and no assembly was provided."), + println("Please confirm samplesheet: [sample: $it.meta.id]: assembler_ont could not be set and no assembly was provided."), "invalid" ] ) : null, - // Check if assembler2 was set - (it.hifireads && !it.assembler2 && !it.assembly) + // Check if assembler_hifi was set + (it.meta.hifireads && !it.meta.assembler_hifi && !it.meta.assembly) ? ( - // Check if assembler1 was set - (it.ontreads && it.assembler1 && it.stragegy == "single") + // Check if assembler_ont was set + (it.meta.ontreads && it.meta.assembler_ont && it.meta.stragegy == "single") ? null : [ - println("Please confirm samplesheet: [sample: $it.meta.id]: assembler2 could not be set and no assembly was provided."), + println("Please confirm samplesheet: [sample: $it.meta.id]: assembler_hifi could not be set and no assembly was provided."), "invalid" ] ) : null, // Check if reads and strategy match - (it.strategy == "single" && it.ontreads && it.hifireads) + (it.meta.strategy == "single" && it.meta.ontreads && it.meta.hifireads) ? [ - println("Please confirm samplesheet: [sample: $it.meta.id]: Strategy is $it.strategy, but both types of reads are provided."), + println("Please confirm samplesheet: [sample: $it.meta.id]: Strategy is $it.meta.strategy, but both types of reads are provided."), "invalid" ] : null, // Check if assembler can do hybrid - (it.strategy == "hybrid" && !hybrid_assemblers.contains(it.assembler1)) + (it.meta.strategy == "hybrid" && !hybrid_assemblers.contains(it.meta.assembler_ont)) ? [ println("Please confirm samplesheet: [sample: $it.meta.id]: Hybrid assembly can only be performed with $hybrid_assemblers"), @@ -265,7 +275,7 @@ workflow PIPELINE_INITIALISATION { ] : null, // Check if qc reads are specified for hybrid assemblies - (it.strategy == "hybrid" && !params.qc_reads) + (it.meta.strategy == "hybrid" && !it.meta.qc_reads) ? [ println("Please confirm samplesheet: [sample: $it.meta.id]: Please specify which reads should be used for qc: '--qc_reads': 'ont' or 'hifi'"), @@ -273,7 +283,7 @@ workflow PIPELINE_INITIALISATION { ] : null, // Check if genome_size is given with --scaffold_longstitch - (it.scaffold_longstitch && !it.genome_size && !(params.jellyfish || it.jellyfish)) + (it.meta.scaffold_longstitch && !it.meta.genome_size && !it.meta.jellyfish) ? [ println("Please confirm samplesheet: [sample: $it.meta.id]: scaffolding with longstitch requires genome-size. Either provide genome-size estimate, or estimate from reads with --jellyfish"), diff --git a/workflows/genomeassembler.nf b/workflows/genomeassembler.nf index b9864de1..7e640091 100644 --- a/workflows/genomeassembler.nf +++ b/workflows/genomeassembler.nf @@ -52,49 +52,51 @@ workflow GENOMEASSEMBLER { The keys are defined in ./subworkflows/local/utils_nfcore_genomeassembler/main.nf - meta: [id: string], - ontreads: path, - hifireads: path, - strategy: string, - assembler1: string, - assembler2: string, - scaffolding: string, - genome_size: integer, - assembler1_args: string, - assembler2_args: string, - ref_fasta: path, - ref_gff: path, - shortread_F: path, - shortread_R: path, - paired: bool - ont_collect: bool, - ont_trim: bool, - ont_jellyfish: bool, - hifi_trim: bool, - hifi_primers: path, - polish_medaka: bool, - medaka_model: string, - polish_pilon: bool, - scaffold_longstitch: bool, - scaffold_links: bool, - scaffold_ragtag: bool, - use_ref: bool, - flye_mode: string, - assembly: path, - ref_map_bam: path, - assembly_map_bam: path, - qc_reads: string ["ont","hifi"], - qc_reads_path: path, - quast: bool, - busco: bool, - busco_lineage: string, - busco_db: path, - lift_annotations: bool, - shortread_F: path, - shortread_R: path, - paired: bool, - use_short_reads: bool, - shortread_trim: bool + meta: [ + id: string, + ontreads: path, + hifireads: path, + strategy: string, + assembler_ont: string, + assembler_hifi: string, + scaffolding: string, + genome_size: integer, + assembler_ont_args: string, + assembler_hifi_args: string, + ref_fasta: path, + ref_gff: path, + shortread_F: path, + shortread_R: path, + paired: bool + ont_collect: bool, + ont_trim: bool, + ont_jellyfish: bool, + hifi_trim: bool, + hifi_primers: path, + polish_medaka: bool, + medaka_model: string, + polish_pilon: bool, + scaffold_longstitch: bool, + scaffold_links: bool, + scaffold_ragtag: bool, + use_ref: bool, + flye_mode: string, + assembly: path, + ref_map_bam: path, + assembly_map_bam: path, + qc_reads: string ["ont","hifi"], + qc_reads_path: path, + quast: bool, + busco: bool, + busco_lineage: string, + busco_db: path, + lift_annotations: bool, + shortread_F: path, + shortread_R: path, + paired: bool, + use_short_reads: bool, + shortread_trim: bool + ] @@ -199,8 +201,8 @@ workflow GENOMEASSEMBLER { ch_main_assembled .branch { it -> - polish: it.polish_medaka || it.polish_pilon - no_polish: !it.polish_medaka && !it.polish_pilon + polish: it.meta.polish_medaka || it.meta.polish_pilon + no_polish: !it.meta.polish_medaka && !it.meta.polish_pilon } .set { ch_main_assembled } @@ -212,10 +214,12 @@ workflow GENOMEASSEMBLER { ch_versions = ch_versions.mix(POLISH.out.versions) + // Update scaffold for meta map + ch_main_polished .branch { it -> - scaffold: it.scaffold_links || it.scaffold_longstitch || it.scaffold_ragtag - no_scaffold: !it.scaffold_links && !it.scaffold_longstitch && !it.scaffold_ragtag + scaffold: it.meta.scaffold_links || it.meta.scaffold_longstitch || it.meta.scaffold_ragtag + no_scaffold: !it.meta.scaffold_links && !it.meta.scaffold_longstitch && !it.meta.scaffold_ragtag } .set { ch_main_polished From e9322ee2cb3170c9a962389f82ffbfdb24f7a589 Mon Sep 17 00:00:00 2001 From: Niklas Schandry Date: Tue, 27 Jan 2026 13:34:11 +0100 Subject: [PATCH 103/162] use csi index, add global param csi_index_size, reported via slack by @afonsoguerra --- conf/modules/QC/alignments.config | 5 +++++ nextflow.config | 9 +++++---- nextflow_schema.json | 4 ++++ subworkflows/local/bam_sort_stat/main.nf | 4 ++-- 4 files changed, 16 insertions(+), 6 deletions(-) diff --git a/conf/modules/QC/alignments.config b/conf/modules/QC/alignments.config index e14f103d..0829d311 100644 --- a/conf/modules/QC/alignments.config +++ b/conf/modules/QC/alignments.config @@ -78,4 +78,9 @@ process { (meta.qc_reads == 'ont' ? "-ax lr:hq" : "-ax map-hifi") } } + withName: '.*SAMTOOLS:.*INDEX.*' { + ext.args = { + "-c -m ${params.csi_index_size}" + } + } } diff --git a/nextflow.config b/nextflow.config index 9a36930c..1fdc7e18 100644 --- a/nextflow.config +++ b/nextflow.config @@ -42,13 +42,14 @@ params { // Pipeline params input = '' // input file outdir = null // outdir + csi_index_size = 14 // Assembly strategy strategy = "single" // assembly_strategy assembler = null - assembler_ont = null - assembler_hifi = null - assembler_ont_args = '' - assembler_hifi_args = '' + assembler_ont = null + assembler_hifi = null + assembler_ont_args = '' + assembler_hifi_args = '' flye_mode = '--nano-hq' // == Read QC and trimming == // -- ONT diff --git a/nextflow_schema.json b/nextflow_schema.json index ff434686..1acdeba3 100644 --- a/nextflow_schema.json +++ b/nextflow_schema.json @@ -377,6 +377,10 @@ "assembly_map_bam": { "type": "string", "description": "A mapping (bam) of reads mapped to the provided assembly can be specified for QC. If provided, alignment to the provided assembly fasta will not run" + }, + "csi_index_size": { + "type": "integer", + "description": "Index size to use for csi index (default: 14), creating and index of size 2^csi_index_size. See samtools index documentation for details. " } } }, diff --git a/subworkflows/local/bam_sort_stat/main.nf b/subworkflows/local/bam_sort_stat/main.nf index eebbe1f1..7bb45342 100644 --- a/subworkflows/local/bam_sort_stat/main.nf +++ b/subworkflows/local/bam_sort_stat/main.nf @@ -21,12 +21,12 @@ workflow BAM_INDEX_STATS_SAMTOOLS { SAMTOOLS_INDEX(bam) - BAM_STATS_SAMTOOLS(bam.join(SAMTOOLS_INDEX.out.bai, by: [0]), fasta) + BAM_STATS_SAMTOOLS(bam.join(SAMTOOLS_INDEX.out.csi, by: [0]), fasta) versions = ch_versions.mix(SAMTOOLS_INDEX.out.versions).mix(BAM_STATS_SAMTOOLS.out.versions) emit: - bai = SAMTOOLS_INDEX.out.bai // channel: [ val(meta), [ bai ] ] + bai = SAMTOOLS_INDEX.out.csi // channel: [ val(meta), [ csi ] ] stats = BAM_STATS_SAMTOOLS.out.stats // channel: [ val(meta), [ stats ] ] flagstat = BAM_STATS_SAMTOOLS.out.flagstat // channel: [ val(meta), [ flagstat ] ] idxstats = BAM_STATS_SAMTOOLS.out.idxstats // channel: [ val(meta), [ idxstats ] ] From 465b888ba9dc6bd27d1f348dd26f42817fa185e4 Mon Sep 17 00:00:00 2001 From: Niklas Schandry Date: Wed, 28 Jan 2026 14:54:25 +0100 Subject: [PATCH 104/162] finish pipeline refactor towards meta-stuffing --- conf/modules/assembly.config | 11 +- conf/modules/hifi-prep.config | 1 - conf/modules/ont-prep.config | 1 - docs/output.md | 6 +- docs/usage.md | 38 +++--- nextflow.config | 2 - nextflow_schema.json | 8 -- subworkflows/local/assemble/main.nf | 26 ++-- subworkflows/local/jellyfish/main.nf | 7 +- .../polishing/medaka/polish_medaka/main.nf | 2 +- .../polishing/pilon/polish_pilon/main.nf | 2 +- subworkflows/local/prepare/jellyfish/main.nf | 15 ++- subworkflows/local/prepare/main.nf | 71 +---------- .../local/prepare/prepare_hifi/main.nf | 8 +- .../local/prepare/prepare_ont/collect/main.nf | 12 +- .../local/prepare/prepare_ont/main.nf | 14 +- .../local/prepare/prepare_shortreads/main.nf | 18 +-- subworkflows/local/qc/busco/main.nf | 2 +- subworkflows/local/qc/main.nf | 21 ++- subworkflows/local/qc/merqury/main.nf | 33 ----- subworkflows/local/scaffolding/main.nf | 120 ++++++++++++++---- .../main.nf | 35 ++--- workflows/genomeassembler.nf | 2 + 23 files changed, 210 insertions(+), 245 deletions(-) delete mode 100644 subworkflows/local/qc/merqury/main.nf diff --git a/conf/modules/assembly.config b/conf/modules/assembly.config index b2c964b2..b5530fb5 100644 --- a/conf/modules/assembly.config +++ b/conf/modules/assembly.config @@ -3,7 +3,12 @@ process { ext.args = { [ meta.genome_size ? "--genome-size ${meta.genome_size}" : '', - meta.flye_args + // If flye is both for ONT and HiFi, we need to detect the type of read based on the read suffix (fastplong) + // This is presumably prone to fail in certain weird situations + (meta.assembler_ont == "flye" && meta.assembler_hifi == "flye" && strategy == "scaffold") ? + (reads ==~ ".*${meta.id}_ont.*" ? meta.assembler_ont_args : meta.assembler_hifi_args) : + (meta.assembler_ont == "flye") ? meta.assembler_ont_args : "", + (meta.assembler_hifi == "flye") ? meta.assembler_hifi_args : "", ].join(" ").trim() } publishDir = [ @@ -13,7 +18,7 @@ process { ] } withName: HIFIASM { - ext.args = { [ meta.hifiasm_args ].join(" ").trim() } + ext.args = { [ meta.assembler_hifi_args ].join(" ").trim() } publishDir = [ path: { "${params.outdir}/${meta.id}/assembly/hifiasm/" }, mode: params.publish_dir_mode, @@ -21,7 +26,7 @@ process { ] } withName: HIFIASM_ONT { - ext.args = { [ meta.hifiasm_args, "--ont" ].join(" ").trim() } + ext.args = { [ meta.assembler_ont_args, "--ont" ].join(" ").trim() } publishDir = [ path: { "${params.outdir}/${meta.id}/assembly/hifiasm_ont/" }, mode: params.publish_dir_mode, diff --git a/conf/modules/hifi-prep.config b/conf/modules/hifi-prep.config index a65796ed..7e0f66a8 100644 --- a/conf/modules/hifi-prep.config +++ b/conf/modules/hifi-prep.config @@ -7,7 +7,6 @@ process { ] ext.args = { [ - meta.trim ? "-A" : '', meta.hifi_fastplong_args ].join(" ").trim() } diff --git a/conf/modules/ont-prep.config b/conf/modules/ont-prep.config index e92e67c3..940459f6 100644 --- a/conf/modules/ont-prep.config +++ b/conf/modules/ont-prep.config @@ -14,7 +14,6 @@ process { ] ext.args = { [ - meta.trim ? "-A" : '', meta.ont_fastplong_args ].join(" ").trim() } diff --git a/docs/output.md b/docs/output.md index cde1f375..b01a3061 100644 --- a/docs/output.md +++ b/docs/output.md @@ -255,19 +255,19 @@ The files in the alignment folder have the following base name structure: - `QC/` - `alignments/`: alignments to assemblies - `_.bam` Alignment - - `_.bai` bam index file + - `_.csi` bam index file - `_.stats` comprehensive statistics from alignment file - `_.idxstats` alignment summary statistics - `_.flagstat` number of alignments for each FLAG type - `shortreads/`: folder containing short read mapping for pilon - `_shortreads.bam` Alignment - - `_shortreads.bai` bam index file + - `_shortreads.csi` bam index file - `_shortreads.stats` comprehensive statistics from alignment file - `_shortreads.idxstats` alignment summary statistics - `_shortreads.flagstat` number of alignments for each FLAG type - `reference/`: folder containing alignment of long reads to reference - `_to_reference.bam` Alignment - - `_to_reference.bai` bam index file + - `_to_reference.csi` bam index file - `_to_reference.stats` comprehensive statistics from alignment file - `_to_reference.idxstats` alignment summary statistics - `_to_reference.flagstat` number of alignments for each FLAG type diff --git a/docs/usage.md b/docs/usage.md index 300c4029..d9e5b154 100644 --- a/docs/usage.md +++ b/docs/usage.md @@ -60,7 +60,7 @@ Assembly strategy is controlled via `strategy` (either pipeline parameter or sam Assembler specific arguments can be provided for the assembler via `hifiasm_args` or `flye_args`, or with more fine-grained control via `assembler_ont_args` and `assembler_hifi_args` for scaffolding. `assembler_ont_args` controls the parameters for the assembler in `single` and `hybrid` strategies, or for the assembler used for ONT reads when using `scaffold`. `assembler_hifi_args` can be used to pass arguments to the assembler used for HiFi reads in `scaffold` mode. -`assembler[1,2]_args` can only be set via the samplesheet and are not available as global pipeline parameters. +`assembler_[ont,hifi]_args` can only be set via the samplesheet and are not available as global pipeline parameters. ## Samplesheet input @@ -284,34 +284,38 @@ Options controlling pipeline behavior Options controlling assembly -| Parameter | Description | Type | -| ------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------- | -| `strategy` | Assembly strategy to use. Valid choices are `'single'`, `'hybrid'` and `'scaffold'` | `string` | -| `assembler` | Assembler to use. Valid choices depend on strategy; for single either `flye` or `hifiasm`, hybrid can be done with `hifiasm` and for scaffolded assembly provide the names of the assemblers separated with an underscore. The first assembler will | -| be used for ONT reads, the second for HiFi reads. | `string` | -| `assembly_scaffolding_order` | When strategy is "scaffold", which assembly should be scaffolded onto which? | `string` | -| `genome_size` | expected genome size, optional | `string` | -| `flye_mode` | flye mode | `string` | -| `flye_args` | additional args for flye | `string` | -| `hifiasm_args` | Extra arguments passed to `hifiasm` | `string` | -| `assembler_ont_args` | Extra arguments passed to assembler_ont; assembling ONT reads in `scaffold` strategy | `string` | -| `assembler_hifi_args` | Extra arguments passed to assembler_hifi; assembling HiFi reads in `scaffold` strategy | `string` | +| Parameter | Description | Type | +| ------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------- | +| `strategy` | Assembly strategy to use. Valid choices are `'single'`, `'hybrid'` and `'scaffold'` | `string` | +| `assembler` | Assembler to use. Valid choices depend on strategy; for single either `flye` or `hifiasm`, hybrid can be done with `hifiasm` and for scaffolded assembly provide the names of the assemblers separated with an underscore. The first assembler will | +| be used for ONT reads, the second for HiFi reads. | `string` | +| `assembler_ont` | Assembler to use for ONT reads. Often determined automatically, but required for complex runs, where both ONT and HiFi reads are provided, but some assemblies should be done using `strategy: "single"` using only ONT reads. Such cases can not unambiguously be resolved otherwise. | `string` | +| `assembler_hifi` | Assembler to use for HiFi reads. Often determined automatically, but required for complex runs, where both ONT and HiFi reads are provided, but some assemblies should be done using `strategy: "single"` using only HiFi reads. Such cases can not unambiguously be resolved otherwise. | `string` | +| `assembly_scaffolding_order` | When strategy is "scaffold", which assembly should be scaffolded onto which? | `string` | +| `genome_size` | expected genome size, optional | `string` | +| `flye_mode` | flye mode | `string` | +| `flye_args` | additional args for flye | `string` | +| `hifiasm_args` | Extra arguments passed to `hifiasm` | `string` | +| `assembler_ont_args` | Extra arguments passed to assembler_ont; assembling ONT reads in `scaffold` strategy | `string` | +| `assembler_hifi_args` | Extra arguments passed to assembler_hifi; assembling HiFi reads in `scaffold` strategy | `string` | ## Long-read preprocessing +All long-reads will be passed to `fastplong` for trimming and quality control. +If reads should not be modified by `fastplong`, adaptor trimming can be disabled using `-A`, quality filtering can be disabled with `-Q`. +These arguments can be passed via `_fastplot_args` for the different read types. + | Parameter | Description | Type | -| --------------------- | --------------------------------------------------------- | --------- | --- | +| --------------------- | --------------------------------------------------------- | --------- | | `ontreads` | Path to ONT reads | `string` | | `ont_collect` | Collect ONT reads from several files? | `boolean` | -| `ont_trim` | Trim ont reads with `fastplong`? | `boolean` | | `ont_adapters` | Adaptors for ONT read-trimming | `string` | | `ont_fastplong_args` | Additional args to be passed to `fastplong` for ONT reads | `string` | | `hifireads` | Path to HiFi reads | `string` | -| `hifi_trim` | Trim HiFi reads with `fastplong` | `boolean` | | `hifi_adapters` | Adaptors for HiFi read-trimming | `string` | | `hifi_fastplong_args` | Additional args to be passed to fastplong for HiFi reads | `string` | | `jellyfish` | Run jellyfish and genomescope (recommended) | `boolean` | -| `jellyfish_k` | Value of k used during k-mer analysis with jellyfish | `integer` | 21 | +| `jellyfish_k` | Value of k used during k-mer analysis with jellyfish | `integer` | | `dump` | dump jellyfish output | `boolean` | ## Short read options diff --git a/nextflow.config b/nextflow.config index 1fdc7e18..6b806e0e 100644 --- a/nextflow.config +++ b/nextflow.config @@ -55,7 +55,6 @@ params { // -- ONT ontreads = null ont_collect = false // collect ONT reads into a single file - ont_trim = false /// run fastplong trimming on ONT reads? ont_adapters = [] // list of adapters for fastplong ont_fastplong_args = '' // args for fastplong // -- Jellyfish (qc reads) -- @@ -64,7 +63,6 @@ params { // -- HiFi -- hifireads = null hifi_adapters = [] - hifi_trim = false // run fastplong trimming on HiFi reads? hifi_fastplong_args = '' // args for fastplong // -- Short read -- use_short_reads = false // short reads available? diff --git a/nextflow_schema.json b/nextflow_schema.json index 1acdeba3..778345f6 100644 --- a/nextflow_schema.json +++ b/nextflow_schema.json @@ -247,10 +247,6 @@ "type": "boolean", "description": "Collect ONT reads from several files?" }, - "ont_trim": { - "type": "boolean", - "description": "Trim ont reads with fastplong?" - }, "ont_adapters": { "type": "string", "default": "[]", @@ -265,10 +261,6 @@ "type": "string", "description": "Path to HiFi reads" }, - "hifi_trim": { - "type": "boolean", - "description": "Trim HiFi reads with fastplonng" - }, "hifi_adapters": { "type": "string", "default": "[]", diff --git a/subworkflows/local/assemble/main.nf b/subworkflows/local/assemble/main.nf index 038f0541..ade089bc 100644 --- a/subworkflows/local/assemble/main.nf +++ b/subworkflows/local/assemble/main.nf @@ -23,6 +23,7 @@ workflow ASSEMBLE { Samples are split into those that need assembly, and those that will not be assembled (i.e. assemblies are provided) */ ch_main.dump(tag: "Assemble - Inputs") + ch_main .branch { it -> @@ -54,6 +55,7 @@ workflow ASSEMBLE { .dump(tag: "Assemble: Branched: Single") ch_main_assemble_branched .hybrid + //.view {"Assemble: Hybrid: $it"} .dump(tag: "Assemble: Branched: Hybrid") ch_main_assemble_branched .scaffold @@ -137,6 +139,7 @@ workflow ASSEMBLE { - Hybrid assembly - Scaffold samples where assembler_hifi (hifi assembler) is hifiasm */ + //ch_main_assemble_branched.hybrid.view {"ASSEMBLE: Branched: Hybrid"} ch_main_assemble_branched .single .filter { @@ -146,7 +149,7 @@ workflow ASSEMBLE { ch_main_assemble_branched .hybrid .filter { - it -> it.meta.assembler_hifi == "hifiasm" + it -> it.meta.assembler_ont == "hifiasm" } ) .mix(ch_main_assemble_branched @@ -154,22 +157,23 @@ workflow ASSEMBLE { .filter { it -> it.meta.assembler_hifi == "hifiasm" } - // the samples for scaffolding should not have ONT reads, otherwise hifiasm will run in --ul mode - .map { it -> [meta: it.meta - it.meta.subMap("ontreads")] } ) - .set { ch_main_assemble_hifi_hifiasm } - - ch_main_assemble_hifi_hifiasm.dump(tag: "Assemble: hifiasm HIFI inputs") - - HIFIASM(ch_main_assemble_hifi_hifiasm - .map { + .map { it -> [ it.meta, it.meta.hifireads, // for hybrid samples include ONT reads in 3rd slot of first input (see hifiasm module) (it.meta.strategy == "hybrid" && it.meta.ontreads) ? it.meta.ontreads : [] ] - }, + } + .set { ch_main_assemble_hifi_hifiasm } + + ch_main_assemble_hifi_hifiasm.dump(tag: "Assemble: hifiasm HIFI inputs") + + + //ch_main_assemble_hifi_hifiasm.view { "Assemble: hifiasm HIFI inputs: $it" } + + HIFIASM(ch_main_assemble_hifi_hifiasm, [[], [], []], [[], [], []], [[], []]) @@ -501,7 +505,7 @@ workflow ASSEMBLE { .to_map .map { it -> - [ [it.meta], it.meta.qc_reads_path, it.meta.ref_fasta ] + [ it.meta, it.meta.qc_reads_path, it.meta.ref_fasta ] } .set { map_to_ref_in } diff --git a/subworkflows/local/jellyfish/main.nf b/subworkflows/local/jellyfish/main.nf index c5c063c7..ff7cbcfb 100644 --- a/subworkflows/local/jellyfish/main.nf +++ b/subworkflows/local/jellyfish/main.nf @@ -16,7 +16,7 @@ workflow JELLYFISH { it -> [ meta: it.meta, - reads: it.ontreads + reads: it.meta.ontreads ] } .set { samples } @@ -25,11 +25,6 @@ workflow JELLYFISH { ch_versions = ch_versions.mix(COUNT.out.versions) - if (params.dump) { - DUMP(kmers) - ch_versions = ch_versions.mix(DUMP.out.versions) - } - HISTO(kmers) ch_versions = ch_versions.mix(HISTO.out.versions) diff --git a/subworkflows/local/polishing/medaka/polish_medaka/main.nf b/subworkflows/local/polishing/medaka/polish_medaka/main.nf index 75eb45b8..5ba42f0a 100644 --- a/subworkflows/local/polishing/medaka/polish_medaka/main.nf +++ b/subworkflows/local/polishing/medaka/polish_medaka/main.nf @@ -48,7 +48,7 @@ workflow POLISH_MEDAKA { ch_medaka_out .filter { - it -> it.lift_annotations + it -> it.meta.lift_annotations } .map { it -> [ diff --git a/subworkflows/local/polishing/pilon/polish_pilon/main.nf b/subworkflows/local/polishing/pilon/polish_pilon/main.nf index 8547f252..10329bc1 100644 --- a/subworkflows/local/polishing/pilon/polish_pilon/main.nf +++ b/subworkflows/local/polishing/pilon/polish_pilon/main.nf @@ -49,7 +49,7 @@ workflow POLISH_PILON { ch_main .filter { - it -> it.lift_annotations + it -> it.meta.lift_annotations } .map { it -> [ diff --git a/subworkflows/local/prepare/jellyfish/main.nf b/subworkflows/local/prepare/jellyfish/main.nf index 61ee849a..a079f19a 100644 --- a/subworkflows/local/prepare/jellyfish/main.nf +++ b/subworkflows/local/prepare/jellyfish/main.nf @@ -29,15 +29,15 @@ workflow JELLYFISH { meta: [ id: it[1], metas: it[0], - jellyfish_k: it[2].unique()[0], - qc_read_mean: it[4].unique()[0] + jellyfish_k: it[2][0], + qc_read_mean: it[4][0] ], - qc_reads_path: it[3].unique()[0] + qc_reads_path: it[3][0] ] } .mix( ch_main - .filter { it -> !it.group } + .filter { it -> !it.meta.group } .map { it -> [ @@ -60,9 +60,10 @@ workflow JELLYFISH { .map { meta, hist -> [ meta, + hist, meta.jellyfish_k, - meta.qc_read_mean, - hist + meta.qc_read_mean + ] } .set { genomescope_in } @@ -82,7 +83,7 @@ workflow JELLYFISH { .collect { meta -> [ meta: meta + [ genome_size: it[1] ] ] } } .mix(GENOMESCOPE.out.estimated_hap_len - .filter { it -> !it[0].ids } + .filter { it -> !it[0].metas } .map { it -> [ meta: it[0] + [ genome_size: it[1] ] ] } diff --git a/subworkflows/local/prepare/main.nf b/subworkflows/local/prepare/main.nf index ecaa5184..4eb37d5c 100644 --- a/subworkflows/local/prepare/main.nf +++ b/subworkflows/local/prepare/main.nf @@ -4,67 +4,6 @@ include { PREPARE_SHORTREADS as SHORTREADS } from './prepare_shortreads/main' include { JELLYFISH } from './jellyfish/main' workflow PREPARE { - /* - Grouped preparations - - Generally, I expect that a group will contain the same set of input. - To reduce redundant work on the inputs that belong one group, in all - prepare_* subworkflows groups will be used as meta.id, if a group is - set. After the preparations are done, results are joined back to all - members of the group. This needs to account for sample level setting - of additional args. For preparation no arg can be set at the sample- - level, so here everything group only. - - The pattern for grouping/ungrouping and mixing samples is: - - Grouping: - channel_grouped is a map that contains at least meta, group and path - within one group the path is expected to be the same for all members - - channel_grouped - .filter { it -> it.group } - .map { it -> [it.meta, it.group, it.path] } - .groupTuple(by: 1) - .map { - it -> - [ - [id: it[1], ids: it[0].id.collect().join("+")], - it[2].unique()[0] - ] - } - .mix( - groups - .filter { it -> !it.group } - .map { - it -> [ it.meta, it.path ] - } - ) - .set { collected_groups } - - This produces one channel that contains meta and path ready to go in - a process. - - For a process that again returns [meta, path] split group in samples - and merge with ungrouped samples: - - PROCESS(collected_groups) - - PROCESS.out - .filter { it -> it[0].ids } - .flatMap { it -> - it[0].ids - .tokenize("+") - .collect { sample -> [ meta: [ id: sample ], path: it[1] ] } - } - .mix(PROCESS.out - .filter { it -> !it[0].ids } - .map { - it -> [ meta: [ id: it[0].id ], path: it[1] ] - } - ) - .set { process_output } - */ - take: ch_main main: @@ -119,7 +58,7 @@ workflow PREPARE { ch_main_shortreaded // ADD ONT READS .filter { - it -> it.ontreads ? true : false + it -> it.meta.ontreads ? true : false } .map { it -> [it.meta.id, it.meta - it.meta.subMap("ontreads")]} .join( @@ -146,7 +85,7 @@ workflow PREPARE { ch_main_sr_ont .filter { - it -> it.hifireads ? true : false + it -> it.meta.hifireads ? true : false } .map { it -> [it.meta.id, it.meta - it.meta.subMap("hifireads")]} .join( @@ -162,13 +101,13 @@ workflow PREPARE { // mix back in those samples where nothing was done to the hifireads reads .mix(ch_main_sr_ont .filter { - it -> it.hifireads ? false : true + it -> it.meta.hifireads ? false : true } ) .set { ch_main_prepared } - + //ch_main_prepared.view {"CH_MAIN_PREPARED: $it"} // Get average read length of the QC reads from fastplong json report def slurp = new groovy.json.JsonSlurper() @@ -189,7 +128,7 @@ workflow PREPARE { } .mix( ch_main_prepared - .filter { it -> it.qc_reads.toLowerCase() == "hifi" } + .filter { it -> it.meta.qc_reads.toLowerCase() == "hifi" } .map { it -> [ it.meta.id, it.meta - it.meta.subMap("fastplong_json")]} diff --git a/subworkflows/local/prepare/prepare_hifi/main.nf b/subworkflows/local/prepare/prepare_hifi/main.nf index ffc1700d..8a652f6f 100644 --- a/subworkflows/local/prepare/prepare_hifi/main.nf +++ b/subworkflows/local/prepare/prepare_hifi/main.nf @@ -55,12 +55,12 @@ workflow PREPARE_HIFI { .filter { it -> it[0].metas } .flatMap { it -> // it looks like [meta, output_path] it[0].metas - .collect { meta -> [ meta: meta.clone() + [hifireads: it[1]] ] } + .collect { metas -> [ meta: metas + [hifireads: it[1]] ] } } .mix(FASTPLONG_HIFI.out.reads .filter { it -> !it[0].metas } .map { - it -> [ meta: it[0].clone() + [ hifireads: it[1] ] ] + meta, hifireads -> [ meta: meta + [ hifireads: hifireads ] ] } ) .set { fastplong_reads_out } @@ -71,10 +71,10 @@ workflow PREPARE_HIFI { .filter { it -> it[0].metas } .flatMap { it -> it[0].metas - .collect { meta -> [ meta.clone(), it[1] ] } + .collect { meta -> [ meta, it[1] ] } } .mix(FASTPLONG_HIFI.out.json - .filter { it -> !it[0].ids } + .filter { it -> !it[0].metas } ) .set { fastplong_json_out } diff --git a/subworkflows/local/prepare/prepare_ont/collect/main.nf b/subworkflows/local/prepare/prepare_ont/collect/main.nf index 5fa5da24..964b90dc 100644 --- a/subworkflows/local/prepare/prepare_ont/collect/main.nf +++ b/subworkflows/local/prepare/prepare_ont/collect/main.nf @@ -11,7 +11,7 @@ workflow COLLECT { .filter { it -> it.ont_collect } - .map { row -> [row.meta, row.ontreads] } + .map { row -> [row.meta, row.meta.ontreads] } .set { reads } COLLECT_READS(reads) @@ -20,16 +20,6 @@ workflow COLLECT { versions = ch_versions - ch_input - .map { it -> it - it.submap('ontreads') } - .map { it -> it.collect { entry -> [ entry.value, entry ] } } - .join(reads - .map { it -> [meta: it[0], ontreads:it[1]] } - .map { it -> it.collect { entry -> [ entry.value, entry ] } } - ) - .map { it -> it.collect { _entry, map -> [ (map.key): map.value ] }.collectEntries() } - .set { reads } - emit: reads versions diff --git a/subworkflows/local/prepare/prepare_ont/main.nf b/subworkflows/local/prepare/prepare_ont/main.nf index 12bcccf8..af0179ce 100644 --- a/subworkflows/local/prepare/prepare_ont/main.nf +++ b/subworkflows/local/prepare/prepare_ont/main.nf @@ -53,7 +53,7 @@ workflow PREPARE_ONT { } .mix( COLLECT.out.reads - .filter { it -> !it[0].ids } + .filter { it -> !it[0].metas } .map { it -> [ meta: it[0] + [ontreads: it[1]] ] } @@ -70,7 +70,7 @@ workflow PREPARE_ONT { // ch_collected is the same samples as the input channel ch_collected - .filter { it -> it.group } + .filter { it -> it.meta.group } .map { it -> [it.meta, it.meta.group, it.meta.ont_trim, it.meta.ontreads, it.meta.ont_adaptors, it.meta.ont_fastplong_args] } .groupTuple(by: 1) .map { @@ -88,7 +88,7 @@ workflow PREPARE_ONT { } .mix( ch_collected - .filter { it -> !it.group } + .filter { it -> !it.meta.group } .map { it -> [ @@ -113,12 +113,12 @@ workflow PREPARE_ONT { .filter { it -> it[0].metas } .flatMap { it -> // it looks like [meta, output_path] it[0].metas - .collect { meta -> [ meta: meta + [ontreads: it[1]] ] } + .collect { metas -> [ meta: metas + [ ontreads: it[1] ] ] } } .mix(FASTPLONG_ONT.out.reads - .filter { it -> !it[0].ids } + .filter { it -> !it[0].metas } .map { - it -> [ meta: [ id: it[0] ] + [ ontreads: it[1] ] ] + it -> [ meta: it[0] + [ ontreads: it[1] ] ] } ) .set { fastplong_reads_out } @@ -129,7 +129,7 @@ workflow PREPARE_ONT { .filter { it -> it[0].metas } .flatMap { it -> // it looks like [meta, output_path] it[0].metas - .collect { meta -> [ meta, + it[1] ] } + .collect { metas -> [metas, it[1] ] } } .mix( FASTPLONG_ONT.out.json diff --git a/subworkflows/local/prepare/prepare_shortreads/main.nf b/subworkflows/local/prepare/prepare_shortreads/main.nf index aa1ff301..0518d05e 100644 --- a/subworkflows/local/prepare/prepare_shortreads/main.nf +++ b/subworkflows/local/prepare/prepare_shortreads/main.nf @@ -44,7 +44,7 @@ workflow PREPARE_SHORTREADS { ] } .mix(shortreads.trim - .filter { it -> !it.group } + .filter { it -> !it.meta.group } .map { it -> [ it.meta, it.meta.shortreads, [] ] } @@ -63,7 +63,7 @@ workflow PREPARE_SHORTREADS { } .mix( FASTP.out.reads - .filter { it -> !it[0].ids } + .filter { it -> !it[0].metas } .map { it -> [ meta: it[0] + [ shortreads: it[1] ] ] } ) .set { trimmed_reads } @@ -87,13 +87,13 @@ workflow PREPARE_SHORTREADS { .map { it -> [ meta: [ id: it[1], metas: it[0] ], - shortreads: it[2].unique()[0], - meryl_k: it[3].unique()[0] + shortreads: it[2][0], + meryl_k: it[3][0] ] } .mix(shortreads - .filter { it -> it.merqury } - .filter { it -> !it.group } + .filter { it -> it.meta.merqury } + .filter { it -> !it.meta.group } .map { it -> [meta: it.meta, shortreads: it.meta.shortreads, meryl_k: it.meta.meryl_k]} ) .multiMap { it -> @@ -113,11 +113,13 @@ workflow PREPARE_SHORTREADS { .collect { meta -> [ meta, it[1] ] } } .mix(MERYL_UNIONSUM.out.meryl_db - .filter { it -> !it[0].ids } + .filter { it -> !it[0].metas } .map { it -> [ it[0], it[1] ] } - ).set { meryl_kmers } + ) + .map {meta , kmers -> [meta.id, kmers]} + .set { meryl_kmers } diff --git a/subworkflows/local/qc/busco/main.nf b/subworkflows/local/qc/busco/main.nf index 65fcfc7a..495f554a 100644 --- a/subworkflows/local/qc/busco/main.nf +++ b/subworkflows/local/qc/busco/main.nf @@ -12,7 +12,7 @@ workflow RUN_BUSCO { ch_main .filter { - it -> it.busco + it -> it.meta.busco } .multiMap { it -> fasta: [ diff --git a/subworkflows/local/qc/main.nf b/subworkflows/local/qc/main.nf index 9773d4d7..90f4cde3 100644 --- a/subworkflows/local/qc/main.nf +++ b/subworkflows/local/qc/main.nf @@ -1,7 +1,7 @@ include { MAP_TO_ASSEMBLY } from '../mapping/map_to_assembly/main' include { RUN_BUSCO } from './busco/main.nf' include { RUN_QUAST } from './quast/main.nf' -include { MERQURY_QC } from './merqury/main.nf' +include { MERQURY_MERQURY as MERQURY } from '../../../modules/nf-core/merqury/merqury/main' workflow QC { take: @@ -29,13 +29,12 @@ workflow QC { .map { it -> [it.meta.id, it.meta] } .join(scaffolds) .join(meryl_kmers) - .multiMap { _id, meta, scaffs, kmers -> - scaffolds: [ meta, scaffs ] - kmers: [ meta, kmers ] + .map { _id, meta, scaffs, kmers -> + [ meta, kmers, scaffs ] } .set { merqury_in } - MERQURY_QC(merqury_in.scaffolds, merqury_in.kmers) + MERQURY(merqury_in) // Make sure that Polish and Scaffold main channels do not contain assembly_map_bam @@ -56,7 +55,7 @@ workflow QC { .map { _id, meta, target_scaffolds -> [ - meta, + meta + [qc_target: target_scaffolds], // QC Target only exists in QC channel, and takes the scaffold that should be qc'ed meta.qc_reads_path, target_scaffolds ] @@ -88,19 +87,19 @@ workflow QC { ch_versions = ch_versions.mix(RUN_BUSCO.out.versions) - MERQURY_QC.out.stats + MERQURY.out.stats .join( - MERQURY_QC.out.spectra_asm_hist + MERQURY.out.spectra_asm_hist ) .join( - MERQURY_QC.out.spectra_cn_hist + MERQURY.out.spectra_cn_hist ) .join( - MERQURY_QC.out.assembly_qv + MERQURY.out.assembly_qv ) .set { merqury_report_files } - ch_versions = ch_versions.mix(MERQURY_QC.out.versions) + ch_versions = ch_versions.mix(MERQURY.out.versions) emit: ch_main // QC does not (and should not) modify ch_main but returns the input. diff --git a/subworkflows/local/qc/merqury/main.nf b/subworkflows/local/qc/merqury/main.nf deleted file mode 100644 index a5eda05b..00000000 --- a/subworkflows/local/qc/merqury/main.nf +++ /dev/null @@ -1,33 +0,0 @@ -include { MERQURY_MERQURY as MERQURY } from '../../../../modules/nf-core/merqury/merqury/main' - -workflow MERQURY_QC { - take: - assembly - meryl_out - - main: - channel.empty().set { versions } - assembly.map { meta, _assembly -> [meta.id, []] }.set { stats } - assembly.map { meta, _assembly -> [meta.id, []] }.set { spectra_asm_hist } - assembly.map { meta, _assembly -> [meta.id, []] }.set { spectra_cn_hist } - assembly.map { meta, _assembly -> [meta.id, []] }.set { assembly_qv } - if (params.merqury) { - meryl_out - .map { it -> [[id: it[0].id], it[1]] } - .join(assembly) - .set { merqury_in } - MERQURY(merqury_in) - MERQURY.out.stats.set { stats } - MERQURY.out.spectra_asm_hist.set { spectra_asm_hist } - MERQURY.out.spectra_cn_hist.set { spectra_cn_hist } - MERQURY.out.assembly_qv.set { assembly_qv } - MERQURY.out.versions.set { versions } - } - - emit: - stats - spectra_asm_hist - spectra_cn_hist - assembly_qv - versions -} diff --git a/subworkflows/local/scaffolding/main.nf b/subworkflows/local/scaffolding/main.nf index 9f93241a..12fd434a 100644 --- a/subworkflows/local/scaffolding/main.nf +++ b/subworkflows/local/scaffolding/main.nf @@ -35,44 +35,120 @@ workflow SCAFFOLD { ch_main .filter { - it -> it.scaffold_longstitch + it -> it.meta.scaffold_longstitch } .set { longstitch_in } RUN_LONGSTITCH(longstitch_in, meryl_kmers) RUN_LONGSTITCH.out.ch_main - .map { it -> it.subMap("meta", "scaffolds_longstitch")} - .map { it -> it.collect { entry -> [ entry.value, entry ] } } .set { longstitch_out } ch_main .filter { - it -> it.scaffold_ragtag + it -> it.meta.scaffold_ragtag } .set { ragtag_in } RUN_RAGTAG(ragtag_in, meryl_kmers) RUN_RAGTAG.out.ch_main - .map { it -> it.subMap("meta","scaffolds_ragtag")} - .map { it -> it.collect { entry -> [ entry.value, entry ] } } .set { ragtag_out } - ch_main - .map { it -> it.collect { entry -> [ entry.value, entry ] } } - .join(links_out) - .join(longstitch_out) - .join(ragtag_out) - .map { it -> it.collect { _entry, map -> [ (map.key): map.value ] }.collectEntries() } - .map { - it -> it - it.subMap("scaffolds_links","scaffolds_longstitch", "scaffolds_ragtag") + - [ - scaffolds: [ - links: it.scaffold_links ?: null, - longstitch: it.scaffold_longstitch ?: null, - ragtag: it.scaffolds_ragtag ?: null - ] - ] - } + + // CONTINUE HERE + // Deal with cases that are single scaffold + links_out + .filter {it -> !it.meta.scaffold_longstitch && !it.meta.scaffold_ragtag } + .map { meta -> [ meta: meta - meta.subMap("links_scaffold") + [ scaffolds: [ links: meta.scaffolds_links ] ] ]} + .mix( + longstitch_out + .filter {it -> !it.meta.scaffold_links && !it.meta.scaffold_ragtag } + .map { meta -> [ meta: meta - meta.subMap("scaffolds_longstitch") + [ scaffolds: [ longstitch: meta.scaffolds_longstitch ] ] ]} + ) + .mix( + ragtag_out + .filter {it -> !it.meta.scaffold_links && !it.meta.scaffold_longstitch } + .map { meta -> [ meta: meta - meta.subMap("scaffolds_ragtag") + [ scaffolds: [ ragtag: meta.scaffolds_ragtag ] ] ]} + ) + // mix in those that are double scaffolded: , links-ragtag, longstitch-ragtag + // links-longstitch + .mix( + links_out + .filter {it -> it.meta.scaffold_longstitch && !it.meta.scaffold_ragtag } + .map {meta -> [meta.id, meta]} + // Join without filtering, inner-join + .join( + longstitch_out + .map {meta -> [meta.id, meta]} + ) + .map { + _id, meta_links, meta_longstitch -> [ + meta: meta_links - + meta_links.subMap("scaffolds_links") + + [scaffolds: [links: meta_links.scaffolds_links, longstitch: meta_longstitch.scaffolds_longstitch]] ] + } + ) + //links-ragtag + .mix( + links_out + .filter {it -> !it.meta.scaffold_longstitch && it.meta.scaffold_ragtag } + .map {meta -> [meta.id, meta]} + // Join without filtering, inner-join + .join( + ragtag_out + .map {meta -> [meta.id, meta]} + ) + .map { + _id, meta_links, meta_ragtag -> [ + meta: meta_links - + meta_links.subMap("scaffolds_links") + + [scaffolds: [links: meta_links.scaffolds_links, ragtag: meta_ragtag.scaffolds_ragtag]] ] + } + ) + //longstitch-ragtag + .mix( + longstitch_out + .filter {it -> !it.meta.scaffold_links && it.meta.scaffold_ragtag } + .map {meta -> [meta.id, meta]} + // Join without filtering, inner-join + .join( + ragtag_out + .map {meta -> [meta.id, meta]} + ) + .map { + _id, meta_longstitch, meta_ragtag -> [ + meta: meta_longstitch - + meta_longstitch.subMap("scaffolds_longstitch") + + [scaffolds: [longstitch: meta_longstitch.scaffolds_longstitch, ragtag: meta_ragtag.scaffolds_ragtag]] ] + } + ) + // mix in triple-scaffolded + .mix( + links_out + .filter {it -> it.meta.scaffold_longstitch && it.meta.scaffold_ragtag } + .map {meta -> [meta.id, meta]} + // Join without filtering, inner-join + .join( + longstitch_out + .map {meta -> [meta.id, meta]} + ) + .join( + ragtag_out + .map {meta -> [meta.id, meta]} + ) + .map { + _id, meta_links, meta_longstitch, meta_ragtag -> [ + meta: meta_links - + meta_links.subMap("scaffolds_links") + + [ + scaffolds: [ + links: meta_links.scaffolds_links, + longstitch: meta_longstitch.scaffolds_longstitch, + ragtag: meta_ragtag.scaffolds_ragtag + ] + ] + ] + } + ) .set { ch_main } diff --git a/subworkflows/local/utils_nfcore_genomeassembler_pipeline/main.nf b/subworkflows/local/utils_nfcore_genomeassembler_pipeline/main.nf index 83c085aa..8390fc16 100644 --- a/subworkflows/local/utils_nfcore_genomeassembler_pipeline/main.nf +++ b/subworkflows/local/utils_nfcore_genomeassembler_pipeline/main.nf @@ -107,24 +107,18 @@ workflow PIPELINE_INITIALISATION { def hifireads = it.hifireads ?: params.hifireads - def assembler = it.strategy == "single" ? - ( it.assembler ?: - (it.ontreads && it.assembler_ont) ? it.assembler_ont : - (it.hifireads && it.assembler_hifi) ? it.assembler_hifi : - params.assembler - ) : - params.assembler - - def assembler_ont = it.assembler_ont ?: + def assembler = it.assembler ?: params.assembler + + def assembler_ont = it.assembler_ont ?: + (strategy == "single" && assembler && ontreads && !hifireads) ? assembler : params.assembler_ont ?: - (strategy == "single" && ontreads && !hifireads) ? assembler : (strategy == "hybrid" && assembler == "hifiasm") ? assembler : assembler.contains("_") ? assembler.tokenize("_")[0] : null - def assembler_hifi = it.assembler_hifi ?: + def assembler_hifi = it.assembler_hifi ?: + (strategy == "single" && assembler && hifireads && !ontreads) ? assembler : params.assembler_hifi ?: - (strategy == "single" && !ontreads && hifireads) ? assembler : assembler.contains("_") ? assembler.tokenize("_")[1] : null @@ -134,9 +128,8 @@ workflow PIPELINE_INITIALISATION { (params.polish_pilon && (it.shortread_F || params.shortread_F)) ? "pilon" : null - // Hard exit here if assembler cannot be determined. - /* - strategy == "single" && ontreads && hifireads && !(assembler_ont || assembler_hifi) ? + + strategy == "single" && ontreads && hifireads && ((!assembler_ont && assembler_hifi) || (assembler_ont && !assembler_hifi)) ? error( """ [$it.sample]: Strategy is 'single', but ONT and HiFi reads are provided. @@ -144,7 +137,7 @@ workflow PIPELINE_INITIALISATION { """ ) : null - */ + // Build the map. Everything goes into meta. [ meta: [ @@ -155,7 +148,7 @@ workflow PIPELINE_INITIALISATION { hifireads: hifireads, // new in refactor-assemblers strategy: strategy, - // The "assembler" value is mainly to ease input, all actual workflow logic should use assembler_ont/2. + // The "assembler" value is mainly to ease input, all actual workflow logic should use assembler_ont/_hifi. // Could still be useful for debugging. assembler: assembler, // assembler_ont: ONT, assembler_hifi: HiFi @@ -165,19 +158,17 @@ workflow PIPELINE_INITIALISATION { assembler_ont_args: it.assembler_ont_args ?: params.assembler_ont_args ?: (assembler_ont == "hifiasm") ? (it.hifiasm_args ?: params.hifiasm_args) : (assembler_ont == "flye") ? (it.flye_args ?: params.flye_args) : - null, + "", assembler_hifi_args: it.assembler_hifi_args ?: params.assembler_hifi_args ?: (assembler_hifi == "hifiasm") ? (it.hifiasm_args ?: params.hifiasm_args) : (assembler_hifi == "flye") ? (it.flye_args ?: params.flye_args) : - null, + "", polish: polish, ont_collect: it.ont_collect ?: params.ont_collect, - ont_trim: it.ont_trim ?: params.ont_trim, ont_adapters: it.ont_adapters ?: params.ont_adapters, ont_fastplong_args: it.ont_fastplong_args ?: params.ont_fastplong_args, jellyfish: it.jellyfish ?: params.jellyfish, jellyfish_k: it.ont_jellyfish_k ?: params.jellyfish_k, - hifi_trim: it.hifi_trim ?: params.hifi_trim, hifi_adapters: it.hifi_adapters ?: params.hifi_adapters, hifi_fastplong_args: it.hifi_fastplong_args ?: params.hifi_fastplong_args, medaka_model: it.medaka_model ?: params.medaka_model, @@ -224,6 +215,7 @@ workflow PIPELINE_INITIALISATION { // sample-level checks // if a check fails, map returns a list that prints what fails, and contains "invalid" // error is raised by subscribe if there is more than one "invalid" + /* ch_samplesheet .map { it -> @@ -300,6 +292,7 @@ workflow PIPELINE_INITIALISATION { ? log.warn("Invalid combination in samplesheet") : null } + */ emit: samplesheet = ch_samplesheet diff --git a/workflows/genomeassembler.nf b/workflows/genomeassembler.nf index 7e640091..aaf52780 100644 --- a/workflows/genomeassembler.nf +++ b/workflows/genomeassembler.nf @@ -185,6 +185,8 @@ workflow GENOMEASSEMBLER { PREPARE.out.meryl_kmers.set { meryl_kmers } + + //ch_main_prepared.view{"Main WF: Prepared out: $it "} /* Assembly */ From 2a63c8822e23a724eea18c17b760a70dc1e0657b Mon Sep 17 00:00:00 2001 From: Niklas Schandry Date: Thu, 29 Jan 2026 10:38:11 +0100 Subject: [PATCH 105/162] add dorado polish --- CHANGELOG.md | 6 +- docs/usage.md | 16 +++-- modules/local/dorado/aligner/main.nf | 49 +++++++++++++ modules/local/dorado/polish/main.nf | 52 ++++++++++++++ nextflow.config | 2 + nextflow_schema.json | 4 ++ subworkflows/local/polishing/dorado/main.nf | 69 +++++++++++++++++++ subworkflows/local/polishing/main.nf | 35 +++++++--- .../polishing/medaka/polish_medaka/main.nf | 7 +- .../polishing/pilon/polish_pilon/main.nf | 6 +- subworkflows/local/prepare/main.nf | 3 + .../local/prepare/prepare_hifi/main.nf | 4 +- .../local/prepare/prepare_ont/main.nf | 4 +- subworkflows/local/scaffolding/links/main.nf | 2 +- .../local/scaffolding/longstitch/main.nf | 2 +- subworkflows/local/scaffolding/ragtag/main.nf | 2 +- .../main.nf | 7 +- workflows/genomeassembler.nf | 7 +- 18 files changed, 235 insertions(+), 42 deletions(-) create mode 100644 modules/local/dorado/aligner/main.nf create mode 100644 modules/local/dorado/polish/main.nf create mode 100644 subworkflows/local/polishing/dorado/main.nf diff --git a/CHANGELOG.md b/CHANGELOG.md index 769ac5e0..d29ab2f7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,10 +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). -## v2.0.0 'Saffron Vulture' - [2025-xx-xx] +## v2.0.0 'Saffron Vulture' - [2026-xx-xx] v2.0.0 of this pipeline is a large refactor of the pipeline to facilitate sample-level parameteristation. This allows to either parameterise the _pipeline_ using `params`, or parameterise _samples_ via the `input` samplesheet. In case both types of parameterisations are used, sample parameters will overwrite the pipeline parameters for that sample. -In addition, v2.0.0 contains some these changes: +In addition, v2.0.0 contains these changes: ### `Added` @@ -15,12 +15,14 @@ In addition, v2.0.0 contains some these changes: - increased flexibility of the scaffolding strategy - added option to group samples - fastp for short-read trimming and qc +- `dorado polish` added for ONT polishing ### `Fixed` ### `Dependencies` - `fastplong` +- `dorado` ### `Deprecated` diff --git a/docs/usage.md b/docs/usage.md index d9e5b154..cb4692de 100644 --- a/docs/usage.md +++ b/docs/usage.md @@ -333,13 +333,15 @@ Options for short reads ## Polishing options -Polishing options - -| Parameter | Description | Type | -| --------------- | ------------------------------------------------ | --------- | -| `polish_pilon` | Polish assembly with pilon? Requires short reads | `boolean` | -| `polish_medaka` | Polish assembly with medaka (ONT only) | `boolean` | -| `medaka_model` | model to use with medaka | `string` | +Polishing options. When using `polish` with either `dorado+pilon` or `medaka+pilon`, the assembly will be polished using ONT reads first, and then the ONT-polished assembly will be polished with short reads using `pilon`. `dorado` and `medaka` are mutually exclusive. + +| Parameter | Description | Type | +| --------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------- | +| `polish_pilon` | Polish assembly with pilon? Requires short reads | `boolean` | +| `polish_medaka` | Polish assembly with medaka (ONT only) | `boolean` | +| `polish_dorado` | Polish assembly with dorado (ONT only) | `boolean` | +| `medaka_model` | model to use with medaka | `string` | +| `polish` | Alternative polish interface: can be 'pilon','medaka', 'dorado', 'dorado+pilon' or 'medaka+pilon'. Only available through samplesheet, takes priority over `polish_*`. | `string` | ## Scaffolding options diff --git a/modules/local/dorado/aligner/main.nf b/modules/local/dorado/aligner/main.nf new file mode 100644 index 00000000..33ea3fc6 --- /dev/null +++ b/modules/local/dorado/aligner/main.nf @@ -0,0 +1,49 @@ +process DORADO_ALIGNER { + tag "${meta.id}" + label 'process_high' + + container "docker.io/nanoporetech/dorado:shaf2aed69855de85e60b363c9be39558ef469ec365" + + input: + tuple val(meta), path(ref), path(reads) + + output: + tuple val(meta), path("${meta.id}_dorado_aligned.bam"), emit: bam + tuple val(meta), path("${meta.id}_dorado_aligned.bai"), emit: bai + path "versions.yml", emit: versions + + when: + task.ext.when == null || task.ext.when + + script: + def args = task.ext.args ?: '' + + """ + dorado aligner \\ + -t ${task.cpus} \\ + ${ref} \\ + ${reads} \\ + ${args} \\ + > ${meta.id}_dorado_aligned.bam + samtools index ${meta.id}_dorado_aligned.bam + + cat <<-END_VERSIONS > versions.yml + "${task.process}": + dorado: "\$(dorado --version 2>&1 | head -n1)" + END_VERSIONS + """ + + stub: + def args = task.ext.args ?: '' + def prefix = task.ext.prefix ?: "${meta.id}" + + """ + touch ${prefix}/${prefix}.bam + touch ${prefix}/${prefix}.bai + + cat <<-END_VERSIONS > versions.yml + "${task.process}": + dorado: "\$(dorado --version 2>&1 | head -n1)" + END_VERSIONS + """ +} diff --git a/modules/local/dorado/polish/main.nf b/modules/local/dorado/polish/main.nf new file mode 100644 index 00000000..d9b06a76 --- /dev/null +++ b/modules/local/dorado/polish/main.nf @@ -0,0 +1,52 @@ +process DORADO_POLISH { + tag "${meta.id}" + label 'process_high' + + container "docker.io/nanoporetech/dorado:shaf2aed69855de85e60b363c9be39558ef469ec365" + + input: + tuple val(meta), path(assembly), path(alignment), path(index) + val(variant_call_format) + + output: + tuple val(meta), path("${meta.id}_dorado_polished.fa.gz"), emit: polished_alignment, optional: true + tuple val(meta), path("${meta.id}_dorado_polished*vcf"), emit: variant_calls, optional: true + path "versions.yml", emit: versions + + when: + task.ext.when == null || task.ext.when + + script: + def args = task.ext.args ?: '' + def variants = ["vcf","gvcf"].contains(variant_call_format) ? "--${variant_call_format}" : "" + def outfile = variants ? "> ${meta.id}_dorado_polished.${variants}" : "| bgzip | ${meta.id}_dorado_polished.fa.gz" + """ + dorado polish \\ + -t ${task.cpus} \\ + ${alignment} \\ + ${assembly} \\ + ${args} \\ + ${variants} \\ + ${outfile} + + + cat <<-END_VERSIONS > versions.yml + "${task.process}": + dorado: "\$(dorado --version 2>&1 | head -n1)" + END_VERSIONS + """ + + stub: + def args = task.ext.args ?: '' + def variants = ["vcf","gvcf"].contains(variant_call_format) ? "--${variant_call_format}" : "" + def outfile = variants ? "touch ${meta.id}_dorado_polished.${variants}" : "echo '' | bgzip > ${meta.id}_dorado_polished.fa.gz" + + """ + ${outfile} + + cat <<-END_VERSIONS > versions.yml + "${task.process}": + dorado: "\$(dorado --version 2>&1 | head -n1)" + END_VERSIONS + """ +} diff --git a/nextflow.config b/nextflow.config index 6b806e0e..e125eee0 100644 --- a/nextflow.config +++ b/nextflow.config @@ -91,6 +91,8 @@ params { // -- Polish: medaka polish_medaka = false // run medaka medaka_model = '' // model for medaka, if empty medaka will guess + // -- Polish: dorado + polish_dorado = false // -- Polish: pilon polish_pilon = false // run pilon // -- QC -- diff --git a/nextflow_schema.json b/nextflow_schema.json index 778345f6..b5f88f5c 100644 --- a/nextflow_schema.json +++ b/nextflow_schema.json @@ -292,6 +292,10 @@ "type": "boolean", "description": "Polish assembly with pilon? Requires short reads" }, + "polish_dorado": { + "type": "boolean", + "description": "Polish assembly with dorado (ONT only)" + }, "polish_medaka": { "type": "boolean", "description": "Polish assembly with medaka (ONT only)" diff --git a/subworkflows/local/polishing/dorado/main.nf b/subworkflows/local/polishing/dorado/main.nf new file mode 100644 index 00000000..d086dc44 --- /dev/null +++ b/subworkflows/local/polishing/dorado/main.nf @@ -0,0 +1,69 @@ +include { DORADO_ALIGNER as ALIGN } from '../../../../modules/local/dorado/aligner/main.nf' +include { DORADO_POLISH as POLISH } from '../../../../modules/local/dorado/polish/main.nf' +include { QC } from '../../qc/main.nf' +include { RUN_LIFTOFF } from '../../liftoff/main' + +workflow POLISH_DORADO { + take: + ch_main + meryl_kmers + + main: + channel.empty().set { ch_versions } + + ch_main + .map { it -> [it.meta, it.meta.assembly, it.meta.ontreads] } + .set { ch_aln_in } + + ALIGN(ch_aln_in) + + ALIGN.out.bam + .join(ALIGN.out.bai) + .map {meta, bam, bai-> [ meta, meta.assembly, bam, bai ] } + .set { ch_polish_in } + + POLISH(ch_polish_in, []) + + POLISH.out.polished_alignment.set { polished_assembly } + + polished_assembly + .map { meta, polished_dorado -> [meta: meta + [ polished: [polished_dorado: polished_dorado ] ] ]} + .set { ch_main_out } + + ch_versions = ch_versions.mix(POLISH.out.versions) + + QC( + ch_main_out.map { it -> [meta: it.meta - it.meta.subMap("assembly_map_bam") + [ assembly_map_bam: null] ] }, + polished_assembly.map { meta, polished -> [meta.id, polished] }, + meryl_kmers + ) + + ch_versions = ch_versions.mix(QC.out.versions) + + ch_main_out + .filter { + it -> it.meta.lift_annotations + } + .map { it -> + [ + it.meta, + it.meta.polished.dorado, + it.meta.ref_fasta, + it.meta.ref_gff + ] + } + .set { liftoff_in } + + RUN_LIFTOFF(liftoff_in) + + ch_versions = ch_versions.mix(RUN_LIFTOFF.out.versions) + + versions = ch_versions + + emit: + ch_main = ch_main_out + quast_out = QC.out.quast_out + busco_out = QC.out.busco_out + merqury_report_files = QC.out.merqury_report_files + versions +} diff --git a/subworkflows/local/polishing/main.nf b/subworkflows/local/polishing/main.nf index 44d0eeff..8ef68879 100644 --- a/subworkflows/local/polishing/main.nf +++ b/subworkflows/local/polishing/main.nf @@ -1,5 +1,6 @@ -include { POLISH_MEDAKA } from './medaka/polish_medaka/main' -include { POLISH_PILON } from './pilon/polish_pilon/main' +include { POLISH_MEDAKA } from './medaka/polish_medaka/main.nf' +include { POLISH_PILON } from './pilon/polish_pilon/main.nf' +include { POLISH_DORADO } from './dorado/main.nf' workflow POLISH { take: @@ -16,24 +17,36 @@ workflow POLISH { ch_main .branch { it -> medaka: ["medaka","medaka+pilon"].contains(it.meta.polish) - no_medaka: !["medaka","medaka+pilon"].contains(it.meta.polish) + dorado: ["dorado","dorado+pilon"].contains(it.meta.polish) + no_ont_polish: !["medaka","medaka+pilon","dorado","dorado+pilon"].contains(it.meta.polish) } .set { ch_main_polish } POLISH_MEDAKA(ch_main_polish.medaka, meryl_kmers) - POLISH_MEDAKA.out.ch_main - .mix(ch_main_polish.no_medaka) - .set { ch_main_polish_pilon } + POLISH_DORADO(ch_main_polish.dorado, meryl_kmers) - POLISH_MEDAKA.out.busco_out.set { polish_busco_reports } + POLISH_MEDAKA.out.busco_out + .mix(POLISH_DORADO.out.busco_out) + .set { polish_busco_reports } - POLISH_MEDAKA.out.quast_out.set { polish_quast_reports } + POLISH_MEDAKA.out.quast_out + .mix(POLISH_DORADO.out.quast_out) + .set { polish_quast_reports } - POLISH_MEDAKA.out.merqury_report_files.set { polish_merqury_reports } + POLISH_MEDAKA.out.merqury_report_files + .mix(POLISH_DORADO.out.merqury_report_files) + .set { polish_merqury_reports } ch_versions = ch_versions.mix(POLISH_MEDAKA.out.versions) + POLISH_MEDAKA.out.ch_main + .mix(POLISH_DORADO.out.ch_main) + .mix(ch_main_polish.no_ont_polish) + .set { ch_main_polish_pilon } + + + /* Polishing with short reads using pilon */ @@ -41,8 +54,8 @@ workflow POLISH { ch_main_polish_pilon .branch { it -> - pilon: ["pilon","medaka+pilon"].contains(it.meta.polish) - no_pilon: !["pilon","medaka+pilon"].contains(it.meta.polish) + pilon: ["pilon","medaka+pilon", "dorado+pilon"].contains(it.meta.polish) + no_pilon: !["pilon","medaka+pilon","dorado+pilon"].contains(it.meta.polish) } .set { ch_main_polish_pilon_in } diff --git a/subworkflows/local/polishing/medaka/polish_medaka/main.nf b/subworkflows/local/polishing/medaka/polish_medaka/main.nf index 5ba42f0a..51ade1da 100644 --- a/subworkflows/local/polishing/medaka/polish_medaka/main.nf +++ b/subworkflows/local/polishing/medaka/polish_medaka/main.nf @@ -11,9 +11,6 @@ workflow POLISH_MEDAKA { channel.empty().set { ch_versions } ch_main - .filter { - it -> it.meta.polish_medaka - } .multiMap { it -> reads: [it.meta, it.meta.ontreads] @@ -30,9 +27,7 @@ workflow POLISH_MEDAKA { // After joining re-create the maps from the stored map .set { ch_medaka_out } - ch_main - .filter { it -> !it.polish_medaka } - .mix(ch_medaka_out) + ch_medaka_out .set { ch_main_out } ch_versions = ch_versions.mix(RUN_MEDAKA.out.versions) diff --git a/subworkflows/local/polishing/pilon/polish_pilon/main.nf b/subworkflows/local/polishing/pilon/polish_pilon/main.nf index 10329bc1..7647d69c 100644 --- a/subworkflows/local/polishing/pilon/polish_pilon/main.nf +++ b/subworkflows/local/polishing/pilon/polish_pilon/main.nf @@ -32,11 +32,7 @@ workflow POLISH_PILON { .set { pilon_polished } pilon_polished - .map { meta, polished_pilon -> [ meta: meta, polished_pilon: polished_pilon ] } - .map { it -> [ meta: it.meta + - [ polished: [it.polished_pilon] ] - ] - } + .map { meta, polished_pilon -> [ meta: meta + [ polished: [pilon: polished_pilon] ] ] } .set { ch_main } ch_versions = ch_versions.mix(RUN_PILON.out.versions) diff --git a/subworkflows/local/prepare/main.nf b/subworkflows/local/prepare/main.nf index 4eb37d5c..43ca36c4 100644 --- a/subworkflows/local/prepare/main.nf +++ b/subworkflows/local/prepare/main.nf @@ -108,6 +108,8 @@ workflow PREPARE { ch_main_prepared } //ch_main_prepared.view {"CH_MAIN_PREPARED: $it"} + + // Get average read length of the QC reads from fastplong json report def slurp = new groovy.json.JsonSlurper() @@ -155,6 +157,7 @@ workflow PREPARE { ] ] } + // branch this channel for jellyfish .branch { it -> jelly: it.meta.jellyfish diff --git a/subworkflows/local/prepare/prepare_hifi/main.nf b/subworkflows/local/prepare/prepare_hifi/main.nf index 8a652f6f..18073f47 100644 --- a/subworkflows/local/prepare/prepare_hifi/main.nf +++ b/subworkflows/local/prepare/prepare_hifi/main.nf @@ -55,12 +55,12 @@ workflow PREPARE_HIFI { .filter { it -> it[0].metas } .flatMap { it -> // it looks like [meta, output_path] it[0].metas - .collect { metas -> [ meta: metas + [hifireads: it[1]] ] } + .collect { metas -> [ meta: metas - metas.subMap("hifireads") + [hifireads: it[1]] ] } } .mix(FASTPLONG_HIFI.out.reads .filter { it -> !it[0].metas } .map { - meta, hifireads -> [ meta: meta + [ hifireads: hifireads ] ] + meta, hifireads -> [ meta: meta - meta.subMap("hifireads") + [ hifireads: hifireads ] ] } ) .set { fastplong_reads_out } diff --git a/subworkflows/local/prepare/prepare_ont/main.nf b/subworkflows/local/prepare/prepare_ont/main.nf index af0179ce..9d464c6e 100644 --- a/subworkflows/local/prepare/prepare_ont/main.nf +++ b/subworkflows/local/prepare/prepare_ont/main.nf @@ -113,12 +113,12 @@ workflow PREPARE_ONT { .filter { it -> it[0].metas } .flatMap { it -> // it looks like [meta, output_path] it[0].metas - .collect { metas -> [ meta: metas + [ ontreads: it[1] ] ] } + .collect { metas -> [ meta: metas - metas.subMap("ontreads") + [ ontreads: it[1] ] ] } } .mix(FASTPLONG_ONT.out.reads .filter { it -> !it[0].metas } .map { - it -> [ meta: it[0] + [ ontreads: it[1] ] ] + it -> [ meta: it[0] - it[0].subMap("ontreads") + [ ontreads: it[1] ] ] } ) .set { fastplong_reads_out } diff --git a/subworkflows/local/scaffolding/links/main.nf b/subworkflows/local/scaffolding/links/main.nf index defdc2a0..4947848b 100644 --- a/subworkflows/local/scaffolding/links/main.nf +++ b/subworkflows/local/scaffolding/links/main.nf @@ -12,7 +12,7 @@ workflow RUN_LINKS { ch_main.dump(tag: "SCAFFOLD: LINKS: WORKFLOW inputs") ch_main .multiMap { it -> - assembly: [it.meta, it.meta.polished ? (it.meta.polished.pilon ?: it.meta.polished.medaka) : it.meta.assembly] + assembly: [it.meta, it.meta.polished ? (it.meta.polished.pilon ?: it.meta.polished.medaka ?: it.meta.polished.dorado) : it.meta.assembly] reads: [it.meta, it.meta.qc_reads_path] } .set { links_in } diff --git a/subworkflows/local/scaffolding/longstitch/main.nf b/subworkflows/local/scaffolding/longstitch/main.nf index 693a04f8..a525e17e 100644 --- a/subworkflows/local/scaffolding/longstitch/main.nf +++ b/subworkflows/local/scaffolding/longstitch/main.nf @@ -24,7 +24,7 @@ workflow RUN_LONGSTITCH { it -> [ it.meta, - it.meta.polished ? (it.polished.pilon ?: it.polished.medaka) : it.assembly, + it.meta.polished ? (it.meta.polished.pilon ?: it.meta.polished.medaka ?: it.meta.polished.dorado) : it.meta.assembly, it.meta.qc_reads_path, it.meta.genome_size ] diff --git a/subworkflows/local/scaffolding/ragtag/main.nf b/subworkflows/local/scaffolding/ragtag/main.nf index 0cd58b10..3df7376d 100644 --- a/subworkflows/local/scaffolding/ragtag/main.nf +++ b/subworkflows/local/scaffolding/ragtag/main.nf @@ -16,7 +16,7 @@ workflow RUN_RAGTAG { assembly: [ it.meta, - it.meta.polished ? (it.meta.polished.pilon ?: it.meta.polished.medaka) : it.meta.assembly + it.meta.polished ? (it.meta.polished.pilon ?: it.meta.polished.medaka ?: it.meta.polished.dorado) : it.meta.assembly ] reference: [it.meta, it.meta.ref_fasta] } diff --git a/subworkflows/local/utils_nfcore_genomeassembler_pipeline/main.nf b/subworkflows/local/utils_nfcore_genomeassembler_pipeline/main.nf index 8390fc16..4e57093c 100644 --- a/subworkflows/local/utils_nfcore_genomeassembler_pipeline/main.nf +++ b/subworkflows/local/utils_nfcore_genomeassembler_pipeline/main.nf @@ -122,14 +122,17 @@ workflow PIPELINE_INITIALISATION { assembler.contains("_") ? assembler.tokenize("_")[1] : null - def polish = it.polish ?: + def polish = it.polish ? it.polish : + (params.polish_medaka && params.polish_dorado) ? error("Both polish_medaka and polish_dorado are set.") : (params.polish_medaka && params.polish_pilon && ontreads) ? "medaka+pilon" : + (params.polish_dorado && params.polish_pilon && ontreads) ? "dorado+pilon" : (params.polish_medaka && ontreads) ? "medaka" : + (params.polish_dorado && ontreads) ? "dorado" : (params.polish_pilon && (it.shortread_F || params.shortread_F)) ? "pilon" : null - strategy == "single" && ontreads && hifireads && ((!assembler_ont && assembler_hifi) || (assembler_ont && !assembler_hifi)) ? + strategy == "single" && ontreads && hifireads && !((!assembler_ont && assembler_hifi) || (assembler_ont && !assembler_hifi)) ? error( """ [$it.sample]: Strategy is 'single', but ONT and HiFi reads are provided. diff --git a/workflows/genomeassembler.nf b/workflows/genomeassembler.nf index aaf52780..0646466b 100644 --- a/workflows/genomeassembler.nf +++ b/workflows/genomeassembler.nf @@ -203,11 +203,14 @@ workflow GENOMEASSEMBLER { ch_main_assembled .branch { it -> - polish: it.meta.polish_medaka || it.meta.polish_pilon - no_polish: !it.meta.polish_medaka && !it.meta.polish_pilon + polish: ["pilon","medaka","medaka+pilon","dorado","dorado+pilon"].contains(it.meta.polish) + no_polish: !["pilon","medaka","medaka+pilon","dorado","dorado+pilon"].contains(it.meta.polish) } .set { ch_main_assembled } + ch_main_assembled.polish.view {"ch_main_assembled.polish: $it"} + ch_main_assembled.no_polish.view {"ch_main_assembled.no_polish: $it"} + POLISH(ch_main_assembled.polish, meryl_kmers) ch_main_assembled.no_polish From 39bd2238d130727f5fd20b239b257f88fb41d5bb Mon Sep 17 00:00:00 2001 From: Niklas Schandry Date: Thu, 29 Jan 2026 11:40:58 +0100 Subject: [PATCH 106/162] integrate dorado into pipeline, add documentation --- assets/report/functions/read_busco.R | 1 + assets/report/scripts/_busco_page.R | 2 +- assets/report/scripts/_merqury.R | 4 ++++ assets/report/scripts/_quast.R | 1 + conf/modules/polishing.config | 14 +++++++++++++ docs/output.md | 7 +++++++ docs/usage.md | 18 ++++++++--------- workflows/genomeassembler.nf | 30 ++++++++++++++++++---------- 8 files changed, 57 insertions(+), 20 deletions(-) diff --git a/assets/report/functions/read_busco.R b/assets/report/functions/read_busco.R index a214604c..cc9f1a22 100644 --- a/assets/report/functions/read_busco.R +++ b/assets/report/functions/read_busco.R @@ -71,6 +71,7 @@ read_busco_batch <- \(x) {read_tsv(x, show_col_types = F) %>% stage = case_when( str_detect(x, "ragtag") ~ "RagTag", str_detect(x, "medaka") ~ "medaka", + str_detect(x, "dorado") ~ "dorado", str_detect(x, "pilon") ~ "pilon", str_detect(x, "longstitch") ~ "longstitch", str_detect(x, "links") ~ "LINKS", diff --git a/assets/report/scripts/_busco_page.R b/assets/report/scripts/_busco_page.R index ef672a42..60a486f1 100644 --- a/assets/report/scripts/_busco_page.R +++ b/assets/report/scripts/_busco_page.R @@ -11,7 +11,7 @@ for (i in 1:length(unique(busco_reports$group))) { dplyr::select(sample, stage, Var, value) |> mutate(Var = str_replace_all(Var, "_", " ") |> str_replace_all("percent", "(%)")) |> pivot_wider(names_from = "Var", values_from = "value", id_cols = c(sample,stage)) |> - dplyr::arrange(factor(stage, levels = c("Assembly","medaka", "pilon","links","longstitch","ragtag")), sample) |> + dplyr::arrange(factor(stage, levels = c("Assembly","medaka", "pilon", "dorado","links","longstitch","ragtag")), sample) |> gt::gt() |> gt::fmt_auto() |> gt::opt_stylize(color = "gray") |> diff --git a/assets/report/scripts/_merqury.R b/assets/report/scripts/_merqury.R index 12209ef6..b868fa40 100644 --- a/assets/report/scripts/_merqury.R +++ b/assets/report/scripts/_merqury.R @@ -9,6 +9,7 @@ merqury_stats <- list.files(paste0(data_base, "merqury"), full.names = T, patter stage = case_when( str_detect(x, "_ragtag") ~ "RagTag", str_detect(x, "_medaka") ~ "medaka", + str_detect(x, "_dorado") ~ "dorado", str_detect(x, "_pilon") ~ "pilon", str_detect(x, "_longstitch") ~ "longstitch", str_detect(x, "_links") ~ "LINKS", @@ -27,6 +28,7 @@ merqury_asm_hists <- list.files(paste0(data_base, "/merqury"), full.names = T, p stage = case_when( str_detect(x, "_ragtag") ~ "RagTag", str_detect(x, "_medaka") ~ "medaka", + str_detect(x, "_dorado") ~ "dorado", str_detect(x, "_pilon") ~ "pilon", str_detect(x, "_longstitch") ~ "longstitch", str_detect(x, "_links") ~ "LINKS", @@ -51,6 +53,7 @@ merqury_cn_hists <- list.files(paste0(data_base, "merqury"), full.names = T, pat stage = case_when( str_detect(x, "_ragtag") ~ "RagTag", str_detect(x, "_medaka") ~ "medaka", + str_detect(x, "_dorado") ~ "dorado", str_detect(x, "_pilon") ~ "pilon", str_detect(x, "_longstitch") ~ "longstitch", str_detect(x, "_links") ~ "LINKS", @@ -77,6 +80,7 @@ merqury_qv <- list.files(paste0(data_base, "merqury"), full.names = T, pattern = stage = case_when( str_detect(x, "_ragtag") ~ "RagTag", str_detect(x, "_medaka") ~ "medaka", + str_detect(x, "_dorado") ~ "dorado", str_detect(x, "_pilon") ~ "pilon", str_detect(x, "_longstitch") ~ "longstitch", str_detect(x, "_links") ~ "LINKS", diff --git a/assets/report/scripts/_quast.R b/assets/report/scripts/_quast.R index 6b8a27e2..0538ff36 100644 --- a/assets/report/scripts/_quast.R +++ b/assets/report/scripts/_quast.R @@ -14,6 +14,7 @@ quast_stats <- list.files(paste0(data_base, "quast"), stage = case_when( str_detect(x, "_ragtag") ~ "RagTag", str_detect(x, "_medaka") ~ "medaka", + str_detect(x, "_dorado") ~ "dorado", str_detect(x, "_pilon") ~ "pilon", str_detect(x, "_longstitch") ~ "longstitch", str_detect(x, "_links") ~ "LINKS", diff --git a/conf/modules/polishing.config b/conf/modules/polishing.config index 8c39f50e..219e9393 100644 --- a/conf/modules/polishing.config +++ b/conf/modules/polishing.config @@ -10,6 +10,20 @@ process { saveAs: { filename -> filename.equals('versions.yml') ? null : filename } ] } + withName: '.*:DORADO:ALIGN.*' { + publishDir = [ + path: { "${params.outdir}/${meta.id}/polish/dorado/alignment" }, + mode: params.publish_dir_mode, + saveAs: { filename -> filename.equals('versions.yml') ? null : filename } + ] + } + withName: '.*:DORADO:POLISH.*' { + publishDir = [ + path: { "${params.outdir}/${meta.id}/polish/dorado/" }, + mode: params.publish_dir_mode, + saveAs: { filename -> filename.equals('versions.yml') ? null : filename } + ] + } // Pilon mapping withName: '.*PILON:MAP_SR.*' { publishDir = [ diff --git a/docs/output.md b/docs/output.md index b01a3061..b8525f3a 100644 --- a/docs/output.md +++ b/docs/output.md @@ -141,6 +141,13 @@ Annotation `gff3` and `unmapped.txt` files are only created if a reference for a - `_medaka.fa.gz` Polished assembly - `_medaka.gff3` annotation liftover - `_medaka.unnapped.txt` annotations that could not be lifted over during annotation liftover + - `dorado/`: output from dorado + - `_dorado.fa.gz` Polished assembly + - `_dorado.gff3` annotation liftover + - `_dorado.unnapped.txt` annotations that could not be lifted over during annotation liftover + - `alignments/` output from dorado aligner + - `_dorado_aligned.bam` Alignment + - `_dorado_aligned.bai` Alignment index diff --git a/docs/usage.md b/docs/usage.md index cb4692de..ef430184 100644 --- a/docs/usage.md +++ b/docs/usage.md @@ -333,15 +333,15 @@ Options for short reads ## Polishing options -Polishing options. When using `polish` with either `dorado+pilon` or `medaka+pilon`, the assembly will be polished using ONT reads first, and then the ONT-polished assembly will be polished with short reads using `pilon`. `dorado` and `medaka` are mutually exclusive. - -| Parameter | Description | Type | -| --------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------- | -| `polish_pilon` | Polish assembly with pilon? Requires short reads | `boolean` | -| `polish_medaka` | Polish assembly with medaka (ONT only) | `boolean` | -| `polish_dorado` | Polish assembly with dorado (ONT only) | `boolean` | -| `medaka_model` | model to use with medaka | `string` | -| `polish` | Alternative polish interface: can be 'pilon','medaka', 'dorado', 'dorado+pilon' or 'medaka+pilon'. Only available through samplesheet, takes priority over `polish_*`. | `string` | +Polishing options. When using `polish` with either `dorado+pilon` or `medaka+pilon`, the assembly will be polished using ONT reads first, and then the ONT-polished assembly will be polished with short reads using `pilon`. `dorado` and `medaka` are mutually exclusive. `dorado` is not available via conda. + +| Parameter | Description | Type | +| --------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | --------- | +| `polish_pilon` | Polish assembly with pilon? Requires short reads | `boolean` | +| `polish_medaka` | Polish assembly with medaka (ONT only) | `boolean` | +| `polish_dorado` | Polish assembly with dorado (ONT only) | `boolean` | +| `medaka_model` | model to use with medaka | `string` | +| `polish` | Alternative polish interface: can be 'pilon','medaka', 'dorado', 'dorado+pilon' or 'medaka+pilon', do not include quotation marks. Only available through samplesheet, takes priority over `polish_*`. | `string` | ## Scaffolding options diff --git a/workflows/genomeassembler.nf b/workflows/genomeassembler.nf index 0646466b..1d77a461 100644 --- a/workflows/genomeassembler.nf +++ b/workflows/genomeassembler.nf @@ -203,17 +203,27 @@ workflow GENOMEASSEMBLER { ch_main_assembled .branch { it -> - polish: ["pilon","medaka","medaka+pilon","dorado","dorado+pilon"].contains(it.meta.polish) - no_polish: !["pilon","medaka","medaka+pilon","dorado","dorado+pilon"].contains(it.meta.polish) + def polishers = ["pilon", "medaka", "medaka+pilon", "dorado", "dorado+pilon"] + /*debug + def polishValue = it.meta.polish + def inList = polishers.contains(polishValue) + println "DEBUG: polish='${polishValue}' (type: ${polishValue.class.name}), inList=${inList}" + polish: inList + no_polish: !inList + DEBUG: polish='"medaka+pilon"' (type: java.lang.String), inList=false + No quotes in samplesheet? + */ + polish: polishers.contains(it.meta.polish) == true + no_polish: true } - .set { ch_main_assembled } + .set { ch_main_assembled_branched } - ch_main_assembled.polish.view {"ch_main_assembled.polish: $it"} - ch_main_assembled.no_polish.view {"ch_main_assembled.no_polish: $it"} + ch_main_assembled_branched.polish.view {"ch_main_assembled_branched.polish: $it"} + ch_main_assembled_branched.no_polish.view {"ch_main_assembled_branched.no_polish: $it"} - POLISH(ch_main_assembled.polish, meryl_kmers) + POLISH(ch_main_assembled_branched.polish, meryl_kmers) - ch_main_assembled.no_polish + ch_main_assembled_branched.no_polish .mix(POLISH.out.ch_main) .set { ch_main_polished } @@ -227,16 +237,16 @@ workflow GENOMEASSEMBLER { no_scaffold: !it.meta.scaffold_links && !it.meta.scaffold_longstitch && !it.meta.scaffold_ragtag } .set { - ch_main_polished + ch_main_polished_branched } /* Scaffolding */ - SCAFFOLD(ch_main_polished.scaffold, meryl_kmers) + SCAFFOLD(ch_main_polished_branched.scaffold, meryl_kmers) // Recreate ch_main, even though it is not used since there are no later steps. - ch_main_polished + ch_main_polished_branched .no_scaffold .mix(SCAFFOLD.out.ch_main) .set { ch_main_scaffolded } From ddf00a77345e86320c84e961a6b0b1db91720ccc Mon Sep 17 00:00:00 2001 From: Niklas Schandry Date: Tue, 3 Feb 2026 09:47:13 +0100 Subject: [PATCH 107/162] local dorado modules, no test, not properly implemented, dorado not stable --- conf/modules/polishing.config | 1 + modules/local/dorado/aligner/main.nf | 8 ++++--- modules/local/dorado/polish/main.nf | 4 ++-- subworkflows/local/polishing/main.nf | 21 ++++++++++++++----- .../polishing/medaka/polish_medaka/main.nf | 2 +- .../polishing/pilon/polish_pilon/main.nf | 5 ++++- 6 files changed, 29 insertions(+), 12 deletions(-) diff --git a/conf/modules/polishing.config b/conf/modules/polishing.config index 219e9393..482fd81b 100644 --- a/conf/modules/polishing.config +++ b/conf/modules/polishing.config @@ -11,6 +11,7 @@ process { ] } withName: '.*:DORADO:ALIGN.*' { + //ext.args = { ["--add-fastq-rg"] } publishDir = [ path: { "${params.outdir}/${meta.id}/polish/dorado/alignment" }, mode: params.publish_dir_mode, diff --git a/modules/local/dorado/aligner/main.nf b/modules/local/dorado/aligner/main.nf index 33ea3fc6..d7ffe135 100644 --- a/modules/local/dorado/aligner/main.nf +++ b/modules/local/dorado/aligner/main.nf @@ -2,14 +2,14 @@ process DORADO_ALIGNER { tag "${meta.id}" label 'process_high' - container "docker.io/nanoporetech/dorado:shaf2aed69855de85e60b363c9be39558ef469ec365" + container "docker.io/nanoporetech/dorado:sha00aa724a69ddc5f47d82bd413039f912fdaf4e77" input: tuple val(meta), path(ref), path(reads) output: tuple val(meta), path("${meta.id}_dorado_aligned.bam"), emit: bam - tuple val(meta), path("${meta.id}_dorado_aligned.bai"), emit: bai + tuple val(meta), path("${meta.id}_dorado_aligned.bam.bai"), emit: bai path "versions.yml", emit: versions when: @@ -24,8 +24,10 @@ process DORADO_ALIGNER { ${ref} \\ ${reads} \\ ${args} \\ + | samtools sort --threads ${task.cpus}\\ > ${meta.id}_dorado_aligned.bam - samtools index ${meta.id}_dorado_aligned.bam + + samtools index ${meta.id}_dorado_aligned.bam > ${meta.id}_dorado_aligned.bam.bai cat <<-END_VERSIONS > versions.yml "${task.process}": diff --git a/modules/local/dorado/polish/main.nf b/modules/local/dorado/polish/main.nf index d9b06a76..6a161f59 100644 --- a/modules/local/dorado/polish/main.nf +++ b/modules/local/dorado/polish/main.nf @@ -2,7 +2,7 @@ process DORADO_POLISH { tag "${meta.id}" label 'process_high' - container "docker.io/nanoporetech/dorado:shaf2aed69855de85e60b363c9be39558ef469ec365" + container "docker.io/nanoporetech/dorado:sha00aa724a69ddc5f47d82bd413039f912fdaf4e77" input: tuple val(meta), path(assembly), path(alignment), path(index) @@ -19,7 +19,7 @@ process DORADO_POLISH { script: def args = task.ext.args ?: '' def variants = ["vcf","gvcf"].contains(variant_call_format) ? "--${variant_call_format}" : "" - def outfile = variants ? "> ${meta.id}_dorado_polished.${variants}" : "| bgzip | ${meta.id}_dorado_polished.fa.gz" + def outfile = variants ? "> ${meta.id}_dorado_polished.${variants}" : "| bgzip > ${meta.id}_dorado_polished.fa.gz" """ dorado polish \\ -t ${task.cpus} \\ diff --git a/subworkflows/local/polishing/main.nf b/subworkflows/local/polishing/main.nf index 8ef68879..bf0668dc 100644 --- a/subworkflows/local/polishing/main.nf +++ b/subworkflows/local/polishing/main.nf @@ -16,12 +16,18 @@ workflow POLISH { ch_main .branch { it -> - medaka: ["medaka","medaka+pilon"].contains(it.meta.polish) - dorado: ["dorado","dorado+pilon"].contains(it.meta.polish) - no_ont_polish: !["medaka","medaka+pilon","dorado","dorado+pilon"].contains(it.meta.polish) + def medaka_polishers = ["medaka","medaka+pilon"] + def dorado_polishers = ["dorado","dorado+pilon"] + medaka: medaka_polishers.contains(it.meta.polish) + dorado: dorado_polishers.contains(it.meta.polish) + no_ont_polish: !medaka_polishers.contains(it.meta.polish) && !dorado_polishers.contains(it.meta.polish) } .set { ch_main_polish } + //ch_main_polish.medaka.view { "ch_main_polish.medaka: $it"} + //ch_main_polish.dorado.view { "ch_main_polish.dorado: $it"} + //ch_main_polish.no_ont_polish.view { "ch_main_polish.no_ont_polish: $it"} + POLISH_MEDAKA(ch_main_polish.medaka, meryl_kmers) POLISH_DORADO(ch_main_polish.dorado, meryl_kmers) @@ -54,11 +60,16 @@ workflow POLISH { ch_main_polish_pilon .branch { it -> - pilon: ["pilon","medaka+pilon", "dorado+pilon"].contains(it.meta.polish) - no_pilon: !["pilon","medaka+pilon","dorado+pilon"].contains(it.meta.polish) + def pilon_polishers = ["pilon","medaka+pilon", "dorado+pilon"] + pilon: pilon_polishers.contains(it.meta.polish) + no_pilon: true } .set { ch_main_polish_pilon_in } + //ch_main_polish_pilon_in.pilon.view {"ch_main_polish_pilon_in.pilon: $it"} + + //ch_main_polish_pilon_in.no_pilon.view {"ch_main_polish_pilon_in.no_pilon: $it"} + POLISH_PILON(ch_main_polish_pilon_in.pilon, meryl_kmers) ch_main_polish_pilon_in.no_pilon.mix(POLISH_PILON.out.ch_main) diff --git a/subworkflows/local/polishing/medaka/polish_medaka/main.nf b/subworkflows/local/polishing/medaka/polish_medaka/main.nf index 51ade1da..b56a5c96 100644 --- a/subworkflows/local/polishing/medaka/polish_medaka/main.nf +++ b/subworkflows/local/polishing/medaka/polish_medaka/main.nf @@ -23,7 +23,7 @@ workflow POLISH_MEDAKA { RUN_MEDAKA.out.medaka_out.set { polished_assembly } polished_assembly - .map { meta, polished_medaka -> [meta: meta + [ polished: [polished_medaka: polished_medaka ] ] ]} + .map { meta, polished_medaka -> [meta: meta + [ polished: [medaka: polished_medaka ] ] ]} // After joining re-create the maps from the stored map .set { ch_medaka_out } diff --git a/subworkflows/local/polishing/pilon/polish_pilon/main.nf b/subworkflows/local/polishing/pilon/polish_pilon/main.nf index 7647d69c..e730969f 100644 --- a/subworkflows/local/polishing/pilon/polish_pilon/main.nf +++ b/subworkflows/local/polishing/pilon/polish_pilon/main.nf @@ -17,11 +17,14 @@ workflow POLISH_PILON { shortreads: [it.meta, it.meta.shortreads] assembly: [ it.meta, - it.meta.polish == "medaka+pilon" ? it.meta.polished.medaka : it.meta.assembly + it.meta.polish == "medaka+pilon" ? it.meta.polished.medaka : it.meta.polish == "dorado+pilon" ? it.meta.polished.dorado : it.meta.assembly ] } .set { map_sr_in } + //map_sr_in.shortreads.view {"POLISH_PILON: map_sr_in.shortreads: $it"} + //map_sr_in.assembly.view {"POLISH_PILON: map_sr_in.assembly: $it"} + MAP_SR(map_sr_in.shortreads, map_sr_in.assembly) ch_versions = ch_versions.mix(MAP_SR.out.versions) From bb3147d2118518c1e5454d9ab69520a9751d452a Mon Sep 17 00:00:00 2001 From: Niklas Schandry Date: Tue, 3 Feb 2026 09:49:17 +0100 Subject: [PATCH 108/162] refactored workflow, finishes full_test successfully --- subworkflows/local/mapping/map_sr/main.nf | 1 - workflows/genomeassembler.nf | 5 +---- 2 files changed, 1 insertion(+), 5 deletions(-) diff --git a/subworkflows/local/mapping/map_sr/main.nf b/subworkflows/local/mapping/map_sr/main.nf index 655c4002..5213a09d 100644 --- a/subworkflows/local/mapping/map_sr/main.nf +++ b/subworkflows/local/mapping/map_sr/main.nf @@ -10,7 +10,6 @@ workflow MAP_SR { channel.empty().set { ch_versions } // map reads to assembly in_reads - .map { meta, reads -> [[id: meta.id], reads] } .join(genome_assembly) .set { map_assembly } diff --git a/workflows/genomeassembler.nf b/workflows/genomeassembler.nf index 1d77a461..e9cf1869 100644 --- a/workflows/genomeassembler.nf +++ b/workflows/genomeassembler.nf @@ -213,14 +213,11 @@ workflow GENOMEASSEMBLER { DEBUG: polish='"medaka+pilon"' (type: java.lang.String), inList=false No quotes in samplesheet? */ - polish: polishers.contains(it.meta.polish) == true + polish: polishers.contains(it.meta.polish) no_polish: true } .set { ch_main_assembled_branched } - ch_main_assembled_branched.polish.view {"ch_main_assembled_branched.polish: $it"} - ch_main_assembled_branched.no_polish.view {"ch_main_assembled_branched.no_polish: $it"} - POLISH(ch_main_assembled_branched.polish, meryl_kmers) ch_main_assembled_branched.no_polish From 0e1964ca1d59463d024eedae7d88279c8c65bf39 Mon Sep 17 00:00:00 2001 From: Niklas Schandry Date: Tue, 3 Feb 2026 10:41:37 +0100 Subject: [PATCH 109/162] update report for new busco output --- assets/report/functions/read_busco.R | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/assets/report/functions/read_busco.R b/assets/report/functions/read_busco.R index cc9f1a22..cf0a5eeb 100644 --- a/assets/report/functions/read_busco.R +++ b/assets/report/functions/read_busco.R @@ -30,7 +30,7 @@ read_busco_report <- function(file) { show_col_types = FALSE ) %>% magrittr::set_colnames(c("count", "stat")) %>% - dplyr::filter(!stat == "Percent gaps") %>% + dplyr::filter(!stat %in% c("Scaffold N50", "Contigs N50", "Percent gaps", "Number of scaffolds", "Percent gaps")) %>% mutate( count = case_when( str_detect(count, "MB") ~ count %>% str_extract("[0-9]+") %>% as.double() %>% { From 152a41839a8ed0e476e35815e0415640f2696737 Mon Sep 17 00:00:00 2001 From: Niklas Schandry Date: Tue, 3 Feb 2026 10:42:08 +0100 Subject: [PATCH 110/162] some documentation and comments --- CHANGELOG.md | 10 +- docs/usage.md | 4 +- subworkflows/local/prepare/main.nf | 44 +++++++ .../local/prepare/prepare_ont/main.nf | 4 +- workflows/genomeassembler.nf | 124 +----------------- 5 files changed, 61 insertions(+), 125 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d29ab2f7..7ada9220 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,17 +5,21 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## v2.0.0 'Saffron Vulture' - [2026-xx-xx] -v2.0.0 of this pipeline is a large refactor of the pipeline to facilitate sample-level parameteristation. This allows to either parameterise the _pipeline_ using `params`, or parameterise _samples_ via the `input` samplesheet. In case both types of parameterisations are used, sample parameters will overwrite the pipeline parameters for that sample. +This is a major release, with breaking changes. +v2.0.0 of genomeassembler is a large refactor of the pipeline to facilitate sample-level parameteristation. This allows to either parameterise the _pipeline_ using `params`, or parameterise _samples_ via the `input` samplesheet. In case both types of parameterisations are used, sample parameters will take priority. + +Since this workflow follows a sample-centric implementation, nextflow will always render the full pipeline dag, but depending on configuration samples may not travel through the whole pipeline. This may also cause terminal output to show steps that will never become an active process. + In addition, v2.0.0 contains these changes: ### `Added` - fastplong for long-read trimming and qc +- fastp for short-read trimming and qc - migration to nf-test - increased flexibility of the scaffolding strategy - added option to group samples -- fastp for short-read trimming and qc -- `dorado polish` added for ONT polishing +- `dorado polish` added as an alternative to `medaka` for ONT polishing. This is an **experimental feature** and not further documented, due to `dorado polish` being under active development. ### `Fixed` diff --git a/docs/usage.md b/docs/usage.md index ef430184..bbf5045a 100644 --- a/docs/usage.md +++ b/docs/usage.md @@ -333,13 +333,13 @@ Options for short reads ## Polishing options -Polishing options. When using `polish` with either `dorado+pilon` or `medaka+pilon`, the assembly will be polished using ONT reads first, and then the ONT-polished assembly will be polished with short reads using `pilon`. `dorado` and `medaka` are mutually exclusive. `dorado` is not available via conda. +Polishing options. When using `polish` with either `dorado+pilon` or `medaka+pilon`, the assembly will be polished using ONT reads first, and then the ONT-polished assembly will be polished with short reads using `pilon`. `dorado` and `medaka` are mutually exclusive. `dorado` is not available via conda. **`dorado` is an experimental feature which may not work for all inputs.** | Parameter | Description | Type | | --------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | --------- | | `polish_pilon` | Polish assembly with pilon? Requires short reads | `boolean` | | `polish_medaka` | Polish assembly with medaka (ONT only) | `boolean` | -| `polish_dorado` | Polish assembly with dorado (ONT only) | `boolean` | +| `polish_dorado` | EXPERIMENTAL: Polish assembly with dorado (ONT only) | `boolean` | | `medaka_model` | model to use with medaka | `string` | | `polish` | Alternative polish interface: can be 'pilon','medaka', 'dorado', 'dorado+pilon' or 'medaka+pilon', do not include quotation marks. Only available through samplesheet, takes priority over `polish_*`. | `string` | diff --git a/subworkflows/local/prepare/main.nf b/subworkflows/local/prepare/main.nf index 43ca36c4..040775bc 100644 --- a/subworkflows/local/prepare/main.nf +++ b/subworkflows/local/prepare/main.nf @@ -4,6 +4,50 @@ include { PREPARE_SHORTREADS as SHORTREADS } from './prepare_shortreads/main' include { JELLYFISH } from './jellyfish/main' workflow PREPARE { + /* + Subworkflows in prepare implement sample grouping. + SHORTREADS, JELLYFISH, ONT and HIFI each implement + the same logic for sample grouping. + Grouping needs to be specified by the user, and can + be used to create sample groups that share inputs, to + minimize redundant input preparations. + Reads of samples from the same group will be prepared + only once, and then the original channel is restored. + + Brief description how this works: + // Move group information into channel, if it exists + .filter { it -> it.meta.group } + .map { it -> [it.meta, it.meta.group, it.meta.ontreads] } + // Group by group + .groupTuple(by: 1) + // Collect all sample-meta into a group meta slot named metas + // Use unique reads; user responsible to group correctly + .map { + it -> + [ + [ + id: it[1], // the group + metas: it[0] + ], + it[2].unique()[0] // Ontreads + ] + } + + After this input channel has been processed, the samples are + recreated from meta[metas]: + + process.OUT + // Take samples with metas in slot [0] + .filter { it -> it[0].metas } + .flatMap { it -> + // $it looks like [meta, output_path] + // recreate meta from metas and update path. + it[0].metas + .collect { meta -> [ meta: meta + [ontreads: it[1]] ] } + } + + + */ take: ch_main main: diff --git a/subworkflows/local/prepare/prepare_ont/main.nf b/subworkflows/local/prepare/prepare_ont/main.nf index 9d464c6e..88bc4972 100644 --- a/subworkflows/local/prepare/prepare_ont/main.nf +++ b/subworkflows/local/prepare/prepare_ont/main.nf @@ -49,13 +49,13 @@ workflow PREPARE_ONT { .filter { it -> it[0].metas } .flatMap { it -> // it looks like [meta, output_path] it[0].metas - .collect { meta -> [ meta: meta + [ontreads: it[1]] ] } + .collect { meta -> [ meta: meta - meta.subMap("ontreads") + [ontreads: it[1]] ] } } .mix( COLLECT.out.reads .filter { it -> !it[0].metas } .map { - it -> [ meta: it[0] + [ontreads: it[1]] ] + it -> [ meta: it[0] - meta.subMap("ontreads") + [ontreads: it[1]] ] } ) .set { ch_collected_reads } diff --git a/workflows/genomeassembler.nf b/workflows/genomeassembler.nf index e9cf1869..57298bad 100644 --- a/workflows/genomeassembler.nf +++ b/workflows/genomeassembler.nf @@ -38,126 +38,14 @@ workflow GENOMEASSEMBLER { ch_input.set { ch_main } /* + This pipeline uses a "meta-stuffing" appraoch. All information + about a sample is always stored in a map stored in [0]/"meta". + Values are extracted from the map to create input channels. + The correspoding key is created or updated from outputs. + This largely eliminates the need for joins. - The "main" channel, contains all sample-wise information. - This channel should be the main input of all subworkflows - and the subworkflows should make changes to this map. The - main channel should stay a map whenever possible and this - main channel reflects all pipeline parameters. - I will make use of the meta map to pass additional infor- - mation into processes. This is neccessary to provide fine - control for parameterization of processes. This is passed - via ext.args to the process and fetched from meta. - - The keys are defined in + The initial keys are defined in ./subworkflows/local/utils_nfcore_genomeassembler/main.nf - - meta: [ - id: string, - ontreads: path, - hifireads: path, - strategy: string, - assembler_ont: string, - assembler_hifi: string, - scaffolding: string, - genome_size: integer, - assembler_ont_args: string, - assembler_hifi_args: string, - ref_fasta: path, - ref_gff: path, - shortread_F: path, - shortread_R: path, - paired: bool - ont_collect: bool, - ont_trim: bool, - ont_jellyfish: bool, - hifi_trim: bool, - hifi_primers: path, - polish_medaka: bool, - medaka_model: string, - polish_pilon: bool, - scaffold_longstitch: bool, - scaffold_links: bool, - scaffold_ragtag: bool, - use_ref: bool, - flye_mode: string, - assembly: path, - ref_map_bam: path, - assembly_map_bam: path, - qc_reads: string ["ont","hifi"], - qc_reads_path: path, - quast: bool, - busco: bool, - busco_lineage: string, - busco_db: path, - lift_annotations: bool, - shortread_F: path, - shortread_R: path, - paired: bool, - use_short_reads: bool, - shortread_trim: bool - ] - - - - =========== - JOINS - =========== - - Since this channel needs to stay a map so I can pull out the correct elements, joining is difficult: - Nextflow's join operator only works on list-typed channels, but the channels here are maps. - For this reason, there are some confuding map operations involved where each map-element is converted to a list, - containing the value and the previous map. The whole channel is turned into a list this way: - - Something like - - [ - meta: [id: something1], - somepath: "/path" - ] - - becomes - - [ - [id: something, meta: [id: something]], - ["/path", somepath: "/path"] - ] - - This can be joined to - - [ - [id: something, meta: [id: something]], - ["different_path", otherpath: "different_path"] - ] - - This makes it possible to join on the first element (the one containing meta): - - [ - [id: something, meta: [id: something]], - ["path", somepath: "path"], - ["different_path", otherpath: "different_path"] - ] - - After joining, the map is recreated from the second list element, to create: - - [ - meta: [id: something], - somepath: "path", - otherpath: "different_path" - ] - - This is (sadly) a somewhat frequent pattern in this pipeline and - it is done like this: - - map_channel_1 - // Convert to list for join - .map { it -> it.collect { entry -> [ entry.value, entry ] } } - .join( map_channel_2 - // Convert to list for join - .map { it -> it.collect { entry -> [ entry.value, entry ] } } - ) - // After joining re-create the maps from the stored map - .map { it -> it.collect { _entry, map -> [ (map.key): map.value ] }.collectEntries() } */ channel.empty().set { meryl_kmers } From 3092b2a92c5b2f921b7b16ddca5273cb070488a2 Mon Sep 17 00:00:00 2001 From: Niklas Schandry Date: Tue, 3 Feb 2026 11:37:42 +0100 Subject: [PATCH 111/162] add funding information to README --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 1295272c..be41bce8 100644 --- a/README.md +++ b/README.md @@ -67,7 +67,7 @@ For more details about the output files and reports, please refer to the ## Credits -nf-core/genomeassembler was originally written by [Niklas Schandry](https://github.com/nschan), of the Faculty of Biology of the Ludwig-Maximilians University (LMU) in Munich, Germany. +nf-core/genomeassembler was originally written by [Niklas Schandry](https://github.com/nschan), of the Faculty of Biology of the Ludwig-Maximilians University (LMU) in Munich, Germany, with funding support from the German Research Foundation (Deutsche Forschungsgemeinschaft [DFG], via Transregional Research Center TRR356 grant 491090170-A05 to Niklas Schandry). I thank the following people for their extensive assistance and constructive reviews during the development of this pipeline: From 353a08d3fd284387043efb149c971e0d47142e20 Mon Sep 17 00:00:00 2001 From: Niklas Schandry Date: Wed, 4 Feb 2026 10:10:05 +0100 Subject: [PATCH 112/162] add hic workflow --- README.md | 2 +- conf/modules/fastp.config | 10 + conf/modules/scaffolding.config | 34 + docs/usage.md | 25 +- modules.json | 25 + modules/nf-core/bwamem2/index/environment.yml | 13 + modules/nf-core/bwamem2/index/main.nf | 55 ++ modules/nf-core/bwamem2/index/meta.yml | 52 ++ .../nf-core/bwamem2/index/tests/main.nf.test | 62 ++ .../bwamem2/index/tests/main.nf.test.snap | 64 ++ modules/nf-core/bwamem2/mem/environment.yml | 13 + modules/nf-core/bwamem2/mem/main.nf | 83 +++ modules/nf-core/bwamem2/mem/meta.yml | 133 ++++ .../nf-core/bwamem2/mem/tests/main.nf.test | 179 +++++ .../bwamem2/mem/tests/main.nf.test.snap | 129 ++++ .../picard/markduplicates/environment.yml | 8 + modules/nf-core/picard/markduplicates/main.nf | 61 ++ .../nf-core/picard/markduplicates/meta.yml | 126 ++++ .../picard/markduplicates/tests/main.nf.test | 173 +++++ .../markduplicates/tests/main.nf.test.snap | 329 ++++++++++ .../markduplicates/tests/nextflow.config | 6 + .../nf-core/samtools/faidx/environment.yml | 10 + modules/nf-core/samtools/faidx/main.nf | 50 ++ modules/nf-core/samtools/faidx/meta.yml | 117 ++++ .../nf-core/samtools/faidx/tests/main.nf.test | 245 +++++++ .../samtools/faidx/tests/main.nf.test.snap | 615 ++++++++++++++++++ .../samtools/faidx/tests/nextflow.config | 7 + modules/nf-core/yahs/environment.yml | 7 + modules/nf-core/yahs/main.nf | 62 ++ modules/nf-core/yahs/meta.yml | 135 ++++ modules/nf-core/yahs/tests/main.nf.test | 138 ++++ modules/nf-core/yahs/tests/main.nf.test.snap | 210 ++++++ modules/nf-core/yahs/tests/nextflow.config | 10 + nextflow.config | 10 +- nextflow_schema.json | 29 + subworkflows/local/assemble/main.nf | 1 - subworkflows/local/prepare/main.nf | 7 +- .../local/prepare/prepare_ont/main.nf | 2 +- .../local/prepare/prepare_shortreads/main.nf | 141 +++- subworkflows/local/scaffolding/hic/main.nf | 142 ++++ subworkflows/local/scaffolding/links/main.nf | 2 +- .../local/scaffolding/longstitch/main.nf | 2 +- subworkflows/local/scaffolding/main.nf | 29 +- subworkflows/local/scaffolding/ragtag/main.nf | 23 +- .../main.nf | 6 +- 45 files changed, 3536 insertions(+), 46 deletions(-) create mode 100644 modules/nf-core/bwamem2/index/environment.yml create mode 100644 modules/nf-core/bwamem2/index/main.nf create mode 100644 modules/nf-core/bwamem2/index/meta.yml create mode 100644 modules/nf-core/bwamem2/index/tests/main.nf.test create mode 100644 modules/nf-core/bwamem2/index/tests/main.nf.test.snap create mode 100644 modules/nf-core/bwamem2/mem/environment.yml create mode 100644 modules/nf-core/bwamem2/mem/main.nf create mode 100644 modules/nf-core/bwamem2/mem/meta.yml create mode 100644 modules/nf-core/bwamem2/mem/tests/main.nf.test create mode 100644 modules/nf-core/bwamem2/mem/tests/main.nf.test.snap create mode 100644 modules/nf-core/picard/markduplicates/environment.yml create mode 100644 modules/nf-core/picard/markduplicates/main.nf create mode 100644 modules/nf-core/picard/markduplicates/meta.yml create mode 100644 modules/nf-core/picard/markduplicates/tests/main.nf.test create mode 100644 modules/nf-core/picard/markduplicates/tests/main.nf.test.snap create mode 100644 modules/nf-core/picard/markduplicates/tests/nextflow.config create mode 100644 modules/nf-core/samtools/faidx/environment.yml create mode 100644 modules/nf-core/samtools/faidx/main.nf create mode 100644 modules/nf-core/samtools/faidx/meta.yml create mode 100644 modules/nf-core/samtools/faidx/tests/main.nf.test create mode 100644 modules/nf-core/samtools/faidx/tests/main.nf.test.snap create mode 100644 modules/nf-core/samtools/faidx/tests/nextflow.config create mode 100644 modules/nf-core/yahs/environment.yml create mode 100644 modules/nf-core/yahs/main.nf create mode 100644 modules/nf-core/yahs/meta.yml create mode 100644 modules/nf-core/yahs/tests/main.nf.test create mode 100644 modules/nf-core/yahs/tests/main.nf.test.snap create mode 100644 modules/nf-core/yahs/tests/nextflow.config create mode 100644 subworkflows/local/scaffolding/hic/main.nf diff --git a/README.md b/README.md index be41bce8..e4bd3785 100644 --- a/README.md +++ b/README.md @@ -67,7 +67,7 @@ For more details about the output files and reports, please refer to the ## Credits -nf-core/genomeassembler was originally written by [Niklas Schandry](https://github.com/nschan), of the Faculty of Biology of the Ludwig-Maximilians University (LMU) in Munich, Germany, with funding support from the German Research Foundation (Deutsche Forschungsgemeinschaft [DFG], via Transregional Research Center TRR356 grant 491090170-A05 to Niklas Schandry). +nf-core/genomeassembler was written, and is currently maintained by [Niklas Schandry](https://github.com/nschan), of the Faculty of Biology of the Ludwig-Maximilians University (LMU) in Munich, Germany, with funding support from the German Research Foundation (Deutsche Forschungsgemeinschaft [DFG], via Transregional Research Center TRR356 grant 491090170-A05 to Niklas Schandry). I thank the following people for their extensive assistance and constructive reviews during the development of this pipeline: diff --git a/conf/modules/fastp.config b/conf/modules/fastp.config index 58abec2f..1019fe0c 100644 --- a/conf/modules/fastp.config +++ b/conf/modules/fastp.config @@ -7,3 +7,13 @@ process { ] } } + +process { + withName: FASTP_HIC { + publishDir = [ + path: { "${params.outdir}/${meta.id}/hic_reads/fastp" }, + mode: params.publish_dir_mode, + saveAs: { filename -> filename.equals('versions.yml') ? null : filename } + ] + } +} diff --git a/conf/modules/scaffolding.config b/conf/modules/scaffolding.config index 878b8bfa..a30cbdd9 100644 --- a/conf/modules/scaffolding.config +++ b/conf/modules/scaffolding.config @@ -30,4 +30,38 @@ process { ] ext.prefix = { "${meta.id}_longstitch" } } + withName: BWAMEM2_INDEX { + publishDir = [ + path: { "${params.outdir}/${meta.id}/scaffold/hic/bwamem2/index" }, + mode: params.publish_dir_mode, + saveAs: { filename -> filename.equals('versions.yml') ? null : filename } + ] + ext.prefix = { "${meta.id}_bwamem_index" } + } + withName: BWAMEM2_MEM { + publishDir = [ + path: { "${params.outdir}/${meta.id}/scaffold/hic/bwamem2/mem" }, + mode: params.publish_dir_mode, + saveAs: { filename -> filename.equals('versions.yml') ? null : filename } + ] + ext.prefix = { "${meta.id}_bwamem" } + ext.args = { "-5SP"} + } + withName: MINIMAP2_HIC { + publishDir = [ + path: { "${params.outdir}/${meta.id}/scaffold/hic/minimap/" }, + mode: params.publish_dir_mode, + saveAs: { filename -> filename.equals('versions.yml') ? null : filename } + ] + ext.prefix = { "${meta.id}_minimap_hic" } + ext.args = { "--no-pairing" } + } + withName: MARKDUP { + publishDir = [ + path: { "${params.outdir}/${meta.id}/scaffold/hic/minimap/" }, + mode: params.publish_dir_mode, + saveAs: { filename -> filename.equals('versions.yml') ? null : filename } + ] + ext.prefix = { "${meta.id}_hic_dedup" } + } } diff --git a/docs/usage.md b/docs/usage.md index bbf5045a..6ff1a91c 100644 --- a/docs/usage.md +++ b/docs/usage.md @@ -7,7 +7,10 @@ ## Introduction This pipeline is designed to assemble haploid (or diploid inbred) genomes from long-reads. `nf-core/genomeassembler` can take ONT and HiFi reads, and supports different assembly strategies. The pipeline can also integrate information on a reference genome (e.g. closely related individual) and short-reads for quality control. -This pipeline can perform assembly, polishing, scaffolding and annotation lift-over from a reference genome. Phasing or HiC scaffolding are currently unsupported. +This pipeline can perform assembly, polishing, scaffolding using long-reads, HiC data, or a reference, and annotation lift-over from a reference genome. + +> [!NOTE] +> Phasing is currently not supported. ![Pipeline metromap](images/genomeassembler.light.png) @@ -35,7 +38,7 @@ Sample parameters take priority over global parameters, if both are provided the > [!NOTE] > The parameter names will be used in subsequent sections. Since all parameters can be provided per-sample or pipeline wide, no examples will be given. -The list of all parameters that can be provided globally is available [here](params.md), parameters that can be set per sample are provided at the [end of this page](#sample-parameters). +The list of all parameters that can be provided globally is available [here](https://nf-co.re/genomeassembler/parameters/), parameters that can be set per sample are provided at the [end of this page](#sample-parameters). ## Samples and grouping @@ -59,8 +62,7 @@ Assembly strategy is controlled via `strategy` (either pipeline parameter or sam - scaffold: Assemble ONT reads and HiFi indepently and scaffold one assembly onto the other. `assembler` has to be provided as `"ontAssembler_hifiAssembler"` and could for example be: "`flye_hifiasm"` to assemble ont reads with `flye` and HiFi reads with `hifiasm` or "hifiasm_hifiasm" to assemble both ont and hifi reads indepently with `hifiasm`. When running in "scaffold" mode, `assembly_scaffolding_order` can be used to control which assembly gets scaffolded onto which, the default being "ont_on_hifi" where ONT assembly is scaffolded onto HifI assembly. Assembler specific arguments can be provided for the assembler via `hifiasm_args` or `flye_args`, or with more fine-grained control via `assembler_ont_args` and `assembler_hifi_args` for scaffolding. -`assembler_ont_args` controls the parameters for the assembler in `single` and `hybrid` strategies, or for the assembler used for ONT reads when using `scaffold`. `assembler_hifi_args` can be used to pass arguments to the assembler used for HiFi reads in `scaffold` mode. -`assembler_[ont,hifi]_args` can only be set via the samplesheet and are not available as global pipeline parameters. +`assembler_ont_args` controls the parameters for the assembler in `single` (with ONT) and `hybrid` strategies, or for the assembler used for ONT reads when using `scaffold`. `assembler_hifi_args` can be used to pass arguments to the assembler used for HiFi reads in `single`, or `scaffold` mode. ## Samplesheet input @@ -338,9 +340,9 @@ Polishing options. When using `polish` with either `dorado+pilon` or `medaka+pil | Parameter | Description | Type | | --------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | --------- | | `polish_pilon` | Polish assembly with pilon? Requires short reads | `boolean` | +| `medaka_model` | model to use with medaka | `string` | | `polish_medaka` | Polish assembly with medaka (ONT only) | `boolean` | | `polish_dorado` | EXPERIMENTAL: Polish assembly with dorado (ONT only) | `boolean` | -| `medaka_model` | model to use with medaka | `string` | | `polish` | Alternative polish interface: can be 'pilon','medaka', 'dorado', 'dorado+pilon' or 'medaka+pilon', do not include quotation marks. Only available through samplesheet, takes priority over `polish_*`. | `string` | ## Scaffolding options @@ -353,6 +355,19 @@ Scaffolding options | `scaffold_links` | Scaffolding with links? | `boolean` | | `scaffold_ragtag` | Scaffold with ragtag (requires reference)? | `boolean` | +### HiC + +HiC scaffolding specific parameters. Supplying HiC reads activates HiC scaffolding. +bwa-mem2 generally is more suitable for HiC alignments than minimap2, and is the recommended option. +However, bwamem2 requires substantial memory for large genomes, which may prohibit use of bwamem2 in some cases. + +| Parameter | Description | Type | +| ------------- | ----------------------------------------------------------- | --------- | +| `hic_aligner` | Aligner to use, default "bwa-mem2", alternative: "minimap2" | `string` | +| `hic_F` | Forward / \_1 HiC reads | `path` | +| `hic_R` | Reverse / \_2 HiC reads | `path` | +| `hic_trim` | Trim HiC reads? default: false | `boolean` | + ## QC options Options for QC tools diff --git a/modules.json b/modules.json index 2dc69151..59d9f05c 100644 --- a/modules.json +++ b/modules.json @@ -10,6 +10,16 @@ "git_sha": "e753770db613ce014b3c4bc94f6cba443427b726", "installed_by": ["modules"] }, + "bwamem2/index": { + "branch": "master", + "git_sha": "d86336f3e7ae0d5f76c67b0859409769cfeb2af2", + "installed_by": ["modules"] + }, + "bwamem2/mem": { + "branch": "master", + "git_sha": "d86336f3e7ae0d5f76c67b0859409769cfeb2af2", + "installed_by": ["modules"] + }, "fastp": { "branch": "master", "git_sha": "d9ec4ef289ad39b8a662a7a12be50409b11df84b", @@ -70,6 +80,11 @@ "installed_by": ["modules"], "patch": "modules/nf-core/minimap2/align/minimap2-align.diff" }, + "picard/markduplicates": { + "branch": "master", + "git_sha": "66d5808eaaabd9de8997c4c31a9e8cdd3b56c080", + "installed_by": ["modules"] + }, "pilon": { "branch": "master", "git_sha": "41dfa3f7c0ffabb96a6a813fe321c6d1cc5b6e46", @@ -85,6 +100,11 @@ "git_sha": "e753770db613ce014b3c4bc94f6cba443427b726", "installed_by": ["modules"] }, + "samtools/faidx": { + "branch": "master", + "git_sha": "9a48bce39a67e2cb34b8f125fc1d50f0ad98b616", + "installed_by": ["modules"] + }, "samtools/fastq": { "branch": "master", "git_sha": "c8be52dba1166c678e74cda9c3a3c221635c8bb1", @@ -119,6 +139,11 @@ "branch": "master", "git_sha": "e753770db613ce014b3c4bc94f6cba443427b726", "installed_by": ["modules"] + }, + "yahs": { + "branch": "master", + "git_sha": "5c3c4f4753d55bd3ba93ce29ed1100e964c52f74", + "installed_by": ["modules"] } } }, diff --git a/modules/nf-core/bwamem2/index/environment.yml b/modules/nf-core/bwamem2/index/environment.yml new file mode 100644 index 00000000..f3637444 --- /dev/null +++ b/modules/nf-core/bwamem2/index/environment.yml @@ -0,0 +1,13 @@ +--- +# 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/bwa-mem2 + - bwa-mem2=2.3 + # renovate: datasource=conda depName=bioconda/htslib + - htslib=1.22.1 + # renovate: datasource=conda depName=bioconda/samtools + - samtools=1.22.1 diff --git a/modules/nf-core/bwamem2/index/main.nf b/modules/nf-core/bwamem2/index/main.nf new file mode 100644 index 00000000..62af1e39 --- /dev/null +++ b/modules/nf-core/bwamem2/index/main.nf @@ -0,0 +1,55 @@ +process BWAMEM2_INDEX { + tag "$fasta" + // NOTE Requires 28N GB memory where N is the size of the reference sequence, floor of 280M + // source: https://github.com/bwa-mem2/bwa-mem2/issues/9 + memory { (280.MB * Math.ceil(fasta.size() / 10000000)) * task.attempt } + + 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/e0/e05ce34b46ad42810eb29f74e4e304c0cb592b2ca15572929ed8bbaee58faf01/data' : + 'community.wave.seqera.io/library/bwa-mem2_htslib_samtools:db98f81f55b64113' }" + + input: + tuple val(meta), path(fasta) + + output: + tuple val(meta), path("bwamem2"), emit: index + path "versions.yml" , emit: versions + + when: + task.ext.when == null || task.ext.when + + script: + def prefix = task.ext.prefix ?: "${fasta}" + def args = task.ext.args ?: '' + """ + mkdir bwamem2 + bwa-mem2 \\ + index \\ + $args \\ + -p bwamem2/${prefix} \\ + $fasta + + cat <<-END_VERSIONS > versions.yml + "${task.process}": + bwamem2: \$(echo \$(bwa-mem2 version 2>&1) | sed 's/.* //') + END_VERSIONS + """ + + stub: + def prefix = task.ext.prefix ?: "${fasta}" + + """ + mkdir bwamem2 + touch bwamem2/${prefix}.0123 + touch bwamem2/${prefix}.ann + touch bwamem2/${prefix}.pac + touch bwamem2/${prefix}.amb + touch bwamem2/${prefix}.bwt.2bit.64 + + cat <<-END_VERSIONS > versions.yml + "${task.process}": + bwamem2: \$(echo \$(bwa-mem2 version 2>&1) | sed 's/.* //') + END_VERSIONS + """ +} diff --git a/modules/nf-core/bwamem2/index/meta.yml b/modules/nf-core/bwamem2/index/meta.yml new file mode 100644 index 00000000..b2aa45fb --- /dev/null +++ b/modules/nf-core/bwamem2/index/meta.yml @@ -0,0 +1,52 @@ +name: bwamem2_index +description: Create BWA-mem2 index for reference genome +keywords: + - index + - fasta + - genome + - reference +tools: + - bwamem2: + description: | + BWA-mem2 is a software package for mapping DNA sequences against + a large reference genome, such as the human genome. + homepage: https://github.com/bwa-mem2/bwa-mem2 + documentation: https://github.com/bwa-mem2/bwa-mem2#usage + licence: ["MIT"] + identifier: "biotools:bwa-mem2" +input: + - - meta: + type: map + description: | + Groovy Map containing sample information + e.g. [ id:'test', single_end:false ] + - fasta: + type: file + description: Input genome fasta file + ontologies: + - edam: "http://edamontology.org/data_2044" # Sequence + - edam: "http://edamontology.org/format_1929" # FASTA +output: + index: + - - meta: + type: map + description: | + Groovy Map containing sample information + e.g. [ id:'test', single_end:false ] + - bwamem2: + type: file + description: BWA genome index files + pattern: "*.{0123,amb,ann,bwt.2bit.64,pac}" + ontologies: + - edam: "http://edamontology.org/data_3210" # Genome index + versions: + - versions.yml: + type: file + description: File containing software versions + pattern: "versions.yml" + ontologies: + - edam: http://edamontology.org/format_3750 # YAML +authors: + - "@maxulysse" +maintainers: + - "@maxulysse" diff --git a/modules/nf-core/bwamem2/index/tests/main.nf.test b/modules/nf-core/bwamem2/index/tests/main.nf.test new file mode 100644 index 00000000..adf44785 --- /dev/null +++ b/modules/nf-core/bwamem2/index/tests/main.nf.test @@ -0,0 +1,62 @@ +nextflow_process { + + name "Test Process BWAMEM2_INDEX" + tag "modules_nfcore" + tag "modules" + tag "bwamem2" + tag "bwamem2/index" + script "../main.nf" + process "BWAMEM2_INDEX" + + test("fasta") { + + when { + process { + """ + input[0] = [ + [id: 'test'], + file(params.modules_testdata_base_path + 'genomics/sarscov2/genome/genome.fasta', checkIfExists: true) + ] + """ + } + } + + then { + assertAll( + { assert process.success }, + { assert snapshot( + process.out.index, + process.out.versions, + path(process.out.versions[0]).yaml + ).match() } + ) + } + } + + test("fasta - stub") { + + options "-stub" + + when { + process { + """ + input[0] = [ + [id: 'test'], + file(params.modules_testdata_base_path + 'genomics/sarscov2/genome/genome.fasta', checkIfExists: true) + ] + """ + } + } + + then { + assertAll( + { assert process.success }, + { assert snapshot( + process.out.index, + process.out.versions, + path(process.out.versions[0]).yaml + ).match() } + ) + } + } +} diff --git a/modules/nf-core/bwamem2/index/tests/main.nf.test.snap b/modules/nf-core/bwamem2/index/tests/main.nf.test.snap new file mode 100644 index 00000000..9ad8b20c --- /dev/null +++ b/modules/nf-core/bwamem2/index/tests/main.nf.test.snap @@ -0,0 +1,64 @@ +{ + "fasta - stub": { + "content": [ + [ + [ + { + "id": "test" + }, + [ + "genome.fasta.0123:md5,d41d8cd98f00b204e9800998ecf8427e", + "genome.fasta.amb:md5,d41d8cd98f00b204e9800998ecf8427e", + "genome.fasta.ann:md5,d41d8cd98f00b204e9800998ecf8427e", + "genome.fasta.bwt.2bit.64:md5,d41d8cd98f00b204e9800998ecf8427e", + "genome.fasta.pac:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ] + ], + [ + "versions.yml:md5,9ffd13d12e7108ed15c58566bc4717d6" + ], + { + "BWAMEM2_INDEX": { + "bwamem2": "2.2.1" + } + } + ], + "meta": { + "nf-test": "0.9.2", + "nextflow": "25.04.6" + }, + "timestamp": "2025-09-04T08:55:53.219699135" + }, + "fasta": { + "content": [ + [ + [ + { + "id": "test" + }, + [ + "genome.fasta.0123:md5,b02870de80106104abcb03cd9463e7d8", + "genome.fasta.amb:md5,3a68b8b2287e07dd3f5f95f4344ba76e", + "genome.fasta.ann:md5,c32e11f6c859f166c7525a9c1d583567", + "genome.fasta.bwt.2bit.64:md5,d097a1b82dee375d41a1ea69895a9216", + "genome.fasta.pac:md5,983e3d2cd6f36e2546e6d25a0da78d66" + ] + ] + ], + [ + "versions.yml:md5,9ffd13d12e7108ed15c58566bc4717d6" + ], + { + "BWAMEM2_INDEX": { + "bwamem2": "2.2.1" + } + } + ], + "meta": { + "nf-test": "0.9.2", + "nextflow": "25.04.6" + }, + "timestamp": "2025-09-04T08:55:45.007921901" + } +} \ No newline at end of file diff --git a/modules/nf-core/bwamem2/mem/environment.yml b/modules/nf-core/bwamem2/mem/environment.yml new file mode 100644 index 00000000..f3637444 --- /dev/null +++ b/modules/nf-core/bwamem2/mem/environment.yml @@ -0,0 +1,13 @@ +--- +# 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/bwa-mem2 + - bwa-mem2=2.3 + # renovate: datasource=conda depName=bioconda/htslib + - htslib=1.22.1 + # renovate: datasource=conda depName=bioconda/samtools + - samtools=1.22.1 diff --git a/modules/nf-core/bwamem2/mem/main.nf b/modules/nf-core/bwamem2/mem/main.nf new file mode 100644 index 00000000..27910cf6 --- /dev/null +++ b/modules/nf-core/bwamem2/mem/main.nf @@ -0,0 +1,83 @@ +process BWAMEM2_MEM { + tag "$meta.id" + label 'process_high' + + 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/e0/e05ce34b46ad42810eb29f74e4e304c0cb592b2ca15572929ed8bbaee58faf01/data' : + 'community.wave.seqera.io/library/bwa-mem2_htslib_samtools:db98f81f55b64113' }" + + input: + tuple val(meta), path(reads) + tuple val(meta2), path(index) + tuple val(meta3), path(fasta) + val sort_bam + + output: + tuple val(meta), path("*.sam") , emit: sam , optional:true + 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 args2 = task.ext.args2 ?: '' + def prefix = task.ext.prefix ?: "${meta.id}" + def samtools_command = sort_bam ? 'sort' : 'view' + + def extension_pattern = /(--output-fmt|-O)+\s+(\S+)/ + def extension_matcher = (args2 =~ extension_pattern) + def extension = extension_matcher.getCount() > 0 ? extension_matcher[0][2].toLowerCase() : "bam" + def reference = fasta && extension=="cram" ? "--reference ${fasta}" : "" + if (!fasta && extension=="cram") error "Fasta reference is required for CRAM output" + + """ + INDEX=`find -L ./ -name "*.amb" | sed 's/\\.amb\$//'` + + bwa-mem2 \\ + mem \\ + $args \\ + -t $task.cpus \\ + \$INDEX \\ + $reads \\ + | samtools $samtools_command $args2 -@ $task.cpus ${reference} -o ${prefix}.${extension} - + + cat <<-END_VERSIONS > versions.yml + "${task.process}": + bwamem2: \$(echo \$(bwa-mem2 version 2>&1) | sed 's/.* //') + samtools: \$(echo \$(samtools --version 2>&1) | sed 's/^.*samtools //; s/Using.*\$//') + END_VERSIONS + """ + + stub: + + def args2 = task.ext.args2 ?: '' + def prefix = task.ext.prefix ?: "${meta.id}" + def extension_pattern = /(--output-fmt|-O)+\s+(\S+)/ + def extension_matcher = (args2 =~ extension_pattern) + def extension = extension_matcher.getCount() > 0 ? extension_matcher[0][2].toLowerCase() : "bam" + if (!fasta && extension=="cram") error "Fasta reference is required for CRAM output" + + def create_index = "" + if (extension == "cram") { + create_index = "touch ${prefix}.crai" + } else if (extension == "bam") { + create_index = "touch ${prefix}.csi" + } + + """ + touch ${prefix}.${extension} + ${create_index} + + cat <<-END_VERSIONS > versions.yml + "${task.process}": + bwamem2: \$(echo \$(bwa-mem2 version 2>&1) | sed 's/.* //') + samtools: \$(echo \$(samtools --version 2>&1) | sed 's/^.*samtools //; s/Using.*\$//') + END_VERSIONS + """ +} diff --git a/modules/nf-core/bwamem2/mem/meta.yml b/modules/nf-core/bwamem2/mem/meta.yml new file mode 100644 index 00000000..6c7d1728 --- /dev/null +++ b/modules/nf-core/bwamem2/mem/meta.yml @@ -0,0 +1,133 @@ +name: bwamem2_mem +description: Performs fastq alignment to a fasta reference using BWA +keywords: + - mem + - bwa + - alignment + - map + - fastq + - bam + - sam +tools: + - bwa: + description: | + BWA-mem2 is a software package for mapping DNA sequences against + a large reference genome, such as the human genome. + homepage: https://github.com/bwa-mem2/bwa-mem2 + documentation: http://www.htslib.org/doc/samtools.html + arxiv: arXiv:1303.3997 + licence: ["MIT"] + identifier: "biotools:bwa-mem2" +input: + - - meta: + type: map + description: | + Groovy Map containing sample information + e.g. [ id:'test', single_end:false ] + - reads: + type: file + description: | + List of input FastQ files of size 1 and 2 for single-end and paired-end data, + respectively. + ontologies: + - edam: "http://edamontology.org/data_2044" # Sequence + - edam: "http://edamontology.org/format_1930" # FASTQ + - - meta2: + type: map + description: | + Groovy Map containing reference/index information + e.g. [ id:'test' ] + - index: + type: file + description: BWA genome index files + pattern: "Directory containing BWA index *.{0132,amb,ann,bwt.2bit.64,pac}" + ontologies: + - edam: "http://edamontology.org/data_3210" # Genome index + - - meta3: + type: map + description: | + Groovy Map containing reference information + e.g. [ id:'genome' ] + - fasta: + type: file + description: Reference genome in FASTA format + pattern: "*.{fa,fasta,fna}" + ontologies: + - edam: "http://edamontology.org/data_2044" # Sequence + - edam: "http://edamontology.org/format_1929" # FASTA + - sort_bam: + type: boolean + description: use samtools sort (true) or samtools view (false) + pattern: "true or false" +output: + sam: + - - meta: + type: map + description: | + Groovy Map containing sample information + e.g. [ id:'test', single_end:false ] + - "*.sam": + type: file + description: Output SAM file containing read alignments + pattern: "*.{sam}" + ontologies: + - edam: "http://edamontology.org/format_2573" # SAM + bam: + - - meta: + type: map + description: | + Groovy Map containing sample information + e.g. [ id:'test', single_end:false ] + - "*.bam": + type: file + description: Output BAM file containing read alignments + pattern: "*.{bam}" + ontologies: + - edam: "http://edamontology.org/format_2572" # BAM + cram: + - - meta: + type: map + description: | + Groovy Map containing sample information + e.g. [ id:'test', single_end:false ] + - "*.cram": + type: file + description: Output CRAM file containing read alignments + pattern: "*.{cram}" + ontologies: + - edam: "http://edamontology.org/format_3462" # CRAM + crai: + - - meta: + type: map + description: | + Groovy Map containing sample information + e.g. [ id:'test', single_end:false ] + - "*.crai": + type: file + description: Index file for CRAM file + pattern: "*.{crai}" + ontologies: [] + csi: + - - meta: + type: map + description: | + Groovy Map containing sample information + e.g. [ id:'test', single_end:false ] + - "*.csi": + type: file + description: Index file for BAM file + pattern: "*.{csi}" + ontologies: [] + versions: + - versions.yml: + type: file + description: File containing software versions + pattern: "versions.yml" + ontologies: + - edam: http://edamontology.org/format_3750 # YAML +authors: + - "@maxulysse" + - "@matthdsm" +maintainers: + - "@maxulysse" + - "@matthdsm" diff --git a/modules/nf-core/bwamem2/mem/tests/main.nf.test b/modules/nf-core/bwamem2/mem/tests/main.nf.test new file mode 100644 index 00000000..9e0ab14a --- /dev/null +++ b/modules/nf-core/bwamem2/mem/tests/main.nf.test @@ -0,0 +1,179 @@ +nextflow_process { + + name "Test Process BWAMEM2_MEM" + script "../main.nf" + process "BWAMEM2_MEM" + + tag "modules" + tag "modules_nfcore" + tag "bwamem2" + tag "bwamem2/mem" + tag "bwamem2/index" + + setup { + run("BWAMEM2_INDEX") { + script "../../index/main.nf" + process { + """ + input[0] = Channel.of([ + [:], // meta map + [file(params.modules_testdata_base_path + 'genomics/sarscov2/genome/genome.fasta', checkIfExists: true)] + ]) + """ + } + } + } + + test("sarscov2 - fastq, index, fasta, false") { + + when { + process { + """ + input[0] = Channel.of([ + [ id:'test', single_end:true ], // meta map + [file(params.modules_testdata_base_path + 'genomics/sarscov2/illumina/fastq/test_1.fastq.gz', checkIfExists: true)] + ]) + input[1] = BWAMEM2_INDEX.out.index + input[2] = Channel.of([[:], [file(params.modules_testdata_base_path + 'genomics/sarscov2/genome/genome.fasta', checkIfExists: true)]]) + input[3] = false + """ + } + } + + then { + assertAll( + { assert process.success }, + { assert snapshot( + bam(process.out.bam[0][1]).getHeaderMD5(), + bam(process.out.bam[0][1]).getReadsMD5(), + process.out.versions + ).match() } + ) + } + + } + + test("sarscov2 - fastq, index, fasta, true") { + + when { + process { + """ + input[0] = Channel.of([ + [ id:'test', single_end:true ], // meta map + [file(params.modules_testdata_base_path + 'genomics/sarscov2/illumina/fastq/test_1.fastq.gz', checkIfExists: true)] + ]) + input[1] = BWAMEM2_INDEX.out.index + input[2] = Channel.of([[:], [file(params.modules_testdata_base_path + 'genomics/sarscov2/genome/genome.fasta', checkIfExists: true)]]) + input[3] = true + """ + } + } + + then { + assertAll( + { assert process.success }, + { assert snapshot( + bam(process.out.bam[0][1]).getHeaderMD5(), + bam(process.out.bam[0][1]).getReadsMD5(), + process.out.versions + ).match() } + ) + } + + } + + test("sarscov2 - [fastq1, fastq2], index, fasta, false") { + + when { + process { + """ + input[0] = Channel.of([ + [ id:'test', single_end:false ], // meta map + [ + file(params.modules_testdata_base_path + 'genomics/sarscov2/illumina/fastq/test_1.fastq.gz', checkIfExists: true), + file(params.modules_testdata_base_path + 'genomics/sarscov2/illumina/fastq/test_2.fastq.gz', checkIfExists: true) + ] + ]) + input[1] = BWAMEM2_INDEX.out.index + input[2] = Channel.of([[:], [file(params.modules_testdata_base_path + 'genomics/sarscov2/genome/genome.fasta', checkIfExists: true)]]) + input[3] = false + """ + } + } + + then { + assertAll( + { assert process.success }, + { assert snapshot( + bam(process.out.bam[0][1]).getHeaderMD5(), + bam(process.out.bam[0][1]).getReadsMD5(), + process.out.versions + ).match() } + ) + } + + } + + test("sarscov2 - [fastq1, fastq2], index, fasta, true") { + + when { + process { + """ + input[0] = Channel.of([ + [ id:'test', single_end:false ], // meta map + [ + file(params.modules_testdata_base_path + 'genomics/sarscov2/illumina/fastq/test_1.fastq.gz', checkIfExists: true), + file(params.modules_testdata_base_path + 'genomics/sarscov2/illumina/fastq/test_2.fastq.gz', checkIfExists: true) + ] + ]) + input[1] = BWAMEM2_INDEX.out.index + input[2] = Channel.of([[:], [file(params.modules_testdata_base_path + 'genomics/sarscov2/genome/genome.fasta', checkIfExists: true)]]) + input[3] = true + """ + } + } + + then { + assertAll( + { assert process.success }, + { assert snapshot( + bam(process.out.bam[0][1]).getHeaderMD5(), + bam(process.out.bam[0][1]).getReadsMD5(), + process.out.versions + ).match() } + ) + } + + } + + test("sarscov2 - [fastq1, fastq2], index, fasta, true - 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/fastq/test_1.fastq.gz', checkIfExists: true), + file(params.modules_testdata_base_path + 'genomics/sarscov2/illumina/fastq/test_2.fastq.gz', checkIfExists: true) + ] + ]) + input[1] = BWAMEM2_INDEX.out.index + input[2] = Channel.of([[:], [file(params.modules_testdata_base_path + 'genomics/sarscov2/genome/genome.fasta', checkIfExists: true)]]) + input[3] = true + """ + } + } + + then { + assertAll( + { assert process.success }, + { assert snapshot(process.out).match() } + ) + } + + } + +} diff --git a/modules/nf-core/bwamem2/mem/tests/main.nf.test.snap b/modules/nf-core/bwamem2/mem/tests/main.nf.test.snap new file mode 100644 index 00000000..b7d40a68 --- /dev/null +++ b/modules/nf-core/bwamem2/mem/tests/main.nf.test.snap @@ -0,0 +1,129 @@ +{ + "sarscov2 - [fastq1, fastq2], index, fasta, false": { + "content": [ + "e414c2d48e2e44c2c52c20ecd88e8bd8", + "57aeef88ed701a8ebc8e2f0a381b2a6", + [ + "versions.yml:md5,3574188ab1f33fd99cff9f5562dfb885" + ] + ], + "meta": { + "nf-test": "0.9.2", + "nextflow": "25.04.7" + }, + "timestamp": "2025-09-23T11:44:52.73673293" + }, + "sarscov2 - [fastq1, fastq2], index, fasta, true - stub": { + "content": [ + { + "0": [ + + ], + "1": [ + [ + { + "id": "test", + "single_end": false + }, + "test.bam:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "2": [ + + ], + "3": [ + + ], + "4": [ + [ + { + "id": "test", + "single_end": false + }, + "test.csi:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "5": [ + "versions.yml:md5,3574188ab1f33fd99cff9f5562dfb885" + ], + "bam": [ + [ + { + "id": "test", + "single_end": false + }, + "test.bam:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "crai": [ + + ], + "cram": [ + + ], + "csi": [ + [ + { + "id": "test", + "single_end": false + }, + "test.csi:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "sam": [ + + ], + "versions": [ + "versions.yml:md5,3574188ab1f33fd99cff9f5562dfb885" + ] + } + ], + "meta": { + "nf-test": "0.9.2", + "nextflow": "25.04.7" + }, + "timestamp": "2025-09-23T11:45:14.834888709" + }, + "sarscov2 - [fastq1, fastq2], index, fasta, true": { + "content": [ + "716ed1ef39deaad346ca7cf86e08f959", + "af8628d9df18b2d3d4f6fd47ef2bb872", + [ + "versions.yml:md5,3574188ab1f33fd99cff9f5562dfb885" + ] + ], + "meta": { + "nf-test": "0.9.2", + "nextflow": "25.04.7" + }, + "timestamp": "2025-09-23T11:45:04.750057645" + }, + "sarscov2 - fastq, index, fasta, false": { + "content": [ + "283a83f604f3f5338acedfee349dccf4", + "798439cbd7fd81cbcc5078022dc5479d", + [ + "versions.yml:md5,3574188ab1f33fd99cff9f5562dfb885" + ] + ], + "meta": { + "nf-test": "0.9.2", + "nextflow": "25.04.7" + }, + "timestamp": "2025-09-23T11:44:28.57550711" + }, + "sarscov2 - fastq, index, fasta, true": { + "content": [ + "ed99048bb552cac58e39923b550b6d5b", + "94fcf617f5b994584c4e8d4044e16b4f", + [ + "versions.yml:md5,3574188ab1f33fd99cff9f5562dfb885" + ] + ], + "meta": { + "nf-test": "0.9.2", + "nextflow": "25.04.7" + }, + "timestamp": "2025-09-23T11:44:40.437183765" + } +} \ No newline at end of file diff --git a/modules/nf-core/picard/markduplicates/environment.yml b/modules/nf-core/picard/markduplicates/environment.yml new file mode 100644 index 00000000..b4ac4fe0 --- /dev/null +++ b/modules/nf-core/picard/markduplicates/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: + # renovate: datasource=conda depName=bioconda/picard + - bioconda::picard=3.4.0 diff --git a/modules/nf-core/picard/markduplicates/main.nf b/modules/nf-core/picard/markduplicates/main.nf new file mode 100644 index 00000000..10621e01 --- /dev/null +++ b/modules/nf-core/picard/markduplicates/main.nf @@ -0,0 +1,61 @@ +process PICARD_MARKDUPLICATES { + 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/08/0861295baa7c01fc593a9da94e82b44a729dcaf8da92be8e565da109aa549b25/data' : + 'community.wave.seqera.io/library/picard:3.4.0--e9963040df0a9bf6' }" + + input: + tuple val(meta), path(reads) + tuple val(meta2), path(fasta) + tuple val(meta3), path(fai) + + output: + tuple val(meta), path("*.bam") , emit: bam, optional: true + tuple val(meta), path("*.bai") , emit: bai, optional: true + tuple val(meta), path("*.cram"), emit: cram, optional: true + tuple val(meta), path("*.metrics.txt"), emit: metrics + tuple val("${task.process}"), val('picard'), eval("picard MarkDuplicates --version 2>&1 | sed -n 's/^Version:*//p'"), topic: versions, emit: versions_picard + + when: + task.ext.when == null || task.ext.when + + script: + def args = task.ext.args ?: '' + def prefix = task.ext.prefix ?: "${meta.id}" + def suffix = task.ext.suffix ?: "${reads.getExtension()}" + def reference = fasta ? "--REFERENCE_SEQUENCE ${fasta}" : "" + def avail_mem = 3072 + if (!task.memory) { + log.info '[Picard MarkDuplicates] Available memory not known - defaulting to 3GB. Specify process memory requirements to change this.' + } else { + avail_mem = (task.memory.mega*0.8).intValue() + } + + if ("$reads" == "${prefix}.${suffix}") error "Input and output names are the same, use \"task.ext.prefix\" to disambiguate!" + + """ + picard \\ + -Xmx${avail_mem}M \\ + MarkDuplicates \\ + $args \\ + --INPUT $reads \\ + --OUTPUT ${prefix}.${suffix} \\ + $reference \\ + --METRICS_FILE ${prefix}.MarkDuplicates.metrics.txt + + """ + + stub: + def prefix = task.ext.prefix ?: "${meta.id}" + def suffix = task.ext.suffix ?: "${reads.getExtension()}" + if ("$reads" == "${prefix}.${suffix}") error "Input and output names are the same, use \"task.ext.prefix\" to disambiguate!" + """ + touch ${prefix}.${suffix} + touch ${prefix}.${suffix}.bai + touch ${prefix}.MarkDuplicates.metrics.txt + + """ +} diff --git a/modules/nf-core/picard/markduplicates/meta.yml b/modules/nf-core/picard/markduplicates/meta.yml new file mode 100644 index 00000000..0ec99c7f --- /dev/null +++ b/modules/nf-core/picard/markduplicates/meta.yml @@ -0,0 +1,126 @@ +name: picard_markduplicates +description: Locate and tag duplicate reads in a BAM file +keywords: + - markduplicates + - pcr + - duplicates + - bam + - sam + - cram +tools: + - picard: + description: | + A set of command line tools (in Java) for manipulating high-throughput sequencing (HTS) + data and formats such as SAM/BAM/CRAM and VCF. + homepage: https://broadinstitute.github.io/picard/ + documentation: https://broadinstitute.github.io/picard/ + licence: ["MIT"] + identifier: biotools:picard_tools +input: + - - meta: + type: map + description: | + Groovy Map containing sample information + e.g. [ id:'test', single_end:false ] + - reads: + type: file + description: Sequence reads file, can be SAM/BAM/CRAM format + pattern: "*.{bam,cram,sam}" + ontologies: [] + - - meta2: + type: map + description: | + Groovy Map containing reference information + e.g. [ id:'genome' ] + - fasta: + type: file + description: Reference genome fasta file, required for CRAM input + pattern: "*.{fasta,fa}" + ontologies: [] + - - meta3: + type: map + description: | + Groovy Map containing reference information + e.g. [ id:'genome' ] + - fai: + type: file + description: Reference genome fasta index + pattern: "*.{fai}" + ontologies: [] +output: + bam: + - - meta: + type: map + description: | + Groovy Map containing sample information + e.g. [ id:'test', single_end:false ] + - "*.bam": + type: file + description: BAM file with duplicate reads marked/removed + pattern: "*.{bam}" + ontologies: [] + bai: + - - meta: + type: map + description: | + Groovy Map containing sample information + e.g. [ id:'test', single_end:false ] + - "*.bai": + type: file + description: An optional BAM index file. If desired, --CREATE_INDEX must be + passed as a flag + pattern: "*.{bai}" + ontologies: [] + cram: + - - meta: + type: map + description: | + Groovy Map containing sample information + e.g. [ id:'test', single_end:false ] + - "*.cram": + type: file + description: Output CRAM file + pattern: "*.{cram}" + ontologies: [] + metrics: + - - meta: + type: map + description: | + Groovy Map containing sample information + e.g. [ id:'test', single_end:false ] + - "*.metrics.txt": + type: file + description: Duplicate metrics file generated by picard + pattern: "*.{metrics.txt}" + ontologies: [] + versions_picard: + - - ${task.process}: + type: string + description: The process the versions were collected from + - picard: + type: string + description: The tool name + - "picard MarkDuplicates --version 2>&1 | sed -n 's/^Version:*//p'": + 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 + - picard: + type: string + description: The tool name + - "picard MarkDuplicates --version 2>&1 | sed -n 's/^Version:*//p'": + type: string + description: The command used to generate the version of the tool + +authors: + - "@drpatelh" + - "@projectoriented" + - "@ramprasadn" +maintainers: + - "@drpatelh" + - "@projectoriented" + - "@ramprasadn" diff --git a/modules/nf-core/picard/markduplicates/tests/main.nf.test b/modules/nf-core/picard/markduplicates/tests/main.nf.test new file mode 100644 index 00000000..e18723be --- /dev/null +++ b/modules/nf-core/picard/markduplicates/tests/main.nf.test @@ -0,0 +1,173 @@ +nextflow_process { + + name "Test Process PICARD_MARKDUPLICATES" + script "../main.nf" + process "PICARD_MARKDUPLICATES" + config "./nextflow.config" + tag "modules" + tag "modules_nfcore" + tag "picard" + tag "picard/markduplicates" + + test("sarscov2 [unsorted 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] = [ [:], [] ] + """ + } + } + + then { + assertAll( + { assert process.success }, + { assert snapshot( + file(process.out.bam[0][1]).name, + path(process.out.metrics.get(0).get(1)).readLines()[0..2], + process.out.findAll { key, val -> key.startsWith("versions") }) + .match() } + ) + } + } + + test("sarscov2 [sorted 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.sorted.bam', checkIfExists: true) + ]) + input[1] = [ [:], [] ] + input[2] = [ [:], [] ] + """ + } + } + + then { + assertAll( + { assert process.success }, + { assert snapshot( + file(process.out.bam[0][1]).name, + path(process.out.metrics.get(0).get(1)).readLines()[0..2], + process.out.findAll { key, val -> key.startsWith("versions") }) + .match() } + ) + } + } + + test("homo_sapiens [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) + ]) + input[1] = Channel.of([ + [ id:'genome' ], + file(params.modules_testdata_base_path + 'genomics/homo_sapiens/genome/genome.fasta', checkIfExists: true) + ]) + input[2] = Channel.of([ + [ id:'genome' ], + file(params.modules_testdata_base_path + 'genomics/homo_sapiens/genome/genome.fasta.fai', checkIfExists: true) + ]) + """ + } + } + + then { + assertAll( + { assert process.success }, + { assert snapshot( + file(process.out.cram[0][1]).name, + path(process.out.metrics.get(0).get(1)).readLines()[0..2], + process.out.findAll { key, val -> key.startsWith("versions") }) + .match() } + ) + } + } + + test("sarscov2 [unsorted bam] - 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.bam', checkIfExists: true) + ]) + input[1] = [ [:], [] ] + input[2] = [ [:], [] ] + """ + } + } + + then { + assertAll( + { assert process.success }, + { assert snapshot(process.out).match() } + ) + } + } + + test("sarscov2 [sorted bam] - 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) + ]) + input[1] = [ [:], [] ] + input[2] = [ [:], [] ] + """ + } + } + + then { + assertAll( + { assert process.success }, + { assert snapshot(process.out).match() } + ) + } + } + + test("homo_sapiens [cram] - 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.sorted.cram', checkIfExists: true) + ]) + input[1] = Channel.of([ + [ id:'genome' ], + file(params.modules_testdata_base_path + 'genomics/homo_sapiens/genome/genome.fasta', checkIfExists: true) + ]) + input[2] = Channel.of([ + [ id:'genome' ], + file(params.modules_testdata_base_path + 'genomics/homo_sapiens/genome/genome.fasta.fai', checkIfExists: true) + ]) + """ + } + } + + then { + assertAll( + { assert process.success }, + { assert snapshot(process.out).match() } + ) + } + } +} diff --git a/modules/nf-core/picard/markduplicates/tests/main.nf.test.snap b/modules/nf-core/picard/markduplicates/tests/main.nf.test.snap new file mode 100644 index 00000000..84801384 --- /dev/null +++ b/modules/nf-core/picard/markduplicates/tests/main.nf.test.snap @@ -0,0 +1,329 @@ +{ + "sarscov2 [sorted bam] - stub": { + "content": [ + { + "0": [ + [ + { + "id": "test", + "single_end": false + }, + "test.marked.bam:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "1": [ + [ + { + "id": "test", + "single_end": false + }, + "test.marked.bam.bai:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "2": [ + + ], + "3": [ + [ + { + "id": "test", + "single_end": false + }, + "test.marked.MarkDuplicates.metrics.txt:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "4": [ + [ + "PICARD_MARKDUPLICATES", + "picard", + "3.4.0" + ] + ], + "bai": [ + [ + { + "id": "test", + "single_end": false + }, + "test.marked.bam.bai:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "bam": [ + [ + { + "id": "test", + "single_end": false + }, + "test.marked.bam:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "cram": [ + + ], + "metrics": [ + [ + { + "id": "test", + "single_end": false + }, + "test.marked.MarkDuplicates.metrics.txt:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "versions_picard": [ + [ + "PICARD_MARKDUPLICATES", + "picard", + "3.4.0" + ] + ] + } + ], + "meta": { + "nf-test": "0.9.3", + "nextflow": "25.10.2" + }, + "timestamp": "2026-02-02T11:46:02.077382134" + }, + "sarscov2 [unsorted bam] - stub": { + "content": [ + { + "0": [ + [ + { + "id": "test", + "single_end": false + }, + "test.marked.bam:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "1": [ + [ + { + "id": "test", + "single_end": false + }, + "test.marked.bam.bai:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "2": [ + + ], + "3": [ + [ + { + "id": "test", + "single_end": false + }, + "test.marked.MarkDuplicates.metrics.txt:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "4": [ + [ + "PICARD_MARKDUPLICATES", + "picard", + "3.4.0" + ] + ], + "bai": [ + [ + { + "id": "test", + "single_end": false + }, + "test.marked.bam.bai:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "bam": [ + [ + { + "id": "test", + "single_end": false + }, + "test.marked.bam:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "cram": [ + + ], + "metrics": [ + [ + { + "id": "test", + "single_end": false + }, + "test.marked.MarkDuplicates.metrics.txt:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "versions_picard": [ + [ + "PICARD_MARKDUPLICATES", + "picard", + "3.4.0" + ] + ] + } + ], + "meta": { + "nf-test": "0.9.3", + "nextflow": "25.10.2" + }, + "timestamp": "2026-02-02T11:45:49.985589186" + }, + "sarscov2 [unsorted bam]": { + "content": [ + "test.marked.bam", + [ + "## htsjdk.samtools.metrics.StringHeader", + "# MarkDuplicates --INPUT test.paired_end.bam --OUTPUT test.marked.bam --METRICS_FILE test.marked.MarkDuplicates.metrics.txt --ASSUME_SORT_ORDER queryname --MAX_SEQUENCES_FOR_DISK_READ_ENDS_MAP 50000 --MAX_FILE_HANDLES_FOR_READ_ENDS_MAP 8000 --SORTING_COLLECTION_SIZE_RATIO 0.25 --TAG_DUPLICATE_SET_MEMBERS false --REMOVE_SEQUENCING_DUPLICATES false --TAGGING_POLICY DontTag --CLEAR_DT true --DUPLEX_UMI false --FLOW_MODE false --FLOW_DUP_STRATEGY FLOW_QUALITY_SUM_STRATEGY --FLOW_USE_END_IN_UNPAIRED_READS false --FLOW_USE_UNPAIRED_CLIPPED_END false --FLOW_UNPAIRED_END_UNCERTAINTY 0 --FLOW_UNPAIRED_START_UNCERTAINTY 0 --FLOW_SKIP_FIRST_N_FLOWS 0 --FLOW_Q_IS_KNOWN_END false --FLOW_EFFECTIVE_QUALITY_THRESHOLD 15 --ADD_PG_TAG_TO_READS true --REMOVE_DUPLICATES false --ASSUME_SORTED false --DUPLICATE_SCORING_STRATEGY SUM_OF_BASE_QUALITIES --PROGRAM_RECORD_ID MarkDuplicates --PROGRAM_GROUP_NAME MarkDuplicates --READ_NAME_REGEX --OPTICAL_DUPLICATE_PIXEL_DISTANCE 100 --MAX_OPTICAL_DUPLICATE_SET_SIZE 300000 --VERBOSITY INFO --QUIET false --VALIDATION_STRINGENCY STRICT --COMPRESSION_LEVEL 5 --MAX_RECORDS_IN_RAM 500000 --CREATE_INDEX false --CREATE_MD5_FILE false --help false --version false --showHidden false --USE_JDK_DEFLATER false --USE_JDK_INFLATER false", + "## htsjdk.samtools.metrics.StringHeader" + ], + { + "versions_picard": [ + [ + "PICARD_MARKDUPLICATES", + "picard", + "3.4.0" + ] + ] + } + ], + "meta": { + "nf-test": "0.9.3", + "nextflow": "25.10.2" + }, + "timestamp": "2026-02-02T11:42:49.704752548" + }, + "sarscov2 [sorted bam]": { + "content": [ + "test.marked.bam", + [ + "## htsjdk.samtools.metrics.StringHeader", + "# MarkDuplicates --INPUT test.paired_end.sorted.bam --OUTPUT test.marked.bam --METRICS_FILE test.marked.MarkDuplicates.metrics.txt --ASSUME_SORT_ORDER queryname --MAX_SEQUENCES_FOR_DISK_READ_ENDS_MAP 50000 --MAX_FILE_HANDLES_FOR_READ_ENDS_MAP 8000 --SORTING_COLLECTION_SIZE_RATIO 0.25 --TAG_DUPLICATE_SET_MEMBERS false --REMOVE_SEQUENCING_DUPLICATES false --TAGGING_POLICY DontTag --CLEAR_DT true --DUPLEX_UMI false --FLOW_MODE false --FLOW_DUP_STRATEGY FLOW_QUALITY_SUM_STRATEGY --FLOW_USE_END_IN_UNPAIRED_READS false --FLOW_USE_UNPAIRED_CLIPPED_END false --FLOW_UNPAIRED_END_UNCERTAINTY 0 --FLOW_UNPAIRED_START_UNCERTAINTY 0 --FLOW_SKIP_FIRST_N_FLOWS 0 --FLOW_Q_IS_KNOWN_END false --FLOW_EFFECTIVE_QUALITY_THRESHOLD 15 --ADD_PG_TAG_TO_READS true --REMOVE_DUPLICATES false --ASSUME_SORTED false --DUPLICATE_SCORING_STRATEGY SUM_OF_BASE_QUALITIES --PROGRAM_RECORD_ID MarkDuplicates --PROGRAM_GROUP_NAME MarkDuplicates --READ_NAME_REGEX --OPTICAL_DUPLICATE_PIXEL_DISTANCE 100 --MAX_OPTICAL_DUPLICATE_SET_SIZE 300000 --VERBOSITY INFO --QUIET false --VALIDATION_STRINGENCY STRICT --COMPRESSION_LEVEL 5 --MAX_RECORDS_IN_RAM 500000 --CREATE_INDEX false --CREATE_MD5_FILE false --help false --version false --showHidden false --USE_JDK_DEFLATER false --USE_JDK_INFLATER false", + "## htsjdk.samtools.metrics.StringHeader" + ], + { + "versions_picard": [ + [ + "PICARD_MARKDUPLICATES", + "picard", + "3.4.0" + ] + ] + } + ], + "meta": { + "nf-test": "0.9.3", + "nextflow": "25.10.2" + }, + "timestamp": "2026-02-02T11:44:17.407572621" + }, + "homo_sapiens [cram]": { + "content": [ + "test.marked.cram", + [ + "## htsjdk.samtools.metrics.StringHeader", + "# MarkDuplicates --INPUT test.paired_end.sorted.cram --OUTPUT test.marked.cram --METRICS_FILE test.marked.MarkDuplicates.metrics.txt --ASSUME_SORT_ORDER queryname --REFERENCE_SEQUENCE genome.fasta --MAX_SEQUENCES_FOR_DISK_READ_ENDS_MAP 50000 --MAX_FILE_HANDLES_FOR_READ_ENDS_MAP 8000 --SORTING_COLLECTION_SIZE_RATIO 0.25 --TAG_DUPLICATE_SET_MEMBERS false --REMOVE_SEQUENCING_DUPLICATES false --TAGGING_POLICY DontTag --CLEAR_DT true --DUPLEX_UMI false --FLOW_MODE false --FLOW_DUP_STRATEGY FLOW_QUALITY_SUM_STRATEGY --FLOW_USE_END_IN_UNPAIRED_READS false --FLOW_USE_UNPAIRED_CLIPPED_END false --FLOW_UNPAIRED_END_UNCERTAINTY 0 --FLOW_UNPAIRED_START_UNCERTAINTY 0 --FLOW_SKIP_FIRST_N_FLOWS 0 --FLOW_Q_IS_KNOWN_END false --FLOW_EFFECTIVE_QUALITY_THRESHOLD 15 --ADD_PG_TAG_TO_READS true --REMOVE_DUPLICATES false --ASSUME_SORTED false --DUPLICATE_SCORING_STRATEGY SUM_OF_BASE_QUALITIES --PROGRAM_RECORD_ID MarkDuplicates --PROGRAM_GROUP_NAME MarkDuplicates --READ_NAME_REGEX --OPTICAL_DUPLICATE_PIXEL_DISTANCE 100 --MAX_OPTICAL_DUPLICATE_SET_SIZE 300000 --VERBOSITY INFO --QUIET false --VALIDATION_STRINGENCY STRICT --COMPRESSION_LEVEL 5 --MAX_RECORDS_IN_RAM 500000 --CREATE_INDEX false --CREATE_MD5_FILE false --help false --version false --showHidden false --USE_JDK_DEFLATER false --USE_JDK_INFLATER false", + "## htsjdk.samtools.metrics.StringHeader" + ], + { + "versions_picard": [ + [ + "PICARD_MARKDUPLICATES", + "picard", + "3.4.0" + ] + ] + } + ], + "meta": { + "nf-test": "0.9.3", + "nextflow": "25.10.2" + }, + "timestamp": "2026-02-02T11:45:33.412603893" + }, + "homo_sapiens [cram] - stub": { + "content": [ + { + "0": [ + + ], + "1": [ + [ + { + "id": "test", + "single_end": false + }, + "test.marked.cram.bai:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "2": [ + [ + { + "id": "test", + "single_end": false + }, + "test.marked.cram:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "3": [ + [ + { + "id": "test", + "single_end": false + }, + "test.marked.MarkDuplicates.metrics.txt:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "4": [ + [ + "PICARD_MARKDUPLICATES", + "picard", + "3.4.0" + ] + ], + "bai": [ + [ + { + "id": "test", + "single_end": false + }, + "test.marked.cram.bai:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "bam": [ + + ], + "cram": [ + [ + { + "id": "test", + "single_end": false + }, + "test.marked.cram:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "metrics": [ + [ + { + "id": "test", + "single_end": false + }, + "test.marked.MarkDuplicates.metrics.txt:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "versions_picard": [ + [ + "PICARD_MARKDUPLICATES", + "picard", + "3.4.0" + ] + ] + } + ], + "meta": { + "nf-test": "0.9.3", + "nextflow": "25.10.2" + }, + "timestamp": "2026-02-02T11:46:18.599127485" + } +} \ No newline at end of file diff --git a/modules/nf-core/picard/markduplicates/tests/nextflow.config b/modules/nf-core/picard/markduplicates/tests/nextflow.config new file mode 100644 index 00000000..02818dd6 --- /dev/null +++ b/modules/nf-core/picard/markduplicates/tests/nextflow.config @@ -0,0 +1,6 @@ +process { + withName: PICARD_MARKDUPLICATES { + ext.prefix = { "${meta.id}.marked" } + ext.args = '--ASSUME_SORT_ORDER queryname' + } +} diff --git a/modules/nf-core/samtools/faidx/environment.yml b/modules/nf-core/samtools/faidx/environment.yml new file mode 100644 index 00000000..89e12a64 --- /dev/null +++ b/modules/nf-core/samtools/faidx/environment.yml @@ -0,0 +1,10 @@ +--- +# 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.22.1 + # renovate: datasource=conda depName=bioconda/samtools + - bioconda::samtools=1.22.1 diff --git a/modules/nf-core/samtools/faidx/main.nf b/modules/nf-core/samtools/faidx/main.nf new file mode 100644 index 00000000..57a03497 --- /dev/null +++ b/modules/nf-core/samtools/faidx/main.nf @@ -0,0 +1,50 @@ +process SAMTOOLS_FAIDX { + tag "$fasta" + label 'process_single' + + conda "${moduleDir}/environment.yml" + container "${ workflow.containerEngine == 'singularity' && !task.ext.singularity_pull_docker_container ? + 'https://depot.galaxyproject.org/singularity/samtools:1.22.1--h96c455f_0' : + 'biocontainers/samtools:1.22.1--h96c455f_0' }" + + input: + tuple val(meta), path(fasta) + tuple val(meta2), path(fai) + val get_sizes + + output: + tuple val(meta), path ("*.{fa,fasta}") , emit: fa, optional: true + tuple val(meta), path ("*.sizes") , emit: sizes, optional: true + tuple val(meta), path ("*.fai") , emit: fai, optional: true + tuple val(meta), path ("*.gzi") , emit: gzi, 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 get_sizes_command = get_sizes ? "cut -f 1,2 ${fasta}.fai > ${fasta}.sizes" : '' + """ + samtools \\ + faidx \\ + $fasta \\ + $args + + ${get_sizes_command} + """ + + stub: + def match = (task.ext.args =~ /-o(?:utput)?\s(.*)\s?/).findAll() + def fastacmd = match[0] ? "touch ${match[0][1]}" : '' + def get_sizes_command = get_sizes ? "touch ${fasta}.sizes" : '' + """ + ${fastacmd} + touch ${fasta}.fai + if [[ "${fasta.extension}" == "gz" ]]; then + touch ${fasta}.gzi + fi + + ${get_sizes_command} + """ +} diff --git a/modules/nf-core/samtools/faidx/meta.yml b/modules/nf-core/samtools/faidx/meta.yml new file mode 100644 index 00000000..163c3015 --- /dev/null +++ b/modules/nf-core/samtools/faidx/meta.yml @@ -0,0 +1,117 @@ +name: samtools_faidx +description: Index FASTA file, and optionally generate a file of chromosome sizes +keywords: + - index + - fasta + - faidx + - chromosome +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 reference information + e.g. [ id:'test' ] + - fasta: + type: file + description: FASTA file + pattern: "*.{fa,fasta}" + ontologies: [] + - - meta2: + type: map + description: | + Groovy Map containing reference information + e.g. [ id:'test' ] + - fai: + type: file + description: FASTA index file + pattern: "*.{fai}" + ontologies: [] + - get_sizes: + type: boolean + description: use cut to get the sizes of the index (true) or not (false) + +output: + fa: + - - meta: + type: map + description: | + Groovy Map containing sample information + e.g. [ id:'test', single_end:false ] + - "*.{fa,fasta}": + type: file + description: FASTA file + pattern: "*.{fa}" + ontologies: [] + sizes: + - - meta: + type: map + description: | + Groovy Map containing sample information + e.g. [ id:'test', single_end:false ] + - "*.sizes": + type: file + description: File containing chromosome lengths + pattern: "*.{sizes}" + ontologies: [] + fai: + - - meta: + type: map + description: | + Groovy Map containing sample information + e.g. [ id:'test', single_end:false ] + - "*.fai": + type: file + description: FASTA index file + pattern: "*.{fai}" + ontologies: [] + gzi: + - - meta: + type: map + description: | + Groovy Map containing sample information + e.g. [ id:'test', single_end:false ] + - "*.gzi": + type: file + description: Optional gzip index file for compressed inputs + pattern: "*.gzi" + 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" + - "@phue" +maintainers: + - "@maxulysse" + - "@phue" diff --git a/modules/nf-core/samtools/faidx/tests/main.nf.test b/modules/nf-core/samtools/faidx/tests/main.nf.test new file mode 100644 index 00000000..02ba5040 --- /dev/null +++ b/modules/nf-core/samtools/faidx/tests/main.nf.test @@ -0,0 +1,245 @@ +nextflow_process { + + name "Test Process SAMTOOLS_FAIDX" + script "../main.nf" + process "SAMTOOLS_FAIDX" + + tag "modules" + tag "modules_nfcore" + tag "samtools" + tag "samtools/faidx" + config "./nextflow.config" + + test("test_samtools_faidx") { + + when { + params { + module_args = '' + } + process { + """ + input[0] = [ [ id:'test', single_end:false ], // meta map + file(params.modules_testdata_base_path + 'genomics/sarscov2/genome/genome.fasta', checkIfExists: true) ] + input[1] = [[],[]] + input[2] = false + """ + } + } + + then { + assert process.success + assertAll( + { assert snapshot(process.out).match()} + ) + } + } + + test("test_samtools_faidx_bgzip") { + + when { + params { + module_args = '' + } + process { + """ + input[0] = [ [ id:'test', single_end:false ], // meta map + file(params.modules_testdata_base_path + 'genomics/sarscov2/genome/genome.fasta.gz', checkIfExists: true)] + input[1] = [[],[]] + input[2] = false + """ + } + } + + then { + assert process.success + assertAll( + { assert snapshot(process.out).match()} + ) + } + } + + test("test_samtools_faidx_fasta") { + + when { + params { + module_args = 'MT192765.1 -o extract.fa' + } + process { + """ + input[0] = [ [ id:'test', single_end:false ], // meta map + file(params.modules_testdata_base_path + 'genomics/sarscov2/genome/genome.fasta', checkIfExists: true) ] + input[1] = [ [ id:'test', single_end:false ], // meta map + file(params.modules_testdata_base_path + 'genomics/sarscov2/genome/genome.fasta.fai', checkIfExists: true) ] + input[2] = false + """ + } + } + + then { + assert process.success + assertAll( + { assert snapshot(process.out).match()} + ) + } + } + + test("test_samtools_faidx_stub_fasta") { + + options "-stub" + when { + params { + module_args = '-o extract.fa' + } + process { + """ + input[0] = [ [ id:'test', single_end:false ], // meta map + file(params.modules_testdata_base_path + 'genomics/sarscov2/genome/genome.fasta', checkIfExists: true) ] + input[1] = [ [ id:'test', single_end:false ], // meta map + file(params.modules_testdata_base_path + 'genomics/sarscov2/genome/genome.fasta.fai', checkIfExists: true) ] + input[2] = false + """ + } + } + + then { + assert process.success + assertAll( + { assert snapshot(process.out).match()} + ) + } + } + + test("test_samtools_faidx_stub_fai") { + + options "-stub" + when { + params { + module_args = '' + } + process { + """ + input[0] = [ [ id:'test', single_end:false ], // meta map + file(params.modules_testdata_base_path + 'genomics/sarscov2/genome/genome.fasta', checkIfExists: true) ] + input[1] = [[],[]] + input[2] = false + """ + } + } + + then { + assert process.success + assertAll( + { assert snapshot(process.out).match()} + ) + } + } + + test("test_samtools_faidx_get_sizes") { + + when { + params { + module_args = '' + } + process { + """ + input[0] = Channel.of([ + [ id:'test' ], // meta map + file(params.modules_testdata_base_path + 'genomics/sarscov2/genome/genome.fasta', checkIfExists: true) + ]) + input[1] = [[],[]] + input[2] = true + """ + } + } + + then { + assert process.success + assertAll( + { assert snapshot(process.out).match()} + ) + } + } + + test("test_samtools_faidx_get_sizes_bgzip") { + + when { + params { + module_args = '' + } + process { + """ + input[0] = Channel.of([ + [ id:'test' ], // meta map + file(params.modules_testdata_base_path + 'genomics/sarscov2/genome/genome.fasta.gz', checkIfExists: true) + ]) + input[1] = [[],[]] + input[2] = true + """ + } + } + + then { + assert process.success + assertAll( + { assert snapshot(process.out).match()} + ) + } + } + + test("test_samtools_faidx_get_sizes - stub") { + + options "-stub" + + when { + params { + module_args = '' + } + process { + """ + input[0] = Channel.of([ + [ id:'test' ], // meta map + file(params.modules_testdata_base_path + 'genomics/sarscov2/genome/genome.fasta', checkIfExists: true) + ]) + input[1] = [[],[]] + input[2] = true + """ + } + } + + then { + assert process.success + assertAll( + { assert snapshot(process.out).match()} + ) + } + } + + test("test_samtools_faidx_get_sizes_bgzip - stub") { + + options "-stub" + + when { + params { + module_args = '' + } + process { + """ + input[0] = Channel.of([ + [ id:'test' ], // meta map + file(params.modules_testdata_base_path + 'genomics/sarscov2/genome/genome.fasta.gz', checkIfExists: true) + ]) + input[1] = [[],[]] + input[2] = true + """ + } + } + + then { + assert process.success + assertAll( + { assert snapshot(process.out).match()} + ) + } + } + +} diff --git a/modules/nf-core/samtools/faidx/tests/main.nf.test.snap b/modules/nf-core/samtools/faidx/tests/main.nf.test.snap new file mode 100644 index 00000000..565d20e7 --- /dev/null +++ b/modules/nf-core/samtools/faidx/tests/main.nf.test.snap @@ -0,0 +1,615 @@ +{ + "test_samtools_faidx": { + "content": [ + { + "0": [ + + ], + "1": [ + + ], + "2": [ + [ + { + "id": "test", + "single_end": false + }, + "genome.fasta.fai:md5,9da2a56e2853dc8c0b86a9e7229c9fe5" + ] + ], + "3": [ + + ], + "4": [ + [ + "SAMTOOLS_FAIDX", + "samtools", + "1.22.1" + ] + ], + "fa": [ + + ], + "fai": [ + [ + { + "id": "test", + "single_end": false + }, + "genome.fasta.fai:md5,9da2a56e2853dc8c0b86a9e7229c9fe5" + ] + ], + "gzi": [ + + ], + "sizes": [ + + ], + "versions_samtools": [ + [ + "SAMTOOLS_FAIDX", + "samtools", + "1.22.1" + ] + ] + } + ], + "meta": { + "nf-test": "0.9.3", + "nextflow": "25.10.2" + }, + "timestamp": "2025-12-23T14:02:40.159309157" + }, + "test_samtools_faidx_get_sizes_bgzip - stub": { + "content": [ + { + "0": [ + + ], + "1": [ + [ + { + "id": "test" + }, + "genome.fasta.gz.sizes:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "2": [ + [ + { + "id": "test" + }, + "genome.fasta.gz.fai:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "3": [ + [ + { + "id": "test" + }, + "genome.fasta.gz.gzi:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "4": [ + [ + "SAMTOOLS_FAIDX", + "samtools", + "1.22.1" + ] + ], + "fa": [ + + ], + "fai": [ + [ + { + "id": "test" + }, + "genome.fasta.gz.fai:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "gzi": [ + [ + { + "id": "test" + }, + "genome.fasta.gz.gzi:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "sizes": [ + [ + { + "id": "test" + }, + "genome.fasta.gz.sizes:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "versions_samtools": [ + [ + "SAMTOOLS_FAIDX", + "samtools", + "1.22.1" + ] + ] + } + ], + "meta": { + "nf-test": "0.9.3", + "nextflow": "25.10.2" + }, + "timestamp": "2025-12-23T14:03:39.550619177" + }, + "test_samtools_faidx_get_sizes": { + "content": [ + { + "0": [ + + ], + "1": [ + [ + { + "id": "test" + }, + "genome.fasta.sizes:md5,a57c401f27ae5133823fb09fb21c8a3c" + ] + ], + "2": [ + [ + { + "id": "test" + }, + "genome.fasta.fai:md5,9da2a56e2853dc8c0b86a9e7229c9fe5" + ] + ], + "3": [ + + ], + "4": [ + [ + "SAMTOOLS_FAIDX", + "samtools", + "1.22.1" + ] + ], + "fa": [ + + ], + "fai": [ + [ + { + "id": "test" + }, + "genome.fasta.fai:md5,9da2a56e2853dc8c0b86a9e7229c9fe5" + ] + ], + "gzi": [ + + ], + "sizes": [ + [ + { + "id": "test" + }, + "genome.fasta.sizes:md5,a57c401f27ae5133823fb09fb21c8a3c" + ] + ], + "versions_samtools": [ + [ + "SAMTOOLS_FAIDX", + "samtools", + "1.22.1" + ] + ] + } + ], + "meta": { + "nf-test": "0.9.3", + "nextflow": "25.10.2" + }, + "timestamp": "2025-12-23T14:03:16.844965756" + }, + "test_samtools_faidx_bgzip": { + "content": [ + { + "0": [ + + ], + "1": [ + + ], + "2": [ + [ + { + "id": "test", + "single_end": false + }, + "genome.fasta.gz.fai:md5,9da2a56e2853dc8c0b86a9e7229c9fe5" + ] + ], + "3": [ + [ + { + "id": "test", + "single_end": false + }, + "genome.fasta.gz.gzi:md5,7dea362b3fac8e00956a4952a3d4f474" + ] + ], + "4": [ + [ + "SAMTOOLS_FAIDX", + "samtools", + "1.22.1" + ] + ], + "fa": [ + + ], + "fai": [ + [ + { + "id": "test", + "single_end": false + }, + "genome.fasta.gz.fai:md5,9da2a56e2853dc8c0b86a9e7229c9fe5" + ] + ], + "gzi": [ + [ + { + "id": "test", + "single_end": false + }, + "genome.fasta.gz.gzi:md5,7dea362b3fac8e00956a4952a3d4f474" + ] + ], + "sizes": [ + + ], + "versions_samtools": [ + [ + "SAMTOOLS_FAIDX", + "samtools", + "1.22.1" + ] + ] + } + ], + "meta": { + "nf-test": "0.9.3", + "nextflow": "25.10.2" + }, + "timestamp": "2025-12-23T14:02:47.301476131" + }, + "test_samtools_faidx_fasta": { + "content": [ + { + "0": [ + [ + { + "id": "test", + "single_end": false + }, + "extract.fa:md5,6a0774a0ad937ba0bfd2ac7457d90f36" + ] + ], + "1": [ + + ], + "2": [ + + ], + "3": [ + + ], + "4": [ + [ + "SAMTOOLS_FAIDX", + "samtools", + "1.22.1" + ] + ], + "fa": [ + [ + { + "id": "test", + "single_end": false + }, + "extract.fa:md5,6a0774a0ad937ba0bfd2ac7457d90f36" + ] + ], + "fai": [ + + ], + "gzi": [ + + ], + "sizes": [ + + ], + "versions_samtools": [ + [ + "SAMTOOLS_FAIDX", + "samtools", + "1.22.1" + ] + ] + } + ], + "meta": { + "nf-test": "0.9.3", + "nextflow": "25.10.2" + }, + "timestamp": "2025-12-23T09:44:40.559583279" + }, + "test_samtools_faidx_get_sizes - stub": { + "content": [ + { + "0": [ + + ], + "1": [ + [ + { + "id": "test" + }, + "genome.fasta.sizes:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "2": [ + [ + { + "id": "test" + }, + "genome.fasta.fai:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "3": [ + + ], + "4": [ + [ + "SAMTOOLS_FAIDX", + "samtools", + "1.22.1" + ] + ], + "fa": [ + + ], + "fai": [ + [ + { + "id": "test" + }, + "genome.fasta.fai:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "gzi": [ + + ], + "sizes": [ + [ + { + "id": "test" + }, + "genome.fasta.sizes:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "versions_samtools": [ + [ + "SAMTOOLS_FAIDX", + "samtools", + "1.22.1" + ] + ] + } + ], + "meta": { + "nf-test": "0.9.3", + "nextflow": "25.10.2" + }, + "timestamp": "2025-12-23T14:03:31.989929281" + }, + "test_samtools_faidx_stub_fasta": { + "content": [ + { + "0": [ + [ + { + "id": "test", + "single_end": false + }, + "extract.fa:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "1": [ + + ], + "2": [ + + ], + "3": [ + + ], + "4": [ + [ + "SAMTOOLS_FAIDX", + "samtools", + "1.22.1" + ] + ], + "fa": [ + [ + { + "id": "test", + "single_end": false + }, + "extract.fa:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "fai": [ + + ], + "gzi": [ + + ], + "sizes": [ + + ], + "versions_samtools": [ + [ + "SAMTOOLS_FAIDX", + "samtools", + "1.22.1" + ] + ] + } + ], + "meta": { + "nf-test": "0.9.3", + "nextflow": "25.10.2" + }, + "timestamp": "2025-12-23T09:44:48.295693103" + }, + "test_samtools_faidx_stub_fai": { + "content": [ + { + "0": [ + + ], + "1": [ + + ], + "2": [ + [ + { + "id": "test", + "single_end": false + }, + "genome.fasta.fai:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "3": [ + + ], + "4": [ + [ + "SAMTOOLS_FAIDX", + "samtools", + "1.22.1" + ] + ], + "fa": [ + + ], + "fai": [ + [ + { + "id": "test", + "single_end": false + }, + "genome.fasta.fai:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "gzi": [ + + ], + "sizes": [ + + ], + "versions_samtools": [ + [ + "SAMTOOLS_FAIDX", + "samtools", + "1.22.1" + ] + ] + } + ], + "meta": { + "nf-test": "0.9.3", + "nextflow": "25.10.2" + }, + "timestamp": "2025-12-23T14:03:09.784289542" + }, + "test_samtools_faidx_get_sizes_bgzip": { + "content": [ + { + "0": [ + + ], + "1": [ + [ + { + "id": "test" + }, + "genome.fasta.gz.sizes:md5,a57c401f27ae5133823fb09fb21c8a3c" + ] + ], + "2": [ + [ + { + "id": "test" + }, + "genome.fasta.gz.fai:md5,9da2a56e2853dc8c0b86a9e7229c9fe5" + ] + ], + "3": [ + [ + { + "id": "test" + }, + "genome.fasta.gz.gzi:md5,7dea362b3fac8e00956a4952a3d4f474" + ] + ], + "4": [ + [ + "SAMTOOLS_FAIDX", + "samtools", + "1.22.1" + ] + ], + "fa": [ + + ], + "fai": [ + [ + { + "id": "test" + }, + "genome.fasta.gz.fai:md5,9da2a56e2853dc8c0b86a9e7229c9fe5" + ] + ], + "gzi": [ + [ + { + "id": "test" + }, + "genome.fasta.gz.gzi:md5,7dea362b3fac8e00956a4952a3d4f474" + ] + ], + "sizes": [ + [ + { + "id": "test" + }, + "genome.fasta.gz.sizes:md5,a57c401f27ae5133823fb09fb21c8a3c" + ] + ], + "versions_samtools": [ + [ + "SAMTOOLS_FAIDX", + "samtools", + "1.22.1" + ] + ] + } + ], + "meta": { + "nf-test": "0.9.3", + "nextflow": "25.10.2" + }, + "timestamp": "2025-12-23T14:03:24.814967939" + } +} \ No newline at end of file diff --git a/modules/nf-core/samtools/faidx/tests/nextflow.config b/modules/nf-core/samtools/faidx/tests/nextflow.config new file mode 100644 index 00000000..202c036e --- /dev/null +++ b/modules/nf-core/samtools/faidx/tests/nextflow.config @@ -0,0 +1,7 @@ +process { + + withName: SAMTOOLS_FAIDX { + ext.args = params.module_args + } + +} diff --git a/modules/nf-core/yahs/environment.yml b/modules/nf-core/yahs/environment.yml new file mode 100644 index 00000000..051b20b6 --- /dev/null +++ b/modules/nf-core/yahs/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::yahs=1.2.2 diff --git a/modules/nf-core/yahs/main.nf b/modules/nf-core/yahs/main.nf new file mode 100644 index 00000000..2291cab0 --- /dev/null +++ b/modules/nf-core/yahs/main.nf @@ -0,0 +1,62 @@ +process YAHS { + 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/yahs:1.2.2--h577a1d6_1': + 'biocontainers/yahs:1.2.2--h577a1d6_1' }" + + input: + tuple val(meta), path(fasta), path(fai), path(hic_map), path(agp) + + output: + // note: typo in yahs file outputs - it writes "inital", not "initial" + tuple val(meta), path("${prefix}_scaffolds_final.fa") , emit: scaffolds_fasta , optional: true + tuple val(meta), path("${prefix}_scaffolds_final.agp") , emit: scaffolds_agp , optional: true + tuple val(meta), path("${prefix}_{inital,no}_break*.agp"), emit: initial_break_agp , optional: true + tuple val(meta), path("${prefix}_r*_*.agp") , emit: round_agp , optional: true + tuple val(meta), path("${prefix}.bin") , emit: binary + tuple val(meta), path("${prefix}.log") , emit: log + 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 agp_input = agp ? "-a ${agp}" : "" + """ + yahs \\ + -o ${prefix} \\ + ${agp_input} \\ + ${args} \\ + ${fasta} \\ + ${hic_map} \\ + 2>| >( tee ${prefix}.log >&2 ) + + cat <<-END_VERSIONS > versions.yml + "${task.process}": + yahs: \$(yahs --version 2>&1) + END_VERSIONS + """ + + stub: + prefix = task.ext.prefix ?: "${meta.id}" + """ + touch ${prefix}_scaffolds_final.fa + touch ${prefix}_scaffolds_final.agp + touch ${prefix}_inital_break_01.agp + touch ${prefix}_no_break.agp + touch ${prefix}_r01.agp + touch ${prefix}_r01_break.agp + touch ${prefix}.bin + touch ${prefix}.log + + cat <<-END_VERSIONS > versions.yml + "${task.process}": + yahs: \$(yahs --version 2>&1) + END_VERSIONS + """ +} diff --git a/modules/nf-core/yahs/meta.yml b/modules/nf-core/yahs/meta.yml new file mode 100644 index 00000000..c33501ab --- /dev/null +++ b/modules/nf-core/yahs/meta.yml @@ -0,0 +1,135 @@ +name: "yahs" +description: Performs assembly scaffolding using YaHS +keywords: + - scaffolding + - assembly + - yahs + - hic +tools: + - "yahs": + description: "YaHS, yet another Hi-C scaffolding tool." + homepage: "https://github.com/c-zhou/yahs" + documentation: "https://github.com/c-zhou/yahs" + tool_dev_url: "https://github.com/c-zhou/yahs" + doi: "10.1093/bioinformatics/btac808" + licence: ["MIT"] + identifier: biotools:yahs +input: + - - meta: + type: map + description: | + Groovy Map containing sample information + e.g. [ id:'test' ] + - fasta: + type: file + description: FASTA reference file + pattern: "*.{fasta,fa}" + ontologies: + - edam: http://edamontology.org/format_1929 # FASTA + - fai: + type: file + description: index of the reference file + pattern: "*.{fai}" + ontologies: + - edam: http://edamontology.org/format_3475 # TSV + - hic_map: + type: file + description: BED file containing coordinates of read alignments + pattern: "*.{bed,bam,bin}" + ontologies: + - edam: http://edamontology.org/format_3003 # BED + - edam: http://edamontology.org/format_2572 # BAM + - agp: + type: file + description: | + Optional AGP file describing a set of scaffolds from the input contigs + to use as a start point + pattern: "*.agp" + ontologies: + - edam: http://edamontology.org/format_3693 # AGP +output: + scaffolds_fasta: + - - meta: + type: map + description: | + Groovy Map containing sample information + e.g. [ id:'test' ] + - ${prefix}_scaffolds_final.fa: + type: file + description: FASTA file with resulting contigs + pattern: "${prefix}_scaffolds_final.fa" + ontologies: + - edam: http://edamontology.org/format_1929 # FASTA + scaffolds_agp: + - - meta: + type: map + description: | + Groovy Map containing sample information + e.g. [ id:'test' ] + - ${prefix}_scaffolds_final.agp: + type: file + description: AGP file containing contigs placing coordinates + pattern: "${prefix}_scaffolds_final.agp" + ontologies: + - edam: http://edamontology.org/format_3693 # AGP + initial_break_agp: + - - meta: + type: map + description: | + Groovy Map containing sample information + e.g. [ id:'test' ] + - ${prefix}_{inital,no}_break*.agp: + type: file + description: AGP file describing initial contig breaks + pattern: "${prefix}_{inital,no}_break*.agp" + ontologies: + - edam: http://edamontology.org/format_3693 # AGP + round_agp: + - - meta: + type: map + description: | + Groovy Map containing sample information + e.g. [ id:'test' ] + - ${prefix}_r*_*.agp: + type: file + description: AGP file describing intermediate rounds of scaffolding + pattern: "${prefix}_{initial,no}_break*.agp" + ontologies: + - edam: http://edamontology.org/format_3693 # AGP + binary: + - - meta: + type: map + description: | + Groovy Map containing sample information + e.g. [ id:'test' ] + - ${prefix}.bin: + type: file + description: | + Binary data file with alignment results of Hi-C reads + to the contigs in internal YaHS binary format + pattern: "${prefix}.bin" + ontologies: [] + log: + - - meta: + type: map + description: | + Groovy Map containing sample information + e.g. [ id:'test' ] + - ${prefix}.log: + type: file + description: Log file describing YaHS run + pattern: "${prefix}.log" + ontologies: + - edam: "http://edamontology.org/format_2330" # Textual format + versions: + - versions.yml: + type: file + description: File containing software versions + pattern: "versions.yml" + ontologies: + - edam: http://edamontology.org/format_3750 # YAML +authors: + - "@ksenia-krasheninnikova" +maintainers: + - "@ksenia-krasheninnikova" + - "@yy5" diff --git a/modules/nf-core/yahs/tests/main.nf.test b/modules/nf-core/yahs/tests/main.nf.test new file mode 100644 index 00000000..ad662ebb --- /dev/null +++ b/modules/nf-core/yahs/tests/main.nf.test @@ -0,0 +1,138 @@ +nextflow_process { + + name "Test Process YAHS" + script "../main.nf" + process "YAHS" + config "./nextflow.config" + + tag "modules" + tag "modules_nfcore" + tag "yahs" + + test("homo_sapiens - bam - fasta - fai") { + + when { + + params { + yahs_args = "" + } + + process { + """ + input[0] = [ + [ id: "test" ], + 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), + file(params.modules_testdata_base_path + 'genomics/homo_sapiens/illumina/bam/test.paired_end.sorted.bam', checkIfExists: true), + [] + ] + """ + } + } + + then { + assertAll( + { assert process.success }, + { assert snapshot( + process.out.scaffolds_fasta, + process.out.scaffolds_agp, + process.out.initial_break_agp, + process.out.round_agp, + process.out.binary, + process.out.versions, + ).match() }, + { file(process.out.log.get(0).get(1)).readLines().last().contains("Real time") } + ) + } + } + + test("homo_sapiens - bam - fasta - fai - agp") { + + setup { + + run("YAHS", alias: "YAHS_INIT") { + script "../main.nf" + process { + """ + input[0] = [ + [ id: "test" ], + 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), + file(params.modules_testdata_base_path + 'genomics/homo_sapiens/illumina/bam/test.paired_end.sorted.bam', checkIfExists: true), + [] + ] + """ + } + } + + } + + when { + + params { + yahs_args = "--no-contig-ec" + } + + process { + """ + input[0] = channel.of( + [ + [ id: "test" ], + 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), + file(params.modules_testdata_base_path + 'genomics/homo_sapiens/illumina/bam/test.paired_end.sorted.bam', checkIfExists: true), + ] + ).combine(YAHS_INIT.out.scaffolds_agp, by: 0) + """ + } + } + + then { + assertAll( + { assert process.success }, + { assert snapshot( + process.out.scaffolds_fasta, + process.out.scaffolds_agp, + process.out.initial_break_agp, + process.out.round_agp, + process.out.binary, + process.out.versions, + ).match() }, + { file(process.out.log.get(0).get(1)).readLines().last().contains("Real time") } + ) + } + } + + test("homo_sapiens - bam - fasta - fai - stub") { + + options "-stub" + + when { + + params { + yahs_args = "" + } + + process { + """ + input[0] = [ + [ id: "test" ], + 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), + file(params.modules_testdata_base_path + 'genomics/homo_sapiens/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/yahs/tests/main.nf.test.snap b/modules/nf-core/yahs/tests/main.nf.test.snap new file mode 100644 index 00000000..1b5a619a --- /dev/null +++ b/modules/nf-core/yahs/tests/main.nf.test.snap @@ -0,0 +1,210 @@ +{ + "homo_sapiens - bam - fasta - fai - stub": { + "content": [ + { + "0": [ + [ + { + "id": "test" + }, + "test_scaffolds_final.fa:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "1": [ + [ + { + "id": "test" + }, + "test_scaffolds_final.agp:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "2": [ + [ + { + "id": "test" + }, + [ + "test_inital_break_01.agp:md5,d41d8cd98f00b204e9800998ecf8427e", + "test_no_break.agp:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ] + ], + "3": [ + [ + { + "id": "test" + }, + "test_r01_break.agp:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "4": [ + [ + { + "id": "test" + }, + "test.bin:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "5": [ + [ + { + "id": "test" + }, + "test.log:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "6": [ + "versions.yml:md5,37472c36bf654e5ffc7507701c5f280a" + ], + "binary": [ + [ + { + "id": "test" + }, + "test.bin:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "initial_break_agp": [ + [ + { + "id": "test" + }, + [ + "test_inital_break_01.agp:md5,d41d8cd98f00b204e9800998ecf8427e", + "test_no_break.agp:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ] + ], + "log": [ + [ + { + "id": "test" + }, + "test.log:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "round_agp": [ + [ + { + "id": "test" + }, + "test_r01_break.agp:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "scaffolds_agp": [ + [ + { + "id": "test" + }, + "test_scaffolds_final.agp:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "scaffolds_fasta": [ + [ + { + "id": "test" + }, + "test_scaffolds_final.fa:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "versions": [ + "versions.yml:md5,37472c36bf654e5ffc7507701c5f280a" + ] + } + ], + "meta": { + "nf-test": "0.9.3", + "nextflow": "25.04.2" + }, + "timestamp": "2025-11-05T13:57:23.370522" + }, + "homo_sapiens - bam - fasta - fai": { + "content": [ + [ + [ + { + "id": "test" + }, + "test_scaffolds_final.fa:md5,a767604036a4cd6980ebd24f11e4dd95" + ] + ], + [ + [ + { + "id": "test" + }, + "test_scaffolds_final.agp:md5,374235b079d9e1c738c6f12697029b78" + ] + ], + [ + [ + { + "id": "test" + }, + "test_inital_break_01.agp:md5,374235b079d9e1c738c6f12697029b78" + ] + ], + [ + + ], + [ + [ + { + "id": "test" + }, + "test.bin:md5,da45a2ec8a97fc24783e9a63373db379" + ] + ], + [ + "versions.yml:md5,37472c36bf654e5ffc7507701c5f280a" + ] + ], + "meta": { + "nf-test": "0.9.3", + "nextflow": "25.04.2" + }, + "timestamp": "2025-11-05T13:57:15.713602" + }, + "homo_sapiens - bam - fasta - fai - agp": { + "content": [ + [ + [ + { + "id": "test" + }, + "test_scaffolds_final.fa:md5,a767604036a4cd6980ebd24f11e4dd95" + ] + ], + [ + [ + { + "id": "test" + }, + "test_scaffolds_final.agp:md5,374235b079d9e1c738c6f12697029b78" + ] + ], + [ + + ], + [ + + ], + [ + [ + { + "id": "test" + }, + "test.bin:md5,da45a2ec8a97fc24783e9a63373db379" + ] + ], + [ + "versions.yml:md5,37472c36bf654e5ffc7507701c5f280a" + ] + ], + "meta": { + "nf-test": "0.9.3", + "nextflow": "25.04.2" + }, + "timestamp": "2025-11-05T13:57:20.143057" + } +} \ No newline at end of file diff --git a/modules/nf-core/yahs/tests/nextflow.config b/modules/nf-core/yahs/tests/nextflow.config new file mode 100644 index 00000000..15279238 --- /dev/null +++ b/modules/nf-core/yahs/tests/nextflow.config @@ -0,0 +1,10 @@ +process { + + withName: 'YAHS' { + ext.args = params.yahs_args + } + + withName: 'YAHS_INIT' { + ext.args = params.yahs_args + } +} diff --git a/nextflow.config b/nextflow.config index e125eee0..89ece358 100644 --- a/nextflow.config +++ b/nextflow.config @@ -67,9 +67,14 @@ params { // -- Short read -- use_short_reads = false // short reads available? shortread_trim = false // trim short reads? - shortread_F = null // fwd shortreads - shortread_R = null // rev shortreads + shortread_F = [] // fwd shortreads + shortread_R = [] // rev shortreads paired = null + // HiC + hic_aligner = "bwa-mem2" + hic_F = [] + hic_R = [] + hic_trim = false // == ASSEMBLY == assembler = "hifiasm" // assembler to use assembly_scaffolding_order = "ont_on_hifi" @@ -110,6 +115,7 @@ params { scaffold_links = false // Scaffold with LINKS scaffold_longstitch = false // Scaffold with Longstitch scaffold_ragtag = false // Scaffold with ragtag + scaffold_hic = false // Scaffold with HiC // -- ANNOTATIONS lift_annotations = true // lift annotations from reference (if reference is provided) } diff --git a/nextflow_schema.json b/nextflow_schema.json index b5f88f5c..d52dd958 100644 --- a/nextflow_schema.json +++ b/nextflow_schema.json @@ -324,6 +324,10 @@ "scaffold_ragtag": { "type": "boolean", "description": "Scaffold with ragtag (requires reference)?" + }, + "scaffold_hic": { + "type": "boolean", + "description": "Scaffold using HiC reads using yahs (requires reads)?" } } }, @@ -426,6 +430,31 @@ "description": "Are shortreads paired?" } } + }, + "hic_options": { + "title": "HiC short read options", + "type": "object", + "description": "Options for HiC short reads", + "default": "", + "properties": { + "hic_aligner": { + "type": "string", + "enum": ["bwa-mem2", "minimap2"], + "description": "Aligner to use for HiC reads; default: bwa-mem2" + }, + "hic_trim": { + "type": "boolean", + "description": "Trim HiC short reads?" + }, + "hic_F": { + "type": "string", + "description": "Path to forward HiC short reads" + }, + "hic_R": { + "type": "string", + "description": "Path to reverse HiC short reads" + } + } } }, "allOf": [ diff --git a/subworkflows/local/assemble/main.nf b/subworkflows/local/assemble/main.nf index ade089bc..596b5239 100644 --- a/subworkflows/local/assemble/main.nf +++ b/subworkflows/local/assemble/main.nf @@ -170,7 +170,6 @@ workflow ASSEMBLE { ch_main_assemble_hifi_hifiasm.dump(tag: "Assemble: hifiasm HIFI inputs") - //ch_main_assemble_hifi_hifiasm.view { "Assemble: hifiasm HIFI inputs: $it" } HIFIASM(ch_main_assemble_hifi_hifiasm, diff --git a/subworkflows/local/prepare/main.nf b/subworkflows/local/prepare/main.nf index 040775bc..a772541f 100644 --- a/subworkflows/local/prepare/main.nf +++ b/subworkflows/local/prepare/main.nf @@ -43,7 +43,10 @@ workflow PREPARE { // $it looks like [meta, output_path] // recreate meta from metas and update path. it[0].metas - .collect { meta -> [ meta: meta + [ontreads: it[1]] ] } + .collect { meta -> [ + meta: meta - meta.subMap("ontreads") + [ontreads: it[1]] + ] + } } @@ -53,7 +56,7 @@ workflow PREPARE { main: ch_main .filter { - it -> (it.meta.shortread_F && it.meta.use_short_reads) ? true : false + it -> ((it.meta.shortread_F && it.meta.use_short_reads) || it.hic_trim) ? true : false } .set { shortreads } diff --git a/subworkflows/local/prepare/prepare_ont/main.nf b/subworkflows/local/prepare/prepare_ont/main.nf index 88bc4972..47911676 100644 --- a/subworkflows/local/prepare/prepare_ont/main.nf +++ b/subworkflows/local/prepare/prepare_ont/main.nf @@ -55,7 +55,7 @@ workflow PREPARE_ONT { COLLECT.out.reads .filter { it -> !it[0].metas } .map { - it -> [ meta: it[0] - meta.subMap("ontreads") + [ontreads: it[1]] ] + meta, ontreads -> [ meta: meta - meta.subMap("ontreads") + [ontreads: ontreads] ] } ) .set { ch_collected_reads } diff --git a/subworkflows/local/prepare/prepare_shortreads/main.nf b/subworkflows/local/prepare/prepare_shortreads/main.nf index 0518d05e..4e712810 100644 --- a/subworkflows/local/prepare/prepare_shortreads/main.nf +++ b/subworkflows/local/prepare/prepare_shortreads/main.nf @@ -1,6 +1,7 @@ -include { FASTP } from '../../../../modules/nf-core/fastp/main' -include { MERYL_COUNT } from '../../../../modules/nf-core/meryl/count/main' -include { MERYL_UNIONSUM } from '../../../../modules/nf-core/meryl/unionsum/main' +include { FASTP } from '../../../../modules/nf-core/fastp/main' +include { FASTP as FASTP_HIC } from '../../../../modules/nf-core/fastp/main' +include { MERYL_COUNT } from '../../../../modules/nf-core/meryl/count/main' +include { MERYL_UNIONSUM } from '../../../../modules/nf-core/meryl/unionsum/main' workflow PREPARE_SHORTREADS { take: @@ -10,27 +11,30 @@ workflow PREPARE_SHORTREADS { channel.empty().set { ch_versions } shortreads_in - .map { it -> create_shortread_channel(it.meta) } // See modified function below, adds shortreads to meta - .set { shortreads } - - shortreads.dump(tag: "shortread channel") - - // shortread trimming - - shortreads + .map { row -> row.meta.shortread_F ? create_shortread_channel(row.meta) : row } // function below .branch { it -> - trim: it.meta.shortread_trim - no_trim: !it.meta.shortread_trim + trim: it.meta.shortread_trim + no_trim: !it.meta.shortread_trim } .set { shortreads } - //shortreads.dump(tag: "Shortreads branched") + shortreads_in + .map { row -> row.meta.hic_F ? create_hic_shortread_channel(row.meta) : row } + .branch { + row -> + trim: row.meta.hic_trim + no_trim: !row.meta.hic_trim + } + .set { hic_trim } + + shortreads.trim.dump(tag: "shortread trim channel") + hic_trim.trim.dump(tag: "hic trim channel") shortreads .trim .filter { it -> it.meta.group } - .map {it -> [it.meta, it.meta.group]} + .map { it -> [it.meta, it.meta.group] } .groupTuple(by: 1) .map { it -> @@ -43,14 +47,42 @@ workflow PREPARE_SHORTREADS { [] ] } - .mix(shortreads.trim - .filter { it -> !it.meta.group } - .map { + .mix( + shortreads + .trim + .filter { it -> !it.meta.group } + .map { it -> [ it.meta, it.meta.shortreads, [] ] - } + } ) .set { trim_in } + hic_trim + .trim + .filter { it -> it.meta.group } + .map {it -> [it.meta, it.meta.group]} + .groupTuple(by: 1) + .map { + it -> + [ + [ + id: it[1], // the group + metas: it[0] + ], + it[0].hic_reads[0], // Pull path from meta + [] + ] + } + .mix( + hic_trim + .trim + .filter { it -> !it.meta.group } + .map { + it -> [ it.meta, it.meta.hic_reads, [] ] + } + ) + .set { hic_trim_in } + trim_in.dump(tag: "Trim in") FASTP(trim_in, false, false, false) @@ -59,12 +91,12 @@ workflow PREPARE_SHORTREADS { .filter { it -> it[0].metas } .flatMap { it -> // looks like [meta <[id, metas]>, output_path] it[0].metas - .collect { meta -> [ meta: meta + [ shortreads: it[1] ] ] } + .collect { meta -> [ meta: meta - meta.subMap("shortreads") + [ shortreads: it[1] ] ] } } .mix( FASTP.out.reads .filter { it -> !it[0].metas } - .map { it -> [ meta: it[0] + [ shortreads: it[1] ] ] } + .map { it -> [ meta: it[0] - it[0].subMap("shortreads") + [ shortreads: it[1] ] ] } ) .set { trimmed_reads } @@ -72,16 +104,58 @@ workflow PREPARE_SHORTREADS { // unite branched: // add trimmed reads to trim channel, then mix with shortreads.no_trim + FASTP_HIC(hic_trim_in, false, false, false) + + FASTP_HIC.out.reads + .filter { it -> it[0].metas } + .flatMap { it -> // looks like [meta <[id, metas]>, output_path] + it[0].metas + .collect { meta -> [ meta: meta - meta.subMap("hic_reads") + [ hic_reads: it[1] ] ] } + } + .mix( + FASTP_HIC.out.reads + .filter { it -> !it[0].metas } + .map { it -> [ meta: it[0] - it[0].subMap("hic_reads") + [ hic_reads: it[1] ] ] } + ) + .set { hic_trimmed_reads } + trimmed_reads .mix( shortreads.no_trim ) .set { shortreads } + // add HiC trimmed to those that need it + + shortreads + .filter { row -> row.meta.hic_trim } + .map { row -> [ row.meta.id, row.meta ] } + .join( + hic_trimmed_reads + .map { meta -> + [ + meta.id, + meta.hic_reads + ] + } + ) + .map { + _id, meta, trimmed_hic_reads -> + [ + meta: meta - meta.subMap("hic_reads") + [ hic_reads: trimmed_hic_reads ] + ] + } + .mix( + trimmed_reads + .filter { row -> !row.meta.hic_trim } + ) + .set { shortreads } + + ch_versions = ch_versions.mix(FASTP.out.versions) shortreads .filter { it -> it.meta.merqury } .filter { it -> it.meta.group } - .map { it -> [it.meta, it.meta.group, it.meta.shortreads, it.meta.meryl_k] } + .map { it -> [ it.meta, it.meta.group, it.meta.shortreads, it.meta.meryl_k ] } // Create a group .groupTuple(by: 1) .map { @@ -154,3 +228,26 @@ def create_shortread_channel(row) { // This function expects a meta map as input } return shortreads } + +def create_hic_shortread_channel(row) { // This function expects a meta map as input + // create meta map + def meta = row + meta.paired = true + meta.single_end = !meta.paired + + // add path(s) of the fastq file(s) to the meta map + def hic_reads = [] + if (!file(row.hic_F).exists()) { + exit(1, "ERROR: hic_F fastq file does not exist!\n${row.hic_F}") + } + if (!meta.paired) { + hic_reads = [meta: meta + [hic_reads: [file(row.hic_F)]]] + } + else { + if (!file(row.hic_R).exists()) { + exit(1, "ERROR: shortread_R fastq file does not exist!\n${row.hic_R}") + } + hic_reads = [ meta: meta + [hic_reads: [file(row.hic_F), file(row.hic_R)]] ] + } + return hic_reads +} diff --git a/subworkflows/local/scaffolding/hic/main.nf b/subworkflows/local/scaffolding/hic/main.nf new file mode 100644 index 00000000..5ad5f322 --- /dev/null +++ b/subworkflows/local/scaffolding/hic/main.nf @@ -0,0 +1,142 @@ +include { QC } from '../../qc/main' +include { YAHS } from '../../../../modules/nf-core/yahs/main' +include { RUN_LIFTOFF } from '../../liftoff/main' +include { BWAMEM2_MEM } from '../../../../modules/nf-core/bwamem2/mem/main' +include { BWAMEM2_INDEX } from '../../../../modules/nf-core/bwamem2/index/main' +include { MINIMAP2_ALIGN as MINIMAP2_HIC } from '../../../../modules/nf-core/minimap2/align/main' +include { PICARD_MARKDUPLICATES as MARKDUP } from '../../../../modules/nf-core/picard/markduplicates/main' +include { SAMTOOLS_FAIDX } from '../../../../modules/nf-core/samtools/faidx/main' +workflow HIC { + take: + ch_main + meryl_kmers + + main: + channel.empty().set { ch_versions } + + ch_main + .branch { it -> + bwamem: it.meta.hic_aligner == "bwa-mem2" + minimap: it.meta.hic_aligner == "minimap2" + } + .set { + hic_align_branched + } + + hic_align_branched + .bwamem + .map { + it -> + [ + it.meta, + it.meta.polished ? (it.meta.polished.pilon ?: it.meta.polished.medaka ?: it.meta.polished.dorado) : it.meta.assembly + ] + } + .set { bwamem_index_in } + + BWAMEM2_INDEX(bwamem_index_in) + BWAMEM2_INDEX.out.index + .map {meta, idx -> + [meta: meta + [bwamem_idx: idx]] + } + .multiMap { + it -> + reads: [it.meta, it.meta.hic_reads] + assembly: [it.meta, it.meta.polished ? (it.meta.polished.pilon ?: it.meta.polished.medaka ?: it.meta.polished.dorado) : it.meta.assembly] + index: [it.meta, it.meta.bwamem_idx] + } + .set{bwamem_mem_in} + + BWAMEM2_MEM(bwamem_mem_in.reads, bwamem_mem_in.index, bwamem_mem_in.assembly, true) + + hic_align_branched + .minimap + .map { it -> + [ + it.meta, + it.meta.hic_reads, + it.meta.polished ? (it.meta.polished.pilon ?: it.meta.polished.medaka ?: it.meta.polished.dorado) : it.meta.assembly + ] + } + .set {minimap2_in} + + MINIMAP2_HIC(minimap2_in, true, "csi", [], []) + + BWAMEM2_MEM.out.bam.mix(MINIMAP2_HIC.out.bam).set{ markdup_in } + + MARKDUP(markdup_in, [], []) + + MARKDUP.out.bam + .map { meta, bam -> [meta.id, meta, bam] } + .join( + MARKDUP.out.bai + .map { meta, bai -> [meta.id, bai] } + ) + .map {_id, meta, bam, bai -> [meta:meta + [hic_dedup_bam: bam, hic_dedup_bai: bai] ]} + .map { + it -> [ + it.meta, + it.meta.polished ? (it.meta.polished.pilon ?: it.meta.polished.medaka ?: it.meta.polished.dorado) : it.meta.assembly + ] + } + .set { faidx_in } + + SAMTOOLS_FAIDX(faidx_in, [], false) + + SAMTOOLS_FAIDX.out.fai + .map { + meta, index -> + [ + meta: meta + [hic_genome_idx: index] + ] + } + .set { indexed } + + indexed + .map { it -> + [ + it.meta, + it.meta.polished ? (it.meta.polished.pilon ?: it.meta.polished.medaka ?: it.meta.polished.dorado) : it.meta.assembly, + it.meta.hic_genome_idx, + it.meta.hic_dedup_bam + ] + } + .set { yahs_in } + + YAHS(yahs_in) + + YAHS.out.scaffolds_fasta + .map { meta, corrected -> [meta: meta + [ scaffolds_hic: corrected] ] } + .set { ch_main_scaffolded } + + QC(ch_main_scaffolded.map { it -> [meta: it.meta - it.meta.subMap("assembly_map_bam") + [assembly_map_bam: null] ] }, + YAHS.out.scaffolds_fasta.map { meta, corrected -> [ meta.id, corrected ] }, + meryl_kmers) + + ch_versions = ch_versions.mix(QC.out.versions) + + ch_main_scaffolded + .filter { + it -> it.lift_annotations + } + .map { it -> + [ + it.meta, + it.meta.scaffolds_hic, + it.meta.ref_fasta, + it.meta.ref_gff + ] + } + .set { liftoff_in } + + RUN_LIFTOFF(liftoff_in) + ch_versions = ch_versions.mix(RUN_LIFTOFF.out.versions) + + emit: + ch_main = ch_main_scaffolded + quast_out = QC.out.quast_out + busco_out = QC.out.busco_out + merqury_report_files = QC.out.merqury_report_files + versions = ch_versions + +} diff --git a/subworkflows/local/scaffolding/links/main.nf b/subworkflows/local/scaffolding/links/main.nf index 4947848b..03c5109f 100644 --- a/subworkflows/local/scaffolding/links/main.nf +++ b/subworkflows/local/scaffolding/links/main.nf @@ -51,7 +51,7 @@ workflow RUN_LINKS { ch_versions = ch_versions.mix(RUN_LIFTOFF.out.versions) emit: - ch_main + ch_main = ch_main_scaffolded quast_out = QC.out.quast_out busco_out = QC.out.busco_out merqury_report_files = QC.out.merqury_report_files diff --git a/subworkflows/local/scaffolding/longstitch/main.nf b/subworkflows/local/scaffolding/longstitch/main.nf index a525e17e..d2b11082 100644 --- a/subworkflows/local/scaffolding/longstitch/main.nf +++ b/subworkflows/local/scaffolding/longstitch/main.nf @@ -65,7 +65,7 @@ workflow RUN_LONGSTITCH { ch_versions = ch_versions.mix(RUN_LIFTOFF.out.versions) emit: - ch_main + ch_main = ch_main_scaffolded quast_out = QC.out.quast_out busco_out = QC.out.busco_out merqury_report_files = QC.out.merqury_report_files diff --git a/subworkflows/local/scaffolding/main.nf b/subworkflows/local/scaffolding/main.nf index 12fd434a..83d65440 100644 --- a/subworkflows/local/scaffolding/main.nf +++ b/subworkflows/local/scaffolding/main.nf @@ -1,6 +1,7 @@ -include { RUN_LINKS } from './links/main' -include { RUN_LONGSTITCH } from './longstitch/main' -include { RUN_RAGTAG } from './ragtag/main' +include { RUN_LINKS } from './links/main' +include { RUN_LONGSTITCH } from './longstitch/main' +include { RUN_RAGTAG } from './ragtag/main' +include { HIC } from './hic/main' workflow SCAFFOLD { take: @@ -45,16 +46,27 @@ workflow SCAFFOLD { ch_main .filter { - it -> it.meta.scaffold_ragtag + it -> it.meta.scaffold_hic } + .set { hic_in } + + HIC(hic_in, meryl_kmers) + HIC.out.ch_main + .set { hic_out } + + ch_main + .filter { + it -> it.meta.scaffold_ragtag && !it.meta.hic_reads && !it.meta.scaffold_longstitch && !it.meta.scaffold_links + } + .mix(hic_out.filter { it -> it.meta.scaffold_ragtag } ) + .mix(longstitch_out.filter { it -> it.meta.scaffold_ragtag } ) + .mix(links_out.filter { it -> it.meta.scaffold_ragtag } ) .set { ragtag_in } RUN_RAGTAG(ragtag_in, meryl_kmers) RUN_RAGTAG.out.ch_main .set { ragtag_out } - - // CONTINUE HERE // Deal with cases that are single scaffold links_out .filter {it -> !it.meta.scaffold_longstitch && !it.meta.scaffold_ragtag } @@ -69,6 +81,11 @@ workflow SCAFFOLD { .filter {it -> !it.meta.scaffold_links && !it.meta.scaffold_longstitch } .map { meta -> [ meta: meta - meta.subMap("scaffolds_ragtag") + [ scaffolds: [ ragtag: meta.scaffolds_ragtag ] ] ]} ) + .mix( + hic_out + .map { meta -> [ meta: meta - meta.subMap("scaffolds_hic") + [ scaffolds: [ hic: meta.scaffolds_hic ] ] ]} + + ) // mix in those that are double scaffolded: , links-ragtag, longstitch-ragtag // links-longstitch .mix( diff --git a/subworkflows/local/scaffolding/ragtag/main.nf b/subworkflows/local/scaffolding/ragtag/main.nf index 3df7376d..2fa2162c 100644 --- a/subworkflows/local/scaffolding/ragtag/main.nf +++ b/subworkflows/local/scaffolding/ragtag/main.nf @@ -13,19 +13,36 @@ workflow RUN_RAGTAG { ch_main .multiMap { it -> + def assembly_to_scaffold = + it.meta.scaffold ? + ( + it.meta.scaffolds_hic ?: + it.meta.scaffolds_longstitch ?: + it.meta.scaffolds_links + ) : + it.meta.polished ? + ( + it.meta.polished.pilon ?: + it.meta.polished.medaka ?: + it.meta.polished.dorado + ) : + it.meta.assembly assembly: [ it.meta, - it.meta.polished ? (it.meta.polished.pilon ?: it.meta.polished.medaka ?: it.meta.polished.dorado) : it.meta.assembly + assembly_to_scaffold + ] + reference: + [ + it.meta, + it.meta.ref_fasta ] - reference: [it.meta, it.meta.ref_fasta] } .set { ragtag_in } ragtag_in.assembly.dump(tag: "SCAFFOLD: RAGTAG: Assembly inputs") ragtag_in.reference.dump(tag: "SCAFFOLD: RAGTAG: Reference inputs") - RAGTAG_SCAFFOLD(ragtag_in.assembly, ragtag_in.reference, [[], []], [[], [], []]) RAGTAG_SCAFFOLD.out.corrected_assembly diff --git a/subworkflows/local/utils_nfcore_genomeassembler_pipeline/main.nf b/subworkflows/local/utils_nfcore_genomeassembler_pipeline/main.nf index 4e57093c..b47c26bd 100644 --- a/subworkflows/local/utils_nfcore_genomeassembler_pipeline/main.nf +++ b/subworkflows/local/utils_nfcore_genomeassembler_pipeline/main.nf @@ -131,7 +131,6 @@ workflow PIPELINE_INITIALISATION { (params.polish_pilon && (it.shortread_F || params.shortread_F)) ? "pilon" : null - strategy == "single" && ontreads && hifireads && !((!assembler_ont && assembler_hifi) || (assembler_ont && !assembler_hifi)) ? error( """ @@ -179,6 +178,11 @@ workflow PIPELINE_INITIALISATION { scaffold_links: it.scaffold_links ?: params.scaffold_links, scaffold_ragtag: it.scaffold_ragtag ?: params.scaffold_ragtag, use_ref: it.use_ref ?: params.use_ref, + // hic + hic_aligner: it.hic_aligner ?: params.hic_aligner, + hic_F: it.hic_F ?: params.hic_F, + hic_R: it.hic_R ?: params.hic_R, + hic_trim: it.hic_trim ?: params.hic_trim, // not new genome_size: it.genome_size ?: params.genome_size, ref_fasta: it.ref_fasta ?: params.ref_fasta, From e1b8649c6487c11fb1b56882d3beaca2be92931d Mon Sep 17 00:00:00 2001 From: Niklas Schandry Date: Thu, 5 Feb 2026 15:41:48 +0100 Subject: [PATCH 113/162] fix test --- conf/modules/fastp.config | 1 + conf/modules/scaffolding.config | 18 +- conf/test_full_local.config | 29 ---- docs/output.md | 43 ++++- docs/usage.md | 10 +- nextflow.config | 4 +- subworkflows/local/prepare/main.nf | 35 ++-- .../local/prepare/prepare_shortreads/main.nf | 13 +- .../main.nf | 16 +- tests/default.nf.test.snap | 161 ++++++++++-------- 10 files changed, 198 insertions(+), 132 deletions(-) delete mode 100644 conf/test_full_local.config diff --git a/conf/modules/fastp.config b/conf/modules/fastp.config index 1019fe0c..5cc4d8a6 100644 --- a/conf/modules/fastp.config +++ b/conf/modules/fastp.config @@ -10,6 +10,7 @@ process { process { withName: FASTP_HIC { + ext.prefix = {"${meta.id}_hicreads"} publishDir = [ path: { "${params.outdir}/${meta.id}/hic_reads/fastp" }, mode: params.publish_dir_mode, diff --git a/conf/modules/scaffolding.config b/conf/modules/scaffolding.config index a30cbdd9..27b60f81 100644 --- a/conf/modules/scaffolding.config +++ b/conf/modules/scaffolding.config @@ -58,7 +58,23 @@ process { } withName: MARKDUP { publishDir = [ - path: { "${params.outdir}/${meta.id}/scaffold/hic/minimap/" }, + path: { "${params.outdir}/${meta.id}/scaffold/hic/markdup/" }, + mode: params.publish_dir_mode, + saveAs: { filename -> filename.equals('versions.yml') ? null : filename } + ] + ext.prefix = { "${meta.id}_hic_dedup" } + } + withName: YAHS { + publishDir = [ + path: { "${params.outdir}/${meta.id}/scaffold/hic/yahs/" }, + mode: params.publish_dir_mode, + saveAs: { filename -> filename.equals('versions.yml') ? null : filename } + ] + ext.prefix = { "${meta.id}_hic_dedup" } + } + withName: '.*HIC:SAMTOOLS_FAIDX.*' { + publishDir = [ + path: { "${params.outdir}/${meta.id}/scaffold/hic/yahs/index/" }, mode: params.publish_dir_mode, saveAs: { filename -> filename.equals('versions.yml') ? null : filename } ] diff --git a/conf/test_full_local.config b/conf/test_full_local.config deleted file mode 100644 index 64f403f5..00000000 --- a/conf/test_full_local.config +++ /dev/null @@ -1,29 +0,0 @@ -/* -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - Nextflow config file for running full-size tests -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - Defines input files and everything required to run a full size pipeline test. - - Use as follows: - nextflow run nf-core/genomeassembler -profile test_full, --outdir - ----------------------------------------------------------------------------------------- -*/ - -params { - config_profile_name = 'Full test profile' - config_profile_description = 'Full test dataset to check pipeline function' - - input = 'https://raw.githubusercontent.com/nf-core/test-datasets/genomeassembler/samplesheet/full_test_samplesheet.csv' - ontreads = '/dss/dsslegfs01/pn73so/pn73so-dss-0002/genomeassembler_test_data/Col_0.ONT.porechopped.fastq.gz' - hifireads = '/dss/dsslegfs01/pn73so/pn73so-dss-0002/genomeassembler_test_data/Col_0.hifi_reads.fastq.gz' - shortread_F = '/dss/dsslegfs01/pn73so/pn73so-dss-0002/genomeassembler_test_data/SRR1604937_1.fastq.gz' - shortread_R = '/dss/dsslegfs01/pn73so/pn73so-dss-0002/genomeassembler_test_data/SRR1604937_2.fastq.gz' - ref_gff = 'http://raw.githubusercontent.com/schatzlab/Col-CEN/main/v1.2/Col-CEN_v1.2_genes.araport11.gff3.gz' - ref_fasta = 'http://raw.githubusercontent.com/schatzlab/Col-CEN/main/v1.2/Col-CEN_v1.2.fasta.gz' - quast = true - busco = true - jellyfish = true - use_ref = true - trim_short_reads = true -} diff --git a/docs/output.md b/docs/output.md index b8525f3a..67c5d5c9 100644 --- a/docs/output.md +++ b/docs/output.md @@ -13,6 +13,7 @@ The pipeline is built using [Nextflow](https://www.nextflow.io/) and processes d - [**Read preparation**](#read-preparation) - [**Long reads**](#long-reads): - [**Short reads**](#short-reads): + - [**HiC reads**](#hic-reads): - [**Assembly**](#assembly), choice between assemblers - [**Polishing**](#polishing) - [**Scaffolding**](#scaffolding) @@ -64,25 +65,47 @@ If the ONT basecalls are scattered across multiple files, `collect` can be used #### Short reads -[TrimGalore!](https://github.com/FelixKrueger/TrimGalore) can remove adapters from illumina short-reads. +[fastp](https://github.com/OpenGene/fastp) performs shortread QC and trimming. [meryl](https://github.com/marbl/meryl) calculates the k-mer spectrum of short reads. +If a group was provided, the group name will be used instead of SampleName below.
Output files - `/` - `reads/` - - `trimgalore/`: - - `_val_1.fq.gz`: Trimmed forward reads - - `_val_2.fq.gz`: Trimmed reverse reads (if included) - - `_1.fastq.gz.trimming_report.txt`: Trimming report forward - - `_2.fastq.gz.trimming_report.txt`: Trimming report reverse (if included) + - `fastp/`: + - `_1.fastp.fastq.gz`: Trimmed forward reads + - `_2.fastp.fastq.gz`: Trimmed reverse reads (if included) + - `.fastp.html`: html report + - `.fastp.json`: json report + - `.fastp.log`: logfile - `meryl/`: output from meryl - `count/`: k-mer counts per file - `unionsum/`: union of k-mer counts per sample
+### HiC reads + +[fastp](https://github.com/OpenGene/fastp) performs shortread QC and trimming. + +If a group was provided, the group name will be used instead of SampleName below. + +
+Output files + +- `/` + - `hic_reads/` + - `fastp/`: + - `_1.fastp.fastq.gz`: Trimmed forward reads + - `_2.fastp.fastq.gz`: Trimmed reverse reads (if included) + - `.fastp.html`: html report + - `.fastp.json`: json report + - `.fastp.log`: logfile + +
+ ### Assembly This folder contains the initial assemblies of the provided reads. @@ -182,6 +205,14 @@ Annotation `gff3` and `unmapped.txt` files are only created if a reference for a - `_ragtag_.stats`: Scaffolding statistics - `_ragtag.gff3` annotation liftover - `_ragtag.unnapped.txt` annotations that could not be lifted over during annotation liftover + - `hic/`: output from HiC scaffolding workflow + - `bwamem2/`: bwamem2 outputs + - `index/`: outputs from bwamem2 index + - `mem/`: outputs from bwamem2 mem + - `minimap/`: minimap2 outputs + - `markdup/`: output from picard markduplicates + - `yahs/`: output from yahs + - `index/`: alignment index used with yahs diff --git a/docs/usage.md b/docs/usage.md index 6ff1a91c..88c95dc3 100644 --- a/docs/usage.md +++ b/docs/usage.md @@ -347,19 +347,21 @@ Polishing options. When using `polish` with either `dorado+pilon` or `medaka+pil ## Scaffolding options -Scaffolding options +Options `_longstitch`, `_links` and `_hic` are mutually exclusive. +`RagTag` scaffolding can be used to either scaffold the (polished) assembly, or can be combined with `longstitch`, `links` or `hic`, to scaffold the scaffolding results onto a reference. `RagTag` will always scaffold the most "advanced" stage of assembly; meaning that if the assembly was scaffolded, the scaffolded assembly will be used, if the assembly was polished, the polished assembly will be used, if the pipeline only carried out assembly for that sample, the assembly will be scaffolded. | Parameter | Description | Type | | --------------------- | ------------------------------------------ | --------- | | `scaffold_longstitch` | Scaffold with longstitch? | `boolean` | | `scaffold_links` | Scaffolding with links? | `boolean` | +| `scaffold_hic` | Scaffold with yahs (requires hic reads)? | `boolean` | | `scaffold_ragtag` | Scaffold with ragtag (requires reference)? | `boolean` | ### HiC -HiC scaffolding specific parameters. Supplying HiC reads activates HiC scaffolding. -bwa-mem2 generally is more suitable for HiC alignments than minimap2, and is the recommended option. -However, bwamem2 requires substantial memory for large genomes, which may prohibit use of bwamem2 in some cases. +HiC scaffolding specific parameters. Supplying HiC reads activates HiC scaffolding, unless explicitly deactivated using `scaffold_hic`. +`bwa-mem2` generally is more suitable for HiC alignments than `minimap2`, and is the recommended option. +However, `bwamem2` requires substantial memory for large genomes, which may prohibit use of `bwamem2` in some cases. | Parameter | Description | Type | | ------------- | ----------------------------------------------------------- | --------- | diff --git a/nextflow.config b/nextflow.config index 89ece358..107ffa24 100644 --- a/nextflow.config +++ b/nextflow.config @@ -74,7 +74,7 @@ params { hic_aligner = "bwa-mem2" hic_F = [] hic_R = [] - hic_trim = false + hic_trim = true // == ASSEMBLY == assembler = "hifiasm" // assembler to use assembly_scaffolding_order = "ont_on_hifi" @@ -115,7 +115,7 @@ params { scaffold_links = false // Scaffold with LINKS scaffold_longstitch = false // Scaffold with Longstitch scaffold_ragtag = false // Scaffold with ragtag - scaffold_hic = false // Scaffold with HiC + scaffold_hic = true // Scaffold with HiC // -- ANNOTATIONS lift_annotations = true // lift annotations from reference (if reference is provided) } diff --git a/subworkflows/local/prepare/main.nf b/subworkflows/local/prepare/main.nf index a772541f..26c28f91 100644 --- a/subworkflows/local/prepare/main.nf +++ b/subworkflows/local/prepare/main.nf @@ -48,12 +48,18 @@ workflow PREPARE { ] } } + .mix( + process.OUT + .filter { it -> !it[0].metas } + .map {meta, ontreads -> [meta: meta -meta.subMap("ontreads") + [ontreads: ontreads]]} + ) */ take: ch_main main: + channel.empty().set{ ch_main_shortreaded } ch_main .filter { it -> ((it.meta.shortread_F && it.meta.use_short_reads) || it.hic_trim) ? true : false @@ -83,14 +89,21 @@ workflow PREPARE { // put shortreads back together with samples without shortreads - ch_main - .filter { - it -> !it.meta.shortread_F ? true : false - } - .map { it -> it.meta - it.meta.subMap("shortread_F","shortread_R", "paired") + [shorteads: null] } - .mix(SHORTREADS.out.main_out) - .set { ch_main_shortreaded } + SHORTREADS.out.main_out + .filter{it-> it.meta.id == "test_hifiasm_hic"} + .view {"Prepare: Shortreads: $it"} + // Added mix with empty to make sure that the channel exists + ch_main_shortreaded + .mix( + ch_main + .filter { + it -> !it.meta.shortread_F && !it.meta.hic_F + } + .map { it -> [meta: it.meta - it.meta.subMap("shortread_F","shortread_R", "paired") + [shorteads: null] ]} + .mix(SHORTREADS.out.main_out) + ) + .set { ch_main_shortreaded } ONT(ontreads) @@ -115,7 +128,7 @@ workflow PREPARE { // After joining re-create the maps from the stored map .map { _id, meta_old, ont_reads -> [ - meta: meta_old + [ontreads: ont_reads] + meta: meta_old -meta_old.subMap("ontreads") + [ontreads: ont_reads] ] } // mix back in those samples where nothing was done to the ont reads @@ -154,8 +167,6 @@ workflow PREPARE { .set { ch_main_prepared } - //ch_main_prepared.view {"CH_MAIN_PREPARED: $it"} - // Get average read length of the QC reads from fastplong json report def slurp = new groovy.json.JsonSlurper() @@ -231,6 +242,10 @@ workflow PREPARE { } .set { main_out } + main_out + .filter{it-> it.meta.id == "test_hifiasm_hic"} + .view {"Prepare: main_out: $it"} + main_out.dump(tag: "Prepare: Combined outputs") JELLYFISH.out.genomescope_summary.set { genomescope_summary } diff --git a/subworkflows/local/prepare/prepare_shortreads/main.nf b/subworkflows/local/prepare/prepare_shortreads/main.nf index 4e712810..6f5dbfbd 100644 --- a/subworkflows/local/prepare/prepare_shortreads/main.nf +++ b/subworkflows/local/prepare/prepare_shortreads/main.nf @@ -128,14 +128,15 @@ workflow PREPARE_SHORTREADS { shortreads .filter { row -> row.meta.hic_trim } .map { row -> [ row.meta.id, row.meta ] } - .join( + .combine( hic_trimmed_reads - .map { meta -> + .map { it -> [ - meta.id, - meta.hic_reads + it.meta.id, + it.meta.hic_reads ] - } + }, + by: 0 ) .map { _id, meta, trimmed_hic_reads -> @@ -146,10 +147,10 @@ workflow PREPARE_SHORTREADS { .mix( trimmed_reads .filter { row -> !row.meta.hic_trim } + .map { it-> [meta: it.meta - it.meta.subMap("hic_reads") + [hic_reads: null]]} ) .set { shortreads } - ch_versions = ch_versions.mix(FASTP.out.versions) shortreads diff --git a/subworkflows/local/utils_nfcore_genomeassembler_pipeline/main.nf b/subworkflows/local/utils_nfcore_genomeassembler_pipeline/main.nf index b47c26bd..2c4c57e7 100644 --- a/subworkflows/local/utils_nfcore_genomeassembler_pipeline/main.nf +++ b/subworkflows/local/utils_nfcore_genomeassembler_pipeline/main.nf @@ -131,6 +131,15 @@ workflow PIPELINE_INITIALISATION { (params.polish_pilon && (it.shortread_F || params.shortread_F)) ? "pilon" : null + def hic_F = it.hic_F ?: params.hic_F + + def scaffold_hic = hic_F ? (it.scaffold_hic != null ? it.scaffold_hic : params.scaffold_hic) : false + + def hic_trim = !scaffold_hic ? false : + (it.hic_trim ?: params.hic_trim) + + + // Check if strategy can be inferred strategy == "single" && ontreads && hifireads && !((!assembler_ont && assembler_hifi) || (assembler_ont && !assembler_hifi)) ? error( """ @@ -177,12 +186,13 @@ workflow PIPELINE_INITIALISATION { scaffold_longstitch: it.scaffold_longstitch ?: params.scaffold_longstitch, scaffold_links: it.scaffold_links ?: params.scaffold_links, scaffold_ragtag: it.scaffold_ragtag ?: params.scaffold_ragtag, + scaffold_hic: scaffold_hic, use_ref: it.use_ref ?: params.use_ref, // hic hic_aligner: it.hic_aligner ?: params.hic_aligner, - hic_F: it.hic_F ?: params.hic_F, - hic_R: it.hic_R ?: params.hic_R, - hic_trim: it.hic_trim ?: params.hic_trim, + hic_F: scaffold_hic ? (hic_F) : [], + hic_R: scaffold_hic ? (it.hic_R ?: params.hic_R) : [], + hic_trim: hic_trim, // not new genome_size: it.genome_size ?: params.genome_size, ref_fasta: it.ref_fasta ?: params.ref_fasta, diff --git a/tests/default.nf.test.snap b/tests/default.nf.test.snap index a3f8f2bb..19a24172 100644 --- a/tests/default.nf.test.snap +++ b/tests/default.nf.test.snap @@ -35,6 +35,9 @@ "LONGSTITCH": { "LongStitch": "1.0.5" }, + "RAGTAG_PATCH": { + "ragtag": "2.1.0" + }, "Workflow": { "nf-core/genomeassembler": "v2.0.0dev" } @@ -64,6 +67,17 @@ "test_flye_hifiasm/assembly/hifiasm/test_flye_hifiasm.ovlp.reverse.bin", "test_flye_hifiasm/assembly/hifiasm/test_flye_hifiasm.ovlp.source.bin", "test_flye_hifiasm/assembly/hifiasm/test_flye_hifiasm.stderr.log", + "test_flye_hifiasm/assembly/ragtag", + "test_flye_hifiasm/assembly/ragtag/test_flye_hifiasm_assembly_patch.comps.fasta", + "test_flye_hifiasm/assembly/ragtag/test_flye_hifiasm_assembly_patch.ctg.agp", + "test_flye_hifiasm/assembly/ragtag/test_flye_hifiasm_assembly_patch.ctg.fasta", + "test_flye_hifiasm/assembly/ragtag/test_flye_hifiasm_assembly_patch.patch.agp", + "test_flye_hifiasm/assembly/ragtag/test_flye_hifiasm_assembly_patch.patch.err", + "test_flye_hifiasm/assembly/ragtag/test_flye_hifiasm_assembly_patch.patch.fasta", + "test_flye_hifiasm/assembly/ragtag/test_flye_hifiasm_assembly_patch.rename.agp", + "test_flye_hifiasm/assembly/ragtag/test_flye_hifiasm_assembly_patch.rename.fasta", + "test_flye_hifiasm/assembly/test_flye_hifiasm_assembly.gff3", + "test_flye_hifiasm/assembly/test_flye_hifiasm_assembly.unmapped.txt", "test_flye_hifiasm/reads", "test_flye_hifiasm/reads/fastplong", "test_flye_hifiasm/reads/fastplong/hifi", @@ -76,22 +90,12 @@ "test_flye_hifiasm/reads/fastplong/ont/test_flye_hifiasm_ont.fastplong.html", "test_flye_hifiasm/reads/fastplong/ont/test_flye_hifiasm_ont.fastplong.json", "test_flye_hifiasm/reads/fastplong/ont/test_flye_hifiasm_ont.fastplong.log", + "test_flye_hifiasm/scaffold", + "test_flye_hifiasm/scaffold/ragtag", + "test_flye_hifiasm/scaffold/ragtag/test_flye_hifiasm_ragtag.agp", + "test_flye_hifiasm/scaffold/ragtag/test_flye_hifiasm_ragtag.fasta", + "test_flye_hifiasm/scaffold/ragtag/test_flye_hifiasm_ragtag.stats", "test_hifi_flye", - "test_hifi_flye/assembly", - "test_hifi_flye/assembly/hifiasm_ont", - "test_hifi_flye/assembly/hifiasm_ont/fasta", - "test_hifi_flye/assembly/hifiasm_ont/fasta/test_hifi_flye.bp.p_utg.fa.gz", - "test_hifi_flye/assembly/hifiasm_ont/test_hifi_flye.bp.hap1.p_ctg.gfa", - "test_hifi_flye/assembly/hifiasm_ont/test_hifi_flye.bp.hap2.p_ctg.gfa", - "test_hifi_flye/assembly/hifiasm_ont/test_hifi_flye.bp.p_ctg.gfa", - "test_hifi_flye/assembly/hifiasm_ont/test_hifi_flye.bp.p_utg.gfa", - "test_hifi_flye/assembly/hifiasm_ont/test_hifi_flye.bp.r_utg.gfa", - "test_hifi_flye/assembly/hifiasm_ont/test_hifi_flye.ec.bin", - "test_hifi_flye/assembly/hifiasm_ont/test_hifi_flye.ovlp.reverse.bin", - "test_hifi_flye/assembly/hifiasm_ont/test_hifi_flye.ovlp.source.bin", - "test_hifi_flye/assembly/hifiasm_ont/test_hifi_flye.stderr.log", - "test_hifi_flye/assembly/test_hifi_flye_assembly.gff3", - "test_hifi_flye/assembly/test_hifi_flye_assembly.unmapped.txt", "test_hifi_flye/reads", "test_hifi_flye/reads/fastplong", "test_hifi_flye/reads/fastplong/hifi", @@ -104,27 +108,20 @@ "test_hifi_flye/reads/fastplong/ont/test_hifi_flye_ont.fastplong.html", "test_hifi_flye/reads/fastplong/ont/test_hifi_flye_ont.fastplong.json", "test_hifi_flye/reads/fastplong/ont/test_hifi_flye_ont.fastplong.log", - "test_hifi_flye/scaffold", - "test_hifi_flye/scaffold/ragtag", - "test_hifi_flye/scaffold/ragtag/test_hifi_flye_ragtag.agp", - "test_hifi_flye/scaffold/ragtag/test_hifi_flye_ragtag.fasta", - "test_hifi_flye/scaffold/ragtag/test_hifi_flye_ragtag.gff3", - "test_hifi_flye/scaffold/ragtag/test_hifi_flye_ragtag.stats", - "test_hifi_flye/scaffold/ragtag/test_hifi_flye_ragtag.unmapped.txt", "test_hifi_hifiasm", "test_hifi_hifiasm/assembly", - "test_hifi_hifiasm/assembly/hifiasm_ont", - "test_hifi_hifiasm/assembly/hifiasm_ont/fasta", - "test_hifi_hifiasm/assembly/hifiasm_ont/fasta/test_hifi_hifiasm.bp.p_utg.fa.gz", - "test_hifi_hifiasm/assembly/hifiasm_ont/test_hifi_hifiasm.bp.hap1.p_ctg.gfa", - "test_hifi_hifiasm/assembly/hifiasm_ont/test_hifi_hifiasm.bp.hap2.p_ctg.gfa", - "test_hifi_hifiasm/assembly/hifiasm_ont/test_hifi_hifiasm.bp.p_ctg.gfa", - "test_hifi_hifiasm/assembly/hifiasm_ont/test_hifi_hifiasm.bp.p_utg.gfa", - "test_hifi_hifiasm/assembly/hifiasm_ont/test_hifi_hifiasm.bp.r_utg.gfa", - "test_hifi_hifiasm/assembly/hifiasm_ont/test_hifi_hifiasm.ec.bin", - "test_hifi_hifiasm/assembly/hifiasm_ont/test_hifi_hifiasm.ovlp.reverse.bin", - "test_hifi_hifiasm/assembly/hifiasm_ont/test_hifi_hifiasm.ovlp.source.bin", - "test_hifi_hifiasm/assembly/hifiasm_ont/test_hifi_hifiasm.stderr.log", + "test_hifi_hifiasm/assembly/hifiasm", + "test_hifi_hifiasm/assembly/hifiasm/fasta", + "test_hifi_hifiasm/assembly/hifiasm/fasta/test_hifi_hifiasm.bp.p_utg.fa.gz", + "test_hifi_hifiasm/assembly/hifiasm/test_hifi_hifiasm.bp.hap1.p_ctg.gfa", + "test_hifi_hifiasm/assembly/hifiasm/test_hifi_hifiasm.bp.hap2.p_ctg.gfa", + "test_hifi_hifiasm/assembly/hifiasm/test_hifi_hifiasm.bp.p_ctg.gfa", + "test_hifi_hifiasm/assembly/hifiasm/test_hifi_hifiasm.bp.p_utg.gfa", + "test_hifi_hifiasm/assembly/hifiasm/test_hifi_hifiasm.bp.r_utg.gfa", + "test_hifi_hifiasm/assembly/hifiasm/test_hifi_hifiasm.ec.bin", + "test_hifi_hifiasm/assembly/hifiasm/test_hifi_hifiasm.ovlp.reverse.bin", + "test_hifi_hifiasm/assembly/hifiasm/test_hifi_hifiasm.ovlp.source.bin", + "test_hifi_hifiasm/assembly/hifiasm/test_hifi_hifiasm.stderr.log", "test_hifi_hifiasm/assembly/test_hifi_hifiasm_assembly.gff3", "test_hifi_hifiasm/assembly/test_hifi_hifiasm_assembly.unmapped.txt", "test_hifi_hifiasm/reads", @@ -160,6 +157,17 @@ "test_hifiasm_flye/assembly/hifiasm_ont/test_hifiasm_flye.ovlp.reverse.bin", "test_hifiasm_flye/assembly/hifiasm_ont/test_hifiasm_flye.ovlp.source.bin", "test_hifiasm_flye/assembly/hifiasm_ont/test_hifiasm_flye.stderr.log", + "test_hifiasm_flye/assembly/ragtag", + "test_hifiasm_flye/assembly/ragtag/test_hifiasm_flye_assembly_patch.comps.fasta", + "test_hifiasm_flye/assembly/ragtag/test_hifiasm_flye_assembly_patch.ctg.agp", + "test_hifiasm_flye/assembly/ragtag/test_hifiasm_flye_assembly_patch.ctg.fasta", + "test_hifiasm_flye/assembly/ragtag/test_hifiasm_flye_assembly_patch.patch.agp", + "test_hifiasm_flye/assembly/ragtag/test_hifiasm_flye_assembly_patch.patch.err", + "test_hifiasm_flye/assembly/ragtag/test_hifiasm_flye_assembly_patch.patch.fasta", + "test_hifiasm_flye/assembly/ragtag/test_hifiasm_flye_assembly_patch.rename.agp", + "test_hifiasm_flye/assembly/ragtag/test_hifiasm_flye_assembly_patch.rename.fasta", + "test_hifiasm_flye/assembly/test_hifiasm_flye_assembly.gff3", + "test_hifiasm_flye/assembly/test_hifiasm_flye_assembly.unmapped.txt", "test_hifiasm_flye/reads", "test_hifiasm_flye/reads/fastplong", "test_hifiasm_flye/reads/fastplong/hifi", @@ -185,7 +193,13 @@ "test_hifiasm_ul/assembly/hifiasm/test_hifiasm_ul.ec.bin", "test_hifiasm_ul/assembly/hifiasm/test_hifiasm_ul.ovlp.reverse.bin", "test_hifiasm_ul/assembly/hifiasm/test_hifiasm_ul.ovlp.source.bin", + "test_hifiasm_ul/assembly/hifiasm/test_hifiasm_ul.re.uidx.bin", + "test_hifiasm_ul/assembly/hifiasm/test_hifiasm_ul.re.uidx.ucr.bin", + "test_hifiasm_ul/assembly/hifiasm/test_hifiasm_ul.re.ul.msk.bin", + "test_hifiasm_ul/assembly/hifiasm/test_hifiasm_ul.re.ul.ovlp.bin", "test_hifiasm_ul/assembly/hifiasm/test_hifiasm_ul.stderr.log", + "test_hifiasm_ul/assembly/hifiasm/test_hifiasm_ul.uidx.bin", + "test_hifiasm_ul/assembly/hifiasm/test_hifiasm_ul.ul.ovlp.bin", "test_hifiasm_ul/assembly/test_hifiasm_ul_assembly.gff3", "test_hifiasm_ul/assembly/test_hifiasm_ul_assembly.unmapped.txt", "test_hifiasm_ul/reads", @@ -215,6 +229,8 @@ "test_ont_flye/assembly/flye/test_ont_flye.assembly_info.txt", "test_ont_flye/assembly/flye/test_ont_flye.flye.log", "test_ont_flye/assembly/flye/test_ont_flye.params.json", + "test_ont_flye/assembly/test_ont_flye_assembly.gff3", + "test_ont_flye/assembly/test_ont_flye_assembly.unmapped.txt", "test_ont_flye/reads", "test_ont_flye/reads/fastplong", "test_ont_flye/reads/fastplong/hifi", @@ -259,9 +275,7 @@ "test_ont_hifiasm/scaffold/ragtag", "test_ont_hifiasm/scaffold/ragtag/test_ont_hifiasm_ragtag.agp", "test_ont_hifiasm/scaffold/ragtag/test_ont_hifiasm_ragtag.fasta", - "test_ont_hifiasm/scaffold/ragtag/test_ont_hifiasm_ragtag.gff3", - "test_ont_hifiasm/scaffold/ragtag/test_ont_hifiasm_ragtag.stats", - "test_ont_hifiasm/scaffold/ragtag/test_ont_hifiasm_ragtag.unmapped.txt" + "test_ont_hifiasm/scaffold/ragtag/test_ont_hifiasm_ragtag.stats" ], [ "test_flye_hifiasm.params.json:md5,afa91c041bce5e190f4a699d11b69db6", @@ -270,66 +284,71 @@ "test_flye_hifiasm.bp.p_ctg.gfa:md5,8fe65466d76815ffe1663ff6d8f2e8d1", "test_flye_hifiasm.bp.p_utg.gfa:md5,ba2c77ebdb2ad3e6060f5574e890c6eb", "test_flye_hifiasm.bp.r_utg.gfa:md5,ba2c77ebdb2ad3e6060f5574e890c6eb", - "test_flye_hifiasm_hifi.fastplong.json:md5,fa79b411112c8176d9b400595a6cdc62", - "test_flye_hifiasm_ont.fastplong.json:md5,87b24dfdd4b0a7a1e743e34948751dfe", - "test_hifi_flye.bp.hap1.p_ctg.gfa:md5,c9a084903ea872a7ac28f3bbd5eee8f3", - "test_hifi_flye.bp.hap2.p_ctg.gfa:md5,d41d8cd98f00b204e9800998ecf8427e", - "test_hifi_flye.bp.p_ctg.gfa:md5,d78f7a647429b254d9d46aab4d1306a0", - "test_hifi_flye.bp.p_utg.gfa:md5,e2b6621386edda1636dfb84ddd676ea8", - "test_hifi_flye.bp.r_utg.gfa:md5,e2b6621386edda1636dfb84ddd676ea8", - "test_hifi_flye_assembly.unmapped.txt:md5,e99c39c4afff7b433fb3973359dae6a4", - "test_hifi_flye_hifi.fastplong.json:md5,36158364b20e75a0bfbc8e3f45d69d2a", - "test_hifi_flye_ont.fastplong.json:md5,eab2fc3332839ce008753c4e00aca912", - "test_hifi_flye_ragtag.fasta:md5,51f2975257b49657716d2ca7cc6d5fe2", - "test_hifi_flye_ragtag.stats:md5,466273b499384b2c781c83dc583b8c99", - "test_hifi_flye_ragtag.unmapped.txt:md5,e99c39c4afff7b433fb3973359dae6a4", - "test_hifi_hifiasm.bp.hap1.p_ctg.gfa:md5,c9a084903ea872a7ac28f3bbd5eee8f3", - "test_hifi_hifiasm.bp.hap2.p_ctg.gfa:md5,d41d8cd98f00b204e9800998ecf8427e", - "test_hifi_hifiasm.bp.p_ctg.gfa:md5,d78f7a647429b254d9d46aab4d1306a0", - "test_hifi_hifiasm.bp.p_utg.gfa:md5,e2b6621386edda1636dfb84ddd676ea8", - "test_hifi_hifiasm.bp.r_utg.gfa:md5,e2b6621386edda1636dfb84ddd676ea8", - "test_hifi_hifiasm_assembly.unmapped.txt:md5,e99c39c4afff7b433fb3973359dae6a4", - "test_hifi_hifiasm_hifi.fastplong.json:md5,ccca90d161aa7612409b275d38f12e23", - "test_hifi_hifiasm_ont.fastplong.json:md5,f83e4be65172652e188ba7afb8ba9219", + "test_flye_hifiasm_assembly_patch.comps.fasta:md5,c29d28dd1a028a4cc1aa4d5c0f584598", + "test_flye_hifiasm_assembly_patch.ctg.fasta:md5,31482a63bf6c58c88cdcb2df5ca65440", + "test_flye_hifiasm_assembly_patch.patch.err:md5,d41d8cd98f00b204e9800998ecf8427e", + "test_flye_hifiasm_assembly_patch.patch.fasta:md5,31482a63bf6c58c88cdcb2df5ca65440", + "test_flye_hifiasm_assembly_patch.rename.fasta:md5,0515edc4c23258ef17d6ba085a6e4d31", + "test_flye_hifiasm_assembly.unmapped.txt:md5,d41d8cd98f00b204e9800998ecf8427e", + "test_flye_hifiasm_hifi.fastplong.json:md5,0e20fedb8dfe0646232060da502883ee", + "test_flye_hifiasm_ont.fastplong.json:md5,826cc5a321dd130a2f2c53cdb1a345b2", + "test_flye_hifiasm_ragtag.fasta:md5,3a03ddeda9a82c1e7d5a7e252313a546", + "test_flye_hifiasm_ragtag.stats:md5,f9712f1a0af0669e12d74ef97181c428", + "test_hifi_flye_hifi.fastplong.json:md5,daad59b32b84b79c29c274ed20f03cce", + "test_hifi_flye_ont.fastplong.json:md5,198eb762846548340557f5f861f10c11", + "test_hifi_hifiasm.bp.hap1.p_ctg.gfa:md5,46ee70869884ad585165bd48081414e9", + "test_hifi_hifiasm.bp.hap2.p_ctg.gfa:md5,7792865547989d6d284f640425c4e36c", + "test_hifi_hifiasm.bp.p_ctg.gfa:md5,8fe65466d76815ffe1663ff6d8f2e8d1", + "test_hifi_hifiasm.bp.p_utg.gfa:md5,ba2c77ebdb2ad3e6060f5574e890c6eb", + "test_hifi_hifiasm.bp.r_utg.gfa:md5,ba2c77ebdb2ad3e6060f5574e890c6eb", + "test_hifi_hifiasm_assembly.unmapped.txt:md5,d41d8cd98f00b204e9800998ecf8427e", + "test_hifi_hifiasm_hifi.fastplong.json:md5,e58f896e9b70344ae4e607324c2c6742", + "test_hifi_hifiasm_ont.fastplong.json:md5,eedbb98c4d3870f38320d75b21c86604", "test_hifiasm_flye.params.json:md5,54b576cb6d4d27656878a7fd3657bde9", "test_hifiasm_flye.bp.hap1.p_ctg.gfa:md5,c9a084903ea872a7ac28f3bbd5eee8f3", "test_hifiasm_flye.bp.hap2.p_ctg.gfa:md5,d41d8cd98f00b204e9800998ecf8427e", "test_hifiasm_flye.bp.p_ctg.gfa:md5,d78f7a647429b254d9d46aab4d1306a0", "test_hifiasm_flye.bp.p_utg.gfa:md5,e2b6621386edda1636dfb84ddd676ea8", "test_hifiasm_flye.bp.r_utg.gfa:md5,e2b6621386edda1636dfb84ddd676ea8", - "test_hifiasm_flye_hifi.fastplong.json:md5,fafb25091859d8f815f1c12e1ce3c20a", - "test_hifiasm_flye_ont.fastplong.json:md5,ef9b8a8aae9d5c28f48245eea51542a9", + "test_hifiasm_flye_assembly_patch.comps.fasta:md5,6cfb8070823979baece63889e324e246", + "test_hifiasm_flye_assembly_patch.ctg.fasta:md5,6ff15d809eaa0ecf6381400cdd7f0770", + "test_hifiasm_flye_assembly_patch.patch.err:md5,d41d8cd98f00b204e9800998ecf8427e", + "test_hifiasm_flye_assembly_patch.patch.fasta:md5,6ff15d809eaa0ecf6381400cdd7f0770", + "test_hifiasm_flye_assembly_patch.rename.fasta:md5,48208c6df370a225c6f54f378d7a8e39", + "test_hifiasm_flye_assembly.unmapped.txt:md5,e99c39c4afff7b433fb3973359dae6a4", + "test_hifiasm_flye_hifi.fastplong.json:md5,2671b946d2bf533341b86a9f2424e5c3", + "test_hifiasm_flye_ont.fastplong.json:md5,924d11a05764994135ff5de294052a02", "test_hifiasm_ul.bp.hap1.p_ctg.gfa:md5,46ee70869884ad585165bd48081414e9", "test_hifiasm_ul.bp.hap2.p_ctg.gfa:md5,7792865547989d6d284f640425c4e36c", "test_hifiasm_ul.bp.p_ctg.gfa:md5,8fe65466d76815ffe1663ff6d8f2e8d1", "test_hifiasm_ul.bp.p_utg.gfa:md5,ba2c77ebdb2ad3e6060f5574e890c6eb", "test_hifiasm_ul.bp.r_utg.gfa:md5,ba2c77ebdb2ad3e6060f5574e890c6eb", "test_hifiasm_ul_assembly.unmapped.txt:md5,d41d8cd98f00b204e9800998ecf8427e", - "test_hifiasm_ul_hifi.fastplong.json:md5,f63fad76bc219fe1875237c8becc9d40", - "test_hifiasm_ul_ont.fastplong.json:md5,628c20cd4d3fc93f6d7f9cbdf57049fd", - "test_hifiasm_ul_longstitch.tigmint-ntLink-arks.fa:md5,ff3182fa3e39281c2b8550c9137d9442", - "test_hifiasm_ul_longstitch.tigmint-ntLink.fa:md5,c983ebdc067bea70c625dd9857733b68", + "test_hifiasm_ul_hifi.fastplong.json:md5,f52a2b6d9aa29fbe5749684ee479de90", + "test_hifiasm_ul_ont.fastplong.json:md5,86da6d58ac4630bfeae3ee08014ecb0b", + "test_hifiasm_ul_longstitch.tigmint-ntLink-arks.fa:md5,2bc73aa0085ed4ef5261f3fadfc64b46", + "test_hifiasm_ul_longstitch.tigmint-ntLink.fa:md5,6191cac0672c778be5fc93b74121ed95", "test_hifiasm_ul_longstitch.unmapped.txt:md5,d41d8cd98f00b204e9800998ecf8427e", "test_ont_flye.params.json:md5,afa91c041bce5e190f4a699d11b69db6", - "test_ont_flye_hifi.fastplong.json:md5,0578aa3e71c029417c34910ef1da88ce", - "test_ont_flye_ont.fastplong.json:md5,161921438b26ce26127efdbd13a59ea9", + "test_ont_flye_assembly.unmapped.txt:md5,d41d8cd98f00b204e9800998ecf8427e", + "test_ont_flye_hifi.fastplong.json:md5,9205a4cf65cce249429badc208348349", + "test_ont_flye_ont.fastplong.json:md5,e418b2481ade21bc45e77d1437bad913", "test_ont_hifiasm.bp.hap1.p_ctg.gfa:md5,c9a084903ea872a7ac28f3bbd5eee8f3", "test_ont_hifiasm.bp.hap2.p_ctg.gfa:md5,d41d8cd98f00b204e9800998ecf8427e", "test_ont_hifiasm.bp.p_ctg.gfa:md5,d78f7a647429b254d9d46aab4d1306a0", "test_ont_hifiasm.bp.p_utg.gfa:md5,e2b6621386edda1636dfb84ddd676ea8", "test_ont_hifiasm.bp.r_utg.gfa:md5,e2b6621386edda1636dfb84ddd676ea8", "test_ont_hifiasm_assembly.unmapped.txt:md5,e99c39c4afff7b433fb3973359dae6a4", - "test_ont_hifiasm_hifi.fastplong.json:md5,a033e6890ae430fe64b4740e3dc8fe91", - "test_ont_hifiasm_ont.fastplong.json:md5,a6de185eb4dc104fd811be325ae27fa1", + "test_ont_hifiasm_hifi.fastplong.json:md5,91878df7edc2b07a885c103f260fc70e", + "test_ont_hifiasm_ont.fastplong.json:md5,e97714274268b5a30be4d9851f2b61b3", "test_ont_hifiasm_ragtag.fasta:md5,51f2975257b49657716d2ca7cc6d5fe2", - "test_ont_hifiasm_ragtag.stats:md5,466273b499384b2c781c83dc583b8c99", - "test_ont_hifiasm_ragtag.unmapped.txt:md5,e99c39c4afff7b433fb3973359dae6a4" + "test_ont_hifiasm_ragtag.stats:md5,466273b499384b2c781c83dc583b8c99" ] ], "meta": { "nf-test": "0.9.2", "nextflow": "25.04.8" }, - "timestamp": "2025-12-09T16:35:35.479027188" + "timestamp": "2026-02-05T14:53:40.86797631" } } \ No newline at end of file From 11cf0cd8d8b282f56c66d9eb0e47738470729ef8 Mon Sep 17 00:00:00 2001 From: Niklas Schandry Date: Tue, 10 Feb 2026 15:40:21 +0100 Subject: [PATCH 114/162] config for hic --- conf/modules/QC/alignments.config | 11 +++++++++++ conf/modules/QC/busco.config | 9 +++++++++ conf/modules/QC/merqury.config | 8 ++++++++ conf/modules/QC/quast.config | 8 ++++++++ conf/modules/scaffolding.config | 10 ++++++++++ 5 files changed, 46 insertions(+) diff --git a/conf/modules/QC/alignments.config b/conf/modules/QC/alignments.config index 0829d311..e9f52ef9 100644 --- a/conf/modules/QC/alignments.config +++ b/conf/modules/QC/alignments.config @@ -67,6 +67,17 @@ process { (meta.qc_reads == 'ont' ? "-ax lr:hq" : "-ax map-hifi") } } + withName: '.*HIC:.*MAP_TO_ASSEMBLY.*' { + ext.prefix = { "${meta.id}_hic" } + publishDir = [ + path: { "${params.outdir}/${meta.id}/QC/alignments/" }, + mode: params.publish_dir_mode, + saveAs: { filename -> filename.equals('versions.yml') ? null : filename } + ] + ext.args = { + (meta.qc_reads == 'ont' ? "-ax lr:hq" : "-ax map-hifi") + } + } withName: '.*RAGTAG:.*MAP_TO_ASSEMBLY.*' { ext.prefix = { "${meta.id}_ragtag" } publishDir = [ diff --git a/conf/modules/QC/busco.config b/conf/modules/QC/busco.config index 4972756f..0c0a907a 100644 --- a/conf/modules/QC/busco.config +++ b/conf/modules/QC/busco.config @@ -54,4 +54,13 @@ process { saveAs: { filename -> filename.equals('versions.yml') ? null : filename } ] } + withName: '.*:SCAFFOLD:.*HIC:QC.*:BUSCO' { + ext.prefix = { "${meta.id}_hic-${lineage}" } + publishDir = [ + path: { "${params.outdir}/${meta.id}/QC/BUSCO/" }, + mode: params.publish_dir_mode, + pattern: "*{-busco,_summary}*", + saveAs: { filename -> filename.equals('versions.yml') ? null : filename } + ] + } } diff --git a/conf/modules/QC/merqury.config b/conf/modules/QC/merqury.config index 06a31325..10d0ef8a 100644 --- a/conf/modules/QC/merqury.config +++ b/conf/modules/QC/merqury.config @@ -39,6 +39,14 @@ process { saveAs: { filename -> filename.equals('versions.yml') ? null : filename } ] } + withName: '.*:SCAFFOLD:HIC:.*:MERQURY' { + ext.prefix = { "${meta.id}_hic" } + publishDir = [ + path: { "${params.outdir}/${meta.id}/QC/merqury/" }, + mode: params.publish_dir_mode, + saveAs: { filename -> filename.equals('versions.yml') ? null : filename } + ] + } // avoid catching ragtag from ont_on_hifi assembly withName: '.*:SCAFFOLD:.*RAGTAG:.*:MERQURY' { ext.prefix = { "${meta.id}_ragtag" } diff --git a/conf/modules/QC/quast.config b/conf/modules/QC/quast.config index b97a7494..9b91af3f 100644 --- a/conf/modules/QC/quast.config +++ b/conf/modules/QC/quast.config @@ -39,6 +39,14 @@ process { saveAs: { filename -> filename.equals('versions.yml') ? null : filename } ] } + withName: '.*SCAFFOLD:HIC.*:QUAST' { + ext.prefix = { "${meta.id}_hic" } + publishDir = [ + path: { "${params.outdir}/${meta.id}/QC/QUAST/" }, + mode: params.publish_dir_mode, + saveAs: { filename -> filename.equals('versions.yml') ? null : filename } + ] + } // avoid catching ragtag from ont_on_hifi assembly withName: '.*:SCAFFOLD:.*RAGTAG:.*:QUAST' { ext.prefix = { "${meta.id}_ragtag" } diff --git a/conf/modules/scaffolding.config b/conf/modules/scaffolding.config index 27b60f81..bec450a7 100644 --- a/conf/modules/scaffolding.config +++ b/conf/modules/scaffolding.config @@ -57,6 +57,7 @@ process { ext.args = { "--no-pairing" } } withName: MARKDUP { + ext.args = "-Djava.io.tmpdir=./tmp-picard-mardkdup --CREATE_INDEX" publishDir = [ path: { "${params.outdir}/${meta.id}/scaffold/hic/markdup/" }, mode: params.publish_dir_mode, @@ -64,6 +65,15 @@ process { ] ext.prefix = { "${meta.id}_hic_dedup" } } + withName: ADD_RG { + ext.args = {[ "--RGID 1", "--RGLB ${meta.id}","--RGPM UNKNOWN", "--RGPL ILLUMINA","--RGPU 0", "--RGSM ${meta.id}"].join(" ").trim()} + publishDir = [ + path: { "${params.outdir}/${meta.id}/scaffold/hic/add_replace_rg/" }, + mode: params.publish_dir_mode, + saveAs: { filename -> filename.equals('versions.yml') ? null : filename } + ] + ext.prefix = { "${meta.id}_add_rg" } + } withName: YAHS { publishDir = [ path: { "${params.outdir}/${meta.id}/scaffold/hic/yahs/" }, From e2acf397dc4bef0659b2d3b09abcad59a91e8670 Mon Sep 17 00:00:00 2001 From: Niklas Schandry Date: Tue, 10 Feb 2026 15:41:16 +0100 Subject: [PATCH 115/162] documentation --- CHANGELOG.md | 13 +++- docs/usage.md | 120 ++++++++++++++++++++++++++++++++++- modules/local/gfa2fa/main.nf | 8 +-- 3 files changed, 134 insertions(+), 7 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7ada9220..e58ae128 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,7 +8,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 This is a major release, with breaking changes. v2.0.0 of genomeassembler is a large refactor of the pipeline to facilitate sample-level parameteristation. This allows to either parameterise the _pipeline_ using `params`, or parameterise _samples_ via the `input` samplesheet. In case both types of parameterisations are used, sample parameters will take priority. -Since this workflow follows a sample-centric implementation, nextflow will always render the full pipeline dag, but depending on configuration samples may not travel through the whole pipeline. This may also cause terminal output to show steps that will never become an active process. +Since this workflow follows a sample-centric implementation, nextflow will always render the full pipeline dag, but depending on configuration samples may not travel through the whole pipeline. This may also cause terminal output to show task instances that will never become an active process. In addition, v2.0.0 contains these changes: @@ -19,14 +19,22 @@ In addition, v2.0.0 contains these changes: - migration to nf-test - increased flexibility of the scaffolding strategy - added option to group samples -- `dorado polish` added as an alternative to `medaka` for ONT polishing. This is an **experimental feature** and not further documented, due to `dorado polish` being under active development. +- `dorado polish` added as an alternative to `medaka` for ONT polishing. This is an **experimental feature**, due to `dorado` being under active development. +- HiC scaffolding subworkflow: + - mapping with `bwamem2` or `minimap2` + - duplicate removal with `picard` + - scaffolding with `yahs` ### `Fixed` ### `Dependencies` - `fastplong` +- `fastp` - `dorado` +- `bwamem2` +- `picard` +- `yahs` ### `Deprecated` @@ -35,6 +43,7 @@ The following tools are no longer used: - `nanoq` - `porechop` - `lima` +- `trimgalor` The following param is no longer implemented: diff --git a/docs/usage.md b/docs/usage.md index 88c95dc3..c1b8b37a 100644 --- a/docs/usage.md +++ b/docs/usage.md @@ -62,7 +62,7 @@ Assembly strategy is controlled via `strategy` (either pipeline parameter or sam - scaffold: Assemble ONT reads and HiFi indepently and scaffold one assembly onto the other. `assembler` has to be provided as `"ontAssembler_hifiAssembler"` and could for example be: "`flye_hifiasm"` to assemble ont reads with `flye` and HiFi reads with `hifiasm` or "hifiasm_hifiasm" to assemble both ont and hifi reads indepently with `hifiasm`. When running in "scaffold" mode, `assembly_scaffolding_order` can be used to control which assembly gets scaffolded onto which, the default being "ont_on_hifi" where ONT assembly is scaffolded onto HifI assembly. Assembler specific arguments can be provided for the assembler via `hifiasm_args` or `flye_args`, or with more fine-grained control via `assembler_ont_args` and `assembler_hifi_args` for scaffolding. -`assembler_ont_args` controls the parameters for the assembler in `single` (with ONT) and `hybrid` strategies, or for the assembler used for ONT reads when using `scaffold`. `assembler_hifi_args` can be used to pass arguments to the assembler used for HiFi reads in `single`, or `scaffold` mode. +`assembler_ont_args` controls the parameters for the assembler used with ONT-reads in `single` and `hybrid` strategies, or for the assembler used for ONT reads when using `scaffold`. `assembler_hifi_args` can be used to pass arguments to the assembler used for HiFi reads in `single`, or `scaffold` mode. ## Samplesheet input @@ -393,3 +393,121 @@ Options controlling annotation liftover | Parameter | Description | Type | | ------------------ | ----------------------------------------- | --------- | | `lift_annotations` | Lift-over annotations (requires ref_gff)? | `boolean` | + +### Updating the pipeline + +When you run the above command, Nextflow automatically pulls the pipeline code from GitHub and stores it as a cached version. When running the pipeline after this, it will always use the cached version if available - even if the pipeline has been updated since. To make sure that you're running the latest version of the pipeline, make sure that you regularly update the cached version of the pipeline: + +```bash +nextflow pull nf-core/genomeassembler +``` + +### Reproducibility + +It is a good idea to specify the pipeline version when running the pipeline on your data. This ensures that a specific version of the pipeline code and software are used when you run your pipeline. If you keep using the same tag, you'll be running the same version of the pipeline, even if there have been changes to the code since. + +First, go to the [nf-core/genomeassembler releases page](https://github.com/nf-core/genomeassembler/releases) and find the latest pipeline version - numeric only (eg. `1.3.1`). Then specify this when running the pipeline with `-r` (one hyphen) - eg. `-r 1.3.1`. Of course, you can switch to another version by changing the number after the `-r` flag. + +This version number will be logged in reports when you run the pipeline, so that you'll know what you used when you look back in the future. + +To further assist in reproducibility, you can use share and reuse [parameter files](#running-the-pipeline) to repeat pipeline runs with the same settings without having to write out a command with every single parameter. + +> [!TIP] +> If you wish to share such profile (such as upload as supplementary material for academic publications), make sure to NOT include cluster specific paths to files, nor institutional specific profiles. + +## Core Nextflow arguments + +> [!NOTE] +> These options are part of Nextflow and use a _single_ hyphen (pipeline parameters use a double-hyphen). + +### `-profile` + +Use this parameter to choose a configuration profile. Profiles can give configuration presets for different compute environments. + +Several generic profiles are bundled with the pipeline which instruct the pipeline to use software packaged using different methods (Docker, Singularity, Podman, Shifter, Charliecloud, Apptainer, Conda) - see below. + +> [!INFO] +> We highly recommend the use of Docker or Singularity containers for full pipeline reproducibility, however when this is not possible, Conda is also supported. + +The pipeline also dynamically loads configurations from [https://github.com/nf-core/configs](https://github.com/nf-core/configs) when it runs, making multiple config profiles for various institutional clusters available at run time. For more information and to check if your system is supported, please see the [nf-core/configs documentation](https://github.com/nf-core/configs#documentation). + +Note that multiple profiles can be loaded, for example: `-profile test,docker` - the order of arguments is important! +They are loaded in sequence, so later profiles can overwrite earlier profiles. + +If `-profile` is not specified, the pipeline will run locally and expect all software to be installed and available on the `PATH`. This is _not_ recommended, since it can lead to different results on different machines dependent on the computer environment. + +- `test` + - A profile with a complete configuration for automated testing + - Includes links to test data so needs no other parameters +- `docker` + - A generic configuration profile to be used with [Docker](https://docker.com/) +- `singularity` + - A generic configuration profile to be used with [Singularity](https://sylabs.io/docs/) +- `podman` + - A generic configuration profile to be used with [Podman](https://podman.io/) +- `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/) +- `apptainer` + - A generic configuration profile to be used with [Apptainer](https://apptainer.org/) +- `wave` + - A generic configuration profile to enable [Wave](https://seqera.io/wave/) containers. Use together with one of the above (requires Nextflow ` 24.03.0-edge` or later). +- `conda` + - A generic configuration profile to be used with [Conda](https://conda.io/docs/). Please only use Conda as a last resort i.e. when it's not possible to run the pipeline with Docker, Singularity, Podman, Shifter, Charliecloud, or Apptainer. + +### `-resume` + +Specify this when restarting a pipeline. Nextflow will use cached results from any pipeline steps where the inputs are the same, continuing from where it got to previously. For input to be considered the same, not only the names must be identical but the files' contents as well. For more info about this parameter, see [this blog post](https://www.nextflow.io/blog/2019/demystifying-nextflow-resume.html). + +You can also supply a run name to resume a specific run: `-resume [run-name]`. Use the `nextflow log` command to show previous run names. + +### `-c` + +Specify the path to a specific config file (this is a core Nextflow command). See the [nf-core website documentation](https://nf-co.re/usage/configuration) for more information. + +## Custom configuration + +### Resource requests + +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. + +### 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. + +### 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. + +### nf-core/configs + +In most cases, you will only need to create a custom config as a one-off but if you and others within your organisation are likely to be running nf-core pipelines regularly and need to use the same settings regularly it may be a good idea to request that your custom config file is uploaded to the `nf-core/configs` git repository. Before you do this please can you test that the config file works with your pipeline of choice using the `-c` parameter. You can then create a pull request to the `nf-core/configs` repository with the addition of your config file, associated documentation file (see examples in [`nf-core/configs/docs`](https://github.com/nf-core/configs/tree/master/docs)), and amending [`nfcore_custom.config`](https://github.com/nf-core/configs/blob/master/nfcore_custom.config) to include your custom profile. + +See the main [Nextflow documentation](https://www.nextflow.io/docs/latest/config.html) for more information about creating your own configuration files. + +If you have any questions or issues please send us a message on [Slack](https://nf-co.re/join/slack) on the [`#configs` channel](https://nfcore.slack.com/channels/configs). + +## Running in the background + +Nextflow handles job submissions and supervises the running jobs. The Nextflow process must run until the pipeline is finished. + +The Nextflow `-bg` flag launches Nextflow in the background, detached from your terminal so that the workflow does not stop if you log out of your session. The logs are saved to a file. + +Alternatively, you can use `screen` / `tmux` or similar tool to create a detached session which you can log back into at a later time. +Some HPC setups also allow you to run nextflow within a cluster job submitted your job scheduler (from where it submits more jobs). + +## Nextflow memory requirements + +In some cases, the Nextflow Java virtual machines can start to request a large amount of memory. +We recommend adding the following line to your environment to limit this (typically in `~/.bashrc` or `~./bash_profile`): + +```bash +NXF_OPTS='-Xms1g -Xmx4g' +``` diff --git a/modules/local/gfa2fa/main.nf b/modules/local/gfa2fa/main.nf index b258f8d6..5f55130e 100644 --- a/modules/local/gfa2fa/main.nf +++ b/modules/local/gfa2fa/main.nf @@ -2,9 +2,9 @@ process GFA_2_FA { 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/52/52ccce28d2ab928ab862e25aae26314d69c8e38bd41ca9431c67ef05221348aa/data' - : 'community.wave.seqera.io/library/coreutils_grep_gzip_lbzip2_pruned:838ba80435a629f8'}" + container "${ workflow.containerEngine == 'singularity' && !task.ext.singularity_pull_docker_container ? + 'https://depot.galaxyproject.org/singularity/samtools:1.22.1--h96c455f_0' : + 'biocontainers/samtools:1.22.1--h96c455f_0' }" input: tuple val(meta), path(gfa_file) @@ -17,7 +17,7 @@ process GFA_2_FA { """ outfile=\$(basename $gfa_file .gfa).fa.gz awk '/^S/{print ">"\$2;print \$3}' ${gfa_file} \\ - | gzip > \$outfile + | bgzip > \$outfile cat <<-END_VERSIONS > versions.yml "${task.process}": awk: \$(mawk -Wversion | sed '1!d; s/.*Awk //; s/,.*//; s/ [0-9]*\$//') From 45a44425768cd8bc57d3f9326a8ff4c78ca733d4 Mon Sep 17 00:00:00 2001 From: Niklas Schandry Date: Tue, 10 Feb 2026 15:41:57 +0100 Subject: [PATCH 116/162] fix issues in handling of HiC data --- subworkflows/local/prepare/main.nf | 8 ----- subworkflows/local/scaffolding/hic/main.nf | 42 ++++++++++++++-------- 2 files changed, 27 insertions(+), 23 deletions(-) diff --git a/subworkflows/local/prepare/main.nf b/subworkflows/local/prepare/main.nf index 26c28f91..67cc163c 100644 --- a/subworkflows/local/prepare/main.nf +++ b/subworkflows/local/prepare/main.nf @@ -89,10 +89,6 @@ workflow PREPARE { // put shortreads back together with samples without shortreads - SHORTREADS.out.main_out - .filter{it-> it.meta.id == "test_hifiasm_hic"} - .view {"Prepare: Shortreads: $it"} - // Added mix with empty to make sure that the channel exists ch_main_shortreaded .mix( @@ -242,10 +238,6 @@ workflow PREPARE { } .set { main_out } - main_out - .filter{it-> it.meta.id == "test_hifiasm_hic"} - .view {"Prepare: main_out: $it"} - main_out.dump(tag: "Prepare: Combined outputs") JELLYFISH.out.genomescope_summary.set { genomescope_summary } diff --git a/subworkflows/local/scaffolding/hic/main.nf b/subworkflows/local/scaffolding/hic/main.nf index 5ad5f322..499f8fda 100644 --- a/subworkflows/local/scaffolding/hic/main.nf +++ b/subworkflows/local/scaffolding/hic/main.nf @@ -1,11 +1,12 @@ -include { QC } from '../../qc/main' -include { YAHS } from '../../../../modules/nf-core/yahs/main' -include { RUN_LIFTOFF } from '../../liftoff/main' -include { BWAMEM2_MEM } from '../../../../modules/nf-core/bwamem2/mem/main' -include { BWAMEM2_INDEX } from '../../../../modules/nf-core/bwamem2/index/main' -include { MINIMAP2_ALIGN as MINIMAP2_HIC } from '../../../../modules/nf-core/minimap2/align/main' -include { PICARD_MARKDUPLICATES as MARKDUP } from '../../../../modules/nf-core/picard/markduplicates/main' -include { SAMTOOLS_FAIDX } from '../../../../modules/nf-core/samtools/faidx/main' +include { QC } from '../../qc/main' +include { YAHS } from '../../../../modules/nf-core/yahs/main' +include { RUN_LIFTOFF } from '../../liftoff/main' +include { BWAMEM2_MEM } from '../../../../modules/nf-core/bwamem2/mem/main' +include { BWAMEM2_INDEX } from '../../../../modules/nf-core/bwamem2/index/main' +include { SAMTOOLS_FAIDX } from '../../../../modules/nf-core/samtools/faidx/main' +include { MINIMAP2_ALIGN as MINIMAP2_HIC } from '../../../../modules/nf-core/minimap2/align/main' +include { PICARD_MARKDUPLICATES as MARKDUP } from '../../../../modules/nf-core/picard/markduplicates/main' +include { PICARD_ADDORREPLACEREADGROUPS as ADD_RG } from '../../../../modules/nf-core/picard/addorreplacereadgroups/main' workflow HIC { take: ch_main @@ -62,9 +63,11 @@ workflow HIC { MINIMAP2_HIC(minimap2_in, true, "csi", [], []) - BWAMEM2_MEM.out.bam.mix(MINIMAP2_HIC.out.bam).set{ markdup_in } + BWAMEM2_MEM.out.bam.mix(MINIMAP2_HIC.out.bam).set{ add_rg_in } - MARKDUP(markdup_in, [], []) + ADD_RG(add_rg_in, [[],[]], [[],[]]) + + MARKDUP(ADD_RG.out.bam, [[],[]], [[],[]]) MARKDUP.out.bam .map { meta, bam -> [meta.id, meta, bam] } @@ -74,14 +77,14 @@ workflow HIC { ) .map {_id, meta, bam, bai -> [meta:meta + [hic_dedup_bam: bam, hic_dedup_bai: bai] ]} .map { - it -> [ + it -> [ it.meta, it.meta.polished ? (it.meta.polished.pilon ?: it.meta.polished.medaka ?: it.meta.polished.dorado) : it.meta.assembly - ] + ] } .set { faidx_in } - SAMTOOLS_FAIDX(faidx_in, [], false) + SAMTOOLS_FAIDX(faidx_in, [[],[]], false) SAMTOOLS_FAIDX.out.fai .map { @@ -98,7 +101,8 @@ workflow HIC { it.meta, it.meta.polished ? (it.meta.polished.pilon ?: it.meta.polished.medaka ?: it.meta.polished.dorado) : it.meta.assembly, it.meta.hic_genome_idx, - it.meta.hic_dedup_bam + it.meta.hic_dedup_bam, + [] ] } .set { yahs_in } @@ -130,7 +134,15 @@ workflow HIC { .set { liftoff_in } RUN_LIFTOFF(liftoff_in) - ch_versions = ch_versions.mix(RUN_LIFTOFF.out.versions) + ch_versions = ch_versions.mix( + RUN_LIFTOFF.out.versions, + QC.out.versions, + BWAMEM2_MEM.out.versions, + BWAMEM2_INDEX.out.versions, + ADD_RG.out.versions_picard, + SAMTOOLS_FAIDX.out.versions_samtools, + MARKDUP.out.versions_picard, + YAHS.out.versions) emit: ch_main = ch_main_scaffolded From 7f3c155fe3ec75f1de814a75f6c2713e223baabc Mon Sep 17 00:00:00 2001 From: Niklas Schandry Date: Tue, 10 Feb 2026 15:42:49 +0100 Subject: [PATCH 117/162] add addorreplacereadgroups module --- .../addorreplacereadgroups/environment.yml | 8 ++ .../picard/addorreplacereadgroups/main.nf | 57 +++++++++ .../picard/addorreplacereadgroups/meta.yml | 117 ++++++++++++++++++ .../addorreplacereadgroups/tests/bam.config | 13 ++ .../addorreplacereadgroups/tests/cram.config | 13 ++ .../addorreplacereadgroups/tests/main.nf.test | 93 ++++++++++++++ .../tests/main.nf.test.snap | 94 ++++++++++++++ 7 files changed, 395 insertions(+) create mode 100644 modules/nf-core/picard/addorreplacereadgroups/environment.yml create mode 100644 modules/nf-core/picard/addorreplacereadgroups/main.nf create mode 100644 modules/nf-core/picard/addorreplacereadgroups/meta.yml create mode 100644 modules/nf-core/picard/addorreplacereadgroups/tests/bam.config create mode 100644 modules/nf-core/picard/addorreplacereadgroups/tests/cram.config create mode 100644 modules/nf-core/picard/addorreplacereadgroups/tests/main.nf.test create mode 100644 modules/nf-core/picard/addorreplacereadgroups/tests/main.nf.test.snap diff --git a/modules/nf-core/picard/addorreplacereadgroups/environment.yml b/modules/nf-core/picard/addorreplacereadgroups/environment.yml new file mode 100644 index 00000000..b4ac4fe0 --- /dev/null +++ b/modules/nf-core/picard/addorreplacereadgroups/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: + # renovate: datasource=conda depName=bioconda/picard + - bioconda::picard=3.4.0 diff --git a/modules/nf-core/picard/addorreplacereadgroups/main.nf b/modules/nf-core/picard/addorreplacereadgroups/main.nf new file mode 100644 index 00000000..5ef3b7d8 --- /dev/null +++ b/modules/nf-core/picard/addorreplacereadgroups/main.nf @@ -0,0 +1,57 @@ +process PICARD_ADDORREPLACEREADGROUPS { + 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/08/0861295baa7c01fc593a9da94e82b44a729dcaf8da92be8e565da109aa549b25/data' : + 'community.wave.seqera.io/library/picard:3.4.0--e9963040df0a9bf6' }" + + input: + tuple val(meta), path(reads) + tuple val(meta2), path(fasta) + tuple val(meta3), path(fasta_index) + + output: + tuple val(meta), path("*.bam") , emit: bam, optional: true + tuple val(meta), path("*.bai") , emit: bai, optional: true + tuple val(meta), path("*.cram"), emit: cram, optional: true + tuple val("${task.process}"), val('picard'), eval("picard AddOrReplaceReadGroups --version 2>&1 | sed -n 's/.*Version://p'"), topic: versions, emit: versions_picard + + when: + task.ext.when == null || task.ext.when + + script: + def args = task.ext.args ?: '' + def prefix = task.ext.prefix ?: "${meta.id}" + def suffix = task.ext.suffix ?: "${reads.getExtension()}" + def reference = fasta ? "--REFERENCE_SEQUENCE ${fasta}" : "" + def avail_mem = 3072 + if (!task.memory) { + log.info '[Picard AddOrReplaceReadGroups] Available memory not known - defaulting to 3GB. Specify process memory requirements to change this.' + } else { + avail_mem = (task.memory.mega*0.8).intValue() + } + + if ("$reads" == "${prefix}.${suffix}") error "Input and output names are the same, use \"task.ext.prefix\" to disambiguate!" + + """ + picard \\ + -Xmx${avail_mem}M \\ + AddOrReplaceReadGroups \\ + $args \\ + $reference \\ + --INPUT ${reads} \\ + --OUTPUT ${prefix}.${suffix} + + """ + + stub: + def prefix = task.ext.prefix ?: "${meta.id}" + def suffix = task.ext.suffix ?: "${reads.getExtension()}" + if ("$reads" == "${prefix}.${suffix}") error "Input and output names are the same, use \"task.ext.prefix\" to disambiguate!" + """ + touch ${prefix}.${suffix} + + """ +} diff --git a/modules/nf-core/picard/addorreplacereadgroups/meta.yml b/modules/nf-core/picard/addorreplacereadgroups/meta.yml new file mode 100644 index 00000000..6c3ed759 --- /dev/null +++ b/modules/nf-core/picard/addorreplacereadgroups/meta.yml @@ -0,0 +1,117 @@ +name: picard_addorreplacereadgroups +description: Assigns all the reads in a file to a single new read-group +keywords: + - add + - replace + - read-group + - picard +tools: + - picard: + description: | + A set of command line tools (in Java) for manipulating high-throughput sequencing (HTS) + data and formats such as SAM/BAM/CRAM and VCF. + homepage: https://broadinstitute.github.io/picard/ + documentation: https://gatk.broadinstitute.org/hc/en-us/articles/360037226472-AddOrReplaceReadGroups-Picard- + tool_dev_url: https://github.com/broadinstitute/picard + licence: ["MIT"] + identifier: biotools:picard_tools +input: + - - meta: + type: map + description: | + Groovy Map containing sample information + e.g. [ id:'test', single_end:false ] + - reads: + type: file + description: Sequence reads file, can be SAM/BAM/CRAM format + pattern: "*.{bam,cram,sam}" + ontologies: [] + - - meta2: + type: map + description: | + Groovy Map containing sample information + e.g. [ id:'test', single_end:false ] + - fasta: + type: file + description: Reference genome file + pattern: "*.{fasta,fa,fasta.gz,fa.gz}" + ontologies: [] + - - meta3: + type: map + description: | + Groovy Map containing sample information + e.g. [ id:'test', single_end:false ] + - fasta_index: + type: file + description: Reference genome index file + pattern: "*.{fai,fasta.fai,fa.fai,fasta.gz.fai,fa.gz.fai}" + ontologies: [] +output: + bam: + - - meta: + type: map + description: | + Groovy Map containing sample information + e.g. [ id:'test', single_end:false ] + - "*.bam": + type: file + description: Output BAM file + pattern: "*.{bam}" + ontologies: [] + bai: + - - meta: + type: map + description: | + Groovy Map containing sample information + e.g. [ id:'test', single_end:false ] + - "*.bai": + type: file + description: An optional BAM index file + pattern: "*.{bai}" + ontologies: [] + cram: + - - meta: + type: map + description: | + Groovy Map containing sample information + e.g. [ id:'test', single_end:false ] + - "*.cram": + type: file + description: Output CRAM file + pattern: "*.{cram}" + ontologies: [] + versions_picard: + - - ${task.process}: + type: string + description: The process the versions were collected from + - picard: + type: string + description: The tool name + - "picard AddOrReplaceReadGroups --version 2>&1 | sed -n 's/.*Version://p'": + 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 + - picard: + type: string + description: The tool name + - "picard AddOrReplaceReadGroups --version 2>&1 | sed -n 's/.*Version://p'": + type: string + description: The command used to generate the version of the tool + +authors: + - "@sateeshperi" + - "@mjcipriano" + - "@hseabolt" + - "@cmatKhan" + - "@muffato" +maintainers: + - "@sateeshperi" + - "@mjcipriano" + - "@hseabolt" + - "@cmatKhan" + - "@muffato" diff --git a/modules/nf-core/picard/addorreplacereadgroups/tests/bam.config b/modules/nf-core/picard/addorreplacereadgroups/tests/bam.config new file mode 100644 index 00000000..3f37c2fd --- /dev/null +++ b/modules/nf-core/picard/addorreplacereadgroups/tests/bam.config @@ -0,0 +1,13 @@ +process { + withName: 'PICARD_ADDORREPLACEREADGROUPS'{ + ext.prefix = { "${meta.id}.replaced"} + ext.args = {[ + "--CREATE_INDEX", + "-LB ${meta.id}", + "-PL ILLUMINA", + "-PU bc1", + "-SM ${meta.id}" + ].join(' ').trim()} + } + +} diff --git a/modules/nf-core/picard/addorreplacereadgroups/tests/cram.config b/modules/nf-core/picard/addorreplacereadgroups/tests/cram.config new file mode 100644 index 00000000..966c14d7 --- /dev/null +++ b/modules/nf-core/picard/addorreplacereadgroups/tests/cram.config @@ -0,0 +1,13 @@ +process { + withName: 'PICARD_ADDORREPLACEREADGROUPS'{ + ext.prefix = { "${meta.id}.replaced"} + ext.args = {[ + "-LB ${meta.id}", + "-PL ILLUMINA", + "-PU bc1", + "-SM ${meta.id}" + ].join(' ').trim()} + ext.suffix = { "cram" } + } + +} diff --git a/modules/nf-core/picard/addorreplacereadgroups/tests/main.nf.test b/modules/nf-core/picard/addorreplacereadgroups/tests/main.nf.test new file mode 100644 index 00000000..45729f3b --- /dev/null +++ b/modules/nf-core/picard/addorreplacereadgroups/tests/main.nf.test @@ -0,0 +1,93 @@ + +nextflow_process { + + name "Test Process PICARD_ADDORREPLACEREADGROUPS" + script "../main.nf" + process "PICARD_ADDORREPLACEREADGROUPS" + + tag "modules" + tag "modules_nfcore" + tag "picard" + tag "picard/addorreplacereadgroups" + + test("sarscov2 - bam") { + config "./bam.config" + + when { + process { + """ + input[0] = [ [:], file(params.modules_testdata_base_path + 'genomics/sarscov2/illumina/bam/test.paired_end.sorted.bam', checkIfExists: true) ] + input[1] = [ [:], [] ] + input[2] = [ [:], [] ] + """ + } + } + + then { + assertAll( + { assert process.success }, + { assert snapshot( + file(process.out.bam[0][1]).name, + file(process.out.bai[0][1]).name, + process.out.findAll { key, val -> key.startsWith("versions") } + ).match() + }, + ) + } + + } + + test("homo_sapiens - cram") { + config "./cram.config" + + when { + process { + """ + input[0] = [ + [ id:'test' ], // meta map + [ + file(params.modules_testdata_base_path + 'genomics/homo_sapiens/illumina/cram/test.paired_end.sorted.cram', checkIfExists: true), ] + ] + input[1] = [ [:], file(params.modules_testdata_base_path + 'genomics/homo_sapiens/genome/genome.fasta', checkIfExists: true) ] + input[2] = [ [:], file(params.modules_testdata_base_path + 'genomics/homo_sapiens/genome/genome.fasta.fai', checkIfExists: true) ] + """ + } + } + + then { + assertAll( + { assert process.success }, + { assert snapshot( + file(process.out.cram[0][1]).name, + process.out.findAll { key, val -> key.startsWith("versions") } + ).match() + }, + ) + } + + } + + test("sarscov2 - bam - stub") { + config "./bam.config" + options "-stub" + + when { + process { + """ + input[0] = [ [:], file(params.modules_testdata_base_path + 'genomics/sarscov2/illumina/bam/test.paired_end.sorted.bam', checkIfExists: true) ] + input[1] = [ [:], [] ] + input[2] = [ [:], [] ] + """ + } + } + + then { + assertAll( + { assert process.success }, + { assert snapshot(process.out).match() } + ) + } + + } + +} diff --git a/modules/nf-core/picard/addorreplacereadgroups/tests/main.nf.test.snap b/modules/nf-core/picard/addorreplacereadgroups/tests/main.nf.test.snap new file mode 100644 index 00000000..d2a99ee2 --- /dev/null +++ b/modules/nf-core/picard/addorreplacereadgroups/tests/main.nf.test.snap @@ -0,0 +1,94 @@ +{ + "homo_sapiens - cram": { + "content": [ + "test.replaced.cram", + { + "versions_picard": [ + [ + "PICARD_ADDORREPLACEREADGROUPS", + "picard", + "3.4.0" + ] + ] + } + ], + "meta": { + "nf-test": "0.9.3", + "nextflow": "25.10.2" + }, + "timestamp": "2026-02-02T09:36:16.966842212" + }, + "sarscov2 - bam - stub": { + "content": [ + { + "0": [ + [ + { + + }, + "null.replaced.bam:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "1": [ + + ], + "2": [ + + ], + "3": [ + [ + "PICARD_ADDORREPLACEREADGROUPS", + "picard", + "3.4.0" + ] + ], + "bai": [ + + ], + "bam": [ + [ + { + + }, + "null.replaced.bam:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "cram": [ + + ], + "versions_picard": [ + [ + "PICARD_ADDORREPLACEREADGROUPS", + "picard", + "3.4.0" + ] + ] + } + ], + "meta": { + "nf-test": "0.9.3", + "nextflow": "25.10.2" + }, + "timestamp": "2026-02-02T09:36:29.861163004" + }, + "sarscov2 - bam": { + "content": [ + "null.replaced.bam", + "null.replaced.bai", + { + "versions_picard": [ + [ + "PICARD_ADDORREPLACEREADGROUPS", + "picard", + "3.4.0" + ] + ] + } + ], + "meta": { + "nf-test": "0.9.3", + "nextflow": "25.10.2" + }, + "timestamp": "2026-02-02T09:36:00.935196996" + } +} \ No newline at end of file From e01d9d6d41104d7c8f20b21671fac6e3e1985b3c Mon Sep 17 00:00:00 2001 From: Niklas Schandry Date: Tue, 10 Feb 2026 15:43:57 +0100 Subject: [PATCH 118/162] update modules --- modules.json | 10 +- modules/nf-core/trimgalore/environment.yml | 9 - modules/nf-core/trimgalore/main.nf | 107 -------- modules/nf-core/trimgalore/meta.yml | 105 -------- modules/nf-core/trimgalore/tests/main.nf.test | 188 ------------- .../trimgalore/tests/main.nf.test.snap | 251 ------------------ .../nf-core/trimgalore/tests/nextflow.config | 5 - 7 files changed, 5 insertions(+), 670 deletions(-) delete mode 100644 modules/nf-core/trimgalore/environment.yml delete mode 100644 modules/nf-core/trimgalore/main.nf delete mode 100644 modules/nf-core/trimgalore/meta.yml delete mode 100644 modules/nf-core/trimgalore/tests/main.nf.test delete mode 100644 modules/nf-core/trimgalore/tests/main.nf.test.snap delete mode 100644 modules/nf-core/trimgalore/tests/nextflow.config diff --git a/modules.json b/modules.json index 59d9f05c..27a0d25c 100644 --- a/modules.json +++ b/modules.json @@ -80,6 +80,11 @@ "installed_by": ["modules"], "patch": "modules/nf-core/minimap2/align/minimap2-align.diff" }, + "picard/addorreplacereadgroups": { + "branch": "master", + "git_sha": "74ec93d00bef147da3fb1f2262e8d31c14108f88", + "installed_by": ["modules"] + }, "picard/markduplicates": { "branch": "master", "git_sha": "66d5808eaaabd9de8997c4c31a9e8cdd3b56c080", @@ -135,11 +140,6 @@ "git_sha": "c8be52dba1166c678e74cda9c3a3c221635c8bb1", "installed_by": ["bam_stats_samtools", "modules"] }, - "trimgalore": { - "branch": "master", - "git_sha": "e753770db613ce014b3c4bc94f6cba443427b726", - "installed_by": ["modules"] - }, "yahs": { "branch": "master", "git_sha": "5c3c4f4753d55bd3ba93ce29ed1100e964c52f74", diff --git a/modules/nf-core/trimgalore/environment.yml b/modules/nf-core/trimgalore/environment.yml deleted file mode 100644 index 568b9e72..00000000 --- a/modules/nf-core/trimgalore/environment.yml +++ /dev/null @@ -1,9 +0,0 @@ ---- -# yaml-language-server: $schema=https://raw.githubusercontent.com/nf-core/modules/master/modules/environment-schema.json -channels: - - conda-forge - - bioconda -dependencies: - - bioconda::cutadapt=4.9 - - bioconda::trim-galore=0.6.10 - - conda-forge::pigz=2.8 diff --git a/modules/nf-core/trimgalore/main.nf b/modules/nf-core/trimgalore/main.nf deleted file mode 100644 index 5fe53669..00000000 --- a/modules/nf-core/trimgalore/main.nf +++ /dev/null @@ -1,107 +0,0 @@ -process TRIMGALORE { - tag "${meta.id}" - label 'process_high' - - 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/9b/9becad054093ad4083a961d12733f2a742e11728fe9aa815d678b882b3ede520/data' - : 'community.wave.seqera.io/library/cutadapt_trim-galore_pigz:a98edd405b34582d'}" - - input: - tuple val(meta), path(reads) - - output: - tuple val(meta), path("*{3prime,5prime,trimmed,val}{,_1,_2}.fq.gz"), emit: reads - tuple val(meta), path("*report.txt") , emit: log, optional: true - tuple val(meta), path("*unpaired{,_1,_2}.fq.gz") , emit: unpaired, optional: true - tuple val(meta), path("*.html") , emit: html, optional: true - tuple val(meta), path("*.zip") , emit: zip, optional: true - path "versions.yml" , emit: versions - - when: - task.ext.when == null || task.ext.when - - script: - def args = task.ext.args ?: '' - // Calculate number of --cores for TrimGalore based on value of task.cpus - // See: https://github.com/FelixKrueger/TrimGalore/blob/master/CHANGELOG.md#version-060-release-on-1-mar-2019 - // See: https://github.com/nf-core/atacseq/pull/65 - def cores = 1 - if (task.cpus) { - cores = (task.cpus as int) - 4 - if (meta.single_end) { - cores = (task.cpus as int) - 3 - } - if (cores < 1) { - cores = 1 - } - if (cores > 8) { - cores = 8 - } - } - - // Added soft-links to original fastqs for consistent naming in MultiQC - def prefix = task.ext.prefix ?: "${meta.id}" - if (meta.single_end) { - def args_list = args.split("\\s(?=--)").toList() - args_list.removeAll { it.toLowerCase().contains('_r2 ') } - """ - [ ! -f ${prefix}.fastq.gz ] && ln -s ${reads} ${prefix}.fastq.gz - trim_galore \\ - ${args_list.join(' ')} \\ - --cores ${cores} \\ - --gzip \\ - ${prefix}.fastq.gz - - cat <<-END_VERSIONS > versions.yml - "${task.process}": - trimgalore: \$(echo \$(trim_galore --version 2>&1) | sed 's/^.*version //; s/Last.*\$//') - cutadapt: \$(cutadapt --version) - pigz: \$( pigz --version 2>&1 | sed 's/pigz //g' ) - END_VERSIONS - """ - } - else { - """ - [ ! -f ${prefix}_1.fastq.gz ] && ln -s ${reads[0]} ${prefix}_1.fastq.gz - [ ! -f ${prefix}_2.fastq.gz ] && ln -s ${reads[1]} ${prefix}_2.fastq.gz - trim_galore \\ - ${args} \\ - --cores ${cores} \\ - --paired \\ - --gzip \\ - ${prefix}_1.fastq.gz \\ - ${prefix}_2.fastq.gz - - cat <<-END_VERSIONS > versions.yml - "${task.process}": - trimgalore: \$(echo \$(trim_galore --version 2>&1) | sed 's/^.*version //; s/Last.*\$//') - cutadapt: \$(cutadapt --version) - pigz: \$( pigz --version 2>&1 | sed 's/pigz //g' ) - END_VERSIONS - """ - } - - stub: - def prefix = task.ext.prefix ?: "${meta.id}" - if (meta.single_end) { - output_command = "echo '' | gzip > ${prefix}_trimmed.fq.gz ;" - output_command += "touch ${prefix}.fastq.gz_trimming_report.txt" - } - else { - output_command = "echo '' | gzip > ${prefix}_1_trimmed.fq.gz ;" - output_command += "touch ${prefix}_1.fastq.gz_trimming_report.txt ;" - output_command += "echo '' | gzip > ${prefix}_2_trimmed.fq.gz ;" - output_command += "touch ${prefix}_2.fastq.gz_trimming_report.txt" - } - """ - ${output_command} - - cat <<-END_VERSIONS > versions.yml - "${task.process}": - trimgalore: \$(echo \$(trim_galore --version 2>&1) | sed 's/^.*version //; s/Last.*\$//') - cutadapt: \$(cutadapt --version) - pigz: \$( pigz --version 2>&1 | sed 's/pigz //g' ) - END_VERSIONS - """ -} diff --git a/modules/nf-core/trimgalore/meta.yml b/modules/nf-core/trimgalore/meta.yml deleted file mode 100644 index 43ac961c..00000000 --- a/modules/nf-core/trimgalore/meta.yml +++ /dev/null @@ -1,105 +0,0 @@ -name: trimgalore -description: Trim FastQ files using Trim Galore! -keywords: - - trimming - - adapters - - sequencing adapters - - fastq -tools: - - trimgalore: - description: | - A wrapper tool around Cutadapt and FastQC to consistently apply quality - and adapter trimming to FastQ files, with some extra functionality for - MspI-digested RRBS-type (Reduced Representation Bisufite-Seq) libraries. - homepage: https://www.bioinformatics.babraham.ac.uk/projects/trim_galore/ - documentation: https://github.com/FelixKrueger/TrimGalore/blob/master/Docs/Trim_Galore_User_Guide.md - licence: ["GPL-3.0-or-later"] - identifier: "" -input: - - - meta: - type: map - description: | - Groovy Map containing sample information - e.g. [ id:'test', single_end:false ] - - reads: - type: file - description: | - List of input FastQ files of size 1 and 2 for single-end and paired-end data, - respectively. - ontologies: [] -output: - reads: - - - meta: - type: map - description: | - Groovy Map containing sample information - e.g. [ id:'test', single_end:false ] - - "*{3prime,5prime,trimmed,val}{,_1,_2}.fq.gz": - type: map - description: | - Groovy Map containing sample information - e.g. [ id:'test', single_end:false ] - pattern: "*{3prime,5prime,trimmed,val}{,_1,_2}.fq.gz" - log: - - - meta: - type: map - description: | - Groovy Map containing sample information - e.g. [ id:'test', single_end:false ] - - "*report.txt": - type: map - description: | - Groovy Map containing sample information - e.g. [ id:'test', single_end:false ] - pattern: "*_{report.txt}" - unpaired: - - - meta: - type: map - description: | - Groovy Map containing sample information - e.g. [ id:'test', single_end:false ] - - "*unpaired{,_1,_2}.fq.gz": - type: map - description: | - Groovy Map containing sample information - e.g. [ id:'test', single_end:false ] - pattern: "*unpaired*.fq.gz" - html: - - - meta: - type: map - description: | - Groovy Map containing sample information - e.g. [ id:'test', single_end:false ] - - "*.html": - type: map - description: | - Groovy Map containing sample information - e.g. [ id:'test', single_end:false ] - pattern: "*_{fastqc.html}" - zip: - - - meta: - type: map - description: | - Groovy Map containing sample information - e.g. [ id:'test', single_end:false ] - - "*.zip": - type: map - description: | - Groovy Map containing sample information - e.g. [ id:'test', single_end:false ] - pattern: "*_{fastqc.zip}" - versions: - - versions.yml: - type: file - description: File containing software versions - pattern: "versions.yml" - ontologies: - - edam: http://edamontology.org/format_3750 # YAML -authors: - - "@drpatelh" - - "@ewels" - - "@FelixKrueger" -maintainers: - - "@drpatelh" - - "@ewels" - - "@FelixKrueger" diff --git a/modules/nf-core/trimgalore/tests/main.nf.test b/modules/nf-core/trimgalore/tests/main.nf.test deleted file mode 100644 index e2f11e03..00000000 --- a/modules/nf-core/trimgalore/tests/main.nf.test +++ /dev/null @@ -1,188 +0,0 @@ -nextflow_process { - - name "Test Process TRIMGALORE" - script "../main.nf" - process "TRIMGALORE" - tag "modules" - tag "modules_nfcore" - tag "trimgalore" - - test("test_trimgalore_single_end") { - - when { - process { - """ - input[0] = [ [ id:'test', single_end:true ], // meta map - [ file(params.modules_testdata_base_path + "genomics/sarscov2/illumina/fastq/test_1.fastq.gz", checkIfExists: true) ] - ] - """ - } - } - - then { - def read_lines = ["@ERR5069949.2151832 NS500628:121:HK3MMAFX2:2:21208:10793:15304/1", - "TCATAAACCAAAGCACTCACAGTGTCAACAATTTCAGCAGGACAACGCCGACAAGTTCCGAGGAACATGTCTGGACCTATAGTTTTCATAAGTCTACACACTGAATTGAAATATTCTGGTTCTAGTGTGCCCTTAGTTAGCAATGTGCGT", - "AAAAAAEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEAAEEEEAEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEAAEEEEE - { assert path(process.out.reads.get(0).get(1)).linesGzip.contains(read_line) } - } - }, - { report1_lines.each { report1_line -> - { assert path(process.out.log.get(0).get(1)).getText().contains(report1_line) } - } - }, - { assert snapshot(path(process.out.versions.get(0)).yaml).match() }, - ) - } - } - - test("test_trimgalore_single_end - stub") { - - options "-stub" - - when { - process { - """ - input[0] = [ [ id:'test', single_end:true ], // meta map - [ file(params.modules_testdata_base_path + "genomics/sarscov2/illumina/fastq/test_1.fastq.gz", checkIfExists: true) ] - ] - """ - } - } - - then { - assertAll( - { assert process.success }, - { assert snapshot( - process.out, - path(process.out.versions.get(0)).yaml - ).match() }, - ) - } - } - - test("test_trimgalore_paired_end") { - - when { - process { - """ - input[0] = [ [ id:'test', single_end:false ], // meta map - [ - file(params.modules_testdata_base_path + "genomics/sarscov2/illumina/fastq/test_1.fastq.gz", checkIfExists: true), - file(params.modules_testdata_base_path + "genomics/sarscov2/illumina/fastq/test_2.fastq.gz", checkIfExists: true) - ] - ] - """ - } - } - - then { - def read1_lines = ["@ERR5069949.2151832 NS500628:121:HK3MMAFX2:2:21208:10793:15304/1", - "TCATAAACCAAAGCACTCACAGTGTCAACAATTTCAGCAGGACAACGCCGACAAGTTCCGAGGAACATGTCTGGACCTATAGTTTTCATAAGTCTACACACTGAATTGAAATATTCTGGTTCTAGTGTGCCCTTAGTTAGCAATGTGCGT", - "AAAAAAEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEAAEEEEAEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEAAEEEEE - { assert path(process.out.reads.get(0).get(1).get(0)).linesGzip.contains(read1_line) } - } - }, - { read2_lines.each { read2_line -> - { assert path(process.out.reads.get(0).get(1).get(1)).linesGzip.contains(read2_line) } - } - }, - { report1_lines.each { report1_line -> - { assert path(process.out.log.get(0).get(1).get(0)).getText().contains(report1_line) } - } - }, - { report2_lines.each { report2_line -> - { assert path(process.out.log.get(0).get(1).get(1)).getText().contains(report2_line) } - } - }, - { assert snapshot(path(process.out.versions.get(0)).yaml).match() }, - ) - } - } - - test("test_trimgalore_paired_end_keep_unpaired") { - - config "./nextflow.config" - - when { - - params { - module_args = '--retain_unpaired --length 150' - } - - process { - """ - input[0] = [ [ id:'test', single_end:false ], // meta map - [ - file(params.modules_testdata_base_path + "genomics/sarscov2/illumina/fastq/test_1.fastq.gz", checkIfExists: true), - file(params.modules_testdata_base_path + "genomics/sarscov2/illumina/fastq/test_2.fastq.gz", checkIfExists: true) - ] - ] - """ - } - } - - then { - assertAll( - { assert process.success }, - { assert snapshot( - path(process.out.versions.get(0)).yaml, - process.out.reads, - process.out.unpaired - ).match() }, - ) - } - } - - test("test_trimgalore_paired_end - stub") { - - options "-stub" - - when { - process { - """ - input[0] = [ [ id:'test', single_end:false ], // meta map - [ - file(params.modules_testdata_base_path + "genomics/sarscov2/illumina/fastq/test_1.fastq.gz", checkIfExists: true), - file(params.modules_testdata_base_path + "genomics/sarscov2/illumina/fastq/test_2.fastq.gz", checkIfExists: true) - ] - ] - """ - } - } - - then { - assertAll( - { assert process.success }, - { assert snapshot(process.out).match() }, - { assert snapshot(path(process.out.versions.get(0)).yaml).match("versions") }, - ) - } - } -} diff --git a/modules/nf-core/trimgalore/tests/main.nf.test.snap b/modules/nf-core/trimgalore/tests/main.nf.test.snap deleted file mode 100644 index c454ad52..00000000 --- a/modules/nf-core/trimgalore/tests/main.nf.test.snap +++ /dev/null @@ -1,251 +0,0 @@ -{ - "test_trimgalore_single_end": { - "content": [ - { - "TRIMGALORE": { - "trimgalore": "0.6.10", - "cutadapt": 4.9, - "pigz": 2.8 - } - } - ], - "meta": { - "nf-test": "0.9.2", - "nextflow": "24.10.3" - }, - "timestamp": "2025-01-06T12:25:01.330769598" - }, - "test_trimgalore_single_end - stub": { - "content": [ - { - "0": [ - [ - { - "id": "test", - "single_end": true - }, - "test_trimmed.fq.gz:md5,68b329da9893e34099c7d8ad5cb9c940" - ] - ], - "1": [ - [ - { - "id": "test", - "single_end": true - }, - "test.fastq.gz_trimming_report.txt:md5,d41d8cd98f00b204e9800998ecf8427e" - ] - ], - "2": [ - - ], - "3": [ - - ], - "4": [ - - ], - "5": [ - "versions.yml:md5,5928323d579768de37e83c56c821757f" - ], - "html": [ - - ], - "log": [ - [ - { - "id": "test", - "single_end": true - }, - "test.fastq.gz_trimming_report.txt:md5,d41d8cd98f00b204e9800998ecf8427e" - ] - ], - "reads": [ - [ - { - "id": "test", - "single_end": true - }, - "test_trimmed.fq.gz:md5,68b329da9893e34099c7d8ad5cb9c940" - ] - ], - "unpaired": [ - - ], - "versions": [ - "versions.yml:md5,5928323d579768de37e83c56c821757f" - ], - "zip": [ - - ] - }, - { - "TRIMGALORE": { - "trimgalore": "0.6.10", - "cutadapt": 4.9, - "pigz": 2.8 - } - } - ], - "meta": { - "nf-test": "0.9.2", - "nextflow": "24.10.3" - }, - "timestamp": "2025-01-06T12:25:15.582246999" - }, - "test_trimgalore_paired_end - stub": { - "content": [ - { - "0": [ - [ - { - "id": "test", - "single_end": false - }, - [ - "test_1_trimmed.fq.gz:md5,68b329da9893e34099c7d8ad5cb9c940", - "test_2_trimmed.fq.gz:md5,68b329da9893e34099c7d8ad5cb9c940" - ] - ] - ], - "1": [ - [ - { - "id": "test", - "single_end": false - }, - [ - "test_1.fastq.gz_trimming_report.txt:md5,d41d8cd98f00b204e9800998ecf8427e", - "test_2.fastq.gz_trimming_report.txt:md5,d41d8cd98f00b204e9800998ecf8427e" - ] - ] - ], - "2": [ - - ], - "3": [ - - ], - "4": [ - - ], - "5": [ - "versions.yml:md5,5928323d579768de37e83c56c821757f" - ], - "html": [ - - ], - "log": [ - [ - { - "id": "test", - "single_end": false - }, - [ - "test_1.fastq.gz_trimming_report.txt:md5,d41d8cd98f00b204e9800998ecf8427e", - "test_2.fastq.gz_trimming_report.txt:md5,d41d8cd98f00b204e9800998ecf8427e" - ] - ] - ], - "reads": [ - [ - { - "id": "test", - "single_end": false - }, - [ - "test_1_trimmed.fq.gz:md5,68b329da9893e34099c7d8ad5cb9c940", - "test_2_trimmed.fq.gz:md5,68b329da9893e34099c7d8ad5cb9c940" - ] - ] - ], - "unpaired": [ - - ], - "versions": [ - "versions.yml:md5,5928323d579768de37e83c56c821757f" - ], - "zip": [ - - ] - } - ], - "meta": { - "nf-test": "0.9.2", - "nextflow": "24.10.3" - }, - "timestamp": "2025-01-06T12:26:05.201562315" - }, - "versions": { - "content": [ - { - "TRIMGALORE": { - "trimgalore": "0.6.10", - "cutadapt": 4.9, - "pigz": 2.8 - } - } - ], - "meta": { - "nf-test": "0.9.2", - "nextflow": "24.10.3" - }, - "timestamp": "2025-01-06T12:26:05.229598492" - }, - "test_trimgalore_paired_end": { - "content": [ - { - "TRIMGALORE": { - "trimgalore": "0.6.10", - "cutadapt": 4.9, - "pigz": 2.8 - } - } - ], - "meta": { - "nf-test": "0.9.2", - "nextflow": "24.10.3" - }, - "timestamp": "2025-01-06T12:25:33.510924538" - }, - "test_trimgalore_paired_end_keep_unpaired": { - "content": [ - { - "TRIMGALORE": { - "trimgalore": "0.6.10", - "cutadapt": 4.9, - "pigz": 2.8 - } - }, - [ - [ - { - "id": "test", - "single_end": false - }, - [ - "test_1_val_1.fq.gz:md5,75413e85910bbc2e1556e12f6479f935", - "test_2_val_2.fq.gz:md5,d3c588c12646ebd36a0812fe02d0bda6" - ] - ] - ], - [ - [ - { - "id": "test", - "single_end": false - }, - [ - "test_1_unpaired_1.fq.gz:md5,17e0e878f6d0e93b9008a05f128660b6", - "test_2_unpaired_2.fq.gz:md5,b09a064368a867e099e66df5ef69b044" - ] - ] - ] - ], - "meta": { - "nf-test": "0.9.2", - "nextflow": "24.10.3" - }, - "timestamp": "2025-01-06T12:25:46.461002981" - } -} \ No newline at end of file diff --git a/modules/nf-core/trimgalore/tests/nextflow.config b/modules/nf-core/trimgalore/tests/nextflow.config deleted file mode 100644 index d8e3ac13..00000000 --- a/modules/nf-core/trimgalore/tests/nextflow.config +++ /dev/null @@ -1,5 +0,0 @@ -process { - withName: TRIMGALORE { - ext.args = params.module_args - } -} From 3d40b0b49010d636aa7839e2663c29857feb0127 Mon Sep 17 00:00:00 2001 From: Niklas Schandry Date: Tue, 10 Feb 2026 15:44:11 +0100 Subject: [PATCH 119/162] update pipeline schema --- nextflow_schema.json | 47 +++++++++++++++++++++++++------------------- 1 file changed, 27 insertions(+), 20 deletions(-) diff --git a/nextflow_schema.json b/nextflow_schema.json index d52dd958..da2d9da1 100644 --- a/nextflow_schema.json +++ b/nextflow_schema.json @@ -212,8 +212,7 @@ }, "hifiasm_args": { "type": "string", - "description": "Extra arguments passed to `hifiasm`", - "default": "\"\"" + "description": "Extra arguments passed to `hifiasm`" }, "assembler_ont": { "type": "string", @@ -254,8 +253,7 @@ }, "ont_fastplong_args": { "type": "string", - "description": "Additional args to be passed to fastplong for ONT reads", - "default": "\"\"" + "description": "Additional args to be passed to fastplong for ONT reads" }, "hifireads": { "type": "string", @@ -268,8 +266,7 @@ }, "hifi_fastplong_args": { "type": "string", - "description": "Additional args to be passed to fastplong for HiFi reads", - "default": "\"\"" + "description": "Additional args to be passed to fastplong for HiFi reads" }, "jellyfish": { "type": "boolean", @@ -302,8 +299,7 @@ }, "medaka_model": { "type": "string", - "description": "model to use with medaka", - "default": "\"\"" + "description": "model to use with medaka" } } }, @@ -327,7 +323,14 @@ }, "scaffold_hic": { "type": "boolean", - "description": "Scaffold using HiC reads using yahs (requires reads)?" + "description": "Scaffold using HiC reads using yahs (requires reads)?", + "default": true + }, + "hic_aligner": { + "type": "string", + "enum": ["bwa-mem2", "minimap2"], + "description": "Aligner to use for HiC reads; default: bwa-mem2", + "default": "bwa-mem2" } } }, @@ -380,7 +383,8 @@ }, "csi_index_size": { "type": "integer", - "description": "Index size to use for csi index (default: 14), creating and index of size 2^csi_index_size. See samtools index documentation for details. " + "description": "Index size to use for csi index (default: 14), creating and index of size 2^csi_index_size. See samtools index documentation for details. ", + "default": 14 } } }, @@ -419,11 +423,13 @@ }, "shortread_F": { "type": "string", - "description": "Path to forward short reads" + "description": "Path to forward short reads", + "default": "[]" }, "shortread_R": { "type": "string", - "description": "Path to reverse short reads" + "description": "Path to reverse short reads", + "default": "[]" }, "paired": { "type": "string", @@ -437,22 +443,20 @@ "description": "Options for HiC short reads", "default": "", "properties": { - "hic_aligner": { - "type": "string", - "enum": ["bwa-mem2", "minimap2"], - "description": "Aligner to use for HiC reads; default: bwa-mem2" - }, "hic_trim": { "type": "boolean", - "description": "Trim HiC short reads?" + "description": "Trim HiC short reads?", + "default": true }, "hic_F": { "type": "string", - "description": "Path to forward HiC short reads" + "description": "Path to forward HiC short reads", + "default": "[]" }, "hic_R": { "type": "string", - "description": "Path to reverse HiC short reads" + "description": "Path to reverse HiC short reads", + "default": "[]" } } } @@ -490,6 +494,9 @@ }, { "$ref": "#/$defs/short_read_options" + }, + { + "$ref": "#/$defs/hic_options" } ] } From 9bb17489b42b0cc007b9380fe31b97ae03341321 Mon Sep 17 00:00:00 2001 From: Niklas Schandry Date: Fri, 13 Feb 2026 08:59:44 +0100 Subject: [PATCH 120/162] update report, sort version issues. rebase after this --- assets/report/functions/read_busco.R | 1 + assets/report/scripts/_busco_page.R | 2 +- assets/report/scripts/_merqury.R | 1 + assets/report/scripts/_quast.R | 3 ++- conf/modules/QC/busco.config | 2 +- conf/modules/QC/merqury.config | 2 +- conf/modules/QC/quast.config | 2 +- subworkflows/local/scaffolding/main.nf | 10 ++++++++-- 8 files changed, 16 insertions(+), 7 deletions(-) diff --git a/assets/report/functions/read_busco.R b/assets/report/functions/read_busco.R index cf0a5eeb..f0753ace 100644 --- a/assets/report/functions/read_busco.R +++ b/assets/report/functions/read_busco.R @@ -75,6 +75,7 @@ read_busco_batch <- \(x) {read_tsv(x, show_col_types = F) %>% str_detect(x, "pilon") ~ "pilon", str_detect(x, "longstitch") ~ "longstitch", str_detect(x, "links") ~ "LINKS", + str_detect(x, "yahs") ~ "HiC", str_detect(x, "assembl[ey]") ~ "Assembly", ), Percent_gaps = Percent_gaps %>% str_remove("%") %>% as.numeric) %>% diff --git a/assets/report/scripts/_busco_page.R b/assets/report/scripts/_busco_page.R index 60a486f1..0cd134d2 100644 --- a/assets/report/scripts/_busco_page.R +++ b/assets/report/scripts/_busco_page.R @@ -11,7 +11,7 @@ for (i in 1:length(unique(busco_reports$group))) { dplyr::select(sample, stage, Var, value) |> mutate(Var = str_replace_all(Var, "_", " ") |> str_replace_all("percent", "(%)")) |> pivot_wider(names_from = "Var", values_from = "value", id_cols = c(sample,stage)) |> - dplyr::arrange(factor(stage, levels = c("Assembly","medaka", "pilon", "dorado","links","longstitch","ragtag")), sample) |> + dplyr::arrange(factor(stage, levels = c("Assembly","medaka", "pilon", "dorado","hic","links","longstitch","ragtag")), sample) |> gt::gt() |> gt::fmt_auto() |> gt::opt_stylize(color = "gray") |> diff --git a/assets/report/scripts/_merqury.R b/assets/report/scripts/_merqury.R index b868fa40..62138dcc 100644 --- a/assets/report/scripts/_merqury.R +++ b/assets/report/scripts/_merqury.R @@ -13,6 +13,7 @@ merqury_stats <- list.files(paste0(data_base, "merqury"), full.names = T, patter str_detect(x, "_pilon") ~ "pilon", str_detect(x, "_longstitch") ~ "longstitch", str_detect(x, "_links") ~ "LINKS", + str_detect(x, "_yahs") ~ "HiC", str_detect(x, "assembl[ey]") ~ "Assembly", TRUE ~ "Unknown")) }) |> bind_rows() |> diff --git a/assets/report/scripts/_quast.R b/assets/report/scripts/_quast.R index 0538ff36..53f3f7c7 100644 --- a/assets/report/scripts/_quast.R +++ b/assets/report/scripts/_quast.R @@ -18,10 +18,11 @@ quast_stats <- list.files(paste0(data_base, "quast"), str_detect(x, "_pilon") ~ "pilon", str_detect(x, "_longstitch") ~ "longstitch", str_detect(x, "_links") ~ "LINKS", + str_detect(x, "_yahs") ~ "HiC", str_detect(x, "assembl[ey]") ~ "Assembly", TRUE ~ "Unknown")) }) |> left_join(groups, by = join_by(sample)) |> - mutate(stage = stage |> fct_relevel("Assembly", "medaka", "pilon", "longstitch", "LINKS", "RagTag") ) |> + mutate(stage = stage |> fct_relevel("Assembly", "medaka", "dorado", "pilon", "longstitch", "LINKS", "HiC", "RagTag") ) |> dplyr::arrange(sample, stage) # This creates code that will generate the length plot based on the contents of the quast report. diff --git a/conf/modules/QC/busco.config b/conf/modules/QC/busco.config index 0c0a907a..aa6bb06d 100644 --- a/conf/modules/QC/busco.config +++ b/conf/modules/QC/busco.config @@ -55,7 +55,7 @@ process { ] } withName: '.*:SCAFFOLD:.*HIC:QC.*:BUSCO' { - ext.prefix = { "${meta.id}_hic-${lineage}" } + ext.prefix = { "${meta.id}_yahs-${lineage}" } publishDir = [ path: { "${params.outdir}/${meta.id}/QC/BUSCO/" }, mode: params.publish_dir_mode, diff --git a/conf/modules/QC/merqury.config b/conf/modules/QC/merqury.config index 10d0ef8a..d4858230 100644 --- a/conf/modules/QC/merqury.config +++ b/conf/modules/QC/merqury.config @@ -40,7 +40,7 @@ process { ] } withName: '.*:SCAFFOLD:HIC:.*:MERQURY' { - ext.prefix = { "${meta.id}_hic" } + ext.prefix = { "${meta.id}_yahs" } publishDir = [ path: { "${params.outdir}/${meta.id}/QC/merqury/" }, mode: params.publish_dir_mode, diff --git a/conf/modules/QC/quast.config b/conf/modules/QC/quast.config index 9b91af3f..657f56f6 100644 --- a/conf/modules/QC/quast.config +++ b/conf/modules/QC/quast.config @@ -40,7 +40,7 @@ process { ] } withName: '.*SCAFFOLD:HIC.*:QUAST' { - ext.prefix = { "${meta.id}_hic" } + ext.prefix = { "${meta.id}_yahs" } publishDir = [ path: { "${params.outdir}/${meta.id}/QC/QUAST/" }, mode: params.publish_dir_mode, diff --git a/subworkflows/local/scaffolding/main.nf b/subworkflows/local/scaffolding/main.nf index 83d65440..8e20d273 100644 --- a/subworkflows/local/scaffolding/main.nf +++ b/subworkflows/local/scaffolding/main.nf @@ -172,7 +172,6 @@ workflow SCAFFOLD { RUN_LINKS.out.busco_out.set { links_busco } RUN_LINKS.out.quast_out.set { links_quast } RUN_LINKS.out.merqury_report_files.set { links_merqury } - ch_versions = ch_versions.mix(RUN_LINKS.out.versions) RUN_LONGSTITCH.out.busco_out.set { longstitch_busco } @@ -180,25 +179,32 @@ workflow SCAFFOLD { RUN_LONGSTITCH.out.merqury_report_files.set { longstitch_merqury } ch_versions = ch_versions.mix(RUN_LONGSTITCH.out.versions) + HIC.out.busco_out.set { hic_busco } + HIC.out.quast_out.set { hic_quast } + HIC.out.merqury_report_files.set { hic_merqury } + ch_versions = ch_versions.mix(HIC.out.versions) + RUN_RAGTAG.out.busco_out.set { ragtag_busco } RUN_RAGTAG.out.quast_out.set { ragtag_quast } RUN_RAGTAG.out.merqury_report_files.set { ragtag_merqury } - ch_versions = ch_versions.mix(RUN_RAGTAG.out.versions) links_busco .concat(longstitch_busco) .concat(ragtag_busco) + .concat(hic_busco) .set { scaffold_busco_reports } links_quast .concat(longstitch_quast) .concat(ragtag_quast) + .concat(hic_quast) .set { scaffold_quast_reports } links_merqury .concat(longstitch_merqury) .concat(ragtag_merqury) + .concat(hic_merqury) .set { scaffold_merqury_reports } versions = ch_versions From 3f06337fd8999e17af687f91a1d3cf25877a0b65 Mon Sep 17 00:00:00 2001 From: Niklas Schandry Date: Fri, 13 Feb 2026 10:14:57 +0100 Subject: [PATCH 121/162] update modules, handle topic --- modules.json | 39 +- modules/local/collect_reads/main.nf | 5 +- modules/nf-core/busco/busco/main.nf | 13 +- modules/nf-core/busco/busco/meta.yml | 57 ++- .../nf-core/busco/busco/tests/main.nf.test | 14 +- .../busco/busco/tests/main.nf.test.snap | 114 +++-- modules/nf-core/bwamem2/index/main.nf | 14 +- modules/nf-core/bwamem2/index/meta.yml | 43 +- .../nf-core/bwamem2/index/tests/main.nf.test | 12 +- .../bwamem2/index/tests/main.nf.test.snap | 128 ++++-- modules/nf-core/bwamem2/mem/main.nf | 14 +- modules/nf-core/bwamem2/mem/meta.yml | 45 +- .../nf-core/bwamem2/mem/tests/main.nf.test | 8 +- .../bwamem2/mem/tests/main.nf.test.snap | 92 ++-- modules/nf-core/fastp/main.nf | 40 +- modules/nf-core/fastp/meta.yml | 29 +- modules/nf-core/fastp/tests/main.nf.test | 18 +- modules/nf-core/fastp/tests/main.nf.test.snap | 402 ++++++++++++------ modules/nf-core/fastplong/main.nf | 13 +- modules/nf-core/fastplong/meta.yml | 34 +- modules/nf-core/fastplong/tests/main.nf.test | 2 +- .../nf-core/fastplong/tests/main.nf.test.snap | 38 +- modules/nf-core/fastqc/main.nf | 18 +- modules/nf-core/flye/main.nf | 12 +- modules/nf-core/flye/meta.yml | 32 +- modules/nf-core/flye/tests/main.nf.test | 10 +- modules/nf-core/flye/tests/main.nf.test.snap | 102 +++-- modules/nf-core/minimap2/align/main.nf | 36 +- modules/nf-core/minimap2/align/meta.yml | 26 +- .../nf-core/minimap2/align/tests/main.nf.test | 12 +- .../minimap2/align/tests/main.nf.test.snap | 208 ++++++--- modules/nf-core/samtools/faidx/main.nf | 3 +- modules/nf-core/samtools/faidx/meta.yml | 17 +- .../nf-core/samtools/faidx/tests/main.nf.test | 118 ++--- .../samtools/faidx/tests/main.nf.test.snap | 311 ++------------ modules/nf-core/samtools/fastq/main.nf | 12 +- modules/nf-core/samtools/fastq/meta.yml | 47 +- .../nf-core/samtools/fastq/tests/main.nf.test | 20 +- .../samtools/fastq/tests/main.nf.test.snap | 231 ++++------ modules/nf-core/samtools/flagstat/main.nf | 12 +- modules/nf-core/samtools/flagstat/meta.yml | 33 +- .../samtools/flagstat/tests/main.nf.test.snap | 40 +- modules/nf-core/samtools/idxstats/main.nf | 12 +- modules/nf-core/samtools/idxstats/meta.yml | 29 +- .../samtools/idxstats/tests/main.nf.test | 10 +- .../samtools/idxstats/tests/main.nf.test.snap | 80 ++-- modules/nf-core/samtools/index/main.nf | 12 +- modules/nf-core/samtools/index/meta.yml | 29 +- .../nf-core/samtools/index/tests/main.nf.test | 27 +- .../samtools/index/tests/main.nf.test.snap | 278 ++++-------- modules/nf-core/samtools/sort/main.nf | 10 +- modules/nf-core/samtools/sort/meta.yml | 29 +- .../nf-core/samtools/sort/tests/main.nf.test | 20 +- .../samtools/sort/tests/main.nf.test.snap | 349 +++++---------- modules/nf-core/samtools/stats/main.nf | 16 +- modules/nf-core/samtools/stats/meta.yml | 28 +- .../samtools/stats/tests/main.nf.test.snap | 80 ++-- modules/nf-core/yahs/main.nf | 12 +- modules/nf-core/yahs/meta.yml | 49 ++- modules/nf-core/yahs/tests/main.nf.test | 4 +- modules/nf-core/yahs/tests/main.nf.test.snap | 50 ++- subworkflows/local/assemble/main.nf | 2 - subworkflows/local/liftoff/main.nf | 1 + subworkflows/local/mapping/map_sr/main.nf | 6 - .../local/mapping/map_to_assembly/main.nf | 7 +- subworkflows/local/mapping/map_to_ref/main.nf | 5 - .../polishing/pilon/polish_pilon/main.nf | 2 - subworkflows/local/prepare/main.nf | 2 - .../local/prepare/prepare_hifi/main.nf | 3 - .../local/prepare/prepare_ont/main.nf | 2 +- .../local/prepare/prepare_shortreads/main.nf | 4 +- subworkflows/local/qc/busco/main.nf | 3 - subworkflows/local/qc/main.nf | 5 - subworkflows/local/scaffolding/hic/main.nf | 16 +- .../nf-core/bam_sort_stats_samtools/main.nf | 8 - .../tests/main.nf.test | 6 +- .../tests/main.nf.test.snap | 74 +--- .../nf-core/bam_stats_samtools/main.nf | 7 - .../bam_stats_samtools/tests/main.nf.test | 9 +- .../tests/main.nf.test.snap | 81 +--- workflows/genomeassembler.nf | 48 +-- 81 files changed, 1856 insertions(+), 2033 deletions(-) diff --git a/modules.json b/modules.json index 27a0d25c..cbc42265 100644 --- a/modules.json +++ b/modules.json @@ -7,38 +7,33 @@ "nf-core": { "busco/busco": { "branch": "master", - "git_sha": "e753770db613ce014b3c4bc94f6cba443427b726", + "git_sha": "56925dff3062ca9387de65dd763568916b562124", "installed_by": ["modules"] }, "bwamem2/index": { "branch": "master", - "git_sha": "d86336f3e7ae0d5f76c67b0859409769cfeb2af2", + "git_sha": "5dd46a36fca68d6ad1a6b22ec47adc8c6863717d", "installed_by": ["modules"] }, "bwamem2/mem": { "branch": "master", - "git_sha": "d86336f3e7ae0d5f76c67b0859409769cfeb2af2", + "git_sha": "5dd46a36fca68d6ad1a6b22ec47adc8c6863717d", "installed_by": ["modules"] }, "fastp": { "branch": "master", - "git_sha": "d9ec4ef289ad39b8a662a7a12be50409b11df84b", + "git_sha": "a331ecfd1aa48b2b2298aab23bb4516c800e410b", "installed_by": ["modules"] }, "fastplong": { "branch": "master", - "git_sha": "e753770db613ce014b3c4bc94f6cba443427b726", + "git_sha": "a331ecfd1aa48b2b2298aab23bb4516c800e410b", "installed_by": ["modules"], "patch": "modules/nf-core/fastplong/fastplong.diff" }, - "fastqc": { - "branch": "master", - "git_sha": "41dfa3f7c0ffabb96a6a813fe321c6d1cc5b6e46", - "installed_by": ["modules"] - }, "flye": { "branch": "master", - "git_sha": "41dfa3f7c0ffabb96a6a813fe321c6d1cc5b6e46", + "git_sha": "1ab453187e9eada07bdd98609ce770971bfe86e4", "installed_by": ["modules"] }, "hifiasm": { @@ -76,7 +71,7 @@ }, "minimap2/align": { "branch": "master", - "git_sha": "e753770db613ce014b3c4bc94f6cba443427b726", + "git_sha": "5c9f8d5b7671237c906abadc9ff732b301ca15ca", "installed_by": ["modules"], "patch": "modules/nf-core/minimap2/align/minimap2-align.diff" }, @@ -107,42 +102,42 @@ }, "samtools/faidx": { "branch": "master", - "git_sha": "9a48bce39a67e2cb34b8f125fc1d50f0ad98b616", + "git_sha": "b2e78932ef01165fd85829513eaca29eff8e640a", "installed_by": ["modules"] }, "samtools/fastq": { "branch": "master", - "git_sha": "c8be52dba1166c678e74cda9c3a3c221635c8bb1", + "git_sha": "1d2fbdcbca677bbe8da0f9d0d2bb7c02f2cab1c9", "installed_by": ["modules"] }, "samtools/flagstat": { "branch": "master", - "git_sha": "e334e12a1e985adc5ffc3fc78a68be1de711de45", + "git_sha": "1d2fbdcbca677bbe8da0f9d0d2bb7c02f2cab1c9", "installed_by": ["bam_stats_samtools", "modules"] }, "samtools/idxstats": { "branch": "master", - "git_sha": "c8be52dba1166c678e74cda9c3a3c221635c8bb1", + "git_sha": "1d2fbdcbca677bbe8da0f9d0d2bb7c02f2cab1c9", "installed_by": ["bam_stats_samtools", "modules"] }, "samtools/index": { "branch": "master", - "git_sha": "c8be52dba1166c678e74cda9c3a3c221635c8bb1", + "git_sha": "1d2fbdcbca677bbe8da0f9d0d2bb7c02f2cab1c9", "installed_by": ["bam_sort_stats_samtools", "modules"] }, "samtools/sort": { "branch": "master", - "git_sha": "e753770db613ce014b3c4bc94f6cba443427b726", + "git_sha": "5cb9a8694da0a0e550921636bb60bc8c56445fd7", "installed_by": ["bam_sort_stats_samtools", "modules"] }, "samtools/stats": { "branch": "master", - "git_sha": "c8be52dba1166c678e74cda9c3a3c221635c8bb1", + "git_sha": "fe93fde0845f907fc91ad7cc7d797930408824df", "installed_by": ["bam_stats_samtools", "modules"] }, "yahs": { "branch": "master", - "git_sha": "5c3c4f4753d55bd3ba93ce29ed1100e964c52f74", + "git_sha": "b9837690be3f0296341829f656347d4662891daa", "installed_by": ["modules"] } } @@ -151,12 +146,12 @@ "nf-core": { "bam_sort_stats_samtools": { "branch": "master", - "git_sha": "c8be52dba1166c678e74cda9c3a3c221635c8bb1", + "git_sha": "7ac6cbe7c17c2dad685da7f70496c8f48ea48687", "installed_by": ["subworkflows"] }, "bam_stats_samtools": { "branch": "master", - "git_sha": "e334e12a1e985adc5ffc3fc78a68be1de711de45", + "git_sha": "7ac6cbe7c17c2dad685da7f70496c8f48ea48687", "installed_by": ["bam_sort_stats_samtools", "subworkflows"] }, "utils_nextflow_pipeline": { diff --git a/modules/local/collect_reads/main.nf b/modules/local/collect_reads/main.nf index 4dbdc87a..a222532f 100644 --- a/modules/local/collect_reads/main.nf +++ b/modules/local/collect_reads/main.nf @@ -12,6 +12,8 @@ process COLLECT_READS { output: tuple val(meta), path("*_all_reads.fq.gz"), emit: combined_reads + tuple val("${task.process}"), val('gzip'), eval('gzip --version | head -n1 | sed "s/gzip //"'), emit: versions_collect_reads, topic: versions + path "versions.yml", emit: versions script: @@ -19,9 +21,6 @@ process COLLECT_READS { """ cat ${reads} > ${prefix}_all_reads.fq.gz - cat <<-END_VERSIONS > versions.yml - "${task.process}": - gzip: \$(echo \$(gzip --version | head -n1 | sed 's/gzip //')) END_VERSIONS """ diff --git a/modules/nf-core/busco/busco/main.nf b/modules/nf-core/busco/busco/main.nf index ee4cb266..aab5920e 100644 --- a/modules/nf-core/busco/busco/main.nf +++ b/modules/nf-core/busco/busco/main.nf @@ -35,8 +35,7 @@ process BUSCO_BUSCO { tuple val(meta), path("busco_downloads/lineages/*"), emit: downloaded_lineages, optional: true tuple val(meta), path("*-busco/*/run_*/busco_sequences/single_copy_busco_sequences/*.faa"), emit: single_copy_faa, optional: true tuple val(meta), path("*-busco/*/run_*/busco_sequences/single_copy_busco_sequences/*.fna"), emit: single_copy_fna, optional: true - - path "versions.yml", emit: versions + tuple val("${task.process}"), val('busco'), eval("busco --version 2> /dev/null | sed 's/BUSCO //g'"), emit: versions_busco, topic: versions when: task.ext.when == null || task.ext.when @@ -115,11 +114,6 @@ process BUSCO_BUSCO { echo "Busco run failed" exit 1 fi - - cat <<-END_VERSIONS > versions.yml - "${task.process}": - busco: \$( busco --version 2> /dev/null | sed 's/BUSCO //g' ) - END_VERSIONS """ stub: @@ -128,10 +122,5 @@ process BUSCO_BUSCO { """ touch ${prefix}-busco.batch_summary.txt mkdir -p ${prefix}-busco/${fasta_name}/run_${lineage}/busco_sequences - - cat <<-END_VERSIONS > versions.yml - "${task.process}": - busco: \$( busco --version 2> /dev/null | sed 's/BUSCO //g' ) - END_VERSIONS """ } diff --git a/modules/nf-core/busco/busco/meta.yml b/modules/nf-core/busco/busco/meta.yml index 281e3db0..0ec0e158 100644 --- a/modules/nf-core/busco/busco/meta.yml +++ b/modules/nf-core/busco/busco/meta.yml @@ -7,14 +7,16 @@ keywords: - proteome tools: - busco: - description: BUSCO provides measures for quantitative assessment of genome assembly, - gene set, and transcriptome completeness based on evolutionarily informed expectations - of gene content from near-universal single-copy orthologs selected from OrthoDB. + description: BUSCO provides measures for quantitative assessment of genome + assembly, gene set, and transcriptome completeness based on evolutionarily + informed expectations of gene content from near-universal single-copy + orthologs selected from OrthoDB. homepage: https://busco.ezlab.org/ documentation: https://busco.ezlab.org/busco_userguide.html tool_dev_url: https://gitlab.com/ezlab/busco doi: "10.1007/978-1-4939-9173-0_14" - licence: ["MIT"] + licence: + - "MIT" identifier: biotools:busco input: - - meta: @@ -29,12 +31,13 @@ input: ontologies: [] - mode: type: string - description: The mode to run Busco in. One of genome, proteins, or transcriptome + description: The mode to run Busco in. One of genome, proteins, or + transcriptome pattern: "{genome,proteins,transcriptome}" - lineage: type: string - description: The BUSCO lineage to use, or "auto", "auto_prok" or "auto_euk" to - automatically select lineage + description: The BUSCO lineage to use, or "auto", "auto_prok" or "auto_euk" + to automatically select lineage - busco_lineages_path: type: directory description: Path to local BUSCO lineages directory. @@ -79,7 +82,7 @@ output: description: Short Busco summary in JSON format pattern: "short_summary.*.json" ontologies: - - edam: http://edamontology.org/format_3464 # JSON + - edam: http://edamontology.org/format_3464 log: - - meta: type: map @@ -102,7 +105,7 @@ output: description: Full BUSCO results table pattern: "full_table.tsv" ontologies: - - edam: http://edamontology.org/format_3475 # TSV + - edam: http://edamontology.org/format_3475 missing_busco_list: - - meta: type: map @@ -114,7 +117,7 @@ output: description: List of missing BUSCOs pattern: "missing_busco_list.tsv" ontologies: - - edam: http://edamontology.org/format_3475 # TSV + - edam: http://edamontology.org/format_3475 single_copy_proteins: - - meta: type: map @@ -144,8 +147,8 @@ output: e.g. [ id:'test' ] - "*-busco/*/translated_proteins": type: directory - description: Six frame translations of each transcript made by the transcriptome - mode + description: Six frame translations of each transcript made by the + transcriptome mode pattern: "translated_dir" busco_dir: - - meta: @@ -165,8 +168,8 @@ output: e.g. [ id:'test' ] - busco_downloads/lineages/*: type: directory - description: Lineages downloaded by BUSCO when running the analysis, for example - bacteria_odb12 + description: Lineages downloaded by BUSCO when running the analysis, for + example bacteria_odb12 pattern: "busco_downloads/lineages/*" single_copy_faa: - - meta: @@ -190,13 +193,27 @@ output: description: Single copy .fna sequence files pattern: "*-busco/*/run_*/busco_sequences/single_copy_busco_sequences/*.fna" ontologies: [] + versions_busco: + - - ${task.process}: + type: string + description: The name of the process + - busco: + type: string + description: The name of the tool + - busco --version 2> /dev/null | sed 's/BUSCO //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: "versions.yml" - ontologies: - - edam: http://edamontology.org/format_3750 # YAML + - - ${task.process}: + type: string + description: The name of the process + - busco: + type: string + description: The name of the tool + - busco --version 2> /dev/null | sed 's/BUSCO //g': + type: eval + description: The expression to obtain the version of the tool authors: - "@priyanka-surana" - "@charles-plessy" diff --git a/modules/nf-core/busco/busco/tests/main.nf.test b/modules/nf-core/busco/busco/tests/main.nf.test index 5610ed93..3f1ad0d0 100644 --- a/modules/nf-core/busco/busco/tests/main.nf.test +++ b/modules/nf-core/busco/busco/tests/main.nf.test @@ -54,7 +54,7 @@ nextflow_process { process.out.batch_summary[0][1], process.out.full_table[0][1], process.out.missing_busco_list[0][1], - process.out.versions[0] + process.out.findAll { key, val -> key.startsWith("versions")} ).match() with(file(process.out.seq_dir[0][1]).listFiles().collect { it.name }) { @@ -136,7 +136,7 @@ nextflow_process { assert snapshot( process.out.full_table[0][1], process.out.missing_busco_list[0][1], - process.out.versions[0] + process.out.findAll { key, val -> key.startsWith("versions")} ).match() with(file(process.out.seq_dir[0][1][0]).listFiles().collect { it.name }) { @@ -192,7 +192,7 @@ nextflow_process { assert snapshot( process.out.batch_summary[0][1], - process.out.versions[0] + process.out.findAll { key, val -> key.startsWith("versions")} ).match() with(path(process.out.log[0][1]).text) { @@ -256,7 +256,7 @@ nextflow_process { process.out.batch_summary[0][1], process.out.full_table[0][1], process.out.missing_busco_list[0][1], - process.out.versions[0] + process.out.findAll { key, val -> key.startsWith("versions")} ).match() with(file(process.out.seq_dir[0][1]).listFiles().collect { it.name }) { @@ -324,7 +324,7 @@ nextflow_process { process.out.missing_busco_list[0][1], process.out.translated_dir[0][1], process.out.single_copy_proteins[0][1], - process.out.versions[0] + process.out.findAll { key, val -> key.startsWith("versions")} ).match() with(file(process.out.seq_dir[0][1]).listFiles().collect { it.name }) { @@ -372,7 +372,7 @@ nextflow_process { process.out.batch_summary[0][1], process.out.full_table[0][1], process.out.missing_busco_list[0][1], - process.out.versions[0] + process.out.findAll { key, val -> key.startsWith("versions")} ).match() with(path(process.out.log[0][1]).text) { @@ -414,7 +414,7 @@ nextflow_process { { assert process.success }, { assert snapshot( process.out.batch_summary, - process.out.versions + process.out.findAll { key, val -> key.startsWith("versions")} ).match() } ) } diff --git a/modules/nf-core/busco/busco/tests/main.nf.test.snap b/modules/nf-core/busco/busco/tests/main.nf.test.snap index 5de40123..88d87bf9 100644 --- a/modules/nf-core/busco/busco/tests/main.nf.test.snap +++ b/modules/nf-core/busco/busco/tests/main.nf.test.snap @@ -9,39 +9,61 @@ "test-bacteria_odb10-busco.batch_summary.txt:md5,d41d8cd98f00b204e9800998ecf8427e" ] ], - [ - "versions.yml:md5,d3cecb346ce389a471bd041a53617d05" - ] + { + "versions_busco": [ + [ + "BUSCO_BUSCO", + "busco", + "6.0.0" + ] + ] + } ], "meta": { - "nf-test": "0.9.2", - "nextflow": "25.04.6" + "nf-test": "0.9.3", + "nextflow": "25.10.3" }, - "timestamp": "2025-07-21T16:11:16.371060201" + "timestamp": "2026-02-10T14:56:39.925901" }, "test_busco_eukaryote_augustus": { "content": [ "test-eukaryota_odb10-busco.batch_summary.txt:md5,3ea3bdc423a461dae514d816bdc61c89", - "versions.yml:md5,d3cecb346ce389a471bd041a53617d05" + { + "versions_busco": [ + [ + "BUSCO_BUSCO", + "busco", + "6.0.0" + ] + ] + } ], "meta": { - "nf-test": "0.9.2", - "nextflow": "25.04.6" + "nf-test": "0.9.3", + "nextflow": "25.10.3" }, - "timestamp": "2025-07-21T16:09:47.906365972" + "timestamp": "2026-02-10T14:55:25.677229" }, "test_busco_genome_single_fasta": { "content": [ "test-bacteria_odb10-busco.batch_summary.txt:md5,12e911830d66bab6dbf3523ac4392597", "full_table.tsv:md5,660e2f556ca6efa97f0c2a8cebd94786", "missing_busco_list.tsv:md5,0e08587f4dc65d9226a31433c1f9ba25", - "versions.yml:md5,d3cecb346ce389a471bd041a53617d05" + { + "versions_busco": [ + [ + "BUSCO_BUSCO", + "busco", + "6.0.0" + ] + ] + } ], "meta": { - "nf-test": "0.9.2", - "nextflow": "25.04.6" + "nf-test": "0.9.3", + "nextflow": "25.10.3" }, - "timestamp": "2025-07-21T16:08:41.497678114" + "timestamp": "2026-02-10T14:54:16.672124" }, "test_busco_genome_multi_fasta": { "content": [ @@ -53,26 +75,42 @@ "missing_busco_list.tsv:md5,5dcdc7707035904a7d467ca1026b399a", "missing_busco_list.tsv:md5,0e08587f4dc65d9226a31433c1f9ba25" ], - "versions.yml:md5,d3cecb346ce389a471bd041a53617d05" + { + "versions_busco": [ + [ + "BUSCO_BUSCO", + "busco", + "6.0.0" + ] + ] + } ], "meta": { - "nf-test": "0.9.2", - "nextflow": "25.04.6" + "nf-test": "0.9.3", + "nextflow": "25.10.3" }, - "timestamp": "2025-07-21T16:09:25.578789984" + "timestamp": "2026-02-10T14:55:02.956339" }, "test_busco_cleanup": { "content": [ "test-bacteria_odb10-busco.batch_summary.txt:md5,12e911830d66bab6dbf3523ac4392597", "full_table.tsv:md5,660e2f556ca6efa97f0c2a8cebd94786", "missing_busco_list.tsv:md5,0e08587f4dc65d9226a31433c1f9ba25", - "versions.yml:md5,d3cecb346ce389a471bd041a53617d05" + { + "versions_busco": [ + [ + "BUSCO_BUSCO", + "busco", + "6.0.0" + ] + ] + } ], "meta": { - "nf-test": "0.9.2", - "nextflow": "25.04.6" + "nf-test": "0.9.3", + "nextflow": "25.10.3" }, - "timestamp": "2025-07-21T16:11:08.495786376" + "timestamp": "2026-02-10T14:56:34.94761" }, "test_busco_transcriptome": { "content": [ @@ -104,25 +142,41 @@ "95696at2.faa:md5,247bfd1aef432f7b5456307768e9149c" ], "single_copy_proteins.faa:md5,73e2c5d6a9b0f01f2deea3cc5f21b764", - "versions.yml:md5,d3cecb346ce389a471bd041a53617d05" + { + "versions_busco": [ + [ + "BUSCO_BUSCO", + "busco", + "6.0.0" + ] + ] + } ], "meta": { - "nf-test": "0.9.2", - "nextflow": "25.04.6" + "nf-test": "0.9.3", + "nextflow": "25.10.3" }, - "timestamp": "2025-07-21T16:10:28.783205973" + "timestamp": "2026-02-10T14:55:59.115826" }, "test_busco_protein": { "content": [ "test-bacteria_odb10-busco.batch_summary.txt:md5,942dbb2d8ff26240860a794213db14a8", "full_table.tsv:md5,4db33686f2755a09fdc9521ca89411bc", "missing_busco_list.tsv:md5,5dcdc7707035904a7d467ca1026b399a", - "versions.yml:md5,d3cecb346ce389a471bd041a53617d05" + { + "versions_busco": [ + [ + "BUSCO_BUSCO", + "busco", + "6.0.0" + ] + ] + } ], "meta": { - "nf-test": "0.9.2", - "nextflow": "25.04.6" + "nf-test": "0.9.3", + "nextflow": "25.10.3" }, - "timestamp": "2025-07-21T16:10:05.674445797" + "timestamp": "2026-02-10T14:55:41.794334" } } \ No newline at end of file diff --git a/modules/nf-core/bwamem2/index/main.nf b/modules/nf-core/bwamem2/index/main.nf index 62af1e39..cb2c4bb2 100644 --- a/modules/nf-core/bwamem2/index/main.nf +++ b/modules/nf-core/bwamem2/index/main.nf @@ -2,7 +2,7 @@ process BWAMEM2_INDEX { tag "$fasta" // NOTE Requires 28N GB memory where N is the size of the reference sequence, floor of 280M // source: https://github.com/bwa-mem2/bwa-mem2/issues/9 - memory { (280.MB * Math.ceil(fasta.size() / 10000000)) * task.attempt } + memory { 280.MB * Math.ceil(fasta.size() / 10000000) * task.attempt } conda "${moduleDir}/environment.yml" container "${ workflow.containerEngine == 'singularity' && !task.ext.singularity_pull_docker_container ? @@ -14,7 +14,7 @@ process BWAMEM2_INDEX { output: tuple val(meta), path("bwamem2"), emit: index - path "versions.yml" , emit: versions + tuple val("${task.process}"), val('bwamem2'), eval('bwa-mem2 version | grep -o -E "[0-9]+(\\.[0-9]+)+"'), emit: versions_bwamem2, topic: versions when: task.ext.when == null || task.ext.when @@ -29,11 +29,6 @@ process BWAMEM2_INDEX { $args \\ -p bwamem2/${prefix} \\ $fasta - - cat <<-END_VERSIONS > versions.yml - "${task.process}": - bwamem2: \$(echo \$(bwa-mem2 version 2>&1) | sed 's/.* //') - END_VERSIONS """ stub: @@ -46,10 +41,5 @@ process BWAMEM2_INDEX { touch bwamem2/${prefix}.pac touch bwamem2/${prefix}.amb touch bwamem2/${prefix}.bwt.2bit.64 - - cat <<-END_VERSIONS > versions.yml - "${task.process}": - bwamem2: \$(echo \$(bwa-mem2 version 2>&1) | sed 's/.* //') - END_VERSIONS """ } diff --git a/modules/nf-core/bwamem2/index/meta.yml b/modules/nf-core/bwamem2/index/meta.yml index b2aa45fb..12074860 100644 --- a/modules/nf-core/bwamem2/index/meta.yml +++ b/modules/nf-core/bwamem2/index/meta.yml @@ -12,7 +12,8 @@ tools: a large reference genome, such as the human genome. homepage: https://github.com/bwa-mem2/bwa-mem2 documentation: https://github.com/bwa-mem2/bwa-mem2#usage - licence: ["MIT"] + licence: + - "MIT" identifier: "biotools:bwa-mem2" input: - - meta: @@ -24,8 +25,8 @@ input: type: file description: Input genome fasta file ontologies: - - edam: "http://edamontology.org/data_2044" # Sequence - - edam: "http://edamontology.org/format_1929" # FASTA + - edam: "http://edamontology.org/data_2044" + - edam: "http://edamontology.org/format_1929" output: index: - - meta: @@ -34,18 +35,38 @@ output: Groovy Map containing sample information e.g. [ id:'test', single_end:false ] - bwamem2: - type: file + type: string description: BWA genome index files pattern: "*.{0123,amb,ann,bwt.2bit.64,pac}" ontologies: - - edam: "http://edamontology.org/data_3210" # Genome index + - edam: "http://edamontology.org/data_3210" + versions_bwamem2: + - - ${task.process}: + type: string + description: The name of the process + - bwamem2: + type: string + description: BWA genome index files + pattern: "*.{0123,amb,ann,bwt.2bit.64,pac}" + ontologies: + - edam: "http://edamontology.org/data_3210" + - bwa-mem2 version | grep -o -E "[0-9]+(\.[0-9]+)+": + type: eval + description: The expression to obtain the version of the tool +topics: 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 name of the process + - bwamem2: + type: string + description: BWA genome index files + pattern: "*.{0123,amb,ann,bwt.2bit.64,pac}" + ontologies: + - edam: "http://edamontology.org/data_3210" + - bwa-mem2 version | grep -o -E "[0-9]+(\.[0-9]+)+": + type: eval + description: The expression to obtain the version of the tool authors: - "@maxulysse" maintainers: diff --git a/modules/nf-core/bwamem2/index/tests/main.nf.test b/modules/nf-core/bwamem2/index/tests/main.nf.test index adf44785..3ee91048 100644 --- a/modules/nf-core/bwamem2/index/tests/main.nf.test +++ b/modules/nf-core/bwamem2/index/tests/main.nf.test @@ -24,11 +24,7 @@ nextflow_process { then { assertAll( { assert process.success }, - { assert snapshot( - process.out.index, - process.out.versions, - path(process.out.versions[0]).yaml - ).match() } + { assert snapshot(process.out).match() } ) } } @@ -51,11 +47,7 @@ nextflow_process { then { assertAll( { assert process.success }, - { assert snapshot( - process.out.index, - process.out.versions, - path(process.out.versions[0]).yaml - ).match() } + { assert snapshot(process.out).match() } ) } } diff --git a/modules/nf-core/bwamem2/index/tests/main.nf.test.snap b/modules/nf-core/bwamem2/index/tests/main.nf.test.snap index 9ad8b20c..776e87be 100644 --- a/modules/nf-core/bwamem2/index/tests/main.nf.test.snap +++ b/modules/nf-core/bwamem2/index/tests/main.nf.test.snap @@ -1,64 +1,108 @@ { "fasta - stub": { "content": [ - [ - [ - { - "id": "test" - }, + { + "0": [ + [ + { + "id": "test" + }, + [ + "genome.fasta.0123:md5,d41d8cd98f00b204e9800998ecf8427e", + "genome.fasta.amb:md5,d41d8cd98f00b204e9800998ecf8427e", + "genome.fasta.ann:md5,d41d8cd98f00b204e9800998ecf8427e", + "genome.fasta.bwt.2bit.64:md5,d41d8cd98f00b204e9800998ecf8427e", + "genome.fasta.pac:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ] + ], + "1": [ + [ + "BWAMEM2_INDEX", + "bwamem2", + "2.2.1" + ] + ], + "index": [ + [ + { + "id": "test" + }, + [ + "genome.fasta.0123:md5,d41d8cd98f00b204e9800998ecf8427e", + "genome.fasta.amb:md5,d41d8cd98f00b204e9800998ecf8427e", + "genome.fasta.ann:md5,d41d8cd98f00b204e9800998ecf8427e", + "genome.fasta.bwt.2bit.64:md5,d41d8cd98f00b204e9800998ecf8427e", + "genome.fasta.pac:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ] + ], + "versions_bwamem2": [ [ - "genome.fasta.0123:md5,d41d8cd98f00b204e9800998ecf8427e", - "genome.fasta.amb:md5,d41d8cd98f00b204e9800998ecf8427e", - "genome.fasta.ann:md5,d41d8cd98f00b204e9800998ecf8427e", - "genome.fasta.bwt.2bit.64:md5,d41d8cd98f00b204e9800998ecf8427e", - "genome.fasta.pac:md5,d41d8cd98f00b204e9800998ecf8427e" + "BWAMEM2_INDEX", + "bwamem2", + "2.2.1" ] ] - ], - [ - "versions.yml:md5,9ffd13d12e7108ed15c58566bc4717d6" - ], - { - "BWAMEM2_INDEX": { - "bwamem2": "2.2.1" - } } ], "meta": { - "nf-test": "0.9.2", - "nextflow": "25.04.6" + "nf-test": "0.9.3", + "nextflow": "25.10.3" }, - "timestamp": "2025-09-04T08:55:53.219699135" + "timestamp": "2026-02-09T16:19:38.013344" }, "fasta": { "content": [ - [ - [ - { - "id": "test" - }, + { + "0": [ + [ + { + "id": "test" + }, + [ + "genome.fasta.0123:md5,b02870de80106104abcb03cd9463e7d8", + "genome.fasta.amb:md5,3a68b8b2287e07dd3f5f95f4344ba76e", + "genome.fasta.ann:md5,c32e11f6c859f166c7525a9c1d583567", + "genome.fasta.bwt.2bit.64:md5,d097a1b82dee375d41a1ea69895a9216", + "genome.fasta.pac:md5,983e3d2cd6f36e2546e6d25a0da78d66" + ] + ] + ], + "1": [ + [ + "BWAMEM2_INDEX", + "bwamem2", + "2.2.1" + ] + ], + "index": [ + [ + { + "id": "test" + }, + [ + "genome.fasta.0123:md5,b02870de80106104abcb03cd9463e7d8", + "genome.fasta.amb:md5,3a68b8b2287e07dd3f5f95f4344ba76e", + "genome.fasta.ann:md5,c32e11f6c859f166c7525a9c1d583567", + "genome.fasta.bwt.2bit.64:md5,d097a1b82dee375d41a1ea69895a9216", + "genome.fasta.pac:md5,983e3d2cd6f36e2546e6d25a0da78d66" + ] + ] + ], + "versions_bwamem2": [ [ - "genome.fasta.0123:md5,b02870de80106104abcb03cd9463e7d8", - "genome.fasta.amb:md5,3a68b8b2287e07dd3f5f95f4344ba76e", - "genome.fasta.ann:md5,c32e11f6c859f166c7525a9c1d583567", - "genome.fasta.bwt.2bit.64:md5,d097a1b82dee375d41a1ea69895a9216", - "genome.fasta.pac:md5,983e3d2cd6f36e2546e6d25a0da78d66" + "BWAMEM2_INDEX", + "bwamem2", + "2.2.1" ] ] - ], - [ - "versions.yml:md5,9ffd13d12e7108ed15c58566bc4717d6" - ], - { - "BWAMEM2_INDEX": { - "bwamem2": "2.2.1" - } } ], "meta": { - "nf-test": "0.9.2", - "nextflow": "25.04.6" + "nf-test": "0.9.3", + "nextflow": "25.10.3" }, - "timestamp": "2025-09-04T08:55:45.007921901" + "timestamp": "2026-02-09T16:19:32.542622" } } \ No newline at end of file diff --git a/modules/nf-core/bwamem2/mem/main.nf b/modules/nf-core/bwamem2/mem/main.nf index 27910cf6..d1c0ac8f 100644 --- a/modules/nf-core/bwamem2/mem/main.nf +++ b/modules/nf-core/bwamem2/mem/main.nf @@ -19,7 +19,7 @@ process BWAMEM2_MEM { 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("${task.process}"), val('bwamem2'), eval('bwa-mem2 version | grep -o -E "[0-9]+(\\.[0-9]+)+"'), emit: versions_bwamem2, topic: versions when: task.ext.when == null || task.ext.when @@ -46,12 +46,6 @@ process BWAMEM2_MEM { \$INDEX \\ $reads \\ | samtools $samtools_command $args2 -@ $task.cpus ${reference} -o ${prefix}.${extension} - - - cat <<-END_VERSIONS > versions.yml - "${task.process}": - bwamem2: \$(echo \$(bwa-mem2 version 2>&1) | sed 's/.* //') - samtools: \$(echo \$(samtools --version 2>&1) | sed 's/^.*samtools //; s/Using.*\$//') - END_VERSIONS """ stub: @@ -73,11 +67,5 @@ process BWAMEM2_MEM { """ touch ${prefix}.${extension} ${create_index} - - cat <<-END_VERSIONS > versions.yml - "${task.process}": - bwamem2: \$(echo \$(bwa-mem2 version 2>&1) | sed 's/.* //') - samtools: \$(echo \$(samtools --version 2>&1) | sed 's/^.*samtools //; s/Using.*\$//') - END_VERSIONS """ } diff --git a/modules/nf-core/bwamem2/mem/meta.yml b/modules/nf-core/bwamem2/mem/meta.yml index 6c7d1728..bcfd006d 100644 --- a/modules/nf-core/bwamem2/mem/meta.yml +++ b/modules/nf-core/bwamem2/mem/meta.yml @@ -16,7 +16,8 @@ tools: homepage: https://github.com/bwa-mem2/bwa-mem2 documentation: http://www.htslib.org/doc/samtools.html arxiv: arXiv:1303.3997 - licence: ["MIT"] + licence: + - "MIT" identifier: "biotools:bwa-mem2" input: - - meta: @@ -30,8 +31,8 @@ input: List of input FastQ files of size 1 and 2 for single-end and paired-end data, respectively. ontologies: - - edam: "http://edamontology.org/data_2044" # Sequence - - edam: "http://edamontology.org/format_1930" # FASTQ + - edam: "http://edamontology.org/data_2044" + - edam: "http://edamontology.org/format_1930" - - meta2: type: map description: | @@ -42,7 +43,7 @@ input: description: BWA genome index files pattern: "Directory containing BWA index *.{0132,amb,ann,bwt.2bit.64,pac}" ontologies: - - edam: "http://edamontology.org/data_3210" # Genome index + - edam: "http://edamontology.org/data_3210" - - meta3: type: map description: | @@ -53,8 +54,8 @@ input: description: Reference genome in FASTA format pattern: "*.{fa,fasta,fna}" ontologies: - - edam: "http://edamontology.org/data_2044" # Sequence - - edam: "http://edamontology.org/format_1929" # FASTA + - edam: "http://edamontology.org/data_2044" + - edam: "http://edamontology.org/format_1929" - sort_bam: type: boolean description: use samtools sort (true) or samtools view (false) @@ -71,7 +72,7 @@ output: description: Output SAM file containing read alignments pattern: "*.{sam}" ontologies: - - edam: "http://edamontology.org/format_2573" # SAM + - edam: "http://edamontology.org/format_2573" bam: - - meta: type: map @@ -83,7 +84,7 @@ output: description: Output BAM file containing read alignments pattern: "*.{bam}" ontologies: - - edam: "http://edamontology.org/format_2572" # BAM + - edam: "http://edamontology.org/format_2572" cram: - - meta: type: map @@ -95,7 +96,7 @@ output: description: Output CRAM file containing read alignments pattern: "*.{cram}" ontologies: - - edam: "http://edamontology.org/format_3462" # CRAM + - edam: "http://edamontology.org/format_3462" crai: - - meta: type: map @@ -118,13 +119,27 @@ output: description: Index file for BAM file pattern: "*.{csi}" ontologies: [] + versions_bwamem2: + - - ${task.process}: + type: string + description: The name of the process + - bwamem2: + type: string + description: The name of the tool + - bwa-mem2 version | grep -o -E "[0-9]+(\.[0-9]+)+": + type: eval + description: The expression to obtain the version of the tool +topics: 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 name of the process + - bwamem2: + type: string + description: The name of the tool + - bwa-mem2 version | grep -o -E "[0-9]+(\.[0-9]+)+": + type: eval + description: The expression to obtain the version of the tool authors: - "@maxulysse" - "@matthdsm" diff --git a/modules/nf-core/bwamem2/mem/tests/main.nf.test b/modules/nf-core/bwamem2/mem/tests/main.nf.test index 9e0ab14a..20e37254 100644 --- a/modules/nf-core/bwamem2/mem/tests/main.nf.test +++ b/modules/nf-core/bwamem2/mem/tests/main.nf.test @@ -46,7 +46,7 @@ nextflow_process { { assert snapshot( bam(process.out.bam[0][1]).getHeaderMD5(), bam(process.out.bam[0][1]).getReadsMD5(), - process.out.versions + process.out.findAll { key, val -> key.startsWith("versions")} ).match() } ) } @@ -75,7 +75,7 @@ nextflow_process { { assert snapshot( bam(process.out.bam[0][1]).getHeaderMD5(), bam(process.out.bam[0][1]).getReadsMD5(), - process.out.versions + process.out.findAll { key, val -> key.startsWith("versions")} ).match() } ) } @@ -107,7 +107,7 @@ nextflow_process { { assert snapshot( bam(process.out.bam[0][1]).getHeaderMD5(), bam(process.out.bam[0][1]).getReadsMD5(), - process.out.versions + process.out.findAll { key, val -> key.startsWith("versions")} ).match() } ) } @@ -139,7 +139,7 @@ nextflow_process { { assert snapshot( bam(process.out.bam[0][1]).getHeaderMD5(), bam(process.out.bam[0][1]).getReadsMD5(), - process.out.versions + process.out.findAll { key, val -> key.startsWith("versions")} ).match() } ) } diff --git a/modules/nf-core/bwamem2/mem/tests/main.nf.test.snap b/modules/nf-core/bwamem2/mem/tests/main.nf.test.snap index b7d40a68..74763935 100644 --- a/modules/nf-core/bwamem2/mem/tests/main.nf.test.snap +++ b/modules/nf-core/bwamem2/mem/tests/main.nf.test.snap @@ -3,15 +3,21 @@ "content": [ "e414c2d48e2e44c2c52c20ecd88e8bd8", "57aeef88ed701a8ebc8e2f0a381b2a6", - [ - "versions.yml:md5,3574188ab1f33fd99cff9f5562dfb885" - ] + { + "versions_bwamem2": [ + [ + "BWAMEM2_MEM", + "bwamem2", + "2.2.1" + ] + ] + } ], "meta": { - "nf-test": "0.9.2", - "nextflow": "25.04.7" + "nf-test": "0.9.3", + "nextflow": "25.10.3" }, - "timestamp": "2025-09-23T11:44:52.73673293" + "timestamp": "2026-02-09T16:25:00.500092" }, "sarscov2 - [fastq1, fastq2], index, fasta, true - stub": { "content": [ @@ -44,7 +50,11 @@ ] ], "5": [ - "versions.yml:md5,3574188ab1f33fd99cff9f5562dfb885" + [ + "BWAMEM2_MEM", + "bwamem2", + "2.2.1" + ] ], "bam": [ [ @@ -73,57 +83,79 @@ "sam": [ ], - "versions": [ - "versions.yml:md5,3574188ab1f33fd99cff9f5562dfb885" + "versions_bwamem2": [ + [ + "BWAMEM2_MEM", + "bwamem2", + "2.2.1" + ] ] } ], "meta": { - "nf-test": "0.9.2", - "nextflow": "25.04.7" + "nf-test": "0.9.3", + "nextflow": "25.10.3" }, - "timestamp": "2025-09-23T11:45:14.834888709" + "timestamp": "2026-02-09T16:25:22.004027" }, "sarscov2 - [fastq1, fastq2], index, fasta, true": { "content": [ "716ed1ef39deaad346ca7cf86e08f959", "af8628d9df18b2d3d4f6fd47ef2bb872", - [ - "versions.yml:md5,3574188ab1f33fd99cff9f5562dfb885" - ] + { + "versions_bwamem2": [ + [ + "BWAMEM2_MEM", + "bwamem2", + "2.2.1" + ] + ] + } ], "meta": { - "nf-test": "0.9.2", - "nextflow": "25.04.7" + "nf-test": "0.9.3", + "nextflow": "25.10.3" }, - "timestamp": "2025-09-23T11:45:04.750057645" + "timestamp": "2026-02-09T16:25:14.131056" }, "sarscov2 - fastq, index, fasta, false": { "content": [ "283a83f604f3f5338acedfee349dccf4", "798439cbd7fd81cbcc5078022dc5479d", - [ - "versions.yml:md5,3574188ab1f33fd99cff9f5562dfb885" - ] + { + "versions_bwamem2": [ + [ + "BWAMEM2_MEM", + "bwamem2", + "2.2.1" + ] + ] + } ], "meta": { - "nf-test": "0.9.2", - "nextflow": "25.04.7" + "nf-test": "0.9.3", + "nextflow": "25.10.3" }, - "timestamp": "2025-09-23T11:44:28.57550711" + "timestamp": "2026-02-09T16:24:34.624533" }, "sarscov2 - fastq, index, fasta, true": { "content": [ "ed99048bb552cac58e39923b550b6d5b", "94fcf617f5b994584c4e8d4044e16b4f", - [ - "versions.yml:md5,3574188ab1f33fd99cff9f5562dfb885" - ] + { + "versions_bwamem2": [ + [ + "BWAMEM2_MEM", + "bwamem2", + "2.2.1" + ] + ] + } ], "meta": { - "nf-test": "0.9.2", - "nextflow": "25.04.7" + "nf-test": "0.9.3", + "nextflow": "25.10.3" }, - "timestamp": "2025-09-23T11:44:40.437183765" + "timestamp": "2026-02-09T16:24:47.191245" } } \ No newline at end of file diff --git a/modules/nf-core/fastp/main.nf b/modules/nf-core/fastp/main.nf index 85013f5d..e13509ca 100644 --- a/modules/nf-core/fastp/main.nf +++ b/modules/nf-core/fastp/main.nf @@ -20,7 +20,7 @@ process FASTP { tuple val(meta), path('*.log') , emit: log tuple val(meta), path('*.fail.fastq.gz') , optional:true, emit: reads_fail tuple val(meta), path('*.merged.fastq.gz'), optional:true, emit: reads_merged - path "versions.yml" , emit: versions + tuple val("${task.process}"), val('fastp'), eval('fastp --version 2>&1 | sed -e "s/fastp //g"'), emit: versions_fastp, topic: versions when: task.ext.when == null || task.ext.when @@ -29,9 +29,9 @@ process FASTP { def args = task.ext.args ?: '' def prefix = task.ext.prefix ?: "${meta.id}" def adapter_list = adapter_fasta ? "--adapter_fasta ${adapter_fasta}" : "" - def fail_fastq = save_trimmed_fail && meta.single_end ? "--failed_out ${prefix}.fail.fastq.gz" : save_trimmed_fail && !meta.single_end ? "--failed_out ${prefix}.paired.fail.fastq.gz --unpaired1 ${prefix}_1.fail.fastq.gz --unpaired2 ${prefix}_2.fail.fastq.gz" : '' - def out_fq1 = discard_trimmed_pass ?: ( meta.single_end ? "--out1 ${prefix}.fastp.fastq.gz" : "--out1 ${prefix}_1.fastp.fastq.gz" ) - def out_fq2 = discard_trimmed_pass ?: "--out2 ${prefix}_2.fastp.fastq.gz" + def fail_fastq = save_trimmed_fail && meta.single_end ? "--failed_out ${prefix}.fail.fastq.gz" : save_trimmed_fail && !meta.single_end ? "--failed_out ${prefix}.paired.fail.fastq.gz --unpaired1 ${prefix}_R1.fail.fastq.gz --unpaired2 ${prefix}_R2.fail.fastq.gz" : '' + def out_fq1 = discard_trimmed_pass ?: ( meta.single_end ? "--out1 ${prefix}.fastp.fastq.gz" : "--out1 ${prefix}_R1.fastp.fastq.gz" ) + def out_fq2 = discard_trimmed_pass ?: "--out2 ${prefix}_R2.fastp.fastq.gz" // Added soft-links to original fastqs for consistent naming in MultiQC // Use single ended for interleaved. Add --interleaved_in in config. if ( task.ext.args?.contains('--interleaved_in') ) { @@ -49,11 +49,6 @@ process FASTP { $args \\ 2>| >(tee ${prefix}.fastp.log >&2) \\ | gzip -c > ${prefix}.fastp.fastq.gz - - cat <<-END_VERSIONS > versions.yml - "${task.process}": - fastp: \$(fastp --version 2>&1 | sed -e "s/fastp //g") - END_VERSIONS """ } else if (meta.single_end) { """ @@ -69,20 +64,15 @@ process FASTP { $fail_fastq \\ $args \\ 2>| >(tee ${prefix}.fastp.log >&2) - - cat <<-END_VERSIONS > versions.yml - "${task.process}": - fastp: \$(fastp --version 2>&1 | sed -e "s/fastp //g") - END_VERSIONS """ } else { def merge_fastq = save_merged ? "-m --merged_out ${prefix}.merged.fastq.gz" : '' """ - [ ! -f ${prefix}_1.fastq.gz ] && ln -sf ${reads[0]} ${prefix}_1.fastq.gz - [ ! -f ${prefix}_2.fastq.gz ] && ln -sf ${reads[1]} ${prefix}_2.fastq.gz + [ ! -f ${prefix}_R1.fastq.gz ] && ln -sf ${reads[0]} ${prefix}_R1.fastq.gz + [ ! -f ${prefix}_R2.fastq.gz ] && ln -sf ${reads[1]} ${prefix}_R2.fastq.gz fastp \\ - --in1 ${prefix}_1.fastq.gz \\ - --in2 ${prefix}_2.fastq.gz \\ + --in1 ${prefix}_R1.fastq.gz \\ + --in2 ${prefix}_R2.fastq.gz \\ $out_fq1 \\ $out_fq2 \\ --json ${prefix}.fastp.json \\ @@ -94,20 +84,15 @@ process FASTP { --detect_adapter_for_pe \\ $args \\ 2>| >(tee ${prefix}.fastp.log >&2) - - cat <<-END_VERSIONS > versions.yml - "${task.process}": - fastp: \$(fastp --version 2>&1 | sed -e "s/fastp //g") - END_VERSIONS """ } stub: def prefix = task.ext.prefix ?: "${meta.id}" def is_single_output = task.ext.args?.contains('--interleaved_in') || meta.single_end - def touch_reads = (discard_trimmed_pass) ? "" : (is_single_output) ? "echo '' | gzip > ${prefix}.fastp.fastq.gz" : "echo '' | gzip > ${prefix}_1.fastp.fastq.gz ; echo '' | gzip > ${prefix}_2.fastp.fastq.gz" + def touch_reads = (discard_trimmed_pass) ? "" : (is_single_output) ? "echo '' | gzip > ${prefix}.fastp.fastq.gz" : "echo '' | gzip > ${prefix}_R1.fastp.fastq.gz ; echo '' | gzip > ${prefix}_R2.fastp.fastq.gz" def touch_merged = (!is_single_output && save_merged) ? "echo '' | gzip > ${prefix}.merged.fastq.gz" : "" - def touch_fail_fastq = (!save_trimmed_fail) ? "" : meta.single_end ? "echo '' | gzip > ${prefix}.fail.fastq.gz" : "echo '' | gzip > ${prefix}.paired.fail.fastq.gz ; echo '' | gzip > ${prefix}_1.fail.fastq.gz ; echo '' | gzip > ${prefix}_2.fail.fastq.gz" + def touch_fail_fastq = (!save_trimmed_fail) ? "" : meta.single_end ? "echo '' | gzip > ${prefix}.fail.fastq.gz" : "echo '' | gzip > ${prefix}.paired.fail.fastq.gz ; echo '' | gzip > ${prefix}_R1.fail.fastq.gz ; echo '' | gzip > ${prefix}_R2.fail.fastq.gz" """ $touch_reads $touch_fail_fastq @@ -115,10 +100,5 @@ process FASTP { touch "${prefix}.fastp.json" touch "${prefix}.fastp.html" touch "${prefix}.fastp.log" - - cat <<-END_VERSIONS > versions.yml - "${task.process}": - fastp: \$(fastp --version 2>&1 | sed -e "s/fastp //g") - END_VERSIONS """ } diff --git a/modules/nf-core/fastp/meta.yml b/modules/nf-core/fastp/meta.yml index 324025fe..a67be395 100644 --- a/modules/nf-core/fastp/meta.yml +++ b/modules/nf-core/fastp/meta.yml @@ -54,6 +54,7 @@ output: description: The trimmed/modified/unmerged fastq reads pattern: "*fastp.fastq.gz" ontologies: + - edam: http://edamontology.org/format_1930 # FASTQ - edam: http://edamontology.org/format_3989 # GZIP format json: - - meta: @@ -100,6 +101,7 @@ output: description: Reads the failed the preprocessing pattern: "*fail.fastq.gz" ontologies: + - edam: http://edamontology.org/format_1930 # FASTQ - edam: http://edamontology.org/format_3989 # GZIP format reads_merged: - - meta: @@ -112,16 +114,31 @@ output: description: Reads that were successfully merged pattern: "*.{merged.fastq.gz}" ontologies: [] + versions_fastp: + - - "${task.process}": + type: string + description: The name of the process + - fastp: + type: string + description: The name of the tool + - 'fastp --version 2>&1 | sed -e "s/fastp //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: "versions.yml" - ontologies: - - edam: http://edamontology.org/format_3750 # YAML + - - "${task.process}": + type: string + description: The name of the process + - fastp: + type: string + description: The name of the tool + - 'fastp --version 2>&1 | sed -e "s/fastp //g"': + type: eval + description: The expression to obtain the version of the tool authors: - "@drpatelh" - "@kevinmenden" + - "@eit-maxlcummins" maintainers: - "@drpatelh" - "@kevinmenden" diff --git a/modules/nf-core/fastp/tests/main.nf.test b/modules/nf-core/fastp/tests/main.nf.test index 5125705c..b7901578 100644 --- a/modules/nf-core/fastp/tests/main.nf.test +++ b/modules/nf-core/fastp/tests/main.nf.test @@ -39,7 +39,7 @@ nextflow_process { process.out.reads, process.out.reads_fail, process.out.reads_merged, - process.out.versions).match() + process.out.findAll { key, val -> key.startsWith('versions') }).match() } ) } @@ -80,7 +80,7 @@ nextflow_process { process.out.reads, process.out.reads_fail, process.out.reads_merged, - process.out.versions).match() } + process.out.findAll { key, val -> key.startsWith('versions') }).match() } ) } } @@ -117,7 +117,7 @@ nextflow_process { { assert process.out.reads_merged == [] }, { assert snapshot( process.out.reads, - process.out.versions).match() } + process.out.findAll { key, val -> key.startsWith('versions') }).match() } ) } } @@ -154,7 +154,7 @@ nextflow_process { process.out.reads, process.out.reads_fail, process.out.reads_merged, - process.out.versions).match() } + process.out.findAll { key, val -> key.startsWith('versions') }).match() } ) } } @@ -194,7 +194,7 @@ nextflow_process { process.out.reads, process.out.reads_fail, process.out.reads_merged, - process.out.versions).match() } + process.out.findAll { key, val -> key.startsWith('versions') }).match() } ) } } @@ -233,7 +233,7 @@ nextflow_process { process.out.reads, process.out.reads_fail, process.out.reads_merged, - process.out.versions).match() }, + process.out.findAll { key, val -> key.startsWith('versions') }).match() }, ) } } @@ -272,7 +272,7 @@ nextflow_process { process.out.reads, process.out.reads_fail, process.out.reads_merged, - process.out.versions).match() } + process.out.findAll { key, val -> key.startsWith('versions') }).match() } ) } } @@ -312,7 +312,7 @@ nextflow_process { process.out.reads_fail, process.out.reads_merged, process.out.reads_merged, - process.out.versions).match() } + process.out.findAll { key, val -> key.startsWith('versions') }).match() } ) } } @@ -354,7 +354,7 @@ nextflow_process { process.out.reads_fail, process.out.reads_merged, process.out.reads_merged, - process.out.versions).match() } + process.out.findAll { key, val -> key.startsWith('versions') }).match() } ) } } diff --git a/modules/nf-core/fastp/tests/main.nf.test.snap b/modules/nf-core/fastp/tests/main.nf.test.snap index a30c680d..56772358 100644 --- a/modules/nf-core/fastp/tests/main.nf.test.snap +++ b/modules/nf-core/fastp/tests/main.nf.test.snap @@ -39,7 +39,11 @@ ], "6": [ - "versions.yml:md5,c4974822658d02533e660fae343f281b" + [ + "FASTP", + "fastp", + "1.0.1" + ] ], "html": [ [ @@ -77,16 +81,20 @@ "reads_merged": [ ], - "versions": [ - "versions.yml:md5,c4974822658d02533e660fae343f281b" + "versions_fastp": [ + [ + "FASTP", + "fastp", + "1.0.1" + ] ] } ], "meta": { - "nf-test": "0.9.2", - "nextflow": "25.04.6" + "nf-test": "0.9.3", + "nextflow": "25.10.2" }, - "timestamp": "2025-09-11T09:55:42.073182" + "timestamp": "2026-01-22T13:00:52.14535813" }, "test_fastp_paired_end": { "content": [ @@ -97,8 +105,8 @@ "single_end": false }, [ - "test_1.fastp.fastq.gz:md5,67b2bbae47f073e05a97a9c2edce23c7", - "test_2.fastp.fastq.gz:md5,25cbdca08e2083dbd4f0502de6b62f39" + "test_R1.fastp.fastq.gz:md5,67b2bbae47f073e05a97a9c2edce23c7", + "test_R2.fastp.fastq.gz:md5,25cbdca08e2083dbd4f0502de6b62f39" ] ] ], @@ -108,15 +116,21 @@ [ ], - [ - "versions.yml:md5,c4974822658d02533e660fae343f281b" - ] + { + "versions_fastp": [ + [ + "FASTP", + "fastp", + "1.0.1" + ] + ] + } ], "meta": { - "nf-test": "0.9.2", - "nextflow": "25.04.6" + "nf-test": "0.9.3", + "nextflow": "25.10.2" }, - "timestamp": "2025-09-19T16:23:12.436191" + "timestamp": "2026-01-23T09:46:26.421773402" }, "test_fastp_paired_end_merged_adapterlist": { "content": [ @@ -127,8 +141,8 @@ "single_end": false }, [ - "test_1.fastp.fastq.gz:md5,54b726a55e992a869fd3fa778afe1672", - "test_2.fastp.fastq.gz:md5,29d3b33b869f7b63417b8ff07bb128ba" + "test_R1.fastp.fastq.gz:md5,54b726a55e992a869fd3fa778afe1672", + "test_R2.fastp.fastq.gz:md5,29d3b33b869f7b63417b8ff07bb128ba" ] ] ], @@ -144,15 +158,21 @@ "test.merged.fastq.gz:md5,c873bb1ab3fa859dcc47306465e749d5" ] ], - [ - "versions.yml:md5,c4974822658d02533e660fae343f281b" - ] + { + "versions_fastp": [ + [ + "FASTP", + "fastp", + "1.0.1" + ] + ] + } ], "meta": { - "nf-test": "0.9.2", - "nextflow": "25.04.6" + "nf-test": "0.9.3", + "nextflow": "25.10.2" }, - "timestamp": "2025-09-19T16:23:32.267735" + "timestamp": "2026-01-23T09:46:59.832295907" }, "test_fastp_single_end_qc_only": { "content": [ @@ -174,15 +194,21 @@ [ ], - [ - "versions.yml:md5,c4974822658d02533e660fae343f281b" - ] + { + "versions_fastp": [ + [ + "FASTP", + "fastp", + "1.0.1" + ] + ] + } ], "meta": { - "nf-test": "0.9.2", - "nextflow": "25.04.6" + "nf-test": "0.9.3", + "nextflow": "25.10.2" }, - "timestamp": "2025-09-19T16:23:36.149003" + "timestamp": "2026-01-23T09:47:06.486959565" }, "test_fastp_paired_end_trim_fail": { "content": [ @@ -193,8 +219,8 @@ "single_end": false }, [ - "test_1.fastp.fastq.gz:md5,6ff32a64c5188b9a9192be1398c262c7", - "test_2.fastp.fastq.gz:md5,db0cb7c9977e94ac2b4b446ebd017a8a" + "test_R1.fastp.fastq.gz:md5,6ff32a64c5188b9a9192be1398c262c7", + "test_R2.fastp.fastq.gz:md5,db0cb7c9977e94ac2b4b446ebd017a8a" ] ] ], @@ -206,23 +232,29 @@ }, [ "test.paired.fail.fastq.gz:md5,409b687c734cedd7a1fec14d316e1366", - "test_1.fail.fastq.gz:md5,4f273cf3159c13f79e8ffae12f5661f6", - "test_2.fail.fastq.gz:md5,f97b9edefb5649aab661fbc9e71fc995" + "test_R1.fail.fastq.gz:md5,4f273cf3159c13f79e8ffae12f5661f6", + "test_R2.fail.fastq.gz:md5,f97b9edefb5649aab661fbc9e71fc995" ] ] ], [ ], - [ - "versions.yml:md5,c4974822658d02533e660fae343f281b" - ] + { + "versions_fastp": [ + [ + "FASTP", + "fastp", + "1.0.1" + ] + ] + } ], "meta": { - "nf-test": "0.9.2", - "nextflow": "25.04.6" + "nf-test": "0.9.3", + "nextflow": "25.10.2" }, - "timestamp": "2025-09-19T16:23:24.23891" + "timestamp": "2026-01-23T09:46:46.736511024" }, "fastp - stub test_fastp_interleaved": { "content": [ @@ -270,7 +302,11 @@ ], "6": [ - "versions.yml:md5,c4974822658d02533e660fae343f281b" + [ + "FASTP", + "fastp", + "1.0.1" + ] ], "html": [ [ @@ -314,16 +350,20 @@ "reads_merged": [ ], - "versions": [ - "versions.yml:md5,c4974822658d02533e660fae343f281b" + "versions_fastp": [ + [ + "FASTP", + "fastp", + "1.0.1" + ] ] } ], "meta": { - "nf-test": "0.9.2", - "nextflow": "25.04.6" + "nf-test": "0.9.3", + "nextflow": "25.10.2" }, - "timestamp": "2025-09-11T09:55:19.47199" + "timestamp": "2026-01-22T13:00:16.097071654" }, "test_fastp_single_end - stub": { "content": [ @@ -371,7 +411,11 @@ ], "6": [ - "versions.yml:md5,c4974822658d02533e660fae343f281b" + [ + "FASTP", + "fastp", + "1.0.1" + ] ], "html": [ [ @@ -415,16 +459,20 @@ "reads_merged": [ ], - "versions": [ - "versions.yml:md5,c4974822658d02533e660fae343f281b" + "versions_fastp": [ + [ + "FASTP", + "fastp", + "1.0.1" + ] ] } ], "meta": { - "nf-test": "0.9.2", - "nextflow": "25.04.6" + "nf-test": "0.9.3", + "nextflow": "25.10.2" }, - "timestamp": "2025-09-11T09:55:09.617001" + "timestamp": "2026-01-22T13:00:03.317192706" }, "test_fastp_paired_end_merged_adapterlist - stub": { "content": [ @@ -436,8 +484,8 @@ "single_end": false }, [ - "test_1.fastp.fastq.gz:md5,68b329da9893e34099c7d8ad5cb9c940", - "test_2.fastp.fastq.gz:md5,68b329da9893e34099c7d8ad5cb9c940" + "test_R1.fastp.fastq.gz:md5,68b329da9893e34099c7d8ad5cb9c940", + "test_R2.fastp.fastq.gz:md5,68b329da9893e34099c7d8ad5cb9c940" ] ] ], @@ -481,7 +529,11 @@ ] ], "6": [ - "versions.yml:md5,c4974822658d02533e660fae343f281b" + [ + "FASTP", + "fastp", + "1.0.1" + ] ], "html": [ [ @@ -517,8 +569,8 @@ "single_end": false }, [ - "test_1.fastp.fastq.gz:md5,68b329da9893e34099c7d8ad5cb9c940", - "test_2.fastp.fastq.gz:md5,68b329da9893e34099c7d8ad5cb9c940" + "test_R1.fastp.fastq.gz:md5,68b329da9893e34099c7d8ad5cb9c940", + "test_R2.fastp.fastq.gz:md5,68b329da9893e34099c7d8ad5cb9c940" ] ] ], @@ -534,16 +586,20 @@ "test.merged.fastq.gz:md5,68b329da9893e34099c7d8ad5cb9c940" ] ], - "versions": [ - "versions.yml:md5,c4974822658d02533e660fae343f281b" + "versions_fastp": [ + [ + "FASTP", + "fastp", + "1.0.1" + ] ] } ], "meta": { - "nf-test": "0.9.2", - "nextflow": "25.04.6" + "nf-test": "0.9.3", + "nextflow": "25.10.2" }, - "timestamp": "2025-09-11T09:55:37.413738" + "timestamp": "2026-01-22T13:00:44.851708205" }, "test_fastp_paired_end_merged - stub": { "content": [ @@ -555,8 +611,8 @@ "single_end": false }, [ - "test_1.fastp.fastq.gz:md5,68b329da9893e34099c7d8ad5cb9c940", - "test_2.fastp.fastq.gz:md5,68b329da9893e34099c7d8ad5cb9c940" + "test_R1.fastp.fastq.gz:md5,68b329da9893e34099c7d8ad5cb9c940", + "test_R2.fastp.fastq.gz:md5,68b329da9893e34099c7d8ad5cb9c940" ] ] ], @@ -600,7 +656,11 @@ ] ], "6": [ - "versions.yml:md5,c4974822658d02533e660fae343f281b" + [ + "FASTP", + "fastp", + "1.0.1" + ] ], "html": [ [ @@ -636,8 +696,8 @@ "single_end": false }, [ - "test_1.fastp.fastq.gz:md5,68b329da9893e34099c7d8ad5cb9c940", - "test_2.fastp.fastq.gz:md5,68b329da9893e34099c7d8ad5cb9c940" + "test_R1.fastp.fastq.gz:md5,68b329da9893e34099c7d8ad5cb9c940", + "test_R2.fastp.fastq.gz:md5,68b329da9893e34099c7d8ad5cb9c940" ] ] ], @@ -653,16 +713,20 @@ "test.merged.fastq.gz:md5,68b329da9893e34099c7d8ad5cb9c940" ] ], - "versions": [ - "versions.yml:md5,c4974822658d02533e660fae343f281b" + "versions_fastp": [ + [ + "FASTP", + "fastp", + "1.0.1" + ] ] } ], "meta": { - "nf-test": "0.9.2", - "nextflow": "25.04.6" + "nf-test": "0.9.3", + "nextflow": "25.10.2" }, - "timestamp": "2025-09-11T09:55:32.965652" + "timestamp": "2026-01-22T13:00:37.581047713" }, "test_fastp_paired_end_merged": { "content": [ @@ -673,8 +737,8 @@ "single_end": false }, [ - "test_1.fastp.fastq.gz:md5,54b726a55e992a869fd3fa778afe1672", - "test_2.fastp.fastq.gz:md5,29d3b33b869f7b63417b8ff07bb128ba" + "test_R1.fastp.fastq.gz:md5,54b726a55e992a869fd3fa778afe1672", + "test_R2.fastp.fastq.gz:md5,29d3b33b869f7b63417b8ff07bb128ba" ] ] ], @@ -690,15 +754,21 @@ "test.merged.fastq.gz:md5,c873bb1ab3fa859dcc47306465e749d5" ] ], - [ - "versions.yml:md5,c4974822658d02533e660fae343f281b" - ] + { + "versions_fastp": [ + [ + "FASTP", + "fastp", + "1.0.1" + ] + ] + } ], "meta": { - "nf-test": "0.9.2", - "nextflow": "25.04.6" + "nf-test": "0.9.3", + "nextflow": "25.10.2" }, - "timestamp": "2025-09-19T16:23:28.074624" + "timestamp": "2026-01-23T09:46:53.190202914" }, "test_fastp_paired_end - stub": { "content": [ @@ -710,8 +780,8 @@ "single_end": false }, [ - "test_1.fastp.fastq.gz:md5,68b329da9893e34099c7d8ad5cb9c940", - "test_2.fastp.fastq.gz:md5,68b329da9893e34099c7d8ad5cb9c940" + "test_R1.fastp.fastq.gz:md5,68b329da9893e34099c7d8ad5cb9c940", + "test_R2.fastp.fastq.gz:md5,68b329da9893e34099c7d8ad5cb9c940" ] ] ], @@ -749,7 +819,11 @@ ], "6": [ - "versions.yml:md5,c4974822658d02533e660fae343f281b" + [ + "FASTP", + "fastp", + "1.0.1" + ] ], "html": [ [ @@ -785,8 +859,8 @@ "single_end": false }, [ - "test_1.fastp.fastq.gz:md5,68b329da9893e34099c7d8ad5cb9c940", - "test_2.fastp.fastq.gz:md5,68b329da9893e34099c7d8ad5cb9c940" + "test_R1.fastp.fastq.gz:md5,68b329da9893e34099c7d8ad5cb9c940", + "test_R2.fastp.fastq.gz:md5,68b329da9893e34099c7d8ad5cb9c940" ] ] ], @@ -796,16 +870,20 @@ "reads_merged": [ ], - "versions": [ - "versions.yml:md5,c4974822658d02533e660fae343f281b" + "versions_fastp": [ + [ + "FASTP", + "fastp", + "1.0.1" + ] ] } ], "meta": { - "nf-test": "0.9.2", - "nextflow": "25.04.6" + "nf-test": "0.9.3", + "nextflow": "25.10.2" }, - "timestamp": "2025-09-11T09:55:14.414258" + "timestamp": "2026-01-22T13:00:09.585957282" }, "test_fastp_single_end": { "content": [ @@ -824,15 +902,21 @@ [ ], - [ - "versions.yml:md5,c4974822658d02533e660fae343f281b" - ] + { + "versions_fastp": [ + [ + "FASTP", + "fastp", + "1.0.1" + ] + ] + } ], "meta": { - "nf-test": "0.9.2", - "nextflow": "25.04.6" + "nf-test": "0.9.3", + "nextflow": "25.10.2" }, - "timestamp": "2025-09-19T16:23:08.469846" + "timestamp": "2026-01-23T09:46:19.624824985" }, "test_fastp_single_end_trim_fail - stub": { "content": [ @@ -886,7 +970,11 @@ ], "6": [ - "versions.yml:md5,c4974822658d02533e660fae343f281b" + [ + "FASTP", + "fastp", + "1.0.1" + ] ], "html": [ [ @@ -936,16 +1024,20 @@ "reads_merged": [ ], - "versions": [ - "versions.yml:md5,c4974822658d02533e660fae343f281b" + "versions_fastp": [ + [ + "FASTP", + "fastp", + "1.0.1" + ] ] } ], "meta": { - "nf-test": "0.9.2", - "nextflow": "25.04.6" + "nf-test": "0.9.3", + "nextflow": "25.10.2" }, - "timestamp": "2025-09-11T09:55:23.871395" + "timestamp": "2026-01-22T13:00:22.800659826" }, "test_fastp_paired_end_trim_fail - stub": { "content": [ @@ -957,8 +1049,8 @@ "single_end": false }, [ - "test_1.fastp.fastq.gz:md5,68b329da9893e34099c7d8ad5cb9c940", - "test_2.fastp.fastq.gz:md5,68b329da9893e34099c7d8ad5cb9c940" + "test_R1.fastp.fastq.gz:md5,68b329da9893e34099c7d8ad5cb9c940", + "test_R2.fastp.fastq.gz:md5,68b329da9893e34099c7d8ad5cb9c940" ] ] ], @@ -997,8 +1089,8 @@ }, [ "test.paired.fail.fastq.gz:md5,68b329da9893e34099c7d8ad5cb9c940", - "test_1.fail.fastq.gz:md5,68b329da9893e34099c7d8ad5cb9c940", - "test_2.fail.fastq.gz:md5,68b329da9893e34099c7d8ad5cb9c940" + "test_R1.fail.fastq.gz:md5,68b329da9893e34099c7d8ad5cb9c940", + "test_R2.fail.fastq.gz:md5,68b329da9893e34099c7d8ad5cb9c940" ] ] ], @@ -1006,7 +1098,11 @@ ], "6": [ - "versions.yml:md5,c4974822658d02533e660fae343f281b" + [ + "FASTP", + "fastp", + "1.0.1" + ] ], "html": [ [ @@ -1042,8 +1138,8 @@ "single_end": false }, [ - "test_1.fastp.fastq.gz:md5,68b329da9893e34099c7d8ad5cb9c940", - "test_2.fastp.fastq.gz:md5,68b329da9893e34099c7d8ad5cb9c940" + "test_R1.fastp.fastq.gz:md5,68b329da9893e34099c7d8ad5cb9c940", + "test_R2.fastp.fastq.gz:md5,68b329da9893e34099c7d8ad5cb9c940" ] ] ], @@ -1055,24 +1151,28 @@ }, [ "test.paired.fail.fastq.gz:md5,68b329da9893e34099c7d8ad5cb9c940", - "test_1.fail.fastq.gz:md5,68b329da9893e34099c7d8ad5cb9c940", - "test_2.fail.fastq.gz:md5,68b329da9893e34099c7d8ad5cb9c940" + "test_R1.fail.fastq.gz:md5,68b329da9893e34099c7d8ad5cb9c940", + "test_R2.fail.fastq.gz:md5,68b329da9893e34099c7d8ad5cb9c940" ] ] ], "reads_merged": [ ], - "versions": [ - "versions.yml:md5,c4974822658d02533e660fae343f281b" + "versions_fastp": [ + [ + "FASTP", + "fastp", + "1.0.1" + ] ] } ], "meta": { - "nf-test": "0.9.2", - "nextflow": "25.04.6" + "nf-test": "0.9.3", + "nextflow": "25.10.2" }, - "timestamp": "2025-09-11T09:55:28.399328" + "timestamp": "2026-01-22T13:00:30.271734068" }, "fastp test_fastp_interleaved": { "content": [ @@ -1085,15 +1185,21 @@ "test.fastp.fastq.gz:md5,217d62dc13a23e92513a1bd8e1bcea39" ] ], - [ - "versions.yml:md5,c4974822658d02533e660fae343f281b" - ] + { + "versions_fastp": [ + [ + "FASTP", + "fastp", + "1.0.1" + ] + ] + } ], "meta": { - "nf-test": "0.9.2", - "nextflow": "25.04.6" + "nf-test": "0.9.3", + "nextflow": "25.10.2" }, - "timestamp": "2025-09-19T16:23:16.479494" + "timestamp": "2026-01-23T09:46:33.4628687" }, "test_fastp_single_end_trim_fail": { "content": [ @@ -1118,15 +1224,21 @@ [ ], - [ - "versions.yml:md5,c4974822658d02533e660fae343f281b" - ] + { + "versions_fastp": [ + [ + "FASTP", + "fastp", + "1.0.1" + ] + ] + } ], "meta": { - "nf-test": "0.9.2", - "nextflow": "25.04.6" + "nf-test": "0.9.3", + "nextflow": "25.10.2" }, - "timestamp": "2025-09-19T16:23:20.299076" + "timestamp": "2026-01-23T09:46:39.895973372" }, "test_fastp_paired_end_qc_only": { "content": [ @@ -1148,15 +1260,21 @@ [ ], - [ - "versions.yml:md5,c4974822658d02533e660fae343f281b" - ] + { + "versions_fastp": [ + [ + "FASTP", + "fastp", + "1.0.1" + ] + ] + } ], "meta": { - "nf-test": "0.9.2", - "nextflow": "25.04.6" + "nf-test": "0.9.3", + "nextflow": "25.10.2" }, - "timestamp": "2025-09-19T16:23:40.113724" + "timestamp": "2026-01-23T09:47:13.015833707" }, "test_fastp_paired_end_qc_only - stub": { "content": [ @@ -1198,7 +1316,11 @@ ], "6": [ - "versions.yml:md5,c4974822658d02533e660fae343f281b" + [ + "FASTP", + "fastp", + "1.0.1" + ] ], "html": [ [ @@ -1236,15 +1358,19 @@ "reads_merged": [ ], - "versions": [ - "versions.yml:md5,c4974822658d02533e660fae343f281b" + "versions_fastp": [ + [ + "FASTP", + "fastp", + "1.0.1" + ] ] } ], "meta": { - "nf-test": "0.9.2", - "nextflow": "25.04.6" + "nf-test": "0.9.3", + "nextflow": "25.10.2" }, - "timestamp": "2025-09-11T09:55:46.696419" + "timestamp": "2026-01-22T13:00:59.670106791" } } \ No newline at end of file diff --git a/modules/nf-core/fastplong/main.nf b/modules/nf-core/fastplong/main.nf index b672c113..ce1be5ef 100644 --- a/modules/nf-core/fastplong/main.nf +++ b/modules/nf-core/fastplong/main.nf @@ -19,7 +19,7 @@ process FASTPLONG { tuple val(meta), path('*.html') , emit: html tuple val(meta), path('*.log') , emit: log tuple val(meta), path('*.fail.fastq.gz') , optional:true, emit: reads_fail - path "versions.yml" , emit: versions + tuple val("${task.process}"), val('fastplong'), eval('fastplong --version 2>&1 | sed -e "s/fastplong //g"'), emit: versions_fastplong, topic: versions when: task.ext.when == null || task.ext.when @@ -43,12 +43,6 @@ process FASTPLONG { --report_title $report_title\\ $args \\ 2> >(tee ${prefix}.fastplong.log >&2) - - - cat <<-END_VERSIONS > versions.yml - "${task.process}": - fastplong: \$(fastplong --version 2>&1 | sed -e "s/fastplong //g") - END_VERSIONS """ stub: @@ -64,10 +58,5 @@ process FASTPLONG { touch ${prefix}.fastplong.json touch ${prefix}.fastplong.html touch ${prefix}.fastplong.log - - cat <<-END_VERSIONS > versions.yml - "${task.process}": - fastplong: \$(fastplong --version 2>&1 | sed -e "s/fastplong //g") - END_VERSIONS """ } diff --git a/modules/nf-core/fastplong/meta.yml b/modules/nf-core/fastplong/meta.yml index d48ce4df..53ebdd58 100644 --- a/modules/nf-core/fastplong/meta.yml +++ b/modules/nf-core/fastplong/meta.yml @@ -55,8 +55,8 @@ output: type: file description: Trimmed and filtered reads pattern: "*fastplong.fastq.gz" - ontologies: + - edam: http://edamontology.org/format_1930 # FASTQ - edam: http://edamontology.org/format_3989 # GZIP format json: - - meta: @@ -66,7 +66,6 @@ output: type: file description: QC report in JSON format pattern: "*.json" - ontologies: - edam: http://edamontology.org/format_3464 # JSON html: @@ -87,7 +86,6 @@ output: type: file description: Log file generated during trimming pattern: "*.log" - ontologies: [] reads_fail: - - meta: @@ -97,18 +95,32 @@ output: type: file description: Reads that failed quality/trimming filters pattern: "*fail.fastq.gz" - ontologies: + - edam: http://edamontology.org/format_1930 # FASTQ - edam: http://edamontology.org/format_3989 # GZIP format + versions_fastplong: + - - "${task.process}": + type: string + description: The name of the process + - fastplong: + type: string + description: The name of the tool + - 'fastplong --version 2>&1 | sed -e "s/fastplong //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: "versions.yml" - - ontologies: - - edam: http://edamontology.org/format_3750 # YAML + - - "${task.process}": + type: string + description: The name of the process + - fastplong: + type: string + description: The name of the tool + - 'fastplong --version 2>&1 | sed -e "s/fastplong //g"': + type: eval + description: The expression to obtain the version of the tool authors: - "@Lupphes" + - "@eit-maxlcummins" maintainers: - "@Lupphes" diff --git a/modules/nf-core/fastplong/tests/main.nf.test b/modules/nf-core/fastplong/tests/main.nf.test index 5bcf1081..4f545c90 100644 --- a/modules/nf-core/fastplong/tests/main.nf.test +++ b/modules/nf-core/fastplong/tests/main.nf.test @@ -35,7 +35,7 @@ nextflow_process { process.out.json, process.out.reads, process.out.reads_fail, - process.out.versions).match() } + process.out.findAll { key, val -> key.startsWith('versions') }).match() } ) } } diff --git a/modules/nf-core/fastplong/tests/main.nf.test.snap b/modules/nf-core/fastplong/tests/main.nf.test.snap index daf08610..041c9b49 100644 --- a/modules/nf-core/fastplong/tests/main.nf.test.snap +++ b/modules/nf-core/fastplong/tests/main.nf.test.snap @@ -22,15 +22,21 @@ [ ], - [ - "versions.yml:md5,1eec92ed637b6807443de07b646dcd07" - ] + { + "versions_fastplong": [ + [ + "FASTPLONG", + "fastplong", + "0.3.0" + ] + ] + } ], "meta": { - "nf-test": "0.9.2", - "nextflow": "24.10.5" + "nf-test": "0.9.3", + "nextflow": "25.10.2" }, - "timestamp": "2025-08-07T11:21:12.065994812" + "timestamp": "2026-01-23T09:48:16.59023584" }, "test_fastplong - pacbio - stub": { "content": [ @@ -75,7 +81,11 @@ ], "5": [ - "versions.yml:md5,1eec92ed637b6807443de07b646dcd07" + [ + "FASTPLONG", + "fastplong", + "0.3.0" + ] ], "html": [ [ @@ -116,15 +126,19 @@ "reads_fail": [ ], - "versions": [ - "versions.yml:md5,1eec92ed637b6807443de07b646dcd07" + "versions_fastplong": [ + [ + "FASTPLONG", + "fastplong", + "0.3.0" + ] ] } ], "meta": { - "nf-test": "0.9.2", - "nextflow": "24.10.5" + "nf-test": "0.9.3", + "nextflow": "25.10.2" }, - "timestamp": "2025-08-07T00:26:16.322516864" + "timestamp": "2026-01-22T13:04:24.553785802" } } \ No newline at end of file diff --git a/modules/nf-core/fastqc/main.nf b/modules/nf-core/fastqc/main.nf index 23e16634..f5629527 100644 --- a/modules/nf-core/fastqc/main.nf +++ b/modules/nf-core/fastqc/main.nf @@ -1,6 +1,6 @@ process FASTQC { tag "${meta.id}" - label 'process_medium' + label 'process_low' conda "${moduleDir}/environment.yml" container "${ workflow.containerEngine == 'singularity' && !task.ext.singularity_pull_docker_container ? @@ -11,9 +11,9 @@ process FASTQC { tuple val(meta), path(reads) output: - tuple val(meta), path("*.html"), emit: html - tuple val(meta), path("*.zip") , emit: zip - path "versions.yml" , emit: versions + tuple val(meta) , path("*.html") , emit: html + tuple val(meta) , path("*.zip") , emit: zip + tuple val("${task.process}"), val('fastqc'), eval('fastqc --version | sed "/FastQC v/!d; s/.*v//"'), emit: versions_fastqc, topic: versions when: task.ext.when == null || task.ext.when @@ -43,11 +43,6 @@ process FASTQC { --threads ${task.cpus} \\ --memory ${fastqc_memory} \\ ${renamed_files} - - cat <<-END_VERSIONS > versions.yml - "${task.process}": - fastqc: \$( fastqc --version | sed '/FastQC v/!d; s/.*v//' ) - END_VERSIONS """ stub: @@ -55,10 +50,5 @@ process FASTQC { """ touch ${prefix}.html touch ${prefix}.zip - - cat <<-END_VERSIONS > versions.yml - "${task.process}": - fastqc: \$( fastqc --version | sed '/FastQC v/!d; s/.*v//' ) - END_VERSIONS """ } diff --git a/modules/nf-core/flye/main.nf b/modules/nf-core/flye/main.nf index fecab194..9da156e3 100644 --- a/modules/nf-core/flye/main.nf +++ b/modules/nf-core/flye/main.nf @@ -18,7 +18,7 @@ process FLYE { tuple val(meta), path("*.txt") , emit: txt tuple val(meta), path("*.log") , emit: log tuple val(meta), path("*.json") , emit: json - path "versions.yml" , emit: versions + tuple val("${task.process}"), val('flye'), eval('flye --version'), emit: versions_flye, topic: versions when: task.ext.when == null || task.ext.when @@ -43,11 +43,6 @@ process FLYE { mv assembly_info.txt ${prefix}.assembly_info.txt mv flye.log ${prefix}.flye.log mv params.json ${prefix}.params.json - - cat <<-END_VERSIONS > versions.yml - "${task.process}": - flye: \$( flye --version ) - END_VERSIONS """ stub: @@ -59,10 +54,5 @@ process FLYE { echo contig_1 > ${prefix}.assembly_info.txt echo stub > ${prefix}.flye.log echo stub > ${prefix}.params.json - - cat <<-END_VERSIONS > versions.yml - "${task.process}": - flye: \$( flye --version ) - END_VERSIONS """ } diff --git a/modules/nf-core/flye/meta.yml b/modules/nf-core/flye/meta.yml index 8e338b6c..f77f4885 100644 --- a/modules/nf-core/flye/meta.yml +++ b/modules/nf-core/flye/meta.yml @@ -24,8 +24,8 @@ input: e.g. [ id:'test' ] - reads: type: file - description: Input reads from Oxford Nanopore or PacBio data in FASTA/FASTQ - format. + description: Input reads from Oxford Nanopore or PacBio data in + FASTA/FASTQ format. pattern: "*.{fasta,fastq,fasta.gz,fastq.gz,fa,fq,fa.gz,fq.gz}" ontologies: - edam: http://edamontology.org/format_1930 # FASTQ @@ -104,13 +104,29 @@ output: pattern: "*.json" ontologies: - edam: http://edamontology.org/format_3464 # JSON + versions_flye: + - - ${task.process}: + type: string + description: The name of the process + - flye: + type: string + description: The name of the tool + - flye --version: + type: eval + description: The expression to obtain the version of the tool + +topics: 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 name of the process + - flye: + type: string + description: The name of the tool + - flye --version: + type: eval + description: The expression to obtain the version of the tool + authors: - "@mirpedrol" maintainers: diff --git a/modules/nf-core/flye/tests/main.nf.test b/modules/nf-core/flye/tests/main.nf.test index afbf926e..e173518e 100644 --- a/modules/nf-core/flye/tests/main.nf.test +++ b/modules/nf-core/flye/tests/main.nf.test @@ -38,7 +38,7 @@ nextflow_process { file(process.out.txt.get(0).get(1)).name, path(process.out.txt.get(0).get(1)).readLines()[1].contains("contig_1"), process.out.json, - process.out.versions + process.out.findAll { key, val -> key.startsWith('versions') } ).match() } ) } @@ -72,7 +72,7 @@ nextflow_process { file(process.out.txt.get(0).get(1)).name, path(process.out.txt.get(0).get(1)).readLines()[1].contains("contig_1"), process.out.json, - process.out.versions + process.out.findAll { key, val -> key.startsWith('versions') } ).match() } ) } @@ -106,7 +106,7 @@ nextflow_process { file(process.out.txt.get(0).get(1)).name, path(process.out.txt.get(0).get(1)).readLines()[1].contains("contig_1"), process.out.json, - process.out.versions + process.out.findAll { key, val -> key.startsWith('versions') } ).match() } ) } @@ -140,7 +140,7 @@ nextflow_process { file(process.out.txt.get(0).get(1)).name, path(process.out.txt.get(0).get(1)).readLines()[1].contains("contig_1"), process.out.json, - process.out.versions + process.out.findAll { key, val -> key.startsWith('versions') } ).match() } ) } @@ -167,7 +167,7 @@ nextflow_process { { assert process.success }, { assert snapshot( process.out, - path(process.out.versions.get(0)).yaml + process.out.findAll { key, val -> key.startsWith('versions') } ).match() } ) diff --git a/modules/nf-core/flye/tests/main.nf.test.snap b/modules/nf-core/flye/tests/main.nf.test.snap index 7101f9ed..ed22fa9d 100644 --- a/modules/nf-core/flye/tests/main.nf.test.snap +++ b/modules/nf-core/flye/tests/main.nf.test.snap @@ -15,15 +15,21 @@ "test.params.json:md5,54b576cb6d4d27656878a7fd3657bde9" ] ], - [ - "versions.yml:md5,80496e451401dbc0269ec404801a90e3" - ] + { + "versions_flye": [ + [ + "FLYE", + "flye", + "2.9.5-b1801" + ] + ] + } ], "meta": { - "nf-test": "0.9.0", - "nextflow": "24.04.4" + "nf-test": "0.9.3", + "nextflow": "25.10.2" }, - "timestamp": "2024-09-16T13:41:09.075217" + "timestamp": "2026-01-22T13:55:32.315805611" }, "flye_pacbio_corr": { "content": [ @@ -41,15 +47,21 @@ "test.params.json:md5,54b576cb6d4d27656878a7fd3657bde9" ] ], - [ - "versions.yml:md5,80496e451401dbc0269ec404801a90e3" - ] + { + "versions_flye": [ + [ + "FLYE", + "flye", + "2.9.5-b1801" + ] + ] + } ], "meta": { - "nf-test": "0.9.0", - "nextflow": "24.04.4" + "nf-test": "0.9.3", + "nextflow": "25.10.2" }, - "timestamp": "2024-09-16T13:33:16.267658" + "timestamp": "2026-01-22T13:54:05.452301952" }, "flye_nano_corr": { "content": [ @@ -67,15 +79,21 @@ "test.params.json:md5,54b576cb6d4d27656878a7fd3657bde9" ] ], - [ - "versions.yml:md5,80496e451401dbc0269ec404801a90e3" - ] + { + "versions_flye": [ + [ + "FLYE", + "flye", + "2.9.5-b1801" + ] + ] + } ], "meta": { - "nf-test": "0.9.0", - "nextflow": "24.04.4" + "nf-test": "0.9.3", + "nextflow": "25.10.2" }, - "timestamp": "2024-09-16T13:44:28.522592" + "timestamp": "2026-01-22T13:58:20.270302808" }, "flye_nano_hq": { "content": [ @@ -93,15 +111,21 @@ "test.params.json:md5,54b576cb6d4d27656878a7fd3657bde9" ] ], - [ - "versions.yml:md5,80496e451401dbc0269ec404801a90e3" - ] + { + "versions_flye": [ + [ + "FLYE", + "flye", + "2.9.5-b1801" + ] + ] + } ], "meta": { - "nf-test": "0.9.0", - "nextflow": "24.04.4" + "nf-test": "0.9.3", + "nextflow": "25.10.2" }, - "timestamp": "2024-09-16T13:46:35.912198" + "timestamp": "2026-01-22T14:00:09.061930982" }, "flye_pacbio_raw - stub": { "content": [ @@ -155,7 +179,11 @@ ] ], "6": [ - "versions.yml:md5,80496e451401dbc0269ec404801a90e3" + [ + "FLYE", + "flye", + "2.9.5-b1801" + ] ], "fasta": [ [ @@ -205,20 +233,28 @@ "test.assembly_info.txt:md5,e3aec731279050302fc8d6f126b3030e" ] ], - "versions": [ - "versions.yml:md5,80496e451401dbc0269ec404801a90e3" + "versions_flye": [ + [ + "FLYE", + "flye", + "2.9.5-b1801" + ] ] }, { - "FLYE": { - "flye": "2.9.5-b1801" - } + "versions_flye": [ + [ + "FLYE", + "flye", + "2.9.5-b1801" + ] + ] } ], "meta": { - "nf-test": "0.9.0", - "nextflow": "24.04.4" + "nf-test": "0.9.3", + "nextflow": "25.10.2" }, - "timestamp": "2024-09-12T09:07:05.234775" + "timestamp": "2026-01-22T14:00:16.619909915" } } \ No newline at end of file diff --git a/modules/nf-core/minimap2/align/main.nf b/modules/nf-core/minimap2/align/main.nf index 50e3ecf9..b5748313 100644 --- a/modules/nf-core/minimap2/align/main.nf +++ b/modules/nf-core/minimap2/align/main.nf @@ -19,7 +19,7 @@ process MINIMAP2_ALIGN { tuple val(meta), path("*.paf") , optional: true, emit: paf tuple val(meta), path("*.bam") , optional: true, emit: bam tuple val(meta), path("*.bam.${bam_index_extension}"), optional: true, emit: index - path "versions.yml" , emit: versions + tuple val("${task.process}"), val("minimap2"), eval("minimap2 --version"), topic: versions, emit: versions_minimap2 when: task.ext.when == null || task.ext.when @@ -37,25 +37,17 @@ process MINIMAP2_ALIGN { def bam_input = "${reads.extension}".matches('sam|bam|cram') def samtools_reset_fastq = bam_input ? "samtools reset --threads ${task.cpus-1} $args3 $reads | samtools fastq --threads ${task.cpus-1} $args4 |" : '' def query = bam_input ? "-" : reads - def target = reference ?: (bam_input ? error("BAM input requires reference") : reads) - + def target = reference ?: (bam_input ? error("Error: minimap2/align BAM input mode requires reference") : reads) """ $samtools_reset_fastq \\ minimap2 \\ - $args \\ - -t $task.cpus \\ - $target \\ - $query \\ - $cigar_paf \\ - $set_cigar_bam \\ - $bam_output - - - cat <<-END_VERSIONS > versions.yml - "${task.process}": - minimap2: \$(minimap2 --version 2>&1) - samtools: \$(echo \$(samtools --version 2>&1) | sed 's/^.*samtools //; s/Using.*\$//') - END_VERSIONS + ${args} \\ + -t ${task.cpus} \\ + ${target} \\ + ${query} \\ + ${cigar_paf} \\ + ${set_cigar_bam} \\ + ${bam_output} """ stub: @@ -63,15 +55,11 @@ process MINIMAP2_ALIGN { def output_file = bam_format ? "${prefix}.bam" : "${prefix}.paf" def bam_index = bam_index_extension ? "touch ${prefix}.bam.${bam_index_extension}" : "" def bam_input = "${reads.extension}".matches('sam|bam|cram') - def target = reference ?: (bam_input ? error("BAM input requires reference") : reads) - + if(bam_input && !reference) { + error("Error: minimap2/align BAM input mode requires reference!") + } """ touch $output_file ${bam_index} - - cat <<-END_VERSIONS > versions.yml - "${task.process}": - minimap2: \$(minimap2 --version 2>&1) - END_VERSIONS """ } diff --git a/modules/nf-core/minimap2/align/meta.yml b/modules/nf-core/minimap2/align/meta.yml index b501526e..40bb20ad 100644 --- a/modules/nf-core/minimap2/align/meta.yml +++ b/modules/nf-core/minimap2/align/meta.yml @@ -85,13 +85,27 @@ output: description: BAM alignment index pattern: "*.bam.*" ontologies: [] + versions_minimap2: + - - ${task.process}: + type: string + description: The process name + - minimap2: + type: string + description: The tool name + - minimap2 --version: + type: eval + description: The tool version +topics: 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 name + - minimap2: + type: string + description: The tool name + - minimap2 --version: + type: eval + description: The tool version authors: - "@heuermh" - "@sofstam" diff --git a/modules/nf-core/minimap2/align/tests/main.nf.test b/modules/nf-core/minimap2/align/tests/main.nf.test index 65061404..34597d6f 100644 --- a/modules/nf-core/minimap2/align/tests/main.nf.test +++ b/modules/nf-core/minimap2/align/tests/main.nf.test @@ -36,7 +36,7 @@ nextflow_process { { assert snapshot( bam(process.out.bam[0][1]).getHeader(), bam(process.out.bam[0][1]).getReadsMD5(), - process.out.versions + process.out.findAll { key, val -> key.startsWith("versions_") } ).match() } ) } @@ -71,7 +71,7 @@ nextflow_process { bam(process.out.bam[0][1]).getHeader(), bam(process.out.bam[0][1]).getReadsMD5(), file(process.out.index[0][1]).name, - process.out.versions + process.out.findAll { key, val -> key.startsWith("versions_") } ).match() } ) } @@ -108,7 +108,7 @@ nextflow_process { { assert snapshot( bam(process.out.bam[0][1]).getHeader(), bam(process.out.bam[0][1]).getReadsMD5(), - process.out.versions + process.out.findAll { key, val -> key.startsWith("versions_") } ).match() } ) } @@ -142,7 +142,7 @@ nextflow_process { { assert snapshot( bam(process.out.bam[0][1]).getHeader(), bam(process.out.bam[0][1]).getReadsMD5(), - process.out.versions + process.out.findAll { key, val -> key.startsWith("versions_") } ).match() } ) } @@ -176,7 +176,7 @@ nextflow_process { { assert snapshot( bam(process.out.bam[0][1]).getHeader(), bam(process.out.bam[0][1]).getReadsMD5(), - process.out.versions + process.out.findAll { key, val -> key.startsWith("versions_") } ).match() } ) } @@ -211,7 +211,7 @@ nextflow_process { bam(process.out.bam[0][1]).getHeader(), bam(process.out.bam[0][1]).getReadsMD5(), file(process.out.index[0][1]).name, - process.out.versions + process.out.findAll { key, val -> key.startsWith("versions_") } ).match() } ) } diff --git a/modules/nf-core/minimap2/align/tests/main.nf.test.snap b/modules/nf-core/minimap2/align/tests/main.nf.test.snap index 89f20336..93e0eb3b 100644 --- a/modules/nf-core/minimap2/align/tests/main.nf.test.snap +++ b/modules/nf-core/minimap2/align/tests/main.nf.test.snap @@ -9,15 +9,21 @@ ], "5d426b9a5f5b2c54f1d7f1e4c238ae94", "test.bam.bai", - [ - "versions.yml:md5,660fcf8ff66d4dce2045ffa0e325eed8" - ] + { + "versions_minimap2": [ + [ + "MINIMAP2_ALIGN", + "minimap2", + "2.29-r1283" + ] + ] + } ], "meta": { - "nf-test": "0.9.2", - "nextflow": "24.10.5" + "nf-test": "0.9.3", + "nextflow": "25.10.2" }, - "timestamp": "2025-04-22T14:48:23.829797899" + "timestamp": "2026-01-22T15:02:10.851485367" }, "sarscov2 - bam, fasta, true, 'bai', false, false - stub": { "content": [ @@ -44,7 +50,11 @@ ] ], "3": [ - "versions.yml:md5,231f31609e2b72661af6a11b7aee3cfe" + [ + "MINIMAP2_ALIGN", + "minimap2", + "2.29-r1283" + ] ], "bam": [ [ @@ -67,16 +77,20 @@ "paf": [ ], - "versions": [ - "versions.yml:md5,231f31609e2b72661af6a11b7aee3cfe" + "versions_minimap2": [ + [ + "MINIMAP2_ALIGN", + "minimap2", + "2.29-r1283" + ] ] } ], "meta": { - "nf-test": "0.9.2", - "nextflow": "24.10.5" + "nf-test": "0.9.3", + "nextflow": "25.10.2" }, - "timestamp": "2025-04-22T14:48:54.665655242" + "timestamp": "2026-01-22T15:02:56.708796666" }, "sarscov2 - fastq, fasta, true, 'bai', false, false - stub": { "content": [ @@ -103,7 +117,11 @@ ] ], "3": [ - "versions.yml:md5,231f31609e2b72661af6a11b7aee3cfe" + [ + "MINIMAP2_ALIGN", + "minimap2", + "2.29-r1283" + ] ], "bam": [ [ @@ -126,16 +144,20 @@ "paf": [ ], - "versions": [ - "versions.yml:md5,231f31609e2b72661af6a11b7aee3cfe" + "versions_minimap2": [ + [ + "MINIMAP2_ALIGN", + "minimap2", + "2.29-r1283" + ] ] } ], "meta": { - "nf-test": "0.9.2", - "nextflow": "24.10.5" + "nf-test": "0.9.3", + "nextflow": "25.10.2" }, - "timestamp": "2025-04-22T14:48:38.492212433" + "timestamp": "2026-01-22T15:02:32.614463827" }, "sarscov2 - fastq, fasta, false, [], false, false - stub": { "content": [ @@ -156,7 +178,11 @@ ], "3": [ - "versions.yml:md5,231f31609e2b72661af6a11b7aee3cfe" + [ + "MINIMAP2_ALIGN", + "minimap2", + "2.29-r1283" + ] ], "bam": [ @@ -173,16 +199,20 @@ "test.paf:md5,d41d8cd98f00b204e9800998ecf8427e" ] ], - "versions": [ - "versions.yml:md5,231f31609e2b72661af6a11b7aee3cfe" + "versions_minimap2": [ + [ + "MINIMAP2_ALIGN", + "minimap2", + "2.29-r1283" + ] ] } ], "meta": { - "nf-test": "0.9.2", - "nextflow": "24.10.5" + "nf-test": "0.9.3", + "nextflow": "25.10.2" }, - "timestamp": "2025-04-22T14:48:43.879647142" + "timestamp": "2026-01-22T15:02:40.02163098" }, "sarscov2 - fastq, fasta, true, [], false, false - stub": { "content": [ @@ -203,7 +233,11 @@ ], "3": [ - "versions.yml:md5,231f31609e2b72661af6a11b7aee3cfe" + [ + "MINIMAP2_ALIGN", + "minimap2", + "2.29-r1283" + ] ], "bam": [ [ @@ -220,16 +254,20 @@ "paf": [ ], - "versions": [ - "versions.yml:md5,231f31609e2b72661af6a11b7aee3cfe" + "versions_minimap2": [ + [ + "MINIMAP2_ALIGN", + "minimap2", + "2.29-r1283" + ] ] } ], "meta": { - "nf-test": "0.9.2", - "nextflow": "24.10.5" + "nf-test": "0.9.3", + "nextflow": "25.10.2" }, - "timestamp": "2025-04-22T14:48:33.262333471" + "timestamp": "2026-01-22T15:02:25.102539679" }, "sarscov2 - [fastq1, fastq2], fasta, true, false, false": { "content": [ @@ -240,15 +278,21 @@ "@PG\tID:samtools\tPN:samtools\tPP:minimap2\tVN:1.21\tCL:samtools sort -@ 1 -o test.bam" ], "1bc392244f228bf52cf0b5a8f6a654c9", - [ - "versions.yml:md5,660fcf8ff66d4dce2045ffa0e325eed8" - ] + { + "versions_minimap2": [ + [ + "MINIMAP2_ALIGN", + "minimap2", + "2.29-r1283" + ] + ] + } ], "meta": { - "nf-test": "0.9.2", - "nextflow": "24.10.5" + "nf-test": "0.9.3", + "nextflow": "25.10.2" }, - "timestamp": "2025-04-22T14:48:07.571731983" + "timestamp": "2026-01-22T15:01:46.456636022" }, "sarscov2 - fastq, fasta, true, [], false, false": { "content": [ @@ -259,15 +303,21 @@ "@PG\tID:samtools\tPN:samtools\tPP:minimap2\tVN:1.21\tCL:samtools sort -@ 1 -o test.bam" ], "f194745c0ccfcb2a9c0aee094a08750", - [ - "versions.yml:md5,660fcf8ff66d4dce2045ffa0e325eed8" - ] + { + "versions_minimap2": [ + [ + "MINIMAP2_ALIGN", + "minimap2", + "2.29-r1283" + ] + ] + } ], "meta": { - "nf-test": "0.9.2", - "nextflow": "24.10.5" + "nf-test": "0.9.3", + "nextflow": "25.10.2" }, - "timestamp": "2025-04-22T14:47:56.497792473" + "timestamp": "2026-01-22T15:01:30.525133177" }, "sarscov2 - fastq, fasta, true, 'bai', false, false": { "content": [ @@ -279,15 +329,21 @@ ], "f194745c0ccfcb2a9c0aee094a08750", "test.bam.bai", - [ - "versions.yml:md5,660fcf8ff66d4dce2045ffa0e325eed8" - ] + { + "versions_minimap2": [ + [ + "MINIMAP2_ALIGN", + "minimap2", + "2.29-r1283" + ] + ] + } ], "meta": { - "nf-test": "0.9.2", - "nextflow": "24.10.5" + "nf-test": "0.9.3", + "nextflow": "25.10.2" }, - "timestamp": "2025-04-22T14:48:01.888544427" + "timestamp": "2026-01-22T15:01:38.84829029" }, "sarscov2 - bam, fasta, true, [], false, false": { "content": [ @@ -298,15 +354,21 @@ "@PG\tID:samtools\tPN:samtools\tPP:minimap2\tVN:1.21\tCL:samtools sort -@ 1 -o test.bam" ], "5d426b9a5f5b2c54f1d7f1e4c238ae94", - [ - "versions.yml:md5,660fcf8ff66d4dce2045ffa0e325eed8" - ] + { + "versions_minimap2": [ + [ + "MINIMAP2_ALIGN", + "minimap2", + "2.29-r1283" + ] + ] + } ], "meta": { - "nf-test": "0.9.2", - "nextflow": "24.10.5" + "nf-test": "0.9.3", + "nextflow": "25.10.2" }, - "timestamp": "2025-04-22T14:48:18.376062313" + "timestamp": "2026-01-22T15:02:02.351060285" }, "sarscov2 - bam, fasta, true, [], false, false - stub": { "content": [ @@ -327,7 +389,11 @@ ], "3": [ - "versions.yml:md5,231f31609e2b72661af6a11b7aee3cfe" + [ + "MINIMAP2_ALIGN", + "minimap2", + "2.29-r1283" + ] ], "bam": [ [ @@ -344,16 +410,20 @@ "paf": [ ], - "versions": [ - "versions.yml:md5,231f31609e2b72661af6a11b7aee3cfe" + "versions_minimap2": [ + [ + "MINIMAP2_ALIGN", + "minimap2", + "2.29-r1283" + ] ] } ], "meta": { - "nf-test": "0.9.2", - "nextflow": "24.10.5" + "nf-test": "0.9.3", + "nextflow": "25.10.2" }, - "timestamp": "2025-04-22T14:48:49.268693724" + "timestamp": "2026-01-22T15:02:47.579634041" }, "sarscov2 - fastq, [], true, false, false": { "content": [ @@ -463,14 +533,20 @@ "@PG\tID:samtools\tPN:samtools\tPP:minimap2\tVN:1.21\tCL:samtools sort -@ 1 -o test.bam" ], "16c1c651f8ec67383bcdee3c55aed94f", - [ - "versions.yml:md5,660fcf8ff66d4dce2045ffa0e325eed8" - ] + { + "versions_minimap2": [ + [ + "MINIMAP2_ALIGN", + "minimap2", + "2.29-r1283" + ] + ] + } ], "meta": { - "nf-test": "0.9.2", - "nextflow": "24.10.5" + "nf-test": "0.9.3", + "nextflow": "25.10.2" }, - "timestamp": "2025-04-22T14:48:12.942360555" + "timestamp": "2026-01-22T15:01:54.090788633" } } \ No newline at end of file diff --git a/modules/nf-core/samtools/faidx/main.nf b/modules/nf-core/samtools/faidx/main.nf index 57a03497..97bfb578 100644 --- a/modules/nf-core/samtools/faidx/main.nf +++ b/modules/nf-core/samtools/faidx/main.nf @@ -8,8 +8,7 @@ process SAMTOOLS_FAIDX { 'biocontainers/samtools:1.22.1--h96c455f_0' }" input: - tuple val(meta), path(fasta) - tuple val(meta2), path(fai) + tuple val(meta), path(fasta), path(fai) val get_sizes output: diff --git a/modules/nf-core/samtools/faidx/meta.yml b/modules/nf-core/samtools/faidx/meta.yml index 163c3015..80aae1da 100644 --- a/modules/nf-core/samtools/faidx/meta.yml +++ b/modules/nf-core/samtools/faidx/meta.yml @@ -1,5 +1,6 @@ name: samtools_faidx -description: Index FASTA file, and optionally generate a file of chromosome sizes +description: Index FASTA file, and optionally generate a file of chromosome + sizes keywords: - index - fasta @@ -14,7 +15,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: @@ -27,11 +29,6 @@ input: description: FASTA file pattern: "*.{fa,fasta}" ontologies: [] - - - meta2: - type: map - description: | - Groovy Map containing reference information - e.g. [ id:'test' ] - fai: type: file description: FASTA index file @@ -40,7 +37,6 @@ input: - get_sizes: type: boolean description: use cut to get the sizes of the index (true) or not (false) - output: fa: - - meta: @@ -94,9 +90,8 @@ output: type: string description: The tool name - "samtools version | sed '1!d;s/.* //'": - type: string + type: eval description: The command used to generate the version of the tool - topics: versions: - - ${task.process}: @@ -106,7 +101,7 @@ topics: type: string description: The tool name - "samtools version | sed '1!d;s/.* //'": - type: string + type: eval description: The command used to generate the version of the tool authors: - "@drpatelh" diff --git a/modules/nf-core/samtools/faidx/tests/main.nf.test b/modules/nf-core/samtools/faidx/tests/main.nf.test index 02ba5040..9a86db86 100644 --- a/modules/nf-core/samtools/faidx/tests/main.nf.test +++ b/modules/nf-core/samtools/faidx/tests/main.nf.test @@ -18,10 +18,12 @@ nextflow_process { } process { """ - input[0] = [ [ id:'test', single_end:false ], // meta map - file(params.modules_testdata_base_path + 'genomics/sarscov2/genome/genome.fasta', checkIfExists: true) ] - input[1] = [[],[]] - input[2] = false + input[0] = [ + [ id:'test' ], + file(params.modules_testdata_base_path + 'genomics/sarscov2/genome/genome.fasta', checkIfExists: true), + [] + ] + input[1] = false """ } } @@ -29,7 +31,7 @@ nextflow_process { then { assert process.success assertAll( - { assert snapshot(process.out).match()} + { assert snapshot(sanitizeOutput(process.out)).match()} ) } } @@ -42,10 +44,12 @@ nextflow_process { } process { """ - input[0] = [ [ id:'test', single_end:false ], // meta map - file(params.modules_testdata_base_path + 'genomics/sarscov2/genome/genome.fasta.gz', checkIfExists: true)] - input[1] = [[],[]] - input[2] = false + input[0] = [ + [ id:'test' ], + file(params.modules_testdata_base_path + 'genomics/sarscov2/genome/genome.fasta.gz', checkIfExists: true), + [] + ] + input[1] = false """ } } @@ -53,7 +57,7 @@ nextflow_process { then { assert process.success assertAll( - { assert snapshot(process.out).match()} + { assert snapshot(sanitizeOutput(process.out)).match()} ) } } @@ -66,11 +70,12 @@ nextflow_process { } process { """ - input[0] = [ [ id:'test', single_end:false ], // meta map - file(params.modules_testdata_base_path + 'genomics/sarscov2/genome/genome.fasta', checkIfExists: true) ] - input[1] = [ [ id:'test', single_end:false ], // meta map - file(params.modules_testdata_base_path + 'genomics/sarscov2/genome/genome.fasta.fai', checkIfExists: true) ] - input[2] = false + input[0] = [ + [ id:'test' ], + 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[1] = false """ } } @@ -78,7 +83,7 @@ nextflow_process { then { assert process.success assertAll( - { assert snapshot(process.out).match()} + { assert snapshot(sanitizeOutput(process.out)).match()} ) } } @@ -92,11 +97,12 @@ nextflow_process { } process { """ - input[0] = [ [ id:'test', single_end:false ], // meta map - file(params.modules_testdata_base_path + 'genomics/sarscov2/genome/genome.fasta', checkIfExists: true) ] - input[1] = [ [ id:'test', single_end:false ], // meta map - file(params.modules_testdata_base_path + 'genomics/sarscov2/genome/genome.fasta.fai', checkIfExists: true) ] - input[2] = false + input[0] = [ + [ id:'test' ], + 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[1] = false """ } } @@ -104,7 +110,7 @@ nextflow_process { then { assert process.success assertAll( - { assert snapshot(process.out).match()} + { assert snapshot(sanitizeOutput(process.out)).match()} ) } } @@ -118,10 +124,12 @@ nextflow_process { } process { """ - input[0] = [ [ id:'test', single_end:false ], // meta map - file(params.modules_testdata_base_path + 'genomics/sarscov2/genome/genome.fasta', checkIfExists: true) ] - input[1] = [[],[]] - input[2] = false + input[0] = [ + [ id:'test' ], + file(params.modules_testdata_base_path + 'genomics/sarscov2/genome/genome.fasta', checkIfExists: true), + [] + ] + input[1] = false """ } } @@ -129,7 +137,7 @@ nextflow_process { then { assert process.success assertAll( - { assert snapshot(process.out).match()} + { assert snapshot(sanitizeOutput(process.out)).match()} ) } } @@ -142,12 +150,12 @@ nextflow_process { } process { """ - input[0] = Channel.of([ - [ id:'test' ], // meta map - file(params.modules_testdata_base_path + 'genomics/sarscov2/genome/genome.fasta', checkIfExists: true) - ]) - input[1] = [[],[]] - input[2] = true + input[0] = [ + [ id:'test' ], + file(params.modules_testdata_base_path + 'genomics/sarscov2/genome/genome.fasta', checkIfExists: true), + [] + ] + input[1] = true """ } } @@ -155,7 +163,7 @@ nextflow_process { then { assert process.success assertAll( - { assert snapshot(process.out).match()} + { assert snapshot(sanitizeOutput(process.out)).match()} ) } } @@ -168,12 +176,12 @@ nextflow_process { } process { """ - input[0] = Channel.of([ - [ id:'test' ], // meta map - file(params.modules_testdata_base_path + 'genomics/sarscov2/genome/genome.fasta.gz', checkIfExists: true) - ]) - input[1] = [[],[]] - input[2] = true + input[0] = [ + [ id:'test' ], + file(params.modules_testdata_base_path + 'genomics/sarscov2/genome/genome.fasta.gz', checkIfExists: true), + [] + ] + input[1] = true """ } } @@ -181,7 +189,7 @@ nextflow_process { then { assert process.success assertAll( - { assert snapshot(process.out).match()} + { assert snapshot(sanitizeOutput(process.out)).match()} ) } } @@ -196,12 +204,12 @@ nextflow_process { } process { """ - input[0] = Channel.of([ - [ id:'test' ], // meta map - file(params.modules_testdata_base_path + 'genomics/sarscov2/genome/genome.fasta', checkIfExists: true) - ]) - input[1] = [[],[]] - input[2] = true + input[0] = [ + [ id:'test' ], + file(params.modules_testdata_base_path + 'genomics/sarscov2/genome/genome.fasta', checkIfExists: true), + [] + ] + input[1] = true """ } } @@ -209,7 +217,7 @@ nextflow_process { then { assert process.success assertAll( - { assert snapshot(process.out).match()} + { assert snapshot(sanitizeOutput(process.out)).match()} ) } } @@ -224,12 +232,12 @@ nextflow_process { } process { """ - input[0] = Channel.of([ - [ id:'test' ], // meta map - file(params.modules_testdata_base_path + 'genomics/sarscov2/genome/genome.fasta.gz', checkIfExists: true) - ]) - input[1] = [[],[]] - input[2] = true + input[0] = [ + [ id:'test' ], + file(params.modules_testdata_base_path + 'genomics/sarscov2/genome/genome.fasta.gz', checkIfExists: true), + [] + ] + input[1] = true """ } } @@ -237,7 +245,7 @@ nextflow_process { then { assert process.success assertAll( - { assert snapshot(process.out).match()} + { assert snapshot(sanitizeOutput(process.out)).match()} ) } } diff --git a/modules/nf-core/samtools/faidx/tests/main.nf.test.snap b/modules/nf-core/samtools/faidx/tests/main.nf.test.snap index 565d20e7..41697444 100644 --- a/modules/nf-core/samtools/faidx/tests/main.nf.test.snap +++ b/modules/nf-core/samtools/faidx/tests/main.nf.test.snap @@ -2,39 +2,13 @@ "test_samtools_faidx": { "content": [ { - "0": [ - - ], - "1": [ - - ], - "2": [ - [ - { - "id": "test", - "single_end": false - }, - "genome.fasta.fai:md5,9da2a56e2853dc8c0b86a9e7229c9fe5" - ] - ], - "3": [ - - ], - "4": [ - [ - "SAMTOOLS_FAIDX", - "samtools", - "1.22.1" - ] - ], "fa": [ ], "fai": [ [ { - "id": "test", - "single_end": false + "id": "test" }, "genome.fasta.fai:md5,9da2a56e2853dc8c0b86a9e7229c9fe5" ] @@ -56,47 +30,13 @@ ], "meta": { "nf-test": "0.9.3", - "nextflow": "25.10.2" + "nextflow": "25.10.3" }, - "timestamp": "2025-12-23T14:02:40.159309157" + "timestamp": "2026-02-10T15:39:12.541649151" }, "test_samtools_faidx_get_sizes_bgzip - stub": { "content": [ { - "0": [ - - ], - "1": [ - [ - { - "id": "test" - }, - "genome.fasta.gz.sizes:md5,d41d8cd98f00b204e9800998ecf8427e" - ] - ], - "2": [ - [ - { - "id": "test" - }, - "genome.fasta.gz.fai:md5,d41d8cd98f00b204e9800998ecf8427e" - ] - ], - "3": [ - [ - { - "id": "test" - }, - "genome.fasta.gz.gzi:md5,d41d8cd98f00b204e9800998ecf8427e" - ] - ], - "4": [ - [ - "SAMTOOLS_FAIDX", - "samtools", - "1.22.1" - ] - ], "fa": [ ], @@ -135,42 +75,13 @@ ], "meta": { "nf-test": "0.9.3", - "nextflow": "25.10.2" + "nextflow": "25.10.3" }, - "timestamp": "2025-12-23T14:03:39.550619177" + "timestamp": "2026-02-10T15:41:44.040426987" }, "test_samtools_faidx_get_sizes": { "content": [ { - "0": [ - - ], - "1": [ - [ - { - "id": "test" - }, - "genome.fasta.sizes:md5,a57c401f27ae5133823fb09fb21c8a3c" - ] - ], - "2": [ - [ - { - "id": "test" - }, - "genome.fasta.fai:md5,9da2a56e2853dc8c0b86a9e7229c9fe5" - ] - ], - "3": [ - - ], - "4": [ - [ - "SAMTOOLS_FAIDX", - "samtools", - "1.22.1" - ] - ], "fa": [ ], @@ -204,52 +115,20 @@ ], "meta": { "nf-test": "0.9.3", - "nextflow": "25.10.2" + "nextflow": "25.10.3" }, - "timestamp": "2025-12-23T14:03:16.844965756" + "timestamp": "2026-02-10T15:47:03.653912015" }, "test_samtools_faidx_bgzip": { "content": [ { - "0": [ - - ], - "1": [ - - ], - "2": [ - [ - { - "id": "test", - "single_end": false - }, - "genome.fasta.gz.fai:md5,9da2a56e2853dc8c0b86a9e7229c9fe5" - ] - ], - "3": [ - [ - { - "id": "test", - "single_end": false - }, - "genome.fasta.gz.gzi:md5,7dea362b3fac8e00956a4952a3d4f474" - ] - ], - "4": [ - [ - "SAMTOOLS_FAIDX", - "samtools", - "1.22.1" - ] - ], "fa": [ ], "fai": [ [ { - "id": "test", - "single_end": false + "id": "test" }, "genome.fasta.gz.fai:md5,9da2a56e2853dc8c0b86a9e7229c9fe5" ] @@ -257,8 +136,7 @@ "gzi": [ [ { - "id": "test", - "single_end": false + "id": "test" }, "genome.fasta.gz.gzi:md5,7dea362b3fac8e00956a4952a3d4f474" ] @@ -277,43 +155,17 @@ ], "meta": { "nf-test": "0.9.3", - "nextflow": "25.10.2" + "nextflow": "25.10.3" }, - "timestamp": "2025-12-23T14:02:47.301476131" + "timestamp": "2026-02-10T15:50:04.023566795" }, "test_samtools_faidx_fasta": { "content": [ { - "0": [ - [ - { - "id": "test", - "single_end": false - }, - "extract.fa:md5,6a0774a0ad937ba0bfd2ac7457d90f36" - ] - ], - "1": [ - - ], - "2": [ - - ], - "3": [ - - ], - "4": [ - [ - "SAMTOOLS_FAIDX", - "samtools", - "1.22.1" - ] - ], "fa": [ [ { - "id": "test", - "single_end": false + "id": "test" }, "extract.fa:md5,6a0774a0ad937ba0bfd2ac7457d90f36" ] @@ -338,42 +190,13 @@ ], "meta": { "nf-test": "0.9.3", - "nextflow": "25.10.2" + "nextflow": "25.10.3" }, - "timestamp": "2025-12-23T09:44:40.559583279" + "timestamp": "2026-02-10T15:39:23.529404162" }, "test_samtools_faidx_get_sizes - stub": { "content": [ { - "0": [ - - ], - "1": [ - [ - { - "id": "test" - }, - "genome.fasta.sizes:md5,d41d8cd98f00b204e9800998ecf8427e" - ] - ], - "2": [ - [ - { - "id": "test" - }, - "genome.fasta.fai:md5,d41d8cd98f00b204e9800998ecf8427e" - ] - ], - "3": [ - - ], - "4": [ - [ - "SAMTOOLS_FAIDX", - "samtools", - "1.22.1" - ] - ], "fa": [ ], @@ -407,43 +230,17 @@ ], "meta": { "nf-test": "0.9.3", - "nextflow": "25.10.2" + "nextflow": "25.10.3" }, - "timestamp": "2025-12-23T14:03:31.989929281" + "timestamp": "2026-02-10T15:41:39.039834304" }, "test_samtools_faidx_stub_fasta": { "content": [ { - "0": [ - [ - { - "id": "test", - "single_end": false - }, - "extract.fa:md5,d41d8cd98f00b204e9800998ecf8427e" - ] - ], - "1": [ - - ], - "2": [ - - ], - "3": [ - - ], - "4": [ - [ - "SAMTOOLS_FAIDX", - "samtools", - "1.22.1" - ] - ], "fa": [ [ { - "id": "test", - "single_end": false + "id": "test" }, "extract.fa:md5,d41d8cd98f00b204e9800998ecf8427e" ] @@ -468,46 +265,20 @@ ], "meta": { "nf-test": "0.9.3", - "nextflow": "25.10.2" + "nextflow": "25.10.3" }, - "timestamp": "2025-12-23T09:44:48.295693103" + "timestamp": "2026-02-10T15:39:28.961701609" }, "test_samtools_faidx_stub_fai": { "content": [ { - "0": [ - - ], - "1": [ - - ], - "2": [ - [ - { - "id": "test", - "single_end": false - }, - "genome.fasta.fai:md5,d41d8cd98f00b204e9800998ecf8427e" - ] - ], - "3": [ - - ], - "4": [ - [ - "SAMTOOLS_FAIDX", - "samtools", - "1.22.1" - ] - ], "fa": [ ], "fai": [ [ { - "id": "test", - "single_end": false + "id": "test" }, "genome.fasta.fai:md5,d41d8cd98f00b204e9800998ecf8427e" ] @@ -529,47 +300,13 @@ ], "meta": { "nf-test": "0.9.3", - "nextflow": "25.10.2" + "nextflow": "25.10.3" }, - "timestamp": "2025-12-23T14:03:09.784289542" + "timestamp": "2026-02-10T15:39:34.471028474" }, "test_samtools_faidx_get_sizes_bgzip": { "content": [ { - "0": [ - - ], - "1": [ - [ - { - "id": "test" - }, - "genome.fasta.gz.sizes:md5,a57c401f27ae5133823fb09fb21c8a3c" - ] - ], - "2": [ - [ - { - "id": "test" - }, - "genome.fasta.gz.fai:md5,9da2a56e2853dc8c0b86a9e7229c9fe5" - ] - ], - "3": [ - [ - { - "id": "test" - }, - "genome.fasta.gz.gzi:md5,7dea362b3fac8e00956a4952a3d4f474" - ] - ], - "4": [ - [ - "SAMTOOLS_FAIDX", - "samtools", - "1.22.1" - ] - ], "fa": [ ], @@ -608,8 +345,8 @@ ], "meta": { "nf-test": "0.9.3", - "nextflow": "25.10.2" + "nextflow": "25.10.3" }, - "timestamp": "2025-12-23T14:03:24.814967939" + "timestamp": "2026-02-10T15:39:45.439016495" } } \ No newline at end of file diff --git a/modules/nf-core/samtools/fastq/main.nf b/modules/nf-core/samtools/fastq/main.nf index d2a8a750..922dbec3 100644 --- a/modules/nf-core/samtools/fastq/main.nf +++ b/modules/nf-core/samtools/fastq/main.nf @@ -16,7 +16,7 @@ process SAMTOOLS_FASTQ { tuple val(meta), path("*_interleaved.fastq") , optional:true, emit: interleaved tuple val(meta), path("*_singleton.fastq.gz") , optional:true, emit: singleton tuple val(meta), path("*_other.fastq.gz") , optional:true, emit: other - path "versions.yml" , emit: versions + 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 @@ -36,11 +36,6 @@ process SAMTOOLS_FASTQ { -0 ${prefix}_other.fastq.gz \\ $input \\ $output - - cat <<-END_VERSIONS > versions.yml - "${task.process}": - samtools: \$(echo \$(samtools --version 2>&1) | sed 's/^.*samtools //; s/Using.*\$//') - END_VERSIONS """ stub: @@ -51,10 +46,5 @@ process SAMTOOLS_FASTQ { """ ${output} echo | gzip > ${prefix}_other.fastq.gz - - 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/fastq/meta.yml b/modules/nf-core/samtools/fastq/meta.yml index 9a5bd42f..cab17ffa 100644 --- a/modules/nf-core/samtools/fastq/meta.yml +++ b/modules/nf-core/samtools/fastq/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: @@ -39,8 +40,8 @@ output: e.g. [ id:'test', single_end:false ] - "*_{1,2}.fastq.gz": type: file - description: Compressed FASTQ file(s) with reads with either the READ1 or - READ2 flag set in separate files. + description: Compressed FASTQ file(s) with reads with either the READ1 + or READ2 flag set in separate files. pattern: "*_{1,2}.fastq.gz" ontologies: [] interleaved: @@ -51,11 +52,11 @@ output: e.g. [ id:'test', single_end:false ] - "*_interleaved.fastq": type: file - description: Compressed FASTQ file with reads with either the READ1 or READ2 - flag set in a combined file. Needs collated input file. + description: Compressed FASTQ file with reads with either the READ1 or + READ2 flag set in a combined file. Needs collated input file. pattern: "*_interleaved.fastq.gz" ontologies: - - edam: http://edamontology.org/format_3989 # GZIP format + - edam: http://edamontology.org/format_3989 singleton: - - meta: type: map @@ -67,7 +68,7 @@ output: description: Compressed FASTQ file with singleton reads pattern: "*_singleton.fastq.gz" ontologies: - - edam: http://edamontology.org/format_3989 # GZIP format + - edam: http://edamontology.org/format_3989 other: - - meta: type: map @@ -76,18 +77,32 @@ output: e.g. [ id:'test', single_end:false ] - "*_other.fastq.gz": type: file - description: Compressed FASTQ file with reads with either both READ1 and READ2 - flags set or unset + description: Compressed FASTQ file with reads with either both READ1 and + READ2 flags set or unset pattern: "*_other.fastq.gz" ontologies: - - edam: http://edamontology.org/format_3989 # GZIP format + - edam: http://edamontology.org/format_3989 + 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: - - 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 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: - "@priyanka-surana" - "@suzannejin" diff --git a/modules/nf-core/samtools/fastq/tests/main.nf.test b/modules/nf-core/samtools/fastq/tests/main.nf.test index 971ea1d4..46ec7e7c 100644 --- a/modules/nf-core/samtools/fastq/tests/main.nf.test +++ b/modules/nf-core/samtools/fastq/tests/main.nf.test @@ -32,7 +32,7 @@ nextflow_process { { assert snapshot(process.out.interleaved).match("bam_interleaved") }, { assert snapshot(file(process.out.singleton[0][1]).name).match("bam_singleton") }, { assert snapshot(file(process.out.other[0][1]).name).match("bam_other") }, - { assert snapshot(process.out.versions).match("bam_versions") } + { assert snapshot(process.out.findAll { key, val -> key.startsWith('versions') }).match("bam_versions") } ) } } @@ -60,7 +60,7 @@ nextflow_process { { assert snapshot(path(process.out.interleaved[0][1]).readLines()[0..6]).match("bam_interlinterleave_eaved") }, { assert snapshot(process.out.singleton).match("bam_singinterleave_leton") }, { assert snapshot(file(process.out.other[0][1]).name).match("bam_interleave_other") }, - { assert snapshot(process.out.versions).match("bam_verinterleave_sions") } + { assert snapshot(process.out.findAll { key, val -> key.startsWith('versions') }).match("bam_verinterleave_sions") } ) } } @@ -86,7 +86,13 @@ nextflow_process { then { assertAll ( { assert process.success }, - { assert snapshot(process.out).match() } + { assert snapshot( + process.out.fastq, + process.out.interleaved, + process.out.singleton, + process.out.other, + process.out.findAll { key, val -> key.startsWith('versions') } + ).match() } ) } } @@ -112,7 +118,13 @@ nextflow_process { then { assertAll ( { assert process.success }, - { assert snapshot(process.out).match() } + { assert snapshot( + process.out.fastq, + process.out.interleaved, + process.out.singleton, + process.out.other, + process.out.findAll { key, val -> key.startsWith('versions') } + ).match() } ) } } diff --git a/modules/nf-core/samtools/fastq/tests/main.nf.test.snap b/modules/nf-core/samtools/fastq/tests/main.nf.test.snap index 590b886b..17b5ade9 100644 --- a/modules/nf-core/samtools/fastq/tests/main.nf.test.snap +++ b/modules/nf-core/samtools/fastq/tests/main.nf.test.snap @@ -29,86 +29,54 @@ }, "bam - stub": { "content": [ - { - "0": [ - [ - { - "id": "test", - "single_end": false - }, - [ - "test_1.fastq.gz:md5,68b329da9893e34099c7d8ad5cb9c940", - "test_2.fastq.gz:md5,68b329da9893e34099c7d8ad5cb9c940" - ] - ] - ], - "1": [ - - ], - "2": [ - [ - { - "id": "test", - "single_end": false - }, - "test_singleton.fastq.gz:md5,68b329da9893e34099c7d8ad5cb9c940" - ] - ], - "3": [ - [ - { - "id": "test", - "single_end": false - }, - "test_other.fastq.gz:md5,68b329da9893e34099c7d8ad5cb9c940" - ] - ], - "4": [ - "versions.yml:md5,391b9e4150ae63ea50d15781b61712c7" - ], - "fastq": [ - [ - { - "id": "test", - "single_end": false - }, - [ - "test_1.fastq.gz:md5,68b329da9893e34099c7d8ad5cb9c940", - "test_2.fastq.gz:md5,68b329da9893e34099c7d8ad5cb9c940" - ] - ] - ], - "interleaved": [ - - ], - "other": [ + [ + [ + { + "id": "test", + "single_end": false + }, [ - { - "id": "test", - "single_end": false - }, - "test_other.fastq.gz:md5,68b329da9893e34099c7d8ad5cb9c940" + "test_1.fastq.gz:md5,68b329da9893e34099c7d8ad5cb9c940", + "test_2.fastq.gz:md5,68b329da9893e34099c7d8ad5cb9c940" ] - ], - "singleton": [ + ] + ], + [ + + ], + [ + [ + { + "id": "test", + "single_end": false + }, + "test_singleton.fastq.gz:md5,68b329da9893e34099c7d8ad5cb9c940" + ] + ], + [ + [ + { + "id": "test", + "single_end": false + }, + "test_other.fastq.gz:md5,68b329da9893e34099c7d8ad5cb9c940" + ] + ], + { + "versions_samtools": [ [ - { - "id": "test", - "single_end": false - }, - "test_singleton.fastq.gz:md5,68b329da9893e34099c7d8ad5cb9c940" + "SAMTOOLS_FASTQ", + "samtools", + "1.22.1" ] - ], - "versions": [ - "versions.yml:md5,391b9e4150ae63ea50d15781b61712c7" ] } ], "meta": { - "nf-test": "0.9.2", - "nextflow": "25.04.6" + "nf-test": "0.9.3", + "nextflow": "25.10.2" }, - "timestamp": "2025-09-10T13:18:13.675438" + "timestamp": "2026-02-02T17:05:55.514934" }, "bam_fastq": { "content": [ @@ -175,27 +143,39 @@ }, "bam_versions": { "content": [ - [ - "versions.yml:md5,391b9e4150ae63ea50d15781b61712c7" - ] + { + "versions_samtools": [ + [ + "SAMTOOLS_FASTQ", + "samtools", + "1.22.1" + ] + ] + } ], "meta": { - "nf-test": "0.9.2", - "nextflow": "25.04.6" + "nf-test": "0.9.3", + "nextflow": "25.10.2" }, - "timestamp": "2025-09-10T13:18:05.023407" + "timestamp": "2026-02-02T17:05:40.578464" }, "bam_verinterleave_sions": { "content": [ - [ - "versions.yml:md5,391b9e4150ae63ea50d15781b61712c7" - ] + { + "versions_samtools": [ + [ + "SAMTOOLS_FASTQ", + "samtools", + "1.22.1" + ] + ] + } ], "meta": { - "nf-test": "0.9.2", - "nextflow": "25.04.6" + "nf-test": "0.9.3", + "nextflow": "25.10.2" }, - "timestamp": "2025-09-10T13:18:09.490066" + "timestamp": "2026-02-02T17:05:47.62481" }, "bam_singleton": { "content": [ @@ -221,67 +201,44 @@ }, "bam_interleave - stub": { "content": [ + [ + + ], + [ + [ + { + "id": "test", + "single_end": false + }, + "test_interleaved.fastq:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + [ + + ], + [ + [ + { + "id": "test", + "single_end": false + }, + "test_other.fastq.gz:md5,68b329da9893e34099c7d8ad5cb9c940" + ] + ], { - "0": [ - - ], - "1": [ + "versions_samtools": [ [ - { - "id": "test", - "single_end": false - }, - "test_interleaved.fastq:md5,d41d8cd98f00b204e9800998ecf8427e" + "SAMTOOLS_FASTQ", + "samtools", + "1.22.1" ] - ], - "2": [ - - ], - "3": [ - [ - { - "id": "test", - "single_end": false - }, - "test_other.fastq.gz:md5,68b329da9893e34099c7d8ad5cb9c940" - ] - ], - "4": [ - "versions.yml:md5,391b9e4150ae63ea50d15781b61712c7" - ], - "fastq": [ - - ], - "interleaved": [ - [ - { - "id": "test", - "single_end": false - }, - "test_interleaved.fastq:md5,d41d8cd98f00b204e9800998ecf8427e" - ] - ], - "other": [ - [ - { - "id": "test", - "single_end": false - }, - "test_other.fastq.gz:md5,68b329da9893e34099c7d8ad5cb9c940" - ] - ], - "singleton": [ - - ], - "versions": [ - "versions.yml:md5,391b9e4150ae63ea50d15781b61712c7" ] } ], "meta": { - "nf-test": "0.9.2", - "nextflow": "25.04.6" + "nf-test": "0.9.3", + "nextflow": "25.10.2" }, - "timestamp": "2025-09-10T13:18:17.613509" + "timestamp": "2026-02-02T17:06:03.676263" } } \ No newline at end of file diff --git a/modules/nf-core/samtools/flagstat/main.nf b/modules/nf-core/samtools/flagstat/main.nf index f148f56b..0cfb7e87 100644 --- a/modules/nf-core/samtools/flagstat/main.nf +++ b/modules/nf-core/samtools/flagstat/main.nf @@ -12,7 +12,7 @@ process SAMTOOLS_FLAGSTAT { output: tuple val(meta), path("*.flagstat"), emit: flagstat - path "versions.yml" , emit: versions + 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,11 +25,6 @@ process SAMTOOLS_FLAGSTAT { --threads ${task.cpus} \\ $bam \\ > ${prefix}.flagstat - - cat <<-END_VERSIONS > versions.yml - "${task.process}": - samtools: \$(echo \$(samtools --version 2>&1) | sed 's/^.*samtools //; s/Using.*\$//') - END_VERSIONS """ stub: @@ -48,10 +43,5 @@ process SAMTOOLS_FLAGSTAT { 850000 + 0 with mate mapped to a different chr 50000 + 0 with mate mapped to a different chr (mapQ>=5) END_FLAGSTAT - - 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/flagstat/meta.yml b/modules/nf-core/samtools/flagstat/meta.yml index ebbc15f2..8caa1bcc 100644 --- a/modules/nf-core/samtools/flagstat/meta.yml +++ b/modules/nf-core/samtools/flagstat/meta.yml @@ -1,6 +1,6 @@ name: samtools_flagstat -description: Counts the number of alignments in a BAM/CRAM/SAM file for each FLAG - type +description: Counts the number of alignments in a BAM/CRAM/SAM file for each + FLAG type keywords: - stats - mapping @@ -17,7 +17,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: @@ -47,13 +48,27 @@ output: description: File containing samtools flagstat output pattern: "*.{flagstat}" 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: - - 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 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" maintainers: diff --git a/modules/nf-core/samtools/flagstat/tests/main.nf.test.snap b/modules/nf-core/samtools/flagstat/tests/main.nf.test.snap index 0a0a9b15..f5c882da 100644 --- a/modules/nf-core/samtools/flagstat/tests/main.nf.test.snap +++ b/modules/nf-core/samtools/flagstat/tests/main.nf.test.snap @@ -12,7 +12,11 @@ ] ], "1": [ - "versions.yml:md5,bdc0bfb2b0542580e7cd65e80d8570bc" + [ + "SAMTOOLS_FLAGSTAT", + "samtools", + "1.22.1" + ] ], "flagstat": [ [ @@ -23,16 +27,20 @@ "test.flagstat:md5,67394650dbae96d1a4fcc70484822159" ] ], - "versions": [ - "versions.yml:md5,bdc0bfb2b0542580e7cd65e80d8570bc" + "versions_samtools": [ + [ + "SAMTOOLS_FLAGSTAT", + "samtools", + "1.22.1" + ] ] } ], "meta": { - "nf-test": "0.9.2", - "nextflow": "25.04.6" + "nf-test": "0.9.3", + "nextflow": "25.10.3" }, - "timestamp": "2025-09-15T15:02:00.813612" + "timestamp": "2026-02-03T11:14:30.820969684" }, "BAM": { "content": [ @@ -47,7 +55,11 @@ ] ], "1": [ - "versions.yml:md5,bdc0bfb2b0542580e7cd65e80d8570bc" + [ + "SAMTOOLS_FLAGSTAT", + "samtools", + "1.22.1" + ] ], "flagstat": [ [ @@ -58,15 +70,19 @@ "test.flagstat:md5,4f7ffd1e6a5e85524d443209ac97d783" ] ], - "versions": [ - "versions.yml:md5,bdc0bfb2b0542580e7cd65e80d8570bc" + "versions_samtools": [ + [ + "SAMTOOLS_FLAGSTAT", + "samtools", + "1.22.1" + ] ] } ], "meta": { - "nf-test": "0.9.2", - "nextflow": "25.04.6" + "nf-test": "0.9.3", + "nextflow": "25.10.3" }, - "timestamp": "2025-09-15T15:01:55.232954" + "timestamp": "2026-02-03T11:14:25.581619424" } } \ No newline at end of file diff --git a/modules/nf-core/samtools/idxstats/main.nf b/modules/nf-core/samtools/idxstats/main.nf index 9181a1a5..d5b70a7f 100644 --- a/modules/nf-core/samtools/idxstats/main.nf +++ b/modules/nf-core/samtools/idxstats/main.nf @@ -12,7 +12,7 @@ process SAMTOOLS_IDXSTATS { output: tuple val(meta), path("*.idxstats"), emit: idxstats - path "versions.yml" , emit: versions + 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 @@ -27,11 +27,6 @@ process SAMTOOLS_IDXSTATS { --threads ${task.cpus-1} \\ $bam \\ > ${prefix}.idxstats - - cat <<-END_VERSIONS > versions.yml - "${task.process}": - samtools: \$(echo \$(samtools --version 2>&1) | sed 's/^.*samtools //; s/Using.*\$//') - END_VERSIONS """ stub: @@ -39,10 +34,5 @@ process SAMTOOLS_IDXSTATS { """ touch ${prefix}.idxstats - - 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/idxstats/meta.yml b/modules/nf-core/samtools/idxstats/meta.yml index 96d42746..fd153841 100644 --- a/modules/nf-core/samtools/idxstats/meta.yml +++ b/modules/nf-core/samtools/idxstats/meta.yml @@ -17,7 +17,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: @@ -47,13 +48,27 @@ output: description: File containing samtools idxstats output pattern: "*.{idxstats}" 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: - - 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 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" maintainers: diff --git a/modules/nf-core/samtools/idxstats/tests/main.nf.test b/modules/nf-core/samtools/idxstats/tests/main.nf.test index 5fd1fc78..c990cd55 100644 --- a/modules/nf-core/samtools/idxstats/tests/main.nf.test +++ b/modules/nf-core/samtools/idxstats/tests/main.nf.test @@ -25,7 +25,10 @@ nextflow_process { then { assertAll ( { assert process.success }, - { assert snapshot(process.out).match() } + { assert snapshot( + process.out.idxstats, + process.out.findAll { key, val -> key.startsWith('versions') } + ).match() } ) } } @@ -47,7 +50,10 @@ nextflow_process { then { assertAll ( { assert process.success }, - { assert snapshot(process.out).match() } + { assert snapshot( + process.out.idxstats, + process.out.findAll { key, val -> key.startsWith('versions') } + ).match() } ) } }} diff --git a/modules/nf-core/samtools/idxstats/tests/main.nf.test.snap b/modules/nf-core/samtools/idxstats/tests/main.nf.test.snap index d3e785e0..19a54c7c 100644 --- a/modules/nf-core/samtools/idxstats/tests/main.nf.test.snap +++ b/modules/nf-core/samtools/idxstats/tests/main.nf.test.snap @@ -1,72 +1,56 @@ { "bam - stub": { "content": [ + [ + [ + { + "id": "test", + "single_end": false + }, + "test.idxstats:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], { - "0": [ - [ - { - "id": "test", - "single_end": false - }, - "test.idxstats:md5,d41d8cd98f00b204e9800998ecf8427e" - ] - ], - "1": [ - "versions.yml:md5,6da44e5235401559cea62052bdc0197b" - ], - "idxstats": [ + "versions_samtools": [ [ - { - "id": "test", - "single_end": false - }, - "test.idxstats:md5,d41d8cd98f00b204e9800998ecf8427e" + "SAMTOOLS_IDXSTATS", + "samtools", + "1.22.1" ] - ], - "versions": [ - "versions.yml:md5,6da44e5235401559cea62052bdc0197b" ] } ], "meta": { - "nf-test": "0.9.2", - "nextflow": "25.04.6" + "nf-test": "0.9.3", + "nextflow": "25.10.3" }, - "timestamp": "2025-09-10T13:47:35.796569" + "timestamp": "2026-02-02T16:21:46.333090477" }, "bam": { "content": [ + [ + [ + { + "id": "test", + "single_end": false + }, + "test.idxstats:md5,df60a8c8d6621100d05178c93fb053a2" + ] + ], { - "0": [ - [ - { - "id": "test", - "single_end": false - }, - "test.idxstats:md5,df60a8c8d6621100d05178c93fb053a2" - ] - ], - "1": [ - "versions.yml:md5,6da44e5235401559cea62052bdc0197b" - ], - "idxstats": [ + "versions_samtools": [ [ - { - "id": "test", - "single_end": false - }, - "test.idxstats:md5,df60a8c8d6621100d05178c93fb053a2" + "SAMTOOLS_IDXSTATS", + "samtools", + "1.22.1" ] - ], - "versions": [ - "versions.yml:md5,6da44e5235401559cea62052bdc0197b" ] } ], "meta": { - "nf-test": "0.9.2", - "nextflow": "25.04.6" + "nf-test": "0.9.3", + "nextflow": "25.10.3" }, - "timestamp": "2025-09-10T13:47:31.86415" + "timestamp": "2026-02-02T16:21:41.063422521" } } \ No newline at end of file diff --git a/modules/nf-core/samtools/index/main.nf b/modules/nf-core/samtools/index/main.nf index a77ad821..e2a0e56d 100644 --- a/modules/nf-core/samtools/index/main.nf +++ b/modules/nf-core/samtools/index/main.nf @@ -14,7 +14,7 @@ process SAMTOOLS_INDEX { 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("${task.process}"), val('samtools'), eval("samtools version | sed '1!d;s/.* //'"), emit: versions_samtools, topic: versions when: task.ext.when == null || task.ext.when @@ -27,11 +27,6 @@ process 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: @@ -40,10 +35,5 @@ process SAMTOOLS_INDEX { "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 1bed6bca..c6d4ce25 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: @@ -60,13 +61,27 @@ output: description: BAM/CRAM/SAM index file pattern: "*.{bai,crai,sai}" 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: - - 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 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" diff --git a/modules/nf-core/samtools/index/tests/main.nf.test b/modules/nf-core/samtools/index/tests/main.nf.test index ca34fb5c..c96cec86 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.bai, + 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.crai, + process.out.findAll { key, val -> key.startsWith('versions') } + ).match() } ) } } @@ -67,7 +73,7 @@ nextflow_process { { assert process.success }, { assert snapshot( file(process.out.csi[0][1]).name, - process.out.versions + 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.bai, + 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.crai, + 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.csi, + 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 3836c6bf..afc8a1ff 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,b8717818c91b07de87c2a5590bad02e6" - ], - "bai": [ - - ], - "crai": [ - - ], - "csi": [ + "versions_samtools": [ [ - { - "id": "test", - "single_end": false - }, - "test.paired_end.sorted.bam.csi:md5,d41d8cd98f00b204e9800998ecf8427e" + "SAMTOOLS_INDEX", + "samtools", + "1.22.1" ] - ], - "versions": [ - "versions.yml:md5,b8717818c91b07de87c2a5590bad02e6" ] } ], "meta": { - "nf-test": "0.9.2", - "nextflow": "25.04.6" + "nf-test": "0.9.3", + "nextflow": "25.10.2" }, - "timestamp": "2025-09-10T14:13:38.25787" + "timestamp": "2026-01-28T17:52:10.030187" }, "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.22.1" ] - ], - "3": [ - "versions.yml:md5,b8717818c91b07de87c2a5590bad02e6" - ], - "bai": [ - - ], - "crai": [ - [ - { - "id": "test", - "single_end": false - }, - "test.paired_end.recalibrated.sorted.cram.crai:md5,d41d8cd98f00b204e9800998ecf8427e" - ] - ], - "csi": [ - - ], - "versions": [ - "versions.yml:md5,b8717818c91b07de87c2a5590bad02e6" ] } ], "meta": { - "nf-test": "0.9.2", - "nextflow": "25.04.6" + "nf-test": "0.9.3", + "nextflow": "25.10.2" }, - "timestamp": "2025-09-10T14:13:34.496412" + "timestamp": "2026-01-28T17:51:59.125484" }, "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.22.1" ] - ], - "1": [ - - ], - "2": [ - - ], - "3": [ - "versions.yml:md5,b8717818c91b07de87c2a5590bad02e6" - ], - "bai": [ - [ - { - "id": "test", - "single_end": false - }, - "test.paired_end.sorted.bam.bai:md5,d41d8cd98f00b204e9800998ecf8427e" - ] - ], - "crai": [ - - ], - "csi": [ - - ], - "versions": [ - "versions.yml:md5,b8717818c91b07de87c2a5590bad02e6" ] } ], "meta": { - "nf-test": "0.9.2", - "nextflow": "25.04.6" + "nf-test": "0.9.3", + "nextflow": "25.10.2" }, - "timestamp": "2025-09-10T14:13:25.934431" + "timestamp": "2026-01-28T17:51:47.277042" }, "csi": { "content": [ "test.paired_end.sorted.bam.csi", - [ - "versions.yml:md5,b8717818c91b07de87c2a5590bad02e6" - ] + { + "versions_samtools": [ + [ + "SAMTOOLS_INDEX", + "samtools", + "1.22.1" + ] + ] + } ], "meta": { - "nf-test": "0.9.2", - "nextflow": "25.04.6" + "nf-test": "0.9.3", + "nextflow": "25.10.2" }, - "timestamp": "2025-09-10T14:13:22.262088" + "timestamp": "2026-01-28T17:51:35.758735" }, "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.22.1" ] - ], - "3": [ - "versions.yml:md5,b8717818c91b07de87c2a5590bad02e6" - ], - "bai": [ - - ], - "crai": [ - [ - { - "id": "test", - "single_end": false - }, - "test.paired_end.recalibrated.sorted.cram.crai:md5,14bc3bd5c89cacc8f4541f9062429029" - ] - ], - "csi": [ - - ], - "versions": [ - "versions.yml:md5,b8717818c91b07de87c2a5590bad02e6" ] } ], "meta": { - "nf-test": "0.9.2", - "nextflow": "25.04.6" + "nf-test": "0.9.3", + "nextflow": "25.10.2" }, - "timestamp": "2025-09-10T14:13:18.191664" + "timestamp": "2026-01-28T17:51:26.561965" }, "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,b8717818c91b07de87c2a5590bad02e6" - ], - "bai": [ + "versions_samtools": [ [ - { - "id": "test", - "single_end": false - }, - "test.paired_end.sorted.bam.bai:md5,704c10dd1326482448ca3073fdebc2f4" + "SAMTOOLS_INDEX", + "samtools", + "1.22.1" ] - ], - "crai": [ - - ], - "csi": [ - - ], - "versions": [ - "versions.yml:md5,b8717818c91b07de87c2a5590bad02e6" ] } ], "meta": { - "nf-test": "0.9.2", - "nextflow": "25.04.6" + "nf-test": "0.9.3", + "nextflow": "25.10.2" }, - "timestamp": "2025-09-10T14:13:08.51539" + "timestamp": "2026-01-28T17:51:15.299035" } } \ No newline at end of file diff --git a/modules/nf-core/samtools/sort/main.nf b/modules/nf-core/samtools/sort/main.nf index d4bd5a32..6b5aa31d 100644 --- a/modules/nf-core/samtools/sort/main.nf +++ b/modules/nf-core/samtools/sort/main.nf @@ -19,7 +19,7 @@ process SAMTOOLS_SORT { tuple val(meta), path("${prefix}.${extension}.crai"), emit: crai, optional: true tuple val(meta), path("${prefix}.${extension}.csi"), emit: csi, optional: true tuple val(meta), path("${prefix}.${extension}.bai"), emit: bai, optional: true - path "versions.yml", emit: versions + 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 @@ -53,10 +53,6 @@ process SAMTOOLS_SORT { -o ${output_file} \\ - - cat <<-END_VERSIONS > versions.yml - "${task.process}": - samtools: \$(echo \$(samtools --version 2>&1) | sed 's/^.*samtools //; s/Using.*\$//') - END_VERSIONS """ stub: @@ -78,9 +74,5 @@ process SAMTOOLS_SORT { touch ${prefix}.${extension} ${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/sort/meta.yml b/modules/nf-core/samtools/sort/meta.yml index 4c4010bb..69968304 100644 --- a/modules/nf-core/samtools/sort/meta.yml +++ b/modules/nf-core/samtools/sort/meta.yml @@ -76,7 +76,6 @@ output: description: Sorted SAM file pattern: "*.{sam}" ontologies: [] - crai: - - meta: type: map @@ -110,13 +109,29 @@ output: description: BAM index file (optional) pattern: "*.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: - - 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 + - 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 ff069190..df47bb25 100644 --- a/modules/nf-core/samtools/sort/tests/main.nf.test +++ b/modules/nf-core/samtools/sort/tests/main.nf.test @@ -34,7 +34,7 @@ nextflow_process { { assert snapshot( process.out.bam, process.out.bai, - process.out.versions + process.out.findAll { key, val -> key.startsWith("versions") } ).match()} ) } @@ -66,7 +66,7 @@ nextflow_process { { assert snapshot( process.out.bam, process.out.bai, - process.out.versions + process.out.findAll { key, val -> key.startsWith("versions") } ).match()} ) } @@ -98,7 +98,7 @@ nextflow_process { { assert snapshot( process.out.bam, process.out.csi, - process.out.versions + process.out.findAll { key, val -> key.startsWith("versions") } ).match()} ) } @@ -133,7 +133,7 @@ nextflow_process { { assert snapshot( process.out.bam, process.out.csi.collect { it.collect { it instanceof Map ? it : file(it).name } }, - process.out.versions + process.out.findAll { key, val -> key.startsWith("versions") } ).match()} ) } @@ -168,7 +168,7 @@ nextflow_process { { assert snapshot( process.out.bam, process.out.bai.collect { it.collect { it instanceof Map ? it : file(it).name } }, - process.out.versions + process.out.findAll { key, val -> key.startsWith("versions") } ).match()} ) } @@ -203,7 +203,7 @@ nextflow_process { { assert snapshot( process.out.bam, process.out.csi.collect { it.collect { it instanceof Map ? it : file(it).name } }, - process.out.versions + process.out.findAll { key, val -> key.startsWith("versions") } ).match()} ) } @@ -235,7 +235,7 @@ nextflow_process { { 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.findAll { key, val -> key.startsWith("versions") } ).match()} ) } @@ -265,7 +265,7 @@ nextflow_process { then { assertAll ( { assert process.success }, - { assert snapshot(process.out).match() } + { assert snapshot(process.out.findAll { key, val -> key.startsWith("versions") }).match() } ) } } @@ -296,7 +296,7 @@ nextflow_process { then { assertAll ( { assert process.success }, - { assert snapshot(process.out).match() } + { assert snapshot(process.out.findAll { key, val -> key.startsWith("versions") }).match() } ) } } @@ -325,7 +325,7 @@ nextflow_process { then { assertAll ( { assert process.success }, - { assert snapshot(process.out).match() } + { assert snapshot(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 473e1745..4e618fa3 100644 --- a/modules/nf-core/samtools/sort/tests/main.nf.test.snap +++ b/modules/nf-core/samtools/sort/tests/main.nf.test.snap @@ -19,80 +19,21 @@ "test.sorted.cram.crai" ] ], - [ - "versions.yml:md5,18e8b3709b62aa2ba61966672eee91b2" - ] - ], - "meta": { - "nf-test": "0.9.2", - "nextflow": "25.04.6" - }, - "timestamp": "2025-09-10T14:43:31.395604" - }, - "bam - stub": { - "content": [ { - "0": [ - [ - { - "id": "test", - "single_end": false - }, - "test.sorted.bam:md5,d41d8cd98f00b204e9800998ecf8427e" - ] - ], - "1": [ - - ], - "2": [ - - ], - "3": [ - - ], - "4": [ - - ], - "5": [ - - ], - "6": [ - "versions.yml:md5,18e8b3709b62aa2ba61966672eee91b2" - ], - "bai": [ - - ], - "bam": [ + "versions_samtools": [ [ - { - "id": "test", - "single_end": false - }, - "test.sorted.bam:md5,d41d8cd98f00b204e9800998ecf8427e" + "SAMTOOLS_SORT", + "samtools", + "1.22.1" ] - ], - "crai": [ - - ], - "cram": [ - - ], - "csi": [ - - ], - "sam": [ - - ], - "versions": [ - "versions.yml:md5,18e8b3709b62aa2ba61966672eee91b2" ] } ], "meta": { - "nf-test": "0.9.2", - "nextflow": "25.04.6" + "nf-test": "0.9.3", + "nextflow": "25.10.0" }, - "timestamp": "2025-09-10T14:43:37.387063" + "timestamp": "2025-10-29T12:47:01.171084" }, "bam_csi_index": { "content": [ @@ -114,15 +55,39 @@ "test.sorted.bam.csi:md5,01394e702c729cb478df914ffaf9f7f8" ] ], - [ - "versions.yml:md5,18e8b3709b62aa2ba61966672eee91b2" - ] + { + "versions_samtools": [ + [ + "SAMTOOLS_SORT", + "samtools", + "1.22.1" + ] + ] + } ], "meta": { - "nf-test": "0.9.2", - "nextflow": "25.04.6" + "nf-test": "0.9.3", + "nextflow": "25.10.0" }, - "timestamp": "2025-09-10T14:43:06.976036" + "timestamp": "2025-10-29T12:46:00.961675" + }, + "bam - stub": { + "content": [ + { + "versions_samtools": [ + [ + "SAMTOOLS_SORT", + "samtools", + "1.22.1" + ] + ] + } + ], + "meta": { + "nf-test": "0.9.3", + "nextflow": "25.10.0" + }, + "timestamp": "2025-10-29T12:47:12.154354" }, "multiple bam bai index": { "content": [ @@ -144,80 +109,39 @@ "test.sorted.bam.bai" ] ], - [ - "versions.yml:md5,18e8b3709b62aa2ba61966672eee91b2" - ] + { + "versions_samtools": [ + [ + "SAMTOOLS_SORT", + "samtools", + "1.22.1" + ] + ] + } ], "meta": { - "nf-test": "0.9.2", - "nextflow": "25.04.6" + "nf-test": "0.9.3", + "nextflow": "25.10.0" }, - "timestamp": "2025-09-10T14:43:18.72271" + "timestamp": "2025-10-29T12:46:25.488622" }, "cram - stub": { "content": [ { - "0": [ - - ], - "1": [ - [ - { - "id": "test", - "single_end": false - }, - "test.sorted.cram:md5,d41d8cd98f00b204e9800998ecf8427e" - ] - ], - "2": [ - - ], - "3": [ - - ], - "4": [ - - ], - "5": [ - - ], - "6": [ - "versions.yml:md5,18e8b3709b62aa2ba61966672eee91b2" - ], - "bai": [ - - ], - "bam": [ - - ], - "crai": [ - - ], - "cram": [ + "versions_samtools": [ [ - { - "id": "test", - "single_end": false - }, - "test.sorted.cram:md5,d41d8cd98f00b204e9800998ecf8427e" + "SAMTOOLS_SORT", + "samtools", + "1.22.1" ] - ], - "csi": [ - - ], - "sam": [ - - ], - "versions": [ - "versions.yml:md5,18e8b3709b62aa2ba61966672eee91b2" ] } ], "meta": { - "nf-test": "0.9.2", - "nextflow": "25.04.6" + "nf-test": "0.9.3", + "nextflow": "25.10.0" }, - "timestamp": "2025-09-10T14:43:48.734367" + "timestamp": "2025-10-29T12:47:28.485045" }, "multiple bam": { "content": [ @@ -233,80 +157,39 @@ [ ], - [ - "versions.yml:md5,18e8b3709b62aa2ba61966672eee91b2" - ] + { + "versions_samtools": [ + [ + "SAMTOOLS_SORT", + "samtools", + "1.22.1" + ] + ] + } ], "meta": { - "nf-test": "0.9.2", - "nextflow": "25.04.6" + "nf-test": "0.9.3", + "nextflow": "25.10.0" }, - "timestamp": "2025-09-10T14:43:12.989244" + "timestamp": "2025-10-29T12:46:13.168476" }, "multiple bam - stub": { "content": [ { - "0": [ + "versions_samtools": [ [ - { - "id": "test", - "single_end": false - }, - "test.sorted.bam:md5,cd4eb0077f25e9cff395366b8883dd1f" + "SAMTOOLS_SORT", + "samtools", + "1.22.1" ] - ], - "1": [ - - ], - "2": [ - - ], - "3": [ - - ], - "4": [ - - ], - "5": [ - - ], - "6": [ - "versions.yml:md5,18e8b3709b62aa2ba61966672eee91b2" - ], - "bai": [ - - ], - "bam": [ - [ - { - "id": "test", - "single_end": false - }, - "test.sorted.bam:md5,cd4eb0077f25e9cff395366b8883dd1f" - ] - ], - "crai": [ - - ], - "cram": [ - - ], - "csi": [ - - ], - "sam": [ - - ], - "versions": [ - "versions.yml:md5,18e8b3709b62aa2ba61966672eee91b2" ] } ], "meta": { - "nf-test": "0.9.2", - "nextflow": "25.04.6" + "nf-test": "0.9.3", + "nextflow": "25.10.0" }, - "timestamp": "2025-09-10T14:43:43.196638" + "timestamp": "2025-10-29T12:47:21.628088" }, "bam_no_index": { "content": [ @@ -322,15 +205,21 @@ [ ], - [ - "versions.yml:md5,18e8b3709b62aa2ba61966672eee91b2" - ] + { + "versions_samtools": [ + [ + "SAMTOOLS_SORT", + "samtools", + "1.22.1" + ] + ] + } ], "meta": { - "nf-test": "0.9.2", - "nextflow": "25.04.6" + "nf-test": "0.9.3", + "nextflow": "25.10.0" }, - "timestamp": "2025-09-10T14:42:54.926504" + "timestamp": "2025-10-29T12:45:47.139418" }, "multiple bam csi index": { "content": [ @@ -352,45 +241,21 @@ "test.sorted.bam.csi" ] ], - [ - "versions.yml:md5,18e8b3709b62aa2ba61966672eee91b2" - ] - ], - "meta": { - "nf-test": "0.9.2", - "nextflow": "25.04.6" - }, - "timestamp": "2025-09-10T14:43:25.059178" - }, - "bam": { - "content": [ - [ - [ - { - "id": "test", - "single_end": false - }, - "test.sorted.bam:md5,34aa85e86abefe637f7a4a9887f016fc" - ] - ], - [ - [ - { - "id": "test", - "single_end": false - }, - "test.sorted.bam.csi" + { + "versions_samtools": [ + [ + "SAMTOOLS_SORT", + "samtools", + "1.22.1" + ] ] - ], - [ - "versions.yml:md5,2659b187d681241451539d4c53500b9f" - ] + } ], "meta": { - "nf-test": "0.9.0", - "nextflow": "24.09.0" + "nf-test": "0.9.3", + "nextflow": "25.10.0" }, - "timestamp": "2024-10-08T11:59:46.372244" + "timestamp": "2025-10-29T12:46:51.5531" }, "bam_bai_index": { "content": [ @@ -412,14 +277,20 @@ "test.sorted.bam.bai:md5,50dd467c169545a4d5d1f709f7e986e0" ] ], - [ - "versions.yml:md5,18e8b3709b62aa2ba61966672eee91b2" - ] + { + "versions_samtools": [ + [ + "SAMTOOLS_SORT", + "samtools", + "1.22.1" + ] + ] + } ], "meta": { - "nf-test": "0.9.2", - "nextflow": "25.04.6" + "nf-test": "0.9.3", + "nextflow": "25.10.0" }, - "timestamp": "2025-09-10T14:43:00.82974" + "timestamp": "2025-10-29T12:45:52.796936" } } \ No newline at end of file diff --git a/modules/nf-core/samtools/stats/main.nf b/modules/nf-core/samtools/stats/main.nf index c06ad3bf..57d24680 100644 --- a/modules/nf-core/samtools/stats/main.nf +++ b/modules/nf-core/samtools/stats/main.nf @@ -13,36 +13,28 @@ process SAMTOOLS_STATS { output: tuple val(meta), path("*.stats"), emit: stats - path "versions.yml" , emit: versions + 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 script: - def prefix = task.ext.prefix ?: "${meta.id}" + def args = task.ext.args ?: '' + def prefix = task.ext.prefix ?: "${meta.id}" def reference = fasta ? "--reference ${fasta}" : "" """ samtools \\ stats \\ + ${args} \\ --threads ${task.cpus} \\ ${reference} \\ ${input} \\ > ${prefix}.stats - - cat <<-END_VERSIONS > versions.yml - "${task.process}": - samtools: \$(echo \$(samtools --version 2>&1) | sed 's/^.*samtools //; s/Using.*\$//') - END_VERSIONS """ stub: def prefix = task.ext.prefix ?: "${meta.id}" """ touch ${prefix}.stats - - 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/stats/meta.yml b/modules/nf-core/samtools/stats/meta.yml index 6dc51885..5c59cce4 100644 --- a/modules/nf-core/samtools/stats/meta.yml +++ b/modules/nf-core/samtools/stats/meta.yml @@ -55,13 +55,29 @@ output: description: File containing samtools stats output pattern: "*.{stats}" 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: - - 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: 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" - "@FriederikeHanssen" diff --git a/modules/nf-core/samtools/stats/tests/main.nf.test.snap b/modules/nf-core/samtools/stats/tests/main.nf.test.snap index a451c04e..94d981b2 100644 --- a/modules/nf-core/samtools/stats/tests/main.nf.test.snap +++ b/modules/nf-core/samtools/stats/tests/main.nf.test.snap @@ -12,7 +12,11 @@ ] ], "1": [ - "versions.yml:md5,7668882f411d0f6356f14a1b8b56fa3c" + [ + "SAMTOOLS_STATS", + "samtools", + "1.22.1" + ] ], "stats": [ [ @@ -23,16 +27,20 @@ "test.stats:md5,f4aec6c41b73d34ac2fc6b3253aa39ba" ] ], - "versions": [ - "versions.yml:md5,7668882f411d0f6356f14a1b8b56fa3c" + "versions_samtools": [ + [ + "SAMTOOLS_STATS", + "samtools", + "1.22.1" + ] ] } ], "meta": { - "nf-test": "0.9.2", - "nextflow": "25.04.6" + "nf-test": "0.9.3", + "nextflow": "25.10.0" }, - "timestamp": "2025-09-10T15:05:52.878044" + "timestamp": "2025-11-01T02:27:18.460724" }, "bam - stub": { "content": [ @@ -47,7 +55,11 @@ ] ], "1": [ - "versions.yml:md5,7668882f411d0f6356f14a1b8b56fa3c" + [ + "SAMTOOLS_STATS", + "samtools", + "1.22.1" + ] ], "stats": [ [ @@ -58,16 +70,20 @@ "test.stats:md5,d41d8cd98f00b204e9800998ecf8427e" ] ], - "versions": [ - "versions.yml:md5,7668882f411d0f6356f14a1b8b56fa3c" + "versions_samtools": [ + [ + "SAMTOOLS_STATS", + "samtools", + "1.22.1" + ] ] } ], "meta": { - "nf-test": "0.9.2", - "nextflow": "25.04.6" + "nf-test": "0.9.3", + "nextflow": "25.10.0" }, - "timestamp": "2025-09-10T15:05:56.722672" + "timestamp": "2025-11-01T02:27:30.245839" }, "cram - stub": { "content": [ @@ -82,7 +98,11 @@ ] ], "1": [ - "versions.yml:md5,7668882f411d0f6356f14a1b8b56fa3c" + [ + "SAMTOOLS_STATS", + "samtools", + "1.22.1" + ] ], "stats": [ [ @@ -93,16 +113,20 @@ "test.stats:md5,d41d8cd98f00b204e9800998ecf8427e" ] ], - "versions": [ - "versions.yml:md5,7668882f411d0f6356f14a1b8b56fa3c" + "versions_samtools": [ + [ + "SAMTOOLS_STATS", + "samtools", + "1.22.1" + ] ] } ], "meta": { - "nf-test": "0.9.2", - "nextflow": "25.04.6" + "nf-test": "0.9.3", + "nextflow": "25.10.0" }, - "timestamp": "2025-09-10T15:06:13.766719" + "timestamp": "2025-11-01T02:27:39.041649" }, "bam": { "content": [ @@ -117,7 +141,11 @@ ] ], "1": [ - "versions.yml:md5,7668882f411d0f6356f14a1b8b56fa3c" + [ + "SAMTOOLS_STATS", + "samtools", + "1.22.1" + ] ], "stats": [ [ @@ -128,15 +156,19 @@ "test.stats:md5,41ba8ad30ddb598dadb177a54c222ab9" ] ], - "versions": [ - "versions.yml:md5,7668882f411d0f6356f14a1b8b56fa3c" + "versions_samtools": [ + [ + "SAMTOOLS_STATS", + "samtools", + "1.22.1" + ] ] } ], "meta": { - "nf-test": "0.9.2", - "nextflow": "25.04.6" + "nf-test": "0.9.3", + "nextflow": "25.10.0" }, - "timestamp": "2025-09-10T15:05:30.301153" + "timestamp": "2025-11-01T02:26:55.988241" } } \ No newline at end of file diff --git a/modules/nf-core/yahs/main.nf b/modules/nf-core/yahs/main.nf index 2291cab0..cb338ae8 100644 --- a/modules/nf-core/yahs/main.nf +++ b/modules/nf-core/yahs/main.nf @@ -18,7 +18,7 @@ process YAHS { tuple val(meta), path("${prefix}_r*_*.agp") , emit: round_agp , optional: true tuple val(meta), path("${prefix}.bin") , emit: binary tuple val(meta), path("${prefix}.log") , emit: log - path "versions.yml" , emit: versions + tuple val("${task.process}"), val('yahs'), eval("yahs --version 2>&1"), emit: versions_yahs, topic: versions when: task.ext.when == null || task.ext.when @@ -35,11 +35,6 @@ process YAHS { ${fasta} \\ ${hic_map} \\ 2>| >( tee ${prefix}.log >&2 ) - - cat <<-END_VERSIONS > versions.yml - "${task.process}": - yahs: \$(yahs --version 2>&1) - END_VERSIONS """ stub: @@ -53,10 +48,5 @@ process YAHS { touch ${prefix}_r01_break.agp touch ${prefix}.bin touch ${prefix}.log - - cat <<-END_VERSIONS > versions.yml - "${task.process}": - yahs: \$(yahs --version 2>&1) - END_VERSIONS """ } diff --git a/modules/nf-core/yahs/meta.yml b/modules/nf-core/yahs/meta.yml index c33501ab..3d06083c 100644 --- a/modules/nf-core/yahs/meta.yml +++ b/modules/nf-core/yahs/meta.yml @@ -12,7 +12,8 @@ tools: documentation: "https://github.com/c-zhou/yahs" tool_dev_url: "https://github.com/c-zhou/yahs" doi: "10.1093/bioinformatics/btac808" - licence: ["MIT"] + licence: + - "MIT" identifier: biotools:yahs input: - - meta: @@ -25,20 +26,20 @@ input: description: FASTA reference file pattern: "*.{fasta,fa}" ontologies: - - edam: http://edamontology.org/format_1929 # FASTA + - edam: http://edamontology.org/format_1929 - fai: type: file description: index of the reference file pattern: "*.{fai}" ontologies: - - edam: http://edamontology.org/format_3475 # TSV + - edam: http://edamontology.org/format_3475 - hic_map: type: file description: BED file containing coordinates of read alignments pattern: "*.{bed,bam,bin}" ontologies: - - edam: http://edamontology.org/format_3003 # BED - - edam: http://edamontology.org/format_2572 # BAM + - edam: http://edamontology.org/format_3003 + - edam: http://edamontology.org/format_2572 - agp: type: file description: | @@ -46,7 +47,7 @@ input: to use as a start point pattern: "*.agp" ontologies: - - edam: http://edamontology.org/format_3693 # AGP + - edam: http://edamontology.org/format_3693 output: scaffolds_fasta: - - meta: @@ -59,7 +60,7 @@ output: description: FASTA file with resulting contigs pattern: "${prefix}_scaffolds_final.fa" ontologies: - - edam: http://edamontology.org/format_1929 # FASTA + - edam: http://edamontology.org/format_1929 scaffolds_agp: - - meta: type: map @@ -71,7 +72,7 @@ output: description: AGP file containing contigs placing coordinates pattern: "${prefix}_scaffolds_final.agp" ontologies: - - edam: http://edamontology.org/format_3693 # AGP + - edam: http://edamontology.org/format_3693 initial_break_agp: - - meta: type: map @@ -83,7 +84,7 @@ output: description: AGP file describing initial contig breaks pattern: "${prefix}_{inital,no}_break*.agp" ontologies: - - edam: http://edamontology.org/format_3693 # AGP + - edam: http://edamontology.org/format_3693 round_agp: - - meta: type: map @@ -95,7 +96,7 @@ output: description: AGP file describing intermediate rounds of scaffolding pattern: "${prefix}_{initial,no}_break*.agp" ontologies: - - edam: http://edamontology.org/format_3693 # AGP + - edam: http://edamontology.org/format_3693 binary: - - meta: type: map @@ -120,14 +121,28 @@ output: description: Log file describing YaHS run pattern: "${prefix}.log" ontologies: - - edam: "http://edamontology.org/format_2330" # Textual format + - edam: "http://edamontology.org/format_2330" + versions_yahs: + - - ${task.process}: + type: string + description: The name of the process + - yahs: + type: string + description: The name of the tool + - yahs --version 2>&1: + type: eval + description: The expression to obtain the version of the tool +topics: 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 name of the process + - yahs: + type: string + description: The name of the tool + - yahs --version 2>&1: + type: eval + description: The expression to obtain the version of the tool authors: - "@ksenia-krasheninnikova" maintainers: diff --git a/modules/nf-core/yahs/tests/main.nf.test b/modules/nf-core/yahs/tests/main.nf.test index ad662ebb..e676604d 100644 --- a/modules/nf-core/yahs/tests/main.nf.test +++ b/modules/nf-core/yahs/tests/main.nf.test @@ -39,7 +39,7 @@ nextflow_process { process.out.initial_break_agp, process.out.round_agp, process.out.binary, - process.out.versions, + process.out.findAll { key, val -> key.startsWith("versions")}, ).match() }, { file(process.out.log.get(0).get(1)).readLines().last().contains("Real time") } ) @@ -96,7 +96,7 @@ nextflow_process { process.out.initial_break_agp, process.out.round_agp, process.out.binary, - process.out.versions, + process.out.findAll { key, val -> key.startsWith("versions")}, ).match() }, { file(process.out.log.get(0).get(1)).readLines().last().contains("Real time") } ) diff --git a/modules/nf-core/yahs/tests/main.nf.test.snap b/modules/nf-core/yahs/tests/main.nf.test.snap index 1b5a619a..e41229da 100644 --- a/modules/nf-core/yahs/tests/main.nf.test.snap +++ b/modules/nf-core/yahs/tests/main.nf.test.snap @@ -54,7 +54,11 @@ ] ], "6": [ - "versions.yml:md5,37472c36bf654e5ffc7507701c5f280a" + [ + "YAHS", + "yahs", + "1.2.2" + ] ], "binary": [ [ @@ -107,16 +111,20 @@ "test_scaffolds_final.fa:md5,d41d8cd98f00b204e9800998ecf8427e" ] ], - "versions": [ - "versions.yml:md5,37472c36bf654e5ffc7507701c5f280a" + "versions_yahs": [ + [ + "YAHS", + "yahs", + "1.2.2" + ] ] } ], "meta": { "nf-test": "0.9.3", - "nextflow": "25.04.2" + "nextflow": "25.10.3" }, - "timestamp": "2025-11-05T13:57:23.370522" + "timestamp": "2026-02-10T13:13:16.431723" }, "homo_sapiens - bam - fasta - fai": { "content": [ @@ -155,15 +163,21 @@ "test.bin:md5,da45a2ec8a97fc24783e9a63373db379" ] ], - [ - "versions.yml:md5,37472c36bf654e5ffc7507701c5f280a" - ] + { + "versions_yahs": [ + [ + "YAHS", + "yahs", + "1.2.2" + ] + ] + } ], "meta": { "nf-test": "0.9.3", - "nextflow": "25.04.2" + "nextflow": "25.10.3" }, - "timestamp": "2025-11-05T13:57:15.713602" + "timestamp": "2026-02-10T13:13:08.310976" }, "homo_sapiens - bam - fasta - fai - agp": { "content": [ @@ -197,14 +211,20 @@ "test.bin:md5,da45a2ec8a97fc24783e9a63373db379" ] ], - [ - "versions.yml:md5,37472c36bf654e5ffc7507701c5f280a" - ] + { + "versions_yahs": [ + [ + "YAHS", + "yahs", + "1.2.2" + ] + ] + } ], "meta": { "nf-test": "0.9.3", - "nextflow": "25.04.2" + "nextflow": "25.10.3" }, - "timestamp": "2025-11-05T13:57:20.143057" + "timestamp": "2026-02-10T13:13:12.93575" } } \ No newline at end of file diff --git a/subworkflows/local/assemble/main.nf b/subworkflows/local/assemble/main.nf index 596b5239..d07eccf7 100644 --- a/subworkflows/local/assemble/main.nf +++ b/subworkflows/local/assemble/main.nf @@ -127,8 +127,6 @@ workflow ASSEMBLE { FLYE_ONT(flye_ont_inputs.reads, flye_ont_inputs.mode) FLYE_HIFI(flye_hifi_inputs.reads, flye_hifi_inputs.mode) - ch_versions = ch_versions.mix(FLYE_ONT.out.versions).mix(FLYE_HIFI.out.versions) - /* ========================= HIFIASM ASSEMBLER diff --git a/subworkflows/local/liftoff/main.nf b/subworkflows/local/liftoff/main.nf index 780c3239..f3d533d0 100644 --- a/subworkflows/local/liftoff/main.nf +++ b/subworkflows/local/liftoff/main.nf @@ -12,6 +12,7 @@ workflow RUN_LIFTOFF { LIFTOFF.out.gff3.set { lifted_annotations } versions = ch_versions.mix(LIFTOFF.out.versions) + emit: lifted_annotations versions diff --git a/subworkflows/local/mapping/map_sr/main.nf b/subworkflows/local/mapping/map_sr/main.nf index 5213a09d..e29204c2 100644 --- a/subworkflows/local/mapping/map_sr/main.nf +++ b/subworkflows/local/mapping/map_sr/main.nf @@ -7,7 +7,6 @@ workflow MAP_SR { genome_assembly main: - channel.empty().set { ch_versions } // map reads to assembly in_reads .join(genome_assembly) @@ -15,8 +14,6 @@ workflow MAP_SR { ALIGN_SHORT(map_assembly, true, 'bai', false, false) - versions = ch_versions.mix(ALIGN_SHORT.out.versions) - ALIGN_SHORT.out.bam.set { aln_to_assembly_bam } ALIGN_SHORT.out.index.set { aln_to_assembly_bai } @@ -31,8 +28,6 @@ workflow MAP_SR { BAM_STATS(aln_to_assembly_bam_bai, ch_fasta) - versions = ch_versions.mix(BAM_STATS.out.versions) - aln_to_assembly_bam .join(aln_to_assembly_bai) .set { aln_to_assembly_bam_bai } @@ -41,5 +36,4 @@ workflow MAP_SR { aln_to_assembly_bam aln_to_assembly_bai aln_to_assembly_bam_bai - versions } diff --git a/subworkflows/local/mapping/map_to_assembly/main.nf b/subworkflows/local/mapping/map_to_assembly/main.nf index 62e127f7..5e45389d 100644 --- a/subworkflows/local/mapping/map_to_assembly/main.nf +++ b/subworkflows/local/mapping/map_to_assembly/main.nf @@ -6,9 +6,6 @@ workflow MAP_TO_ASSEMBLY { map_assembly // meta: [id, qc_reads], reads, refs main: - channel.empty().set { ch_versions } - // map reads to assembly - ALIGN(map_assembly, true, 'bai', false, false) ALIGN.out.bam @@ -27,9 +24,7 @@ workflow MAP_TO_ASSEMBLY { BAM_STATS(aln_to_assembly_bam_bai, ch_fasta ) - versions = ch_versions.mix(ALIGN.out.versions).mix(BAM_STATS.out.versions) - emit: aln_to_assembly_bam // [id], bam - versions + versions = channel.empty() } diff --git a/subworkflows/local/mapping/map_to_ref/main.nf b/subworkflows/local/mapping/map_to_ref/main.nf index dec632cb..d97df25d 100644 --- a/subworkflows/local/mapping/map_to_ref/main.nf +++ b/subworkflows/local/mapping/map_to_ref/main.nf @@ -6,8 +6,6 @@ workflow MAP_TO_REF { ch_map_ref // meta, reads, refs main: - channel.empty().set { ch_versions } - // Map reads to reference ALIGN(ch_map_ref, true, 'bai', false, false) @@ -27,9 +25,6 @@ workflow MAP_TO_REF { BAM_STATS(ch_aln_to_ref_bam_bai, ch_fasta) - versions = ch_versions.mix(ALIGN.out.versions).mix(BAM_STATS.out.versions) - emit: ch_aln_to_ref_bam // meta, bam - versions } diff --git a/subworkflows/local/polishing/pilon/polish_pilon/main.nf b/subworkflows/local/polishing/pilon/polish_pilon/main.nf index e730969f..a444d2f1 100644 --- a/subworkflows/local/polishing/pilon/polish_pilon/main.nf +++ b/subworkflows/local/polishing/pilon/polish_pilon/main.nf @@ -27,8 +27,6 @@ workflow POLISH_PILON { MAP_SR(map_sr_in.shortreads, map_sr_in.assembly) - ch_versions = ch_versions.mix(MAP_SR.out.versions) - RUN_PILON(map_sr_in.assembly, MAP_SR.out.aln_to_assembly_bam_bai) RUN_PILON.out.improved_assembly diff --git a/subworkflows/local/prepare/main.nf b/subworkflows/local/prepare/main.nf index 67cc163c..11b60a33 100644 --- a/subworkflows/local/prepare/main.nf +++ b/subworkflows/local/prepare/main.nf @@ -245,8 +245,6 @@ workflow PREPARE { JELLYFISH.out.genomescope_plot.set { genomescope_plot } SHORTREADS.out.versions - .mix(ONT.out.versions) - .mix(HIFI.out.versions) .mix(JELLYFISH.out.versions) .set { versions } diff --git a/subworkflows/local/prepare/prepare_hifi/main.nf b/subworkflows/local/prepare/prepare_hifi/main.nf index 18073f47..798249ba 100644 --- a/subworkflows/local/prepare/prepare_hifi/main.nf +++ b/subworkflows/local/prepare/prepare_hifi/main.nf @@ -80,10 +80,7 @@ workflow PREPARE_HIFI { fastplong_reads_out.dump(tag: "Prepare-HIFI output") - versions = ch_versions.mix(FASTPLONG_HIFI.out.versions) - emit: main_out = fastplong_reads_out fastplong_hifi_reports = fastplong_json_out - versions } diff --git a/subworkflows/local/prepare/prepare_ont/main.nf b/subworkflows/local/prepare/prepare_ont/main.nf index 47911676..63914b71 100644 --- a/subworkflows/local/prepare/prepare_ont/main.nf +++ b/subworkflows/local/prepare/prepare_ont/main.nf @@ -137,7 +137,7 @@ workflow PREPARE_ONT { ) .set { fastplong_json_out } - versions = ch_versions.mix(COLLECT.out.versions).mix(FASTPLONG_ONT.out.versions) + versions = ch_versions.mix(COLLECT.out.versions) fastplong_reads_out.dump(tag: "Prepare-ONT output") diff --git a/subworkflows/local/prepare/prepare_shortreads/main.nf b/subworkflows/local/prepare/prepare_shortreads/main.nf index 6f5dbfbd..f6b36008 100644 --- a/subworkflows/local/prepare/prepare_shortreads/main.nf +++ b/subworkflows/local/prepare/prepare_shortreads/main.nf @@ -151,8 +151,6 @@ workflow PREPARE_SHORTREADS { ) .set { shortreads } - ch_versions = ch_versions.mix(FASTP.out.versions) - shortreads .filter { it -> it.meta.merqury } .filter { it -> it.meta.group } @@ -202,9 +200,9 @@ workflow PREPARE_SHORTREADS { emit: main_out = shortreads + fastp_json = FASTP.out.json meryl_kmers versions - fastp_json = FASTP.out.json } def create_shortread_channel(row) { // This function expects a meta map as input diff --git a/subworkflows/local/qc/busco/main.nf b/subworkflows/local/qc/busco/main.nf index 495f554a..0267f05d 100644 --- a/subworkflows/local/qc/busco/main.nf +++ b/subworkflows/local/qc/busco/main.nf @@ -5,7 +5,6 @@ workflow RUN_BUSCO { ch_main main: - channel.empty().set { versions } channel.empty().set { batch_summary } channel.empty().set { short_summary_txt } channel.empty().set { short_summary_json } @@ -28,11 +27,9 @@ workflow RUN_BUSCO { BUSCO.out.batch_summary.set { batch_summary } BUSCO.out.short_summaries_txt.set { short_summary_txt } BUSCO.out.short_summaries_json.set { short_summary_json } - BUSCO.out.versions.set { versions } emit: batch_summary short_summary_json short_summary_txt - versions } diff --git a/subworkflows/local/qc/main.nf b/subworkflows/local/qc/main.nf index 90f4cde3..61310087 100644 --- a/subworkflows/local/qc/main.nf +++ b/subworkflows/local/qc/main.nf @@ -74,9 +74,6 @@ workflow QC { .mix(ch_map_branched.no_map_to_assembly) .set { ch_qc } - - ch_versions = ch_versions.mix(MAP_TO_ASSEMBLY.out.versions) - RUN_QUAST(ch_qc) RUN_QUAST.out.quast_tsv.set { quast_out } @@ -85,8 +82,6 @@ workflow QC { RUN_BUSCO(ch_qc) RUN_BUSCO.out.batch_summary.set { busco_out } - ch_versions = ch_versions.mix(RUN_BUSCO.out.versions) - MERQURY.out.stats .join( MERQURY.out.spectra_asm_hist diff --git a/subworkflows/local/scaffolding/hic/main.nf b/subworkflows/local/scaffolding/hic/main.nf index 499f8fda..78dcb706 100644 --- a/subworkflows/local/scaffolding/hic/main.nf +++ b/subworkflows/local/scaffolding/hic/main.nf @@ -79,12 +79,13 @@ workflow HIC { .map { it -> [ it.meta, - it.meta.polished ? (it.meta.polished.pilon ?: it.meta.polished.medaka ?: it.meta.polished.dorado) : it.meta.assembly + it.meta.polished ? (it.meta.polished.pilon ?: it.meta.polished.medaka ?: it.meta.polished.dorado) : it.meta.assembly, + [] ] } .set { faidx_in } - SAMTOOLS_FAIDX(faidx_in, [[],[]], false) + SAMTOOLS_FAIDX(faidx_in, false) SAMTOOLS_FAIDX.out.fai .map { @@ -117,8 +118,6 @@ workflow HIC { YAHS.out.scaffolds_fasta.map { meta, corrected -> [ meta.id, corrected ] }, meryl_kmers) - ch_versions = ch_versions.mix(QC.out.versions) - ch_main_scaffolded .filter { it -> it.lift_annotations @@ -134,15 +133,10 @@ workflow HIC { .set { liftoff_in } RUN_LIFTOFF(liftoff_in) + ch_versions = ch_versions.mix( RUN_LIFTOFF.out.versions, - QC.out.versions, - BWAMEM2_MEM.out.versions, - BWAMEM2_INDEX.out.versions, - ADD_RG.out.versions_picard, - SAMTOOLS_FAIDX.out.versions_samtools, - MARKDUP.out.versions_picard, - YAHS.out.versions) + QC.out.versions) emit: ch_main = ch_main_scaffolded diff --git a/subworkflows/nf-core/bam_sort_stats_samtools/main.nf b/subworkflows/nf-core/bam_sort_stats_samtools/main.nf index 46072636..312c2d24 100644 --- a/subworkflows/nf-core/bam_sort_stats_samtools/main.nf +++ b/subworkflows/nf-core/bam_sort_stats_samtools/main.nf @@ -12,14 +12,9 @@ workflow BAM_SORT_STATS_SAMTOOLS { ch_fasta // channel: [ val(meta), path(fasta) ] main: - - ch_versions = Channel.empty() - SAMTOOLS_SORT ( ch_bam, ch_fasta, '' ) - ch_versions = ch_versions.mix(SAMTOOLS_SORT.out.versions.first()) SAMTOOLS_INDEX ( SAMTOOLS_SORT.out.bam ) - ch_versions = ch_versions.mix(SAMTOOLS_INDEX.out.versions.first()) SAMTOOLS_SORT.out.bam .join(SAMTOOLS_INDEX.out.bai, by: [0], remainder: true) @@ -35,7 +30,6 @@ workflow BAM_SORT_STATS_SAMTOOLS { .set { ch_bam_bai } BAM_STATS_SAMTOOLS ( ch_bam_bai, ch_fasta ) - ch_versions = ch_versions.mix(BAM_STATS_SAMTOOLS.out.versions) emit: bam = SAMTOOLS_SORT.out.bam // channel: [ val(meta), [ bam ] ] @@ -45,6 +39,4 @@ workflow BAM_SORT_STATS_SAMTOOLS { stats = BAM_STATS_SAMTOOLS.out.stats // channel: [ val(meta), [ stats ] ] flagstat = BAM_STATS_SAMTOOLS.out.flagstat // channel: [ val(meta), [ flagstat ] ] idxstats = BAM_STATS_SAMTOOLS.out.idxstats // channel: [ val(meta), [ idxstats ] ] - - versions = ch_versions // channel: [ versions.yml ] } diff --git a/subworkflows/nf-core/bam_sort_stats_samtools/tests/main.nf.test b/subworkflows/nf-core/bam_sort_stats_samtools/tests/main.nf.test index 821a3cf5..c5841289 100644 --- a/subworkflows/nf-core/bam_sort_stats_samtools/tests/main.nf.test +++ b/subworkflows/nf-core/bam_sort_stats_samtools/tests/main.nf.test @@ -41,8 +41,7 @@ nextflow_workflow { { assert snapshot( workflow.out.flagstat, workflow.out.idxstats, - workflow.out.stats, - workflow.out.versions).match() } + workflow.out.stats).match() } ) } } @@ -72,8 +71,7 @@ nextflow_workflow { { assert snapshot( workflow.out.flagstat, workflow.out.idxstats, - workflow.out.stats, - workflow.out.versions).match() } + workflow.out.stats).match() } ) } } diff --git a/subworkflows/nf-core/bam_sort_stats_samtools/tests/main.nf.test.snap b/subworkflows/nf-core/bam_sort_stats_samtools/tests/main.nf.test.snap index 1cc7fa3d..f62d68c9 100644 --- a/subworkflows/nf-core/bam_sort_stats_samtools/tests/main.nf.test.snap +++ b/subworkflows/nf-core/bam_sort_stats_samtools/tests/main.nf.test.snap @@ -27,20 +27,13 @@ }, "test.stats:md5,1101fe711c4a389fdb5c4a1532107d1f" ] - ], - [ - "versions.yml:md5,199b33608db0a9457645d070ab24b71b", - "versions.yml:md5,54f02345c3a7699f9272e6ef9ce916c5", - "versions.yml:md5,6a93080732801bacb21c3acbe13858a5", - "versions.yml:md5,8f2a377b0149cf437f0730d293b62c81", - "versions.yml:md5,de3b0ae7c3ac4188662d57fd3219e312" ] ], "meta": { - "nf-test": "0.9.2", - "nextflow": "25.04.6" + "nf-test": "0.9.3", + "nextflow": "25.10.3" }, - "timestamp": "2025-09-10T13:37:01.228228" + "timestamp": "2026-02-03T11:33:01.647190952" }, "test_bam_sort_stats_samtools_paired_end": { "content": [ @@ -70,20 +63,13 @@ }, "test.stats:md5,f26c554c244ee86c89d62ebed509fd95" ] - ], - [ - "versions.yml:md5,199b33608db0a9457645d070ab24b71b", - "versions.yml:md5,54f02345c3a7699f9272e6ef9ce916c5", - "versions.yml:md5,6a93080732801bacb21c3acbe13858a5", - "versions.yml:md5,8f2a377b0149cf437f0730d293b62c81", - "versions.yml:md5,de3b0ae7c3ac4188662d57fd3219e312" ] ], "meta": { - "nf-test": "0.9.2", - "nextflow": "25.04.6" + "nf-test": "0.9.3", + "nextflow": "25.10.3" }, - "timestamp": "2025-09-10T13:37:08.591748" + "timestamp": "2026-02-03T11:33:08.706742267" }, "test_bam_sort_stats_samtools_single_end - stub": { "content": [ @@ -124,7 +110,7 @@ "id": "test", "single_end": false }, - "test.flagstat:md5,d41d8cd98f00b204e9800998ecf8427e" + "test.flagstat:md5,67394650dbae96d1a4fcc70484822159" ] ], "5": [ @@ -136,13 +122,6 @@ "test.idxstats:md5,d41d8cd98f00b204e9800998ecf8427e" ] ], - "6": [ - "versions.yml:md5,199b33608db0a9457645d070ab24b71b", - "versions.yml:md5,54f02345c3a7699f9272e6ef9ce916c5", - "versions.yml:md5,6a93080732801bacb21c3acbe13858a5", - "versions.yml:md5,8f2a377b0149cf437f0730d293b62c81", - "versions.yml:md5,de3b0ae7c3ac4188662d57fd3219e312" - ], "bai": [ [ { @@ -170,7 +149,7 @@ "id": "test", "single_end": false }, - "test.flagstat:md5,d41d8cd98f00b204e9800998ecf8427e" + "test.flagstat:md5,67394650dbae96d1a4fcc70484822159" ] ], "idxstats": [ @@ -190,21 +169,14 @@ }, "test.stats:md5,d41d8cd98f00b204e9800998ecf8427e" ] - ], - "versions": [ - "versions.yml:md5,199b33608db0a9457645d070ab24b71b", - "versions.yml:md5,54f02345c3a7699f9272e6ef9ce916c5", - "versions.yml:md5,6a93080732801bacb21c3acbe13858a5", - "versions.yml:md5,8f2a377b0149cf437f0730d293b62c81", - "versions.yml:md5,de3b0ae7c3ac4188662d57fd3219e312" ] } ], "meta": { - "nf-test": "0.9.2", - "nextflow": "25.04.6" + "nf-test": "0.9.3", + "nextflow": "25.10.3" }, - "timestamp": "2025-09-10T13:37:15.315753" + "timestamp": "2026-02-03T11:11:02.1412136" }, "test_bam_sort_stats_samtools_paired_end - stub": { "content": [ @@ -245,7 +217,7 @@ "id": "test", "single_end": false }, - "test.flagstat:md5,d41d8cd98f00b204e9800998ecf8427e" + "test.flagstat:md5,67394650dbae96d1a4fcc70484822159" ] ], "5": [ @@ -257,13 +229,6 @@ "test.idxstats:md5,d41d8cd98f00b204e9800998ecf8427e" ] ], - "6": [ - "versions.yml:md5,199b33608db0a9457645d070ab24b71b", - "versions.yml:md5,54f02345c3a7699f9272e6ef9ce916c5", - "versions.yml:md5,6a93080732801bacb21c3acbe13858a5", - "versions.yml:md5,8f2a377b0149cf437f0730d293b62c81", - "versions.yml:md5,de3b0ae7c3ac4188662d57fd3219e312" - ], "bai": [ [ { @@ -291,7 +256,7 @@ "id": "test", "single_end": false }, - "test.flagstat:md5,d41d8cd98f00b204e9800998ecf8427e" + "test.flagstat:md5,67394650dbae96d1a4fcc70484822159" ] ], "idxstats": [ @@ -311,20 +276,13 @@ }, "test.stats:md5,d41d8cd98f00b204e9800998ecf8427e" ] - ], - "versions": [ - "versions.yml:md5,199b33608db0a9457645d070ab24b71b", - "versions.yml:md5,54f02345c3a7699f9272e6ef9ce916c5", - "versions.yml:md5,6a93080732801bacb21c3acbe13858a5", - "versions.yml:md5,8f2a377b0149cf437f0730d293b62c81", - "versions.yml:md5,de3b0ae7c3ac4188662d57fd3219e312" ] } ], "meta": { - "nf-test": "0.9.2", - "nextflow": "25.04.6" + "nf-test": "0.9.3", + "nextflow": "25.10.3" }, - "timestamp": "2025-09-10T13:37:22.163708" + "timestamp": "2026-02-03T11:11:09.165267895" } } \ No newline at end of file diff --git a/subworkflows/nf-core/bam_stats_samtools/main.nf b/subworkflows/nf-core/bam_stats_samtools/main.nf index 44d4c010..34e8fe10 100644 --- a/subworkflows/nf-core/bam_stats_samtools/main.nf +++ b/subworkflows/nf-core/bam_stats_samtools/main.nf @@ -12,21 +12,14 @@ workflow BAM_STATS_SAMTOOLS { ch_fasta // channel: [ val(meta), path(fasta) ] main: - ch_versions = Channel.empty() - SAMTOOLS_STATS ( ch_bam_bai, ch_fasta ) - ch_versions = ch_versions.mix(SAMTOOLS_STATS.out.versions) SAMTOOLS_FLAGSTAT ( ch_bam_bai ) - ch_versions = ch_versions.mix(SAMTOOLS_FLAGSTAT.out.versions) SAMTOOLS_IDXSTATS ( ch_bam_bai ) - ch_versions = ch_versions.mix(SAMTOOLS_IDXSTATS.out.versions) emit: stats = SAMTOOLS_STATS.out.stats // channel: [ val(meta), path(stats) ] flagstat = SAMTOOLS_FLAGSTAT.out.flagstat // channel: [ val(meta), path(flagstat) ] idxstats = SAMTOOLS_IDXSTATS.out.idxstats // channel: [ val(meta), path(idxstats) ] - - versions = ch_versions // channel: [ path(versions.yml) ] } diff --git a/subworkflows/nf-core/bam_stats_samtools/tests/main.nf.test b/subworkflows/nf-core/bam_stats_samtools/tests/main.nf.test index 76e7a40a..2f329695 100644 --- a/subworkflows/nf-core/bam_stats_samtools/tests/main.nf.test +++ b/subworkflows/nf-core/bam_stats_samtools/tests/main.nf.test @@ -36,8 +36,7 @@ nextflow_workflow { { assert snapshot( workflow.out.flagstat, workflow.out.idxstats, - workflow.out.stats, - workflow.out.versions).match() } + workflow.out.stats).match() } ) } } @@ -66,8 +65,7 @@ nextflow_workflow { { assert snapshot( workflow.out.flagstat, workflow.out.idxstats, - workflow.out.stats, - workflow.out.versions).match() } + workflow.out.stats).match() } ) } } @@ -96,8 +94,7 @@ nextflow_workflow { { assert snapshot( workflow.out.flagstat, workflow.out.idxstats, - workflow.out.stats, - workflow.out.versions).match() } + workflow.out.stats).match() } ) } } diff --git a/subworkflows/nf-core/bam_stats_samtools/tests/main.nf.test.snap b/subworkflows/nf-core/bam_stats_samtools/tests/main.nf.test.snap index dadc71a7..9c8ff1b5 100644 --- a/subworkflows/nf-core/bam_stats_samtools/tests/main.nf.test.snap +++ b/subworkflows/nf-core/bam_stats_samtools/tests/main.nf.test.snap @@ -29,11 +29,6 @@ "test.idxstats:md5,d41d8cd98f00b204e9800998ecf8427e" ] ], - "3": [ - "versions.yml:md5,088c14fc7d21fa2e662860d7cbf9a181", - "versions.yml:md5,ade6457ea5ae73a41c505bb22681d0fa", - "versions.yml:md5,c7826ee705b3af5245db8d9cf64ed520" - ], "flagstat": [ [ { @@ -60,19 +55,14 @@ }, "test.stats:md5,d41d8cd98f00b204e9800998ecf8427e" ] - ], - "versions": [ - "versions.yml:md5,088c14fc7d21fa2e662860d7cbf9a181", - "versions.yml:md5,ade6457ea5ae73a41c505bb22681d0fa", - "versions.yml:md5,c7826ee705b3af5245db8d9cf64ed520" ] } ], "meta": { - "nf-test": "0.9.2", - "nextflow": "25.04.6" + "nf-test": "0.9.3", + "nextflow": "25.10.3" }, - "timestamp": "2025-09-15T15:20:35.417926" + "timestamp": "2026-02-03T11:10:30.076183827" }, "test_bam_stats_samtools_single_end - stub": { "content": [ @@ -104,11 +94,6 @@ "test.idxstats:md5,d41d8cd98f00b204e9800998ecf8427e" ] ], - "3": [ - "versions.yml:md5,088c14fc7d21fa2e662860d7cbf9a181", - "versions.yml:md5,ade6457ea5ae73a41c505bb22681d0fa", - "versions.yml:md5,c7826ee705b3af5245db8d9cf64ed520" - ], "flagstat": [ [ { @@ -135,19 +120,14 @@ }, "test.stats:md5,d41d8cd98f00b204e9800998ecf8427e" ] - ], - "versions": [ - "versions.yml:md5,088c14fc7d21fa2e662860d7cbf9a181", - "versions.yml:md5,ade6457ea5ae73a41c505bb22681d0fa", - "versions.yml:md5,c7826ee705b3af5245db8d9cf64ed520" ] } ], "meta": { - "nf-test": "0.9.2", - "nextflow": "25.04.6" + "nf-test": "0.9.3", + "nextflow": "25.10.3" }, - "timestamp": "2025-09-15T15:20:25.439222" + "timestamp": "2026-02-03T11:10:24.379362883" }, "test_bam_stats_samtools_paired_end_cram - stub": { "content": [ @@ -179,11 +159,6 @@ "test.idxstats:md5,d41d8cd98f00b204e9800998ecf8427e" ] ], - "3": [ - "versions.yml:md5,088c14fc7d21fa2e662860d7cbf9a181", - "versions.yml:md5,ade6457ea5ae73a41c505bb22681d0fa", - "versions.yml:md5,c7826ee705b3af5245db8d9cf64ed520" - ], "flagstat": [ [ { @@ -210,19 +185,14 @@ }, "test.stats:md5,d41d8cd98f00b204e9800998ecf8427e" ] - ], - "versions": [ - "versions.yml:md5,088c14fc7d21fa2e662860d7cbf9a181", - "versions.yml:md5,ade6457ea5ae73a41c505bb22681d0fa", - "versions.yml:md5,c7826ee705b3af5245db8d9cf64ed520" ] } ], "meta": { - "nf-test": "0.9.2", - "nextflow": "25.04.6" + "nf-test": "0.9.3", + "nextflow": "25.10.3" }, - "timestamp": "2025-09-15T15:20:46.02738" + "timestamp": "2026-02-03T11:10:35.91658956" }, "test_bam_stats_samtools_single_end": { "content": [ @@ -252,18 +222,13 @@ }, "test.stats:md5,7a05a22bdb17e8df6e8c2d100ff09a31" ] - ], - [ - "versions.yml:md5,088c14fc7d21fa2e662860d7cbf9a181", - "versions.yml:md5,ade6457ea5ae73a41c505bb22681d0fa", - "versions.yml:md5,c7826ee705b3af5245db8d9cf64ed520" ] ], "meta": { - "nf-test": "0.9.2", - "nextflow": "25.04.6" + "nf-test": "0.9.3", + "nextflow": "25.10.3" }, - "timestamp": "2025-09-15T15:20:00.064679" + "timestamp": "2026-02-03T11:32:20.243663217" }, "test_bam_stats_samtools_paired_end": { "content": [ @@ -293,18 +258,13 @@ }, "test.stats:md5,a391612b5ef5b181e854ccaad8c8a068" ] - ], - [ - "versions.yml:md5,088c14fc7d21fa2e662860d7cbf9a181", - "versions.yml:md5,ade6457ea5ae73a41c505bb22681d0fa", - "versions.yml:md5,c7826ee705b3af5245db8d9cf64ed520" ] ], "meta": { - "nf-test": "0.9.2", - "nextflow": "25.04.6" + "nf-test": "0.9.3", + "nextflow": "25.10.3" }, - "timestamp": "2025-09-15T15:20:08.141202" + "timestamp": "2026-02-03T11:32:26.434187887" }, "test_bam_stats_samtools_paired_end_cram": { "content": [ @@ -334,17 +294,12 @@ }, "test.stats:md5,2b0e31ab01b867a6ff312023ae03838d" ] - ], - [ - "versions.yml:md5,088c14fc7d21fa2e662860d7cbf9a181", - "versions.yml:md5,ade6457ea5ae73a41c505bb22681d0fa", - "versions.yml:md5,c7826ee705b3af5245db8d9cf64ed520" ] ], "meta": { - "nf-test": "0.9.2", - "nextflow": "25.04.6" + "nf-test": "0.9.3", + "nextflow": "25.10.3" }, - "timestamp": "2025-09-15T15:20:16.06318" + "timestamp": "2026-02-03T11:32:32.441454186" } } \ No newline at end of file diff --git a/workflows/genomeassembler.nf b/workflows/genomeassembler.nf index 57298bad..9b92cddc 100644 --- a/workflows/genomeassembler.nf +++ b/workflows/genomeassembler.nf @@ -155,11 +155,28 @@ workflow GENOMEASSEMBLER { ch_versions = 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')}" + } + ch_collated_versions = softwareVersionsToYAML(ch_versions.mix(topic_versions.versions_file)) + .mix(topic_versions_string) /* Report */ - softwareVersionsToYAML(ch_versions) + ch_collated_versions .collectFile( storeDir: "${params.outdir}/pipeline_info", name: 'nf_core_' + 'pipeline_software_' + 'versions.yml', @@ -239,35 +256,6 @@ workflow GENOMEASSEMBLER { ch_main.map { it -> [sample: [id: it.meta.id, group: it.group]]}.collect() ) - // - // Collate and save software 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_' + 'genomeassembler_software_' + 'versions.yml', - sort: true, - newLine: true - ) - _report = REPORT.out.report_html.toList() emit: From d8582ad0c1e8c52bfe89f2bd839c6a093c7b1578 Mon Sep 17 00:00:00 2001 From: Niklas Schandry Date: Fri, 13 Feb 2026 10:18:19 +0100 Subject: [PATCH 122/162] remove ci.yml --- .github/workflows/ci.yml | 95 ---------------------------------------- 1 file changed, 95 deletions(-) delete mode 100644 .github/workflows/ci.yml diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml deleted file mode 100644 index 9ce539c1..00000000 --- a/.github/workflows/ci.yml +++ /dev/null @@ -1,95 +0,0 @@ -name: nf-core CI -# This workflow runs the pipeline with the minimal test dataset to check that it completes without any syntax errors -on: - push: - branches: - - dev - pull_request: - release: - types: [published] - workflow_dispatch: - -env: - NXF_ANSI_LOG: false - NXF_SINGULARITY_CACHEDIR: ${{ github.workspace }}/.singularity - NXF_SINGULARITY_LIBRARYDIR: ${{ github.workspace }}/.singularity - -concurrency: - group: "${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}" - cancel-in-progress: true - -jobs: - test: - name: "Run pipeline with test data (${{ matrix.NXF_VER }} | ${{ matrix.ASSEMBLER }} | ${{ matrix.profile }})" - # Only run on push if this is the nf-core dev branch (merged PRs) - if: "${{ github.event_name != 'push' || (github.event_name == 'push' && github.repository == 'nf-core/genomeassembler') }}" - runs-on: ubuntu-latest - strategy: - matrix: - NXF_VER: - - "25.04.0" - - "latest-everything" - ASSEMBLER: - - "hifi_flye" - - "hifi_hifiasm" - - "ont_flye" - - "ont_hifiasm" - - "hifiont_hifiasm" - - "hifiont_flye_on_hifiasm" - - "hifiont_hifiasm_on_hifiasm" - profile: - - "conda" - - "docker" - - "singularity" - isMaster: - - ${{ github.base_ref == 'master' }} - # Exclude conda and singularity on dev - exclude: - - isMaster: false - profile: "conda" - - isMaster: false - profile: "singularity" - - steps: - - name: Check out pipeline code - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4 - with: - fetch-depth: 0 - - - name: Set up Nextflow - uses: nf-core/setup-nextflow@v2 - with: - version: "${{ matrix.NXF_VER }}" - - - name: Set up Apptainer - if: matrix.profile == 'singularity' - uses: eWaterCycle/setup-apptainer@main - - - name: Set up Singularity - if: matrix.profile == 'singularity' - run: | - mkdir -p $NXF_SINGULARITY_CACHEDIR - mkdir -p $NXF_SINGULARITY_LIBRARYDIR - - - name: Set up Miniconda - if: matrix.profile == 'conda' - uses: conda-incubator/setup-miniconda@a4260408e20b96e80095f42ff7f1a15b27dd94ca # v3 - with: - miniconda-version: "latest" - auto-update-conda: true - conda-solver: libmamba - channels: conda-forge,bioconda - - - name: Set up Conda - if: matrix.profile == 'conda' - run: | - echo $(realpath $CONDA)/condabin >> $GITHUB_PATH - echo $(realpath python) >> $GITHUB_PATH - - - name: "Run pipeline with test data ${{ matrix.NXF_VER }} | ${{ matrix.ASSEMBLER }} | ${{ matrix.profile }}" - continue-on-error: ${{ matrix.NXF_VER == 'latest-everything' }} - run: | - nextflow run ${GITHUB_WORKSPACE} -profile ${{ matrix.ASSEMBLER }},test,${{matrix.profile}} --outdir ./results_${{matrix.profile}}_${{ matrix.ASSEMBLER }} - - - name: Clean up Disk space - uses: jlumbroso/free-disk-space@54081f138730dfa15788a46383842cd2f914a1be # v1.3.1 From 582a9366255770fbd9eded58287359300d1c4e16 Mon Sep 17 00:00:00 2001 From: Niklas Schandry Date: Fri, 13 Feb 2026 10:19:36 +0100 Subject: [PATCH 123/162] fix broken merge --- .github/workflows/fix_linting.yml | 6 ------ 1 file changed, 6 deletions(-) diff --git a/.github/workflows/fix_linting.yml b/.github/workflows/fix_linting.yml index 53dde2f3..ed185515 100644 --- a/.github/workflows/fix_linting.yml +++ b/.github/workflows/fix_linting.yml @@ -32,15 +32,9 @@ jobs: GITHUB_TOKEN: ${{ secrets.nf_core_bot_auth_token }} # Install and run pre-commit -<<<<<<< HEAD - uses: actions/setup-python@e797f83bcb11b83ae66e0230d6156d7c80228e7c # v6 with: python-version: "3.14" -======= - - uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5 - with: - python-version: "3.13" ->>>>>>> 533baa7 (Important! Template update for nf-core/tools v3.3.1 (#164)) - name: Install pre-commit run: pip install pre-commit From 2991bd5122e8a7d1e444b52eb27880eb0d8c4e68 Mon Sep 17 00:00:00 2001 From: Niklas Schandry Date: Fri, 13 Feb 2026 10:34:41 +0100 Subject: [PATCH 124/162] remove trace timestamp --- nextflow.config | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/nextflow.config b/nextflow.config index 107ffa24..09ba9ed4 100644 --- a/nextflow.config +++ b/nextflow.config @@ -296,23 +296,22 @@ process.shell = [ // Disable process selector warnings by default. Use debug profile to enable warnings. nextflow.enable.configProcessNamesValidation = false -def trace_timestamp = new java.util.Date().format( 'yyyy-MM-dd_HH-mm-ss') timeline { enabled = true - file = "${params.outdir}/pipeline_info/execution_timeline_${trace_timestamp}.html" + file = "${params.outdir}/pipeline_info/execution_timeline_${params.trace_report_suffix}.html" } report { enabled = true - file = "${params.outdir}/pipeline_info/execution_report_${trace_timestamp}.html" + file = "${params.outdir}/pipeline_info/execution_report_${params.trace_report_suffix}.html" } trace { enabled = true - file = "${params.outdir}/pipeline_info/execution_trace_${trace_timestamp}.txt" + file = "${params.outdir}/pipeline_info/execution_trace_${params.trace_report_suffix}.txt" } dag { enabled = true - file = "${params.outdir}/pipeline_info/pipeline_dag_${trace_timestamp}.html" + file = "${params.outdir}/pipeline_info/pipeline_dag_${params.trace_report_suffix}.html" } manifest { From ccec26090384a3e34a71c351a544e2ab2726f1e2 Mon Sep 17 00:00:00 2001 From: Niklas Schandry Date: Fri, 13 Feb 2026 12:24:20 +0100 Subject: [PATCH 125/162] update test --- tests/.nftignore | 1 + tests/default.nf.test | 7 +++++-- tests/default.nf.test.snap | 13 ++++++------- 3 files changed, 12 insertions(+), 9 deletions(-) diff --git a/tests/.nftignore b/tests/.nftignore index 7839efd2..3b433e52 100644 --- a/tests/.nftignore +++ b/tests/.nftignore @@ -7,3 +7,4 @@ pipeline_info/*.{html,json,txt,yml} */*.{log,bin,gz,gff3,agp,html,gz} *.{log,bin,gz,gff3,agp,html,gz} */*/*/*.assembly_info.txt +*tigmint-ntLink-arks.fa diff --git a/tests/default.nf.test b/tests/default.nf.test index 67fb220f..3fa9782d 100644 --- a/tests/default.nf.test +++ b/tests/default.nf.test @@ -16,14 +16,17 @@ nextflow_pipeline { // 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') + def stable_path = getAllFilesFromDir( + params.outdir, + ignoreFile: 'tests/.nftignore', + ) 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_genomeassembler_software_versions.yml"), + removeNextflowVersion("$outputDir/pipeline_info/nf_core_pipeline_software_versions.yml"), // All stable path name, with a relative path stable_name, // All files with stable contents diff --git a/tests/default.nf.test.snap b/tests/default.nf.test.snap index 19a24172..ecffe035 100644 --- a/tests/default.nf.test.snap +++ b/tests/default.nf.test.snap @@ -16,12 +16,12 @@ "flye": "2.9.5-b1801" }, "GFA_2_FA_HIFI": { - "awk": "mawk 1.3.4", - "gzip": 1.13 + "awk": null, + "gzip": null }, "GFA_2_FA_ONT": { - "awk": "mawk 1.3.4", - "gzip": 1.13 + "awk": null, + "gzip": null }, "HIFIASM": { "hifiasm": "0.25.0-r726" @@ -44,7 +44,6 @@ }, [ "pipeline_info", - "pipeline_info/nf_core_genomeassembler_software_versions.yml", "pipeline_info/nf_core_pipeline_software_versions.yml", "test_flye_hifiasm", "test_flye_hifiasm/assembly", @@ -326,7 +325,7 @@ "test_hifiasm_ul_assembly.unmapped.txt:md5,d41d8cd98f00b204e9800998ecf8427e", "test_hifiasm_ul_hifi.fastplong.json:md5,f52a2b6d9aa29fbe5749684ee479de90", "test_hifiasm_ul_ont.fastplong.json:md5,86da6d58ac4630bfeae3ee08014ecb0b", - "test_hifiasm_ul_longstitch.tigmint-ntLink-arks.fa:md5,2bc73aa0085ed4ef5261f3fadfc64b46", + "test_hifiasm_ul_longstitch.tigmint-ntLink-arks.fa:md5,b5ae075fa81a8a0dd3a69283d48ae63a", "test_hifiasm_ul_longstitch.tigmint-ntLink.fa:md5,6191cac0672c778be5fc93b74121ed95", "test_hifiasm_ul_longstitch.unmapped.txt:md5,d41d8cd98f00b204e9800998ecf8427e", "test_ont_flye.params.json:md5,afa91c041bce5e190f4a699d11b69db6", @@ -349,6 +348,6 @@ "nf-test": "0.9.2", "nextflow": "25.04.8" }, - "timestamp": "2026-02-05T14:53:40.86797631" + "timestamp": "2026-02-13T11:26:09.065736384" } } \ No newline at end of file From 97105a06176b9916b6eb232c4315a38d97985efb Mon Sep 17 00:00:00 2001 From: Niklas Schandry Date: Fri, 13 Feb 2026 12:41:21 +0100 Subject: [PATCH 126/162] fix nf-test.yml --- .github/workflows/nf-test.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/nf-test.yml b/.github/workflows/nf-test.yml index 0854baf7..df97d801 100644 --- a/.github/workflows/nf-test.yml +++ b/.github/workflows/nf-test.yml @@ -35,7 +35,7 @@ jobs: nf-test-changes: name: nf-test-changes runs-on: # use self-hosted runners - - runs-on=$-nf-test-changes + - runs-on=nf-test-changes - runner4cpu-linux-x64 outputs: shard: ${{ steps.set-shards.outputs.shard }} From 88fde2f82ea924f332df9e6cb5ad62078ea35f7a Mon Sep 17 00:00:00 2001 From: Niklas Schandry Date: Fri, 13 Feb 2026 13:52:25 +0100 Subject: [PATCH 127/162] fix ext.args for minimap2 going into samtools index --- conf/modules/QC/alignments.config | 64 +++++++++++++++++++++++++++++++ 1 file changed, 64 insertions(+) diff --git a/conf/modules/QC/alignments.config b/conf/modules/QC/alignments.config index e9f52ef9..f324af4c 100644 --- a/conf/modules/QC/alignments.config +++ b/conf/modules/QC/alignments.config @@ -7,6 +7,14 @@ process { mode: params.publish_dir_mode, saveAs: { filename -> filename.equals('versions.yml') ? null : filename } ] + } + withName: '.*MAP_TO_REF.*:ALIGN' { + ext.prefix = { "${meta.id}_to_reference" } + publishDir = [ + path: { "${params.outdir}/${meta.id}/QC/alignments/reference/" }, + mode: params.publish_dir_mode, + saveAs: { filename -> filename.equals('versions.yml') ? null : filename } + ] ext.args = { (meta.qc_reads == 'ont' ? "-ax lr:hq" : "-ax map-hifi") } @@ -19,6 +27,14 @@ process { mode: params.publish_dir_mode, saveAs: { filename -> filename.equals('versions.yml') ? null : filename } ] + } + withName: '.*ASSEMBLE:.*MAP_TO_ASSEMBLY.*:ALIGN' { + ext.prefix = { "${meta.id}_assembly" } + publishDir = [ + path: { "${params.outdir}/${meta.id}/QC/alignments/" }, + mode: params.publish_dir_mode, + saveAs: { filename -> filename.equals('versions.yml') ? null : filename } + ] ext.args = { (meta.qc_reads == 'ont' ? "-ax lr:hq" : "-ax map-hifi") } @@ -30,6 +46,14 @@ process { mode: params.publish_dir_mode, saveAs: { filename -> filename.equals('versions.yml') ? null : filename } ] + } + withName: '.*MEDAKA:.*MAP_TO_ASSEMBLY.*:ALIGN' { + ext.prefix = { "${meta.id}_medaka" } + publishDir = [ + path: { "${params.outdir}/${meta.id}/QC/alignments/" }, + mode: params.publish_dir_mode, + saveAs: { filename -> filename.equals('versions.yml') ? null : filename } + ] ext.args = { (meta.qc_reads == 'ont' ? "-ax lr:hq" : "-ax map-hifi") } @@ -41,6 +65,14 @@ process { mode: params.publish_dir_mode, saveAs: { filename -> filename.equals('versions.yml') ? null : filename } ] + } + withName: '.*PILON:.*MAP_TO_ASSEMBLY.*:ALIGN' { + ext.prefix = { "${meta.id}_pilon" } + publishDir = [ + path: { "${params.outdir}/${meta.id}/QC/alignments/" }, + mode: params.publish_dir_mode, + saveAs: { filename -> filename.equals('versions.yml') ? null : filename } + ] ext.args = { (meta.qc_reads == 'ont' ? "-ax lr:hq" : "-ax map-hifi") } @@ -52,6 +84,14 @@ process { mode: params.publish_dir_mode, saveAs: { filename -> filename.equals('versions.yml') ? null : filename } ] + } + withName: '.*LONGSTITCH:.*MAP_TO_ASSEMBLY.*:ALIGN' { + ext.prefix = { "${meta.id}_longstitch" } + publishDir = [ + path: { "${params.outdir}/${meta.id}/QC/alignments/" }, + mode: params.publish_dir_mode, + saveAs: { filename -> filename.equals('versions.yml') ? null : filename } + ] ext.args = { (meta.qc_reads == 'ont' ? "-ax lr:hq" : "-ax map-hifi") } @@ -63,6 +103,14 @@ process { mode: params.publish_dir_mode, saveAs: { filename -> filename.equals('versions.yml') ? null : filename } ] + } + withName: '.*LINKS:.*MAP_TO_ASSEMBLY.*:ALIGN' { + ext.prefix = { "${meta.id}_links" } + publishDir = [ + path: { "${params.outdir}/${meta.id}/QC/alignments/" }, + mode: params.publish_dir_mode, + saveAs: { filename -> filename.equals('versions.yml') ? null : filename } + ] ext.args = { (meta.qc_reads == 'ont' ? "-ax lr:hq" : "-ax map-hifi") } @@ -74,6 +122,14 @@ process { mode: params.publish_dir_mode, saveAs: { filename -> filename.equals('versions.yml') ? null : filename } ] + } + withName: '.*HIC:.*MAP_TO_ASSEMBLY.*:ALIGN' { + ext.prefix = { "${meta.id}_hic" } + publishDir = [ + path: { "${params.outdir}/${meta.id}/QC/alignments/" }, + mode: params.publish_dir_mode, + saveAs: { filename -> filename.equals('versions.yml') ? null : filename } + ] ext.args = { (meta.qc_reads == 'ont' ? "-ax lr:hq" : "-ax map-hifi") } @@ -85,6 +141,14 @@ process { mode: params.publish_dir_mode, saveAs: { filename -> filename.equals('versions.yml') ? null : filename } ] + } + withName: '.*RAGTAG:.*MAP_TO_ASSEMBLY.*:ALIGN' { + ext.prefix = { "${meta.id}_ragtag" } + publishDir = [ + path: { "${params.outdir}/${meta.id}/QC/alignments/" }, + mode: params.publish_dir_mode, + saveAs: { filename -> filename.equals('versions.yml') ? null : filename } + ] ext.args = { (meta.qc_reads == 'ont' ? "-ax lr:hq" : "-ax map-hifi") } From 55a571bb8d073e2bd2b936dbba277ca7a7229eb3 Mon Sep 17 00:00:00 2001 From: Niklas Schandry Date: Fri, 13 Feb 2026 13:52:52 +0100 Subject: [PATCH 128/162] align nf-test.yml with TEMPLATE --- .github/workflows/nf-test.yml | 43 +++++++++++++++++++---------------- 1 file changed, 23 insertions(+), 20 deletions(-) diff --git a/.github/workflows/nf-test.yml b/.github/workflows/nf-test.yml index df97d801..c98d76ec 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/**" @@ -35,8 +28,8 @@ jobs: nf-test-changes: name: nf-test-changes runs-on: # use self-hosted runners - - runs-on=nf-test-changes - - runner4cpu-linux-x64 + - 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 }} @@ -69,7 +62,7 @@ jobs: needs: [nf-test-changes] if: ${{ needs.nf-test-changes.outputs.total_shards != '0' }} runs-on: # use self-hosted runners - - runs-on=$-nf-test + - runs-on=${{ github.run_id }}-nf-test - runner=4cpu-linux-x64 strategy: fail-fast: false @@ -97,7 +90,9 @@ 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 }} NXF_VERSION: ${{ matrix.NXF_VER }} @@ -105,14 +100,30 @@ jobs: 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: # use self-hosted runners - - runs-on=$-confirm-pass + - 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 @@ -131,11 +142,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 ./ From 6808b04be7558f2828f90627ad9a31441f2eca1a Mon Sep 17 00:00:00 2001 From: Niklas Schandry Date: Fri, 13 Feb 2026 16:23:01 +0100 Subject: [PATCH 129/162] tools stuff --- .github/workflows/linting_comment.yml | 2 +- modules.json | 5 +++++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/.github/workflows/linting_comment.yml b/.github/workflows/linting_comment.yml index 3f43d741..e6e9bc26 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/modules.json b/modules.json index cbc42265..96526542 100644 --- a/modules.json +++ b/modules.json @@ -31,6 +31,11 @@ "installed_by": ["modules"], "patch": "modules/nf-core/fastplong/fastplong.diff" }, + "fastqc": { + "branch": "master", + "git_sha": "3009f27c4e4b6e99da4eeebe82799e13924a4a1f", + "installed_by": ["modules"] + }, "flye": { "branch": "master", "git_sha": "1ab453187e9eada07bdd98609ce770971bfe86e4", From 98b4c4d5aa39f9dee4a9537552c2494f20b0559f Mon Sep 17 00:00:00 2001 From: Niklas Schandry Date: Mon, 16 Feb 2026 09:55:28 +0100 Subject: [PATCH 130/162] scope config properly --- .github/workflows/linting.yml | 16 ++++++++-------- .github/workflows/linting_comment.yml | 2 +- .prettierignore | 5 ----- conf/modules/polishing.config | 10 +++++++++- ro-crate-metadata.json | 25 +++++++++++++++++++------ 5 files changed, 37 insertions(+), 21 deletions(-) diff --git a/.github/workflows/linting.yml b/.github/workflows/linting.yml index 7a527a34..8b0f88c3 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@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5 + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4 - - name: Set up Python 3.14 - uses: actions/setup-python@e797f83bcb11b83ae66e0230d6156d7c80228e7c # v6 + - name: Set up Python 3.13 + uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5 with: - python-version: "3.14" + python-version: "3.13" - 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@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5 + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4 - name: Install Nextflow uses: nf-core/setup-nextflow@v2 - - uses: actions/setup-python@e797f83bcb11b83ae66e0230d6156d7c80228e7c # v6 + - uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5 with: - python-version: "3.14" + python-version: "3.13" architecture: "x64" - name: read .nf-core.yml @@ -71,7 +71,7 @@ jobs: - name: Upload linting log file artifact if: ${{ always() }} - uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5 + uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4 with: name: linting-logs path: | diff --git a/.github/workflows/linting_comment.yml b/.github/workflows/linting_comment.yml index e6e9bc26..d43797d9 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@773744901bac0e8cbb5a0dc842800d45e9b2b405 # v2 + uses: marocchino/sticky-pull-request-comment@52423e01640425a022ef5fd42c6fb5f633a02728 # v2 with: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} number: ${{ steps.pr_number.outputs.pr_number }} diff --git a/.prettierignore b/.prettierignore index 0e2ed66e..edd29f01 100644 --- a/.prettierignore +++ b/.prettierignore @@ -10,9 +10,4 @@ testing/ testing* *.pyc bin/ -.nf-test/ ro-crate-metadata.json -modules/nf-core/ -subworkflows/nf-core/ -*.svg -tests/ diff --git a/conf/modules/polishing.config b/conf/modules/polishing.config index 482fd81b..d6685492 100644 --- a/conf/modules/polishing.config +++ b/conf/modules/polishing.config @@ -26,7 +26,7 @@ process { ] } // Pilon mapping - withName: '.*PILON:MAP_SR.*' { + withName: '.*PILON:MAP_SR:ALIGN.*' { publishDir = [ path: { "${params.outdir}/${meta.id}/QC/alignments/shortreads/" }, mode: params.publish_dir_mode, @@ -35,6 +35,14 @@ process { ext.prefix = { "${meta.id}_shortreads" } ext.args = { "-ax sr " } } + withName: '.*PILON:MAP_SR.*' { + publishDir = [ + path: { "${params.outdir}/${meta.id}/QC/alignments/shortreads/" }, + mode: params.publish_dir_mode, + saveAs: { filename -> filename.equals('versions.yml') ? null : filename } + ] + ext.prefix = { "${meta.id}_shortreads" } + } withName: PILON { ext.prefix = { "${meta.id}_pilon" } publishDir = [ diff --git a/ro-crate-metadata.json b/ro-crate-metadata.json index ad17825c..64504d17 100644 --- a/ro-crate-metadata.json +++ b/ro-crate-metadata.json @@ -23,7 +23,7 @@ "@type": "Dataset", "creativeWorkStatus": "Stable", "datePublished": "2025-11-20T09:31:46+00:00", - "description": "

\n \n \n \"nf-core/genomeassembler\"\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/genomeassembler)\n[![GitHub Actions CI Status](https://github.com/nf-core/genomeassembler/actions/workflows/nf-test.yml/badge.svg)](https://github.com/nf-core/genomeassembler/actions/workflows/nf-test.yml)\n[![GitHub Actions Linting Status](https://github.com/nf-core/genomeassembler/actions/workflows/linting.yml/badge.svg)](https://github.com/nf-core/genomeassembler/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/genomeassembler/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.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.5.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/genomeassembler)\n\n[![Get help on Slack](http://img.shields.io/badge/slack-nf--core%20%23genomeassembler-4A154B?labelColor=000000&logo=slack)](https://nfcore.slack.com/channels/genomeassembler)[![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/genomeassembler** is a bioinformatics pipeline that ...\n\n\n\n\n1. Read QC ([`FastQC`](https://www.bioinformatics.babraham.ac.uk/projects/fastqc/))\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/genomeassembler \\\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/genomeassembler/usage) and the [parameter documentation](https://nf-co.re/genomeassembler/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/genomeassembler/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/genomeassembler/output).\n\n## Credits\n\nnf-core/genomeassembler was originally written by Niklas Schandry.\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 `#genomeassembler` channel](https://nfcore.slack.com/channels/genomeassembler) (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/genomeassembler\"\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/genomeassembler)\n[![GitHub Actions CI Status](https://github.com/nf-core/genomeassembler/actions/workflows/nf-test.yml/badge.svg)](https://github.com/nf-core/genomeassembler/actions/workflows/nf-test.yml)\n[![GitHub Actions Linting Status](https://github.com/nf-core/genomeassembler/actions/workflows/linting.yml/badge.svg)](https://github.com/nf-core/genomeassembler/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/genomeassembler/results)[![Cite with Zenodo](http://img.shields.io/badge/DOI-10.5281/zenodo.XXXXXXX-1073c8?labelColor=000000)](https://doi.org/10.5281/zenodo.14986998)\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.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.5.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/genomeassembler)\n\n[![Get help on Slack](http://img.shields.io/badge/slack-nf--core%20%23genomeassembler-4A154B?labelColor=000000&logo=slack)](https://nfcore.slack.com/channels/genomeassembler)[![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/genomeassembler** is a bioinformatics pipeline that carries out genome assembly, polishing and scaffolding from long reads (ONT or pacbio). Assembly can be done via `flye` or `hifiasm`, polishing can be carried out with `medaka` (ONT), or `pilon` (requires short-reads), and scaffolding can be done using `LINKS`, `Longstitch`, or `RagTag` (if a reference is available). Quality control includes `BUSCO`, `QUAST` and `merqury` (requires short-reads).\nCurrently, this pipeline does not implement phasing of polyploid genomes or HiC scaffolding.\n\n\n \n \"nf-core/genomeassembler\"\n\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\nFirst, prepare a samplesheet with your input data that looks as follows:\n\n`samplesheet.csv`:\n\n```csv\nsample,ontreads,hifireads,ref_fasta,ref_gff,shortread_F,shortread_R,paired\nsampleName,ontreads.fa.gz,hifireads.fa.gz,assembly.fasta.gz,reference.fasta,reference.gff,short_F1.fastq,short_F2.fastq,true\n```\n\nEach row represents one genome to be assembled. `sample` should contain the name of the sample, `ontreads` should contain a path to ONT reads (fastq.gz), `hifireads` a path to HiFi reads (fastq.gz), `ref_fasta` and `ref_gff` contain reference genome fasta and annotations. `shortread_F` and `shortread_R` contain paths to short-read data, `paired` indicates if short-reads are paired. Columns can be omitted if they contain no data, with the exception of `shortread_R`, which needs to be present if `shortread_F` is there, even if it is empty.\n\nNow, you can run the pipeline using:\n\n```bash\nnextflow run nf-core/genomeassembler \\\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/genomeassembler/usage) and the [parameter documentation](https://nf-co.re/genomeassembler/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/genomeassembler/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/genomeassembler/output).\n\n## Credits\n\nnf-core/genomeassembler was written, and is currently maintained by [Niklas Schandry](https://github.com/nschan), of the Faculty of Biology of the Ludwig-Maximilians University (LMU) in Munich, Germany, with funding support from the German Research Foundation (Deutsche Forschungsgemeinschaft\u00a0[DFG], via Transregional Research Center TRR356 grant 491090170-A05 to Niklas Schandry).\n\nI thank the following people for their extensive assistance and constructive reviews during the development of this pipeline:\n\n- [Mahesh Binzer-Panchal](https://github.com/mahesh-panchal)\n- [Matthias H\u00f6rtenhuber](https://github.com/mashehu)\n- [Louis Le N\u00e9zet](https://github.com/LouisLeNezet)\n- [J\u00falia Mir Pedrol](https://github.com/mirpedrol)\n- [Daniel Straub](https://github.com/d4straub)\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 `#genomeassembler` channel](https://nfcore.slack.com/channels/genomeassembler) (you can join with [this invite](https://nf-co.re/join/slack)).\n\n## Citations\n\nIf you use nf-core/genomeassembler for your analysis, please cite it using the following doi: [10.5281/zenodo.14986998](https://doi.org/10.5281/zenodo.14986998)\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" @@ -146,17 +146,30 @@ "dateCreated": "", "dateModified": "2025-11-20T09:31:46Z", "dct:conformsTo": "https://bioschemas.org/profiles/ComputationalWorkflow/1.0-RELEASE/", - "keywords": ["nf-core", "nextflow", "genome-assembly"], - "license": ["MIT"], - "name": ["nf-core/genomeassembler"], + "keywords": [ + "nf-core", + "nextflow", + "genome-assembly" + ], + "license": [ + "MIT" + ], + "name": [ + "nf-core/genomeassembler" + ], "programmingLanguage": { "@id": "https://w3id.org/workflowhub/workflow-ro-crate#nextflow" }, "sdPublisher": { "@id": "https://nf-co.re/" }, - "url": ["https://github.com/nf-core/genomeassembler", "https://nf-co.re/genomeassembler/1.1.0/"], - "version": ["1.1.0"] + "url": [ + "https://github.com/nf-core/genomeassembler", + "https://nf-co.re/genomeassembler/1.1.0/" + ], + "version": [ + "1.1.0" + ] }, { "@id": "https://w3id.org/workflowhub/workflow-ro-crate#nextflow", From 20a279775230096fbedee6f120eabc333af142d6 Mon Sep 17 00:00:00 2001 From: Niklas Schandry Date: Mon, 16 Feb 2026 09:55:56 +0100 Subject: [PATCH 131/162] modules update --- modules.json | 2 +- modules/nf-core/hifiasm/main.nf | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/modules.json b/modules.json index 96526542..0b8b52ca 100644 --- a/modules.json +++ b/modules.json @@ -43,7 +43,7 @@ }, "hifiasm": { "branch": "master", - "git_sha": "e753770db613ce014b3c4bc94f6cba443427b726", + "git_sha": "dbc43f924a7410a2b828f2a022305f1d6e71ed84", "installed_by": ["modules"] }, "liftoff": { diff --git a/modules/nf-core/hifiasm/main.nf b/modules/nf-core/hifiasm/main.nf index df295b70..26557d84 100644 --- a/modules/nf-core/hifiasm/main.nf +++ b/modules/nf-core/hifiasm/main.nf @@ -33,8 +33,8 @@ process HIFIASM { def args = task.ext.args ?: '' prefix = task.ext.prefix ?: "${meta.id}" - def long_reads_sorted = long_reads instanceof List ? long_reads.sort{ it.name } : long_reads - def ul_reads_sorted = ul_reads instanceof List ? ul_reads.sort{ it.name } : ul_reads + def long_reads_sorted = long_reads instanceof List ? long_reads.sort{ read -> read.name } : long_reads + def ul_reads_sorted = ul_reads instanceof List ? ul_reads.sort{ read -> read.name } : ul_reads def ultralong = ul_reads ? "--ul ${ul_reads_sorted}" : "" if([paternal_kmer_dump, maternal_kmer_dump].any() && [hic_read1, hic_read2].any()) { From 228e35a263c1d69b9aa72c25f9ad94baaf6d54b5 Mon Sep 17 00:00:00 2001 From: Niklas Schandry Date: Mon, 16 Feb 2026 14:49:52 +0100 Subject: [PATCH 132/162] change test samplesheet --- conf/test_full.config | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/conf/test_full.config b/conf/test_full.config index 166158ed..db0a42dd 100644 --- a/conf/test_full.config +++ b/conf/test_full.config @@ -24,7 +24,7 @@ params { config_profile_name = 'Full test profile' config_profile_description = 'Full test dataset to check pipeline function' - input = 'https://raw.githubusercontent.com/nf-core/test-datasets/genomeassembler/samplesheet/full_test_samplesheet.csv' + input = 'https://raw.githubusercontent.com/nf-core/test-datasets/genomeassembler/samplesheet/full_test_samplesheet_v2.csv' ontreads = 's3://nf-core-awsmegatests/genomeassembler/input/Col_0.ONT.porechopped.fastq.gz' hifireads = 's3://nf-core-awsmegatests/genomeassembler/input/Col_0.hifi_reads.fastq.gz' shortread_F = 's3://nf-core-awsmegatests/genomeassembler/input/SRR1604937_1.fastq.gz' From 96e3fc5a39fd9fe4786b87586d81c743828ed57d Mon Sep 17 00:00:00 2001 From: Niklas Schandry Date: Mon, 16 Feb 2026 14:50:05 +0100 Subject: [PATCH 133/162] update changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index e58ae128..440f8a8a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -24,6 +24,7 @@ In addition, v2.0.0 contains these changes: - mapping with `bwamem2` or `minimap2` - duplicate removal with `picard` - scaffolding with `yahs` +- Switched to the versions topic, requires nextflow >=25.10.0 ### `Fixed` From 1886f344f22dff7104ad38004c71f21f08217182 Mon Sep 17 00:00:00 2001 From: Niklas Schandry Date: Mon, 16 Feb 2026 14:50:34 +0100 Subject: [PATCH 134/162] switch workflows to versions topic, remove single process subworkflows --- modules/local/collect_reads/main.nf | 7 -- modules/local/dorado/aligner/main.nf | 14 +-- modules/local/dorado/polish/main.nf | 14 +-- modules/local/genomescope/main.nf | 13 +-- modules/local/gfa2fa/main.nf | 13 +-- modules/local/jellyfish/count/main.nf | 11 +-- modules/local/jellyfish/dump/main.nf | 10 +- modules/local/jellyfish/histo/main.nf | 10 +- modules/local/jellyfish/stats/main.nf | 13 +-- modules/local/longstitch/main.nf | 3 +- modules/local/medaka/main.nf | 11 +-- modules/local/medaka/medaka_consensus/main.nf | 11 +-- modules/local/quast/main.nf | 12 +-- modules/local/report/main.nf | 34 +++---- nextflow.config | 2 +- subworkflows/local/assemble/main.nf | 21 +---- subworkflows/local/bam_sort_stat/main.nf | 5 - subworkflows/local/jellyfish/main.nf | 94 ------------------- subworkflows/local/liftoff/main.nf | 5 - .../local/mapping/map_to_assembly/main.nf | 1 - subworkflows/local/polishing/dorado/main.nf | 14 +-- subworkflows/local/polishing/main.nf | 6 -- .../polishing/medaka/polish_medaka/main.nf | 25 ++--- .../local/polishing/medaka/run_medaka/main.nf | 22 ----- .../polishing/pilon/polish_pilon/main.nf | 32 ++++--- .../local/polishing/pilon/run_pilon/main.nf | 31 ------ subworkflows/local/prepare/main.nf | 5 - .../local/prepare/prepare_ont/main.nf | 3 - subworkflows/local/qc/busco/main.nf | 35 ------- subworkflows/local/qc/main.nf | 46 +++++++-- subworkflows/local/qc/quast/main.nf | 44 --------- subworkflows/local/scaffolding/links/main.nf | 11 +-- .../local/scaffolding/longstitch/main.nf | 9 +- subworkflows/local/scaffolding/main.nf | 8 -- subworkflows/local/scaffolding/ragtag/main.nf | 9 +- 35 files changed, 113 insertions(+), 491 deletions(-) delete mode 100644 subworkflows/local/jellyfish/main.nf delete mode 100644 subworkflows/local/polishing/medaka/run_medaka/main.nf delete mode 100644 subworkflows/local/polishing/pilon/run_pilon/main.nf delete mode 100644 subworkflows/local/qc/busco/main.nf delete mode 100644 subworkflows/local/qc/quast/main.nf diff --git a/modules/local/collect_reads/main.nf b/modules/local/collect_reads/main.nf index a222532f..a753c647 100644 --- a/modules/local/collect_reads/main.nf +++ b/modules/local/collect_reads/main.nf @@ -14,23 +14,16 @@ process COLLECT_READS { tuple val(meta), path("*_all_reads.fq.gz"), emit: combined_reads tuple val("${task.process}"), val('gzip'), eval('gzip --version | head -n1 | sed "s/gzip //"'), emit: versions_collect_reads, topic: versions - path "versions.yml", emit: versions - script: def prefix = task.ext.prefix ?: "${meta.id}" """ cat ${reads} > ${prefix}_all_reads.fq.gz - END_VERSIONS """ stub: def prefix = task.ext.prefix ?: "${meta.id}" """ touch ${prefix}_all_reads.fq; gzip ${prefix}_all_reads.fq - cat <<-END_VERSIONS > versions.yml - "${task.process}": - gzip: \$(echo \$(gzip --version | head -n1 | sed 's/gzip //')) - END_VERSIONS """ } diff --git a/modules/local/dorado/aligner/main.nf b/modules/local/dorado/aligner/main.nf index d7ffe135..64e10137 100644 --- a/modules/local/dorado/aligner/main.nf +++ b/modules/local/dorado/aligner/main.nf @@ -10,7 +10,9 @@ process DORADO_ALIGNER { output: tuple val(meta), path("${meta.id}_dorado_aligned.bam"), emit: bam tuple val(meta), path("${meta.id}_dorado_aligned.bam.bai"), emit: bai - path "versions.yml", emit: versions + tuple val("${task.process}"), val('dorado'), eval('dorado --version 2>&1 | head -n1'), emit: versions_dorado, topic: versions + 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 @@ -28,11 +30,6 @@ process DORADO_ALIGNER { > ${meta.id}_dorado_aligned.bam samtools index ${meta.id}_dorado_aligned.bam > ${meta.id}_dorado_aligned.bam.bai - - cat <<-END_VERSIONS > versions.yml - "${task.process}": - dorado: "\$(dorado --version 2>&1 | head -n1)" - END_VERSIONS """ stub: @@ -42,10 +39,5 @@ process DORADO_ALIGNER { """ touch ${prefix}/${prefix}.bam touch ${prefix}/${prefix}.bai - - cat <<-END_VERSIONS > versions.yml - "${task.process}": - dorado: "\$(dorado --version 2>&1 | head -n1)" - END_VERSIONS """ } diff --git a/modules/local/dorado/polish/main.nf b/modules/local/dorado/polish/main.nf index 6a161f59..3d8443cb 100644 --- a/modules/local/dorado/polish/main.nf +++ b/modules/local/dorado/polish/main.nf @@ -11,7 +11,8 @@ process DORADO_POLISH { output: tuple val(meta), path("${meta.id}_dorado_polished.fa.gz"), emit: polished_alignment, optional: true tuple val(meta), path("${meta.id}_dorado_polished*vcf"), emit: variant_calls, optional: true - path "versions.yml", emit: versions + tuple val("${task.process}"), val('dorado'), eval('dorado --version 2>&1 | head -n1'), emit: versions_dorado, topic: versions + when: task.ext.when == null || task.ext.when @@ -28,12 +29,6 @@ process DORADO_POLISH { ${args} \\ ${variants} \\ ${outfile} - - - cat <<-END_VERSIONS > versions.yml - "${task.process}": - dorado: "\$(dorado --version 2>&1 | head -n1)" - END_VERSIONS """ stub: @@ -43,10 +38,5 @@ process DORADO_POLISH { """ ${outfile} - - cat <<-END_VERSIONS > versions.yml - "${task.process}": - dorado: "\$(dorado --version 2>&1 | head -n1)" - END_VERSIONS """ } diff --git a/modules/local/genomescope/main.nf b/modules/local/genomescope/main.nf index 9f6d4696..e39c48e4 100644 --- a/modules/local/genomescope/main.nf +++ b/modules/local/genomescope/main.nf @@ -14,7 +14,8 @@ process GENOMESCOPE { tuple val(meta), path("*_plot.log.png") , emit: plot_log tuple val(meta), path("*_plot.png") , emit: plot tuple val(meta), env(est_hap_len) , emit: estimated_hap_len - path "versions.yml" , emit: versions + tuple val("${task.process}"), val('genomescope2'), eval("genomescope2 -v | sed 's/GenomeScope //'"), emit: versions_genomescope, topic: versions + script: def prefix = task.ext.prefix ?: "${meta.id}" @@ -28,12 +29,8 @@ process GENOMESCOPE { | sed 's@ bp@@g' \\ | sed 's@,@@g' \\ | awk '{printf "%i", (\$4+\$5)/2 }') - - cat <<-END_VERSIONS > versions.yml - "${task.process}": - genomescope2: \$(echo \$(genomescope2 -v | sed 's/GenomeScope //')) - END_VERSIONS """ + stub: def prefix = task.ext.prefix ?: "${meta.id}" """ @@ -41,9 +38,5 @@ process GENOMESCOPE { touch ${prefix}_plot.log.png touch ${prefix}_plot.png est_hap_len=1 - cat <<-END_VERSIONS > versions.yml - "${task.process}": - genomescope2: \$(echo \$(genomescope2 -v) | sed 's/GenomeScope //') - END_VERSIONS """ } diff --git a/modules/local/gfa2fa/main.nf b/modules/local/gfa2fa/main.nf index 5f55130e..5d38675d 100644 --- a/modules/local/gfa2fa/main.nf +++ b/modules/local/gfa2fa/main.nf @@ -11,6 +11,9 @@ process GFA_2_FA { output: tuple val(meta), path("*fa.gz"), emit: contigs_fasta + tuple val("${task.process}"), val('awk'), eval("mawk -Wversion | sed '1!d; s/.*Awk //; s/,.*//; s/ [0-9]*\$//'"), emit: versions_awk, topic: versions + tuple val("${task.process}"), val('gzip'), eval("gzip --version | head -n1 | sed 's/gzip //'"), emit: versions_gzip, topic: versions + path "versions.yml", emit: versions script: @@ -18,21 +21,11 @@ process GFA_2_FA { outfile=\$(basename $gfa_file .gfa).fa.gz awk '/^S/{print ">"\$2;print \$3}' ${gfa_file} \\ | bgzip > \$outfile - cat <<-END_VERSIONS > versions.yml - "${task.process}": - awk: \$(mawk -Wversion | sed '1!d; s/.*Awk //; s/,.*//; s/ [0-9]*\$//') - gzip: \$(echo \$(gzip --version | head -n1 | sed 's/gzip //')) - END_VERSIONS """ stub: """ outfile=\$(basename $gfa_file .gfa).fa.gz touch \$outfile - cat <<-END_VERSIONS > versions.yml - "${task.process}": - awk: \$(mawk -Wversion | sed '1!d; s/.*Awk //; s/,.*//; s/ [0-9]*\$//') - gzip: \$(echo \$(gzip --version | head -n1 | sed 's/gzip //')) - END_VERSIONS """ } diff --git a/modules/local/jellyfish/count/main.nf b/modules/local/jellyfish/count/main.nf index 3aebbf94..c371d2ac 100644 --- a/modules/local/jellyfish/count/main.nf +++ b/modules/local/jellyfish/count/main.nf @@ -10,7 +10,8 @@ process COUNT { output: tuple val(meta), path("*.jf"), emit: kmers - path "versions.yml", emit: versions + tuple val("${task.process}"), val('jellyfish'), eval("jellyfish --version sed 's/jellyfish //'"), emit: versions_jellyfish, topic: versions + script: def prefix = task.ext.prefix ?: "${meta.id}" @@ -30,20 +31,12 @@ process COUNT { -C \\ -t ${task.cpus} ${fasta.baseName}.fasta mv mer_counts.jf ${prefix}_mer_counts.jf - cat <<-END_VERSIONS > versions.yml - "${task.process}": - jellyfish: \$(echo \$(jellyfish --version sed 's/jellyfish //')) - END_VERSIONS """ stub: def prefix = task.ext.prefix ?: "${meta.id}" """ touch ${prefix}_mer_counts.jf - cat <<-END_VERSIONS > versions.yml - "${task.process}": - jellyfish: \$(echo \$(jellyfish --version sed 's/jellyfish //')) - END_VERSIONS """ } diff --git a/modules/local/jellyfish/dump/main.nf b/modules/local/jellyfish/dump/main.nf index e4ed6d22..82c0ce9a 100644 --- a/modules/local/jellyfish/dump/main.nf +++ b/modules/local/jellyfish/dump/main.nf @@ -10,24 +10,16 @@ process DUMP { output: tuple val(meta), path("*.fa"), emit: dumped_kmers - path "versions.yml", emit: versions + tuple val("${task.process}"), val('jellyfish'), eval("jellyfish --version sed 's/jellyfish //'"), emit: versions_jellyfish, topic: versions script: def prefix = task.ext.prefix ?: "${meta.id}" """ jellyfish dump ${kmers} > ${prefix}_kmers.fa - cat <<-END_VERSIONS > versions.yml - "${task.process}": - jellyfish: \$(echo \$(jellyfish --version sed 's/jellyfish //')) - END_VERSIONS """ stub: def prefix = task.ext.prefix ?: "${meta.id}" """ touch ${prefix}_kmers.fa - cat <<-END_VERSIONS > versions.yml - "${task.process}": - jellyfish: \$(echo \$(jellyfish --version sed 's/jellyfish //')) - END_VERSIONS """ } diff --git a/modules/local/jellyfish/histo/main.nf b/modules/local/jellyfish/histo/main.nf index 9f78f55d..42958b6d 100644 --- a/modules/local/jellyfish/histo/main.nf +++ b/modules/local/jellyfish/histo/main.nf @@ -10,25 +10,17 @@ process HISTO { output: tuple val(meta), path("*.tsv"), emit: histo - path "versions.yml", emit: versions + tuple val("${task.process}"), val('jellyfish'), eval("jellyfish --version sed 's/jellyfish //'"), emit: versions_jellyfish, topic: versions script: def prefix = task.ext.prefix ?: "${meta.id}" """ jellyfish histo ${kmers} > ${prefix}_hist.tsv - cat <<-END_VERSIONS > versions.yml - "${task.process}": - jellyfish: \$(echo \$(jellyfish --version sed 's/jellyfish //')) - END_VERSIONS """ stub: def prefix = task.ext.prefix ?: "${meta.id}" """ touch ${prefix}_hist.tsv - cat <<-END_VERSIONS > versions.yml - "${task.process}": - jellyfish: \$(echo \$(jellyfish --version sed 's/jellyfish //')) - END_VERSIONS """ } diff --git a/modules/local/jellyfish/stats/main.nf b/modules/local/jellyfish/stats/main.nf index 64eebfbf..6e8d3318 100644 --- a/modules/local/jellyfish/stats/main.nf +++ b/modules/local/jellyfish/stats/main.nf @@ -10,26 +10,19 @@ process STATS { output: tuple val(meta), path("*.txt"), emit: stats - path "versions.yml", emit: versions + tuple val("${task.process}"), val('jellyfish'), eval("jellyfish --version sed 's/jellyfish //'"), emit: versions_jellyfish, topic: versions + script: def prefix = task.ext.prefix ?: "${meta.id}" """ jellyfish stats ${kmers} > ${prefix}_stats.txt - cat <<-END_VERSIONS > versions.yml - "${task.process}": - jellyfish: \$(echo \$(jellyfish --version sed 's/jellyfish //')) - END_VERSIONS - """ + """ stub: def prefix = task.ext.prefix ?: "${meta.id}" """ touch ${prefix}_stats.txt - cat <<-END_VERSIONS > versions.yml - "${task.process}": - jellyfish: \$(echo \$(jellyfish --version sed 's/jellyfish //')) - END_VERSIONS """ } diff --git a/modules/local/longstitch/main.nf b/modules/local/longstitch/main.nf index 01362fc3..e4ab4448 100644 --- a/modules/local/longstitch/main.nf +++ b/modules/local/longstitch/main.nf @@ -12,7 +12,8 @@ process LONGSTITCH { output: tuple val(meta), path("*.tigmint-ntLink-arks.fa"), emit: ntlLinks_arks_scaffolds tuple val(meta), path("*.tigmint-ntLink.fa"), emit: ntlLinks_scaffolds - path "versions.yml", emit: versions + tuple val("${task.process}"), val('LongStitch'), eval("longstitch | head -n1 | sed 's/LongStitch v//'"), emit: versions_longstitch, topic: versions + script: def prefix = task.ext.prefix ?: "${meta.id}" diff --git a/modules/local/medaka/main.nf b/modules/local/medaka/main.nf index 7117c7da..2a7440c3 100644 --- a/modules/local/medaka/main.nf +++ b/modules/local/medaka/main.nf @@ -14,7 +14,7 @@ process MEDAKA { output: tuple val(meta), path("*_medaka.fa.gz"), emit: assembly - path "versions.yml", emit: versions + tuple val("${task.process}"), val('medaka'), eval("medaka --version 2>&1 | sed 's/medaka //g'"), emit: versions_medaka, topic: versions when: task.ext.when == null || task.ext.when @@ -38,19 +38,10 @@ process MEDAKA { mv consensus.fasta ${prefix}.fa gzip -n ${prefix}.fa - - cat <<-END_VERSIONS > versions.yml - "${task.process}": - medaka: \$( medaka --version 2>&1 | sed 's/medaka //g' ) - END_VERSIONS """ stub: def prefix = task.ext.prefix ?: "${meta.id}" """ touch ${prefix}_medaka.fa.gz - cat <<-END_VERSIONS > versions.yml - "${task.process}": - medaka: \$( medaka --version 2>&1 | sed 's/medaka //g' ) - END_VERSIONS """ } diff --git a/modules/local/medaka/medaka_consensus/main.nf b/modules/local/medaka/medaka_consensus/main.nf index 80ef6e54..ee2a43c9 100644 --- a/modules/local/medaka/medaka_consensus/main.nf +++ b/modules/local/medaka/medaka_consensus/main.nf @@ -13,7 +13,7 @@ process MEDAKA_PARALLEL { output: tuple val(meta), path("*_medaka.fa.gz"), emit: assembly - path "versions.yml", emit: versions + tuple val("${task.process}"), val('medaka'), eval("medaka --version 2>&1 | sed 's/medaka //g'"), emit: versions_medaka, topic: versions when: task.ext.when == null || task.ext.when @@ -60,19 +60,10 @@ process MEDAKA_PARALLEL { inference/*.hdf \$assembly ${prefix}.fa gzip -n ${prefix}.fa - - cat <<-END_VERSIONS > versions.yml - "${task.process}": - medaka: \$( medaka --version 2>&1 | sed 's/medaka //g' ) - END_VERSIONS """ stub: def prefix = task.ext.prefix ?: "${meta.id}" """ touch ${prefix}_medaka.fa.gz - cat <<-END_VERSIONS > versions.yml - "${task.process}": - medaka: \$( medaka --version 2>&1 | sed 's/medaka //g' ) - END_VERSIONS """ } diff --git a/modules/local/quast/main.nf b/modules/local/quast/main.nf index dd3b9d13..8e063fa4 100644 --- a/modules/local/quast/main.nf +++ b/modules/local/quast/main.nf @@ -15,7 +15,7 @@ process QUAST { output: path "${meta.id}*/*", emit: results path "*report.tsv", emit: tsv - path "versions.yml", emit: versions + tuple val("${task.process}"), val('quast'), eval("quast.py --version 2>&1 | sed 's/^.*QUAST v//; s/ .*\$//' | tail -n1"), emit: versions_medaka, topic: versions when: task.ext.when == null || task.ext.when @@ -41,21 +41,11 @@ process QUAST { ${args} ln -s ${prefix}/report.tsv ${prefix}_report.tsv - - cat <<-END_VERSIONS > versions.yml - "${task.process}": - quast: \$(quast.py --version 2>&1 | sed 's/^.*QUAST v//; s/ .*\$//' | tail -n1) - END_VERSIONS """ stub: def prefix = task.ext.prefix ?: "${meta.id}" """ mkdir ${prefix} && touch ${prefix}/report.tsv ln -s ${prefix}/report.tsv ${prefix}_report.tsv - - cat <<-END_VERSIONS > versions.yml - "${task.process}": - quast: \$(quast.py --version 2>&1 | sed 's/^.*QUAST v//; s/ .*\$//' | tail -n1) - END_VERSIONS """ } diff --git a/modules/local/report/main.nf b/modules/local/report/main.nf index 10bec57c..f4450896 100644 --- a/modules/local/report/main.nf +++ b/modules/local/report/main.nf @@ -18,7 +18,7 @@ process REPORT { path quast_files, stageAs: "data/quast/*" path busco_files, stageAs: "data/busco/*" path meryl_files, stageAs: "data/merqury/*" - path versions, stageAs: "software_versions.yml" + val versions val groups output: @@ -26,8 +26,12 @@ process REPORT { path ("busco_files/reports.csv"), emit: busco_table, optional: true path ("quast_files/reports.csv"), emit: quast_table, optional: true path ("genomescope_files/*"), emit: genomescope_plots, optional: true - path "versions.yml", emit: versions - + // Versions are not pushed to versions topic as it is an input. + tuple val("${task.process}"), val('R'), eval("R --version | head -n1 | sed 's/R version //; s/ .*//'"), emit: versions_R + tuple val("${task.process}"), val('r-tidyverse'), eval("ls /opt/conda/pkgs/ | grep tidyverse | sed 's/r-tidyverse-//; s/-.*//'"), emit: versions_tidyverse + tuple val("${task.process}"), val('r-plotly'), eval("ls /opt/conda/pkgs/ | grep plotly | sed 's/r-plotly-//; s/-.*//'"), emit: versions_plotly + tuple val("${task.process}"), val('r-quarto'), eval("ls /opt/conda/pkgs/ | grep r-quarto | sed 's/r-quarto-//; s/-.*//'"), emit: versions_rquarto + tuple val("${task.process}"), val('quarto-cli'), eval("quarto --version"), emit: versions_quartocli when: task.ext.when == null || task.ext.when @@ -58,24 +62,21 @@ process REPORT { def groupBuilder = new groovy.yaml.YamlBuilder() groupBuilder(groups) def group_content = groupBuilder.toString().tokenize('\n').join("\n ") + def versionBuilder = new groovy.yaml.YamlBuilder() + versionBuilder(versions) + def versions_content = versionBuilder.toString().tokenize('\n').join("\n ") """ cat <<- END_YAML_GROUPS > groups.yml ${group_content} END_YAML_GROUPS + cat <<- END_YAML_GROUPS > versions.yml + ${versions_content} + END_YAML_GROUPS export HOME="\$PWD" quarto render report.qmd \\ ${report_profile} \\ ${report_params} - - cat <<-END_VERSIONS > versions.yml - "${task.process}": - R: \$(R --version | head -n1 | sed 's/R version //; s/ .*//') - r-tidyverse: \$(ls /opt/conda/pkgs/ | grep tidyverse | sed 's/r-tidyverse-//; s/-.*//') - r-plotly: \$(ls /opt/conda/pkgs/ | grep plotly | sed 's/r-plotly-//; s/-.*//') - r-quarto: \$(ls /opt/conda/pkgs/ | grep r-quarto | sed 's/r-quarto-//; s/-.*//') - quarto-cli: \$(quarto --version) - END_VERSIONS """ stub: """ @@ -84,14 +85,5 @@ process REPORT { mkdir busco_files && touch busco_files/reports.csv mkdir quast_files && touch quast_files/reports.csv mkdir genomescope_files && touch genomescope_files/file.txt - - cat <<-END_VERSIONS > versions.yml - "${task.process}": - R: \$(R --version | head -n1 | sed 's/R version //; s/ .*//') - r-tidyverse: \$(ls /opt/conda/pkgs/ | grep tidyverse | sed 's/r-tidyverse-//; s/-.*//') - r-plotly: \$(ls /opt/conda/pkgs/ | grep plotly | sed 's/r-plotly-//; s/-.*//') - r-quarto: \$(ls /opt/conda/pkgs/ | grep r-quarto | sed 's/r-quarto-//; s/-.*//') - quarto-cli: \$(quarto --version) - END_VERSIONS """ } diff --git a/nextflow.config b/nextflow.config index 09ba9ed4..cad35ca9 100644 --- a/nextflow.config +++ b/nextflow.config @@ -331,7 +331,7 @@ manifest { description = """Assemble genomes from long ONT or pacbio HiFi reads""" mainScript = 'main.nf' defaultBranch = 'master' - nextflowVersion = '!>=25.04.0' + nextflowVersion = '!>=25.10.0' version = '2.0.0dev' doi = '10.5281/zenodo.14986998' } diff --git a/subworkflows/local/assemble/main.nf b/subworkflows/local/assemble/main.nf index d07eccf7..243036f0 100644 --- a/subworkflows/local/assemble/main.nf +++ b/subworkflows/local/assemble/main.nf @@ -5,7 +5,7 @@ include { HIFIASM as HIFIASM_ONT } from '../../../modules/nf-core/hifiasm/main' include { GFA_2_FA as GFA_2_FA_HIFI } from '../../../modules/local/gfa2fa/main' include { GFA_2_FA as GFA_2_FA_ONT} from '../../../modules/local/gfa2fa/main' include { MAP_TO_REF } from '../mapping/map_to_ref/main' -include { RUN_LIFTOFF } from '../liftoff/main' +include { LIFTOFF } from '../../../modules/nf-core/liftoff/main' include { RAGTAG_PATCH } from '../../../modules/nf-core/ragtag/patch/main' include { QC } from '../qc/main' @@ -16,9 +16,6 @@ workflow ASSEMBLE { meryl_kmers main: - // Empty channels - channel.empty().set { ch_versions } - /* Samples are split into those that need assembly, and those that will not be assembled (i.e. assemblies are provided) */ @@ -176,9 +173,7 @@ workflow ASSEMBLE { [[], []]) // hifiasm produces GFA files - GFA_2_FA_HIFI( HIFIASM.out.processed_unitigs) - - ch_versions = ch_versions.mix(HIFIASM.out.versions).mix(GFA_2_FA_HIFI.out.versions) + GFA_2_FA_HIFI( HIFIASM.out.primary_contigs ) /* hifiasm with ONLY ont reads. @@ -200,9 +195,7 @@ workflow ASSEMBLE { HIFIASM_ONT(ch_main_assemble_ont_hifiasm.map { it -> [ it.meta, it.meta.ontreads, [] ] }, [[], [], []], [[], [], []], [[], []]) - GFA_2_FA_ONT( HIFIASM_ONT.out.processed_unitigs) - - ch_versions = ch_versions.mix(HIFIASM_ONT.out.versions).mix(GFA_2_FA_ONT.out.versions) + GFA_2_FA_ONT( HIFIASM_ONT.out.primary_contigs) // Flye: FLYE_ONT.out.fasta @@ -455,8 +448,6 @@ workflow ASSEMBLE { ch_main_assembled.dump(tag: "Assemble: Assembled") - ch_versions = ch_versions.mix(RAGTAG_PATCH.out.versions) - // Mix with whatever was not destined for assembly ch_main_branched .no_assemble @@ -527,8 +518,6 @@ workflow ASSEMBLE { QC(ch_main_to_qc, scaffolds, meryl_kmers) - ch_versions = ch_versions.mix(QC.out.versions) - // If annotation liftover on the initial assembly is desired, it happens here. ch_main_to_qc .filter { @@ -544,13 +533,11 @@ workflow ASSEMBLE { } .set { liftoff_in } liftoff_in.dump(tag: "ASSEMBLE: LIFTOFF: INPUT") - RUN_LIFTOFF(liftoff_in) - ch_versions = ch_versions.mix(RUN_LIFTOFF.out.versions) + LIFTOFF(liftoff_in, []) emit: ch_main = ch_main_to_qc assembly_quast_reports = QC.out.quast_out assembly_busco_reports = QC.out.busco_out assembly_merqury_reports = QC.out.merqury_report_files - versions = ch_versions } diff --git a/subworkflows/local/bam_sort_stat/main.nf b/subworkflows/local/bam_sort_stat/main.nf index 7bb45342..826d36a7 100644 --- a/subworkflows/local/bam_sort_stat/main.nf +++ b/subworkflows/local/bam_sort_stat/main.nf @@ -17,18 +17,13 @@ workflow BAM_INDEX_STATS_SAMTOOLS { fasta main: - channel.empty().set { ch_versions } - SAMTOOLS_INDEX(bam) BAM_STATS_SAMTOOLS(bam.join(SAMTOOLS_INDEX.out.csi, by: [0]), fasta) - versions = ch_versions.mix(SAMTOOLS_INDEX.out.versions).mix(BAM_STATS_SAMTOOLS.out.versions) - emit: bai = SAMTOOLS_INDEX.out.csi // channel: [ val(meta), [ csi ] ] stats = BAM_STATS_SAMTOOLS.out.stats // channel: [ val(meta), [ stats ] ] flagstat = BAM_STATS_SAMTOOLS.out.flagstat // channel: [ val(meta), [ flagstat ] ] idxstats = BAM_STATS_SAMTOOLS.out.idxstats // channel: [ val(meta), [ idxstats ] ] - versions } diff --git a/subworkflows/local/jellyfish/main.nf b/subworkflows/local/jellyfish/main.nf deleted file mode 100644 index ff7cbcfb..00000000 --- a/subworkflows/local/jellyfish/main.nf +++ /dev/null @@ -1,94 +0,0 @@ -include { COUNT } from '../../../modules/local/jellyfish/count/main' -include { DUMP } from '../../../modules/local/jellyfish/dump/main' -include { HISTO } from '../../../modules/local/jellyfish/histo/main' -include { STATS } from '../../../modules/local/jellyfish/stats/main' -include { GENOMESCOPE } from '../../../modules/local/genomescope/main' - -workflow JELLYFISH { - take: - inputs - nanoq_out - - main: - channel.empty().set { genomescope_in } - channel.empty().set { ch_versions } - inputs.map { - it -> - [ - meta: it.meta, - reads: it.meta.ontreads - ] - } - .set { samples } - COUNT(samples) - COUNT.out.kmers.set { kmers } - - ch_versions = ch_versions.mix(COUNT.out.versions) - - HISTO(kmers) - ch_versions = ch_versions.mix(HISTO.out.versions) - - if (!params.read_length == null) { - HISTO.out.histo - .map { - it -> - [ - it[0], - it[1], - params.kmer_length, - params.read_length - ] - } - .set { genomescope_in } - } - - if (params.read_length == null) { - HISTO.out.histo - .map { - it -> - [ - it[0], - it[1], - params.kmer_length - ] - } - .join(nanoq_out) - .set { genomescope_in } - } - - GENOMESCOPE(genomescope_in) - - ch_versions = ch_versions.mix(GENOMESCOPE.out.versions) - - STATS(kmers) - - ch_versions = ch_versions.mix(STATS.out.versions) - - inputs - .map { - it -> it.subMap('genome_size') - } - .join( - GENOMESCOPE.out.estimated_hap_len - .map { - it -> - [ - meta: it[0], - genome_size: it[1] - ] - } - ) - .set { outputs } - - GENOMESCOPE.out.summary.set { genomescope_summary } - - GENOMESCOPE.out.plot.set { genomescope_plot } - - versions = ch_versions - - emit: - outputs - genomescope_summary - genomescope_plot - versions -} diff --git a/subworkflows/local/liftoff/main.nf b/subworkflows/local/liftoff/main.nf index f3d533d0..baee9e18 100644 --- a/subworkflows/local/liftoff/main.nf +++ b/subworkflows/local/liftoff/main.nf @@ -5,15 +5,10 @@ workflow RUN_LIFTOFF { liftoff_in main: - channel.empty().set { ch_versions } - LIFTOFF(liftoff_in, []) LIFTOFF.out.gff3.set { lifted_annotations } - versions = ch_versions.mix(LIFTOFF.out.versions) - emit: lifted_annotations - versions } diff --git a/subworkflows/local/mapping/map_to_assembly/main.nf b/subworkflows/local/mapping/map_to_assembly/main.nf index 5e45389d..47c31897 100644 --- a/subworkflows/local/mapping/map_to_assembly/main.nf +++ b/subworkflows/local/mapping/map_to_assembly/main.nf @@ -26,5 +26,4 @@ workflow MAP_TO_ASSEMBLY { emit: aln_to_assembly_bam // [id], bam - versions = channel.empty() } diff --git a/subworkflows/local/polishing/dorado/main.nf b/subworkflows/local/polishing/dorado/main.nf index d086dc44..be3663d2 100644 --- a/subworkflows/local/polishing/dorado/main.nf +++ b/subworkflows/local/polishing/dorado/main.nf @@ -1,7 +1,7 @@ include { DORADO_ALIGNER as ALIGN } from '../../../../modules/local/dorado/aligner/main.nf' include { DORADO_POLISH as POLISH } from '../../../../modules/local/dorado/polish/main.nf' include { QC } from '../../qc/main.nf' -include { RUN_LIFTOFF } from '../../liftoff/main' +include { LIFTOFF } from '../../../../modules/nf-core/liftoff/main' workflow POLISH_DORADO { take: @@ -9,7 +9,6 @@ workflow POLISH_DORADO { meryl_kmers main: - channel.empty().set { ch_versions } ch_main .map { it -> [it.meta, it.meta.assembly, it.meta.ontreads] } @@ -30,16 +29,12 @@ workflow POLISH_DORADO { .map { meta, polished_dorado -> [meta: meta + [ polished: [polished_dorado: polished_dorado ] ] ]} .set { ch_main_out } - ch_versions = ch_versions.mix(POLISH.out.versions) - QC( ch_main_out.map { it -> [meta: it.meta - it.meta.subMap("assembly_map_bam") + [ assembly_map_bam: null] ] }, polished_assembly.map { meta, polished -> [meta.id, polished] }, meryl_kmers ) - ch_versions = ch_versions.mix(QC.out.versions) - ch_main_out .filter { it -> it.meta.lift_annotations @@ -54,16 +49,11 @@ workflow POLISH_DORADO { } .set { liftoff_in } - RUN_LIFTOFF(liftoff_in) - - ch_versions = ch_versions.mix(RUN_LIFTOFF.out.versions) - - versions = ch_versions + LIFTOFF(liftoff_in, []) emit: ch_main = ch_main_out quast_out = QC.out.quast_out busco_out = QC.out.busco_out merqury_report_files = QC.out.merqury_report_files - versions } diff --git a/subworkflows/local/polishing/main.nf b/subworkflows/local/polishing/main.nf index bf0668dc..01c1e662 100644 --- a/subworkflows/local/polishing/main.nf +++ b/subworkflows/local/polishing/main.nf @@ -44,8 +44,6 @@ workflow POLISH { .mix(POLISH_DORADO.out.merqury_report_files) .set { polish_merqury_reports } - ch_versions = ch_versions.mix(POLISH_MEDAKA.out.versions) - POLISH_MEDAKA.out.ch_main .mix(POLISH_DORADO.out.ch_main) .mix(ch_main_polish.no_ont_polish) @@ -93,10 +91,6 @@ workflow POLISH { ) .set { polish_merqury_reports } - ch_versions = ch_versions.mix(POLISH_PILON.out.versions) - - versions = ch_versions - emit: ch_main = ch_out polish_busco_reports diff --git a/subworkflows/local/polishing/medaka/polish_medaka/main.nf b/subworkflows/local/polishing/medaka/polish_medaka/main.nf index b56a5c96..d11228fa 100644 --- a/subworkflows/local/polishing/medaka/polish_medaka/main.nf +++ b/subworkflows/local/polishing/medaka/polish_medaka/main.nf @@ -1,6 +1,6 @@ -include { RUN_MEDAKA } from '../run_medaka/main' +include { MEDAKA_PARALLEL as MEDAKA } from '../../../../../modules/local/medaka/medaka_consensus/main' include { QC } from '../../../qc/main.nf' -include { RUN_LIFTOFF } from '../../../liftoff/main' +include { LIFTOFF } from '../../../../../modules/nf-core/liftoff/main' workflow POLISH_MEDAKA { take: @@ -11,16 +11,16 @@ workflow POLISH_MEDAKA { channel.empty().set { ch_versions } ch_main - .multiMap { + .map { it -> - reads: [it.meta, it.meta.ontreads] - reference: [it.meta, it.meta.assembly] + [ it.meta, it.meta.ontreads, it.meta.assembly ] + } .set { ch_medaka_in } - RUN_MEDAKA(ch_medaka_in.reads, ch_medaka_in.reference) + MEDAKA(ch_medaka_in) - RUN_MEDAKA.out.medaka_out.set { polished_assembly } + MEDAKA.out.assembly.set { polished_assembly } polished_assembly .map { meta, polished_medaka -> [meta: meta + [ polished: [medaka: polished_medaka ] ] ]} @@ -30,8 +30,6 @@ workflow POLISH_MEDAKA { ch_medaka_out .set { ch_main_out } - ch_versions = ch_versions.mix(RUN_MEDAKA.out.versions) - QC( ch_medaka_out.map { it -> [meta: it.meta - it.meta.subMap("assembly_map_bam") + [ assembly_map_bam: null] ] }, polished_assembly.map { meta, polished -> [meta.id, polished] }, @@ -39,8 +37,6 @@ workflow POLISH_MEDAKA { ) - ch_versions = ch_versions.mix(QC.out.versions) - ch_medaka_out .filter { it -> it.meta.lift_annotations @@ -55,16 +51,11 @@ workflow POLISH_MEDAKA { } .set { liftoff_in } - RUN_LIFTOFF(liftoff_in) - - ch_versions = ch_versions.mix(RUN_LIFTOFF.out.versions) - - versions = ch_versions + LIFTOFF(liftoff_in, []) emit: ch_main = ch_main_out quast_out = QC.out.quast_out busco_out = QC.out.busco_out merqury_report_files = QC.out.merqury_report_files - versions } diff --git a/subworkflows/local/polishing/medaka/run_medaka/main.nf b/subworkflows/local/polishing/medaka/run_medaka/main.nf deleted file mode 100644 index 2ba3ceb6..00000000 --- a/subworkflows/local/polishing/medaka/run_medaka/main.nf +++ /dev/null @@ -1,22 +0,0 @@ -include { MEDAKA_PARALLEL as MEDAKA } from '../../../../../modules/local/medaka/medaka_consensus/main' - -workflow RUN_MEDAKA { - take: - in_reads - assembly - - main: - - in_reads - .join(assembly) - .set { medaka_in } - - MEDAKA(medaka_in) - - MEDAKA.out.assembly.set { medaka_out } - MEDAKA.out.versions.set { versions } - - emit: - medaka_out - versions -} diff --git a/subworkflows/local/polishing/pilon/polish_pilon/main.nf b/subworkflows/local/polishing/pilon/polish_pilon/main.nf index a444d2f1..81afa0fe 100644 --- a/subworkflows/local/polishing/pilon/polish_pilon/main.nf +++ b/subworkflows/local/polishing/pilon/polish_pilon/main.nf @@ -1,6 +1,6 @@ -include { RUN_PILON } from '../run_pilon/main' +include { PILON } from '../../../../../modules/nf-core/pilon/main' include { MAP_SR } from '../../../mapping/map_sr/main' -include { RUN_LIFTOFF } from '../../../liftoff/main' +include { LIFTOFF } from '../../../../../modules/nf-core/liftoff/main' include { QC } from '../../../qc/main.nf' workflow POLISH_PILON { @@ -27,23 +27,32 @@ workflow POLISH_PILON { MAP_SR(map_sr_in.shortreads, map_sr_in.assembly) - RUN_PILON(map_sr_in.assembly, MAP_SR.out.aln_to_assembly_bam_bai) - RUN_PILON.out.improved_assembly - .set { pilon_polished } + map_sr_in.assembly + .combine(MAP_SR.out.aln_to_assembly_bam_bai) + .multiMap { + meta, assembly, bam, bai -> + assembly: [meta, assembly] + bam_bai: [meta, bam, bai] + } + .set { pilon_in } + + PILON( + pilon_in.assembly, + pilon_in.bam_bai, + "bam", + ) + + pilon_polished = PILON.out.improved_assembly pilon_polished .map { meta, polished_pilon -> [ meta: meta + [ polished: [pilon: polished_pilon] ] ] } .set { ch_main } - ch_versions = ch_versions.mix(RUN_PILON.out.versions) - QC(ch_main.map { it -> [meta: it.meta - it.meta.subMap("assembly_map_bam") + [assembly_map_bam: null] ]}, pilon_polished.map {meta, polished -> [meta.id, polished ]}, meryl_kmers) - ch_versions = ch_versions.mix(QC.out.versions) - ch_main .filter { it -> it.meta.lift_annotations @@ -58,14 +67,11 @@ workflow POLISH_PILON { } .set { liftoff_in } - RUN_LIFTOFF(liftoff_in) - - ch_versions = ch_versions.mix(RUN_LIFTOFF.out.versions) + LIFTOFF(liftoff_in, []) emit: ch_main quast_out = QC.out.quast_out busco_out = QC.out.busco_out merqury_report_files = QC.out.merqury_report_files - versions = ch_versions } diff --git a/subworkflows/local/polishing/pilon/run_pilon/main.nf b/subworkflows/local/polishing/pilon/run_pilon/main.nf deleted file mode 100644 index 762d55ad..00000000 --- a/subworkflows/local/polishing/pilon/run_pilon/main.nf +++ /dev/null @@ -1,31 +0,0 @@ -include { PILON } from '../../../../../modules/nf-core/pilon/main' - -workflow RUN_PILON { - take: - assembly_in - aln_to_assembly_bam_bai - - main: - - assembly_in - .join(aln_to_assembly_bam_bai) - .multiMap { - meta, assembly, bam, bai -> - assembly: [meta, assembly] - bam_bai: [meta, bam, bai] - } - .set { pilon_in } - - PILON( - pilon_in.assembly, - pilon_in.bam_bai, - "bam", - ) - versions = PILON.out.versions - - improved_assembly = PILON.out.improved_assembly - - emit: - improved_assembly - versions -} diff --git a/subworkflows/local/prepare/main.nf b/subworkflows/local/prepare/main.nf index 11b60a33..8e199465 100644 --- a/subworkflows/local/prepare/main.nf +++ b/subworkflows/local/prepare/main.nf @@ -244,10 +244,6 @@ workflow PREPARE { JELLYFISH.out.genomescope_plot.set { genomescope_plot } - SHORTREADS.out.versions - .mix(JELLYFISH.out.versions) - .set { versions } - fastplong_json_reports = HIFI.out.fastplong_hifi_reports.mix(ONT.out.fastplong_ont_reports) emit: @@ -257,5 +253,4 @@ workflow PREPARE { meryl_kmers genomescope_summary genomescope_plot - versions } diff --git a/subworkflows/local/prepare/prepare_ont/main.nf b/subworkflows/local/prepare/prepare_ont/main.nf index 63914b71..ac9c6c7b 100644 --- a/subworkflows/local/prepare/prepare_ont/main.nf +++ b/subworkflows/local/prepare/prepare_ont/main.nf @@ -137,12 +137,9 @@ workflow PREPARE_ONT { ) .set { fastplong_json_out } - versions = ch_versions.mix(COLLECT.out.versions) - fastplong_reads_out.dump(tag: "Prepare-ONT output") emit: main_out = fastplong_reads_out fastplong_ont_reports = fastplong_json_out - versions } diff --git a/subworkflows/local/qc/busco/main.nf b/subworkflows/local/qc/busco/main.nf deleted file mode 100644 index 0267f05d..00000000 --- a/subworkflows/local/qc/busco/main.nf +++ /dev/null @@ -1,35 +0,0 @@ -include { BUSCO_BUSCO as BUSCO } from '../../../../modules/nf-core/busco/busco/main' - -workflow RUN_BUSCO { - take: - ch_main - - main: - channel.empty().set { batch_summary } - channel.empty().set { short_summary_txt } - channel.empty().set { short_summary_json } - - ch_main - .filter { - it -> it.meta.busco - } - .multiMap { it -> - fasta: [ - it.meta, - it.meta.qc_target - ] - busco_lineage: it.meta.busco_lineage - busco_db: it.meta.busco_db ? file(it.meta.busco_db, checkIfExists: true) : [] - } - .set { busco_in } - - BUSCO(busco_in.fasta, 'genome', busco_in.busco_lineage, busco_in.busco_db , [], true) - BUSCO.out.batch_summary.set { batch_summary } - BUSCO.out.short_summaries_txt.set { short_summary_txt } - BUSCO.out.short_summaries_json.set { short_summary_json } - - emit: - batch_summary - short_summary_json - short_summary_txt -} diff --git a/subworkflows/local/qc/main.nf b/subworkflows/local/qc/main.nf index 61310087..2a024239 100644 --- a/subworkflows/local/qc/main.nf +++ b/subworkflows/local/qc/main.nf @@ -1,6 +1,7 @@ include { MAP_TO_ASSEMBLY } from '../mapping/map_to_assembly/main' include { RUN_BUSCO } from './busco/main.nf' -include { RUN_QUAST } from './quast/main.nf' +include { QUAST } from '../../../modules/local/quast/main' +include { BUSCO_BUSCO as BUSCO } from '../../../modules/nf-core/busco/busco/main' include { MERQURY_MERQURY as MERQURY } from '../../../modules/nf-core/merqury/merqury/main' workflow QC { @@ -74,13 +75,43 @@ workflow QC { .mix(ch_map_branched.no_map_to_assembly) .set { ch_qc } - RUN_QUAST(ch_qc) - RUN_QUAST.out.quast_tsv.set { quast_out } + ch_qc + .filter { + it -> it.meta.quast + } + .multiMap { it -> + quast_in: [ + it.meta, + it.meta.qc_target, + it.meta.ref_fasta ?: [], + it.meta.ref_gff ?: [], + it.meta.ref_map_bam ?: [], + it.meta.assembly_map_bam + ] + use_ref: it.meta.use_ref + } + .set { quast_in } - ch_versions = ch_versions.mix(RUN_QUAST.out.versions) + QUAST(quast_in) + QUAST.out.tsv.set { quast_out } + + ch_qc + .filter { + it -> it.meta.busco + } + .multiMap { it -> + fasta: [ + it.meta, + it.meta.qc_target + ] + busco_lineage: it.meta.busco_lineage + busco_db: it.meta.busco_db ? file(it.meta.busco_db, checkIfExists: true) : [] + } + .set { busco_in } + + BUSCO(busco_in.fasta, 'genome', busco_in.busco_lineage, busco_in.busco_db , [], true) + BUSCO.out.batch_summary.set { busco_out } - RUN_BUSCO(ch_qc) - RUN_BUSCO.out.batch_summary.set { busco_out } MERQURY.out.stats .join( @@ -94,12 +125,9 @@ workflow QC { ) .set { merqury_report_files } - ch_versions = ch_versions.mix(MERQURY.out.versions) - emit: ch_main // QC does not (and should not) modify ch_main but returns the input. quast_out busco_out merqury_report_files - versions = ch_versions } diff --git a/subworkflows/local/qc/quast/main.nf b/subworkflows/local/qc/quast/main.nf deleted file mode 100644 index 4e63a97d..00000000 --- a/subworkflows/local/qc/quast/main.nf +++ /dev/null @@ -1,44 +0,0 @@ -include { QUAST } from '../../../../modules/local/quast/main' - -workflow RUN_QUAST { - take: - ch_main - - main: - channel.empty().set { versions } - /* prepare for quast: - * This makes use of the input channel to obtain the reference and reference annotations - * See quast module for details - */ - channel.empty().set { quast_results } - channel.empty().set { quast_tsv } - - ch_main - .filter { - it -> it.meta.quast - } - .multiMap { it -> - quast_in: [ - it.meta, - it.meta.qc_target, - it.meta.ref_fasta ?: [], - it.meta.ref_gff ?: [], - it.meta.ref_map_bam ?: [], - it.meta.assembly_map_bam - ] - use_ref: it.meta.use_ref - } - .set { quast_in } - /* - * Run QUAST - */ - QUAST(quast_in.quast_in, quast_in.use_ref, false) - QUAST.out.results.set { quast_results } - QUAST.out.tsv.set { quast_tsv } - QUAST.out.versions.set { versions } - - emit: - quast_results - quast_tsv - versions -} diff --git a/subworkflows/local/scaffolding/links/main.nf b/subworkflows/local/scaffolding/links/main.nf index 03c5109f..4e1ba95e 100644 --- a/subworkflows/local/scaffolding/links/main.nf +++ b/subworkflows/local/scaffolding/links/main.nf @@ -1,6 +1,6 @@ include { LINKS } from '../../../../modules/nf-core/links/main' include { QC } from '../../qc/main' -include { RUN_LIFTOFF } from '../../liftoff/main' +include { LIFTOFF } from '../../../../modules/nf-core/liftoff/main' workflow RUN_LINKS { take: @@ -8,7 +8,6 @@ workflow RUN_LINKS { meryl_kmers main: - channel.empty().set { ch_versions } ch_main.dump(tag: "SCAFFOLD: LINKS: WORKFLOW inputs") ch_main .multiMap { it -> @@ -25,14 +24,10 @@ workflow RUN_LINKS { .map { meta, scaff_links -> [meta: meta + [scaffolds_links: scaff_links] ] } .set { ch_main_scaffolded } - ch_versions = ch_versions.mix(LINKS.out.versions) - QC(ch_main_scaffolded.map { it -> [meta: it.meta - it.meta.subMap("assembly_map_bam") + [assembly_map_bam: null] ]}, LINKS.out.scaffolds_fasta.map { meta, scaffold -> [meta.id, scaffold]}, meryl_kmers) - ch_versions = ch_versions.mix(QC.out.versions) - ch_main_scaffolded .filter { it -> it.lift_annotations @@ -47,13 +42,11 @@ workflow RUN_LINKS { } .set { liftoff_in } - RUN_LIFTOFF(liftoff_in) - ch_versions = ch_versions.mix(RUN_LIFTOFF.out.versions) + LIFTOFF(liftoff_in, []) emit: ch_main = ch_main_scaffolded quast_out = QC.out.quast_out busco_out = QC.out.busco_out merqury_report_files = QC.out.merqury_report_files - versions = ch_versions } diff --git a/subworkflows/local/scaffolding/longstitch/main.nf b/subworkflows/local/scaffolding/longstitch/main.nf index d2b11082..d0201932 100644 --- a/subworkflows/local/scaffolding/longstitch/main.nf +++ b/subworkflows/local/scaffolding/longstitch/main.nf @@ -1,6 +1,6 @@ include { LONGSTITCH } from '../../../../modules/local/longstitch/main' include { QC } from '../../qc/main' -include { RUN_LIFTOFF } from '../../liftoff/main' +include { LIFTOFF } from '../../../../modules/nf-core/liftoff/main' workflow RUN_LONGSTITCH { take: @@ -39,14 +39,10 @@ workflow RUN_LONGSTITCH { .map { meta, scaff_longst -> [meta: meta + [scaffolds_longstitch: scaff_longst] ] } .set { ch_main_scaffolded } - ch_versions = ch_versions.mix(LONGSTITCH.out.versions) - QC(ch_main_scaffolded.map { it -> [ meta: it.meta - it.meta.subMap("assembly_map_bam") + [assembly_map_bam: null] ] }, LONGSTITCH.out.ntlLinks_arks_scaffolds.map { meta, scaffold -> [meta.id, scaffold]}, meryl_kmers) - ch_versions = ch_versions.mix(QC.out.versions) - ch_main_scaffolded .filter { it -> it.meta.lift_annotations @@ -61,8 +57,7 @@ workflow RUN_LONGSTITCH { } .set { liftoff_in } - RUN_LIFTOFF(liftoff_in) - ch_versions = ch_versions.mix(RUN_LIFTOFF.out.versions) + LIFTOFF(liftoff_in,[]) emit: ch_main = ch_main_scaffolded diff --git a/subworkflows/local/scaffolding/main.nf b/subworkflows/local/scaffolding/main.nf index 8e20d273..11647143 100644 --- a/subworkflows/local/scaffolding/main.nf +++ b/subworkflows/local/scaffolding/main.nf @@ -9,7 +9,6 @@ workflow SCAFFOLD { meryl_kmers main: - channel.empty().set { ch_versions } channel.empty().set { links_busco } channel.empty().set { links_quast } channel.empty().set { links_merqury } @@ -172,22 +171,18 @@ workflow SCAFFOLD { RUN_LINKS.out.busco_out.set { links_busco } RUN_LINKS.out.quast_out.set { links_quast } RUN_LINKS.out.merqury_report_files.set { links_merqury } - ch_versions = ch_versions.mix(RUN_LINKS.out.versions) RUN_LONGSTITCH.out.busco_out.set { longstitch_busco } RUN_LONGSTITCH.out.quast_out.set { longstitch_quast } RUN_LONGSTITCH.out.merqury_report_files.set { longstitch_merqury } - ch_versions = ch_versions.mix(RUN_LONGSTITCH.out.versions) HIC.out.busco_out.set { hic_busco } HIC.out.quast_out.set { hic_quast } HIC.out.merqury_report_files.set { hic_merqury } - ch_versions = ch_versions.mix(HIC.out.versions) RUN_RAGTAG.out.busco_out.set { ragtag_busco } RUN_RAGTAG.out.quast_out.set { ragtag_quast } RUN_RAGTAG.out.merqury_report_files.set { ragtag_merqury } - ch_versions = ch_versions.mix(RUN_RAGTAG.out.versions) links_busco .concat(longstitch_busco) @@ -207,12 +202,9 @@ workflow SCAFFOLD { .concat(hic_merqury) .set { scaffold_merqury_reports } - versions = ch_versions - emit: ch_main scaffold_busco_reports scaffold_quast_reports scaffold_merqury_reports - versions } diff --git a/subworkflows/local/scaffolding/ragtag/main.nf b/subworkflows/local/scaffolding/ragtag/main.nf index 2fa2162c..27e7f951 100644 --- a/subworkflows/local/scaffolding/ragtag/main.nf +++ b/subworkflows/local/scaffolding/ragtag/main.nf @@ -1,6 +1,6 @@ include { RAGTAG_SCAFFOLD } from '../../../../modules/nf-core/ragtag/scaffold/main' include { QC } from '../../qc/main' -include { RUN_LIFTOFF } from '../../liftoff/main' +include { LIFTOFF } from '../../../../modules/nf-core/liftoff/main' workflow RUN_RAGTAG { @@ -9,8 +9,6 @@ workflow RUN_RAGTAG { meryl_kmers main: - channel.empty().set { ch_versions } - ch_main .multiMap { it -> def assembly_to_scaffold = @@ -53,7 +51,6 @@ workflow RUN_RAGTAG { RAGTAG_SCAFFOLD.out.corrected_assembly.map { meta, corrected -> [ meta.id, corrected ] }, meryl_kmers) - ch_versions = ch_versions.mix(QC.out.versions) ch_main_scaffolded .filter { @@ -69,13 +66,11 @@ workflow RUN_RAGTAG { } .set { liftoff_in } - RUN_LIFTOFF(liftoff_in) - ch_versions = ch_versions.mix(RUN_LIFTOFF.out.versions) + LIFTOFF(liftoff_in, []) emit: ch_main quast_out = QC.out.quast_out busco_out = QC.out.busco_out merqury_report_files = QC.out.merqury_report_files - versions = ch_versions } From 452d47592a328d902fb9b75c0962a5fb4e486f65 Mon Sep 17 00:00:00 2001 From: Niklas Schandry Date: Tue, 17 Feb 2026 16:54:07 +0100 Subject: [PATCH 135/162] update modules for versions topic --- modules.json | 18 +-- modules/nf-core/hifiasm/main.nf | 13 +- modules/nf-core/hifiasm/meta.yml | 48 ++++--- modules/nf-core/hifiasm/tests/main.nf.test | 10 +- .../nf-core/hifiasm/tests/main.nf.test.snap | 122 ++++++++++++------ modules/nf-core/liftoff/main.nf | 12 +- modules/nf-core/liftoff/meta.yml | 33 +++-- modules/nf-core/liftoff/tests/main.nf.test | 2 +- .../nf-core/liftoff/tests/main.nf.test.snap | 48 ++++--- modules/nf-core/links/main.nf | 2 +- modules/nf-core/links/meta.yml | 49 ++++--- modules/nf-core/links/tests/main.nf.test | 12 +- modules/nf-core/links/tests/main.nf.test.snap | 94 ++++++-------- modules/nf-core/merqury/merqury/main.nf | 38 ++---- .../merqury/merqury/merqury-merqury.diff | 33 +++-- modules/nf-core/merqury/merqury/meta.yml | 29 ++++- .../merqury/merqury/tests/main.nf.test | 4 +- .../merqury/merqury/tests/main.nf.test.snap | 62 ++++++--- modules/nf-core/meryl/count/environment.yml | 2 +- modules/nf-core/meryl/count/main.nf | 17 +-- modules/nf-core/meryl/count/meta.yml | 29 ++++- .../meryl/count/tests/main.nf.test.snap | 44 +++++-- .../nf-core/meryl/unionsum/environment.yml | 2 +- modules/nf-core/meryl/unionsum/main.nf | 12 +- modules/nf-core/meryl/unionsum/meta.yml | 32 +++-- .../meryl/unionsum/tests/main.nf.test.snap | 66 +++++++--- modules/nf-core/pilon/main.nf | 13 +- modules/nf-core/pilon/meta.yml | 63 +++++---- modules/nf-core/pilon/tests/main.nf.test.snap | 88 +++++++++---- modules/nf-core/ragtag/patch/main.nf | 12 +- modules/nf-core/ragtag/patch/meta.yml | 41 ++++-- .../nf-core/ragtag/patch/tests/main.nf.test | 2 +- .../ragtag/patch/tests/main.nf.test.snap | 42 ++++-- modules/nf-core/ragtag/scaffold/main.nf | 11 +- modules/nf-core/ragtag/scaffold/meta.yml | 36 ++++-- .../ragtag/scaffold/tests/main.nf.test.snap | 44 +++++-- 36 files changed, 707 insertions(+), 478 deletions(-) diff --git a/modules.json b/modules.json index 0b8b52ca..2330dd16 100644 --- a/modules.json +++ b/modules.json @@ -43,35 +43,35 @@ }, "hifiasm": { "branch": "master", - "git_sha": "dbc43f924a7410a2b828f2a022305f1d6e71ed84", + "git_sha": "c87519b3c0bfcb0b022b77738cf8164b3af042eb", "installed_by": ["modules"] }, "liftoff": { "branch": "master", - "git_sha": "e753770db613ce014b3c4bc94f6cba443427b726", + "git_sha": "c7659d7353f37c1fb4407aaf013bc7d4b6cec015", "installed_by": ["modules"], "patch": "modules/nf-core/liftoff/liftoff.diff" }, "links": { "branch": "master", - "git_sha": "e753770db613ce014b3c4bc94f6cba443427b726", + "git_sha": "61b37255166a4484051e63ebbb1376960a08e73e", "installed_by": ["modules"], "patch": "modules/nf-core/links/links.diff" }, "merqury/merqury": { "branch": "master", - "git_sha": "e753770db613ce014b3c4bc94f6cba443427b726", + "git_sha": "43f0c26fd70298d296a27ce3a68f1c0f7530f600", "installed_by": ["modules"], "patch": "modules/nf-core/merqury/merqury/merqury-merqury.diff" }, "meryl/count": { "branch": "master", - "git_sha": "41dfa3f7c0ffabb96a6a813fe321c6d1cc5b6e46", + "git_sha": "43f0c26fd70298d296a27ce3a68f1c0f7530f600", "installed_by": ["modules"] }, "meryl/unionsum": { "branch": "master", - "git_sha": "41dfa3f7c0ffabb96a6a813fe321c6d1cc5b6e46", + "git_sha": "43f0c26fd70298d296a27ce3a68f1c0f7530f600", "installed_by": ["modules"] }, "minimap2/align": { @@ -92,17 +92,17 @@ }, "pilon": { "branch": "master", - "git_sha": "41dfa3f7c0ffabb96a6a813fe321c6d1cc5b6e46", + "git_sha": "79f27eb566084b865363b634136cfe2256b84051", "installed_by": ["modules"] }, "ragtag/patch": { "branch": "master", - "git_sha": "41dfa3f7c0ffabb96a6a813fe321c6d1cc5b6e46", + "git_sha": "96f35d1a14c9bd453c5cefff865622e43057dbc7", "installed_by": ["modules"] }, "ragtag/scaffold": { "branch": "master", - "git_sha": "e753770db613ce014b3c4bc94f6cba443427b726", + "git_sha": "96f35d1a14c9bd453c5cefff865622e43057dbc7", "installed_by": ["modules"] }, "samtools/faidx": { diff --git a/modules/nf-core/hifiasm/main.nf b/modules/nf-core/hifiasm/main.nf index 26557d84..65d98df7 100644 --- a/modules/nf-core/hifiasm/main.nf +++ b/modules/nf-core/hifiasm/main.nf @@ -24,7 +24,8 @@ process HIFIASM { tuple val(meta), path("*.ec.fa.gz") , emit: corrected_reads , optional: true tuple val(meta), path("*.ovlp.paf.gz") , emit: read_overlaps , optional: true tuple val(meta), path("${prefix}.stderr.log") , emit: log - path "versions.yml" , emit: versions + tuple val("${task.process}"), val('hifasm'), eval('hifiasm --version 2>&1'), emit: versions_hifiasm, topic: versions + when: task.ext.when == null || task.ext.when @@ -76,11 +77,6 @@ process HIFIASM { if [ -f ${prefix}.ovlp.paf ]; then gzip ${prefix}.ovlp.paf fi - - cat <<-END_VERSIONS > versions.yml - "${task.process}": - hifiasm: \$(hifiasm --version 2>&1) - END_VERSIONS """ stub: @@ -101,10 +97,5 @@ process HIFIASM { echo "" | gzip > ${prefix}.ec.fa.gz echo "" | gzip > ${prefix}.ovlp.paf.gz touch ${prefix}.stderr.log - - cat <<-END_VERSIONS > versions.yml - "${task.process}": - hifiasm: \$(hifiasm --version 2>&1) - END_VERSIONS """ } diff --git a/modules/nf-core/hifiasm/meta.yml b/modules/nf-core/hifiasm/meta.yml index 272a0828..61a9a997 100644 --- a/modules/nf-core/hifiasm/meta.yml +++ b/modules/nf-core/hifiasm/meta.yml @@ -14,7 +14,8 @@ tools: documentation: https://github.com/chhylp123/hifiasm tool_dev_url: https://github.com/chhylp123/hifiasm doi: "10.1038/s41592-020-01056-5" - licence: ["MIT"] + licence: + - "MIT" identifier: biotools:hifiasm input: - - meta: @@ -24,7 +25,8 @@ input: e.g. [ id:'test', single_end:false ] - long_reads: type: file - description: Long reads PacBio HiFi reads or ONT reads (requires ext.arg '--ont'). + description: Long reads PacBio HiFi reads or ONT reads (requires ext.arg + '--ont'). ontologies: [] - ul_reads: type: file @@ -36,13 +38,13 @@ input: Groovy Map containing information about parental kmers. - paternal_kmer_dump: type: file - description: Yak kmer dump file for paternal reads (can be used for haplotype - resolution). It can have an arbitrary extension. + description: Yak kmer dump file for paternal reads (can be used for + haplotype resolution). It can have an arbitrary extension. ontologies: [] - maternal_kmer_dump: type: file - description: Yak kmer dump file for maternal reads (can be used for haplotype - resolution). It can have an arbitrary extension. + description: Yak kmer dump file for maternal reads (can be used for + haplotype resolution). It can have an arbitrary extension. ontologies: [] - - meta2: type: map @@ -76,7 +78,7 @@ output: description: Raw unitigs pattern: "*.r_utg.gfa" ontologies: - - edam: http://edamontology.org/format_3975 # GFA 1 + - edam: http://edamontology.org/format_3975 bin_files: - - meta: type: map @@ -105,7 +107,7 @@ output: description: Processed unitigs pattern: "*.p_utg.gfa" ontologies: - - edam: http://edamontology.org/format_3975 # GFA 1 + - edam: http://edamontology.org/format_3975 primary_contigs: - - meta: type: map @@ -175,7 +177,7 @@ output: reads produced by the hifiasm error correction module pattern: "*.ec.fa.gz" ontologies: - - edam: http://edamontology.org/format_3989 # GZIP format + - edam: http://edamontology.org/format_3989 read_overlaps: - - meta: type: map @@ -189,7 +191,7 @@ output: among all error-corrected reads pattern: "*.ovlp.paf.gz" ontologies: - - edam: http://edamontology.org/format_3989 # GZIP format + - edam: http://edamontology.org/format_3989 log: - - meta: type: map @@ -201,13 +203,27 @@ output: description: Stderr log pattern: "*.stderr.log" ontologies: [] + versions_hifiasm: + - - ${task.process}: + type: string + description: The name of the process + - hifasm: + type: string + description: The name of the tool + - hifiasm --version 2>&1: + type: eval + description: The expression to obtain the version of the tool +topics: 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 name of the process + - hifasm: + type: string + description: The name of the tool + - hifiasm --version 2>&1: + type: eval + description: The expression to obtain the version of the tool authors: - "@sidorov-si" - "@scorreard" diff --git a/modules/nf-core/hifiasm/tests/main.nf.test b/modules/nf-core/hifiasm/tests/main.nf.test index ceda17b9..d158847b 100644 --- a/modules/nf-core/hifiasm/tests/main.nf.test +++ b/modules/nf-core/hifiasm/tests/main.nf.test @@ -59,7 +59,7 @@ nextflow_process { process.out.hap2_contigs, process.out.fasta, process.out.paf, - process.out.versions + process.out.findAll { key, val -> key.startsWith('versions') } ).match() } ) } @@ -133,7 +133,7 @@ nextflow_process { process.out.primary_contigs, process.out.hap1_contigs, process.out.hap2_contigs, - process.out.versions + process.out.findAll { key, val -> key.startsWith('versions') } ).match() } ) } @@ -191,7 +191,7 @@ nextflow_process { process.out.primary_contigs, process.out.hap1_contigs, process.out.hap2_contigs, - process.out.versions + process.out.findAll { key, val -> key.startsWith('versions') } ).match() } ) } @@ -249,7 +249,7 @@ nextflow_process { process.out.raw_unitigs, process.out.processed_unitigs, process.out.hap1_contigs, - process.out.versions + process.out.findAll { key, val -> key.startsWith('versions') } ).match() } ) } @@ -309,7 +309,7 @@ nextflow_process { process.out.primary_contigs, process.out.alternate_contigs, process.out.hap1_contigs, - process.out.versions + process.out.findAll { key, val -> key.startsWith('versions') } ).match() } ) } diff --git a/modules/nf-core/hifiasm/tests/main.nf.test.snap b/modules/nf-core/hifiasm/tests/main.nf.test.snap index cf8a7eba..044a6dbb 100644 --- a/modules/nf-core/hifiasm/tests/main.nf.test.snap +++ b/modules/nf-core/hifiasm/tests/main.nf.test.snap @@ -36,15 +36,21 @@ "test.hic.hap1.p_ctg.gfa:md5,f67a8fdfa756961360732c79d189054d" ] ], - [ - "versions.yml:md5,b7828129854c24ce80a025f90bf3f557" - ] + { + "versions_hifiasm": [ + [ + "HIFIASM", + "hifasm", + "0.25.0-r726" + ] + ] + } ], + "timestamp": "2026-02-16T15:34:30.534362903", "meta": { - "nf-test": "0.9.2", - "nextflow": "24.10.5" - }, - "timestamp": "2025-04-15T14:24:48.67437687" + "nf-test": "0.9.4", + "nextflow": "26.01.1" + } }, "homo_sapiens pacbio hifi [fastq, [,], [,], [bin] ]": { "content": [ @@ -88,15 +94,21 @@ "test.bp.hap2.p_ctg.gfa:md5,ac2116fd2f22c67d4c304cbf9b9f7793" ] ], - [ - "versions.yml:md5,b7828129854c24ce80a025f90bf3f557" - ] + { + "versions_hifiasm": [ + [ + "HIFIASM", + "hifasm", + "0.25.0-r726" + ] + ] + } ], + "timestamp": "2026-02-16T16:19:44.451598985", "meta": { - "nf-test": "0.9.2", - "nextflow": "24.10.5" - }, - "timestamp": "2025-04-15T14:24:20.599937477" + "nf-test": "0.9.4", + "nextflow": "25.04.8" + } }, "homo_sapiens pacbio hifi [fastq x2, [,], [,], [,] ]": { "content": [ @@ -140,15 +152,21 @@ "test.bp.hap2.p_ctg.gfa:md5,ce096a66c9bba039c6a22ba9e9409d01" ] ], - [ - "versions.yml:md5,b7828129854c24ce80a025f90bf3f557" - ] + { + "versions_hifiasm": [ + [ + "HIFIASM", + "hifasm", + "0.25.0-r726" + ] + ] + } ], + "timestamp": "2026-02-16T16:19:55.227290441", "meta": { - "nf-test": "0.9.2", - "nextflow": "24.10.5" - }, - "timestamp": "2025-04-15T14:24:28.744387853" + "nf-test": "0.9.4", + "nextflow": "25.04.8" + } }, "homo_sapiens pacbio hifi [fastq, [,], [,], [,] ] - stub": { "content": [ @@ -176,7 +194,11 @@ ] ], "10": [ - "versions.yml:md5,b7828129854c24ce80a025f90bf3f557" + [ + "HIFIASM", + "hifasm", + "0.25.0-r726" + ] ], "2": [ [ @@ -334,16 +356,20 @@ "test.ovlp.paf.gz:md5,68b329da9893e34099c7d8ad5cb9c940" ] ], - "versions": [ - "versions.yml:md5,b7828129854c24ce80a025f90bf3f557" + "versions_hifiasm": [ + [ + "HIFIASM", + "hifasm", + "0.25.0-r726" + ] ] } ], + "timestamp": "2026-02-16T10:53:50.939408883", "meta": { - "nf-test": "0.9.2", - "nextflow": "24.10.5" - }, - "timestamp": "2025-04-17T15:56:54.410648332" + "nf-test": "0.9.4", + "nextflow": "26.01.1" + } }, "homo_sapiens pacbio hifi [fastq, [,], [,], [,]]": { "content": [ @@ -389,15 +415,21 @@ ], null, null, - [ - "versions.yml:md5,b7828129854c24ce80a025f90bf3f557" - ] + { + "versions_hifiasm": [ + [ + "HIFIASM", + "hifasm", + "0.25.0-r726" + ] + ] + } ], + "timestamp": "2026-02-16T10:52:36.945051713", "meta": { - "nf-test": "0.9.2", - "nextflow": "24.10.5" - }, - "timestamp": "2025-04-15T14:24:12.033205578" + "nf-test": "0.9.4", + "nextflow": "26.01.1" + } }, "homo_sapiens pacbio hifi [fastq, [yak, yak], [,], [,] ]": { "content": [ @@ -425,14 +457,20 @@ "test.dip.hap1.p_ctg.gfa:md5,eed5da5f3dd415dbb711edb61a09802f" ] ], - [ - "versions.yml:md5,b7828129854c24ce80a025f90bf3f557" - ] + { + "versions_hifiasm": [ + [ + "HIFIASM", + "hifasm", + "0.25.0-r726" + ] + ] + } ], + "timestamp": "2026-02-16T16:20:05.943561958", "meta": { - "nf-test": "0.9.2", - "nextflow": "24.10.5" - }, - "timestamp": "2025-04-15T14:24:37.330378652" + "nf-test": "0.9.4", + "nextflow": "25.04.8" + } } } \ No newline at end of file diff --git a/modules/nf-core/liftoff/main.nf b/modules/nf-core/liftoff/main.nf index 649e4ba4..dec05984 100644 --- a/modules/nf-core/liftoff/main.nf +++ b/modules/nf-core/liftoff/main.nf @@ -15,7 +15,7 @@ process LIFTOFF { tuple val(meta), path("${prefix}.gff3") , emit: gff3 tuple val(meta), path("*.polished.gff3") , emit: polished_gff3, optional: true tuple val(meta), path("*.unmapped.txt") , emit: unmapped_txt - path "versions.yml" , emit: versions + tuple val("${task.process}"), val('liftoff'), eval('liftoff --version | sed "s/v//"'), emit: versions_liftoff, topic: versions when: task.ext.when == null || task.ext.when @@ -56,11 +56,6 @@ process LIFTOFF { "${prefix}.gff3_polished" \\ "${prefix}.polished.gff3" \\ || echo "-polish is absent" - - cat <<-END_VERSIONS > versions.yml - "${task.process}": - liftoff: \$(liftoff --version 2> /dev/null) - END_VERSIONS """ stub: @@ -71,10 +66,5 @@ process LIFTOFF { touch "${prefix}.gff3" touch "${prefix}.unmapped.txt" $touch_polished - - cat <<-END_VERSIONS > versions.yml - "${task.process}": - liftoff: \$(liftoff --version 2> /dev/null) - END_VERSIONS """ } diff --git a/modules/nf-core/liftoff/meta.yml b/modules/nf-core/liftoff/meta.yml index 984ccb0c..35efaf78 100644 --- a/modules/nf-core/liftoff/meta.yml +++ b/modules/nf-core/liftoff/meta.yml @@ -1,4 +1,3 @@ -# yaml-language-server: $schema=https://raw.githubusercontent.com/nf-core/modules/master/modules/meta-schema.json name: "liftoff" description: | Uses Liftoff to accurately map annotations in GFF or GTF between assemblies of the same, @@ -18,7 +17,8 @@ tools: documentation: "https://github.com/agshumate/Liftoff" tool_dev_url: "https://github.com/agshumate/Liftoff" doi: "10.1093/bioinformatics/bty191" - licence: ["GPL v3 License"] + licence: + - "GPL v3 License" identifier: biotools:liftoff input: - - meta: @@ -67,7 +67,8 @@ output: e.g. `[ id:'test' ]` - "*.polished.gff3": type: file - description: Polished lifted annotations for the target assembly in gff3 format + description: Polished lifted annotations for the target assembly in gff3 + format pattern: "*.polished.gff3" ontologies: [] unmapped_txt: @@ -81,13 +82,27 @@ output: description: List of unmapped reference annotations pattern: "*.unmapped.txt" ontologies: [] + versions_liftoff: + - - ${task.process}: + type: string + description: The name of the process + - liftoff: + type: string + description: The name of the tool + - liftoff --version | sed "s/v//": + type: eval + description: The expression to obtain the version of the tool +topics: 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 name of the process + - liftoff: + type: string + description: The name of the tool + - liftoff --version | sed "s/v//": + type: eval + description: The expression to obtain the version of the tool authors: - "@GallVp" maintainers: diff --git a/modules/nf-core/liftoff/tests/main.nf.test b/modules/nf-core/liftoff/tests/main.nf.test index 161f925b..bdc705a0 100644 --- a/modules/nf-core/liftoff/tests/main.nf.test +++ b/modules/nf-core/liftoff/tests/main.nf.test @@ -34,9 +34,9 @@ nextflow_process { assertAll( { assert process.success }, { assert snapshot(process.out.unmapped_txt).match("unmapped_txt") }, + { assert snapshot(process.out.findAll { key, val -> key.startsWith('versions') }).match() }, { assert file(process.out.gff3[0][1]).text.contains("chr21\tLiftoff\texon\t34608061\t34608118\t.\t+\t.") }, { assert file(process.out.polished_gff3[0][1]).text.contains("chr21\tLiftoff\texon\t34608061\t34608118\t.\t+\t.") }, - { assert snapshot(process.out.versions).match("versions") } ) } diff --git a/modules/nf-core/liftoff/tests/main.nf.test.snap b/modules/nf-core/liftoff/tests/main.nf.test.snap index f6064467..545f8ab3 100644 --- a/modules/nf-core/liftoff/tests/main.nf.test.snap +++ b/modules/nf-core/liftoff/tests/main.nf.test.snap @@ -10,23 +10,29 @@ ] ] ], + "timestamp": "2023-12-01T13:57:40.748507", "meta": { "nf-test": "0.8.4", "nextflow": "23.10.1" - }, - "timestamp": "2023-12-01T13:57:40.748507" + } }, - "versions": { + "homo_sapiens-genome_21_fasta-genome_1_fasta-genome_1_gtf": { "content": [ - [ - "versions.yml:md5,205d9c609e7fe27d8199550d842bdce8" - ] + { + "versions_liftoff": [ + [ + "LIFTOFF", + "liftoff", + "1.6.3" + ] + ] + } ], + "timestamp": "2026-02-16T17:56:04.622385608", "meta": { - "nf-test": "0.8.4", - "nextflow": "23.10.1" - }, - "timestamp": "2023-12-01T13:57:40.752414" + "nf-test": "0.9.4", + "nextflow": "25.04.8" + } }, "homo_sapiens-genome_21_fasta-genome_1_fasta-genome_1_gtf-stub": { "content": [ @@ -56,7 +62,11 @@ ] ], "3": [ - "versions.yml:md5,205d9c609e7fe27d8199550d842bdce8" + [ + "LIFTOFF", + "liftoff", + "1.6.3" + ] ], "gff3": [ [ @@ -82,15 +92,19 @@ "test.unmapped.txt:md5,d41d8cd98f00b204e9800998ecf8427e" ] ], - "versions": [ - "versions.yml:md5,205d9c609e7fe27d8199550d842bdce8" + "versions_liftoff": [ + [ + "LIFTOFF", + "liftoff", + "1.6.3" + ] ] } ], + "timestamp": "2026-02-16T17:56:15.752736312", "meta": { - "nf-test": "0.8.4", - "nextflow": "23.10.1" - }, - "timestamp": "2024-03-19T09:15:25.661428" + "nf-test": "0.9.4", + "nextflow": "25.04.8" + } } } \ No newline at end of file diff --git a/modules/nf-core/links/main.nf b/modules/nf-core/links/main.nf index 39c50db4..0034f41c 100644 --- a/modules/nf-core/links/main.nf +++ b/modules/nf-core/links/main.nf @@ -22,7 +22,7 @@ process LINKS { tuple val(meta), path("*.assembly_correspondence.tsv"), emit: assembly_correspondence tuple val(meta), path("*.simplepair_checkpoint.tsv"), emit: simplepair_checkpoint, optional: true tuple val(meta), path("*.tigpair_checkpoint.tsv"), emit: tigpair_checkpoint - path "versions.yml", emit: versions + tuple val("${task.process}"), val('liftoff'), eval("echo \$(LINKS | grep -o 'LINKS v.*' | sed 's/LINKS v//')"), emit: versions_links, topic: versions when: task.ext.when == null || task.ext.when diff --git a/modules/nf-core/links/meta.yml b/modules/nf-core/links/meta.yml index 288e1bd7..9cdcde4f 100644 --- a/modules/nf-core/links/meta.yml +++ b/modules/nf-core/links/meta.yml @@ -17,9 +17,9 @@ tools: documentation: "https://github.com/bcgsc/LINKS" tool_dev_url: "https://github.com/bcgsc/LINKS" doi: "10.1186/s13742-015-0076-3" - licence: ["GPL v3"] + licence: + - "GPL v3" identifier: "" - input: - - meta: type: map @@ -38,11 +38,11 @@ input: e.g. `[ id:'sample1' ]` - reads: type: file - description: fastq file(s) containing the long reads to be used for scaffolding + description: fastq file(s) containing the long reads to be used for + scaffolding pattern: "*.{fq,fastq,fq.gz,fastq.gz}" - ontologies: - - edam: http://edamontology.org/format_1930 # FASTQ + - edam: http://edamontology.org/format_1930 output: log: - - meta: @@ -70,7 +70,7 @@ output: that distance. pattern: "*.pairing_distribution.csv" ontologies: - - edam: http://edamontology.org/format_3752 # CSV + - edam: http://edamontology.org/format_3752 pairing_issues: - - meta: type: map @@ -146,7 +146,7 @@ output: links ratio, gap or overlap pattern: "*.assembly_correspondence.tsv" ontologies: - - edam: http://edamontology.org/format_3475 # TSV + - edam: http://edamontology.org/format_3475 simplepair_checkpoint: - - meta: type: map @@ -155,11 +155,11 @@ output: e.g. `[ id:'sample1']` - "*.simplepair_checkpoint.tsv": type: file - description: checkpoint file, contains info to rebuild datastructure for .gv - graph + description: checkpoint file, contains info to rebuild datastructure for + .gv graph pattern: "*.simplepair_checkpoint.tsv" ontologies: - - edam: http://edamontology.org/format_3475 # TSV + - edam: http://edamontology.org/format_3475 tigpair_checkpoint: - - meta: type: map @@ -179,15 +179,28 @@ output: 4) scaffold with output of ARCS pattern: "*.tigpair_checkpoint.tsv" ontologies: - - edam: http://edamontology.org/format_3475 # TSV + - edam: http://edamontology.org/format_3475 + versions_links: + - - ${task.process}: + type: string + description: The name of the process + - liftoff: + type: string + description: The name of the tool + - echo \$(LINKS | grep -o 'LINKS v.*' | sed 's/LINKS v//'): + type: eval + description: The expression to obtain the version of the tool +topics: 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 name of the process + - liftoff: + type: string + description: The name of the tool + - echo \$(LINKS | grep -o 'LINKS v.*' | sed 's/LINKS v//'): + type: eval + description: The expression to obtain the version of the tool authors: - "@nschan" maintainers: diff --git a/modules/nf-core/links/tests/main.nf.test b/modules/nf-core/links/tests/main.nf.test index c045b4aa..b10ecd46 100644 --- a/modules/nf-core/links/tests/main.nf.test +++ b/modules/nf-core/links/tests/main.nf.test @@ -33,14 +33,14 @@ nextflow_process { { assert process.success }, { assert snapshot( file(process.out.log[0][1]).name, - process.out.pairing_issues, + path(process.out.pairing_issues[0][1]).text == "", process.out.scaffolds_csv, process.out.scaffolds_fasta, process.out.bloom, file(process.out.scaffolds_graph[0][1]).name, process.out.assembly_correspondence, - process.out.tigpair_checkpoint, - process.out.versions + file(process.out.tigpair_checkpoint[0][1]).name, + process.out.findAll { key, val -> key.startsWith('versions') } ).match() } ) @@ -74,14 +74,14 @@ nextflow_process { { assert process.success }, { assert snapshot( file(process.out.log[0][1]).name, - process.out.pairing_issues, + file(process.out.pairing_issues[0][1]).text=="", process.out.scaffolds_csv, process.out.scaffolds_fasta, process.out.bloom, file(process.out.scaffolds_graph[0][1]).name, process.out.assembly_correspondence, - process.out.tigpair_checkpoint, - process.out.versions + file(process.out.tigpair_checkpoint[0][1]).text=="", + process.out.findAll { key, val -> key.startsWith('versions') } ).match() } ) diff --git a/modules/nf-core/links/tests/main.nf.test.snap b/modules/nf-core/links/tests/main.nf.test.snap index 8c19d398..a7870de4 100644 --- a/modules/nf-core/links/tests/main.nf.test.snap +++ b/modules/nf-core/links/tests/main.nf.test.snap @@ -2,14 +2,7 @@ "LINKS - sarscov2 test data - scaffolds": { "content": [ "test.log", - [ - [ - { - "id": "test" - }, - "test.pairing_issues:md5,d41d8cd98f00b204e9800998ecf8427e" - ] - ], + true, [ [ { @@ -43,23 +36,22 @@ "test.assembly_correspondence.tsv:md5,a65d30663dce705d382df52ab87ca8a4" ] ], - [ - [ - { - "id": "test" - }, - "test.tigpair_checkpoint.tsv:md5,d41d8cd98f00b204e9800998ecf8427e" + true, + { + "versions_links": [ + [ + "LINKS", + "liftoff", + "2.0.1" + ] ] - ], - [ - "versions.yml:md5,f58863e433b849b1ef0dfc19cb57656b" - ] + } ], + "timestamp": "2026-02-17T16:35:15.663121765", "meta": { - "nf-test": "0.9.2", - "nextflow": "24.10.3" - }, - "timestamp": "2025-04-25T14:13:53.050775593" + "nf-test": "0.9.4", + "nextflow": "25.04.8" + } }, "LINKS - stub": { "content": [ @@ -81,7 +73,11 @@ ] ], "10": [ - "versions.yml:md5,f58863e433b849b1ef0dfc19cb57656b" + [ + "LINKS", + "liftoff", + "2.0.1" + ] ], "2": [ [ @@ -227,28 +223,25 @@ "test.tigpair_checkpoint.tsv:md5,d41d8cd98f00b204e9800998ecf8427e" ] ], - "versions": [ - "versions.yml:md5,f58863e433b849b1ef0dfc19cb57656b" + "versions_links": [ + [ + "LINKS", + "liftoff", + "2.0.1" + ] ] } ], + "timestamp": "2026-02-16T11:20:02.239712752", "meta": { - "nf-test": "0.9.2", - "nextflow": "24.10.3" - }, - "timestamp": "2025-04-11T11:49:53.947870525" + "nf-test": "0.9.4", + "nextflow": "26.01.1" + } }, "LINKS - sarscov2 test data - contigs": { "content": [ "test.log", - [ - [ - { - "id": "test" - }, - "test.pairing_issues:md5,d41d8cd98f00b204e9800998ecf8427e" - ] - ], + true, [ [ { @@ -282,22 +275,21 @@ "test.assembly_correspondence.tsv:md5,b36e951b0a1bb4b1c1ccd50925392e3d" ] ], - [ - [ - { - "id": "test" - }, - "test.tigpair_checkpoint.tsv:md5,168f2075f524a86216118c7230ad65e9" + "test.tigpair_checkpoint.tsv", + { + "versions_links": [ + [ + "LINKS", + "liftoff", + "2.0.1" + ] ] - ], - [ - "versions.yml:md5,f58863e433b849b1ef0dfc19cb57656b" - ] + } ], + "timestamp": "2026-02-17T10:53:57.921992204", "meta": { - "nf-test": "0.9.2", - "nextflow": "24.10.3" - }, - "timestamp": "2025-04-25T14:07:49.212617595" + "nf-test": "0.9.4", + "nextflow": "25.04.8" + } } } \ No newline at end of file diff --git a/modules/nf-core/merqury/merqury/main.nf b/modules/nf-core/merqury/merqury/main.nf index 13e07e88..5d3871a5 100644 --- a/modules/nf-core/merqury/merqury/main.nf +++ b/modules/nf-core/merqury/merqury/main.nf @@ -16,19 +16,20 @@ process MERQURY_MERQURY { tuple val(meta), path("*_only.wig") , emit: assembly_only_kmers_wig tuple val(meta), path("*.completeness.stats"), emit: stats tuple val(meta), path("*.dist_only.hist") , emit: dist_hist - tuple val(meta), path("*.spectra-cn.fl.png") , emit: spectra_cn_fl_png, optional: true // optional to make full_test pass, where this file is not created. - tuple val(meta), path("*.spectra-cn.hist") , emit: spectra_cn_hist, optional: true // optional to make full_test pass, where this file is not created. - tuple val(meta), path("*.spectra-cn.ln.png") , emit: spectra_cn_ln_png, optional: true // optional to make full_test pass, where this file is not created. - tuple val(meta), path("*.spectra-cn.st.png") , emit: spectra_cn_st_png, optional: true // optional to make full_test pass, where this file is not created. - tuple val(meta), path("*.spectra-asm.fl.png"), emit: spectra_asm_fl_png, optional: true // optional to make full_test pass, where this file is not created. - tuple val(meta), path("*.spectra-asm.hist") , emit: spectra_asm_hist, optional: true // optional to make full_test pass, where this file is not created. - tuple val(meta), path("*.spectra-asm.ln.png"), emit: spectra_asm_ln_png, optional: true // optional to make full_test pass, where this file is not created. - tuple val(meta), path("*.spectra-asm.st.png"), emit: spectra_asm_st_png, optional: true // optional to make full_test pass, where this file is not created. - tuple val(meta), path("${prefix}.qv") , emit: assembly_qv - tuple val(meta), path("${prefix}.*.qv") , emit: scaffold_qv, optional: true // optional to make full_test pass, where this file is not created. - tuple val(meta), path("*.hist.ploidy") , emit: read_ploidy, optional: true // optional to make full_test pass, where this file is not created. - tuple val(meta), path("*.hapmers.blob.png") , emit: hapmers_blob_png, optional: true - path "versions.yml" , emit: versions + tuple val(meta), path("*.spectra-cn.fl.png") , emit: spectra_cn_fl_png, optional: true // optional to make full_test pass, where this file is not created. + tuple val(meta), path("*.spectra-cn.hist") , emit: spectra_cn_hist, optional: true // optional to make full_test pass, where this file is not created. + tuple val(meta), path("*.spectra-cn.ln.png") , emit: spectra_cn_ln_png, optional: true // optional to make full_test pass, where this file is not created. + tuple val(meta), path("*.spectra-cn.st.png") , emit: spectra_cn_st_png, optional: true // optional to make full_test pass, where this file is not created. + tuple val(meta), path("*.spectra-asm.fl.png"), emit: spectra_asm_fl_png, optional: true // optional to make full_test pass, where this file is not created. + tuple val(meta), path("*.spectra-asm.hist") , emit: spectra_asm_hist, optional: true // optional to make full_test pass, where this file is not created. + tuple val(meta), path("*.spectra-asm.ln.png"), emit: spectra_asm_ln_png, optional: true // optional to make full_test pass, where this file is not created. + tuple val(meta), path("*.spectra-asm.st.png"), emit: spectra_asm_st_png, optional: true // optional to make full_test pass, where this file is not created. + tuple val(meta), path("${prefix}.qv") , emit: assembly_qv, optional: true // optional to make full_test pass, where this file is not created. + tuple val(meta), path("${prefix}.*.qv") , emit: scaffold_qv, optional: true // optional to make full_test pass, where this file is not created. + tuple val(meta), path("*.hist.ploidy") , emit: read_ploidy, optional: true // optional to make full_test pass, where this file is not created. + tuple val(meta), path("*.hapmers.blob.png") , emit: hapmers_blob_png , optional: true + // WARN: Version information not provided by tool on CLI. Please update this string when bumping container versions. + tuple val("${task.process}"), val('merqury'), val('1.3'), emit: versions_merqury, topic: versions when: task.ext.when == null || task.ext.when @@ -36,7 +37,6 @@ process MERQURY_MERQURY { script: // def args = task.ext.args ?: '' prefix = task.ext.prefix ?: "${meta.id}" - def VERSION = 1.3 // WARN: Version information not provided by tool on CLI. Please update this string when bumping container versions. """ # Nextflow changes the container --entrypoint to /bin/bash (container default entrypoint: /usr/local/env-execute) # Check for container variable initialisation script and source it. @@ -52,11 +52,6 @@ process MERQURY_MERQURY { $meryl_db \\ $assembly \\ $prefix - - cat <<-END_VERSIONS > versions.yml - "${task.process}": - merqury: $VERSION - END_VERSIONS """ stub: @@ -78,10 +73,5 @@ process MERQURY_MERQURY { touch ${prefix}.qv touch ${prefix}.${prefix}.qv touch ${prefix}.hist.ploidy - - cat <<-END_VERSIONS > versions.yml - "${task.process}": - merqury: $VERSION - END_VERSIONS """ } diff --git a/modules/nf-core/merqury/merqury/merqury-merqury.diff b/modules/nf-core/merqury/merqury/merqury-merqury.diff index a0a65d32..9da9985a 100644 --- a/modules/nf-core/merqury/merqury/merqury-merqury.diff +++ b/modules/nf-core/merqury/merqury/merqury-merqury.diff @@ -4,7 +4,7 @@ Changes in component 'nf-core/merqury/merqury' Changes in 'merqury/merqury/main.nf': --- modules/nf-core/merqury/merqury/main.nf +++ modules/nf-core/merqury/merqury/main.nf -@@ -16,18 +16,18 @@ +@@ -16,17 +16,17 @@ tuple val(meta), path("*_only.wig") , emit: assembly_only_kmers_wig tuple val(meta), path("*.completeness.stats"), emit: stats tuple val(meta), path("*.dist_only.hist") , emit: dist_hist @@ -16,24 +16,23 @@ Changes in 'merqury/merqury/main.nf': - tuple val(meta), path("*.spectra-asm.hist") , emit: spectra_asm_hist - tuple val(meta), path("*.spectra-asm.ln.png"), emit: spectra_asm_ln_png - tuple val(meta), path("*.spectra-asm.st.png"), emit: spectra_asm_st_png -+ tuple val(meta), path("*.spectra-cn.fl.png") , emit: spectra_cn_fl_png, optional: true // optional to make full_test pass, where this file is not created. -+ tuple val(meta), path("*.spectra-cn.hist") , emit: spectra_cn_hist, optional: true // optional to make full_test pass, where this file is not created. -+ tuple val(meta), path("*.spectra-cn.ln.png") , emit: spectra_cn_ln_png, optional: true // optional to make full_test pass, where this file is not created. -+ tuple val(meta), path("*.spectra-cn.st.png") , emit: spectra_cn_st_png, optional: true // optional to make full_test pass, where this file is not created. -+ tuple val(meta), path("*.spectra-asm.fl.png"), emit: spectra_asm_fl_png, optional: true // optional to make full_test pass, where this file is not created. -+ tuple val(meta), path("*.spectra-asm.hist") , emit: spectra_asm_hist, optional: true // optional to make full_test pass, where this file is not created. -+ tuple val(meta), path("*.spectra-asm.ln.png"), emit: spectra_asm_ln_png, optional: true // optional to make full_test pass, where this file is not created. -+ tuple val(meta), path("*.spectra-asm.st.png"), emit: spectra_asm_st_png, optional: true // optional to make full_test pass, where this file is not created. - tuple val(meta), path("${prefix}.qv") , emit: assembly_qv +- tuple val(meta), path("${prefix}.qv") , emit: assembly_qv - tuple val(meta), path("${prefix}.*.qv") , emit: scaffold_qv - tuple val(meta), path("*.hist.ploidy") , emit: read_ploidy -- tuple val(meta), path("*.hapmers.blob.png") , emit: hapmers_blob_png , optional: true -+ tuple val(meta), path("${prefix}.*.qv") , emit: scaffold_qv, optional: true // optional to make full_test pass, where this file is not created. -+ tuple val(meta), path("*.hist.ploidy") , emit: read_ploidy, optional: true // optional to make full_test pass, where this file is not created. -+ tuple val(meta), path("*.hapmers.blob.png") , emit: hapmers_blob_png, optional: true - path "versions.yml" , emit: versions - - when: ++ tuple val(meta), path("*.spectra-cn.fl.png") , emit: spectra_cn_fl_png, optional: true // optional to make full_test pass, where this file is not created. ++ tuple val(meta), path("*.spectra-cn.hist") , emit: spectra_cn_hist, optional: true // optional to make full_test pass, where this file is not created. ++ tuple val(meta), path("*.spectra-cn.ln.png") , emit: spectra_cn_ln_png, optional: true // optional to make full_test pass, where this file is not created. ++ tuple val(meta), path("*.spectra-cn.st.png") , emit: spectra_cn_st_png, optional: true // optional to make full_test pass, where this file is not created. ++ tuple val(meta), path("*.spectra-asm.fl.png"), emit: spectra_asm_fl_png, optional: true // optional to make full_test pass, where this file is not created. ++ tuple val(meta), path("*.spectra-asm.hist") , emit: spectra_asm_hist, optional: true // optional to make full_test pass, where this file is not created. ++ tuple val(meta), path("*.spectra-asm.ln.png"), emit: spectra_asm_ln_png, optional: true // optional to make full_test pass, where this file is not created. ++ tuple val(meta), path("*.spectra-asm.st.png"), emit: spectra_asm_st_png, optional: true // optional to make full_test pass, where this file is not created. ++ tuple val(meta), path("${prefix}.qv") , emit: assembly_qv, optional: true // optional to make full_test pass, where this file is not created. ++ tuple val(meta), path("${prefix}.*.qv") , emit: scaffold_qv, optional: true // optional to make full_test pass, where this file is not created. ++ tuple val(meta), path("*.hist.ploidy") , emit: read_ploidy, optional: true // optional to make full_test pass, where this file is not created. + tuple val(meta), path("*.hapmers.blob.png") , emit: hapmers_blob_png , optional: true + // WARN: Version information not provided by tool on CLI. Please update this string when bumping container versions. + tuple val("${task.process}"), val('merqury'), val('1.3'), emit: versions_merqury, topic: versions 'modules/nf-core/merqury/merqury/tests/main.nf.test' is unchanged 'modules/nf-core/merqury/merqury/tests/main.nf.test.snap' is unchanged diff --git a/modules/nf-core/merqury/merqury/meta.yml b/modules/nf-core/merqury/merqury/meta.yml index 9e72531b..c4ef77c8 100644 --- a/modules/nf-core/merqury/merqury/meta.yml +++ b/modules/nf-core/merqury/merqury/meta.yml @@ -9,7 +9,8 @@ tools: description: "Evaluate genome assemblies with k-mers and more." tool_dev_url: "https://github.com/marbl/merqury" doi: "10.1186/s13059-020-02134-9" - licence: ["PUBLIC DOMAIN"] + licence: + - "PUBLIC DOMAIN" identifier: biotools:merqury input: - - meta: @@ -204,13 +205,27 @@ output: description: "Hap-mer blob plot" pattern: "*.hapmers.blob.png" ontologies: [] + versions_merqury: + - - ${task.process}: + type: string + description: The name of the process + - merqury: + type: string + description: The name of the tool + - '"1.3"': + type: string + description: The expression to obtain the version of the tool +topics: 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 name of the process + - merqury: + type: string + description: The name of the tool + - '"1.3"': + type: string + description: The expression to obtain the version of the tool authors: - "@mahesh-panchal" maintainers: diff --git a/modules/nf-core/merqury/merqury/tests/main.nf.test b/modules/nf-core/merqury/merqury/tests/main.nf.test index f20031fa..7d961bb8 100644 --- a/modules/nf-core/merqury/merqury/tests/main.nf.test +++ b/modules/nf-core/merqury/merqury/tests/main.nf.test @@ -66,7 +66,7 @@ nextflow_process { process.out.assembly_qv, process.out.scaffold_qv, process.out.read_ploidy, - process.out.versions, + process.out.findAll { key, val -> key.startsWith('versions') }, file(process.out.spectra_cn_fl_png[0][1]).name, file(process.out.spectra_cn_ln_png[0][1]).name, file(process.out.spectra_cn_st_png[0][1]).name, @@ -146,7 +146,7 @@ nextflow_process { process.out.assembly_qv, process.out.scaffold_qv, process.out.read_ploidy, - process.out.versions, + process.out.findAll { key, val -> key.startsWith('versions') }, process.out.spectra_cn_fl_png[0][1] .collect { file(it).name }.join(','), process.out.spectra_cn_ln_png[0][1] .collect { file(it).name }.join(','), process.out.spectra_cn_st_png[0][1] .collect { file(it).name }.join(','), diff --git a/modules/nf-core/merqury/merqury/tests/main.nf.test.snap b/modules/nf-core/merqury/merqury/tests/main.nf.test.snap index 4081b60f..2c52cd0f 100644 --- a/modules/nf-core/merqury/merqury/tests/main.nf.test.snap +++ b/modules/nf-core/merqury/merqury/tests/main.nf.test.snap @@ -69,7 +69,11 @@ ], "16": [ - "versions.yml:md5,825a4c61369638389227eee16dfb08b5" + [ + "MERQURY_MERQURY", + "merqury", + "1.3" + ] ], "2": [ [ @@ -281,16 +285,20 @@ "test.completeness.stats:md5,d41d8cd98f00b204e9800998ecf8427e" ] ], - "versions": [ - "versions.yml:md5,825a4c61369638389227eee16dfb08b5" + "versions_merqury": [ + [ + "MERQURY_MERQURY", + "merqury", + "1.3" + ] ] } ], + "timestamp": "2026-02-16T14:11:24.534630119", "meta": { - "nf-test": "0.8.4", - "nextflow": "23.10.1" - }, - "timestamp": "2024-05-22T21:00:35.907142" + "nf-test": "0.9.4", + "nextflow": "26.01.1" + } }, "homo_sapiens-genome": { "content": [ @@ -375,9 +383,15 @@ "test.unionsum.hist.ploidy:md5,3fe5dc377d8c9562980e1b93d01cac94" ] ], - [ - "versions.yml:md5,825a4c61369638389227eee16dfb08b5" - ], + { + "versions_merqury": [ + [ + "MERQURY_MERQURY", + "merqury", + "1.3" + ] + ] + }, "test.genome.spectra-cn.fl.png", "test.genome.spectra-cn.ln.png", "test.genome.spectra-cn.st.png", @@ -385,11 +399,11 @@ "test.spectra-asm.ln.png", "test.spectra-asm.st.png" ], + "timestamp": "2026-02-16T15:50:35.075502455", "meta": { - "nf-test": "0.9.1", - "nextflow": "24.10.0" - }, - "timestamp": "2024-11-04T12:42:32.360126893" + "nf-test": "0.9.4", + "nextflow": "25.04.8" + } }, "homo_sapiens-genome-trio": { "content": [ @@ -474,9 +488,15 @@ "test.unionsum.hist.ploidy:md5,3fe5dc377d8c9562980e1b93d01cac94" ] ], - [ - "versions.yml:md5,825a4c61369638389227eee16dfb08b5" - ], + { + "versions_merqury": [ + [ + "MERQURY_MERQURY", + "merqury", + "1.3" + ] + ] + }, "test.genome.spectra-cn.fl.png,test.genome.test.test2_1.spectra-cn.fl.png,test.genome.test.test2_2.spectra-cn.fl.png", "test.genome.spectra-cn.ln.png,test.genome.test.test2_1.spectra-cn.ln.png,test.genome.test.test2_2.spectra-cn.ln.png", "test.genome.spectra-cn.st.png,test.genome.test.test2_1.spectra-cn.st.png,test.genome.test.test2_2.spectra-cn.st.png", @@ -485,10 +505,10 @@ "test.spectra-asm.st.png", "test.hapmers.blob.png" ], + "timestamp": "2026-02-16T15:52:37.264260087", "meta": { - "nf-test": "0.9.2", - "nextflow": "24.10.2" - }, - "timestamp": "2024-12-04T20:06:48.980375307" + "nf-test": "0.9.4", + "nextflow": "25.04.8" + } } } \ No newline at end of file diff --git a/modules/nf-core/meryl/count/environment.yml b/modules/nf-core/meryl/count/environment.yml index deebca1f..84d58043 100644 --- a/modules/nf-core/meryl/count/environment.yml +++ b/modules/nf-core/meryl/count/environment.yml @@ -4,4 +4,4 @@ channels: - conda-forge - bioconda dependencies: - - bioconda::meryl=1.4.1 + - bioconda::meryl=1.4.1=h4ac6f70_0 diff --git a/modules/nf-core/meryl/count/main.nf b/modules/nf-core/meryl/count/main.nf index c90079d6..93c4ec0d 100644 --- a/modules/nf-core/meryl/count/main.nf +++ b/modules/nf-core/meryl/count/main.nf @@ -4,8 +4,8 @@ process MERYL_COUNT { conda "${moduleDir}/environment.yml" container "${ workflow.containerEngine == 'singularity' && !task.ext.singularity_pull_docker_container ? - 'https://depot.galaxyproject.org/singularity/meryl:1.4.1--h4ac6f70_1': - 'biocontainers/meryl:1.4.1--h4ac6f70_1' }" + 'https://depot.galaxyproject.org/singularity/meryl:1.4.1--h4ac6f70_0': + 'biocontainers/meryl:1.4.1--h4ac6f70_0' }" input: tuple val(meta), path(reads) @@ -13,7 +13,8 @@ process MERYL_COUNT { output: tuple val(meta), path("*.meryl") , emit: meryl_db - path "versions.yml" , emit: versions + tuple val("${task.process}"), val('meryl'), eval("meryl --version |& sed 's/meryl //'"), emit: versions_meryl, topic: versions + when: task.ext.when == null || task.ext.when @@ -32,11 +33,6 @@ process MERYL_COUNT { \$READ \\ output ${prefix}.\${READ%.f*}.meryl done - - cat <<-END_VERSIONS > versions.yml - "${task.process}": - meryl: \$( meryl --version |& sed -n 's/.* \\([a-f0-9]\\{40\\}\\))/\\1/p' ) - END_VERSIONS """ stub: @@ -45,10 +41,5 @@ process MERYL_COUNT { for READ in ${reads}; do touch ${prefix}.\${READ%.f*}.meryl done - - cat <<-END_VERSIONS > versions.yml - "${task.process}": - meryl: \$( meryl --version |& sed -n 's/.* \\([a-f0-9]\\{40\\}\\))/\\1/p' ) - END_VERSIONS """ } diff --git a/modules/nf-core/meryl/count/meta.yml b/modules/nf-core/meryl/count/meta.yml index 0cb2a294..080eef04 100644 --- a/modules/nf-core/meryl/count/meta.yml +++ b/modules/nf-core/meryl/count/meta.yml @@ -10,7 +10,8 @@ tools: homepage: "https://github.com/marbl/meryl" documentation: "https://meryl.readthedocs.io/en/latest/quick-start.html" tool_dev_url: "https://github.com/marbl/meryl" - licence: ["GPL"] + licence: + - "GPL" identifier: biotools:meryl input: - - meta: @@ -38,13 +39,27 @@ output: type: directory description: A Meryl k-mer database pattern: "*.meryl" + versions_meryl: + - - ${task.process}: + type: string + description: The name of the process + - meryl: + type: string + description: The name of the tool + - meryl --version |& sed 's/meryl //': + type: eval + description: The expression to obtain the version of the tool +topics: 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 name of the process + - meryl: + type: string + description: The name of the tool + - meryl --version |& sed 's/meryl //': + type: eval + description: The expression to obtain the version of the tool authors: - "@mahesh-panchal" maintainers: diff --git a/modules/nf-core/meryl/count/tests/main.nf.test.snap b/modules/nf-core/meryl/count/tests/main.nf.test.snap index 2e94c03b..ccbb599f 100644 --- a/modules/nf-core/meryl/count/tests/main.nf.test.snap +++ b/modules/nf-core/meryl/count/tests/main.nf.test.snap @@ -142,7 +142,11 @@ ] ], "1": [ - "versions.yml:md5,f1c1f87947a64d681c3f0678036cafeb" + [ + "MERYL_COUNT", + "meryl", + "1.4.1" + ] ], "meryl_db": [ [ @@ -283,16 +287,20 @@ ] ] ], - "versions": [ - "versions.yml:md5,f1c1f87947a64d681c3f0678036cafeb" + "versions_meryl": [ + [ + "MERYL_COUNT", + "meryl", + "1.4.1" + ] ] } ], + "timestamp": "2026-02-17T14:13:53.351513294", "meta": { - "nf-test": "0.9.2", - "nextflow": "24.10.2" - }, - "timestamp": "2024-12-04T12:27:45.159763589" + "nf-test": "0.9.4", + "nextflow": "25.04.8" + } }, "bacteroides_fragilis - fastq - stub": { "content": [ @@ -307,7 +315,11 @@ ] ], "1": [ - "versions.yml:md5,f1c1f87947a64d681c3f0678036cafeb" + [ + "MERYL_COUNT", + "meryl", + "1.4.1" + ] ], "meryl_db": [ [ @@ -318,15 +330,19 @@ "test.test1_1.meryl:md5,d41d8cd98f00b204e9800998ecf8427e" ] ], - "versions": [ - "versions.yml:md5,f1c1f87947a64d681c3f0678036cafeb" + "versions_meryl": [ + [ + "MERYL_COUNT", + "meryl", + "1.4.1" + ] ] } ], + "timestamp": "2026-02-17T14:14:00.895730474", "meta": { - "nf-test": "0.9.2", - "nextflow": "24.10.2" - }, - "timestamp": "2024-12-04T20:07:50.042776716" + "nf-test": "0.9.4", + "nextflow": "25.04.8" + } } } \ No newline at end of file diff --git a/modules/nf-core/meryl/unionsum/environment.yml b/modules/nf-core/meryl/unionsum/environment.yml index deebca1f..84d58043 100644 --- a/modules/nf-core/meryl/unionsum/environment.yml +++ b/modules/nf-core/meryl/unionsum/environment.yml @@ -4,4 +4,4 @@ channels: - conda-forge - bioconda dependencies: - - bioconda::meryl=1.4.1 + - bioconda::meryl=1.4.1=h4ac6f70_0 diff --git a/modules/nf-core/meryl/unionsum/main.nf b/modules/nf-core/meryl/unionsum/main.nf index bc2853b0..8bf3c21b 100644 --- a/modules/nf-core/meryl/unionsum/main.nf +++ b/modules/nf-core/meryl/unionsum/main.nf @@ -13,7 +13,7 @@ process MERYL_UNIONSUM { output: tuple val(meta), path("*.unionsum.meryl"), emit: meryl_db - path "versions.yml" , emit: versions + tuple val("${task.process}"), val('meryl'), eval("meryl --version |& sed 's/meryl //'"), emit: versions_meryl, topic: versions when: task.ext.when == null || task.ext.when @@ -29,11 +29,6 @@ process MERYL_UNIONSUM { $args \\ output ${prefix}.unionsum.meryl \\ $meryl_dbs - - cat <<-END_VERSIONS > versions.yml - "${task.process}": - meryl: \$( meryl --version |& sed 's/meryl //' ) - END_VERSIONS """ stub: @@ -41,10 +36,5 @@ process MERYL_UNIONSUM { def prefix = task.ext.prefix ?: "${meta.id}" """ touch ${prefix}.unionsum.meryl - - cat <<-END_VERSIONS > versions.yml - "${task.process}": - meryl: \$( meryl --version |& sed 's/meryl //' ) - END_VERSIONS """ } diff --git a/modules/nf-core/meryl/unionsum/meta.yml b/modules/nf-core/meryl/unionsum/meta.yml index ce056955..add02adf 100644 --- a/modules/nf-core/meryl/unionsum/meta.yml +++ b/modules/nf-core/meryl/unionsum/meta.yml @@ -10,7 +10,8 @@ tools: homepage: "https://github.com/marbl/meryl" documentation: "https://meryl.readthedocs.io/en/latest/quick-start.html" tool_dev_url: "https://github.com/marbl/meryl" - licence: ["GPL"] + licence: + - "GPL" identifier: biotools:meryl input: - - meta: @@ -33,15 +34,30 @@ output: e.g. [ id:'test', single_end:false ] - "*.unionsum.meryl": type: directory - description: A Meryl k-mer database that is the union sum of the input databases + description: A Meryl k-mer database that is the union sum of the input + databases pattern: "*.unionsum.meryl" + versions_meryl: + - - ${task.process}: + type: string + description: The name of the process + - meryl: + type: string + description: The name of the tool + - meryl --version |& sed 's/meryl //': + type: eval + description: The expression to obtain the version of the tool +topics: 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 name of the process + - meryl: + type: string + description: The name of the tool + - meryl --version |& sed 's/meryl //': + type: eval + description: The expression to obtain the version of the tool authors: - "@mahesh-panchal" maintainers: diff --git a/modules/nf-core/meryl/unionsum/tests/main.nf.test.snap b/modules/nf-core/meryl/unionsum/tests/main.nf.test.snap index fd29eb18..923bac5a 100644 --- a/modules/nf-core/meryl/unionsum/tests/main.nf.test.snap +++ b/modules/nf-core/meryl/unionsum/tests/main.nf.test.snap @@ -12,7 +12,11 @@ ] ], "1": [ - "versions.yml:md5,c97980ac5ebd37a77768c105861ad719" + [ + "MERYL_UNIONSUM", + "meryl", + "1.4.1" + ] ], "meryl_db": [ [ @@ -23,16 +27,20 @@ "test.unionsum.meryl:md5,d41d8cd98f00b204e9800998ecf8427e" ] ], - "versions": [ - "versions.yml:md5,c97980ac5ebd37a77768c105861ad719" + "versions_meryl": [ + [ + "MERYL_UNIONSUM", + "meryl", + "1.4.1" + ] ] } ], + "timestamp": "2026-02-17T14:05:04.447542381", "meta": { - "nf-test": "0.8.4", - "nextflow": "23.10.1" - }, - "timestamp": "2024-05-22T12:40:21.306142" + "nf-test": "0.9.4", + "nextflow": "25.04.8" + } }, "sarscov2 - fastq - single_end": { "content": [ @@ -177,7 +185,11 @@ ] ], "1": [ - "versions.yml:md5,c97980ac5ebd37a77768c105861ad719" + [ + "MERYL_UNIONSUM", + "meryl", + "1.4.1" + ] ], "meryl_db": [ [ @@ -318,16 +330,20 @@ ] ] ], - "versions": [ - "versions.yml:md5,c97980ac5ebd37a77768c105861ad719" + "versions_meryl": [ + [ + "MERYL_UNIONSUM", + "meryl", + "1.4.1" + ] ] } ], + "timestamp": "2026-02-17T14:04:44.447203629", "meta": { - "nf-test": "0.8.4", - "nextflow": "23.10.1" - }, - "timestamp": "2024-03-27T10:19:25.091170112" + "nf-test": "0.9.4", + "nextflow": "25.04.8" + } }, "sarscov2 - fastq - paired_end": { "content": [ @@ -472,7 +488,11 @@ ] ], "1": [ - "versions.yml:md5,c97980ac5ebd37a77768c105861ad719" + [ + "MERYL_UNIONSUM", + "meryl", + "1.4.1" + ] ], "meryl_db": [ [ @@ -613,15 +633,19 @@ ] ] ], - "versions": [ - "versions.yml:md5,c97980ac5ebd37a77768c105861ad719" + "versions_meryl": [ + [ + "MERYL_UNIONSUM", + "meryl", + "1.4.1" + ] ] } ], + "timestamp": "2026-02-17T14:04:55.83723977", "meta": { - "nf-test": "0.9.1", - "nextflow": "24.10.0" - }, - "timestamp": "2024-11-04T12:45:34.142906416" + "nf-test": "0.9.4", + "nextflow": "25.04.8" + } } } \ No newline at end of file diff --git a/modules/nf-core/pilon/main.nf b/modules/nf-core/pilon/main.nf index 92cac75a..2a3cbe93 100644 --- a/modules/nf-core/pilon/main.nf +++ b/modules/nf-core/pilon/main.nf @@ -18,7 +18,8 @@ process PILON { tuple val(meta), path("*.change"), emit: change_record , optional : true tuple val(meta), path("*.bed") , emit: tracks_bed , optional : true tuple val(meta), path("*.wig") , emit: tracks_wig , optional : true - path "versions.yml" , emit: versions + tuple val("${task.process}"), val('pilon'), eval("pilon --version | sed 's/^.*version //; s/ .*\$//'"), emit: versions_pilon, topic: versions + when: task.ext.when == null || task.ext.when @@ -43,11 +44,6 @@ process PILON { --output ${prefix} \\ $args \\ --$pilon_mode $bam - - cat <<-END_VERSIONS > versions.yml - "${task.process}": - pilon: \$(echo \$(pilon --version) | sed 's/^.*version //; s/ .*\$//' ) - END_VERSIONS """ stub: @@ -60,11 +56,6 @@ process PILON { touch ${prefix}.change touch ${prefix}.bed touch ${prefix}.wig - - cat <<-END_VERSIONS > versions.yml - "${task.process}": - pilon: \$(echo \$(pilon --version) | sed 's/^.*version //; s/ .*\$//' ) - END_VERSIONS """ } diff --git a/modules/nf-core/pilon/meta.yml b/modules/nf-core/pilon/meta.yml index 87142587..db0e96d9 100644 --- a/modules/nf-core/pilon/meta.yml +++ b/modules/nf-core/pilon/meta.yml @@ -1,6 +1,6 @@ name: "pilon" -description: Automatically improve draft assemblies and find variation among strains, - including large event detection +description: Automatically improve draft assemblies and find variation among + strains, including large event detection keywords: - polishing - assembly @@ -13,7 +13,8 @@ tools: documentation: "https://github.com/broadinstitute/pilon/wiki/Requirements-&-Usage" tool_dev_url: "https://github.com/broadinstitute/pilon" doi: "10.1371/journal.pone.0112963" - licence: ["GPL-2.0-or-later"] + licence: + - "GPL-2.0-or-later" identifier: biotools:pilon input: - - meta: @@ -43,13 +44,17 @@ input: ontologies: [] - pilon_mode: type: string - description: Indicates the type of bam file used (frags for paired-end sequencing - of DNA fragments, such as Illumina paired-end reads of fragment size <1000bp, - jumps for paired sequencing data of larger insert size, such as Illumina mate - pair libraries, typically of insert size >1000bp, unpaired for unpaired sequencing - reads, bam will automatically classify the BAM as one of the three types above - (version 1.17 and higher). - enum: ["frags", "jumps", "unpaired", "bam"] + description: Indicates the type of bam file used (frags for paired-end + sequencing of DNA fragments, such as Illumina paired-end reads of fragment + size <1000bp, jumps for paired sequencing data of larger insert size, such + as Illumina mate pair libraries, typically of insert size >1000bp, + unpaired for unpaired sequencing reads, bam will automatically classify + the BAM as one of the three types above (version 1.17 and higher). + enum: + - "frags" + - "jumps" + - "unpaired" + - "bam" output: improved_assembly: - - meta: @@ -81,8 +86,8 @@ output: e.g. [ id:'test', single_end:false ] - "*.change": type: file - description: file containing a space-delimited record of every change made - in the assembly as instructed by the --fix option + description: file containing a space-delimited record of every change + made in the assembly as instructed by the --fix option pattern: "*.{change}" ontologies: [] tracks_bed: @@ -93,8 +98,8 @@ output: e.g. [ id:'test', single_end:false ] - "*.bed": type: file - description: files that may be viewed in genome browsers such as IGV, GenomeView, - and other applications that support these formats + description: files that may be viewed in genome browsers such as IGV, + GenomeView, and other applications that support these formats pattern: "*.{bed}" ontologies: [] tracks_wig: @@ -105,17 +110,31 @@ output: e.g. [ id:'test', single_end:false ] - "*.wig": type: file - description: files that may be viewed in genome browsers such as IGV, GenomeView, - and other applications that support these formats + description: files that may be viewed in genome browsers such as IGV, + GenomeView, and other applications that support these formats pattern: "*.{wig}" ontologies: [] + versions_pilon: + - - ${task.process}: + type: string + description: The name of the process + - pilon: + type: string + description: The name of the tool + - pilon --version | sed 's/^.*version //; s/ .*\$//': + type: eval + description: The expression to obtain the version of the tool +topics: 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 name of the process + - pilon: + type: string + description: The name of the tool + - pilon --version | sed 's/^.*version //; s/ .*\$//': + type: eval + description: The expression to obtain the version of the tool authors: - "@scorreard" maintainers: diff --git a/modules/nf-core/pilon/tests/main.nf.test.snap b/modules/nf-core/pilon/tests/main.nf.test.snap index 91208228..d8a284f1 100644 --- a/modules/nf-core/pilon/tests/main.nf.test.snap +++ b/modules/nf-core/pilon/tests/main.nf.test.snap @@ -48,7 +48,11 @@ ] ], "5": [ - "versions.yml:md5,73f60b48e5c3838296b66520b61a551a" + [ + "PILON", + "pilon", + "1.24" + ] ], "change_record": [ [ @@ -95,16 +99,20 @@ "test.vcf:md5,d41d8cd98f00b204e9800998ecf8427e" ] ], - "versions": [ - "versions.yml:md5,73f60b48e5c3838296b66520b61a551a" + "versions_pilon": [ + [ + "PILON", + "pilon", + "1.24" + ] ] } ], + "timestamp": "2026-02-16T15:08:00.664784809", "meta": { - "nf-test": "0.8.4", - "nextflow": "24.04.2" - }, - "timestamp": "2024-06-27T13:18:02.740029689" + "nf-test": "0.9.4", + "nextflow": "26.01.1" + } }, "homo sapiens bam": { "content": [ @@ -131,7 +139,11 @@ ], "5": [ - "versions.yml:md5,73f60b48e5c3838296b66520b61a551a" + [ + "PILON", + "pilon", + "1.24" + ] ], "change_record": [ @@ -154,16 +166,20 @@ "vcf": [ ], - "versions": [ - "versions.yml:md5,73f60b48e5c3838296b66520b61a551a" + "versions_pilon": [ + [ + "PILON", + "pilon", + "1.24" + ] ] } ], + "timestamp": "2026-02-16T15:07:51.999859591", "meta": { - "nf-test": "0.8.4", - "nextflow": "24.04.2" - }, - "timestamp": "2024-06-14T11:20:04.988498289" + "nf-test": "0.9.4", + "nextflow": "26.01.1" + } }, "homo sapiens frags": { "content": [ @@ -190,7 +206,11 @@ ], "5": [ - "versions.yml:md5,73f60b48e5c3838296b66520b61a551a" + [ + "PILON", + "pilon", + "1.24" + ] ], "change_record": [ @@ -213,16 +233,20 @@ "vcf": [ ], - "versions": [ - "versions.yml:md5,73f60b48e5c3838296b66520b61a551a" + "versions_pilon": [ + [ + "PILON", + "pilon", + "1.24" + ] ] } ], + "timestamp": "2026-02-16T15:10:08.79891187", "meta": { - "nf-test": "0.8.4", - "nextflow": "24.04.2" - }, - "timestamp": "2024-06-14T11:29:53.995788701" + "nf-test": "0.9.4", + "nextflow": "26.01.1" + } }, "homo sapiens frags - stub": { "content": [ @@ -273,7 +297,11 @@ ] ], "5": [ - "versions.yml:md5,73f60b48e5c3838296b66520b61a551a" + [ + "PILON", + "pilon", + "1.24" + ] ], "change_record": [ [ @@ -320,15 +348,19 @@ "test.vcf:md5,d41d8cd98f00b204e9800998ecf8427e" ] ], - "versions": [ - "versions.yml:md5,73f60b48e5c3838296b66520b61a551a" + "versions_pilon": [ + [ + "PILON", + "pilon", + "1.24" + ] ] } ], + "timestamp": "2026-02-16T15:08:13.764321711", "meta": { - "nf-test": "0.8.4", - "nextflow": "24.04.2" - }, - "timestamp": "2024-06-27T13:18:31.732478412" + "nf-test": "0.9.4", + "nextflow": "26.01.1" + } } } \ No newline at end of file diff --git a/modules/nf-core/ragtag/patch/main.nf b/modules/nf-core/ragtag/patch/main.nf index e6e4712b..cfd8e35a 100644 --- a/modules/nf-core/ragtag/patch/main.nf +++ b/modules/nf-core/ragtag/patch/main.nf @@ -23,7 +23,8 @@ process RAGTAG_PATCH { tuple val(meta), path("*.rename.agp"), emit: qry_rename_agp, optional: true tuple val(meta), path("*.rename.fasta"), emit: qry_rename_fasta, optional: true tuple val(meta), path("*.patch.err"), emit: stderr - path "versions.yml", emit: versions + tuple val("${task.process}"), val('ragtag'), eval("ragtag.py -v | sed 's/v//'"), emit: versions_ragtag, topic: versions + when: task.ext.when == null || task.ext.when @@ -79,11 +80,6 @@ process RAGTAG_PATCH { do mv "\$alignment_file" "\${alignment_file/${prefix}\\//${prefix}_}" done - - cat <<-END_VERSIONS > versions.yml - "${task.process}": - ragtag: \$(echo \$(ragtag.py -v | sed 's/v//')) - END_VERSIONS """ stub: @@ -101,9 +97,5 @@ process RAGTAG_PATCH { touch ${prefix}.rename.fasta touch ${prefix}.ragtag.patch.asm.1 touch ${prefix}.patch.err - - cat <<-END_VERSIONS > versions.yml - ragtag: \$(echo \$(ragtag.py -v | sed 's/v//')) - END_VERSIONS """ } diff --git a/modules/nf-core/ragtag/patch/meta.yml b/modules/nf-core/ragtag/patch/meta.yml index 9cad9c91..30976dba 100644 --- a/modules/nf-core/ragtag/patch/meta.yml +++ b/modules/nf-core/ragtag/patch/meta.yml @@ -1,7 +1,6 @@ name: "ragtag_patch" description: "Homology-based assembly patching: Make continuous joins and fill gaps in 'target.fa' using sequences from 'query.fa'" - keywords: - assembly - consensus @@ -14,7 +13,8 @@ tools: documentation: "https://github.com/malonge/RagTag/wiki" tool_dev_url: "https://github.com/malonge/RagTag" doi: "10.1186/s13059-022-02823-7" - licence: ["MIT"] + licence: + - "MIT" identifier: biotools:ragtag input: - - meta: @@ -88,8 +88,9 @@ output: e.g. [ id:'test' ] - "*.comps.fasta": type: file - description: The split target assembly and the renamed query assembly combined - into one FASTA file. This file contains all components in ragtag.patch.agp + description: The split target assembly and the renamed query assembly + combined into one FASTA file. This file contains all components in + ragtag.patch.agp pattern: "*.comps.fasta" ontologies: [] assembly_alignments: @@ -111,7 +112,8 @@ output: e.g. [ id:'test' ] - "*.ctg.agp": type: file - description: An AGP file defining how the target assembly was split at gaps + description: An AGP file defining how the target assembly was split at + gaps pattern: "*.ctg.agp" ontologies: [] target_splits_fasta: @@ -144,7 +146,8 @@ output: e.g. [ id:'test' ] - "*.rename.fasta": type: file - description: A FASTA file with the original query sequence, but with new names + description: A FASTA file with the original query sequence, but with new + names pattern: "*.rename.fasta" ontologies: [] stderr: @@ -158,13 +161,27 @@ output: description: Standard error logging for all external RagTag commands pattern: "*.patch.err" ontologies: [] + versions_ragtag: + - - ${task.process}: + type: string + description: The name of the process + - ragtag: + type: string + description: The name of the tool + - ragtag.py -v | sed 's/v//': + type: eval + description: The expression to obtain the version of the tool +topics: 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 name of the process + - ragtag: + type: string + description: The name of the tool + - ragtag.py -v | sed 's/v//': + type: eval + description: The expression to obtain the version of the tool authors: - "@nschan" maintainers: diff --git a/modules/nf-core/ragtag/patch/tests/main.nf.test b/modules/nf-core/ragtag/patch/tests/main.nf.test index a7c0fee8..e532a95d 100644 --- a/modules/nf-core/ragtag/patch/tests/main.nf.test +++ b/modules/nf-core/ragtag/patch/tests/main.nf.test @@ -44,7 +44,7 @@ test("A. thaliana Col-0 test data - ragtag - patch") { process.out.patch_components_fasta, process.out.target_splits_agp, process.out.target_splits_fasta, - process.out.versions + process.out.findAll { key, val -> key.startsWith('versions') } ).match() }, ) diff --git a/modules/nf-core/ragtag/patch/tests/main.nf.test.snap b/modules/nf-core/ragtag/patch/tests/main.nf.test.snap index b1444692..db994efe 100644 --- a/modules/nf-core/ragtag/patch/tests/main.nf.test.snap +++ b/modules/nf-core/ragtag/patch/tests/main.nf.test.snap @@ -75,7 +75,11 @@ ] ], "9": [ - "versions.yml:md5,cecbb39907d607affa6522e395b78a1f" + [ + "RAGTAG_PATCH", + "ragtag", + "2.1.0" + ] ], "assembly_alignments": [ [ @@ -149,16 +153,20 @@ "test.ctg.fasta:md5,d41d8cd98f00b204e9800998ecf8427e" ] ], - "versions": [ - "versions.yml:md5,cecbb39907d607affa6522e395b78a1f" + "versions_ragtag": [ + [ + "RAGTAG_PATCH", + "ragtag", + "2.1.0" + ] ] } ], + "timestamp": "2026-02-16T15:14:07.370603076", "meta": { - "nf-test": "0.9.2", - "nextflow": "24.10.3" - }, - "timestamp": "2025-04-04T14:10:01.648597527" + "nf-test": "0.9.4", + "nextflow": "25.04.8" + } }, "A. thaliana Col-0 test data - ragtag - patch": { "content": [ @@ -202,14 +210,20 @@ "test.ctg.fasta:md5,5cf615df690061ab15e4fee62abf3ebc" ] ], - [ - "versions.yml:md5,4c0992a27edf294209711ce4f181eb5a" - ] + { + "versions_ragtag": [ + [ + "RAGTAG_PATCH", + "ragtag", + "2.1.0" + ] + ] + } ], + "timestamp": "2026-02-16T15:55:07.632218179", "meta": { - "nf-test": "0.9.2", - "nextflow": "24.10.3" - }, - "timestamp": "2025-04-04T14:25:42.121285998" + "nf-test": "0.9.4", + "nextflow": "25.04.8" + } } } \ No newline at end of file diff --git a/modules/nf-core/ragtag/scaffold/main.nf b/modules/nf-core/ragtag/scaffold/main.nf index b258a0aa..e96bfc05 100644 --- a/modules/nf-core/ragtag/scaffold/main.nf +++ b/modules/nf-core/ragtag/scaffold/main.nf @@ -17,7 +17,7 @@ process RAGTAG_SCAFFOLD { tuple val(meta), path("*.fasta"), emit: corrected_assembly tuple val(meta), path("*.agp"), emit: corrected_agp tuple val(meta), path("*.stats"), emit: corrected_stats - path "versions.yml", emit: versions + tuple val("${task.process}"), val('ragtag'), eval("ragtag.py -v | sed 's/v//'"), emit: versions_ragtag, topic: versions when: task.ext.when == null || task.ext.when @@ -57,11 +57,6 @@ process RAGTAG_SCAFFOLD { mv ${prefix}/ragtag.scaffold.fasta ${prefix}.fasta mv ${prefix}/ragtag.scaffold.agp ${prefix}.agp mv ${prefix}/ragtag.scaffold.stats ${prefix}.stats - - cat <<-END_VERSIONS > versions.yml - "${task.process}": - ragtag: \$(echo \$(ragtag.py -v | sed 's/v//')) - END_VERSIONS """ stub: @@ -74,9 +69,5 @@ process RAGTAG_SCAFFOLD { touch ${prefix}.fasta touch ${prefix}.agp touch ${prefix}.stats - - cat <<-END_VERSIONS > versions.yml - ragtag: \$(echo \$(ragtag.py -v | sed 's/v//')) - END_VERSIONS """ } diff --git a/modules/nf-core/ragtag/scaffold/meta.yml b/modules/nf-core/ragtag/scaffold/meta.yml index 6b5d55a8..5f3127fc 100644 --- a/modules/nf-core/ragtag/scaffold/meta.yml +++ b/modules/nf-core/ragtag/scaffold/meta.yml @@ -1,4 +1,3 @@ -# yaml-language-server: $schema=https://raw.githubusercontent.com/nf-core/modules/master/modules/meta-schema.json name: ragtag_scaffold description: | Scaffolding is the process of ordering and orienting draft assembly (query) @@ -18,9 +17,9 @@ tools: documentation: "https://github.com/malonge/RagTag/wiki" tool_dev_url: "https://github.com/malonge/RagTag" doi: "10.1186/s13059-022-02823-7" - licence: ["MIT"] + licence: + - "MIT" identifier: biotools:ragtag - input: - - meta: type: map @@ -64,8 +63,8 @@ input: ontologies: [] - hard_skip: type: file - description: list of query headers to leave unplaced and exclude from 'chr0' - ('-C') + description: list of query headers to leave unplaced and exclude from + 'chr0' ('-C') pattern: "*.txt" ontologies: [] output: @@ -101,15 +100,28 @@ output: type: file description: Statistics on the scaffold pattern: "*.stats" - ontologies: [] + versions_ragtag: + - - ${task.process}: + type: string + description: The name of the process + - ragtag: + type: string + description: The name of the tool + - ragtag.py -v | sed 's/v//': + type: eval + description: The expression to obtain the version of the tool +topics: 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 name of the process + - ragtag: + type: string + description: The name of the tool + - ragtag.py -v | sed 's/v//': + type: eval + description: The expression to obtain the version of the tool authors: - "@nschan" maintainers: diff --git a/modules/nf-core/ragtag/scaffold/tests/main.nf.test.snap b/modules/nf-core/ragtag/scaffold/tests/main.nf.test.snap index e4faf0b0..cb27f70a 100644 --- a/modules/nf-core/ragtag/scaffold/tests/main.nf.test.snap +++ b/modules/nf-core/ragtag/scaffold/tests/main.nf.test.snap @@ -27,7 +27,11 @@ ] ], "3": [ - "versions.yml:md5,48710c1720f668d8ba3397f99892959e" + [ + "RAGTAG_SCAFFOLD", + "ragtag", + "2.1.0" + ] ], "corrected_agp": [ [ @@ -53,16 +57,20 @@ "test.stats:md5,209e973e4bac1653b8d5fddb7fa13b63" ] ], - "versions": [ - "versions.yml:md5,48710c1720f668d8ba3397f99892959e" + "versions_ragtag": [ + [ + "RAGTAG_SCAFFOLD", + "ragtag", + "2.1.0" + ] ] } ], + "timestamp": "2026-02-16T15:16:15.735574744", "meta": { - "nf-test": "0.9.2", - "nextflow": "24.10.3" - }, - "timestamp": "2025-04-04T13:37:54.181644032" + "nf-test": "0.9.4", + "nextflow": "26.01.1" + } }, "A. thaliana Col-0 test data - ragtag - scaffold - stub": { "content": [ @@ -92,7 +100,11 @@ ] ], "3": [ - "versions.yml:md5,cecbb39907d607affa6522e395b78a1f" + [ + "RAGTAG_SCAFFOLD", + "ragtag", + "2.1.0" + ] ], "corrected_agp": [ [ @@ -118,15 +130,19 @@ "test.stats:md5,d41d8cd98f00b204e9800998ecf8427e" ] ], - "versions": [ - "versions.yml:md5,cecbb39907d607affa6522e395b78a1f" + "versions_ragtag": [ + [ + "RAGTAG_SCAFFOLD", + "ragtag", + "2.1.0" + ] ] } ], + "timestamp": "2026-02-16T15:16:24.915889235", "meta": { - "nf-test": "0.9.2", - "nextflow": "24.10.3" - }, - "timestamp": "2025-04-04T13:38:21.635495713" + "nf-test": "0.9.4", + "nextflow": "26.01.1" + } } } \ No newline at end of file From 22cbd74761f77eda781d824cf42e33849bc97b10 Mon Sep 17 00:00:00 2001 From: Niklas Schandry Date: Wed, 18 Feb 2026 11:41:59 +0100 Subject: [PATCH 136/162] clean up versions, fix busco include --- subworkflows/local/polishing/main.nf | 3 --- subworkflows/local/qc/main.nf | 2 -- 2 files changed, 5 deletions(-) diff --git a/subworkflows/local/polishing/main.nf b/subworkflows/local/polishing/main.nf index 01c1e662..ffef8ed2 100644 --- a/subworkflows/local/polishing/main.nf +++ b/subworkflows/local/polishing/main.nf @@ -8,8 +8,6 @@ workflow POLISH { meryl_kmers main: - - channel.empty().set { ch_versions } channel.empty().set { polish_busco_reports } channel.empty().set { polish_quast_reports } channel.empty().set { polish_merqury_reports } @@ -96,5 +94,4 @@ workflow POLISH { polish_busco_reports polish_quast_reports polish_merqury_reports - versions } diff --git a/subworkflows/local/qc/main.nf b/subworkflows/local/qc/main.nf index 2a024239..11226cf1 100644 --- a/subworkflows/local/qc/main.nf +++ b/subworkflows/local/qc/main.nf @@ -1,5 +1,4 @@ include { MAP_TO_ASSEMBLY } from '../mapping/map_to_assembly/main' -include { RUN_BUSCO } from './busco/main.nf' include { QUAST } from '../../../modules/local/quast/main' include { BUSCO_BUSCO as BUSCO } from '../../../modules/nf-core/busco/busco/main' include { MERQURY_MERQURY as MERQURY } from '../../../modules/nf-core/merqury/merqury/main' @@ -11,7 +10,6 @@ workflow QC { meryl_kmers main: - channel.empty().set { ch_versions } channel.empty().set { quast_out } channel.empty().set { busco_out } channel.empty().set { merqury_report_files } From 5cc633b2cd0d208af0d4c18d4b373d342d49b878 Mon Sep 17 00:00:00 2001 From: Niklas Schandry Date: Wed, 18 Feb 2026 13:54:25 +0100 Subject: [PATCH 137/162] finish transition to versions topic, new test snapshot --- conf/modules.config | 3 +- conf/modules/QC/alignments.config | 48 +++++++------------ conf/modules/QC/busco.config | 21 +++----- conf/modules/QC/jellyfish-genomescope.config | 15 ++---- conf/modules/QC/merqury.config | 21 +++----- conf/modules/QC/meryl.config | 6 +-- conf/modules/QC/quast.config | 21 +++----- conf/modules/assembly.config | 21 +++----- conf/modules/fastp.config | 6 +-- conf/modules/hifi-prep.config | 6 +-- conf/modules/liftoff.config | 18 +++---- conf/modules/ont-prep.config | 6 +-- conf/modules/polishing.config | 18 +++---- conf/modules/report.config | 3 +- conf/modules/scaffolding.config | 30 ++++-------- modules/local/gfa2fa/main.nf | 2 - modules/local/longstitch/main.nf | 11 +---- modules/local/report/main.nf | 4 +- subworkflows/local/prepare/jellyfish/main.nf | 10 ---- .../local/prepare/prepare_ont/collect/main.nf | 6 --- .../local/prepare/prepare_shortreads/main.nf | 3 -- subworkflows/local/qc/main.nf | 3 +- subworkflows/local/scaffolding/hic/main.nf | 6 --- tests/default.nf.test.snap | 28 +++++------ workflows/genomeassembler.nf | 15 +----- 25 files changed, 101 insertions(+), 230 deletions(-) diff --git a/conf/modules.config b/conf/modules.config index e94bbf69..c84cc2a2 100644 --- a/conf/modules.config +++ b/conf/modules.config @@ -14,8 +14,7 @@ process { // General catch-all publishDir = [ path: { "${params.outdir}/${meta.id}/${task.process.tokenize(':')[-1].tokenize('_')[0].toLowerCase()}" }, - mode: params.publish_dir_mode, - saveAs: { filename -> filename.equals('versions.yml') ? null : filename } + mode: params.publish_dir_mode ] } diff --git a/conf/modules/QC/alignments.config b/conf/modules/QC/alignments.config index f324af4c..b4c5ae9a 100644 --- a/conf/modules/QC/alignments.config +++ b/conf/modules/QC/alignments.config @@ -4,16 +4,14 @@ process { ext.prefix = { "${meta.id}_to_reference" } publishDir = [ path: { "${params.outdir}/${meta.id}/QC/alignments/reference/" }, - mode: params.publish_dir_mode, - saveAs: { filename -> filename.equals('versions.yml') ? null : filename } + mode: params.publish_dir_mode ] } withName: '.*MAP_TO_REF.*:ALIGN' { ext.prefix = { "${meta.id}_to_reference" } publishDir = [ path: { "${params.outdir}/${meta.id}/QC/alignments/reference/" }, - mode: params.publish_dir_mode, - saveAs: { filename -> filename.equals('versions.yml') ? null : filename } + mode: params.publish_dir_mode ] ext.args = { (meta.qc_reads == 'ont' ? "-ax lr:hq" : "-ax map-hifi") @@ -24,16 +22,14 @@ process { ext.prefix = { "${meta.id}_assembly" } publishDir = [ path: { "${params.outdir}/${meta.id}/QC/alignments/" }, - mode: params.publish_dir_mode, - saveAs: { filename -> filename.equals('versions.yml') ? null : filename } + mode: params.publish_dir_mode ] } withName: '.*ASSEMBLE:.*MAP_TO_ASSEMBLY.*:ALIGN' { ext.prefix = { "${meta.id}_assembly" } publishDir = [ path: { "${params.outdir}/${meta.id}/QC/alignments/" }, - mode: params.publish_dir_mode, - saveAs: { filename -> filename.equals('versions.yml') ? null : filename } + mode: params.publish_dir_mode ] ext.args = { (meta.qc_reads == 'ont' ? "-ax lr:hq" : "-ax map-hifi") @@ -43,16 +39,14 @@ process { ext.prefix = { "${meta.id}_medaka" } publishDir = [ path: { "${params.outdir}/${meta.id}/QC/alignments/" }, - mode: params.publish_dir_mode, - saveAs: { filename -> filename.equals('versions.yml') ? null : filename } + mode: params.publish_dir_mode ] } withName: '.*MEDAKA:.*MAP_TO_ASSEMBLY.*:ALIGN' { ext.prefix = { "${meta.id}_medaka" } publishDir = [ path: { "${params.outdir}/${meta.id}/QC/alignments/" }, - mode: params.publish_dir_mode, - saveAs: { filename -> filename.equals('versions.yml') ? null : filename } + mode: params.publish_dir_mode ] ext.args = { (meta.qc_reads == 'ont' ? "-ax lr:hq" : "-ax map-hifi") @@ -62,16 +56,14 @@ process { ext.prefix = { "${meta.id}_pilon" } publishDir = [ path: { "${params.outdir}/${meta.id}/QC/alignments/" }, - mode: params.publish_dir_mode, - saveAs: { filename -> filename.equals('versions.yml') ? null : filename } + mode: params.publish_dir_mode ] } withName: '.*PILON:.*MAP_TO_ASSEMBLY.*:ALIGN' { ext.prefix = { "${meta.id}_pilon" } publishDir = [ path: { "${params.outdir}/${meta.id}/QC/alignments/" }, - mode: params.publish_dir_mode, - saveAs: { filename -> filename.equals('versions.yml') ? null : filename } + mode: params.publish_dir_mode ] ext.args = { (meta.qc_reads == 'ont' ? "-ax lr:hq" : "-ax map-hifi") @@ -81,16 +73,14 @@ process { ext.prefix = { "${meta.id}_longstitch" } publishDir = [ path: { "${params.outdir}/${meta.id}/QC/alignments/" }, - mode: params.publish_dir_mode, - saveAs: { filename -> filename.equals('versions.yml') ? null : filename } + mode: params.publish_dir_mode ] } withName: '.*LONGSTITCH:.*MAP_TO_ASSEMBLY.*:ALIGN' { ext.prefix = { "${meta.id}_longstitch" } publishDir = [ path: { "${params.outdir}/${meta.id}/QC/alignments/" }, - mode: params.publish_dir_mode, - saveAs: { filename -> filename.equals('versions.yml') ? null : filename } + mode: params.publish_dir_mode ] ext.args = { (meta.qc_reads == 'ont' ? "-ax lr:hq" : "-ax map-hifi") @@ -100,16 +90,14 @@ process { ext.prefix = { "${meta.id}_links" } publishDir = [ path: { "${params.outdir}/${meta.id}/QC/alignments/" }, - mode: params.publish_dir_mode, - saveAs: { filename -> filename.equals('versions.yml') ? null : filename } + mode: params.publish_dir_mode ] } withName: '.*LINKS:.*MAP_TO_ASSEMBLY.*:ALIGN' { ext.prefix = { "${meta.id}_links" } publishDir = [ path: { "${params.outdir}/${meta.id}/QC/alignments/" }, - mode: params.publish_dir_mode, - saveAs: { filename -> filename.equals('versions.yml') ? null : filename } + mode: params.publish_dir_mode ] ext.args = { (meta.qc_reads == 'ont' ? "-ax lr:hq" : "-ax map-hifi") @@ -119,16 +107,14 @@ process { ext.prefix = { "${meta.id}_hic" } publishDir = [ path: { "${params.outdir}/${meta.id}/QC/alignments/" }, - mode: params.publish_dir_mode, - saveAs: { filename -> filename.equals('versions.yml') ? null : filename } + mode: params.publish_dir_mode ] } withName: '.*HIC:.*MAP_TO_ASSEMBLY.*:ALIGN' { ext.prefix = { "${meta.id}_hic" } publishDir = [ path: { "${params.outdir}/${meta.id}/QC/alignments/" }, - mode: params.publish_dir_mode, - saveAs: { filename -> filename.equals('versions.yml') ? null : filename } + mode: params.publish_dir_mode ] ext.args = { (meta.qc_reads == 'ont' ? "-ax lr:hq" : "-ax map-hifi") @@ -138,16 +124,14 @@ process { ext.prefix = { "${meta.id}_ragtag" } publishDir = [ path: { "${params.outdir}/${meta.id}/QC/alignments/" }, - mode: params.publish_dir_mode, - saveAs: { filename -> filename.equals('versions.yml') ? null : filename } + mode: params.publish_dir_mode ] } withName: '.*RAGTAG:.*MAP_TO_ASSEMBLY.*:ALIGN' { ext.prefix = { "${meta.id}_ragtag" } publishDir = [ path: { "${params.outdir}/${meta.id}/QC/alignments/" }, - mode: params.publish_dir_mode, - saveAs: { filename -> filename.equals('versions.yml') ? null : filename } + mode: params.publish_dir_mode ] ext.args = { (meta.qc_reads == 'ont' ? "-ax lr:hq" : "-ax map-hifi") diff --git a/conf/modules/QC/busco.config b/conf/modules/QC/busco.config index aa6bb06d..f13505c5 100644 --- a/conf/modules/QC/busco.config +++ b/conf/modules/QC/busco.config @@ -4,8 +4,7 @@ process { publishDir = [ path: { "${params.outdir}/${meta.id}/QC/BUSCO/" }, mode: params.publish_dir_mode, - pattern: "*{-busco,_summary}*", - saveAs: { filename -> filename.equals('versions.yml') ? null : filename } + pattern: "*{-busco,_summary}*" ] } withName: '.*PILON:.*:BUSCO' { @@ -13,8 +12,7 @@ process { publishDir = [ path: { "${params.outdir}/${meta.id}/QC/BUSCO/" }, mode: params.publish_dir_mode, - pattern: "*{-busco,_summary}*", - saveAs: { filename -> filename.equals('versions.yml') ? null : filename } + pattern: "*{-busco,_summary}*" ] } withName: '.*MEDAKA:.*:BUSCO' { @@ -22,8 +20,7 @@ process { publishDir = [ path: { "${params.outdir}/${meta.id}/QC/BUSCO/" }, mode: params.publish_dir_mode, - pattern: "*{-busco,_summary}*", - saveAs: { filename -> filename.equals('versions.yml') ? null : filename } + pattern: "*{-busco,_summary}*" ] } withName: '.*LINKS:.*:BUSCO' { @@ -31,8 +28,7 @@ process { publishDir = [ path: { "${params.outdir}/${meta.id}/QC/BUSCO/" }, mode: params.publish_dir_mode, - pattern: "*{-busco,_summary}*", - saveAs: { filename -> filename.equals('versions.yml') ? null : filename } + pattern: "*{-busco,_summary}*" ] } withName: '.*LONGSTITCH:.*:BUSCO' { @@ -40,8 +36,7 @@ process { publishDir = [ path: { "${params.outdir}/${meta.id}/QC/BUSCO/" }, mode: params.publish_dir_mode, - pattern: "*{-busco,_summary}*", - saveAs: { filename -> filename.equals('versions.yml') ? null : filename } + pattern: "*{-busco,_summary}*" ] } // avoid catching ragtag from ont_on_hifi assembly @@ -50,8 +45,7 @@ process { publishDir = [ path: { "${params.outdir}/${meta.id}/QC/BUSCO/" }, mode: params.publish_dir_mode, - pattern: "*{-busco,_summary}*", - saveAs: { filename -> filename.equals('versions.yml') ? null : filename } + pattern: "*{-busco,_summary}*" ] } withName: '.*:SCAFFOLD:.*HIC:QC.*:BUSCO' { @@ -59,8 +53,7 @@ process { publishDir = [ path: { "${params.outdir}/${meta.id}/QC/BUSCO/" }, mode: params.publish_dir_mode, - pattern: "*{-busco,_summary}*", - saveAs: { filename -> filename.equals('versions.yml') ? null : filename } + pattern: "*{-busco,_summary}*" ] } } diff --git a/conf/modules/QC/jellyfish-genomescope.config b/conf/modules/QC/jellyfish-genomescope.config index 18f70cd9..61ccb062 100644 --- a/conf/modules/QC/jellyfish-genomescope.config +++ b/conf/modules/QC/jellyfish-genomescope.config @@ -2,36 +2,31 @@ process { withName: COUNT { publishDir = [ path: { "${params.outdir}/${meta.id}/reads/genomescope/jellyfish/count/" }, - mode: params.publish_dir_mode, - saveAs: { filename -> filename.equals('versions.yml') ? null : filename } + mode: params.publish_dir_mode ] } withName: DUMP { publishDir = [ path: { "${params.outdir}/${meta.id}/reads/genomescope/jellyfish/dump/" }, - mode: params.publish_dir_mode, - saveAs: { filename -> filename.equals('versions.yml') ? null : filename } + mode: params.publish_dir_mode ] } withName: STATS { publishDir = [ path: { "${params.outdir}/${meta.id}/reads/genomescope/jellyfish/stats/" }, - mode: params.publish_dir_mode, - saveAs: { filename -> filename.equals('versions.yml') ? null : filename } + mode: params.publish_dir_mode ] } withName: HISTO { publishDir = [ path: { "${params.outdir}/${meta.id}/reads/genomescope/jellyfish/histo/" }, - mode: params.publish_dir_mode, - saveAs: { filename -> filename.equals('versions.yml') ? null : filename } + mode: params.publish_dir_mode ] } withName: GENOMESCOPE { publishDir = [ path: { "${params.outdir}/${meta.id}/reads/genomescope/genomescope/" }, - mode: params.publish_dir_mode, - saveAs: { filename -> filename.equals('versions.yml') ? null : filename } + mode: params.publish_dir_mode ] } } diff --git a/conf/modules/QC/merqury.config b/conf/modules/QC/merqury.config index d4858230..1d09663c 100644 --- a/conf/modules/QC/merqury.config +++ b/conf/modules/QC/merqury.config @@ -3,48 +3,42 @@ process { ext.prefix = { "${meta.id}_assembly" } publishDir = [ path: { "${params.outdir}/${meta.id}/QC/merqury/" }, - mode: params.publish_dir_mode, - saveAs: { filename -> filename.equals('versions.yml') ? null : filename } + mode: params.publish_dir_mode ] } withName: '.*PILON:.*:MERQURY' { ext.prefix = { "${meta.id}_pilon" } publishDir = [ path: { "${params.outdir}/${meta.id}/QC/merqury/" }, - mode: params.publish_dir_mode, - saveAs: { filename -> filename.equals('versions.yml') ? null : filename } + mode: params.publish_dir_mode ] } withName: '.*MEDAKA:.*:MERQURY' { ext.prefix = { "${meta.id}_medaka" } publishDir = [ path: { "${params.outdir}/${meta.id}/QC/merqury/" }, - mode: params.publish_dir_mode, - saveAs: { filename -> filename.equals('versions.yml') ? null : filename } + mode: params.publish_dir_mode ] } withName: '.*LINKS:.*:MERQURY' { ext.prefix = { "${meta.id}_links" } publishDir = [ path: { "${params.outdir}/${meta.id}/QC/merqury/" }, - mode: params.publish_dir_mode, - saveAs: { filename -> filename.equals('versions.yml') ? null : filename } + mode: params.publish_dir_mode ] } withName: '.*LONGSTITCH:.*:MERQURY' { ext.prefix = { "${meta.id}_longstitch" } publishDir = [ path: { "${params.outdir}/${meta.id}/QC/merqury/" }, - mode: params.publish_dir_mode, - saveAs: { filename -> filename.equals('versions.yml') ? null : filename } + mode: params.publish_dir_mode ] } withName: '.*:SCAFFOLD:HIC:.*:MERQURY' { ext.prefix = { "${meta.id}_yahs" } publishDir = [ path: { "${params.outdir}/${meta.id}/QC/merqury/" }, - mode: params.publish_dir_mode, - saveAs: { filename -> filename.equals('versions.yml') ? null : filename } + mode: params.publish_dir_mode ] } // avoid catching ragtag from ont_on_hifi assembly @@ -52,8 +46,7 @@ process { ext.prefix = { "${meta.id}_ragtag" } publishDir = [ path: { "${params.outdir}/${meta.id}/QC/merqury/" }, - mode: params.publish_dir_mode, - saveAs: { filename -> filename.equals('versions.yml') ? null : filename } + mode: params.publish_dir_mode ] } } diff --git a/conf/modules/QC/meryl.config b/conf/modules/QC/meryl.config index 41452a69..322e71cb 100644 --- a/conf/modules/QC/meryl.config +++ b/conf/modules/QC/meryl.config @@ -2,15 +2,13 @@ process { withName: MERYL_COUNT { publishDir = [ path: { "${params.outdir}/${meta.id}/reads/meryl/count/" }, - mode: params.publish_dir_mode, - saveAs: { filename -> filename.equals('versions.yml') ? null : filename } + mode: params.publish_dir_mode ] } withName: MERYL_UNIONSUM { publishDir = [ path: { "${params.outdir}/${meta.id}/reads/meryl/unionsum/" }, - mode: params.publish_dir_mode, - saveAs: { filename -> filename.equals('versions.yml') ? null : filename } + mode: params.publish_dir_mode ] } } diff --git a/conf/modules/QC/quast.config b/conf/modules/QC/quast.config index 657f56f6..63523f6d 100644 --- a/conf/modules/QC/quast.config +++ b/conf/modules/QC/quast.config @@ -3,48 +3,42 @@ process { ext.prefix = { "${meta.id}_assembly" } publishDir = [ path: { "${params.outdir}/${meta.id}/QC/QUAST" }, - mode: params.publish_dir_mode, - saveAs: { filename -> filename.equals('versions.yml') ? null : filename } + mode: params.publish_dir_mode ] } withName: '.*PILON:.*:QUAST' { ext.prefix = { "${meta.id}_pilon" } publishDir = [ path: { "${params.outdir}/${meta.id}/QC/QUAST" }, - mode: params.publish_dir_mode, - saveAs: { filename -> filename.equals('versions.yml') ? null : filename } + mode: params.publish_dir_mode ] } withName: '.*MEDAKA:.*:QUAST' { ext.prefix = { "${meta.id}_medaka" } publishDir = [ path: { "${params.outdir}/${meta.id}/QC/QUAST" }, - mode: params.publish_dir_mode, - saveAs: { filename -> filename.equals('versions.yml') ? null : filename } + mode: params.publish_dir_mode ] } withName: '.*LINKS:.*:QUAST' { ext.prefix = { "${meta.id}_links" } publishDir = [ path: { "${params.outdir}/${meta.id}/QC/QUAST/" }, - mode: params.publish_dir_mode, - saveAs: { filename -> filename.equals('versions.yml') ? null : filename } + mode: params.publish_dir_mode ] } withName: '.*LONGSTITCH:.*:QUAST' { ext.prefix = { "${meta.id}_longstitch" } publishDir = [ path: { "${params.outdir}/${meta.id}/QC/QUAST/" }, - mode: params.publish_dir_mode, - saveAs: { filename -> filename.equals('versions.yml') ? null : filename } + mode: params.publish_dir_mode ] } withName: '.*SCAFFOLD:HIC.*:QUAST' { ext.prefix = { "${meta.id}_yahs" } publishDir = [ path: { "${params.outdir}/${meta.id}/QC/QUAST/" }, - mode: params.publish_dir_mode, - saveAs: { filename -> filename.equals('versions.yml') ? null : filename } + mode: params.publish_dir_mode ] } // avoid catching ragtag from ont_on_hifi assembly @@ -52,8 +46,7 @@ process { ext.prefix = { "${meta.id}_ragtag" } publishDir = [ path: { "${params.outdir}/${meta.id}/QC/QUAST/" }, - mode: params.publish_dir_mode, - saveAs: { filename -> filename.equals('versions.yml') ? null : filename } + mode: params.publish_dir_mode ] } } diff --git a/conf/modules/assembly.config b/conf/modules/assembly.config index b5530fb5..b12a4c96 100644 --- a/conf/modules/assembly.config +++ b/conf/modules/assembly.config @@ -13,52 +13,45 @@ process { } publishDir = [ path: { "${params.outdir}/${meta.id}/assembly/flye/" }, - mode: params.publish_dir_mode, - saveAs: { filename -> filename.equals('versions.yml') ? null : filename } + mode: params.publish_dir_mode ] } withName: HIFIASM { ext.args = { [ meta.assembler_hifi_args ].join(" ").trim() } publishDir = [ path: { "${params.outdir}/${meta.id}/assembly/hifiasm/" }, - mode: params.publish_dir_mode, - saveAs: { filename -> filename.equals('versions.yml') ? null : filename } + mode: params.publish_dir_mode ] } withName: HIFIASM_ONT { ext.args = { [ meta.assembler_ont_args, "--ont" ].join(" ").trim() } publishDir = [ path: { "${params.outdir}/${meta.id}/assembly/hifiasm_ont/" }, - mode: params.publish_dir_mode, - saveAs: { filename -> filename.equals('versions.yml') ? null : filename } + mode: params.publish_dir_mode ] } withName: GFA_2_FA { publishDir = [ path: { "${params.outdir}/${meta.id}/assembly/hifiasm/fasta" }, - mode: params.publish_dir_mode, - saveAs: { filename -> filename.equals('versions.yml') ? null : filename } + mode: params.publish_dir_mode ] } withName: GFA_2_FA_HIFI { publishDir = [ path: { "${params.outdir}/${meta.id}/assembly/hifiasm/fasta" }, - mode: params.publish_dir_mode, - saveAs: { filename -> filename.equals('versions.yml') ? null : filename } + mode: params.publish_dir_mode ] } withName: GFA_2_FA_ONT { publishDir = [ path: { "${params.outdir}/${meta.id}/assembly/hifiasm_ont/fasta" }, - mode: params.publish_dir_mode, - saveAs: { filename -> filename.equals('versions.yml') ? null : filename } + mode: params.publish_dir_mode ] } withName: '.*ASSEMBLE:.*RAGTAG_PATCH' { publishDir = [ path: { "${params.outdir}/${meta.id}/assembly/ragtag/" }, - mode: params.publish_dir_mode, - saveAs: { filename -> filename.equals('versions.yml') ? null : filename } + mode: params.publish_dir_mode ] ext.prefix = { "${meta.id}_assembly_patch" } } diff --git a/conf/modules/fastp.config b/conf/modules/fastp.config index 5cc4d8a6..7d9f5b72 100644 --- a/conf/modules/fastp.config +++ b/conf/modules/fastp.config @@ -2,8 +2,7 @@ process { withName: FASTP { publishDir = [ path: { "${params.outdir}/${meta.id}/reads/fastp" }, - mode: params.publish_dir_mode, - saveAs: { filename -> filename.equals('versions.yml') ? null : filename } + mode: params.publish_dir_mode ] } } @@ -13,8 +12,7 @@ process { ext.prefix = {"${meta.id}_hicreads"} publishDir = [ path: { "${params.outdir}/${meta.id}/hic_reads/fastp" }, - mode: params.publish_dir_mode, - saveAs: { filename -> filename.equals('versions.yml') ? null : filename } + mode: params.publish_dir_mode ] } } diff --git a/conf/modules/hifi-prep.config b/conf/modules/hifi-prep.config index 7e0f66a8..2c61f5ab 100644 --- a/conf/modules/hifi-prep.config +++ b/conf/modules/hifi-prep.config @@ -2,8 +2,7 @@ process { withName: FASTPLONG_HIFI { publishDir = [ path: { "${params.outdir}/${meta.id}/reads/fastplong/hifi/" }, - mode: params.publish_dir_mode, - saveAs: { filename -> filename.equals('versions.yml') ? null : filename } + mode: params.publish_dir_mode ] ext.args = { [ @@ -15,8 +14,7 @@ process { withName: TO_FASTQ { publishDir = [ path: { "${params.outdir}/${meta.id}/reads/lima/fastq/" }, - mode: params.publish_dir_mode, - saveAs: { filename -> filename.equals('versions.yml') ? null : filename } + mode: params.publish_dir_mode ] } } diff --git a/conf/modules/liftoff.config b/conf/modules/liftoff.config index 3f470e90..cc86bdc0 100644 --- a/conf/modules/liftoff.config +++ b/conf/modules/liftoff.config @@ -2,48 +2,42 @@ process { withName: '.*ASSEMBLE:.*LIFTOFF' { publishDir = [ path: { "${params.outdir}/${meta.id}/assembly/" }, - mode: params.publish_dir_mode, - saveAs: { filename -> filename.equals('versions.yml') ? null : filename } + mode: params.publish_dir_mode ] ext.prefix = { "${meta.id}_assembly" } } withName: '.*PILON:.*LIFTOFF' { publishDir = [ path: { "${params.outdir}/${meta.id}/polish/pilon/" }, - mode: params.publish_dir_mode, - saveAs: { filename -> filename.equals('versions.yml') ? null : filename } + mode: params.publish_dir_mode ] ext.prefix = { "${meta.id}_pilon" } } withName: '.*MEDAKA:.*LIFTOFF' { publishDir = [ path: { "${params.outdir}/${meta.id}/polish/medaka" }, - mode: params.publish_dir_mode, - saveAs: { filename -> filename.equals('versions.yml') ? null : filename } + mode: params.publish_dir_mode ] ext.prefix = { "${meta.id}_medaka" } } withName: '.*RAGTAG:.*LIFTOFF' { publishDir = [ path: { "${params.outdir}/${meta.id}/scaffold/ragtag/" }, - mode: params.publish_dir_mode, - saveAs: { filename -> filename.equals('versions.yml') ? null : filename } + mode: params.publish_dir_mode ] ext.prefix = { "${meta.id}_ragtag" } } withName: '.*LONGSTITCH:.*LIFTOFF' { publishDir = [ path: { "${params.outdir}/${meta.id}/scaffold/longstitch" }, - mode: params.publish_dir_mode, - saveAs: { filename -> filename.equals('versions.yml') ? null : filename } + mode: params.publish_dir_mode ] ext.prefix = { "${meta.id}_longstitch" } } withName: '.*LINKS:.*LIFTOFF' { publishDir = [ path: { "${params.outdir}/${meta.id}/scaffold/links" }, - mode: params.publish_dir_mode, - saveAs: { filename -> filename.equals('versions.yml') ? null : filename } + mode: params.publish_dir_mode ] ext.prefix = { "${meta.id}_links" } } diff --git a/conf/modules/ont-prep.config b/conf/modules/ont-prep.config index 940459f6..dddbe9d0 100644 --- a/conf/modules/ont-prep.config +++ b/conf/modules/ont-prep.config @@ -2,15 +2,13 @@ process { withName: COLLECT { publishDir = [ path: { "${params.outdir}/${meta.id}/reads/collect" }, - mode: params.publish_dir_mode, - saveAs: { filename -> filename.equals('versions.yml') ? null : filename } + mode: params.publish_dir_mode ] } withName: FASTPLONG_ONT { publishDir = [ path: { "${params.outdir}/${meta.id}/reads/fastplong/ont/" }, - mode: params.publish_dir_mode, - saveAs: { filename -> filename.equals('versions.yml') ? null : filename } + mode: params.publish_dir_mode ] ext.args = { [ diff --git a/conf/modules/polishing.config b/conf/modules/polishing.config index d6685492..40234c77 100644 --- a/conf/modules/polishing.config +++ b/conf/modules/polishing.config @@ -6,31 +6,27 @@ process { ext.prefix = { "${meta.id}_medaka" } publishDir = [ path: { "${params.outdir}/${meta.id}/polish/medaka" }, - mode: params.publish_dir_mode, - saveAs: { filename -> filename.equals('versions.yml') ? null : filename } + mode: params.publish_dir_mode ] } withName: '.*:DORADO:ALIGN.*' { //ext.args = { ["--add-fastq-rg"] } publishDir = [ path: { "${params.outdir}/${meta.id}/polish/dorado/alignment" }, - mode: params.publish_dir_mode, - saveAs: { filename -> filename.equals('versions.yml') ? null : filename } + mode: params.publish_dir_mode ] } withName: '.*:DORADO:POLISH.*' { publishDir = [ path: { "${params.outdir}/${meta.id}/polish/dorado/" }, - mode: params.publish_dir_mode, - saveAs: { filename -> filename.equals('versions.yml') ? null : filename } + mode: params.publish_dir_mode ] } // Pilon mapping withName: '.*PILON:MAP_SR:ALIGN.*' { publishDir = [ path: { "${params.outdir}/${meta.id}/QC/alignments/shortreads/" }, - mode: params.publish_dir_mode, - saveAs: { filename -> filename.equals('versions.yml') ? null : filename } + mode: params.publish_dir_mode ] ext.prefix = { "${meta.id}_shortreads" } ext.args = { "-ax sr " } @@ -38,8 +34,7 @@ process { withName: '.*PILON:MAP_SR.*' { publishDir = [ path: { "${params.outdir}/${meta.id}/QC/alignments/shortreads/" }, - mode: params.publish_dir_mode, - saveAs: { filename -> filename.equals('versions.yml') ? null : filename } + mode: params.publish_dir_mode ] ext.prefix = { "${meta.id}_shortreads" } } @@ -47,8 +42,7 @@ process { ext.prefix = { "${meta.id}_pilon" } publishDir = [ path: { "${params.outdir}/${meta.id}/polish/pilon" }, - mode: params.publish_dir_mode, - saveAs: { filename -> filename.equals('versions.yml') ? null : filename } + mode: params.publish_dir_mode ] } } diff --git a/conf/modules/report.config b/conf/modules/report.config index b405d0f6..54ac3730 100644 --- a/conf/modules/report.config +++ b/conf/modules/report.config @@ -2,8 +2,7 @@ process { withName: REPORT { publishDir = [ path: { "${params.outdir}/report/" }, - mode: params.publish_dir_mode, - saveAs: { filename -> filename.equals('versions.yml') ? null : filename } + mode: params.publish_dir_mode ] } } diff --git a/conf/modules/scaffolding.config b/conf/modules/scaffolding.config index bec450a7..7f75b6ce 100644 --- a/conf/modules/scaffolding.config +++ b/conf/modules/scaffolding.config @@ -2,8 +2,7 @@ process { withName: '.*SCAFFOLD:.*RAGTAG_SCAFFOLD' { publishDir = [ path: { "${params.outdir}/${meta.id}/scaffold/ragtag/" }, - mode: params.publish_dir_mode, - saveAs: { filename -> filename.equals('versions.yml') ? null : filename } + mode: params.publish_dir_mode ] ext.prefix = { "${meta.id}_ragtag" } ext.args = [ @@ -16,8 +15,7 @@ process { withName: LINKS { publishDir = [ path: { "${params.outdir}/${meta.id}/scaffold/links/" }, - mode: params.publish_dir_mode, - saveAs: { filename -> filename.equals('versions.yml') ? null : filename } + mode: params.publish_dir_mode ] ext.prefix = { "${meta.id}_links" } ext.args = ["-t 40,200", "-d 500,2000,5000"].join(" ").trim() @@ -25,24 +23,21 @@ process { withName: LONGSTITCH { publishDir = [ path: { "${params.outdir}/${meta.id}/scaffold/longstitch/" }, - mode: params.publish_dir_mode, - saveAs: { filename -> filename.equals('versions.yml') ? null : filename } + mode: params.publish_dir_mode ] ext.prefix = { "${meta.id}_longstitch" } } withName: BWAMEM2_INDEX { publishDir = [ path: { "${params.outdir}/${meta.id}/scaffold/hic/bwamem2/index" }, - mode: params.publish_dir_mode, - saveAs: { filename -> filename.equals('versions.yml') ? null : filename } + mode: params.publish_dir_mode ] ext.prefix = { "${meta.id}_bwamem_index" } } withName: BWAMEM2_MEM { publishDir = [ path: { "${params.outdir}/${meta.id}/scaffold/hic/bwamem2/mem" }, - mode: params.publish_dir_mode, - saveAs: { filename -> filename.equals('versions.yml') ? null : filename } + mode: params.publish_dir_mode ] ext.prefix = { "${meta.id}_bwamem" } ext.args = { "-5SP"} @@ -50,8 +45,7 @@ process { withName: MINIMAP2_HIC { publishDir = [ path: { "${params.outdir}/${meta.id}/scaffold/hic/minimap/" }, - mode: params.publish_dir_mode, - saveAs: { filename -> filename.equals('versions.yml') ? null : filename } + mode: params.publish_dir_mode ] ext.prefix = { "${meta.id}_minimap_hic" } ext.args = { "--no-pairing" } @@ -60,8 +54,7 @@ process { ext.args = "-Djava.io.tmpdir=./tmp-picard-mardkdup --CREATE_INDEX" publishDir = [ path: { "${params.outdir}/${meta.id}/scaffold/hic/markdup/" }, - mode: params.publish_dir_mode, - saveAs: { filename -> filename.equals('versions.yml') ? null : filename } + mode: params.publish_dir_mode ] ext.prefix = { "${meta.id}_hic_dedup" } } @@ -69,24 +62,21 @@ process { ext.args = {[ "--RGID 1", "--RGLB ${meta.id}","--RGPM UNKNOWN", "--RGPL ILLUMINA","--RGPU 0", "--RGSM ${meta.id}"].join(" ").trim()} publishDir = [ path: { "${params.outdir}/${meta.id}/scaffold/hic/add_replace_rg/" }, - mode: params.publish_dir_mode, - saveAs: { filename -> filename.equals('versions.yml') ? null : filename } + mode: params.publish_dir_mode ] ext.prefix = { "${meta.id}_add_rg" } } withName: YAHS { publishDir = [ path: { "${params.outdir}/${meta.id}/scaffold/hic/yahs/" }, - mode: params.publish_dir_mode, - saveAs: { filename -> filename.equals('versions.yml') ? null : filename } + mode: params.publish_dir_mode ] ext.prefix = { "${meta.id}_hic_dedup" } } withName: '.*HIC:SAMTOOLS_FAIDX.*' { publishDir = [ path: { "${params.outdir}/${meta.id}/scaffold/hic/yahs/index/" }, - mode: params.publish_dir_mode, - saveAs: { filename -> filename.equals('versions.yml') ? null : filename } + mode: params.publish_dir_mode ] ext.prefix = { "${meta.id}_hic_dedup" } } diff --git a/modules/local/gfa2fa/main.nf b/modules/local/gfa2fa/main.nf index 5d38675d..c97cf042 100644 --- a/modules/local/gfa2fa/main.nf +++ b/modules/local/gfa2fa/main.nf @@ -14,8 +14,6 @@ process GFA_2_FA { tuple val("${task.process}"), val('awk'), eval("mawk -Wversion | sed '1!d; s/.*Awk //; s/,.*//; s/ [0-9]*\$//'"), emit: versions_awk, topic: versions tuple val("${task.process}"), val('gzip'), eval("gzip --version | head -n1 | sed 's/gzip //'"), emit: versions_gzip, topic: versions - path "versions.yml", emit: versions - script: """ outfile=\$(basename $gfa_file .gfa).fa.gz diff --git a/modules/local/longstitch/main.nf b/modules/local/longstitch/main.nf index e4ab4448..ebb28487 100644 --- a/modules/local/longstitch/main.nf +++ b/modules/local/longstitch/main.nf @@ -12,7 +12,7 @@ process LONGSTITCH { output: tuple val(meta), path("*.tigmint-ntLink-arks.fa"), emit: ntlLinks_arks_scaffolds tuple val(meta), path("*.tigmint-ntLink.fa"), emit: ntlLinks_scaffolds - tuple val("${task.process}"), val('LongStitch'), eval("longstitch | head -n1 | sed 's/LongStitch v//'"), emit: versions_longstitch, topic: versions + tuple val("${task.process}"), val('LongStitch'), eval("longstitch | head -n1 | sed 's/LongStitch v//'"), emit: versions_longstitch, topic: versions script: @@ -44,20 +44,11 @@ process LONGSTITCH { mv *.tigmint-ntLink.longstitch-scaffolds.fa ${prefix}.tigmint-ntLink.fa sed -i 's/\\(scaffold[0-9]*\\),.*/\\1/' ${prefix}.tigmint-ntLink.fa - - cat <<-END_VERSIONS > versions.yml - "${task.process}": - LongStitch: \$(echo \$(longstitch | head -n1 | sed 's/LongStitch v//')) - END_VERSIONS """ stub: def prefix = task.ext.prefix ?: "${meta.id}" """ touch ${prefix}.tigmint-ntLink-arks.fa touch ${prefix}.tigmint-ntLink.fa - cat <<-END_VERSIONS > versions.yml - "${task.process}": - LongStitch: \$(echo \$(longstitch | head -n1 | sed 's/LongStitch v//')) - END_VERSIONS """ } diff --git a/modules/local/report/main.nf b/modules/local/report/main.nf index f4450896..6bd04f9b 100644 --- a/modules/local/report/main.nf +++ b/modules/local/report/main.nf @@ -69,9 +69,9 @@ process REPORT { cat <<- END_YAML_GROUPS > groups.yml ${group_content} END_YAML_GROUPS - cat <<- END_YAML_GROUPS > versions.yml + cat <<- END_YAML_VERSIONS > versions.yml ${versions_content} - END_YAML_GROUPS + END_YAML_VERSIONS export HOME="\$PWD" quarto render report.qmd \\ diff --git a/subworkflows/local/prepare/jellyfish/main.nf b/subworkflows/local/prepare/jellyfish/main.nf index a079f19a..644ff638 100644 --- a/subworkflows/local/prepare/jellyfish/main.nf +++ b/subworkflows/local/prepare/jellyfish/main.nf @@ -51,10 +51,7 @@ workflow JELLYFISH { COUNT(samples) COUNT.out.kmers.set { kmers } - ch_versions = ch_versions.mix(COUNT.out.versions) - HISTO(kmers) - ch_versions = ch_versions.mix(HISTO.out.versions) HISTO.out.histo .map { meta, hist -> @@ -72,10 +69,6 @@ workflow JELLYFISH { GENOMESCOPE(genomescope_in) - ch_versions = ch_versions - .mix(GENOMESCOPE.out.versions) - .mix(STATS.out.versions) - GENOMESCOPE.out.estimated_hap_len .filter { it -> it[0].metas } .flatMap { it -> @@ -96,11 +89,8 @@ workflow JELLYFISH { GENOMESCOPE.out.plot.set { genomescope_plot } - versions = ch_versions - emit: main_out = outputs genomescope_summary genomescope_plot - versions } diff --git a/subworkflows/local/prepare/prepare_ont/collect/main.nf b/subworkflows/local/prepare/prepare_ont/collect/main.nf index 964b90dc..c4fcff03 100644 --- a/subworkflows/local/prepare/prepare_ont/collect/main.nf +++ b/subworkflows/local/prepare/prepare_ont/collect/main.nf @@ -5,8 +5,6 @@ workflow COLLECT { ch_input main: - channel.empty().set { ch_versions } - ch_input .filter { it -> it.ont_collect @@ -16,11 +14,7 @@ workflow COLLECT { COLLECT_READS(reads) COLLECT_READS.out.combined_reads.set { reads } - ch_versions.mix(COLLECT_READS.out.versions) - - versions = ch_versions emit: reads - versions } diff --git a/subworkflows/local/prepare/prepare_shortreads/main.nf b/subworkflows/local/prepare/prepare_shortreads/main.nf index f6b36008..87301de2 100644 --- a/subworkflows/local/prepare/prepare_shortreads/main.nf +++ b/subworkflows/local/prepare/prepare_shortreads/main.nf @@ -196,13 +196,10 @@ workflow PREPARE_SHORTREADS { - versions = ch_versions.mix(MERYL_COUNT.out.versions).mix(MERYL_UNIONSUM.out.versions) - emit: main_out = shortreads fastp_json = FASTP.out.json meryl_kmers - versions } def create_shortread_channel(row) { // This function expects a meta map as input diff --git a/subworkflows/local/qc/main.nf b/subworkflows/local/qc/main.nf index 11226cf1..6f4cdf57 100644 --- a/subworkflows/local/qc/main.nf +++ b/subworkflows/local/qc/main.nf @@ -87,10 +87,11 @@ workflow QC { it.meta.assembly_map_bam ] use_ref: it.meta.use_ref + use_gff: it.meta.use_ref && it.meta.ref_gff ? true : false } .set { quast_in } - QUAST(quast_in) + QUAST(quast_in) // works magically QUAST.out.tsv.set { quast_out } ch_qc diff --git a/subworkflows/local/scaffolding/hic/main.nf b/subworkflows/local/scaffolding/hic/main.nf index 78dcb706..1d3da509 100644 --- a/subworkflows/local/scaffolding/hic/main.nf +++ b/subworkflows/local/scaffolding/hic/main.nf @@ -134,15 +134,9 @@ workflow HIC { RUN_LIFTOFF(liftoff_in) - ch_versions = ch_versions.mix( - RUN_LIFTOFF.out.versions, - QC.out.versions) - emit: ch_main = ch_main_scaffolded quast_out = QC.out.quast_out busco_out = QC.out.busco_out merqury_report_files = QC.out.merqury_report_files - versions = ch_versions - } diff --git a/tests/default.nf.test.snap b/tests/default.nf.test.snap index ecffe035..60bc84cf 100644 --- a/tests/default.nf.test.snap +++ b/tests/default.nf.test.snap @@ -24,13 +24,13 @@ "gzip": null }, "HIFIASM": { - "hifiasm": "0.25.0-r726" + "hifasm": "0.25.0-r726" }, "HIFIASM_ONT": { - "hifiasm": "0.25.0-r726" + "hifasm": "0.25.0-r726" }, "LIFTOFF": { - "liftoff": "v1.6.3" + "liftoff": "1.6.3" }, "LONGSTITCH": { "LongStitch": "1.0.5" @@ -38,8 +38,8 @@ "RAGTAG_PATCH": { "ragtag": "2.1.0" }, - "Workflow": { - "nf-core/genomeassembler": "v2.0.0dev" + "RAGTAG_SCAFFOLD": { + "ragtag": "2.1.0" } }, [ @@ -56,7 +56,7 @@ "test_flye_hifiasm/assembly/flye/test_flye_hifiasm.params.json", "test_flye_hifiasm/assembly/hifiasm", "test_flye_hifiasm/assembly/hifiasm/fasta", - "test_flye_hifiasm/assembly/hifiasm/fasta/test_flye_hifiasm.bp.p_utg.fa.gz", + "test_flye_hifiasm/assembly/hifiasm/fasta/test_flye_hifiasm.bp.p_ctg.fa.gz", "test_flye_hifiasm/assembly/hifiasm/test_flye_hifiasm.bp.hap1.p_ctg.gfa", "test_flye_hifiasm/assembly/hifiasm/test_flye_hifiasm.bp.hap2.p_ctg.gfa", "test_flye_hifiasm/assembly/hifiasm/test_flye_hifiasm.bp.p_ctg.gfa", @@ -111,7 +111,7 @@ "test_hifi_hifiasm/assembly", "test_hifi_hifiasm/assembly/hifiasm", "test_hifi_hifiasm/assembly/hifiasm/fasta", - "test_hifi_hifiasm/assembly/hifiasm/fasta/test_hifi_hifiasm.bp.p_utg.fa.gz", + "test_hifi_hifiasm/assembly/hifiasm/fasta/test_hifi_hifiasm.bp.p_ctg.fa.gz", "test_hifi_hifiasm/assembly/hifiasm/test_hifi_hifiasm.bp.hap1.p_ctg.gfa", "test_hifi_hifiasm/assembly/hifiasm/test_hifi_hifiasm.bp.hap2.p_ctg.gfa", "test_hifi_hifiasm/assembly/hifiasm/test_hifi_hifiasm.bp.p_ctg.gfa", @@ -146,7 +146,7 @@ "test_hifiasm_flye/assembly/flye/test_hifiasm_flye.params.json", "test_hifiasm_flye/assembly/hifiasm_ont", "test_hifiasm_flye/assembly/hifiasm_ont/fasta", - "test_hifiasm_flye/assembly/hifiasm_ont/fasta/test_hifiasm_flye.bp.p_utg.fa.gz", + "test_hifiasm_flye/assembly/hifiasm_ont/fasta/test_hifiasm_flye.bp.p_ctg.fa.gz", "test_hifiasm_flye/assembly/hifiasm_ont/test_hifiasm_flye.bp.hap1.p_ctg.gfa", "test_hifiasm_flye/assembly/hifiasm_ont/test_hifiasm_flye.bp.hap2.p_ctg.gfa", "test_hifiasm_flye/assembly/hifiasm_ont/test_hifiasm_flye.bp.p_ctg.gfa", @@ -183,7 +183,7 @@ "test_hifiasm_ul/assembly", "test_hifiasm_ul/assembly/hifiasm", "test_hifiasm_ul/assembly/hifiasm/fasta", - "test_hifiasm_ul/assembly/hifiasm/fasta/test_hifiasm_ul.bp.p_utg.fa.gz", + "test_hifiasm_ul/assembly/hifiasm/fasta/test_hifiasm_ul.bp.p_ctg.fa.gz", "test_hifiasm_ul/assembly/hifiasm/test_hifiasm_ul.bp.hap1.p_ctg.gfa", "test_hifiasm_ul/assembly/hifiasm/test_hifiasm_ul.bp.hap2.p_ctg.gfa", "test_hifiasm_ul/assembly/hifiasm/test_hifiasm_ul.bp.p_ctg.gfa", @@ -246,7 +246,7 @@ "test_ont_hifiasm/assembly", "test_ont_hifiasm/assembly/hifiasm_ont", "test_ont_hifiasm/assembly/hifiasm_ont/fasta", - "test_ont_hifiasm/assembly/hifiasm_ont/fasta/test_ont_hifiasm.bp.p_utg.fa.gz", + "test_ont_hifiasm/assembly/hifiasm_ont/fasta/test_ont_hifiasm.bp.p_ctg.fa.gz", "test_ont_hifiasm/assembly/hifiasm_ont/test_ont_hifiasm.bp.hap1.p_ctg.gfa", "test_ont_hifiasm/assembly/hifiasm_ont/test_ont_hifiasm.bp.hap2.p_ctg.gfa", "test_ont_hifiasm/assembly/hifiasm_ont/test_ont_hifiasm.bp.p_ctg.gfa", @@ -325,8 +325,8 @@ "test_hifiasm_ul_assembly.unmapped.txt:md5,d41d8cd98f00b204e9800998ecf8427e", "test_hifiasm_ul_hifi.fastplong.json:md5,f52a2b6d9aa29fbe5749684ee479de90", "test_hifiasm_ul_ont.fastplong.json:md5,86da6d58ac4630bfeae3ee08014ecb0b", - "test_hifiasm_ul_longstitch.tigmint-ntLink-arks.fa:md5,b5ae075fa81a8a0dd3a69283d48ae63a", - "test_hifiasm_ul_longstitch.tigmint-ntLink.fa:md5,6191cac0672c778be5fc93b74121ed95", + "test_hifiasm_ul_longstitch.tigmint-ntLink-arks.fa:md5,f509aa667afa93699aa4d0258053d706", + "test_hifiasm_ul_longstitch.tigmint-ntLink.fa:md5,301136e56bd854239f31e96e32bc49d4", "test_hifiasm_ul_longstitch.unmapped.txt:md5,d41d8cd98f00b204e9800998ecf8427e", "test_ont_flye.params.json:md5,afa91c041bce5e190f4a699d11b69db6", "test_ont_flye_assembly.unmapped.txt:md5,d41d8cd98f00b204e9800998ecf8427e", @@ -346,8 +346,8 @@ ], "meta": { "nf-test": "0.9.2", - "nextflow": "25.04.8" + "nextflow": "25.10.0" }, - "timestamp": "2026-02-13T11:26:09.065736384" + "timestamp": "2026-02-18T12:57:57.883672792" } } \ No newline at end of file diff --git a/workflows/genomeassembler.nf b/workflows/genomeassembler.nf index 9b92cddc..1b6fa8fc 100644 --- a/workflows/genomeassembler.nf +++ b/workflows/genomeassembler.nf @@ -49,8 +49,6 @@ workflow GENOMEASSEMBLER { */ channel.empty().set { meryl_kmers } - channel.empty().set { ch_versions } - // Initialize channels for QC report collection channel .of([]) @@ -84,7 +82,6 @@ workflow GENOMEASSEMBLER { ASSEMBLE.out.ch_main.set { ch_main_assembled } - ch_versions = ch_versions.mix(ASSEMBLE.out.versions) /* Polishing */ @@ -111,9 +108,6 @@ workflow GENOMEASSEMBLER { ch_main_assembled_branched.no_polish .mix(POLISH.out.ch_main) .set { ch_main_polished } - - ch_versions = ch_versions.mix(POLISH.out.versions) - // Update scaffold for meta map ch_main_polished @@ -150,11 +144,6 @@ workflow GENOMEASSEMBLER { .collect { it -> it[1] } .set { genomescope_files } - ch_versions = ch_versions.mix(PREPARE.out.versions).mix(ASSEMBLE.out.versions).mix(POLISH.out.versions).mix(SCAFFOLD.out.versions) - - ch_versions = ch_versions - - def topic_versions = channel.topic("versions") .distinct() .branch { entry -> @@ -171,8 +160,7 @@ workflow GENOMEASSEMBLER { tool_versions.unique().sort() "${process}:\n${tool_versions.join('\n')}" } - ch_collated_versions = softwareVersionsToYAML(ch_versions.mix(topic_versions.versions_file)) - .mix(topic_versions_string) + ch_collated_versions = topic_versions_string /* Report */ @@ -260,5 +248,4 @@ workflow GENOMEASSEMBLER { emit: _report - versions = ch_versions // channel: [ path(versions.yml) ] } From 494fd272fb9257f9fefd8f60d3e2787fe3aa61d0 Mon Sep 17 00:00:00 2001 From: Niklas Schandry Date: Wed, 18 Feb 2026 14:08:56 +0100 Subject: [PATCH 138/162] linting fixes --- .github/workflows/linting.yml | 16 +++++++-------- .github/workflows/linting_comment.yml | 2 +- .prettierignore | 3 +++ assets/nf-core-genomeassembler_logo_light.png | Bin 87890 -> 85875 bytes .../nf-core-genomeassembler_logo_dark.png | Bin 24253 -> 23923 bytes .../nf-core-genomeassembler_logo_light.png | Bin 20378 -> 19988 bytes modules/nf-core/fastplong/fastplong.diff | 19 ------------------ modules/nf-core/fastplong/main.nf | 2 +- 8 files changed, 13 insertions(+), 29 deletions(-) delete mode 100644 modules/nf-core/fastplong/fastplong.diff diff --git a/.github/workflows/linting.yml b/.github/workflows/linting.yml index 8b0f88c3..7a527a34 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@93cb6efe18208431cddfb8368fd83d5badbf9bfd # 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@93cb6efe18208431cddfb8368fd83d5badbf9bfd # 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 @@ -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/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/.prettierignore b/.prettierignore index edd29f01..dd749d43 100644 --- a/.prettierignore +++ b/.prettierignore @@ -10,4 +10,7 @@ testing/ testing* *.pyc bin/ +.nf-test/ ro-crate-metadata.json +modules/nf-core/ +subworkflows/nf-core/ diff --git a/assets/nf-core-genomeassembler_logo_light.png b/assets/nf-core-genomeassembler_logo_light.png index 9a4851b02a6d80e5a4bcf1ce913b5560f683eaf8..e17ba5263011bcacf5ad461cb26ead122d9ac850 100644 GIT binary patch literal 85875 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 z

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

wF&ud3xKf;zfxax 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 diff --git a/docs/images/nf-core-genomeassembler_logo_dark.png b/docs/images/nf-core-genomeassembler_logo_dark.png index 0039f2ff7145c20deae0bcc9cc35d161bd985fe9..aa72431d5265121bac288eb8cfd23c191a3fedef 100644 GIT binary patch literal 23923 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 EKV8w(oB#j- literal 24253 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 diff --git a/docs/images/nf-core-genomeassembler_logo_light.png b/docs/images/nf-core-genomeassembler_logo_light.png index edbdc154098a0e79499ba8093548828459ef1ff2..a3cb80827cfc1e17751e7876d1a392f314683e9c 100644 GIT binary patch delta 19726 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 Date: Wed, 18 Feb 2026 14:09:49 +0100 Subject: [PATCH 139/162] linting fixes --- modules.json | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/modules.json b/modules.json index 2330dd16..4d65ad24 100644 --- a/modules.json +++ b/modules.json @@ -28,8 +28,7 @@ "fastplong": { "branch": "master", "git_sha": "a331ecfd1aa48b2b2298aab23bb4516c800e410b", - "installed_by": ["modules"], - "patch": "modules/nf-core/fastplong/fastplong.diff" + "installed_by": ["modules"] }, "fastqc": { "branch": "master", From 1f11419a5be873e5daa71004184f48eb550abc19 Mon Sep 17 00:00:00 2001 From: Niklas Schandry Date: Wed, 18 Feb 2026 14:30:41 +0100 Subject: [PATCH 140/162] linting fixes --- .github/workflows/nf-test.yml | 2 +- README.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/nf-test.yml b/.github/workflows/nf-test.yml index c98d76ec..fd742d1b 100644 --- a/.github/workflows/nf-test.yml +++ b/.github/workflows/nf-test.yml @@ -78,7 +78,7 @@ jobs: - isMain: false profile: "singularity" NXF_VER: - - "25.04.0" + - "25.10.0" - "latest-everything" env: NXF_ANSI_LOG: false diff --git a/README.md b/README.md index e4bd3785..98aa584a 100644 --- a/README.md +++ b/README.md @@ -10,7 +10,7 @@ [![GitHub Actions Linting Status](https://github.com/nf-core/genomeassembler/actions/workflows/linting.yml/badge.svg)](https://github.com/nf-core/genomeassembler/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/genomeassembler/results)[![Cite with Zenodo](http://img.shields.io/badge/DOI-10.5281/zenodo.XXXXXXX-1073c8?labelColor=000000)](https://doi.org/10.5281/zenodo.14986998) [![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/) +[![Nextflow](https://img.shields.io/badge/version-%E2%89%A525.10.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.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.5.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/) From c72551135c934c5ffa5c04e018ce66356a757318 Mon Sep 17 00:00:00 2001 From: Niklas Schandry Date: Wed, 18 Feb 2026 14:44:26 +0100 Subject: [PATCH 141/162] reinstall fastplong without patch --- modules/nf-core/fastplong/main.nf | 2 +- ro-crate-metadata.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/nf-core/fastplong/main.nf b/modules/nf-core/fastplong/main.nf index 3d202575..ce1be5ef 100644 --- a/modules/nf-core/fastplong/main.nf +++ b/modules/nf-core/fastplong/main.nf @@ -59,4 +59,4 @@ process FASTPLONG { touch ${prefix}.fastplong.html touch ${prefix}.fastplong.log """ -} \ No newline at end of file +} diff --git a/ro-crate-metadata.json b/ro-crate-metadata.json index 64504d17..4a4fad02 100644 --- a/ro-crate-metadata.json +++ b/ro-crate-metadata.json @@ -23,7 +23,7 @@ "@type": "Dataset", "creativeWorkStatus": "Stable", "datePublished": "2025-11-20T09:31:46+00:00", - "description": "

\n \n \n \"nf-core/genomeassembler\"\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/genomeassembler)\n[![GitHub Actions CI Status](https://github.com/nf-core/genomeassembler/actions/workflows/nf-test.yml/badge.svg)](https://github.com/nf-core/genomeassembler/actions/workflows/nf-test.yml)\n[![GitHub Actions Linting Status](https://github.com/nf-core/genomeassembler/actions/workflows/linting.yml/badge.svg)](https://github.com/nf-core/genomeassembler/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/genomeassembler/results)[![Cite with Zenodo](http://img.shields.io/badge/DOI-10.5281/zenodo.XXXXXXX-1073c8?labelColor=000000)](https://doi.org/10.5281/zenodo.14986998)\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.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.5.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/genomeassembler)\n\n[![Get help on Slack](http://img.shields.io/badge/slack-nf--core%20%23genomeassembler-4A154B?labelColor=000000&logo=slack)](https://nfcore.slack.com/channels/genomeassembler)[![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/genomeassembler** is a bioinformatics pipeline that carries out genome assembly, polishing and scaffolding from long reads (ONT or pacbio). Assembly can be done via `flye` or `hifiasm`, polishing can be carried out with `medaka` (ONT), or `pilon` (requires short-reads), and scaffolding can be done using `LINKS`, `Longstitch`, or `RagTag` (if a reference is available). Quality control includes `BUSCO`, `QUAST` and `merqury` (requires short-reads).\nCurrently, this pipeline does not implement phasing of polyploid genomes or HiC scaffolding.\n\n\n \n \"nf-core/genomeassembler\"\n\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\nFirst, prepare a samplesheet with your input data that looks as follows:\n\n`samplesheet.csv`:\n\n```csv\nsample,ontreads,hifireads,ref_fasta,ref_gff,shortread_F,shortread_R,paired\nsampleName,ontreads.fa.gz,hifireads.fa.gz,assembly.fasta.gz,reference.fasta,reference.gff,short_F1.fastq,short_F2.fastq,true\n```\n\nEach row represents one genome to be assembled. `sample` should contain the name of the sample, `ontreads` should contain a path to ONT reads (fastq.gz), `hifireads` a path to HiFi reads (fastq.gz), `ref_fasta` and `ref_gff` contain reference genome fasta and annotations. `shortread_F` and `shortread_R` contain paths to short-read data, `paired` indicates if short-reads are paired. Columns can be omitted if they contain no data, with the exception of `shortread_R`, which needs to be present if `shortread_F` is there, even if it is empty.\n\nNow, you can run the pipeline using:\n\n```bash\nnextflow run nf-core/genomeassembler \\\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/genomeassembler/usage) and the [parameter documentation](https://nf-co.re/genomeassembler/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/genomeassembler/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/genomeassembler/output).\n\n## Credits\n\nnf-core/genomeassembler was written, and is currently maintained by [Niklas Schandry](https://github.com/nschan), of the Faculty of Biology of the Ludwig-Maximilians University (LMU) in Munich, Germany, with funding support from the German Research Foundation (Deutsche Forschungsgemeinschaft\u00a0[DFG], via Transregional Research Center TRR356 grant 491090170-A05 to Niklas Schandry).\n\nI thank the following people for their extensive assistance and constructive reviews during the development of this pipeline:\n\n- [Mahesh Binzer-Panchal](https://github.com/mahesh-panchal)\n- [Matthias H\u00f6rtenhuber](https://github.com/mashehu)\n- [Louis Le N\u00e9zet](https://github.com/LouisLeNezet)\n- [J\u00falia Mir Pedrol](https://github.com/mirpedrol)\n- [Daniel Straub](https://github.com/d4straub)\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 `#genomeassembler` channel](https://nfcore.slack.com/channels/genomeassembler) (you can join with [this invite](https://nf-co.re/join/slack)).\n\n## Citations\n\nIf you use nf-core/genomeassembler for your analysis, please cite it using the following doi: [10.5281/zenodo.14986998](https://doi.org/10.5281/zenodo.14986998)\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/genomeassembler\"\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/genomeassembler)\n[![GitHub Actions CI Status](https://github.com/nf-core/genomeassembler/actions/workflows/nf-test.yml/badge.svg)](https://github.com/nf-core/genomeassembler/actions/workflows/nf-test.yml)\n[![GitHub Actions Linting Status](https://github.com/nf-core/genomeassembler/actions/workflows/linting.yml/badge.svg)](https://github.com/nf-core/genomeassembler/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/genomeassembler/results)[![Cite with Zenodo](http://img.shields.io/badge/DOI-10.5281/zenodo.XXXXXXX-1073c8?labelColor=000000)](https://doi.org/10.5281/zenodo.14986998)\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.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.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.5.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/genomeassembler)\n\n[![Get help on Slack](http://img.shields.io/badge/slack-nf--core%20%23genomeassembler-4A154B?labelColor=000000&logo=slack)](https://nfcore.slack.com/channels/genomeassembler)[![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/genomeassembler** is a bioinformatics pipeline that carries out genome assembly, polishing and scaffolding from long reads (ONT or pacbio). Assembly can be done via `flye` or `hifiasm`, polishing can be carried out with `medaka` (ONT), or `pilon` (requires short-reads), and scaffolding can be done using `LINKS`, `Longstitch`, or `RagTag` (if a reference is available). Quality control includes `BUSCO`, `QUAST` and `merqury` (requires short-reads).\nCurrently, this pipeline does not implement phasing of polyploid genomes or HiC scaffolding.\n\n\n \n \"nf-core/genomeassembler\"\n\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\nFirst, prepare a samplesheet with your input data that looks as follows:\n\n`samplesheet.csv`:\n\n```csv\nsample,ontreads,hifireads,ref_fasta,ref_gff,shortread_F,shortread_R,paired\nsampleName,ontreads.fa.gz,hifireads.fa.gz,assembly.fasta.gz,reference.fasta,reference.gff,short_F1.fastq,short_F2.fastq,true\n```\n\nEach row represents one genome to be assembled. `sample` should contain the name of the sample, `ontreads` should contain a path to ONT reads (fastq.gz), `hifireads` a path to HiFi reads (fastq.gz), `ref_fasta` and `ref_gff` contain reference genome fasta and annotations. `shortread_F` and `shortread_R` contain paths to short-read data, `paired` indicates if short-reads are paired. Columns can be omitted if they contain no data, with the exception of `shortread_R`, which needs to be present if `shortread_F` is there, even if it is empty.\n\nNow, you can run the pipeline using:\n\n```bash\nnextflow run nf-core/genomeassembler \\\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/genomeassembler/usage) and the [parameter documentation](https://nf-co.re/genomeassembler/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/genomeassembler/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/genomeassembler/output).\n\n## Credits\n\nnf-core/genomeassembler was written, and is currently maintained by [Niklas Schandry](https://github.com/nschan), of the Faculty of Biology of the Ludwig-Maximilians University (LMU) in Munich, Germany, with funding support from the German Research Foundation (Deutsche Forschungsgemeinschaft\u00a0[DFG], via Transregional Research Center TRR356 grant 491090170-A05 to Niklas Schandry).\n\nI thank the following people for their extensive assistance and constructive reviews during the development of this pipeline:\n\n- [Mahesh Binzer-Panchal](https://github.com/mahesh-panchal)\n- [Matthias H\u00f6rtenhuber](https://github.com/mashehu)\n- [Louis Le N\u00e9zet](https://github.com/LouisLeNezet)\n- [J\u00falia Mir Pedrol](https://github.com/mirpedrol)\n- [Daniel Straub](https://github.com/d4straub)\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 `#genomeassembler` channel](https://nfcore.slack.com/channels/genomeassembler) (you can join with [this invite](https://nf-co.re/join/slack)).\n\n## Citations\n\nIf you use nf-core/genomeassembler for your analysis, please cite it using the following doi: [10.5281/zenodo.14986998](https://doi.org/10.5281/zenodo.14986998)\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" From 59e6bb1f024eeed6bd4963892c096cfd617cfb8b Mon Sep 17 00:00:00 2001 From: Niklas Schandry Date: Wed, 18 Feb 2026 15:25:26 +0100 Subject: [PATCH 142/162] more files unchanged --- .nf-core.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.nf-core.yml b/.nf-core.yml index 18079628..587f3004 100644 --- a/.nf-core.yml +++ b/.nf-core.yml @@ -7,6 +7,9 @@ lint: files_unchanged: - assets/sendmail_template.txt - .github/CONTRIBUTING.md + - assets/nf-core-genomeassembler_logo_light.png + - docs/images/nf-core-genomeassembler_logo_light.png + - docs/images/nf-core-genomeassembler_logo_dark.png nf_core_version: 3.5.1 repository_type: pipeline template: From 2c08e3aa73d308b99a4e471a81df081fbaa5568c Mon Sep 17 00:00:00 2001 From: Niklas Schandry Date: Wed, 18 Feb 2026 15:48:26 +0100 Subject: [PATCH 143/162] fix bug in assembler argument resolution that may affect test --- nextflow.config | 2 +- .../utils_nfcore_genomeassembler_pipeline/main.nf | 15 +++++++++------ 2 files changed, 10 insertions(+), 7 deletions(-) diff --git a/nextflow.config b/nextflow.config index cad35ca9..c60089a1 100644 --- a/nextflow.config +++ b/nextflow.config @@ -81,7 +81,7 @@ params { // -- Assembly: Flye -- genome_size = null // genomesize, optional, can be estimated from ONT reads // DEPRECATED: flye_mode = '--nano-hq' - flye_args = "" // extra flye args + flye_args = '' // extra flye args // -- Assembly: hifiasm -- hifiasm_args = '' // extra hifiasm args // QC Oriented extra options diff --git a/subworkflows/local/utils_nfcore_genomeassembler_pipeline/main.nf b/subworkflows/local/utils_nfcore_genomeassembler_pipeline/main.nf index 2c4c57e7..69470907 100644 --- a/subworkflows/local/utils_nfcore_genomeassembler_pipeline/main.nf +++ b/subworkflows/local/utils_nfcore_genomeassembler_pipeline/main.nf @@ -138,6 +138,9 @@ workflow PIPELINE_INITIALISATION { def hic_trim = !scaffold_hic ? false : (it.hic_trim ?: params.hic_trim) + def assembler_ont_args = it.assembler_ont_args ?: params.assembler_ont_args ?: '' + def assembler_hifi_args = it.assembler_hifi_args ?: params.assembler_hifi_args ?: '' + // Check if strategy can be inferred strategy == "single" && ontreads && hifireads && !((!assembler_ont && assembler_hifi) || (assembler_ont && !assembler_hifi)) ? @@ -166,13 +169,13 @@ workflow PIPELINE_INITIALISATION { assembler_ont: assembler_ont, assembler_hifi: assembler_hifi, assembly_scaffolding_order: it.assembly_scaffolding_order ?: params.assembly_scaffolding_order ?: "ont_on_hifi", - assembler_ont_args: it.assembler_ont_args ?: params.assembler_ont_args ?: - (assembler_ont == "hifiasm") ? (it.hifiasm_args ?: params.hifiasm_args) : - (assembler_ont == "flye") ? (it.flye_args ?: params.flye_args) : + assembler_ont_args: assembler_ont_args + " " + + (assembler == "flye" && strategy == "single") || (assembler_ont == "flye") ? (it.flye_args ?: params.flye_args) : + (assembler == "hifiasm" && strategy == "single") || (assembler_ont == "hifiasm") ? (it.hifiasm_args ?: params.hifiasm_args) : "", - assembler_hifi_args: it.assembler_hifi_args ?: params.assembler_hifi_args ?: - (assembler_hifi == "hifiasm") ? (it.hifiasm_args ?: params.hifiasm_args) : - (assembler_hifi == "flye") ? (it.flye_args ?: params.flye_args) : + assembler_hifi_args: assembler_hifi_args + " " + + (assembler == "flye" && strategy == "single") || (assembler_hifi == "hifiasm") ? (it.hifiasm_args ?: params.hifiasm_args) : + (assembler == "flye" && strategy == "single") || (assembler_hifi == "flye") ? (it.flye_args ?: params.flye_args) : "", polish: polish, ont_collect: it.ont_collect ?: params.ont_collect, From 5c71d57de579041443492ddec19ceeef7ea7a4a7 Mon Sep 17 00:00:00 2001 From: Niklas Schandry Date: Wed, 18 Feb 2026 16:47:56 +0100 Subject: [PATCH 144/162] fix versions in gfa2fa, update test --- modules/local/gfa2fa/main.nf | 4 ++-- .../main.nf | 9 ++++----- tests/default.nf.test.snap | 16 ++++++++-------- 3 files changed, 14 insertions(+), 15 deletions(-) diff --git a/modules/local/gfa2fa/main.nf b/modules/local/gfa2fa/main.nf index c97cf042..8ef54bf6 100644 --- a/modules/local/gfa2fa/main.nf +++ b/modules/local/gfa2fa/main.nf @@ -11,8 +11,8 @@ process GFA_2_FA { output: tuple val(meta), path("*fa.gz"), emit: contigs_fasta - tuple val("${task.process}"), val('awk'), eval("mawk -Wversion | sed '1!d; s/.*Awk //; s/,.*//; s/ [0-9]*\$//'"), emit: versions_awk, topic: versions - tuple val("${task.process}"), val('gzip'), eval("gzip --version | head -n1 | sed 's/gzip //'"), emit: versions_gzip, topic: versions + tuple val("${task.process}"), val('awk'), eval("awk |& head -n1 | grep -o 'v[0-9\\.]*' | sed 's/v//'"), emit: versions_awk, topic: versions + tuple val("${task.process}"), val('bgzip'), eval("bgzip --version | head -n1 | sed 's/bgzip (htslib) //'"), emit: versions_gzip, topic: versions script: """ diff --git a/subworkflows/local/utils_nfcore_genomeassembler_pipeline/main.nf b/subworkflows/local/utils_nfcore_genomeassembler_pipeline/main.nf index 69470907..6a9d5850 100644 --- a/subworkflows/local/utils_nfcore_genomeassembler_pipeline/main.nf +++ b/subworkflows/local/utils_nfcore_genomeassembler_pipeline/main.nf @@ -141,7 +141,6 @@ workflow PIPELINE_INITIALISATION { def assembler_ont_args = it.assembler_ont_args ?: params.assembler_ont_args ?: '' def assembler_hifi_args = it.assembler_hifi_args ?: params.assembler_hifi_args ?: '' - // Check if strategy can be inferred strategy == "single" && ontreads && hifireads && !((!assembler_ont && assembler_hifi) || (assembler_ont && !assembler_hifi)) ? error( @@ -170,13 +169,13 @@ workflow PIPELINE_INITIALISATION { assembler_hifi: assembler_hifi, assembly_scaffolding_order: it.assembly_scaffolding_order ?: params.assembly_scaffolding_order ?: "ont_on_hifi", assembler_ont_args: assembler_ont_args + " " + - (assembler == "flye" && strategy == "single") || (assembler_ont == "flye") ? (it.flye_args ?: params.flye_args) : + ((assembler == "flye" && strategy == "single") || (assembler_ont == "flye") ? (it.flye_args ?: params.flye_args) : (assembler == "hifiasm" && strategy == "single") || (assembler_ont == "hifiasm") ? (it.hifiasm_args ?: params.hifiasm_args) : - "", + ""), assembler_hifi_args: assembler_hifi_args + " " + - (assembler == "flye" && strategy == "single") || (assembler_hifi == "hifiasm") ? (it.hifiasm_args ?: params.hifiasm_args) : + ((assembler == "flye" && strategy == "single") || (assembler_hifi == "hifiasm") ? (it.hifiasm_args ?: params.hifiasm_args) : (assembler == "flye" && strategy == "single") || (assembler_hifi == "flye") ? (it.flye_args ?: params.flye_args) : - "", + ""), polish: polish, ont_collect: it.ont_collect ?: params.ont_collect, ont_adapters: it.ont_adapters ?: params.ont_adapters, diff --git a/tests/default.nf.test.snap b/tests/default.nf.test.snap index 60bc84cf..10bb385d 100644 --- a/tests/default.nf.test.snap +++ b/tests/default.nf.test.snap @@ -16,12 +16,12 @@ "flye": "2.9.5-b1801" }, "GFA_2_FA_HIFI": { - "awk": null, - "gzip": null + "awk": "1.36.1", + "bgzip": "1.22.1" }, "GFA_2_FA_ONT": { - "awk": null, - "gzip": null + "awk": "1.36.1", + "bgzip": "1.22.1" }, "HIFIASM": { "hifasm": "0.25.0-r726" @@ -325,7 +325,7 @@ "test_hifiasm_ul_assembly.unmapped.txt:md5,d41d8cd98f00b204e9800998ecf8427e", "test_hifiasm_ul_hifi.fastplong.json:md5,f52a2b6d9aa29fbe5749684ee479de90", "test_hifiasm_ul_ont.fastplong.json:md5,86da6d58ac4630bfeae3ee08014ecb0b", - "test_hifiasm_ul_longstitch.tigmint-ntLink-arks.fa:md5,f509aa667afa93699aa4d0258053d706", + "test_hifiasm_ul_longstitch.tigmint-ntLink-arks.fa:md5,2bc73aa0085ed4ef5261f3fadfc64b46", "test_hifiasm_ul_longstitch.tigmint-ntLink.fa:md5,301136e56bd854239f31e96e32bc49d4", "test_hifiasm_ul_longstitch.unmapped.txt:md5,d41d8cd98f00b204e9800998ecf8427e", "test_ont_flye.params.json:md5,afa91c041bce5e190f4a699d11b69db6", @@ -344,10 +344,10 @@ "test_ont_hifiasm_ragtag.stats:md5,466273b499384b2c781c83dc583b8c99" ] ], + "timestamp": "2026-02-18T16:47:20.810744058", "meta": { - "nf-test": "0.9.2", + "nf-test": "0.9.4", "nextflow": "25.10.0" - }, - "timestamp": "2026-02-18T12:57:57.883672792" + } } } \ No newline at end of file From 7765a61714bc89fbb2e8c97d3338782e692917d7 Mon Sep 17 00:00:00 2001 From: Niklas Schandry Date: Thu, 19 Feb 2026 09:45:53 +0100 Subject: [PATCH 145/162] sort out assembler arg approach update test --- conf/modules/assembly.config | 5 +-- docs/usage.md | 36 ++++++++++--------- modules/local/gfa2fa/environment.yml | 8 +---- modules/local/gfa2fa/main.nf | 1 - .../main.nf | 16 +++------ tests/.nftignore | 2 +- tests/default.nf.test.snap | 5 +-- 7 files changed, 31 insertions(+), 42 deletions(-) diff --git a/conf/modules/assembly.config b/conf/modules/assembly.config index b12a4c96..1c3885a8 100644 --- a/conf/modules/assembly.config +++ b/conf/modules/assembly.config @@ -9,6 +9,7 @@ process { (reads ==~ ".*${meta.id}_ont.*" ? meta.assembler_ont_args : meta.assembler_hifi_args) : (meta.assembler_ont == "flye") ? meta.assembler_ont_args : "", (meta.assembler_hifi == "flye") ? meta.assembler_hifi_args : "", + meta.flye_args ].join(" ").trim() } publishDir = [ @@ -17,14 +18,14 @@ process { ] } withName: HIFIASM { - ext.args = { [ meta.assembler_hifi_args ].join(" ").trim() } + ext.args = { [ meta.assembler_hifi_args, meta.hifiasm_args ].join(" ").trim() } publishDir = [ path: { "${params.outdir}/${meta.id}/assembly/hifiasm/" }, mode: params.publish_dir_mode ] } withName: HIFIASM_ONT { - ext.args = { [ meta.assembler_ont_args, "--ont" ].join(" ").trim() } + ext.args = { [ meta.assembler_ont_args, meta.hifiasm_args, "--ont" ].join(" ").trim() } publishDir = [ path: { "${params.outdir}/${meta.id}/assembly/hifiasm_ont/" }, mode: params.publish_dir_mode diff --git a/docs/usage.md b/docs/usage.md index c1b8b37a..8ed4b9ca 100644 --- a/docs/usage.md +++ b/docs/usage.md @@ -284,7 +284,11 @@ Options controlling pipeline behavior ## Assembly options -Options controlling assembly +Options controlling assembly. + +> NOTE: hifiasm_args and flye_args will be passed to the respective assembler in all cases. If the same assembler is used in different strategies, these need may need to be parameterised per sample. + +The difference between `{hifiasm,flye}_args` and `assembler_{ont,hifi}_args` is subtle: the former will be applied for all cases where this particular assembler is used, whereas the latter will apply the args to the assembler used for assembling a specific type of data. `{hifiasm,flye}_args` are generally expected to be used for e.g. system specific configuration via `params`, although they can also be set per-sample, whereas `assembler_{ont,hifi}_args` provide a bit more of an abstract interface, possibly more appropriate to adjust certain parameters per-sample. | Parameter | Description | Type | | ------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------- | @@ -298,8 +302,8 @@ Options controlling assembly | `flye_mode` | flye mode | `string` | | `flye_args` | additional args for flye | `string` | | `hifiasm_args` | Extra arguments passed to `hifiasm` | `string` | -| `assembler_ont_args` | Extra arguments passed to assembler_ont; assembling ONT reads in `scaffold` strategy | `string` | -| `assembler_hifi_args` | Extra arguments passed to assembler_hifi; assembling HiFi reads in `scaffold` strategy | `string` | +| `assembler_ont_args` | Extra arguments passed to assembler_ont, assembling ONT reads and hybrid assemblies | `string` | +| `assembler_hifi_args` | Extra arguments passed to assembler_hifi; assembling HiFi reads | `string` | ## Long-read preprocessing @@ -307,22 +311,22 @@ All long-reads will be passed to `fastplong` for trimming and quality control. If reads should not be modified by `fastplong`, adaptor trimming can be disabled using `-A`, quality filtering can be disabled with `-Q`. These arguments can be passed via `_fastplot_args` for the different read types. -| Parameter | Description | Type | -| --------------------- | --------------------------------------------------------- | --------- | -| `ontreads` | Path to ONT reads | `string` | -| `ont_collect` | Collect ONT reads from several files? | `boolean` | -| `ont_adapters` | Adaptors for ONT read-trimming | `string` | -| `ont_fastplong_args` | Additional args to be passed to `fastplong` for ONT reads | `string` | -| `hifireads` | Path to HiFi reads | `string` | -| `hifi_adapters` | Adaptors for HiFi read-trimming | `string` | -| `hifi_fastplong_args` | Additional args to be passed to fastplong for HiFi reads | `string` | -| `jellyfish` | Run jellyfish and genomescope (recommended) | `boolean` | -| `jellyfish_k` | Value of k used during k-mer analysis with jellyfish | `integer` | -| `dump` | dump jellyfish output | `boolean` | +| Parameter | Description | Type | +| --------------------- | ---------------------------------------------------------- | --------- | +| `ontreads` | Path to ONT reads | `string` | +| `ont_collect` | Collect ONT reads from several files? | `boolean` | +| `ont_adapters` | Adaptors for ONT read-trimming | `string` | +| `ont_fastplong_args` | Additional args to be passed to `fastplong` for ONT reads | `string` | +| `hifireads` | Path to HiFi reads | `string` | +| `hifi_adapters` | Adaptors for HiFi read-trimming | `string` | +| `hifi_fastplong_args` | Additional args to be passed to `fastplong` for HiFi reads | `string` | +| `jellyfish` | Run jellyfish and genomescope (recommended) | `boolean` | +| `jellyfish_k` | Value of k used during k-mer analysis with jellyfish | `integer` | +| `dump` | dump jellyfish output | `boolean` | ## Short read options -Options for short reads +Options for short reads. | Parameter | Description | Type | | ----------------- | ------------------------------- | --------- | diff --git a/modules/local/gfa2fa/environment.yml b/modules/local/gfa2fa/environment.yml index 2e1fcd06..dd58a0bd 100644 --- a/modules/local/gfa2fa/environment.yml +++ b/modules/local/gfa2fa/environment.yml @@ -2,10 +2,4 @@ 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 - - bioconda::mawk=1.3.4 + - conda-forge::samtools=1.22.1 diff --git a/modules/local/gfa2fa/main.nf b/modules/local/gfa2fa/main.nf index 8ef54bf6..dc11c08e 100644 --- a/modules/local/gfa2fa/main.nf +++ b/modules/local/gfa2fa/main.nf @@ -11,7 +11,6 @@ process GFA_2_FA { output: tuple val(meta), path("*fa.gz"), emit: contigs_fasta - tuple val("${task.process}"), val('awk'), eval("awk |& head -n1 | grep -o 'v[0-9\\.]*' | sed 's/v//'"), emit: versions_awk, topic: versions tuple val("${task.process}"), val('bgzip'), eval("bgzip --version | head -n1 | sed 's/bgzip (htslib) //'"), emit: versions_gzip, topic: versions script: diff --git a/subworkflows/local/utils_nfcore_genomeassembler_pipeline/main.nf b/subworkflows/local/utils_nfcore_genomeassembler_pipeline/main.nf index 6a9d5850..c452d4a4 100644 --- a/subworkflows/local/utils_nfcore_genomeassembler_pipeline/main.nf +++ b/subworkflows/local/utils_nfcore_genomeassembler_pipeline/main.nf @@ -122,7 +122,7 @@ workflow PIPELINE_INITIALISATION { assembler.contains("_") ? assembler.tokenize("_")[1] : null - def polish = it.polish ? it.polish : + def polish = it.polish ?: (params.polish_medaka && params.polish_dorado) ? error("Both polish_medaka and polish_dorado are set.") : (params.polish_medaka && params.polish_pilon && ontreads) ? "medaka+pilon" : (params.polish_dorado && params.polish_pilon && ontreads) ? "dorado+pilon" : @@ -162,20 +162,14 @@ workflow PIPELINE_INITIALISATION { // new in refactor-assemblers strategy: strategy, // The "assembler" value is mainly to ease input, all actual workflow logic should use assembler_ont/_hifi. - // Could still be useful for debugging. assembler: assembler, - // assembler_ont: ONT, assembler_hifi: HiFi assembler_ont: assembler_ont, assembler_hifi: assembler_hifi, assembly_scaffolding_order: it.assembly_scaffolding_order ?: params.assembly_scaffolding_order ?: "ont_on_hifi", - assembler_ont_args: assembler_ont_args + " " + - ((assembler == "flye" && strategy == "single") || (assembler_ont == "flye") ? (it.flye_args ?: params.flye_args) : - (assembler == "hifiasm" && strategy == "single") || (assembler_ont == "hifiasm") ? (it.hifiasm_args ?: params.hifiasm_args) : - ""), - assembler_hifi_args: assembler_hifi_args + " " + - ((assembler == "flye" && strategy == "single") || (assembler_hifi == "hifiasm") ? (it.hifiasm_args ?: params.hifiasm_args) : - (assembler == "flye" && strategy == "single") || (assembler_hifi == "flye") ? (it.flye_args ?: params.flye_args) : - ""), + assembler_ont_args: assembler_ont_args, + assembler_hifi_args: assembler_hifi_args, + hifiasm_args: it.hifiasm_args ?: params.hifiasm_args, + flye_args: it.flye_args ?: params.flye_args, polish: polish, ont_collect: it.ont_collect ?: params.ont_collect, ont_adapters: it.ont_adapters ?: params.ont_adapters, diff --git a/tests/.nftignore b/tests/.nftignore index 3b433e52..07e46d3b 100644 --- a/tests/.nftignore +++ b/tests/.nftignore @@ -7,4 +7,4 @@ pipeline_info/*.{html,json,txt,yml} */*.{log,bin,gz,gff3,agp,html,gz} *.{log,bin,gz,gff3,agp,html,gz} */*/*/*.assembly_info.txt -*tigmint-ntLink-arks.fa +*/scaffold/longstitch/*tigmint-ntLink-arks.fa diff --git a/tests/default.nf.test.snap b/tests/default.nf.test.snap index 10bb385d..613aea67 100644 --- a/tests/default.nf.test.snap +++ b/tests/default.nf.test.snap @@ -16,11 +16,9 @@ "flye": "2.9.5-b1801" }, "GFA_2_FA_HIFI": { - "awk": "1.36.1", "bgzip": "1.22.1" }, "GFA_2_FA_ONT": { - "awk": "1.36.1", "bgzip": "1.22.1" }, "HIFIASM": { @@ -325,7 +323,6 @@ "test_hifiasm_ul_assembly.unmapped.txt:md5,d41d8cd98f00b204e9800998ecf8427e", "test_hifiasm_ul_hifi.fastplong.json:md5,f52a2b6d9aa29fbe5749684ee479de90", "test_hifiasm_ul_ont.fastplong.json:md5,86da6d58ac4630bfeae3ee08014ecb0b", - "test_hifiasm_ul_longstitch.tigmint-ntLink-arks.fa:md5,2bc73aa0085ed4ef5261f3fadfc64b46", "test_hifiasm_ul_longstitch.tigmint-ntLink.fa:md5,301136e56bd854239f31e96e32bc49d4", "test_hifiasm_ul_longstitch.unmapped.txt:md5,d41d8cd98f00b204e9800998ecf8427e", "test_ont_flye.params.json:md5,afa91c041bce5e190f4a699d11b69db6", @@ -344,7 +341,7 @@ "test_ont_hifiasm_ragtag.stats:md5,466273b499384b2c781c83dc583b8c99" ] ], - "timestamp": "2026-02-18T16:47:20.810744058", + "timestamp": "2026-02-19T09:41:46.416703767", "meta": { "nf-test": "0.9.4", "nextflow": "25.10.0" From b5949d3a5847ee2df10b8717a50e8ebafd31afe8 Mon Sep 17 00:00:00 2001 From: Niklas Schandry Date: Thu, 19 Feb 2026 10:12:24 +0100 Subject: [PATCH 146/162] exclude unstable files --- tests/.nftignore | 6 ++++++ tests/default.nf.test.snap | 7 +------ 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/tests/.nftignore b/tests/.nftignore index 07e46d3b..c31dce4d 100644 --- a/tests/.nftignore +++ b/tests/.nftignore @@ -8,3 +8,9 @@ pipeline_info/*.{html,json,txt,yml} *.{log,bin,gz,gff3,agp,html,gz} */*/*/*.assembly_info.txt */scaffold/longstitch/*tigmint-ntLink-arks.fa +test_flye_hifiasm/assembly/ragtag/test_flye_hifiasm_assembly_patch.comps.fasta +test_flye_hifiasm/assembly/ragtag/test_flye_hifiasm_assembly_patch.ctg.fasta +test_flye_hifiasm/assembly/ragtag/test_flye_hifiasm_assembly_patch.patch.fasta +test_flye_hifiasm/assembly/ragtag/test_flye_hifiasm_assembly_patch.patch.fasta +test_flye_hifiasm/scaffold/ragtag/test_flye_hifiasm_ragtag.fasta +test_flye_hifiasm/scaffold/ragtag/test_flye_hifiasm_ragtag.stats diff --git a/tests/default.nf.test.snap b/tests/default.nf.test.snap index 613aea67..67008918 100644 --- a/tests/default.nf.test.snap +++ b/tests/default.nf.test.snap @@ -281,16 +281,11 @@ "test_flye_hifiasm.bp.p_ctg.gfa:md5,8fe65466d76815ffe1663ff6d8f2e8d1", "test_flye_hifiasm.bp.p_utg.gfa:md5,ba2c77ebdb2ad3e6060f5574e890c6eb", "test_flye_hifiasm.bp.r_utg.gfa:md5,ba2c77ebdb2ad3e6060f5574e890c6eb", - "test_flye_hifiasm_assembly_patch.comps.fasta:md5,c29d28dd1a028a4cc1aa4d5c0f584598", - "test_flye_hifiasm_assembly_patch.ctg.fasta:md5,31482a63bf6c58c88cdcb2df5ca65440", "test_flye_hifiasm_assembly_patch.patch.err:md5,d41d8cd98f00b204e9800998ecf8427e", - "test_flye_hifiasm_assembly_patch.patch.fasta:md5,31482a63bf6c58c88cdcb2df5ca65440", "test_flye_hifiasm_assembly_patch.rename.fasta:md5,0515edc4c23258ef17d6ba085a6e4d31", "test_flye_hifiasm_assembly.unmapped.txt:md5,d41d8cd98f00b204e9800998ecf8427e", "test_flye_hifiasm_hifi.fastplong.json:md5,0e20fedb8dfe0646232060da502883ee", "test_flye_hifiasm_ont.fastplong.json:md5,826cc5a321dd130a2f2c53cdb1a345b2", - "test_flye_hifiasm_ragtag.fasta:md5,3a03ddeda9a82c1e7d5a7e252313a546", - "test_flye_hifiasm_ragtag.stats:md5,f9712f1a0af0669e12d74ef97181c428", "test_hifi_flye_hifi.fastplong.json:md5,daad59b32b84b79c29c274ed20f03cce", "test_hifi_flye_ont.fastplong.json:md5,198eb762846548340557f5f861f10c11", "test_hifi_hifiasm.bp.hap1.p_ctg.gfa:md5,46ee70869884ad585165bd48081414e9", @@ -341,7 +336,7 @@ "test_ont_hifiasm_ragtag.stats:md5,466273b499384b2c781c83dc583b8c99" ] ], - "timestamp": "2026-02-19T09:41:46.416703767", + "timestamp": "2026-02-19T10:11:41.761287489", "meta": { "nf-test": "0.9.4", "nextflow": "25.10.0" From 4cf59b84fb83b13edf119e4c98bfcbbc128813ad Mon Sep 17 00:00:00 2001 From: Niklas Schandry Date: Thu, 19 Feb 2026 11:12:11 +0100 Subject: [PATCH 147/162] new metromap --- docs/images/genomeassembler.dark.png | Bin 584725 -> 0 bytes docs/images/genomeassembler.light.png | Bin 572615 -> 0 bytes docs/images/genomeassembler_v2.light.png | Bin 0 -> 616476 bytes docs/usage.md | 2 +- 4 files changed, 1 insertion(+), 1 deletion(-) delete mode 100644 docs/images/genomeassembler.dark.png delete mode 100644 docs/images/genomeassembler.light.png create mode 100644 docs/images/genomeassembler_v2.light.png diff --git a/docs/images/genomeassembler.dark.png b/docs/images/genomeassembler.dark.png deleted file mode 100644 index 4b5721109cf86f4156a647a7bc45435e8784d644..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 584725 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'I!|Jf@)trW0F>q46&KN!XP; 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 ?yQOr=BS@ZQ$m>65t2IgA^lBl!{d!h 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 z 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

+@+5hgfm0?u;JN3s;3W`l=ZoRBCNucpV2 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ⅆ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�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 Q@W7XIT-e|tCn*N6YKnxnM(qm29EPfIcx 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%0Dw%s3vzCF}>tkUEhdMRA+#O_gP#yv<)4G4a;CH%; zeVMK{gA}P}K6RGdvq{7KIuQ5Qbzi0JY{b;RcVEYMvFnf~*N-@+b2XvtG z+xHrgab`hd`$JDp&;16e=L?`H`3+17deE%U;%CwnNmu{c$I89WfJ!{#C%T*5=|0}!X%M`V4n|SvK{KAK=h11 zJg%hw$&AwQ`F~(ElOT%I@C5dVKK()S_@pS1GJ#^LwSIjN1-^uQFk3SZK7EW}P?Rue zaH2N*bfE)gtSE3~GAL!}gFuGfcng5+&ZMLu9-iKCPxQ!{Wg1n-XZ36+HHAg-PTa+V zvzhcXz84ADGd}_H2e!a;C4JPdT`klqd-mepyX!iV)74J7=P5ysLrJ(KG3R^?L-SN$; zK(`Shdkb`pYr%ysJimZlgc~XE`9?9Jh(6nscD~B%K>Xp0}zK!?OP9MBe2Np*jVX<>V+NNHevcCW4IJ z%yz&%U*48@&K)%7N=iwI#Ioq@Wv#@EdpFoGN(+RQAHo3cCDN2?b z%P|TfUInMkbSRKJ(Gc<( zd0YiluA#wJi7|9zkF`b+0kC|J2CM}b&n+)_bQ_=`(XcskrY4PoGe8_85g0x_*qoHt zTm##BcyOwty?qU@6H-M2ac0iCB&G*mEs#PF@9OFrRCEZj^#ZAP3Xtdpf}d^XdNeOn zV$7bsOd~#_e$*GomRX$?JWj1W8X6O`U7xlb6Dv_$FZs+GuK?gJT2rde9eMbU`+UyX zK<8}wg$I~Jx8D$=2TT(Nl%O^PVKY7gkf4I|uuI}=^gF76&~R+r_fwRuacvS!RnCeZ zaA9=<^6%~=0&V99e8zbaNCq?@rhpeTON`MyzbSFs4`_W07#7#oY9H!R-Gd*_?O=Msxis}7HYycg9k1NK>0X- zhqMBnW7%Ion(Czp0n-zNY|7ifG8{qLHaJk92XKj+5zh>60V?TFAUlUmq5)V~>M&mb zE8dLIIn+sgjHoN9N(gJKI6FC90Hbu;L7e`WxfdFSLXxm3j*T~DtPSv0s7Y*(^mZbloNh^%_4)-A=sExn4|OOF%!euuJXLyh7vd$3(#lw3UXjI z`72yr5X;(6@-orsFdALg#(NB=VWvqOOGlC%h&_pxJW=Wf+A>r~Wx*d6L9f&;yoRtF z90M2DQ7Xb~l3F}U8gSDCV)J{e`4&kv+6e-9!L%|W>m&K{UDv7Nt^j?{{ZK?k)jaOa z&^X;7Q&Fx|reszVDU01?)s8`Xw?! zR+6nR0sc}!4gw6YlTWOp?xA2ufWq5T@EL*EIQXa$8-N$dnB5p{Hh|FlpI?`J^{ak3 z^J`b6z$p_1iZpFxJX%U(rY{4*-eLUdRYs&s#r2@Q6L-0H$UrKVWPB_KDjKv#gAd%7!h(A7G zOv&)kF%d7RHv#^Mj{rtxDS26^vuqJ>y7d+T10@UW;=%61w9uF0=96?$p-LYLM})Bei2aOv022cxtg*@7?1Tst-itqL zEz-newkzK9&t8s!G|&1Szlms{Q893j!aFeiOI6~4>+i8-Pw@P55MTG#<#E=|*P zzO71RFnOXnWjvUiO%)GT*9sBnq3|UV`E}AGP=|#WO{w-1Ja@q9x&ITw4>C|3qXor$ zWvD_iJlNeNj*R$-JAk1q)!>%HwO==dO95>_yG(?KP2`q>3ppp1%Z+4-;;n>Avxs zBAw!Q=?f(h)4X4YfKSa6wohz?)HSs%F;Z8?-0%#kP3U|fq1HdzfYroNtjLWQtk6#{0`Yq zpaOP@?s3jKv5?^W@4bKkkOme|UTOB5LX?sWt(8O16+1%MnTE6~F$@$M!oaxKkz|8n zEDV&Nk73gHTL~-lUdm9l0)Xzt&`0@df@31}QB8ef@WDS>#j+oN#f}qd z2pxbP07&>y&3^ewFUkqjfpit@OXoQAa5h5>@;gD!!ilq?OBl9XnX}}@>we+D==^*} zKOm~3eC1lfV`w%0FXyg1_Ff0iGR#OSs3Vj#AX_O@bAXGNXzCY!zIC*`0`BLb1Z0}s zo4vRk9h#HC4^p7o-Aa;PI146NFcay`JL>J^zx(!{hd__}>*Za z%WupsSXO;$%k@(2XW1X(cOv{>cm;|=l9+I(T~Z$V+d!Z{&x|&QO|(hlri|uJ*d^1z zhFBxvlHmsWj)B>%zyf9ydUXFbn_G$iPwwienWRNz7#Xk*JGg}j@5)FHg#IIBA)GH$ z0eivVT*})@&qjMQi;8fesXW9?>b{_di%~PuFTp^1vypVb-vvldiLYLtQq z6gI}X%l31Z`}_v9&u!C)@X?78txOr7q&=~POQO9o3Ro=AbH4mwZv*2$-VXunNoM5B z52gXH*k71{(TktcWny`mA2e&+vW(w(3Ed|MQt*r~X`@+*kOQ=8Th}h2g3kLB(Z-GX z+zc*Fu%o_3H?Fx5Gdm-&~DBCIQaKbr2KUoA0Jvq+S94 zzK0T1f8>^$R{2Q$t_KCT!R_S4929A-U*`@)1TwxPvK$#Pz{Tq=6T0i?)Qln zgE%7`OVLP@XCW~E(tS?w?MMQF``26g5I1L0`qJuiKs$@0?(^kZ|NL8Z(2VPI4~8gv zQuhpJN^`i9%|~F3ZP?iygjqkgGC0$bmn$L-uABC9MZhWke_D}d8LIWx3BS@v8iviR zg{;z{$S09sgI`7rOY1C#S9ZAuvGuP8=R5MiRP4ghSh4d{9*p2AE!tPkGu{l(xgM08 zGZ49AYsTUdN|TW@F!w6ok^2j4co&dDT9W)sz9lV@Ueh&~WzT)rXwfuU)S=ZpzNSlo z_F~H!{DfcVgL!<*rn7PtM-ZKmQ4V>rW**&s&KK*TC1=-(R!JQ*#g5JVs}-USX4t`a z$}dTSrmv7=%R2CZScHh*dCKr=dv^R5lY z@)69zhrTVFfyl>w@fR#D_IwBx$kyNcH+@}|t<;YD$qh#>C_6Sg``%vYYyeT2u@}$x z80uHgLa>6XZg{O;UwPVzt*X1XYSkh`-Z??E zb*21K4S!;kkJ04@{1rS?`Mt-rHCk&-kPyTtoMivap8zLRLDJXXAxtVA#AiGM^$UbB z8msn=g?p%`Cp{d1XCX*%#|{yG*G%pZBXXQ!?ZMSQCje|`^}t4PWcpB;DXGm0`k&vZ z!=UX;Al6iMKHL>GF>=BQ?Wjv*feF{fxQ#oG^2NF$2|-bV!A^wH9#Mi3A=W1H)6_kR zV9_IweR*T?^arueZai}_AtCRY2{H-_M{BFUzR6onDUD4oA~LNTMahh%Da8Jd+ysc| zaPRxMflH~qqtD3xPihDVXw>h&Nf~}sN}bid05Ub@(>E$oX8A$;?^9!AR4i3cMY<2l zMC_m74<$xTJE|dfHBb| zWn@G_>KB=m6b{%cDG*g;BtPMU$Hax{Ha`L^t6cEBk61lMmqeK`+aJ9-MI9_H|Kc8CGjJR)T5piKRs1!IiP<@YU4ND8dP9-pnl-k%Qt; zgnT!O40xm$zbIakF@=TQte{7{ILh*b>^h^3F@>DUlH|pDW%l}e*57*sG6n?b>b6Gn z@9MV`^k4o)*??7|G6b>3h$SdD&nzeTJN%d;p;oYs(E(X)<3PGhe~wD-w@E_xG>}{L z1PCL|NzH=7OB$1t+k0D6WPoMkSoYQiRc>jLCSKMo=XxGT zbZku6(Ah!Oa>{A*dsDmQ7ENVKlBB~-suNd}&j)S3s^^F;vD6#u4w zRS3kBEjIEi3SaTT!)yUPvcUl7LSbNF$Otf-_{b25ee>0NVGTA`UYWiWGeX{Zdhfwv z6n&G%q>xpE05#9Lq-LYVE{6md><6Z;+KJ3kSA$=!f6I$;8F9()XL3b@bqi{pL z0c(;aQi6&wO!$P;S6HVboW{I<{2x~eUUWtJ(yr~aveq>)caWN0=I1|uox=p}kNou6 z4R?4tJk`^n4nW3tJrKnvnjyXLP;wsp@86fild9PA(3>(R zXJYv)XnF{-cF{OcmldxA6ej>3ZmAL>oXG10b;y1IIo)DO`Mb%JS~vAkO!Hf+%rm2+CKYR{R~!u*wRv9uiC`ofWyO`vwWSMXaEjOgAyHf#W z&WI1EI&ZLtr?p5-fO4W_QXH34#uk9g?A^R_TRfOj+r9q~?0@==g_oba2#_dx|2n6_ zNSM&p2oP=}9jkJnZx6rO-3{8%AR=t3X&w|`1X3ZzDRo1Q+QUhoDx^h8+Y zHpG!ka9vL}D~dsdk?nDGZkT!MR_lsL%UKeQ8S9vdXb#QJ59L?O@gpLAh=SJbT{M%Jorx37Oi)VFc@!NuJOB!qnslaPuc)Ow2hV=2Mr zaiJSIwts20N5B=Fc2Lj7B_ksvCA;w`7syLIN)S=YVfO>vH2LgtOOssPI$pVC!F>K& zqcY82_OLi3I-{o-2HxY>l&`^g;uas<>1eBeb$|;m@a#*n_LCvas<#)u7Cl-J&p!bk4;qiiBXqxDs#1!D)H3o+&RFM1EMIcn|#h1Fe40 zplOA?N>pg)p?}bAjLjOK;NHc+PD1j7xKxGylnBO#ov6Ij$?e+wjdv{auU#x0D=et* zLB(1%!&WUY?)TSk8t+5T0LY#DfhsYnApeH?5eAsnax?W&*aN3qb9CI@^I(+7H$V~n z0Ca~l3ZmONNfi2gTPiKU-d_{1#UwX`otirt-sO~W!P5DOt2HR`Z(|sWU*u_0I`_%i=){tp*Z=Hazk}rN=EOj8 zptQIuy-=8K7ULiNzrRC1NaJrW5p+v2 z?&~do?D5B93L`l~PKw^opAB_cvW8Xc%`B4#qk9q#veIsBxg#y~%k+FNvYrIO2uP9TjTZTo!CDLi%&yA{$ z!klfBMF*bB=$OPw445YLe?eWY-LqUKr%o#Uhn)&e@jF@Hho$3Ue`j{J&=TJgZK^s& zUH(ydL(=Rs3TuBq=F#8_RPH2aq$AW!w`f%ai0h%!`sW;LAr8?PNn^qzi~F>VS1=%| ze&zm%)>*vk*Um_w_e59iyty{Y?ZYc$wG63T1Np68)9bn(W(>R)t1Awn!A);;d97wx z#S!3rUHcZ;G>IxrN6OOG0?*uSUPT&s@8dO9slIiGPxQS(gN6OQ%axI8m>O@8SqdSr zYT>{2IEuyZR8}(ss`bB9RcZ)f>h)H#Wua^uH!PkFZI`vv+0MQHY<543SgUvO?xf2P z1QQmo>pnLHu4}02UC-lGL^Tze`FZ58fxcX&hEAMf>H*0O$nA2_Yg*7&a_b zx9Yk+X8D@ON>!anwR4>_x7VNye6)Z%A}fm zYDH;Ln$l@P;Ght5CzdCzVK8tfsZQQNd8!unFW~lx3F($;i&v^ z_}Rm7Y<3jHHvS=TLe?FyL7soFIORi5S(Gy8OC=1P#Lm%e&sHEfil zan)X4%R)@8A}KuIAYX^M@7TrLFW*@^qs&g4*}`O>Zlv}2B=5SQ_PZ_~{eMbHAksxw zf~*9YXw$fMlLmDHLEm~PJzcNJ*~EUWE>bkeZOY07*R^3YJN#l2&>8x><>n6b(mZ|9={<1D4jf;z+4AUZC&#hVGIrUByTeJRedF608_ zEsO3&2_=WotgjG5PBV|DCbh$h4Mi=Z;f1xc^O?>pPS~3>nlmb*IT*1Vch0Ne#ub}u zH4huH-u4&>yz?-==4&Wo6#d4)P(ahWm{7~$B`^KC5o&-*5e2=C!+4e1kCUCLCUrXr z%uI&KS@qcX4{EzfdkSX*_KJtPH4$CdGCsXYUDx;dvt(2bg-Uvy4O zlLe0Yye>XkC~aJ-4jRCAc=Q&TWLDwZ`qRaw{ zjH=JZK0v7(KhfWQ|L@mC&~`Jqa)&#C9uSc{_KlIh&*rn?8mnnX#l$7PFqffdz31Vq z`YLL0n&k;5eA-4QTE{jfnwH(ne|g&jpE*8Jr~l9lE)VVJA6v1M_ucx>0wqC=`j`O^ zm&FPU4u8k*R&vWFhDGNUg@F5YZ~`DDpk4OAWB0#bE3-p8>Ly5Gk}e$x*|9E&y0sgg z3fe!Q&Dt9u&Q+19({#dB%jYfGwU3N``=U7|<$XcwGuC1=^+G>G8UaRR5F9vdMrY14 zm9LNF1x#>Q+~N|#i5rg-DeRy0&yY_JhbFNUj3#5Nh}g#LOFso2JM>OlTYWn^$L-1y z;{B`5z8yi`jg{^kk;#z+EL)#(@PoOL`f5+8r%!jhrkq^XhA;rb?9s!A{?;>3oPhZ7 zeV{8DoXF?0CdUJk_{WL7DixG`l>kfWA-L=W{d>uOy?)XU*1^IVfQq(&;_NuOUv-0| zd^30@%j_dxW;8xSj+JTx7T>{!*m3sbGFt*WBil>?GMEXt|894GsE56_Yz>+G z@zG?e4Rw->^&}oL%T`zjAKl&hs){`&(jVrd*ln5HE|wfoswyNX2$=AC1}Vq$uVfuG zZk=I|Y-3gjogP!BZSnwHICNj>(=c6V7XD^F}re$$^C>!+%-HbyeYK*?X?IMdhW=iS2eLc&6GZ8s}C}V z3b#h;uWnJeUD4#SG4G^YbzB)U;NKsy7bm=T7n`LTwCAyI1bArsN!T>tfVto8Xx#2E zfpF?+54pG}aew)JHKAA2D`IQpoVl`iiEZ8>he>V0+9v>nR$Qe%x^G|ntgV4 z1~eUXnD0Ym%y(wRyiw5>8>i_gkIr-!gjZ+y2E$fNEG+HKL^9u3D{{NiK3G4=q}ZbL z;5ycEtkR1e^cnFCq}}E(3}ppe$)Q=A6P%Vbyo#EkO3t-+X%pUi(e%Y8>lJ}w=r+y_ z++?)aj@Ax>F^>}>s>{}{Q=}2W$^sr>!=8vu_B6=fC|}G^&E(k`D2c3|q1e}sqV$xu z$)C;Jr#W%WV5^?c%)1weTBiY@!h0EF&f(5 zLgqKRT7`8N=O2hd6pPUI9aOadaYwX?nfP+ z6NvUNK1;p&@ykqFnZ4%%wOmhLzP>p7lf7(yQO+s8)!xYRnaF_?$ytWFIt+NuJ!;Ne z^E&b8CLjX4AJJGb$+fW6R_cUBM@wq}@Wbk}yE&qOsRm7cn_8vackW9Pa5^a#Wq?dP zG>Ygyzy2}tK@GE~qx}J|hsNLdYd)j&un3uPqu4&%HZtv6eU28!dI7)EpFV|35ft1j z+JTcg9LL->^isEK>nfAGMb!g%DEe|~LedL*dLe&K!2vT$o;ZJkReBKOGz+Y!=ZQJN<*Qf1*C&RZ@AFD{n-s7u zmNzie?E`>J=#7b%>?ZSS1(eA8(@reRzCtF)&WF}sw)S%S=a#E<@h=S{2r_2 zSkWu{Qbo8-Qiv;gg?M6KxqB$ibJlA52ysvbIW*5W?;n&qg0K(Q@^&!fx@*{ zH?im+>E141SN1qtG_Sllg_>L-&i@q2Lr2R>db7|+JK`Q^K|8R5k@0a$gs)SXh+)yT zbnVwD{ML`PRhCks>~{6ELi5Ny!p6h%Cl=yXcNH3(1!C*pym3D|xoG{cAQ3e*q$pG( zb@oV-zmU!Ph9mMs;vibito9RSbi4wK;jEWgS!UN(rpdVU_Hjr&6#0(hT=ZHGbQ+q` zYS5XSV$$WDrdK^_2i#kUMD6-y3A%qUnU{#)xmY_X>+J1QnU3X+d9W-S5NI^ixUSPK zd2tde^lIDrRI-VuwZfRk2TVRdHVO>=##0L5V2iZ-+aF6u8~_Zw)rAyCv+$lK{qU9% zLmik9TZYZpygN+@la?CQxxxp|f-Bv(!h6$g1BbdJZ8SKz0%srV@8?@&!FU06z2&u! zQpRHufDiDvDRA^E+7`s0bgBHE%Kte`!p0Cut5gfX^aQZ7D?*%fkMN}EBPypB-MQcG zaAgSX;vP`)%GMz28X=q;C9uptDJJ?qcC{i+Dx==eyl^ zxE=N9nVJne&%UNo~d>9IfCOCMB3kPuV4@eJ9_d~DC>6f$@J%QmNQGv zgk$q=p86+ci^619rQ;oU)1@t6gzFjR?Ua5*YHe-3<3M<~;<$NWhDa|D?#NWWq8@Y) zH8?E1IQ!y3Yx|hx9b6gP(6R=jQr=_6Z3NCZ2{*Z7VG4Vun}vIP`L!y{bgIR)4N{a& z9+qwC-pV2QCQ9zf&U;s0M|h@Nl+8cux_*@J&e8FXzwrhmNV=5^ReNJHm0LU2yknBT zj&k>sk31W;5-!+DF4Kh3(aN&qiGv(?I7`=3iGr;=GTCCx*Yauix*dmQ7fxrOinsH} z_!n*EOdWk?&srG^O-+nH1w_IH<2uFi{Rd`3EQrTvUD5KgOsvTC?^P&lJKjtT`eIJO#ZtUg-??yy?+=Ohc39I zGbKh~hWh-&XZmv-5HO+`ft|il3kqqXwgn0Ta`oDx?y7-1=|!HOlvCZYhCpOn?>c={}YuUkyg$#-<`bd7k z1hB#y(h9w9bW?(GY*{d`{x($~XPTz3GMS}0r$VQJf_H^_(V*hf@z2KElqx3$uhy62 z|08*%vIJDY5lbLH`3)B~%pAElT33D0D1@e+Ib`W<(=iafuujSO-f7nX4CWXOHpri4 zT1eyE{gn#~=!U+TKS`kEn!Rv7^S|lg?T|5l;Jk=N%`J0dNpQ-2z8IradreHsinGUS zk-@XryiB5hGIrK;W=EoUb(tCQs!QG+`Bz2&ryfn{CBTTCw z8;y_~Du;;)mhZmrCO5%^$N3~Hr{t8L%e}}80hnJSmtU|CxhUS0j5bK9wl~ek@V}ALm z&O1UFP5Z%r1R~D?FTfPb9uqzX8r43U6|zWGn(m3EC)rc=g6ykv$)n?OQ?%5XQ?FFi z_gD93T4HC;a2T>X1YT7kVgMXNx^DGK`}F2~K#vQEUJNEFg*;#(c<^d1;sNY8&vXV# zLAF!CXHx`#+U_@UTIa3=rU8kfibEM&d>K7+`6Kp4AR}Rv7FhOTc+Ea-38IR2j6Cf_ zR0JBkUV=!jK4$MRX8;#PHoyf zib}T|Vq;f7R)HT(ICEFrB>u9G3%4BTd99J1dpgkNGZ$bZC?cm@(EUyrW%=F8_0SEW zYlDxbRt*{1_^v9E^eIy33V6#dr3M0`Cze&Zc_RJ|_NEfjtDo2M9I(c(iCsI07MvVR z^Xs|gY%gcRBOX2fFnuR;lo*)xj6Pz67vRYQ_VSGNOlS>B-^fi^RMhh@GOjf!GO2Ul zZw8%cj+s6t$DVt&-xOoLleQe(%*Q3CY<#FP@95)6K^~4_ zNGyLi(0q!N-s77mTJpFEc+n^@Q=@NDJjVI&YSFLbBMcD>V8{R)W#kUI^Cf)~)z!X> z=C$KQ6T3^32G0iZ)d_Z+mnR+5VQ?!rm*x4mi(Ye0u8`uhN4d$WpM8a7|e@Z}Hf5F&C5u@!Z4*73CJNeSd)_HyQ4IKAB$!>2K?I-6^+ z2PY~W#gdbZg<_Oa6?t?E)bj1Xkio#4^$<}SQVZFaIk!5S+rMDI2{uoNJb~@?_4PPm zw!EmybXvap6uXSSF*|kDdps6ieW(~D{)zXNLLW8%a)!x~01i|ZUBT~sRieJN`XHl^ z@qg-Z|EydiF653Lg9t~}miU(INTvDp&vg;qbI~_Cc6D{0J6%*@h_@9)ia#)E-d~7P z9-df|u~j|!L^9EPgYLbHA$~z+B?(kD*-z2<){dX2eL?#wBqUnU4;}u%L^9&s^~Zq+ zUi#64sb{4I=N9q{gEklI3Qxdfvkx8`RgQ1G;<-;bzjzJ{A(d>)-u`rYl17bSO3sb< z8U$lD4wA1l#IKJhBS{I6J>I2z6O5ZO;)6}McJKNOcMNDwZ|rrTcVn)3uX6$9DT-Xb z&lib403z>98YN<*g;zCQ8p#}DVpmO-ZAY+sxLqGs^_fuIJ(-Q8mpDzN5u)ihqKe?> zf0{-Lvd;3`WG{{zUq8);K=1pJzHKFU>>ek_(P^eJ{D)x*R)@fFE%~M@L)2w}!YmZ? znSAi*5R1LKle;~t-$d3ot8A^&pXXk-R=Iv`cU{2sdG~d2I@jp6^4oCjMwJK@Dh#I3 zBq{7^KtdoI$5QCIL#KPTXC-OY>hD}86nDlnEJ3D%dho>qmo0HRYJA_E{l*7YN|v2TMW z%j5%Up08I{pk!5CX*mVtvD)p6OUrYSJ}j*hR%G2`JSX_DZJh)3`jf>gh3ow_9+E@j zSqiCp)kpj1KM{1*hW6)L%g@ndi!9rbKJu=Ad9O`?D2hjYkU_O;`|L@1EmjNeU!$E1`%zeZ& z?}_aL9(U^`o%eHMh#~n+6BK-Zx7y;h2{?4Epm{Zf4bGj~Tns1z&!gdb;0k91dK5UpfSNLQg*cu$legOpWN(}XuItwf9q2VfT8?KrfT zNDL0o7afYfFK;Izl@;!dZNZZz<=duYOy(jQ&Xqk^hpLf@WfOzYoYvg33@V3ya{=mh z$pyU$L1j5OBMiSwOf)wDApSlJ6fj3riuyCb*7nvr9{GTPIy5-~zw>E4nk-BFytI+- z!c?iIn=hQAD`KZ$LXULTQ_HJGF!?No$)|gp#fWer9HY#E=S<0C@*<{xJ~r1U-GO_Q zdSSR~$On(Skio*GHuKn+a+x>CnPquij;NqSoryESi(x(zE4bv73@4+0E`dXs7taCD zSDR?Rm=|5raV^EFE-ZH$PA1cBRT?&YYPQlxmIu5X^5Eb7O)p?uISGZcw)=%qb;n8e z(qGQoRPax0def8mHm#qH$A&Wqxa3%QwD}G=EZ?3J&J%E18-K#t(LdxsAD&~hc|KlM zO@6haTi&>EE_ugb7%2Jcw2SXM7 zd;;e`7TyVXlz(6-*mmGB0dq%-Z9W?o^)BUc`P`+g^ISq8oKwrM*vKv@CG~f3i;}(n zQDj352~U`+%Rjd|boE3Wg(+I*59$0L!Xe@yWJRgcXSIv{OM`FfOz6H(gJE-4?}hbU zgvAQ!en@LPpyMX1ix-PZ+8rR^j+=|eKMH*$`@J=dhr40XO;FYxbU)uQT-x_4Y!cv- zeJ-YQWdcxnr3*H=U#cf_+ff4smqk)Pdc{z_7FTjm!r>&k5S4cQ3<{*Z^TmBVKd`;b zavc9&{SRtDVYJEeadSKd;s>@JH(fw~FLGc{!@*}oN9)0UqZwh;W!PH{IN@m9i-T*l z)0*!cf(1NuP6%PVYZ^VO%h!}`Svq;bKAs&M_a4P9#r|4@NRjuQXxbypRogM0>MUxP zeS80N!j|yRwKpI11r-&GA*LCkn)0CIFY>#8vwd9KG#0y-gdHJ zRn8UTpf3CNS6vGY32AA5aWxVQ3J(|2Q~*)r89Vqe1~U#FBp^BwohF6Pz_(&TF-!Ks-!;2qkBV1p5qQ?luHfu*BiFZ z>O#lz4r1ebxUg<)4XVlemxhwq)m^mJMM*9TQ&$57)rS!%>;Gmj&xZB;UxgrN+}rws zexTgU@UWnt6EMRU{o+B}F4;|d#|8ku^8VY&+RPH%a=F2u#}WGLDtj>Tf*d z*K1)~dI)FqBZT!5z>HFiKJ&3+AXgvSWf#Kicdk!s?+Yp_Mx4|--gd7v>OQ#6qko-c zdjPqf`Yr9wkB~6Ex$f=y_HcoQTiYL(@0l$u%-h)ov8=f3ntl|jVCF67m&fy(n*3lC z^c@04M}_h_vcFhP$!Mn}&|Hj76@otzA`&I${1GPpW-rBw9gGp7_xcpO>(H% zYI^9HQc^y1aOMYqXHeHG3%N%KbE;@Jz4G!{tK5=wZ6u!zb}Re<^cJw;pkYMeAWB^{ z7>9hfEQB-^&JJfHAYCa>@&R=L#}(W(?>#V!;HhiD)=h%%r*-m28{4wiSi0_glpx)f z7txJBZy2qo`%cRpQkj?baXJABFA>?Q9%UIs6}OyhFw-8fLC#B@;1>n_)q(%6nj2D? zOwNPHk*&fA6NqMdi)xmher^c$S8OSLDZ_A#=&@6oo8JXF7 zEyPNvZ!2D{6iGbJ(kkT~D0%QYGBNwdvyU%bbuvwE4uP0OyiJ@t#u@2__zNf|gknH3 zcC`d__#v|oVS@Mc{3yAm-Z@YtO94_?kEYm$Y(R_b7|=!eP){^$4wNR|v6*5U8W}Nm zy|^oM4|Vx&Lz}ifD&Duun9{X*FeK9ssH((!H;huhJ~;=>qi@q5+ehNiAKXffB;3(8W>2Ey~kXLb!h^KQ2|jAEW?T23~i4#i;usFutENvU=qiE_GQ{tqdS8coK1^`ln@l$kQBxr8>{RQ^M3YoK#H z37OT-l@c)BG?hYz1KKZcIZD0s<1M~BmUXE{(Q%FX2zshZsLHasoU9kxH@281A9eS+cW-+Gfn;f}IEf~eo;oY4W?V)|eVUtNf!%i#Y)JVk|yZ=70 zBouxkfWX`^u-DJ43+_IX1-7%zAI!BccJtQ*gP zpk)fsFQ75cOaF_#3Iv;A+;a`MOyhK_{D4a32vq>7{EBGxTNYj#ok%~WCBt|pK1++R z(EEVc&-q-+8?SQxU(lg&`Yp%`su03t4Dz%pt(5H~ZQggn)-tNrRZ zg+ykVKE-svb(<`t*6gD9JYXk&C%5gqdY9wmB%SUi0cx4QJ%wd4_h_n4s?qm@wsD{0 zi)f1}v(j-Ug&l{e=7$=^dnF>?&h-Wt8mn2hwuX2qUEOe21^)qq?xVmq!IAUofR7H? zZi9kcxQp*Z&!)CQoI7qhXl)q57~gD`kLi3o+49VEyy<)eM#g+5{c^11&ce-l^%Utd z`#iBy>ki(Hj^?czBf03rknOH}>E55NpM34oa0+$N%AJ_0ztfwo78*T;Oz+Y`!TWAZ zqw?V1;?g^2iNof;Tw zuhSZT;aYu65_8Am#=90)8}y43r~ya**=9+kuLepuvYBN}F?v}kW+T>4q2BX?6a2>2)p6h!EDDGF|IdivMYEe^if#k&*GKN7GAb3=~H0j@H)M`H*|6ic&J{ zT5eYh=WcV$+&=tCZSSTbG6blF=r!1&I1@x{#{ue;%IO*xJ#lPyz&)$p@L>!{bw8Z! zDndJ!M0$j7>9v|ZmXf84WtiK+8T{(5wSL(I3sQZ;{V-+{<9#Bt9zLY1^Ab4w;U}*2+hO!w)ZB?hq4r; z!?ZbfF$mL^snLqvW@JW^0Gm=;Dgr_4hHox{CAKYp(EpTd-+=(6 z6paoQcZNR1zx1VAl2pdQ)>H^+5b!7?HurY`OeaN;S|G1WhnEp2jIn}2RXc+w9kU?W z$oE%w41Eil5}@@*4WKE~$LD*ZuT0r=o2qKk`h5dKVA>`|5hr_1+)RrNltzoa?y+@K zY>Gi-FN$&@$tDe^Bs8irUcr|ugKcg9LNtW0LrXo5f=N{`4s_dp?h{-UWm;t=S*lqOqVt+nI2!H ziXCd04DP15B~N%y&SZ?qni?5Yk}F}>5!zV>;o@O*FbXeYMcH5rTA%!Oc6BrKop0VG?|Y@T-jZGRQN4wiH?A(ZYxZLJeuXa0 zo2~Gu%fSZrb+9uF1VwNCD?YFlRB^qo&K~{qZv9WWN=?QNJA7uCR@N)+%JCldMq!Fu zbZ`9~4GhlG3mIU6=NM2qESNz2XO-mZm{R%s$g^{DreHkY2~MN_P)2-lxNJL$?}@*L zs22@S=o7oNW8O5M8z$pe$(7l*|h*`P~{jW_S|=@5Cw3<8Kdwy zrpM;@PQhd>Oyn`4g>BLhpuU}Jp6xG+;7>jtmdR4xoJwq-qFg%SgE)m=2%T>Kug2;B z+lF!;?_rj7YAt{8UaiK?kSK6CCzI^YZPf1X!5C5VaIqP$fiD@&E=BRocqAVSzg58rtc^xxN&4Bw$>%|(3M|E`WX zHI-dvr1g1CVC}EhYFw`5$E5D8CErE06gHi>nBlK0bP*XYh44 z)8Sw!(TI zD>%v~g5(zveWn6HOXWggj5Dyrg!@ywZxedp^-pB{%BAoLz8gF2IhQynR>B|4Vf%*v zBd{KIQgOvSjfmD_6-U#b|6K=Y3d7F<67Fg_EKb&OT_Aq3%YO|0&6&>atA^R4;c57@ ztR&_yU@n0dMPa|OKCPg#bUB_?760c+&cOe!6p;+6LB#FAI-6Y?IpOgQ?*|k@q)q@rN@Xmq1BhFz z5&x&>@I(=QB$x}R&7|;1xHG)aO@c0>LY&KobMQ;A_*jcNj6&K95}uGvD9yAxD+<9O zk8Rk#*Nh)!bBKp?OA(wTTycGp##H~0gUsJwIx!f2>Ek`Yl>8*-9r@P=WBnbw)f`bW z_?HIscZlCD;eQSn_nwfJRzeY;KK_f{aw2ps<(%J@+>n07;u3hp}5bB%CH9){8n4fVF7O4J2;@=kee zhX_ynbIwT~LaX^rjv$I z_{H|qz<*vj{`1mv`|ZYGUYbwsWlNtq+UtEt`~5q=8GK$~Ou##83kuwAdlN~Qg<4Ax z$5f6zCiH>WyzS_??-++IYpv0LeccOq%K{1oc=V%b!2>}^!Vwe;MU%NpQvRk%gI5MC z(NLz2;OP{xH$#e|(S(J#ujvm6^*EhZnB;)|JX~|AGYj>Z565@JCy3y)k)TLgE6|5K z0J%*Ihi&Y2Ct*gWx%&8XX3`JO1LbBzWE4=u# zXmX06X}?6G0u)O$S>U4uSX|FIFq#&=*_&y^end$YMS@-4&hUs7KF$vZ7T^S4TqVX` zWU=_KugOM$=NVhy#ji33PG8HbYXkBAj`>QCERX+YS$nDp2l!W^^%8uZU|pN}6YX7x6vZ(|^zM?}z>U7knK1aaIi$R2?KOTb_YD>dF|6$9?WUhxUE^AAax; z{MCGV=|oA4>;z$|r|1C#|PH7@@58N_?(7&_>cVmKPpT96+2B(C=~gjBGZC8%>8L59TRJ=T97btA!W=P7 zlFp{qfk)N+Ew*F{9|G&3I71oI(%wv*&5s=OD8b`BKR~2u{qKQ#`YDwj4iqXLVyUQb z0cIffMynx)p5ENmKK#1vr;V^2o))SWuUEa((5mOyy_{0-0w);yRody;CvI1LFMXv_ z_Bl#J+)weKwk&QW<3zX}Z#`W@en^0mL4^4~sXgY)i+F*lU~f79IW=}3ag0F8>Ai(J zb?ZJN>ev)f$p1VlzWC?i3>u#sG92%zAG*^m1@OU>fy z&Q+3G6W73LX5zaYBOU5^FRIZ>q~KM7u``H+Yq%7$k0{NkN6)NTeeWweK_8(!NDRcj zQk%%XJW6_#jGglAU}A5=_iz6Eoxgy;;Vv<7<)%R|@3)XE#FQHDjqY22qIMED7cDWU zb*{YQe~H7h{5|Rc7itP5aH`&6C4L^BR#H|kUoO=o@rL(1AU~X?&ha*wfn^cY9Ah4< z;$2R3jJ*en?tn0@L}~4};|8D}iWlSn%KZ&vU$TkG$4An+H)0W|JmV#n>tz|_#I}$XP@6-@JHEwbb4cu z^IL)Y`VV&T>eOpoY)B`Wqu&)eY&u5nIcp@MOyq&!syVAhGwbz&42#42ZB2u|ImH<@ ztuTy`fIufSZ)w}scUGr;D(b+q1mux5xuk1T1mlQjp{nQo%~fvjG@Hizd#oZ}9JwWS ze%$^*w3~oa?Jc<*O=^~z+2^^g_wTOsJD9AqDveJ!6?2iys`#+AzwyrP-zsO2kdRPu ziN`~YxNoZzjJ&eUP%0Q_X_X=1+`YRgf@mgIJ1ctsCzczZAPfB)?@%xBi6wrO_C3Ts zHh4-wj2qix!AFB|m&GkLESKI8`ZC0K?Qa(&%*N@-`2c^%5sEm;x1goh1-*<_(7GY1 z44_RVYX2du6nF>(?0X;2>T>|1#O^T?7ZBF(=@MB_f)j zo9t4c)0;$Dak3tb{X7oehj!26No7%VeRY=qhD%O0yxhL^PkbWR|6O$+ByGQeE69xd zIm2g7P!eSo^C@9(&TSy3WukW&b^El}n->}Igmcs7Q$WfUW6 zrLptrUOsVl-=tNc!!YnUrieE=tfP6GyCNcm?%5-|z>-v^GVA+`UBwj)|0JNYcmK3} zjvZ&)Ghf3`4+^z3u47At|EPDU)s%YWnsfMl{!eAM=!2;)Cq^rI|41=knMsbz_&fpj z^PzZ4GSG4{iUn12b`cRe;9<6daBCsitCpifDKIfF##VyVK|8FqI*8HCNEZwgi6V<3 zEexL@nDI>NJI7nf_01XNvv;7Qw*o1n)Ez_Dxjqt*j=2Wus{`oY^Ji{DKc|4K_3^=G z@>)fLHdV&mLnYQ-j!Xy51h1EOMmZl;W%q%V$kX?~z(=ALa6fnjC}Fco=JmC;1d-+8 zGWn_RygAkAL{;)rbK9VHF%5%~ow>X;41Ak!eX8fRysRgirO`N-*#&zQTje(1h3&<$ zyx$|x2ljH0=d@U9)me@BOyirq&GKVV zy_qeJvOlg`;RUXjCW7m@jz*}l`Xi=8Qs3~e3(A6L%jz=F5i<8LB-VcImm>N}C>v;8 zu<6ny-|b>N^bFDbXqch5=9!scd=nx@L^*cV8S$MclWge4Q?WMpi@qI*V5u#6DQy-M z)!3vs3lGnR=~zBaz~(`k2LY_lGt6&U?snZDRP;oMuP1Q=AKePt)_vZl7*gQ&&?W8s+cKg?)0yrZEAj>q?B8fsVAYX`HN`hk9l z7|;|&WMnp+YRJDWBz{vJ@w_D87Qj~*=-1f2X0mUbxKm(6=B%JIdb zyW5~Izl?q^xi)?x;pY^{H=Dc;qYD$a%?^zz)^XL{!z-xZ<-w3Oj*gD2AZqh1?I=OR zZ|mH(S36@>);ei~1%qP9U_ttD)yf)le7H%2*X|<)#E`Yvgd2xl{oJ|B)`K4`-@qaq zpcfLA7D3{uXI2x1IN_*kd?Uh()ZG5tGK{Kd+jO@2bsB&`=kQTvh=;K5iY(g!*XWoI zjkU;^5id$ut|O6hSlpJ`HQBvT_V(de_! z-lumdDVH($R-}#@lIEQwkBQ$WyMfmqLZn`lt`9P)6gV@nrlL6 zoLnhbraGzc^K#!+C$!#sQt|Dd#_rU1$9x+iPV`l6`ZRN*_I;BNHNMCWiVmeq zhvMiwQBORq%C(WD%cP$N(m$HilYdb_M0s5Jb`v8inYM7?c*~BvaBtbRZiEUL3A6d!M~mWb6{%&G z#=p+@8<{Fio-r|4N|y>QVMlzHaRa$@sRw0@JYY;iN^#~p?ep<3;^E_sLhqD_Q4^bo zKBYt6x%ow4E^QVJ(Xu4o}uee|pol?3z)lc7B~7bV$q7u&-7j0uS2+jmI7b zm#3Mq?A)YoQ`>kYqtLYkJ+}7*26J1@SO-;R=f~2$v<-1r#||$)d%S3eIB!L4?yh$$ z^J%1v@?FVEHpG!5P1iGt&WGFGwpm6hmutyBjrLE{0h;7f+}5&L&BoNjU5;7V7k8yI z{O+)T84K&zw@9t$_nkN{A8>x$I;|*)5fL`3;{8_ELMTcyixBmb|4-`3_7&d+SRf;z zI*Q6e&nlf!Q&x0e%TB=DE8-#4pu7w|j>@)f@NSo zd(uz+(4>Q*mcyAo`_*r+!_xVAUvUZF)$p?Z;;PDQT_CYgH1R6-OD0|FOQ@Q1qN=h( zl72ow#z!+t<)V+t95s4{XpaU?K{=4PH{^lrX~*j+ypz{j620FO7)TuS8UPB3PCu3k z${7Upva=Z(9$x{kA0=aO`w$wD>wK}1{PewsnJ(J1YTR?V>F0Pu&6;m=|5lR608qu) zwMz}=7C4aK!JtnuC8U*8VL$wQb3S&s?>^;y!?>{V$z0c#%^{Zkc-aTsCTL8lr?&k4 zLt6oCn7`u#8RWfb8IsvoBgY#!=y=4%cv6gs6}I@Tb6&(pk*PhprR%UzT8 zMaSk@o*h_M)KFKYxIcBy^K%<1<)%RXZH2@ToWQKw;Dy)WtZNyrfKelhJ2rkWMe2;b za+#fyKY#8TXJU0f6T{UPPdDN{1|BtMH`i9bB_fFvofwwsx#uF2A@y&j{vWw2LbCHG z@GEqhUg?+LVh zG6x&+j)g%d82C){uo+e~?7Bvi}ALujw9C3Nhtp*Iqpx1#gN1vh9f{*cdPHA4zwZ z?uuEZzV{nSNvSugPah$j6w-&PH#VFaq1xNQ5xu=(VgL5++pu)wB5lELc}ER--Dev5 zcflj+Q&2$!Kh%JbC?;4us8TLm1A%ispMsmi%1@Wyuy}sZoz-VS#Z=B~%wHQrgZ@rF zs8f6vBy{f_J)l5?87%%{@_e@N~nyp z*Z&H8KHXU{2`i=}Bh(qyo8#{h-J~xnzP_R$TB*~&UoxtIZq!lD3?I+ZnjwnFexJ1W z2}Kz0jb!4g7a38n(cu?=zwfO&anv2~MBspHyhg7X!90>>wqub=%^NH(-| zvkTi=TB>>)>wErJtYlv)>vvy!=Jvws=5VpcoCnqYxTN||R-CWAe3NOX*ARYW)k` zRmV0B5$h^_1nTQL=bUJPU(dF1@{~q3@m-JZW0c-O=Ik zvQd5KJXfGFug?}_xVImtD&!D~O7?*ilMin@oM#Wl)*E|>fcKnsEzs-rj-2u2RHK82 zZfRJ99@iimR-(ELe_n8yXQWfN&>u+<IiE%LQfKT1T>qMs|6rGj zpD=5bvrntjdRJ}MC|;7jH8QHoVip;D*i3zK{mHa~J>jx=NQ~akHsyO9v4cyoV{um% z`x341xV*pbZ)n`K=eRG5qRXOTA0&ZZ^uqyP_c$cGb9r#j~Zn z$QlqU{XA(GovuY-JxJ5&>w-cHTr!lWaVXd^0@%g~xd{&kYcI~CSLLJyj(&e+&i zmVq&FUh`q>TvW9iK2`k3%{&s)IHQ)f}uYQqeehq(ei%e=UzJiaMp(HjY{! z<;2BzH*!L2vIWt~bc#S%>iUZ(o{gOoK9t`|^9+#$YUugf(v7^PS!hP4l7e$$rbyrM zFP07AZ1`MtsI?Ow2%A9YV2Qu)=;J0Y?02UZkZ?}jnf{pX{&3_%B^nrTtXK;zK!-`{z(;7 zwQRYqp&>)Ah@7js$)Zj5L&hVtXWQx2?!Q{}vRwEKy~2`KmT!tY_dV?DJ>|cXGl3ex z%+xTj$uRUyq`;?fiS=Dp6tl7<<^>{x>ceS|g>+dW#j^Y>`u)LoO$_fc6HQp2$EaXI z-?keqvyd5^T?K*zkRjZ$5@)(uAh=nULtl+HC+*|Z+5#<>Um8q$nYlR=)o2OTc-nF5 zNI{Xh16wGvA1{{4kZZaSqQrGxGEFvgh{OqAe&uq1|7!Mvc>2j)+}YFeQGD`$b|>sO zRjNXFqEqi0afaxqtCsTo_s~;vcXK!=2b8~;We2-N-)-)QjnglwDUPyS!d_;cC)wnV zavGK)ilJ0o`c~+&6wd5ut)N|^i;Uqqv!$bWbB^%5V_56Hi8AVi=Z#?>@u zH?Q#eYT}f?_rsQGh5ZEyB#thr)I9-t$0DtwTr`+9n%YL1+neT-m7}hz#&=sPAN+=9 z&rHumrAVqYv~E0j{OM9b(M%oR!C=XAWJn#Ww9G;)h2s`(2Rgp;Ux(%kdsShPPO=G? zC$u`As64l{)G>6X%e|38SWxs|T{X2kRy9jQjLtnPM^EE6|IRUoFFgjeiv@0!&2R85 z5!JuvnoUS-uBk3)A)V@u(Dj~cQog4nw z_2J*FI|d{5Vgnwu^zy$Qvyx&dedpg%=r&y|xt5jo6X3_(7w07(e6kI)8Sm?Z@5GVU zb41HVE8)0X88v%JT-`+znlIe!9EXuX7Qp>MtL@a<`g-Ei9wt#6Ov)s@W(B$9cdV>A zlB9F`llsS~k=`pfxz~B%dn;SuJ)Ws>qJ`L8f}fhABy!>fi0Az9W7*w_HOWoJ%s z)fok$gY2G7spZD3g^J~I3-_=PnB|{b8!CYPK<;vyYH3_1{SuXo=84|WIJ(<2@`VeTt;3XRVytXv z7^-y8&TdoKffi4Yd=u9yvEbPR?y?ZzIROel@3Pukud@TJp zWzG205_m?a#1qXj3qno#@BX%diq@8NqN=n4abBSNY6d=sVF>Fgy;Z>b^P{px(XFW$ z&5k|29J^+obCY<|#9CZ6&=J%hQOD04)P8>5jF#S>`|>7-rE>o_p!+!#qfEeKau_;- z!Z)l$WMHUM`#nz80IeMwXauv8xF349D3wVVgBYb4IJ~B-oqge%FbcC<=8pK=S3@^{ z4N1K@uUDz0OwPhAI2oQ)4HkaIh7I)FljkQhMxQ0UltK0^Z6>|e24EPlJ3>aQsu(Xu zrX7G8?)2o*VGXKZwE+YJ6Dri9F+2Oy7~h>Xk8Dy-U!zeYG1pbjLngE5dK4RnQ%1?y&y7oGeXmH| z&1d9bEu|(>8rN zs$$h*Zhi}m6I8UM<1oPF8+Gg3xjGA%EcJj2kbB!|1>{%=H{rX z?j5t6eHN;cwzgR~n+RslIsDK!oTrlcnt-^NV|G4$??^;-Pptp$?ekq$gi3q`Mf|+^ z3Oiq{AukgNI9mH$Rwj?RC3<&V=Y>r|8esrs6f+(n;1Z~q(oZ^0E-?UsLzC|(yDl?qFH64@?LnT zgz{*Q0gV?gqt7L*YfF9a04Nf?y`QZbU~K3B47Jl?;Hj#k@#s_P+|!I@XqsyGxfaDv z6%-WUmHAUQV+ZQ+Mzso>$^Cm zcbzkzjGmQGBcg1Qmm5`?{?6t)ype~cJPr!PoY5sPyddSi{ld$|t(J+=DD|p$JqE6L zvj*G3dE}Sus8Vc}XFxi&7WKi%3E%#6y)@&$Q?6&m24I1fN)y^LT4vGJk@iZs2yR&(?I&?Pf!+HX*{6lg& zjldfUhkTYAWGOxrdBPfjhVRh%gxeHqpnickB9b~Q7zf%dmPTH=|9KKy+yJkMbZ{O$fy&~3iI}k z=9l$-y|6pMYb?gSn_2gqJ{3uF^b}ipAtF?BG5;;|{EPC6>w5ax#DzhD@~B6~DptGS z|p1$!kA$kSs^6MrD4x@u2j_I%o%eohP1^;C|{ z2gDVATTzc0sy5-g*1jvdZ;!h^Si7IVwNV3;5|zW>yCkNo=UaZGinOPgzbnW);TuZ4 zBQW@X^3WhngGuM$aDQ3Ys-K7J%2*k2Zgg|b51}m^{4ZGvWo?OANsN`5_+R?rr}+a= z+nVEv0=gOsK{}-dBj@?PzPtPIlRm%qD++Q>YJw(Qgu9l7_8lR?`z2TRy6X66Hdf<( z#CgQI`^J$2b+~Mz`Hxqr2Gk{<=o1fyb?>8ud$xW5(pIwMm)c7DeN>CVwgOcivcgF!l?`9q*hcC@c?*J#I{l2$gg zqSG(p4mYZ5&syDq)LCtrCRS z+aw;>p)|@56LX2qPP(G2&->+mx2H|L*;^*3M`*v-`LJC13So#BAzz?uQ84LeG_tM- zL(pga*n+*p>6CH3b#~$4*mb0zQiR9bS5JA7pG462@2POE`%zhjA`SP#&JUC|%k+Es zj3wV^(V2h(!Z(ox4 zN4SsT==vgip!wPM);+T&j}G=Qb|l$^_TzIS|Dp|AxnR=dY{KhX>y%uk5AZuDfMq(? z>#x;5!+0>HmYFEc{)V_$U}28t)h`1ajPV%}D^4K%B-(&OuhFBe1zp`T(;$2wDk%^L zEGCu7X73!!FS!K=cafG$f%1vE1|I}y!DpM+|k8Gj&O?l!towP;fD$H&r1^{ zQer7YYY(bzvd)f>M|MpDNVkD{Zc7r&gYC=W67?&m&vJ|{Z;)3h51Ko6HeVs)r)&0e z(MzdG0e2(Gtj+jL(d6=+heHem2S)D>Nd5L0jhzHKjhm*J*#a%SV&%fjnk?rMR_#|9 zdwV8xvCpaM*;$x^?tLh!I!{v3oK|wRm--rKW(ksT0bX zdO`t3)v>ceqy9@g(dsLk>~BTMrE^zX9FDJ(BPAAo5p8q-7&Su2h%#?;?8zV4Hr=yT zHzO5!w)q2H=vXr6?jpsT?xdfvDin~`ta zm8fskH&TQxJKMg0zmF%-+=o7qzf4CAVZG_=us9CViMbpRY#w%NyvE*mJ|sUp!kVpr zbdIJfdg|BYV~(FVe>z?b^uYcA%4OBwB4n?AG4I$vSb#R{#6YoH#x<*3doCXKQ>2|k zplQU{28t@L`Qmgg_4C>d6eeoEG&0Ohouk;vvd(+cuF>fjun!qH zMi#mo(9?}1L#s1 z&bNwO^Pd0sOzb@jurwM3jT&olIv2F{+PwCTKgZ`_&p+Sl zH{)+Y^5MJh!A-PKa^w||nD~)0##f2!hc8vkmNZQaKPCQ_=BtkBiclFWV%z1%9k`fC zx$YGgKdTbJh0vx>AHBeSJIPkP5c;c*>DE8n| zi8>}~GpuW1hiWo{*L2#7&}op~k6$CO?KyjC>wfj#Rpe>g&B3nZYH`gHeQ!@}wUVDr zanniU`Yq&cU$P5m4Cp`qeA@V?asQIS!9z$|t5_=oYEQ`sr4}}rh({3kjfW|*S`@oc zr8*qnL>g0=N;O8<#PXBfN<}-~gOvi$cR+!pdUUg5XHFpKziU%sYGJG*4 z2K{^2(ZB78PKptmpY*G0R!5)9&K?qF348TuS)5CJ8rLb`jGeF0KK795pAj^HJ-!Uk zXE@GQ)}kDn`)H*6L0?PDW9>AL&H17;RUrJbF{%6%2RsWQ}~veFO{qx^=8AGn=?$oq7%k}hf3u~H(s1lt(pGw8)o ztE_af+Aj33U8B>N%IPDY4>8Qxa)0UIn;g!!cIf%BWAi@|_#*zSoVZ57x#h}DGCTZ| zK$?w!o9xdLNy?>o@LA&7@B}G=P;Kl^ZRGbw4(;iI(MQV-&Gx zSF7G2_t5)f?xmiJJvTVNuE$IV+8oWsR6!#0=%2H^rtE=I*?~n%`&t8HKIz>9bX~dNL>OtRE!g*LeogUo}A#VQbg ze|+06i<>s}x&AY!&+*g#jzou-|3GwtfQ{qmDj;+_w}9wK{; zR%uZDxA<_b*dlDY^qFg4PyP^_U`1U!?JYMPT+GXF|Hd!)&{8GH_>XKs{a7wMl>?61zlEWEHf5? z!pHLOrCuxj!(}ihdm?TBoH3{=M)o%q8JGAhU#jA}NlgxCVbYpIAx~{P3JD-_tyXNY ztn^=OlQ8Rw=*w5~cwgxPS>hbbtZ%%yqT7=ea$PZ8*v}1_niUb$@xRGY z**f^U!+#s@j?6JUo$1Y?P&Vl;stYu-JB`l%!ux+9(BMulElYTvFoW2aCB2 ze>J+=YcD=k9P3+xyWWI5px&aizY2I^! zROT|1#G`CH8T1g^dKXp|5AYTviaiAXo8s4a17A2f^T4`k=iuGcw_Mq!G5)?Oi1yhM znuNc(Fae!Wjm0-G61ob{LBF4!S8O4=uSfwTd=B{ zFl_wStf*C)Pz#-%gw>>rT|;S5o8M9R{>YelKjPM@u4biH5<)P_4#b|NkCj2+=ZP zEtgftbKX0-Snl*q&%F)Hw{cwI^GsK>f|F=metck@aeeft+`wZl$N$SO4>JmLZk`It z^LVytkXAY+-1Rv>-;wn}K2XEdRxW!!1ZTfzrR9UO*8S};!AFM+5?|(kZlaK2L18K+ zK2)fs3fOjT0MM3N z(ozs57Oq5tSV)XMDIJ&c7mxfyCTX?DnfcDose&QDBM{ z1JXY?K+IyhKbPbLeF9+o)aM6^xMA3kXy7vPz-OG8Ce?bdm=_?SYNm@ITF=A!_;(q& zfA+nclv=8le@=K_t1#igD&zFxWQ#Fyu(>C_x1S3bVAEtC2YiCJq`|;*v8vFXtXKP_ z=?-vC??bj+4w~?<&4B0alk5TsHi%Vlgix|-a}Th)0jR;S8BE45%U9ZrXkbPSE5NnO z01pCuo5p%%+^5v4|2HFSsxsPu&I+3kIYf_=o(M>$D`JEn^JQV0(k*pKa{GU)E+m8InhH?#4 zk~;XX64e5_6B;FYB8eioPIG-4wU~RLOc8D7XG^XaM+3^4#BQk1>wtn)c|2BT7J_fE z;iac?p@!Y~#<;h%hLe`_%w0Q;}YSuLPA(Riyh3}<70iZ*revkJEQB3)|P8=J}IrbbX+n17J>aM;{5ZGTYTK8WsP z1lHoLrjTZS40SQ(S#dTzh}h(axs?6H=~L#@3|oUgzl5D$O2S2)G<8!ynQ;i3HSbvO z3pw@zD@CDp;U9ZW6q|4ImbXH79ya02p4!if9 zUHi3g0?C*br{VL0sUi$T#m}X4)?AwCsN+k6gb{9PAg2fF-;QaY4p>uk2BUMQ(^NnXG zeu&*`CstNbQQ;_0=25?*jY zctroh?D6BF{waux4p&l-2t8S}^CD#A4#~AtihD5LSkPr0rz`SM?U2M$D@F?6k0MP z4g)U*I~U-_6kKL{${w@@<*rs>4KvhFA@pNiC;py8H(ljD&`HBNLj|K>bO@XA=N$g; z^=5v!kOMlO7AH=f@`f0p|6sxYRXBF~*UJ_oBd=8jt@d|O?D9hp8wIR~O6eAsBadaA z_7^{;Gy6XIQ>FFBMF3X0e+%W-9DE7-z6KwAqvYI{K>dlj^PUBxz(ij=D zf7)jd^S{)m!?B)pPF$YW2SEA>wMzITg?~nd>Sa2hCcPa^Jg%~OcXuwOzvjF zYfDqIPm^BjkvE=RR$d^(3kFbagl_07jR);%4_~&MXw+4pd#P9T&i*%lY3;Rx&zgmr zyj2szna|*Vy1Bd3LHXqdeO@-)wo)s7%gvOjWq2mP1JGD&`X_}mE1QKicVhtXp-24s znfEL#N?6HoR150*$GLB;5Jl0F!s}N&c^6*WcmkqhN_CG49&pp%*$8DgahZDM%ztX` zlGAw3Emr5xiCqVtVj}IB`*Q^GY$lzma?(Pqx8lU+c+m1;@zla5o$J?@T4ZV@vM2U< z4_j!{Bj98lWcsqN^`u;SV}IT%sD6mpd?%v52R~d(&wWmGy={a5yWszyLx*BhyTs+Z zg%&JiBz>Pw@)^3;#mM1i?*_@!VZvospCwSw{Z6kcA_BYf;%TS?8};Xa6C&)fVd4Bp z*M2zv8s{la>@}Y26or7iDyY}UCm}hZ-BAbKsQDiuD^`~c0+eO}mM3u2qZnQbg)^5e z()+^OR^$VZ+#10-*aEwcMn-GZc#m;Au)gXvUd8AD;pO<)RX@*La!%dp5YtD4WIg4 z^3J$Ir*W5~A-OtFlv>fyGMhy4;iabM;ttmq%hb6?3lc^%P^q!?OenE=;}FS&$T}1I z$OF$n=(zu{3yor1d5l`SrK0t*7%#PA>7PMR`v9MIn6atZK<-}d{7btgIIO`Z!~L>V zzfz)60Vs`p-Mt;6FyS^$F$b$czxcZe>!iH9X)SI8@iAW=K|V`QE%E3(R(9C17L#F2 zI(*X9Q7=a&v7gMm0@;pC?VSY(PU22@Nb`gm_dNKiYD|;TI3md-`^}WHN)$wy4!+jd zr;Xmvs!p7^a4xg7SBAvAVqj)=F7)prdek$C>p;~Y_UD2}-Tdc*3+A}g= zAM9@zJ=`NRU+A3#d|jf?nS%0F*Amfn;188*m~%wFC}X*_RS6`Ymco{Z1IJQ>I!zg> zsLkLTCip7Cb;4aSibcBc>a`Ss{;2CN=g~-xc!tvbjW4$c+8jP0*E`;u+~4q5Dye~Y z?JY!ZA?(-SDV*4kyrT1Yr#!8Bs~mW|Amg`+K9zBHy6u&iq7+&J1}s7!o3OuP9$va@ zDa0gpp2VE#)o{YU@z}S&C94;*%kjK-;l!SK%biblmU?qk8RUMz(?ke3_2#bt9O$GC zAxxZi;NDug;TeG=pju~{w?VLBB>{+amMO`8n>(onx5>c7>R%jhq3i^(j2 zX+g$M&3~Qd_LU6LDZd5C>G)io+<(v8L2tic9J{i zVvFmf0>x%{@KaszQ`3I-0LYf(GL3u}|JJwXe$+c^LjlmsZL$TnWJnV8qU_5k1TxfgF9mt~ytv|ie{W-M9#J=eIT$W8OEihRx_Fn<|B{LWd)%41iYcN~<DxCAd}@M5fieUgt-6x`v$DgS7nINmaiPUL;|;Dg?sZK+ zAazK#8YmoTz?RSvU^jM7DTm)C-2Unq{q8;|N@+hRvUNz?{2IKoVT9O$v2(Uk*$J&T zEg}%71~}$vYb?K9x1|zsyo0$u&UfV}hFZ^F+&!}LG*w3cW9Pht!t#I8R$Uf-!L=wS`FfJ$jRwEKi3r}#NtPtU{NXibeJKh|bu$D|S{OPL;|m<}4|!mdkSW8{3MHa`?)$|2;E z#2a~0;)bJWv`{X=i75beFrPin7CT-D>0I*e`fO4Epf<%blT^3f&I#zq&0oPQBg@^f z(htRS@tTwMh!f<975T3#vU+oZiqcvGJ?}AmrVeF2L+WB5#eWW-BR?h+4xSLcd85bt z;-7=((I%ldp*r(U+gY%o@$rGs$oBfFmMz*;!D-cmU^ldLmQB@nN2_7 zSFs3^OfQM7die2-oRv+#WPfIHqz^2MJYspYswk|u=b2dJnMLsDX}aXb{GVId8G&dX z=DfE7Pq$X$DTgttyTQeL87khji}I+A|A(;m0LS`o!-o-BWy>yZR7gVh<|a`xBeJq* zM)sC*TOox|woo?7rZP&%p4pKV3CVn~FZDdn|2>ZP{U3+J^ZcHp-_w16zMtQ$s7s?-2YAKo@$P6j!RKn;txM*p)^E1`w$B<#V= zS1FbzATbku7U@Zx+Z^;j2NdrZ^%l}LSgf*lS60am)6$}OZ%3i&gLTS=bU3zcJ>8V8 zuV1nJ4$BeVne6)muMLL(u-FZmMYUh+@UC|b z$!QX@UAWNhGN^w6vAln)`r^KjGqfa+$neO@Mw`dyf!{(Uo}yi#oBd%P)badA_`r0J z0|SEG9d$Sn#g$+JTa)e`aICrx1s}WL!JfjAHx`S-}3>5n13)at;%og~o%h0p`mvj@w1m znL^XgYCxJf8A-w-$~rUTkA0#i>pO|yB(xWr53csuE^#w9(S`PsS4h~6lw*4N)Eype zpE1V&cPuw?Qo|LUyls0l~r zjGnIdQ7zi{O8XY^C7&B;g^(m{c!_{#KJ|5(EX-K3lKL}q%e~A$U=Pvui0Qy;@kjlx zy-d~>RODA!thT9NYI@xRmb2q=oGp$$Z7SyW;|cKlR0aw+{Ro4Xm~|{VQ=UPCQwa@@ z>T!cZSiP2V1{$2cqxwhmKMhXiWD1LkLf-g-?#DzC4aGq}y*Fx)mc?UoPXDbVo3kgq zK?K?czHF#qB5I`*xcEiTQgHzIM2Y4wn$QDomev{07ztn?a&lMl@`=EzI45Aa|DxfbY{&%)-YtD&+092O7sL$Q4^WbJV=-{;!)9i&n}+QPX}In)7fS zm`)*QUx~SU0KVpZ*F!1Tm2jzFlzjAtTyENvb=~&_;HuXV3V6lT>GMP;CcD5LQ_y@* z_i!405`1#7gnq!dS;%E%LyI1KmRaeY|oSD_e!oQpw4`1h`z@!{k zq_dI3Cy;34e}rkW|HCwC@hrCWN)7Nn8m8;04t714HAaAO1)J)VkQm9ZzBL2v;I+x2$uQV1O!M2I(On! zWlwih1Xdr+w@8$_ZEpDkytTCxF80H44ElKS7G<%%J1qukWk7&!m-s!#ic$PR!nI^& zyeb9@{$#@C>K5=UHVVvs5h>8`&V$8X8x%6x4pZ-ZJJv|Btm=O{sh2t6S}-=Yw40;N zmri?a0^-&VySjVd;;Zd}SU_Wyegg`qpQ&`N|D4_I$)pdKM;#nGX=~?Nj9QSx&SA`h zji}*`??4)iHsKHnk;7GaF3OHLj4Y4WF-SbBFhU zw=WDH!i$*B_Z0Aui&2KijUG1occaO;ak%A8u>*T!vYXx0xan2EiRi{KM-yEgf)WF` zm{Y&nJ^io|R*7)w*SH3kd7-nY?g>V{&~O+aKf2RQiVkl^D0>{__T)M3buKpjDQP_$ zhmsG+U8e7D95Q0ae2Gtb_O)R74*np}T{J}t0f+jUpKLWE0R;R-HWz3I)uo(e9TY4Q ziF+tNKNl0q@`F;uK+=E~?>KU+>EM#Ud@$xRGTbYW1?~J1Y9f6F-sR?L_36cRtFgoR zMz!IcKS^o58IymZ6Ur2p&!~#cCmb3*S$!dj5$kGgG1)>{{I&zHpN0DltXg%S+;wIw zR5)!>I~i;grR#$v5sMcd3j0i6)^+Jj2GT5bZ^pO4b|NEHyQp4gjyv&7)G32R{6*db z4h;p^e0>p4Hx@czy?)!E$*<6FG#s$_Pe2>NxihTeOwV3}=*<>jKf;87;;Ii=a*RNaEN4MPT5Xlk1fdi{U+&3*F z>6ff}aNy(}v#{>1eFzCCvWTJ^m3roqjw+LEZvt`H`3ppy0ajw%!X{5Rwes7*S=of*Ys&qpru4nNx!REDalLbH3=#3BznrFeS6`Jbzg35(qsBee99C@C!B?fcVP|+ zHy5N2MfL%6T{I3k{kp`!iJ5||4F)jWiZqXIR&)mvc9P{fF4A+tbdG1mQO)@dH=t%ivmtH0C|E%r&-GwZHszLg@+q6)aD;&Qp_rS&fN$}Q z$A`N-|2_W8c=N#P$tAEL$cv@jYR;|N>kyjAwcjA`blnYhmoRF?dRnJ^&d0W28|b%E za^f$5PF>*R!kh|&hWxEf_j6?FKyD&q=J_j13c$jZJQM2*rt@(xUxa_Hx(sKwdJ|YU zkwIWCV+FJ6wsYaa%wN)EPBe94Fq2om>v<+{2rpTxVi6wNx14lB=y@BOU^b{qUY#3Y zdo{&VA?XrYU`Df|yEqnXh%@syUr#(i1rJ0CjVGSZ*Qq(1@f-5lo{CE58$N4s02SJA z^$kRa>E0H$JME{IKqQbB8B%VP+aF2UBdl*08UAHNj;)1s1fr65GIAWm3y92sLvZA9FbYA{n@>30T zE_~;awRclM{iTD$(rT&2w}BXq|HKihl#Z52GC5q6)R7-)C%7z;%5aBH;qZ-M1Se?NVU3rQ9@p{)*r~ z5g7Cm@}^=V%u5ylIC%F?0w8a+VNcje%MHC|sbx1R&#=Mw^T6{HdaVABg$RnNV!xrB z3Jy5olG0O2zy7LvTW<=@FPIPi|LVzA%9pFq62i>5Uj4=4l^gSM`!d7!V zldz~a&n>NrN^=auZXPp~O9oTR5cb#EBS`fY(A3zQT@5afDLI6!KGikk9_DF?DX?sR zDfKbzVXLeP=t2gy#~cq~tPCISPXE~!Url~Sdj-)2nAT+HGz)ez=4GnQ&<;+2DPwv+ z&J^1QV(=73NI0A7jP_@F?*$={j0~v(Lu=FcIgYAS-yJIv_cbGFq2ksS<$UlIP=3TH zg7b!#sM8WRUR``bH>Y$krj&*vMheQ?llY;T>T_raOCU+(46EYt}Vk4*pc_c=s;~zF$5`1-H84+T zy5C9mikv0t!PmBwcz~VLG;m*mq@SqA5*P>bc>*t(s;xML#c&^gg_;`q@sDn#$7r`b z?g7q6qHH>dDjP>k%6`$(`XvD zO2nL1DChrlhXsc|$E+)G$Qu?1r;+!jdk1{i%Tgp~7nfU;06z?v66+s;&Y(C4YeKc2 zXmj@wSWze`vn-B$X4ZX=Pq4Ihg6AR&U#f)HZFy^xM{RHC9;-RH5?-n~cDz+gz*Vhv za|Q@)7N9>~e4r7pQK01<1{8cZy02;FFeM2QdV{pJ2jqCdzvD1{1GQN|7UUsfhNS`v z$jEDt{D`*`)gX}F3U^}sC>AdY{P$~GY#Yv1bC!BFpAqpJxclW_WC)^2VS4$VWD}Ga zzkH7vKY$|56Kqh#I)cANC?^YrP39z{J6;Bd@cR>{LrwdW!H-{-)L*C*mi$zYqKv~l zp^BB*K7nWPRSA4hr|pze;f$wakDDX$LQWr8iLiz`dO=e;{=T3_@(!K);;D?IUPC>a z@YcS2q8NoDPoW8G>j=Yr=uCJ$Efe$kJnMhhLDvYp349!(eOqrlK`Pv1%=R29@OVZ7 z`D-m9sc}|h@H9|-99h?$CrGIh?uCk=c!CArXRnu>VhMU+X%`Q|j1iK3wnia(TQ#n}kv1 zqTjyH4V6PLr2}P^_|MPy+OtKRlmPC}wmf*JUTsO3r&F48SF5m;ibn?y_!vNfP~U0W zD8}1b2VaMEtsEmhFYxj^=|8B9BS02aDg}Yj32>TQHv$5kC(STVCr{lEr{O6?&xcd1 z{Yaj}!vW8#H4*LUfC5I2frocTuAw5kc@s)mse`57}Vzyy4s)y<l5JGxG+R+re{TXgIT?a&eEPTmPS77E{?NOkw!8n zDmtt!b%7dd{^s9na+&O{6uZ9*QdyWSYVK(>cp)#;>3iJ5?_Ytc<0DvvG6SL!wI3x* z+Q;G#ra2GM<4?**{9s1&l?tLf<&o>{|6i0RAFA`Jtli%leGH!a)PqD+7nzYcz=I!S zBAhdhF}aiuvao*GeugsDu`F$zhg*{HHAU<#dDCi)?V~P^FE)SQw6&`NO&kHSUE<(w zz-K#j<(9A##34R+8HW%HIzH1F5CJJhpOp}IY{;^>plZlaF({v<`)S-l0^a_w(%5Vg zhn5bA*S8AAF^BN6;4V^gJFZn}1iRXN*K3vqARvN^zP%nz`tWNzGN=g_mFUoh)3CeV24#vD6!2_tkHQ~xiEdc1!V9LeTC3WciZY`=c?5b4{8eA~B z-uj+UpMid)0BEc3wb={&$i@B3vUcqM4m&%g#gC>Y&tWKw46KN81#O3zVN%qmqTbra zjus`Qy}}G|g~K(%x9q|RG_eIu;$R@TxDN8%UmJm$%5>Fb8c^VT0*livvV!`JI^l>6 zZN~7w4O9<%fE#+&KC4$j*N!Lg+2mM*pC7`_p}8BCPih`Jq2|&eG=w2%odtKvsOB$> z8tA9blfZULr<^jj3f{j;JjisBN4fISIXZip3WKY{nn>K&)pk&j-HhRKVUHs^?B|(!f@LkcMNq*^*!UU1@6=_<3~YZ z{kQ{ee%uHs6<#cp*Y@2U{#g%G zu~cFKIO04m2r^4gF$TduG9es#U3u`02ewEAHFlO#vVhI)*CCbSm#wSxf!P&5T9!T! z%;v4J$8Tq#a&UWFiVUHV9|EvCpTCmUJWWy5?=Zmc4pwa6s^(ur;Ke_XIFC0^)(-Sq z{X<&^@R!3O`TJUs3DzL5#o*CvF?IY}+>^oDO!Bd+zQUZJ z_kQ4eyNZiv@te*W_wV0ZjO=`oS1qxhGi&+-K1G8lJCi65{V1@&`QW61bzNJEIOP&t zF2h z!^bMYjO#|-8S?8eL=P31Mbt{*(GiFEhb3LLy7wv?1`isrh~y&R416Hsh1p$X!BY2I zh^hJGS3g+j-UYi`M(uX$`ZBoO7Oh-=$-9llaM_toF5Y&dO8JUnXxN7xj3usA($l|N za^~SNjsLx_1)CW{@Q**0k%G|$;07RU)L0&c+A$C$I|1=kPY&f-1ts$02r`eX7F0#( zJnfNm{6Ga0d9w?HgKY8L<+3+LGkCnP%RnS&J5xv)Z~lA9oZ|6qW-~(ETT;eQplvcu z&-jj9nPLuq^iP#pRrnKxe;b|jgw!~s(K)W8w2zZhH|opFTj!dHPm2xY++;xb7?798 zI?ZzISUhhfUXj@!0Xm(Pm(N3hCyDnmnoq-I&aq50Rs@1NpKt9y3L+!hq)3woa97kD zeD~8dmM0n-BIyM_$>9F%eoB|1(}}#=e9q_X*>$hN3rQjawDzm_j-upd@X^eM<}?oG{8>rww*~2EJ!Z+9iulYv4OMrl%mAwv8J5BO${x?t4(!qJ zN1Y%x`(`tAB?pSoRw|&-fJukM0AtwS0C|JJUBv+@uCu{#Pv$GaSc^O>A^Mej8T@{; zfRyqgjKUlm9*%-$JrZwXs2Uv0=Rt)a2 zQ^<0_KL89wf5R*>-QVZHv@8aYRz7;UVS3n@#eMgY0Pxsx*zw3(fAG6PuNKpJoL6#w zF$DEO2kj7vObr&L|9upMv5$~z+TSB7c>ObfmtspK9k(H+)q7a))(&G5ZXjQjj`bsB zoaP9c%CQ%xO%&h;eg1OI&4{S8`&&o%aaY;H9k7g2?=szbD4#{B)D5}rgJ3Q=K#bP6 zEaQ~*buS2H3%+HN@YFx_ExmD-)V*HQ{eeFt9xBwU@33~iK-a@ zGV#?bV^w9RXsj{Q)z+v(>pQ~%)5WW^IaP2r;C|2Tzhqrl?=PjHM0KH^ujfIk3 zYplZ9)Q_(<*_wIkGP}k>&L1%E7vU=t*aFB^{nDKrsR*-pJBN80j~qfaMF^zYLF z*Z(mf?K<>0v8TwAfn#f(SH}=_{$^6vqC%>u%cpWL%=EAMt^@>rsA!Jho#ENr^*h{8 z7@;|MT_^p1b=xcC6kQ*TnRjeH@AUbd{a!77XwIndOaB+YPHK~6^oLakA6^CxsZ}uL zN)dLLY~(>b<^`0J?r~fkxoFY;C$ibD?Yll9TW?hcReW5b{<#D}ql0N#ArIi_bs&*r zmMql}?Wj_k$f=_WSK5;uEB22DImdTFETT?($}NRMs+77C5(OUQa)U^0yw>Xf-uK-+ zqD5q5rjR-l47|B9^p7f7K6%0?h#$*^Xy2ysCtlPz^DUdh^asK_)eo#xCzks1SmB7m z+pC&i^+H&3`SxAt_#-irS#qIr+r6ikQx(>=YcU)=_? z+8eE(Ql%PTRlr2K{@c@~Ak=_kCH(EwK3FC3XmnriMef7XfDsV%rWb;=?SG&Gsep8j zkKQ3@N+P^@%brJHb*NHIS|$71U`fW8bgb!)IPB(8#cyyt zi628De(iKpzO`1r6>mzo{kUt@oe^v`YANs?!cHwnS>r4BD0r# zVN(2jd8CM^vemK1Z?IA4dtzW2o}!w)Y?H{RMiIv2^UI)=zRI7GME;dg3pxO;F(Sf81#xtn^*CPn*(gOr&Z@nR#H)UrV^~!W*K^~$F-P`VLFG6n zSSH2l*$4HnLe0dEBu5}0KQlmF38CLfv=S>jNtVFEH!}$NJTHmIpmxZyX?VqP?8ii; zNx38hS?->JR#=hwoLcYiVh4Z1fD9C$AzHOkWD!R0i)|)+%y{#OMnX(a;4{Luha0Qs z1dsB%@^S3vHa0!#)32~YKoD4I9E7q2nb@Diu4R+WUjUQzXRe1W4EG5Z&wF(stI6&Z z8wKR)LERbus<8S~xqk6UL^s4>|5k>u1FE_$o4KsXwOwRY10EuG*Jr?JZGKQ=pUfK&TJoDw1npz1EjW1!Fxa0~J6$;JAz%CC?*#RQ289@G}&hsjNz!t^~=mPl?J2JSOA8z%H zcEqsAeep%6d%M3^wGYyY>b!n`LMXX_^EfqAYbyeBl?|35c?)s8+Mk_)MqyoO+2A;% z&|mNL?Ni``cZm33)MYVuDeB%dWb$v|HL4huA^&_7;eP|Vme6u*4c^>5FdEVQ!-I($JCa6{Cb15%5@T#oAuB6ES=V)=e2;sCl7(1`SS5$^Ce`8dkrU^dG#arS( zgBFT)h>MF&67*f4Q9Ns6{H<^$Les(3i?MRI^8M8jJBabgZ~wR>>XQ<@qn81TFp*4p z$2O~5L3AaN4z5^w^kO4C#_-W~BY2Ey@EHF{-jh3ejMtCQ-gu$^<1tzicaq%!8gU}8 z9F+a$A8%rkfs}I9PR#NPa}{U~PK2kE=^zY;qPi{JHpkeEZD^mg?zure)o2Z}JQYj- zZX6z`+>R3v{gt6Ne|nD01)i~q$FB~doBP*x)8#@r)hIT@jori6VL;LjeoGvHQNG-% z1d+i}D#j%CGk6DGBt8^?m>CyN6fzlF4{O_)fijZ-?YB0fB1)4K0PgHr?=_ic;3R+P z?%OLBOc@@#FirxGpqtLzdve_7KD&sX0-=U{gh@?f$ro2xfShB?{B~M35XvaO-kh5^ z_1_y~U@1n@8@UsVzS3;`15M$DM4z z!;ho9k>w{rGYpM*4x+0=;8;k2g3sboqo2X@eUpT?~`OFP;`nI}wwwmOS32n~D#rpQk7 zoh$PvQAI4XCO10BJVvWsY-BVypyFo*UW(eivS+Yz!>0!}-6Bn%DWC&+9ZhpEwAE0X z_k5Uk2qyeH1>AFiKjq}-RFbZKd`uhtM!-bM<8uqf0jXtiZrUS9+KcMpIx;tKgH^vK{lY`Ge z(k41xd=k1090t6Y;vXCaM|ix|ajmqT^ItV*!6OO)_1~=m*O46|=4$=6t$cXJ18j}5 z!aXsO2f#uxT^=fVv*QUU_*2WsT+P}x*i-mC{KIK#>PYMur=4dOFVq(5d2^Ub?$n$O z6i~m$8Z3~=l0hHalC;0-cNhmFUM_MVTkre z>hCy}_azD+FQ%ID^-r(2=JLlcHfh4m*F})WmZ%lQB=Ia(3kW5Si?}uMe>r5BAIb!N z<*9sDu*hg;aGgiq>`W|*EuUs$q#0|BKE7=CY5!lBjm5nl5YzmYBJ*6gNU9zf5Zva- zW$E05PEhG}e?$%JK%}&_By&mOw@^XneuVG&4N~0o0Pebi@8TX&Do>2 zq~RnNqmae%!8of>;ie^eU$(sW)Iye;9<3xY*OfPyQye%Y%PHBFz>{joWY)(ULbeBT zxHecpx*w07m(6mIhR5J;zo3Gh`NpeTl~GBZimwXf@g8EM?_`|_Z90c3$`vWN^(U%J z^_*v03`-uzdvfE*zR|>&=aX2cb(A}zM!FHDpvl7SJH|%RcNf(kECm~E1QWQRH>_O> zS;8Bj2IBP8<Yk7WFl(0I` zB#z$uYoD5l2Z7!4j&pk#I*|X~RPmcwD1MdlGaE?>P&2+k{n&iDqG%uHr2S?wY{{t8ih9F zR+3pCcrj=f8T3>+Td;pe_5Xy)5w3u%z{AFv801XLL6-h^ym?ssTuDy~gli2R=TYgAVpPe%yJZih>SzS}9UC;Tcu` zWaQn~KWSih3(k@YPV9dUR2j>#F;+p$;btOj&CMo}7GkpODadJrq=g@!l0A=9^5l0qovO4O5XvUMXxi~a4H=w0q9 z;Mis0+Sid0L5wJ@f5V$$rPjKQED*`dg%eB9fc0=d^P`9-S(Hud@#PsF?JF*34m~!2H^H&*FPAMtO$P&QxvrS# zKoXF;@uV+*=T^7KkE;E;WP`R)*tq%f2g6`W`s6bG2u2h@0dh$L=sk{7^YQPnYs|WV zzkmv&^`yA|%um33vw$S)tJaPYRQPI?^?Kk`cd5_r)>H6&$~jv}ip-nT)mTwj7+8w<_?fkhUE4jpH@ z&E(kdn!8m_`5cMqMX((@+h7Mslb^u&IxfeT|AR+gBC@dDnplm|=P9R_QpePMs^4v^ z!iv7JCbV;AwD83%Ry}M|-Fgv2Cr!|XHYOSM{6X#;cCUGuY)>=a?gD++gD}-fT|?!O zjlhdS()rGef>+9*XqB9-OKTv(Ct;Rk-G8H1#wmQwImNQ$J!hs)1*8+M^`=%>7wB=b zkTP}-6{v9~z}KvDjFREFLHR(5ZJVZAIs6N{gh;2G7dIcC(rDA6{%k#1$Q?y5ur8jA zc2XUUJDE&L;$~8W*{9qxEB}tTr!#dj{>NMuG7pR>L!^Ofgn3UUzFC1`QJP+bPV}b< zutPbJLai1|2lEccYuxTXHQxdA?Rb)1D2ug5tO<&f5XzSm2*2`N$&$z=UiDMIDPtyk z!?H1LhVubslJ%f?rvsxHG6BPG<3)#bYY7IM%S%f_`1j7do)Z|k3^duWm86B>AJPk- zNb3|x9DsK;-6spKru&>8C6L`Gj-*m2>TA>rMHxOdeiN4+T1ppxdaRkKXk?EO&O=VYO-%(5&!S3)D6XmzcN(elpGpab|lRBC~wXP?;HC58w|{}2kA7l zmc({J@GuK5+v*o)1mtvW&=vHozG}{Nd8LM+hVOgRveThh5p~h7Lp8bzvdZHNx6U?* zq!rQen{N}AdZsg6cvlcUryz_GHY_y_Q{pJ3v$h(1eMe){1N;CtyCx$ne`Ux=B(3)g zH8PTi*_r2(M<$IKltW}tIx3#!I&9EOp(erw(YY4r)A*%|xE4Ns_R%x0hf~EayRdE8 zp7PBEtYz^3{DUIa>!)z#ttZ;ZKm~rJZIMZ2CS#1R6kYNi2TdW+Oa6iHRa6?^Jq8Bz zkXV-iAUnEK$G@lJ;bzTCS71)aA4~^h_wchm7g*$fui8N8@~q-&FTJG+kyYP)1tqKU z(8hgOQ^568y9}zgS0nGp9v9H>$z+xD^64pS*)6x~Xwav^yiJ$+3q9UEyt{c_%#k)Z z_la3R6rBfn>XW{%it3&gf_o;_PCh|`Ey0$L`ucYs%CzD&TrDG)i zR?Eis+rlPwPGYz6ib$*Y->^V|CnApj zYzYkyd0UD|8E1tSa4dMLo?fX3e}}~ES@i!Nv|=M2ax0@MFy!o~c;5ou#A;E9HGC7! z^w#qf$&T`^Xfm0G{VhGZD@M}RVp*IDN)`lhfF)gL6=p@zWH1LUNQWpI5R_qL<)7FC zT=&GmE|QPrX}BiRLW#wX=dBaXU};p(^hA}_ezAAOk)Z>&M} z2o#EwbPW@s0z+i;88zu;xKk?RFy4&;z3|v)1 z&8)(1r2$OBV&il{68pC~%Skop+ax@t>!+Jsp}q7~dk9nO zCkj#Yc?4#(8BVFNI*$Fko`6Y|sY(8#>Si+UN%hbM08iGN@XzTm@l`xm(;?NI8CH4; z4VQ*mKhlSFAh)`uu~yZFKDJMR->Gssv*fgT=n^R@?$QYyQPh=M&Cqrg&U%m*f z1s)cE;G!%fK{z?=OqEJa0k4@uqwRf|5Pt-W@U~wUfNFfctrI|%jP8nqf8ny^?y_c}mx!UvRamjw}+T2ih)R|_7p1rpd8Da9G18U-E zK*I*8V|USP5ph45S@BUxKMyO%0fXRfTa-DnjJQB3ts2}GY1ptA7*2g~rcl*txfPJq z@8?kbBror+Qm>x*A$qUq!DM5Ad#dDqrJ@nwOq@xnbC+Pu(YgL0qw>{}Cl5K|$)k=Y z3L_wDH)|Eczi`s{96Y^+kj6XYf3BfPBHGSzSWUWyx1Axsa{)!bpcFlkRT@sWv5z_RB%9m#@r>YI<%EC_E`Ihkp7RoUJVujd&-G$jq0kN97E|5@kctC9^S%0n^r zShKFvWRz*i(OaXtQ4}Jorh+|Auzjajy_Bu~%}as14>xQv<0Q9Ng%f!cjG27whKgl6 zlxK_uNwb0<4xKuiL`HYT$B28=hx_O!py=6m8aCVJ5%7LLh4qRD>qp&hgNEvX zsc8B?PZgKOL9#qtVUu8G0eQsula7WG@BQLxk~5!}tuEUj|suM+R*lX&Hn?(*TS2lx!6i!lk#M&^*cQF;&j991Y{E z62i~Gs+H&KN)q6jMZc%!tWf!Swzc5E(VcRl=I6d7`K2+JK9Vcvalj7%>&!(Ctqe4w zOoz#`TuH)Ln4<3O@)-C937V3CMetqg;R}pc-XAUrZ!c57H(k+9=3n$g8^EudtC6zIuTwX5AdUiR!@wIx)Vc)n^O% zz=9>SV?+Muoi?STo%oFoyCyoG1W@8%=F*mprUB=aA|) zK(`XpLYr3r&GP*L{zE9vHI=MQu4<*Z@a_b+*=7w75Dzekxl(2Mk+2uv(|F`jyFDGj z>GOtKF8n^hce3!!liiF~3M^!y;nLExF2MD~Wn2e9bVNRFr^7tMgV|5bknR#VYCr3X zjvZ&nE1xQ(6m=C@fgutRvw=WiUZO|EnqMIdgj-5Hu$l| zwjcdBkH`=TRV2&82wSSdVyIO+U-PEohX0%k1Z{@t7#bgs8TWNWV* z?a&Si*o}Y#NnXYlS<7Ls1GnJr4yF{`Wb&&}6=hnx@D@HP&X1;QYUd;v3rN&k(Vigj ziKgX!*OLueyLiW$mb}AJ6SLeuTVQ6UliyCPkXschakiruNn^YowKVR*p#z(H2(p5BQ z{tN6=ESauV5#R#_q z+eD!fyVx|&7c<3#^@)(%@R75939Op_@fsAKZf{R6QQxmQOCI|IrLJq!2jH|7=o}~I z<9G#(9(YfUd@X(Hf`wh60RCrvLTjYY&RccyEPIC$5JOap|k7! zw^23pG&9+s)7i~EcaA3)v?9zrwf>i0(VXHrTkW=5n9={7k6;h6`f@2Qpv{P25oNg5 zt5+r1BZx6t1q+~TjBRAFu z*h=-2oVunyP%9Bzx%vX+z^eDg_D@h8Wh%eLqwx5V+Tk7Tv*H(C-fE?jI9l2hbl$&_ zs6f!U`0D;`+9bl`^QS-2=ikDj#i)A|&9B#L@J6;|$|NFKU@^Jkqm!gV7)fE8xCxUBx= zIS8;`3r=A>ZH%?1beBH7{YEd;OAsW+^$q7`cDWmPQPx zn<9lZ$rx4{UzP%`ks-Ouz3q})N=l!4K}*D=v4tJ=d{Ib5r0OG}Ppl)_<@R6c@!v3( zWo5CsNDDmI)g%9dmii6kCo^?*bqe)5v3t3j1nC}7X1v*zYa!zH3SEitz=nKN&Ft@{ zgE2ptCbsCr$D`zgJhyVNDRcr2dXjn(Q>z#54IThm8aBb~ka+7ti<3&r3mn4EFK=={ zQA5HT1Wt!rgY0LEAf+ATr<;HOg$mqB=PV_$8qf)C@mXU8+6%Kdj$)GpEOBp&u=l20 z8`i6r<2Sf@7*5855#Xfx4tOs!=9C=w^`m>>9*bp=20Y7GIJ_MeFMUGIV@CMqV7Bb3 z>`#M&#)7)i*SJJo>7=4CE8HPrG#O{1!4VuJ9BXm47CEm!K0M-cNDx^jJws|8Q||aDZb9ewb(3 zE5iCy-k)lqd@K?LYoMqnZaN3@H19X|5kMj|wtSs#>cPVsG&>aZQyWb-5a=o58o7#U zGcuA+Jh?*T^)4Vt>8RwFIAuB(NE>7;A7%@x^@7VJizuCW&;b#=O_k%whtm2YFa55p ziPFleqIz93XSq8%nJh^MpsmwL)dxGoMctgfHrykl)-a~G3&ZV zoGbseNZpX!g(ZHp?e~78-RBriqXHsOnEW%N`Eswd~a+e zQWzeMa~`=(7WEgMGx%e0m2t1xU=`-iG<%3#FaOe|QqlcC7wTm(J1_hxXxrk$%EvsD z!3Xseqp&I%jq)_n4)KGS_@fgc%B!pxRa!2zz^!i>?)+T3d-nRz>*++*g zFQ*a)??ijHfMX{m5ZiB`!u|u|)UtkoQl(9pe)b2r_N?(a!_weInx5tnUPf8-H(X*L z?u`px#+?VOXdYCaDJIV_P)0>VVGBECKi`+qW@-%3&$^)<4==zhTp)|LuqB{>0s#ar zzQpRjv#SNMCx(PY?98~&%@oX_aVIS}rY`g}KkFPKhA%HmDGVKV{u-k_ZZ>s#57ONn z!UDqEwmlJd?zBo@O1XTRil>&RW|@zj7Hr^~y3EjmCKG|ScbhK3OqLI%dLCexwy(s6 z=n1W*h#f`8HXV}BLAoT+aBzLG^cA&E0t=#xUMMN+y;NbEzh)ebG_GW3IEwq>iY%!< z+^C^Ht6`-KBMv6s=S)~KX{4sdgw-U@5=ot5+>8@E70$%`E z^cyaX!55RVCFnOW4@QfPAEv4BswboDTmiK1VG7HwU4wZM4CZh{wen6wi|e3T zjUaf^f}N2fm>g@mUK(2k`bYx&-GhsEMTOAJCFQRaR03V#lU?Q4^Du$;`chvkgwFZ+ zGS&Cj;)K;dF2G39X;j*1<^B7+jm2g!nJfXm!Ny=$BmK%?tNLPSJ?3%^6be135oyNdR=3ntg=`#$CRS^e7r1YCsZ&vRx z^m715`>cKKDQPM?b({BV8LXfqc?<#Zy9oIO)~TS{pJpLigUGI4tHHwOV@~bd`QznG z?}_68QOLLDf$CG6Se%|fR$gHpdjz$cpqz&pS3F$B|W1q|)oa2H;4(nZJyuBAd208jcT$Qa@xk-S`e>wXBF!1SmIlxxK2KzL^dc z8>>SH8DJFku&QNQcj(t-O}gtO&0XM+Sjc?+35pL@vufEOXK>c8ACh)29tT^c06 z%g>PSa%v}90t=DwQ9_jpR-P{F^}%q9S;Q*_fU?KH>Qa(_2tb~uJlD zs~@`t{LF|*#->)M=oj^FC0*s1ZtHbuop&K8NAd}DXLtbQxMA_vK@;Ck+~#nKF3Ou{ zmh?P3JN4B82ZT(6XGOC@UtjcKWrUi8nu#*yIp(dxPFq%1)+yKdIOdgD6)`R2aggXD zmWVFaZEpi3eOynGZ|UNe8`KW`23Xi{TV6&EgN${)7^~JB1MA0i%t#Lza}X$Ep%TtZ z%6Y>IWTK}#^mDnl4RfcS(31RiaY^GvS< zJWUA`GHKiTMZ41e>yz)czsY#Eblh)I+kf&B^VPKi!7LH`t{oIz7H7VPTcAy^NJ*efQJa52xYF)S%6??urnf#hl zAQnmN412Q7}q`ZE0(sieRlhvP{~ zO6|EIM}M4We9QFK3bL=a%=EVTIXLoSU_(c{QticZ8%3FD0>2A*78g*3Ki}~)5qAp9 zYuwgTX5?(a*6dhI@mc0&`&o7$V}6a(_SMOzI}!c^zM)qN=nPaDSYN*0>To?u02nM1 zI}_5VWdTnF|5N~m;=2_;FsR7?9MSu0^V8QeKWb|)31{9QBPo6Z9HdK7lZ23Asy|;r zE6hv==r?%)(z{qU1*CGgl@%0We&l%bZG*Hfn+ll>d)I<@jIJV2Sdj;JP2C!%zloQL zJej4w2`0=H_E^e!%PA?N@Aq7$a8Lr14My0KLs7(WTVeIGTs{l>V6wetToSx;K_DE3 zaQf5uA?H*ol+arNWjye~W65q2>xBxiPPc9rDb&ItySnzcV<{x=7vgp>_%+L~i4b;@ zj6w164(!>Lw7w&n8$h{ADhd_#_MjlQ9%NKhQsRVi(vg6KoZA)Bqd}nNClErdvGibU zHew&)&G(`npFXAP*R8We?j^pw36DY|W56dXWLN$bzRygQkP?`0p;jIr83#zy&4+;cwy49v-A|HC^rwTK*@hnW)KN!Dz#H7}L#T)dCDWW%A zp`SDkDkES`dEbyAs}Br!zQSCV{Bb8ElaRJ6NAWRB2nS?pT2?w}T%|j5a%PCp#+C`^hF%Z0|i2CLr=Tfh1P&; z+EIaJuiS^LV6^_^ZNmmBf zK0T~3V~G000S$Q{9<{bicSTwn2Ibo9np21AEwPIG^JD zXmYmvlVr)IS9uJ=zlIwQHhHXom|%(baq`}vL6?9-r{HvwjOIm$aUJxHj{A|{&%4=u z1LaB9$|X55^u_z--3sZ0_=D-1l*7KY2gMwPIYhZQ`Tm?~L|K{ABG?wCUtuWMUi(Y+ zWLG;akJ&)m?cQ1sXAQ}&_S}h^IrP52`0+aFr&2nVUVk>B7PgRFrK)_^;B?FPdBo*v z#Wm*J8hD!6T4@y2MwI?Hxy8Q_UL}jobjm&PLi~JRpr?~=*|6TQH`Y8*+Y@ui7mL+; z)wvspE;@7;)qndUN+N{beq9=;ZGN+kBXvf7GUb*_#mZey>3jn$9gg8gVm~a$NrB_D zq`nxx#mZzqKdhz3sc`76kPKJN0Eez0bONG8tC)U>i(pyYCQCz#admC>mUgsBHz@rt9a_siE9rg-Cv{~ zU&7PY&bi-N>WA)~8B=7)HiE-Wy`wsrQdL-nHkDF8Rl-tq^r@uTxz@n8E0N%A?E-rC z0eRJBe9vVz6i>L~GSHcy0FQP)?c=&FirPUE_yA_We7zpn(r~cz%HxYkW^0$CpQ7El zf@krEE^!?Jc9!{r0EL)dr5SY_;fj}ZQ5J^Zh&+B zlYZA<=~&4ueGD%4ma{XW#v|f;Ywy1u9;_a|W6WXl=71o{v`F`G{t4?N;S3getHJ>O{f_g~Nx!(Lgf)m3 z+<34SQA`ff4>kPv@As{5aV<<&JOZAK`8WgPRwU-L-p8^fZAc9qD8twAweUd(H#oZ> zd4=B4ZzUV9Gu&!vD(Z_DCii|f9DZFMtttzhES3+q+j=zP>>vw-Ba~kpjpWZp@mgJugZgdXZrJ~OPUM6hyhGmd*y2NKLT^wf5Y7~*u$ZQnh z*Y^lNt8iLPoPWQE&UB#sMfTC(c$GTsk%Wt%Hl4?o!AB=HetFYFN)=%ldvF=Ql+3x> ztATA{og2F~cKO42(rA9jH|YJnvrkk&h#yIw=E zGEEpJcj8druA<-cxyu0Wi^QtjlJ_C>qDU<)Tb%)n*3rF>@)LBu;nZ9=M(;j!y{^&e zk`mA1KqA1)Lb?Mt@rKkhxmrz+dYPHKbSoo9-d|k?JFG^l7EP!|M8HwaY;XJL1M2x} zf|0-O5vBY>y%J2az+0Q0zr`ErCRe*F_8_eqqf5s@c4zX&EMdh?VB|b=S}~W)&(L@? zyB~NdR;1UyT#0?Xxw%O=@ey8cTT~&Paxl*P|3lVy$5Y+E|J$;6wxqJN$1zh$_7)-| zTgEZ6iBc#dp~5k;N660Xl(HQ&duBz5tjxaG>F)FU=l55S`+jua>ivGduGe)v*A;v5 zdBq5kX-+%$Vh!j-%oUGC&LgM2WbUtze_eT}G*+hp-w5r<$5+qVXES#TGK&AJL956gwoVw2)92uSSmH zINXL|yewva9z1;U$n$94LUpy`b)uZTFklfPv#l{g+NWIyc^0hQLC!`tR z*4nZ(47n(FaAiLffAor`PHTtKHG#CfOgrYtd>q>5IxMGSk3l(&|5>YiBIZL80f`(d zp6%x&RAJI3pQW@x+l_Orp1TaAOq+`Rr^3*n%=5@^h56~USv}YUd`Kg_HgIW@W;KX$ zlQ!3f>m7Tf7RCJ~bV%X1nSP?FCW$w3jgEa+vL=%s93Fu7l;_R|2#>v1H4R=gVc45{ z&r!8|X&39?9$LIU54b`1@syKa|1D!=&H;)f#J^ousgdaF~=61YC` zh>eWKwE58^maiMRR#KDdCXhSu?KeqVj)3(mCZ>gbU%k4*kdqdTRl=nHfhYH~!C@9e zNTSR-`pmsZD2A?|&MRw-eEsS=M~teSaBIj}Rw=tjz5WMQ6G#RnQq7{YK1;F}>&^x} zkCeC9A@56fkgSb=u?>kNT6H1q;mVw?FU{OKJhtzazn=JEZ$^_`QL&@nMNiSh&9z^$ zW5*f3YA}-Vv+EZo>yiH7ckIpZU5INxQq^`aH;D|%jf3+p=vRl~&|%9c9jBu$@J19z zN|;^-M8uINoQiyd`kQ=6ZR7HpbBPV{_7c&x$ito=%zv*zWn)}tZ=BOxALTx!HK|#z zo|iH+C1!b1!A+&JAqd zYo;x;NROX>LjKzyj%cc^s0H=tPkAW`+|R{Q4M7t;4__7hgpnn}?cDdSCnd(to42!*SDNSS@35&u?SKZRRIO;JXNzMH7jUV?d$AVh zNsxcMk&2o+`Sy6}c)M3Rb5gj1?Q-ZCn#1_^m&6tVm&!2;Ka7#BzihqAx(VCWfc*`v z+6TA&GhEMeom&hbCq;Rc6x0|y5*A!;J>l3~dpg;zW{l$oqF@oHrJ>H-*lG^S2zP)c zp8k37fR}&O_v!V0(c>Cn({Ge2N&A<(#s{bngRP)%c1kBXj&`8*wbfD#6(8x9_?F@Y zwVX~yv?pB-kMB$Yl67BtBfBtq*@sQSSoS0Cll{mqcgtjBrN?lCF+6SDiK&N*xvB1z zZ{6Kx5k^W3x`aUtnL&fzG)}ZPVmb*C3CyvFJqm$)ny-oQ&Yn>=fjqzE9bP15tuwsX zLB6SMdPIIc)7M^}`HyOb9RWW|UQLe$RQcDMb#GDI$477n$>%BlEt`Z@cb&X{fSBcy~w)kc}V>Z?$h^%b7hVQWC z2Tn5XrT{4-Zz6m5nlXE|9%>n_a5B~ji~fPDBUARNuHi?-_X%2*KM_|OZ)BYF8MV&C zyoj9S<;q_OX#Dq@1POT=l?aw$56}V#?mI{o6S7{*Iv-CH+=w}QuQ6Z>h3y;7@7!s! z6Fh6S#GycWSHAcl2^XNB{pgnTh?BO8w_S7e-S|JZrY>yA8;7GM7Qp4MYh#_c-p?jEX%!oOjb%!^k32b z&95Y)deRvvvniv03yODrk1g#QXt5ri+7?|9`J8;C)gK3)+$0&2pO(j0P4}uCZ}~W= z1d4XPKwbD=-AN2q*n!uH& zCgTl$hS!F}$t+H}VHo9k*_i48SisCO$}&ufiyBkPvzK@&Fso_SxJljdEf(PFuf|#@ z`kC5!tt<;N0=)VPgL_>o@>%`Ja1*-{<%M1ISe-C0p|3+V>T0+fV~GkT;Bx9{53Qsd zll+~A_IMJ=YDbS0a|1V?OVjQ!_tOLi+STMDBNSdd4rZ$U{zMVbi$DE)Flsz$wo;dx zqCGimE4SV$&31dSiA;2T&JE?I}!!Q;q zw^&BREo)5#?IY9@FyE(=s!^;Bn6&d;!N$3m()X!RlqPVD48zXwE8TY0e{Z*3XE7W0 z3Mym^dl+KvhD=No&O?&v;8zf7Z!pW|_K|O%?7!kjS;izcOEvd3mhoN6f8A8*2R!8| znqVF-jT;N@PL6h2sfC1TeBGM!z$7@k;fjWK7OaNgZHVhzS2 zS8}_;tLc52Y701Q|IYdh#yj5esQbgF*Mjd&sM8<}5>gY#S8yDvZrCVk7M-!nRB&o6 zG;?OH&Pl$apLE1rU0jHN0yZ1cSaOTjlonC6HnWA4<$ydzcpcM~5c!g2`A*X?uXJv$ zR3msk1$=OBCP8IW@(0>Q{|(t1m$3namp^l|25;}2b(dh*xU5mQ`10)JQf&R)Od)af zqc;=3NKoQY@ADookfP|a;yOHc+Anl`Y|k0o<7Y*0UviS?%e6HVET8Ti6nOPSWa%ylV z5obqrU)vPU{dh7xR5TYPRi#So^wTaFMwwq=2S^Pu$QL*I5LNmnPh>ZHN(yjwnf0nc z&7BnXH;=f1kAI@ZzFyj<$?lL9hAK2{3KgHFFW4|nc&&)*^xGfk3L)rC%(=A!1|U*<6u zVP2()Lq0H^a(*LK#zTw@4Q;-A`CDm$I(_EbtrFEITM=OIY?H_TJsSAZPLc`_qkK9a zzElJ2*O-WT^4X8@SGGuC#qRhAUAb>nc7KR;*qg3zHtM@x@*cRvvMXl(<8KG?UV{# z(O<#IGKDq@Ef`cSR3+u)azyz+kQXa;T?cpS>j!)I7)EnsSXd_}@@@k=T14shYnxa# z+K7}PKtQGD?NQ%Fob+3oQ)JS&aVjl#b6mBeo)YEB#+3iUsUCMOPR9NZ{Ic-8>F^aB zm*Zvo9KR9O01GWuT|Ko@ynd^K5QBDc7L-jRU8_l!t6*UBUx+73vv2MXhI>lNX8t`O z)`QAPaxpU3w~LDSdru!g3NJTls;F#?Bg??`gBa7Fgi)lK418NR7T9R46Qp_a&@wz? z&KIDAqYlOb12Co6I1Lr_PlR~-t_)o2*ou97S-J3Vv;hVdIs%l;e#*U4HNRLi4K2>T%zM)+L`ssNs5Hu!P&>d!)m6y}uo(;d?w~5!xscdUMmg`*Jk? z;!3nWjXt=F0L&DB`l{jnyD-!%;PQ!wMCSd^ku)SCd$8|EG`?iRd)&=$t(jXSX)p4{ zyBJk#uF!Sasbn07aV!Y&nAa{VElmn;a=Iw3Dl(dSDAz%sc4f}KeroJkFY>B@ovR!? z19!YSJP9$;uD~&HKm0;6h1-FZcCz`+hP8g21=jclxGGc6+|OqZ`2L3b@iH_mOLgRh zgxYtMeCg}(mUo(~yT242{-CFsk!-3wiMut661Y^&4!vCDd+ee8-H~h->|qH*r~Rzx>KYj^vhoxhy|{G}-a8@0LbtOMgpIH0DBnpTU!1R z4#cRY_IQ=Q+CsUw+b1mAgZ;%KLV@wwmpyZ%`N2E%#}YJZm@*#8C83>02yk&#~R^;>yUb@E?K<)YfToIeG8J*M!j?o{9(LHmPLRkWW!$J9q< zIRuahqHA#+X~EQ&`e>IQLed_KV=^ByPsh*A0`MIYvQWbwzlSeclO?XpZz2q&4T_m& z*EzB)X+E^y$*NYn>U7iLl{hj_iaNC&lY;4H*L|H5(>(kB^VP-10jvF$~x?)3T?F z?!IW7sb=kN_VTM3Inu(tX_vO5{W)M@jjqD(PCJ3w$i~0`JLmNe`MNd*k>=_YCaVbrx%o;$Vd5)sYT{F1 zvd-BV4Jj-f{IP!SW`bwR&ZM%oEh(z-v8K8smt9&bJ|$J0k~LdvB+mUjT%9EKakmIZ zN=|;hXK5;~_w-xgISemlXwV=UtjlIh9(r%$VWJN}yXlI9!H!vK`RmbE%S=*2{ZqmZ z0S3#PK`m@+%Wo;q?+S7)KU2N_SzpQ+V7i!qfKup}663L-``52uA3XbVm#>nf+HLB# z(1&B+KRW4k_$c{Tw_onoilCGC05m)8O}v#XdGDqF>95LbILqE#r7I4AooJkcRTa}8 zqS+yVoEvYti?`=`lY4sYlxZwm+ZE<#Ut~+ddot<>*b?D#Z8ZRZ{E~>{0g=UDMOH^{a!<)iyJHqBzav`IgaSnn?)65VjFS5a*2P) zsw%6*KlS>mVzJ2_5dN?0)5Bp>^`0+^Xo^u@AUo%@88Txz=;Tm?^dv;7g`L5vF}TqX zc)cjS5+mCsy=YP2^kF40RdebtM>qh*IR|-pV(19yZ9C!@Sj z$zT?>qOKmCj<;-er%+AzMkk-=Qt|(~wtO7E1c&a|AJypXL)zzEo_3sE&|cuIxTS)Y@U6cNWP-a2&4o=(7ER&)HGR6D8~pV?ST zD+U*1%ZrOBEAp5Su{}KnueBUC!hp3N5?o01=Pr{9x4d#on@ zTk;v7*+y)^?oDRXa>m9&WEjjBTkJgO$YEcWAw^Xh>Gm2!V=ShJ1$rQEVd50bukHj+ zm~9$#vgd`uXvdZM`ug=xcn;%SM_qjRh)O;l%Qw{M+ij-8FzjoM-@H^_@mZsdrTKS z0y@!NF}F#C%zU&sZRr$5Ug0N7Pk9?8LlX3J{z<2+g*h^Npt0Hx|LU`|bJonoI_0hH zY`44+1aOWPXi9%FHE@nNtq+%_E?YLzvpag)j?(4HMW_O-NxFJ~{kFOPHmt+LZj5nl zmQ({lfC0yYmQeA#h~k5minr^7@CER^X|>`Pphr@&dT3b!>S8Gg*0ajBI@zVyHVeaM zb1fZvSckISJg$`F*=NCgTsLK7FU*rB8xQ?^QvBD$?76PZa%|QW+;T%Ri;e|iGu6lb zK-!3r%3&TJ2JARxBuq$_OOm5z*^TKLC3Q!MoZ1QG-dp}Uwzd*QWF=0Tc6PJF@ZSaX?XAHLGOc3$SDoi>ejpr=fIYS zk|Mgz3Gy67{%C(b1~_){T??fK&Kj(`%?|sqK7R0lES@$!!au7y&1WJ0W+xV=TD@+Q zM2U)exqQ^s6*(B0_=Y<(;xx^k4bNR3d$T>xhG9#Uk^EzeOHfSfzrl7_C0n2H>~SW8_mRvjkUh9~^X9 z+D^FEYW@Cw<yM}upJVmHtnLKdvwY+FP~gw40LU_2of@fm{_tsHiu2zdvjA^JU=t4XM77%tWBg@> z+wZR`6H&*k#_DVRaZd7AsYf#>sXlhR<}KUhD?m3wbX7S z=lxOU4tfOE)W97)pB?wDcQZnihyy)x`ySjkwo%k|bESN$b+pv`D?Lj5sOf+ zRHnhux!eIB85aFGV_D?hGehpQMM6gN+01UAI! z@Rz&gQe3|Ro04Z-@o5hDk8%qx|KmKBb#b#;g#>SX=we1pq~JY~9(TNu5dTW-el)Yl zF=)$J@*nJdZ+H1F@sO~nxOfp8X*4{PICLLcEC*RY8;6sKH-C&i|FEU9xADJ&B&HV{ z!QQxbEAOktJ)7Jwsk#Rs;XSjBqyy$}}$>cIpD9)^q@f_sgC;dmOEk_+TdePGQ5JuSKn;OV_VQVaFZK@UQ_&K zHN-b+sKm0VF@0qzW;yt>^@q8(nCBnpsC+w2k<0U_^EFXcI+^q085G}24G65eAohpJ zgyXTNS>njNUw(Pn^~>LZ)XT}-)+=I!%Q+(%&c*GKt{b)@{@dzFwB@ z>JhM@m(1e=268^RjlEdp{!=Lp9zTj(+b}Kh<0+5hOZ482J5WUC<0#=z9$f= zAnZC${ducl5f34Jr+#S1efaHX1rz5?IzG+y#l?ODw`S3+vVt3mkH31Vi@2k=`_ z_Wl^FT-Krjti9!t=ufNe;Jg~}nA`---%oF!?Jk$3l}kJFf6;!!Fh_nRipmuMfjhQ) zRxjpWchMkV74KJqn0LZ^pXqd7yEH;x7gTWn8@$93@hYkH9R&EJN7mXT9ZywS6_0+C zFJ4X^1m4PlA6xR0xTM;qg0_3|q~834FqP~{0q^C&hH+|78z`rHRsZ6n%J3T;zJTmDgp1;GKSo(0JZkSoCd+fge4Fa#?N;9bHJ(d1RtJbuDh+Fp@sM0l#lzg&{ z7tGy5M>FvDf-ro*V@+#d8BGGoL1Mxcy7(EgXa{TP*{F4NuwbUAKDK9r66p%c8*

l{)hjcg#R))Tr!e#*vU;tgV3rSLd<4~wz>qjXSmJ%mHFYnh*B@Bvo0fty z&|3G088K3kZx3EBe~u9`Tud9TTFU1Rl~8^tv!cSxrZeH011dFc9i%D1)b^AHraZZe z>>GepYeX;+2ZTB4i@vA8ozLb{4uFet8)-6T{lxqeqLRwh{lb|V=V{{+yA564#T}~UkaM9ix z&U`*&Lj;ai$2Q^arl94{4IJ)y0s2%EaX!85fwLsTar$A6K-U3)1H|G5NqVbHHg(+c zFgFyIS-A^ykA}c5GeW150NXZ@^Yd}8?xpv`%TC#~T&ia`T}G>5e#GWXJm z0SI{e3T(Q93_3lUckWg++}L0rYP?E=Cii~MlZv1p*{{s+8jNZqbp<^lvoVdVNePtu zXIqe8Js~=E$LJfehp`v%O)BV*oTo0(@$u|JoZz5uY zWLd1!!kLHaY8|?n*X6PW`;)LEq$cGJ=cJ%IGb(L|r7qA#HFhdks$$tVz2GcT=zhSYD_EZT$j`1!byGy5h@Em$v|J&>d(GRv`vd^eT7^PG9Fv!9VU+f8c8n;%uuHhPLr$Wz zlJsV&afs)JjH-{ZQ_j=ae}SZ<#f=4e#vtATmLYWjcP+&4e)Z6k+hdXQtZN>E0+^ZH z#5*O~i`DrOiIbR{%~#ZV8!Tu=tJ5#@M_KxOFqMjT#^ceHG}p)X_%VU>4GUw_^Hj7K zn00hING?AUN*{|rC%#CU-uuV85=D&n9|dpo4G8akwHb;UC3jVm*uBB2s;)Rv)XD1J zq(>?CvV%l7-67qK^{cSZBA_MF@QU0%RVca!1YX7ZDqnU1NZW83m3K(2{p?-FfxhkUCn@aU{)Z|a)fnLw-QV<^*p~TK%={e2dcz!)3sTuQC zfqiKE_tDYm@=N;|sRr)?3xcLNG%avW z?+S=za|$I-C1tIFx2I!c@Ukt4K@ z6=6$0M@>pJjfp5%^%sS2Zu&6jh7-;5ZFXeGmQed8>bT%K z21y8&2jItJMlbAmD@yarFtu^ZCz@JW{iVxrHkyF z{%)P-=r&~MCoG>3|HN7n_pXHJ{Z$NV_F71;x zJ$jyUZ@eq4-$SkM$oo)uK{-RH9NQ*skvkJqd(+RbXjnNn)6fBHTm_o1c0OKzk!Cdf zo{CUsBhskw=IL{OrA@xbz+XL&UvNEpzQSoit0Z<$eqJ z#tXtJmyQMD?aqTP&m`Yu)EYR&F{ltEqy>VqpAh`q=fRzD6U z5?|T=;ks@L_{N$D`X5}UHFNOeY zT#h?GCu|2#A{)E(RB>#C|bhfo{ygLh`M2FWbL(Sli zO>1=+{^cqoc3b?1phx?hjg6FvB$Ht5G&b%1uCmY5Xk`hXCEw=uets&bz78?H_;=%x zPS$Ejrj+T&{J*p-^J1DJE<`rowBxr?Z_vdD{aV86iYj&ygcg5W&>W(<1C)N#rWWn( zy)u2KQ=FWf0q+1qBL9zbMY>5p9ug51{nJqB#rh2by4hUx3HaHjE4fi8Yp9Ir41ede zMQ7bu6}6H3t)gXLWwR)biO_+P?Oz>@=2i=#gef9h!qHm-F(M}+TDRhV%cmVR2RrrI z0Wuh9{(SHj3(LgebCX=&lF`{$e7ga&9tw|l0YiW2>sOu;c~p)+-e7jU;$mTIf{RF4 zY^3^Y2$;q4m!2BaCXP;Oa|@D7nS%+1et;fJC>E6n%3nIuv^>~8a~>7-qs=F_iL`ur z)q&fw;d1k{U+`^4PKA5AqY%3hMa^Nx{RJv{I?V+X=v((XmcaPF9iICl|6Upr0yYw6Dq) zoR7X^01LEra5X>6+y_`L0n&0oq5cJSK#{hlLAudB?6aO{7=b@1V*se~-9iUSuS>V< zeu9QA8qA<@Z&Gqc>;X!2`&?8knC$!nGeVKqNo@;ElInTZ94cm!h?JMrznwC_;Qa(J z)#lXKJ6Wx_$`>c*3h>dp&xlq*a+6{KZVf+zA)q)BZfBO zjGL|aY*T9djq6MYUw-gbUCp|mLqt|jT_;H+a?M3T?e5)_+OZj?D<|HT)a{t9^Gag_f|+od#IpZ`clGpV4vmdl9HvG*f!giLVPIe%-0q z?*eLr&5Ziz+%zV)cFf5*cIBm;@x8}OPkG|>$pH>nSN|XzWcy{O+ zn8{i}m4uNrJDVaJl4SAqDfVGc4fQzAQXwX?8}NPGE)^Ea*v*;z zeV8-9s(Z+Z@F*_3s;KkoJ=eNKUj&|rCpeVH-Spi@3X(z1udD6SO}{P4#8M@6_MZ%k z%cQ1P=FpN#HEV3m6NPx)5P=KLgd!8J2}Y6AellAR0%!CA64+&fg)swJm8tzIICST+D9z@yAtb zud36hwlgEU(ok$SfmB3knZJ3z@y^!`Qyr&XK;g*^zDq%addZVIz2F6_nQX@-Qa+@# z&t_|(Ql{leeG|@hL4mnQEi5r3y-@)wg(C;V@Y`m0lz!!RrdE;h&AThYBdm=A#)g=B zUC%Sde52?$?Q%V*Qu&82xX<~!i=wS{A`2~$G>+YDEAjdN<^sfkxjxQQ>t3$$HkaSi z!RMm;PSt{wzdJ!ECXLxeFFoDh)-pM0?_7LjnoM9n$+hobXZD?mFq2>;>jS=l8rv3- zi#?Zu228xqq5vZRE3#}V)?e-CegQZs(^ zlEC<~fbC3MH1!;yDEHj#t&T$n$bi~MJjaUiExYIFIYUUL`2{=`C^6Wu058BQpwJ;C zk_PMb{SeL-qqj*IN@@{N4+ zw+@jaepMOEkgV3C)d$K!WOLN_whhUfyxh`sVGDtm-@GC~ndP2`*3Xc0ic$HmIAeA@^2v@eZ_ zJ(ClDc@dBaH9p#eCO?*LMPfgC&k!vMrc5PvlWXI zBt?*QH%Mjg>NT2Ye1Jp{AWVD8b4td}OB7ISa25*bNDX?jAg*%ck-7%n>(FA>{wLk7 zP>Os;z)fa>N=xQ$cVWlM!YOf-QbI0;#uZ}{M#*QNGJ=no1^{6>>4M@+G+CZh@~ztnOOa4sbp%l|(v1|QEjZB|2Ons@*5r_fQ) zo1Ys=*ls-uip)0uh~MmXysTg{C>)NA5g`@7qX@OvA)oeFgoj?QXB6pdG30+)?w#}X z$pS_N)T5~h1dAs?eP;m}F@ZU;S$7X_TH)K1^aiNx-Jd-GgJN5L01lcw!TOCOAJ)YL zdaLYrsM_ZCj!m`dlZ=wFc)=J#iQ>US?xnBaOvB%Q<5l>Cf^YP0$6VfC!TmKnziX=| zT@*xGBU#1f7R@i{TmScBwOn)neb5WjL0`R?WguE?#cqZ1%O%Lv$RT_gcSC6Rj_DPx zXjVm?3`6Le4wB~T9Cc~MgG4<@Rwy~N!kD3AJafCT3Fb}Ax)zbj)<$z39A=j%3zB}7 zi()y7Z?5@Zdv_=dz6mhSMJvTyY!l&#i@!Dk-v*wvDvn zqmNr0mc-#tv>gNZpq%Ry=MNBjono50$lTXla>AjIMa}Kl!@2bQ^z1UMf-Atp^70vJ z%d!&yH?_wUKkE81`LU+%7wzKx;RQ~XC zn01B#jqCON2vE+x2dxq>9unTG^lS6t8P6gKz~q5f*aL4KvS__qvCQWr1Z(~JxTC5{ z|B+Av|LLSx47Hhm-(qL_CExUdtsblVE5cOMO(X_a`scN!{(jNtq-`JPAZ_n6#t?bk zb5XwULF$LkE@?G}qDjWJF@k%H$)t zR6CB<-5kePr|;NmnUEwj4nktpH-yx2iPP0pp>a0Xg7THL*=hX zuaXMMoWoPomve+DTmyks0BCo#$rnrE*hX^l+KqClXVGa@cl#>iL%BFPP5X`gG{|KJ z=ux#I3*V9aI&$F&y{Xb3!oS=*7shXBZOK_AzLgRq#^x+OCpJ_BFN+4f^uB}hR5A2D z!w`^U%y1pE8-IOqBDyMytz2IH(ZSsmiuUqQZc`3gB;9P{2A88GAEt7@AV9BzPIR}o z2j4$r?1Dh>uqta=Eu0Y^*Wbjkc|PmtJdBCA@~nqQ&1Xc(eB>dq3gm_tWOl!}_p#dz z3=B5n#~MSL;ZWC3ANxzT=MNx%y`1kjnm$y@q8}0fin7cF&2T1A;F~KUx)bSmmH7r) z#8^|_k_nF&O;;pND%e#Wm0d(rKWwg2{>NS(rY;wBN-a9+zNP%9Ii}JGZ+(Hz8W zyYR+$!!?dB5yZ~JW~#_9bA&i)&0$3-v^Z3SLez@R!gdfwmU2M@1yZtMgtg#orwXJW z)a<;lh$F6EljFlD3&TW{83X!2x$%lz$Sk1u3w|xBY3dNtGpP9BB5JNC>V11@p^<9I zmGgz)V|(h46=o!;H+8ji%7a=?GF(A>;5Q3OCC6fcTU=}j`Le$amTv`6)c9+~SPdvy zO=$Mmiw&GhnvcqWM0P>jHRQT`DqFCZ4e@h`>p3ssb(LcyeowW2wY|24PHLbM*{AQI z2Jc8HKwEyoSS#U|I4oqgpB~$sGfVn`z;oZ^T zHO;305;e|#*&Mg2fbI?-o6J=%N0Pp{|VqnQ85p2WPAclikFA7Q@#bWP9wFPb$RVE1D5FHlugkl;Ztg zz=hopWB`IX+;<@TIal%rugrxa>eN5;n>?Q&3@5aX4olQX^Rb#GRUzs}?DdLiz84Za z>9mmwP5P1479g9aSj68Z1t|a8#28GvpMPg+EqK=_T<#E@54g^OwX0@8oXwpejxNDO z%}Zt2KsYpu9i_&h?3;VZ5meK7MjY75(IN|eKBm8Ed|kDNGzpqaG!Qr5I#8DWRLfos z<^`jBg&rZ0Uett_l;mHbK77RuBm_VXBuuD@q^MxAAyNph?ZkNi(_PTXZ(eI-`}NFr zK}YFh=5{hf$~-5O-C|haWgpd=Q!K;GA!q0G>heQdvl|`(8;!E@*S-KaahSktGL2L! zC?*X`|G*g5O}OAeaX8gzgYpTAeFe(i{Vaq?Gz!iz=4RXG{)+tYh=&++x|Q)@B^zK1 z@-MwRsk$Dy2a*KSFMR{W7ICwz1Rl1gWq$tv?Uha>2J!9_5MMdbvMuDA0hRnR*}0MZmm@U ze%m}4hk&r4e+I=YO1DVF&S!OXbt%iv-b>+1RYt)fDOG$Jg)xkyzr;tbEw8TBU?-%o zIx!W%8>*E(!B-1I``hZslgPzMH_1lkf1F%D`28%l)OSJ1qG7kh-l1ZX0$;qdP*Ynq z_Y%w>U&$~~MSwn!tgyQ<gD2JD_$ z*;+d!Y0WxDH}XTAIi9XMn~~u12aW`OzhfhnD!5Ox@+v|I{w}w%)tsN?%4vH1+ynlo z3aSG2gtdf}ZDrf_52j*tUlEAvu1_4M2BIit>z4j*z~6xksu8jUz^bxNlf^Vu&egPJ z#47fEAffVc%_@lIh-oVl&=sx!Su=am=G-JrN&dkIc5Inmn3G?-zuAUv#}4+~cmR>61MhU<5xaqrNm zfC0(yeKK3pf=AWYa&RCWEY#6%(~tBR=*8%oL+Snm8VQQxpL%_?;V>;t{P5dXm?wW7 z(BH45cFfSwFwIT>Lq^t0QMqHzv6R1DTASO(Z`6&EFop@N_xFzvQCZz+6ET0Nd-e(P!d<>PT#i_ z&X)!p=MQ!fifH8yKonhrb((;WgC-(p|NCnQYIg%h=)0w}bIX=gmf~qwvOs5QJ0Heu zwfPdP2EdViJ8DgPxci{i7F+ZP47&Z}j@RS_q!>VhF|ZvKTLo?Ym$O4@eB>~HCI0=8 zQkrZu*F)s@YcNGiTb+DGBdh}RD0jtv6Cw=1QOuV$ymS;Z<{S6!-Ma|FiHxuJaWyI@ z@ZB$Yo$T~z|M0XFN}nOcB)S+8z9*h~5N^c{Wax9qV=A%N*S^%l+@7EU0eBC&acE&V z!s?!(x56tg_W<#roG5W)CwWvc33%w#`8g~7 zU@4Mk02fTKI4f1C>av+NpgOoznQ~FXemeSU>~bF=Qn(zf;7m%KDM-{t`ZlZ;pR~Ru{nB)K%$QpZao2P7 z1xl@rV3DDx%0D$t#iUMa)f4ar^Fy+~msuOtdjfxwG{dXndfC^$lyST~d26Q%yWCqt z?HSV%_TzCI9-p)4Y%OzE8N1s*j1}D8lrE@~1Jmjze4N`|0AECyQO?*hC?vz{K3B(6 zvO^K25haA_q&rXujP~g|{FXRg`db$V0s?hvQK9oAV4Qf*z_LF^q#<#(pdnAQQXQdS zQz1Wjfvo^K@?V)P>|T08XX;s^p84*&73|n9z3}#4+V6>VR!~(5MCQ{I4AIO@ufHXJ zy4drLm?qxKF-ST`+&Y5B)r&VdkSlj&uvYMP?0iaWTwH{fQmWJM+s@I-TiGC*N2fs> zSl23ruFc(q0ojal5l|Kcw^hL>?h{F+NS&xBg!Mn_uveyer_5CB!!mcAN0_NHgw*~s ze1>jmr6f!7e^4E#N4e)^_BqDqah2MFo*CiXX`n5suyGSL+Z;!J=bsk$gKO~HWr^vg z$JSc2!Mq9Aq{)<#!;-h^K0Dx}qqtnY0;RqQ)n@=!mHT~Rb#K*sx^FqERdK7gra~r* zR!FRe8L?BKh4IFvp=Z(xm+5-Bs&9iYHV51O4coZ`HR6ke4Sv6j(Z30c3J!mS*!sK5 z7ikR3SN%2W!C!tRh)817QxY4#o$Hume8~c^% ztsIH7-9$Q`0Yo~GNz@~1D@|m93|W#DIXD_ad!dS4&HC~-qYcRO4KMABw<4Fm$^myK z7a4ZkM2<_**K({`20D;knCrpeLO ze9``A|7sssA5IDSHvUZFa(;UqSNgW+(Y?t6EwjDErKT~)>>UzcfL8At6mrAWiOJ%&#J-^COEx{8DmUf6m0}-fjwcM13W(ZmbLlos!U~NRH^s79}XU6n$jO zfn)BcMF9e-kKUDH9W%Vg`G&*VQQeo2C1tud64P83_LoI=#{@4fQxX@E)=Q^J6JIaC zl*`Qqtw=89C%hkek4TE_xx3X{Vmf_-c!4M5qrz$5XunHV!9AaL&!D@H#wIycCf7cW zRTK1^8zM)S-Ad#511CXc6l4(yS(sniXU?GxzX}^4VECYRm+Y@ie+`Cd$hdN#Pq9Wgi%vHJk{rKym&Ju>vr*& zZww!h_^0yKBMNJJO@x#JZ1nX>TuHZf)SyQq&|pGrtQ%DiHK3HQd$IGv!?oi3;_w?d zWf|3D=&qXwqpIQiDQDupBgklxAaW0W6Tx=I38QKj0B9fn`SXH4x0N6>DzkNQ9%0onNmVGWpjPLi~*=H1<+CyAJm&^1VD*p+W)}9RpYMIZ5}_cmIUQ$2U#>N{MjQ z2etrYb57Am;8cI7ceMRL3SG}GT$bi>rnLmU8X?dw4qaNBHzJN->%jjpzYSu6*|bbv z9r(7Mo8_6%MIOvZ{^wHXy1w~f$26Zy7{(gqvYWLD*L)(-bML9dCaUz2FZ1h#dT%EY zWc~o{uUH0Ppfd7$cX?BG=ZtPC6KcPGBPYCPumY=)h>k7x+4>APinEd3-hf@4^rI`# z3BV(&+~vdw#enh?ID|h*&jr|U)ze!!fQ2%9_CHtan)<^_P?yCG{)M0Fm=QIF!z&+2 z!9z=4%{LDVa0W5S z_{B}f8hTlTeeXxa0>E?PZ6=blv=)DIaV~6r48C`~k(Glv34BeR5S0RiOWfm21@i_^11G z@UgqDpU#f+(Hpo4{L`dduW&*Z=|pyo+8%a*LrDgO_JoW5{6;iuvmk$#{wHW1M5>>zRlBX2~B8IcE1X3VqZbSTn&6sItuGW7{@_lhhzN5rG@`A9?gmpjb*G^ z=%Go4{PuIDH&uv0HRJk)V31gR|MK<{0c?_2b9mEFY`c5~Xyp~>)`UK5@e#Prj2lN$ zeKZDOV8&ft#xQA%XAuw+V0iSiR~>xY-dgv)|5~86luf44(boditDXk3M1bo< zhtcPFp{DQwf8)4D8swe_nX)H4h6RU4bf=R^MwCAjfbGK1h0c2vT=uj?yi%OkFB}m7 zv|9YUPN!Q4-Cb0H&7pFrrF;C5Sn6u%8FE@<|38OM39$(_)7pQXJ+PTTyCDjYc4iJ? zOS{UmY0Uv%jy+xAO)@}HM+T8&tycot_;Jc@4djd zKjO7Y>r@UE-(dOkCNv`pnFosA1`MuhU0duWi84`WvqwLhwx3+C>sxfFpkrfa_Zy4| z){!9);==?gA#T&!z~ebH?8eRM!G!5pTKV{ilD>xH9hStI*J(q+*wmv*dKKsY9ig;W z*dYBuhRC#dI1EN+I{5oS!4QONM3gSk z08zrK85P?9cIsHqW~4hPY@6~R4({Y(5c_U^bN56lP>Q(+bqzIh|Rwr;GbKUg~WApXPw>=lq}b3%c8fXRLQx0%09m zs(VFip2#X%S-wkL<+v2wVTbi1q((K}$@>?MjJXj2Z$=_$1$Ar+p1RQ`l1Qye#VYst zAJf}@VaARDIk#1OXX+?eiRnQbnMgNR7T*oV*Jsn`BxzXen``Q3^SHrW0uKUCPw!-# z6Hs@W3u2IISFP||$dW5|?@)mir6rOP`K>NsUub}<=j_N!pbyA!OY9(0vmpOwa#KV6 zvT$5y37Fsz#Tfwuq)ZCZKCorywDBDH0dVNOMcLPX`&=|hc3O!tU8po4=K~6!S;59* zUYh2|wcyyaC!vK_z2-EVgXOlkY!$4o(3mQK^;RK}+Pn@^<{Mu$dkEWMc3d|w6~ZFL z;GMbcF0>PA03CC9Kh<4+0MiWrUw0OZ>MtCjfkf%`LkMKiCQ0uv#nKg;9!uCAC1t_f z?@ei?a&v1OP1RIu6K4Ea`|p|4szJaKhOui~UA%B25U8J0rkeRM%$t^YPbW7^j;<~s zwIY%u7w`$6_A|Ziqtgr@N=27DSEwo#dtY4WLjUhV|94!x7kK@dY2~hR+t`AN7N$Zj z^_O34H8bT^NgFVIsXXTpmoE>#s8`>rgG*qYcjVx^WV_CZulmQ0u{B|Wes+3Y=Oy>% ztlY?Ys`~nWdjj+WK!yP)s67wJKs*tpWkT3OPFh>HmB??)t~Z7dcw?*YY*M z{L|@(m#7Y?LX-K8EY6Dk9U!EkBU#y_0=pkNWXoI4NpjipiR}uZ)Bu}sjohmxDi~qp z!IMNqL5u=D82?U323TSm$u?cIPfQmnyuV+8odAQItsm_r9+#~S^pPYK=QPrl0+S4{ z-y1uIRu_w-YTKDE(l~HDuE8yG{+kQ%pQB;}={Mr~MRCwK>|YVWL!1e<-t#p-f4Zlz z8oZi%M3Z+qWGxl3<6(894y=fDT~}5s1HodP@b0Vs-W*k$2&py6P2=~qvq8;t@LlyY z#hJ4={|OnNWZXOWwwla`sGQV0Q_EC_CB+jbN&03?o`TiASdYC68wB) z*KaW6)lK@La9(Rqrol4@1350$Aub?>1p4KJx(X_L$Q5&h(ydvv9dKo`9W%yP{QsLG zO_UFtb1k0^^y6cz41}#w@TXE&DdujqosKRAj1dLdE?D3KiKHY2T^`8~qAYm(oQx7D zVQ$6PgVcoZ?-dhy1R#9|SZaJ9tGDc*Len5F$Zzn&hV$%=^%7E7E=rsa81vo!K8?G@ zlg3f%JqPAsm6SW+@G@8iKXcEi8ep2RufMr2N7PS7KlHBku+%62E}aM3eiRt8tZz4F zr^o%LR?XI1(Ug~$s%8HMnmf^||B}NTj<3;)A{fwWYd4zzt^N;FqXi^Nyo6JN07jt` zUnGF}bU2rAZ9*Bb72x=(mJGeb35e876L^k&aQ+8+FFxKsZ~zhkb$KG(Q84beX*T2Q zzJTP*?4md}zaB6;`udOAbT+$fUt3zQLlAG_&jR{V2|&uL{$L%ln4vPf1RWXzyo=Je zPrMzyQXzH-H3CCEjr zJMR-cz=x5rTyXTal>ucAZ~mo3y{bLAY_(RsO{)c$m#&D5WZ&(7X8aaGJ8`)3Xu#bU zOw^-0F(v_v!EP06h+N<7HIJFGlXrbXlqnsi4dO>w3Ol>a3vk!;ZpnO@y8ACtlT6h4 zwn}V!ZvQX7fWoO7 zj3xnl@QsL{;!pLMFC2EPs`SSF{!yjoH)|=stfAi2BWJT)9wjD@0jyBYQJdYij3eUA z@9!!1{ze^q?^x(~CbyMJWL>#g_`J%wZY+Wx`sh)qz9os#_wJ=OZN#}6qWht0vOvw1 zZOM@ZEF$c0mZaZ48&vsKw&DItl)@w()tsE&5^#nnOq|q0(CQ%jRz7kY`2PZfi`t(9 z-ov?oSLHVaUiBKcWSc1lXmLHu#{N-c{a+VGRY&a9x@^Gyfa%8k^c1AOE%Q{d)e2E( zm|m#3<+uyk^{1?ImF55T0q$oIQ7SJ_;LykscYavT5$ZD+q{*i{3$UH7dX{0XS zyNy2$zy49W`Ww5u%Hl=;&jES&A4~Dvi2o(`MEnko?TqZ(4h1KgOLNgN9iM}jFKq&k z8b)Bl8D$e!o<`08DFG04kW4OiF!r+5LzdS$ zmxiJ!4W=_{>H&QCGU)#>_TEuVZd>=TB1i-TLhmJjAiYR$(nSSD2)#&0Iz&2%hTe-x z4WJ+(p@V>Q0Szb+M7ne-q4!?o+noEp_Z{Q=<9Dxvk)uNoA^TZ-@44ojYpq+kx=~BF zgagTSWI*Ga!zsP<@*0svV0%9~mm8lG1N1^;Wk@YO)oPdC*aOusPahxd$VxI;OXSE0 zNK}0YS5klTB*vliuve_&fIXKh{Av6~OT#LFpxn)coWuho5x3Ex6lHeZJ6D7xH-xpQ~J} zrMm11We=3@u9s-XUEhp?1fKjYp~yS*Sfz;xy4!8n*n=P6r!&vj5(KF^{S{Wc!<|d> zcSWyxFOVNV_Yr0R9uXDI%t&ugn$iCFx_w3#$X;r*S*`7^u$V77$BIBs{LH%cS7i}( zOO>3ONd6L5>QWzuprT0Ur={n4LXF@Q`ky4Na^oAp-_{)&>=jw%?=C$gb}3II)*oNv zYr}#*pN#j5Chu3=e0|81FUWOS6Mg7+s>@A~QsjiN{``XC_k1Be{eHi(Vw==;1V+6+U=T(Qs>Cq035Q(6{O5K;zWl%DZ zy4#a!d-48XjP3?t=&Dnm5WPBTQ&nXFjbdzQ>Aae{Dr}{2J?UKy&BHO(1^WJiVyRx? zH{=FAd%)Q!{lcx}QxCYQM?ZvI6bECj z5jmU!{F!$}J}z4GpB~ZIq%H&rJBNvzy8e;($JfS9L4FX`H5SCTwAr>A>5u>}(B;kT z-Si{ERx`^Kw;*X%nz%^5wV^Qm5Ls9)c@ds%yRkN)b*r}f8yRBE(< z)>*xxV)#?K;JdrvaODH1QUlCp&NT_G%9Rj@M&Q`jzb_B{YV6DLph3EQfp4)W-e}oJ zcvt5BZJRC{L3SZ+ZW$q%(<#Xl{s~QSU698Ay;bsm_`KC#`7K*Urzq0IKv0z2b1LcP zKP)jChDVc>2>OVMg?^IGa}Jx3jOtPw{_S%VxM?>yFCJp{?jIqAAer;K4$RYVMv!g5 zlF;tpNnY8zN)qZzo>>Iuxph(L!}7F>3Vy0MG%(Z{0A>@2@@T5I@k1amX`);@o$Wa> zwijC(Z6%R(3e=R5HY6qA$fS%-tn+SCUZgVefFskob9FiAv zlkW?H)Sc=LZ4N#+7%}nE)^?T=-;Gz-ek0`LFH*i5e}w=yOO66^hXK6uYN!1%79h})T~AC^ao$4k!&WGQ-O%z5Rg!JHaA1VU;y$d((o&rIDcy7} zuzDE0T;@#=O-Lc?Ab(#gQW$_?%X%n@36zVUBcr-{hoqxARwRJdvhl4JJjf`SGxfLx z@n$7=#uQa|TZ*=`_N^@u_CfOPxq1Oustm4{7AoLKMe=^8DIGoan5vw{+sz#--;K!P zfHM=|k~|OZd8Hr`Q5&F)W{^+2)n5SJ0E+jd-aZ}@)k7la!ZkokyH)yw5ftKLp&px5 z6p!3>hhT?)Z$?LMQeE?r!e!rvy$%6J$d0Xh)(G`ItfCM@`VKjrcS)hY8c%PQP%ZWKe2IV(GaNdK5`r>|-O}MI-ys&P+ug6Zfi5Dn3Dizc}>jXYxf~ohn*yI#_T8Uh;5*UnF zeq{WCR~KK@M4pu8LUrJfw2xsD^%Hl&WI==!P773QKPp<$@f&xs-h422+o*4dRLcwW zS$~dC=)2Ny4H|+hAznOc8+&b^Q-X<&i*tEXa2sE%$Ao;R1wFm3i#~b9sj0C3_Avo3 zBO~Luu-3pj1Ea!Js+z<{bi&G*)kVY(#;)|++4=*8&O&r{I_Hh-ny>}D-pm2EvAoL& z;`zS_svMsBBbyPOpz^rmmQ+la`pf^Edb2(yj()AvCC#B7PbN)J2vUKV`1gDzv04 zt9IzmZIm097KQ7$vyisc>ld$wpQXSke3_awY0$Na6MoW%*I31Aqr0jRwX)^X);*nI zg7DKMuYVSd0N1o^w~6ZBtsTcqQ5y;BgVoXiU29y*(5*B{3-bG20ld^i*lf8*8}VDw zuu%RNjc5f8)rzQ6#|1qclP6Pxqez;NMs&HkH*se|tlI~D4i z@u?rwukMk>>`V1z&K8cT<29=yFCBM5YDGSwL~zF7&DM%BhtI}i(hzDkC6hJM7w2)< zdYr!a2PeAlPPRHdY*2QeX+Kiz*7abDq+a=h8Wqjf zcP~7O0=K0&n5^OBnBV>N>sMpYclmECOszt=#8+AYWwNSny0 zo0K@%==1cR+^nB^|FMkybCa4XIeWXtv3ZL6Hz$ZS5mv!e){XfVNry|ENI+Cqqa1PI zy39o<8{{A>Bjd!RkvQUfvqL!W899C<$67V+SWotor2TZXWNl+bcPne(;&8XOpM_#- zA#k}MHh#Z|-I}TgTS?H>2E4?(@O4w6Xt_Z{{Wf&;9czUL4ESvYy1KfOv_{K`MA!&P znEhY3qzSum{h=IA&8pTu8I2-AB5dCLdjAR^0gmE4YvJi#2cmLiPHhQUT*|Xh8xuXN zLtKdrNymDeh~f?^wLN0y>{@~)b*8hB2iC3W zg_P;CsDeU^cC-0K1Wx_W8zN7=TWZ4({&%fpMMNs?k~!~%?UXdR^jU94VK%o6aZKL7 zs-J7T+m4LFJ{dA=Y%DD-hOW4FO2uD{4on73sJ7LU`5sTnh=6Y_<2lM;?R}6Y7C2>o zNBgK%PBYnJVSMOS&2`pNDjn$0^G59Np#ha=FOhk0OOa2X`uWJ)${jWB6oj6u&-(kPPv?~*H1{-X~nBlk?UI0wRF+#jw{F3 zNkl`KGHJ}X32@3cE`CTK?kGDg{rp_61_)3B52npumeu|ZJ;tX3P(SNOl7}xSwXrR= zhvOOt*!ObQ?CmxlcpG zXSFeDWS}=0uRHI#yqYsFQO6dKbx)yOz>YtTNx7#O=O57`LxlZSpZ4!FpDz6^81ynU zaQ%Bab+AB8kM7bHI`uaCXL=j|95(1g#@W9a5Ncg=`sQPn>``S@;(eu!)ukv^pE+b0e30=rV( z9aDBb64h0tKv;^N3bD^ZZ1Cpndz+-z9LU0UPFy!vasJLdplYD?h4XVyAp)E>&wk)d z0$g@~O^XOg2S=<(j=-0_z*}x=JfpcPQ+^w-6IO;RhNlEzVZruq(-p#y)d}whb(#4< z!z~tgkVQJOKx8o#gr3DCdAh%uDc~kH!_YaCR&aUKLVd`yDcOqc(nkrwaEmIC9k!g5!cp0v@g8njX-;b?o^3PAan zp9d3%GO?uB0!cO!fxY{zOcbBU0#fww5#R4bo@j+!bZ1HhF|+is`7Elj1<&D@D5*xu zU`jiIeW=`bbIwt}^=cFtB^T01r12i?XB94~IjWyST(RRSjPLWHq($g&z*(rxs6)|2 zZQY}!uw$a@E;yJEpu`uG;vsJ`YMyx;|+U@EoDk1Ks@HqCPD-cP$@BKwtO!&fLSqoopn0fXnwD*RyV$F*q&h3*dsS0kv00v~f&1CLY!GF78G;d1W*@}JSZ@ki69 zYgrc-G4CfsC9U<~c^FxnQM>TjJCQ{Wh0`Lm4}nsJ34Q%};K6{;z~XC zS-3Fmn}ZNny3r{Z$vzw5MMqe7DiWgKl-X?5H;u9=^j+?J}dAMB=1 z6S73~&@@j>bjqhn;Sy;jHaal(CNuIKeQP4Lr#Da?zB~)^%57sI5vL`vfK-*En3Fq0 z-RqLFICs3lH``+mLb^Y5lXRrgTVsW8A09Ac>q}$Oe8A9R&{kMpthr4(VxphSzs%=j z@g;l+_;)NlN)iE_&qKI}XQb+f4RD8*0%*Dg!P0#gHV!S4Z{K|h7Ra>%01VlGRtpR-dIC?_So(7& zc#@AGo&7{?fhw3mPJ~Yi#|#Cd5c@WH02WsXp)BK^7k!<8`b24qw|s*^_gU{xS71u zoia(Vq2ok4PvigC#U48_bwc3SWXh{VSm{@&4iW;~pV8W?hi%8VMMt}$muR|u58BnR z^~^zo(k001$XFw%a%yN(Yf#E~oJb=W%MsFdKcX4znvRXx{&iLY+)j{NyRcmKhly`b z{@lh94F3+PdfnSXFgj*_2$Qnb1*i20tf~lS8S(88h>*n95H3PI5K`Li>Z5+DMq4Ls z`Dj@#&7E$haGR-Hi5J@e=xE(Ig>G3CLZSU95je>w?_aJASjL=%Z+r5=t>Add`Z4j& zko=c&kFgUJdG;-gF2{0^ZnsD(KB=(oO`szcD(1l5^?!-VeuB8CmV#pZTxNrVpiou>Ku3SV3IDx5 znxY_el>GHKXsV7-Z$C&g`Kj2GS=&eg`x4tgJ-Fgysjo)VAWxy}+$L1BN!o0Ps8Il` zv|@D7eTbxv>Qe&kmqyUM3ZmUS2}LWen#p$|`*3&kY3qH`Jo)PK7JU1MYd@s2LR7g%&Bw-k|Kd$QZqUe@wh#V1}=X~GkK1v)P`k_ z3e^pp4r(6$U8;)D$c|}fZN$m4NvZVoh%37Hl@=YvfqyG}*1-eVl_ zHj>SzZWFm=?zU&PTrsTCRxi~DBJy8ws_^?m8^n;k-@)BKs1C`88#>~PE1kl`*fA*# z!;6`*2rOMS0XVodvXF=u{LhMv0g^TN!rImLxO_xN6jNfPj!4AS=@+{wii>*{N1U*) zF~wxLu&k_nB67RT1Z5??&2kLdzs~4b_lyG+u2lHg*UagE0xEt#2Z%O%avRV^miLa8 z(0pDl&@HTkYb~uAD}8w1g7w{HI_L_*qa!BJi8bV>p((Y100-_jvD*AB#R|(u-qM<2 z@+6$E!(J4&J}+>-EwfmN3MKC=)yf-NLt|w(%e`rT=hy;Uf77qM9>MK@rX|i8rge2o zhdt(E`1eRmmyZm0V&7ZxZ9OjofCJ*I_AWtF&J|oe__+Vd{rIJr1Ky#t|M-Panj+nQ zN4{I+8kz>{1FKTE&iicy+rhhNg&l_>HmJDUfy*CtMm7izno#}wp!`~6ar90?YVpz-7Oj(YsA?4 zj_##7;UVC)?F@3&*BJucd&Xjs2wWZyNr!c;2{x~2L73-j}w3V)6K zIq$al9pT%Df(mJKV~ik(zZc-`$L8F7_9dLwN7Uv8>KR1LI-XTVZUDsM@R8+npjK+)Rw`!BLwB7J+Tv#y!JK`)LcL#+O5_)i4a1z}_~ zdiGS|v$I{27K!KHc3>>(It+H<>cf7~B7V7+airj-(#5tOzm2}opNu>y;_T4-I|Jjh zucZ1zn<+nOR80dtm^y9EWb=%0M zB_*KMorP*3>P33Y@6C4M3Ag@SwXpeBUaZzo<-;~V9s+xm|D**xSg-wFBSyNe^idnA zhPIPt*=8~B7Yix3u|n24g3CQR1BrO=Tli>cvltE0T@f3xuQnJR`U@ytHnu(aNBnf~ z+l!?&BavN*AiXlQ5U61{P*};(>GT}_oe!UI1ytXq{B(*31{y8>@LkTQmj3P$tCvqL!UMlz50n?j@`SU@;NwrcHN1v zW7p~Vqg|#J5|>Ur;qnri$&dD2KKfjP1o$f7VC4suF47)M(?0_S8e0LlJnU)s9D+p9 zR`@sF0fEjAGdc7PZmm;tcoBghg+h65_2xb+{bn^4a}qZ3{oLqzu36AAXeQ>&0Y6uk z=-m7e6=acz_TkdR>qmdoO$(~?xI)4fA=YllGGAQUqGErtVvw}|hTL{>Jq{wqO+Cgv zT00Clt8%`EZ2bcOQNDS^YKgwKt+7m8%?D8K@Lvqaf+)WICpCC*3g-=pl?7(4D(lGM z-)@l*nDL3=Bf5W>g@hn|IU*#&`v-kd3eAy?Y+l(oe18U5ONEO5zA8^a?tW=ZWOh&j zr*T-g`YkK?vQiu>H(2*FuScpy+jX@xL=M`$2Lq5NhUuy!BG=epjM4WD*_(F7zctRt zfoS$_Hg7dl9TrOJ_fjHfk`fmWo4|%NJ!N9oFig?ixv?;nzWl#l09HA0aTm_SR_ZGM zZCXtHM%sT4`rT=BZ2~ez0(v{J-y=RgNK)4Ck5{K8z&80c7M>NqG3}!eZBcnk)K^r6_x2+20oV}jTS;FPPI5c9}&$Zhy`CFeqSnE@v7NocK;1=L})dHF?CU1MWo zw{gtPU!aaOHJS5x^5qGnea%A6CU3)k`g91Bc5O~dP%hL5WFti@n-5IMHK^(?V&>>9 z7g*`GHm552DWE4hqu=+^^T0v9*;?uC^Tgd%gXf}E=z%-d-}70DV|#bT&_n-!DAuE4 z3x!~`i@?+pYW&kWT-rjI=n`FO=sYh$v%D5-iSI31k;kMRO5?OCp{g|WgLrWrptFrR z2dpiwMMkM@jP}{^T;>%5`{}Q^aAro^N>MPPrWW|#%}dY(%xKu&AG01yop(Or53oo2L*H!dElKUzkk<^Y-u50#6PrD5-O(4q)@Pq zJ&xP#Vmf~=H%=An_Ru2pm76$bjk;4rdy>iwBgQI_2@mzaV>M&w+|G$ElM_1qhZG6f zI_j-VNnO_W{_>0!H<zfPV^75JR})d|sKpm(42eJ!bEq^!26pmsnsGz2Y6ol<2ai zh><>Ql^p&`PWajYI)C&Du4{a9~LCa~YTraY{qkfSmd-uB@By-e}-x+++9Y~&0- z@ZI#?8`v|=VH`F-V@tZVK4+W#XLCUbs@9KrmQglwPbj)(eiFV9=E79eds_+}G&Dyb z5p`dE&)<>Lsd~6S9z7Vu@(ZzQll_#GhpsIbJOSW<9&`=22LLzQIBQx95_jCO_GY$q zS1hxYi$4vds3GWIk+z zC@72+z-HahBZ{P(fzMs_nY);M9d7Jf&F)IUgK{Dof^n#T_YwZT^}*a;R3uxDU4Iy$ zfkMSjhkxV#96H4s4d}itMc3=93a9Q=9ZhOjXx&2nk*p8mDCcRvBl)1&1J7-QQ`23h zzTB$x92wbEEW|N6edYyVp!lMH)feN_CUtBZAes0W70k;{?H0T{EwT0Gi*}_5m1+Xq zVwO)NMElp@9K;4cw8%3$gN>vkhtB$7qiHYj0;sc-4{RUlS7p@DB++HBS7PhY{$OZG9LR5JpsFs#&P79x zjf0LgE_&3Pe9mR+dgXS0sfZ0#*0@5DIsnn;ACQ2D6w3&|shH!9WxsHJ&qbNJP0G(z zWtBo>`k;8e$ zP{mnAmCD)GuTtcC?(JXhJ%aT&uz-t64a+`QrU>=@n~c~vmljwl1&w3-=y1KLXo5G^6uuth*?ZlnJ?=pqm8AT?#Shf2JX7jR#Z{Z?Z8dF?;172Bo>J-&-bWTsQMboxHXZl?@isO zQX?$l%?;??Cpj37=TF@YLi>aMLfkQsDFa)a4`!3nn%;@ccVi>6i*Z+#707inkpd9eM`5)&_sK%3}S{sN6De(n1+Z|axW zy7I#)1)cUv+{=q)dylBRm?ATT@~*r<1)wj^wUlCZ0<9$%_>yQ!Tu41r!v^h2m3Zj%s+z*NH`ILRtErV zMEl&B<1=;=qDc}s(;sh;K-rNey^a&XN#n}4#{}gXL)gA7eiG2z2W3-dqd8hup<|sU6aFFO51bc)r|M9BAzyzE0(9e*!OrO+vZpM z43&GXpzK<_BA}pRhaKv4wmfI*v%FJjL|&LqM7*s~T)vKv8*|tiKg$+b-09IUSH54z z5CUR1Z-&FUVjs>z*5N*<7DF?+Uff=ijoITnDMG`nl;K1YQGsUvV9gN)M%&WKA9U5; z83lb`e99wd8fl4VvZ#o#S&vt?8Hlj*$knyl;5(kpu7<+}Ux`4J8Amp3VWr}s6w-AL zA;Z5fT<4257_K>+-Yzy>K&dp`+q4psk#j_f(JzI2+Y{PAW1xj&f)NK;*PBzutWG^b9V@}iT9pt^*sAa zUp(X}`Z7k!d=57b@(TKW0Rh`jot=m+8~N3Cv|_U#}mT$5kQNQ zdm>oaR5?&@Ud~ZBn_XX1^*{xE0&Kxk8S9H1I~#$5g)=C!P8$7x0zng;14?XPwRZ}C zJ@j>LM-3_Yy3XfcRHLT=iRQx8sR&JyB0inylfy&civfK`9i%&^eh=Vg#JhxN)t%&X zeneP_%uWtf6Z(aN>k)38nmc2p+P|%|X+Wh=A)4h)SPCE4nib)@L{F<$j9m$#%x8)6 z2MyFH`{7eE>zaLDfT|xvQ1i;f$Hef@;(CRJ5h7Kbxlk^UuC%x;L>lo zNn&R9Zf5J=6AHa4S|6UDQ+TdqZT&;Gx;MCm;Ocx(=j|NPA3MMlaEIpIcCSG`vKJ;9 z(=|;lI4vD!Lu< z_w2Yz))dpSj#4pB$(~jj{!I&Wc_U&cn~{Q?D)75A#!LSr6O}gT2%;jv-4u!%)~`s z0?oa5o@DF|oE*Zy$`lKx05NtQ5;~+S_<|Z*KwzdCJ8f4=@*=B2F<*Hq=XYBS9qLDs z)vK=9C}HLJb2Ye7&n@DMJD58viM=TPJBnrfwETa{!kH)1KgK6U`fdw#X?x*3t1^+* zIhJDgfwoxzcw$qy&$fyW)_AKeYI-6G(0i39Ir5)u-||H3>kfe>GjTsDBV_cO+__7R zFO;2;(QcRW>yn@B2kHRTCJMuD+LTv`eR)$9QINGY{ejEzD-#~0JsZs*sxh|^w$EZ( z0|8a;GriKPh*AqVfyNmHjN-o%n+EBs{ch!2X45S`PnLSrMo+8&nLV$esGpnM z{)jf$FCJ=1Dp>A$9jgxS1cSOv!Ux`xXX~$Qar#D?WsoZRTQYS<{+wOib5=y|KGfHS zZ;D||@s(=XH>RCqqdJ7o4%@?sw;8+g_{Bk(DqDN_#IQjW-e%2jHP3tmXj7%H(C`F` zvOiq{ntFMx2(EY~XEx8c^uQsW`xWiOhYw2tWbdIv1g7+j)+KDo{stBhMV!czeE=h)EAOHOWE_(;cnRfGZmSnJmowO!IA-~Z1Mgh^{rmUePrDePA!mRxRZi|FYfr6N z7BTsN^Z`O%YJIqLs>#duy9Y zd->3pnyEy{E4L4`OVOiW!^9Vt-%v>nX6r`WmcrpD+o@;1JS0Q)V*==-A{8DuWqgB7u28aGy&mm^a=Ub%m$K(1{(Ny`1_18DK6CuariZs6TX9Us}+D zQ@=joMScBzop=c5$)P<5D5g$*!LRXC>JCG_V2;Kwx{#k?hI_n?oaCDz9h_QvLVTGz zTn3NZgE=L-a78dsPFH*XnuSx@CDS=hXDwJdb0g%H50y4MgSETWVjs9fkA3s?i?u$k ztf2j!fPgkX`n?GsSDz88g#zp&DLE28l%1F_Lnh2RC1BTeYPt=?xxhXR*Uy1P4gAr~aTxz7B_ym*93%lvgGh$Qn} zl?t0-CmCbX&y-$mPb+KU#VhdJz7`we)5c*5;qiZN*t=%|O{oE~;WJvY8nR11tkmWQ z$@(F9%zd|QOmWxM(R9o>hACHQeX-zb^MN;R0#U^r)D;v_q2Gd!dxY^g`%C0*Lax?N zc)UHC$qk+brgXefUCaG?4o`H2)KN!t*53v(+1rt*dxqrS;gGcg&HeiCoJ5Zg_YH!5oF9% zvn_;_onJmN9M8BDS%s8*8jWhYLNN&K+N?xghDSri7#4oa6vFMrD zV=EO6UvCbdZMBvGG|88vwGUBLDB1U4o$kH6c}dr?!Y+rZNe^=IloQRifK=+wZ>H$4m{=ho5l&a~+VYchu+DIr z!Wr(|T?g>|T{UmTi9ibReafM6yYlsiyD+n35Qt zQ=tfZES+7&L4=kSSkO4ZOZCUOAb9*NDcLc3Y^L1OxG9B>TDP`i4iBc_&+l6Ht!QFQ zENGHK0_y$rHs8;zb?$QTN?mgWoGR4t@xF?`ucOXT&Mi&PL>zhME*?(KOqnKi=W=K@@CMK{~bY-w?qkp;Wd=eembc@(! zneb$07@5_ZRen7}{%$0?ZwVBOm)Wgn!Tv|52*y+E|$f{pUG z|Dp%6ndPyv%nKsia&_+(_){`N$?AqArink^+WC{JYTPeyR6e5xQsnetWSYNnififf zR+78dfRpCs>(Q?(muzVetGG$z<8JOKh&fHipE@VUn%`Gnzkc1-Dp7qzgGzXqJTFGg zLwP3iqdz?xaddaYGYsMC1NHmp*nF7~YWx${h&jLj z*cV;N9ym7R2(V>GB5HM(@t}U&iOFhZExc+^{YRxEv}ojJ*DSx#TW`RJn(t$806P1H zh2lr_DdrV`3qtv$^=L^OOqjoY;i=AMbIk+W!c}ug{9G~B z#q%u02=-M+@eIi<&{oKY;r!j;q~_GNG^q4|J5qwDJoRoyuGcSJnB+pXyZ8IyQnCt} zbY}1j>J(&ubva36W&%{G#<`(|A@Co3{jCKjL#JT5uSk3Iqv*@j+|LajM&{@zUW@0= z@_49uTizz< z(r*RD1u;gP@3lAShgQMTE&+s0(9SI1MnC;c>Ah;Vc=pGY)7PAB;b-@OVJ@b=!_cXY zCW%WRwE$gV1CSNkHZJg^5932=o{BOozI5Lwh!`3oIe|DsH{K9Gfwy4vz_ zJTu7bQCt1vU*M5^8-MNLY<}8;b{&Bf)xP5V{YMcgHzO2C0*3 z4uaL@_kCOR(6&R4fO5n;S8A)j`#FzG%HkGm+EK2Xyv99WxhTb&i954(9Ti{zZ+GbX z3go9KB@qFc;*~M5``(J#T+h`f!d{!bB~i6vh8mSRUmosG|Lg+$xlv98=z`1RYM%MpG6`sns-)tf41rpGEG3vY+9Ai9I&E$3 zzs+Mz7R0;E-ocg}j{Usg`uQ+4ZYycYR2TVVDD&Ijja*=URXkI$SVx@(1v_*%jvJrq zL)&g;d38IfOF=uXICdDq?vTpyedv>NFW}yS->0b9xW8R+#%kjs-yV?*a#4!sdANC0V z;EiB|v?%g42E+GJoSIwPz7~wai5TO6{Q zRr0aBc``!cSwvz*DXWlPRm!T`d@r`XHL}Zw%S0+q2Sm+2`6Zw`uw}}$kxVe1W|yIP5g-b?0Dp)NXZDx6d!Tw$ajjNH;Ap4wK5%P4BU*(?c*&ehVRX0NrFYJA(i8TbxbH8(F6ulO6~UQ+}` z?uKOJ4*_?EAzQ!)ccNqX%f~zsHjOPK-F<6MO>)7-8BB#T@yrIodk942Hmvw~Tb;}C z)z(%J4uCm5QHx#EHz1&7yf33ll6G#i>0mpY&130``j77fUzpSbA=oZl6TR|SR9Co~ z7pGgJ5icqMhypZ8C#DCW7_=p1;ElxQN#iHU^elJvv`)DnRX1m^GTL_T3QK&ENGw0* z1KzOQZc6G^;N0M5M)HVRAh1C;iHp|EvH6w4BnfvwYgN^h&6yLO`cSZT*2ZUG$L_A1EIrdnFm1R+|KF9QdL(kU z>vIOPKr*;^Vaq4g%~drtv%-n8d5(DVZhHHpKz>@L(vT3j`?k`43rWGu6gP&-yZ>hT+F!9Hg zs1AzD2wSuZlq2b&m5`WVc8wk5lPnH#>WY~mkpmPwBk1hD?R|Rn%h0mil(EzFaF-bg^z=78Z}_Z9}*3l7!^HdV468?ok7V? zXq*DuFAy&cvbAUP9=N&izsvrU=eEA`Ze}9>=ic<(r(HpGG;Ujlc~V4*RvMUn4l0ik z&~ye=!A0Svm2aQnpQUt_`>|+d(c@_=_%`5Xh~;Cr7wAXxVz#IrAO*z)xnkyYL30~wwZ%{}# z6`Lmtt^%X3_4V7LoCLVCh@oEq1OlEam}~WhnWP+!@$VvPyvc+)Z$!e-sj_U%ma+*U&**ux9x5#6Zw7bU zG4QvxUm*>=(cuo7d^)M&H{Ld&J0kTOe$@%z&t-#@=??8y4bWyyj&;JRPz1ze3O`hU zyFm>fhGR<^0!Fc~bk&;&XuZn(r95+n)_wB0$_?v+i-Fp8WJ$Hk`k?%@*x% zMvku-H+O`Y;7jjd13odHIX}iu5qu4%QJ$L1gd~Jbs#Apn=>08UrALx{!A#sshquNj z<@sNwHunXsdye=0&x_Mvnx9Cm+XX&x-v}QLE@K8I6ZgP_NAx)(4BW!6Z)-g67M6<$b|v=VufhN^j}t`9+|OWTJzcIzHVz5NJm zeE>tu!^4AtPH^slVGwoi5`f8Of;dobzK9eQ#9V)tu>nSfICR)k%^)6`8Ras>ilYk7 z8qrodM}OELEb()6_4_P4V?0DyfC=f{h-vtJjhQ+bK|Lz);zr&3*?Z1qVIqk+6V)0> z;L=2f;aajcxegbKRsaxMxpA2>2uw&RpO*^2E-taY*h9uq;RN3aIz$*cn;jMmw5UTZ z02=b#(WQKM9d=8RY)9|V8+w0GH=PBa49z8T*4OrFsR1Ah2-6qMp%a~z=#3Q4??Q-! zBBj6UceD&{Ag@>wRu<7hlYd~1vJ50=FIE|Jj9P4CkJXquB@L9WEY~7H()(LVq}Guf zf4rDiV#aFD3oe0pKAtr;(xDOjWJni0CD7Qdwf0BRl$+YM=NCjeRP3RDm4K%fOBWK% znt~V1A#@1nSYvBB3$x-O78HmI6IGg%HL%nEH~{|U()b4CR;ic*!>-1wj5%CEuDbv0 z1^6R{vBSg@KLKk($k2Cmp6Hi9#~9PRWoO+Z0xqhZ44zUz;y``6DX`b|D6=k9&4D?uAsE=q)yX^P5vTEa zgXJM-_(m~ku^pUIa0)cj=ztFNostgknb0%qI5juO4>}*5EKXP=QIV9}-ILypxz@I_ zyNZlDRAGBxY0C~bQRn~6Y3dLd6q;ueo7QV94qqZj8(SWbm8R$hdpubQi~AN#<0Tk$X5o>vOi;o zV-NRssW*I+{;6Qe$r`_#G5$F65H#~X?yJ9?MDVI-UXcLQ)SmmphX zUQ9q(1?)>F^ZGjt?510{`ZGd4>D+}^UWw)*u>fOxa@luv6#r%g2`QhnPJwysf+2Kq zR}h=Q94>K@XT5X?ZKh6~?O)6(`qQSl(>h}%J=Y`qnu@S4eMphn$?2HEJ(m(tku}US z(5httjud$$+ZVZaKatOG0)j8gC^{#`wDUVmo`oNlaBv06I9~B=<+T#Ib19mRwuLxw zM;3bQT6=IOv8X!Zr)b;pVau?I-u}nS;;iv!EGs}ZHM#T4t_93XOQ|V9!-!_zy;**f z1KSaD-q9S`ixwt%l!-!pz37l+A5MM12f979)@}E{Tr;6#G`XLtd$h#8`tdqQq=&Xt z=u2O2Z6~uqwyF#@;p`|&xk0--*U8?4;l2^VJKj$v*9GLmM^`^54FBfaf~_8f{}lvu zB59#-c*D%)jHkY5Yg7K9(M#iiJ9<3T@Hf`^N@5u{e&bp-|D4%o~lE*zAxucacB;`^kru;I+x9Vo# zc5ElCV*xS*go5%2sDJgNE~88lq32c1_-*nEDx$r(YGIPP?(B25!{b`1_+ZnpZR^sY z_vn_Ar35q^WCNUnNs{5UK($fh*c?Dq^W03!o1HG!6D})%7K(o<2!Un`hHy8bnGa6= zK>!!jn8k|R>wIkUadZ`$eoyrUCPJzJO|(s$%0k6yh1z*EJx)OEOUVyurW#F zq%D_Oe7vQ``+;@Obwf@G%+vRDt?8-u(1Jio`<4^eAKGFT6Gyi`13u*J@ZKAqX!k$I z^yvxuj|QF-TayIp)lBS2}HQl(R=4ajWp}c8uJ)9z&8Vo$Tul!zF_3g=}@yR@y zlaOZ89+mts8DFRM2QOc!Wn}NPU-{f%^%7-JL_^*1pW`sdTk`M<^~YV$*62yUM6K<@ z_s@XA?&qJMox*Q;NK``k4?TR_t}D6Q)fJ8tl6$7P{Qh$4WnxW2s-q|H_Qsh!GYew` zPpk-;c<#G10Zob}lDB;Ko(+v2EpvQO_>|2D`0_zK8VVlN-Jx4kVLoZpdYN}=v=@SPlOrS))d(wU!ieLIy>wc*FN zfBiBB&_k^Z?~Q2$j6XS?Y^r~!U*o&M_U9|8qKtl)A!r>tMm&4B(y$bL#JqSM@O6`z zm>VPlcW5Mh?%K2{K#-l>+}s`%=Pd#Zp}oDdf$U{XF}K!kj15o*+(ZIq@l z^PKzdymW$Z?(v!^hRsHlrrlkXmff~e_`pQ21N;EFz}o<;=@x#o0HzkDK1#L`uqM%_ zdfL&&3&X=q(m2z7V_NgJCTYh6Av`@Pc12Z_x8W)AxeleEOR_K!{m8T{*_p(Doj=#z zB2^jRCVX!zJ9U3VmyWDD;VW%L-_eqk#IXGutV$Qk zT#n!Q+j~c4<4@hSRtq4~NCPiIHXGy?t$=apwB~!|`;yuM+r~!@%j!JG9|7v5I>e?Y zBo;Fk?raAz3T@Sg*U%jiC7}{CY3r*uv4Nql%M@t7Klr+tzXAt#ffU`L?K@hBj`MaC zaQKJ>W`;Rz+AMq5`8lLj+WBR>DP!Q1%d-@G z;_G9`H^eMC^3oYW`8nB3E^I0F$n*Rru&T z=6Ya!{o*-VCTjy!;ph^F@T6hS4lu0=_O0o(Y!@YH3EJffDarG)oNX*?_vpjc*6Ks~ z(T9EY+W~EI<8-@eXe$mJ0Vuhwvy)I?sr!Xv%URC@BMl^;4ABBD3Q=1BQYJM)_)*Bs zBgo&hWsales2&Y6+vBQC?w;j51?G`&>stu`F?ZaeL;WGk+et+yS>{VVw~pn)F`y5GcE_ zm&IWzlL!gl@eOF9($WmYKpB$S{EX;t(B0OTsgso_&vjVyzlfK+cpa+1+kVA&z__s{ z(sU@dC@ON|7EDnRgQDo%7Jwly3aQsJ07U_&SEZ`is6cO9pUE2+57nYjsIvfq;|k6H z?qfX0|3Q9so#-(&11PBiBRNxjex~TDZb)EIWoShJ^vE7*Yb(bJHuAHo^{+_vm!KnJ zx}I=JEa~AUGF9>&1 zF5y!0!K6_1$_6H_`GHKoT=h9bwgu=NKVHau3()*8-U95M?907=MIBi~!|mBRX0OX$ zJH-t8&b$utAt7FaUVL z9U$ZX&6Wm_=q@;*_M7PpofHLN8dN{^=KWqM!#(^@q5-Dv@l+g9``{XzUNW*up0`an zGORZQS|E5pZ){JGeo8D@HijW9riEyeqN4vl_TDlo>hFCURRJk!38lMB6ozh4DG3Fn zK}xzCq`O238BkE^7(%*+mR1nyW<{JqQ|4u%bulueJX6ZmF-`!-|qWQ7D8sd`=L|#K#dXp5D!0i2n zlnJ~6Qmi{~oBWL;r~G$upmy=3oW(#r7)Hew30wSyWH3JukVg-6^NP5gyL<_EH z9y{PNn(0uvl3(z&7~o!pMA!_={nb-gl9+*E5^d+{9jp6xc#}lGf#Iyf-ZQ6YJDUa9 zRj2B2dY)$qcQ;$pxYPKHpO?^FS9?%<{23=3lPr84XxfSH)mg<+npgCfAUtuzD(*KxRX0bNS^{PAnIfi7rv6{OD8Sm+hlvFp(I5ka3AKf%?k_ckS5-(2X|`f#6sRDj z1~wbC42pw}RTc94UBZ8_qjmAa;)O3w@hy3kA3L>$^sn!t4yoXjt11{c6FzFD7?aYo+Z#- zGTocflpWQis*uDkHms%l>kRl!Y70lLUy<>Gpd|C$?Dgx7a-SCp27hIQWo`w6Gd=8Y zto+8S2V1Z`@D1R^87UIecCV2oE&)O7>!OARK`S2;eX`$F7MGS_T)Nt5IMZI8AVTwb zGq-p>^u7f#sW?H1=_0^yt)S1h@0MxDUmkn|8*t2aub6`{GD>!0VMr?m!AFgdkumn)+p-?7=zf^*xHXLBMB|_A#)BY02JZ2yQ0SEfi zW9t9!b4VhoJKZRLFK~UvpKxv&I5i(MZPx3W1b7%KG*|x@J_wv+0v}`l?bnN*!=KvM zrOwO$Kf%gK2t=29WjUJ1nYbk4ze;~z1q_M6iJlJwwTl7wZQa4lUM#5N*VV8{opNd@bFHp}Lp%@VvGXYu78 z_)aqW2pn4gjFqEj#RT3u2|L(x*Z^!pl zJTb)0!@nyPEam#A?Gpff(3Bo!67X#HNnFw z>;Pq=_x2x~X`OKYzgFG&8b`jLDLMOJ^!F?7klsr3W?AFPb|WhAwV;nwB$li|GfvaM zgZ|#iem?PwVU8H3xgJd5T@a+kBb%#fCdQsqw(R;otM4P}+i&c{X3;g1SVL~H9&DYR ze%tlR+jYwcU30uQjZ?7T0>nTpx#T-v6B)jN%AWMPTuk+xdI6K zG?T!38kbIz*vUE1X@}Kb=EQf8#`TzM14o`!058Cs%3qpt1Gk!eWLp$W6q1xT{;#hW z5V^MA@pt|guL9O|;A_O!a%?Voy8hYka1@SB&(nykjJ1csZ{HFO4#$j0mf>Dp^%?fd zrg0k(+}qEW4xnn3bPjk8+?r<;ddA$S(dGx!$=p#3yUE3dZd$>*JpJj&zIZ4q);u67 z(&O6p(gafbZxgmAPRf%OhyjDlh&S~7h+$g!-$3LS>$N+WcV^`K$MTXh-w57ajHIW4 zj%Vt#v9aZ8Y%uCDskB_1V>%mn{gX{t2VqL{(VW2xLzgc9n?LX4>bwEA8xN8b$_5;V zu$puB^vwY&KKOTphW`?o>)@l`JZ6@Ol7~rNE$A$N-F^7)-=%k<0m*bo-{Tot`>Tor z7J)m!t40FiDl}~}(u_TcCHnkq{)%EiS`$$9?mChs&poxWy_ax(_J1gY9_z3(XKCb} z*NdlHpL`Rx=&o5%w>=KZCD&^&`%Lgw`kO5Oz3-M4!vC!W7-b~2rU>JovHlo9MOS|# z$YbZG#N7~~5nAts;5~#$tNx3ZwqI{O{>g&-P8k?#A7o=;JrEb$SoDlzfu|QQg%V%G zI+M@U*}U{xd~sPr5slZ3%O28NIx};oL4Nb%HQvx%GSRDWu+d+pCu~TmxPWJu2jpzu zEOH=;M6b>{=X{YUC}~tf+Ni8yqo~Wb_J=ps!fr}=DYp-P^>lT0)!K2)Xr`;0aqTDPVtO}a`(lp#U*cx8xY9+3 z%dodF>ow0CbT&P|l9%cDHq)s^zpQQvPv39rn1IO|@kOb6lS!k}~} z(xRdnkKaan2jxLVU}a6Bh2`~#4|_in19fU9%wt(k6MQyU(+nTbKF7^HFkA&+yVwto z`kH3kAW%N%+RwX}oap~*DrhjoGYDxxpJFY!sUL7sGJ0ST#~hbQ?bNheNH%AKpR2cB zJ*r;0*T+pc9(12ASo0dHv-7(#Za+!#zPS{90ZvZOv2m-WYn?9We7ExAaGJ>7I{_M@ zqHINce--@GVlW@Hfi8s=dD`Be-X5S#b=FcaimNhD{-aI%T5-z0EHnYr)py^nq#~Ui z)p~XLC&jLmZNJXDxU}Yj)MU+&*rxN{q_z>_wn8-?Oo`)#(AWc9YHk?%{$hh(AFwDs z)Ao#O^yo$7(-}Og$;B z8EJWU8!azc&I+cq$_y9eg%U0F;SOzt*Opd5DK#L(bmR24`te!_7QXMH z8C}4E!{=*1LkIlK?wxEq$mDtba6p*@RvqFie}L`qa5$GV(GH=lRQ|62@a9Q~m&~Ye z;QZFy&B4t~RF&h$%`8yqnVnO9%sxlyjd0&9iM)cf`F4^h^9aAFne{Y~{g5_EJvT+j zVf^eqP(91j0Kd)BT0Dwjan8Z|`3LhXBCS2Z$623J-*B6-f;-W*^9yHr zKKQs5_G7>fbhSf%)vTr(97<3s&`OYxmx>hm1pE`dygqf%F$iB90y-oXaLZGwqMDo@ znm6mSmZcKlw7)6doSB1T@5q4OXG}fA#^a^f)c};= zJgLsyVNLCK;0YzxqH$)t<^zs!DH1Qw*Q7PpHtR1>Hz})vvDSbJYGhxXukkbHncp!1 z24s|;!#djsVScpug;N;Tv=hz`C6o*gN2%l4FX_y^*EJSxj=C)y;3<2V1+a-p_EFQC z+H}O@N!wspbdyw6`i~N8L@tZEEkopKw%@jBR*{(%T$v+R{>>;Br6EkAn!qsDh8l=K zk9z@U7$s6C%mE>BdVvb!Z#|nu*M&G+nOjC3cb{lqmKI=R0i-swc!DlMVR58y}_AKVXPnDYFR_~LVlnjEpS z%TtHgX6d?|*@;RC6TMiLZtHFkaV?p%6t zHIS8ojw-xi=p>3jV)Oc3|5!`P$=3K1c`-Q$|KShZjm;Ovel%Uzj>oMUiLGGs{RFRW zF)P8^sS3=l^BjEC%7M@oJb!UA^WGM|01S3YiiT7@d!&U_s1F+8F_8=E!;6INm7@vd zgy9nZ<@SU{;`>?8>C(?@2DM2s5{m=cX0yJZKm4xsBLrby-n>%9^W%h0FA=7P1CNw9 z!*JCPmZn=%PF}Ge_SM}+4LSiwwKwP#=oYp789tSqLCo59$0Np@xRR=zj=`QQ%1Q(jHWw5}tv*pjSG zvIQ;M4V!r#d>*dSoj*DV#qE7>w*30@0Lj+^h?iAvlaoGkQLm}`drP!oq&UN+6j-~# zVOS4V$mI%3Jh8SE@8U!uIM(?))sQn;wD)q~^M?JFbJPhdc3{_A(sw?bw62x-Dy2&c zBMM@U98;~Jx(xqxnuYRL9+Eto@ifY!U?7w}-?4??VE03C?GOl)U!!Dn1j1nsKM+Ug zHi=nH>kr-P;uP&PSmZOy-h+putvfv@YagYvKH6HzuKVm`F{xCUGTsas9uB%hC-_S@ zUcsVwvF6axd+U^*J~|EgId3+<-i^%7i(o8fp4^L_NNFgAN-W>RLio{^?`Elp zwPTae5X9nCbVda(L>Dk?8cLA5ZpVI$Qkcqr(_sFCaS~L2hfwx4bJSGn9Ne35`NVrn zQ<>Kje!6^NW(9lkWEK@zyWsT)O$+{e#iSDGoQv}`Uj7#c zWIa!37Ep>~>c)l;Xua5S@{4$QCqxJ_a11W|_}vx#>O9yq?!bGe9ZFbVk~!aX>}2n) z;G3O+H9th0&!}e+7=D@5CQyUVR95>pH?zx8e}(8i(z^kK6M zK%NLnPs%MHZ=LHMT7Y&G>3!eETHr&2$)PT%>p~w-21wbVuw{yNc6CNG68wDaUD*+} z-hvF!c0Y1l0099|LuD`#K9Vl*yNYq8LXnP&V09mX%TqQu2gQW@q#1c;9EB;7f5aXnTpAM$GdrhfnjgEDq5`l$L^@DH&rW$)RT+WG1>T+z!Th$03up=$O~Zr++`>RWX)AoO09J z&iP0Eo$NNNhXWdVdyH*sj#unZYrk96ti+t3da?F0JUsXO?2R?xq95l8N>^0=1*=+` zY)eIM5kKW`SkD!0bs^cpOXg!p5EgmG{PceAH`C>P)nH#LOl$iWJs-M|fWDg)o{x}A z>UsJ7${MA*fTG^GlCIQ^X?ex_YQD0ZwO^OT~-s*TN6XKSaJ*qTgz~Bai z9bvTjg?kdm>ts?-pn>Lfb8pVrlg4wqO(uwtDBL3Ca-F5zxO{I{P*CPs?iEgSN4H#p zVNl?u&Iao_f36-OJcB?}fkxLUt6Trq?qvTwE6 zB-7&LFnRdBv%U#jx3D~c8H+?odp$4f#4}#Vs1l)`Diq=}V}{%P8_44;;Mbw=lsTIs zj)DbAeT#L=U~JNHlLo(42i?$~R488IgEtt2RAfJC&tl_OjCxearUklNBFNbL1K6%d(xMJ9iWYjRBUZeaj}m~qMNiZqF-Ejnr=;b4cXG3?r-3+9aYc-c+uO(Cl2E3e#)4&1ob9C9qe}} zx-A@Iw~(>F(T5_)cwj0dsQ$=FoVMeV?h)EjukB09rGu9SRb$2D=KVCpYS!V~GL*<=_x<>YbS?RIrM|VQF$y6tJ*imfMefBo*)!2v`aVrl4ni$c_48nAup9!B8Rt-fi;b9m>+gjUQ;IF3m+Fw&?A~7m?smG7Skc5m}=-oxQ~qER>i6UKsNg##F4oe|2Pvzw_U-5N9g^`k=J3kINHE z(vUtA#%TdbDZC%^nv|kVo*R-DUE@tFIJx#rxi&<|Gn&A>-G!1|qRTr-^oKshYrnIt zS`h>L3Pn5SEMb?Y>CbvCv0UCarK_G!hd*NVhvWvMo=?Xw46rT#P^O=KQGzg1it4#c zi{1%)aB*#myX9+mI&C&$!JrsRY(2w(FupFnCO{v26*a;&i5ICrukeajAU9UTFkjo- zuJb-o*J{c$GGQqD=-iI`wVh|4uAA^y5XUyN7CK-(d;$8v%4d#Aqv~7*@HDmSGrLecTMNZzpwkakg(@ zZax35EaO>g=xn9la}!%S0LS8^s9pM|Br+L*=CQnd&i;z*a%{CfhFOn9?K&r|KQ?RNteT`9kj{Lw)q-6RlK zJuPnA6MG@0B~fAB7icCf`%ZLUo=Ec5?~vLV*VwGYh;SdsrqTE~jS^o>5xj-N&gU>- zMv4vn-I*w4UW*d_;E<*xK(QsjQG=o}cFpt0qW_^N&c`jh;RXQKraOv}-`kRW=!QFM z8KLOCSyzFTFoh2{smsbQfCqBiJmvNGMho=9s(SNfCBClqO0{V&L&Zrk(i@1zhu z$Ma=%7bg#hteMb&RSEDcfz@I}j%0*2ENJL(9eVbb&qnt=EHMiUlwHZqJ-FpZiEOUG zR-ekDQP-=KWJ*eTgSn>q@EgeyMKAEWj_9(Af)!>X3PgP3!-)I*$$;YGs;`}V)-w&` zsAYZsE}D6TZ1po~MC__abvV&&nx9wUbv7zJ>2bUN_%7+0gG_BR``q7KsNfQ z0o9P5%cr}n2 zyaME=+Tj;@n@+o)`SgOH*_x+n?YF$aWZE!2!ekoX^_=AfyYlA42Y?-D$>KZGdg4@247+DXYUfFa2MowXVc|r^?of22sm@f*empji7mp=q z({@Y{McB+z?;gc)mr+rk;!Q`SHKARta^%&w+=eB+29{yR3a}lbZ>Gg0*^|{>%OYST zxSWT}jbQ660ke<*^*T3`mhu%NHhX>c5FsWJk9sGIWEi-7LX2X;kz^n}96vJA8{wSO zZ|fzY6oB%lIA4iToWaLOk=<(X#h3XVWE>*47$bgpj7Qz*%3SRXk5aNVO_KeY+7=VwZ)g?sFJP>kuN3DM(*4Q zrKS%5>60{9;<}Whis|I2Qq#aE;=3P~OjN({kwGi)X$pc9ug(WjDr^hqY-goX&YGLr zU6YYb+1HvsBdUGDBpqCNKA7c=FyGvHcX-5gp4uQuf+&RKe4*%k&gAdn-Ams2_>e%) zI-kbXy>)rIr-8QO_oNq+IdXN9ed9}AGsPig|DoX2uQhQwXIiwif<7X;@W&V=?t6>y znV{mI0x@!^Uqw*n{dj`gaU@}WG|`VE$DYjZ`62cJwU|ksJv}Ixz6KkHOL=5iJl355 zVO=6La$85?6CWlE#CikQp|csQk_;}|#m)HDZ^FC7g6}TX2?wJ)Y;w5U_dMjtZx-gAPqK>2DecJ~7=)z!lFaBVp~w zLouTWP~S|5h4ee`W|h2}F~?|6$++}0uBqsji}lLka4I7U#r2@|Ew0Fja$cZdcy7-% zjBSGH)*gBKm8UhYF~aL3@tk+?Nta1e4Z3&P6#teSk1j)ID38^GF~TRRmil7D?|Z#) z1%&O3BgaqJo8?AqX$tB6=NvJSw_sOL3F?4$INhGds1#*5MtR+{UWYX5K3YE0;6dE! zyF_bu;q|lLk+whJc-@S2pP@wvqooX-{M+**a~(+-c8URyTeDoc?=A1?F^@5{go##1 zPHuN2OZ|iaf;*@bfAe@qbAFm}KTh7X%^>ysFc5C#Ox1H_RsSnnU~K zpl%LO$#>?{nEbR6Tk4q=)-*9@Zbf9lu@&-E-<|1G%jf8Bd60i+mgF3QzOdMH_UBxxM#8HUIX#v&i|P+IAqA?x!1rVcH;C%!gj| zI3g`0h=0yDS4_Z!*S8ghKcP1D-Jd<~S!lC)c333Yl0OHG(g9&HjU%g4rY>9^D={_! z>qZ<$UbNS08Jm4J%fAMWXq$aBOj_~BO||UkdR({1(g%;Z#PmifQYJ4Be={ z8T^}E;*IF>U0>4t>UisRCASgcOylRyo|}s_Awu&^3QmV(#a~`ClT!`JEK0PJ!SAt$ z8YAaDnILIjyC472^&+sNq9N)d?5aR@1UERFa39bij~uE7g})AY7apvOk@PBp(1Y#m zRFoh73InFKRImS72lNxqHu2R?(?%XFqqDvL=w%sG!q|_F`l5AI7)RhoaeDfIU!@Ci z&K+#5?BCC^AOUS5S);RxjHY7u*mD*J5Oj1)&)IO|wAo6gM*NDgfyjR&gNol1U;Gwj zjH7RVL$gn~0!+cpC5JKKP^y~g+-fD@VcEI6c)#^H*NPmT(Xdmqe3yedG*H&`Q($d{ zNmrg0efv#p;T^q0l>zQz`(WAi=W`C~TxqzS-ZIR0zmZ+Wc4P2N&?qwSFVkESZYw79 z`v+PgVL{%6ON&`cr5(+?1Kpkxh4{q3cPZ8~w6}B`6%7G!bl`nC5=j@y^K921A-+wC zAdpyNisjK-#|oERbAG&z#R;#cR>_|iyX8IjHkNRDow8Fsm$ohx(sbp07nVUD!$k9; z!#Et}NxLOG=?2?v_j^oLmm4C`dCpOdx;!eer<+!H?&f27$e4G^s;awi6;Gg%n-a#U z)!wUQxn;`^aido_$kn0pcB=~RWK2`X;CFRcx9;3mIRbGtx{h6yqQOX{fPO)4Pn+_LeV}VC{xXZ5%syA2Sm-e^`Zx^ zR7vuJoSJp%M?2Z1zq*O!=e7_KM$KqP?T5d&UZofto1Gw)wm_(By4C#ICTzNAnZr^k@m+{U%wvFd$ho=WBOH#*La#oXw;*=tL>+f&>0Y}O{vJ<$u zGa*9MXV57{3C{jCr87Av(M#$OCi>b+|3SkJTW(>OoiIMM=jw_;tgdvBKDo=QF-&W9 z%jMlYxV#`fGoy%L!_ zbEH`e9`IAR%qqHm%`ij6><&Ka-td;he$j-^2KRI)H<^VYwQ2Z8Tg_RCKT1^2f*EJ= z&qi8NE;Ifk2`4RKVhhxRxgG!cV?HwtoSU8-h4~bhaUFVLT5FRKtRZ+r<+GHq%y1)3 zIwmgVg?9XuaLL8-X&>*Sm(JZvL4Y{BkEX@xh-XH^vG`qD>vSmiO1yB?L(s=LUwj;q zrK(rrXl-F9|HN+sKf{Ylc}jxx$6~~$Fj_EHhCBKhl^5HFZ&TZwHnSGt4B0O*(kv@3 zQ8IZFH;2qPm@QIw8S+YW#^}O6lEJ`;HC>FFm{6R+MVb5aX1UMYnVdDTLnI@uVkGJV z81FSEP8hP}S#Y1xnv3*M75dRu_>|dQaZJxTVp=1d!wyz1@Q<*mro$4B6KG(!}sSO}1bU0#Xw% z>ck;-}y-`MsY|YIkr^01<-EXR{U%dZ%y@ntE4T(dRQQGokomx}tc$#3xazHb!r0 zCgY-onf{Au==_7j{fS}bDbq&4^qaTu(^Q zC7aQIVD}EoxuK>y2ZhUJo(j1O3mc?rcQAr1)*NrkjPZV;>V(a5=QlfXGA_&I7%(>$H+Ea_|86-o(cXT)LhsM-;g@m~}qA93L?AhuPrOtO+*weVP6KL|1 z79>2~*?8V4%Q++VIhUgZIs-AofpSlx#3~jqzXjG^YkEdxnSm;aR+Ls(-kvz2FN^~} zoLXAhXcC>q>+Wqmsn_ZEfsA=zfcZaj0frZu@N?4CAg#k%RBIFGZbxRC9l4l9MpR^F zv}a`8{_H!K#sLSv9OjNk4DL;`rqMadZX=G>|L%xIE{@2+2Q!VwcZ!|#j} zPj7$sD)OYRPo1){M#Y(K{_MkA_gqwUE7~|IGt1|z31nS~WAvJ{83?k@R$9MuCe%tT zekZCli@Z79b}rUc_T?FiBxVh^-wWZyaMs@yf@G=S`nmy;XsWTH{yKmQtP!I6A{CB1 z_-@86^!9O@>8%e$4n?FViI-akMdtlEjdJb8Sco&%8a9+Bw>`Nb%mWYT@qY&`2Aj_D zB3u^XJ?af{@^7Pt_&$oYZmk5Ul0)_B&Dp1E{Cm1A0OclqP`ZTJx(X1-`_Vf%!C zefzYP&nl7yl3z9UzM1un155M3%J-DdB1~GuepE<7XT5S&f6udVnj5@g3-a#xnD&P@ z8BXSCefmo2wib-Y{q)e(@G*qbggWBoKqD-tjCWvoVsn+@?p6DemOM<|x-~@-$klae zO*hg~O9U-}`C518Qs6AFnlf|n{>Omroxq(X=g+7w9l^YZX}gYuYmqad%{ELQNfZae z`w$ru7F{YwD=8XuN2*A>Ds0ptOdE5}czgb5+gQhLf+Y5AfVJ>GCgL^?%g%4=0Vn5# zr8d9hkK6 zK(sIK>_~Y#F_Aa_N?wESj&L^KCGJdaB30(TDp&i?^!B}7nPm&A2pw6>o{lz$B3%+? zfEYd=d`^^lEyB{Ftu8p+tbSVDr<7IPs(W%=@G+5Zu`pwufu@#hw7)$ujP9LbPb5^^ z*NpMYJs;KB_953B3!P*d)l6|F=LGSU&LWEJ6X#+(Egj4s)?T%z-Y=?2k)rFiBLnE1 zAL3N~Ln#86We&%zRhN9BD8h(~n)ymQN4(031;bJ^Np z`yv|o?Oa~iaNgENkWk%uAr|xnjH>-^u%yMbCM>EbVAjYnnOdzyhzZ}t!Jzk*e}3Rt z{j8-X$mijC@A?^u67!abo3I1-m5M7 z6-zkff|DHTK!fLQEZ?MSNiv+?Au!L%B-5BwWLA_4aK>HuB&{*u)QlJEwz2udD5&^w z8Q5%ZxsBGBPP6S}RK|24FETVhJYV&jt{3%ChwB@gg;x0sinG9doC7|KC$cUarjwZW z+duD(yO5`OI-%mu*>r9PiR)aX8y^jnZL{i$OP^+CUkpPSw|zk$QI z_vD4MO%D{Ew^uKD9X4M$^Tnzw&hAqZDk0Y*v6tK3B`n=AwG2MV67N3Zv9!y0SItt% z&osh+ixeu9Jbum0`+-rOyaKrVF^h=M&8Ot>dY*EFbnCJh$OABGd06Y6u7I+X)M+9S zhdB>>7nDkQUO>})DYyVG&E{=s*wN*sU#9Dt!<70i{Dm3`xY;`O!4KAb3yzIf4_5N|tB07%r zwb>LJmeBToU1MYvbwAE&?ZgNho}tJuQ3leSf@<@iimPP8n1bsC02Q<9zvim(dlzQ%C6}TLkLB|TufGtNU1^#*A96^*lN|SehkL|JBXWBxG zNFdZE)7fIbD1*w_XKx0I_IcJ)6Immy*|Xe#))l;p!W-aCGjJ4b3{-g5#+=||qz7Zh zx%Gw3#ZIk^-IXx8w0+QZyv|!koWBypIwS<%Jw^unfQNNSo~h&a%s#2aCsP;C$R}Cd z%$L3>l&phtLENOrTNL(!DUYn?)|$S?Fg1%hiF*k&POJI)g#YRg>qaJFuKDh#yxgU@ zY0qaa6iF%mL?fJF)-ufq=><1Aa)G2&Gv^G-Q>~5|X@)`HwJc0W5N|5jOvMYuU(HYU zbRMQEE^&!b%{syB?NJXU{K+41o=L%!-*gPwR}ESPy=8(Nxn89TyPxseu|u?_-QMRW ztpvXQR2kqANFV4IFm9ZkYu+t#UHos8+zc0~`J6-YhNqfH&*@Y`khqIatbpJQ1E zdX1`yOvV?&7|O__Nw=otsJt-=*hWF_pb~?zsS&3(tZ}2eBjX*$MBr7moBiIf5PHb< zmgwE$fMGidQAJU43*pake*mRxPoxxXENp)%=%|HO)0938eRg5*f7)uX*jbB8Yd!x; zVEa6kzVBPIi^>>LOU~j2Np!E9+VT9modj=4N4Mhr>-3l$3Y2}8tStJ2RL)d9PHS(6 zs8#d+ktb%uAUWmu;LP0$VpLNu4yQ*2wxu!Y#A^u+|KX$woOX4yS{%2iw9W#Yp#iVh zPBv*tRrXtH%M*@9N}Vi-hcs=wD(P?8#@SE()-cWG5{q4u(6d7yi>gfAK}Id1;^VO7 z5fsiGSHQX``(Rr=U8B1yGxA;O@7?=yZ*a7!>P{HZ4-o}s>Cdm)%-B&DllR9cq5Xu2 zq?a+FKNPXk7TYBv_f>IzPAKWd9LmD#x3q@ZDB#)kA5GeuCYooyr0MEeqclZyd#XO7`)c;8PS^-!xTK_NC)mqz8Pz(P?iMZ!?mP4CS$*LPsxZ;<0^)}1Q-_iN`mV&u zzBx%#@YYZKJsf772D?#~73s$D$+M#cgcTRfXWNZiR+-Xp4T5iDWrNpZ)xoLo)zo~8 z5chdSVKt>V96^=o5E#uH;hhayT7E^G9-_;H>aFFIuT=1`DXPrUH=d-hP0oDcGzSBlRr}*vNwc6vKA&Bk6^61_cPkE_b8Uf=r5m!0O zO9p0%QPTLzfAT=z8{elXLTl$g3lcA9yfi+PI?X|d`_Ka{^}wJT&XmoqYCyWIqv*_M zfF`~Dp7_jhaBB0~NOk#&4A1`)`p^v(eHFositq-tJo-KFoU?S@l?knxLK>Q}p)ZMZ zE64Wd zPx&M@-`1UZ$bkl=kZY-zBYf;yGP;S$Xe98bSAWLDtC%=@&c%gcTcP3P6ho18z6A#o zeElDRIUa4bpAuBpVo#e%OR8C^;O(QQ_3PHW@N2)N7vlD0P3UmK5MLyT#Ot?_*KA+* z0gw(|W1lW4C@)?XZjc6Ll$jv=()?b#Fh&haR$pq&*^Q^OtDA1_Rnt}AZbw>bxOv>F z6~7;Uf2Rha{tsi!aIWx{oa+9Y!)T_y6z%V|!RL6!)wwFy)Va(Mj_T)m>6GG&Sz{ zf&|)+kh+vet~q$=tUh^dbdG8sT}K*T!+MYQv)}Z89gs+I$1!6OY{hbv4)fr$tv|lH z@NlV(+}HOay%*P_K0jBax%UWuPa~R49g>r>sOek!F_EZd zOl7Fv@hO=5sK!-DZ|3*){fmv4&Ut*N=_#pE)U}`nQwxpD-(lOYNTL7bb5g^T_e6gb zaq&%Yhgbs)JY_-%xSCQYZ0y+C9z=X!?v@T*mTT1|fu7JgbES?}EHd74%?vuM`uMGW zf)g;}t=#y(`rImIIQ_~YW9_-@`*DZi8=faGc3tPpV|k}y6nHQpZJoKPI|)3manxw> zzR~!@Rn9<-|Fivt@3H(XHk2k3X=qrt+lg-L>3e;bF7m-M&P*ZbGIc=GKh?l^K@XIKUi)-=m(9wfh2;2Bb%;>~*XrUn*2MU%1D$Q7Rq zGs1nEf`3TVHWG26_}nrDn#C_2p{9a^wn5_4=@pxS1jT zx-gXbCps*6Mu%FOq3e@HMmj=87vdQC=qwz=MLn*5YkK3JS_hEpDE7Zm)L8pPv^8PS zjW$}drco5j8X+APaJW|2#80!p;Qp2VJKLQ`qlX)PbB|m8Ud#kvk>QCA)k2> z;TaNb$B(TEX?jwWm-|rdxp2?)q&6}n(<^TXWn=f=^9#<<9^U4h@jqB<`(AOxC28CE zyh{(QXN%D*8j;dkhMq^%+O$Z>rUt7__XaRw;v9K=&wQRu>N=>1Gp z5vT2g9uP__&F8RJuhTSmIIb-Ty_hErq=27XU81hKSHn3QlzE!$*ij1{*NkB5g5l2} zs#3dBUCX>Y_LYJzt4X0K&BWPmvW%lB8o3n}cKSy-5=M^)E8`y#dM*^krM2LjJr3Y+ z-hX>wBYv?z+QWMcBZHo^Li5Q8iongY3X2}&`aMX{-ti*LBUVYcK(A?}3{t%b#T43n&T-VIjq%p8CJRR|xc4}>&eeb7#2my*`tn;o|oM>DeQH;KgFxz62_a70pXo^ zwm9AHN1m!CJlQ#=r9cxDN;KV8do$+*aC5M^pg>HFje8XE3>2QQM@~4!>j2%^+P6MD z*xvq{an{^>#_X!h)X9On%x>@E8CwD-0+F7LuAoSJVx;-G|N19P3xbvav~)sEO)o=)7BaXVti+aw`_4v zkJAz59D}c(_PS8&Ke$_L%RUe8{n3(tedK#pLl{!kVtG+Z(qM6g^VoT))At=^SQf*5 zPY?hw6Eln-pYU~jSJtP}rN{wQ=*EjrEGY5-B~?XkeNGyY>Jj7zdJ{@b@2kHvuYV%` zr<{x0W6b2xC1LeL)VEUP7m8MklsL5zfnko;6VSThfW^}~EC(V;9bcXxTXOiMBzr)@ zv(6_f62$Vs{YH^CJzwIqL%DY2>tv^%jhJ&!^l8ghRUMz z(yR>9!?<7tK_Ks(k2kb^d!jcnaR2j*9T`1b$;gWc7#KvFHQH6CLk>NWWa9Vn?ayTz z;S*jWPSl?W&pa(7m{_{5Mz#><{o{lbo)4>>|K?{+Vt28lX<7f!h!&^I{*Y|$1BwLu zQ+pRjW~^mMRWY_fTw5@4f^%B{3AJ}B43eC_30*+aFUj6vnkNlV$%NU0nDwx3WM(m> zmJd+%H^5~f@&W#IvMXt&+6s@?FB4_Z_o zrLRtgd%~lRBne?|+c%zOA%n$f6sXQ~F~Y0g$ICPwz!W98mtK2+f^CNgg^(B{acV8G z&f-kNd0AC`^%>1E>HlA)F^};=cCF)`D4W! zhp|C61w4BSzaG9CXw5haFLaVRtVqnbPLt5xCTe|-41!5n8f~Qk$Y_}yZSMEO($)}C zFii9AN+mjcEaG|jk<%sv1U=Zgo*R2-2;lR<@2iVYb6oeqPq;u-$1wgOz<+=%l|eU2 zLp`#xzdMTu@$mjq`I*d8b59;69c7@|1KFtJ5&3w#K39T8#kzjJ#yI>mO>1~Yv;zO$ zE}UMIWx%gpfgmv7t30K^E8gA!35{3FG6m;YwbF(bh2LC-!ae_ zytWKv2j8vxo^_lp2HCUh3}G+s7oK}9;|Eh=STG6bv^!nR?L^M7xT3GxaM1&)iD$w; zL}xero_R1s8c~Gpwoj?rb8F0(aZ`F16Gy7>sb!?*ncxo&XL1&xpG!GI+)9kLQDHeN z^(4|Z2FVN1mz48T>Q5R+G%RE5MzVD!O_SetWYjdKY=@1MiAm95;0_gjmi>K#IWcA) zdi&-tejH4iHqH-16eKG~ivmO;k<#;vFOe75Ss0vIFuts?`G<<13^g%MI!3tYFYW0b z4-ZOc20rQRQnAU!YxcxP+b@Mb?6mKr2SC+6`D_zUX-H!#N=nYy8wRxCM@T7l8XE-#hd4uFIfU|;g77fAx)T|3EiCKogC_J4^_mB1|HltZ5Hx)4p%|Es zXjxA+dlt;zg!)Dnqm|X<_SxIvpNQnk@-87WjX*4v>nqT~WtxhNF1oodfq#^o^XTSe z!>HO#zuFI+9CeEmyS(wL9Z0BLSp0;;XeQ37h!S?N6zqF4u7e`K%C5&;gGmEr*S}MN z+qe*dUn6{Lg9*}+P_rB}f9W?idv*vAPd=`*4C0wRNL=Siv@>S_*S=M`J zP&Pd4e-23ZX^bO(z;}gQH;WB|im>;my%uG~jSH$4qK1#TZp$sd;Pj8xa|>I%6ep>R z+3rV0*M%Ob+hBJ*i+ny+I{$?Xgc3$-{|E~XEVVneYpR*x!`^rF;~A7)wEklZ?1S zE)fm6N0>9&9FeM!i6+odGErc#6=r=nsb?Lz#BXOq6A)GTq6XDVR$&6aw4kz@3VL}+ zK_7JPa#q^Do$9k(DH1&KlI4EtmQW=>{HDx&apOGWHyc%5Ap99cr$XY>+taHJW@u@`+e?= ztv{?VbEhVdlg#~OCux%~_`?%C>I`ogo!x{jCFgBLsg_FTF8i?dXzf2a&$>dc4yT=J zvl#+=4ZLAF0!K4$1|^0^jq?|dhIQ_j5es4YBt9=LqFE0}LUxp;Pk+?m7NgnBRY$f# z>q+t6R-Gp>2fYbq+@sVFGUn8ueqP=VC{M zTla2nXK=-;s0-(omt_MZ$%0pnZ1YVm*@hoOL({y$z&`J z_F5PS_?W7FMD3ziMA|6K#Xow5jmO@;-6x3o%>i` zf7uVosEIduq9u+{)Z?-sNflg;)tvslQHdenA4cQt+e!hIVLh#to z6)FFVp_5S*XkL5xoivdk_uU4avlWLBcIsP8Uc4r4=cQ)?vEK_g`nXU(KHxrV#uM8} zRT{Wn7T~?Tk7^s*Z(2;xp->QAjA{@5f|ZC{zj*R&%YuJ&V-brf+g`N)U`)Sb5F2N^&xvq%=e&!MHMwI+u=hhK;SP?*KEjVNjZ;H6sJjGpI)hz0LQxc zP357&y=l3m-53Pc>ofqra$ulK>e=q+H-IEdyO9p(iL)G`iI2+q48aZZjAyJtQSZ5O z9j3$8Oba zV6LkEES$SoXW;8oDf&gf51~fuC!gF?cT;>=BGNJSo1@7ZN9x95J`Tn(MJ$aDAx1{& za!K!Gh-H-K_ooqeymBm+SZHP9*W$|DldQn-Iy?qj8=vd@SSF!FGO_}a|W|zf_2?N!^h~v{#b5f3!Y|KTEFEM|IZ(My^**~v6l1OZ{#_s z`c99kkw3bMe-PU;;iJ%y``y8}xcVU1gnIPXMK3HfPdeQr#~Ci1OLhtNWcS_}Eb+Qr zvJ1PTP-753K!J)oBk@qKJI<_dU=V-cYAf!!I{u$fGrYPaD>>4+& z7i{p{?^{~1{WlWotB)7IR_mT3fA%fewm+-l=wM}2n8)$=RJDazpaaoz@ofBa!EC%- z-*KJwvXO6FFLqs#dY@6eMy4O}d4bTnuhu>-E`4Z8Fmi*57+{Nt1JYF8)cuAN^!WbZ7cr1W4zV!enez8vuISP z?(>tTHA_zUGm~OCc4QVYtCsBX;?SSyB*(6L8}lzNz}TR%g}2*DjDe9NLT55Tme{?i z;j8Szm#V9Z5M0l4L?wdnl^ZH4aGE&wrL1S%&zKWLGray6`hS0M`6OeaI5;!QGJT{p z&`dN|r~6~#YIe@DnbCiSyyEKJ^@=8SS)>y_#UEqjz8H8=B5h^1kK)Qli%OeOFUOVh z+Zsl(oWBbc3(Qj0D9%*M^i6ix0A(BrFl#U>OoHHWMqQ;&gePiDVVY z-gv3=5x%!BX{>T-eC>3!NtU3wGM5~OP}x(#XKEK6-ue*Z4T)Z=in5z}OzbQ7)Y{|a z9bw(8pH^7zL_aNG){oTn?VS!(lEzlNW;ee!Hx`D+mVB^!@k#Zg!fhoZ);@3A_0)2g zs2yh=RpX4)omnC8E4ca z&VMV*%q7IXTVhXh?1_f3nR3Oh==6<1obA<+qa`tl01Za0(9$7J?;YuVl})Lj=Vro5enMdS2UdS)Fix5**gf@@{G|{ z31S;~?*mDsSS7QpI&Hw;ZT|`ajBX`S*o07eYI^as(p&&hkgu+4geqh^pW?9Yt6y-f ziV`Hpp?@BGnM_-+?Z;Tt{pHh=mUde;0Ywc2BGZoj{LIyt{MZf--gR3%$lkOtayH)O z%vzc*3fvVW{_#z~I>P)FQtg>5Dn=3}=?52{TX<4muZjvya{}$UJfn&#FU3_co9}F^ zat9CKn%R$gnBd{nowEJ3KhcIRwuCw$DWh-^`g$~Yk zi}O3NXDbl!@pIml61KV90$hv2AHFU;+{&H3ef}-Iy&d(i81Fo|t5korX%w{>aveM+1lYOVO?nTf|cdnE9@!52#x=YT7SqoM)&&__GwDn7W3rJ7MqBNv)9c2DF497w9K=I_E! zOupmF2Q3B)v%^b#mTT0E8yJb@aVJ{Jyex-Yy1oaUsZL~&|;}tZZVP8H| ztnT;m3Ylj-skp!swHtQ{=R)-jP>tFLiaD;w!Tn7&*jiHN6z(b%H28 z&4+jxJafA1JIzK00qd05>z@cxktB7A{kQ+ZQ=YMrWQ{5+MQN|OcYZuefH%xvqH@+6@w?Yp)^z98_cYA+yyLnl9zTY@K z(=^)u!;8^4wFKrxVq7GPV!XbKNcg|yNPp9Ena6U3gAGH+GV2ky z(fPt5^HSC2xwMV$C3|k?g~4=3^ARV-B@$07Ok6nxxJru2`)y9!eCf@|Qo^_bQJ$h) zMrBuVmt^p>vdMnltv>f+Y=ik(58j1Zp!406E>z@4O$lvcnsC5%0L?mm6!HQk-*cNV4gYVa;m{q=h8tI+1d_i@kKr~HX2pL5Q~ z-WXu|6KtIv(EAhz#k)_%Q*Y!4ef#yrLJxEqA6cfPYMtfSWFN4YiJ8Hd6v9cJ1p$b{ z_S_ZzQHP`vdHXr7?a#UPzza<$|Lky@?Wudh>@LUx=vAcZ=HDbV2y>1uw~;iW_!MMg z%b1~U-Ev(o-si0fb@fm6ADbG`t@X3K1p_6#{Pnw&RjJo!56h-Rn~L&4!TT0pt5F@J zmTu#Rt@d#S($<#Eb2_{voda)|uz1{lvDUSKe)Zu&U+I>mcyXpEY1FpBfd0Q zlABCm8iJJ9ub9=ykZc@yeefZtlaTyk`60oFF^rrw5S2TV)ERW5!OuH{xNfYitIn@> z`ojW?{UzILRrHz!bWg{4(pNl&hI1%(wj(ccW2_UM=OUSqqH7Y1`B67Lpl4OdV-xeD zcY{LD%PucbJYi?FBh}2L(-vLia-}5(TX0&lsOk254It(4+*6WmDWz(F7cV4<8qScq zAO2EY2@h&84hiE0b9B8+MoAAG0}!Kp(x5VrkV%Y3cUn2gfaIm|_cQ!g72Lz+E$wJq zO}X--4$4Be{7h zH$Z^NHg9%5-ot6+2JK$Z5_b2OWV^k9?#UIRyjcZ}op(u@2Z@IjrK?vLlk)gmj`ZEb z4%XeoFxhYp7D>5y3I*}3c1!91ei42)I$QlBB zYM`R1tV|X2K22I2`wH<*#DQg17Q`gLW$=x66E?&I2Nx^CN_~NZ<3}g&>D`@@Vs{9N zORl#q`F?R@!#rRnCW~G3pg(BVv~KAc^PMw~@K{{d7|l;CI%g_yLZar%hu0e)Ec^Gf zN|&!y9ByQ1Zat!WiQjg;tadfFNa<%JLBU=UqQ{Is+x?Xm;^>X2yxRl_t+vnBK8<~pZT+@w<$R>$f1rQkZJu3UJ#|yN1kvi%-{F{A;Cy17qvkO z8@wWEdI}7}=p|5E>b2e{OW5{SH_feWhT7;29^Gp1-Uj7 zWi3q`pguBiByavPCv=NoTV#dh4{GJVQIe9~=kZQULxKC}&-Ow)Pkf$J}R4pY%m@y;H} zs2(K8e83ZU_klviGka{^8)@r&SU~a)?`fSe(6_u()1=>rU>`g1e!7xyT>r{nr2YdM zTT{`q!ON8GVeonD?~vQ+xL}@i2l=jD{(t+f5&YIM*g4bPfO}$Bb$l}2cqow#dfxZn zZO|`OMtaO7d3t>dW`Pj%v4uu>A*ZCo?d-Wg&8y>L`x1+*a(VS}f6+Hirz<4Ho=&8{ zW;E<7F>v~E&4BKuB}%85X?zj!oh4epX8*@?_W$%XEi9ta(evlX^Q1^)g#UL@f>IvuiF>jmMB5}uy01w=!EcB%@ zR4HjDyq@ORvn!mq-vl?dIP8ry4Mq+04N~k}j5TMV$ zB5c3b86cyp&*PI2W02frfTc*@>hl*rj{(KR=lI#L5Tvx{>%1EB>*$|4Bum84hvV}x zjRYP?1k_`4uxIN+NHO3O8YIwfboPCQ5b_P-d~z5w)Fe8^3%XLvfJJ+G-c!$1&?+Kvv+(<*)qFGIZol;ztws zhBRV-WQ8gz^Zfs@LO6H980@$_)eltQzXh?NpLzHf$p=3QSfI@D0vR)%&b>*LE%-|0NkNGiaTQ~?;K*uBD`HsTIH50zwmp@*RG|Y8@ zGtsRFpXT}CIA@c0sSMsuU-M!5Y z=Lo$?x=7Tb9P)oR>Hluhznmo6+f_S2_U8rRur$Y|g1+Xr%8EvxMhGpWTDQM1@9a1ALhfh`Fi7inMaXps@emJNm2 z3fC@;_7=8ppg58c(taUSv4;PH9P9V8=#J{sf4}-fKI8idNBi|6z`~gc7GCB8s#i?$ z{_iG&Jv+q9BKf_#-N3UJFv-S%Kv;kyg&LMULm-*~Hi8JRVR@4{D~%j{2P4{khH=d^{xLAbo|wpT&Jg_Da!jgr z=8_ZmSQA{nM2R?B)w>EBbzVe)$joRn*Z0#jM}>FZsv*N{_RnHyQiy zN2Bk)B}>KXMxTxa?izIjl9%ak^r-Q#fTCqf>F5RrPtOD8C_rOc_9y9%avb9=zD}I7 z21Tk1c&$r*72rQ(a4;Gaz&DL7fsD#&?H&jmcp>~Z*DKW~;=n9mK<}HDE?cNNkct(KG)1pB8 zw5ALdw?b{ruA7ahc%jB$5JlJ1IyVo4oa4+^#Zfs_NmQCK39ddS*LKj&gS+6Ro$C;@5g6|gHutz#l@e6)*Zv8ii+HB#9M#Qg4K`9EgpuSq60%v~^?uW*f zNsIOMH^_~qCjtatgjyv44u~x#x_Tz0ad7|7Ta8vk@)eTzL0|vX8AAI5)B91^tr2}- z=1?*DE(7tc*JOug#X;_V1)~TP<=zKw+IH_>H$dlfuy+jZY2mS*;GJJ`VppmJPex=Ji6CK-2gH#3chQlE0+@wA&Nm#6GQ^KR z3^%0y+(SbVO^@cq2@FIF`xZhRlaxg$XntJ?*FM)QraSnF0JLXlPRyc$yZ{Ebbs zz6m6>6$JH-7O32Xi*Rg?*z2G`X1I)bvkrT@f2^4M2@h=#4vkIdM!w?ZF)(#4Y3W(U zpW~h8d#8s335y)U~(4 zh=H@VF3VWEc?eN+)G#?KIT>w?mtl|r?~^<_(o!uSSmE;pNs<(4WQCt#h1KbFZ~^@* z1&|e<{eQW_Lr}xjm%5_^NO$JpUbDdsy@qfF!k>x+3?+Cvu8_b4u3f6Lb_~12#|@m% zq77<<;MKBJG-e(wG=B*d;=Z3`dqL8~`UrTWSzs-}`Q=OUOjK(j;?fr1)<1C$Xf}GU z9L|az8PPh7W3ZKU(W-3@nsDZU8`Q8cvC>2|oMxcHOZP$#6xVYFBk}MQN1s_qi$OL! z1L(n*oj8&ugJ>g`n2M+hhWS0(u_3dfxQ$KrCM}&p?W=tf7B38YDlm9-W02t z5$ZM*CDVJdj$JzB*QTrXR2PqO|Hvq3O+tXYdA}2@&4G4sbw(AMspuV614?i`<1@Sjy| zLMZ18G2qx2l5yxVwVA&6Q5Fp||Gfa^3Ad!TVMHdZtesYg{!r^*y3osDwU-sj{yhhvTmh}^w zbO$rwwDQ&1JbYz!b27XPj185fmfD6T3(h){ke9zDi5F&{vX^}dg@M`@gw)s65DrD* z5z~^n&on;O5#z%~M8BL!43!Yao_)Qo=O!Qk#GWExu2Gf`*x~T>9GmNT(+&B*%(W1* z-Ln5+u0avCD;Aul9(Iq4s(S4r_(Z-CtK?6YSo2X2my5>tE3t?ut=OTJz|q_r1=`K6 z_^tJH?k)ti6cyVtsHirVItAgS{6>fW!{?wbK%n{FKH~kTZQ3RH`kueOIh>V*ml`C8 zs-@5wE?IOQ+lyD%3%ZUkWgKR(15T6&U6baW+zjl*@ zNEn^dtIMDLgViKoO7`(v{5DIYK2IQDXNXy1ES! zjpKiD0cIe;<40P0S}rHYcJ9(_n1cSYYS~5;62e#`Jx=%f9U0umvFLJ+#EQdG(~WN~ zAt4cxFsNSohM4D~5paqvLJJvsh@1Rn{T#zE4_qI=U{(B5kn_vpkNk{YgVdf*3nb!o!OmBNxuY7-`yn!Wr2}1SE({|F>}F zvcM5UpIpgtcF_m36e5nSccLF$5n|#+I;3Vmfs6~Z)1n7J3-^rV3L>s2rC$04BECnS z2v92T80XmR@M<0VWUJCcbOF)qk6Z3Xl(OBY1|XRcQYJb(eFa5MHHf; zJp%y{FG5CNviF`t^oD)8x`}RVzu&lDXFV7W)cP*dii1s6n|LI&1RHJ)u2V?rtC_d1 z+BX2;V!-G0&&VySTFw+3(p6{asA@Yt1687-NDwq0b9%;fg={dUeF#WT;buoj=(pGG zJ<6i64NP+;q>LDtYVeXdf(!a5ved!eozZPDT0xLU^O8B1y+y7_c!nhl;#kDx#c&RV z!|a15`uR42$-v$V7cgigsBc_>x4(p>E_($t#piD!7a`OSat7MqG#N-1h0w{1W?ZJ* z<_e76Iy)nVfs3_Ix6*hT94a<&Y^Kk-_Q zWPUBdv(6xN&YdbEH!hZBn~Zx%ic^o6& z?Y^Ew7d{?~`LGXgujWx9@#tATw-hwY!~zl4p9JQ|u@LFA{N2Pqu7@`sImc4s|K@sB zEkC1HdB1`GYqp1P4c_adh}~HOJ$`dUd^!Th=C+Ujh@_}JdGRFq5)kgi0~Z@*h8%`=qfd=Ox1 zej>RZMldsR=u?Q4j?ezVtUe?BUn6X!2J|n+jH zTw-z0^~s(}C^LZ_Zb2vz@{iLn?Q?wks+dj>HHKPaT* z0KA}}_r#rOL5fQjyJ9j}xEWliS*_lznS%B#(z-{_$C=+QsbB4l$ERa^kAaRylQ?%R zer6yACwqp-^_P@>hYUr5ZdIcrja6bxPOOOI7eR!}+GBx4ndKY3c^_PN zB8k&G zE5>)gtuqVsmF#s3fLCX@NM9=K9ru|fpwb`w69Zl=Q}2Wm^&hwXiwSY-kCB?e{{R49 z6+;q_Gq8;RO=|SN1I^*Kz4$VZpsMh-=ds{}HW-Lwj)3-{q?5}0=Luo5H=-NJ4Ga69 z0NfV^Oily9((Na1ddON;e8crq1cJ99c~TAu+=A44__Tiko~~9EuO&$%J7om{vj$$P z;H7jzWIIB~Gq_=c@6uuB?j&H(?yiSFg3E>8_3yIi!XXfJ|0neYaj(2O_FC$mram9pdGi~tdpG9t< z+qlI5%KNVuCJ+<8>cvVe8qnRoVa2`;UdTk-arztzlH!5YWhBnk2YiZe#civ!<*c&8 zy#(Jh3tGZY z(J6bkvQm&yI|xKP%L6`s3_7*|M4DeyJ_=-7(eN8G4y8)W1O4E>9*bG0yx~B+KRyK# zTcOLGe_a51pW1!JhO>mV-K(LIqd!QOK>$$ff0j*kEq1Y#{JKW zq|`)3WCGEA(s)9o?ZcLt{|1R*mMRc8;9G8{b_ntF!Ae_}MZ~xV<gww3*$1@bZN z1@__}SJESmph0HXw%hvXU@Hb(Z|g9ONij>n=-5NDG>{#LZIQcx-^zUgrc(J105vo7 zQjj4{1oc(xEsMpdQ#)JWPR+VRijK-_oNE)qvR$Ux^vn-o7*>A<(4jWPPpz!UUNE~z zG<#?t0-TX>xP7X!$q4%Q^w@y#c&Z{LL*%Dj965HjC4YxdBF053@BM~|T|J}}nX1-< zk1a3a6o6fc!B=1aeF{r(w4|&dL%cYK#W{i$Hk{^nKk#7A0aeyWQE2YtR0+aOhQUZf z5q-SX-9Y>{{S_x91T7W%^bT}gxQ(b*#aZGW~ zhPbaD$h2WI#Rn@iA8sM_k{M_rFdd<`n1z(bLE$6D=n)jM^kT=$z|IUdkRgf41|xl` zM)u)d6Y2Laj{|u!IiGm-KN;z7Vh9lYm~^i)dpv!Du1!VXf!fqnXW>eQPMfOYo5Quv zR3Veh5f7`0-OejnNSf;)5D-ApR9zYfdqv&-U?cOf_v4^n3(f(`^>9r4eyAwK>s4z{ zV%+IBRzv2M4RMV?GvTGPu~6z#U%;cnS9FkhqZHlV=b%~YRm8RNn)Up>$j~`&MX{y) zu4VvSM@w2Qj(|zUX$^ITptZ^=$w9v7Kc#s9&yNQ1wUs_r^k^SdcmTP1Cc}!U`c1QH zpKPd`ogy;*eY1Ki5h=Jxj*w38c<7;*a?UXzV9#%l4BajM{muTQycoGB12Z`0k2J~p0hPFa zLeK62r?Y?T`7a*CF?;-z?IDe=_sc%)ufL&>%J6jz$7|V7qC04K^(70&GA86CW+ND} zH3*wTArn;C8O}Zb1=x0>x~|W;I)?zJvj>0)@Qs1VG5czO&-e?*5*f;vODA%DUasWu{=l}wl=tJep;())#$f^O6bjm#1Y*cdVL~19#g^a)7 z*qB<2?g6<`NZsR3T!-wlED?-Knj`o5nBy7~9E&Pwc zx+{nFldt~4U@=w+YFWyPR7;VpHJrnKQbR5BYRwes&jM7@o@o*>@ghUx zW)Ri+gO-Ag6v z>40&W_^=77Px!6=j~xuFTc%@t$G|PppnY9ii}shRWU)gV*>rX;9@!@Ay|61+fJ*b~ zTGPqmlLm~`ph8B_Yo}?|$ehVX>f`Z(bAo}2&|V>4WWWUm@0e&cZOiquL`w#jt*DEq zl9Et90kb7F>}Xtq8;FaB7DO0nGV9Sf21FDX$PLH)C+m&g3=Y!Uco+vT5~3U#MLwI1jnYJS zlL>h55e?L^_Gb0beCuTa_aze@9>8j1TF$vc>3$IFLe}b`~`{orv417d!0OWRL#{la~zYrh04B11qd)p3qN(LA^+3;rXkaBJj%(LmpZg zk)QMC^v0}C`)Ruip-m)GAMKs9a(lBt_L>aiLk8{q=YxG(?%!~M@-`+u$s<#2&0HPA zb{%Cx1ZODoI&ZTDrS=uP)U3h{!~Nh-ttofo?jV_oJdKX;2kO>mUKf{=k4Ws-PJg6l zp#yKVxdQ-LLJXpFvk~b)V*aI(WwLtM3S;L@+l@Wb3tofae^n~@SWVK#7p-c3ep)Q^ z5uQ-k!%Heh#3DuV{2yfzo5zT>BXhLwhX+2dBcu(_I*Uks2J%Z9j{$nPd0)qpMVf!2 z3SQ9dqc=$*%|Sjt9v|Xu z@8sEXdWFIO=~bk1{Vk^dA%06;LYu9EdHN{KxrDApjYf^50W_P5CM+3F6n} zO7dgER~PSMmx55-@T*4L`{?p06rfrVysvPDgb8V{jG}@rsAk*Ychn`g_KFt`)ktVl z9MaEC_cI$@GOI^rhLOUlwT2?5hHiltZ70MdgGk*;AG#eV@h$GM{_y^3E-Z2F?CrS6 z$i%}Kq={n2Pi$Y8paAud5S3;n@p`f9JI<=D$Q8B4w|gu&yN3nYyF4X3i`uUO~kftIuR0Y71tw_`L( zEMO*Wy;UrX7rj>+kndx=t>=E3dU*UU!RBx{Qs4n~K)%hRme*TaT9BkYRsi)9N+WlP zBm`?zJ9hcm4e8o{Wf87u>zGUK{jcBx@Du8i93k2a@x6?IgfG_~)>>bMgM@1B72ZWE z>kGe;FqRth-}pqVjjQs`C_O5h)RYMQ3f3xgUCT-vOJ#j6f(;$d4eu}+wG>A$QpE?tVDR6TQ~wCU&~4G#xq|vJ3+Y| zY8!WZ*&>#J@qMTTkbT(0I?9dR6)>>N*^cJf&QDwpK(J_$oCKi!Ad5i3$eJy`?@Y_9 zWW~Ty+L=++b4Yf`e)+z`Y;=prWgP3kF9o8)GY}m2<|ubxBr&8srk&(qB-Wq%gEIyq zxv5v`-=jhhJ!Z+^r8RH}`a~`w*Qi$tZS47Ftm(L|918P@gtP^S=;Fpj|74~q?4yVc zkSvN29ZMl-wJe9~kZGim8u`P=7(+1E#KDv4M6uVbpdW@lbk{y__(dTSuoJ|l7%Ove zS=w^uJ(g~+R*KF1WKIoKE_mv!zl?mn`a`FAd+NPJn!N=_6;*5bQ*{=Bq6M8@N6Ons z#kd_w3Zj%PKW}&s-GC`_%?MM&DblQHq}Q~dM|Cq)-Y|Xj4GZHb+IGR;-#DS;Lh3ng z(vfJUFkcWr*j-MVZETnuOy~;pLZ5;0>%VXv0yM63`|pr2FtY+#7a)W(FPA;g%<_L= zx{TDidr>`YN%9U|0L_e)0X)rTQMTsm2n>N{q>#b!b=<4>EB!1>e2%6R9II+JG8S7% z0eSu19o$NUTwZ&QO*9tPoi}-?bl=J+9wvqNzUbu?Ud(y1!50Ua#*!^A`P9pqNAJR- z7vjd7kG?^IdnPeaivj1l*8)`EEv(N>-a0@43E$;9w(y*xr1=SjF=CDb1XB<2BM=rv zsvu>p4&-7Wj8P11nczlNJ;#$8-G#X)9&Tm?HKpqs81gHO&s+@i=6- zml?4c{l4%-Db>)~GFp9NwyBhH?VmlJ@Mxr|sm9i;z6Sqjp8z#y?SSxA^ zT0VRn^+@pX{9aKm;>Q>GCrn|UsTIlEft=X_h2oi!LA&xbY=Tui+8WRf9>nntWy3Z(n=xDT=T>ypM@5IP73^{xzjt~rdZb85d zqEceJnCh*j@`e2)xH;C8#t#==j+`~@O-X^Fz_L*ouYx^)3mvTX<&AX%a~^F$C{Lly z@teN~tF%s4R7A=mI<6Miywyc6C&!qVaO<7un1F(@&&Ie-Es<`}r3Nwl_#enf?$e2{ zNY0T2kX#&?s%P6V5ZhBvCoD_l?|+QN<^uUsoIVsl&3wA*q|WC~AhS=u5~nO-8(gV? zr_D&t(76H5QT_P^29y>A^>;(o7@zZRc;em**e2A$|F8#VTbGE~<`J=(nugi+1;^3U z@#b=Ehl!IY_^8TPKT~Td5{DV^%kmocmi%VT)#$W`%ZX#18?S{c^;bYo@surC-@(t0 z=10z{6kaGOx9Bs;ubz4CbN3JwRfg=IO$PDvL5{1;_C^*W+VAFjdt#V2_4km>DNQte zmeh0Nm}BRc%-M7Q&!(jugUHhVW^BsGHQ$yNh6S^ZacF;0zH>eN+=aCpOs08sgZ= zIu`|$iQ@Yl7F%RLtc!nH@ovN7hXsx!qOx6xoG0yhpYi62k6imM9brC+ zOV(4aD`FBO_u!~NJQRQMLG`70*l|3>5_C#5{jYckp{+}Tv?lS_&&JTud3U1wdcfg#WF;jCn2I-;sKKy@{fIHq-Cs$)dm}_JK@C@0g^;Y> zz%9~Y-Vs%Twhqn6;dJJ%vf!2WdgQt-LD4y3#c&X_QUbC|Op|oEN*!aPUCxVd#i@q@U9iiaL3o%}#AV`(e13~AiHq3f66)?rd66r$Z{542WQ2IAjSt4jf z#!#G#)M_yE9!JPikZ6faMw6l}H&4DovhHDEL#VoJTHoCZ2hqF;SbJga&S$^Gvv_a( zQ(=~}S~Kz+r$gTJ6*9EvA3`IuAM|Yd zQ;BY@y;P!PG$d}MzX~8#vS@i0sb>dbYD-+xS;;(g|CriOtUZc9y|~R&RtU@q zT>aTh)&iYDpKjUEuYkasZU2H)&0UjL`w(GC5Bgf!n#o=qgX$eYdFsOUHQqa+8G3Z< zU+&yl7z0O~0iBuCDQsD#@MvRUVA}dv)RP9tB9KM*)kpzGl6dBeH47{5U>(;xX4%)^93D?aZX}PBchiRHCkvoIAlfa zM6=(@bxZCELsStk2OX|Q&Qjm37O!p&o%@{-HcuZYvIZ6|T8_@Od+;&75k*7svOCT` ztGH!d1nLZ9h>*{BF&8DMUw!-aedfm=ZTV}MtU-bvQ6d*HLQ~b+$MjPC&app=ws{+k z?)`$ZFLe{CjwyC|as4aX;x%zB!fQvkdTPc$j!)SHYaWjby>7-%Xq$ zjp|E>iib0htCu{Wyk#UMmTeSGKcG}-`&F$AdLb>CUAlE2%vfOg1qyiBKnF#^l`)v$ z)~u>w_UvZrd#G)<3xhu?*GrEEuhDt@SiHd{xBo=T(vQ&;ZCRhcSKJ`G`&Lx zI_#z5F;|GzzOrSPb>e@EJ8Q)=sUiaItH{ll%eD5LS|k>cNKRNAYrQkfxG3yn_@n;{p+s4JcECNFAac)f^*7+WuKfhe#~m4ArHl-`$mo7aG`H`*r<- zP$`59KdJ1XA0cKz@HEkd(rkcnffa?Ym`h>Wh~L(GV+l*B6}X#%;L6)-7UE}5pv=XK z;LN9bq({`S&m(m_Zmmq5bDo1h1Y*EzEm#t|uJGqo87mVg5!x)|=lXdNO-7%j(98)> z7^t9Q+(w!svMLUptl87zQx*kh^RcP9O(bK zA+F8n8Y@6gzifFPAbpSAvUY0UA&dK;^C%zTw+5{UAkCkYLgJqF;6$I1Qa-{h zmO+gjefz|72l~SFeW~=hple|3NN+``GU-am=75b80rUD56`4>D%=^-J@4h;DR>IT; z^TYc~{vKyOUh;p%C?oNpkXkR{Hdd=E?WqGtE9wl<` zaa)|=R$Sv^b6N6TL&KC=q1gy34B5&B&}#FDvfSKFvGfZ(!4yhH5}H{wD2%$n&l-HA zf{dvWcN-H!+uqevXsZ!dK<O|Td#3AbD9*R2S+ zqWt;vb&JthtJ&72qMRSS3@ot+c7_9%vK}Olu_@sTXK}eEOgo8JWnyoM3mLI6FnGFc zPvAuJ8r}C*a^76LRqnL$LB#R5LzcC96#PnAutzmUB%VQb2)=y-E*GXC3C4g^dO6RE zwe`9cqEEg6)8(r^R52 zy>lMxn?DcUP1Y8!iA6!v;%Bmzua5Vv@MmT29r%mm_P7nb8cRM72@Cd8O{aKZSu!NZ zvaqbze#$_e6Jl8V>fB~4P;tqO`*RZgpInz0SIp8Ze$yH+$`*Z^knpq9#(W19(2R`5 z^an2dTB3Iz1?d8L7?I5vcf<8jrDw|Y^U1mn;LfqvHNOt6n+0irzU-Agy(=1ik(Pw( zV}x$*|FOxo*pZfYG^MANejjhMFP=f0ZWB8XrmD2~#EE9_ymL6gW>4UKR7i;zjv8~; z>}gh9c*GY88-DfGiR{u*I!Xg?RLx7W)O$&?pWpa$b8~y!y}PRZ#Jv#+v>uNNO)obX zkee`szI`hV6xs`C=xz*~_d}E}V^O{VtT7yha_d>e^L(a}aiPAb6NlR0TpUfx10_tJ zE)bKop2B!$``1!ENOG7ez4LB`d2;?#!0f!mqaff+r~$7&hY=GW=P^rcrhug>`WVt+ ze%VlUW*D8_9clkk1$+MZ9=5~C#$lyHfaN!*jVlaR(RcvO!;GYWf? zTBUZMZn*B=J>1{H^IiuX;M+`c!Og2X01(|giGSAelYEm16S9Ma&3AJd(61ccyvG;= zvM8xKisvjCb04W#VnzMfi<)sCl3@5?agnvEQVSHmgw5I~M4v|D73%|Y!-2=}P&t<{ z%a)zoA4ghl-k1?glq2p`PFDN`Z?X4KXm_{ua6kBqtv{ZDJFQk>-0_X@AX#O{R1k>q z7SpMT!oEn>8jtlxLmzt|`}nKeu0@!#z(IU?0luoqJFlQVBvTLG+>w+=pVmqU6AyLkWI%tzPL;JG3S4b4W%9F^Cdry58GbMf{LlCPxO(8DAhOF~2I?|!XWza(`zj!S?84^z_w=v){fWi&dz5c0 zD4boYudi2r_>gk?M|eQ>_wRSGPT?>(4E#J2Sgf=w!y#Y_?IOM_aDrzdw(C^fm>=w^ ziBYq~30sVhT0&#=ntWT*M<@GCMe zaEQojvJXdb?4>U7nZC!sI@Ld9*qcGc;8pCf_Va@qO6+J~O3L?iA^`;lE{&_J!4d;; zi}TjTRAsu?%8#@xkvGBwm364OZAb3|EK zeC?J7Wn%cuKD5N}cU8T>;VBsVAVcunAx)>^MmUF7G@jwW_}Z+S=dq=&puENR2DULH z7}>_fS=X!{xi?G5k#m=*Ok3b+{VKpV=@MYQ{OTOr;=Lq+uAd?S{`dCZURA#z>LzTz z{4q%`om@~BF-Kol$lW=C)j(%Wq64!rS>0pd| zUe9YWDiRGuzw)@za_6otB{BA8?+5ezV_%<3h1SryuD|}mjx@v47q1C_e5rS_z*vt) z$cFf19{KCcUFvnT$&+n=s`Z~oza4GzP=A@cEaF!GC)x$Z=k7%FHeLo7{fJt2Z+(u5 z(5#iw@A-*Z47<6`Pl!4D&N-Z=C0xz$zX*Hls3_N{50n@hfuY->OF_~B1c@Oe1cnYl zX;5&Kl#uQgQ0W#>5TpcYkPxIxxV+sqAVbcP1f7#N_c{qJ4j7br#-b|QwH?+0nKJlcZ}d77n4MJ+&oYs18}< z%6&c_y`uM_5;*e_x6Gscov!{ab*}K8?hX-^+Am<=Dd0*CgovF(z zPm$l-u7q!n6Q@5*DsD{^Yx022)GCgFRT$_Y?;j~b@Dc?!8a%tsp#o> z8Hr*gJXiX&R4j^pUi@r@vMGi(1#fJuqkl zbtHK&?AdZ}nksPUgP!Ef6-Oqh7DVK5rM-ZeHolgs2fJT_Cx`F*2H=py!{L$?h=)EP zLr7Pb9p#MQH+{G{$)|*5uuE~F!+g$?iNdgM88;4Sz5rcBCR*{jyY`(;F-qMyUP8m4 zi0g+Q_;+W%Rd~2blp>m0%7g4RA@wcW=hQY&5zqi|p=L#RBtR?4Vh+5@66!K2* zc;f}{#2hB@^N)nAx_({J9nod@T#EN(+&4R^m)UaLoDfx#%*c>*i*Ao*;!E8VZI%pR zSFE}V7lW z1|(bV@E%!nDNe5FAMq`>{qePLF6)W_#WF5+zl+g)yE$g&L*@O{f8_w=9p#4O+qZAv zqY$+MMirsJE@h}?-b&b!4D9seOilFBHHiDKFFuCci^QPl<;48?Z&73mESF6GU^J#o zgF8rvhokx|v|&5Rzd5@e1?ZV2vI#Hj5@X zLW4SSg6odWS5hl(H+=F4{Yu`UU&(>UJh3^or6AJbDao`lX%x4vVUO_zJnQmy>L;yo zYX7~HDu>DMgM{jz6YZA5AI}ce)9@hJDuj!Xf$$HRi>JiRB-U<)_w<(vOlLdn! zmQN>aZK=F>BOjH!qG<1ch*@y*{?0}31v(GG@0-v3H(|>%I&%6qt&*wciG>&k@c97b-2+&H_&a1y%=2Z z4F9xamT5g=QJT<+v=KXb2KjAMGap|A;~t{BL@c&4O-x%W+;E3}W`&5aiYW zoO(fqak1DD6Vb{=BH;02pR6|=)t*HgV&R@>e~^L-3prIUXbaFWh_-97A1+|A-&|oU z5HM>{1IB}0Fuh!|6r6UxpSaJihmR4@g|UU}Ve0$BKO;b-WGDFn|5bJe>oW8oSV2m< zQK+5&eQ65dBE+UgZepuMP`8vxi!VyC-rZyeMvh^J!KeF?-9GfmBg+sPQqowOl#ZoU zz=8Pqjds0*YkVtSR};4#2}5Rms(ty4Dhfgen~xY8l(f?2GUSFLtey13(HyLpZf2Pm z%N{DaK{0<`ZPUj*k5BLt3+j99jT;wG&pWzW_iB$23%l_vC`~kRFDmUvx?(3rSWCWJ zgW`aT`Kh=zI#9UNEY>F432D(1+?Whkj&j>6#Qa0~u(3nl9yuOuzREf#AxP zhx&aaNd5BS^~l!%7h;ZLToOjxF?mbw&ZkAj%6{n7=OE}1n;8Kjfl|u4MKl}6(x)qI zMqkW>E;5)1Y>MLHjX8Q1)L)98%d29Ov}kLHENo%^7U^B>wTDZlRHsR}u>MNcO}hO! zl)l%N#`BqXp+K?JMh6&KjTDh(ngij}J4#!vph_HxfjRri3-1FGe2>%@dd>Cy1zI^9 zSCO`V0hSG|!R87ipx_Lp|Czi}-Y=e#1CkQBe%!(E8F9Zz#U}r9DS@rj*uzKLMVRJi zK#7JnlD==mr|r+)hko}J|Df>q>7msaNkezkKS-WCyQuF!hI|K$9!+0FXpUKX*qnd0>i~OV(YCc%i8KJN)m7W#iBd?wp8%vgN_bzj&K#OfYEh<7n$?q;7 zNoBN4GOG0ZJRZ$1%;7OM|K32fg~T2A2LD?j`JZx{%Bf68wLhr+*oi9-Xk1nd0sQEv z(mzc0q%?wv)WnfR2&WhRnUKG2E^)TVP*|%xnm|cEZhzXT+N<5f;HzU==mJU!^&(j3 zO=$Itx_eIfk6w>5qN~s-(Y8}h^JILnZ>B-b%e>CH0A-$7(a(hVT*Dk~+YaCbm^cK3 zMna3ph3*r&9I3hl0yYC#sB>Hvyyc3$SiHFKe#d|4LcdxoaQkIUK**gL`vcG zj{eesle>gsw4XeZv{=v5XGxXimf!Iy%3qUe~)fvZIzM+YgVUWyc!V0~BFsRv3e z7+l~W+d&O>tDx@t-o#Ipl09sFf?wsQ7IOR7^8nuwiX&yZ3vgpV1pZDcXXw2v@UNQ? zPgkwBYA#H%;{gZ>X%iE>z?QdWiE(%M@`|P3tB^B#htvU@>_b5%!FGIJ87V9E{UgQb zYDu<|7ux}nyi+)S`@xHWoIs<&4qiMVxOkX;Ph9@d^SV}mx&;0dXvG!FtHBu*5)Oc1 z^skH~BaenqfBu6|Is`Td(FkSTf;9LFX95m&_QxJxf#NSySe z5E3S#!jZ@xST;r;MAIg!Z34gP0ily@2LGU11cQhzj9mpz`v-}OS zEaavF`(8^BksZL#%&P=vTIVw@m_l4{Xqhpi7_1;=vQ74Y&-8aT1V!S<7O|$Lr5Van z0>&jNj}T1#>~pqx_XX}K1eyut1nBo-3m-*JBF3*TtZ{eip0wh6fL!TDOaL(=mz&j} zNg)W;p4R%OCnZSuYjBW-w+@j}+UE#oiv|h8&ebm#9oF1gAL3eIYf!4s0#?D#UIyUaC>wk3>TKPAv_xx2 zs)T0=k#Qxn;RqoUkLmmBIL-U>J7dc%v_9ICyThoz(=o&;e(~tG(8Kx9s(jPIuXo+f z`Kys@%IN;^%+kvI|C@#I20H0!wG=UfuV2a4K^78$adw=noi zSO6F=LeV#aG5Po=psy6~l2LE|2G2>-z59HeZm|p2^d0DxM<3NRFmc(`vq_R5BUrP? z8L+Zf3iH#+q2{;Hpg{sl53G@X0B4h+@w|cHk$jtml%-s@B@64U^ToHz6(5Dr(aF$L z3z**1))s1^JHj+jeepbVq(ir|Lm512>y02HD&7REJtJH(#4l?FFPj6@Lypw65zA4f zp7q_P`koK!DfkAq7KM!zgIjO0@NF&s%K~Wjs!7z-=Iy_l{x4IqZL^bi^t(}I|*0ma{t7*yf z-zoBiqJpmh8PR`$jI8%pphXTzjOGQ47PVjGRvBx&RwF@(!Lx-P_S69`5;*;Q$AmjT?21#J4uOP@QXxHmi)TQ`f;4bzkv6ACVPXJ2q;gq zO5m7${tAxxR<>d>U!ZWm;%)2HA!r_r&Rpn!i^o@V$L*E z2J@TOiO`dcsNv_M$j59h2LN{wSoMiCn^ITJ)edOIN;7-kHfxx=jBB_(OguLB9QV}? zn@B)MZEOiwH+H2_j{T5mA{2&G-fQ8BSaj+7bl(1j1pFfZO1kiWY8TGG^bgc7_n3v) zLK=68#o5?z+mk$ zM0!fZRGHg9`qM^>=Hnh2oz6ELswVCeQ%W*o77o@eEhZ*W6~RC0QTIyyGxerHn7{%@ z8><%=h}f7dXC_b8ZorA>Tj)HM%v8Y-ZIjqqv-(=HxqL~7X~u#E!zqEnLL8ZaM66+z zr!_DK*|O#nH4f1`qm1Wpi4+@!(I=_p5vca%0?#X8WZJwZSH>nWuSJBBXIF`YKF$@r z1jv2UvIe{~iDvPeB=6m4v{DlZu#=*T?kLlw^Xc=?Kla(0691BqxBK}4I1NlpSg{Wo z3rC=AHwmZou)aatoArh;zqAA#V4TrAW<91PbOIavdwWCOFZwTu(O6 zR~V&= zsBb!}ln)_Es&hTS;{`hH-R)|t*?)QX+z#x5fnOh`WzDe)q?_PVPrb6+`4j$>Yww9> zR9ZiUv+-*~cXdi1%Z?}+^g*tV0?xLRI>uxVr!-LN<4$K(u>N8rp!^qx;4dn30}T&n zI6=)AQ&2G{`xrg&(U2v)VkkOp(CC{(>ihva`$T6ns{Ij0_M{vuZy&|QP5jo zUv~!;*Dg)^^4Q`XYic71E)biR`^K<=_d!!*wGI167irQw2j64MtM-pr*-)3YeHur- z%8}X4mZe+#f-yJmJO0q;0gNQnpwOuDBgbZUnBouMsieKiSg##W+Eh88xx75vZV;9m z6b?-WlBVIPv{)pxl>E9asDrj}1o8%1c0(V=LvZZE;sl!o6H7n%mcz46T3cE+lDcI& zS)KZT;J3L6(>(z3tdubBUj96>(O+#5G(*4|4}Okx|}SRi*Jp&y7onkuu3fFUY{o*3p4l2W$NEe4bmr;B{*v@RHE#2Tf;W2VF?U+C{l1;y zAZ6ZU?v{7mNA+u$K$qPx=6}TyM(DL<{+~h&_}olED52ssvwG|TK2JhfZJnZeDh?5K zsC=d=>IH6}q5DUVH>hB@buhgzZgx@JuV3iplx6`gGPS$&fhBl~k8-Qx^I*dm9U6xJ50`1F2hvy2rP zYUj@2vPA4(1N(lGL2|YUN;=w1Jac*yw@}aaD%#MQikDd)v96M2iJ8?sUWo8LN(L6(!KM<5~UkZ7eUa zDE?C>Mk?dmim7(r%<`C3N|KRf&K;w9+t=d4dc#w23)L`8wAF&r2qb@&Ja+_n2#qFz z3Q6u~bfZ1bsLUVV{#SO%ycQh)VE@-~@NMq>F(6fWO6c;!=&R#x5@;_3Pu{)8|NTJ3 zO!;_xV7ah{@h?C5L6GN>RZjY?CIfd0EMNY^^mRiLLP^r32InL0%T|cM*P^;)1-lvX z=KuB?N=f>>!uvE4maqo2oJyr`gOP_l{68^&;`}6_?qh`+Kd+nt2Wk8TYE^#2w%KVl)Tc~54$5M3PhJ7qk!05**S2Q92qUrPMbV0we2z z-LV@SG5YzDajr~z8fJFkLGPw5o>+4YQ?9jMpfJAr?KI)F3;TiY6>JCJVT747&epA$ z3?9`KG9n`O@Wo4v@##CR9>RlGmt$+Z(9Qfkf8SeJtp=nhxG-jbP`;C2+At&}gyo zvR+4M0+u1x@(s(2X8P#aN3rywzvU{%=37G)AS{g9ktQoPR0sULak4d+rXufR!BW>A#^MnqwptyqC#Xo9GdAWn);fN%`&RuiRh!)@5hcUQek>S^m`adD?8IT zgEUA0J_^>rO(vn}KQU;MXnT}Mxv{uPx$9Zf;Ghqo*aX`u=N$859Yf;MhuZ4Ce}r{G+k z<93IH7wt2oyb9$au&Ic_#v|#1m#!%I91q`2Mm?63;2&;yxlg=I-~0atgQ-25b4V;Y z`icmGoE+Ri8cWP5(x6;R>%`AuVWU~3+8 zSzxAVs(qIeoUO>L(ex*dm%zHV0ev&`te>#;&pUtd*Xa>xTcKxHdJI6msEgLpA?$zq)=+6ux3Ptf#y-Mbm+qlx<8dx%Y-;( zAam~!aJ>3t68zX(YqZTi;#58dzNQ1$#!gkQ{gy<;%=Z&|??q4Mr_pK^NJEeuGv=%< z?UR+{T^|K9AL7?ji7h)BTk}oiBhN4Q<&rILyWHJy(`!W&rGXv*{d2fEa%e2?2IOc3 z5mMnFQU1C=Un`?fI@8k39PKC0^Nr4F71#qkWDm~4dPf2!6&vWs_#Pp zsw9?M_hbY4(YlPlwbhdzY847cSPEwo*z~5Z4!=gD#{Wer;wf1x135~Z%T$SR*aPQa zG9G`K5z$n`W?<$6k}7eBvx&*gp_VICHRY0=JLQN4;69lGp%OwJb=m%njF~yzj#k!^Em6>Zge}|LI|p;*^BGk1tbbF(vZoobE$_%O@{h7JTQcK(mwBTUi- z1_wfvxt2b{%1A3+V_h|UniMNWLBKOP%aGAuHgd(~fDryHrz(aGGdfup4ztJ8K-^kH z5tcDtxha7Z44%~MQ9`tyg#8vJ9gB)4;V{iy`JLh{%;C479nZ~wK;4$d9^SZ}_U%pY ztqFTu+BnxOAb_$US{8)*)ROK#PeWkxOxvqVz!Dk=MfC2&KFfnh;rFw@e^g+y>O^ezZZn;>n*2e)qFi z`h?*q=D6Pk#x?4SO*HtaXDF(+ORHNHG8xj|-?#Z$F{C&`UE8;Bvyw(B|A3NeZcXGx z-dmt}MAi?>IIwiS%)~#N&5XYh7sb0a3y7u z7;dmZ1fGJMTR8^bmsf{em!)wjuM@lf`D$aY8_K{kcGh*4g?m1dGd7v%AgS?qfoN&C ztYGiFlP!iU`+~7etwr2zYbu*LUb@9_T|its31CyzcrFcUyE+pnXX+!_sEjYK7a39YeGbBj!4% ztt4HidV0j=INGcIEjqlpWQl}4?+=&z<-l!RO^s+l#K(NT6#?Q-7#-6GQ`sB;TM*fTt&-`u_hs%;oD6VDSS_F5cI9kl3EbVX9hC z`=cF#<*vho$fWYE;aq>44ItI6fGs#(2pNM9TDSH8^MLdP!rx~iQif8}>Y@^i&D+f&P2RYhw2 zBDR?XFisp3e1qY`l$F6ri;k-tmzB33yUINZ`Hri*8gztSM^8-BK@xb$=BFk?N*Ynf zc>lFYP0h;;x4iy0YFpgc?cuM3ntu4*N47k1QOW@!hJKI|s6UNXej09DKJsZ~=zwP3 zM_{ROTdZVawWl|BhJoFA)tUU8v%wQyIujYPLGTp|LoClsHt8D7=1wR&@!`Ns-p)Xq zMBVgLev$76+}{0vYxy_v)v*QxdVEC`6PRAh++rL2#Q|jB_(*;jM1;UC?K$Ux5l-(r}>1)P9R=ag4jiKb>0&9@iYq#>33H!gh zbDs6gH3tM)k1vzFmDYw${viBwWlL~3ekzO|KcySlt!<#m{C z@TS`*s|=;AX{Zh)o||9Pzl%-@mYR|G-bIqHjQvXUx0Qamo^2O8{p+QFZ^KuULMeE0 z<}k+Kz==xi31$*4HNQ!LrQAc6A0PKy9Jx8nBJy*j8gv~oEd>Ts&yc2IeYR|Bp-NDa z1`_qUZ}`1lr62e^mqYI$$>*~|n6trkQ>iNN_#LX~x;E7_q|;6hH$QFMB0o^%>l!Mp zdwC|VJD@0rMQ{U4A}TUEm+(oF>dEinqvazNkp1i%K`or&qzj_;bsoU*L2pQxOP>Ux zxqqylm(tgwT)P)cgrF0{5f>qh3_04K%>bz`cBD7krfVTG50S#ZVhyRj)`po!BY3xTL@Ycdr#E; z?vuEBX0-5{csX!yar^=UXjXqejYu@bPvl zV|zGRGx!3j)!DL!2v%iUSz>4O!P6+6I}4IYraJ!|>FWlP(CF&qcCY#_I!@K}NX$Qb z2YaAPbAf~blXODERaW=%{PLM30H~IVXm!jQ2YrNud{YJhOl|20v}tX+_A9IaXoo&% zS7hUE;9hV_2tsGC*?3t>h*6Q%2uO9j$epo5?GCmoS_jI?)OlDAE6&7NZg!t2QtO6o zT71Ck+(3DI(Z@DD$x8>{dJJ!ntcj!N$yF?9OvT@HMS2=Mc$Wx%fDFb%Rur`sG=UvV z5|iraNPWi6??A_GET^>M{tne@DbG*we#9)HK!`z4it>K9Uv&pj6S$kaB*ZB5{(PA3 z)flU-m^vE(EoH{KCQa*6bjIKD4G8=K10fUjo_8<67^m~!-w(byufFO0MMpuw)^%S_ zlcY?>4@;*&lO?W=Vmt{%1Y|3pj(q?gyV0u4FSNFBhYlT}#!4U!Zw+R>wnUV z3MQs&8R)^h7xJ>}6Cb1RZ4LOg8a(l>G*~;O6tXnYh3z~)RH@?aJLe6S3MDw#XH~du zn|hq@b|kL(1|$At{ngIc2{wE^*-@;mzlxYWQ8{!2nTCd|OHV=q0XRU?VchCISp{(%o;{72Vhg$Xt@gcZO?G84W{bME9dZm-yM6Qt_UD7KZm zEDHU}fNkgyhnYG<%Ri6-VskpJ9(ebsP}d_eC{z|C4YEP#O*5WTy}-OJoHqvCgb4EG z(CY(c@b3X@XKJYdaa0l8e%L%udu0l?JNZ(qzCIko7@*fU_oTO`SMNrrbRv#lx^?zO zJyQ7M(=}wLq@XY$(0I-|iqA)oYN;4SpJz#)Z3La}T4411bbm8jbx4f2S=jUN+1hAv z%A3z$6Ue+{ZZYwakJqwLc@?}3=Uu;p^;Qu!|BFgOT&6g^p345Sq(4DN7SLFs$i;4U zjewVw2b}pMZ|5z4JH_@J(Qk#XS~?p>2Z~{3X*gS*ne=csnC<-7LFGrv$iS{kDM_eZ zlfVyrZkAHulqL(?MB`J+gel#&sv2w^=Vobh1<7~(q+Q{$J&g_H|lbY;EL=3Z3fzxuY4iHm5PZmYe^BK{ht1JhBO>SR0Rk>ew z{Bw^RGdUW4AAY}w+xdX~+K~lkj-rOOuElVJ?`=P3{hl6K-}kUlD#>MFSjyD^oZb!y zimdgCiICN=cWCAWZ^z%e(XBnqP;6;`F3$G&6~z4Q)`tkQ3yw-*0NN^?SCRHJbu~I; zlY|A$(pFuRc3)**wG=FDFe9b(P^5m3jmhkOm+LWnGoj62bS2ew%6JR9%nShJCQ~({ z;3OP_D@wq8fp8!CSje0(TEbe>qPo1JBtwIOg0ukGSOwmH9zZtVhA8iMgXckgt-0g+ zT_2sf9$#n0%?Kdwh1o*SeNO%^C27{6)7`qC^cIK!^yzy0AcB6bwWH)Y8bLnphW0v{ ziw$J2PpZku6ek(yTdaIn-AhQ&V$&D(&?%IqNCeuz0}!vNt!S%L=S7B%ZN;zQd~Eq* z(~y(P_tGW=xX5^D&|N#kN39_C&!>^ifB1_DoWVj zr-iUEeXjwvI0h$XBP&}h4i@Mdd4}cI0}E{t1U`o!GOL^bcV7XCI(Exd3$$W&g+SNZ z_9*mI`O$-pSvIkO=2AsG@5Qo8-xGckA=dF@$S6gs^b14uV2$9X+#YH^y-0Mdy?eC% z6^O{L08aa^K`BlMXJtGo21%$jp|R$3rNPOn7M?Jyqr%CMhn%?DJsQ@@wlq#|I4(b| zJHA&RwSS0^<^FBYS77j_-97g^QOr^kZ?<26E8%&v^H2#(J{Mdl2=0qzCdv463~(x z=Jti6x<{)m_BSh|oW%xeO*4{N2KKJ9x@R5V_Pzr<%0qyt$ThN;*aCtS3WgX-zn&o; zc&6uM!cx>m@-qT99%jL0UDtoSh-&IZSlh6+423!=37I@qC0m1i8wztie|2`~H2n35 zeAP1~IC${#;;c|Eh!FY6jGIx)LuVKTBK`@kK1aXs3T1l8S84MlN%4O=Bw&RE=*%V5 z@A+0x=%HhvP4l0HxkaI$kZ&E*HC9@~0Qj*mt z7U1k$8DwFMb3C;D0T`W+SL0)`zf;Vh*xLT{z+Qd6gXI^}bb7FE2a@7}!ZlR6%5kj2 zU!R-V&o)S9%?P7i?_<*I!mNib%z8geAODvHP<9Lk#2JejU3~fomfOa@4jl>loH-Zq zzux6)yEVyInFT&!5${vy!t@eK*X`1N(iyPp0g|3fgMscl*;4}*-f8kI!MA~ocyA6 zkZ*%0lkQ7QhZ%}QRvbYJOFV5JDPqJ~$vpnhEtJ{uLr`8e*v3!WC>YDnP@;W>k1IUx zC&akw8PrOrh!-8@RZo-9wKu)g(=9YG``pcm(Naf@SV54`!*!)e0j3kQf!4 z6{rpnx~^a2K%fS&`p-!#K7<;JI}UwaM1K^+-CG9>VD2Pagc13y^;LQhc%0|h*Hczp z(qzdH_Ped1_F=|=*x)alKqoJIR`m!|rFBQaj)|6l@Q^I@|E*n$*6q|uC6q)?OsM)AuDba zY_)R#-0pjw#pKm^CfCJ563IiR2006lTe0S(F_(P$pEdokaB{yjAZZ27AyJIt$O-Rs z4=WyfTZTerJ_827JI0+pqFF;v<=_2rp6UwVoLsiQv3ub!lv{+JY{UHrZ!$m(8!^bk*@}d9CkpO!de3L+} zEPo)lue(n_5sy;bRJfN(<;Z5AGOOsYM-kM`QoTLs>%EXkGlTLAfLT54kd-1dqbmveKfzKFjzd z_5FxQ{BSJDEE2jS{tr6MAcZnC;k)Bt`oK~|VQXV08elc(JU-kT=LCHiD(Y_rfR2Z^kg(WVu&$<4|Dt{vQ zlk@2Q4ZgJp>a;Uo`J+~BrYTzoX&wjpDm|Wl>*W00?Ujh%eX?HV=COQ@o2&0CwJD69~TUU@s_Mq*tT`x z0(>G*ejE{fEDs07Sh0!wpHCzwv`m_9&J6z?ticFeu}-)v%o`UMcU|VkJ~N>`kN(A4 zx9tzW$%Fc{K1_u_Sux}eunmrXT4GvHe7~C%JqUP&9?N1cI+3v@kDVe0eF1V0jjl?d zy^Y^5RfS8so-TTr_j^!RL0_u`Nj$Gty`rj^yL(kK@3q)|13e6@proYw%W6Kqz0xk5SRT<{>w-37x5s~me?x+$pvf-2Y$;l)esinVTI=jfXgFIXbGf-+JG}N$ z_Y?U}RhbT8hhU6XK=j37^;yBBd}5wQ5tx3Y@^gCUh}4OHp7?XJ!CP_!*v}8fXjDL^ z?Us6qnEHgvHvfq2+S!rHL0ujS?q|C-qLWM$uQH~^p4=vckH}S$i_n?MQc&<-DTqy# zmFqsNnSkwF_*;EoV`#zu4gcpMi92?l1kqypH6`-tLkuIzgP^a##Q7@7im=7jTXzF4 z1KE9sh^+$SXX*sJ2f$0?9hJ_W}k*P{XX{=ScNR#mCmGqLPksXi^uSb4`6;{ljEHTJiEP1QIJ zMKehj0A~0Wu>{_WzfSEd?euNe%?*;_{VnphUZi5-P)!Bc!l7LPO10M+3>Bw<5u6Vb zMPmnOguX8o@NK=I(NQ!^y;uFJ{at3FV9g53oH6Y|oyzhX3vTH)_hi{m^~Mj&@YtR6 zO3m$fYIZ4*f!T5wC7kHf6-75I@H=>n< z*gfp^SA4CcSqqVO%YQ_eg8jpvEO7ZscEneE{97d-rgF=}1f9iJdDGOxvz#8R`aF!x zvQ*h6n3=LuguKjClG7&?P+S@sdik!@{km|~v&;(YJVOtHYWxG24k`am@w3fILZTVP zNta3=)pVsxaxOXaKJ`2{bAi&b$ueFbC+-LCTH-7Dcd&`tN z?-+bG5$}Fe3aYupqv|iWt|v_4}G{%Z16>HVl#(SCs&-p2w(->ES;g}t3PpG zS?)CXVFz`5rn2hKltmA$720t`EPWVSPfu9;g2ndO1(NRJAnINFJ9Ai#Z_G?-{~%uD zgv7>K`GwsymJg7i?P@ocAzi_-F8nh&Ewm+^>?52 z34whDw0r-DH+`V!_32=OS~@)opJ*SAl(kRAvveZUeZjW)aJRjDrDX|TD2^T#Sslt( zJ*X=#eSP5}$2*#v%WgNeIBTn1a`;XDBTWHe+9`j&0$=n4VD#5}upZpL_qLc_IS0&C zJLL^OjH%$)AiD2h!Lc_E20A$)oIMi?7qgQvZ!r;(1mOHAsHa$K_@lXZ3()2?l>4~i z{ZCrCJ)AU>lC7j0<4h7r+A4E$hf(rk3S6_9Y+v~Y2fnL1v+uGp)w{%H$0r4fL9+1@ z_8>u^HX2ciTyF4@8-ey=nz2oD?+bq$_|5B!Gpsy17fy5UywHAz>e%LpMvB!n= z(g@k~GG6O)-$+PEsDJbL)BYrPBe3};__zrpPM4pRwdGd1+S3pMo1x6#!DX^&>iz-m z{~(NKyz_OpTxq<$UF!8;Z0r3}Yz-gh{_g(R30Lx__bZMbV^@T|XOfVW?LfAI&ezYC z_BJGRLV-Z%;;6hym_l<;&TWkMV=hCe3Wd#9Hs*GB7~kVPFawALN&ee7@p0Tg2;>=t zx~1$5yIzY0Ls2+>K8KG!Ofb=KOPvB}fR6qZj~=9--;FC zJ^p}PrsT|66)_3-a;8Tb{7ZUI_1~DU+75C^yd=60%MlpV<;4W%316 z=b4*^2BNhex5YCYVa23L^x_Mff1l$F`Z;1w)!s5|Fj>u$aNAPjc3D!@x?PLQpe0Y+ zOl3U+byb8Ptqz5NWSBajNv;NO;^S9Aym=p_BnB$%hJ>12039Rib>vWeyU!8%@T|dQ z{VjJ8llR|(dYE?a|5j87ER}4@h0qH7^ z)4Sgv7ys8bfLHfp=xm%(Wzpg@!I!_4=>^RL$-l=dP00&L$LTSQ&;Hhtlb7)6o+4(@ zA7sm_4^P4O$>3W-BZE;keka;TT zOw@ptNoaUC)hKjg#&Rf8CC~&rO_)Dc-mWA z$~3m|Zw-v1_Y!o(+@x8IX}k+|GdvEM_$6vTOon)^clch2pc?uWZB9Gj577fzRU#J5 z=aJ1I4dA7|srP+MFXkCP8&;#9#3A>=4dUeKa^7(U~=-orl z8w)R=Af8iw)!M836Wep=9O-IA!3n;Jmbp@Nl&CuJ9|NDm;Dkl7 z=?#9<6PKy#7lEC6Np)A3XMwi1e4PMot%9ZN@yY(RQNUwP27Vgfh_2Qh6?nk0LSZ#} zN8-%Au6Ha*TDRx)g1~szo!`Iz>aS%X9f-E(UR`Ya_?O-w?K7Fd`@R9<#tIkqNo<&* z8|Z0IvPNau^fX#92bLbNfy;8k-++@=)p9%{JiH5$oB!d%AQ%8b|UG!t)GlMnyOLHdkL;esMmjc zdl`0rRn&I7%PMDp!$D^t%A?1SZZhwR&}e5;E6)wREG$v1S|zF`tHQjE(Bn^%O(0%w zc+ph@wrHWYoPubki;R(99WoAdnE^12UI4N_ylc!u&5SZpy8s;X9tb+3FNY#iMk-rR83{H=1W}`gGnx1wX{qgE0u^;C?62|BLB|o%IH@qd@eA=+ zU{WD&PzWsTA*Y~dYjXr9Lg1e37xT97(jDPEx4osq#xb;^O|Plrtl#lJA{Rx9n&SIABKJ}O_uCKUWL^8bE93_Y)tsod1tQuOe`Cgvi>!-XXL{ zKriJ{(Y21x1o_IsrwdGm@+e|4IryUI76`B83R<*tPws+?Z^JMoy|~so=(LV+`ka4J z*7(fZ85`2+UB?h{aE;ak=drUGm50+(K>o-iP85O#(vhs;OF;6nLI7Sahkn^Y^Ikin zuafEO-$rwp9@2m4`AZ(fPlt#bm!$aitgr8rM!&p3S3Sl6CchMVnBj}yULwpv6PVVC z>sX>ik0LLV_ex8C9(;Nog{WI>GX)oL$m2mYeElXAcP@1kBN#k2Ewk`-T}CY+=U9H$ z8kdNtcQbU$DgmW$;rZfq@>k_FT@!2dqKmJjA4Iznlg*Cp7(8+-w>NZ!kXm+qQT&we z7A7O2ROtzBugRK`YYmLS$&FrsW&Ce7V*Bmvs`&yD;loqCtXXIWNv^@H6Rro_6?^te z2K>_0P|@mCokZ|c!c?E;PJXxIE4wVcgS7=Kby?RtJ=<=y1Dy`-31VSpvZ8`z?6q=n z6~g-g=lOWg`djq%TXycOkyUKs!iSw!fdP>gaaB%`-xouecTET{I>9^HG-Wxfmrubu zDD@wd_Fz;mR{)DQ*43CIHwM=*^1$@dZ3plljX@>P0j_h>95F_-yZ@A;Sy`vG(ogV% zI9b!?A&H6Z$tcA}FdV=&Ts@jyY+-J`0xoo9M^rFC$$DVZyWU-^l~J%|rbnRhwj_}T zj$?;WGV49Sv>lA~ET(gRAO&WlOxI&1%^_YqM$ z0LrWr01XBD^6A=<(Im-_)E|7_I`0M*GOr2?a?or>tRuc6y^JS&cb8)8m$>m)a#aCQ zeEukRooAQ1*_T0A3Fd?12LN4*L86Ge(xKnPPQUYr$PQj&RHWeFW5=TO1NGWhf;(x@M0JqRhRgeDb1#|-1jL+RUVX#`A7 z9^>F)d+B?FYa9-%faFNBFDCB4#5*6k@k;qc6n%H7DQ;5a-@+!yLFK_;fEb42HKcj6 zo61@H1xd34BxZo3LS^{u&b#^j$kVZ8UmN@JvOE#p>dAP2hCAq$7Qrk|ml!wk%kjty zuOdBW-;0QdXVVQ=ArLSkHZpv4TJZ)&Z?}cp(Eg|OI>Oobpl2d=frDw;ldj%JLo933 zD~&fk7d3;uPGM1e_U)e9Gu}s!Jit1L#M+;Bh@ig_wOQ^v?!?oh*3mDCCRqM}Axh-y zc4CELhjzw$FMo%=bi#XkUs1PO*e7URmBVsq>vkH5XH?i`P%U(t~|)vqt}q`1IR;*x967_f5LGTK8sRQj{!# zA?Nd)dnBK)B*G{S#-jOrXeb*s12(A)1vH^edl?J}C;ok~`UT#Y$?i*qV|ah8xcicy zbeRQW-<&i(ZL1HQg8YYR2pvad>PZ=0U>P2-KMj_A%A>5_nQk!uD=E*|E{X47s|K(wZu*k zV>Lb{z4Y~Ioo&mg^r5->a3*;vg@5(p@Y%<$SqDUOaT{bt4C&}7AA>}bRB(f*}S z)wZ3;(q;}iLi34GY!5UnZ592z32Rpmup)~Kelznh`(vh~@Y-E;z=p~%s5=I^{F;xr zo|&J0Cnt|xc)wKE%Duqi<~QK4EO9YR>!ymG_iGu>W>A)JCg!)ka5(>A`Q%e*;6sg8 zZ}_}FEa!PH=kc`XZv{sq^_9{jEg<&-Ft*HMU+8ce zIK__s3imhagsE8h8+CAx1np03g$lQfD|;HWLZ?kYfQ2~54-!0g1=Em4E?!kH;++t_ zq=WucrYKerUw9p{K82jtTG&y)9;3>9 zc!yt6Iu+Ob16X*)f}yCSR9|3#9}Vmm*u1e`Jz$>r)e&D=+;O7bj!!jXG5g~%oCgiW zFEp4Y4Uw1xWBhb74c&R<4bhLVuf9nbJL5iZj5qRaOmu@?erwECygb39GK^ikj8rRIrsjo(9}uWA#6iDE`W!Xo93v2-HSEI5#1 zw`58YA^>vxakmi#L6<(EB^i-C1tBRC*j};JXp7pceT|5PBhS=^5Iqyq?GzbL@^8-1 zc=I{;U-d`|7y;97YI2xy2QZrmq*Y7)(ewvoXT}xZZaQ(dQQB0?De)cwE5Iz^PWDT! z=3$Yfi$J+Yt06{wm}Q(7cvEuPEi(`&ua0iYVK9DuDgn+nEwvp?pFeQxy21WZmE$*D=#-1m=%mtT_Y zIguQx(`w+clGNs=l1ufPs*8SMeDDt_D3?kVlt)qt|4C|Ys ze!GR-u$z9(d{ZIl5GJ@-`?K;AD#dRDdZWNvZ}Z;(^MC!P3xuSbG|qKi9v9%54bgmm z3|CX6D(?QBRJa(Cq9paJh#gCa?0j6^QcU)}MKszG8VaBmW058FhwcOi{JhJ0V}%1t3x=Ggxx>Un3mTZ?8NS_i^Q?NcnC5UVWtjF z%PXK2dU5e7dCCm)_3249CuRimF}}X8%9F)O9ehWHCv zT@>ySoF+V8lOISqEaO!or0rft@dkpP5H~50!7m7AJeQL4WW{HS4bB}a8!*rU06Xpd z9fAD6;zEOvxL^+3>I;J?vUm!vdMt}jssH8z2#qf%GHAbpT6n9C;3Jb;h)@r}qF+cg z9N79MmIkt^=;xZIvu%4t|0I7@R*}oksJ|))g3_37w+`?TCeh)Dhe^O$T zBPVxArWh^FUgGbqg^38WN7o!nkSpLQAt?{MwCk%@4uQ`EzEG0f1KyYnXFR~j2@Mu| zSo?YCz}5lff|6$`k@a-f~?R_k*D_C{&^$3m< zyek@CV`>qZZTsBt4MEB)P?kS;y!^fUo?&7ASfAc>0~~_t(dQK9atWSP+6kCgU{aT1 zcija4%UUwWP;t?M{A423(IT(mk7?Gu54^t+U(RCUQI>Ey53Og1{qr`~zn{CEUM3L% z)e~=IEYcC;c9@cmLHbfiUNuDL+ZW7tDr-QAYH_i;p05>Hs`_CCU=_eabo`Z(?#pNR7%*#JJ(~KtX|PRM-l-Mi z!W9>$Pw{`M#kHV_&Xn3di0rrI4&V=F6F6$QI0G)wVW@P&X;aZlHiO?-f=ATB7P*Y< z%p`xOgAE(Wf4do%jA)@K$^AS4%rKB3OGQAI*=u#Y+?Epoyi7yPF$f|%sGO3h)&?<$ zC1S3CHP+{~#wSt~E75EB)V>@;^;o8Usn0qdMcSn|>$(*ti)YgPLU9|s*)pR^ zWiC%Xfx+ln=lz0R$3JgBA>o%CIXY`y>}&0xBLvOEyg%gsjxYSkhD?WPCi7XtMhr;Z zKAj8Bv`~ehAYrtZBcdNe%?W`U8#41}Ds-_T{NR-Z;L$*+kokQs?80KjQy?AiX)?qE z+uw_Mdf>FO&h4f|7u`c5Mk zIP%?0E+6np>i_BK>9IZx7={u!EfHF%h7m0$rGbu5ASgIEtN?0sd4&M9)RnG5Y%^fZ zHRK6&iU*U@v>E`F1Vrxoe!?8G4=l9ggXQ_-d}BB4rK>xn9QFTZO2 zRc$9yc`AcCO}UgHW;z_7xqbm6DbR{dd|BxO5kVJNMA`l2?iF|r2!Dt%Ya!0TS&05R5Yw!7)@Oy07F;8z-bLPK7ikaE2PpleN6PWmJ=I?VgTRz#8 z=+wQTXhR!6&Z0)=yqEeI5W&fBzLwkwd{3)HK1m)iDW@&B^ew6lAI-*LX9$oG$yQWo zD<*A>6JzP&4hDc#(RWMDseR6BvnmYZH^YK%KCd_1P-s2?hC_ehl{938F zRZtF>5RY1FTkn#v@sdfyTMKNida$|fb8nz926)vk?!D|NwU;YXFHPkBYk~$W3X2+H z{gQ6L4(n-d^Lg?Hrlj~llA`R9OvQ#+EVTgS!m4Z!=NP)g;;{O;{uGkMO8v&jD9y$m zWZE2VY)wT{-y;|<7{sR*bnq z?DN8=VPi&%E)PEJ0}Y|rt6B|LT}AF(#XP|oKFMsZ2IUoUDG0b}<*g3HUn@&$|W3V6WudMN>|M4|Q>J~k+8eW~NLTw^z}*=p>Q zbyDv&ZGccH|DmII{8z_DwaxlWZr_J>jcX|(O&jN}raf`w2?Ti})$(WzP10?PY7wL3 zEqZp-CHSy7;_)&F zH}d_MkuD`JGLKl0Xp~a>>I74JL{;DLtI7PD25w|l`pIExgZy0Sq=}p?fOd2@Fe&lJ zjrLL}f2j?J-Nt=Y%axn$ARQW_;tH3vVIL;8)Z5=L71N7_U)>nb|M`l6;yubRdkCSU zWb_)`<-wYBUu+G=kFB;0So_Pv)sWCzEkGq;!HD9V=r0lSA~@e6lA|g%nyXXK0@8XEQ?weOEoLx2sW#!(oWx|1vo5^&-lTM zM3A$KNn5Rd>>{|QkM|fIE)F^BUBS%Cijqsj90r_5T+9W)8>(H1WY8x+TzmB$9|+dc z$J*T^!A#G7JG`;X&$MffhtnK$SZBXz;Vl>d#+kE&Pke(8Z9CvWXFU{RdsVQQ8TG;X z7shYg&87Z7-&Cb*pJw)PyyO^F5HCL~_s#67)JS0xF6TYldVx~YTAK-5IL-ZW=0y^Cb zZ$8$D^X#;sV|9Lki=Hr3;na5(KZ!99Flbw9U8_UK?R5T!10Ylau+?`?_PEgu3H|S0 z7Gt9^6z;`RN(RXFaK#+4L3m6Sm%HQWlfJrA} zfy2)Urds?XW=B8mmkuC0k*swAW*L~$W#V`nJhgsW zJ8XPsq{ZLYCiC(ed%LC5BaVr@fbczVklbEv6n3|b<;#T_OaG^MtqDmpD%m~|fI&`~ z7Q=(70Evt=6PLAxvnYJ3%eVAx}Z20YJ)t5{O) zC76qv!klqhXHzc1hlXyT2I!UKY08BJmnq5(bE)HX(Gh~Q#^>fqIm~$@%TKZluX~B8 znx44b!z7*#mv-!^R@oV}{e#(yB#n8a~SFIX5^~d@}ARM5Pz1wA@^{ z=+%%`to(g5CN6aEdW$GQt6tbIzM%;(!{0=b^6|1554?hxk3QT4Gw&0^ zHPpAlMgDKaohuA5@)tUMe3XMxH(_cRrr5Riafn5LltDgdZEo1eT-M9<3p~F=EZfX_u6Wvf4Of~AYK~6=^%qV!Q zpfwy1KOe!X8e4t8{5n-tRXT4}@Nru{Nq)uY7TK%Sj4!>_dufZ4|J0~(;myRk>*Sf( z#G*MKqMmG{IGN3y1kp%F4UPm_uFPnu>rD3FwdqL5k&k&bz}}o1>DbBZY5xqkH--eX zCFIE^v$9*yzOA~xK%m2GjssJhD z+`gK>mSxZvbRIZ#jZl;+7;*h8IuK=cQR3WUj8905L8|an>cz)FuR0%8c#Fw~apQgL zeP`al-;d>N7c_icrNbv;J`8Bwcd5 zy#R7Bajqk#6>U%AYD_sZ&9X$_`giT;BUH=>xe_CJaif&DuIu74DSQ+hza*#a8{Q!5 z-8eJq>waVidaJ@jxpfM%4YJ3Bj7c!O!0<1}=ZPcn0R~eIOh!2u$%Ks1VD*X@zJ90! zZAKX4XGO#iWDLAFJ<;txn)^HhS3W>g_&y%0TE9p1e7KyUL}QU^JIK?ivy>@rXnTTx zC8dk&h-*f@hEt&Ua1hoefn*U~!T0F^W4CloEDveiyqg7Id9V`c@I629{U|lXtCsY$ z4i2cu?Iy7l$o<5AMOE^idhM%V3_1}kxkC|cPD9> zkW?g#sj=&JQc#GEhw&Y>pZ^N1akA!@=FXLLL-%uL0$yCwHEDne+!`SK31Rhjc*Y@# zX^p&mHmX^gfbHxT<+aJ;ELTw%`MB~9IT>30R`2lRlh<0;o(sw~ ze|7A{$fDPU=+@u;nT*cZiy~wX1g9m5wK4c4EOsUptCVlyn4il{M?C;6$l4LfC#cWv zP%!;vx`be%B#`Raq-VD_J>aAh&_z{_Ge}rUigeFaaM1AxVkwS1y?QUYOol+lPK{xHumSitnPBnnaB--}MvzM2&( z=2=yOvmnS5PDI*+a9PhP75teRSu+jCf{?q^U}IF%x<6By(toA_e5gmFpXEax`2yc* zn0|Wq&cA5ebBQ9CoHA}Lt3X^oKuZ8jDronicR038#7;ga?vzeAQhR{xX+~*&*=$eA z4YfeJ{^}sH=Nvr`^b-ZpOa(bPR6w&Fc|^Qo0;wj=KiKF)BO~!HQ#laeTA8`wT03eO z*rf0yk0-!mG|biCsW!t?;w|x?Nf6TVx?NOxg{Q@ugL`|u1>@M?b-vxJWtB69%Z@3h zb*92FGuwcZvdn?{^1@(ji3c}igbg~jIRD>IKt=4~9b9D^sm>j`3i~*3uSNn1^5+n3 z65mn=@1yUNlapV6D)WEUZ;aTf8E-1mHW(3mXJ+;onLgo|JLV z5@Z9|isv$xo)IPMFR3uIyc_HJ+TsSeO>en(tDuVF7f-6^yB;q?saBZAf6$(}`!;a{ zkAkfukg-h=gY+Y$A9~h_MB-csp6ye@tXBfa(LW?U6nDZhK#Y|AL-e4t$j;-%gkstj z8wuUldl0B07Fb6e@=YR;g)-tn85#`)?6qDA{6V;Se+kc1lTkZU;3xndp63{s<_%zW?lBc501P1&K682bxVr} ziNn@Wx%w#O*p^(Mbz)UiKJ3QiUhI!;I5Yf7k!)_MuqLq7tm4NWA{Cigl$gojl^=*C zz1HesqfEhE^S1lww6~mmf7Ppt!l;>XCbYa%n-bg0X#a!+&g}H_W2dFQc~!yD zw&r(xV@nM6w3PTa(q`w|PNj_Wf@v+v*uR$seW(b`@FNNTfZUMvgMkm&D$x|YFEB@> zLca{aYr=8#fsxZY9UU&sg{nIzsbYPOkr7hq?) z#etz$wLB9W7YiLqiyap6grA}8`rIhNGwlmB6#}X|NscAfz4rhNyn34DdvQZsav1ew z?pl*NWc%(-n`SX`2nikKS&`2VnTFMxEX7R0&zc!+?|-N>u^GN+zTP#|W_n;N12rIcVz^ z7>lg^J>Qbm8oRXRO&4(4n~dHVKR>?k<(A!Tf!e>&r52A%6sBZ?g0RabyUJck`iJnF zs)t8&yu2xh9FZvuv?_0s(786UtyeinLldR`;qTOs=!JgsPVbUj+rB(So*Qmk7}l8! z`vG$(0%<2%c=g39=gQ(_H|@Frj4|V@33_z5Qf*=t`ParjYh6E5j3?m+yA1BZ^BE|G zq|X(`08=t5_gijdwZMtj=s3-KS*l|YU#&Q#Q**ZQG7a{+h0zib2=A-cIaV^Pa_|96 ze>z116?|37?M8n0E~gO_4-WMG>Uw9I5Kq3c>p#K#Avr>#C)Fexizr-zlSt=vPp;Q{ z%I3URGvP$&SiJ^lPG2IkyCZ!OnouJBqHmmRVex1JDWt6n;zpb``5vAr((%$I7$_)2 zszrU>vl$Deebe?1pB$QWvZTBW9uUs^(~Q=|qN3k&EcNT;zPA;+(Vi`irGGNy7**cc zsia?^5sbrNOb;9mOjUe`P!}6{AxGS9k)0#Bo#uoNjXoLuwI)>$fL5iF+|s<)SH~eo z*O9`pW5Hsg#If?Y9TOYUz1%&>EHaB#5xuaq-w&!xe%pGJJEW+nOI9oI+2vERK9D{E z%>nKg`s>>$IE$aObJ0jU89zd?Y4WbwUVp6Ht%Z8e@f^_1XodTlYd(zFI zwv$j2IL`K#N36tb2>+mcbo(B z5>=u&>LRW4I&I{O;bP&j_ljU@*b^Sb52K4qx=Vdq2IN4`F)crbfhcu6?4KJo2|7(x zc|Nf(ere{rtKBu=w8VJE&%yn9Jw(6`7gqQ+`J!!Fl<8F;b}p-IG6=-L(VyD;}J|AD&OxwOa1!`C5(+wDr!KTxkxgs1y+Xk zw?xU3gr9(0d0OG~ARF^p=gDY~pB}jGQ(=WdEojBtz*y6TjxRKp`rlW4jWZXFB=+K2 ziOW6Uwa_EGove1Mq&!L$&=p);<W{f&MGHJr*FUuEH9jIhznDn%svbwG6w)w`JkX1s%v)7ZFfl3QYr?b`L-sHm zk4`?%g!{kZ{KA2Me$%7HKRpWY_)*5&H1vZ3!%fo;B9njbpqRaW1UE)!^}dWBYdWKAsfZdCqNSvDdrQ=9&FrkBcxf7(7@%M^C(y25_9QIB9vLnq=ybtcq0 z?qWy?*6HwWV=sh5*|Do;BT6@KKWg63Q^EHTDAvcnO44yM>ePN6>q$}W5zWA@IYb@n z&n7O`;an6PWAIq1xtrf_o0ImVtF_}Tm!1Bs#p$scpRGJS@_yqmC3F4ofMl9?A5B&?Y4_vZc#41f$AC{B zVg2dkOQ|D&6C>2ev{HDRt85DhBV%c_`Et2ONYjzVtgdI!p7&gKI})*@+3ke={e%M_ z+v?P6FuZ`O{lQ4rT1#pB2Q403-k;8WB(6K=ypoZ`o!?kY^1?G686U?#eYiM235#Qp z8~FY^c?4LNsVVbn^D4{j)?QA=XE=h?Gzu`L&Y^W~8u4M^rtj*PkRvZaR3h2l>uLRs zoh@eqh%4fN7uwA$S+XWeqZJ+&ub-ORt;S|CjB54OgYfI`SISL!fB$+a5jt8r&-%e? z>&6=lIJGR1KWom*J5Y-J`95a6bzh%*W@0(SnSp`3v=;JzR(Tw(vbaxt^p2bhZzGGJ z6!y(43tVs7S;DKg1zgfyQ=v`9Bn`9!jG;q?FPgh}ZH?XrOd+cdXjInj`+kc|HwuY} zkRs-%#U4%Zz3*|rVY-Ja7e%NeMJ@=A7&ThQL1Yq+94t=MFQ4Xh`4t!fx^{}MmKih^+!%gOS#BpZ)Te9( z1HH%{Zk)<>dgh`skZLRcMh2UkU780ZOJ=n2T;aSxD?nM8Him4ixbiW4^N2*O;zHd) zQ@-QHhoHI`-_aO8;@;A4)qGua+lYWl*qynyfJ>|i5Qyc|1Y06kPVs3{6-fIZ{mEj_ zYG#sCePi>Q>xELYLh~&J2ZuUcbLDKz4H#kk0K&7x7yc@is+KlQ|EcLb{(t$gLlhv+ zhpr%HLIh5c^6gaYOvx*%^bh%|t+X7R>9gwdMHiib-BUD~cK8hj8-3K1y3JZ}DSZg$~;! z1TQ;i8h>00R`V+_D92R_g9kBM}RZgC(pI?$_gLpxy)3P zL+;-p%ZOt#*!v(Cp>UB}xyUwdnPOKHpwP25b z4Z}N5BZ#uJudNZ_MSZRW0=Zmxx1%opD<~x#MNGa(nXb&@%x+kitcny%&i?Rzq)hG zv2EDW&u;bW0rcD`VVh;wYd*Hhkp#M_Ai>oqroATWTEqjAbBZkB=hk3L0&-$=2ZHk0 zbK<|tm*>kJ1msoi83ZN~sg8{7%YSPJ{yJ5k4t2Cmfr|MDpo5qmQW~FtSo=36PEb#G zSvvczl*M}tlwn8MTX$-VHdwF~1HO=YAi}}Hi6~NXacP#kDAUM2iv-Of@^6y-@q)=! zlisgLC1gZw&!{m3A)r?Fq%ilWzlep~E}7d=wy)Ru?snXEJqpCA2?hiQ!ANTIMrLES zyQVd&s4LQjVR@xL!(3DPvO{m3wUYjvG-FvmQS)~-H`(xMHDCMYJIz!M#~_$M#>B|) z4+&DjhEKaCyT;|8^4~z4f$4amZHbAaKU$&`Ml74sg=rD?f$Igo+wWzWMC-#9-xr~C z{w6NDY?r;vnjk6kgw)cDV&z_rFA~6UzjZ0M<^~xM@l5Qx40zfLQZJPV&1KH%W(ZMt zzt-w9C9-X7(_+z$9rsvMl1dg)W!Xj)@T-9)0O?;$up`C02ZZ zO(PeKK~!2RD|P@~KL7QRCQNNoQucSSDcaMyRoa3Pwv}tRZmC9^YavE0RbC`iSzGr< z6qx6CT^mhxuJ?+)%LBZ=G`X0j7bJxCOvCDGTpkAFX=qGU*b$+&K z_jjqqwZe9yrl7De7uZ5o!|ZNSOl2opIUb8La1UT^ZYK+0U}vTLGW5DsSHcdhUG)S2 zs!Ux5&?xwarA8!!ipUJDi0l6<;xB)vDeH?j+^V(l)w}*EOiH^`ownbE3H-EqtSa-2 zoMvO>WGn%>?2TBN63%p$>ywi21AQG?+}_W5c=UI2;fcoSWG{MQbCP}8yqM40V}<mFg*NQL2zV^wb4n%XkZ6}AVZ#@tL z@4XttoTaQ!1}()1j1H$so^wcO5Roz%C+fCoipgZS;G{nq!mjN7F4r62cqXAr+k5}IpQ8=S2}Q?% z4--i^R`kxReT?;cvOvV`J&D)~Z=77VqY|L@x_wXi`0RdM>p2xd20hn{%$4fXyqB_l z=RyX{2CU5Cb$`>|+!a&f32~8W`W!r-LQWGiRn~r9YD&P{Jd@A< z>SQE(Geh@7yTp7|_Ypab{tgMp{6s*GRamh?KCo_-T!R@IN^w$Py^KDXCa zKiaD!F5EtP3TAB)Bpw}~vE0rSvrjt?dL6u`d7PO~{=9rWfBN0!)=SyF<0kZ`L=V;= zZgYixy?tGT z#DZsmPQE4rcpwQ^?Zm2lsGyrC`b2zPz|l83iTZLCd4F~R@6THQ9oA4^?FBbc{j&h0 zN*xZea0DZWON8)P$%?R{4tuj(;#*_ZqKsw(-Nv97ZH}vr--OK|@{B~IJ*^@xZ-bU4 zX0^Fx{T^3)4Mx*_Net4hk2<=?2|H6%K__OpboG86vL|3>`Vu2{9f_3)=8?e zf3_?tBOwy=g_*)8d+QB_q%%cSu{O*Q8i@Kz~Bu>PSd)&Jyo0VrOR7Lc5Y029S0 z-*v`5T;q`V)kT1Kw4U}7Ny*ErM@AF|p&SMm|tK?euX%!N)2LVWvx)A5z z$qIZ{V}m5Tl*VP=cB|vzC)seHuYSS4qrfnsjaA_3Lk!ApEUrH8jP>q!c2QYk=KG2c ziSby8;MnZ4><^xbhAZ8zy{Hi%Ef^dObLkuNS*M(X%l2qeioILN~|S zU$T6k|G#s~@SQ$xagGa|+QE*O?f6zq!5RlYtW_&qQHNI8N{^M zMaQuJK!eIvJpn1av{LhK(9bgqe#+H_&?CF9ZKSDZ%{#1FBT3K;eprST*gy|-(%Q_l zHvu5QCW(_3FVob=`WHmcOy0*9Y*ac?D`g_$d^ZTqNuXy>uBM z-)Q<_zX^wcuZ)gK?rH&%%VLF=zj`2AT`cHM5o;nf$RSFtk0;G zoy;Oill7X)Ko-9lMaAJWhwxGw1&Z=+S+i^wlTI1;{p8*{C}}qQ zRAdL~Y(4HJR7m3M^n~Dvwzt!0GBq=acJxGE5YM@R6ogO^j_`XvXyM1k=_;R(Aqy~Oq4K68pY|D<+uAm%b4V$R7@uKyGzX#4_W;H(~QTqt75y|RE0`6J-KG2Nzw-O97`Vy81Pxm?>+jLH7JUKhL1~(45gv#ndVuD= z1RIGW#!EK8mgUFaM!$nA4{(%9*-`U+>Mhl<|i)%HC5QQ8r)oMs=fAx#7?QN9^)ka<@gbhps_S(OZxKx-KZ@q=8^vXrUtt9A?rl!>||9|EhNo zbY0GnED|`A!)&3Ze2b0)j8h(E1O9CCcWF+;oq$Oc@a@}6+~R2=O+Mnjs@4xQ8aPK` zvzuf#v(INh!D9YE&DZ16h4=(*z6Ja=$|q5c8;8xiNBoluHn;N* z28c5aO-m!j!Nu)7{M2pA+G$xw34Rc8f_Vo_^kY&|2*K*N*`OBai-72(Y=J zOSjjuDo0BvJ`Yk=U;{d{DCh3hEJc4klQuQbY!!NsT~EaDZMldZo@*8<4rBQMh7WIR zW0dUY{P5BTvA>lumCkGPk@NoM$8uT7kerakxu<~r9ZdNXtr3A2^&$ZrrzB;ncxeHz zd#^4Nd|{%%xv}h5A?Z}!zU5v+RQSyAc0%s`^x!IuvC!;7uaM50&&`CD*5a}s42)@{ zIV_8NUT|F^X+_}fGp7da6nB*2MO-C~h~H7%j<>0OwGtaB zo9XXs2h%k?fhiz2&Z#*7aL>N%mqvD7bsEK3(ONbssD{Rpo>Ov7)JGKn;WZ ziP${tQ14i($#%w4f9iFVe1jT1H{{B@90{Q8(jqzU} zo`Ex{dlEWvv8A|lh`f?1qC+_|&4n^Ne`a#Vx9%P;b;W^4Eu z9%$7IHzkQ;0!mWWZ^3=0XUe==#1{QZUSWM{UiOP)H8LF_YA8X$#~UD{Cmy?cd7o^6 zds^)5G^me<@WszFBK)xv;LJnH9@P!zJL=7HuD{)eX{0GEs{YCajx&leZ5k3lV1~Q&g+AQ;`}AR zf7+YEa6!p8R33`|fg_iE4FPTE8*J#|%lf|$A6PiVbD)#%8qF3}e?{C@9@*sh%E7&K z1CcX{SR{03e9SaZvB4P!{U_n6J$JyH!80Rhe?~l+C=TAfl`lxpO|UYVkhky7v^cPf zM*M5GojDza9$=SqDCO%zNiAz@R?B{kS5NTDk#m^S==^*vxI;}hAo_$CDj-@lZxJtD~8qRaXS zm3$Bc{4+>?je?Yvl4(W#SQ?L$JfFs2fP&$chEyi0CzaP0z( zgzDYVJDX!ePNiTmKs`H$pyP`h3JW||0k}I9VqaQB=7U4iN@0vA z++i4}7cG@$27<_Jiz(<FsXn_HM(xqIp1vWr_uQg>J&zN>xzxenFm z;Zl8Fk%&EC`3};uTde+pi&_;>`Z!Cg;U2gpU+)aYm1xB1Xa~EqNhtUO;A3x>8`@IZUTUp=>-G=FU z#qpQ!GN!4IF5UXmrq$=V*Y$mKl}{$zS#RZ2#&6~#k%~mJ+EIpk-ntGmG@_HKJl`e| zeE&Heg?E>@xYyq)um8MpOpg4uIhRf!lcpE4b91r+$OKp+xB*L{er>4x1w_1b0j)i1 zvq()jXG;}W3%`Bl(x&O8$R|AdC;sgI{GabTfoDM|P-Pj$v=K_ERX~Hse*)tfoEF`S zBMelsQI#q$cEWL_4>B`eJ-a_K8+=XT+fyy2RFUl(Lac3$`_vH#P=@#HO_`xLn?Cer zTdLbiX7NMBgdVPw|G6CHgaw`E66g35or?^Pg+pq3e|T4A2KR~g+qM_#S*Xb0n{v}Q zd)A1S%pFYKypyrcJU8w*II~xX=z9+l0Y8wyzTjAVS<@+C9q+3{*VIhcV!hW}eYG01 zPHm3(gQ5(~9h~d$@dnyM^-ofQ@w;-Sd25D5M9(2D1Q1smUR@|L4PC2Qrx2plc ziaYLG{uwvmvS2!E!nntONYEUM+{*=o5_O$9of+Skw~+7r+{{_ol?AgAg` ztkcR$*ej-BQ?{OnGM|8WK$v9hv0f~(ZsR-4^#OsxnY*?AuzM^>R`Nu~1e1z~9s9Ui&+WJ$sZ>3eQGDwDGT0qpXr8o~oi1sT=*0dXL zamriI@reb?N5-kym1U~ryB2x2({krqyVv>|aN?&8sI%uRH}3}4Rt?<05|QcnMg2KM zw%qY~JkNwSMx!9}A?uEg11c{$1$WzfU{fg5BK0X1UqAGSBraf2)i5GG=UEeJt}`KT zH`RLJKtP;r#VZK}v0a9!5C;6{!XWM;e?F*kboqn} z>$jCz%Rbyb%d`xKx#YitU%lJD6*PP9WZdt)Nj%F$2C+JJ+8Vq8(7glL!1swiQd0vK z=W14ejZB!zTPNwGTVx4WUdFmRAR#YT21#li>TD8&I z{r1x3kyE$BTL2h&z%?txWRfk)7mPe@doH?8%!v&7MI>O_heH%TqxL(BV&yB|2JK=o z3m-=+VA{(Fh1-Wg^bC|e7PoLcqud8b>AehKA77sO<-mw3uOy@I#An7%d#}mM49*5a zTPv8c!=F`!-omHmW|ZZnAHC~w25B`7MfU61O#yW zi;t#D;|ejn!`mH5PR&cb6ni`G-5MoVE=8ZAm!Td}YE`lzQ)2_f#kVP(lZL}T;>%&O zFEQAK1Bkd!P3S=#e_wqm(-~_nQfp4SMA1Gp~`x zf1n+zCrybbZCOlG9__HNK(LJoSxe1FIPqziI=bGO(lm@HEpJ8!ik84R>vHuQ5Ea`6 z)S4&Ye;5NFA(8u4wRsV$a5seUXf%rq(^LWWJ)pC_>>E|VtscqvxC1IH-|DdVm|zj;2w?h)FkdBQ-mY( zDa+ru`?HFY_m>z_m2i6^KOlr)c=LEOFn4pza&nv3q>@z46Hm!trv8)Ly8}yxj9h3z zxC)+3^doI(htIOv#K{C>xT52xp(xpt<3=J8O{HhM8B<5_Gg}D60nWCN+llOEy1|m{ z@mpTq8{O{eHWkiDX1NvG{QIGf!r#XM)oq&#;TA{Fg^8kP?2K;U_?` zc6~f2{Hnc4ecsMHqrL8|qRq!p+9WYm)JV{eC;muV$S(86(=x?u{(XZrSv>10_}e#r z4!>_;-R_svmOCEnk_;OBc+_rn&f7TiWjQZ8~4^ zV3Z&+TTf0V`N%NstQW9^&KEjlv+(gB>}QE(1e%i}VB5CJn>F-V?SMzXn2c3!Xe;jW zCm3*2f&nK7){c?BKkP(|j&ct6P}0|X_=T&qa&@1*$r=kq)R%8z@b#TzE}THwD!p_8 zi40}>EqEYz5W)NYkgH6$p?rEHwo-mKWU6eV@YPEyVzc$?4)~f_Z|ht#$i&KAh@^q8 zVSjbWr~l>x?EAu_6NiAPgN-f}=J@s<_>h!&Ad9I2n!S%B%!cj62=~!X+a#tCSnt50&-PJ%D%nAOn%a0)O_gu2YTzHpk@hmWBAU& z6tVK6hN7|dQ#PeNpO|%e(wDde;_T*6a7-nYxL%Y(vylFrK6g_OGrvxF*KI3N#iu{&~Mw~Kwq0* z#DZruk1cz8w7no&Aa_X&VsaVM_Y`GiMXjwF_(@-!ot@bq%(8%qBnGe*3+~38-~J2R zLNH*CCV5<5Owl)p-UWt=+b3s##y}k$74loh;-X&ZAU1rcWZp5NV&qo+p=P-OSYt`7 zxArCwIpNHI4=w%w`OpG9$xVm07*CgxRo<#PN{CmP&54`|(R^>P_n7pU@77ltNB&t> z<`;AKdYzxpehU`3qkxGn3lC3`w>7|*gu%anO*4CSn@qVn`>L?UQb$wu98W6kEswSA zh`-aU(z&q9H{s9wFFqLY;!L7u`uJdzy3yTW_c~XfgO;x^Pdd(@u!4F65T%GSJLZ!J zbiugsRm&Fe1g?%y3~!1!iGf#huC8$vIjCDuyPy)q|NnM|M0n(l$F+>_&#O+4-(<5` zX{${&txyj7jI0&La z$0vim8F*xsZd(f`9K-C?UXN*$O1w6Ke<>#Br>&@YAAqyHppulT>M}g$)4NAP-laxN^}c4~m{r zMt2mBY_-P3{rqP_xA}*PoPkHjlSl-#Q`h za9|#Ihe^0=N`$J`eQELKO{E#i+Hbds%Z!X31tuK=0AbbLR5MxML;k2OgAP?j}bs zvY|;`$7X$zb+pA+6lUf}cpYUARx95QU>$r+a95FP3VLBMgn{|-OwA`;QD9^mQxEg=27yli%Qf;0EiS&^W(ommYFVJM-^u$ zp0vAj>T#DvL5LLO>s$APYAjxy=vBk1Zq8mgj@z!La${6>s`KiXlSAsb5tBQm^Xtkr#@*Z~!yMZEz z&0vI;F>+-M@sCb4`6L-vZaa>%Rt3iQR1vjhesBh{2pso=-Gc!Y`*ZPQn(JC6J+nJg z&VQFIy;old)cTTuYdMphw!^yKR>RMnMm$ch&6zh4%Y6S(jgMv1fSwzaMi>uhF` zT@k#24;W8&Rt}tHM$>=iwz9w?lKseYZfvJ&r4F}G&bzqpNG7dGf2jnOp9d?DYGej% z-Cez2>GiXnPPu1?`UP(I<~tf^a?y}1fm&|8+8d5{T~nvbqYp^{EaK@M9y=fmY?w2O zH!EpIH^{9<`MaZKUMQueu70}WGQQDA?1(BP$N1?Y_xS;VkZ{NZFq2sD{2tNgjpnQh zS4_&@zIW$KY{&N|w&qs*y+Q`EgyiFzL9SW^oN=Sh-xa?{r{(%13h`w{S7IElTu;tolFut+-ArMIOzv>jMocWCa!QhX%fIv9 z=-+uSqU{9l(0*(208E}3G0pkBshv9E@YhHA)@Hc5+km{^>;DdG6~lV77FM=bpGN%b zq3@QVM9h*!t9~H1e8#c*;W60!|AN~uSs`Py;t!`qHhRPBaN1$a=`sN##jLNNP6EKl z@t-frbDG1oj#WW zy{A(Vdj!o;qtN>4GHf2WpNr!+V~}g*xErGJ1O8D$h2=yH*^EgDWg6NoI|^LYgvV=% z&|I{2?=2gr`6UFDF{;RpwWG_@=*(a0O;Mt_i9qO z^Cu&~&W{AJ-!)Cfc)>pj`2-ow0xM2}&Vg;_CwxOs37U+jiLre?{-u9xXQr#9av-icQX~2^^)Z-zvp!UpJS!`~b7(Rtxb05Q9^qU^A{!~bH z*oPf8Obbv3;FSKkq$dhWypScH@Zw@dnf6NIBc{hBO~wr^xDRZ@K9kO?L))9AQVJC9 zYutCvI1~xG12SvKC|sNYtI=$o^l0APz^@qFfSA{S3f6rk_c-U7awzq22xTbrx zwi_AUY(}xMdmQVJhFboRRjO>P9Ybc#kC3B>DvW6)@RO|58Ay_{j#|eqjtQz&c9^xw z#6cOGY%n2V1Eat0%ckpFvA<_BnYhhGYvIIv4pMe@Wt2WF(e%S(0NpPba05HVg7J|R zNXzl>J=Bf5dqUXYnb!}#aJAnjD5;Y=d_w~b9LeDd7pF_4qt+GA718c(#7<6iq)WezRBuT?N{QCxP% z)L|n_MzVseY`lKZWb%D^FLzl?^hZR)(YQ*xKz*!lrcPx=V>uPhQmLwD+FP;1fj?Hc zLE7@=f)6_eDg3ymR1qKa@>f==-hicPg37tm^Q61c0hITwbm=oXn&Vj?I<}xRXUPH2tsM^vPn0>!*H?~sc zuLp#NSP2p;(G_gq7w@eN1P`uHnCZLgCV+Zsr2^j+*rRjSC`?!$`jQCK=AMq5amuAD zfB+XVRXegcgbS}5;J#hs4D58)0rVvW^k-)Fivi$z{rhj#GIn{3(FMvhP))_TR1B7W zHeIVeD__p32)8kN9$ATu1ZS>w@=g3p{VQBL#3~{=YCO!FMLMh`ZPXqjIbpnqaTu-q zXG`yq7eDh3@6$spf?q!REFM4sVtDuI&y+&clB4JthVO|Bb~YuWy(LS1y^m=Dx{$Nu z4X5L3s?+ge`3S#k>4L*AWAX|m3rw(z(MRRAghocJi+#jp;P7G87JYuA75+OV2-g+} zp%8*=;++{WdVX<^x4itjZv`Tv<$O6!`Hfi9-lki4F(gLJ!tki6Od~G>E2EhUxpvgy3;lih>gc3mWgyz_G`MJ!1A9Z^qJs1RW51`W^ zLesv@iMF0D8eFP@0yV$!3exmt9h{SgB-@zo&wOVDqW?+p_35`_(m?3MgT%80YZ(;( zZ!9U*Htk`)f!$F1@(#=8v8$?+4OL-0HJi35sW4mN;}Hvxi-ORq2Zq*wNE9>SasoI*AM1@ChRM;o``FJI7> znH5$tx{azvlH(S@37h`05nY{1I0Pfs6qW~qW7@YQLbuBxj*!<3Q;a`DjZA! z2cS`*I}k$op@lXrdJKUR8_vZkazZB)MjV3vn))U;9K>By6DxyG;dIe5cW|jX@D5g9 zUcO-by>ju%UFk0egkQTde9%s%KmT1&M>hadD|cri;rr z;W*Ha4<#hqGjka;9@6?|z}0v{k!^=p=DdW~l>?!#FhYk8r+&GK2DCMY5S0$p4C5uLQp{^pc(=i7n3vptZi- zIaf`fD-Y^8#46|cuz77Dy&Mr^hKLxXz31w>XuT(HU@cyDO#%jkYrOy{_CRl?jxEf| z?V54tn%!4R#@2z@$GuKK_L&4iJN)3!nC2FEl#ln!T>CwI&f< z(M-}laBn#UMY#b;_Dq)$vg!&L@)I3xI>!PJ8RmMUm_8#B3%b%fv|Va(TTd0ZGRu1? zbEpquM+VodYB^}&ML1}Q0JT{`=8NW8v45!9HZwn1?o4S#B7@QUU7KQVJO2ypH|`OU zJY`W57Kb0+?|8t`4<)|E#Tj~F`QF`~D3Ae{>(X9QJg8_#2~ zFprIg7=_$s%FIYHFIqh76->50N&!Oq^AUTP@}nVU4oGb>nW48=+hsz|Bcdw#qJW>B zT<68b+Mm{GpPcq4I|lJ}^Tk3kZ3|Lso(bO&@y`=R%ihmlFzFR$kMQ#!8CnWweuY6J zPS7$bSgDpSXTn*!dDZYi(1xgvzN7SAK4Z)?ls_0|D$S9y^9~h?jZIAi*4Eba%w+emo;2$}g+CLB8jh87rKso3>X3Q;nhfBa6nPu;>*G zi9HtOnU^c)?}@p!4~!SYz`>*{-%;(xb%yU>Dt95NhzhzZX!Y(lCAXFvufgUZ0x3%( zFWp>(D3j0RPKfw)!1ll#Y4TGTu(!Z=om6xj-Ee{kO(A21ofXE~688AYtjruM5$X#4 zJO9OLg3#l-`0nlNU-6hVFe!*C*}DNZB;ROA`AO~Z&cvvz+SCL!D4tWWh}Yg3>+SKir(YD*g~#u&(IR3f$&7 zEJo67-olqM@J|C5z4m)_NW=isdZEVl^X<4l*jI+D+ljGnrn8O-wOGrzaQIs3pNef=YKcaUB))YTBy*C;brBFW05Fk*)e z&^Iy@ukH@(nLcGsdb#AhF4OQ|RIh%jIsA=F@b|s~TGs9>3N-DPg?B=_dDxR03O_oG z6qc@QRVSY73GlMqs#Ew8LE%zLt*?zg#l90u_^A`G{ADbSgGy3$PMA5nsO_Ml#mw|z z?Wb6d|C3kz4T*eI-FjA57W1b%e0?VQtr0 zv**F66&oUUdTwVjvFNN${Tg`iWNC#%Ptl711^XI|j~Ui9v}vb1tsGYKQ}4urGkIZ= zm75FIM|FJae=ZoeJRocP%*RX%E|tOim;;8Lc!%Eu|A(CijIYb(NI3Tqsu4#^2Dod? zzTbipve^S|kf_tAI5zHtPVf7YYLat4-dPKu4NyU7s-8ffI0C2mqF|v$Z^SG=!BMBygRSbG9KME;WUYsChB?Tk+JH{}~gf>G!QY2y;6@@ee zTF_Juw@^|3BDH-Ee{NerLY&$UOOXb#ryCg1-hZ*P0_Ja@ZB+j00y0o30cK_gY-8G8 zyVABbn!mr|_h~cSle!0Lk|+_Mt7P;^kewoDCkpltjEL8+Sl%Ej*Av{l^7e9y?V!BH zi?@cO9+JQMfOI@haQtJw9KQ{5k=5g56oMSHH9(RbyRHabdjv@HuJ3-@^jhfFe8bHu z9~AF)2&i6R6Q6|nP4d@o(s`8L3h?0B)P{@P%kFKsi3)A}H{?Y#epou&w7>}A;pb7m z+tP4^huH*7!b01|9|#GSJ+Ns37O=?gD#TCsQ!;Lflb6tF4KO;*kQc>J$X`*JAt;J} z?$}L8-TGTSUE_Xn>)jQC1o^fh;cb1fDfo%giKfo`*K!HSwM)*@1jGAmKHr``n0oe3 zD0j|SBtEq35!J zW?vLdC*E{Levs2cO;)K>TQtLya`6RwM585LnJLwgni6p5mm#ZS1@au=CrhiGrEShY zA&BV*Z~$hV8d#0~W^gvWDo65&{U=bVvj8kkD*jA0JK2^?tc9`iW_X7g`bW z`TC&>lf7}vo2r!<0z||Df-r^U7_+DHV6F}7Snc?d_Xe;jCRWL;z`d*bi^(bJ-T`Zd zbaCRrfQ&q>Hmf;ryf;-y!0(h-_EVOy3`A!Mo>I&!zJbwSc1+}&e(*}wu73KEKzDIB zYy&%b(qf}xM(!39KoNl(YW*x{E%8@803TZTB`h22MfTH?{|bfYtB8xN6X(6PM5Vl3 z{Oz$yrY>pxfy)(G2^E~2CWYK$99+2ZH(<8?_9kF;O3>gG?uuk2W(5Z{t~gK^*H2$B=PwV6^~zOAzL! zu`cz!J{*-$?GMuF;h|mY)sk&u0Ev}`O*pJGR!OO?axu1Rh9EC5ZvoH7*rd!qu3a?X zLcm%=Rk_;+^<@TC+G)mrhnR#sOi1gu-SCHBg;o z-Y!Wwu;+F5Fhp2SaZ^7t`-X(X32{M5!+xtEG$2W|1^BNf_3qH^>YifvpFNr8jNCnED}R<*opHW-XfK7AzEV9wnvg!+G>M?;{VThsP>|~vm(ro9 z51Y)t&cNE0_ijxkL_v8(<+s#ps=}c2DLuy*6td59Mg{pO(7un-h%eVgvzvn@am5pp z#f{W{2T9-zuErzbor*8(%MG-1Q|h`$i!SrayD+v)yE+9&!A1-ov9H ze$M$TEyov@Y`V8B5@?SYz$tb&CrSTH2uk#czaYTcj%ejzZKvH&#;0d~=n^U9?Exj+ zVahg{r@`%&W{!K(|lT;R!?q2jDP-wDR-_o-C*Cd zWc+3xu?4g(@viAh8gldHWd)Uo11*Q#Pf&f}KUcr-l;%FV$5h@KOy$i}@6m{QSINS_ zfFvLwz!N5O87AjMyR4Ft18Z4Q`~Wg+#bAS3#81{7Te8 z(51ZE*cww5#{?}Bt}*h`w+Tq|p%)#?$@|bwe4*wN?yV@!%o=n*!_dT~+?kXP^|p^; zZ|$qXq8r7O_0#F4q%`b7pIE`;dq51wph@^lN{2}4QOPs*{10k{Uz`$5@*X;be zhgPSHps-1jyxvzE&?ygZmPZBEUMM^a)&A|gP%z&ahc|~6R#wHzz$jx5TE@(|+fEi; z7PX^BoI!Fj5q9@CiuM15BVbP|B;S zROWKjZQjKVpkO1f#4yghFUlC1T)6S$BYGEOI+qc!>#h(~L(QCuoz@2TB}LVwQ|BPX z6ap*mro3o-{&msK#$U4T2g$XA%lo=VqLWPwgCE{Go&$~i??Os ze^=iC?NyJBkh-DK=5(mRG!QK&cx^jPP{!C z@vcg2a;~gvXCD`1M&8bK=~3ju7nJ;|>+j8T&cHq8<*IR!cj7E1poyKZrBUxoZb~EQ z=}l8Pbk8676ZWp+I8H05RwCfl7-}#N{MT+Y$1m!FXcd}UL}T?-LXXFyp1YrO>JRkC zy-Am~qhU%T6Z|Db;R3+YW5xe3|5i<1o*c_lk1To4<)E zl1mS8M#PzYVIkWN@gu}&0uE@1!RwJ?ZY3QE_p-%j2!`x30B5qMyO^ebbLJ$;?8t`m z@2ru78Y4Ttm)h&I;^HxIycW-{Yg0;h_-(>w`FNxvkK{}*W`uMF)zWG&GC<}a@qpFm zWBnf_KFaf1kr_ptwsXviS^TQ0wO_|4lro1>6Ka-^Za`q>``Sgx44DEoo22!J9_+Ui zEG%fiy^s(f<&RTd)O2)Pz+n) zV5d%-B=H=rXF?gP$IgoRq(zOo z538O*8Z;QtWH@RpU!v`lqh^Eu=!IxlY($MG&5vP5<=<=Q8O2ltZ#~%)iwUD^(gCOB z47yvZU(D@8cg6^@+gnMuEvJ*M2K>JIw+6Kq`Iz>^+#R<{M8@L7cvg0$1P5VB#eeUs zw{FgR$1KZV~J2+?(aVEr0yreV*{z-kfEGD~WfYt0jv90Og%z#~;Zt%iJ!%hcsHQ216%I zyGMk%A#gy|clBkbiL1amG;P4!t+whkj05N*22dD5H>d`m9J&kW_9>(}Pw>kZF|F(d ztEzP~MxV1=pihGG^R45)7eOzlOeey6%Y7 zyb+>O=lx-s;{^}25mgYmu2z}$m)#3D;I&mq)D0AEuR=kBZ!?ez@Vg%pKn$oS2G7M0 zrisr!!@3b4T_K(*Rpo(BKig)f+KynkUI*dRZTMCmOXA&~{S4KY6Er+SXqBEWNoYFjjBuZ0=2d zK<^1!XM8)0%}GJ*C9~*J=jkXlcWMB5W-Yx29~gF{Afh!zaVBUm@Fp~_+m^3TId5LF z^Vs6m`QwQOz3C%O6Q6*Um3rC;CpWuSeXIkaki$#TLjfV@pe@vPznM&J?0eutn76_& zzf3i!tLz9^^S~81NfMM0XJ!aAy=HB?D(-IfC*NJ$Z~&X_O#V2q-V4YwFRkxAWG663OFj-J4=cB+g3v__Z!&wgG z1srpg(ci$Z>N_QW(i#Y0mh;Vob7pzP#)jelbW4ZFsvp5)JEswqBxNZmtz?@oHSyWN zj28&oAQCPO@LAqDjTto{q?12F#fgo&U&jt=U#z~Yv$4VOmToh|j2mAFNELl(8PZq| zqR5l6K|U6H)~7~rE}r?+MJarF?|rCnTpqd3+(Ur5Rw**&&_+NljIb=++{=Hv5AJ+X zc+W?=@4;@Hjh3CwpVuFLnXtSmOA~?KxwIn4$frNa&OTU4Tam10avh zayH*64*Dp`Eh@Nuha0= z!Af(j>uDJ4*lxK13HrofB-lg@gkAkD7dag<@RL6(xEs#t!?j_Tv3)dO9Asi&zczr2 zU!b-@^5o>&xIh`HfVqdNbT@BL7610Kkb6~NX*bc4lG`o9H;(u?x40ic=xGKf z1Rx5K+HP)U_lL+VM*C62vbb3I)CC2<`U7a~K{}ZTJ&f9|JIK6Nc=m<336NLV%O3^6 zU{!-LFB%>heIX?-G~_gi?yU(hjh<4f1@z~bUDpSy_<7`>!GEwZDpXVNx0baS01{B@=$)9(D zQ`YkhxMhoR(doA9gA5hrDBpoM&#R3jLAw8iDNOU*>o!T)YNq{)(`%=gih3~UU^?H2 z*zbb%`2pFOu@^IV2KyXPZ!O?ISUkV2#TLEtM}2*`C=yIL(j}wP1UL>ekuQGT`tM|W zFzzeh&j5p^!U5i_H?Ezwz?V2O?BPS76DpCXTz1*dGQ@yr%%3ME5($30pI)S(Iv=gk zWt+H@vI>1F3@6ZWn(JnCLIoX!RAk5Tv_O-8FC$LNA14c%GiZ?D+^vY#coE(JVZKE(Zq_t$?4u9%xVDSU0oe_aZZNn<63!Ksp0kZAAW)k^a_ZpJWGwyI(Ir84VK79Oy!(RX8sxC3OfTU+k=rF3opg@^ab)@gu^144-Xeo|Ie z^z+m8&)wNl{Nh=3H7N__8{h&c;+rT zrTF9gNhY+RZig((}^z z-BXI|^S2wKAEc6Y%l*c}k7|Z-?=3c7b1?FnLBGPxi}D0(>*Py`;uq~tceWRYg!&E8 zI{mK*4wvc`eu&qD6iywsWV|c~#XnjiP#@R+{K6cCm3{AixQet>k^M4T`PbV}ILlvb zm{HV)XeA7~iCkUn*7~_#bQcxMc>?t|XVtRn2 zJ8Ir?!>HQWuGp3p>8fRMLh3okLb}ODmBX-`{&TA*2_D0r`k;(1Acl~7|)c8 zZLU`bO&Mw>A*ugx>-%@MF++0y*IxU^NS?7G5lI4D#UZIs#*3&S?V#&%fd{k-5i(%8+(M0 zcu@Ir0SsE=idg{!rBEDGPqH&!$_W4hHDV z?hXCWKHNgJgxK_!BvqNK1+=F^TvR1pmYo76pQC~S?kx@j{gXsEn(t`|w)nY9L+SOz zr_Ix?e%qJQe_8i>&VVQ8=YO4G-_r&EBa` z+u{ckUxKMW1Kh&lG8{r@Dd#~MOTf*-KD6MD;0vCz^l77ng_4bK%#-WpXNYlkuYe(3 zq)B|K)Ict_jA07j_`T}R2YuUQcjlz}`n}=aRy8dZt*a=?JNJ?_@6?Z~KijOiv`9ot zzTv(3-mQx`w*6TS;{U?VSD99Ge-Sjw|JeJ+Q=#Nr@G#hhRodCFObp)Q-x~xe58^s{ ztN3tt(sy7v1}r)REZPJNLKBR_dx}sBqXS~XS=ZK!mBc3IE`>GH#6yE(-KyfJ{h8lh zoRFbAR=o2%O7-lTR%tWRdKn$9qFp(`5lrfyH zuz0{wfvRN3V=IO)NLhf%+GS@9u6O*uPml-^kZ{&l+K80}M_c`kxI^^KqnY_-jzHQT z_<7%?0)|9Z9}ketaC5!hmz=$X4Cl(f#-b z!-ZE!)vh1Xh_YFW{FBo3G>1nr4(z)DVf0wT@t`YJ#PEb;KY`IiGk+YPo5tmwc-Z$GYv`WFy0W3H)xIYc@p76wRB zu%(tc2Jk>&;zz(M)S)7#jolB>t2h8I72{>U;#1Uv$hF8MQr$o8E*6^DSC_zACUpk9 zvez}%9j`6bzcy(k9!4kaTPxcw%a}sR&I&RKL z#o^N#YDac~0u+SPYo4X%7rW+)Y&-`#Yr{K74*1PROn&((a%Zv}AKTv-8g@q#8+HY#Xz_RK>=N0F3?t&TOLLXyNjw7MX)nn3Eu#+sSPlkYk2O&0 z(Y(<_8k4_E7zM0lr*1Sp+3)#aO}yuF3mEc+%(Bakj+HOf>|rfGyp)|+s?W4u{X=Z7 z|Fun=p$t|XCCj8o+ixeFnRf)OVJ>G{_ z|I!KNe_CO4IvoA>1CJ}$>9awpEa8fWp(Co@BBfgQJU{D^$KFSzyD#Y6rZz-}!SGZ? zZoPK!Ztrb`;UInRi@?Oo1C^=^IwcDeMMbG#x%uvw?`pMsyTWO201)#nu>;t(M?OF7 zh(+#A^pckZ;(dSf{T$#fqP^Yft@3K*jPBYpXt#civ8;ADZ-@QPV6$4}%vRw%-t?l3 zsS2|&xB|_uAKbrte1@WmONcnG(lAOt{B=SLz+;F&P+tV4UVY4em^$ zsBjmVIHpS16|1KiOa#qAEV`FHoaNb`6aQb}Bo{2&pX#XL1F;@V>M=2q^v8!)=4aZF z<@NaGhTj)W2!oZ=Jj?cP7F-3dxUv&%^=*T*!RevMj@(|Y?Q#iL)?jL{RZo?>y^~9v zL!`m8^C7J0>JPN=Gn4`i!a^3=P91il_b2n{F~?s zTI-#;@CupO68-|b1L9w3jFzta_iBF@B{X!XCP2010OBk$OK{1|KP~lX6zo4)3oFp; zRA4R75>itUu@3%J)XL~rtvSF@%Z+4P%KN@!T-W#1QNT7ZlIw(R(At>^!d-PW#? z5O$_>S8u@R8jmT!U^+%2S@V=)XALrZ8cXGM4AUFiL)VM0apuh)>aZo|mA7tC&|`t! zxq%{`aME2vc5xvcPEqC;!pN@h9s_fG9e&-euXLtXQ;Wx27X8|BdDyZYBgEx^;WU(g zr^GcZU^6G&|JljtU0nZx6k$F4Ll^9Ntip0gxdIuHnD74xtz36(MeeDvGyw-e902UJ zZHj&&nu4Q~pO>lsliUD|%+%yt`jv8C{5%=rGC?^)Y?`|6s`kMrY4MO`j(tjA{7|6n z^qHz>9MB+Cj1Uh;L}Tc*o&!lji?e{9rLd$ZlcswTBq-9RBnyD7gRdLk+}X1`G=*A} zRaG(FkCs9=bsu62mgcF))RsX2QgWo=1kh7bbBOTrYI)N$F`?}3?M;0*w{g{NQ7=}u znh<}QP;__ay*mD-w=@r&GW}pS9W-66R+>0;8y#2*?<-0)uL!GPYtQteuT7BR=CktK)5i$8BrZSuX1VFZ14k z90TA2+VV@)$Z{y6$W6`P(%)rJuv3`63?k#1*fZ@E;0|(eO@^uz;nu#eyE5`?YK2c8j+Ugz<#C0LqV>E$TenS2SH|a>9L0R;gYbcoE`>043?%ns)6APygSY+g! zh^}bW-(@HoTsTo-WV+M#jr3I=-LFReb$+qpgHts0%XP1sf@xyEeuhSx*FW*V0Ys1Y zmc@!4Y=OK9na&3WZ#VkIVh4(5Dhz~F1y8`C_SsLd3Wh;%69{PfLAvQ1AR};J!a?~w3E+%8 zk;F=_5wGt5Kx8ac`cv;mh(hjGINZ+#bqkC72~d;XK|5uoxXmqH4=~ucIjFi9G3d}H_ADvFzwlA zMls2clEgzWt;B@kkJustUrn_zk1*JJ{MPaQvhvCxHtZC*CUw9{D&_f3!{e|Se3d7- zyP=hB-NW16+O)X=G%(!Xhsj7c)Ieq!ojh>4S&J{W6clc#gU`;eANuA`eG0Rqtlv7I zDTjg#NO+QQU;`@tbKy7k`ia49nWx!0>^Jh--QiX`V)S$oc9>imSD9o=EFUyE`dOK; zsIU3Myv%Ha0%rnS$XQvR zglV%x+g~$!)!CEXsrtOP12;8pS zwZ^8g_eSlp!rl(X5RCxMH^K*kI56YokwK2Umv*3z==j;ZEt$toj#>DhThHh#?sq<9 ziN53OOuyraJBbhTyM;4BNb5b$W;y!dE9=_M&w_h>b+F|oP0GO7#Z@}Lp<=As`|}<< z>zy!$nI8&2qc>lK9?MXK+k&wbVffs&e#EZfDq-eT5a;jqD`NRZQhYv7L#@p9hlg?c zWk0o*Ls(mlV`?fYs)cS_kz;_O!?oQYd)ShZhVse8!9I=^2?zc@&kva($^C6K(kPH0 zZ+R4|@A8(PqVoF3Swf8rsIIgCVd&6vi|!Baf3|i)#YPJLWZUdh`sNG{NT{IS8;TUN z(3HR~nK;cgWgPbTXX7%i2U{Ksgx8fNOzRF-3B|PGr|Z1 z;9M=~WC2k)wm?qN-4&ooBkF9{JhvM0F-3iYe!=?PK*OY$<@t$p?cu`<_m;+*CF-$| z^zoREWargCyERukrknPwB3W`yIZLGow61n>G0k3SnJK|#T$aY=S88E>2Dy`H?_7@# z-K39%UEtX&mm`vB3UQxT>!_Sg6~s;I$!c5hUL34Jjf9G8sON1-i+8?Szk%P4_NF$R zgBf)!yUmko%NqA>AS*n;=r**gNDHppUy$uVQ+_>c-JQoJ-DFyWpYEIpXBql&|FPv0 z7tv|oE{X)R?3Ve(Gf5jnBP}~o@icB~l zsM)B5m*w(9QO;OSUst$I(;^4m9DRuv+PYCQlS#*>G6? zd*k&f%c9;F^m7#kQDBX-4z@iVKimQ>wOgPe!X?1?`M#^rpZ6+c5pwle&?)9`2o4X2 zcGxW@QGE<;$)4^-R&AElcc22+&=|g4N=oRUbGxtagb+vbumW+}pj+CJ2fV()?Lacv zdhv}yCI^scptAYNMqu~TAty2vfKNd0pjZdRMMfB@SE+dIMay?A9P_6%?n_m{R}I7z zuD$tM8roRJ(441DV{Z4eY_W~qL9FYa-pFGW{=$a(L2Gd&y$N4U7D&*g-blTdl_y38 z#;06Y;g1M^2d@n1tF=Beeo}ybBML=*UHLdvs~1p+@j_<)g%bs5dHU{_o#K@d*+T0M z&Pe!J=YnAS8{RrwPQa|V3#{yoVW5O&GxCJ`p~+NP@icdvTR zvH~g(E$|bM1S~nWi%$F+ppbEhQ~0T0@zc=2IXgSE6QEMMq~C323%VhLAVO(t>p!#$ z;@LjYaWOHyJ|}Jce&<1E18Xf3L0jFhP)k`BV`D)ybWCLm7D;)neG8h1BR;4g3+-Kn zROxkLR}k0pE?Ap(!1`i4L5r=Tkm%45tI)$)?$1AfTbM#nP}jXxI38?sCm?c@SHRgT z9K=V6Qe(>$8ef&>OSGjQnAGBJuRrG)elm&68EA*KY<5XrwDWWr^|l)r4Q>%mdPTJB zf<19PxAe;sd2FVIS~9K)Meysa2vtvRFb_Z;Xy{J(AD>TdA7`pMcXMZSSD}Q5*q}_K z4G~A=L?|a)YvokcECC2%yZ4DR{6k>?4qALRSddLvAuXUFtFZ65@SmM)8ov~}Gm*j| z`F#aoJH|9HlK!P!Uu;>#&(O?tao(#VmmnP1<=fBX14dqqDCL(bJjCSwCpK3zR=n)X zd+J}=YTe$Vm(j)A@+yDtm?IW4F8|Inm+@mgS(zoU*&F8!*7DAQiLG)mR>?-`@>by5 zsp9RDFg~^wHSF#$u1{nA^TWDM-(YuuU@u}cmV<+%X7jfMq#gPUDdN+AMa-4mzF-BD z)8bD72DI_K@$KprEPcN<&ax%nsZ!Dp2CzF`Is!M0J{XY(i=&i+q!VS>?01BD=^nTC zmFuNQV;AY7LjgA&zb4L3UI!V{p^Qo7C%LU(1U|A0rqo)Rcb;+3H%P-~6c8k&*@z#I zsYXO&d~^xbKt(5rx7?!%vCK^Jkd;5(90v#HH!ur3o^lp9e1UV&v$Pmc-QmCN%xc3Hsok4A|L-iuzeB{CZ z?B|1ft$?0CCPeO>s?X?K`}Yg7iTe=j3asy?!&UfB$0X&-&3dJF^IpL=q8ed0P);EO z9{jw%-3H!E|C6v~!qPQQ76c5$gvG=p7#~&RN9BqRyYNevs>vCo%xKb(0lQ)k@-Gd~ zvG5~P%pgajKu2N=j2N;C3Q4qti?B`B%@_P+osfCxFEz!v4<}9#m!=0uZx-0xg7HTf zqf7sPUw^G;m_v`ePevK)UdHucJOPjfk7kzM#iSIe@ z{F|Hw1ABjx!>3Fd{y3vYj(SY3%70$b?U%}b7lpT*mKH$9lJUiMx`?NOBCcKPE6J>}X z%?fLJMcOLru|!J$%M(^GNh11``+v^pZ43Qy?uY!f+kJk=_l8u8f^~bip~|Kv`!jJm z*37+E)W3DTsn+_~)KV;lpCHpiR>0+i1kT7U1`-*Kf?-OQM&t8al?0g65sgAHBl6?XGGqr3UTx zN9|C$6Bh--%x($#Uw=CXEzbBNmdl%%O8F$!T1&x+6KCV4p9E`{(50kJf)hiOzf7? zk@!}CzdoZY1Z<=LjZ0+mK)u99TB9d9nXk3Itb(07(h=ZkVqnuMBHEz&#ybL3?>t2U zHcN|%c~PgTN#0_5IFh|a(g0N}ZK`@IIX0#*b}82P)SyFk?_?7|=EMLKOr@Pa8xuNN zhm82K{&>z-PuRZXTK_Jjma z=WWd}Yd2U78ScD%6hNfwY?g`I_H^!HC*a?r+C6y1F&a_#g+q3qkgyKp55~-=)v=ka z#m(eNCWYG<3snD*tK=!>taBRrb2+pqQXcs3lRGh;7+IowzY3y~qeOqdKq0!k>LnmX#CDc`A z#`|fQN$FSBRB|A+!EE2vj}NMsHz{mTLOTk|9B%s>z7MDUiCdB!>mV$ycO;#wu=v;+ zm)-8i{}v)J8?1yN!Z;8+3`ud~`=}`>Uj zS~9*%xfOU{_7lM#+b+W6^Q$q{Fi%T?&8&+S!qZsJ9)V#dvC^BidMfgx!~F;xo2-Yq zUsRJ0vZxsz+$9^L^toD=WqO|OLBq1}a0wWI$C;-LJF?vH)#~(l|9AIV=Z|mn+QVhp zw5-}yMm9lxA9I;{!^JG2XOXC-9i$`W1oM^An;&J6Py75m*0b3>x;9k!P|n!=Z&76J zKF=~~DE}2Mf?OcPYtqJIK3Zm+>Wg*<)%nH<>z-0vW$apJT$aR%-K-zYSfHOg-*7tY zYyPyEWu&I2mW?eRhXNP{0ieFRA0I>B9*IMEKR}GZr5tkDml6loY#_a_)9CqbUNDr_%}E<^jDsbRNfc(+11)pX`Zb*o ztKbxy9;@0>al$@-jFikLi+qP=HeEB}ei~^j+CDWOh2Oe98)fyi7ABkGpfat5VI%QA zqPZx4W<;5TdM3TTq#Ps-_RTFt{^-6Wj-+A6#|Cb6l}X$j7WS?3N_mdw@a(e?g!J0t z;$jFDGmYPw@>?`-<`2kWl3Hoex@@=k?BL?w@G^75$%>X+Ado+=A30%AQPKt8Zlm?~ z>dr&Av%IaUp(k>toW06}O$%sza;saIsfa`IGXiFuh99 z#A$cpRT&`P07oro&5WVp;iR?zJQm$N%kiAu07=3gr=3`%EY9tM18$Ph=*S9uSVW5mBVd!tUlXa>9>Z~-=~ z9>Lj?pd=2}2_X=G9cO<8CKWp%!!4@MyX6(Z?I2=1Ey{?OlDQca6~)dAWD{5|{~xlx zI;zU8-ItP-kWgvqkOqF#dnk_M#_5V&)BZhYsS{ns92yN9sW z`_B11zYK>?YJY3C_EindnAO2w*Gk2osFa)sJrp{0N~=aW=_LtI@=Aj$=mzuRKm#v$ z0+qu3pe?m-gOK|m4$)%dv&VGKa>k{1mDa(MtuPq|GzyDBr(l7ed+uRCGjWvm{hf%* z-WVYjk+68>I8*)cm7+r1`MzmA3&088eKuDkO6$i+d3wS%q&dD%wA} zS``1TH-WN$2Yw=IsV+q&|DI<39v3MtZmRF0nb8r?_bdB<2}f_6yA^ly@reBv1}e?) z3uS(r$a`chjYmdX^lCAqOrY*~aalKxbc}I+`)Sl|-W0sn!;Z@p;;=-{wtsBc!|>z_ z!`FtX;ZR*6T%WK@$K9H$#_j_j!zHtT=Z^93-japfTW|m0y|w$s{z(wXi=O*IQ`fF* z7f=lNLHb&9S-WjQppYh3CJFmakJr0PFUnqqizpMU6S^U=`?M3duKEP)YXIp!y5?`v zapZ~oESMGf^Qe$yU9@>W)4NEg|o6PEhXuV3uDuY-n7nCv8& zNCJsJk;&vzb*M;#g}9O+egu{BnEF3Pn_Ba~UQrb9LxV3RfEk=ay?=h%AIJ!#{{x5G zAP)@aCsjKjRh~jM&YbNH=Qr$b#!G#2h4L1D=qlYjL?2PjsC)TFqn2r+?FDY5q2Qgu zQO*J{S>V|!n=E%KSog7iYI^2(Ey$G`*Fs{WQb5*|=mtZ8`z?d>5C^m8Uw=_PSEvi` zDz2d=%U;s1rE%0_ud)Wb*BSGqq8K}pxy+^VIai&yGLPmNdwW+P$L5PVa zGv8NaS^ZFd4n~qaqH16^tX*EvDAnJRUpaX_?Juj4Xn_r5Ed0f?f?5F;l~R2y83RRmYK)Y-uwA1JifJD_eY_p zO>|G}=5H|%z{r{n>Vx9KMVRZwsH+P-_?=;TsEA&7jt!oFw%0Hd7Y<<6foWWgFQdDA z{u0Q?{Oed$-h*rZK}S`gZWdlh0R24+bt|XP-TJtEN5d=mw)Sbp(yJ>cQ09sV04PjN zH?WC<2+J9^C15As1_GBvecFXd3&3NZfd=q=^=iGTLpFy?s;@jK6D;rMFEKnnLsxKe zGVL36gX@xavTDpJJ(7^NHdXeJqc~i3z+Cvs;61{g9E{ZDklq?l+J_(zxj}($OhCCY z!8Td11c7LV7l0>g{@ZJq2D|8m@;2`$aW5GFH9movZwwDfBaEUsgU=gpJpH{ob3V8$ z-eoHRiF7BV#?<2N7w{1Kia|chsnAWxNeHy3Q=&yVz#~raiLwYfH5+!|?TgZnUh2FR z;>-)Rnh7gti611Hk+(RTqg{wv!cVOsK$Mquu)^mpcks8GeS#a~cFj5X60V|^SVtfy z{(O$|)HM46dSkyVAZ?8u5vEdhWt!cgVcU{3OFiuAi)ILxdtt4&dvw~4TuBy;>K@ zC-Oh(bT9T#*)RwWi<3c*I3;~kK$@5{$Pq^H$)%K z5VljVzau@9p%#p~7yQ(3E5TP1O@zWGcU(GV_Lew;ccr6(^erN!&Jx&KF5wm7_@dFg zrl%n~mbM8$v}ul#|6z$7NZ4PV=|^0x)Q!*OHp`_u^qdk$Il))xpPI=I1>=#4ipz|m za=1p9xORfR5%@-rIv^IzGP0PIfB=mDXmgd_OiyJuVV5KvI;cYRiQA{5_M;>FiRrFs zvqfNma&a%RuqY+#W9<=XJ!YDP{S;cH4ZW|;_^pW24cP{8Um&4Y^L)9OQ5Df4ydO61>`~yf|Se!{<-|m8xCc{=Tq9Y-*6q&h@D9} zt6%N{ol#P?IHTo()NBJMBc{2qVa${oH-3HTm`hc|8#*)wB$fT}2tp=5F_|upZ4Tsp zFX&>?%nG{93HR~u)g77QA$NRDuk_8@uC$O!64$(nx}x;3_TduNPH+!EJ7X|7;os3p zapUOgoY!oQ;Z9sXHf&SVmW>%r_+L+T-)HH?-rb0htA>r?a$x4_a4L)XD^E#AOl#{< z#x|EDj{=`PU5Sc|jg3v$G&&K_TZnV63bgFiTd%Nh?i@nh6BR_XLu_RrE(rR;Y^|k% zx_zxev(7iV5EfK@AmbDF!Qo;1fy^ub>XdFjNK}DOs^||U7S{RG!|(9dEdQ8bNJuDh zCh>niX_pYj{%fRu|EsBdvyf6pmdzFecar9&PO3HOG>IyUu&F>&;bu5E(_I(iGYoNd^Chp)+yInIs zp9_M|n;rKB&efYhKGF_&iUvSX%qlVO)1Mu%vDijpZp}4ul8`_%GjGkqh)W&V(`R(L z9(xo*CuLYoCog5hqn6Zjmj6<`zg4IR9|#+f*}EQ)vyj74+Bn7Z-74)Wx#_yIK4MIO zRKo*=+y$L3ts~XH970&ej0#o2#fD9J$#5}$yx$;`xFCYLFD@QSK#13 zF7#$RnM|U$&+t<=CH@IZiBx`+Q7lGjiv|vNTq4}_!vpc8HevH*&US1wL492q^yS`J z38P$J^p4DE0`}_~8JutW(I6|b7hHx3EAsxN-*w90%tJqg8R*G~2ajn(uWWq84@4^( zi1jSi;@mhMAJb2#zn@R9{ju0m`Zxkn2qo)z&_oQR;b==qO9Sn`J|vIJDY|&3dM>{n zltJhT(TBFF@Yw@9%a1x!bh!);KTyv?at0Ka*xvV7uLhkCXI=o!XJrF=eeYHm5dQX$ zO40U9;IeSLoM7Z}ATj;BP^p3Fr&jcHSg1$b%x?xy(0UN~EpPE*Jx5(bgL<#HnFd~K z1GJb5QZxaNJ?VmQ4+b-Hs!W4kR$rW#^;!!KsO0PcgH!(o0%&vlpwt7(sHZ3mf<(muTd&u_TGlkUH<&@aciwa{{Cf zfqYfnNlfBNxO&-5aD!vqM)_%#fbZD#=TNL0a0Gd;Vm6;8S^yZV-wcCEtA*zBrOb3 z!?2U(QIVOKEzq-eS_Q2*eP%G@&=ZhQMe-k0d#F+m&;}@ zw{!1<^}56Fr?J0lyzU%seL;v9rjL4sh94*n*x=2o^`TLA0~5B*XEuI*VwBf(U4U1k z!etGmOt=&Gw>B~|iaaMDdjb+2;93eQ@D&oe0g@8hfQ^ul<@e)oz=`H2XYdheh8#nB z2@vm*V7(%;Ual`CDd5hOgS3bmg)wT@ii{+F@*bv0MpWceh{yEMdHVC{?^PSMymzq#Tpi4n=(%zk!MQzPFA zPxI8?cf7P{KHuNka=Qs^g2l`zl{p4+s8E>x!xDBr00a_ARad8EXrJ=D7^X4~tz#gb zggXT_}ug?>rw99&drI*ViL^AqkftWK(AM-5YwX0PxOAx)3Dm zy5NGEGKOlU2y%8}4m&8IRc)b`XIOl?NbcNhT(Lp<9$W@qj$0>$H^yOLc+n2HOq2RY#Bv>kdsw^&zEf#>m9P0EC_>3Agjv zt~6HupccV#{{cDui2j{^RR8@uXvQ?TC;!xEh#Y6E?uYq(s0}rB&-uWJ8}}eLEYddh zRcL+pwAp&|4NG{KEcMn<)X>P&MEhsg1-eX4efu*W$hwOTt*r;DT)dFPH3NSX)c5PN zWv?akEiOU|K44~wh&2*P)%n_StBUvX@i^SMfdMFUfF0X!|{NHrJzE&ncdQ4@vJ5( zWl3+wM?1#XBOduhpZRuHFN=<=sq@MlaT{kF{Q0$122k|{Y6`)ZY1&A6>_rbjDYrwxn9xok8vl89YZ z>-}?+w!lpE%dn)L-I%g9!1BvBoiXZcc;`Z;_E(l5`79n9k+W#N`=QftzG8=OSj4cPGirZ;YtV+)+nDLH|jxXX}H@p`u(*R|#1%j(EG&i5%oQ{@c3|pL!DfPZ7 z5vz^B=f$neJue$i^4<~vxipz3?J6r2P43`_qzy(DMaP7Mkhmoi^WkV)K`g7@=I+6E{vy&eZj~D%~fqoT{Ig|Un=eUG7I>-kK zteXS@EyhSz%k@YA3-7f1mC=S8uc)Z#1`MHnCf)bnfJ7~9fhr-8eo%%o{s)v~I@!ND z;q|{ah4??;6!R;8M)>Z}MU(-#A+2=5W!S)09FMvW44 z%3~i{##vrL-fHezU@+2gGXPU^kFW1|kiUD~XI=-5#83GM=%ObQyS0`oNO74g(1}P2 zE{+~!LL%h}YhJ4}==iSA7>U+Ev=%N?x*JkKtf({k4j>GyrBZNPcZHpO>Kiuc#kwUfty(OMS9Y<6NC(2y#e-qT1 zPTpV^mj3>5o_`Trbz&FR^z9o4vCp5}&C^;4Z)`Xibdl|SU}Z8Lb6biGo5|jU4g=I(?AB67i(-LV6h@DA$V#0As{_*$+yn9D^-bV zBJtR+sDAr*hYwC#ueG!4xyg^Hu!#LvNnQ6N)Xm#NZtWOZrE`tzDvDt&b1nnRqF1gA z4eALEMnC}Oi=;v&z_=U!+NTmPr7yj>C+4R(n%Oup38H? z3-Q9_Rf&R#lhz0#!?;!VZ-ZH3+!yqQ8h{yW$#(`(g=!Fs$!|b72MAFTY?lf~{ADv+ zbA}VjRnN!eQhgVY4ivy*#3iIh(<87T+@4O;nmVQ)RHvh#ASz=(o7hveuKvS{-4NKw zg#<`Q`jZtKnZ|#!$b%=R?2sbIG@2LF_V@Q4G(gt5HJ76UUvAFDCiW~?2)(L;w~OOq zvL1_gU+jNHKo5IUR>iW!_5}7Ocw0wajoP~RVE$Uc{|*~TP%|?p zdu5{cSt|GiYWa&mJ#`8QT_{X;B-H>&LBN+HfXqF$YbksdqYyJ!0afZbc7W?rjyx zH~;sI0Fc$YF(&&&eKf1l%0HqoY>z|?E}D1nBs^5y6G3!8O$cQq2r(lUcu9p{f8Fmh z5pNrEnW(#(65%0$5ZKEkpZL9zUpRIK)(^83ZlyWLJH(-oTZY5xzP`0bm>*4(5uJnO5t(*}&UpWGUM&4yWWSP%geBKC)4{@nLN=pth)z3v?BY7apTel7@fU7rJqfefvL60p zXZWck>CRn7gYZzwSsyrh%%TPE?i;A})Kp2;h7rlq*hRPLXLBy68C8eff4sq(!t}cz zu_^i>eQig1p2Bk40l)!95fGHmU$V$*e5KX|44*ro*A@9YTSf7v%wtsNpKX*_l2aDi z2M^#io5RSMpRl8ZmrkO41>=#khnXRnveE9@bSB7XI84o&@g&EaTW)$|7vz{z;2qtR zJ%Y7cI}StaKPf&Tyc<>pWlBgoz3UXWH$9~x||Ig^tVx$TxQ6;V}moR+XTSCCr~{G zo6|_5VlIdxHg8%YUY9WaxGei|EdU?_+7{FhAXb1El9)m=1v;w!WBpUZFoCsia6>=y zM*H?FrSZ+&dRFk*MZ;Uub-WpY6||7oJ&roZAu$76X@4^+u^i}m6%A}FL*4$RW(rAn z9oprXJrQW25h+l_9Qb{G#eGkz>Q$#qnIj5wowz1rc}u~eoc$Bzm18IL3%UT@Oz}Qd zs=b8{s4M3jqoq(hlD-yM8Df@{^5holf=Y`#Crc$IOj1%(1s$E2w9K-3wbH(oSz9uU zq%`F}j2CrX37-I)wm4L#rfpZGH=bbrc-pklVhFPQP|RdAxm{mBrb@mmwoh0(rJ3+F z;n@HA)0d@}W;QkAUPBq((qPEH&VfI4;(|rP!9j$*&e-Rg$l!6ug#YTIB{T&J5cuhh>p$Cp zIx?kRfUyFZsY~Q&<_egOgvsuHQwQOZ*{T*VJ4A3{l|AgcAb3#q?h*d zs_HZxWUa^_VbbL|&b_11o=4&9b*vr?F4;+VTx)}?Xq=t$^g16RP$FB7cryk{$FJi*qHaKJak0rIF%7b!d4vFkiRi(88K#E98fgMz(=qhi z>IwYy1EhvAiaY>3(W3^Cdh6=`k5@E`EGu#Gs4K%*7icq^C8`5gBkN;ppBBvX6Qh(; zuDCv_c#fDXgl|MHQ~xNKyEytaco-m;SKuNLe*k3)k1_$xyweTNWNM*z+}zqKdFO4m zm-{JY?Fxx+=`|%KLw|@!b}pKzEbT+Et3*%#24JPz1gXB*IA4ZJUi1K4v4D5wQUb*% z_HLi)Q z|9_{$|DxTONj%fRZdn*^P-5>68oO{#(P?}wo0&VBWfo^3u)O(}{I+t5n;APMpFZr0RB*ipf$!{0}E}KN>8Px%n{lOJ&%u5ewhg)mwf68RTw2&$ecGu)73YE!6kH zX)=_zoy1&Dh~V7F;ND4=l9}BA_~v^rfcMH6VYae_9|~NYM}PSX3!+t>P2bFhckhlS z?+jCAJ3SCF8w8}(0l?Eob7oX-gvllGdy$_Cm*-}epI+){Ux-xLGXT(R^i|d$Kr6}> zX4v7284!EIUtMtm2w%LWHJ9`uyCR>UD~D;;hjaoZ{u5xiCb+r?kI)NIY(584yyhyAHBZ& zsOdZb5ENhYb;j|)q_-0XF9vPB(GmbglOoI3;cGW_Y)kORLSDzthU*i{`LaZ}^E%I4 z8wFysk(W31kP>6WS@M(_X+li9$*f9RLcK?0{Bd#P24HiZO8|qS@;=B$?gcN74q1d` zJY}wE_-i4vjb=xxh3eZ5#)3C-5XhJoTBcLxKfgBBM-@2Y{|1w69o`BmlM%_hm=*P_sX8 z62jEVcH2ghg~@r~9brqXjU^KgLk;Rq5&%U4Vc(u7%?N|d)Ah9-CS2*k?^251+Whltj7wu$;r$Q?48XnyaZC?+|{kMYQq&&GDeY^qtcF4MO zme^7s$j*A5`a;DgJ!|+t{hiDBhee29^ZMvo=6t#iF7Xhp4}AWI{R;pP3Y^l@6{)7D z#N0f5p$^93HQZSh7th_1L{jBdWvk@6#sO-MmC1}(OJhkHbU)+*Y==**2iPhq=+eE# zsw+>FITy3$Qv7IC+f&n3PbxpfOACP8ZHSVY=}nT9=Wg8cOvFEVY#3G!YVaJX5eT=A z>)7!8%^OQnA^HF=Z_y99H2Dsj**^EBp2E3ve~z+&51hKD?A#A|!ht0N%qCA>W?K*< zmkZ^8jDcj%4Nn?@XMYDtPGP=x)oO!d$VyxY&*&)TpG3tfeCne_9XDAlAmjn^{3CN2 zqes}o!OekkN&+$FhfH3bR+3$>EK}p|98JTY+Knj;L{S&kN;ZY=RJ{wUrCQO1>Y}(s zhWy&4Xx^K*)ZwWkpW`<)>r6g_IjGiGE`>=jWE=o(<%&`<9=yuG4*TxGu3SuVT&z~FGMIH36p$a>Mas;=l3WT?I?NHe+T1Y_$njwH42?)Er3{F8#sQ~ z%HqqdWpw0n!NXgB)}%S_N{Ql{PHt8|w&Rt~jn)>Q7jS(e1s^w4%_?WZjo3P`_MpK~ zNVZ(x`?^=biqjflZ))Z(Ls*bAJZE;s{!9XnLObf!oB* z1@-#hhg$bPKUCN2oh;-n_eGjh|L?19M5zFn9k6FG62!~Dk|4y31oC7iv16gv!Is+& z;>sjE$_uQJc)-Z70>g~bijAK$X~RDhHHh~#d@R{c2LW3O7*mHC649C=DIOCxMGZC8(tAe12+O z{G*sJ7V0xjuHWksSW^Bw%nC1<%SV?f-()C7Jp)4rOg$~HQ2|0ba=OF%mRh|HU*!ua zlYp(`75QSS@1x^GgOxF2H>+hP3u+W!8xNPtkk#k_VWdGTAKW&tBYZsR-ot5qBroxIG~%#W-_1!A;F3I{)#P|B}1;AyfzZF`L5}`pn>eq~JC0%L z4HhDkpTBCRbS}vzJx5am-oD!?6;2ZV%!vX8FD;W52iT0?Di_&vECYR$ z2zx`Z#51Hq`On#w&qtU)TE!3ml2YaOn}cUWX_usfaVu`UL}p@}eXGt~RZ&|LLoPJ2 z`j<^-6g#rEimEKFyso3MKXIm@g0WiYevfm{N82uUeBgTdJ8FhCa7e4I#^qK=iTmAO zo)qxI8zaUba@cQ+)Zi+Nl(e9z)=!sB4@j0-DW~JUga=XZedVY!?~ai~>?oNN?lERf zCLstAYFPbUEW54?AZY;uYj9aKCpK7H%QE#yVGUn69+majXr51Lom6o0%Vw=5S2BM2 zBg6C^pU>a#KbTSz69a+zDQrBYFYuSDIT?+$P~i`YQJ7-c3MZaKgW{Bu!0|ulJE8qJhuEPKz`f~!XXVU#hOim7;H2X?pT~R1o zihTwH zTuuUFVnILVq5ybPy-Pb>gp63@qgCR;4`m7(P` zig{U=VKE-rrU<<8NAWq^-zJfQXZUFCR+=4N%|j+R$?;dULA6pkj5;SSt!_)#U|95Q zCvO3HlaP2FxHWBgS-z{<7`=+=Q3fq#pw#X|;YwEA`_LYeXG)!{c*B-BSCs7Q*Jd@7 zw|P>p^|^G+sAywr?jW+QPgEgH!jq5 zzC2hri&HvZO=vg5l9I#ulP!wQF$-X;soAa_h=C3tdc+PAA7Bi+Vn(KUW7Hx=1l6zc zzc9y9x4WGi%)cz9L@klT=MAnVE+03?v8AOY}{vqz3lqg_c