From b80bf708982a74e55a21a8dbaf03738647ccca20 Mon Sep 17 00:00:00 2001 From: Jeff Jaureguy <67065808+Jaureguy760@users.noreply.github.com> Date: Sun, 15 Mar 2026 00:39:25 -0700 Subject: [PATCH] feat: update nf-core modules and pipeline configs Update nf-core modules across all 4 pipelines (nf-atacseq, nf-rnaseq, nf-outrider, nf-scatac). Add test_local.config profiles and update nextflow.config for each pipeline. Co-Authored-By: Claude Opus 4.6 (1M context) --- pipelines/nf-atacseq/.github/.dockstore.yml | 5 + pipelines/nf-atacseq/.github/CONTRIBUTING.md | 29 + .../.github/ISSUE_TEMPLATE/config.yml | 5 + .../.github/actions/get-shards/action.yml | 19 + .../.github/actions/nf-test/action.yml | 15 + .../nf-atacseq/.github/workflows/branch.yml | 13 + .../.github/workflows/linting_comment.yml | 13 + .../nf-atacseq/.github/workflows/nf-test.yml | 40 + pipelines/nf-atacseq/README.md | 26 +- .../nf-atacseq/assets/email_template.html | 48 + .../nf-atacseq/assets/email_template.txt | 34 + .../nf-atacseq/assets/multiqc_config.yml | 11 +- .../assets/nf-core-nf-atacseq_logo_light.png | Bin 0 -> 6628 bytes .../assets/nf-core-pipeline_logo_light.png | Bin 0 -> 10153 bytes pipelines/nf-atacseq/conf/modules.config | 5 +- pipelines/nf-atacseq/conf/test_local.config | 9 + .../images/nf-core-nf-atacseq_logo_dark.png | Bin 0 -> 6296 bytes .../images/nf-core-nf-atacseq_logo_light.png | Bin 0 -> 6628 bytes pipelines/nf-atacseq/environment.yml | 23 + pipelines/nf-atacseq/modules.json | 125 +- .../modules/local/wasp2_make_reads/main.nf | 6 + .../nf-core/bowtie2/align/environment.yml | 13 + .../modules/nf-core/bowtie2/align/main.nf | 107 +- .../modules/nf-core/bowtie2/align/meta.yml | 192 +++ .../bowtie2/align/tests/cram_crai.config | 5 + .../bowtie2/align/tests/large_index.config | 5 + .../nf-core/bowtie2/align/tests/main.nf.test | 623 ++++++++ .../bowtie2/align/tests/main.nf.test.snap | 551 +++++++ .../nf-core/bowtie2/align/tests/sam.config | 5 + .../nf-core/bowtie2/align/tests/sam2.config | 5 + .../modules/nf-core/bwa/index/environment.yml | 13 + .../modules/nf-core/bwa/index/main.nf | 44 +- .../modules/nf-core/bwa/index/meta.yml | 71 + .../nf-core/bwa/index/tests/main.nf.test | 57 + .../nf-core/bwa/index/tests/main.nf.test.snap | 108 ++ .../modules/nf-core/bwa/mem/environment.yml | 13 + .../modules/nf-core/bwa/mem/main.nf | 59 +- .../modules/nf-core/bwa/mem/meta.yml | 149 ++ .../nf-core/bwa/mem/tests/main.nf.test | 255 +++ .../nf-core/bwa/mem/tests/main.nf.test.snap | 375 +++++ .../modules/nf-core/fastp/environment.yml | 8 + .../nf-atacseq/modules/nf-core/fastp/main.nf | 104 +- .../nf-atacseq/modules/nf-core/fastp/meta.yml | 144 ++ .../modules/nf-core/fastp/tests/main.nf.test | 661 ++++++++ .../nf-core/fastp/tests/main.nf.test.snap | 1376 +++++++++++++++++ .../fastp/tests/nextflow.interleaved.config | 5 + .../fastp/tests/nextflow.save_failed.config | 5 + .../modules/nf-core/fastqc/environment.yml | 7 + .../nf-atacseq/modules/nf-core/fastqc/main.nf | 52 +- .../modules/nf-core/fastqc/meta.yml | 111 ++ .../modules/nf-core/fastqc/tests/main.nf.test | 309 ++++ .../nf-core/fastqc/tests/main.nf.test.snap | 476 ++++++ .../nf-core/macs2/callpeak/environment.yml | 9 + .../modules/nf-core/macs2/callpeak/main.nf | 59 +- .../modules/nf-core/macs2/callpeak/meta.yml | 106 ++ .../nf-core/macs2/callpeak/tests/bam.config | 5 + .../nf-core/macs2/callpeak/tests/bed.config | 5 + .../nf-core/macs2/callpeak/tests/main.nf.test | 125 ++ .../macs2/callpeak/tests/main.nf.test.snap | 222 +++ .../modules/nf-core/multiqc/environment.yml | 7 + .../modules/nf-core/multiqc/main.nf | 56 +- .../modules/nf-core/multiqc/meta.yml | 133 ++ .../multiqc/tests/custom_prefix.config | 5 + .../nf-core/multiqc/tests/main.nf.test | 161 ++ .../nf-core/multiqc/tests/main.nf.test.snap | 130 ++ .../nf-core/multiqc/tests/nextflow.config | 5 + .../picard/markduplicates/environment.yml | 8 + .../nf-core/picard/markduplicates/main.nf | 66 +- .../nf-core/picard/markduplicates/meta.yml | 124 ++ .../picard/markduplicates/tests/main.nf.test | 173 +++ .../markduplicates/tests/main.nf.test.snap | 218 +++ .../markduplicates/tests/nextflow.config | 6 + .../nf-core/samtools/faidx/environment.yml | 10 + .../modules/nf-core/samtools/faidx/main.nf | 44 +- .../modules/nf-core/samtools/faidx/meta.yml | 112 ++ .../nf-core/samtools/faidx/tests/main.nf.test | 253 +++ .../samtools/faidx/tests/main.nf.test.snap | 352 +++++ .../samtools/faidx/tests/nextflow.config | 7 + .../nf-core/samtools/flagstat/environment.yml | 10 + .../modules/nf-core/samtools/flagstat/main.nf | 41 +- .../nf-core/samtools/flagstat/meta.yml | 75 + .../samtools/flagstat/tests/main.nf.test | 56 + .../samtools/flagstat/tests/main.nf.test.snap | 88 ++ .../nf-core/samtools/idxstats/environment.yml | 10 + .../modules/nf-core/samtools/idxstats/main.nf | 27 +- .../nf-core/samtools/idxstats/meta.yml | 75 + .../samtools/idxstats/tests/main.nf.test | 59 + .../samtools/idxstats/tests/main.nf.test.snap | 56 + .../nf-core/samtools/index/environment.yml | 10 + .../modules/nf-core/samtools/index/main.nf | 35 +- .../modules/nf-core/samtools/index/meta.yml | 92 ++ .../samtools/index/tests/csi.nextflow.config | 7 + .../nf-core/samtools/index/tests/main.nf.test | 155 ++ .../samtools/index/tests/main.nf.test.snap | 156 ++ .../nf-core/samtools/sort/environment.yml | 10 + .../modules/nf-core/samtools/sort/main.nf | 75 +- .../modules/nf-core/samtools/sort/meta.yml | 142 ++ .../nf-core/samtools/sort/tests/main.nf.test | 332 ++++ .../samtools/sort/tests/main.nf.test.snap | 296 ++++ .../samtools/sort/tests/nextflow.config | 7 + .../samtools/sort/tests/nextflow_cram.config | 8 + .../nf-core/samtools/stats/environment.yml | 10 + .../modules/nf-core/samtools/stats/main.nf | 38 +- .../modules/nf-core/samtools/stats/meta.yml | 88 ++ .../nf-core/samtools/stats/tests/main.nf.test | 113 ++ .../samtools/stats/tests/main.nf.test.snap | 174 +++ pipelines/nf-atacseq/nextflow.config | 56 +- pipelines/nf-atacseq/nextflow_schema.json | 46 +- pipelines/nf-atacseq/nf-test.config | 1 + .../subworkflows/local/prepare_genome/main.nf | 7 +- .../subworkflows/local/wasp_mapping/main.nf | 17 +- .../nf-core/bam_markduplicates_picard/main.nf | 67 +- .../bam_markduplicates_picard/meta.yml | 81 +- .../tests/main.nf.test | 155 ++ .../tests/main.nf.test.snap | 292 ++++ .../tests/nextflow.config | 5 + .../nf-core/bam_sort_stats_samtools/main.nf | 52 +- .../nf-core/bam_sort_stats_samtools/meta.yml | 98 +- .../tests/main.nf.test | 132 ++ .../tests/main.nf.test.snap | 288 ++++ .../nf-core/bam_stats_samtools/main.nf | 15 +- .../nf-core/bam_stats_samtools/meta.yml | 47 +- .../bam_stats_samtools/tests/main.nf.test | 185 +++ .../tests/main.nf.test.snap | 305 ++++ .../nf-core/fastq_align_bowtie2/main.nf | 61 +- .../nf-core/fastq_align_bowtie2/meta.yml | 112 +- .../fastq_align_bowtie2/tests/main.nf.test | 189 +++ .../tests/main.nf.test.snap | 230 +++ .../fastq_align_bowtie2/tests/nextflow.config | 8 + .../nf-core/fastq_align_bwa/main.nf | 53 +- .../nf-core/fastq_align_bwa/meta.yml | 82 +- .../fastq_align_bwa/tests/main.nf.test | 77 + .../fastq_align_bwa/tests/main.nf.test.snap | 264 ++++ .../fastq_align_bwa/tests/nextflow.config | 8 + .../tests/data/bwa_index/chr_test.fa | 664 ++++---- .../tests/data/bwa_index/chr_test.fa.amb | 2 +- .../tests/data/bwa_index/chr_test.fa.ann | 4 +- .../tests/data/bwa_index/chr_test.fa.bwt | Bin 19892 -> 20088 bytes .../tests/data/bwa_index/chr_test.fa.pac | Bin 4952 -> 5002 bytes .../tests/data/bwa_index/chr_test.fa.sa | Bin 9952 -> 10056 bytes pipelines/nf-atacseq/tests/data/chr_test.fa | 336 +++- .../nf-atacseq/tests/data/chr_test.fa.fai | 2 +- .../data/generate_realistic_reference.py | 123 ++ .../tests/data/generate_test_data.sh | 342 +++- .../nf-atacseq/tests/data/real_counts.tsv | 1 + pipelines/nf-atacseq/tests/data/real_test.bam | 1 + .../nf-atacseq/tests/data/real_test.bam.bai | 1 + .../nf-atacseq/tests/data/real_wasp_data.json | 1 + pipelines/nf-atacseq/tests/data/regions.bed | 16 +- .../nf-atacseq/tests/data/sample1_R1.fq.gz | Bin 6334 -> 91126 bytes .../nf-atacseq/tests/data/sample1_R2.fq.gz | Bin 6417 -> 90892 bytes .../tests/data/samplesheet_test.csv | 2 +- pipelines/nf-atacseq/tests/data/variants.vcf | 38 + .../nf-atacseq/tests/data/variants.vcf.gz | Bin 45 -> 563 bytes .../nf-atacseq/tests/data/variants.vcf.gz.tbi | Bin 49 -> 121 bytes pipelines/nf-atacseq/tests/default.nf.test | 28 + pipelines/nf-atacseq/tests/main.nf.test | 9 +- .../local/wasp2_filter_remapped.nf.test | 87 +- .../modules/local/wasp2_make_reads.nf.test | 59 +- pipelines/nf-atacseq/tests/nextflow.config | 13 +- pipelines/nf-atacseq/workflows/atacseq.nf | 34 +- pipelines/nf-outrider/conf/test_local.config | 9 + pipelines/nf-outrider/nextflow.config | 59 +- pipelines/nf-rnaseq/conf/test_local.config | 9 + pipelines/nf-rnaseq/nextflow.config | 114 +- pipelines/nf-scatac/conf/test_local.config | 9 + pipelines/nf-scatac/nextflow.config | 58 +- 167 files changed, 14593 insertions(+), 1341 deletions(-) create mode 100644 pipelines/nf-atacseq/.github/.dockstore.yml create mode 100644 pipelines/nf-atacseq/.github/CONTRIBUTING.md create mode 100644 pipelines/nf-atacseq/.github/ISSUE_TEMPLATE/config.yml create mode 100644 pipelines/nf-atacseq/.github/actions/get-shards/action.yml create mode 100644 pipelines/nf-atacseq/.github/actions/nf-test/action.yml create mode 100644 pipelines/nf-atacseq/.github/workflows/branch.yml create mode 100644 pipelines/nf-atacseq/.github/workflows/linting_comment.yml create mode 100644 pipelines/nf-atacseq/.github/workflows/nf-test.yml create mode 100644 pipelines/nf-atacseq/assets/email_template.html create mode 100644 pipelines/nf-atacseq/assets/email_template.txt create mode 100644 pipelines/nf-atacseq/assets/nf-core-nf-atacseq_logo_light.png create mode 100644 pipelines/nf-atacseq/assets/nf-core-pipeline_logo_light.png create mode 100644 pipelines/nf-atacseq/docs/images/nf-core-nf-atacseq_logo_dark.png create mode 100644 pipelines/nf-atacseq/docs/images/nf-core-nf-atacseq_logo_light.png create mode 100644 pipelines/nf-atacseq/environment.yml create mode 100644 pipelines/nf-atacseq/modules/nf-core/bowtie2/align/environment.yml create mode 100644 pipelines/nf-atacseq/modules/nf-core/bowtie2/align/meta.yml create mode 100644 pipelines/nf-atacseq/modules/nf-core/bowtie2/align/tests/cram_crai.config create mode 100644 pipelines/nf-atacseq/modules/nf-core/bowtie2/align/tests/large_index.config create mode 100644 pipelines/nf-atacseq/modules/nf-core/bowtie2/align/tests/main.nf.test create mode 100644 pipelines/nf-atacseq/modules/nf-core/bowtie2/align/tests/main.nf.test.snap create mode 100644 pipelines/nf-atacseq/modules/nf-core/bowtie2/align/tests/sam.config create mode 100644 pipelines/nf-atacseq/modules/nf-core/bowtie2/align/tests/sam2.config create mode 100644 pipelines/nf-atacseq/modules/nf-core/bwa/index/environment.yml create mode 100644 pipelines/nf-atacseq/modules/nf-core/bwa/index/meta.yml create mode 100644 pipelines/nf-atacseq/modules/nf-core/bwa/index/tests/main.nf.test create mode 100644 pipelines/nf-atacseq/modules/nf-core/bwa/index/tests/main.nf.test.snap create mode 100644 pipelines/nf-atacseq/modules/nf-core/bwa/mem/environment.yml create mode 100644 pipelines/nf-atacseq/modules/nf-core/bwa/mem/meta.yml create mode 100644 pipelines/nf-atacseq/modules/nf-core/bwa/mem/tests/main.nf.test create mode 100644 pipelines/nf-atacseq/modules/nf-core/bwa/mem/tests/main.nf.test.snap create mode 100644 pipelines/nf-atacseq/modules/nf-core/fastp/environment.yml create mode 100644 pipelines/nf-atacseq/modules/nf-core/fastp/meta.yml create mode 100644 pipelines/nf-atacseq/modules/nf-core/fastp/tests/main.nf.test create mode 100644 pipelines/nf-atacseq/modules/nf-core/fastp/tests/main.nf.test.snap create mode 100644 pipelines/nf-atacseq/modules/nf-core/fastp/tests/nextflow.interleaved.config create mode 100644 pipelines/nf-atacseq/modules/nf-core/fastp/tests/nextflow.save_failed.config create mode 100644 pipelines/nf-atacseq/modules/nf-core/fastqc/environment.yml create mode 100644 pipelines/nf-atacseq/modules/nf-core/fastqc/meta.yml create mode 100644 pipelines/nf-atacseq/modules/nf-core/fastqc/tests/main.nf.test create mode 100644 pipelines/nf-atacseq/modules/nf-core/fastqc/tests/main.nf.test.snap create mode 100644 pipelines/nf-atacseq/modules/nf-core/macs2/callpeak/environment.yml create mode 100644 pipelines/nf-atacseq/modules/nf-core/macs2/callpeak/meta.yml create mode 100644 pipelines/nf-atacseq/modules/nf-core/macs2/callpeak/tests/bam.config create mode 100644 pipelines/nf-atacseq/modules/nf-core/macs2/callpeak/tests/bed.config create mode 100644 pipelines/nf-atacseq/modules/nf-core/macs2/callpeak/tests/main.nf.test create mode 100644 pipelines/nf-atacseq/modules/nf-core/macs2/callpeak/tests/main.nf.test.snap create mode 100644 pipelines/nf-atacseq/modules/nf-core/multiqc/environment.yml create mode 100644 pipelines/nf-atacseq/modules/nf-core/multiqc/meta.yml create mode 100644 pipelines/nf-atacseq/modules/nf-core/multiqc/tests/custom_prefix.config create mode 100644 pipelines/nf-atacseq/modules/nf-core/multiqc/tests/main.nf.test create mode 100644 pipelines/nf-atacseq/modules/nf-core/multiqc/tests/main.nf.test.snap create mode 100644 pipelines/nf-atacseq/modules/nf-core/multiqc/tests/nextflow.config create mode 100644 pipelines/nf-atacseq/modules/nf-core/picard/markduplicates/environment.yml create mode 100644 pipelines/nf-atacseq/modules/nf-core/picard/markduplicates/meta.yml create mode 100644 pipelines/nf-atacseq/modules/nf-core/picard/markduplicates/tests/main.nf.test create mode 100644 pipelines/nf-atacseq/modules/nf-core/picard/markduplicates/tests/main.nf.test.snap create mode 100644 pipelines/nf-atacseq/modules/nf-core/picard/markduplicates/tests/nextflow.config create mode 100644 pipelines/nf-atacseq/modules/nf-core/samtools/faidx/environment.yml create mode 100644 pipelines/nf-atacseq/modules/nf-core/samtools/faidx/meta.yml create mode 100644 pipelines/nf-atacseq/modules/nf-core/samtools/faidx/tests/main.nf.test create mode 100644 pipelines/nf-atacseq/modules/nf-core/samtools/faidx/tests/main.nf.test.snap create mode 100644 pipelines/nf-atacseq/modules/nf-core/samtools/faidx/tests/nextflow.config create mode 100644 pipelines/nf-atacseq/modules/nf-core/samtools/flagstat/environment.yml create mode 100644 pipelines/nf-atacseq/modules/nf-core/samtools/flagstat/meta.yml create mode 100644 pipelines/nf-atacseq/modules/nf-core/samtools/flagstat/tests/main.nf.test create mode 100644 pipelines/nf-atacseq/modules/nf-core/samtools/flagstat/tests/main.nf.test.snap create mode 100644 pipelines/nf-atacseq/modules/nf-core/samtools/idxstats/environment.yml create mode 100644 pipelines/nf-atacseq/modules/nf-core/samtools/idxstats/meta.yml create mode 100644 pipelines/nf-atacseq/modules/nf-core/samtools/idxstats/tests/main.nf.test create mode 100644 pipelines/nf-atacseq/modules/nf-core/samtools/idxstats/tests/main.nf.test.snap create mode 100644 pipelines/nf-atacseq/modules/nf-core/samtools/index/environment.yml create mode 100644 pipelines/nf-atacseq/modules/nf-core/samtools/index/meta.yml create mode 100644 pipelines/nf-atacseq/modules/nf-core/samtools/index/tests/csi.nextflow.config create mode 100644 pipelines/nf-atacseq/modules/nf-core/samtools/index/tests/main.nf.test create mode 100644 pipelines/nf-atacseq/modules/nf-core/samtools/index/tests/main.nf.test.snap create mode 100644 pipelines/nf-atacseq/modules/nf-core/samtools/sort/environment.yml create mode 100644 pipelines/nf-atacseq/modules/nf-core/samtools/sort/meta.yml create mode 100644 pipelines/nf-atacseq/modules/nf-core/samtools/sort/tests/main.nf.test create mode 100644 pipelines/nf-atacseq/modules/nf-core/samtools/sort/tests/main.nf.test.snap create mode 100644 pipelines/nf-atacseq/modules/nf-core/samtools/sort/tests/nextflow.config create mode 100644 pipelines/nf-atacseq/modules/nf-core/samtools/sort/tests/nextflow_cram.config create mode 100644 pipelines/nf-atacseq/modules/nf-core/samtools/stats/environment.yml create mode 100644 pipelines/nf-atacseq/modules/nf-core/samtools/stats/meta.yml create mode 100644 pipelines/nf-atacseq/modules/nf-core/samtools/stats/tests/main.nf.test create mode 100644 pipelines/nf-atacseq/modules/nf-core/samtools/stats/tests/main.nf.test.snap create mode 100644 pipelines/nf-atacseq/subworkflows/nf-core/bam_markduplicates_picard/tests/main.nf.test create mode 100644 pipelines/nf-atacseq/subworkflows/nf-core/bam_markduplicates_picard/tests/main.nf.test.snap create mode 100644 pipelines/nf-atacseq/subworkflows/nf-core/bam_markduplicates_picard/tests/nextflow.config create mode 100644 pipelines/nf-atacseq/subworkflows/nf-core/bam_sort_stats_samtools/tests/main.nf.test create mode 100644 pipelines/nf-atacseq/subworkflows/nf-core/bam_sort_stats_samtools/tests/main.nf.test.snap create mode 100644 pipelines/nf-atacseq/subworkflows/nf-core/bam_stats_samtools/tests/main.nf.test create mode 100644 pipelines/nf-atacseq/subworkflows/nf-core/bam_stats_samtools/tests/main.nf.test.snap create mode 100644 pipelines/nf-atacseq/subworkflows/nf-core/fastq_align_bowtie2/tests/main.nf.test create mode 100644 pipelines/nf-atacseq/subworkflows/nf-core/fastq_align_bowtie2/tests/main.nf.test.snap create mode 100644 pipelines/nf-atacseq/subworkflows/nf-core/fastq_align_bowtie2/tests/nextflow.config create mode 100644 pipelines/nf-atacseq/subworkflows/nf-core/fastq_align_bwa/tests/main.nf.test create mode 100644 pipelines/nf-atacseq/subworkflows/nf-core/fastq_align_bwa/tests/main.nf.test.snap create mode 100644 pipelines/nf-atacseq/subworkflows/nf-core/fastq_align_bwa/tests/nextflow.config mode change 120000 => 100644 pipelines/nf-atacseq/tests/data/chr_test.fa mode change 120000 => 100644 pipelines/nf-atacseq/tests/data/chr_test.fa.fai create mode 100644 pipelines/nf-atacseq/tests/data/generate_realistic_reference.py create mode 120000 pipelines/nf-atacseq/tests/data/real_counts.tsv create mode 120000 pipelines/nf-atacseq/tests/data/real_test.bam create mode 120000 pipelines/nf-atacseq/tests/data/real_test.bam.bai create mode 120000 pipelines/nf-atacseq/tests/data/real_wasp_data.json mode change 120000 => 100644 pipelines/nf-atacseq/tests/data/regions.bed create mode 100644 pipelines/nf-atacseq/tests/data/variants.vcf mode change 120000 => 100644 pipelines/nf-atacseq/tests/data/variants.vcf.gz mode change 120000 => 100644 pipelines/nf-atacseq/tests/data/variants.vcf.gz.tbi create mode 100644 pipelines/nf-atacseq/tests/default.nf.test diff --git a/pipelines/nf-atacseq/.github/.dockstore.yml b/pipelines/nf-atacseq/.github/.dockstore.yml new file mode 100644 index 0000000..8c3f31e --- /dev/null +++ b/pipelines/nf-atacseq/.github/.dockstore.yml @@ -0,0 +1,5 @@ +version: 1.2 +workflows: + - subclass: nfl + primaryDescriptorPath: /pipelines/nf-atacseq/main.nf + publish: true diff --git a/pipelines/nf-atacseq/.github/CONTRIBUTING.md b/pipelines/nf-atacseq/.github/CONTRIBUTING.md new file mode 100644 index 0000000..a5995f2 --- /dev/null +++ b/pipelines/nf-atacseq/.github/CONTRIBUTING.md @@ -0,0 +1,29 @@ +# Contributing to nf-atacseq + +## Getting help + +For questions, bugs, or feature requests, please open an issue on [GitHub](https://github.com/mcvickerlab/WASP2/issues). + +## Development workflow + +1. Fork the repository +2. Create a feature branch from `dev` +3. Make your changes +4. Run `nf-core pipelines lint` to verify compliance +5. Submit a pull request to `dev` + +## Code style + +- Follow nf-core module conventions for new modules +- Use `tuple val(meta), path(...)` for all process inputs/outputs +- Include `stub:` blocks in all processes +- Add `versions.yml` output to all processes +- Write `meta.yml` documentation for new modules + +## Testing + +Run the test profile before submitting changes: + +```bash +nextflow run main.nf -profile test,docker --outdir test_results +``` diff --git a/pipelines/nf-atacseq/.github/ISSUE_TEMPLATE/config.yml b/pipelines/nf-atacseq/.github/ISSUE_TEMPLATE/config.yml new file mode 100644 index 0000000..77ae53a --- /dev/null +++ b/pipelines/nf-atacseq/.github/ISSUE_TEMPLATE/config.yml @@ -0,0 +1,5 @@ +blank_issues_enabled: true +contact_links: + - name: WASP2 Documentation + url: https://wasp2.readthedocs.io + about: Check the documentation for usage help diff --git a/pipelines/nf-atacseq/.github/actions/get-shards/action.yml b/pipelines/nf-atacseq/.github/actions/get-shards/action.yml new file mode 100644 index 0000000..08ec9a5 --- /dev/null +++ b/pipelines/nf-atacseq/.github/actions/get-shards/action.yml @@ -0,0 +1,19 @@ +name: Get test shards +description: Get nf-test shards for parallel execution +inputs: + test-path: + description: Path to test files + required: false + default: tests +outputs: + shards: + description: JSON array of test shards + value: ${{ steps.get-shards.outputs.shards }} +runs: + using: composite + steps: + - id: get-shards + shell: bash + run: | + shards=$(find ${{ inputs.test-path }} -name "*.nf.test" | jq -R -s -c 'split("\n") | map(select(. != ""))') + echo "shards=$shards" >> $GITHUB_OUTPUT diff --git a/pipelines/nf-atacseq/.github/actions/nf-test/action.yml b/pipelines/nf-atacseq/.github/actions/nf-test/action.yml new file mode 100644 index 0000000..8187d63 --- /dev/null +++ b/pipelines/nf-atacseq/.github/actions/nf-test/action.yml @@ -0,0 +1,15 @@ +name: Run nf-test +description: Run nf-test for a specific test file +inputs: + test-file: + description: Path to the nf-test file + required: true + profile: + description: Nextflow profile to use + required: false + default: test,docker +runs: + using: composite + steps: + - shell: bash + run: nf-test test ${{ inputs.test-file }} --profile ${{ inputs.profile }} diff --git a/pipelines/nf-atacseq/.github/workflows/branch.yml b/pipelines/nf-atacseq/.github/workflows/branch.yml new file mode 100644 index 0000000..27d29aa --- /dev/null +++ b/pipelines/nf-atacseq/.github/workflows/branch.yml @@ -0,0 +1,13 @@ +name: nf-core branch protection +on: + pull_request_target: + branches: [master, main] + +jobs: + test: + runs-on: ubuntu-latest + steps: + - name: Check PRs + if: github.repository == 'mcvickerlab/WASP2' + run: | + { [[ ${{github.event.pull_request.head.repo.full_name}} == mcvickerlab/WASP2 ]] && [[ $GITHUB_HEAD_REF == "dev" ]]; } || echo "This PR is not from dev. It will be reviewed manually." diff --git a/pipelines/nf-atacseq/.github/workflows/linting_comment.yml b/pipelines/nf-atacseq/.github/workflows/linting_comment.yml new file mode 100644 index 0000000..86e12a3 --- /dev/null +++ b/pipelines/nf-atacseq/.github/workflows/linting_comment.yml @@ -0,0 +1,13 @@ +name: nf-core linting comment +on: + workflow_run: + workflows: ["nf-core linting"] + types: [completed] + +jobs: + linting-comment: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: Post linting comment + run: echo "Linting workflow completed" diff --git a/pipelines/nf-atacseq/.github/workflows/nf-test.yml b/pipelines/nf-atacseq/.github/workflows/nf-test.yml new file mode 100644 index 0000000..fa7474d --- /dev/null +++ b/pipelines/nf-atacseq/.github/workflows/nf-test.yml @@ -0,0 +1,40 @@ +name: Run nf-test +on: + pull_request: + branches: [dev, master] + release: + types: [published] + merge_group: + workflow_dispatch: + +concurrency: + group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} + cancel-in-progress: true + +env: + NXF_ANSI_LOG: false + NFT_VER: "0.9.2" + +jobs: + nf-test: + name: "nf-test | NXF ${{ matrix.NXF_VER }}" + runs-on: ubuntu-latest + strategy: + matrix: + NXF_VER: + - "23.04.0" + - "latest-everything" + steps: + - uses: actions/checkout@v4 + + - uses: nf-core/setup-nextflow@v2 + with: + version: "${{ matrix.NXF_VER }}" + + - name: Install nf-test + run: | + wget -qO- https://code.askimed.com/install/nf-test | bash + sudo mv nf-test /usr/local/bin/ + + - name: Run nf-test + run: nf-test test --profile test,docker diff --git a/pipelines/nf-atacseq/README.md b/pipelines/nf-atacseq/README.md index a0f718a..64d62d0 100644 --- a/pipelines/nf-atacseq/README.md +++ b/pipelines/nf-atacseq/README.md @@ -1,6 +1,6 @@ # nf-atacseq -[![nf-atacseq Tests](https://github.com/your-org/WASP2/actions/workflows/nf-atacseq-tests.yml/badge.svg)](https://github.com/your-org/WASP2/actions/workflows/nf-atacseq-tests.yml) +[![nf-atacseq CI](https://github.com/mcvickerlab/WASP2/actions/workflows/ci.yml/badge.svg)](https://github.com/mcvickerlab/WASP2/actions/workflows/ci.yml) ATAC-seq Allelic Imbalance (AI) Pipeline with WASP2 mapping bias correction. @@ -60,6 +60,12 @@ nextflow run pipelines/nf-atacseq -profile test,docker nextflow run pipelines/nf-atacseq -profile test,docker -stub-run # Workflow validation only ``` +### Local Test (chr21 data) + +```bash +nextflow run pipelines/nf-atacseq -profile test_local,docker +``` + ## Samplesheet Format ```csv @@ -112,6 +118,19 @@ results/ See [docs/output.md](docs/output.md) for detailed output descriptions. +## Validation with chr21 1000 Genomes Data + +Run a quick validation using chr21 data from the 1000 Genomes Project: + +```bash +# Uses pre-configured chr21 test data (NA12878, HG00096) +nextflow run pipelines/nf-atacseq -profile test_local,docker + +# Expect: ~2-5 min runtime, allele counts at chr21 het SNPs +``` + +This profile uses downsampled chr21 FASTQ reads and a chr21-only VCF, providing a fast end-to-end validation without downloading full genomes. + ## Testing ### Run nf-test Suite @@ -148,6 +167,7 @@ nextflow run . -profile test -stub-run | `singularity` | Run with Singularity containers | | `conda` | Run with Conda environments | | `test` | Minimal test configuration | +| `test_local` | Local test with chr21 1000 Genomes data | | `test_full` | Full test with real data | ## Pipeline DAG @@ -168,7 +188,7 @@ FASTQ → FastQC → Fastp → BWA/Bowtie2 → Samtools → Picard → MACS2 → If you use nf-atacseq, please cite: -- **WASP2**: [GitHub Repository](https://github.com/your-org/WASP2) +- **WASP2**: [GitHub Repository](https://github.com/mcvickerlab/WASP2) - **Nextflow**: Di Tommaso, P., et al. (2017). Nextflow enables reproducible computational workflows. *Nature Biotechnology*. ## License @@ -177,5 +197,5 @@ MIT License - see [LICENSE](../../LICENSE) for details. ## Support -- [Issues](https://github.com/your-org/WASP2/issues) +- [Issues](https://github.com/mcvickerlab/WASP2/issues) - [Documentation](docs/) diff --git a/pipelines/nf-atacseq/assets/email_template.html b/pipelines/nf-atacseq/assets/email_template.html new file mode 100644 index 0000000..161c1e4 --- /dev/null +++ b/pipelines/nf-atacseq/assets/email_template.html @@ -0,0 +1,48 @@ + + + + + + ${workflow.manifest.name} Pipeline Report + + +
+ + + +

${workflow.manifest.name} v${workflow.manifest.version}

+

Run Name: $runName

+ +<% if (!success) { %> +
+

⚠️ ${workflow.manifest.name} execution completed unsuccessfully!

+

The exit status of the task that caused the workflow execution to fail was: $exitStatus.

+

The full error message was:

+
${errorReport}
+
+<% } else { %> +
+${workflow.manifest.name} execution completed successfully! +
+<% } %> + +

The workflow was completed at $dateComplete (duration: $duration)

+

The command used to launch the workflow was as follows:

+
$commandLine
+ +

Pipeline Configuration:

+ + + + +<% if (summary.size() > 0) { %> +<% for (e in summary) { %> +<% } %> +<% } %> +
Nextflow Version$nextflowVersion
Run Name$runName
Session ID$sessionId
${e.key}${e.value}
+ +

--- ${workflow.manifest.name}

+ +
+ + diff --git a/pipelines/nf-atacseq/assets/email_template.txt b/pipelines/nf-atacseq/assets/email_template.txt new file mode 100644 index 0000000..17cb4f4 --- /dev/null +++ b/pipelines/nf-atacseq/assets/email_template.txt @@ -0,0 +1,34 @@ +---------------------------------------------------- + ${workflow.manifest.name} v${workflow.manifest.version} +---------------------------------------------------- +Run Name: $runName + +<% if (success) { + out << "## ${workflow.manifest.name} execution completed successfully! ##" +} else { + out << """#################################################### +## ${workflow.manifest.name} execution completed unsuccessfully! ## +#################################################### +The exit status of the task that caused the workflow execution to fail was: $exitStatus. +The full error message was: + +${errorReport} +""" +} %> + + +The workflow was completed at $dateComplete (duration: $duration) + +The command used to launch the workflow was as follows: + + $commandLine + + + +Pipeline Configuration: +----------------------- +<% for (e in summary) { %> ${e.key.padRight(30)}: ${e.value} +<% } %> + +-- +${workflow.manifest.name} diff --git a/pipelines/nf-atacseq/assets/multiqc_config.yml b/pipelines/nf-atacseq/assets/multiqc_config.yml index ecb2e54..2b9c9d4 100644 --- a/pipelines/nf-atacseq/assets/multiqc_config.yml +++ b/pipelines/nf-atacseq/assets/multiqc_config.yml @@ -1,14 +1,17 @@ # MultiQC configuration for nf-atacseq report_comment: > - This report has been generated by the nf-atacseq - pipeline. It summarizes QC metrics from ATAC-seq allelic imbalance analysis with WASP2. + This report has been generated by the nf-core/nf-atacseq + analysis pipeline. For information about how to interpret these results, please see the + documentation. report_section_order: - software_versions: - order: -1000 nf-atacseq-methods-description: + order: -1000 + software_versions: order: -1001 + wasp2-nf-atacseq-summary: + order: -1002 export_plots: true diff --git a/pipelines/nf-atacseq/assets/nf-core-nf-atacseq_logo_light.png b/pipelines/nf-atacseq/assets/nf-core-nf-atacseq_logo_light.png new file mode 100644 index 0000000000000000000000000000000000000000..11ceb4e13bd0c6c0899733785dc69632cd35004b GIT binary patch literal 6628 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 KPa4#pz5O48n497N literal 0 HcmV?d00001 diff --git a/pipelines/nf-atacseq/assets/nf-core-pipeline_logo_light.png b/pipelines/nf-atacseq/assets/nf-core-pipeline_logo_light.png new file mode 100644 index 0000000000000000000000000000000000000000..476ce6635100247b6fad91cf4472c144504125c4 GIT binary patch literal 10153 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 literal 0 HcmV?d00001 diff --git a/pipelines/nf-atacseq/docs/images/nf-core-nf-atacseq_logo_light.png b/pipelines/nf-atacseq/docs/images/nf-core-nf-atacseq_logo_light.png new file mode 100644 index 0000000000000000000000000000000000000000..11ceb4e13bd0c6c0899733785dc69632cd35004b GIT binary patch literal 6628 zcmeHM=Qmty-MUdlxmj#ON}liB6Q!%h5X-!e9)k1jA^9 zF&K5U8Kcc8qr5vGos{}s=gYnJ+H0@9@B3G--*sI(&d5NEg^8Pqj*gB+NBfBh z9o-*R!1XW2KY$f+%D6yB$5pHIM9nNTpEMh0mw{RA-Eq>obm`!Uk%^reSna1h8`L{| zw%@LQrP8B4u+hTG(|T?*x8jx_SO@8q>FyC5{~qLg??Z{my&Ohu#y|cLP(+4Y9z?C;%0UH!$C1p-jpIdfNvsb>QmMSbHADdm{ZGHlAEhren%+DkZeo ztUHb-N(Jx7-@6p|s^8Pt`fEnVw1VsQ?WuA2ORt4-1cYpP-0h5?IupTZ@jm<4qP8f6 z--vh7bdmOO-82B(^m1T;M?CAqV?|!n@GQV&;OXCDJn)mr&rxB4d&4Kjg6uvp3b1} zpcD-2a+$C0H{*{gy107Hb=A$gdZMp*EH5eoY*+M>o}NCurP1iViIk}YuT9YMs=x~5 z@B{q^_G5i3Nw;Wy2v4;7&d!^Zkit3Z-Ladd7AP)BExf#@?~mcCwLcmzwMDJ*y%79| z7VN=l!iSoY`M%ngQbG`U_~69jT#SJr_VfG?aCaIN|XO&jBb?=CC`X~n**8y>$(Zsxj?2lWOba37dCku@i#w%@USEl z5p@~I>7&|$q*5OMX{s`O8a;OfgFFR}A>SQpI&dE6)p9)BI7d<5s-0AURrimJ6zM;X z+UpdD%JbruS2ih!eJ-=Ol(t(7N3hB*Mb3ium+t+?A|g2=9cBi)hPj{pOnd_BwocV} zHLk}GkkjxC=coVR{Wn%2@|Q|K$M3jJFV{tsW2Q>l%(a`0lahO-6Nh-5@<*bwg8gAcUSA zChxg;#LCJN)G@m;V#%{#M#517+s_~Su21T%u4#qXTzlK{==fBidiNCpL`tCFijOJ& zX}N`9g&@7=C6WSiGv%{tnS`Es1>d#DN&_yLCG^^+6_7|G*xD(bUccg6;l{+*di#W6 z3r-86r$X2;Y*g7P8|Cf>iXwgHbT-|IRqqyFXb|&<|YtnmT zzPf>A=lBUytZkl=n-dN+eQB|lVRTWNg(y%>%k#qcsBI*1CC)Q0FU2?WW-?JSpc5CG zH6gCgEh8x@)rT4a-qpnh7TC6#U$3n!mrw4$vl8EE=>zfh4x3fE8R<2f8q}oDES;cp zQHX^s*9jS*vWW4xyFUM`QaDC4h8OWlkJ|X=TgkU*94Gy$XqD!7&K0C4P!?}}nFDsg zt}CaClp-aMfRt9{T$~JA6qTT#jN}MfpFetM%vusJI+KX((=Yy%{ZL@dm8+W1H{x(; zFUVu@-=k79w+$X1d~DmTKg9xr0+c)IW>&5E29(PT?I!db_l}1rZ=!qNfEng+r-h$g zeTrZ_Xa4WntHZ)1bhP!icX#4rMJPO%&lTG`qtBsJfe7w!isqMF=PLXDo^d&G*p8e{ zA!?qL`zg4)aBr8Amh^@{boI-#tgoYKm>5eM-+@@WjGWXQ>_zXk-}iydV4zuu=cr`J z{^Y+U#cvjVQf>{Vv=w-y_ua41@sK~?1b!zG80eNv0hJj?>$K7razFOytHw+UG&0E~a8ZcXA);aX-?rF)^~b zp3YHPb-~redQWRmUZ(2MumiwfjgG6SnP!VRI4}_dq|=r43n6@ncRHwwN>z>s1q#;0 z=AP|%YF-fy$KS0Z>S6fR*+4Acf#)#y^YS#(I~TLa_Y)Gbqv+Z9&*~e|%KEzP^p`$_ z!=pP#kM_EnY5jGtwolZsGUL!O3#!*7h-Mo~-Og8j)k;;U!ZE5B*B7)~WMPJL9b>Ol zUjf3q^U}F5iii6)b#vUX_f|ijRQdDD$xH`eMqkTUyekH)_TQss!-G7dzE+taiYlL% zgVGK6;!2a*tLYV3GaND>TY~Xe$mp=FoRot+xFEGSRh$$--6qat# zbx!r@K+UYhVh(lVTRn4=;!jJ4PA^ZtY7lL9I?x;2Rv!4%DNsF7ibCo(FNA zCDfI5GCXtJSTTDrRdRl~>6Aw>LjkkvfgPkVp^Cv6K%k~PC8W*pox@YgS!2@;PZLgb z>WG^M{KuL@DP`lv#*Hn;pBUK6x!>hLWAx|3KzDlwr=;wMrH#FYe2!hGX!)3y0Zz+ASEpc4hlf-;-G11z$65QRj)m-JQ`d`sCvEC z@Z=oh4lDGH*uba@)IOyqk(NvsLAsHKC-xRva;vQgF1JYR8Ll1hbw6hx6T-gnc-W=l zw%q9qMO(vk;!6V-e2HA+T?rLIl`UgCy8t^oUAvRgLx5qmX}v_tpVyRGHkCh|bs$ZXlPhRiCo-6W%`5w<(oyQTSVP-8(HtZzEU8!j<3*I~WMBzlX3 zGr#>M0U#Bm)~dV%KYwgu_6=;i`8g{GHc$*}a=O?jb=)d~d-TWReDd`cE33e%vX=Bf ztOFMH16AHKFf6ViOwIF{{53StTPsJ^m1d;mvX&r>m9@gC+e#5j#D$OFzfQgrP29^r z$v<7C(xQ)jBfRqnSJBU5IW0c0-t-yaCZ-CZaMsZAkEdxyr;O_37Jch|&2s1m(*Al$ z`t~-6z|Z8ViZGsD_vGY3UR!xzd*#Ye)iDRphJPkE6E7ogbKmX=2?`-^6gp-{9SyB8 z!^Ph}`2Y;Y>h_sWPb~Z<67{9KA`do!T&cPVnQKq53@#+;)@_f>)bPB$%eXTWYPd}; zoVQ=k@~7hK!YY1?8tnJytm{%gP(O&(idIYEZZ0B44b?R3G^o3mkHWuuuNR$c-I8}# zajK4qorh!fMXog1O;MAZ$mY$0J?|kv&&=oG42!6wSyN$J;Uh zpEMR-A*}K#MHyKfj(GDAQu1pD28u@VN1xA%4hAugl4fe&m@C(^&1(Ht5TUeT_U`<{ z9 zlt{Y`bMWp}91c~;QTr!GEn4jsf=_&Zf)|H>I2EpTf@{%Y+(>QO5l`}yP%hUdJ7d8LJ*AxB|o~`WLf8 zm!^Z-T(oWZgDj%4k(Jrn-(zaIsZCJYG$3_8I`kK&bp&M;x!eiV??yVLkEPoVdI^%| zDA^{oU)oVw&c9()fAX^;Tcmm;fXu(Yx^gQcSIpDhxq@ckqIfZ6V$PvG?w3Ql9bJ(AV*sr(}(H~H0#lNsDNhun0cow)T&V=7x69x zC=JKK1(R-3&J7$L>v$S^#AMQS>|10lKG0J!&w9QZ3_1ENT zcVsSnq6zIKyI)gjiZ-}b9z1fjle=o5-Z0pK3W&9G!3Kkd*34GHtfo2z?r^hWX{=Et zL5;Hsd_lTNN=5a7svle_^<}DK;faZj6zu%TAoQ;-iS_}DAN*aRZK&${TtH@(Pclkx z+y)XknlR{?0VOg`n=jN3;gwXq_v$M1rNSlF=k`dLs|Q?7X-BrJ-hTD>mXJ7Gx&7mf z*Ouj3P0`z0kt#Phc`HCsely)K7qd*3;sL7Fx1k5%OK{%dO0@*~G`!(k54#CF;czI` zKut;@2 z*>RtVA9J!Z*O0h~>E~np`HfX>J1suqCF;df^YA|b?k1&G9p-JNj0|r%uWdrWX*vg$ zbwvTH>ybGW@E#aHch*Wo%mI}i{0AOzILdHnK;;@(tNtXX6;acl8blZ69%Iec*ENAX z<>n1oVVZ4}{9vEUK2^P#AXwua4lO{m=M6<%l)n2|vh0A1MXgYMe_i*BeRb@$4)~&< zzKwKsXE}#v%{}~57nu}kI3U5u_6Y#SwEA*V?3RGRhoXlf#?|I?2;{|vOM3hY$VyW~ zKv)Cdcc`_)c5v_Qy|-^089=>V3XcQRhPIUsHrsJK@B*7hNKJh3pF)#!go6VTA!UqJFf6N;Nq}0b}_K~pa zYUJ+eo;;d-^fHCONNUm2-m8lV+NIu2Kqq8S-QHrOuL+9yA$(a z%ca?#1M*CFyYC+0kHq(;MUH=vpy~ktbdb#b)J!|O5aX;Q zXu@N3NT$JMJ4rXqFy@O&nGxFEgOmS}F<%PaD}K*nZTwMMa0Mcax-I9jU+Gq)eO60u zY|?@Ne!y6hogw#o47N5&OWtoA_it6kSMhfT64=Jaslw(t8!*(8=e2|vo}>UDENoY9 z81P<_tRD*riBtq6*b#81~C z?P-RUku~Jn3WOnkxuv>UWA&Io33}BB3`p;1jhrd=O1|4N5WIG=(Y_x(Vzs2e&EUDO zE5-wmH_*9^7C`q!oVQyJ$OQnjhVN)zTM-cq_2^VPrT=|9f{9b&{>kE?L=OTQZe>+m z!IJA222LpHH+b|^Oyhb9XWo{|Y{Xl5GKhoQMs{YGkZJB;CnfW@jI<;V?s?r7xnz>B z7&BI@YA9FOJ^?&PIMETSV99BX>WdUxWuX94_&_z-a&g!le$DR!Kf|wyXC7YeW2ke1 zb4g_H8Ya}Tx8ZEyGy+bp2f4Akuos1?B=m|;?4)~Q+g6IpV~E`5JAKJjn!xb(OXztx zy_cdq-5UzP;A+?q;uH0)jC-bmUCmNRk-Y#SNP2QT=#>kAb<0U7s=H(ytdZ&kI6@Z{ zSP!>O$rD3XwhKAC8i=dPhz(+fnrUF-xtzpjYiY6E;9?$vPl zVe<~9d4m7^u&qmYNP-K#yx%`|sz4bt>HRgl*7BzDa{bj&2>#-@j*`F=dthRON}VjH zu&Fwno_guJ>jpctM(1cId=SgGeasrbwY-z2l`|#}7*iAc*5^wUettJ4DOow+**ePI zRkb;Gq?Bx4im_bGnH2K)Nap>+G5Mmt)rMDK;WMi z9@3VCQ_Q`ffUA{vEJw0!4G9Y1op!70QZwniCUqG80(g%i>(*aoLS-UjKzAi3CGw2> zKl61WO`%{~d3?y}gN@k+_!f=+mmTeNx^FvuA>=z*x%1%BCd{V3!QTN7Sc!oD5mh)o z7348FVJ|ux3Ii}D#qlxU4X(88%LW4afEf<0(@#LYT)!EqI z(`^gCHL|*z^atJhr`P|tOOHb-9bNHDt^a!a|98JOjADuB7aCd$Bce*=WPo2MbUK;_ KPa4#pz5O48n497N literal 0 HcmV?d00001 diff --git a/pipelines/nf-atacseq/environment.yml b/pipelines/nf-atacseq/environment.yml new file mode 100644 index 0000000..8ba8147 --- /dev/null +++ b/pipelines/nf-atacseq/environment.yml @@ -0,0 +1,23 @@ +--- +# yaml-language-server: $schema=https://raw.githubusercontent.com/nf-core/modules/master/modules/environment-schema.json +# Conda environment for nf-atacseq local Python/WASP2 modules +# (wasp2_make_reads, wasp2_filter_remapped, wasp2_count_variants, wasp2_find_imbalance) +channels: + - conda-forge + - bioconda +dependencies: + - python>=3.10 + - numpy>=1.21,<2.0 + - pandas>=2.0 + - polars>=0.19 + - scipy>=1.10 + - pysam + - pybedtools + - samtools + - bcftools + - bedtools + - typer + - rich + - pip + - pip: + - wasp2==1.2.1 diff --git a/pipelines/nf-atacseq/modules.json b/pipelines/nf-atacseq/modules.json index 0d78430..9efc78d 100644 --- a/pipelines/nf-atacseq/modules.json +++ b/pipelines/nf-atacseq/modules.json @@ -1,5 +1,126 @@ { "name": "wasp2/nf-atacseq", - "homePage": "", - "repos": {} + "homePage": "https://github.com/mcvickerlab/WASP2", + "repos": { + "https://github.com/nf-core/modules.git": { + "modules": { + "nf-core": { + "bowtie2/align": { + "branch": "master", + "git_sha": "92b8df948fd8cdb223e051f5f5e414818a073ee0", + "installed_by": ["fastq_align_bowtie2"] + }, + "bowtie2/build": { + "branch": "master", + "git_sha": "447f7bc0fa41dfc2400c8cad4c0291880dc060cf", + "installed_by": ["modules"] + }, + "bwa/index": { + "branch": "master", + "git_sha": "966ba9887e2b04d89d64db06c01508873bde13b1", + "installed_by": ["modules"] + }, + "bwa/mem": { + "branch": "master", + "git_sha": "707241c72951f24fd89982c4c80c5983a4c437ef", + "installed_by": ["fastq_align_bwa"] + }, + "fastp": { + "branch": "master", + "git_sha": "a331ecfd1aa48b2b2298aab23bb4516c800e410b", + "installed_by": ["modules"] + }, + "fastqc": { + "branch": "master", + "git_sha": "3009f27c4e4b6e99da4eeebe82799e13924a4a1f", + "installed_by": ["modules"] + }, + "macs2/callpeak": { + "branch": "master", + "git_sha": "fe0ec4b67b1abd71ff9b5ece41fd5a4d8abadad5", + "installed_by": ["modules"] + }, + "multiqc": { + "branch": "master", + "git_sha": "2c73cc8fa92cf48de3da0b643fdf357a8a290b36", + "installed_by": ["modules"] + }, + "picard/markduplicates": { + "branch": "master", + "git_sha": "a631e12055f6c23ba2c942d3902b3ed1b9eed859", + "installed_by": ["bam_markduplicates_picard"] + }, + "samtools/faidx": { + "branch": "master", + "git_sha": "b2e78932ef01165fd85829513eaca29eff8e640a", + "installed_by": ["modules"] + }, + "samtools/flagstat": { + "branch": "master", + "git_sha": "1d2fbdcbca677bbe8da0f9d0d2bb7c02f2cab1c9", + "installed_by": ["bam_stats_samtools"] + }, + "samtools/idxstats": { + "branch": "master", + "git_sha": "1d2fbdcbca677bbe8da0f9d0d2bb7c02f2cab1c9", + "installed_by": ["bam_stats_samtools"] + }, + "samtools/index": { + "branch": "master", + "git_sha": "1d2fbdcbca677bbe8da0f9d0d2bb7c02f2cab1c9", + "installed_by": [ + "bam_markduplicates_picard", + "bam_sort_stats_samtools", + "fastq_align_bowtie2", + "fastq_align_bwa" + ] + }, + "samtools/sort": { + "branch": "master", + "git_sha": "5cb9a8694da0a0e550921636bb60bc8c56445fd7", + "installed_by": ["bam_sort_stats_samtools"] + }, + "samtools/stats": { + "branch": "master", + "git_sha": "fe93fde0845f907fc91ad7cc7d797930408824df", + "installed_by": ["bam_stats_samtools"] + } + } + }, + "subworkflows": { + "nf-core": { + "bam_markduplicates_picard": { + "branch": "master", + "git_sha": "a631e12055f6c23ba2c942d3902b3ed1b9eed859", + "installed_by": ["subworkflows"] + }, + "bam_sort_stats_samtools": { + "branch": "master", + "git_sha": "7ac6cbe7c17c2dad685da7f70496c8f48ea48687", + "installed_by": ["fastq_align_bowtie2", "fastq_align_bwa"] + }, + "bam_stats_samtools": { + "branch": "master", + "git_sha": "7ac6cbe7c17c2dad685da7f70496c8f48ea48687", + "installed_by": [ + "bam_markduplicates_picard", + "bam_sort_stats_samtools", + "fastq_align_bowtie2", + "fastq_align_bwa" + ] + }, + "fastq_align_bowtie2": { + "branch": "master", + "git_sha": "9afa0584136287aa20fc18296f45f103c0c4e69a", + "installed_by": ["subworkflows"] + }, + "fastq_align_bwa": { + "branch": "master", + "git_sha": "9afa0584136287aa20fc18296f45f103c0c4e69a", + "installed_by": ["subworkflows"] + } + } + } + } + } } diff --git a/pipelines/nf-atacseq/modules/local/wasp2_make_reads/main.nf b/pipelines/nf-atacseq/modules/local/wasp2_make_reads/main.nf index 7d0245e..998477e 100644 --- a/pipelines/nf-atacseq/modules/local/wasp2_make_reads/main.nf +++ b/pipelines/nf-atacseq/modules/local/wasp2_make_reads/main.nf @@ -65,6 +65,12 @@ process WASP2_MAKE_READS { [ -f "\$f" ] && [ "\$f" != "${prefix}_keep.bam" ] && mv "\$f" ${prefix}_keep.bam && break done + # Update JSON to reflect renamed files + bam_prefix=\$(basename ${bam} .bam) + if [ "\$bam_prefix" != "${prefix}" ]; then + sed -i "s|\$bam_prefix|${prefix}|g" ${prefix}_wasp_data_files.json + fi + # Validate outputs for expected in ${prefix}_remap_r1.fq.gz ${prefix}_remap_r2.fq.gz ${prefix}_to_remap.bam ${prefix}_keep.bam ${prefix}_wasp_data_files.json; do if [ ! -f "\$expected" ]; then diff --git a/pipelines/nf-atacseq/modules/nf-core/bowtie2/align/environment.yml b/pipelines/nf-atacseq/modules/nf-core/bowtie2/align/environment.yml new file mode 100644 index 0000000..066ff52 --- /dev/null +++ b/pipelines/nf-atacseq/modules/nf-core/bowtie2/align/environment.yml @@ -0,0 +1,13 @@ +--- +# yaml-language-server: $schema=https://raw.githubusercontent.com/nf-core/modules/master/modules/environment-schema.json +channels: + - conda-forge + - bioconda +dependencies: + # renovate: datasource=conda depName=bioconda/bowtie2 + - bioconda::bowtie2=2.5.4 + # renovate: datasource=conda depName=bioconda/htslib + - bioconda::htslib=1.21 + # renovate: datasource=conda depName=bioconda/samtools + - bioconda::samtools=1.21 + - conda-forge::pigz=2.8 diff --git a/pipelines/nf-atacseq/modules/nf-core/bowtie2/align/main.nf b/pipelines/nf-atacseq/modules/nf-core/bowtie2/align/main.nf index f2a1dd9..0a8c1a0 100644 --- a/pipelines/nf-atacseq/modules/nf-core/bowtie2/align/main.nf +++ b/pipelines/nf-atacseq/modules/nf-core/bowtie2/align/main.nf @@ -2,62 +2,105 @@ process BOWTIE2_ALIGN { tag "$meta.id" label 'process_high' - conda "bioconda::bowtie2=2.5.2 bioconda::samtools=1.19" + conda "${moduleDir}/environment.yml" container "${ workflow.containerEngine == 'singularity' && !task.ext.singularity_pull_docker_container ? - 'https://depot.galaxyproject.org/singularity/mulled-v2-ac74a7f02cebcfcc07d8e8d1d750af9c83b4d45a:f70b31a2db15c023d641c32f433fb02cd04df5a6' : - 'biocontainers/mulled-v2-ac74a7f02cebcfcc07d8e8d1d750af9c83b4d45a:f70b31a2db15c023d641c32f433fb02cd04df5a6' }" + 'https://community-cr-prod.seqera.io/docker/registry/v2/blobs/sha256/b4/b41b403e81883126c3227fc45840015538e8e2212f13abc9ae84e4b98891d51c/data' : + 'community.wave.seqera.io/library/bowtie2_htslib_samtools_pigz:edeb13799090a2a6' }" input: - tuple val(meta), path(reads) - path index - path fasta + tuple val(meta) , path(reads) + tuple val(meta2), path(index) + tuple val(meta3), path(fasta) val save_unaligned val sort_bam output: - tuple val(meta), path("*.bam"), emit: aligned - tuple val(meta), path("*.log"), emit: log - path "versions.yml", emit: versions + tuple val(meta), path("*.sam") , emit: sam , optional:true + tuple val(meta), path("*.bam") , emit: bam , optional:true + tuple val(meta), path("*.cram") , emit: cram , optional:true + tuple val(meta), path("*.csi") , emit: csi , optional:true + tuple val(meta), path("*.crai") , emit: crai , optional:true + tuple val(meta), path("*.log") , emit: log + tuple val(meta), path("*fastq.gz") , emit: fastq , optional:true + tuple val("${task.process}"), val('bowtie2'), eval("bowtie2 --version 2>&1 | sed -n '1s/.*bowtie2-align-s version //p'"), emit: versions_bowtie2, topic: versions + tuple val("${task.process}"), val('samtools'), eval("samtools version | sed '1!d;s/.* //'"), emit: versions_samtools, topic: versions + tuple val("${task.process}"), val('pigz'), eval("pigz --version 2>&1 | sed 's/pigz //'"), emit: versions_pigz, topic: versions when: task.ext.when == null || task.ext.when script: - def args = task.ext.args ?: '' - def args2 = task.ext.args2 ?: '' + def args = task.ext.args ?: "" + def args2 = task.ext.args2 ?: "" def prefix = task.ext.prefix ?: "${meta.id}" + def rg = args.contains("--rg-id") ? "" : "--rg-id ${prefix} --rg SM:${prefix}" - def read_inputs = meta.single_end ? "-U ${reads}" : "-1 ${reads[0]} -2 ${reads[1]}" - def samtools_command = sort_bam ? "samtools sort -@ ${task.cpus} -o ${prefix}.bam -" : "samtools view -@ ${task.cpus} -bS -o ${prefix}.bam -" + def unaligned = "" + def reads_args = "" + if (meta.single_end) { + unaligned = save_unaligned ? "--un-gz ${prefix}.unmapped.fastq.gz" : "" + reads_args = "-U ${reads}" + } else { + unaligned = save_unaligned ? "--un-conc-gz ${prefix}.unmapped.fastq.gz" : "" + reads_args = "-1 ${reads[0]} -2 ${reads[1]}" + } + + def samtools_command = sort_bam ? 'sort' : 'view' + def extension_pattern = /(--output-fmt|-O)+\s+(\S+)/ + def extension_matcher = (args2 =~ extension_pattern) + def extension = extension_matcher.getCount() > 0 ? extension_matcher[0][2].toLowerCase() : "bam" + def reference = fasta && extension=="cram" ? "--reference ${fasta}" : "" + if (!fasta && extension=="cram") error "Fasta reference is required for CRAM output" """ - INDEX=`find -L ./ -name "*.1.bt2" | sed 's/\\.1.bt2\$//'` + INDEX=`find -L ./ -name "*.rev.1.bt2" | sed "s/\\.rev.1.bt2\$//"` + [ -z "\$INDEX" ] && INDEX=`find -L ./ -name "*.rev.1.bt2l" | sed "s/\\.rev.1.bt2l\$//"` + [ -z "\$INDEX" ] && echo "Bowtie2 index files not found" 1>&2 && exit 1 bowtie2 \\ - $args \\ - --threads $task.cpus \\ -x \$INDEX \\ - $read_inputs \\ - 2> ${prefix}.bowtie2.log \\ - | $samtools_command - - cat <<-END_VERSIONS > versions.yml - "${task.process}": - bowtie2: \$(bowtie2 --version | head -n1 | sed 's/.*version //') - samtools: \$(samtools --version | head -n1 | sed 's/samtools //') - END_VERSIONS + $reads_args \\ + --threads $task.cpus \\ + $unaligned \\ + $rg \\ + $args \\ + 2>| >(tee ${prefix}.bowtie2.log >&2) \\ + | samtools $samtools_command $args2 --threads $task.cpus ${reference} -o ${prefix}.${extension} - + + if [ -f ${prefix}.unmapped.fastq.1.gz ]; then + mv ${prefix}.unmapped.fastq.1.gz ${prefix}.unmapped_1.fastq.gz + fi + + if [ -f ${prefix}.unmapped.fastq.2.gz ]; then + mv ${prefix}.unmapped.fastq.2.gz ${prefix}.unmapped_2.fastq.gz + fi """ stub: + def args2 = task.ext.args2 ?: "" def prefix = task.ext.prefix ?: "${meta.id}" + def extension_pattern = /(--output-fmt|-O)+\s+(\S+)/ + def extension = (args2 ==~ extension_pattern) ? (args2 =~ extension_pattern)[0][2].toLowerCase() : "bam" + def create_unmapped = "" + if (meta.single_end) { + create_unmapped = save_unaligned ? "touch ${prefix}.unmapped.fastq.gz" : "" + } else { + create_unmapped = save_unaligned ? "touch ${prefix}.unmapped_1.fastq.gz && touch ${prefix}.unmapped_2.fastq.gz" : "" + } + if (!fasta && extension=="cram") error "Fasta reference is required for CRAM output" + + def create_index = "" + if (extension == "cram") { + create_index = "touch ${prefix}.crai" + } else if (extension == "bam") { + create_index = "touch ${prefix}.csi" + } + """ - touch ${prefix}.bam + touch ${prefix}.${extension} + ${create_index} touch ${prefix}.bowtie2.log - - cat <<-END_VERSIONS > versions.yml - "${task.process}": - bowtie2: 2.5.2 - samtools: 1.19 - END_VERSIONS + ${create_unmapped} """ + } diff --git a/pipelines/nf-atacseq/modules/nf-core/bowtie2/align/meta.yml b/pipelines/nf-atacseq/modules/nf-core/bowtie2/align/meta.yml new file mode 100644 index 0000000..2d8051d --- /dev/null +++ b/pipelines/nf-atacseq/modules/nf-core/bowtie2/align/meta.yml @@ -0,0 +1,192 @@ +name: bowtie2_align +description: Align reads to a reference genome using bowtie2 +keywords: + - align + - map + - fasta + - fastq + - genome + - reference +tools: + - bowtie2: + description: | + Bowtie 2 is an ultrafast and memory-efficient tool for aligning + sequencing reads to long reference sequences. + homepage: http://bowtie-bio.sourceforge.net/bowtie2/index.shtml + documentation: http://bowtie-bio.sourceforge.net/bowtie2/manual.shtml + doi: 10.1186/gb-2009-10-3-r25 + licence: ["GPL-3.0-or-later"] + identifier: "" +input: + - - meta: + type: map + description: | + Groovy Map containing sample information + e.g. [ id:'test', single_end:false ] + - reads: + type: file + description: | + List of input FastQ files of size 1 and 2 for single-end and paired-end data, + respectively. + ontologies: [] + - - meta2: + type: map + description: | + Groovy Map containing reference information + e.g. [ id:'test', single_end:false ] + - index: + type: file + description: Bowtie2 genome index files + pattern: "*.ebwt" + ontologies: [] + - - meta3: + type: map + description: | + Groovy Map containing reference information + e.g. [ id:'test', single_end:false ] + - fasta: + type: file + description: Bowtie2 genome fasta file + pattern: "*.fasta" + ontologies: [] + - save_unaligned: + type: boolean + description: | + Save reads that do not map to the reference (true) or discard them (false) + (default: false) + - sort_bam: + type: boolean + description: use samtools sort (true) or samtools view (false) + pattern: "true or false" +output: + sam: + - - meta: + type: map + description: Groovy Map containing sample information + - "*.sam": + type: file + description: Output SAM file containing read alignments + pattern: "*.sam" + ontologies: [] + bam: + - - meta: + type: map + description: Groovy Map containing sample information + - "*.bam": + type: file + description: Output BAM file containing read alignments + pattern: "*.bam" + ontologies: [] + cram: + - - meta: + type: map + description: Groovy Map containing sample information + - "*.cram": + type: file + description: Output CRAM file containing read alignments + pattern: "*.cram" + ontologies: [] + csi: + - - meta: + type: map + description: Groovy Map containing sample information + - "*.csi": + type: file + description: Output SAM/BAM index for large inputs + pattern: "*.csi" + ontologies: [] + crai: + - - meta: + type: map + description: Groovy Map containing sample information + - "*.crai": + type: file + description: Output CRAM index + pattern: "*.crai" + ontologies: [] + log: + - - meta: + type: map + description: Groovy Map containing sample information + - "*.log": + type: file + description: Alignment log + pattern: "*.log" + ontologies: [] + fastq: + - - meta: + type: map + description: Groovy Map containing sample information + - "*fastq.gz": + type: file + description: Unaligned FastQ files + pattern: "*.fastq.gz" + ontologies: + - edam: http://edamontology.org/format_3989 # GZIP format + versions_bowtie2: + - - ${task.process}: + type: string + description: The name of the process + - bowtie2: + type: string + description: The name of the tool + - "bowtie2 --version 2>&1 | sed -n '1s/.*bowtie2-align-s version //p'": + type: eval + description: The expression to obtain the version of bowtie2 + versions_samtools: + - - ${task.process}: + type: string + description: The name of the process + - samtools: + type: string + description: The name of the tool + - "samtools version | sed '1!d;s/.* //'": + type: eval + description: The expression to obtain the version of samtools + versions_pigz: + - - ${task.process}: + type: string + description: The name of the process + - pigz: + type: string + description: The name of the tool + - "pigz --version 2>&1 | sed 's/pigz //'": + type: eval + description: The expression to obtain the version of pigz + +topics: + versions: + - - ${task.process}: + type: string + description: The name of the process + - bowtie2: + type: string + description: The name of the tool + - "bowtie2 --version 2>&1 | sed -n '1s/.*bowtie2-align-s version //p'": + type: eval + description: The expression to obtain the version of bowtie2 + - - ${task.process}: + type: string + description: The name of the process + - samtools: + type: string + description: The name of the tool + - "samtools version | sed '1!d;s/.* //'": + type: eval + description: The expression to obtain the version of samtools + - - ${task.process}: + type: string + description: The name of the process + - pigz: + type: string + description: The name of the tool + - "pigz --version 2>&1 | sed 's/pigz //'": + type: eval + description: The expression to obtain the version of pigz + +authors: + - "@joseespinosa" + - "@drpatelh" +maintainers: + - "@joseespinosa" + - "@drpatelh" diff --git a/pipelines/nf-atacseq/modules/nf-core/bowtie2/align/tests/cram_crai.config b/pipelines/nf-atacseq/modules/nf-core/bowtie2/align/tests/cram_crai.config new file mode 100644 index 0000000..03f1d5e --- /dev/null +++ b/pipelines/nf-atacseq/modules/nf-core/bowtie2/align/tests/cram_crai.config @@ -0,0 +1,5 @@ +process { + withName: BOWTIE2_ALIGN { + ext.args2 = '--output-fmt cram --write-index' + } +} diff --git a/pipelines/nf-atacseq/modules/nf-core/bowtie2/align/tests/large_index.config b/pipelines/nf-atacseq/modules/nf-core/bowtie2/align/tests/large_index.config new file mode 100644 index 0000000..b2f0c40 --- /dev/null +++ b/pipelines/nf-atacseq/modules/nf-core/bowtie2/align/tests/large_index.config @@ -0,0 +1,5 @@ +process { + withName: BOWTIE2_BUILD { + ext.args = '--large-index' + } +} diff --git a/pipelines/nf-atacseq/modules/nf-core/bowtie2/align/tests/main.nf.test b/pipelines/nf-atacseq/modules/nf-core/bowtie2/align/tests/main.nf.test new file mode 100644 index 0000000..1705b66 --- /dev/null +++ b/pipelines/nf-atacseq/modules/nf-core/bowtie2/align/tests/main.nf.test @@ -0,0 +1,623 @@ +nextflow_process { + + name "Test Process BOWTIE2_ALIGN" + script "../main.nf" + process "BOWTIE2_ALIGN" + tag "modules" + tag "modules_nfcore" + tag "bowtie2" + tag "bowtie2/build" + tag "bowtie2/align" + + test("sarscov2 - fastq, index, fasta, false, false - bam") { + + setup { + run("BOWTIE2_BUILD") { + script "../../build/main.nf" + process { + """ + input[0] = [ + [ id:'test'], + file(params.modules_testdata_base_path + 'genomics/sarscov2/genome/genome.fasta', checkIfExists: true) + ] + """ + } + } + } + + 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] = BOWTIE2_BUILD.out.index + input[2] = [[ id:'test'], file(params.modules_testdata_base_path + 'genomics/sarscov2/genome/genome.fasta', checkIfExists: true)] + input[3] = false //save_unaligned + input[4] = false //sort + """ + } + } + + then { + assertAll ( + { assert process.success }, + { assert snapshot( + file(process.out.bam[0][1]).name, + process.out.log, + process.out.fastq, + process.out.findAll { key, val -> key.startsWith('versions') } + ).match() } + ) + } + + } + + test("sarscov2 - fastq, index, fasta, false, false - sam") { + + config "./sam.config" + setup { + run("BOWTIE2_BUILD") { + script "../../build/main.nf" + process { + """ + input[0] = [ + [ id:'test'], + file(params.modules_testdata_base_path + 'genomics/sarscov2/genome/genome.fasta', checkIfExists: true) + ] + """ + } + } + } + + 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] = BOWTIE2_BUILD.out.index + input[2] = [[ id:'test'], file(params.modules_testdata_base_path + 'genomics/sarscov2/genome/genome.fasta', checkIfExists: true)] + input[3] = false //save_unaligned + input[4] = false //sort + """ + } + } + + then { + assertAll ( + { assert process.success }, + { assert snapshot( + file(process.out.sam[0][1]).readLines()[0..4], + process.out.log, + process.out.fastq, + process.out.findAll { key, val -> key.startsWith('versions') } + ).match() } + ) + } + + } + + test("sarscov2 - fastq, index, fasta, false, false - sam2") { + + config "./sam2.config" + setup { + run("BOWTIE2_BUILD") { + script "../../build/main.nf" + process { + """ + input[0] = [ + [ id:'test'], + file(params.modules_testdata_base_path + 'genomics/sarscov2/genome/genome.fasta', checkIfExists: true) + ] + """ + } + } + } + + 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] = BOWTIE2_BUILD.out.index + input[2] = [[ id:'test'], file(params.modules_testdata_base_path + 'genomics/sarscov2/genome/genome.fasta', checkIfExists: true)] + input[3] = false //save_unaligned + input[4] = false //sort + """ + } + } + + then { + assertAll ( + { assert process.success }, + { assert snapshot( + file(process.out.sam[0][1]).readLines()[0..4], + process.out.log, + process.out.fastq, + process.out.findAll { key, val -> key.startsWith('versions') } + ).match() } + ) + } + + } + + test("sarscov2 - fastq, index, fasta, false, true - bam") { + + setup { + run("BOWTIE2_BUILD") { + script "../../build/main.nf" + process { + """ + input[0] = [ + [ id:'test'], + file(params.modules_testdata_base_path + 'genomics/sarscov2/genome/genome.fasta', checkIfExists: true) + ] + """ + } + } + } + + 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] = BOWTIE2_BUILD.out.index + input[2] = [[ id:'test'], file(params.modules_testdata_base_path + 'genomics/sarscov2/genome/genome.fasta', checkIfExists: true)] + input[3] = false //save_unaligned + input[4] = false //sort + """ + } + } + + then { + assertAll ( + { assert process.success }, + { assert snapshot( + file(process.out.bam[0][1]).name, + process.out.log, + process.out.fastq, + process.out.findAll { key, val -> key.startsWith('versions') } + ).match() } + ) + } + + } + + test("sarscov2 - [fastq1, fastq2], index, fasta, false, false - bam") { + + setup { + run("BOWTIE2_BUILD") { + script "../../build/main.nf" + process { + """ + input[0] = [ + [ id:'test'], + file(params.modules_testdata_base_path + 'genomics/sarscov2/genome/genome.fasta', checkIfExists: true) + ] + """ + } + } + } + + 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] = BOWTIE2_BUILD.out.index + input[2] = [[ id:'test'], file(params.modules_testdata_base_path + 'genomics/sarscov2/genome/genome.fasta', checkIfExists: true)] + input[3] = false //save_unaligned + input[4] = false //sort + """ + } + } + + then { + assertAll ( + { assert process.success }, + { assert snapshot( + file(process.out.bam[0][1]).name, + process.out.log, + process.out.fastq, + process.out.findAll { key, val -> key.startsWith('versions') } + ).match() } + ) + } + + } + + test("sarscov2 - [fastq1, fastq2], index, fasta, false, true - bam") { + + setup { + run("BOWTIE2_BUILD") { + script "../../build/main.nf" + process { + """ + input[0] = [ + [ id:'test'], + file(params.modules_testdata_base_path + 'genomics/sarscov2/genome/genome.fasta', checkIfExists: true) + ] + """ + } + } + } + + 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] = BOWTIE2_BUILD.out.index + input[2] = [[ id:'test'], file(params.modules_testdata_base_path + 'genomics/sarscov2/genome/genome.fasta', checkIfExists: true)] + input[3] = false //save_unaligned + input[4] = false //sort + """ + } + } + + then { + assertAll ( + { assert process.success }, + { assert snapshot( + file(process.out.bam[0][1]).name, + process.out.log, + process.out.fastq, + process.out.findAll { key, val -> key.startsWith('versions') } + ).match() } + ) + } + + } + + test("sarscov2 - fastq, large_index, fasta, false, false - bam") { + + config "./large_index.config" + setup { + run("BOWTIE2_BUILD") { + script "../../build/main.nf" + process { + """ + input[0] = [ + [ id:'test'], + file(params.modules_testdata_base_path + 'genomics/sarscov2/genome/genome.fasta', checkIfExists: true) + ] + """ + } + } + } + + 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] = BOWTIE2_BUILD.out.index + input[2] = [[ id:'test'], file(params.modules_testdata_base_path + 'genomics/sarscov2/genome/genome.fasta', checkIfExists: true)] + input[3] = false //save_unaligned + input[4] = false //sort + """ + } + } + + then { + assertAll ( + { assert process.success }, + { assert snapshot( + file(process.out.bam[0][1]).name, + process.out.log, + process.out.fastq, + process.out.findAll { key, val -> key.startsWith('versions') } + ).match() } + ) + } + + } + + test("sarscov2 - [fastq1, fastq2], large_index, fasta, false, false - bam") { + + config "./large_index.config" + setup { + run("BOWTIE2_BUILD") { + script "../../build/main.nf" + process { + """ + input[0] = [ + [ id:'test'], + file(params.modules_testdata_base_path + 'genomics/sarscov2/genome/genome.fasta', checkIfExists: true) + ] + """ + } + } + } + + 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] = BOWTIE2_BUILD.out.index + input[2] = [[ id:'test'], file(params.modules_testdata_base_path + 'genomics/sarscov2/genome/genome.fasta', checkIfExists: true)] + input[3] = false //save_unaligned + input[4] = false //sort + """ + } + } + + then { + assertAll ( + { assert process.success }, + { assert snapshot( + file(process.out.bam[0][1]).name, + process.out.log, + process.out.fastq, + process.out.findAll { key, val -> key.startsWith('versions') } + ).match() } + ) + } + + } + + test("sarscov2 - [fastq1, fastq2], index, fasta, true, false - bam") { + + setup { + run("BOWTIE2_BUILD") { + script "../../build/main.nf" + process { + """ + input[0] = [ + [ id:'test'], + file(params.modules_testdata_base_path + 'genomics/sarscov2/genome/genome.fasta', checkIfExists: true) + ] + """ + } + } + } + + 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] = BOWTIE2_BUILD.out.index + input[2] = [[ id:'test'], file(params.modules_testdata_base_path + 'genomics/sarscov2/genome/genome.fasta', checkIfExists: true)] + input[3] = false //save_unaligned + input[4] = false //sort + """ + } + } + + then { + assertAll ( + { assert process.success }, + { assert snapshot( + file(process.out.bam[0][1]).name, + process.out.log, + process.out.fastq, + process.out.findAll { key, val -> key.startsWith('versions') } + ).match() } + ) + } + + } + + test("sarscov2 - fastq, index, fasta, true, false - bam") { + + setup { + run("BOWTIE2_BUILD") { + script "../../build/main.nf" + process { + """ + input[0] = [ + [ id:'test'], + file(params.modules_testdata_base_path + 'genomics/sarscov2/genome/genome.fasta', checkIfExists: true) + ] + """ + } + } + } + + 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] = BOWTIE2_BUILD.out.index + input[2] = [[ id:'test'], file(params.modules_testdata_base_path + 'genomics/sarscov2/genome/genome.fasta', checkIfExists: true)] + input[3] = false //save_unaligned + input[4] = false //sort + """ + } + } + + then { + assertAll ( + { assert process.success }, + { assert snapshot( + file(process.out.bam[0][1]).name, + process.out.log, + process.out.fastq, + process.out.findAll { key, val -> key.startsWith('versions') } + ).match() } + + ) + } + + } + + test("sarscov2 - [fastq1, fastq2], index, fasta, true, true - cram") { + + config "./cram_crai.config" + setup { + run("BOWTIE2_BUILD") { + script "../../build/main.nf" + process { + """ + input[0] = [ + [ id:'test'], + file(params.modules_testdata_base_path + 'genomics/sarscov2/genome/genome.fasta', checkIfExists: true) + ] + """ + } + } + } + + 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] = BOWTIE2_BUILD.out.index + input[2] = [[ id:'test'], file(params.modules_testdata_base_path + 'genomics/sarscov2/genome/genome.fasta', checkIfExists: true)] + input[3] = false //save_unaligned + input[4] = true //sort + """ + } + } + + then { + assertAll ( + { assert process.success }, + { assert snapshot( + file(process.out.cram[0][1]).name, + file(process.out.crai[0][1]).name + ).match() } + ) + } + + } + + test("sarscov2 - [fastq1, fastq2], index, fasta, false, false - stub") { + + options "-stub" + setup { + run("BOWTIE2_BUILD") { + script "../../build/main.nf" + process { + """ + input[0] = [ + [ id:'test'], + file(params.modules_testdata_base_path + 'genomics/sarscov2/genome/genome.fasta', checkIfExists: true) + ] + """ + } + } + } + + 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] = BOWTIE2_BUILD.out.index + input[2] = [[ id:'test'], file(params.modules_testdata_base_path + 'genomics/sarscov2/genome/genome.fasta', checkIfExists: true)] + input[3] = false //save_unaligned + input[4] = false //sort + """ + } + } + + then { + assertAll ( + { assert process.success }, + { assert snapshot( + file(process.out.bam[0][1]).name, + file(process.out.csi[0][1]).name, + file(process.out.log[0][1]).name, + process.out.fastq, + process.out.findAll { key, val -> key.startsWith('versions') } + ).match() } + ) + } + + } + + test("sarscov2 - fastq, index, fasta, true, false - stub") { + + options "-stub" + setup { + run("BOWTIE2_BUILD") { + script "../../build/main.nf" + process { + """ + input[0] = [ + [ id:'test'], + file(params.modules_testdata_base_path + 'genomics/sarscov2/genome/genome.fasta', checkIfExists: true) + ] + """ + } + } + } + + 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] = BOWTIE2_BUILD.out.index + input[2] = [[ id:'test'], file(params.modules_testdata_base_path + 'genomics/sarscov2/genome/genome.fasta', checkIfExists: true)] + input[3] = false //save_unaligned + input[4] = false //sort + """ + } + } + + then { + assertAll ( + { assert process.success }, + { assert snapshot( + file(process.out.bam[0][1]).name, + file(process.out.csi[0][1]).name, + file(process.out.log[0][1]).name, + process.out.fastq, + process.out.findAll { key, val -> key.startsWith('versions') } + ).match() } + ) + } + + } + +} diff --git a/pipelines/nf-atacseq/modules/nf-core/bowtie2/align/tests/main.nf.test.snap b/pipelines/nf-atacseq/modules/nf-core/bowtie2/align/tests/main.nf.test.snap new file mode 100644 index 0000000..b1df41e --- /dev/null +++ b/pipelines/nf-atacseq/modules/nf-core/bowtie2/align/tests/main.nf.test.snap @@ -0,0 +1,551 @@ +{ + "sarscov2 - [fastq1, fastq2], large_index, fasta, false, false - bam": { + "content": [ + "test.bam", + [ + [ + { + "id": "test", + "single_end": false + }, + "test.bowtie2.log:md5,bd89ce1b28c93bf822bae391ffcedd19" + ] + ], + [ + + ], + { + "versions_bowtie2": [ + [ + "BOWTIE2_ALIGN", + "bowtie2", + "2.5.4" + ] + ], + "versions_pigz": [ + [ + "BOWTIE2_ALIGN", + "pigz", + "2.8" + ] + ], + "versions_samtools": [ + [ + "BOWTIE2_ALIGN", + "samtools", + "1.21" + ] + ] + } + ], + "meta": { + "nf-test": "0.9.3", + "nextflow": "25.10.3" + }, + "timestamp": "2026-02-03T15:18:12.706444258" + }, + "sarscov2 - fastq, index, fasta, false, false - sam2": { + "content": [ + [ + "ERR5069949.2151832\t16\tMT192765.1\t17453\t42\t150M\t*\t0\t0\tACGCACATTGCTAACTAAGGGCACACTAGAACCAGAATATTTCAATTCAGTGTGTAGACTTATGAAAACTATAGGTCCAGACATGTTCCTCGGAACTTGTCGGCGTTGTCCTGCTGAAATTGTTGACACTGTGAGTGCTTTGGTTTATGA\tAAAA&1 | sed -n "s/^Version: //p"'), topic: versions, emit: versions_bwa when: task.ext.when == null || task.ext.when script: - def args = task.ext.args ?: '' + def prefix = task.ext.prefix ?: "${fasta.baseName}" + def args = task.ext.args ?: '' """ mkdir bwa - bwa index $args -p bwa/${fasta.baseName} $fasta - - cat <<-END_VERSIONS > versions.yml - "${task.process}": - bwa: \$(bwa 2>&1 | grep -o 'Version: [0-9.]*' | sed 's/Version: //') - END_VERSIONS + bwa \\ + index \\ + $args \\ + -p bwa/${prefix} \\ + $fasta """ stub: + def prefix = task.ext.prefix ?: "${fasta.baseName}" """ mkdir bwa - touch bwa/${fasta.baseName}.amb - touch bwa/${fasta.baseName}.ann - touch bwa/${fasta.baseName}.bwt - touch bwa/${fasta.baseName}.pac - touch bwa/${fasta.baseName}.sa - - cat <<-END_VERSIONS > versions.yml - "${task.process}": - bwa: 0.7.18 - END_VERSIONS + touch bwa/${prefix}.amb + touch bwa/${prefix}.ann + touch bwa/${prefix}.bwt + touch bwa/${prefix}.pac + touch bwa/${prefix}.sa """ } diff --git a/pipelines/nf-atacseq/modules/nf-core/bwa/index/meta.yml b/pipelines/nf-atacseq/modules/nf-core/bwa/index/meta.yml new file mode 100644 index 0000000..f5bf7f5 --- /dev/null +++ b/pipelines/nf-atacseq/modules/nf-core/bwa/index/meta.yml @@ -0,0 +1,71 @@ +name: bwa_index +description: Create BWA index for reference genome +keywords: + - index + - fasta + - genome + - reference +tools: + - bwa: + description: | + BWA is a software package for mapping DNA sequences against + a large reference genome, such as the human genome. + homepage: http://bio-bwa.sourceforge.net/ + documentation: https://bio-bwa.sourceforge.net/bwa.shtml + arxiv: arXiv:1303.3997 + licence: ["GPL-3.0-or-later"] + identifier: "biotools:bwa" +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 + 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 ] + - bwa: + type: map + description: | + Groovy Map containing reference information. + e.g. [ id:'test', single_end:false ] + pattern: "*.{amb,ann,bwt,pac,sa}" + ontologies: + - edam: "http://edamontology.org/data_3210" # Genome index + versions_bwa: + - - ${task.process}: + type: string + description: The process the versions were collected from + - bwa: + type: string + description: The tool name + - 'bwa 2>&1 | sed -n "s/^Version: //p"': + type: string + description: The command used to generate the version of the tool +topics: + versions: + - - ${task.process}: + type: string + description: The process the versions were collected from + - bwa: + type: string + description: The tool name + - 'bwa 2>&1 | sed -n "s/^Version: //p"': + type: string + description: The command used to generate the version of the tool +authors: + - "@drpatelh" + - "@maxulysse" +maintainers: + - "@maxulysse" + - "@gallvp" diff --git a/pipelines/nf-atacseq/modules/nf-core/bwa/index/tests/main.nf.test b/pipelines/nf-atacseq/modules/nf-core/bwa/index/tests/main.nf.test new file mode 100644 index 0000000..f0fba82 --- /dev/null +++ b/pipelines/nf-atacseq/modules/nf-core/bwa/index/tests/main.nf.test @@ -0,0 +1,57 @@ +nextflow_process { + + name "Test Process BWA_INDEX" + tag "modules_nfcore" + tag "modules" + tag "bwa" + tag "bwa/index" + script "../main.nf" + process "BWA_INDEX" + + test("BWA index") { + + when { + process { + """ + input[0] = [ + [id: 'test'], + file(params.modules_testdata_base_path + 'genomics/sarscov2/genome/genome.fasta', checkIfExists: true) + ] + """ + } + } + + then { + assert process.success + assertAll( + { assert snapshot(process.out).match() } + ) + } + + } + + test("BWA index - stub") { + + options "-stub" + + when { + process { + """ + input[0] = [ + [id: 'test'], + file(params.modules_testdata_base_path + 'genomics/sarscov2/genome/genome.fasta', checkIfExists: true) + ] + """ + } + } + + then { + assert process.success + assertAll( + { assert snapshot(process.out).match() } + ) + } + + } + +} diff --git a/pipelines/nf-atacseq/modules/nf-core/bwa/index/tests/main.nf.test.snap b/pipelines/nf-atacseq/modules/nf-core/bwa/index/tests/main.nf.test.snap new file mode 100644 index 0000000..21a6f73 --- /dev/null +++ b/pipelines/nf-atacseq/modules/nf-core/bwa/index/tests/main.nf.test.snap @@ -0,0 +1,108 @@ +{ + "BWA index - stub": { + "content": [ + { + "0": [ + [ + { + "id": "test" + }, + [ + "genome.amb:md5,d41d8cd98f00b204e9800998ecf8427e", + "genome.ann:md5,d41d8cd98f00b204e9800998ecf8427e", + "genome.bwt:md5,d41d8cd98f00b204e9800998ecf8427e", + "genome.pac:md5,d41d8cd98f00b204e9800998ecf8427e", + "genome.sa:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ] + ], + "1": [ + [ + "BWA_INDEX", + "bwa", + "0.7.19-r1273" + ] + ], + "index": [ + [ + { + "id": "test" + }, + [ + "genome.amb:md5,d41d8cd98f00b204e9800998ecf8427e", + "genome.ann:md5,d41d8cd98f00b204e9800998ecf8427e", + "genome.bwt:md5,d41d8cd98f00b204e9800998ecf8427e", + "genome.pac:md5,d41d8cd98f00b204e9800998ecf8427e", + "genome.sa:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ] + ], + "versions_bwa": [ + [ + "BWA_INDEX", + "bwa", + "0.7.19-r1273" + ] + ] + } + ], + "meta": { + "nf-test": "0.9.3", + "nextflow": "25.10.2" + }, + "timestamp": "2026-01-23T16:58:59.966558606" + }, + "BWA index": { + "content": [ + { + "0": [ + [ + { + "id": "test" + }, + [ + "genome.amb:md5,3a68b8b2287e07dd3f5f95f4344ba76e", + "genome.ann:md5,c32e11f6c859f166c7525a9c1d583567", + "genome.bwt:md5,0469c30a1e239dd08f68afe66fde99da", + "genome.pac:md5,983e3d2cd6f36e2546e6d25a0da78d66", + "genome.sa:md5,ab3952cabf026b48cd3eb5bccbb636d1" + ] + ] + ], + "1": [ + [ + "BWA_INDEX", + "bwa", + "0.7.19-r1273" + ] + ], + "index": [ + [ + { + "id": "test" + }, + [ + "genome.amb:md5,3a68b8b2287e07dd3f5f95f4344ba76e", + "genome.ann:md5,c32e11f6c859f166c7525a9c1d583567", + "genome.bwt:md5,0469c30a1e239dd08f68afe66fde99da", + "genome.pac:md5,983e3d2cd6f36e2546e6d25a0da78d66", + "genome.sa:md5,ab3952cabf026b48cd3eb5bccbb636d1" + ] + ] + ], + "versions_bwa": [ + [ + "BWA_INDEX", + "bwa", + "0.7.19-r1273" + ] + ] + } + ], + "meta": { + "nf-test": "0.9.3", + "nextflow": "25.10.2" + }, + "timestamp": "2026-01-23T16:58:53.330725134" + } +} \ No newline at end of file diff --git a/pipelines/nf-atacseq/modules/nf-core/bwa/mem/environment.yml b/pipelines/nf-atacseq/modules/nf-core/bwa/mem/environment.yml new file mode 100644 index 0000000..54e6794 --- /dev/null +++ b/pipelines/nf-atacseq/modules/nf-core/bwa/mem/environment.yml @@ -0,0 +1,13 @@ +--- +# yaml-language-server: $schema=https://raw.githubusercontent.com/nf-core/modules/master/modules/environment-schema.json +channels: + - conda-forge + - bioconda + +dependencies: + # renovate: datasource=conda depName=bioconda/bwa + - bioconda::bwa=0.7.19 + # renovate: datasource=conda depName=bioconda/htslib + - bioconda::htslib=1.22.1 + # renovate: datasource=conda depName=bioconda/samtools + - bioconda::samtools=1.22.1 diff --git a/pipelines/nf-atacseq/modules/nf-core/bwa/mem/main.nf b/pipelines/nf-atacseq/modules/nf-core/bwa/mem/main.nf index c94299b..e373267 100644 --- a/pipelines/nf-atacseq/modules/nf-core/bwa/mem/main.nf +++ b/pipelines/nf-atacseq/modules/nf-core/bwa/mem/main.nf @@ -2,59 +2,62 @@ process BWA_MEM { tag "$meta.id" label 'process_high' - conda "bioconda::bwa=0.7.18 bioconda::samtools=1.19" + conda "${moduleDir}/environment.yml" container "${ workflow.containerEngine == 'singularity' && !task.ext.singularity_pull_docker_container ? - 'https://depot.galaxyproject.org/singularity/mulled-v2-fe8faa35dbf6dc65a0f7f5d4ea12e31a79f73e40:219b6c272b25e7e642ae3571' : - 'biocontainers/mulled-v2-fe8faa35dbf6dc65a0f7f5d4ea12e31a79f73e40:219b6c272b25e7e642ae3571' }" + 'https://community-cr-prod.seqera.io/docker/registry/v2/blobs/sha256/d7/d7e24dc1e4d93ca4d3a76a78d4c834a7be3985b0e1e56fddd61662e047863a8a/data' : + 'community.wave.seqera.io/library/bwa_htslib_samtools:83b50ff84ead50d0' }" input: - tuple val(meta), path(reads) - path index - path fasta + tuple val(meta) , path(reads) + tuple val(meta2), path(index) + tuple val(meta3), path(fasta) val sort_bam output: - tuple val(meta), path("*.bam"), emit: bam - path "versions.yml", emit: versions + tuple val(meta), path("*.bam") , emit: bam, optional: true + tuple val(meta), path("*.cram") , emit: cram, optional: true + tuple val(meta), path("*.csi") , emit: csi, optional: true + tuple val(meta), path("*.crai") , emit: crai, optional: true + tuple val("${task.process}"), val('bwa'), eval('bwa 2>&1 | sed -n "s/^Version: //p"'), topic: versions, emit: versions_bwa + tuple val("${task.process}"), val('samtools'), eval("samtools version | sed '1!d;s/.* //'"), topic: versions, emit: versions_samtools when: task.ext.when == null || task.ext.when script: - def args = task.ext.args ?: '' + def args = task.ext.args ?: '' def args2 = task.ext.args2 ?: '' def prefix = task.ext.prefix ?: "${meta.id}" - def read_group = "@RG\\tID:${meta.id}\\tSM:${meta.id}\\tPL:ILLUMINA" - - def samtools_command = sort_bam ? "samtools sort -@ ${task.cpus} -o ${prefix}.bam -" : "samtools view -@ ${task.cpus} $args2 -o ${prefix}.bam -" - + def samtools_command = sort_bam ? 'sort' : 'view' + def extension = args2.contains("--output-fmt sam") ? "sam" : + args2.contains("--output-fmt cram") ? "cram": + sort_bam && args2.contains("-O cram")? "cram": + !sort_bam && args2.contains("-C") ? "cram": + "bam" + def reference = fasta && extension=="cram" ? "--reference ${fasta}" : "" + if (!fasta && extension=="cram") error "Fasta reference is required for CRAM output" """ INDEX=`find -L ./ -name "*.amb" | sed 's/\\.amb\$//'` bwa mem \\ $args \\ - -R "$read_group" \\ -t $task.cpus \\ \$INDEX \\ $reads \\ - | $samtools_command - - cat <<-END_VERSIONS > versions.yml - "${task.process}": - bwa: \$(bwa 2>&1 | grep -o 'Version: [0-9.]*' | sed 's/Version: //') - samtools: \$(samtools --version | head -n1 | sed 's/samtools //') - END_VERSIONS + | samtools $samtools_command $args2 ${reference} --threads $task.cpus -o ${prefix}.${extension} - """ stub: + def args2 = task.ext.args2 ?: '' def prefix = task.ext.prefix ?: "${meta.id}" + def extension = args2.contains("--output-fmt sam") ? "sam" : + args2.contains("--output-fmt cram") ? "cram": + sort_bam && args2.contains("-O cram")? "cram": + !sort_bam && args2.contains("-C") ? "cram": + "bam" """ - touch ${prefix}.bam - - cat <<-END_VERSIONS > versions.yml - "${task.process}": - bwa: 0.7.18 - samtools: 1.19 - END_VERSIONS + touch ${prefix}.${extension} + touch ${prefix}.csi + touch ${prefix}.crai """ } diff --git a/pipelines/nf-atacseq/modules/nf-core/bwa/mem/meta.yml b/pipelines/nf-atacseq/modules/nf-core/bwa/mem/meta.yml new file mode 100644 index 0000000..450a3fe --- /dev/null +++ b/pipelines/nf-atacseq/modules/nf-core/bwa/mem/meta.yml @@ -0,0 +1,149 @@ +name: bwa_mem +description: Performs fastq alignment to a fasta reference using BWA +keywords: + - mem + - bwa + - alignment + - map + - fastq + - bam + - sam +tools: + - bwa: + description: | + BWA is a software package for mapping DNA sequences against + a large reference genome, such as the human genome. + homepage: http://bio-bwa.sourceforge.net/ + documentation: https://bio-bwa.sourceforge.net/bwa.shtml + arxiv: arXiv:1303.3997 + licence: + - "GPL-3.0-or-later" + identifier: "biotools:bwa" +input: + - - meta: + type: map + description: | + Groovy Map containing sample information + e.g. [ id:'test', single_end:false ] + - reads: + type: file + description: | + List of input FastQ files of size 1 and 2 for single-end and paired-end data, + respectively. + ontologies: + - edam: "http://edamontology.org/data_2044" + - edam: "http://edamontology.org/format_1930" + - - meta2: + type: map + description: | + Groovy Map containing reference information. + e.g. [ id:'test', single_end:false ] + - index: + type: file + description: BWA genome index files + pattern: "Directory containing BWA index *.{amb,ann,bwt,pac,sa}" + ontologies: + - edam: "http://edamontology.org/data_3210" + - - meta3: + type: map + description: | + Groovy Map containing sample information + e.g. [ id:'test', single_end:false ] + - fasta: + type: file + description: Reference genome in FASTA format + pattern: "*.{fasta,fa}" + ontologies: + - edam: "http://edamontology.org/data_2044" + - edam: "http://edamontology.org/format_1929" + - sort_bam: + type: boolean + description: use samtools sort (true) or samtools view (false) + pattern: "true or false" +output: + bam: + - - meta: + type: map + description: Groovy Map containing sample information + - "*.bam": + type: file + description: Output BAM file containing read alignments + pattern: "*.{bam}" + ontologies: + - edam: "http://edamontology.org/format_2572" + cram: + - - meta: + type: map + description: Groovy Map containing sample information + - "*.cram": + type: file + description: Output CRAM file containing read alignments + pattern: "*.{cram}" + ontologies: + - edam: "http://edamontology.org/format_3462" + csi: + - - meta: + type: map + description: Groovy Map containing sample information + - "*.csi": + type: file + description: Optional index file for BAM file + pattern: "*.{csi}" + ontologies: [] + crai: + - - meta: + type: map + description: Groovy Map containing sample information + - "*.crai": + type: file + description: Optional index file for CRAM file + pattern: "*.{crai}" + ontologies: [] + versions_bwa: + - - ${task.process}: + type: string + description: The name of the process + - bwa: + type: string + description: The name of the tool + - 'bwa 2>&1 | sed -n "s/^Version: //p"': + type: eval + description: The expression to obtain the version of the tool + versions_samtools: + - - ${task.process}: + type: string + description: The name of the process + - samtools: + type: string + description: The name of the tool + - samtools version | sed '1!d;s/.* //': + type: eval + description: The expression to obtain the version of the tool +topics: + versions: + - - ${task.process}: + type: string + description: The name of the process + - bwa: + type: string + description: The name of the tool + - 'bwa 2>&1 | sed -n "s/^Version: //p"': + type: eval + description: The expression to obtain the version of the tool + - - ${task.process}: + type: string + description: The name of the process + - samtools: + type: string + description: The name of the tool + - samtools version | sed '1!d;s/.* //': + type: eval + description: The expression to obtain the version of the tool +authors: + - "@drpatelh" + - "@jeremy1805" + - "@matthdsm" +maintainers: + - "@drpatelh" + - "@jeremy1805" + - "@matthdsm" diff --git a/pipelines/nf-atacseq/modules/nf-core/bwa/mem/tests/main.nf.test b/pipelines/nf-atacseq/modules/nf-core/bwa/mem/tests/main.nf.test new file mode 100644 index 0000000..6486ab0 --- /dev/null +++ b/pipelines/nf-atacseq/modules/nf-core/bwa/mem/tests/main.nf.test @@ -0,0 +1,255 @@ +nextflow_process { + + name "Test Process BWA_MEM" + tag "modules_nfcore" + tag "modules" + tag "bwa" + tag "bwa/mem" + tag "bwa/index" + script "../main.nf" + process "BWA_MEM" + + setup { + run("BWA_INDEX") { + script "../../index/main.nf" + process { + """ + input[0] = [ + [id: 'test'], + file(params.modules_testdata_base_path + 'genomics/sarscov2/genome/genome.fasta', checkIfExists: true) + ] + """ + } + } + } + + 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) + ] + ] + input[1] = BWA_INDEX.out.index + input[2] = [[id: 'test'],file(params.modules_testdata_base_path + 'genomics/sarscov2/genome/genome.fasta', checkIfExists: true)] + input[3] = false + """ + } + } + + then { + assertAll( + { assert process.success }, + { assert snapshot( + process.out.cram, + process.out.csi, + process.out.crai, + process.out.findAll { key, val -> key.startsWith("versions") }, + bam(process.out.bam[0][1]).getReadsMD5() + ).match() + } + ) + } + + } + + test("Single-End Sort") { + + 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] = BWA_INDEX.out.index + input[2] = [[id: 'test'],file(params.modules_testdata_base_path + 'genomics/sarscov2/genome/genome.fasta', checkIfExists: true)] + input[3] = true + """ + } + } + + then { + assertAll( + { assert process.success }, + { assert snapshot( + process.out.cram, + process.out.csi, + process.out.crai, + process.out.findAll { key, val -> key.startsWith("versions") }, + bam(process.out.bam[0][1]).getReadsMD5() + ).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) + ] + ] + input[1] = BWA_INDEX.out.index + input[2] = [[id: 'test'],file(params.modules_testdata_base_path + 'genomics/sarscov2/genome/genome.fasta', checkIfExists: true)] + input[3] = false + """ + } + } + + then { + assertAll( + { assert process.success }, + { assert snapshot( + process.out.cram, + process.out.csi, + process.out.crai, + process.out.findAll { key, val -> key.startsWith("versions") }, + bam(process.out.bam[0][1]).getReadsMD5() + ).match() + } + ) + } + + } + + test("Paired-End Sort") { + + 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] = BWA_INDEX.out.index + input[2] = [[id: 'test'],file(params.modules_testdata_base_path + 'genomics/sarscov2/genome/genome.fasta', checkIfExists: true)] + input[3] = true + """ + } + } + + then { + assertAll( + { assert process.success }, + { assert snapshot( + process.out.cram, + process.out.csi, + process.out.crai, + process.out.findAll { key, val -> key.startsWith("versions") }, + bam(process.out.bam[0][1]).getReadsMD5() + ).match() + } + ) + } + + } + + test("Paired-End - no fasta") { + + 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] = BWA_INDEX.out.index + input[2] = [[:],[]] + input[3] = false + """ + } + } + + then { + assertAll( + { assert process.success }, + { assert snapshot( + process.out.cram, + process.out.csi, + process.out.crai, + process.out.findAll { key, val -> key.startsWith("versions") }, + bam(process.out.bam[0][1]).getReadsMD5() + ).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) + ] + ] + input[1] = BWA_INDEX.out.index + input[2] = [[id: 'test'],file(params.modules_testdata_base_path + 'genomics/sarscov2/genome/genome.fasta', checkIfExists: true)] + input[3] = false + """ + } + } + + then { + assertAll( + { assert process.success }, + { assert snapshot(process.out).match() } + ) + } + } + + test("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) + ] + ] + input[1] = BWA_INDEX.out.index + input[2] = [[id: 'test'],file(params.modules_testdata_base_path + 'genomics/sarscov2/genome/genome.fasta', checkIfExists: true)] + input[3] = false + """ + } + } + + then { + assertAll( + { assert process.success }, + { assert snapshot(process.out).match() } + ) + } + } +} diff --git a/pipelines/nf-atacseq/modules/nf-core/bwa/mem/tests/main.nf.test.snap b/pipelines/nf-atacseq/modules/nf-core/bwa/mem/tests/main.nf.test.snap new file mode 100644 index 0000000..8aca4b2 --- /dev/null +++ b/pipelines/nf-atacseq/modules/nf-core/bwa/mem/tests/main.nf.test.snap @@ -0,0 +1,375 @@ +{ + "Single-End": { + "content": [ + [ + + ], + [ + + ], + [ + + ], + { + "versions_bwa": [ + [ + "BWA_MEM", + "bwa", + "0.7.19-r1273" + ] + ], + "versions_samtools": [ + [ + "BWA_MEM", + "samtools", + "1.22.1" + ] + ] + }, + "798439cbd7fd81cbcc5078022dc5479d" + ], + "timestamp": "2026-02-18T12:42:52.901827", + "meta": { + "nf-test": "0.9.4", + "nextflow": "25.10.4" + } + }, + "Single-End Sort": { + "content": [ + [ + + ], + [ + + ], + [ + + ], + { + "versions_bwa": [ + [ + "BWA_MEM", + "bwa", + "0.7.19-r1273" + ] + ], + "versions_samtools": [ + [ + "BWA_MEM", + "samtools", + "1.22.1" + ] + ] + }, + "94fcf617f5b994584c4e8d4044e16b4f" + ], + "timestamp": "2026-02-18T12:43:01.149915", + "meta": { + "nf-test": "0.9.4", + "nextflow": "25.10.4" + } + }, + "Paired-End": { + "content": [ + [ + + ], + [ + + ], + [ + + ], + { + "versions_bwa": [ + [ + "BWA_MEM", + "bwa", + "0.7.19-r1273" + ] + ], + "versions_samtools": [ + [ + "BWA_MEM", + "samtools", + "1.22.1" + ] + ] + }, + "57aeef88ed701a8ebc8e2f0a381b2a6" + ], + "timestamp": "2026-02-18T12:43:09.528042", + "meta": { + "nf-test": "0.9.4", + "nextflow": "25.10.4" + } + }, + "Paired-End Sort": { + "content": [ + [ + + ], + [ + + ], + [ + + ], + { + "versions_bwa": [ + [ + "BWA_MEM", + "bwa", + "0.7.19-r1273" + ] + ], + "versions_samtools": [ + [ + "BWA_MEM", + "samtools", + "1.22.1" + ] + ] + }, + "af8628d9df18b2d3d4f6fd47ef2bb872" + ], + "timestamp": "2026-02-18T12:43:17.876121", + "meta": { + "nf-test": "0.9.4", + "nextflow": "25.10.4" + } + }, + "Single-end - stub": { + "content": [ + { + "0": [ + [ + { + "id": "test", + "single_end": true + }, + "test.bam:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "1": [ + + ], + "2": [ + [ + { + "id": "test", + "single_end": true + }, + "test.csi:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "3": [ + [ + { + "id": "test", + "single_end": true + }, + "test.crai:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "4": [ + [ + "BWA_MEM", + "bwa", + "0.7.19-r1273" + ] + ], + "5": [ + [ + "BWA_MEM", + "samtools", + "1.22.1" + ] + ], + "bam": [ + [ + { + "id": "test", + "single_end": true + }, + "test.bam:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "crai": [ + [ + { + "id": "test", + "single_end": true + }, + "test.crai:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "cram": [ + + ], + "csi": [ + [ + { + "id": "test", + "single_end": true + }, + "test.csi:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "versions_bwa": [ + [ + "BWA_MEM", + "bwa", + "0.7.19-r1273" + ] + ], + "versions_samtools": [ + [ + "BWA_MEM", + "samtools", + "1.22.1" + ] + ] + } + ], + "timestamp": "2026-02-18T12:43:33.853248", + "meta": { + "nf-test": "0.9.4", + "nextflow": "25.10.4" + } + }, + "Paired-End - no fasta": { + "content": [ + [ + + ], + [ + + ], + [ + + ], + { + "versions_bwa": [ + [ + "BWA_MEM", + "bwa", + "0.7.19-r1273" + ] + ], + "versions_samtools": [ + [ + "BWA_MEM", + "samtools", + "1.22.1" + ] + ] + }, + "57aeef88ed701a8ebc8e2f0a381b2a6" + ], + "timestamp": "2026-02-18T12:43:26.121474", + "meta": { + "nf-test": "0.9.4", + "nextflow": "25.10.4" + } + }, + "Paired-end - stub": { + "content": [ + { + "0": [ + [ + { + "id": "test", + "single_end": false + }, + "test.bam:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "1": [ + + ], + "2": [ + [ + { + "id": "test", + "single_end": false + }, + "test.csi:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "3": [ + [ + { + "id": "test", + "single_end": false + }, + "test.crai:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "4": [ + [ + "BWA_MEM", + "bwa", + "0.7.19-r1273" + ] + ], + "5": [ + [ + "BWA_MEM", + "samtools", + "1.22.1" + ] + ], + "bam": [ + [ + { + "id": "test", + "single_end": false + }, + "test.bam:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "crai": [ + [ + { + "id": "test", + "single_end": false + }, + "test.crai:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "cram": [ + + ], + "csi": [ + [ + { + "id": "test", + "single_end": false + }, + "test.csi:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "versions_bwa": [ + [ + "BWA_MEM", + "bwa", + "0.7.19-r1273" + ] + ], + "versions_samtools": [ + [ + "BWA_MEM", + "samtools", + "1.22.1" + ] + ] + } + ], + "timestamp": "2026-02-18T12:43:42.119907", + "meta": { + "nf-test": "0.9.4", + "nextflow": "25.10.4" + } + } +} \ No newline at end of file diff --git a/pipelines/nf-atacseq/modules/nf-core/fastp/environment.yml b/pipelines/nf-atacseq/modules/nf-core/fastp/environment.yml new file mode 100644 index 0000000..0c36eed --- /dev/null +++ b/pipelines/nf-atacseq/modules/nf-core/fastp/environment.yml @@ -0,0 +1,8 @@ +--- +# yaml-language-server: $schema=https://raw.githubusercontent.com/nf-core/modules/master/modules/environment-schema.json +channels: + - conda-forge + - bioconda +dependencies: + # renovate: datasource=conda depName=bioconda/fastp + - bioconda::fastp=1.0.1 diff --git a/pipelines/nf-atacseq/modules/nf-core/fastp/main.nf b/pipelines/nf-atacseq/modules/nf-core/fastp/main.nf index c9b1380..e13509c 100644 --- a/pipelines/nf-atacseq/modules/nf-core/fastp/main.nf +++ b/pipelines/nf-atacseq/modules/nf-core/fastp/main.nf @@ -2,23 +2,25 @@ process FASTP { tag "$meta.id" label 'process_medium' - conda "bioconda::fastp=0.23.4" + conda "${moduleDir}/environment.yml" container "${ workflow.containerEngine == 'singularity' && !task.ext.singularity_pull_docker_container ? - 'https://depot.galaxyproject.org/singularity/fastp:0.23.4--h5f740d0_0' : - 'biocontainers/fastp:0.23.4--h5f740d0_0' }" + 'https://community-cr-prod.seqera.io/docker/registry/v2/blobs/sha256/52/527b18847a97451091dba07a886b24f17f742a861f9f6c9a6bfb79d4f1f3bf9d/data' : + 'community.wave.seqera.io/library/fastp:1.0.1--c8b87fe62dcc103c' }" input: - tuple val(meta), path(reads) - path adapter_fasta + 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'), emit: reads - tuple val(meta), path('*.json'), emit: json - tuple val(meta), path('*.html'), emit: html - tuple val(meta), path('*.log'), emit: log - path "versions.yml", emit: versions + 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 @@ -27,58 +29,76 @@ process FASTP { def args = task.ext.args ?: '' def prefix = task.ext.prefix ?: "${meta.id}" def adapter_list = adapter_fasta ? "--adapter_fasta ${adapter_fasta}" : "" - - if (meta.single_end) { + 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 \\ - --in1 ${reads[0]} \\ - --out1 ${prefix}.fastp.fastq.gz \\ + --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) + 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 - cat <<-END_VERSIONS > versions.yml - "${task.process}": - fastp: \$(fastp --version 2>&1 | sed 's/fastp //') - END_VERSIONS + 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 ${reads[0]} \\ - --in2 ${reads[1]} \\ - --out1 ${prefix}_1.fastp.fastq.gz \\ - --out2 ${prefix}_2.fastp.fastq.gz \\ - --thread $task.cpus \\ + --in1 ${prefix}_R1.fastq.gz \\ + --in2 ${prefix}_R2.fastq.gz \\ + $out_fq1 \\ + $out_fq2 \\ --json ${prefix}.fastp.json \\ --html ${prefix}.fastp.html \\ - --detect_adapter_for_pe \\ $adapter_list \\ + $fail_fastq \\ + $merge_fastq \\ + --thread $task.cpus \\ + --detect_adapter_for_pe \\ $args \\ - 2> >(tee ${prefix}.fastp.log >&2) - - cat <<-END_VERSIONS > versions.yml - "${task.process}": - fastp: \$(fastp --version 2>&1 | sed 's/fastp //') - END_VERSIONS + 2>| >(tee ${prefix}.fastp.log >&2) """ } stub: - def prefix = task.ext.prefix ?: "${meta.id}" + 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 ${prefix}_1.fastp.fastq.gz - touch ${prefix}_2.fastp.fastq.gz - echo '{}' > ${prefix}.fastp.json - touch ${prefix}.fastp.html - touch ${prefix}.fastp.log - - cat <<-END_VERSIONS > versions.yml - "${task.process}": - fastp: 0.23.4 - END_VERSIONS + $touch_reads + $touch_fail_fastq + $touch_merged + touch "${prefix}.fastp.json" + touch "${prefix}.fastp.html" + touch "${prefix}.fastp.log" """ } diff --git a/pipelines/nf-atacseq/modules/nf-core/fastp/meta.yml b/pipelines/nf-atacseq/modules/nf-core/fastp/meta.yml new file mode 100644 index 0000000..a67be39 --- /dev/null +++ b/pipelines/nf-atacseq/modules/nf-core/fastp/meta.yml @@ -0,0 +1,144 @@ +name: fastp +description: Perform adapter/quality trimming on sequencing reads +keywords: + - trimming + - quality control + - fastq +tools: + - fastp: + description: | + A tool designed to provide fast all-in-one preprocessing for FastQ files. This tool is developed in C++ with multithreading supported to afford high performance. + documentation: https://github.com/OpenGene/fastp + doi: 10.1093/bioinformatics/bty560 + licence: ["MIT"] + identifier: biotools:fastp +input: + - - meta: + type: map + description: | + Groovy Map containing sample information. Use 'single_end: true' to specify single ended or interleaved FASTQs. Use 'single_end: false' for paired-end reads. + e.g. [ id:'test', single_end:false ] + - reads: + type: file + description: | + List of input FastQ files of size 1 and 2 for single-end and paired-end data, + respectively. If you wish to run interleaved paired-end data, supply as single-end data + but with `--interleaved_in` in your `modules.conf`'s `ext.args` for the module. + ontologies: [] + - adapter_fasta: + type: file + description: File in FASTA format containing possible adapters to remove. + pattern: "*.{fasta,fna,fas,fa}" + ontologies: [] + - discard_trimmed_pass: + type: boolean + description: | + Specify true to not write any reads that pass trimming thresholds. + This can be used to use fastp for the output report only. + - save_trimmed_fail: + type: boolean + description: Specify true to save files that failed to pass trimming thresholds + ending in `*.fail.fastq.gz` + - save_merged: + type: boolean + description: Specify true to save all merged reads to a file ending in `*.merged.fastq.gz` +output: + reads: + - - meta: + type: map + description: | + Groovy Map containing sample information + e.g. [ id:'test', single_end:false ] + - "*.fastp.fastq.gz": + type: file + description: The trimmed/modified/unmerged fastq reads + pattern: "*fastp.fastq.gz" + ontologies: + - edam: http://edamontology.org/format_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" diff --git a/pipelines/nf-atacseq/modules/nf-core/fastp/tests/main.nf.test b/pipelines/nf-atacseq/modules/nf-core/fastp/tests/main.nf.test new file mode 100644 index 0000000..b790157 --- /dev/null +++ b/pipelines/nf-atacseq/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/pipelines/nf-atacseq/modules/nf-core/fastp/tests/main.nf.test.snap b/pipelines/nf-atacseq/modules/nf-core/fastp/tests/main.nf.test.snap new file mode 100644 index 0000000..5677235 --- /dev/null +++ b/pipelines/nf-atacseq/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.0.1" + ] + ], + "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.0.1" + ] + ] + } + ], + "meta": { + "nf-test": "0.9.3", + "nextflow": "25.10.2" + }, + "timestamp": "2026-01-22T13:00:52.14535813" + }, + "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.0.1" + ] + ] + } + ], + "meta": { + "nf-test": "0.9.3", + "nextflow": "25.10.2" + }, + "timestamp": "2026-01-23T09:46:26.421773402" + }, + "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.0.1" + ] + ] + } + ], + "meta": { + "nf-test": "0.9.3", + "nextflow": "25.10.2" + }, + "timestamp": "2026-01-23T09:46:59.832295907" + }, + "test_fastp_single_end_qc_only": { + "content": [ + [ + + ], + [ + + ], + [ + + ], + [ + + ], + [ + + ], + [ + + ], + { + "versions_fastp": [ + [ + "FASTP", + "fastp", + "1.0.1" + ] + ] + } + ], + "meta": { + "nf-test": "0.9.3", + "nextflow": "25.10.2" + }, + "timestamp": "2026-01-23T09:47:06.486959565" + }, + "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.0.1" + ] + ] + } + ], + "meta": { + "nf-test": "0.9.3", + "nextflow": "25.10.2" + }, + "timestamp": "2026-01-23T09:46:46.736511024" + }, + "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.0.1" + ] + ], + "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.0.1" + ] + ] + } + ], + "meta": { + "nf-test": "0.9.3", + "nextflow": "25.10.2" + }, + "timestamp": "2026-01-22T13:00:16.097071654" + }, + "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.0.1" + ] + ], + "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.0.1" + ] + ] + } + ], + "meta": { + "nf-test": "0.9.3", + "nextflow": "25.10.2" + }, + "timestamp": "2026-01-22T13:00:03.317192706" + }, + "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.0.1" + ] + ], + "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.0.1" + ] + ] + } + ], + "meta": { + "nf-test": "0.9.3", + "nextflow": "25.10.2" + }, + "timestamp": "2026-01-22T13:00:44.851708205" + }, + "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.0.1" + ] + ], + "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.0.1" + ] + ] + } + ], + "meta": { + "nf-test": "0.9.3", + "nextflow": "25.10.2" + }, + "timestamp": "2026-01-22T13:00:37.581047713" + }, + "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.0.1" + ] + ] + } + ], + "meta": { + "nf-test": "0.9.3", + "nextflow": "25.10.2" + }, + "timestamp": "2026-01-23T09:46:53.190202914" + }, + "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.0.1" + ] + ], + "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.0.1" + ] + ] + } + ], + "meta": { + "nf-test": "0.9.3", + "nextflow": "25.10.2" + }, + "timestamp": "2026-01-22T13:00:09.585957282" + }, + "test_fastp_single_end": { + "content": [ + [ + [ + { + "id": "test", + "single_end": true + }, + "test.fastp.fastq.gz:md5,67b2bbae47f073e05a97a9c2edce23c7" + ] + ], + [ + + ], + [ + + ], + { + "versions_fastp": [ + [ + "FASTP", + "fastp", + "1.0.1" + ] + ] + } + ], + "meta": { + "nf-test": "0.9.3", + "nextflow": "25.10.2" + }, + "timestamp": "2026-01-23T09:46:19.624824985" + }, + "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.0.1" + ] + ], + "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.0.1" + ] + ] + } + ], + "meta": { + "nf-test": "0.9.3", + "nextflow": "25.10.2" + }, + "timestamp": "2026-01-22T13:00:22.800659826" + }, + "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.0.1" + ] + ], + "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.0.1" + ] + ] + } + ], + "meta": { + "nf-test": "0.9.3", + "nextflow": "25.10.2" + }, + "timestamp": "2026-01-22T13:00:30.271734068" + }, + "fastp test_fastp_interleaved": { + "content": [ + [ + [ + { + "id": "test", + "single_end": true + }, + "test.fastp.fastq.gz:md5,217d62dc13a23e92513a1bd8e1bcea39" + ] + ], + { + "versions_fastp": [ + [ + "FASTP", + "fastp", + "1.0.1" + ] + ] + } + ], + "meta": { + "nf-test": "0.9.3", + "nextflow": "25.10.2" + }, + "timestamp": "2026-01-23T09:46:33.4628687" + }, + "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.0.1" + ] + ] + } + ], + "meta": { + "nf-test": "0.9.3", + "nextflow": "25.10.2" + }, + "timestamp": "2026-01-23T09:46:39.895973372" + }, + "test_fastp_paired_end_qc_only": { + "content": [ + [ + + ], + [ + + ], + [ + + ], + [ + + ], + [ + + ], + [ + + ], + { + "versions_fastp": [ + [ + "FASTP", + "fastp", + "1.0.1" + ] + ] + } + ], + "meta": { + "nf-test": "0.9.3", + "nextflow": "25.10.2" + }, + "timestamp": "2026-01-23T09:47:13.015833707" + }, + "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.0.1" + ] + ], + "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.0.1" + ] + ] + } + ], + "meta": { + "nf-test": "0.9.3", + "nextflow": "25.10.2" + }, + "timestamp": "2026-01-22T13:00:59.670106791" + } +} \ No newline at end of file diff --git a/pipelines/nf-atacseq/modules/nf-core/fastp/tests/nextflow.interleaved.config b/pipelines/nf-atacseq/modules/nf-core/fastp/tests/nextflow.interleaved.config new file mode 100644 index 0000000..4be8dbd --- /dev/null +++ b/pipelines/nf-atacseq/modules/nf-core/fastp/tests/nextflow.interleaved.config @@ -0,0 +1,5 @@ +process { + withName: FASTP { + ext.args = "--interleaved_in -e 30" + } +} diff --git a/pipelines/nf-atacseq/modules/nf-core/fastp/tests/nextflow.save_failed.config b/pipelines/nf-atacseq/modules/nf-core/fastp/tests/nextflow.save_failed.config new file mode 100644 index 0000000..53b61b0 --- /dev/null +++ b/pipelines/nf-atacseq/modules/nf-core/fastp/tests/nextflow.save_failed.config @@ -0,0 +1,5 @@ +process { + withName: FASTP { + ext.args = "-e 30" + } +} diff --git a/pipelines/nf-atacseq/modules/nf-core/fastqc/environment.yml b/pipelines/nf-atacseq/modules/nf-core/fastqc/environment.yml new file mode 100644 index 0000000..f9f54ee --- /dev/null +++ b/pipelines/nf-atacseq/modules/nf-core/fastqc/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::fastqc=0.12.1 diff --git a/pipelines/nf-atacseq/modules/nf-core/fastqc/main.nf b/pipelines/nf-atacseq/modules/nf-core/fastqc/main.nf index 40d10a5..f562952 100644 --- a/pipelines/nf-atacseq/modules/nf-core/fastqc/main.nf +++ b/pipelines/nf-atacseq/modules/nf-core/fastqc/main.nf @@ -1,8 +1,8 @@ process FASTQC { - tag "$meta.id" - label 'process_medium' + tag "${meta.id}" + label 'process_low' - conda "bioconda::fastqc=0.12.1" + conda "${moduleDir}/environment.yml" container "${ workflow.containerEngine == 'singularity' && !task.ext.singularity_pull_docker_container ? 'https://depot.galaxyproject.org/singularity/fastqc:0.12.1--hdfd78af_0' : 'biocontainers/fastqc:0.12.1--hdfd78af_0' }" @@ -11,34 +11,44 @@ process FASTQC { tuple val(meta), path(reads) output: - tuple val(meta), path("*.html"), emit: html - tuple val(meta), path("*.zip"), emit: zip - path "versions.yml", emit: versions + tuple val(meta) , path("*.html") , emit: html + tuple val(meta) , path("*.zip") , emit: zip + tuple val("${task.process}"), val('fastqc'), eval('fastqc --version | sed "/FastQC v/!d; s/.*v//"'), emit: versions_fastqc, topic: versions when: task.ext.when == null || task.ext.when script: - def args = task.ext.args ?: '' - def prefix = task.ext.prefix ?: "${meta.id}" - """ - fastqc $args --threads $task.cpus $reads + def args = task.ext.args ?: '' + def prefix = task.ext.prefix ?: "${meta.id}" + // Make list of old name and new name pairs to use for renaming in the bash while loop + def old_new_pairs = reads instanceof Path || reads.size() == 1 ? [[ reads, "${prefix}.${reads.extension}" ]] : reads.withIndex().collect { entry, index -> [ entry, "${prefix}_${index + 1}.${entry.extension}" ] } + def rename_to = old_new_pairs*.join(' ').join(' ') + def renamed_files = old_new_pairs.collect{ _old_name, new_name -> new_name }.join(' ') + + // The total amount of allocated RAM by FastQC is equal to the number of threads defined (--threads) time the amount of RAM defined (--memory) + // https://github.com/s-andrews/FastQC/blob/1faeea0412093224d7f6a07f777fad60a5650795/fastqc#L211-L222 + // Dividing the task.memory by task.cpu allows to stick to requested amount of RAM in the label + def memory_in_mb = task.memory ? task.memory.toUnit('MB') / task.cpus : null + // FastQC memory value allowed range (100 - 10000) + def fastqc_memory = memory_in_mb > 10000 ? 10000 : (memory_in_mb < 100 ? 100 : memory_in_mb) - cat <<-END_VERSIONS > versions.yml - "${task.process}": - fastqc: \$( fastqc --version | sed '/FastQC v/!d; s/.*v//' ) - END_VERSIONS + """ + printf "%s %s\\n" ${rename_to} | while read old_name new_name; do + [ -f "\${new_name}" ] || ln -s \$old_name \$new_name + done + + fastqc \\ + ${args} \\ + --threads ${task.cpus} \\ + --memory ${fastqc_memory} \\ + ${renamed_files} """ stub: def prefix = task.ext.prefix ?: "${meta.id}" """ - touch ${prefix}_fastqc.html - touch ${prefix}_fastqc.zip - - cat <<-END_VERSIONS > versions.yml - "${task.process}": - fastqc: 0.12.1 - END_VERSIONS + touch ${prefix}.html + touch ${prefix}.zip """ } diff --git a/pipelines/nf-atacseq/modules/nf-core/fastqc/meta.yml b/pipelines/nf-atacseq/modules/nf-core/fastqc/meta.yml new file mode 100644 index 0000000..49164c8 --- /dev/null +++ b/pipelines/nf-atacseq/modules/nf-core/fastqc/meta.yml @@ -0,0 +1,111 @@ +name: fastqc +description: Run FastQC on sequenced reads +keywords: + - quality control + - qc + - adapters + - fastq +tools: + - fastqc: + description: | + FastQC gives general quality metrics about your reads. + It provides information about the quality score distribution + across your reads, the per base sequence content (%A/C/G/T). + + You get information about adapter contamination and other + overrepresented sequences. + homepage: https://www.bioinformatics.babraham.ac.uk/projects/fastqc/ + documentation: https://www.bioinformatics.babraham.ac.uk/projects/fastqc/Help/ + licence: ["GPL-2.0-only"] + identifier: biotools:fastqc +input: + - - meta: + type: map + description: | + Groovy Map containing sample information + e.g. [ id:'test', single_end:false ] + - reads: + type: file + description: | + List of input FastQ files of size 1 and 2 for single-end and paired-end data, + respectively. + ontologies: [] +output: + html: + - - meta: + type: map + description: | + Groovy Map containing sample information + e.g. [ id:'test', single_end:false ] + - "*.html": + type: file + description: FastQC report + pattern: "*_{fastqc.html}" + ontologies: [] + zip: + - - meta: + type: map + description: | + Groovy Map containing sample information + e.g. [ id:'test', single_end:false ] + - "*.zip": + type: file + description: FastQC report archive + pattern: "*_{fastqc.zip}" + ontologies: [] + versions_fastqc: + - - ${task.process}: + type: string + description: The process the versions were collected from + - fastqc: + type: string + description: The tool name + - fastqc --version | sed "/FastQC v/!d; s/.*v//": + type: eval + description: The expression to obtain the version of the tool + +topics: + versions: + - - ${task.process}: + type: string + description: The process the versions were collected from + - fastqc: + type: string + description: The tool name + - fastqc --version | sed "/FastQC v/!d; s/.*v//": + type: eval + description: The expression to obtain the version of the tool +authors: + - "@drpatelh" + - "@grst" + - "@ewels" + - "@FelixKrueger" +maintainers: + - "@drpatelh" + - "@grst" + - "@ewels" + - "@FelixKrueger" +containers: + conda: + linux_amd64: + lock_file: https://wave.seqera.io/v1alpha1/builds/bd-af7a5314d5015c29_1/condalock + linux_arm64: + lock_file: https://wave.seqera.io/v1alpha1/builds/bd-df99cb252670875a_2/condalock + docker: + linux_amd64: + build_id: bd-af7a5314d5015c29_1 + name: community.wave.seqera.io/library/fastqc:0.12.1--af7a5314d5015c29 + scanId: sc-a618548acbee5a8a_30 + linux_arm64: + build_id: bd-df99cb252670875a_2 + name: community.wave.seqera.io/library/fastqc:0.12.1--df99cb252670875a + scanId: sc-b5913ed5d42b22d2_18 + singularity: + linux_amd64: + build_id: bd-104d26ddd9519960_1 + name: oras://community.wave.seqera.io/library/fastqc:0.12.1--104d26ddd9519960 + https: https://community.wave.seqera.io/v2/library/fastqc/blobs/sha256:e0c976cb2eca5fee72618a581537a4f8ea42fcae24c9b201e2e0f764fd28648a + linux_arm64: + build_id: bd-d56b505a93aef38a_1 + name: oras://community.wave.seqera.io/library/fastqc:0.12.1--d56b505a93aef38a + https: https://community.wave.seqera.io/v2/library/fastqc/blobs/sha256:fd39534bf298698cbe3ee4d4a6f1e73330ec4bca44c38dd9a4d06cb5ea838017 diff --git a/pipelines/nf-atacseq/modules/nf-core/fastqc/tests/main.nf.test b/pipelines/nf-atacseq/modules/nf-core/fastqc/tests/main.nf.test new file mode 100644 index 0000000..66c44da --- /dev/null +++ b/pipelines/nf-atacseq/modules/nf-core/fastqc/tests/main.nf.test @@ -0,0 +1,309 @@ +nextflow_process { + + name "Test Process FASTQC" + script "../main.nf" + process "FASTQC" + + tag "modules" + tag "modules_nfcore" + tag "fastqc" + + test("sarscov2 single-end [fastq]") { + + when { + process { + """ + input[0] = Channel.of([ + [ id: 'test', single_end:true ], + [ file(params.modules_testdata_base_path + 'genomics/sarscov2/illumina/fastq/test_1.fastq.gz', checkIfExists: true) ] + ]) + """ + } + } + + then { + assertAll ( + { assert process.success }, + // NOTE The report contains the date inside it, which means that the md5sum is stable per day, but not longer than that. So you can't md5sum it. + // looks like this:
Mon 2 Oct 2023
test.gz
+ // https://github.com/nf-core/modules/pull/3903#issuecomment-1743620039 + { assert process.out.html[0][1] ==~ ".*/test_fastqc.html" }, + { assert process.out.zip[0][1] ==~ ".*/test_fastqc.zip" }, + { assert path(process.out.html[0][1]).text.contains("File typeConventional base calls") }, + { assert snapshot(sanitizeOutput(process.out).findAll { key, val -> key != 'html' && key != 'zip' }).match() } + ) + } + } + + test("sarscov2 paired-end [fastq]") { + + when { + process { + """ + input[0] = Channel.of([ + [id: 'test', single_end: false], // meta map + [ file(params.modules_testdata_base_path + 'genomics/sarscov2/illumina/fastq/test_1.fastq.gz', checkIfExists: true), + file(params.modules_testdata_base_path + 'genomics/sarscov2/illumina/fastq/test_2.fastq.gz', checkIfExists: true) ] + ]) + """ + } + } + + then { + assertAll ( + { assert process.success }, + { assert process.out.html[0][1][0] ==~ ".*/test_1_fastqc.html" }, + { assert process.out.html[0][1][1] ==~ ".*/test_2_fastqc.html" }, + { assert process.out.zip[0][1][0] ==~ ".*/test_1_fastqc.zip" }, + { assert process.out.zip[0][1][1] ==~ ".*/test_2_fastqc.zip" }, + { assert path(process.out.html[0][1][0]).text.contains("File typeConventional base calls") }, + { assert path(process.out.html[0][1][1]).text.contains("File typeConventional base calls") }, + { assert snapshot(sanitizeOutput(process.out).findAll { key, val -> key != 'html' && key != 'zip' }).match() } + ) + } + } + + test("sarscov2 interleaved [fastq]") { + + when { + process { + """ + input[0] = Channel.of([ + [id: 'test', single_end: false], // meta map + file(params.modules_testdata_base_path + 'genomics/sarscov2/illumina/fastq/test_interleaved.fastq.gz', checkIfExists: true) + ]) + """ + } + } + + then { + assertAll ( + { assert process.success }, + { assert process.out.html[0][1] ==~ ".*/test_fastqc.html" }, + { assert process.out.zip[0][1] ==~ ".*/test_fastqc.zip" }, + { assert path(process.out.html[0][1]).text.contains("File typeConventional base calls") }, + { assert snapshot(sanitizeOutput(process.out).findAll { key, val -> key != 'html' && key != 'zip' }).match() } + ) + } + } + + test("sarscov2 paired-end [bam]") { + + when { + process { + """ + input[0] = Channel.of([ + [id: 'test', single_end: false], // meta map + file(params.modules_testdata_base_path + 'genomics/sarscov2/illumina/bam/test.paired_end.sorted.bam', checkIfExists: true) + ]) + """ + } + } + + then { + assertAll ( + { assert process.success }, + { assert process.out.html[0][1] ==~ ".*/test_fastqc.html" }, + { assert process.out.zip[0][1] ==~ ".*/test_fastqc.zip" }, + { assert path(process.out.html[0][1]).text.contains("File typeConventional base calls") }, + { assert snapshot(sanitizeOutput(process.out).findAll { key, val -> key != 'html' && key != 'zip' }).match() } + ) + } + } + + test("sarscov2 multiple [fastq]") { + + when { + process { + """ + input[0] = Channel.of([ + [id: 'test', single_end: false], // meta map + [ file(params.modules_testdata_base_path + 'genomics/sarscov2/illumina/fastq/test_1.fastq.gz', checkIfExists: true), + file(params.modules_testdata_base_path + 'genomics/sarscov2/illumina/fastq/test_2.fastq.gz', checkIfExists: true), + file(params.modules_testdata_base_path + 'genomics/sarscov2/illumina/fastq/test2_1.fastq.gz', checkIfExists: true), + file(params.modules_testdata_base_path + 'genomics/sarscov2/illumina/fastq/test2_2.fastq.gz', checkIfExists: true) ] + ]) + """ + } + } + + then { + assertAll ( + { assert process.success }, + { assert process.out.html[0][1][0] ==~ ".*/test_1_fastqc.html" }, + { assert process.out.html[0][1][1] ==~ ".*/test_2_fastqc.html" }, + { assert process.out.html[0][1][2] ==~ ".*/test_3_fastqc.html" }, + { assert process.out.html[0][1][3] ==~ ".*/test_4_fastqc.html" }, + { assert process.out.zip[0][1][0] ==~ ".*/test_1_fastqc.zip" }, + { assert process.out.zip[0][1][1] ==~ ".*/test_2_fastqc.zip" }, + { assert process.out.zip[0][1][2] ==~ ".*/test_3_fastqc.zip" }, + { assert process.out.zip[0][1][3] ==~ ".*/test_4_fastqc.zip" }, + { assert path(process.out.html[0][1][0]).text.contains("File typeConventional base calls") }, + { assert path(process.out.html[0][1][1]).text.contains("File typeConventional base calls") }, + { assert path(process.out.html[0][1][2]).text.contains("File typeConventional base calls") }, + { assert path(process.out.html[0][1][3]).text.contains("File typeConventional base calls") }, + { assert snapshot(sanitizeOutput(process.out).findAll { key, val -> key != 'html' && key != 'zip' }).match() } + ) + } + } + + test("sarscov2 custom_prefix") { + + when { + process { + """ + input[0] = Channel.of([ + [ id:'mysample', single_end:true ], // meta map + file(params.modules_testdata_base_path + 'genomics/sarscov2/illumina/fastq/test_1.fastq.gz', checkIfExists: true) + ]) + """ + } + } + + then { + assertAll ( + { assert process.success }, + { assert process.out.html[0][1] ==~ ".*/mysample_fastqc.html" }, + { assert process.out.zip[0][1] ==~ ".*/mysample_fastqc.zip" }, + { assert path(process.out.html[0][1]).text.contains("File typeConventional base calls") }, + { assert snapshot(sanitizeOutput(process.out).findAll { key, val -> key != 'html' && key != 'zip' }).match() } + ) + } + } + + test("sarscov2 single-end [fastq] - stub") { + + options "-stub" + when { + process { + """ + input[0] = Channel.of([ + [ id: 'test', single_end:true ], + [ file(params.modules_testdata_base_path + 'genomics/sarscov2/illumina/fastq/test_1.fastq.gz', checkIfExists: true) ] + ]) + """ + } + } + + then { + assertAll ( + { assert process.success }, + { assert snapshot(process.out).match() } + ) + } + } + + test("sarscov2 paired-end [fastq] - stub") { + + options "-stub" + when { + process { + """ + input[0] = Channel.of([ + [id: 'test', single_end: false], // meta map + [ file(params.modules_testdata_base_path + 'genomics/sarscov2/illumina/fastq/test_1.fastq.gz', checkIfExists: true), + file(params.modules_testdata_base_path + 'genomics/sarscov2/illumina/fastq/test_2.fastq.gz', checkIfExists: true) ] + ]) + """ + } + } + + then { + assertAll ( + { assert process.success }, + { assert snapshot(process.out).match() } + ) + } + } + + test("sarscov2 interleaved [fastq] - stub") { + + options "-stub" + when { + process { + """ + input[0] = Channel.of([ + [id: 'test', single_end: false], // meta map + file(params.modules_testdata_base_path + 'genomics/sarscov2/illumina/fastq/test_interleaved.fastq.gz', checkIfExists: true) + ]) + """ + } + } + + then { + assertAll ( + { assert process.success }, + { assert snapshot(process.out).match() } + ) + } + } + + test("sarscov2 paired-end [bam] - stub") { + + options "-stub" + when { + process { + """ + input[0] = Channel.of([ + [id: 'test', single_end: false], // meta map + file(params.modules_testdata_base_path + 'genomics/sarscov2/illumina/bam/test.paired_end.sorted.bam', checkIfExists: true) + ]) + """ + } + } + + then { + assertAll ( + { assert process.success }, + { assert snapshot(process.out).match() } + ) + } + } + + test("sarscov2 multiple [fastq] - stub") { + + options "-stub" + when { + process { + """ + input[0] = Channel.of([ + [id: 'test', single_end: false], // meta map + [ file(params.modules_testdata_base_path + 'genomics/sarscov2/illumina/fastq/test_1.fastq.gz', checkIfExists: true), + file(params.modules_testdata_base_path + 'genomics/sarscov2/illumina/fastq/test_2.fastq.gz', checkIfExists: true), + file(params.modules_testdata_base_path + 'genomics/sarscov2/illumina/fastq/test2_1.fastq.gz', checkIfExists: true), + file(params.modules_testdata_base_path + 'genomics/sarscov2/illumina/fastq/test2_2.fastq.gz', checkIfExists: true) ] + ]) + """ + } + } + + then { + assertAll ( + { assert process.success }, + { assert snapshot(process.out).match() } + ) + } + } + + test("sarscov2 custom_prefix - stub") { + + options "-stub" + when { + process { + """ + input[0] = Channel.of([ + [ id:'mysample', single_end:true ], // meta map + file(params.modules_testdata_base_path + 'genomics/sarscov2/illumina/fastq/test_1.fastq.gz', checkIfExists: true) + ]) + """ + } + } + + then { + assertAll ( + { assert process.success }, + { assert snapshot(process.out).match() } + ) + } + } +} diff --git a/pipelines/nf-atacseq/modules/nf-core/fastqc/tests/main.nf.test.snap b/pipelines/nf-atacseq/modules/nf-core/fastqc/tests/main.nf.test.snap new file mode 100644 index 0000000..c8ee120 --- /dev/null +++ b/pipelines/nf-atacseq/modules/nf-core/fastqc/tests/main.nf.test.snap @@ -0,0 +1,476 @@ +{ + "sarscov2 custom_prefix": { + "content": [ + { + "versions_fastqc": [ + [ + "FASTQC", + "fastqc", + "0.12.1" + ] + ] + } + ], + "meta": { + "nf-test": "0.9.2", + "nextflow": "25.10.0" + }, + "timestamp": "2025-10-28T16:39:14.518503" + }, + "sarscov2 single-end [fastq] - stub": { + "content": [ + { + "0": [ + [ + { + "id": "test", + "single_end": true + }, + "test.html:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "1": [ + [ + { + "id": "test", + "single_end": true + }, + "test.zip:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "2": [ + [ + "FASTQC", + "fastqc", + "0.12.1" + ] + ], + "html": [ + [ + { + "id": "test", + "single_end": true + }, + "test.html:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "versions_fastqc": [ + [ + "FASTQC", + "fastqc", + "0.12.1" + ] + ], + "zip": [ + [ + { + "id": "test", + "single_end": true + }, + "test.zip:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ] + } + ], + "meta": { + "nf-test": "0.9.2", + "nextflow": "25.10.0" + }, + "timestamp": "2025-10-28T16:39:19.309008" + }, + "sarscov2 custom_prefix - stub": { + "content": [ + { + "0": [ + [ + { + "id": "mysample", + "single_end": true + }, + "mysample.html:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "1": [ + [ + { + "id": "mysample", + "single_end": true + }, + "mysample.zip:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "2": [ + [ + "FASTQC", + "fastqc", + "0.12.1" + ] + ], + "html": [ + [ + { + "id": "mysample", + "single_end": true + }, + "mysample.html:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "versions_fastqc": [ + [ + "FASTQC", + "fastqc", + "0.12.1" + ] + ], + "zip": [ + [ + { + "id": "mysample", + "single_end": true + }, + "mysample.zip:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ] + } + ], + "meta": { + "nf-test": "0.9.2", + "nextflow": "25.10.0" + }, + "timestamp": "2025-10-28T16:39:44.94888" + }, + "sarscov2 interleaved [fastq]": { + "content": [ + { + "versions_fastqc": [ + [ + "FASTQC", + "fastqc", + "0.12.1" + ] + ] + } + ], + "meta": { + "nf-test": "0.9.2", + "nextflow": "25.10.0" + }, + "timestamp": "2025-10-28T16:38:45.168496" + }, + "sarscov2 paired-end [bam]": { + "content": [ + { + "versions_fastqc": [ + [ + "FASTQC", + "fastqc", + "0.12.1" + ] + ] + } + ], + "meta": { + "nf-test": "0.9.2", + "nextflow": "25.10.0" + }, + "timestamp": "2025-10-28T16:38:53.268919" + }, + "sarscov2 multiple [fastq]": { + "content": [ + { + "versions_fastqc": [ + [ + "FASTQC", + "fastqc", + "0.12.1" + ] + ] + } + ], + "meta": { + "nf-test": "0.9.2", + "nextflow": "25.10.0" + }, + "timestamp": "2025-10-28T16:39:05.050305" + }, + "sarscov2 paired-end [fastq]": { + "content": [ + { + "versions_fastqc": [ + [ + "FASTQC", + "fastqc", + "0.12.1" + ] + ] + } + ], + "meta": { + "nf-test": "0.9.2", + "nextflow": "25.10.0" + }, + "timestamp": "2025-10-28T16:38:37.2373" + }, + "sarscov2 paired-end [fastq] - stub": { + "content": [ + { + "0": [ + [ + { + "id": "test", + "single_end": false + }, + "test.html:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "1": [ + [ + { + "id": "test", + "single_end": false + }, + "test.zip:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "2": [ + [ + "FASTQC", + "fastqc", + "0.12.1" + ] + ], + "html": [ + [ + { + "id": "test", + "single_end": false + }, + "test.html:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "versions_fastqc": [ + [ + "FASTQC", + "fastqc", + "0.12.1" + ] + ], + "zip": [ + [ + { + "id": "test", + "single_end": false + }, + "test.zip:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ] + } + ], + "meta": { + "nf-test": "0.9.2", + "nextflow": "25.10.0" + }, + "timestamp": "2025-10-28T16:39:24.450398" + }, + "sarscov2 multiple [fastq] - stub": { + "content": [ + { + "0": [ + [ + { + "id": "test", + "single_end": false + }, + "test.html:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "1": [ + [ + { + "id": "test", + "single_end": false + }, + "test.zip:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "2": [ + [ + "FASTQC", + "fastqc", + "0.12.1" + ] + ], + "html": [ + [ + { + "id": "test", + "single_end": false + }, + "test.html:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "versions_fastqc": [ + [ + "FASTQC", + "fastqc", + "0.12.1" + ] + ], + "zip": [ + [ + { + "id": "test", + "single_end": false + }, + "test.zip:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ] + } + ], + "meta": { + "nf-test": "0.9.2", + "nextflow": "25.10.0" + }, + "timestamp": "2025-10-28T16:39:39.758762" + }, + "sarscov2 single-end [fastq]": { + "content": [ + { + "versions_fastqc": [ + [ + "FASTQC", + "fastqc", + "0.12.1" + ] + ] + } + ], + "meta": { + "nf-test": "0.9.2", + "nextflow": "25.10.0" + }, + "timestamp": "2025-10-28T16:38:29.555068" + }, + "sarscov2 interleaved [fastq] - stub": { + "content": [ + { + "0": [ + [ + { + "id": "test", + "single_end": false + }, + "test.html:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "1": [ + [ + { + "id": "test", + "single_end": false + }, + "test.zip:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "2": [ + [ + "FASTQC", + "fastqc", + "0.12.1" + ] + ], + "html": [ + [ + { + "id": "test", + "single_end": false + }, + "test.html:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "versions_fastqc": [ + [ + "FASTQC", + "fastqc", + "0.12.1" + ] + ], + "zip": [ + [ + { + "id": "test", + "single_end": false + }, + "test.zip:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ] + } + ], + "meta": { + "nf-test": "0.9.2", + "nextflow": "25.10.0" + }, + "timestamp": "2025-10-28T16:39:29.193136" + }, + "sarscov2 paired-end [bam] - stub": { + "content": [ + { + "0": [ + [ + { + "id": "test", + "single_end": false + }, + "test.html:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "1": [ + [ + { + "id": "test", + "single_end": false + }, + "test.zip:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "2": [ + [ + "FASTQC", + "fastqc", + "0.12.1" + ] + ], + "html": [ + [ + { + "id": "test", + "single_end": false + }, + "test.html:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "versions_fastqc": [ + [ + "FASTQC", + "fastqc", + "0.12.1" + ] + ], + "zip": [ + [ + { + "id": "test", + "single_end": false + }, + "test.zip:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ] + } + ], + "meta": { + "nf-test": "0.9.2", + "nextflow": "25.10.0" + }, + "timestamp": "2025-10-28T16:39:34.144919" + } +} \ No newline at end of file diff --git a/pipelines/nf-atacseq/modules/nf-core/macs2/callpeak/environment.yml b/pipelines/nf-atacseq/modules/nf-core/macs2/callpeak/environment.yml new file mode 100644 index 0000000..7f8c3ca --- /dev/null +++ b/pipelines/nf-atacseq/modules/nf-core/macs2/callpeak/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: + - bioconda::macs2=2.2.9.1=py39hff71179_1 + - conda-forge::python=3.9.19 + - conda-forge::setuptools=70.0 diff --git a/pipelines/nf-atacseq/modules/nf-core/macs2/callpeak/main.nf b/pipelines/nf-atacseq/modules/nf-core/macs2/callpeak/main.nf index 709500b..730845d 100644 --- a/pipelines/nf-atacseq/modules/nf-core/macs2/callpeak/main.nf +++ b/pipelines/nf-atacseq/modules/nf-core/macs2/callpeak/main.nf @@ -2,21 +2,23 @@ process MACS2_CALLPEAK { tag "$meta.id" label 'process_medium' - conda "bioconda::macs2=2.2.9.1" + conda "${moduleDir}/environment.yml" container "${ workflow.containerEngine == 'singularity' && !task.ext.singularity_pull_docker_container ? - 'https://depot.galaxyproject.org/singularity/macs2:2.2.9.1--py39hf95cd2a_0' : - 'biocontainers/macs2:2.2.9.1--py39hf95cd2a_0' }" + 'https://depot.galaxyproject.org/singularity/macs2:2.2.9.1--py39hff71179_1': + 'biocontainers/macs2:2.2.9.1--py39hff71179_1' }" input: - tuple val(meta), path(bam) - val gsize + tuple val(meta), path(ipbam), path(controlbam) + val macs2_gsize output: - tuple val(meta), path("*.narrowPeak"), emit: peak - tuple val(meta), path("*.xls"), emit: xls - tuple val(meta), path("*.summits.bed"), emit: summits, optional: true - tuple val(meta), path("*.bdg"), emit: bedgraph, optional: true - path "versions.yml", emit: versions + tuple val(meta), path("*.{narrowPeak,broadPeak}"), emit: peak + tuple val(meta), path("*.xls") , emit: xls + path "versions.yml" , emit: versions + + tuple val(meta), path("*.gappedPeak"), optional:true, emit: gapped + tuple val(meta), path("*.bed") , optional:true, emit: bed + tuple val(meta), path("*.bdg") , optional:true, emit: bdg when: task.ext.when == null || task.ext.when @@ -24,32 +26,43 @@ process MACS2_CALLPEAK { script: def args = task.ext.args ?: '' def prefix = task.ext.prefix ?: "${meta.id}" - def format = meta.single_end ? 'BAM' : 'BAMPE' + def args_list = args.tokenize() + def format = meta.single_end ? 'BAM' : 'BAMPE' + def control = controlbam ? "--control $controlbam" : '' + if(args_list.contains('--format')){ + def id = args_list.findIndexOf{args_i -> args_i=='--format'} + format = args_list[id+1] + args_list.remove(id+1) + args_list.remove(id) + } """ - macs2 callpeak \\ - $args \\ - -g $gsize \\ - -f $format \\ - -t $bam \\ - -n $prefix \\ - --outdir . + macs2 \\ + callpeak \\ + ${args_list.join(' ')} \\ + --gsize $macs2_gsize \\ + --format $format \\ + --name $prefix \\ + --treatment $ipbam \\ + $control cat <<-END_VERSIONS > versions.yml "${task.process}": - macs2: \$(macs2 --version 2>&1 | sed 's/macs2 //') + macs2: \$(macs2 --version | sed -e "s/macs2 //g") END_VERSIONS """ stub: def prefix = task.ext.prefix ?: "${meta.id}" """ - touch ${prefix}_peaks.narrowPeak - touch ${prefix}_peaks.xls - touch ${prefix}_summits.bed + touch ${prefix}.gappedPeak + touch ${prefix}.bed + touch ${prefix}.bdg + touch ${prefix}.narrowPeak + touch ${prefix}.xls cat <<-END_VERSIONS > versions.yml "${task.process}": - macs2: 2.2.9.1 + macs3: \$(macs3 --version | sed -e "s/macs3 //g") END_VERSIONS """ } diff --git a/pipelines/nf-atacseq/modules/nf-core/macs2/callpeak/meta.yml b/pipelines/nf-atacseq/modules/nf-core/macs2/callpeak/meta.yml new file mode 100644 index 0000000..4e354fa --- /dev/null +++ b/pipelines/nf-atacseq/modules/nf-core/macs2/callpeak/meta.yml @@ -0,0 +1,106 @@ +name: macs2_callpeak +description: Peak calling of enriched genomic regions of ChIP-seq and ATAC-seq experiments +keywords: + - alignment + - atac-seq + - chip-seq + - peak-calling +tools: + - macs2: + description: Model Based Analysis for ChIP-Seq data + documentation: https://docs.csc.fi/apps/macs2/ + tool_dev_url: https://github.com/macs3-project/MACS + doi: "10.1101/496521" + licence: ["BSD"] + identifier: "" +input: + - - meta: + type: map + description: | + Groovy Map containing sample information + e.g. [ id:'test', single_end:false ] + - ipbam: + type: file + description: The ChIP-seq treatment file + ontologies: [] + - controlbam: + type: file + description: The control file + ontologies: [] + - macs2_gsize: + type: string + description: Effective genome size. It can be 1.0e+9 or 1000000000, or shortcuts:'hs' + for human (2.7e9), 'mm' for mouse (1.87e9), 'ce' for C. elegans (9e7) and 'dm' + for fruitfly (1.2e8) +output: + peak: + - - meta: + type: file + description: BED file containing annotated peaks + pattern: "*.gappedPeak,*.narrowPeak}" + ontologies: [] + - "*.{narrowPeak,broadPeak}": + type: file + description: BED file containing annotated peaks + pattern: "*.gappedPeak,*.narrowPeak}" + ontologies: [] + xls: + - - meta: + type: file + description: BED file containing annotated peaks + pattern: "*.gappedPeak,*.narrowPeak}" + ontologies: [] + - "*.xls": + type: file + description: xls file containing annotated peaks + pattern: "*.xls" + ontologies: [] + versions: + - versions.yml: + type: file + description: File containing software version + pattern: "versions.yml" + ontologies: + - edam: http://edamontology.org/format_3750 # YAML + gapped: + - - meta: + type: file + description: BED file containing annotated peaks + pattern: "*.gappedPeak,*.narrowPeak}" + ontologies: [] + - "*.gappedPeak": + type: file + description: Optional BED file containing gapped peak + pattern: "*.gappedPeak" + ontologies: [] + bed: + - - meta: + type: file + description: BED file containing annotated peaks + pattern: "*.gappedPeak,*.narrowPeak}" + ontologies: [] + - "*.bed": + type: file + description: Optional BED file containing peak summits locations for every + peak + pattern: "*.bed" + ontologies: [] + bdg: + - - meta: + type: file + description: BED file containing annotated peaks + pattern: "*.gappedPeak,*.narrowPeak}" + ontologies: [] + - "*.bdg": + type: file + description: Optional bedGraph files for input and treatment input samples + pattern: "*.bdg" + ontologies: [] +authors: + - "@ntoda03" + - "@JoseEspinosa" + - "@jianhong" +maintainers: + - "@ntoda03" + - "@JoseEspinosa" + - "@jianhong" diff --git a/pipelines/nf-atacseq/modules/nf-core/macs2/callpeak/tests/bam.config b/pipelines/nf-atacseq/modules/nf-core/macs2/callpeak/tests/bam.config new file mode 100644 index 0000000..17a7d3e --- /dev/null +++ b/pipelines/nf-atacseq/modules/nf-core/macs2/callpeak/tests/bam.config @@ -0,0 +1,5 @@ +process { + withName: 'MACS2_CALLPEAK' { + ext.args = '--qval 0.1' + } +} diff --git a/pipelines/nf-atacseq/modules/nf-core/macs2/callpeak/tests/bed.config b/pipelines/nf-atacseq/modules/nf-core/macs2/callpeak/tests/bed.config new file mode 100644 index 0000000..aeba9a9 --- /dev/null +++ b/pipelines/nf-atacseq/modules/nf-core/macs2/callpeak/tests/bed.config @@ -0,0 +1,5 @@ +process { + withName: 'MACS2_CALLPEAK' { + ext.args = '--format BED --qval 10 --nomodel --extsize 200' + } +} diff --git a/pipelines/nf-atacseq/modules/nf-core/macs2/callpeak/tests/main.nf.test b/pipelines/nf-atacseq/modules/nf-core/macs2/callpeak/tests/main.nf.test new file mode 100644 index 0000000..b9ebc06 --- /dev/null +++ b/pipelines/nf-atacseq/modules/nf-core/macs2/callpeak/tests/main.nf.test @@ -0,0 +1,125 @@ +nextflow_process { + + name "Test Process MACS2_CALLPEAK" + script "../main.nf" + process "MACS2_CALLPEAK" + + tag "modules" + tag "modules_nfcore" + tag "macs2" + tag "macs2/callpeak" + + test("homo_sapiens - callpeak - bed") { + + when { + config "./bed.config" + process { + """ + input[0] = [ + [ id:'test', single_end:false ], // meta map + [ file(params.modules_testdata_base_path + 'genomics/homo_sapiens/pacbio/bed/alz.ccs.fl.NEB_5p--NEB_Clontech_3p.flnc.clustered.singletons.merged.aligned_tc.bed', checkIfExists: true) ], + [] + ] + input[1] = 4000 + """ + } + } + + then { + assertAll( + { assert process.success }, + { assert snapshot( + sanitizeOutput(process.out), + path(process.out.versions[0]).yaml + ).match() } + ) + } + + } + + test("homo_sapiens - callpeak - bam") { + + when { + config "./bam.config" + process { + """ + input[0] = [ + [ id:'test', single_end:false ], // meta map + [ file(params.modules_testdata_base_path + 'genomics/homo_sapiens/illumina/bam/test.paired_end.name.sorted.bam', checkIfExists: true) ], + [] + ] + input[1] = 40000 + """ + } + } + + then { + assertAll( + { assert process.success }, + { assert snapshot( + sanitizeOutput(process.out), + path(process.out.versions[0]).yaml + ).match() } + ) + } + + } + + test("homo_sapiens - callpeak - control - bam") { + + when { + config "./bam.config" + process { + """ + input[0] = [ + [ id:'test', single_end:false ], // meta map + [ file(params.modules_testdata_base_path + 'genomics/homo_sapiens/illumina/bam/test.paired_end.name.sorted.bam', checkIfExists: true) ], + [ file(params.modules_testdata_base_path + 'genomics/homo_sapiens/illumina/bam/test2.paired_end.name.sorted.bam', checkIfExists: true) ] + ] + input[1] = 40000 + """ + } + } + + then { + assertAll( + { assert process.success }, + { assert snapshot( + sanitizeOutput(process.out), + path(process.out.versions[0]).yaml + ).match() } + ) + } + + } + + test("sarscov2 - bam - stub") { + + options "-stub" + + when { + process { + """ + input[0] = [ + [ id:'test', single_end:false ], // meta map + [ file(params.modules_testdata_base_path + 'genomics/sarscov2/illumina/bam/test.paired_end.sorted.bam', checkIfExists: true) ], + [] + ] + input[1] = 40000 + """ + } + } + + then { + assertAll( + { assert process.success }, + { assert snapshot( + sanitizeOutput(process.out), + path(process.out.versions[0]).yaml + ).match() } + ) + } + + } + +} diff --git a/pipelines/nf-atacseq/modules/nf-core/macs2/callpeak/tests/main.nf.test.snap b/pipelines/nf-atacseq/modules/nf-core/macs2/callpeak/tests/main.nf.test.snap new file mode 100644 index 0000000..a9aa3ce --- /dev/null +++ b/pipelines/nf-atacseq/modules/nf-core/macs2/callpeak/tests/main.nf.test.snap @@ -0,0 +1,222 @@ +{ + "homo_sapiens - callpeak - bam": { + "content": [ + { + "bdg": [ + + ], + "bed": [ + [ + { + "id": "test", + "single_end": false + }, + "test_summits.bed:md5,26f0f97b6c14dbca129e947a58067c82" + ] + ], + "gapped": [ + + ], + "peak": [ + [ + { + "id": "test", + "single_end": false + }, + "test_peaks.narrowPeak:md5,2e4da1c1704595e12aaf99cc715ad70c" + ] + ], + "versions": [ + "versions.yml:md5,ba6bf9efdccff6f86c722ce9b61ce75e" + ], + "xls": [ + [ + { + "id": "test", + "single_end": false + }, + "test_peaks.xls:md5,dd0cbdd9520b150b3dd5f7bede0d4a1e" + ] + ] + }, + { + "MACS2_CALLPEAK": { + "macs2": "2.2.9.1" + } + } + ], + "timestamp": "2026-02-16T13:06:13.194555144", + "meta": { + "nf-test": "0.9.4", + "nextflow": "25.10.4" + } + }, + "sarscov2 - bam - stub": { + "content": [ + { + "bdg": [ + [ + { + "id": "test", + "single_end": false + }, + "test.bdg:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "bed": [ + [ + { + "id": "test", + "single_end": false + }, + "test.bed:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "gapped": [ + [ + { + "id": "test", + "single_end": false + }, + "test.gappedPeak:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "peak": [ + [ + { + "id": "test", + "single_end": false + }, + "test.narrowPeak:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "versions": [ + "versions.yml:md5,b0ab3b36d39f9851effaf8b0d5cc0b92" + ], + "xls": [ + [ + { + "id": "test", + "single_end": false + }, + "test.xls:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ] + }, + { + "MACS2_CALLPEAK": { + "macs3": null + } + } + ], + "timestamp": "2026-02-16T13:06:28.267130581", + "meta": { + "nf-test": "0.9.4", + "nextflow": "25.10.4" + } + }, + "homo_sapiens - callpeak - control - bam": { + "content": [ + { + "bdg": [ + + ], + "bed": [ + [ + { + "id": "test", + "single_end": false + }, + "test_summits.bed:md5,4f3c7c53a1d730d90d1b3dd9d3197af4" + ] + ], + "gapped": [ + + ], + "peak": [ + [ + { + "id": "test", + "single_end": false + }, + "test_peaks.narrowPeak:md5,653e1108cc57ca07d0f60fc0f4fb8ba3" + ] + ], + "versions": [ + "versions.yml:md5,ba6bf9efdccff6f86c722ce9b61ce75e" + ], + "xls": [ + [ + { + "id": "test", + "single_end": false + }, + "test_peaks.xls:md5,ba5c031a290fc98828d7a3b9320863ac" + ] + ] + }, + { + "MACS2_CALLPEAK": { + "macs2": "2.2.9.1" + } + } + ], + "timestamp": "2026-02-16T13:06:20.925967923", + "meta": { + "nf-test": "0.9.4", + "nextflow": "25.10.4" + } + }, + "homo_sapiens - callpeak - bed": { + "content": [ + { + "bdg": [ + + ], + "bed": [ + [ + { + "id": "test", + "single_end": false + }, + "test_summits.bed:md5,28833eeb7816688f0d698f51670be946" + ] + ], + "gapped": [ + + ], + "peak": [ + [ + { + "id": "test", + "single_end": false + }, + "test_peaks.narrowPeak:md5,10e7d4747f8a2513e5ebb04856a51673" + ] + ], + "versions": [ + "versions.yml:md5,ba6bf9efdccff6f86c722ce9b61ce75e" + ], + "xls": [ + [ + { + "id": "test", + "single_end": false + }, + "test_peaks.xls:md5,0b7c5a46179fe9d3f61c8dbc192a3c3d" + ] + ] + }, + { + "MACS2_CALLPEAK": { + "macs2": "2.2.9.1" + } + } + ], + "timestamp": "2026-02-16T13:06:05.333162919", + "meta": { + "nf-test": "0.9.4", + "nextflow": "25.10.4" + } + } +} \ No newline at end of file diff --git a/pipelines/nf-atacseq/modules/nf-core/multiqc/environment.yml b/pipelines/nf-atacseq/modules/nf-core/multiqc/environment.yml new file mode 100644 index 0000000..009874d --- /dev/null +++ b/pipelines/nf-atacseq/modules/nf-core/multiqc/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::multiqc=1.33 diff --git a/pipelines/nf-atacseq/modules/nf-core/multiqc/main.nf b/pipelines/nf-atacseq/modules/nf-core/multiqc/main.nf index b3a9eba..5376aea 100644 --- a/pipelines/nf-atacseq/modules/nf-core/multiqc/main.nf +++ b/pipelines/nf-atacseq/modules/nf-core/multiqc/main.nf @@ -1,54 +1,50 @@ process MULTIQC { + tag "${meta.id}" label 'process_single' - conda "bioconda::multiqc=1.19" - container "${ workflow.containerEngine == 'singularity' && !task.ext.singularity_pull_docker_container ? - 'https://depot.galaxyproject.org/singularity/multiqc:1.19--pyhdfd78af_0' : - 'biocontainers/multiqc:1.19--pyhdfd78af_0' }" + conda "${moduleDir}/environment.yml" + container "${workflow.containerEngine == 'singularity' && !task.ext.singularity_pull_docker_container + ? 'https://community-cr-prod.seqera.io/docker/registry/v2/blobs/sha256/34/34e733a9ae16a27e80fe00f863ea1479c96416017f24a907996126283e7ecd4d/data' + : 'community.wave.seqera.io/library/multiqc:1.33--ee7739d47738383b'}" input: - path multiqc_files, stageAs: "?/*" - path multiqc_config - path extra_multiqc_config - path multiqc_logo + tuple val(meta), path(multiqc_files, stageAs: "?/*"), path(multiqc_config, stageAs: "?/*"), path(multiqc_logo), path(replace_names), path(sample_names) output: - path "*multiqc_report.html", emit: report - path "*_data", emit: data - path "*_plots", emit: plots, optional: true - path "versions.yml", emit: versions + tuple val(meta), path("*.html"), emit: report + tuple val(meta), path("*_data"), emit: data + tuple val(meta), path("*_plots"), emit: plots, optional: true + // MultiQC should not push its versions to the `versions` topic. Its input depends on the versions topic to be resolved thus outputting to the topic will let the pipeline hang forever + tuple val("${task.process}"), val('multiqc'), eval('multiqc --version | sed "s/.* //g"'), emit: versions when: task.ext.when == null || task.ext.when script: def args = task.ext.args ?: '' - def config = multiqc_config ? "--config $multiqc_config" : "" - def extra_config = extra_multiqc_config ? "--config $extra_multiqc_config" : "" - def logo = multiqc_logo ? "--cl-config 'custom_logo: $multiqc_logo'" : "" + def prefix = task.ext.prefix ? "--filename ${task.ext.prefix}.html" : '' + def config = multiqc_config ? multiqc_config instanceof List ? "--config ${multiqc_config.join(' --config ')}" : "--config ${multiqc_config}" : "" + def logo = multiqc_logo ? "--cl-config 'custom_logo: \"${multiqc_logo}\"'" : '' + def replace = replace_names ? "--replace-names ${replace_names}" : '' + def samples = sample_names ? "--sample-names ${sample_names}" : '' """ multiqc \\ --force \\ - $args \\ - $config \\ - $extra_config \\ - $logo \\ + ${args} \\ + ${config} \\ + ${prefix} \\ + ${logo} \\ + ${replace} \\ + ${samples} \\ . - - cat <<-END_VERSIONS > versions.yml - "${task.process}": - multiqc: \$( multiqc --version | sed 's/multiqc, version //' ) - END_VERSIONS """ stub: """ - touch multiqc_report.html mkdir multiqc_data - - cat <<-END_VERSIONS > versions.yml - "${task.process}": - multiqc: 1.19 - END_VERSIONS + touch multiqc_data/.stub + mkdir multiqc_plots + touch multiqc_plots/.stub + touch multiqc_report.html """ } diff --git a/pipelines/nf-atacseq/modules/nf-core/multiqc/meta.yml b/pipelines/nf-atacseq/modules/nf-core/multiqc/meta.yml new file mode 100644 index 0000000..ef434a9 --- /dev/null +++ b/pipelines/nf-atacseq/modules/nf-core/multiqc/meta.yml @@ -0,0 +1,133 @@ +name: multiqc +description: Aggregate results from bioinformatics analyses across many samples + into a single report +keywords: + - QC + - bioinformatics tools + - Beautiful stand-alone HTML report +tools: + - multiqc: + description: | + MultiQC searches a given directory for analysis logs and compiles a HTML report. + It's a general use tool, perfect for summarising the output from numerous bioinformatics tools. + homepage: https://multiqc.info/ + documentation: https://multiqc.info/docs/ + licence: + - "GPL-3.0-or-later" + identifier: biotools:multiqc +input: + - - meta: + type: map + description: | + Groovy Map containing sample information + e.g. [ id:'sample1', single_end:false ] + - multiqc_files: + type: file + description: | + List of reports / files recognised by MultiQC, for example the html and zip output of FastQC + ontologies: [] + - multiqc_config: + type: file + description: Optional config yml for MultiQC + pattern: "*.{yml,yaml}" + ontologies: + - edam: http://edamontology.org/format_3750 + - multiqc_logo: + type: file + description: Optional logo file for MultiQC + pattern: "*.{png}" + ontologies: [] + - replace_names: + type: file + description: | + Optional two-column sample renaming file. First column a set of + patterns, second column a set of corresponding replacements. Passed via + MultiQC's `--replace-names` option. + pattern: "*.{tsv}" + ontologies: + - edam: http://edamontology.org/format_3475 + - sample_names: + type: file + description: | + Optional TSV file with headers, passed to the MultiQC --sample_names + argument. + pattern: "*.{tsv}" + ontologies: + - edam: http://edamontology.org/format_3475 +output: + report: + - - meta: + type: map + description: | + Groovy Map containing sample information + e.g. [ id:'sample1', single_end:false ] + - "*.html": + type: file + description: MultiQC report file + pattern: ".html" + ontologies: [] + data: + - - meta: + type: map + description: | + Groovy Map containing sample information + e.g. [ id:'sample1', single_end:false ] + - "*_data": + type: directory + description: MultiQC data dir + pattern: "multiqc_data" + plots: + - - meta: + type: map + description: | + Groovy Map containing sample information + e.g. [ id:'sample1', single_end:false ] + - "*_plots": + type: file + description: Plots created by MultiQC + pattern: "*_plots" + ontologies: [] + versions: + - - ${task.process}: + type: string + description: The process the versions were collected from + - multiqc: + type: string + description: The tool name + - multiqc --version | sed "s/.* //g": + type: eval + description: The expression to obtain the version of the tool +authors: + - "@abhi18av" + - "@bunop" + - "@drpatelh" + - "@jfy133" +maintainers: + - "@abhi18av" + - "@bunop" + - "@drpatelh" + - "@jfy133" +containers: + conda: + linux/amd64: + lock_file: https://wave.seqera.io/v1alpha1/builds/bd-ee7739d47738383b_1/condalock + linux/arm64: + lock_file: https://wave.seqera.io/v1alpha1/builds/bd-58d7dee710ab3aa8_1/condalock + docker: + linux/amd64: + build_id: bd-ee7739d47738383b_1 + name: community.wave.seqera.io/library/multiqc:1.33--ee7739d47738383b + scanId: sc-6ddec592dcadd583_4 + linux/arm64: + build_id: bd-58d7dee710ab3aa8_1 + name: community.wave.seqera.io/library/multiqc:1.33--58d7dee710ab3aa8 + scanId: sc-a04c42273e34c55c_2 + singularity: + linux/amd64: + build_id: bd-e3576ddf588fa00d_1 + https: https://community-cr-prod.seqera.io/docker/registry/v2/blobs/sha256/34/34e733a9ae16a27e80fe00f863ea1479c96416017f24a907996126283e7ecd4d/data + name: oras://community.wave.seqera.io/library/multiqc:1.33--e3576ddf588fa00d + linux/arm64: + build_id: bd-2537ca5f8445e3c2_1 + https: https://community-cr-prod.seqera.io/docker/registry/v2/blobs/sha256/78/78b89e91d89e9cc99ad5ade5be311f347838cb2acbfb4f13bc343b170be09ce4/data + name: oras://community.wave.seqera.io/library/multiqc:1.33--2537ca5f8445e3c2 diff --git a/pipelines/nf-atacseq/modules/nf-core/multiqc/tests/custom_prefix.config b/pipelines/nf-atacseq/modules/nf-core/multiqc/tests/custom_prefix.config new file mode 100644 index 0000000..b30b135 --- /dev/null +++ b/pipelines/nf-atacseq/modules/nf-core/multiqc/tests/custom_prefix.config @@ -0,0 +1,5 @@ +process { + withName: 'MULTIQC' { + ext.prefix = "custom_prefix" + } +} diff --git a/pipelines/nf-atacseq/modules/nf-core/multiqc/tests/main.nf.test b/pipelines/nf-atacseq/modules/nf-core/multiqc/tests/main.nf.test new file mode 100644 index 0000000..0e422ea --- /dev/null +++ b/pipelines/nf-atacseq/modules/nf-core/multiqc/tests/main.nf.test @@ -0,0 +1,161 @@ +nextflow_process { + + name "Test Process MULTIQC" + script "../main.nf" + process "MULTIQC" + + tag "modules" + tag "modules_nfcore" + tag "multiqc" + + config "./nextflow.config" + + test("sarscov2 single-end [fastqc]") { + + when { + process { + """ + input[0] = channel.of([ + [ id: 'FASTQC' ], + file(params.modules_testdata_base_path + 'genomics/sarscov2/illumina/fastqc/test_fastqc.zip', checkIfExists: true), + [], + [], + [], + [] + ]) + """ + } + } + + then { + assert process.success + assertAll( + { assert snapshot( + file(process.out.report[0][1]).name, + file(process.out.data[0][1]).name, + process.out.findAll { key, val -> key.startsWith("versions") + }).match() } + ) + } + } + + test("sarscov2 single-end [fastqc] - custom prefix") { + config "./custom_prefix.config" + + when { + process { + """ + input[0] = channel.of([ + [ id: 'FASTQC' ], + file(params.modules_testdata_base_path + 'genomics/sarscov2/illumina/fastqc/test_fastqc.zip', checkIfExists: true), + [], + [], + [], + [] + ]) + """ + } + } + + then { + assert process.success + assertAll( + { assert snapshot( + file(process.out.report[0][1]).name, + file(process.out.data[0][1]).name, + process.out.findAll { key, val -> key.startsWith("versions") + }).match() } + ) + } + } + + test("sarscov2 single-end [fastqc] [config]") { + + when { + process { + """ + input[0] = channel.of([ + [ id: 'FASTQC' ], + file(params.modules_testdata_base_path + 'genomics/sarscov2/illumina/fastqc/test_fastqc.zip', checkIfExists: true), + file("https://github.com/nf-core/tools/raw/dev/nf_core/pipeline-template/assets/multiqc_config.yml", checkIfExists: true), + [], + [], + [] + ]) + """ + } + } + + then { + assert process.success + assertAll( + { assert snapshot( + file(process.out.report[0][1]).name, + file(process.out.data[0][1]).name, + file(process.out.plots[0][1]).name, + process.out.findAll { key, val -> key.startsWith("versions") + }).match() } + ) + } + } + + test("sarscov2 single-end [fastqc] [multiple configs]") { + + when { + process { + """ + input[0] = channel.of([ + [ id: 'FASTQC' ], + file(params.modules_testdata_base_path + 'genomics/sarscov2/illumina/fastqc/test_fastqc.zip', checkIfExists: true), + [ + file("https://github.com/nf-core/tools/raw/dev/nf_core/pipeline-template/assets/multiqc_config.yml", checkIfExists: true), + file("https://github.com/nf-core/tools/raw/dev/nf_core/pipeline-template/assets/multiqc_config.yml", checkIfExists: true) + ], + [], + [], + [] + ]) + """ + } + } + + then { + assert process.success + assertAll( + { assert snapshot( + file(process.out.report[0][1]).name, + file(process.out.data[0][1]).name, + file(process.out.plots[0][1]).name, + process.out.findAll { key, val -> key.startsWith("versions") + }).match() } + ) + } + } + + test("sarscov2 single-end [fastqc] - stub") { + + options "-stub" + + when { + process { + """ + input[0] = channel.of([ + [ id: 'FASTQC' ], + file(params.modules_testdata_base_path + 'genomics/sarscov2/illumina/fastqc/test_fastqc.zip', checkIfExists: true), + [], + [], + [], + [] + ]) + """ + } + } + + then { + assert process.success + assertAll( + { assert snapshot(sanitizeOutput(process.out)).match() } + ) + } + } +} diff --git a/pipelines/nf-atacseq/modules/nf-core/multiqc/tests/main.nf.test.snap b/pipelines/nf-atacseq/modules/nf-core/multiqc/tests/main.nf.test.snap new file mode 100644 index 0000000..c022701 --- /dev/null +++ b/pipelines/nf-atacseq/modules/nf-core/multiqc/tests/main.nf.test.snap @@ -0,0 +1,130 @@ +{ + "sarscov2 single-end [fastqc] [multiple configs]": { + "content": [ + "multiqc_report.html", + "multiqc_data", + "multiqc_plots", + { + "versions": [ + [ + "MULTIQC", + "multiqc", + "1.33" + ] + ] + } + ], + "meta": { + "nf-test": "0.9.3", + "nextflow": "25.10.4" + }, + "timestamp": "2026-02-26T20:21:35.851707" + }, + "sarscov2 single-end [fastqc]": { + "content": [ + "multiqc_report.html", + "multiqc_data", + { + "versions": [ + [ + "MULTIQC", + "multiqc", + "1.33" + ] + ] + } + ], + "meta": { + "nf-test": "0.9.4", + "nextflow": "25.10.4" + }, + "timestamp": "2026-02-26T15:10:36.019680076" + }, + "sarscov2 single-end [fastqc] - stub": { + "content": [ + { + "data": [ + [ + { + "id": "FASTQC" + }, + [ + ".stub:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ] + ], + "plots": [ + [ + { + "id": "FASTQC" + }, + [ + ".stub:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ] + ], + "report": [ + [ + { + "id": "FASTQC" + }, + "multiqc_report.html:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "versions": [ + [ + "MULTIQC", + "multiqc", + "1.33" + ] + ] + } + ], + "meta": { + "nf-test": "0.9.4", + "nextflow": "25.10.4" + }, + "timestamp": "2026-02-26T15:14:39.789193051" + }, + "sarscov2 single-end [fastqc] [config]": { + "content": [ + "multiqc_report.html", + "multiqc_data", + "multiqc_plots", + { + "versions": [ + [ + "MULTIQC", + "multiqc", + "1.33" + ] + ] + } + ], + "meta": { + "nf-test": "0.9.4", + "nextflow": "25.10.4" + }, + "timestamp": "2026-02-26T15:21:29.116129274" + }, + "sarscov2 single-end [fastqc] - custom prefix": { + "content": [ + "custom_prefix.html", + "custom_prefix_data", + { + "versions": [ + [ + "MULTIQC", + "multiqc", + "1.33" + ] + ] + } + ], + "meta": { + "nf-test": "0.9.4", + "nextflow": "25.10.4" + }, + "timestamp": "2026-02-26T15:10:43.419877592" + } +} \ No newline at end of file diff --git a/pipelines/nf-atacseq/modules/nf-core/multiqc/tests/nextflow.config b/pipelines/nf-atacseq/modules/nf-core/multiqc/tests/nextflow.config new file mode 100644 index 0000000..c537a6a --- /dev/null +++ b/pipelines/nf-atacseq/modules/nf-core/multiqc/tests/nextflow.config @@ -0,0 +1,5 @@ +process { + withName: 'MULTIQC' { + ext.prefix = null + } +} diff --git a/pipelines/nf-atacseq/modules/nf-core/picard/markduplicates/environment.yml b/pipelines/nf-atacseq/modules/nf-core/picard/markduplicates/environment.yml new file mode 100644 index 0000000..b4ac4fe --- /dev/null +++ b/pipelines/nf-atacseq/modules/nf-core/picard/markduplicates/environment.yml @@ -0,0 +1,8 @@ +--- +# yaml-language-server: $schema=https://raw.githubusercontent.com/nf-core/modules/master/modules/environment-schema.json +channels: + - conda-forge + - bioconda +dependencies: + # renovate: datasource=conda depName=bioconda/picard + - bioconda::picard=3.4.0 diff --git a/pipelines/nf-atacseq/modules/nf-core/picard/markduplicates/main.nf b/pipelines/nf-atacseq/modules/nf-core/picard/markduplicates/main.nf index de90d7f..17bcf27 100644 --- a/pipelines/nf-atacseq/modules/nf-core/picard/markduplicates/main.nf +++ b/pipelines/nf-atacseq/modules/nf-core/picard/markduplicates/main.nf @@ -1,22 +1,23 @@ process PICARD_MARKDUPLICATES { - tag "$meta.id" + tag "${meta.id}" label 'process_medium' - conda "bioconda::picard=3.1.1" - container "${ workflow.containerEngine == 'singularity' && !task.ext.singularity_pull_docker_container ? - 'https://depot.galaxyproject.org/singularity/picard:3.1.1--hdfd78af_0' : - 'biocontainers/picard:3.1.1--hdfd78af_0' }" + conda "${moduleDir}/environment.yml" + container "${workflow.containerEngine == 'singularity' && !task.ext.singularity_pull_docker_container + ? 'https://community-cr-prod.seqera.io/docker/registry/v2/blobs/sha256/08/0861295baa7c01fc593a9da94e82b44a729dcaf8da92be8e565da109aa549b25/data' + : 'community.wave.seqera.io/library/picard:3.4.0--e9963040df0a9bf6'}" input: - tuple val(meta), path(bam) - path fasta - path fasta_fai + tuple val(meta), path(reads) + tuple val(meta2), path(fasta) + tuple val(meta3), path(fai) output: - tuple val(meta), path("*.markdup.bam"), emit: bam - tuple val(meta), path("*.markdup.bam.bai"), emit: bai - tuple val(meta), path("*.metrics.txt"), emit: metrics - path "versions.yml", emit: versions + tuple val(meta), path("*.bam"), emit: bam, optional: true + tuple val(meta), path("*.bai"), emit: bai, optional: true + tuple val(meta), path("*.cram"), emit: cram, optional: true + tuple val(meta), path("*.metrics.txt"), emit: metrics + tuple val("${task.process}"), val('picard'), eval("picard MarkDuplicates --version 2>&1 | sed -n 's/.*Version://p'"), topic: versions, emit: versions_picard when: task.ext.when == null || task.ext.when @@ -24,38 +25,39 @@ process PICARD_MARKDUPLICATES { script: def args = task.ext.args ?: '' def prefix = task.ext.prefix ?: "${meta.id}" + def suffix = task.ext.suffix ?: "${reads.getExtension()}" + def reference = fasta ? "--REFERENCE_SEQUENCE ${fasta}" : "" def avail_mem = 3072 if (!task.memory) { - log.info '[Picard MarkDuplicates] Available memory not known - defaulting to 3GB' - } else { - avail_mem = (task.memory.mega*0.8).intValue() + log.info('[Picard MarkDuplicates] Available memory not known - defaulting to 3GB. Specify process memory requirements to change this.') + } + else { + avail_mem = (task.memory.mega * 0.8).intValue() + } + + if ("${reads}" == "${prefix}.${suffix}") { + error("Input and output names are the same, use \"task.ext.prefix\" to disambiguate!") } """ picard \\ -Xmx${avail_mem}M \\ MarkDuplicates \\ - $args \\ - --INPUT $bam \\ - --OUTPUT ${prefix}.markdup.bam \\ - --METRICS_FILE ${prefix}.metrics.txt \\ - --CREATE_INDEX true - - cat <<-END_VERSIONS > versions.yml - "${task.process}": - picard: \$(picard MarkDuplicates --version 2>&1 | grep -o 'Version:[0-9.]*' | sed 's/Version://') - END_VERSIONS + ${args} \\ + --INPUT ${reads} \\ + --OUTPUT ${prefix}.${suffix} \\ + ${reference} \\ + --METRICS_FILE ${prefix}.metrics.txt """ stub: def prefix = task.ext.prefix ?: "${meta.id}" + def suffix = task.ext.suffix ?: "${reads.getExtension()}" + if ("${reads}" == "${prefix}.${suffix}") { + error("Input and output names are the same, use \"task.ext.prefix\" to disambiguate!") + } """ - touch ${prefix}.markdup.bam - touch ${prefix}.markdup.bam.bai + touch ${prefix}.${suffix} + touch ${prefix}.${suffix}.bai touch ${prefix}.metrics.txt - - cat <<-END_VERSIONS > versions.yml - "${task.process}": - picard: 3.1.1 - END_VERSIONS """ } diff --git a/pipelines/nf-atacseq/modules/nf-core/picard/markduplicates/meta.yml b/pipelines/nf-atacseq/modules/nf-core/picard/markduplicates/meta.yml new file mode 100644 index 0000000..aa0ddbd --- /dev/null +++ b/pipelines/nf-atacseq/modules/nf-core/picard/markduplicates/meta.yml @@ -0,0 +1,124 @@ +name: picard_markduplicates +description: Locate and tag duplicate reads in a BAM file +keywords: + - markduplicates + - pcr + - duplicates + - bam + - sam + - cram +tools: + - picard: + description: | + A set of command line tools (in Java) for manipulating high-throughput sequencing (HTS) + data and formats such as SAM/BAM/CRAM and VCF. + homepage: https://broadinstitute.github.io/picard/ + documentation: https://broadinstitute.github.io/picard/ + licence: ["MIT"] + identifier: biotools:picard_tools +input: + - - meta: + type: map + description: | + Groovy Map containing sample information + e.g. [ id:'test', single_end:false ] + - reads: + type: file + description: Sequence reads file, can be SAM/BAM/CRAM format + pattern: "*.{bam,cram,sam}" + ontologies: [] + - - meta2: + type: map + description: | + Groovy Map containing reference information + e.g. [ id:'genome' ] + - fasta: + type: file + description: Reference genome fasta file, required for CRAM input + pattern: "*.{fasta,fa}" + ontologies: [] + - - meta3: + type: map + description: | + Groovy Map containing reference information + e.g. [ id:'genome' ] + - fai: + type: file + description: Reference genome fasta index + pattern: "*.{fai}" + ontologies: [] +output: + bam: + - - meta: + type: map + description: | + Groovy Map containing sample information + e.g. [ id:'test', single_end:false ] + - "*.bam": + type: file + description: BAM file with duplicate reads marked/removed + pattern: "*.{bam}" + ontologies: [] + bai: + - - meta: + type: map + description: | + Groovy Map containing sample information + e.g. [ id:'test', single_end:false ] + - "*.bai": + type: file + description: An optional BAM index file. If desired, --CREATE_INDEX must be + passed as a flag + pattern: "*.{bai}" + ontologies: [] + cram: + - - meta: + type: map + description: | + Groovy Map containing sample information + e.g. [ id:'test', single_end:false ] + - "*.cram": + type: file + description: Output CRAM file + pattern: "*.{cram}" + ontologies: [] + metrics: + - - meta: + type: map + description: | + Groovy Map containing sample information + e.g. [ id:'test', single_end:false ] + - "*.metrics.txt": + type: file + description: Duplicate metrics file generated by picard + pattern: "*.{metrics.txt}" + ontologies: [] + versions_picard: + - - ${task.process}: + type: string + description: The process the versions were collected from + - picard: + type: string + description: The tool name + - "picard MarkDuplicates --version 2>&1 | sed -n 's/.*Version://p'": + type: string + description: The command used to generate the version of the tool +topics: + versions: + - - ${task.process}: + type: string + description: The process the versions were collected from + - picard: + type: string + description: The tool name + - "picard MarkDuplicates --version 2>&1 | sed -n 's/.*Version://p'": + type: string + description: The command used to generate the version of the tool +authors: + - "@drpatelh" + - "@projectoriented" + - "@ramprasadn" +maintainers: + - "@drpatelh" + - "@projectoriented" + - "@ramprasadn" diff --git a/pipelines/nf-atacseq/modules/nf-core/picard/markduplicates/tests/main.nf.test b/pipelines/nf-atacseq/modules/nf-core/picard/markduplicates/tests/main.nf.test new file mode 100644 index 0000000..4d00645 --- /dev/null +++ b/pipelines/nf-atacseq/modules/nf-core/picard/markduplicates/tests/main.nf.test @@ -0,0 +1,173 @@ +nextflow_process { + + name "Test Process PICARD_MARKDUPLICATES" + script "../main.nf" + process "PICARD_MARKDUPLICATES" + config "./nextflow.config" + tag "modules" + tag "modules_nfcore" + tag "picard" + tag "picard/markduplicates" + + test("sarscov2 [unsorted bam]") { + + when { + process { + """ + input[0] = Channel.of([ + [ id:'test', single_end:false ], // meta map + file(params.modules_testdata_base_path + 'genomics/sarscov2/illumina/bam/test.paired_end.bam', checkIfExists: true) + ]) + input[1] = [ [:], [] ] + input[2] = [ [:], [] ] + """ + } + } + + then { + assertAll( + { assert process.success }, + { assert snapshot( + file(process.out.bam[0][1]).name, + path(process.out.metrics.get(0).get(1)).readLines()[0..2], + process.out.findAll { key, val -> key.startsWith("versions") }) + .match() } + ) + } + } + + test("sarscov2 [sorted bam]") { + + when { + process { + """ + input[0] = Channel.of([ + [ id:'test', single_end:false ], // meta map + file(params.modules_testdata_base_path + 'genomics/sarscov2/illumina/bam/test.paired_end.sorted.bam', checkIfExists: true) + ]) + input[1] = [ [:], [] ] + input[2] = [ [:], [] ] + """ + } + } + + then { + assertAll( + { assert process.success }, + { assert snapshot( + file(process.out.bam[0][1]).name, + path(process.out.metrics.get(0).get(1)).readLines()[0..2], + process.out.findAll { key, val -> key.startsWith("versions") }) + .match() } + ) + } + } + + test("homo_sapiens [cram]") { + + when { + process { + """ + input[0] = Channel.of([ + [ id:'test', single_end:false ], // meta map + file(params.modules_testdata_base_path + 'genomics/homo_sapiens/illumina/cram/test.paired_end.sorted.cram', checkIfExists: true) + ]) + input[1] = Channel.of([ + [ id:'genome' ], + file(params.modules_testdata_base_path + 'genomics/homo_sapiens/genome/genome.fasta', checkIfExists: true) + ]) + input[2] = Channel.of([ + [ id:'genome' ], + file(params.modules_testdata_base_path + 'genomics/homo_sapiens/genome/genome.fasta.fai', checkIfExists: true) + ]) + """ + } + } + + then { + assertAll( + { assert process.success }, + { assert snapshot( + file(process.out.cram[0][1]).name, + path(process.out.metrics.get(0).get(1)).readLines()[0..2], + process.out.findAll { key, val -> key.startsWith("versions") }) + .match() } + ) + } + } + + test("sarscov2 [unsorted bam] - stub") { + options "-stub" + when { + process { + """ + input[0] = Channel.of([ + [ id:'test', single_end:false ], // meta map + file(params.modules_testdata_base_path + 'genomics/sarscov2/illumina/bam/test.paired_end.bam', checkIfExists: true) + ]) + input[1] = [ [:], [] ] + input[2] = [ [:], [] ] + """ + } + } + + then { + assertAll( + { assert process.success }, + { assert snapshot(sanitizeOutput(process.out)).match() } + ) + } + } + + test("sarscov2 [sorted bam] - stub") { + options "-stub" + when { + process { + """ + input[0] = Channel.of([ + [ id:'test', single_end:false ], // meta map + file(params.modules_testdata_base_path + 'genomics/sarscov2/illumina/bam/test.paired_end.sorted.bam', checkIfExists: true) + ]) + input[1] = [ [:], [] ] + input[2] = [ [:], [] ] + """ + } + } + + then { + assertAll( + { assert process.success }, + { assert snapshot(sanitizeOutput(process.out)).match() } + ) + } + } + + test("homo_sapiens [cram] - stub") { + options "-stub" + when { + process { + """ + input[0] = Channel.of([ + [ id:'test', single_end:false ], // meta map + file(params.modules_testdata_base_path + 'genomics/homo_sapiens/illumina/cram/test.paired_end.sorted.cram', checkIfExists: true) + ]) + input[1] = Channel.of([ + [ id:'genome' ], + file(params.modules_testdata_base_path + 'genomics/homo_sapiens/genome/genome.fasta', checkIfExists: true) + ]) + input[2] = Channel.of([ + [ id:'genome' ], + file(params.modules_testdata_base_path + 'genomics/homo_sapiens/genome/genome.fasta.fai', checkIfExists: true) + ]) + """ + } + } + + then { + assertAll( + { assert process.success }, + { assert snapshot(sanitizeOutput(process.out)).match() } + ) + } + } +} diff --git a/pipelines/nf-atacseq/modules/nf-core/picard/markduplicates/tests/main.nf.test.snap b/pipelines/nf-atacseq/modules/nf-core/picard/markduplicates/tests/main.nf.test.snap new file mode 100644 index 0000000..4ea479a --- /dev/null +++ b/pipelines/nf-atacseq/modules/nf-core/picard/markduplicates/tests/main.nf.test.snap @@ -0,0 +1,218 @@ +{ + "sarscov2 [sorted bam] - stub": { + "content": [ + { + "bai": [ + [ + { + "id": "test", + "single_end": false + }, + "test.md.bam.bai:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "bam": [ + [ + { + "id": "test", + "single_end": false + }, + "test.md.bam:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "cram": [ + + ], + "metrics": [ + [ + { + "id": "test", + "single_end": false + }, + "test.md.metrics.txt:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "versions_picard": [ + [ + "PICARD_MARKDUPLICATES", + "picard", + "3.4.0" + ] + ] + } + ], + "timestamp": "2026-02-19T17:43:13.544887277", + "meta": { + "nf-test": "0.9.4", + "nextflow": "25.10.4" + } + }, + "sarscov2 [unsorted bam] - stub": { + "content": [ + { + "bai": [ + [ + { + "id": "test", + "single_end": false + }, + "test.md.bam.bai:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "bam": [ + [ + { + "id": "test", + "single_end": false + }, + "test.md.bam:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "cram": [ + + ], + "metrics": [ + [ + { + "id": "test", + "single_end": false + }, + "test.md.metrics.txt:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "versions_picard": [ + [ + "PICARD_MARKDUPLICATES", + "picard", + "3.4.0" + ] + ] + } + ], + "timestamp": "2026-02-19T17:43:06.193033248", + "meta": { + "nf-test": "0.9.4", + "nextflow": "25.10.4" + } + }, + "sarscov2 [unsorted bam]": { + "content": [ + "test.md.bam", + [ + "## htsjdk.samtools.metrics.StringHeader", + "# MarkDuplicates --INPUT test.paired_end.bam --OUTPUT test.md.bam --METRICS_FILE test.md.metrics.txt --ASSUME_SORT_ORDER queryname --MAX_SEQUENCES_FOR_DISK_READ_ENDS_MAP 50000 --MAX_FILE_HANDLES_FOR_READ_ENDS_MAP 8000 --SORTING_COLLECTION_SIZE_RATIO 0.25 --TAG_DUPLICATE_SET_MEMBERS false --REMOVE_SEQUENCING_DUPLICATES false --TAGGING_POLICY DontTag --CLEAR_DT true --DUPLEX_UMI false --FLOW_MODE false --FLOW_DUP_STRATEGY FLOW_QUALITY_SUM_STRATEGY --FLOW_USE_END_IN_UNPAIRED_READS false --FLOW_USE_UNPAIRED_CLIPPED_END false --FLOW_UNPAIRED_END_UNCERTAINTY 0 --FLOW_UNPAIRED_START_UNCERTAINTY 0 --FLOW_SKIP_FIRST_N_FLOWS 0 --FLOW_Q_IS_KNOWN_END false --FLOW_EFFECTIVE_QUALITY_THRESHOLD 15 --ADD_PG_TAG_TO_READS true --REMOVE_DUPLICATES false --ASSUME_SORTED false --DUPLICATE_SCORING_STRATEGY SUM_OF_BASE_QUALITIES --PROGRAM_RECORD_ID MarkDuplicates --PROGRAM_GROUP_NAME MarkDuplicates --READ_NAME_REGEX --OPTICAL_DUPLICATE_PIXEL_DISTANCE 100 --MAX_OPTICAL_DUPLICATE_SET_SIZE 300000 --VERBOSITY INFO --QUIET false --VALIDATION_STRINGENCY STRICT --COMPRESSION_LEVEL 5 --MAX_RECORDS_IN_RAM 500000 --CREATE_INDEX false --CREATE_MD5_FILE false --help false --version false --showHidden false --USE_JDK_DEFLATER false --USE_JDK_INFLATER false", + "## htsjdk.samtools.metrics.StringHeader" + ], + { + "versions_picard": [ + [ + "PICARD_MARKDUPLICATES", + "picard", + "3.4.0" + ] + ] + } + ], + "timestamp": "2026-02-19T17:42:40.574463587", + "meta": { + "nf-test": "0.9.4", + "nextflow": "25.10.4" + } + }, + "sarscov2 [sorted bam]": { + "content": [ + "test.md.bam", + [ + "## htsjdk.samtools.metrics.StringHeader", + "# MarkDuplicates --INPUT test.paired_end.sorted.bam --OUTPUT test.md.bam --METRICS_FILE test.md.metrics.txt --ASSUME_SORT_ORDER queryname --MAX_SEQUENCES_FOR_DISK_READ_ENDS_MAP 50000 --MAX_FILE_HANDLES_FOR_READ_ENDS_MAP 8000 --SORTING_COLLECTION_SIZE_RATIO 0.25 --TAG_DUPLICATE_SET_MEMBERS false --REMOVE_SEQUENCING_DUPLICATES false --TAGGING_POLICY DontTag --CLEAR_DT true --DUPLEX_UMI false --FLOW_MODE false --FLOW_DUP_STRATEGY FLOW_QUALITY_SUM_STRATEGY --FLOW_USE_END_IN_UNPAIRED_READS false --FLOW_USE_UNPAIRED_CLIPPED_END false --FLOW_UNPAIRED_END_UNCERTAINTY 0 --FLOW_UNPAIRED_START_UNCERTAINTY 0 --FLOW_SKIP_FIRST_N_FLOWS 0 --FLOW_Q_IS_KNOWN_END false --FLOW_EFFECTIVE_QUALITY_THRESHOLD 15 --ADD_PG_TAG_TO_READS true --REMOVE_DUPLICATES false --ASSUME_SORTED false --DUPLICATE_SCORING_STRATEGY SUM_OF_BASE_QUALITIES --PROGRAM_RECORD_ID MarkDuplicates --PROGRAM_GROUP_NAME MarkDuplicates --READ_NAME_REGEX --OPTICAL_DUPLICATE_PIXEL_DISTANCE 100 --MAX_OPTICAL_DUPLICATE_SET_SIZE 300000 --VERBOSITY INFO --QUIET false --VALIDATION_STRINGENCY STRICT --COMPRESSION_LEVEL 5 --MAX_RECORDS_IN_RAM 500000 --CREATE_INDEX false --CREATE_MD5_FILE false --help false --version false --showHidden false --USE_JDK_DEFLATER false --USE_JDK_INFLATER false", + "## htsjdk.samtools.metrics.StringHeader" + ], + { + "versions_picard": [ + [ + "PICARD_MARKDUPLICATES", + "picard", + "3.4.0" + ] + ] + } + ], + "timestamp": "2026-02-19T17:42:49.374645492", + "meta": { + "nf-test": "0.9.4", + "nextflow": "25.10.4" + } + }, + "homo_sapiens [cram]": { + "content": [ + "test.md.cram", + [ + "## htsjdk.samtools.metrics.StringHeader", + "# MarkDuplicates --INPUT test.paired_end.sorted.cram --OUTPUT test.md.cram --METRICS_FILE test.md.metrics.txt --ASSUME_SORT_ORDER queryname --REFERENCE_SEQUENCE genome.fasta --MAX_SEQUENCES_FOR_DISK_READ_ENDS_MAP 50000 --MAX_FILE_HANDLES_FOR_READ_ENDS_MAP 8000 --SORTING_COLLECTION_SIZE_RATIO 0.25 --TAG_DUPLICATE_SET_MEMBERS false --REMOVE_SEQUENCING_DUPLICATES false --TAGGING_POLICY DontTag --CLEAR_DT true --DUPLEX_UMI false --FLOW_MODE false --FLOW_DUP_STRATEGY FLOW_QUALITY_SUM_STRATEGY --FLOW_USE_END_IN_UNPAIRED_READS false --FLOW_USE_UNPAIRED_CLIPPED_END false --FLOW_UNPAIRED_END_UNCERTAINTY 0 --FLOW_UNPAIRED_START_UNCERTAINTY 0 --FLOW_SKIP_FIRST_N_FLOWS 0 --FLOW_Q_IS_KNOWN_END false --FLOW_EFFECTIVE_QUALITY_THRESHOLD 15 --ADD_PG_TAG_TO_READS true --REMOVE_DUPLICATES false --ASSUME_SORTED false --DUPLICATE_SCORING_STRATEGY SUM_OF_BASE_QUALITIES --PROGRAM_RECORD_ID MarkDuplicates --PROGRAM_GROUP_NAME MarkDuplicates --READ_NAME_REGEX --OPTICAL_DUPLICATE_PIXEL_DISTANCE 100 --MAX_OPTICAL_DUPLICATE_SET_SIZE 300000 --VERBOSITY INFO --QUIET false --VALIDATION_STRINGENCY STRICT --COMPRESSION_LEVEL 5 --MAX_RECORDS_IN_RAM 500000 --CREATE_INDEX false --CREATE_MD5_FILE false --help false --version false --showHidden false --USE_JDK_DEFLATER false --USE_JDK_INFLATER false", + "## htsjdk.samtools.metrics.StringHeader" + ], + { + "versions_picard": [ + [ + "PICARD_MARKDUPLICATES", + "picard", + "3.4.0" + ] + ] + } + ], + "timestamp": "2026-02-19T17:42:59.07843756", + "meta": { + "nf-test": "0.9.4", + "nextflow": "25.10.4" + } + }, + "homo_sapiens [cram] - stub": { + "content": [ + { + "bai": [ + [ + { + "id": "test", + "single_end": false + }, + "test.md.cram.bai:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "bam": [ + + ], + "cram": [ + [ + { + "id": "test", + "single_end": false + }, + "test.md.cram:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "metrics": [ + [ + { + "id": "test", + "single_end": false + }, + "test.md.metrics.txt:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "versions_picard": [ + [ + "PICARD_MARKDUPLICATES", + "picard", + "3.4.0" + ] + ] + } + ], + "timestamp": "2026-02-19T17:43:20.676018462", + "meta": { + "nf-test": "0.9.4", + "nextflow": "25.10.4" + } + } +} \ No newline at end of file diff --git a/pipelines/nf-atacseq/modules/nf-core/picard/markduplicates/tests/nextflow.config b/pipelines/nf-atacseq/modules/nf-core/picard/markduplicates/tests/nextflow.config new file mode 100644 index 0000000..f8dd0f1 --- /dev/null +++ b/pipelines/nf-atacseq/modules/nf-core/picard/markduplicates/tests/nextflow.config @@ -0,0 +1,6 @@ +process { + withName: PICARD_MARKDUPLICATES { + ext.prefix = { "${meta.id}.md" } + ext.args = '--ASSUME_SORT_ORDER queryname' + } +} diff --git a/pipelines/nf-atacseq/modules/nf-core/samtools/faidx/environment.yml b/pipelines/nf-atacseq/modules/nf-core/samtools/faidx/environment.yml new file mode 100644 index 0000000..89e12a6 --- /dev/null +++ b/pipelines/nf-atacseq/modules/nf-core/samtools/faidx/environment.yml @@ -0,0 +1,10 @@ +--- +# yaml-language-server: $schema=https://raw.githubusercontent.com/nf-core/modules/master/modules/environment-schema.json +channels: + - conda-forge + - bioconda +dependencies: + # renovate: datasource=conda depName=bioconda/htslib + - bioconda::htslib=1.22.1 + # renovate: datasource=conda depName=bioconda/samtools + - bioconda::samtools=1.22.1 diff --git a/pipelines/nf-atacseq/modules/nf-core/samtools/faidx/main.nf b/pipelines/nf-atacseq/modules/nf-core/samtools/faidx/main.nf index bb37a19..97bfb57 100644 --- a/pipelines/nf-atacseq/modules/nf-core/samtools/faidx/main.nf +++ b/pipelines/nf-atacseq/modules/nf-core/samtools/faidx/main.nf @@ -2,42 +2,48 @@ process SAMTOOLS_FAIDX { tag "$fasta" label 'process_single' - conda "bioconda::samtools=1.19" + conda "${moduleDir}/environment.yml" container "${ workflow.containerEngine == 'singularity' && !task.ext.singularity_pull_docker_container ? - 'https://depot.galaxyproject.org/singularity/samtools:1.19--h50ea8bc_0' : - 'biocontainers/samtools:1.19--h50ea8bc_0' }" + 'https://depot.galaxyproject.org/singularity/samtools:1.22.1--h96c455f_0' : + 'biocontainers/samtools:1.22.1--h96c455f_0' }" input: - tuple val(meta), path(fasta) + tuple val(meta), path(fasta), path(fai) + val get_sizes output: - tuple val(meta), path("*.fai"), emit: fai - tuple val(meta), path("*.gzi"), emit: gzi, optional: true - path "versions.yml", emit: versions + tuple val(meta), path ("*.{fa,fasta}") , emit: fa, optional: true + tuple val(meta), path ("*.sizes") , emit: sizes, optional: true + tuple val(meta), path ("*.fai") , emit: fai, optional: true + tuple val(meta), path ("*.gzi") , emit: gzi, optional: true + tuple val("${task.process}"), val('samtools'), eval("samtools version | sed '1!d;s/.* //'"), topic: versions, emit: versions_samtools when: task.ext.when == null || task.ext.when script: def args = task.ext.args ?: '' + def get_sizes_command = get_sizes ? "cut -f 1,2 ${fasta}.fai > ${fasta}.sizes" : '' """ - samtools faidx \\ - $args \\ - $fasta - - cat <<-END_VERSIONS > versions.yml - "${task.process}": - samtools: \$(samtools --version | head -n1 | sed 's/samtools //') - END_VERSIONS + samtools \\ + faidx \\ + $fasta \\ + $args + + ${get_sizes_command} """ stub: + def match = (task.ext.args =~ /-o(?:utput)?\s(.*)\s?/).findAll() + def fastacmd = match[0] ? "touch ${match[0][1]}" : '' + def get_sizes_command = get_sizes ? "touch ${fasta}.sizes" : '' """ + ${fastacmd} touch ${fasta}.fai + if [[ "${fasta.extension}" == "gz" ]]; then + touch ${fasta}.gzi + fi - cat <<-END_VERSIONS > versions.yml - "${task.process}": - samtools: 1.19 - END_VERSIONS + ${get_sizes_command} """ } diff --git a/pipelines/nf-atacseq/modules/nf-core/samtools/faidx/meta.yml b/pipelines/nf-atacseq/modules/nf-core/samtools/faidx/meta.yml new file mode 100644 index 0000000..80aae1d --- /dev/null +++ b/pipelines/nf-atacseq/modules/nf-core/samtools/faidx/meta.yml @@ -0,0 +1,112 @@ +name: samtools_faidx +description: Index FASTA file, and optionally generate a file of chromosome + sizes +keywords: + - index + - fasta + - faidx + - chromosome +tools: + - samtools: + description: | + SAMtools is a set of utilities for interacting with and post-processing + short DNA sequence read alignments in the SAM, BAM and CRAM formats, written by Heng Li. + These files are generated as output by short read aligners like BWA. + homepage: http://www.htslib.org/ + documentation: http://www.htslib.org/doc/samtools.html + doi: 10.1093/bioinformatics/btp352 + licence: + - "MIT" + identifier: biotools:samtools +input: + - - meta: + type: map + description: | + Groovy Map containing reference information + e.g. [ id:'test' ] + - fasta: + type: file + description: FASTA file + pattern: "*.{fa,fasta}" + ontologies: [] + - fai: + type: file + description: FASTA index file + pattern: "*.{fai}" + ontologies: [] + - get_sizes: + type: boolean + description: use cut to get the sizes of the index (true) or not (false) +output: + fa: + - - meta: + type: map + description: | + Groovy Map containing sample information + e.g. [ id:'test', single_end:false ] + - "*.{fa,fasta}": + type: file + description: FASTA file + pattern: "*.{fa}" + ontologies: [] + sizes: + - - meta: + type: map + description: | + Groovy Map containing sample information + e.g. [ id:'test', single_end:false ] + - "*.sizes": + type: file + description: File containing chromosome lengths + pattern: "*.{sizes}" + ontologies: [] + fai: + - - meta: + type: map + description: | + Groovy Map containing sample information + e.g. [ id:'test', single_end:false ] + - "*.fai": + type: file + description: FASTA index file + pattern: "*.{fai}" + ontologies: [] + gzi: + - - meta: + type: map + description: | + Groovy Map containing sample information + e.g. [ id:'test', single_end:false ] + - "*.gzi": + type: file + description: Optional gzip index file for compressed inputs + pattern: "*.gzi" + ontologies: [] + versions_samtools: + - - ${task.process}: + type: string + description: The process the versions were collected from + - samtools: + type: string + description: The tool name + - "samtools version | sed '1!d;s/.* //'": + type: eval + description: The command used to generate the version of the tool +topics: + versions: + - - ${task.process}: + type: string + description: The process the versions were collected from + - samtools: + type: string + description: The tool name + - "samtools version | sed '1!d;s/.* //'": + type: eval + description: The command used to generate the version of the tool +authors: + - "@drpatelh" + - "@ewels" + - "@phue" +maintainers: + - "@maxulysse" + - "@phue" diff --git a/pipelines/nf-atacseq/modules/nf-core/samtools/faidx/tests/main.nf.test b/pipelines/nf-atacseq/modules/nf-core/samtools/faidx/tests/main.nf.test new file mode 100644 index 0000000..9a86db8 --- /dev/null +++ b/pipelines/nf-atacseq/modules/nf-core/samtools/faidx/tests/main.nf.test @@ -0,0 +1,253 @@ +nextflow_process { + + name "Test Process SAMTOOLS_FAIDX" + script "../main.nf" + process "SAMTOOLS_FAIDX" + + tag "modules" + tag "modules_nfcore" + tag "samtools" + tag "samtools/faidx" + config "./nextflow.config" + + test("test_samtools_faidx") { + + when { + params { + module_args = '' + } + process { + """ + input[0] = [ + [ id:'test' ], + file(params.modules_testdata_base_path + 'genomics/sarscov2/genome/genome.fasta', checkIfExists: true), + [] + ] + input[1] = false + """ + } + } + + then { + assert process.success + assertAll( + { assert snapshot(sanitizeOutput(process.out)).match()} + ) + } + } + + test("test_samtools_faidx_bgzip") { + + when { + params { + module_args = '' + } + process { + """ + input[0] = [ + [ id:'test' ], + file(params.modules_testdata_base_path + 'genomics/sarscov2/genome/genome.fasta.gz', checkIfExists: true), + [] + ] + input[1] = false + """ + } + } + + then { + assert process.success + assertAll( + { assert snapshot(sanitizeOutput(process.out)).match()} + ) + } + } + + test("test_samtools_faidx_fasta") { + + when { + params { + module_args = 'MT192765.1 -o extract.fa' + } + process { + """ + input[0] = [ + [ id:'test' ], + file(params.modules_testdata_base_path + 'genomics/sarscov2/genome/genome.fasta', checkIfExists: true), + file(params.modules_testdata_base_path + 'genomics/sarscov2/genome/genome.fasta.fai', checkIfExists: true) + ] + input[1] = false + """ + } + } + + then { + assert process.success + assertAll( + { assert snapshot(sanitizeOutput(process.out)).match()} + ) + } + } + + test("test_samtools_faidx_stub_fasta") { + + options "-stub" + when { + params { + module_args = '-o extract.fa' + } + process { + """ + input[0] = [ + [ id:'test' ], + file(params.modules_testdata_base_path + 'genomics/sarscov2/genome/genome.fasta', checkIfExists: true), + file(params.modules_testdata_base_path + 'genomics/sarscov2/genome/genome.fasta.fai', checkIfExists: true) + ] + input[1] = false + """ + } + } + + then { + assert process.success + assertAll( + { assert snapshot(sanitizeOutput(process.out)).match()} + ) + } + } + + test("test_samtools_faidx_stub_fai") { + + options "-stub" + when { + params { + module_args = '' + } + process { + """ + input[0] = [ + [ id:'test' ], + file(params.modules_testdata_base_path + 'genomics/sarscov2/genome/genome.fasta', checkIfExists: true), + [] + ] + input[1] = false + """ + } + } + + then { + assert process.success + assertAll( + { assert snapshot(sanitizeOutput(process.out)).match()} + ) + } + } + + test("test_samtools_faidx_get_sizes") { + + when { + params { + module_args = '' + } + process { + """ + input[0] = [ + [ id:'test' ], + file(params.modules_testdata_base_path + 'genomics/sarscov2/genome/genome.fasta', checkIfExists: true), + [] + ] + input[1] = true + """ + } + } + + then { + assert process.success + assertAll( + { assert snapshot(sanitizeOutput(process.out)).match()} + ) + } + } + + test("test_samtools_faidx_get_sizes_bgzip") { + + when { + params { + module_args = '' + } + process { + """ + input[0] = [ + [ id:'test' ], + file(params.modules_testdata_base_path + 'genomics/sarscov2/genome/genome.fasta.gz', checkIfExists: true), + [] + ] + input[1] = true + """ + } + } + + then { + assert process.success + assertAll( + { assert snapshot(sanitizeOutput(process.out)).match()} + ) + } + } + + test("test_samtools_faidx_get_sizes - stub") { + + options "-stub" + + when { + params { + module_args = '' + } + process { + """ + input[0] = [ + [ id:'test' ], + file(params.modules_testdata_base_path + 'genomics/sarscov2/genome/genome.fasta', checkIfExists: true), + [] + ] + input[1] = true + """ + } + } + + then { + assert process.success + assertAll( + { assert snapshot(sanitizeOutput(process.out)).match()} + ) + } + } + + test("test_samtools_faidx_get_sizes_bgzip - stub") { + + options "-stub" + + when { + params { + module_args = '' + } + process { + """ + input[0] = [ + [ id:'test' ], + file(params.modules_testdata_base_path + 'genomics/sarscov2/genome/genome.fasta.gz', checkIfExists: true), + [] + ] + input[1] = true + """ + } + } + + then { + assert process.success + assertAll( + { assert snapshot(sanitizeOutput(process.out)).match()} + ) + } + } + +} diff --git a/pipelines/nf-atacseq/modules/nf-core/samtools/faidx/tests/main.nf.test.snap b/pipelines/nf-atacseq/modules/nf-core/samtools/faidx/tests/main.nf.test.snap new file mode 100644 index 0000000..4169744 --- /dev/null +++ b/pipelines/nf-atacseq/modules/nf-core/samtools/faidx/tests/main.nf.test.snap @@ -0,0 +1,352 @@ +{ + "test_samtools_faidx": { + "content": [ + { + "fa": [ + + ], + "fai": [ + [ + { + "id": "test" + }, + "genome.fasta.fai:md5,9da2a56e2853dc8c0b86a9e7229c9fe5" + ] + ], + "gzi": [ + + ], + "sizes": [ + + ], + "versions_samtools": [ + [ + "SAMTOOLS_FAIDX", + "samtools", + "1.22.1" + ] + ] + } + ], + "meta": { + "nf-test": "0.9.3", + "nextflow": "25.10.3" + }, + "timestamp": "2026-02-10T15:39:12.541649151" + }, + "test_samtools_faidx_get_sizes_bgzip - stub": { + "content": [ + { + "fa": [ + + ], + "fai": [ + [ + { + "id": "test" + }, + "genome.fasta.gz.fai:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "gzi": [ + [ + { + "id": "test" + }, + "genome.fasta.gz.gzi:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "sizes": [ + [ + { + "id": "test" + }, + "genome.fasta.gz.sizes:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "versions_samtools": [ + [ + "SAMTOOLS_FAIDX", + "samtools", + "1.22.1" + ] + ] + } + ], + "meta": { + "nf-test": "0.9.3", + "nextflow": "25.10.3" + }, + "timestamp": "2026-02-10T15:41:44.040426987" + }, + "test_samtools_faidx_get_sizes": { + "content": [ + { + "fa": [ + + ], + "fai": [ + [ + { + "id": "test" + }, + "genome.fasta.fai:md5,9da2a56e2853dc8c0b86a9e7229c9fe5" + ] + ], + "gzi": [ + + ], + "sizes": [ + [ + { + "id": "test" + }, + "genome.fasta.sizes:md5,a57c401f27ae5133823fb09fb21c8a3c" + ] + ], + "versions_samtools": [ + [ + "SAMTOOLS_FAIDX", + "samtools", + "1.22.1" + ] + ] + } + ], + "meta": { + "nf-test": "0.9.3", + "nextflow": "25.10.3" + }, + "timestamp": "2026-02-10T15:47:03.653912015" + }, + "test_samtools_faidx_bgzip": { + "content": [ + { + "fa": [ + + ], + "fai": [ + [ + { + "id": "test" + }, + "genome.fasta.gz.fai:md5,9da2a56e2853dc8c0b86a9e7229c9fe5" + ] + ], + "gzi": [ + [ + { + "id": "test" + }, + "genome.fasta.gz.gzi:md5,7dea362b3fac8e00956a4952a3d4f474" + ] + ], + "sizes": [ + + ], + "versions_samtools": [ + [ + "SAMTOOLS_FAIDX", + "samtools", + "1.22.1" + ] + ] + } + ], + "meta": { + "nf-test": "0.9.3", + "nextflow": "25.10.3" + }, + "timestamp": "2026-02-10T15:50:04.023566795" + }, + "test_samtools_faidx_fasta": { + "content": [ + { + "fa": [ + [ + { + "id": "test" + }, + "extract.fa:md5,6a0774a0ad937ba0bfd2ac7457d90f36" + ] + ], + "fai": [ + + ], + "gzi": [ + + ], + "sizes": [ + + ], + "versions_samtools": [ + [ + "SAMTOOLS_FAIDX", + "samtools", + "1.22.1" + ] + ] + } + ], + "meta": { + "nf-test": "0.9.3", + "nextflow": "25.10.3" + }, + "timestamp": "2026-02-10T15:39:23.529404162" + }, + "test_samtools_faidx_get_sizes - stub": { + "content": [ + { + "fa": [ + + ], + "fai": [ + [ + { + "id": "test" + }, + "genome.fasta.fai:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "gzi": [ + + ], + "sizes": [ + [ + { + "id": "test" + }, + "genome.fasta.sizes:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "versions_samtools": [ + [ + "SAMTOOLS_FAIDX", + "samtools", + "1.22.1" + ] + ] + } + ], + "meta": { + "nf-test": "0.9.3", + "nextflow": "25.10.3" + }, + "timestamp": "2026-02-10T15:41:39.039834304" + }, + "test_samtools_faidx_stub_fasta": { + "content": [ + { + "fa": [ + [ + { + "id": "test" + }, + "extract.fa:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "fai": [ + + ], + "gzi": [ + + ], + "sizes": [ + + ], + "versions_samtools": [ + [ + "SAMTOOLS_FAIDX", + "samtools", + "1.22.1" + ] + ] + } + ], + "meta": { + "nf-test": "0.9.3", + "nextflow": "25.10.3" + }, + "timestamp": "2026-02-10T15:39:28.961701609" + }, + "test_samtools_faidx_stub_fai": { + "content": [ + { + "fa": [ + + ], + "fai": [ + [ + { + "id": "test" + }, + "genome.fasta.fai:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "gzi": [ + + ], + "sizes": [ + + ], + "versions_samtools": [ + [ + "SAMTOOLS_FAIDX", + "samtools", + "1.22.1" + ] + ] + } + ], + "meta": { + "nf-test": "0.9.3", + "nextflow": "25.10.3" + }, + "timestamp": "2026-02-10T15:39:34.471028474" + }, + "test_samtools_faidx_get_sizes_bgzip": { + "content": [ + { + "fa": [ + + ], + "fai": [ + [ + { + "id": "test" + }, + "genome.fasta.gz.fai:md5,9da2a56e2853dc8c0b86a9e7229c9fe5" + ] + ], + "gzi": [ + [ + { + "id": "test" + }, + "genome.fasta.gz.gzi:md5,7dea362b3fac8e00956a4952a3d4f474" + ] + ], + "sizes": [ + [ + { + "id": "test" + }, + "genome.fasta.gz.sizes:md5,a57c401f27ae5133823fb09fb21c8a3c" + ] + ], + "versions_samtools": [ + [ + "SAMTOOLS_FAIDX", + "samtools", + "1.22.1" + ] + ] + } + ], + "meta": { + "nf-test": "0.9.3", + "nextflow": "25.10.3" + }, + "timestamp": "2026-02-10T15:39:45.439016495" + } +} \ No newline at end of file diff --git a/pipelines/nf-atacseq/modules/nf-core/samtools/faidx/tests/nextflow.config b/pipelines/nf-atacseq/modules/nf-core/samtools/faidx/tests/nextflow.config new file mode 100644 index 0000000..202c036 --- /dev/null +++ b/pipelines/nf-atacseq/modules/nf-core/samtools/faidx/tests/nextflow.config @@ -0,0 +1,7 @@ +process { + + withName: SAMTOOLS_FAIDX { + ext.args = params.module_args + } + +} diff --git a/pipelines/nf-atacseq/modules/nf-core/samtools/flagstat/environment.yml b/pipelines/nf-atacseq/modules/nf-core/samtools/flagstat/environment.yml new file mode 100644 index 0000000..89e12a6 --- /dev/null +++ b/pipelines/nf-atacseq/modules/nf-core/samtools/flagstat/environment.yml @@ -0,0 +1,10 @@ +--- +# yaml-language-server: $schema=https://raw.githubusercontent.com/nf-core/modules/master/modules/environment-schema.json +channels: + - conda-forge + - bioconda +dependencies: + # renovate: datasource=conda depName=bioconda/htslib + - bioconda::htslib=1.22.1 + # renovate: datasource=conda depName=bioconda/samtools + - bioconda::samtools=1.22.1 diff --git a/pipelines/nf-atacseq/modules/nf-core/samtools/flagstat/main.nf b/pipelines/nf-atacseq/modules/nf-core/samtools/flagstat/main.nf index 38465a3..0cfb7e8 100644 --- a/pipelines/nf-atacseq/modules/nf-core/samtools/flagstat/main.nf +++ b/pipelines/nf-atacseq/modules/nf-core/samtools/flagstat/main.nf @@ -1,46 +1,47 @@ process SAMTOOLS_FLAGSTAT { tag "$meta.id" - label 'process_low' + label 'process_single' - conda "bioconda::samtools=1.19" + conda "${moduleDir}/environment.yml" container "${ workflow.containerEngine == 'singularity' && !task.ext.singularity_pull_docker_container ? - 'https://depot.galaxyproject.org/singularity/samtools:1.19--h50ea8bc_0' : - 'biocontainers/samtools:1.19--h50ea8bc_0' }" + 'https://depot.galaxyproject.org/singularity/samtools:1.22.1--h96c455f_0' : + 'biocontainers/samtools:1.22.1--h96c455f_0' }" input: tuple val(meta), path(bam), path(bai) output: tuple val(meta), path("*.flagstat"), emit: flagstat - path "versions.yml", emit: versions + tuple val("${task.process}"), val('samtools'), eval("samtools version | sed '1!d;s/.* //'"), emit: versions_samtools, topic: versions when: task.ext.when == null || task.ext.when script: - def args = task.ext.args ?: '' def prefix = task.ext.prefix ?: "${meta.id}" """ - samtools flagstat \\ - $args \\ - -@ $task.cpus \\ + samtools \\ + flagstat \\ + --threads ${task.cpus} \\ $bam \\ > ${prefix}.flagstat - - cat <<-END_VERSIONS > versions.yml - "${task.process}": - samtools: \$(samtools --version | head -n1 | sed 's/samtools //') - END_VERSIONS """ stub: def prefix = task.ext.prefix ?: "${meta.id}" """ - touch ${prefix}.flagstat - - cat <<-END_VERSIONS > versions.yml - "${task.process}": - samtools: 1.19 - END_VERSIONS + cat <<-END_FLAGSTAT > ${prefix}.flagstat + 1000000 + 0 in total (QC-passed reads + QC-failed reads) + 0 + 0 secondary + 0 + 0 supplementary + 0 + 0 duplicates + 900000 + 0 mapped (90.00% : N/A) + 1000000 + 0 paired in sequencing + 500000 + 0 read1 + 500000 + 0 read2 + 800000 + 0 properly paired (80.00% : N/A) + 850000 + 0 with mate mapped to a different chr + 50000 + 0 with mate mapped to a different chr (mapQ>=5) + END_FLAGSTAT """ } diff --git a/pipelines/nf-atacseq/modules/nf-core/samtools/flagstat/meta.yml b/pipelines/nf-atacseq/modules/nf-core/samtools/flagstat/meta.yml new file mode 100644 index 0000000..8caa1bc --- /dev/null +++ b/pipelines/nf-atacseq/modules/nf-core/samtools/flagstat/meta.yml @@ -0,0 +1,75 @@ +name: samtools_flagstat +description: Counts the number of alignments in a BAM/CRAM/SAM file for each + FLAG type +keywords: + - stats + - mapping + - counts + - bam + - sam + - cram +tools: + - samtools: + description: | + SAMtools is a set of utilities for interacting with and post-processing + short DNA sequence read alignments in the SAM, BAM and CRAM formats, written by Heng Li. + These files are generated as output by short read aligners like BWA. + homepage: http://www.htslib.org/ + documentation: http://www.htslib.org/doc/samtools.html + doi: 10.1093/bioinformatics/btp352 + licence: + - "MIT" + identifier: biotools:samtools +input: + - - meta: + type: map + description: | + Groovy Map containing sample information + e.g. [ id:'test', single_end:false ] + - bam: + type: file + description: BAM/CRAM/SAM file + pattern: "*.{bam,cram,sam}" + ontologies: [] + - bai: + type: file + description: Index for BAM/CRAM/SAM file + pattern: "*.{bai,crai,sai}" + ontologies: [] +output: + flagstat: + - - meta: + type: map + description: | + Groovy Map containing sample information + e.g. [ id:'test', single_end:false ] + - "*.flagstat": + type: file + description: File containing samtools flagstat output + pattern: "*.{flagstat}" + ontologies: [] + versions_samtools: + - - ${task.process}: + type: string + description: The name of the process + - samtools: + type: string + description: The name of the tool + - samtools version | sed '1!d;s/.* //': + type: eval + description: The expression to obtain the version of the tool +topics: + versions: + - - ${task.process}: + type: string + description: The name of the process + - samtools: + type: string + description: The name of the tool + - samtools version | sed '1!d;s/.* //': + type: eval + description: The expression to obtain the version of the tool +authors: + - "@drpatelh" +maintainers: + - "@drpatelh" diff --git a/pipelines/nf-atacseq/modules/nf-core/samtools/flagstat/tests/main.nf.test b/pipelines/nf-atacseq/modules/nf-core/samtools/flagstat/tests/main.nf.test new file mode 100644 index 0000000..3b648a3 --- /dev/null +++ b/pipelines/nf-atacseq/modules/nf-core/samtools/flagstat/tests/main.nf.test @@ -0,0 +1,56 @@ +nextflow_process { + + name "Test Process SAMTOOLS_FLAGSTAT" + script "../main.nf" + process "SAMTOOLS_FLAGSTAT" + tag "modules" + tag "modules_nfcore" + tag "samtools" + tag "samtools/flagstat" + + test("BAM") { + + when { + process { + """ + input[0] = Channel.of([ + [ id:'test', single_end:false ], // meta map + file(params.modules_testdata_base_path + 'genomics/sarscov2/illumina/bam/test.paired_end.sorted.bam', checkIfExists: true), + file(params.modules_testdata_base_path + 'genomics/sarscov2/illumina/bam/test.paired_end.sorted.bam.bai', checkIfExists: true) + ]) + """ + } + } + + then { + assertAll ( + { assert process.success }, + { assert snapshot(process.out).match() } + ) + } + } + + test("BAM - stub") { + + options "-stub" + + when { + process { + """ + input[0] = Channel.of([ + [ id:'test', single_end:false ], // meta map + file(params.modules_testdata_base_path + 'genomics/sarscov2/illumina/bam/test.paired_end.sorted.bam', checkIfExists: true), + file(params.modules_testdata_base_path + 'genomics/sarscov2/illumina/bam/test.paired_end.sorted.bam.bai', checkIfExists: true) + ]) + """ + } + } + + then { + assertAll ( + { assert process.success }, + { assert snapshot(process.out).match() } + ) + } + } +} diff --git a/pipelines/nf-atacseq/modules/nf-core/samtools/flagstat/tests/main.nf.test.snap b/pipelines/nf-atacseq/modules/nf-core/samtools/flagstat/tests/main.nf.test.snap new file mode 100644 index 0000000..f5c882d --- /dev/null +++ b/pipelines/nf-atacseq/modules/nf-core/samtools/flagstat/tests/main.nf.test.snap @@ -0,0 +1,88 @@ +{ + "BAM - stub": { + "content": [ + { + "0": [ + [ + { + "id": "test", + "single_end": false + }, + "test.flagstat:md5,67394650dbae96d1a4fcc70484822159" + ] + ], + "1": [ + [ + "SAMTOOLS_FLAGSTAT", + "samtools", + "1.22.1" + ] + ], + "flagstat": [ + [ + { + "id": "test", + "single_end": false + }, + "test.flagstat:md5,67394650dbae96d1a4fcc70484822159" + ] + ], + "versions_samtools": [ + [ + "SAMTOOLS_FLAGSTAT", + "samtools", + "1.22.1" + ] + ] + } + ], + "meta": { + "nf-test": "0.9.3", + "nextflow": "25.10.3" + }, + "timestamp": "2026-02-03T11:14:30.820969684" + }, + "BAM": { + "content": [ + { + "0": [ + [ + { + "id": "test", + "single_end": false + }, + "test.flagstat:md5,4f7ffd1e6a5e85524d443209ac97d783" + ] + ], + "1": [ + [ + "SAMTOOLS_FLAGSTAT", + "samtools", + "1.22.1" + ] + ], + "flagstat": [ + [ + { + "id": "test", + "single_end": false + }, + "test.flagstat:md5,4f7ffd1e6a5e85524d443209ac97d783" + ] + ], + "versions_samtools": [ + [ + "SAMTOOLS_FLAGSTAT", + "samtools", + "1.22.1" + ] + ] + } + ], + "meta": { + "nf-test": "0.9.3", + "nextflow": "25.10.3" + }, + "timestamp": "2026-02-03T11:14:25.581619424" + } +} \ No newline at end of file diff --git a/pipelines/nf-atacseq/modules/nf-core/samtools/idxstats/environment.yml b/pipelines/nf-atacseq/modules/nf-core/samtools/idxstats/environment.yml new file mode 100644 index 0000000..89e12a6 --- /dev/null +++ b/pipelines/nf-atacseq/modules/nf-core/samtools/idxstats/environment.yml @@ -0,0 +1,10 @@ +--- +# yaml-language-server: $schema=https://raw.githubusercontent.com/nf-core/modules/master/modules/environment-schema.json +channels: + - conda-forge + - bioconda +dependencies: + # renovate: datasource=conda depName=bioconda/htslib + - bioconda::htslib=1.22.1 + # renovate: datasource=conda depName=bioconda/samtools + - bioconda::samtools=1.22.1 diff --git a/pipelines/nf-atacseq/modules/nf-core/samtools/idxstats/main.nf b/pipelines/nf-atacseq/modules/nf-core/samtools/idxstats/main.nf index 7b76f0d..d5b70a7 100644 --- a/pipelines/nf-atacseq/modules/nf-core/samtools/idxstats/main.nf +++ b/pipelines/nf-atacseq/modules/nf-core/samtools/idxstats/main.nf @@ -2,44 +2,37 @@ process SAMTOOLS_IDXSTATS { tag "$meta.id" label 'process_single' - conda "bioconda::samtools=1.19" + conda "${moduleDir}/environment.yml" container "${ workflow.containerEngine == 'singularity' && !task.ext.singularity_pull_docker_container ? - 'https://depot.galaxyproject.org/singularity/samtools:1.19--h50ea8bc_0' : - 'biocontainers/samtools:1.19--h50ea8bc_0' }" + 'https://depot.galaxyproject.org/singularity/samtools:1.22.1--h96c455f_0' : + 'biocontainers/samtools:1.22.1--h96c455f_0' }" input: tuple val(meta), path(bam), path(bai) output: tuple val(meta), path("*.idxstats"), emit: idxstats - path "versions.yml", emit: versions + tuple val("${task.process}"), val('samtools'), eval("samtools version | sed '1!d;s/.* //'"), emit: versions_samtools, topic: versions when: task.ext.when == null || task.ext.when script: - def args = task.ext.args ?: '' def prefix = task.ext.prefix ?: "${meta.id}" + """ - samtools idxstats \\ - $args \\ + # Note: --threads value represents *additional* CPUs to allocate (total CPUs = 1 + --threads). + samtools \\ + idxstats \\ + --threads ${task.cpus-1} \\ $bam \\ > ${prefix}.idxstats - - cat <<-END_VERSIONS > versions.yml - "${task.process}": - samtools: \$(samtools --version | head -n1 | sed 's/samtools //') - END_VERSIONS """ stub: def prefix = task.ext.prefix ?: "${meta.id}" + """ touch ${prefix}.idxstats - - cat <<-END_VERSIONS > versions.yml - "${task.process}": - samtools: 1.19 - END_VERSIONS """ } diff --git a/pipelines/nf-atacseq/modules/nf-core/samtools/idxstats/meta.yml b/pipelines/nf-atacseq/modules/nf-core/samtools/idxstats/meta.yml new file mode 100644 index 0000000..fd15384 --- /dev/null +++ b/pipelines/nf-atacseq/modules/nf-core/samtools/idxstats/meta.yml @@ -0,0 +1,75 @@ +name: samtools_idxstats +description: Reports alignment summary statistics for a BAM/CRAM/SAM file +keywords: + - stats + - mapping + - counts + - chromosome + - bam + - sam + - cram +tools: + - samtools: + description: | + SAMtools is a set of utilities for interacting with and post-processing + short DNA sequence read alignments in the SAM, BAM and CRAM formats, written by Heng Li. + These files are generated as output by short read aligners like BWA. + homepage: http://www.htslib.org/ + documentation: http://www.htslib.org/doc/samtools.html + doi: 10.1093/bioinformatics/btp352 + licence: + - "MIT" + identifier: biotools:samtools +input: + - - meta: + type: map + description: | + Groovy Map containing sample information + e.g. [ id:'test', single_end:false ] + - bam: + type: file + description: BAM/CRAM/SAM file + pattern: "*.{bam,cram,sam}" + ontologies: [] + - bai: + type: file + description: Index for BAM/CRAM/SAM file + pattern: "*.{bai,crai,sai}" + ontologies: [] +output: + idxstats: + - - meta: + type: map + description: | + Groovy Map containing sample information + e.g. [ id:'test', single_end:false ] + - "*.idxstats": + type: file + description: File containing samtools idxstats output + pattern: "*.{idxstats}" + ontologies: [] + versions_samtools: + - - ${task.process}: + type: string + description: The name of the process + - samtools: + type: string + description: The name of the tool + - samtools version | sed '1!d;s/.* //': + type: eval + description: The expression to obtain the version of the tool +topics: + versions: + - - ${task.process}: + type: string + description: The name of the process + - samtools: + type: string + description: The name of the tool + - samtools version | sed '1!d;s/.* //': + type: eval + description: The expression to obtain the version of the tool +authors: + - "@drpatelh" +maintainers: + - "@drpatelh" diff --git a/pipelines/nf-atacseq/modules/nf-core/samtools/idxstats/tests/main.nf.test b/pipelines/nf-atacseq/modules/nf-core/samtools/idxstats/tests/main.nf.test new file mode 100644 index 0000000..c990cd5 --- /dev/null +++ b/pipelines/nf-atacseq/modules/nf-core/samtools/idxstats/tests/main.nf.test @@ -0,0 +1,59 @@ +nextflow_process { + + name "Test Process SAMTOOLS_IDXSTATS" + script "../main.nf" + process "SAMTOOLS_IDXSTATS" + tag "modules" + tag "modules_nfcore" + tag "samtools" + tag "samtools/idxstats" + + test("bam") { + + when { + process { + """ + input[0] = Channel.of([ + [ id:'test', single_end:false ], // meta map + file(params.modules_testdata_base_path + 'genomics/sarscov2/illumina/bam/test.paired_end.sorted.bam', checkIfExists: true), + file(params.modules_testdata_base_path + 'genomics/sarscov2/illumina/bam/test.paired_end.sorted.bam.bai', checkIfExists: true) + ]) + """ + } + } + + then { + assertAll ( + { assert process.success }, + { assert snapshot( + process.out.idxstats, + process.out.findAll { key, val -> key.startsWith('versions') } + ).match() } + ) + } + } + + test("bam - stub") { + options "-stub" + when { + process { + """ + input[0] = Channel.of([ + [ id:'test', single_end:false ], // meta map + file(params.modules_testdata_base_path + 'genomics/sarscov2/illumina/bam/test.paired_end.sorted.bam', checkIfExists: true), + file(params.modules_testdata_base_path + 'genomics/sarscov2/illumina/bam/test.paired_end.sorted.bam.bai', checkIfExists: true) + ]) + """ + } + } + + then { + assertAll ( + { assert process.success }, + { assert snapshot( + process.out.idxstats, + process.out.findAll { key, val -> key.startsWith('versions') } + ).match() } + ) + } + }} diff --git a/pipelines/nf-atacseq/modules/nf-core/samtools/idxstats/tests/main.nf.test.snap b/pipelines/nf-atacseq/modules/nf-core/samtools/idxstats/tests/main.nf.test.snap new file mode 100644 index 0000000..19a54c7 --- /dev/null +++ b/pipelines/nf-atacseq/modules/nf-core/samtools/idxstats/tests/main.nf.test.snap @@ -0,0 +1,56 @@ +{ + "bam - stub": { + "content": [ + [ + [ + { + "id": "test", + "single_end": false + }, + "test.idxstats:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + { + "versions_samtools": [ + [ + "SAMTOOLS_IDXSTATS", + "samtools", + "1.22.1" + ] + ] + } + ], + "meta": { + "nf-test": "0.9.3", + "nextflow": "25.10.3" + }, + "timestamp": "2026-02-02T16:21:46.333090477" + }, + "bam": { + "content": [ + [ + [ + { + "id": "test", + "single_end": false + }, + "test.idxstats:md5,df60a8c8d6621100d05178c93fb053a2" + ] + ], + { + "versions_samtools": [ + [ + "SAMTOOLS_IDXSTATS", + "samtools", + "1.22.1" + ] + ] + } + ], + "meta": { + "nf-test": "0.9.3", + "nextflow": "25.10.3" + }, + "timestamp": "2026-02-02T16:21:41.063422521" + } +} \ No newline at end of file diff --git a/pipelines/nf-atacseq/modules/nf-core/samtools/index/environment.yml b/pipelines/nf-atacseq/modules/nf-core/samtools/index/environment.yml new file mode 100644 index 0000000..89e12a6 --- /dev/null +++ b/pipelines/nf-atacseq/modules/nf-core/samtools/index/environment.yml @@ -0,0 +1,10 @@ +--- +# yaml-language-server: $schema=https://raw.githubusercontent.com/nf-core/modules/master/modules/environment-schema.json +channels: + - conda-forge + - bioconda +dependencies: + # renovate: datasource=conda depName=bioconda/htslib + - bioconda::htslib=1.22.1 + # renovate: datasource=conda depName=bioconda/samtools + - bioconda::samtools=1.22.1 diff --git a/pipelines/nf-atacseq/modules/nf-core/samtools/index/main.nf b/pipelines/nf-atacseq/modules/nf-core/samtools/index/main.nf index 343f905..e2a0e56 100644 --- a/pipelines/nf-atacseq/modules/nf-core/samtools/index/main.nf +++ b/pipelines/nf-atacseq/modules/nf-core/samtools/index/main.nf @@ -2,17 +2,19 @@ process SAMTOOLS_INDEX { tag "$meta.id" label 'process_low' - conda "bioconda::samtools=1.19" + conda "${moduleDir}/environment.yml" container "${ workflow.containerEngine == 'singularity' && !task.ext.singularity_pull_docker_container ? - 'https://depot.galaxyproject.org/singularity/samtools:1.19--h50ea8bc_0' : - 'biocontainers/samtools:1.19--h50ea8bc_0' }" + 'https://depot.galaxyproject.org/singularity/samtools:1.22.1--h96c455f_0' : + 'biocontainers/samtools:1.22.1--h96c455f_0' }" input: - tuple val(meta), path(bam) + tuple val(meta), path(input) output: - tuple val(meta), path("*.bai"), emit: bai - path "versions.yml", emit: versions + tuple val(meta), path("*.bai") , optional:true, emit: bai + tuple val(meta), path("*.csi") , optional:true, emit: csi + tuple val(meta), path("*.crai"), optional:true, emit: crai + tuple val("${task.process}"), val('samtools'), eval("samtools version | sed '1!d;s/.* //'"), emit: versions_samtools, topic: versions when: task.ext.when == null || task.ext.when @@ -20,21 +22,18 @@ process SAMTOOLS_INDEX { script: def args = task.ext.args ?: '' """ - samtools index $args -@ $task.cpus $bam - - cat <<-END_VERSIONS > versions.yml - "${task.process}": - samtools: \$(samtools --version | head -n1 | sed 's/samtools //') - END_VERSIONS + samtools \\ + index \\ + -@ ${task.cpus} \\ + $args \\ + $input """ stub: + def args = task.ext.args ?: '' + def extension = file(input).getExtension() == 'cram' ? + "crai" : args.contains("-c") ? "csi" : "bai" """ - touch ${bam}.bai - - cat <<-END_VERSIONS > versions.yml - "${task.process}": - samtools: 1.19 - END_VERSIONS + touch ${input}.${extension} """ } diff --git a/pipelines/nf-atacseq/modules/nf-core/samtools/index/meta.yml b/pipelines/nf-atacseq/modules/nf-core/samtools/index/meta.yml new file mode 100644 index 0000000..c6d4ce2 --- /dev/null +++ b/pipelines/nf-atacseq/modules/nf-core/samtools/index/meta.yml @@ -0,0 +1,92 @@ +name: samtools_index +description: Index SAM/BAM/CRAM file +keywords: + - index + - bam + - sam + - cram +tools: + - samtools: + description: | + SAMtools is a set of utilities for interacting with and post-processing + short DNA sequence read alignments in the SAM, BAM and CRAM formats, written by Heng Li. + These files are generated as output by short read aligners like BWA. + homepage: http://www.htslib.org/ + documentation: http://www.htslib.org/doc/samtools.html + doi: 10.1093/bioinformatics/btp352 + licence: + - "MIT" + identifier: biotools:samtools +input: + - - meta: + type: map + description: | + Groovy Map containing sample information + e.g. [ id:'test', single_end:false ] + - input: + type: file + description: input file + ontologies: [] +output: + bai: + - - meta: + type: map + description: | + Groovy Map containing sample information + e.g. [ id:'test', single_end:false ] + - "*.bai": + type: file + description: BAM/CRAM/SAM index file + pattern: "*.{bai,crai,sai}" + ontologies: [] + csi: + - - meta: + type: map + description: | + Groovy Map containing sample information + e.g. [ id:'test', single_end:false ] + - "*.csi": + type: file + description: CSI index file + pattern: "*.{csi}" + ontologies: [] + crai: + - - meta: + type: map + description: | + Groovy Map containing sample information + e.g. [ id:'test', single_end:false ] + - "*.crai": + type: file + description: BAM/CRAM/SAM index file + pattern: "*.{bai,crai,sai}" + ontologies: [] + versions_samtools: + - - ${task.process}: + type: string + description: The name of the process + - samtools: + type: string + description: The name of the tool + - samtools version | sed '1!d;s/.* //': + type: eval + description: The expression to obtain the version of the tool +topics: + versions: + - - ${task.process}: + type: string + description: The name of the process + - samtools: + type: string + description: The name of the tool + - samtools version | sed '1!d;s/.* //': + type: eval + description: The expression to obtain the version of the tool +authors: + - "@drpatelh" + - "@ewels" + - "@maxulysse" +maintainers: + - "@drpatelh" + - "@ewels" + - "@maxulysse" diff --git a/pipelines/nf-atacseq/modules/nf-core/samtools/index/tests/csi.nextflow.config b/pipelines/nf-atacseq/modules/nf-core/samtools/index/tests/csi.nextflow.config new file mode 100644 index 0000000..0ed260e --- /dev/null +++ b/pipelines/nf-atacseq/modules/nf-core/samtools/index/tests/csi.nextflow.config @@ -0,0 +1,7 @@ +process { + + withName: SAMTOOLS_INDEX { + ext.args = '-c' + } + +} diff --git a/pipelines/nf-atacseq/modules/nf-core/samtools/index/tests/main.nf.test b/pipelines/nf-atacseq/modules/nf-core/samtools/index/tests/main.nf.test new file mode 100644 index 0000000..c96cec8 --- /dev/null +++ b/pipelines/nf-atacseq/modules/nf-core/samtools/index/tests/main.nf.test @@ -0,0 +1,155 @@ +nextflow_process { + + name "Test Process SAMTOOLS_INDEX" + script "../main.nf" + process "SAMTOOLS_INDEX" + tag "modules" + tag "modules_nfcore" + tag "samtools" + tag "samtools/index" + + test("bai") { + when { + process { + """ + input[0] = Channel.of([ + [ id:'test', single_end:false ], // meta map + file(params.modules_testdata_base_path + 'genomics/sarscov2/illumina/bam/test.paired_end.sorted.bam', checkIfExists: true) + ]) + """ + } + } + + then { + assertAll ( + { assert process.success }, + { assert snapshot( + process.out.bai, + process.out.findAll { key, val -> key.startsWith('versions') } + ).match() } + ) + } + } + + test("crai") { + when { + process { + """ + input[0] = Channel.of([ + [ id:'test', single_end:false ], // meta map + file(params.modules_testdata_base_path + 'genomics/homo_sapiens/illumina/cram/test.paired_end.recalibrated.sorted.cram', checkIfExists: true) + ]) + """ + } + } + + then { + assertAll ( + { assert process.success }, + { assert snapshot( + process.out.crai, + process.out.findAll { key, val -> key.startsWith('versions') } + ).match() } + ) + } + } + + test("csi") { + config "./csi.nextflow.config" + + when { + process { + """ + input[0] = Channel.of([ + [ id:'test', single_end:false ], // meta map + file(params.modules_testdata_base_path + 'genomics/sarscov2/illumina/bam/test.paired_end.sorted.bam', checkIfExists: true) + ]) + """ + } + } + + then { + assertAll ( + { assert process.success }, + { assert snapshot( + file(process.out.csi[0][1]).name, + process.out.findAll { key, val -> key.startsWith('versions') } + ).match() } + ) + } + } + + test("bai - stub") { + options "-stub" + when { + process { + """ + input[0] = Channel.of([ + [ id:'test', single_end:false ], // meta map + file(params.modules_testdata_base_path + 'genomics/sarscov2/illumina/bam/test.paired_end.sorted.bam', checkIfExists: true) + ]) + """ + } + } + + then { + assertAll ( + { assert process.success }, + { assert snapshot( + process.out.bai, + process.out.findAll { key, val -> key.startsWith('versions') } + ).match() } + ) + } + } + + test("crai - stub") { + options "-stub" + when { + process { + """ + input[0] = Channel.of([ + [ id:'test', single_end:false ], // meta map + file(params.modules_testdata_base_path + 'genomics/homo_sapiens/illumina/cram/test.paired_end.recalibrated.sorted.cram', checkIfExists: true) + ]) + """ + } + } + + then { + assertAll ( + { assert process.success }, + { assert snapshot( + process.out.crai, + process.out.findAll { key, val -> key.startsWith('versions') } + ).match() } + ) + } + } + + test("csi - stub") { + options "-stub" + config "./csi.nextflow.config" + + when { + process { + """ + input[0] = Channel.of([ + [ id:'test', single_end:false ], // meta map + file(params.modules_testdata_base_path + 'genomics/sarscov2/illumina/bam/test.paired_end.sorted.bam', checkIfExists: true) + ]) + """ + } + } + + then { + assertAll ( + { assert process.success }, + { assert snapshot( + process.out.csi, + process.out.findAll { key, val -> key.startsWith('versions') } + ).match() } + ) + } + } +} diff --git a/pipelines/nf-atacseq/modules/nf-core/samtools/index/tests/main.nf.test.snap b/pipelines/nf-atacseq/modules/nf-core/samtools/index/tests/main.nf.test.snap new file mode 100644 index 0000000..afc8a1f --- /dev/null +++ b/pipelines/nf-atacseq/modules/nf-core/samtools/index/tests/main.nf.test.snap @@ -0,0 +1,156 @@ +{ + "csi - stub": { + "content": [ + [ + [ + { + "id": "test", + "single_end": false + }, + "test.paired_end.sorted.bam.csi:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + { + "versions_samtools": [ + [ + "SAMTOOLS_INDEX", + "samtools", + "1.22.1" + ] + ] + } + ], + "meta": { + "nf-test": "0.9.3", + "nextflow": "25.10.2" + }, + "timestamp": "2026-01-28T17:52:10.030187" + }, + "crai - stub": { + "content": [ + [ + [ + { + "id": "test", + "single_end": false + }, + "test.paired_end.recalibrated.sorted.cram.crai:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + { + "versions_samtools": [ + [ + "SAMTOOLS_INDEX", + "samtools", + "1.22.1" + ] + ] + } + ], + "meta": { + "nf-test": "0.9.3", + "nextflow": "25.10.2" + }, + "timestamp": "2026-01-28T17:51:59.125484" + }, + "bai - stub": { + "content": [ + [ + [ + { + "id": "test", + "single_end": false + }, + "test.paired_end.sorted.bam.bai:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + { + "versions_samtools": [ + [ + "SAMTOOLS_INDEX", + "samtools", + "1.22.1" + ] + ] + } + ], + "meta": { + "nf-test": "0.9.3", + "nextflow": "25.10.2" + }, + "timestamp": "2026-01-28T17:51:47.277042" + }, + "csi": { + "content": [ + "test.paired_end.sorted.bam.csi", + { + "versions_samtools": [ + [ + "SAMTOOLS_INDEX", + "samtools", + "1.22.1" + ] + ] + } + ], + "meta": { + "nf-test": "0.9.3", + "nextflow": "25.10.2" + }, + "timestamp": "2026-01-28T17:51:35.758735" + }, + "crai": { + "content": [ + [ + [ + { + "id": "test", + "single_end": false + }, + "test.paired_end.recalibrated.sorted.cram.crai:md5,14bc3bd5c89cacc8f4541f9062429029" + ] + ], + { + "versions_samtools": [ + [ + "SAMTOOLS_INDEX", + "samtools", + "1.22.1" + ] + ] + } + ], + "meta": { + "nf-test": "0.9.3", + "nextflow": "25.10.2" + }, + "timestamp": "2026-01-28T17:51:26.561965" + }, + "bai": { + "content": [ + [ + [ + { + "id": "test", + "single_end": false + }, + "test.paired_end.sorted.bam.bai:md5,704c10dd1326482448ca3073fdebc2f4" + ] + ], + { + "versions_samtools": [ + [ + "SAMTOOLS_INDEX", + "samtools", + "1.22.1" + ] + ] + } + ], + "meta": { + "nf-test": "0.9.3", + "nextflow": "25.10.2" + }, + "timestamp": "2026-01-28T17:51:15.299035" + } +} \ No newline at end of file diff --git a/pipelines/nf-atacseq/modules/nf-core/samtools/sort/environment.yml b/pipelines/nf-atacseq/modules/nf-core/samtools/sort/environment.yml new file mode 100644 index 0000000..89e12a6 --- /dev/null +++ b/pipelines/nf-atacseq/modules/nf-core/samtools/sort/environment.yml @@ -0,0 +1,10 @@ +--- +# yaml-language-server: $schema=https://raw.githubusercontent.com/nf-core/modules/master/modules/environment-schema.json +channels: + - conda-forge + - bioconda +dependencies: + # renovate: datasource=conda depName=bioconda/htslib + - bioconda::htslib=1.22.1 + # renovate: datasource=conda depName=bioconda/samtools + - bioconda::samtools=1.22.1 diff --git a/pipelines/nf-atacseq/modules/nf-core/samtools/sort/main.nf b/pipelines/nf-atacseq/modules/nf-core/samtools/sort/main.nf index 3215395..6b5aa31 100644 --- a/pipelines/nf-atacseq/modules/nf-core/samtools/sort/main.nf +++ b/pipelines/nf-atacseq/modules/nf-core/samtools/sort/main.nf @@ -2,46 +2,77 @@ process SAMTOOLS_SORT { tag "$meta.id" label 'process_medium' - conda "bioconda::samtools=1.19" + conda "${moduleDir}/environment.yml" container "${ workflow.containerEngine == 'singularity' && !task.ext.singularity_pull_docker_container ? - 'https://depot.galaxyproject.org/singularity/samtools:1.19--h50ea8bc_0' : - 'biocontainers/samtools:1.19--h50ea8bc_0' }" + 'https://depot.galaxyproject.org/singularity/samtools:1.22.1--h96c455f_0' : + 'biocontainers/samtools:1.22.1--h96c455f_0' }" input: - tuple val(meta), path(bam) - path fasta + tuple val(meta) , path(bam) + tuple val(meta2), path(fasta) + val index_format output: - tuple val(meta), path("*.sorted.bam"), emit: bam - path "versions.yml", emit: versions + tuple val(meta), path("${prefix}.bam"), emit: bam, optional: true + tuple val(meta), path("${prefix}.cram"), emit: cram, optional: true + tuple val(meta), path("${prefix}.sam"), emit: sam, optional: true + tuple val(meta), path("${prefix}.${extension}.crai"), emit: crai, optional: true + tuple val(meta), path("${prefix}.${extension}.csi"), emit: csi, optional: true + tuple val(meta), path("${prefix}.${extension}.bai"), emit: bai, optional: true + tuple val("${task.process}"), val('samtools'), eval("samtools version | sed '1!d;s/.* //'"), topic: versions, emit: versions_samtools when: task.ext.when == null || task.ext.when script: def args = task.ext.args ?: '' - def prefix = task.ext.prefix ?: "${meta.id}" + prefix = task.ext.prefix ?: "${meta.id}" + extension = args.contains("--output-fmt sam") ? "sam" : + args.contains("--output-fmt cram") ? "cram" : + "bam" + def reference = fasta ? "--reference ${fasta}" : "" + output_file = index_format ? "${prefix}.${extension}##idx##${prefix}.${extension}.${index_format} --write-index" : "${prefix}.${extension}" + if (index_format) { + if (!index_format.matches('bai|csi|crai')) { + error "Index format not one of bai, csi, crai." + } else if (extension == "sam") { + error "Indexing not compatible with SAM output" + } + } + if ("$bam" == "${prefix}.bam") error "Input and output names are the same, use \"task.ext.prefix\" to disambiguate!" + """ + samtools cat \\ + ${bam} \\ + | \\ samtools sort \\ $args \\ - -@ $task.cpus \\ - -o ${prefix}.sorted.bam \\ - $bam - - cat <<-END_VERSIONS > versions.yml - "${task.process}": - samtools: \$(samtools --version | head -n1 | sed 's/samtools //') - END_VERSIONS + -T ${prefix} \\ + --threads $task.cpus \\ + ${reference} \\ + -o ${output_file} \\ + - + """ stub: - def prefix = task.ext.prefix ?: "${meta.id}" + def args = task.ext.args ?: '' + prefix = task.ext.prefix ?: "${meta.id}" + extension = args.contains("--output-fmt sam") ? "sam" : + args.contains("--output-fmt cram") ? "cram" : + "bam" + if (index_format) { + if (!index_format.matches('bai|csi|crai')) { + error "Index format not one of bai, csi, crai." + } else if (extension == "sam") { + error "Indexing not compatible with SAM output" + } + } + index = index_format ? "touch ${prefix}.${extension}.${index_format}" : "" + """ - touch ${prefix}.sorted.bam + touch ${prefix}.${extension} + ${index} - cat <<-END_VERSIONS > versions.yml - "${task.process}": - samtools: 1.19 - END_VERSIONS """ } diff --git a/pipelines/nf-atacseq/modules/nf-core/samtools/sort/meta.yml b/pipelines/nf-atacseq/modules/nf-core/samtools/sort/meta.yml new file mode 100644 index 0000000..6996830 --- /dev/null +++ b/pipelines/nf-atacseq/modules/nf-core/samtools/sort/meta.yml @@ -0,0 +1,142 @@ +name: samtools_sort +description: Sort SAM/BAM/CRAM file +keywords: + - sort + - bam + - sam + - cram +tools: + - samtools: + description: | + SAMtools is a set of utilities for interacting with and post-processing + short DNA sequence read alignments in the SAM, BAM and CRAM formats, written by Heng Li. + These files are generated as output by short read aligners like BWA. + homepage: http://www.htslib.org/ + documentation: http://www.htslib.org/doc/samtools.html + doi: 10.1093/bioinformatics/btp352 + licence: ["MIT"] + identifier: biotools:samtools +input: + - - meta: + type: map + description: | + Groovy Map containing sample information + e.g. [ id:'test', single_end:false ] + - bam: + type: file + description: BAM/CRAM/SAM file(s) + pattern: "*.{bam,cram,sam}" + ontologies: [] + - - meta2: + type: map + description: | + Groovy Map containing reference information + e.g. [ id:'genome' ] + - fasta: + type: file + description: Reference genome FASTA file + pattern: "*.{fa,fasta,fna}" + optional: true + ontologies: [] + - index_format: + type: string + description: Index format to use (optional) + pattern: "bai|csi|crai" +output: + bam: + - - meta: + type: map + description: | + Groovy Map containing sample information + e.g. [ id:'test', single_end:false ] + - "${prefix}.bam": + type: file + description: Sorted BAM file + pattern: "*.{bam}" + ontologies: [] + cram: + - - meta: + type: map + description: | + Groovy Map containing sample information + e.g. [ id:'test', single_end:false ] + - "${prefix}.cram": + type: file + description: Sorted CRAM file + pattern: "*.{cram}" + ontologies: [] + sam: + - - meta: + type: map + description: | + Groovy Map containing sample information + e.g. [ id:'test', single_end:false ] + - "${prefix}.sam": + type: file + description: Sorted SAM file + pattern: "*.{sam}" + ontologies: [] + crai: + - - meta: + type: map + description: | + Groovy Map containing sample information + e.g. [ id:'test', single_end:false ] + - "${prefix}.${extension}.crai": + type: file + description: CRAM index file (optional) + pattern: "*.crai" + ontologies: [] + csi: + - - meta: + type: map + description: | + Groovy Map containing sample information + e.g. [ id:'test', single_end:false ] + - "${prefix}.${extension}.csi": + type: file + description: BAM index file (optional) + pattern: "*.csi" + ontologies: [] + bai: + - - meta: + type: map + description: | + Groovy Map containing sample information + e.g. [ id:'test', single_end:false ] + - "${prefix}.${extension}.bai": + type: file + description: BAM index file (optional) + pattern: "*.bai" + ontologies: [] + versions_samtools: + - - ${task.process}: + type: string + description: The process the versions were collected from + - samtools: + type: string + description: The tool name + - "samtools version | sed '1!d;s/.* //'": + type: string + description: The command used to generate the version of the tool + +topics: + versions: + - - ${task.process}: + type: string + description: The process the versions were collected from + - samtools: + type: string + description: The tool name + - "samtools version | sed '1!d;s/.* //'": + type: string + description: The command used to generate the version of the tool + +authors: + - "@drpatelh" + - "@ewels" + - "@matthdsm" +maintainers: + - "@drpatelh" + - "@ewels" + - "@matthdsm" diff --git a/pipelines/nf-atacseq/modules/nf-core/samtools/sort/tests/main.nf.test b/pipelines/nf-atacseq/modules/nf-core/samtools/sort/tests/main.nf.test new file mode 100644 index 0000000..df47bb2 --- /dev/null +++ b/pipelines/nf-atacseq/modules/nf-core/samtools/sort/tests/main.nf.test @@ -0,0 +1,332 @@ +nextflow_process { + + name "Test Process SAMTOOLS_SORT" + script "../main.nf" + process "SAMTOOLS_SORT" + tag "modules" + tag "modules_nfcore" + tag "samtools" + tag "samtools/sort" + + test("bam_no_index") { + + config "./nextflow.config" + + when { + process { + """ + input[0] = Channel.of([ + [ id:'test', single_end:false ], // meta map + file(params.modules_testdata_base_path + 'genomics/sarscov2/illumina/bam/test.paired_end.bam', checkIfExists: true) + ]) + input[1] = Channel.of([ + [ id:'fasta' ], // meta map + file(params.modules_testdata_base_path + 'genomics/sarscov2/genome/genome.fasta', checkIfExists: true) + ]) + input[2] = '' + """ + } + } + + then { + assertAll ( + { assert process.success }, + { assert snapshot( + process.out.bam, + process.out.bai, + process.out.findAll { key, val -> key.startsWith("versions") } + ).match()} + ) + } + } + + test("bam_bai_index") { + + config "./nextflow.config" + + when { + process { + """ + input[0] = Channel.of([ + [ id:'test', single_end:false ], // meta map + file(params.modules_testdata_base_path + 'genomics/sarscov2/illumina/bam/test.paired_end.bam', checkIfExists: true) + ]) + input[1] = Channel.of([ + [ id:'fasta' ], // meta map + file(params.modules_testdata_base_path + 'genomics/sarscov2/genome/genome.fasta', checkIfExists: true) + ]) + input[2] = 'bai' + """ + } + } + + then { + assertAll ( + { assert process.success }, + { assert snapshot( + process.out.bam, + process.out.bai, + process.out.findAll { key, val -> key.startsWith("versions") } + ).match()} + ) + } + } + + test("bam_csi_index") { + + config "./nextflow.config" + + when { + process { + """ + input[0] = Channel.of([ + [ id:'test', single_end:false ], // meta map + file(params.modules_testdata_base_path + 'genomics/sarscov2/illumina/bam/test.paired_end.bam', checkIfExists: true) + ]) + input[1] = Channel.of([ + [ id:'fasta' ], // meta map + file(params.modules_testdata_base_path + 'genomics/sarscov2/genome/genome.fasta', checkIfExists: true) + ]) + input[2] = 'csi' + """ + } + } + + then { + assertAll ( + { assert process.success }, + { assert snapshot( + process.out.bam, + process.out.csi, + process.out.findAll { key, val -> key.startsWith("versions") } + ).match()} + ) + } + } + + test("multiple bam") { + + config "./nextflow.config" + + when { + process { + """ + input[0] = Channel.of([ + [ id:'test', single_end:false ], // meta map + [ + file(params.modules_testdata_base_path + 'genomics/homo_sapiens/illumina/bam/test.paired_end.sorted.bam', checkIfExists: true), + file(params.modules_testdata_base_path + 'genomics/homo_sapiens/illumina/bam/test2.paired_end.sorted.bam', checkIfExists: true) + ] + ]) + input[1] = Channel.of([ + [ id:'fasta' ], // meta map + file(params.modules_testdata_base_path + 'genomics/homo_sapiens/genome/genome.fasta', checkIfExists: true) + ]) + input[2] = '' + """ + } + } + + then { + assertAll ( + { assert process.success }, + { assert snapshot( + process.out.bam, + process.out.csi.collect { it.collect { it instanceof Map ? it : file(it).name } }, + process.out.findAll { key, val -> key.startsWith("versions") } + ).match()} + ) + } + } + + test("multiple bam bai index") { + + config "./nextflow.config" + + when { + process { + """ + input[0] = Channel.of([ + [ id:'test', single_end:false ], // meta map + [ + file(params.modules_testdata_base_path + 'genomics/homo_sapiens/illumina/bam/test.paired_end.sorted.bam', checkIfExists: true), + file(params.modules_testdata_base_path + 'genomics/homo_sapiens/illumina/bam/test2.paired_end.sorted.bam', checkIfExists: true) + ] + ]) + input[1] = Channel.of([ + [ id:'fasta' ], // meta map + file(params.modules_testdata_base_path + 'genomics/homo_sapiens/genome/genome.fasta', checkIfExists: true) + ]) + input[2] = 'bai' + """ + } + } + + then { + assertAll ( + { assert process.success }, + { assert snapshot( + process.out.bam, + process.out.bai.collect { it.collect { it instanceof Map ? it : file(it).name } }, + process.out.findAll { key, val -> key.startsWith("versions") } + ).match()} + ) + } + } + + test("multiple bam csi index") { + + config "./nextflow.config" + + when { + process { + """ + input[0] = Channel.of([ + [ id:'test', single_end:false ], // meta map + [ + file(params.modules_testdata_base_path + 'genomics/homo_sapiens/illumina/bam/test.paired_end.sorted.bam', checkIfExists: true), + file(params.modules_testdata_base_path + 'genomics/homo_sapiens/illumina/bam/test2.paired_end.sorted.bam', checkIfExists: true) + ] + ]) + input[1] = Channel.of([ + [ id:'fasta' ], // meta map + file(params.modules_testdata_base_path + 'genomics/homo_sapiens/genome/genome.fasta', checkIfExists: true) + ]) + input[2] = 'csi' + """ + } + } + + then { + assertAll ( + { assert process.success }, + { assert snapshot( + process.out.bam, + process.out.csi.collect { it.collect { it instanceof Map ? it : file(it).name } }, + process.out.findAll { key, val -> key.startsWith("versions") } + ).match()} + ) + } + } + + test("cram") { + + config "./nextflow_cram.config" + + when { + process { + """ + input[0] = Channel.of([ + [ id:'test', single_end:false ], // meta map + file(params.modules_testdata_base_path + 'genomics/homo_sapiens/illumina/cram/test.paired_end.sorted.cram', checkIfExists: true) + ]) + input[1] = Channel.of([ + [ id:'fasta' ], // meta map + file(params.modules_testdata_base_path + 'genomics/homo_sapiens/genome/genome.fasta', checkIfExists: true) + ]) + input[2] = '' + """ + } + } + + then { + assertAll ( + { assert process.success }, + { assert snapshot( + process.out.cram.collect { it.collect { it instanceof Map ? it : file(it).name } }, + process.out.crai.collect { it.collect { it instanceof Map ? it : file(it).name } }, + process.out.findAll { key, val -> key.startsWith("versions") } + ).match()} + ) + } + } + + test("bam - stub") { + + options "-stub" + config "./nextflow.config" + + when { + process { + """ + input[0] = Channel.of([ + [ id:'test', single_end:false ], // meta map + file(params.modules_testdata_base_path + 'genomics/sarscov2/illumina/bam/test.paired_end.bam', checkIfExists: true) + ]) + input[1] = Channel.of([ + [ id:'fasta' ], // meta map + file(params.modules_testdata_base_path + 'genomics/sarscov2/genome/genome.fasta', checkIfExists: true) + ]) + input[2] = '' + """ + } + } + + then { + assertAll ( + { assert process.success }, + { assert snapshot(process.out.findAll { key, val -> key.startsWith("versions") }).match() } + ) + } + } + + test("multiple bam - stub") { + + config "./nextflow.config" + + when { + process { + """ + input[0] = Channel.of([ + [ id:'test', single_end:false ], // meta map + [ + file(params.modules_testdata_base_path + 'genomics/homo_sapiens/illumina/bam/test.paired_end.sorted.bam', checkIfExists: true), + file(params.modules_testdata_base_path + 'genomics/homo_sapiens/illumina/bam/test2.paired_end.sorted.bam', checkIfExists: true) + ] + ]) + input[1] = Channel.of([ + [ id:'fasta' ], // meta map + file(params.modules_testdata_base_path + 'genomics/homo_sapiens/genome/genome.fasta', checkIfExists: true) + ]) + input[2] = '' + """ + } + } + + then { + assertAll ( + { assert process.success }, + { assert snapshot(process.out.findAll { key, val -> key.startsWith("versions") }).match() } + ) + } + } + + test("cram - stub") { + + options "-stub" + config "./nextflow_cram.config" + + when { + process { + """ + input[0] = Channel.of([ + [ id:'test', single_end:false ], // meta map + file(params.modules_testdata_base_path + 'genomics/homo_sapiens/illumina/cram/test.paired_end.sorted.cram', checkIfExists: true) + ]) + input[1] = Channel.of([ + [ id:'fasta' ], // meta map + file(params.modules_testdata_base_path + 'genomics/homo_sapiens/genome/genome.fasta', checkIfExists: true) + ]) + input[2] = '' + """ + } + } + + then { + assertAll ( + { assert process.success }, + { assert snapshot(process.out.findAll { key, val -> key.startsWith("versions") }).match() } + ) + } + } +} diff --git a/pipelines/nf-atacseq/modules/nf-core/samtools/sort/tests/main.nf.test.snap b/pipelines/nf-atacseq/modules/nf-core/samtools/sort/tests/main.nf.test.snap new file mode 100644 index 0000000..4e618fa --- /dev/null +++ b/pipelines/nf-atacseq/modules/nf-core/samtools/sort/tests/main.nf.test.snap @@ -0,0 +1,296 @@ +{ + "cram": { + "content": [ + [ + [ + { + "id": "test", + "single_end": false + }, + "test.sorted.cram" + ] + ], + [ + [ + { + "id": "test", + "single_end": false + }, + "test.sorted.cram.crai" + ] + ], + { + "versions_samtools": [ + [ + "SAMTOOLS_SORT", + "samtools", + "1.22.1" + ] + ] + } + ], + "meta": { + "nf-test": "0.9.3", + "nextflow": "25.10.0" + }, + "timestamp": "2025-10-29T12:47:01.171084" + }, + "bam_csi_index": { + "content": [ + [ + [ + { + "id": "test", + "single_end": false + }, + "test.sorted.bam:md5,72ca1dff5344a5e5e6b892fe5f6b134d" + ] + ], + [ + [ + { + "id": "test", + "single_end": false + }, + "test.sorted.bam.csi:md5,01394e702c729cb478df914ffaf9f7f8" + ] + ], + { + "versions_samtools": [ + [ + "SAMTOOLS_SORT", + "samtools", + "1.22.1" + ] + ] + } + ], + "meta": { + "nf-test": "0.9.3", + "nextflow": "25.10.0" + }, + "timestamp": "2025-10-29T12:46:00.961675" + }, + "bam - stub": { + "content": [ + { + "versions_samtools": [ + [ + "SAMTOOLS_SORT", + "samtools", + "1.22.1" + ] + ] + } + ], + "meta": { + "nf-test": "0.9.3", + "nextflow": "25.10.0" + }, + "timestamp": "2025-10-29T12:47:12.154354" + }, + "multiple bam bai index": { + "content": [ + [ + [ + { + "id": "test", + "single_end": false + }, + "test.sorted.bam:md5,3ffa2affc29f0aa6e7b36dded84625fe" + ] + ], + [ + [ + { + "id": "test", + "single_end": false + }, + "test.sorted.bam.bai" + ] + ], + { + "versions_samtools": [ + [ + "SAMTOOLS_SORT", + "samtools", + "1.22.1" + ] + ] + } + ], + "meta": { + "nf-test": "0.9.3", + "nextflow": "25.10.0" + }, + "timestamp": "2025-10-29T12:46:25.488622" + }, + "cram - stub": { + "content": [ + { + "versions_samtools": [ + [ + "SAMTOOLS_SORT", + "samtools", + "1.22.1" + ] + ] + } + ], + "meta": { + "nf-test": "0.9.3", + "nextflow": "25.10.0" + }, + "timestamp": "2025-10-29T12:47:28.485045" + }, + "multiple bam": { + "content": [ + [ + [ + { + "id": "test", + "single_end": false + }, + "test.sorted.bam:md5,cd4eb0077f25e9cff395366b8883dd1f" + ] + ], + [ + + ], + { + "versions_samtools": [ + [ + "SAMTOOLS_SORT", + "samtools", + "1.22.1" + ] + ] + } + ], + "meta": { + "nf-test": "0.9.3", + "nextflow": "25.10.0" + }, + "timestamp": "2025-10-29T12:46:13.168476" + }, + "multiple bam - stub": { + "content": [ + { + "versions_samtools": [ + [ + "SAMTOOLS_SORT", + "samtools", + "1.22.1" + ] + ] + } + ], + "meta": { + "nf-test": "0.9.3", + "nextflow": "25.10.0" + }, + "timestamp": "2025-10-29T12:47:21.628088" + }, + "bam_no_index": { + "content": [ + [ + [ + { + "id": "test", + "single_end": false + }, + "test.sorted.bam:md5,26b27d1f9bcb61c25da21b562349784e" + ] + ], + [ + + ], + { + "versions_samtools": [ + [ + "SAMTOOLS_SORT", + "samtools", + "1.22.1" + ] + ] + } + ], + "meta": { + "nf-test": "0.9.3", + "nextflow": "25.10.0" + }, + "timestamp": "2025-10-29T12:45:47.139418" + }, + "multiple bam csi index": { + "content": [ + [ + [ + { + "id": "test", + "single_end": false + }, + "test.sorted.bam:md5,295503ba5342531a3310c33ad0efbc22" + ] + ], + [ + [ + { + "id": "test", + "single_end": false + }, + "test.sorted.bam.csi" + ] + ], + { + "versions_samtools": [ + [ + "SAMTOOLS_SORT", + "samtools", + "1.22.1" + ] + ] + } + ], + "meta": { + "nf-test": "0.9.3", + "nextflow": "25.10.0" + }, + "timestamp": "2025-10-29T12:46:51.5531" + }, + "bam_bai_index": { + "content": [ + [ + [ + { + "id": "test", + "single_end": false + }, + "test.sorted.bam:md5,cae7564cb83bb4a5911205bf94124b54" + ] + ], + [ + [ + { + "id": "test", + "single_end": false + }, + "test.sorted.bam.bai:md5,50dd467c169545a4d5d1f709f7e986e0" + ] + ], + { + "versions_samtools": [ + [ + "SAMTOOLS_SORT", + "samtools", + "1.22.1" + ] + ] + } + ], + "meta": { + "nf-test": "0.9.3", + "nextflow": "25.10.0" + }, + "timestamp": "2025-10-29T12:45:52.796936" + } +} \ No newline at end of file diff --git a/pipelines/nf-atacseq/modules/nf-core/samtools/sort/tests/nextflow.config b/pipelines/nf-atacseq/modules/nf-core/samtools/sort/tests/nextflow.config new file mode 100644 index 0000000..723f62b --- /dev/null +++ b/pipelines/nf-atacseq/modules/nf-core/samtools/sort/tests/nextflow.config @@ -0,0 +1,7 @@ +process { + + withName: SAMTOOLS_SORT { + ext.prefix = { "${meta.id}.sorted" } + } + +} diff --git a/pipelines/nf-atacseq/modules/nf-core/samtools/sort/tests/nextflow_cram.config b/pipelines/nf-atacseq/modules/nf-core/samtools/sort/tests/nextflow_cram.config new file mode 100644 index 0000000..3a8c018 --- /dev/null +++ b/pipelines/nf-atacseq/modules/nf-core/samtools/sort/tests/nextflow_cram.config @@ -0,0 +1,8 @@ +process { + + withName: SAMTOOLS_SORT { + ext.prefix = { "${meta.id}.sorted" } + ext.args = "--write-index --output-fmt cram" + } + +} diff --git a/pipelines/nf-atacseq/modules/nf-core/samtools/stats/environment.yml b/pipelines/nf-atacseq/modules/nf-core/samtools/stats/environment.yml new file mode 100644 index 0000000..89e12a6 --- /dev/null +++ b/pipelines/nf-atacseq/modules/nf-core/samtools/stats/environment.yml @@ -0,0 +1,10 @@ +--- +# yaml-language-server: $schema=https://raw.githubusercontent.com/nf-core/modules/master/modules/environment-schema.json +channels: + - conda-forge + - bioconda +dependencies: + # renovate: datasource=conda depName=bioconda/htslib + - bioconda::htslib=1.22.1 + # renovate: datasource=conda depName=bioconda/samtools + - bioconda::samtools=1.22.1 diff --git a/pipelines/nf-atacseq/modules/nf-core/samtools/stats/main.nf b/pipelines/nf-atacseq/modules/nf-core/samtools/stats/main.nf index 413e8b2..57d2468 100644 --- a/pipelines/nf-atacseq/modules/nf-core/samtools/stats/main.nf +++ b/pipelines/nf-atacseq/modules/nf-core/samtools/stats/main.nf @@ -1,48 +1,40 @@ process SAMTOOLS_STATS { tag "$meta.id" - label 'process_low' + label 'process_single' - conda "bioconda::samtools=1.19" + conda "${moduleDir}/environment.yml" container "${ workflow.containerEngine == 'singularity' && !task.ext.singularity_pull_docker_container ? - 'https://depot.galaxyproject.org/singularity/samtools:1.19--h50ea8bc_0' : - 'biocontainers/samtools:1.19--h50ea8bc_0' }" + 'https://depot.galaxyproject.org/singularity/samtools:1.22.1--h96c455f_0' : + 'biocontainers/samtools:1.22.1--h96c455f_0' }" input: - tuple val(meta), path(bam), path(bai) - path fasta + tuple val(meta), path(input), path(input_index) + tuple val(meta2), path(fasta) output: tuple val(meta), path("*.stats"), emit: stats - path "versions.yml", emit: versions + tuple val("${task.process}"), val('samtools'), eval('samtools version | sed "1!d;s/.* //"'), emit: versions_samtools, topic: versions when: task.ext.when == null || task.ext.when script: - def args = task.ext.args ?: '' - def prefix = task.ext.prefix ?: "${meta.id}" + def args = task.ext.args ?: '' + def prefix = task.ext.prefix ?: "${meta.id}" def reference = fasta ? "--reference ${fasta}" : "" """ - samtools stats \\ - $args \\ - $reference \\ - $bam \\ + samtools \\ + stats \\ + ${args} \\ + --threads ${task.cpus} \\ + ${reference} \\ + ${input} \\ > ${prefix}.stats - - cat <<-END_VERSIONS > versions.yml - "${task.process}": - samtools: \$(samtools --version | head -n1 | sed 's/samtools //') - END_VERSIONS """ stub: def prefix = task.ext.prefix ?: "${meta.id}" """ touch ${prefix}.stats - - cat <<-END_VERSIONS > versions.yml - "${task.process}": - samtools: 1.19 - END_VERSIONS """ } diff --git a/pipelines/nf-atacseq/modules/nf-core/samtools/stats/meta.yml b/pipelines/nf-atacseq/modules/nf-core/samtools/stats/meta.yml new file mode 100644 index 0000000..5c59cce --- /dev/null +++ b/pipelines/nf-atacseq/modules/nf-core/samtools/stats/meta.yml @@ -0,0 +1,88 @@ +name: samtools_stats +description: Produces comprehensive statistics from SAM/BAM/CRAM file +keywords: + - statistics + - counts + - bam + - sam + - cram +tools: + - samtools: + description: | + SAMtools is a set of utilities for interacting with and post-processing + short DNA sequence read alignments in the SAM, BAM and CRAM formats, written by Heng Li. + These files are generated as output by short read aligners like BWA. + homepage: http://www.htslib.org/ + documentation: http://www.htslib.org/doc/samtools.html + doi: 10.1093/bioinformatics/btp352 + licence: ["MIT"] + identifier: biotools:samtools +input: + - - meta: + type: map + description: | + Groovy Map containing sample information + e.g. [ id:'test', single_end:false ] + - input: + type: file + description: BAM/CRAM file from alignment + pattern: "*.{bam,cram}" + ontologies: [] + - input_index: + type: file + description: BAI/CRAI file from alignment + pattern: "*.{bai,crai}" + ontologies: [] + - - meta2: + type: map + description: | + Groovy Map containing reference information + e.g. [ id:'genome' ] + - fasta: + type: file + description: Reference file the CRAM was created with (optional) + pattern: "*.{fasta,fa}" + ontologies: [] +output: + stats: + - - meta: + type: map + description: | + Groovy Map containing sample information + e.g. [ id:'test', single_end:false ] + - "*.stats": + type: file + description: File containing samtools stats output + pattern: "*.{stats}" + ontologies: [] + versions_samtools: + - - ${task.process}: + type: string + description: Name of the process + - samtools: + type: string + description: Name of the tool + - samtools version | sed "1!d;s/.* //": + type: eval + description: The expression to obtain the version of the tool + +topics: + versions: + - - ${task.process}: + type: string + description: Name of the process + - samtools: + type: string + description: Name of the tool + - samtools version | sed "1!d;s/.* //": + type: eval + description: The expression to obtain the version of the tool + +authors: + - "@drpatelh" + - "@FriederikeHanssen" + - "@ramprasadn" +maintainers: + - "@drpatelh" + - "@FriederikeHanssen" + - "@ramprasadn" diff --git a/pipelines/nf-atacseq/modules/nf-core/samtools/stats/tests/main.nf.test b/pipelines/nf-atacseq/modules/nf-core/samtools/stats/tests/main.nf.test new file mode 100644 index 0000000..5bc8930 --- /dev/null +++ b/pipelines/nf-atacseq/modules/nf-core/samtools/stats/tests/main.nf.test @@ -0,0 +1,113 @@ +nextflow_process { + + name "Test Process SAMTOOLS_STATS" + script "../main.nf" + process "SAMTOOLS_STATS" + + tag "modules" + tag "modules_nfcore" + tag "samtools" + tag "samtools/stats" + + test("bam") { + + when { + process { + """ + input[0] = Channel.of([ + [ id:'test', single_end:false ], // meta map + file(params.modules_testdata_base_path + 'genomics/sarscov2/illumina/bam/test.paired_end.sorted.bam', checkIfExists: true), + file(params.modules_testdata_base_path + 'genomics/sarscov2/illumina/bam/test.paired_end.sorted.bam.bai', checkIfExists: true) + ]) + input[1] = [[],[]] + """ + } + } + + then { + assertAll( + {assert process.success}, + {assert snapshot(process.out).match()} + ) + } + } + + test("cram") { + + when { + process { + """ + input[0] = Channel.of([ + [ id:'test', single_end:false ], // meta map + file(params.modules_testdata_base_path + 'genomics/homo_sapiens/illumina/cram/test.paired_end.recalibrated.sorted.cram', checkIfExists: true), + file(params.modules_testdata_base_path + 'genomics/homo_sapiens/illumina/cram/test.paired_end.recalibrated.sorted.cram.crai', checkIfExists: true) + ]) + input[1] = Channel.of([ + [ id:'genome' ], // meta map + file(params.modules_testdata_base_path + 'genomics/homo_sapiens/genome/chr21/sequence/genome.fasta', checkIfExists: true) + ]) + """ + } + } + + then { + assertAll( + {assert process.success}, + {assert snapshot(process.out).match()} + ) + } + } + + test("bam - stub") { + + options "-stub" + + when { + process { + """ + input[0] = Channel.of([ + [ id:'test', single_end:false ], // meta map + file(params.modules_testdata_base_path + 'genomics/sarscov2/illumina/bam/test.paired_end.sorted.bam', checkIfExists: true), + file(params.modules_testdata_base_path + 'genomics/sarscov2/illumina/bam/test.paired_end.sorted.bam.bai', checkIfExists: true) + ]) + input[1] = [[],[]] + """ + } + } + + then { + assertAll( + {assert process.success}, + {assert snapshot(process.out).match()} + ) + } + } + + test("cram - stub") { + + options "-stub" + + when { + process { + """ + input[0] = Channel.of([ + [ id:'test', single_end:false ], // meta map + file(params.modules_testdata_base_path + 'genomics/homo_sapiens/illumina/cram/test.paired_end.recalibrated.sorted.cram', checkIfExists: true), + file(params.modules_testdata_base_path + 'genomics/homo_sapiens/illumina/cram/test.paired_end.recalibrated.sorted.cram.crai', checkIfExists: true) + ]) + input[1] = Channel.of([ + [ id:'genome' ], // meta map + file(params.modules_testdata_base_path + 'genomics/homo_sapiens/genome/chr21/sequence/genome.fasta', checkIfExists: true) + ]) + """ + } + } + + then { + assertAll( + {assert process.success}, + {assert snapshot(process.out).match()} + ) + } + } +} diff --git a/pipelines/nf-atacseq/modules/nf-core/samtools/stats/tests/main.nf.test.snap b/pipelines/nf-atacseq/modules/nf-core/samtools/stats/tests/main.nf.test.snap new file mode 100644 index 0000000..94d981b --- /dev/null +++ b/pipelines/nf-atacseq/modules/nf-core/samtools/stats/tests/main.nf.test.snap @@ -0,0 +1,174 @@ +{ + "cram": { + "content": [ + { + "0": [ + [ + { + "id": "test", + "single_end": false + }, + "test.stats:md5,f4aec6c41b73d34ac2fc6b3253aa39ba" + ] + ], + "1": [ + [ + "SAMTOOLS_STATS", + "samtools", + "1.22.1" + ] + ], + "stats": [ + [ + { + "id": "test", + "single_end": false + }, + "test.stats:md5,f4aec6c41b73d34ac2fc6b3253aa39ba" + ] + ], + "versions_samtools": [ + [ + "SAMTOOLS_STATS", + "samtools", + "1.22.1" + ] + ] + } + ], + "meta": { + "nf-test": "0.9.3", + "nextflow": "25.10.0" + }, + "timestamp": "2025-11-01T02:27:18.460724" + }, + "bam - stub": { + "content": [ + { + "0": [ + [ + { + "id": "test", + "single_end": false + }, + "test.stats:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "1": [ + [ + "SAMTOOLS_STATS", + "samtools", + "1.22.1" + ] + ], + "stats": [ + [ + { + "id": "test", + "single_end": false + }, + "test.stats:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "versions_samtools": [ + [ + "SAMTOOLS_STATS", + "samtools", + "1.22.1" + ] + ] + } + ], + "meta": { + "nf-test": "0.9.3", + "nextflow": "25.10.0" + }, + "timestamp": "2025-11-01T02:27:30.245839" + }, + "cram - stub": { + "content": [ + { + "0": [ + [ + { + "id": "test", + "single_end": false + }, + "test.stats:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "1": [ + [ + "SAMTOOLS_STATS", + "samtools", + "1.22.1" + ] + ], + "stats": [ + [ + { + "id": "test", + "single_end": false + }, + "test.stats:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "versions_samtools": [ + [ + "SAMTOOLS_STATS", + "samtools", + "1.22.1" + ] + ] + } + ], + "meta": { + "nf-test": "0.9.3", + "nextflow": "25.10.0" + }, + "timestamp": "2025-11-01T02:27:39.041649" + }, + "bam": { + "content": [ + { + "0": [ + [ + { + "id": "test", + "single_end": false + }, + "test.stats:md5,41ba8ad30ddb598dadb177a54c222ab9" + ] + ], + "1": [ + [ + "SAMTOOLS_STATS", + "samtools", + "1.22.1" + ] + ], + "stats": [ + [ + { + "id": "test", + "single_end": false + }, + "test.stats:md5,41ba8ad30ddb598dadb177a54c222ab9" + ] + ], + "versions_samtools": [ + [ + "SAMTOOLS_STATS", + "samtools", + "1.22.1" + ] + ] + } + ], + "meta": { + "nf-test": "0.9.3", + "nextflow": "25.10.0" + }, + "timestamp": "2025-11-01T02:26:55.988241" + } +} \ No newline at end of file diff --git a/pipelines/nf-atacseq/nextflow.config b/pipelines/nf-atacseq/nextflow.config index e844cbf..1357933 100644 --- a/pipelines/nf-atacseq/nextflow.config +++ b/pipelines/nf-atacseq/nextflow.config @@ -15,6 +15,8 @@ plugins { manifest { name = 'wasp2/nf-atacseq' author = 'WASP2 Team' + homePage = 'https://github.com/mcvickerlab/WASP2' + doi = 'https://doi.org/10.1038/nmeth.3582' description = 'ATAC-seq Allelic Imbalance Pipeline with WASP2 mapping bias correction' mainScript = 'main.nf' nextflowVersion = '!>=23.04.0' @@ -60,11 +62,6 @@ params { skip_peak_calling = false // Require peaks parameter if true skip_multiqc = false - // Resource limits - max_cpus = 16 - max_memory = '128.GB' - max_time = '240.h' - // Institutional config support (nf-core compatible) custom_config_base = 'https://raw.githubusercontent.com/nf-core/configs/master' custom_config_version = 'master' @@ -73,17 +70,18 @@ params { help = false version = false tracedir = "${params.outdir}/pipeline_info" + validate_params = true } // Load configuration files includeConfig 'conf/base.config' includeConfig 'conf/modules.config' -// Load nf-core institutional configs +// Load nf-core custom profiles from https://github.com/nf-core/configs try { - includeConfig "${params.custom_config_base}/nfcore_custom.config" + includeConfig params.custom_config_base && (!System.getenv('NXF_OFFLINE') || !params.custom_config_base.startsWith('http')) ? "${params.custom_config_base}/nfcore_custom.config" : "/dev/null" } catch (Exception e) { - System.err.println("WARNING: Could not load nf-core/configs: ${params.custom_config_base}") + System.err.println("WARNING: Could not load nf-core/config profiles: ${params.custom_config_base}") } // Execution profiles @@ -97,7 +95,6 @@ profiles { conda.enabled = true docker.enabled = false singularity.enabled = false - process.conda = "${projectDir}/../../environment.yml" } docker { docker.enabled = true @@ -153,15 +150,15 @@ profiles { def trace_timestamp = new java.util.Date().format('yyyy-MM-dd_HH-mm-ss') timeline { enabled = true - file = "${params.tracedir}/execution_timeline_${trace_timestamp}.html" + file = "${params.tracedir}/timeline_${trace_timestamp}.html" } report { enabled = true - file = "${params.tracedir}/execution_report_${trace_timestamp}.html" + file = "${params.tracedir}/report_${trace_timestamp}.html" } trace { enabled = true - file = "${params.tracedir}/execution_trace_${trace_timestamp}.txt" + file = "${params.tracedir}/trace_${trace_timestamp}.txt" } dag { enabled = true @@ -184,7 +181,7 @@ process { withName: 'WASP2_MAKE_READS|WASP2_FILTER_REMAPPED|WASP2_COUNT_VARIANTS|WASP2_FIND_IMBALANCE' { container = wasp2_container } - withName: 'BWA_MEM' { + withName: 'BWA_INDEX|BWA_MEM' { container = bwa_samtools_container } withName: 'SAMTOOLS_INDEX|SAMTOOLS_FAIDX|SAMTOOLS_STATS|SAMTOOLS_FLAGSTAT|SAMTOOLS_IDXSTATS|SAMTOOLS_SORT' { @@ -196,32 +193,33 @@ process { process.shell = ['/bin/bash', '-euo', 'pipefail'] // Function to ensure resources don't exceed limits +// Resource capping is handled by process.resourceLimits in conf/base.config. +// This function is retained for backward compatibility with process label closures. def check_max(obj, type) { if (type == 'memory') { try { - if (obj.compareTo(params.max_memory as nextflow.util.MemoryUnit) == 1) - return params.max_memory as nextflow.util.MemoryUnit - else - return obj - } catch (all) { - println "WARNING: Invalid max_memory '${params.max_memory}', using default" + def max = (params.max_memory as nextflow.util.MemoryUnit) ?: 128.GB + if (obj.compareTo(max) == 1) + return max + else return obj + } catch (Exception e) { + log.warn "Invalid memory config: ${e.message}. Using ${obj}" return obj } } else if (type == 'time') { try { - if (obj.compareTo(params.max_time as nextflow.util.Duration) == 1) - return params.max_time as nextflow.util.Duration - else - return obj - } catch (all) { - println "WARNING: Invalid max_time '${params.max_time}', using default" + def max = (params.max_time as nextflow.util.Duration) ?: 240.h + if (obj.compareTo(max) == 1) + return max + else return obj + } catch (Exception e) { + log.warn "Invalid time config: ${e.message}. Using ${obj}" return obj } } else if (type == 'cpus') { - try { - return Math.min(obj, params.max_cpus as int) - } catch (all) { - println "WARNING: Invalid max_cpus '${params.max_cpus}', using default" + try { return Math.min(obj, (params.max_cpus ?: 16) as int) } + catch (Exception e) { + log.warn "Invalid CPU config: ${e.message}. Using ${obj}" return obj } } diff --git a/pipelines/nf-atacseq/nextflow_schema.json b/pipelines/nf-atacseq/nextflow_schema.json index d36e09b..57cfc51 100644 --- a/pipelines/nf-atacseq/nextflow_schema.json +++ b/pipelines/nf-atacseq/nextflow_schema.json @@ -226,32 +226,25 @@ } } }, - "max_job_request_options": { - "title": "Max resource options", + "institutional_config_options": { + "title": "Institutional config options", "type": "object", - "fa_icon": "fas fa-server", - "description": "Set the maximum resource limits for pipeline processes.", + "fa_icon": "fas fa-university", + "description": "Parameters used to describe centralised config profiles. These should not be edited.", "properties": { - "max_cpus": { - "type": "integer", - "default": 16, - "minimum": 1, - "description": "Maximum number of CPUs that can be requested for any single process.", - "fa_icon": "fas fa-microchip" - }, - "max_memory": { + "custom_config_base": { "type": "string", - "default": "128.GB", - "pattern": "^\\d+(\\.\\d+)?\\.?\\s*(K|M|G|T)?B$", - "description": "Maximum amount of memory that can be requested for any single process.", - "fa_icon": "fas fa-memory" + "default": "https://raw.githubusercontent.com/nf-core/configs/master", + "description": "Base URL for loading nf-core custom config profiles.", + "hidden": true, + "fa_icon": "fas fa-users-cog" }, - "max_time": { + "custom_config_version": { "type": "string", - "default": "240.h", - "pattern": "^(\\d+\\.?\\s*(s|m|h|d)\\.?\\s*)+$", - "description": "Maximum amount of time that can be requested for any single process.", - "fa_icon": "fas fa-clock" + "default": "master", + "description": "Git tag/branch for nf-core custom config profiles.", + "hidden": true, + "fa_icon": "fas fa-users-cog" } } }, @@ -277,9 +270,16 @@ }, "tracedir": { "type": "string", - "default": "${params.outdir}/pipeline_info", + "default": "./results/pipeline_info", "description": "Directory to keep pipeline Nextflow trace, timeline, report, and DAG files.", "fa_icon": "fas fa-folder" + }, + "validate_params": { + "type": "boolean", + "default": true, + "description": "Boolean whether to validate parameters against the schema at runtime.", + "fa_icon": "fas fa-check-square", + "hidden": true } } } @@ -292,7 +292,7 @@ { "$ref": "#/definitions/aligner_options" }, { "$ref": "#/definitions/wasp2_options" }, { "$ref": "#/definitions/processing_options" }, - { "$ref": "#/definitions/max_job_request_options" }, + { "$ref": "#/definitions/institutional_config_options" }, { "$ref": "#/definitions/generic_options" } ] } diff --git a/pipelines/nf-atacseq/nf-test.config b/pipelines/nf-atacseq/nf-test.config index 32f4307..d1d396c 100644 --- a/pipelines/nf-atacseq/nf-test.config +++ b/pipelines/nf-atacseq/nf-test.config @@ -11,5 +11,6 @@ config { copy "modules/**" copy "subworkflows/**" copy "workflows/**" + copy "tests/**" } } diff --git a/pipelines/nf-atacseq/subworkflows/local/prepare_genome/main.nf b/pipelines/nf-atacseq/subworkflows/local/prepare_genome/main.nf index 965537a..afcc968 100644 --- a/pipelines/nf-atacseq/subworkflows/local/prepare_genome/main.nf +++ b/pipelines/nf-atacseq/subworkflows/local/prepare_genome/main.nf @@ -3,7 +3,7 @@ // include { BWA_INDEX } from '../../../modules/nf-core/bwa/index/main' -include { BOWTIE2_BUILD } from '../../../modules/nf-core/bowtie2/index/main' +include { BOWTIE2_BUILD } from '../../../modules/nf-core/bowtie2/build/main' include { SAMTOOLS_FAIDX } from '../../../modules/nf-core/samtools/faidx/main' workflow PREPARE_GENOME { @@ -32,9 +32,8 @@ workflow PREPARE_GENOME { ch_fasta_fai = Channel.fromPath(params.fasta_fai, checkIfExists: true) .map { fai -> [[id: file(params.fasta).baseName], fai] } } else { - SAMTOOLS_FAIDX ( ch_fasta ) + SAMTOOLS_FAIDX ( ch_fasta.map { meta, fasta -> [meta, fasta, []] }, false ) ch_fasta_fai = SAMTOOLS_FAIDX.out.fai - ch_versions = ch_versions.mix(SAMTOOLS_FAIDX.out.versions) } // @@ -47,7 +46,6 @@ workflow PREPARE_GENOME { } else { BWA_INDEX ( ch_fasta ) ch_bwa_index = BWA_INDEX.out.index.map { meta, index -> index } - ch_versions = ch_versions.mix(BWA_INDEX.out.versions) } } @@ -61,7 +59,6 @@ workflow PREPARE_GENOME { } else { BOWTIE2_BUILD ( ch_fasta ) ch_bowtie2_index = BOWTIE2_BUILD.out.index.map { meta, index -> index } - ch_versions = ch_versions.mix(BOWTIE2_BUILD.out.versions) } } diff --git a/pipelines/nf-atacseq/subworkflows/local/wasp_mapping/main.nf b/pipelines/nf-atacseq/subworkflows/local/wasp_mapping/main.nf index 298d74c..908d3f6 100644 --- a/pipelines/nf-atacseq/subworkflows/local/wasp_mapping/main.nf +++ b/pipelines/nf-atacseq/subworkflows/local/wasp_mapping/main.nf @@ -26,6 +26,10 @@ workflow WASP_MAPPING { main: ch_versions = Channel.empty() + // Wrap plain path channels with meta for nf-core modules + ch_index_meta = ch_aligner_index.map { index -> [[id: 'genome'], index] } + ch_fasta_meta = ch_fasta.map { fasta -> [[id: 'genome'], fasta] } + // // MODULE: Generate reads with swapped alleles for remapping // @@ -53,29 +57,26 @@ workflow WASP_MAPPING { if (aligner == 'bwa') { BWA_MEM( ch_remap_reads, - ch_aligner_index, - ch_fasta, + ch_index_meta, + ch_fasta_meta, true // sort_bam ) ch_remapped_raw = BWA_MEM.out.bam - ch_versions = ch_versions.mix(BWA_MEM.out.versions.first()) } else { BOWTIE2_ALIGN( ch_remap_reads, - ch_aligner_index, - ch_fasta, + ch_index_meta, + ch_fasta_meta, false, // save_unaligned true // sort_bam ) - ch_remapped_raw = BOWTIE2_ALIGN.out.aligned - ch_versions = ch_versions.mix(BOWTIE2_ALIGN.out.versions.first()) + ch_remapped_raw = BOWTIE2_ALIGN.out.bam } // // MODULE: Index remapped BAM (aligners already sort when sort_bam=true) // SAMTOOLS_INDEX(ch_remapped_raw) - ch_versions = ch_versions.mix(SAMTOOLS_INDEX.out.versions.first()) // Combine BAM with index ch_remapped = ch_remapped_raw diff --git a/pipelines/nf-atacseq/subworkflows/nf-core/bam_markduplicates_picard/main.nf b/pipelines/nf-atacseq/subworkflows/nf-core/bam_markduplicates_picard/main.nf index 03e8241..c33064e 100644 --- a/pipelines/nf-atacseq/subworkflows/nf-core/bam_markduplicates_picard/main.nf +++ b/pipelines/nf-atacseq/subworkflows/nf-core/bam_markduplicates_picard/main.nf @@ -1,49 +1,46 @@ // -// Mark duplicates with Picard and run BAM stats +// Picard MarkDuplicates, index BAM file and run samtools stats, flagstat and idxstats // include { PICARD_MARKDUPLICATES } from '../../../modules/nf-core/picard/markduplicates/main' +include { SAMTOOLS_INDEX } from '../../../modules/nf-core/samtools/index/main' include { BAM_STATS_SAMTOOLS } from '../bam_stats_samtools/main' workflow BAM_MARKDUPLICATES_PICARD { + take: - ch_bam // channel: [ val(meta), path(bam) ] - ch_fasta // channel: path(fasta) - ch_fai // channel: path(fasta_fai) + ch_reads // channel: [ val(meta), path(reads) ] + ch_fasta // channel: [ val(meta), path(fasta) ] + ch_fai // channel: [ val(meta), path(fai) ] main: - ch_versions = Channel.empty() - - // - // Mark duplicates with Picard - // - PICARD_MARKDUPLICATES ( - ch_bam, - ch_fasta, - ch_fai - ) - ch_versions = ch_versions.mix(PICARD_MARKDUPLICATES.out.versions.first()) - - // - // Join BAM and BAI for stats - // - ch_bam_bai = PICARD_MARKDUPLICATES.out.bam - .join(PICARD_MARKDUPLICATES.out.bai, by: [0], failOnMismatch: true) - - // - // Run BAM stats - // - BAM_STATS_SAMTOOLS ( ch_bam_bai, ch_fasta ) - ch_versions = ch_versions.mix(BAM_STATS_SAMTOOLS.out.versions) + PICARD_MARKDUPLICATES ( ch_reads, ch_fasta, ch_fai ) - emit: - bam = PICARD_MARKDUPLICATES.out.bam // channel: [ val(meta), path(bam) ] - bai = PICARD_MARKDUPLICATES.out.bai // channel: [ val(meta), path(bai) ] - metrics = PICARD_MARKDUPLICATES.out.metrics // channel: [ val(meta), path(metrics) ] + ch_markdup = PICARD_MARKDUPLICATES.out.bam.mix(PICARD_MARKDUPLICATES.out.cram) + + SAMTOOLS_INDEX ( ch_markdup ) - stats = BAM_STATS_SAMTOOLS.out.stats // channel: [ val(meta), path(stats) ] - flagstat = BAM_STATS_SAMTOOLS.out.flagstat // channel: [ val(meta), path(flagstat) ] - idxstats = BAM_STATS_SAMTOOLS.out.idxstats // channel: [ val(meta), path(idxstats) ] + ch_reads_index = ch_markdup + .join(SAMTOOLS_INDEX.out.bai, by: [0], remainder: true) + .join(SAMTOOLS_INDEX.out.crai, by: [0], remainder: true) + .join(SAMTOOLS_INDEX.out.csi, by: [0], remainder: true) + .map{meta, reads, bai, crai, csi -> + if (bai) [ meta, reads, bai ] + else if (crai) [ meta, reads, crai ] + else [ meta, reads, csi ] + } - versions = ch_versions // channel: path(versions.yml) + BAM_STATS_SAMTOOLS ( ch_reads_index, ch_fasta ) + + emit: + bam = PICARD_MARKDUPLICATES.out.bam // channel: [ val(meta), path(bam) ] + cram = PICARD_MARKDUPLICATES.out.cram // channel: [ val(meta), path(cram) ] + metrics = PICARD_MARKDUPLICATES.out.metrics // channel: [ val(meta), path(metrics) ] + bai = SAMTOOLS_INDEX.out.bai // channel: [ val(meta), path(bai) ] + crai = SAMTOOLS_INDEX.out.crai // channel: [ val(meta), path(crai) ] + csi = SAMTOOLS_INDEX.out.csi // channel: [ val(meta), path(csi) ] + + stats = BAM_STATS_SAMTOOLS.out.stats // channel: [ val(meta), path(stats) ] + flagstat = BAM_STATS_SAMTOOLS.out.flagstat // channel: [ val(meta), path(flagstat) ] + idxstats = BAM_STATS_SAMTOOLS.out.idxstats // channel: [ val(meta), path(idxstats) ] } diff --git a/pipelines/nf-atacseq/subworkflows/nf-core/bam_markduplicates_picard/meta.yml b/pipelines/nf-atacseq/subworkflows/nf-core/bam_markduplicates_picard/meta.yml index 1b08bb0..433d35b 100644 --- a/pipelines/nf-atacseq/subworkflows/nf-core/bam_markduplicates_picard/meta.yml +++ b/pipelines/nf-atacseq/subworkflows/nf-core/bam_markduplicates_picard/meta.yml @@ -1,78 +1,71 @@ # yaml-language-server: $schema=https://raw.githubusercontent.com/nf-core/modules/master/subworkflows/yaml-schema.json name: "bam_markduplicates_picard" -description: Mark duplicates with Picard and collect BAM statistics +description: Picard MarkDuplicates, index BAM file and run samtools stats, flagstat and idxstats keywords: + - markduplicates - bam - - duplicates - - picard - - dedup - - qc + - sam + - cram components: - picard/markduplicates + - samtools/index + - samtools/stats + - samtools/idxstats + - samtools/flagstat - bam_stats_samtools input: - - ch_bam: - type: channel + - ch_reads: description: | - Channel containing BAM files to deduplicate - Structure: [ val(meta), path(bam) ] - pattern: "*.bam" + Sequence reads in BAM/CRAM/SAM format + Structure: [ val(meta), path(reads) ] - ch_fasta: - type: channel description: | - Channel containing reference FASTA - Structure: path(fasta) - pattern: "*.{fa,fasta,fa.gz,fasta.gz}" - - ch_fai: - type: channel + Reference genome fasta file required for CRAM input + Structure: [ path(fasta) ] + - ch_fasta: description: | - Channel containing FASTA index - Structure: path(fasta.fai) - pattern: "*.fai" + Index of the reference genome fasta file + Structure: [ path(fai) ] output: - bam: - type: channel description: | - Deduplicated BAM file + processed BAM/SAM file Structure: [ val(meta), path(bam) ] - pattern: "*.markdup.bam" - bai: - type: channel description: | - BAM index file + BAM/SAM samtools index Structure: [ val(meta), path(bai) ] - pattern: "*.bai" - - metrics: - type: channel + - cram: + description: | + processed CRAM file + Structure: [ val(meta), path(cram) ] + - crai: + description: | + CRAM samtools index + Structure: [ val(meta), path(crai) ] + - csi: description: | - Picard MarkDuplicates metrics - Structure: [ val(meta), path(metrics) ] - pattern: "*.metrics.txt" + CSI samtools index + Structure: [ val(meta), path(csi) ] - stats: - type: channel description: | - Samtools stats output + File containing samtools stats output Structure: [ val(meta), path(stats) ] - pattern: "*.stats" - flagstat: - type: channel description: | - Samtools flagstat output + File containing samtools flagstat output Structure: [ val(meta), path(flagstat) ] - pattern: "*.flagstat" - idxstats: - type: channel description: | - Samtools idxstats output + File containing samtools idxstats output Structure: [ val(meta), path(idxstats) ] - pattern: "*.idxstats" - versions: - type: channel description: | - Version information + Files containing software versions Structure: [ path(versions.yml) ] - pattern: "versions.yml" authors: - - "@jjaureguy760" + - "@dmarron" + - "@drpatelh" maintainers: - - "@jjaureguy760" + - "@dmarron" + - "@drpatelh" diff --git a/pipelines/nf-atacseq/subworkflows/nf-core/bam_markduplicates_picard/tests/main.nf.test b/pipelines/nf-atacseq/subworkflows/nf-core/bam_markduplicates_picard/tests/main.nf.test new file mode 100644 index 0000000..816ff3e --- /dev/null +++ b/pipelines/nf-atacseq/subworkflows/nf-core/bam_markduplicates_picard/tests/main.nf.test @@ -0,0 +1,155 @@ +nextflow_workflow { + + name "Test Workflow BAM_MARKDUPLICATES_PICARD" + script "../main.nf" + workflow "BAM_MARKDUPLICATES_PICARD" + config "./nextflow.config" + + tag "picard" + tag "picard/markduplicates" + tag "subworkflows" + tag "subworkflows_nfcore" + tag "bam_markduplicates_picard" + tag "subworkflows/bam_markduplicates_picard" + tag "subworkflows/bam_stats_samtools" + tag "bam_stats_samtools" + tag "samtools" + tag "samtools/flagstat" + tag "samtools/idxstats" + tag "samtools/index" + tag "samtools/stats" + + test("sarscov2 - bam") { + + when { + workflow { + """ + input[0] = Channel.of([ + [ id:'test', single_end: false ], // meta map + file(params.modules_testdata_base_path + 'genomics/sarscov2/illumina/bam/test.paired_end.sorted.bam', checkIfExists: true) + ]) + input[1] = Channel.of([ + [ id:'genome' ], + file(params.modules_testdata_base_path + 'genomics/sarscov2/genome/genome.fasta', checkIfExists: true) + ]) + input[2] = Channel.of([ + [ id:'genome' ], + file(params.modules_testdata_base_path + 'genomics/sarscov2/genome/genome.fasta.fai', checkIfExists: true) + ]) + """ + } + } + + then { + assertAll( + { assert workflow.success}, + { assert path(workflow.out.metrics.get(0).get(1)).getText().contains("97") }, + { assert snapshot( + path(workflow.out.bam[0][1]), + path(workflow.out.bai[0][1]), + path(workflow.out.flagstat[0][1]), + path(workflow.out.idxstats[0][1]), + path(workflow.out.stats[0][1]) + ).match() } + ) + } + } + + test("homo_sapiens - cram") { + + when { + workflow { + """ + input[0] = Channel.of([ + [ id:'test' ], // meta map + file(params.modules_testdata_base_path + 'genomics/homo_sapiens/illumina/cram/test.paired_end.sorted.cram', checkIfExists: true) + ]) + input[1] = Channel.of([ + [ id:'genome' ], + file(params.modules_testdata_base_path + 'genomics/homo_sapiens/genome/genome.fasta', checkIfExists: true) + ]) + input[2] = Channel.of([ + [ id:'genome' ], + file(params.modules_testdata_base_path + 'genomics/homo_sapiens/genome/genome.fasta.fai', checkIfExists: true) + ]) + """ + } + } + + then { + assertAll( + { assert workflow.success}, + { assert path(workflow.out.metrics.get(0).get(1)).getText().contains("0.999986") }, + { assert snapshot( + file(workflow.out.cram[0][1]).name, + path(workflow.out.crai[0][1]), + path(workflow.out.flagstat[0][1]), + path(workflow.out.idxstats[0][1]), + path(workflow.out.stats[0][1]) + ).match() } + ) + } + } + + test("sarscov2 - bam - stub") { + + options "-stub" + + when { + workflow { + """ + input[0] = Channel.of([ + [ id:'test', single_end: false ], // meta map + file(params.modules_testdata_base_path + 'genomics/sarscov2/illumina/bam/test.paired_end.sorted.bam', checkIfExists: true) + ]) + input[1] = Channel.of([ + [ id:'genome' ], + file(params.modules_testdata_base_path + 'genomics/sarscov2/genome/genome.fasta', checkIfExists: true) + ]) + input[2] = Channel.of([ + [ id:'genome' ], + file(params.modules_testdata_base_path + 'genomics/sarscov2/genome/genome.fasta.fai', checkIfExists: true) + ]) + """ + } + } + + then { + assertAll( + { assert workflow.success}, + { assert snapshot(workflow.out).match() } + ) + } + } + + test("homo_sapiens - cram - stub") { + + options "-stub" + + when { + workflow { + """ + input[0] = Channel.of([ + [ id:'test' ], // meta map + file(params.modules_testdata_base_path + 'genomics/homo_sapiens/illumina/cram/test.paired_end.sorted.cram', checkIfExists: true) + ]) + input[1] = Channel.of([ + [ id:'genome' ], + file(params.modules_testdata_base_path + 'genomics/homo_sapiens/genome/genome.fasta', checkIfExists: true) + ]) + input[2] = Channel.of([ + [ id:'genome' ], + file(params.modules_testdata_base_path + 'genomics/homo_sapiens/genome/genome.fasta.fai', checkIfExists: true) + ]) + """ + } + } + + then { + assertAll( + { assert workflow.success}, + { assert snapshot(workflow.out).match() } + ) + } + } +} diff --git a/pipelines/nf-atacseq/subworkflows/nf-core/bam_markduplicates_picard/tests/main.nf.test.snap b/pipelines/nf-atacseq/subworkflows/nf-core/bam_markduplicates_picard/tests/main.nf.test.snap new file mode 100644 index 0000000..bfa595e --- /dev/null +++ b/pipelines/nf-atacseq/subworkflows/nf-core/bam_markduplicates_picard/tests/main.nf.test.snap @@ -0,0 +1,292 @@ +{ + "homo_sapiens - cram": { + "content": [ + "test.md.cram", + "test.md.cram.crai:md5,b641c19be42d4841ec7155c686b70f39", + "test.flagstat:md5,93b0ef463df947ede1f42ff60396c34d", + "test.idxstats:md5,e179601fa7b8ebce81ac3765206f6c15", + "test.stats:md5,8ec963e4ee888c8cc9d41348cedd5106" + ], + "timestamp": "2026-02-19T19:00:47.4418381", + "meta": { + "nf-test": "0.9.4", + "nextflow": "25.10.4" + } + }, + "sarscov2 - bam - stub": { + "content": [ + { + "0": [ + [ + { + "id": "test", + "single_end": false + }, + "test.md.bam:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "1": [ + + ], + "2": [ + [ + { + "id": "test", + "single_end": false + }, + "test.md.metrics.txt:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "3": [ + [ + { + "id": "test", + "single_end": false + }, + "test.md.bam.bai:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "4": [ + + ], + "5": [ + + ], + "6": [ + [ + { + "id": "test", + "single_end": false + }, + "test.stats:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "7": [ + [ + { + "id": "test", + "single_end": false + }, + "test.flagstat:md5,67394650dbae96d1a4fcc70484822159" + ] + ], + "8": [ + [ + { + "id": "test", + "single_end": false + }, + "test.idxstats:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "bai": [ + [ + { + "id": "test", + "single_end": false + }, + "test.md.bam.bai:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "bam": [ + [ + { + "id": "test", + "single_end": false + }, + "test.md.bam:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "crai": [ + + ], + "cram": [ + + ], + "csi": [ + + ], + "flagstat": [ + [ + { + "id": "test", + "single_end": false + }, + "test.flagstat:md5,67394650dbae96d1a4fcc70484822159" + ] + ], + "idxstats": [ + [ + { + "id": "test", + "single_end": false + }, + "test.idxstats:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "metrics": [ + [ + { + "id": "test", + "single_end": false + }, + "test.md.metrics.txt:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "stats": [ + [ + { + "id": "test", + "single_end": false + }, + "test.stats:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ] + } + ], + "timestamp": "2026-02-19T19:00:56.802484512", + "meta": { + "nf-test": "0.9.4", + "nextflow": "25.10.4" + } + }, + "homo_sapiens - cram - stub": { + "content": [ + { + "0": [ + + ], + "1": [ + [ + { + "id": "test" + }, + "test.md.cram:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "2": [ + [ + { + "id": "test" + }, + "test.md.metrics.txt:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "3": [ + + ], + "4": [ + [ + { + "id": "test" + }, + "test.md.cram.crai:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "5": [ + + ], + "6": [ + [ + { + "id": "test" + }, + "test.stats:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "7": [ + [ + { + "id": "test" + }, + "test.flagstat:md5,67394650dbae96d1a4fcc70484822159" + ] + ], + "8": [ + [ + { + "id": "test" + }, + "test.idxstats:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "bai": [ + + ], + "bam": [ + + ], + "crai": [ + [ + { + "id": "test" + }, + "test.md.cram.crai:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "cram": [ + [ + { + "id": "test" + }, + "test.md.cram:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "csi": [ + + ], + "flagstat": [ + [ + { + "id": "test" + }, + "test.flagstat:md5,67394650dbae96d1a4fcc70484822159" + ] + ], + "idxstats": [ + [ + { + "id": "test" + }, + "test.idxstats:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "metrics": [ + [ + { + "id": "test" + }, + "test.md.metrics.txt:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "stats": [ + [ + { + "id": "test" + }, + "test.stats:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ] + } + ], + "timestamp": "2026-02-19T19:01:05.884074864", + "meta": { + "nf-test": "0.9.4", + "nextflow": "25.10.4" + } + }, + "sarscov2 - bam": { + "content": [ + "test.md.bam:md5,8aa8fc57298588fed0b03aacddd7ea77", + "test.md.bam.bai:md5,8973dd987f3ac6c352716ef89139c567", + "test.flagstat:md5,4f7ffd1e6a5e85524d443209ac97d783", + "test.idxstats:md5,df60a8c8d6621100d05178c93fb053a2", + "test.stats:md5,950c07a54b20e443105a5391400a4c92" + ], + "timestamp": "2026-02-19T19:00:36.539092187", + "meta": { + "nf-test": "0.9.4", + "nextflow": "25.10.4" + } + } +} \ No newline at end of file diff --git a/pipelines/nf-atacseq/subworkflows/nf-core/bam_markduplicates_picard/tests/nextflow.config b/pipelines/nf-atacseq/subworkflows/nf-core/bam_markduplicates_picard/tests/nextflow.config new file mode 100644 index 0000000..2427cc4 --- /dev/null +++ b/pipelines/nf-atacseq/subworkflows/nf-core/bam_markduplicates_picard/tests/nextflow.config @@ -0,0 +1,5 @@ +process { + withName: 'PICARD_MARKDUPLICATES' { + ext.prefix = { "${meta.id}.md" } + } +} diff --git a/pipelines/nf-atacseq/subworkflows/nf-core/bam_sort_stats_samtools/main.nf b/pipelines/nf-atacseq/subworkflows/nf-core/bam_sort_stats_samtools/main.nf index 42fa4d6..312c2d2 100644 --- a/pipelines/nf-atacseq/subworkflows/nf-core/bam_sort_stats_samtools/main.nf +++ b/pipelines/nf-atacseq/subworkflows/nf-core/bam_sort_stats_samtools/main.nf @@ -1,5 +1,5 @@ // -// Sort, index BAM file and run samtools stats, flagstat +// Sort, index BAM file and run samtools stats, flagstat and idxstats // include { SAMTOOLS_SORT } from '../../../modules/nf-core/samtools/sort/main' @@ -8,43 +8,35 @@ include { BAM_STATS_SAMTOOLS } from '../bam_stats_samtools/main' workflow BAM_SORT_STATS_SAMTOOLS { take: - ch_bam // channel: [ val(meta), path(bam) ] - ch_fasta // channel: path(fasta) + ch_bam // channel: [ val(meta), [ bam ] ] + ch_fasta // channel: [ val(meta), path(fasta) ] main: - ch_versions = Channel.empty() + SAMTOOLS_SORT ( ch_bam, ch_fasta, '' ) - // - // Sort BAM file - // - SAMTOOLS_SORT ( ch_bam, ch_fasta ) - ch_versions = ch_versions.mix(SAMTOOLS_SORT.out.versions.first()) - - // - // Index sorted BAM file - // SAMTOOLS_INDEX ( SAMTOOLS_SORT.out.bam ) - ch_versions = ch_versions.mix(SAMTOOLS_INDEX.out.versions.first()) - // - // Join BAM and BAI for stats - // - ch_bam_bai = SAMTOOLS_SORT.out.bam - .join(SAMTOOLS_INDEX.out.bai, by: [0], failOnMismatch: true) + SAMTOOLS_SORT.out.bam + .join(SAMTOOLS_INDEX.out.bai, by: [0], remainder: true) + .join(SAMTOOLS_INDEX.out.csi, by: [0], remainder: true) + .map { + meta, bam, bai, csi -> + if (bai) { + [ meta, bam, bai ] + } else { + [ meta, bam, csi ] + } + } + .set { ch_bam_bai } - // - // Run samtools stats and flagstat - // BAM_STATS_SAMTOOLS ( ch_bam_bai, ch_fasta ) - ch_versions = ch_versions.mix(BAM_STATS_SAMTOOLS.out.versions) emit: - bam = SAMTOOLS_SORT.out.bam // channel: [ val(meta), path(bam) ] - bai = SAMTOOLS_INDEX.out.bai // channel: [ val(meta), path(bai) ] - - stats = BAM_STATS_SAMTOOLS.out.stats // channel: [ val(meta), path(stats) ] - flagstat = BAM_STATS_SAMTOOLS.out.flagstat // channel: [ val(meta), path(flagstat) ] - idxstats = BAM_STATS_SAMTOOLS.out.idxstats // channel: [ val(meta), path(idxstats) ] + bam = SAMTOOLS_SORT.out.bam // channel: [ val(meta), [ bam ] ] + bai = SAMTOOLS_INDEX.out.bai // channel: [ val(meta), [ bai ] ] + csi = SAMTOOLS_INDEX.out.csi // channel: [ val(meta), [ csi ] ] - versions = ch_versions // channel: path(versions.yml) + stats = BAM_STATS_SAMTOOLS.out.stats // channel: [ val(meta), [ stats ] ] + flagstat = BAM_STATS_SAMTOOLS.out.flagstat // channel: [ val(meta), [ flagstat ] ] + idxstats = BAM_STATS_SAMTOOLS.out.idxstats // channel: [ val(meta), [ idxstats ] ] } diff --git a/pipelines/nf-atacseq/subworkflows/nf-core/bam_sort_stats_samtools/meta.yml b/pipelines/nf-atacseq/subworkflows/nf-core/bam_sort_stats_samtools/meta.yml index 08b172a..e01f9cc 100644 --- a/pipelines/nf-atacseq/subworkflows/nf-core/bam_sort_stats_samtools/meta.yml +++ b/pipelines/nf-atacseq/subworkflows/nf-core/bam_sort_stats_samtools/meta.yml @@ -1,66 +1,70 @@ # yaml-language-server: $schema=https://raw.githubusercontent.com/nf-core/modules/master/subworkflows/yaml-schema.json -name: "bam_sort_stats_samtools" -description: Sort BAM files and collect statistics with samtools +name: bam_sort_stats_samtools +description: Sort SAM/BAM/CRAM file keywords: - - bam - sort - - statistics - - samtools + - bam + - sam + - cram components: - samtools/sort - samtools/index + - samtools/stats + - samtools/idxstats + - samtools/flagstat - bam_stats_samtools input: - - ch_bam: - type: channel - description: | - Channel containing unsorted BAM files - Structure: [ val(meta), path(bam) ] - pattern: "*.bam" - - ch_fasta: - type: channel + - meta: + type: map description: | - Channel containing reference FASTA for stats calculation - Structure: path(fasta) - pattern: "*.{fa,fasta,fa.gz,fasta.gz}" -output: + Groovy Map containing sample information + e.g. [ id:'test', single_end:false ] - bam: - type: channel + type: file + description: BAM/CRAM/SAM file + pattern: "*.{bam,cram,sam}" + - fasta: + type: file + description: Reference genome fasta file + pattern: "*.{fasta,fa}" +# TODO Update when we decide on a standard for subworkflow docs +output: + - meta: + type: map description: | - Sorted BAM file - Structure: [ val(meta), path(bam) ] - pattern: "*.sorted.bam" + Groovy Map containing sample information + e.g. [ id:'test', single_end:false ] + - bam: + type: file + description: Sorted BAM/CRAM/SAM file + pattern: "*.{bam,cram,sam}" - bai: - type: channel - description: | - BAM index file - Structure: [ val(meta), path(bai) ] - pattern: "*.bai" + type: file + description: BAM/CRAM/SAM index file + pattern: "*.{bai,crai,sai}" + - crai: + type: file + description: BAM/CRAM/SAM index file + pattern: "*.{bai,crai,sai}" - stats: - type: channel - description: | - Samtools stats output - Structure: [ val(meta), path(stats) ] - pattern: "*.stats" + type: file + description: File containing samtools stats output + pattern: "*.{stats}" - flagstat: - type: channel - description: | - Samtools flagstat output - Structure: [ val(meta), path(flagstat) ] - pattern: "*.flagstat" + type: file + description: File containing samtools flagstat output + pattern: "*.{flagstat}" - idxstats: - type: channel - description: | - Samtools idxstats output with per-chromosome counts - Structure: [ val(meta), path(idxstats) ] - pattern: "*.idxstats" + type: file + description: File containing samtools idxstats output + pattern: "*.{idxstats}" - versions: - type: channel - description: | - Version information - Structure: path(versions.yml) + type: file + description: File containing software versions pattern: "versions.yml" authors: - - "@jjaureguy760" + - "@drpatelh" + - "@ewels" maintainers: - - "@jjaureguy760" + - "@drpatelh" + - "@ewels" diff --git a/pipelines/nf-atacseq/subworkflows/nf-core/bam_sort_stats_samtools/tests/main.nf.test b/pipelines/nf-atacseq/subworkflows/nf-core/bam_sort_stats_samtools/tests/main.nf.test new file mode 100644 index 0000000..c584128 --- /dev/null +++ b/pipelines/nf-atacseq/subworkflows/nf-core/bam_sort_stats_samtools/tests/main.nf.test @@ -0,0 +1,132 @@ +nextflow_workflow { + + name "Test Workflow BAM_SORT_STATS_SAMTOOLS" + script "../main.nf" + workflow "BAM_SORT_STATS_SAMTOOLS" + tag "subworkflows" + tag "subworkflows_nfcore" + tag "subworkflows/bam_sort_stats_samtools" + tag "bam_sort_stats_samtools" + tag "subworkflows/bam_stats_samtools" + tag "bam_stats_samtools" + tag "samtools" + tag "samtools/index" + tag "samtools/sort" + tag "samtools/stats" + tag "samtools/idxstats" + tag "samtools/flagstat" + + test("test_bam_sort_stats_samtools_single_end") { + + when { + workflow { + """ + input[0] = Channel.of([ + [ id:'test', single_end:false ], // meta map + file(params.modules_testdata_base_path + 'genomics/sarscov2/illumina/bam/test.single_end.bam', checkIfExists: true) + ]) + input[1] = Channel.of([ + [ id:'genome' ], + file(params.modules_testdata_base_path + 'genomics/sarscov2/genome/genome.fasta', checkIfExists: true) + ]) + """ + } + } + + then { + assertAll( + { assert workflow.success}, + { assert workflow.out.bam.get(0).get(1) ==~ ".*.bam"}, + { assert workflow.out.bai.get(0).get(1) ==~ ".*.bai"}, + { assert snapshot( + workflow.out.flagstat, + workflow.out.idxstats, + workflow.out.stats).match() } + ) + } + } + + test("test_bam_sort_stats_samtools_paired_end") { + + when { + workflow { + """ + input[0] = Channel.of([ + [ id:'test', single_end:false ], // meta map + file(params.modules_testdata_base_path + 'genomics/sarscov2/illumina/bam/test.paired_end.bam', checkIfExists: true) + ]) + input[1] = Channel.of([ + [ id:'genome' ], + file(params.modules_testdata_base_path + 'genomics/sarscov2/genome/genome.fasta', checkIfExists: true) + ]) + """ + } + } + + then { + assertAll( + { assert workflow.success}, + { assert workflow.out.bam.get(0).get(1) ==~ ".*.bam"}, + { assert workflow.out.bai.get(0).get(1) ==~ ".*.bai"}, + { assert snapshot( + workflow.out.flagstat, + workflow.out.idxstats, + workflow.out.stats).match() } + ) + } + } + + test("test_bam_sort_stats_samtools_single_end - stub") { + + options "-stub" + + when { + workflow { + """ + input[0] = Channel.of([ + [ id:'test', single_end:false ], // meta map + file(params.modules_testdata_base_path + 'genomics/sarscov2/illumina/bam/test.single_end.bam', checkIfExists: true) + ]) + input[1] = Channel.of([ + [ id:'genome' ], + file(params.modules_testdata_base_path + 'genomics/sarscov2/genome/genome.fasta', checkIfExists: true) + ]) + """ + } + } + + then { + assertAll( + { assert workflow.success}, + { assert snapshot(workflow.out).match() } + ) + } + } + + test("test_bam_sort_stats_samtools_paired_end - stub") { + + options "-stub" + + when { + workflow { + """ + input[0] = Channel.of([ + [ id:'test', single_end:false ], // meta map + file(params.modules_testdata_base_path + 'genomics/sarscov2/illumina/bam/test.paired_end.bam', checkIfExists: true) + ]) + input[1] = Channel.of([ + [ id:'genome' ], + file(params.modules_testdata_base_path + 'genomics/sarscov2/genome/genome.fasta', checkIfExists: true) + ]) + """ + } + } + + then { + assertAll( + { assert workflow.success}, + { assert snapshot(workflow.out).match() } + ) + } + } +} diff --git a/pipelines/nf-atacseq/subworkflows/nf-core/bam_sort_stats_samtools/tests/main.nf.test.snap b/pipelines/nf-atacseq/subworkflows/nf-core/bam_sort_stats_samtools/tests/main.nf.test.snap new file mode 100644 index 0000000..f62d68c --- /dev/null +++ b/pipelines/nf-atacseq/subworkflows/nf-core/bam_sort_stats_samtools/tests/main.nf.test.snap @@ -0,0 +1,288 @@ +{ + "test_bam_sort_stats_samtools_single_end": { + "content": [ + [ + [ + { + "id": "test", + "single_end": false + }, + "test.flagstat:md5,2191911d72575a2358b08b1df64ccb53" + ] + ], + [ + [ + { + "id": "test", + "single_end": false + }, + "test.idxstats:md5,613e048487662c694aa4a2f73ca96a20" + ] + ], + [ + [ + { + "id": "test", + "single_end": false + }, + "test.stats:md5,1101fe711c4a389fdb5c4a1532107d1f" + ] + ] + ], + "meta": { + "nf-test": "0.9.3", + "nextflow": "25.10.3" + }, + "timestamp": "2026-02-03T11:33:01.647190952" + }, + "test_bam_sort_stats_samtools_paired_end": { + "content": [ + [ + [ + { + "id": "test", + "single_end": false + }, + "test.flagstat:md5,4f7ffd1e6a5e85524d443209ac97d783" + ] + ], + [ + [ + { + "id": "test", + "single_end": false + }, + "test.idxstats:md5,df60a8c8d6621100d05178c93fb053a2" + ] + ], + [ + [ + { + "id": "test", + "single_end": false + }, + "test.stats:md5,f26c554c244ee86c89d62ebed509fd95" + ] + ] + ], + "meta": { + "nf-test": "0.9.3", + "nextflow": "25.10.3" + }, + "timestamp": "2026-02-03T11:33:08.706742267" + }, + "test_bam_sort_stats_samtools_single_end - stub": { + "content": [ + { + "0": [ + [ + { + "id": "test", + "single_end": false + }, + "test.bam:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "1": [ + [ + { + "id": "test", + "single_end": false + }, + "test.bam.bai:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "2": [ + + ], + "3": [ + [ + { + "id": "test", + "single_end": false + }, + "test.stats:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "4": [ + [ + { + "id": "test", + "single_end": false + }, + "test.flagstat:md5,67394650dbae96d1a4fcc70484822159" + ] + ], + "5": [ + [ + { + "id": "test", + "single_end": false + }, + "test.idxstats:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "bai": [ + [ + { + "id": "test", + "single_end": false + }, + "test.bam.bai:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "bam": [ + [ + { + "id": "test", + "single_end": false + }, + "test.bam:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "csi": [ + + ], + "flagstat": [ + [ + { + "id": "test", + "single_end": false + }, + "test.flagstat:md5,67394650dbae96d1a4fcc70484822159" + ] + ], + "idxstats": [ + [ + { + "id": "test", + "single_end": false + }, + "test.idxstats:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "stats": [ + [ + { + "id": "test", + "single_end": false + }, + "test.stats:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ] + } + ], + "meta": { + "nf-test": "0.9.3", + "nextflow": "25.10.3" + }, + "timestamp": "2026-02-03T11:11:02.1412136" + }, + "test_bam_sort_stats_samtools_paired_end - stub": { + "content": [ + { + "0": [ + [ + { + "id": "test", + "single_end": false + }, + "test.bam:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "1": [ + [ + { + "id": "test", + "single_end": false + }, + "test.bam.bai:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "2": [ + + ], + "3": [ + [ + { + "id": "test", + "single_end": false + }, + "test.stats:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "4": [ + [ + { + "id": "test", + "single_end": false + }, + "test.flagstat:md5,67394650dbae96d1a4fcc70484822159" + ] + ], + "5": [ + [ + { + "id": "test", + "single_end": false + }, + "test.idxstats:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "bai": [ + [ + { + "id": "test", + "single_end": false + }, + "test.bam.bai:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "bam": [ + [ + { + "id": "test", + "single_end": false + }, + "test.bam:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "csi": [ + + ], + "flagstat": [ + [ + { + "id": "test", + "single_end": false + }, + "test.flagstat:md5,67394650dbae96d1a4fcc70484822159" + ] + ], + "idxstats": [ + [ + { + "id": "test", + "single_end": false + }, + "test.idxstats:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "stats": [ + [ + { + "id": "test", + "single_end": false + }, + "test.stats:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ] + } + ], + "meta": { + "nf-test": "0.9.3", + "nextflow": "25.10.3" + }, + "timestamp": "2026-02-03T11:11:09.165267895" + } +} \ No newline at end of file diff --git a/pipelines/nf-atacseq/subworkflows/nf-core/bam_stats_samtools/main.nf b/pipelines/nf-atacseq/subworkflows/nf-core/bam_stats_samtools/main.nf index 7ee13e9..34e8fe1 100644 --- a/pipelines/nf-atacseq/subworkflows/nf-core/bam_stats_samtools/main.nf +++ b/pipelines/nf-atacseq/subworkflows/nf-core/bam_stats_samtools/main.nf @@ -1,32 +1,25 @@ // -// Run samtools stats, flagstat, and idxstats +// Run SAMtools stats, flagstat and idxstats // include { SAMTOOLS_STATS } from '../../../modules/nf-core/samtools/stats/main' -include { SAMTOOLS_FLAGSTAT } from '../../../modules/nf-core/samtools/flagstat/main' include { SAMTOOLS_IDXSTATS } from '../../../modules/nf-core/samtools/idxstats/main' +include { SAMTOOLS_FLAGSTAT } from '../../../modules/nf-core/samtools/flagstat/main' workflow BAM_STATS_SAMTOOLS { take: - ch_bam_bai // channel: [ val(meta), path(bam), path(bai) ] - ch_fasta // channel: path(fasta) + ch_bam_bai // channel: [ val(meta), path(bam), path(bai) ] + ch_fasta // channel: [ val(meta), path(fasta) ] main: - ch_versions = Channel.empty() - SAMTOOLS_STATS ( ch_bam_bai, ch_fasta ) - ch_versions = ch_versions.mix(SAMTOOLS_STATS.out.versions.first()) SAMTOOLS_FLAGSTAT ( ch_bam_bai ) - ch_versions = ch_versions.mix(SAMTOOLS_FLAGSTAT.out.versions.first()) SAMTOOLS_IDXSTATS ( ch_bam_bai ) - ch_versions = ch_versions.mix(SAMTOOLS_IDXSTATS.out.versions.first()) emit: stats = SAMTOOLS_STATS.out.stats // channel: [ val(meta), path(stats) ] flagstat = SAMTOOLS_FLAGSTAT.out.flagstat // channel: [ val(meta), path(flagstat) ] idxstats = SAMTOOLS_IDXSTATS.out.idxstats // channel: [ val(meta), path(idxstats) ] - - versions = ch_versions // channel: path(versions.yml) } diff --git a/pipelines/nf-atacseq/subworkflows/nf-core/bam_stats_samtools/meta.yml b/pipelines/nf-atacseq/subworkflows/nf-core/bam_stats_samtools/meta.yml index b1a9700..809bf73 100644 --- a/pipelines/nf-atacseq/subworkflows/nf-core/bam_stats_samtools/meta.yml +++ b/pipelines/nf-atacseq/subworkflows/nf-core/bam_stats_samtools/meta.yml @@ -1,54 +1,43 @@ # yaml-language-server: $schema=https://raw.githubusercontent.com/nf-core/modules/master/subworkflows/yaml-schema.json -name: "bam_stats_samtools" -description: Run samtools stats, flagstat, and idxstats on BAM files +name: bam_stats_samtools +description: Produces comprehensive statistics from SAM/BAM/CRAM file keywords: - - bam - statistics - - qc - - samtools + - counts + - bam + - sam + - cram components: - samtools/stats - - samtools/flagstat - samtools/idxstats + - samtools/flagstat input: - ch_bam_bai: - type: channel description: | - Channel containing BAM and BAI files + The input channel containing the BAM/CRAM and it's index Structure: [ val(meta), path(bam), path(bai) ] - pattern: "*.{bam,bai}" - ch_fasta: - type: channel description: | - Channel containing reference FASTA for stats calculation - Structure: path(fasta) - pattern: "*.{fa,fasta,fa.gz,fasta.gz}" + Reference genome fasta file + Structure: [ path(fasta) ] output: - stats: - type: channel description: | - Samtools stats output with alignment metrics + File containing samtools stats output Structure: [ val(meta), path(stats) ] - pattern: "*.stats" - flagstat: - type: channel description: | - Samtools flagstat output with flag counts + File containing samtools flagstat output Structure: [ val(meta), path(flagstat) ] - pattern: "*.flagstat" - idxstats: - type: channel description: | - Samtools idxstats output with per-chromosome counts - Structure: [ val(meta), path(idxstats) ] - pattern: "*.idxstats" + File containing samtools idxstats output + Structure: [ val(meta), path(idxstats)] - versions: - type: channel description: | - Version information - Structure: path(versions.yml) - pattern: "versions.yml" + Files containing software versions + Structure: [ path(versions.yml) ] authors: - - "@jjaureguy760" + - "@drpatelh" maintainers: - - "@jjaureguy760" + - "@drpatelh" diff --git a/pipelines/nf-atacseq/subworkflows/nf-core/bam_stats_samtools/tests/main.nf.test b/pipelines/nf-atacseq/subworkflows/nf-core/bam_stats_samtools/tests/main.nf.test new file mode 100644 index 0000000..2f32969 --- /dev/null +++ b/pipelines/nf-atacseq/subworkflows/nf-core/bam_stats_samtools/tests/main.nf.test @@ -0,0 +1,185 @@ +nextflow_workflow { + + name "Test Workflow BAM_STATS_SAMTOOLS" + script "../main.nf" + workflow "BAM_STATS_SAMTOOLS" + tag "subworkflows" + tag "subworkflows_nfcore" + tag "bam_stats_samtools" + tag "subworkflows/bam_stats_samtools" + tag "samtools" + tag "samtools/flagstat" + tag "samtools/idxstats" + tag "samtools/stats" + + test("test_bam_stats_samtools_single_end") { + + when { + workflow { + """ + input[0] = Channel.of([ + [ id:'test', single_end:true ], // meta map + file(params.modules_testdata_base_path + 'genomics/sarscov2/illumina/bam/test.single_end.sorted.bam', checkIfExists: true), + file(params.modules_testdata_base_path + 'genomics/sarscov2/illumina/bam/test.single_end.sorted.bam.bai', checkIfExists: true) + ]) + input[1] = Channel.of([ + [ id:'genome' ], + file(params.modules_testdata_base_path + 'genomics/sarscov2/genome/genome.fasta', checkIfExists: true) + ]) + """ + } + } + + then { + assertAll( + { assert workflow.success}, + { assert snapshot( + workflow.out.flagstat, + workflow.out.idxstats, + workflow.out.stats).match() } + ) + } + } + + test("test_bam_stats_samtools_paired_end") { + + when { + workflow { + """ + input[0] = Channel.of([ + [ id:'test', single_end:true ], // meta map + file(params.modules_testdata_base_path + 'genomics/sarscov2/illumina/bam/test.paired_end.sorted.bam', checkIfExists: true), + file(params.modules_testdata_base_path + 'genomics/sarscov2/illumina/bam/test.paired_end.sorted.bam.bai', checkIfExists: true) + ]) + input[1] = Channel.of([ + [ id:'genome' ], + file(params.modules_testdata_base_path + 'genomics/sarscov2/genome/genome.fasta', checkIfExists: true) + ]) + """ + } + } + + then { + assertAll( + { assert workflow.success }, + { assert snapshot( + workflow.out.flagstat, + workflow.out.idxstats, + workflow.out.stats).match() } + ) + } + } + + test("test_bam_stats_samtools_paired_end_cram") { + + when { + workflow { + """ + input[0] = Channel.of([ + [ id:'test', single_end:false ], // meta map + file(params.modules_testdata_base_path + 'genomics/homo_sapiens/illumina/cram/test.paired_end.sorted.cram', checkIfExists: true), + file(params.modules_testdata_base_path + 'genomics/homo_sapiens/illumina/cram/test.paired_end.sorted.cram.crai', checkIfExists: true) + ]) + input[1] = Channel.of([ + [ id:'genome' ], + file(params.modules_testdata_base_path + 'genomics/homo_sapiens/genome/genome.fasta', checkIfExists: true) + ]) + """ + } + } + + then { + assertAll( + { assert workflow.success}, + { assert snapshot( + workflow.out.flagstat, + workflow.out.idxstats, + workflow.out.stats).match() } + ) + } + } + + test ("test_bam_stats_samtools_single_end - stub") { + + options "-stub" + + when { + workflow { + """ + input[0] = Channel.of([ + [ id:'test', single_end:true ], // meta map + file(params.modules_testdata_base_path + 'genomics/sarscov2/illumina/bam/test.single_end.sorted.bam', checkIfExists: true), + file(params.modules_testdata_base_path + 'genomics/sarscov2/illumina/bam/test.single_end.sorted.bam.bai', checkIfExists: true) + ]) + input[1] = Channel.of([ + [ id:'genome' ], + file(params.modules_testdata_base_path + 'genomics/sarscov2/genome/genome.fasta', checkIfExists: true) + ]) + """ + } + } + + then { + assertAll( + { assert workflow.success}, + { assert snapshot(workflow.out).match() } + ) + } + } + + test("test_bam_stats_samtools_paired_end - stub") { + + options "-stub" + + when { + workflow { + """ + input[0] = Channel.of([ + [ id:'test', single_end:true ], // meta map + file(params.modules_testdata_base_path + 'genomics/sarscov2/illumina/bam/test.paired_end.sorted.bam', checkIfExists: true), + file(params.modules_testdata_base_path + 'genomics/sarscov2/illumina/bam/test.paired_end.sorted.bam.bai', checkIfExists: true) + ]) + input[1] = Channel.of([ + [ id:'genome' ], + file(params.modules_testdata_base_path + 'genomics/sarscov2/genome/genome.fasta', checkIfExists: true) + ]) + """ + } + } + + then { + assertAll( + { assert workflow.success }, + { assert snapshot(workflow.out).match() } + ) + } + } + + test("test_bam_stats_samtools_paired_end_cram - stub") { + + options "-stub" + + when { + workflow { + """ + input[0] = Channel.of([ + [ id:'test', single_end:false ], // meta map + file(params.modules_testdata_base_path + 'genomics/homo_sapiens/illumina/cram/test.paired_end.sorted.cram', checkIfExists: true), + file(params.modules_testdata_base_path + 'genomics/homo_sapiens/illumina/cram/test.paired_end.sorted.cram.crai', checkIfExists: true) + ]) + input[1] = Channel.of([ + [ id:'genome' ], + file(params.modules_testdata_base_path + 'genomics/homo_sapiens/genome/genome.fasta', checkIfExists: true) + ]) + """ + } + } + + then { + assertAll( + { assert workflow.success}, + { assert snapshot(workflow.out).match() } + ) + } + } +} diff --git a/pipelines/nf-atacseq/subworkflows/nf-core/bam_stats_samtools/tests/main.nf.test.snap b/pipelines/nf-atacseq/subworkflows/nf-core/bam_stats_samtools/tests/main.nf.test.snap new file mode 100644 index 0000000..9c8ff1b --- /dev/null +++ b/pipelines/nf-atacseq/subworkflows/nf-core/bam_stats_samtools/tests/main.nf.test.snap @@ -0,0 +1,305 @@ +{ + "test_bam_stats_samtools_paired_end - stub": { + "content": [ + { + "0": [ + [ + { + "id": "test", + "single_end": true + }, + "test.stats:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "1": [ + [ + { + "id": "test", + "single_end": true + }, + "test.flagstat:md5,67394650dbae96d1a4fcc70484822159" + ] + ], + "2": [ + [ + { + "id": "test", + "single_end": true + }, + "test.idxstats:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "flagstat": [ + [ + { + "id": "test", + "single_end": true + }, + "test.flagstat:md5,67394650dbae96d1a4fcc70484822159" + ] + ], + "idxstats": [ + [ + { + "id": "test", + "single_end": true + }, + "test.idxstats:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "stats": [ + [ + { + "id": "test", + "single_end": true + }, + "test.stats:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ] + } + ], + "meta": { + "nf-test": "0.9.3", + "nextflow": "25.10.3" + }, + "timestamp": "2026-02-03T11:10:30.076183827" + }, + "test_bam_stats_samtools_single_end - stub": { + "content": [ + { + "0": [ + [ + { + "id": "test", + "single_end": true + }, + "test.stats:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "1": [ + [ + { + "id": "test", + "single_end": true + }, + "test.flagstat:md5,67394650dbae96d1a4fcc70484822159" + ] + ], + "2": [ + [ + { + "id": "test", + "single_end": true + }, + "test.idxstats:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "flagstat": [ + [ + { + "id": "test", + "single_end": true + }, + "test.flagstat:md5,67394650dbae96d1a4fcc70484822159" + ] + ], + "idxstats": [ + [ + { + "id": "test", + "single_end": true + }, + "test.idxstats:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "stats": [ + [ + { + "id": "test", + "single_end": true + }, + "test.stats:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ] + } + ], + "meta": { + "nf-test": "0.9.3", + "nextflow": "25.10.3" + }, + "timestamp": "2026-02-03T11:10:24.379362883" + }, + "test_bam_stats_samtools_paired_end_cram - stub": { + "content": [ + { + "0": [ + [ + { + "id": "test", + "single_end": false + }, + "test.stats:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "1": [ + [ + { + "id": "test", + "single_end": false + }, + "test.flagstat:md5,67394650dbae96d1a4fcc70484822159" + ] + ], + "2": [ + [ + { + "id": "test", + "single_end": false + }, + "test.idxstats:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "flagstat": [ + [ + { + "id": "test", + "single_end": false + }, + "test.flagstat:md5,67394650dbae96d1a4fcc70484822159" + ] + ], + "idxstats": [ + [ + { + "id": "test", + "single_end": false + }, + "test.idxstats:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "stats": [ + [ + { + "id": "test", + "single_end": false + }, + "test.stats:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ] + } + ], + "meta": { + "nf-test": "0.9.3", + "nextflow": "25.10.3" + }, + "timestamp": "2026-02-03T11:10:35.91658956" + }, + "test_bam_stats_samtools_single_end": { + "content": [ + [ + [ + { + "id": "test", + "single_end": true + }, + "test.flagstat:md5,2191911d72575a2358b08b1df64ccb53" + ] + ], + [ + [ + { + "id": "test", + "single_end": true + }, + "test.idxstats:md5,613e048487662c694aa4a2f73ca96a20" + ] + ], + [ + [ + { + "id": "test", + "single_end": true + }, + "test.stats:md5,7a05a22bdb17e8df6e8c2d100ff09a31" + ] + ] + ], + "meta": { + "nf-test": "0.9.3", + "nextflow": "25.10.3" + }, + "timestamp": "2026-02-03T11:32:20.243663217" + }, + "test_bam_stats_samtools_paired_end": { + "content": [ + [ + [ + { + "id": "test", + "single_end": true + }, + "test.flagstat:md5,4f7ffd1e6a5e85524d443209ac97d783" + ] + ], + [ + [ + { + "id": "test", + "single_end": true + }, + "test.idxstats:md5,df60a8c8d6621100d05178c93fb053a2" + ] + ], + [ + [ + { + "id": "test", + "single_end": true + }, + "test.stats:md5,a391612b5ef5b181e854ccaad8c8a068" + ] + ] + ], + "meta": { + "nf-test": "0.9.3", + "nextflow": "25.10.3" + }, + "timestamp": "2026-02-03T11:32:26.434187887" + }, + "test_bam_stats_samtools_paired_end_cram": { + "content": [ + [ + [ + { + "id": "test", + "single_end": false + }, + "test.flagstat:md5,a53f3d26e2e9851f7d528442bbfe9781" + ] + ], + [ + [ + { + "id": "test", + "single_end": false + }, + "test.idxstats:md5,e179601fa7b8ebce81ac3765206f6c15" + ] + ], + [ + [ + { + "id": "test", + "single_end": false + }, + "test.stats:md5,2b0e31ab01b867a6ff312023ae03838d" + ] + ] + ], + "meta": { + "nf-test": "0.9.3", + "nextflow": "25.10.3" + }, + "timestamp": "2026-02-03T11:32:32.441454186" + } +} \ No newline at end of file diff --git a/pipelines/nf-atacseq/subworkflows/nf-core/fastq_align_bowtie2/main.nf b/pipelines/nf-atacseq/subworkflows/nf-core/fastq_align_bowtie2/main.nf index 4a42b76..8cbc514 100644 --- a/pipelines/nf-atacseq/subworkflows/nf-core/fastq_align_bowtie2/main.nf +++ b/pipelines/nf-atacseq/subworkflows/nf-core/fastq_align_bowtie2/main.nf @@ -3,56 +3,41 @@ // include { BOWTIE2_ALIGN } from '../../../modules/nf-core/bowtie2/align/main' -include { SAMTOOLS_INDEX } from '../../../modules/nf-core/samtools/index/main' -include { BAM_STATS_SAMTOOLS } from '../bam_stats_samtools/main' +include { BAM_SORT_STATS_SAMTOOLS } from '../bam_sort_stats_samtools/main' workflow FASTQ_ALIGN_BOWTIE2 { take: - ch_reads // channel: [ val(meta), path(reads) ] - ch_index // channel: path(index) - ch_fasta // channel: path(fasta) + ch_reads // channel: [ val(meta), [ reads ] ] + ch_index // channel: /path/to/bowtie2/index/ + save_unaligned // val + sort_bam // val + ch_fasta // channel: /path/to/reference.fasta main: - ch_versions = Channel.empty() - // - // Align reads with Bowtie2 (outputs sorted BAM) - // - BOWTIE2_ALIGN ( - ch_reads, - ch_index, - ch_fasta, - false, // save_unaligned - true // sort_bam - ) - ch_versions = ch_versions.mix(BOWTIE2_ALIGN.out.versions.first()) + ch_versions = channel.empty() // - // Index BAM file + // Map reads with Bowtie2 // - SAMTOOLS_INDEX ( BOWTIE2_ALIGN.out.aligned ) - ch_versions = ch_versions.mix(SAMTOOLS_INDEX.out.versions.first()) + BOWTIE2_ALIGN ( ch_reads, ch_index, ch_fasta, save_unaligned, sort_bam ) // - // Join BAM and BAI + // Sort, index BAM file and run samtools stats, flagstat and idxstats // - ch_bam_bai = BOWTIE2_ALIGN.out.aligned - .join(SAMTOOLS_INDEX.out.bai, by: [0], failOnMismatch: true) - - // - // Run BAM stats - // - BAM_STATS_SAMTOOLS ( ch_bam_bai, ch_fasta ) - ch_versions = ch_versions.mix(BAM_STATS_SAMTOOLS.out.versions) + BAM_SORT_STATS_SAMTOOLS ( BOWTIE2_ALIGN.out.bam, ch_fasta ) emit: - bam = BOWTIE2_ALIGN.out.aligned // channel: [ val(meta), path(bam) ] - bai = SAMTOOLS_INDEX.out.bai // channel: [ val(meta), path(bai) ] - log_out = BOWTIE2_ALIGN.out.log // channel: [ val(meta), path(log) ] - - stats = BAM_STATS_SAMTOOLS.out.stats // channel: [ val(meta), path(stats) ] - flagstat = BAM_STATS_SAMTOOLS.out.flagstat // channel: [ val(meta), path(flagstat) ] - idxstats = BAM_STATS_SAMTOOLS.out.idxstats // channel: [ val(meta), path(idxstats) ] - - versions = ch_versions // channel: path(versions.yml) + bam_orig = BOWTIE2_ALIGN.out.bam // channel: [ val(meta), aligned ] + log_out = BOWTIE2_ALIGN.out.log // channel: [ val(meta), log ] + fastq = BOWTIE2_ALIGN.out.fastq // channel: [ val(meta), fastq ] + + bam = BAM_SORT_STATS_SAMTOOLS.out.bam // channel: [ val(meta), [ bam ] ] + bai = BAM_SORT_STATS_SAMTOOLS.out.bai // channel: [ val(meta), [ bai ] ] + csi = BAM_SORT_STATS_SAMTOOLS.out.csi // channel: [ val(meta), [ csi ] ] + stats = BAM_SORT_STATS_SAMTOOLS.out.stats // channel: [ val(meta), [ stats ] ] + flagstat = BAM_SORT_STATS_SAMTOOLS.out.flagstat // channel: [ val(meta), [ flagstat ] ] + idxstats = BAM_SORT_STATS_SAMTOOLS.out.idxstats // channel: [ val(meta), [ idxstats ] ] + + versions = ch_versions // channel: [ versions.yml ] } diff --git a/pipelines/nf-atacseq/subworkflows/nf-core/fastq_align_bowtie2/meta.yml b/pipelines/nf-atacseq/subworkflows/nf-core/fastq_align_bowtie2/meta.yml index 4434311..b18e405 100644 --- a/pipelines/nf-atacseq/subworkflows/nf-core/fastq_align_bowtie2/meta.yml +++ b/pipelines/nf-atacseq/subworkflows/nf-core/fastq_align_bowtie2/meta.yml @@ -1,79 +1,67 @@ # yaml-language-server: $schema=https://raw.githubusercontent.com/nf-core/modules/master/subworkflows/yaml-schema.json -name: "fastq_align_bowtie2" -description: Align reads with Bowtie2 and collect BAM statistics +name: fastq_align_bowtie2 +description: Align reads to a reference genome using bowtie2 then sort with samtools keywords: - - alignment - - bowtie2 - - bam - - map - - fastq + - align + - fasta + - genome + - reference components: - bowtie2/align + - samtools/sort - samtools/index - - bam_stats_samtools + - samtools/stats + - samtools/idxstats + - samtools/flagstat + - bam_sort_stats_samtools input: + - meta: + type: map + description: | + Groovy Map containing sample information + e.g. [ id:'test', single_end:false ] - ch_reads: - type: channel + type: file description: | - Channel containing FASTQ reads - Structure: [ val(meta), path(reads) ] - pattern: "*.{fq,fastq,fq.gz,fastq.gz}" + List of input FastQ files of size 1 and 2 for single-end and paired-end data, + respectively. - ch_index: - type: channel + type: file + description: Bowtie2 genome index files + pattern: "*.ebwt" + - save_unaligned: + type: boolean description: | - Channel containing Bowtie2 index files - Structure: path(index) - pattern: "*.bt2" - - ch_fasta: - type: channel + Save reads that do not map to the reference (true) or discard them (false) + (default: false) + - sort_bam: + type: boolean description: | - Channel containing reference FASTA - Structure: path(fasta) - pattern: "*.{fa,fasta,fa.gz,fasta.gz}" + Use samtools sort (true) or samtools view (false) + default: false + - ch_fasta: + type: file + description: Reference fasta file + pattern: "*.{fasta,fa}" +# TODO Update when we decide on a standard for subworkflow docs output: - bam: - type: channel - description: | - Aligned BAM file - Structure: [ val(meta), path(bam) ] - pattern: "*.bam" - - bai: - type: channel - description: | - BAM index file - Structure: [ val(meta), path(bai) ] - pattern: "*.bai" - - log_out: - type: channel - description: | - Bowtie2 alignment log - Structure: [ val(meta), path(log) ] - pattern: "*.log" - - stats: - type: channel - description: | - Samtools stats output - Structure: [ val(meta), path(stats) ] - pattern: "*.stats" - - flagstat: - type: channel - description: | - Samtools flagstat output - Structure: [ val(meta), path(flagstat) ] - pattern: "*.flagstat" - - idxstats: - type: channel - description: | - Samtools idxstats output - Structure: [ val(meta), path(idxstats) ] - pattern: "*.idxstats" + type: file + description: Output BAM file containing read alignments + pattern: "*.{bam}" - versions: - type: channel - description: | - Version information - Structure: path(versions.yml) + type: file + description: File containing software versions pattern: "versions.yml" + - fastq: + type: file + description: Unaligned FastQ files + pattern: "*.fastq.gz" + - log: + type: file + description: Alignment log + pattern: "*.log" authors: - - "@jjaureguy760" + - "@drpatelh" maintainers: - - "@jjaureguy760" + - "@drpatelh" diff --git a/pipelines/nf-atacseq/subworkflows/nf-core/fastq_align_bowtie2/tests/main.nf.test b/pipelines/nf-atacseq/subworkflows/nf-core/fastq_align_bowtie2/tests/main.nf.test new file mode 100644 index 0000000..6eca398 --- /dev/null +++ b/pipelines/nf-atacseq/subworkflows/nf-core/fastq_align_bowtie2/tests/main.nf.test @@ -0,0 +1,189 @@ +nextflow_workflow { + + name "Test Subworkflow FASTQ_ALIGN_BOWTIE2" + script "../main.nf" + config "./nextflow.config" + workflow "FASTQ_ALIGN_BOWTIE2" + + tag "subworkflows" + tag "subworkflows_nfcore" + tag "subworkflows/fastq_align_bowtie2" + tag "subworkflows/bam_sort_stats_samtools" + tag "bowtie2" + tag "bowtie2/build" + tag "bowtie2/align" + + test("test_align_bowtie2_single_end") { + setup { + run("BOWTIE2_BUILD") { + script "../../../../modules/nf-core/bowtie2/build/main.nf" + process { + """ + input[0] = Channel.value([ [ id:'genome' ],file(params.modules_testdata_base_path + 'genomics/sarscov2/genome/genome.fasta', checkIfExists: true)]) + """ + } + } + } + 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] = BOWTIE2_BUILD.out.index + input[2] = false + input[3] = false + input[4] = Channel.value([ [ id:'genome' ],file(params.modules_testdata_base_path + 'genomics/sarscov2/genome/genome.fasta', checkIfExists: true)]) + """ + } + } + + then { + assertAll( + { assert workflow.success}, + { assert snapshot( + file(workflow.out.bam_orig[0][1]).name, + workflow.out.fastq, + workflow.out.log_out, + file(workflow.out.bam[0][1]).name, + file(workflow.out.bai[0][1]).name, + workflow.out.csi, + workflow.out.stats, + workflow.out.flagstat, + workflow.out.idxstats, + workflow.out.versions + ).match()} + ) + } + } + + test("test_align_bowtie2_paired_end") { + setup { + run("BOWTIE2_BUILD") { + script "../../../../modules/nf-core/bowtie2/build/main.nf" + process { + """ + input[0] = Channel.value([ [ id:'genome' ],file(params.modules_testdata_base_path + 'genomics/sarscov2/genome/genome.fasta', checkIfExists: true)]) + """ + } + } + } + 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] = BOWTIE2_BUILD.out.index + input[2] = false + input[3] = false + input[4] = Channel.value([ [ id:'genome' ],file(params.modules_testdata_base_path + 'genomics/sarscov2/genome/genome.fasta', checkIfExists: true)]) + """ + } + } + + then { + assertAll( + { assert workflow.success}, + { assert snapshot( + file(workflow.out.bam_orig[0][1]).name, + workflow.out.fastq, + workflow.out.log_out, + file(workflow.out.bam[0][1]).name, + file(workflow.out.bai[0][1]).name, + workflow.out.csi, + workflow.out.stats, + workflow.out.flagstat, + workflow.out.idxstats, + workflow.out.versions + ).match()} + ) + } + } + + test("test_align_bowtie2_single_end - stub") { + + options "-stub" + + setup { + run("BOWTIE2_BUILD") { + script "../../../../modules/nf-core/bowtie2/build/main.nf" + process { + """ + input[0] = Channel.value([ [ id:'genome' ],file(params.modules_testdata_base_path + 'genomics/sarscov2/genome/genome.fasta', checkIfExists: true)]) + """ + } + } + } + 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] = BOWTIE2_BUILD.out.index + input[2] = false + input[3] = false + input[4] = Channel.value([ [ id:'genome' ],file(params.modules_testdata_base_path + 'genomics/sarscov2/genome/genome.fasta', checkIfExists: true)]) + """ + } + } + + then { + assertAll( + { assert workflow.success}, + { assert snapshot( + file(workflow.out.bam_orig[0][1]).name, + workflow.out.fastq, + workflow.out.log_out, + file(workflow.out.bam[0][1]).name, + file(workflow.out.bai[0][1]).name, + workflow.out.csi, + workflow.out.stats, + workflow.out.flagstat, + workflow.out.idxstats, + workflow.out.versions + ).match()} + ) + } + } + + test("test_align_bowtie2_paired_end - stub") { + + options "-stub" + + setup { + run("BOWTIE2_BUILD") { + script "../../../../modules/nf-core/bowtie2/build/main.nf" + process { + """ + input[0] = Channel.value([ [ id:'genome' ],file(params.modules_testdata_base_path + 'genomics/sarscov2/genome/genome.fasta', checkIfExists: true)]) + """ + } + } + } + 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] = BOWTIE2_BUILD.out.index + input[2] = false + input[3] = false + input[4] = Channel.value([ [ id:'genome' ],file(params.modules_testdata_base_path + 'genomics/sarscov2/genome/genome.fasta', checkIfExists: true)]) + """ + } + } + + then { + assertAll( + { assert workflow.success}, + { assert snapshot( + file(workflow.out.bam_orig[0][1]).name, + workflow.out.fastq, + workflow.out.log_out, + file(workflow.out.bam[0][1]).name, + file(workflow.out.bai[0][1]).name, + workflow.out.csi, + workflow.out.stats, + workflow.out.flagstat, + workflow.out.idxstats, + workflow.out.versions + ).match()} + ) + } + } +} diff --git a/pipelines/nf-atacseq/subworkflows/nf-core/fastq_align_bowtie2/tests/main.nf.test.snap b/pipelines/nf-atacseq/subworkflows/nf-core/fastq_align_bowtie2/tests/main.nf.test.snap new file mode 100644 index 0000000..2dc8896 --- /dev/null +++ b/pipelines/nf-atacseq/subworkflows/nf-core/fastq_align_bowtie2/tests/main.nf.test.snap @@ -0,0 +1,230 @@ +{ + "test_align_bowtie2_single_end - stub": { + "content": [ + "test.bam", + [ + + ], + [ + [ + { + "id": "test", + "single_end": true + }, + "test.bowtie2.log:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "test.sorted.bam", + "test.sorted.bam.bai", + [ + + ], + [ + [ + { + "id": "test", + "single_end": true + }, + "test.sorted.stats:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + [ + [ + { + "id": "test", + "single_end": true + }, + "test.sorted.flagstat:md5,67394650dbae96d1a4fcc70484822159" + ] + ], + [ + [ + { + "id": "test", + "single_end": true + }, + "test.sorted.idxstats:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + [ + + ] + ], + "meta": { + "nf-test": "0.9.3", + "nextflow": "25.10.3" + }, + "timestamp": "2026-02-03T15:14:25.504699933" + }, + "test_align_bowtie2_single_end": { + "content": [ + "test.bam", + [ + + ], + [ + [ + { + "id": "test", + "single_end": true + }, + "test.bowtie2.log:md5,7b8a9e61b7646da1089b041333c41a87" + ] + ], + "test.sorted.bam", + "test.sorted.bam.bai", + [ + + ], + [ + [ + { + "id": "test", + "single_end": true + }, + "test.sorted.stats:md5,48b911852e91d77db59154f7355ede4f" + ] + ], + [ + [ + { + "id": "test", + "single_end": true + }, + "test.sorted.flagstat:md5,e9ce9093133116bc54fd335cfe698372" + ] + ], + [ + [ + { + "id": "test", + "single_end": true + }, + "test.sorted.idxstats:md5,e16eb632f7f462514b0873c7ac8ac905" + ] + ], + [ + + ] + ], + "meta": { + "nf-test": "0.9.3", + "nextflow": "25.10.3" + }, + "timestamp": "2026-02-03T15:14:08.108143527" + }, + "test_align_bowtie2_paired_end": { + "content": [ + "test.bam", + [ + + ], + [ + [ + { + "id": "test", + "single_end": false + }, + "test.bowtie2.log:md5,bd89ce1b28c93bf822bae391ffcedd19" + ] + ], + "test.sorted.bam", + "test.sorted.bam.bai", + [ + + ], + [ + [ + { + "id": "test", + "single_end": false + }, + "test.sorted.stats:md5,cb422b3fcd4327488cb6bc5ac15a48ff" + ] + ], + [ + [ + { + "id": "test", + "single_end": false + }, + "test.sorted.flagstat:md5,49f3d51a8804ce58fe9cecd2549d279b" + ] + ], + [ + [ + { + "id": "test", + "single_end": false + }, + "test.sorted.idxstats:md5,29ff2fa56d35b2a47625b8f517f1a947" + ] + ], + [ + + ] + ], + "meta": { + "nf-test": "0.9.3", + "nextflow": "25.10.3" + }, + "timestamp": "2026-02-03T15:14:17.07821488" + }, + "test_align_bowtie2_paired_end - stub": { + "content": [ + "test.bam", + [ + + ], + [ + [ + { + "id": "test", + "single_end": false + }, + "test.bowtie2.log:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "test.sorted.bam", + "test.sorted.bam.bai", + [ + + ], + [ + [ + { + "id": "test", + "single_end": false + }, + "test.sorted.stats:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + [ + [ + { + "id": "test", + "single_end": false + }, + "test.sorted.flagstat:md5,67394650dbae96d1a4fcc70484822159" + ] + ], + [ + [ + { + "id": "test", + "single_end": false + }, + "test.sorted.idxstats:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + [ + + ] + ], + "meta": { + "nf-test": "0.9.3", + "nextflow": "25.10.3" + }, + "timestamp": "2026-02-03T15:14:34.088967148" + } +} \ No newline at end of file diff --git a/pipelines/nf-atacseq/subworkflows/nf-core/fastq_align_bowtie2/tests/nextflow.config b/pipelines/nf-atacseq/subworkflows/nf-core/fastq_align_bowtie2/tests/nextflow.config new file mode 100644 index 0000000..9086ebf --- /dev/null +++ b/pipelines/nf-atacseq/subworkflows/nf-core/fastq_align_bowtie2/tests/nextflow.config @@ -0,0 +1,8 @@ +process { + withName: '.*:BAM_SORT_STATS_SAMTOOLS:SAMTOOLS_.*' { + ext.prefix = { "${meta.id}.sorted" } + } + withName: '.*:BAM_SORT_STATS_SAMTOOLS:BAM_STATS_SAMTOOLS:.*' { + ext.prefix = { "${meta.id}.sorted" } + } +} diff --git a/pipelines/nf-atacseq/subworkflows/nf-core/fastq_align_bwa/main.nf b/pipelines/nf-atacseq/subworkflows/nf-core/fastq_align_bwa/main.nf index 2524b46..e06a5fa 100644 --- a/pipelines/nf-atacseq/subworkflows/nf-core/fastq_align_bwa/main.nf +++ b/pipelines/nf-atacseq/subworkflows/nf-core/fastq_align_bwa/main.nf @@ -1,56 +1,41 @@ // -// Alignment with BWA-MEM +// Alignment with BWA // include { BWA_MEM } from '../../../modules/nf-core/bwa/mem/main' -include { SAMTOOLS_INDEX } from '../../../modules/nf-core/samtools/index/main' -include { BAM_STATS_SAMTOOLS } from '../bam_stats_samtools/main' +include { BAM_SORT_STATS_SAMTOOLS } from '../bam_sort_stats_samtools/main' workflow FASTQ_ALIGN_BWA { take: - ch_reads // channel: [ val(meta), path(reads) ] - ch_index // channel: path(index) - ch_fasta // channel: path(fasta) + ch_reads // channel (mandatory): [ val(meta), [ path(reads) ] ] + ch_index // channel (mandatory): [ val(meta2), path(index) ] + val_sort_bam // boolean (mandatory): true or false + ch_fasta // channel (optional) : [ val(meta3), path(fasta) ] main: - ch_versions = Channel.empty() + ch_versions = channel.empty() // - // Align reads with BWA-MEM (outputs sorted BAM) + // Map reads with BWA // - BWA_MEM ( - ch_reads, - ch_index, - ch_fasta, - true // sort_bam - ) - ch_versions = ch_versions.mix(BWA_MEM.out.versions.first()) - // - // Index BAM file - // - SAMTOOLS_INDEX ( BWA_MEM.out.bam ) - ch_versions = ch_versions.mix(SAMTOOLS_INDEX.out.versions.first()) + BWA_MEM ( ch_reads, ch_index, ch_fasta, val_sort_bam ) // - // Join BAM and BAI + // Sort, index BAM file and run samtools stats, flagstat and idxstats // - ch_bam_bai = BWA_MEM.out.bam - .join(SAMTOOLS_INDEX.out.bai, by: [0], failOnMismatch: true) - // - // Run BAM stats - // - BAM_STATS_SAMTOOLS ( ch_bam_bai, ch_fasta ) - ch_versions = ch_versions.mix(BAM_STATS_SAMTOOLS.out.versions) + BAM_SORT_STATS_SAMTOOLS ( BWA_MEM.out.bam, ch_fasta ) emit: - bam = BWA_MEM.out.bam // channel: [ val(meta), path(bam) ] - bai = SAMTOOLS_INDEX.out.bai // channel: [ val(meta), path(bai) ] + bam_orig = BWA_MEM.out.bam // channel: [ val(meta), path(bam) ] - stats = BAM_STATS_SAMTOOLS.out.stats // channel: [ val(meta), path(stats) ] - flagstat = BAM_STATS_SAMTOOLS.out.flagstat // channel: [ val(meta), path(flagstat) ] - idxstats = BAM_STATS_SAMTOOLS.out.idxstats // channel: [ val(meta), path(idxstats) ] + bam = BAM_SORT_STATS_SAMTOOLS.out.bam // channel: [ val(meta), path(bam) ] + bai = BAM_SORT_STATS_SAMTOOLS.out.bai // channel: [ val(meta), path(bai) ] + csi = BAM_SORT_STATS_SAMTOOLS.out.csi // channel: [ val(meta), path(csi) ] + stats = BAM_SORT_STATS_SAMTOOLS.out.stats // channel: [ val(meta), path(stats) ] + flagstat = BAM_SORT_STATS_SAMTOOLS.out.flagstat // channel: [ val(meta), path(flagstat) ] + idxstats = BAM_SORT_STATS_SAMTOOLS.out.idxstats // channel: [ val(meta), path(idxstats) ] - versions = ch_versions // channel: path(versions.yml) + versions = ch_versions // channel: [ path(versions.yml) ] } diff --git a/pipelines/nf-atacseq/subworkflows/nf-core/fastq_align_bwa/meta.yml b/pipelines/nf-atacseq/subworkflows/nf-core/fastq_align_bwa/meta.yml index 31ebdc4..fa21840 100644 --- a/pipelines/nf-atacseq/subworkflows/nf-core/fastq_align_bwa/meta.yml +++ b/pipelines/nf-atacseq/subworkflows/nf-core/fastq_align_bwa/meta.yml @@ -1,73 +1,73 @@ # yaml-language-server: $schema=https://raw.githubusercontent.com/nf-core/modules/master/subworkflows/yaml-schema.json -name: "fastq_align_bwa" -description: Align reads with BWA-MEM and collect BAM statistics +name: fastq_align_bwa +description: Align reads to a reference genome using bwa then sort with samtools keywords: - - alignment - - bwa - - bam - - map - - fastq + - align + - fasta + - genome + - reference components: - bwa/mem + - bwa/align + - samtools/sort - samtools/index - - bam_stats_samtools + - samtools/stats + - samtools/idxstats + - samtools/flagstat + - bam_sort_stats_samtools input: - ch_reads: - type: channel description: | - Channel containing FASTQ reads - Structure: [ val(meta), path(reads) ] - pattern: "*.{fq,fastq,fq.gz,fastq.gz}" + List of input FastQ files of size 1 and 2 for single-end and paired-end data, + respectively. + Structure: [ val(meta), [ path(reads) ] ] - ch_index: - type: channel description: | - Channel containing BWA index files - Structure: path(index) - pattern: "*.{amb,ann,bwt,pac,sa}" + BWA genome index files + Structure: [ val(meta), path(index) ] + - val_sort_bam: + type: boolean + description: If true bwa modules sort resulting bam files + pattern: "true|false" - ch_fasta: - type: channel + type: file description: | - Channel containing reference FASTA - Structure: path(fasta) - pattern: "*.{fa,fasta,fa.gz,fasta.gz}" + Optional reference fasta file. This only needs to be given if val_sort_bam = true. + Structure: [ val(meta), path(fasta) ] output: + - bam_orig: + description: | + BAM file produced by bwa + Structure: [ val(meta), path(bam) ] - bam: - type: channel description: | - Aligned BAM file + BAM file ordered by samtools Structure: [ val(meta), path(bam) ] - pattern: "*.bam" - bai: - type: channel description: | - BAM index file + BAI index of the ordered BAM file Structure: [ val(meta), path(bai) ] - pattern: "*.bai" + - csi: + description: | + CSI index of the ordered BAM file + Structure: [ val(meta), path(csi) ] - stats: - type: channel description: | - Samtools stats output + File containing samtools stats output Structure: [ val(meta), path(stats) ] - pattern: "*.stats" - flagstat: - type: channel description: | - Samtools flagstat output + File containing samtools flagstat output Structure: [ val(meta), path(flagstat) ] - pattern: "*.flagstat" - idxstats: - type: channel description: | - Samtools idxstats output + File containing samtools idxstats output Structure: [ val(meta), path(idxstats) ] - pattern: "*.idxstats" - versions: - type: channel description: | - Version information - Structure: path(versions.yml) - pattern: "versions.yml" + Files containing software versions + Structure: [ path(versions.yml) ] authors: - - "@jjaureguy760" + - "@JoseEspinosa" maintainers: - - "@jjaureguy760" + - "@JoseEspinosa" diff --git a/pipelines/nf-atacseq/subworkflows/nf-core/fastq_align_bwa/tests/main.nf.test b/pipelines/nf-atacseq/subworkflows/nf-core/fastq_align_bwa/tests/main.nf.test new file mode 100644 index 0000000..7262325 --- /dev/null +++ b/pipelines/nf-atacseq/subworkflows/nf-core/fastq_align_bwa/tests/main.nf.test @@ -0,0 +1,77 @@ +nextflow_workflow { + + name "Test Subworkflow FASTQ_ALIGN_BWA" + script "../main.nf" + config "./nextflow.config" + workflow "FASTQ_ALIGN_BWA" + + tag "subworkflows" + tag "subworkflows_nfcore" + tag "subworkflows/fastq_align_bwa" + tag "subworkflows/bam_sort_stats_samtools" + tag "bwa" + tag "bwa/mem" + tag "bwa/index" + + + test("fastq_align_bwa_single_end") { + setup { + run("BWA_INDEX") { + script "../../../../modules/nf-core/bwa/index/main.nf" + process { + """ + input[0] = Channel.value([ [ id:'genome' ],file(params.modules_testdata_base_path + 'genomics/sarscov2/genome/genome.fasta', checkIfExists: true)]) + """ + } + } + } + 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] = BWA_INDEX.out.index + input[2] = false + input[3] = Channel.value([[id: 'genome'], file(params.modules_testdata_base_path + 'genomics/sarscov2/genome/genome.fasta', checkIfExists: true)]) + """ + } + } + + then { + assertAll( + { assert workflow.success}, + { assert snapshot(workflow.out).match()} + ) + } + } + + test("fastq_align_bwa_paired_end") { + setup { + run("BWA_INDEX") { + script "../../../../modules/nf-core/bwa/index/main.nf" + process { + """ + input[0] = Channel.value([ [ id:'genome' ],file(params.modules_testdata_base_path + 'genomics/sarscov2/genome/genome.fasta', checkIfExists: true)]) + """ + } + } + } + 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] = BWA_INDEX.out.index + input[2] = false + input[3] = Channel.value([[id: 'genome'], file(params.modules_testdata_base_path + 'genomics/sarscov2/genome/genome.fasta', checkIfExists: true)]) + """ + } + } + + then { + assertAll( + { assert workflow.success}, + { assert snapshot(workflow.out).match()} + ) + } + } +} diff --git a/pipelines/nf-atacseq/subworkflows/nf-core/fastq_align_bwa/tests/main.nf.test.snap b/pipelines/nf-atacseq/subworkflows/nf-core/fastq_align_bwa/tests/main.nf.test.snap new file mode 100644 index 0000000..9a16da2 --- /dev/null +++ b/pipelines/nf-atacseq/subworkflows/nf-core/fastq_align_bwa/tests/main.nf.test.snap @@ -0,0 +1,264 @@ +{ + "fastq_align_bwa_paired_end": { + "content": [ + { + "0": [ + [ + { + "id": "test", + "single_end": false + }, + "test.bam:md5,5dbdcfdba65fac634dcbb6984cffe2c4" + ] + ], + "1": [ + [ + { + "id": "test", + "single_end": false + }, + "test.sorted.bam:md5,ba4b90f87517a16a6ae6142f37a75d79" + ] + ], + "2": [ + [ + { + "id": "test", + "single_end": false + }, + "test.sorted.bam.bai:md5,4c5e6fa0e71327b79034eebd652f2121" + ] + ], + "3": [ + + ], + "4": [ + [ + { + "id": "test", + "single_end": false + }, + "test.sorted.bam.stats:md5,75934f2a51780a80d2ab4674301a018d" + ] + ], + "5": [ + [ + { + "id": "test", + "single_end": false + }, + "test.sorted.bam.flagstat:md5,18d602435a02a4d721b78d1812622159" + ] + ], + "6": [ + [ + { + "id": "test", + "single_end": false + }, + "test.sorted.bam.idxstats:md5,85d20a901eef23ca50c323638a2eb602" + ] + ], + "7": [ + + ], + "bai": [ + [ + { + "id": "test", + "single_end": false + }, + "test.sorted.bam.bai:md5,4c5e6fa0e71327b79034eebd652f2121" + ] + ], + "bam": [ + [ + { + "id": "test", + "single_end": false + }, + "test.sorted.bam:md5,ba4b90f87517a16a6ae6142f37a75d79" + ] + ], + "bam_orig": [ + [ + { + "id": "test", + "single_end": false + }, + "test.bam:md5,5dbdcfdba65fac634dcbb6984cffe2c4" + ] + ], + "csi": [ + + ], + "flagstat": [ + [ + { + "id": "test", + "single_end": false + }, + "test.sorted.bam.flagstat:md5,18d602435a02a4d721b78d1812622159" + ] + ], + "idxstats": [ + [ + { + "id": "test", + "single_end": false + }, + "test.sorted.bam.idxstats:md5,85d20a901eef23ca50c323638a2eb602" + ] + ], + "stats": [ + [ + { + "id": "test", + "single_end": false + }, + "test.sorted.bam.stats:md5,75934f2a51780a80d2ab4674301a018d" + ] + ], + "versions": [ + + ] + } + ], + "timestamp": "2026-02-18T12:47:43.306112", + "meta": { + "nf-test": "0.9.4", + "nextflow": "25.10.4" + } + }, + "fastq_align_bwa_single_end": { + "content": [ + { + "0": [ + [ + { + "id": "test", + "single_end": true + }, + "test.bam:md5,f7af092ddd5203f647ba96b926392c3e" + ] + ], + "1": [ + [ + { + "id": "test", + "single_end": true + }, + "test.sorted.bam:md5,c406a43adde2d9673e71d8a8c7db7cfd" + ] + ], + "2": [ + [ + { + "id": "test", + "single_end": true + }, + "test.sorted.bam.bai:md5,f79a40341ecfaae11d8621b138d4c2ea" + ] + ], + "3": [ + + ], + "4": [ + [ + { + "id": "test", + "single_end": true + }, + "test.sorted.bam.stats:md5,0883b19c92a783883b3e11d5bfcc5d6a" + ] + ], + "5": [ + [ + { + "id": "test", + "single_end": true + }, + "test.sorted.bam.flagstat:md5,2191911d72575a2358b08b1df64ccb53" + ] + ], + "6": [ + [ + { + "id": "test", + "single_end": true + }, + "test.sorted.bam.idxstats:md5,613e048487662c694aa4a2f73ca96a20" + ] + ], + "7": [ + + ], + "bai": [ + [ + { + "id": "test", + "single_end": true + }, + "test.sorted.bam.bai:md5,f79a40341ecfaae11d8621b138d4c2ea" + ] + ], + "bam": [ + [ + { + "id": "test", + "single_end": true + }, + "test.sorted.bam:md5,c406a43adde2d9673e71d8a8c7db7cfd" + ] + ], + "bam_orig": [ + [ + { + "id": "test", + "single_end": true + }, + "test.bam:md5,f7af092ddd5203f647ba96b926392c3e" + ] + ], + "csi": [ + + ], + "flagstat": [ + [ + { + "id": "test", + "single_end": true + }, + "test.sorted.bam.flagstat:md5,2191911d72575a2358b08b1df64ccb53" + ] + ], + "idxstats": [ + [ + { + "id": "test", + "single_end": true + }, + "test.sorted.bam.idxstats:md5,613e048487662c694aa4a2f73ca96a20" + ] + ], + "stats": [ + [ + { + "id": "test", + "single_end": true + }, + "test.sorted.bam.stats:md5,0883b19c92a783883b3e11d5bfcc5d6a" + ] + ], + "versions": [ + + ] + } + ], + "timestamp": "2026-02-18T12:47:30.203617", + "meta": { + "nf-test": "0.9.4", + "nextflow": "25.10.4" + } + } +} \ No newline at end of file diff --git a/pipelines/nf-atacseq/subworkflows/nf-core/fastq_align_bwa/tests/nextflow.config b/pipelines/nf-atacseq/subworkflows/nf-core/fastq_align_bwa/tests/nextflow.config new file mode 100644 index 0000000..2f85e80 --- /dev/null +++ b/pipelines/nf-atacseq/subworkflows/nf-core/fastq_align_bwa/tests/nextflow.config @@ -0,0 +1,8 @@ +process { + withName: '.*:BAM_SORT_STATS_SAMTOOLS:SAMTOOLS_.*' { + ext.prefix = { "${meta.id}.sorted" } + } + withName: '.*:BAM_SORT_STATS_SAMTOOLS:BAM_STATS_SAMTOOLS:.*' { + ext.prefix = { "${meta.id}.sorted.bam" } + } +} diff --git a/pipelines/nf-atacseq/tests/data/bwa_index/chr_test.fa b/pipelines/nf-atacseq/tests/data/bwa_index/chr_test.fa index 923c055..182b3f7 100644 --- a/pipelines/nf-atacseq/tests/data/bwa_index/chr_test.fa +++ b/pipelines/nf-atacseq/tests/data/bwa_index/chr_test.fa @@ -1,331 +1,335 @@ >chr_test -ATGCATGCATGCATGCATGCATGCATGCATGCATGCATGCATGCATGCATGCATGCATGC -ATGCATGCATGCATGCATGCATGCATGCATGCATGCATGCATGCATGCATGCATGCATGC -ATGCATGCATGCATGCATGCATGCATGCATGCATGCATGCATGCATGCATGCATGCATGC -ATGCATGCATGCATGCATGCATGCATGCATGCATGCATGCATGCATGCATGCATGCATGC -ATGCATGCATGCATGCATGCATGCATGCATGCATGCATGCATGCATGCATGCATGCATGC -ATGCATGCATGCATGCATGCATGCATGCATGCATGCATGCATGCATGCATGCATGCATGC -ATGCATGCATGCATGCATGCATGCATGCATGCATGCATGCATGCATGCATGCATGCATGC -ATGCATGCATGCATGCATGCATGCATGCATGCATGCATGCATGCATGCATGCATGCATGC -AAACCCGGGTTTAAACCCGGGTTTAAACCCGGGTTTAAACCCGGGTTTAAACCCGGGTTT -AAACCCGGGTTTAAACCCGGGTTTAAACCCGGGTTTAAACCCGGGTTTAAACCCGGGTTT -AAACCCGGGTTTAAACCCGGGTTTAAACCCGGGTTTAAACCCGGGTTTAAACCCGGGTTT -AAACCCGGGTTTAAACCCGGGTTTAAACCCGGGTTTAAACCCGGGTTTAAACCCGGGTTT -AAACCCGGGTTTAAACCCGGGTTTAAACCCGGGTTTAAACCCGGGTTTAAACCCGGGTTT -AAACCCGGGTTTAAACCCGGGTTTAAACCCGGGTTTAAACCCGGGTTTAAACCCGGGTTT -AAACCCGGGTTTAAACCCGGGTTTAAACCCGGGTTTAAACCCGGGTTTAAACCCGGGTTT -AAACCCGGGTTTAAACCCGGGTTTAAACCCGGGTTTAAACCCGGGTTTAAACCCGGGTTT -AAACCCGGGTTTAAACCCGGGTTTAAACCCGGGTTTAAACCCGGGTTTAAACCCGGGTTT -CGATCGATCGATCGATCGATCGATCGATCGATCGATCGATCGATCGATCGATCGATCGAT -CGATCGATCGATCGATCGATCGATCGATCGATCGATCGATCGATCGATCGATCGATCGAT -CGATCGATCGATCGATCGATCGATCGATCGATCGATCGATCGATCGATCGATCGATCGAT -CGATCGATCGATCGATCGATCGATCGATCGATCGATCGATCGATCGATCGATCGATCGAT -CGATCGATCGATCGATCGATCGATCGATCGATCGATCGATCGATCGATCGATCGATCGAT -CGATCGATCGATCGATCGATCGATCGATCGATCGATCGATCGATCGATCGATCGATCGAT -CGATCGATCGATCGATCGATCGATCGATCGATCGATCGATCGATCGATCGATCGATCGAT -CGATCGATCGATCGATCGATCGATCGATCGATCGATCGATCGATCGATCGATCGATCGAT -TGACTGACTGACTGACTGACTGACTGACTGACTGACTGACTGACTGACTGACTGACTGAC -TGACTGACTGACTGACTGACTGACTGACTGACTGACTGACTGACTGACTGACTGACTGAC -TGACTGACTGACTGACTGACTGACTGACTGACTGACTGACTGACTGACTGACTGACTGAC -TGACTGACTGACTGACTGACTGACTGACTGACTGACTGACTGACTGACTGACTGACTGAC -TGACTGACTGACTGACTGACTGACTGACTGACTGACTGACTGACTGACTGACTGACTGAC -TGACTGACTGACTGACTGACTGACTGACTGACTGACTGACTGACTGACTGACTGACTGAC -TGACTGACTGACTGACTGACTGACTGACTGACTGACTGACTGACTGACTGACTGACTGAC -TGACTGACTGACTGACTGACTGACTGACTGACTGACTGACTGACTGACTGACTGACTGAC -TGACTGACTGACTGACTGACTGACTGACTGACTGACTGACTGACTGACTGACTGACTGAC -ACGTACGTACGTACGTACGTACGTACGTACGTACGTACGTACGTACGTACGTACGTACGT -ACGTACGTACGTACGTACGTACGTACGTACGTACGTACGTACGTACGTACGTACGTACGT -ACGTACGTACGTACGTACGTACGTACGTACGTACGTACGTACGTACGTACGTACGTACGT -ACGTACGTACGTACGTACGTACGTACGTACGTACGTACGTACGTACGTACGTACGTACGT -ACGTACGTACGTACGTACGTACGTACGTACGTACGTACGTACGTACGTACGTACGTACGT -ACGTACGTACGTACGTACGTACGTACGTACGTACGTACGTACGTACGTACGTACGTACGT -ACGTACGTACGTACGTACGTACGTACGTACGTACGTACGTACGTACGTACGTACGTACGT -ACGTACGTACGTACGTACGTACGTACGTACGTACGTACGTACGTACGTACGTACGTACGT -GTCAGTCAGTCAGTCAGTCAGTCAGTCAGTCAGTCAGTCAGTCAGTCAGTCAGTCAGTCA -GTCAGTCAGTCAGTCAGTCAGTCAGTCAGTCAGTCAGTCAGTCAGTCAGTCAGTCAGTCA -GTCAGTCAGTCAGTCAGTCAGTCAGTCAGTCAGTCAGTCAGTCAGTCAGTCAGTCAGTCA -GTCAGTCAGTCAGTCAGTCAGTCAGTCAGTCAGTCAGTCAGTCAGTCAGTCAGTCAGTCA -GTCAGTCAGTCAGTCAGTCAGTCAGTCAGTCAGTCAGTCAGTCAGTCAGTCAGTCAGTCA -GTCAGTCAGTCAGTCAGTCAGTCAGTCAGTCAGTCAGTCAGTCAGTCAGTCAGTCAGTCA -GTCAGTCAGTCAGTCAGTCAGTCAGTCAGTCAGTCAGTCAGTCAGTCAGTCAGTCAGTCA -GTCAGTCAGTCAGTCAGTCAGTCAGTCAGTCAGTCAGTCAGTCAGTCAGTCAGTCAGTCA -GTCAGTCAGTCAGTCAGTCAGTCAGTCAGTCAGTCAGTCAGTCAGTCAGTCAGTCAGTCA -GATCGATCGATCGATCGATCGATCGATCGATCGATCGATCGATCGATCGATCGATCGATC -GATCGATCGATCGATCGATCGATCGATCGATCGATCGATCGATCGATCGATCGATCGATC -GATCGATCGATCGATCGATCGATCGATCGATCGATCGATCGATCGATCGATCGATCGATC -GATCGATCGATCGATCGATCGATCGATCGATCGATCGATCGATCGATCGATCGATCGATC -GATCGATCGATCGATCGATCGATCGATCGATCGATCGATCGATCGATCGATCGATCGATC -GATCGATCGATCGATCGATCGATCGATCGATCGATCGATCGATCGATCGATCGATCGATC -GATCGATCGATCGATCGATCGATCGATCGATCGATCGATCGATCGATCGATCGATCGATC -GATCGATCGATCGATCGATCGATCGATCGATCGATCGATCGATCGATCGATCGATCGATC -GATCGATCGATCGATCGATCGATCGATCGATCGATCGATCGATCGATCGATCGATCGATC -TCAGTCAGTCAGTCAGTCAGTCAGTCAGTCAGTCAGTCAGTCAGTCAGTCAGTCAGTCAG -TCAGTCAGTCAGTCAGTCAGTCAGTCAGTCAGTCAGTCAGTCAGTCAGTCAGTCAGTCAG -TCAGTCAGTCAGTCAGTCAGTCAGTCAGTCAGTCAGTCAGTCAGTCAGTCAGTCAGTCAG -TCAGTCAGTCAGTCAGTCAGTCAGTCAGTCAGTCAGTCAGTCAGTCAGTCAGTCAGTCAG -TCAGTCAGTCAGTCAGTCAGTCAGTCAGTCAGTCAGTCAGTCAGTCAGTCAGTCAGTCAG -TCAGTCAGTCAGTCAGTCAGTCAGTCAGTCAGTCAGTCAGTCAGTCAGTCAGTCAGTCAG -TCAGTCAGTCAGTCAGTCAGTCAGTCAGTCAGTCAGTCAGTCAGTCAGTCAGTCAGTCAG -TCAGTCAGTCAGTCAGTCAGTCAGTCAGTCAGTCAGTCAGTCAGTCAGTCAGTCAGTCAG -TCAGTCAGTCAGTCAGTCAGTCAGTCAGTCAGTCAGTCAGTCAGTCAGTCAGTCAGTCAG -AGTCAGTCAGTCAGTCAGTCAGTCAGTCAGTCAGTCAGTCAGTCAGTCAGTCAGTCAGTC -AGTCAGTCAGTCAGTCAGTCAGTCAGTCAGTCAGTCAGTCAGTCAGTCAGTCAGTCAGTC -AGTCAGTCAGTCAGTCAGTCAGTCAGTCAGTCAGTCAGTCAGTCAGTCAGTCAGTCAGTC -AGTCAGTCAGTCAGTCAGTCAGTCAGTCAGTCAGTCAGTCAGTCAGTCAGTCAGTCAGTC -AGTCAGTCAGTCAGTCAGTCAGTCAGTCAGTCAGTCAGTCAGTCAGTCAGTCAGTCAGTC -AGTCAGTCAGTCAGTCAGTCAGTCAGTCAGTCAGTCAGTCAGTCAGTCAGTCAGTCAGTC -AGTCAGTCAGTCAGTCAGTCAGTCAGTCAGTCAGTCAGTCAGTCAGTCAGTCAGTCAGTC -AGTCAGTCAGTCAGTCAGTCAGTCAGTCAGTCAGTCAGTCAGTCAGTCAGTCAGTCAGTC -AGTCAGTCAGTCAGTCAGTCAGTCAGTCAGTCAGTCAGTCAGTCAGTCAGTCAGTCAGTC -CATGCATGCATGCATGCATGCATGCATGCATGCATGCATGCATGCATGCATGCATGCATG -CATGCATGCATGCATGCATGCATGCATGCATGCATGCATGCATGCATGCATGCATGCATG -CATGCATGCATGCATGCATGCATGCATGCATGCATGCATGCATGCATGCATGCATGCATG -CATGCATGCATGCATGCATGCATGCATGCATGCATGCATGCATGCATGCATGCATGCATG -CATGCATGCATGCATGCATGCATGCATGCATGCATGCATGCATGCATGCATGCATGCATG -CATGCATGCATGCATGCATGCATGCATGCATGCATGCATGCATGCATGCATGCATGCATG -CATGCATGCATGCATGCATGCATGCATGCATGCATGCATGCATGCATGCATGCATGCATG -CATGCATGCATGCATGCATGCATGCATGCATGCATGCATGCATGCATGCATGCATGCATG -CATGCATGCATGCATGCATGCATGCATGCATGCATGCATGCATGCATGCATGCATGCATG -TACGTACGTACGTACGTACGTACGTACGTACGTACGTACGTACGTACGTACGTACGTACG -TACGTACGTACGTACGTACGTACGTACGTACGTACGTACGTACGTACGTACGTACGTACG -TACGTACGTACGTACGTACGTACGTACGTACGTACGTACGTACGTACGTACGTACGTACG -TACGTACGTACGTACGTACGTACGTACGTACGTACGTACGTACGTACGTACGTACGTACG -TACGTACGTACGTACGTACGTACGTACGTACGTACGTACGTACGTACGTACGTACGTACG -TACGTACGTACGTACGTACGTACGTACGTACGTACGTACGTACGTACGTACGTACGTACG -TACGTACGTACGTACGTACGTACGTACGTACGTACGTACGTACGTACGTACGTACGTACG -TACGTACGTACGTACGTACGTACGTACGTACGTACGTACGTACGTACGTACGTACGTACG -TACGTACGTACGTACGTACGTACGTACGTACGTACGTACGTACGTACGTACGTACGTACG -GCATGCATGCATGCATGCATGCATGCATGCATGCATGCATGCATGCATGCATGCATGCAT -GCATGCATGCATGCATGCATGCATGCATGCATGCATGCATGCATGCATGCATGCATGCAT -GCATGCATGCATGCATGCATGCATGCATGCATGCATGCATGCATGCATGCATGCATGCAT -GCATGCATGCATGCATGCATGCATGCATGCATGCATGCATGCATGCATGCATGCATGCAT -GCATGCATGCATGCATGCATGCATGCATGCATGCATGCATGCATGCATGCATGCATGCAT -GCATGCATGCATGCATGCATGCATGCATGCATGCATGCATGCATGCATGCATGCATGCAT -GCATGCATGCATGCATGCATGCATGCATGCATGCATGCATGCATGCATGCATGCATGCAT -GCATGCATGCATGCATGCATGCATGCATGCATGCATGCATGCATGCATGCATGCATGCAT -GCATGCATGCATGCATGCATGCATGCATGCATGCATGCATGCATGCATGCATGCATGCAT -ATGCATGCATGCATGCATGCATGCATGCATGCATGCATGCATGCATGCATGCATGCATGC -ATGCATGCATGCATGCATGCATGCATGCATGCATGCATGCATGCATGCATGCATGCATGC -ATGCATGCATGCATGCATGCATGCATGCATGCATGCATGCATGCATGCATGCATGCATGC -ATGCATGCATGCATGCATGCATGCATGCATGCATGCATGCATGCATGCATGCATGCATGC -ATGCATGCATGCATGCATGCATGCATGCATGCATGCATGCATGCATGCATGCATGCATGC -ATGCATGCATGCATGCATGCATGCATGCATGCATGCATGCATGCATGCATGCATGCATGC -ATGCATGCATGCATGCATGCATGCATGCATGCATGCATGCATGCATGCATGCATGCATGC -ATGCATGCATGCATGCATGCATGCATGCATGCATGCATGCATGCATGCATGCATGCATGC -ATGCATGCATGCATGCATGCATGCATGCATGCATGCATGCATGCATGCATGCATGCATGC -CGATCGATCGATCGATCGATCGATCGATCGATCGATCGATCGATCGATCGATCGATCGAT -CGATCGATCGATCGATCGATCGATCGATCGATCGATCGATCGATCGATCGATCGATCGAT -CGATCGATCGATCGATCGATCGATCGATCGATCGATCGATCGATCGATCGATCGATCGAT -CGATCGATCGATCGATCGATCGATCGATCGATCGATCGATCGATCGATCGATCGATCGAT -CGATCGATCGATCGATCGATCGATCGATCGATCGATCGATCGATCGATCGATCGATCGAT -CGATCGATCGATCGATCGATCGATCGATCGATCGATCGATCGATCGATCGATCGATCGAT -CGATCGATCGATCGATCGATCGATCGATCGATCGATCGATCGATCGATCGATCGATCGAT -CGATCGATCGATCGATCGATCGATCGATCGATCGATCGATCGATCGATCGATCGATCGAT -CGATCGATCGATCGATCGATCGATCGATCGATCGATCGATCGATCGATCGATCGATCGAT -GTACGTACGTACGTACGTACGTACGTACGTACGTACGTACGTACGTACGTACGTACGTAC -GTACGTACGTACGTACGTACGTACGTACGTACGTACGTACGTACGTACGTACGTACGTAC -GTACGTACGTACGTACGTACGTACGTACGTACGTACGTACGTACGTACGTACGTACGTAC -GTACGTACGTACGTACGTACGTACGTACGTACGTACGTACGTACGTACGTACGTACGTAC -GTACGTACGTACGTACGTACGTACGTACGTACGTACGTACGTACGTACGTACGTACGTAC -GTACGTACGTACGTACGTACGTACGTACGTACGTACGTACGTACGTACGTACGTACGTAC -GTACGTACGTACGTACGTACGTACGTACGTACGTACGTACGTACGTACGTACGTACGTAC -GTACGTACGTACGTACGTACGTACGTACGTACGTACGTACGTACGTACGTACGTACGTAC -GTACGTACGTACGTACGTACGTACGTACGTACGTACGTACGTACGTACGTACGTACGTAC -TACGTACGTACGTACGTACGTACGTACGTACGTACGTACGTACGTACGTACGTACGTACG -TACGTACGTACGTACGTACGTACGTACGTACGTACGTACGTACGTACGTACGTACGTACG -TACGTACGTACGTACGTACGTACGTACGTACGTACGTACGTACGTACGTACGTACGTACG -TACGTACGTACGTACGTACGTACGTACGTACGTACGTACGTACGTACGTACGTACGTACG -TACGTACGTACGTACGTACGTACGTACGTACGTACGTACGTACGTACGTACGTACGTACG -TACGTACGTACGTACGTACGTACGTACGTACGTACGTACGTACGTACGTACGTACGTACG -TACGTACGTACGTACGTACGTACGTACGTACGTACGTACGTACGTACGTACGTACGTACG -TACGTACGTACGTACGTACGTACGTACGTACGTACGTACGTACGTACGTACGTACGTACG -TACGTACGTACGTACGTACGTACGTACGTACGTACGTACGTACGTACGTACGTACGTACG -ACGTACGTACGTACGTACGTACGTACGTACGTACGTACGTACGTACGTACGTACGTACGT -ACGTACGTACGTACGTACGTACGTACGTACGTACGTACGTACGTACGTACGTACGTACGT -ACGTACGTACGTACGTACGTACGTACGTACGTACGTACGTACGTACGTACGTACGTACGT -ACGTACGTACGTACGTACGTACGTACGTACGTACGTACGTACGTACGTACGTACGTACGT -ACGTACGTACGTACGTACGTACGTACGTACGTACGTACGTACGTACGTACGTACGTACGT -ACGTACGTACGTACGTACGTACGTACGTACGTACGTACGTACGTACGTACGTACGTACGT -ACGTACGTACGTACGTACGTACGTACGTACGTACGTACGTACGTACGTACGTACGTACGT -ACGTACGTACGTACGTACGTACGTACGTACGTACGTACGTACGTACGTACGTACGTACGT -ACGTACGTACGTACGTACGTACGTACGTACGTACGTACGTACGTACGTACGTACGTACGT -GATCGATCGATCGATCGATCGATCGATCGATCGATCGATCGATCGATCGATCGATCGATC -GATCGATCGATCGATCGATCGATCGATCGATCGATCGATCGATCGATCGATCGATCGATC -GATCGATCGATCGATCGATCGATCGATCGATCGATCGATCGATCGATCGATCGATCGATC -GATCGATCGATCGATCGATCGATCGATCGATCGATCGATCGATCGATCGATCGATCGATC -GATCGATCGATCGATCGATCGATCGATCGATCGATCGATCGATCGATCGATCGATCGATC -GATCGATCGATCGATCGATCGATCGATCGATCGATCGATCGATCGATCGATCGATCGATC -GATCGATCGATCGATCGATCGATCGATCGATCGATCGATCGATCGATCGATCGATCGATC -GATCGATCGATCGATCGATCGATCGATCGATCGATCGATCGATCGATCGATCGATCGATC -GATCGATCGATCGATCGATCGATCGATCGATCGATCGATCGATCGATCGATCGATCGATC -TCGATCGATCGATCGATCGATCGATCGATCGATCGATCGATCGATCGATCGATCGATCGA -TCGATCGATCGATCGATCGATCGATCGATCGATCGATCGATCGATCGATCGATCGATCGA -TCGATCGATCGATCGATCGATCGATCGATCGATCGATCGATCGATCGATCGATCGATCGA -TCGATCGATCGATCGATCGATCGATCGATCGATCGATCGATCGATCGATCGATCGATCGA -TCGATCGATCGATCGATCGATCGATCGATCGATCGATCGATCGATCGATCGATCGATCGA -TCGATCGATCGATCGATCGATCGATCGATCGATCGATCGATCGATCGATCGATCGATCGA -TCGATCGATCGATCGATCGATCGATCGATCGATCGATCGATCGATCGATCGATCGATCGA -TCGATCGATCGATCGATCGATCGATCGATCGATCGATCGATCGATCGATCGATCGATCGA -TCGATCGATCGATCGATCGATCGATCGATCGATCGATCGATCGATCGATCGATCGATCGA -ATGCATGCATGCATGCATGCATGCATGCATGCATGCATGCATGCATGCATGCATGCATGC -ATGCATGCATGCATGCATGCATGCATGCATGCATGCATGCATGCATGCATGCATGCATGC -ATGCATGCATGCATGCATGCATGCATGCATGCATGCATGCATGCATGCATGCATGCATGC -ATGCATGCATGCATGCATGCATGCATGCATGCATGCATGCATGCATGCATGCATGCATGC -ATGCATGCATGCATGCATGCATGCATGCATGCATGCATGCATGCATGCATGCATGCATGC -ATGCATGCATGCATGCATGCATGCATGCATGCATGCATGCATGCATGCATGCATGCATGC -ATGCATGCATGCATGCATGCATGCATGCATGCATGCATGCATGCATGCATGCATGCATGC -ATGCATGCATGCATGCATGCATGCATGCATGCATGCATGCATGCATGCATGCATGCATGC -ATGCATGCATGCATGCATGCATGCATGCATGCATGCATGCATGCATGCATGCATGCATGC -CGATCGATCGATCGATCGATCGATCGATCGATCGATCGATCGATCGATCGATCGATCGAT -CGATCGATCGATCGATCGATCGATCGATCGATCGATCGATCGATCGATCGATCGATCGAT -CGATCGATCGATCGATCGATCGATCGATCGATCGATCGATCGATCGATCGATCGATCGAT -CGATCGATCGATCGATCGATCGATCGATCGATCGATCGATCGATCGATCGATCGATCGAT -CGATCGATCGATCGATCGATCGATCGATCGATCGATCGATCGATCGATCGATCGATCGAT -CGATCGATCGATCGATCGATCGATCGATCGATCGATCGATCGATCGATCGATCGATCGAT -CGATCGATCGATCGATCGATCGATCGATCGATCGATCGATCGATCGATCGATCGATCGAT -CGATCGATCGATCGATCGATCGATCGATCGATCGATCGATCGATCGATCGATCGATCGAT -CGATCGATCGATCGATCGATCGATCGATCGATCGATCGATCGATCGATCGATCGATCGAT -GTACGTACGTACGTACGTACGTACGTACGTACGTACGTACGTACGTACGTACGTACGTAC -GTACGTACGTACGTACGTACGTACGTACGTACGTACGTACGTACGTACGTACGTACGTAC -GTACGTACGTACGTACGTACGTACGTACGTACGTACGTACGTACGTACGTACGTACGTAC -GTACGTACGTACGTACGTACGTACGTACGTACGTACGTACGTACGTACGTACGTACGTAC -GTACGTACGTACGTACGTACGTACGTACGTACGTACGTACGTACGTACGTACGTACGTAC -GTACGTACGTACGTACGTACGTACGTACGTACGTACGTACGTACGTACGTACGTACGTAC -GTACGTACGTACGTACGTACGTACGTACGTACGTACGTACGTACGTACGTACGTACGTAC -GTACGTACGTACGTACGTACGTACGTACGTACGTACGTACGTACGTACGTACGTACGTAC -GTACGTACGTACGTACGTACGTACGTACGTACGTACGTACGTACGTACGTACGTACGTAC -ACGTACGTACGTACGTACGTACGTACGTACGTACGTACGTACGTACGTACGTACGTACGT -ACGTACGTACGTACGTACGTACGTACGTACGTACGTACGTACGTACGTACGTACGTACGT -ACGTACGTACGTACGTACGTACGTACGTACGTACGTACGTACGTACGTACGTACGTACGT -ACGTACGTACGTACGTACGTACGTACGTACGTACGTACGTACGTACGTACGTACGTACGT -ACGTACGTACGTACGTACGTACGTACGTACGTACGTACGTACGTACGTACGTACGTACGT -ACGTACGTACGTACGTACGTACGTACGTACGTACGTACGTACGTACGTACGTACGTACGT -ACGTACGTACGTACGTACGTACGTACGTACGTACGTACGTACGTACGTACGTACGTACGT -ACGTACGTACGTACGTACGTACGTACGTACGTACGTACGTACGTACGTACGTACGTACGT -ACGTACGTACGTACGTACGTACGTACGTACGTACGTACGTACGTACGTACGTACGTACGT -TCGATCGATCGATCGATCGATCGATCGATCGATCGATCGATCGATCGATCGATCGATCGA -TCGATCGATCGATCGATCGATCGATCGATCGATCGATCGATCGATCGATCGATCGATCGA -TCGATCGATCGATCGATCGATCGATCGATCGATCGATCGATCGATCGATCGATCGATCGA -TCGATCGATCGATCGATCGATCGATCGATCGATCGATCGATCGATCGATCGATCGATCGA -TCGATCGATCGATCGATCGATCGATCGATCGATCGATCGATCGATCGATCGATCGATCGA -TCGATCGATCGATCGATCGATCGATCGATCGATCGATCGATCGATCGATCGATCGATCGA -TCGATCGATCGATCGATCGATCGATCGATCGATCGATCGATCGATCGATCGATCGATCGA -TCGATCGATCGATCGATCGATCGATCGATCGATCGATCGATCGATCGATCGATCGATCGA -TCGATCGATCGATCGATCGATCGATCGATCGATCGATCGATCGATCGATCGATCGATCGA -GATCGATCGATCGATCGATCGATCGATCGATCGATCGATCGATCGATCGATCGATCGATC -GATCGATCGATCGATCGATCGATCGATCGATCGATCGATCGATCGATCGATCGATCGATC -GATCGATCGATCGATCGATCGATCGATCGATCGATCGATCGATCGATCGATCGATCGATC -GATCGATCGATCGATCGATCGATCGATCGATCGATCGATCGATCGATCGATCGATCGATC -GATCGATCGATCGATCGATCGATCGATCGATCGATCGATCGATCGATCGATCGATCGATC -GATCGATCGATCGATCGATCGATCGATCGATCGATCGATCGATCGATCGATCGATCGATC -GATCGATCGATCGATCGATCGATCGATCGATCGATCGATCGATCGATCGATCGATCGATC -GATCGATCGATCGATCGATCGATCGATCGATCGATCGATCGATCGATCGATCGATCGATC -GATCGATCGATCGATCGATCGATCGATCGATCGATCGATCGATCGATCGATCGATCGATC -CGATCGATCGATCGATCGATCGATCGATCGATCGATCGATCGATCGATCGATCGATCGAT -CGATCGATCGATCGATCGATCGATCGATCGATCGATCGATCGATCGATCGATCGATCGAT -CGATCGATCGATCGATCGATCGATCGATCGATCGATCGATCGATCGATCGATCGATCGAT -CGATCGATCGATCGATCGATCGATCGATCGATCGATCGATCGATCGATCGATCGATCGAT -CGATCGATCGATCGATCGATCGATCGATCGATCGATCGATCGATCGATCGATCGATCGAT -CGATCGATCGATCGATCGATCGATCGATCGATCGATCGATCGATCGATCGATCGATCGAT -CGATCGATCGATCGATCGATCGATCGATCGATCGATCGATCGATCGATCGATCGATCGAT -CGATCGATCGATCGATCGATCGATCGATCGATCGATCGATCGATCGATCGATCGATCGAT -CGATCGATCGATCGATCGATCGATCGATCGATCGATCGATCGATCGATCGATCGATCGAT -GTACGTACGTACGTACGTACGTACGTACGTACGTACGTACGTACGTACGTACGTACGTAC -GTACGTACGTACGTACGTACGTACGTACGTACGTACGTACGTACGTACGTACGTACGTAC -GTACGTACGTACGTACGTACGTACGTACGTACGTACGTACGTACGTACGTACGTACGTAC -GTACGTACGTACGTACGTACGTACGTACGTACGTACGTACGTACGTACGTACGTACGTAC -GTACGTACGTACGTACGTACGTACGTACGTACGTACGTACGTACGTACGTACGTACGTAC -GTACGTACGTACGTACGTACGTACGTACGTACGTACGTACGTACGTACGTACGTACGTAC -GTACGTACGTACGTACGTACGTACGTACGTACGTACGTACGTACGTACGTACGTACGTAC -GTACGTACGTACGTACGTACGTACGTACGTACGTACGTACGTACGTACGTACGTACGTAC -GTACGTACGTACGTACGTACGTACGTACGTACGTACGTACGTACGTACGTACGTACGTAC -ATGCATGCATGCATGCATGCATGCATGCATGCATGCATGCATGCATGCATGCATGCATGC -ATGCATGCATGCATGCATGCATGCATGCATGCATGCATGCATGCATGCATGCATGCATGC -ATGCATGCATGCATGCATGCATGCATGCATGCATGCATGCATGCATGCATGCATGCATGC -ATGCATGCATGCATGCATGCATGCATGCATGCATGCATGCATGCATGCATGCATGCATGC -ATGCATGCATGCATGCATGCATGCATGCATGCATGCATGCATGCATGCATGCATGCATGC -ATGCATGCATGCATGCATGCATGCATGCATGCATGCATGCATGCATGCATGCATGCATGC -ATGCATGCATGCATGCATGCATGCATGCATGCATGCATGCATGCATGCATGCATGCATGC -ATGCATGCATGCATGCATGCATGCATGCATGCATGCATGCATGCATGCATGCATGCATGC -ATGCATGCATGCATGCATGCATGCATGCATGCATGCATGCATGCATGCATGCATGCATGC -CATGCATGCATGCATGCATGCATGCATGCATGCATGCATGCATGCATGCATGCATGCATG -CATGCATGCATGCATGCATGCATGCATGCATGCATGCATGCATGCATGCATGCATGCATG -CATGCATGCATGCATGCATGCATGCATGCATGCATGCATGCATGCATGCATGCATGCATG -CATGCATGCATGCATGCATGCATGCATGCATGCATGCATGCATGCATGCATGCATGCATG -CATGCATGCATGCATGCATGCATGCATGCATGCATGCATGCATGCATGCATGCATGCATG -CATGCATGCATGCATGCATGCATGCATGCATGCATGCATGCATGCATGCATGCATGCATG -CATGCATGCATGCATGCATGCATGCATGCATGCATGCATGCATGCATGCATGCATGCATG -CATGCATGCATGCATGCATGCATGCATGCATGCATGCATGCATGCATGCATGCATGCATG -CATGCATGCATGCATGCATGCATGCATGCATGCATGCATGCATGCATGCATGCATGCATG -TACGTACGTACGTACGTACGTACGTACGTACGTACGTACGTACGTACGTACGTACGTACG -TACGTACGTACGTACGTACGTACGTACGTACGTACGTACGTACGTACGTACGTACGTACG -TACGTACGTACGTACGTACGTACGTACGTACGTACGTACGTACGTACGTACGTACGTACG -TACGTACGTACGTACGTACGTACGTACGTACGTACGTACGTACGTACGTACGTACGTACG -TACGTACGTACGTACGTACGTACGTACGTACGTACGTACGTACGTACGTACGTACGTACG -TACGTACGTACGTACGTACGTACGTACGTACGTACGTACGTACGTACGTACGTACGTACG -TACGTACGTACGTACGTACGTACGTACGTACGTACGTACGTACGTACGTACGTACGTACG -TACGTACGTACGTACGTACGTACGTACGTACGTACGTACGTACGTACGTACGTACGTACG -TACGTACGTACGTACGTACGTACGTACGTACGTACGTACGTACGTACGTACGTACGTACG -ATGCATGCATGCATGCATGCATGCATGCATGCATGCATGCATGCATGCATGCATGCATGC -ATGCATGCATGCATGCATGCATGCATGCATGCATGCATGCATGCATGCATGCATGCATGC -ATGCATGCATGCATGCATGCATGCATGCATGCATGCATGCATGCATGCATGCATGCATGC -ATGCATGCATGCATGCATGCATGCATGCATGCATGCATGCATGCATGCATGCATGCATGC -ATGCATGCATGCATGCATGCATGCATGCATGCATGCATGCATGCATGCATGCATGCATGC -ATGCATGCATGCATGCATGCATGCATGCATGCATGCATGCATGCATGCATGCATGCATGC -ATGCATGCATGCATGCATGCATGCATGCATGCATGCATGCATGCATGCATGCATGCATGC -ATGCATGCATGCATGCATGCATGCATGCATGCATGCATGCATGCATGCATGCATGCATGC -ATGCATGCATGCATGCATGCATGCATGCATGCATGCATGCATGCATGCATGCATGCATGC -GATCGATCGATCGATCGATCGATCGATCGATCGATCGATCGATCGATCGATCGATCGATC -GATCGATCGATCGATCGATCGATCGATCGATCGATCGATCGATCGATCGATCGATCGATC -GATCGATCGATCGATCGATCGATCGATCGATCGATCGATCGATCGATCGATCGATCGATC -GATCGATCGATCGATCGATCGATCGATCGATCGATCGATCGATCGATCGATCGATCGATC -GATCGATCGATCGATCGATCGATCGATCGATCGATCGATCGATCGATCGATCGATCGATC -GATCGATCGATCGATCGATCGATCGATCGATCGATCGATCGATCGATCGATCGATCGATC -GATCGATCGATCGATCGATCGATCGATCGATCGATCGATCGATCGATCGATCGATCGATC -GATCGATCGATCGATCGATCGATCGATCGATCGATCGATCGATCGATCGATCGATCGATC -GATCGATCGATCGATCGATCGATCGATCGATCGATCGATCGATCGATCGATCGATCGATC -CGATCGATCGATCGATCGATCGATCGATCGATCGATCGATCGATCGATCGATCGATCGAT -CGATCGATCGATCGATCGATCGATCGATCGATCGATCGATCGATCGATCGATCGATCGAT -CGATCGATCGATCGATCGATCGATCGATCGATCGATCGATCGATCGATCGATCGATCGAT -CGATCGATCGATCGATCGATCGATCGATCGATCGATCGATCGATCGATCGATCGATCGAT -CGATCGATCGATCGATCGATCGATCGATCGATCGATCGATCGATCGATCGATCGATCGAT -CGATCGATCGATCGATCGATCGATCGATCGATCGATCGATCGATCGATCGATCGATCGAT -CGATCGATCGATCGATCGATCGATCGATCGATCGATCGATCGATCGATCGATCGATCGAT -CGATCGATCGATCGATCGATCGATCGATCGATCGATCGATCGATCGATCGATCGATCGAT -CGATCGATCGATCGATCGATCGATCGATCGATCGATCGATCGATCGATCGATCGATCGAT -TCGATCGATCGATCGATCGATCGATCGATCGATCGATCGATCGATCGATCGATCGATCGA -TCGATCGATCGATCGATCGATCGATCGATCGATCGATCGATCGATCGATCGATCGATCGA -TCGATCGATCGATCGATCGATCGATCGATCGATCGATCGATCGATCGATCGATCGATCGA -TCGATCGATCGATCGATCGATCGATCGATCGATCGATCGATCGATCGATCGATCGATCGA -TCGATCGATCGATCGATCGATCGATCGATCGATCGATCGATCGATCGATCGATCGATCGA -TCGATCGATCGATCGATCGATCGATCGATCGATCGATCGATCGATCGATCGATCGATCGA -TCGATCGATCGATCGATCGATCGATCGATCGATCGATCGATCGATCGATCGATCGATCGA -TCGATCGATCGATCGATCGATCGATCGATCGATCGATCGATCGATCGATCGATCGATCGA -TCGATCGATCGATCGATCGATCGATCGATCGATCGATCGATCGATCGATCGATCGATCGA -ACGTACGTACGTACGTACGTACGTACGTACGTACGTACGTACGTACGTACGTACGTACGT -ACGTACGTACGTACGTACGTACGTACGTACGTACGTACGTACGTACGTACGTACGTACGT -ACGTACGTACGTACGTACGTACGTACGTACGTACGTACGTACGTACGTACGTACGTACGT -ACGTACGTACGTACGTACGTACGTACGTACGTACGTACGTACGTACGTACGTACGTACGT -ACGTACGTACGTACGTACGTACGTACGTACGTACGTACGTACGTACGTACGTACGTACGT -ACGTACGTACGTACGTACGTACGTACGTACGTACGTACGTACGTACGTACGTACGTACGT -ACGTACGTACGTACGTACGTACGTACGTACGTACGTACGTACGTACGTACGTACGTACGT -ACGTACGTACGTACGTACGTACGTACGTACGTACGTACGTACGTACGTACGTACGTACGT -ACGTACGTACGTACGTACGTACGTACGTACGTACGTACGTACGTACGTACGTACGTACGT -GTACGTACGTACGTACGTACGTACGTACGTACGTACGTACGTACGTACGTACGTACGTAC -GTACGTACGTACGTACGTACGTACGTACGTACGTACGTACGTACGTACGTACGTACGTAC -GTACGTACGTACGTACGTACGTACGTACGTACGTACGTACGTACGTACGTACGTACGTAC -GTACGTACGTACGTACGTACGTACGTACGTACGTACGTACGTACGTACGTACGTACGTAC -GTACGTACGTACGTACGTACGTACGTACGTACGTACGTACGTACGTACGTACGTACGTAC -GTACGTACGTACGTACGTACGTACGTACGTACGTACGTACGTACGTACGTACGTACGTAC -GTACGTACGTACGTACGTACGTACGTACGTACGTACGTACGTACGTACGTACGTACGTAC -GTACGTACGTACGTACGTACGTACGTACGTACGTACGTACGTACGTACGTACGTACGTAC -GTACGTACGTACGTACGTACGTACGTACGTACGTACGTACGTACGTACGTACGTACGTAC -TACGTACGTACGTACGTACGTACGTACGTACGTACGTACGTACGTACGTACGTACGTACG -TACGTACGTACGTACGTACGTACGTACGTACGTACGTACGTACGTACGTACGTACGTACG -TACGTACGTACGTACGTACGTACGTACGTACGTACGTACGTACGTACGTACGTACGTACG -TACGTACGTACGTACGTACGTACGTACGTACGTACGTACGTACGTACGTACGTACGTACG -TACGTACGTACGTACGTACGTACGTACGTACGTACGTACGTACGTACGTACGTACGTACG -TACGTACGTACGTACGTACGTACGTACGTACGTACGTACGTACGTACGTACGTACGTACG -TACGTACGTACGTACGTACGTACGTACGTACGTACGTACGTACGTACGTACGTACGTACG -TACGTACGTACGTACGTACGTACGTACGTACGTACGTACGTACGTACGTACGTACGTACG -TACGTACGTACGTACGTACGTACGTACGTACGTACGTACGTACGTACGTACGTACGTACG +GAAAGGCATAATAAGTAGCACGTACTAACGCGTCTTCGCTGAAAATAGTTAACGGAGATC +GTGCGAATAACCTGTCTAATAGCTACTAAAGCTATCTCCAGGTAGATTCCATACCTGGAG +TGTATACCCTACCATAGGATTACTATGATCGTTAATGAAAGACCAAGAACTTGCAATTTG +GCATTCAATTAACTCTACCCCATATATCAGTTCCTGATCTTGAGTCACAAGGAACAGGTG +TCAGATGTTGATCCAAACCCTACGGCGACTGCAAATAGGAGATCCATAAGGAGTTAACCT +CGAATCCCCAAAGCTGACCCCAGTCCCCAGACCACTTCAAATCCAGTCTCACACAATGTG +TTTAGACTGGGTAGTTCGTTTTATCGCGTTAATTGTTATCCAATGTCGGAAAATCATGAG +TAGAGGATACTAACTCGCGCCGGTCTCGTAAGGTGAAAATTAAGGATTTATCGGCGTATG +CCTGTGAATATGTATAGATTAGATATATGTGCAAATCTGGGGCAAAAGTAGGAGGACCAA +TGCTGAGGAGCGACGTTTTCCACGCGTGCACTTTGACCACATGTACAACTCGAACAGTGG +GTCAAGTGTTTGTGAAAAGGAATGCTAAAATTACTGACTCTTTAACTCTAGAATTCAGGC +ATTTCCTGGGCAAGAAAATGTAGGTGCGGGCTTGCCAATGTAAGGCTTAATTAACCTCCG +AAGTGCAGGTATTGCTGACCTTTTCTTCGTTATGGGATCTGACGAATTACCTACTGTACC +ATTCTCCACTCTCATGCTATTTTAAGTAGAGGCTGCCTATGCCTTTGTGATCTGGCCCTT +GGCAAGCCGTAGCTGCACTTATTCAACGACATAAACCGATTGGTACATTATTCTCGATGG +AGTCACGTGGGGCGCGTTTGATGAATCTCCACTCGTACACCGCCCTCATTGGGCCAAACT +CAACCTTACTTACATGGCTGATATTCATTCCAGTCTTAACTGGGAGAATAGAACTACACA +AAAGAAGATAAGTGTGTATCAGCTTCATTGTCAAGAAGTTCTTGAGCGGGATATTTATGT +ACACAAGCTGTTATGGCGCGTTAGAACTGTCCCCGGACCAAGTTACTTAGAGATTTGGTA +AAGGAGTTAGATAGTGATGATAAATAGATGTCCACAACCTTGTAATCGCCACAGTTTTAT +ATCTGCCAAAGGGAGTGGTCTGGCGAATTAATTTACACCGTTTCCTCGTTAAACTGTAAT +TTATATTGGGAAGAGGCCTGATCGTGCTTCCGCGGTGTTTAACTAAACAGCCATGATACG +CATTGATAGTTTCTCTCCTATTCCCAAGCTACCAGACATCATTAATACTACCGCAACGAG +TAAACTGTAATATCTACGATAATGATGACATTCTTTGCAAGTGGGGTATCAGTGGCAGTT +AAACTCTAGATGCTATCGCTCTTCTCGAGCTTAGTGTGTACTCACCAGTCGCAGGAAGTT +TGGCTGTTTGAAGTTTAATCACTACTCTAGCTTATCCGCGCTAAACATTCTGATCGTGCA +CGTGTCGGACTCAAAATGTCCCAGTATTTACAGGGCTCAAGTGGTGTGACTCGTAATTAG +TGGCCATTTTAAATTGACATTTGTTTTACTCATATCGTTCTCGGTTTATATGACGACTCT +CGATTAATTTGTTGACGTTCTGTCTGCGCGGATCGGTGGAGGCAGACAATAGTGCCGAAA +TGTTACTTGGGGAATACTAAGTTCCAAGTCCCCTAGTTATATCGAGGAGTGATGAGATCT +CCTACTGCATTGCCACACCTTCCCCATACACTTCCTAAATAAGCTGACCCTAGAATAAAG +CTGAGGAATTTCGTACTGAAAGGTTTTGAAGCATGATATTTATTAAGATCTTTATCGTCG +TATACCACATGGCGTCTCCTGGTGTATTGAAATGTTCATACGACTGCAAAAGGAGTAACA +TACGTGGTTAGATACCCGTTCCGGTTATGTCTGCCTCTAAAGCCAGAAGGCAGGTTCTCA +CCACTAGACTGTTTATTACTCCTTTAAACTTATTCTGGACCGTACAGTCTGAACCGGTCA +GATTGGGTTATATACACGCCAAAATCATTTTCAGCGCGATTAAATTGTCATAACCTAACC +TACTCGGGTAAACTCTGACGTCATCTGCTGAACTTCTGGAGCGAAGGGTAATTAAATTTA +TAGTTTTACCCTATATTATTTAAAGGAATCTGCTTCCCATCATCCTGTTATCTATGTGTC +TGTTGCCTTGAGGGACTTTCGTCTCTGAGGTGACGTGCTAATTGTTTGGTTAATCACATT +ATTTGTTCACGGACAAATCATAGTAGAGTGAGCAACATTACTGGGGTCGCGTGAAATAGT +TATAGGGCTTATTATAACCTTGTCTAAGTATATGGTAAGCTCAGTCACGTCTTCTCGACG +TGGAAAATATTGAACCGACGCCCACAGCGGTTATTGCATACTCTAGGGTGTATATAACTT +TTGAAGTACTACAGAGACAGATCATTGAGGATAAGAGCCTAATGATCAGGACATAGTGGA +TGCAAGGTCTAAATGGGGCGTTTGTACCTATGTCCCACTTGGCGAAAACTGTTGATGATT +ACTTGCGAGGCAATTGTGGAGGACTGGAAGACGACAAGTATTTTAATGATACATTACCTC +GTTTGAATTCACCCATACTTAATTGTGTGACGAATATCCCAGCGATATACGACCTGTCAA +ACATTCAATCGGTAAAGGAATTTCATAAAGCGACTAATTGACATTGATCAACCACTGGGA +CAACTACCTATATCTAGAAAACAGATTTAAAACTGCCCGTTTCTTATACGACTGCCAGAC +CACACCTCCAGCGCAGCTTACCTTTAAATACAAGCCTAGCGCCCTCTATAACCCGACGCG +AGATGAGCCTCCAGCCATCAGACACAGGCTAAAATTGCCTTTATCGGAACTTCAATGTCA +GGTACACAAAAGGGAAAATCATTTGGAAATACTTTGATACTTATAAAGGATTCGTCCTTC +TCTACGTCCGGAGACCCATCTCGCACCATTTATCGGTTTAGGCCTAATTTTGAAAGGACT +AGCCACTATGACACTCATGAACGGCCTATTACCAACCATCGACTGAATGACGTACGGATA +TCCGGATAGGACGGAACTCGTTTATGCTATGCTGGTAACGCAGCTAGCCCGGGGCATTAG +TAGATGCGTCCCAAAACGAGTATGTGTATCTCGCACTCTTACAATTCTTGGTGAGAAGAG +TGAGGTCTAATATCAGGAGTATGACTTGGTCCTCTACCTAGAGGATGACATACGGAGTTT +TAGGTGGAGACAGAAAATTAGTATACTAGCCGAATGAAACTTAAATCTGAGACGATTGCA +CATCATCCGCAGACATGCGATTAGCCACATAATGGGTTCGTTGAGATGTCTCAGACCCAT +ACAAGTATCTCTATGATTAAGGTTAGCTAATTGTGGAGATCCTTGAAAGGAGACTTGGAT +CCGGTGCATTACCTTCATGATGCTTCCGACCTATGGTGCGCGAGTTGCGCTGTATTTGTG +CACCTAAGAGAAACGTGACACGCGTAGCAGCTCCTTAAGGCCCGGGTGGCTAGAATTTTA +GATGAATACGGTTTGTAAATTTAAATTAGTCCCAGTCGGCGTCCTTACCTCTACATCACT +AAGGCTATGCGGCGATTAACTTAATGTAGTGGGGACAGTAGTTGTTATCTCAGCCGTCTT +AAGTCTGCTTGTAACAACCCCTTTAAGTTAGAGCTTGTGTTTTAAAGTCAGCTTTTAGCC +ATACAAATAGTGCTTCTGTAGGTTTTGCCGATTACGCGTTATATAACTTTACTGTCCATA +GTGCTTCTTCTTGTAAAGAATGAACGTTAACAATAGATAAACGTAGGAATCCACGCCAGA +GTTGATAACTTAATGAGTATAGCCGGTTATACGTGGGGAATACACTAGGTAAGGTTAGAC +TTAGGTGTTTATTGGCGGTGAATTTGGACAAACTAAAATCGTGGCCGTAGCAAGTAAAAT +CGTTGTGAAACCTCAGACTATAATCCCCTGCTGGCTTGAAAGCGATCTACAAGCACTTCA +CGCTAGCAAAGAACGGGGTATGTCCCTCCAATACTTTTGACGTGAAGTGATATGTTAGTC +AAATAAAATTACACATCCTGGTTTTGACTGTTTTCAAACCATGAGTGTGCTAGAACTGTC +AAATTAGATCTGCTAAGGCGAAAACTATGAAAGCTAAGACAGCTTCTATCGAGGGTTGTT +TCTTATACCTTACCTATTAATTTTAGTTATAGCCGAGCTCAAGGAGAAATAAAGGAATTT +CCTCTCCAGATACCCAGAGTGATGTCTGTTGACTAGACCAAGTAAAGAAGTGTAAAGCCG +AGGCAACGGCTAGTACTTTGAATGACCTAATATAGTAACGAGGTTTTGTGATACACATAT +CGTGATGACATCACATCTTGCAAATCCAGTATAGAGTAGTTGCAATTACTTTCTTGTGGT +AGCACTTGCGTCTTACACGATTCAATATGACATCGGCACGTCGTGTAAGTCTCCAGGAGT +TATATAAGTTGTAATAATATATGAATTGAGGAAGTCAGTTTGATCGCTAACATGCAACCC +CAGATAATATATGAGAGGAAAGGAGATACGCACGATCATCTATTCAATTTATTGACTCGC +CCATAACGATCGGAAACCTTAATCCTGTACCACCTTCATCGGCTTTCCCAGAAGGATAAG +TGTTGGTCTAAAGAATGCGACCCTTTATAGTTGGGTCGTTCACTTGTTGATTTCTTGATA +CTGAGCGATTAGGATAGCCGAATTTTCTCTTGCTGACAGTTGTGAAAGATCTACAGTTAG +ATGTCAAGACGCTCATAGGGGATTCATTTATTTAGATTGGAGGCTGCCAGTTCTATTGTA +GGCAAGACCCTTTGAAACTTTAGTGGAATTGCCGTGCTTGTGCTGTTAGCCTCAACGCTT +GCGGTATTATCATAGGCTATTACGTGACCCGAGTGTACGGATATGTTTCTAATTAAAAGT +ATTAGAAAGTTATGAATAGGCGGTCGGTCGTACCTTGGTAACGCTGGGCTATTTAGGAAC +CTGCTTTGTCTTCGGTGTAGACTTGTTCACAACGTTGACCCGAAATTTAGTTCTCTCTAA +CTATTTAGCTCCAGTTTTGTATCCACGAAAGTTCAGTTGGTATTTTAGTCATTTTCTGAT +GAGCCGTACATGCAGCTATGTTTGTCCAACGGTATAACCGAATCAAACAAAGATCAGTCC +TAACATCGATGAGTGGAATTGGTTGTACACTGCGACGCTCCTAAGTGGGGATGATGCAAA +TAAAACGCCGGACAGCTCCGATCGCATCGTAAGTTACATTCGATAGAGCGAATATCAGCG +AGCTTCTTCGGTACCTTCTGTGCATCATGGAATAGCGTAGGAAGGTATTTCTCAAGAACG +TGCATCAAGTCAGAAATCTAGCATCACTCCGTCTACCGGTAATGTTCAACGGATAAAGCT +CGGAGTTCGAATCGGTAAATATGTAGGAACGCTAGAGATTCGAGCAGTACGGTAGTGTAG +CTATTCACTTAGGCAAGAACTATCGGGGACCACTCGCAGGATTCGATACATGATTCCTAT +AGCATGATTGCGATGCTGTTGCACTATACTCGACGACGCATGTATAGACAATCGCAGATA +GAATTTAGGTTGCCCCACTACACAAGTCTGTCTATTGTACACGTTGTGGCTTAGAATCGA +TTACGACCGGAAATAAATATTTTATCTTATTAGCTGTACCTATCTGGCATTTCTAAGGAC +AATTGATATGCCTACTTATCCAGTCCACCTCAGAATCCACGATCTTGGAATTACCTTTAA +ACCTGCTTGAAACAGGTCGTGATTCAATCAAATCTATCTGAAGTCCGTGGAGCATTTTCA +AAACGCTTTGATACCTTTCCGGTGACACAAAAGGAGGAACTAAAAGGGCACATACCCTAT +GATATAAAACTCAATGTGTCATTAAACAAAGGTATAAGTCTTTCAACTGACTATGAATGA +CCACTGCACGAGGAGGTTGTTAGAATGAAAAGCTGAGAAGGCAGTATCTCATCTTTTATC +TGTAGTAGGGTTCTTTCGTCTAACTGACTATTTGAGGCATTATTCTCAGGCTTTCAGTTG +TGTTTCGCTAACTAGACATACTACGTCTTATGTGAAGCTACGTCTGGTTGTTAAGTTTCA +ATCGAGTAAACTTTGAAAACGACCTACAGCCTTGACGAAGCTCCCACAACTGTGATAACT +AGTTCTTGCCCTGCACGCGCGGATTCTCACCTCTCAACAACCGCGTACCCTTCGCCCGTT +GCGTAAGGCATGTAATCCGCGCTTGAGCCATACCCACCGGCCAGATTAATCAGTCTGAGA +CGATACGCAGTTATAGCTGTAATGGGGAAATACCCCGGAAGTTTCTGATCCATTAAAACC +GCACGGATCTCGACGCAAAACTCCATGTTCCAACAATACGGCTTTAGGCAGGTGCCAACG +TCGACGCTGGCTAAGTAACTTACCACAGAGGATTCTGAGCTTCTTTGCGTTATTAGATGT +TTCTAACCTTAAAATAGTAAATAGAATACTGTGGACCAAGGCATAAATGCCGTGCTGGTT +AAAACCAGGTGCATTTAAAGCTCGATCAAGGCCGGTTTTGGGCTGTTTACTTTCTGAAAT +AACTGCGATGCCGGCCCGAGGAAGATCTAAACTACCAATGAAATTACAAGTGGCTTCAAG +GCCAAGCCATTTGAGTACTTGACTTATGTGAGTACTTTCCTAAACCATCAAGGGCAGGGT +TTGTTGCAATCGTATGGGCGTATATGGACAATTGAACGAGGCAATGTAGATGTCCCTCGT +GTAGGGGTATGCTAGCAACTTTTGTTATTTCTCCAAGAGCAATGCTCGTATAATCTTCAG +ACCACTATCTTTCGTGGGTTTTCTCGTATTCCGGCGTCGTATAGTATATCACAAGAGCTC +GTACATTCTAAAATATTAGTAATTTTCAAGGTGTAATTTTACACGATGTTAGACTCGTTC +TATCACACTGCTTGGTAGTTTAATATGCTGTAGTACTTGAGGATCGTCGGTGGAACGGTC +CTAGGATCTAAACTAGTGATTACGAACTCTTTGTGTAAAATATGAGCGTATTCGCACTCA +GTTGCAATTAAATAGCTAAATGATCGGTAAATATCCGGGGTAAATCAACTTGAGTTTAGA +GGATCCGTCGTTAAGAGATGATGTACATTCGTCGATTTAGGATCCTAACGTGGCGTTCGT +ATGAAAAGAGCTGAACTAAATAGGAAAACGTTAACCAGTGACTACGCCCCAACCATTGCA +AGATGTACCCCAATGATGGTTTTGGTATCGAAACTTCTCTTAATTGTGTTTCTTAAGTAC +TGGCAAAATTCGAGCCGGCATCGTTTGTTGATAGTTGGGTCTAGGATTTTACACCTTGTG +TTAGCACTGGGCCATTAATTCAATAGTAACAAGAATACTAATTACCAATGTGCGTGAAAA +TCTCCTTGACTGGTGCAACGTCATTCACAGTCGGATCTCAAGTTATTAGGTGCTAACTGT +ATACACCAAATTTAGGATAAGAGCCGGCTTAAGGCTAATCTAGACCCAATATTAATCAAT +ATTTTACGTAATGCATCCACGCGGCGTGCTCTTGGTGAGCAGCTGGGATTAAACGCGTAG +GTCGAACTATCGAGGGTTTACAAGAAAGCCAAGTGAAAATGAGACTATTGGCCATCGCGA +GATTTGAATAAATGTCCCTTGGTACTTATACGTTGGGCGAACGGGGATGAGCCAGGCTGC +TATCATCGTTTCGAGGTAGCTTCCAAGTGGATGAACTCAAAGACTGGCATTATGTGAAGA +GCATAGCGCTTTTCCCCGTATTATGGCAGCAGCTGGTTACCCATACTTGTGATCCCCGTA +ATTCTACTGTCATAGAAGGATGACCGAATCAATGAGCCGGGTGGTGTCCAAAAGCGATCC +TAATCCTTGCTGATTTACCTTGAGCGGTCACGTCTGTCTCAGCGACATTCGCCTTGCGTT +AGACTAGGCCGTAAGTAAGGAGTGCACTCCACAACGGCGTAATGCGTGCGGCGAGTAATG +TATTAGCATGTTAACCACATTCTTGGCAGCCAGATCAAAATCACTTTTCATCTGGTTGTC +TTAACAATCCGATAGAATCTAATGTAGCGATGCGTACTAGAAATAGTTACAATCTACAGT +CTTGCTGCACTTGCTGCTAATAATGAGCGAGGACCTATCCCTCCTTAAGCAAGTTCCTTG +TTCCGTGCGGGGAGCCCTGGCGCTAACTCTTTACATGATTAGTATCGCATGTTGTTACAT +ATATAATAGATTTACATCATTTCAAATGCAATGATTCGTGCTCCTAAAATGAGTCGTATG +AATAGCCACAGCGTACGGAAACCTGAATTGATTTGTAATTTAAAGATCAACTTAATCTGT +GTTGATCAGAGCGAGCATTGCAGAATACCCCTGCATCTAGGAATCGGTGCCAGTGTAAAA +GCCTGTTAGTAAAACCACGACTATGTAGTGTGTACCACACTCGGAGTGCGTCAAGCGAAG +TCAAACATGGAAATGAAACCATGCGTACGGAAAAGACCAGTGATTTATAAGGACATTCAC +ATAGACTCCAAAACTGACCCGATGGAGTCTACGCCGAACAGTTGGTATCAACATTTGTCT +CGATTTTCTGTTGGGAACATCCATCCCTACCCACAACGTACTGGACCATAATCAAGGGTT +TGGAACAGTACGCTCCTGTACTCAAGAAGTCCTTGCACGAAAGCAATAGGTTGAACTTCA +TCATATAGGCGATGACAGTGCTATCAGCCGGACTGGCTGTTCTCGTAGAAGTCACTCGAA +TCAATAAGATACGAATACTCCATCCTGTACGGGGACACTATATTATGCTAGCCGATTCTG +TAAATGTAGTCTTTACCGAGAATTGCTGACACTGATTTGAGTGTAGGAGGTCCGGTATAC +ACTTATCATCAACTTATTCCTACACTCGGTTTTCAATAGTTCGTAGCCCCAGGTTGCATG +AATATTATACCTCGGATAACACCTACTAATCCGTCCACAGCCTAGCACTTACTGGCGATC +AATGGAGCATGATGTACTTAGGGGACGGTATGAACATTCTTAACAGTTCCAAATGACCTG +TAGCAAATACAATAGCATCTTTGTTTAAGCATGGTCCTCTGCGGTTTGAAATGTCGCTAA +TCTAGTGATATTCCTTGTAAGCCACTGTTACTCTAATTTAGCCCACTCCAGAACGAGTTT +GTGTCCATGAAAATGTAACTCCCCAGACATGCAAATACGCCTTATTGCTGAATATCGGAA +CAAACAAAGTCGTTATCATCCTGAAATCGACGACAAGTACATATTAAAGGTTTGTTTGGC +AAAATAGGTAGCAAGTAGGATGTTCATAACAATTAAAGCGCGTAACTCCTAAATGTGCAT +TATGCGCCGAGGACCGATAGCTGACGCCGCTCTAGCTTCTATTGTTCCACTGTACGGTAC +AAAGATTGAATACGGAAACAGAATTCGTCAATTTGTTGAATTATGTTCTATTCGTTTTAT +CTGGTATATTTGTTACCTAACGTATTTAGGGAAAGTAGCTTCATGAAGAAATCTAATCCC +TCGCGTGACGAGTTTGCTGTGATTATTATGCGACCTGACTCTTGTAGTGTGGAGTTCGTT +GTCGTATCTGTACAAACTGCCGACACGTAGACAGGCCTGTCTAATAAACCAGGGACCTTT +AAGCGTCTTTGTAATTAAGTAAGTACCAGACCATCCTTAGATCAATATGATGCGCAACCG +GACCGGATCAAATGTTCCAAGCTCGGTAGGTTATCCTATAAGAGCCTCAGCAAAATGATG +TAAATTGTCAGCGTGTAGTACGGAAACAGATCACGGTATAATCAAGTCTAAATATTTAGC +CCCGGTCTTGGAATGGCCTTTTATGCAACCAATTTGTGGCGATTAATTTCTCAACAGTAA +GACAGAGAAAGCTAGAGAAGCTGGTATTATTCTGCATGTTGTCGAACCAGCTGTGTACAG +TCAACATTTTGCTATTTACTAAGTTGAAGCTTTCGGTTTCATGTGAAATATCTGGCCAAA +TCGAATGCACCCTTTGACCGGCAGTTTTCATAAGCCACGTGTTTGCATTTCTCTTTAACG +CATTGAAAATCACCGCGAACGACCTCACAACTGTCTAGCTTACCGATACGTTAGTGGTCT +CCTCGCAGAATCGAACGAACCCGAATAATATGGTGATATTCTTTAACGACTGATTAGGGT +CTTATTCGAGATTTTCAGTCTTTAAGCGTGAGCAGCGTGTTAATCACCTAGCAACATTAT +AGAAAGGAGAAAGGTACGAGCAGTTTAAAAGTTACTTCTAATTTTAACTATTGTCCAACT +AAGTGTAGATTATTTAGGCTTGTGTCCAAGTGAGATCATACTGTTTTCGTGTGATAGGTA +TCCGCATCATAACTAGTTATATTAGCACCGTGTATGAAGAAACGGTGGACCGTAGCACAA +CTCATTGTTATTTTGTCCCCTCTTGGTTTATTGGATCCTAGATTATATACGAATAGAGCC +CCTTTCGCAACAGCATCAGAATCAGACCTGCGCTCTCGACTGATAATAGCAATTTGTTAA +GAGCGGATAGACGCAGAAGAATAACATGATTTGTGCACTTAGTCCAGTCCAGATAAGAAG +TTGAGGCATTGACTTAACTTTTCATTGTCCGCTTGCTATCCCCACGATCCTGCTAAACTA +AAAGCTTTTGGCGCGGAAGAGCCGTTATGGAGGTTCGGCGAAATTGTATCACTAGCTAGA +CCATTTTCTGTAGGCTTTTAGCTTGATCGACGTAAATTCGATTCTATATGGTAGAAAGGT +ACGACCGTTATACGCTCACGTACAGCCTAAATTCACTTGTGGAGGCGATATAAGCTAATA +AGCGGTTCATTTTGAGGAACCGTTACTTTGAGATTCACTTACAGCAACTAAGGTTGTGTT +ACCGTTTCTTCTCAATTTACTGCTGGAGCGGCTATTATGCGTCCATCACCTTCATAGCCC +TAGTCATCAAGCCCATAGAGGTATGTTCGTGTGTAAACGAATTCCAAGACTAATTGGTGG +AAATTTCAGTTTGGATTGAATGAGGCTGATACTTCTATACACTTAAGGGTTCCCCGTAAG +TATATTGCCATAAGGGAGTAGTAACACTAAGGTTGTGAAAATATTGCACGACGTAGGTAT +TCTCAATTTCCTTCTAATTCTGTAGGATTTATGTAAGGCGACCGGGACTCTATTGTTTTG +TCTCCGAGAGTTTCTTAATCAATTGTCAGGCTAGTAGATCAAGTGTAATAAATGATTAGA +GGTCCTCATTTGGAGAATTTATCTATATCCTTGGTCGTCCACGCGGTATCGGAGTTGCTA +TACAATAAGTTGGTTCCAGAAAGCGTCTTAATTACATACTCTTGGTTTATCAACGAGATG +GTACCTAATACTCTCCTCTCAGTTCAGTAATAAGGACCGTTAACCGCACAATTGCATGTC +ACCATGTAACACATCCTAGGTTCAGTGGTGCAAACAAATCAAAGTCGTTCGATGTCACTA +AAACATTTTGCTTAGTAAGCTCACTTGGTTATGCAATATTCTTCACTTCCACAAGTGACT +CTACTTAAGGCGACGCACCTCCCTACAATTCGCATACGCCAGGTACACACAGCATGGAAT +AGTGTAGTACCTACTCATGCGCGAACGGTCGCCTGCAGAATTCCAACATGGAGGTCTTCT +GGCCTAGTGCTTGTGCTTCCGGGATACACCGCACTCATATCACAGTTTTCCCTGGCACAG +GTTATAGTCCGCTAGCGTGTTGAAGCTAGTTCACCCTTACTATGATCCAAGAAAAGCTTT +TCGGCCGGCCATCCTTCACCATACGTTTCGGGGTCTTAGTTCATTATCAGAGTCGGTGCC +ATTGTTCCATGTAGGTACGTGGAGGAAGTAACTCTTGATATGCTATACGTGTAGCATACT +ATACTCCAGAATCCGTCGCAACAATCCCTTTATCTGCCCCTTTATTTACATTCCCCGCAT +GTTTTGATTACTTAAATGTCGGGTACTGCTGGTATACACCGTATGCACCGAAAGACAGCA +ACCCCTCAAAGCTTCGACGAGTTACCTGGTGTGAGACTATCAGCTTATAACCCTTACTAA +CAGCAGTAGACGAATTCTCCTAGTATAAAGTCAATTACAGTTGACTAAATTCGAAGTAGC +CGAGTGGGTCTCATTAGACCCTACATGTATCTCTTGTTTTCAAAACGGCTGTGAAAGTCG +GAATATTATGTGAGTATGATTCACTCGGCGGAACACTCAAACTCGCTGAATCATTGATTC +GCCGATGATTAAGCCGACCCTCCCAATTACCGCTGCAGCACTACAATCTCAATTTAGGTA +TACGGATCTAGGTCCGTTCGTTACCAGTTACCAATACGCAACCGAGCTCGAAGAGAACAC +AAATTTACGAAGCAAAATTCGGAATCAGGGTATCGTGCAGAATGGCAGGAGAGCTGGAAC +TGTTGTCAGATTTCCCTCTAGTAATCGTACGAGAATATATTCTATGTCACACATTAACCT +ATAGGTAAAGCCTCATTATACTCCGTTTAATGCAGACTTATAGGATGCCATGCAACAAGT +CTAATCGTCGCGAGGACACTCAAAAGGATCAGTGGAAAGTAACACTTTGTGGTTCAATTC +AGAAAATCAGCTTGTTTGTACCTACAAGTACAAAACTTGGAGTGGTAGAGAGGTCAATCG +ATTAAGTTAAAAGGTTAACGCATGCGCCTAGTCATTAATTGGTTGCTGCGCAAAATAATG +CATGCGTAGTAAATCCCAGCCCCAAGTCGAATAGATTATTAACGCCGGAAGCAGCCATCT +GCGGAATCTTCGTTGTGTCGAGCGTCAAACGTTGCTCCATGGCTCCCTCCCTTTATCGGG +TTCTCTCATTGAGTCCAACTAAACATCTACAAAAGAACTTTGTTATGTGATATAGCTTAG +GTCTAATCTTAGGCTGACATGCATAACGCTTTGTCGAGGTCTATTAACATAGCCGAATGC +ATGCAAGCTTTGATGGATATTAACTTCCCAATGTCTAAGATTAAAGAAGAGGACACCCAT +TATGTCAATCATCTAGCTAAATCGAGCTGCGAGCCGGAGAGTAAACAGTTTCCTTTTCTT +CGGCGGTTATTTGAAAATTCCTTTCTTATGGCAGTGTTTCGAGCGAGCAGTATATTAGAC +CCAACCTCGATAATCGTTAATCACATAGCGACTATGATAGTATCATTACCAGCAGCATAC +ATAAAATTGTAAAGTGTGTTACTGTTTGCGTGGGTGATTATAGTACAGTCTTTTGCAAAT +CTACGGCCCTGACAGAACTTCACATTAAAGGCCATCCACAGAACAATGGACAACGTATAA +AACCTAAAAGGATATCGTTTTCCTGGGGTTTTCAGTTGTTTTAATGACCGGTAAATTTTC +TTACCCTATTGTGTTTCCTTACACAGAAATATCTGAATATTGAGGTACCTGTGAACATTA +TCATTCATACAACATATCCTATCGCCCATCCTGTGCGGCGACTACTCCAGCACTCACTAA +TTGTTAATCATCTCATACAACTCGTCAGAATTAACATTACCGCAAACTGCTTACTAGCGC +AATCAGGTCAAGAGGAGGACGGCTTTGTCACTTAAAAGAATAAGGTGTAGCTGCATAAAA +CAATGTGTATCTTCTGAGCTTCACAGCCGTGGGCTATCTATGGTTCCGGTCCTGTTGATT +GCTCCCGATGTTGAACAATACTTTCCACTTTCCGTGACAGAAACTTTAGAGCAAGAGGTC +AAACTTTACCCAAGCCCATAGGTAGAAGTTACGCGCGCATTGACGTTTGATCAAGGGACA +GCTGTGAATATCCGTCCCACGTAATCGTGACTTCTCATCAATATTATATTACTGCCGCTA +ATCAACAACTTCCTTGTTTCGACTGAAACGATTTTAGTCAAGTCGAAGACCTCATACGAT +AAGATTTGCAACATGTCTAAAAGAGAACGGGAACTGGCAAAAGGCTTGGTAGATCCGTCT +ATAGCGTAAAACTGATTAACCCATTAGGTCTGAATAACTTTACACAACCCTCCGCACTGT +TAAATGACGGGCTTTGCTCTGTTTTGACACATCAGCTAGAAACTCGCCACGAAGGCATAA +GGCTCCCATATAGCGTAGCTGACAAACATATGAGGTGGCTGCATAAACTAAATTGAGGCT +CGCGTTCGGATACTTGCCCATGTAGCAAGTCTTGGCAACCAACTATATAATCATCACGAA +TTGAGTGCTAAAGACATGCGAACAGTTGGGGCTGCTATATAGTATGACAGATATAGAAAT +TTTATAAAATGTCGTAGGAATCTGGAGGCCAAAATCATTAGACACTCTTGTAAAAGGTAT +GGTAATGTGTATGACCTCTTGGCATAGTGTCCAATTATTCTCGGTTTACTCTCAGAGACA +CAGTCATGTAAAAGTGGTGAGGAATTACCGCCGTGTTTTGCCAACCAAGAAGCATTGAAC +AGTAGATCAATAATGATATTCGGTAGCGTATTTACGCTTTGCGGTTTTCAGAAGAAACTA +TCACAATTGAAACTCTATTCTTCGCCTCATTCCGTACCGTTAGGAATGACTCGAATCGTA +CTGTCTGCCGCGGGGCATAGTGTATTGCTCCCCACCAGGTTCAGATAGTTCGAATCAGTG +CGCTGTACAATTGCCTTACGTGTAGATTTGCATCACCGCTTCACGTAGGCACCCAGAGTG +CTCACTAAAGCCACTAGAGAGATAGAGTTAGAAATTAAGTATCGGTTACGCCCCTCAGAC +GACATAACTCACTTCTACCGAATATCCTTTCTATCTTGGATACTACTAATGCTTCCGTTC +ACGCCGCAATCATGTGGATCCTCCAGTAAGCAGGGTGCTGTCATGACTATACAGTACGGA +TCCGTAAGCATTTTGAGGATGATAACATAGGGTCGGTTACTGTGGATTTCCGTTACTTAG +GAGAGCAGCTTTAGCTGACTTTGCTGAGGCTGCGCGTGTTAGACAGCAATTTACGAACGG +CGCACTCTATAGCAGGCACTCACAGTGGACCAGTAGTCCTATTGCAAGAGTTCATTATGG +AACATTTTAGTCCTCTATCACACGGACCATTGCAGTAGATAACTCTAATCCTATGTCTTT +ATTTGGTTGCCTGGAACCCCTTACCACTAGACACCCCAATAAGTAATCTTGCTTCCATGT +CGAATTGATACTCATCGAAAACATATAAAACTAATTATGCTTGTGTTCCTGTGGTCTGTT +ATATAGAGGCGCCCTATTGGCCGCGGGATAAGGATCATTTTGGCACACTAACGGGATCCT +AAAACTTTATCTTTCAACGACTCCTACATGCCTTTTAGGTTAGTACGCGAATCGCCTAAC +AAGCCAATGGGTATTGGAGAATTAGACAAAATGGTTGAGGAATAAAGTGGCGCAGGATTT +TGTCCGAGAAAGGGATAGCAAACGGTCGCAGGCAGGAGTAACAATTTTCAACCGACCTTA +ATAGAGCTCAAAAGCTACCGGAGAAAGCTTCGTCTATGCTTAATACATATGCTAACCTAT +GAATTTCGTAAGCGTAATATAAACTTATCAGATATTTTAAAAGCATCCTATTCAGTCGTA +CTTTTGGCAGGAAAGGTCAGGCGAAACAGAGTCTCCCTGCGGAGGCTTTTAAAATAAATA +GCGGGCCTAGCATCGATTCTAAAAGACGACCCCAGGTGCGTAACCGTGCCTCCCCAAGTC +TTCTTTTAACAATTACCTAGAGAACGGCGTCAGTCGCGAATGACCTTACGAACGTTTACG +CGGAGCCGAGTAAGATTAATAACTGCTTATTGATTTGCAATCGTTTGATACGGGTGGCCC +GAAGCTCAATATCAACATAAATAAAATTAGTCGGAATGGTCGCTTAAATCGCGCGCTGTC +ACTGTCTTCATATGAGGGAGTTGTGTAAGACTGCATTGATATATAGGTATGATTTCGGTT +TAGAACTTTGTCTGTTAGCAACTCCGCATGATTGAAGGAAATCCTCGTTGGTAAGATCTC +TTTAGCATTTGCACAGCTGACTCTAACAGCATAGTATGTGATCGTATTATGTCTGCAGTT +TGTAACACAGTGGGCGGCATGGATGGTACTTAATGGACGTAATGAGCAGTAGACCACCGG +TGTTACCTAACCATCATTAGAGTAGGCGAGATTGCGCTTGTACGACTTATATATAAGGGT +AACCGGAATACCGTTCCTCTTATCAACAACAGTTACTGGTCTTAATTCACATCGGATATT +GCGATCGCCAAGACTATCCCGTAAGTCGTAAGCTAACCAACTAGCGGTTAGGTTTATTGA +GGTTTTGATGGGAACTTCTCAGACACGTCGTCAACTACCTAATTTCTTGGATGGAGCTAG +GCTAACTGTCCCAGAACTTTCTGACACTCGAGATCCTCTAACTAATTGGAATCCAGGAAT +TCCCTTATTGCATCGCCACAAACGACCATAAATTACAGCATGTTTCATTGTCTAACGTGC +CTATCCACGAAATTGAATTCGGTTCACATTATATATCCCCTTCTACCGCTAATTTAATGT +TTAACGTTGATGGGGCAAAGCACATTCGAGAAGTACCGAAAAGTCTCAATCCAAAGACCG +GAGGAACTGGCTTCGGTAAGAATCGCGAGTATCCTTGGATGCCCTGCCTGATTATAACTT +GTTCCATGTAGATAGGCGTAGCTAATTCATAGCAATACAATAAACGAGTCAGAACTGTAG +TCTAACATAACAGCCTGCTCTCCAGGTAACAGCCCATTATTAGATATAGTATCACGATCG +TCGGTTGTATTAGTGGTGATAACTATCGATTCTGCCACTAATAGAATGTGCAGAAATAAA +GTATCTGAAAGAAAACGAAGTCACAGAGAATAAAGCTCACTTCATAAAAGTCGGTTGCAG +TAGACGCATATCAATTTTCCCTGCTGCATTTTAGAGTTCGGAATAGTTAAACATAATACT +GGAAGCGCTTCCGGCAATCAGGAATAACCCCATATAAACCAACCTTTGTTGCTATTGCCA +GCGCTATTCTCGTCAAAATTTCTCCCTATGGTCTTCACATCATGCATCACCGGACCCTTT +GATAGACGATGACCCAATTACAATCACTCCACGGATGAGCATCCCATTTTATACGAGGCC +CACTGGAAACAATTGCAATCGACGTGACCAAGTAGAGGAGCGTGCTCGAAAGGTGATGAT +TGCCGAATTCTAACAAGGATACTATAAGCCACGGAACGCTGACGTTGAACAGACCTGGTC +TCCTGGGCACTTCGCAGCACCTCAGTAGTAATTCCGGTAGATTAGGACTTAGCATTCCGT +TGATCTTACAGGATTTATAAATAAGGAGATCTGTCTTGTTTAATTAGGAGGACGCTTTTC +CCGCGTAAGTACGGGAAAACGTTCTTCTGATTTTGTTTGCCACTTGACATTGTAGCTGCT +AGGAGAAGGGATAATATCCGCGTTTTCTTTTACCGTAACGTCGGAGCATACCATGGTAAT +TGTCCGTGTCAAAACTAGATATCTAGGTTGCAAAATTCAGTCAGTAAGTCCTGAGGCCTT +CCGCATTATTAATTCTACAGACATATGAATTTGCTCCACCGGCTAGCACAGTCAACTCAA +CCCACGATAGGGGAACGAAATCACAAATAGGTTCACATGGTCAATACAAGGCAAACCATT +CCCCATAACTCACGCACTGACGGTAAGGCCATTTCAGGTCAAGCGGTGAATGCTGTGAAA +AGCAGCTCGACCACCTGCCGTGGATGGCAAACCGATAACAAAGGACTCCGATACTTCATT +TGTAAACGTTTGCAGTGCTGACGTAACTCATATCTACAGTCAAACCGAATGGTTTGATCG +GCATTATGTAAAGGAATCGACACACGTTGCGTCTTCTAGATTATTACACACCTGTCTGCG +ACGGATATAGGTAAATAAGTCAGCCTCCACTCTGCAGAAGATACTAGAAACGTATCAGTA +ATAGCTATCAGGATTTCGCCATCCTCGCACTGTGCCCGGATATCACAGCAAGATTCTAGG +ATGGCACTTGTGTGACTAGAGGTTTTACTCGTTGAGCCATTCTTACTATAGGCATGGGAT +TACAATGTGCATGTTTGTGATGTTATCCCATATCTTGCATGTATCAGCCTACCAATTAGA +CATATGACTAGATGTAGTCGATCAACGCAAGGGTGCGGACTTTGATTCCTTTTGAATTGA +AGTCAACTCAGATGCTCCTTAAGACGTTTTACAGTAGGTATTTTGTGGTACAAACCAGAA +CCAGTGCCAGTCGGTAGTTATTGTAGTGTGTTCTTAATACATATTTGGTATTGGAGTTTC +TAACATTTAAAAGGAGCCTATTACACTTACTTAATTTGCGTCTATATTTCTGTTACGATA +TGTCGTCTGTCGATTTTACGAGTTTCATACGTGCGGGTTCCCTGTTCGCAATGGGCCCCT +TGCTAATGTCCCGCATCTTTAGGATGCAAACTTACTCACGCCTCCTTTACCGAGACTTGG +TGGGAGAGAAGACTCCTGTAGAATCCCGATCTGAATGGTTTCAGTGTAAGGGTCCCTTCT +AGCCATATCATTGAATATTCTTGTACTTTAAGTAACTCGATCCTACCAGTACAATTCTAG +GTTTGCCTTATAGCCGGAATGAGTATCAGCGTCATTCACCCCGGCCGGATATTATTTGCA +ATGTCAGGGACACCCAAAATAGACCGGTTAGAAGGCATATGCGATGAGAGTTGGTGCCTA +AATTAAACGATACAATTGATATGACAAGGACTATACGATGAAATCCATGAGATAATTATC +GTAACTCGGCCAACCTAAAACCGTGCAAGATAGGAGCGGTCCTAGAAGTACTATCGACAC +CTTAAATACTCACTTGAGTTTTCCGATCCTATAGTGCCAATCATATGGCGCAGGAATATT +ACAAACTAAGAAAGTCAACAAAAGATGTAAATTGCAACACCTGGCATCGGTGGGGTTGTC +CCCTTAAACCCTGAAACCAACTGTTATGCTCAACATTATATCGAGGCTAAAACGCGTATC +GTGGCACATTAATAACGATCACATAAGCTTTGCGGCTAGCAATAATAATTTAGGACAGCT +TAGATTTTGACCCGTGCTAATCCTCAGTATGGAGTAATTTTACGGATCTCTCGTTGTAAC +CGTCCTCAGTCGTGTACATTTTAACCTTTGTAAACTAGTTTACGAACGAGTATTTAGAAG +GTCCGTACTCTCACCCAACTGACACATTGTACTAGCTCAAGATCGCAAACACTAAGGGTG +TGAGTCGCGGGATAGCGCTTAAATATGACTGCTAATGGTCAAGAGCACGCGCATAATATT +CCACTGGTTCTAGGTCACCACTACGGTCAGACGTTGACCTGCATGCCCTACATCCGGCAC +GGGCTACTAACGGCCTAATATTCTTTGAGCCATATCCATACTCGTCTATGCATATTCAGG +TATACGGCTATAGTGCGTTATTAACTTCGTCGTGATTAAATCCTTTAATTGTTCCATTAT +AAGTATACATGCTTAGATGCGTGAACTTGAGGGATATCGTTGCTCTAAAGTTGTCTTATA +GACTAAATCTAAACAAGCCGTGCAAGACTACTTAAATTACAAATCTTACAGACATCTCGC +CACTGCGCTAACACTAACAA diff --git a/pipelines/nf-atacseq/tests/data/bwa_index/chr_test.fa.amb b/pipelines/nf-atacseq/tests/data/bwa_index/chr_test.fa.amb index 0719bfe..5d6da8b 100644 --- a/pipelines/nf-atacseq/tests/data/bwa_index/chr_test.fa.amb +++ b/pipelines/nf-atacseq/tests/data/bwa_index/chr_test.fa.amb @@ -1 +1 @@ -19800 1 0 +20000 1 0 diff --git a/pipelines/nf-atacseq/tests/data/bwa_index/chr_test.fa.ann b/pipelines/nf-atacseq/tests/data/bwa_index/chr_test.fa.ann index 01f4a1e..a633aab 100644 --- a/pipelines/nf-atacseq/tests/data/bwa_index/chr_test.fa.ann +++ b/pipelines/nf-atacseq/tests/data/bwa_index/chr_test.fa.ann @@ -1,3 +1,3 @@ -19800 1 11 +20000 1 11 0 chr_test (null) -0 19800 0 +0 20000 0 diff --git a/pipelines/nf-atacseq/tests/data/bwa_index/chr_test.fa.bwt b/pipelines/nf-atacseq/tests/data/bwa_index/chr_test.fa.bwt index 7b2e7ab932001844441e378374437a5bf4190022..9ed48528da8fdad93fa3fdd99b96d6bb5f1252fe 100644 GIT binary patch literal 20088 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 KS4pLemi`aHQ&&s? diff --git a/pipelines/nf-atacseq/tests/data/bwa_index/chr_test.fa.pac b/pipelines/nf-atacseq/tests/data/bwa_index/chr_test.fa.pac index dd3924555fc62b211f7bbbddfd620963a1daa594..d99d805eaae25d13f4ef77571814d7c250f2e4c1 100644 GIT binary patch literal 5002 zcmV;56Lsu>q{9m&Y;b1ZXMi&BXpOmp1?|Hp90|?TtdCO{qU^=9UN_>=3IwjwJL6Cl#+Z2nD2+@gycb~AK%Ny#RUt>vH7(H)?)-lC$T9|9!mbH---v9>qP>cTRKs zEQ)z^m-~(BU!*0nc^^Fn69HrE6VG-!EgPz4{x~;L+Yx1S`jiJje0)xxGt<KHfQ7`j1lnRTj z>4U@Z7X5bc9>eoKpo(5L=hkb#2LUBG#!rmDcU<*CaY#)(F~yK#zUc+eb}Ea>~vDuC@<;n;J*zVcPGtea1-7(0PRxG#HrA_?j5l5=}+)J z6Z`yh&ArQ5V%rN7n-h z;ar0Pc%c2U;4A(KIM2@r-_5qnQ95ncuFrt)Okv~#iv-5&$i;73^S769B_O1)bWzBA z^T%JneD|SjOL!KvkE_hYWdPItCS$<+Ocw>mYQT4f(|O>1=$I_{=76#KfM|dG|M95K|Fe`n}EyAsq@Si?ZV9K3DVKG-G=A` zJ_UwUNv}RkcPi}6ANVYBA`&$CqX?G{G@&x+SEnQxLOvIPkA0 z54s~?fTACxTIBJ4IOo@Ly zZv-+h8z?myNWL(9II|_MF}k3{a};3hW^ zk@ROI2pX*IT|*!EZVQa>Edv1Y6JEdJ@AL&YyPSdT1IXTRrT}w*oFOON*sJf~F??L` z|1UCFoeCf@C_i1)j8uz`-uQA9EC?>ZmMI3DvHlJh49f;9{^JodZa7U7en8Z-BFp4F ze}3yEer@p>^fQFoM%%ErQj5&O`wTNUJ}50O<7Wiq6-dJ~h@dEpWMfUwL-XO;QwBDG zUk!T{Urnd=NGQVZt-ynrSMx8cZ%6Ns-;8*e@{E=b_kMUw?gBRw@;gEq(<+Zo&&c{I zh9)VoMs!vUAb$kb%vr6n08Ij7~jsB;)2e?~%-Q7-a0oLu8DD^1f9ZL@n*l zu^aB6AlUJRpfEH1-#nhh&FMdIp%0AZ$IVMobc0ci`VSZI7oUJi+mAy)+};aYqEGYz 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 diff --git a/pipelines/nf-atacseq/tests/data/bwa_index/chr_test.fa.sa b/pipelines/nf-atacseq/tests/data/bwa_index/chr_test.fa.sa index 76e12a691624efbd776e74c6d0eb21cd6fdac93a..b19e11c12a249e55ae6c377d7b5aaed36d461f3c 100644 GIT binary patch literal 10056 zcmXw*dtA@=9>;&>ek;;q6p5&$q)3X=MRZpZp{0b7M3b&6(nU&&NNaLy=FD!HvCW2Y zILBqim~DnJ!(r}>9h}GGyq^5?^m>2ZpU?O6dEb6Dh8h0NrS(fw#P^9) zBk%fks_ebE_kV*pZ=Kjc9XA$9_n^M+QnzQR%T($twY&T_k~at5x1x>ghv=^~`tx$G z?8{gWC+=&=$3Wg`OdPf`2l5lNZ*G(Bc)vh!!+LQ#{JIno~QKfhIVqiG+Inz<2~xRu7~V{`2IHX z(Sh@td4Ct;f4EluS9o_Cb>MF!djRi0Q7rv&l-Qf^Jjs(D#`T|7a(x6hWoZYQ>2o-}w!myn+*^4EtsQ%f91&<}-~@-MFt^8&>!%)urX zX-oRB)kf+0&Z4=o_)p?mOa0jJ&Pw9bmiLZMkbi$i@d5Ywmfsam>iKZ8)(i2gpst>+ zmVMrMaRPJeNPu)S^%=r^iy$9n^l^H7`Q0Zk*FB}HnNLo{fAe(N54IK^xc@zVhk7N- zo<}@7uad5sAT!~Xl(T93*UL-21fm(HbbCQp)HJV5-Nd!M8pAF^J;ytDL?-#q-S_??&?&-c6tnFY}$?Fs(=9*RfE###r3VzQfdMM7iws zYs4AU$;xih`7YvOes4~ZpHG-GuZ*-F$-MmgE$MGsh--?(^PG3YMtT5s?3X4TOnvW& zl#ZSvW^!Jy1=5bM|1Rg9r^)*VS~gU3eir99F<;Z^&%)($jHkbcQ4dE3%5GFI<|m1H zHR4@gF^liNBwm5uvcJG?#Jzu^4jTQnKGuwLD$vX;bKZ56yg%c6_t>{FOa6Y;%V_HO z9Q8YLhSpvQO5 zH|Ef0^0ACO{z-lHSt|dbyyqACbN*`CZHU`d>isig*?;9-S?1Ced@q>$1aiNM7_E1p z&MuN)5ANF>r1h3=;!@bxU3wJf-Q?aIiE{??ttdr)nTy1I*5V27?Vm2~Q6erQzdf0Y zS;V1$-wS)bZ_7Lx5UYK2Ld363#J?)V6WlAUi}Z*@v1LE;U-+G&o-VTfg|*g4F+Zk} z_vCf5SG5sy=)bf0IWL#}S+tl-TvygeH;opfsI$TZ>E_{LY^j(;zDlM^=hullIX8KN zw9ibj!d867{JX%uBY4o&of5=m;kL+%> zq7`+xDNx#iI_hjKT~{nNaQ{EiC;es5X71I^mtMnrZ}yf>qhG>XNbjev-erzBQU_1S z_i^6SpLYzHqd1t8Po^H5obVeYJ|S+d)JXyNJr}R_PwBt+$**g^?Dv^tuGHt- zoLfxXwxr0`8_AD^O*N{h;R4B zT0g_RoE@bXWr{;riVgJf6TaVuxNfJ8e>9QbB;vY@KFjSY`!M2`M*ZB5kUfTTe_AGe zn>d6{=e%~}d(4vv-q+7u_Ljt_^-$^eiT6JCyK-(2eR3DQl6aYBYhO=SF=L+ioZtJ) z)YnnYwdS2oZ_6);dfy~z_{~B8i5f3%qTV6~OIK9l$oW4K-#F$~QnA)ER*Avz3ifn< zADZWDJ-dTg<|!UZ6o+$e3i*#^J%Bm+DepB6k^e=(;K%v7+^1-v797ayBjWRtbFR~8 zj>KVNwERD$56+OM9Dd)^EVb@I-b_15&!Jvj=%X%HvPUq_&QiC(k;fI(oh^AT4wQdm zotR19BCDm};qQf6WzrLS;!huZZzH{ie4XYyTeD@~dc6^Iq>c`ducs4OAPEQelkrA>MEYY zFNV0arjDPMYW*&AA&mTb5x3pmTAxavoFb0TZDse55Uc2K7yOIuWG`jDJfT0s$!|3I z{hYek9U%YxoHv~FZ<5b;^zRN|`5k92{5@CNn0S6pJ;u`yyQsf`)ZGQ**O|HDjy_3V z(umho`oV0H&QGxr`v-|_{X{S3YT{byHYTD2?@TX|ZdxpEDHFRFiY2MyQSR}W`fH}n za*6w;LGp{V5mycte{3UqIg1XlVl?%0C{Q{nUR+Bbb>g07#KD+6d6~*@%s_D@eiz)N zM^}ly_G0sL@z*+WD*4#Q@9|dRvv;D_&r`>J>!sUKZ+oeOM&5IXc(lrw-(mcGmr3s< zUqk5UV(P)0_4c#n7f>!PNfNh*i+|H+UFfeV)(+$)ZF2Doc|CUw>i_Mk7!>E{nU<#&?!*U;~My=1qc-gc!)8qG~v9cMCl)^#SG?hp0l)pxXg`{E~HMM@V=46 zzccZ^Z7aX)KH?_wWX1cgX2_nxz07+{hn0%$skcz>^E>?;UZwTRf+2>!d1xm?uL{wE z`Bd5mT`xLvzjOGvSt5HHaqGstulmXE&-?dMPag0{yw+X#J0z2O`KY7pYsqVAs`N49 z_Ke@zTua&io**7X_h7zM(O-4s*ECChb}Pj!=J`tp>9f@5HS$`)`%<}Aueao9%=*E{{j5|93x*_TWLL=circJqBmrZaTP0=iv`qG z9ew3puJ!Zeap!dDrG3QZAd-k86_FJzI$9c>iGX-EF4qyVr?xGsOeE zuhdC;e>*WDS-i`=3;Ij%WL~c)Z`(a(|Co9H_)Y0(^7}ME`W^gE(2pNQ%YJ&Jcqd=% z6e4>1irJj|W{UJN^7Y6^dT*)t{c!OE@>oH=H1VCKL0aEN9LMsVZ^y}Q93d{l?na#7 zrEgDjpLfWM6LnbJPx}r!iYf49cj-F%YX|ZEbh+%;%EcMfnK$$5B7O6Xnbv>qDz5Sn z=k*e^sju_Q!%MI8O&-6bzqhTD|7G$}PyAh(Lmd*e{(wH}Z7NMU8qPD9KCzJfl8NYC zEuNVyn$aJB&6O@>F4);fd+^>k{4?mw8SGm~{QroPe-!VoUM4+_{&L_xMd*fwT7OPm z{Db(pGv|*|KesqH+D-nu$?t4G=>yEa#zoS}?5`XkozMK(!hB4jZ){g+J(Bm{u9LpH zK|Du1(y32}R_5PKxy0N#Ks}Di z(Y~wXVLkb`;@l~8|9} tg8m8bEPGn9_?Y+q#CzkaBE+DwD8aFVmfJ+n0Ndhz` zE&(--=y;q6(U`b^aT)cfGwzdZG?}271)~^sj&n}#PyV_6{p#NDy{dYzs@vnID@7{*}`6<TtgHMHr!!zK6;ok5` z@R#Aw3z$Fr;W*|GcZFYpABDHhVgB%K@XPQI;JNSv@MZAX@G|&RcsM)*{!^O9shKE! z)Lpuk>)({E+Vd4@OR4k@JPhu*S=?^9bUHi;?hDU>$HHUaRq%9p3j8p<1YQ8Y39p0K zz@Na+!JFWoJ(xfI4m=F*SjPNUFn@Ru+!vk$kA=s;tKjMI6!>9y3A_M)6J7^z?VxdY zaeYn2sx#nzaO*np*7o=}Nlow!xF6hFgx_A;NZ;u7)M#{x>R%kBPr6F8-Z6lY|Mr@6C-?U{=NIWC-YHRfWxmulTAH|6+Dxto z{HGN0E^kS*21(zfPbE3i2JuT4zU z4Y^OqohKJOSiV=LNfXI6lj{*9{u8+_3sh&3dy`xxInzcRpGD4}>la6^p4>;|u9Hh8 zx0T!iavR7!BKI{pk0lz%O70tSyLeBx=IeM(JL&5l(tR_ee)ux*`QrP^UB~<3+htMR znkTK1*u0K!-*f>#d>QzB@qOi{4`>0+uM4H2ST3l5h3X^`B zB;7kpdY-;L!&TSOH^Qje*?R&MR5~JA+IO}zZmo1XeS^%ZkI}c3zN==+4e#+SrHhe=1wk@iiJ#uZ7oM@WMT zq(*0H`TNq1Jhv@8x82}Qa3j3@J^VbkEl#T4;7)KOd=ukzb&+oq+LimagLQYGb+?Um z7s>s%vhI4}TZGRW-*MKR3EvKj#<`y--6pY##Alr@&_0zz- z*0L@$SwBlyKc$@4{xCH>m?W)XT{f{U-B~}?^bIk}*Mar(fWE)c*O~RxpY=01S$<#E zPYmm)g1$bip9cD_rEg}aa!Xi0rS#oT--Gn6p>Gp?-6NE%rf-O+>JF^S2lV}ozRs-6 z{;bQvtV`b{+)Q#=_=uAbaI za;wOlCigSBXmbB1_lR6z7xgh!;f3(;;U(~$@OGm$ULE`-ychf& z{0Dd>yajI0^CubZxRUw9^Wj18k?X2ScxYuAZCXpesret0Il54^SrKl}6ueTS_VKY|V`QSHEew=!N3{$oDcT`!i9;2RTYl zl5??B9rCWU1b=TY)#35drR3}bR8J(AP3|1IdE|1*eM)X5xy|Gn$T|9|-%fHz$^A~Q zklZD5ZRBFf{Xp(Hxj|jj?<6@F)@2Cqu@Z8xk?X>Z!=T6yh_@XFU@Kv^}v@mL-k{eGzQ-zl{S^` ze89IHUsIlV7Kc6XGOwyWs@DvW7V!O{+EsO9 znDlg#v|*O?8hr!8RoByZtWmY6zqEa*)GkBn(@h#ZR606Y8Zuj&uvS`2-(a)qv-GW^ z?;83R(6^etjr2WD-v;_#qi;Zj`qk5Stfy*E_E~$**N%Pb^OBB750j3bBMnKCCh%OX zjZodsR{Ar#&!BoTbvNpNq6HImysfA7@!QfA_%---cnrJ&{u$gCUIDL!+rhKp%i(t> z*!=mI08fNB!9(Cf;5Be}xCgud-sb&hJDbOgq$%)g@aymxcmw=1xG%f{UJ18@XTz7n z@4TUL65xsOCU^*Z2)qXF4)=f;z}tGL-+$7iTe)t(@;u0o6aO+>dKT^Zit5%<>0NjP z+;y{f=jGBQ_yBkSJP$qzJ{GfA|1+06Y&q2|gCS9i9PCgCB#J!HePF!4JUqzprsWaghGKtF%AdA3hMi|2_Pj z@jKy%`@;vqhdb-|7wB*oYIL}(>OmFK-_cJiRj*}Vk77TsOA(J{y#(`|Ooa!*-QZ*3 zj_^l?%A4RVv8u1aJHgMv>*39F<*S32!_UA=;8}1hJRKenUk8tc2g6h0L2x(t7`P++ zQ33OZw~S-{@J{e^@OpUj9Oe%%ho6C$z_Z|1cse{Dz78G>4~B1IUp`=e|81hiYxiOw zas7?d_t?+3$%Vol*e3?|$uxK%+y}k_J`NrYx4;*|=fV%c*Tc>58}L2wYWN@Uf502z zM)uD=_-%M7+<|>%V82a+2f}^eE8yec(QpfVA$%_U5PUt{48H;21K;AI>wexrdYyec zmHiwCPlR7#-=1gRUT5Dya zmEudsXNS)f-&lMT@CD&(TB>m_XGuHbbH>*)MZ5rC8NRcT%#Yt0b@*cNO~O}fMl@#MZG7icd&k=!nF;e79yMs6cH z??Cw%lG{w~M{>F3EaWbbn@w&Hxl82yxPM2<-5}>RQ{()Le|IS2dFW6qJ_X(pzJ4A4 z_R^wFQU~}Hct`kpzBgOwXIwA81vQpXqXy=)vP%3^zH~)9sTa=`^9q`ktb1J$chr_test +GAAAGGCATAATAAGTAGCACGTACTAACGCGTCTTCGCTGAAAATAGTTAACGGAGATC +GTGCGAATAACCTGTCTAATAGCTACTAAAGCTATCTCCAGGTAGATTCCATACCTGGAG +TGTATACCCTACCATAGGATTACTATGATCGTTAATGAAAGACCAAGAACTTGCAATTTG +GCATTCAATTAACTCTACCCCATATATCAGTTCCTGATCTTGAGTCACAAGGAACAGGTG +TCAGATGTTGATCCAAACCCTACGGCGACTGCAAATAGGAGATCCATAAGGAGTTAACCT +CGAATCCCCAAAGCTGACCCCAGTCCCCAGACCACTTCAAATCCAGTCTCACACAATGTG +TTTAGACTGGGTAGTTCGTTTTATCGCGTTAATTGTTATCCAATGTCGGAAAATCATGAG +TAGAGGATACTAACTCGCGCCGGTCTCGTAAGGTGAAAATTAAGGATTTATCGGCGTATG +CCTGTGAATATGTATAGATTAGATATATGTGCAAATCTGGGGCAAAAGTAGGAGGACCAA +TGCTGAGGAGCGACGTTTTCCACGCGTGCACTTTGACCACATGTACAACTCGAACAGTGG +GTCAAGTGTTTGTGAAAAGGAATGCTAAAATTACTGACTCTTTAACTCTAGAATTCAGGC +ATTTCCTGGGCAAGAAAATGTAGGTGCGGGCTTGCCAATGTAAGGCTTAATTAACCTCCG +AAGTGCAGGTATTGCTGACCTTTTCTTCGTTATGGGATCTGACGAATTACCTACTGTACC +ATTCTCCACTCTCATGCTATTTTAAGTAGAGGCTGCCTATGCCTTTGTGATCTGGCCCTT +GGCAAGCCGTAGCTGCACTTATTCAACGACATAAACCGATTGGTACATTATTCTCGATGG +AGTCACGTGGGGCGCGTTTGATGAATCTCCACTCGTACACCGCCCTCATTGGGCCAAACT +CAACCTTACTTACATGGCTGATATTCATTCCAGTCTTAACTGGGAGAATAGAACTACACA +AAAGAAGATAAGTGTGTATCAGCTTCATTGTCAAGAAGTTCTTGAGCGGGATATTTATGT +ACACAAGCTGTTATGGCGCGTTAGAACTGTCCCCGGACCAAGTTACTTAGAGATTTGGTA +AAGGAGTTAGATAGTGATGATAAATAGATGTCCACAACCTTGTAATCGCCACAGTTTTAT +ATCTGCCAAAGGGAGTGGTCTGGCGAATTAATTTACACCGTTTCCTCGTTAAACTGTAAT +TTATATTGGGAAGAGGCCTGATCGTGCTTCCGCGGTGTTTAACTAAACAGCCATGATACG +CATTGATAGTTTCTCTCCTATTCCCAAGCTACCAGACATCATTAATACTACCGCAACGAG +TAAACTGTAATATCTACGATAATGATGACATTCTTTGCAAGTGGGGTATCAGTGGCAGTT +AAACTCTAGATGCTATCGCTCTTCTCGAGCTTAGTGTGTACTCACCAGTCGCAGGAAGTT +TGGCTGTTTGAAGTTTAATCACTACTCTAGCTTATCCGCGCTAAACATTCTGATCGTGCA +CGTGTCGGACTCAAAATGTCCCAGTATTTACAGGGCTCAAGTGGTGTGACTCGTAATTAG +TGGCCATTTTAAATTGACATTTGTTTTACTCATATCGTTCTCGGTTTATATGACGACTCT +CGATTAATTTGTTGACGTTCTGTCTGCGCGGATCGGTGGAGGCAGACAATAGTGCCGAAA +TGTTACTTGGGGAATACTAAGTTCCAAGTCCCCTAGTTATATCGAGGAGTGATGAGATCT +CCTACTGCATTGCCACACCTTCCCCATACACTTCCTAAATAAGCTGACCCTAGAATAAAG +CTGAGGAATTTCGTACTGAAAGGTTTTGAAGCATGATATTTATTAAGATCTTTATCGTCG +TATACCACATGGCGTCTCCTGGTGTATTGAAATGTTCATACGACTGCAAAAGGAGTAACA +TACGTGGTTAGATACCCGTTCCGGTTATGTCTGCCTCTAAAGCCAGAAGGCAGGTTCTCA +CCACTAGACTGTTTATTACTCCTTTAAACTTATTCTGGACCGTACAGTCTGAACCGGTCA +GATTGGGTTATATACACGCCAAAATCATTTTCAGCGCGATTAAATTGTCATAACCTAACC +TACTCGGGTAAACTCTGACGTCATCTGCTGAACTTCTGGAGCGAAGGGTAATTAAATTTA +TAGTTTTACCCTATATTATTTAAAGGAATCTGCTTCCCATCATCCTGTTATCTATGTGTC +TGTTGCCTTGAGGGACTTTCGTCTCTGAGGTGACGTGCTAATTGTTTGGTTAATCACATT +ATTTGTTCACGGACAAATCATAGTAGAGTGAGCAACATTACTGGGGTCGCGTGAAATAGT +TATAGGGCTTATTATAACCTTGTCTAAGTATATGGTAAGCTCAGTCACGTCTTCTCGACG +TGGAAAATATTGAACCGACGCCCACAGCGGTTATTGCATACTCTAGGGTGTATATAACTT +TTGAAGTACTACAGAGACAGATCATTGAGGATAAGAGCCTAATGATCAGGACATAGTGGA +TGCAAGGTCTAAATGGGGCGTTTGTACCTATGTCCCACTTGGCGAAAACTGTTGATGATT +ACTTGCGAGGCAATTGTGGAGGACTGGAAGACGACAAGTATTTTAATGATACATTACCTC +GTTTGAATTCACCCATACTTAATTGTGTGACGAATATCCCAGCGATATACGACCTGTCAA +ACATTCAATCGGTAAAGGAATTTCATAAAGCGACTAATTGACATTGATCAACCACTGGGA +CAACTACCTATATCTAGAAAACAGATTTAAAACTGCCCGTTTCTTATACGACTGCCAGAC +CACACCTCCAGCGCAGCTTACCTTTAAATACAAGCCTAGCGCCCTCTATAACCCGACGCG +AGATGAGCCTCCAGCCATCAGACACAGGCTAAAATTGCCTTTATCGGAACTTCAATGTCA +GGTACACAAAAGGGAAAATCATTTGGAAATACTTTGATACTTATAAAGGATTCGTCCTTC +TCTACGTCCGGAGACCCATCTCGCACCATTTATCGGTTTAGGCCTAATTTTGAAAGGACT +AGCCACTATGACACTCATGAACGGCCTATTACCAACCATCGACTGAATGACGTACGGATA +TCCGGATAGGACGGAACTCGTTTATGCTATGCTGGTAACGCAGCTAGCCCGGGGCATTAG +TAGATGCGTCCCAAAACGAGTATGTGTATCTCGCACTCTTACAATTCTTGGTGAGAAGAG +TGAGGTCTAATATCAGGAGTATGACTTGGTCCTCTACCTAGAGGATGACATACGGAGTTT +TAGGTGGAGACAGAAAATTAGTATACTAGCCGAATGAAACTTAAATCTGAGACGATTGCA +CATCATCCGCAGACATGCGATTAGCCACATAATGGGTTCGTTGAGATGTCTCAGACCCAT +ACAAGTATCTCTATGATTAAGGTTAGCTAATTGTGGAGATCCTTGAAAGGAGACTTGGAT +CCGGTGCATTACCTTCATGATGCTTCCGACCTATGGTGCGCGAGTTGCGCTGTATTTGTG +CACCTAAGAGAAACGTGACACGCGTAGCAGCTCCTTAAGGCCCGGGTGGCTAGAATTTTA +GATGAATACGGTTTGTAAATTTAAATTAGTCCCAGTCGGCGTCCTTACCTCTACATCACT +AAGGCTATGCGGCGATTAACTTAATGTAGTGGGGACAGTAGTTGTTATCTCAGCCGTCTT +AAGTCTGCTTGTAACAACCCCTTTAAGTTAGAGCTTGTGTTTTAAAGTCAGCTTTTAGCC +ATACAAATAGTGCTTCTGTAGGTTTTGCCGATTACGCGTTATATAACTTTACTGTCCATA +GTGCTTCTTCTTGTAAAGAATGAACGTTAACAATAGATAAACGTAGGAATCCACGCCAGA +GTTGATAACTTAATGAGTATAGCCGGTTATACGTGGGGAATACACTAGGTAAGGTTAGAC +TTAGGTGTTTATTGGCGGTGAATTTGGACAAACTAAAATCGTGGCCGTAGCAAGTAAAAT +CGTTGTGAAACCTCAGACTATAATCCCCTGCTGGCTTGAAAGCGATCTACAAGCACTTCA +CGCTAGCAAAGAACGGGGTATGTCCCTCCAATACTTTTGACGTGAAGTGATATGTTAGTC +AAATAAAATTACACATCCTGGTTTTGACTGTTTTCAAACCATGAGTGTGCTAGAACTGTC +AAATTAGATCTGCTAAGGCGAAAACTATGAAAGCTAAGACAGCTTCTATCGAGGGTTGTT +TCTTATACCTTACCTATTAATTTTAGTTATAGCCGAGCTCAAGGAGAAATAAAGGAATTT +CCTCTCCAGATACCCAGAGTGATGTCTGTTGACTAGACCAAGTAAAGAAGTGTAAAGCCG +AGGCAACGGCTAGTACTTTGAATGACCTAATATAGTAACGAGGTTTTGTGATACACATAT +CGTGATGACATCACATCTTGCAAATCCAGTATAGAGTAGTTGCAATTACTTTCTTGTGGT +AGCACTTGCGTCTTACACGATTCAATATGACATCGGCACGTCGTGTAAGTCTCCAGGAGT +TATATAAGTTGTAATAATATATGAATTGAGGAAGTCAGTTTGATCGCTAACATGCAACCC +CAGATAATATATGAGAGGAAAGGAGATACGCACGATCATCTATTCAATTTATTGACTCGC +CCATAACGATCGGAAACCTTAATCCTGTACCACCTTCATCGGCTTTCCCAGAAGGATAAG +TGTTGGTCTAAAGAATGCGACCCTTTATAGTTGGGTCGTTCACTTGTTGATTTCTTGATA +CTGAGCGATTAGGATAGCCGAATTTTCTCTTGCTGACAGTTGTGAAAGATCTACAGTTAG +ATGTCAAGACGCTCATAGGGGATTCATTTATTTAGATTGGAGGCTGCCAGTTCTATTGTA +GGCAAGACCCTTTGAAACTTTAGTGGAATTGCCGTGCTTGTGCTGTTAGCCTCAACGCTT +GCGGTATTATCATAGGCTATTACGTGACCCGAGTGTACGGATATGTTTCTAATTAAAAGT +ATTAGAAAGTTATGAATAGGCGGTCGGTCGTACCTTGGTAACGCTGGGCTATTTAGGAAC +CTGCTTTGTCTTCGGTGTAGACTTGTTCACAACGTTGACCCGAAATTTAGTTCTCTCTAA +CTATTTAGCTCCAGTTTTGTATCCACGAAAGTTCAGTTGGTATTTTAGTCATTTTCTGAT +GAGCCGTACATGCAGCTATGTTTGTCCAACGGTATAACCGAATCAAACAAAGATCAGTCC +TAACATCGATGAGTGGAATTGGTTGTACACTGCGACGCTCCTAAGTGGGGATGATGCAAA +TAAAACGCCGGACAGCTCCGATCGCATCGTAAGTTACATTCGATAGAGCGAATATCAGCG +AGCTTCTTCGGTACCTTCTGTGCATCATGGAATAGCGTAGGAAGGTATTTCTCAAGAACG +TGCATCAAGTCAGAAATCTAGCATCACTCCGTCTACCGGTAATGTTCAACGGATAAAGCT +CGGAGTTCGAATCGGTAAATATGTAGGAACGCTAGAGATTCGAGCAGTACGGTAGTGTAG +CTATTCACTTAGGCAAGAACTATCGGGGACCACTCGCAGGATTCGATACATGATTCCTAT +AGCATGATTGCGATGCTGTTGCACTATACTCGACGACGCATGTATAGACAATCGCAGATA +GAATTTAGGTTGCCCCACTACACAAGTCTGTCTATTGTACACGTTGTGGCTTAGAATCGA +TTACGACCGGAAATAAATATTTTATCTTATTAGCTGTACCTATCTGGCATTTCTAAGGAC +AATTGATATGCCTACTTATCCAGTCCACCTCAGAATCCACGATCTTGGAATTACCTTTAA +ACCTGCTTGAAACAGGTCGTGATTCAATCAAATCTATCTGAAGTCCGTGGAGCATTTTCA +AAACGCTTTGATACCTTTCCGGTGACACAAAAGGAGGAACTAAAAGGGCACATACCCTAT +GATATAAAACTCAATGTGTCATTAAACAAAGGTATAAGTCTTTCAACTGACTATGAATGA +CCACTGCACGAGGAGGTTGTTAGAATGAAAAGCTGAGAAGGCAGTATCTCATCTTTTATC +TGTAGTAGGGTTCTTTCGTCTAACTGACTATTTGAGGCATTATTCTCAGGCTTTCAGTTG +TGTTTCGCTAACTAGACATACTACGTCTTATGTGAAGCTACGTCTGGTTGTTAAGTTTCA +ATCGAGTAAACTTTGAAAACGACCTACAGCCTTGACGAAGCTCCCACAACTGTGATAACT +AGTTCTTGCCCTGCACGCGCGGATTCTCACCTCTCAACAACCGCGTACCCTTCGCCCGTT +GCGTAAGGCATGTAATCCGCGCTTGAGCCATACCCACCGGCCAGATTAATCAGTCTGAGA +CGATACGCAGTTATAGCTGTAATGGGGAAATACCCCGGAAGTTTCTGATCCATTAAAACC +GCACGGATCTCGACGCAAAACTCCATGTTCCAACAATACGGCTTTAGGCAGGTGCCAACG +TCGACGCTGGCTAAGTAACTTACCACAGAGGATTCTGAGCTTCTTTGCGTTATTAGATGT +TTCTAACCTTAAAATAGTAAATAGAATACTGTGGACCAAGGCATAAATGCCGTGCTGGTT +AAAACCAGGTGCATTTAAAGCTCGATCAAGGCCGGTTTTGGGCTGTTTACTTTCTGAAAT +AACTGCGATGCCGGCCCGAGGAAGATCTAAACTACCAATGAAATTACAAGTGGCTTCAAG +GCCAAGCCATTTGAGTACTTGACTTATGTGAGTACTTTCCTAAACCATCAAGGGCAGGGT +TTGTTGCAATCGTATGGGCGTATATGGACAATTGAACGAGGCAATGTAGATGTCCCTCGT +GTAGGGGTATGCTAGCAACTTTTGTTATTTCTCCAAGAGCAATGCTCGTATAATCTTCAG +ACCACTATCTTTCGTGGGTTTTCTCGTATTCCGGCGTCGTATAGTATATCACAAGAGCTC +GTACATTCTAAAATATTAGTAATTTTCAAGGTGTAATTTTACACGATGTTAGACTCGTTC +TATCACACTGCTTGGTAGTTTAATATGCTGTAGTACTTGAGGATCGTCGGTGGAACGGTC +CTAGGATCTAAACTAGTGATTACGAACTCTTTGTGTAAAATATGAGCGTATTCGCACTCA +GTTGCAATTAAATAGCTAAATGATCGGTAAATATCCGGGGTAAATCAACTTGAGTTTAGA +GGATCCGTCGTTAAGAGATGATGTACATTCGTCGATTTAGGATCCTAACGTGGCGTTCGT +ATGAAAAGAGCTGAACTAAATAGGAAAACGTTAACCAGTGACTACGCCCCAACCATTGCA +AGATGTACCCCAATGATGGTTTTGGTATCGAAACTTCTCTTAATTGTGTTTCTTAAGTAC +TGGCAAAATTCGAGCCGGCATCGTTTGTTGATAGTTGGGTCTAGGATTTTACACCTTGTG +TTAGCACTGGGCCATTAATTCAATAGTAACAAGAATACTAATTACCAATGTGCGTGAAAA +TCTCCTTGACTGGTGCAACGTCATTCACAGTCGGATCTCAAGTTATTAGGTGCTAACTGT +ATACACCAAATTTAGGATAAGAGCCGGCTTAAGGCTAATCTAGACCCAATATTAATCAAT +ATTTTACGTAATGCATCCACGCGGCGTGCTCTTGGTGAGCAGCTGGGATTAAACGCGTAG +GTCGAACTATCGAGGGTTTACAAGAAAGCCAAGTGAAAATGAGACTATTGGCCATCGCGA +GATTTGAATAAATGTCCCTTGGTACTTATACGTTGGGCGAACGGGGATGAGCCAGGCTGC +TATCATCGTTTCGAGGTAGCTTCCAAGTGGATGAACTCAAAGACTGGCATTATGTGAAGA +GCATAGCGCTTTTCCCCGTATTATGGCAGCAGCTGGTTACCCATACTTGTGATCCCCGTA +ATTCTACTGTCATAGAAGGATGACCGAATCAATGAGCCGGGTGGTGTCCAAAAGCGATCC +TAATCCTTGCTGATTTACCTTGAGCGGTCACGTCTGTCTCAGCGACATTCGCCTTGCGTT +AGACTAGGCCGTAAGTAAGGAGTGCACTCCACAACGGCGTAATGCGTGCGGCGAGTAATG +TATTAGCATGTTAACCACATTCTTGGCAGCCAGATCAAAATCACTTTTCATCTGGTTGTC +TTAACAATCCGATAGAATCTAATGTAGCGATGCGTACTAGAAATAGTTACAATCTACAGT +CTTGCTGCACTTGCTGCTAATAATGAGCGAGGACCTATCCCTCCTTAAGCAAGTTCCTTG +TTCCGTGCGGGGAGCCCTGGCGCTAACTCTTTACATGATTAGTATCGCATGTTGTTACAT +ATATAATAGATTTACATCATTTCAAATGCAATGATTCGTGCTCCTAAAATGAGTCGTATG +AATAGCCACAGCGTACGGAAACCTGAATTGATTTGTAATTTAAAGATCAACTTAATCTGT +GTTGATCAGAGCGAGCATTGCAGAATACCCCTGCATCTAGGAATCGGTGCCAGTGTAAAA +GCCTGTTAGTAAAACCACGACTATGTAGTGTGTACCACACTCGGAGTGCGTCAAGCGAAG +TCAAACATGGAAATGAAACCATGCGTACGGAAAAGACCAGTGATTTATAAGGACATTCAC +ATAGACTCCAAAACTGACCCGATGGAGTCTACGCCGAACAGTTGGTATCAACATTTGTCT +CGATTTTCTGTTGGGAACATCCATCCCTACCCACAACGTACTGGACCATAATCAAGGGTT +TGGAACAGTACGCTCCTGTACTCAAGAAGTCCTTGCACGAAAGCAATAGGTTGAACTTCA +TCATATAGGCGATGACAGTGCTATCAGCCGGACTGGCTGTTCTCGTAGAAGTCACTCGAA +TCAATAAGATACGAATACTCCATCCTGTACGGGGACACTATATTATGCTAGCCGATTCTG +TAAATGTAGTCTTTACCGAGAATTGCTGACACTGATTTGAGTGTAGGAGGTCCGGTATAC +ACTTATCATCAACTTATTCCTACACTCGGTTTTCAATAGTTCGTAGCCCCAGGTTGCATG +AATATTATACCTCGGATAACACCTACTAATCCGTCCACAGCCTAGCACTTACTGGCGATC +AATGGAGCATGATGTACTTAGGGGACGGTATGAACATTCTTAACAGTTCCAAATGACCTG +TAGCAAATACAATAGCATCTTTGTTTAAGCATGGTCCTCTGCGGTTTGAAATGTCGCTAA +TCTAGTGATATTCCTTGTAAGCCACTGTTACTCTAATTTAGCCCACTCCAGAACGAGTTT +GTGTCCATGAAAATGTAACTCCCCAGACATGCAAATACGCCTTATTGCTGAATATCGGAA +CAAACAAAGTCGTTATCATCCTGAAATCGACGACAAGTACATATTAAAGGTTTGTTTGGC +AAAATAGGTAGCAAGTAGGATGTTCATAACAATTAAAGCGCGTAACTCCTAAATGTGCAT +TATGCGCCGAGGACCGATAGCTGACGCCGCTCTAGCTTCTATTGTTCCACTGTACGGTAC +AAAGATTGAATACGGAAACAGAATTCGTCAATTTGTTGAATTATGTTCTATTCGTTTTAT +CTGGTATATTTGTTACCTAACGTATTTAGGGAAAGTAGCTTCATGAAGAAATCTAATCCC +TCGCGTGACGAGTTTGCTGTGATTATTATGCGACCTGACTCTTGTAGTGTGGAGTTCGTT +GTCGTATCTGTACAAACTGCCGACACGTAGACAGGCCTGTCTAATAAACCAGGGACCTTT +AAGCGTCTTTGTAATTAAGTAAGTACCAGACCATCCTTAGATCAATATGATGCGCAACCG +GACCGGATCAAATGTTCCAAGCTCGGTAGGTTATCCTATAAGAGCCTCAGCAAAATGATG +TAAATTGTCAGCGTGTAGTACGGAAACAGATCACGGTATAATCAAGTCTAAATATTTAGC +CCCGGTCTTGGAATGGCCTTTTATGCAACCAATTTGTGGCGATTAATTTCTCAACAGTAA +GACAGAGAAAGCTAGAGAAGCTGGTATTATTCTGCATGTTGTCGAACCAGCTGTGTACAG +TCAACATTTTGCTATTTACTAAGTTGAAGCTTTCGGTTTCATGTGAAATATCTGGCCAAA +TCGAATGCACCCTTTGACCGGCAGTTTTCATAAGCCACGTGTTTGCATTTCTCTTTAACG +CATTGAAAATCACCGCGAACGACCTCACAACTGTCTAGCTTACCGATACGTTAGTGGTCT +CCTCGCAGAATCGAACGAACCCGAATAATATGGTGATATTCTTTAACGACTGATTAGGGT +CTTATTCGAGATTTTCAGTCTTTAAGCGTGAGCAGCGTGTTAATCACCTAGCAACATTAT +AGAAAGGAGAAAGGTACGAGCAGTTTAAAAGTTACTTCTAATTTTAACTATTGTCCAACT +AAGTGTAGATTATTTAGGCTTGTGTCCAAGTGAGATCATACTGTTTTCGTGTGATAGGTA +TCCGCATCATAACTAGTTATATTAGCACCGTGTATGAAGAAACGGTGGACCGTAGCACAA +CTCATTGTTATTTTGTCCCCTCTTGGTTTATTGGATCCTAGATTATATACGAATAGAGCC +CCTTTCGCAACAGCATCAGAATCAGACCTGCGCTCTCGACTGATAATAGCAATTTGTTAA +GAGCGGATAGACGCAGAAGAATAACATGATTTGTGCACTTAGTCCAGTCCAGATAAGAAG +TTGAGGCATTGACTTAACTTTTCATTGTCCGCTTGCTATCCCCACGATCCTGCTAAACTA +AAAGCTTTTGGCGCGGAAGAGCCGTTATGGAGGTTCGGCGAAATTGTATCACTAGCTAGA +CCATTTTCTGTAGGCTTTTAGCTTGATCGACGTAAATTCGATTCTATATGGTAGAAAGGT +ACGACCGTTATACGCTCACGTACAGCCTAAATTCACTTGTGGAGGCGATATAAGCTAATA +AGCGGTTCATTTTGAGGAACCGTTACTTTGAGATTCACTTACAGCAACTAAGGTTGTGTT +ACCGTTTCTTCTCAATTTACTGCTGGAGCGGCTATTATGCGTCCATCACCTTCATAGCCC +TAGTCATCAAGCCCATAGAGGTATGTTCGTGTGTAAACGAATTCCAAGACTAATTGGTGG +AAATTTCAGTTTGGATTGAATGAGGCTGATACTTCTATACACTTAAGGGTTCCCCGTAAG +TATATTGCCATAAGGGAGTAGTAACACTAAGGTTGTGAAAATATTGCACGACGTAGGTAT +TCTCAATTTCCTTCTAATTCTGTAGGATTTATGTAAGGCGACCGGGACTCTATTGTTTTG +TCTCCGAGAGTTTCTTAATCAATTGTCAGGCTAGTAGATCAAGTGTAATAAATGATTAGA +GGTCCTCATTTGGAGAATTTATCTATATCCTTGGTCGTCCACGCGGTATCGGAGTTGCTA +TACAATAAGTTGGTTCCAGAAAGCGTCTTAATTACATACTCTTGGTTTATCAACGAGATG +GTACCTAATACTCTCCTCTCAGTTCAGTAATAAGGACCGTTAACCGCACAATTGCATGTC +ACCATGTAACACATCCTAGGTTCAGTGGTGCAAACAAATCAAAGTCGTTCGATGTCACTA +AAACATTTTGCTTAGTAAGCTCACTTGGTTATGCAATATTCTTCACTTCCACAAGTGACT +CTACTTAAGGCGACGCACCTCCCTACAATTCGCATACGCCAGGTACACACAGCATGGAAT +AGTGTAGTACCTACTCATGCGCGAACGGTCGCCTGCAGAATTCCAACATGGAGGTCTTCT +GGCCTAGTGCTTGTGCTTCCGGGATACACCGCACTCATATCACAGTTTTCCCTGGCACAG +GTTATAGTCCGCTAGCGTGTTGAAGCTAGTTCACCCTTACTATGATCCAAGAAAAGCTTT +TCGGCCGGCCATCCTTCACCATACGTTTCGGGGTCTTAGTTCATTATCAGAGTCGGTGCC +ATTGTTCCATGTAGGTACGTGGAGGAAGTAACTCTTGATATGCTATACGTGTAGCATACT +ATACTCCAGAATCCGTCGCAACAATCCCTTTATCTGCCCCTTTATTTACATTCCCCGCAT +GTTTTGATTACTTAAATGTCGGGTACTGCTGGTATACACCGTATGCACCGAAAGACAGCA +ACCCCTCAAAGCTTCGACGAGTTACCTGGTGTGAGACTATCAGCTTATAACCCTTACTAA +CAGCAGTAGACGAATTCTCCTAGTATAAAGTCAATTACAGTTGACTAAATTCGAAGTAGC +CGAGTGGGTCTCATTAGACCCTACATGTATCTCTTGTTTTCAAAACGGCTGTGAAAGTCG +GAATATTATGTGAGTATGATTCACTCGGCGGAACACTCAAACTCGCTGAATCATTGATTC +GCCGATGATTAAGCCGACCCTCCCAATTACCGCTGCAGCACTACAATCTCAATTTAGGTA +TACGGATCTAGGTCCGTTCGTTACCAGTTACCAATACGCAACCGAGCTCGAAGAGAACAC +AAATTTACGAAGCAAAATTCGGAATCAGGGTATCGTGCAGAATGGCAGGAGAGCTGGAAC +TGTTGTCAGATTTCCCTCTAGTAATCGTACGAGAATATATTCTATGTCACACATTAACCT +ATAGGTAAAGCCTCATTATACTCCGTTTAATGCAGACTTATAGGATGCCATGCAACAAGT +CTAATCGTCGCGAGGACACTCAAAAGGATCAGTGGAAAGTAACACTTTGTGGTTCAATTC +AGAAAATCAGCTTGTTTGTACCTACAAGTACAAAACTTGGAGTGGTAGAGAGGTCAATCG +ATTAAGTTAAAAGGTTAACGCATGCGCCTAGTCATTAATTGGTTGCTGCGCAAAATAATG +CATGCGTAGTAAATCCCAGCCCCAAGTCGAATAGATTATTAACGCCGGAAGCAGCCATCT +GCGGAATCTTCGTTGTGTCGAGCGTCAAACGTTGCTCCATGGCTCCCTCCCTTTATCGGG +TTCTCTCATTGAGTCCAACTAAACATCTACAAAAGAACTTTGTTATGTGATATAGCTTAG +GTCTAATCTTAGGCTGACATGCATAACGCTTTGTCGAGGTCTATTAACATAGCCGAATGC +ATGCAAGCTTTGATGGATATTAACTTCCCAATGTCTAAGATTAAAGAAGAGGACACCCAT +TATGTCAATCATCTAGCTAAATCGAGCTGCGAGCCGGAGAGTAAACAGTTTCCTTTTCTT +CGGCGGTTATTTGAAAATTCCTTTCTTATGGCAGTGTTTCGAGCGAGCAGTATATTAGAC +CCAACCTCGATAATCGTTAATCACATAGCGACTATGATAGTATCATTACCAGCAGCATAC +ATAAAATTGTAAAGTGTGTTACTGTTTGCGTGGGTGATTATAGTACAGTCTTTTGCAAAT +CTACGGCCCTGACAGAACTTCACATTAAAGGCCATCCACAGAACAATGGACAACGTATAA +AACCTAAAAGGATATCGTTTTCCTGGGGTTTTCAGTTGTTTTAATGACCGGTAAATTTTC +TTACCCTATTGTGTTTCCTTACACAGAAATATCTGAATATTGAGGTACCTGTGAACATTA +TCATTCATACAACATATCCTATCGCCCATCCTGTGCGGCGACTACTCCAGCACTCACTAA +TTGTTAATCATCTCATACAACTCGTCAGAATTAACATTACCGCAAACTGCTTACTAGCGC +AATCAGGTCAAGAGGAGGACGGCTTTGTCACTTAAAAGAATAAGGTGTAGCTGCATAAAA +CAATGTGTATCTTCTGAGCTTCACAGCCGTGGGCTATCTATGGTTCCGGTCCTGTTGATT +GCTCCCGATGTTGAACAATACTTTCCACTTTCCGTGACAGAAACTTTAGAGCAAGAGGTC +AAACTTTACCCAAGCCCATAGGTAGAAGTTACGCGCGCATTGACGTTTGATCAAGGGACA +GCTGTGAATATCCGTCCCACGTAATCGTGACTTCTCATCAATATTATATTACTGCCGCTA +ATCAACAACTTCCTTGTTTCGACTGAAACGATTTTAGTCAAGTCGAAGACCTCATACGAT +AAGATTTGCAACATGTCTAAAAGAGAACGGGAACTGGCAAAAGGCTTGGTAGATCCGTCT +ATAGCGTAAAACTGATTAACCCATTAGGTCTGAATAACTTTACACAACCCTCCGCACTGT +TAAATGACGGGCTTTGCTCTGTTTTGACACATCAGCTAGAAACTCGCCACGAAGGCATAA +GGCTCCCATATAGCGTAGCTGACAAACATATGAGGTGGCTGCATAAACTAAATTGAGGCT +CGCGTTCGGATACTTGCCCATGTAGCAAGTCTTGGCAACCAACTATATAATCATCACGAA +TTGAGTGCTAAAGACATGCGAACAGTTGGGGCTGCTATATAGTATGACAGATATAGAAAT +TTTATAAAATGTCGTAGGAATCTGGAGGCCAAAATCATTAGACACTCTTGTAAAAGGTAT +GGTAATGTGTATGACCTCTTGGCATAGTGTCCAATTATTCTCGGTTTACTCTCAGAGACA +CAGTCATGTAAAAGTGGTGAGGAATTACCGCCGTGTTTTGCCAACCAAGAAGCATTGAAC +AGTAGATCAATAATGATATTCGGTAGCGTATTTACGCTTTGCGGTTTTCAGAAGAAACTA +TCACAATTGAAACTCTATTCTTCGCCTCATTCCGTACCGTTAGGAATGACTCGAATCGTA +CTGTCTGCCGCGGGGCATAGTGTATTGCTCCCCACCAGGTTCAGATAGTTCGAATCAGTG +CGCTGTACAATTGCCTTACGTGTAGATTTGCATCACCGCTTCACGTAGGCACCCAGAGTG +CTCACTAAAGCCACTAGAGAGATAGAGTTAGAAATTAAGTATCGGTTACGCCCCTCAGAC +GACATAACTCACTTCTACCGAATATCCTTTCTATCTTGGATACTACTAATGCTTCCGTTC +ACGCCGCAATCATGTGGATCCTCCAGTAAGCAGGGTGCTGTCATGACTATACAGTACGGA +TCCGTAAGCATTTTGAGGATGATAACATAGGGTCGGTTACTGTGGATTTCCGTTACTTAG +GAGAGCAGCTTTAGCTGACTTTGCTGAGGCTGCGCGTGTTAGACAGCAATTTACGAACGG +CGCACTCTATAGCAGGCACTCACAGTGGACCAGTAGTCCTATTGCAAGAGTTCATTATGG +AACATTTTAGTCCTCTATCACACGGACCATTGCAGTAGATAACTCTAATCCTATGTCTTT +ATTTGGTTGCCTGGAACCCCTTACCACTAGACACCCCAATAAGTAATCTTGCTTCCATGT +CGAATTGATACTCATCGAAAACATATAAAACTAATTATGCTTGTGTTCCTGTGGTCTGTT +ATATAGAGGCGCCCTATTGGCCGCGGGATAAGGATCATTTTGGCACACTAACGGGATCCT +AAAACTTTATCTTTCAACGACTCCTACATGCCTTTTAGGTTAGTACGCGAATCGCCTAAC +AAGCCAATGGGTATTGGAGAATTAGACAAAATGGTTGAGGAATAAAGTGGCGCAGGATTT +TGTCCGAGAAAGGGATAGCAAACGGTCGCAGGCAGGAGTAACAATTTTCAACCGACCTTA +ATAGAGCTCAAAAGCTACCGGAGAAAGCTTCGTCTATGCTTAATACATATGCTAACCTAT +GAATTTCGTAAGCGTAATATAAACTTATCAGATATTTTAAAAGCATCCTATTCAGTCGTA +CTTTTGGCAGGAAAGGTCAGGCGAAACAGAGTCTCCCTGCGGAGGCTTTTAAAATAAATA +GCGGGCCTAGCATCGATTCTAAAAGACGACCCCAGGTGCGTAACCGTGCCTCCCCAAGTC +TTCTTTTAACAATTACCTAGAGAACGGCGTCAGTCGCGAATGACCTTACGAACGTTTACG +CGGAGCCGAGTAAGATTAATAACTGCTTATTGATTTGCAATCGTTTGATACGGGTGGCCC +GAAGCTCAATATCAACATAAATAAAATTAGTCGGAATGGTCGCTTAAATCGCGCGCTGTC +ACTGTCTTCATATGAGGGAGTTGTGTAAGACTGCATTGATATATAGGTATGATTTCGGTT +TAGAACTTTGTCTGTTAGCAACTCCGCATGATTGAAGGAAATCCTCGTTGGTAAGATCTC +TTTAGCATTTGCACAGCTGACTCTAACAGCATAGTATGTGATCGTATTATGTCTGCAGTT +TGTAACACAGTGGGCGGCATGGATGGTACTTAATGGACGTAATGAGCAGTAGACCACCGG +TGTTACCTAACCATCATTAGAGTAGGCGAGATTGCGCTTGTACGACTTATATATAAGGGT +AACCGGAATACCGTTCCTCTTATCAACAACAGTTACTGGTCTTAATTCACATCGGATATT +GCGATCGCCAAGACTATCCCGTAAGTCGTAAGCTAACCAACTAGCGGTTAGGTTTATTGA +GGTTTTGATGGGAACTTCTCAGACACGTCGTCAACTACCTAATTTCTTGGATGGAGCTAG +GCTAACTGTCCCAGAACTTTCTGACACTCGAGATCCTCTAACTAATTGGAATCCAGGAAT +TCCCTTATTGCATCGCCACAAACGACCATAAATTACAGCATGTTTCATTGTCTAACGTGC +CTATCCACGAAATTGAATTCGGTTCACATTATATATCCCCTTCTACCGCTAATTTAATGT +TTAACGTTGATGGGGCAAAGCACATTCGAGAAGTACCGAAAAGTCTCAATCCAAAGACCG +GAGGAACTGGCTTCGGTAAGAATCGCGAGTATCCTTGGATGCCCTGCCTGATTATAACTT +GTTCCATGTAGATAGGCGTAGCTAATTCATAGCAATACAATAAACGAGTCAGAACTGTAG +TCTAACATAACAGCCTGCTCTCCAGGTAACAGCCCATTATTAGATATAGTATCACGATCG +TCGGTTGTATTAGTGGTGATAACTATCGATTCTGCCACTAATAGAATGTGCAGAAATAAA +GTATCTGAAAGAAAACGAAGTCACAGAGAATAAAGCTCACTTCATAAAAGTCGGTTGCAG +TAGACGCATATCAATTTTCCCTGCTGCATTTTAGAGTTCGGAATAGTTAAACATAATACT +GGAAGCGCTTCCGGCAATCAGGAATAACCCCATATAAACCAACCTTTGTTGCTATTGCCA +GCGCTATTCTCGTCAAAATTTCTCCCTATGGTCTTCACATCATGCATCACCGGACCCTTT +GATAGACGATGACCCAATTACAATCACTCCACGGATGAGCATCCCATTTTATACGAGGCC +CACTGGAAACAATTGCAATCGACGTGACCAAGTAGAGGAGCGTGCTCGAAAGGTGATGAT +TGCCGAATTCTAACAAGGATACTATAAGCCACGGAACGCTGACGTTGAACAGACCTGGTC +TCCTGGGCACTTCGCAGCACCTCAGTAGTAATTCCGGTAGATTAGGACTTAGCATTCCGT +TGATCTTACAGGATTTATAAATAAGGAGATCTGTCTTGTTTAATTAGGAGGACGCTTTTC +CCGCGTAAGTACGGGAAAACGTTCTTCTGATTTTGTTTGCCACTTGACATTGTAGCTGCT +AGGAGAAGGGATAATATCCGCGTTTTCTTTTACCGTAACGTCGGAGCATACCATGGTAAT +TGTCCGTGTCAAAACTAGATATCTAGGTTGCAAAATTCAGTCAGTAAGTCCTGAGGCCTT +CCGCATTATTAATTCTACAGACATATGAATTTGCTCCACCGGCTAGCACAGTCAACTCAA +CCCACGATAGGGGAACGAAATCACAAATAGGTTCACATGGTCAATACAAGGCAAACCATT +CCCCATAACTCACGCACTGACGGTAAGGCCATTTCAGGTCAAGCGGTGAATGCTGTGAAA +AGCAGCTCGACCACCTGCCGTGGATGGCAAACCGATAACAAAGGACTCCGATACTTCATT +TGTAAACGTTTGCAGTGCTGACGTAACTCATATCTACAGTCAAACCGAATGGTTTGATCG +GCATTATGTAAAGGAATCGACACACGTTGCGTCTTCTAGATTATTACACACCTGTCTGCG +ACGGATATAGGTAAATAAGTCAGCCTCCACTCTGCAGAAGATACTAGAAACGTATCAGTA +ATAGCTATCAGGATTTCGCCATCCTCGCACTGTGCCCGGATATCACAGCAAGATTCTAGG +ATGGCACTTGTGTGACTAGAGGTTTTACTCGTTGAGCCATTCTTACTATAGGCATGGGAT +TACAATGTGCATGTTTGTGATGTTATCCCATATCTTGCATGTATCAGCCTACCAATTAGA +CATATGACTAGATGTAGTCGATCAACGCAAGGGTGCGGACTTTGATTCCTTTTGAATTGA +AGTCAACTCAGATGCTCCTTAAGACGTTTTACAGTAGGTATTTTGTGGTACAAACCAGAA +CCAGTGCCAGTCGGTAGTTATTGTAGTGTGTTCTTAATACATATTTGGTATTGGAGTTTC +TAACATTTAAAAGGAGCCTATTACACTTACTTAATTTGCGTCTATATTTCTGTTACGATA +TGTCGTCTGTCGATTTTACGAGTTTCATACGTGCGGGTTCCCTGTTCGCAATGGGCCCCT +TGCTAATGTCCCGCATCTTTAGGATGCAAACTTACTCACGCCTCCTTTACCGAGACTTGG +TGGGAGAGAAGACTCCTGTAGAATCCCGATCTGAATGGTTTCAGTGTAAGGGTCCCTTCT +AGCCATATCATTGAATATTCTTGTACTTTAAGTAACTCGATCCTACCAGTACAATTCTAG +GTTTGCCTTATAGCCGGAATGAGTATCAGCGTCATTCACCCCGGCCGGATATTATTTGCA +ATGTCAGGGACACCCAAAATAGACCGGTTAGAAGGCATATGCGATGAGAGTTGGTGCCTA +AATTAAACGATACAATTGATATGACAAGGACTATACGATGAAATCCATGAGATAATTATC +GTAACTCGGCCAACCTAAAACCGTGCAAGATAGGAGCGGTCCTAGAAGTACTATCGACAC +CTTAAATACTCACTTGAGTTTTCCGATCCTATAGTGCCAATCATATGGCGCAGGAATATT +ACAAACTAAGAAAGTCAACAAAAGATGTAAATTGCAACACCTGGCATCGGTGGGGTTGTC +CCCTTAAACCCTGAAACCAACTGTTATGCTCAACATTATATCGAGGCTAAAACGCGTATC +GTGGCACATTAATAACGATCACATAAGCTTTGCGGCTAGCAATAATAATTTAGGACAGCT +TAGATTTTGACCCGTGCTAATCCTCAGTATGGAGTAATTTTACGGATCTCTCGTTGTAAC +CGTCCTCAGTCGTGTACATTTTAACCTTTGTAAACTAGTTTACGAACGAGTATTTAGAAG +GTCCGTACTCTCACCCAACTGACACATTGTACTAGCTCAAGATCGCAAACACTAAGGGTG +TGAGTCGCGGGATAGCGCTTAAATATGACTGCTAATGGTCAAGAGCACGCGCATAATATT +CCACTGGTTCTAGGTCACCACTACGGTCAGACGTTGACCTGCATGCCCTACATCCGGCAC +GGGCTACTAACGGCCTAATATTCTTTGAGCCATATCCATACTCGTCTATGCATATTCAGG +TATACGGCTATAGTGCGTTATTAACTTCGTCGTGATTAAATCCTTTAATTGTTCCATTAT +AAGTATACATGCTTAGATGCGTGAACTTGAGGGATATCGTTGCTCTAAAGTTGTCTTATA +GACTAAATCTAAACAAGCCGTGCAAGACTACTTAAATTACAAATCTTACAGACATCTCGC +CACTGCGCTAACACTAACAA diff --git a/pipelines/nf-atacseq/tests/data/chr_test.fa.fai b/pipelines/nf-atacseq/tests/data/chr_test.fa.fai deleted file mode 120000 index 8158c3c..0000000 --- a/pipelines/nf-atacseq/tests/data/chr_test.fa.fai +++ /dev/null @@ -1 +0,0 @@ -../../../../tests/shared_data/chr_test.fa.fai \ No newline at end of file diff --git a/pipelines/nf-atacseq/tests/data/chr_test.fa.fai b/pipelines/nf-atacseq/tests/data/chr_test.fa.fai new file mode 100644 index 0000000..4e99d5b --- /dev/null +++ b/pipelines/nf-atacseq/tests/data/chr_test.fa.fai @@ -0,0 +1 @@ +chr_test 20000 10 60 61 diff --git a/pipelines/nf-atacseq/tests/data/generate_realistic_reference.py b/pipelines/nf-atacseq/tests/data/generate_realistic_reference.py new file mode 100644 index 0000000..cb9d937 --- /dev/null +++ b/pipelines/nf-atacseq/tests/data/generate_realistic_reference.py @@ -0,0 +1,123 @@ +#!/usr/bin/env python3 +""" +Generate a realistic ~20kb non-repetitive reference sequence for ATAC-seq testing. + +Properties: + - ~42% GC content (human-like) + - No homopolymer runs > 5bp + - High k-mer uniqueness (>99% unique 20-mers) + - Deterministic (seeded RNG) + +Output: chr_test.fa with contig name 'chr_test' +""" + +import random +import sys + +SEED = 42 +LENGTH = 20000 +CONTIG = "chr_test" +LINE_WIDTH = 60 +MAX_HOMOPOLYMER = 5 + +# Target base frequencies for ~42% GC +# A=29%, T=29%, G=21%, C=21% +BASES = "ATGC" +WEIGHTS = [0.29, 0.29, 0.21, 0.21] + + +def generate_sequence(length, seed=SEED): + """Generate a non-repetitive sequence with controlled GC content.""" + rng = random.Random(seed) + + seq = [] + homopolymer_count = 0 + last_base = None + + for _ in range(length): + # Pick a base using weighted random + base = rng.choices(BASES, weights=WEIGHTS, k=1)[0] + + # Prevent long homopolymers + if base == last_base: + homopolymer_count += 1 + if homopolymer_count >= MAX_HOMOPOLYMER: + # Force a different base + alternatives = [b for b in BASES if b != base] + alt_weights = [WEIGHTS[BASES.index(b)] for b in alternatives] + total = sum(alt_weights) + alt_weights = [w / total for w in alt_weights] + base = rng.choices(alternatives, weights=alt_weights, k=1)[0] + homopolymer_count = 1 + else: + homopolymer_count = 1 + + seq.append(base) + last_base = base + + return "".join(seq) + + +def validate_sequence(seq): + """Validate sequence properties.""" + gc = sum(1 for b in seq if b in "GC") / len(seq) + + # Check k-mer uniqueness + kmers_20 = set() + for i in range(len(seq) - 19): + kmers_20.add(seq[i : i + 20]) + unique_20 = len(kmers_20) + total_20 = len(seq) - 19 + uniqueness = unique_20 / total_20 + + # Check max homopolymer + max_hp = 1 + current_hp = 1 + for i in range(1, len(seq)): + if seq[i] == seq[i - 1]: + current_hp += 1 + max_hp = max(max_hp, current_hp) + else: + current_hp = 1 + + return { + "length": len(seq), + "gc_content": gc, + "unique_20mers": unique_20, + "total_20mers": total_20, + "uniqueness_pct": uniqueness * 100, + "max_homopolymer": max_hp, + } + + +def write_fasta(seq, contig, filepath, line_width=LINE_WIDTH): + """Write sequence as FASTA.""" + with open(filepath, "w") as f: + f.write(f">{contig}\n") + for i in range(0, len(seq), line_width): + f.write(seq[i : i + line_width] + "\n") + + +def main(): + output = sys.argv[1] if len(sys.argv) > 1 else "chr_test.fa" + + print(f"Generating {LENGTH}bp non-repetitive reference sequence...") + seq = generate_sequence(LENGTH) + + stats = validate_sequence(seq) + print(f" Length: {stats['length']}bp") + print(f" GC content: {stats['gc_content']:.1%}") + print(f" Unique 20-mers: {stats['unique_20mers']}/{stats['total_20mers']} ({stats['uniqueness_pct']:.1f}%)") + print(f" Max homopolymer: {stats['max_homopolymer']}bp") + + # Validate + assert stats["gc_content"] > 0.38 and stats["gc_content"] < 0.46, f"GC content out of range: {stats['gc_content']}" + assert stats["uniqueness_pct"] > 99.0, f"Uniqueness too low: {stats['uniqueness_pct']}" + assert stats["max_homopolymer"] <= MAX_HOMOPOLYMER, f"Homopolymer too long: {stats['max_homopolymer']}" + + write_fasta(seq, CONTIG, output) + print(f" Wrote {output}") + + +if __name__ == "__main__": + main() diff --git a/pipelines/nf-atacseq/tests/data/generate_test_data.sh b/pipelines/nf-atacseq/tests/data/generate_test_data.sh index f5cb288..9cb2eb4 100755 --- a/pipelines/nf-atacseq/tests/data/generate_test_data.sh +++ b/pipelines/nf-atacseq/tests/data/generate_test_data.sh @@ -1,11 +1,18 @@ #!/bin/bash # ============================================================================= -# WASP2 nf-atacseq Test Data Generator +# WASP2 nf-atacseq Test Data Generator (v2 — realistic reference) # ============================================================================= -# Creates ATAC-seq-like test data by symlinking shared core data and generating -# pipeline-specific files (shorter fragment FASTQs, BWA index, samplesheet). +# Generates self-contained ATAC-seq test data with a non-repetitive reference +# so BWA alignment produces meaningful mapping rates (>80%). # -# Prerequisites: samtools, bgzip, tabix, wgsim, bwa (WASP2_dev2 conda env) +# Previous version used the shared chr_test.fa which is a repetitive ATGC +# pattern yielding ~0% mapping. This version generates its own reference. +# +# To produce non-zero allele counts, reads are simulated from BOTH haplotypes: +# half from the REF haplotype, half from an ALT haplotype with het SNPs applied. +# +# Prerequisites: python3, samtools, bgzip, tabix, wgsim, bwa +# (all available in WASP2_dev2 conda env or WASP2 micromamba env) # # Usage: # cd pipelines/nf-atacseq/tests/data @@ -17,106 +24,307 @@ set -euo pipefail SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" cd "$SCRIPT_DIR" -SHARED_DATA="../../../../tests/shared_data" +# BWA may not be in PATH; check common conda/micromamba locations +if ! command -v bwa &>/dev/null; then + for candidate in \ + /usr/local/Cellar/micromamba/*/envs/WASP2/bin/bwa \ + /usr/local/Cellar/micromamba/*/envs/WASP2_dev2/bin/bwa \ + "${HOME}/miniforge3/envs/WASP2/bin/bwa" \ + "${HOME}/miniconda3/envs/WASP2/bin/bwa"; do + if [[ -x "$candidate" ]]; then + export PATH="$(dirname "$candidate"):$PATH" + break + fi + done +fi echo "===================================================================" -echo " WASP2 nf-atacseq Test Data Generator" +echo " WASP2 nf-atacseq Test Data Generator (v2)" echo "===================================================================" -# Validate shared core data exists -if [[ ! -f "$SHARED_DATA/chr_test.fa" ]]; then - echo "ERROR: Shared core data not found at $SHARED_DATA" - echo " Run: cd tests/shared_data && bash generate_core_data.sh" - exit 1 -fi - # ----------------------------------------------------------------------------- -# Symlink shared reference and variants +# Check prerequisites # ----------------------------------------------------------------------------- -echo "[1/4] Symlinking shared reference data..." +echo "[0/7] Checking prerequisites..." + +check_tool() { + if ! command -v "$1" &>/dev/null; then + echo "ERROR: $1 is required but not found in PATH" + echo " Try: conda activate WASP2_dev2" + exit 1 + fi + echo " OK: $1" +} + +check_tool python3 +check_tool samtools +check_tool bwa +check_tool wgsim +check_tool bgzip +check_tool tabix +echo "" +# ----------------------------------------------------------------------------- +# Clean stale symlinks and old data (one-time migration from v1) +# ----------------------------------------------------------------------------- +echo "[1/7] Cleaning stale data..." for f in chr_test.fa chr_test.fa.fai variants.vcf.gz variants.vcf.gz.tbi annotation.gtf regions.bed; do - if [[ ! -e "$f" ]]; then - ln -sf "$SHARED_DATA/$f" "$f" - echo " ✓ Linked $f" - else - echo " - $f already exists" + if [[ -L "$f" ]]; then + rm -f "$f" + echo " Removed symlink: $f" fi done +rm -rf bwa_index +rm -f sample1_R1.fq.gz sample1_R2.fq.gz +rm -f chr_test.fa chr_test.fa.fai variants.vcf variants.vcf.gz variants.vcf.gz.tbi regions.bed +echo " Cleaned previous outputs" +echo "" + +# ----------------------------------------------------------------------------- +# Generate realistic non-repetitive reference +# ----------------------------------------------------------------------------- +echo "[2/7] Generating realistic reference genome..." +python3 "${SCRIPT_DIR}/generate_realistic_reference.py" chr_test.fa +samtools faidx chr_test.fa +echo " Created chr_test.fa + .fai" +echo "" +# ----------------------------------------------------------------------------- +# Generate VCF with ~30 het SNPs + ALT haplotype reference +# ----------------------------------------------------------------------------- +echo "[3/7] Creating VCF with 30 het SNPs and ALT haplotype..." + +python3 - <<'PYEOF' +import random + +# Read reference +with open("chr_test.fa") as f: + lines = f.readlines() +seq = "".join(l.strip() for l in lines[1:]) + +# Deterministic SNP positions spread across the reference +rng = random.Random(99) +positions = sorted(rng.sample(range(200, 19800), 30)) + +# Transition mapping for plausible variants +transitions = {"A": "G", "G": "A", "T": "C", "C": "T"} + +# --- Write VCF --- +vcf_lines = [] +vcf_lines.append("##fileformat=VCFv4.2") +vcf_lines.append("##source=WASP2_nf_atacseq_test_data_v2") +vcf_lines.append("##reference=chr_test.fa") +vcf_lines.append("##contig=") +vcf_lines.append('##INFO=') +vcf_lines.append('##FORMAT=') +vcf_lines.append('##FORMAT=') +vcf_lines.append("#CHROM\tPOS\tID\tREF\tALT\tQUAL\tFILTER\tINFO\tFORMAT\tsample1") + +snp_map = {} # pos -> (ref, alt) +for i, pos in enumerate(positions): + ref = seq[pos - 1] # 1-based + alt = transitions[ref] + snp_id = f"snp{i+1:03d}" + vcf_lines.append( + f"chr_test\t{pos}\t{snp_id}\t{ref}\t{alt}\t100\tPASS\tDP=50\tGT:DP\t0|1:50" + ) + snp_map[pos] = (ref, alt) + +with open("variants.vcf", "w") as f: + f.write("\n".join(vcf_lines) + "\n") + +print(f" Created variants.vcf with {len(positions)} het SNPs") + +# --- Write ALT haplotype reference --- +alt_seq = list(seq) +for pos, (ref, alt) in snp_map.items(): + assert alt_seq[pos - 1] == ref, f"Mismatch at {pos}: expected {ref}, got {alt_seq[pos-1]}" + alt_seq[pos - 1] = alt + +with open("chr_test_alt.fa", "w") as f: + f.write(">chr_test\n") + alt_str = "".join(alt_seq) + for i in range(0, len(alt_str), 60): + f.write(alt_str[i:i+60] + "\n") + +print(f" Created chr_test_alt.fa (ALT haplotype with {len(snp_map)} substitutions)") +PYEOF + +# Compress and index +rm -f variants.vcf.gz variants.vcf.gz.tbi +bgzip -c variants.vcf > variants.vcf.gz +tabix -p vcf variants.vcf.gz +echo " Created variants.vcf.gz + .tbi" echo "" # ----------------------------------------------------------------------------- -# Simulate ATAC-seq-like reads (shorter fragments, 150-250bp) +# Create regions BED covering all SNP positions # ----------------------------------------------------------------------------- -echo "[2/4] Simulating ATAC-seq reads..." +echo "[4/7] Creating regions BED..." + +python3 - <<'PYEOF' +import random + +rng = random.Random(99) +positions = sorted(rng.sample(range(200, 19800), 30)) -NUM_READS=500 +# Create ~500bp regions centered on each SNP, merge overlapping +regions = [] +for pos in positions: + start = max(0, pos - 250) + end = min(20000, pos + 250) + regions.append((start, end)) + +# Merge overlapping regions +merged = [regions[0]] +for start, end in regions[1:]: + if start <= merged[-1][1]: + merged[-1] = (merged[-1][0], max(merged[-1][1], end)) + else: + merged.append((start, end)) + +with open("regions.bed", "w") as f: + for i, (start, end) in enumerate(merged): + f.write(f"chr_test\t{start}\t{end}\tpeak_{i+1}\n") + +print(f" Created regions.bed with {len(merged)} peaks covering {len(positions)} SNPs") +PYEOF +echo "" + +# ----------------------------------------------------------------------------- +# Simulate ATAC-seq reads from BOTH haplotypes (REF + ALT) +# ----------------------------------------------------------------------------- +echo "[5/7] Simulating ATAC-seq paired-end reads (dual haplotype)..." + +# 20kb genome, 75bp reads, ~20x total coverage +# Split: ~1350 pairs from REF, ~1350 pairs from ALT +NUM_READS_PER_HAP=1350 READ_LEN=75 FRAG_SIZE=180 FRAG_STD=30 ERROR_RATE=0.001 -SEED=100 - -if [[ -f "sample1_R1.fq.gz" && -f "sample1_R2.fq.gz" ]]; then - echo " FASTQs already exist, skipping" -else - wgsim -N $NUM_READS \ - -1 $READ_LEN \ - -2 $READ_LEN \ - -r 0 -R 0 -X 0 \ - -e $ERROR_RATE \ - -S $SEED \ - -d $FRAG_SIZE \ - -s $FRAG_STD \ - "$SHARED_DATA/chr_test.fa" \ - sample1_R1.fq \ - sample1_R2.fq \ - > /dev/null 2>&1 - - gzip -f sample1_R1.fq - gzip -f sample1_R2.fq - echo " ✓ Created sample1_R{1,2}.fq.gz (${NUM_READS} pairs, ${READ_LEN}bp, ${FRAG_SIZE}bp frags)" -fi +# Simulate from REF haplotype +wgsim -N $NUM_READS_PER_HAP \ + -1 $READ_LEN \ + -2 $READ_LEN \ + -r 0 -R 0 -X 0 \ + -e $ERROR_RATE \ + -S 100 \ + -d $FRAG_SIZE \ + -s $FRAG_STD \ + chr_test.fa \ + ref_R1.fq \ + ref_R2.fq \ + > /dev/null 2>&1 +echo " Simulated ${NUM_READS_PER_HAP} pairs from REF haplotype" + +# Simulate from ALT haplotype +wgsim -N $NUM_READS_PER_HAP \ + -1 $READ_LEN \ + -2 $READ_LEN \ + -r 0 -R 0 -X 0 \ + -e $ERROR_RATE \ + -S 200 \ + -d $FRAG_SIZE \ + -s $FRAG_STD \ + chr_test_alt.fa \ + alt_R1.fq \ + alt_R2.fq \ + > /dev/null 2>&1 +echo " Simulated ${NUM_READS_PER_HAP} pairs from ALT haplotype" + +# Combine and compress +cat ref_R1.fq alt_R1.fq | gzip -c > sample1_R1.fq.gz +cat ref_R2.fq alt_R2.fq | gzip -c > sample1_R2.fq.gz +echo " Combined into sample1_R{1,2}.fq.gz ($((NUM_READS_PER_HAP * 2)) total pairs)" + +# Clean up temporary files +rm -f ref_R1.fq ref_R2.fq alt_R1.fq alt_R2.fq chr_test_alt.fa echo "" # ----------------------------------------------------------------------------- -# Build BWA index (for local testing) +# Build BWA index # ----------------------------------------------------------------------------- -echo "[3/4] Building BWA index..." +echo "[6/7] Building BWA index..." BWA_INDEX_DIR="bwa_index" -if [[ -f "${BWA_INDEX_DIR}/chr_test.fa.bwt" ]]; then - echo " BWA index already exists, skipping" -else - mkdir -p "$BWA_INDEX_DIR" - cp "$SHARED_DATA/chr_test.fa" "$BWA_INDEX_DIR/" - bwa index "$BWA_INDEX_DIR/chr_test.fa" 2>&1 | tail -2 - echo " ✓ Created BWA index ($(du -sh $BWA_INDEX_DIR | cut -f1))" -fi - +mkdir -p "$BWA_INDEX_DIR" +cp chr_test.fa "$BWA_INDEX_DIR/" +bwa index "$BWA_INDEX_DIR/chr_test.fa" 2>&1 | tail -2 +echo " Created BWA index" echo "" # ----------------------------------------------------------------------------- -# Create test samplesheet +# Create samplesheets (both test and local variants) # ----------------------------------------------------------------------------- -echo "[4/4] Creating test samplesheet..." +echo "[7/7] Creating samplesheets..." -SAMPLESHEET="samplesheet_test.csv" -if [[ -f "$SAMPLESHEET" ]]; then - echo " $SAMPLESHEET already exists, skipping" -else - cat > "$SAMPLESHEET" << EOF +# test samplesheet uses absolute paths +cat > samplesheet_test.csv << EOF sample,fastq_1,fastq_2,sample_name -test_sample1,${SCRIPT_DIR}/sample1_R1.fq.gz,${SCRIPT_DIR}/sample1_R2.fq.gz,SAMPLE1 +test_sample1,${SCRIPT_DIR}/sample1_R1.fq.gz,${SCRIPT_DIR}/sample1_R2.fq.gz,sample1 EOF - echo " ✓ Created $SAMPLESHEET" -fi +echo " Created samplesheet_test.csv" + +# local samplesheet uses ${projectDir} relative paths (for nextflow) +cat > samplesheet_local.csv << 'EOF' +sample,fastq_1,fastq_2,sample_name +test_sample1,${projectDir}/tests/data/sample1_R1.fq.gz,${projectDir}/tests/data/sample1_R2.fq.gz,sample1 +EOF +echo " Created samplesheet_local.csv" + +# ----------------------------------------------------------------------------- +# Quick validation +# ----------------------------------------------------------------------------- +echo "" +echo "===================================================================" +echo " Validation" +echo "===================================================================" + +# Check BWA alignment quality +echo "" +echo "--- Quick alignment test (first 100 pairs) ---" +bwa mem -t 2 \ + -R "@RG\tID:sample1\tSM:sample1\tPL:ILLUMINA\tLB:lib1" \ + "$BWA_INDEX_DIR/chr_test.fa" \ + <(gunzip -c sample1_R1.fq.gz | head -400) \ + <(gunzip -c sample1_R2.fq.gz | head -400) \ + 2>/dev/null \ +| samtools flagstat - 2>/dev/null + +echo "" + +# Check VCF REF alleles match reference +echo "--- VCF REF allele validation ---" +python3 - <<'PYEOF' +seq_lines = open("chr_test.fa").readlines() +seq = "".join(l.strip() for l in seq_lines[1:]) + +errors = 0 +total = 0 +with open("variants.vcf") as f: + for line in f: + if line.startswith("#"): + continue + fields = line.strip().split("\t") + pos = int(fields[1]) + ref = fields[3] + actual = seq[pos - 1] + total += 1 + if ref != actual: + print(f" MISMATCH at pos {pos}: VCF REF={ref}, actual={actual}") + errors += 1 + +if errors == 0: + print(f" All {total} REF alleles match reference") +else: + print(f" {errors}/{total} mismatches found!") +PYEOF echo "" echo "===================================================================" -echo " SUCCESS! nf-atacseq test data generated." +echo " SUCCESS! nf-atacseq test data generated (v2)." echo "===================================================================" echo "Total: $(du -sh . | cut -f1)" echo "" diff --git a/pipelines/nf-atacseq/tests/data/real_counts.tsv b/pipelines/nf-atacseq/tests/data/real_counts.tsv new file mode 120000 index 0000000..08351b0 --- /dev/null +++ b/pipelines/nf-atacseq/tests/data/real_counts.tsv @@ -0,0 +1 @@ +../../../../tests/shared_data/expected_counts.tsv \ No newline at end of file diff --git a/pipelines/nf-atacseq/tests/data/real_test.bam b/pipelines/nf-atacseq/tests/data/real_test.bam new file mode 120000 index 0000000..21f7b54 --- /dev/null +++ b/pipelines/nf-atacseq/tests/data/real_test.bam @@ -0,0 +1 @@ +../../../../tests/shared_data/sample1.bam \ No newline at end of file diff --git a/pipelines/nf-atacseq/tests/data/real_test.bam.bai b/pipelines/nf-atacseq/tests/data/real_test.bam.bai new file mode 120000 index 0000000..0037730 --- /dev/null +++ b/pipelines/nf-atacseq/tests/data/real_test.bam.bai @@ -0,0 +1 @@ +../../../../tests/shared_data/sample1.bam.bai \ No newline at end of file diff --git a/pipelines/nf-atacseq/tests/data/real_wasp_data.json b/pipelines/nf-atacseq/tests/data/real_wasp_data.json new file mode 120000 index 0000000..bd05953 --- /dev/null +++ b/pipelines/nf-atacseq/tests/data/real_wasp_data.json @@ -0,0 +1 @@ +../../../../tests/shared_data/wasp_data.json \ No newline at end of file diff --git a/pipelines/nf-atacseq/tests/data/regions.bed b/pipelines/nf-atacseq/tests/data/regions.bed deleted file mode 120000 index da6c378..0000000 --- a/pipelines/nf-atacseq/tests/data/regions.bed +++ /dev/null @@ -1 +0,0 @@ -../../../../tests/shared_data/regions.bed \ No newline at end of file diff --git a/pipelines/nf-atacseq/tests/data/regions.bed b/pipelines/nf-atacseq/tests/data/regions.bed new file mode 100644 index 0000000..9b399eb --- /dev/null +++ b/pipelines/nf-atacseq/tests/data/regions.bed @@ -0,0 +1,15 @@ +chr_test 2668 3393 peak_1 +chr_test 4316 4816 peak_2 +chr_test 4939 5439 peak_3 +chr_test 5808 6315 peak_4 +chr_test 6486 7995 peak_5 +chr_test 8090 8679 peak_6 +chr_test 11052 11552 peak_7 +chr_test 12204 13167 peak_8 +chr_test 13187 13687 peak_9 +chr_test 13766 14266 peak_10 +chr_test 15071 15819 peak_11 +chr_test 15982 16482 peak_12 +chr_test 17274 18099 peak_13 +chr_test 18526 19026 peak_14 +chr_test 19230 19730 peak_15 diff --git a/pipelines/nf-atacseq/tests/data/sample1_R1.fq.gz b/pipelines/nf-atacseq/tests/data/sample1_R1.fq.gz index 2d8e601ff5bcf55b8d0150a890db78cba00b95c1..88debd7dc6acc74857a9daab5b5f884ced8bb38f 100644 GIT binary patch literal 91126 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

RNJPA2g49-#(8q_L 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�qwKbeg@iC4)#`v}>G}VN- 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 diff --git a/pipelines/nf-atacseq/tests/data/samplesheet_test.csv b/pipelines/nf-atacseq/tests/data/samplesheet_test.csv index d50c362..cf1884a 100644 --- a/pipelines/nf-atacseq/tests/data/samplesheet_test.csv +++ b/pipelines/nf-atacseq/tests/data/samplesheet_test.csv @@ -1,2 +1,2 @@ sample,fastq_1,fastq_2,sample_name -test_sample1,${projectDir}/tests/data/sample1_R1.fq.gz,${projectDir}/tests/data/sample1_R2.fq.gz,sample1 +test_sample1,/Users/jeffjaureguy/Desktop/WASP2/pipelines/nf-atacseq/tests/data/sample1_R1.fq.gz,/Users/jeffjaureguy/Desktop/WASP2/pipelines/nf-atacseq/tests/data/sample1_R2.fq.gz,sample1 diff --git a/pipelines/nf-atacseq/tests/data/variants.vcf b/pipelines/nf-atacseq/tests/data/variants.vcf new file mode 100644 index 0000000..e3e67f2 --- /dev/null +++ b/pipelines/nf-atacseq/tests/data/variants.vcf @@ -0,0 +1,38 @@ +##fileformat=VCFv4.2 +##source=WASP2_nf_atacseq_test_data_v2 +##reference=chr_test.fa +##contig= +##INFO= +##FORMAT= +##FORMAT= +#CHROM POS ID REF ALT QUAL FILTER INFO FORMAT sample1 +chr_test 2918 snp001 A G 100 PASS DP=50 GT:DP 0|1:50 +chr_test 3037 snp002 A G 100 PASS DP=50 GT:DP 0|1:50 +chr_test 3077 snp003 C T 100 PASS DP=50 GT:DP 0|1:50 +chr_test 3143 snp004 G A 100 PASS DP=50 GT:DP 0|1:50 +chr_test 4566 snp005 T C 100 PASS DP=50 GT:DP 0|1:50 +chr_test 5189 snp006 A G 100 PASS DP=50 GT:DP 0|1:50 +chr_test 6058 snp007 T C 100 PASS DP=50 GT:DP 0|1:50 +chr_test 6065 snp008 T C 100 PASS DP=50 GT:DP 0|1:50 +chr_test 6736 snp009 T C 100 PASS DP=50 GT:DP 0|1:50 +chr_test 6756 snp010 T C 100 PASS DP=50 GT:DP 0|1:50 +chr_test 7166 snp011 T C 100 PASS DP=50 GT:DP 0|1:50 +chr_test 7300 snp012 G A 100 PASS DP=50 GT:DP 0|1:50 +chr_test 7745 snp013 T C 100 PASS DP=50 GT:DP 0|1:50 +chr_test 8340 snp014 T C 100 PASS DP=50 GT:DP 0|1:50 +chr_test 8429 snp015 C T 100 PASS DP=50 GT:DP 0|1:50 +chr_test 11302 snp016 G A 100 PASS DP=50 GT:DP 0|1:50 +chr_test 12454 snp017 T C 100 PASS DP=50 GT:DP 0|1:50 +chr_test 12676 snp018 A G 100 PASS DP=50 GT:DP 0|1:50 +chr_test 12752 snp019 A G 100 PASS DP=50 GT:DP 0|1:50 +chr_test 12917 snp020 T C 100 PASS DP=50 GT:DP 0|1:50 +chr_test 13437 snp021 A G 100 PASS DP=50 GT:DP 0|1:50 +chr_test 14016 snp022 G A 100 PASS DP=50 GT:DP 0|1:50 +chr_test 15321 snp023 C T 100 PASS DP=50 GT:DP 0|1:50 +chr_test 15569 snp024 T C 100 PASS DP=50 GT:DP 0|1:50 +chr_test 16232 snp025 T C 100 PASS DP=50 GT:DP 0|1:50 +chr_test 17524 snp026 C T 100 PASS DP=50 GT:DP 0|1:50 +chr_test 17593 snp027 C T 100 PASS DP=50 GT:DP 0|1:50 +chr_test 17849 snp028 T C 100 PASS DP=50 GT:DP 0|1:50 +chr_test 18776 snp029 C T 100 PASS DP=50 GT:DP 0|1:50 +chr_test 19480 snp030 T C 100 PASS DP=50 GT:DP 0|1:50 diff --git a/pipelines/nf-atacseq/tests/data/variants.vcf.gz b/pipelines/nf-atacseq/tests/data/variants.vcf.gz deleted file mode 120000 index 380b7aa..0000000 --- a/pipelines/nf-atacseq/tests/data/variants.vcf.gz +++ /dev/null @@ -1 +0,0 @@ -../../../../tests/shared_data/variants.vcf.gz \ No newline at end of file diff --git a/pipelines/nf-atacseq/tests/data/variants.vcf.gz b/pipelines/nf-atacseq/tests/data/variants.vcf.gz new file mode 100644 index 0000000000000000000000000000000000000000..d7a7f83507f76ffa613e5489bb3ba60e19970e4f GIT binary patch literal 563 zcmb2|=3rp}f&Xj_PR>jW{~3Dw59T!+2)LX-7asDkYZ2Smh;S#xB_1jP1-F%p=9)$@ z&Av6;6zcOw6;qkV4ub#XS6WiKVEO)|2X z)26?nM0V4B!Hi{sQ5zDO{31*j8O_q0>3Gjx#UN?>oHv<)ZisiS;CTDS_OFk` z&$wtcWHB-@$fNmRjB)Cfy;;tNJgg5QYo-34<}2agyz|xR-91GStErb#-W)EUU>#o7 z!2j~b@qP6*`)%*b`#6x7wv?&Z*Z6JFc39E*3r__xg(a-nt`+*@tfi zPX44?v{+0<%R4Z6no_D>o=CILj3Cp0Tt=T~t_tKW67x&t6+JfVl(cl4ucxT2X~|Qm mUS9V#ZXa{b%(D7adGi0=AFNZ~Ue|VI!weQ_24-+70TBQackv_u literal 0 HcmV?d00001 diff --git a/pipelines/nf-atacseq/tests/data/variants.vcf.gz.tbi b/pipelines/nf-atacseq/tests/data/variants.vcf.gz.tbi deleted file mode 120000 index 7a95bbe..0000000 --- a/pipelines/nf-atacseq/tests/data/variants.vcf.gz.tbi +++ /dev/null @@ -1 +0,0 @@ -../../../../tests/shared_data/variants.vcf.gz.tbi \ No newline at end of file diff --git a/pipelines/nf-atacseq/tests/data/variants.vcf.gz.tbi b/pipelines/nf-atacseq/tests/data/variants.vcf.gz.tbi new file mode 100644 index 0000000000000000000000000000000000000000..98e77de5066d4ed112b463bbb09135100c1c4c76 GIT binary patch literal 121 zcmb2|=3rp}f&Xj_PR>jWF$~;=pHfm%5)u-ak|cPUP6f;o?wD-c^~gJ@+goBrql~{| zhZCDd>N&23dlx)1uw!nvxS_M&v%op<$Ww^`Mqx4IL shared_data/sample1.bam (aligned reads) + // These serve as stand-ins; a full integration test requires + // actual make-reads output. The process will validate via + // wasp2-map filter-remapped + samtools index + samtools flagstat. + input[0] = [ + [ id:'test_real', single_end:false ], + file("${projectDir}/tests/data/real_test.bam"), + file("${projectDir}/tests/data/real_test.bam.bai"), + file("${projectDir}/tests/data/real_test.bam"), + file("${projectDir}/tests/data/real_test.bam"), + file("${projectDir}/tests/data/real_wasp_data.json") + ] + """ + } + } + + then { + assertAll( + // Process completes successfully + { assert process.success }, + + // BAM output: filtered WASP-corrected BAM + index + { assert process.out.bam.size() == 1 }, + { assert path(process.out.bam[0][1]).exists() }, + { assert path(process.out.bam[0][1]).toFile().size() > 0 }, + { assert path(process.out.bam[0][2]).exists() }, + { assert path(process.out.bam[0][2]).toFile().size() > 0 }, + + // Stats output: samtools flagstat results + { assert process.out.stats.size() == 1 }, + { assert path(process.out.stats[0][1]).exists() }, + { assert path(process.out.stats[0][1]).text.contains("mapped") }, + + // versions.yml emitted with both tools + { assert process.out.versions.size() == 1 }, + { assert path(process.out.versions[0]).text.contains("wasp2") }, + { assert path(process.out.versions[0]).text.contains("samtools") }, + + // meta map preserved + { assert process.out.bam[0][0].id == 'test_real' } + ) + } + } + + test("wasp2_filter_remapped - stub") { options "-stub-run" @@ -28,10 +92,23 @@ nextflow_process { } then { - assert process.success - assert process.out.bam - assert process.out.stats - assert process.out.versions + assertAll( + { assert process.success }, + + // All output channels emitted + { assert process.out.bam.size() == 1 }, + { assert process.out.stats.size() == 1 }, + { assert process.out.versions.size() == 1 }, + + // Stub versions contain expected tools + { assert snapshot(process.out.versions).match("versions_stub") }, + + // Stats file exists with stub content + { assert path(process.out.stats[0][1]).exists() }, + + // meta map preserved through stub + { assert process.out.bam[0][0].id == 'test_sample' } + ) } } } diff --git a/pipelines/nf-atacseq/tests/modules/local/wasp2_make_reads.nf.test b/pipelines/nf-atacseq/tests/modules/local/wasp2_make_reads.nf.test index 1ceadaa..79e7969 100644 --- a/pipelines/nf-atacseq/tests/modules/local/wasp2_make_reads.nf.test +++ b/pipelines/nf-atacseq/tests/modules/local/wasp2_make_reads.nf.test @@ -7,14 +7,15 @@ nextflow_process { tag "modules" tag "modules_local" tag "wasp2" + tag "wasp2_make_reads" - test("Should generate swapped-allele reads for remapping - real") { + test("wasp2_make_reads - paired_end - real") { when { process { """ input[0] = [ - [ id:'test_real', single_end:false, sample_name:'SAMPLE1' ], + [ id:'test_real', single_end:false, sample_name:'sample1' ], file("${projectDir}/tests/data/real_test.bam"), file("${projectDir}/tests/data/real_test.bam.bai") ] @@ -25,16 +26,42 @@ nextflow_process { then { assertAll( + // Process completes successfully { assert process.success }, + + // FASTQ outputs: paired-end swapped-allele reads + { assert process.out.fastq.size() == 1 }, + { assert path(process.out.fastq[0][1]).exists() }, + { assert path(process.out.fastq[0][2]).exists() }, + { assert path(process.out.fastq[0][1]).toFile().size() > 0 }, + { assert path(process.out.fastq[0][2]).toFile().size() > 0 }, + + // to_remap BAM: reads that overlap variants and need remapping { assert process.out.to_remap_bam.size() == 1 }, + { assert path(process.out.to_remap_bam[0][1]).exists() }, + { assert path(process.out.to_remap_bam[0][1]).toFile().size() > 0 }, + + // keep BAM: reads that don't overlap variants (pass through) { assert process.out.keep_bam.size() == 1 }, + { assert path(process.out.keep_bam[0][1]).exists() }, + + // JSON: WASP data tracking file for filter-remapped step { assert process.out.json.size() == 1 }, - { assert process.out.versions.size() == 1 } + { assert path(process.out.json[0][1]).exists() }, + { assert path(process.out.json[0][1]).text.length() > 2 }, + + // versions.yml emitted + { assert process.out.versions.size() == 1 }, + { assert path(process.out.versions[0]).text.contains("wasp2") }, + + // meta map preserved + { assert process.out.fastq[0][0].id == 'test_real' }, + { assert process.out.to_remap_bam[0][0].id == 'test_real' } ) } } - test("Should generate swapped-allele reads for remapping - stub") { + test("wasp2_make_reads - paired_end - stub") { options "-stub-run" @@ -42,7 +69,7 @@ nextflow_process { process { """ input[0] = [ - [ id:'test_sample', single_end:false, sample_name:'NA12878' ], + [ id:'test_sample', single_end:false, sample_name:'sample1' ], file("${projectDir}/tests/data/stub_test.bam"), file("${projectDir}/tests/data/stub_test.bam.bai") ] @@ -52,12 +79,22 @@ nextflow_process { } then { - assert process.success - assert process.out.fastq - assert process.out.to_remap_bam - assert process.out.keep_bam - assert process.out.json - assert process.out.versions + assertAll( + { assert process.success }, + + // All output channels emitted + { assert process.out.fastq.size() == 1 }, + { assert process.out.to_remap_bam.size() == 1 }, + { assert process.out.keep_bam.size() == 1 }, + { assert process.out.json.size() == 1 }, + { assert process.out.versions.size() == 1 }, + + // Stub versions contain expected tool + { assert snapshot(process.out.versions).match("versions_stub") }, + + // meta map preserved through stub + { assert process.out.fastq[0][0].id == 'test_sample' } + ) } } } diff --git a/pipelines/nf-atacseq/tests/nextflow.config b/pipelines/nf-atacseq/tests/nextflow.config index 979614b..a87dc0f 100644 --- a/pipelines/nf-atacseq/tests/nextflow.config +++ b/pipelines/nf-atacseq/tests/nextflow.config @@ -3,7 +3,14 @@ * Loaded via nf-test.config configFile directive */ params { - max_cpus = 2 - max_memory = '6.GB' - max_time = '6.h' + modules_testdata_base_path = 'https://raw.githubusercontent.com/nf-core/test-datasets/modules/data/' + pipelines_testdata_base_path = 'https://raw.githubusercontent.com/nf-core/test-datasets/' +} + +process { + resourceLimits = [ + cpus: 2, + memory: 6.GB, + time: 6.h + ] } diff --git a/pipelines/nf-atacseq/workflows/atacseq.nf b/pipelines/nf-atacseq/workflows/atacseq.nf index caeff59..2972171 100644 --- a/pipelines/nf-atacseq/workflows/atacseq.nf +++ b/pipelines/nf-atacseq/workflows/atacseq.nf @@ -53,6 +53,7 @@ workflow ATACSEQ { ch_versions = ch_versions.mix(PREPARE_GENOME.out.versions) ch_fasta = PREPARE_GENOME.out.fasta + ch_fasta_meta = ch_fasta.map { fasta -> [[id: 'genome'], fasta] } ch_vcf = params.vcf ? Channel.fromPath(params.vcf, checkIfExists: true).collect() : Channel.empty() // @@ -60,7 +61,6 @@ workflow ATACSEQ { // if (!params.skip_fastqc) { FASTQC ( ch_samplesheet ) - ch_versions = ch_versions.mix(FASTQC.out.versions.first()) ch_multiqc_files = ch_multiqc_files.mix(FASTQC.out.zip.collect { it[1] }) } @@ -69,13 +69,12 @@ workflow ATACSEQ { // if (!params.skip_trimming) { FASTP ( - ch_samplesheet, - [], // adapter_fasta + ch_samplesheet.map { meta, reads -> [meta, reads, []] }, + false, // discard_trimmed_pass false, // save_trimmed_fail false // save_merged ) ch_reads = FASTP.out.reads - ch_versions = ch_versions.mix(FASTP.out.versions.first()) ch_multiqc_files = ch_multiqc_files.mix(FASTP.out.json.collect { it[1] }) } else { ch_reads = ch_samplesheet @@ -94,8 +93,9 @@ workflow ATACSEQ { if (params.aligner == 'bwa') { FASTQ_ALIGN_BWA ( ch_reads, - PREPARE_GENOME.out.bwa_index, - ch_fasta + PREPARE_GENOME.out.bwa_index.map { index -> [[id: 'genome'], index] }, + true, + ch_fasta_meta ) ch_aligned_bam = FASTQ_ALIGN_BWA.out.bam ch_aligned_bai = FASTQ_ALIGN_BWA.out.bai @@ -107,6 +107,8 @@ workflow ATACSEQ { FASTQ_ALIGN_BOWTIE2 ( ch_reads, PREPARE_GENOME.out.bowtie2_index, + false, + true, ch_fasta ) ch_aligned_bam = FASTQ_ALIGN_BOWTIE2.out.bam @@ -130,16 +132,16 @@ workflow ATACSEQ { // SUBWORKFLOW: Mark duplicates with Picard and run BAM stats (optional) // ch_fasta_fai = PREPARE_GENOME.out.fasta_fai + ch_fasta_fai_meta = ch_fasta_fai.map { fai -> [[id: 'genome'], fai] } if (!params.skip_dedup) { BAM_MARKDUPLICATES_PICARD ( ch_bam_indexed.map { meta, bam, bai -> [meta, bam] }, - ch_fasta, - ch_fasta_fai + ch_fasta_meta, + ch_fasta_fai_meta ) ch_bam_dedup = BAM_MARKDUPLICATES_PICARD.out.bam .join(BAM_MARKDUPLICATES_PICARD.out.bai, by: [0], failOnMismatch: true) - ch_versions = ch_versions.mix(BAM_MARKDUPLICATES_PICARD.out.versions) // Add deduplication stats to MultiQC ch_multiqc_files = ch_multiqc_files.mix(BAM_MARKDUPLICATES_PICARD.out.metrics.collect { it[1] }) @@ -155,7 +157,7 @@ workflow ATACSEQ { // if (!params.skip_peak_calling) { MACS2_CALLPEAK ( - ch_bam_dedup.map { meta, bam, bai -> [meta, bam] }, + ch_bam_dedup.map { meta, bam, bai -> [meta, bam, []] }, params.macs_gsize ) ch_peaks = MACS2_CALLPEAK.out.peak @@ -238,16 +240,14 @@ workflow ATACSEQ { // ch_multiqc_report = Channel.empty() if (!params.skip_multiqc) { - ch_multiqc_config = Channel.fromPath("${projectDir}/assets/multiqc_config.yml", checkIfExists: false).ifEmpty([]) + def multiqc_config_file = file("${projectDir}/assets/multiqc_config.yml") MULTIQC ( - ch_multiqc_files.collect(), - ch_multiqc_config.toList(), - [], // extra_multiqc_config - [] // multiqc_logo + ch_multiqc_files.collect().map { files -> + [ [id: 'multiqc'], files, multiqc_config_file.exists() ? [multiqc_config_file] : [], [], [], [] ] + } ) - ch_multiqc_report = MULTIQC.out.report - ch_versions = ch_versions.mix(MULTIQC.out.versions) + ch_multiqc_report = MULTIQC.out.report.map { meta, report -> report } } emit: diff --git a/pipelines/nf-outrider/conf/test_local.config b/pipelines/nf-outrider/conf/test_local.config index b988b75..7f22492 100644 --- a/pipelines/nf-outrider/conf/test_local.config +++ b/pipelines/nf-outrider/conf/test_local.config @@ -26,3 +26,12 @@ params { outrider_min_samples = 3 outrider_min_count = 1 // Low threshold for simulated test data (~5x coverage) } + +// Override base.config resourceLimits so local workstations don't OOM +process { + resourceLimits = [ + cpus: 2, + memory: 6.GB, + time: 1.h + ] +} diff --git a/pipelines/nf-outrider/nextflow.config b/pipelines/nf-outrider/nextflow.config index a70e9d3..03e66cd 100644 --- a/pipelines/nf-outrider/nextflow.config +++ b/pipelines/nf-outrider/nextflow.config @@ -6,10 +6,17 @@ ---------------------------------------------------------------------------------------- */ +// Plugin configuration +plugins { + id 'nf-validation@1.1.3' +} + // Pipeline metadata manifest { name = 'wasp2/nf-outrider' author = 'WASP2 Team' + homePage = 'https://github.com/mcvickerlab/WASP2' + doi = 'https://doi.org/10.1038/nmeth.3582' description = 'WASP2 + OUTRIDER for aberrant expression and mono-allelic expression detection' mainScript = 'main.nf' nextflowVersion = '!>=23.04.0' @@ -65,11 +72,6 @@ params { // Processing options skip_multiqc = false - // Resource limits - max_cpus = 16 - max_memory = '128.GB' - max_time = '240.h' - // Institutional config support (nf-core compatible) custom_config_base = 'https://raw.githubusercontent.com/nf-core/configs/master' custom_config_version = 'master' @@ -77,6 +79,7 @@ params { // Generic options help = false version = false + validate_params = true tracedir = "${params.outdir}/pipeline_info" } @@ -84,11 +87,11 @@ params { includeConfig 'conf/base.config' includeConfig 'conf/modules.config' -// Load nf-core institutional configs +// Load nf-core custom profiles from https://github.com/nf-core/configs try { - includeConfig "${params.custom_config_base}/nfcore_custom.config" + includeConfig params.custom_config_base && (!System.getenv('NXF_OFFLINE') || !params.custom_config_base.startsWith('http')) ? "${params.custom_config_base}/nfcore_custom.config" : "/dev/null" } catch (Exception e) { - System.err.println("WARNING: Could not load nf-core/configs: ${params.custom_config_base}") + System.err.println("WARNING: Could not load nf-core/config profiles: ${params.custom_config_base}") } // Execution profiles @@ -102,7 +105,6 @@ profiles { conda.enabled = true docker.enabled = false singularity.enabled = false - process.conda = "${projectDir}/../../environment.yml" } docker { docker.enabled = true @@ -158,15 +160,15 @@ profiles { def trace_timestamp = new java.util.Date().format('yyyy-MM-dd_HH-mm-ss') timeline { enabled = true - file = "${params.tracedir}/execution_timeline_${trace_timestamp}.html" + file = "${params.tracedir}/timeline_${trace_timestamp}.html" } report { enabled = true - file = "${params.tracedir}/execution_report_${trace_timestamp}.html" + file = "${params.tracedir}/report_${trace_timestamp}.html" } trace { enabled = true - file = "${params.tracedir}/execution_trace_${trace_timestamp}.txt" + file = "${params.tracedir}/trace_${trace_timestamp}.txt" } dag { enabled = true @@ -196,32 +198,33 @@ process { process.shell = ['/bin/bash', '-euo', 'pipefail'] // Function to ensure resources don't exceed limits +// Resource capping is handled by process.resourceLimits in conf/base.config. +// This function is retained for backward compatibility with process label closures. def check_max(obj, type) { if (type == 'memory') { try { - if (obj.compareTo(params.max_memory as nextflow.util.MemoryUnit) == 1) - return params.max_memory as nextflow.util.MemoryUnit - else - return obj - } catch (all) { - println "WARNING: Invalid max_memory '${params.max_memory}', using default" + def max = (params.max_memory as nextflow.util.MemoryUnit) ?: 128.GB + if (obj.compareTo(max) == 1) + return max + else return obj + } catch (Exception e) { + log.warn "Invalid memory config: ${e.message}. Using ${obj}" return obj } } else if (type == 'time') { try { - if (obj.compareTo(params.max_time as nextflow.util.Duration) == 1) - return params.max_time as nextflow.util.Duration - else - return obj - } catch (all) { - println "WARNING: Invalid max_time '${params.max_time}', using default" + def max = (params.max_time as nextflow.util.Duration) ?: 240.h + if (obj.compareTo(max) == 1) + return max + else return obj + } catch (Exception e) { + log.warn "Invalid time config: ${e.message}. Using ${obj}" return obj } } else if (type == 'cpus') { - try { - return Math.min(obj, params.max_cpus as int) - } catch (all) { - println "WARNING: Invalid max_cpus '${params.max_cpus}', using default" + try { return Math.min(obj, (params.max_cpus ?: 16) as int) } + catch (Exception e) { + log.warn "Invalid CPU config: ${e.message}. Using ${obj}" return obj } } diff --git a/pipelines/nf-rnaseq/conf/test_local.config b/pipelines/nf-rnaseq/conf/test_local.config index 5eaae60..e8063d6 100644 --- a/pipelines/nf-rnaseq/conf/test_local.config +++ b/pipelines/nf-rnaseq/conf/test_local.config @@ -25,3 +25,12 @@ params { // Lower thresholds for small test dataset min_count = 1 } + +// Override base.config resourceLimits so local workstations don't OOM +process { + resourceLimits = [ + cpus: 2, + memory: 4.GB, + time: 2.h + ] +} diff --git a/pipelines/nf-rnaseq/nextflow.config b/pipelines/nf-rnaseq/nextflow.config index f26865d..5625145 100644 --- a/pipelines/nf-rnaseq/nextflow.config +++ b/pipelines/nf-rnaseq/nextflow.config @@ -4,6 +4,22 @@ ======================================================================================== */ +plugins { + id 'nf-validation@1.1.3' +} + +// Pipeline manifest +manifest { + name = 'wasp2/nf-rnaseq' + author = 'WASP2 Team' + homePage = 'https://github.com/mcvickerlab/WASP2' + doi = 'https://doi.org/10.1038/nmeth.3582' + description = 'RNA-seq Allele-Specific Expression (ASE) pipeline with WASP2' + mainScript = 'main.nf' + nextflowVersion = '!>=23.04.0' + version = '1.0.0' +} + // Global default params params { // Pipeline options @@ -39,10 +55,8 @@ params { // ML Output options output_format = null // ML output formats: zarr,parquet,anndata (comma-separated) - // Resource limits - max_cpus = 16 - max_memory = '128.GB' - max_time = '240.h' + // Validation + validate_params = true // Trace directory tracedir = "${params.outdir}/pipeline_info" @@ -56,11 +70,11 @@ params { includeConfig 'conf/base.config' includeConfig 'conf/modules.config' -// Load nf-core institutional configs +// Load nf-core custom profiles from https://github.com/nf-core/configs try { - includeConfig "${params.custom_config_base}/nfcore_custom.config" + includeConfig params.custom_config_base && (!System.getenv('NXF_OFFLINE') || !params.custom_config_base.startsWith('http')) ? "${params.custom_config_base}/nfcore_custom.config" : "/dev/null" } catch (Exception e) { - System.err.println("WARNING: Could not load nf-core/configs: ${params.custom_config_base}") + System.err.println("WARNING: Could not load nf-core/config profiles: ${params.custom_config_base}") } profiles { @@ -74,7 +88,6 @@ profiles { conda.enabled = true docker.enabled = false singularity.enabled = false - process.conda = "${projectDir}/../../environment.yml" } docker { @@ -84,6 +97,13 @@ profiles { singularity.enabled = false } + arm { + // Apple Silicon / ARM64 compatibility — forces linux/amd64 containers + // via Rosetta 2 emulation. Combine with a container profile: + // nextflow run main.nf -profile docker,arm [options] + includeConfig 'conf/arm.config' + } + singularity { singularity.enabled = true singularity.autoMounts = true @@ -136,37 +156,22 @@ profiles { } } -// Container overrides -def wasp2_container = 'ghcr.io/mcvickerlab/wasp2:1.4.0' -def star_container = 'community.wave.seqera.io/library/htslib_samtools_star_gawk:ae438e9a604351a4' -process { - withName: 'WASP2_UNIFIED_MAKE_READS|WASP2_FILTER_REMAPPED|WASP2_COUNT_ALLELES|WASP2_ANALYZE_IMBALANCE|WASP2_ML_OUTPUT' { - container = wasp2_container - } - withName: 'STAR_ALIGN.*' { - container = star_container - } -} - -// Capture exit codes from upstream processes when piping -process.shell = ['/bin/bash', '-euo', 'pipefail'] - // Execution reports def trace_timestamp = new java.util.Date().format('yyyy-MM-dd_HH-mm-ss') timeline { enabled = true - file = "${params.tracedir}/execution_timeline_${trace_timestamp}.html" + file = "${params.tracedir}/timeline_${trace_timestamp}.html" } report { enabled = true - file = "${params.tracedir}/execution_report_${trace_timestamp}.html" + file = "${params.tracedir}/report_${trace_timestamp}.html" } trace { enabled = true - file = "${params.tracedir}/execution_trace_${trace_timestamp}.txt" + file = "${params.tracedir}/trace_${trace_timestamp}.txt" } dag { @@ -181,44 +186,49 @@ env { R_ENVIRON_USER = "/.Renviron" } -// Pipeline manifest -manifest { - name = 'wasp2/nf-rnaseq' - author = 'WASP2 Team' - homePage = 'https://github.com/mcvickerlab/WASP2' - description = 'RNA-seq Allele-Specific Expression (ASE) pipeline with WASP2' - mainScript = 'main.nf' - nextflowVersion = '!>=23.04.0' - version = '1.0.0' +// Container overrides +def wasp2_container = 'ghcr.io/mcvickerlab/wasp2:1.4.0' +def star_container = 'community.wave.seqera.io/library/htslib_samtools_star_gawk:ae438e9a604351a4' +process { + withName: 'WASP2_UNIFIED_MAKE_READS|WASP2_FILTER_REMAPPED|WASP2_COUNT_ALLELES|WASP2_ANALYZE_IMBALANCE|WASP2_ML_OUTPUT' { + container = wasp2_container + } + withName: 'STAR_ALIGN.*' { + container = star_container + } } -// Function to check max resource limits +// Capture exit codes from upstream processes when piping +process.shell = ['/bin/bash', '-euo', 'pipefail'] + +// Function to ensure resources don't exceed limits +// Resource capping is handled by process.resourceLimits in conf/base.config. +// This function is retained for backward compatibility with process label closures. def check_max(obj, type) { if (type == 'memory') { try { - if (obj.compareTo(params.max_memory as nextflow.util.MemoryUnit) == 1) - return params.max_memory as nextflow.util.MemoryUnit - else - return obj - } catch (all) { - println " ### ERROR ### Max memory '${params.max_memory}' is not valid!" + def max = (params.max_memory as nextflow.util.MemoryUnit) ?: 128.GB + if (obj.compareTo(max) == 1) + return max + else return obj + } catch (Exception e) { + log.warn "Invalid memory config: ${e.message}. Using ${obj}" return obj } } else if (type == 'time') { try { - if (obj.compareTo(params.max_time as nextflow.util.Duration) == 1) - return params.max_time as nextflow.util.Duration - else - return obj - } catch (all) { - println " ### ERROR ### Max time '${params.max_time}' is not valid!" + def max = (params.max_time as nextflow.util.Duration) ?: 240.h + if (obj.compareTo(max) == 1) + return max + else return obj + } catch (Exception e) { + log.warn "Invalid time config: ${e.message}. Using ${obj}" return obj } } else if (type == 'cpus') { - try { - return Math.min(obj, params.max_cpus as int) - } catch (all) { - println " ### ERROR ### Max cpus '${params.max_cpus}' is not valid!" + try { return Math.min(obj, (params.max_cpus ?: 16) as int) } + catch (Exception e) { + log.warn "Invalid CPU config: ${e.message}. Using ${obj}" return obj } } diff --git a/pipelines/nf-scatac/conf/test_local.config b/pipelines/nf-scatac/conf/test_local.config index e6bbcd9..a4f54cb 100644 --- a/pipelines/nf-scatac/conf/test_local.config +++ b/pipelines/nf-scatac/conf/test_local.config @@ -23,3 +23,12 @@ params { skip_anndata = false create_zarr = false } + +// Override base.config resourceLimits so local workstations don't OOM +process { + resourceLimits = [ + cpus: 2, + memory: 6.GB, + time: 1.h + ] +} diff --git a/pipelines/nf-scatac/nextflow.config b/pipelines/nf-scatac/nextflow.config index 679d953..0a5352e 100644 --- a/pipelines/nf-scatac/nextflow.config +++ b/pipelines/nf-scatac/nextflow.config @@ -4,9 +4,16 @@ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ +// Plugin configuration +plugins { + id 'nf-validation@1.1.3' +} + manifest { name = 'wasp2/nf-scatac' author = 'WASP2 Team' + homePage = 'https://github.com/mcvickerlab/WASP2' + doi = 'https://doi.org/10.1038/nmeth.3582' description = 'Single-Cell ATAC-seq Allelic Imbalance Pipeline' mainScript = 'main.nf' nextflowVersion = '!>=23.04.0' @@ -36,11 +43,6 @@ params { // ML Output options output_format = null // ML output formats: zarr,parquet,anndata (comma-separated) - // Resource limits - max_cpus = 16 - max_memory = '128.GB' - max_time = '240.h' - // Institutional config support (nf-core compatible) custom_config_base = 'https://raw.githubusercontent.com/nf-core/configs/master' custom_config_version = 'master' @@ -55,11 +57,11 @@ params { includeConfig 'conf/base.config' includeConfig 'conf/modules.config' -// Load nf-core institutional configs +// Load nf-core custom profiles from https://github.com/nf-core/configs try { - includeConfig "${params.custom_config_base}/nfcore_custom.config" + includeConfig params.custom_config_base && (!System.getenv('NXF_OFFLINE') || !params.custom_config_base.startsWith('http')) ? "${params.custom_config_base}/nfcore_custom.config" : "/dev/null" } catch (Exception e) { - System.err.println("WARNING: Could not load nf-core/configs: ${params.custom_config_base}") + System.err.println("WARNING: Could not load nf-core/config profiles: ${params.custom_config_base}") } // Container version - override all WASP2/SCATAC processes to use 1.4.0 @@ -77,6 +79,11 @@ profiles { process.beforeScript = 'echo $HOSTNAME' cleanup = false } + conda { + conda.enabled = true + docker.enabled = false + singularity.enabled = false + } docker { docker.enabled = true conda.enabled = false @@ -92,12 +99,6 @@ profiles { conda.enabled = false docker.enabled = false } - conda { - conda.enabled = true - docker.enabled = false - singularity.enabled = false - process.conda = "${projectDir}/../../environment.yml" - } test { includeConfig 'conf/test.config' } @@ -145,32 +146,43 @@ dag { file = "${params.tracedir}/pipeline_dag_${trace_timestamp}.html" } +// Export these variables to prevent local Python/Perl libs from conflicting +env { + PYTHONNOUSERSITE = 1 + R_PROFILE_USER = "/.Rprofile" + R_ENVIRON_USER = "/.Renviron" +} + process.shell = ['/bin/bash', '-euo', 'pipefail'] -// Resource limit checker with logging for configuration errors +// Function to ensure resources don't exceed limits +// Resource capping is handled by process.resourceLimits in conf/base.config. +// This function is retained for backward compatibility with process label closures. def check_max(obj, type) { if (type == 'memory') { try { - if (obj.compareTo(params.max_memory as nextflow.util.MemoryUnit) == 1) - return params.max_memory as nextflow.util.MemoryUnit + def max = (params.max_memory as nextflow.util.MemoryUnit) ?: 128.GB + if (obj.compareTo(max) == 1) + return max else return obj } catch (Exception e) { - log.warn "Invalid memory config (${obj}, max=${params.max_memory}): ${e.message}. Using ${obj}" + log.warn "Invalid memory config: ${e.message}. Using ${obj}" return obj } } else if (type == 'time') { try { - if (obj.compareTo(params.max_time as nextflow.util.Duration) == 1) - return params.max_time as nextflow.util.Duration + def max = (params.max_time as nextflow.util.Duration) ?: 240.h + if (obj.compareTo(max) == 1) + return max else return obj } catch (Exception e) { - log.warn "Invalid time config (${obj}, max=${params.max_time}): ${e.message}. Using ${obj}" + log.warn "Invalid time config: ${e.message}. Using ${obj}" return obj } } else if (type == 'cpus') { - try { return Math.min(obj, params.max_cpus as int) } + try { return Math.min(obj, (params.max_cpus ?: 16) as int) } catch (Exception e) { - log.warn "Invalid CPU config (${obj}, max=${params.max_cpus}): ${e.message}. Using ${obj}" + log.warn "Invalid CPU config: ${e.message}. Using ${obj}" return obj } }