diff --git a/CITATIONS.md b/CITATIONS.md index 32b76f4..a6169db 100644 --- a/CITATIONS.md +++ b/CITATIONS.md @@ -64,6 +64,50 @@ > Shen W, Le S, Li Y, Hu F. SeqKit: A Cross-Platform and Ultrafast Toolkit for FASTA/Q File Manipulation. PLoS One. 2016 Oct 5;11(10):e0163962. doi: 10.1371/journal.pone.0163962. PMID: 27706213; PMCID: PMC5051824. +## Read preprocessing tools + +Only the tools you select through the preprocessing parameters are run. See `docs/usage.md`. + +- [AdapterRemoval](https://pubmed.ncbi.nlm.nih.gov/26868221/) + +> Schubert M, Lindgreen S, Orlando L. AdapterRemoval v2: rapid adapter trimming, identification, and read merging. BMC Res Notes. 2016 Feb 12;9:88. doi: 10.1186/s13104-016-1900-2. PMID: 26868221; PMCID: PMC4751634. + +- [BBMap/BBDuk/Clumpify](https://sourceforge.net/projects/bbmap/) + +> Bushnell B. (2022) BBMap. sourceforge.net/projects/bbmap/. + +- [Cutadapt](https://doi.org/10.14806/ej.17.1.200) + +> Martin M. Cutadapt removes adapter sequences from high-throughput sequencing reads. EMBnet.journal. 2011;17(1):10-12. doi: 10.14806/ej.17.1.200. + +- [Deacon](https://github.com/bede/deacon) + +> Constantinides B. Deacon: fast alignment-free host depletion. github.com/bede/deacon. + +- [fastp](https://pubmed.ncbi.nlm.nih.gov/30423086/) + +> Chen S, Zhou Y, Chen Y, Gu J. fastp: an ultra-fast all-in-one FASTQ preprocessor. Bioinformatics. 2018 Sep 1;34(17):i884-i890. doi: 10.1093/bioinformatics/bty560. PMID: 30423086; PMCID: PMC6129281. + +- [Hostile](https://pubmed.ncbi.nlm.nih.gov/38191289/) + +> Constantinides B, Hunt M, Crook DW. Hostile: accurate decontamination of microbial host sequences. Bioinformatics. 2023 Dec 1;39(12):btad728. doi: 10.1093/bioinformatics/btad728. PMID: 38191289; PMCID: PMC10746824. + +- [leeHom](https://pubmed.ncbi.nlm.nih.gov/25100869/) + +> Renaud G, Stenzel U, Kelso J. leeHom: adaptor trimming and merging for Illumina sequencing reads. Nucleic Acids Res. 2014 Oct;42(18):e141. doi: 10.1093/nar/gku699. PMID: 25100869; PMCID: PMC4191382. + +- [PRINSEQ++](https://doi.org/10.7287/peerj.preprints.27553v1) + +> Cantu VA, Sadural J, Edwards R. PRINSEQ++, a multi-threaded tool for fast and efficient quality control and preprocessing of sequencing datasets. PeerJ Preprints. 2019;7:e27553v1. doi: 10.7287/peerj.preprints.27553v1. + +- [Trim Galore!](https://www.bioinformatics.babraham.ac.uk/projects/trim_galore/) + +> Krueger F. Trim Galore!: a wrapper around Cutadapt and FastQC. bioinformatics.babraham.ac.uk/projects/trim_galore/. + +- [Trimmomatic](https://pubmed.ncbi.nlm.nih.gov/24695404/) + +> Bolger AM, Lohse M, Usadel B. Trimmomatic: a flexible trimmer for Illumina sequence data. Bioinformatics. 2014 Aug 1;30(15):2114-20. doi: 10.1093/bioinformatics/btu170. PMID: 24695404; PMCID: PMC4103590. + ## Software packaging/containerisation tools - [Anaconda](https://anaconda.com) diff --git a/conf/containers_conda_lock_files_amd64.config b/conf/containers_conda_lock_files_amd64.config index 375bc63..be577de 100644 --- a/conf/containers_conda_lock_files_amd64.config +++ b/conf/containers_conda_lock_files_amd64.config @@ -1,3 +1,4 @@ +process { withName: 'FASTP' { conda = 'modules/nf-core/fastp/.conda-lock/linux_amd64-bd-4df8d6c11b471bde_1.txt' } } process { withName: 'FASTQC' { conda = 'modules/nf-core/fastqc/.conda-lock/linux_amd64-bd-5cb1a2fa2f18c7c2_1.txt' } } process { withName: 'MULTIQC' { conda = 'modules/nf-core/multiqc/.conda-lock/linux_amd64-bd-c17fb751507e9dfc_1.txt' } } process { withName: 'RGI_BWT' { conda = 'modules/nf-core/rgi/bwt/.conda-lock/linux_amd64-bd-e905ecb8305e2609_1.txt' } } diff --git a/conf/containers_conda_lock_files_arm64.config b/conf/containers_conda_lock_files_arm64.config index f10a4e3..951799a 100644 --- a/conf/containers_conda_lock_files_arm64.config +++ b/conf/containers_conda_lock_files_arm64.config @@ -1,3 +1,4 @@ +process { withName: 'FASTP' { conda = 'modules/nf-core/fastp/.conda-lock/linux_arm64-bd-95b49074d3fe9875_1.txt' } } process { withName: 'FASTQC' { conda = 'modules/nf-core/fastqc/.conda-lock/linux_arm64-bd-e455e32f745abe68_1.txt' } } process { withName: 'MULTIQC' { conda = 'modules/nf-core/multiqc/.conda-lock/linux_arm64-bd-5c84a5000a226ab5_1.txt' } } process { withName: 'RGI_BWT' { conda = 'modules/nf-core/rgi/bwt/.conda-lock/linux_arm64-bd-fab5219836c1d880_1.txt' } } diff --git a/conf/containers_docker_amd64.config b/conf/containers_docker_amd64.config index d4a0596..6d202d9 100644 --- a/conf/containers_docker_amd64.config +++ b/conf/containers_docker_amd64.config @@ -1,3 +1,4 @@ +process { withName: 'FASTP' { container = 'community.wave.seqera.io/library/fastp:1.3.6--4df8d6c11b471bde' } } process { withName: 'FASTQC' { container = 'community.wave.seqera.io/library/fastqc:0.12.1--5cb1a2fa2f18c7c2' } } process { withName: 'MULTIQC' { container = 'community.wave.seqera.io/library/multiqc:1.35--c17fb751507e9dfc' } } process { withName: 'RGI_BWT' { container = 'community.wave.seqera.io/library/rgi_kma:e905ecb8305e2609' } } diff --git a/conf/containers_docker_arm64.config b/conf/containers_docker_arm64.config index 75dc75b..fe49bde 100644 --- a/conf/containers_docker_arm64.config +++ b/conf/containers_docker_arm64.config @@ -1,3 +1,4 @@ +process { withName: 'FASTP' { container = 'community.wave.seqera.io/library/fastp:1.3.6--95b49074d3fe9875' } } process { withName: 'FASTQC' { container = 'community.wave.seqera.io/library/fastqc:0.12.1--e455e32f745abe68' } } process { withName: 'MULTIQC' { container = 'community.wave.seqera.io/library/multiqc:1.35--5c84a5000a226ab5' } } process { withName: 'RGI_BWT' { container = 'community.wave.seqera.io/library/rgi_kma:fab5219836c1d880' } } diff --git a/conf/containers_singularity_https_amd64.config b/conf/containers_singularity_https_amd64.config index d2714de..2521fc9 100644 --- a/conf/containers_singularity_https_amd64.config +++ b/conf/containers_singularity_https_amd64.config @@ -1,3 +1,4 @@ +process { withName: 'FASTP' { container = 'https://community-cr-prod.seqera.io/docker/registry/v2/blobs/sha256/d0/d013aad5427d824afe472e6607ea47685ff0181f1fb09e52a179e0ec39e43e88/data' } } process { withName: 'FASTQC' { container = 'https://community-cr-prod.seqera.io/docker/registry/v2/blobs/sha256/f2/f20b021476d1d87658820f971ebecc1e8cdbde0f338eb0d9cea2b0a8fc54a54b/data' } } process { withName: 'MULTIQC' { container = 'https://community-cr-prod.seqera.io/docker/registry/v2/blobs/sha256/c8/c8e346f4f6080eadf1253505e6ff09ef004454fc18e8d672006fd7b222cc412e/data' } } process { withName: 'RGI_BWT' { container = 'https://community-cr-prod.seqera.io/docker/registry/v2/blobs/sha256/3f/3f452c8e124ee58ab6b26442d15401c57d471cb753f53921570dc484df4e7620/data' } } diff --git a/conf/containers_singularity_https_arm64.config b/conf/containers_singularity_https_arm64.config index d80474b..6b383a3 100644 --- a/conf/containers_singularity_https_arm64.config +++ b/conf/containers_singularity_https_arm64.config @@ -1,3 +1,4 @@ +process { withName: 'FASTP' { container = 'https://community-cr-prod.seqera.io/docker/registry/v2/blobs/sha256/42/42ea3a248c30a6eb0ab5d5ca35ce8b50aab6ca75e982d43420779214517a516b/data' } } process { withName: 'FASTQC' { container = 'https://community-cr-prod.seqera.io/docker/registry/v2/blobs/sha256/46/46daf2dad0169afd2ae047c3e50ed3776259f664bf07e5e06b045dc23449e994/data' } } process { withName: 'MULTIQC' { container = 'https://community-cr-prod.seqera.io/docker/registry/v2/blobs/sha256/e4/e48aa28aebc881254a499b24c3e1ce77b8df1b85a5432699ed6f72eb17ac7fb5/data' } } process { withName: 'RGI_BWT' { container = 'https://community-cr-prod.seqera.io/docker/registry/v2/blobs/sha256/c8/c8e83b71b5dae10e1d771490c36f49d314718888c3cc8214350eb7f149090f0e/data' } } diff --git a/conf/containers_singularity_oras_amd64.config b/conf/containers_singularity_oras_amd64.config index 1fb613f..5f4914e 100644 --- a/conf/containers_singularity_oras_amd64.config +++ b/conf/containers_singularity_oras_amd64.config @@ -1,3 +1,4 @@ +process { withName: 'FASTP' { container = 'oras://community.wave.seqera.io/library/fastp:1.3.6--63f52bbe4fa9aee1' } } process { withName: 'FASTQC' { container = 'oras://community.wave.seqera.io/library/fastqc:0.12.1--5c4bd442468d75dd' } } process { withName: 'MULTIQC' { container = 'oras://community.wave.seqera.io/library/multiqc:1.35--c680f2aea25ccec2' } } process { withName: 'RGI_BWT' { container = 'oras://community.wave.seqera.io/library/rgi_kma:06c9375141c15d16' } } diff --git a/conf/containers_singularity_oras_arm64.config b/conf/containers_singularity_oras_arm64.config index 5a52534..88a109b 100644 --- a/conf/containers_singularity_oras_arm64.config +++ b/conf/containers_singularity_oras_arm64.config @@ -1,3 +1,4 @@ +process { withName: 'FASTP' { container = 'oras://community.wave.seqera.io/library/fastp:1.3.6--469416be288311d1' } } process { withName: 'FASTQC' { container = 'oras://community.wave.seqera.io/library/fastqc:0.12.1--127a87fc06499035' } } process { withName: 'MULTIQC' { container = 'oras://community.wave.seqera.io/library/multiqc:1.35--c0468833d65b2f81' } } process { withName: 'RGI_BWT' { container = 'oras://community.wave.seqera.io/library/rgi_kma:a6eaadaaffbeaf85' } } diff --git a/conf/modules.config b/conf/modules.config index 17eb876..2f6635b 100644 --- a/conf/modules.config +++ b/conf/modules.config @@ -89,4 +89,68 @@ process { mode: params.publish_dir_mode ] } + + // FASTQ_SHORTREADS_PREPROCESS_QC ships these in its own nextflow.config, which Nextflow + // does not load for an installed subworkflow. Its meta.yml asks the pipeline to copy them + // here, and without them the complexity filters and the deduplication step do nothing. + withName: ".*:FASTQ_COMPLEXITY_FILTER:BBMAP_BBDUK" { + ext.args = [ + 'entropy=0.7', + 'minlength=0' + ].join(' ') + ext.prefix = { "${meta.id}.trim" } + } + + withName: ".*:FASTQ_COMPLEXITY_FILTER:FASTP" { + ext.args = [ + '--low_complexity_filter', + '--disable_adapter_trimming', + '--disable_trim_poly_g', + '--disable_quality_filtering', + '--disable_length_filtering' + ].join(' ') + } + + withName: "BBMAP_CLUMPIFY" { + ext.args = 'dedupe=t' + } + + // Intermediate FASTQ files are only worth keeping when the user asks for them; the logs and + // reports are small and always published so MultiQC and the reader can see what each step did. + withName: ".*:FASTQ_SHORTREADS_PREPROCESS_QC:.*" { + publishDir = [ + [ + path: { "${params.outdir}/preprocessing" }, + mode: params.publish_dir_mode, + enabled: params.save_preprocessed_reads, + pattern: '*.{fastq,fq}.gz' + ], + [ + path: { "${params.outdir}/preprocessing" }, + mode: params.publish_dir_mode, + pattern: '*.{log,json,txt,tsv,html,settings}' + ], + ] + } + + // FastQC runs twice per library inside FASTQ_SHORTREADS_PREPROCESS_QC, once before and once + // after preprocessing, and at that point reads are still per-run. Without these prefixes all + // four sets of reports would collide on the same file names. Declared after the block above + // so that its publishDir wins. + withName: ".*:PRE_STATS:FASTQC" { + ext.prefix = { "${meta.id}_${meta.run_accession}_raw" } + publishDir = [ + path: { "${params.outdir}/fastqc" }, + mode: params.publish_dir_mode, + pattern: '*.{html,zip}' + ] + } + withName: ".*:POST_STATS:FASTQC" { + ext.prefix = { "${meta.id}_${meta.run_accession}_processed" } + publishDir = [ + path: { "${params.outdir}/fastqc" }, + mode: params.publish_dir_mode, + pattern: '*.{html,zip}' + ] + } } diff --git a/docs/output.md b/docs/output.md index 3d6ad22..9de9903 100644 --- a/docs/output.md +++ b/docs/output.md @@ -10,7 +10,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: -- [FastQC](#fastqc) - Raw read QC and preprocessing +- [Read preprocessing](#read-preprocessing) - FastQC, adapter trimming, complexity filtering and host removal - [HUMAnN v3 / v4](#humann-v3--v4) - Functional profiling via MetaPhlAn + HUMAnN - [FMH FunProfiler](#fmh-funprofiler) - Sketch-based functional profiling - [mifaser](#mifaser) - Read-level functional profiling @@ -24,13 +24,18 @@ The pipeline is built using [Nextflow](https://www.nextflow.io/) and processes d Other than FastQC, MultiQC and pipeline information, all other steps (the profilers) are off by default, and must be switched on manually. ::: -### FastQC +### Read preprocessing -[FastQC](https://www.bioinformatics.babraham.ac.uk/projects/fastqc/) gives general quality metrics about your sequenced reads. It provides information about the quality score distribution across your reads, per base sequence content, adapter contamination, and overrepresented sequences. For further reading and documentation see the [FastQC help pages](https://www.bioinformatics.babraham.ac.uk/projects/fastqc/Help/). +Preprocessing runs through the nf-core [`fastq_shortreads_preprocess_qc`](https://nf-co.re/subworkflows/fastq_shortreads_preprocess_qc/) subworkflow, per sequencing run and before runs of the same sample are merged. Only FastQC is on by default; the steps that modify reads are switched on individually, see [read preprocessing in the usage docs](usage.md#read-preprocessing). + +[FastQC](https://www.bioinformatics.babraham.ac.uk/projects/fastqc/) gives general quality metrics about your sequenced reads. It provides information about the quality score distribution across your reads, per base sequence content, adapter contamination, and overrepresented sequences. For further reading and documentation see the [FastQC help pages](https://www.bioinformatics.babraham.ac.uk/projects/fastqc/Help/). It runs twice, once before and once after preprocessing. - `fastqc/` - - `*_fastqc.html`: FastQC report containing quality metrics for each sample. - - `*_fastqc.zip`: Zip archive containing the FastQC report and data files. + - `*_raw_fastqc.{html,zip}`: FastQC report for the reads as they arrived. + - `*_processed_fastqc.{html,zip}`: FastQC report for the reads that go on to the profilers. +- `preprocessing/` + - `*.{log,json,settings,txt}`: Logs and reports from the trimming, complexity filtering, deduplication and host removal steps that ran. Always published, and picked up by MultiQC. + - `*.fastq.gz`: The intermediate FASTQ files themselves, only with `--save_preprocessed_reads`. ### HUMAnN v3 / v4 diff --git a/docs/usage.md b/docs/usage.md index d3cc90a..86af88b 100644 --- a/docs/usage.md +++ b/docs/usage.md @@ -60,6 +60,38 @@ The pipeline will only run the profilers you explicitly turn on, and for which a Each `--run_` flag requires a matching database entry in the `--databases` CSV. Database rows for tools that are not enabled will be ignored. ::: +## Read preprocessing + +Preprocessing is handled by the nf-core [`fastq_shortreads_preprocess_qc`](https://nf-co.re/subworkflows/fastq_shortreads_preprocess_qc/) subworkflow. It runs per sequencing run, before runs of the same sample are merged, so each run is trimmed against its own adapters and quality profile. + +Only read QC is on by default: FastQC runs and reports into MultiQC, and the reads reach the profilers untouched. Every step that modifies reads is opt-in. + +FastQC runs twice per run, once on the raw reads and once after preprocessing, so the reports are suffixed `_raw` and `_processed`. With no preprocessing steps enabled the two are identical by construction. + +| Flag | Default | Effect | +| -------------------------------------- | --------- | ------------------------------------------------------------------------------------------------ | +| `--skip_preprocessing_qc` | `false` | Skip FastQC. Nothing else in this table depends on it | +| `--perform_shortread_qc` | `false` | Turn on adapter trimming and quality filtering | +| `--shortread_qc_tool` | `fastp` | One of `fastp`, `adapterremoval`, `trimmomatic`, `cutadapt`, `trimgalore`, `bbduk`, `leehom` | +| `--shortread_qc_skipadaptertrim` | `false` | Keep the quality filtering but skip adapter removal | +| `--shortread_qc_adapterlist` | `null` | Custom adapter file. FASTA for `bbduk`/`fastp`, plain text for `adapterremoval` | +| `--shortread_qc_mergepairs` | `false` | Emit merged read pairs instead of the trimmed pairs. `fastp` and `adapterremoval` only | +| `--shortread_qc_savetrimmedfail` | `false` | Also keep the reads that failed the `fastp` filters | +| `--shortread_qc_dedup` | `false` | Deduplicate with BBMap `clumpify` | +| `--perform_shortread_complexityfilter` | `false` | Turn on low-complexity filtering | +| `--shortread_complexityfilter_tool` | `bbduk` | One of `bbduk`, `prinseqplusplus`, `fastp` | +| `--perform_shortread_hostremoval` | `false` | Turn on host decontamination | +| `--shortread_hostremoval_tool` | `hostile` | Either `hostile` or `deacon` | +| `--shortread_hostremoval_reference` | `null` | Host genome FASTA to build an index from | +| `--shortread_hostremoval_index` | `null` | Pre-built index directory, used instead of building one from the reference | +| `--shortread_hostremoval_index_name` | `null` | Name of the pre-built index | +| `--save_preprocessed_reads` | `false` | Publish the intermediate FASTQs to `/preprocessing`. Logs and reports publish regardless | + +Tool-specific arguments (minimum read length, entropy thresholds and similar) are set through `ext.args` in `conf/modules.config` rather than as pipeline parameters. See [Custom Tool Arguments](#custom-tool-arguments). + +> [!NOTE] +> The pipeline still does no read preprocessing by default, so reads are expected to arrive already cleaned unless you enable the steps above. + ## Databases input ```bash @@ -78,6 +110,20 @@ Use the `db_name` column to record the database release or version used for the | `db_params` | No | Additional parameters to pass to the profiler (no quotes allowed). | | `db_path` | Yes | Absolute path to the database file or directory. Gzipped TAR archives (`.tar.gz`) are automatically decompressed. | +### Database versions and compatibility + +The pipeline passes `db_path` straight to the profiler and never checks it against the tool version, so pairing a database with a compatible tool release is up to you. The versions a run actually used are recorded in `/pipeline_info/nf_core_funcprofiler_software_mqc_versions.yml`. + +These combinations were exercised on a human gut metagenome cohort during development; RGI, DIAMOND and HUMAnN v4 were only tested against the small CI databases. + +| Tool | Tool version | Database | +| --------------- | ------------ | ----------------------------------------------------------------------------------------------------------------- | +| HUMAnN v3 | 3.6.1 | ChocoPhlAn full `v201901_v31`, UniRef90 `201901b` full, `utility_mapping` full | +| MetaPhlAn | 4.0.6 | `mpa_vJan21_CHOCOPhlAnSGB_202103` | +| FMH FunProfiler | 1.1.1 | KO sketches from [Zenodo record 10045253](https://zenodo.org/records/10045253), scaled 1000 / k=11 and scaled 500 | +| mi-faser | 1.64 | `GS-24-all`, shipped inside the mi-faser biocontainer rather than downloaded separately | +| eggNOG-mapper | 2.1.13 | eggNOG 5.0.2 data directory | + ### HUMANn databases HUMANn requires four database components per named database, each as a separate row with the same `db_name`. The example below uses a HUMANn v3-compatible UniRef90 database set; replace `uniref90_v3` with the exact release or version used in your analysis. diff --git a/modules.json b/modules.json index 68fa76c..0633086 100644 --- a/modules.json +++ b/modules.json @@ -5,10 +5,40 @@ "https://github.com/nf-core/modules.git": { "modules": { "nf-core": { + "adapterremoval": { + "branch": "master", + "git_sha": "6d46786420b4d7bc88eba026eb389c0c5535d120", + "installed_by": ["fastq_removeadapters_merge"] + }, + "bbmap/bbduk": { + "branch": "master", + "git_sha": "6d46786420b4d7bc88eba026eb389c0c5535d120", + "installed_by": ["fastq_complexity_filter", "fastq_removeadapters_merge"] + }, + "bbmap/clumpify": { + "branch": "master", + "git_sha": "f946047c97ed78d3cdcecdc64169c7f9faef99df", + "installed_by": ["fastq_shortreads_preprocess_qc"] + }, "cat/fastq": { "branch": "master", "git_sha": "6d46786420b4d7bc88eba026eb389c0c5535d120", - "installed_by": ["modules"] + "installed_by": ["fastq_removeadapters_merge", "fastq_shortreads_preprocess_qc", "modules"] + }, + "cutadapt": { + "branch": "master", + "git_sha": "6d46786420b4d7bc88eba026eb389c0c5535d120", + "installed_by": ["fastq_removeadapters_merge"] + }, + "deacon/filter": { + "branch": "master", + "git_sha": "719abfd0ec41bcc7e2f540b4fd5c8150fdfe228e", + "installed_by": ["fastq_index_filter_deacon"] + }, + "deacon/index": { + "branch": "master", + "git_sha": "719abfd0ec41bcc7e2f540b4fd5c8150fdfe228e", + "installed_by": ["fastq_index_filter_deacon"] }, "diamond/blastx": { "branch": "master", @@ -20,10 +50,15 @@ "git_sha": "6d46786420b4d7bc88eba026eb389c0c5535d120", "installed_by": ["modules"] }, + "fastp": { + "branch": "master", + "git_sha": "826a9b28d6da61097d60e1923bb8a2e51de3c5b4", + "installed_by": ["fastq_complexity_filter", "fastq_removeadapters_merge"] + }, "fastqc": { "branch": "master", "git_sha": "6d46786420b4d7bc88eba026eb389c0c5535d120", - "installed_by": ["modules"] + "installed_by": ["fastq_qc_stats", "modules"] }, "fmhfunprofiler": { "branch": "master", @@ -35,6 +70,16 @@ "git_sha": "0902eac3012baaf4f9ab6513c8c55acc9353c96c", "installed_by": ["modules"] }, + "hostile/clean": { + "branch": "master", + "git_sha": "14295214ba15060cf1e65bdba3e0c4b3a450d5b1", + "installed_by": ["fastq_fetch_clean_hostile"] + }, + "hostile/fetch": { + "branch": "master", + "git_sha": "14295214ba15060cf1e65bdba3e0c4b3a450d5b1", + "installed_by": ["fastq_fetch_clean_hostile"] + }, "humann3/humann": { "branch": "master", "git_sha": "7a2d8f7e1861c9c62472f1cd168d33cef918b108", @@ -50,6 +95,11 @@ "git_sha": "7a2d8f7e1861c9c62472f1cd168d33cef918b108", "installed_by": ["modules"] }, + "leehom": { + "branch": "master", + "git_sha": "9339809fcb90af8a8b7051e6cd914894d5c52002", + "installed_by": ["fastq_removeadapters_merge"] + }, "metaphlan/metaphlan": { "branch": "master", "git_sha": "6d46786420b4d7bc88eba026eb389c0c5535d120", @@ -65,6 +115,11 @@ "git_sha": "98403d15b0e50edae1f3fec5eae5e24982f1fade", "installed_by": ["modules"] }, + "prinseqplusplus": { + "branch": "master", + "git_sha": "6232d580ed64273c6d1ae867edc3f27bc837ee80", + "installed_by": ["fastq_complexity_filter"] + }, "rgi/bwt": { "branch": "master", "git_sha": "587ef528a2f42a2415ad1b5f760ea93ab1983603", @@ -75,11 +130,71 @@ "git_sha": "6d46786420b4d7bc88eba026eb389c0c5535d120", "installed_by": ["modules"] }, + "seqfu/check": { + "branch": "master", + "git_sha": "e0c07e2169e7569da58d8d8c3ca71023fc6a217e", + "installed_by": ["fastq_qc_stats"] + }, + "seqfu/stats": { + "branch": "master", + "git_sha": "e0c07e2169e7569da58d8d8c3ca71023fc6a217e", + "installed_by": ["fastq_qc_stats"] + }, "seqkit/fq2fa": { "branch": "master", "git_sha": "d07a945f421a4a91ce4e5a22db63e0e7f8741607", "installed_by": ["modules"] }, + "seqkit/pair": { + "branch": "master", + "git_sha": "d07a945f421a4a91ce4e5a22db63e0e7f8741607", + "installed_by": ["fastq_sanitise_seqkit"] + }, + "seqkit/replace": { + "branch": "master", + "git_sha": "d07a945f421a4a91ce4e5a22db63e0e7f8741607", + "installed_by": ["fastq_preprocess_seqkit"] + }, + "seqkit/rmdup": { + "branch": "master", + "git_sha": "d07a945f421a4a91ce4e5a22db63e0e7f8741607", + "installed_by": ["fastq_preprocess_seqkit"] + }, + "seqkit/sana": { + "branch": "master", + "git_sha": "d07a945f421a4a91ce4e5a22db63e0e7f8741607", + "installed_by": ["fastq_sanitise_seqkit"] + }, + "seqkit/seq": { + "branch": "master", + "git_sha": "d07a945f421a4a91ce4e5a22db63e0e7f8741607", + "installed_by": ["fastq_preprocess_seqkit"] + }, + "seqkit/stats": { + "branch": "master", + "git_sha": "d07a945f421a4a91ce4e5a22db63e0e7f8741607", + "installed_by": ["fastq_qc_stats"] + }, + "seqtk/comp": { + "branch": "master", + "git_sha": "6d46786420b4d7bc88eba026eb389c0c5535d120", + "installed_by": ["fastq_qc_stats"] + }, + "trimgalore": { + "branch": "master", + "git_sha": "2a30bfb6a1b4667692d734e33e000a3bd1d74a0d", + "installed_by": ["fastq_removeadapters_merge"] + }, + "trimmomatic": { + "branch": "master", + "git_sha": "6d46786420b4d7bc88eba026eb389c0c5535d120", + "installed_by": ["fastq_removeadapters_merge"] + }, + "umitools/extract": { + "branch": "master", + "git_sha": "6d46786420b4d7bc88eba026eb389c0c5535d120", + "installed_by": ["fastq_shortreads_preprocess_qc"] + }, "untar": { "branch": "master", "git_sha": "6d46786420b4d7bc88eba026eb389c0c5535d120", @@ -89,6 +204,51 @@ }, "subworkflows": { "nf-core": { + "fastq_complexity_filter": { + "branch": "master", + "git_sha": "826a9b28d6da61097d60e1923bb8a2e51de3c5b4", + "installed_by": ["fastq_shortreads_preprocess_qc"] + }, + "fastq_decontaminate_deacon_hostile": { + "branch": "master", + "git_sha": "ff4fdc4784fff8e4d2e8b0a2a6aec6591be20a38", + "installed_by": ["fastq_shortreads_preprocess_qc"] + }, + "fastq_fetch_clean_hostile": { + "branch": "master", + "git_sha": "14295214ba15060cf1e65bdba3e0c4b3a450d5b1", + "installed_by": ["fastq_decontaminate_deacon_hostile"] + }, + "fastq_index_filter_deacon": { + "branch": "master", + "git_sha": "ff4fdc4784fff8e4d2e8b0a2a6aec6591be20a38", + "installed_by": ["fastq_decontaminate_deacon_hostile"] + }, + "fastq_preprocess_seqkit": { + "branch": "master", + "git_sha": "d07a945f421a4a91ce4e5a22db63e0e7f8741607", + "installed_by": ["fastq_shortreads_preprocess_qc"] + }, + "fastq_qc_stats": { + "branch": "master", + "git_sha": "d07a945f421a4a91ce4e5a22db63e0e7f8741607", + "installed_by": ["fastq_shortreads_preprocess_qc"] + }, + "fastq_removeadapters_merge": { + "branch": "master", + "git_sha": "c2850907e07fc8d2f2800efd6cb7880dbc69b553", + "installed_by": ["fastq_shortreads_preprocess_qc"] + }, + "fastq_sanitise_seqkit": { + "branch": "master", + "git_sha": "d07a945f421a4a91ce4e5a22db63e0e7f8741607", + "installed_by": ["fastq_preprocess_seqkit"] + }, + "fastq_shortreads_preprocess_qc": { + "branch": "master", + "git_sha": "ff4fdc4784fff8e4d2e8b0a2a6aec6591be20a38", + "installed_by": ["subworkflows"] + }, "utils_nextflow_pipeline": { "branch": "master", "git_sha": "1a545fcbd762911c21a64ced3dbef99b2b51ac75", diff --git a/modules/nf-core/adapterremoval/environment.yml b/modules/nf-core/adapterremoval/environment.yml new file mode 100644 index 0000000..bef33d3 --- /dev/null +++ b/modules/nf-core/adapterremoval/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::adapterremoval=2.3.4 diff --git a/modules/nf-core/adapterremoval/main.nf b/modules/nf-core/adapterremoval/main.nf new file mode 100644 index 0000000..e2d03a3 --- /dev/null +++ b/modules/nf-core/adapterremoval/main.nf @@ -0,0 +1,106 @@ +process ADAPTERREMOVAL { + tag "$meta.id" + label 'process_medium' + + conda "${moduleDir}/environment.yml" + container "${ workflow.containerEngine in ['singularity', 'apptainer'] && !task.ext.singularity_pull_docker_container ? + 'https://depot.galaxyproject.org/singularity/adapterremoval:2.3.4--pl5321haf24da9_2' : + 'quay.io/biocontainers/adapterremoval:2.3.4--pl5321haf24da9_2' }" + + input: + tuple val(meta), path(reads) + path(adapterlist) + + output: + tuple val(meta), path("${prefix}.truncated.fastq.gz") , emit: singles_truncated , optional: true + tuple val(meta), path("${prefix}.discarded.fastq.gz") , emit: discarded , optional: true + tuple val(meta), path("${prefix}.pair{1,2}.truncated.fastq.gz"), emit: paired_truncated , optional: true + tuple val(meta), path("${prefix}.collapsed.fastq.gz") , emit: collapsed , optional: true + tuple val(meta), path("${prefix}.collapsed.truncated.fastq.gz"), emit: collapsed_truncated, optional: true + tuple val(meta), path("${prefix}.paired.fastq.gz") , emit: paired_interleaved , optional: true + tuple val(meta), path('*.settings') , emit: settings + tuple val("${task.process}"), val('AdapterRemoval'), eval('AdapterRemoval --version 2>&1 | sed -e "s/AdapterRemoval ver. //g"'), emit: versions_adapterremoval, topic: versions + + + when: + task.ext.when == null || task.ext.when + + script: + def args = task.ext.args ?: '' + prefix = task.ext.prefix ?: "${meta.id}" + def list = adapterlist ? "--adapter-list ${adapterlist}" : "" + + if (meta.single_end) { + """ + AdapterRemoval \\ + --file1 ${reads} \\ + ${args} \\ + ${list} \\ + --basename ${prefix} \\ + --threads ${task.cpus} \\ + --seed 42 \\ + --gzip + + ensure_fastq() { + if [ -f "\${1}" ]; then + mv "\${1}" "\${1::-3}.fastq.gz" + fi + + } + + ensure_fastq '${prefix}.truncated.gz' + ensure_fastq '${prefix}.discarded.gz' + """ + } else { + """ + AdapterRemoval \\ + --file1 ${reads[0]} \\ + --file2 ${reads[1]} \\ + ${args} \\ + ${list} \\ + --basename ${prefix} \\ + --threads ${task.cpus} \\ + --seed 42 \\ + --gzip + + ensure_fastq() { + if [ -f "\${1}" ]; then + mv "\${1}" "\${1::-3}.fastq.gz" + fi + + } + + ensure_fastq '${prefix}.truncated.gz' + ensure_fastq '${prefix}.discarded.gz' + ensure_fastq '${prefix}.pair1.truncated.gz' + ensure_fastq '${prefix}.pair2.truncated.gz' + ensure_fastq '${prefix}.collapsed.gz' + ensure_fastq '${prefix}.collapsed.truncated.gz' + ensure_fastq '${prefix}.paired.gz' + """ + } + + stub: + def args = task.ext.args ?: '' + prefix = task.ext.prefix ?: "${meta.id}" + collapse_cmd = args.contains('--collapse') + + """ + echo ${args} + + touch '${prefix}.settings' + echo | gzip > '${prefix}.truncated.fastq.gz' + echo | gzip > '${prefix}.discarded.fastq.gz' + + if [ "${meta.single_end}" = false ]; then + echo | gzip > '${prefix}.pair1.truncated.fastq.gz' + echo | gzip > '${prefix}.pair2.truncated.fastq.gz' + echo | gzip > '${prefix}.paired.fastq.gz' + + if [ "${collapse_cmd}" = true ]; then + echo | gzip > '${prefix}.collapsed.truncated.fastq.gz' + echo | gzip > '${prefix}.collapsed.fastq.gz' + fi + fi + """ +} diff --git a/modules/nf-core/adapterremoval/meta.yml b/modules/nf-core/adapterremoval/meta.yml new file mode 100644 index 0000000..e8cbcaf --- /dev/null +++ b/modules/nf-core/adapterremoval/meta.yml @@ -0,0 +1,153 @@ +name: adapterremoval +description: Trim sequencing adapters and collapse overlapping reads +keywords: + - trimming + - adapters + - merging + - fastq +tools: + - adapterremoval: + description: The AdapterRemoval v2 tool for merging and clipping reads. + homepage: https://github.com/MikkelSchubert/adapterremoval + documentation: https://adapterremoval.readthedocs.io + licence: + - "GPL v3" + identifier: biotools:adapterremoval +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. + pattern: "*.{fq,fastq,fq.gz,fastq.gz}" + ontologies: + - edam: "http://edamontology.org/format_1930" + - adapterlist: + type: file + description: Optional text file containing list of adapters to look for for + removal with one adapter per line. Otherwise will look for default + adapters (see AdapterRemoval man page), or can be modified to remove + user-specified adapters via ext.args. + ontologies: + - edam: "http://edamontology.org/format_2330" +output: + singles_truncated: + - - meta: + type: map + description: Groovy Map containing sample information + - ${prefix}.truncated.fastq.gz: + type: file + description: | + Adapter trimmed FastQ files of either single-end reads, or singleton + 'orphaned' reads from merging of paired-end data (i.e., one of the pair + was lost due to filtering thresholds). + pattern: "*.truncated.fastq.gz" + ontologies: + - edam: "http://edamontology.org/format_1930" + - edam: http://edamontology.org/format_3989 + discarded: + - - meta: + type: map + description: Groovy Map containing sample information + - ${prefix}.discarded.fastq.gz: + type: file + description: | + Adapter trimmed FastQ files of reads that did not pass filtering + thresholds. + pattern: "*.discarded.fastq.gz" + ontologies: + - edam: "http://edamontology.org/format_1930" + - edam: http://edamontology.org/format_3989 + paired_truncated: + - - meta: + type: map + description: Groovy Map containing sample information + - ${prefix}.pair{1,2}.truncated.fastq.gz: + type: file + description: | + Adapter trimmed R{1,2} FastQ files of paired-end reads that did not merge + with their respective R{1,2} pair due to long templates. The respective pair + is stored in 'pair{1,2}_truncated'. + pattern: "*.pair{1,2}.truncated.fastq.gz" + ontologies: + - edam: "http://edamontology.org/format_1930" + collapsed: + - - meta: + type: map + description: Groovy Map containing sample information + - ${prefix}.collapsed.fastq.gz: + type: file + description: | + Collapsed FastQ of paired-end reads that successfully merged with their + respective R1 pair but were not trimmed. + pattern: "*.collapsed.fastq.gz" + ontologies: + - edam: "http://edamontology.org/format_1930" + - edam: http://edamontology.org/format_3989 + collapsed_truncated: + - - meta: + type: map + description: Groovy Map containing sample information + - ${prefix}.collapsed.truncated.fastq.gz: + type: file + description: | + Collapsed FastQ of paired-end reads that successfully merged with their + respective R1 pair and were trimmed of adapter due to sufficient overlap. + pattern: "*.collapsed.truncated.fastq.gz" + ontologies: + - edam: "http://edamontology.org/format_1930" + - edam: http://edamontology.org/format_3989 + paired_interleaved: + - - meta: + type: map + description: Groovy Map containing sample information + - ${prefix}.paired.fastq.gz: + type: file + description: | + Write paired-end reads to a single file, interleaving mate 1 and mate 2 reads + pattern: "*.paired.fastq.gz" + ontologies: + - edam: "http://edamontology.org/format_1930" + - edam: http://edamontology.org/format_3989 + settings: + - - meta: + type: map + description: Groovy Map containing sample information + - "*.settings": + type: file + description: AdapterRemoval log file + pattern: "*.settings" + ontologies: + - edam: "http://edamontology.org/format_2330" + versions_adapterremoval: + - - ${task.process}: + type: string + description: The name of the process + - AdapterRemoval: + type: string + description: The name of the tool + - AdapterRemoval --version 2>&1 | sed -e "s/AdapterRemoval ver. //g": + type: eval + description: The expression to obtain the version of the tool +topics: + versions: + - - ${task.process}: + type: string + description: The name of the process + - AdapterRemoval: + type: string + description: The name of the tool + - AdapterRemoval --version 2>&1 | sed -e "s/AdapterRemoval ver. //g": + type: eval + description: The expression to obtain the version of the tool +authors: + - "@maxibor" + - "@jfy133" +maintainers: + - "@maxibor" + - "@jfy133" diff --git a/modules/nf-core/adapterremoval/tests/main.nf.test b/modules/nf-core/adapterremoval/tests/main.nf.test new file mode 100644 index 0000000..bf7a399 --- /dev/null +++ b/modules/nf-core/adapterremoval/tests/main.nf.test @@ -0,0 +1,228 @@ +nextflow_process { + + name "Test Process ADAPTERREMOVAL" + script "../main.nf" + config "./nextflow.config" + process "ADAPTERREMOVAL" + + tag "modules" + tag "modules_nfcore" + tag "adapterremoval" + + test("single-end - sarscov2 - [fastq]") { + when { + params { + adapterremoval_args = "" + } + process { + """ + input[0] = [ + [ id:'test', single_end:true ], + file(params.modules_testdata_base_path + 'genomics/sarscov2/illumina/fastq/test_1.fastq.gz', checkIfExists: true) + ] + input[1] = [] + """ + } + } + + then { + assertAll( + { assert process.success }, + { assert snapshot( + process.out.singles_truncated, + process.out.settings, + process.out.findAll { key, val -> key.startsWith("versions")} + ).match() }, + ) + } + } + + test("paired-end - sarscov2 - [fastq]") { + + when { + params { + adapterremoval_args = "" + } + process { + """ + input[0] = [ + [ id:'test', single_end:false ], + [ + 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] = [] + """ + } + } + + then { + assertAll( + { assert process.success }, + { assert snapshot( + process.out.paired_truncated, + process.out.settings, + process.out.findAll { key, val -> key.startsWith("versions")} + ).match() } + ) + } + } + + test("paired-end collapse - sarscov2 - [fastq]") { + when { + params { + adapterremoval_args = "--collapse" + } + process { + """ + input[0] = [ + [ id:'test', single_end:false ], + [ + 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] = [] + """ + } + } + + then { + assertAll( + { assert process.success }, + { assert snapshot( + process.out.paired_truncated, + process.out.collapsed, + file(process.out.collapsed_truncated[0][1]).name, // Check for present but is empty + process.out.settings, + process.out.findAll { key, val -> key.startsWith("versions")} + ).match() } + ) + } + + } + + test("paired-end adapterlist - sarscov2 - [fastq]") { + when { + params { + adapterremoval_args = "" + } + process { + """ + input[0] = [ + [ id:'test', single_end:false ], + [ + 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] = file(params.modules_testdata_base_path + '/delete_me/adapterremoval/adapterremoval_adapterlist.txt', checkIfExists: true) + """ + } + } + + then { + assertAll( + { assert process.success }, + { assert snapshot( + process.out.paired_truncated, + process.out.settings, + process.out.findAll { key, val -> key.startsWith("versions")} + ).match() } + ) + } + } + + test("paired-end collapse adapterlist - sarscov2 - [fastq] -- stub") { + options "-stub" + when { + params { + adapterremoval_args = "--collapse" + } + process { + """ + input[0] = [ + [ id:'test', single_end:false ], + [ + 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] = file(params.modules_testdata_base_path + '/delete_me/adapterremoval/adapterremoval_adapterlist.txt', checkIfExists: true) + """ + } + } + + then { + assertAll( + { assert process.success }, + { assert snapshot( + process.out, + process.out.findAll { key, val -> key.startsWith("versions") } + ).match() } + ) + } + } + + test("single-end - sarscov2 - [fastq] -- stub") { + options "-stub" + when { + params { + adapterremoval_args = "" + } + process { + """ + input[0] = [ + [ id:'test', single_end:true ], + file(params.modules_testdata_base_path + 'genomics/sarscov2/illumina/fastq/test_1.fastq.gz', checkIfExists: true) + ] + input[1] = [] + """ + } + } + + then { + assertAll( + { assert process.success }, + { assert snapshot( + process.out, + process.out.findAll { key, val -> key.startsWith("versions")} + ).match() } + ) + } + } + + test("paired-end - sarscov2 - [fastq] -- stub") { + options "-stub" + when { + params { + adapterremoval_args = "" + } + process { + """ + input[0] = [ + [ id:'test', single_end:false ], + [ + 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] = [] + """ + } + } + + then { + assertAll( + { assert process.success }, + { assert snapshot( + process.out, + process.out.findAll { key, val -> key.startsWith("versions")} + ).match() } + ) + } + + } + +} diff --git a/modules/nf-core/adapterremoval/tests/main.nf.test.snap b/modules/nf-core/adapterremoval/tests/main.nf.test.snap new file mode 100644 index 0000000..2126c24 --- /dev/null +++ b/modules/nf-core/adapterremoval/tests/main.nf.test.snap @@ -0,0 +1,585 @@ +{ + "paired-end collapse adapterlist - sarscov2 - [fastq] -- stub": { + "content": [ + { + "0": [ + [ + { + "id": "test", + "single_end": false + }, + "test.truncated.fastq.gz:md5,68b329da9893e34099c7d8ad5cb9c940" + ] + ], + "1": [ + [ + { + "id": "test", + "single_end": false + }, + "test.discarded.fastq.gz:md5,68b329da9893e34099c7d8ad5cb9c940" + ] + ], + "2": [ + [ + { + "id": "test", + "single_end": false + }, + [ + "test.pair1.truncated.fastq.gz:md5,68b329da9893e34099c7d8ad5cb9c940", + "test.pair2.truncated.fastq.gz:md5,68b329da9893e34099c7d8ad5cb9c940" + ] + ] + ], + "3": [ + [ + { + "id": "test", + "single_end": false + }, + "test.collapsed.fastq.gz:md5,68b329da9893e34099c7d8ad5cb9c940" + ] + ], + "4": [ + [ + { + "id": "test", + "single_end": false + }, + "test.collapsed.truncated.fastq.gz:md5,68b329da9893e34099c7d8ad5cb9c940" + ] + ], + "5": [ + [ + { + "id": "test", + "single_end": false + }, + "test.paired.fastq.gz:md5,68b329da9893e34099c7d8ad5cb9c940" + ] + ], + "6": [ + [ + { + "id": "test", + "single_end": false + }, + "test.settings:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "7": [ + [ + "ADAPTERREMOVAL", + "AdapterRemoval", + "2.3.4" + ] + ], + "collapsed": [ + [ + { + "id": "test", + "single_end": false + }, + "test.collapsed.fastq.gz:md5,68b329da9893e34099c7d8ad5cb9c940" + ] + ], + "collapsed_truncated": [ + [ + { + "id": "test", + "single_end": false + }, + "test.collapsed.truncated.fastq.gz:md5,68b329da9893e34099c7d8ad5cb9c940" + ] + ], + "discarded": [ + [ + { + "id": "test", + "single_end": false + }, + "test.discarded.fastq.gz:md5,68b329da9893e34099c7d8ad5cb9c940" + ] + ], + "paired_interleaved": [ + [ + { + "id": "test", + "single_end": false + }, + "test.paired.fastq.gz:md5,68b329da9893e34099c7d8ad5cb9c940" + ] + ], + "paired_truncated": [ + [ + { + "id": "test", + "single_end": false + }, + [ + "test.pair1.truncated.fastq.gz:md5,68b329da9893e34099c7d8ad5cb9c940", + "test.pair2.truncated.fastq.gz:md5,68b329da9893e34099c7d8ad5cb9c940" + ] + ] + ], + "settings": [ + [ + { + "id": "test", + "single_end": false + }, + "test.settings:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "singles_truncated": [ + [ + { + "id": "test", + "single_end": false + }, + "test.truncated.fastq.gz:md5,68b329da9893e34099c7d8ad5cb9c940" + ] + ], + "versions_adapterremoval": [ + [ + "ADAPTERREMOVAL", + "AdapterRemoval", + "2.3.4" + ] + ] + }, + { + "versions_adapterremoval": [ + [ + "ADAPTERREMOVAL", + "AdapterRemoval", + "2.3.4" + ] + ] + } + ], + "meta": { + "nf-test": "0.9.3", + "nextflow": "25.10.2" + }, + "timestamp": "2026-03-12T10:02:07.692151288" + }, + "single-end - sarscov2 - [fastq] -- stub": { + "content": [ + { + "0": [ + [ + { + "id": "test", + "single_end": true + }, + "test.truncated.fastq.gz:md5,68b329da9893e34099c7d8ad5cb9c940" + ] + ], + "1": [ + [ + { + "id": "test", + "single_end": true + }, + "test.discarded.fastq.gz:md5,68b329da9893e34099c7d8ad5cb9c940" + ] + ], + "2": [ + + ], + "3": [ + + ], + "4": [ + + ], + "5": [ + + ], + "6": [ + [ + { + "id": "test", + "single_end": true + }, + "test.settings:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "7": [ + [ + "ADAPTERREMOVAL", + "AdapterRemoval", + "2.3.4" + ] + ], + "collapsed": [ + + ], + "collapsed_truncated": [ + + ], + "discarded": [ + [ + { + "id": "test", + "single_end": true + }, + "test.discarded.fastq.gz:md5,68b329da9893e34099c7d8ad5cb9c940" + ] + ], + "paired_interleaved": [ + + ], + "paired_truncated": [ + + ], + "settings": [ + [ + { + "id": "test", + "single_end": true + }, + "test.settings:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "singles_truncated": [ + [ + { + "id": "test", + "single_end": true + }, + "test.truncated.fastq.gz:md5,68b329da9893e34099c7d8ad5cb9c940" + ] + ], + "versions_adapterremoval": [ + [ + "ADAPTERREMOVAL", + "AdapterRemoval", + "2.3.4" + ] + ] + }, + { + "versions_adapterremoval": [ + [ + "ADAPTERREMOVAL", + "AdapterRemoval", + "2.3.4" + ] + ] + } + ], + "meta": { + "nf-test": "0.9.4", + "nextflow": "25.10.4" + }, + "timestamp": "2026-03-11T14:46:06.552516" + }, + "single-end - sarscov2 - [fastq]": { + "content": [ + [ + [ + { + "id": "test", + "single_end": true + }, + "test.truncated.fastq.gz:md5,119d1b1a0a71ca6e080ff7c53ee0b690" + ] + ], + [ + [ + { + "id": "test", + "single_end": true + }, + "test.settings:md5,9065cf46619a40116bdd55b697eaec33" + ] + ], + { + "versions_adapterremoval": [ + [ + "ADAPTERREMOVAL", + "AdapterRemoval", + "2.3.4" + ] + ] + } + ], + "meta": { + "nf-test": "0.9.4", + "nextflow": "25.10.4" + }, + "timestamp": "2026-03-11T14:45:44.704111" + }, + "paired-end - sarscov2 - [fastq] -- stub": { + "content": [ + { + "0": [ + [ + { + "id": "test", + "single_end": false + }, + "test.truncated.fastq.gz:md5,68b329da9893e34099c7d8ad5cb9c940" + ] + ], + "1": [ + [ + { + "id": "test", + "single_end": false + }, + "test.discarded.fastq.gz:md5,68b329da9893e34099c7d8ad5cb9c940" + ] + ], + "2": [ + [ + { + "id": "test", + "single_end": false + }, + [ + "test.pair1.truncated.fastq.gz:md5,68b329da9893e34099c7d8ad5cb9c940", + "test.pair2.truncated.fastq.gz:md5,68b329da9893e34099c7d8ad5cb9c940" + ] + ] + ], + "3": [ + + ], + "4": [ + + ], + "5": [ + [ + { + "id": "test", + "single_end": false + }, + "test.paired.fastq.gz:md5,68b329da9893e34099c7d8ad5cb9c940" + ] + ], + "6": [ + [ + { + "id": "test", + "single_end": false + }, + "test.settings:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "7": [ + [ + "ADAPTERREMOVAL", + "AdapterRemoval", + "2.3.4" + ] + ], + "collapsed": [ + + ], + "collapsed_truncated": [ + + ], + "discarded": [ + [ + { + "id": "test", + "single_end": false + }, + "test.discarded.fastq.gz:md5,68b329da9893e34099c7d8ad5cb9c940" + ] + ], + "paired_interleaved": [ + [ + { + "id": "test", + "single_end": false + }, + "test.paired.fastq.gz:md5,68b329da9893e34099c7d8ad5cb9c940" + ] + ], + "paired_truncated": [ + [ + { + "id": "test", + "single_end": false + }, + [ + "test.pair1.truncated.fastq.gz:md5,68b329da9893e34099c7d8ad5cb9c940", + "test.pair2.truncated.fastq.gz:md5,68b329da9893e34099c7d8ad5cb9c940" + ] + ] + ], + "settings": [ + [ + { + "id": "test", + "single_end": false + }, + "test.settings:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "singles_truncated": [ + [ + { + "id": "test", + "single_end": false + }, + "test.truncated.fastq.gz:md5,68b329da9893e34099c7d8ad5cb9c940" + ] + ], + "versions_adapterremoval": [ + [ + "ADAPTERREMOVAL", + "AdapterRemoval", + "2.3.4" + ] + ] + }, + { + "versions_adapterremoval": [ + [ + "ADAPTERREMOVAL", + "AdapterRemoval", + "2.3.4" + ] + ] + } + ], + "meta": { + "nf-test": "0.9.4", + "nextflow": "25.10.4" + }, + "timestamp": "2026-03-11T14:46:10.993837" + }, + "paired-end - sarscov2 - [fastq]": { + "content": [ + [ + [ + { + "id": "test", + "single_end": false + }, + [ + "test.pair1.truncated.fastq.gz:md5,e3da014fbb9b428e952c62e8f0fb6402", + "test.pair2.truncated.fastq.gz:md5,2ebae722295ea66d84075a3b042e2b42" + ] + ] + ], + [ + [ + { + "id": "test", + "single_end": false + }, + "test.settings:md5,9b7f6eeb6af14639588b76d434bd2d8f" + ] + ], + { + "versions_adapterremoval": [ + [ + "ADAPTERREMOVAL", + "AdapterRemoval", + "2.3.4" + ] + ] + } + ], + "meta": { + "nf-test": "0.9.4", + "nextflow": "25.10.4" + }, + "timestamp": "2026-03-11T14:45:49.128578" + }, + "paired-end adapterlist - sarscov2 - [fastq]": { + "content": [ + [ + [ + { + "id": "test", + "single_end": false + }, + [ + "test.pair1.truncated.fastq.gz:md5,e3da014fbb9b428e952c62e8f0fb6402", + "test.pair2.truncated.fastq.gz:md5,2ebae722295ea66d84075a3b042e2b42" + ] + ] + ], + [ + [ + { + "id": "test", + "single_end": false + }, + "test.settings:md5,5eca9942f2e3a970737f81251c7f49e7" + ] + ], + { + "versions_adapterremoval": [ + [ + "ADAPTERREMOVAL", + "AdapterRemoval", + "2.3.4" + ] + ] + } + ], + "meta": { + "nf-test": "0.9.4", + "nextflow": "25.10.4" + }, + "timestamp": "2026-03-11T14:45:58.153655" + }, + "paired-end collapse - sarscov2 - [fastq]": { + "content": [ + [ + [ + { + "id": "test", + "single_end": false + }, + [ + "test.pair1.truncated.fastq.gz:md5,d6113ac35457dc942e4e47d6530e1d5e", + "test.pair2.truncated.fastq.gz:md5,304c48e7ad50d46acf73ae6de4014f64" + ] + ] + ], + [ + [ + { + "id": "test", + "single_end": false + }, + "test.collapsed.fastq.gz:md5,369452751050a7f1e31b839702d61417" + ] + ], + "test.collapsed.truncated.fastq.gz", + [ + [ + { + "id": "test", + "single_end": false + }, + "test.settings:md5,6059588d1cac39c081d15c2024fcdf04" + ] + ], + { + "versions_adapterremoval": [ + [ + "ADAPTERREMOVAL", + "AdapterRemoval", + "2.3.4" + ] + ] + } + ], + "meta": { + "nf-test": "0.9.4", + "nextflow": "25.10.4" + }, + "timestamp": "2026-03-11T14:45:53.48399" + } +} \ No newline at end of file diff --git a/modules/nf-core/adapterremoval/tests/nextflow.config b/modules/nf-core/adapterremoval/tests/nextflow.config new file mode 100644 index 0000000..f0c9b67 --- /dev/null +++ b/modules/nf-core/adapterremoval/tests/nextflow.config @@ -0,0 +1,5 @@ +process { + withName: 'ADAPTERREMOVAL' { + ext.args = params.adapterremoval_args + } +} diff --git a/modules/nf-core/bbmap/bbduk/environment.yml b/modules/nf-core/bbmap/bbduk/environment.yml new file mode 100644 index 0000000..a33ddca --- /dev/null +++ b/modules/nf-core/bbmap/bbduk/environment.yml @@ -0,0 +1,8 @@ +--- +# yaml-language-server: $schema=https://raw.githubusercontent.com/nf-core/modules/master/modules/environment-schema.json +channels: + - conda-forge + - bioconda +dependencies: + - bioconda::bbmap=39.18 + - pigz=2.8 diff --git a/modules/nf-core/bbmap/bbduk/main.nf b/modules/nf-core/bbmap/bbduk/main.nf new file mode 100644 index 0000000..b61a1ec --- /dev/null +++ b/modules/nf-core/bbmap/bbduk/main.nf @@ -0,0 +1,46 @@ +process BBMAP_BBDUK { + tag "$meta.id" + label 'process_medium' + + conda "${moduleDir}/environment.yml" + container "${ workflow.containerEngine in ['singularity', 'apptainer'] && !task.ext.singularity_pull_docker_container ? + 'https://community-cr-prod.seqera.io/docker/registry/v2/blobs/sha256/5a/5aae5977ff9de3e01ff962dc495bfa23f4304c676446b5fdf2de5c7edfa2dc4e/data' : + 'community.wave.seqera.io/library/bbmap_pigz:07416fe99b090fa9' }" + + input: + tuple val(meta), path(reads) + path contaminants + + output: + tuple val(meta), path('*.fastq.gz'), emit: reads + tuple val(meta), path('*.log') , emit: log + tuple val("${task.process}"), val('bbmap'), eval('bbversion.sh | grep -v "Duplicate cpuset"'), emit: versions_bbmap, topic: versions + + when: + task.ext.when == null || task.ext.when + + script: + def args = task.ext.args ?: '' + def prefix = task.ext.prefix ?: "${meta.id}" + def raw = meta.single_end ? "in=${reads[0]}" : "in1=${reads[0]} in2=${reads[1]}" + def trimmed = meta.single_end ? "out=${prefix}.fastq.gz" : "out1=${prefix}_1.fastq.gz out2=${prefix}_2.fastq.gz" + def contaminants_fa = contaminants ? "ref=$contaminants" : '' + """ + bbduk.sh \\ + -Xmx${task.memory.toGiga()}g \\ + $raw \\ + $trimmed \\ + threads=$task.cpus \\ + $args \\ + $contaminants_fa \\ + &> ${prefix}.bbduk.log + """ + + stub: + def prefix = task.ext.prefix ?: "${meta.id}" + def output_command = meta.single_end ? "echo '' | gzip > ${prefix}.fastq.gz" : "echo '' | gzip > ${prefix}_1.fastq.gz ; echo '' | gzip > ${prefix}_2.fastq.gz" + """ + touch ${prefix}.bbduk.log + $output_command + """ +} diff --git a/modules/nf-core/bbmap/bbduk/meta.yml b/modules/nf-core/bbmap/bbduk/meta.yml new file mode 100644 index 0000000..408ff57 --- /dev/null +++ b/modules/nf-core/bbmap/bbduk/meta.yml @@ -0,0 +1,82 @@ +name: bbmap_bbduk +description: Adapter and quality trimming of sequencing reads +keywords: + - trimming + - adapter trimming + - quality trimming + - fastq +tools: + - bbmap: + description: BBMap is a short read aligner, as well as various other + bioinformatic tools. + homepage: https://jgi.doe.gov/data-and-tools/software-tools/bbtools/bb-tools-user-guide/ + documentation: https://jgi.doe.gov/data-and-tools/software-tools/bbtools/bb-tools-user-guide/ + licence: + - "UC-LBL license (see package)" + identifier: biotools:bbmap +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: [] + - contaminants: + type: file + description: | + Reference files containing adapter and/or contaminant sequences for sequence kmer matching + ontologies: [] +output: + reads: + - - meta: + type: map + description: | + Groovy Map containing sample information + e.g. [ id:'test', single_end:false ] + - "*.fastq.gz": + type: file + description: The trimmed/modified fastq reads + pattern: "*fastq.gz" + ontologies: + - edam: http://edamontology.org/format_3989 # GZIP + log: + - - meta: + type: map + description: | + Groovy Map containing sample information + e.g. [ id:'test', single_end:false ] + - "*.log": + type: file + description: Bbduk log file + pattern: "*bbduk.log" + ontologies: [] + versions_bbmap: + - - "${task.process}": + type: string + description: The name of the process + - bbmap: + type: string + description: The name of the tool + - bbversion.sh | grep -v "Duplicate cpuset": + type: eval + description: The expression to obtain the version of the tool +topics: + versions: + - - "${task.process}": + type: string + description: The name of the process + - bbmap: + type: string + description: The name of the tool + - bbversion.sh | grep -v "Duplicate cpuset": + type: eval + description: The expression to obtain the version of the tool +authors: + - "@MGordon09" +maintainers: + - "@MGordon09" diff --git a/modules/nf-core/bbmap/bbduk/tests/main.nf.test b/modules/nf-core/bbmap/bbduk/tests/main.nf.test new file mode 100644 index 0000000..56282b5 --- /dev/null +++ b/modules/nf-core/bbmap/bbduk/tests/main.nf.test @@ -0,0 +1,169 @@ +nextflow_process { + + name "Test Process BBMAP_BBDUK" + script "../main.nf" + process "BBMAP_BBDUK" + config "./nextflow.config" + + tag "modules" + tag "modules_nfcore" + tag "bbmap" + tag "bbmap/bbduk" + + test("sarscov2 - single end fastq - fastq") { + + 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) ] + ] + input[1] = [] + """ + } + } + + then { + assertAll( + { assert process.success }, + { assert path(process.out.log.get(0).get(1)).getText().contains("Input is being processed as unpaired")}, + { assert snapshot(process.out.reads, + process.out.findAll { key, val -> key.startsWith('versions') }).match() } + ) + } + + } + + test("sarscov2 - paired end fastq - fastq") { + + 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) ] + ] + input[1] = [] + """ + } + } + + then { + assertAll( + { assert process.success }, + { assert path(process.out.log.get(0).get(1)).getText().contains("Input is being processed as paired")}, + { assert snapshot(process.out.reads, + process.out.findAll { key, val -> key.startsWith('versions') }).match() } + + ) + } + } + + test("sarscov2 - single end w/ contams [fastq,fasta] - fastq") { + + 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) ] + ] + input[1] = [ + file(params.modules_testdata_base_path + 'genomics/sarscov2/genome/transcriptome.fasta', checkIfExists: true) + ] + """ + } + } + + then { + assertAll( + { assert process.success }, + { assert process.out.reads.get(0).get(1).endsWith("test.trim.fastq.gz") }, + { assert path(process.out.log.get(0).get(1)).getText().contains("Input is being processed as unpaired")}, + { assert snapshot(process.out.findAll { key, val -> key.startsWith('versions') }).match() } + + ) + } + } + + test("sarscov2 - paired end w/ contams [fastq,fasta] - fastq") { + + 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) ] + ] + input[1] = [ + file(params.modules_testdata_base_path + 'genomics/sarscov2/genome/transcriptome.fasta', checkIfExists: true) + ] + """ + } + } + + then { + assertAll( + { assert process.success }, + { assert process.out.reads.get(0).get(1).get(0).endsWith("test.trim_1.fastq.gz") }, + { assert process.out.reads.get(0).get(1).get(1).endsWith("test.trim_2.fastq.gz") }, + { assert path(process.out.log.get(0).get(1)).getText().contains("Input is being processed as paired")}, + { assert snapshot(process.out.findAll { key, val -> key.startsWith('versions') }).match() } + + ) + } + } + + test("sarscov2 - single end fastq - fastq - 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) ] + ] + input[1] = [] + """ + } + } + + then { + assertAll( + { assert process.success }, + { assert snapshot(process.out).match() } + + ) + } + + } + + test("sarscov2 - paired end fastq - fastq - 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) ] + ] + input[1] = [] + """ + } + } + + then { + assertAll( + { assert process.success }, + { assert snapshot(process.out).match() } + ) + } + } + +} diff --git a/modules/nf-core/bbmap/bbduk/tests/main.nf.test.snap b/modules/nf-core/bbmap/bbduk/tests/main.nf.test.snap new file mode 100644 index 0000000..8a1427c --- /dev/null +++ b/modules/nf-core/bbmap/bbduk/tests/main.nf.test.snap @@ -0,0 +1,223 @@ +{ + "sarscov2 - paired end fastq - fastq": { + "content": [ + [ + [ + { + "id": "test", + "single_end": false + }, + [ + "test.trim_1.fastq.gz:md5,4161df271f9bfcd25d5845a1e220dbec", + "test.trim_2.fastq.gz:md5,2ebae722295ea66d84075a3b042e2b42" + ] + ] + ], + { + "versions_bbmap": [ + [ + "BBMAP_BBDUK", + "bbmap", + "39.18" + ] + ] + } + ], + "timestamp": "2026-03-11T13:48:35.546486468", + "meta": { + "nf-test": "0.9.4", + "nextflow": "25.10.4" + } + }, + "sarscov2 - single end w/ contams [fastq,fasta] - fastq": { + "content": [ + { + "versions_bbmap": [ + [ + "BBMAP_BBDUK", + "bbmap", + "39.18" + ] + ] + } + ], + "timestamp": "2026-03-11T13:48:42.244604664", + "meta": { + "nf-test": "0.9.4", + "nextflow": "25.10.4" + } + }, + "sarscov2 - paired end w/ contams [fastq,fasta] - fastq": { + "content": [ + { + "versions_bbmap": [ + [ + "BBMAP_BBDUK", + "bbmap", + "39.18" + ] + ] + } + ], + "timestamp": "2026-03-11T13:48:48.784934284", + "meta": { + "nf-test": "0.9.4", + "nextflow": "25.10.4" + } + }, + "sarscov2 - paired end fastq - fastq - stub": { + "content": [ + { + "0": [ + [ + { + "id": "test", + "single_end": false + }, + [ + "test.trim_1.fastq.gz:md5,68b329da9893e34099c7d8ad5cb9c940", + "test.trim_2.fastq.gz:md5,68b329da9893e34099c7d8ad5cb9c940" + ] + ] + ], + "1": [ + [ + { + "id": "test", + "single_end": false + }, + "test.trim.bbduk.log:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "2": [ + [ + "BBMAP_BBDUK", + "bbmap", + "39.18" + ] + ], + "log": [ + [ + { + "id": "test", + "single_end": false + }, + "test.trim.bbduk.log:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "reads": [ + [ + { + "id": "test", + "single_end": false + }, + [ + "test.trim_1.fastq.gz:md5,68b329da9893e34099c7d8ad5cb9c940", + "test.trim_2.fastq.gz:md5,68b329da9893e34099c7d8ad5cb9c940" + ] + ] + ], + "versions_bbmap": [ + [ + "BBMAP_BBDUK", + "bbmap", + "39.18" + ] + ] + } + ], + "timestamp": "2026-03-11T13:49:01.865562833", + "meta": { + "nf-test": "0.9.4", + "nextflow": "25.10.4" + } + }, + "sarscov2 - single end fastq - fastq - stub": { + "content": [ + { + "0": [ + [ + { + "id": "test", + "single_end": true + }, + "test.trim.fastq.gz:md5,68b329da9893e34099c7d8ad5cb9c940" + ] + ], + "1": [ + [ + { + "id": "test", + "single_end": true + }, + "test.trim.bbduk.log:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "2": [ + [ + "BBMAP_BBDUK", + "bbmap", + "39.18" + ] + ], + "log": [ + [ + { + "id": "test", + "single_end": true + }, + "test.trim.bbduk.log:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "reads": [ + [ + { + "id": "test", + "single_end": true + }, + "test.trim.fastq.gz:md5,68b329da9893e34099c7d8ad5cb9c940" + ] + ], + "versions_bbmap": [ + [ + "BBMAP_BBDUK", + "bbmap", + "39.18" + ] + ] + } + ], + "timestamp": "2026-03-11T13:48:55.283515523", + "meta": { + "nf-test": "0.9.4", + "nextflow": "25.10.4" + } + }, + "sarscov2 - single end fastq - fastq": { + "content": [ + [ + [ + { + "id": "test", + "single_end": true + }, + "test.trim.fastq.gz:md5,4161df271f9bfcd25d5845a1e220dbec" + ] + ], + { + "versions_bbmap": [ + [ + "BBMAP_BBDUK", + "bbmap", + "39.18" + ] + ] + } + ], + "timestamp": "2026-03-11T13:48:28.903217831", + "meta": { + "nf-test": "0.9.4", + "nextflow": "25.10.4" + } + } +} \ No newline at end of file diff --git a/modules/nf-core/bbmap/bbduk/tests/nextflow.config b/modules/nf-core/bbmap/bbduk/tests/nextflow.config new file mode 100644 index 0000000..44c775d --- /dev/null +++ b/modules/nf-core/bbmap/bbduk/tests/nextflow.config @@ -0,0 +1,8 @@ +process { + + withName: BBMAP_BBDUK { + ext.args = 'trimq=10 qtrim=r' + ext.prefix = { "${meta.id}.trim" } + } + +} diff --git a/modules/nf-core/bbmap/clumpify/environment.yml b/modules/nf-core/bbmap/clumpify/environment.yml new file mode 100644 index 0000000..a33ddca --- /dev/null +++ b/modules/nf-core/bbmap/clumpify/environment.yml @@ -0,0 +1,8 @@ +--- +# yaml-language-server: $schema=https://raw.githubusercontent.com/nf-core/modules/master/modules/environment-schema.json +channels: + - conda-forge + - bioconda +dependencies: + - bioconda::bbmap=39.18 + - pigz=2.8 diff --git a/modules/nf-core/bbmap/clumpify/main.nf b/modules/nf-core/bbmap/clumpify/main.nf new file mode 100644 index 0000000..89e1e71 --- /dev/null +++ b/modules/nf-core/bbmap/clumpify/main.nf @@ -0,0 +1,44 @@ +process BBMAP_CLUMPIFY { + tag "$meta.id" + label 'process_single' + label 'process_high_memory' + + conda "${moduleDir}/environment.yml" + container "${ workflow.containerEngine in ['singularity', 'apptainer'] && !task.ext.singularity_pull_docker_container ? + 'https://community-cr-prod.seqera.io/docker/registry/v2/blobs/sha256/5a/5aae5977ff9de3e01ff962dc495bfa23f4304c676446b5fdf2de5c7edfa2dc4e/data' : + 'community.wave.seqera.io/library/bbmap_pigz:07416fe99b090fa9' }" + + input: + tuple val(meta), path(reads) + + output: + tuple val(meta), path('*.fastq.gz'), emit: reads + tuple val(meta), path('*.log') , emit: log + tuple val("${task.process}"), val('bbmap'), eval('bbversion.sh | grep -v "Duplicate cpuset"'), emit: versions_bbmap, topic: versions + + when: + task.ext.when == null || task.ext.when + + script: + def args = task.ext.args ?: '' + def prefix = task.ext.prefix ?: "${meta.id}" + def raw = meta.single_end ? "in=$reads" : "in1=${reads[0]} in2=${reads[1]}" + def clumped = meta.single_end ? "out=${prefix}.clumped.fastq.gz" : "out1=${prefix}_1.clumped.fastq.gz out2=${prefix}_2.clumped.fastq.gz" + """ + clumpify.sh \\ + $raw \\ + $clumped \\ + $args \\ + &> ${prefix}.clumpify.log + """ + + stub: + def prefix = task.ext.prefix ?: "${meta.id}" + def output_command = meta.single_end ? + "echo '' | gzip > ${prefix}.clumped.fastq.gz" : + "echo '' | gzip > ${prefix}_1.clumped.fastq.gz ; echo '' | gzip > ${prefix}_2.clumped.fastq.gz" + """ + touch ${prefix}.clumpify.log + $output_command + """ +} diff --git a/modules/nf-core/bbmap/clumpify/meta.yml b/modules/nf-core/bbmap/clumpify/meta.yml new file mode 100644 index 0000000..478d4bc --- /dev/null +++ b/modules/nf-core/bbmap/clumpify/meta.yml @@ -0,0 +1,79 @@ +name: bbmap_clumpify +description: Create 30% Smaller, Faster Gzipped Fastq Files. And remove + duplicates +keywords: + - clumping fastqs + - smaller fastqs + - deduping + - fastq +tools: + - bbmap: + description: BBMap is a short read aligner, as well as various other + bioinformatic tools. + homepage: https://jgi.doe.gov/data-and-tools/software-tools/bbtools/bb-tools-user-guide/clumpify-guide/ + documentation: https://www.biostars.org/p/225338/ + licence: + - "UC-LBL license (see package)" + identifier: biotools:bbmap +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 ] + - "*.fastq.gz": + type: file + description: The reordered/clumped (and if necessary deduped) fastq + reads + pattern: "*.clumped.fastq.gz" + 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 ] + - "*.log": + type: file + description: Clumpify log file + pattern: "*clumpify.log" + ontologies: [] + versions_bbmap: + - - "${task.process}": + type: string + description: The name of the process + - bbmap: + type: string + description: The name of the tool + - bbversion.sh | grep -v "Duplicate cpuset": + type: eval + description: The expression to obtain the version of the tool +topics: + versions: + - - "${task.process}": + type: string + description: The name of the process + - bbmap: + type: string + description: The name of the tool + - bbversion.sh | grep -v "Duplicate cpuset": + type: eval + description: The expression to obtain the version of the tool +authors: + - "@tamuanand" +maintainers: + - "@tamuanand" diff --git a/modules/nf-core/bbmap/clumpify/tests/main.nf.test b/modules/nf-core/bbmap/clumpify/tests/main.nf.test new file mode 100644 index 0000000..0f5cd36 --- /dev/null +++ b/modules/nf-core/bbmap/clumpify/tests/main.nf.test @@ -0,0 +1,115 @@ + +nextflow_process { + + name "Test Process BBMAP_CLUMPIFY" + script "../main.nf" + process "BBMAP_CLUMPIFY" + + tag "modules" + tag "modules_nfcore" + tag "bbmap" + tag "bbmap/clumpify" + + test("test-bbmap-clumpify-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 { + assertAll( + { assert process.success }, + { assert snapshot(sanitizeOutput(process.out, unstableKeys: ["log"])).match() } + ) + } + } + + test("test-bbmap-clumpify-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 { + assertAll( + { assert process.success }, + { assert snapshot(sanitizeOutput(process.out, unstableKeys: ["log"])).match() } + ) + } + } + + test("test-bbmap-clumpify-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(sanitizeOutput(process.out)).match() } + ) + } + } + + test("test-bbmap-clumpify-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(sanitizeOutput(process.out)).match() } + ) + } + } + +} diff --git a/modules/nf-core/bbmap/clumpify/tests/main.nf.test.snap b/modules/nf-core/bbmap/clumpify/tests/main.nf.test.snap new file mode 100644 index 0000000..ff84405 --- /dev/null +++ b/modules/nf-core/bbmap/clumpify/tests/main.nf.test.snap @@ -0,0 +1,152 @@ +{ + "test-bbmap-clumpify-paired-end": { + "content": [ + { + "log": [ + [ + { + "id": "test", + "single_end": false + }, + "test.clumpify.log" + ] + ], + "reads": [ + [ + { + "id": "test", + "single_end": false + }, + [ + "test_1.clumped.fastq.gz:md5,27e51643262c1ef3905c4be184c3814c", + "test_2.clumped.fastq.gz:md5,c70ab7bbd44d6b6fadd6a1a79ef1648f" + ] + ] + ], + "versions_bbmap": [ + [ + "BBMAP_CLUMPIFY", + "bbmap", + "39.18" + ] + ] + } + ], + "timestamp": "2026-06-12T13:45:10.280022778", + "meta": { + "nf-test": "0.9.5", + "nextflow": "26.04.3" + } + }, + "test-bbmap-clumpify-single-end": { + "content": [ + { + "log": [ + [ + { + "id": "test", + "single_end": true + }, + "test.clumpify.log" + ] + ], + "reads": [ + [ + { + "id": "test", + "single_end": true + }, + "test.clumped.fastq.gz:md5,27e51643262c1ef3905c4be184c3814c" + ] + ], + "versions_bbmap": [ + [ + "BBMAP_CLUMPIFY", + "bbmap", + "39.18" + ] + ] + } + ], + "timestamp": "2026-06-12T13:45:03.743249724", + "meta": { + "nf-test": "0.9.5", + "nextflow": "26.04.3" + } + }, + "test-bbmap-clumpify-single-end - stub": { + "content": [ + { + "log": [ + [ + { + "id": "test", + "single_end": true + }, + "test.clumpify.log:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "reads": [ + [ + { + "id": "test", + "single_end": true + }, + "test.clumped.fastq.gz:md5,68b329da9893e34099c7d8ad5cb9c940" + ] + ], + "versions_bbmap": [ + [ + "BBMAP_CLUMPIFY", + "bbmap", + "39.18" + ] + ] + } + ], + "timestamp": "2026-06-12T13:45:15.652095723", + "meta": { + "nf-test": "0.9.5", + "nextflow": "26.04.3" + } + }, + "test-bbmap-clumpify-paired-end - stub": { + "content": [ + { + "log": [ + [ + { + "id": "test", + "single_end": false + }, + "test.clumpify.log:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "reads": [ + [ + { + "id": "test", + "single_end": false + }, + [ + "test_1.clumped.fastq.gz:md5,68b329da9893e34099c7d8ad5cb9c940", + "test_2.clumped.fastq.gz:md5,68b329da9893e34099c7d8ad5cb9c940" + ] + ] + ], + "versions_bbmap": [ + [ + "BBMAP_CLUMPIFY", + "bbmap", + "39.18" + ] + ] + } + ], + "timestamp": "2026-06-12T13:45:20.572814339", + "meta": { + "nf-test": "0.9.5", + "nextflow": "26.04.3" + } + } +} \ No newline at end of file diff --git a/modules/nf-core/cutadapt/environment.yml b/modules/nf-core/cutadapt/environment.yml new file mode 100644 index 0000000..cf84fbf --- /dev/null +++ b/modules/nf-core/cutadapt/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::cutadapt=5.2 diff --git a/modules/nf-core/cutadapt/main.nf b/modules/nf-core/cutadapt/main.nf new file mode 100644 index 0000000..d92f039 --- /dev/null +++ b/modules/nf-core/cutadapt/main.nf @@ -0,0 +1,47 @@ +process CUTADAPT { + tag "$meta.id" + label 'process_medium' + + conda "${moduleDir}/environment.yml" + container "${workflow.containerEngine in ['singularity', 'apptainer'] && !task.ext.singularity_pull_docker_container ? + 'https://depot.galaxyproject.org/singularity/cutadapt:5.2--py311haab0aaa_0' : + 'quay.io/biocontainers/cutadapt:5.2--py311haab0aaa_0'}" + + input: + tuple val(meta), path(reads) + + output: + tuple val(meta), path('*.trim.fastq.gz'), emit: reads + tuple val(meta), path('*.log') , emit: log + tuple val("${task.process}"), val("cutadapt"), eval('cutadapt --version'), topic: versions, emit: versions_cutadapt + + when: + task.ext.when == null || task.ext.when + + script: + def args = task.ext.args ?: '' + def prefix = task.ext.prefix ?: "${meta.id}" + def trimmed = meta.single_end ? "-o ${prefix}.trim.fastq.gz" : "-o ${prefix}_1.trim.fastq.gz -p ${prefix}_2.trim.fastq.gz" + """ + cutadapt \\ + --cores $task.cpus \\ + $args \\ + $trimmed \\ + $reads \\ + > ${prefix}.cutadapt.log + """ + + stub: + def prefix = task.ext.prefix ?: "${meta.id}" + if (meta.single_end) { + output_command = "echo '' | gzip > ${prefix}.trim.fastq.gz ;" + } + else { + output_command = "echo '' | gzip > ${prefix}_1.trim.fastq.gz ;" + output_command += "echo '' | gzip > ${prefix}_2.trim.fastq.gz ;" + } + """ + ${output_command} + touch ${prefix}.cutadapt.log + """ +} diff --git a/modules/nf-core/cutadapt/meta.yml b/modules/nf-core/cutadapt/meta.yml new file mode 100644 index 0000000..a4717b5 --- /dev/null +++ b/modules/nf-core/cutadapt/meta.yml @@ -0,0 +1,89 @@ +name: cutadapt +description: Removes adapter sequences from sequencing reads +keywords: + - adapter + - primers + - poly-A tails + - trimming + - fastq +tools: + - cutadapt: + description: | + Cutadapt finds and removes adapter sequences, primers, poly-A tails + and other types of unwanted sequence from your high-throughput sequencing reads. + homepage: https://cutadapt.readthedocs.io/en/stable/index.html + documentation: https://github.com/marcelm/cutadapt + doi: 10.14806/ej.17.1.200 + licence: ["MIT"] + identifier: biotools:cutadapt + +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. + pattern: "*.{fq,fastq,fq.gz,fastq.gz}" + ontologies: + - edam: "http://edamontology.org/format_1930" # FASTQ +output: + reads: + - - meta: + type: map + description: | + Groovy Map containing sample information + e.g. [ id:'test', single_end:false ] + - "*.trim.fastq.gz": + type: file + description: The trimmed/modified fastq reads + pattern: "*fastq.gz" + ontologies: + - edam: "http://edamontology.org/format_1930" # FASTQ + - 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 ] + - "*.log": + type: file + description: cutadapt log file + pattern: "*cutadapt.log" + ontologies: + - edam: "http://edamontology.org/format_2330" # Textual format + versions_cutadapt: + - - ${task.process}: + type: string + description: The name of the process + - cutadapt: + type: string + description: The name of the tool + - cutadapt --version: + type: eval + description: The expression to obtain the version of the tool + +topics: + versions: + - - ${task.process}: + type: string + description: The name of the process + - cutadapt: + type: string + description: The name of the tool + - cutadapt --version: + type: eval + description: The expression to obtain the version of the tool + +authors: + - "@drpatelh" + - "@kevinmenden" +maintainers: + - "@drpatelh" + - "@kevinmenden" + - "@vagkaratzas" diff --git a/modules/nf-core/cutadapt/tests/main.nf.test b/modules/nf-core/cutadapt/tests/main.nf.test new file mode 100644 index 0000000..0e3f7fe --- /dev/null +++ b/modules/nf-core/cutadapt/tests/main.nf.test @@ -0,0 +1,93 @@ +nextflow_process { + + name "Test Process CUTADAPT" + script "../main.nf" + process "CUTADAPT" + + tag "modules" + tag "modules_nfcore" + tag "cutadapt" + + test("sarscov2 - fastq - single-end") { + + when { + process { + """ + input[0] = [ + [ 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.reads, + file(process.out.log[0][1]).readLines()[1..10], // python versions differ in the default conda env and container + process.out.findAll { key, val -> key.startsWith("versions")} + ).match() } + ) + } + } + + test("sarscov2 - fastq - paired-end") { + + when { + process { + """ + input[0] = [ + [ id: 'test', single_end:false ], + [ + 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.reads, + file(process.out.log[0][1]).readLines()[1..14], // python versions differ in the default conda env and container + process.out.findAll { key, val -> key.startsWith("versions")} + ).match() } + ) + } + } + + test("sarscov2 - fastq - single-end - stub") { + + options "-stub" + + when { + process { + """ + input[0] = [ + [ id: 'test', single_end:true ], + [ + file(params.modules_testdata_base_path + 'genomics/sarscov2/illumina/fastq/test_1.fastq.gz', checkIfExists: true) + ] + ] + """ + } + } + + then { + assertAll ( + { assert snapshot( + process.out.reads, + process.out.log, + process.out.findAll { key, val -> key.startsWith("versions")} + ).match() } + ) + } + } +} diff --git a/modules/nf-core/cutadapt/tests/main.nf.test.snap b/modules/nf-core/cutadapt/tests/main.nf.test.snap new file mode 100644 index 0000000..baf5c4a --- /dev/null +++ b/modules/nf-core/cutadapt/tests/main.nf.test.snap @@ -0,0 +1,123 @@ +{ + "sarscov2 - fastq - single-end - stub": { + "content": [ + [ + [ + { + "id": "test", + "single_end": true + }, + "test.trim.fastq.gz:md5,68b329da9893e34099c7d8ad5cb9c940" + ] + ], + [ + [ + { + "id": "test", + "single_end": true + }, + "test.cutadapt.log:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + { + "versions_cutadapt": [ + [ + "CUTADAPT", + "cutadapt", + "5.2" + ] + ] + } + ], + "meta": { + "nf-test": "0.9.3", + "nextflow": "25.10.2" + }, + "timestamp": "2025-12-11T12:48:07.964157954" + }, + "sarscov2 - fastq - paired-end": { + "content": [ + [ + [ + { + "id": "test", + "single_end": false + }, + [ + "test_1.trim.fastq.gz:md5,4161df271f9bfcd25d5845a1e220dbec", + "test_2.trim.fastq.gz:md5,2ebae722295ea66d84075a3b042e2b42" + ] + ] + ], + [ + "Command line parameters: --cores 2 -o test_1.trim.fastq.gz -p test_2.trim.fastq.gz test_1.fastq.gz test_2.fastq.gz", + "Processing paired-end reads on 2 cores ...", + "", + "=== Summary ===", + "", + "Total read pairs processed: 100", + "Pairs written (passing filters): 100 (100.0%)", + "", + "Total basepairs processed: 27,645 bp", + " Read 1: 13,897 bp", + " Read 2: 13,748 bp", + "Total written (filtered): 27,645 bp (100.0%)", + " Read 1: 13,897 bp", + " Read 2: 13,748 bp" + ], + { + "versions_cutadapt": [ + [ + "CUTADAPT", + "cutadapt", + "5.2" + ] + ] + } + ], + "meta": { + "nf-test": "0.9.3", + "nextflow": "25.10.2" + }, + "timestamp": "2025-12-11T14:12:08.287985982" + }, + "sarscov2 - fastq - single-end": { + "content": [ + [ + [ + { + "id": "test", + "single_end": true + }, + "test.trim.fastq.gz:md5,4161df271f9bfcd25d5845a1e220dbec" + ] + ], + [ + "Command line parameters: --cores 2 -o test.trim.fastq.gz test_1.fastq.gz", + "Processing single-end reads on 2 cores ...", + "", + "=== Summary ===", + "", + "Total reads processed: 100", + "Reads written (passing filters): 100 (100.0%)", + "", + "Total basepairs processed: 13,897 bp", + "Total written (filtered): 13,897 bp (100.0%)" + ], + { + "versions_cutadapt": [ + [ + "CUTADAPT", + "cutadapt", + "5.2" + ] + ] + } + ], + "meta": { + "nf-test": "0.9.3", + "nextflow": "25.10.2" + }, + "timestamp": "2025-12-11T14:06:44.706447277" + } +} \ No newline at end of file diff --git a/modules/nf-core/cutadapt/tests/nextflow.config b/modules/nf-core/cutadapt/tests/nextflow.config new file mode 100644 index 0000000..6c3b425 --- /dev/null +++ b/modules/nf-core/cutadapt/tests/nextflow.config @@ -0,0 +1,7 @@ +process { + + withName: CUTADAPT { + ext.args = '-q 25' + } + +} diff --git a/modules/nf-core/deacon/filter/environment.yml b/modules/nf-core/deacon/filter/environment.yml new file mode 100644 index 0000000..f1944dd --- /dev/null +++ b/modules/nf-core/deacon/filter/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::deacon=0.15.0 diff --git a/modules/nf-core/deacon/filter/main.nf b/modules/nf-core/deacon/filter/main.nf new file mode 100644 index 0000000..bbe3dcd --- /dev/null +++ b/modules/nf-core/deacon/filter/main.nf @@ -0,0 +1,45 @@ +process DEACON_FILTER { + tag "$meta.id" + label 'process_medium' + + conda "${moduleDir}/environment.yml" + container "${ workflow.containerEngine in ['singularity', 'apptainer'] && !task.ext.singularity_pull_docker_container ? + 'https://depot.galaxyproject.org/singularity/deacon:0.15.0--hdd79491_0': + 'quay.io/biocontainers/deacon:0.15.0--hdd79491_0' }" + + input: + tuple val(meta), path(index), path(reads) + + output: + tuple val(meta), path("${prefix}*.fq.gz"), emit: fastq_filtered + tuple val(meta), path("${prefix}.json") , emit: log + tuple val("${task.process}"), val('deacon'), eval('deacon --version | head -n1 | sed "s/deacon //g"'), emit: versions_deacon, topic: versions + + when: + task.ext.when == null || task.ext.when + + script: + def args = task.ext.args ?: '' + prefix = task.ext.prefix ?: "${meta.id}" + def read_type = (reads instanceof List) ? "-o ${prefix}_1.fq -O ${prefix}_2.fq" : "> ${prefix}.fq" // deacon's automatic compression does not work + if (!(reads instanceof List) && "${reads}" == "${prefix}.fq.gz") error "Input and output names are the same, set prefix in module configuration to disambiguate!" + """ + deacon \\ + filter \\ + --threads ${task.cpus} \\ + $args \\ + --summary ${prefix}.json \\ + -d $index \\ + $reads \\ + ${read_type} + + gzip -f ${prefix}*.fq + """ + + stub: + prefix = task.ext.prefix ?: "${meta.id}" + """ + echo | gzip > '${prefix}.fq.gz' + touch ${prefix}.json + """ +} diff --git a/modules/nf-core/deacon/filter/meta.yml b/modules/nf-core/deacon/filter/meta.yml new file mode 100644 index 0000000..e1cdd3b --- /dev/null +++ b/modules/nf-core/deacon/filter/meta.yml @@ -0,0 +1,96 @@ +# yaml-language-server: $schema=https://raw.githubusercontent.com/nf-core/modules/master/modules/meta-schema.json +name: "deacon_filter" +description: Filter DNA sequences using index of reference genome +keywords: + - filter + - index + - fasta + - fastq + - genome + - reference + - minimizer + - decontamination +tools: + - "deacon": + description: "Fast alignment-free sequence filter" + homepage: "https://github.com/bede/deacon" + documentation: "https://github.com/bede/deacon#readme" + tool_dev_url: "https://github.com/bede/deacon" + doi: "10.1093/bioinformatics/btae004" + licence: ["MIT"] + identifier: "biotools:deacon" + +input: + - - meta: + type: map + description: | + Groovy Map containing reference information. + e.g. [ id:'test', single_end:false ] + - index: + type: file + description: Deacon minimizer index file + pattern: "*.idx" + ontologies: + - edam: "http://edamontology.org/data_3210" # Genome index + - reads: + type: file + description: List of input FastQ files of size 1 and 2 for single-end and paired-end data, respectively. + pattern: "*.{fastq,fastq.gz,fqs,fqs.gz,fq,fq.gz}" + ontologies: + - edam: "http://edamontology.org/format_1930" # FASTQ + +output: + fastq_filtered: + - - meta: + type: map + description: | + Groovy Map containing reference information. + e.g. [ id:'test', single_end:false ] + - "${prefix}*.fq.gz": + type: file + description: List of output filtered FastQ files of size 1 and 2, for single-end and paired-end data, respectively. + pattern: "*.fq.gz" + ontologies: + - edam: "http://edamontology.org/format_1930" # FASTQ + log: + - - meta: + type: map + description: | + Groovy Map containing reference information. + e.g. [ id:'test', single_end:false ] + - "${prefix}.json": + type: file + description: JSON file containing summary of results. + pattern: "*.json" + ontologies: + - edam: "http://edamontology.org/format_3464" # JSON + versions_deacon: + - - ${task.process}: + type: string + description: The name of the process + - deacon: + type: string + description: The name of the tool + - deacon --version | head -n1 | sed "s/deacon //g": + type: eval + description: The expression to obtain the version of the tool + +topics: + versions: + - - ${task.process}: + type: string + description: The name of the process + - deacon: + type: string + description: The name of the tool + - deacon --version | head -n1 | sed "s/deacon //g": + type: eval + description: The expression to obtain the version of the tool + +authors: + - "@Baksic-Ivan" + - "@Omer0191" +maintainers: + - "@Baksic-Ivan" + - "@Omer0191" + - "@vagkaratzas" diff --git a/modules/nf-core/deacon/filter/tests/main.nf.test b/modules/nf-core/deacon/filter/tests/main.nf.test new file mode 100644 index 0000000..40c1e62 --- /dev/null +++ b/modules/nf-core/deacon/filter/tests/main.nf.test @@ -0,0 +1,143 @@ +nextflow_process { + + name "Test Process DEACON_FILTER" + script "../main.nf" + process "DEACON_FILTER" + + tag "modules" + tag "modules_nfcore" + tag "deacon" + tag "deacon/filter" + tag "deacon/index" + + test("sarscov2 - fastq - single-end") { + + setup { + run("DEACON_INDEX") { + script "../../index/main.nf" + process { + """ + input[0] = [ + [ id:'test', single_end:true ], + file(params.modules_testdata_base_path + 'genomics/sarscov2/genome/genome.fasta', checkIfExists: true) + ] + """ + } + } + } + + when { + process { + """ + filter_fastq_ch = Channel.of( + [ + [ id:'test', single_end:true ], + file(params.modules_testdata_base_path + 'genomics/sarscov2/illumina/fastq/test_1.fastq.gz', checkIfExists: true) + ] + ) + input[0] = DEACON_INDEX.out.index.join(filter_fastq_ch) + """ + } + } + + then { + assertAll( + { assert process.success }, + { assert snapshot( + file(process.out.log[0][1]).name, + file(process.out.log[0][1]).readLines()[15..16], // seqs_in and seqs_out + process.out.fastq_filtered, + process.out.findAll { key, val -> key.startsWith("versions") } + ).match()} + ) + } + + } + + test("sarscov2 - fastq - paired-end") { + + setup { + run("DEACON_INDEX") { + script "../../index/main.nf" + process { + """ + input[0] = [ + [ id:'test', single_end:false ], // single_end:false to join paired reads channel + file(params.modules_testdata_base_path + 'genomics/sarscov2/genome/genome.fasta', checkIfExists: true) + ] + """ + } + } + } + + when { + process { + """ + filter_fastq_ch = Channel.of( + [ + [ id:'test', single_end:false ], + [ + 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[0] = DEACON_INDEX.out.index.join(filter_fastq_ch) + """ + } + } + + then { + assertAll( + { assert process.success }, + { assert snapshot( + file(process.out.log[0][1]).name, + process.out.fastq_filtered, + process.out.findAll { key, val -> key.startsWith("versions") } + ).match()} + ) + } + + } + + test("sarscov2 - fastq - single-end - stub") { + + options "-stub" + + setup { + run("DEACON_INDEX") { + script "../../index/main.nf" + process { + """ + input[0] = [ + [ id:'test', single_end:true ], + file(params.modules_testdata_base_path + 'genomics/sarscov2/genome/genome.fasta', checkIfExists: true) + ] + """ + } + } + } + + when { + process { + """ + filter_fastq_ch = Channel.of( + [ + [ id:'test', single_end:true ], + file(params.modules_testdata_base_path + 'genomics/sarscov2/illumina/fastq/test_1.fastq.gz', checkIfExists: true), + ] + ) + input[0] = DEACON_INDEX.out.index.join(filter_fastq_ch) + """ + } + } + + then { + assertAll( + { assert process.success }, + { assert snapshot(sanitizeOutput(process.out)).match() } + ) + } + } + +} diff --git a/modules/nf-core/deacon/filter/tests/main.nf.test.snap b/modules/nf-core/deacon/filter/tests/main.nf.test.snap new file mode 100644 index 0000000..7cfe72f --- /dev/null +++ b/modules/nf-core/deacon/filter/tests/main.nf.test.snap @@ -0,0 +1,101 @@ +{ + "sarscov2 - fastq - single-end - stub": { + "content": [ + { + "fastq_filtered": [ + [ + { + "id": "test", + "single_end": true + }, + "test.fq.gz:md5,68b329da9893e34099c7d8ad5cb9c940" + ] + ], + "log": [ + [ + { + "id": "test", + "single_end": true + }, + "test.json:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "versions_deacon": [ + [ + "DEACON_FILTER", + "deacon", + "0.15.0" + ] + ] + } + ], + "timestamp": "2026-06-25T08:35:57.335088038", + "meta": { + "nf-test": "0.9.5", + "nextflow": "26.04.3" + } + }, + "sarscov2 - fastq - paired-end": { + "content": [ + "test.json", + [ + [ + { + "id": "test", + "single_end": false + }, + [ + "test_1.fq.gz:md5,bf1aa22249e7e2b462c6be1a53ac55e9", + "test_2.fq.gz:md5,d3106ad7395c78a212e6214177d5c054" + ] + ] + ], + { + "versions_deacon": [ + [ + "DEACON_FILTER", + "deacon", + "0.15.0" + ] + ] + } + ], + "timestamp": "2026-06-24T15:52:29.181295815", + "meta": { + "nf-test": "0.9.5", + "nextflow": "26.04.3" + } + }, + "sarscov2 - fastq - single-end": { + "content": [ + "test.json", + [ + " \"seqs_in\": 100,", + " \"seqs_out\": 3," + ], + [ + [ + { + "id": "test", + "single_end": true + }, + "test.fq.gz:md5,2250e99ec61fca0e28e2dcb5124d1a16" + ] + ], + { + "versions_deacon": [ + [ + "DEACON_FILTER", + "deacon", + "0.15.0" + ] + ] + } + ], + "timestamp": "2026-06-24T15:52:21.504406613", + "meta": { + "nf-test": "0.9.5", + "nextflow": "26.04.3" + } + } +} \ No newline at end of file diff --git a/modules/nf-core/deacon/index/environment.yml b/modules/nf-core/deacon/index/environment.yml new file mode 100644 index 0000000..f1944dd --- /dev/null +++ b/modules/nf-core/deacon/index/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::deacon=0.15.0 diff --git a/modules/nf-core/deacon/index/main.nf b/modules/nf-core/deacon/index/main.nf new file mode 100644 index 0000000..5c049c1 --- /dev/null +++ b/modules/nf-core/deacon/index/main.nf @@ -0,0 +1,37 @@ +process DEACON_INDEX { + tag "$fasta" + label 'process_low' + + conda "${moduleDir}/environment.yml" + container "${ workflow.containerEngine in ['singularity', 'apptainer'] && !task.ext.singularity_pull_docker_container ? + 'https://depot.galaxyproject.org/singularity/deacon:0.15.0--hdd79491_0': + 'quay.io/biocontainers/deacon:0.15.0--hdd79491_0' }" + + input: + tuple val(meta), path(fasta) + + output: + tuple val(meta), path("*.idx"), emit: index + tuple val("${task.process}"), val('deacon'), eval('deacon --version | head -n1 | sed "s/deacon //g"'), emit: versions_deacon, topic: versions + + when: + task.ext.when == null || task.ext.when + + script: + def args = task.ext.args ?: '' + def prefix = task.ext.prefix ?: "${meta.id}" + """ + deacon \\ + index \\ + build \\ + --threads ${task.cpus} \\ + $args \\ + $fasta > ${prefix}.idx + """ + + stub: + def prefix = task.ext.prefix ?: "${fasta.baseName}" + """ + touch ${prefix}.idx + """ +} diff --git a/modules/nf-core/deacon/index/meta.yml b/modules/nf-core/deacon/index/meta.yml new file mode 100644 index 0000000..34aecef --- /dev/null +++ b/modules/nf-core/deacon/index/meta.yml @@ -0,0 +1,74 @@ +# yaml-language-server: $schema=https://raw.githubusercontent.com/nf-core/modules/master/modules/meta-schema.json +name: "deacon_index" +description: Create deacon index for reference genome +keywords: + - index + - fasta + - genome + - reference + - minimizer + - decontamination +tools: + - "deacon": + description: "Fast alignment-free sequence filter" + homepage: "https://github.com/bede/deacon" + documentation: "https://github.com/bede/deacon#readme" + tool_dev_url: "https://github.com/bede/deacon" + doi: "10.1093/bioinformatics/btae004" + licence: ["MIT"] + identifier: "biotools:deacon" + +input: + - - meta: + type: map + description: | + Groovy Map containing reference information. + e.g. [ id:'test', single_end:false ] + - fasta: + type: file + description: Input genome fasta file + pattern: "*.{fasta,fasta.gz,fas,fas.gz,fa,fa.gz,fna,fna.gz}" + ontologies: + - edam: "http://edamontology.org/data_2044" # Sequence + - edam: "http://edamontology.org/format_1929" # FASTA + +output: + index: + - - meta: + type: map + description: | + Groovy Map containing reference information. + e.g. [ id:'test', single_end:false ] + - "*.idx": + type: file + description: Deacon minimizer index file + pattern: "*.idx" + ontologies: + - edam: "http://edamontology.org/data_3210" # Genome index + versions_deacon: + - - ${task.process}: + type: string + description: The name of the process + - deacon: + type: string + description: The name of the tool + - deacon --version | head -n1 | sed "s/deacon //g": + type: eval + description: The expression to obtain the version of the tool + +topics: + versions: + - - ${task.process}: + type: string + description: The name of the process + - deacon: + type: string + description: The name of the tool + - deacon --version | head -n1 | sed "s/deacon //g": + type: eval + description: The expression to obtain the version of the tool +authors: + - "@mberacochea" +maintainers: + - "@mberacochea" + - "@pmoris" diff --git a/modules/nf-core/deacon/index/tests/main.nf.test b/modules/nf-core/deacon/index/tests/main.nf.test new file mode 100644 index 0000000..506a859 --- /dev/null +++ b/modules/nf-core/deacon/index/tests/main.nf.test @@ -0,0 +1,78 @@ +nextflow_process { + + name "Test Process DEACON_INDEX" + script "../main.nf" + process "DEACON_INDEX" + + tag "modules" + tag "modules_nfcore" + tag "deacon" + tag "deacon/index" + + test("index sarscov2 - 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(sanitizeOutput(process.out)).match() } + ) + } + + } + + test("index sarscov2 - fasta gzipped") { + + when { + process { + """ + input[0] = [ + [ id:'test'], + file(params.modules_testdata_base_path + 'genomics/sarscov2/genome/genome.fasta.gz', checkIfExists: true) + ] + """ + } + } + + then { + assertAll( + { assert process.success }, + { assert snapshot(sanitizeOutput(process.out)).match() } + ) + } + + } + + test("index sarscov2 - 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(sanitizeOutput(process.out)).match() } + ) + } + } +} diff --git a/modules/nf-core/deacon/index/tests/main.nf.test.snap b/modules/nf-core/deacon/index/tests/main.nf.test.snap new file mode 100644 index 0000000..46fd189 --- /dev/null +++ b/modules/nf-core/deacon/index/tests/main.nf.test.snap @@ -0,0 +1,80 @@ +{ + "index sarscov2 - fasta": { + "content": [ + { + "index": [ + [ + { + "id": "test" + }, + "test.idx:md5,84e4985c91800686db9c9dca28fabd1a" + ] + ], + "versions_deacon": [ + [ + "DEACON_INDEX", + "deacon", + "0.15.0" + ] + ] + } + ], + "timestamp": "2026-06-25T08:36:03.824580444", + "meta": { + "nf-test": "0.9.5", + "nextflow": "26.04.3" + } + }, + "index sarscov2 - fasta - stub": { + "content": [ + { + "index": [ + [ + { + "id": "test" + }, + "genome.idx:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "versions_deacon": [ + [ + "DEACON_INDEX", + "deacon", + "0.15.0" + ] + ] + } + ], + "timestamp": "2026-06-25T08:36:16.88783127", + "meta": { + "nf-test": "0.9.5", + "nextflow": "26.04.3" + } + }, + "index sarscov2 - fasta gzipped": { + "content": [ + { + "index": [ + [ + { + "id": "test" + }, + "test.idx:md5,84e4985c91800686db9c9dca28fabd1a" + ] + ], + "versions_deacon": [ + [ + "DEACON_INDEX", + "deacon", + "0.15.0" + ] + ] + } + ], + "timestamp": "2026-06-25T08:36:10.326633427", + "meta": { + "nf-test": "0.9.5", + "nextflow": "26.04.3" + } + } +} \ No newline at end of file diff --git a/modules/nf-core/fastp/environment.yml b/modules/nf-core/fastp/environment.yml new file mode 100644 index 0000000..bc42bd4 --- /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.3.6 diff --git a/modules/nf-core/fastp/main.nf b/modules/nf-core/fastp/main.nf new file mode 100644 index 0000000..18e4e77 --- /dev/null +++ b/modules/nf-core/fastp/main.nf @@ -0,0 +1,104 @@ +process FASTP { + tag "$meta.id" + label 'process_medium' + + conda "${moduleDir}/environment.yml" + container "${ workflow.containerEngine in ['singularity', 'apptainer'] && !task.ext.singularity_pull_docker_container +? 'https://community-cr-prod.seqera.io/docker/registry/v2/blobs/sha256/d0/d013aad5427d824afe472e6607ea47685ff0181f1fb09e52a179e0ec39e43e88/data' +: 'community.wave.seqera.io/library/fastp:1.3.6--4df8d6c11b471bde' }" + + 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 + 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 + + 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}_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') ) { + """ + [ ! -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 + """ + } 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) + """ + } else { + def merge_fastq = save_merged ? "-m --merged_out ${prefix}.merged.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}_R1.fastq.gz \\ + --in2 ${prefix}_R2.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) + """ + } + + 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}_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}_R1.fail.fastq.gz ; echo '' | gzip > ${prefix}_R2.fail.fastq.gz" + """ + $touch_reads + $touch_fail_fastq + $touch_merged + touch "${prefix}.fastp.json" + touch "${prefix}.fastp.html" + touch "${prefix}.fastp.log" + """ +} diff --git a/modules/nf-core/fastp/meta.yml b/modules/nf-core/fastp/meta.yml new file mode 100644 index 0000000..893ea04 --- /dev/null +++ b/modules/nf-core/fastp/meta.yml @@ -0,0 +1,172 @@ +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 + publication: + author: "Chen S., Zhou Y., Chen Y. & Gu J." + year: 2018 + title: "fastp: an ultra-fast all-in-one FASTQ preprocessor" + source: "Bioinformatics" + 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_1930 # FASTQ + - 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_1930 # FASTQ + - 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_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: + - - "${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" +containers: + docker: + linux/arm64: + name: community.wave.seqera.io/library/fastp:1.3.6--95b49074d3fe9875 + build_id: bd-95b49074d3fe9875_1 + scan_id: sc-0b07e24d0edcdef4_1 + linux/amd64: + name: community.wave.seqera.io/library/fastp:1.3.6--4df8d6c11b471bde + build_id: bd-4df8d6c11b471bde_1 + scan_id: sc-23500527233b592b_1 + singularity: + linux/amd64: + name: oras://community.wave.seqera.io/library/fastp:1.3.6--63f52bbe4fa9aee1 + build_id: bd-63f52bbe4fa9aee1_1 + https: https://community-cr-prod.seqera.io/docker/registry/v2/blobs/sha256/d0/d013aad5427d824afe472e6607ea47685ff0181f1fb09e52a179e0ec39e43e88/data + linux/arm64: + name: oras://community.wave.seqera.io/library/fastp:1.3.6--469416be288311d1 + build_id: bd-469416be288311d1_1 + https: https://community-cr-prod.seqera.io/docker/registry/v2/blobs/sha256/42/42ea3a248c30a6eb0ab5d5ca35ce8b50aab6ca75e982d43420779214517a516b/data + conda: + linux/amd64: + lock_file: modules/nf-core/fastp/.conda-lock/linux_amd64-bd-4df8d6c11b471bde_1.txt + linux/arm64: + lock_file: modules/nf-core/fastp/.conda-lock/linux_arm64-bd-95b49074d3fe9875_1.txt 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 0000000..b790157 --- /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.findAll { key, val -> key.startsWith('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.findAll { key, val -> key.startsWith('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.findAll { key, val -> key.startsWith('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.findAll { key, val -> key.startsWith('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.findAll { key, val -> key.startsWith('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.findAll { key, val -> key.startsWith('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.findAll { key, val -> key.startsWith('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.findAll { key, val -> key.startsWith('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.findAll { key, val -> key.startsWith('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 0000000..77b9ec2 --- /dev/null +++ b/modules/nf-core/fastp/tests/main.nf.test.snap @@ -0,0 +1,1376 @@ +{ + "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": [ + [ + "FASTP", + "fastp", + "1.3.6" + ] + ], + "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_fastp": [ + [ + "FASTP", + "fastp", + "1.3.6" + ] + ] + } + ], + "timestamp": "2026-07-31T10:06:56.864151", + "meta": { + "nf-test": "0.9.5", + "nextflow": "26.04.6" + } + }, + "test_fastp_paired_end": { + "content": [ + [ + [ + { + "id": "test", + "single_end": false + }, + [ + "test_R1.fastp.fastq.gz:md5,67b2bbae47f073e05a97a9c2edce23c7", + "test_R2.fastp.fastq.gz:md5,25cbdca08e2083dbd4f0502de6b62f39" + ] + ] + ], + [ + + ], + [ + + ], + { + "versions_fastp": [ + [ + "FASTP", + "fastp", + "1.3.6" + ] + ] + } + ], + "timestamp": "2026-07-31T10:06:04.967568", + "meta": { + "nf-test": "0.9.5", + "nextflow": "26.04.6" + } + }, + "test_fastp_paired_end_merged_adapterlist": { + "content": [ + [ + [ + { + "id": "test", + "single_end": false + }, + [ + "test_R1.fastp.fastq.gz:md5,54b726a55e992a869fd3fa778afe1672", + "test_R2.fastp.fastq.gz:md5,29d3b33b869f7b63417b8ff07bb128ba" + ] + ] + ], + [ + + ], + [ + [ + { + "id": "test", + "single_end": false + }, + "test.merged.fastq.gz:md5,c873bb1ab3fa859dcc47306465e749d5" + ] + ], + { + "versions_fastp": [ + [ + "FASTP", + "fastp", + "1.3.6" + ] + ] + } + ], + "timestamp": "2026-07-31T10:06:24.635687", + "meta": { + "nf-test": "0.9.5", + "nextflow": "26.04.6" + } + }, + "test_fastp_single_end_qc_only": { + "content": [ + [ + + ], + [ + + ], + [ + + ], + [ + + ], + [ + + ], + [ + + ], + { + "versions_fastp": [ + [ + "FASTP", + "fastp", + "1.3.6" + ] + ] + } + ], + "timestamp": "2026-07-31T10:06:28.028482", + "meta": { + "nf-test": "0.9.5", + "nextflow": "26.04.6" + } + }, + "test_fastp_paired_end_trim_fail": { + "content": [ + [ + [ + { + "id": "test", + "single_end": false + }, + [ + "test_R1.fastp.fastq.gz:md5,6ff32a64c5188b9a9192be1398c262c7", + "test_R2.fastp.fastq.gz:md5,db0cb7c9977e94ac2b4b446ebd017a8a" + ] + ] + ], + [ + [ + { + "id": "test", + "single_end": false + }, + [ + "test.paired.fail.fastq.gz:md5,409b687c734cedd7a1fec14d316e1366", + "test_R1.fail.fastq.gz:md5,4f273cf3159c13f79e8ffae12f5661f6", + "test_R2.fail.fastq.gz:md5,f97b9edefb5649aab661fbc9e71fc995" + ] + ] + ], + [ + + ], + { + "versions_fastp": [ + [ + "FASTP", + "fastp", + "1.3.6" + ] + ] + } + ], + "timestamp": "2026-07-31T10:06:17.244064", + "meta": { + "nf-test": "0.9.5", + "nextflow": "26.04.6" + } + }, + "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": [ + [ + "FASTP", + "fastp", + "1.3.6" + ] + ], + "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_fastp": [ + [ + "FASTP", + "fastp", + "1.3.6" + ] + ] + } + ], + "timestamp": "2026-07-31T10:06:40.981576", + "meta": { + "nf-test": "0.9.5", + "nextflow": "26.04.6" + } + }, + "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": [ + [ + "FASTP", + "fastp", + "1.3.6" + ] + ], + "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_fastp": [ + [ + "FASTP", + "fastp", + "1.3.6" + ] + ] + } + ], + "timestamp": "2026-07-31T10:06:34.535562", + "meta": { + "nf-test": "0.9.5", + "nextflow": "26.04.6" + } + }, + "test_fastp_paired_end_merged_adapterlist - stub": { + "content": [ + { + "0": [ + [ + { + "id": "test", + "single_end": false + }, + [ + "test_R1.fastp.fastq.gz:md5,68b329da9893e34099c7d8ad5cb9c940", + "test_R2.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": [ + [ + "FASTP", + "fastp", + "1.3.6" + ] + ], + "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_R1.fastp.fastq.gz:md5,68b329da9893e34099c7d8ad5cb9c940", + "test_R2.fastp.fastq.gz:md5,68b329da9893e34099c7d8ad5cb9c940" + ] + ] + ], + "reads_fail": [ + + ], + "reads_merged": [ + [ + { + "id": "test", + "single_end": false + }, + "test.merged.fastq.gz:md5,68b329da9893e34099c7d8ad5cb9c940" + ] + ], + "versions_fastp": [ + [ + "FASTP", + "fastp", + "1.3.6" + ] + ] + } + ], + "timestamp": "2026-07-31T10:06:53.745838", + "meta": { + "nf-test": "0.9.5", + "nextflow": "26.04.6" + } + }, + "test_fastp_paired_end_merged - stub": { + "content": [ + { + "0": [ + [ + { + "id": "test", + "single_end": false + }, + [ + "test_R1.fastp.fastq.gz:md5,68b329da9893e34099c7d8ad5cb9c940", + "test_R2.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": [ + [ + "FASTP", + "fastp", + "1.3.6" + ] + ], + "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_R1.fastp.fastq.gz:md5,68b329da9893e34099c7d8ad5cb9c940", + "test_R2.fastp.fastq.gz:md5,68b329da9893e34099c7d8ad5cb9c940" + ] + ] + ], + "reads_fail": [ + + ], + "reads_merged": [ + [ + { + "id": "test", + "single_end": false + }, + "test.merged.fastq.gz:md5,68b329da9893e34099c7d8ad5cb9c940" + ] + ], + "versions_fastp": [ + [ + "FASTP", + "fastp", + "1.3.6" + ] + ] + } + ], + "timestamp": "2026-07-31T10:06:50.440269", + "meta": { + "nf-test": "0.9.5", + "nextflow": "26.04.6" + } + }, + "test_fastp_paired_end_merged": { + "content": [ + [ + [ + { + "id": "test", + "single_end": false + }, + [ + "test_R1.fastp.fastq.gz:md5,54b726a55e992a869fd3fa778afe1672", + "test_R2.fastp.fastq.gz:md5,29d3b33b869f7b63417b8ff07bb128ba" + ] + ] + ], + [ + + ], + [ + [ + { + "id": "test", + "single_end": false + }, + "test.merged.fastq.gz:md5,c873bb1ab3fa859dcc47306465e749d5" + ] + ], + { + "versions_fastp": [ + [ + "FASTP", + "fastp", + "1.3.6" + ] + ] + } + ], + "timestamp": "2026-07-31T10:06:21.026574", + "meta": { + "nf-test": "0.9.5", + "nextflow": "26.04.6" + } + }, + "test_fastp_paired_end - stub": { + "content": [ + { + "0": [ + [ + { + "id": "test", + "single_end": false + }, + [ + "test_R1.fastp.fastq.gz:md5,68b329da9893e34099c7d8ad5cb9c940", + "test_R2.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": [ + [ + "FASTP", + "fastp", + "1.3.6" + ] + ], + "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_R1.fastp.fastq.gz:md5,68b329da9893e34099c7d8ad5cb9c940", + "test_R2.fastp.fastq.gz:md5,68b329da9893e34099c7d8ad5cb9c940" + ] + ] + ], + "reads_fail": [ + + ], + "reads_merged": [ + + ], + "versions_fastp": [ + [ + "FASTP", + "fastp", + "1.3.6" + ] + ] + } + ], + "timestamp": "2026-07-31T10:06:37.802435", + "meta": { + "nf-test": "0.9.5", + "nextflow": "26.04.6" + } + }, + "test_fastp_single_end": { + "content": [ + [ + [ + { + "id": "test", + "single_end": true + }, + "test.fastp.fastq.gz:md5,67b2bbae47f073e05a97a9c2edce23c7" + ] + ], + [ + + ], + [ + + ], + { + "versions_fastp": [ + [ + "FASTP", + "fastp", + "1.3.6" + ] + ] + } + ], + "timestamp": "2026-07-31T10:06:01.184475", + "meta": { + "nf-test": "0.9.5", + "nextflow": "26.04.6" + } + }, + "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": [ + [ + "FASTP", + "fastp", + "1.3.6" + ] + ], + "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_fastp": [ + [ + "FASTP", + "fastp", + "1.3.6" + ] + ] + } + ], + "timestamp": "2026-07-31T10:06:44.158414", + "meta": { + "nf-test": "0.9.5", + "nextflow": "26.04.6" + } + }, + "test_fastp_paired_end_trim_fail - stub": { + "content": [ + { + "0": [ + [ + { + "id": "test", + "single_end": false + }, + [ + "test_R1.fastp.fastq.gz:md5,68b329da9893e34099c7d8ad5cb9c940", + "test_R2.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_R1.fail.fastq.gz:md5,68b329da9893e34099c7d8ad5cb9c940", + "test_R2.fail.fastq.gz:md5,68b329da9893e34099c7d8ad5cb9c940" + ] + ] + ], + "5": [ + + ], + "6": [ + [ + "FASTP", + "fastp", + "1.3.6" + ] + ], + "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_R1.fastp.fastq.gz:md5,68b329da9893e34099c7d8ad5cb9c940", + "test_R2.fastp.fastq.gz:md5,68b329da9893e34099c7d8ad5cb9c940" + ] + ] + ], + "reads_fail": [ + [ + { + "id": "test", + "single_end": false + }, + [ + "test.paired.fail.fastq.gz:md5,68b329da9893e34099c7d8ad5cb9c940", + "test_R1.fail.fastq.gz:md5,68b329da9893e34099c7d8ad5cb9c940", + "test_R2.fail.fastq.gz:md5,68b329da9893e34099c7d8ad5cb9c940" + ] + ] + ], + "reads_merged": [ + + ], + "versions_fastp": [ + [ + "FASTP", + "fastp", + "1.3.6" + ] + ] + } + ], + "timestamp": "2026-07-31T10:06:47.329995", + "meta": { + "nf-test": "0.9.5", + "nextflow": "26.04.6" + } + }, + "fastp test_fastp_interleaved": { + "content": [ + [ + [ + { + "id": "test", + "single_end": true + }, + "test.fastp.fastq.gz:md5,217d62dc13a23e92513a1bd8e1bcea39" + ] + ], + { + "versions_fastp": [ + [ + "FASTP", + "fastp", + "1.3.6" + ] + ] + } + ], + "timestamp": "2026-07-31T10:06:09.066984", + "meta": { + "nf-test": "0.9.5", + "nextflow": "26.04.6" + } + }, + "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_fastp": [ + [ + "FASTP", + "fastp", + "1.3.6" + ] + ] + } + ], + "timestamp": "2026-07-31T10:06:13.177593", + "meta": { + "nf-test": "0.9.5", + "nextflow": "26.04.6" + } + }, + "test_fastp_paired_end_qc_only": { + "content": [ + [ + + ], + [ + + ], + [ + + ], + [ + + ], + [ + + ], + [ + + ], + { + "versions_fastp": [ + [ + "FASTP", + "fastp", + "1.3.6" + ] + ] + } + ], + "timestamp": "2026-07-31T10:06:31.289427", + "meta": { + "nf-test": "0.9.5", + "nextflow": "26.04.6" + } + }, + "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": [ + [ + "FASTP", + "fastp", + "1.3.6" + ] + ], + "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_fastp": [ + [ + "FASTP", + "fastp", + "1.3.6" + ] + ] + } + ], + "timestamp": "2026-07-31T10:07:00.16477", + "meta": { + "nf-test": "0.9.5", + "nextflow": "26.04.6" + } + } +} \ 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 0000000..4be8dbd --- /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 0000000..53b61b0 --- /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/modules/nf-core/hostile/clean/environment.yml b/modules/nf-core/hostile/clean/environment.yml new file mode 100644 index 0000000..55afd60 --- /dev/null +++ b/modules/nf-core/hostile/clean/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/hostile + - bioconda::hostile=2.0.2 diff --git a/modules/nf-core/hostile/clean/main.nf b/modules/nf-core/hostile/clean/main.nf new file mode 100644 index 0000000..aa087d2 --- /dev/null +++ b/modules/nf-core/hostile/clean/main.nf @@ -0,0 +1,54 @@ +process HOSTILE_CLEAN { + tag "${meta.id}" + label 'process_low' + + conda "${moduleDir}/environment.yml" + container "${workflow.containerEngine in ['singularity', 'apptainer'] && !task.ext.singularity_pull_docker_container + ? 'https://community-cr-prod.seqera.io/docker/registry/v2/blobs/sha256/7c/7caca3a47606de8e3460b35823193a471272aa6ab7cfafbf9aabf4615c9fa181/data' + : 'community.wave.seqera.io/library/hostile:2.0.2--a7f5e5d341b6b94b'}" + + input: + tuple val(meta) , path(reads, stageAs: "input_reads/") + tuple val(reference_name), path(reference_dir) + + output: + tuple val(meta), path('*.fastq.gz'), emit: fastq + tuple val(meta), path('*.json') , emit: json + tuple val("${task.process}"), val('hostile'), eval("hostile --version"), emit: versions_hostile, topic: versions + + when: + task.ext.when == null || task.ext.when + + script: + def args = task.ext.args ?: '' + def prefix = task.ext.prefix ?: "${meta.id}" + def sorted_reads = meta.single_end ? [reads].flatten() : reads.sort { read -> read.simpleName } + def reads_cmd = meta.single_end ? "--fastq1 ${sorted_reads[0]}" : "--fastq1 ${sorted_reads[0]} --fastq2 ${sorted_reads[1]}" + """ + export HOSTILE_CACHE_DIR=${reference_dir} + + ## Reorder the reads for reproducibility + ## Set offline as we never want this process to auto-download reference files as required input channel + hostile \\ + clean \\ + ${args} \\ + --threads ${task.cpus} \\ + ${reads_cmd} \\ + --index ${reference_name} \\ + --output . \\ + --reorder \\ + --airplane \\ + | tee > ${prefix}.json + """ + + stub: + def prefix = task.ext.prefix ?: "${meta.id}" + def fake_read2 = !meta.single_end ? "echo '' | gzip > ${prefix}.clean_2.fastq.gz" : "" + """ + export HOSTILE_CACHE_DIR=${reference_dir} + echo "" | gzip > ${prefix}.clean_1.fastq.gz + ${fake_read2} + + touch ${prefix}.json + """ +} diff --git a/modules/nf-core/hostile/clean/meta.yml b/modules/nf-core/hostile/clean/meta.yml new file mode 100644 index 0000000..89220c2 --- /dev/null +++ b/modules/nf-core/hostile/clean/meta.yml @@ -0,0 +1,94 @@ +name: "hostile_clean" +description: Removes host reads from short- and long-read FASTQ sequencing files +keywords: + - hostile + - decontamination + - human removal + - host removal + - clean +tools: + - "hostile": + description: "Hostile: accurate host decontamination" + homepage: "https://github.com/bede/hostile" + documentation: "https://github.com/bede/hostile" + tool_dev_url: "https://github.com/bede/hostile" + doi: "10.1093/bioinformatics/btad728" + licence: + - "MIT" + identifier: biotools:hostile +input: + - - meta: + type: map + description: | + Groovy Map containing sample information + e.g. `[ id:'sample1', single_end:false ]` + - reads: + type: file + description: Paired or single end FASTQ files + pattern: "*.{fastq,fq,fastq.gz,fq.gz}" + ontologies: + - edam: "http://edamontology.org/format_1930" + - - reference_name: + type: string + description: | + Name of the reference to align against and thus remove mapped reads to. + - reference_dir: + type: directory + description: | + Directory containing index file(s) corresponding to the preferred aligner (bowtie2 short reads or minimap for long reads). + Note that single end data is assumed to be long reads. If you have single-end short read you must supply both the BowTie2 + indices AND explicitly specify `--aligner bowtie2` + ontologies: + - edam: "http://edamontology.org/data_1049" +output: + fastq: + - - meta: + type: map + description: | + Groovy Map containing sample information + e.g. `[ id:'sample1', single_end:false ]` + - "*.fastq.gz": + type: file + description: | + Cleaned FASTQ files with host reads removed + pattern: "*.{fastq,fq,fastq.gz,fq.gz}" + ontologies: + - edam: "http://edamontology.org/format_1930" + json: + - - meta: + type: map + description: | + Groovy Map containing sample information + e.g. `[ id:'sample1', single_end:false ]` + - "*.json": + type: file + description: | + JSON report containing statistics from hostile cleaning + pattern: "*.json" + ontologies: + - edam: "http://edamontology.org/format_3464" + versions_hostile: + - - ${task.process}: + type: string + description: The name of the process + - hostile: + type: string + description: The name of the tool + - hostile --version: + type: eval + description: The expression to obtain the version of the tool +topics: + versions: + - - ${task.process}: + type: string + description: The name of the process + - hostile: + type: string + description: The name of the tool + - hostile --version: + type: eval + description: The expression to obtain the version of the tool +authors: + - "@jfy133" +maintainers: + - "@jfy133" diff --git a/modules/nf-core/hostile/clean/tests/main.nf.test b/modules/nf-core/hostile/clean/tests/main.nf.test new file mode 100644 index 0000000..116f28f --- /dev/null +++ b/modules/nf-core/hostile/clean/tests/main.nf.test @@ -0,0 +1,114 @@ +nextflow_process { + + name "Test Process HOSTILE_CLEAN" + script "../main.nf" + process "HOSTILE_CLEAN" + config './nextflow.config' + + tag "modules" + tag "modules_nfcore" + tag "hostile" + tag "hostile/clean" + tag "bowtie2/build" + + setup { + run("BOWTIE2_BUILD") { + script "../../../bowtie2/build/main.nf" + process { + """ + input[0] = [ + [ id:'test' ], + file(params.modules_testdata_base_path + 'genomics/sarscov2/genome/genome.fasta', checkIfExists: true) + ] + """ + } + } + } + + test("fastq - single-end") { + when { + params { + module_args = "--aligner bowtie2" + } + process { + """ + input[0] = [ + [ id:'test', single_end:true ], // meta map + [ + file(params.modules_testdata_base_path + 'genomics/homo_sapiens/illumina/fastq/test_1.fastq.gz', checkIfExists: true), + ], + ] + input[1] = BOWTIE2_BUILD.out.index.map { ['genome', it[1]] } + """ + } + } + + then { + assertAll( + { assert process.success }, + { assert path(process.out.json[0][1]).readLines().any{ it.contains('\"reads_removed\": 0')} }, + { assert snapshot(sanitizeOutput(process.out, unstableKeys:["json"])).match() } + ) + } + + } + + + test("fastq - paired-end") { + when { + params { + module_args = "" + } + process { + """ + input[0] = [ + [ id:'test', single_end:false ], // meta map + [ + file(params.modules_testdata_base_path + 'genomics/homo_sapiens/illumina/fastq/test_1.fastq.gz', checkIfExists: true), + file(params.modules_testdata_base_path + 'genomics/homo_sapiens/illumina/fastq/test_2.fastq.gz', checkIfExists: true), + ], + ] + input[1] = BOWTIE2_BUILD.out.index.map { ['genome', it[1]] } + """ + } + } + + then { + assertAll( + { assert process.success }, + { assert path(process.out.json[0][1]).readLines().any{ it.contains('\"reads_removed\": 0')} }, + { assert snapshot(sanitizeOutput(process.out, unstableKeys:["json"])).match() } + ) + } + + } + + test("fastq - single-end - stub") { + + options "-stub" + + when { + params { + module_args = "" + } + process { + """ + input[0] = [ + [ id:'test', single_end:true ], // meta map + [file(params.modules_testdata_base_path + 'genomics/homo_sapiens/illumina/fastq/test_1.fastq.gz', checkIfExists: true)], + ] + input[1] = BOWTIE2_BUILD.out.index.map { ['genome', it[1]] } + """ + } + } + + then { + assertAll( + { assert process.success }, + { assert snapshot(sanitizeOutput(process.out)).match() } + ) + } + + } + +} diff --git a/modules/nf-core/hostile/clean/tests/main.nf.test.snap b/modules/nf-core/hostile/clean/tests/main.nf.test.snap new file mode 100644 index 0000000..42d1589 --- /dev/null +++ b/modules/nf-core/hostile/clean/tests/main.nf.test.snap @@ -0,0 +1,113 @@ +{ + "fastq - single-end - stub": { + "content": [ + { + "fastq": [ + [ + { + "id": "test", + "single_end": true + }, + "test.clean_1.fastq.gz:md5,68b329da9893e34099c7d8ad5cb9c940" + ] + ], + "json": [ + [ + { + "id": "test", + "single_end": true + }, + "test.json:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "versions_hostile": [ + [ + "HOSTILE_CLEAN", + "hostile", + "2.0.2" + ] + ] + } + ], + "timestamp": "2026-05-22T10:10:32.316335997", + "meta": { + "nf-test": "0.9.5", + "nextflow": "26.04.0" + } + }, + "fastq - paired-end": { + "content": [ + { + "fastq": [ + [ + { + "id": "test", + "single_end": false + }, + [ + "test_1.clean_1.fastq.gz:md5,6de988b85909e6529bad0022703bcab2", + "test_2.clean_2.fastq.gz:md5,539acd65e93bf16a5ace7d6034e704c7" + ] + ] + ], + "json": [ + [ + { + "id": "test", + "single_end": false + }, + "test.json" + ] + ], + "versions_hostile": [ + [ + "HOSTILE_CLEAN", + "hostile", + "2.0.2" + ] + ] + } + ], + "timestamp": "2026-05-22T10:13:45.740888243", + "meta": { + "nf-test": "0.9.5", + "nextflow": "26.04.0" + } + }, + "fastq - single-end": { + "content": [ + { + "fastq": [ + [ + { + "id": "test", + "single_end": true + }, + "test_1.clean.fastq.gz:md5,9224ada21765cb3ad5fc05f40df78f17" + ] + ], + "json": [ + [ + { + "id": "test", + "single_end": true + }, + "test.json" + ] + ], + "versions_hostile": [ + [ + "HOSTILE_CLEAN", + "hostile", + "2.0.2" + ] + ] + } + ], + "timestamp": "2026-05-22T10:13:22.374303854", + "meta": { + "nf-test": "0.9.5", + "nextflow": "26.04.0" + } + } +} \ No newline at end of file diff --git a/modules/nf-core/hostile/clean/tests/nextflow.config b/modules/nf-core/hostile/clean/tests/nextflow.config new file mode 100644 index 0000000..864528a --- /dev/null +++ b/modules/nf-core/hostile/clean/tests/nextflow.config @@ -0,0 +1,5 @@ +process { + withName: 'HOSTILE_CLEAN' { + ext.args = params.module_args + } +} diff --git a/modules/nf-core/hostile/fetch/environment.yml b/modules/nf-core/hostile/fetch/environment.yml new file mode 100644 index 0000000..f1f048b --- /dev/null +++ b/modules/nf-core/hostile/fetch/environment.yml @@ -0,0 +1,9 @@ +--- +# yaml-language-server: $schema=https://raw.githubusercontent.com/nf-core/modules/master/modules/environment-schema.json +channels: + - conda-forge + - bioconda + +dependencies: + # renovate: datasource=conda depName=bioconda/hostile + - bioconda::hostile=2.0.2 diff --git a/modules/nf-core/hostile/fetch/main.nf b/modules/nf-core/hostile/fetch/main.nf new file mode 100644 index 0000000..b0aa695 --- /dev/null +++ b/modules/nf-core/hostile/fetch/main.nf @@ -0,0 +1,44 @@ +process HOSTILE_FETCH { + label 'process_single' + + conda "${moduleDir}/environment.yml" + container "${workflow.containerEngine in ['singularity', 'apptainer'] && !task.ext.singularity_pull_docker_container + ? 'https://community-cr-prod.seqera.io/docker/registry/v2/blobs/sha256/7c/7caca3a47606de8e3460b35823193a471272aa6ab7cfafbf9aabf4615c9fa181/data' + : 'community.wave.seqera.io/library/hostile:2.0.2--a7f5e5d341b6b94b'}" + + input: + val index_name + + output: + tuple val(index_name), path('reference/'), emit: reference + tuple val("${task.process}"), val('hostile'), eval("hostile --version"), emit: versions_hostile, topic: versions + + when: + task.ext.when == null || task.ext.when + + script: + def args = task.ext.args ?: '' + """ + mkdir reference/ + export HOSTILE_CACHE_DIR=./reference + + hostile \\ + index \\ + fetch \\ + --name ${index_name} \\ + ${args} + """ + + stub: + """ + mkdir reference/ + export HOSTILE_CACHE_DIR=./reference + touch reference/human-t2t-hla.1.bt2 + touch reference/human-t2t-hla.2.bt2 + touch reference/human-t2t-hla.3.bt2 + touch reference/human-t2t-hla.4.bt2 + touch reference/human-t2t-hla.rev.1.bt2 + touch reference/human-t2t-hla.rev.2.bt2 + touch reference/human-t2t-hla.mmi + """ +} diff --git a/modules/nf-core/hostile/fetch/meta.yml b/modules/nf-core/hostile/fetch/meta.yml new file mode 100644 index 0000000..339974d --- /dev/null +++ b/modules/nf-core/hostile/fetch/meta.yml @@ -0,0 +1,58 @@ +name: "hostile_fetch" +description: Downloads required reference genomes for Hostile +keywords: + - hostile + - decontamination + - human removal + - download +tools: + - "hostile": + description: "Hostile: accurate host decontamination" + homepage: "https://github.com/bede/hostile" + documentation: "https://github.com/bede/hostile" + tool_dev_url: "https://github.com/bede/hostile" + doi: "10.1093/bioinformatics/btad728" + licence: + - "MIT" + identifier: biotools:hostile +input: + - index_name: + type: string + description: Name of the reference genome index to download +output: + reference: + - - index_name: + type: directory + description: Name of the reference genome index downloaded + - reference/: + type: directory + description: Directory containing required reference genome files for + hostile clean + pattern: "reference/" + ontologies: + - edam: "http://edamontology.org/data_1049" + versions_hostile: + - - ${task.process}: + type: string + description: The name of the process + - hostile: + type: string + description: The name of the tool + - hostile --version: + type: eval + description: The expression to obtain the version of the tool +topics: + versions: + - - ${task.process}: + type: string + description: The name of the process + - hostile: + type: string + description: The name of the tool + - hostile --version: + type: eval + description: The expression to obtain the version of the tool +authors: + - "@jfy133" +maintainers: + - "@jfy133" diff --git a/modules/nf-core/hostile/fetch/tests/main.nf.test b/modules/nf-core/hostile/fetch/tests/main.nf.test new file mode 100644 index 0000000..c2ea911 --- /dev/null +++ b/modules/nf-core/hostile/fetch/tests/main.nf.test @@ -0,0 +1,59 @@ +nextflow_process { + + name "Test Process HOSTILE_FETCH" + script "../main.nf" + process "HOSTILE_FETCH" + config './nextflow.config' + + tag "modules" + tag "modules_nfcore" + tag "hostile" + tag "hostile/fetch" + + test("human-t2t-hla - bowtie2") { + + when { + params { + module_args = "--bowtie2" + } + process { + """ + input[0] = 'human-t2t-hla' + """ + } + } + + then { + assertAll( + { assert process.success }, + { assert snapshot(sanitizeOutput(process.out)).match() } + ) + } + + } + + test("human-t2t-hla - stub") { + + options "-stub" + + when { + params { + module_args = "" + } + process { + """ + input[0] = 'human-t2t-hla' + """ + } + } + + then { + assertAll( + { assert process.success }, + { assert snapshot(sanitizeOutput(process.out)).match() } + ) + } + + } + +} diff --git a/modules/nf-core/hostile/fetch/tests/main.nf.test.snap b/modules/nf-core/hostile/fetch/tests/main.nf.test.snap new file mode 100644 index 0000000..66284f0 --- /dev/null +++ b/modules/nf-core/hostile/fetch/tests/main.nf.test.snap @@ -0,0 +1,65 @@ +{ + "human-t2t-hla - bowtie2": { + "content": [ + { + "reference": [ + [ + "human-t2t-hla", + [ + "human-t2t-hla.1.bt2:md5,b481b623a14908f29c5dce42077f8763", + "human-t2t-hla.2.bt2:md5,8df357276c7c3a4e768ecd70ca951b53", + "human-t2t-hla.3.bt2:md5,78289afb436f8d0a9526ecfa95e3fa24", + "human-t2t-hla.4.bt2:md5,198badf146cd0b3886b674424250d419", + "human-t2t-hla.rev.1.bt2:md5,34c65db94aba9cc49cb31e14d2698c1c", + "human-t2t-hla.rev.2.bt2:md5,53c9901609915475a2e1b009046d8371" + ] + ] + ], + "versions_hostile": [ + [ + "HOSTILE_FETCH", + "hostile", + "2.0.2" + ] + ] + } + ], + "timestamp": "2026-05-22T10:17:43.280616381", + "meta": { + "nf-test": "0.9.5", + "nextflow": "26.04.0" + } + }, + "human-t2t-hla - stub": { + "content": [ + { + "reference": [ + [ + "human-t2t-hla", + [ + "human-t2t-hla.1.bt2:md5,d41d8cd98f00b204e9800998ecf8427e", + "human-t2t-hla.2.bt2:md5,d41d8cd98f00b204e9800998ecf8427e", + "human-t2t-hla.3.bt2:md5,d41d8cd98f00b204e9800998ecf8427e", + "human-t2t-hla.4.bt2:md5,d41d8cd98f00b204e9800998ecf8427e", + "human-t2t-hla.mmi:md5,d41d8cd98f00b204e9800998ecf8427e", + "human-t2t-hla.rev.1.bt2:md5,d41d8cd98f00b204e9800998ecf8427e", + "human-t2t-hla.rev.2.bt2:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ] + ], + "versions_hostile": [ + [ + "HOSTILE_FETCH", + "hostile", + "2.0.2" + ] + ] + } + ], + "timestamp": "2026-05-22T10:45:40.756531067", + "meta": { + "nf-test": "0.9.5", + "nextflow": "26.04.0" + } + } +} \ No newline at end of file diff --git a/modules/nf-core/hostile/fetch/tests/nextflow.config b/modules/nf-core/hostile/fetch/tests/nextflow.config new file mode 100644 index 0000000..8bbf469 --- /dev/null +++ b/modules/nf-core/hostile/fetch/tests/nextflow.config @@ -0,0 +1,5 @@ +process { + withName: 'HOSTILE_FETCH' { + ext.args = params.module_args + } +} diff --git a/modules/nf-core/leehom/environment.yml b/modules/nf-core/leehom/environment.yml new file mode 100644 index 0000000..cc93ca3 --- /dev/null +++ b/modules/nf-core/leehom/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::leehom=1.2.15 diff --git a/modules/nf-core/leehom/main.nf b/modules/nf-core/leehom/main.nf new file mode 100644 index 0000000..610d206 --- /dev/null +++ b/modules/nf-core/leehom/main.nf @@ -0,0 +1,83 @@ +process LEEHOM { + tag "$meta.id" + label 'process_low' + + // WARN: Version information not provided by tool on CLI. Please update version string below when bumping container versions. + conda "${moduleDir}/environment.yml" + container "${ workflow.containerEngine in ['singularity', 'apptainer'] && !task.ext.singularity_pull_docker_container ? + 'https://depot.galaxyproject.org/singularity/leehom:1.2.15--h29e30f7_1' : + 'quay.io/biocontainers/leehom:1.2.15--h29e30f7_1' }" + + input: + tuple val(meta), path(reads) + + output: + tuple val(meta), path("${prefix}.bam") , emit: bam , optional: true + tuple val(meta), path("${prefix}.fq.gz") , emit: fq_pass , optional: true + tuple val(meta), path("${prefix}.fail.fq.gz") , emit: fq_fail , optional: true + tuple val(meta), path("${prefix}_r1.fq.gz") , emit: unmerged_r1_fq_pass, optional: true + tuple val(meta), path("${prefix}_r1.fail.fq.gz"), emit: unmerged_r1_fq_fail, optional: true + tuple val(meta), path("${prefix}_r2.fq.gz") , emit: unmerged_r2_fq_pass, optional: true + tuple val(meta), path("${prefix}_r2.fail.fq.gz"), emit: unmerged_r2_fq_fail, optional: true + tuple val(meta), path("*.log") , emit: log + // WARN: Version information not provided by tool on CLI. Please update this string when bumping container versions. + tuple val("${task.process}"), val('last'), val("1.2.15"), emit: versions_last, topic: versions + + when: + task.ext.when == null || task.ext.when + + script: + def args = task.ext.args ?: '' + prefix = task.ext.prefix ?: "${meta.id}" + if (reads.toString().endsWith('.bam')) { + """ + leeHom \\ + ${args} \\ + -t ${task.cpus} \\ + -o ${prefix}.bam \\ + --log ${prefix}.log \\ + ${reads} + """ + } else if (meta.single_end) { + """ + leeHom \\ + ${args} \\ + -t ${task.cpus} \\ + -fq1 ${reads} \\ + -fqo ${prefix} \\ + --log ${prefix}.log + """ + } else { + """ + leeHom \\ + ${args} \\ + -t ${task.cpus} \\ + -fq1 ${reads[0]} \\ + -fq2 ${reads[1]} \\ + -fqo ${prefix} \\ + --log ${prefix}.log + """ + } + + stub: + prefix = task.ext.prefix ?: "${meta.id}" + is_bam = reads.toString().endsWith('.bam') + is_single_end = meta.single_end + + """ + if [[ "${is_bam}" == "true" ]]; then + touch ${prefix}.bam + else + echo "" | gzip > ${prefix}.fq.gz + echo "" | gzip > ${prefix}.fail.fq.gz + if [[ "${is_single_end}" == "false" ]]; then + echo "" | gzip > ${prefix}_r1.fq.gz + echo "" | gzip > ${prefix}_r1.fail.fq.gz + echo "" | gzip > ${prefix}_r2.fq.gz + echo "" | gzip > ${prefix}_r2.fail.fq.gz + fi + fi + touch ${prefix}.log + """ + +} diff --git a/modules/nf-core/leehom/meta.yml b/modules/nf-core/leehom/meta.yml new file mode 100644 index 0000000..378b125 --- /dev/null +++ b/modules/nf-core/leehom/meta.yml @@ -0,0 +1,152 @@ +name: leehom +description: Bayesian reconstruction of ancient DNA fragments +keywords: + - ancient DNA + - adapter removal + - clipping + - trimming + - merging + - collapsing + - preprocessing + - bayesian +tools: + - leehom: + description: Bayesian reconstruction of ancient DNA fragments + homepage: "https://grenaud.github.io/leeHom/" + documentation: "https://github.com/grenaud/leeHom" + tool_dev_url: "https://github.com/grenaud/leeHom" + doi: "10.1093/nar/gku699" + licence: + - "GPL v3" + identifier: biotools:leehom +input: + - - meta: + type: map + description: | + Groovy Map containing sample information + e.g. [ id:'test', single_end:false ] + - reads: + type: file + description: Unaligned BAM or one or two gzipped FASTQ file(s) + pattern: "*.{bam,fq.gz,fastq.gz}" + ontologies: [] +output: + bam: + - - meta: + type: map + description: | + Groovy Map containing sample information + e.g. [ id:'test', single_end:false ] + - ${prefix}.bam: + type: file + description: BAM file + pattern: "*.bam" + ontologies: [] + fq_pass: + - - meta: + type: map + description: | + Groovy Map containing sample information + e.g. [ id:'test', single_end:false ] + - ${prefix}.fq.gz: + type: file + description: Trimmed and merged FASTQ + pattern: "*.fq.gz" + ontologies: + - edam: http://edamontology.org/format_3989 # GZIP format + fq_fail: + - - meta: + type: map + description: | + Groovy Map containing sample information + e.g. [ id:'test', single_end:false ] + - ${prefix}.fail.fq.gz: + type: file + description: Failed trimmed and merged FASTQs + pattern: "*.fail.fq.gz" + ontologies: + - edam: http://edamontology.org/format_3989 # GZIP format + unmerged_r1_fq_pass: + - - meta: + type: map + description: | + Groovy Map containing sample information + e.g. [ id:'test', single_end:false ] + - ${prefix}_r1.fq.gz: + type: file + description: Passed unmerged R1 FASTQs + pattern: "*.r1.fq.gz" + ontologies: + - edam: http://edamontology.org/format_3989 # GZIP format + unmerged_r1_fq_fail: + - - meta: + type: map + description: | + Groovy Map containing sample information + e.g. [ id:'test', single_end:false ] + - ${prefix}_r1.fail.fq.gz: + type: file + description: Failed unmerged R1 FASTQs + pattern: "*.r1.fail.fq.gz" + ontologies: + - edam: http://edamontology.org/format_3989 # GZIP format + unmerged_r2_fq_pass: + - - meta: + type: map + description: | + Groovy Map containing sample information + e.g. [ id:'test', single_end:false ] + - ${prefix}_r2.fq.gz: + type: file + description: Passed unmerged R2 FASTQs + pattern: "*.r2.fq.gz" + ontologies: + - edam: http://edamontology.org/format_3989 # GZIP format + unmerged_r2_fq_fail: + - - meta: + type: map + description: | + Groovy Map containing sample information + e.g. [ id:'test', single_end:false ] + - ${prefix}_r2.fail.fq.gz: + type: file + description: Failed unmerged R2 FASTQs + pattern: "*.r2.fail.fq.gz" + 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 ] + - "*.log": + type: file + description: Log file of command + pattern: "*.log" + ontologies: [] + versions_last: + - - ${task.process}: + type: string + description: The name of the process + - last: + type: string + description: The name of the tool + - 1.2.15: + type: string + description: The expression to obtain the version of the tool +topics: + versions: + - - ${task.process}: + type: string + description: The name of the process + - last: + type: string + description: The name of the tool + - 1.2.15: + type: string + description: The expression to obtain the version of the tool +authors: + - "@jfy133" +maintainers: + - "@jfy133" diff --git a/modules/nf-core/leehom/tests/main.nf.test b/modules/nf-core/leehom/tests/main.nf.test new file mode 100644 index 0000000..50cd8c5 --- /dev/null +++ b/modules/nf-core/leehom/tests/main.nf.test @@ -0,0 +1,166 @@ +nextflow_process { + + name "Test Process LEEHOM" + config "./nextflow.config" + script "../main.nf" + process "LEEHOM" + + tag "modules" + tag "modules_nfcore" + tag "leehom" + tag "samtools" + tag "samtools/view" + + test("homo_sapiens - bam") { + setup { + run("SAMTOOLS_VIEW") { + script "../../samtools/view/main.nf" + process { + """ + input[0] = [ + [id:'test', single_end:false], + file(params.modules_testdata_base_path + 'genomics/homo_sapiens/illumina/bam/test.paired_end.sorted.bam', checkIfExists:true), + [] + ] + input[1] = [[],[],[]] + input[2] = [[], []] + input[3] = [[], []] + input[4] = [] + """ + } + } + } + when { + params { + samtools_arg = '-f4 -b' + } + process { + """ + input[0] = SAMTOOLS_VIEW.out.bam + """ + } + } + then { + assertAll( + { assert process.success }, + { assert snapshot( + process.out.bam.collect{meta, bam_ -> file(bam_).name + ':readsMD5,' + bam(bam_).getReadsMD5()}, + sanitizeOutput(process.out, unstableKeys:["bam"]) + ).match() } + ) + } + } + test("sarscov2 - fastq single end") { + when { + params { + samtools_arg = '' + } + process { + """ + input[0] = [ + [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(sanitizeOutput(process.out, unstableKeys:["fq_fail", "unmerged_r1_fq_fail"])).match() } // Fail files are empty + ) + } + } + test("sarscov2 - fastq paired end") { + when { + params { + samtools_arg = '' + } + process { + """ + input[0] = [ + [id:'test', single_end:false], + [ + 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(sanitizeOutput(process.out, unstableKeys:["fq_fail", "unmerged_r1_fq_fail", "unmerged_r2_fq_fail"])).match() } // Fail files are empty + ) + } + } + + test("homo_sapiens - bam - stub") { + options "-stub" + when { + params { + samtools_arg = '' + } + process { + """ + input[0] = [ + [id:'test', single_end:false], + 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(sanitizeOutput(process.out)).match() } + ) + } + } + test("sarscov2 - fastq single end - stub") { + options "-stub" + when { + params { + samtools_arg = '' + } + process { + """ + input[0] = [ + [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(sanitizeOutput(process.out)).match() } + ) + } + } + test("sarscov2 - fastq paired end - stub") { + options "-stub" + when { + params { + samtools_arg = '' + } + process { + """ + input[0] = [ + [id:'test', single_end:false], + [ + 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(sanitizeOutput(process.out)).match() } + ) + } + } +} diff --git a/modules/nf-core/leehom/tests/main.nf.test.snap b/modules/nf-core/leehom/tests/main.nf.test.snap new file mode 100644 index 0000000..f90abd5 --- /dev/null +++ b/modules/nf-core/leehom/tests/main.nf.test.snap @@ -0,0 +1,401 @@ +{ + "sarscov2 - fastq paired end": { + "content": [ + { + "bam": [ + + ], + "fq_fail": [ + [ + { + "id": "test", + "single_end": false + }, + "test.fail.fq.gz" + ] + ], + "fq_pass": [ + [ + { + "id": "test", + "single_end": false + }, + "test.fq.gz:md5,5bd6d0b0d35f1b690d90f9bc803703fd" + ] + ], + "log": [ + [ + { + "id": "test", + "single_end": false + }, + "test.log:md5,800b5a88dc0822886bfbb271029e2a4a" + ] + ], + "unmerged_r1_fq_fail": [ + [ + { + "id": "test", + "single_end": false + }, + "test_r1.fail.fq.gz" + ] + ], + "unmerged_r1_fq_pass": [ + [ + { + "id": "test", + "single_end": false + }, + "test_r1.fq.gz:md5,aca8e68e644127a910bf209dc933c6cb" + ] + ], + "unmerged_r2_fq_fail": [ + [ + { + "id": "test", + "single_end": false + }, + "test_r2.fail.fq.gz" + ] + ], + "unmerged_r2_fq_pass": [ + [ + { + "id": "test", + "single_end": false + }, + "test_r2.fq.gz:md5,8429b111dbb880ada2a4e48e333423e2" + ] + ], + "versions_last": [ + [ + "LEEHOM", + "last", + "1.2.15" + ] + ] + } + ], + "timestamp": "2026-06-08T17:41:19.220071452", + "meta": { + "nf-test": "0.9.4", + "nextflow": "26.03.0" + } + }, + "sarscov2 - fastq paired end - stub": { + "content": [ + { + "bam": [ + + ], + "fq_fail": [ + [ + { + "id": "test", + "single_end": false + }, + "test.fail.fq.gz:md5,68b329da9893e34099c7d8ad5cb9c940" + ] + ], + "fq_pass": [ + [ + { + "id": "test", + "single_end": false + }, + "test.fq.gz:md5,68b329da9893e34099c7d8ad5cb9c940" + ] + ], + "log": [ + [ + { + "id": "test", + "single_end": false + }, + "test.log:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "unmerged_r1_fq_fail": [ + [ + { + "id": "test", + "single_end": false + }, + "test_r1.fail.fq.gz:md5,68b329da9893e34099c7d8ad5cb9c940" + ] + ], + "unmerged_r1_fq_pass": [ + [ + { + "id": "test", + "single_end": false + }, + "test_r1.fq.gz:md5,68b329da9893e34099c7d8ad5cb9c940" + ] + ], + "unmerged_r2_fq_fail": [ + [ + { + "id": "test", + "single_end": false + }, + "test_r2.fail.fq.gz:md5,68b329da9893e34099c7d8ad5cb9c940" + ] + ], + "unmerged_r2_fq_pass": [ + [ + { + "id": "test", + "single_end": false + }, + "test_r2.fq.gz:md5,68b329da9893e34099c7d8ad5cb9c940" + ] + ], + "versions_last": [ + [ + "LEEHOM", + "last", + "1.2.15" + ] + ] + } + ], + "timestamp": "2026-06-08T17:36:35.255477128", + "meta": { + "nf-test": "0.9.4", + "nextflow": "26.03.0" + } + }, + "sarscov2 - fastq single end": { + "content": [ + { + "bam": [ + + ], + "fq_fail": [ + [ + { + "id": "test", + "single_end": true + }, + "test.fail.fq.gz" + ] + ], + "fq_pass": [ + [ + { + "id": "test", + "single_end": true + }, + "test.fq.gz:md5,304af6f5f6bb58c70abf7924eacfa175" + ] + ], + "log": [ + [ + { + "id": "test", + "single_end": true + }, + "test.log:md5,59aa280cb72dfbea05ba913cb89db143" + ] + ], + "unmerged_r1_fq_fail": [ + + ], + "unmerged_r1_fq_pass": [ + + ], + "unmerged_r2_fq_fail": [ + + ], + "unmerged_r2_fq_pass": [ + + ], + "versions_last": [ + [ + "LEEHOM", + "last", + "1.2.15" + ] + ] + } + ], + "timestamp": "2026-06-08T17:39:00.72056377", + "meta": { + "nf-test": "0.9.4", + "nextflow": "26.03.0" + } + }, + "homo_sapiens - bam": { + "content": [ + [ + "test.bam:readsMD5,ef014d5b0cd0903595da255cdce79662" + ], + { + "bam": [ + [ + { + "id": "test", + "single_end": false + }, + "test.bam" + ] + ], + "fq_fail": [ + + ], + "fq_pass": [ + + ], + "log": [ + [ + { + "id": "test", + "single_end": false + }, + "test.log:md5,d1f5da273eb69f41babda510797c7671" + ] + ], + "unmerged_r1_fq_fail": [ + + ], + "unmerged_r1_fq_pass": [ + + ], + "unmerged_r2_fq_fail": [ + + ], + "unmerged_r2_fq_pass": [ + + ], + "versions_last": [ + [ + "LEEHOM", + "last", + "1.2.15" + ] + ] + } + ], + "timestamp": "2026-07-10T14:34:56.225495163", + "meta": { + "nf-test": "0.9.5", + "nextflow": "26.04.4" + } + }, + "sarscov2 - fastq single end - stub": { + "content": [ + { + "bam": [ + + ], + "fq_fail": [ + [ + { + "id": "test", + "single_end": true + }, + "test.fail.fq.gz:md5,68b329da9893e34099c7d8ad5cb9c940" + ] + ], + "fq_pass": [ + [ + { + "id": "test", + "single_end": true + }, + "test.fq.gz:md5,68b329da9893e34099c7d8ad5cb9c940" + ] + ], + "log": [ + [ + { + "id": "test", + "single_end": true + }, + "test.log:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "unmerged_r1_fq_fail": [ + + ], + "unmerged_r1_fq_pass": [ + + ], + "unmerged_r2_fq_fail": [ + + ], + "unmerged_r2_fq_pass": [ + + ], + "versions_last": [ + [ + "LEEHOM", + "last", + "1.2.15" + ] + ] + } + ], + "timestamp": "2026-06-08T17:36:30.144467799", + "meta": { + "nf-test": "0.9.4", + "nextflow": "26.03.0" + } + }, + "homo_sapiens - bam - stub": { + "content": [ + { + "bam": [ + [ + { + "id": "test", + "single_end": false + }, + "test.bam:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "fq_fail": [ + + ], + "fq_pass": [ + + ], + "log": [ + [ + { + "id": "test", + "single_end": false + }, + "test.log:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "unmerged_r1_fq_fail": [ + + ], + "unmerged_r1_fq_pass": [ + + ], + "unmerged_r2_fq_fail": [ + + ], + "unmerged_r2_fq_pass": [ + + ], + "versions_last": [ + [ + "LEEHOM", + "last", + "1.2.15" + ] + ] + } + ], + "timestamp": "2026-06-08T17:36:25.069263319", + "meta": { + "nf-test": "0.9.4", + "nextflow": "26.03.0" + } + } +} \ No newline at end of file diff --git a/modules/nf-core/leehom/tests/nextflow.config b/modules/nf-core/leehom/tests/nextflow.config new file mode 100644 index 0000000..b38abbd --- /dev/null +++ b/modules/nf-core/leehom/tests/nextflow.config @@ -0,0 +1,5 @@ +process { + withName: "SAMTOOLS_VIEW" { + ext.args = { params.samtools_arg } + } +} diff --git a/modules/nf-core/prinseqplusplus/environment.yml b/modules/nf-core/prinseqplusplus/environment.yml new file mode 100644 index 0000000..4656e94 --- /dev/null +++ b/modules/nf-core/prinseqplusplus/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::prinseq-plus-plus=1.2.3 diff --git a/modules/nf-core/prinseqplusplus/main.nf b/modules/nf-core/prinseqplusplus/main.nf new file mode 100644 index 0000000..67b3e7c --- /dev/null +++ b/modules/nf-core/prinseqplusplus/main.nf @@ -0,0 +1,58 @@ +process PRINSEQPLUSPLUS { + tag "$meta.id" + label 'process_low' + + conda "${moduleDir}/environment.yml" + container "${ workflow.containerEngine in ['singularity', 'apptainer'] && !task.ext.singularity_pull_docker_container ? + 'https://depot.galaxyproject.org/singularity/prinseq-plus-plus:1.2.3--hc90279e_1': + 'quay.io/biocontainers/prinseq-plus-plus:1.2.3--hc90279e_1' }" + + input: + tuple val(meta), path(reads) + + output: + tuple val(meta), path("*_good_out*.fastq.gz") , emit: good_reads + tuple val(meta), path("*_single_out*.fastq.gz"), optional: true, emit: single_reads + tuple val(meta), path("*_bad_out*.fastq.gz") , optional: true, emit: bad_reads + tuple val(meta), path("*.log") , emit: log + tuple val("${task.process}"), val('prinseqplusplus'), eval("prinseq++ --version | cut -f 2 -d ' '"), topic: versions, emit: versions_prinseqplusplus + + when: + task.ext.when == null || task.ext.when + + script: + def args = task.ext.args ?: '' + def prefix = task.ext.prefix ?: "${meta.id}" + + if (meta.single_end) { + """ + prinseq++ \\ + -threads $task.cpus \\ + -fastq ${reads} \\ + -out_name ${prefix} \\ + -out_gz \\ + -VERBOSE 1 \\ + $args \\ + | tee ${prefix}.log + """ + } else { + """ + prinseq++ \\ + -threads $task.cpus \\ + -fastq ${reads[0]} \\ + -fastq2 ${reads[1]} \\ + -out_name ${prefix} \\ + -out_gz \\ + -VERBOSE 1 \\ + $args \\ + | tee ${prefix}.log + """ + } + + stub: + def prefix = task.ext.prefix ?: "${meta.id}" + """ + echo "" | gzip > ${prefix}_good_out.fastq.gz + touch ${prefix}.log + """ +} diff --git a/modules/nf-core/prinseqplusplus/meta.yml b/modules/nf-core/prinseqplusplus/meta.yml new file mode 100644 index 0000000..bc156e0 --- /dev/null +++ b/modules/nf-core/prinseqplusplus/meta.yml @@ -0,0 +1,104 @@ +name: "prinseqplusplus" +description: PRINSEQ++ is a C++ implementation of the prinseq-lite.pl program. + It can be used to filter, reformat or trim genomic and metagenomic sequence + data +keywords: + - fastq + - fasta + - filter + - trim +tools: + - "prinseqplusplus": + description: "PRINSEQ++ - Multi-threaded C++ sequence cleaning" + homepage: "https://github.com/Adrian-Cantu/PRINSEQ-plus-plus" + documentation: "https://github.com/Adrian-Cantu/PRINSEQ-plus-plus" + tool_dev_url: "https://github.com/Adrian-Cantu/PRINSEQ-plus-plus" + doi: "10.7287/peerj.preprints.27553v1" + licence: + - "GPL v2" + 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: + good_reads: + - - meta: + type: map + description: | + Groovy Map containing sample information + e.g. [ id:'test', single_end:false ] + - "*_good_out*.fastq.gz": + type: file + description: Reads passing filter(s) in gzipped FASTQ format + pattern: "*_good_out_{R1,R2}.fastq.gz" + ontologies: [] + single_reads: + - - meta: + type: map + description: | + Groovy Map containing sample information + e.g. [ id:'test', single_end:false ] + - "*_single_out*.fastq.gz": + type: file + description: | + Single reads without the pair passing filter(s) in gzipped FASTQ format + pattern: "*_single_out_{R1,R2}.fastq.gz" + ontologies: [] + bad_reads: + - - meta: + type: map + description: | + Groovy Map containing sample information + e.g. [ id:'test', single_end:false ] + - "*_bad_out*.fastq.gz": + type: file + description: | + Reads without not passing filter(s) in gzipped FASTQ format + pattern: "*_bad_out_{R1,R2}.fastq.gz" + ontologies: [] + log: + - - meta: + type: map + description: | + Groovy Map containing sample information + e.g. [ id:'test', single_end:false ] + - "*.log": + type: file + description: | + Verbose level 2 STDOUT information in a log file + pattern: "*.log" + ontologies: [] + versions_prinseqplusplus: + - - ${task.process}: + type: string + description: The name of the process + - prinseqplusplus: + type: string + description: The name of the tool + - prinseq++ --version | cut -f 2 -d ' ': + type: eval + description: The expression to obtain the version of the tool +topics: + versions: + - - ${task.process}: + type: string + description: The name of the process + - prinseqplusplus: + type: string + description: The name of the tool + - prinseq++ --version | cut -f 2 -d ' ': + type: eval + description: The expression to obtain the version of the tool +authors: + - "@jfy133" +maintainers: + - "@jfy133" diff --git a/modules/nf-core/prinseqplusplus/tests/main.nf.test b/modules/nf-core/prinseqplusplus/tests/main.nf.test new file mode 100644 index 0000000..7aadfb0 --- /dev/null +++ b/modules/nf-core/prinseqplusplus/tests/main.nf.test @@ -0,0 +1,86 @@ + +nextflow_process { + + name "Test Process PRINSEQPLUSPLUS" + script "../main.nf" + process "PRINSEQPLUSPLUS" + config "./nextflow.config" + + tag "modules" + tag "modules_nfcore" + tag "prinseqplusplus" + + test("test-prinseqplusplus-single-end") { + + when { + process { + """ + input[0] = [ + [ 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( + sanitizeOutput(process.out, unstableKeys: ["good_reads", "bad_reads", "log"]) + ).match() + } + ) + } + } + + test("test-prinseqplusplus-paired-end") { + + when { + process { + """ + input[0] = [ + [ id:'test', single_end:false ], + [ + 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( + sanitizeOutput(process.out, unstableKeys: ["good_reads", "single_reads", "bad_reads", "log"]) + ).match() + } + ) + } + } + + test("test-prinseqplusplus-stub") { + options "-stub" + + when { + process { + """ + input[0] = [ + [ 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(sanitizeOutput(process.out)).match() } + ) + } + } + +} diff --git a/modules/nf-core/prinseqplusplus/tests/main.nf.test.snap b/modules/nf-core/prinseqplusplus/tests/main.nf.test.snap new file mode 100644 index 0000000..907c656 --- /dev/null +++ b/modules/nf-core/prinseqplusplus/tests/main.nf.test.snap @@ -0,0 +1,155 @@ +{ + "test-prinseqplusplus-stub": { + "content": [ + { + "bad_reads": [ + + ], + "good_reads": [ + [ + { + "id": "test", + "single_end": true + }, + "test_good_out.fastq.gz:md5,68b329da9893e34099c7d8ad5cb9c940" + ] + ], + "log": [ + [ + { + "id": "test", + "single_end": true + }, + "test.log:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "single_reads": [ + + ], + "versions_prinseqplusplus": [ + [ + "PRINSEQPLUSPLUS", + "prinseqplusplus", + "1.2" + ] + ] + } + ], + "timestamp": "2026-05-15T07:13:35.013113132", + "meta": { + "nf-test": "0.9.5", + "nextflow": "25.10.2" + } + }, + "test-prinseqplusplus-single-end": { + "content": [ + { + "bad_reads": [ + [ + { + "id": "test", + "single_end": true + }, + "test_bad_out.fastq.gz" + ] + ], + "good_reads": [ + [ + { + "id": "test", + "single_end": true + }, + "test_good_out.fastq.gz" + ] + ], + "log": [ + [ + { + "id": "test", + "single_end": true + }, + "test.log" + ] + ], + "single_reads": [ + + ], + "versions_prinseqplusplus": [ + [ + "PRINSEQPLUSPLUS", + "prinseqplusplus", + "1.2" + ] + ] + } + ], + "timestamp": "2026-05-15T07:13:24.142656202", + "meta": { + "nf-test": "0.9.5", + "nextflow": "25.10.2" + } + }, + "test-prinseqplusplus-paired-end": { + "content": [ + { + "bad_reads": [ + [ + { + "id": "test", + "single_end": false + }, + [ + "test_bad_out_R1.fastq.gz", + "test_bad_out_R2.fastq.gz" + ] + ] + ], + "good_reads": [ + [ + { + "id": "test", + "single_end": false + }, + [ + "test_good_out_R1.fastq.gz", + "test_good_out_R2.fastq.gz" + ] + ] + ], + "log": [ + [ + { + "id": "test", + "single_end": false + }, + "test.log" + ] + ], + "single_reads": [ + [ + { + "id": "test", + "single_end": false + }, + [ + "test_single_out_R1.fastq.gz", + "test_single_out_R2.fastq.gz" + ] + ] + ], + "versions_prinseqplusplus": [ + [ + "PRINSEQPLUSPLUS", + "prinseqplusplus", + "1.2" + ] + ] + } + ], + "timestamp": "2026-05-15T07:13:29.589626825", + "meta": { + "nf-test": "0.9.5", + "nextflow": "25.10.2" + } + } +} \ No newline at end of file diff --git a/modules/nf-core/prinseqplusplus/tests/nextflow.config b/modules/nf-core/prinseqplusplus/tests/nextflow.config new file mode 100644 index 0000000..14bae89 --- /dev/null +++ b/modules/nf-core/prinseqplusplus/tests/nextflow.config @@ -0,0 +1,5 @@ +process { + withName: PRINSEQPLUSPLUS { + ext.args = "-lc_entropy=0.8" + } +} diff --git a/modules/nf-core/seqfu/check/environment.yml b/modules/nf-core/seqfu/check/environment.yml new file mode 100644 index 0000000..210e292 --- /dev/null +++ b/modules/nf-core/seqfu/check/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::seqfu=1.22.3 diff --git a/modules/nf-core/seqfu/check/main.nf b/modules/nf-core/seqfu/check/main.nf new file mode 100644 index 0000000..45f4c09 --- /dev/null +++ b/modules/nf-core/seqfu/check/main.nf @@ -0,0 +1,36 @@ +process SEQFU_CHECK { + tag "${meta.id}" + label 'process_low' + + conda "${moduleDir}/environment.yml" + container "${workflow.containerEngine in ['singularity', 'apptainer'] && !task.ext.singularity_pull_docker_container + ? 'https://depot.galaxyproject.org/singularity/seqfu:1.22.3--hc29b5fc_1' + : 'quay.io/biocontainers/seqfu:1.22.3--hc29b5fc_1'}" + + input: + tuple val(meta), path(reads) + + output: + tuple val(meta), path("${prefix}.tsv"), emit: check + tuple val("${task.process}"), val('seqfu'), eval('seqfu version'), emit: versions_seqfu, topic: versions + + when: + task.ext.when == null || task.ext.when + + script: + def args = task.ext.args ?: '' + prefix = task.ext.prefix ?: "${meta.id}" + def dirFlag = (reads instanceof List ? reads.every { read -> read.isDirectory() } : reads.isDirectory()) ? "--dir" : "" + """ + seqfu \\ + check \\ + ${args} \\ + ${dirFlag} ${reads} > ${prefix}.tsv + """ + + stub: + prefix = task.ext.prefix ?: "${meta.id}" + """ + touch ${prefix}.tsv + """ +} diff --git a/modules/nf-core/seqfu/check/meta.yml b/modules/nf-core/seqfu/check/meta.yml new file mode 100644 index 0000000..73db1c9 --- /dev/null +++ b/modules/nf-core/seqfu/check/meta.yml @@ -0,0 +1,74 @@ +name: "seqfu_check" +description: Evaluates the integrity of DNA FASTQ files +keywords: + - check + - seqfu + - fastq +tools: + - "seqfu": + description: "A general-purpose program to manipulate and parse information from FASTQ files" + homepage: https://telatin.github.io/seqfu2/ + documentation: https://telatin.github.io/seqfu2/tools/check.html#usage + tool_dev_url: https://github.com/telatin/seqfu2 + doi: "10.3390/bioengineering8050059" + publication: + author: "Telatin A., Fariselli P. & Birolo G." + year: 2021 + title: "SeqFu: A Suite of Utilities for the Robust and Reproducible Manipulation of Sequence Files" + source: "Bioengineering" + licence: ["MIT"] + identifier: biotools:seqfu + +input: + - - meta: + type: map + description: Groovy Map containing sample information. e.g. `[ + id:'sample1' ]` + - reads: + type: file + description: Either fastq file(s) or directory containing fastq files to be checked + pattern: "*.{fq,fastq}[.gz]" + ontologies: + - edam: http://edamontology.org/format_1931 # FASTQ-illumina format + +output: + check: + - - meta: + type: map + description: Groovy Map containing sample information. e.g. `[ + id:'sample1' ]` + - "${prefix}.tsv": + type: file + description: > + Tab-separated output file with basic sequence statistics. + pattern: "*.tsv" + ontologies: + - edam: http://edamontology.org/format_3475 # TSV + versions_seqfu: + - - ${task.process}: + type: string + description: The name of the process + - seqfu: + type: string + description: The name of the tool + - seqfu version: + type: eval + description: The expression to obtain the version of the tool +topics: + versions: + - - ${task.process}: + type: string + description: The name of the process + - seqfu: + type: string + description: The name of the tool + - seqfu version: + type: eval + description: The expression to obtain the version of the tool +authors: + - "@kornkv" + - "@mrozon-zip" +maintainers: + - "@kornkv" + - "@mrozon-zip" + - "@vagkaratzas" diff --git a/modules/nf-core/seqfu/check/tests/main.nf.test b/modules/nf-core/seqfu/check/tests/main.nf.test new file mode 100644 index 0000000..3d660d7 --- /dev/null +++ b/modules/nf-core/seqfu/check/tests/main.nf.test @@ -0,0 +1,107 @@ +nextflow_process { + + name "Test Process SEQFU_CHECK" + script "../main.nf" + process "SEQFU_CHECK" + + tag "modules" + tag "modules_nfcore" + tag "seqfu" + tag "seqfu/check" + + test("sarscov2 - fastq - 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 { + assertAll( + { assert process.success }, + { assert snapshot(sanitizeOutput(process.out)).match() } + ) + } + } + + test("sarscov2 - fastq - paired-end") { + + when { + process { + """ + input[0] = [ + [ id:'test_paired', 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(sanitizeOutput(process.out)).match() } + ) + } + } + + test("sarscov2 - fastq - directory") { + when { + process { + """ + // stage input fastq files in a folder + tmpdir = file("tmpdir", type: 'dir') + tmpdir.mkdir() + fastq_file_1 = file(params.modules_testdata_base_path + 'genomics/sarscov2/illumina/fastq/test_1.fastq.gz', checkIfExists: true) + fastq_file_2 = file(params.modules_testdata_base_path + 'genomics/sarscov2/illumina/fastq/test_2.fastq.gz', checkIfExists: true) + fastq_file_1.copyTo(tmpdir) + fastq_file_2.copyTo(tmpdir) + + // define inputs of the process here. Example: + input[0] = [ + [ id:'test' ], + [ + file(tmpdir, type:'dir', checkIfExists: true) + ] + ] + """ + } + } + then { + assertAll( + { assert process.success }, + { assert snapshot(sanitizeOutput(process.out)).match() } + ) + } + } + + test("sarscov2 - fastq - 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(sanitizeOutput(process.out)).match() } + ) + } + } +} diff --git a/modules/nf-core/seqfu/check/tests/main.nf.test.snap b/modules/nf-core/seqfu/check/tests/main.nf.test.snap new file mode 100644 index 0000000..47c455f --- /dev/null +++ b/modules/nf-core/seqfu/check/tests/main.nf.test.snap @@ -0,0 +1,109 @@ +{ + "sarscov2 - fastq - single_end - stub": { + "content": [ + { + "check": [ + [ + { + "id": "test", + "single_end": true + }, + "test.tsv:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "versions_seqfu": [ + [ + "SEQFU_CHECK", + "seqfu", + "1.22.3" + ] + ] + } + ], + "timestamp": "2026-02-20T11:31:38.98527329", + "meta": { + "nf-test": "0.9.4", + "nextflow": "25.10.4" + } + }, + "sarscov2 - fastq - directory": { + "content": [ + { + "check": [ + [ + { + "id": "test" + }, + "test.tsv:md5,cd149b0f08b698981591be4a25729d7e" + ] + ], + "versions_seqfu": [ + [ + "SEQFU_CHECK", + "seqfu", + "1.22.3" + ] + ] + } + ], + "timestamp": "2026-02-20T11:31:23.018342252", + "meta": { + "nf-test": "0.9.4", + "nextflow": "25.10.4" + } + }, + "sarscov2 - fastq - paired-end": { + "content": [ + { + "check": [ + [ + { + "id": "test_paired", + "single_end": false + }, + "test_paired.tsv:md5,c3e6eae028f23041bd09cac9c371f318" + ] + ], + "versions_seqfu": [ + [ + "SEQFU_CHECK", + "seqfu", + "1.22.3" + ] + ] + } + ], + "timestamp": "2026-02-20T12:23:18.115807113", + "meta": { + "nf-test": "0.9.4", + "nextflow": "25.10.4" + } + }, + "sarscov2 - fastq - single-end": { + "content": [ + { + "check": [ + [ + { + "id": "test", + "single_end": true + }, + "test.tsv:md5,4c6409169772005cfb06be9e41f2c1e2" + ] + ], + "versions_seqfu": [ + [ + "SEQFU_CHECK", + "seqfu", + "1.22.3" + ] + ] + } + ], + "timestamp": "2026-02-20T11:30:55.596299312", + "meta": { + "nf-test": "0.9.4", + "nextflow": "25.10.4" + } + } +} \ No newline at end of file diff --git a/modules/nf-core/seqfu/stats/environment.yml b/modules/nf-core/seqfu/stats/environment.yml new file mode 100644 index 0000000..210e292 --- /dev/null +++ b/modules/nf-core/seqfu/stats/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::seqfu=1.22.3 diff --git a/modules/nf-core/seqfu/stats/main.nf b/modules/nf-core/seqfu/stats/main.nf new file mode 100644 index 0000000..5872e04 --- /dev/null +++ b/modules/nf-core/seqfu/stats/main.nf @@ -0,0 +1,42 @@ +process SEQFU_STATS { + tag "${meta.id}" + label 'process_single' + + conda "${moduleDir}/environment.yml" + container "${workflow.containerEngine in ['singularity', 'apptainer'] && !task.ext.singularity_pull_docker_container + ? 'https://depot.galaxyproject.org/singularity/seqfu:1.22.3--hfd12232_2' + : 'quay.io/biocontainers/seqfu:1.22.3--hfd12232_2'}" + + input: + // stats can get one or more fasta or fastq files + tuple val(meta), path(files) + + output: + tuple val(meta), path("*.tsv"), emit: stats + tuple val(meta), path("*_mqc.txt"), emit: multiqc + tuple val("${task.process}"), val('seqfu'), eval('seqfu version'), emit: versions_seqfu, topic: versions + + when: + task.ext.when == null || task.ext.when + + script: + def args = task.ext.args ?: '' + def prefix = task.ext.prefix ?: "${meta.id}" + """ + seqfu \\ + stats \\ + ${args} \\ + --multiqc ${prefix}_mqc.txt \\ + ${files} > ${prefix}.tsv + """ + + stub: + def args = task.ext.args ?: '' + def prefix = task.ext.prefix ?: "${meta.id}" + """ + echo ${args} + + touch ${prefix}.tsv + touch ${prefix}_mqc.txt + """ +} diff --git a/modules/nf-core/seqfu/stats/meta.yml b/modules/nf-core/seqfu/stats/meta.yml new file mode 100644 index 0000000..a172cc5 --- /dev/null +++ b/modules/nf-core/seqfu/stats/meta.yml @@ -0,0 +1,83 @@ +name: "seqfu_stats" +description: Statistics for FASTA or FASTQ files +keywords: + - seqfu + - stats + - n50 +tools: + - "seqfu": + description: "Cross-platform compiled suite of tools to manipulate and inspect + FASTA and FASTQ files" + homepage: "https://telatin.github.io/seqfu2/" + documentation: "https://telatin.github.io/seqfu2/" + tool_dev_url: "https://github.com/telatin/seqfu2" + doi: "10.3390/bioengineering8050059" + publication: + author: "Telatin A., Fariselli P. & Birolo G." + year: 2021 + title: "SeqFu: A Suite of Utilities for the Robust and Reproducible Manipulation of Sequence Files" + source: "Bioengineering" + licence: + - "GPL v3" + identifier: biotools:seqfu +input: + - - meta: + type: map + description: | + Groovy Map containing sample information + e.g. `[ id:'sample1', single_end:false ]` + - files: + type: file + description: One or more FASTA or FASTQ files + pattern: "*.{fasta,fastq,fasta.gz,fastq.gz,fq,fq.gz}" + ontologies: + - edam: http://edamontology.org/format_1930 +output: + stats: + - - meta: + type: map + description: | + Groovy Map containing sample information + e.g. `[ id:'sample1', single_end:false ]` + - "*.tsv": + type: file + description: Tab-separated output file with basic sequence statistics. + pattern: "*.{tsv}" + ontologies: + - edam: http://edamontology.org/format_3475 + multiqc: + - - meta: + type: map + description: | + Groovy Map containing sample information + e.g. `[ id:'sample1', single_end:false ]` + - "*_mqc.txt": + type: file + description: MultiQC ready table + pattern: "*.{_mqc.txt}" + ontologies: [] + versions_seqfu: + - - ${task.process}: + type: string + description: The name of the process + - seqfu: + type: string + description: The name of the tool + - seqfu version: + type: eval + description: The expression to obtain the version of the tool +topics: + versions: + - - ${task.process}: + type: string + description: The name of the process + - seqfu: + type: string + description: The name of the tool + - seqfu version: + type: eval + description: The expression to obtain the version of the tool +authors: + - "@telatin" +maintainers: + - "@telatin" diff --git a/modules/nf-core/seqfu/stats/tests/main.nf.test b/modules/nf-core/seqfu/stats/tests/main.nf.test new file mode 100644 index 0000000..c52781c --- /dev/null +++ b/modules/nf-core/seqfu/stats/tests/main.nf.test @@ -0,0 +1,81 @@ +nextflow_process { + + name "Test Process SEQFU_STATS" + script "../main.nf" + process "SEQFU_STATS" + + tag "modules" + tag "modules_nfcore" + tag "seqfu" + tag "seqfu/stats" + + // test with 1 FAA file (with multiple sequences of different length) + test("seqfu stats - faa") { + when { + process { + """ + input[0] = [ + [ id:'test_faa' ], + file(params.modules_testdata_base_path + 'genomics/sarscov2/genome/proteome.fasta', checkIfExists: true) + ] + """ + } + } + + then { + assertAll( + { assert process.success }, + { assert snapshot(sanitizeOutput(process.out)).match() } + ) + } + } + + // test feeding a mix of files including compressed + test("seqfu stats - multiple files") { + when { + process { + """ + input[0] = [ + [ id:'test_multiple' ], + [ + file(params.modules_testdata_base_path + 'genomics/sarscov2/genome/proteome.fasta', checkIfExists: true), + file(params.modules_testdata_base_path + 'genomics/sarscov2/genome/genome.fasta', checkIfExists: true), + file(params.modules_testdata_base_path + 'genomics/sarscov2/genome/genome.fasta.gz', checkIfExists: true), + file(params.modules_testdata_base_path + 'genomics/sarscov2/illumina/fastq/test_1.fastq.gz', checkIfExists: true) + ] + ] + """ + } + } + + then { + assertAll( + { assert process.success }, + { assert snapshot(sanitizeOutput(process.out)).match() } + ) + } + } + + test("seqfu stats - faa - stub") { + + options "-stub" + + when { + process { + """ + input[0] = [ + [ id:'test_stub' ], + file(params.modules_testdata_base_path + 'genomics/sarscov2/genome/proteome.fasta', checkIfExists: true) + ] + """ + } + } + + then { + assertAll( + { assert process.success}, + { assert snapshot(sanitizeOutput(process.out)).match() } + ) + } + } +} diff --git a/modules/nf-core/seqfu/stats/tests/main.nf.test.snap b/modules/nf-core/seqfu/stats/tests/main.nf.test.snap new file mode 100644 index 0000000..e6ac1e3 --- /dev/null +++ b/modules/nf-core/seqfu/stats/tests/main.nf.test.snap @@ -0,0 +1,104 @@ +{ + "seqfu stats - faa - stub": { + "content": [ + { + "multiqc": [ + [ + { + "id": "test_stub" + }, + "test_stub_mqc.txt:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "stats": [ + [ + { + "id": "test_stub" + }, + "test_stub.tsv:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "versions_seqfu": [ + [ + "SEQFU_STATS", + "seqfu", + "1.22.3" + ] + ] + } + ], + "timestamp": "2026-02-20T11:32:51.572529816", + "meta": { + "nf-test": "0.9.4", + "nextflow": "25.10.4" + } + }, + "seqfu stats - faa": { + "content": [ + { + "multiqc": [ + [ + { + "id": "test_faa" + }, + "test_faa_mqc.txt:md5,8f3c2edaf1ea5be912c9f99b21b2856c" + ] + ], + "stats": [ + [ + { + "id": "test_faa" + }, + "test_faa.tsv:md5,0d6bf2cc788f7828761440a1689cac04" + ] + ], + "versions_seqfu": [ + [ + "SEQFU_STATS", + "seqfu", + "1.22.3" + ] + ] + } + ], + "timestamp": "2026-02-20T11:32:28.645841762", + "meta": { + "nf-test": "0.9.4", + "nextflow": "25.10.4" + } + }, + "seqfu stats - multiple files": { + "content": [ + { + "multiqc": [ + [ + { + "id": "test_multiple" + }, + "test_multiple_mqc.txt:md5,dbc6e762eebbf756cd0687807de60445" + ] + ], + "stats": [ + [ + { + "id": "test_multiple" + }, + "test_multiple.tsv:md5,d016de3d84187a06c8e19b8dabccb3ae" + ] + ], + "versions_seqfu": [ + [ + "SEQFU_STATS", + "seqfu", + "1.22.3" + ] + ] + } + ], + "timestamp": "2026-02-20T11:32:36.800156393", + "meta": { + "nf-test": "0.9.4", + "nextflow": "25.10.4" + } + } +} \ No newline at end of file diff --git a/modules/nf-core/seqkit/pair/environment.yml b/modules/nf-core/seqkit/pair/environment.yml new file mode 100644 index 0000000..09abb79 --- /dev/null +++ b/modules/nf-core/seqkit/pair/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::seqkit=2.13.0 diff --git a/modules/nf-core/seqkit/pair/main.nf b/modules/nf-core/seqkit/pair/main.nf new file mode 100644 index 0000000..dd0a8ca --- /dev/null +++ b/modules/nf-core/seqkit/pair/main.nf @@ -0,0 +1,44 @@ +process SEQKIT_PAIR { + tag "${meta.id}" + label 'process_medium' + + conda "${moduleDir}/environment.yml" + container "${workflow.containerEngine in ['singularity', 'apptainer'] && !task.ext.singularity_pull_docker_container + ? 'https://community-cr-prod.seqera.io/docker/registry/v2/blobs/sha256/4f/4fe272ab9a519cf418160471a485b5ef50ea3f571a8e4555a826f70a4d8243ae/data' + : 'community.wave.seqera.io/library/seqkit:2.13.0--05c0a96bf9fb2751'}" + + input: + tuple val(meta), path(reads) + + output: + tuple val(meta), path("*.paired.fastq.gz"), emit: reads + tuple val(meta), path("*.unpaired.fastq.gz"), optional: true, emit: unpaired_reads + tuple val("${task.process}"), val('seqkit'), eval("seqkit version | sed 's/^.*v//'"), emit: versions_seqkit, topic: versions + + when: + task.ext.when == null || task.ext.when + + script: + def args = task.ext.args ?: '' + """ + seqkit \\ + pair \\ + -1 ${reads[0]} \\ + -2 ${reads[1]} \\ + ${args} \\ + --threads ${task.cpus} + + # gzip fastq + find . -maxdepth 1 -name "*.fastq" -exec gzip {} \\; + """ + + stub: + def args = task.ext.args ?: '' + def prefix = task.ext.prefix ?: "${meta.id}" + """ + echo ${args} + + echo "" | gzip > ${prefix}_1.paired.fastq.gz + echo "" | gzip > ${prefix}_2.paired.fastq.gz + """ +} diff --git a/modules/nf-core/seqkit/pair/meta.yml b/modules/nf-core/seqkit/pair/meta.yml new file mode 100644 index 0000000..40a62c3 --- /dev/null +++ b/modules/nf-core/seqkit/pair/meta.yml @@ -0,0 +1,81 @@ +name: seqkit_pair +description: match up paired-end reads from two fastq files +keywords: + - seqkit + - pair + - fastq +tools: + - seqkit: + description: Cross-platform and ultrafast toolkit for FASTA/Q file manipulation, + written by Wei Shen. + homepage: https://bioinf.shenwei.me/seqkit/usage/ + documentation: https://bioinf.shenwei.me/seqkit/usage/ + tool_dev_url: https://github.com/shenwei356/seqkit/ + doi: "10.1371/journal.pone.0163962" + licence: ["MIT"] + identifier: biotools:seqkit +input: + - - meta: + type: map + description: | + Groovy Map containing sample information + e.g. [ id:'test', single_end:false ] + - reads: + type: file + description: | + List of input paired-end FastQ files. + ontologies: [] +output: + reads: + - - meta: + type: map + description: | + Groovy Map containing sample information + e.g. [ id:'test', single_end:false ] + - "*.paired.fastq.gz": + type: file + description: Paired fastq reads + pattern: "*.paired.fastq.gz" + ontologies: + - edam: http://edamontology.org/format_3989 # GZIP format + unpaired_reads: + - - meta: + type: map + description: | + Groovy Map containing sample information + e.g. [ id:'test', single_end:false ] + - "*.unpaired.fastq.gz": + type: file + description: Unpaired reads (optional) + pattern: "*.unpaired.fastq.gz" + ontologies: + - edam: http://edamontology.org/format_3989 # GZIP format + versions_seqkit: + - - ${task.process}: + type: string + description: The name of the process + - seqkit: + type: string + description: The name of the tool + - seqkit version | sed 's/^.*v//': + type: eval + description: The expression to obtain the version of the tool +topics: + versions: + - - ${task.process}: + type: string + description: The name of the process + - seqkit: + type: string + description: The name of the tool + - seqkit version | sed 's/^.*v//': + type: eval + description: The expression to obtain the version of the tool +authors: + - "@sateeshperi" + - "@mjcipriano" + - "@hseabolt" +maintainers: + - "@sateeshperi" + - "@mjcipriano" + - "@hseabolt" diff --git a/modules/nf-core/seqkit/pair/tests/main.nf.test b/modules/nf-core/seqkit/pair/tests/main.nf.test new file mode 100644 index 0000000..535ff00 --- /dev/null +++ b/modules/nf-core/seqkit/pair/tests/main.nf.test @@ -0,0 +1,66 @@ +nextflow_process { + + name "Test Process SEQKIT_PAIR" + script "../main.nf" + process "SEQKIT_PAIR" + config "./nextflow.config" + + tag "modules" + tag "modules_nfcore" + tag "seqkit" + tag "seqkit/pair" + + test("sarscov2 - bam") { + when { + params { + modules_args = '-u' + } + process { + """ + input[0] = [ + [ id:'test' ], // 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 { + assert process.success + assertAll( + { assert snapshot(sanitizeOutput(process.out)).match() } + ) + } + } + + test("sarscov2 - bam - stub") { + options '-stub' + + when { + params { + modules_args = '-u' + } + process { + """ + input[0] = [ + [ id:'test' ], // 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 { + assert process.success + assertAll( + { assert snapshot(sanitizeOutput(process.out)).match() } + ) + } + } +} diff --git a/modules/nf-core/seqkit/pair/tests/main.nf.test.snap b/modules/nf-core/seqkit/pair/tests/main.nf.test.snap new file mode 100644 index 0000000..9542638 --- /dev/null +++ b/modules/nf-core/seqkit/pair/tests/main.nf.test.snap @@ -0,0 +1,66 @@ +{ + "sarscov2 - bam - stub": { + "content": [ + { + "reads": [ + [ + { + "id": "test" + }, + [ + "test_1.paired.fastq.gz:md5,68b329da9893e34099c7d8ad5cb9c940", + "test_2.paired.fastq.gz:md5,68b329da9893e34099c7d8ad5cb9c940" + ] + ] + ], + "unpaired_reads": [ + + ], + "versions_seqkit": [ + [ + "SEQKIT_PAIR", + "seqkit", + "2.13.0" + ] + ] + } + ], + "timestamp": "2026-06-15T10:20:15.265249096", + "meta": { + "nf-test": "0.9.5", + "nextflow": "26.04.3" + } + }, + "sarscov2 - bam": { + "content": [ + { + "reads": [ + [ + { + "id": "test" + }, + [ + "test_1.paired.fastq.gz:md5,4161df271f9bfcd25d5845a1e220dbec", + "test_2.paired.fastq.gz:md5,2ebae722295ea66d84075a3b042e2b42" + ] + ] + ], + "unpaired_reads": [ + + ], + "versions_seqkit": [ + [ + "SEQKIT_PAIR", + "seqkit", + "2.13.0" + ] + ] + } + ], + "timestamp": "2026-06-12T17:25:41.233583224", + "meta": { + "nf-test": "0.9.5", + "nextflow": "26.04.3" + } + } +} \ No newline at end of file diff --git a/modules/nf-core/seqkit/pair/tests/nextflow.config b/modules/nf-core/seqkit/pair/tests/nextflow.config new file mode 100644 index 0000000..10b96f3 --- /dev/null +++ b/modules/nf-core/seqkit/pair/tests/nextflow.config @@ -0,0 +1,5 @@ +process { + withName: SEQKIT_PAIR { + ext.args = params.modules_args ?: '' + } +} diff --git a/modules/nf-core/seqkit/replace/environment.yml b/modules/nf-core/seqkit/replace/environment.yml new file mode 100644 index 0000000..09abb79 --- /dev/null +++ b/modules/nf-core/seqkit/replace/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::seqkit=2.13.0 diff --git a/modules/nf-core/seqkit/replace/main.nf b/modules/nf-core/seqkit/replace/main.nf new file mode 100644 index 0000000..18a1b99 --- /dev/null +++ b/modules/nf-core/seqkit/replace/main.nf @@ -0,0 +1,61 @@ +process SEQKIT_REPLACE { + tag "${meta.id}" + label 'process_low' + + conda "${moduleDir}/environment.yml" + container "${workflow.containerEngine in ['singularity', 'apptainer'] && !task.ext.singularity_pull_docker_container + ? 'https://community-cr-prod.seqera.io/docker/registry/v2/blobs/sha256/4f/4fe272ab9a519cf418160471a485b5ef50ea3f571a8e4555a826f70a4d8243ae/data' + : 'community.wave.seqera.io/library/seqkit:2.13.0--05c0a96bf9fb2751'}" + + input: + tuple val(meta), path(fastx) + val out_ext + + output: + tuple val(meta), path("*.fast*"), emit: fastx + tuple val("${task.process}"), val('seqkit'), eval("seqkit version | sed 's/^.*v//'"), emit: versions_seqkit, topic: versions + + when: + task.ext.when == null || task.ext.when + + script: + def args = task.ext.args ?: '' + def prefix = task.ext.prefix ?: "${meta.id}" + def extension = "fastq" + if ("${fastx}" ==~ /.+\.fasta|.+\.fasta.gz|.+\.fa|.+\.fa.gz|.+\.fas|.+\.fas.gz|.+\.fna|.+\.fna.gz|.+\.faa|.+\.faa.gz/) { + extension = "fasta" + } + def isgz = "" + if ("${fastx}" ==~ /.+\.gz/) { + isgz = ".gz" + } + def endswith = out_ext ?: "${extension}${isgz}" + """ + seqkit \\ + replace \\ + ${args} \\ + --threads ${task.cpus} \\ + -i ${fastx} \\ + -o ${prefix}.${endswith} + """ + + stub: + def args = task.ext.args ?: '' + def prefix = task.ext.prefix ?: "${meta.id}" + def extension = "fastq" + if ("${fastx}" ==~ /.+\.fasta|.+\.fasta.gz|.+\.fa|.+\.fa.gz|.+\.fas|.+\.fas.gz|.+\.fna|.+\.fna.gz|.+\.faa|.+\.faa.gz/) { + extension = "fasta" + } + def isgz = "" + if ("${fastx}" ==~ /.+\.gz/) { + isgz = ".gz" + } + def endswith = out_ext ?: "${extension}${isgz}" + + def create_cmd = endswith.endsWith('gz') ? "echo '' | gzip >" : "touch" + """ + echo ${args} + + ${create_cmd} ${prefix}.${endswith} + """ +} diff --git a/modules/nf-core/seqkit/replace/meta.yml b/modules/nf-core/seqkit/replace/meta.yml new file mode 100644 index 0000000..e8ef679 --- /dev/null +++ b/modules/nf-core/seqkit/replace/meta.yml @@ -0,0 +1,74 @@ +name: seqkit_replace +description: Use seqkit to find/replace strings within sequences and sequence headers +keywords: + - seqkit + - replace + - sequence + - sequence headers + - fasta +tools: + - seqkit: + description: Cross-platform and ultrafast toolkit for FASTA/Q file manipulation, + written by Wei Shen. + homepage: https://bioinf.shenwei.me/seqkit/usage/ + documentation: https://bioinf.shenwei.me/seqkit/usage/ + tool_dev_url: https://github.com/shenwei356/seqkit/ + doi: "10.1371/journal.pone.016396" + identifier: biotools:seqkit +input: + - - meta: + type: map + description: | + Groovy Map containing sample information + e.g. [ id:'test', single_end:false ] + - fastx: + type: file + description: fasta/q file + pattern: "*.{fasta,fastq,fa,fq,fas,fna,faa}*" + ontologies: + - edam: http://edamontology.org/format_1930 # FASTQ + - out_ext: + type: string + description: > + Output file extension. Leave empty to auto-detect from input (for example + `.fasta`, `.fasta.gz`) +output: + fastx: + - - meta: + type: map + description: | + Groovy Map containing sample information + e.g. [ id:'test', single_end:false ] + - "*.fast*": + type: file + description: fasta/q file with replaced values + pattern: "*.{fasta,fastq,fa,fq,fas,fna,faa}*" + ontologies: + - edam: http://edamontology.org/format_1930 # FASTQ + versions_seqkit: + - - ${task.process}: + type: string + description: The name of the process + - seqkit: + type: string + description: The name of the tool + - seqkit version | sed 's/^.*v//': + type: eval + description: The expression to obtain the version of seqkit + +topics: + versions: + - - ${task.process}: + type: string + description: The name of the process + - seqkit: + type: string + description: The name of the tool + - seqkit version | sed 's/^.*v//': + type: eval + description: The expression to obtain the version of seqkit + +authors: + - "@mjcipriano" +maintainers: + - "@mjcipriano" diff --git a/modules/nf-core/seqkit/replace/tests/main.nf.test b/modules/nf-core/seqkit/replace/tests/main.nf.test new file mode 100644 index 0000000..679312e --- /dev/null +++ b/modules/nf-core/seqkit/replace/tests/main.nf.test @@ -0,0 +1,111 @@ +nextflow_process { + + name "Test Process SEQKIT_REPLACE" + script "../main.nf" + process "SEQKIT_REPLACE" + + tag "modules" + tag "modules_nfcore" + tag "seqkit" + tag "seqkit/replace" + + config "./nextflow.config" + + test("sarscov2 - fasta - replace") { + when { + params { + modules_args = "-s -p 'A' -r 'N'" + } + process { + """ + input[0] = [ + [ id:'test' ], // meta map + file(params.modules_testdata_base_path + 'genomics/sarscov2/genome/proteome.fasta.gz', checkIfExists: true) + ] + input[1] = '' + """ + } + } + + then { + assert process.success + assertAll( + { assert snapshot(sanitizeOutput(process.out)).match() } + ) + } + } + + test("sarscov2 - fasta - replace - uncompressed") { + when { + params { + modules_args = "-s -p 'A' -r 'N'" + } + process { + """ + input[0] = [ + [ id:'test' ], // meta map + file(params.modules_testdata_base_path + 'genomics/sarscov2/genome/genome.fasta', checkIfExists: true) + ] + input[1] = '' + """ + } + } + + then { + assert process.success + assertAll( + { assert snapshot(sanitizeOutput(process.out)).match() } + ) + } + } + + test("sarscov2 - fasta - uncomp - custom") { + when { + params { + modules_args = "-s -p 'T' -r 'N'" + } + process { + """ + input[0] = [ + [ id:'test' ], // meta map + file(params.modules_testdata_base_path + 'genomics/sarscov2/genome/genome.fasta', checkIfExists: true) + ] + input[1] = 'fasta' + """ + } + } + + then { + assert process.success + assertAll( + { assert snapshot(sanitizeOutput(process.out)).match() } + ) + } + } + + test("sarscov2 - fasta - stub") { + options '-stub' + + when { + params { + modules_args = "" + } + process { + """ + input[0] = [ + [ id:'test' ], // meta map + file(params.modules_testdata_base_path + 'genomics/sarscov2/genome/genome.fasta', checkIfExists: true) + ] + input[1] = '' + """ + } + } + + then { + assert process.success + assertAll( + { assert snapshot(sanitizeOutput(process.out)).match() } + ) + } + } +} diff --git a/modules/nf-core/seqkit/replace/tests/main.nf.test.snap b/modules/nf-core/seqkit/replace/tests/main.nf.test.snap new file mode 100644 index 0000000..02bace1 --- /dev/null +++ b/modules/nf-core/seqkit/replace/tests/main.nf.test.snap @@ -0,0 +1,106 @@ +{ + "sarscov2 - fasta - stub": { + "content": [ + { + "fastx": [ + [ + { + "id": "test" + }, + "test.fasta:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "versions_seqkit": [ + [ + "SEQKIT_REPLACE", + "seqkit", + "2.13.0" + ] + ] + } + ], + "timestamp": "2026-06-13T12:47:21.226842064", + "meta": { + "nf-test": "0.9.5", + "nextflow": "26.04.3" + } + }, + "sarscov2 - fasta - replace - uncompressed": { + "content": [ + { + "fastx": [ + [ + { + "id": "test" + }, + "test.fasta:md5,b1518908253a4997fcad98270751112e" + ] + ], + "versions_seqkit": [ + [ + "SEQKIT_REPLACE", + "seqkit", + "2.13.0" + ] + ] + } + ], + "timestamp": "2026-06-12T17:26:01.449665395", + "meta": { + "nf-test": "0.9.5", + "nextflow": "26.04.3" + } + }, + "sarscov2 - fasta - uncomp - custom": { + "content": [ + { + "fastx": [ + [ + { + "id": "test" + }, + "test.fasta:md5,05d3294a62c72f5489f067c1da3c2f6c" + ] + ], + "versions_seqkit": [ + [ + "SEQKIT_REPLACE", + "seqkit", + "2.13.0" + ] + ] + } + ], + "timestamp": "2026-06-13T12:54:19.323281146", + "meta": { + "nf-test": "0.9.5", + "nextflow": "26.04.3" + } + }, + "sarscov2 - fasta - replace": { + "content": [ + { + "fastx": [ + [ + { + "id": "test" + }, + "test.fasta.gz:md5,c40eaff961f6f2a48bb7e8fd156ed5d7" + ] + ], + "versions_seqkit": [ + [ + "SEQKIT_REPLACE", + "seqkit", + "2.13.0" + ] + ] + } + ], + "timestamp": "2026-06-12T17:25:54.471016576", + "meta": { + "nf-test": "0.9.5", + "nextflow": "26.04.3" + } + } +} \ No newline at end of file diff --git a/modules/nf-core/seqkit/replace/tests/nextflow.config b/modules/nf-core/seqkit/replace/tests/nextflow.config new file mode 100644 index 0000000..09856f3 --- /dev/null +++ b/modules/nf-core/seqkit/replace/tests/nextflow.config @@ -0,0 +1,5 @@ +process { + withName: SEQKIT_REPLACE { + ext.args = params.modules_args ?: '' + } +} diff --git a/modules/nf-core/seqkit/rmdup/environment.yml b/modules/nf-core/seqkit/rmdup/environment.yml new file mode 100644 index 0000000..09abb79 --- /dev/null +++ b/modules/nf-core/seqkit/rmdup/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::seqkit=2.13.0 diff --git a/modules/nf-core/seqkit/rmdup/main.nf b/modules/nf-core/seqkit/rmdup/main.nf new file mode 100644 index 0000000..7f86538 --- /dev/null +++ b/modules/nf-core/seqkit/rmdup/main.nf @@ -0,0 +1,64 @@ +process SEQKIT_RMDUP { + tag "${meta.id}" + label 'process_low' + // File IO can be a bottleneck. See: https://bioinf.shenwei.me/seqkit/usage/#parallelization-of-cpu-intensive-jobs + + conda "${moduleDir}/environment.yml" + container "${workflow.containerEngine in ['singularity', 'apptainer'] && !task.ext.singularity_pull_docker_container + ? 'https://community-cr-prod.seqera.io/docker/registry/v2/blobs/sha256/4f/4fe272ab9a519cf418160471a485b5ef50ea3f571a8e4555a826f70a4d8243ae/data' + : 'community.wave.seqera.io/library/seqkit:2.13.0--05c0a96bf9fb2751'}" + + input: + tuple val(meta), path(fastx) + + output: + tuple val(meta), path("${prefix}.${extension}"), emit: fastx + tuple val(meta), path("*.log"), emit: log + tuple val("${task.process}"), val('seqkit'), eval("seqkit version | sed 's/^.*v//'"), emit: versions_seqkit, topic: versions + + when: + task.ext.when == null || task.ext.when + + script: + def args = task.ext.args ?: '' + prefix = task.ext.prefix ?: "${meta.id}" + extension = "fastq" + if ("${fastx}" ==~ /.+\.fasta|.+\.fasta.gz|.+\.fa|.+\.fa.gz|.+\.fas|.+\.fas.gz|.+\.fna|.+\.fna.gz|.+\.fsa|.+\.fsa.gz/) { + extension = "fasta" + } + extension = fastx.toString().endsWith('.gz') ? "${extension}.gz" : extension + // SeqKit/rmdup takes care of compressing the output: https://bioinf.shenwei.me/seqkit/usage/#rmdup + if ("${prefix}.${extension}" == "${fastx}") { + error("Input and output names are the same, use \"task.ext.prefix\" to disambiguate!") + } + """ + seqkit \\ + rmdup \\ + --threads ${task.cpus} \\ + ${args} \\ + ${fastx} \\ + -o ${prefix}.${extension} \\ + 2>| >(tee ${prefix}.log >&2) + """ + + stub: + def args = task.ext.args ?: '' + prefix = task.ext.prefix ?: "${meta.id}" + extension = "fastq" + if ("${fastx}" ==~ /.+\.fasta|.+\.fasta.gz|.+\.fa|.+\.fa.gz|.+\.fas|.+\.fas.gz|.+\.fna|.+\.fna.gz|.+\.fsa|.+\.fsa.gz/) { + extension = "fasta" + } + extension = fastx.toString().endsWith('.gz') ? "${extension}.gz" : extension + if ("${prefix}.${extension}" == "${fastx}") { + error("Input and output names are the same, use \"task.ext.prefix\" to disambiguate!") + } + def create_cmd = extension.endsWith('gz') ? "echo '' | gzip >" : "touch" + """ + echo ${args} + + ${create_cmd} ${prefix}.${extension} + echo \\ + '[INFO] 0 duplicated records removed' \\ + > ${prefix}.log + """ +} diff --git a/modules/nf-core/seqkit/rmdup/meta.yml b/modules/nf-core/seqkit/rmdup/meta.yml new file mode 100644 index 0000000..e8dd3f0 --- /dev/null +++ b/modules/nf-core/seqkit/rmdup/meta.yml @@ -0,0 +1,80 @@ +# yaml-language-server: $schema=https://raw.githubusercontent.com/nf-core/modules/master/modules/meta-schema.json +name: "seqkit_rmdup" +description: Transforms sequences (extract ID, filter by length, remove gaps, reverse + complement...) +keywords: + - genomics + - fasta + - fastq + - remove + - duplicates +tools: + - "seqkit": + description: "A cross-platform and ultrafast toolkit for FASTA/Q file manipulation" + homepage: "https://bioinf.shenwei.me/seqkit/" + documentation: "https://bioinf.shenwei.me/seqkit/usage/" + tool_dev_url: "https://github.com/shenwei356/seqkit" + doi: "10.1371/journal.pone.0163962" + licence: ["MIT"] + identifier: biotools:seqkit +input: + - - meta: + type: map + description: | + Groovy Map containing sample information + e.g. `[ id:'sample1' ]` + - fastx: + type: file + description: Input fasta/fastq file + pattern: "*.{fsa,fas,fa,fasta,fastq,fq,fsa.gz,fas.gz,fa.gz,fasta.gz,fastq.gz,fq.gz}" + ontologies: + - edam: http://edamontology.org/format_1930 # FASTQ +output: + fastx: + - - meta: + type: map + description: | + Groovy Map containing sample information + e.g. `[ id:'sample1' ]` + - ${prefix}.${extension}: + type: file + description: Output fasta/fastq file + pattern: "*.{fasta,fasta.gz,fastq,fastq.gz}" + ontologies: + - edam: http://edamontology.org/format_1930 # FASTQ + log: + - - meta: + type: map + description: | + Groovy Map containing sample information + e.g. `[ id:'sample1' ]` + - "*.log": + type: file + description: Log containing information regarding removed duplicates + pattern: "*.log" + ontologies: [] + versions_seqkit: + - - ${task.process}: + type: string + description: The name of the process + - seqkit: + type: string + description: The name of the tool + - seqkit version | sed 's/^.*v//': + type: eval + description: The expression to obtain the version of the tool +topics: + versions: + - - ${task.process}: + type: string + description: The name of the process + - seqkit: + type: string + description: The name of the tool + - seqkit version | sed 's/^.*v//': + type: eval + description: The expression to obtain the version of the tool +authors: + - "@GallVp" +maintainers: + - "@GallVp" diff --git a/modules/nf-core/seqkit/rmdup/tests/main.nf.test b/modules/nf-core/seqkit/rmdup/tests/main.nf.test new file mode 100644 index 0000000..411a2dd --- /dev/null +++ b/modules/nf-core/seqkit/rmdup/tests/main.nf.test @@ -0,0 +1,163 @@ +nextflow_process { + + name "Test Process SEQKIT_RMDUP" + script "../main.nf" + process "SEQKIT_RMDUP" + + tag "modules" + tag "modules_nfcore" + tag "seqkit" + tag "seqkit/rmdup" + + test("sarscov2-genome_fasta") { + when { + process { + """ + input[0] = [ + [ id:'test' ], // meta map + file(params.modules_testdata_base_path + 'genomics/sarscov2/genome/genome.fasta', checkIfExists: true) + ] + """ + } + } + + then { + assert process.success + assertAll( + { assert snapshot(sanitizeOutput(process.out)).match() }, + { assert path(process.out.log[0][1]).text.contains('0 duplicated records removed') } + ) + } + } + + test("repeated-fasta") { + when { + process { + """ + def repeated_fasta = file('repeated.fasta') + repeated_fasta.text = '>A\\nAGCTAGCTAGCT\\n>B\\nAGCTAGCTAGCT\\n>A\\nAGCTAGCTAGCT' + + input[0] = [ + [ id:'test' ], // meta map + repeated_fasta + ] + """ + } + } + + then { + assert process.success + assertAll( + { assert snapshot(sanitizeOutput(process.out)).match() }, + { assert path(process.out.log[0][1]).text.contains('1 duplicated records removed') } + ) + } + } + + test("sarscov2-genome_fasta_gz") { + when { + process { + """ + input[0] = [ + [ id:'test' ], // meta map + file(params.modules_testdata_base_path + 'genomics/sarscov2/genome/genome.fasta.gz', checkIfExists: true) + ] + """ + } + } + + then { + assert process.success + assertAll( + { assert snapshot(sanitizeOutput(process.out)).match() }, + { assert path(process.out.log[0][1]).text.contains('0 duplicated records removed') } + ) + } + } + + test("sarscov2-test_1_fastq_gz") { + when { + process { + """ + input[0] = [ + [ id:'test' ], // meta map + file(params.modules_testdata_base_path + 'genomics/sarscov2/illumina/fastq/test_1.fastq.gz', checkIfExists: true) + ] + """ + } + } + + then { + assert process.success + assertAll( + { assert snapshot(sanitizeOutput(process.out)).match() }, + { assert path(process.out.log[0][1]).text.contains('0 duplicated records removed') } + ) + } + } + + test("file_name_conflict-fail_with_error") { + when { + process { + """ + input[0] = [ + [ id:'test_1' ], // meta map + file(params.modules_testdata_base_path + 'genomics/sarscov2/illumina/fastq/test_1.fastq.gz', checkIfExists: true) + ] + """ + } + } + + then { + assert process.failed + assertAll( + { assert process.stdout.toString().contains("Input and output names are the same") } + ) + } + } + + test("sarscov2-genome_fasta-stub") { + options '-stub' + + when { + process { + """ + input[0] = [ + [ id:'test' ], // meta map + file(params.modules_testdata_base_path + 'genomics/sarscov2/genome/genome.fasta', checkIfExists: true) + ] + """ + } + } + + then { + assert process.success + assertAll( + { assert snapshot(sanitizeOutput(process.out)).match() }, + { assert path(process.out.log[0][1]).text.contains('0 duplicated records removed') } + ) + } + } + + test("file_name_conflict-fail_with_error-stub") { + options '-stub' + + when { + process { + """ + input[0] = [ + [ id:'genome' ], // meta map + file(params.modules_testdata_base_path + 'genomics/sarscov2/genome/genome.fasta', checkIfExists: true) + ] + """ + } + } + + then { + assert process.failed + assertAll( + { assert process.stdout.toString().contains("Input and output names are the same") } + ) + } + } +} diff --git a/modules/nf-core/seqkit/rmdup/tests/main.nf.test.snap b/modules/nf-core/seqkit/rmdup/tests/main.nf.test.snap new file mode 100644 index 0000000..0bd79b3 --- /dev/null +++ b/modules/nf-core/seqkit/rmdup/tests/main.nf.test.snap @@ -0,0 +1,172 @@ +{ + "sarscov2-genome_fasta-stub": { + "content": [ + { + "fastx": [ + [ + { + "id": "test" + }, + "test.fasta:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "log": [ + [ + { + "id": "test" + }, + "test.log:md5,cf833211befdf890bb6b2a3cd0b91853" + ] + ], + "versions_seqkit": [ + [ + "SEQKIT_RMDUP", + "seqkit", + "2.13.0" + ] + ] + } + ], + "timestamp": "2026-06-12T17:26:48.435786445", + "meta": { + "nf-test": "0.9.5", + "nextflow": "26.04.3" + } + }, + "sarscov2-test_1_fastq_gz": { + "content": [ + { + "fastx": [ + [ + { + "id": "test" + }, + "test.fastq.gz:md5,4161df271f9bfcd25d5845a1e220dbec" + ] + ], + "log": [ + [ + { + "id": "test" + }, + "test.log:md5,a41135cfe024baaf42f135583fe73f0d" + ] + ], + "versions_seqkit": [ + [ + "SEQKIT_RMDUP", + "seqkit", + "2.13.0" + ] + ] + } + ], + "timestamp": "2026-06-12T17:26:39.040489053", + "meta": { + "nf-test": "0.9.5", + "nextflow": "26.04.3" + } + }, + "sarscov2-genome_fasta": { + "content": [ + { + "fastx": [ + [ + { + "id": "test" + }, + "test.fasta:md5,483f4a5dfe60171c86ee9b7e6dff908b" + ] + ], + "log": [ + [ + { + "id": "test" + }, + "test.log:md5,a41135cfe024baaf42f135583fe73f0d" + ] + ], + "versions_seqkit": [ + [ + "SEQKIT_RMDUP", + "seqkit", + "2.13.0" + ] + ] + } + ], + "timestamp": "2026-06-12T17:26:21.035761383", + "meta": { + "nf-test": "0.9.5", + "nextflow": "26.04.3" + } + }, + "repeated-fasta": { + "content": [ + { + "fastx": [ + [ + { + "id": "test" + }, + "test.fasta:md5,7510a742291241e7d7556bf720caf65c" + ] + ], + "log": [ + [ + { + "id": "test" + }, + "test.log:md5,314c0aaef0f832a217a3f6ce3f8bc117" + ] + ], + "versions_seqkit": [ + [ + "SEQKIT_RMDUP", + "seqkit", + "2.13.0" + ] + ] + } + ], + "timestamp": "2026-06-12T17:26:26.868734329", + "meta": { + "nf-test": "0.9.5", + "nextflow": "26.04.3" + } + }, + "sarscov2-genome_fasta_gz": { + "content": [ + { + "fastx": [ + [ + { + "id": "test" + }, + "test.fasta.gz:md5,483f4a5dfe60171c86ee9b7e6dff908b" + ] + ], + "log": [ + [ + { + "id": "test" + }, + "test.log:md5,a41135cfe024baaf42f135583fe73f0d" + ] + ], + "versions_seqkit": [ + [ + "SEQKIT_RMDUP", + "seqkit", + "2.13.0" + ] + ] + } + ], + "timestamp": "2026-06-12T17:26:33.522032009", + "meta": { + "nf-test": "0.9.5", + "nextflow": "26.04.3" + } + } +} \ No newline at end of file diff --git a/modules/nf-core/seqkit/sana/environment.yml b/modules/nf-core/seqkit/sana/environment.yml new file mode 100644 index 0000000..09abb79 --- /dev/null +++ b/modules/nf-core/seqkit/sana/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::seqkit=2.13.0 diff --git a/modules/nf-core/seqkit/sana/main.nf b/modules/nf-core/seqkit/sana/main.nf new file mode 100644 index 0000000..dd800ba --- /dev/null +++ b/modules/nf-core/seqkit/sana/main.nf @@ -0,0 +1,43 @@ +process SEQKIT_SANA { + tag "${meta.id}" + label 'process_single' + + conda "${moduleDir}/environment.yml" + container "${workflow.containerEngine in ['singularity', 'apptainer'] && !task.ext.singularity_pull_docker_container + ? 'https://community-cr-prod.seqera.io/docker/registry/v2/blobs/sha256/4f/4fe272ab9a519cf418160471a485b5ef50ea3f571a8e4555a826f70a4d8243ae/data' + : 'community.wave.seqera.io/library/seqkit:2.13.0--05c0a96bf9fb2751'}" + + input: + tuple val(meta), path(reads) + + output: + tuple val(meta), path("${prefix}${extension}"), emit: reads + tuple val(meta), path("${prefix}.log"), emit: log + tuple val("${task.process}"), val('seqkit'), eval("seqkit version | sed 's/^.*v//'"), emit: versions_seqkit, topic: versions + + when: + task.ext.when == null || task.ext.when + + script: + def args = task.ext.args ?: '' + prefix = task.ext.prefix ?: "${meta.id}" + extension = reads.getName() - reads.getSimpleName() + """ + seqkit sana \\ + ${args} \\ + ${reads} \\ + -o ${prefix}${extension} > ${prefix}.log 2>&1 + """ + + stub: + def args = task.ext.args ?: '' + prefix = task.ext.prefix ?: "${meta.id}" + extension = reads.getName() - reads.getSimpleName() + def create_cmd = extension.endsWith('gz') ? "echo '' | gzip >" : "touch" + """ + echo ${args} + + ${create_cmd} ${prefix}${extension} + touch ${prefix}.log + """ +} diff --git a/modules/nf-core/seqkit/sana/meta.yml b/modules/nf-core/seqkit/sana/meta.yml new file mode 100644 index 0000000..6941d25 --- /dev/null +++ b/modules/nf-core/seqkit/sana/meta.yml @@ -0,0 +1,71 @@ +name: "seqkit_sana" +description: Sanitize broken single line FASTQ files +keywords: + - fastq + - quality check + - skip malformed + - parser +tools: + - "seqkit": + description: A cross-platform and ultrafast toolkit for FASTA/Q file manipulation. + homepage: "https://bioinf.shenwei.me/seqkit" + documentation: "https://bioinf.shenwei.me/seqkit" + tool_dev_url: "https://github.com/shenwei356/seqkit" + doi: "10.1371/journal.pone.0163962" + licence: ["MIT"] + identifier: biotools:seqkit +input: + - - meta: + type: map + description: Groovy Map containing sample information. e.g. [ id:'sample1'] + - reads: + type: file + description: One line for each sequence and quality value + pattern: "*.{fq,fastq}{,.gz}" + ontologies: + - edam: http://edamontology.org/format_1930 # FASTQ +output: + reads: + - - meta: + type: map + description: Groovy Map containing sample information. e.g. [ id:'sample1'] + - ${prefix}${extension}: + type: file + description: Parsed fastq file without malformed entries/lines + pattern: "*.${extension}" + ontologies: + - edam: http://edamontology.org/format_1930 # FASTQ + log: + - - meta: + type: map + description: Groovy Map containing sample information. e.g. [ id:'sample1'] + - ${prefix}.log: + type: file + description: Log file produced by the seqkit/sana software + pattern: "*.log" + ontologies: [] + versions_seqkit: + - - ${task.process}: + type: string + description: The name of the process + - seqkit: + type: string + description: The name of the tool + - seqkit version | sed 's/^.*v//': + type: eval + description: The expression to obtain the version of the tool +topics: + versions: + - - ${task.process}: + type: string + description: The name of the process + - seqkit: + type: string + description: The name of the tool + - seqkit version | sed 's/^.*v//': + type: eval + description: The expression to obtain the version of the tool +authors: + - "@vagkaratzas" +maintainers: + - "@vagkaratzas" diff --git a/modules/nf-core/seqkit/sana/tests/main.nf.test b/modules/nf-core/seqkit/sana/tests/main.nf.test new file mode 100644 index 0000000..026746c --- /dev/null +++ b/modules/nf-core/seqkit/sana/tests/main.nf.test @@ -0,0 +1,53 @@ +nextflow_process { + + name "Test Process SEQKIT_SANA" + script "../main.nf" + process "SEQKIT_SANA" + + tag "modules" + tag "modules_nfcore" + tag "seqkit" + tag "seqkit/sana" + + test("sarscov2 - fastq") { + when { + process { + """ + input[0] = [ + [ id:'test', single_end:true ], // meta map + file(params.modules_testdata_base_path + 'genomics/sarscov2/illumina/fastq/test_1_broken.fastq.gz', checkIfExists: true) + ] + """ + } + } + + then { + assert process.success + assertAll( + { assert snapshot(sanitizeOutput(process.out)).match() } + ) + } + } + + test("sarscov2 - fastq - 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_broken.fastq.gz', checkIfExists: true) + ] + """ + } + } + + then { + assert process.success + assertAll( + { assert snapshot(sanitizeOutput(process.out)).match() } + ) + } + } +} diff --git a/modules/nf-core/seqkit/sana/tests/main.nf.test.snap b/modules/nf-core/seqkit/sana/tests/main.nf.test.snap new file mode 100644 index 0000000..8971a2e --- /dev/null +++ b/modules/nf-core/seqkit/sana/tests/main.nf.test.snap @@ -0,0 +1,74 @@ +{ + "sarscov2 - fastq - stub": { + "content": [ + { + "log": [ + [ + { + "id": "test", + "single_end": true + }, + "test.log:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "reads": [ + [ + { + "id": "test", + "single_end": true + }, + "test.fastq.gz:md5,68b329da9893e34099c7d8ad5cb9c940" + ] + ], + "versions_seqkit": [ + [ + "SEQKIT_SANA", + "seqkit", + "2.13.0" + ] + ] + } + ], + "timestamp": "2026-06-15T10:29:52.287449164", + "meta": { + "nf-test": "0.9.5", + "nextflow": "26.04.3" + } + }, + "sarscov2 - fastq": { + "content": [ + { + "log": [ + [ + { + "id": "test", + "single_end": true + }, + "test.log:md5,72633c56cad32996aa9c7daa9e687e59" + ] + ], + "reads": [ + [ + { + "id": "test", + "single_end": true + }, + "test.fastq.gz:md5,03b43487ca6aeecc5eb8b55bbc1fe2f3" + ] + ], + "versions_seqkit": [ + [ + "SEQKIT_SANA", + "seqkit", + "2.13.0" + ] + ] + } + ], + "timestamp": "2026-06-13T12:30:27.845745504", + "meta": { + "nf-test": "0.9.5", + "nextflow": "26.04.3" + } + } +} \ No newline at end of file diff --git a/modules/nf-core/seqkit/seq/environment.yml b/modules/nf-core/seqkit/seq/environment.yml new file mode 100644 index 0000000..09abb79 --- /dev/null +++ b/modules/nf-core/seqkit/seq/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::seqkit=2.13.0 diff --git a/modules/nf-core/seqkit/seq/main.nf b/modules/nf-core/seqkit/seq/main.nf new file mode 100644 index 0000000..3bade4e --- /dev/null +++ b/modules/nf-core/seqkit/seq/main.nf @@ -0,0 +1,63 @@ +process SEQKIT_SEQ { + tag "${meta.id}" + label 'process_low' + // File IO can be a bottleneck. See: https://bioinf.shenwei.me/seqkit/usage/#parallelization-of-cpu-intensive-jobs + + conda "${moduleDir}/environment.yml" + container "${workflow.containerEngine in ['singularity', 'apptainer'] && !task.ext.singularity_pull_docker_container + ? 'https://community-cr-prod.seqera.io/docker/registry/v2/blobs/sha256/4f/4fe272ab9a519cf418160471a485b5ef50ea3f571a8e4555a826f70a4d8243ae/data' + : 'community.wave.seqera.io/library/seqkit:2.13.0--05c0a96bf9fb2751'}" + + input: + tuple val(meta), path(fastx) + + output: + tuple val(meta), path("${prefix}.*"), emit: fastx + tuple val("${task.process}"), val('seqkit'), eval("seqkit version | sed 's/^.*v//'"), emit: versions_seqkit, topic: versions + + when: + task.ext.when == null || task.ext.when + + script: + def args = task.ext.args ?: '' + def args2 = task.ext.args2 ?: '' + prefix = task.ext.prefix ?: "${meta.id}" + def extension = "fastq" + if ("${fastx}" ==~ /.+\.fasta|.+\.fasta.gz|.+\.fa|.+\.fa.gz|.+\.fas|.+\.fas.gz|.+\.fna|.+\.fna.gz|.+\.fsa|.+\.fsa.gz/) { + extension = "fasta" + } + extension = fastx.toString().endsWith('.gz') ? "${extension}.gz" : extension + def call_gzip = extension.endsWith('.gz') ? "| gzip -c ${args2}" : '' + if ("${prefix}.${extension}" == "${fastx}") { + error("Input and output names are the same, use \"task.ext.prefix\" to disambiguate!") + } + """ + seqkit \\ + seq \\ + --threads ${task.cpus} \\ + ${args} \\ + ${fastx} \\ + ${call_gzip} \\ + > ${prefix}.${extension} + """ + + stub: + def args = task.ext.args ?: '' + def args2 = task.ext.args2 ?: '' + prefix = task.ext.prefix ?: "${meta.id}" + def extension = "fastq" + if ("${fastx}" ==~ /.+\.fasta|.+\.fasta.gz|.+\.fa|.+\.fa.gz|.+\.fas|.+\.fas.gz|.+\.fna|.+\.fna.gz|.+\.fsa|.+\.fsa.gz/) { + extension = "fasta" + } + extension = fastx.toString().endsWith('.gz') ? "${extension}.gz" : extension + if ("${prefix}.${extension}" == "${fastx}") { + error("Input and output names are the same, use \"task.ext.prefix\" to disambiguate!") + } + def create_cmd = extension.endsWith('gz') ? "echo '' | gzip >" : "touch" + """ + echo ${args} + echo ${args2} + + ${create_cmd} ${prefix}.${extension} + """ +} diff --git a/modules/nf-core/seqkit/seq/meta.yml b/modules/nf-core/seqkit/seq/meta.yml new file mode 100644 index 0000000..df93e0c --- /dev/null +++ b/modules/nf-core/seqkit/seq/meta.yml @@ -0,0 +1,71 @@ +# yaml-language-server: $schema=https://raw.githubusercontent.com/nf-core/modules/master/modules/meta-schema.json +name: "seqkit_seq" +description: Transforms sequences (extract ID, filter by length, remove gaps, reverse + complement...) +keywords: + - genomics + - fasta + - fastq + - transform + - filter + - gaps + - complement +tools: + - "seqkit": + description: "A cross-platform and ultrafast toolkit for FASTA/Q file manipulation" + homepage: "https://bioinf.shenwei.me/seqkit/" + documentation: "https://bioinf.shenwei.me/seqkit/usage/" + tool_dev_url: "https://github.com/shenwei356/seqkit" + doi: "10.1371/journal.pone.0163962" + licence: ["MIT"] + identifier: biotools:seqkit +input: + - - meta: + type: map + description: | + Groovy Map containing sample information + e.g. `[ id:'sample1' ]` + - fastx: + type: file + description: Input fasta/fastq file + pattern: "*.{fsa,fas,fa,fasta,fastq,fq,fsa.gz,fas.gz,fa.gz,fasta.gz,fastq.gz,fq.gz}" + ontologies: + - edam: http://edamontology.org/format_1930 # FASTQ +output: + fastx: + - - meta: + type: map + description: | + Groovy Map containing sample information + e.g. `[ id:'sample1' ]` + - ${prefix}.*: + type: file + description: Output fasta/fastq file + pattern: "*.{fasta,fasta.gz,fastq,fastq.gz}" + ontologies: + - edam: http://edamontology.org/format_1930 # FASTQ + versions_seqkit: + - - ${task.process}: + type: string + description: The name of the process + - seqkit: + type: string + description: The name of the tool + - seqkit version | sed 's/^.*v//': + type: eval + description: The expression to obtain the version of the tool +topics: + versions: + - - ${task.process}: + type: string + description: The name of the process + - seqkit: + type: string + description: The name of the tool + - seqkit version | sed 's/^.*v//': + type: eval + description: The expression to obtain the version of the tool +authors: + - "@GallVp" +maintainers: + - "@GallVp" diff --git a/modules/nf-core/seqkit/seq/tests/main.nf.test b/modules/nf-core/seqkit/seq/tests/main.nf.test new file mode 100644 index 0000000..457be88 --- /dev/null +++ b/modules/nf-core/seqkit/seq/tests/main.nf.test @@ -0,0 +1,154 @@ +nextflow_process { + + name "Test Process SEQKIT_SEQ" + script "../main.nf" + process "SEQKIT_SEQ" + config './nextflow.config' + + tag "modules" + tag "modules_nfcore" + tag "seqkit" + tag "seqkit/seq" + + test("sarscov2-genome_fasta") { + when { + params { + modules_args2 = '-n' + } + process { + """ + input[0] = [ + [ id:'test' ], // meta map + file(params.modules_testdata_base_path + 'genomics/sarscov2/genome/genome.fasta', checkIfExists: true) + ] + """ + } + } + + then { + assert process.success + assertAll( + { assert snapshot(sanitizeOutput(process.out)).match() } + ) + } + } + + test("sarscov2-genome_fasta_gz") { + when { + params { + modules_args2 = '-n' + } + process { + """ + input[0] = [ + [ id:'test' ], // meta map + file(params.modules_testdata_base_path + 'genomics/sarscov2/genome/genome.fasta.gz', checkIfExists: true) + ] + """ + } + } + + then { + assert process.success + assertAll( + { assert snapshot(sanitizeOutput(process.out)).match() } + ) + } + } + + test("sarscov2-test_1_fastq_gz") { + when { + params { + modules_args2 = '-n' + } + process { + """ + input[0] = [ + [ id:'test' ], // meta map + file(params.modules_testdata_base_path + 'genomics/sarscov2/illumina/fastq/test_1.fastq.gz', checkIfExists: true) + ] + """ + } + } + + then { + assert process.success + assertAll( + { assert snapshot(sanitizeOutput(process.out)).match() } + ) + } + } + + test("file_name_conflict-fail_with_error") { + when { + params { + modules_args2 = '-n' + } + process { + """ + input[0] = [ + [ id:'test_1' ], // meta map + file(params.modules_testdata_base_path + 'genomics/sarscov2/illumina/fastq/test_1.fastq.gz', checkIfExists: true) + ] + """ + } + } + + then { + assert process.failed + assertAll( + { assert process.stdout.toString().contains("Input and output names are the same") } + ) + } + } + + test("sarscov2-genome_fasta-stub") { + options '-stub' + + when { + params { + modules_args2 = '-n' + } + process { + """ + input[0] = [ + [ id:'test' ], // meta map + file(params.modules_testdata_base_path + 'genomics/sarscov2/genome/genome.fasta', checkIfExists: true) + ] + """ + } + } + + then { + assert process.success + assertAll( + { assert snapshot(sanitizeOutput(process.out)).match() } + ) + } + } + + test("file_name_conflict-fail_with_error-stub") { + options '-stub' + + when { + params { + modules_args2 = '-n' + } + process { + """ + input[0] = [ + [ id:'genome' ], // meta map + file(params.modules_testdata_base_path + 'genomics/sarscov2/genome/genome.fasta', checkIfExists: true) + ] + """ + } + } + + then { + assert process.failed + assertAll( + { assert process.stdout.toString().contains("Input and output names are the same") } + ) + } + } +} diff --git a/modules/nf-core/seqkit/seq/tests/main.nf.test.snap b/modules/nf-core/seqkit/seq/tests/main.nf.test.snap new file mode 100644 index 0000000..d8e79f6 --- /dev/null +++ b/modules/nf-core/seqkit/seq/tests/main.nf.test.snap @@ -0,0 +1,106 @@ +{ + "sarscov2-genome_fasta-stub": { + "content": [ + { + "fastx": [ + [ + { + "id": "test" + }, + "test.fasta:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "versions_seqkit": [ + [ + "SEQKIT_SEQ", + "seqkit", + "2.13.0" + ] + ] + } + ], + "timestamp": "2026-06-14T18:54:38.462339108", + "meta": { + "nf-test": "0.9.5", + "nextflow": "26.04.3" + } + }, + "sarscov2-test_1_fastq_gz": { + "content": [ + { + "fastx": [ + [ + { + "id": "test" + }, + "test.fastq.gz:md5,4161df271f9bfcd25d5845a1e220dbec" + ] + ], + "versions_seqkit": [ + [ + "SEQKIT_SEQ", + "seqkit", + "2.13.0" + ] + ] + } + ], + "timestamp": "2026-06-12T17:27:53.391265296", + "meta": { + "nf-test": "0.9.5", + "nextflow": "26.04.3" + } + }, + "sarscov2-genome_fasta": { + "content": [ + { + "fastx": [ + [ + { + "id": "test" + }, + "test.fasta:md5,483f4a5dfe60171c86ee9b7e6dff908b" + ] + ], + "versions_seqkit": [ + [ + "SEQKIT_SEQ", + "seqkit", + "2.13.0" + ] + ] + } + ], + "timestamp": "2026-06-12T17:27:43.147566645", + "meta": { + "nf-test": "0.9.5", + "nextflow": "26.04.3" + } + }, + "sarscov2-genome_fasta_gz": { + "content": [ + { + "fastx": [ + [ + { + "id": "test" + }, + "test.fasta.gz:md5,483f4a5dfe60171c86ee9b7e6dff908b" + ] + ], + "versions_seqkit": [ + [ + "SEQKIT_SEQ", + "seqkit", + "2.13.0" + ] + ] + } + ], + "timestamp": "2026-06-12T17:27:48.241605452", + "meta": { + "nf-test": "0.9.5", + "nextflow": "26.04.3" + } + } +} \ No newline at end of file diff --git a/modules/nf-core/seqkit/seq/tests/nextflow.config b/modules/nf-core/seqkit/seq/tests/nextflow.config new file mode 100644 index 0000000..cd1f022 --- /dev/null +++ b/modules/nf-core/seqkit/seq/tests/nextflow.config @@ -0,0 +1,5 @@ +process { + withName: SEQKIT_SEQ { + ext.args2 = params.modules_args2 ?: '' + } +} diff --git a/modules/nf-core/seqkit/stats/environment.yml b/modules/nf-core/seqkit/stats/environment.yml new file mode 100644 index 0000000..09abb79 --- /dev/null +++ b/modules/nf-core/seqkit/stats/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::seqkit=2.13.0 diff --git a/modules/nf-core/seqkit/stats/main.nf b/modules/nf-core/seqkit/stats/main.nf new file mode 100644 index 0000000..dd9e2bc --- /dev/null +++ b/modules/nf-core/seqkit/stats/main.nf @@ -0,0 +1,39 @@ +process SEQKIT_STATS { + tag "${meta.id}" + label 'process_low' + + conda "${moduleDir}/environment.yml" + container "${workflow.containerEngine in ['singularity', 'apptainer'] && !task.ext.singularity_pull_docker_container + ? 'https://community-cr-prod.seqera.io/docker/registry/v2/blobs/sha256/4f/4fe272ab9a519cf418160471a485b5ef50ea3f571a8e4555a826f70a4d8243ae/data' + : 'community.wave.seqera.io/library/seqkit:2.13.0--05c0a96bf9fb2751'}" + + input: + tuple val(meta), path(reads) + + output: + tuple val(meta), path("*.tsv"), emit: stats + tuple val("${task.process}"), val('seqkit'), eval("seqkit version | sed 's/^.*v//'"), emit: versions_seqkit, topic: versions + + when: + task.ext.when == null || task.ext.when + + script: + def args = task.ext.args ?: '--all' + def prefix = task.ext.prefix ?: "${meta.id}" + """ + seqkit stats \\ + --tabular \\ + --threads ${task.cpus} \\ + ${args} \\ + ${reads} > '${prefix}.tsv' + """ + + stub: + def args = task.ext.args ?: '--all' + def prefix = task.ext.prefix ?: "${meta.id}" + """ + echo ${args} + + touch ${prefix}.tsv + """ +} diff --git a/modules/nf-core/seqkit/stats/meta.yml b/modules/nf-core/seqkit/stats/meta.yml new file mode 100644 index 0000000..3143449 --- /dev/null +++ b/modules/nf-core/seqkit/stats/meta.yml @@ -0,0 +1,72 @@ +name: "seqkit_stats" +description: simple statistics of FASTA/Q files +keywords: + - seqkit + - fasta + - stats +tools: + - "seqkit": + description: Cross-platform and ultrafast toolkit for FASTA/Q file manipulation, + written by Wei Shen. + homepage: https://bioinf.shenwei.me/seqkit/usage/ + documentation: https://bioinf.shenwei.me/seqkit/usage/ + tool_dev_url: https://github.com/shenwei356/seqkit/ + doi: "10.1371/journal.pone.0163962" + licence: ["MIT"] + identifier: biotools:seqkit +input: + - - meta: + type: map + description: > + Groovy Map containing sample information e.g. [ id:'test', single_end:false + ] + - reads: + type: file + description: > + Either FASTA or FASTQ files. + pattern: "*.{fa,fna,faa,fasta,fq,fastq}[.gz]" + ontologies: + - edam: http://edamontology.org/format_1930 # FASTQ +output: + stats: + - - meta: + type: map + description: > + Groovy Map containing sample information e.g. [ id:'test', single_end:false + ] + - "*.tsv": + type: file + description: > + Tab-separated output file with basic sequence statistics. + pattern: "*.tsv" + ontologies: + - edam: http://edamontology.org/format_3475 # TSV + versions_seqkit: + - - ${task.process}: + type: string + description: The name of the process + - seqkit: + type: string + description: The name of the tool + - seqkit version | sed 's/^.*v//': + type: eval + description: The expression to obtain the version of seqkit + +topics: + versions: + - - ${task.process}: + type: string + description: The name of the process + - seqkit: + type: string + description: The name of the tool + - seqkit version | sed 's/^.*v//': + type: eval + description: The expression to obtain the version of seqkit + +authors: + - "@Midnighter" + - "@heuermh" +maintainers: + - "@Midnighter" + - "@heuermh" diff --git a/modules/nf-core/seqkit/stats/tests/main.nf.test b/modules/nf-core/seqkit/stats/tests/main.nf.test new file mode 100644 index 0000000..aa942ec --- /dev/null +++ b/modules/nf-core/seqkit/stats/tests/main.nf.test @@ -0,0 +1,136 @@ +nextflow_process { + + name "Test Process SEQKIT_STATS" + script "../main.nf" + process "SEQKIT_STATS" + + tag "modules" + tag "modules_nfcore" + tag "seqkit" + tag "seqkit/stats" + + test("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 { + assert process.success + assertAll( + { assert snapshot(sanitizeOutput(process.out)).match() } + ) + } + } + + test("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 { + assert process.success + assertAll( + { assert snapshot(sanitizeOutput(process.out)).match() } + ) + } + } + + test("nanopore") { + when { + process { + """ + input[0] = [ + [ id:'test', single_end:true ], // meta map + file(params.modules_testdata_base_path + 'genomics/sarscov2/nanopore/fastq/test.fastq.gz', checkIfExists: true) + ] + """ + } + } + + then { + assert process.success + assertAll( + { assert snapshot(sanitizeOutput(process.out)).match() } + ) + } + } + + test("genome_fasta") { + when { + process { + """ + input[0] = [ + [ id:'test', single_end:false ], // meta map + file(params.modules_testdata_base_path + 'genomics/sarscov2/genome/genome.fasta', checkIfExists: true) + ] + """ + } + } + + then { + assert process.success + assertAll( + { assert snapshot(sanitizeOutput(process.out)).match() } + ) + } + } + + test("transcriptome_fasta") { + when { + process { + """ + input[0] = [ + [ id:'test', single_end:false ], // meta map + file(params.modules_testdata_base_path + 'genomics/sarscov2/genome/transcriptome.fasta', checkIfExists: true) + ] + """ + } + } + + then { + assert process.success + assertAll( + { assert snapshot(sanitizeOutput(process.out)).match() } + ) + } + } + + test("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 { + assert process.success + assertAll( + { assert snapshot(sanitizeOutput(process.out)).match() } + ) + } + } +} diff --git a/modules/nf-core/seqkit/stats/tests/main.nf.test.snap b/modules/nf-core/seqkit/stats/tests/main.nf.test.snap new file mode 100644 index 0000000..6c75aa0 --- /dev/null +++ b/modules/nf-core/seqkit/stats/tests/main.nf.test.snap @@ -0,0 +1,164 @@ +{ + "nanopore": { + "content": [ + { + "stats": [ + [ + { + "id": "test", + "single_end": true + }, + "test.tsv:md5,388047b0263b8d0a3041d6d80b316782" + ] + ], + "versions_seqkit": [ + [ + "SEQKIT_STATS", + "seqkit", + "2.13.0" + ] + ] + } + ], + "timestamp": "2026-06-12T17:30:18.267532797", + "meta": { + "nf-test": "0.9.5", + "nextflow": "26.04.3" + } + }, + "genome_fasta": { + "content": [ + { + "stats": [ + [ + { + "id": "test", + "single_end": false + }, + "test.tsv:md5,6f6f8b42abd183c6ab90346a311ca522" + ] + ], + "versions_seqkit": [ + [ + "SEQKIT_STATS", + "seqkit", + "2.13.0" + ] + ] + } + ], + "timestamp": "2026-06-12T17:30:22.693843391", + "meta": { + "nf-test": "0.9.5", + "nextflow": "26.04.3" + } + }, + "transcriptome_fasta": { + "content": [ + { + "stats": [ + [ + { + "id": "test", + "single_end": false + }, + "test.tsv:md5,c79d860b2b0379810e4df3e8359fc8ed" + ] + ], + "versions_seqkit": [ + [ + "SEQKIT_STATS", + "seqkit", + "2.13.0" + ] + ] + } + ], + "timestamp": "2026-06-12T17:30:27.169227967", + "meta": { + "nf-test": "0.9.5", + "nextflow": "26.04.3" + } + }, + "single_end": { + "content": [ + { + "stats": [ + [ + { + "id": "test", + "single_end": true + }, + "test.tsv:md5,76d5ab444de880ef443f358841e44cf9" + ] + ], + "versions_seqkit": [ + [ + "SEQKIT_STATS", + "seqkit", + "2.13.0" + ] + ] + } + ], + "timestamp": "2026-06-12T17:30:09.156247735", + "meta": { + "nf-test": "0.9.5", + "nextflow": "26.04.3" + } + }, + "paired_end": { + "content": [ + { + "stats": [ + [ + { + "id": "test", + "single_end": false + }, + "test.tsv:md5,2d96d9f7d85456090e138b81bf97bcd5" + ] + ], + "versions_seqkit": [ + [ + "SEQKIT_STATS", + "seqkit", + "2.13.0" + ] + ] + } + ], + "timestamp": "2026-06-12T17:35:49.824033505", + "meta": { + "nf-test": "0.9.5", + "nextflow": "26.04.3" + } + }, + "single_end - stub": { + "content": [ + { + "stats": [ + [ + { + "id": "test", + "single_end": true + }, + "test.tsv:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "versions_seqkit": [ + [ + "SEQKIT_STATS", + "seqkit", + "2.13.0" + ] + ] + } + ], + "timestamp": "2026-06-12T17:30:31.558337736", + "meta": { + "nf-test": "0.9.5", + "nextflow": "26.04.3" + } + } +} \ No newline at end of file diff --git a/modules/nf-core/seqtk/comp/environment.yml b/modules/nf-core/seqtk/comp/environment.yml new file mode 100644 index 0000000..bfa1b01 --- /dev/null +++ b/modules/nf-core/seqtk/comp/environment.yml @@ -0,0 +1,6 @@ +--- +channels: + - conda-forge + - bioconda +dependencies: + - "bioconda::seqtk=1.4" diff --git a/modules/nf-core/seqtk/comp/main.nf b/modules/nf-core/seqtk/comp/main.nf new file mode 100644 index 0000000..e5d7c06 --- /dev/null +++ b/modules/nf-core/seqtk/comp/main.nf @@ -0,0 +1,37 @@ + +process SEQTK_COMP { + tag "$meta.id" + label 'process_low' + + conda "${moduleDir}/environment.yml" + container "${ workflow.containerEngine in ['singularity', 'apptainer'] && !task.ext.singularity_pull_docker_container ? + 'https://depot.galaxyproject.org/singularity/seqtk:1.4--h577a1d6_3': + 'quay.io/biocontainers/seqtk:1.4--h577a1d6_3' }" + + input: + tuple val(meta), path(fastx) + + output: + tuple val(meta), path("*.seqtk_stats.tsv"), emit: seqtk_stats + tuple val("${task.process}"), val('seqtk'), eval("seqtk 2>&1 | sed -n 's/^Version: //p'"), emit: versions_seqtk, topic: versions + + when: + task.ext.when == null || task.ext.when + + script: + def args = task.ext.args ?: '' + def prefix = task.ext.prefix ?: "${meta.id}" + + """ + seqtk comp \\ + ${args} \\ + ${fastx} > ${prefix}.seqtk_stats.tsv + """ + + stub: + def prefix = task.ext.prefix ?: "${meta.id}" + + """ + echo "" > ${prefix}.seqtk_stats.tsv + """ +} diff --git a/modules/nf-core/seqtk/comp/meta.yml b/modules/nf-core/seqtk/comp/meta.yml new file mode 100644 index 0000000..bcdd15d --- /dev/null +++ b/modules/nf-core/seqtk/comp/meta.yml @@ -0,0 +1,73 @@ +name: "seqtk_comp" +description: Computes sequence statistics from FASTQ or FASTA files +keywords: + - seqtk + - comp + - fastx + +tools: + - "seqtk_comp": + description: | + Seqtk is a fast and lightweight tool for processing sequences in the FASTA or FASTQ format. + The seqtk comp command computes base composition, sequence length, and GC content for quality control. + + homepage: https://github.com/lh3/seqtk + documentation: https://docs.csc.fi/apps/seqtk/ + tool_dev_url: https://github.com/lh3/seqtk + licence: ["MIT"] + identifier: biotools:seqtk + +input: + - - meta: + type: map + description: | + Groovy Map containing sample information + e.g. [ id:'sample1' ] + - fastx: + type: file + description: A FASTQ or FASTA file + pattern: "*.{fastq,fq,fasta,fa,fas,fna}{,.gz}" + + ontologies: + - edam: http://edamontology.org/format_1930 # FASTQ +output: + seqtk_stats: + - - meta: + type: map + description: | + Groovy Map containing sample information + e.g. [ id:'sample1', single_end:false ] + - "*.seqtk_stats.tsv": + type: file + description: The output TSV file summarizing sequence statistics with + columns for sequence name, length, counts of A, C, G, T, and N bases, + and GC content percentage." + pattern: "*.seqtk_stats.tsv" + + ontologies: + - edam: http://edamontology.org/format_3475 # TSV + versions_seqtk: + - - ${task.process}: + type: string + description: The name of the process + - seqtk: + type: string + description: The name of the tool + - "seqtk 2>&1 | sed -n 's/^Version: //p'": + type: eval + description: The expression to obtain the version of the tool +topics: + versions: + - - ${task.process}: + type: string + description: The name of the process + - seqtk: + type: string + description: The name of the tool + - "seqtk 2>&1 | sed -n 's/^Version: //p'": + type: eval + description: The expression to obtain the version of the tool +authors: + - "@sainsachiko" +maintainers: + - "@sainsachiko" diff --git a/modules/nf-core/seqtk/comp/tests/main.nf.test b/modules/nf-core/seqtk/comp/tests/main.nf.test new file mode 100644 index 0000000..8149813 --- /dev/null +++ b/modules/nf-core/seqtk/comp/tests/main.nf.test @@ -0,0 +1,59 @@ +nextflow_process { + + name "Test Process SEQTK_COMP" + script "../main.nf" + process "SEQTK_COMP" + + tag "modules" + tag "modules_nfcore" + tag "seqtk" + tag "seqtk/comp" + + test("bacteroides_fragilis - fasta") { + + when { + process { + """ + input[0] = [ + [ id:'test', single_end:true ], + file( params.modules_testdata_base_path + 'genomics/prokaryotes/bacteroides_fragilis/genome/genome.fna.gz', checkIfExists: true), + ] + """ + } + } + + then { + assertAll( + { assert process.success }, + { assert snapshot(process.out).match() } + ) + with(process.out.seqtk_stats[0]) { + assert path(get(1)).text.contains("NZ_CP069563.1") + } + } + + } + + test("bacteroides_fragilis - fasta - stub") { + + options "-stub" + + when { + process { + """ + input[0] = [ + [ id:'test', single_end:true ], + file( params.modules_testdata_base_path + 'genomics/prokaryotes/bacteroides_fragilis/genome/genome.fna.gz', checkIfExists: true), + ] + """ + } + } + + then { + assertAll( + { assert process.success }, + { assert snapshot(process.out).match() } + ) + } + } +} diff --git a/modules/nf-core/seqtk/comp/tests/main.nf.test.snap b/modules/nf-core/seqtk/comp/tests/main.nf.test.snap new file mode 100644 index 0000000..a78f282 --- /dev/null +++ b/modules/nf-core/seqtk/comp/tests/main.nf.test.snap @@ -0,0 +1,88 @@ +{ + "bacteroides_fragilis - fasta": { + "content": [ + { + "0": [ + [ + { + "id": "test", + "single_end": true + }, + "test.seqtk_stats.tsv:md5,46c8788954c64c718a799c0f6a41ecdc" + ] + ], + "1": [ + [ + "SEQTK_COMP", + "seqtk", + "1.4-r122" + ] + ], + "seqtk_stats": [ + [ + { + "id": "test", + "single_end": true + }, + "test.seqtk_stats.tsv:md5,46c8788954c64c718a799c0f6a41ecdc" + ] + ], + "versions_seqtk": [ + [ + "SEQTK_COMP", + "seqtk", + "1.4-r122" + ] + ] + } + ], + "meta": { + "nf-test": "0.9.2", + "nextflow": "25.10.2" + }, + "timestamp": "2026-01-14T12:07:29.217316713" + }, + "bacteroides_fragilis - fasta - stub": { + "content": [ + { + "0": [ + [ + { + "id": "test", + "single_end": true + }, + "test.seqtk_stats.tsv:md5,68b329da9893e34099c7d8ad5cb9c940" + ] + ], + "1": [ + [ + "SEQTK_COMP", + "seqtk", + "1.4-r122" + ] + ], + "seqtk_stats": [ + [ + { + "id": "test", + "single_end": true + }, + "test.seqtk_stats.tsv:md5,68b329da9893e34099c7d8ad5cb9c940" + ] + ], + "versions_seqtk": [ + [ + "SEQTK_COMP", + "seqtk", + "1.4-r122" + ] + ] + } + ], + "meta": { + "nf-test": "0.9.2", + "nextflow": "25.10.2" + }, + "timestamp": "2026-01-14T12:07:37.125237854" + } +} \ No newline at end of file diff --git a/modules/nf-core/seqtk/comp/tests/tags.yml b/modules/nf-core/seqtk/comp/tests/tags.yml new file mode 100644 index 0000000..eb168ac --- /dev/null +++ b/modules/nf-core/seqtk/comp/tests/tags.yml @@ -0,0 +1,2 @@ +seqtk/mergepe: + - "modules/nf-core/seqtk/comp/**" diff --git a/modules/nf-core/trimgalore/environment.yml b/modules/nf-core/trimgalore/environment.yml new file mode 100644 index 0000000..6f07df0 --- /dev/null +++ b/modules/nf-core/trimgalore/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::trim-galore=2.3.0 diff --git a/modules/nf-core/trimgalore/main.nf b/modules/nf-core/trimgalore/main.nf new file mode 100644 index 0000000..af819db --- /dev/null +++ b/modules/nf-core/trimgalore/main.nf @@ -0,0 +1,87 @@ +process TRIMGALORE { + tag "${meta.id}" + label 'process_medium' + label 'process_low_memory' + + conda "${moduleDir}/environment.yml" + container "${workflow.containerEngine in ['singularity', 'apptainer'] && !task.ext.singularity_pull_docker_container ? + 'https://community-cr-prod.seqera.io/docker/registry/v2/blobs/sha256/e0/e00369598bd6b7b34a7c83d5496c381104bf8b885c31a4b65b92e6ea2059fbb3/data' : + 'community.wave.seqera.io/library/trim-galore:2.3.0--6a38a479b4972363'}" + + 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 + tuple val("${task.process}"), val("trimgalore"), eval('trim_galore --version | grep -Eo "[0-9]+(\\.[0-9]+)+"'), topic: versions, emit: versions_trimgalore + + 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 { arg -> arg.toLowerCase().contains('_r2 ') } + """ + [ ! -f ${prefix}.fastq.gz ] && ln -s ${reads} ${prefix}.fastq.gz + trim_galore \\ + ${args_list.join(' ')} \\ + --cores ${cores} \\ + --gzip \\ + ${prefix}.fastq.gz + """ + } + 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 + """ + } + + 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} + """ +} diff --git a/modules/nf-core/trimgalore/meta.yml b/modules/nf-core/trimgalore/meta.yml new file mode 100644 index 0000000..dcd9b17 --- /dev/null +++ b/modules/nf-core/trimgalore/meta.yml @@ -0,0 +1,129 @@ +name: trimgalore +description: | + A wrapper around Cutadapt and FastQC to consistently apply adapter and quality trimming to FastQ files, + with extra functionality for RRBS data +keywords: + - trimming + - adapters + - sequencing + - 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: biotools:trim_galore + +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/format_1930" # FASTQ +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: file + description: The trimmed/modified fastq reads + pattern: "*{3prime,5prime,trimmed,val}{,_1,_2}.fq.gz" + ontologies: + - edam: "http://edamontology.org/format_1930" # FASTQ + - 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 ] + - "*report.txt": + type: file + description: trimgalore log file + pattern: "*report.txt" + ontologies: + - edam: "http://edamontology.org/format_2330" # Textual format + unpaired: + - - meta: + type: map + description: | + Groovy Map containing sample information + e.g. [ id:'test', single_end:false ] + - "*unpaired{,_1,_2}.fq.gz": + type: file + description: unpaired reads when --retain_unpaired flag is used + pattern: "*unpaired*.fq.gz" + ontologies: + - edam: "http://edamontology.org/format_1930" # FASTQ + - edam: http://edamontology.org/format_3989 # GZIP format + html: + - - meta: + type: map + description: | + Groovy Map containing sample information + e.g. [ id:'test', single_end:false ] + - "*.html": + type: file + description: FastQC HTML report after trimming when the --fastqc flag is used + pattern: "*_fastqc.html" + ontologies: + - edam: "http://edamontology.org/format_2331" # HTML + zip: + - - meta: + type: map + description: | + Groovy Map containing sample information + e.g. [ id:'test', single_end:false ] + - "*.zip": + type: file + description: FastQC report output zip after trimming when the --fastqc flag + is used + pattern: "*_fastqc.zip" + ontologies: + - edam: http://edamontology.org/format_3987 # ZIP format + versions_trimgalore: + - - ${task.process}: + type: string + description: The name of the process + - trimgalore: + type: string + description: The name of the tool + - trim_galore --version | grep -Eo "[0-9]+(\.[0-9]+)+": + type: eval + description: The expression to obtain the version of the tool + +topics: + versions: + - - ${task.process}: + type: string + description: The name of the process + - trimgalore: + type: string + description: The name of the tool + - trim_galore --version | grep -Eo "[0-9]+(\.[0-9]+)+": + type: eval + description: The expression to obtain the version of the tool + +authors: + - "@drpatelh" + - "@ewels" + - "@FelixKrueger" +maintainers: + - "@drpatelh" + - "@ewels" + - "@FelixKrueger" + - "@vagkaratzas" diff --git a/modules/nf-core/trimgalore/tests/main.nf.test b/modules/nf-core/trimgalore/tests/main.nf.test new file mode 100644 index 0000000..11d68f4 --- /dev/null +++ b/modules/nf-core/trimgalore/tests/main.nf.test @@ -0,0 +1,154 @@ +nextflow_process { + + name "Test Process TRIMGALORE" + script "../main.nf" + process "TRIMGALORE" + + tag "modules" + tag "modules_nfcore" + tag "trimgalore" + + test("sarscov2 - fastq - single-end") { + + when { + process { + """ + input[0] = [ + [ 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.reads, + path(process.out.log[0][1]).readLines().dropWhile { !it.startsWith("=== Summary") }.join('\n').md5(), + process.out.findAll { key, val -> key.startsWith("versions") } + ).match() } + ) + } + } + + test("sarscov2 - fastq - paired-end") { + + when { + process { + """ + input[0] = [ [ id:'test', single_end:false ], + [ + 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.reads, + path(process.out.log[0][1][0]).readLines().dropWhile { !it.startsWith("=== Summary") }.join('\n').md5(), + path(process.out.log[0][1][1]).readLines().dropWhile { !it.startsWith("=== Summary") }.join('\n').md5(), + process.out.findAll { key, val -> key.startsWith("versions") } + ).match() } + ) + } + } + + test("sarscov2 - fastq - paired-end - keep-unpaired") { + + config "./nextflow.config" + + when { + params { + module_args = '--retain_unpaired --length 150' + } + + process { + """ + input[0] = [ [ id:'test', single_end:false ], + [ + 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.reads, + process.out.unpaired, + path(process.out.log[0][1][0]).readLines().dropWhile { !it.startsWith("=== Summary") }.join('\n').md5(), + path(process.out.log[0][1][1]).readLines().dropWhile { !it.startsWith("=== Summary") }.join('\n').md5(), + process.out.findAll { key, val -> key.startsWith("versions") } + ).match() } + ) + } + } + + test("sarscov2 - fastq - single-end - stub") { + + options "-stub" + + when { + process { + """ + input[0] = [ + [ 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.reads, + process.out.log, + process.out.findAll { key, val -> key.startsWith("versions") } + ).match() } + ) + } + } + + test("sarscov2 - fastq - paired-end - stub") { + + options "-stub" + + when { + process { + """ + input[0] = [ [ id:'test', single_end:false ], + [ + 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.reads, + process.out.log, + process.out.findAll { key, val -> key.startsWith("versions") } + ).match() } + ) + } + } +} diff --git a/modules/nf-core/trimgalore/tests/main.nf.test.snap b/modules/nf-core/trimgalore/tests/main.nf.test.snap new file mode 100644 index 0000000..a909b56 --- /dev/null +++ b/modules/nf-core/trimgalore/tests/main.nf.test.snap @@ -0,0 +1,184 @@ +{ + "sarscov2 - fastq - paired-end - stub": { + "content": [ + [ + [ + { + "id": "test", + "single_end": false + }, + [ + "test_1_trimmed.fq.gz:md5,68b329da9893e34099c7d8ad5cb9c940", + "test_2_trimmed.fq.gz:md5,68b329da9893e34099c7d8ad5cb9c940" + ] + ] + ], + [ + [ + { + "id": "test", + "single_end": false + }, + [ + "test_1.fastq.gz_trimming_report.txt:md5,d41d8cd98f00b204e9800998ecf8427e", + "test_2.fastq.gz_trimming_report.txt:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ] + ], + { + "versions_trimgalore": [ + [ + "TRIMGALORE", + "trimgalore", + "2.3.0" + ] + ] + } + ], + "timestamp": "2026-05-05T08:48:10.832490464", + "meta": { + "nf-test": "0.9.5", + "nextflow": "26.04.0" + } + }, + "sarscov2 - fastq - paired-end - keep-unpaired": { + "content": [ + [ + [ + { + "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" + ] + ] + ], + "4cbef464fbc09252a1282078ec23942e", + "ee1b8a371f1490e5faf5e346523b8886", + { + "versions_trimgalore": [ + [ + "TRIMGALORE", + "trimgalore", + "2.3.0" + ] + ] + } + ], + "timestamp": "2026-05-05T09:34:57.668635539", + "meta": { + "nf-test": "0.9.5", + "nextflow": "26.04.0" + } + }, + "sarscov2 - fastq - single-end - stub": { + "content": [ + [ + [ + { + "id": "test", + "single_end": true + }, + "test_trimmed.fq.gz:md5,68b329da9893e34099c7d8ad5cb9c940" + ] + ], + [ + [ + { + "id": "test", + "single_end": true + }, + "test.fastq.gz_trimming_report.txt:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + { + "versions_trimgalore": [ + [ + "TRIMGALORE", + "trimgalore", + "2.3.0" + ] + ] + } + ], + "timestamp": "2026-05-05T08:48:05.803691794", + "meta": { + "nf-test": "0.9.5", + "nextflow": "26.04.0" + } + }, + "sarscov2 - fastq - paired-end": { + "content": [ + [ + [ + { + "id": "test", + "single_end": false + }, + [ + "test_1_val_1.fq.gz:md5,566d44cca0d22c522d6cf0e50c7165dc", + "test_2_val_2.fq.gz:md5,3c023e8e890b897821df3dc98f48c2b3" + ] + ] + ], + "4da0165f1c5a0d3f1d5eea9ad17fb1fb", + "808929d2f70ffea0ddd7750bbb400b02", + { + "versions_trimgalore": [ + [ + "TRIMGALORE", + "trimgalore", + "2.3.0" + ] + ] + } + ], + "timestamp": "2026-05-05T09:34:52.551563029", + "meta": { + "nf-test": "0.9.5", + "nextflow": "26.04.0" + } + }, + "sarscov2 - fastq - single-end": { + "content": [ + [ + [ + { + "id": "test", + "single_end": true + }, + "test_trimmed.fq.gz:md5,566d44cca0d22c522d6cf0e50c7165dc" + ] + ], + "76dd6b9d009e332d2aa6443de525c182", + { + "versions_trimgalore": [ + [ + "TRIMGALORE", + "trimgalore", + "2.3.0" + ] + ] + } + ], + "timestamp": "2026-05-05T09:34:47.273738299", + "meta": { + "nf-test": "0.9.5", + "nextflow": "26.04.0" + } + } +} \ No newline at end of file diff --git a/modules/nf-core/trimgalore/tests/nextflow.config b/modules/nf-core/trimgalore/tests/nextflow.config new file mode 100644 index 0000000..d8e3ac1 --- /dev/null +++ b/modules/nf-core/trimgalore/tests/nextflow.config @@ -0,0 +1,5 @@ +process { + withName: TRIMGALORE { + ext.args = params.module_args + } +} diff --git a/modules/nf-core/trimmomatic/environment.yml b/modules/nf-core/trimmomatic/environment.yml new file mode 100644 index 0000000..994bfd5 --- /dev/null +++ b/modules/nf-core/trimmomatic/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::trimmomatic=0.39 diff --git a/modules/nf-core/trimmomatic/main.nf b/modules/nf-core/trimmomatic/main.nf new file mode 100644 index 0000000..3b2d416 --- /dev/null +++ b/modules/nf-core/trimmomatic/main.nf @@ -0,0 +1,63 @@ +process TRIMMOMATIC { + tag "$meta.id" + label 'process_medium' + + conda "${moduleDir}/environment.yml" + container "${ workflow.containerEngine in ['singularity', 'apptainer'] && !task.ext.singularity_pull_docker_container ? + 'https://depot.galaxyproject.org/singularity/trimmomatic:0.39--hdfd78af_2': + 'quay.io/biocontainers/trimmomatic:0.39--hdfd78af_2' }" + + input: + tuple val(meta), path(reads) + + output: + tuple val(meta), path("*.paired.trim*.fastq.gz") , emit: trimmed_reads + tuple val(meta), path("*.unpaired.trim_*.fastq.gz"), emit: unpaired_reads, optional:true + tuple val(meta), path("*_trim.log") , emit: trim_log + tuple val(meta), path("*_out.log") , emit: out_log + tuple val(meta), path("*.summary") , emit: summary + tuple val("${task.process}"), val('trimmomatic'), eval("trimmomatic -version"), topic: versions, emit: versions_trimmomatic + + when: + task.ext.when == null || task.ext.when + + script: + def args = task.ext.args ?: '' + def prefix = task.ext.prefix ?: "${meta.id}" + def trimmed = meta.single_end ? "SE" : "PE" + def output = meta.single_end ? + "${prefix}.SE.paired.trim.fastq.gz" // HACK to avoid unpaired and paired in the trimmed_reads output + : "${prefix}.paired.trim_1.fastq.gz ${prefix}.unpaired.trim_1.fastq.gz ${prefix}.paired.trim_2.fastq.gz ${prefix}.unpaired.trim_2.fastq.gz" + def qual_trim = task.ext.args2 ?: '' + """ + trimmomatic \\ + $trimmed \\ + -threads $task.cpus \\ + -trimlog ${prefix}_trim.log \\ + -summary ${prefix}.summary \\ + $reads \\ + $output \\ + $qual_trim \\ + $args 2>| >(tee ${prefix}_out.log >&2) + """ + + stub: + def prefix = task.ext.prefix ?: "${meta.id}" + + if (meta.single_end) { + output_command = "echo '' | gzip > ${prefix}.SE.paired.trim.fastq.gz" + } else { + output_command = "echo '' | gzip > ${prefix}.paired.trim_1.fastq.gz\n" + output_command += "echo '' | gzip > ${prefix}.paired.trim_2.fastq.gz\n" + output_command += "echo '' | gzip > ${prefix}.unpaired.trim_1.fastq.gz\n" + output_command += "echo '' | gzip > ${prefix}.unpaired.trim_2.fastq.gz" + } + + """ + $output_command + touch ${prefix}.summary + touch ${prefix}_trim.log + touch ${prefix}_out.log + """ + +} diff --git a/modules/nf-core/trimmomatic/meta.yml b/modules/nf-core/trimmomatic/meta.yml new file mode 100644 index 0000000..7e7839c --- /dev/null +++ b/modules/nf-core/trimmomatic/meta.yml @@ -0,0 +1,112 @@ +name: "trimmomatic" +description: Performs quality and adapter trimming on paired end and single end + reads +keywords: + - trimming + - adapter trimming + - quality trimming +tools: + - "trimmomatic": + description: "A flexible read trimming tool for Illumina NGS data" + homepage: "http://www.usadellab.org/cms/?page=trimmomatic" + documentation: "https://github.com/usadellab/Trimmomatic" + doi: "10.1093/bioinformatics/btu170" + licence: + - "GPL v3" + identifier: biotools:trimmomatic +input: + - - meta: + type: map + description: | + Groovy Map containing sample information + e.g. [ id:'test', single_end:false ] + - reads: + type: file + description: | + Input FastQ files of size 1 or 2 for single-end and paired-end data, respectively. + pattern: "*.fastq.gz" + ontologies: + - edam: http://edamontology.org/format_3989 # GZIP format +output: + trimmed_reads: + - - meta: + type: map + description: | + Groovy Map containing sample information + e.g. [ id:'test', single_end:false ] + - "*.paired.trim*.fastq.gz": + type: file + description: The trimmed/modified paired end fastq reads + pattern: "*.paired.trim*.fastq.gz" + ontologies: + - edam: http://edamontology.org/format_3989 # GZIP format + unpaired_reads: + - - meta: + type: map + description: | + Groovy Map containing sample information + e.g. [ id:'test', single_end:false ] + - "*.unpaired.trim_*.fastq.gz": + type: file + description: The trimmed/modified unpaired end fastq reads + pattern: "*.unpaired.trim_*.fastq.gz" + ontologies: + - edam: http://edamontology.org/format_3989 # GZIP format + trim_log: + - - meta: + type: map + description: | + Groovy Map containing sample information + e.g. [ id:'test', single_end:false ] + - "*_trim.log": + type: file + description: trimmomatic log file, from the trim_log parameter + pattern: "*.log" + ontologies: [] + out_log: + - - meta: + type: map + description: | + Groovy Map containing sample information + e.g. [ id:'test', single_end:false ] + - "*_out.log": + type: file + description: log of output from the standard out + pattern: "*.log" + ontologies: [] + summary: + - - meta: + type: map + description: | + Groovy Map containing sample information + e.g. [ id:'test', single_end:false ] + - "*.summary": + type: file + description: trimmomatic summary file of surviving and dropped reads + pattern: "*.summary" + ontologies: [] + versions_trimmomatic: + - - ${task.process}: + type: string + description: The name of the process + - trimmomatic: + type: string + description: The name of the tool + - trimmomatic -version: + type: eval + description: The expression to obtain the version of the tool +topics: + versions: + - - ${task.process}: + type: string + description: The name of the process + - trimmomatic: + type: string + description: The name of the tool + - trimmomatic -version: + type: eval + description: The expression to obtain the version of the tool +authors: + - "@alyssa-ab" +maintainers: + - "@alyssa-ab" diff --git a/modules/nf-core/trimmomatic/tests/main.nf.test b/modules/nf-core/trimmomatic/tests/main.nf.test new file mode 100644 index 0000000..ce4a298 --- /dev/null +++ b/modules/nf-core/trimmomatic/tests/main.nf.test @@ -0,0 +1,111 @@ +nextflow_process { + + name "Test Process TRIMMOMATIC" + script "../main.nf" + process "TRIMMOMATIC" + tag "modules" + tag "modules_nfcore" + tag "trimmomatic" + + test("Single-Read") { + config "./nextflow_SE.config" + when { + process { + """ + input[0] = [ + [ 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.trimmed_reads, + process.out.summary, + file(process.out.trim_log[0][1]).name, + process.out.findAll { key, val -> key.startsWith("versions")} + ).match() } + ) + } + } + + test("Paired-Reads") { + config "./nextflow_PE.config" + when { + process { + """ + input[0] = [ + [ id: 'test', single_end:false ], + [ + 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.trimmed_reads, + process.out.summary, + file(process.out.trim_log[0][1]).name, + process.out.findAll { key, val -> key.startsWith("versions")} + ).match() } + ) + } + } + + test("No Adaptors") { + + when { + process { + """ + input[0] = [ + [ id: 'test', single_end:false ], + [ + 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.failed } + ) + } + } + + test("Single-Read - stub") { + options "-stub" + when { + process { + """ + input[0] = [ + [ 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() } + ) + } + } + +} diff --git a/modules/nf-core/trimmomatic/tests/main.nf.test.snap b/modules/nf-core/trimmomatic/tests/main.nf.test.snap new file mode 100644 index 0000000..d47ee14 --- /dev/null +++ b/modules/nf-core/trimmomatic/tests/main.nf.test.snap @@ -0,0 +1,182 @@ +{ + "Single-Read - stub": { + "content": [ + { + "0": [ + [ + { + "id": "test", + "single_end": true + }, + "test.SE.paired.trim.fastq.gz:md5,68b329da9893e34099c7d8ad5cb9c940" + ] + ], + "1": [ + + ], + "2": [ + [ + { + "id": "test", + "single_end": true + }, + "test_trim.log:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "3": [ + [ + { + "id": "test", + "single_end": true + }, + "test_out.log:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "4": [ + [ + { + "id": "test", + "single_end": true + }, + "test.summary:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "5": [ + [ + "TRIMMOMATIC", + "trimmomatic", + "0.39" + ] + ], + "out_log": [ + [ + { + "id": "test", + "single_end": true + }, + "test_out.log:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "summary": [ + [ + { + "id": "test", + "single_end": true + }, + "test.summary:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "trim_log": [ + [ + { + "id": "test", + "single_end": true + }, + "test_trim.log:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "trimmed_reads": [ + [ + { + "id": "test", + "single_end": true + }, + "test.SE.paired.trim.fastq.gz:md5,68b329da9893e34099c7d8ad5cb9c940" + ] + ], + "unpaired_reads": [ + + ], + "versions_trimmomatic": [ + [ + "TRIMMOMATIC", + "trimmomatic", + "0.39" + ] + ] + } + ], + "meta": { + "nf-test": "0.9.3", + "nextflow": "25.10.3" + }, + "timestamp": "2026-02-11T11:27:00.107996" + }, + "Single-Read": { + "content": [ + [ + [ + { + "id": "test", + "single_end": true + }, + "test.SE.paired.trim.fastq.gz:md5,e68abbd3b88f7ec12940a4f5c2b8bfb9" + ] + ], + [ + [ + { + "id": "test", + "single_end": true + }, + "test.summary:md5,24c973237557a1439c775ca19a5deaa5" + ] + ], + "test_trim.log", + { + "versions_trimmomatic": [ + [ + "TRIMMOMATIC", + "trimmomatic", + "0.39" + ] + ] + } + ], + "meta": { + "nf-test": "0.9.3", + "nextflow": "25.10.3" + }, + "timestamp": "2026-02-11T12:21:46.59093" + }, + "Paired-Reads": { + "content": [ + [ + [ + { + "id": "test", + "single_end": false + }, + [ + "test.paired.trim_1.fastq.gz:md5,a866e64f451745f176316d0df7d52b30", + "test.paired.trim_2.fastq.gz:md5,725d4ab909b39291ab56b090cab32075" + ] + ] + ], + [ + [ + { + "id": "test", + "single_end": false + }, + "test.summary:md5,9698e5e5c060bbe64588998fe35f8d71" + ] + ], + "test_trim.log", + { + "versions_trimmomatic": [ + [ + "TRIMMOMATIC", + "trimmomatic", + "0.39" + ] + ] + } + ], + "meta": { + "nf-test": "0.9.3", + "nextflow": "25.10.3" + }, + "timestamp": "2026-02-11T12:21:53.926135" + } +} \ No newline at end of file diff --git a/modules/nf-core/trimmomatic/tests/nextflow_PE.config b/modules/nf-core/trimmomatic/tests/nextflow_PE.config new file mode 100644 index 0000000..3f8fdfe --- /dev/null +++ b/modules/nf-core/trimmomatic/tests/nextflow_PE.config @@ -0,0 +1,6 @@ +process { + + withName: TRIMMOMATIC { + ext.args = 'ILLUMINACLIP:TruSeq3-PE.fa:2:30:10 LEADING:3 TRAILING:3 SLIDINGWINDOW:4:15 MINLEN:36' + } +} diff --git a/modules/nf-core/trimmomatic/tests/nextflow_SE.config b/modules/nf-core/trimmomatic/tests/nextflow_SE.config new file mode 100644 index 0000000..af777fd --- /dev/null +++ b/modules/nf-core/trimmomatic/tests/nextflow_SE.config @@ -0,0 +1,6 @@ +process { + + withName: TRIMMOMATIC { + ext.args = 'ILLUMINACLIP:TruSeq3-SE:2:30:10 LEADING:3 TRAILING:3 SLIDINGWINDOW:4:15 MINLEN:36' + } +} diff --git a/modules/nf-core/umitools/extract/environment.yml b/modules/nf-core/umitools/extract/environment.yml new file mode 100644 index 0000000..4d05188 --- /dev/null +++ b/modules/nf-core/umitools/extract/environment.yml @@ -0,0 +1,17 @@ +--- +# 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/umi_tools + - bioconda::umi_tools=1.1.6 + - conda-forge::future=0.18.3 + - conda-forge::matplotlib=3.8.2 + - conda-forge::numpy=1.26.4 + - conda-forge::pandas=2.2.0 + - conda-forge::python=3.9.18 + - conda-forge::regex=2023.12.25 + - conda-forge::scipy=1.12.0 + - conda-forge::pybktree=1.1 + - bioconda::pysam=0.22.0 diff --git a/modules/nf-core/umitools/extract/main.nf b/modules/nf-core/umitools/extract/main.nf new file mode 100644 index 0000000..0aafe59 --- /dev/null +++ b/modules/nf-core/umitools/extract/main.nf @@ -0,0 +1,59 @@ +process UMITOOLS_EXTRACT { + tag "$meta.id" + label "process_single" + label "process_long" + + conda "${moduleDir}/environment.yml" + container "${ workflow.containerEngine in ['singularity', 'apptainer'] && !task.ext.singularity_pull_docker_container ? + 'https://community-cr-prod.seqera.io/docker/registry/v2/blobs/sha256/32/32476f0107d72dbd2210a4e56b2873abde07300025cc11052680475509d2db81/data' : + 'community.wave.seqera.io/library/umi_tools_future_matplotlib_numpy_pruned:1ee668bafc8c9f81' }" + + input: + tuple val(meta), path(reads) + + output: + tuple val(meta), path("*.fastq.gz"), emit: reads + tuple val(meta), path("*.log") , emit: log + tuple val("${task.process}"), val('umitools'), eval("umi_tools --version | sed -n '/version:/s/.*: //p'"), emit: versions_umitools, topic: versions + + when: + task.ext.when == null || task.ext.when + + script: + def args = task.ext.args ?: '' + def prefix = task.ext.prefix ?: "${meta.id}" + if (meta.single_end) { + """ + umi_tools \\ + extract \\ + -I $reads \\ + -S ${prefix}.umi_extract.fastq.gz \\ + $args \\ + > ${prefix}.umi_extract.log + """ + } else { + """ + umi_tools \\ + extract \\ + -I ${reads[0]} \\ + --read2-in=${reads[1]} \\ + -S ${prefix}.umi_extract_1.fastq.gz \\ + --read2-out=${prefix}.umi_extract_2.fastq.gz \\ + $args \\ + > ${prefix}.umi_extract.log + """ + } + + stub: + def prefix = task.ext.prefix ?: "${meta.id}" + if (meta.single_end) { + output_command = "echo '' | gzip > ${prefix}.umi_extract.fastq.gz" + } else { + output_command = "echo '' | gzip > ${prefix}.umi_extract_1.fastq.gz ;" + output_command += "echo '' | gzip > ${prefix}.umi_extract_2.fastq.gz" + } + """ + touch ${prefix}.umi_extract.log + ${output_command} + """ +} diff --git a/modules/nf-core/umitools/extract/meta.yml b/modules/nf-core/umitools/extract/meta.yml new file mode 100644 index 0000000..0bec392 --- /dev/null +++ b/modules/nf-core/umitools/extract/meta.yml @@ -0,0 +1,80 @@ +name: umitools_extract +description: Extracts UMI barcode from a read and add it to the read name, leaving + any sample barcode in place +keywords: + - UMI + - barcode + - extract + - umitools +tools: + - umi_tools: + description: > + UMI-tools contains tools for dealing with Unique Molecular Identifiers (UMIs)/Random + Molecular Tags (RMTs) and single cell RNA-Seq cell barcodes + documentation: https://umi-tools.readthedocs.io/en/latest/ + license: "MIT" + identifier: "" +input: + - - meta: + type: map + description: | + Groovy Map containing sample information + e.g. [ id:'test', single_end:false ] + - reads: + type: list + description: | + List of input FASTQ files whose UMIs will be extracted. +output: + reads: + - - meta: + type: map + description: | + Groovy Map containing sample information + e.g. [ id:'test', single_end:false ] + - "*.fastq.gz": + type: file + description: > + Extracted FASTQ files. | For single-end reads, pattern is \${prefix}.umi_extract.fastq.gz. + | For paired-end reads, pattern is \${prefix}.umi_extract_{1,2}.fastq.gz. + pattern: "*.{fastq.gz}" + ontologies: [] + log: + - - meta: + type: map + description: | + Groovy Map containing sample information + e.g. [ id:'test', single_end:false ] + - "*.log": + type: file + description: Logfile for umi_tools + pattern: "*.{log}" + ontologies: [] + versions_umitools: + - - ${task.process}: + type: string + description: The name of the process + - umitools: + type: string + description: The name of the tool + - "umi_tools --version | sed -n '/version:/s/.*: //p'": + type: eval + description: The expression to obtain the version of umitools + +topics: + versions: + - - ${task.process}: + type: string + description: The name of the process + - umitools: + type: string + description: The name of the tool + - "umi_tools --version | sed -n '/version:/s/.*: //p'": + type: eval + description: The expression to obtain the version of umitools + +authors: + - "@drpatelh" + - "@grst" +maintainers: + - "@drpatelh" + - "@grst" diff --git a/modules/nf-core/umitools/extract/tests/main.nf.test b/modules/nf-core/umitools/extract/tests/main.nf.test new file mode 100644 index 0000000..b6fa048 --- /dev/null +++ b/modules/nf-core/umitools/extract/tests/main.nf.test @@ -0,0 +1,106 @@ +nextflow_process { + + name "Test Process UMITOOLS_EXTRACT" + script "../main.nf" + process "UMITOOLS_EXTRACT" + config "./nextflow.config" + tag "modules_nfcore" + tag "modules" + tag "umitools" + tag "umitools/extract" + + test("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 { + assertAll ( + { assert process.success }, + { assert snapshot( + process.out.reads.collect { item -> item.collect { element -> element instanceof Map ? element : file(element).name }}, + process.out.log.collect { item -> item.collect { element -> element instanceof Map ? element : file(element).name }}, + process.out.findAll { key, val -> key.startsWith('versions') } + ).match() } + ) + } + } + + test("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).match() } + ) + } + } + + test("pair 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 { + assertAll ( + { assert process.success }, + { assert snapshot( + file(process.out.reads[0][1][0]).name, + file(process.out.reads[0][1][1]).name, + process.out.log.collect { item -> item.collect { element -> element instanceof Map ? element : file(element).name }}, + process.out.findAll { key, val -> key.startsWith('versions') } + ).match() } + ) + } + } + + test("pair 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() } + ) + } + } +} diff --git a/modules/nf-core/umitools/extract/tests/main.nf.test.snap b/modules/nf-core/umitools/extract/tests/main.nf.test.snap new file mode 100644 index 0000000..9162342 --- /dev/null +++ b/modules/nf-core/umitools/extract/tests/main.nf.test.snap @@ -0,0 +1,195 @@ +{ + "pair end - stub": { + "content": [ + { + "0": [ + [ + { + "id": "test", + "single_end": false + }, + [ + "test.umi_extract_1.fastq.gz:md5,68b329da9893e34099c7d8ad5cb9c940", + "test.umi_extract_2.fastq.gz:md5,68b329da9893e34099c7d8ad5cb9c940" + ] + ] + ], + "1": [ + [ + { + "id": "test", + "single_end": false + }, + "test.umi_extract.log:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "2": [ + [ + "UMITOOLS_EXTRACT", + "umitools", + "1.1.6" + ] + ], + "log": [ + [ + { + "id": "test", + "single_end": false + }, + "test.umi_extract.log:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "reads": [ + [ + { + "id": "test", + "single_end": false + }, + [ + "test.umi_extract_1.fastq.gz:md5,68b329da9893e34099c7d8ad5cb9c940", + "test.umi_extract_2.fastq.gz:md5,68b329da9893e34099c7d8ad5cb9c940" + ] + ] + ], + "versions_umitools": [ + [ + "UMITOOLS_EXTRACT", + "umitools", + "1.1.6" + ] + ] + } + ], + "meta": { + "nf-test": "0.9.3", + "nextflow": "25.10.3" + }, + "timestamp": "2026-02-03T13:17:55.612739621" + }, + "single end - stub": { + "content": [ + { + "0": [ + [ + { + "id": "test", + "single_end": true + }, + "test.umi_extract.fastq.gz:md5,68b329da9893e34099c7d8ad5cb9c940" + ] + ], + "1": [ + [ + { + "id": "test", + "single_end": true + }, + "test.umi_extract.log:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "2": [ + [ + "UMITOOLS_EXTRACT", + "umitools", + "1.1.6" + ] + ], + "log": [ + [ + { + "id": "test", + "single_end": true + }, + "test.umi_extract.log:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "reads": [ + [ + { + "id": "test", + "single_end": true + }, + "test.umi_extract.fastq.gz:md5,68b329da9893e34099c7d8ad5cb9c940" + ] + ], + "versions_umitools": [ + [ + "UMITOOLS_EXTRACT", + "umitools", + "1.1.6" + ] + ] + } + ], + "meta": { + "nf-test": "0.9.3", + "nextflow": "25.10.3" + }, + "timestamp": "2026-02-03T13:17:37.310716092" + }, + "pair end": { + "content": [ + "test.umi_extract_1.fastq.gz", + "test.umi_extract_2.fastq.gz", + [ + [ + { + "id": "test", + "single_end": false + }, + "test.umi_extract.log" + ] + ], + { + "versions_umitools": [ + [ + "UMITOOLS_EXTRACT", + "umitools", + "1.1.6" + ] + ] + } + ], + "meta": { + "nf-test": "0.9.3", + "nextflow": "25.10.3" + }, + "timestamp": "2026-02-03T13:17:47.683802227" + }, + "single end": { + "content": [ + [ + [ + { + "id": "test", + "single_end": true + }, + "test.umi_extract.fastq.gz" + ] + ], + [ + [ + { + "id": "test", + "single_end": true + }, + "test.umi_extract.log" + ] + ], + { + "versions_umitools": [ + [ + "UMITOOLS_EXTRACT", + "umitools", + "1.1.6" + ] + ] + } + ], + "meta": { + "nf-test": "0.9.3", + "nextflow": "25.10.3" + }, + "timestamp": "2026-02-03T13:17:29.607052009" + } +} \ No newline at end of file diff --git a/modules/nf-core/umitools/extract/tests/nextflow.config b/modules/nf-core/umitools/extract/tests/nextflow.config new file mode 100644 index 0000000..628f5fc --- /dev/null +++ b/modules/nf-core/umitools/extract/tests/nextflow.config @@ -0,0 +1,9 @@ +process { + + publishDir = { "${params.outdir}/${task.process.tokenize(':')[-1].tokenize('_')[0].toLowerCase()}" } + + withName: UMITOOLS_EXTRACT { + ext.args = '--bc-pattern="NNNN"' + } + +} diff --git a/nextflow.config b/nextflow.config index 7dd69d8..5bb7f45 100644 --- a/nextflow.config +++ b/nextflow.config @@ -47,20 +47,40 @@ params { databases = null save_untarred_databases = false + + // fastq_shortreads_preprocess_qc subworkflow // FASTQ preprocessing - skip_preprocessing_qc = false + skip_preprocessing_qc = false + save_preprocessed_reads = false + + // Short read adapter trimming / quality filtering + perform_shortread_qc = false + shortread_qc_tool = 'fastp' + shortread_qc_skipadaptertrim = false + shortread_qc_adapterlist = null + shortread_qc_mergepairs = false + shortread_qc_dedup = false + shortread_qc_savetrimmedfail = false + + // Short read complexity filtering + perform_shortread_complexityfilter = false + shortread_complexityfilter_tool = 'bbduk' + + // Short read host removal + perform_shortread_hostremoval = false + shortread_hostremoval_tool = 'hostile' + shortread_hostremoval_reference = null + shortread_hostremoval_index = null + shortread_hostremoval_index_name = null + // fastq_shortreads_preprocess_qc subworkflow end - // RGI + // Profilers run_rgi = false - // Humann run_humann_v3 = false run_humann_v4 = false - // fmhfunprofiler run_fmhfunprofiler = false - // mi-faser run_mifaser = false run_diamond = false - // eggnogmapper run_eggnogmapper = false } diff --git a/nextflow_schema.json b/nextflow_schema.json index e48421b..ab2fe2f 100644 --- a/nextflow_schema.json +++ b/nextflow_schema.json @@ -61,15 +61,144 @@ } }, "preprocessing_qc_options": { - "title": "Preprocessing QC options", + "title": "Preprocessing general options", "type": "object", - "description": "", + "description": "Options that apply to read preprocessing as a whole", "default": "", "properties": { "skip_preprocessing_qc": { "type": "boolean", "description": "Skip quality control step when preprocessing", "help_text": "When True, this won't perform any of the quality control steps in preprocessing, such as discarding low-quality reads." + }, + "save_preprocessed_reads": { + "type": "boolean", + "fa_icon": "fas fa-save", + "description": "Save reads from adapter clipping/pair-merging, length filtering, complexity filtering and/or host-removal", + "help_text": "Saves the FASTQ files of every preprocessing step that ran, so you can inspect what each step removed. Files are published to `/preprocessing/`.\n\nOnly steps that actually ran produce output, so enabling this without enabling any of `--perform_shortread_qc`, `--perform_shortread_complexityfilter` or `--perform_shortread_hostremoval` saves nothing." + } + }, + "fa_icon": "fas fa-users-cog" + }, + "preprocessing_shortread_qc_options": { + "title": "Preprocessing short-read QC options", + "type": "object", + "description": "Options for adapter clipping, quality trimming, pair-merging and deduplication of short reads", + "default": "", + "fa_icon": "fas fa-cut", + "properties": { + "perform_shortread_qc": { + "type": "boolean", + "fa_icon": "fas fa-toolbox", + "description": "Turn on short-read adapter clipping and quality trimming", + "help_text": "Runs the `fastq_shortreads_preprocess_qc` nf-core subworkflow on every short-read library before run merging. The tool is selected with `--shortread_qc_tool`." + }, + "shortread_qc_tool": { + "type": "string", + "default": "fastp", + "enum": ["fastp", "adapterremoval", "trimmomatic", "cutadapt", "trimgalore", "bbduk", "leehom"], + "fa_icon": "fas fa-tools", + "description": "Specify which tool to use for short-read QC" + }, + "shortread_qc_skipadaptertrim": { + "type": "boolean", + "fa_icon": "fas fa-forward", + "description": "Skip adapter trimming", + "help_text": "Turns off adapter removal while keeping the other operations of the selected tool, e.g. quality trimming or pair merging. Useful when the reads have already been adapter-clipped." + }, + "shortread_qc_adapterlist": { + "type": "string", + "format": "file-path", + "exists": true, + "fa_icon": "fas fa-list", + "description": "Specify a list of adapter sequences to be removed", + "help_text": "A file of adapter sequences to clip instead of the tool's built-in defaults. The expected format depends on the tool: fastp and Trimmomatic take FASTA, AdapterRemoval takes a two-column (paired-end) or one-column (single-end) text file." + }, + "shortread_qc_mergepairs": { + "type": "boolean", + "fa_icon": "fas fa-object-group", + "description": "Turn on merging of read pairs", + "help_text": "Collapses overlapping read pairs into single reads. Note that funcprofiler concatenates R1 and R2 for most profilers anyway, so this mainly matters for the RGI path, which consumes the paired channel." + }, + "shortread_qc_dedup": { + "type": "boolean", + "fa_icon": "fas fa-clone", + "description": "Turn on deduplication of reads with Clumpify", + "help_text": "Removes exact and near-duplicate reads with BBMap's Clumpify (`dedupe=t`) after adapter clipping." + }, + "shortread_qc_savetrimmedfail": { + "type": "boolean", + "fa_icon": "fas fa-save", + "description": "Save reads that failed the trimming/filtering thresholds" + } + } + }, + "preprocessing_shortread_complexityfilter_options": { + "title": "Preprocessing short-read complexity filtering options", + "type": "object", + "description": "Options for removing low-complexity reads", + "default": "", + "fa_icon": "fas fa-filter", + "properties": { + "perform_shortread_complexityfilter": { + "type": "boolean", + "fa_icon": "fas fa-toolbox", + "description": "Turn on removal of low-complexity reads", + "help_text": "Low-complexity reads (homopolymers, short tandem repeats) inflate spurious hits in translated search. Removing them cuts both runtime and false positives." + }, + "shortread_complexityfilter_tool": { + "type": "string", + "default": "bbduk", + "enum": ["bbduk", "prinseqplusplus", "fastp"], + "fa_icon": "fas fa-tools", + "description": "Specify which tool to use for complexity filtering", + "help_text": "Thresholds are not exposed as params. Override them through `ext.args` in `conf/modules.config`, e.g. `entropy=` for BBDuk or `--low_complexity_filter --complexity_threshold` for fastp." + } + } + }, + "preprocessing_hostremoval_options": { + "title": "Preprocessing host-removal options", + "type": "object", + "description": "Options for removing host-derived reads", + "default": "", + "fa_icon": "fas fa-user-times", + "properties": { + "perform_shortread_hostremoval": { + "type": "boolean", + "fa_icon": "fas fa-toolbox", + "description": "Turn on removal of host-derived reads", + "help_text": "Requires either `--shortread_hostremoval_reference`, `--shortread_hostremoval_index` or `--shortread_hostremoval_index_name`, depending on the selected tool." + }, + "shortread_hostremoval_tool": { + "type": "string", + "default": "hostile", + "enum": ["hostile", "deacon"], + "fa_icon": "fas fa-tools", + "description": "Specify which tool to use for host removal", + "help_text": "Hostile fetches and caches its own reference indexes by name; Deacon builds a minimiser index from a FASTA reference." + }, + "shortread_hostremoval_reference": { + "type": "string", + "format": "file-path", + "exists": true, + "mimetype": "text/plain", + "fa_icon": "fas fa-file-alt", + "description": "Specify a reference genome FASTA to remove reads against", + "help_text": "Used by Deacon to build a filtering index. Ignored when a pre-built index is supplied with `--shortread_hostremoval_index`." + }, + "shortread_hostremoval_index": { + "type": "string", + "format": "path", + "exists": true, + "fa_icon": "fas fa-database", + "description": "Specify a pre-built host-removal index", + "help_text": "Skips index construction. The expected format depends on the tool: a Deacon `.idx` file, or a Hostile index directory." + }, + "shortread_hostremoval_index_name": { + "type": "string", + "fa_icon": "fas fa-tag", + "description": "Specify the name of a Hostile-hosted index to download", + "help_text": "Hostile resolves the name against its own index registry and caches the download, e.g. `human-t2t-hla`. Only used when `--shortread_hostremoval_tool hostile`." } } }, @@ -272,6 +401,15 @@ { "$ref": "#/$defs/preprocessing_qc_options" }, + { + "$ref": "#/$defs/preprocessing_shortread_qc_options" + }, + { + "$ref": "#/$defs/preprocessing_shortread_complexityfilter_options" + }, + { + "$ref": "#/$defs/preprocessing_hostremoval_options" + }, { "$ref": "#/$defs/profiling_options" }, diff --git a/subworkflows/local/dataprep/main.nf b/subworkflows/local/dataprep/main.nf index 971ac2a..977006e 100644 --- a/subworkflows/local/dataprep/main.nf +++ b/subworkflows/local/dataprep/main.nf @@ -1,5 +1,6 @@ include { CAT_FASTQ as MERGE_RUNS } from '../../../modules/nf-core/cat/fastq/main' include { CAT_FASTQ } from '../../../modules/nf-core/cat/fastq/main' +include { FASTQ_SHORTREADS_PREPROCESS_QC } from '../../nf-core/fastq_shortreads_preprocess_qc/main' workflow DATAPREP { @@ -34,8 +35,59 @@ workflow DATAPREP { return [meta, reads] } + // Step 1b: Optional preprocessing and read QC, per run. + // This runs before the run merging below so that each run is trimmed against its own + // adapters and quality profile, which is the order nf-core/taxprofiler uses. The + // subworkflow can concatenate runs itself, but MERGE_RUNS already does that here, so + // its final concatenation stays switched off. + ch_multiqc_files = Channel.empty() + def run_preprocessing = !params.skip_preprocessing_qc || params.perform_shortread_qc || params.perform_shortread_complexityfilter || params.perform_shortread_hostremoval + + if (run_preprocessing) { + FASTQ_SHORTREADS_PREPROCESS_QC( + ch_validated, + params.skip_preprocessing_qc, + true, + true, + true, + true, + true, + true, + true, + true, + true, + 0, + !params.perform_shortread_qc || params.shortread_qc_skipadaptertrim, + params.shortread_qc_tool, + params.shortread_qc_adapterlist ? Channel.value(file(params.shortread_qc_adapterlist, checkIfExists: true)) : Channel.value([]), + params.shortread_qc_mergepairs, + false, + params.shortread_qc_savetrimmedfail, + !params.perform_shortread_complexityfilter, + params.shortread_complexityfilter_tool, + !params.shortread_qc_dedup, + !params.perform_shortread_hostremoval, + params.shortread_hostremoval_reference ? Channel.value([[id: 'host'], file(params.shortread_hostremoval_reference, checkIfExists: true)]) : Channel.value([[], []]), + params.shortread_hostremoval_index ? Channel.value([params.shortread_hostremoval_index_name, file(params.shortread_hostremoval_index, checkIfExists: true)]) : Channel.value([[], []]), + params.shortread_hostremoval_index_name, + params.shortread_hostremoval_tool, + true, + ) + ch_preprocessed = FASTQ_SHORTREADS_PREPROCESS_QC.out.reads + // multiqc_files carries the trimming and complexity-filter logs but not the FastQC + // archives, which the subworkflow emits on their own channels. MultiQC wants bare + // paths, so drop the meta on the way out. + ch_multiqc_files = FASTQ_SHORTREADS_PREPROCESS_QC.out.multiqc_files + .mix(FASTQ_SHORTREADS_PREPROCESS_QC.out.pre_stats_fastqc_zip) + .mix(FASTQ_SHORTREADS_PREPROCESS_QC.out.post_stats_fastqc_zip) + .map { _meta, report -> report } + } + else { + ch_preprocessed = ch_validated + } + // Step 2: Group by meta.id and merge runs if needed - ch_grouped = ch_validated + ch_grouped = ch_preprocessed .map { meta, reads -> // Create grouping key and new meta without run_accession for grouping def group_key = meta.id @@ -92,4 +144,5 @@ workflow DATAPREP { emit: reads = ch_reads // Paired-end reads (R1, R2) or single-end reads_concat = ch_concatenated // All reads concatenated into single file + multiqc_files = ch_multiqc_files // Reports from the preprocessing subworkflow } diff --git a/subworkflows/local/dataprep/meta.yml b/subworkflows/local/dataprep/meta.yml index 915faa4..8107d12 100644 --- a/subworkflows/local/dataprep/meta.yml +++ b/subworkflows/local/dataprep/meta.yml @@ -2,18 +2,21 @@ name: "dataprep" description: | Validate the input samplesheet and prepare read channels for profiling. Sets - `single_end`, `run_accession` and `instrument_platform` on the meta map, merges - multiple runs belonging to the same sample, and produces an additional channel - in which read pairs are concatenated into a single file for tools that cannot - take paired input. + `single_end`, `run_accession` and `instrument_platform` on the meta map, + optionally runs read QC and preprocessing per sequencing run, merges multiple + runs belonging to the same sample, and produces an additional channel in which + read pairs are concatenated into a single file for tools that cannot take + paired input. keywords: - samplesheet - fastq + - preprocessing - merging - concatenation - metagenome components: - cat/fastq + - fastq_shortreads_preprocess_qc input: - samplesheet: type: file @@ -37,6 +40,13 @@ output: with `single_end` forced to `true`, for tools that accept only one FASTQ. Structure: [ val(meta), [ path(reads) ] ] pattern: "*.{fq,fastq}{,.gz}" + - multiqc_files: + type: file + description: | + Logs and reports from the preprocessing steps that ran, ready for MultiQC. + Empty when preprocessing is skipped entirely. + Structure: [ path(report) ] + pattern: "*.{html,json,log,settings,txt,zip}" authors: - "@nickp60" maintainers: diff --git a/subworkflows/local/dataprep/tests/main.nf.test.snap b/subworkflows/local/dataprep/tests/main.nf.test.snap index 8b9fa61..efa3445 100644 --- a/subworkflows/local/dataprep/tests/main.nf.test.snap +++ b/subworkflows/local/dataprep/tests/main.nf.test.snap @@ -47,6 +47,12 @@ "minigut.merged.fastq.gz:md5,1fbae8f5fe6fdfa6552cfca32d9c3129" ] ] + ], + "2": [ + + ], + "multiqc_files": [ + ], "reads": [ [ @@ -96,10 +102,10 @@ ] } ], - "timestamp": "2026-04-18T00:05:15.049339", + "timestamp": "2026-08-05T17:01:48.189660194", "meta": { "nf-test": "0.9.5", - "nextflow": "25.10.4" + "nextflow": "26.04.6" } } } \ No newline at end of file diff --git a/subworkflows/local/utils_nfcore_funcprofiler_pipeline/main.nf b/subworkflows/local/utils_nfcore_funcprofiler_pipeline/main.nf index 226c01d..f535d97 100644 --- a/subworkflows/local/utils_nfcore_funcprofiler_pipeline/main.nf +++ b/subworkflows/local/utils_nfcore_funcprofiler_pipeline/main.nf @@ -182,8 +182,44 @@ def validateInputSamplesheet(input) { // Generate methods description for MultiQC // def toolCitationText() { - // pending fastq_shortreads_preprocess_qc implementation - def text_qc = "" + def text_qc = ["Read quality was assessed with FastQC (Andrews 2010)."].join(' ').trim() + + def shortread_qc_citations = [ + 'fastp': "fastp (Chen et al. 2018)", + 'adapterremoval': "AdapterRemoval (Schubert et al. 2016)", + 'trimmomatic': "Trimmomatic (Bolger et al. 2014)", + 'cutadapt': "Cutadapt (Martin 2011)", + 'trimgalore': "Trim Galore! (Krueger 2015)", + 'bbduk': "BBDuk (Bushnell 2022)", + 'leehom': "leeHom (Renaud et al. 2014)", + ] + + def complexityfilter_citations = [ + 'bbduk': "BBDuk (Bushnell 2022)", + 'prinseqplusplus': "PRINSEQ++ (Cantu et al. 2019)", + 'fastp': "fastp (Chen et al. 2018)", + ] + + def hostremoval_citations = [ + 'hostile': "Hostile (Constantinides et al. 2023)", + 'deacon': "Deacon (Constantinides 2024)", + ] + + def text_shortread_qc = [ + "Adapter clipping and quality filtering were performed with", + "${shortread_qc_citations[params.shortread_qc_tool]}.", + params.shortread_qc_dedup ? "Duplicate reads were removed with Clumpify (Bushnell 2022)." : "", + ].join(' ').trim() + + def text_complexityfilter = [ + "Low-complexity reads were removed with", + "${complexityfilter_citations[params.shortread_complexityfilter_tool]}.", + ].join(' ').trim() + + def text_hostremoval = [ + "Host-derived reads were removed with", + "${hostremoval_citations[params.shortread_hostremoval_tool]}.", + ].join(' ').trim() def text_humann = [ "Functional profiling was performed with", @@ -208,6 +244,9 @@ def toolCitationText() { def citation_text = [ "Tools used in the workflow included:", !params.skip_preprocessing_qc ? text_qc : "", + params.perform_shortread_qc ? text_shortread_qc : "", + params.perform_shortread_complexityfilter ? text_complexityfilter : "", + params.perform_shortread_hostremoval ? text_hostremoval : "", params.run_humann_v3 || params.run_humann_v4 ? text_humann : "", params.run_diamond ? text_diamond : "", params.run_fmhfunprofiler ? text_fmhfunprofiler : "", @@ -221,9 +260,28 @@ def toolCitationText() { } def toolBibliographyText() { - // - //def text_qc = [!params.skip_preprocessing_qc ? "
  • Andrews, S. (2010). FastQC: A Quality Control Tool for High Throughput Sequence Data [Online]. Available at: http://www.bioinformatics.babraham.ac.uk/projects/fastqc/
  • " : ""].join(' ').trim() - def text_qc = "" // pending implementation of fastq_shortreads_preprocess_qc subworkflow + def text_qc = [!params.skip_preprocessing_qc ? "
  • Andrews, S. (2010). FastQC: A Quality Control Tool for High Throughput Sequence Data [Online]. Available at: http://www.bioinformatics.babraham.ac.uk/projects/fastqc/
  • " : ""].join(' ').trim() + + // One reference per preprocessing tool, keyed the same way as the params that select them. + def bib_preprocessing = [ + 'adapterremoval': "
  • Schubert, M., Lindgreen, S., & Orlando, L. (2016). AdapterRemoval v2: rapid adapter trimming, identification, and read merging. BMC Research Notes, 9, 88. 10.1186/s13104-016-1900-2
  • ", + 'bbduk': "
  • Bushnell, B. (2022). BBMap. https://sourceforge.net/projects/bbmap/
  • ", + 'cutadapt': "
  • Martin, M. (2011). Cutadapt removes adapter sequences from high-throughput sequencing reads. EMBnet.journal, 17(1), 10–12. 10.14806/ej.17.1.200
  • ", + 'deacon': "
  • Constantinides, B. (2024). Deacon: fast alignment-free host depletion. https://github.com/bede/deacon
  • ", + 'fastp': "
  • Chen, S., Zhou, Y., Chen, Y., & Gu, J. (2018). fastp: an ultra-fast all-in-one FASTQ preprocessor. Bioinformatics, 34(17), i884–i890. 10.1093/bioinformatics/bty560
  • ", + 'hostile': "
  • Constantinides, B., Hunt, M., & Crook, D. W. (2023). Hostile: accurate decontamination of microbial host sequences. Bioinformatics, 39(12), btad728. 10.1093/bioinformatics/btad728
  • ", + 'leehom': "
  • Renaud, G., Stenzel, U., & Kelso, J. (2014). leeHom: adaptor trimming and merging for Illumina sequencing reads. Nucleic Acids Research, 42(18), e141. 10.1093/nar/gku699
  • ", + 'prinseqplusplus': "
  • Cantu, V. A., Sadural, J., & Edwards, R. (2019). PRINSEQ++, a multi-threaded tool for fast and efficient quality control and preprocessing of sequencing datasets. PeerJ Preprints, 7, e27553v1. 10.7287/peerj.preprints.27553v1
  • ", + 'trimgalore': "
  • Krueger, F. (2015). Trim Galore!: a wrapper around Cutadapt and FastQC. https://www.bioinformatics.babraham.ac.uk/projects/trim_galore/
  • ", + 'trimmomatic': "
  • Bolger, A. M., Lohse, M., & Usadel, B. (2014). Trimmomatic: a flexible trimmer for Illumina sequence data. Bioinformatics, 30(15), 2114–2120. 10.1093/bioinformatics/btu170
  • ", + ] + + def text_preprocessing = [ + params.perform_shortread_qc ? bib_preprocessing[params.shortread_qc_tool] : "", + params.perform_shortread_qc && params.shortread_qc_dedup ? bib_preprocessing['bbduk'] : "", + params.perform_shortread_complexityfilter ? bib_preprocessing[params.shortread_complexityfilter_tool] : "", + params.perform_shortread_hostremoval ? bib_preprocessing[params.shortread_hostremoval_tool] : "", + ].unique().join(' ').trim() def text_humann = [ params.run_humann_v3 || params.run_humann_v4 ? "
  • Beghini, F., McIver, L. J., Blanco-M\u00edguez, A., Dubois, L., Asnicar, F., Maharjan, S., Mailyan, A., Thomas, A. M., Manghi, P., Valles-Colomer, M., Weingart, G., Zhang, Y., Zolfo, M., Huttenhower, C., Franzosa, E. A., & Segata, N. (2021). Integrating taxonomic, functional, and strain-level profiling of diverse microbial communities with bioBakery 3. eLife, 10, e65088. 10.7554/eLife.65088
  • " : "", @@ -246,6 +304,7 @@ def toolBibliographyText() { def reference_text = [ text_qc, + text_preprocessing, text_humann, text_eggnggmapper, text_rgi, diff --git a/subworkflows/nf-core/fastq_complexity_filter/main.nf b/subworkflows/nf-core/fastq_complexity_filter/main.nf new file mode 100644 index 0000000..4c9678a --- /dev/null +++ b/subworkflows/nf-core/fastq_complexity_filter/main.nf @@ -0,0 +1,48 @@ +include { PRINSEQPLUSPLUS } from '../../../modules/nf-core/prinseqplusplus/main' +include { BBMAP_BBDUK } from '../../../modules/nf-core/bbmap/bbduk/main' +include { FASTP } from '../../../modules/nf-core/fastp/main' + +workflow FASTQ_COMPLEXITY_FILTER { + + take: + ch_reads // channel: [ val(meta), [ fastq ] ] + val_complexity_filter_tool // string: [mandatory] tool_name // choose from: ["prinseqplusplus", "bbduk", "fastp"] + + main: + + ch_log = channel.empty() + ch_report = channel.empty() + ch_multiqc_files = channel.empty() + + if (val_complexity_filter_tool == "prinseqplusplus") { + PRINSEQPLUSPLUS( ch_reads ) + + ch_filtered_reads = PRINSEQPLUSPLUS.out.good_reads + ch_log = PRINSEQPLUSPLUS.out.log + } else if (val_complexity_filter_tool == "bbduk") { + BBMAP_BBDUK( ch_reads, [] ) + + ch_filtered_reads = BBMAP_BBDUK.out.reads + ch_multiqc_files = ch_multiqc_files.mix(BBMAP_BBDUK.out.log) + } else if (val_complexity_filter_tool == "fastp") { + FASTP( + ch_reads.map { meta, files -> [ meta, files, [] ] }, + false, + false, + false + ) + + ch_filtered_reads = FASTP.out.reads + ch_log = FASTP.out.log + ch_report = FASTP.out.html + ch_multiqc_files = ch_multiqc_files.mix(FASTP.out.json) + } else { + error('Please choose one of the available complexity filtering tools: ["prinseqplusplus", "bbduk", "fastp"]') + } + + emit: + filtered_reads = ch_filtered_reads // channel: [ val(meta), [ fastq.gz ] ] + logfile = ch_log // channel: [ val(meta), [ {txt} ] ] + report = ch_report // channel: [ val(meta), [ {html} ] ] + multiqc_files = ch_multiqc_files +} diff --git a/subworkflows/nf-core/fastq_complexity_filter/meta.yml b/subworkflows/nf-core/fastq_complexity_filter/meta.yml new file mode 100644 index 0000000..75f56d0 --- /dev/null +++ b/subworkflows/nf-core/fastq_complexity_filter/meta.yml @@ -0,0 +1,68 @@ +# yaml-language-server: $schema=https://raw.githubusercontent.com/nf-core/modules/master/subworkflows/yaml-schema.json +name: "fastq_complexity_filter" +description: | + Perform low-complexity filtering of FASTQ reads using a selectable tool. + One of PRINSEQ++, BBDuk, or fastp is executed based on the provided tool name. + Only complexity filtering is applied; no adapter trimming, read merging, or + quality trimming is performed. +keywords: + - fastq + - complexity filtering + - low complexity + - entropy + - qc +components: + - prinseqplusplus + - bbmap/bbduk + - fastp + +input: + - ch_reads: + type: file + description: | + Input channel containing sample metadata and FASTQ reads. + Structure: [ val(meta), path(fastq) ] + pattern: "*.{fastq,fastq.gz}" + - val_complexity_filter_tool: + type: string + description: | + Complexity filtering tool to use. + Must be one of: 'prinseqplusplus', 'bbduk', or 'fastp'. + +output: + - filtered_reads: + type: file + description: | + Channel containing complexity-filtered FASTQ reads. + Structure: [ val(meta), path(fastq) ] + pattern: "*.{fastq,fastq.gz}" + - logfile: + type: file + description: | + Tool-specific log files, when available. + Structure: [ val(meta), path(log) ] + pattern: "*.log" + - report: + type: file + description: | + HTML report generated by fastp. Empty for other tools. + Structure: [ val(meta), path(html) ] + pattern: "*.html" + - multiqc_files: + type: file + description: | + Files emitted for MultiQC aggregation (e.g. fastp JSON reports, + BBDuk logs). + Structure: [ path(file) ] + pattern: "*.{json,log}" + - versions: + type: file + description: | + File containing software versions + Structure: [ path(versions.yml) ] + pattern: "versions.yml" + +authors: + - "@vagkaratzas" +maintainers: + - "@vagkaratzas" diff --git a/subworkflows/nf-core/fastq_complexity_filter/nextflow.config b/subworkflows/nf-core/fastq_complexity_filter/nextflow.config new file mode 100644 index 0000000..78f02d1 --- /dev/null +++ b/subworkflows/nf-core/fastq_complexity_filter/nextflow.config @@ -0,0 +1,22 @@ +// IMPORTANT: Add this configuration to your modules.config + +process { + withName: ".*:FASTQ_COMPLEXITY_FILTER:BBMAP_BBDUK" { + ext.args = [ + 'entropy=0.7', // needed to turn on complexity filtering + 'minlength=0' // needed, to not discard reads shorter than this + ].join(' ') + ext.prefix = { "${meta.id}.trim" } + } + + // need FASTP to only do complexity filtering + withName: ".*:FASTQ_COMPLEXITY_FILTER:FASTP" { + ext.args = [ + '--low_complexity_filter', + '--disable_adapter_trimming', + '--disable_trim_poly_g', + '--disable_quality_filtering', + '--disable_length_filtering' + ].join(' ') + } +} diff --git a/subworkflows/nf-core/fastq_complexity_filter/tests/main.nf.test b/subworkflows/nf-core/fastq_complexity_filter/tests/main.nf.test new file mode 100644 index 0000000..6a54e89 --- /dev/null +++ b/subworkflows/nf-core/fastq_complexity_filter/tests/main.nf.test @@ -0,0 +1,118 @@ +nextflow_workflow { + + name "Test Subworkflow FASTQ_COMPLEXITY_FILTER" + script "../main.nf" + workflow "FASTQ_COMPLEXITY_FILTER" + config "./nextflow.config" + + tag "subworkflows" + tag "subworkflows_nfcore" + tag "subworkflows/fastq_complexity_filter" + + tag "prinseqplusplus" + tag "bbmap" + tag "bbmap/bbduk" + tag "fastp" + + test("sarscov2 - fastq - single_end - prinseqplusplus") { + + when { + workflow { + """ + 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) + ]) + input[1] = "prinseqplusplus" // val_complexity_filter_tool + """ + } + } + then { + assert workflow.success + assertAll( + { assert snapshot( + file(workflow.out.filtered_reads[0][1]).name, // unstable + workflow.out.logfile[0][1] + ).match()} + ) + } + } + + test("sarscov2 - fastq - paired_end - bbduk") { + + when { + workflow { + """ + input[0] = channel.of([ + [ id:'test', single_end:false ], + [ + 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] = "bbduk" // val_complexity_filter_tool + """ + } + } + then { + assert workflow.success + assertAll( + { assert snapshot( + workflow.out.filtered_reads[0][1], + workflow.out.multiqc_files.collect { file(it[1]).name } + ).match()} + ) + } + } + + test("sarscov2 - fastq - single_end - fastp") { + + when { + workflow { + """ + 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) + ]) + input[1] = "fastp" // val_complexity_filter_tool + """ + } + } + then { + assert workflow.success + assertAll( + { assert snapshot( + workflow.out.filtered_reads[0][1], + path(workflow.out.logfile[0][1]).readLines().size(), + path(workflow.out.report[0][1]).readLines().size(), + workflow.out.multiqc_files[0][1] + ).match()} + ) + } + } + + test("sarscov2 - fastq - single_end - fastp - stub") { + + options "-stub" + + when { + workflow { + """ + 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) + ]) + input[1] = "fastp" // val_complexity_filter_tool + """ + } + } + then { + assert workflow.success + assertAll( + { assert snapshot( + workflow.out + ).match()} + ) + } + } +} diff --git a/subworkflows/nf-core/fastq_complexity_filter/tests/main.nf.test.snap b/subworkflows/nf-core/fastq_complexity_filter/tests/main.nf.test.snap new file mode 100644 index 0000000..2a0315c --- /dev/null +++ b/subworkflows/nf-core/fastq_complexity_filter/tests/main.nf.test.snap @@ -0,0 +1,125 @@ +{ + "sarscov2 - fastq - single_end - fastp - stub": { + "content": [ + { + "0": [ + [ + { + "id": "test", + "single_end": true + }, + "test.fastp.fastq.gz:md5,68b329da9893e34099c7d8ad5cb9c940" + ] + ], + "1": [ + [ + { + "id": "test", + "single_end": true + }, + "test.fastp.log:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "2": [ + [ + { + "id": "test", + "single_end": true + }, + "test.fastp.html:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "3": [ + [ + { + "id": "test", + "single_end": true + }, + "test.fastp.json:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "filtered_reads": [ + [ + { + "id": "test", + "single_end": true + }, + "test.fastp.fastq.gz:md5,68b329da9893e34099c7d8ad5cb9c940" + ] + ], + "logfile": [ + [ + { + "id": "test", + "single_end": true + }, + "test.fastp.log:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "multiqc_files": [ + [ + { + "id": "test", + "single_end": true + }, + "test.fastp.json:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "report": [ + [ + { + "id": "test", + "single_end": true + }, + "test.fastp.html:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ] + } + ], + "timestamp": "2026-05-15T07:14:24.213399133", + "meta": { + "nf-test": "0.9.5", + "nextflow": "25.10.2" + } + }, + "sarscov2 - fastq - single_end - prinseqplusplus": { + "content": [ + "test_good_out.fastq.gz", + "test.log:md5,f9bceec6e29b63bac4b8da14efeadfcd" + ], + "timestamp": "2026-05-15T07:13:47.00739468", + "meta": { + "nf-test": "0.9.5", + "nextflow": "25.10.2" + } + }, + "sarscov2 - fastq - single_end - fastp": { + "content": [ + "test.fastp.fastq.gz:md5,0536c516b46941a349c8ba69b324fd73", + 27, + 2394, + "test.fastp.json:md5,3c1b5d91fb701b0a3753b48873b543f5" + ], + "timestamp": "2026-07-31T10:08:06.668906", + "meta": { + "nf-test": "0.9.5", + "nextflow": "26.04.6" + } + }, + "sarscov2 - fastq - paired_end - bbduk": { + "content": [ + [ + "test.trim_1.fastq.gz:md5,4161df271f9bfcd25d5845a1e220dbec", + "test.trim_2.fastq.gz:md5,2ebae722295ea66d84075a3b042e2b42" + ], + [ + "test.trim.bbduk.log" + ] + ], + "timestamp": "2026-05-15T07:14:08.730079029", + "meta": { + "nf-test": "0.9.5", + "nextflow": "25.10.2" + } + } +} \ No newline at end of file diff --git a/subworkflows/nf-core/fastq_complexity_filter/tests/nextflow.config b/subworkflows/nf-core/fastq_complexity_filter/tests/nextflow.config new file mode 100644 index 0000000..fa6b940 --- /dev/null +++ b/subworkflows/nf-core/fastq_complexity_filter/tests/nextflow.config @@ -0,0 +1,24 @@ +process { + withName: 'PRINSEQPLUSPLUS' { + ext.args = "-lc_entropy=0.8" + } + + withName: 'BBMAP_BBDUK' { + ext.args = [ + 'entropy=0.7', // needed to turn on complexity filtering + 'minlength=0' // needed, to not discard reads shorter than this + ].join(' ') + ext.prefix = { "${meta.id}.trim" } + } + + withName: 'FASTP' { + ext.args = [ + '--low_complexity_filter', // needed to turn on complexity filtering + '--complexity_threshold 60', // default is 30, testing with 60 to filter something out + '--disable_adapter_trimming', + '--disable_trim_poly_g', + '--disable_quality_filtering', + '--disable_length_filtering' + ].join(' ') + } +} diff --git a/subworkflows/nf-core/fastq_decontaminate_deacon_hostile/main.nf b/subworkflows/nf-core/fastq_decontaminate_deacon_hostile/main.nf new file mode 100644 index 0000000..6b1099f --- /dev/null +++ b/subworkflows/nf-core/fastq_decontaminate_deacon_hostile/main.nf @@ -0,0 +1,50 @@ +include { FASTQ_FETCH_CLEAN_HOSTILE } from '../fastq_fetch_clean_hostile/main' +include { FASTQ_INDEX_FILTER_DEACON } from '../fastq_index_filter_deacon/main' + +workflow FASTQ_DECONTAMINATE_DEACON_HOSTILE { + + take: + ch_reads // channel: [ val(meta), [ reads ] ] + ch_fasta // channel: [ val(meta), [ fasta ] ] (optional) + ch_reference // channel: [ val(reference_name), path(reference_dir) ] (optional) + index_name // val (optional) + decontaminator // string (enum): 'hostile' or 'deacon' + + main: + + reference = channel.empty() + json = channel.empty() + index = channel.empty() + summary = channel.empty() + + if (decontaminator != "hostile" && decontaminator != "deacon"){ + error("Unknown decontaminator '${decontaminator}'") + } + + // Fastq decontamination + if (decontaminator == "hostile") { + FASTQ_FETCH_CLEAN_HOSTILE ( + ch_reads, + ch_reference, + index_name + ) + fastq_filtered = FASTQ_FETCH_CLEAN_HOSTILE.out.fastq + reference = FASTQ_FETCH_CLEAN_HOSTILE.out.reference + json = FASTQ_FETCH_CLEAN_HOSTILE.out.json + } else if (decontaminator == "deacon") { + FASTQ_INDEX_FILTER_DEACON ( + ch_fasta.join(ch_reads) + ) + fastq_filtered = FASTQ_INDEX_FILTER_DEACON.out.fastq_filtered + index = FASTQ_INDEX_FILTER_DEACON.out.index + summary = FASTQ_INDEX_FILTER_DEACON.out.summary + } + + + emit: + fastq_filtered = fastq_filtered // channel: [ val(meta), [ fastq ] ] + reference = reference // channel: [ val(reference_name), path(reference_dir) ] (hostile only) + json = json // channel: [ val(meta), [ *.json ] ] (hostile only) + index = index // channel: [ val(meta), [ index ] ] (deacon only) + summary = summary // channel: [ val(meta), [ log ] ] (deacon only) +} diff --git a/subworkflows/nf-core/fastq_decontaminate_deacon_hostile/meta.yml b/subworkflows/nf-core/fastq_decontaminate_deacon_hostile/meta.yml new file mode 100644 index 0000000..beb8d34 --- /dev/null +++ b/subworkflows/nf-core/fastq_decontaminate_deacon_hostile/meta.yml @@ -0,0 +1,79 @@ +# yaml-language-server: $schema=https://raw.githubusercontent.com/nf-core/modules/master/subworkflows/yaml-schema.json +name: "fastq_decontaminate_deacon_hostile" +description: Decontaminate FastQ files by filtering reads that match a reference genome using Deacon or Hostile +keywords: + - hostile + - deacon + - filter + - index + - fasta + - fastq + - genome + - reference + - minimizer + - decontamination +components: + - hostile/fetch + - hostile/clean + - deacon/index + - deacon/filter + - fastq_fetch_clean_hostile + - fastq_index_filter_deacon +input: + - ch_reads: + type: file + description: | + List of FastQ files of size 1 and 2 for single-end and paired-end data, respectively. + Structure: [ val(meta), [ path(reads) ] ] + - ch_fasta: + type: file + description: | + Input genome fasta file used by Deacon. Meta must match the meta of ch_reads. + Structure: [ val(meta), [ path(fasta) ] ] + - ch_reference: + type: directory + description: | + Directory containing index file(s) corresponding to the preferred aligner (bowtie2 short reads or minimap for long reads) used by Hostile. + Note that single end data is assumed to be long reads. If you have single-end short read you must supply both the BowTie2 + indices AND explicitly specify `--aligner bowtie2` + - index_name: + type: string + description: Name of the reference genome index to download for Hostile fetch if the reference is not provided. + - decontaminator: + type: string + description: Name of the deacontamination tool to use. + enum: ["deacon", "hostile"] +output: + - fastq_filtered: + type: file + description: | + List of output filtered FastQ files of size 1 and 2 for single-end and paired-end data, respectively. + Structure: [ val(meta), path(${prefix}*.fq.gz) ] + pattern: "*.fq.gz" + - reference: + type: channel + description: | + Channel containing reference name and directory with index files for Hostile. + Structure: [ val(reference_name), path(reference_dir) ] + - json: + type: channel + description: | + Channel containing sample metadata and Hostile cleaning log in JSON format. + Structure: [ val(meta), path(*.json) ] + pattern: "*.json" + - index: + type: file + description: | + Deacon minimizer index file. + Structure: [ val(meta), path(index) ] + pattern: ".idx" + - summary: + type: file + description: | + JSON file containing summary of results. + Structure: [ val(meta), path(${prefix}.json) ] + pattern: "*.json" +authors: + - "@Baksic-Ivan" +maintainers: + - "@Baksic-Ivan" diff --git a/subworkflows/nf-core/fastq_decontaminate_deacon_hostile/tests/main.nf.test b/subworkflows/nf-core/fastq_decontaminate_deacon_hostile/tests/main.nf.test new file mode 100644 index 0000000..83e12b0 --- /dev/null +++ b/subworkflows/nf-core/fastq_decontaminate_deacon_hostile/tests/main.nf.test @@ -0,0 +1,192 @@ +nextflow_workflow { + + name "Test Subworkflow FASTQ_DECONTAMINATE_DEACON_HOSTILE" + script "../main.nf" + workflow "FASTQ_DECONTAMINATE_DEACON_HOSTILE" + config './nextflow.config' + + tag "subworkflows" + tag "subworkflows_nfcore" + tag "subworkflows/fastq_index_filter_deacon" + tag "subworkflows/fastq_fetch_clean_hostile" + tag "subworkflows/fastq_decontaminate_deacon_hostile" + tag "hostile" + tag "hostile/fetch" + tag "hostile/clean" + tag "bowtie2/build" + tag "deacon" + tag "deacon/filter" + tag "deacon/index" + + test("sarscov2 - fastq - single-end - hostile") { + setup { + run("BOWTIE2_BUILD") { + script "../../../../modules/nf-core/bowtie2/build/main.nf" + process { + """ + input[0] = [ + [ id:'test' ], + file(params.modules_testdata_base_path + 'genomics/sarscov2/genome/genome.fasta', checkIfExists: true) + ] + """ + } + } + } + when { + workflow { + """ + input[0] = channel.of([ + [ id:'test_single', single_end:true ], + file(params.modules_testdata_base_path + 'genomics/homo_sapiens/illumina/fastq/test_1.fastq.gz', checkIfExists: true) + ]) + input[1] = [] + input[2] = BOWTIE2_BUILD.out.index.map { meta, index -> tuple('genome', index) } + input[3] = [] + input[4] = 'hostile' + """ + } + } + then { + assertAll( + { assert workflow.success }, + { assert snapshot(sanitizeOutput(workflow.out, unstableKeys:["json"])).match() } + ) + } + } + + test("sarscov2 - fastq - paired-end - hostile") { + setup { + run("BOWTIE2_BUILD") { + script "../../../../modules/nf-core/bowtie2/build/main.nf" + process { + """ + input[0] = [ + [ id:'test' ], + file(params.modules_testdata_base_path + 'genomics/sarscov2/genome/genome.fasta', checkIfExists: true) + ] + """ + } + } + } + when { + workflow { + """ + input[0] = channel.of([ + [ id:'test_paired', single_end:false ], + [ + file(params.modules_testdata_base_path + 'genomics/homo_sapiens/illumina/fastq/test_1.fastq.gz', checkIfExists: true), + file(params.modules_testdata_base_path + 'genomics/homo_sapiens/illumina/fastq/test_2.fastq.gz', checkIfExists: true) + ] + ]) + input[1] = [] + input[2] = BOWTIE2_BUILD.out.index.map { meta, index -> tuple('genome', index) } + input[3] = [] + input[4] = 'hostile' + """ + } + } + then { + assertAll( + { assert workflow.success }, + { assert snapshot(sanitizeOutput(workflow.out, unstableKeys:["json"])).match() } + ) + } + } + + test("sarscov2 - fastq - single-end - deacon") { + when { + workflow { + """ + 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) + ] + ) + input[1] = channel.of( + [ + [ id:'test', single_end:true ], + file(params.modules_testdata_base_path + 'genomics/sarscov2/genome/genome.fasta', checkIfExists: true) + ] + ) + input[2] = [] + input[3] = [] + input[4] = 'deacon' + """ + } + } + then { + assertAll( + { assert workflow.success }, + { assert snapshot(sanitizeOutput(workflow.out, unstableKeys:["summary"])).match() } + ) + } + } + + test("sarscov2 - fastq - paired-end - deacon") { + when { + workflow { + """ + input[0] = channel.of( + [ + [ id:'test', single_end:false ], + [ + 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] = channel.of( + [ + [ id:'test', single_end:false ], + file(params.modules_testdata_base_path + 'genomics/sarscov2/genome/genome.fasta', checkIfExists: true) + ] + ) + input[2] = [] + input[3] = [] + input[4] = 'deacon' + """ + } + } + then { + assertAll( + { assert workflow.success }, + { assert snapshot(sanitizeOutput(workflow.out, unstableKeys:["summary"])).match() } + ) + } + } + + test("sarscov2 - fastq - single-end - deacon - stub") { + + options "-stub" + + when { + workflow { + """ + 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) + ] + ) + input[1] = channel.of( + [ + [ id:'test', single_end:true ], + file(params.modules_testdata_base_path + 'genomics/sarscov2/genome/genome.fasta', checkIfExists: true) + ] + ) + input[2] = [] + input[3] = [] + input[4] = 'deacon' + """ + } + } + + then { + assertAll( + { assert workflow.success }, + { assert snapshot(sanitizeOutput(workflow.out)).match() } + ) + } + } +} diff --git a/subworkflows/nf-core/fastq_decontaminate_deacon_hostile/tests/main.nf.test.snap b/subworkflows/nf-core/fastq_decontaminate_deacon_hostile/tests/main.nf.test.snap new file mode 100644 index 0000000..a11e3d7 --- /dev/null +++ b/subworkflows/nf-core/fastq_decontaminate_deacon_hostile/tests/main.nf.test.snap @@ -0,0 +1,239 @@ +{ + "sarscov2 - fastq - single-end - deacon": { + "content": [ + { + "fastq_filtered": [ + [ + { + "id": "test", + "single_end": true + }, + "test.fq.gz:md5,2250e99ec61fca0e28e2dcb5124d1a16" + ] + ], + "index": [ + [ + { + "id": "test", + "single_end": true, + "index_id": "genome" + }, + "genome.idx:md5,84e4985c91800686db9c9dca28fabd1a" + ] + ], + "json": [ + + ], + "reference": [ + + ], + "summary": [ + [ + { + "id": "test", + "single_end": true + }, + "test.json" + ] + ] + } + ], + "timestamp": "2026-06-25T09:33:51.466919727", + "meta": { + "nf-test": "0.9.5", + "nextflow": "26.04.3" + } + }, + "sarscov2 - fastq - single-end - hostile": { + "content": [ + { + "fastq_filtered": [ + [ + { + "id": "test_single", + "single_end": true + }, + "test_1.clean.fastq.gz:md5,9224ada21765cb3ad5fc05f40df78f17" + ] + ], + "index": [ + + ], + "json": [ + [ + { + "id": "test_single", + "single_end": true + }, + "test_single.json" + ] + ], + "reference": [ + [ + "genome", + [ + "genome.1.bt2:md5,cbe3d0bbea55bc57c99b4bfa25b5fbdf", + "genome.2.bt2:md5,47b153cd1319abc88dda532462651fcf", + "genome.3.bt2:md5,4ed93abba181d8dfab2e303e33114777", + "genome.4.bt2:md5,c25be5f8b0378abf7a58c8a880b87626", + "genome.rev.1.bt2:md5,52be6950579598a990570fbcf5372184", + "genome.rev.2.bt2:md5,e3b4ef343dea4dd571642010a7d09597" + ] + ] + ], + "summary": [ + + ] + } + ], + "timestamp": "2026-06-25T09:36:52.498104951", + "meta": { + "nf-test": "0.9.5", + "nextflow": "26.04.3" + } + }, + "sarscov2 - fastq - paired-end - deacon": { + "content": [ + { + "fastq_filtered": [ + [ + { + "id": "test", + "single_end": false + }, + [ + "test_1.fq.gz:md5,bf1aa22249e7e2b462c6be1a53ac55e9", + "test_2.fq.gz:md5,d3106ad7395c78a212e6214177d5c054" + ] + ] + ], + "index": [ + [ + { + "id": "test", + "single_end": false, + "index_id": "genome" + }, + "genome.idx:md5,84e4985c91800686db9c9dca28fabd1a" + ] + ], + "json": [ + + ], + "reference": [ + + ], + "summary": [ + [ + { + "id": "test", + "single_end": false + }, + "test.json" + ] + ] + } + ], + "timestamp": "2026-06-25T09:34:04.449112509", + "meta": { + "nf-test": "0.9.5", + "nextflow": "26.04.3" + } + }, + "sarscov2 - fastq - paired-end - hostile": { + "content": [ + { + "fastq_filtered": [ + [ + { + "id": "test_paired", + "single_end": false + }, + [ + "test_1.clean_1.fastq.gz:md5,6de988b85909e6529bad0022703bcab2", + "test_2.clean_2.fastq.gz:md5,539acd65e93bf16a5ace7d6034e704c7" + ] + ] + ], + "index": [ + + ], + "json": [ + [ + { + "id": "test_paired", + "single_end": false + }, + "test_paired.json" + ] + ], + "reference": [ + [ + "genome", + [ + "genome.1.bt2:md5,cbe3d0bbea55bc57c99b4bfa25b5fbdf", + "genome.2.bt2:md5,47b153cd1319abc88dda532462651fcf", + "genome.3.bt2:md5,4ed93abba181d8dfab2e303e33114777", + "genome.4.bt2:md5,c25be5f8b0378abf7a58c8a880b87626", + "genome.rev.1.bt2:md5,52be6950579598a990570fbcf5372184", + "genome.rev.2.bt2:md5,e3b4ef343dea4dd571642010a7d09597" + ] + ] + ], + "summary": [ + + ] + } + ], + "timestamp": "2026-06-25T09:37:16.895941056", + "meta": { + "nf-test": "0.9.5", + "nextflow": "26.04.3" + } + }, + "sarscov2 - fastq - single-end - deacon - stub": { + "content": [ + { + "fastq_filtered": [ + [ + { + "id": "test", + "single_end": true + }, + "test.fq.gz:md5,68b329da9893e34099c7d8ad5cb9c940" + ] + ], + "index": [ + [ + { + "id": "test", + "single_end": true, + "index_id": "genome" + }, + "genome.idx:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "json": [ + + ], + "reference": [ + + ], + "summary": [ + [ + { + "id": "test", + "single_end": true + }, + "test.json:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ] + } + ], + "timestamp": "2026-06-25T09:34:17.49591082", + "meta": { + "nf-test": "0.9.5", + "nextflow": "26.04.3" + } + } +} \ No newline at end of file diff --git a/subworkflows/nf-core/fastq_decontaminate_deacon_hostile/tests/nextflow.config b/subworkflows/nf-core/fastq_decontaminate_deacon_hostile/tests/nextflow.config new file mode 100644 index 0000000..08d4009 --- /dev/null +++ b/subworkflows/nf-core/fastq_decontaminate_deacon_hostile/tests/nextflow.config @@ -0,0 +1,5 @@ +process { + withName: 'HOSTILE_CLEAN' { + ext.args = '--aligner bowtie2' + } +} diff --git a/subworkflows/nf-core/fastq_fetch_clean_hostile/main.nf b/subworkflows/nf-core/fastq_fetch_clean_hostile/main.nf new file mode 100644 index 0000000..c071359 --- /dev/null +++ b/subworkflows/nf-core/fastq_fetch_clean_hostile/main.nf @@ -0,0 +1,35 @@ +include { HOSTILE_FETCH } from '../../../modules/nf-core/hostile/fetch/main' +include { HOSTILE_CLEAN } from '../../../modules/nf-core/hostile/clean/main' + +workflow FASTQ_FETCH_CLEAN_HOSTILE { + + take: + ch_reads // channel: [ val(meta), [ fastq ] ] + ch_reference // channel: [ val(reference_name), path(reference_dir) ] (optional) + index_name // val (optional) + + main: + + if (!index_name && !ch_reference) { + error "Provide either the reference index name for HOSTILE_FETCH or an existing reference path for HOSTILE_CLEAN." + } + + if (index_name && ch_reference) { + error "Cannot provide both the reference index name for fetching and a reference path, please provide only one." + } + + if (index_name) { + HOSTILE_FETCH( index_name ) + out_reference = HOSTILE_FETCH.out.reference + } + else { + out_reference = ch_reference + } + + HOSTILE_CLEAN( ch_reads, out_reference ) + + emit: + reference = out_reference // channel: [ val(reference_name), path(reference_dir) ] + fastq = HOSTILE_CLEAN.out.fastq // channel: [ val(meta), [ *.fastq.gz ] ] + json = HOSTILE_CLEAN.out.json // channel: [ val(meta), [ *.json ] ] +} diff --git a/subworkflows/nf-core/fastq_fetch_clean_hostile/meta.yml b/subworkflows/nf-core/fastq_fetch_clean_hostile/meta.yml new file mode 100644 index 0000000..38ae866 --- /dev/null +++ b/subworkflows/nf-core/fastq_fetch_clean_hostile/meta.yml @@ -0,0 +1,58 @@ +# yaml-language-server: $schema=https://raw.githubusercontent.com/nf-core/modules/master/subworkflows/yaml-schema.json +name: "fastq_fetch_clean_hostile" +description: Downloads required reference genomes for hostile and removes host reads from short-read FASTQ sequencing files +keywords: + - hostile + - decontamination + - human removal + - download + - host removal + - clean +components: + - hostile/fetch + - hostile/clean +input: + - meta: + type: map + description: | + Groovy Map containing sample information + e.g. `[ id:'sample1', single_end:false ]` + - reads: + type: file + description: Input FASTQ files for hostile cleaning + pattern: "*.{fastq,fastq.gz,fq,fq.gz}" + - reference_name: + type: string + description: Name of the reference to align against and thus remove mapped reads to. + - reference_dir: + type: directory + description: | + Directory containing index file(s) corresponding to the preferred aligner (bowtie2 short reads or minimap for long reads). + Note that single end data is assumed to be long reads. If you have single-end short read you must supply both the BowTie2 + indices AND explicitly specify `--aligner bowtie2` + - index_name: + type: string + description: Name of the reference genome index to download for hostile fetch if the reference is not provided. +output: + - reference: + type: channel + description: | + Channel containing reference name and directory with index files for Hostile. + Structure: [ val(reference_name), path(reference_dir) ] + - fastq: + type: file + description: | + Channel containing sample metadata and cleaned FASTQ files. + Structure: [ val(meta), path(*.fastq.gz) ] + pattern: "*.{fastq,fq,fastq.gz,fq.gz}" + - json: + type: channel + description: | + Channel containing sample metadata and Hostile cleaning log in JSON format. + Structure: [ val(meta), path(*.json) ] + pattern: "*.json" +authors: + - "@maia-munteanu" +maintainers: + - "@maia-munteanu" + - "@vagkaratzas" diff --git a/subworkflows/nf-core/fastq_fetch_clean_hostile/tests/main.nf.test b/subworkflows/nf-core/fastq_fetch_clean_hostile/tests/main.nf.test new file mode 100644 index 0000000..f00a106 --- /dev/null +++ b/subworkflows/nf-core/fastq_fetch_clean_hostile/tests/main.nf.test @@ -0,0 +1,101 @@ +nextflow_workflow { + + name "Test Subworkflow FASTQ_FETCH_CLEAN_HOSTILE" + script "../main.nf" + workflow "FASTQ_FETCH_CLEAN_HOSTILE" + config './nextflow.config' + + tag "subworkflows" + tag "subworkflows_nfcore" + tag "subworkflows/fastq_fetch_clean_hostile" + tag "hostile" + tag "hostile/fetch" + tag "hostile/clean" + tag "bowtie2/build" + + setup { + run("BOWTIE2_BUILD") { + script "../../../../modules/nf-core/bowtie2/build/main.nf" + process { + """ + input[0] = [ + [ id:'test' ], + file(params.modules_testdata_base_path + 'genomics/sarscov2/genome/genome.fasta', checkIfExists: true) + ] + """ + } + } + } + + test("fastq - single-end - with reference") { + when { + workflow { + """ + input[0] = Channel.of([ + [ id:'test_single', single_end:true ], + file(params.modules_testdata_base_path + 'genomics/homo_sapiens/illumina/fastq/test_1.fastq.gz', checkIfExists: true) + ]) + input[1] = BOWTIE2_BUILD.out.index.map { meta, index -> tuple('genome', index) } + input[2] = null + """ + } + } + + then { + assertAll( + { assert workflow.success }, + { assert snapshot(sanitizeOutput(workflow.out, unstableKeys: ["json"])).match() } + ) + } + } + + test("fastq - paired-end - with reference") { + when { + workflow { + """ + input[0] = Channel.of([ + [ id:'test_paired', single_end:false ], + [ + file(params.modules_testdata_base_path + 'genomics/homo_sapiens/illumina/fastq/test_1.fastq.gz', checkIfExists: true), + file(params.modules_testdata_base_path + 'genomics/homo_sapiens/illumina/fastq/test_2.fastq.gz', checkIfExists: true) + ] + ]) + input[1] = BOWTIE2_BUILD.out.index.map { meta, index -> tuple('genome', index) } + input[2] = null + """ + } + } + + then { + assertAll( + { assert workflow.success }, + { assert snapshot(sanitizeOutput(workflow.out, unstableKeys: ["json"])).match() } + ) + } + } + + test("fastq - single-end - with reference - stub") { + + options "-stub" + + when { + workflow { + """ + input[0] = Channel.of([ + [ id:'test_single', single_end:true ], + file(params.modules_testdata_base_path + 'genomics/homo_sapiens/illumina/fastq/test_1.fastq.gz', checkIfExists: true) + ]) + input[1] = BOWTIE2_BUILD.out.index.map { ['genome', it[1]] } + input[2] = null + """ + } + } + + then { + assertAll( + { assert workflow.success }, + { assert snapshot(sanitizeOutput(workflow.out)).match() } + ) + } + } +} diff --git a/subworkflows/nf-core/fastq_fetch_clean_hostile/tests/main.nf.test.snap b/subworkflows/nf-core/fastq_fetch_clean_hostile/tests/main.nf.test.snap new file mode 100644 index 0000000..7ff2e26 --- /dev/null +++ b/subworkflows/nf-core/fastq_fetch_clean_hostile/tests/main.nf.test.snap @@ -0,0 +1,131 @@ +{ + "fastq - single-end - with reference - stub": { + "content": [ + { + "fastq": [ + [ + { + "id": "test_single", + "single_end": true + }, + "test_single.clean_1.fastq.gz:md5,68b329da9893e34099c7d8ad5cb9c940" + ] + ], + "json": [ + [ + { + "id": "test_single", + "single_end": true + }, + "test_single.json:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "reference": [ + [ + "genome", + [ + "genome.1.bt2:md5,d41d8cd98f00b204e9800998ecf8427e", + "genome.2.bt2:md5,d41d8cd98f00b204e9800998ecf8427e", + "genome.3.bt2:md5,d41d8cd98f00b204e9800998ecf8427e", + "genome.4.bt2:md5,d41d8cd98f00b204e9800998ecf8427e", + "genome.rev.1.bt2:md5,d41d8cd98f00b204e9800998ecf8427e", + "genome.rev.2.bt2:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ] + ] + } + ], + "timestamp": "2026-05-26T14:30:20.029664659", + "meta": { + "nf-test": "0.9.5", + "nextflow": "26.04.0" + } + }, + "fastq - single-end - with reference": { + "content": [ + { + "fastq": [ + [ + { + "id": "test_single", + "single_end": true + }, + "test_1.clean.fastq.gz:md5,9224ada21765cb3ad5fc05f40df78f17" + ] + ], + "json": [ + [ + { + "id": "test_single", + "single_end": true + }, + "test_single.json" + ] + ], + "reference": [ + [ + "genome", + [ + "genome.1.bt2:md5,cbe3d0bbea55bc57c99b4bfa25b5fbdf", + "genome.2.bt2:md5,47b153cd1319abc88dda532462651fcf", + "genome.3.bt2:md5,4ed93abba181d8dfab2e303e33114777", + "genome.4.bt2:md5,c25be5f8b0378abf7a58c8a880b87626", + "genome.rev.1.bt2:md5,52be6950579598a990570fbcf5372184", + "genome.rev.2.bt2:md5,e3b4ef343dea4dd571642010a7d09597" + ] + ] + ] + } + ], + "timestamp": "2026-05-26T14:43:39.771257929", + "meta": { + "nf-test": "0.9.5", + "nextflow": "26.04.0" + } + }, + "fastq - paired-end - with reference": { + "content": [ + { + "fastq": [ + [ + { + "id": "test_paired", + "single_end": false + }, + [ + "test_1.clean_1.fastq.gz:md5,6de988b85909e6529bad0022703bcab2", + "test_2.clean_2.fastq.gz:md5,539acd65e93bf16a5ace7d6034e704c7" + ] + ] + ], + "json": [ + [ + { + "id": "test_paired", + "single_end": false + }, + "test_paired.json" + ] + ], + "reference": [ + [ + "genome", + [ + "genome.1.bt2:md5,cbe3d0bbea55bc57c99b4bfa25b5fbdf", + "genome.2.bt2:md5,47b153cd1319abc88dda532462651fcf", + "genome.3.bt2:md5,4ed93abba181d8dfab2e303e33114777", + "genome.4.bt2:md5,c25be5f8b0378abf7a58c8a880b87626", + "genome.rev.1.bt2:md5,52be6950579598a990570fbcf5372184", + "genome.rev.2.bt2:md5,e3b4ef343dea4dd571642010a7d09597" + ] + ] + ] + } + ], + "timestamp": "2026-05-26T14:44:03.047395234", + "meta": { + "nf-test": "0.9.5", + "nextflow": "26.04.0" + } + } +} \ No newline at end of file diff --git a/subworkflows/nf-core/fastq_fetch_clean_hostile/tests/nextflow.config b/subworkflows/nf-core/fastq_fetch_clean_hostile/tests/nextflow.config new file mode 100644 index 0000000..08d4009 --- /dev/null +++ b/subworkflows/nf-core/fastq_fetch_clean_hostile/tests/nextflow.config @@ -0,0 +1,5 @@ +process { + withName: 'HOSTILE_CLEAN' { + ext.args = '--aligner bowtie2' + } +} diff --git a/subworkflows/nf-core/fastq_index_filter_deacon/main.nf b/subworkflows/nf-core/fastq_index_filter_deacon/main.nf new file mode 100644 index 0000000..33cecad --- /dev/null +++ b/subworkflows/nf-core/fastq_index_filter_deacon/main.nf @@ -0,0 +1,65 @@ +include { DEACON_INDEX } from '../../../modules/nf-core/deacon/index/main' +include { DEACON_FILTER } from '../../../modules/nf-core/deacon/filter/main' + +workflow FASTQ_INDEX_FILTER_DEACON { + + take: + ch_fasta_reads // [ val(meta), [ fasta ], [ reads ] ] + + main: + + // Check if fastqs are single-end or paired-end + ch_fasta_reads = ch_fasta_reads + .map { meta, fasta, reads -> + if (meta.single_end) { + if (reads instanceof List && reads.size() != 1) { + error("Error: Check your meta.single_end value. Single-end reads should contain one file only.") + } + return [ meta, fasta, reads ] + } else { + if (!(reads instanceof List) || reads.size() != 2) { + error("Error: Check your meta.single_end value. Paired-end reads should contain two files; a forward and a reverse.") + } + return [ meta, fasta, reads ] + } + } + + // Extract unique reference fasta files and create fasta-specific metadata + // This ensures each unique reference is indexed only once + ch_unique_fastas = ch_fasta_reads + .map { _meta, fasta, _reads -> fasta } + .unique() + .map { fasta -> + def meta_fasta = [ id: fasta.baseName ] + [ meta_fasta, fasta ] + } + + // Index unique FASTA files only + DEACON_INDEX ( ch_unique_fastas ) + + // Match indexes back to original samples using fasta file base name + ch_indexes = DEACON_INDEX.out.index + .map { meta_fasta, index -> [ meta_fasta.id, index ] } + + ch_reads_with_index = ch_fasta_reads + .map { meta, fasta, reads -> + [ fasta.baseName, meta, reads ] + } + .combine(ch_indexes, by: 0) + .map { _fasta_id, meta, reads, index -> + [ meta, index, reads ] + } + + // Filter reads using the matched index + DEACON_FILTER(ch_reads_with_index) + + // Create output channel with indexes, the original sample-level metadata and index id + ch_index_with_meta = ch_reads_with_index + .map { meta, index, _reads -> [ meta + [ index_id: index.baseName ], index ] } + + + emit: + index = ch_index_with_meta // channel: [ val(meta), [ index ] ] + fastq_filtered = DEACON_FILTER.out.fastq_filtered // channel: [ val(meta), [ fastq ] ] + summary = DEACON_FILTER.out.log // channel: [ val(meta), [ log ] ] +} diff --git a/subworkflows/nf-core/fastq_index_filter_deacon/meta.yml b/subworkflows/nf-core/fastq_index_filter_deacon/meta.yml new file mode 100644 index 0000000..6cb64e0 --- /dev/null +++ b/subworkflows/nf-core/fastq_index_filter_deacon/meta.yml @@ -0,0 +1,48 @@ +# yaml-language-server: $schema=https://raw.githubusercontent.com/nf-core/modules/master/subworkflows/yaml-schema.json +name: "fastq_index_filter_deacon" +description: | + Decontaminate FastQ files by filtering reads that match a reference genome using Deacon +keywords: + - filter + - index + - fasta + - fastq + - genome + - reference + - minimizer + - decontamination +components: + - deacon/index + - deacon/filter +input: + - ch_fasta_reads: + type: file + description: | + Input genome fasta file and a list of FastQ files of size 1 and 2 for single-end and paired-end data, respectively. + Structure: [ val(meta), path(fasta), [ path(reads) ] ] +output: + - index: + type: file + description: | + Deacon minimizer index file + Structure: [ val(meta), path(index) ] + pattern: ".idx" + - fastq_filtered: + type: file + description: | + List of output filtered FastQ files of size 1 and 2 for single-end and paired-end data, respectively. + Structure: [ val(meta), path(${prefix}*.fq) ] + pattern: "*.fq.gz" + - summary: + type: file + description: | + JSON file containing summary of results. + Structure: [ val(meta), path(${prefix}.json) ] + pattern: "*.json" +authors: + - "@Baksic-Ivan" + - "@Omer0191" +maintainers: + - "@Baksic-Ivan" + - "@Omer0191" + - "@pmoris" diff --git a/subworkflows/nf-core/fastq_index_filter_deacon/tests/main.nf.test b/subworkflows/nf-core/fastq_index_filter_deacon/tests/main.nf.test new file mode 100644 index 0000000..7c1f04d --- /dev/null +++ b/subworkflows/nf-core/fastq_index_filter_deacon/tests/main.nf.test @@ -0,0 +1,127 @@ +nextflow_workflow { + + name "Test Subworkflow FASTQ_INDEX_FILTER_DEACON" + script "../main.nf" + workflow "FASTQ_INDEX_FILTER_DEACON" + + tag "subworkflows" + tag "subworkflows_nfcore" + tag "subworkflows/fastq_index_filter_deacon" + tag "deacon" + tag "deacon/index" + tag "deacon/filter" + + test("sarscov2 - fastq - single-end") { + + when { + workflow { + """ + input[0] = channel.of( + [ + [ id:'test', single_end:true ], + file(params.modules_testdata_base_path + 'genomics/sarscov2/genome/genome.fasta', checkIfExists: true), + file(params.modules_testdata_base_path + 'genomics/sarscov2/illumina/fastq/test_1.fastq.gz', checkIfExists: true) + ] + ) + """ + } + } + + then { + assertAll( + { assert workflow.success }, + { assert snapshot(sanitizeOutput(workflow.out, unstableKeys:["summary"])).match() } + ) + } + } + + test("sarscov2 - fastq - paired-end") { + + when { + workflow { + """ + input[0] = channel.of( + [ + [ id:'test', single_end:false ], + file(params.modules_testdata_base_path + 'genomics/sarscov2/genome/genome.fasta', checkIfExists: true), + [ + 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 workflow.success }, + { assert snapshot(sanitizeOutput(workflow.out, unstableKeys:["summary"])).match() } + ) + } + } + + test("sarscov2 - fastq - single-end - stub") { + + options "-stub" + + when { + workflow { + """ + input[0] = channel.of( + [ + [ id:'test', single_end:true ], + file(params.modules_testdata_base_path + 'genomics/sarscov2/genome/genome.fasta', checkIfExists: true), + file(params.modules_testdata_base_path + 'genomics/sarscov2/illumina/fastq/test_1.fastq.gz', checkIfExists: true) + ] + ) + """ + } + } + + then { + assertAll( + { assert workflow.success }, + { assert snapshot(sanitizeOutput(workflow.out)).match() } + ) + } + } + + test("sarscov2 - identical fasta should be indexed only once") { + + when { + workflow { + """ + input[0] = channel.of( + [ + [ id:'sample1', single_end:true ], + file(params.modules_testdata_base_path + 'genomics/sarscov2/genome/genome.fasta', checkIfExists: true), + file(params.modules_testdata_base_path + 'genomics/sarscov2/illumina/fastq/test_1.fastq.gz', checkIfExists: true) + ], + [ + [ id:'sample2', single_end:true ], + file(params.modules_testdata_base_path + 'genomics/sarscov2/genome/genome.fasta', checkIfExists: true), + file(params.modules_testdata_base_path + 'genomics/sarscov2/illumina/fastq/test_2.fastq.gz', checkIfExists: true) + ] + ) + """ + } + } + + then { + assertAll( + { assert workflow.success }, + { assert workflow.out.fastq_filtered.size() == 2 }, + { assert workflow.out.index.size() == 2 }, + { + def indexRuns = workflow.trace.tasks.count { task -> + task.name.contains('DEACON_INDEX') + } + assert indexRuns == 1 + }, + { assert snapshot(sanitizeOutput(workflow.out, unstableKeys:["summary"])).match() } + ) + } + } +} diff --git a/subworkflows/nf-core/fastq_index_filter_deacon/tests/main.nf.test.snap b/subworkflows/nf-core/fastq_index_filter_deacon/tests/main.nf.test.snap new file mode 100644 index 0000000..92f58ba --- /dev/null +++ b/subworkflows/nf-core/fastq_index_filter_deacon/tests/main.nf.test.snap @@ -0,0 +1,183 @@ +{ + "sarscov2 - fastq - single-end - stub": { + "content": [ + { + "fastq_filtered": [ + [ + { + "id": "test", + "single_end": true + }, + "test.fq.gz:md5,68b329da9893e34099c7d8ad5cb9c940" + ] + ], + "index": [ + [ + { + "id": "test", + "single_end": true, + "index_id": "genome" + }, + "genome.idx:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "summary": [ + [ + { + "id": "test", + "single_end": true + }, + "test.json:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ] + } + ], + "timestamp": "2026-06-25T09:44:29.66627682", + "meta": { + "nf-test": "0.9.5", + "nextflow": "26.04.3" + } + }, + "sarscov2 - fastq - paired-end": { + "content": [ + { + "fastq_filtered": [ + [ + { + "id": "test", + "single_end": false + }, + [ + "test_1.fq.gz:md5,bf1aa22249e7e2b462c6be1a53ac55e9", + "test_2.fq.gz:md5,d3106ad7395c78a212e6214177d5c054" + ] + ] + ], + "index": [ + [ + { + "id": "test", + "single_end": false, + "index_id": "genome" + }, + "genome.idx:md5,84e4985c91800686db9c9dca28fabd1a" + ] + ], + "summary": [ + [ + { + "id": "test", + "single_end": false + }, + "test.json" + ] + ] + } + ], + "timestamp": "2026-06-25T09:31:25.930510862", + "meta": { + "nf-test": "0.9.5", + "nextflow": "26.04.3" + } + }, + "sarscov2 - fastq - single-end": { + "content": [ + { + "fastq_filtered": [ + [ + { + "id": "test", + "single_end": true + }, + "test.fq.gz:md5,2250e99ec61fca0e28e2dcb5124d1a16" + ] + ], + "index": [ + [ + { + "id": "test", + "single_end": true, + "index_id": "genome" + }, + "genome.idx:md5,84e4985c91800686db9c9dca28fabd1a" + ] + ], + "summary": [ + [ + { + "id": "test", + "single_end": true + }, + "test.json" + ] + ] + } + ], + "timestamp": "2026-06-25T09:31:18.10054354", + "meta": { + "nf-test": "0.9.5", + "nextflow": "26.04.3" + } + }, + "sarscov2 - identical fasta should be indexed only once": { + "content": [ + { + "fastq_filtered": [ + [ + { + "id": "sample1", + "single_end": true + }, + "sample1.fq.gz:md5,2250e99ec61fca0e28e2dcb5124d1a16" + ], + [ + { + "id": "sample2", + "single_end": true + }, + "sample2.fq.gz:md5,fd26c42f54e3316dfc8d082d9b6ed209" + ] + ], + "index": [ + [ + { + "id": "sample1", + "single_end": true, + "index_id": "genome" + }, + "genome.idx:md5,84e4985c91800686db9c9dca28fabd1a" + ], + [ + { + "id": "sample2", + "single_end": true, + "index_id": "genome" + }, + "genome.idx:md5,84e4985c91800686db9c9dca28fabd1a" + ] + ], + "summary": [ + [ + { + "id": "sample1", + "single_end": true + }, + "sample1.json" + ], + [ + { + "id": "sample2", + "single_end": true + }, + "sample2.json" + ] + ] + } + ], + "timestamp": "2026-06-25T09:24:55.20131212", + "meta": { + "nf-test": "0.9.5", + "nextflow": "26.04.3" + } + } +} \ No newline at end of file diff --git a/subworkflows/nf-core/fastq_preprocess_seqkit/main.nf b/subworkflows/nf-core/fastq_preprocess_seqkit/main.nf new file mode 100644 index 0000000..0c4e01f --- /dev/null +++ b/subworkflows/nf-core/fastq_preprocess_seqkit/main.nf @@ -0,0 +1,70 @@ +include { FASTQ_SANITISE_SEQKIT } from '../fastq_sanitise_seqkit' +include { SEQKIT_SEQ } from '../../../modules/nf-core/seqkit/seq' +include { SEQKIT_REPLACE } from '../../../modules/nf-core/seqkit/replace' +include { SEQKIT_RMDUP } from '../../../modules/nf-core/seqkit/rmdup' + +workflow FASTQ_PREPROCESS_SEQKIT { + take: + ch_reads // channel: [ val(meta), [ fastq ] ] + skip_seqkit_sana_pair // boolean + skip_seqkit_seq // boolean + skip_seqkit_replace // boolean + skip_seqkit_rmdup // boolean + + main: + if (!skip_seqkit_sana_pair) { + FASTQ_SANITISE_SEQKIT(ch_reads) + ch_reads = FASTQ_SANITISE_SEQKIT.out.reads + } + + // Split paired-end reads and add strandedness to meta + ch_reads_split = ch_reads.flatMap { meta, reads -> + if (meta.single_end) { + if (reads instanceof List && reads.size() != 1) { + error("Error: Check your meta.single_end value. Single-end reads should contain one file only.") + } + return [[meta + [strandness: 'single'], reads]] + } + else { + if (!(reads instanceof List) || reads.size() != 2) { + error("Error: Check your meta.single_end value. Paired-end data should have exactly 2 files.") + } + return [[meta + [strandness: 'R1'], reads[0]], [meta + [strandness: 'R2'], reads[1]]] + } + } + + if (!skip_seqkit_seq) { + SEQKIT_SEQ(ch_reads_split) + ch_reads_split = SEQKIT_SEQ.out.fastx + } + + if (!skip_seqkit_replace) { + SEQKIT_REPLACE(ch_reads_split, '') + ch_reads_split = SEQKIT_REPLACE.out.fastx + } + + if (!skip_seqkit_rmdup) { + SEQKIT_RMDUP(ch_reads_split) + ch_reads_split = SEQKIT_RMDUP.out.fastx + } + + ch_reads = ch_reads_split + .map { meta, fastq -> + // Remove strandness field from meta to merge back together + def clean_meta = meta.findAll { key, _value -> key != 'strandness' } + return [clean_meta, fastq] + } + .groupTuple(by: 0) + .map { meta, files -> + if (meta.single_end) { + return [meta, files[0]] + } + else { + def sorted_files = files.flatten().sort { index -> index.name } + return [meta, sorted_files] + } + } + + emit: + reads = ch_reads // channel: [ val(meta), [ fastq ] ] +} diff --git a/subworkflows/nf-core/fastq_preprocess_seqkit/meta.yml b/subworkflows/nf-core/fastq_preprocess_seqkit/meta.yml new file mode 100644 index 0000000..a3c6eaa --- /dev/null +++ b/subworkflows/nf-core/fastq_preprocess_seqkit/meta.yml @@ -0,0 +1,58 @@ +# yaml-language-server: $schema=https://raw.githubusercontent.com/nf-core/modules/master/subworkflows/yaml-schema.json +name: "fastq_preprocess_seqkit" +description: Subworkflow that preprocesses FASTQ files +keywords: + - fastq + - seqkit + - preprocessing +components: + - fastq_sanitise_seqkit + - seqkit/sana + - seqkit/pair + - seqkit/seq + - seqkit/replace + - seqkit/rmdup +input: + - ch_reads: + type: channel + description: | + Channel containing sample metadata and FASTQ files. + Structure: [ val(meta), [ fastq ] ] + Where meta is a map containing at least: + - id: sample identifier + - single_end: boolean indicating if data is single-end (true) or paired-end (false) + pattern: "*.{fastq,fastq.gz,fq,fq.gz}" + - skip_seqkit_sana_pair: + type: boolean + description: | + If true, skips the seqkit_sana_pair subworkflow. + - skip_seqkit_seq: + type: boolean + description: | + If true, skips the seqkit_seq process. + - skip_seqkit_replace: + type: boolean + description: | + If true, skips the seqkit_replace process. + - skip_seqkit_rmdup: + type: boolean + description: | + If true, skips the seqkit_rmdup process. +output: + - reads: + type: channel + description: | + Channel containing filtered FASTQ files. + Structure: [ val(meta), [ fastq ] ] + pattern: "*.{fastq,fastq.gz,fq,fq.gz}" + - versions: + type: file + description: | + File containing software versions + Structure: [ path(versions.yml) ] + pattern: "versions.yml" +authors: + - "@maia-munteanu" +maintainers: + - "@maia-munteanu" + - "@vagkaratzas" diff --git a/subworkflows/nf-core/fastq_preprocess_seqkit/nextflow.config b/subworkflows/nf-core/fastq_preprocess_seqkit/nextflow.config new file mode 100644 index 0000000..4e61558 --- /dev/null +++ b/subworkflows/nf-core/fastq_preprocess_seqkit/nextflow.config @@ -0,0 +1,6 @@ +// IMPORTANT: This config file should be included to ensure that the subworkflow works properly. +process { + withName: SEQKIT_SANA { + ext.prefix = { "${meta.id}_${meta.strandness}" } + } +} diff --git a/subworkflows/nf-core/fastq_preprocess_seqkit/tests/main.nf.test b/subworkflows/nf-core/fastq_preprocess_seqkit/tests/main.nf.test new file mode 100644 index 0000000..2495c7e --- /dev/null +++ b/subworkflows/nf-core/fastq_preprocess_seqkit/tests/main.nf.test @@ -0,0 +1,140 @@ +nextflow_workflow { + + name "Test Subworkflow FASTQ_PREPROCESS_SEQKIT" + script "../main.nf" + workflow "FASTQ_PREPROCESS_SEQKIT" + config './nextflow.config' + + tag "subworkflows" + tag "subworkflows_nfcore" + tag "subworkflows/fastq_sanitise_seqkit" + tag "subworkflows/fastq_preprocess_seqkit" + tag "seqkit" + tag "seqkit/sana" + tag "seqkit/pair" + tag "seqkit/seq" + tag "seqkit/replace" + tag "seqkit/rmdup" + + test("sarscov2 - fastq - single_end") { + + when { + workflow { + """ + input[0] = Channel.of([ + [ id:'test_single', single_end:true ], // meta map + file(params.modules_testdata_base_path + 'genomics/sarscov2/illumina/fastq/test_1.fastq.gz', checkIfExists: true) + ]) + input[1] = false // skip_seqkit_sana_pair + input[2] = false // skip_seqkit_seq + input[3] = false // skip_seqkit_replace + input[4] = false // skip_seqkit_rmdup + """ + } + } + + then { + assertAll( + { assert workflow.success }, + { assert snapshot( + sanitizeOutput(workflow.out) + ).match() } + ) + } + } + + test("sarscov2 - fastq - paired_end") { + + when { + workflow { + """ + input[0] = Channel.of([ + [ id:'test_paired', 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] = false // skip_seqkit_sana_pair + input[2] = false // skip_seqkit_seq + input[3] = false // skip_seqkit_replace + input[4] = false // skip_seqkit_rmdup + """ + } + } + + then { + assertAll( + { assert workflow.success }, + { assert snapshot( + sanitizeOutput(workflow.out) + ).match() } + ) + } + } + + test("sarscov2 - fastq - both with single broken") { + + when { + workflow { + """ + input[0] = Channel.of( + [ + [ id:'test_both', single_end:true ], // meta map + file(params.modules_testdata_base_path + 'genomics/sarscov2/illumina/fastq/test_1_broken.fastq.gz', checkIfExists: true) + ], + [ + [ id:'test_both', 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] = false // skip_seqkit_sana_pair + input[2] = false // skip_seqkit_seq + input[3] = false // skip_seqkit_replace + input[4] = false // skip_seqkit_rmdup + """ + } + } + + then { + assertAll( + { assert workflow.success }, + { assert snapshot( + sanitizeOutput(workflow.out) + ).match() } + ) + } + } + + test("sarscov2 - fastq - stub") { + + options "-stub" + + when { + workflow { + """ + input[0] = Channel.of([ + [ id: 'test_stub', single_end:true ], + file(params.modules_testdata_base_path + 'genomics/sarscov2/illumina/fastq/test_1_broken.fastq.gz', checkIfExists: true) + ]) + input[1] = false // skip_seqkit_sana_pair + input[2] = false // skip_seqkit_seq + input[3] = false // skip_seqkit_replace + input[4] = false // skip_seqkit_rmdup + """ + } + } + + then { + assertAll( + { assert workflow.success }, + { assert snapshot( + sanitizeOutput(workflow.out) + ).match() } + ) + } + } +} diff --git a/subworkflows/nf-core/fastq_preprocess_seqkit/tests/main.nf.test.snap b/subworkflows/nf-core/fastq_preprocess_seqkit/tests/main.nf.test.snap new file mode 100644 index 0000000..4eb01fa --- /dev/null +++ b/subworkflows/nf-core/fastq_preprocess_seqkit/tests/main.nf.test.snap @@ -0,0 +1,95 @@ +{ + "sarscov2 - fastq - stub": { + "content": [ + { + "reads": [ + [ + { + "id": "test_stub", + "single_end": true + }, + "test_stub_single.fastq.gz:md5,68b329da9893e34099c7d8ad5cb9c940" + ] + ] + } + ], + "timestamp": "2026-06-14T17:59:37.811271025", + "meta": { + "nf-test": "0.9.5", + "nextflow": "26.04.3" + } + }, + "sarscov2 - fastq - single_end": { + "content": [ + { + "reads": [ + [ + { + "id": "test_single", + "single_end": true + }, + "test_single_single.fastq.gz:md5,c1dbd30da9ad368bb7bf5a117bb765b2" + ] + ] + } + ], + "timestamp": "2026-06-12T17:59:36.869448293", + "meta": { + "nf-test": "0.9.5", + "nextflow": "26.04.3" + } + }, + "sarscov2 - fastq - paired_end": { + "content": [ + { + "reads": [ + [ + { + "id": "test_paired", + "single_end": false + }, + [ + "test_paired_R1.fastq.gz:md5,c1dbd30da9ad368bb7bf5a117bb765b2", + "test_paired_R2.fastq.gz:md5,e69b523dbbc543b86b078718b55db757" + ] + ] + ] + } + ], + "timestamp": "2026-06-12T17:59:44.347549388", + "meta": { + "nf-test": "0.9.5", + "nextflow": "26.04.3" + } + }, + "sarscov2 - fastq - both with single broken": { + "content": [ + { + "reads": [ + [ + { + "id": "test_both", + "single_end": false + }, + [ + "test_both_R1.fastq.gz:md5,c1dbd30da9ad368bb7bf5a117bb765b2", + "test_both_R2.fastq.gz:md5,e69b523dbbc543b86b078718b55db757" + ] + ], + [ + { + "id": "test_both", + "single_end": true + }, + "test_both_single.fastq.gz:md5,8d33c2a78ed2a29919b44e64b1251d6d" + ] + ] + } + ], + "timestamp": "2026-06-12T17:59:52.287204859", + "meta": { + "nf-test": "0.9.5", + "nextflow": "26.04.3" + } + } +} \ No newline at end of file diff --git a/subworkflows/nf-core/fastq_preprocess_seqkit/tests/nextflow.config b/subworkflows/nf-core/fastq_preprocess_seqkit/tests/nextflow.config new file mode 100644 index 0000000..5c3717f --- /dev/null +++ b/subworkflows/nf-core/fastq_preprocess_seqkit/tests/nextflow.config @@ -0,0 +1,25 @@ +process { + withName: SEQKIT_SANA { + ext.prefix = { "${meta.id}_${meta.strandness}" } + } + + withName: SEQKIT_SEQ { + ext.args = [ + "--remove-gaps", + "--upper-case", + "--validate-seq", + "--min-len 30", + "--max-len 5000" + ].join(' ').trim() + ext.prefix = { "intermediate_seqkit_seq_${meta.strandness}" } + } + + withName: SEQKIT_REPLACE { + ext.args = '-p "/" -r "_"' + ext.prefix = { "intermediate_seqkit_replace_${meta.strandness}" } + } + + withName: SEQKIT_RMDUP { + ext.prefix = { "${meta.id}_${meta.strandness}" } + } +} diff --git a/subworkflows/nf-core/fastq_qc_stats/main.nf b/subworkflows/nf-core/fastq_qc_stats/main.nf new file mode 100644 index 0000000..c61c289 --- /dev/null +++ b/subworkflows/nf-core/fastq_qc_stats/main.nf @@ -0,0 +1,60 @@ +include { FASTQC } from '../../../modules/nf-core/fastqc' +include { SEQFU_CHECK } from '../../../modules/nf-core/seqfu/check' +include { SEQFU_STATS } from '../../../modules/nf-core/seqfu/stats' +include { SEQKIT_STATS } from '../../../modules/nf-core/seqkit/stats' +include { SEQTK_COMP } from '../../../modules/nf-core/seqtk/comp' + +workflow FASTQ_QC_STATS { + take: + ch_reads // channel: [ val(meta), [ fastq ] ] + skip_fastqc // boolean + skip_seqfu_check // boolean + skip_seqfu_stats // boolean + skip_seqkit_stats // boolean + skip_seqtk_comp // boolean + + main: + ch_fastqc_html = channel.empty() + ch_fastqc_zip = channel.empty() + ch_seqfu_check = channel.empty() + ch_seqfu_stats = channel.empty() + ch_seqfu_multiqc = channel.empty() + ch_seqkit_stats = channel.empty() + ch_seqtk_stats = channel.empty() + + if (!skip_fastqc) { + FASTQC(ch_reads) + ch_fastqc_html = FASTQC.out.html + ch_fastqc_zip = FASTQC.out.zip + } + + if (!skip_seqfu_check) { + SEQFU_CHECK(ch_reads) + ch_seqfu_check = SEQFU_CHECK.out.check + } + + if (!skip_seqfu_stats) { + SEQFU_STATS(ch_reads) + ch_seqfu_stats = SEQFU_STATS.out.stats + ch_seqfu_multiqc = SEQFU_STATS.out.multiqc + } + + if (!skip_seqkit_stats) { + SEQKIT_STATS(ch_reads) + ch_seqkit_stats = SEQKIT_STATS.out.stats + } + + if (!skip_seqtk_comp) { + SEQTK_COMP(ch_reads) + ch_seqtk_stats = SEQTK_COMP.out.seqtk_stats + } + + emit: + fastqc_html = ch_fastqc_html + fastqc_zip = ch_fastqc_zip + seqfu_check = ch_seqfu_check + seqfu_stats = ch_seqfu_stats + seqfu_multiqc = ch_seqfu_multiqc + seqkit_stats = ch_seqkit_stats + seqtk_stats = ch_seqtk_stats +} diff --git a/subworkflows/nf-core/fastq_qc_stats/meta.yml b/subworkflows/nf-core/fastq_qc_stats/meta.yml new file mode 100644 index 0000000..0e8bc30 --- /dev/null +++ b/subworkflows/nf-core/fastq_qc_stats/meta.yml @@ -0,0 +1,76 @@ +# yaml-language-server: $schema=https://raw.githubusercontent.com/nf-core/modules/master/modules/meta-schema.json +name: "fastq_qc_stats" +description: Generate statistics for short read sequencing data using multiple tools +keywords: + - fastq + - qc + - fastqc + - seqfu + - seqkit + - seqtk +components: + - fastqc + - seqfu/check + - seqfu/stats + - seqkit/stats + - seqtk/comp +input: + - reads: + type: file + description: | + List of input FastQ files of size 1 and 2 for single-end and paired-end data, + respectively. + - skip_fastqc: + type: boolean + description: | + Skip fastqc process + - skip_seqfu_check: + type: boolean + description: | + Skip seqfu_check process + - skip_seqfu_stats: + type: boolean + description: | + Skip seqfu_stats process + - skip_seqkit_stats: + type: boolean + description: | + Skip seqkit_stats process + - skip_seqtk_comp: + type: boolean + description: | + Skip seqtk_comp process +output: + - fastqc_html: + type: file + description: FastQC report + pattern: "*_fastqc.html" + - fastqc_zip: + type: file + description: FastQC report archive + pattern: "*_fastqc.zip" + - seqfu_check: + type: file + description: seqfu check tsv report + pattern: "*.tsv" + - seqfu_stats: + type: file + description: seqfu stats tsv report + pattern: "*.tsv" + - seqfu_multiqc: + type: file + description: seqfu stats MultiQC report + pattern: "*_mqc.txt" + - seqkit_stats: + type: file + description: seqkit stats report + pattern: "*.tsv" + - versions: + type: file + description: File containing software versions + pattern: "versions.yml" +authors: + - "@pablo-scd" +maintainers: + - "@pablo-scd" + - "@vagkaratzas" diff --git a/subworkflows/nf-core/fastq_qc_stats/tests/main.nf.test b/subworkflows/nf-core/fastq_qc_stats/tests/main.nf.test new file mode 100644 index 0000000..3706439 --- /dev/null +++ b/subworkflows/nf-core/fastq_qc_stats/tests/main.nf.test @@ -0,0 +1,167 @@ +nextflow_workflow { + name "Test Subworkflow FASTQ_QC_STATS" + script "../main.nf" + workflow "FASTQ_QC_STATS" + + tag "subworkflows" + tag "subworkflows_nfcore" + tag "subworkflows/fastq_qc_stats" + tag "fastqc" + tag "seqfu" + tag "seqfu/check" + tag "seqfu/stats" + tag "seqkit" + tag "seqkit/stats" + tag "seqtk" + tag "seqtk/comp" + + test("sarscov2 - fastq - single_end") { + when { + workflow { + """ + input[0] = channel.of([ + [ id:'test_single', single_end:true ], + file(params.modules_testdata_base_path + 'genomics/sarscov2/illumina/fastq/test_1.fastq.gz', checkIfExists: true) + ]) + input[1] = false + input[2] = false + input[3] = false + input[4] = false + input[5] = false + """ + } + } + then { + assertAll( + { assert workflow.success }, + { assert snapshot( + file(workflow.out.fastqc_html[0][1]).name, + file(workflow.out.fastqc_zip[0][1]).name, + workflow.out.seqfu_check, + workflow.out.seqfu_stats, + workflow.out.seqfu_multiqc, + workflow.out.seqkit_stats, + workflow.out.seqtk_stats + ).match() } + ) + } + } + + test("sarscov2 - fastq - single_end - skip all") { + when { + workflow { + """ + input[0] = channel.of([ + [ id:'test_single', single_end:true ], // meta map + file(params.modules_testdata_base_path + 'genomics/sarscov2/illumina/fastq/test_1.fastq.gz', checkIfExists: true) + ]) + input[1] = true + input[2] = true + input[3] = true + input[4] = true + input[5] = true + """ + } + } + then { + assertAll( + { assert workflow.success }, + { assert snapshot(workflow.out).match() } + ) + } + } + + test("sarscov2 - fastq - paired_end") { + when { + workflow { + """ + input[0] = channel.of([ + [ id:'test_paired', 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] = false + input[2] = false + input[3] = false + input[4] = false + input[5] = false + """ + } + } + then { + assertAll( + { assert workflow.success }, + { assert snapshot( + file(workflow.out.fastqc_html[0][1][0]).name, + file(workflow.out.fastqc_zip[0][1][0]).name, + file(workflow.out.fastqc_html[0][1][1]).name, + file(workflow.out.fastqc_zip[0][1][1]).name, + workflow.out.seqfu_check, + workflow.out.seqfu_stats, + workflow.out.seqfu_multiqc, + workflow.out.seqkit_stats, + workflow.out.seqtk_stats + ).match() } + ) + } + } + + test("sarscov2 - fastq - single_end - stub") { + + options "-stub" + + when { + workflow { + """ + input[0] = channel.of([ + [ id:'test_single', single_end:true ], // meta map + file(params.modules_testdata_base_path + 'genomics/sarscov2/illumina/fastq/test_1.fastq.gz', checkIfExists: true) + ]) + input[1] = false + input[2] = false + input[3] = false + input[4] = false + input[5] = false + """ + } + } + then { + assertAll( + { assert workflow.success}, + { assert snapshot(sanitizeOutput(workflow.out)).match() } + ) + } + } + + test("sarscov2 - fastq - paired_end - stub") { + + options "-stub" + + when { + workflow { + """ + input[0] = channel.of([ + [ id:'test_paired', 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] = false + input[2] = false + input[3] = false + input[4] = false + input[5] = false + """ + } + } + then { + assertAll( + { assert workflow.success}, + { assert snapshot(sanitizeOutput(workflow.out)).match() } + ) + } + } +} diff --git a/subworkflows/nf-core/fastq_qc_stats/tests/main.nf.test.snap b/subworkflows/nf-core/fastq_qc_stats/tests/main.nf.test.snap new file mode 100644 index 0000000..d61689e --- /dev/null +++ b/subworkflows/nf-core/fastq_qc_stats/tests/main.nf.test.snap @@ -0,0 +1,317 @@ +{ + "sarscov2 - fastq - single_end - stub": { + "content": [ + { + "fastqc_html": [ + [ + { + "id": "test_single", + "single_end": true + }, + "test_single.html:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "fastqc_zip": [ + [ + { + "id": "test_single", + "single_end": true + }, + "test_single.zip:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "seqfu_check": [ + [ + { + "id": "test_single", + "single_end": true + }, + "test_single.tsv:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "seqfu_multiqc": [ + [ + { + "id": "test_single", + "single_end": true + }, + "test_single_mqc.txt:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "seqfu_stats": [ + [ + { + "id": "test_single", + "single_end": true + }, + "test_single.tsv:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "seqkit_stats": [ + [ + { + "id": "test_single", + "single_end": true + }, + "test_single.tsv:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "seqtk_stats": [ + [ + { + "id": "test_single", + "single_end": true + }, + "test_single.seqtk_stats.tsv:md5,68b329da9893e34099c7d8ad5cb9c940" + ] + ] + } + ], + "timestamp": "2026-02-20T11:46:39.792312215", + "meta": { + "nf-test": "0.9.4", + "nextflow": "25.10.4" + } + }, + "sarscov2 - fastq - single_end - skip all": { + "content": [ + { + "0": [ + + ], + "1": [ + + ], + "2": [ + + ], + "3": [ + + ], + "4": [ + + ], + "5": [ + + ], + "6": [ + + ], + "fastqc_html": [ + + ], + "fastqc_zip": [ + + ], + "seqfu_check": [ + + ], + "seqfu_multiqc": [ + + ], + "seqfu_stats": [ + + ], + "seqkit_stats": [ + + ], + "seqtk_stats": [ + + ] + } + ], + "timestamp": "2026-02-20T11:46:19.133405302", + "meta": { + "nf-test": "0.9.4", + "nextflow": "25.10.4" + } + }, + "sarscov2 - fastq - single_end": { + "content": [ + "test_single_fastqc.html", + "test_single_fastqc.zip", + [ + [ + { + "id": "test_single", + "single_end": true + }, + "test_single.tsv:md5,4c6409169772005cfb06be9e41f2c1e2" + ] + ], + [ + [ + { + "id": "test_single", + "single_end": true + }, + "test_single.tsv:md5,7573d0c83cfc9af6e1ced67a45265381" + ] + ], + [ + [ + { + "id": "test_single", + "single_end": true + }, + "test_single_mqc.txt:md5,a039b8c1cc923db88d2484d3abbf00fe" + ] + ], + [ + [ + { + "id": "test_single", + "single_end": true + }, + "test_single.tsv:md5,76d5ab444de880ef443f358841e44cf9" + ] + ], + [ + [ + { + "id": "test_single", + "single_end": true + }, + "test_single.seqtk_stats.tsv:md5,162f75dd3cfce99bd195aa5f8bd467d5" + ] + ] + ], + "timestamp": "2026-06-12T17:45:03.575449675", + "meta": { + "nf-test": "0.9.5", + "nextflow": "26.04.3" + } + }, + "sarscov2 - fastq - paired_end": { + "content": [ + "test_paired_1_fastqc.html", + "test_paired_1_fastqc.zip", + "test_paired_2_fastqc.html", + "test_paired_2_fastqc.zip", + [ + [ + { + "id": "test_paired", + "single_end": false + }, + "test_paired.tsv:md5,c3e6eae028f23041bd09cac9c371f318" + ] + ], + [ + [ + { + "id": "test_paired", + "single_end": false + }, + "test_paired.tsv:md5,d9277db149ae44112a704b66d72ec07e" + ] + ], + [ + [ + { + "id": "test_paired", + "single_end": false + }, + "test_paired_mqc.txt:md5,082187706aad0e06b86297d4fcd60bec" + ] + ], + [ + [ + { + "id": "test_paired", + "single_end": false + }, + "test_paired.tsv:md5,2d96d9f7d85456090e138b81bf97bcd5" + ] + ], + [ + [ + { + "id": "test_paired", + "single_end": false + }, + "test_paired.seqtk_stats.tsv:md5,162f75dd3cfce99bd195aa5f8bd467d5" + ] + ] + ], + "timestamp": "2026-06-12T17:45:19.114777238", + "meta": { + "nf-test": "0.9.5", + "nextflow": "26.04.3" + } + }, + "sarscov2 - fastq - paired_end - stub": { + "content": [ + { + "fastqc_html": [ + [ + { + "id": "test_paired", + "single_end": false + }, + "test_paired.html:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "fastqc_zip": [ + [ + { + "id": "test_paired", + "single_end": false + }, + "test_paired.zip:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "seqfu_check": [ + [ + { + "id": "test_paired", + "single_end": false + }, + "test_paired.tsv:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "seqfu_multiqc": [ + [ + { + "id": "test_paired", + "single_end": false + }, + "test_paired_mqc.txt:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "seqfu_stats": [ + [ + { + "id": "test_paired", + "single_end": false + }, + "test_paired.tsv:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "seqkit_stats": [ + [ + { + "id": "test_paired", + "single_end": false + }, + "test_paired.tsv:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "seqtk_stats": [ + [ + { + "id": "test_paired", + "single_end": false + }, + "test_paired.seqtk_stats.tsv:md5,68b329da9893e34099c7d8ad5cb9c940" + ] + ] + } + ], + "timestamp": "2026-02-20T11:46:48.694731294", + "meta": { + "nf-test": "0.9.4", + "nextflow": "25.10.4" + } + } +} \ No newline at end of file diff --git a/subworkflows/nf-core/fastq_removeadapters_merge/main.nf b/subworkflows/nf-core/fastq_removeadapters_merge/main.nf new file mode 100644 index 0000000..d67509d --- /dev/null +++ b/subworkflows/nf-core/fastq_removeadapters_merge/main.nf @@ -0,0 +1,133 @@ +// both SE and PE +include { TRIMMOMATIC } from '../../../modules/nf-core/trimmomatic/main' +include { CUTADAPT } from '../../../modules/nf-core/cutadapt/main' +include { TRIMGALORE } from '../../../modules/nf-core/trimgalore/main' +include { BBMAP_BBDUK } from '../../../modules/nf-core/bbmap/bbduk/main' +include { LEEHOM } from '../../../modules/nf-core/leehom/main' +// both SE and PE, plus merging +include { FASTP } from '../../../modules/nf-core/fastp/main' +include { ADAPTERREMOVAL as ADAPTERREMOVAL_SE } from '../../../modules/nf-core/adapterremoval/main' +include { ADAPTERREMOVAL as ADAPTERREMOVAL_PE } from '../../../modules/nf-core/adapterremoval/main' +// helper module for concatenating adapterremoval paired-end processed reads +include { CAT_FASTQ } from '../../../modules/nf-core/cat/fastq/main' + +workflow FASTQ_REMOVEADAPTERS_MERGE { + + take: + ch_input_reads // channel: [mandatory] meta, reads + val_adapter_tool // string: [mandatory] tool_name // choose from: ["trimmomatic", "cutadapt", "trimgalore", "bbduk", "leehom", "fastp", "adapterremoval"] + ch_custom_adapters_file // channel: [optional] {fasta,txt} // fasta, for bbduk or fastp, or txt, for adapterremoval + val_save_merged // boolean: [mandatory] if true, will return the merged reads instead, for fastp and adapterremoval + val_fastp_discard_trimmed_pass // boolean: [mandatory] // only for fastp + val_fastp_save_trimmed_fail // boolean: [mandatory] // only for fastp + + main: + + ch_discarded_reads = channel.empty() // from trimmomatic, trimgalore, leehom, fastp, adapterremoval + ch_log = channel.empty() // from trimmomatic, trimgalore, fastp + ch_report = channel.empty() // from trimmomatic, trimgalore, fastp + ch_multiqc_files = channel.empty() // from trimmomatic, cutadapt, bbduk, leehom, fastp, adapterremoval + + if (val_adapter_tool == "trimmomatic") { + TRIMMOMATIC( ch_input_reads ) + + ch_processed_reads = TRIMMOMATIC.out.trimmed_reads + ch_discarded_reads = ch_discarded_reads.mix(TRIMMOMATIC.out.unpaired_reads.transpose()) // .transpose() because paired reads will output 2 unpaired files in an array + ch_log = TRIMMOMATIC.out.trim_log + ch_report = TRIMMOMATIC.out.summary + ch_multiqc_files = ch_multiqc_files.mix(TRIMMOMATIC.out.out_log) + } else if (val_adapter_tool == "cutadapt") { + CUTADAPT( ch_input_reads ) + + ch_processed_reads = CUTADAPT.out.reads + ch_multiqc_files = ch_multiqc_files.mix(CUTADAPT.out.log) + } else if (val_adapter_tool == "trimgalore") { + TRIMGALORE( ch_input_reads ) + + ch_processed_reads = TRIMGALORE.out.reads + ch_discarded_reads = ch_discarded_reads.mix(TRIMGALORE.out.unpaired) + ch_log = TRIMGALORE.out.log + ch_report = TRIMGALORE.out.html.mix(TRIMGALORE.out.zip) + } else if (val_adapter_tool == "bbduk") { + BBMAP_BBDUK( ch_input_reads, ch_custom_adapters_file ) + + ch_processed_reads = BBMAP_BBDUK.out.reads + ch_multiqc_files = ch_multiqc_files.mix(BBMAP_BBDUK.out.log) + } else if (val_adapter_tool == "leehom") { + LEEHOM( ch_input_reads ) + + ch_processed_reads = LEEHOM.out.fq_pass + .join(LEEHOM.out.unmerged_r1_fq_pass, by: 0, remainder: true) + .join(LEEHOM.out.unmerged_r2_fq_pass, by: 0, remainder: true) + .map { meta, single, r1, r2 -> + if (meta.single_end) { + return [meta, single] + } else { + return [meta, [r1, r2]] + } + } + ch_discarded_reads = ch_discarded_reads.mix(LEEHOM.out.fq_fail, LEEHOM.out.unmerged_r1_fq_fail, LEEHOM.out.unmerged_r2_fq_fail) + ch_multiqc_files = ch_multiqc_files.mix(LEEHOM.out.log) + } else if (val_adapter_tool == "fastp") { + FASTP( + ch_input_reads.map { meta, files -> [ meta, files, ch_custom_adapters_file ] }, + val_fastp_discard_trimmed_pass, + val_fastp_save_trimmed_fail, + val_save_merged + ) + + if (val_save_merged) { + ch_processed_reads = FASTP.out.reads_merged + } else { + ch_processed_reads = FASTP.out.reads + } + ch_discarded_reads = ch_discarded_reads.mix(FASTP.out.reads_fail.transpose()) // .transpose() because paired reads have 3 fail files in an array + ch_log = FASTP.out.log + ch_report = FASTP.out.html + ch_multiqc_files = ch_multiqc_files.mix(FASTP.out.json) + } else if (val_adapter_tool == "adapterremoval") { + ch_adapterremoval_in = ch_input_reads + .branch { meta, _reads -> + single: meta.single_end + paired: !meta.single_end + } + + ADAPTERREMOVAL_SE( ch_adapterremoval_in.single, ch_custom_adapters_file ) + ADAPTERREMOVAL_PE( ch_adapterremoval_in.paired, ch_custom_adapters_file ) + + if (val_save_merged) { // merge + ch_concat_fastq = channel.empty() + .mix( + ADAPTERREMOVAL_PE.out.collapsed, + ADAPTERREMOVAL_PE.out.collapsed_truncated, + ADAPTERREMOVAL_PE.out.singles_truncated, + ) + .map { meta, reads -> + def meta_new = meta.clone() + meta_new.single_end = true + [meta_new, reads] + } + .groupTuple() + // Paired-end reads cause a nested tuple during grouping. + // We want to present a flat list of files to `CAT_FASTQ`. + .map { meta, fastq -> [meta, fastq.flatten()] } + + CAT_FASTQ( ch_concat_fastq ) + + ch_processed_reads = CAT_FASTQ.out.reads.mix(ADAPTERREMOVAL_SE.out.singles_truncated) + } else { // no merge + ch_processed_reads = ADAPTERREMOVAL_PE.out.paired_truncated.mix(ADAPTERREMOVAL_SE.out.singles_truncated) + } + ch_discarded_reads = ch_discarded_reads.mix(ADAPTERREMOVAL_SE.out.discarded, ADAPTERREMOVAL_PE.out.discarded) + ch_multiqc_files = ch_multiqc_files.mix(ADAPTERREMOVAL_PE.out.settings, ADAPTERREMOVAL_SE.out.settings) + } else { + error('Please choose one of the available adapter removal and merging tools: ["trimmomatic", "cutadapt", "trimgalore", "bbduk", "leehom", "fastp", "adapterremoval"]') + } + + emit: + processed_reads = ch_processed_reads // channel: [ val(meta), [ fastq.gz ] ] + discarded_reads = ch_discarded_reads // channel: [ val(meta), [ fastq.gz ] ] + logfile = ch_log // channel: [ val(meta), [ {log,txt} ] ] + report = ch_report // channel: [ val(meta), [ {summary,html,zip} ] ] + multiqc_files = ch_multiqc_files +} diff --git a/subworkflows/nf-core/fastq_removeadapters_merge/meta.yml b/subworkflows/nf-core/fastq_removeadapters_merge/meta.yml new file mode 100644 index 0000000..f89e7fe --- /dev/null +++ b/subworkflows/nf-core/fastq_removeadapters_merge/meta.yml @@ -0,0 +1,86 @@ +# yaml-language-server: $schema=https://raw.githubusercontent.com/nf-core/modules/master/subworkflows/yaml-schema.json +name: "fastq_removeadapters_merge" +description: Remove adapters and merge reads based on various module choices +keywords: + - adapters + - removal + - short reads + - merge + - trim +components: + - trimmomatic + - cutadapt + - trimgalore + - bbmap/bbduk + - leehom + - fastp + - adapterremoval + - cat/fastq +input: + - ch_input_reads: + type: file + description: | + List of FastQ files of size 1 and 2 for single-end and paired-end data, respectively. + Structure: [ val(meta), [ path(reads) ] ] + - val_adapter_tool: + type: string + description: | + Choose one of the available adapter removal and/or merging tools + enum: ["trimmomatic", "cutadapt", "trimgalore", "bbduk", "leehom", "fastp", "adapterremoval"] + - ch_custom_adapters_file: + type: file + description: | + Optional reference files, containing adapter and/or contaminant sequences for removal. + In fasta format for bbmap/bbduk and fastp, or in text format for AdapterRemoval (one adapter per line). + - val_save_merged: + type: boolean + description: | + Specify true to output merged reads instead + Used by fastp and adapterremoval + - val_fastp_discard_trimmed_pass: + type: boolean + description: | + Used only by fastp. + Specify true to not write any reads that pass trimming thresholds from the fastp process. + This can be used to use fastp for the output report only. + - val_fastp_save_trimmed_fail: + type: boolean + description: | + Used only by fastp. + Specify true to save files that failed to pass fastp trimming thresholds +output: + - processed_reads: + type: file + description: | + Structure: [ val(meta), path(fastq.gz) ] + The trimmed/modified single or paired end or merged fastq reads + pattern: "*.fastq.gz" + - discarded_reads: + type: file + description: | + Structure: [ val(meta), path(fastq.gz) ] + The discarded reads + pattern: "*.fastq.gz" + - logfile: + type: file + description: | + Execution log file + (trimmomatic {log}, trimgalore {txt}, fastp {log}) + pattern: "*.{log,txt}" + - report: + type: file + description: | + Execution report + (trimmomatic {summary}, trimgalore {html,zip}, fastp {html}) + pattern: "*.{summary,html,zip}" + - multiqc_files: + type: file + description: | + MultiQC-compatible output files from tools used in preprocessing + (trimmomatic, cutadapt, bbduk, leehom, fastp, adapterremoval) +authors: + - "@kornkv" + - "@vagkaratzas" +maintainers: + - "@kornkv" + - "@vagkaratzas" diff --git a/subworkflows/nf-core/fastq_removeadapters_merge/tests/main.nf.test b/subworkflows/nf-core/fastq_removeadapters_merge/tests/main.nf.test new file mode 100644 index 0000000..378a939 --- /dev/null +++ b/subworkflows/nf-core/fastq_removeadapters_merge/tests/main.nf.test @@ -0,0 +1,300 @@ +nextflow_workflow { + + name "Test Subworkflow FASTQ_REMOVEADAPTERS_MERGE" + script "../main.nf" + workflow "FASTQ_REMOVEADAPTERS_MERGE" + + tag "subworkflows" + tag "subworkflows_nfcore" + tag "subworkflows/fastq_removeadapters_merge" + tag "trimmomatic" + tag "cutadapt" + tag "trimgalore" + tag "bbmap" + tag "bbmap/bbduk" + tag "leehom" + tag "fastp" + tag "adapterremoval" + tag "cat" + tag "cat/fastq" + + test("sarscov2 - fastq - trimmomatic - single-end") { + config "./nextflow_SE.config" + when { + params { + save_merged = false + adapterremoval_args = save_merged ? "--collapse" : "" + } + workflow { + """ + 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) + ]) + input[1] = "trimmomatic" // val_adapter_tool + input[2] = [] // ch_custom_adapters_file + input[3] = params.save_merged // val_save_merged + input[4] = false // val_fastp_discard_trimmed_pass + input[5] = false // val_fastp_save_trimmed_fail + """ + } + } + + then { + assertAll( + { assert workflow.success }, + { assert snapshot( + workflow.out.processed_reads[0][1], + workflow.out.logfile[0][1], + workflow.out.report[0][1], + workflow.out.multiqc_files.collect { file(it[1]).name }, + ).match()} + ) + } + } + + test("sarscov2 - fastq - cutadapt - paired-end") { + config "./nextflow.config" + when { + params { + save_merged = false + adapterremoval_args = save_merged ? "--collapse" : "" + } + workflow { + """ + input[0] = channel.of([ + [ id:'test', single_end:false ], + [ + 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] = "cutadapt" // val_adapter_tool + input[2] = [] // ch_custom_adapters_file + input[3] = params.save_merged // val_save_merged + input[4] = false // val_fastp_discard_trimmed_pass + input[5] = false // val_fastp_save_trimmed_fail + """ + } + } + + then { + assertAll( + { assert workflow.success }, + { assert snapshot( + workflow.out.processed_reads[0][1], + workflow.out.multiqc_files.collect { file(it[1]).name } + ).match()} + ) + } + } + + test("sarscov2 - fastq - trimgalore - single-end") { + when { + params { + save_merged = false + adapterremoval_args = save_merged ? "--collapse" : "" + } + workflow { + """ + 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) + ]) + input[1] = "trimgalore" // val_adapter_tool + input[2] = [] // ch_custom_adapters_file + input[3] = params.save_merged // val_save_merged + input[4] = false // val_fastp_discard_trimmed_pass + input[5] = false // val_fastp_save_trimmed_fail + """ + } + } + + then { + assertAll( + { assert workflow.success }, + { assert snapshot( + workflow.out.processed_reads[0][1], + path(workflow.out.logfile[0][1]).readLines().size() + ).match()} + ) + } + } + + test("sarscov2 - fastq - bbduk - paired-end") { + config "./nextflow.config" + when { + params { + save_merged = false + adapterremoval_args = save_merged ? "--collapse" : "" + } + workflow { + """ + input[0] = channel.of([ + [ id:'test_bbduk', single_end:false ], + [ + 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] = "bbduk" // val_adapter_tool + input[2] = [] // ch_custom_adapters_file + input[3] = params.save_merged // val_save_merged + input[4] = false // val_fastp_discard_trimmed_pass + input[5] = false // val_fastp_save_trimmed_fail + """ + } + } + + then { + assertAll( + { assert workflow.success }, + { assert snapshot( + workflow.out.processed_reads[0][1], + workflow.out.multiqc_files.collect { file(it[1]).name } + ).match()} + ) + } + } + + test("sarscov2 - fastq - leehom - single-end") { + when { + params { + save_merged = false + adapterremoval_args = save_merged ? "--collapse" : "" + } + workflow { + """ + 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) + ]) + input[1] = "leehom" // val_adapter_tool + input[2] = [] // ch_custom_adapters_file + input[3] = params.save_merged // val_save_merged + input[4] = false // val_fastp_discard_trimmed_pass + input[5] = false // val_fastp_save_trimmed_fail + """ + } + } + + then { + assertAll( + { assert workflow.success }, + { assert snapshot( + workflow.out.processed_reads[0][1], + workflow.out.discarded_reads.collect { file(it[1]).name }, + workflow.out.multiqc_files.collect { file(it[1]).name } + ).match()} + ) + } + } + + test("sarscov2 - fastq - fastp - single-end") { + when { + params { + save_merged = false + adapterremoval_args = save_merged ? "--collapse" : "" + } + workflow { + """ + 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) + ]) + input[1] = "fastp" // val_adapter_tool + input[2] = [] // ch_custom_adapters_file + input[3] = params.save_merged // val_save_merged + input[4] = false // val_fastp_discard_trimmed_pass + input[5] = true // val_fastp_save_trimmed_fail + """ + } + } + + then { + assertAll( + { assert workflow.success }, + { assert snapshot( + workflow.out.processed_reads[0][1], + workflow.out.discarded_reads[0][1], + path(workflow.out.logfile[0][1]).readLines().size(), + path(workflow.out.report[0][1]).readLines().size(), + workflow.out.multiqc_files.collect { file(it[1]).name } + ).match()} + ) + } + } + + test("sarscov2 - fastq - adapterremoval - paired-end - merge") { + config "./nextflow_PE.config" + when { + params { + save_merged = true + adapterremoval_args = save_merged ? "--collapse" : "" + } + workflow { + """ + input[0] = channel.of([ + [ id:'test', single_end:false ], + [ + 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] = "adapterremoval" // val_adapter_tool + input[2] = [] // ch_custom_adapters_file + input[3] = params.save_merged // val_save_merged + input[4] = false // val_fastp_discard_trimmed_pass + input[5] = false // val_fastp_save_trimmed_fail + """ + } + } + + then { + assertAll( + { assert workflow.success }, + { assert snapshot( + path(workflow.out.processed_reads[0][1]).linesGzip.size(), + workflow.out.discarded_reads.collect { file(it[1]).name }, + workflow.out.multiqc_files.collect { file(it[1]).name } + ).match()} + ) + } + } + + test("sarscov2 - fastq - trimmomatic - paired-end - stub") { + config "./nextflow_PE.config" + options "-stub" + + when { + params { + save_merged = false + adapterremoval_args = save_merged ? "--collapse" : "" + } + workflow { + """ + input[0] = [ + [ id:'test', single_end:false ], + [ + 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] = "trimmomatic" // val_adapter_tool + input[2] = [] // ch_custom_adapters_file + input[3] = params.save_merged // val_save_merged + input[4] = false // val_fastp_discard_trimmed_pass + input[5] = false // val_fastp_save_trimmed_fail + """ + } + } + + then { + assertAll( + { assert workflow.success }, + { assert snapshot(workflow.out).match() } + ) + } + } + +} diff --git a/subworkflows/nf-core/fastq_removeadapters_merge/tests/main.nf.test.snap b/subworkflows/nf-core/fastq_removeadapters_merge/tests/main.nf.test.snap new file mode 100644 index 0000000..46a5e9f --- /dev/null +++ b/subworkflows/nf-core/fastq_removeadapters_merge/tests/main.nf.test.snap @@ -0,0 +1,229 @@ +{ + "sarscov2 - fastq - trimmomatic - single-end": { + "content": [ + "test.SE.paired.trim.fastq.gz:md5,e68abbd3b88f7ec12940a4f5c2b8bfb9", + "test_trim.log:md5,e4c3f619e9b0e26847f8f3e3d9af319b", + "test.summary:md5,24c973237557a1439c775ca19a5deaa5", + [ + "test_out.log" + ] + ], + "timestamp": "2026-02-11T11:27:07.921874", + "meta": { + "nf-test": "0.9.3", + "nextflow": "25.10.3" + } + }, + "sarscov2 - fastq - leehom - single-end": { + "content": [ + "test.fq.gz:md5,304af6f5f6bb58c70abf7924eacfa175", + [ + "test.fail.fq.gz" + ], + [ + "test.log" + ] + ], + "timestamp": "2026-06-08T19:03:09.749238762", + "meta": { + "nf-test": "0.9.4", + "nextflow": "26.03.0" + } + }, + "sarscov2 - fastq - trimmomatic - paired-end - stub": { + "content": [ + { + "0": [ + [ + { + "id": "test", + "single_end": false + }, + [ + "test.paired.trim_1.fastq.gz:md5,68b329da9893e34099c7d8ad5cb9c940", + "test.paired.trim_2.fastq.gz:md5,68b329da9893e34099c7d8ad5cb9c940" + ] + ] + ], + "1": [ + [ + { + "id": "test", + "single_end": false + }, + "test.unpaired.trim_1.fastq.gz:md5,68b329da9893e34099c7d8ad5cb9c940" + ], + [ + { + "id": "test", + "single_end": false + }, + "test.unpaired.trim_2.fastq.gz:md5,68b329da9893e34099c7d8ad5cb9c940" + ] + ], + "2": [ + [ + { + "id": "test", + "single_end": false + }, + "test_trim.log:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "3": [ + [ + { + "id": "test", + "single_end": false + }, + "test.summary:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "4": [ + [ + { + "id": "test", + "single_end": false + }, + "test_out.log:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "discarded_reads": [ + [ + { + "id": "test", + "single_end": false + }, + "test.unpaired.trim_1.fastq.gz:md5,68b329da9893e34099c7d8ad5cb9c940" + ], + [ + { + "id": "test", + "single_end": false + }, + "test.unpaired.trim_2.fastq.gz:md5,68b329da9893e34099c7d8ad5cb9c940" + ] + ], + "logfile": [ + [ + { + "id": "test", + "single_end": false + }, + "test_trim.log:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "multiqc_files": [ + [ + { + "id": "test", + "single_end": false + }, + "test_out.log:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "processed_reads": [ + [ + { + "id": "test", + "single_end": false + }, + [ + "test.paired.trim_1.fastq.gz:md5,68b329da9893e34099c7d8ad5cb9c940", + "test.paired.trim_2.fastq.gz:md5,68b329da9893e34099c7d8ad5cb9c940" + ] + ] + ], + "report": [ + [ + { + "id": "test", + "single_end": false + }, + "test.summary:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ] + } + ], + "timestamp": "2026-06-08T19:03:48.202547582", + "meta": { + "nf-test": "0.9.4", + "nextflow": "26.03.0" + } + }, + "sarscov2 - fastq - cutadapt - paired-end": { + "content": [ + [ + "test_1.trim.fastq.gz:md5,682dab8e982563cffac2bd60bf7444f4", + "test_2.trim.fastq.gz:md5,1a1e8ab23af40e6474be0cdf82fab907" + ], + [ + "test.cutadapt.log" + ] + ], + "timestamp": "2025-12-19T11:19:27.959952445", + "meta": { + "nf-test": "0.9.3", + "nextflow": "25.10.2" + } + }, + "sarscov2 - fastq - bbduk - paired-end": { + "content": [ + [ + "test_bbduk_1.fastq.gz:md5,4161df271f9bfcd25d5845a1e220dbec", + "test_bbduk_2.fastq.gz:md5,2ebae722295ea66d84075a3b042e2b42" + ], + [ + "test_bbduk.bbduk.log" + ] + ], + "timestamp": "2026-03-12T09:50:13.626050809", + "meta": { + "nf-test": "0.9.3", + "nextflow": "25.10.2" + } + }, + "sarscov2 - fastq - trimgalore - single-end": { + "content": [ + "test_trimmed.fq.gz:md5,566d44cca0d22c522d6cf0e50c7165dc", + 51 + ], + "timestamp": "2026-05-05T09:08:25.274389209", + "meta": { + "nf-test": "0.9.5", + "nextflow": "26.04.0" + } + }, + "sarscov2 - fastq - adapterremoval - paired-end - merge": { + "content": [ + 336, + [ + "test.discarded.fastq.gz" + ], + [ + "test.settings" + ] + ], + "timestamp": "2026-03-11T16:49:49.466864", + "meta": { + "nf-test": "0.9.4", + "nextflow": "25.10.4" + } + }, + "sarscov2 - fastq - fastp - single-end": { + "content": [ + "test.fastp.fastq.gz:md5,67b2bbae47f073e05a97a9c2edce23c7", + "test.fail.fastq.gz:md5,3e4aaadb66a5b8fc9b881bf39c227abd", + 33, + 2395, + [ + "test.fastp.json" + ] + ], + "timestamp": "2026-06-08T19:03:25.728460818", + "meta": { + "nf-test": "0.9.4", + "nextflow": "26.03.0" + } + } +} \ No newline at end of file diff --git a/subworkflows/nf-core/fastq_removeadapters_merge/tests/nextflow.config b/subworkflows/nf-core/fastq_removeadapters_merge/tests/nextflow.config new file mode 100644 index 0000000..e1badf4 --- /dev/null +++ b/subworkflows/nf-core/fastq_removeadapters_merge/tests/nextflow.config @@ -0,0 +1,9 @@ +process { + withName: 'CUTADAPT' { + ext.args = '-q 25' + } + + withName: 'BBMAP_BBDUK' { + ext.args = 'trimq=10 qtrim=r' + } +} diff --git a/subworkflows/nf-core/fastq_removeadapters_merge/tests/nextflow_PE.config b/subworkflows/nf-core/fastq_removeadapters_merge/tests/nextflow_PE.config new file mode 100644 index 0000000..dc4eef5 --- /dev/null +++ b/subworkflows/nf-core/fastq_removeadapters_merge/tests/nextflow_PE.config @@ -0,0 +1,9 @@ +process { + withName: 'TRIMMOMATIC' { + ext.args = 'ILLUMINACLIP:TruSeq3-PE.fa:2:30:10 LEADING:3 TRAILING:3 SLIDINGWINDOW:4:15 MINLEN:36' + } + + withName: 'ADAPTERREMOVAL_PE' { + ext.args = params.adapterremoval_args + } +} diff --git a/subworkflows/nf-core/fastq_removeadapters_merge/tests/nextflow_SE.config b/subworkflows/nf-core/fastq_removeadapters_merge/tests/nextflow_SE.config new file mode 100644 index 0000000..82c1867 --- /dev/null +++ b/subworkflows/nf-core/fastq_removeadapters_merge/tests/nextflow_SE.config @@ -0,0 +1,9 @@ +process { + withName: 'TRIMMOMATIC' { + ext.args = 'ILLUMINACLIP:TruSeq3-SE:2:30:10 LEADING:3 TRAILING:3 SLIDINGWINDOW:4:15 MINLEN:36' + } + + withName: 'ADAPTERREMOVAL_SE' { + ext.args = params.adapterremoval_args + } +} diff --git a/subworkflows/nf-core/fastq_sanitise_seqkit/main.nf b/subworkflows/nf-core/fastq_sanitise_seqkit/main.nf new file mode 100644 index 0000000..f19b73f --- /dev/null +++ b/subworkflows/nf-core/fastq_sanitise_seqkit/main.nf @@ -0,0 +1,47 @@ +include { SEQKIT_SANA } from '../../../modules/nf-core/seqkit/sana' +include { SEQKIT_PAIR } from '../../../modules/nf-core/seqkit/pair' + +workflow FASTQ_SANITISE_SEQKIT { + take: + ch_reads // channel: [ val(meta), [ fastq ] ] + + main: + // Add strandness information to meta + ch_reads_with_strandness = ch_reads.flatMap { meta, reads -> + if (meta.single_end) { + if (reads instanceof List && reads.size() != 1) { + error("Error: Check your meta.single_end value. Single-end reads should contain one file only.") + } + return [[meta + [strandness: 'single'], reads]] + } + else { + if (!(reads instanceof List) || reads.size() != 2) { + error("Error: Check your meta.single_end value. Paired-end reads should contain two files; a forward and a reverse.") + } + return [[meta + [strandness: 'R1'], reads[0]], [meta + [strandness: 'R2'], reads[1]]] + } + } + + SEQKIT_SANA(ch_reads_with_strandness) + + ch_sanitized_reads = SEQKIT_SANA.out.reads + .map { meta, fastq -> + // Remove strandness field from meta to merge back together + def clean_meta = meta.findAll { key, _value -> key != 'strandness' } + return [clean_meta, fastq] + } + .groupTuple(by: 0) + .branch { meta, fastq -> + single_end: meta.single_end + return [meta, fastq] + paired_end: !meta.single_end + return [meta, fastq] + } + + SEQKIT_PAIR(ch_sanitized_reads.paired_end) + + ch_reads = ch_sanitized_reads.single_end.mix(SEQKIT_PAIR.out.reads, SEQKIT_PAIR.out.unpaired_reads) + + emit: + reads = ch_reads // channel: [ val(meta), [ fastq ] ] +} diff --git a/subworkflows/nf-core/fastq_sanitise_seqkit/meta.yml b/subworkflows/nf-core/fastq_sanitise_seqkit/meta.yml new file mode 100644 index 0000000..25d4aa2 --- /dev/null +++ b/subworkflows/nf-core/fastq_sanitise_seqkit/meta.yml @@ -0,0 +1,45 @@ +# yaml-language-server: $schema=https://raw.githubusercontent.com/nf-core/modules/master/subworkflows/yaml-schema.json +name: "fastq_sanitise_seqkit" +description: | + Filters and reports malformed FASTQ sequences with seqkit/sana, + and then pairs any paired-end files using seqkit/pair +keywords: + - fastq + - quality control + - filtering + - malformed + - pairing + - seqkit + - preprocessing +components: + - seqkit/sana + - seqkit/pair +input: + - ch_reads: + type: channel + description: | + Channel containing sample metadata and FASTQ files. + Structure: [ val(meta), [ fastq ] ] + Where meta is a map containing at least: + - id: sample identifier + - single_end: boolean indicating if data is single-end (true) or paired-end (false) + pattern: "*.{fastq,fastq.gz,fq,fq.gz}" +output: + - reads: + type: channel + description: | + Channel containing filtered (i.e., non-malformed) and paired FASTQ files. + For single-end data: returns filtered reads + For paired-end data: returns properly paired reads and any unpaired reads + Structure: [ val(meta), [ fastq ] ] + pattern: "*.{fastq,fastq.gz,fq,fq.gz}" + - versions: + type: file + description: | + File containing software versions + Structure: [ path(versions.yml) ] + pattern: "versions.yml" +authors: + - "@vagkaratzas" +maintainers: + - "@vagkaratzas" diff --git a/subworkflows/nf-core/fastq_sanitise_seqkit/nextflow.config b/subworkflows/nf-core/fastq_sanitise_seqkit/nextflow.config new file mode 100644 index 0000000..0b0a68d --- /dev/null +++ b/subworkflows/nf-core/fastq_sanitise_seqkit/nextflow.config @@ -0,0 +1,8 @@ +// IMPORTANT: This config file should be included to ensure that the subworkflow works properly. +process { + + withName: SEQKIT_SANA { + ext.prefix = { "${meta.id}_${meta.strandness}" } + } + +} diff --git a/subworkflows/nf-core/fastq_sanitise_seqkit/tests/main.nf.test b/subworkflows/nf-core/fastq_sanitise_seqkit/tests/main.nf.test new file mode 100644 index 0000000..85cd246 --- /dev/null +++ b/subworkflows/nf-core/fastq_sanitise_seqkit/tests/main.nf.test @@ -0,0 +1,119 @@ +nextflow_workflow { + + name "Test Subworkflow FASTQ_SANITISE_SEQKIT" + script "../main.nf" + workflow "FASTQ_SANITISE_SEQKIT" + config './nextflow.config' + + tag "subworkflows" + tag "subworkflows_nfcore" + tag "subworkflows/fastq_sanitise_seqkit" + tag "seqkit" + tag "seqkit/sana" + tag "seqkit/pair" + + + test("sarscov2 - fastq - single_end") { + + when { + workflow { + """ + input[0] = Channel.of([ + [ id:'test_single', single_end:true ], // meta map + file(params.modules_testdata_base_path + 'genomics/sarscov2/illumina/fastq/test_1.fastq.gz', checkIfExists: true) + ]) + """ + } + } + + then { + assertAll( + { assert workflow.success}, + { assert snapshot( + sanitizeOutput(workflow.out) + ).match() } + ) + } + } + + test("sarscov2 - fastq - paired_end") { + + when { + workflow { + """ + input[0] = Channel.of([ + [ id:'test_paired', 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 workflow.success}, + { assert snapshot( + sanitizeOutput(workflow.out) + ).match() } + ) + } + } + + test("sarscov2 - fastq - both with single broken") { + + when { + workflow { + """ + input[0] = Channel.of([ + [ id:'test_both', single_end:true ], // meta map + file(params.modules_testdata_base_path + 'genomics/sarscov2/illumina/fastq/test_1_broken.fastq.gz', checkIfExists: true) + ], + [ + [ id:'test_both', 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 workflow.success}, + { assert snapshot( + sanitizeOutput(workflow.out) + ).match() } + ) + } + } + + test("sarscov2 - fastq - stub") { + + options "-stub" + + when { + workflow { + """ + input[0] = Channel.of([ + [ id: 'test_stub', single_end:true ], + file(params.modules_testdata_base_path + 'genomics/sarscov2/illumina/fastq/test_1_broken.fastq.gz', checkIfExists: true) + ]) + """ + } + } + + then { + assertAll( + { assert workflow.success }, + { assert snapshot( + sanitizeOutput(workflow.out) + ).match() } + ) + } + } +} diff --git a/subworkflows/nf-core/fastq_sanitise_seqkit/tests/main.nf.test.snap b/subworkflows/nf-core/fastq_sanitise_seqkit/tests/main.nf.test.snap new file mode 100644 index 0000000..2f664b0 --- /dev/null +++ b/subworkflows/nf-core/fastq_sanitise_seqkit/tests/main.nf.test.snap @@ -0,0 +1,101 @@ +{ + "sarscov2 - fastq - stub": { + "content": [ + { + "reads": [ + [ + { + "id": "test_stub", + "single_end": true + }, + [ + "test_stub_single.fastq.gz:md5,68b329da9893e34099c7d8ad5cb9c940" + ] + ] + ] + } + ], + "timestamp": "2026-06-15T10:58:37.652046254", + "meta": { + "nf-test": "0.9.5", + "nextflow": "26.04.3" + } + }, + "sarscov2 - fastq - single_end": { + "content": [ + { + "reads": [ + [ + { + "id": "test_single", + "single_end": true + }, + [ + "test_single_single.fastq.gz:md5,4161df271f9bfcd25d5845a1e220dbec" + ] + ] + ] + } + ], + "timestamp": "2026-06-12T17:37:45.293059368", + "meta": { + "nf-test": "0.9.5", + "nextflow": "26.04.3" + } + }, + "sarscov2 - fastq - paired_end": { + "content": [ + { + "reads": [ + [ + { + "id": "test_paired", + "single_end": false + }, + [ + "test_paired_R1.paired.fastq.gz:md5,4161df271f9bfcd25d5845a1e220dbec", + "test_paired_R2.paired.fastq.gz:md5,2ebae722295ea66d84075a3b042e2b42" + ] + ] + ] + } + ], + "timestamp": "2026-06-12T17:37:50.905358878", + "meta": { + "nf-test": "0.9.5", + "nextflow": "26.04.3" + } + }, + "sarscov2 - fastq - both with single broken": { + "content": [ + { + "reads": [ + [ + { + "id": "test_both", + "single_end": false + }, + [ + "test_both_R1.paired.fastq.gz:md5,4161df271f9bfcd25d5845a1e220dbec", + "test_both_R2.paired.fastq.gz:md5,2ebae722295ea66d84075a3b042e2b42" + ] + ], + [ + { + "id": "test_both", + "single_end": true + }, + [ + "test_both_single.fastq.gz:md5,03b43487ca6aeecc5eb8b55bbc1fe2f3" + ] + ] + ] + } + ], + "timestamp": "2026-06-12T17:37:56.877663489", + "meta": { + "nf-test": "0.9.5", + "nextflow": "26.04.3" + } + } +} \ No newline at end of file diff --git a/subworkflows/nf-core/fastq_sanitise_seqkit/tests/nextflow.config b/subworkflows/nf-core/fastq_sanitise_seqkit/tests/nextflow.config new file mode 100644 index 0000000..f8debb9 --- /dev/null +++ b/subworkflows/nf-core/fastq_sanitise_seqkit/tests/nextflow.config @@ -0,0 +1,11 @@ +process { + + withName: SEQKIT_SANA { + ext.prefix = { "${meta.id}_${meta.strandness}" } + } + + withName: SEQKIT_PAIR { + ext.args = '-u' + } + +} diff --git a/subworkflows/nf-core/fastq_shortreads_preprocess_qc/main.nf b/subworkflows/nf-core/fastq_shortreads_preprocess_qc/main.nf new file mode 100644 index 0000000..3003581 --- /dev/null +++ b/subworkflows/nf-core/fastq_shortreads_preprocess_qc/main.nf @@ -0,0 +1,222 @@ +// statistics +include { FASTQ_QC_STATS as PRE_STATS } from '../fastq_qc_stats' +include { FASTQ_QC_STATS as POST_STATS } from '../fastq_qc_stats' +// preprocessing +include { FASTQ_PREPROCESS_SEQKIT } from '../fastq_preprocess_seqkit' +// barcoding +include { UMITOOLS_EXTRACT } from '../../../modules/nf-core/umitools/extract' +// adapter removal and merging +include { FASTQ_REMOVEADAPTERS_MERGE } from '../fastq_removeadapters_merge' +// complexity filtering +include { FASTQ_COMPLEXITY_FILTER } from '../fastq_complexity_filter' +// deduplication +include { BBMAP_CLUMPIFY } from '../../../modules/nf-core/bbmap/clumpify' +// host decontamination +include { FASTQ_DECONTAMINATE_DEACON_HOSTILE } from '../fastq_decontaminate_deacon_hostile' +// final concatenation +include { CAT_FASTQ } from '../../../modules/nf-core/cat/fastq' + +workflow FASTQ_SHORTREADS_PREPROCESS_QC { + take: + ch_reads // channel: [ val(meta), [ fastq ] ] + skip_fastqc // boolean + skip_seqfu_check // boolean + skip_seqfu_stats // boolean + skip_seqkit_stats // boolean + skip_seqtk_comp // boolean + skip_seqkit_sana_pair // boolean + skip_seqkit_seq // boolean + skip_seqkit_replace // boolean + skip_seqkit_rmdup // boolean + skip_umitools_extract // boolean + val_umi_discard_read // integer: 0, 1 or 2 + skip_adapterremoval // boolean + val_adapter_tool // string: [mandatory] tool_name // choose from: ["trimmomatic", "cutadapt", "trimgalore", "bbduk", "leehom", "fastp", "adapterremoval"] + ch_custom_adapters_file // channel: [optional] [ {fasta,txt} ] // fasta, for bbduk or fastp, or txt, for adapterremoval + val_save_merged // boolean: [mandatory] if true, will return the merged reads instead, for fastp and adapterremoval + val_fastp_discard_trimmed_pass // boolean: [mandatory] // only for fastp + val_fastp_save_trimmed_fail // boolean: [mandatory] // only for fastp + skip_complexity_filtering // boolean + val_complexity_filter_tool // string: [mandatory] tool_name // choose from: ["prinseqplusplus", "bbduk", "fastp"] + skip_deduplication // boolean + skip_decontamination // boolean + ch_decontamination_fasta // channel: [ val(meta), [ fasta ] ] (optional) + ch_decontamination_reference // channel: [ val(reference_name), path(reference_dir) ] (optional) + val_decontamination_index_name // val (optional) + val_decontamination_tool // string (enum): 'hostile' or 'deacon' + skip_final_concatenation // boolean + + main: + + ch_multiqc_files = channel.empty() + ch_umi_log = channel.empty() + ch_adapterremoval_discarded_reads = channel.empty() + ch_adapterremoval_logfile = channel.empty() + ch_adapterremoval_report = channel.empty() + ch_complexity_filter_log = channel.empty() + ch_complexity_filter_report = channel.empty() + ch_clumpify_log = channel.empty() + ch_hostile_reference = channel.empty() + ch_hostile_json = channel.empty() + ch_deacon_index = channel.empty() + ch_deacon_summary = channel.empty() + + // pre-statistics + PRE_STATS( + ch_reads, + skip_fastqc, + skip_seqfu_check, + skip_seqfu_stats, + skip_seqkit_stats, + skip_seqtk_comp, + ) + ch_pre_stats_fastqc_html = PRE_STATS.out.fastqc_html + ch_pre_stats_fastqc_zip = PRE_STATS.out.fastqc_zip + ch_pre_stats_seqfu_check = PRE_STATS.out.seqfu_check + ch_pre_stats_seqfu_stats = PRE_STATS.out.seqfu_stats + ch_pre_stats_seqkit_stats = PRE_STATS.out.seqkit_stats + ch_pre_stats_seqtk_stats = PRE_STATS.out.seqtk_stats + ch_multiqc_files = ch_multiqc_files.mix(PRE_STATS.out.seqfu_multiqc) + + // preprocessing + FASTQ_PREPROCESS_SEQKIT( + ch_reads, + skip_seqkit_sana_pair, + skip_seqkit_seq, + skip_seqkit_replace, + skip_seqkit_rmdup, + ) + ch_reads = FASTQ_PREPROCESS_SEQKIT.out.reads + + // barcoding + if (!skip_umitools_extract) { + UMITOOLS_EXTRACT(ch_reads) + ch_umi_reads = UMITOOLS_EXTRACT.out.reads + ch_umi_log = UMITOOLS_EXTRACT.out.log + + // Discard R1 / R2 if required + if (val_umi_discard_read in [1, 2]) { + ch_umi_reads = UMITOOLS_EXTRACT.out.reads.map { meta, reads -> + meta.single_end ? [meta, reads] : [meta + ['single_end': true], reads[val_umi_discard_read % 2]] + } + } + + ch_reads = ch_umi_reads + } + + // adapter removal and merging + if (!skip_adapterremoval) { + FASTQ_REMOVEADAPTERS_MERGE( + ch_reads, + val_adapter_tool, + ch_custom_adapters_file, + val_save_merged, + val_fastp_discard_trimmed_pass, + val_fastp_save_trimmed_fail, + ) + ch_adapterremoval_discarded_reads = FASTQ_REMOVEADAPTERS_MERGE.out.discarded_reads + ch_adapterremoval_logfile = FASTQ_REMOVEADAPTERS_MERGE.out.logfile + ch_adapterremoval_report = FASTQ_REMOVEADAPTERS_MERGE.out.report + ch_reads = FASTQ_REMOVEADAPTERS_MERGE.out.processed_reads + ch_multiqc_files = ch_multiqc_files.mix(FASTQ_REMOVEADAPTERS_MERGE.out.multiqc_files) + } + + // complexity filtering + if (!skip_complexity_filtering) { + FASTQ_COMPLEXITY_FILTER(ch_reads, val_complexity_filter_tool) + ch_reads = FASTQ_COMPLEXITY_FILTER.out.filtered_reads + ch_complexity_filter_log = FASTQ_COMPLEXITY_FILTER.out.logfile + ch_complexity_filter_report = FASTQ_COMPLEXITY_FILTER.out.report + ch_multiqc_files = ch_multiqc_files.mix(FASTQ_COMPLEXITY_FILTER.out.multiqc_files) + } + + // deduplication + if (!skip_deduplication) { + BBMAP_CLUMPIFY(ch_reads) + ch_reads = BBMAP_CLUMPIFY.out.reads + ch_clumpify_log = BBMAP_CLUMPIFY.out.log + } + + // host decontamination + if (!skip_decontamination) { + FASTQ_DECONTAMINATE_DEACON_HOSTILE( + ch_reads, + ch_decontamination_fasta, + ch_decontamination_reference, + val_decontamination_index_name, + val_decontamination_tool, + ) + ch_reads = FASTQ_DECONTAMINATE_DEACON_HOSTILE.out.fastq_filtered + ch_hostile_reference = FASTQ_DECONTAMINATE_DEACON_HOSTILE.out.reference + ch_hostile_json = FASTQ_DECONTAMINATE_DEACON_HOSTILE.out.json + ch_deacon_index = FASTQ_DECONTAMINATE_DEACON_HOSTILE.out.index + ch_deacon_summary = FASTQ_DECONTAMINATE_DEACON_HOSTILE.out.summary + } + + + // final concatenation + if (!skip_final_concatenation) { + ch_reads_for_cat_branch = ch_reads + .groupTuple() + .map { meta, reads -> + [meta, reads.flatten()] + } + .branch { meta, reads -> + cat: (meta.single_end && reads.size() > 1) || (!meta.single_end && reads.size() > 2) + skip: true + } + + CAT_FASTQ(ch_reads_for_cat_branch.cat) + + ch_reads = CAT_FASTQ.out.reads + .mix(ch_reads_for_cat_branch.skip) + .map { meta, reads -> + def new_reads = meta.single_end ? reads[0] : reads.flatten() + [meta, new_reads] + } + } + + // post-statistics + POST_STATS( + ch_reads, + skip_fastqc, + skip_seqfu_check, + skip_seqfu_stats, + skip_seqkit_stats, + skip_seqtk_comp, + ) + ch_post_stats_fastqc_html = POST_STATS.out.fastqc_html + ch_post_stats_fastqc_zip = POST_STATS.out.fastqc_zip + ch_post_stats_seqfu_check = POST_STATS.out.seqfu_check + ch_post_stats_seqfu_stats = POST_STATS.out.seqfu_stats + ch_post_stats_seqkit_stats = POST_STATS.out.seqkit_stats + ch_post_stats_seqtk_stats = POST_STATS.out.seqtk_stats + ch_multiqc_files = ch_multiqc_files.mix(POST_STATS.out.seqfu_multiqc) + + emit: + reads = ch_reads // channel: [ val(meta), [ fastq ] ] + pre_stats_fastqc_html = ch_pre_stats_fastqc_html + pre_stats_fastqc_zip = ch_pre_stats_fastqc_zip + pre_stats_seqfu_check = ch_pre_stats_seqfu_check + pre_stats_seqfu_stats = ch_pre_stats_seqfu_stats + pre_stats_seqkit_stats = ch_pre_stats_seqkit_stats + pre_stats_seqtk_stats = ch_pre_stats_seqtk_stats + post_stats_fastqc_html = ch_post_stats_fastqc_html + post_stats_fastqc_zip = ch_post_stats_fastqc_zip + post_stats_seqfu_check = ch_post_stats_seqfu_check + post_stats_seqfu_stats = ch_post_stats_seqfu_stats + post_stats_seqkit_stats = ch_post_stats_seqkit_stats + post_stats_seqtk_stats = ch_post_stats_seqtk_stats + umi_log = ch_umi_log + adapterremoval_discarded_reads = ch_adapterremoval_discarded_reads + adapterremoval_logfile = ch_adapterremoval_logfile + adapterremoval_report = ch_adapterremoval_report + complexity_filter_log = ch_complexity_filter_log + complexity_filter_report = ch_complexity_filter_report + clumpify_log = ch_clumpify_log + hostile_reference = ch_hostile_reference + hostile_json = ch_hostile_json + deacon_index = ch_deacon_index + deacon_summary = ch_deacon_summary + multiqc_files = ch_multiqc_files +} diff --git a/subworkflows/nf-core/fastq_shortreads_preprocess_qc/meta.yml b/subworkflows/nf-core/fastq_shortreads_preprocess_qc/meta.yml new file mode 100644 index 0000000..c4f70ee --- /dev/null +++ b/subworkflows/nf-core/fastq_shortreads_preprocess_qc/meta.yml @@ -0,0 +1,332 @@ +# yaml-language-server: $schema=https://raw.githubusercontent.com/nf-core/modules/master/subworkflows/yaml-schema.json +name: "fastq_shortreads_preprocess_qc" +description: | + Quality check and preprocessing subworkflow of Illumina short reads + that can do: quality check of input reads and generate statistics, + preprocess and validate reads, barcode removal, remove adapters and merge reads, + filter by sequence complexity, deduplicate reads, remove host contamination, + concatenate reads and generate statistics for post-processing reads. + WARNING: requires at least the process configurations from the nextflow.config + to be added to the modules.config in the pipeline in order to work as intended. +keywords: + - fastq + - illumina + - short + - reads + - qc + - stats + - preprocessing + - barcoding + - adapters + - merge + - complexity + - deduplication + - host + - decontamination +components: + - fastq_qc_stats + - fastqc + - seqfu/check + - seqfu/stats + - seqkit/stats + - seqtk/comp + - fastq_preprocess_seqkit + - fastq_sanitise_seqkit + - seqkit/sana + - seqkit/pair + - seqkit/seq + - seqkit/replace + - seqkit/rmdup + - umitools/extract + - fastq_removeadapters_merge + - trimmomatic + - cutadapt + - trimgalore + - bbmap/bbduk + - leehom + - fastp + - adapterremoval + - cat/fastq + - fastq_complexity_filter + - prinseqplusplus + - bbmap/clumpify + - fastq_decontaminate_deacon_hostile + - fastq_index_filter_deacon + - fastq_fetch_clean_hostile + - hostile/fetch + - hostile/clean + - bowtie2/build + - deacon/filter + - deacon/index + +input: + - ch_reads: + type: file + description: | + List of FastQ files of size 1 and 2 for single-end and paired-end data, respectively. + Structure: [ val(meta), [ path(reads) ] ] + pattern: "*.fastq.gz" + - skip_fastqc: + type: boolean + description: | + Skip FastQC quality control step + - skip_seqfu_check: + type: boolean + description: | + Skip SeqFu check step + - skip_seqfu_stats: + type: boolean + description: | + Skip SeqFu statistics step + - skip_seqkit_stats: + type: boolean + description: | + Skip SeqKit statistics step + - skip_seqtk_comp: + type: boolean + description: | + Skip SeqTk composition analysis step + - skip_seqkit_sana_pair: + type: boolean + description: | + Skip SeqKit sanitize and pair step + - skip_seqkit_seq: + type: boolean + description: | + Skip SeqKit sequence processing step + - skip_seqkit_replace: + type: boolean + description: | + Skip SeqKit replace step + - skip_seqkit_rmdup: + type: boolean + description: | + Skip SeqKit remove duplicates step + - skip_umitools_extract: + type: boolean + description: | + Skip UMI-tools extract barcoding step + - val_umi_discard_read: + type: integer + description: | + Discard R1 or R2 after UMI extraction (0 = keep both, 1 = discard R1, 2 = discard R2) + - skip_adapterremoval: + type: boolean + description: | + Skip the adapter removal and merge subworkflow completely + - val_adapter_tool: + type: string + description: | + Choose one of the available adapter removal and/or merging tools + enum: ["trimmomatic", "cutadapt", "trimgalore", "bbduk", "leehom", "fastp", "adapterremoval"] + - ch_custom_adapters_file: + type: file + description: | + Optional reference files, containing adapter and/or contaminant sequences for removal. + In fasta format for bbmap/bbduk and fastp, or in text format for AdapterRemoval (one adapter per line). + - val_save_merged: + type: boolean + description: | + Specify true to output merged reads instead + Used by fastp and adapterremoval + - val_fastp_discard_trimmed_pass: + type: boolean + description: | + Used only by fastp. + Specify true to not write any reads that pass trimming thresholds from the fastp process. + This can be used to use fastp for the output report only. + - val_fastp_save_trimmed_fail: + type: boolean + description: | + Used only by fastp. + Specify true to save files that failed to pass fastp trimming thresholds + - skip_complexity_filtering: + type: boolean + description: | + Skip PRINSEQ++ complexity filtering step + - val_complexity_filter_tool: + type: string + description: | + Complexity filtering tool to use. + Must be one of: 'prinseqplusplus', 'bbduk', or 'fastp'. + - skip_deduplication: + type: boolean + description: | + Skip BBMap Clumpify deduplication step + - skip_decontamination: + type: boolean + description: | + Skip host decontamination step + - ch_decontamination_fasta: + type: file + description: | + Reference genome FASTA file for decontamination (optional) + Structure: [ val(meta), [ path(fasta) ] ] + pattern: "*.{fasta,fa,fna}" + - ch_decontamination_reference: + type: directory + description: | + Pre-built reference index directory for decontamination (optional) + Structure: [ val(reference_name), path(reference_dir) ] + - val_decontamination_index_name: + type: string + description: | + Name for the decontamination index (optional) + - val_decontamination_tool: + type: string + description: | + Decontamination tool to use ('hostile' or 'deacon') + - skip_final_concatenation: + type: boolean + description: | + Skip final FASTQ concatenation step + +output: + - reads: + type: file + description: | + Channel containing processed short reads + Structure: [ val(meta), path(reads) ] + pattern: "*.fastq.gz" + - pre_stats_fastqc_html: + type: file + description: | + FastQC HTML reports for pre-processing reads + Structure: [ val(meta), path(html) ] + pattern: "*.html" + - pre_stats_fastqc_zip: + type: file + description: | + FastQC ZIP archives for pre-processing reads + Structure: [ val(meta), path(zip) ] + pattern: "*.zip" + - pre_stats_seqfu_check: + type: file + description: | + SeqFu check results for pre-processing reads + Structure: [ val(meta), path(check) ] + - pre_stats_seqfu_stats: + type: file + description: | + SeqFu statistics for pre-processing reads + Structure: [ val(meta), path(stats) ] + - pre_stats_seqfu_multiqc: + type: file + description: | + SeqFu MultiQC-compatible stats for pre-processing reads + Structure: [ val(meta), path(multiqc) ] + - pre_stats_seqkit_stats: + type: file + description: | + SeqKit statistics for pre-processing reads + Structure: [ val(meta), path(stats) ] + - pre_stats_seqtk_stats: + type: file + description: | + SeqTk composition statistics for pre-processing reads + Structure: [ val(meta), path(stats) ] + - post_stats_fastqc_html: + type: file + description: | + FastQC HTML reports for post-processing reads + Structure: [ val(meta), path(html) ] + pattern: "*.html" + - post_stats_fastqc_zip: + type: file + description: | + FastQC ZIP archives for post-processing reads + Structure: [ val(meta), path(zip) ] + pattern: "*.zip" + - post_stats_seqfu_check: + type: file + description: | + SeqFu check results for post-processing reads + Structure: [ val(meta), path(check) ] + - post_stats_seqfu_stats: + type: file + description: | + SeqFu statistics for post-processing reads + Structure: [ val(meta), path(stats) ] + - post_stats_seqfu_multiqc: + type: file + description: | + SeqFu MultiQC-compatible stats for post-processing reads + Structure: [ val(meta), path(multiqc) ] + - post_stats_seqkit_stats: + type: file + description: | + SeqKit statistics for post-processing reads + Structure: [ val(meta), path(stats) ] + - post_stats_seqtk_stats: + type: file + description: | + SeqTk composition statistics for post-processing reads + Structure: [ val(meta), path(stats) ] + - umi_log: + type: file + description: | + UMI-tools extract log file + Structure: [ val(meta), path(log) ] + - adapterremoval_discarded_reads: + type: file + description: | + Reads discarded during adapter removal or merging + Structure: [ val(meta), path(fastq) ] + pattern: "*.fastq.gz" + - adapterremoval_logfile: + type: file + description: | + Adapter removal execution log file + (trimmomatic {log}, trimgalore {txt}, fastp {log}) + Structure: [ val(meta), path({log,txt}) ] + - adapterremoval_report: + type: file + description: | + Adapter removal report + (trimmomatic {summary}, trimgalore {html,zip}, fastp {html}) + Structure: [ val(meta), path({summary,html,zip}) ] + - complexity_filter_log: + type: file + description: | + Log file from complexity filtering + Structure: [ val(meta), path(log) ] + - complexity_filter_report: + type: file + description: | + Report generated by complexity filtering + HTML report generated by fastp. Empty for other tools. + Structure: [ val(meta), path(html) ] + - clumpify_log: + type: file + description: | + BBMap Clumpify log file + Structure: [ val(meta), path(log) ] + - hostile_reference: + type: file + description: | + Hostile reference files used for decontamination + Structure: [ val(reference_name), path(reference_dir) ] + - hostile_json: + type: file + description: | + Hostile JSON report + Structure: [ val(meta), path(json) ] + - deacon_index: + type: directory + description: | + Deacon index directory + Structure: [ val(meta), path(index) ] + - deacon_summary: + type: file + description: | + Deacon decontamination summary file + Structure: [ val(meta), path(log) ] + - multiqc_files: + type: file + description: | + MultiQC compatible files for aggregated reporting + Structure: [ path(files) ] +authors: + - "@vagkaratzas" +maintainers: + - "@vagkaratzas" diff --git a/subworkflows/nf-core/fastq_shortreads_preprocess_qc/nextflow.config b/subworkflows/nf-core/fastq_shortreads_preprocess_qc/nextflow.config new file mode 100644 index 0000000..b81471f --- /dev/null +++ b/subworkflows/nf-core/fastq_shortreads_preprocess_qc/nextflow.config @@ -0,0 +1,26 @@ +// IMPORTANT: Add this configuration to your modules.config + +process { + withName: ".*:FASTQ_COMPLEXITY_FILTER:BBMAP_BBDUK" { + ext.args = [ + 'entropy=0.7', // needed to turn on complexity filtering + 'minlength=0' // needed, to not discard reads shorter than this + ].join(' ') + ext.prefix = { "${meta.id}.trim" } + } + + // need FASTP to only do complexity filtering + withName: ".*:FASTQ_COMPLEXITY_FILTER:FASTP" { + ext.args = [ + '--low_complexity_filter', + '--disable_adapter_trimming', + '--disable_trim_poly_g', + '--disable_quality_filtering', + '--disable_length_filtering' + ].join(' ') + } + + withName: "BBMAP_CLUMPIFY" { + ext.args = 'dedupe=t' + } +} diff --git a/subworkflows/nf-core/fastq_shortreads_preprocess_qc/tests/main.nf.test b/subworkflows/nf-core/fastq_shortreads_preprocess_qc/tests/main.nf.test new file mode 100644 index 0000000..4f0752b --- /dev/null +++ b/subworkflows/nf-core/fastq_shortreads_preprocess_qc/tests/main.nf.test @@ -0,0 +1,411 @@ +nextflow_workflow { + + name "Test Subworkflow FASTQ_SHORTREADS_PREPROCESS_QC" + script "../main.nf" + workflow "FASTQ_SHORTREADS_PREPROCESS_QC" + config './nextflow.config' + + tag "subworkflows" + tag "subworkflows_nfcore" + tag "subworkflows/fastq_shortreads_preprocess_qc" + tag "subworkflows/fastq_qc_stats" + tag "fastqc" + tag "seqfu" + tag "seqfu/check" + tag "seqfu/stats" + tag "seqkit" + tag "seqkit/stats" + tag "seqtk" + tag "seqtk/comp" + tag "subworkflows/fastq_preprocess_seqkit" + tag "subworkflows/fastq_sanitise_seqkit" + tag "seqkit" + tag "seqkit/sana" + tag "seqkit/pair" + tag "seqkit/seq" + tag "seqkit/replace" + tag "seqkit/rmdup" + tag "umitools" + tag "umitools/extract" + tag "subworkflows/fastq_removeadapters_merge" + tag "trimmomatic" + tag "cutadapt" + tag "trimgalore" + tag "bbmap" + tag "bbmap/bbduk" + tag "leehom" + tag "fastp" + tag "adapterremoval" + tag "cat" + tag "cat/fastq" + tag "subworkflows/fastq_complexity_filter" + tag "prinseqplusplus" + tag "bbmap/clumpify" + tag "subworkflows/fastq_decontaminate_deacon_hostile" + tag "subworkflows/fastq_index_filter_deacon" + tag "subworkflows/fastq_fetch_clean_hostile" + tag "hostile" + tag "hostile/fetch" + tag "hostile/clean" + tag "bowtie2/build" + tag "deacon" + tag "deacon/filter" + tag "deacon/index" + + test("sarscov2 - fastq - seqfu - seqkit - deacon - single_end") { + + when { + params { + save_merged = false + adapterremoval_args = save_merged ? "--collapse" : "" + } + workflow { + """ + 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) + ]) + input[1] = true // skip_fastqc + input[2] = false // skip_seqfu_check + input[3] = false // skip_seqfu_stats + input[4] = true // skip_seqkit_stats + input[5] = true // skip_seqtk_comp + input[6] = false // skip_seqkit_sana_pair + input[7] = false // skip_seqkit_seq + input[8] = false // skip_seqkit_replace + input[9] = false // skip_seqkit_rmdup + input[10] = true // skip_umitools_extract + input[11] = 0 // val_umi_discard_read + input[12] = true // skip_adapterremoval + input[13] = "" // val_adapter_tool + input[14] = [] // ch_custom_adapters_file + input[15] = false // val_save_merged + input[16] = false // val_fastp_discard_trimmed_pass + input[17] = false // val_fastp_save_trimmed_fail + input[18] = true // skip_complexity_filtering + input[19] = "" // val_complexity_filter_tool + input[20] = true // skip_deduplication + input[21] = false // skip_decontamination + input[22] = channel.of( + [ + [ id:'test', single_end:true ], + file(params.modules_testdata_base_path + 'genomics/sarscov2/genome/genome.fasta', checkIfExists: true) + ] + ) // ch_decontamination_fasta + input[23] = [] // ch_decontamination_reference + input[24] = [] // val_decontamination_index_name + input[25] = 'deacon' // val_decontamination_tool + input[26] = true // skip_final_concatenation + """ + } + } + then { + assert workflow.success + assertAll( + { assert snapshot( + workflow.out.reads[0][1], + workflow.out.pre_stats_seqfu_check, + workflow.out.pre_stats_seqfu_stats, + workflow.out.post_stats_seqfu_check, + workflow.out.post_stats_seqfu_stats, + workflow.out.deacon_index, + file(workflow.out.deacon_summary[0][1]).name, + workflow.out.multiqc_files + ).match() } + ) + } + } + + test("sarscov2 - fastq - umitools - prinseq - clumpify - cat - single_end") { + + when { + params { + save_merged = false + adapterremoval_args = save_merged ? "--collapse" : "" + } + workflow { + """ + 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) + ]) + input[1] = true // skip_fastqc + input[2] = true // skip_seqfu_check + input[3] = true // skip_seqfu_stats + input[4] = true // skip_seqkit_stats + input[5] = true // skip_seqtk_comp + input[6] = true // skip_seqkit_sana_pair + input[7] = true // skip_seqkit_seq + input[8] = true // skip_seqkit_replace + input[9] = true // skip_seqkit_rmdup + input[10] = false // skip_umitools_extract + input[11] = 0 // val_umi_discard_read + input[12] = true // skip_adapterremoval + input[13] = "" // val_adapter_tool + input[14] = [] // ch_custom_adapters_file + input[15] = false // val_save_merged + input[16] = false // val_fastp_discard_trimmed_pass + input[17] = false // val_fastp_save_trimmed_fail + input[18] = false // skip_complexity_filtering + input[19] = 'prinseqplusplus' // val_complexity_filter_tool + input[20] = false // skip_deduplication + input[21] = true // skip_decontamination + input[22] = channel.of( + [ + [ id:'test', single_end:true ], + file(params.modules_testdata_base_path + 'genomics/sarscov2/genome/genome.fasta', checkIfExists: true) + ] + ) // ch_decontamination_fasta + input[23] = [] // ch_decontamination_reference + input[24] = [] // val_decontamination_index_name + input[25] = 'deacon' // val_decontamination_tool + input[26] = false // skip_final_concatenation + """ + } + } + then { + assert workflow.success + assertAll( + { assert snapshot( + path(workflow.out.reads[0][1]).linesGzip.size(), + path(workflow.out.umi_log[0][1]).readLines().size(), + path(workflow.out.clumpify_log[0][1]).readLines().size() + ).match() } + ) + } + } + + test("sarscov2 - fastq - fastqc - seqkit - cutadapt - clumpify - cat - paired_end") { + tag "test" + + when { + params { + save_merged = false + adapterremoval_args = save_merged ? "--collapse" : "" + } + workflow { + """ + input[0] = channel.of([ + [ id:'test', single_end:false ], + [ + 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] = false // skip_fastqc + input[2] = true // skip_seqfu_check + input[3] = true // skip_seqfu_stats + input[4] = true // skip_seqkit_stats + input[5] = true // skip_seqtk_comp + input[6] = false // skip_seqkit_sana_pair + input[7] = false // skip_seqkit_seq + input[8] = false // skip_seqkit_replace + input[9] = false // skip_seqkit_rmdup + input[10] = true // skip_umitools_extract + input[11] = 0 // val_umi_discard_read + input[12] = false // skip_adapterremoval + input[13] = "cutadapt" // val_adapter_tool + input[14] = [] // ch_custom_adapters_file + input[15] = false // val_save_merged + input[16] = false // val_fastp_discard_trimmed_pass + input[17] = false // val_fastp_save_trimmed_fail + input[18] = true // skip_complexity_filtering + input[19] = "" // val_complexity_filter_tool + input[20] = false // skip_deduplication + input[21] = true // skip_decontamination + input[22] = [] // ch_decontamination_fasta + input[23] = [] // ch_decontamination_reference + input[24] = [] // val_decontamination_index_name + input[25] = '' // val_decontamination_tool + input[26] = false // skip_final_concatenation + """ + } + } + then { + assert workflow.success + assertAll( + { assert snapshot( + path(workflow.out.reads[0][1][0]).linesGzip.size(), + path(workflow.out.reads[0][1][1]).linesGzip.size(), + file(workflow.out.pre_stats_fastqc_html[0][1][0]).name, + file(workflow.out.pre_stats_fastqc_html[0][1][1]).name, + file(workflow.out.post_stats_fastqc_html[0][1][0]).name, + file(workflow.out.post_stats_fastqc_html[0][1][1]).name, + file(workflow.out.pre_stats_fastqc_zip[0][1][0]).name, + file(workflow.out.pre_stats_fastqc_zip[0][1][1]).name, + file(workflow.out.post_stats_fastqc_zip[0][1][0]).name, + file(workflow.out.post_stats_fastqc_zip[0][1][1]).name, + path(workflow.out.clumpify_log[0][1]).readLines().size() + ).match() } + ) + } + } + + test("sarscov2 - fastq - adapterremoval - merge - cat more files - paired_end") { + + when { + params { + save_merged = true + adapterremoval_args = save_merged ? "--collapse" : "" + } + workflow { + """ + input[0] = channel.of( + [ + [ id:'test', single_end:false ], + [ + 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) + ] + ], + [ + [ id:'test2', single_end:false ], + [ + 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) + ] + ] + ) + input[1] = true // skip_fastqc + input[2] = true // skip_seqfu_check + input[3] = true // skip_seqfu_stats + input[4] = true // skip_seqkit_stats + input[5] = true // skip_seqtk_comp + input[6] = true // skip_seqkit_sana_pair + input[7] = true // skip_seqkit_seq + input[8] = true // skip_seqkit_replace + input[9] = true // skip_seqkit_rmdup + input[10] = true // skip_umitools_extract + input[11] = 0 // val_umi_discard_read + input[12] = false // skip_adapterremoval + input[13] = "adapterremoval" // val_adapter_tool + input[14] = [] // ch_custom_adapters_file + input[15] = params.save_merged // val_save_merged + input[16] = false // val_fastp_discard_trimmed_pass + input[17] = false // val_fastp_save_trimmed_fail + input[18] = true // skip_complexity_filtering + input[19] = "" // val_complexity_filter_tool + input[20] = true // skip_deduplication + input[21] = true // skip_decontamination + input[22] = [] // ch_decontamination_fasta + input[23] = [] // ch_decontamination_reference + input[24] = [] // val_decontamination_index_name + input[25] = '' // val_decontamination_tool + input[26] = false // skip_final_concatenation + """ + } + } + then { + assert workflow.success + assertAll( + { assert snapshot( + workflow.out.reads[0][1], + workflow.out.reads[1][1], + workflow.out.adapterremoval_discarded_reads.collect { file(it[1]).name } + ).match() } + ) + } + } + + test("sarscov2 - fastq - skip all - single_end") { + + when { + params { + save_merged = false + adapterremoval_args = save_merged ? "--collapse" : "" + } + workflow { + """ + 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) + ]) + input[1] = true // skip_fastqc + input[2] = true // skip_seqfu_check + input[3] = true // skip_seqfu_stats + input[4] = true // skip_seqkit_stats + input[5] = true // skip_seqtk_comp + input[6] = true // skip_seqkit_sana_pair + input[7] = true // skip_seqkit_seq + input[8] = true // skip_seqkit_replace + input[9] = true // skip_seqkit_rmdup + input[10] = true // skip_umitools_extract + input[11] = 0 // val_umi_discard_read + input[12] = true // skip_adapterremoval + input[13] = "" // val_adapter_tool + input[14] = [] // ch_custom_adapters_file + input[15] = false // val_save_merged + input[16] = false // val_fastp_discard_trimmed_pass + input[17] = false // val_fastp_save_trimmed_fail + input[18] = true // skip_complexity_filtering + input[19] = "" // val_complexity_filter_tool + input[20] = true // skip_deduplication + input[21] = true // skip_decontamination + input[22] = [] // ch_decontamination_fasta + input[23] = [] // ch_decontamination_reference + input[24] = [] // val_decontamination_index_name + input[25] = "" // val_decontamination_tool + input[26] = true // skip_final_concatenation + """ + } + } + then { + assert workflow.success + assertAll( + { assert snapshot(workflow.out.reads[0][1]).match() } + ) + } + } + + test("sarscov2 - fastq - skip all - single_end - stub") { + + options "-stub" + + when { + params { + save_merged = false + adapterremoval_args = save_merged ? "--collapse" : "" + } + workflow { + """ + 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) + ]) + input[1] = true // skip_fastqc + input[2] = true // skip_seqfu_check + input[3] = true // skip_seqfu_stats + input[4] = true // skip_seqkit_stats + input[5] = true // skip_seqtk_comp + input[6] = true // skip_seqkit_sana_pair + input[7] = true // skip_seqkit_seq + input[8] = true // skip_seqkit_replace + input[9] = true // skip_seqkit_rmdup + input[10] = true // skip_umitools_extract + input[11] = 0 // val_umi_discard_read + input[12] = true // skip_adapterremoval + input[13] = "" // val_adapter_tool + input[14] = [] // ch_custom_adapters_file + input[15] = false // val_save_merged + input[16] = false // val_fastp_discard_trimmed_pass + input[17] = false // val_fastp_save_trimmed_fail + input[18] = true // skip_complexity_filtering + input[19] = "" // val_complexity_filter_tool + input[20] = true // skip_deduplication + input[21] = true // skip_decontamination + input[22] = [] // ch_decontamination_fasta + input[23] = [] // ch_decontamination_reference + input[24] = [] // val_decontamination_index_name + input[25] = "" // val_decontamination_tool + input[26] = true // skip_final_concatenation + """ + } + } + then { + assert workflow.success + assertAll( + { assert snapshot(workflow.out.reads).match() } + ) + } + } +} diff --git a/subworkflows/nf-core/fastq_shortreads_preprocess_qc/tests/main.nf.test.snap b/subworkflows/nf-core/fastq_shortreads_preprocess_qc/tests/main.nf.test.snap new file mode 100644 index 0000000..d1e0d3f --- /dev/null +++ b/subworkflows/nf-core/fastq_shortreads_preprocess_qc/tests/main.nf.test.snap @@ -0,0 +1,150 @@ +{ + "sarscov2 - fastq - seqfu - seqkit - deacon - single_end": { + "content": [ + "test.fq.gz:md5,f3a7626275fad3775b6005fad9c13c27", + [ + [ + { + "id": "test", + "single_end": true + }, + "test.tsv:md5,4c6409169772005cfb06be9e41f2c1e2" + ] + ], + [ + [ + { + "id": "test", + "single_end": true + }, + "test.tsv:md5,7573d0c83cfc9af6e1ced67a45265381" + ] + ], + [ + [ + { + "id": "test", + "single_end": true + }, + "test.tsv:md5,24dd7cfbb9ae0034d0bd804f464f11fa" + ] + ], + [ + [ + { + "id": "test", + "single_end": true + }, + "test.tsv:md5,35dd18aff6780370b48027fec9c7d900" + ] + ], + [ + [ + { + "id": "test", + "single_end": true, + "index_id": "genome" + }, + "genome.idx:md5,84e4985c91800686db9c9dca28fabd1a" + ] + ], + "test.json", + [ + [ + { + "id": "test", + "single_end": true + }, + "test_mqc.txt:md5,1facba42f81058e557e3d85dcff2a6f3" + ], + [ + { + "id": "test", + "single_end": true + }, + "test_mqc.txt:md5,a039b8c1cc923db88d2484d3abbf00fe" + ] + ] + ], + "timestamp": "2026-06-25T10:02:19.793626706", + "meta": { + "nf-test": "0.9.5", + "nextflow": "26.04.3" + } + }, + "sarscov2 - fastq - adapterremoval - merge - cat more files - paired_end": { + "content": [ + "test.merged.fastq.gz:md5,369452751050a7f1e31b839702d61417", + "test2.merged.fastq.gz:md5,369452751050a7f1e31b839702d61417", + [ + "test.discarded.fastq.gz", + "test2.discarded.fastq.gz" + ] + ], + "timestamp": "2026-06-08T19:07:29.023690862", + "meta": { + "nf-test": "0.9.4", + "nextflow": "26.03.0" + } + }, + "sarscov2 - fastq - fastqc - seqkit - cutadapt - clumpify - cat - paired_end": { + "content": [ + 400, + 400, + "test_1_fastqc.html", + "test_2_fastqc.html", + "test_1_fastqc.html", + "test_2_fastqc.html", + "test_1_fastqc.zip", + "test_2_fastqc.zip", + "test_1_fastqc.zip", + "test_2_fastqc.zip", + 41 + ], + "timestamp": "2026-06-08T19:13:23.478389692", + "meta": { + "nf-test": "0.9.4", + "nextflow": "26.03.0" + } + }, + "sarscov2 - fastq - skip all - single_end": { + "content": [ + "/nf-core/test-datasets/modules/data/genomics/sarscov2/illumina/fastq/test_1.fastq.gz" + ], + "timestamp": "2026-06-08T19:07:33.894706204", + "meta": { + "nf-test": "0.9.4", + "nextflow": "26.03.0" + } + }, + "sarscov2 - fastq - umitools - prinseq - clumpify - cat - single_end": { + "content": [ + 400, + 51, + 41 + ], + "timestamp": "2026-06-08T19:07:12.173965635", + "meta": { + "nf-test": "0.9.4", + "nextflow": "26.03.0" + } + }, + "sarscov2 - fastq - skip all - single_end - stub": { + "content": [ + [ + [ + { + "id": "test", + "single_end": true + }, + "/nf-core/test-datasets/modules/data/genomics/sarscov2/illumina/fastq/test_1.fastq.gz" + ] + ] + ], + "timestamp": "2026-01-13T12:17:19.455574032", + "meta": { + "nf-test": "0.9.3", + "nextflow": "25.10.2" + } + } +} \ No newline at end of file diff --git a/subworkflows/nf-core/fastq_shortreads_preprocess_qc/tests/nextflow.config b/subworkflows/nf-core/fastq_shortreads_preprocess_qc/tests/nextflow.config new file mode 100644 index 0000000..ab4478f --- /dev/null +++ b/subworkflows/nf-core/fastq_shortreads_preprocess_qc/tests/nextflow.config @@ -0,0 +1,60 @@ +process { + withName: "SEQKIT_SANA" { + ext.prefix = { "${meta.id}_${meta.strandness}" } + } + + withName: "SEQKIT_SEQ" { + ext.args = [ + "--remove-gaps", + "--upper-case", + "--validate-seq", + "--min-len 30", + "--max-len 5000" + ].join(' ').trim() + ext.prefix = { "intermediate_seqkit_seq_${meta.strandness}" } + } + + withName: "SEQKIT_REPLACE" { + ext.args = '-p "/" -r "_"' + ext.prefix = { "intermediate_seqkit_replace_${meta.strandness}" } + } + + withName: "SEQKIT_RMDUP" { + ext.prefix = { "${meta.id}_${meta.strandness}" } + } + + withName: "UMITOOLS_EXTRACT" { + ext.args = '--bc-pattern="NNNN"' + } + + withName: "CUTADAPT" { + ext.args = '-q 25' + } + + withName: "ADAPTERREMOVAL_PE" { + ext.args = params.adapterremoval_args + } + + withName: ".*:FASTQ_COMPLEXITY_FILTER:BBMAP_BBDUK" { + ext.args = [ + 'entropy=0.7', // needed to turn on complexity filtering + 'minlength=0' // needed, to not discard reads shorter than this + ].join(' ') + ext.prefix = { "${meta.id}.trim" } + } + + // need FASTP to only do complexity filtering + withName: ".*:FASTQ_COMPLEXITY_FILTER:FASTP" { + ext.args = [ + '--low_complexity_filter', + '--disable_adapter_trimming', + '--disable_trim_poly_g', + '--disable_quality_filtering', + '--disable_length_filtering' + ].join(' ') + } + + withName: "BBMAP_CLUMPIFY" { + ext.args = 'dedupe=t' + } +} diff --git a/tests/default.nf.test.snap b/tests/default.nf.test.snap index 34c585e..8ba63e6 100644 --- a/tests/default.nf.test.snap +++ b/tests/default.nf.test.snap @@ -4,6 +4,15 @@ [ "cat", "cat/minigut.merged.fastq.gz", + "fastqc", + "fastqc/minigut_1_processed_1_fastqc.html", + "fastqc/minigut_1_processed_1_fastqc.zip", + "fastqc/minigut_1_processed_2_fastqc.html", + "fastqc/minigut_1_processed_2_fastqc.zip", + "fastqc/minigut_1_raw_1_fastqc.html", + "fastqc/minigut_1_raw_1_fastqc.zip", + "fastqc/minigut_1_raw_2_fastqc.html", + "fastqc/minigut_1_raw_2_fastqc.zip", "fmhfunprofiler", "fmhfunprofiler/KOs_sketched_scaled_1000_demo.sig.zip", "fmhfunprofiler/KOs_sketched_scaled_1000_demo.sig.zip/minigut_KOs_sketched_scaled_1000_demo.sig.fmhfunprofiler.csv", @@ -92,7 +101,7 @@ "snps.txt:md5,529d4fdb74c8940c0930ec1a7a6d4980" ] ], - "timestamp": "2026-07-31T09:50:44.24986845", + "timestamp": "2026-08-05T17:53:23.842325056", "meta": { "nf-test": "0.9.5", "nextflow": "26.04.6" diff --git a/tests/nextflow.config b/tests/nextflow.config index cbac3c9..3afd5ca 100644 --- a/tests/nextflow.config +++ b/tests/nextflow.config @@ -25,9 +25,6 @@ process { saveAs: { filename -> filename.equals('versions.yml') ? null : filename } ] - withName: 'FASTQC' { - ext.when = false - } withName: 'MULTIQC' { ext.when = false } diff --git a/workflows/funcprofiler.nf b/workflows/funcprofiler.nf index b5f6698..6f3a9d3 100644 --- a/workflows/funcprofiler.nf +++ b/workflows/funcprofiler.nf @@ -61,6 +61,8 @@ workflow FUNCPROFILER { samplesheet ) + ch_multiqc_files = ch_multiqc_files.mix(DATAPREP.out.multiqc_files) + DBPREP( databases )