From 226bfda5a920147df495f3bbdbaf43781694fb7b Mon Sep 17 00:00:00 2001 From: Usman Rashid Date: Mon, 25 Nov 2024 13:23:12 +1300 Subject: [PATCH 01/49] Multiple changes --- .github/include.yaml | 10 +++ .github/workflows/ci.yml | 115 ++++++++++++++++------------ .gitignore | 2 + cleanNXF.sh | 4 + conf/test.config | 13 ++-- main.nf | 21 +++-- modules/local/utils/main.nf | 44 +++++++++++ nextflow.config | 78 +++++++------------ nf-test.config | 10 +++ test/nf-test/nextflow.config | 22 ++++++ test/nf-test/tiny/main.nf.test | 32 ++++++++ test/nf-test/tiny/main.nf.test.snap | 42 ++++++++++ 12 files changed, 284 insertions(+), 109 deletions(-) create mode 100644 .github/include.yaml create mode 100644 modules/local/utils/main.nf create mode 100644 nf-test.config create mode 100644 test/nf-test/nextflow.config create mode 100644 test/nf-test/tiny/main.nf.test create mode 100644 test/nf-test/tiny/main.nf.test.snap diff --git a/.github/include.yaml b/.github/include.yaml new file mode 100644 index 0000000..5d850e1 --- /dev/null +++ b/.github/include.yaml @@ -0,0 +1,10 @@ +".": + - ./.github/workflows/** + - ./nf-test.config +tests: + - ./assets/* + - ./bin/* + - ./conf/* + - ./main.nf + - ./nextflow_schema.json + - ./nextflow.config diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 6c27abd..43d4c6c 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1,78 +1,99 @@ name: EDTA Nextflow CI on: - push: - branches: - - nextflow_reboot pull_request: - branches: - - nextflow_reboot env: NXF_ANSI_LOG: false - NXF_SINGULARITY_CACHEDIR: ${{ github.workspace }}/.singularity - NXF_SINGULARITY_LIBRARYDIR: ${{ github.workspace }}/.singularity + NFT_WORKDIR: "~" + NFT_DIFF: "pdiff" + NFT_DIFF_ARGS: "--line-numbers --expand-tabs=2" concurrency: group: "${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}" cancel-in-progress: true - jobs: + nf-test-changes: + name: Check for changes + runs-on: ubuntu-latest + outputs: + nf_test_files: ${{ steps.list.outputs.components }} + steps: + - uses: actions/checkout@v4.2.1 + with: + fetch-depth: 0 + + - name: List nf-test files + id: list + uses: adamrtalbot/detect-nf-test-changes@v0.0.4 + with: + head: ${{ github.sha }} + base: origin/${{ github.base_ref }} + include: .github/include.yaml + + - name: print list of nf-test files + run: | + echo ${{ steps.list.outputs.components }} + test: - name: Run pipeline with test data - # Only run on push if this is the jguhlin nextflow_reboot branch (merged PRs) - if: "${{ github.event_name != 'push' || (github.event_name == 'push' && github.repository == 'jguhlin/EDTA') }}" + name: ${{ matrix.nf_test_files }} ${{ matrix.profile }} NF-${{ matrix.NXF_VER }} + needs: [nf-test-changes] + if: needs.nf-test-changes.outputs.nf_test_files != '[]' runs-on: ubuntu-latest strategy: + fail-fast: false matrix: - nextflow: - - '24.04.4' + NXF_VER: + - "24.04.2" + + nf_test_files: ["${{ fromJson(needs.nf-test-changes.outputs.nf_test_files) }}"] profile: - - docker - - singularity - - conda + - "docker" steps: - name: Check out pipeline code - uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4 + uses: actions/checkout@v4.2.1 - name: Install Nextflow uses: nf-core/setup-nextflow@v2 with: - version: ${{ matrix.nextflow }} + version: "${{ matrix.NXF_VER }}" - # - name: Disk space cleanup - # uses: jlumbroso/free-disk-space@54081f138730dfa15788a46383842cd2f914a1be # v1.3.1 - # Will be needed with large data sets. Can take long to run + - uses: actions/setup-python@v5.2.0 + with: + python-version: "3.11" + architecture: "x64" - - name: Setup apptainer - if: matrix.profile == 'singularity' - uses: eWaterCycle/setup-apptainer@main - - - name: Set up Singularity - if: matrix.profile == 'singularity' + - name: Install pdiff to see diff between nf-test snapshots run: | - mkdir -p $NXF_SINGULARITY_CACHEDIR - mkdir -p $NXF_SINGULARITY_LIBRARYDIR - - - name: Set up miniconda - if: matrix.profile == 'conda' - uses: conda-incubator/setup-miniconda@a4260408e20b96e80095f42ff7f1a15b27dd94ca # v3 + python -m pip install --upgrade pip + pip install pdiff + + - uses: nf-core/setup-nf-test@v1.1.2 with: - miniconda-version: "latest" - auto-update-conda: true - channels: conda-forge,bioconda + version: 0.9.0 - - name: Conda setup - if: matrix.profile == 'conda' + - name: Disk space cleanup + if: matrix.nf_test_files == 'tests/stub/main.nf.test' + uses: jlumbroso/free-disk-space@v1.3.1 + + - name: Run nf-test run: | - conda clean -a - conda install -n base conda-libmamba-solver - conda config --set solver libmamba - echo $(realpath $CONDA)/condabin >> $GITHUB_PATH - echo $(realpath python) >> $GITHUB_PATH + nf-test test --verbose ${{ matrix.nf_test_files }} --profile "+${{ matrix.profile }}" + + confirm-pass: + runs-on: ubuntu-latest + needs: [test] + if: always() + steps: + - name: All tests ok + if: ${{ !contains(needs.*.result, 'failure') }} + run: exit 0 + - name: One or more tests failed + if: ${{ contains(needs.*.result, 'failure') }} + run: exit 1 - - name: Run pipeline with test data + - name: debug-print + if: always() run: | - nextflow run \ - ${GITHUB_WORKSPACE} \ - -profile ${{ matrix.profile }},test + echo "toJSON(needs) = ${{ toJSON(needs) }}" + echo "toJSON(needs.*.result) = ${{ toJSON(needs.*.result) }}" diff --git a/.gitignore b/.gitignore index 9b1e821..f636970 100644 --- a/.gitignore +++ b/.gitignore @@ -3,3 +3,5 @@ bin/TIR-Learner*/Module3/Maize_model.sav work/ .nextflow.log* .nextflow/* +results/ +.nf-test* diff --git a/cleanNXF.sh b/cleanNXF.sh index 8c64a3e..81b8d85 100755 --- a/cleanNXF.sh +++ b/cleanNXF.sh @@ -9,3 +9,7 @@ do rm -rf "work/$i" done echo "Cleaned work..." + +rm -f .nf-test.log +rm -rf .nf-test +echo "Cleaned nf-test..." diff --git a/conf/test.config b/conf/test.config index 1988af8..e921133 100644 --- a/conf/test.config +++ b/conf/test.config @@ -1,8 +1,11 @@ +process { + resourceLimits = [ + cpus: 4, + memory: '15.GB', + time: '1.h' + ] +} + params { genomes = 'https://raw.githubusercontent.com/nf-core/test-datasets/modules/data/genomics/sarscov2/genome/genome.fasta' - // TODO: Replace with a larger genome as LTRHARVEST does not detect any LTRs on this one - - max_cpus = 2 - max_memory = '6.GB' - max_time = '1.hour' } \ No newline at end of file diff --git a/main.nf b/main.nf index b855de3..d49f489 100755 --- a/main.nf +++ b/main.nf @@ -16,11 +16,6 @@ params.exclude = '' params.maxint = 5000 params.outdir = 'results' -// Max resource options -params.max_cpus = 12 -params.max_memory = '16.GB' -params.max_time = '1.hour' - // TODO: Check inputed repeat libraries, CDS, etc... // TODO: Check exclude file @@ -30,11 +25,12 @@ include { LTRFINDER } from './modules/nf-core/ltrfinder/main include { CAT_CAT } from './modules/nf-core/cat/cat/main.nf' include { LTRRETRIEVER_LTRRETRIEVER } from './modules/nf-core/ltrretriever/ltrretriever/main.nf' include { TIRLEARNER } from './modules/gallvp/tirlearner/main.nf' -// nf-core -v modules -g https://github.com/GallVp/nxf-components.git install include { ANNOSINE } from './modules/gallvp/annosine/main.nf' include { REPEATMODELER_BUILDDATABASE } from './modules/nf-core/repeatmodeler/builddatabase/main.nf' include { REPEATMODELER_REPEATMODELER } from './modules/nf-core/repeatmodeler/repeatmodeler/main.nf' +include { softwareVersionsToYAML } from './modules/local/utils/main.nf' + // Test run: // ./main.nf -profile docker,test // ./main.nf -profile conda,test @@ -48,7 +44,7 @@ workflow { // Create a meta object for each genome ch_meta_genome = ch_genome.map { genome -> - meta = [:] + def meta = [:] meta.id = genome.baseName [ meta, genome ] @@ -152,4 +148,15 @@ workflow { ch_repeatmodeler_fasta = REPEATMODELER_REPEATMODELER.out.fasta ch_versions = ch_versions.mix(REPEATMODELER_REPEATMODELER.out.versions.first()) + + // Function: Save versions + ch_versions_yml = softwareVersionsToYAML(ch_versions) + | collectFile( + storeDir: "${params.outdir}/pipeline_info", + name: 'software_versions.yml', + sort: true, + newLine: true, + cache: false + ) + } diff --git a/modules/local/utils/main.nf b/modules/local/utils/main.nf new file mode 100644 index 0000000..fee91f3 --- /dev/null +++ b/modules/local/utils/main.nf @@ -0,0 +1,44 @@ +// +// Generate workflow version string +// +def getWorkflowVersion() { + def version_string = "" as String + if (workflow.manifest.version) { + def prefix_v = workflow.manifest.version[0] != 'v' ? 'v' : '' + version_string += "${prefix_v}${workflow.manifest.version}" + } + + if (workflow.commitId) { + def git_shortsha = workflow.commitId.substring(0, 7) + version_string += "-g${git_shortsha}" + } + + return version_string +} + +// +// Get software versions for pipeline +// +def processVersionsFromYAML(yaml_file) { + def yaml = new org.yaml.snakeyaml.Yaml() + def versions = yaml.load(yaml_file).collectEntries { k, v -> [k.tokenize(':')[-1], v] } + return yaml.dumpAsMap(versions).trim() +} + +// +// Get workflow version for pipeline +// +def workflowVersionToYAML() { + return """ + Workflow: + ${workflow.manifest.name}: ${getWorkflowVersion()} + Nextflow: ${workflow.nextflow.version} + """.stripIndent().trim() +} + +// +// Get channel of software versions used in pipeline in YAML format +// +def softwareVersionsToYAML(ch_versions) { + return ch_versions.unique().map { version -> processVersionsFromYAML(version) }.unique().mix(Channel.of(workflowVersionToYAML())) +} \ No newline at end of file diff --git a/nextflow.config b/nextflow.config index d0b0bfa..12a89b6 100644 --- a/nextflow.config +++ b/nextflow.config @@ -1,38 +1,38 @@ process { - cpus = { check_max( 1 * task.attempt, 'cpus' ) } - memory = { check_max( 6.GB * task.attempt, 'memory' ) } - time = { check_max( 4.h * task.attempt, 'time' ) } + cpus = { 1 * task.attempt } + memory = { 6.GB * task.attempt } + time = { 4.h * task.attempt } errorStrategy = { task.exitStatus in ((130..145) + 104) ? 'retry' : 'finish' } maxRetries = 1 maxErrors = '-1' withLabel:process_single { - cpus = { check_max( 1 , 'cpus' ) } - memory = { check_max( 6.GB * task.attempt, 'memory' ) } - time = { check_max( 4.h * task.attempt, 'time' ) } + cpus = { 1 } + memory = { 6.GB * task.attempt } + time = { 4.h * task.attempt } } withLabel:process_low { - cpus = { check_max( 2 * task.attempt, 'cpus' ) } - memory = { check_max( 12.GB * task.attempt, 'memory' ) } - time = { check_max( 4.h * task.attempt, 'time' ) } + cpus = { 2 * task.attempt } + memory = { 12.GB * task.attempt } + time = { 4.h * task.attempt } } withLabel:process_medium { - cpus = { check_max( 6 * task.attempt, 'cpus' ) } - memory = { check_max( 36.GB * task.attempt, 'memory' ) } - time = { check_max( 8.h * task.attempt, 'time' ) } + cpus = { 6 * task.attempt } + memory = { 36.GB * task.attempt } + time = { 8.h * task.attempt } } withLabel:process_high { - cpus = { check_max( 12 * task.attempt, 'cpus' ) } - memory = { check_max( 72.GB * task.attempt, 'memory' ) } - time = { check_max( 16.h * task.attempt, 'time' ) } + cpus = { 12 * task.attempt } + memory = { 72.GB * task.attempt } + time = { 16.h * task.attempt } } withLabel:process_long { - time = { check_max( 20.h * task.attempt, 'time' ) } + time = { 20.h * task.attempt } } withLabel:process_high_memory { - memory = { check_max( 200.GB * task.attempt, 'memory' ) } + memory = { 200.GB * task.attempt } } withLabel:error_ignore { errorStrategy = 'ignore' @@ -43,6 +43,15 @@ process { } } +// Max resources +process { + resourceLimits = [ + cpus: 12, + memory: '16.GB', + time: '1.hour' + ] +} + profiles { singularity { singularity.enabled = true @@ -82,40 +91,9 @@ conda { } manifest { + name = 'jguhlin/EDTA' nextflowVersion = '!>=23.04.0' + version = '0.1.0dev' } includeConfig 'conf/modules.config' - -// Function to ensure that resource requirements don't go beyond -// a maximum limit -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! Using default value: $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! Using default value: $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! Using default value: $obj" - return obj - } - } -} \ No newline at end of file diff --git a/nf-test.config b/nf-test.config new file mode 100644 index 0000000..c3a9a1e --- /dev/null +++ b/nf-test.config @@ -0,0 +1,10 @@ +config { + testsDir "." + workDir System.getenv("NFT_WORKDIR") ?: ".nf-test" + configFile "test/nf-test/nextflow.config" + + plugins { + load "nft-bam@0.4.0" + load "nft-utils@0.0.3" + } +} diff --git a/test/nf-test/nextflow.config b/test/nf-test/nextflow.config new file mode 100644 index 0000000..ed1a805 --- /dev/null +++ b/test/nf-test/nextflow.config @@ -0,0 +1,22 @@ +/* +======================================================================================== + Nextflow config file for running tests +======================================================================================== +*/ + +params { + modules_testdata_base_path = 'https://raw.githubusercontent.com/nf-core/test-datasets/modules/data/' +} + +timeline { enabled = false } +report { enabled = false } +trace { enabled = false } +dag { enabled = false } + +process { + resourceLimits = [ + cpus: 4, + memory: '15.GB', + time: '1.h' + ] +} diff --git a/test/nf-test/tiny/main.nf.test b/test/nf-test/tiny/main.nf.test new file mode 100644 index 0000000..5022c49 --- /dev/null +++ b/test/nf-test/tiny/main.nf.test @@ -0,0 +1,32 @@ +nextflow_pipeline { + + name "Test with a tiny genome" + script "main.nf" + + test("tiny genome") { + + when { + params { + genomes = "https://raw.githubusercontent.com/nf-core/test-datasets/modules/data/genomics/sarscov2/genome/genome.fasta" + outdir = "$outputDir" + } + } + + then { + def stable_path = getAllFilesFromDir(params.outdir, false, ['pipeline_info/*.{html,json,txt,yml}'], null, ['**']) + + assertAll( + { assert workflow.success}, + { assert snapshot( + [ + 'successful tasks': workflow.trace.succeeded().size(), + 'versions': removeNextflowVersion("$outputDir/pipeline_info/software_versions.yml"), + 'stable paths': stable_path + ] + ).match() } + ) + } + + } + +} diff --git a/test/nf-test/tiny/main.nf.test.snap b/test/nf-test/tiny/main.nf.test.snap new file mode 100644 index 0000000..a61631f --- /dev/null +++ b/test/nf-test/tiny/main.nf.test.snap @@ -0,0 +1,42 @@ +{ + "tiny genome": { + "content": [ + { + "successful tasks": 7, + "versions": { + "ANNOSINE": { + "annosine": "2.0.7" + }, + "CAT_CAT": { + "pigz": "2.3.4" + }, + "LTRFINDER": { + "LTR_FINDER_parallel": "v1.1", + "ltr_finder": "v1.07" + }, + "LTRHARVEST": { + "LTR_HARVEST_parallel": "v1.1", + "genometools": "1.6.5" + }, + "LTRRETRIEVER_LTRRETRIEVER": { + "LTR_retriever": "v2.9.9" + }, + "TIRLEARNER": { + "TIR-Learner": "v3.0.2 by Tianyu (Sky) Lu (tlu83@wisc.edu) published under GPLv3" + }, + "Workflow": { + "jguhlin/EDTA": "v0.1.0dev" + } + }, + "stable paths": [ + + ] + } + ], + "meta": { + "nf-test": "0.9.0", + "nextflow": "24.10.1" + }, + "timestamp": "2024-11-25T13:20:57.770583" + } +} \ No newline at end of file From 69c12641abdb87c3fea4a3f369664091af04e203 Mon Sep 17 00:00:00 2001 From: Usman Rashid Date: Mon, 25 Nov 2024 14:44:00 +1300 Subject: [PATCH 02/49] Updated test CI --- main.nf | 6 ++++ test/nf-test/small/main.nf.test | 32 +++++++++++++++++++ test/nf-test/small/main.nf.test.snap | 48 ++++++++++++++++++++++++++++ 3 files changed, 86 insertions(+) create mode 100644 test/nf-test/small/main.nf.test create mode 100644 test/nf-test/small/main.nf.test.snap diff --git a/main.nf b/main.nf index d49f489..091684c 100755 --- a/main.nf +++ b/main.nf @@ -150,6 +150,12 @@ workflow { // Function: Save versions + ch_versions = ch_versions + | unique + | map { yml -> + if ( yml ) { yml } + } + ch_versions_yml = softwareVersionsToYAML(ch_versions) | collectFile( storeDir: "${params.outdir}/pipeline_info", diff --git a/test/nf-test/small/main.nf.test b/test/nf-test/small/main.nf.test new file mode 100644 index 0000000..4cb9c26 --- /dev/null +++ b/test/nf-test/small/main.nf.test @@ -0,0 +1,32 @@ +nextflow_pipeline { + + name "Test with a small genome" + script "main.nf" + + test("small genome") { + + when { + params { + genomes = "https://raw.githubusercontent.com/nf-core/test-datasets/modules/data/genomics/eukaryotes/actinidia_chinensis/genome/chr1/genome.fasta.gz" + outdir = "$outputDir" + } + } + + then { + def stable_path = getAllFilesFromDir(params.outdir, false, ['pipeline_info/*.{html,json,txt,yml}'], null, ['**']) + + assertAll( + { assert workflow.success}, + { assert snapshot( + [ + 'successful tasks': workflow.trace.succeeded().size(), + 'versions': removeNextflowVersion("$outputDir/pipeline_info/software_versions.yml"), + 'stable paths': stable_path + ] + ).match() } + ) + } + + } + +} diff --git a/test/nf-test/small/main.nf.test.snap b/test/nf-test/small/main.nf.test.snap new file mode 100644 index 0000000..d53e834 --- /dev/null +++ b/test/nf-test/small/main.nf.test.snap @@ -0,0 +1,48 @@ +{ + "small genome": { + "content": [ + { + "successful tasks": 9, + "versions": { + "ANNOSINE": { + "annosine": "2.0.7" + }, + "CAT_CAT": { + "pigz": "2.3.4" + }, + "LTRFINDER": { + "LTR_FINDER_parallel": "v1.1", + "ltr_finder": "v1.07" + }, + "LTRHARVEST": { + "LTR_HARVEST_parallel": "v1.1", + "genometools": "1.6.5" + }, + "LTRRETRIEVER_LTRRETRIEVER": { + "LTR_retriever": "v2.9.9" + }, + "REPEATMODELER_BUILDDATABASE": { + "repeatmodeler": "2.0.5" + }, + "REPEATMODELER_REPEATMODELER": { + "repeatmodeler": "2.0.5" + }, + "TIRLEARNER": { + "TIR-Learner": "v3.0.2 by Tianyu (Sky) Lu (tlu83@wisc.edu) published under GPLv3" + }, + "Workflow": { + "jguhlin/EDTA": "v0.1.0dev" + } + }, + "stable paths": [ + + ] + } + ], + "meta": { + "nf-test": "0.9.0", + "nextflow": "24.10.1" + }, + "timestamp": "2024-11-25T13:53:22.07666" + } +} \ No newline at end of file From 2bfe387e32dae9378f976b752c9613a0c2bbb2b4 Mon Sep 17 00:00:00 2001 From: Usman Rashid Date: Mon, 25 Nov 2024 15:23:50 +1300 Subject: [PATCH 03/49] Added missing modules, profiles and manifest fields --- .github/workflows/ci.yml | 18 ++- conf/modules.config | 10 +- main.nf | 147 +----------------- modules.json | 10 ++ modules/gallvp/gunzip/environment.yml | 7 + modules/gallvp/gunzip/main.nf | 55 +++++++ modules/gallvp/gunzip/meta.yml | 47 ++++++ modules/gallvp/gunzip/tests/main.nf.test | 121 ++++++++++++++ modules/gallvp/gunzip/tests/main.nf.test.snap | 134 ++++++++++++++++ modules/gallvp/gunzip/tests/nextflow.config | 5 + modules/gallvp/gunzip/tests/tags.yml | 2 + modules/nf-core/gunzip/environment.yml | 7 + modules/nf-core/gunzip/main.nf | 55 +++++++ modules/nf-core/gunzip/meta.yml | 47 ++++++ modules/nf-core/gunzip/tests/main.nf.test | 121 ++++++++++++++ .../nf-core/gunzip/tests/main.nf.test.snap | 134 ++++++++++++++++ modules/nf-core/gunzip/tests/nextflow.config | 5 + modules/nf-core/gunzip/tests/tags.yml | 2 + nextflow.config | 4 +- workflows/edta.nf | 144 +++++++++++++++++ 20 files changed, 920 insertions(+), 155 deletions(-) create mode 100644 modules/gallvp/gunzip/environment.yml create mode 100644 modules/gallvp/gunzip/main.nf create mode 100644 modules/gallvp/gunzip/meta.yml create mode 100644 modules/gallvp/gunzip/tests/main.nf.test create mode 100644 modules/gallvp/gunzip/tests/main.nf.test.snap create mode 100644 modules/gallvp/gunzip/tests/nextflow.config create mode 100644 modules/gallvp/gunzip/tests/tags.yml create mode 100644 modules/nf-core/gunzip/environment.yml create mode 100644 modules/nf-core/gunzip/main.nf create mode 100644 modules/nf-core/gunzip/meta.yml create mode 100644 modules/nf-core/gunzip/tests/main.nf.test create mode 100644 modules/nf-core/gunzip/tests/main.nf.test.snap create mode 100644 modules/nf-core/gunzip/tests/nextflow.config create mode 100644 modules/nf-core/gunzip/tests/tags.yml create mode 100644 workflows/edta.nf diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 43d4c6c..20d4b62 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -7,10 +7,13 @@ env: NFT_WORKDIR: "~" NFT_DIFF: "pdiff" NFT_DIFF_ARGS: "--line-numbers --expand-tabs=2" + NXF_SINGULARITY_CACHEDIR: ${{ github.workspace }}/.singularity + NXF_SINGULARITY_LIBRARYDIR: ${{ github.workspace }}/.singularity concurrency: group: "${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}" cancel-in-progress: true + jobs: nf-test-changes: name: Check for changes @@ -46,8 +49,7 @@ jobs: - "24.04.2" nf_test_files: ["${{ fromJson(needs.nf-test-changes.outputs.nf_test_files) }}"] - profile: - - "docker" + profile: [conda, docker, singularity] steps: - name: Check out pipeline code @@ -71,10 +73,16 @@ jobs: - uses: nf-core/setup-nf-test@v1.1.2 with: version: 0.9.0 + + - name: Setup apptainer + if: matrix.profile == 'singularity' + uses: eWaterCycle/setup-apptainer@main - - name: Disk space cleanup - if: matrix.nf_test_files == 'tests/stub/main.nf.test' - uses: jlumbroso/free-disk-space@v1.3.1 + - name: Set up Singularity + if: matrix.profile == 'singularity' + run: | + mkdir -p $NXF_SINGULARITY_CACHEDIR + mkdir -p $NXF_SINGULARITY_LIBRARYDIR - name: Run nf-test run: | diff --git a/conf/modules.config b/conf/modules.config index c274e02..81d11dc 100644 --- a/conf/modules.config +++ b/conf/modules.config @@ -1,22 +1,22 @@ process { - withName: 'LTRHARVEST' { + withName: 'EDTA:LTRHARVEST' { ext.args = '-size 1000000 -time 300' ext.prefix = { "${meta.id}_ltrharvest" } } - withName: 'LTRFINDER' { + withName: 'EDTA:LTRFINDER' { ext.args = '-harvest_out -size 1000000 -time 300' } - withName: 'CAT_CAT' { + withName: 'EDTA:CAT_CAT' { ext.prefix = { "${meta.id}_ltrharvest_ltrfinder.tabout" } } - withName: 'ANNOSINE' { + withName: 'EDTA:ANNOSINE' { ext.args = '--num_alignments 50000 -rpm 0 --copy_number 3 --shift 100 -auto 1' } - withName: 'REPEATMODELER_REPEATMODELER' { + withName: 'EDTA:REPEATMODELER_REPEATMODELER' { ext.args = '-engine ncbi' } diff --git a/main.nf b/main.nf index 091684c..fa74a8b 100755 --- a/main.nf +++ b/main.nf @@ -16,153 +16,12 @@ params.exclude = '' params.maxint = 5000 params.outdir = 'results' -// TODO: Check inputed repeat libraries, CDS, etc... -// TODO: Check exclude file - -include { SANITIZE_HEADERS } from './modules/local/sanitize/main.nf' -include { LTRHARVEST } from './modules/nf-core/ltrharvest/main.nf' -include { LTRFINDER } from './modules/nf-core/ltrfinder/main.nf' -include { CAT_CAT } from './modules/nf-core/cat/cat/main.nf' -include { LTRRETRIEVER_LTRRETRIEVER } from './modules/nf-core/ltrretriever/ltrretriever/main.nf' -include { TIRLEARNER } from './modules/gallvp/tirlearner/main.nf' -include { ANNOSINE } from './modules/gallvp/annosine/main.nf' -include { REPEATMODELER_BUILDDATABASE } from './modules/nf-core/repeatmodeler/builddatabase/main.nf' -include { REPEATMODELER_REPEATMODELER } from './modules/nf-core/repeatmodeler/repeatmodeler/main.nf' - -include { softwareVersionsToYAML } from './modules/local/utils/main.nf' +include { EDTA } from './workflows/edta.nf' // Test run: // ./main.nf -profile docker,test // ./main.nf -profile conda,test -workflow { - - // Versions channel - ch_versions = Channel.empty() - - - ch_genome = Channel.fromPath(params.genomes) - - // Create a meta object for each genome - ch_meta_genome = ch_genome.map { genome -> - def meta = [:] - meta.id = genome.baseName - - [ meta, genome ] - } - - // MODULE: SANITIZE_HEADERS - SANITIZE_HEADERS ( ch_meta_genome ) - - ch_sanitized_fasta = SANITIZE_HEADERS.out.fasta - - // MODULE: LTRHARVEST - LTRHARVEST ( ch_sanitized_fasta ) - - ch_ltrharvest_gff3 = LTRHARVEST.out.gff3 - ch_ltrharvest_scn = LTRHARVEST.out.scn - - ch_versions = ch_versions.mix(LTRHARVEST.out.versions) - - // MODULE: LTRFINDER - LTRFINDER { ch_sanitized_fasta } - - ch_ltrfinder_gff3 = LTRFINDER.out.gff - ch_ltrfinder_scn = LTRFINDER.out.scn - - ch_versions = ch_versions.mix(LTRFINDER.out.versions) - - // MODULE: CAT_CAT - ch_cat_cat_inputs = ch_ltrharvest_scn - | join(ch_ltrfinder_scn) - | map { meta, harvested, found -> [ meta, [ harvested, found ] ] } - CAT_CAT ( ch_cat_cat_inputs ) - - ch_ltr_candidates = CAT_CAT.out.file_out - ch_versions = ch_versions.mix(CAT_CAT.out.versions.first()) - - // MODULE: LTRRETRIEVER_LTRRETRIEVER - ch_ltrretriever_inputs = ch_sanitized_fasta.join(ch_ltr_candidates) - - LTRRETRIEVER_LTRRETRIEVER ( - ch_ltrretriever_inputs.map { meta, fasta, ltr -> [ meta, fasta ] }, - ch_ltrretriever_inputs.map { meta, fasta, ltr -> ltr }, - [], - [], - [] - ) - - ch_ltrretriever_log = LTRRETRIEVER_LTRRETRIEVER.out.log - ch_pass_list = LTRRETRIEVER_LTRRETRIEVER.out.pass_list - ch_annotation_out = LTRRETRIEVER_LTRRETRIEVER.out.annotation_out - ch_annotation_gff = LTRRETRIEVER_LTRRETRIEVER.out.annotation_gff - ch_ltrlib = LTRRETRIEVER_LTRRETRIEVER.out.ltrlib - ch_versions = ch_versions.mix(LTRRETRIEVER_LTRRETRIEVER.out.versions.first()) - - // MODULE: TIRLEARNER - TIRLEARNER ( - ch_sanitized_fasta, - params.species - ) - - ch_tirlearner_filtered_gff = TIRLEARNER.out.filtered_gff - ch_versions = ch_versions.mix(TIRLEARNER.out.versions.first()) - - // These can also run in parallel - // MODULE: ANNOSINE - ANNOSINE ( - ch_sanitized_fasta, - 3 // mode - ) - - // Currently it's a topic, so need to fix that - ch_versions = ch_versions.mix(ANNOSINE.out.versions) - cb_annosine_seed_sine = ANNOSINE.out.fa - - // MODULE: REPEATMODELER_BUILDDATABASE - ch_repeatmodeler_inputs = ch_sanitized_fasta - | map { meta, fasta -> - def size = fasta.size() - def size_threshold = 100_000 // bytes -> bp - - // TODO: Not the best way to set a size threshould - // but it is simple - // This is needed to avoid, - // Error: Database genome is not large enough ( minimum 40000 bp ) to process with RepeatModeler. - if ( size < size_threshold ) { - log.warn "RepeatModeler is skipped for genome '${meta.id}' as it is smaller than ${size_threshold} bytes" - return null - } - - return [ meta, fasta ] - } - | filter { it } - - REPEATMODELER_BUILDDATABASE ( ch_repeatmodeler_inputs ) - - ch_repeatmodeler_db = REPEATMODELER_BUILDDATABASE.out.db - ch_versions = ch_versions.mix(REPEATMODELER_BUILDDATABASE.out.versions.first()) - - // MODULE: REPEATMODELER_REPEATMODELER - REPEATMODELER_REPEATMODELER ( ch_repeatmodeler_db ) - - ch_repeatmodeler_fasta = REPEATMODELER_REPEATMODELER.out.fasta - ch_versions = ch_versions.mix(REPEATMODELER_REPEATMODELER.out.versions.first()) - - - // Function: Save versions - ch_versions = ch_versions - | unique - | map { yml -> - if ( yml ) { yml } - } - - ch_versions_yml = softwareVersionsToYAML(ch_versions) - | collectFile( - storeDir: "${params.outdir}/pipeline_info", - name: 'software_versions.yml', - sort: true, - newLine: true, - cache: false - ) +workflow { + EDTA() } diff --git a/modules.json b/modules.json index 746eb3b..f9eedd5 100644 --- a/modules.json +++ b/modules.json @@ -10,6 +10,11 @@ "git_sha": "a8939d36280e7d9037c7cf164eeede19e46546a4", "installed_by": ["modules"] }, + "gunzip": { + "branch": "main", + "git_sha": "ae9714c21ede9199a3118e3c20b65484aa73e232", + "installed_by": ["modules"] + }, "tirlearner": { "branch": "main", "git_sha": "a8939d36280e7d9037c7cf164eeede19e46546a4", @@ -26,6 +31,11 @@ "git_sha": "666652151335353eef2fcd58880bcef5bc2928e1", "installed_by": ["modules"] }, + "gunzip": { + "branch": "master", + "git_sha": "666652151335353eef2fcd58880bcef5bc2928e1", + "installed_by": ["modules"] + }, "ltrfinder": { "branch": "master", "git_sha": "666652151335353eef2fcd58880bcef5bc2928e1", diff --git a/modules/gallvp/gunzip/environment.yml b/modules/gallvp/gunzip/environment.yml new file mode 100644 index 0000000..c779485 --- /dev/null +++ b/modules/gallvp/gunzip/environment.yml @@ -0,0 +1,7 @@ +channels: + - conda-forge + - bioconda +dependencies: + - conda-forge::grep=3.11 + - conda-forge::sed=4.8 + - conda-forge::tar=1.34 diff --git a/modules/gallvp/gunzip/main.nf b/modules/gallvp/gunzip/main.nf new file mode 100644 index 0000000..5e67e3b --- /dev/null +++ b/modules/gallvp/gunzip/main.nf @@ -0,0 +1,55 @@ +process GUNZIP { + tag "$archive" + label 'process_single' + + conda "${moduleDir}/environment.yml" + container "${ workflow.containerEngine == 'singularity' && !task.ext.singularity_pull_docker_container ? + 'https://depot.galaxyproject.org/singularity/ubuntu:22.04' : + 'nf-core/ubuntu:22.04' }" + + input: + tuple val(meta), path(archive) + + output: + tuple val(meta), path("$gunzip"), emit: gunzip + path "versions.yml" , emit: versions + + when: + task.ext.when == null || task.ext.when + + script: + def args = task.ext.args ?: '' + def extension = ( archive.toString() - '.gz' ).tokenize('.')[-1] + def name = archive.toString() - '.gz' - ".$extension" + def prefix = task.ext.prefix ?: name + gunzip = prefix + ".$extension" + """ + # Not calling gunzip itself because it creates files + # with the original group ownership rather than the + # default one for that user / the work directory + gzip \\ + -cd \\ + $args \\ + $archive \\ + > $gunzip + + cat <<-END_VERSIONS > versions.yml + "${task.process}": + gunzip: \$(echo \$(gunzip --version 2>&1) | sed 's/^.*(gzip) //; s/ Copyright.*\$//') + END_VERSIONS + """ + + stub: + def args = task.ext.args ?: '' + def extension = ( archive.toString() - '.gz' ).tokenize('.')[-1] + def name = archive.toString() - '.gz' - ".$extension" + def prefix = task.ext.prefix ?: name + gunzip = prefix + ".$extension" + """ + touch $gunzip + cat <<-END_VERSIONS > versions.yml + "${task.process}": + gunzip: \$(echo \$(gunzip --version 2>&1) | sed 's/^.*(gzip) //; s/ Copyright.*\$//') + END_VERSIONS + """ +} diff --git a/modules/gallvp/gunzip/meta.yml b/modules/gallvp/gunzip/meta.yml new file mode 100644 index 0000000..9066c03 --- /dev/null +++ b/modules/gallvp/gunzip/meta.yml @@ -0,0 +1,47 @@ +name: gunzip +description: Compresses and decompresses files. +keywords: + - gunzip + - compression + - decompression +tools: + - gunzip: + description: | + gzip is a file format and a software application used for file compression and decompression. + documentation: https://www.gnu.org/software/gzip/manual/gzip.html + licence: ["GPL-3.0-or-later"] + identifier: "" +input: + - - meta: + type: map + description: | + Optional groovy Map containing meta information + e.g. [ id:'test', single_end:false ] + - archive: + type: file + description: File to be compressed/uncompressed + pattern: "*.*" +output: + - gunzip: + - meta: + type: file + description: Compressed/uncompressed file + pattern: "*.*" + - $gunzip: + type: file + description: Compressed/uncompressed file + pattern: "*.*" + - versions: + - versions.yml: + type: file + description: File containing software versions + pattern: "versions.yml" +authors: + - "@joseespinosa" + - "@drpatelh" + - "@jfy133" +maintainers: + - "@joseespinosa" + - "@drpatelh" + - "@jfy133" + - "@gallvp" diff --git a/modules/gallvp/gunzip/tests/main.nf.test b/modules/gallvp/gunzip/tests/main.nf.test new file mode 100644 index 0000000..f661057 --- /dev/null +++ b/modules/gallvp/gunzip/tests/main.nf.test @@ -0,0 +1,121 @@ +nextflow_process { + + name "Test Process GUNZIP" + script "../main.nf" + process "GUNZIP" + tag "gunzip" + tag "modules_gallvp" + tag "modules" + + test("Should run without failures") { + + when { + params { + outdir = "$outputDir" + } + process { + """ + input[0] = Channel.of([ + [], + 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("Should run without failures - prefix") { + + config './nextflow.config' + + when { + params { + outdir = "$outputDir" + } + process { + """ + input[0] = Channel.of([ + [ id: 'test' ], + 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("Should run without failures - stub") { + + options '-stub' + + when { + params { + outdir = "$outputDir" + } + process { + """ + input[0] = Channel.of([ + [], + 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("Should run without failures - prefix - stub") { + + options '-stub' + config './nextflow.config' + + when { + params { + outdir = "$outputDir" + } + process { + """ + input[0] = Channel.of([ + [ id: 'test' ], + file(params.modules_testdata_base_path + 'genomics/sarscov2/illumina/fastq/test_1.fastq.gz', checkIfExists: true) + ] + ) + """ + } + } + + then { + assertAll( + { assert process.success }, + { assert snapshot(process.out).match() } + ) + } + + } + +} diff --git a/modules/gallvp/gunzip/tests/main.nf.test.snap b/modules/gallvp/gunzip/tests/main.nf.test.snap new file mode 100644 index 0000000..069967e --- /dev/null +++ b/modules/gallvp/gunzip/tests/main.nf.test.snap @@ -0,0 +1,134 @@ +{ + "Should run without failures - prefix - stub": { + "content": [ + { + "0": [ + [ + { + "id": "test" + }, + "test.xyz.fastq:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "1": [ + "versions.yml:md5,54376d32aca20e937a4ec26dac228e84" + ], + "gunzip": [ + [ + { + "id": "test" + }, + "test.xyz.fastq:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "versions": [ + "versions.yml:md5,54376d32aca20e937a4ec26dac228e84" + ] + } + ], + "meta": { + "nf-test": "0.8.4", + "nextflow": "24.04.2" + }, + "timestamp": "2024-06-25T11:35:10.861293" + }, + "Should run without failures - stub": { + "content": [ + { + "0": [ + [ + [ + + ], + "test_1.fastq:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "1": [ + "versions.yml:md5,54376d32aca20e937a4ec26dac228e84" + ], + "gunzip": [ + [ + [ + + ], + "test_1.fastq:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "versions": [ + "versions.yml:md5,54376d32aca20e937a4ec26dac228e84" + ] + } + ], + "meta": { + "nf-test": "0.8.4", + "nextflow": "24.04.2" + }, + "timestamp": "2024-06-25T11:35:05.857145" + }, + "Should run without failures": { + "content": [ + { + "0": [ + [ + [ + + ], + "test_1.fastq:md5,4161df271f9bfcd25d5845a1e220dbec" + ] + ], + "1": [ + "versions.yml:md5,54376d32aca20e937a4ec26dac228e84" + ], + "gunzip": [ + [ + [ + + ], + "test_1.fastq:md5,4161df271f9bfcd25d5845a1e220dbec" + ] + ], + "versions": [ + "versions.yml:md5,54376d32aca20e937a4ec26dac228e84" + ] + } + ], + "meta": { + "nf-test": "0.8.4", + "nextflow": "24.04.2" + }, + "timestamp": "2023-10-17T15:35:37.690477896" + }, + "Should run without failures - prefix": { + "content": [ + { + "0": [ + [ + { + "id": "test" + }, + "test.xyz.fastq:md5,4161df271f9bfcd25d5845a1e220dbec" + ] + ], + "1": [ + "versions.yml:md5,54376d32aca20e937a4ec26dac228e84" + ], + "gunzip": [ + [ + { + "id": "test" + }, + "test.xyz.fastq:md5,4161df271f9bfcd25d5845a1e220dbec" + ] + ], + "versions": [ + "versions.yml:md5,54376d32aca20e937a4ec26dac228e84" + ] + } + ], + "meta": { + "nf-test": "0.8.4", + "nextflow": "24.04.2" + }, + "timestamp": "2024-06-25T11:33:32.921739" + } +} \ No newline at end of file diff --git a/modules/gallvp/gunzip/tests/nextflow.config b/modules/gallvp/gunzip/tests/nextflow.config new file mode 100644 index 0000000..dec7764 --- /dev/null +++ b/modules/gallvp/gunzip/tests/nextflow.config @@ -0,0 +1,5 @@ +process { + withName: GUNZIP { + ext.prefix = { "${meta.id}.xyz" } + } +} diff --git a/modules/gallvp/gunzip/tests/tags.yml b/modules/gallvp/gunzip/tests/tags.yml new file mode 100644 index 0000000..fd3f691 --- /dev/null +++ b/modules/gallvp/gunzip/tests/tags.yml @@ -0,0 +1,2 @@ +gunzip: + - modules/nf-core/gunzip/** diff --git a/modules/nf-core/gunzip/environment.yml b/modules/nf-core/gunzip/environment.yml new file mode 100644 index 0000000..c779485 --- /dev/null +++ b/modules/nf-core/gunzip/environment.yml @@ -0,0 +1,7 @@ +channels: + - conda-forge + - bioconda +dependencies: + - conda-forge::grep=3.11 + - conda-forge::sed=4.8 + - conda-forge::tar=1.34 diff --git a/modules/nf-core/gunzip/main.nf b/modules/nf-core/gunzip/main.nf new file mode 100644 index 0000000..5e67e3b --- /dev/null +++ b/modules/nf-core/gunzip/main.nf @@ -0,0 +1,55 @@ +process GUNZIP { + tag "$archive" + label 'process_single' + + conda "${moduleDir}/environment.yml" + container "${ workflow.containerEngine == 'singularity' && !task.ext.singularity_pull_docker_container ? + 'https://depot.galaxyproject.org/singularity/ubuntu:22.04' : + 'nf-core/ubuntu:22.04' }" + + input: + tuple val(meta), path(archive) + + output: + tuple val(meta), path("$gunzip"), emit: gunzip + path "versions.yml" , emit: versions + + when: + task.ext.when == null || task.ext.when + + script: + def args = task.ext.args ?: '' + def extension = ( archive.toString() - '.gz' ).tokenize('.')[-1] + def name = archive.toString() - '.gz' - ".$extension" + def prefix = task.ext.prefix ?: name + gunzip = prefix + ".$extension" + """ + # Not calling gunzip itself because it creates files + # with the original group ownership rather than the + # default one for that user / the work directory + gzip \\ + -cd \\ + $args \\ + $archive \\ + > $gunzip + + cat <<-END_VERSIONS > versions.yml + "${task.process}": + gunzip: \$(echo \$(gunzip --version 2>&1) | sed 's/^.*(gzip) //; s/ Copyright.*\$//') + END_VERSIONS + """ + + stub: + def args = task.ext.args ?: '' + def extension = ( archive.toString() - '.gz' ).tokenize('.')[-1] + def name = archive.toString() - '.gz' - ".$extension" + def prefix = task.ext.prefix ?: name + gunzip = prefix + ".$extension" + """ + touch $gunzip + cat <<-END_VERSIONS > versions.yml + "${task.process}": + gunzip: \$(echo \$(gunzip --version 2>&1) | sed 's/^.*(gzip) //; s/ Copyright.*\$//') + END_VERSIONS + """ +} diff --git a/modules/nf-core/gunzip/meta.yml b/modules/nf-core/gunzip/meta.yml new file mode 100644 index 0000000..9066c03 --- /dev/null +++ b/modules/nf-core/gunzip/meta.yml @@ -0,0 +1,47 @@ +name: gunzip +description: Compresses and decompresses files. +keywords: + - gunzip + - compression + - decompression +tools: + - gunzip: + description: | + gzip is a file format and a software application used for file compression and decompression. + documentation: https://www.gnu.org/software/gzip/manual/gzip.html + licence: ["GPL-3.0-or-later"] + identifier: "" +input: + - - meta: + type: map + description: | + Optional groovy Map containing meta information + e.g. [ id:'test', single_end:false ] + - archive: + type: file + description: File to be compressed/uncompressed + pattern: "*.*" +output: + - gunzip: + - meta: + type: file + description: Compressed/uncompressed file + pattern: "*.*" + - $gunzip: + type: file + description: Compressed/uncompressed file + pattern: "*.*" + - versions: + - versions.yml: + type: file + description: File containing software versions + pattern: "versions.yml" +authors: + - "@joseespinosa" + - "@drpatelh" + - "@jfy133" +maintainers: + - "@joseespinosa" + - "@drpatelh" + - "@jfy133" + - "@gallvp" diff --git a/modules/nf-core/gunzip/tests/main.nf.test b/modules/nf-core/gunzip/tests/main.nf.test new file mode 100644 index 0000000..776211a --- /dev/null +++ b/modules/nf-core/gunzip/tests/main.nf.test @@ -0,0 +1,121 @@ +nextflow_process { + + name "Test Process GUNZIP" + script "../main.nf" + process "GUNZIP" + tag "gunzip" + tag "modules_nfcore" + tag "modules" + + test("Should run without failures") { + + when { + params { + outdir = "$outputDir" + } + process { + """ + input[0] = Channel.of([ + [], + 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("Should run without failures - prefix") { + + config './nextflow.config' + + when { + params { + outdir = "$outputDir" + } + process { + """ + input[0] = Channel.of([ + [ id: 'test' ], + 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("Should run without failures - stub") { + + options '-stub' + + when { + params { + outdir = "$outputDir" + } + process { + """ + input[0] = Channel.of([ + [], + 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("Should run without failures - prefix - stub") { + + options '-stub' + config './nextflow.config' + + when { + params { + outdir = "$outputDir" + } + process { + """ + input[0] = Channel.of([ + [ id: 'test' ], + file(params.modules_testdata_base_path + 'genomics/sarscov2/illumina/fastq/test_1.fastq.gz', checkIfExists: true) + ] + ) + """ + } + } + + then { + assertAll( + { assert process.success }, + { assert snapshot(process.out).match() } + ) + } + + } + +} diff --git a/modules/nf-core/gunzip/tests/main.nf.test.snap b/modules/nf-core/gunzip/tests/main.nf.test.snap new file mode 100644 index 0000000..069967e --- /dev/null +++ b/modules/nf-core/gunzip/tests/main.nf.test.snap @@ -0,0 +1,134 @@ +{ + "Should run without failures - prefix - stub": { + "content": [ + { + "0": [ + [ + { + "id": "test" + }, + "test.xyz.fastq:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "1": [ + "versions.yml:md5,54376d32aca20e937a4ec26dac228e84" + ], + "gunzip": [ + [ + { + "id": "test" + }, + "test.xyz.fastq:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "versions": [ + "versions.yml:md5,54376d32aca20e937a4ec26dac228e84" + ] + } + ], + "meta": { + "nf-test": "0.8.4", + "nextflow": "24.04.2" + }, + "timestamp": "2024-06-25T11:35:10.861293" + }, + "Should run without failures - stub": { + "content": [ + { + "0": [ + [ + [ + + ], + "test_1.fastq:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "1": [ + "versions.yml:md5,54376d32aca20e937a4ec26dac228e84" + ], + "gunzip": [ + [ + [ + + ], + "test_1.fastq:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "versions": [ + "versions.yml:md5,54376d32aca20e937a4ec26dac228e84" + ] + } + ], + "meta": { + "nf-test": "0.8.4", + "nextflow": "24.04.2" + }, + "timestamp": "2024-06-25T11:35:05.857145" + }, + "Should run without failures": { + "content": [ + { + "0": [ + [ + [ + + ], + "test_1.fastq:md5,4161df271f9bfcd25d5845a1e220dbec" + ] + ], + "1": [ + "versions.yml:md5,54376d32aca20e937a4ec26dac228e84" + ], + "gunzip": [ + [ + [ + + ], + "test_1.fastq:md5,4161df271f9bfcd25d5845a1e220dbec" + ] + ], + "versions": [ + "versions.yml:md5,54376d32aca20e937a4ec26dac228e84" + ] + } + ], + "meta": { + "nf-test": "0.8.4", + "nextflow": "24.04.2" + }, + "timestamp": "2023-10-17T15:35:37.690477896" + }, + "Should run without failures - prefix": { + "content": [ + { + "0": [ + [ + { + "id": "test" + }, + "test.xyz.fastq:md5,4161df271f9bfcd25d5845a1e220dbec" + ] + ], + "1": [ + "versions.yml:md5,54376d32aca20e937a4ec26dac228e84" + ], + "gunzip": [ + [ + { + "id": "test" + }, + "test.xyz.fastq:md5,4161df271f9bfcd25d5845a1e220dbec" + ] + ], + "versions": [ + "versions.yml:md5,54376d32aca20e937a4ec26dac228e84" + ] + } + ], + "meta": { + "nf-test": "0.8.4", + "nextflow": "24.04.2" + }, + "timestamp": "2024-06-25T11:33:32.921739" + } +} \ No newline at end of file diff --git a/modules/nf-core/gunzip/tests/nextflow.config b/modules/nf-core/gunzip/tests/nextflow.config new file mode 100644 index 0000000..dec7764 --- /dev/null +++ b/modules/nf-core/gunzip/tests/nextflow.config @@ -0,0 +1,5 @@ +process { + withName: GUNZIP { + ext.prefix = { "${meta.id}.xyz" } + } +} diff --git a/modules/nf-core/gunzip/tests/tags.yml b/modules/nf-core/gunzip/tests/tags.yml new file mode 100644 index 0000000..fd3f691 --- /dev/null +++ b/modules/nf-core/gunzip/tests/tags.yml @@ -0,0 +1,2 @@ +gunzip: + - modules/nf-core/gunzip/** diff --git a/nextflow.config b/nextflow.config index 12a89b6..34dfae1 100644 --- a/nextflow.config +++ b/nextflow.config @@ -92,8 +92,10 @@ conda { manifest { name = 'jguhlin/EDTA' - nextflowVersion = '!>=23.04.0' + description = 'Extensive de-novo TE Annotator on Nextflow' + manifest.author = 'Usman Rashid, Joseph Guhlin & Shujun Ou' version = '0.1.0dev' + nextflowVersion = '!>=23.04.0' } includeConfig 'conf/modules.config' diff --git a/workflows/edta.nf b/workflows/edta.nf new file mode 100644 index 0000000..df8b2d9 --- /dev/null +++ b/workflows/edta.nf @@ -0,0 +1,144 @@ +include { SANITIZE_HEADERS } from '../modules/local/sanitize/main.nf' +include { LTRHARVEST } from '../modules/nf-core/ltrharvest/main.nf' +include { LTRFINDER } from '../modules/nf-core/ltrfinder/main.nf' +include { CAT_CAT } from '../modules/nf-core/cat/cat/main.nf' +include { LTRRETRIEVER_LTRRETRIEVER } from '../modules/nf-core/ltrretriever/ltrretriever/main.nf' +include { TIRLEARNER } from '../modules/gallvp/tirlearner/main.nf' +include { ANNOSINE } from '../modules/gallvp/annosine/main.nf' +include { REPEATMODELER_BUILDDATABASE } from '../modules/nf-core/repeatmodeler/builddatabase/main.nf' +include { REPEATMODELER_REPEATMODELER } from '../modules/nf-core/repeatmodeler/repeatmodeler/main.nf' + +include { softwareVersionsToYAML } from '../modules/local/utils/main.nf' + +workflow EDTA { + + // Versions channel + ch_versions = Channel.empty() + + + ch_genome = Channel.fromPath(params.genomes) + + // Create a meta object for each genome + ch_meta_genome = ch_genome.map { genome -> + def meta = [:] + meta.id = genome.baseName + + [ meta, genome ] + } + + // MODULE: SANITIZE_HEADERS + SANITIZE_HEADERS ( ch_meta_genome ) + + ch_sanitized_fasta = SANITIZE_HEADERS.out.fasta + + // MODULE: LTRHARVEST + LTRHARVEST ( ch_sanitized_fasta ) + + ch_ltrharvest_gff3 = LTRHARVEST.out.gff3 + ch_ltrharvest_scn = LTRHARVEST.out.scn + + ch_versions = ch_versions.mix(LTRHARVEST.out.versions) + + // MODULE: LTRFINDER + LTRFINDER { ch_sanitized_fasta } + + ch_ltrfinder_gff3 = LTRFINDER.out.gff + ch_ltrfinder_scn = LTRFINDER.out.scn + + ch_versions = ch_versions.mix(LTRFINDER.out.versions) + + // MODULE: CAT_CAT + ch_cat_cat_inputs = ch_ltrharvest_scn + | join(ch_ltrfinder_scn) + | map { meta, harvested, found -> [ meta, [ harvested, found ] ] } + CAT_CAT ( ch_cat_cat_inputs ) + + ch_ltr_candidates = CAT_CAT.out.file_out + ch_versions = ch_versions.mix(CAT_CAT.out.versions.first()) + + // MODULE: LTRRETRIEVER_LTRRETRIEVER + ch_ltrretriever_inputs = ch_sanitized_fasta.join(ch_ltr_candidates) + + LTRRETRIEVER_LTRRETRIEVER ( + ch_ltrretriever_inputs.map { meta, fasta, ltr -> [ meta, fasta ] }, + ch_ltrretriever_inputs.map { meta, fasta, ltr -> ltr }, + [], + [], + [] + ) + + ch_ltrretriever_log = LTRRETRIEVER_LTRRETRIEVER.out.log + ch_pass_list = LTRRETRIEVER_LTRRETRIEVER.out.pass_list + ch_annotation_out = LTRRETRIEVER_LTRRETRIEVER.out.annotation_out + ch_annotation_gff = LTRRETRIEVER_LTRRETRIEVER.out.annotation_gff + ch_ltrlib = LTRRETRIEVER_LTRRETRIEVER.out.ltrlib + ch_versions = ch_versions.mix(LTRRETRIEVER_LTRRETRIEVER.out.versions.first()) + + // MODULE: TIRLEARNER + TIRLEARNER ( + ch_sanitized_fasta, + params.species + ) + + ch_tirlearner_filtered_gff = TIRLEARNER.out.filtered_gff + ch_versions = ch_versions.mix(TIRLEARNER.out.versions.first()) + + // These can also run in parallel + // MODULE: ANNOSINE + ANNOSINE ( + ch_sanitized_fasta, + 3 // mode + ) + + // Currently it's a topic, so need to fix that + ch_versions = ch_versions.mix(ANNOSINE.out.versions) + cb_annosine_seed_sine = ANNOSINE.out.fa + + // MODULE: REPEATMODELER_BUILDDATABASE + ch_repeatmodeler_inputs = ch_sanitized_fasta + | map { meta, fasta -> + def size = fasta.size() + def size_threshold = 100_000 // bytes -> bp + + // TODO: Not the best way to set a size threshould + // but it is simple + // This is needed to avoid, + // Error: Database genome is not large enough ( minimum 40000 bp ) to process with RepeatModeler. + if ( size < size_threshold ) { + log.warn "RepeatModeler is skipped for genome '${meta.id}' as it is smaller than ${size_threshold} bytes" + return null + } + + return [ meta, fasta ] + } + | filter { it } + + REPEATMODELER_BUILDDATABASE ( ch_repeatmodeler_inputs ) + + ch_repeatmodeler_db = REPEATMODELER_BUILDDATABASE.out.db + ch_versions = ch_versions.mix(REPEATMODELER_BUILDDATABASE.out.versions.first()) + + // MODULE: REPEATMODELER_REPEATMODELER + REPEATMODELER_REPEATMODELER ( ch_repeatmodeler_db ) + + ch_repeatmodeler_fasta = REPEATMODELER_REPEATMODELER.out.fasta + ch_versions = ch_versions.mix(REPEATMODELER_REPEATMODELER.out.versions.first()) + + + // Function: Save versions + ch_versions = ch_versions + | unique + | map { yml -> + if ( yml ) { yml } + } + + ch_versions_yml = softwareVersionsToYAML(ch_versions) + | collectFile( + storeDir: "${params.outdir}/pipeline_info", + name: 'software_versions.yml', + sort: true, + newLine: true, + cache: false + ) + +} \ No newline at end of file From 3c9b2f81744b3479ab8ff506811c803fbefa3e5e Mon Sep 17 00:00:00 2001 From: Usman Rashid Date: Mon, 25 Nov 2024 15:41:03 +1300 Subject: [PATCH 04/49] Now using EDTA's test genome for the small test --- test/nf-test/small/main.nf.test | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/nf-test/small/main.nf.test b/test/nf-test/small/main.nf.test index 4cb9c26..7d40a02 100644 --- a/test/nf-test/small/main.nf.test +++ b/test/nf-test/small/main.nf.test @@ -7,7 +7,7 @@ nextflow_pipeline { when { params { - genomes = "https://raw.githubusercontent.com/nf-core/test-datasets/modules/data/genomics/eukaryotes/actinidia_chinensis/genome/chr1/genome.fasta.gz" + genomes = "https://raw.githubusercontent.com/jguhlin/EDTA/a2cd9a0777e4ac6e39545bacc3e752f94eb2f389/test/genome.fa" outdir = "$outputDir" } } From 4b7bc1bf6ba3aaf62ba6223b369ae93acc387e5d Mon Sep 17 00:00:00 2001 From: Usman Rashid Date: Tue, 26 Nov 2024 21:42:17 +1300 Subject: [PATCH 05/49] Updated PR template --- .github/PULL_REQUEST_TEMPLATE.md | 11 +++++------ nextflow.config | 2 +- 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index a78bd61..7919df0 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -1,19 +1,18 @@ ## PR checklist -- [ ] PR to `nextflow_reboot` branch -- [ ] `conda` and `container` directives. -- [ ] Docker container + singularity container (optional) +For Nextflow implementation, + +- [ ] `conda` and `container` directives are included for each process +- [ ] Docker container + singularity container (optional) are included for each process - [ ] Flow `meta.id` with each data channel - [ ] Use nf-core resource labels such as `process_high` - [ ] Used nf-core module diff --git a/nextflow.config b/nextflow.config index 34dfae1..ccf5850 100644 --- a/nextflow.config +++ b/nextflow.config @@ -93,7 +93,7 @@ conda { manifest { name = 'jguhlin/EDTA' description = 'Extensive de-novo TE Annotator on Nextflow' - manifest.author = 'Usman Rashid, Joseph Guhlin & Shujun Ou' + author = 'Usman Rashid, Joseph Guhlin & Shujun Ou' version = '0.1.0dev' nextflowVersion = '!>=23.04.0' } From 0195b02da466f55d3a6bf85abcf1fd03a9b204b1 Mon Sep 17 00:00:00 2001 From: Usman Rashid Date: Tue, 26 Nov 2024 22:21:39 +1300 Subject: [PATCH 06/49] Added fasta_helitronscanner_scan_draw --- conf/modules.config | 19 +++ modules.json | 19 +++ .../helitronscanner/draw/environment.yml | 7 + modules/gallvp/helitronscanner/draw/main.nf | 73 ++++++++ modules/gallvp/helitronscanner/draw/meta.yml | 69 ++++++++ .../helitronscanner/draw/tests/main.nf.test | 159 ++++++++++++++++++ .../draw/tests/main.nf.test.snap | 80 +++++++++ .../draw/tests/nextflow.config | 5 + .../helitronscanner/scan/environment.yml | 5 + modules/gallvp/helitronscanner/scan/main.nf | 72 ++++++++ modules/gallvp/helitronscanner/scan/meta.yml | 60 +++++++ .../helitronscanner/scan/tests/main.nf.test | 148 ++++++++++++++++ .../scan/tests/main.nf.test.snap | 104 ++++++++++++ .../fasta_helitronscanner_scan_draw/main.nf | 86 ++++++++++ .../fasta_helitronscanner_scan_draw/meta.yml | 45 +++++ .../tests/main.nf.test | 97 +++++++++++ .../tests/main.nf.test.snap | 137 +++++++++++++++ .../tests/nextflow.config | 20 +++ workflows/edta.nf | 26 +-- 19 files changed, 1220 insertions(+), 11 deletions(-) create mode 100644 modules/gallvp/helitronscanner/draw/environment.yml create mode 100644 modules/gallvp/helitronscanner/draw/main.nf create mode 100644 modules/gallvp/helitronscanner/draw/meta.yml create mode 100644 modules/gallvp/helitronscanner/draw/tests/main.nf.test create mode 100644 modules/gallvp/helitronscanner/draw/tests/main.nf.test.snap create mode 100644 modules/gallvp/helitronscanner/draw/tests/nextflow.config create mode 100644 modules/gallvp/helitronscanner/scan/environment.yml create mode 100644 modules/gallvp/helitronscanner/scan/main.nf create mode 100644 modules/gallvp/helitronscanner/scan/meta.yml create mode 100644 modules/gallvp/helitronscanner/scan/tests/main.nf.test create mode 100644 modules/gallvp/helitronscanner/scan/tests/main.nf.test.snap create mode 100644 subworkflows/gallvp/fasta_helitronscanner_scan_draw/main.nf create mode 100644 subworkflows/gallvp/fasta_helitronscanner_scan_draw/meta.yml create mode 100644 subworkflows/gallvp/fasta_helitronscanner_scan_draw/tests/main.nf.test create mode 100644 subworkflows/gallvp/fasta_helitronscanner_scan_draw/tests/main.nf.test.snap create mode 100644 subworkflows/gallvp/fasta_helitronscanner_scan_draw/tests/nextflow.config diff --git a/conf/modules.config b/conf/modules.config index 81d11dc..e70a6ea 100644 --- a/conf/modules.config +++ b/conf/modules.config @@ -19,5 +19,24 @@ process { withName: 'EDTA:REPEATMODELER_REPEATMODELER' { ext.args = '-engine ncbi' } + + withName: 'EDTA::FASTA_HELITRONSCANNER_SCAN_DRAW:HELITRONSCANNER_DRAW' { + ext.args = '-pure_helitron' + } + + withName: 'EDTA::FASTA_HELITRONSCANNER_SCAN_DRAW:HELITRONSCANNER_SCAN_HEAD_RC' { + ext.prefix = { "${meta.id}.rc" } + ext.args = '--rc' + } + + withName: 'EDTA::FASTA_HELITRONSCANNER_SCAN_DRAW:HELITRONSCANNER_SCAN_TAIL_RC' { + ext.prefix = { "${meta.id}.rc" } + ext.args = '--rc' + } + + withName: 'EDTA::FASTA_HELITRONSCANNER_SCAN_DRAW:HELITRONSCANNER_DRAW_RC' { + ext.prefix = { "${meta.id}.rc" } + ext.args = '-pure_helitron' + } } \ No newline at end of file diff --git a/modules.json b/modules.json index f9eedd5..5d34d4e 100644 --- a/modules.json +++ b/modules.json @@ -15,12 +15,31 @@ "git_sha": "ae9714c21ede9199a3118e3c20b65484aa73e232", "installed_by": ["modules"] }, + "helitronscanner/draw": { + "branch": "main", + "git_sha": "929d59d82f2e90fe79eb6f93d1ae739f22a894e1", + "installed_by": ["fasta_helitronscanner_scan_draw"] + }, + "helitronscanner/scan": { + "branch": "main", + "git_sha": "929d59d82f2e90fe79eb6f93d1ae739f22a894e1", + "installed_by": ["fasta_helitronscanner_scan_draw"] + }, "tirlearner": { "branch": "main", "git_sha": "a8939d36280e7d9037c7cf164eeede19e46546a4", "installed_by": ["modules"] } } + }, + "subworkflows": { + "gallvp": { + "fasta_helitronscanner_scan_draw": { + "branch": "main", + "git_sha": "970e3af38229845dd38c13d29b0905651a8e61f0", + "installed_by": ["subworkflows"] + } + } } }, "https://github.com/nf-core/modules.git": { diff --git a/modules/gallvp/helitronscanner/draw/environment.yml b/modules/gallvp/helitronscanner/draw/environment.yml new file mode 100644 index 0000000..479f61c --- /dev/null +++ b/modules/gallvp/helitronscanner/draw/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::helitronscanner=1.0" diff --git a/modules/gallvp/helitronscanner/draw/main.nf b/modules/gallvp/helitronscanner/draw/main.nf new file mode 100644 index 0000000..16ee59a --- /dev/null +++ b/modules/gallvp/helitronscanner/draw/main.nf @@ -0,0 +1,73 @@ +process HELITRONSCANNER_DRAW { + tag "$meta.id" + label 'process_high' + + conda "${moduleDir}/environment.yml" + container "${ workflow.containerEngine == 'singularity' && !task.ext.singularity_pull_docker_container ? + 'https://depot.galaxyproject.org/singularity/helitronscanner:1.0--hdfd78af_0': + 'biocontainers/helitronscanner:1.0--hdfd78af_0' }" + + input: + tuple val(meta), path(fasta) + tuple val(meta2), path(head) + tuple val(meta3), path(tail) + + output: + tuple val(meta), path("*.draw") , emit: draw + path "versions.yml" , emit: versions + + when: + task.ext.when == null || task.ext.when + + script: + def args = task.ext.args ?: '' + def args2 = task.ext.args2 ?: '' + def prefix = task.ext.prefix ?: "${meta.id}" + if ( !task.memory ) { error '[HELITRONSCANNER_DRAW] Available memory not known. Specify process memory requirements to fix this.' } + def avail_mem = (task.memory.giga*0.8).intValue() + """ + # Nextflow changes the container --entrypoint to /bin/bash (container default entrypoint: /usr/local/env-execute) + # Check for container variable initialisation script and source it. + if [ -f "/usr/local/env-activate.sh" ]; then + set +u # Otherwise, errors out because of various unbound variables + . "/usr/local/env-activate.sh" + set -u + fi + + HelitronScanner \\ + pairends \\ + -Xmx${avail_mem}g \\ + -head_score $head \\ + -tail_score $tail \\ + -output ${prefix}.pairends \\ + ${args2} + + HelitronScanner \\ + draw \\ + -Xmx${avail_mem}g \\ + -pscore ${prefix}.pairends \\ + -g $fasta \\ + -output ${prefix}.draw \\ + ${args} + + mv ${prefix}.draw.hel.fa \\ + ${prefix}.draw + + cat <<-END_VERSIONS > versions.yml + "${task.process}": + helitronscanner: \$(HelitronScanner |& sed -n 's/HelitronScanner V\\(.*\\)/V\\1/p') + END_VERSIONS + """ + + stub: + def args = task.ext.args ?: '' + def prefix = task.ext.prefix ?: "${meta.id}" + """ + touch ${prefix}.draw + + cat <<-END_VERSIONS > versions.yml + "${task.process}": + helitronscanner: \$(HelitronScanner |& sed -n 's/HelitronScanner V\\(.*\\)/V\\1/p') + END_VERSIONS + """ +} diff --git a/modules/gallvp/helitronscanner/draw/meta.yml b/modules/gallvp/helitronscanner/draw/meta.yml new file mode 100644 index 0000000..96bd260 --- /dev/null +++ b/modules/gallvp/helitronscanner/draw/meta.yml @@ -0,0 +1,69 @@ +# yaml-language-server: $schema=https://raw.githubusercontent.com/nf-core/modules/master/modules/meta-schema.json +name: "helitronscanner_draw" +description: HelitronScanner draw tool for Helitron transposons in genomes +keywords: + - genomics + - helitron + - scanner +tools: + - "helitronscanner": + description: "HelitronScanner uncovers a large overlooked cache of Helitron transposons + in many genomes" + homepage: "https://sourceforge.net/projects/helitronscanner" + documentation: "https://sourceforge.net/projects/helitronscanner" + tool_dev_url: "https://sourceforge.net/projects/helitronscanner" + doi: "10.1073/pnas.1410068111" + licence: ["GPL v3-or-later"] + identifier: "" + +input: + - - meta: + type: map + description: | + Groovy Map containing sample information + e.g. `[ id:'sample1' ]` + - fasta: + type: file + description: Genome data to scan for Helitrons + pattern: "*.{fa,fsa,fasta}" + - - meta2: + type: map + description: | + Groovy Map containing sample information + e.g. `[ id:'sample1' ]` + - head: + type: file + description: Output of the HelitronScanner head command + pattern: "*.head" + - - meta3: + type: map + description: | + Groovy Map containing sample information + e.g. `[ id:'sample1' ]` + - tail: + type: file + description: Output of the HelitronScanner tail command + pattern: "*.tail" +output: + - draw: + - meta: + type: map + description: | + Groovy Map containing sample information + e.g. `[ id:'sample1', single_end:false ]` + pattern: "*.draw" + - "*.draw": + type: map + description: | + The draw output from HelitronScanner + pattern: "*.draw" + - versions: + - versions.yml: + type: file + description: File containing software versions + pattern: "versions.yml" +authors: + - "@GallVp" + - "@jguhlin" +maintainers: + - "@GallVp" diff --git a/modules/gallvp/helitronscanner/draw/tests/main.nf.test b/modules/gallvp/helitronscanner/draw/tests/main.nf.test new file mode 100644 index 0000000..331f563 --- /dev/null +++ b/modules/gallvp/helitronscanner/draw/tests/main.nf.test @@ -0,0 +1,159 @@ +nextflow_process { + + name "Test Process HELITRONSCANNER_DRAW" + script "../main.nf" + config "./nextflow.config" + process "HELITRONSCANNER_DRAW" + + tag "modules" + tag "modules_gallvp" + tag "helitronscanner" + tag "helitronscanner/draw" + tag "helitronscanner/scan" + tag "gunzip" + + setup { + run('GUNZIP') { + script "../../../gunzip/main.nf" + + process { + """ + input[0] = [ + [ id:'test' ], // meta map + file(params.modules_testdata_base_path + 'genomics/eukaryotes/actinidia_chinensis/genome/chr1/genome.fasta.gz', checkIfExists: true) + ] + """ + } + } + + run('HELITRONSCANNER_SCAN', alias: 'HELITRONSCANNER_SCANHEAD') { + script "../../scan/main.nf" + + process { + """ + input[0] = GUNZIP.out.gunzip + input[1] = 'head' + input[2] = [] + input[3] = 0 + """ + } + } + + run('HELITRONSCANNER_SCAN', alias: 'HELITRONSCANNER_SCANTAIL') { + script "../../scan/main.nf" + + process { + """ + input[0] = GUNZIP.out.gunzip + input[1] = 'tail' + input[2] = [] + input[3] = 0 + """ + } + } + } + + test("actinidia_chinensis-genome_1_fasta_gz-success") { + + when { + process { + """ + input[0] = GUNZIP.out.gunzip + input[1] = HELITRONSCANNER_SCANHEAD.out.scan + input[2] = HELITRONSCANNER_SCANTAIL.out.scan + """ + } + } + + then { + assertAll( + { assert process.success }, + { assert snapshot(process.out).match() }, + ) + } + + } + + test("sarscov2 - genome - success") { + + setup { + run('HELITRONSCANNER_SCAN', alias: 'HELITRONSCANNER_SCANHEAD_SARSCOV2') { + script "../../scan/main.nf" + + process { + """ + input[0] = [ + [ id: 'test' ], + file(params.modules_testdata_base_path + 'genomics/sarscov2/genome/genome.fasta', checkIfExists: true) + ] + input[1] = 'head' + input[2] = [] + input[3] = 0 + """ + } + } + + run('HELITRONSCANNER_SCAN', alias: 'HELITRONSCANNER_SCANTAIL_SARSCOV2') { + script "../../scan/main.nf" + + process { + """ + input[0] = [ + [ id: 'test' ], + file(params.modules_testdata_base_path + 'genomics/sarscov2/genome/genome.fasta', checkIfExists: true) + ] + input[1] = 'tail' + input[2] = [] + input[3] = 0 + """ + } + } + } + + when { + process { + """ + input[0] = [ + [ id: 'test' ], + file(params.modules_testdata_base_path + 'genomics/sarscov2/genome/genome.fasta', checkIfExists: true) + ] + input[1] = HELITRONSCANNER_SCANHEAD_SARSCOV2.out.scan + input[2] = HELITRONSCANNER_SCANTAIL_SARSCOV2.out.scan + """ + } + } + + then { + assertAll( + { assert process.success }, + { assert process.out.draw[0][1] != null }, + { assert snapshot(process.out.versions).match() } + ) + } + + } + + test("stub") { + + options "-stub" + + when { + process { + """ + input[0] = GUNZIP.out.gunzip + input[1] = HELITRONSCANNER_SCANHEAD.out.scan + input[2] = HELITRONSCANNER_SCANTAIL.out.scan + """ + } + } + + then { + assertAll( + { assert process.success }, + { assert snapshot(process.out).match() } + ) + } + + } + +} diff --git a/modules/gallvp/helitronscanner/draw/tests/main.nf.test.snap b/modules/gallvp/helitronscanner/draw/tests/main.nf.test.snap new file mode 100644 index 0000000..208d14d --- /dev/null +++ b/modules/gallvp/helitronscanner/draw/tests/main.nf.test.snap @@ -0,0 +1,80 @@ +{ + "actinidia_chinensis-genome_1_fasta_gz-success": { + "content": [ + { + "0": [ + [ + { + "id": "test" + }, + "test.draw:md5,434aaaa70c294464e6bae93b2895b13c" + ] + ], + "1": [ + "versions.yml:md5,4b206968a702782fa04d2ba560c89728" + ], + "draw": [ + [ + { + "id": "test" + }, + "test.draw:md5,434aaaa70c294464e6bae93b2895b13c" + ] + ], + "versions": [ + "versions.yml:md5,4b206968a702782fa04d2ba560c89728" + ] + } + ], + "meta": { + "nf-test": "0.9.0", + "nextflow": "24.04.4" + }, + "timestamp": "2024-10-17T11:51:49.477212" + }, + "stub": { + "content": [ + { + "0": [ + [ + { + "id": "test" + }, + "test.draw:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "1": [ + "versions.yml:md5,4b206968a702782fa04d2ba560c89728" + ], + "draw": [ + [ + { + "id": "test" + }, + "test.draw:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "versions": [ + "versions.yml:md5,4b206968a702782fa04d2ba560c89728" + ] + } + ], + "meta": { + "nf-test": "0.9.0", + "nextflow": "24.04.4" + }, + "timestamp": "2024-10-17T11:52:47.744899" + }, + "sarscov2 - genome - success": { + "content": [ + [ + "versions.yml:md5,4b206968a702782fa04d2ba560c89728" + ] + ], + "meta": { + "nf-test": "0.9.0", + "nextflow": "24.04.4" + }, + "timestamp": "2024-10-17T11:58:21.800568" + } +} \ No newline at end of file diff --git a/modules/gallvp/helitronscanner/draw/tests/nextflow.config b/modules/gallvp/helitronscanner/draw/tests/nextflow.config new file mode 100644 index 0000000..57f4900 --- /dev/null +++ b/modules/gallvp/helitronscanner/draw/tests/nextflow.config @@ -0,0 +1,5 @@ +process { + withName: 'HELITRONSCANNER_DRAW' { + ext.args = '-pure_helitron' + } +} diff --git a/modules/gallvp/helitronscanner/scan/environment.yml b/modules/gallvp/helitronscanner/scan/environment.yml new file mode 100644 index 0000000..ea89d39 --- /dev/null +++ b/modules/gallvp/helitronscanner/scan/environment.yml @@ -0,0 +1,5 @@ +channels: + - conda-forge + - bioconda +dependencies: + - "bioconda::helitronscanner=1.0" diff --git a/modules/gallvp/helitronscanner/scan/main.nf b/modules/gallvp/helitronscanner/scan/main.nf new file mode 100644 index 0000000..0f748ab --- /dev/null +++ b/modules/gallvp/helitronscanner/scan/main.nf @@ -0,0 +1,72 @@ +process HELITRONSCANNER_SCAN { + tag "$meta.id" + label 'process_high' + + conda "${moduleDir}/environment.yml" + container "${ workflow.containerEngine == 'singularity' && !task.ext.singularity_pull_docker_container ? + 'https://depot.galaxyproject.org/singularity/helitronscanner:1.0--hdfd78af_0': + 'biocontainers/helitronscanner:1.0--hdfd78af_0' }" + + input: + tuple val(meta), path(fasta) + val command + path lcv_filepath + val buffer_size + + output: + tuple val(meta), path("*.$command") , emit: scan + path "versions.yml" , emit: versions + + when: + task.ext.when == null || task.ext.when + + script: + if ( command !in [ 'head', 'tail' ] ) error "[HELITRONSCANNER_SCAN] command argument should be 'head' or 'tail'" + if ( !task.memory ) { error '[HELITRONSCANNER_SCAN] Available memory not known. Specify process memory requirements to fix this.' } + + def args = task.ext.args ?: '' + def prefix = task.ext.prefix ?: "${meta.id}" + + def is_head = { command == 'head' }() + def subcommand = is_head ? 'scanHead' : 'scanTail' + def lcvs_file = is_head ? 'head.lcvs' : 'tail.lcvs' + def lcv_arg = lcv_filepath ? "-lcv_filepath $lcv_filepath" : "-lcv_filepath \$HELITRONSCANNER_TRAININGSET_PATH/$lcvs_file" + def avail_mem = (task.memory.giga*0.8).intValue() + """ + # Nextflow changes the container --entrypoint to /bin/bash (container default entrypoint: /usr/local/env-execute) + # Check for container variable initialisation script and source it. + if [ -f "/usr/local/env-activate.sh" ]; then + set +u # Otherwise, errors out because of various unbound variables + . "/usr/local/env-activate.sh" + set -u + fi + + HelitronScanner \\ + $subcommand \\ + -Xmx${avail_mem}g \\ + $lcv_arg \\ + -genome $fasta \\ + -buffer_size $buffer_size \\ + -threads_LCV $task.cpus \\ + $args \\ + -output ${prefix}.${command} + + cat <<-END_VERSIONS > versions.yml + "${task.process}": + helitronscanner: \$(HelitronScanner |& sed -n 's/HelitronScanner V\\(.*\\)/V\\1/p') + END_VERSIONS + """ + + stub: + if ( command !in [ 'head', 'tail' ] ) error "[HELITRONSCANNER_SCAN] command argument should be 'head' or 'tail'" + def args = task.ext.args ?: '' + def prefix = task.ext.prefix ?: "${meta.id}" + """ + touch ${prefix}.${command} + + cat <<-END_VERSIONS > versions.yml + "${task.process}": + helitronscanner: \$(HelitronScanner |& sed -n 's/HelitronScanner V\\(.*\\)/V\\1/p') + END_VERSIONS + """ +} diff --git a/modules/gallvp/helitronscanner/scan/meta.yml b/modules/gallvp/helitronscanner/scan/meta.yml new file mode 100644 index 0000000..ed8f956 --- /dev/null +++ b/modules/gallvp/helitronscanner/scan/meta.yml @@ -0,0 +1,60 @@ +# yaml-language-server: $schema=https://raw.githubusercontent.com/nf-core/modules/master/modules/meta-schema.json +name: "helitronscanner_scan" +description: HelitronScanner scanHead and scanTail tools for Helitron transposons + in genomes +keywords: + - genomics + - helitron + - scanner +tools: + - "helitronscanner": + description: "HelitronScanner uncovers a large overlooked cache of Helitron transposons + in many genomes" + homepage: "https://sourceforge.net/projects/helitronscanner" + documentation: "https://sourceforge.net/projects/helitronscanner" + tool_dev_url: "https://sourceforge.net/projects/helitronscanner" + doi: "10.1073/pnas.1410068111" + licence: ["GPL v3-or-later"] + identifier: "" +input: + - - meta: + type: map + description: | + Groovy Map containing sample information + e.g. `[ id:'sample1' ]` + - fasta: + type: file + description: Genome data to scan for Helitrons + pattern: "*.{fa,fsa,fasta}" + - - command: + type: string + description: Command to execute. One of [ 'head', 'tail' ] + - - lcv_filepath: + type: file + description: LCV file path. If not provided by setting it to [], the LCV file + packaged with the module will be used + pattern: "*.lcvs" + - - buffer_size: + type: integer + description: Genome slice size (use negative or zero for non-buffer, i.e. treat + every whole chromosome) +output: + - scan: + - meta: + type: map + description: | + Groovy Map containing sample information + e.g. `[ id:'sample1' ]` + - "*.$command": + type: file + description: Head or tail file depending on the command + pattern: "*.$command" + - versions: + - versions.yml: + type: file + description: File containing software versions + pattern: "versions.yml" +authors: + - "@GallVp" +maintainers: + - "@GallVp" diff --git a/modules/gallvp/helitronscanner/scan/tests/main.nf.test b/modules/gallvp/helitronscanner/scan/tests/main.nf.test new file mode 100644 index 0000000..55bcfed --- /dev/null +++ b/modules/gallvp/helitronscanner/scan/tests/main.nf.test @@ -0,0 +1,148 @@ +nextflow_process { + + name "Test Process HELITRONSCANNER_SCAN" + script "../main.nf" + process "HELITRONSCANNER_SCAN" + + tag "modules" + tag "modules_gallvp" + tag "helitronscanner" + tag "helitronscanner/scan" + tag "gunzip" + + setup { + run('GUNZIP') { + script "../../../gunzip/main.nf" + + process { + """ + input[0] = [ + [ id:'test' ], // meta map + file(params.modules_testdata_base_path + 'genomics/eukaryotes/actinidia_chinensis/genome/chr1/genome.fasta.gz', checkIfExists: true) + ] + """ + } + } + } + + test("actinidia_chinensis - genome_1_fasta_gz - head - success") { + + when { + process { + """ + input[0] = GUNZIP.out.gunzip + input[1] = 'head' + input[2] = [] + input[3] = 0 + """ + } + } + + then { + assertAll( + { assert process.success }, + { assert path(process.out.scan[0][1]).text.contains('2729827:1 2729980:1 2730005:1') }, + { assert snapshot(process.out.versions).match() } + ) + } + + } + + test("actinidia_chinensis - genome_1_fasta_gz - tail - success") { + + when { + process { + """ + input[0] = GUNZIP.out.gunzip + input[1] = 'tail' + input[2] = [] + input[3] = 0 + """ + } + } + + then { + assertAll( + { assert process.success }, + { assert path(process.out.scan[0][1]).text.contains('7265:1 7951:1 9264:2 9398:1') }, + { assert snapshot(process.out.versions).match() } + ) + } + + } + + test("sarscov2 - genome - head - success") { + + when { + process { + """ + input[0] = [ + [ id: 'test' ], + file(params.modules_testdata_base_path + 'genomics/sarscov2/genome/genome.fasta', checkIfExists: true) + ] + input[1] = 'head' + input[2] = [] + input[3] = 0 + """ + } + } + + then { + assertAll( + { assert process.success }, + { assert path(process.out.scan[0][1]).text.contains('113:2 236:1 1158:1 1754:2 1771:1') }, + { assert snapshot(process.out.versions).match() } + ) + } + + } + + test("stub") { + + options "-stub" + + when { + process { + """ + input[0] = GUNZIP.out.gunzip + input[1] = 'head' + input[2] = [] + input[3] = 0 + """ + } + } + + then { + assertAll( + { assert process.success }, + { assert snapshot(process.out).match() } + ) + } + + } + + test("stub - tail") { + + options "-stub" + + when { + process { + """ + input[0] = GUNZIP.out.gunzip + input[1] = 'tail' + input[2] = [] + input[3] = 0 + """ + } + } + + then { + assertAll( + { assert process.success }, + { assert snapshot(process.out).match() } + ) + } + + } + +} diff --git a/modules/gallvp/helitronscanner/scan/tests/main.nf.test.snap b/modules/gallvp/helitronscanner/scan/tests/main.nf.test.snap new file mode 100644 index 0000000..ac7ae5e --- /dev/null +++ b/modules/gallvp/helitronscanner/scan/tests/main.nf.test.snap @@ -0,0 +1,104 @@ +{ + "actinidia_chinensis - genome_1_fasta_gz - tail - success": { + "content": [ + [ + "versions.yml:md5,3c675f4be863f58c88db5a2936beaa88" + ] + ], + "meta": { + "nf-test": "0.9.0", + "nextflow": "24.04.4" + }, + "timestamp": "2024-10-03T11:51:54.403269" + }, + "stub": { + "content": [ + { + "0": [ + [ + { + "id": "test" + }, + "test.head:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "1": [ + "versions.yml:md5,3c675f4be863f58c88db5a2936beaa88" + ], + "scan": [ + [ + { + "id": "test" + }, + "test.head:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "versions": [ + "versions.yml:md5,3c675f4be863f58c88db5a2936beaa88" + ] + } + ], + "meta": { + "nf-test": "0.9.0", + "nextflow": "24.04.4" + }, + "timestamp": "2024-10-03T11:52:10.250343" + }, + "sarscov2 - genome - head - success": { + "content": [ + [ + "versions.yml:md5,3c675f4be863f58c88db5a2936beaa88" + ] + ], + "meta": { + "nf-test": "0.9.0", + "nextflow": "24.04.4" + }, + "timestamp": "2024-10-03T11:43:05.068944" + }, + "stub - tail": { + "content": [ + { + "0": [ + [ + { + "id": "test" + }, + "test.tail:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "1": [ + "versions.yml:md5,3c675f4be863f58c88db5a2936beaa88" + ], + "scan": [ + [ + { + "id": "test" + }, + "test.tail:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "versions": [ + "versions.yml:md5,3c675f4be863f58c88db5a2936beaa88" + ] + } + ], + "meta": { + "nf-test": "0.9.0", + "nextflow": "24.04.4" + }, + "timestamp": "2024-10-03T11:39:42.225262" + }, + "actinidia_chinensis - genome_1_fasta_gz - head - success": { + "content": [ + [ + "versions.yml:md5,3c675f4be863f58c88db5a2936beaa88" + ] + ], + "meta": { + "nf-test": "0.9.0", + "nextflow": "24.04.4" + }, + "timestamp": "2024-10-03T11:51:18.942428" + } +} \ No newline at end of file diff --git a/subworkflows/gallvp/fasta_helitronscanner_scan_draw/main.nf b/subworkflows/gallvp/fasta_helitronscanner_scan_draw/main.nf new file mode 100644 index 0000000..40c2541 --- /dev/null +++ b/subworkflows/gallvp/fasta_helitronscanner_scan_draw/main.nf @@ -0,0 +1,86 @@ +include { HELITRONSCANNER_SCAN as HELITRONSCANNER_SCAN_HEAD } from '../../../modules/gallvp/helitronscanner/scan/main.nf' +include { HELITRONSCANNER_SCAN as HELITRONSCANNER_SCAN_TAIL } from '../../../modules/gallvp/helitronscanner/scan/main.nf' +include { HELITRONSCANNER_DRAW } from '../../../modules/gallvp/helitronscanner/draw/main.nf' + +include { HELITRONSCANNER_SCAN as HELITRONSCANNER_SCAN_HEAD_RC } from '../../../modules/gallvp/helitronscanner/scan/main.nf' +include { HELITRONSCANNER_SCAN as HELITRONSCANNER_SCAN_TAIL_RC } from '../../../modules/gallvp/helitronscanner/scan/main.nf' +include { HELITRONSCANNER_DRAW as HELITRONSCANNER_DRAW_RC } from '../../../modules/gallvp/helitronscanner/draw/main.nf' + +workflow FASTA_HELITRONSCANNER_SCAN_DRAW { + + take: + ch_fasta // channel: [ val(meta), fasta ] + + main: + + ch_versions = Channel.empty() + + // MODULE: HELITRONSCANNER_SCAN as HELITRONSCANNER_SCAN_HEAD + HELITRONSCANNER_SCAN_HEAD ( + ch_fasta, + 'head', // command + [], // lcv_filepath + 0 // buffer_size + ) + + ch_helitronscanner_scan_head = HELITRONSCANNER_SCAN_HEAD.out.scan + ch_versions = ch_versions.mix(HELITRONSCANNER_SCAN_HEAD.out.versions) + + // MODULE: HELITRONSCANNER_SCAN as HELITRONSCANNER_SCAN_TAIL + HELITRONSCANNER_SCAN_TAIL ( + ch_fasta, + 'tail', // command + [], // lcv_filepath + 0 // buffer_size + ) + + ch_helitronscanner_scan_tail = HELITRONSCANNER_SCAN_TAIL.out.scan + ch_versions = ch_versions.mix(HELITRONSCANNER_SCAN_TAIL.out.versions) + + // MODULE: HELITRONSCANNER_DRAW + HELITRONSCANNER_DRAW ( + ch_fasta, + ch_helitronscanner_scan_head, + ch_helitronscanner_scan_tail + ) + + ch_helitronscanner_draw = HELITRONSCANNER_DRAW.out.draw + ch_versions = ch_versions.mix(HELITRONSCANNER_DRAW.out.versions) + + // MODULE: HELITRONSCANNER_SCAN as HELITRONSCANNER_SCAN_HEAD_RC + HELITRONSCANNER_SCAN_HEAD_RC ( + ch_fasta, + 'head', // command + [], // lcv_filepath + 0 // buffer_size + ) + + ch_helitronscanner_scan_head_rc = HELITRONSCANNER_SCAN_HEAD_RC.out.scan + ch_versions = ch_versions.mix(HELITRONSCANNER_SCAN_HEAD_RC.out.versions) + + // MODULE: HELITRONSCANNER_SCAN as HELITRONSCANNER_SCAN_TAIL_RC + HELITRONSCANNER_SCAN_TAIL_RC ( + ch_fasta, + 'tail', // command + [], // lcv_filepath + 0 // buffer_size + ) + + ch_helitronscanner_scan_tail_rc = HELITRONSCANNER_SCAN_TAIL_RC.out.scan + ch_versions = ch_versions.mix(HELITRONSCANNER_SCAN_TAIL_RC.out.versions) + + // MODULE: HELITRONSCANNER_DRAW as HELITRONSCANNER_DRAW_RC + HELITRONSCANNER_DRAW_RC ( + ch_fasta, + ch_helitronscanner_scan_head_rc, + ch_helitronscanner_scan_tail_rc + ) + + ch_helitronscanner_draw_rc = HELITRONSCANNER_DRAW_RC.out.draw + ch_versions = ch_versions.mix(HELITRONSCANNER_DRAW_RC.out.versions) + + emit: + helitronscanner_draw = ch_helitronscanner_draw // channel: [ val(meta), draw ] + helitronscanner_draw_rc = ch_helitronscanner_draw_rc // channel: [ val(meta), rc.draw ] + versions = ch_versions // channel: [ versions.yml ] +} diff --git a/subworkflows/gallvp/fasta_helitronscanner_scan_draw/meta.yml b/subworkflows/gallvp/fasta_helitronscanner_scan_draw/meta.yml new file mode 100644 index 0000000..62c4b39 --- /dev/null +++ b/subworkflows/gallvp/fasta_helitronscanner_scan_draw/meta.yml @@ -0,0 +1,45 @@ +# yaml-language-server: $schema=https://raw.githubusercontent.com/nf-core/modules/master/subworkflows/yaml-schema.json +name: "fasta_helitronscanner_scan_draw" +description: Find helitrons suing Helitronscanner scan, pairends and draw subcommands +keywords: + - helitron + - scan + - draw + - pairends + - repeat + - genomics + - fasta +components: + - helitronscanner/scan + - helitronscanner/draw +input: + - ch_fasta: + type: file + description: | + Genome fasta + Structure: [ val(meta), fasta ] + pattern: "*.fasta" +output: + - helitronscanner_draw: + type: file + description: | + Helitronscanner draw file + Structure: [ val(meta), draw ] + pattern: "*.draw" + - helitronscanner_draw_rc: + type: file + description: | + Helitronscanner reverse complement draw file + Structure: [ val(meta), rc.draw ] + pattern: "*.rc.draw" + - versions: + type: file + description: | + File containing software versions + Structure: [ path(versions.yml) ] + pattern: "versions.yml" +authors: + - "@GallVp" +maintainers: + - "@GallVp" + - "@jguhlin" diff --git a/subworkflows/gallvp/fasta_helitronscanner_scan_draw/tests/main.nf.test b/subworkflows/gallvp/fasta_helitronscanner_scan_draw/tests/main.nf.test new file mode 100644 index 0000000..7a9bd05 --- /dev/null +++ b/subworkflows/gallvp/fasta_helitronscanner_scan_draw/tests/main.nf.test @@ -0,0 +1,97 @@ +nextflow_workflow { + + name "Test Subworkflow FASTA_HELITRONSCANNER_SCAN_DRAW" + script "../main.nf" + workflow "FASTA_HELITRONSCANNER_SCAN_DRAW" + config "./nextflow.config" + + tag "subworkflows" + tag "subworkflows_gallvp" + tag "subworkflows/fasta_helitronscanner_scan_draw" + + tag "helitronscanner/draw" + tag "helitronscanner/scan" + tag "gunzip" + + setup { + run('GUNZIP') { + script "../../../../modules/gallvp/gunzip/main.nf" + + process { + """ + input[0] = [ + [ id:'test' ], // meta map + file(params.modules_testdata_base_path + 'genomics/eukaryotes/actinidia_chinensis/genome/chr1/genome.fasta.gz', checkIfExists: true) + ] + """ + } + } + } + + test("actinidia_chinensis - genome_1_fasta_gz") { + + when { + workflow { + """ + input[0] = GUNZIP.out.gunzip + """ + } + } + + then { + assertAll( + { assert workflow.success }, + { assert snapshot(workflow.out).match() } + ) + } + + } + + test("sarscov2 - genome") { + + when { + workflow { + """ + input[0] = [ + [ id: 'test' ], + file(params.modules_testdata_base_path + 'genomics/sarscov2/genome/genome.fasta', checkIfExists: true) + ] + """ + } + } + + then { + assertAll( + { assert workflow.success }, + { assert workflow.out.helitronscanner_draw }, // empty + { assert workflow.out.helitronscanner_draw_rc }, // empty + { assert snapshot( + workflow.out.versions + ).match() + } + ) + } + + } + + test("stub") { + + options "-stub" + + when { + workflow { + """ + input[0] = GUNZIP.out.gunzip + """ + } + } + + then { + assertAll( + { assert workflow.success }, + { assert snapshot(workflow.out).match() } + ) + } + + } +} diff --git a/subworkflows/gallvp/fasta_helitronscanner_scan_draw/tests/main.nf.test.snap b/subworkflows/gallvp/fasta_helitronscanner_scan_draw/tests/main.nf.test.snap new file mode 100644 index 0000000..f6b8077 --- /dev/null +++ b/subworkflows/gallvp/fasta_helitronscanner_scan_draw/tests/main.nf.test.snap @@ -0,0 +1,137 @@ +{ + "actinidia_chinensis - genome_1_fasta_gz": { + "content": [ + { + "0": [ + [ + { + "id": "test" + }, + "test.draw:md5,434aaaa70c294464e6bae93b2895b13c" + ] + ], + "1": [ + [ + { + "id": "test" + }, + "test.rc.draw:md5,7aeed0fdf50f8cb2516b2e92f456835e" + ] + ], + "2": [ + "versions.yml:md5,42ab90da7ef86125b9dec685f4f08e84", + "versions.yml:md5,9b6a0f2f56d89451cf16702e2f358520", + "versions.yml:md5,a928ae7c94a23e5c62c27915dada660c", + "versions.yml:md5,c417497b1caea1cd51f8440df92daa40", + "versions.yml:md5,d786d80e914a86ce0361cfc0bc62cda9", + "versions.yml:md5,f3b6aa20e81f04fd731fad64f7ea3c14" + ], + "helitronscanner_draw": [ + [ + { + "id": "test" + }, + "test.draw:md5,434aaaa70c294464e6bae93b2895b13c" + ] + ], + "helitronscanner_draw_rc": [ + [ + { + "id": "test" + }, + "test.rc.draw:md5,7aeed0fdf50f8cb2516b2e92f456835e" + ] + ], + "versions": [ + "versions.yml:md5,42ab90da7ef86125b9dec685f4f08e84", + "versions.yml:md5,9b6a0f2f56d89451cf16702e2f358520", + "versions.yml:md5,a928ae7c94a23e5c62c27915dada660c", + "versions.yml:md5,c417497b1caea1cd51f8440df92daa40", + "versions.yml:md5,d786d80e914a86ce0361cfc0bc62cda9", + "versions.yml:md5,f3b6aa20e81f04fd731fad64f7ea3c14" + ] + } + ], + "meta": { + "nf-test": "0.9.0", + "nextflow": "24.04.4" + }, + "timestamp": "2024-11-21T11:53:51.717511" + }, + "stub": { + "content": [ + { + "0": [ + [ + { + "id": "test" + }, + "test.draw:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "1": [ + [ + { + "id": "test" + }, + "test.rc.draw:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "2": [ + "versions.yml:md5,42ab90da7ef86125b9dec685f4f08e84", + "versions.yml:md5,9b6a0f2f56d89451cf16702e2f358520", + "versions.yml:md5,a928ae7c94a23e5c62c27915dada660c", + "versions.yml:md5,c417497b1caea1cd51f8440df92daa40", + "versions.yml:md5,d786d80e914a86ce0361cfc0bc62cda9", + "versions.yml:md5,f3b6aa20e81f04fd731fad64f7ea3c14" + ], + "helitronscanner_draw": [ + [ + { + "id": "test" + }, + "test.draw:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "helitronscanner_draw_rc": [ + [ + { + "id": "test" + }, + "test.rc.draw:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "versions": [ + "versions.yml:md5,42ab90da7ef86125b9dec685f4f08e84", + "versions.yml:md5,9b6a0f2f56d89451cf16702e2f358520", + "versions.yml:md5,a928ae7c94a23e5c62c27915dada660c", + "versions.yml:md5,c417497b1caea1cd51f8440df92daa40", + "versions.yml:md5,d786d80e914a86ce0361cfc0bc62cda9", + "versions.yml:md5,f3b6aa20e81f04fd731fad64f7ea3c14" + ] + } + ], + "meta": { + "nf-test": "0.9.0", + "nextflow": "24.04.4" + }, + "timestamp": "2024-11-21T11:54:17.824589" + }, + "sarscov2 - genome": { + "content": [ + [ + "versions.yml:md5,42ab90da7ef86125b9dec685f4f08e84", + "versions.yml:md5,9b6a0f2f56d89451cf16702e2f358520", + "versions.yml:md5,a928ae7c94a23e5c62c27915dada660c", + "versions.yml:md5,c417497b1caea1cd51f8440df92daa40", + "versions.yml:md5,d786d80e914a86ce0361cfc0bc62cda9", + "versions.yml:md5,f3b6aa20e81f04fd731fad64f7ea3c14" + ] + ], + "meta": { + "nf-test": "0.9.0", + "nextflow": "24.04.4" + }, + "timestamp": "2024-11-21T12:03:43.135817" + } +} \ No newline at end of file diff --git a/subworkflows/gallvp/fasta_helitronscanner_scan_draw/tests/nextflow.config b/subworkflows/gallvp/fasta_helitronscanner_scan_draw/tests/nextflow.config new file mode 100644 index 0000000..b98d370 --- /dev/null +++ b/subworkflows/gallvp/fasta_helitronscanner_scan_draw/tests/nextflow.config @@ -0,0 +1,20 @@ +process { + withName: 'HELITRONSCANNER_DRAW' { + ext.args = '-pure_helitron' + } + + withName: 'HELITRONSCANNER_SCAN_HEAD_RC' { + ext.prefix = { "${meta.id}.rc" } + ext.args = '--rc' + } + + withName: 'HELITRONSCANNER_SCAN_TAIL_RC' { + ext.prefix = { "${meta.id}.rc" } + ext.args = '--rc' + } + + withName: 'HELITRONSCANNER_DRAW_RC' { + ext.prefix = { "${meta.id}.rc" } + ext.args = '-pure_helitron' + } +} diff --git a/workflows/edta.nf b/workflows/edta.nf index df8b2d9..357b427 100644 --- a/workflows/edta.nf +++ b/workflows/edta.nf @@ -1,14 +1,15 @@ -include { SANITIZE_HEADERS } from '../modules/local/sanitize/main.nf' -include { LTRHARVEST } from '../modules/nf-core/ltrharvest/main.nf' -include { LTRFINDER } from '../modules/nf-core/ltrfinder/main.nf' -include { CAT_CAT } from '../modules/nf-core/cat/cat/main.nf' -include { LTRRETRIEVER_LTRRETRIEVER } from '../modules/nf-core/ltrretriever/ltrretriever/main.nf' -include { TIRLEARNER } from '../modules/gallvp/tirlearner/main.nf' -include { ANNOSINE } from '../modules/gallvp/annosine/main.nf' -include { REPEATMODELER_BUILDDATABASE } from '../modules/nf-core/repeatmodeler/builddatabase/main.nf' -include { REPEATMODELER_REPEATMODELER } from '../modules/nf-core/repeatmodeler/repeatmodeler/main.nf' - -include { softwareVersionsToYAML } from '../modules/local/utils/main.nf' +include { SANITIZE_HEADERS } from '../modules/local/sanitize/main.nf' +include { LTRHARVEST } from '../modules/nf-core/ltrharvest/main.nf' +include { LTRFINDER } from '../modules/nf-core/ltrfinder/main.nf' +include { CAT_CAT } from '../modules/nf-core/cat/cat/main.nf' +include { LTRRETRIEVER_LTRRETRIEVER } from '../modules/nf-core/ltrretriever/ltrretriever/main.nf' +include { TIRLEARNER } from '../modules/gallvp/tirlearner/main.nf' +include { ANNOSINE } from '../modules/gallvp/annosine/main.nf' +include { REPEATMODELER_BUILDDATABASE } from '../modules/nf-core/repeatmodeler/builddatabase/main.nf' +include { REPEATMODELER_REPEATMODELER } from '../modules/nf-core/repeatmodeler/repeatmodeler/main.nf' +include { FASTA_HELITRONSCANNER_SCAN_DRAW } from '../subworkflows/gallvp/fasta_helitronscanner_scan_draw/main.nf' + +include { softwareVersionsToYAML } from '../modules/local/utils/main.nf' workflow EDTA { @@ -124,6 +125,9 @@ workflow EDTA { ch_repeatmodeler_fasta = REPEATMODELER_REPEATMODELER.out.fasta ch_versions = ch_versions.mix(REPEATMODELER_REPEATMODELER.out.versions.first()) + // MODULE: FASTA_HELITRONSCANNER_SCAN_DRAW + FASTA_HELITRONSCANNER_SCAN_DRAW ( ch_sanitized_fasta ) + // Function: Save versions ch_versions = ch_versions From 345341069f0e9767c6cab065855c6789b3f27104 Mon Sep 17 00:00:00 2001 From: Usman Rashid Date: Tue, 26 Nov 2024 22:39:10 +1300 Subject: [PATCH 07/49] Updated snapshots --- conf/modules.config | 8 ++++---- test/nf-test/small/main.nf.test.snap | 22 ++++++++++++++++++++-- test/nf-test/tiny/main.nf.test.snap | 22 ++++++++++++++++++++-- workflows/edta.nf | 4 ++++ 4 files changed, 48 insertions(+), 8 deletions(-) diff --git a/conf/modules.config b/conf/modules.config index e70a6ea..39b1af8 100644 --- a/conf/modules.config +++ b/conf/modules.config @@ -20,21 +20,21 @@ process { ext.args = '-engine ncbi' } - withName: 'EDTA::FASTA_HELITRONSCANNER_SCAN_DRAW:HELITRONSCANNER_DRAW' { + withName: 'EDTA:FASTA_HELITRONSCANNER_SCAN_DRAW:HELITRONSCANNER_DRAW' { ext.args = '-pure_helitron' } - withName: 'EDTA::FASTA_HELITRONSCANNER_SCAN_DRAW:HELITRONSCANNER_SCAN_HEAD_RC' { + withName: 'EDTA:FASTA_HELITRONSCANNER_SCAN_DRAW:HELITRONSCANNER_SCAN_HEAD_RC' { ext.prefix = { "${meta.id}.rc" } ext.args = '--rc' } - withName: 'EDTA::FASTA_HELITRONSCANNER_SCAN_DRAW:HELITRONSCANNER_SCAN_TAIL_RC' { + withName: 'EDTA:FASTA_HELITRONSCANNER_SCAN_DRAW:HELITRONSCANNER_SCAN_TAIL_RC' { ext.prefix = { "${meta.id}.rc" } ext.args = '--rc' } - withName: 'EDTA::FASTA_HELITRONSCANNER_SCAN_DRAW:HELITRONSCANNER_DRAW_RC' { + withName: 'EDTA:FASTA_HELITRONSCANNER_SCAN_DRAW:HELITRONSCANNER_DRAW_RC' { ext.prefix = { "${meta.id}.rc" } ext.args = '-pure_helitron' } diff --git a/test/nf-test/small/main.nf.test.snap b/test/nf-test/small/main.nf.test.snap index d53e834..9154059 100644 --- a/test/nf-test/small/main.nf.test.snap +++ b/test/nf-test/small/main.nf.test.snap @@ -2,7 +2,7 @@ "small genome": { "content": [ { - "successful tasks": 9, + "successful tasks": 15, "versions": { "ANNOSINE": { "annosine": "2.0.7" @@ -10,6 +10,24 @@ "CAT_CAT": { "pigz": "2.3.4" }, + "HELITRONSCANNER_DRAW": { + "helitronscanner": "V1.1" + }, + "HELITRONSCANNER_DRAW_RC": { + "helitronscanner": "V1.1" + }, + "HELITRONSCANNER_SCAN_HEAD": { + "helitronscanner": "V1.1" + }, + "HELITRONSCANNER_SCAN_HEAD_RC": { + "helitronscanner": "V1.1" + }, + "HELITRONSCANNER_SCAN_TAIL": { + "helitronscanner": "V1.1" + }, + "HELITRONSCANNER_SCAN_TAIL_RC": { + "helitronscanner": "V1.1" + }, "LTRFINDER": { "LTR_FINDER_parallel": "v1.1", "ltr_finder": "v1.07" @@ -43,6 +61,6 @@ "nf-test": "0.9.0", "nextflow": "24.10.1" }, - "timestamp": "2024-11-25T13:53:22.07666" + "timestamp": "2024-11-26T22:38:20.031297" } } \ No newline at end of file diff --git a/test/nf-test/tiny/main.nf.test.snap b/test/nf-test/tiny/main.nf.test.snap index a61631f..946ed5d 100644 --- a/test/nf-test/tiny/main.nf.test.snap +++ b/test/nf-test/tiny/main.nf.test.snap @@ -2,7 +2,7 @@ "tiny genome": { "content": [ { - "successful tasks": 7, + "successful tasks": 13, "versions": { "ANNOSINE": { "annosine": "2.0.7" @@ -10,6 +10,24 @@ "CAT_CAT": { "pigz": "2.3.4" }, + "HELITRONSCANNER_DRAW": { + "helitronscanner": "V1.1" + }, + "HELITRONSCANNER_DRAW_RC": { + "helitronscanner": "V1.1" + }, + "HELITRONSCANNER_SCAN_HEAD": { + "helitronscanner": "V1.1" + }, + "HELITRONSCANNER_SCAN_HEAD_RC": { + "helitronscanner": "V1.1" + }, + "HELITRONSCANNER_SCAN_TAIL": { + "helitronscanner": "V1.1" + }, + "HELITRONSCANNER_SCAN_TAIL_RC": { + "helitronscanner": "V1.1" + }, "LTRFINDER": { "LTR_FINDER_parallel": "v1.1", "ltr_finder": "v1.07" @@ -37,6 +55,6 @@ "nf-test": "0.9.0", "nextflow": "24.10.1" }, - "timestamp": "2024-11-25T13:20:57.770583" + "timestamp": "2024-11-26T22:35:05.489004" } } \ No newline at end of file diff --git a/workflows/edta.nf b/workflows/edta.nf index 357b427..cf99f9f 100644 --- a/workflows/edta.nf +++ b/workflows/edta.nf @@ -128,6 +128,10 @@ workflow EDTA { // MODULE: FASTA_HELITRONSCANNER_SCAN_DRAW FASTA_HELITRONSCANNER_SCAN_DRAW ( ch_sanitized_fasta ) + ch_helitronscanner_draw = FASTA_HELITRONSCANNER_SCAN_DRAW.out.helitronscanner_draw + ch_helitronscanner_draw_rc = FASTA_HELITRONSCANNER_SCAN_DRAW.out.helitronscanner_draw_rc + ch_versions = ch_versions.mix(FASTA_HELITRONSCANNER_SCAN_DRAW.out.versions) + // Function: Save versions ch_versions = ch_versions From 29dbed0f0ddf8a78e524c628b889fc30fcc2eaec Mon Sep 17 00:00:00 2001 From: Usman Rashid Date: Thu, 28 Nov 2024 18:25:55 +1300 Subject: [PATCH 08/49] Added CLEANUP_TANDEM --- bin/cleanup_tandem.pl | 0 modules/local/cleanup_tandem/environment.yml | 8 +++ modules/local/cleanup_tandem/main.nf | 49 +++++++++++++ .../local/cleanup_tandem/tests/main.nf.test | 56 +++++++++++++++ .../cleanup_tandem/tests/main.nf.test.snap | 68 +++++++++++++++++++ 5 files changed, 181 insertions(+) mode change 100644 => 100755 bin/cleanup_tandem.pl create mode 100644 modules/local/cleanup_tandem/environment.yml create mode 100644 modules/local/cleanup_tandem/main.nf create mode 100644 modules/local/cleanup_tandem/tests/main.nf.test create mode 100644 modules/local/cleanup_tandem/tests/main.nf.test.snap diff --git a/bin/cleanup_tandem.pl b/bin/cleanup_tandem.pl old mode 100644 new mode 100755 diff --git a/modules/local/cleanup_tandem/environment.yml b/modules/local/cleanup_tandem/environment.yml new file mode 100644 index 0000000..8d38804 --- /dev/null +++ b/modules/local/cleanup_tandem/environment.yml @@ -0,0 +1,8 @@ +--- +# yaml-language-server: $schema=https://raw.githubusercontent.com/nf-core/modules/master/modules/environment-schema.json +channels: + - conda-forge + - bioconda +dependencies: + - "bioconda::trf=4.09.1" + - "conda-forge::perl=5.32.1" \ No newline at end of file diff --git a/modules/local/cleanup_tandem/main.nf b/modules/local/cleanup_tandem/main.nf new file mode 100644 index 0000000..ae84cd2 --- /dev/null +++ b/modules/local/cleanup_tandem/main.nf @@ -0,0 +1,49 @@ +process CLEANUP_TANDEM { + tag "$meta.id" + label 'process_single' + + conda "${moduleDir}/environment.yml" + container "${ workflow.containerEngine == 'singularity' && !task.ext.singularity_pull_docker_container ? + 'https://community-cr-prod.seqera.io/docker/registry/v2/blobs/sha256/64/64d26063bedc2efcba20750a408a21f50907986d0d9aee685b03d7d05d3fbd8b/data': + 'community.wave.seqera.io/library/trf_perl:44f9844b1bf765c3' }" + + input: + tuple val(meta), path(fasta) + + output: + tuple val(meta), path("*.fasta") , emit: cleaned_fasta + path "versions.yml" , emit: versions + + when: + task.ext.when == null || task.ext.when + + script: + def prefix = task.ext.prefix ?: "${meta.id}" + def args = task.ext.args ?: '' + if ( "$fasta" == "${prefix}.fasta" ) error "Input and output names are the same, use \"task.ext.prefix\" to disambiguate!" + """ + cleanup_tandem.pl \\ + -f $fasta \\ + $args \\ + > ${prefix}.fasta + + cat <<-END_VERSIONS > versions.yml + "${task.process}": + perl: \$(perl -v | sed -n 's|This is perl.*(\\(.*\\)).*|\\1|p') + trf: \$(trf -v |& sed -n 's|.*Version \\(.*\\)|\\1|p') + END_VERSIONS + """ + + stub: + def prefix = task.ext.prefix ?: "${meta.id}" + if ( "$fasta" == "${prefix}.fasta" ) error "Input and output names are the same, use \"task.ext.prefix\" to disambiguate!" + """ + touch ${prefix}.fasta + + cat <<-END_VERSIONS > versions.yml + "${task.process}": + perl: \$(perl -v | sed -n 's|This is perl.*(\\(.*\\)).*|\\1|p') + trf: \$(trf -v |& sed -n 's|.*Version \\(.*\\)|\\1|p') + END_VERSIONS + """ +} \ No newline at end of file diff --git a/modules/local/cleanup_tandem/tests/main.nf.test b/modules/local/cleanup_tandem/tests/main.nf.test new file mode 100644 index 0000000..b13870e --- /dev/null +++ b/modules/local/cleanup_tandem/tests/main.nf.test @@ -0,0 +1,56 @@ +nextflow_process { + + name "Test Process CLEANUP_TANDEM" + script "../main.nf" + process "CLEANUP_TANDEM" + + tag "cleanup_tandem" + tag "modules_local" + tag "modules" + + test("sarscov2 - genome") { + + when { + process { + """ + input[0] = [ + [ id: 'sarscov2' ], + file(params.modules_testdata_base_path + 'genomics/sarscov2/genome/genome.fasta', checkIfExists: true) + ] + """ + } + } + + then { + assertAll( + { assert process.success }, + { assert snapshot(process.out).match() } + ) + } + + } + + test("sarscov2 - genome - stub") { + options '-stub' + + when { + process { + """ + input[0] = [ + [ id: 'sarscov2' ], + file(params.modules_testdata_base_path + 'genomics/sarscov2/genome/genome.fasta', checkIfExists: true) + ] + """ + } + } + + then { + assertAll( + { assert process.success }, + { assert snapshot(process.out).match() } + ) + } + + } + +} diff --git a/modules/local/cleanup_tandem/tests/main.nf.test.snap b/modules/local/cleanup_tandem/tests/main.nf.test.snap new file mode 100644 index 0000000..a36d60e --- /dev/null +++ b/modules/local/cleanup_tandem/tests/main.nf.test.snap @@ -0,0 +1,68 @@ +{ + "sarscov2 - genome - stub": { + "content": [ + { + "0": [ + [ + { + "id": "sarscov2" + }, + "sarscov2.fasta:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "1": [ + "versions.yml:md5,2cce04ed211ff9c6f7f322d2b217af83" + ], + "cleaned_fasta": [ + [ + { + "id": "sarscov2" + }, + "sarscov2.fasta:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "versions": [ + "versions.yml:md5,2cce04ed211ff9c6f7f322d2b217af83" + ] + } + ], + "meta": { + "nf-test": "0.9.0", + "nextflow": "24.10.1" + }, + "timestamp": "2024-11-28T16:12:53.161124" + }, + "sarscov2 - genome": { + "content": [ + { + "0": [ + [ + { + "id": "sarscov2" + }, + "sarscov2.fasta:md5,e73599798195a519ba2565c3f0275b93" + ] + ], + "1": [ + "versions.yml:md5,2cce04ed211ff9c6f7f322d2b217af83" + ], + "cleaned_fasta": [ + [ + { + "id": "sarscov2" + }, + "sarscov2.fasta:md5,e73599798195a519ba2565c3f0275b93" + ] + ], + "versions": [ + "versions.yml:md5,2cce04ed211ff9c6f7f322d2b217af83" + ] + } + ], + "meta": { + "nf-test": "0.9.0", + "nextflow": "24.10.1" + }, + "timestamp": "2024-11-28T16:12:48.160979" + } +} \ No newline at end of file From d43a72d7bca49876ef2cce2ec4be0bdcbaaa2468 Mon Sep 17 00:00:00 2001 From: Joseph Guhlin Date: Thu, 21 Nov 2024 11:51:55 +1300 Subject: [PATCH 09/49] Initial try on format_helitronscanner --- .../format_helitronscanner/environment.yml | 7 +++ modules/local/format_helitronscanner/main.nf | 55 +++++++++++++++++++ 2 files changed, 62 insertions(+) create mode 100644 modules/local/format_helitronscanner/environment.yml create mode 100644 modules/local/format_helitronscanner/main.nf diff --git a/modules/local/format_helitronscanner/environment.yml b/modules/local/format_helitronscanner/environment.yml new file mode 100644 index 0000000..d2f633e --- /dev/null +++ b/modules/local/format_helitronscanner/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::perl-bioperl=1.7.8" diff --git a/modules/local/format_helitronscanner/main.nf b/modules/local/format_helitronscanner/main.nf new file mode 100644 index 0000000..9d41ab6 --- /dev/null +++ b/modules/local/format_helitronscanner/main.nf @@ -0,0 +1,55 @@ +process FORMAT_HELITRONSCANNER_OUT { + tag "$meta.id" + label 'process_single' + + conda "${moduleDir}/environment.yml" + container "${ workflow.containerEngine == 'singularity' && !task.ext.singularity_pull_docker_container ? + 'https://depot.galaxyproject.org/singularity/perl-bioperl:1.7.8--hdfd78af_1': + 'biocontainers/perl-bioperl:1.7.8--hdfd78af_1' }" + + input: + tuple val(meta), path(genome) + path hel_fa // HelitronScanner.draw.hel.fa file + path rc_hel_fa // HelitronScanner.draw.rc.hel.fa file + + output: + tuple val(meta), path("${meta.id}.HelitronScanner.filtered.tabout"), emit: filtered_tabout + tuple val(meta), path("${meta.id}.HelitronScanner.filtered.fa"), emit: filtered_fa, optional: true + tuple val(meta), path("${meta.id}.HelitronScanner.filtered.ext.fa"), emit: filtered_ext_fa, optional: true + tuple val(meta), path("${meta.id}.format_helitronscanner_out.log"), emit: log + path "versions.yml", emit: versions + + when: + task.ext.when == null || task.ext.when + + script: + // Set default values for the optional arguments + def sitefilter = task.ext.sitefilter != null ? task.ext.sitefilter : 1 + def minscore = task.ext.minscore != null ? task.ext.minscore : 12 + def keepshorter = task.ext.keepshorter != null ? task.ext.keepshorter : 1 + def extlen = task.ext.extlen != null ? task.ext.extlen : 30 + def extout = task.ext.extout != null ? task.ext.extout : 1 + def prefix = meta.id + + """ + # Create symbolic links to match expected filenames + ln -s $hel_fa ${genome}.HelitronScanner.draw.hel.fa + ln -s $rc_hel_fa ${genome}.HelitronScanner.draw.rc.hel.fa + + # Run the Perl script with the provided arguments + perl format_helitronscanner_out.pl \\ + -genome $genome \\ + -sitefilter $sitefilter \\ + -minscore $minscore \\ + -keepshorter $keepshorter \\ + -extlen $extlen \\ + -extout $extout \\ + &> >(tee "${prefix}.format_helitronscanner_out.log" 2>&1) + + # Capture version information + cat <<-END_VERSIONS > versions.yml + "${task.process}": + Perl: \$(perl -v | grep 'This is perl' | awk '{print \$4}') + END_VERSIONS + """ +} From c65812d533d9b1b6f3c193e0086bc5f314e459c3 Mon Sep 17 00:00:00 2001 From: Joseph Guhlin Date: Thu, 28 Nov 2024 11:36:13 +1300 Subject: [PATCH 10/49] Make changes suggested by @GallVp --- .../environment.yml | 0 .../main.nf | 7 +++---- 2 files changed, 3 insertions(+), 4 deletions(-) rename modules/local/{format_helitronscanner => format_helitronscanner_out}/environment.yml (100%) rename modules/local/{format_helitronscanner => format_helitronscanner_out}/main.nf (91%) diff --git a/modules/local/format_helitronscanner/environment.yml b/modules/local/format_helitronscanner_out/environment.yml similarity index 100% rename from modules/local/format_helitronscanner/environment.yml rename to modules/local/format_helitronscanner_out/environment.yml diff --git a/modules/local/format_helitronscanner/main.nf b/modules/local/format_helitronscanner_out/main.nf similarity index 91% rename from modules/local/format_helitronscanner/main.nf rename to modules/local/format_helitronscanner_out/main.nf index 9d41ab6..2364644 100644 --- a/modules/local/format_helitronscanner/main.nf +++ b/modules/local/format_helitronscanner_out/main.nf @@ -29,14 +29,14 @@ process FORMAT_HELITRONSCANNER_OUT { def keepshorter = task.ext.keepshorter != null ? task.ext.keepshorter : 1 def extlen = task.ext.extlen != null ? task.ext.extlen : 30 def extout = task.ext.extout != null ? task.ext.extout : 1 - def prefix = meta.id + def prefix = task.ext.prefix ?: "${meta.id}" + def args = task.ext.args ?: '' """ # Create symbolic links to match expected filenames ln -s $hel_fa ${genome}.HelitronScanner.draw.hel.fa ln -s $rc_hel_fa ${genome}.HelitronScanner.draw.rc.hel.fa - # Run the Perl script with the provided arguments perl format_helitronscanner_out.pl \\ -genome $genome \\ -sitefilter $sitefilter \\ @@ -46,10 +46,9 @@ process FORMAT_HELITRONSCANNER_OUT { -extout $extout \\ &> >(tee "${prefix}.format_helitronscanner_out.log" 2>&1) - # Capture version information cat <<-END_VERSIONS > versions.yml "${task.process}": - Perl: \$(perl -v | grep 'This is perl' | awk '{print \$4}') + perl: \$(perl -v | sed -n 's|This is perl.*(\\(.*\\)).*|\\1|p') END_VERSIONS """ } From 0492d4aadc5c33df01be745a64b4a77aabb076d8 Mon Sep 17 00:00:00 2001 From: Joseph Guhlin Date: Thu, 28 Nov 2024 11:49:23 +1300 Subject: [PATCH 11/49] Added some tests - not yet working, need the nf-test.config from the existing PR --- .../tests/environment.yml | 7 ++ .../tests/main.nf.test | 90 +++++++++++++++++++ 2 files changed, 97 insertions(+) create mode 100644 modules/local/format_helitronscanner_out/tests/environment.yml create mode 100644 modules/local/format_helitronscanner_out/tests/main.nf.test diff --git a/modules/local/format_helitronscanner_out/tests/environment.yml b/modules/local/format_helitronscanner_out/tests/environment.yml new file mode 100644 index 0000000..d2f633e --- /dev/null +++ b/modules/local/format_helitronscanner_out/tests/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::perl-bioperl=1.7.8" diff --git a/modules/local/format_helitronscanner_out/tests/main.nf.test b/modules/local/format_helitronscanner_out/tests/main.nf.test new file mode 100644 index 0000000..d1808cd --- /dev/null +++ b/modules/local/format_helitronscanner_out/tests/main.nf.test @@ -0,0 +1,90 @@ +nextflow_process { + + name "Test Process FORMAT_HELITRONSCANNER_OUT" + script "../main.nf" + process "FORMAT_HELITRONSCANNER_OUT" + + tag "modules" + tag "modules_nfcore" + tag "format_helitronscanner_out" + + test("homo_sapiens - genome_21_fasta") { + + when { + process { + """ + input[0] = [ + [ id:'test' ], + 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.filtered_tabout, + process.out.log + process.out.versions + ).match() + }, + { assert path(process.out.filtered_tabout[0][1]).text.contains("46510803 46520182 9380 46510803 46510940 138 46520042 46520182 141 86.52 0 chr21") }, + ) + } + + } + + test("sarscov2 - genome_fasta - no helitrons") { + + when { + process { + """ + input[0] = [ + [ id:'test' ], + file(params.modules_testdata_base_path + 'genomics/sarscov2/genome/genome.fasta', checkIfExists: true) + ] + """ + } + } + + then { + assertAll( + { assert process.success }, + { assert snapshot( + process.out.gff3, + process.out.versions + ).match() + }, + { assert path(process.out.filtered_tabout[0][1]).text.contains("predictions are reported in the following way") }, + ) + } + + } + + test("homo_sapiens - genome_fasta - stub") { + + options '-stub' + + when { + process { + """ + input[0] = [ + [ id:'test' ], + file(params.modules_testdata_base_path + 'genomics/homo_sapiens/genome/genome.fasta', checkIfExists: true) + ] + """ + } + } + + then { + assertAll( + { assert process.success }, + { assert snapshot(process.out).match() } + ) + } + + } + +} From 28d08e007a21ed5d3bb41e66cb67b3cd1a31cb68 Mon Sep 17 00:00:00 2001 From: Joseph Guhlin Date: Thu, 28 Nov 2024 12:11:23 +1300 Subject: [PATCH 12/49] Typo --- modules/local/format_helitronscanner_out/tests/main.nf.test | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/local/format_helitronscanner_out/tests/main.nf.test b/modules/local/format_helitronscanner_out/tests/main.nf.test index d1808cd..5745153 100644 --- a/modules/local/format_helitronscanner_out/tests/main.nf.test +++ b/modules/local/format_helitronscanner_out/tests/main.nf.test @@ -26,7 +26,7 @@ nextflow_process { { assert process.success }, { assert snapshot( process.out.filtered_tabout, - process.out.log + process.out.log, process.out.versions ).match() }, From 5726e3944adde58acb631a2b3e5f438ff054798e Mon Sep 17 00:00:00 2001 From: Usman Rashid Date: Tue, 3 Dec 2024 14:33:19 +1300 Subject: [PATCH 13/49] Incorporated FORMAT_HELITRONSCANNER_OUT --- bin/format_helitronscanner_out.pl | 0 conf/modules.config | 4 ++ .../environment.yml | 2 +- .../local/format_helitronscanner_out/main.nf | 49 +++++++------- .../tests/environment.yml | 7 -- .../tests/main.nf.test | 65 ++----------------- .../tests/main.nf.test.snap | 63 ++++++++++++++++++ test/nf-test/small/main.nf.test.snap | 9 ++- test/nf-test/tiny/main.nf.test.snap | 9 ++- workflows/edta.nf | 19 ++++++ 10 files changed, 130 insertions(+), 97 deletions(-) mode change 100644 => 100755 bin/format_helitronscanner_out.pl delete mode 100644 modules/local/format_helitronscanner_out/tests/environment.yml create mode 100644 modules/local/format_helitronscanner_out/tests/main.nf.test.snap diff --git a/bin/format_helitronscanner_out.pl b/bin/format_helitronscanner_out.pl old mode 100644 new mode 100755 diff --git a/conf/modules.config b/conf/modules.config index 39b1af8..918b569 100644 --- a/conf/modules.config +++ b/conf/modules.config @@ -38,5 +38,9 @@ process { ext.prefix = { "${meta.id}.rc" } ext.args = '-pure_helitron' } + + withName: 'EDTA:FORMAT_HELITRONSCANNER_OUT' { + ext.args = '-sitefilter 1 -minscore 12 -keepshorter 1 -extlen 30 -extout 1' + } } \ No newline at end of file diff --git a/modules/local/format_helitronscanner_out/environment.yml b/modules/local/format_helitronscanner_out/environment.yml index d2f633e..b6205eb 100644 --- a/modules/local/format_helitronscanner_out/environment.yml +++ b/modules/local/format_helitronscanner_out/environment.yml @@ -4,4 +4,4 @@ channels: - conda-forge - bioconda dependencies: - - "bioconda::perl-bioperl=1.7.8" + - "conda-forge::perl=5.32.1" diff --git a/modules/local/format_helitronscanner_out/main.nf b/modules/local/format_helitronscanner_out/main.nf index 2364644..ebfe998 100644 --- a/modules/local/format_helitronscanner_out/main.nf +++ b/modules/local/format_helitronscanner_out/main.nf @@ -4,46 +4,35 @@ process FORMAT_HELITRONSCANNER_OUT { conda "${moduleDir}/environment.yml" container "${ workflow.containerEngine == 'singularity' && !task.ext.singularity_pull_docker_container ? - 'https://depot.galaxyproject.org/singularity/perl-bioperl:1.7.8--hdfd78af_1': - 'biocontainers/perl-bioperl:1.7.8--hdfd78af_1' }" + 'https://community-cr-prod.seqera.io/docker/registry/v2/blobs/sha256/c0/c0905ae7aa2a1c4135b448b49dd205b4eb370d4733b832594563ee06832ebd09/data': + 'community.wave.seqera.io/library/perl:67db1a8d53d64b14' }" input: tuple val(meta), path(genome) - path hel_fa // HelitronScanner.draw.hel.fa file - path rc_hel_fa // HelitronScanner.draw.rc.hel.fa file + path hel_fa + path rc_hel_fa output: - tuple val(meta), path("${meta.id}.HelitronScanner.filtered.tabout"), emit: filtered_tabout - tuple val(meta), path("${meta.id}.HelitronScanner.filtered.fa"), emit: filtered_fa, optional: true - tuple val(meta), path("${meta.id}.HelitronScanner.filtered.ext.fa"), emit: filtered_ext_fa, optional: true - tuple val(meta), path("${meta.id}.format_helitronscanner_out.log"), emit: log - path "versions.yml", emit: versions + tuple val(meta), path("*.HelitronScanner.filtered.tabout") , emit: filtered_tabout + tuple val(meta), path("*.HelitronScanner.filtered.fa") , emit: filtered_fa , optional: true + tuple val(meta), path("*.HelitronScanner.filtered.ext.fa") , emit: filtered_ext_fa , optional: true + tuple val(meta), path("*.format_helitronscanner_out.log") , emit: log + path "versions.yml" , emit: versions when: task.ext.when == null || task.ext.when script: - // Set default values for the optional arguments - def sitefilter = task.ext.sitefilter != null ? task.ext.sitefilter : 1 - def minscore = task.ext.minscore != null ? task.ext.minscore : 12 - def keepshorter = task.ext.keepshorter != null ? task.ext.keepshorter : 1 - def extlen = task.ext.extlen != null ? task.ext.extlen : 30 - def extout = task.ext.extout != null ? task.ext.extout : 1 - def prefix = task.ext.prefix ?: "${meta.id}" - def args = task.ext.args ?: '' - + def prefix = task.ext.prefix ?: "$genome" + def args = task.ext.args ?: '' """ # Create symbolic links to match expected filenames ln -s $hel_fa ${genome}.HelitronScanner.draw.hel.fa ln -s $rc_hel_fa ${genome}.HelitronScanner.draw.rc.hel.fa - perl format_helitronscanner_out.pl \\ + format_helitronscanner_out.pl \\ -genome $genome \\ - -sitefilter $sitefilter \\ - -minscore $minscore \\ - -keepshorter $keepshorter \\ - -extlen $extlen \\ - -extout $extout \\ + $args \\ &> >(tee "${prefix}.format_helitronscanner_out.log" 2>&1) cat <<-END_VERSIONS > versions.yml @@ -51,4 +40,16 @@ process FORMAT_HELITRONSCANNER_OUT { perl: \$(perl -v | sed -n 's|This is perl.*(\\(.*\\)).*|\\1|p') END_VERSIONS """ + + stub: + def prefix = task.ext.prefix ?: "$genome" + """ + touch ${prefix}.HelitronScanner.filtered.tabout + touch ${prefix}.format_helitronscanner_out.log + + cat <<-END_VERSIONS > versions.yml + "${task.process}": + perl: \$(perl -v | sed -n 's|This is perl.*(\\(.*\\)).*|\\1|p') + END_VERSIONS + """ } diff --git a/modules/local/format_helitronscanner_out/tests/environment.yml b/modules/local/format_helitronscanner_out/tests/environment.yml deleted file mode 100644 index d2f633e..0000000 --- a/modules/local/format_helitronscanner_out/tests/environment.yml +++ /dev/null @@ -1,7 +0,0 @@ ---- -# yaml-language-server: $schema=https://raw.githubusercontent.com/nf-core/modules/master/modules/environment-schema.json -channels: - - conda-forge - - bioconda -dependencies: - - "bioconda::perl-bioperl=1.7.8" diff --git a/modules/local/format_helitronscanner_out/tests/main.nf.test b/modules/local/format_helitronscanner_out/tests/main.nf.test index 5745153..864bc15 100644 --- a/modules/local/format_helitronscanner_out/tests/main.nf.test +++ b/modules/local/format_helitronscanner_out/tests/main.nf.test @@ -5,65 +5,10 @@ nextflow_process { process "FORMAT_HELITRONSCANNER_OUT" tag "modules" - tag "modules_nfcore" + tag "modules_local" tag "format_helitronscanner_out" - test("homo_sapiens - genome_21_fasta") { - - when { - process { - """ - input[0] = [ - [ id:'test' ], - 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.filtered_tabout, - process.out.log, - process.out.versions - ).match() - }, - { assert path(process.out.filtered_tabout[0][1]).text.contains("46510803 46520182 9380 46510803 46510940 138 46520042 46520182 141 86.52 0 chr21") }, - ) - } - - } - - test("sarscov2 - genome_fasta - no helitrons") { - - when { - process { - """ - input[0] = [ - [ id:'test' ], - file(params.modules_testdata_base_path + 'genomics/sarscov2/genome/genome.fasta', checkIfExists: true) - ] - """ - } - } - - then { - assertAll( - { assert process.success }, - { assert snapshot( - process.out.gff3, - process.out.versions - ).match() - }, - { assert path(process.out.filtered_tabout[0][1]).text.contains("predictions are reported in the following way") }, - ) - } - - } - - test("homo_sapiens - genome_fasta - stub") { + test("sarscov2 - genome_fasta - stub") { options '-stub' @@ -71,9 +16,11 @@ nextflow_process { process { """ input[0] = [ - [ id:'test' ], - file(params.modules_testdata_base_path + 'genomics/homo_sapiens/genome/genome.fasta', checkIfExists: true) + [ id:'sarscov2' ], + file(params.modules_testdata_base_path + 'genomics/sarscov2/genome/genome.fasta', checkIfExists: true) ] + input[1] = [] + input[2] = [] """ } } diff --git a/modules/local/format_helitronscanner_out/tests/main.nf.test.snap b/modules/local/format_helitronscanner_out/tests/main.nf.test.snap new file mode 100644 index 0000000..9079015 --- /dev/null +++ b/modules/local/format_helitronscanner_out/tests/main.nf.test.snap @@ -0,0 +1,63 @@ +{ + "sarscov2 - genome_fasta - stub": { + "content": [ + { + "0": [ + [ + { + "id": "sarscov2" + }, + "genome.fasta.HelitronScanner.filtered.tabout:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "1": [ + + ], + "2": [ + + ], + "3": [ + [ + { + "id": "sarscov2" + }, + "genome.fasta.format_helitronscanner_out.log:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "4": [ + "versions.yml:md5,250d1a5d5f60868f6cf0f17bd52edd91" + ], + "filtered_ext_fa": [ + + ], + "filtered_fa": [ + + ], + "filtered_tabout": [ + [ + { + "id": "sarscov2" + }, + "genome.fasta.HelitronScanner.filtered.tabout:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "log": [ + [ + { + "id": "sarscov2" + }, + "genome.fasta.format_helitronscanner_out.log:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "versions": [ + "versions.yml:md5,250d1a5d5f60868f6cf0f17bd52edd91" + ] + } + ], + "meta": { + "nf-test": "0.9.0", + "nextflow": "24.04.4" + }, + "timestamp": "2024-12-03T14:26:36.313536" + } +} \ No newline at end of file diff --git a/test/nf-test/small/main.nf.test.snap b/test/nf-test/small/main.nf.test.snap index 9154059..5a164b2 100644 --- a/test/nf-test/small/main.nf.test.snap +++ b/test/nf-test/small/main.nf.test.snap @@ -2,7 +2,7 @@ "small genome": { "content": [ { - "successful tasks": 15, + "successful tasks": 16, "versions": { "ANNOSINE": { "annosine": "2.0.7" @@ -10,6 +10,9 @@ "CAT_CAT": { "pigz": "2.3.4" }, + "FORMAT_HELITRONSCANNER_OUT": { + "perl": "v5.32.1" + }, "HELITRONSCANNER_DRAW": { "helitronscanner": "V1.1" }, @@ -59,8 +62,8 @@ ], "meta": { "nf-test": "0.9.0", - "nextflow": "24.10.1" + "nextflow": "24.04.4" }, - "timestamp": "2024-11-26T22:38:20.031297" + "timestamp": "2024-12-03T14:20:03.748125" } } \ No newline at end of file diff --git a/test/nf-test/tiny/main.nf.test.snap b/test/nf-test/tiny/main.nf.test.snap index 946ed5d..20a57ef 100644 --- a/test/nf-test/tiny/main.nf.test.snap +++ b/test/nf-test/tiny/main.nf.test.snap @@ -2,7 +2,7 @@ "tiny genome": { "content": [ { - "successful tasks": 13, + "successful tasks": 14, "versions": { "ANNOSINE": { "annosine": "2.0.7" @@ -10,6 +10,9 @@ "CAT_CAT": { "pigz": "2.3.4" }, + "FORMAT_HELITRONSCANNER_OUT": { + "perl": "v5.32.1" + }, "HELITRONSCANNER_DRAW": { "helitronscanner": "V1.1" }, @@ -53,8 +56,8 @@ ], "meta": { "nf-test": "0.9.0", - "nextflow": "24.10.1" + "nextflow": "24.04.4" }, - "timestamp": "2024-11-26T22:35:05.489004" + "timestamp": "2024-12-03T14:16:25.986375" } } \ No newline at end of file diff --git a/workflows/edta.nf b/workflows/edta.nf index cf99f9f..05a6da3 100644 --- a/workflows/edta.nf +++ b/workflows/edta.nf @@ -8,6 +8,7 @@ include { ANNOSINE } from '../modules/gallvp/annosine/m include { REPEATMODELER_BUILDDATABASE } from '../modules/nf-core/repeatmodeler/builddatabase/main.nf' include { REPEATMODELER_REPEATMODELER } from '../modules/nf-core/repeatmodeler/repeatmodeler/main.nf' include { FASTA_HELITRONSCANNER_SCAN_DRAW } from '../subworkflows/gallvp/fasta_helitronscanner_scan_draw/main.nf' +include { FORMAT_HELITRONSCANNER_OUT } from '../modules/local/format_helitronscanner_out/main.nf' include { softwareVersionsToYAML } from '../modules/local/utils/main.nf' @@ -132,6 +133,24 @@ workflow EDTA { ch_helitronscanner_draw_rc = FASTA_HELITRONSCANNER_SCAN_DRAW.out.helitronscanner_draw_rc ch_versions = ch_versions.mix(FASTA_HELITRONSCANNER_SCAN_DRAW.out.versions) + // MODULE: FORMAT_HELITRONSCANNER_OUT + ch_format_helitronscanner_inputs = ch_sanitized_fasta + | join(ch_helitronscanner_draw) + | join(ch_helitronscanner_draw_rc) + | multiMap { meta, fasta, draw, draw_rc -> + genome: [ meta, fasta ] + hel_fa: draw + rc_hel_fa: draw_rc + } + + FORMAT_HELITRONSCANNER_OUT ( + ch_format_helitronscanner_inputs.genome, + ch_format_helitronscanner_inputs.hel_fa, + ch_format_helitronscanner_inputs.rc_hel_fa, + ) + + ch_versions = ch_versions.mix(FORMAT_HELITRONSCANNER_OUT.out.versions.first()) + // Function: Save versions ch_versions = ch_versions From 104fac34041b41a9a29d5d20f983f945fcbf27b9 Mon Sep 17 00:00:00 2001 From: Usman Rashid Date: Thu, 5 Dec 2024 13:21:56 +1300 Subject: [PATCH 14/49] Renamed test to tests to avoid meta.id conflicts in nf-test and added LTR_RETRIEVER_POSTPROCESS --- .github/check_binaries.sh | 12 + .github/workflows/linting.yml | 20 + .pre-commit-config.yaml | 11 + bin/EDTA_raw.pl | 761 ++++++++++++++++++ bin/setup_LTR_retriever_postprocess.sh | 26 + conf/modules.config | 10 + conf/test.config | 2 +- .../ltr_retriever_postprocess/environment.yml | 10 + .../local/ltr_retriever_postprocess/main.nf | 80 ++ .../nf-core/ltrretriever/ltrretriever/main.nf | 2 + .../ltrretriever/tests/main.nf.test.snap | 14 +- nextflow.config | 4 + nf-test.config | 2 +- {test => tests}/Alyrata.test.fa | 0 {test => tests}/Col.test.fa | 0 {test => tests}/Ler.test.fa | 0 {test => tests}/README.txt | 0 {test => tests}/genome.cds.fa | 0 {test => tests}/genome.cds.list | 0 {test => tests}/genome.exclude.bed | 0 {test => tests}/genome.fa | 0 {test => tests}/nf-test/nextflow.config | 0 {test => tests}/nf-test/small/main.nf.test | 26 +- .../nf-test/small/main.nf.test.snap | 21 +- {test => tests}/nf-test/tiny/main.nf.test | 0 .../nf-test/tiny/main.nf.test.snap | 0 workflows/edta.nf | 44 +- 27 files changed, 1027 insertions(+), 18 deletions(-) create mode 100755 .github/check_binaries.sh create mode 100644 .github/workflows/linting.yml create mode 100644 .pre-commit-config.yaml create mode 100755 bin/EDTA_raw.pl create mode 100755 bin/setup_LTR_retriever_postprocess.sh create mode 100644 modules/local/ltr_retriever_postprocess/environment.yml create mode 100644 modules/local/ltr_retriever_postprocess/main.nf rename {test => tests}/Alyrata.test.fa (100%) rename {test => tests}/Col.test.fa (100%) rename {test => tests}/Ler.test.fa (100%) rename {test => tests}/README.txt (100%) rename {test => tests}/genome.cds.fa (100%) rename {test => tests}/genome.cds.list (100%) rename {test => tests}/genome.exclude.bed (100%) rename {test => tests}/genome.fa (100%) rename {test => tests}/nf-test/nextflow.config (100%) rename {test => tests}/nf-test/small/main.nf.test (50%) rename {test => tests}/nf-test/small/main.nf.test.snap (72%) rename {test => tests}/nf-test/tiny/main.nf.test (100%) rename {test => tests}/nf-test/tiny/main.nf.test.snap (100%) diff --git a/.github/check_binaries.sh b/.github/check_binaries.sh new file mode 100755 index 0000000..79e8784 --- /dev/null +++ b/.github/check_binaries.sh @@ -0,0 +1,12 @@ +#!/usr/bin/env bash + +set -euo pipefail + +EDTA_raw_pl_version=$(md5sum EDTA_raw.pl | cut -f1 -d' ') +bin_EDTA_raw_pl_version=$(md5sum bin/EDTA_raw.pl | cut -f1 -d' ') + +if [[ $EDTA_raw_pl_version != $bin_EDTA_raw_pl_version ]]; then + echo 'EDTA_raw.pl != bin/EDTA_raw.pl' + echo 'Please synchronize bin/EDTA_raw.pl with EDTA_raw.pl' + exit 1 +fi diff --git a/.github/workflows/linting.yml b/.github/workflows/linting.yml new file mode 100644 index 0000000..03b6e41 --- /dev/null +++ b/.github/workflows/linting.yml @@ -0,0 +1,20 @@ +name: linting +on: + pull_request: + +jobs: + pre-commit: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4 + + - name: Set up Python 3.12 + uses: actions/setup-python@82c7e631bb3cdc910f68e0081d67478d79c6982d # v5 + with: + python-version: "3.12" + + - name: Install pre-commit + run: pip install pre-commit + + - name: Run pre-commit + run: pre-commit run --all-files diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 0000000..650f6a9 --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,11 @@ +repos: + - repo: local + hooks: + - id: check_binaries + name: Check binaries + language: system + entry: > + .github/check_binaries.sh + always_run: true + fail_fast: true + pass_filenames: false diff --git a/bin/EDTA_raw.pl b/bin/EDTA_raw.pl new file mode 100755 index 0000000..31c3cf0 --- /dev/null +++ b/bin/EDTA_raw.pl @@ -0,0 +1,761 @@ +#!/usr/bin/env perl +use warnings; +use strict; +use FindBin; +use File::Basename; +use File::Spec; # for obtaining the real path of a file +use Pod::Usage; + +######################################################## +##### Perform initial searches for TE candidates #### +##### Shujun Ou (shujun.ou.1@gmail.com, 07/16/2020) #### +######################################################## + +## Input: +# $genome + +## Output: +# $genome.LTR.raw.fa, $genome.LTR.intact.raw.fa, $genome.LTR.intact.raw.gff3 +# $genome.TIR.intact.fa, $genome.TIR.intact.raw.gff3 +# $genome.Helitron.intact.raw.fa, $genome.Helitron.intact.raw.gff3 +# $genome.LINE.raw.fa, $genome.SINE.raw.fa + +my $usage = "\nObtain raw TE libraries using various structure-based programs + +perl EDTA_raw.pl [options] + --genome [File] The genome FASTA + --species [rice|maize|others] Specify the species for identification + of TIR candidates. Default: others + --type [ltr|tir|helitron|line|sine|all] + Specify which type of raw TE candidates + you want to get. Default: all + --rmlib [FASTA] The RepeatModeler library, classified output. + --overwrite [0|1] If previous results are found, decide to + overwrite (1, rerun) or not (0, default). + --convert_seq_name [0|1] Convert long sequence name to <= 15 + characters and remove annotations (1, + default) or use the original (0) + --u [float] Neutral mutation rate to calculate the age of intact LTR elements. + Intact LTR age is found in this file: *EDTA_raw/LTR/*.pass.list. + Default: 1.3e-8 (per bp per year, from rice). + --genometools [path] Path to the GenomeTools program. (default: find from ENV) + --annosine [path] Path to the AnnoSINE program. (default: find from EDTA/bin) + --ltrretriever [path] Path to the LTR_retriever program. (default: find from ENV) + --blastplus [path] Path to the BLAST+ program. (default: find from ENV) + --tesorter [path] Path to the TEsorter program. (default: find from ENV) + --GRF [path] Path to the GRF program. (default: find from ENV) + --trf_path [path] Path to the TRF program. (default: find from ENV) + --mdust [path] Path to the mdust program. (default: find from ENV) + --repeatmasker [path] Path to the RepeatMasker program. (default: find from ENV) + --repeatmodeler [path] Path to the RepeatModeler2 program. (default: find from ENV) + --threads|-t [int] Number of theads to run this script. Default: 4 + --help|-h Display this help info +\n"; + +# pre-defined +my $genome = ''; +my $species = 'others'; +my $type = 'all'; +my $RMlib = 'null'; +my $overwrite = 0; #0, no rerun. 1, rerun even old results exist. +my $convert_name = 1; #0, use original seq names; 1 shorten names. +my $maxint = 5000; #maximum interval length (bp) between TIRs (for GRF in TIR-Learner) +my $miu = 1.3e-8; #mutation rate, per bp per year, from rice +my $threads = 4; +my $script_path = $FindBin::Bin; +my $cleanup_misclas = "$script_path/bin/cleanup_misclas.pl"; +my $get_range = "$script_path/bin/get_range.pl"; +my $rename_LTR = "$script_path/bin/rename_LTR_skim.pl"; +my $rename_RM = "$script_path/bin/rename_RM_TE.pl"; +my $filter_gff = "$script_path/bin/filter_gff3.pl"; +my $rename_tirlearner = "$script_path/bin/rename_tirlearner.pl"; +my $call_seq = "$script_path/bin/call_seq_by_list.pl"; +my $output_by_list = "$script_path/bin/output_by_list.pl"; +my $cleanup_tandem = "$script_path/bin/cleanup_tandem.pl"; +my $get_ext_seq = "$script_path/bin/get_ext_seq.pl"; +my $format_helitronscanner = "$script_path/bin/format_helitronscanner_out.pl"; +my $flank_filter = "$script_path/bin/flanking_filter.pl"; +my $make_bed = "$script_path/bin/make_bed_with_intact.pl"; +my $bed2gff = "$script_path/bin/bed2gff.pl"; +my $genometools = ''; #path to the genometools program +my $repeatmasker = ''; #path to the RepeatMasker program +my $repeatmodeler = ''; #path to the RepeatModeler program +my $LTR_retriever = ''; #path to the LTR_retriever program +my $TEsorter = ''; #path to the TEsorter program +my $blastplus = ''; #path to the blastn program +my $mdust = ''; #path to mdust +my $trf = ''; #path to trf +my $GRF = ''; #path to GRF +my $annosine = ""; #path to the AnnoSINE program + +# my $TIR_Learner = "$script_path/bin/TIR-Learner3/"; #tianyulu +# my $LTR_FINDER = "$script_path/bin/LTR_FINDER_parallel/LTR_FINDER_parallel"; #tianyulu +# my $LTR_HARVEST = "$script_path/bin/LTR_HARVEST_parallel/LTR_HARVEST_parallel"; #tianyulu +my $HelitronScanner_Runner = "$script_path/bin/run_helitron_scanner.sh"; + +my $TIR_Learner = ""; #path to TIR-Learner3 program #tianyulu +my $LTR_FINDER = ""; #path to LTR_FINDER_parallel program #tianyulu +my $LTR_HARVEST = ""; #path to LTR_HARVEST_parallel program #tianyulu +my $HelitronScanner = ""; #path to HelitronScanner program #tianyulu + +my $help = undef; + +# read parameters +my $k=0; +foreach (@ARGV){ + $genome = $ARGV[$k+1] if /^--genome$/i and $ARGV[$k+1] !~ /^-/; + $species = $ARGV[$k+1] if /^--species$/i and $ARGV[$k+1] !~ /^-/; + $type = lc $ARGV[$k+1] if /^--type$/i and $ARGV[$k+1] !~ /^-/; + $RMlib = $ARGV[$k+1] if /^--rmlib$/i and $ARGV[$k+1] !~ /^-/; + $overwrite = $ARGV[$k+1] if /^--overwrite$/i and $ARGV[$k+1] !~ /^-/; + $convert_name = $ARGV[$k+1] if /^--convert_seq_name$/i and $ARGV[$k+1] !~ /^-/; + $miu = $ARGV[$k+1] if /^--u$/i and $ARGV[$k+1] !~ /^-/; + $genometools = $ARGV[$k+1] if /^--genometools/i and $ARGV[$k+1] !~ /^-/; + $repeatmasker = $ARGV[$k+1] if /^--repeatmasker$/i and $ARGV[$k+1] !~ /^-/; + $repeatmodeler = $ARGV[$k+1] if /^--repeatmodeler$/i and $ARGV[$k+1] !~ /^-/; + $annosine = $ARGV[$k+1] if /^--annosine$/i and $ARGV[$k+1] !~ /^-/; + $LTR_retriever = $ARGV[$k+1] if /^--ltrretriever/i and $ARGV[$k+1] !~ /^-/; + $TEsorter = $ARGV[$k+1] if /^--tesorter$/i and $ARGV[$k+1] !~ /^-/; + $blastplus = $ARGV[$k+1] if /^--blastplus$/i and $ARGV[$k+1] !~ /^-/; + $mdust = $ARGV[$k+1] if /^--mdust$/i and $ARGV[$k+1] !~ /^-/; + $trf = $ARGV[$k+1] if /^--trf_path$/i and $ARGV[$k+1] !~ /^-/; + $GRF = $ARGV[$k+1] if /^--GRF$/i and $ARGV[$k+1] !~ /^-/; + $threads = $ARGV[$k+1] if /^--threads$|^-t$/i and $ARGV[$k+1] !~ /^-/; + $help = 1 if /^--help$|^-h$/i; + $k++; + } + +# check files and parameters +if ($help){ + pod2usage( { + -verbose => 0, + -exitval => 0, + -message => "$usage\n" } ); + } + +if (!-s $genome){ + pod2usage( { + -message => "At least 1 parameter is required:\n1) Input fasta file: --genome\n". + "\n$usage\n\n", + -verbose => 0, + -exitval => 2 } ); + } + +if ($species){ + $species =~ s/rice/Rice/i; + $species =~ s/maize/Maize/i; + $species =~ s/others/others/i; + die "The expected value for the species parameter is Rice or Maize or others!\n" unless $species eq "Rice" or $species eq "Maize" or $species eq "others"; + } + +die "The expected value for the type parameter is ltr or tir or helitron or all!\n" unless $type eq "ltr" or $type eq "line" or $type eq "tir" or $type eq "helitron" or $type eq "sine" or $type eq "all"; + +# check bolean +if ($overwrite != 0 and $overwrite != 1){ die "The expected value for the overwrite parameter is 0 or 1!\n"}; +if ($convert_name != 0 and $convert_name != 1){ die "The expected value for the convert_seq_name parameter is 0 or 1!\n"}; +if ($threads !~ /^[0-9]+$/){ die "The expected value for the threads parameter is an integer!\n"}; +if ($miu !~ /[0-9\.e\-]+/){ die "The expected value for the u parameter is float value without units!\n"} + +chomp (my $date = `date`); +print STDERR "$date\tEDTA_raw: Check dependencies, prepare working directories.\n\n"; + +# check files and dependencies +# die "The LTR_FINDER_parallel is not found in $LTR_FINDER!\n" unless -s $LTR_FINDER; #tianyulu +# die "The LTR_HARVEST_parallel is not found in $LTR_HARVEST!\n" unless -s $LTR_HARVEST; #tianyulu +# die "The TIR_Learner is not found in $TIR_Learner!\n" unless -s "$TIR_Learner/bin/main.py"; #tianyulu +die "The script get_range.pl is not found in $get_range!\n" unless -s $get_range; +die "The script rename_LTR_skim.pl is not found in $rename_LTR!\n" unless -s $rename_LTR; +die "The script filter_gff3.pl is not found in $filter_gff!\n" unless -s $filter_gff; +die "The script call_seq_by_list.pl is not found in $call_seq!\n" unless -s $call_seq; +die "The script output_by_list.pl is not found in $output_by_list!\n" unless -s $output_by_list; +die "The script rename_tirlearner.pl is not found in $rename_tirlearner!\n" unless -s $rename_tirlearner; +die "The script cleanup_tandem.pl is not found in $cleanup_tandem!\n" unless -s $cleanup_tandem; +die "The script get_ext_seq.pl is not found in $get_ext_seq!\n" unless -s $get_ext_seq; +# die "The HelitronScanner is not found in $HelitronScanner!\n" unless -s $HelitronScanner; #tianyulu +die "The script format_helitronscanner_out.pl is not found in $format_helitronscanner!\n" unless -s $format_helitronscanner; +die "The script flanking_filter.pl is not found in $flank_filter!\n" unless -s $flank_filter; +die "The script bed2gff.pl is not found in $bed2gff!\n" unless -s $bed2gff; +die "The script make_bed_with_intact.pl is not found in $make_bed!\n" unless -s $make_bed; + +# GenomeTools +chomp ($genometools=`command -v gt 2>/dev/null`) if $genometools eq ''; +$genometools =~ s/\s+$//; +$genometools = dirname($genometools) unless -d $genometools; +$genometools="$genometools/" if $genometools ne '' and $genometools !~ /\/$/; +die "Error: gt is not found in the genometools path $genometools!\n" unless -X "${genometools}gt"; +# RepeatMasker +my $rand=int(rand(1000000)); +chomp ($repeatmasker=`command -v RepeatMasker 2>/dev/null`) if $repeatmasker eq ''; +$repeatmasker =~ s/\s+$//; +$repeatmasker = dirname($repeatmasker) unless -d $repeatmasker; +$repeatmasker="$repeatmasker/" if $repeatmasker ne '' and $repeatmasker !~ /\/$/; +die "Error: RepeatMasker is not found in the RepeatMasker path $repeatmasker!\n" unless -X "${repeatmasker}RepeatMasker"; +# `cp $script_path/database/dummy060817.fa ./dummy060817.fa.$rand`; +`cp \"$script_path/database/dummy060817.fa\" ./dummy060817.fa.$rand`; #tianyulu +my $RM_test=`${repeatmasker}RepeatMasker -e ncbi -q -pa 1 -no_is -nolow dummy060817.fa.$rand -lib dummy060817.fa.$rand 2>/dev/null`; +die "Error: The RMblast engine is not installed in RepeatMasker!\n" unless $RM_test=~s/done//gi; +`rm dummy060817.fa.$rand*`; +# RepeatModeler +chomp ($repeatmodeler=`command -v RepeatModeler 2>/dev/null`) if $repeatmodeler eq ''; +$repeatmodeler =~ s/\s+$//; +$repeatmodeler = dirname($repeatmodeler) unless -d $repeatmodeler; +$repeatmodeler="$repeatmodeler/" if $repeatmodeler ne '' and $repeatmodeler !~ /\/$/; +die "Error: RepeatModeler is not found in the RepeatModeler path $repeatmodeler!\n" unless -X "${repeatmodeler}RepeatModeler"; +# AnnoSINE +chomp ($annosine=`command -v AnnoSINE_v2 2>/dev/null`) if $annosine eq ''; +$annosine =~ s/\s+$//; +$annosine = dirname($annosine) unless -d $annosine; +$annosine="$annosine/" if $annosine ne '' and $annosine !~ /\/$/; +die "Error: AnnoSINE is not found in the AnnoSINE path $annosine!\n" unless (-X "${annosine}AnnoSINE_v2"); +# LTR_retriever +chomp ($LTR_retriever=`command -v LTR_retriever 2>/dev/null`) if $LTR_retriever eq ''; +$LTR_retriever =~ s/\s+$//; +$LTR_retriever = dirname($LTR_retriever) unless -d $LTR_retriever; +$LTR_retriever="$LTR_retriever/" if $LTR_retriever ne '' and $LTR_retriever !~ /\/$/; +die "Error: LTR_retriever is not found in the LTR_retriever path $LTR_retriever!\n" unless -X "${LTR_retriever}LTR_retriever"; +# TEsorter +chomp ($TEsorter=`command -v TEsorter 2>/dev/null`) if $TEsorter eq ''; +$TEsorter =~ s/\s+$//; +$TEsorter = dirname($TEsorter) unless -d $TEsorter; +$TEsorter="$TEsorter/" if $TEsorter ne '' and $TEsorter !~ /\/$/; +die "Error: TEsorter is not found in the TEsorter path $TEsorter!\n" unless -X "${TEsorter}TEsorter"; +# makeblastdb, blastn +chomp ($blastplus=`command -v makeblastdb 2>/dev/null`) if $blastplus eq ''; +$blastplus =~ s/\s+$//; +$blastplus = dirname($blastplus) unless -d $blastplus; +$blastplus="$blastplus/" if $blastplus ne '' and $blastplus !~ /\/$/; +die "Error: makeblastdb is not found in the BLAST+ path $blastplus!\n" unless -X "${blastplus}makeblastdb"; +die "Error: blastn is not found in the BLAST+ path $blastplus!\n" unless -X "${blastplus}blastn"; +# mdust +chomp ($mdust=`command -v mdust 2>/dev/null`) if $mdust eq ''; +$mdust =~ s/\s+$//; +$mdust = dirname($mdust) unless -d $mdust; +$mdust = "$mdust/" if $mdust ne '' and $mdust !~ /\/$/; +die "Error: mdust is not found in the mdust path $mdust!\n" unless -X "${mdust}mdust"; +# trf +chomp ($trf=`command -v trf 2>/dev/null`) if $trf eq ''; +$trf=~s/\n$//; +`$trf 2>/dev/null`; +die "Error: Tandem Repeat Finder is not found in the TRF path $trf!\n" if $?==32256; +# GRF +chomp ($GRF = `command -v grf-main 2>/dev/null`) if $GRF eq ''; +$GRF =~ s/\n$//; +my $grfp= dirname ($GRF); +$grfp =~ s/\n$//; +`${grfp}grf-main 2>/dev/null`; +die "Error: The Generic Repeat Finder (GRF) is not found in the GRF path: $grfp\n" if $?==32256; + +# # TIR-Learner3 +# chomp ($TIR_Learner = `which TIR-Learner 2>/dev/null`) if $TIR_Learner eq ''; +# $TIR_Learner =~ s/\n$//; +# my $tirp= dirname ($TIR_Learner); +# $tirp =~ s/\n$//; +# `${tirp}TIR-Learner 2>/dev/null`; +# die "Error: TIR-Learner3 is not found in the TIR-Learner path: $tirp\n" if $?==32256; + +# TIR-Learner #tianyuLu +# Remove any trailing whitespace +$TIR_Learner =~ s/\s+$//; +if ($TIR_Learner eq "") { + # Find TIR-Learner path and remove any trailing newline + chomp ($TIR_Learner=`command -v TIR-Learner 2>/dev/null`); + die "Error: TIR-Learner not installed!\n" if $TIR_Learner eq ""; + # $TIR_Learner =~ s/\s+$//; +} else { + # # Extract directory name from path if path is not a directory + # $TIR_Learner = dirname($TIR_Learner) unless -d $TIR_Learner; + # If path is directory + if (-d $TIR_Learner) { + # # Add trailing slash if path not empty string and not already end with slash + # $TIR_Learner .= "/" if $TIR_Learner ne "" and $TIR_Learner !~ /\/$/; + # Add trailing slash if path not already end with slash + $TIR_Learner .= "/" if $TIR_Learner !~ /\/$/; + $TIR_Learner = "python3 $TIR_Learner/TIR-Learner.py"; + } +} +`$TIR_Learner 2>/dev/null`; +die "Error: TIR-Learner is not found in the path $TIR_Learner!\n" if $?==32256 || $?==2; + +# LTR_FINDER_parallel #tianyuLu +$LTR_FINDER =~ s/\s+$//; +if ($LTR_FINDER eq "") { + chomp ($LTR_FINDER=`command -v LTR_FINDER_parallel 2>/dev/null`); + die "Error: LTR_FINDER_parallel not installed!\n" if $LTR_FINDER eq ""; +} else { + if (-d $LTR_FINDER) { + $LTR_FINDER .= "/" if $LTR_FINDER !~ /\/$/; + $LTR_FINDER = "perl $LTR_FINDER/LTR_FINDER_parallel"; + } +} +`$LTR_FINDER 2>/dev/null`; +die "Error: LTR_FINDER_parallel is not found in the path $LTR_FINDER!\n" if $?==32256 || $?==2; + +# LTR_HARVEST_parallel #tianyuLu +$LTR_HARVEST =~ s/\s+$//; +if ($LTR_HARVEST eq "") { + chomp ($LTR_HARVEST=`command -v LTR_HARVEST_parallel 2>/dev/null`); + die "Error: LTR_HARVEST_parallel not installed!\n" if $LTR_HARVEST eq ""; +} else { + if (-d $LTR_HARVEST) { + $LTR_HARVEST .= "/" if $LTR_HARVEST !~ /\/$/; + $LTR_HARVEST = "perl $LTR_HARVEST/LTR_HARVEST_parallel"; + } +} +`$LTR_HARVEST 2>/dev/null`; +die "Error: LTR_HARVEST_parallel is not found in the path $LTR_HARVEST!\n" if $?==32256 || $?==2; + +# HelitronScanner #tianyuLu +$HelitronScanner =~ s/\s+$//; +if ($HelitronScanner eq "") { + chomp ($HelitronScanner=`command -v HelitronScanner 2>/dev/null`); + die "Error: HelitronScanner not installed!\n" if $HelitronScanner eq ""; +} else { + if (-d $HelitronScanner) { + $HelitronScanner .= "/" if $HelitronScanner !~ /\/$/; + } +} + +# make a softlink to the genome +my $genome_file = basename($genome); +`ln -s $genome $genome_file` unless -e $genome_file; +$genome = $genome_file; + +# check $RMlib +if ($RMlib ne 'null'){ + if (-e $RMlib){ + print "\tA RepeatModeler library $RMlib is provided via --rmlib. Please make sure this is a RepeatModeler2 generated and classified library (some levels of unknown classification is OK).\n\n"; + chomp ($RMlib = `realpath $RMlib`); + `ln -s $RMlib $genome.RM2.raw.fa` unless -s "$genome.RM2.raw.fa"; + $RMlib = "$genome.RM2.raw.fa"; + } else { + die "\tERROR: The RepeatModeler library $RMlib you specified is not found!\n\n"; + } + } + +# check if duplicated sequences found +my $id_mode = 0; #record the mode of id conversion. +my $id_len = `grep \\> $genome|perl -ne 'chomp; s/>//g; my \$len=length \$_; \$max=\$len if \$max<\$len; print "\$max\\n"'`; #find out the longest sequence ID length in the genome +$id_len =~ s/\s+$//; +$id_len = (split /\s+/, $id_len)[-1]; +my $raw_id = `grep \\> $genome|wc -l`; +my $old_id = `grep \\> $genome|sort -u|wc -l`; +if ($raw_id > $old_id){ + chomp ($date = `date`); + die "$date\tERROR: Identical sequence IDs found in the provided genome! Please resolve this issue and try again.\n"; + } + +if ($convert_name == 1){ +if (-s "$genome.mod"){ + $genome = "$genome.mod"; + } else { + +# remove sequence annotations (content after the first space in sequence names) and replace special characters with _, convert non-ATGC bases into Ns +`perl -nle 'my \$info=(split)[0]; \$info=~s/[\\~!@#\\\$%\\^&\\*\\(\\)\\+\\\-\\=\\?\\[\\]\\{\\}\\:;",\\<\\/\\\\\|]+/_/g; \$info=~s/_+/_/g; \$info=~s/[^ATGCN]/N/gi unless /^>/; print \$info' $genome > $genome.$rand.mod`; + +# try to shortern sequences +my $id_len_max = 13; # allowed longest length of a sequence ID in the input file +if ($id_len > $id_len_max){ + chomp ($date = `date`); + print "$date\tThe longest sequence ID in the genome contains $id_len characters, which is longer than the limit ($id_len_max)\n"; + print "\tTrying to reformat seq IDs...\n\t\tAttempt 1...\n"; + `perl -lne 'chomp; if (s/^>+//) {s/^\\s+//; \$_=(split)[0]; s/(.{1,$id_len_max}).*/>\$1/g;} print "\$_"' $genome.$rand.mod > $genome.$rand.temp`; + my $new_id = `grep \\> $genome.$rand.temp|sort -u|wc -l`; + chomp ($date = `date`); + if ($old_id == $new_id){ + $id_mode = 1; + `mv $genome.$rand.temp $genome.mod`; + `rm $genome.$rand.mod 2>/dev/null`; + print "$date\tSeq ID conversion successful!\n\n"; + } else { + print "\t\tAttempt 2...\n"; + `perl -ne 'chomp; if (/^>/) {\$_=">\$1" if /([0-9]+)/;} print "\$_\n"' $genome.$rand.mod > $genome.$rand.temp`; + $new_id = `grep \\> $genome.$rand.temp|sort -u|wc -l`; + if ($old_id == $new_id){ + $id_mode = 2; + `mv $genome.$rand.temp $genome.mod`; + `rm $genome.$rand.mod 2>/dev/null`; + print "$date\tSeq ID conversion successful!\n\n"; + } else { + `rm $genome.$rand.temp $genome.$rand.mod 2>/dev/null`; + die "$date\tERROR: Fail to convert seq IDs to <= $id_len_max characters! Please provide a genome with shorter seq IDs.\n\n"; + } + } + } else { + `mv $genome.$rand.mod $genome.mod`; + } +$genome = "$genome.mod"; +} +} + +# Make working directories +`mkdir $genome.EDTA.raw` unless -e "$genome.EDTA.raw" && -d "$genome.EDTA.raw"; +`mkdir $genome.EDTA.raw/LTR` unless -e "$genome.EDTA.raw/LTR" && -d "$genome.EDTA.raw/LTR"; +`mkdir $genome.EDTA.raw/SINE` unless -e "$genome.EDTA.raw/SINE" && -d "$genome.EDTA.raw/SINE"; +`mkdir $genome.EDTA.raw/LINE` unless -e "$genome.EDTA.raw/LINE" && -d "$genome.EDTA.raw/LINE"; +`mkdir $genome.EDTA.raw/TIR` unless -e "$genome.EDTA.raw/TIR" && -d "$genome.EDTA.raw/TIR"; +`mkdir $genome.EDTA.raw/Helitron` unless -e "$genome.EDTA.raw/Helitron" && -d "$genome.EDTA.raw/Helitron"; + + +########################### +###### LTR_retriever ###### +########################### + +if ($type eq "ltr" or $type eq "all"){ + +chomp ($date = `date`); +print STDERR "$date\tStart to find LTR candidates.\n\n"; + +# enter the working directory and create genome softlink +chdir "$genome.EDTA.raw/LTR"; +`ln -s ../../$genome $genome` unless -s $genome; + +# Try to recover existing results +chomp ($date = `date`); +if ($overwrite eq 0 and -s "$genome.LTR.raw.fa"){ + print STDERR "$date\tExisting result file $genome.LTR.raw.fa found!\n\t\tWill keep this file without rerunning this module.\n\t\tPlease specify --overwrite 1 if you want to rerun this module.\n\n"; + } else { + print STDERR "$date\tIdentify LTR retrotransposon candidates from scratch.\n\n"; + +# run LTRharvest +if ($overwrite eq 0 and -s "$genome.harvest.combine.scn"){ + print STDERR "$date\tExisting raw result $genome.harvest.scn found!\n\t\tWill use this for further analyses.\n\n"; + } else { + # `perl $LTR_HARVEST -seq $genome -threads $threads -gt $genometools -size 1000000 -time 300`; + `$LTR_HARVEST -seq $genome -threads $threads -gt $genometools -size 1000000 -time 300`; #tianyulu + } + +# run LTR_FINDER_parallel +if ($overwrite eq 0 and -s "$genome.finder.combine.scn"){ + print STDERR "$date\tExisting raw result $genome.finder.combine.scn found!\n\t\tWill use this for further analyses.\n\n"; + } else { + # `perl $LTR_FINDER -seq $genome -threads $threads -harvest_out -size 1000000 -time 300`; + `$LTR_FINDER -seq $genome -threads $threads -harvest_out -size 1000000 -time 300`; #tianyulu + } + +# run LTR_retriever +if ($overwrite eq 0 and -s "$genome.LTRlib.fa"){ + print STDERR "$date\tExisting LTR_retriever result $genome.LTRlib.fa found!\n\t\tWill use this for further analyses.\n\n"; + } else { + `cat $genome.harvest.combine.scn $genome.finder.combine.scn > $genome.rawLTR.scn`; + `${LTR_retriever}LTR_retriever -genome $genome -inharvest $genome.rawLTR.scn -u $miu -threads $threads -noanno -trf_path $trf -blastplus $blastplus -repeatmasker $repeatmasker`; + } + +# get full-length LTR from pass.list +`awk '{if (\$1 !~ /#/) print \$1"\\t"\$1}' $genome.pass.list | perl $call_seq - -C $genome > $genome.LTR.intact.fa.ori`; +`perl -i -nle 's/\\|.*//; print \$_' $genome.LTR.intact.fa.ori`; +`perl $rename_LTR $genome.LTR.intact.fa.ori $genome.defalse > $genome.LTR.intact.fa.anno`; +`mv $genome.LTR.intact.fa.anno $genome.LTR.intact.fa.ori`; + +# remove simple repeats and candidates with simple repeats at terminals +`${mdust}mdust $genome.LTR.intact.fa.ori > $genome.LTR.intact.fa.ori.dusted`; +`perl $cleanup_tandem -misschar N -nc 50000 -nr 0.9 -minlen 100 -minscore 3000 -trf 1 -trf_path $trf -cleanN 1 -cleanT 1 -f $genome.LTR.intact.fa.ori.dusted > $genome.LTR.intact.fa.ori.dusted.cln`; + +# annotate and remove not LTR candidates +`${TEsorter}TEsorter $genome.LTR.intact.fa.ori.dusted.cln --disable-pass2 -p $threads 2>/dev/null`; +`perl $cleanup_misclas $genome.LTR.intact.fa.ori.dusted.cln.rexdb.cls.tsv`; +`mv $genome.LTR.intact.fa.ori.dusted.cln.cln $genome.LTR.intact.raw.fa`; +`mv $genome.LTR.intact.fa.ori.dusted.cln.cln.list $genome.LTR.intact.raw.fa.anno.list`; +`cp $genome.LTR.intact.raw.fa.anno.list ../`; + +# generate annotated output and gff +`perl $output_by_list 1 $genome.LTR.intact.fa.ori 1 $genome.LTR.intact.raw.fa -FA -ex|grep \\>|perl -nle 's/>//; print "Name\\t\$_"' > $genome.LTR.intact.fa.ori.rmlist`; +`perl $filter_gff $genome.pass.list.gff3 $genome.LTR.intact.fa.ori.rmlist | perl -nle 's/LTR_retriever/EDTA/gi; print \$_' > $genome.LTR.intact.raw.gff3`; +`rm $genome`; + } + +# copy result files out +`touch $genome.LTRlib.fa` unless -e "$genome.LTRlib.fa"; +`cp $genome.LTRlib.fa $genome.LTR.raw.fa`; +`cp $genome.LTRlib.fa ../$genome.LTR.raw.fa`; +`cp $genome.LTR.intact.raw.fa $genome.LTR.intact.raw.gff3 ../ 2>/dev/null`; +`cp $genome.LTR.intact.fa ../$genome.LTR.intact.raw.fa` if -s "$genome.LTR.intact.fa"; +`cp $genome.LTR.intact.gff3 ../$genome.LTR.intact.raw.gff3` if -s "$genome.LTR.intact.gff3"; +chdir '../..'; + +# check results +chomp ($date = `date`); +die "Error: LTR results not found!\n\n" unless -e "$genome.EDTA.raw/$genome.LTR.raw.fa"; +if (-s "$genome.EDTA.raw/$genome.LTR.raw.fa"){ + print STDERR "$date\tFinish finding LTR candidates.\n\n"; + } else { + print STDERR "$date\tWarning: The LTR result file has 0 bp!\n\n"; + } + +} + + +############################# +###### AnnoSINE ###### +############################# +if ($type eq "sine" or $type eq "all"){ + +chomp ($date = `date`); +print STDERR "$date\tStart to find SINE candidates.\n\n"; + +# enter the working directory and create genome softlink +chdir "$genome.EDTA.raw/SINE"; +`ln -s ../../$genome $genome` unless -s $genome; + +# Remove existing results +`rm -rf Seed_SINE.fa Step* HMM_out 2>/dev/null` if $overwrite eq 1; + +# run AnnoSINE_v2 +my $status; # record status of AnnoSINE execution +if (-s "Seed_SINE.fa"){ + print STDERR "$date\tExisting result file Seed_SINE.fa found!\n\t\tWill keep this file without rerunning this module.\n\t\tPlease specify --overwrite 1 if you want to rerun AnnoSINE_v2.\n\n"; + } else { + $status = system("python3 ${annosine}AnnoSINE_v2 -t $threads -a 2 --num_alignments 50000 -rpm 0 --copy_number 3 --shift 100 -auto 1 3 $genome ./ > /dev/null 2>&1"); + } + +# filter and reclassify AnnoSINE candidates with TEsorter and make SINE library +if (-s "Seed_SINE.fa"){ + # annotate and remove non-SINE candidates + `awk '{gsub(/Unknown/, "unknown"); print \$1}' Seed_SINE.fa > $genome.AnnoSINE.raw.fa`; + `${TEsorter}TEsorter $genome.AnnoSINE.raw.fa --disable-pass2 -p $threads 2>/dev/null`; + `touch $genome.AnnoSINE.raw.fa.rexdb.cls.tsv` unless -e "$genome.AnnoSINE.raw.fa.rexdb.cls.tsv"; + `perl $cleanup_misclas $genome.AnnoSINE.raw.fa.rexdb.cls.tsv`; + + # clean up tandem repeat + `perl $cleanup_tandem -misschar N -nc 50000 -nr 0.8 -minlen 80 -minscore 3000 -trf 1 -trf_path $trf -cleanN 1 -cleanT 1 -f $genome.AnnoSINE.raw.fa.cln > $genome.SINE.raw.fa`; + } +elsif ($status == 0) { + print "\t\tAnnoSINE is finished without error, but the Seed_SINE.fa file is not produced.\n\n"; + `touch $genome.SINE.raw.fa`; + } +else { + print "\t\tAnnoSINE exited with error, please test run AnnoSINE to make sure it's working.\n\n"; + } + +# copy result files out +`cp $genome.SINE.raw.fa ../`; +chdir '../..'; + +# check results +chomp ($date = `date`); +die "Error: SINE results not found!\n\n" unless -e "$genome.EDTA.raw/$genome.SINE.raw.fa"; +if (-s "$genome.EDTA.raw/$genome.SINE.raw.fa"){ + print STDERR "$date\tFinish finding SINE candidates.\n\n"; + } else { + print STDERR "$date\tWarning: The SINE result file has 0 bp!\n\n"; + } + +} + + +############################# +###### RepeatModeler ###### +############################# + +if ($type eq "line" or $type eq "all"){ + +chomp ($date = `date`); +print STDERR "$date\tStart to find LINE candidates.\n\n"; + +# enter the working directory and create genome softlink +chdir "$genome.EDTA.raw/LINE"; +`ln -s ../../$genome $genome` unless -s $genome; +`cp ../../$RMlib $RMlib` if $RMlib ne 'null'; + +# Try to recover existing results or run RepeatModeler2 +chomp ($date = `date`); +if ($overwrite eq 0 and -s $RMlib){ + if (-s "$genome-families.fa"){ + print STDERR "$date\tExisting result file $genome-families.fa found!\n\t\tWill not use the provided RepeatModeler2 library since --overwrite 0.\n\t\tPlease specify --overwrite 1 if you want to use the provided --rmlib file.\n\n"; + } else { + `cp $RMlib "$genome-families.fa" 2>/dev/null`; + } + } + +if ($overwrite eq 0 and -s "$genome-families.fa"){ + print STDERR "$date\tExisting result file $genome-families.fa found!\n\t\tWill keep this file without rerunning this module.\n\t\tPlease specify --overwrite 1 if you want to rerun this module.\n\n"; + } else { + # run RepeatModeler2 + print STDERR "$date\tIdentify LINE retrotransposon candidates from scratch.\n\n"; + my $status; # record status of RepeatModeler execution + `${repeatmodeler}BuildDatabase -name $genome $genome`; + $status = system("${repeatmodeler}RepeatModeler -engine ncbi -threads $threads -database $genome > repeatmodeler.log 2>&1"); + if ($status != 0) { + # Execute the old version of RepeatModeler + $status = system("${repeatmodeler}RepeatModeler -engine ncbi -pa $threads -database $genome > repeatmodeler.log 2>&1"); + print "ERROR: RepeatModeler did not run correctly. Please test run this command: + ${repeatmodeler}RepeatModeler -engine ncbi -pa $threads -database $genome + " and exit unless $status == 0; + } + `rm $genome.nhr $genome.nin $genome.nnd $genome.nni $genome.nog $genome.nsq $genome.njs $genome.translation 2>/dev/null`; + } + +# filter and reclassify RepeatModeler candidates with TEsorter and make LINE library +if (-s "$genome-families.fa"){ + # annotate and remove misclassified candidates + `awk '{gsub(/Unknown/, "unknown"); print \$1}' $genome-families.fa > $genome.RM2.raw.fa` if -e "$genome-families.fa"; + `${TEsorter}TEsorter $genome.RM2.raw.fa --disable-pass2 -p $threads 2>/dev/null`; + `perl $cleanup_misclas $genome.RM2.raw.fa.rexdb.cls.tsv`; + + # reclassify clean candidates + `${TEsorter}TEsorter $genome.RM2.raw.fa.cln --disable-pass2 -p $threads 2>/dev/null`; + `perl -nle 's/>\\S+\\s+/>/; print \$_' $genome.RM2.raw.fa.cln.rexdb.cls.lib > $genome.RM2.raw.fa.cln`; + + # clean up tandem repeat + `perl $cleanup_tandem -misschar N -nc 50000 -nr 0.8 -minlen 80 -minscore 3000 -trf 1 -trf_path $trf -cleanN 1 -cleanT 1 -f $genome.RM2.raw.fa.cln > $genome.RM2.raw.fa.cln2`; + `grep -P 'LINE|SINE' $genome.RM2.raw.fa.cln2 | perl $output_by_list 1 $genome.RM2.raw.fa.cln2 1 - -FA > $genome.LINE.raw.fa`; + `grep -P 'LINE|SINE' $genome.RM2.raw.fa.cln2 | perl $output_by_list 1 $genome.RM2.raw.fa.cln2 1 - -FA -ex > $genome.RM2.fa`; + } else { + print "\t\tRepeatModeler is finished, but the $genome-families.fa file is not produced.\n\n"; + `touch $genome.RM2.raw.fa $genome.LINE.raw.fa $genome.RM2.fa`; + } + +# copy result files out +`cp $genome.LINE.raw.fa $genome.RM2.fa ../`; #update the filtered RM2 result in the EDTA/raw folder +`cp $genome.RM2.raw.fa ../../`; #update the raw RM2 result in the EDTA folder +chdir '../..'; + +# check results +chomp ($date = `date`); +die "Error: LINE results not found!\n\n" unless -e "$genome.EDTA.raw/$genome.LINE.raw.fa"; +if (-s "$genome.EDTA.raw/$genome.LINE.raw.fa"){ + print STDERR "$date\tFinish finding LINE candidates.\n\n"; + } else { + print STDERR "$date\tWarning: The LINE result file has 0 bp!\n\n"; + } +} + + +########################### +###### TIR-Learner ###### +########################### + +if ($type eq "tir" or $type eq "all"){ + +chomp ($date = `date`); +print STDERR "$date\tStart to find TIR candidates.\n\n"; + +# pre-set parameters +my $genome_file_real_path=File::Spec->rel2abs($genome); # the genome file with real path + +# enter the working directory and create genome softlink +chdir "$genome.EDTA.raw/TIR"; +`ln -s ../../$genome $genome` unless -s $genome; + +# Try to recover existing results +chomp ($date = `date`); +if ($overwrite eq 0 and (-s "$genome.TIR.intact.raw.fa" or -s "$genome.TIR.intact.fa")){ + print STDERR "$date\tExisting result file $genome.TIR.intact.raw.fa found!\n\t\tWill keep this file without rerunning this module.\n\t\tPlease specify --overwrite 1 if you want to rerun this module.\n\n"; + } else { + print STDERR "$date\tIdentify TIR candidates from scratch.\n\n"; + print STDERR "Species: $species\n"; + + # run TIR-Learner + if ($overwrite eq 0 and -s "./TIR-Learner-Result/TIR-Learner_FinalAnn.fa"){ + print STDERR "$date\tExisting raw result TIR-Learner_FinalAnn.fa found!\n\t\tWill use this for further analyses.\n\t\tPlease specify --overwrite 1 if you want to rerun this module.\n\n"; + } else { + `$TIR_Learner -f $genome_file_real_path -s $species -t $threads -l $maxint -c -o $genome_file_real_path.EDTA.raw/TIR --grf_path $grfp --gt_path $genometools`; #TianyuLu + } + + # clean raw predictions with flanking alignment + `perl $rename_tirlearner ./TIR-Learner-Result/TIR-Learner_FinalAnn.fa | perl -nle 's/TIR-Learner_//g; print \$_' > $genome.TIR`; + `perl $get_ext_seq $genome $genome.TIR`; + `perl $flank_filter -genome $genome -query $genome.TIR.ext30.fa -miniden 90 -mincov 0.9 -maxct 20 -blastplus $blastplus -t $threads`; + + # recover superfamily info + `perl $output_by_list 1 $genome.TIR 1 $genome.TIR.ext30.fa.pass.fa -FA -MSU0 -MSU1 > $genome.TIR.ext30.fa.pass.fa.ori`; + + # remove simple repeats and candidates with simple repeats at terminals + `${mdust}mdust $genome.TIR.ext30.fa.pass.fa.ori > $genome.TIR.ext30.fa.pass.fa.dusted`; + `perl $cleanup_tandem -misschar N -nc 50000 -nr 0.9 -minlen 80 -minscore 3000 -trf 1 -trf_path $trf -cleanN 1 -cleanT 1 -f $genome.TIR.ext30.fa.pass.fa.dusted > $genome.TIR.ext30.fa.pass.fa.dusted.cln`; + + # annotate and remove non-TIR candidates + `${TEsorter}TEsorter $genome.TIR.ext30.fa.pass.fa.dusted.cln --disable-pass2 -p $threads 2>/dev/null`; + `perl $cleanup_misclas $genome.TIR.ext30.fa.pass.fa.dusted.cln.rexdb.cls.tsv`; + `mv $genome.TIR.ext30.fa.pass.fa.dusted.cln.cln $genome.TIR.intact.raw.fa`; + `cp $genome.TIR.ext30.fa.pass.fa.dusted.cln.cln.list $genome.TIR.intact.raw.fa.anno.list`; + `cp $genome.TIR.intact.raw.fa.anno.list ../`; + + # get gff3 of intact TIR elements + `perl -nle 's/\\-\\+\\-/_Len:/; my (\$chr, \$method, \$supfam, \$s, \$e, \$anno) = (split)[0,1,2,3,4,8]; my \$class='DNA'; \$class='MITE' if \$e-\$s+1 <= 600; my (\$tir, \$iden, \$tsd)=(\$1, \$2/100, \$3) if \$anno=~/TIR:(.*)_([0-9.]+)_TSD:([a-z0-9._]+)_LEN/i; print "\$chr \$s \$e \$chr:\$s..\$e \$class/\$supfam structural \$iden . . . TSD=\$tsd;TIR=\$tir"' ./TIR-Learner-Result/TIR-Learner_FinalAnn.gff3 | perl $output_by_list 4 - 1 $genome.TIR.intact.raw.fa -MSU0 -MSU1 > $genome.TIR.intact.raw.bed`; + `perl $bed2gff $genome.TIR.intact.raw.bed TIR > $genome.TIR.intact.raw.gff3`; + } + +# copy result files out +`cp $genome.TIR.intact.bed ../$genome.TIR.intact.raw.bed` if -s "$genome.TIR.intact.bed"; # recover /dev/null`; +chdir '../..'; + +# check results +chomp ($date = `date`); +die "Error: TIR results not found!\n\n" unless -e "$genome.EDTA.raw/$genome.TIR.intact.raw.fa"; +if (-s "$genome.EDTA.raw/$genome.TIR.intact.raw.fa"){ + print STDERR "$date\tFinish finding TIR candidates.\n\n"; + } else { + print STDERR "Warning: The TIR result file has 0 bp!\n\n"; + } + +} + + +############################# +###### HelitronScanner ###### +############################# + +if ($type eq "helitron" or $type eq "all"){ + +chomp ($date = `date`); +print STDERR "$date\tStart to find Helitron candidates.\n\n"; + +# enter the working directory and create genome softlink +chdir "$genome.EDTA.raw/Helitron"; +`ln -s ../../$genome $genome` unless -s $genome; + +# Try to recover existing results +chomp ($date = `date`); +if ($overwrite eq 0 and (-s "$genome.Helitron.intact.raw.fa" or -s "$genome.Helitron.intact.fa")){ + print STDERR "$date\tExisting result file $genome.Helitron.intact.raw.fa found!\n\t\tWill keep this file without rerunning this module.\n\t\tPlease specify --overwrite 1 if you want to rerun this module.\n\n"; + } else { + print STDERR "$date\tIdentify Helitron candidates from scratch.\n\n"; + +# run HelitronScanner +`$HelitronScanner_Runner $genome $threads \"$HelitronScanner\"`; + +# filter candidates based on repeatness of flanking regions +`perl $format_helitronscanner -genome $genome -sitefilter 1 -minscore 12 -keepshorter 1 -extlen 30 -extout 1`; +`perl $flank_filter -genome $genome -query $genome.HelitronScanner.filtered.ext.fa -miniden 90 -mincov 0.9 -maxct 5 -blastplus $blastplus -t $threads`; #more relaxed +#`perl $flank_filter -genome $genome -query $genome.HelitronScanner.filtered.ext.fa -miniden 80 -mincov 0.8 -maxct 5 -blastplus $blastplus -t $threads`; #more stringent + +# remove simple repeats and candidates with simple repeats at terminals +`perl $output_by_list 1 $genome.HelitronScanner.filtered.fa 1 $genome.HelitronScanner.filtered.ext.fa.pass.fa -FA > $genome.HelitronScanner.filtered.fa.pass.fa`; +`${mdust}mdust $genome.HelitronScanner.filtered.fa.pass.fa > $genome.HelitronScanner.filtered.fa.pass.fa.dusted`; +`perl $cleanup_tandem -misschar N -nc 50000 -nr 0.9 -minlen 100 -minscore 3000 -trf 1 -trf_path $trf -cleanN 1 -cleanT 1 -f $genome.HelitronScanner.filtered.fa.pass.fa.dusted | perl -nle 's/^(>.*)\\s+(.*)\$/\$1#DNA\\/Helitron\\t\$2/; print \$_' > $genome.HelitronScanner.filtered.fa.pass.fa.dusted.cln`; + +# annotate and remove non-Helitron candidates +`${TEsorter}TEsorter $genome.HelitronScanner.filtered.fa.pass.fa.dusted.cln --disable-pass2 -p $threads 2>/dev/null`; +`perl $cleanup_misclas $genome.HelitronScanner.filtered.fa.pass.fa.dusted.cln.rexdb.cls.tsv`; +`mv $genome.HelitronScanner.filtered.fa.pass.fa.dusted.cln.cln $genome.Helitron.intact.raw.fa`; +`cp $genome.HelitronScanner.filtered.fa.pass.fa.dusted.cln.cln.list $genome.Helitron.intact.raw.fa.anno.list`; +`cp $genome.Helitron.intact.raw.fa.anno.list ../`; + +# get intact Helitrons and gff3 +`perl $make_bed $genome.Helitron.intact.raw.fa > $genome.Helitron.intact.raw.bed`; +`perl $bed2gff $genome.Helitron.intact.raw.bed HEL > $genome.Helitron.intact.raw.gff3`; + } + +# copy result files out +`cp $genome.Helitron.intact.bed ../$genome.Helitron.intact.raw.bed` if -s "$genome.Helitron.intact.bed"; # recover /dev/null`; +chdir '../..'; + +# check results +chomp ($date = `date`); +die "Error: Helitron results not found!\n\n" unless -e "$genome.EDTA.raw/$genome.Helitron.intact.raw.fa"; +if (-s "$genome.EDTA.raw/$genome.Helitron.intact.raw.fa"){ + print STDERR "$date\tFinish finding Helitron candidates.\n\n"; + } else { + print STDERR "$date\tWarning: The Helitron result file has 0 bp!\n\n"; + } + +} + +chomp ($date = `date`); +print STDERR "$date\tExecution of EDTA_raw.pl is finished!\n\n"; diff --git a/bin/setup_LTR_retriever_postprocess.sh b/bin/setup_LTR_retriever_postprocess.sh new file mode 100755 index 0000000..140b4b5 --- /dev/null +++ b/bin/setup_LTR_retriever_postprocess.sh @@ -0,0 +1,26 @@ +#!/usr/bin/env bash + +script_path=$(dirname $0) + +cat > input/LTR_retriever_postprocess.pl << EOF + +my \$genome = 'genome'; +my \$threads = $1; + +my \$call_seq = '$script_path/call_seq_by_list.pl'; +my \$rename_LTR = '$script_path/rename_LTR_skim.pl'; +my \$mdust = ''; # Assumed on PATH +my \$cleanup_tandem = '$script_path/cleanup_tandem.pl'; +my \$trf = '$(which trf)'; +my \$TEsorter = ''; # Assumed on PATH +my \$cleanup_misclas= '$script_path/cleanup_misclas.pl'; +my \$output_by_list = '$script_path/output_by_list.pl'; +my \$filter_gff = '$script_path/filter_gff3.pl'; + +EOF + +sed -n 443,462p "$script_path/EDTA_raw.pl" \ + >> input/LTR_retriever_postprocess.pl + +sed -n 466,472p "$script_path/EDTA_raw.pl" \ + >> input/LTR_retriever_postprocess.pl diff --git a/conf/modules.config b/conf/modules.config index 918b569..c521005 100644 --- a/conf/modules.config +++ b/conf/modules.config @@ -42,5 +42,15 @@ process { withName: 'EDTA:FORMAT_HELITRONSCANNER_OUT' { ext.args = '-sitefilter 1 -minscore 12 -keepshorter 1 -extlen 30 -extout 1' } + + withName: 'EDTA:LTR_RETRIEVER_POSTPROCESS' { + ext.prefix = { "${meta.id}.pp" } + + publishDir = [ + path: { "${params.outdir}/ltr" }, + mode: params.publish_dir_mode, + saveAs: { filename -> filename.equals('versions.yml') ? null : filename } + ] + } } \ No newline at end of file diff --git a/conf/test.config b/conf/test.config index e921133..e0a2931 100644 --- a/conf/test.config +++ b/conf/test.config @@ -7,5 +7,5 @@ process { } params { - genomes = 'https://raw.githubusercontent.com/nf-core/test-datasets/modules/data/genomics/sarscov2/genome/genome.fasta' + genomes = 'https://raw.githubusercontent.com/jguhlin/EDTA/a2cd9a0777e4ac6e39545bacc3e752f94eb2f389/test/genome.fa' } \ No newline at end of file diff --git a/modules/local/ltr_retriever_postprocess/environment.yml b/modules/local/ltr_retriever_postprocess/environment.yml new file mode 100644 index 0000000..6f8a4fc --- /dev/null +++ b/modules/local/ltr_retriever_postprocess/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: + - bioconda::mdust=2006.10.17 + - bioconda::tesorter=1.4.7 + - bioconda::trf=4.09.1 + - conda-forge::perl=5.32.1 \ No newline at end of file diff --git a/modules/local/ltr_retriever_postprocess/main.nf b/modules/local/ltr_retriever_postprocess/main.nf new file mode 100644 index 0000000..c957229 --- /dev/null +++ b/modules/local/ltr_retriever_postprocess/main.nf @@ -0,0 +1,80 @@ +process LTR_RETRIEVER_POSTPROCESS { + tag "$meta.id" + label 'process_low' + + conda "${moduleDir}/environment.yml" + container "${ workflow.containerEngine == 'singularity' && !task.ext.singularity_pull_docker_container ? + 'https://community-cr-prod.seqera.io/docker/registry/v2/blobs/sha256/64/64d26063bedc2efcba20750a408a21f50907986d0d9aee685b03d7d05d3fbd8b/data': + 'community.wave.seqera.io/library/mdust_tesorter_trf_perl:3424609103d3b065' }" + + input: + tuple val(meta), path(genome, name: 'input/genome') + path "input/genome.pass.list" + path "input/genome.pass.list.gff3" + path "input/genome.defalse" + path "input/genome.LTRlib.fa" + + output: + tuple val(meta), path('*.LTR.raw.fa') , emit: raw_fa + tuple val(meta), path('*.LTR.intact.raw.fa') , emit: intact_raw_fa + tuple val(meta), path('*.LTR.intact.raw.gff3') , emit: intact_raw_gff3 + tuple val(meta), path('*.LTR.intact.raw.fa.anno.list') , emit: anno_list + path "versions.yml" , emit: versions + + when: + task.ext.when == null || task.ext.when + + script: + def prefix = task.ext.prefix ?: "${meta.id}" + def MDUST_VERSION = '2006.10.17' // WARN: Manually update when changing Bioconda assets + if ( "$prefix" == 'genome' ) error "Input and output names are the same, use \"task.ext.prefix\" to disambiguate!" + """ + setup_LTR_retriever_postprocess.sh \\ + $task.cpus + + cd input + + perl LTR_retriever_postprocess.pl + + cd - + + mv \\ + genome.LTR.raw.fa \\ + ${prefix}.LTR.raw.fa + + mv \\ + genome.LTR.intact.raw.fa \\ + ${prefix}.genome.LTR.intact.raw.fa + + mv \\ + genome.LTR.intact.raw.gff3 \\ + ${prefix}.LTR.intact.raw.gff3 + + mv \\ + genome.LTR.intact.raw.fa.anno.list \\ + ${prefix}.LTR.intact.raw.fa.anno.list + + cat <<-END_VERSIONS > versions.yml + "${task.process}": + perl: \$(perl -v | sed -n 's|This is perl.*(\\(.*\\)).*|\\1|p') + trf: \$(trf -v |& sed -n 's|.*Version \\(.*\\)|\\1|p') + TEsorter: \$(TEsorter -v | tr -d 'TEsorter ') + mdust: $MDUST_VERSION + END_VERSIONS + """ + + stub: + def prefix = task.ext.prefix ?: "${meta.id}" + def MDUST_VERSION = '2006.10.17' // WARN: Manually update when changing Bioconda assets + if ( "$prefix" == 'genome' ) error "Input and output names are the same, use \"task.ext.prefix\" to disambiguate!" + """ + + cat <<-END_VERSIONS > versions.yml + "${task.process}": + perl: \$(perl -v | sed -n 's|This is perl.*(\\(.*\\)).*|\\1|p') + trf: \$(trf -v |& sed -n 's|.*Version \\(.*\\)|\\1|p') + TEsorter: \$(TEsorter -v | tr -d 'TEsorter ') + mdust: $MDUST_VERSION + END_VERSIONS + """ +} \ No newline at end of file diff --git a/modules/nf-core/ltrretriever/ltrretriever/main.nf b/modules/nf-core/ltrretriever/ltrretriever/main.nf index 8e1e2be..ca97232 100644 --- a/modules/nf-core/ltrretriever/ltrretriever/main.nf +++ b/modules/nf-core/ltrretriever/ltrretriever/main.nf @@ -21,6 +21,7 @@ process LTRRETRIEVER_LTRRETRIEVER { tuple val(meta), path("*.LTRlib.fa") , emit: ltrlib , optional: true tuple val(meta), path("${prefix}.out") , emit: annotation_out , optional: true tuple val(meta), path("*.out.gff3") , emit: annotation_gff , optional: true + tuple val(meta), path("*.defalse") , emit: defalse , optional: true path "versions.yml" , emit: versions when: @@ -66,6 +67,7 @@ process LTRRETRIEVER_LTRRETRIEVER { mv "${writable_genome}.LTRlib.fa" "${prefix}.LTRlib.fa" || echo ".LTRlib.fa was not produced" mv "${writable_genome}.out" "${prefix}.out" || echo ".out was not produced" mv "${writable_genome}.out.gff3" "${prefix}.out.gff3" || echo ".out.gff3 was not produced" + mv "${writable_genome}.defalse" "${prefix}.defalse" || echo ".defalse was not produced" cat <<-END_VERSIONS > versions.yml "${task.process}": diff --git a/modules/nf-core/ltrretriever/ltrretriever/tests/main.nf.test.snap b/modules/nf-core/ltrretriever/ltrretriever/tests/main.nf.test.snap index 825b1e5..4942e1a 100644 --- a/modules/nf-core/ltrretriever/ltrretriever/tests/main.nf.test.snap +++ b/modules/nf-core/ltrretriever/ltrretriever/tests/main.nf.test.snap @@ -61,6 +61,9 @@ ] ], "6": [ + + ], + "7": [ "versions.yml:md5,3ab159acaee06b342b56e2d35e5e669b" ], "annotation_gff": [ @@ -78,6 +81,9 @@ }, "test.out:md5,d41d8cd98f00b204e9800998ecf8427e" ] + ], + "defalse": [ + ], "log": [ [ @@ -117,10 +123,10 @@ } ], "meta": { - "nf-test": "0.8.4", - "nextflow": "23.10.1" + "nf-test": "0.9.0", + "nextflow": "24.04.4" }, - "timestamp": "2024-02-19T11:04:15.954424" + "timestamp": "2024-12-05T12:39:11.669541" }, "annotation_out": { "content": [ @@ -151,4 +157,4 @@ }, "timestamp": "2024-07-16T14:03:52.324194" } -} +} \ No newline at end of file diff --git a/nextflow.config b/nextflow.config index ccf5850..7eb1232 100644 --- a/nextflow.config +++ b/nextflow.config @@ -1,3 +1,7 @@ +params { + publish_dir_mode = 'copy' +} + process { cpus = { 1 * task.attempt } diff --git a/nf-test.config b/nf-test.config index c3a9a1e..29d7e09 100644 --- a/nf-test.config +++ b/nf-test.config @@ -1,7 +1,7 @@ config { testsDir "." workDir System.getenv("NFT_WORKDIR") ?: ".nf-test" - configFile "test/nf-test/nextflow.config" + configFile "tests/nf-test/nextflow.config" plugins { load "nft-bam@0.4.0" diff --git a/test/Alyrata.test.fa b/tests/Alyrata.test.fa similarity index 100% rename from test/Alyrata.test.fa rename to tests/Alyrata.test.fa diff --git a/test/Col.test.fa b/tests/Col.test.fa similarity index 100% rename from test/Col.test.fa rename to tests/Col.test.fa diff --git a/test/Ler.test.fa b/tests/Ler.test.fa similarity index 100% rename from test/Ler.test.fa rename to tests/Ler.test.fa diff --git a/test/README.txt b/tests/README.txt similarity index 100% rename from test/README.txt rename to tests/README.txt diff --git a/test/genome.cds.fa b/tests/genome.cds.fa similarity index 100% rename from test/genome.cds.fa rename to tests/genome.cds.fa diff --git a/test/genome.cds.list b/tests/genome.cds.list similarity index 100% rename from test/genome.cds.list rename to tests/genome.cds.list diff --git a/test/genome.exclude.bed b/tests/genome.exclude.bed similarity index 100% rename from test/genome.exclude.bed rename to tests/genome.exclude.bed diff --git a/test/genome.fa b/tests/genome.fa similarity index 100% rename from test/genome.fa rename to tests/genome.fa diff --git a/test/nf-test/nextflow.config b/tests/nf-test/nextflow.config similarity index 100% rename from test/nf-test/nextflow.config rename to tests/nf-test/nextflow.config diff --git a/test/nf-test/small/main.nf.test b/tests/nf-test/small/main.nf.test similarity index 50% rename from test/nf-test/small/main.nf.test rename to tests/nf-test/small/main.nf.test index 7d40a02..ce37a4f 100644 --- a/test/nf-test/small/main.nf.test +++ b/tests/nf-test/small/main.nf.test @@ -13,7 +13,28 @@ nextflow_pipeline { } then { - def stable_path = getAllFilesFromDir(params.outdir, false, ['pipeline_info/*.{html,json,txt,yml}'], null, ['**']) + + def stable_path = getAllFilesFromDir( + params.outdir, + false, + [ + 'pipeline_info/*.{html,json,txt,yml}', + 'ltr/*.pp.LTR.intact.raw.gff3', + 'ltr/*.pp.LTR.raw.fa', + ], + null, + ['**'] + ) + + def stable_name = getAllFilesFromDir( + params.outdir, + true, + [ + 'pipeline_info/*.{html,json,txt,yml}' + ], + null, + ['**'] + ) assertAll( { assert workflow.success}, @@ -21,7 +42,8 @@ nextflow_pipeline { [ 'successful tasks': workflow.trace.succeeded().size(), 'versions': removeNextflowVersion("$outputDir/pipeline_info/software_versions.yml"), - 'stable paths': stable_path + 'stable paths': stable_path, + 'stable names': getRelativePath(stable_name, outputDir), ] ).match() } ) diff --git a/test/nf-test/small/main.nf.test.snap b/tests/nf-test/small/main.nf.test.snap similarity index 72% rename from test/nf-test/small/main.nf.test.snap rename to tests/nf-test/small/main.nf.test.snap index 5a164b2..a943b0a 100644 --- a/test/nf-test/small/main.nf.test.snap +++ b/tests/nf-test/small/main.nf.test.snap @@ -2,7 +2,7 @@ "small genome": { "content": [ { - "successful tasks": 16, + "successful tasks": 17, "versions": { "ANNOSINE": { "annosine": "2.0.7" @@ -42,6 +42,12 @@ "LTRRETRIEVER_LTRRETRIEVER": { "LTR_retriever": "v2.9.9" }, + "LTR_RETRIEVER_POSTPROCESS": { + "perl": "v5.32.1", + "trf": 4.09, + "TEsorter": "1.4.7", + "mdust": "2006.10.17" + }, "REPEATMODELER_BUILDDATABASE": { "repeatmodeler": "2.0.5" }, @@ -56,7 +62,16 @@ } }, "stable paths": [ - + "genome.pp.LTR.intact.raw.fa.anno.list:md5,51261f020350b448acdf89d8fb625448", + "genome.pp.genome.LTR.intact.raw.fa:md5,ae2336f498897d1af4e8d3be28bb3f25" + ], + "stable names": [ + "ltr", + "ltr/genome.pp.LTR.intact.raw.fa.anno.list", + "ltr/genome.pp.LTR.intact.raw.gff3", + "ltr/genome.pp.LTR.raw.fa", + "ltr/genome.pp.genome.LTR.intact.raw.fa", + "pipeline_info" ] } ], @@ -64,6 +79,6 @@ "nf-test": "0.9.0", "nextflow": "24.04.4" }, - "timestamp": "2024-12-03T14:20:03.748125" + "timestamp": "2024-12-05T12:10:52.622584" } } \ No newline at end of file diff --git a/test/nf-test/tiny/main.nf.test b/tests/nf-test/tiny/main.nf.test similarity index 100% rename from test/nf-test/tiny/main.nf.test rename to tests/nf-test/tiny/main.nf.test diff --git a/test/nf-test/tiny/main.nf.test.snap b/tests/nf-test/tiny/main.nf.test.snap similarity index 100% rename from test/nf-test/tiny/main.nf.test.snap rename to tests/nf-test/tiny/main.nf.test.snap diff --git a/workflows/edta.nf b/workflows/edta.nf index 05a6da3..a4799cf 100644 --- a/workflows/edta.nf +++ b/workflows/edta.nf @@ -9,11 +9,14 @@ include { REPEATMODELER_BUILDDATABASE } from '../modules/nf-core/repeatmod include { REPEATMODELER_REPEATMODELER } from '../modules/nf-core/repeatmodeler/repeatmodeler/main.nf' include { FASTA_HELITRONSCANNER_SCAN_DRAW } from '../subworkflows/gallvp/fasta_helitronscanner_scan_draw/main.nf' include { FORMAT_HELITRONSCANNER_OUT } from '../modules/local/format_helitronscanner_out/main.nf' +include { LTR_RETRIEVER_POSTPROCESS } from '../modules/local/ltr_retriever_postprocess/main.nf' include { softwareVersionsToYAML } from '../modules/local/utils/main.nf' workflow EDTA { + main: + // Versions channel ch_versions = Channel.empty() @@ -36,7 +39,6 @@ workflow EDTA { // MODULE: LTRHARVEST LTRHARVEST ( ch_sanitized_fasta ) - ch_ltrharvest_gff3 = LTRHARVEST.out.gff3 ch_ltrharvest_scn = LTRHARVEST.out.scn ch_versions = ch_versions.mix(LTRHARVEST.out.versions) @@ -44,7 +46,6 @@ workflow EDTA { // MODULE: LTRFINDER LTRFINDER { ch_sanitized_fasta } - ch_ltrfinder_gff3 = LTRFINDER.out.gff ch_ltrfinder_scn = LTRFINDER.out.scn ch_versions = ch_versions.mix(LTRFINDER.out.versions) @@ -62,17 +63,17 @@ workflow EDTA { ch_ltrretriever_inputs = ch_sanitized_fasta.join(ch_ltr_candidates) LTRRETRIEVER_LTRRETRIEVER ( - ch_ltrretriever_inputs.map { meta, fasta, ltr -> [ meta, fasta ] }, - ch_ltrretriever_inputs.map { meta, fasta, ltr -> ltr }, + ch_ltrretriever_inputs.map { meta, fasta, _ltr -> [ meta, fasta ] }, + ch_ltrretriever_inputs.map { _meta, _fasta, ltr -> ltr }, [], [], [] ) - ch_ltrretriever_log = LTRRETRIEVER_LTRRETRIEVER.out.log ch_pass_list = LTRRETRIEVER_LTRRETRIEVER.out.pass_list - ch_annotation_out = LTRRETRIEVER_LTRRETRIEVER.out.annotation_out + ch_pass_list_gff = LTRRETRIEVER_LTRRETRIEVER.out.pass_list_gff ch_annotation_gff = LTRRETRIEVER_LTRRETRIEVER.out.annotation_gff + ch_defalse = LTRRETRIEVER_LTRRETRIEVER.out.defalse ch_ltrlib = LTRRETRIEVER_LTRRETRIEVER.out.ltrlib ch_versions = ch_versions.mix(LTRRETRIEVER_LTRRETRIEVER.out.versions.first()) @@ -102,7 +103,7 @@ workflow EDTA { def size = fasta.size() def size_threshold = 100_000 // bytes -> bp - // TODO: Not the best way to set a size threshould + // TODO: Not the best way to set a size threshold // but it is simple // This is needed to avoid, // Error: Database genome is not large enough ( minimum 40000 bp ) to process with RepeatModeler. @@ -151,6 +152,32 @@ workflow EDTA { ch_versions = ch_versions.mix(FORMAT_HELITRONSCANNER_OUT.out.versions.first()) + // MODULE: LTR_RETRIEVER_POSTPROCESS + ltr_retriever_postprocess_inputs = ch_sanitized_fasta + | join(ch_pass_list) + | join(ch_pass_list_gff) + | join(ch_annotation_gff) + | join(ch_defalse) + | join(ch_ltrlib) + | multiMap { meta, fasta, pass, p_gff, a_gff, defalse, ltr -> + genome: [ meta, fasta ] + pass : pass + p_gff : p_gff + a_gff : a_gff + defalse : defalse + ltr : ltr + } + + LTR_RETRIEVER_POSTPROCESS ( + ltr_retriever_postprocess_inputs.genome, + ltr_retriever_postprocess_inputs.pass, + ltr_retriever_postprocess_inputs.p_gff, + ltr_retriever_postprocess_inputs.defalse, + ltr_retriever_postprocess_inputs.ltr, + ) + + ch_versions = ch_versions.mix(LTR_RETRIEVER_POSTPROCESS.out.versions.first()) + // Function: Save versions ch_versions = ch_versions @@ -168,4 +195,7 @@ workflow EDTA { cache: false ) + emit: + versions_yml = ch_versions_yml // [ software_versions.yml ] + } \ No newline at end of file From 427f3db1677fad77b0f0ffc9a3c1c9cd7b366a8b Mon Sep 17 00:00:00 2001 From: Usman Rashid Date: Thu, 5 Dec 2024 15:52:06 +1300 Subject: [PATCH 15/49] Fixed singularity container --- modules/local/ltr_retriever_postprocess/main.nf | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/local/ltr_retriever_postprocess/main.nf b/modules/local/ltr_retriever_postprocess/main.nf index c957229..e679be6 100644 --- a/modules/local/ltr_retriever_postprocess/main.nf +++ b/modules/local/ltr_retriever_postprocess/main.nf @@ -4,7 +4,7 @@ process LTR_RETRIEVER_POSTPROCESS { 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/64/64d26063bedc2efcba20750a408a21f50907986d0d9aee685b03d7d05d3fbd8b/data': + 'https://community-cr-prod.seqera.io/docker/registry/v2/blobs/sha256/cb/cb105ba7d586ab31c1f39feb04c0255a39cc5a55ae7f6ea53f4bf76cdba8a3e5/data': 'community.wave.seqera.io/library/mdust_tesorter_trf_perl:3424609103d3b065' }" input: From f68a619e98b05e34eb81b36312cd7195f9a37fa5 Mon Sep 17 00:00:00 2001 From: Usman Rashid Date: Thu, 5 Dec 2024 16:22:50 +1300 Subject: [PATCH 16/49] Fixed nextflow-setup version --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 20d4b62..d53140c 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -56,7 +56,7 @@ jobs: uses: actions/checkout@v4.2.1 - name: Install Nextflow - uses: nf-core/setup-nextflow@v2 + uses: nf-core/setup-nextflow@v2.0.0 with: version: "${{ matrix.NXF_VER }}" From f0491de3b57d4bf78dda24fe5af8e347c8255c1c Mon Sep 17 00:00:00 2001 From: Usman Rashid Date: Mon, 25 Nov 2024 13:23:12 +1300 Subject: [PATCH 17/49] Multiple changes --- .github/include.yaml | 10 ++ .github/workflows/ci.yml | 115 ++++++++++++-------- .gitignore | 2 + cleanNXF.sh | 15 +++ conf/test.config | 11 ++ main.nf | 162 ++++++++++++++++++++++++++++ modules/local/utils/main.nf | 44 ++++++++ nextflow.config | 99 +++++++++++++++++ nf-test.config | 10 ++ test/nf-test/nextflow.config | 22 ++++ test/nf-test/tiny/main.nf.test | 32 ++++++ test/nf-test/tiny/main.nf.test.snap | 42 ++++++++ 12 files changed, 517 insertions(+), 47 deletions(-) create mode 100644 .github/include.yaml create mode 100755 cleanNXF.sh create mode 100644 conf/test.config create mode 100755 main.nf create mode 100644 modules/local/utils/main.nf create mode 100644 nextflow.config create mode 100644 nf-test.config create mode 100644 test/nf-test/nextflow.config create mode 100644 test/nf-test/tiny/main.nf.test create mode 100644 test/nf-test/tiny/main.nf.test.snap diff --git a/.github/include.yaml b/.github/include.yaml new file mode 100644 index 0000000..5d850e1 --- /dev/null +++ b/.github/include.yaml @@ -0,0 +1,10 @@ +".": + - ./.github/workflows/** + - ./nf-test.config +tests: + - ./assets/* + - ./bin/* + - ./conf/* + - ./main.nf + - ./nextflow_schema.json + - ./nextflow.config diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 6c27abd..43d4c6c 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1,78 +1,99 @@ name: EDTA Nextflow CI on: - push: - branches: - - nextflow_reboot pull_request: - branches: - - nextflow_reboot env: NXF_ANSI_LOG: false - NXF_SINGULARITY_CACHEDIR: ${{ github.workspace }}/.singularity - NXF_SINGULARITY_LIBRARYDIR: ${{ github.workspace }}/.singularity + NFT_WORKDIR: "~" + NFT_DIFF: "pdiff" + NFT_DIFF_ARGS: "--line-numbers --expand-tabs=2" concurrency: group: "${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}" cancel-in-progress: true - jobs: + nf-test-changes: + name: Check for changes + runs-on: ubuntu-latest + outputs: + nf_test_files: ${{ steps.list.outputs.components }} + steps: + - uses: actions/checkout@v4.2.1 + with: + fetch-depth: 0 + + - name: List nf-test files + id: list + uses: adamrtalbot/detect-nf-test-changes@v0.0.4 + with: + head: ${{ github.sha }} + base: origin/${{ github.base_ref }} + include: .github/include.yaml + + - name: print list of nf-test files + run: | + echo ${{ steps.list.outputs.components }} + test: - name: Run pipeline with test data - # Only run on push if this is the jguhlin nextflow_reboot branch (merged PRs) - if: "${{ github.event_name != 'push' || (github.event_name == 'push' && github.repository == 'jguhlin/EDTA') }}" + name: ${{ matrix.nf_test_files }} ${{ matrix.profile }} NF-${{ matrix.NXF_VER }} + needs: [nf-test-changes] + if: needs.nf-test-changes.outputs.nf_test_files != '[]' runs-on: ubuntu-latest strategy: + fail-fast: false matrix: - nextflow: - - '24.04.4' + NXF_VER: + - "24.04.2" + + nf_test_files: ["${{ fromJson(needs.nf-test-changes.outputs.nf_test_files) }}"] profile: - - docker - - singularity - - conda + - "docker" steps: - name: Check out pipeline code - uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4 + uses: actions/checkout@v4.2.1 - name: Install Nextflow uses: nf-core/setup-nextflow@v2 with: - version: ${{ matrix.nextflow }} + version: "${{ matrix.NXF_VER }}" - # - name: Disk space cleanup - # uses: jlumbroso/free-disk-space@54081f138730dfa15788a46383842cd2f914a1be # v1.3.1 - # Will be needed with large data sets. Can take long to run + - uses: actions/setup-python@v5.2.0 + with: + python-version: "3.11" + architecture: "x64" - - name: Setup apptainer - if: matrix.profile == 'singularity' - uses: eWaterCycle/setup-apptainer@main - - - name: Set up Singularity - if: matrix.profile == 'singularity' + - name: Install pdiff to see diff between nf-test snapshots run: | - mkdir -p $NXF_SINGULARITY_CACHEDIR - mkdir -p $NXF_SINGULARITY_LIBRARYDIR - - - name: Set up miniconda - if: matrix.profile == 'conda' - uses: conda-incubator/setup-miniconda@a4260408e20b96e80095f42ff7f1a15b27dd94ca # v3 + python -m pip install --upgrade pip + pip install pdiff + + - uses: nf-core/setup-nf-test@v1.1.2 with: - miniconda-version: "latest" - auto-update-conda: true - channels: conda-forge,bioconda + version: 0.9.0 - - name: Conda setup - if: matrix.profile == 'conda' + - name: Disk space cleanup + if: matrix.nf_test_files == 'tests/stub/main.nf.test' + uses: jlumbroso/free-disk-space@v1.3.1 + + - name: Run nf-test run: | - conda clean -a - conda install -n base conda-libmamba-solver - conda config --set solver libmamba - echo $(realpath $CONDA)/condabin >> $GITHUB_PATH - echo $(realpath python) >> $GITHUB_PATH + nf-test test --verbose ${{ matrix.nf_test_files }} --profile "+${{ matrix.profile }}" + + confirm-pass: + runs-on: ubuntu-latest + needs: [test] + if: always() + steps: + - name: All tests ok + if: ${{ !contains(needs.*.result, 'failure') }} + run: exit 0 + - name: One or more tests failed + if: ${{ contains(needs.*.result, 'failure') }} + run: exit 1 - - name: Run pipeline with test data + - name: debug-print + if: always() run: | - nextflow run \ - ${GITHUB_WORKSPACE} \ - -profile ${{ matrix.profile }},test + echo "toJSON(needs) = ${{ toJSON(needs) }}" + echo "toJSON(needs.*.result) = ${{ toJSON(needs.*.result) }}" diff --git a/.gitignore b/.gitignore index 9b1e821..f636970 100644 --- a/.gitignore +++ b/.gitignore @@ -3,3 +3,5 @@ bin/TIR-Learner*/Module3/Maize_model.sav work/ .nextflow.log* .nextflow/* +results/ +.nf-test* diff --git a/cleanNXF.sh b/cleanNXF.sh new file mode 100755 index 0000000..81b8d85 --- /dev/null +++ b/cleanNXF.sh @@ -0,0 +1,15 @@ +#!/usr/bin/env bash + +rm -rf .nextflow* +echo "Cleaned .nextflow..." +rm -rf .nextflow.pid +echo "Cleaned .nextflow.pid..." +for i in $(ls work | grep -v "conda"); +do + rm -rf "work/$i" +done +echo "Cleaned work..." + +rm -f .nf-test.log +rm -rf .nf-test +echo "Cleaned nf-test..." diff --git a/conf/test.config b/conf/test.config new file mode 100644 index 0000000..e921133 --- /dev/null +++ b/conf/test.config @@ -0,0 +1,11 @@ +process { + resourceLimits = [ + cpus: 4, + memory: '15.GB', + time: '1.h' + ] +} + +params { + genomes = 'https://raw.githubusercontent.com/nf-core/test-datasets/modules/data/genomics/sarscov2/genome/genome.fasta' +} \ No newline at end of file diff --git a/main.nf b/main.nf new file mode 100755 index 0000000..d49f489 --- /dev/null +++ b/main.nf @@ -0,0 +1,162 @@ +#!/usr/bin/env nextflow + +nextflow.enable.dsl = 2 + +params.genomes = 'genomes/*' +params.species = 'others' +params.cds = '' +params.curatedlib = '' +params.rmlib = '' +params.sensitive = false +params.anno = false +params.rmout = '' +params.maxdiv = 40 +params.evaluate = true +params.exclude = '' +params.maxint = 5000 +params.outdir = 'results' + +// TODO: Check inputed repeat libraries, CDS, etc... +// TODO: Check exclude file + +include { SANITIZE_HEADERS } from './modules/local/sanitize/main.nf' +include { LTRHARVEST } from './modules/nf-core/ltrharvest/main.nf' +include { LTRFINDER } from './modules/nf-core/ltrfinder/main.nf' +include { CAT_CAT } from './modules/nf-core/cat/cat/main.nf' +include { LTRRETRIEVER_LTRRETRIEVER } from './modules/nf-core/ltrretriever/ltrretriever/main.nf' +include { TIRLEARNER } from './modules/gallvp/tirlearner/main.nf' +include { ANNOSINE } from './modules/gallvp/annosine/main.nf' +include { REPEATMODELER_BUILDDATABASE } from './modules/nf-core/repeatmodeler/builddatabase/main.nf' +include { REPEATMODELER_REPEATMODELER } from './modules/nf-core/repeatmodeler/repeatmodeler/main.nf' + +include { softwareVersionsToYAML } from './modules/local/utils/main.nf' + +// Test run: +// ./main.nf -profile docker,test +// ./main.nf -profile conda,test +workflow { + + // Versions channel + ch_versions = Channel.empty() + + + ch_genome = Channel.fromPath(params.genomes) + + // Create a meta object for each genome + ch_meta_genome = ch_genome.map { genome -> + def meta = [:] + meta.id = genome.baseName + + [ meta, genome ] + } + + // MODULE: SANITIZE_HEADERS + SANITIZE_HEADERS ( ch_meta_genome ) + + ch_sanitized_fasta = SANITIZE_HEADERS.out.fasta + + // MODULE: LTRHARVEST + LTRHARVEST ( ch_sanitized_fasta ) + + ch_ltrharvest_gff3 = LTRHARVEST.out.gff3 + ch_ltrharvest_scn = LTRHARVEST.out.scn + + ch_versions = ch_versions.mix(LTRHARVEST.out.versions) + + // MODULE: LTRFINDER + LTRFINDER { ch_sanitized_fasta } + + ch_ltrfinder_gff3 = LTRFINDER.out.gff + ch_ltrfinder_scn = LTRFINDER.out.scn + + ch_versions = ch_versions.mix(LTRFINDER.out.versions) + + // MODULE: CAT_CAT + ch_cat_cat_inputs = ch_ltrharvest_scn + | join(ch_ltrfinder_scn) + | map { meta, harvested, found -> [ meta, [ harvested, found ] ] } + CAT_CAT ( ch_cat_cat_inputs ) + + ch_ltr_candidates = CAT_CAT.out.file_out + ch_versions = ch_versions.mix(CAT_CAT.out.versions.first()) + + // MODULE: LTRRETRIEVER_LTRRETRIEVER + ch_ltrretriever_inputs = ch_sanitized_fasta.join(ch_ltr_candidates) + + LTRRETRIEVER_LTRRETRIEVER ( + ch_ltrretriever_inputs.map { meta, fasta, ltr -> [ meta, fasta ] }, + ch_ltrretriever_inputs.map { meta, fasta, ltr -> ltr }, + [], + [], + [] + ) + + ch_ltrretriever_log = LTRRETRIEVER_LTRRETRIEVER.out.log + ch_pass_list = LTRRETRIEVER_LTRRETRIEVER.out.pass_list + ch_annotation_out = LTRRETRIEVER_LTRRETRIEVER.out.annotation_out + ch_annotation_gff = LTRRETRIEVER_LTRRETRIEVER.out.annotation_gff + ch_ltrlib = LTRRETRIEVER_LTRRETRIEVER.out.ltrlib + ch_versions = ch_versions.mix(LTRRETRIEVER_LTRRETRIEVER.out.versions.first()) + + // MODULE: TIRLEARNER + TIRLEARNER ( + ch_sanitized_fasta, + params.species + ) + + ch_tirlearner_filtered_gff = TIRLEARNER.out.filtered_gff + ch_versions = ch_versions.mix(TIRLEARNER.out.versions.first()) + + // These can also run in parallel + // MODULE: ANNOSINE + ANNOSINE ( + ch_sanitized_fasta, + 3 // mode + ) + + // Currently it's a topic, so need to fix that + ch_versions = ch_versions.mix(ANNOSINE.out.versions) + cb_annosine_seed_sine = ANNOSINE.out.fa + + // MODULE: REPEATMODELER_BUILDDATABASE + ch_repeatmodeler_inputs = ch_sanitized_fasta + | map { meta, fasta -> + def size = fasta.size() + def size_threshold = 100_000 // bytes -> bp + + // TODO: Not the best way to set a size threshould + // but it is simple + // This is needed to avoid, + // Error: Database genome is not large enough ( minimum 40000 bp ) to process with RepeatModeler. + if ( size < size_threshold ) { + log.warn "RepeatModeler is skipped for genome '${meta.id}' as it is smaller than ${size_threshold} bytes" + return null + } + + return [ meta, fasta ] + } + | filter { it } + + REPEATMODELER_BUILDDATABASE ( ch_repeatmodeler_inputs ) + + ch_repeatmodeler_db = REPEATMODELER_BUILDDATABASE.out.db + ch_versions = ch_versions.mix(REPEATMODELER_BUILDDATABASE.out.versions.first()) + + // MODULE: REPEATMODELER_REPEATMODELER + REPEATMODELER_REPEATMODELER ( ch_repeatmodeler_db ) + + ch_repeatmodeler_fasta = REPEATMODELER_REPEATMODELER.out.fasta + ch_versions = ch_versions.mix(REPEATMODELER_REPEATMODELER.out.versions.first()) + + + // Function: Save versions + ch_versions_yml = softwareVersionsToYAML(ch_versions) + | collectFile( + storeDir: "${params.outdir}/pipeline_info", + name: 'software_versions.yml', + sort: true, + newLine: true, + cache: false + ) + +} diff --git a/modules/local/utils/main.nf b/modules/local/utils/main.nf new file mode 100644 index 0000000..fee91f3 --- /dev/null +++ b/modules/local/utils/main.nf @@ -0,0 +1,44 @@ +// +// Generate workflow version string +// +def getWorkflowVersion() { + def version_string = "" as String + if (workflow.manifest.version) { + def prefix_v = workflow.manifest.version[0] != 'v' ? 'v' : '' + version_string += "${prefix_v}${workflow.manifest.version}" + } + + if (workflow.commitId) { + def git_shortsha = workflow.commitId.substring(0, 7) + version_string += "-g${git_shortsha}" + } + + return version_string +} + +// +// Get software versions for pipeline +// +def processVersionsFromYAML(yaml_file) { + def yaml = new org.yaml.snakeyaml.Yaml() + def versions = yaml.load(yaml_file).collectEntries { k, v -> [k.tokenize(':')[-1], v] } + return yaml.dumpAsMap(versions).trim() +} + +// +// Get workflow version for pipeline +// +def workflowVersionToYAML() { + return """ + Workflow: + ${workflow.manifest.name}: ${getWorkflowVersion()} + Nextflow: ${workflow.nextflow.version} + """.stripIndent().trim() +} + +// +// Get channel of software versions used in pipeline in YAML format +// +def softwareVersionsToYAML(ch_versions) { + return ch_versions.unique().map { version -> processVersionsFromYAML(version) }.unique().mix(Channel.of(workflowVersionToYAML())) +} \ No newline at end of file diff --git a/nextflow.config b/nextflow.config new file mode 100644 index 0000000..12a89b6 --- /dev/null +++ b/nextflow.config @@ -0,0 +1,99 @@ +process { + + cpus = { 1 * task.attempt } + memory = { 6.GB * task.attempt } + time = { 4.h * task.attempt } + + errorStrategy = { task.exitStatus in ((130..145) + 104) ? 'retry' : 'finish' } + maxRetries = 1 + maxErrors = '-1' + + withLabel:process_single { + cpus = { 1 } + memory = { 6.GB * task.attempt } + time = { 4.h * task.attempt } + } + withLabel:process_low { + cpus = { 2 * task.attempt } + memory = { 12.GB * task.attempt } + time = { 4.h * task.attempt } + } + withLabel:process_medium { + cpus = { 6 * task.attempt } + memory = { 36.GB * task.attempt } + time = { 8.h * task.attempt } + } + withLabel:process_high { + cpus = { 12 * task.attempt } + memory = { 72.GB * task.attempt } + time = { 16.h * task.attempt } + } + withLabel:process_long { + time = { 20.h * task.attempt } + } + withLabel:process_high_memory { + memory = { 200.GB * task.attempt } + } + withLabel:error_ignore { + errorStrategy = 'ignore' + } + withLabel:error_retry { + errorStrategy = 'retry' + maxRetries = 2 + } +} + +// Max resources +process { + resourceLimits = [ + cpus: 12, + memory: '16.GB', + time: '1.hour' + ] +} + +profiles { + singularity { + singularity.enabled = true + singularity.autoMounts = true + } + apptainer { + apptainer.enabled = true + apptainer.autoMounts = true + } + conda { + conda.enabled = true + } + mamba { + conda.enabled = true + conda.useMamba = true + } + podman { + podman.enabled = true + podman.userEmulation = true + podman.runOptions = "--runtime crun --platform linux/x86_64 --systemd=always" + } + docker { + docker.enabled = true + docker.runOptions = '-u $(id -u):$(id -g) --platform=linux/amd64' + } + test { includeConfig './conf/test.config' } +} + +docker.registry = 'quay.io' +podman.registry = 'quay.io' +singularity.registry = 'quay.io' +apptainer.registry = 'quay.io' + +// Increase time available to build Conda environment +conda { + createTimeout = "120 min" +} + +manifest { + name = 'jguhlin/EDTA' + nextflowVersion = '!>=23.04.0' + version = '0.1.0dev' +} + +includeConfig 'conf/modules.config' diff --git a/nf-test.config b/nf-test.config new file mode 100644 index 0000000..c3a9a1e --- /dev/null +++ b/nf-test.config @@ -0,0 +1,10 @@ +config { + testsDir "." + workDir System.getenv("NFT_WORKDIR") ?: ".nf-test" + configFile "test/nf-test/nextflow.config" + + plugins { + load "nft-bam@0.4.0" + load "nft-utils@0.0.3" + } +} diff --git a/test/nf-test/nextflow.config b/test/nf-test/nextflow.config new file mode 100644 index 0000000..ed1a805 --- /dev/null +++ b/test/nf-test/nextflow.config @@ -0,0 +1,22 @@ +/* +======================================================================================== + Nextflow config file for running tests +======================================================================================== +*/ + +params { + modules_testdata_base_path = 'https://raw.githubusercontent.com/nf-core/test-datasets/modules/data/' +} + +timeline { enabled = false } +report { enabled = false } +trace { enabled = false } +dag { enabled = false } + +process { + resourceLimits = [ + cpus: 4, + memory: '15.GB', + time: '1.h' + ] +} diff --git a/test/nf-test/tiny/main.nf.test b/test/nf-test/tiny/main.nf.test new file mode 100644 index 0000000..5022c49 --- /dev/null +++ b/test/nf-test/tiny/main.nf.test @@ -0,0 +1,32 @@ +nextflow_pipeline { + + name "Test with a tiny genome" + script "main.nf" + + test("tiny genome") { + + when { + params { + genomes = "https://raw.githubusercontent.com/nf-core/test-datasets/modules/data/genomics/sarscov2/genome/genome.fasta" + outdir = "$outputDir" + } + } + + then { + def stable_path = getAllFilesFromDir(params.outdir, false, ['pipeline_info/*.{html,json,txt,yml}'], null, ['**']) + + assertAll( + { assert workflow.success}, + { assert snapshot( + [ + 'successful tasks': workflow.trace.succeeded().size(), + 'versions': removeNextflowVersion("$outputDir/pipeline_info/software_versions.yml"), + 'stable paths': stable_path + ] + ).match() } + ) + } + + } + +} diff --git a/test/nf-test/tiny/main.nf.test.snap b/test/nf-test/tiny/main.nf.test.snap new file mode 100644 index 0000000..a61631f --- /dev/null +++ b/test/nf-test/tiny/main.nf.test.snap @@ -0,0 +1,42 @@ +{ + "tiny genome": { + "content": [ + { + "successful tasks": 7, + "versions": { + "ANNOSINE": { + "annosine": "2.0.7" + }, + "CAT_CAT": { + "pigz": "2.3.4" + }, + "LTRFINDER": { + "LTR_FINDER_parallel": "v1.1", + "ltr_finder": "v1.07" + }, + "LTRHARVEST": { + "LTR_HARVEST_parallel": "v1.1", + "genometools": "1.6.5" + }, + "LTRRETRIEVER_LTRRETRIEVER": { + "LTR_retriever": "v2.9.9" + }, + "TIRLEARNER": { + "TIR-Learner": "v3.0.2 by Tianyu (Sky) Lu (tlu83@wisc.edu) published under GPLv3" + }, + "Workflow": { + "jguhlin/EDTA": "v0.1.0dev" + } + }, + "stable paths": [ + + ] + } + ], + "meta": { + "nf-test": "0.9.0", + "nextflow": "24.10.1" + }, + "timestamp": "2024-11-25T13:20:57.770583" + } +} \ No newline at end of file From 74edd95010e66055c9c81f1a4ca398050344c0d4 Mon Sep 17 00:00:00 2001 From: Usman Rashid Date: Mon, 25 Nov 2024 14:44:00 +1300 Subject: [PATCH 18/49] Updated test CI --- main.nf | 6 ++++ test/nf-test/small/main.nf.test | 32 +++++++++++++++++++ test/nf-test/small/main.nf.test.snap | 48 ++++++++++++++++++++++++++++ 3 files changed, 86 insertions(+) create mode 100644 test/nf-test/small/main.nf.test create mode 100644 test/nf-test/small/main.nf.test.snap diff --git a/main.nf b/main.nf index d49f489..091684c 100755 --- a/main.nf +++ b/main.nf @@ -150,6 +150,12 @@ workflow { // Function: Save versions + ch_versions = ch_versions + | unique + | map { yml -> + if ( yml ) { yml } + } + ch_versions_yml = softwareVersionsToYAML(ch_versions) | collectFile( storeDir: "${params.outdir}/pipeline_info", diff --git a/test/nf-test/small/main.nf.test b/test/nf-test/small/main.nf.test new file mode 100644 index 0000000..4cb9c26 --- /dev/null +++ b/test/nf-test/small/main.nf.test @@ -0,0 +1,32 @@ +nextflow_pipeline { + + name "Test with a small genome" + script "main.nf" + + test("small genome") { + + when { + params { + genomes = "https://raw.githubusercontent.com/nf-core/test-datasets/modules/data/genomics/eukaryotes/actinidia_chinensis/genome/chr1/genome.fasta.gz" + outdir = "$outputDir" + } + } + + then { + def stable_path = getAllFilesFromDir(params.outdir, false, ['pipeline_info/*.{html,json,txt,yml}'], null, ['**']) + + assertAll( + { assert workflow.success}, + { assert snapshot( + [ + 'successful tasks': workflow.trace.succeeded().size(), + 'versions': removeNextflowVersion("$outputDir/pipeline_info/software_versions.yml"), + 'stable paths': stable_path + ] + ).match() } + ) + } + + } + +} diff --git a/test/nf-test/small/main.nf.test.snap b/test/nf-test/small/main.nf.test.snap new file mode 100644 index 0000000..d53e834 --- /dev/null +++ b/test/nf-test/small/main.nf.test.snap @@ -0,0 +1,48 @@ +{ + "small genome": { + "content": [ + { + "successful tasks": 9, + "versions": { + "ANNOSINE": { + "annosine": "2.0.7" + }, + "CAT_CAT": { + "pigz": "2.3.4" + }, + "LTRFINDER": { + "LTR_FINDER_parallel": "v1.1", + "ltr_finder": "v1.07" + }, + "LTRHARVEST": { + "LTR_HARVEST_parallel": "v1.1", + "genometools": "1.6.5" + }, + "LTRRETRIEVER_LTRRETRIEVER": { + "LTR_retriever": "v2.9.9" + }, + "REPEATMODELER_BUILDDATABASE": { + "repeatmodeler": "2.0.5" + }, + "REPEATMODELER_REPEATMODELER": { + "repeatmodeler": "2.0.5" + }, + "TIRLEARNER": { + "TIR-Learner": "v3.0.2 by Tianyu (Sky) Lu (tlu83@wisc.edu) published under GPLv3" + }, + "Workflow": { + "jguhlin/EDTA": "v0.1.0dev" + } + }, + "stable paths": [ + + ] + } + ], + "meta": { + "nf-test": "0.9.0", + "nextflow": "24.10.1" + }, + "timestamp": "2024-11-25T13:53:22.07666" + } +} \ No newline at end of file From cbd0d2f431d814aa5b95e144d6b519206fcc512c Mon Sep 17 00:00:00 2001 From: Usman Rashid Date: Mon, 25 Nov 2024 15:23:50 +1300 Subject: [PATCH 19/49] Added missing modules, profiles and manifest fields --- .github/workflows/ci.yml | 18 ++- conf/modules.config | 23 +++ main.nf | 147 +----------------- modules.json | 68 ++++++++ modules/gallvp/gunzip/environment.yml | 7 + modules/gallvp/gunzip/main.nf | 55 +++++++ modules/gallvp/gunzip/meta.yml | 47 ++++++ modules/gallvp/gunzip/tests/main.nf.test | 121 ++++++++++++++ modules/gallvp/gunzip/tests/main.nf.test.snap | 134 ++++++++++++++++ modules/gallvp/gunzip/tests/nextflow.config | 5 + modules/gallvp/gunzip/tests/tags.yml | 2 + modules/nf-core/gunzip/environment.yml | 7 + modules/nf-core/gunzip/main.nf | 55 +++++++ modules/nf-core/gunzip/meta.yml | 47 ++++++ modules/nf-core/gunzip/tests/main.nf.test | 121 ++++++++++++++ .../nf-core/gunzip/tests/main.nf.test.snap | 134 ++++++++++++++++ modules/nf-core/gunzip/tests/nextflow.config | 5 + modules/nf-core/gunzip/tests/tags.yml | 2 + nextflow.config | 4 +- workflows/edta.nf | 144 +++++++++++++++++ 20 files changed, 996 insertions(+), 150 deletions(-) create mode 100644 conf/modules.config create mode 100644 modules.json create mode 100644 modules/gallvp/gunzip/environment.yml create mode 100644 modules/gallvp/gunzip/main.nf create mode 100644 modules/gallvp/gunzip/meta.yml create mode 100644 modules/gallvp/gunzip/tests/main.nf.test create mode 100644 modules/gallvp/gunzip/tests/main.nf.test.snap create mode 100644 modules/gallvp/gunzip/tests/nextflow.config create mode 100644 modules/gallvp/gunzip/tests/tags.yml create mode 100644 modules/nf-core/gunzip/environment.yml create mode 100644 modules/nf-core/gunzip/main.nf create mode 100644 modules/nf-core/gunzip/meta.yml create mode 100644 modules/nf-core/gunzip/tests/main.nf.test create mode 100644 modules/nf-core/gunzip/tests/main.nf.test.snap create mode 100644 modules/nf-core/gunzip/tests/nextflow.config create mode 100644 modules/nf-core/gunzip/tests/tags.yml create mode 100644 workflows/edta.nf diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 43d4c6c..20d4b62 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -7,10 +7,13 @@ env: NFT_WORKDIR: "~" NFT_DIFF: "pdiff" NFT_DIFF_ARGS: "--line-numbers --expand-tabs=2" + NXF_SINGULARITY_CACHEDIR: ${{ github.workspace }}/.singularity + NXF_SINGULARITY_LIBRARYDIR: ${{ github.workspace }}/.singularity concurrency: group: "${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}" cancel-in-progress: true + jobs: nf-test-changes: name: Check for changes @@ -46,8 +49,7 @@ jobs: - "24.04.2" nf_test_files: ["${{ fromJson(needs.nf-test-changes.outputs.nf_test_files) }}"] - profile: - - "docker" + profile: [conda, docker, singularity] steps: - name: Check out pipeline code @@ -71,10 +73,16 @@ jobs: - uses: nf-core/setup-nf-test@v1.1.2 with: version: 0.9.0 + + - name: Setup apptainer + if: matrix.profile == 'singularity' + uses: eWaterCycle/setup-apptainer@main - - name: Disk space cleanup - if: matrix.nf_test_files == 'tests/stub/main.nf.test' - uses: jlumbroso/free-disk-space@v1.3.1 + - name: Set up Singularity + if: matrix.profile == 'singularity' + run: | + mkdir -p $NXF_SINGULARITY_CACHEDIR + mkdir -p $NXF_SINGULARITY_LIBRARYDIR - name: Run nf-test run: | diff --git a/conf/modules.config b/conf/modules.config new file mode 100644 index 0000000..81d11dc --- /dev/null +++ b/conf/modules.config @@ -0,0 +1,23 @@ +process { + withName: 'EDTA:LTRHARVEST' { + ext.args = '-size 1000000 -time 300' + ext.prefix = { "${meta.id}_ltrharvest" } + } + + withName: 'EDTA:LTRFINDER' { + ext.args = '-harvest_out -size 1000000 -time 300' + } + + withName: 'EDTA:CAT_CAT' { + ext.prefix = { "${meta.id}_ltrharvest_ltrfinder.tabout" } + } + + withName: 'EDTA:ANNOSINE' { + ext.args = '--num_alignments 50000 -rpm 0 --copy_number 3 --shift 100 -auto 1' + } + + withName: 'EDTA:REPEATMODELER_REPEATMODELER' { + ext.args = '-engine ncbi' + } + +} \ No newline at end of file diff --git a/main.nf b/main.nf index 091684c..fa74a8b 100755 --- a/main.nf +++ b/main.nf @@ -16,153 +16,12 @@ params.exclude = '' params.maxint = 5000 params.outdir = 'results' -// TODO: Check inputed repeat libraries, CDS, etc... -// TODO: Check exclude file - -include { SANITIZE_HEADERS } from './modules/local/sanitize/main.nf' -include { LTRHARVEST } from './modules/nf-core/ltrharvest/main.nf' -include { LTRFINDER } from './modules/nf-core/ltrfinder/main.nf' -include { CAT_CAT } from './modules/nf-core/cat/cat/main.nf' -include { LTRRETRIEVER_LTRRETRIEVER } from './modules/nf-core/ltrretriever/ltrretriever/main.nf' -include { TIRLEARNER } from './modules/gallvp/tirlearner/main.nf' -include { ANNOSINE } from './modules/gallvp/annosine/main.nf' -include { REPEATMODELER_BUILDDATABASE } from './modules/nf-core/repeatmodeler/builddatabase/main.nf' -include { REPEATMODELER_REPEATMODELER } from './modules/nf-core/repeatmodeler/repeatmodeler/main.nf' - -include { softwareVersionsToYAML } from './modules/local/utils/main.nf' +include { EDTA } from './workflows/edta.nf' // Test run: // ./main.nf -profile docker,test // ./main.nf -profile conda,test -workflow { - - // Versions channel - ch_versions = Channel.empty() - - - ch_genome = Channel.fromPath(params.genomes) - - // Create a meta object for each genome - ch_meta_genome = ch_genome.map { genome -> - def meta = [:] - meta.id = genome.baseName - - [ meta, genome ] - } - - // MODULE: SANITIZE_HEADERS - SANITIZE_HEADERS ( ch_meta_genome ) - - ch_sanitized_fasta = SANITIZE_HEADERS.out.fasta - - // MODULE: LTRHARVEST - LTRHARVEST ( ch_sanitized_fasta ) - - ch_ltrharvest_gff3 = LTRHARVEST.out.gff3 - ch_ltrharvest_scn = LTRHARVEST.out.scn - - ch_versions = ch_versions.mix(LTRHARVEST.out.versions) - - // MODULE: LTRFINDER - LTRFINDER { ch_sanitized_fasta } - - ch_ltrfinder_gff3 = LTRFINDER.out.gff - ch_ltrfinder_scn = LTRFINDER.out.scn - - ch_versions = ch_versions.mix(LTRFINDER.out.versions) - - // MODULE: CAT_CAT - ch_cat_cat_inputs = ch_ltrharvest_scn - | join(ch_ltrfinder_scn) - | map { meta, harvested, found -> [ meta, [ harvested, found ] ] } - CAT_CAT ( ch_cat_cat_inputs ) - - ch_ltr_candidates = CAT_CAT.out.file_out - ch_versions = ch_versions.mix(CAT_CAT.out.versions.first()) - - // MODULE: LTRRETRIEVER_LTRRETRIEVER - ch_ltrretriever_inputs = ch_sanitized_fasta.join(ch_ltr_candidates) - - LTRRETRIEVER_LTRRETRIEVER ( - ch_ltrretriever_inputs.map { meta, fasta, ltr -> [ meta, fasta ] }, - ch_ltrretriever_inputs.map { meta, fasta, ltr -> ltr }, - [], - [], - [] - ) - - ch_ltrretriever_log = LTRRETRIEVER_LTRRETRIEVER.out.log - ch_pass_list = LTRRETRIEVER_LTRRETRIEVER.out.pass_list - ch_annotation_out = LTRRETRIEVER_LTRRETRIEVER.out.annotation_out - ch_annotation_gff = LTRRETRIEVER_LTRRETRIEVER.out.annotation_gff - ch_ltrlib = LTRRETRIEVER_LTRRETRIEVER.out.ltrlib - ch_versions = ch_versions.mix(LTRRETRIEVER_LTRRETRIEVER.out.versions.first()) - - // MODULE: TIRLEARNER - TIRLEARNER ( - ch_sanitized_fasta, - params.species - ) - - ch_tirlearner_filtered_gff = TIRLEARNER.out.filtered_gff - ch_versions = ch_versions.mix(TIRLEARNER.out.versions.first()) - - // These can also run in parallel - // MODULE: ANNOSINE - ANNOSINE ( - ch_sanitized_fasta, - 3 // mode - ) - - // Currently it's a topic, so need to fix that - ch_versions = ch_versions.mix(ANNOSINE.out.versions) - cb_annosine_seed_sine = ANNOSINE.out.fa - - // MODULE: REPEATMODELER_BUILDDATABASE - ch_repeatmodeler_inputs = ch_sanitized_fasta - | map { meta, fasta -> - def size = fasta.size() - def size_threshold = 100_000 // bytes -> bp - - // TODO: Not the best way to set a size threshould - // but it is simple - // This is needed to avoid, - // Error: Database genome is not large enough ( minimum 40000 bp ) to process with RepeatModeler. - if ( size < size_threshold ) { - log.warn "RepeatModeler is skipped for genome '${meta.id}' as it is smaller than ${size_threshold} bytes" - return null - } - - return [ meta, fasta ] - } - | filter { it } - - REPEATMODELER_BUILDDATABASE ( ch_repeatmodeler_inputs ) - - ch_repeatmodeler_db = REPEATMODELER_BUILDDATABASE.out.db - ch_versions = ch_versions.mix(REPEATMODELER_BUILDDATABASE.out.versions.first()) - - // MODULE: REPEATMODELER_REPEATMODELER - REPEATMODELER_REPEATMODELER ( ch_repeatmodeler_db ) - - ch_repeatmodeler_fasta = REPEATMODELER_REPEATMODELER.out.fasta - ch_versions = ch_versions.mix(REPEATMODELER_REPEATMODELER.out.versions.first()) - - - // Function: Save versions - ch_versions = ch_versions - | unique - | map { yml -> - if ( yml ) { yml } - } - - ch_versions_yml = softwareVersionsToYAML(ch_versions) - | collectFile( - storeDir: "${params.outdir}/pipeline_info", - name: 'software_versions.yml', - sort: true, - newLine: true, - cache: false - ) +workflow { + EDTA() } diff --git a/modules.json b/modules.json new file mode 100644 index 0000000..f9eedd5 --- /dev/null +++ b/modules.json @@ -0,0 +1,68 @@ +{ + "name": "", + "homePage": "", + "repos": { + "https://github.com/GallVp/nxf-components.git": { + "modules": { + "gallvp": { + "annosine": { + "branch": "main", + "git_sha": "a8939d36280e7d9037c7cf164eeede19e46546a4", + "installed_by": ["modules"] + }, + "gunzip": { + "branch": "main", + "git_sha": "ae9714c21ede9199a3118e3c20b65484aa73e232", + "installed_by": ["modules"] + }, + "tirlearner": { + "branch": "main", + "git_sha": "a8939d36280e7d9037c7cf164eeede19e46546a4", + "installed_by": ["modules"] + } + } + } + }, + "https://github.com/nf-core/modules.git": { + "modules": { + "nf-core": { + "cat/cat": { + "branch": "master", + "git_sha": "666652151335353eef2fcd58880bcef5bc2928e1", + "installed_by": ["modules"] + }, + "gunzip": { + "branch": "master", + "git_sha": "666652151335353eef2fcd58880bcef5bc2928e1", + "installed_by": ["modules"] + }, + "ltrfinder": { + "branch": "master", + "git_sha": "666652151335353eef2fcd58880bcef5bc2928e1", + "installed_by": ["modules"] + }, + "ltrharvest": { + "branch": "master", + "git_sha": "666652151335353eef2fcd58880bcef5bc2928e1", + "installed_by": ["modules"] + }, + "ltrretriever/ltrretriever": { + "branch": "master", + "git_sha": "666652151335353eef2fcd58880bcef5bc2928e1", + "installed_by": ["modules"] + }, + "repeatmodeler/builddatabase": { + "branch": "master", + "git_sha": "666652151335353eef2fcd58880bcef5bc2928e1", + "installed_by": ["modules"] + }, + "repeatmodeler/repeatmodeler": { + "branch": "master", + "git_sha": "666652151335353eef2fcd58880bcef5bc2928e1", + "installed_by": ["modules"] + } + } + } + } + } +} diff --git a/modules/gallvp/gunzip/environment.yml b/modules/gallvp/gunzip/environment.yml new file mode 100644 index 0000000..c779485 --- /dev/null +++ b/modules/gallvp/gunzip/environment.yml @@ -0,0 +1,7 @@ +channels: + - conda-forge + - bioconda +dependencies: + - conda-forge::grep=3.11 + - conda-forge::sed=4.8 + - conda-forge::tar=1.34 diff --git a/modules/gallvp/gunzip/main.nf b/modules/gallvp/gunzip/main.nf new file mode 100644 index 0000000..5e67e3b --- /dev/null +++ b/modules/gallvp/gunzip/main.nf @@ -0,0 +1,55 @@ +process GUNZIP { + tag "$archive" + label 'process_single' + + conda "${moduleDir}/environment.yml" + container "${ workflow.containerEngine == 'singularity' && !task.ext.singularity_pull_docker_container ? + 'https://depot.galaxyproject.org/singularity/ubuntu:22.04' : + 'nf-core/ubuntu:22.04' }" + + input: + tuple val(meta), path(archive) + + output: + tuple val(meta), path("$gunzip"), emit: gunzip + path "versions.yml" , emit: versions + + when: + task.ext.when == null || task.ext.when + + script: + def args = task.ext.args ?: '' + def extension = ( archive.toString() - '.gz' ).tokenize('.')[-1] + def name = archive.toString() - '.gz' - ".$extension" + def prefix = task.ext.prefix ?: name + gunzip = prefix + ".$extension" + """ + # Not calling gunzip itself because it creates files + # with the original group ownership rather than the + # default one for that user / the work directory + gzip \\ + -cd \\ + $args \\ + $archive \\ + > $gunzip + + cat <<-END_VERSIONS > versions.yml + "${task.process}": + gunzip: \$(echo \$(gunzip --version 2>&1) | sed 's/^.*(gzip) //; s/ Copyright.*\$//') + END_VERSIONS + """ + + stub: + def args = task.ext.args ?: '' + def extension = ( archive.toString() - '.gz' ).tokenize('.')[-1] + def name = archive.toString() - '.gz' - ".$extension" + def prefix = task.ext.prefix ?: name + gunzip = prefix + ".$extension" + """ + touch $gunzip + cat <<-END_VERSIONS > versions.yml + "${task.process}": + gunzip: \$(echo \$(gunzip --version 2>&1) | sed 's/^.*(gzip) //; s/ Copyright.*\$//') + END_VERSIONS + """ +} diff --git a/modules/gallvp/gunzip/meta.yml b/modules/gallvp/gunzip/meta.yml new file mode 100644 index 0000000..9066c03 --- /dev/null +++ b/modules/gallvp/gunzip/meta.yml @@ -0,0 +1,47 @@ +name: gunzip +description: Compresses and decompresses files. +keywords: + - gunzip + - compression + - decompression +tools: + - gunzip: + description: | + gzip is a file format and a software application used for file compression and decompression. + documentation: https://www.gnu.org/software/gzip/manual/gzip.html + licence: ["GPL-3.0-or-later"] + identifier: "" +input: + - - meta: + type: map + description: | + Optional groovy Map containing meta information + e.g. [ id:'test', single_end:false ] + - archive: + type: file + description: File to be compressed/uncompressed + pattern: "*.*" +output: + - gunzip: + - meta: + type: file + description: Compressed/uncompressed file + pattern: "*.*" + - $gunzip: + type: file + description: Compressed/uncompressed file + pattern: "*.*" + - versions: + - versions.yml: + type: file + description: File containing software versions + pattern: "versions.yml" +authors: + - "@joseespinosa" + - "@drpatelh" + - "@jfy133" +maintainers: + - "@joseespinosa" + - "@drpatelh" + - "@jfy133" + - "@gallvp" diff --git a/modules/gallvp/gunzip/tests/main.nf.test b/modules/gallvp/gunzip/tests/main.nf.test new file mode 100644 index 0000000..f661057 --- /dev/null +++ b/modules/gallvp/gunzip/tests/main.nf.test @@ -0,0 +1,121 @@ +nextflow_process { + + name "Test Process GUNZIP" + script "../main.nf" + process "GUNZIP" + tag "gunzip" + tag "modules_gallvp" + tag "modules" + + test("Should run without failures") { + + when { + params { + outdir = "$outputDir" + } + process { + """ + input[0] = Channel.of([ + [], + 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("Should run without failures - prefix") { + + config './nextflow.config' + + when { + params { + outdir = "$outputDir" + } + process { + """ + input[0] = Channel.of([ + [ id: 'test' ], + 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("Should run without failures - stub") { + + options '-stub' + + when { + params { + outdir = "$outputDir" + } + process { + """ + input[0] = Channel.of([ + [], + 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("Should run without failures - prefix - stub") { + + options '-stub' + config './nextflow.config' + + when { + params { + outdir = "$outputDir" + } + process { + """ + input[0] = Channel.of([ + [ id: 'test' ], + file(params.modules_testdata_base_path + 'genomics/sarscov2/illumina/fastq/test_1.fastq.gz', checkIfExists: true) + ] + ) + """ + } + } + + then { + assertAll( + { assert process.success }, + { assert snapshot(process.out).match() } + ) + } + + } + +} diff --git a/modules/gallvp/gunzip/tests/main.nf.test.snap b/modules/gallvp/gunzip/tests/main.nf.test.snap new file mode 100644 index 0000000..069967e --- /dev/null +++ b/modules/gallvp/gunzip/tests/main.nf.test.snap @@ -0,0 +1,134 @@ +{ + "Should run without failures - prefix - stub": { + "content": [ + { + "0": [ + [ + { + "id": "test" + }, + "test.xyz.fastq:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "1": [ + "versions.yml:md5,54376d32aca20e937a4ec26dac228e84" + ], + "gunzip": [ + [ + { + "id": "test" + }, + "test.xyz.fastq:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "versions": [ + "versions.yml:md5,54376d32aca20e937a4ec26dac228e84" + ] + } + ], + "meta": { + "nf-test": "0.8.4", + "nextflow": "24.04.2" + }, + "timestamp": "2024-06-25T11:35:10.861293" + }, + "Should run without failures - stub": { + "content": [ + { + "0": [ + [ + [ + + ], + "test_1.fastq:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "1": [ + "versions.yml:md5,54376d32aca20e937a4ec26dac228e84" + ], + "gunzip": [ + [ + [ + + ], + "test_1.fastq:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "versions": [ + "versions.yml:md5,54376d32aca20e937a4ec26dac228e84" + ] + } + ], + "meta": { + "nf-test": "0.8.4", + "nextflow": "24.04.2" + }, + "timestamp": "2024-06-25T11:35:05.857145" + }, + "Should run without failures": { + "content": [ + { + "0": [ + [ + [ + + ], + "test_1.fastq:md5,4161df271f9bfcd25d5845a1e220dbec" + ] + ], + "1": [ + "versions.yml:md5,54376d32aca20e937a4ec26dac228e84" + ], + "gunzip": [ + [ + [ + + ], + "test_1.fastq:md5,4161df271f9bfcd25d5845a1e220dbec" + ] + ], + "versions": [ + "versions.yml:md5,54376d32aca20e937a4ec26dac228e84" + ] + } + ], + "meta": { + "nf-test": "0.8.4", + "nextflow": "24.04.2" + }, + "timestamp": "2023-10-17T15:35:37.690477896" + }, + "Should run without failures - prefix": { + "content": [ + { + "0": [ + [ + { + "id": "test" + }, + "test.xyz.fastq:md5,4161df271f9bfcd25d5845a1e220dbec" + ] + ], + "1": [ + "versions.yml:md5,54376d32aca20e937a4ec26dac228e84" + ], + "gunzip": [ + [ + { + "id": "test" + }, + "test.xyz.fastq:md5,4161df271f9bfcd25d5845a1e220dbec" + ] + ], + "versions": [ + "versions.yml:md5,54376d32aca20e937a4ec26dac228e84" + ] + } + ], + "meta": { + "nf-test": "0.8.4", + "nextflow": "24.04.2" + }, + "timestamp": "2024-06-25T11:33:32.921739" + } +} \ No newline at end of file diff --git a/modules/gallvp/gunzip/tests/nextflow.config b/modules/gallvp/gunzip/tests/nextflow.config new file mode 100644 index 0000000..dec7764 --- /dev/null +++ b/modules/gallvp/gunzip/tests/nextflow.config @@ -0,0 +1,5 @@ +process { + withName: GUNZIP { + ext.prefix = { "${meta.id}.xyz" } + } +} diff --git a/modules/gallvp/gunzip/tests/tags.yml b/modules/gallvp/gunzip/tests/tags.yml new file mode 100644 index 0000000..fd3f691 --- /dev/null +++ b/modules/gallvp/gunzip/tests/tags.yml @@ -0,0 +1,2 @@ +gunzip: + - modules/nf-core/gunzip/** diff --git a/modules/nf-core/gunzip/environment.yml b/modules/nf-core/gunzip/environment.yml new file mode 100644 index 0000000..c779485 --- /dev/null +++ b/modules/nf-core/gunzip/environment.yml @@ -0,0 +1,7 @@ +channels: + - conda-forge + - bioconda +dependencies: + - conda-forge::grep=3.11 + - conda-forge::sed=4.8 + - conda-forge::tar=1.34 diff --git a/modules/nf-core/gunzip/main.nf b/modules/nf-core/gunzip/main.nf new file mode 100644 index 0000000..5e67e3b --- /dev/null +++ b/modules/nf-core/gunzip/main.nf @@ -0,0 +1,55 @@ +process GUNZIP { + tag "$archive" + label 'process_single' + + conda "${moduleDir}/environment.yml" + container "${ workflow.containerEngine == 'singularity' && !task.ext.singularity_pull_docker_container ? + 'https://depot.galaxyproject.org/singularity/ubuntu:22.04' : + 'nf-core/ubuntu:22.04' }" + + input: + tuple val(meta), path(archive) + + output: + tuple val(meta), path("$gunzip"), emit: gunzip + path "versions.yml" , emit: versions + + when: + task.ext.when == null || task.ext.when + + script: + def args = task.ext.args ?: '' + def extension = ( archive.toString() - '.gz' ).tokenize('.')[-1] + def name = archive.toString() - '.gz' - ".$extension" + def prefix = task.ext.prefix ?: name + gunzip = prefix + ".$extension" + """ + # Not calling gunzip itself because it creates files + # with the original group ownership rather than the + # default one for that user / the work directory + gzip \\ + -cd \\ + $args \\ + $archive \\ + > $gunzip + + cat <<-END_VERSIONS > versions.yml + "${task.process}": + gunzip: \$(echo \$(gunzip --version 2>&1) | sed 's/^.*(gzip) //; s/ Copyright.*\$//') + END_VERSIONS + """ + + stub: + def args = task.ext.args ?: '' + def extension = ( archive.toString() - '.gz' ).tokenize('.')[-1] + def name = archive.toString() - '.gz' - ".$extension" + def prefix = task.ext.prefix ?: name + gunzip = prefix + ".$extension" + """ + touch $gunzip + cat <<-END_VERSIONS > versions.yml + "${task.process}": + gunzip: \$(echo \$(gunzip --version 2>&1) | sed 's/^.*(gzip) //; s/ Copyright.*\$//') + END_VERSIONS + """ +} diff --git a/modules/nf-core/gunzip/meta.yml b/modules/nf-core/gunzip/meta.yml new file mode 100644 index 0000000..9066c03 --- /dev/null +++ b/modules/nf-core/gunzip/meta.yml @@ -0,0 +1,47 @@ +name: gunzip +description: Compresses and decompresses files. +keywords: + - gunzip + - compression + - decompression +tools: + - gunzip: + description: | + gzip is a file format and a software application used for file compression and decompression. + documentation: https://www.gnu.org/software/gzip/manual/gzip.html + licence: ["GPL-3.0-or-later"] + identifier: "" +input: + - - meta: + type: map + description: | + Optional groovy Map containing meta information + e.g. [ id:'test', single_end:false ] + - archive: + type: file + description: File to be compressed/uncompressed + pattern: "*.*" +output: + - gunzip: + - meta: + type: file + description: Compressed/uncompressed file + pattern: "*.*" + - $gunzip: + type: file + description: Compressed/uncompressed file + pattern: "*.*" + - versions: + - versions.yml: + type: file + description: File containing software versions + pattern: "versions.yml" +authors: + - "@joseespinosa" + - "@drpatelh" + - "@jfy133" +maintainers: + - "@joseespinosa" + - "@drpatelh" + - "@jfy133" + - "@gallvp" diff --git a/modules/nf-core/gunzip/tests/main.nf.test b/modules/nf-core/gunzip/tests/main.nf.test new file mode 100644 index 0000000..776211a --- /dev/null +++ b/modules/nf-core/gunzip/tests/main.nf.test @@ -0,0 +1,121 @@ +nextflow_process { + + name "Test Process GUNZIP" + script "../main.nf" + process "GUNZIP" + tag "gunzip" + tag "modules_nfcore" + tag "modules" + + test("Should run without failures") { + + when { + params { + outdir = "$outputDir" + } + process { + """ + input[0] = Channel.of([ + [], + 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("Should run without failures - prefix") { + + config './nextflow.config' + + when { + params { + outdir = "$outputDir" + } + process { + """ + input[0] = Channel.of([ + [ id: 'test' ], + 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("Should run without failures - stub") { + + options '-stub' + + when { + params { + outdir = "$outputDir" + } + process { + """ + input[0] = Channel.of([ + [], + 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("Should run without failures - prefix - stub") { + + options '-stub' + config './nextflow.config' + + when { + params { + outdir = "$outputDir" + } + process { + """ + input[0] = Channel.of([ + [ id: 'test' ], + file(params.modules_testdata_base_path + 'genomics/sarscov2/illumina/fastq/test_1.fastq.gz', checkIfExists: true) + ] + ) + """ + } + } + + then { + assertAll( + { assert process.success }, + { assert snapshot(process.out).match() } + ) + } + + } + +} diff --git a/modules/nf-core/gunzip/tests/main.nf.test.snap b/modules/nf-core/gunzip/tests/main.nf.test.snap new file mode 100644 index 0000000..069967e --- /dev/null +++ b/modules/nf-core/gunzip/tests/main.nf.test.snap @@ -0,0 +1,134 @@ +{ + "Should run without failures - prefix - stub": { + "content": [ + { + "0": [ + [ + { + "id": "test" + }, + "test.xyz.fastq:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "1": [ + "versions.yml:md5,54376d32aca20e937a4ec26dac228e84" + ], + "gunzip": [ + [ + { + "id": "test" + }, + "test.xyz.fastq:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "versions": [ + "versions.yml:md5,54376d32aca20e937a4ec26dac228e84" + ] + } + ], + "meta": { + "nf-test": "0.8.4", + "nextflow": "24.04.2" + }, + "timestamp": "2024-06-25T11:35:10.861293" + }, + "Should run without failures - stub": { + "content": [ + { + "0": [ + [ + [ + + ], + "test_1.fastq:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "1": [ + "versions.yml:md5,54376d32aca20e937a4ec26dac228e84" + ], + "gunzip": [ + [ + [ + + ], + "test_1.fastq:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "versions": [ + "versions.yml:md5,54376d32aca20e937a4ec26dac228e84" + ] + } + ], + "meta": { + "nf-test": "0.8.4", + "nextflow": "24.04.2" + }, + "timestamp": "2024-06-25T11:35:05.857145" + }, + "Should run without failures": { + "content": [ + { + "0": [ + [ + [ + + ], + "test_1.fastq:md5,4161df271f9bfcd25d5845a1e220dbec" + ] + ], + "1": [ + "versions.yml:md5,54376d32aca20e937a4ec26dac228e84" + ], + "gunzip": [ + [ + [ + + ], + "test_1.fastq:md5,4161df271f9bfcd25d5845a1e220dbec" + ] + ], + "versions": [ + "versions.yml:md5,54376d32aca20e937a4ec26dac228e84" + ] + } + ], + "meta": { + "nf-test": "0.8.4", + "nextflow": "24.04.2" + }, + "timestamp": "2023-10-17T15:35:37.690477896" + }, + "Should run without failures - prefix": { + "content": [ + { + "0": [ + [ + { + "id": "test" + }, + "test.xyz.fastq:md5,4161df271f9bfcd25d5845a1e220dbec" + ] + ], + "1": [ + "versions.yml:md5,54376d32aca20e937a4ec26dac228e84" + ], + "gunzip": [ + [ + { + "id": "test" + }, + "test.xyz.fastq:md5,4161df271f9bfcd25d5845a1e220dbec" + ] + ], + "versions": [ + "versions.yml:md5,54376d32aca20e937a4ec26dac228e84" + ] + } + ], + "meta": { + "nf-test": "0.8.4", + "nextflow": "24.04.2" + }, + "timestamp": "2024-06-25T11:33:32.921739" + } +} \ No newline at end of file diff --git a/modules/nf-core/gunzip/tests/nextflow.config b/modules/nf-core/gunzip/tests/nextflow.config new file mode 100644 index 0000000..dec7764 --- /dev/null +++ b/modules/nf-core/gunzip/tests/nextflow.config @@ -0,0 +1,5 @@ +process { + withName: GUNZIP { + ext.prefix = { "${meta.id}.xyz" } + } +} diff --git a/modules/nf-core/gunzip/tests/tags.yml b/modules/nf-core/gunzip/tests/tags.yml new file mode 100644 index 0000000..fd3f691 --- /dev/null +++ b/modules/nf-core/gunzip/tests/tags.yml @@ -0,0 +1,2 @@ +gunzip: + - modules/nf-core/gunzip/** diff --git a/nextflow.config b/nextflow.config index 12a89b6..34dfae1 100644 --- a/nextflow.config +++ b/nextflow.config @@ -92,8 +92,10 @@ conda { manifest { name = 'jguhlin/EDTA' - nextflowVersion = '!>=23.04.0' + description = 'Extensive de-novo TE Annotator on Nextflow' + manifest.author = 'Usman Rashid, Joseph Guhlin & Shujun Ou' version = '0.1.0dev' + nextflowVersion = '!>=23.04.0' } includeConfig 'conf/modules.config' diff --git a/workflows/edta.nf b/workflows/edta.nf new file mode 100644 index 0000000..df8b2d9 --- /dev/null +++ b/workflows/edta.nf @@ -0,0 +1,144 @@ +include { SANITIZE_HEADERS } from '../modules/local/sanitize/main.nf' +include { LTRHARVEST } from '../modules/nf-core/ltrharvest/main.nf' +include { LTRFINDER } from '../modules/nf-core/ltrfinder/main.nf' +include { CAT_CAT } from '../modules/nf-core/cat/cat/main.nf' +include { LTRRETRIEVER_LTRRETRIEVER } from '../modules/nf-core/ltrretriever/ltrretriever/main.nf' +include { TIRLEARNER } from '../modules/gallvp/tirlearner/main.nf' +include { ANNOSINE } from '../modules/gallvp/annosine/main.nf' +include { REPEATMODELER_BUILDDATABASE } from '../modules/nf-core/repeatmodeler/builddatabase/main.nf' +include { REPEATMODELER_REPEATMODELER } from '../modules/nf-core/repeatmodeler/repeatmodeler/main.nf' + +include { softwareVersionsToYAML } from '../modules/local/utils/main.nf' + +workflow EDTA { + + // Versions channel + ch_versions = Channel.empty() + + + ch_genome = Channel.fromPath(params.genomes) + + // Create a meta object for each genome + ch_meta_genome = ch_genome.map { genome -> + def meta = [:] + meta.id = genome.baseName + + [ meta, genome ] + } + + // MODULE: SANITIZE_HEADERS + SANITIZE_HEADERS ( ch_meta_genome ) + + ch_sanitized_fasta = SANITIZE_HEADERS.out.fasta + + // MODULE: LTRHARVEST + LTRHARVEST ( ch_sanitized_fasta ) + + ch_ltrharvest_gff3 = LTRHARVEST.out.gff3 + ch_ltrharvest_scn = LTRHARVEST.out.scn + + ch_versions = ch_versions.mix(LTRHARVEST.out.versions) + + // MODULE: LTRFINDER + LTRFINDER { ch_sanitized_fasta } + + ch_ltrfinder_gff3 = LTRFINDER.out.gff + ch_ltrfinder_scn = LTRFINDER.out.scn + + ch_versions = ch_versions.mix(LTRFINDER.out.versions) + + // MODULE: CAT_CAT + ch_cat_cat_inputs = ch_ltrharvest_scn + | join(ch_ltrfinder_scn) + | map { meta, harvested, found -> [ meta, [ harvested, found ] ] } + CAT_CAT ( ch_cat_cat_inputs ) + + ch_ltr_candidates = CAT_CAT.out.file_out + ch_versions = ch_versions.mix(CAT_CAT.out.versions.first()) + + // MODULE: LTRRETRIEVER_LTRRETRIEVER + ch_ltrretriever_inputs = ch_sanitized_fasta.join(ch_ltr_candidates) + + LTRRETRIEVER_LTRRETRIEVER ( + ch_ltrretriever_inputs.map { meta, fasta, ltr -> [ meta, fasta ] }, + ch_ltrretriever_inputs.map { meta, fasta, ltr -> ltr }, + [], + [], + [] + ) + + ch_ltrretriever_log = LTRRETRIEVER_LTRRETRIEVER.out.log + ch_pass_list = LTRRETRIEVER_LTRRETRIEVER.out.pass_list + ch_annotation_out = LTRRETRIEVER_LTRRETRIEVER.out.annotation_out + ch_annotation_gff = LTRRETRIEVER_LTRRETRIEVER.out.annotation_gff + ch_ltrlib = LTRRETRIEVER_LTRRETRIEVER.out.ltrlib + ch_versions = ch_versions.mix(LTRRETRIEVER_LTRRETRIEVER.out.versions.first()) + + // MODULE: TIRLEARNER + TIRLEARNER ( + ch_sanitized_fasta, + params.species + ) + + ch_tirlearner_filtered_gff = TIRLEARNER.out.filtered_gff + ch_versions = ch_versions.mix(TIRLEARNER.out.versions.first()) + + // These can also run in parallel + // MODULE: ANNOSINE + ANNOSINE ( + ch_sanitized_fasta, + 3 // mode + ) + + // Currently it's a topic, so need to fix that + ch_versions = ch_versions.mix(ANNOSINE.out.versions) + cb_annosine_seed_sine = ANNOSINE.out.fa + + // MODULE: REPEATMODELER_BUILDDATABASE + ch_repeatmodeler_inputs = ch_sanitized_fasta + | map { meta, fasta -> + def size = fasta.size() + def size_threshold = 100_000 // bytes -> bp + + // TODO: Not the best way to set a size threshould + // but it is simple + // This is needed to avoid, + // Error: Database genome is not large enough ( minimum 40000 bp ) to process with RepeatModeler. + if ( size < size_threshold ) { + log.warn "RepeatModeler is skipped for genome '${meta.id}' as it is smaller than ${size_threshold} bytes" + return null + } + + return [ meta, fasta ] + } + | filter { it } + + REPEATMODELER_BUILDDATABASE ( ch_repeatmodeler_inputs ) + + ch_repeatmodeler_db = REPEATMODELER_BUILDDATABASE.out.db + ch_versions = ch_versions.mix(REPEATMODELER_BUILDDATABASE.out.versions.first()) + + // MODULE: REPEATMODELER_REPEATMODELER + REPEATMODELER_REPEATMODELER ( ch_repeatmodeler_db ) + + ch_repeatmodeler_fasta = REPEATMODELER_REPEATMODELER.out.fasta + ch_versions = ch_versions.mix(REPEATMODELER_REPEATMODELER.out.versions.first()) + + + // Function: Save versions + ch_versions = ch_versions + | unique + | map { yml -> + if ( yml ) { yml } + } + + ch_versions_yml = softwareVersionsToYAML(ch_versions) + | collectFile( + storeDir: "${params.outdir}/pipeline_info", + name: 'software_versions.yml', + sort: true, + newLine: true, + cache: false + ) + +} \ No newline at end of file From b55514543dd690f4d1e5070526f723886ed10dea Mon Sep 17 00:00:00 2001 From: Usman Rashid Date: Mon, 25 Nov 2024 15:41:03 +1300 Subject: [PATCH 20/49] Now using EDTA's test genome for the small test --- test/nf-test/small/main.nf.test | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/nf-test/small/main.nf.test b/test/nf-test/small/main.nf.test index 4cb9c26..7d40a02 100644 --- a/test/nf-test/small/main.nf.test +++ b/test/nf-test/small/main.nf.test @@ -7,7 +7,7 @@ nextflow_pipeline { when { params { - genomes = "https://raw.githubusercontent.com/nf-core/test-datasets/modules/data/genomics/eukaryotes/actinidia_chinensis/genome/chr1/genome.fasta.gz" + genomes = "https://raw.githubusercontent.com/jguhlin/EDTA/a2cd9a0777e4ac6e39545bacc3e752f94eb2f389/test/genome.fa" outdir = "$outputDir" } } From bdfe5ffe82fd105d45fafc92713aaeb79984a7be Mon Sep 17 00:00:00 2001 From: Usman Rashid Date: Tue, 26 Nov 2024 21:42:17 +1300 Subject: [PATCH 21/49] Updated PR template --- .github/PULL_REQUEST_TEMPLATE.md | 11 +++++------ nextflow.config | 2 +- 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index a78bd61..7919df0 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -1,19 +1,18 @@ ## PR checklist -- [ ] PR to `nextflow_reboot` branch -- [ ] `conda` and `container` directives. -- [ ] Docker container + singularity container (optional) +For Nextflow implementation, + +- [ ] `conda` and `container` directives are included for each process +- [ ] Docker container + singularity container (optional) are included for each process - [ ] Flow `meta.id` with each data channel - [ ] Use nf-core resource labels such as `process_high` - [ ] Used nf-core module diff --git a/nextflow.config b/nextflow.config index 34dfae1..ccf5850 100644 --- a/nextflow.config +++ b/nextflow.config @@ -93,7 +93,7 @@ conda { manifest { name = 'jguhlin/EDTA' description = 'Extensive de-novo TE Annotator on Nextflow' - manifest.author = 'Usman Rashid, Joseph Guhlin & Shujun Ou' + author = 'Usman Rashid, Joseph Guhlin & Shujun Ou' version = '0.1.0dev' nextflowVersion = '!>=23.04.0' } From aaae5e88dbce8046c55bdc94669817474a10fd43 Mon Sep 17 00:00:00 2001 From: Usman Rashid Date: Tue, 26 Nov 2024 22:21:39 +1300 Subject: [PATCH 22/49] Added fasta_helitronscanner_scan_draw --- conf/modules.config | 19 +++ modules.json | 19 +++ .../helitronscanner/draw/environment.yml | 7 + modules/gallvp/helitronscanner/draw/main.nf | 73 ++++++++ modules/gallvp/helitronscanner/draw/meta.yml | 69 ++++++++ .../helitronscanner/draw/tests/main.nf.test | 159 ++++++++++++++++++ .../draw/tests/main.nf.test.snap | 80 +++++++++ .../draw/tests/nextflow.config | 5 + .../helitronscanner/scan/environment.yml | 5 + modules/gallvp/helitronscanner/scan/main.nf | 72 ++++++++ modules/gallvp/helitronscanner/scan/meta.yml | 60 +++++++ .../helitronscanner/scan/tests/main.nf.test | 148 ++++++++++++++++ .../scan/tests/main.nf.test.snap | 104 ++++++++++++ .../fasta_helitronscanner_scan_draw/main.nf | 86 ++++++++++ .../fasta_helitronscanner_scan_draw/meta.yml | 45 +++++ .../tests/main.nf.test | 97 +++++++++++ .../tests/main.nf.test.snap | 137 +++++++++++++++ .../tests/nextflow.config | 20 +++ workflows/edta.nf | 26 +-- 19 files changed, 1220 insertions(+), 11 deletions(-) create mode 100644 modules/gallvp/helitronscanner/draw/environment.yml create mode 100644 modules/gallvp/helitronscanner/draw/main.nf create mode 100644 modules/gallvp/helitronscanner/draw/meta.yml create mode 100644 modules/gallvp/helitronscanner/draw/tests/main.nf.test create mode 100644 modules/gallvp/helitronscanner/draw/tests/main.nf.test.snap create mode 100644 modules/gallvp/helitronscanner/draw/tests/nextflow.config create mode 100644 modules/gallvp/helitronscanner/scan/environment.yml create mode 100644 modules/gallvp/helitronscanner/scan/main.nf create mode 100644 modules/gallvp/helitronscanner/scan/meta.yml create mode 100644 modules/gallvp/helitronscanner/scan/tests/main.nf.test create mode 100644 modules/gallvp/helitronscanner/scan/tests/main.nf.test.snap create mode 100644 subworkflows/gallvp/fasta_helitronscanner_scan_draw/main.nf create mode 100644 subworkflows/gallvp/fasta_helitronscanner_scan_draw/meta.yml create mode 100644 subworkflows/gallvp/fasta_helitronscanner_scan_draw/tests/main.nf.test create mode 100644 subworkflows/gallvp/fasta_helitronscanner_scan_draw/tests/main.nf.test.snap create mode 100644 subworkflows/gallvp/fasta_helitronscanner_scan_draw/tests/nextflow.config diff --git a/conf/modules.config b/conf/modules.config index 81d11dc..e70a6ea 100644 --- a/conf/modules.config +++ b/conf/modules.config @@ -19,5 +19,24 @@ process { withName: 'EDTA:REPEATMODELER_REPEATMODELER' { ext.args = '-engine ncbi' } + + withName: 'EDTA::FASTA_HELITRONSCANNER_SCAN_DRAW:HELITRONSCANNER_DRAW' { + ext.args = '-pure_helitron' + } + + withName: 'EDTA::FASTA_HELITRONSCANNER_SCAN_DRAW:HELITRONSCANNER_SCAN_HEAD_RC' { + ext.prefix = { "${meta.id}.rc" } + ext.args = '--rc' + } + + withName: 'EDTA::FASTA_HELITRONSCANNER_SCAN_DRAW:HELITRONSCANNER_SCAN_TAIL_RC' { + ext.prefix = { "${meta.id}.rc" } + ext.args = '--rc' + } + + withName: 'EDTA::FASTA_HELITRONSCANNER_SCAN_DRAW:HELITRONSCANNER_DRAW_RC' { + ext.prefix = { "${meta.id}.rc" } + ext.args = '-pure_helitron' + } } \ No newline at end of file diff --git a/modules.json b/modules.json index f9eedd5..5d34d4e 100644 --- a/modules.json +++ b/modules.json @@ -15,12 +15,31 @@ "git_sha": "ae9714c21ede9199a3118e3c20b65484aa73e232", "installed_by": ["modules"] }, + "helitronscanner/draw": { + "branch": "main", + "git_sha": "929d59d82f2e90fe79eb6f93d1ae739f22a894e1", + "installed_by": ["fasta_helitronscanner_scan_draw"] + }, + "helitronscanner/scan": { + "branch": "main", + "git_sha": "929d59d82f2e90fe79eb6f93d1ae739f22a894e1", + "installed_by": ["fasta_helitronscanner_scan_draw"] + }, "tirlearner": { "branch": "main", "git_sha": "a8939d36280e7d9037c7cf164eeede19e46546a4", "installed_by": ["modules"] } } + }, + "subworkflows": { + "gallvp": { + "fasta_helitronscanner_scan_draw": { + "branch": "main", + "git_sha": "970e3af38229845dd38c13d29b0905651a8e61f0", + "installed_by": ["subworkflows"] + } + } } }, "https://github.com/nf-core/modules.git": { diff --git a/modules/gallvp/helitronscanner/draw/environment.yml b/modules/gallvp/helitronscanner/draw/environment.yml new file mode 100644 index 0000000..479f61c --- /dev/null +++ b/modules/gallvp/helitronscanner/draw/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::helitronscanner=1.0" diff --git a/modules/gallvp/helitronscanner/draw/main.nf b/modules/gallvp/helitronscanner/draw/main.nf new file mode 100644 index 0000000..16ee59a --- /dev/null +++ b/modules/gallvp/helitronscanner/draw/main.nf @@ -0,0 +1,73 @@ +process HELITRONSCANNER_DRAW { + tag "$meta.id" + label 'process_high' + + conda "${moduleDir}/environment.yml" + container "${ workflow.containerEngine == 'singularity' && !task.ext.singularity_pull_docker_container ? + 'https://depot.galaxyproject.org/singularity/helitronscanner:1.0--hdfd78af_0': + 'biocontainers/helitronscanner:1.0--hdfd78af_0' }" + + input: + tuple val(meta), path(fasta) + tuple val(meta2), path(head) + tuple val(meta3), path(tail) + + output: + tuple val(meta), path("*.draw") , emit: draw + path "versions.yml" , emit: versions + + when: + task.ext.when == null || task.ext.when + + script: + def args = task.ext.args ?: '' + def args2 = task.ext.args2 ?: '' + def prefix = task.ext.prefix ?: "${meta.id}" + if ( !task.memory ) { error '[HELITRONSCANNER_DRAW] Available memory not known. Specify process memory requirements to fix this.' } + def avail_mem = (task.memory.giga*0.8).intValue() + """ + # Nextflow changes the container --entrypoint to /bin/bash (container default entrypoint: /usr/local/env-execute) + # Check for container variable initialisation script and source it. + if [ -f "/usr/local/env-activate.sh" ]; then + set +u # Otherwise, errors out because of various unbound variables + . "/usr/local/env-activate.sh" + set -u + fi + + HelitronScanner \\ + pairends \\ + -Xmx${avail_mem}g \\ + -head_score $head \\ + -tail_score $tail \\ + -output ${prefix}.pairends \\ + ${args2} + + HelitronScanner \\ + draw \\ + -Xmx${avail_mem}g \\ + -pscore ${prefix}.pairends \\ + -g $fasta \\ + -output ${prefix}.draw \\ + ${args} + + mv ${prefix}.draw.hel.fa \\ + ${prefix}.draw + + cat <<-END_VERSIONS > versions.yml + "${task.process}": + helitronscanner: \$(HelitronScanner |& sed -n 's/HelitronScanner V\\(.*\\)/V\\1/p') + END_VERSIONS + """ + + stub: + def args = task.ext.args ?: '' + def prefix = task.ext.prefix ?: "${meta.id}" + """ + touch ${prefix}.draw + + cat <<-END_VERSIONS > versions.yml + "${task.process}": + helitronscanner: \$(HelitronScanner |& sed -n 's/HelitronScanner V\\(.*\\)/V\\1/p') + END_VERSIONS + """ +} diff --git a/modules/gallvp/helitronscanner/draw/meta.yml b/modules/gallvp/helitronscanner/draw/meta.yml new file mode 100644 index 0000000..96bd260 --- /dev/null +++ b/modules/gallvp/helitronscanner/draw/meta.yml @@ -0,0 +1,69 @@ +# yaml-language-server: $schema=https://raw.githubusercontent.com/nf-core/modules/master/modules/meta-schema.json +name: "helitronscanner_draw" +description: HelitronScanner draw tool for Helitron transposons in genomes +keywords: + - genomics + - helitron + - scanner +tools: + - "helitronscanner": + description: "HelitronScanner uncovers a large overlooked cache of Helitron transposons + in many genomes" + homepage: "https://sourceforge.net/projects/helitronscanner" + documentation: "https://sourceforge.net/projects/helitronscanner" + tool_dev_url: "https://sourceforge.net/projects/helitronscanner" + doi: "10.1073/pnas.1410068111" + licence: ["GPL v3-or-later"] + identifier: "" + +input: + - - meta: + type: map + description: | + Groovy Map containing sample information + e.g. `[ id:'sample1' ]` + - fasta: + type: file + description: Genome data to scan for Helitrons + pattern: "*.{fa,fsa,fasta}" + - - meta2: + type: map + description: | + Groovy Map containing sample information + e.g. `[ id:'sample1' ]` + - head: + type: file + description: Output of the HelitronScanner head command + pattern: "*.head" + - - meta3: + type: map + description: | + Groovy Map containing sample information + e.g. `[ id:'sample1' ]` + - tail: + type: file + description: Output of the HelitronScanner tail command + pattern: "*.tail" +output: + - draw: + - meta: + type: map + description: | + Groovy Map containing sample information + e.g. `[ id:'sample1', single_end:false ]` + pattern: "*.draw" + - "*.draw": + type: map + description: | + The draw output from HelitronScanner + pattern: "*.draw" + - versions: + - versions.yml: + type: file + description: File containing software versions + pattern: "versions.yml" +authors: + - "@GallVp" + - "@jguhlin" +maintainers: + - "@GallVp" diff --git a/modules/gallvp/helitronscanner/draw/tests/main.nf.test b/modules/gallvp/helitronscanner/draw/tests/main.nf.test new file mode 100644 index 0000000..331f563 --- /dev/null +++ b/modules/gallvp/helitronscanner/draw/tests/main.nf.test @@ -0,0 +1,159 @@ +nextflow_process { + + name "Test Process HELITRONSCANNER_DRAW" + script "../main.nf" + config "./nextflow.config" + process "HELITRONSCANNER_DRAW" + + tag "modules" + tag "modules_gallvp" + tag "helitronscanner" + tag "helitronscanner/draw" + tag "helitronscanner/scan" + tag "gunzip" + + setup { + run('GUNZIP') { + script "../../../gunzip/main.nf" + + process { + """ + input[0] = [ + [ id:'test' ], // meta map + file(params.modules_testdata_base_path + 'genomics/eukaryotes/actinidia_chinensis/genome/chr1/genome.fasta.gz', checkIfExists: true) + ] + """ + } + } + + run('HELITRONSCANNER_SCAN', alias: 'HELITRONSCANNER_SCANHEAD') { + script "../../scan/main.nf" + + process { + """ + input[0] = GUNZIP.out.gunzip + input[1] = 'head' + input[2] = [] + input[3] = 0 + """ + } + } + + run('HELITRONSCANNER_SCAN', alias: 'HELITRONSCANNER_SCANTAIL') { + script "../../scan/main.nf" + + process { + """ + input[0] = GUNZIP.out.gunzip + input[1] = 'tail' + input[2] = [] + input[3] = 0 + """ + } + } + } + + test("actinidia_chinensis-genome_1_fasta_gz-success") { + + when { + process { + """ + input[0] = GUNZIP.out.gunzip + input[1] = HELITRONSCANNER_SCANHEAD.out.scan + input[2] = HELITRONSCANNER_SCANTAIL.out.scan + """ + } + } + + then { + assertAll( + { assert process.success }, + { assert snapshot(process.out).match() }, + ) + } + + } + + test("sarscov2 - genome - success") { + + setup { + run('HELITRONSCANNER_SCAN', alias: 'HELITRONSCANNER_SCANHEAD_SARSCOV2') { + script "../../scan/main.nf" + + process { + """ + input[0] = [ + [ id: 'test' ], + file(params.modules_testdata_base_path + 'genomics/sarscov2/genome/genome.fasta', checkIfExists: true) + ] + input[1] = 'head' + input[2] = [] + input[3] = 0 + """ + } + } + + run('HELITRONSCANNER_SCAN', alias: 'HELITRONSCANNER_SCANTAIL_SARSCOV2') { + script "../../scan/main.nf" + + process { + """ + input[0] = [ + [ id: 'test' ], + file(params.modules_testdata_base_path + 'genomics/sarscov2/genome/genome.fasta', checkIfExists: true) + ] + input[1] = 'tail' + input[2] = [] + input[3] = 0 + """ + } + } + } + + when { + process { + """ + input[0] = [ + [ id: 'test' ], + file(params.modules_testdata_base_path + 'genomics/sarscov2/genome/genome.fasta', checkIfExists: true) + ] + input[1] = HELITRONSCANNER_SCANHEAD_SARSCOV2.out.scan + input[2] = HELITRONSCANNER_SCANTAIL_SARSCOV2.out.scan + """ + } + } + + then { + assertAll( + { assert process.success }, + { assert process.out.draw[0][1] != null }, + { assert snapshot(process.out.versions).match() } + ) + } + + } + + test("stub") { + + options "-stub" + + when { + process { + """ + input[0] = GUNZIP.out.gunzip + input[1] = HELITRONSCANNER_SCANHEAD.out.scan + input[2] = HELITRONSCANNER_SCANTAIL.out.scan + """ + } + } + + then { + assertAll( + { assert process.success }, + { assert snapshot(process.out).match() } + ) + } + + } + +} diff --git a/modules/gallvp/helitronscanner/draw/tests/main.nf.test.snap b/modules/gallvp/helitronscanner/draw/tests/main.nf.test.snap new file mode 100644 index 0000000..208d14d --- /dev/null +++ b/modules/gallvp/helitronscanner/draw/tests/main.nf.test.snap @@ -0,0 +1,80 @@ +{ + "actinidia_chinensis-genome_1_fasta_gz-success": { + "content": [ + { + "0": [ + [ + { + "id": "test" + }, + "test.draw:md5,434aaaa70c294464e6bae93b2895b13c" + ] + ], + "1": [ + "versions.yml:md5,4b206968a702782fa04d2ba560c89728" + ], + "draw": [ + [ + { + "id": "test" + }, + "test.draw:md5,434aaaa70c294464e6bae93b2895b13c" + ] + ], + "versions": [ + "versions.yml:md5,4b206968a702782fa04d2ba560c89728" + ] + } + ], + "meta": { + "nf-test": "0.9.0", + "nextflow": "24.04.4" + }, + "timestamp": "2024-10-17T11:51:49.477212" + }, + "stub": { + "content": [ + { + "0": [ + [ + { + "id": "test" + }, + "test.draw:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "1": [ + "versions.yml:md5,4b206968a702782fa04d2ba560c89728" + ], + "draw": [ + [ + { + "id": "test" + }, + "test.draw:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "versions": [ + "versions.yml:md5,4b206968a702782fa04d2ba560c89728" + ] + } + ], + "meta": { + "nf-test": "0.9.0", + "nextflow": "24.04.4" + }, + "timestamp": "2024-10-17T11:52:47.744899" + }, + "sarscov2 - genome - success": { + "content": [ + [ + "versions.yml:md5,4b206968a702782fa04d2ba560c89728" + ] + ], + "meta": { + "nf-test": "0.9.0", + "nextflow": "24.04.4" + }, + "timestamp": "2024-10-17T11:58:21.800568" + } +} \ No newline at end of file diff --git a/modules/gallvp/helitronscanner/draw/tests/nextflow.config b/modules/gallvp/helitronscanner/draw/tests/nextflow.config new file mode 100644 index 0000000..57f4900 --- /dev/null +++ b/modules/gallvp/helitronscanner/draw/tests/nextflow.config @@ -0,0 +1,5 @@ +process { + withName: 'HELITRONSCANNER_DRAW' { + ext.args = '-pure_helitron' + } +} diff --git a/modules/gallvp/helitronscanner/scan/environment.yml b/modules/gallvp/helitronscanner/scan/environment.yml new file mode 100644 index 0000000..ea89d39 --- /dev/null +++ b/modules/gallvp/helitronscanner/scan/environment.yml @@ -0,0 +1,5 @@ +channels: + - conda-forge + - bioconda +dependencies: + - "bioconda::helitronscanner=1.0" diff --git a/modules/gallvp/helitronscanner/scan/main.nf b/modules/gallvp/helitronscanner/scan/main.nf new file mode 100644 index 0000000..0f748ab --- /dev/null +++ b/modules/gallvp/helitronscanner/scan/main.nf @@ -0,0 +1,72 @@ +process HELITRONSCANNER_SCAN { + tag "$meta.id" + label 'process_high' + + conda "${moduleDir}/environment.yml" + container "${ workflow.containerEngine == 'singularity' && !task.ext.singularity_pull_docker_container ? + 'https://depot.galaxyproject.org/singularity/helitronscanner:1.0--hdfd78af_0': + 'biocontainers/helitronscanner:1.0--hdfd78af_0' }" + + input: + tuple val(meta), path(fasta) + val command + path lcv_filepath + val buffer_size + + output: + tuple val(meta), path("*.$command") , emit: scan + path "versions.yml" , emit: versions + + when: + task.ext.when == null || task.ext.when + + script: + if ( command !in [ 'head', 'tail' ] ) error "[HELITRONSCANNER_SCAN] command argument should be 'head' or 'tail'" + if ( !task.memory ) { error '[HELITRONSCANNER_SCAN] Available memory not known. Specify process memory requirements to fix this.' } + + def args = task.ext.args ?: '' + def prefix = task.ext.prefix ?: "${meta.id}" + + def is_head = { command == 'head' }() + def subcommand = is_head ? 'scanHead' : 'scanTail' + def lcvs_file = is_head ? 'head.lcvs' : 'tail.lcvs' + def lcv_arg = lcv_filepath ? "-lcv_filepath $lcv_filepath" : "-lcv_filepath \$HELITRONSCANNER_TRAININGSET_PATH/$lcvs_file" + def avail_mem = (task.memory.giga*0.8).intValue() + """ + # Nextflow changes the container --entrypoint to /bin/bash (container default entrypoint: /usr/local/env-execute) + # Check for container variable initialisation script and source it. + if [ -f "/usr/local/env-activate.sh" ]; then + set +u # Otherwise, errors out because of various unbound variables + . "/usr/local/env-activate.sh" + set -u + fi + + HelitronScanner \\ + $subcommand \\ + -Xmx${avail_mem}g \\ + $lcv_arg \\ + -genome $fasta \\ + -buffer_size $buffer_size \\ + -threads_LCV $task.cpus \\ + $args \\ + -output ${prefix}.${command} + + cat <<-END_VERSIONS > versions.yml + "${task.process}": + helitronscanner: \$(HelitronScanner |& sed -n 's/HelitronScanner V\\(.*\\)/V\\1/p') + END_VERSIONS + """ + + stub: + if ( command !in [ 'head', 'tail' ] ) error "[HELITRONSCANNER_SCAN] command argument should be 'head' or 'tail'" + def args = task.ext.args ?: '' + def prefix = task.ext.prefix ?: "${meta.id}" + """ + touch ${prefix}.${command} + + cat <<-END_VERSIONS > versions.yml + "${task.process}": + helitronscanner: \$(HelitronScanner |& sed -n 's/HelitronScanner V\\(.*\\)/V\\1/p') + END_VERSIONS + """ +} diff --git a/modules/gallvp/helitronscanner/scan/meta.yml b/modules/gallvp/helitronscanner/scan/meta.yml new file mode 100644 index 0000000..ed8f956 --- /dev/null +++ b/modules/gallvp/helitronscanner/scan/meta.yml @@ -0,0 +1,60 @@ +# yaml-language-server: $schema=https://raw.githubusercontent.com/nf-core/modules/master/modules/meta-schema.json +name: "helitronscanner_scan" +description: HelitronScanner scanHead and scanTail tools for Helitron transposons + in genomes +keywords: + - genomics + - helitron + - scanner +tools: + - "helitronscanner": + description: "HelitronScanner uncovers a large overlooked cache of Helitron transposons + in many genomes" + homepage: "https://sourceforge.net/projects/helitronscanner" + documentation: "https://sourceforge.net/projects/helitronscanner" + tool_dev_url: "https://sourceforge.net/projects/helitronscanner" + doi: "10.1073/pnas.1410068111" + licence: ["GPL v3-or-later"] + identifier: "" +input: + - - meta: + type: map + description: | + Groovy Map containing sample information + e.g. `[ id:'sample1' ]` + - fasta: + type: file + description: Genome data to scan for Helitrons + pattern: "*.{fa,fsa,fasta}" + - - command: + type: string + description: Command to execute. One of [ 'head', 'tail' ] + - - lcv_filepath: + type: file + description: LCV file path. If not provided by setting it to [], the LCV file + packaged with the module will be used + pattern: "*.lcvs" + - - buffer_size: + type: integer + description: Genome slice size (use negative or zero for non-buffer, i.e. treat + every whole chromosome) +output: + - scan: + - meta: + type: map + description: | + Groovy Map containing sample information + e.g. `[ id:'sample1' ]` + - "*.$command": + type: file + description: Head or tail file depending on the command + pattern: "*.$command" + - versions: + - versions.yml: + type: file + description: File containing software versions + pattern: "versions.yml" +authors: + - "@GallVp" +maintainers: + - "@GallVp" diff --git a/modules/gallvp/helitronscanner/scan/tests/main.nf.test b/modules/gallvp/helitronscanner/scan/tests/main.nf.test new file mode 100644 index 0000000..55bcfed --- /dev/null +++ b/modules/gallvp/helitronscanner/scan/tests/main.nf.test @@ -0,0 +1,148 @@ +nextflow_process { + + name "Test Process HELITRONSCANNER_SCAN" + script "../main.nf" + process "HELITRONSCANNER_SCAN" + + tag "modules" + tag "modules_gallvp" + tag "helitronscanner" + tag "helitronscanner/scan" + tag "gunzip" + + setup { + run('GUNZIP') { + script "../../../gunzip/main.nf" + + process { + """ + input[0] = [ + [ id:'test' ], // meta map + file(params.modules_testdata_base_path + 'genomics/eukaryotes/actinidia_chinensis/genome/chr1/genome.fasta.gz', checkIfExists: true) + ] + """ + } + } + } + + test("actinidia_chinensis - genome_1_fasta_gz - head - success") { + + when { + process { + """ + input[0] = GUNZIP.out.gunzip + input[1] = 'head' + input[2] = [] + input[3] = 0 + """ + } + } + + then { + assertAll( + { assert process.success }, + { assert path(process.out.scan[0][1]).text.contains('2729827:1 2729980:1 2730005:1') }, + { assert snapshot(process.out.versions).match() } + ) + } + + } + + test("actinidia_chinensis - genome_1_fasta_gz - tail - success") { + + when { + process { + """ + input[0] = GUNZIP.out.gunzip + input[1] = 'tail' + input[2] = [] + input[3] = 0 + """ + } + } + + then { + assertAll( + { assert process.success }, + { assert path(process.out.scan[0][1]).text.contains('7265:1 7951:1 9264:2 9398:1') }, + { assert snapshot(process.out.versions).match() } + ) + } + + } + + test("sarscov2 - genome - head - success") { + + when { + process { + """ + input[0] = [ + [ id: 'test' ], + file(params.modules_testdata_base_path + 'genomics/sarscov2/genome/genome.fasta', checkIfExists: true) + ] + input[1] = 'head' + input[2] = [] + input[3] = 0 + """ + } + } + + then { + assertAll( + { assert process.success }, + { assert path(process.out.scan[0][1]).text.contains('113:2 236:1 1158:1 1754:2 1771:1') }, + { assert snapshot(process.out.versions).match() } + ) + } + + } + + test("stub") { + + options "-stub" + + when { + process { + """ + input[0] = GUNZIP.out.gunzip + input[1] = 'head' + input[2] = [] + input[3] = 0 + """ + } + } + + then { + assertAll( + { assert process.success }, + { assert snapshot(process.out).match() } + ) + } + + } + + test("stub - tail") { + + options "-stub" + + when { + process { + """ + input[0] = GUNZIP.out.gunzip + input[1] = 'tail' + input[2] = [] + input[3] = 0 + """ + } + } + + then { + assertAll( + { assert process.success }, + { assert snapshot(process.out).match() } + ) + } + + } + +} diff --git a/modules/gallvp/helitronscanner/scan/tests/main.nf.test.snap b/modules/gallvp/helitronscanner/scan/tests/main.nf.test.snap new file mode 100644 index 0000000..ac7ae5e --- /dev/null +++ b/modules/gallvp/helitronscanner/scan/tests/main.nf.test.snap @@ -0,0 +1,104 @@ +{ + "actinidia_chinensis - genome_1_fasta_gz - tail - success": { + "content": [ + [ + "versions.yml:md5,3c675f4be863f58c88db5a2936beaa88" + ] + ], + "meta": { + "nf-test": "0.9.0", + "nextflow": "24.04.4" + }, + "timestamp": "2024-10-03T11:51:54.403269" + }, + "stub": { + "content": [ + { + "0": [ + [ + { + "id": "test" + }, + "test.head:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "1": [ + "versions.yml:md5,3c675f4be863f58c88db5a2936beaa88" + ], + "scan": [ + [ + { + "id": "test" + }, + "test.head:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "versions": [ + "versions.yml:md5,3c675f4be863f58c88db5a2936beaa88" + ] + } + ], + "meta": { + "nf-test": "0.9.0", + "nextflow": "24.04.4" + }, + "timestamp": "2024-10-03T11:52:10.250343" + }, + "sarscov2 - genome - head - success": { + "content": [ + [ + "versions.yml:md5,3c675f4be863f58c88db5a2936beaa88" + ] + ], + "meta": { + "nf-test": "0.9.0", + "nextflow": "24.04.4" + }, + "timestamp": "2024-10-03T11:43:05.068944" + }, + "stub - tail": { + "content": [ + { + "0": [ + [ + { + "id": "test" + }, + "test.tail:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "1": [ + "versions.yml:md5,3c675f4be863f58c88db5a2936beaa88" + ], + "scan": [ + [ + { + "id": "test" + }, + "test.tail:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "versions": [ + "versions.yml:md5,3c675f4be863f58c88db5a2936beaa88" + ] + } + ], + "meta": { + "nf-test": "0.9.0", + "nextflow": "24.04.4" + }, + "timestamp": "2024-10-03T11:39:42.225262" + }, + "actinidia_chinensis - genome_1_fasta_gz - head - success": { + "content": [ + [ + "versions.yml:md5,3c675f4be863f58c88db5a2936beaa88" + ] + ], + "meta": { + "nf-test": "0.9.0", + "nextflow": "24.04.4" + }, + "timestamp": "2024-10-03T11:51:18.942428" + } +} \ No newline at end of file diff --git a/subworkflows/gallvp/fasta_helitronscanner_scan_draw/main.nf b/subworkflows/gallvp/fasta_helitronscanner_scan_draw/main.nf new file mode 100644 index 0000000..40c2541 --- /dev/null +++ b/subworkflows/gallvp/fasta_helitronscanner_scan_draw/main.nf @@ -0,0 +1,86 @@ +include { HELITRONSCANNER_SCAN as HELITRONSCANNER_SCAN_HEAD } from '../../../modules/gallvp/helitronscanner/scan/main.nf' +include { HELITRONSCANNER_SCAN as HELITRONSCANNER_SCAN_TAIL } from '../../../modules/gallvp/helitronscanner/scan/main.nf' +include { HELITRONSCANNER_DRAW } from '../../../modules/gallvp/helitronscanner/draw/main.nf' + +include { HELITRONSCANNER_SCAN as HELITRONSCANNER_SCAN_HEAD_RC } from '../../../modules/gallvp/helitronscanner/scan/main.nf' +include { HELITRONSCANNER_SCAN as HELITRONSCANNER_SCAN_TAIL_RC } from '../../../modules/gallvp/helitronscanner/scan/main.nf' +include { HELITRONSCANNER_DRAW as HELITRONSCANNER_DRAW_RC } from '../../../modules/gallvp/helitronscanner/draw/main.nf' + +workflow FASTA_HELITRONSCANNER_SCAN_DRAW { + + take: + ch_fasta // channel: [ val(meta), fasta ] + + main: + + ch_versions = Channel.empty() + + // MODULE: HELITRONSCANNER_SCAN as HELITRONSCANNER_SCAN_HEAD + HELITRONSCANNER_SCAN_HEAD ( + ch_fasta, + 'head', // command + [], // lcv_filepath + 0 // buffer_size + ) + + ch_helitronscanner_scan_head = HELITRONSCANNER_SCAN_HEAD.out.scan + ch_versions = ch_versions.mix(HELITRONSCANNER_SCAN_HEAD.out.versions) + + // MODULE: HELITRONSCANNER_SCAN as HELITRONSCANNER_SCAN_TAIL + HELITRONSCANNER_SCAN_TAIL ( + ch_fasta, + 'tail', // command + [], // lcv_filepath + 0 // buffer_size + ) + + ch_helitronscanner_scan_tail = HELITRONSCANNER_SCAN_TAIL.out.scan + ch_versions = ch_versions.mix(HELITRONSCANNER_SCAN_TAIL.out.versions) + + // MODULE: HELITRONSCANNER_DRAW + HELITRONSCANNER_DRAW ( + ch_fasta, + ch_helitronscanner_scan_head, + ch_helitronscanner_scan_tail + ) + + ch_helitronscanner_draw = HELITRONSCANNER_DRAW.out.draw + ch_versions = ch_versions.mix(HELITRONSCANNER_DRAW.out.versions) + + // MODULE: HELITRONSCANNER_SCAN as HELITRONSCANNER_SCAN_HEAD_RC + HELITRONSCANNER_SCAN_HEAD_RC ( + ch_fasta, + 'head', // command + [], // lcv_filepath + 0 // buffer_size + ) + + ch_helitronscanner_scan_head_rc = HELITRONSCANNER_SCAN_HEAD_RC.out.scan + ch_versions = ch_versions.mix(HELITRONSCANNER_SCAN_HEAD_RC.out.versions) + + // MODULE: HELITRONSCANNER_SCAN as HELITRONSCANNER_SCAN_TAIL_RC + HELITRONSCANNER_SCAN_TAIL_RC ( + ch_fasta, + 'tail', // command + [], // lcv_filepath + 0 // buffer_size + ) + + ch_helitronscanner_scan_tail_rc = HELITRONSCANNER_SCAN_TAIL_RC.out.scan + ch_versions = ch_versions.mix(HELITRONSCANNER_SCAN_TAIL_RC.out.versions) + + // MODULE: HELITRONSCANNER_DRAW as HELITRONSCANNER_DRAW_RC + HELITRONSCANNER_DRAW_RC ( + ch_fasta, + ch_helitronscanner_scan_head_rc, + ch_helitronscanner_scan_tail_rc + ) + + ch_helitronscanner_draw_rc = HELITRONSCANNER_DRAW_RC.out.draw + ch_versions = ch_versions.mix(HELITRONSCANNER_DRAW_RC.out.versions) + + emit: + helitronscanner_draw = ch_helitronscanner_draw // channel: [ val(meta), draw ] + helitronscanner_draw_rc = ch_helitronscanner_draw_rc // channel: [ val(meta), rc.draw ] + versions = ch_versions // channel: [ versions.yml ] +} diff --git a/subworkflows/gallvp/fasta_helitronscanner_scan_draw/meta.yml b/subworkflows/gallvp/fasta_helitronscanner_scan_draw/meta.yml new file mode 100644 index 0000000..62c4b39 --- /dev/null +++ b/subworkflows/gallvp/fasta_helitronscanner_scan_draw/meta.yml @@ -0,0 +1,45 @@ +# yaml-language-server: $schema=https://raw.githubusercontent.com/nf-core/modules/master/subworkflows/yaml-schema.json +name: "fasta_helitronscanner_scan_draw" +description: Find helitrons suing Helitronscanner scan, pairends and draw subcommands +keywords: + - helitron + - scan + - draw + - pairends + - repeat + - genomics + - fasta +components: + - helitronscanner/scan + - helitronscanner/draw +input: + - ch_fasta: + type: file + description: | + Genome fasta + Structure: [ val(meta), fasta ] + pattern: "*.fasta" +output: + - helitronscanner_draw: + type: file + description: | + Helitronscanner draw file + Structure: [ val(meta), draw ] + pattern: "*.draw" + - helitronscanner_draw_rc: + type: file + description: | + Helitronscanner reverse complement draw file + Structure: [ val(meta), rc.draw ] + pattern: "*.rc.draw" + - versions: + type: file + description: | + File containing software versions + Structure: [ path(versions.yml) ] + pattern: "versions.yml" +authors: + - "@GallVp" +maintainers: + - "@GallVp" + - "@jguhlin" diff --git a/subworkflows/gallvp/fasta_helitronscanner_scan_draw/tests/main.nf.test b/subworkflows/gallvp/fasta_helitronscanner_scan_draw/tests/main.nf.test new file mode 100644 index 0000000..7a9bd05 --- /dev/null +++ b/subworkflows/gallvp/fasta_helitronscanner_scan_draw/tests/main.nf.test @@ -0,0 +1,97 @@ +nextflow_workflow { + + name "Test Subworkflow FASTA_HELITRONSCANNER_SCAN_DRAW" + script "../main.nf" + workflow "FASTA_HELITRONSCANNER_SCAN_DRAW" + config "./nextflow.config" + + tag "subworkflows" + tag "subworkflows_gallvp" + tag "subworkflows/fasta_helitronscanner_scan_draw" + + tag "helitronscanner/draw" + tag "helitronscanner/scan" + tag "gunzip" + + setup { + run('GUNZIP') { + script "../../../../modules/gallvp/gunzip/main.nf" + + process { + """ + input[0] = [ + [ id:'test' ], // meta map + file(params.modules_testdata_base_path + 'genomics/eukaryotes/actinidia_chinensis/genome/chr1/genome.fasta.gz', checkIfExists: true) + ] + """ + } + } + } + + test("actinidia_chinensis - genome_1_fasta_gz") { + + when { + workflow { + """ + input[0] = GUNZIP.out.gunzip + """ + } + } + + then { + assertAll( + { assert workflow.success }, + { assert snapshot(workflow.out).match() } + ) + } + + } + + test("sarscov2 - genome") { + + when { + workflow { + """ + input[0] = [ + [ id: 'test' ], + file(params.modules_testdata_base_path + 'genomics/sarscov2/genome/genome.fasta', checkIfExists: true) + ] + """ + } + } + + then { + assertAll( + { assert workflow.success }, + { assert workflow.out.helitronscanner_draw }, // empty + { assert workflow.out.helitronscanner_draw_rc }, // empty + { assert snapshot( + workflow.out.versions + ).match() + } + ) + } + + } + + test("stub") { + + options "-stub" + + when { + workflow { + """ + input[0] = GUNZIP.out.gunzip + """ + } + } + + then { + assertAll( + { assert workflow.success }, + { assert snapshot(workflow.out).match() } + ) + } + + } +} diff --git a/subworkflows/gallvp/fasta_helitronscanner_scan_draw/tests/main.nf.test.snap b/subworkflows/gallvp/fasta_helitronscanner_scan_draw/tests/main.nf.test.snap new file mode 100644 index 0000000..f6b8077 --- /dev/null +++ b/subworkflows/gallvp/fasta_helitronscanner_scan_draw/tests/main.nf.test.snap @@ -0,0 +1,137 @@ +{ + "actinidia_chinensis - genome_1_fasta_gz": { + "content": [ + { + "0": [ + [ + { + "id": "test" + }, + "test.draw:md5,434aaaa70c294464e6bae93b2895b13c" + ] + ], + "1": [ + [ + { + "id": "test" + }, + "test.rc.draw:md5,7aeed0fdf50f8cb2516b2e92f456835e" + ] + ], + "2": [ + "versions.yml:md5,42ab90da7ef86125b9dec685f4f08e84", + "versions.yml:md5,9b6a0f2f56d89451cf16702e2f358520", + "versions.yml:md5,a928ae7c94a23e5c62c27915dada660c", + "versions.yml:md5,c417497b1caea1cd51f8440df92daa40", + "versions.yml:md5,d786d80e914a86ce0361cfc0bc62cda9", + "versions.yml:md5,f3b6aa20e81f04fd731fad64f7ea3c14" + ], + "helitronscanner_draw": [ + [ + { + "id": "test" + }, + "test.draw:md5,434aaaa70c294464e6bae93b2895b13c" + ] + ], + "helitronscanner_draw_rc": [ + [ + { + "id": "test" + }, + "test.rc.draw:md5,7aeed0fdf50f8cb2516b2e92f456835e" + ] + ], + "versions": [ + "versions.yml:md5,42ab90da7ef86125b9dec685f4f08e84", + "versions.yml:md5,9b6a0f2f56d89451cf16702e2f358520", + "versions.yml:md5,a928ae7c94a23e5c62c27915dada660c", + "versions.yml:md5,c417497b1caea1cd51f8440df92daa40", + "versions.yml:md5,d786d80e914a86ce0361cfc0bc62cda9", + "versions.yml:md5,f3b6aa20e81f04fd731fad64f7ea3c14" + ] + } + ], + "meta": { + "nf-test": "0.9.0", + "nextflow": "24.04.4" + }, + "timestamp": "2024-11-21T11:53:51.717511" + }, + "stub": { + "content": [ + { + "0": [ + [ + { + "id": "test" + }, + "test.draw:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "1": [ + [ + { + "id": "test" + }, + "test.rc.draw:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "2": [ + "versions.yml:md5,42ab90da7ef86125b9dec685f4f08e84", + "versions.yml:md5,9b6a0f2f56d89451cf16702e2f358520", + "versions.yml:md5,a928ae7c94a23e5c62c27915dada660c", + "versions.yml:md5,c417497b1caea1cd51f8440df92daa40", + "versions.yml:md5,d786d80e914a86ce0361cfc0bc62cda9", + "versions.yml:md5,f3b6aa20e81f04fd731fad64f7ea3c14" + ], + "helitronscanner_draw": [ + [ + { + "id": "test" + }, + "test.draw:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "helitronscanner_draw_rc": [ + [ + { + "id": "test" + }, + "test.rc.draw:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "versions": [ + "versions.yml:md5,42ab90da7ef86125b9dec685f4f08e84", + "versions.yml:md5,9b6a0f2f56d89451cf16702e2f358520", + "versions.yml:md5,a928ae7c94a23e5c62c27915dada660c", + "versions.yml:md5,c417497b1caea1cd51f8440df92daa40", + "versions.yml:md5,d786d80e914a86ce0361cfc0bc62cda9", + "versions.yml:md5,f3b6aa20e81f04fd731fad64f7ea3c14" + ] + } + ], + "meta": { + "nf-test": "0.9.0", + "nextflow": "24.04.4" + }, + "timestamp": "2024-11-21T11:54:17.824589" + }, + "sarscov2 - genome": { + "content": [ + [ + "versions.yml:md5,42ab90da7ef86125b9dec685f4f08e84", + "versions.yml:md5,9b6a0f2f56d89451cf16702e2f358520", + "versions.yml:md5,a928ae7c94a23e5c62c27915dada660c", + "versions.yml:md5,c417497b1caea1cd51f8440df92daa40", + "versions.yml:md5,d786d80e914a86ce0361cfc0bc62cda9", + "versions.yml:md5,f3b6aa20e81f04fd731fad64f7ea3c14" + ] + ], + "meta": { + "nf-test": "0.9.0", + "nextflow": "24.04.4" + }, + "timestamp": "2024-11-21T12:03:43.135817" + } +} \ No newline at end of file diff --git a/subworkflows/gallvp/fasta_helitronscanner_scan_draw/tests/nextflow.config b/subworkflows/gallvp/fasta_helitronscanner_scan_draw/tests/nextflow.config new file mode 100644 index 0000000..b98d370 --- /dev/null +++ b/subworkflows/gallvp/fasta_helitronscanner_scan_draw/tests/nextflow.config @@ -0,0 +1,20 @@ +process { + withName: 'HELITRONSCANNER_DRAW' { + ext.args = '-pure_helitron' + } + + withName: 'HELITRONSCANNER_SCAN_HEAD_RC' { + ext.prefix = { "${meta.id}.rc" } + ext.args = '--rc' + } + + withName: 'HELITRONSCANNER_SCAN_TAIL_RC' { + ext.prefix = { "${meta.id}.rc" } + ext.args = '--rc' + } + + withName: 'HELITRONSCANNER_DRAW_RC' { + ext.prefix = { "${meta.id}.rc" } + ext.args = '-pure_helitron' + } +} diff --git a/workflows/edta.nf b/workflows/edta.nf index df8b2d9..357b427 100644 --- a/workflows/edta.nf +++ b/workflows/edta.nf @@ -1,14 +1,15 @@ -include { SANITIZE_HEADERS } from '../modules/local/sanitize/main.nf' -include { LTRHARVEST } from '../modules/nf-core/ltrharvest/main.nf' -include { LTRFINDER } from '../modules/nf-core/ltrfinder/main.nf' -include { CAT_CAT } from '../modules/nf-core/cat/cat/main.nf' -include { LTRRETRIEVER_LTRRETRIEVER } from '../modules/nf-core/ltrretriever/ltrretriever/main.nf' -include { TIRLEARNER } from '../modules/gallvp/tirlearner/main.nf' -include { ANNOSINE } from '../modules/gallvp/annosine/main.nf' -include { REPEATMODELER_BUILDDATABASE } from '../modules/nf-core/repeatmodeler/builddatabase/main.nf' -include { REPEATMODELER_REPEATMODELER } from '../modules/nf-core/repeatmodeler/repeatmodeler/main.nf' - -include { softwareVersionsToYAML } from '../modules/local/utils/main.nf' +include { SANITIZE_HEADERS } from '../modules/local/sanitize/main.nf' +include { LTRHARVEST } from '../modules/nf-core/ltrharvest/main.nf' +include { LTRFINDER } from '../modules/nf-core/ltrfinder/main.nf' +include { CAT_CAT } from '../modules/nf-core/cat/cat/main.nf' +include { LTRRETRIEVER_LTRRETRIEVER } from '../modules/nf-core/ltrretriever/ltrretriever/main.nf' +include { TIRLEARNER } from '../modules/gallvp/tirlearner/main.nf' +include { ANNOSINE } from '../modules/gallvp/annosine/main.nf' +include { REPEATMODELER_BUILDDATABASE } from '../modules/nf-core/repeatmodeler/builddatabase/main.nf' +include { REPEATMODELER_REPEATMODELER } from '../modules/nf-core/repeatmodeler/repeatmodeler/main.nf' +include { FASTA_HELITRONSCANNER_SCAN_DRAW } from '../subworkflows/gallvp/fasta_helitronscanner_scan_draw/main.nf' + +include { softwareVersionsToYAML } from '../modules/local/utils/main.nf' workflow EDTA { @@ -124,6 +125,9 @@ workflow EDTA { ch_repeatmodeler_fasta = REPEATMODELER_REPEATMODELER.out.fasta ch_versions = ch_versions.mix(REPEATMODELER_REPEATMODELER.out.versions.first()) + // MODULE: FASTA_HELITRONSCANNER_SCAN_DRAW + FASTA_HELITRONSCANNER_SCAN_DRAW ( ch_sanitized_fasta ) + // Function: Save versions ch_versions = ch_versions From 5a874e79e64c30db9bc83126fb8a8880544b42c2 Mon Sep 17 00:00:00 2001 From: Usman Rashid Date: Tue, 26 Nov 2024 22:39:10 +1300 Subject: [PATCH 23/49] Updated snapshots --- conf/modules.config | 8 ++++---- test/nf-test/small/main.nf.test.snap | 22 ++++++++++++++++++++-- test/nf-test/tiny/main.nf.test.snap | 22 ++++++++++++++++++++-- workflows/edta.nf | 4 ++++ 4 files changed, 48 insertions(+), 8 deletions(-) diff --git a/conf/modules.config b/conf/modules.config index e70a6ea..39b1af8 100644 --- a/conf/modules.config +++ b/conf/modules.config @@ -20,21 +20,21 @@ process { ext.args = '-engine ncbi' } - withName: 'EDTA::FASTA_HELITRONSCANNER_SCAN_DRAW:HELITRONSCANNER_DRAW' { + withName: 'EDTA:FASTA_HELITRONSCANNER_SCAN_DRAW:HELITRONSCANNER_DRAW' { ext.args = '-pure_helitron' } - withName: 'EDTA::FASTA_HELITRONSCANNER_SCAN_DRAW:HELITRONSCANNER_SCAN_HEAD_RC' { + withName: 'EDTA:FASTA_HELITRONSCANNER_SCAN_DRAW:HELITRONSCANNER_SCAN_HEAD_RC' { ext.prefix = { "${meta.id}.rc" } ext.args = '--rc' } - withName: 'EDTA::FASTA_HELITRONSCANNER_SCAN_DRAW:HELITRONSCANNER_SCAN_TAIL_RC' { + withName: 'EDTA:FASTA_HELITRONSCANNER_SCAN_DRAW:HELITRONSCANNER_SCAN_TAIL_RC' { ext.prefix = { "${meta.id}.rc" } ext.args = '--rc' } - withName: 'EDTA::FASTA_HELITRONSCANNER_SCAN_DRAW:HELITRONSCANNER_DRAW_RC' { + withName: 'EDTA:FASTA_HELITRONSCANNER_SCAN_DRAW:HELITRONSCANNER_DRAW_RC' { ext.prefix = { "${meta.id}.rc" } ext.args = '-pure_helitron' } diff --git a/test/nf-test/small/main.nf.test.snap b/test/nf-test/small/main.nf.test.snap index d53e834..9154059 100644 --- a/test/nf-test/small/main.nf.test.snap +++ b/test/nf-test/small/main.nf.test.snap @@ -2,7 +2,7 @@ "small genome": { "content": [ { - "successful tasks": 9, + "successful tasks": 15, "versions": { "ANNOSINE": { "annosine": "2.0.7" @@ -10,6 +10,24 @@ "CAT_CAT": { "pigz": "2.3.4" }, + "HELITRONSCANNER_DRAW": { + "helitronscanner": "V1.1" + }, + "HELITRONSCANNER_DRAW_RC": { + "helitronscanner": "V1.1" + }, + "HELITRONSCANNER_SCAN_HEAD": { + "helitronscanner": "V1.1" + }, + "HELITRONSCANNER_SCAN_HEAD_RC": { + "helitronscanner": "V1.1" + }, + "HELITRONSCANNER_SCAN_TAIL": { + "helitronscanner": "V1.1" + }, + "HELITRONSCANNER_SCAN_TAIL_RC": { + "helitronscanner": "V1.1" + }, "LTRFINDER": { "LTR_FINDER_parallel": "v1.1", "ltr_finder": "v1.07" @@ -43,6 +61,6 @@ "nf-test": "0.9.0", "nextflow": "24.10.1" }, - "timestamp": "2024-11-25T13:53:22.07666" + "timestamp": "2024-11-26T22:38:20.031297" } } \ No newline at end of file diff --git a/test/nf-test/tiny/main.nf.test.snap b/test/nf-test/tiny/main.nf.test.snap index a61631f..946ed5d 100644 --- a/test/nf-test/tiny/main.nf.test.snap +++ b/test/nf-test/tiny/main.nf.test.snap @@ -2,7 +2,7 @@ "tiny genome": { "content": [ { - "successful tasks": 7, + "successful tasks": 13, "versions": { "ANNOSINE": { "annosine": "2.0.7" @@ -10,6 +10,24 @@ "CAT_CAT": { "pigz": "2.3.4" }, + "HELITRONSCANNER_DRAW": { + "helitronscanner": "V1.1" + }, + "HELITRONSCANNER_DRAW_RC": { + "helitronscanner": "V1.1" + }, + "HELITRONSCANNER_SCAN_HEAD": { + "helitronscanner": "V1.1" + }, + "HELITRONSCANNER_SCAN_HEAD_RC": { + "helitronscanner": "V1.1" + }, + "HELITRONSCANNER_SCAN_TAIL": { + "helitronscanner": "V1.1" + }, + "HELITRONSCANNER_SCAN_TAIL_RC": { + "helitronscanner": "V1.1" + }, "LTRFINDER": { "LTR_FINDER_parallel": "v1.1", "ltr_finder": "v1.07" @@ -37,6 +55,6 @@ "nf-test": "0.9.0", "nextflow": "24.10.1" }, - "timestamp": "2024-11-25T13:20:57.770583" + "timestamp": "2024-11-26T22:35:05.489004" } } \ No newline at end of file diff --git a/workflows/edta.nf b/workflows/edta.nf index 357b427..cf99f9f 100644 --- a/workflows/edta.nf +++ b/workflows/edta.nf @@ -128,6 +128,10 @@ workflow EDTA { // MODULE: FASTA_HELITRONSCANNER_SCAN_DRAW FASTA_HELITRONSCANNER_SCAN_DRAW ( ch_sanitized_fasta ) + ch_helitronscanner_draw = FASTA_HELITRONSCANNER_SCAN_DRAW.out.helitronscanner_draw + ch_helitronscanner_draw_rc = FASTA_HELITRONSCANNER_SCAN_DRAW.out.helitronscanner_draw_rc + ch_versions = ch_versions.mix(FASTA_HELITRONSCANNER_SCAN_DRAW.out.versions) + // Function: Save versions ch_versions = ch_versions From d2feab0390bfd416f381b9b98fdc907171ecc6f8 Mon Sep 17 00:00:00 2001 From: Usman Rashid Date: Thu, 28 Nov 2024 18:25:55 +1300 Subject: [PATCH 24/49] Added CLEANUP_TANDEM --- bin/cleanup_tandem.pl | 0 modules/local/cleanup_tandem/environment.yml | 8 +++ modules/local/cleanup_tandem/main.nf | 49 +++++++++++++ .../local/cleanup_tandem/tests/main.nf.test | 56 +++++++++++++++ .../cleanup_tandem/tests/main.nf.test.snap | 68 +++++++++++++++++++ 5 files changed, 181 insertions(+) mode change 100644 => 100755 bin/cleanup_tandem.pl create mode 100644 modules/local/cleanup_tandem/environment.yml create mode 100644 modules/local/cleanup_tandem/main.nf create mode 100644 modules/local/cleanup_tandem/tests/main.nf.test create mode 100644 modules/local/cleanup_tandem/tests/main.nf.test.snap diff --git a/bin/cleanup_tandem.pl b/bin/cleanup_tandem.pl old mode 100644 new mode 100755 diff --git a/modules/local/cleanup_tandem/environment.yml b/modules/local/cleanup_tandem/environment.yml new file mode 100644 index 0000000..8d38804 --- /dev/null +++ b/modules/local/cleanup_tandem/environment.yml @@ -0,0 +1,8 @@ +--- +# yaml-language-server: $schema=https://raw.githubusercontent.com/nf-core/modules/master/modules/environment-schema.json +channels: + - conda-forge + - bioconda +dependencies: + - "bioconda::trf=4.09.1" + - "conda-forge::perl=5.32.1" \ No newline at end of file diff --git a/modules/local/cleanup_tandem/main.nf b/modules/local/cleanup_tandem/main.nf new file mode 100644 index 0000000..ae84cd2 --- /dev/null +++ b/modules/local/cleanup_tandem/main.nf @@ -0,0 +1,49 @@ +process CLEANUP_TANDEM { + tag "$meta.id" + label 'process_single' + + conda "${moduleDir}/environment.yml" + container "${ workflow.containerEngine == 'singularity' && !task.ext.singularity_pull_docker_container ? + 'https://community-cr-prod.seqera.io/docker/registry/v2/blobs/sha256/64/64d26063bedc2efcba20750a408a21f50907986d0d9aee685b03d7d05d3fbd8b/data': + 'community.wave.seqera.io/library/trf_perl:44f9844b1bf765c3' }" + + input: + tuple val(meta), path(fasta) + + output: + tuple val(meta), path("*.fasta") , emit: cleaned_fasta + path "versions.yml" , emit: versions + + when: + task.ext.when == null || task.ext.when + + script: + def prefix = task.ext.prefix ?: "${meta.id}" + def args = task.ext.args ?: '' + if ( "$fasta" == "${prefix}.fasta" ) error "Input and output names are the same, use \"task.ext.prefix\" to disambiguate!" + """ + cleanup_tandem.pl \\ + -f $fasta \\ + $args \\ + > ${prefix}.fasta + + cat <<-END_VERSIONS > versions.yml + "${task.process}": + perl: \$(perl -v | sed -n 's|This is perl.*(\\(.*\\)).*|\\1|p') + trf: \$(trf -v |& sed -n 's|.*Version \\(.*\\)|\\1|p') + END_VERSIONS + """ + + stub: + def prefix = task.ext.prefix ?: "${meta.id}" + if ( "$fasta" == "${prefix}.fasta" ) error "Input and output names are the same, use \"task.ext.prefix\" to disambiguate!" + """ + touch ${prefix}.fasta + + cat <<-END_VERSIONS > versions.yml + "${task.process}": + perl: \$(perl -v | sed -n 's|This is perl.*(\\(.*\\)).*|\\1|p') + trf: \$(trf -v |& sed -n 's|.*Version \\(.*\\)|\\1|p') + END_VERSIONS + """ +} \ No newline at end of file diff --git a/modules/local/cleanup_tandem/tests/main.nf.test b/modules/local/cleanup_tandem/tests/main.nf.test new file mode 100644 index 0000000..b13870e --- /dev/null +++ b/modules/local/cleanup_tandem/tests/main.nf.test @@ -0,0 +1,56 @@ +nextflow_process { + + name "Test Process CLEANUP_TANDEM" + script "../main.nf" + process "CLEANUP_TANDEM" + + tag "cleanup_tandem" + tag "modules_local" + tag "modules" + + test("sarscov2 - genome") { + + when { + process { + """ + input[0] = [ + [ id: 'sarscov2' ], + file(params.modules_testdata_base_path + 'genomics/sarscov2/genome/genome.fasta', checkIfExists: true) + ] + """ + } + } + + then { + assertAll( + { assert process.success }, + { assert snapshot(process.out).match() } + ) + } + + } + + test("sarscov2 - genome - stub") { + options '-stub' + + when { + process { + """ + input[0] = [ + [ id: 'sarscov2' ], + file(params.modules_testdata_base_path + 'genomics/sarscov2/genome/genome.fasta', checkIfExists: true) + ] + """ + } + } + + then { + assertAll( + { assert process.success }, + { assert snapshot(process.out).match() } + ) + } + + } + +} diff --git a/modules/local/cleanup_tandem/tests/main.nf.test.snap b/modules/local/cleanup_tandem/tests/main.nf.test.snap new file mode 100644 index 0000000..a36d60e --- /dev/null +++ b/modules/local/cleanup_tandem/tests/main.nf.test.snap @@ -0,0 +1,68 @@ +{ + "sarscov2 - genome - stub": { + "content": [ + { + "0": [ + [ + { + "id": "sarscov2" + }, + "sarscov2.fasta:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "1": [ + "versions.yml:md5,2cce04ed211ff9c6f7f322d2b217af83" + ], + "cleaned_fasta": [ + [ + { + "id": "sarscov2" + }, + "sarscov2.fasta:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "versions": [ + "versions.yml:md5,2cce04ed211ff9c6f7f322d2b217af83" + ] + } + ], + "meta": { + "nf-test": "0.9.0", + "nextflow": "24.10.1" + }, + "timestamp": "2024-11-28T16:12:53.161124" + }, + "sarscov2 - genome": { + "content": [ + { + "0": [ + [ + { + "id": "sarscov2" + }, + "sarscov2.fasta:md5,e73599798195a519ba2565c3f0275b93" + ] + ], + "1": [ + "versions.yml:md5,2cce04ed211ff9c6f7f322d2b217af83" + ], + "cleaned_fasta": [ + [ + { + "id": "sarscov2" + }, + "sarscov2.fasta:md5,e73599798195a519ba2565c3f0275b93" + ] + ], + "versions": [ + "versions.yml:md5,2cce04ed211ff9c6f7f322d2b217af83" + ] + } + ], + "meta": { + "nf-test": "0.9.0", + "nextflow": "24.10.1" + }, + "timestamp": "2024-11-28T16:12:48.160979" + } +} \ No newline at end of file From 1d99e2a2e7f9d9c700b4e880a7c1de1c633b2e52 Mon Sep 17 00:00:00 2001 From: Joseph Guhlin Date: Thu, 21 Nov 2024 11:51:55 +1300 Subject: [PATCH 25/49] Initial try on format_helitronscanner --- .../format_helitronscanner/environment.yml | 7 +++ modules/local/format_helitronscanner/main.nf | 55 +++++++++++++++++++ 2 files changed, 62 insertions(+) create mode 100644 modules/local/format_helitronscanner/environment.yml create mode 100644 modules/local/format_helitronscanner/main.nf diff --git a/modules/local/format_helitronscanner/environment.yml b/modules/local/format_helitronscanner/environment.yml new file mode 100644 index 0000000..d2f633e --- /dev/null +++ b/modules/local/format_helitronscanner/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::perl-bioperl=1.7.8" diff --git a/modules/local/format_helitronscanner/main.nf b/modules/local/format_helitronscanner/main.nf new file mode 100644 index 0000000..9d41ab6 --- /dev/null +++ b/modules/local/format_helitronscanner/main.nf @@ -0,0 +1,55 @@ +process FORMAT_HELITRONSCANNER_OUT { + tag "$meta.id" + label 'process_single' + + conda "${moduleDir}/environment.yml" + container "${ workflow.containerEngine == 'singularity' && !task.ext.singularity_pull_docker_container ? + 'https://depot.galaxyproject.org/singularity/perl-bioperl:1.7.8--hdfd78af_1': + 'biocontainers/perl-bioperl:1.7.8--hdfd78af_1' }" + + input: + tuple val(meta), path(genome) + path hel_fa // HelitronScanner.draw.hel.fa file + path rc_hel_fa // HelitronScanner.draw.rc.hel.fa file + + output: + tuple val(meta), path("${meta.id}.HelitronScanner.filtered.tabout"), emit: filtered_tabout + tuple val(meta), path("${meta.id}.HelitronScanner.filtered.fa"), emit: filtered_fa, optional: true + tuple val(meta), path("${meta.id}.HelitronScanner.filtered.ext.fa"), emit: filtered_ext_fa, optional: true + tuple val(meta), path("${meta.id}.format_helitronscanner_out.log"), emit: log + path "versions.yml", emit: versions + + when: + task.ext.when == null || task.ext.when + + script: + // Set default values for the optional arguments + def sitefilter = task.ext.sitefilter != null ? task.ext.sitefilter : 1 + def minscore = task.ext.minscore != null ? task.ext.minscore : 12 + def keepshorter = task.ext.keepshorter != null ? task.ext.keepshorter : 1 + def extlen = task.ext.extlen != null ? task.ext.extlen : 30 + def extout = task.ext.extout != null ? task.ext.extout : 1 + def prefix = meta.id + + """ + # Create symbolic links to match expected filenames + ln -s $hel_fa ${genome}.HelitronScanner.draw.hel.fa + ln -s $rc_hel_fa ${genome}.HelitronScanner.draw.rc.hel.fa + + # Run the Perl script with the provided arguments + perl format_helitronscanner_out.pl \\ + -genome $genome \\ + -sitefilter $sitefilter \\ + -minscore $minscore \\ + -keepshorter $keepshorter \\ + -extlen $extlen \\ + -extout $extout \\ + &> >(tee "${prefix}.format_helitronscanner_out.log" 2>&1) + + # Capture version information + cat <<-END_VERSIONS > versions.yml + "${task.process}": + Perl: \$(perl -v | grep 'This is perl' | awk '{print \$4}') + END_VERSIONS + """ +} From 30c2fd6990c7164f09a1bd37dd3dac80486b80de Mon Sep 17 00:00:00 2001 From: Joseph Guhlin Date: Thu, 28 Nov 2024 11:36:13 +1300 Subject: [PATCH 26/49] Make changes suggested by @GallVp --- .../environment.yml | 0 .../main.nf | 7 +++---- 2 files changed, 3 insertions(+), 4 deletions(-) rename modules/local/{format_helitronscanner => format_helitronscanner_out}/environment.yml (100%) rename modules/local/{format_helitronscanner => format_helitronscanner_out}/main.nf (91%) diff --git a/modules/local/format_helitronscanner/environment.yml b/modules/local/format_helitronscanner_out/environment.yml similarity index 100% rename from modules/local/format_helitronscanner/environment.yml rename to modules/local/format_helitronscanner_out/environment.yml diff --git a/modules/local/format_helitronscanner/main.nf b/modules/local/format_helitronscanner_out/main.nf similarity index 91% rename from modules/local/format_helitronscanner/main.nf rename to modules/local/format_helitronscanner_out/main.nf index 9d41ab6..2364644 100644 --- a/modules/local/format_helitronscanner/main.nf +++ b/modules/local/format_helitronscanner_out/main.nf @@ -29,14 +29,14 @@ process FORMAT_HELITRONSCANNER_OUT { def keepshorter = task.ext.keepshorter != null ? task.ext.keepshorter : 1 def extlen = task.ext.extlen != null ? task.ext.extlen : 30 def extout = task.ext.extout != null ? task.ext.extout : 1 - def prefix = meta.id + def prefix = task.ext.prefix ?: "${meta.id}" + def args = task.ext.args ?: '' """ # Create symbolic links to match expected filenames ln -s $hel_fa ${genome}.HelitronScanner.draw.hel.fa ln -s $rc_hel_fa ${genome}.HelitronScanner.draw.rc.hel.fa - # Run the Perl script with the provided arguments perl format_helitronscanner_out.pl \\ -genome $genome \\ -sitefilter $sitefilter \\ @@ -46,10 +46,9 @@ process FORMAT_HELITRONSCANNER_OUT { -extout $extout \\ &> >(tee "${prefix}.format_helitronscanner_out.log" 2>&1) - # Capture version information cat <<-END_VERSIONS > versions.yml "${task.process}": - Perl: \$(perl -v | grep 'This is perl' | awk '{print \$4}') + perl: \$(perl -v | sed -n 's|This is perl.*(\\(.*\\)).*|\\1|p') END_VERSIONS """ } From 1f35229c5dcb07c0e6805d0bf4febff0978166c2 Mon Sep 17 00:00:00 2001 From: Joseph Guhlin Date: Thu, 28 Nov 2024 11:49:23 +1300 Subject: [PATCH 27/49] Added some tests - not yet working, need the nf-test.config from the existing PR --- .../tests/environment.yml | 7 ++ .../tests/main.nf.test | 90 +++++++++++++++++++ 2 files changed, 97 insertions(+) create mode 100644 modules/local/format_helitronscanner_out/tests/environment.yml create mode 100644 modules/local/format_helitronscanner_out/tests/main.nf.test diff --git a/modules/local/format_helitronscanner_out/tests/environment.yml b/modules/local/format_helitronscanner_out/tests/environment.yml new file mode 100644 index 0000000..d2f633e --- /dev/null +++ b/modules/local/format_helitronscanner_out/tests/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::perl-bioperl=1.7.8" diff --git a/modules/local/format_helitronscanner_out/tests/main.nf.test b/modules/local/format_helitronscanner_out/tests/main.nf.test new file mode 100644 index 0000000..d1808cd --- /dev/null +++ b/modules/local/format_helitronscanner_out/tests/main.nf.test @@ -0,0 +1,90 @@ +nextflow_process { + + name "Test Process FORMAT_HELITRONSCANNER_OUT" + script "../main.nf" + process "FORMAT_HELITRONSCANNER_OUT" + + tag "modules" + tag "modules_nfcore" + tag "format_helitronscanner_out" + + test("homo_sapiens - genome_21_fasta") { + + when { + process { + """ + input[0] = [ + [ id:'test' ], + 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.filtered_tabout, + process.out.log + process.out.versions + ).match() + }, + { assert path(process.out.filtered_tabout[0][1]).text.contains("46510803 46520182 9380 46510803 46510940 138 46520042 46520182 141 86.52 0 chr21") }, + ) + } + + } + + test("sarscov2 - genome_fasta - no helitrons") { + + when { + process { + """ + input[0] = [ + [ id:'test' ], + file(params.modules_testdata_base_path + 'genomics/sarscov2/genome/genome.fasta', checkIfExists: true) + ] + """ + } + } + + then { + assertAll( + { assert process.success }, + { assert snapshot( + process.out.gff3, + process.out.versions + ).match() + }, + { assert path(process.out.filtered_tabout[0][1]).text.contains("predictions are reported in the following way") }, + ) + } + + } + + test("homo_sapiens - genome_fasta - stub") { + + options '-stub' + + when { + process { + """ + input[0] = [ + [ id:'test' ], + file(params.modules_testdata_base_path + 'genomics/homo_sapiens/genome/genome.fasta', checkIfExists: true) + ] + """ + } + } + + then { + assertAll( + { assert process.success }, + { assert snapshot(process.out).match() } + ) + } + + } + +} From 1fb9dd2241c9411384376d82677a12d569db181c Mon Sep 17 00:00:00 2001 From: Joseph Guhlin Date: Thu, 28 Nov 2024 12:11:23 +1300 Subject: [PATCH 28/49] Typo --- modules/local/format_helitronscanner_out/tests/main.nf.test | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/local/format_helitronscanner_out/tests/main.nf.test b/modules/local/format_helitronscanner_out/tests/main.nf.test index d1808cd..5745153 100644 --- a/modules/local/format_helitronscanner_out/tests/main.nf.test +++ b/modules/local/format_helitronscanner_out/tests/main.nf.test @@ -26,7 +26,7 @@ nextflow_process { { assert process.success }, { assert snapshot( process.out.filtered_tabout, - process.out.log + process.out.log, process.out.versions ).match() }, From fde5ef376f0362b65319458f2f790171dae8a76a Mon Sep 17 00:00:00 2001 From: Usman Rashid Date: Tue, 3 Dec 2024 14:33:19 +1300 Subject: [PATCH 29/49] Incorporated FORMAT_HELITRONSCANNER_OUT --- bin/format_helitronscanner_out.pl | 0 conf/modules.config | 4 ++ .../environment.yml | 2 +- .../local/format_helitronscanner_out/main.nf | 49 +++++++------- .../tests/environment.yml | 7 -- .../tests/main.nf.test | 65 ++----------------- .../tests/main.nf.test.snap | 63 ++++++++++++++++++ test/nf-test/small/main.nf.test.snap | 9 ++- test/nf-test/tiny/main.nf.test.snap | 9 ++- workflows/edta.nf | 19 ++++++ 10 files changed, 130 insertions(+), 97 deletions(-) mode change 100644 => 100755 bin/format_helitronscanner_out.pl delete mode 100644 modules/local/format_helitronscanner_out/tests/environment.yml create mode 100644 modules/local/format_helitronscanner_out/tests/main.nf.test.snap diff --git a/bin/format_helitronscanner_out.pl b/bin/format_helitronscanner_out.pl old mode 100644 new mode 100755 diff --git a/conf/modules.config b/conf/modules.config index 39b1af8..918b569 100644 --- a/conf/modules.config +++ b/conf/modules.config @@ -38,5 +38,9 @@ process { ext.prefix = { "${meta.id}.rc" } ext.args = '-pure_helitron' } + + withName: 'EDTA:FORMAT_HELITRONSCANNER_OUT' { + ext.args = '-sitefilter 1 -minscore 12 -keepshorter 1 -extlen 30 -extout 1' + } } \ No newline at end of file diff --git a/modules/local/format_helitronscanner_out/environment.yml b/modules/local/format_helitronscanner_out/environment.yml index d2f633e..b6205eb 100644 --- a/modules/local/format_helitronscanner_out/environment.yml +++ b/modules/local/format_helitronscanner_out/environment.yml @@ -4,4 +4,4 @@ channels: - conda-forge - bioconda dependencies: - - "bioconda::perl-bioperl=1.7.8" + - "conda-forge::perl=5.32.1" diff --git a/modules/local/format_helitronscanner_out/main.nf b/modules/local/format_helitronscanner_out/main.nf index 2364644..ebfe998 100644 --- a/modules/local/format_helitronscanner_out/main.nf +++ b/modules/local/format_helitronscanner_out/main.nf @@ -4,46 +4,35 @@ process FORMAT_HELITRONSCANNER_OUT { conda "${moduleDir}/environment.yml" container "${ workflow.containerEngine == 'singularity' && !task.ext.singularity_pull_docker_container ? - 'https://depot.galaxyproject.org/singularity/perl-bioperl:1.7.8--hdfd78af_1': - 'biocontainers/perl-bioperl:1.7.8--hdfd78af_1' }" + 'https://community-cr-prod.seqera.io/docker/registry/v2/blobs/sha256/c0/c0905ae7aa2a1c4135b448b49dd205b4eb370d4733b832594563ee06832ebd09/data': + 'community.wave.seqera.io/library/perl:67db1a8d53d64b14' }" input: tuple val(meta), path(genome) - path hel_fa // HelitronScanner.draw.hel.fa file - path rc_hel_fa // HelitronScanner.draw.rc.hel.fa file + path hel_fa + path rc_hel_fa output: - tuple val(meta), path("${meta.id}.HelitronScanner.filtered.tabout"), emit: filtered_tabout - tuple val(meta), path("${meta.id}.HelitronScanner.filtered.fa"), emit: filtered_fa, optional: true - tuple val(meta), path("${meta.id}.HelitronScanner.filtered.ext.fa"), emit: filtered_ext_fa, optional: true - tuple val(meta), path("${meta.id}.format_helitronscanner_out.log"), emit: log - path "versions.yml", emit: versions + tuple val(meta), path("*.HelitronScanner.filtered.tabout") , emit: filtered_tabout + tuple val(meta), path("*.HelitronScanner.filtered.fa") , emit: filtered_fa , optional: true + tuple val(meta), path("*.HelitronScanner.filtered.ext.fa") , emit: filtered_ext_fa , optional: true + tuple val(meta), path("*.format_helitronscanner_out.log") , emit: log + path "versions.yml" , emit: versions when: task.ext.when == null || task.ext.when script: - // Set default values for the optional arguments - def sitefilter = task.ext.sitefilter != null ? task.ext.sitefilter : 1 - def minscore = task.ext.minscore != null ? task.ext.minscore : 12 - def keepshorter = task.ext.keepshorter != null ? task.ext.keepshorter : 1 - def extlen = task.ext.extlen != null ? task.ext.extlen : 30 - def extout = task.ext.extout != null ? task.ext.extout : 1 - def prefix = task.ext.prefix ?: "${meta.id}" - def args = task.ext.args ?: '' - + def prefix = task.ext.prefix ?: "$genome" + def args = task.ext.args ?: '' """ # Create symbolic links to match expected filenames ln -s $hel_fa ${genome}.HelitronScanner.draw.hel.fa ln -s $rc_hel_fa ${genome}.HelitronScanner.draw.rc.hel.fa - perl format_helitronscanner_out.pl \\ + format_helitronscanner_out.pl \\ -genome $genome \\ - -sitefilter $sitefilter \\ - -minscore $minscore \\ - -keepshorter $keepshorter \\ - -extlen $extlen \\ - -extout $extout \\ + $args \\ &> >(tee "${prefix}.format_helitronscanner_out.log" 2>&1) cat <<-END_VERSIONS > versions.yml @@ -51,4 +40,16 @@ process FORMAT_HELITRONSCANNER_OUT { perl: \$(perl -v | sed -n 's|This is perl.*(\\(.*\\)).*|\\1|p') END_VERSIONS """ + + stub: + def prefix = task.ext.prefix ?: "$genome" + """ + touch ${prefix}.HelitronScanner.filtered.tabout + touch ${prefix}.format_helitronscanner_out.log + + cat <<-END_VERSIONS > versions.yml + "${task.process}": + perl: \$(perl -v | sed -n 's|This is perl.*(\\(.*\\)).*|\\1|p') + END_VERSIONS + """ } diff --git a/modules/local/format_helitronscanner_out/tests/environment.yml b/modules/local/format_helitronscanner_out/tests/environment.yml deleted file mode 100644 index d2f633e..0000000 --- a/modules/local/format_helitronscanner_out/tests/environment.yml +++ /dev/null @@ -1,7 +0,0 @@ ---- -# yaml-language-server: $schema=https://raw.githubusercontent.com/nf-core/modules/master/modules/environment-schema.json -channels: - - conda-forge - - bioconda -dependencies: - - "bioconda::perl-bioperl=1.7.8" diff --git a/modules/local/format_helitronscanner_out/tests/main.nf.test b/modules/local/format_helitronscanner_out/tests/main.nf.test index 5745153..864bc15 100644 --- a/modules/local/format_helitronscanner_out/tests/main.nf.test +++ b/modules/local/format_helitronscanner_out/tests/main.nf.test @@ -5,65 +5,10 @@ nextflow_process { process "FORMAT_HELITRONSCANNER_OUT" tag "modules" - tag "modules_nfcore" + tag "modules_local" tag "format_helitronscanner_out" - test("homo_sapiens - genome_21_fasta") { - - when { - process { - """ - input[0] = [ - [ id:'test' ], - 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.filtered_tabout, - process.out.log, - process.out.versions - ).match() - }, - { assert path(process.out.filtered_tabout[0][1]).text.contains("46510803 46520182 9380 46510803 46510940 138 46520042 46520182 141 86.52 0 chr21") }, - ) - } - - } - - test("sarscov2 - genome_fasta - no helitrons") { - - when { - process { - """ - input[0] = [ - [ id:'test' ], - file(params.modules_testdata_base_path + 'genomics/sarscov2/genome/genome.fasta', checkIfExists: true) - ] - """ - } - } - - then { - assertAll( - { assert process.success }, - { assert snapshot( - process.out.gff3, - process.out.versions - ).match() - }, - { assert path(process.out.filtered_tabout[0][1]).text.contains("predictions are reported in the following way") }, - ) - } - - } - - test("homo_sapiens - genome_fasta - stub") { + test("sarscov2 - genome_fasta - stub") { options '-stub' @@ -71,9 +16,11 @@ nextflow_process { process { """ input[0] = [ - [ id:'test' ], - file(params.modules_testdata_base_path + 'genomics/homo_sapiens/genome/genome.fasta', checkIfExists: true) + [ id:'sarscov2' ], + file(params.modules_testdata_base_path + 'genomics/sarscov2/genome/genome.fasta', checkIfExists: true) ] + input[1] = [] + input[2] = [] """ } } diff --git a/modules/local/format_helitronscanner_out/tests/main.nf.test.snap b/modules/local/format_helitronscanner_out/tests/main.nf.test.snap new file mode 100644 index 0000000..9079015 --- /dev/null +++ b/modules/local/format_helitronscanner_out/tests/main.nf.test.snap @@ -0,0 +1,63 @@ +{ + "sarscov2 - genome_fasta - stub": { + "content": [ + { + "0": [ + [ + { + "id": "sarscov2" + }, + "genome.fasta.HelitronScanner.filtered.tabout:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "1": [ + + ], + "2": [ + + ], + "3": [ + [ + { + "id": "sarscov2" + }, + "genome.fasta.format_helitronscanner_out.log:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "4": [ + "versions.yml:md5,250d1a5d5f60868f6cf0f17bd52edd91" + ], + "filtered_ext_fa": [ + + ], + "filtered_fa": [ + + ], + "filtered_tabout": [ + [ + { + "id": "sarscov2" + }, + "genome.fasta.HelitronScanner.filtered.tabout:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "log": [ + [ + { + "id": "sarscov2" + }, + "genome.fasta.format_helitronscanner_out.log:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "versions": [ + "versions.yml:md5,250d1a5d5f60868f6cf0f17bd52edd91" + ] + } + ], + "meta": { + "nf-test": "0.9.0", + "nextflow": "24.04.4" + }, + "timestamp": "2024-12-03T14:26:36.313536" + } +} \ No newline at end of file diff --git a/test/nf-test/small/main.nf.test.snap b/test/nf-test/small/main.nf.test.snap index 9154059..5a164b2 100644 --- a/test/nf-test/small/main.nf.test.snap +++ b/test/nf-test/small/main.nf.test.snap @@ -2,7 +2,7 @@ "small genome": { "content": [ { - "successful tasks": 15, + "successful tasks": 16, "versions": { "ANNOSINE": { "annosine": "2.0.7" @@ -10,6 +10,9 @@ "CAT_CAT": { "pigz": "2.3.4" }, + "FORMAT_HELITRONSCANNER_OUT": { + "perl": "v5.32.1" + }, "HELITRONSCANNER_DRAW": { "helitronscanner": "V1.1" }, @@ -59,8 +62,8 @@ ], "meta": { "nf-test": "0.9.0", - "nextflow": "24.10.1" + "nextflow": "24.04.4" }, - "timestamp": "2024-11-26T22:38:20.031297" + "timestamp": "2024-12-03T14:20:03.748125" } } \ No newline at end of file diff --git a/test/nf-test/tiny/main.nf.test.snap b/test/nf-test/tiny/main.nf.test.snap index 946ed5d..20a57ef 100644 --- a/test/nf-test/tiny/main.nf.test.snap +++ b/test/nf-test/tiny/main.nf.test.snap @@ -2,7 +2,7 @@ "tiny genome": { "content": [ { - "successful tasks": 13, + "successful tasks": 14, "versions": { "ANNOSINE": { "annosine": "2.0.7" @@ -10,6 +10,9 @@ "CAT_CAT": { "pigz": "2.3.4" }, + "FORMAT_HELITRONSCANNER_OUT": { + "perl": "v5.32.1" + }, "HELITRONSCANNER_DRAW": { "helitronscanner": "V1.1" }, @@ -53,8 +56,8 @@ ], "meta": { "nf-test": "0.9.0", - "nextflow": "24.10.1" + "nextflow": "24.04.4" }, - "timestamp": "2024-11-26T22:35:05.489004" + "timestamp": "2024-12-03T14:16:25.986375" } } \ No newline at end of file diff --git a/workflows/edta.nf b/workflows/edta.nf index cf99f9f..05a6da3 100644 --- a/workflows/edta.nf +++ b/workflows/edta.nf @@ -8,6 +8,7 @@ include { ANNOSINE } from '../modules/gallvp/annosine/m include { REPEATMODELER_BUILDDATABASE } from '../modules/nf-core/repeatmodeler/builddatabase/main.nf' include { REPEATMODELER_REPEATMODELER } from '../modules/nf-core/repeatmodeler/repeatmodeler/main.nf' include { FASTA_HELITRONSCANNER_SCAN_DRAW } from '../subworkflows/gallvp/fasta_helitronscanner_scan_draw/main.nf' +include { FORMAT_HELITRONSCANNER_OUT } from '../modules/local/format_helitronscanner_out/main.nf' include { softwareVersionsToYAML } from '../modules/local/utils/main.nf' @@ -132,6 +133,24 @@ workflow EDTA { ch_helitronscanner_draw_rc = FASTA_HELITRONSCANNER_SCAN_DRAW.out.helitronscanner_draw_rc ch_versions = ch_versions.mix(FASTA_HELITRONSCANNER_SCAN_DRAW.out.versions) + // MODULE: FORMAT_HELITRONSCANNER_OUT + ch_format_helitronscanner_inputs = ch_sanitized_fasta + | join(ch_helitronscanner_draw) + | join(ch_helitronscanner_draw_rc) + | multiMap { meta, fasta, draw, draw_rc -> + genome: [ meta, fasta ] + hel_fa: draw + rc_hel_fa: draw_rc + } + + FORMAT_HELITRONSCANNER_OUT ( + ch_format_helitronscanner_inputs.genome, + ch_format_helitronscanner_inputs.hel_fa, + ch_format_helitronscanner_inputs.rc_hel_fa, + ) + + ch_versions = ch_versions.mix(FORMAT_HELITRONSCANNER_OUT.out.versions.first()) + // Function: Save versions ch_versions = ch_versions From 41683a77682b23bc8c101147e83131c2ef69d487 Mon Sep 17 00:00:00 2001 From: Usman Rashid Date: Thu, 5 Dec 2024 13:21:56 +1300 Subject: [PATCH 30/49] Renamed test to tests to avoid meta.id conflicts in nf-test and added LTR_RETRIEVER_POSTPROCESS --- .github/check_binaries.sh | 12 + .github/workflows/linting.yml | 20 + .pre-commit-config.yaml | 11 + bin/EDTA_raw.pl | 761 ++++++++++++++++++ bin/setup_LTR_retriever_postprocess.sh | 26 + conf/modules.config | 10 + conf/test.config | 2 +- .../ltr_retriever_postprocess/environment.yml | 10 + .../local/ltr_retriever_postprocess/main.nf | 80 ++ .../nf-core/ltrretriever/ltrretriever/main.nf | 96 +++ .../ltrretriever/tests/main.nf.test.snap | 160 ++++ nextflow.config | 4 + nf-test.config | 2 +- {test => tests}/Alyrata.test.fa | 0 {test => tests}/Col.test.fa | 0 {test => tests}/Ler.test.fa | 0 {test => tests}/README.txt | 0 {test => tests}/genome.cds.fa | 0 {test => tests}/genome.cds.list | 0 {test => tests}/genome.exclude.bed | 0 {test => tests}/genome.fa | 0 {test => tests}/nf-test/nextflow.config | 0 {test => tests}/nf-test/small/main.nf.test | 26 +- .../nf-test/small/main.nf.test.snap | 21 +- {test => tests}/nf-test/tiny/main.nf.test | 0 .../nf-test/tiny/main.nf.test.snap | 0 workflows/edta.nf | 44 +- 27 files changed, 1271 insertions(+), 14 deletions(-) create mode 100755 .github/check_binaries.sh create mode 100644 .github/workflows/linting.yml create mode 100644 .pre-commit-config.yaml create mode 100755 bin/EDTA_raw.pl create mode 100755 bin/setup_LTR_retriever_postprocess.sh create mode 100644 modules/local/ltr_retriever_postprocess/environment.yml create mode 100644 modules/local/ltr_retriever_postprocess/main.nf create mode 100644 modules/nf-core/ltrretriever/ltrretriever/main.nf create mode 100644 modules/nf-core/ltrretriever/ltrretriever/tests/main.nf.test.snap rename {test => tests}/Alyrata.test.fa (100%) rename {test => tests}/Col.test.fa (100%) rename {test => tests}/Ler.test.fa (100%) rename {test => tests}/README.txt (100%) rename {test => tests}/genome.cds.fa (100%) rename {test => tests}/genome.cds.list (100%) rename {test => tests}/genome.exclude.bed (100%) rename {test => tests}/genome.fa (100%) rename {test => tests}/nf-test/nextflow.config (100%) rename {test => tests}/nf-test/small/main.nf.test (50%) rename {test => tests}/nf-test/small/main.nf.test.snap (72%) rename {test => tests}/nf-test/tiny/main.nf.test (100%) rename {test => tests}/nf-test/tiny/main.nf.test.snap (100%) diff --git a/.github/check_binaries.sh b/.github/check_binaries.sh new file mode 100755 index 0000000..79e8784 --- /dev/null +++ b/.github/check_binaries.sh @@ -0,0 +1,12 @@ +#!/usr/bin/env bash + +set -euo pipefail + +EDTA_raw_pl_version=$(md5sum EDTA_raw.pl | cut -f1 -d' ') +bin_EDTA_raw_pl_version=$(md5sum bin/EDTA_raw.pl | cut -f1 -d' ') + +if [[ $EDTA_raw_pl_version != $bin_EDTA_raw_pl_version ]]; then + echo 'EDTA_raw.pl != bin/EDTA_raw.pl' + echo 'Please synchronize bin/EDTA_raw.pl with EDTA_raw.pl' + exit 1 +fi diff --git a/.github/workflows/linting.yml b/.github/workflows/linting.yml new file mode 100644 index 0000000..03b6e41 --- /dev/null +++ b/.github/workflows/linting.yml @@ -0,0 +1,20 @@ +name: linting +on: + pull_request: + +jobs: + pre-commit: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4 + + - name: Set up Python 3.12 + uses: actions/setup-python@82c7e631bb3cdc910f68e0081d67478d79c6982d # v5 + with: + python-version: "3.12" + + - name: Install pre-commit + run: pip install pre-commit + + - name: Run pre-commit + run: pre-commit run --all-files diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 0000000..650f6a9 --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,11 @@ +repos: + - repo: local + hooks: + - id: check_binaries + name: Check binaries + language: system + entry: > + .github/check_binaries.sh + always_run: true + fail_fast: true + pass_filenames: false diff --git a/bin/EDTA_raw.pl b/bin/EDTA_raw.pl new file mode 100755 index 0000000..31c3cf0 --- /dev/null +++ b/bin/EDTA_raw.pl @@ -0,0 +1,761 @@ +#!/usr/bin/env perl +use warnings; +use strict; +use FindBin; +use File::Basename; +use File::Spec; # for obtaining the real path of a file +use Pod::Usage; + +######################################################## +##### Perform initial searches for TE candidates #### +##### Shujun Ou (shujun.ou.1@gmail.com, 07/16/2020) #### +######################################################## + +## Input: +# $genome + +## Output: +# $genome.LTR.raw.fa, $genome.LTR.intact.raw.fa, $genome.LTR.intact.raw.gff3 +# $genome.TIR.intact.fa, $genome.TIR.intact.raw.gff3 +# $genome.Helitron.intact.raw.fa, $genome.Helitron.intact.raw.gff3 +# $genome.LINE.raw.fa, $genome.SINE.raw.fa + +my $usage = "\nObtain raw TE libraries using various structure-based programs + +perl EDTA_raw.pl [options] + --genome [File] The genome FASTA + --species [rice|maize|others] Specify the species for identification + of TIR candidates. Default: others + --type [ltr|tir|helitron|line|sine|all] + Specify which type of raw TE candidates + you want to get. Default: all + --rmlib [FASTA] The RepeatModeler library, classified output. + --overwrite [0|1] If previous results are found, decide to + overwrite (1, rerun) or not (0, default). + --convert_seq_name [0|1] Convert long sequence name to <= 15 + characters and remove annotations (1, + default) or use the original (0) + --u [float] Neutral mutation rate to calculate the age of intact LTR elements. + Intact LTR age is found in this file: *EDTA_raw/LTR/*.pass.list. + Default: 1.3e-8 (per bp per year, from rice). + --genometools [path] Path to the GenomeTools program. (default: find from ENV) + --annosine [path] Path to the AnnoSINE program. (default: find from EDTA/bin) + --ltrretriever [path] Path to the LTR_retriever program. (default: find from ENV) + --blastplus [path] Path to the BLAST+ program. (default: find from ENV) + --tesorter [path] Path to the TEsorter program. (default: find from ENV) + --GRF [path] Path to the GRF program. (default: find from ENV) + --trf_path [path] Path to the TRF program. (default: find from ENV) + --mdust [path] Path to the mdust program. (default: find from ENV) + --repeatmasker [path] Path to the RepeatMasker program. (default: find from ENV) + --repeatmodeler [path] Path to the RepeatModeler2 program. (default: find from ENV) + --threads|-t [int] Number of theads to run this script. Default: 4 + --help|-h Display this help info +\n"; + +# pre-defined +my $genome = ''; +my $species = 'others'; +my $type = 'all'; +my $RMlib = 'null'; +my $overwrite = 0; #0, no rerun. 1, rerun even old results exist. +my $convert_name = 1; #0, use original seq names; 1 shorten names. +my $maxint = 5000; #maximum interval length (bp) between TIRs (for GRF in TIR-Learner) +my $miu = 1.3e-8; #mutation rate, per bp per year, from rice +my $threads = 4; +my $script_path = $FindBin::Bin; +my $cleanup_misclas = "$script_path/bin/cleanup_misclas.pl"; +my $get_range = "$script_path/bin/get_range.pl"; +my $rename_LTR = "$script_path/bin/rename_LTR_skim.pl"; +my $rename_RM = "$script_path/bin/rename_RM_TE.pl"; +my $filter_gff = "$script_path/bin/filter_gff3.pl"; +my $rename_tirlearner = "$script_path/bin/rename_tirlearner.pl"; +my $call_seq = "$script_path/bin/call_seq_by_list.pl"; +my $output_by_list = "$script_path/bin/output_by_list.pl"; +my $cleanup_tandem = "$script_path/bin/cleanup_tandem.pl"; +my $get_ext_seq = "$script_path/bin/get_ext_seq.pl"; +my $format_helitronscanner = "$script_path/bin/format_helitronscanner_out.pl"; +my $flank_filter = "$script_path/bin/flanking_filter.pl"; +my $make_bed = "$script_path/bin/make_bed_with_intact.pl"; +my $bed2gff = "$script_path/bin/bed2gff.pl"; +my $genometools = ''; #path to the genometools program +my $repeatmasker = ''; #path to the RepeatMasker program +my $repeatmodeler = ''; #path to the RepeatModeler program +my $LTR_retriever = ''; #path to the LTR_retriever program +my $TEsorter = ''; #path to the TEsorter program +my $blastplus = ''; #path to the blastn program +my $mdust = ''; #path to mdust +my $trf = ''; #path to trf +my $GRF = ''; #path to GRF +my $annosine = ""; #path to the AnnoSINE program + +# my $TIR_Learner = "$script_path/bin/TIR-Learner3/"; #tianyulu +# my $LTR_FINDER = "$script_path/bin/LTR_FINDER_parallel/LTR_FINDER_parallel"; #tianyulu +# my $LTR_HARVEST = "$script_path/bin/LTR_HARVEST_parallel/LTR_HARVEST_parallel"; #tianyulu +my $HelitronScanner_Runner = "$script_path/bin/run_helitron_scanner.sh"; + +my $TIR_Learner = ""; #path to TIR-Learner3 program #tianyulu +my $LTR_FINDER = ""; #path to LTR_FINDER_parallel program #tianyulu +my $LTR_HARVEST = ""; #path to LTR_HARVEST_parallel program #tianyulu +my $HelitronScanner = ""; #path to HelitronScanner program #tianyulu + +my $help = undef; + +# read parameters +my $k=0; +foreach (@ARGV){ + $genome = $ARGV[$k+1] if /^--genome$/i and $ARGV[$k+1] !~ /^-/; + $species = $ARGV[$k+1] if /^--species$/i and $ARGV[$k+1] !~ /^-/; + $type = lc $ARGV[$k+1] if /^--type$/i and $ARGV[$k+1] !~ /^-/; + $RMlib = $ARGV[$k+1] if /^--rmlib$/i and $ARGV[$k+1] !~ /^-/; + $overwrite = $ARGV[$k+1] if /^--overwrite$/i and $ARGV[$k+1] !~ /^-/; + $convert_name = $ARGV[$k+1] if /^--convert_seq_name$/i and $ARGV[$k+1] !~ /^-/; + $miu = $ARGV[$k+1] if /^--u$/i and $ARGV[$k+1] !~ /^-/; + $genometools = $ARGV[$k+1] if /^--genometools/i and $ARGV[$k+1] !~ /^-/; + $repeatmasker = $ARGV[$k+1] if /^--repeatmasker$/i and $ARGV[$k+1] !~ /^-/; + $repeatmodeler = $ARGV[$k+1] if /^--repeatmodeler$/i and $ARGV[$k+1] !~ /^-/; + $annosine = $ARGV[$k+1] if /^--annosine$/i and $ARGV[$k+1] !~ /^-/; + $LTR_retriever = $ARGV[$k+1] if /^--ltrretriever/i and $ARGV[$k+1] !~ /^-/; + $TEsorter = $ARGV[$k+1] if /^--tesorter$/i and $ARGV[$k+1] !~ /^-/; + $blastplus = $ARGV[$k+1] if /^--blastplus$/i and $ARGV[$k+1] !~ /^-/; + $mdust = $ARGV[$k+1] if /^--mdust$/i and $ARGV[$k+1] !~ /^-/; + $trf = $ARGV[$k+1] if /^--trf_path$/i and $ARGV[$k+1] !~ /^-/; + $GRF = $ARGV[$k+1] if /^--GRF$/i and $ARGV[$k+1] !~ /^-/; + $threads = $ARGV[$k+1] if /^--threads$|^-t$/i and $ARGV[$k+1] !~ /^-/; + $help = 1 if /^--help$|^-h$/i; + $k++; + } + +# check files and parameters +if ($help){ + pod2usage( { + -verbose => 0, + -exitval => 0, + -message => "$usage\n" } ); + } + +if (!-s $genome){ + pod2usage( { + -message => "At least 1 parameter is required:\n1) Input fasta file: --genome\n". + "\n$usage\n\n", + -verbose => 0, + -exitval => 2 } ); + } + +if ($species){ + $species =~ s/rice/Rice/i; + $species =~ s/maize/Maize/i; + $species =~ s/others/others/i; + die "The expected value for the species parameter is Rice or Maize or others!\n" unless $species eq "Rice" or $species eq "Maize" or $species eq "others"; + } + +die "The expected value for the type parameter is ltr or tir or helitron or all!\n" unless $type eq "ltr" or $type eq "line" or $type eq "tir" or $type eq "helitron" or $type eq "sine" or $type eq "all"; + +# check bolean +if ($overwrite != 0 and $overwrite != 1){ die "The expected value for the overwrite parameter is 0 or 1!\n"}; +if ($convert_name != 0 and $convert_name != 1){ die "The expected value for the convert_seq_name parameter is 0 or 1!\n"}; +if ($threads !~ /^[0-9]+$/){ die "The expected value for the threads parameter is an integer!\n"}; +if ($miu !~ /[0-9\.e\-]+/){ die "The expected value for the u parameter is float value without units!\n"} + +chomp (my $date = `date`); +print STDERR "$date\tEDTA_raw: Check dependencies, prepare working directories.\n\n"; + +# check files and dependencies +# die "The LTR_FINDER_parallel is not found in $LTR_FINDER!\n" unless -s $LTR_FINDER; #tianyulu +# die "The LTR_HARVEST_parallel is not found in $LTR_HARVEST!\n" unless -s $LTR_HARVEST; #tianyulu +# die "The TIR_Learner is not found in $TIR_Learner!\n" unless -s "$TIR_Learner/bin/main.py"; #tianyulu +die "The script get_range.pl is not found in $get_range!\n" unless -s $get_range; +die "The script rename_LTR_skim.pl is not found in $rename_LTR!\n" unless -s $rename_LTR; +die "The script filter_gff3.pl is not found in $filter_gff!\n" unless -s $filter_gff; +die "The script call_seq_by_list.pl is not found in $call_seq!\n" unless -s $call_seq; +die "The script output_by_list.pl is not found in $output_by_list!\n" unless -s $output_by_list; +die "The script rename_tirlearner.pl is not found in $rename_tirlearner!\n" unless -s $rename_tirlearner; +die "The script cleanup_tandem.pl is not found in $cleanup_tandem!\n" unless -s $cleanup_tandem; +die "The script get_ext_seq.pl is not found in $get_ext_seq!\n" unless -s $get_ext_seq; +# die "The HelitronScanner is not found in $HelitronScanner!\n" unless -s $HelitronScanner; #tianyulu +die "The script format_helitronscanner_out.pl is not found in $format_helitronscanner!\n" unless -s $format_helitronscanner; +die "The script flanking_filter.pl is not found in $flank_filter!\n" unless -s $flank_filter; +die "The script bed2gff.pl is not found in $bed2gff!\n" unless -s $bed2gff; +die "The script make_bed_with_intact.pl is not found in $make_bed!\n" unless -s $make_bed; + +# GenomeTools +chomp ($genometools=`command -v gt 2>/dev/null`) if $genometools eq ''; +$genometools =~ s/\s+$//; +$genometools = dirname($genometools) unless -d $genometools; +$genometools="$genometools/" if $genometools ne '' and $genometools !~ /\/$/; +die "Error: gt is not found in the genometools path $genometools!\n" unless -X "${genometools}gt"; +# RepeatMasker +my $rand=int(rand(1000000)); +chomp ($repeatmasker=`command -v RepeatMasker 2>/dev/null`) if $repeatmasker eq ''; +$repeatmasker =~ s/\s+$//; +$repeatmasker = dirname($repeatmasker) unless -d $repeatmasker; +$repeatmasker="$repeatmasker/" if $repeatmasker ne '' and $repeatmasker !~ /\/$/; +die "Error: RepeatMasker is not found in the RepeatMasker path $repeatmasker!\n" unless -X "${repeatmasker}RepeatMasker"; +# `cp $script_path/database/dummy060817.fa ./dummy060817.fa.$rand`; +`cp \"$script_path/database/dummy060817.fa\" ./dummy060817.fa.$rand`; #tianyulu +my $RM_test=`${repeatmasker}RepeatMasker -e ncbi -q -pa 1 -no_is -nolow dummy060817.fa.$rand -lib dummy060817.fa.$rand 2>/dev/null`; +die "Error: The RMblast engine is not installed in RepeatMasker!\n" unless $RM_test=~s/done//gi; +`rm dummy060817.fa.$rand*`; +# RepeatModeler +chomp ($repeatmodeler=`command -v RepeatModeler 2>/dev/null`) if $repeatmodeler eq ''; +$repeatmodeler =~ s/\s+$//; +$repeatmodeler = dirname($repeatmodeler) unless -d $repeatmodeler; +$repeatmodeler="$repeatmodeler/" if $repeatmodeler ne '' and $repeatmodeler !~ /\/$/; +die "Error: RepeatModeler is not found in the RepeatModeler path $repeatmodeler!\n" unless -X "${repeatmodeler}RepeatModeler"; +# AnnoSINE +chomp ($annosine=`command -v AnnoSINE_v2 2>/dev/null`) if $annosine eq ''; +$annosine =~ s/\s+$//; +$annosine = dirname($annosine) unless -d $annosine; +$annosine="$annosine/" if $annosine ne '' and $annosine !~ /\/$/; +die "Error: AnnoSINE is not found in the AnnoSINE path $annosine!\n" unless (-X "${annosine}AnnoSINE_v2"); +# LTR_retriever +chomp ($LTR_retriever=`command -v LTR_retriever 2>/dev/null`) if $LTR_retriever eq ''; +$LTR_retriever =~ s/\s+$//; +$LTR_retriever = dirname($LTR_retriever) unless -d $LTR_retriever; +$LTR_retriever="$LTR_retriever/" if $LTR_retriever ne '' and $LTR_retriever !~ /\/$/; +die "Error: LTR_retriever is not found in the LTR_retriever path $LTR_retriever!\n" unless -X "${LTR_retriever}LTR_retriever"; +# TEsorter +chomp ($TEsorter=`command -v TEsorter 2>/dev/null`) if $TEsorter eq ''; +$TEsorter =~ s/\s+$//; +$TEsorter = dirname($TEsorter) unless -d $TEsorter; +$TEsorter="$TEsorter/" if $TEsorter ne '' and $TEsorter !~ /\/$/; +die "Error: TEsorter is not found in the TEsorter path $TEsorter!\n" unless -X "${TEsorter}TEsorter"; +# makeblastdb, blastn +chomp ($blastplus=`command -v makeblastdb 2>/dev/null`) if $blastplus eq ''; +$blastplus =~ s/\s+$//; +$blastplus = dirname($blastplus) unless -d $blastplus; +$blastplus="$blastplus/" if $blastplus ne '' and $blastplus !~ /\/$/; +die "Error: makeblastdb is not found in the BLAST+ path $blastplus!\n" unless -X "${blastplus}makeblastdb"; +die "Error: blastn is not found in the BLAST+ path $blastplus!\n" unless -X "${blastplus}blastn"; +# mdust +chomp ($mdust=`command -v mdust 2>/dev/null`) if $mdust eq ''; +$mdust =~ s/\s+$//; +$mdust = dirname($mdust) unless -d $mdust; +$mdust = "$mdust/" if $mdust ne '' and $mdust !~ /\/$/; +die "Error: mdust is not found in the mdust path $mdust!\n" unless -X "${mdust}mdust"; +# trf +chomp ($trf=`command -v trf 2>/dev/null`) if $trf eq ''; +$trf=~s/\n$//; +`$trf 2>/dev/null`; +die "Error: Tandem Repeat Finder is not found in the TRF path $trf!\n" if $?==32256; +# GRF +chomp ($GRF = `command -v grf-main 2>/dev/null`) if $GRF eq ''; +$GRF =~ s/\n$//; +my $grfp= dirname ($GRF); +$grfp =~ s/\n$//; +`${grfp}grf-main 2>/dev/null`; +die "Error: The Generic Repeat Finder (GRF) is not found in the GRF path: $grfp\n" if $?==32256; + +# # TIR-Learner3 +# chomp ($TIR_Learner = `which TIR-Learner 2>/dev/null`) if $TIR_Learner eq ''; +# $TIR_Learner =~ s/\n$//; +# my $tirp= dirname ($TIR_Learner); +# $tirp =~ s/\n$//; +# `${tirp}TIR-Learner 2>/dev/null`; +# die "Error: TIR-Learner3 is not found in the TIR-Learner path: $tirp\n" if $?==32256; + +# TIR-Learner #tianyuLu +# Remove any trailing whitespace +$TIR_Learner =~ s/\s+$//; +if ($TIR_Learner eq "") { + # Find TIR-Learner path and remove any trailing newline + chomp ($TIR_Learner=`command -v TIR-Learner 2>/dev/null`); + die "Error: TIR-Learner not installed!\n" if $TIR_Learner eq ""; + # $TIR_Learner =~ s/\s+$//; +} else { + # # Extract directory name from path if path is not a directory + # $TIR_Learner = dirname($TIR_Learner) unless -d $TIR_Learner; + # If path is directory + if (-d $TIR_Learner) { + # # Add trailing slash if path not empty string and not already end with slash + # $TIR_Learner .= "/" if $TIR_Learner ne "" and $TIR_Learner !~ /\/$/; + # Add trailing slash if path not already end with slash + $TIR_Learner .= "/" if $TIR_Learner !~ /\/$/; + $TIR_Learner = "python3 $TIR_Learner/TIR-Learner.py"; + } +} +`$TIR_Learner 2>/dev/null`; +die "Error: TIR-Learner is not found in the path $TIR_Learner!\n" if $?==32256 || $?==2; + +# LTR_FINDER_parallel #tianyuLu +$LTR_FINDER =~ s/\s+$//; +if ($LTR_FINDER eq "") { + chomp ($LTR_FINDER=`command -v LTR_FINDER_parallel 2>/dev/null`); + die "Error: LTR_FINDER_parallel not installed!\n" if $LTR_FINDER eq ""; +} else { + if (-d $LTR_FINDER) { + $LTR_FINDER .= "/" if $LTR_FINDER !~ /\/$/; + $LTR_FINDER = "perl $LTR_FINDER/LTR_FINDER_parallel"; + } +} +`$LTR_FINDER 2>/dev/null`; +die "Error: LTR_FINDER_parallel is not found in the path $LTR_FINDER!\n" if $?==32256 || $?==2; + +# LTR_HARVEST_parallel #tianyuLu +$LTR_HARVEST =~ s/\s+$//; +if ($LTR_HARVEST eq "") { + chomp ($LTR_HARVEST=`command -v LTR_HARVEST_parallel 2>/dev/null`); + die "Error: LTR_HARVEST_parallel not installed!\n" if $LTR_HARVEST eq ""; +} else { + if (-d $LTR_HARVEST) { + $LTR_HARVEST .= "/" if $LTR_HARVEST !~ /\/$/; + $LTR_HARVEST = "perl $LTR_HARVEST/LTR_HARVEST_parallel"; + } +} +`$LTR_HARVEST 2>/dev/null`; +die "Error: LTR_HARVEST_parallel is not found in the path $LTR_HARVEST!\n" if $?==32256 || $?==2; + +# HelitronScanner #tianyuLu +$HelitronScanner =~ s/\s+$//; +if ($HelitronScanner eq "") { + chomp ($HelitronScanner=`command -v HelitronScanner 2>/dev/null`); + die "Error: HelitronScanner not installed!\n" if $HelitronScanner eq ""; +} else { + if (-d $HelitronScanner) { + $HelitronScanner .= "/" if $HelitronScanner !~ /\/$/; + } +} + +# make a softlink to the genome +my $genome_file = basename($genome); +`ln -s $genome $genome_file` unless -e $genome_file; +$genome = $genome_file; + +# check $RMlib +if ($RMlib ne 'null'){ + if (-e $RMlib){ + print "\tA RepeatModeler library $RMlib is provided via --rmlib. Please make sure this is a RepeatModeler2 generated and classified library (some levels of unknown classification is OK).\n\n"; + chomp ($RMlib = `realpath $RMlib`); + `ln -s $RMlib $genome.RM2.raw.fa` unless -s "$genome.RM2.raw.fa"; + $RMlib = "$genome.RM2.raw.fa"; + } else { + die "\tERROR: The RepeatModeler library $RMlib you specified is not found!\n\n"; + } + } + +# check if duplicated sequences found +my $id_mode = 0; #record the mode of id conversion. +my $id_len = `grep \\> $genome|perl -ne 'chomp; s/>//g; my \$len=length \$_; \$max=\$len if \$max<\$len; print "\$max\\n"'`; #find out the longest sequence ID length in the genome +$id_len =~ s/\s+$//; +$id_len = (split /\s+/, $id_len)[-1]; +my $raw_id = `grep \\> $genome|wc -l`; +my $old_id = `grep \\> $genome|sort -u|wc -l`; +if ($raw_id > $old_id){ + chomp ($date = `date`); + die "$date\tERROR: Identical sequence IDs found in the provided genome! Please resolve this issue and try again.\n"; + } + +if ($convert_name == 1){ +if (-s "$genome.mod"){ + $genome = "$genome.mod"; + } else { + +# remove sequence annotations (content after the first space in sequence names) and replace special characters with _, convert non-ATGC bases into Ns +`perl -nle 'my \$info=(split)[0]; \$info=~s/[\\~!@#\\\$%\\^&\\*\\(\\)\\+\\\-\\=\\?\\[\\]\\{\\}\\:;",\\<\\/\\\\\|]+/_/g; \$info=~s/_+/_/g; \$info=~s/[^ATGCN]/N/gi unless /^>/; print \$info' $genome > $genome.$rand.mod`; + +# try to shortern sequences +my $id_len_max = 13; # allowed longest length of a sequence ID in the input file +if ($id_len > $id_len_max){ + chomp ($date = `date`); + print "$date\tThe longest sequence ID in the genome contains $id_len characters, which is longer than the limit ($id_len_max)\n"; + print "\tTrying to reformat seq IDs...\n\t\tAttempt 1...\n"; + `perl -lne 'chomp; if (s/^>+//) {s/^\\s+//; \$_=(split)[0]; s/(.{1,$id_len_max}).*/>\$1/g;} print "\$_"' $genome.$rand.mod > $genome.$rand.temp`; + my $new_id = `grep \\> $genome.$rand.temp|sort -u|wc -l`; + chomp ($date = `date`); + if ($old_id == $new_id){ + $id_mode = 1; + `mv $genome.$rand.temp $genome.mod`; + `rm $genome.$rand.mod 2>/dev/null`; + print "$date\tSeq ID conversion successful!\n\n"; + } else { + print "\t\tAttempt 2...\n"; + `perl -ne 'chomp; if (/^>/) {\$_=">\$1" if /([0-9]+)/;} print "\$_\n"' $genome.$rand.mod > $genome.$rand.temp`; + $new_id = `grep \\> $genome.$rand.temp|sort -u|wc -l`; + if ($old_id == $new_id){ + $id_mode = 2; + `mv $genome.$rand.temp $genome.mod`; + `rm $genome.$rand.mod 2>/dev/null`; + print "$date\tSeq ID conversion successful!\n\n"; + } else { + `rm $genome.$rand.temp $genome.$rand.mod 2>/dev/null`; + die "$date\tERROR: Fail to convert seq IDs to <= $id_len_max characters! Please provide a genome with shorter seq IDs.\n\n"; + } + } + } else { + `mv $genome.$rand.mod $genome.mod`; + } +$genome = "$genome.mod"; +} +} + +# Make working directories +`mkdir $genome.EDTA.raw` unless -e "$genome.EDTA.raw" && -d "$genome.EDTA.raw"; +`mkdir $genome.EDTA.raw/LTR` unless -e "$genome.EDTA.raw/LTR" && -d "$genome.EDTA.raw/LTR"; +`mkdir $genome.EDTA.raw/SINE` unless -e "$genome.EDTA.raw/SINE" && -d "$genome.EDTA.raw/SINE"; +`mkdir $genome.EDTA.raw/LINE` unless -e "$genome.EDTA.raw/LINE" && -d "$genome.EDTA.raw/LINE"; +`mkdir $genome.EDTA.raw/TIR` unless -e "$genome.EDTA.raw/TIR" && -d "$genome.EDTA.raw/TIR"; +`mkdir $genome.EDTA.raw/Helitron` unless -e "$genome.EDTA.raw/Helitron" && -d "$genome.EDTA.raw/Helitron"; + + +########################### +###### LTR_retriever ###### +########################### + +if ($type eq "ltr" or $type eq "all"){ + +chomp ($date = `date`); +print STDERR "$date\tStart to find LTR candidates.\n\n"; + +# enter the working directory and create genome softlink +chdir "$genome.EDTA.raw/LTR"; +`ln -s ../../$genome $genome` unless -s $genome; + +# Try to recover existing results +chomp ($date = `date`); +if ($overwrite eq 0 and -s "$genome.LTR.raw.fa"){ + print STDERR "$date\tExisting result file $genome.LTR.raw.fa found!\n\t\tWill keep this file without rerunning this module.\n\t\tPlease specify --overwrite 1 if you want to rerun this module.\n\n"; + } else { + print STDERR "$date\tIdentify LTR retrotransposon candidates from scratch.\n\n"; + +# run LTRharvest +if ($overwrite eq 0 and -s "$genome.harvest.combine.scn"){ + print STDERR "$date\tExisting raw result $genome.harvest.scn found!\n\t\tWill use this for further analyses.\n\n"; + } else { + # `perl $LTR_HARVEST -seq $genome -threads $threads -gt $genometools -size 1000000 -time 300`; + `$LTR_HARVEST -seq $genome -threads $threads -gt $genometools -size 1000000 -time 300`; #tianyulu + } + +# run LTR_FINDER_parallel +if ($overwrite eq 0 and -s "$genome.finder.combine.scn"){ + print STDERR "$date\tExisting raw result $genome.finder.combine.scn found!\n\t\tWill use this for further analyses.\n\n"; + } else { + # `perl $LTR_FINDER -seq $genome -threads $threads -harvest_out -size 1000000 -time 300`; + `$LTR_FINDER -seq $genome -threads $threads -harvest_out -size 1000000 -time 300`; #tianyulu + } + +# run LTR_retriever +if ($overwrite eq 0 and -s "$genome.LTRlib.fa"){ + print STDERR "$date\tExisting LTR_retriever result $genome.LTRlib.fa found!\n\t\tWill use this for further analyses.\n\n"; + } else { + `cat $genome.harvest.combine.scn $genome.finder.combine.scn > $genome.rawLTR.scn`; + `${LTR_retriever}LTR_retriever -genome $genome -inharvest $genome.rawLTR.scn -u $miu -threads $threads -noanno -trf_path $trf -blastplus $blastplus -repeatmasker $repeatmasker`; + } + +# get full-length LTR from pass.list +`awk '{if (\$1 !~ /#/) print \$1"\\t"\$1}' $genome.pass.list | perl $call_seq - -C $genome > $genome.LTR.intact.fa.ori`; +`perl -i -nle 's/\\|.*//; print \$_' $genome.LTR.intact.fa.ori`; +`perl $rename_LTR $genome.LTR.intact.fa.ori $genome.defalse > $genome.LTR.intact.fa.anno`; +`mv $genome.LTR.intact.fa.anno $genome.LTR.intact.fa.ori`; + +# remove simple repeats and candidates with simple repeats at terminals +`${mdust}mdust $genome.LTR.intact.fa.ori > $genome.LTR.intact.fa.ori.dusted`; +`perl $cleanup_tandem -misschar N -nc 50000 -nr 0.9 -minlen 100 -minscore 3000 -trf 1 -trf_path $trf -cleanN 1 -cleanT 1 -f $genome.LTR.intact.fa.ori.dusted > $genome.LTR.intact.fa.ori.dusted.cln`; + +# annotate and remove not LTR candidates +`${TEsorter}TEsorter $genome.LTR.intact.fa.ori.dusted.cln --disable-pass2 -p $threads 2>/dev/null`; +`perl $cleanup_misclas $genome.LTR.intact.fa.ori.dusted.cln.rexdb.cls.tsv`; +`mv $genome.LTR.intact.fa.ori.dusted.cln.cln $genome.LTR.intact.raw.fa`; +`mv $genome.LTR.intact.fa.ori.dusted.cln.cln.list $genome.LTR.intact.raw.fa.anno.list`; +`cp $genome.LTR.intact.raw.fa.anno.list ../`; + +# generate annotated output and gff +`perl $output_by_list 1 $genome.LTR.intact.fa.ori 1 $genome.LTR.intact.raw.fa -FA -ex|grep \\>|perl -nle 's/>//; print "Name\\t\$_"' > $genome.LTR.intact.fa.ori.rmlist`; +`perl $filter_gff $genome.pass.list.gff3 $genome.LTR.intact.fa.ori.rmlist | perl -nle 's/LTR_retriever/EDTA/gi; print \$_' > $genome.LTR.intact.raw.gff3`; +`rm $genome`; + } + +# copy result files out +`touch $genome.LTRlib.fa` unless -e "$genome.LTRlib.fa"; +`cp $genome.LTRlib.fa $genome.LTR.raw.fa`; +`cp $genome.LTRlib.fa ../$genome.LTR.raw.fa`; +`cp $genome.LTR.intact.raw.fa $genome.LTR.intact.raw.gff3 ../ 2>/dev/null`; +`cp $genome.LTR.intact.fa ../$genome.LTR.intact.raw.fa` if -s "$genome.LTR.intact.fa"; +`cp $genome.LTR.intact.gff3 ../$genome.LTR.intact.raw.gff3` if -s "$genome.LTR.intact.gff3"; +chdir '../..'; + +# check results +chomp ($date = `date`); +die "Error: LTR results not found!\n\n" unless -e "$genome.EDTA.raw/$genome.LTR.raw.fa"; +if (-s "$genome.EDTA.raw/$genome.LTR.raw.fa"){ + print STDERR "$date\tFinish finding LTR candidates.\n\n"; + } else { + print STDERR "$date\tWarning: The LTR result file has 0 bp!\n\n"; + } + +} + + +############################# +###### AnnoSINE ###### +############################# +if ($type eq "sine" or $type eq "all"){ + +chomp ($date = `date`); +print STDERR "$date\tStart to find SINE candidates.\n\n"; + +# enter the working directory and create genome softlink +chdir "$genome.EDTA.raw/SINE"; +`ln -s ../../$genome $genome` unless -s $genome; + +# Remove existing results +`rm -rf Seed_SINE.fa Step* HMM_out 2>/dev/null` if $overwrite eq 1; + +# run AnnoSINE_v2 +my $status; # record status of AnnoSINE execution +if (-s "Seed_SINE.fa"){ + print STDERR "$date\tExisting result file Seed_SINE.fa found!\n\t\tWill keep this file without rerunning this module.\n\t\tPlease specify --overwrite 1 if you want to rerun AnnoSINE_v2.\n\n"; + } else { + $status = system("python3 ${annosine}AnnoSINE_v2 -t $threads -a 2 --num_alignments 50000 -rpm 0 --copy_number 3 --shift 100 -auto 1 3 $genome ./ > /dev/null 2>&1"); + } + +# filter and reclassify AnnoSINE candidates with TEsorter and make SINE library +if (-s "Seed_SINE.fa"){ + # annotate and remove non-SINE candidates + `awk '{gsub(/Unknown/, "unknown"); print \$1}' Seed_SINE.fa > $genome.AnnoSINE.raw.fa`; + `${TEsorter}TEsorter $genome.AnnoSINE.raw.fa --disable-pass2 -p $threads 2>/dev/null`; + `touch $genome.AnnoSINE.raw.fa.rexdb.cls.tsv` unless -e "$genome.AnnoSINE.raw.fa.rexdb.cls.tsv"; + `perl $cleanup_misclas $genome.AnnoSINE.raw.fa.rexdb.cls.tsv`; + + # clean up tandem repeat + `perl $cleanup_tandem -misschar N -nc 50000 -nr 0.8 -minlen 80 -minscore 3000 -trf 1 -trf_path $trf -cleanN 1 -cleanT 1 -f $genome.AnnoSINE.raw.fa.cln > $genome.SINE.raw.fa`; + } +elsif ($status == 0) { + print "\t\tAnnoSINE is finished without error, but the Seed_SINE.fa file is not produced.\n\n"; + `touch $genome.SINE.raw.fa`; + } +else { + print "\t\tAnnoSINE exited with error, please test run AnnoSINE to make sure it's working.\n\n"; + } + +# copy result files out +`cp $genome.SINE.raw.fa ../`; +chdir '../..'; + +# check results +chomp ($date = `date`); +die "Error: SINE results not found!\n\n" unless -e "$genome.EDTA.raw/$genome.SINE.raw.fa"; +if (-s "$genome.EDTA.raw/$genome.SINE.raw.fa"){ + print STDERR "$date\tFinish finding SINE candidates.\n\n"; + } else { + print STDERR "$date\tWarning: The SINE result file has 0 bp!\n\n"; + } + +} + + +############################# +###### RepeatModeler ###### +############################# + +if ($type eq "line" or $type eq "all"){ + +chomp ($date = `date`); +print STDERR "$date\tStart to find LINE candidates.\n\n"; + +# enter the working directory and create genome softlink +chdir "$genome.EDTA.raw/LINE"; +`ln -s ../../$genome $genome` unless -s $genome; +`cp ../../$RMlib $RMlib` if $RMlib ne 'null'; + +# Try to recover existing results or run RepeatModeler2 +chomp ($date = `date`); +if ($overwrite eq 0 and -s $RMlib){ + if (-s "$genome-families.fa"){ + print STDERR "$date\tExisting result file $genome-families.fa found!\n\t\tWill not use the provided RepeatModeler2 library since --overwrite 0.\n\t\tPlease specify --overwrite 1 if you want to use the provided --rmlib file.\n\n"; + } else { + `cp $RMlib "$genome-families.fa" 2>/dev/null`; + } + } + +if ($overwrite eq 0 and -s "$genome-families.fa"){ + print STDERR "$date\tExisting result file $genome-families.fa found!\n\t\tWill keep this file without rerunning this module.\n\t\tPlease specify --overwrite 1 if you want to rerun this module.\n\n"; + } else { + # run RepeatModeler2 + print STDERR "$date\tIdentify LINE retrotransposon candidates from scratch.\n\n"; + my $status; # record status of RepeatModeler execution + `${repeatmodeler}BuildDatabase -name $genome $genome`; + $status = system("${repeatmodeler}RepeatModeler -engine ncbi -threads $threads -database $genome > repeatmodeler.log 2>&1"); + if ($status != 0) { + # Execute the old version of RepeatModeler + $status = system("${repeatmodeler}RepeatModeler -engine ncbi -pa $threads -database $genome > repeatmodeler.log 2>&1"); + print "ERROR: RepeatModeler did not run correctly. Please test run this command: + ${repeatmodeler}RepeatModeler -engine ncbi -pa $threads -database $genome + " and exit unless $status == 0; + } + `rm $genome.nhr $genome.nin $genome.nnd $genome.nni $genome.nog $genome.nsq $genome.njs $genome.translation 2>/dev/null`; + } + +# filter and reclassify RepeatModeler candidates with TEsorter and make LINE library +if (-s "$genome-families.fa"){ + # annotate and remove misclassified candidates + `awk '{gsub(/Unknown/, "unknown"); print \$1}' $genome-families.fa > $genome.RM2.raw.fa` if -e "$genome-families.fa"; + `${TEsorter}TEsorter $genome.RM2.raw.fa --disable-pass2 -p $threads 2>/dev/null`; + `perl $cleanup_misclas $genome.RM2.raw.fa.rexdb.cls.tsv`; + + # reclassify clean candidates + `${TEsorter}TEsorter $genome.RM2.raw.fa.cln --disable-pass2 -p $threads 2>/dev/null`; + `perl -nle 's/>\\S+\\s+/>/; print \$_' $genome.RM2.raw.fa.cln.rexdb.cls.lib > $genome.RM2.raw.fa.cln`; + + # clean up tandem repeat + `perl $cleanup_tandem -misschar N -nc 50000 -nr 0.8 -minlen 80 -minscore 3000 -trf 1 -trf_path $trf -cleanN 1 -cleanT 1 -f $genome.RM2.raw.fa.cln > $genome.RM2.raw.fa.cln2`; + `grep -P 'LINE|SINE' $genome.RM2.raw.fa.cln2 | perl $output_by_list 1 $genome.RM2.raw.fa.cln2 1 - -FA > $genome.LINE.raw.fa`; + `grep -P 'LINE|SINE' $genome.RM2.raw.fa.cln2 | perl $output_by_list 1 $genome.RM2.raw.fa.cln2 1 - -FA -ex > $genome.RM2.fa`; + } else { + print "\t\tRepeatModeler is finished, but the $genome-families.fa file is not produced.\n\n"; + `touch $genome.RM2.raw.fa $genome.LINE.raw.fa $genome.RM2.fa`; + } + +# copy result files out +`cp $genome.LINE.raw.fa $genome.RM2.fa ../`; #update the filtered RM2 result in the EDTA/raw folder +`cp $genome.RM2.raw.fa ../../`; #update the raw RM2 result in the EDTA folder +chdir '../..'; + +# check results +chomp ($date = `date`); +die "Error: LINE results not found!\n\n" unless -e "$genome.EDTA.raw/$genome.LINE.raw.fa"; +if (-s "$genome.EDTA.raw/$genome.LINE.raw.fa"){ + print STDERR "$date\tFinish finding LINE candidates.\n\n"; + } else { + print STDERR "$date\tWarning: The LINE result file has 0 bp!\n\n"; + } +} + + +########################### +###### TIR-Learner ###### +########################### + +if ($type eq "tir" or $type eq "all"){ + +chomp ($date = `date`); +print STDERR "$date\tStart to find TIR candidates.\n\n"; + +# pre-set parameters +my $genome_file_real_path=File::Spec->rel2abs($genome); # the genome file with real path + +# enter the working directory and create genome softlink +chdir "$genome.EDTA.raw/TIR"; +`ln -s ../../$genome $genome` unless -s $genome; + +# Try to recover existing results +chomp ($date = `date`); +if ($overwrite eq 0 and (-s "$genome.TIR.intact.raw.fa" or -s "$genome.TIR.intact.fa")){ + print STDERR "$date\tExisting result file $genome.TIR.intact.raw.fa found!\n\t\tWill keep this file without rerunning this module.\n\t\tPlease specify --overwrite 1 if you want to rerun this module.\n\n"; + } else { + print STDERR "$date\tIdentify TIR candidates from scratch.\n\n"; + print STDERR "Species: $species\n"; + + # run TIR-Learner + if ($overwrite eq 0 and -s "./TIR-Learner-Result/TIR-Learner_FinalAnn.fa"){ + print STDERR "$date\tExisting raw result TIR-Learner_FinalAnn.fa found!\n\t\tWill use this for further analyses.\n\t\tPlease specify --overwrite 1 if you want to rerun this module.\n\n"; + } else { + `$TIR_Learner -f $genome_file_real_path -s $species -t $threads -l $maxint -c -o $genome_file_real_path.EDTA.raw/TIR --grf_path $grfp --gt_path $genometools`; #TianyuLu + } + + # clean raw predictions with flanking alignment + `perl $rename_tirlearner ./TIR-Learner-Result/TIR-Learner_FinalAnn.fa | perl -nle 's/TIR-Learner_//g; print \$_' > $genome.TIR`; + `perl $get_ext_seq $genome $genome.TIR`; + `perl $flank_filter -genome $genome -query $genome.TIR.ext30.fa -miniden 90 -mincov 0.9 -maxct 20 -blastplus $blastplus -t $threads`; + + # recover superfamily info + `perl $output_by_list 1 $genome.TIR 1 $genome.TIR.ext30.fa.pass.fa -FA -MSU0 -MSU1 > $genome.TIR.ext30.fa.pass.fa.ori`; + + # remove simple repeats and candidates with simple repeats at terminals + `${mdust}mdust $genome.TIR.ext30.fa.pass.fa.ori > $genome.TIR.ext30.fa.pass.fa.dusted`; + `perl $cleanup_tandem -misschar N -nc 50000 -nr 0.9 -minlen 80 -minscore 3000 -trf 1 -trf_path $trf -cleanN 1 -cleanT 1 -f $genome.TIR.ext30.fa.pass.fa.dusted > $genome.TIR.ext30.fa.pass.fa.dusted.cln`; + + # annotate and remove non-TIR candidates + `${TEsorter}TEsorter $genome.TIR.ext30.fa.pass.fa.dusted.cln --disable-pass2 -p $threads 2>/dev/null`; + `perl $cleanup_misclas $genome.TIR.ext30.fa.pass.fa.dusted.cln.rexdb.cls.tsv`; + `mv $genome.TIR.ext30.fa.pass.fa.dusted.cln.cln $genome.TIR.intact.raw.fa`; + `cp $genome.TIR.ext30.fa.pass.fa.dusted.cln.cln.list $genome.TIR.intact.raw.fa.anno.list`; + `cp $genome.TIR.intact.raw.fa.anno.list ../`; + + # get gff3 of intact TIR elements + `perl -nle 's/\\-\\+\\-/_Len:/; my (\$chr, \$method, \$supfam, \$s, \$e, \$anno) = (split)[0,1,2,3,4,8]; my \$class='DNA'; \$class='MITE' if \$e-\$s+1 <= 600; my (\$tir, \$iden, \$tsd)=(\$1, \$2/100, \$3) if \$anno=~/TIR:(.*)_([0-9.]+)_TSD:([a-z0-9._]+)_LEN/i; print "\$chr \$s \$e \$chr:\$s..\$e \$class/\$supfam structural \$iden . . . TSD=\$tsd;TIR=\$tir"' ./TIR-Learner-Result/TIR-Learner_FinalAnn.gff3 | perl $output_by_list 4 - 1 $genome.TIR.intact.raw.fa -MSU0 -MSU1 > $genome.TIR.intact.raw.bed`; + `perl $bed2gff $genome.TIR.intact.raw.bed TIR > $genome.TIR.intact.raw.gff3`; + } + +# copy result files out +`cp $genome.TIR.intact.bed ../$genome.TIR.intact.raw.bed` if -s "$genome.TIR.intact.bed"; # recover /dev/null`; +chdir '../..'; + +# check results +chomp ($date = `date`); +die "Error: TIR results not found!\n\n" unless -e "$genome.EDTA.raw/$genome.TIR.intact.raw.fa"; +if (-s "$genome.EDTA.raw/$genome.TIR.intact.raw.fa"){ + print STDERR "$date\tFinish finding TIR candidates.\n\n"; + } else { + print STDERR "Warning: The TIR result file has 0 bp!\n\n"; + } + +} + + +############################# +###### HelitronScanner ###### +############################# + +if ($type eq "helitron" or $type eq "all"){ + +chomp ($date = `date`); +print STDERR "$date\tStart to find Helitron candidates.\n\n"; + +# enter the working directory and create genome softlink +chdir "$genome.EDTA.raw/Helitron"; +`ln -s ../../$genome $genome` unless -s $genome; + +# Try to recover existing results +chomp ($date = `date`); +if ($overwrite eq 0 and (-s "$genome.Helitron.intact.raw.fa" or -s "$genome.Helitron.intact.fa")){ + print STDERR "$date\tExisting result file $genome.Helitron.intact.raw.fa found!\n\t\tWill keep this file without rerunning this module.\n\t\tPlease specify --overwrite 1 if you want to rerun this module.\n\n"; + } else { + print STDERR "$date\tIdentify Helitron candidates from scratch.\n\n"; + +# run HelitronScanner +`$HelitronScanner_Runner $genome $threads \"$HelitronScanner\"`; + +# filter candidates based on repeatness of flanking regions +`perl $format_helitronscanner -genome $genome -sitefilter 1 -minscore 12 -keepshorter 1 -extlen 30 -extout 1`; +`perl $flank_filter -genome $genome -query $genome.HelitronScanner.filtered.ext.fa -miniden 90 -mincov 0.9 -maxct 5 -blastplus $blastplus -t $threads`; #more relaxed +#`perl $flank_filter -genome $genome -query $genome.HelitronScanner.filtered.ext.fa -miniden 80 -mincov 0.8 -maxct 5 -blastplus $blastplus -t $threads`; #more stringent + +# remove simple repeats and candidates with simple repeats at terminals +`perl $output_by_list 1 $genome.HelitronScanner.filtered.fa 1 $genome.HelitronScanner.filtered.ext.fa.pass.fa -FA > $genome.HelitronScanner.filtered.fa.pass.fa`; +`${mdust}mdust $genome.HelitronScanner.filtered.fa.pass.fa > $genome.HelitronScanner.filtered.fa.pass.fa.dusted`; +`perl $cleanup_tandem -misschar N -nc 50000 -nr 0.9 -minlen 100 -minscore 3000 -trf 1 -trf_path $trf -cleanN 1 -cleanT 1 -f $genome.HelitronScanner.filtered.fa.pass.fa.dusted | perl -nle 's/^(>.*)\\s+(.*)\$/\$1#DNA\\/Helitron\\t\$2/; print \$_' > $genome.HelitronScanner.filtered.fa.pass.fa.dusted.cln`; + +# annotate and remove non-Helitron candidates +`${TEsorter}TEsorter $genome.HelitronScanner.filtered.fa.pass.fa.dusted.cln --disable-pass2 -p $threads 2>/dev/null`; +`perl $cleanup_misclas $genome.HelitronScanner.filtered.fa.pass.fa.dusted.cln.rexdb.cls.tsv`; +`mv $genome.HelitronScanner.filtered.fa.pass.fa.dusted.cln.cln $genome.Helitron.intact.raw.fa`; +`cp $genome.HelitronScanner.filtered.fa.pass.fa.dusted.cln.cln.list $genome.Helitron.intact.raw.fa.anno.list`; +`cp $genome.Helitron.intact.raw.fa.anno.list ../`; + +# get intact Helitrons and gff3 +`perl $make_bed $genome.Helitron.intact.raw.fa > $genome.Helitron.intact.raw.bed`; +`perl $bed2gff $genome.Helitron.intact.raw.bed HEL > $genome.Helitron.intact.raw.gff3`; + } + +# copy result files out +`cp $genome.Helitron.intact.bed ../$genome.Helitron.intact.raw.bed` if -s "$genome.Helitron.intact.bed"; # recover /dev/null`; +chdir '../..'; + +# check results +chomp ($date = `date`); +die "Error: Helitron results not found!\n\n" unless -e "$genome.EDTA.raw/$genome.Helitron.intact.raw.fa"; +if (-s "$genome.EDTA.raw/$genome.Helitron.intact.raw.fa"){ + print STDERR "$date\tFinish finding Helitron candidates.\n\n"; + } else { + print STDERR "$date\tWarning: The Helitron result file has 0 bp!\n\n"; + } + +} + +chomp ($date = `date`); +print STDERR "$date\tExecution of EDTA_raw.pl is finished!\n\n"; diff --git a/bin/setup_LTR_retriever_postprocess.sh b/bin/setup_LTR_retriever_postprocess.sh new file mode 100755 index 0000000..140b4b5 --- /dev/null +++ b/bin/setup_LTR_retriever_postprocess.sh @@ -0,0 +1,26 @@ +#!/usr/bin/env bash + +script_path=$(dirname $0) + +cat > input/LTR_retriever_postprocess.pl << EOF + +my \$genome = 'genome'; +my \$threads = $1; + +my \$call_seq = '$script_path/call_seq_by_list.pl'; +my \$rename_LTR = '$script_path/rename_LTR_skim.pl'; +my \$mdust = ''; # Assumed on PATH +my \$cleanup_tandem = '$script_path/cleanup_tandem.pl'; +my \$trf = '$(which trf)'; +my \$TEsorter = ''; # Assumed on PATH +my \$cleanup_misclas= '$script_path/cleanup_misclas.pl'; +my \$output_by_list = '$script_path/output_by_list.pl'; +my \$filter_gff = '$script_path/filter_gff3.pl'; + +EOF + +sed -n 443,462p "$script_path/EDTA_raw.pl" \ + >> input/LTR_retriever_postprocess.pl + +sed -n 466,472p "$script_path/EDTA_raw.pl" \ + >> input/LTR_retriever_postprocess.pl diff --git a/conf/modules.config b/conf/modules.config index 918b569..c521005 100644 --- a/conf/modules.config +++ b/conf/modules.config @@ -42,5 +42,15 @@ process { withName: 'EDTA:FORMAT_HELITRONSCANNER_OUT' { ext.args = '-sitefilter 1 -minscore 12 -keepshorter 1 -extlen 30 -extout 1' } + + withName: 'EDTA:LTR_RETRIEVER_POSTPROCESS' { + ext.prefix = { "${meta.id}.pp" } + + publishDir = [ + path: { "${params.outdir}/ltr" }, + mode: params.publish_dir_mode, + saveAs: { filename -> filename.equals('versions.yml') ? null : filename } + ] + } } \ No newline at end of file diff --git a/conf/test.config b/conf/test.config index e921133..e0a2931 100644 --- a/conf/test.config +++ b/conf/test.config @@ -7,5 +7,5 @@ process { } params { - genomes = 'https://raw.githubusercontent.com/nf-core/test-datasets/modules/data/genomics/sarscov2/genome/genome.fasta' + genomes = 'https://raw.githubusercontent.com/jguhlin/EDTA/a2cd9a0777e4ac6e39545bacc3e752f94eb2f389/test/genome.fa' } \ No newline at end of file diff --git a/modules/local/ltr_retriever_postprocess/environment.yml b/modules/local/ltr_retriever_postprocess/environment.yml new file mode 100644 index 0000000..6f8a4fc --- /dev/null +++ b/modules/local/ltr_retriever_postprocess/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: + - bioconda::mdust=2006.10.17 + - bioconda::tesorter=1.4.7 + - bioconda::trf=4.09.1 + - conda-forge::perl=5.32.1 \ No newline at end of file diff --git a/modules/local/ltr_retriever_postprocess/main.nf b/modules/local/ltr_retriever_postprocess/main.nf new file mode 100644 index 0000000..c957229 --- /dev/null +++ b/modules/local/ltr_retriever_postprocess/main.nf @@ -0,0 +1,80 @@ +process LTR_RETRIEVER_POSTPROCESS { + tag "$meta.id" + label 'process_low' + + conda "${moduleDir}/environment.yml" + container "${ workflow.containerEngine == 'singularity' && !task.ext.singularity_pull_docker_container ? + 'https://community-cr-prod.seqera.io/docker/registry/v2/blobs/sha256/64/64d26063bedc2efcba20750a408a21f50907986d0d9aee685b03d7d05d3fbd8b/data': + 'community.wave.seqera.io/library/mdust_tesorter_trf_perl:3424609103d3b065' }" + + input: + tuple val(meta), path(genome, name: 'input/genome') + path "input/genome.pass.list" + path "input/genome.pass.list.gff3" + path "input/genome.defalse" + path "input/genome.LTRlib.fa" + + output: + tuple val(meta), path('*.LTR.raw.fa') , emit: raw_fa + tuple val(meta), path('*.LTR.intact.raw.fa') , emit: intact_raw_fa + tuple val(meta), path('*.LTR.intact.raw.gff3') , emit: intact_raw_gff3 + tuple val(meta), path('*.LTR.intact.raw.fa.anno.list') , emit: anno_list + path "versions.yml" , emit: versions + + when: + task.ext.when == null || task.ext.when + + script: + def prefix = task.ext.prefix ?: "${meta.id}" + def MDUST_VERSION = '2006.10.17' // WARN: Manually update when changing Bioconda assets + if ( "$prefix" == 'genome' ) error "Input and output names are the same, use \"task.ext.prefix\" to disambiguate!" + """ + setup_LTR_retriever_postprocess.sh \\ + $task.cpus + + cd input + + perl LTR_retriever_postprocess.pl + + cd - + + mv \\ + genome.LTR.raw.fa \\ + ${prefix}.LTR.raw.fa + + mv \\ + genome.LTR.intact.raw.fa \\ + ${prefix}.genome.LTR.intact.raw.fa + + mv \\ + genome.LTR.intact.raw.gff3 \\ + ${prefix}.LTR.intact.raw.gff3 + + mv \\ + genome.LTR.intact.raw.fa.anno.list \\ + ${prefix}.LTR.intact.raw.fa.anno.list + + cat <<-END_VERSIONS > versions.yml + "${task.process}": + perl: \$(perl -v | sed -n 's|This is perl.*(\\(.*\\)).*|\\1|p') + trf: \$(trf -v |& sed -n 's|.*Version \\(.*\\)|\\1|p') + TEsorter: \$(TEsorter -v | tr -d 'TEsorter ') + mdust: $MDUST_VERSION + END_VERSIONS + """ + + stub: + def prefix = task.ext.prefix ?: "${meta.id}" + def MDUST_VERSION = '2006.10.17' // WARN: Manually update when changing Bioconda assets + if ( "$prefix" == 'genome' ) error "Input and output names are the same, use \"task.ext.prefix\" to disambiguate!" + """ + + cat <<-END_VERSIONS > versions.yml + "${task.process}": + perl: \$(perl -v | sed -n 's|This is perl.*(\\(.*\\)).*|\\1|p') + trf: \$(trf -v |& sed -n 's|.*Version \\(.*\\)|\\1|p') + TEsorter: \$(TEsorter -v | tr -d 'TEsorter ') + mdust: $MDUST_VERSION + END_VERSIONS + """ +} \ No newline at end of file diff --git a/modules/nf-core/ltrretriever/ltrretriever/main.nf b/modules/nf-core/ltrretriever/ltrretriever/main.nf new file mode 100644 index 0000000..ca97232 --- /dev/null +++ b/modules/nf-core/ltrretriever/ltrretriever/main.nf @@ -0,0 +1,96 @@ +process LTRRETRIEVER_LTRRETRIEVER { + tag "$meta.id" + label 'process_high' + + conda "${moduleDir}/environment.yml" + container "${ workflow.containerEngine == 'singularity' && !task.ext.singularity_pull_docker_container ? + 'https://depot.galaxyproject.org/singularity/ltr_retriever:2.9.9--hdfd78af_0': + 'biocontainers/ltr_retriever:2.9.9--hdfd78af_0' }" + + input: + tuple val(meta), path(genome) + path(harvest) + path(finder) + path(mgescan) + path(non_tgca) + + output: + tuple val(meta), path("*.log") , emit: log + tuple val(meta), path("${prefix}.pass.list"), emit: pass_list , optional: true + tuple val(meta), path("*.pass.list.gff3") , emit: pass_list_gff , optional: true + tuple val(meta), path("*.LTRlib.fa") , emit: ltrlib , optional: true + tuple val(meta), path("${prefix}.out") , emit: annotation_out , optional: true + tuple val(meta), path("*.out.gff3") , emit: annotation_gff , optional: true + tuple val(meta), path("*.defalse") , emit: defalse , optional: true + path "versions.yml" , emit: versions + + when: + task.ext.when == null || task.ext.when + + script: + def args = task.ext.args ?: '' + prefix = task.ext.prefix ?: "${meta.id}" + def inharvest = harvest ? "-inharvest $harvest" : '' + def infinder = finder ? "-infinder $finder" : '' + def inmgescan = mgescan ? "-inmgescan $mgescan" : '' + def non_tgca_file = non_tgca ? "-nonTGCA $non_tgca" : '' + def writable_genome = "${genome.baseName}.writable.${genome.extension}" + // writable_genome: + // This is needed to avoid LTR_retriever:2.9.9 failure when the input `genome` is + // readonly. LTR_retriever triggers a 'die' if the genome is readonly. + // See: https://github.com/oushujun/LTR_retriever/blob/4039eb7778fd9cbc60021e99a8693285e0fa2daf/LTR_retriever#L312 + // + // This copy with permissions logic can be removed once https://github.com/oushujun/LTR_retriever/issues/176 + // has been resolved. + """ + cp \\ + $genome \\ + $writable_genome + + chmod \\ + a+w \\ + $writable_genome + + LTR_retriever \\ + -genome $writable_genome \\ + $inharvest \\ + $infinder \\ + $inmgescan \\ + $non_tgca_file \\ + -threads $task.cpus \\ + $args \\ + &> >(tee "${prefix}.log" 2>&1) \\ + || echo "Errors from LTR_retriever printed to ${prefix}.log" + + mv "${writable_genome}.pass.list" "${prefix}.pass.list" || echo ".pass.list was not produced" + mv "${writable_genome}.pass.list.gff3" "${prefix}.pass.list.gff3" || echo ".pass.list.gff3 was not produced" + mv "${writable_genome}.LTRlib.fa" "${prefix}.LTRlib.fa" || echo ".LTRlib.fa was not produced" + mv "${writable_genome}.out" "${prefix}.out" || echo ".out was not produced" + mv "${writable_genome}.out.gff3" "${prefix}.out.gff3" || echo ".out.gff3 was not produced" + mv "${writable_genome}.defalse" "${prefix}.defalse" || echo ".defalse was not produced" + + cat <<-END_VERSIONS > versions.yml + "${task.process}": + LTR_retriever: \$(LTR_retriever -h 2>&1 | grep '### LTR_retriever' | sed 's/### LTR_retriever //; s/ ###//') + END_VERSIONS + """ + + stub: + def args = task.ext.args ?: '' + prefix = task.ext.prefix ?: "${meta.id}" + def touch_out = args.contains('-noanno') ? '' : "touch ${prefix}.out" + def touch_out_gff = args.contains('-noanno') ? '' : "touch ${prefix}.out.gff3" + """ + touch "${prefix}.log" + touch "${prefix}.pass.list" + touch "${prefix}.pass.list.gff3" + touch "${prefix}.LTRlib.fa" + $touch_out + $touch_out_gff + + cat <<-END_VERSIONS > versions.yml + "${task.process}": + LTR_retriever: \$(LTR_retriever -h 2>&1 | grep '### LTR_retriever' | sed 's/### LTR_retriever //; s/ ###//') + END_VERSIONS + """ +} diff --git a/modules/nf-core/ltrretriever/ltrretriever/tests/main.nf.test.snap b/modules/nf-core/ltrretriever/ltrretriever/tests/main.nf.test.snap new file mode 100644 index 0000000..4942e1a --- /dev/null +++ b/modules/nf-core/ltrretriever/ltrretriever/tests/main.nf.test.snap @@ -0,0 +1,160 @@ +{ + "versions": { + "content": [ + "\"LTRRETRIEVER_LTRRETRIEVER\":\n LTR_retriever: v2.9.9\n" + ], + "meta": { + "nf-test": "0.8.4", + "nextflow": "23.10.1" + }, + "timestamp": "2024-02-19T11:17:50.208819" + }, + "stub": { + "content": [ + { + "0": [ + [ + { + "id": "test" + }, + "test.log:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "1": [ + [ + { + "id": "test" + }, + "test.pass.list:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "2": [ + [ + { + "id": "test" + }, + "test.pass.list.gff3:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "3": [ + [ + { + "id": "test" + }, + "test.LTRlib.fa:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "4": [ + [ + { + "id": "test" + }, + "test.out:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "5": [ + [ + { + "id": "test" + }, + "test.out.gff3:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "6": [ + + ], + "7": [ + "versions.yml:md5,3ab159acaee06b342b56e2d35e5e669b" + ], + "annotation_gff": [ + [ + { + "id": "test" + }, + "test.out.gff3:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "annotation_out": [ + [ + { + "id": "test" + }, + "test.out:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "defalse": [ + + ], + "log": [ + [ + { + "id": "test" + }, + "test.log:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "ltrlib": [ + [ + { + "id": "test" + }, + "test.LTRlib.fa:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "pass_list": [ + [ + { + "id": "test" + }, + "test.pass.list:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "pass_list_gff": [ + [ + { + "id": "test" + }, + "test.pass.list.gff3:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "versions": [ + "versions.yml:md5,3ab159acaee06b342b56e2d35e5e669b" + ] + } + ], + "meta": { + "nf-test": "0.9.0", + "nextflow": "24.04.4" + }, + "timestamp": "2024-12-05T12:39:11.669541" + }, + "annotation_out": { + "content": [ + [ + [ + { + "id": "test" + }, + "test.out:md5,33d89bea9031f25de8f0d3591ab94d87" + ] + ] + ], + "meta": { + "nf-test": "0.8.4", + "nextflow": "24.04.3" + }, + "timestamp": "2024-07-16T14:18:02.458476" + }, + "versions_no_ltr": { + "content": [ + [ + "versions.yml:md5,3ab159acaee06b342b56e2d35e5e669b" + ] + ], + "meta": { + "nf-test": "0.8.4", + "nextflow": "24.04.3" + }, + "timestamp": "2024-07-16T14:03:52.324194" + } +} \ No newline at end of file diff --git a/nextflow.config b/nextflow.config index ccf5850..7eb1232 100644 --- a/nextflow.config +++ b/nextflow.config @@ -1,3 +1,7 @@ +params { + publish_dir_mode = 'copy' +} + process { cpus = { 1 * task.attempt } diff --git a/nf-test.config b/nf-test.config index c3a9a1e..29d7e09 100644 --- a/nf-test.config +++ b/nf-test.config @@ -1,7 +1,7 @@ config { testsDir "." workDir System.getenv("NFT_WORKDIR") ?: ".nf-test" - configFile "test/nf-test/nextflow.config" + configFile "tests/nf-test/nextflow.config" plugins { load "nft-bam@0.4.0" diff --git a/test/Alyrata.test.fa b/tests/Alyrata.test.fa similarity index 100% rename from test/Alyrata.test.fa rename to tests/Alyrata.test.fa diff --git a/test/Col.test.fa b/tests/Col.test.fa similarity index 100% rename from test/Col.test.fa rename to tests/Col.test.fa diff --git a/test/Ler.test.fa b/tests/Ler.test.fa similarity index 100% rename from test/Ler.test.fa rename to tests/Ler.test.fa diff --git a/test/README.txt b/tests/README.txt similarity index 100% rename from test/README.txt rename to tests/README.txt diff --git a/test/genome.cds.fa b/tests/genome.cds.fa similarity index 100% rename from test/genome.cds.fa rename to tests/genome.cds.fa diff --git a/test/genome.cds.list b/tests/genome.cds.list similarity index 100% rename from test/genome.cds.list rename to tests/genome.cds.list diff --git a/test/genome.exclude.bed b/tests/genome.exclude.bed similarity index 100% rename from test/genome.exclude.bed rename to tests/genome.exclude.bed diff --git a/test/genome.fa b/tests/genome.fa similarity index 100% rename from test/genome.fa rename to tests/genome.fa diff --git a/test/nf-test/nextflow.config b/tests/nf-test/nextflow.config similarity index 100% rename from test/nf-test/nextflow.config rename to tests/nf-test/nextflow.config diff --git a/test/nf-test/small/main.nf.test b/tests/nf-test/small/main.nf.test similarity index 50% rename from test/nf-test/small/main.nf.test rename to tests/nf-test/small/main.nf.test index 7d40a02..ce37a4f 100644 --- a/test/nf-test/small/main.nf.test +++ b/tests/nf-test/small/main.nf.test @@ -13,7 +13,28 @@ nextflow_pipeline { } then { - def stable_path = getAllFilesFromDir(params.outdir, false, ['pipeline_info/*.{html,json,txt,yml}'], null, ['**']) + + def stable_path = getAllFilesFromDir( + params.outdir, + false, + [ + 'pipeline_info/*.{html,json,txt,yml}', + 'ltr/*.pp.LTR.intact.raw.gff3', + 'ltr/*.pp.LTR.raw.fa', + ], + null, + ['**'] + ) + + def stable_name = getAllFilesFromDir( + params.outdir, + true, + [ + 'pipeline_info/*.{html,json,txt,yml}' + ], + null, + ['**'] + ) assertAll( { assert workflow.success}, @@ -21,7 +42,8 @@ nextflow_pipeline { [ 'successful tasks': workflow.trace.succeeded().size(), 'versions': removeNextflowVersion("$outputDir/pipeline_info/software_versions.yml"), - 'stable paths': stable_path + 'stable paths': stable_path, + 'stable names': getRelativePath(stable_name, outputDir), ] ).match() } ) diff --git a/test/nf-test/small/main.nf.test.snap b/tests/nf-test/small/main.nf.test.snap similarity index 72% rename from test/nf-test/small/main.nf.test.snap rename to tests/nf-test/small/main.nf.test.snap index 5a164b2..a943b0a 100644 --- a/test/nf-test/small/main.nf.test.snap +++ b/tests/nf-test/small/main.nf.test.snap @@ -2,7 +2,7 @@ "small genome": { "content": [ { - "successful tasks": 16, + "successful tasks": 17, "versions": { "ANNOSINE": { "annosine": "2.0.7" @@ -42,6 +42,12 @@ "LTRRETRIEVER_LTRRETRIEVER": { "LTR_retriever": "v2.9.9" }, + "LTR_RETRIEVER_POSTPROCESS": { + "perl": "v5.32.1", + "trf": 4.09, + "TEsorter": "1.4.7", + "mdust": "2006.10.17" + }, "REPEATMODELER_BUILDDATABASE": { "repeatmodeler": "2.0.5" }, @@ -56,7 +62,16 @@ } }, "stable paths": [ - + "genome.pp.LTR.intact.raw.fa.anno.list:md5,51261f020350b448acdf89d8fb625448", + "genome.pp.genome.LTR.intact.raw.fa:md5,ae2336f498897d1af4e8d3be28bb3f25" + ], + "stable names": [ + "ltr", + "ltr/genome.pp.LTR.intact.raw.fa.anno.list", + "ltr/genome.pp.LTR.intact.raw.gff3", + "ltr/genome.pp.LTR.raw.fa", + "ltr/genome.pp.genome.LTR.intact.raw.fa", + "pipeline_info" ] } ], @@ -64,6 +79,6 @@ "nf-test": "0.9.0", "nextflow": "24.04.4" }, - "timestamp": "2024-12-03T14:20:03.748125" + "timestamp": "2024-12-05T12:10:52.622584" } } \ No newline at end of file diff --git a/test/nf-test/tiny/main.nf.test b/tests/nf-test/tiny/main.nf.test similarity index 100% rename from test/nf-test/tiny/main.nf.test rename to tests/nf-test/tiny/main.nf.test diff --git a/test/nf-test/tiny/main.nf.test.snap b/tests/nf-test/tiny/main.nf.test.snap similarity index 100% rename from test/nf-test/tiny/main.nf.test.snap rename to tests/nf-test/tiny/main.nf.test.snap diff --git a/workflows/edta.nf b/workflows/edta.nf index 05a6da3..a4799cf 100644 --- a/workflows/edta.nf +++ b/workflows/edta.nf @@ -9,11 +9,14 @@ include { REPEATMODELER_BUILDDATABASE } from '../modules/nf-core/repeatmod include { REPEATMODELER_REPEATMODELER } from '../modules/nf-core/repeatmodeler/repeatmodeler/main.nf' include { FASTA_HELITRONSCANNER_SCAN_DRAW } from '../subworkflows/gallvp/fasta_helitronscanner_scan_draw/main.nf' include { FORMAT_HELITRONSCANNER_OUT } from '../modules/local/format_helitronscanner_out/main.nf' +include { LTR_RETRIEVER_POSTPROCESS } from '../modules/local/ltr_retriever_postprocess/main.nf' include { softwareVersionsToYAML } from '../modules/local/utils/main.nf' workflow EDTA { + main: + // Versions channel ch_versions = Channel.empty() @@ -36,7 +39,6 @@ workflow EDTA { // MODULE: LTRHARVEST LTRHARVEST ( ch_sanitized_fasta ) - ch_ltrharvest_gff3 = LTRHARVEST.out.gff3 ch_ltrharvest_scn = LTRHARVEST.out.scn ch_versions = ch_versions.mix(LTRHARVEST.out.versions) @@ -44,7 +46,6 @@ workflow EDTA { // MODULE: LTRFINDER LTRFINDER { ch_sanitized_fasta } - ch_ltrfinder_gff3 = LTRFINDER.out.gff ch_ltrfinder_scn = LTRFINDER.out.scn ch_versions = ch_versions.mix(LTRFINDER.out.versions) @@ -62,17 +63,17 @@ workflow EDTA { ch_ltrretriever_inputs = ch_sanitized_fasta.join(ch_ltr_candidates) LTRRETRIEVER_LTRRETRIEVER ( - ch_ltrretriever_inputs.map { meta, fasta, ltr -> [ meta, fasta ] }, - ch_ltrretriever_inputs.map { meta, fasta, ltr -> ltr }, + ch_ltrretriever_inputs.map { meta, fasta, _ltr -> [ meta, fasta ] }, + ch_ltrretriever_inputs.map { _meta, _fasta, ltr -> ltr }, [], [], [] ) - ch_ltrretriever_log = LTRRETRIEVER_LTRRETRIEVER.out.log ch_pass_list = LTRRETRIEVER_LTRRETRIEVER.out.pass_list - ch_annotation_out = LTRRETRIEVER_LTRRETRIEVER.out.annotation_out + ch_pass_list_gff = LTRRETRIEVER_LTRRETRIEVER.out.pass_list_gff ch_annotation_gff = LTRRETRIEVER_LTRRETRIEVER.out.annotation_gff + ch_defalse = LTRRETRIEVER_LTRRETRIEVER.out.defalse ch_ltrlib = LTRRETRIEVER_LTRRETRIEVER.out.ltrlib ch_versions = ch_versions.mix(LTRRETRIEVER_LTRRETRIEVER.out.versions.first()) @@ -102,7 +103,7 @@ workflow EDTA { def size = fasta.size() def size_threshold = 100_000 // bytes -> bp - // TODO: Not the best way to set a size threshould + // TODO: Not the best way to set a size threshold // but it is simple // This is needed to avoid, // Error: Database genome is not large enough ( minimum 40000 bp ) to process with RepeatModeler. @@ -151,6 +152,32 @@ workflow EDTA { ch_versions = ch_versions.mix(FORMAT_HELITRONSCANNER_OUT.out.versions.first()) + // MODULE: LTR_RETRIEVER_POSTPROCESS + ltr_retriever_postprocess_inputs = ch_sanitized_fasta + | join(ch_pass_list) + | join(ch_pass_list_gff) + | join(ch_annotation_gff) + | join(ch_defalse) + | join(ch_ltrlib) + | multiMap { meta, fasta, pass, p_gff, a_gff, defalse, ltr -> + genome: [ meta, fasta ] + pass : pass + p_gff : p_gff + a_gff : a_gff + defalse : defalse + ltr : ltr + } + + LTR_RETRIEVER_POSTPROCESS ( + ltr_retriever_postprocess_inputs.genome, + ltr_retriever_postprocess_inputs.pass, + ltr_retriever_postprocess_inputs.p_gff, + ltr_retriever_postprocess_inputs.defalse, + ltr_retriever_postprocess_inputs.ltr, + ) + + ch_versions = ch_versions.mix(LTR_RETRIEVER_POSTPROCESS.out.versions.first()) + // Function: Save versions ch_versions = ch_versions @@ -168,4 +195,7 @@ workflow EDTA { cache: false ) + emit: + versions_yml = ch_versions_yml // [ software_versions.yml ] + } \ No newline at end of file From 82b34ac8ad50c459a0842616d6fb44ca31955731 Mon Sep 17 00:00:00 2001 From: Usman Rashid Date: Thu, 5 Dec 2024 15:52:06 +1300 Subject: [PATCH 31/49] Fixed singularity container --- modules/local/ltr_retriever_postprocess/main.nf | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/local/ltr_retriever_postprocess/main.nf b/modules/local/ltr_retriever_postprocess/main.nf index c957229..e679be6 100644 --- a/modules/local/ltr_retriever_postprocess/main.nf +++ b/modules/local/ltr_retriever_postprocess/main.nf @@ -4,7 +4,7 @@ process LTR_RETRIEVER_POSTPROCESS { 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/64/64d26063bedc2efcba20750a408a21f50907986d0d9aee685b03d7d05d3fbd8b/data': + 'https://community-cr-prod.seqera.io/docker/registry/v2/blobs/sha256/cb/cb105ba7d586ab31c1f39feb04c0255a39cc5a55ae7f6ea53f4bf76cdba8a3e5/data': 'community.wave.seqera.io/library/mdust_tesorter_trf_perl:3424609103d3b065' }" input: From c94149666446b326f40bdbdf1fbac42bbb1711f2 Mon Sep 17 00:00:00 2001 From: Usman Rashid Date: Thu, 5 Dec 2024 16:22:50 +1300 Subject: [PATCH 32/49] Fixed nextflow-setup version --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 20d4b62..d53140c 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -56,7 +56,7 @@ jobs: uses: actions/checkout@v4.2.1 - name: Install Nextflow - uses: nf-core/setup-nextflow@v2 + uses: nf-core/setup-nextflow@v2.0.0 with: version: "${{ matrix.NXF_VER }}" From 9816823772de4041706a1deb20f9e23d7fd12f29 Mon Sep 17 00:00:00 2001 From: Usman Rashid Date: Wed, 11 Dec 2024 14:31:17 +1300 Subject: [PATCH 33/49] Updated master from upstream --- bin/EDTA_raw.pl | 31 +-- bin/setup_LTR_retriever_postprocess.sh | 4 +- modules/gallvp/annosine/environment.yml | 6 + modules/gallvp/annosine/main.nf | 58 ++++++ modules/gallvp/annosine/meta.yml | 51 +++++ modules/gallvp/annosine/tests/main.nf.test | 61 ++++++ .../gallvp/annosine/tests/main.nf.test.snap | 72 +++++++ modules/gallvp/annosine/tests/nextflow.config | 5 + modules/gallvp/tirlearner/environment.yml | 5 + modules/gallvp/tirlearner/main.nf | 75 +++++++ modules/gallvp/tirlearner/meta.yml | 94 +++++++++ modules/gallvp/tirlearner/tests/main.nf.test | 91 +++++++++ .../gallvp/tirlearner/tests/main.nf.test.snap | 155 ++++++++++++++ modules/local/sanitize/main.nf | 38 ++++ modules/nf-core/cat/cat/environment.yml | 5 + modules/nf-core/cat/cat/main.nf | 78 +++++++ modules/nf-core/cat/cat/meta.yml | 43 ++++ modules/nf-core/cat/cat/tests/main.nf.test | 191 ++++++++++++++++++ .../nf-core/cat/cat/tests/main.nf.test.snap | 147 ++++++++++++++ .../cat/tests/nextflow_unzipped_zipped.config | 6 + .../cat/tests/nextflow_zipped_unzipped.config | 8 + modules/nf-core/cat/cat/tests/tags.yml | 2 + modules/nf-core/ltrfinder/environment.yml | 7 + modules/nf-core/ltrfinder/main.nf | 53 +++++ modules/nf-core/ltrfinder/meta.yml | 70 +++++++ modules/nf-core/ltrfinder/tests/main.nf.test | 92 +++++++++ .../nf-core/ltrfinder/tests/main.nf.test.snap | 149 ++++++++++++++ modules/nf-core/ltrharvest/environment.yml | 7 + modules/nf-core/ltrharvest/main.nf | 56 +++++ modules/nf-core/ltrharvest/meta.yml | 68 +++++++ modules/nf-core/ltrharvest/tests/main.nf.test | 89 ++++++++ .../ltrharvest/tests/main.nf.test.snap | 91 +++++++++ .../ltrretriever/ltrretriever/environment.yml | 7 + .../ltrretriever/ltrretriever/meta.yml | 119 +++++++++++ .../ltrretriever/tests/main.nf.test | 186 +++++++++++++++++ .../ltrretriever/tests/nextflow.config | 15 ++ .../builddatabase/environment.yml | 7 + .../repeatmodeler/builddatabase/main.nf | 50 +++++ .../repeatmodeler/builddatabase/meta.yml | 47 +++++ .../builddatabase/tests/main.nf.test | 60 ++++++ .../builddatabase/tests/main.nf.test.snap | 92 +++++++++ .../builddatabase/tests/tags.yml | 2 + .../repeatmodeler/environment.yml | 7 + .../repeatmodeler/repeatmodeler/main.nf | 54 +++++ .../repeatmodeler/repeatmodeler/meta.yml | 68 +++++++ .../repeatmodeler/tests/main.nf.test | 74 +++++++ .../repeatmodeler/tests/main.nf.test.snap | 113 +++++++++++ .../repeatmodeler/tests/tags.yml | 2 + 48 files changed, 2785 insertions(+), 26 deletions(-) create mode 100644 modules/gallvp/annosine/environment.yml create mode 100644 modules/gallvp/annosine/main.nf create mode 100644 modules/gallvp/annosine/meta.yml create mode 100644 modules/gallvp/annosine/tests/main.nf.test create mode 100644 modules/gallvp/annosine/tests/main.nf.test.snap create mode 100644 modules/gallvp/annosine/tests/nextflow.config create mode 100644 modules/gallvp/tirlearner/environment.yml create mode 100644 modules/gallvp/tirlearner/main.nf create mode 100644 modules/gallvp/tirlearner/meta.yml create mode 100644 modules/gallvp/tirlearner/tests/main.nf.test create mode 100644 modules/gallvp/tirlearner/tests/main.nf.test.snap create mode 100644 modules/local/sanitize/main.nf create mode 100644 modules/nf-core/cat/cat/environment.yml create mode 100644 modules/nf-core/cat/cat/main.nf create mode 100644 modules/nf-core/cat/cat/meta.yml create mode 100644 modules/nf-core/cat/cat/tests/main.nf.test create mode 100644 modules/nf-core/cat/cat/tests/main.nf.test.snap create mode 100644 modules/nf-core/cat/cat/tests/nextflow_unzipped_zipped.config create mode 100644 modules/nf-core/cat/cat/tests/nextflow_zipped_unzipped.config create mode 100644 modules/nf-core/cat/cat/tests/tags.yml create mode 100644 modules/nf-core/ltrfinder/environment.yml create mode 100644 modules/nf-core/ltrfinder/main.nf create mode 100644 modules/nf-core/ltrfinder/meta.yml create mode 100644 modules/nf-core/ltrfinder/tests/main.nf.test create mode 100644 modules/nf-core/ltrfinder/tests/main.nf.test.snap create mode 100644 modules/nf-core/ltrharvest/environment.yml create mode 100644 modules/nf-core/ltrharvest/main.nf create mode 100644 modules/nf-core/ltrharvest/meta.yml create mode 100644 modules/nf-core/ltrharvest/tests/main.nf.test create mode 100644 modules/nf-core/ltrharvest/tests/main.nf.test.snap create mode 100644 modules/nf-core/ltrretriever/ltrretriever/environment.yml create mode 100644 modules/nf-core/ltrretriever/ltrretriever/meta.yml create mode 100644 modules/nf-core/ltrretriever/ltrretriever/tests/main.nf.test create mode 100644 modules/nf-core/ltrretriever/ltrretriever/tests/nextflow.config create mode 100644 modules/nf-core/repeatmodeler/builddatabase/environment.yml create mode 100644 modules/nf-core/repeatmodeler/builddatabase/main.nf create mode 100644 modules/nf-core/repeatmodeler/builddatabase/meta.yml create mode 100644 modules/nf-core/repeatmodeler/builddatabase/tests/main.nf.test create mode 100644 modules/nf-core/repeatmodeler/builddatabase/tests/main.nf.test.snap create mode 100644 modules/nf-core/repeatmodeler/builddatabase/tests/tags.yml create mode 100644 modules/nf-core/repeatmodeler/repeatmodeler/environment.yml create mode 100644 modules/nf-core/repeatmodeler/repeatmodeler/main.nf create mode 100644 modules/nf-core/repeatmodeler/repeatmodeler/meta.yml create mode 100644 modules/nf-core/repeatmodeler/repeatmodeler/tests/main.nf.test create mode 100644 modules/nf-core/repeatmodeler/repeatmodeler/tests/main.nf.test.snap create mode 100644 modules/nf-core/repeatmodeler/repeatmodeler/tests/tags.yml diff --git a/bin/EDTA_raw.pl b/bin/EDTA_raw.pl index 31c3cf0..c011985 100755 --- a/bin/EDTA_raw.pl +++ b/bin/EDTA_raw.pl @@ -87,16 +87,11 @@ my $trf = ''; #path to trf my $GRF = ''; #path to GRF my $annosine = ""; #path to the AnnoSINE program - -# my $TIR_Learner = "$script_path/bin/TIR-Learner3/"; #tianyulu -# my $LTR_FINDER = "$script_path/bin/LTR_FINDER_parallel/LTR_FINDER_parallel"; #tianyulu -# my $LTR_HARVEST = "$script_path/bin/LTR_HARVEST_parallel/LTR_HARVEST_parallel"; #tianyulu -my $HelitronScanner_Runner = "$script_path/bin/run_helitron_scanner.sh"; - my $TIR_Learner = ""; #path to TIR-Learner3 program #tianyulu my $LTR_FINDER = ""; #path to LTR_FINDER_parallel program #tianyulu my $LTR_HARVEST = ""; #path to LTR_HARVEST_parallel program #tianyulu my $HelitronScanner = ""; #path to HelitronScanner program #tianyulu +my $HelitronScanner_Runner = "$script_path/bin/run_helitron_scanner.sh"; my $help = undef; @@ -160,9 +155,6 @@ print STDERR "$date\tEDTA_raw: Check dependencies, prepare working directories.\n\n"; # check files and dependencies -# die "The LTR_FINDER_parallel is not found in $LTR_FINDER!\n" unless -s $LTR_FINDER; #tianyulu -# die "The LTR_HARVEST_parallel is not found in $LTR_HARVEST!\n" unless -s $LTR_HARVEST; #tianyulu -# die "The TIR_Learner is not found in $TIR_Learner!\n" unless -s "$TIR_Learner/bin/main.py"; #tianyulu die "The script get_range.pl is not found in $get_range!\n" unless -s $get_range; die "The script rename_LTR_skim.pl is not found in $rename_LTR!\n" unless -s $rename_LTR; die "The script filter_gff3.pl is not found in $filter_gff!\n" unless -s $filter_gff; @@ -171,7 +163,6 @@ die "The script rename_tirlearner.pl is not found in $rename_tirlearner!\n" unless -s $rename_tirlearner; die "The script cleanup_tandem.pl is not found in $cleanup_tandem!\n" unless -s $cleanup_tandem; die "The script get_ext_seq.pl is not found in $get_ext_seq!\n" unless -s $get_ext_seq; -# die "The HelitronScanner is not found in $HelitronScanner!\n" unless -s $HelitronScanner; #tianyulu die "The script format_helitronscanner_out.pl is not found in $format_helitronscanner!\n" unless -s $format_helitronscanner; die "The script flanking_filter.pl is not found in $flank_filter!\n" unless -s $flank_filter; die "The script bed2gff.pl is not found in $bed2gff!\n" unless -s $bed2gff; @@ -190,7 +181,6 @@ $repeatmasker = dirname($repeatmasker) unless -d $repeatmasker; $repeatmasker="$repeatmasker/" if $repeatmasker ne '' and $repeatmasker !~ /\/$/; die "Error: RepeatMasker is not found in the RepeatMasker path $repeatmasker!\n" unless -X "${repeatmasker}RepeatMasker"; -# `cp $script_path/database/dummy060817.fa ./dummy060817.fa.$rand`; `cp \"$script_path/database/dummy060817.fa\" ./dummy060817.fa.$rand`; #tianyulu my $RM_test=`${repeatmasker}RepeatMasker -e ncbi -q -pa 1 -no_is -nolow dummy060817.fa.$rand -lib dummy060817.fa.$rand 2>/dev/null`; die "Error: The RMblast engine is not installed in RepeatMasker!\n" unless $RM_test=~s/done//gi; @@ -245,14 +235,6 @@ `${grfp}grf-main 2>/dev/null`; die "Error: The Generic Repeat Finder (GRF) is not found in the GRF path: $grfp\n" if $?==32256; -# # TIR-Learner3 -# chomp ($TIR_Learner = `which TIR-Learner 2>/dev/null`) if $TIR_Learner eq ''; -# $TIR_Learner =~ s/\n$//; -# my $tirp= dirname ($TIR_Learner); -# $tirp =~ s/\n$//; -# `${tirp}TIR-Learner 2>/dev/null`; -# die "Error: TIR-Learner3 is not found in the TIR-Learner path: $tirp\n" if $?==32256; - # TIR-Learner #tianyuLu # Remove any trailing whitespace $TIR_Learner =~ s/\s+$//; @@ -260,14 +242,10 @@ # Find TIR-Learner path and remove any trailing newline chomp ($TIR_Learner=`command -v TIR-Learner 2>/dev/null`); die "Error: TIR-Learner not installed!\n" if $TIR_Learner eq ""; - # $TIR_Learner =~ s/\s+$//; } else { # # Extract directory name from path if path is not a directory - # $TIR_Learner = dirname($TIR_Learner) unless -d $TIR_Learner; # If path is directory if (-d $TIR_Learner) { - # # Add trailing slash if path not empty string and not already end with slash - # $TIR_Learner .= "/" if $TIR_Learner ne "" and $TIR_Learner !~ /\/$/; # Add trailing slash if path not already end with slash $TIR_Learner .= "/" if $TIR_Learner !~ /\/$/; $TIR_Learner = "python3 $TIR_Learner/TIR-Learner.py"; @@ -715,7 +693,12 @@ print STDERR "$date\tIdentify Helitron candidates from scratch.\n\n"; # run HelitronScanner -`$HelitronScanner_Runner $genome $threads \"$HelitronScanner\"`; +if ($overwrite eq 0 and (-s "$genome.HelitronScanner.draw.hel.fa" and -s "$genome.HelitronScanner.draw.rc.hel.fa")){ +#cat $genome.HelitronScanner.draw.hel.fa $genome.HelitronScanner.draw.rc.hel.fa + print STDERR "$date\tExisting HelitronScanner result files $genome.HelitronScanner.draw.hel.fa $genome.HelitronScanner.draw.rc.hel.fa found!\n\t\tWill keep these files without rerunning HelitronScanner\n\t\tPlease specify --overwrite 1 if you want to rerun this module.\n\n"; + } else { + `$HelitronScanner_Runner $genome $threads \"$HelitronScanner\"`; + } # filter candidates based on repeatness of flanking regions `perl $format_helitronscanner -genome $genome -sitefilter 1 -minscore 12 -keepshorter 1 -extlen 30 -extout 1`; diff --git a/bin/setup_LTR_retriever_postprocess.sh b/bin/setup_LTR_retriever_postprocess.sh index 140b4b5..66e1cf5 100755 --- a/bin/setup_LTR_retriever_postprocess.sh +++ b/bin/setup_LTR_retriever_postprocess.sh @@ -19,8 +19,8 @@ my \$filter_gff = '$script_path/filter_gff3.pl'; EOF -sed -n 443,462p "$script_path/EDTA_raw.pl" \ +sed -n 421,440p "$script_path/EDTA_raw.pl" \ >> input/LTR_retriever_postprocess.pl -sed -n 466,472p "$script_path/EDTA_raw.pl" \ +sed -n 445,450p "$script_path/EDTA_raw.pl" \ >> input/LTR_retriever_postprocess.pl diff --git a/modules/gallvp/annosine/environment.yml b/modules/gallvp/annosine/environment.yml new file mode 100644 index 0000000..9aef5ec --- /dev/null +++ b/modules/gallvp/annosine/environment.yml @@ -0,0 +1,6 @@ +channels: + - conda-forge + - bioconda + +dependencies: + - "bioconda::annosine2=2.0.7" diff --git a/modules/gallvp/annosine/main.nf b/modules/gallvp/annosine/main.nf new file mode 100644 index 0000000..6408b28 --- /dev/null +++ b/modules/gallvp/annosine/main.nf @@ -0,0 +1,58 @@ +process ANNOSINE { + tag "$meta.id" + label 'process_high' + + conda "${moduleDir}/environment.yml" + container "${ workflow.containerEngine == 'singularity' && !task.ext.singularity_pull_docker_container ? + 'https://depot.galaxyproject.org/singularity/annosine2:2.0.7--pyh7cba7a3_0': + 'biocontainers/annosine2:2.0.7--pyh7cba7a3_0' }" + + input: + tuple val(meta), path(fasta) + val mode + + output: + tuple val(meta), path("*.fa") , emit: fa , optional: true + path "versions.yml" , emit: versions + + when: + task.ext.when == null || task.ext.when + + script: + def args = task.ext.args ?: '' + def prefix = task.ext.prefix ?: "${meta.id}" + def VERSION = '2.0.7' // WARN: Manually update when changing Bioconda assets + if ( "$fasta" == "${prefix}.fa" ) error "Input and output names are the same, use \"task.ext.prefix\" to disambiguate!" + """ + AnnoSINE_v2 \\ + $args \\ + --threads $task.cpus \\ + $mode \\ + $fasta \\ + $prefix + + mv \\ + $prefix/Seed_SINE.fa \\ + ${prefix}.fa \\ + || echo 'AnnoSINE_v2 did not find SINE sequences. See log for details!' + + cat <<-END_VERSIONS > versions.yml + "${task.process}": + annosine: $VERSION + END_VERSIONS + """ + + stub: + def args = task.ext.args ?: '' + def prefix = task.ext.prefix ?: "${meta.id}" + def VERSION = '2.0.7' // WARN: Manually update when changing Bioconda assets + if ( "$fasta" == "${prefix}.fa" ) error "Input and output names are the same, use \"task.ext.prefix\" to disambiguate!" + """ + touch ${prefix}.fa + + cat <<-END_VERSIONS > versions.yml + "${task.process}": + annosine: $VERSION + END_VERSIONS + """ +} diff --git a/modules/gallvp/annosine/meta.yml b/modules/gallvp/annosine/meta.yml new file mode 100644 index 0000000..ad205b5 --- /dev/null +++ b/modules/gallvp/annosine/meta.yml @@ -0,0 +1,51 @@ +# yaml-language-server: $schema=https://raw.githubusercontent.com/nf-core/modules/master/modules/meta-schema.json +name: "annosine" +description: Accelerating de novo SINE annotation in plant and animal genomes +keywords: + - genomics + - SINE + - annotation + - plant +tools: + - "annosine": + description: "AnnoSINE_v2 - SINE Annotation Tool for Plant and Animal Genomes" + homepage: "https://github.com/liaoherui/AnnoSINE_v2" + documentation: "https://github.com/liaoherui/AnnoSINE_v2" + tool_dev_url: "https://github.com/liaoherui/AnnoSINE_v2" + doi: "10.1101/2024.03.01.582874" + licence: ["MIT"] + identifier: biotools:annosine + +input: + - - meta: + type: map + description: | + Groovy Map containing sample information + e.g. `[ id:'sample1' ]` + - fasta: + type: file + description: Input genome assembly + pattern: "*.{fasta,fa,fsa}" + - - mode: + type: integer + description: Run mode +output: + - fa: + - meta: + type: map + description: | + Groovy Map containing sample information + e.g. `[ id:'sample1' ]` + - "*.fa": + type: file + description: Non-redundant SINE library with serial number + pattern: "*.fa" + - versions: + - versions.yml: + type: file + description: File containing software versions + pattern: "versions.yml" +authors: + - "@GallVp" +maintainers: + - "@GallVp" diff --git a/modules/gallvp/annosine/tests/main.nf.test b/modules/gallvp/annosine/tests/main.nf.test new file mode 100644 index 0000000..3bafb5d --- /dev/null +++ b/modules/gallvp/annosine/tests/main.nf.test @@ -0,0 +1,61 @@ +nextflow_process { + + name "Test Process ANNOSINE" + script "../main.nf" + process "ANNOSINE" + + tag "modules" + tag "modules_gallvp" + tag "annosine" + + test("actinidia_chinensis - fasta") { + + config './nextflow.config' + + when { + process { + """ + input[0] = [ + [ id:'test', single_end:false ], // meta map + file(params.modules_testdata_base_path + 'genomics/eukaryotes/actinidia_chinensis/genome/chr1/genome.fasta.gz', checkIfExists: true) + ] + input[1] = 3 + """ + } + } + + then { + assertAll( + { assert process.success }, + { assert snapshot(process.out).match() } + ) + } + + } + + test("actinidia_chinensis - fasta - stub") { + + options '-stub' + + when { + process { + """ + input[0] = [ + [ id:'test', single_end:false ], // meta map + file(params.modules_testdata_base_path + 'genomics/eukaryotes/actinidia_chinensis/genome/chr1/genome.fasta.gz', checkIfExists: true) + ] + input[1] = 3 + """ + } + } + + then { + assertAll( + { assert process.success }, + { assert snapshot(process.out).match() } + ) + } + + } + +} \ No newline at end of file diff --git a/modules/gallvp/annosine/tests/main.nf.test.snap b/modules/gallvp/annosine/tests/main.nf.test.snap new file mode 100644 index 0000000..4c87403 --- /dev/null +++ b/modules/gallvp/annosine/tests/main.nf.test.snap @@ -0,0 +1,72 @@ +{ + "actinidia_chinensis - fasta": { + "content": [ + { + "0": [ + [ + { + "id": "test", + "single_end": false + }, + "test.fa:md5,fc3bd39555133a1b50623bccc7d4cf9b" + ] + ], + "1": [ + "versions.yml:md5,e4a37dd3eccd5ff39c2262542db40e98" + ], + "fa": [ + [ + { + "id": "test", + "single_end": false + }, + "test.fa:md5,fc3bd39555133a1b50623bccc7d4cf9b" + ] + ], + "versions": [ + "versions.yml:md5,e4a37dd3eccd5ff39c2262542db40e98" + ] + } + ], + "meta": { + "nf-test": "0.8.4", + "nextflow": "24.04.2" + }, + "timestamp": "2024-07-02T15:32:32.714269" + }, + "actinidia_chinensis - fasta - stub": { + "content": [ + { + "0": [ + [ + { + "id": "test", + "single_end": false + }, + "test.fa:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "1": [ + "versions.yml:md5,e4a37dd3eccd5ff39c2262542db40e98" + ], + "fa": [ + [ + { + "id": "test", + "single_end": false + }, + "test.fa:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "versions": [ + "versions.yml:md5,e4a37dd3eccd5ff39c2262542db40e98" + ] + } + ], + "meta": { + "nf-test": "0.8.4", + "nextflow": "24.04.2" + }, + "timestamp": "2024-07-02T15:32:37.586287" + } +} \ No newline at end of file diff --git a/modules/gallvp/annosine/tests/nextflow.config b/modules/gallvp/annosine/tests/nextflow.config new file mode 100644 index 0000000..efdc449 --- /dev/null +++ b/modules/gallvp/annosine/tests/nextflow.config @@ -0,0 +1,5 @@ +process { + withName: ANNOSINE { + ext.args = '-a 2 -rpm 0 --copy_number 1 --shift 200' + } +} diff --git a/modules/gallvp/tirlearner/environment.yml b/modules/gallvp/tirlearner/environment.yml new file mode 100644 index 0000000..d60c615 --- /dev/null +++ b/modules/gallvp/tirlearner/environment.yml @@ -0,0 +1,5 @@ +channels: + - conda-forge + - bioconda +dependencies: + - "bioconda::tir-learner=3.0.3" diff --git a/modules/gallvp/tirlearner/main.nf b/modules/gallvp/tirlearner/main.nf new file mode 100644 index 0000000..c253245 --- /dev/null +++ b/modules/gallvp/tirlearner/main.nf @@ -0,0 +1,75 @@ +process TIRLEARNER { + tag "$meta.id" + label 'process_high' + + conda "${moduleDir}/environment.yml" + container "${ workflow.containerEngine == 'singularity' && !task.ext.singularity_pull_docker_container ? + 'https://depot.galaxyproject.org/singularity/tir-learner:3.0.3--hdfd78af_0': + 'biocontainers/tir-learner:3.0.3--hdfd78af_0' }" + + input: + tuple val(meta), path(fasta) + val species + + output: + tuple val(meta), path("${prefix}.log") , emit: log + tuple val(meta), path("${prefix}.fa") , emit: fasta , optional: true + tuple val(meta), path("${prefix}.gff3") , emit: gff , optional: true + tuple val(meta), path("${prefix}.filtered.fa") , emit: filtered_fasta , optional: true + tuple val(meta), path("${prefix}.filtered.gff3"), emit: filtered_gff , optional: true + path "versions.yml" , emit: versions + + when: + task.ext.when == null || task.ext.when + + script: + def args = task.ext.args ?: '' + prefix = task.ext.prefix ?: "${meta.id}" + def is_zipped = fasta.toString().endsWith('.gz') + def input_name = "$fasta" - ( is_zipped ? '.gz' : '' ) + def unzip_fasta = is_zipped ? "gzip -cdf $fasta > $input_name" : '' + + if ( "$input_name" == "${prefix}.fa" ) error "Input and output names are the same, use \"task.ext.prefix\" to disambiguate!" + """ + $unzip_fasta + + TIR-Learner \\ + -f $input_name \\ + -s $species \\ + -t $task.cpus \\ + -o $prefix \\ + $args \\ + &> >(tee "${prefix}.log" 2>&1) \\ + || echo "TIR-Learner failed! See ${prefix}.log" + + mv "${prefix}/TIR-Learner-Result/TIR-Learner_FinalAnn.fa" "${prefix}.fa" || echo "TIR-Learner failed to find TIRs. See ${prefix}.log" + mv "${prefix}/TIR-Learner-Result/TIR-Learner_FinalAnn.gff3" "${prefix}.gff3" || echo "TIR-Learner failed to find TIRs. See ${prefix}.log" + + mv "${prefix}/TIR-Learner-Result/TIR-Learner_FinalAnn_filter.fa" "${prefix}.filtered.fa" || echo "TIR-Learner failed to find TIRs. See ${prefix}.log" + mv "${prefix}/TIR-Learner-Result/TIR-Learner_FinalAnn_filter.gff3" "${prefix}.filtered.gff3" || echo "TIR-Learner failed to find TIRs. See ${prefix}.log" + + cat <<-END_VERSIONS > versions.yml + "${task.process}": + TIR-Learner: \$(TIR-Learner -v | head -n 1 | sed 's/TIR-Learner //') + END_VERSIONS + """ + + stub: + def args = task.ext.args ?: '' + prefix = task.ext.prefix ?: "${meta.id}" + if ( "$fasta" == "${prefix}.fa" ) error "Input and output names are the same, use \"task.ext.prefix\" to disambiguate!" + """ + touch ${prefix}.log + + touch "${prefix}.fa" + touch "${prefix}.gff3" + + touch "${prefix}.filtered.fa" + touch "${prefix}.filtered.gff3" + + cat <<-END_VERSIONS > versions.yml + "${task.process}": + TIR-Learner: \$(TIR-Learner -v | head -n 1 | sed 's/TIR-Learner //') + END_VERSIONS + """ +} diff --git a/modules/gallvp/tirlearner/meta.yml b/modules/gallvp/tirlearner/meta.yml new file mode 100644 index 0000000..ff2c9c2 --- /dev/null +++ b/modules/gallvp/tirlearner/meta.yml @@ -0,0 +1,94 @@ +# yaml-language-server: $schema=https://raw.githubusercontent.com/nf-core/modules/master/modules/meta-schema.json +name: "tirlearner" +description: | + Ensemble Method for TIR Transposable Element Annotation +keywords: + - genomics + - annotation + - repeat + - transposable +tools: + - "TIR-Learner": + description: TIR-Learner + homepage: "https://github.com/lutianyu2001/TIR-Learner" + documentation: "https://github.com/lutianyu2001/TIR-Learner" + tool_dev_url: "https://github.com/lutianyu2001/TIR-Learner" + doi: "10.1016/j.molp.2019.02.008" + licence: ["GPLv3"] + identifier: "" +input: + - - meta: + type: map + description: | + Groovy Map containing sample information + e.g. `[ id:'sample1' ]` + - fasta: + type: file + description: Genome sequences in fasta format + pattern: "*.{fsa,fa,fasta}" + - - species: + type: string + description: | + "maize", "rice" or "others" +output: + - log: + - meta: + type: map + description: | + Groovy Map containing sample information + e.g. `[ id:'sample1' ]` + - ${prefix}.log: + type: file + description: Log from TIR-Learner + pattern: "*.log" + - fasta: + - meta: + type: map + description: | + Groovy Map containing sample information + e.g. `[ id:'sample1' ]` + - ${prefix}.fa: + type: file + description: Putative TIRs in fasta format + pattern: "*.fa" + - gff: + - meta: + type: map + description: | + Groovy Map containing sample information + e.g. `[ id:'sample1' ]` + - ${prefix}.gff3: + type: file + description: Putative TIRs in GFF3 format + pattern: "*.gff3" + - filtered_fasta: + - meta: + type: map + description: | + Groovy Map containing sample information + e.g. `[ id:'sample1' ]` + - ${prefix}.filtered.fa: + type: file + description: Filtered TIRs in fasta format + pattern: "*.fa" + - filtered_gff: + - meta: + type: map + description: | + Groovy Map containing sample information + e.g. `[ id:'sample1' ]` + - ${prefix}.filtered.gff3: + type: file + description: Filtered TIRs in GFF3 format + pattern: "*.gff3" + - versions: + - versions.yml: + type: file + description: File containing software versions + pattern: "versions.yml" +authors: + - "@jguhlin" + - "@GallVp" +maintainers: + - "@jguhlin" + - "@GallVp" diff --git a/modules/gallvp/tirlearner/tests/main.nf.test b/modules/gallvp/tirlearner/tests/main.nf.test new file mode 100644 index 0000000..034fbac --- /dev/null +++ b/modules/gallvp/tirlearner/tests/main.nf.test @@ -0,0 +1,91 @@ +nextflow_process { + + name "Test Process TIRLEARNER" + script "../main.nf" + process "TIRLEARNER" + + tag "modules" + tag "modules_gallvp" + tag "tirlearner" + + + test("actinidia_chinensis-genome_1_fasta_gz-success") { + + when { + process { + """ + input[0] = [ + [ id: 'test' ], + file(params.modules_testdata_base_path + 'genomics/eukaryotes/actinidia_chinensis/genome/chr1/genome.fasta.gz', checkIfExists: true) + ] + input[1] = 'others' + """ + } + } + + then { + assertAll( + { assert process.success }, + { assert file(process.out.log[0][1]).text.contains('Module 4 Begin') }, + { assert snapshot( + process.out.fasta, + process.out.gff, + process.out.filtered_fasta, + process.out.filtered_gff, + process.out.versions + ).match() + } + ) + } + + } + + test("sarscov2-genome") { + + when { + process { + """ + input[0] = [ + [ id: 'test' ], + file(params.modules_testdata_base_path + 'genomics/sarscov2/genome/genome.fasta', checkIfExists: true) + ] + input[1] = 'others' + """ + } + } + + then { + assertAll( + { assert process.success }, + { assert file(process.out.log[0][1]).text.contains('ValueError: All objects passed were None') }, + { assert snapshot(process.out.versions).match() } + ) + } + + } + + test("stub") { + + options "-stub" + + when { + process { + """ + input[0] = [ + [ id: 'test' ], + file(params.modules_testdata_base_path + 'genomics/eukaryotes/actinidia_chinensis/genome/chr1/genome.fasta.gz', checkIfExists: true) + ] + input[1] = 'others' + """ + } + } + + then { + assertAll( + { assert process.success }, + { assert snapshot(process.out).match() } + ) + } + + } +} diff --git a/modules/gallvp/tirlearner/tests/main.nf.test.snap b/modules/gallvp/tirlearner/tests/main.nf.test.snap new file mode 100644 index 0000000..dd1a6d0 --- /dev/null +++ b/modules/gallvp/tirlearner/tests/main.nf.test.snap @@ -0,0 +1,155 @@ +{ + "sarscov2-genome": { + "content": [ + [ + "versions.yml:md5,9db74013377c09d8452530315592acdf" + ] + ], + "meta": { + "nf-test": "0.8.4", + "nextflow": "24.04.4" + }, + "timestamp": "2024-08-22T13:03:25.394783" + }, + "actinidia_chinensis-genome_1_fasta_gz-success": { + "content": [ + [ + [ + { + "id": "test" + }, + "test.fa:md5,65987decb67d1e3f95b993f86d14c0d0" + ] + ], + [ + [ + { + "id": "test" + }, + "test.gff3:md5,90bf040e7b29763351237207297dc801" + ] + ], + [ + [ + { + "id": "test" + }, + "test.filtered.fa:md5,65987decb67d1e3f95b993f86d14c0d0" + ] + ], + [ + [ + { + "id": "test" + }, + "test.filtered.gff3:md5,90bf040e7b29763351237207297dc801" + ] + ], + [ + "versions.yml:md5,9db74013377c09d8452530315592acdf" + ] + ], + "meta": { + "nf-test": "0.8.4", + "nextflow": "24.04.4" + }, + "timestamp": "2024-08-22T13:21:08.136874" + }, + "stub": { + "content": [ + { + "0": [ + [ + { + "id": "test" + }, + "test.log:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "1": [ + [ + { + "id": "test" + }, + "test.fa:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "2": [ + [ + { + "id": "test" + }, + "test.gff3:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "3": [ + [ + { + "id": "test" + }, + "test.filtered.fa:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "4": [ + [ + { + "id": "test" + }, + "test.filtered.gff3:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "5": [ + "versions.yml:md5,9db74013377c09d8452530315592acdf" + ], + "fasta": [ + [ + { + "id": "test" + }, + "test.fa:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "filtered_fasta": [ + [ + { + "id": "test" + }, + "test.filtered.fa:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "filtered_gff": [ + [ + { + "id": "test" + }, + "test.filtered.gff3:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "gff": [ + [ + { + "id": "test" + }, + "test.gff3:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "log": [ + [ + { + "id": "test" + }, + "test.log:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "versions": [ + "versions.yml:md5,9db74013377c09d8452530315592acdf" + ] + } + ], + "meta": { + "nf-test": "0.8.4", + "nextflow": "24.04.4" + }, + "timestamp": "2024-08-22T12:56:16.022839" + } +} \ No newline at end of file diff --git a/modules/local/sanitize/main.nf b/modules/local/sanitize/main.nf new file mode 100644 index 0000000..c1519af --- /dev/null +++ b/modules/local/sanitize/main.nf @@ -0,0 +1,38 @@ +// Rename FASTA headers (just makes everything easier later) + +process SANITIZE_HEADERS { + tag "$meta.id" + label 'process_single' + + // Eventually port fffx (pronounced f3x) to bioconda + // conda "${moduleDir}/environment.yml" + // container "docker.io/gallvp/edta-components:v0.1" + + container "${ workflow.containerEngine == 'singularity' || workflow.containerEngine == 'apptainer' + ? 'https://depot.galaxyproject.org/singularity/ubuntu:20.04' + : 'nf-core/ubuntu:20.04' }" + + input: + tuple val(meta), path(fasta) + + output: + tuple val(meta), path('*.sanitized.fasta'), emit: fasta + tuple val(meta), path('*.sanitized.translation_table.tsv'), emit: translation_table + eval('fffx --version'), topic: versions + + + when: + task.ext.when == null || task.ext.when + + script: + """ + fffx length-filter ${fasta} filtered.fa 1000 + fffx sanitize filtered.fa ${fasta.baseName}.sanitized + """ + + stub: + """ + touch ${fasta.baseName}.sanitized.fasta + touch ${fasta.baseName}.sanitized.translation_table.tsv + """ +} \ No newline at end of file diff --git a/modules/nf-core/cat/cat/environment.yml b/modules/nf-core/cat/cat/environment.yml new file mode 100644 index 0000000..9b01c86 --- /dev/null +++ b/modules/nf-core/cat/cat/environment.yml @@ -0,0 +1,5 @@ +channels: + - conda-forge + - bioconda +dependencies: + - conda-forge::pigz=2.3.4 diff --git a/modules/nf-core/cat/cat/main.nf b/modules/nf-core/cat/cat/main.nf new file mode 100644 index 0000000..2862c64 --- /dev/null +++ b/modules/nf-core/cat/cat/main.nf @@ -0,0 +1,78 @@ +process CAT_CAT { + tag "$meta.id" + label 'process_low' + + conda "${moduleDir}/environment.yml" + container "${ workflow.containerEngine == 'singularity' && !task.ext.singularity_pull_docker_container ? + 'https://depot.galaxyproject.org/singularity/pigz:2.3.4' : + 'biocontainers/pigz:2.3.4' }" + + input: + tuple val(meta), path(files_in) + + output: + tuple val(meta), path("${prefix}"), emit: file_out + path "versions.yml" , emit: versions + + when: + task.ext.when == null || task.ext.when + + script: + def args = task.ext.args ?: '' + def args2 = task.ext.args2 ?: '' + def file_list = files_in.collect { it.toString() } + + // choose appropriate concatenation tool depending on input and output format + + // | input | output | command1 | command2 | + // |-----------|------------|----------|----------| + // | gzipped | gzipped | cat | | + // | ungzipped | ungzipped | cat | | + // | gzipped | ungzipped | zcat | | + // | ungzipped | gzipped | cat | pigz | + + // Use input file ending as default + prefix = task.ext.prefix ?: "${meta.id}${getFileSuffix(file_list[0])}" + out_zip = prefix.endsWith('.gz') + in_zip = file_list[0].endsWith('.gz') + command1 = (in_zip && !out_zip) ? 'zcat' : 'cat' + command2 = (!in_zip && out_zip) ? "| pigz -c -p $task.cpus $args2" : '' + if(file_list.contains(prefix.trim())) { + error "The name of the input file can't be the same as for the output prefix in the " + + "module CAT_CAT (currently `$prefix`). Please choose a different one." + } + """ + $command1 \\ + $args \\ + ${file_list.join(' ')} \\ + $command2 \\ + > ${prefix} + + cat <<-END_VERSIONS > versions.yml + "${task.process}": + pigz: \$( pigz --version 2>&1 | sed 's/pigz //g' ) + END_VERSIONS + """ + + stub: + def file_list = files_in.collect { it.toString() } + prefix = task.ext.prefix ?: "${meta.id}${file_list[0].substring(file_list[0].lastIndexOf('.'))}" + if(file_list.contains(prefix.trim())) { + error "The name of the input file can't be the same as for the output prefix in the " + + "module CAT_CAT (currently `$prefix`). Please choose a different one." + } + """ + touch $prefix + + cat <<-END_VERSIONS > versions.yml + "${task.process}": + pigz: \$( pigz --version 2>&1 | sed 's/pigz //g' ) + END_VERSIONS + """ +} + +// for .gz files also include the second to last extension if it is present. E.g., .fasta.gz +def getFileSuffix(filename) { + def match = filename =~ /^.*?((\.\w{1,5})?(\.\w{1,5}\.gz$))/ + return match ? match[0][1] : filename.substring(filename.lastIndexOf('.')) +} diff --git a/modules/nf-core/cat/cat/meta.yml b/modules/nf-core/cat/cat/meta.yml new file mode 100644 index 0000000..81778a0 --- /dev/null +++ b/modules/nf-core/cat/cat/meta.yml @@ -0,0 +1,43 @@ +name: cat_cat +description: A module for concatenation of gzipped or uncompressed files +keywords: + - concatenate + - gzip + - cat +tools: + - cat: + description: Just concatenation + documentation: https://man7.org/linux/man-pages/man1/cat.1.html + licence: ["GPL-3.0-or-later"] + identifier: "" +input: + - - meta: + type: map + description: | + Groovy Map containing sample information + e.g. [ id:'test', single_end:false ] + - files_in: + type: file + description: List of compressed / uncompressed files + pattern: "*" +output: + - file_out: + - meta: + type: file + description: Concatenated file. Will be gzipped if file_out ends with ".gz" + pattern: "${file_out}" + - ${prefix}: + type: file + description: Concatenated file. Will be gzipped if file_out ends with ".gz" + pattern: "${file_out}" + - versions: + - versions.yml: + type: file + description: File containing software versions + pattern: "versions.yml" +authors: + - "@erikrikarddaniel" + - "@FriederikeHanssen" +maintainers: + - "@erikrikarddaniel" + - "@FriederikeHanssen" diff --git a/modules/nf-core/cat/cat/tests/main.nf.test b/modules/nf-core/cat/cat/tests/main.nf.test new file mode 100644 index 0000000..9cb1617 --- /dev/null +++ b/modules/nf-core/cat/cat/tests/main.nf.test @@ -0,0 +1,191 @@ +nextflow_process { + + name "Test Process CAT_CAT" + script "../main.nf" + process "CAT_CAT" + tag "modules" + tag "modules_nfcore" + tag "cat" + tag "cat/cat" + + test("test_cat_name_conflict") { + when { + params { + outdir = "${outputDir}" + } + process { + """ + input[0] = + [ + [ id:'genome', single_end:true ], + [ + file(params.modules_testdata_base_path + 'genomics/sarscov2/genome/genome.fasta', checkIfExists: true), + file(params.modules_testdata_base_path + 'genomics/sarscov2/genome/genome.sizes', checkIfExists: true) + ] + ] + """ + } + } + then { + assertAll( + { assert !process.success }, + { assert process.stdout.toString().contains("The name of the input file can't be the same as for the output prefix") }, + { assert snapshot(process.out.versions).match() } + ) + } + } + + test("test_cat_unzipped_unzipped") { + when { + params { + outdir = "${outputDir}" + } + process { + """ + input[0] = + [ + [ id:'test', single_end:true ], + [ + file(params.modules_testdata_base_path + 'genomics/sarscov2/genome/genome.fasta', checkIfExists: true), + file(params.modules_testdata_base_path + 'genomics/sarscov2/genome/genome.sizes', checkIfExists: true) + ] + ] + """ + } + } + then { + assertAll( + { assert process.success }, + { assert snapshot(process.out).match() } + ) + } + } + + + test("test_cat_zipped_zipped") { + when { + params { + outdir = "${outputDir}" + } + process { + """ + input[0] = + [ + [ id:'test', single_end:true ], + [ + file(params.modules_testdata_base_path + 'genomics/sarscov2/genome/genome.gff3.gz', checkIfExists: true), + file(params.modules_testdata_base_path + 'genomics/sarscov2/genome/alignment/last/contigs.genome.maf.gz', checkIfExists: true) + ] + ] + """ + } + } + then { + def lines = path(process.out.file_out.get(0).get(1)).linesGzip + assertAll( + { assert process.success }, + { assert snapshot( + lines[0..5], + lines.size(), + process.out.versions + ).match() + } + ) + } + } + + test("test_cat_zipped_unzipped") { + config './nextflow_zipped_unzipped.config' + + when { + params { + outdir = "${outputDir}" + } + process { + """ + input[0] = + [ + [ id:'test', single_end:true ], + [ + file(params.modules_testdata_base_path + 'genomics/sarscov2/genome/genome.gff3.gz', checkIfExists: true), + file(params.modules_testdata_base_path + 'genomics/sarscov2/genome/alignment/last/contigs.genome.maf.gz', checkIfExists: true) + ] + ] + """ + } + } + + then { + assertAll( + { assert process.success }, + { assert snapshot(process.out).match() } + ) + } + + } + + test("test_cat_unzipped_zipped") { + config './nextflow_unzipped_zipped.config' + when { + params { + outdir = "${outputDir}" + } + process { + """ + input[0] = + [ + [ id:'test', single_end:true ], + [ + file(params.modules_testdata_base_path + 'genomics/sarscov2/genome/genome.fasta', checkIfExists: true), + file(params.modules_testdata_base_path + 'genomics/sarscov2/genome/genome.sizes', checkIfExists: true) + ] + ] + """ + } + } + then { + def lines = path(process.out.file_out.get(0).get(1)).linesGzip + assertAll( + { assert process.success }, + { assert snapshot( + lines[0..5], + lines.size(), + process.out.versions + ).match() + } + ) + } + } + + test("test_cat_one_file_unzipped_zipped") { + config './nextflow_unzipped_zipped.config' + when { + params { + outdir = "${outputDir}" + } + process { + """ + input[0] = + [ + [ id:'test', single_end:true ], + [ + file(params.modules_testdata_base_path + 'genomics/sarscov2/genome/genome.fasta', checkIfExists: true) + ] + ] + """ + } + } + then { + def lines = path(process.out.file_out.get(0).get(1)).linesGzip + assertAll( + { assert process.success }, + { assert snapshot( + lines[0..5], + lines.size(), + process.out.versions + ).match() + } + ) + } + } +} diff --git a/modules/nf-core/cat/cat/tests/main.nf.test.snap b/modules/nf-core/cat/cat/tests/main.nf.test.snap new file mode 100644 index 0000000..b7623ee --- /dev/null +++ b/modules/nf-core/cat/cat/tests/main.nf.test.snap @@ -0,0 +1,147 @@ +{ + "test_cat_unzipped_unzipped": { + "content": [ + { + "0": [ + [ + { + "id": "test", + "single_end": true + }, + "test.fasta:md5,f44b33a0e441ad58b2d3700270e2dbe2" + ] + ], + "1": [ + "versions.yml:md5,115ed6177ebcff24eb99d503fa5ef894" + ], + "file_out": [ + [ + { + "id": "test", + "single_end": true + }, + "test.fasta:md5,f44b33a0e441ad58b2d3700270e2dbe2" + ] + ], + "versions": [ + "versions.yml:md5,115ed6177ebcff24eb99d503fa5ef894" + ] + } + ], + "meta": { + "nf-test": "0.8.4", + "nextflow": "24.04.3" + }, + "timestamp": "2023-10-16T14:32:18.500464399" + }, + "test_cat_zipped_unzipped": { + "content": [ + { + "0": [ + [ + { + "id": "test", + "single_end": true + }, + "cat.txt:md5,c439d3b60e7bc03e8802a451a0d9a5d9" + ] + ], + "1": [ + "versions.yml:md5,115ed6177ebcff24eb99d503fa5ef894" + ], + "file_out": [ + [ + { + "id": "test", + "single_end": true + }, + "cat.txt:md5,c439d3b60e7bc03e8802a451a0d9a5d9" + ] + ], + "versions": [ + "versions.yml:md5,115ed6177ebcff24eb99d503fa5ef894" + ] + } + ], + "meta": { + "nf-test": "0.8.4", + "nextflow": "24.04.3" + }, + "timestamp": "2023-10-16T14:32:49.642741302" + }, + "test_cat_zipped_zipped": { + "content": [ + [ + "MT192765.1\tGenbank\ttranscript\t259\t29667\t.\t+\t.\tID=unknown_transcript_1;geneID=orf1ab;gene_name=orf1ab", + "MT192765.1\tGenbank\tgene\t259\t21548\t.\t+\t.\tParent=unknown_transcript_1", + "MT192765.1\tGenbank\tCDS\t259\t13461\t.\t+\t0\tParent=unknown_transcript_1;exception=\"ribosomal slippage\";gbkey=CDS;gene=orf1ab;note=\"pp1ab;translated=by -1 ribosomal frameshift\";product=\"orf1ab polyprotein\";protein_id=QIK50426.1", + "MT192765.1\tGenbank\tCDS\t13461\t21548\t.\t+\t0\tParent=unknown_transcript_1;exception=\"ribosomal slippage\";gbkey=CDS;gene=orf1ab;note=\"pp1ab;translated=by -1 ribosomal frameshift\";product=\"orf1ab polyprotein\";protein_id=QIK50426.1", + "MT192765.1\tGenbank\tCDS\t21556\t25377\t.\t+\t0\tParent=unknown_transcript_1;gbkey=CDS;gene=S;note=\"structural protein\";product=\"surface glycoprotein\";protein_id=QIK50427.1", + "MT192765.1\tGenbank\tgene\t21556\t25377\t.\t+\t.\tParent=unknown_transcript_1" + ], + 78, + [ + "versions.yml:md5,115ed6177ebcff24eb99d503fa5ef894" + ] + ], + "meta": { + "nf-test": "0.8.4", + "nextflow": "24.04.3" + }, + "timestamp": "2024-07-22T11:51:46.802978" + }, + "test_cat_name_conflict": { + "content": [ + [ + + ] + ], + "meta": { + "nf-test": "0.8.4", + "nextflow": "24.04.3" + }, + "timestamp": "2024-07-22T11:51:29.45394" + }, + "test_cat_one_file_unzipped_zipped": { + "content": [ + [ + ">MT192765.1 Severe acute respiratory syndrome coronavirus 2 isolate SARS-CoV-2/human/USA/PC00101P/2020, complete genome", + "GTTTATACCTTCCCAGGTAACAAACCAACCAACTTTCGATCTCTTGTAGATCTGTTCTCTAAACGAACTTTAAAATCTGT", + "GTGGCTGTCACTCGGCTGCATGCTTAGTGCACTCACGCAGTATAATTAATAACTAATTACTGTCGTTGACAGGACACGAG", + "TAACTCGTCTATCTTCTGCAGGCTGCTTACGGTTTCGTCCGTGTTGCAGCCGATCATCAGCACATCTAGGTTTTGTCCGG", + "GTGTGACCGAAAGGTAAGATGGAGAGCCTTGTCCCTGGTTTCAACGAGAAAACACACGTCCAACTCAGTTTGCCTGTTTT", + "ACAGGTTCGCGACGTGCTCGTACGTGGCTTTGGAGACTCCGTGGAGGAGGTCTTATCAGAGGCACGTCAACATCTTAAAG" + ], + 374, + [ + "versions.yml:md5,115ed6177ebcff24eb99d503fa5ef894" + ] + ], + "meta": { + "nf-test": "0.8.4", + "nextflow": "24.04.3" + }, + "timestamp": "2024-07-22T11:52:02.774016" + }, + "test_cat_unzipped_zipped": { + "content": [ + [ + ">MT192765.1 Severe acute respiratory syndrome coronavirus 2 isolate SARS-CoV-2/human/USA/PC00101P/2020, complete genome", + "GTTTATACCTTCCCAGGTAACAAACCAACCAACTTTCGATCTCTTGTAGATCTGTTCTCTAAACGAACTTTAAAATCTGT", + "GTGGCTGTCACTCGGCTGCATGCTTAGTGCACTCACGCAGTATAATTAATAACTAATTACTGTCGTTGACAGGACACGAG", + "TAACTCGTCTATCTTCTGCAGGCTGCTTACGGTTTCGTCCGTGTTGCAGCCGATCATCAGCACATCTAGGTTTTGTCCGG", + "GTGTGACCGAAAGGTAAGATGGAGAGCCTTGTCCCTGGTTTCAACGAGAAAACACACGTCCAACTCAGTTTGCCTGTTTT", + "ACAGGTTCGCGACGTGCTCGTACGTGGCTTTGGAGACTCCGTGGAGGAGGTCTTATCAGAGGCACGTCAACATCTTAAAG" + ], + 375, + [ + "versions.yml:md5,115ed6177ebcff24eb99d503fa5ef894" + ] + ], + "meta": { + "nf-test": "0.8.4", + "nextflow": "24.04.3" + }, + "timestamp": "2024-07-22T11:51:57.581523" + } +} \ No newline at end of file diff --git a/modules/nf-core/cat/cat/tests/nextflow_unzipped_zipped.config b/modules/nf-core/cat/cat/tests/nextflow_unzipped_zipped.config new file mode 100644 index 0000000..ec26b0f --- /dev/null +++ b/modules/nf-core/cat/cat/tests/nextflow_unzipped_zipped.config @@ -0,0 +1,6 @@ + +process { + withName: CAT_CAT { + ext.prefix = 'cat.txt.gz' + } +} diff --git a/modules/nf-core/cat/cat/tests/nextflow_zipped_unzipped.config b/modules/nf-core/cat/cat/tests/nextflow_zipped_unzipped.config new file mode 100644 index 0000000..fbc7978 --- /dev/null +++ b/modules/nf-core/cat/cat/tests/nextflow_zipped_unzipped.config @@ -0,0 +1,8 @@ + +process { + + withName: CAT_CAT { + ext.prefix = 'cat.txt' + } + +} diff --git a/modules/nf-core/cat/cat/tests/tags.yml b/modules/nf-core/cat/cat/tests/tags.yml new file mode 100644 index 0000000..37b578f --- /dev/null +++ b/modules/nf-core/cat/cat/tests/tags.yml @@ -0,0 +1,2 @@ +cat/cat: + - modules/nf-core/cat/cat/** diff --git a/modules/nf-core/ltrfinder/environment.yml b/modules/nf-core/ltrfinder/environment.yml new file mode 100644 index 0000000..2932d5c --- /dev/null +++ b/modules/nf-core/ltrfinder/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::ltr_finder_parallel=1.1" diff --git a/modules/nf-core/ltrfinder/main.nf b/modules/nf-core/ltrfinder/main.nf new file mode 100644 index 0000000..3e59e3c --- /dev/null +++ b/modules/nf-core/ltrfinder/main.nf @@ -0,0 +1,53 @@ +process LTRFINDER { + tag "$meta.id" + label 'process_high' + + conda "${moduleDir}/environment.yml" + container "${ workflow.containerEngine == 'singularity' && !task.ext.singularity_pull_docker_container ? + 'https://depot.galaxyproject.org/singularity/ltr_finder_parallel:1.1--hdfd78af_0': + 'biocontainers/ltr_finder_parallel:1.1--hdfd78af_0' }" + + input: + tuple val(meta), path(fasta) + + output: + tuple val(meta), path("*.scn") , emit: scn + tuple val(meta), path("*.gff3") , emit: gff + path "versions.yml" , emit: versions + + when: + task.ext.when == null || task.ext.when + + script: + def args = task.ext.args ?: '' + def prefix = task.ext.prefix ?: "${meta.id}" + """ + LTR_FINDER_parallel \\ + -seq $fasta \\ + -threads $task.cpus \\ + $args + + mv "${fasta}.finder.combine.scn" "${prefix}.scn" + mv "${fasta}.finder.combine.gff3" "${prefix}.gff3" + + cat <<-END_VERSIONS > versions.yml + "${task.process}": + LTR_FINDER_parallel: \$(LTR_FINDER_parallel -h | grep 'Version:' | sed 's/Version: //') + ltr_finder: \$(ltr_finder -h 2>&1 | grep 'ltr_finder' | sed 's/ltr_finder //') + END_VERSIONS + """ + + stub: + def args = task.ext.args ?: '' + def prefix = task.ext.prefix ?: "${meta.id}" + """ + touch "${prefix}.scn" + touch "${prefix}.gff3" + + cat <<-END_VERSIONS > versions.yml + "${task.process}": + LTR_FINDER_parallel: \$(LTR_FINDER_parallel -h | grep 'Version:' | sed 's/Version: //') + ltr_finder: \$(ltr_finder -h 2>&1 | grep 'ltr_finder' | sed 's/ltr_finder //') + END_VERSIONS + """ +} diff --git a/modules/nf-core/ltrfinder/meta.yml b/modules/nf-core/ltrfinder/meta.yml new file mode 100644 index 0000000..547fb67 --- /dev/null +++ b/modules/nf-core/ltrfinder/meta.yml @@ -0,0 +1,70 @@ +# yaml-language-server: $schema=https://raw.githubusercontent.com/nf-core/modules/master/modules/meta-schema.json +name: "ltrfinder" +description: | + Finds full-length LTR retrotranspsons in genome sequences using the + parallel version of LTR_Finder +keywords: + - genomics + - annotation + - parallel + - repeat + - long terminal retrotransposon + - retrotransposon +tools: + - "LTR_FINDER_parallel": + description: A Perl wrapper for LTR_FINDER + homepage: "https://github.com/oushujun/LTR_FINDER_parallel" + documentation: "https://github.com/oushujun/LTR_FINDER_parallel" + tool_dev_url: "https://github.com/oushujun/LTR_FINDER_parallel" + doi: "10.1186/s13100-019-0193-0" + licence: ["MIT"] + identifier: "" + - "LTR_Finder": + description: An efficient program for finding full-length LTR retrotranspsons + in genome sequences + homepage: "https://github.com/xzhub/LTR_Finder" + documentation: "https://github.com/xzhub/LTR_Finder" + tool_dev_url: "https://github.com/xzhub/LTR_Finder" + doi: "10.1093/nar/gkm286" + licence: ["MIT"] + identifier: "" +input: + - - meta: + type: map + description: | + Groovy Map containing sample information + e.g. `[ id:'sample1' ]` + - fasta: + type: file + description: Genome sequences in fasta format + pattern: "*.{fsa,fa,fasta}" +output: + - scn: + - meta: + type: map + description: | + Groovy Map containing sample information + e.g. `[ id:'sample1' ]` + - "*.scn": + type: file + description: Annotation in LTRharvest or LTR_FINDER format + pattern: "*.scn" + - gff: + - meta: + type: map + description: | + Groovy Map containing sample information + e.g. `[ id:'sample1' ]` + - "*.gff3": + type: file + description: Annotation in gff3 format + pattern: "*.gff3" + - versions: + - versions.yml: + type: file + description: File containing software versions + pattern: "versions.yml" +authors: + - "@GallVp" +maintainers: + - "@GallVp" diff --git a/modules/nf-core/ltrfinder/tests/main.nf.test b/modules/nf-core/ltrfinder/tests/main.nf.test new file mode 100644 index 0000000..dc8c803 --- /dev/null +++ b/modules/nf-core/ltrfinder/tests/main.nf.test @@ -0,0 +1,92 @@ +nextflow_process { + + name "Test Process LTRFINDER" + script "../main.nf" + process "LTRFINDER" + + tag "modules" + tag "modules_nfcore" + tag "ltrfinder" + tag "gunzip" + + test("actinidia_chinensis-genome_21_fasta_gz-success") { + + setup { + run('GUNZIP') { + script "../../gunzip/main.nf" + + process { + """ + input[0] = [ + [ id:'test' ], // meta map + file(params.modules_testdata_base_path + 'genomics/eukaryotes/actinidia_chinensis/genome/chr1/genome.fasta.gz', checkIfExists: true) + ] + """ + } + } + } + + when { + process { + """ + input[0] = GUNZIP.out.gunzip + """ + } + } + + then { + assertAll( + { assert process.success }, + { assert snapshot(process.out).match() } + ) + } + + } + + test("sarscov2-genome_fasta-no_ltr") { + + when { + process { + """ + input[0] = [ + [ id:'test' ], + file(params.modules_testdata_base_path + 'genomics/sarscov2/genome/genome.fasta', checkIfExists: true) + ] + """ + } + } + + then { + assertAll( + { assert process.success }, + { assert snapshot(process.out).match() } + ) + } + + } + + test("stub") { + + options "-stub" + + when { + process { + """ + input[0] = [ + [ id:'test' ], // meta map + file(params.modules_testdata_base_path + 'genomics/eukaryotes/actinidia_chinensis/genome/chr1/genome.fasta.gz', checkIfExists: true) + ] + """ + } + } + + then { + assertAll( + { assert process.success }, + { assert snapshot(process.out).match() } + ) + } + + } + +} diff --git a/modules/nf-core/ltrfinder/tests/main.nf.test.snap b/modules/nf-core/ltrfinder/tests/main.nf.test.snap new file mode 100644 index 0000000..0f1790f --- /dev/null +++ b/modules/nf-core/ltrfinder/tests/main.nf.test.snap @@ -0,0 +1,149 @@ +{ + "actinidia_chinensis-genome_21_fasta_gz-success": { + "content": [ + { + "0": [ + [ + { + "id": "test" + }, + "test.scn:md5,006193c9eaf3f552ccb0369f159e7660" + ] + ], + "1": [ + [ + { + "id": "test" + }, + "test.gff3:md5,96e5305163939e4381e1b94b660dc0a2" + ] + ], + "2": [ + "versions.yml:md5,7b24225b810fa88cfb2a887de11be333" + ], + "gff": [ + [ + { + "id": "test" + }, + "test.gff3:md5,96e5305163939e4381e1b94b660dc0a2" + ] + ], + "scn": [ + [ + { + "id": "test" + }, + "test.scn:md5,006193c9eaf3f552ccb0369f159e7660" + ] + ], + "versions": [ + "versions.yml:md5,7b24225b810fa88cfb2a887de11be333" + ] + } + ], + "meta": { + "nf-test": "0.8.4", + "nextflow": "23.10.1" + }, + "timestamp": "2024-02-16T09:14:38.509965" + }, + "sarscov2-genome_fasta-no_ltr": { + "content": [ + { + "0": [ + [ + { + "id": "test" + }, + "test.scn:md5,2ce449dff751e59dbc292b6888491954" + ] + ], + "1": [ + [ + { + "id": "test" + }, + "test.gff3:md5,bddeb04277af08b5850e64708e8af02a" + ] + ], + "2": [ + "versions.yml:md5,7b24225b810fa88cfb2a887de11be333" + ], + "gff": [ + [ + { + "id": "test" + }, + "test.gff3:md5,bddeb04277af08b5850e64708e8af02a" + ] + ], + "scn": [ + [ + { + "id": "test" + }, + "test.scn:md5,2ce449dff751e59dbc292b6888491954" + ] + ], + "versions": [ + "versions.yml:md5,7b24225b810fa88cfb2a887de11be333" + ] + } + ], + "meta": { + "nf-test": "0.8.4", + "nextflow": "24.04.2" + }, + "timestamp": "2024-07-16T13:03:03.505263" + }, + "stub": { + "content": [ + { + "0": [ + [ + { + "id": "test" + }, + "test.scn:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "1": [ + [ + { + "id": "test" + }, + "test.gff3:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "2": [ + "versions.yml:md5,7b24225b810fa88cfb2a887de11be333" + ], + "gff": [ + [ + { + "id": "test" + }, + "test.gff3:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "scn": [ + [ + { + "id": "test" + }, + "test.scn:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "versions": [ + "versions.yml:md5,7b24225b810fa88cfb2a887de11be333" + ] + } + ], + "meta": { + "nf-test": "0.8.4", + "nextflow": "23.10.1" + }, + "timestamp": "2024-02-16T09:14:43.054758" + } +} \ No newline at end of file diff --git a/modules/nf-core/ltrharvest/environment.yml b/modules/nf-core/ltrharvest/environment.yml new file mode 100644 index 0000000..c6cac36 --- /dev/null +++ b/modules/nf-core/ltrharvest/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::ltr_harvest_parallel=1.1" diff --git a/modules/nf-core/ltrharvest/main.nf b/modules/nf-core/ltrharvest/main.nf new file mode 100644 index 0000000..1e5e06d --- /dev/null +++ b/modules/nf-core/ltrharvest/main.nf @@ -0,0 +1,56 @@ +process LTRHARVEST { + tag "$meta.id" + label 'process_medium' + + conda "${moduleDir}/environment.yml" + container "${ workflow.containerEngine == 'singularity' && !task.ext.singularity_pull_docker_container ? + 'https://depot.galaxyproject.org/singularity/ltr_harvest_parallel:1.1--hdfd78af_0': + 'biocontainers/ltr_harvest_parallel:1.1--hdfd78af_0' }" + + input: + tuple val(meta), path(fasta) + + output: + tuple val(meta), path("*.gff3") , emit: gff3 + tuple val(meta), path("*.scn") , emit: scn + path "versions.yml" , emit: versions + + when: + task.ext.when == null || task.ext.when + + script: + def args = task.ext.args ?: '' + def prefix = task.ext.prefix ?: "${meta.id}" + """ + LTR_HARVEST_parallel \\ + -seq $fasta \\ + $args \\ + -threads $task.cpus + + mv "${fasta}.harvest.combine.gff3" \\ + "${prefix}.gff3" + + mv "${fasta}.harvest.combine.scn" \\ + "${prefix}.scn" + + cat <<-END_VERSIONS > versions.yml + "${task.process}": + LTR_HARVEST_parallel: \$(LTR_HARVEST_parallel -h | sed -n '/Version/s/Version: //p') + genometools: \$(gt --version | sed '1!d ; s/gt (GenomeTools) //') + END_VERSIONS + """ + + stub: + def args = task.ext.args ?: '' + def prefix = task.ext.prefix ?: "${meta.id}" + """ + touch "${prefix}.gff3" + touch "${prefix}.scn" + + cat <<-END_VERSIONS > versions.yml + "${task.process}": + LTR_HARVEST_parallel: \$(LTR_HARVEST_parallel -h | sed -n '/Version/s/Version: //p') + genometools: \$(gt --version | sed '1!d ; s/gt (GenomeTools) //') + END_VERSIONS + """ +} diff --git a/modules/nf-core/ltrharvest/meta.yml b/modules/nf-core/ltrharvest/meta.yml new file mode 100644 index 0000000..1806418 --- /dev/null +++ b/modules/nf-core/ltrharvest/meta.yml @@ -0,0 +1,68 @@ +# yaml-language-server: $schema=https://raw.githubusercontent.com/nf-core/modules/master/modules/meta-schema.json +name: "ltrharvest" +description: | + Predicts LTR retrotransposons using the parallel version of GenomeTools gt-ltrharvest + utility included in the EDTA toolchain +keywords: + - genomics + - genome + - annotation + - repeat + - transposons + - retrotransposons +tools: + - "LTR_HARVEST_parallel": + description: A Perl wrapper for LTR_harvest + homepage: "https://github.com/oushujun/EDTA/tree/v2.2.0/bin/LTR_HARVEST_parallel" + documentation: "https://github.com/oushujun/EDTA/tree/v2.2.0/bin/LTR_HARVEST_parallel" + tool_dev_url: "https://github.com/oushujun/EDTA/tree/v2.2.0/bin/LTR_HARVEST_parallel" + licence: ["MIT"] + identifier: "" + - "gt": + description: "The GenomeTools genome analysis system" + homepage: "https://genometools.org/index.html" + documentation: "https://genometools.org/documentation.html" + tool_dev_url: "https://github.com/genometools/genometools" + doi: "10.1109/TCBB.2013.68" + licence: ["ISC"] + identifier: "" +input: + - - meta: + type: map + description: | + Groovy Map containing sample information + e.g. `[ id:'sample1' ]` + - fasta: + type: file + description: Input genome fasta + pattern: "*.{fsa,fa,fasta}" +output: + - gff3: + - meta: + type: map + description: | + Groovy Map containing sample information + e.g. `[ id:'sample1' ]` + - "*.gff3": + type: file + description: Predicted LTR candidates in gff3 format + pattern: "*.gff3" + - scn: + - meta: + type: map + description: | + Groovy Map containing sample information + e.g. `[ id:'sample1' ]` + - "*.scn": + type: file + description: Predicted LTR candidates in scn format + pattern: "*.scn" + - versions: + - versions.yml: + type: file + description: File containing software versions + pattern: "versions.yml" +authors: + - "@GallVp" +maintainers: + - "@GallVp" diff --git a/modules/nf-core/ltrharvest/tests/main.nf.test b/modules/nf-core/ltrharvest/tests/main.nf.test new file mode 100644 index 0000000..e200fde --- /dev/null +++ b/modules/nf-core/ltrharvest/tests/main.nf.test @@ -0,0 +1,89 @@ +nextflow_process { + + name "Test Process LTRHARVEST" + script "../main.nf" + process "LTRHARVEST" + + tag "modules" + tag "modules_nfcore" + tag "ltrharvest" + + test("homo_sapiens - genome_21_fasta") { + + when { + process { + """ + input[0] = [ + [ id:'test' ], + 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.gff3, + process.out.versions + ).match() + }, + { assert path(process.out.scn[0][1]).text.contains("46510803 46520182 9380 46510803 46510940 138 46520042 46520182 141 86.52 0 chr21") }, + ) + } + + } + + test("sarscov2 - genome_fasta - no_ltr") { + + when { + process { + """ + input[0] = [ + [ id:'test' ], + file(params.modules_testdata_base_path + 'genomics/sarscov2/genome/genome.fasta', checkIfExists: true) + ] + """ + } + } + + then { + assertAll( + { assert process.success }, + { assert snapshot( + process.out.gff3, + process.out.versions + ).match() + }, + { assert path(process.out.scn[0][1]).text.contains("predictions are reported in the following way") }, + ) + } + + } + + test("homo_sapiens - genome_fasta - stub") { + + options '-stub' + + when { + process { + """ + input[0] = [ + [ id:'test' ], + file(params.modules_testdata_base_path + 'genomics/homo_sapiens/genome/genome.fasta', checkIfExists: true) + ] + """ + } + } + + then { + assertAll( + { assert process.success }, + { assert snapshot(process.out).match() } + ) + } + + } + +} diff --git a/modules/nf-core/ltrharvest/tests/main.nf.test.snap b/modules/nf-core/ltrharvest/tests/main.nf.test.snap new file mode 100644 index 0000000..f3a8da8 --- /dev/null +++ b/modules/nf-core/ltrharvest/tests/main.nf.test.snap @@ -0,0 +1,91 @@ +{ + "sarscov2 - genome_fasta - no_ltr": { + "content": [ + [ + [ + { + "id": "test" + }, + "test.gff3:md5,bddeb04277af08b5850e64708e8af02a" + ] + ], + [ + "versions.yml:md5,51e82185b713482d1d48b6f15abe7fcc" + ] + ], + "meta": { + "nf-test": "0.8.4", + "nextflow": "24.04.3" + }, + "timestamp": "2024-07-17T10:40:36.380052" + }, + "homo_sapiens - genome_fasta - stub": { + "content": [ + { + "0": [ + [ + { + "id": "test" + }, + "test.gff3:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "1": [ + [ + { + "id": "test" + }, + "test.scn:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "2": [ + "versions.yml:md5,51e82185b713482d1d48b6f15abe7fcc" + ], + "gff3": [ + [ + { + "id": "test" + }, + "test.gff3:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "scn": [ + [ + { + "id": "test" + }, + "test.scn:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "versions": [ + "versions.yml:md5,51e82185b713482d1d48b6f15abe7fcc" + ] + } + ], + "meta": { + "nf-test": "0.8.4", + "nextflow": "24.04.3" + }, + "timestamp": "2024-07-17T10:40:40.967557" + }, + "homo_sapiens - genome_21_fasta": { + "content": [ + [ + [ + { + "id": "test" + }, + "test.gff3:md5,da13c4ba22e44ef944ddec38aa72c468" + ] + ], + [ + "versions.yml:md5,51e82185b713482d1d48b6f15abe7fcc" + ] + ], + "meta": { + "nf-test": "0.8.4", + "nextflow": "24.04.3" + }, + "timestamp": "2024-07-17T10:40:30.946" + } +} \ No newline at end of file diff --git a/modules/nf-core/ltrretriever/ltrretriever/environment.yml b/modules/nf-core/ltrretriever/ltrretriever/environment.yml new file mode 100644 index 0000000..f1c392a --- /dev/null +++ b/modules/nf-core/ltrretriever/ltrretriever/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::LTR_retriever=2.9.9" diff --git a/modules/nf-core/ltrretriever/ltrretriever/meta.yml b/modules/nf-core/ltrretriever/ltrretriever/meta.yml new file mode 100644 index 0000000..9645de2 --- /dev/null +++ b/modules/nf-core/ltrretriever/ltrretriever/meta.yml @@ -0,0 +1,119 @@ +# yaml-language-server: $schema=https://raw.githubusercontent.com/nf-core/modules/master/modules/meta-schema.json +name: "ltrretriever_ltrretriever" +description: Identifies LTR retrotransposons using LTR_retriever +keywords: + - genomics + - annotation + - repeat + - long terminal repeat + - retrotransposon +tools: + - "LTR_retriever": + description: Sensitive and accurate identification of LTR retrotransposons + homepage: "https://github.com/oushujun/LTR_retriever" + documentation: "https://github.com/oushujun/LTR_retriever" + tool_dev_url: "https://github.com/oushujun/LTR_retriever" + doi: "10.1104/pp.17.01310" + licence: ["GPL v3"] + identifier: "" +input: + - - meta: + type: map + description: | + Groovy Map containing sample information + e.g. `[ id:'sample1' ]` + - genome: + type: file + description: Genomic sequences in fasta format + pattern: "*.{fsa,fa,fasta}" + - - harvest: + type: file + description: LTR-RT candidates from GenomeTools ltrharvest in the old tabular + format + pattern: "*.tabout" + - - finder: + type: file + description: LTR-RT candidates from LTR_FINDER + pattern: "*.scn" + - - mgescan: + type: file + description: LTR-RT candidates from MGEScan_LTR + pattern: "*.out" + - - non_tgca: + type: file + description: Non-canonical LTR-RT candidates from GenomeTools ltrharvest in + the old tabular format + pattern: "*.tabout" +output: + - log: + - meta: + type: map + description: | + Groovy Map containing sample information + e.g. `[ id:'sample1' ]` + - "*.log": + type: file + description: Output log from LTR_retriever + pattern: "*.log" + - pass_list: + - meta: + type: map + description: | + Groovy Map containing sample information + e.g. `[ id:'sample1' ]` + - ${prefix}.pass.list: + type: file + description: Intact LTR-RTs with coordinate and structural information in summary + table format + pattern: "*.pass.list" + - pass_list_gff: + - meta: + type: map + description: | + Groovy Map containing sample information + e.g. `[ id:'sample1' ]` + - "*.pass.list.gff3": + type: file + description: Intact LTR-RTs with coordinate and structural information in gff3 + format + pattern: "*.pass.list.gff3" + - ltrlib: + - meta: + type: map + description: | + Groovy Map containing sample information + e.g. `[ id:'sample1' ]` + - "*.LTRlib.fa": + type: file + description: All non-redundant LTR-RTs + pattern: "*.LTRlib.fa" + - annotation_out: + - meta: + type: map + description: | + Groovy Map containing sample information + e.g. `[ id:'sample1' ]` + - ${prefix}.out: + type: file + description: Whole-genome LTR-RT annotation by the non-redundant library + pattern: "*.out" + - annotation_gff: + - meta: + type: map + description: | + Groovy Map containing sample information + e.g. `[ id:'sample1' ]` + - "*.out.gff3": + type: file + description: Whole-genome LTR-RT annotation by the non-redundant library in + gff3 format + pattern: "*.out.gff3" + - versions: + - versions.yml: + type: file + description: File containing software versions + pattern: "versions.yml" +authors: + - "@GallVp" +maintainers: + - "@GallVp" diff --git a/modules/nf-core/ltrretriever/ltrretriever/tests/main.nf.test b/modules/nf-core/ltrretriever/ltrretriever/tests/main.nf.test new file mode 100644 index 0000000..4d512f3 --- /dev/null +++ b/modules/nf-core/ltrretriever/ltrretriever/tests/main.nf.test @@ -0,0 +1,186 @@ +nextflow_process { + + name "Test Process LTRRETRIEVER_LTRRETRIEVER" + script "../main.nf" + process "LTRRETRIEVER_LTRRETRIEVER" + config "./nextflow.config" + + tag "modules" + tag "modules_nfcore" + tag "ltrretriever" + tag "ltrretriever/ltrretriever" + tag "gunzip" + tag "ltrharvest" + tag "ltrfinder" + tag "cat/cat" + + test("sarscov2-genome-no_ltr") { + + setup { + + run("LTRHARVEST") { + script "../../../ltrharvest/main.nf" + + process { + """ + input[0] = [ + [ id: 'test' ], + file(params.modules_testdata_base_path + 'genomics/sarscov2/genome/genome.fasta', checkIfExists: true) + ] + """ + } + } + + run("LTRFINDER") { + script "../../../ltrfinder/main.nf" + + process { + """ + input[0] = [ + [ id: 'test' ], + file(params.modules_testdata_base_path + 'genomics/sarscov2/genome/genome.fasta', checkIfExists: true) + ] + """ + } + } + + run("CAT_CAT") { + script "../../../cat/cat/main.nf" + + process { + """ + input[0] = LTRHARVEST.out.scn.mix(LTRFINDER.out.scn).groupTuple() + """ + } + } + } + + when { + process { + """ + input[0] = input[0] = [ + [ id: 'test' ], + file(params.modules_testdata_base_path + 'genomics/sarscov2/genome/genome.fasta', checkIfExists: true) + ] + input[1] = CAT_CAT.out.file_out.map { meta, tabout -> tabout } + input[2] = [] + input[3] = [] + input[4] = [] + """ + } + } + + then { + assertAll( + { assert process.success }, + { assert path(process.out.log[0][1]).text.contains("ERROR: No candidate is found in the file(s) you specified.") }, + { assert snapshot(process.out.versions).match("versions_no_ltr") } + ) + } + + } + + test("actinidia_chinensis-genome_21_fasta_gz-success") { + + setup { + + run('GUNZIP') { + script "../../../gunzip/main.nf" + + process { + """ + input[0] = [ + [ id:'test' ], // meta map + file(params.modules_testdata_base_path + 'genomics/eukaryotes/actinidia_chinensis/genome/chr1/genome.fasta.gz', checkIfExists: true) + ] + """ + } + } + + run("LTRHARVEST") { + script "../../../ltrharvest/main.nf" + + process { + """ + input[0] = GUNZIP.out.gunzip + """ + } + } + + run("LTRFINDER") { + script "../../../ltrfinder/main.nf" + + process { + """ + input[0] = GUNZIP.out.gunzip + """ + } + } + + run("CAT_CAT") { + script "../../../cat/cat/main.nf" + + process { + """ + input[0] = LTRHARVEST.out.scn.mix(LTRFINDER.out.scn).groupTuple() + """ + } + } + } + + when { + process { + """ + input[0] = GUNZIP.out.gunzip + input[1] = CAT_CAT.out.file_out.map { meta, tabout -> tabout } + input[2] = [] + input[3] = [] + input[4] = [] + """ + } + } + + then { + assertAll( + { assert process.success }, + { assert path(process.out.log[0][1]).text.contains("####### Result files #########") }, + { assert path(process.out.pass_list[0][1]).text.contains("Copia\tLTR") }, + { assert path(process.out.pass_list_gff[0][1]).text.contains("chr1\tLTR_retriever\ttarget_site_duplication") }, + { assert path(process.out.ltrlib[0][1]).text.contains("LTR#LTR/Copia") }, + { assert snapshot(process.out.annotation_out).match("annotation_out") }, + { assert path(process.out.annotation_gff[0][1]).text.contains("Classification=LTR/Copia") }, + { assert snapshot(path(process.out.versions[0]).text).match("versions") } + ) + } + + } + + test("stub") { + + options '-stub' + + when { + process { + """ + input[0] = [ + [ id:'test' ], // meta map + file(params.modules_testdata_base_path + 'genomics/eukaryotes/actinidia_chinensis/genome/chr1/genome.fasta.gz', checkIfExists: true) + ] + input[1] = [] + input[2] = [] + input[3] = [] + input[4] = [] + """ + } + } + + then { + assertAll( + { assert process.success }, + { assert snapshot(process.out).match() } + ) + } + + } + +} diff --git a/modules/nf-core/ltrretriever/ltrretriever/tests/nextflow.config b/modules/nf-core/ltrretriever/ltrretriever/tests/nextflow.config new file mode 100644 index 0000000..7f67556 --- /dev/null +++ b/modules/nf-core/ltrretriever/ltrretriever/tests/nextflow.config @@ -0,0 +1,15 @@ +process { + + withName: LTRFINDER { + ext.args = '-harvest_out' + // LTRRETRIEVER_LTRRETRIEVER requires -harvest_out + } + + withName: LTRHARVEST { + ext.prefix = { "${meta.id}_ltrharvest" } + } + + withName: CAT_CAT { + ext.prefix = { "${meta.id}_ltrharvest_ltrfinder.tabout" } + } +} diff --git a/modules/nf-core/repeatmodeler/builddatabase/environment.yml b/modules/nf-core/repeatmodeler/builddatabase/environment.yml new file mode 100644 index 0000000..5314307 --- /dev/null +++ b/modules/nf-core/repeatmodeler/builddatabase/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::repeatmodeler=2.0.5" diff --git a/modules/nf-core/repeatmodeler/builddatabase/main.nf b/modules/nf-core/repeatmodeler/builddatabase/main.nf new file mode 100644 index 0000000..6fe244b --- /dev/null +++ b/modules/nf-core/repeatmodeler/builddatabase/main.nf @@ -0,0 +1,50 @@ +process REPEATMODELER_BUILDDATABASE { + tag "$meta.id" + label 'process_single' + + conda "${moduleDir}/environment.yml" + container "${ workflow.containerEngine == 'singularity' && !task.ext.singularity_pull_docker_container ? + 'https://depot.galaxyproject.org/singularity/repeatmodeler:2.0.5--pl5321hdfd78af_0': + 'biocontainers/repeatmodeler:2.0.5--pl5321hdfd78af_0' }" + + input: + tuple val(meta), path(fasta) + + output: + tuple val(meta), path("${prefix}.*") , emit: db + path "versions.yml" , emit: versions + + when: + task.ext.when == null || task.ext.when + + script: + prefix = task.ext.prefix ?: "${meta.id}" + """ + BuildDatabase \\ + -name $prefix \\ + $fasta + + cat <<-END_VERSIONS > versions.yml + "${task.process}": + repeatmodeler: \$(RepeatModeler --version | sed 's/RepeatModeler version //') + END_VERSIONS + """ + + stub: + prefix = task.ext.prefix ?: "${meta.id}" + """ + touch ${prefix}.nhr + touch ${prefix}.nin + touch ${prefix}.njs + touch ${prefix}.nnd + touch ${prefix}.nni + touch ${prefix}.nog + touch ${prefix}.nsq + touch ${prefix}.translation + + cat <<-END_VERSIONS > versions.yml + "${task.process}": + repeatmodeler: \$(RepeatModeler --version | sed 's/RepeatModeler version //') + END_VERSIONS + """ +} diff --git a/modules/nf-core/repeatmodeler/builddatabase/meta.yml b/modules/nf-core/repeatmodeler/builddatabase/meta.yml new file mode 100644 index 0000000..cc78cf0 --- /dev/null +++ b/modules/nf-core/repeatmodeler/builddatabase/meta.yml @@ -0,0 +1,47 @@ +# yaml-language-server: $schema=https://raw.githubusercontent.com/nf-core/modules/master/modules/meta-schema.json +name: "repeatmodeler_builddatabase" +description: Create a database for RepeatModeler +keywords: + - genomics + - fasta + - repeat +tools: + - "repeatmodeler": + description: "RepeatModeler is a de-novo repeat family identification and modeling + package." + homepage: "https://github.com/Dfam-consortium/RepeatModeler" + documentation: "https://github.com/Dfam-consortium/RepeatModeler" + tool_dev_url: "https://github.com/Dfam-consortium/RepeatModeler" + licence: ["Open Software License v2.1"] + identifier: biotools:repeatmodeler + +input: + - - meta: + type: map + description: | + Groovy Map containing sample information + e.g. `[ id:'sample1' ]` + - fasta: + type: file + description: Fasta file + pattern: "*.{fasta,fsa,fa}" +output: + - db: + - meta: + type: map + description: | + Groovy Map containing sample information + e.g. `[ id:'sample1' ]` + - ${prefix}.*: + type: file + description: Database files for repeatmodeler + pattern: "`${prefix}.*`" + - versions: + - versions.yml: + type: file + description: File containing software versions + pattern: "versions.yml" +authors: + - "@GallVp" +maintainers: + - "@GallVp" diff --git a/modules/nf-core/repeatmodeler/builddatabase/tests/main.nf.test b/modules/nf-core/repeatmodeler/builddatabase/tests/main.nf.test new file mode 100644 index 0000000..78b78a6 --- /dev/null +++ b/modules/nf-core/repeatmodeler/builddatabase/tests/main.nf.test @@ -0,0 +1,60 @@ +nextflow_process { + + name "Test Process REPEATMODELER_BUILDDATABASE" + script "../main.nf" + process "REPEATMODELER_BUILDDATABASE" + + tag "modules" + tag "modules_nfcore" + tag "repeatmodeler" + tag "repeatmodeler/builddatabase" + + test("sarscov2-genome_fasta") { + + when { + process { + """ + input[0] = [ + [ id:'test' ], // meta map + file(params.modules_testdata_base_path + 'genomics/sarscov2/genome/genome.fasta', checkIfExists: true) + ] + """ + } + } + + then { + assertAll( + { assert process.success }, + { assert snapshot(process.out.versions).match("versions") }, + { assert snapshot(process.out.db[0][1].collect { file(it).name }.sort().toString()).match("db") }, + { assert snapshot(process.out.db[0][1].findAll { ! ( "$it"[-3..-1] in [ 'nin', 'njs' ] ) } ).match("stable_md5") } + ) + } + + } + + test("sarscov2-genome_fasta-stub") { + + options "-stub" + + when { + process { + """ + input[0] = [ + [ id:'test' ], // meta map + file(params.modules_testdata_base_path + 'genomics/sarscov2/genome/genome.fasta', checkIfExists: true) + ] + """ + } + } + + then { + assertAll( + { assert process.success }, + { assert snapshot(process.out).match() } + ) + } + + } + +} diff --git a/modules/nf-core/repeatmodeler/builddatabase/tests/main.nf.test.snap b/modules/nf-core/repeatmodeler/builddatabase/tests/main.nf.test.snap new file mode 100644 index 0000000..1f1a551 --- /dev/null +++ b/modules/nf-core/repeatmodeler/builddatabase/tests/main.nf.test.snap @@ -0,0 +1,92 @@ +{ + "sarscov2-genome_fasta-stub": { + "content": [ + { + "0": [ + [ + { + "id": "test" + }, + [ + "test.nhr:md5,d41d8cd98f00b204e9800998ecf8427e", + "test.nin:md5,d41d8cd98f00b204e9800998ecf8427e", + "test.njs:md5,d41d8cd98f00b204e9800998ecf8427e", + "test.nnd:md5,d41d8cd98f00b204e9800998ecf8427e", + "test.nni:md5,d41d8cd98f00b204e9800998ecf8427e", + "test.nog:md5,d41d8cd98f00b204e9800998ecf8427e", + "test.nsq:md5,d41d8cd98f00b204e9800998ecf8427e", + "test.translation:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ] + ], + "1": [ + "versions.yml:md5,7944637266bc3e2726899eaad5e46c87" + ], + "db": [ + [ + { + "id": "test" + }, + [ + "test.nhr:md5,d41d8cd98f00b204e9800998ecf8427e", + "test.nin:md5,d41d8cd98f00b204e9800998ecf8427e", + "test.njs:md5,d41d8cd98f00b204e9800998ecf8427e", + "test.nnd:md5,d41d8cd98f00b204e9800998ecf8427e", + "test.nni:md5,d41d8cd98f00b204e9800998ecf8427e", + "test.nog:md5,d41d8cd98f00b204e9800998ecf8427e", + "test.nsq:md5,d41d8cd98f00b204e9800998ecf8427e", + "test.translation:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ] + ], + "versions": [ + "versions.yml:md5,7944637266bc3e2726899eaad5e46c87" + ] + } + ], + "meta": { + "nf-test": "0.8.4", + "nextflow": "23.10.1" + }, + "timestamp": "2024-04-02T12:06:44.261566" + }, + "versions": { + "content": [ + [ + "versions.yml:md5,7944637266bc3e2726899eaad5e46c87" + ] + ], + "meta": { + "nf-test": "0.8.4", + "nextflow": "23.10.1" + }, + "timestamp": "2024-01-09T15:14:48.807063" + }, + "stable_md5": { + "content": [ + [ + "test.nhr:md5,1a41cb6d0b00c28f62ad60e75ae2f6fc", + "test.nnd:md5,2002e13acf59079a1a5782c918894579", + "test.nni:md5,26a954ba0fd80983b550d8f6b8b35ff8", + "test.nog:md5,30896f123998e926ea2237b89091e7fe", + "test.nsq:md5,982cbc7d9e38743b9b1037588862b9da", + "test.translation:md5,ccbb119522c09daa976a9015ba999329" + ] + ], + "meta": { + "nf-test": "0.8.4", + "nextflow": "23.10.1" + }, + "timestamp": "2024-04-23T10:03:41.669433" + }, + "db": { + "content": [ + "[test.nhr, test.nin, test.njs, test.nnd, test.nni, test.nog, test.nsq, test.translation]" + ], + "meta": { + "nf-test": "0.8.4", + "nextflow": "23.10.1" + }, + "timestamp": "2024-04-02T12:08:36.94713" + } +} \ No newline at end of file diff --git a/modules/nf-core/repeatmodeler/builddatabase/tests/tags.yml b/modules/nf-core/repeatmodeler/builddatabase/tests/tags.yml new file mode 100644 index 0000000..c524294 --- /dev/null +++ b/modules/nf-core/repeatmodeler/builddatabase/tests/tags.yml @@ -0,0 +1,2 @@ +repeatmodeler/builddatabase: + - "modules/nf-core/repeatmodeler/builddatabase/**" diff --git a/modules/nf-core/repeatmodeler/repeatmodeler/environment.yml b/modules/nf-core/repeatmodeler/repeatmodeler/environment.yml new file mode 100644 index 0000000..5314307 --- /dev/null +++ b/modules/nf-core/repeatmodeler/repeatmodeler/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::repeatmodeler=2.0.5" diff --git a/modules/nf-core/repeatmodeler/repeatmodeler/main.nf b/modules/nf-core/repeatmodeler/repeatmodeler/main.nf new file mode 100644 index 0000000..9d0449f --- /dev/null +++ b/modules/nf-core/repeatmodeler/repeatmodeler/main.nf @@ -0,0 +1,54 @@ +process REPEATMODELER_REPEATMODELER { + tag "$meta.id" + label 'process_medium' + + conda "${moduleDir}/environment.yml" + container "${ workflow.containerEngine == 'singularity' && !task.ext.singularity_pull_docker_container ? + 'https://depot.galaxyproject.org/singularity/repeatmodeler:2.0.5--pl5321hdfd78af_0': + 'biocontainers/repeatmodeler:2.0.5--pl5321hdfd78af_0' }" + + input: + tuple val(meta), path(db) + + output: + tuple val(meta), path("*.fa") , emit: fasta + tuple val(meta), path("*.stk"), emit: stk + tuple val(meta), path("*.log"), emit: log + path "versions.yml" , emit: versions + + when: + task.ext.when == null || task.ext.when + + script: + def args = task.ext.args ?: '' + def prefix = task.ext.prefix ?: "${meta.id}" + def db_name = file(db[0]).getBaseName() + """ + RepeatModeler \\ + -database $db_name \\ + $args \\ + -threads $task.cpus + + mv ${db_name}-families.fa ${prefix}.fa + mv ${db_name}-families.stk ${prefix}.stk + mv ${db_name}-rmod.log ${prefix}.log + + cat <<-END_VERSIONS > versions.yml + "${task.process}": + repeatmodeler: \$(RepeatModeler --version | sed 's/RepeatModeler version //') + END_VERSIONS + """ + + stub: + def prefix = task.ext.prefix ?: "${meta.id}" + """ + touch ${prefix}.fa + touch ${prefix}.stk + touch ${prefix}.log + + cat <<-END_VERSIONS > versions.yml + "${task.process}": + repeatmodeler: \$(RepeatModeler --version | sed 's/RepeatModeler version //') + END_VERSIONS + """ +} diff --git a/modules/nf-core/repeatmodeler/repeatmodeler/meta.yml b/modules/nf-core/repeatmodeler/repeatmodeler/meta.yml new file mode 100644 index 0000000..6693ae9 --- /dev/null +++ b/modules/nf-core/repeatmodeler/repeatmodeler/meta.yml @@ -0,0 +1,68 @@ +# yaml-language-server: $schema=https://raw.githubusercontent.com/nf-core/modules/master/modules/meta-schema.json +name: "repeatmodeler_repeatmodeler" +description: Performs de novo transposable element (TE) family identification with + RepeatModeler +keywords: + - genomics + - fasta + - repeat + - transposable element +tools: + - "repeatmodeler": + description: "RepeatModeler is a de-novo repeat family identification and modeling + package." + homepage: "https://github.com/Dfam-consortium/RepeatModeler" + documentation: "https://github.com/Dfam-consortium/RepeatModeler" + tool_dev_url: "https://github.com/Dfam-consortium/RepeatModeler" + licence: ["Open Software License v2.1"] + identifier: biotools:repeatmodeler +input: + - - meta: + type: map + description: | + Groovy Map containing sample information + e.g. `[ id:'sample1' ]` + - db: + type: file + description: RepeatModeler database files generated with REPEATMODELER_BUILDDATABASE + pattern: "*" +output: + - fasta: + - meta: + type: map + description: | + Groovy Map containing sample information + e.g. `[ id:'sample1' ]` + - "*.fa": + type: file + description: Consensus repeat sequences + pattern: "*.fa" + - stk: + - meta: + type: map + description: | + Groovy Map containing sample information + e.g. `[ id:'sample1' ]` + - "*.stk": + type: file + description: Seed alignments + pattern: "*.stk" + - log: + - meta: + type: map + description: | + Groovy Map containing sample information + e.g. `[ id:'sample1' ]` + - "*.log": + type: file + description: A summarized log of the run + pattern: "*.log" + - versions: + - versions.yml: + type: file + description: File containing software versions + pattern: "versions.yml" +authors: + - "@GallVp" +maintainers: + - "@GallVp" diff --git a/modules/nf-core/repeatmodeler/repeatmodeler/tests/main.nf.test b/modules/nf-core/repeatmodeler/repeatmodeler/tests/main.nf.test new file mode 100644 index 0000000..829e222 --- /dev/null +++ b/modules/nf-core/repeatmodeler/repeatmodeler/tests/main.nf.test @@ -0,0 +1,74 @@ +nextflow_process { + + name "Test Process REPEATMODELER_REPEATMODELER" + script "../main.nf" + process "REPEATMODELER_REPEATMODELER" + + tag "modules" + tag "modules_nfcore" + tag "repeatmodeler" + tag "repeatmodeler/repeatmodeler" + tag "repeatmodeler/builddatabase" + + test("homo_sapiens-genome_fasta") { + + setup { + run("REPEATMODELER_BUILDDATABASE") { + script "../../../../nf-core/repeatmodeler/builddatabase" + + process { + """ + input[0] = [ + [ id:'test' ], // meta map + file(params.modules_testdata_base_path + 'genomics/homo_sapiens/genome/genome.fasta', checkIfExists: true) + ] + """ + } + } + } + + when { + process { + """ + input[0] = REPEATMODELER_BUILDDATABASE.out.db + """ + } + } + + then { + assertAll( + { assert process.success }, + { assert snapshot(process.out.fasta).match("fasta") }, + { assert snapshot(process.out.stk).match("stk") }, + { assert file(process.out.log[0][1]).text.contains('1 families discovered.') }, + { assert snapshot(process.out.versions).match("versions") } + ) + } + + } + + test("homo_sapiens-genome_fasta-stub") { + + options "-stub" + + when { + process { + """ + input[0] = [ + [ id:'test' ], // meta map + file(params.modules_testdata_base_path + 'genomics/homo_sapiens/genome/genome.fasta', checkIfExists: true) + ] + """ + } + } + + then { + assertAll( + { assert process.success }, + { assert snapshot(process.out).match() } + ) + } + + } + +} \ No newline at end of file diff --git a/modules/nf-core/repeatmodeler/repeatmodeler/tests/main.nf.test.snap b/modules/nf-core/repeatmodeler/repeatmodeler/tests/main.nf.test.snap new file mode 100644 index 0000000..e923952 --- /dev/null +++ b/modules/nf-core/repeatmodeler/repeatmodeler/tests/main.nf.test.snap @@ -0,0 +1,113 @@ +{ + "versions": { + "content": [ + [ + "versions.yml:md5,1bb6846ecf1304c262eaef4d3de60cf9" + ] + ], + "meta": { + "nf-test": "0.8.4", + "nextflow": "23.10.1" + }, + "timestamp": "2024-01-09T15:06:55.753492" + }, + "homo_sapiens-genome_fasta-stub": { + "content": [ + { + "0": [ + [ + { + "id": "test" + }, + "test.fa:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "1": [ + [ + { + "id": "test" + }, + "test.stk:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "2": [ + [ + { + "id": "test" + }, + "test.log:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "3": [ + "versions.yml:md5,1bb6846ecf1304c262eaef4d3de60cf9" + ], + "fasta": [ + [ + { + "id": "test" + }, + "test.fa:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "log": [ + [ + { + "id": "test" + }, + "test.log:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "stk": [ + [ + { + "id": "test" + }, + "test.stk:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "versions": [ + "versions.yml:md5,1bb6846ecf1304c262eaef4d3de60cf9" + ] + } + ], + "meta": { + "nf-test": "0.8.4", + "nextflow": "23.10.1" + }, + "timestamp": "2024-04-29T13:16:41.45166" + }, + "stk": { + "content": [ + [ + [ + { + "id": "test" + }, + "test.stk:md5,acd01ad35763c11315e2297a4f051d57" + ] + ] + ], + "meta": { + "nf-test": "0.8.4", + "nextflow": "23.10.1" + }, + "timestamp": "2024-01-09T15:06:55.740963" + }, + "fasta": { + "content": [ + [ + [ + { + "id": "test" + }, + "test.fa:md5,e25326771341204e1f8054d9529411e5" + ] + ] + ], + "meta": { + "nf-test": "0.8.4", + "nextflow": "23.10.1" + }, + "timestamp": "2024-01-09T15:06:55.737658" + } +} \ No newline at end of file diff --git a/modules/nf-core/repeatmodeler/repeatmodeler/tests/tags.yml b/modules/nf-core/repeatmodeler/repeatmodeler/tests/tags.yml new file mode 100644 index 0000000..df65110 --- /dev/null +++ b/modules/nf-core/repeatmodeler/repeatmodeler/tests/tags.yml @@ -0,0 +1,2 @@ +repeatmodeler/repeatmodeler: + - "modules/nf-core/repeatmodeler/repeatmodeler/**" From aa5abbb31f52bfd99841cd1a303bf7b900b05c2d Mon Sep 17 00:00:00 2001 From: Usman Rashid Date: Thu, 12 Dec 2024 13:35:00 +1300 Subject: [PATCH 34/49] Added TIR_LEARNER_POSTPROCESS --- bin/setup_TIR-Learner_postprocess.sh | 28 +++++++ conf/modules.config | 10 +++ .../local/ltr_retriever_postprocess/main.nf | 4 + .../tir_learner_postprocess/environment.yml | 10 +++ modules/local/tir_learner_postprocess/main.nf | 76 +++++++++++++++++++ tests/nf-test/small/main.nf.test | 2 + tests/nf-test/small/main.nf.test.snap | 21 +++-- workflows/edta.nf | 32 ++++++-- 8 files changed, 171 insertions(+), 12 deletions(-) create mode 100755 bin/setup_TIR-Learner_postprocess.sh create mode 100644 modules/local/tir_learner_postprocess/environment.yml create mode 100644 modules/local/tir_learner_postprocess/main.nf diff --git a/bin/setup_TIR-Learner_postprocess.sh b/bin/setup_TIR-Learner_postprocess.sh new file mode 100755 index 0000000..777059f --- /dev/null +++ b/bin/setup_TIR-Learner_postprocess.sh @@ -0,0 +1,28 @@ +#!/usr/bin/env bash + +script_path=$(dirname $0) + +cat > input/TIR-Learner_postprocess.pl << EOF +my \$genome = 'genome'; +my \$threads = $1; + +my \$rename_tirlearner = "$script_path/rename_tirlearner.pl"; +my \$get_ext_seq = "$script_path/get_ext_seq.pl"; +my \$flank_filter = "$script_path/flanking_filter.pl"; +my \$output_by_list = '$script_path/output_by_list.pl'; +my \$cleanup_tandem = '$script_path/cleanup_tandem.pl'; +my \$cleanup_misclas = '$script_path/cleanup_misclas.pl'; +my \$bed2gff = "$script_path/bed2gff.pl"; + +my \$blastplus = ''; # Assumed on PATH +my \$mdust = ''; # Assumed on PATH +my \$trf = '$(which trf)'; +my \$TEsorter = ''; # Assumed on PATH + +EOF + +sed -n 632,653p "$script_path/EDTA_raw.pl" \ + >> input/TIR-Learner_postprocess.pl + +sed -n 657,660p "$script_path/EDTA_raw.pl" \ + >> input/TIR-Learner_postprocess.pl diff --git a/conf/modules.config b/conf/modules.config index c521005..a854d4e 100644 --- a/conf/modules.config +++ b/conf/modules.config @@ -52,5 +52,15 @@ process { saveAs: { filename -> filename.equals('versions.yml') ? null : filename } ] } + + withName: 'EDTA:TIR_LEARNER_POSTPROCESS' { + ext.prefix = { "${meta.id}.pp" } + + publishDir = [ + path: { "${params.outdir}/tir" }, + mode: params.publish_dir_mode, + saveAs: { filename -> filename.equals('versions.yml') ? null : filename } + ] + } } \ No newline at end of file diff --git a/modules/local/ltr_retriever_postprocess/main.nf b/modules/local/ltr_retriever_postprocess/main.nf index e679be6..e950b59 100644 --- a/modules/local/ltr_retriever_postprocess/main.nf +++ b/modules/local/ltr_retriever_postprocess/main.nf @@ -68,6 +68,10 @@ process LTR_RETRIEVER_POSTPROCESS { def MDUST_VERSION = '2006.10.17' // WARN: Manually update when changing Bioconda assets if ( "$prefix" == 'genome' ) error "Input and output names are the same, use \"task.ext.prefix\" to disambiguate!" """ + touch ${prefix}.LTR.raw.fa + touch ${prefix}.genome.LTR.intact.raw.fa + touch ${prefix}.LTR.intact.raw.gff3 + touch ${prefix}.LTR.intact.raw.fa.anno.list cat <<-END_VERSIONS > versions.yml "${task.process}": diff --git a/modules/local/tir_learner_postprocess/environment.yml b/modules/local/tir_learner_postprocess/environment.yml new file mode 100644 index 0000000..6f8a4fc --- /dev/null +++ b/modules/local/tir_learner_postprocess/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: + - bioconda::mdust=2006.10.17 + - bioconda::tesorter=1.4.7 + - bioconda::trf=4.09.1 + - conda-forge::perl=5.32.1 \ No newline at end of file diff --git a/modules/local/tir_learner_postprocess/main.nf b/modules/local/tir_learner_postprocess/main.nf new file mode 100644 index 0000000..275fc5a --- /dev/null +++ b/modules/local/tir_learner_postprocess/main.nf @@ -0,0 +1,76 @@ +process TIR_LEARNER_POSTPROCESS { + tag "$meta.id" + label 'process_low' + + conda "${moduleDir}/environment.yml" + container "${ workflow.containerEngine == 'singularity' && !task.ext.singularity_pull_docker_container ? + 'https://community-cr-prod.seqera.io/docker/registry/v2/blobs/sha256/cb/cb105ba7d586ab31c1f39feb04c0255a39cc5a55ae7f6ea53f4bf76cdba8a3e5/data': + 'community.wave.seqera.io/library/mdust_tesorter_trf_perl:3424609103d3b065' }" + + input: + tuple val(meta), path(genome, name: 'input/genome') + path "input/TIR-Learner-Result/TIR-Learner_FinalAnn.fa" + path "input/TIR-Learner-Result/TIR-Learner_FinalAnn.gff3" + + output: + tuple val(meta), path('*.TIR.intact.raw.fa') , emit: intact_raw_fa + tuple val(meta), path('*.TIR.intact.raw.gff3') , emit: intact_raw_gff3 + tuple val(meta), path('*.TIR.intact.raw.bed') , emit: intact_raw_bed + path "versions.yml" , emit: versions + + when: + task.ext.when == null || task.ext.when + + script: + def prefix = task.ext.prefix ?: "${meta.id}" + def MDUST_VERSION = '2006.10.17' // WARN: Manually update when changing Bioconda assets + if ( "$prefix" == 'genome' ) error "Input and output names are the same, use \"task.ext.prefix\" to disambiguate!" + """ + setup_TIR-Learner_postprocess.sh \\ + $task.cpus + + cd input + + perl TIR-Learner_postprocess.pl + + cd - + + mv \\ + genome.TIR.intact.raw.fa \\ + ${prefix}.TIR.intact.raw.fa + + mv \\ + genome.TIR.intact.raw.gff3 \\ + ${prefix}.TIR.intact.raw.gff3 + + mv \\ + genome.TIR.intact.raw.bed \\ + ${prefix}.TIR.intact.raw.bed + + cat <<-END_VERSIONS > versions.yml + "${task.process}": + perl: \$(perl -v | sed -n 's|This is perl.*(\\(.*\\)).*|\\1|p') + trf: \$(trf -v |& sed -n 's|.*Version \\(.*\\)|\\1|p') + TEsorter: \$(TEsorter -v | tr -d 'TEsorter ') + mdust: $MDUST_VERSION + END_VERSIONS + """ + + stub: + def prefix = task.ext.prefix ?: "${meta.id}" + def MDUST_VERSION = '2006.10.17' // WARN: Manually update when changing Bioconda assets + if ( "$prefix" == 'genome' ) error "Input and output names are the same, use \"task.ext.prefix\" to disambiguate!" + """ + touch ${prefix}.TIR.intact.raw.fa + touch ${prefix}.TIR.intact.raw.gff3 + touch ${prefix}.TIR.intact.raw.bed + + cat <<-END_VERSIONS > versions.yml + "${task.process}": + perl: \$(perl -v | sed -n 's|This is perl.*(\\(.*\\)).*|\\1|p') + trf: \$(trf -v |& sed -n 's|.*Version \\(.*\\)|\\1|p') + TEsorter: \$(TEsorter -v | tr -d 'TEsorter ') + mdust: $MDUST_VERSION + END_VERSIONS + """ +} \ No newline at end of file diff --git a/tests/nf-test/small/main.nf.test b/tests/nf-test/small/main.nf.test index ce37a4f..d53e28f 100644 --- a/tests/nf-test/small/main.nf.test +++ b/tests/nf-test/small/main.nf.test @@ -21,6 +21,8 @@ nextflow_pipeline { 'pipeline_info/*.{html,json,txt,yml}', 'ltr/*.pp.LTR.intact.raw.gff3', 'ltr/*.pp.LTR.raw.fa', + 'tir/*.pp.TIR.intact.raw.fa', + 'tir/*.pp.TIR.intact.raw.gff3', ], null, ['**'] diff --git a/tests/nf-test/small/main.nf.test.snap b/tests/nf-test/small/main.nf.test.snap index a943b0a..1469232 100644 --- a/tests/nf-test/small/main.nf.test.snap +++ b/tests/nf-test/small/main.nf.test.snap @@ -2,7 +2,7 @@ "small genome": { "content": [ { - "successful tasks": 17, + "successful tasks": 18, "versions": { "ANNOSINE": { "annosine": "2.0.7" @@ -57,13 +57,20 @@ "TIRLEARNER": { "TIR-Learner": "v3.0.2 by Tianyu (Sky) Lu (tlu83@wisc.edu) published under GPLv3" }, + "TIR_LEARNER_POSTPROCESS": { + "perl": "v5.32.1", + "trf": 4.09, + "TEsorter": "1.4.7", + "mdust": "2006.10.17" + }, "Workflow": { "jguhlin/EDTA": "v0.1.0dev" } }, "stable paths": [ "genome.pp.LTR.intact.raw.fa.anno.list:md5,51261f020350b448acdf89d8fb625448", - "genome.pp.genome.LTR.intact.raw.fa:md5,ae2336f498897d1af4e8d3be28bb3f25" + "genome.pp.genome.LTR.intact.raw.fa:md5,ae2336f498897d1af4e8d3be28bb3f25", + "genome.pp.TIR.intact.raw.bed:md5,12406184046b11edc2ddadbc82765966" ], "stable names": [ "ltr", @@ -71,14 +78,18 @@ "ltr/genome.pp.LTR.intact.raw.gff3", "ltr/genome.pp.LTR.raw.fa", "ltr/genome.pp.genome.LTR.intact.raw.fa", - "pipeline_info" + "pipeline_info", + "tir", + "tir/genome.pp.TIR.intact.raw.bed", + "tir/genome.pp.TIR.intact.raw.fa", + "tir/genome.pp.TIR.intact.raw.gff3" ] } ], "meta": { - "nf-test": "0.9.0", + "nf-test": "0.9.2", "nextflow": "24.04.4" }, - "timestamp": "2024-12-05T12:10:52.622584" + "timestamp": "2024-12-12T13:06:43.800123" } } \ No newline at end of file diff --git a/workflows/edta.nf b/workflows/edta.nf index a4799cf..58f96d2 100644 --- a/workflows/edta.nf +++ b/workflows/edta.nf @@ -10,6 +10,7 @@ include { REPEATMODELER_REPEATMODELER } from '../modules/nf-core/repeatmod include { FASTA_HELITRONSCANNER_SCAN_DRAW } from '../subworkflows/gallvp/fasta_helitronscanner_scan_draw/main.nf' include { FORMAT_HELITRONSCANNER_OUT } from '../modules/local/format_helitronscanner_out/main.nf' include { LTR_RETRIEVER_POSTPROCESS } from '../modules/local/ltr_retriever_postprocess/main.nf' +include { TIR_LEARNER_POSTPROCESS } from '../modules/local/tir_learner_postprocess/main.nf' include { softwareVersionsToYAML } from '../modules/local/utils/main.nf' @@ -83,7 +84,8 @@ workflow EDTA { params.species ) - ch_tirlearner_filtered_gff = TIRLEARNER.out.filtered_gff + ch_tirlearner_fasta = TIRLEARNER.out.fasta + ch_tirlearner_gff = TIRLEARNER.out.gff ch_versions = ch_versions.mix(TIRLEARNER.out.versions.first()) // These can also run in parallel @@ -153,7 +155,7 @@ workflow EDTA { ch_versions = ch_versions.mix(FORMAT_HELITRONSCANNER_OUT.out.versions.first()) // MODULE: LTR_RETRIEVER_POSTPROCESS - ltr_retriever_postprocess_inputs = ch_sanitized_fasta + ch_ltr_retriever_postprocess_inputs = ch_sanitized_fasta | join(ch_pass_list) | join(ch_pass_list_gff) | join(ch_annotation_gff) @@ -169,15 +171,31 @@ workflow EDTA { } LTR_RETRIEVER_POSTPROCESS ( - ltr_retriever_postprocess_inputs.genome, - ltr_retriever_postprocess_inputs.pass, - ltr_retriever_postprocess_inputs.p_gff, - ltr_retriever_postprocess_inputs.defalse, - ltr_retriever_postprocess_inputs.ltr, + ch_ltr_retriever_postprocess_inputs.genome, + ch_ltr_retriever_postprocess_inputs.pass, + ch_ltr_retriever_postprocess_inputs.p_gff, + ch_ltr_retriever_postprocess_inputs.defalse, + ch_ltr_retriever_postprocess_inputs.ltr, ) ch_versions = ch_versions.mix(LTR_RETRIEVER_POSTPROCESS.out.versions.first()) + // MODULE: TIR_LEARNER_POSTPROCESS + ch_tir_learner_postprocess_inputs = ch_sanitized_fasta + | join(ch_tirlearner_fasta) + | join(ch_tirlearner_gff) + | multiMap { meta, fasta, tir_fa, tir_gff -> + genome : [ meta, fasta ] + tir_fa : tir_fa + tir_gff : tir_gff + } + TIR_LEARNER_POSTPROCESS ( + ch_tir_learner_postprocess_inputs.genome, + ch_tir_learner_postprocess_inputs.tir_fa, + ch_tir_learner_postprocess_inputs.tir_gff + ) + + ch_versions = ch_versions.mix(TIR_LEARNER_POSTPROCESS.out.versions.first()) // Function: Save versions ch_versions = ch_versions From 295db65bf3630b3a3096096f0f8c079d58271097 Mon Sep 17 00:00:00 2001 From: Usman Rashid Date: Thu, 12 Dec 2024 17:33:00 +1300 Subject: [PATCH 35/49] Added REPEATMODELER_POSTPROCESS --- bin/setup_RepeatModeler_postprocess.sh | 22 ++++++ conf/modules.config | 10 +++ conf/test.config | 2 +- main.nf | 16 +---- .../repeatmodeler_postprocess/environment.yml | 10 +++ .../local/repeatmodeler_postprocess/main.nf | 69 +++++++++++++++++++ tests/nf-test/small/main.nf.test | 2 +- tests/nf-test/tiny/main.nf.test | 2 +- workflows/edta.nf | 17 ++++- 9 files changed, 131 insertions(+), 19 deletions(-) create mode 100755 bin/setup_RepeatModeler_postprocess.sh create mode 100644 modules/local/repeatmodeler_postprocess/environment.yml create mode 100644 modules/local/repeatmodeler_postprocess/main.nf diff --git a/bin/setup_RepeatModeler_postprocess.sh b/bin/setup_RepeatModeler_postprocess.sh new file mode 100755 index 0000000..a601979 --- /dev/null +++ b/bin/setup_RepeatModeler_postprocess.sh @@ -0,0 +1,22 @@ +#!/usr/bin/env bash + +script_path=$(dirname $0) + +cat > input/RepeatModeler_postprocess.pl << EOF +my \$genome = 'genome'; +my \$threads = $1; + +my \$cleanup_misclas = '$script_path/cleanup_misclas.pl'; +my \$cleanup_tandem = '$script_path/cleanup_tandem.pl'; +my \$output_by_list = '$script_path/output_by_list.pl'; + +my \$TEsorter = ''; # Assumed on PATH +my \$trf = '$(which trf)'; + +EOF + +sed -n 567,579p "$script_path/EDTA_raw.pl" \ + >> input/RepeatModeler_postprocess.pl + +sed -n 585,586p "$script_path/EDTA_raw.pl" \ + >> input/RepeatModeler_postprocess.pl diff --git a/conf/modules.config b/conf/modules.config index a854d4e..6ff3d77 100644 --- a/conf/modules.config +++ b/conf/modules.config @@ -62,5 +62,15 @@ process { saveAs: { filename -> filename.equals('versions.yml') ? null : filename } ] } + + withName: 'EDTA:REPEATMODELER_POSTPROCESS' { + ext.prefix = { "${meta.id}.pp" } + + publishDir = [ + path: { "${params.outdir}/rm2" }, + mode: params.publish_dir_mode, + saveAs: { filename -> filename.equals('versions.yml') ? null : filename } + ] + } } \ No newline at end of file diff --git a/conf/test.config b/conf/test.config index e0a2931..73fe0a6 100644 --- a/conf/test.config +++ b/conf/test.config @@ -7,5 +7,5 @@ process { } params { - genomes = 'https://raw.githubusercontent.com/jguhlin/EDTA/a2cd9a0777e4ac6e39545bacc3e752f94eb2f389/test/genome.fa' + genome = 'https://raw.githubusercontent.com/jguhlin/EDTA/a2cd9a0777e4ac6e39545bacc3e752f94eb2f389/test/genome.fa' } \ No newline at end of file diff --git a/main.nf b/main.nf index fa74a8b..6607f9e 100755 --- a/main.nf +++ b/main.nf @@ -2,26 +2,12 @@ nextflow.enable.dsl = 2 -params.genomes = 'genomes/*' +params.genome = 'genomes/*' params.species = 'others' -params.cds = '' -params.curatedlib = '' -params.rmlib = '' -params.sensitive = false -params.anno = false -params.rmout = '' -params.maxdiv = 40 -params.evaluate = true -params.exclude = '' -params.maxint = 5000 params.outdir = 'results' include { EDTA } from './workflows/edta.nf' -// Test run: -// ./main.nf -profile docker,test -// ./main.nf -profile conda,test - workflow { EDTA() } diff --git a/modules/local/repeatmodeler_postprocess/environment.yml b/modules/local/repeatmodeler_postprocess/environment.yml new file mode 100644 index 0000000..6f8a4fc --- /dev/null +++ b/modules/local/repeatmodeler_postprocess/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: + - bioconda::mdust=2006.10.17 + - bioconda::tesorter=1.4.7 + - bioconda::trf=4.09.1 + - conda-forge::perl=5.32.1 \ No newline at end of file diff --git a/modules/local/repeatmodeler_postprocess/main.nf b/modules/local/repeatmodeler_postprocess/main.nf new file mode 100644 index 0000000..f538422 --- /dev/null +++ b/modules/local/repeatmodeler_postprocess/main.nf @@ -0,0 +1,69 @@ +process REPEATMODELER_POSTPROCESS { + tag "$meta.id" + label 'process_low' + + conda "${moduleDir}/environment.yml" + container "${ workflow.containerEngine == 'singularity' && !task.ext.singularity_pull_docker_container ? + 'https://community-cr-prod.seqera.io/docker/registry/v2/blobs/sha256/cb/cb105ba7d586ab31c1f39feb04c0255a39cc5a55ae7f6ea53f4bf76cdba8a3e5/data': + 'community.wave.seqera.io/library/mdust_tesorter_trf_perl:3424609103d3b065' }" + + input: + tuple val(meta), path(genome, name: 'input/genome') + path "input/genome-families.fa" + + output: + tuple val(meta), path('*.RM2.fa') , emit: rm2_fa + tuple val(meta), path('*.LINE.raw.fa') , emit: line_raw + path "versions.yml" , emit: versions + + when: + task.ext.when == null || task.ext.when + + script: + def prefix = task.ext.prefix ?: "${meta.id}" + def MDUST_VERSION = '2006.10.17' // WARN: Manually update when changing Bioconda assets + if ( "$prefix" == 'genome' ) error "Input and output names are the same, use \"task.ext.prefix\" to disambiguate!" + """ + setup_RepeatModeler_postprocess.sh \\ + $task.cpus + + cd input + + perl RepeatModeler_postprocess.pl + + cd - + + mv \\ + genome.RM2.fa \\ + ${prefix}.RM2.fa + + mv \\ + genome.LINE.raw.fa \\ + ${prefix}.LINE.raw.fa + + cat <<-END_VERSIONS > versions.yml + "${task.process}": + perl: \$(perl -v | sed -n 's|This is perl.*(\\(.*\\)).*|\\1|p') + trf: \$(trf -v |& sed -n 's|.*Version \\(.*\\)|\\1|p') + TEsorter: \$(TEsorter -v | tr -d 'TEsorter ') + mdust: $MDUST_VERSION + END_VERSIONS + """ + + stub: + def prefix = task.ext.prefix ?: "${meta.id}" + def MDUST_VERSION = '2006.10.17' // WARN: Manually update when changing Bioconda assets + if ( "$prefix" == 'genome' ) error "Input and output names are the same, use \"task.ext.prefix\" to disambiguate!" + """ + touch ${prefix}.RM2.fa + touch ${prefix}.LINE.raw.fa + + cat <<-END_VERSIONS > versions.yml + "${task.process}": + perl: \$(perl -v | sed -n 's|This is perl.*(\\(.*\\)).*|\\1|p') + trf: \$(trf -v |& sed -n 's|.*Version \\(.*\\)|\\1|p') + TEsorter: \$(TEsorter -v | tr -d 'TEsorter ') + mdust: $MDUST_VERSION + END_VERSIONS + """ +} \ No newline at end of file diff --git a/tests/nf-test/small/main.nf.test b/tests/nf-test/small/main.nf.test index d53e28f..01353a0 100644 --- a/tests/nf-test/small/main.nf.test +++ b/tests/nf-test/small/main.nf.test @@ -7,7 +7,7 @@ nextflow_pipeline { when { params { - genomes = "https://raw.githubusercontent.com/jguhlin/EDTA/a2cd9a0777e4ac6e39545bacc3e752f94eb2f389/test/genome.fa" + genome = "https://raw.githubusercontent.com/jguhlin/EDTA/a2cd9a0777e4ac6e39545bacc3e752f94eb2f389/test/genome.fa" outdir = "$outputDir" } } diff --git a/tests/nf-test/tiny/main.nf.test b/tests/nf-test/tiny/main.nf.test index 5022c49..0a92bd5 100644 --- a/tests/nf-test/tiny/main.nf.test +++ b/tests/nf-test/tiny/main.nf.test @@ -7,7 +7,7 @@ nextflow_pipeline { when { params { - genomes = "https://raw.githubusercontent.com/nf-core/test-datasets/modules/data/genomics/sarscov2/genome/genome.fasta" + genome = "https://raw.githubusercontent.com/nf-core/test-datasets/modules/data/genomics/sarscov2/genome/genome.fasta" outdir = "$outputDir" } } diff --git a/workflows/edta.nf b/workflows/edta.nf index 58f96d2..ea91e2e 100644 --- a/workflows/edta.nf +++ b/workflows/edta.nf @@ -11,6 +11,7 @@ include { FASTA_HELITRONSCANNER_SCAN_DRAW } from '../subworkflows/gallvp/fasta include { FORMAT_HELITRONSCANNER_OUT } from '../modules/local/format_helitronscanner_out/main.nf' include { LTR_RETRIEVER_POSTPROCESS } from '../modules/local/ltr_retriever_postprocess/main.nf' include { TIR_LEARNER_POSTPROCESS } from '../modules/local/tir_learner_postprocess/main.nf' +include { REPEATMODELER_POSTPROCESS } from '../modules/local/repeatmodeler_postprocess/main.nf' include { softwareVersionsToYAML } from '../modules/local/utils/main.nf' @@ -22,7 +23,7 @@ workflow EDTA { ch_versions = Channel.empty() - ch_genome = Channel.fromPath(params.genomes) + ch_genome = Channel.fromPath(params.genome) // Create a meta object for each genome ch_meta_genome = ch_genome.map { genome -> @@ -197,6 +198,20 @@ workflow EDTA { ch_versions = ch_versions.mix(TIR_LEARNER_POSTPROCESS.out.versions.first()) + // MODULE: REPEATMODELER_POSTPROCESS + ch_repeatmodeler_postprocess_inputs = ch_sanitized_fasta + | join(ch_repeatmodeler_fasta) + | multiMap { meta, fasta, rm_fa -> + genome : [ meta, fasta ] + rm_fa : rm_fa + } + REPEATMODELER_POSTPROCESS ( + ch_repeatmodeler_postprocess_inputs.genome, + ch_repeatmodeler_postprocess_inputs.rm_fa, + ) + + ch_versions = ch_versions.mix(REPEATMODELER_POSTPROCESS.out.versions.first()) + // Function: Save versions ch_versions = ch_versions | unique From d36deb0c4f04da612747d91b1440f53919a87e73 Mon Sep 17 00:00:00 2001 From: Usman Rashid Date: Thu, 12 Dec 2024 18:20:16 +1300 Subject: [PATCH 36/49] Updated snapshots --- tests/nf-test/small/main.nf.test | 2 ++ tests/nf-test/small/main.nf.test.snap | 13 +++++++++++-- 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/tests/nf-test/small/main.nf.test b/tests/nf-test/small/main.nf.test index 01353a0..5e111fb 100644 --- a/tests/nf-test/small/main.nf.test +++ b/tests/nf-test/small/main.nf.test @@ -23,6 +23,8 @@ nextflow_pipeline { 'ltr/*.pp.LTR.raw.fa', 'tir/*.pp.TIR.intact.raw.fa', 'tir/*.pp.TIR.intact.raw.gff3', + 'rm2/*.pp.LINE.raw.fa', + 'rm2/*.pp.RM2.fa', ], null, ['**'] diff --git a/tests/nf-test/small/main.nf.test.snap b/tests/nf-test/small/main.nf.test.snap index 1469232..636069c 100644 --- a/tests/nf-test/small/main.nf.test.snap +++ b/tests/nf-test/small/main.nf.test.snap @@ -2,7 +2,7 @@ "small genome": { "content": [ { - "successful tasks": 18, + "successful tasks": 19, "versions": { "ANNOSINE": { "annosine": "2.0.7" @@ -51,6 +51,12 @@ "REPEATMODELER_BUILDDATABASE": { "repeatmodeler": "2.0.5" }, + "REPEATMODELER_POSTPROCESS": { + "perl": "v5.32.1", + "trf": 4.09, + "TEsorter": "1.4.7", + "mdust": "2006.10.17" + }, "REPEATMODELER_REPEATMODELER": { "repeatmodeler": "2.0.5" }, @@ -79,6 +85,9 @@ "ltr/genome.pp.LTR.raw.fa", "ltr/genome.pp.genome.LTR.intact.raw.fa", "pipeline_info", + "rm2", + "rm2/genome.pp.LINE.raw.fa", + "rm2/genome.pp.RM2.fa", "tir", "tir/genome.pp.TIR.intact.raw.bed", "tir/genome.pp.TIR.intact.raw.fa", @@ -90,6 +99,6 @@ "nf-test": "0.9.2", "nextflow": "24.04.4" }, - "timestamp": "2024-12-12T13:06:43.800123" + "timestamp": "2024-12-12T18:08:43.061775" } } \ No newline at end of file From f30f663ccd46bb233ed0d6e46d45cc7a1877b328 Mon Sep 17 00:00:00 2001 From: Usman Rashid Date: Fri, 13 Dec 2024 11:13:34 +1300 Subject: [PATCH 37/49] Added HELITRONSCANNER_POSTPROCESS --- bin/setup_HelitronScanner_postprocess.sh | 27 ++ conf/modules.config | 56 +-- conf/test.config | 6 +- .../environment.yml | 10 + .../local/helitronscanner_postprocess/main.nf | 82 +++++ modules/local/utils/main.nf | 23 +- nextflow.config | 8 +- tests/nf-test/small/main.nf.test | 15 +- tests/nf-test/small/main.nf.test.snap | 46 ++- tests/nf-test/tiny/main.nf.test.snap | 11 +- workflows/edta.nf | 324 ++++++++++-------- 11 files changed, 405 insertions(+), 203 deletions(-) create mode 100755 bin/setup_HelitronScanner_postprocess.sh create mode 100644 modules/local/helitronscanner_postprocess/environment.yml create mode 100644 modules/local/helitronscanner_postprocess/main.nf diff --git a/bin/setup_HelitronScanner_postprocess.sh b/bin/setup_HelitronScanner_postprocess.sh new file mode 100755 index 0000000..59df997 --- /dev/null +++ b/bin/setup_HelitronScanner_postprocess.sh @@ -0,0 +1,27 @@ +#!/usr/bin/env bash + +script_path=$(dirname $0) + +cat > input/HelitronScanner_postprocess.pl << EOF +my \$genome = 'genome'; +my \$threads = $1; + +my \$flank_filter = "$script_path/flanking_filter.pl"; +my \$output_by_list = '$script_path/output_by_list.pl'; +my \$cleanup_tandem = '$script_path/cleanup_tandem.pl'; +my \$cleanup_misclas = '$script_path/cleanup_misclas.pl'; +my \$make_bed = "$script_path/make_bed_with_intact.pl"; +my \$bed2gff = "$script_path/bed2gff.pl"; + +my \$blastplus = ''; # Assumed on PATH +my \$mdust = ''; # Assumed on PATH +my \$trf = '$(which trf)'; +my \$TEsorter = ''; # Assumed on PATH + +EOF + +sed -n 705,722p "$script_path/EDTA_raw.pl" \ + >> input/HelitronScanner_postprocess.pl + +sed -n 726,729p "$script_path/EDTA_raw.pl" \ + >> input/HelitronScanner_postprocess.pl diff --git a/conf/modules.config b/conf/modules.config index 6ff3d77..ff251a2 100644 --- a/conf/modules.config +++ b/conf/modules.config @@ -11,6 +11,26 @@ process { withName: 'EDTA:CAT_CAT' { ext.prefix = { "${meta.id}_ltrharvest_ltrfinder.tabout" } } + + withName: 'EDTA:LTR_RETRIEVER_POSTPROCESS' { + ext.prefix = { "${meta.id}.pp" } + + publishDir = [ + path: { "${params.outdir}/raw/ltr" }, + mode: params.publish_dir_mode, + saveAs: { filename -> filename.equals('versions.yml') ? null : filename } + ] + } + + withName: 'EDTA:TIR_LEARNER_POSTPROCESS' { + ext.prefix = { "${meta.id}.pp" } + + publishDir = [ + path: { "${params.outdir}/raw/tir" }, + mode: params.publish_dir_mode, + saveAs: { filename -> filename.equals('versions.yml') ? null : filename } + ] + } withName: 'EDTA:ANNOSINE' { ext.args = '--num_alignments 50000 -rpm 0 --copy_number 3 --shift 100 -auto 1' @@ -20,6 +40,16 @@ process { ext.args = '-engine ncbi' } + withName: 'EDTA:REPEATMODELER_POSTPROCESS' { + ext.prefix = { "${meta.id}.pp" } + + publishDir = [ + path: { "${params.outdir}/raw/line" }, + mode: params.publish_dir_mode, + saveAs: { filename -> filename.equals('versions.yml') ? null : filename } + ] + } + withName: 'EDTA:FASTA_HELITRONSCANNER_SCAN_DRAW:HELITRONSCANNER_DRAW' { ext.args = '-pure_helitron' } @@ -40,34 +70,18 @@ process { } withName: 'EDTA:FORMAT_HELITRONSCANNER_OUT' { - ext.args = '-sitefilter 1 -minscore 12 -keepshorter 1 -extlen 30 -extout 1' + ext.args = '-sitefilter 1 -minscore 12 -keepshorter 1 -extout 0' } - withName: 'EDTA:LTR_RETRIEVER_POSTPROCESS' { - ext.prefix = { "${meta.id}.pp" } - - publishDir = [ - path: { "${params.outdir}/ltr" }, - mode: params.publish_dir_mode, - saveAs: { filename -> filename.equals('versions.yml') ? null : filename } - ] - } - - withName: 'EDTA:TIR_LEARNER_POSTPROCESS' { - ext.prefix = { "${meta.id}.pp" } - - publishDir = [ - path: { "${params.outdir}/tir" }, - mode: params.publish_dir_mode, - saveAs: { filename -> filename.equals('versions.yml') ? null : filename } - ] + withName: 'EDTA:FORMAT_HELITRONSCANNER_OUT_EXT' { + ext.args = '-sitefilter 1 -minscore 12 -keepshorter 1 -extlen 30 -extout 1' } - withName: 'EDTA:REPEATMODELER_POSTPROCESS' { + withName: 'EDTA:HELITRONSCANNER_POSTPROCESS' { ext.prefix = { "${meta.id}.pp" } publishDir = [ - path: { "${params.outdir}/rm2" }, + path: { "${params.outdir}/raw/helitron" }, mode: params.publish_dir_mode, saveAs: { filename -> filename.equals('versions.yml') ? null : filename } ] diff --git a/conf/test.config b/conf/test.config index 73fe0a6..29ab554 100644 --- a/conf/test.config +++ b/conf/test.config @@ -1,11 +1,11 @@ process { resourceLimits = [ - cpus: 4, - memory: '15.GB', + cpus: 10, + memory: '32.GB', time: '1.h' ] } params { - genome = 'https://raw.githubusercontent.com/jguhlin/EDTA/a2cd9a0777e4ac6e39545bacc3e752f94eb2f389/test/genome.fa' + genome = 'https://raw.githubusercontent.com/nf-core/test-datasets/modules/data/genomics/eukaryotes/actinidia_chinensis/genome/chr1/genome.fasta.gz' } \ No newline at end of file diff --git a/modules/local/helitronscanner_postprocess/environment.yml b/modules/local/helitronscanner_postprocess/environment.yml new file mode 100644 index 0000000..6f8a4fc --- /dev/null +++ b/modules/local/helitronscanner_postprocess/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: + - bioconda::mdust=2006.10.17 + - bioconda::tesorter=1.4.7 + - bioconda::trf=4.09.1 + - conda-forge::perl=5.32.1 \ No newline at end of file diff --git a/modules/local/helitronscanner_postprocess/main.nf b/modules/local/helitronscanner_postprocess/main.nf new file mode 100644 index 0000000..d0eb6a3 --- /dev/null +++ b/modules/local/helitronscanner_postprocess/main.nf @@ -0,0 +1,82 @@ +process HELITRONSCANNER_POSTPROCESS { + tag "$meta.id" + label 'process_low' + + conda "${moduleDir}/environment.yml" + container "${ workflow.containerEngine == 'singularity' && !task.ext.singularity_pull_docker_container ? + 'https://community-cr-prod.seqera.io/docker/registry/v2/blobs/sha256/cb/cb105ba7d586ab31c1f39feb04c0255a39cc5a55ae7f6ea53f4bf76cdba8a3e5/data': + 'community.wave.seqera.io/library/mdust_tesorter_trf_perl:3424609103d3b065' }" + + input: + tuple val(meta), path(genome, name: 'input/genome') + path "input/genome.HelitronScanner.filtered.fa" + path "input/genome.HelitronScanner.filtered.ext.fa" + + output: + tuple val(meta), path('*.Helitron.intact.raw.fa.anno.list') , emit: anno_list + tuple val(meta), path('*.Helitron.intact.raw.bed') , emit: raw_bed + tuple val(meta), path('*.Helitron.intact.raw.gff3') , emit: raw_gff + tuple val(meta), path('*.Helitron.intact.raw.fa') , emit: raw_fa + path "versions.yml" , emit: versions + + when: + task.ext.when == null || task.ext.when + + script: + def prefix = task.ext.prefix ?: "${meta.id}" + def MDUST_VERSION = '2006.10.17' // WARN: Manually update when changing Bioconda assets + if ( "$prefix" == 'genome' ) error "Input and output names are the same, use \"task.ext.prefix\" to disambiguate!" + """ + setup_HelitronScanner_postprocess.sh \\ + $task.cpus + + cd input + + perl HelitronScanner_postprocess.pl + + cd - + + mv \\ + genome.Helitron.intact.raw.fa.anno.list \\ + ${prefix}.Helitron.intact.raw.fa.anno.list + + mv \\ + genome.Helitron.intact.raw.bed \\ + ${prefix}.Helitron.intact.raw.bed + + mv \\ + genome.Helitron.intact.raw.gff3 \\ + ${prefix}.Helitron.intact.raw.gff3 + + mv \\ + genome.Helitron.intact.raw.fa \\ + ${prefix}.Helitron.intact.raw.fa + + cat <<-END_VERSIONS > versions.yml + "${task.process}": + perl: \$(perl -v | sed -n 's|This is perl.*(\\(.*\\)).*|\\1|p') + trf: \$(trf -v |& sed -n 's|.*Version \\(.*\\)|\\1|p') + TEsorter: \$(TEsorter -v | tr -d 'TEsorter ') + mdust: $MDUST_VERSION + END_VERSIONS + """ + + stub: + def prefix = task.ext.prefix ?: "${meta.id}" + def MDUST_VERSION = '2006.10.17' // WARN: Manually update when changing Bioconda assets + if ( "$prefix" == 'genome' ) error "Input and output names are the same, use \"task.ext.prefix\" to disambiguate!" + """ + touch ${prefix}.Helitron.intact.raw.fa.anno.list + touch ${prefix}.Helitron.intact.raw.bed + touch ${prefix}.Helitron.intact.raw.gff3 + touch ${prefix}.Helitron.intact.raw.fa + + cat <<-END_VERSIONS > versions.yml + "${task.process}": + perl: \$(perl -v | sed -n 's|This is perl.*(\\(.*\\)).*|\\1|p') + trf: \$(trf -v |& sed -n 's|.*Version \\(.*\\)|\\1|p') + TEsorter: \$(TEsorter -v | tr -d 'TEsorter ') + mdust: $MDUST_VERSION + END_VERSIONS + """ +} \ No newline at end of file diff --git a/modules/local/utils/main.nf b/modules/local/utils/main.nf index fee91f3..dea2b26 100644 --- a/modules/local/utils/main.nf +++ b/modules/local/utils/main.nf @@ -41,4 +41,25 @@ def workflowVersionToYAML() { // def softwareVersionsToYAML(ch_versions) { return ch_versions.unique().map { version -> processVersionsFromYAML(version) }.unique().mix(Channel.of(workflowVersionToYAML())) -} \ No newline at end of file +} + +// +// Gen a working sample ID from a file name +// +def idFromFileName(fileName) { + + def trial = ( fileName + ).replaceFirst( + /\.f(ast)?q$/, '' + ).replaceFirst( + /\.f(asta|sa|a|as|aa|na)?$/, '' + ).replaceFirst( + /\.gff(3)?$/, '' + ).replaceFirst( + /\.gz$/, '' + ) + + if ( trial == fileName ) { return fileName } + + return idFromFileName ( trial ) +} diff --git a/nextflow.config b/nextflow.config index 7eb1232..655993a 100644 --- a/nextflow.config +++ b/nextflow.config @@ -51,8 +51,8 @@ process { process { resourceLimits = [ cpus: 12, - memory: '16.GB', - time: '1.hour' + memory: '72.GB', + time: '7.days' ] } @@ -95,9 +95,9 @@ conda { } manifest { - name = 'jguhlin/EDTA' + name = 'oushujun/EDTA' description = 'Extensive de-novo TE Annotator on Nextflow' - author = 'Usman Rashid, Joseph Guhlin & Shujun Ou' + author = 'Shujun Ou, Usman Rashid & Joseph Guhlin' version = '0.1.0dev' nextflowVersion = '!>=23.04.0' } diff --git a/tests/nf-test/small/main.nf.test b/tests/nf-test/small/main.nf.test index 5e111fb..d0cd760 100644 --- a/tests/nf-test/small/main.nf.test +++ b/tests/nf-test/small/main.nf.test @@ -19,12 +19,15 @@ nextflow_pipeline { false, [ 'pipeline_info/*.{html,json,txt,yml}', - 'ltr/*.pp.LTR.intact.raw.gff3', - 'ltr/*.pp.LTR.raw.fa', - 'tir/*.pp.TIR.intact.raw.fa', - 'tir/*.pp.TIR.intact.raw.gff3', - 'rm2/*.pp.LINE.raw.fa', - 'rm2/*.pp.RM2.fa', + 'raw/ltr/*.pp.LTR.intact.raw.gff3', + 'raw/ltr/*.pp.LTR.raw.fa', + 'raw/tir/*.pp.TIR.intact.raw.fa', + 'raw/tir/*.pp.TIR.intact.raw.gff3', + 'raw/line/*.pp.LINE.raw.fa', + 'raw/line/*.pp.RM2.fa', + 'raw/helitron/*.Helitron.intact.raw.fa', + 'raw/helitron/*.Helitron.intact.raw.fa.anno.list', + 'raw/helitron/*.Helitron.intact.raw.gff3' ], null, ['**'] diff --git a/tests/nf-test/small/main.nf.test.snap b/tests/nf-test/small/main.nf.test.snap index 636069c..e13891a 100644 --- a/tests/nf-test/small/main.nf.test.snap +++ b/tests/nf-test/small/main.nf.test.snap @@ -2,7 +2,7 @@ "small genome": { "content": [ { - "successful tasks": 19, + "successful tasks": 21, "versions": { "ANNOSINE": { "annosine": "2.0.7" @@ -13,12 +13,21 @@ "FORMAT_HELITRONSCANNER_OUT": { "perl": "v5.32.1" }, + "FORMAT_HELITRONSCANNER_OUT_EXT": { + "perl": "v5.32.1" + }, "HELITRONSCANNER_DRAW": { "helitronscanner": "V1.1" }, "HELITRONSCANNER_DRAW_RC": { "helitronscanner": "V1.1" }, + "HELITRONSCANNER_POSTPROCESS": { + "perl": "v5.32.1", + "trf": 4.09, + "TEsorter": "1.4.7", + "mdust": "2006.10.17" + }, "HELITRONSCANNER_SCAN_HEAD": { "helitronscanner": "V1.1" }, @@ -70,28 +79,35 @@ "mdust": "2006.10.17" }, "Workflow": { - "jguhlin/EDTA": "v0.1.0dev" + "oushujun/EDTA": "v0.1.0dev" } }, "stable paths": [ + "genome.pp.Helitron.intact.raw.bed:md5,a8b170a1ee8d36c7f8e3af5ba1e43cb6", "genome.pp.LTR.intact.raw.fa.anno.list:md5,51261f020350b448acdf89d8fb625448", "genome.pp.genome.LTR.intact.raw.fa:md5,ae2336f498897d1af4e8d3be28bb3f25", "genome.pp.TIR.intact.raw.bed:md5,12406184046b11edc2ddadbc82765966" ], "stable names": [ - "ltr", - "ltr/genome.pp.LTR.intact.raw.fa.anno.list", - "ltr/genome.pp.LTR.intact.raw.gff3", - "ltr/genome.pp.LTR.raw.fa", - "ltr/genome.pp.genome.LTR.intact.raw.fa", "pipeline_info", - "rm2", - "rm2/genome.pp.LINE.raw.fa", - "rm2/genome.pp.RM2.fa", - "tir", - "tir/genome.pp.TIR.intact.raw.bed", - "tir/genome.pp.TIR.intact.raw.fa", - "tir/genome.pp.TIR.intact.raw.gff3" + "raw", + "raw/helitron", + "raw/helitron/genome.pp.Helitron.intact.raw.bed", + "raw/helitron/genome.pp.Helitron.intact.raw.fa", + "raw/helitron/genome.pp.Helitron.intact.raw.fa.anno.list", + "raw/helitron/genome.pp.Helitron.intact.raw.gff3", + "raw/line", + "raw/line/genome.pp.LINE.raw.fa", + "raw/line/genome.pp.RM2.fa", + "raw/ltr", + "raw/ltr/genome.pp.LTR.intact.raw.fa.anno.list", + "raw/ltr/genome.pp.LTR.intact.raw.gff3", + "raw/ltr/genome.pp.LTR.raw.fa", + "raw/ltr/genome.pp.genome.LTR.intact.raw.fa", + "raw/tir", + "raw/tir/genome.pp.TIR.intact.raw.bed", + "raw/tir/genome.pp.TIR.intact.raw.fa", + "raw/tir/genome.pp.TIR.intact.raw.gff3" ] } ], @@ -99,6 +115,6 @@ "nf-test": "0.9.2", "nextflow": "24.04.4" }, - "timestamp": "2024-12-12T18:08:43.061775" + "timestamp": "2024-12-13T11:02:40.702113" } } \ No newline at end of file diff --git a/tests/nf-test/tiny/main.nf.test.snap b/tests/nf-test/tiny/main.nf.test.snap index 20a57ef..896e0b8 100644 --- a/tests/nf-test/tiny/main.nf.test.snap +++ b/tests/nf-test/tiny/main.nf.test.snap @@ -2,7 +2,7 @@ "tiny genome": { "content": [ { - "successful tasks": 14, + "successful tasks": 15, "versions": { "ANNOSINE": { "annosine": "2.0.7" @@ -13,6 +13,9 @@ "FORMAT_HELITRONSCANNER_OUT": { "perl": "v5.32.1" }, + "FORMAT_HELITRONSCANNER_OUT_EXT": { + "perl": "v5.32.1" + }, "HELITRONSCANNER_DRAW": { "helitronscanner": "V1.1" }, @@ -46,7 +49,7 @@ "TIR-Learner": "v3.0.2 by Tianyu (Sky) Lu (tlu83@wisc.edu) published under GPLv3" }, "Workflow": { - "jguhlin/EDTA": "v0.1.0dev" + "oushujun/EDTA": "v0.1.0dev" } }, "stable paths": [ @@ -55,9 +58,9 @@ } ], "meta": { - "nf-test": "0.9.0", + "nf-test": "0.9.2", "nextflow": "24.04.4" }, - "timestamp": "2024-12-03T14:16:25.986375" + "timestamp": "2024-12-13T09:54:27.064885" } } \ No newline at end of file diff --git a/workflows/edta.nf b/workflows/edta.nf index ea91e2e..686b50d 100644 --- a/workflows/edta.nf +++ b/workflows/edta.nf @@ -1,68 +1,76 @@ -include { SANITIZE_HEADERS } from '../modules/local/sanitize/main.nf' -include { LTRHARVEST } from '../modules/nf-core/ltrharvest/main.nf' -include { LTRFINDER } from '../modules/nf-core/ltrfinder/main.nf' -include { CAT_CAT } from '../modules/nf-core/cat/cat/main.nf' -include { LTRRETRIEVER_LTRRETRIEVER } from '../modules/nf-core/ltrretriever/ltrretriever/main.nf' -include { TIRLEARNER } from '../modules/gallvp/tirlearner/main.nf' -include { ANNOSINE } from '../modules/gallvp/annosine/main.nf' -include { REPEATMODELER_BUILDDATABASE } from '../modules/nf-core/repeatmodeler/builddatabase/main.nf' -include { REPEATMODELER_REPEATMODELER } from '../modules/nf-core/repeatmodeler/repeatmodeler/main.nf' -include { FASTA_HELITRONSCANNER_SCAN_DRAW } from '../subworkflows/gallvp/fasta_helitronscanner_scan_draw/main.nf' -include { FORMAT_HELITRONSCANNER_OUT } from '../modules/local/format_helitronscanner_out/main.nf' -include { LTR_RETRIEVER_POSTPROCESS } from '../modules/local/ltr_retriever_postprocess/main.nf' -include { TIR_LEARNER_POSTPROCESS } from '../modules/local/tir_learner_postprocess/main.nf' -include { REPEATMODELER_POSTPROCESS } from '../modules/local/repeatmodeler_postprocess/main.nf' - -include { softwareVersionsToYAML } from '../modules/local/utils/main.nf' +include { SANITIZE_HEADERS } from '../modules/local/sanitize/main.nf' + +include { LTRHARVEST } from '../modules/nf-core/ltrharvest/main.nf' +include { LTRFINDER } from '../modules/nf-core/ltrfinder/main.nf' +include { CAT_CAT } from '../modules/nf-core/cat/cat/main.nf' +include { LTRRETRIEVER_LTRRETRIEVER } from '../modules/nf-core/ltrretriever/ltrretriever/main.nf' +include { LTR_RETRIEVER_POSTPROCESS } from '../modules/local/ltr_retriever_postprocess/main.nf' + +include { TIRLEARNER } from '../modules/gallvp/tirlearner/main.nf' +include { TIR_LEARNER_POSTPROCESS } from '../modules/local/tir_learner_postprocess/main.nf' + +include { ANNOSINE } from '../modules/gallvp/annosine/main.nf' + +include { REPEATMODELER_BUILDDATABASE } from '../modules/nf-core/repeatmodeler/builddatabase/main.nf' +include { REPEATMODELER_REPEATMODELER } from '../modules/nf-core/repeatmodeler/repeatmodeler/main.nf' +include { REPEATMODELER_POSTPROCESS } from '../modules/local/repeatmodeler_postprocess/main.nf' + +include { FASTA_HELITRONSCANNER_SCAN_DRAW } from '../subworkflows/gallvp/fasta_helitronscanner_scan_draw/main.nf' +include { FORMAT_HELITRONSCANNER_OUT } from '../modules/local/format_helitronscanner_out/main.nf' +include { FORMAT_HELITRONSCANNER_OUT as FORMAT_HELITRONSCANNER_OUT_EXT } from '../modules/local/format_helitronscanner_out/main.nf' +include { HELITRONSCANNER_POSTPROCESS } from '../modules/local/helitronscanner_postprocess/main.nf' + + + +include { softwareVersionsToYAML } from '../modules/local/utils/main.nf' +include { idFromFileName } from '../modules/local/utils/main.nf' workflow EDTA { main: // Versions channel - ch_versions = Channel.empty() + ch_versions = Channel.empty() - ch_genome = Channel.fromPath(params.genome) - - // Create a meta object for each genome - ch_meta_genome = ch_genome.map { genome -> - def meta = [:] - meta.id = genome.baseName - - [ meta, genome ] - } + ch_genome = Channel.fromPath(params.genome) + | map { genome -> + def meta = [:] + meta.id = idFromFileName ( genome.baseName ) + + [ meta, genome ] + } // MODULE: SANITIZE_HEADERS - SANITIZE_HEADERS ( ch_meta_genome ) + SANITIZE_HEADERS ( ch_genome ) - ch_sanitized_fasta = SANITIZE_HEADERS.out.fasta + ch_sanitized_fasta = SANITIZE_HEADERS.out.fasta // MODULE: LTRHARVEST LTRHARVEST ( ch_sanitized_fasta ) - ch_ltrharvest_scn = LTRHARVEST.out.scn + ch_ltrharvest_scn = LTRHARVEST.out.scn - ch_versions = ch_versions.mix(LTRHARVEST.out.versions) + ch_versions = ch_versions.mix(LTRHARVEST.out.versions) // MODULE: LTRFINDER LTRFINDER { ch_sanitized_fasta } - ch_ltrfinder_scn = LTRFINDER.out.scn + ch_ltrfinder_scn = LTRFINDER.out.scn - ch_versions = ch_versions.mix(LTRFINDER.out.versions) + ch_versions = ch_versions.mix(LTRFINDER.out.versions) // MODULE: CAT_CAT - ch_cat_cat_inputs = ch_ltrharvest_scn - | join(ch_ltrfinder_scn) - | map { meta, harvested, found -> [ meta, [ harvested, found ] ] } + ch_cat_cat_inputs = ch_ltrharvest_scn + | join(ch_ltrfinder_scn) + | map { meta, harvested, found -> [ meta, [ harvested, found ] ] } CAT_CAT ( ch_cat_cat_inputs ) - ch_ltr_candidates = CAT_CAT.out.file_out - ch_versions = ch_versions.mix(CAT_CAT.out.versions.first()) + ch_ltr_candidates = CAT_CAT.out.file_out + ch_versions = ch_versions.mix(CAT_CAT.out.versions.first()) // MODULE: LTRRETRIEVER_LTRRETRIEVER - ch_ltrretriever_inputs = ch_sanitized_fasta.join(ch_ltr_candidates) + ch_ltrretriever_inputs = ch_sanitized_fasta.join(ch_ltr_candidates) LTRRETRIEVER_LTRRETRIEVER ( ch_ltrretriever_inputs.map { meta, fasta, _ltr -> [ meta, fasta ] }, @@ -72,12 +80,33 @@ workflow EDTA { [] ) - ch_pass_list = LTRRETRIEVER_LTRRETRIEVER.out.pass_list - ch_pass_list_gff = LTRRETRIEVER_LTRRETRIEVER.out.pass_list_gff - ch_annotation_gff = LTRRETRIEVER_LTRRETRIEVER.out.annotation_gff - ch_defalse = LTRRETRIEVER_LTRRETRIEVER.out.defalse - ch_ltrlib = LTRRETRIEVER_LTRRETRIEVER.out.ltrlib - ch_versions = ch_versions.mix(LTRRETRIEVER_LTRRETRIEVER.out.versions.first()) + ch_versions = ch_versions.mix(LTRRETRIEVER_LTRRETRIEVER.out.versions.first()) + + // MODULE: LTR_RETRIEVER_POSTPROCESS + ch_ltr_retriever_postprocess_inputs = ch_sanitized_fasta + | join ( LTRRETRIEVER_LTRRETRIEVER.out.pass_list ) + | join ( LTRRETRIEVER_LTRRETRIEVER.out.pass_list_gff ) + | join ( LTRRETRIEVER_LTRRETRIEVER.out.annotation_gff ) + | join ( LTRRETRIEVER_LTRRETRIEVER.out.defalse ) + | join ( LTRRETRIEVER_LTRRETRIEVER.out.ltrlib ) + | multiMap { meta, fasta, pass, p_gff, a_gff, defalse, ltr -> + genome: [ meta, fasta ] + pass : pass + p_gff : p_gff + a_gff : a_gff + defalse : defalse + ltr : ltr + } + + LTR_RETRIEVER_POSTPROCESS ( + ch_ltr_retriever_postprocess_inputs.genome, + ch_ltr_retriever_postprocess_inputs.pass, + ch_ltr_retriever_postprocess_inputs.p_gff, + ch_ltr_retriever_postprocess_inputs.defalse, + ch_ltr_retriever_postprocess_inputs.ltr, + ) + + ch_versions = ch_versions.mix(LTR_RETRIEVER_POSTPROCESS.out.versions.first()) // MODULE: TIRLEARNER TIRLEARNER ( @@ -85,11 +114,25 @@ workflow EDTA { params.species ) - ch_tirlearner_fasta = TIRLEARNER.out.fasta - ch_tirlearner_gff = TIRLEARNER.out.gff - ch_versions = ch_versions.mix(TIRLEARNER.out.versions.first()) + ch_versions = ch_versions.mix(TIRLEARNER.out.versions.first()) + + // MODULE: TIR_LEARNER_POSTPROCESS + ch_tir_learner_postprocess_inputs = ch_sanitized_fasta + | join ( TIRLEARNER.out.fasta ) + | join ( TIRLEARNER.out.gff ) + | multiMap { meta, fasta, tir_fa, tir_gff -> + genome : [ meta, fasta ] + tir_fa : tir_fa + tir_gff : tir_gff + } + TIR_LEARNER_POSTPROCESS ( + ch_tir_learner_postprocess_inputs.genome, + ch_tir_learner_postprocess_inputs.tir_fa, + ch_tir_learner_postprocess_inputs.tir_gff + ) + + ch_versions = ch_versions.mix(TIR_LEARNER_POSTPROCESS.out.versions.first()) - // These can also run in parallel // MODULE: ANNOSINE ANNOSINE ( ch_sanitized_fasta, @@ -97,55 +140,64 @@ workflow EDTA { ) // Currently it's a topic, so need to fix that - ch_versions = ch_versions.mix(ANNOSINE.out.versions) - cb_annosine_seed_sine = ANNOSINE.out.fa + ch_versions = ch_versions.mix(ANNOSINE.out.versions) + ch_annosine_seed_sine = ANNOSINE.out.fa // MODULE: REPEATMODELER_BUILDDATABASE - ch_repeatmodeler_inputs = ch_sanitized_fasta - | map { meta, fasta -> - def size = fasta.size() - def size_threshold = 100_000 // bytes -> bp - - // TODO: Not the best way to set a size threshold - // but it is simple - // This is needed to avoid, - // Error: Database genome is not large enough ( minimum 40000 bp ) to process with RepeatModeler. - if ( size < size_threshold ) { - log.warn "RepeatModeler is skipped for genome '${meta.id}' as it is smaller than ${size_threshold} bytes" - return null - } - - return [ meta, fasta ] - } - | filter { it } + ch_repeatmodeler_inputs = ch_sanitized_fasta + | map { meta, fasta -> + def size = fasta.size() + def size_threshold = 100_000 // bytes -> bp + + if ( size < size_threshold ) { + log.warn "RepeatModeler is skipped for genome '${meta.id}' as it is smaller than ${size_threshold} bytes" + return null + } + + return [ meta, fasta ] + } + | filter { it } REPEATMODELER_BUILDDATABASE ( ch_repeatmodeler_inputs ) - ch_repeatmodeler_db = REPEATMODELER_BUILDDATABASE.out.db - ch_versions = ch_versions.mix(REPEATMODELER_BUILDDATABASE.out.versions.first()) + ch_repeatmodeler_db = REPEATMODELER_BUILDDATABASE.out.db + ch_versions = ch_versions.mix(REPEATMODELER_BUILDDATABASE.out.versions.first()) // MODULE: REPEATMODELER_REPEATMODELER REPEATMODELER_REPEATMODELER ( ch_repeatmodeler_db ) - ch_repeatmodeler_fasta = REPEATMODELER_REPEATMODELER.out.fasta - ch_versions = ch_versions.mix(REPEATMODELER_REPEATMODELER.out.versions.first()) + ch_versions = ch_versions.mix(REPEATMODELER_REPEATMODELER.out.versions.first()) + + // MODULE: REPEATMODELER_POSTPROCESS + ch_repeatmodeler_postprocess_inputs = ch_sanitized_fasta + | join ( REPEATMODELER_REPEATMODELER.out.fasta ) + | multiMap { meta, fasta, rm_fa -> + genome : [ meta, fasta ] + rm_fa : rm_fa + } + REPEATMODELER_POSTPROCESS ( + ch_repeatmodeler_postprocess_inputs.genome, + ch_repeatmodeler_postprocess_inputs.rm_fa, + ) + + ch_versions = ch_versions.mix(REPEATMODELER_POSTPROCESS.out.versions.first()) // MODULE: FASTA_HELITRONSCANNER_SCAN_DRAW FASTA_HELITRONSCANNER_SCAN_DRAW ( ch_sanitized_fasta ) - ch_helitronscanner_draw = FASTA_HELITRONSCANNER_SCAN_DRAW.out.helitronscanner_draw - ch_helitronscanner_draw_rc = FASTA_HELITRONSCANNER_SCAN_DRAW.out.helitronscanner_draw_rc - ch_versions = ch_versions.mix(FASTA_HELITRONSCANNER_SCAN_DRAW.out.versions) + ch_helitronscanner_draw = FASTA_HELITRONSCANNER_SCAN_DRAW.out.helitronscanner_draw + ch_helitronscanner_draw_rc = FASTA_HELITRONSCANNER_SCAN_DRAW.out.helitronscanner_draw_rc + ch_versions = ch_versions.mix(FASTA_HELITRONSCANNER_SCAN_DRAW.out.versions) // MODULE: FORMAT_HELITRONSCANNER_OUT - ch_format_helitronscanner_inputs = ch_sanitized_fasta - | join(ch_helitronscanner_draw) - | join(ch_helitronscanner_draw_rc) - | multiMap { meta, fasta, draw, draw_rc -> - genome: [ meta, fasta ] - hel_fa: draw - rc_hel_fa: draw_rc - } + ch_format_helitronscanner_inputs = ch_sanitized_fasta + | join(ch_helitronscanner_draw) + | join(ch_helitronscanner_draw_rc) + | multiMap { meta, fasta, draw, draw_rc -> + genome: [ meta, fasta ] + hel_fa: draw + rc_hel_fa: draw_rc + } FORMAT_HELITRONSCANNER_OUT ( ch_format_helitronscanner_inputs.genome, @@ -153,82 +205,56 @@ workflow EDTA { ch_format_helitronscanner_inputs.rc_hel_fa, ) - ch_versions = ch_versions.mix(FORMAT_HELITRONSCANNER_OUT.out.versions.first()) - - // MODULE: LTR_RETRIEVER_POSTPROCESS - ch_ltr_retriever_postprocess_inputs = ch_sanitized_fasta - | join(ch_pass_list) - | join(ch_pass_list_gff) - | join(ch_annotation_gff) - | join(ch_defalse) - | join(ch_ltrlib) - | multiMap { meta, fasta, pass, p_gff, a_gff, defalse, ltr -> - genome: [ meta, fasta ] - pass : pass - p_gff : p_gff - a_gff : a_gff - defalse : defalse - ltr : ltr - } + ch_helitronscanner_out_fa = FORMAT_HELITRONSCANNER_OUT.out.filtered_fa + | filter { _meta, fasta -> fasta.countFasta() > 0 } + ch_versions = ch_versions.mix(FORMAT_HELITRONSCANNER_OUT.out.versions.first()) - LTR_RETRIEVER_POSTPROCESS ( - ch_ltr_retriever_postprocess_inputs.genome, - ch_ltr_retriever_postprocess_inputs.pass, - ch_ltr_retriever_postprocess_inputs.p_gff, - ch_ltr_retriever_postprocess_inputs.defalse, - ch_ltr_retriever_postprocess_inputs.ltr, - ) - - ch_versions = ch_versions.mix(LTR_RETRIEVER_POSTPROCESS.out.versions.first()) - - // MODULE: TIR_LEARNER_POSTPROCESS - ch_tir_learner_postprocess_inputs = ch_sanitized_fasta - | join(ch_tirlearner_fasta) - | join(ch_tirlearner_gff) - | multiMap { meta, fasta, tir_fa, tir_gff -> - genome : [ meta, fasta ] - tir_fa : tir_fa - tir_gff : tir_gff - } - TIR_LEARNER_POSTPROCESS ( - ch_tir_learner_postprocess_inputs.genome, - ch_tir_learner_postprocess_inputs.tir_fa, - ch_tir_learner_postprocess_inputs.tir_gff + + // MODULE: FORMAT_HELITRONSCANNER_OUT as FORMAT_HELITRONSCANNER_OUT_EXT + FORMAT_HELITRONSCANNER_OUT_EXT ( + ch_format_helitronscanner_inputs.genome, + ch_format_helitronscanner_inputs.hel_fa, + ch_format_helitronscanner_inputs.rc_hel_fa, ) - ch_versions = ch_versions.mix(TIR_LEARNER_POSTPROCESS.out.versions.first()) - - // MODULE: REPEATMODELER_POSTPROCESS - ch_repeatmodeler_postprocess_inputs = ch_sanitized_fasta - | join(ch_repeatmodeler_fasta) - | multiMap { meta, fasta, rm_fa -> - genome : [ meta, fasta ] - rm_fa : rm_fa - } - REPEATMODELER_POSTPROCESS ( - ch_repeatmodeler_postprocess_inputs.genome, - ch_repeatmodeler_postprocess_inputs.rm_fa, + ch_helitronscanner_out_ext_fa = FORMAT_HELITRONSCANNER_OUT_EXT.out.filtered_ext_fa + | filter { _meta, fasta -> fasta.countFasta() > 0 } + ch_versions = ch_versions.mix(FORMAT_HELITRONSCANNER_OUT_EXT.out.versions.first()) + + // MODULE: HELITRONSCANNER_POSTPROCESS + ch_helitronscanner_post_inputs = ch_sanitized_fasta + | join(ch_helitronscanner_out_fa) + | join(ch_helitronscanner_out_ext_fa) + | multiMap { meta, fasta, hs_fa, hs_ext_fa -> + genome : [ meta, fasta ] + hs_fa : hs_fa + hs_ext_fa : hs_ext_fa + } + HELITRONSCANNER_POSTPROCESS ( + ch_helitronscanner_post_inputs.genome, + ch_helitronscanner_post_inputs.hs_fa, + ch_helitronscanner_post_inputs.hs_ext_fa, ) - ch_versions = ch_versions.mix(REPEATMODELER_POSTPROCESS.out.versions.first()) + ch_versions = ch_versions.mix(HELITRONSCANNER_POSTPROCESS.out.versions.first()) // Function: Save versions - ch_versions = ch_versions - | unique - | map { yml -> - if ( yml ) { yml } - } + ch_versions = ch_versions + | unique + | map { yml -> + if ( yml ) { yml } + } - ch_versions_yml = softwareVersionsToYAML(ch_versions) - | collectFile( - storeDir: "${params.outdir}/pipeline_info", - name: 'software_versions.yml', - sort: true, - newLine: true, - cache: false - ) + ch_versions_yml = softwareVersionsToYAML(ch_versions) + | collectFile( + storeDir: "${params.outdir}/pipeline_info", + name: 'software_versions.yml', + sort: true, + newLine: true, + cache: false + ) emit: - versions_yml = ch_versions_yml // [ software_versions.yml ] + versions_yml = ch_versions_yml // [ software_versions.yml ] } \ No newline at end of file From 81207b36ca46a0fc5b53584f8bd4ba7c16a5f915 Mon Sep 17 00:00:00 2001 From: Usman Rashid Date: Fri, 13 Dec 2024 12:19:32 +1300 Subject: [PATCH 38/49] Added ANNOSINE_POSTPROCESS --- bin/setup_AnnoSINE_postprocess.sh | 18 +++ conf/modules.config | 22 +++- conf/test.config | 12 +- main.nf | 4 - .../annosine_postprocess/environment.yml | 10 ++ modules/local/annosine_postprocess/main.nf | 63 +++++++++++ nextflow.config | 8 +- workflows/edta.nf | 107 ++++++++++-------- 8 files changed, 181 insertions(+), 63 deletions(-) create mode 100755 bin/setup_AnnoSINE_postprocess.sh create mode 100644 modules/local/annosine_postprocess/environment.yml create mode 100644 modules/local/annosine_postprocess/main.nf diff --git a/bin/setup_AnnoSINE_postprocess.sh b/bin/setup_AnnoSINE_postprocess.sh new file mode 100755 index 0000000..daf76fe --- /dev/null +++ b/bin/setup_AnnoSINE_postprocess.sh @@ -0,0 +1,18 @@ +#!/usr/bin/env bash + +script_path=$(dirname $0) + +cat > input/AnnoSINE_postprocess.pl << EOF +my \$genome = 'genome'; +my \$threads = $1; + +my \$cleanup_misclas = '$script_path/cleanup_misclas.pl'; +my \$cleanup_tandem = '$script_path/cleanup_tandem.pl'; + +my \$TEsorter = ''; # Assumed on PATH +my \$trf = '$(which trf)'; + +EOF + +sed -n 490,497p "$script_path/EDTA_raw.pl" \ + >> input/AnnoSINE_postprocess.pl diff --git a/conf/modules.config b/conf/modules.config index ff251a2..9482021 100644 --- a/conf/modules.config +++ b/conf/modules.config @@ -22,19 +22,19 @@ process { ] } - withName: 'EDTA:TIR_LEARNER_POSTPROCESS' { + withName: 'EDTA:ANNOSINE' { + ext.args = params.annosine_ext_args ?: '-a 2 --num_alignments 50000 -rpm 0 --copy_number 3 --shift 100 -auto 1' + } + + withName: 'EDTA:ANNOSINE_POSTPROCESS' { ext.prefix = { "${meta.id}.pp" } publishDir = [ - path: { "${params.outdir}/raw/tir" }, + path: { "${params.outdir}/raw/sine" }, mode: params.publish_dir_mode, saveAs: { filename -> filename.equals('versions.yml') ? null : filename } ] } - - withName: 'EDTA:ANNOSINE' { - ext.args = '--num_alignments 50000 -rpm 0 --copy_number 3 --shift 100 -auto 1' - } withName: 'EDTA:REPEATMODELER_REPEATMODELER' { ext.args = '-engine ncbi' @@ -50,6 +50,16 @@ process { ] } + withName: 'EDTA:TIR_LEARNER_POSTPROCESS' { + ext.prefix = { "${meta.id}.pp" } + + publishDir = [ + path: { "${params.outdir}/raw/tir" }, + mode: params.publish_dir_mode, + saveAs: { filename -> filename.equals('versions.yml') ? null : filename } + ] + } + withName: 'EDTA:FASTA_HELITRONSCANNER_SCAN_DRAW:HELITRONSCANNER_DRAW' { ext.args = '-pure_helitron' } diff --git a/conf/test.config b/conf/test.config index 29ab554..7f76e47 100644 --- a/conf/test.config +++ b/conf/test.config @@ -1,3 +1,11 @@ +params { + genome = 'https://raw.githubusercontent.com/nf-core/test-datasets/modules/data/genomics/eukaryotes/actinidia_chinensis/genome/chr1/genome.fasta.gz' + + // Following parameters for ANNOSINE are not recommended. + // These are used here with the test data to produce testable outputs + annosine_ext_args = '-a 2 -rpm 0 --copy_number 1 --shift 200' +} + process { resourceLimits = [ cpus: 10, @@ -5,7 +13,3 @@ process { time: '1.h' ] } - -params { - genome = 'https://raw.githubusercontent.com/nf-core/test-datasets/modules/data/genomics/eukaryotes/actinidia_chinensis/genome/chr1/genome.fasta.gz' -} \ No newline at end of file diff --git a/main.nf b/main.nf index 6607f9e..a50616d 100755 --- a/main.nf +++ b/main.nf @@ -2,10 +2,6 @@ nextflow.enable.dsl = 2 -params.genome = 'genomes/*' -params.species = 'others' -params.outdir = 'results' - include { EDTA } from './workflows/edta.nf' workflow { diff --git a/modules/local/annosine_postprocess/environment.yml b/modules/local/annosine_postprocess/environment.yml new file mode 100644 index 0000000..6f8a4fc --- /dev/null +++ b/modules/local/annosine_postprocess/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: + - bioconda::mdust=2006.10.17 + - bioconda::tesorter=1.4.7 + - bioconda::trf=4.09.1 + - conda-forge::perl=5.32.1 \ No newline at end of file diff --git a/modules/local/annosine_postprocess/main.nf b/modules/local/annosine_postprocess/main.nf new file mode 100644 index 0000000..42814ca --- /dev/null +++ b/modules/local/annosine_postprocess/main.nf @@ -0,0 +1,63 @@ +process ANNOSINE_POSTPROCESS { + tag "$meta.id" + label 'process_low' + + conda "${moduleDir}/environment.yml" + container "${ workflow.containerEngine == 'singularity' && !task.ext.singularity_pull_docker_container ? + 'https://community-cr-prod.seqera.io/docker/registry/v2/blobs/sha256/cb/cb105ba7d586ab31c1f39feb04c0255a39cc5a55ae7f6ea53f4bf76cdba8a3e5/data': + 'community.wave.seqera.io/library/mdust_tesorter_trf_perl:3424609103d3b065' }" + + input: + tuple val(meta), path(genome, name: 'input/genome') + path "input/Seed_SINE.fa" + + output: + tuple val(meta), path('*.SINE.raw.fa') , emit: sine_fa + path "versions.yml" , emit: versions + + when: + task.ext.when == null || task.ext.when + + script: + def prefix = task.ext.prefix ?: "${meta.id}" + def MDUST_VERSION = '2006.10.17' // WARN: Manually update when changing Bioconda assets + if ( "$prefix" == 'genome' ) error "Input and output names are the same, use \"task.ext.prefix\" to disambiguate!" + """ + setup_AnnoSINE_postprocess.sh \\ + $task.cpus + + cd input + + perl AnnoSINE_postprocess.pl + + cd - + + mv \\ + input/genome.SINE.raw.fa \\ + ${prefix}.SINE.raw.fa + + cat <<-END_VERSIONS > versions.yml + "${task.process}": + perl: \$(perl -v | sed -n 's|This is perl.*(\\(.*\\)).*|\\1|p') + trf: \$(trf -v |& sed -n 's|.*Version \\(.*\\)|\\1|p') + TEsorter: \$(TEsorter -v | tr -d 'TEsorter ') + mdust: $MDUST_VERSION + END_VERSIONS + """ + + stub: + def prefix = task.ext.prefix ?: "${meta.id}" + def MDUST_VERSION = '2006.10.17' // WARN: Manually update when changing Bioconda assets + if ( "$prefix" == 'genome' ) error "Input and output names are the same, use \"task.ext.prefix\" to disambiguate!" + """ + touch ${prefix}.SINE.raw.fa + + cat <<-END_VERSIONS > versions.yml + "${task.process}": + perl: \$(perl -v | sed -n 's|This is perl.*(\\(.*\\)).*|\\1|p') + trf: \$(trf -v |& sed -n 's|.*Version \\(.*\\)|\\1|p') + TEsorter: \$(TEsorter -v | tr -d 'TEsorter ') + mdust: $MDUST_VERSION + END_VERSIONS + """ +} \ No newline at end of file diff --git a/nextflow.config b/nextflow.config index 655993a..25a0b75 100644 --- a/nextflow.config +++ b/nextflow.config @@ -1,5 +1,11 @@ params { - publish_dir_mode = 'copy' + genome = 'genomes/*' + species = 'others' + outdir = 'results' + + annosine_ext_args = null + + publish_dir_mode = 'copy' } process { diff --git a/workflows/edta.nf b/workflows/edta.nf index 686b50d..9d23a07 100644 --- a/workflows/edta.nf +++ b/workflows/edta.nf @@ -1,29 +1,28 @@ -include { SANITIZE_HEADERS } from '../modules/local/sanitize/main.nf' +include { SANITIZE_HEADERS } from '../modules/local/sanitize/main' -include { LTRHARVEST } from '../modules/nf-core/ltrharvest/main.nf' -include { LTRFINDER } from '../modules/nf-core/ltrfinder/main.nf' -include { CAT_CAT } from '../modules/nf-core/cat/cat/main.nf' -include { LTRRETRIEVER_LTRRETRIEVER } from '../modules/nf-core/ltrretriever/ltrretriever/main.nf' -include { LTR_RETRIEVER_POSTPROCESS } from '../modules/local/ltr_retriever_postprocess/main.nf' +include { LTRHARVEST } from '../modules/nf-core/ltrharvest/main' +include { LTRFINDER } from '../modules/nf-core/ltrfinder/main' +include { CAT_CAT } from '../modules/nf-core/cat/cat/main' +include { LTRRETRIEVER_LTRRETRIEVER } from '../modules/nf-core/ltrretriever/ltrretriever/main' +include { LTR_RETRIEVER_POSTPROCESS } from '../modules/local/ltr_retriever_postprocess/main' -include { TIRLEARNER } from '../modules/gallvp/tirlearner/main.nf' -include { TIR_LEARNER_POSTPROCESS } from '../modules/local/tir_learner_postprocess/main.nf' +include { ANNOSINE } from '../modules/gallvp/annosine/main' +include { ANNOSINE_POSTPROCESS } from '../modules/local/annosine_postprocess/main' -include { ANNOSINE } from '../modules/gallvp/annosine/main.nf' +include { REPEATMODELER_BUILDDATABASE } from '../modules/nf-core/repeatmodeler/builddatabase/main' +include { REPEATMODELER_REPEATMODELER } from '../modules/nf-core/repeatmodeler/repeatmodeler/main' +include { REPEATMODELER_POSTPROCESS } from '../modules/local/repeatmodeler_postprocess/main' -include { REPEATMODELER_BUILDDATABASE } from '../modules/nf-core/repeatmodeler/builddatabase/main.nf' -include { REPEATMODELER_REPEATMODELER } from '../modules/nf-core/repeatmodeler/repeatmodeler/main.nf' -include { REPEATMODELER_POSTPROCESS } from '../modules/local/repeatmodeler_postprocess/main.nf' +include { TIRLEARNER } from '../modules/gallvp/tirlearner/main' +include { TIR_LEARNER_POSTPROCESS } from '../modules/local/tir_learner_postprocess/main' -include { FASTA_HELITRONSCANNER_SCAN_DRAW } from '../subworkflows/gallvp/fasta_helitronscanner_scan_draw/main.nf' -include { FORMAT_HELITRONSCANNER_OUT } from '../modules/local/format_helitronscanner_out/main.nf' -include { FORMAT_HELITRONSCANNER_OUT as FORMAT_HELITRONSCANNER_OUT_EXT } from '../modules/local/format_helitronscanner_out/main.nf' -include { HELITRONSCANNER_POSTPROCESS } from '../modules/local/helitronscanner_postprocess/main.nf' +include { FASTA_HELITRONSCANNER_SCAN_DRAW } from '../subworkflows/gallvp/fasta_helitronscanner_scan_draw/main' +include { FORMAT_HELITRONSCANNER_OUT } from '../modules/local/format_helitronscanner_out/main' +include { FORMAT_HELITRONSCANNER_OUT as FORMAT_HELITRONSCANNER_OUT_EXT } from '../modules/local/format_helitronscanner_out/main' +include { HELITRONSCANNER_POSTPROCESS } from '../modules/local/helitronscanner_postprocess/main' - - -include { softwareVersionsToYAML } from '../modules/local/utils/main.nf' -include { idFromFileName } from '../modules/local/utils/main.nf' +include { softwareVersionsToYAML } from '../modules/local/utils/main' +include { idFromFileName } from '../modules/local/utils/main' workflow EDTA { @@ -51,14 +50,14 @@ workflow EDTA { ch_ltrharvest_scn = LTRHARVEST.out.scn - ch_versions = ch_versions.mix(LTRHARVEST.out.versions) + ch_versions = ch_versions.mix(LTRHARVEST.out.versions.first()) // MODULE: LTRFINDER LTRFINDER { ch_sanitized_fasta } ch_ltrfinder_scn = LTRFINDER.out.scn - ch_versions = ch_versions.mix(LTRFINDER.out.versions) + ch_versions = ch_versions.mix(LTRFINDER.out.versions.first()) // MODULE: CAT_CAT ch_cat_cat_inputs = ch_ltrharvest_scn @@ -108,40 +107,27 @@ workflow EDTA { ch_versions = ch_versions.mix(LTR_RETRIEVER_POSTPROCESS.out.versions.first()) - // MODULE: TIRLEARNER - TIRLEARNER ( + // MODULE: ANNOSINE + ANNOSINE ( ch_sanitized_fasta, - params.species + 3 // mode ) - ch_versions = ch_versions.mix(TIRLEARNER.out.versions.first()) + ch_versions = ch_versions.mix(ANNOSINE.out.versions.first()) - // MODULE: TIR_LEARNER_POSTPROCESS - ch_tir_learner_postprocess_inputs = ch_sanitized_fasta - | join ( TIRLEARNER.out.fasta ) - | join ( TIRLEARNER.out.gff ) - | multiMap { meta, fasta, tir_fa, tir_gff -> + // MODULE: ANNOSINE_POSTPROCESS + ch_annosine_postprocess_inputs = ch_sanitized_fasta + | join ( ANNOSINE.out.fa ) + | multiMap { meta, fasta, anno_fa -> genome : [ meta, fasta ] - tir_fa : tir_fa - tir_gff : tir_gff + anno_fa : anno_fa } - TIR_LEARNER_POSTPROCESS ( - ch_tir_learner_postprocess_inputs.genome, - ch_tir_learner_postprocess_inputs.tir_fa, - ch_tir_learner_postprocess_inputs.tir_gff + ANNOSINE_POSTPROCESS ( + ch_annosine_postprocess_inputs.genome, + ch_annosine_postprocess_inputs.anno_fa ) - ch_versions = ch_versions.mix(TIR_LEARNER_POSTPROCESS.out.versions.first()) - - // MODULE: ANNOSINE - ANNOSINE ( - ch_sanitized_fasta, - 3 // mode - ) - - // Currently it's a topic, so need to fix that - ch_versions = ch_versions.mix(ANNOSINE.out.versions) - ch_annosine_seed_sine = ANNOSINE.out.fa + ch_versions = ch_versions.mix(ANNOSINE_POSTPROCESS.out.versions.first()) // MODULE: REPEATMODELER_BUILDDATABASE ch_repeatmodeler_inputs = ch_sanitized_fasta @@ -182,6 +168,31 @@ workflow EDTA { ch_versions = ch_versions.mix(REPEATMODELER_POSTPROCESS.out.versions.first()) + // MODULE: TIRLEARNER + TIRLEARNER ( + ch_sanitized_fasta, + params.species + ) + + ch_versions = ch_versions.mix(TIRLEARNER.out.versions.first()) + + // MODULE: TIR_LEARNER_POSTPROCESS + ch_tir_learner_postprocess_inputs = ch_sanitized_fasta + | join ( TIRLEARNER.out.fasta ) + | join ( TIRLEARNER.out.gff ) + | multiMap { meta, fasta, tir_fa, tir_gff -> + genome : [ meta, fasta ] + tir_fa : tir_fa + tir_gff : tir_gff + } + TIR_LEARNER_POSTPROCESS ( + ch_tir_learner_postprocess_inputs.genome, + ch_tir_learner_postprocess_inputs.tir_fa, + ch_tir_learner_postprocess_inputs.tir_gff + ) + + ch_versions = ch_versions.mix(TIR_LEARNER_POSTPROCESS.out.versions.first()) + // MODULE: FASTA_HELITRONSCANNER_SCAN_DRAW FASTA_HELITRONSCANNER_SCAN_DRAW ( ch_sanitized_fasta ) From ba29b092faab91373370454f364b0969b9881c39 Mon Sep 17 00:00:00 2001 From: Usman Rashid Date: Fri, 13 Dec 2024 15:50:43 +1300 Subject: [PATCH 39/49] Added PROCESS_K --- .github/check_binaries.sh | 9 + bin/EDTA_processK.pl | 216 ++++++++++++++++++ conf/modules.config | 10 + modules/local/process_k/environment.yml | 8 + modules/local/process_k/main.nf | 89 ++++++++ .../local/repeatmodeler_postprocess/main.nf | 13 +- tests/nf-test/small/main.nf.test | 3 +- tests/nf-test/small/main.nf.test.snap | 14 +- workflows/edta.nf | 45 ++++ 9 files changed, 397 insertions(+), 10 deletions(-) create mode 100755 bin/EDTA_processK.pl create mode 100644 modules/local/process_k/environment.yml create mode 100644 modules/local/process_k/main.nf diff --git a/.github/check_binaries.sh b/.github/check_binaries.sh index 79e8784..354d472 100755 --- a/.github/check_binaries.sh +++ b/.github/check_binaries.sh @@ -10,3 +10,12 @@ if [[ $EDTA_raw_pl_version != $bin_EDTA_raw_pl_version ]]; then echo 'Please synchronize bin/EDTA_raw.pl with EDTA_raw.pl' exit 1 fi + +EDTA_processK_pl_version=$(md5sum EDTA_processK.pl | cut -f1 -d' ') +bin_EDTA_processK_pl_version=$(md5sum bin/EDTA_processK.pl | cut -f1 -d' ') + +if [[ $EDTA_processK_pl_version != $bin_EDTA_processK_pl_version ]]; then + echo 'EDTA_processK.pl != bin/EDTA_processK.pl' + echo 'Please synchronize bin/EDTA_processK.pl with EDTA_processK.pl' + exit 1 +fi diff --git a/bin/EDTA_processK.pl b/bin/EDTA_processK.pl new file mode 100755 index 0000000..002e43f --- /dev/null +++ b/bin/EDTA_processK.pl @@ -0,0 +1,216 @@ +#!/usr/bin/env perl +use warnings; +use strict; +use FindBin; +use File::Basename; + +##################################################################### +##### Perform EDTA basic and advance filtering on TE candidates ##### +##### Shujun Ou (shujun.ou.1@gmail.com, 12/28/2023) ##### +##################################################################### + +## Input: +# $genome.SINE.raw.fa +# $genome.LINE.raw.fa +# $genome.LTR.raw.fa +# $genome.LTR.intact.raw.fa +# $genome.TIR.intact.raw.fa +# $genome.Helitron.intact.raw.fa + +## Output: +# $genome.EDTA.fa.stg1 + +my $usage = "\nPerform EDTA basic and advance filtering for raw TE candidates and generate the stage 1 library + perl EDTA_processF.pl [options] + -genome [File] The genome FASTA + -ltr [File] The raw LTR library FASTA + -ltrint [File] The intact LTR library FASTA + -sine [File] The raw SINE library FASTA + -line [File] The raw LINE library FASTA + -tir [File] The raw TIR library FASTA + -helitron [File] The raw Helitron library FASTA + -mindiff_ltr [float] The minimum fold difference in richness between LTRs and contaminants (default: 1) + -mindiff_tir [float] The minimum fold difference in richness between TIRs and contaminants (default: 1) + -mindiff_hel [float] The minimum fold difference in richness between Helitrons and contaminants (default: 2) + -repeatmasker [path] The directory containing RepeatMasker (default: read from ENV) + -blast [path] The directory containing Blastn (default: read from ENV) + -threads|-t [int] Number of theads to run this script + -help|-h Display this help info +\n"; + +# user input +my $genome = ''; +my $LTRraw = ''; +my $LTRintact = ''; +my $SINEraw = ''; +my $LINEraw = ''; +my $TIRraw = ''; +my $HELraw = ''; +my $err = ''; + +# minimum richness difference between $TE1 and $TE2 for a sequence to be considered as REAL to $TE1. +# Smaller number is more inclusive during purging, hence higher false positives +my $mindiff_LTR = 1; +my $mindiff_TIR = 1; +my $mindiff_HEL = 1.5; + +my $threads = 4; +my $script_path = $FindBin::Bin; +my $TE_purifier = "$script_path/bin/TE_purifier.pl"; +my $rename_TE = "$script_path/bin/rename_TE.pl"; +my $cleanup_tandem = "$script_path/bin/cleanup_tandem.pl"; +my $cleanup_nested = "$script_path/bin/cleanup_nested.pl"; +my $cleanup_proteins = "$script_path/bin/cleanup_proteins.pl"; +my $repeatmasker = " "; +my $blast = " "; + +# read parameters +my $k=0; +foreach (@ARGV){ + $genome = $ARGV[$k+1] if /^-genome$/i and $ARGV[$k+1] !~ /^-/; + $LTRraw = $ARGV[$k+1] if /^-ltr$/i and $ARGV[$k+1] !~ /^-/; + $LTRintact = $ARGV[$k+1] if /^-ltrint$/i and $ARGV[$k+1] !~ /^-/; + $SINEraw = $ARGV[$k+1] if /^-sine$/i and $ARGV[$k+1] !~ /^-/; + $LINEraw = $ARGV[$k+1] if /^-line$/i and $ARGV[$k+1] !~ /^-/; + $TIRraw = $ARGV[$k+1] if /^-tir/i and $ARGV[$k+1] !~ /^-/; + $HELraw = $ARGV[$k+1] if /^-helitron/i and $ARGV[$k+1] !~ /^-/; + $mindiff_LTR = $ARGV[$k+1] if /^-mindiff_ltr/i and $ARGV[$k+1] !~ /^-/; + $mindiff_TIR = $ARGV[$k+1] if /^-mindiff_tir/i and $ARGV[$k+1] !~ /^-/; + $mindiff_HEL = $ARGV[$k+1] if /^-mindiff_hel/i and $ARGV[$k+1] !~ /^-/; + $repeatmasker = $ARGV[$k+1] if /^-repeatmasker/i and $ARGV[$k+1] !~ /^-/; + $blast = $ARGV[$k+1] if /^-blast/i and $ARGV[$k+1] !~ /^-/; + $threads = $ARGV[$k+1] if /^-threads$|^-t$/i and $ARGV[$k+1] !~ /^-/; + die $usage if /^-help$|^-h$/i; + $k++; + } + +# check files and dependencies +die "Genome file $genome not exists!\n$usage" unless -s $genome; +die "LTR raw library file $LTRraw not exists!\n$usage" unless -s $LTRraw; +die "Intact LTR file $LTRintact not exists!\n$usage" unless -s $LTRintact; +#die "LINE raw library file $LINEraw not exists!\n$usage" unless -e $LINE; # allow empty file +#die "SINE raw library file $SINEraw not exists!\n$usage" unless -e $SINE; # allow empty file +die "TIR raw library file $TIRraw not exists!\n$usage" unless -s $TIRraw; +die "Helitron raw library file $HELraw not exists!\n$usage" unless -s $HELraw; +die "The script TE_purifier.pl is not found in $TE_purifier!\n" unless -s $TE_purifier; +die "The script rename_TE.pl is not found in $rename_TE!\n" unless -s $rename_TE; +die "The script cleanup_tandem.pl is not found in $cleanup_tandem!\n" unless -s $cleanup_tandem; +die "The script cleanup_nested.pl is not found in $cleanup_nested!\n" unless -s $cleanup_nested; +die "The script cleanup_proteins.pl is not found in $cleanup_proteins!\n" unless -s $cleanup_proteins; + +# make a softlink to the genome +my $genome_file = basename($genome); +`ln -s $genome $genome_file` unless -e $genome_file; +$genome = $genome_file; +my $LTR = "$genome.LTR.raw.fa"; +my $LTRint = "$genome.LTR.intact.raw.fa"; +my $SINE = "$genome.SINE.raw.fa"; +my $LINE = "$genome.LINE.raw.fa"; +my $TIR = "$genome.TIR.intact.raw.fa"; +my $HEL = "$genome.Helitron.intact.raw.fa"; + +# Make working directories +`mkdir $genome.EDTA.combine` unless -e "$genome.EDTA.combine" && -d "$genome.EDTA.combine"; + +# enter the combine folder for EDTA processing +chdir "$genome.EDTA.combine"; +`cp ../$LTRraw $LTR` unless -s "$LTR"; +`cp ../$LTRintact $LTRint` unless -s "$LTRint"; +`cp ../$SINEraw $SINE` unless -s "$SINE"; +`cp ../$LINEraw $LINE` unless -s "$LINE"; +`cp ../$TIRraw $TIR` unless -s "$TIR"; +`cp ../$HELraw $HEL` unless -s "$HEL"; + + +################################## +###### define subroutines ###### +################################## + +# purify $TE2 contaminants in $TE1 +# This function better works for redundant libraries +sub Purifier() { + my ($TE1, $TE2, $mindiff) = ($_[0], $_[1], $_[2]); + # mark contaminents with lowercase letters based on relative richness + `perl $TE_purifier -TE1 $TE1 -TE2 $TE2 -t $threads -mindiff $mindiff`; + # remove lowercase sequences + `perl $cleanup_tandem -misschar l -Nscreen 1 -nc 50000 -nr 0.8 -minlen 80 -cleanN 1 -cleanT 1 -minrm 1 -trf 0 -f $TE1-$TE2.fa > $TE1.HQ`; + } + + +################################# +###### Advance filtering ###### +################################# + +## Purge contaminants in redundant libraries +# purify raw LTR (clean LTR library is better than dirty intact LTR for purging LTRs from other TEs) +&Purifier("$LTR", "$TIR", $mindiff_LTR); +&Purifier("$LTR.HQ", "$HEL", $mindiff_LTR); +`mv $LTR.HQ.HQ $LTR.HQ`; + +# purify Helitron +&Purifier("$HEL", "$TIR", $mindiff_HEL); +&Purifier("$HEL.HQ", "$LTR", $mindiff_LTR); +`perl $cleanup_tandem -misschar l -Nscreen 1 -nc 50000 -nr 0.8 -minlen 80 -cleanN 1 -cleanT 0 -minrm 1 -trf 0 -f $HEL.HQ-$LTR.fa > $HEL.int.cln`; # more relaxed in filtering intact helitrons +`mv $HEL.HQ.HQ $HEL.cln`; + +# purify TIR +&Purifier("$TIR", "$LTR", $mindiff_TIR); +&Purifier("$TIR.HQ", "$HEL", $mindiff_TIR); +`perl $cleanup_tandem -misschar l -Nscreen 1 -nc 50000 -nr 0.8 -minlen 80 -cleanN 1 -cleanT 0 -minrm 1 -trf 0 -f $TIR.HQ-$HEL.fa > $TIR.int.cln`; # more relaxed in filtering intact TIRs +`mv $TIR.HQ.HQ $TIR.cln`; + +# purify intact LTR from TIRs. Including Helitron is too damaging for now. +&Purifier("$LTRint", "$TIR.cln", 10); # 10 is permissive +`perl $cleanup_tandem -misschar l -Nscreen 1 -nc 50000 -nr 0.8 -minlen 80 -cleanN 1 -cleanT 0 -minrm 1 -trf 0 -f $LTRint-$TIR.cln.fa > $LTRint.cln`; +#&Purifier("$LTRint.HQ", "$HEL.cln", 10); # 10 is permissive +#`perl $cleanup_tandem -misschar l -Nscreen 1 -nc 50000 -nr 0.8 -minlen 80 -cleanN 1 -cleanT 0 -minrm 1 -trf 0 -f $LTRint.HQ-$HEL.cln.fa > $LTRint.cln`; # more relaxed in filtering intact LTRs + +## Purge contaminants in non-redundant libraries +# clean LINEs in LTRs +if (-s "$LINE"){ + $err = `${repeatmasker}RepeatMasker -e ncbi -pa $threads -q -no_is -nolow -div 40 -lib $LINE $LTR 2>&1`; + if ($err !~ /done/) { + `ln -s $LTR $LTR.masked` if $err =~ s/^.*(No repetitive sequences were detected.*)\s+$/Warning: No sequences were masked/s; + print STDERR "\n$err\n"; + } + `perl $cleanup_tandem -misschar N -nc 50000 -nr 0.9 -minlen 80 -minscore 3000 -trf 0 -cleanN 1 -cleanT 1 -f $LTR.masked > $LTR.cln`; + } else { + `cp $LTR $LTR.cln`; + } + +# clean LINEs and LTRs in SINEs +if (-s "$SINE"){ + `cat $LTR.cln $LINE > $genome.LINE_LTR.raw.fa`; + $err = `${repeatmasker}RepeatMasker -e ncbi -pa $threads -q -no_is -nolow -div 40 -lib $genome.LINE_LTR.raw.fa $SINE 2>&1`; + if ($err !~ /done/) { + `ln -s $SINE $SINE.masked` if $err =~ s/^.*(No repetitive sequences were detected.*)\s+$/Warning: No sequences were masked/s; + print STDERR "\n$err\n"; + } + `perl $cleanup_tandem -misschar N -nc 50000 -nr 0.9 -minlen 80 -minscore 3000 -trf 0 -cleanN 1 -f $SINE.masked > $SINE.cln`; + } else { + `cp $SINE $SINE.cln`; + } + + +## clean LTRs and nonLTRs in TIRs and Helitrons +`cat $TIR.cln $HEL.cln | perl -nle 's/>/\\n>/g unless /^>/; print \$_' > $genome.TIR.Helitron.fa.stg1.raw`; +`cat $LTR.HQ $SINE.cln $LINE > $genome.LTR.SINE.LINE.fa`; +$err = `${repeatmasker}RepeatMasker -e ncbi -pa $threads -q -no_is -nolow -div 40 -lib $genome.LTR.SINE.LINE.fa $genome.TIR.Helitron.fa.stg1.raw 2>&1`; +if ($err !~ /done/) { + `ln -s $genome.TIR.Helitron.fa.stg1.raw $genome.TIR.Helitron.fa.stg1.raw.masked` if $err =~ s/^.*(No repetitive sequences were detected.*)\s+$/Warning: No sequences were masked/s; + print STDERR "\n$err\n"; + } +`perl $cleanup_tandem -misschar N -nc 50000 -nr 0.9 -minlen 80 -minscore 3000 -trf 0 -cleanN 1 -cleanT 1 -f $genome.TIR.Helitron.fa.stg1.raw.masked > $genome.TIR.Helitron.fa.stg1.raw.cln`; + + +## cluster TIRs and Helitrons and make stg1 raw library +`perl $cleanup_nested -in $genome.TIR.Helitron.fa.stg1.raw.cln -threads $threads -minlen 80 -cov 0.95 -blastplus $blast`; +`cat $LTR.cln $LINE $SINE.cln $genome.TIR.Helitron.fa.stg1.raw.cln.cln > $genome.EDTA.fa.stg1`; + +## generate clean intact TEs +`cat $LTRint.cln $LINE $SINE.cln $TIR.int.cln $HEL.int.cln > $genome.EDTA.intact.fa.cln`; + +## clean up the folder +`rm *.ndb *.not *.ntf *.nto *.cat.gz *.cat *.masked *.ori.out *.nhr *.nin *.nsq 2>/dev/null`; + +chdir '..'; diff --git a/conf/modules.config b/conf/modules.config index 9482021..82cff15 100644 --- a/conf/modules.config +++ b/conf/modules.config @@ -96,5 +96,15 @@ process { saveAs: { filename -> filename.equals('versions.yml') ? null : filename } ] } + + withName: 'EDTA:PROCESS_K' { + ext.prefix = { "${meta.id}.stg1" } + + publishDir = [ + path: { "${params.outdir}/stg1" }, + mode: params.publish_dir_mode, + saveAs: { filename -> filename.equals('versions.yml') ? null : filename } + ] + } } \ No newline at end of file diff --git a/modules/local/process_k/environment.yml b/modules/local/process_k/environment.yml new file mode 100644 index 0000000..e03f80c --- /dev/null +++ b/modules/local/process_k/environment.yml @@ -0,0 +1,8 @@ +--- +# yaml-language-server: $schema=https://raw.githubusercontent.com/nf-core/modules/master/modules/environment-schema.json +channels: + - conda-forge + - bioconda +dependencies: + - bioconda::blast=2.16.0 + - bioconda::repeatmasker=4.1.7 \ No newline at end of file diff --git a/modules/local/process_k/main.nf b/modules/local/process_k/main.nf new file mode 100644 index 0000000..7061d91 --- /dev/null +++ b/modules/local/process_k/main.nf @@ -0,0 +1,89 @@ +process PROCESS_K { + tag "$meta.id" + label 'process_medium' + + conda "${moduleDir}/environment.yml" + container "${ workflow.containerEngine == 'singularity' && !task.ext.singularity_pull_docker_container ? + 'https://community-cr-prod.seqera.io/docker/registry/v2/blobs/sha256/91/91c1946f5edb90aa9d2c26eb14e7348f8f4e94dda03bdb2185b72cb3c2d54932/data': + 'community.wave.seqera.io/library/blast_repeatmasker:816962ca420d1d16' }" + + input: + tuple val(meta) , path(genome, name: 'genome') + path(ltr , name: "genome.EDTA.raw/genome.LTR.raw.fa") + path(ltrint , name: "genome.EDTA.raw/genome.LTR.intact.raw.fa") + path(sine , name: "genome.EDTA.raw/genome.SINE.raw.fa") + path(line , name: "genome.EDTA.raw/genome.LINE.raw.fa") + path(tir , name: "genome.EDTA.raw/genome.TIR.intact.raw.fa") + path(helitron , name: "genome.EDTA.raw/genome.Helitron.intact.raw.fa") + + output: + tuple val(meta), path('*.fasta') , emit: stg1_fa + path "versions.yml" , emit: versions + + when: + task.ext.when == null || task.ext.when + + script: + def prefix = task.ext.prefix ?: "${meta.id}" + def args = task.ext.args ?: '' + if ( "$prefix" == 'genome' ) error "Input and output names are the same, use \"task.ext.prefix\" to disambiguate!" + + def touch_ltr = ltr ? '' : "touch genome.EDTA.raw/genome.LTR.raw.fa" + def touch_ltrint = ltrint ? '' : "touch genome.EDTA.raw/genome.LTR.intact.raw.fa" + def touch_sine = sine ? '' : "touch genome.EDTA.raw/genome.SINE.raw.fa" + def touch_line = line ? '' : "touch genome.EDTA.raw/genome.LINE.raw.fa" + def touch_tir = tir ? '' : "touch genome.EDTA.raw/genome.TIR.intact.raw.fa" + def touch_helitron = helitron ? '' : "touch genome.EDTA.raw/genome.Helitron.intact.raw.fa" + """ + mkdir -p genome.EDTA.raw + $touch_ltr + $touch_ltrint + $touch_sine + $touch_line + $touch_tir + $touch_helitron + + PROGRAM_PATH=\$(dirname \$(which EDTA_processK.pl)) + + sed "s|\\\$script_path/bin|\$PROGRAM_PATH|g" \\ + \$PROGRAM_PATH/EDTA_processK.pl \\ + > EDTA_processK.pl + + perl \\ + EDTA_processK.pl \\ + -genome genome \\ + -ltr genome.EDTA.raw/genome.LTR.raw.fa \\ + -ltrint genome.EDTA.raw/genome.LTR.intact.raw.fa \\ + -sine genome.EDTA.raw/genome.SINE.raw.fa \\ + -line genome.EDTA.raw/genome.LINE.raw.fa \\ + -tir genome.EDTA.raw/genome.TIR.intact.raw.fa \\ + -helitron genome.EDTA.raw/genome.Helitron.intact.raw.fa \\ + $args \\ + ${task.cpus} + + mv \\ + genome.EDTA.combine/genome.EDTA.fa.stg1 \\ + ${prefix}.fasta + + cat <<-END_VERSIONS > versions.yml + "${task.process}": + perl: \$(perl -v | sed -n 's|This is perl.*(\\(.*\\)).*|\\1|p') + blast: \$(blastn -version | sed -n 's|Package: blast \\(.*\\)|\\1|p') + repeatmasker: \$(RepeatMasker -v | sed 's/RepeatMasker version //1') + END_VERSIONS + """ + + stub: + def prefix = task.ext.prefix ?: "${meta.id}" + if ( "$prefix" == 'genome' ) error "Input and output names are the same, use \"task.ext.prefix\" to disambiguate!" + """ + touch ${prefix}.fasta + + cat <<-END_VERSIONS > versions.yml + "${task.process}": + perl: \$(perl -v | sed -n 's|This is perl.*(\\(.*\\)).*|\\1|p') + blast: \$(blastn -version | sed -n 's|Package: blast \\(.*\\)|\\1|p') + repeatmasker: \$(RepeatMasker -v | sed 's/RepeatMasker version //1') + END_VERSIONS + """ +} \ No newline at end of file diff --git a/modules/local/repeatmodeler_postprocess/main.nf b/modules/local/repeatmodeler_postprocess/main.nf index f538422..c1f6459 100644 --- a/modules/local/repeatmodeler_postprocess/main.nf +++ b/modules/local/repeatmodeler_postprocess/main.nf @@ -13,7 +13,7 @@ process REPEATMODELER_POSTPROCESS { output: tuple val(meta), path('*.RM2.fa') , emit: rm2_fa - tuple val(meta), path('*.LINE.raw.fa') , emit: line_raw + tuple val(meta), path('*.LINE.raw.fa') , emit: line_raw , optional: true path "versions.yml" , emit: versions when: @@ -37,9 +37,13 @@ process REPEATMODELER_POSTPROCESS { genome.RM2.fa \\ ${prefix}.RM2.fa - mv \\ - genome.LINE.raw.fa \\ - ${prefix}.LINE.raw.fa + if [[ -s genome.LINE.raw.fa ]]; then + mv \\ + genome.LINE.raw.fa \\ + ${prefix}.LINE.raw.fa + else + rm genome.LINE.raw.fa + fi cat <<-END_VERSIONS > versions.yml "${task.process}": @@ -56,7 +60,6 @@ process REPEATMODELER_POSTPROCESS { if ( "$prefix" == 'genome' ) error "Input and output names are the same, use \"task.ext.prefix\" to disambiguate!" """ touch ${prefix}.RM2.fa - touch ${prefix}.LINE.raw.fa cat <<-END_VERSIONS > versions.yml "${task.process}": diff --git a/tests/nf-test/small/main.nf.test b/tests/nf-test/small/main.nf.test index d0cd760..e293c39 100644 --- a/tests/nf-test/small/main.nf.test +++ b/tests/nf-test/small/main.nf.test @@ -27,7 +27,8 @@ nextflow_pipeline { 'raw/line/*.pp.RM2.fa', 'raw/helitron/*.Helitron.intact.raw.fa', 'raw/helitron/*.Helitron.intact.raw.fa.anno.list', - 'raw/helitron/*.Helitron.intact.raw.gff3' + 'raw/helitron/*.Helitron.intact.raw.gff3', + 'stg1/*.stg1.fasta' ], null, ['**'] diff --git a/tests/nf-test/small/main.nf.test.snap b/tests/nf-test/small/main.nf.test.snap index e13891a..1ca54ba 100644 --- a/tests/nf-test/small/main.nf.test.snap +++ b/tests/nf-test/small/main.nf.test.snap @@ -2,7 +2,7 @@ "small genome": { "content": [ { - "successful tasks": 21, + "successful tasks": 22, "versions": { "ANNOSINE": { "annosine": "2.0.7" @@ -57,6 +57,11 @@ "TEsorter": "1.4.7", "mdust": "2006.10.17" }, + "PROCESS_K": { + "perl": "v5.32.1", + "blast": "2.16.0, build Nov 27 2024 09:40:57", + "repeatmasker": "4.1.7-p1" + }, "REPEATMODELER_BUILDDATABASE": { "repeatmodeler": "2.0.5" }, @@ -97,7 +102,6 @@ "raw/helitron/genome.pp.Helitron.intact.raw.fa.anno.list", "raw/helitron/genome.pp.Helitron.intact.raw.gff3", "raw/line", - "raw/line/genome.pp.LINE.raw.fa", "raw/line/genome.pp.RM2.fa", "raw/ltr", "raw/ltr/genome.pp.LTR.intact.raw.fa.anno.list", @@ -107,7 +111,9 @@ "raw/tir", "raw/tir/genome.pp.TIR.intact.raw.bed", "raw/tir/genome.pp.TIR.intact.raw.fa", - "raw/tir/genome.pp.TIR.intact.raw.gff3" + "raw/tir/genome.pp.TIR.intact.raw.gff3", + "stg1", + "stg1/genome.stg1.fasta" ] } ], @@ -115,6 +121,6 @@ "nf-test": "0.9.2", "nextflow": "24.04.4" }, - "timestamp": "2024-12-13T11:02:40.702113" + "timestamp": "2024-12-13T15:35:15.109085" } } \ No newline at end of file diff --git a/workflows/edta.nf b/workflows/edta.nf index 9d23a07..70d70de 100644 --- a/workflows/edta.nf +++ b/workflows/edta.nf @@ -21,6 +21,8 @@ include { FORMAT_HELITRONSCANNER_OUT } from '../modules/local/for include { FORMAT_HELITRONSCANNER_OUT as FORMAT_HELITRONSCANNER_OUT_EXT } from '../modules/local/format_helitronscanner_out/main' include { HELITRONSCANNER_POSTPROCESS } from '../modules/local/helitronscanner_postprocess/main' +include { PROCESS_K } from '../modules/local/process_k/main' + include { softwareVersionsToYAML } from '../modules/local/utils/main' include { idFromFileName } from '../modules/local/utils/main' @@ -249,6 +251,49 @@ workflow EDTA { ch_versions = ch_versions.mix(HELITRONSCANNER_POSTPROCESS.out.versions.first()) + // MODULE: PROCESS_K + ch_process_k_inputs = ch_sanitized_fasta + | join ( LTR_RETRIEVER_POSTPROCESS.out.raw_fa ) + | join ( LTR_RETRIEVER_POSTPROCESS.out.intact_raw_fa ) + | join ( ANNOSINE_POSTPROCESS.out.sine_fa , remainder: true ) + | join ( REPEATMODELER_POSTPROCESS.out.line_raw , remainder: true ) + | join ( TIR_LEARNER_POSTPROCESS.out.intact_raw_fa ) + | join ( HELITRONSCANNER_POSTPROCESS.out.raw_fa ) + | multiMap { meta, genome, ltr, ltrint, sine, line, tir, helitron -> + genome : [ meta, genome ] + ltr : ltr ?: [] + ltrint : ltrint ?: [] + sine : sine ?: [] + line : line ?: [] + tir : tir ?: [] + helitron: helitron ?: [] + } + + PROCESS_K ( + ch_process_k_inputs.genome, + ch_process_k_inputs.ltr, + ch_process_k_inputs.ltrint, + ch_process_k_inputs.sine, + ch_process_k_inputs.line, + ch_process_k_inputs.tir, + ch_process_k_inputs.helitron, + ) + + ch_versions = ch_versions.mix(PROCESS_K.out.versions.first()) + + // Warn: End of the pipeline if no inputs are available for ProcessK + ch_sanitized_fasta + | join ( LTR_RETRIEVER_POSTPROCESS.out.raw_fa , remainder: true ) + | join ( LTR_RETRIEVER_POSTPROCESS.out.intact_raw_fa , remainder: true ) + | join ( TIR_LEARNER_POSTPROCESS.out.intact_raw_fa , remainder: true ) + | join ( HELITRONSCANNER_POSTPROCESS.out.raw_fa , remainder: true ) + | map { meta, _genome, ltr, ltrint, tir, helitron -> + if ( !ltr || !ltrint || !tir || !helitron ) { + log.warn "One or more TE classes needed to complete EDTA were not found in genome '$meta.id'" + + ". Multiple processing steps are being skipped." + } + } + // Function: Save versions ch_versions = ch_versions | unique From b71316f50a4e27e1c3d806c6e650c8cd342f9f6b Mon Sep 17 00:00:00 2001 From: Usman Rashid Date: Mon, 16 Dec 2024 16:04:57 +1300 Subject: [PATCH 40/49] Fixed conda version --- modules/local/process_k/environment.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/local/process_k/environment.yml b/modules/local/process_k/environment.yml index e03f80c..ee63cb5 100644 --- a/modules/local/process_k/environment.yml +++ b/modules/local/process_k/environment.yml @@ -4,5 +4,5 @@ channels: - conda-forge - bioconda dependencies: - - bioconda::blast=2.16.0 + - bioconda::blast=2.14.1 - bioconda::repeatmasker=4.1.7 \ No newline at end of file From 96daead66c3fd2772c59a0994f2d70c84e75f4be Mon Sep 17 00:00:00 2001 From: Usman Rashid Date: Mon, 16 Dec 2024 16:21:53 +1300 Subject: [PATCH 41/49] One more attempt to match conda vers --- modules/local/process_k/environment.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/local/process_k/environment.yml b/modules/local/process_k/environment.yml index ee63cb5..9aec455 100644 --- a/modules/local/process_k/environment.yml +++ b/modules/local/process_k/environment.yml @@ -4,5 +4,5 @@ channels: - conda-forge - bioconda dependencies: - - bioconda::blast=2.14.1 - - bioconda::repeatmasker=4.1.7 \ No newline at end of file + - bioconda::blast=2.16.0-hc155240_3 + - bioconda::repeatmasker=4.1.7p1-pl5321hdfd78af_1 \ No newline at end of file From 6b9f76d290aef9e0a186a9a696e4a3ac6964406b Mon Sep 17 00:00:00 2001 From: Usman Rashid Date: Mon, 16 Dec 2024 16:37:46 +1300 Subject: [PATCH 42/49] Fixed syntax --- modules/local/process_k/environment.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/local/process_k/environment.yml b/modules/local/process_k/environment.yml index 9aec455..8cab40b 100644 --- a/modules/local/process_k/environment.yml +++ b/modules/local/process_k/environment.yml @@ -4,5 +4,5 @@ channels: - conda-forge - bioconda dependencies: - - bioconda::blast=2.16.0-hc155240_3 - - bioconda::repeatmasker=4.1.7p1-pl5321hdfd78af_1 \ No newline at end of file + - bioconda::blast=2.16.0=hc155240_3 + - bioconda::repeatmasker=4.1.7p1=pl5321hdfd78af_1 \ No newline at end of file From a9d9738853af1daa58c835adba6cf960b72c06f0 Mon Sep 17 00:00:00 2001 From: Usman Rashid Date: Mon, 16 Dec 2024 16:49:02 +1300 Subject: [PATCH 43/49] Removed blast from versions --- modules/local/process_k/environment.yml | 4 ++-- modules/local/process_k/main.nf | 2 -- tests/nf-test/small/main.nf.test.snap | 1 - 3 files changed, 2 insertions(+), 5 deletions(-) diff --git a/modules/local/process_k/environment.yml b/modules/local/process_k/environment.yml index 8cab40b..e03f80c 100644 --- a/modules/local/process_k/environment.yml +++ b/modules/local/process_k/environment.yml @@ -4,5 +4,5 @@ channels: - conda-forge - bioconda dependencies: - - bioconda::blast=2.16.0=hc155240_3 - - bioconda::repeatmasker=4.1.7p1=pl5321hdfd78af_1 \ No newline at end of file + - bioconda::blast=2.16.0 + - bioconda::repeatmasker=4.1.7 \ No newline at end of file diff --git a/modules/local/process_k/main.nf b/modules/local/process_k/main.nf index 7061d91..e385899 100644 --- a/modules/local/process_k/main.nf +++ b/modules/local/process_k/main.nf @@ -68,7 +68,6 @@ process PROCESS_K { cat <<-END_VERSIONS > versions.yml "${task.process}": perl: \$(perl -v | sed -n 's|This is perl.*(\\(.*\\)).*|\\1|p') - blast: \$(blastn -version | sed -n 's|Package: blast \\(.*\\)|\\1|p') repeatmasker: \$(RepeatMasker -v | sed 's/RepeatMasker version //1') END_VERSIONS """ @@ -82,7 +81,6 @@ process PROCESS_K { cat <<-END_VERSIONS > versions.yml "${task.process}": perl: \$(perl -v | sed -n 's|This is perl.*(\\(.*\\)).*|\\1|p') - blast: \$(blastn -version | sed -n 's|Package: blast \\(.*\\)|\\1|p') repeatmasker: \$(RepeatMasker -v | sed 's/RepeatMasker version //1') END_VERSIONS """ diff --git a/tests/nf-test/small/main.nf.test.snap b/tests/nf-test/small/main.nf.test.snap index 1ca54ba..8bd6b84 100644 --- a/tests/nf-test/small/main.nf.test.snap +++ b/tests/nf-test/small/main.nf.test.snap @@ -59,7 +59,6 @@ }, "PROCESS_K": { "perl": "v5.32.1", - "blast": "2.16.0, build Nov 27 2024 09:40:57", "repeatmasker": "4.1.7-p1" }, "REPEATMODELER_BUILDDATABASE": { From 09c711814725c47df4940227aef4e6dfd231c547 Mon Sep 17 00:00:00 2001 From: Usman Rashid Date: Tue, 17 Dec 2024 14:10:59 +1300 Subject: [PATCH 44/49] Added final_filter --- .github/check_binaries.sh | 9 + bin/EDTA.pl | 861 ++++++++++++++++++ bin/setup_RepeatModeler_postprocess.sh | 2 +- bin/setup_combine_intact_TEs.sh | 14 + bin/setup_final_filter.sh | 31 + conf/modules.config | 27 +- modules/local/annosine_postprocess/main.nf | 8 +- .../local/combine_intact_tes/environment.yml | 10 + modules/local/combine_intact_tes/main.nf | 64 ++ modules/local/final_filter/environment.yml | 8 + modules/local/final_filter/main.nf | 69 ++ .../local/helitronscanner_postprocess/main.nf | 26 +- .../local/ltr_retriever_postprocess/main.nf | 26 +- modules/local/process_k/main.nf | 14 +- .../local/repeatmodeler_postprocess/main.nf | 14 +- modules/local/tir_learner_postprocess/main.nf | 20 +- tests/nf-test/small/main.nf.test | 22 +- tests/nf-test/small/main.nf.test.snap | 53 +- workflows/edta.nf | 52 ++ 19 files changed, 1236 insertions(+), 94 deletions(-) create mode 100755 bin/EDTA.pl create mode 100755 bin/setup_combine_intact_TEs.sh create mode 100755 bin/setup_final_filter.sh create mode 100644 modules/local/combine_intact_tes/environment.yml create mode 100644 modules/local/combine_intact_tes/main.nf create mode 100644 modules/local/final_filter/environment.yml create mode 100644 modules/local/final_filter/main.nf diff --git a/.github/check_binaries.sh b/.github/check_binaries.sh index 354d472..2fe4f9d 100755 --- a/.github/check_binaries.sh +++ b/.github/check_binaries.sh @@ -19,3 +19,12 @@ if [[ $EDTA_processK_pl_version != $bin_EDTA_processK_pl_version ]]; then echo 'Please synchronize bin/EDTA_processK.pl with EDTA_processK.pl' exit 1 fi + +EDTA_pl_version=$(md5sum EDTA.pl | cut -f1 -d' ') +bin_EDTA_pl_version=$(md5sum bin/EDTA.pl | cut -f1 -d' ') + +if [[ $EDTA_pl_version != $bin_EDTA_pl_version ]]; then + echo 'EDTA.pl != bin/EDTA.pl' + echo 'Please synchronize bin/EDTA.pl with EDTA.pl' + exit 1 +fi diff --git a/bin/EDTA.pl b/bin/EDTA.pl new file mode 100755 index 0000000..4e7f475 --- /dev/null +++ b/bin/EDTA.pl @@ -0,0 +1,861 @@ +#!/usr/bin/env perl +use warnings; +use strict; +use FindBin; +use File::Basename; +use Getopt::Long; +use Pod::Usage; +use POSIX qw(strftime); +use Cwd qw(abs_path); + +my $version = "v2.2.2"; +#v1.0 05/31/2019 +#v1.1 06/05/2019 +#v1.2 06/16/2019 +#v1.3 07/20/2019 +#v1.4 08/07/2019 +#v1.5 08/14/2019 +#v1.6 11/09/2019 +#v1.7 12/25/2019 +#v1.8 02/09/2020 +#v1.9 07/24/2020 +#v2.0 11/25/2021 +#v2.1 10/10/2022 +#v2.2 01/05/2024 +#v2.2.2 08/08/2024 + +print " +######################################################### +##### Extensive de-novo TE Annotator (EDTA) $version ##### +##### Shujun Ou (shujun.ou.1\@gmail.com) ##### +######################################################### +\n\nParameters: @ARGV\n\n\n"; + + +## Input: $genome +## Output: $genome.EDTA.TElib.fa + +my $usage = "\nThis is the Extensive de-novo TE Annotator that generates a high-quality +structure-based TE library. Usage: + +perl EDTA.pl [options] + --genome [File] The genome FASTA file. Required. + --species [Rice|Maize|others] Specify the species for identification of TIR + candidates. Default: others + --step [all|filter|final|anno] Specify which steps you want to run EDTA. + all: run the entire pipeline (default) + filter: start from raw TEs to the end. + final: start from filtered TEs to finalizing the run. + anno: perform whole-genome annotation/analysis after + TE library construction. + --overwrite [0|1] If previous raw TE results are found, decide to overwrite + (1, rerun) or not (0, default). + --cds [File] Provide a FASTA file containing the coding sequence (no introns, + UTRs, nor TEs) of this genome or its close relative. + --curatedlib [File] Provided a curated library to keep consistant naming and + classification for known TEs. TEs in this file will be + trusted 100%, so please ONLY provide MANUALLY CURATED ones. + This option is not mandatory. It's totally OK if no file is + provided (default). + --rmlib [File] Provide the RepeatModeler library containing classified TEs to enhance + the sensitivity especially for LINEs. If no file is provided (default), + EDTA will generate such file for you. + --sensitive [0|1] Use RepeatModeler to identify remaining TEs (1) or not (0, + default). This step may help to recover some TEs. + --anno [0|1] Perform (1) or not perform (0, default) whole-genome TE annotation + after TE library construction. + --rmout [File] Provide your own homology-based TE annotation instead of using the + EDTA library for masking. File is in RepeatMasker .out format. This + file will be merged with the structural-based TE annotation. (--anno 1 + required). Default: use the EDTA library for annotation. + --maxdiv [0-100] Maximum divergence (0-100%, default: 40) of repeat fragments comparing to + library sequences. + --evaluate [0|1] Evaluate (1) classification consistency of the TE annotation. + (--anno 1 required). Default: 1. + --exclude [File] Exclude regions (bed format) from TE masking in the MAKER.masked + output. Default: undef. (--anno 1 required). + --force [0|1] When no confident TE candidates are found: 0, interrupt and exit + (default); 1, use rice TEs to continue. + --u [float] Neutral mutation rate to calculate the age of intact LTR elements. + Intact LTR age is found in this file: *EDTA_raw/LTR/*.pass.list. + Default: 1.3e-8 (per bp per year, from rice). + --repeatmodeler [path] The directory containing RepeatModeler (default: read from ENV) + --repeatmasker [path] The directory containing RepeatMasker (default: read from ENV) + --annosine [path] The directory containing AnnoSINE_v2 (default: read from ENV) + --ltrretriever [path] The directory containing LTR_retriever (default: read from ENV) + --check_dependencies Check if dependencies are fullfiled and quit + --threads|-t [int] Number of theads to run this script (default: 4) + --debug [0|1] Retain intermediate files (default: 0) + --help|-h Display this help info +\n"; + +# pre-defined +my $genome = ''; +my $check_dependencies = undef; +my $species = "others"; +my $step = "ALL"; +my $overwrite = 0; #0, no rerun. 1, rerun even old results exist. +my $HQlib = ''; #curated library +my $RMlib = 'null'; #RepeatModeler library, classified +my $cds = ''; #a fasta file containing cds of this genome. +my $sensitive = 0; #0, will not run RepeatModeler to get remaining TEs (default). 1, run RepeatModeler +my $anno = 0; #0, will not annotate whole-genome TE (default). 1, annotate with RepeatMasker +my $rmout = ''; #a RM .out file for custom homology-based annotation. +my $evaluate = 1; #1 will evaluate the consistancy of the TE annotation +my $exclude = ''; #a bed file exclude from TE annotation +my $force = 0; #if there is no confident TE found in EDTA_raw, 1 will use rice TEs as raw lib, 0 will error and interrupt. +my $miu = 1.3e-8; #mutation rate, per bp per year, from rice +my $threads = 4; +my $maxdiv = 40; # maximum divergence from lib sequences for fragmented repeats +my $script_path = $FindBin::Bin; +my $EDTA_raw = "$script_path/EDTA_raw.pl"; +my $EDTA_process = "$script_path/EDTA_processK.pl"; +my $cleanup_proteins = "$script_path/bin/cleanup_proteins.pl"; +my $cleanup_TE = "$script_path/bin/cleanup_TE.pl"; +my $cleanup_tandem = "$script_path/bin/cleanup_tandem.pl"; +my $cleanup_nested = "$script_path/bin/cleanup_nested.pl"; +my $count_nested = "$script_path/bin/count_nested.pl"; +my $count_base = "$script_path/bin/count_base.pl"; +my $make_masked = "$script_path/bin/make_masked.pl"; +my $make_gff3 = "$script_path/bin/make_gff3_with_RMout.pl"; +my $protlib = "$script_path/database/alluniRefprexp082813"; +my $rice_LTR = "$script_path/database/rice7.0.0.liban.LTR"; +my $rice_SINE = "$script_path/database/rice7.0.0.liban.SINE"; +my $rice_LINE = "$script_path/database/rice7.0.0.liban.LINE"; +my $rice_TIR = "$script_path/database/rice7.0.0.liban.TIR"; +my $rice_helitron = "$script_path/database/rice7.0.0.liban.Helitron"; +my $rename_TE = "$script_path/bin/rename_TE.pl"; +#my $rename_RM = "$script_path/bin/rename_RM_TE.pl"; +my $call_seq = "$script_path/bin/call_seq_by_list.pl"; +my $buildSummary = "$script_path/bin/buildSummary.pl"; #modified from RepeatMasker. Robert M. Hubley (rhubley@systemsbiology.org) +my $filter_gff = "$script_path/bin/filter_gff3.pl"; +my $combine_RMrows = "$script_path/bin/combine_RMrows.pl"; +my $RMout2bed = "$script_path/bin/RMout2bed.pl"; +my $gff2RMout = "$script_path/bin/gff2RMout.pl"; +my $bed2gff = "$script_path/bin/bed2gff.pl"; +my $gff2bed = "$script_path/bin/gff2bed.pl"; +my $get_frag = "$script_path/bin/get_frag.pl"; +my $keep_nest = "$script_path/bin/keep_nest.pl"; +my $combine_overlap = "$script_path/bin/combine_overlap.pl"; +my $split_overlap = "$script_path/bin/split_overlap.pl"; +my $reclassify = "$script_path/bin/classify_by_lib_RM.pl"; +my $rename_by_list = "$script_path/bin/rename_by_list.pl"; +my $output_by_list = "$script_path/bin/output_by_list.pl"; +my $format_TElib = "$script_path/bin/format_TElib.pl"; +my $format_gff3 = "$script_path/bin/format_gff3.pl"; +my $add_id = "$script_path/bin/add_id.pl"; +my $div_table = "$script_path/bin/div_table2.pl"; +my $div_plot = "$script_path/bin/div_plot2.R"; +my $density_table = "$script_path/bin/density_table.py"; +my $density_plot = "$script_path/bin/density_plot.R"; +my $LTR_retriever = ""; +my $genometools = ""; +my $repeatmodeler = ""; +my $repeatmasker = ""; +my $TEsorter = ""; +my $blastplus = ""; +my $mdust = ""; +my $trf = ""; +my $GRF = ""; +my $annosine = ""; + +my $beta2 = 0; #0, beta2 is not ready. 1, developer mode. +#my $reanno = 0; #0, use existing whole-genome RM results (beta); 1, de novo Repeatmasker using the EDTA library (default) +my $debug = 0; +my $help = undef; + +# read parameters +if ( !GetOptions( 'genome=s' => \$genome, + 'species=s' => \$species, + 'step=s' => \$step, + 'overwrite=i' => \$overwrite, + 'curatedlib=s' => \$HQlib, + 'rmlib=s' => \$RMlib, + 'cds=s' => \$cds, + 'protlib=s' => \$protlib, + 'sensitive=s' => \$sensitive, + 'anno=i' => \$anno, + 'rmout=s' => \$rmout, + 'maxdiv=i' => \$maxdiv, + 'evaluate=i' => \$evaluate, + 'exclude=s' => \$exclude, + 'force=i' => \$force, + 'u=s' => \$miu, + 'repeatmodeler=s' => \$repeatmodeler, + 'repeatmasker=s' => \$repeatmasker, + 'tesorter=s' => \$TEsorter, + 'blast=s' => \$blastplus, + 'annosine=s' => \$annosine, + 'ltrretriever=s' => \$LTR_retriever, + 'threads|t=i' => \$threads, + 'check_dependencies!' => \$check_dependencies, + 'debug=i' => \$debug, + 'help|h!' => \$help ) ) + +{ + pod2usage( { -message => 'Failed to parse command line', + -verbose => 1, + -exitval => 1 } ); + } + +if ($help) { + pod2usage( { -verbose => 0, + -exitval => 0, + -message => "$usage\n" } ); + } + +if ( (! -s $genome) and (! $check_dependencies) ){ + pod2usage( { + -message => "At least 1 parameter is required:\n1) Input fasta file: --genome\n". + "$usage\n\n", + -verbose => 0, + -exitval => 2 } ); + } + +# get $maxdiv +$maxdiv = $maxdiv*100 if $maxdiv < 1; +$maxdiv =~ s/%//g; + +# check bolean +if ($maxdiv < 0 or $maxdiv > 100){die "The expected value for the div parameter is 0 - 100!\n"} +if ($overwrite != 0 and $overwrite != 1){ die "The expected value for the overwrite parameter is 0 or 1!\n"} +if ($sensitive != 0 and $sensitive != 1){ die "The expected value for the sensitive parameter is 0 or 1!\n"} +if ($anno != 0 and $anno != 1){ die "The expected value for the anno parameter is 0 or 1!\n"} +if ($evaluate != 0 and $evaluate != 1){ die "The expected value for the evaluate parameter is 0 or 1!\n"} +if ($force != 0 and $force != 1){ die "The expected value for the force parameter is 0 or 1!\n"} +if ($miu !~ /[0-9\.e\-]+/){ die "The expected value for the u parameter is float value without units!\n"} +if ($debug != 0 and $debug != 1){ die "The expected value for the debug parameter is 0 or 1!\n"} +if ($threads !~ /^[0-9]+$/){ die "The expected value for the threads parameter is an integer!\n"} + + +# define RepeatMasker -pa parameter +#my $rm_threads = int($threads/4); +my $rm_threads = $threads; + +chomp (my $date = `date`); +print "$date\tDependency checking:\n"; + +# check files and dependencies +die "The script EDTA_raw.pl is not found in $EDTA_raw!\n" unless -s $EDTA_raw; +die "The script EDTA_processK13.pl is not found in $EDTA_process!\n" unless -s $EDTA_process; +die "The script cleanup_proteins.pl is not found in $cleanup_proteins!\n" unless -s $cleanup_proteins; +die "The script cleanup_TE.pl is not found in $cleanup_TE!\n" unless -s $cleanup_TE; +die "The script cleanup_tandem.pl is not found in $cleanup_tandem!\n" unless -s $cleanup_tandem; +die "The script cleanup_nested.pl is not found in $cleanup_nested!\n" unless -s $cleanup_nested; +die "The script count_nested.pl is not found in $count_nested!\n" unless -s $count_nested; +die "The script count_base.pl is not found in $count_base!\n" unless -s $count_base; +die "The script make_masked.pl is not found in $make_masked!\n" unless -s $make_masked; +die "The script make_gff3_with_RMout.pl is not found in $make_gff3!\n" unless -s $make_gff3; +die "The protein-coding sequence library is not found in $protlib!\n" unless -s $protlib; +die "The rice LTR sequence library is not found in $rice_LTR!\n" unless -s $rice_LTR; +die "The rice SINE sequence library is not found in $rice_SINE!\n" unless -s $rice_SINE; +die "The rice LINE sequence library is not found in $rice_LINE!\n" unless -s $rice_LINE; +die "The rice TIR sequence library is not found in $rice_TIR!\n" unless -s $rice_TIR; +die "The rice Helitron sequence library is not found in $rice_helitron!\n" unless -s $rice_helitron; +die "The script rename_TE.pl is not found in $rename_TE!\n" unless -s $rename_TE; +die "The script call_seq_by_list.pl is not found in $call_seq!\n" unless -s $call_seq; +die "The script buildSummary.pl is not found in $buildSummary!\n" unless -s $buildSummary; +die "The script filter_gff3.pl is not found in $filter_gff!\n" unless -s $filter_gff; +die "The script RMout2bed.pl is not found in $RMout2bed!\n" unless -s $RMout2bed; +die "The script gff2RMout.pl is not found in $gff2RMout!\n" unless -s $gff2RMout; +die "The script combine_RMrows.pl is not found in $combine_RMrows!\n" unless -s $combine_RMrows; +die "The script bed2gff.pl is not found in $bed2gff!\n" unless -s $bed2gff; +die "The script gff2bed.pl is not found in $gff2bed!\n" unless -s $gff2bed; +die "The script get_frag.pl is not found in $get_frag!\n" unless -s $get_frag; +die "The script keep_nest.pl is not found in $keep_nest!\n" unless -s $keep_nest; +die "The script combine_overlap.pl is not found in $combine_overlap!\n" unless -s $combine_overlap; +die "The script split_overlap.pl is not found in $split_overlap!\n" unless -s $split_overlap; +die "The script classify_by_lib_RM.pl is not found in $reclassify!\n" unless -s $reclassify; +die "The script rename_by_list.pl is not found in $rename_by_list!\n" unless -s $rename_by_list; +die "The script output_by_list.pl is not found in $output_by_list!\n" unless -s $output_by_list; +die "The script format_gff3.pl is not found in $format_gff3!\n" unless -s $format_gff3; +die "The script add_id.pl is not found in $add_id!\n" unless -s $add_id; +die "The script div_table2.pl is not found in $div_table!\n" unless -s $div_table; +die "The script div_plot2.R is not found in $div_plot!\n" unless -s $div_plot; +die "The script density_table.py is not found in $density_table!\n" unless -s $density_table; +die "The script density_plot.R is not found in $density_plot!\n" unless -s $density_plot; + +# GenomeTools +chomp ($genometools=`command -v gt 2>/dev/null`) if $genometools eq ''; +$genometools =~ s/\s+$//; +$genometools = dirname($genometools) unless -d $genometools; +$genometools="$genometools/" if $genometools ne '' and $genometools !~ /\/$/; +die "Error: gt is not found in the genometools path $genometools!\n" unless -X "${genometools}gt"; +# AnnoSINE +chomp ($annosine=`command -v AnnoSINE_v2 2>/dev/null`) if $annosine eq ''; +$annosine =~ s/\s+$//; +$annosine = dirname($annosine) unless -d $annosine; +$annosine="$annosine/" if $annosine ne '' and $annosine !~ /\/$/; +die "Error: AnnoSINE is not found in the AnnoSINE path $annosine!\n" unless (-X "${annosine}AnnoSINE_v2"); +# LTR_retriever +chomp ($LTR_retriever=`command -v LTR_retriever 2>/dev/null`) if $LTR_retriever eq ''; +$LTR_retriever =~ s/\s+$//; +$LTR_retriever = dirname($LTR_retriever) unless -d $LTR_retriever; +$LTR_retriever="$LTR_retriever/" if $LTR_retriever ne '' and $LTR_retriever !~ /\/$/; +die "Error: LTR_retriever is not found in the LTR_retriever path $LTR_retriever!\n" unless -X "${LTR_retriever}LTR_retriever"; +# RepeatMasker +my $rand=int(rand(1000000)); +chomp ($repeatmasker=`command -v RepeatMasker 2>/dev/null`) if $repeatmasker eq ''; +$repeatmasker =~ s/\s+$//; +$repeatmasker = dirname($repeatmasker) unless -d $repeatmasker; +$repeatmasker="$repeatmasker/" if $repeatmasker ne '' and $repeatmasker !~ /\/$/; +die "Error: RepeatMasker is not found in the RepeatMasker path $repeatmasker!\n" unless -X "${repeatmasker}RepeatMasker"; +`cp $script_path/database/dummy060817.fa ./dummy060817.fa.$rand`; +my $RM_test=`${repeatmasker}RepeatMasker -e ncbi -q -pa 1 -no_is -nolow dummy060817.fa.$rand -lib dummy060817.fa.$rand 2>/dev/null`; +die "Error: The RMblast engine is not installed in RepeatMasker!\n" unless $RM_test=~s/done//gi; +`rm dummy060817.fa.$rand* 2>/dev/null`; +# RepeatModeler +chomp ($repeatmodeler=`command -v RepeatModeler 2>/dev/null`) if $repeatmodeler eq ''; +$repeatmodeler =~ s/\s+$//; +$repeatmodeler = dirname($repeatmodeler) unless -d $repeatmodeler; +$repeatmodeler="$repeatmodeler/" if $repeatmodeler ne '' and $repeatmodeler !~ /\/$/; +die "Error: RepeatModeler is not found in the RepeatModeler path $repeatmodeler!\n" unless -X "${repeatmodeler}RepeatModeler"; +# makeblastdb, blastn, blastx +chomp ($blastplus=`command -v makeblastdb 2>/dev/null`) if $blastplus eq ''; +$blastplus =~ s/\s+$//; +$blastplus = dirname($blastplus) unless -d $blastplus; +$blastplus="$blastplus/" if $blastplus ne '' and $blastplus !~ /\/$/; +die "Error: makeblastdb is not found in the BLAST+ path $blastplus!\n" unless -X "${blastplus}makeblastdb"; +die "Error: blastn is not found in the BLAST+ path $blastplus!\n" unless -X "${blastplus}blastn"; +die "Error: blastx is not found in the BLAST+ path $blastplus!\n" unless -X "${blastplus}blastx"; +# TEsorter +chomp ($TEsorter=`command -v TEsorter 2>/dev/null`) if $TEsorter eq ''; +$TEsorter =~ s/\s+$//; +$TEsorter = dirname($TEsorter) unless -d $TEsorter; +$TEsorter="$TEsorter/" if $TEsorter ne '' and $TEsorter !~ /\/$/; +die "Error: TEsorter is not found in the TEsorter path $TEsorter!\n" unless -X "${TEsorter}TEsorter"; +# mdust +chomp ($mdust=`command -v mdust 2>/dev/null`) if $mdust eq ''; +$mdust =~ s/\s+$//; +$mdust = dirname($mdust) unless -d $mdust; +$mdust = "$mdust/" if $mdust ne '' and $mdust !~ /\/$/; +die "Error: mdust is not found in the mdust path $mdust!\n" unless -X "${mdust}mdust"; +# trf +chomp ($trf=`command -v trf 2>/dev/null`) if $trf eq ''; +$trf=~s/\n$//; +`$trf 2>/dev/null`; +die "Error: Tandem Repeat Finder is not found in the TRF path $trf!\n" if $?==32256; +# GRF +chomp ($GRF = `command -v grf-main 2>/dev/null`) if $GRF eq ''; +$GRF =~ s/\n$//; +`$GRF 2>/dev/null`; +die "Error: The Generic Repeat Finder (GRF) is not found in the GRF path: $GRF\n" if $?==32256; + +print "\tAll passed!\n\n"; +exit if $check_dependencies; + +# make a softlink to the user-provided files +my $genome_file = basename($genome); +`ln -s $genome $genome_file` unless -e $genome_file; +$genome = $genome_file; + +# check if duplicated sequences found +my $id_mode = 0; #record the mode of id conversion. +my $id_len = `grep \\> $genome|perl -ne 'chomp; s/>//g; my \$len=length \$_; \$max=\$len if \$max<\$len; print "\$max\\n"'`; #find out the longest sequence ID length in the genome +$id_len =~ s/\s+$//; +$id_len = (split /\s+/, $id_len)[-1]; +my $raw_id = `grep \\> $genome|wc -l`; +my $old_id = `grep \\> $genome|sort -u|wc -l`; +if ($raw_id > $old_id){ + chomp ($date = `date`); + die "$date\tERROR: Identical sequence IDs found in the provided genome! Please resolve this issue and try again.\n"; + } + +# remove sequence annotations (content after the first space in sequence names) and replace special characters with _, convert non-ATGC bases into Ns +`perl -nle 'my \$info=(split)[0]; \$info=~s/[\\~!@#\\\$%\\^&\\*\\(\\)\\+\\\-\\=\\?\\[\\]\\{\\}\\:;",\\<\\/\\\\\|]+/_/g; \$info=~s/_+/_/g; \$info=~s/[^ATGCN]/N/gi unless /^>/; print \$info' $genome > $genome.$rand.mod`; + +# try to shortern sequences +my $id_len_max = 13; # allowed longest length of a sequence ID in the input file +if ($id_len > $id_len_max){ + chomp ($date = `date`); + print "$date\tThe longest sequence ID in the genome contains $id_len characters, which is longer than the limit ($id_len_max)\n"; + print "\tTrying to reformat seq IDs...\n\t\tAttempt 1...\n"; + `perl -lne 'chomp; if (s/^>+//) {s/^\\s+//; \$_=(split)[0]; s/(.{1,$id_len_max}).*/>\$1/g;} print "\$_"' $genome.$rand.mod > $genome.$rand.temp`; + my $new_id = `grep \\> $genome.$rand.temp|sort -u|wc -l`; + chomp ($date = `date`); + if ($old_id == $new_id){ + $id_mode = 1; + `mv $genome.$rand.temp $genome.mod`; + `rm $genome.$rand.mod 2>/dev/null`; + print "$date\tSeq ID conversion successful!\n\n"; + } else { + print "\t\tAttempt 2...\n"; + `perl -ne 'chomp; if (/^>/) {\$_=">\$1" if /([0-9]+)/;} print "\$_\n"' $genome.$rand.mod > $genome.$rand.temp`; + $new_id = `grep \\> $genome.$rand.temp|sort -u|wc -l`; + if ($old_id == $new_id){ + $id_mode = 2; + `mv $genome.$rand.temp $genome.mod`; + `rm $genome.$rand.mod 2>/dev/null`; + print "$date\tSeq ID conversion successful!\n\n"; + } else { + `rm $genome.$rand.temp $genome.$rand.mod 2>/dev/null`; + die "$date\tERROR: Fail to convert seq IDs to <= $id_len_max characters! Please provide a genome with shorter seq IDs.\n\n"; + } + } + } else { + `mv $genome.$rand.mod $genome.mod`; + } +$genome = "$genome.mod"; + +# check $HQlib +if ($HQlib ne ''){ + if (-s $HQlib){ + print "\tA custom library $HQlib is provided via --curatedlib. Please make sure this is a manually curated library but not machine generated.\n\n"; + chomp ($HQlib = `realpath $HQlib`); + my $HQlib_file = basename($HQlib); + `ln -s $HQlib $HQlib_file` unless -e $HQlib_file; + $HQlib = $HQlib_file; + } else { + die "\tERROR: The custom library $HQlib you specified is not found!\n\n"; + } + } + +# check $RMlib +if ($RMlib ne 'null'){ + if (-s $RMlib){ + print "\tA RepeatModeler library $RMlib is provided via --rmlib. Please make sure this is a RepeatModeler2 generated and classified library (some levels of unknown classification is OK).\n\n"; + chomp ($RMlib = `realpath $RMlib`); + `ln -s $RMlib $genome.RM2.raw.fa` unless -e "$genome.RM2.raw.fa"; + #`cp $RMlib $genome.RM2.raw.fa` unless -s "$genome.RM2.raw.fa"; + $RMlib = "$genome.RM2.raw.fa"; + } else { + die "\tERROR: The RepeatModeler library $RMlib you specified is not found!\n\n"; + } + }# else { + # `touch $genome.RM2.raw.fa 2>/dev/null`; + #} + +if ($cds ne ''){ + if (-s $cds){ + print "\tA CDS file $cds is provided via --cds. Please make sure this is the DNA sequence of coding regions only.\n\n"; + chomp ($cds = `realpath $cds`); + my $cds_file = basename($cds); + `ln -s $cds $cds_file` unless -e $cds_file; + $cds = $cds_file; + } else { + die "\tERROR: The CDS file $cds you specified is not found!\n\n"; + } + } + +if ($rmout ne ''){ + if (-s $rmout){ + print "\tA RepeatMasker .out file $rmout is provided via --rmout.\n\n"; + chomp ($rmout = `realpath $rmout`); + } else { + die "\tERROR: The RepeatMasker .out file $rmout you specified is not found!\n\n"; + } + } + +if ($exclude ne ''){ + if (-s $exclude){ + print "\tA BED file is provided via --exclude. Regions specified by this file will be excluded from TE annotation and masking.\n\n"; + my $exclude_file = basename($exclude); + `ln -s $exclude $exclude_file ` unless -e $exclude_file; + $exclude = $exclude_file; + } else { + die "\tERROR: The exclusion BED file $exclude you specified is not found!\n\n"; + } + } + +$step = uc $step; +goto $step; + + +############################################################ +####### Get raw LTR/SINE/LINE/TIR/Helitron candidates ###### +############################################################ + +ALL: + +# report status +chomp ($date = `date`); +print "$date\tObtain raw TE libraries using various structure-based programs: \n"; + +# Get raw TE candidates +`perl $EDTA_raw --genome $genome --overwrite $overwrite --species $species --u $miu --threads $threads --genometools $genometools --ltrretriever $LTR_retriever --blastplus $blastplus --tesorter $TEsorter --GRF $GRF --trf_path $trf --repeatmasker $repeatmasker --repeatmodeler $repeatmodeler --annosine $annosine --convert_seq_name 0 --rmlib $RMlib`; + +chdir "$genome.EDTA.raw"; + +# Force to use rice TEs when raw.fa is empty +if ($force eq 1){ + `cp $rice_LTR $genome.LTR.raw.fa` unless -s "$genome.LTR.raw.fa"; + `cp $rice_LTR $genome.LTR.intact.raw.fa` unless -s "$genome.LTR.intact.raw.fa"; + `cp $rice_LINE $genome.LINE.raw.fa` unless -s "$genome.LINE.raw.fa"; + `cp $rice_SINE $genome.SINE.raw.fa` unless -s "$genome.SINE.raw.fa"; + `cp $rice_TIR $genome.TIR.intact.raw.fa` unless -s "$genome.TIR.intact.raw.fa"; + `cp $rice_helitron $genome.Helitron.intact.raw.fa` unless -s "$genome.Helitron.intact.raw.fa"; + } + +# check results and report status +die "ERROR: Raw LTR results not found in $genome.EDTA.raw/$genome.LTR.raw.fa and $genome.EDTA.raw/$genome.LTR.intact.raw.fa\n\tIf you believe the program is working properly, this may be caused by the lack of intact LTRs in your genome. Consider to use the --force 1 parameter to overwrite this check\n" unless (-s "$genome.LTR.raw.fa" and -s "$genome.LTR.intact.raw.fa"); +die "ERROR: Raw SINE results not found in $genome.EDTA.raw/$genome.SINE.raw.fa\n\tIf you believe the program is working properly, this may be caused by the lack of SINEs in your genome.\n" unless -e "$genome.SINE.raw.fa"; # allow empty file +die "ERROR: Raw LINE results not found in $genome.EDTA.raw/$genome.LINE.raw.fa\n\tIf you believe the program is working properly, this may be caused by the lack of LINEs in your genome.\n" unless -e "$genome.LINE.raw.fa"; # allow empty file +die "ERROR: Raw TIR results not found in $genome.EDTA.raw/$genome.TIR.intact.raw.fa\n\tIf you believe the program is working properly, this may be caused by the lack of intact TIRs in your genome. Consider to use the --force 1 parameter to overwrite this check\n" unless -s "$genome.TIR.intact.raw.fa"; +die "ERROR: Raw Helitron results not found in $genome.EDTA.raw/$genome.Helitron.intact.raw.fa\n\tIf you believe the program is working properly, this may be caused by the lack of intact Helitrons in your genome. Consider to use the --force 1 parameter to overwrite this check\n" unless -s "$genome.Helitron.intact.raw.fa"; + +# combine intact TEs +`cat $genome.LTR.intact.raw.fa $genome.TIR.intact.raw.fa $genome.Helitron.intact.raw.fa > $genome.EDTA.intact.raw.fa`; +`cat $genome.TIR.intact.raw.bed $genome.Helitron.intact.raw.bed | perl $bed2gff - TE_struc > $genome.EDTA.intact.gff3.temp`; +`cat $genome.LTR.intact.raw.gff3 >> $genome.EDTA.intact.gff3.temp`; +`sort -sV -k1,1 -k4,4 $genome.EDTA.intact.gff3.temp | grep -v '^#' > $genome.EDTA.intact.raw.gff3; rm $genome.EDTA.intact.gff3.temp`; + +chomp ($date = `date`); +print "$date\tObtain raw TE libraries finished. +\tAll intact TEs found by EDTA: \n\t\t$genome.EDTA.intact.raw.fa \n\t\t$genome.EDTA.intact.raw.gff3\n\n"; +chdir ".."; + + +############################################################ +####### Filter LTR/SINE/LINE/TIR/Helitron candidates ####### +############################################################ + +FILTER: + +# report status +chomp ($date = `date`); +print "$date\tPerform EDTA advance filtering for raw TE candidates and generate the stage 1 library: \n\n"; + +# remove existing results +`rm ./$genome.EDTA.combine/* 2>/dev/null` if $overwrite == 1; + +# Filter raw TE candidates and the make stage 1 library +`perl $EDTA_process -genome $genome -ltr $genome.EDTA.raw/$genome.LTR.raw.fa -ltrint $genome.EDTA.raw/$genome.LTR.intact.raw.fa -line $genome.EDTA.raw/$genome.LINE.raw.fa -sine $genome.EDTA.raw/$genome.SINE.raw.fa -tir $genome.EDTA.raw/$genome.TIR.intact.raw.fa -helitron $genome.EDTA.raw/$genome.Helitron.intact.raw.fa -repeatmasker $repeatmasker -blast $blastplus -threads $threads`; + +# check results, remove intermediate files, and report status +die "ERROR: Stage 1 library not found in $genome.EDTA.combine/$genome.EDTA.fa.stg1" unless -s "$genome.EDTA.combine/$genome.EDTA.fa.stg1"; +chdir "$genome.EDTA.combine"; +`rm ./$genome.LTR.raw.fa*Q* ./$genome.LTR.intact.raw.fa*Q* ./$genome.TIR.intact.raw.fa*Q* ./$genome.Helitron.intact.raw.fa*Q* ./$genome.TIR.Helitron.fa*Q* $genome*tbl $genome*out $genome*cleanup $genome*RMoutput $genome*stg1.raw* $genome.LTR.raw.fa-* $genome.LTR.intact.raw.fa-* $genome.TIR.intact.raw.fa-* $genome.Helitron.intact.raw.fa-* $genome.LINE_LTR.raw.fa $genome.LTR.SINE.LINE.fa *.ndb *.not *.ntf *.nto *.cat.gz *.cat *.masked *.ori.out *.nhr *.nin *.nsq 2>/dev/null` unless $debug eq 1; + +chdir ".."; +chomp ($date = `date`); +print "$date\tEDTA advance filtering finished.\n\n"; + + +#################################### +###### Final purge CDS in TEs ###### +#################################### + +FINAL: + +# report status +chomp ($date = `date`); +print "$date\tPerform EDTA final steps to generate a non-redundant comprehensive TE library.\n\n"; + +# Make the final working directory +`mkdir $genome.EDTA.final` unless -e "$genome.EDTA.final" && -d "$genome.EDTA.final"; +chdir "$genome.EDTA.final"; +`rm ./* 2>/dev/null` if $overwrite == 1; +`cp ../$genome.EDTA.raw/$genome.RM2.fa ./`; +`cp ../$genome.EDTA.combine/$genome.EDTA.fa.stg1 ./`; +`cp ../$cds ./` if $cds ne ''; +`cp ../$HQlib ./` if $HQlib ne ''; +`cp ../$genome.EDTA.combine/$genome.EDTA.intact.fa.cln ./$genome.EDTA.intact.fa.cln`; +#`cp ../$genome.EDTA.raw/$genome.EDTA.intact.raw.fa ./`; +`cp ../$genome.EDTA.raw/$genome.EDTA.intact.raw.gff3 ./`; +`cp ../$exclude ./` if $exclude ne ''; + +# identify remaining TEs in the filtered RM2 library +if ($sensitive == 1 and -s "$genome.RM2.fa"){ + print "\tFilter RepeatModeler results that are ignored in the raw step.\n\n"; + chomp ($date = `date`); + my $rm_status = `${repeatmasker}RepeatMasker -e ncbi -pa $rm_threads -q -no_is -nolow -div 40 -lib $genome.EDTA.fa.stg1 $genome.RM2.fa 2>/dev/null`; + `cp $genome.RM2.fa $genome.RM2.fa.masked` if $rm_status =~ /No repetitive sequences were detected/i; + # clean up tandem and coding sequences in the RM2 library + `perl $cleanup_tandem -misschar N -nc 50000 -nr 0.8 -minlen 80 -minscore 3000 -trf 1 -trf_path $trf -cleanN 1 -cleanT 1 -f $genome.RM2.fa.masked > $genome.RM2.fa.stg1`; + `perl $cleanup_proteins -seq $genome.RM2.fa.stg1 -rmdnate 0 -rmline 0 -rmprot 1 -protlib $protlib -blast $blastplus -threads $threads`; + if (-s "$genome.RM2.fa.stg1.clean"){ + `cat $genome.EDTA.fa.stg1 $genome.RM2.fa.stg1.clean > $genome.EDTA.raw.fa`; + } else { + print "\t\tNo extra repeat sequences found in the RepeatModeler output.\n\n"; + `cp $genome.EDTA.fa.stg1 $genome.EDTA.raw.fa`; + } + } else { + print "\tSkipping the RepeatModeler results (--sensitive 0).\n\t\tRun EDTA.pl --step final --sensitive 1 if you want to add RepeatModeler results.\n\n"; + `cp $genome.EDTA.fa.stg1 $genome.EDTA.raw.fa`; + } + +# remove CDS in the non-redundant library and intact TEs +if (-s "$cds"){ + # report status + chomp ($date = `date`); + + # cleanup TE-related sequences in the CDS file with TEsorter + print "$date\tClean up TE-related sequences in the CDS file with TEsorter.\n\n"; + `perl $cleanup_TE -cds $cds -minlen 300 -tesorter $TEsorter -repeatmasker $repeatmasker -t $threads -rawlib $genome.EDTA.raw.fa 2>/dev/null`; + `rm ./$cds ./$cds.code.r* 2>/dev/null` unless $debug eq 1; + die "\tERROR: The $cds file is empty after TE clean up. Please check the file and $cds.code.noTE.\n\n" unless -s "$cds.code.noTE"; + $cds = "$cds.code.noTE"; + + # remove cds-related sequences in the EDTA library + print "\tRemove CDS-related sequences in the EDTA library.\n\n"; + my $rm_status = `${repeatmasker}RepeatMasker -e ncbi -pa $rm_threads -q -no_is -nolow -div 40 -cutoff 225 -lib $cds $genome.EDTA.raw.fa 2>/dev/null`; + `cp $genome.EDTA.raw.fa $genome.EDTA.raw.fa.masked` if $rm_status =~ /No repetitive sequences were detected/i; + `perl $cleanup_tandem -misschar N -Nscreen 1 -nc 1000 -nr 0.3 -minlen 80 -maxlen 5000000 -trf 0 -cleanN 1 -cleanT 1 -f $genome.EDTA.raw.fa.masked > $genome.EDTA.raw.fa.cln`; + + # remove cds-related sequences in intact TEs + print "\tRemove CDS-related sequences in intact TEs.\n\n"; + $rm_status = `${repeatmasker}RepeatMasker -e ncbi -pa $rm_threads -q -no_is -nolow -div 40 -cutoff 225 -lib $cds $genome.EDTA.intact.fa.cln 2>/dev/null`; + `cp $genome.EDTA.intact.fa.cln $genome.EDTA.intact.fa.cln.masked` if $rm_status =~ /No repetitive sequences were detected/i; + `perl $cleanup_tandem -misschar N -Nscreen 1 -nc 1000 -nr 0.8 -minlen 80 -maxlen 5000000 -trf 0 -cleanN 0 -f $genome.EDTA.intact.fa.cln.masked > $genome.EDTA.intact.fa.cln.rmCDS`; + `perl $output_by_list 1 $genome.EDTA.intact.fa.cln 1 $genome.EDTA.intact.fa.cln.masked.cleanup -ex -FA > $genome.EDTA.intact.fa.cln2`; + } else { + print "\tSkipping the CDS cleaning step (--cds [File]) since no CDS file is provided or it's empty.\n\n"; + copy_file("$genome.EDTA.raw.fa", "./$genome.EDTA.raw.fa.cln"); + copy_file("$genome.EDTA.intact.fa.cln", "./$genome.EDTA.intact.fa.cln2"); + } + +# Final rounds of redundancy removal and make final EDTA library +`perl $cleanup_nested -in $genome.EDTA.raw.fa.cln -threads $threads -minlen 80 -cov 0.95 -blastplus $blastplus 2>/dev/null`; + +# rename all TEs in the EDTA library +`perl $rename_TE $genome.EDTA.raw.fa.cln.cln > $genome.EDTA.TElib.fa`; +#`perl $rename_TE $genome.EDTA.raw.fa.cln.cln | perl $format_TElib - > $genome.EDTA.TElib.fa`; + +# identify novel TEs using the user provided $HQlib +if ($HQlib ne ''){ + # report status + chomp ($date = `date`); + print "$date\tCombine the high-quality TE library $HQlib with the EDTA library:\n\n"; + + # remove known TEs in the EDTA library + my $rm_status = `${repeatmasker}RepeatMasker -e ncbi -pa $rm_threads -q -no_is -nolow -div 40 -lib $HQlib $genome.EDTA.TElib.fa 2>/dev/null`; + `cp $genome.EDTA.TElib.fa $genome.EDTA.TElib.fa.masked` if $rm_status =~ /No repetitive sequences were detected/i; + `perl $cleanup_tandem -misschar N -nc 50000 -nr 0.8 -minlen 80 -minscore 3000 -trf 0 -cleanN 1 -cleanT 0 -f $genome.EDTA.TElib.fa.masked > $genome.EDTA.TElib.novel.fa`; + rename "$genome.EDTA.TElib.fa", "$genome.EDTA.TElib.ori.fa"; + `cat $HQlib $genome.EDTA.TElib.novel.fa > $genome.EDTA.TElib.fa`; + copy_file("$genome.EDTA.TElib.novel.fa", ".."); + } + +# reclassify intact TEs with the TE lib #113 +`${repeatmasker}RepeatMasker -e ncbi -pa $rm_threads -q -no_is -nolow -div 40 -lib $genome.EDTA.TElib.fa $genome.EDTA.intact.fa.cln2 2>/dev/null` unless (-s "$genome.EDTA.intact.fa.cln2.out" and $overwrite == 0); +die "ERROR: The masked file for $genome.EDTA.intact.fa.cln2 is not found! The RepeatMasker annotation on this file may be failed. Please check the $genome.EDTA.TElib.fa file for sequence naming formats especially when you provide a library via --curatedlib.\n" unless -s "$genome.EDTA.intact.fa.cln2.out"; +`perl $reclassify -seq $genome.EDTA.intact.fa.cln2 -RM $genome.EDTA.intact.fa.cln2.out -cov 80 -len 80 -iden 60`; # 80-80-60 + +# remove inconsistently classified intact TEs and generate the final intact TEs +`perl $output_by_list 1 $genome.EDTA.intact.fa.cln2.rename 1 $genome.EDTA.intact.fa.cln2.false.list -ex -FA > $genome.EDTA.intact.fa`; + + +## generate clean intact gff3 +my $intact_gff_head = "##This file follows the ENSEMBL standard: https://useast.ensembl.org/info/website/upload/gff3.html +##Column 3: Sequence Ontology of repeat features. Please refer to the SO database for more details: http://www.sequenceontology.org/. In cases where the SO database does not have the repeat feature, tentative SO names are used, with a full list included in EDTA/bin/TE_Sequence_Ontology.txt (Enhancement notes), and the sequence_ontology in Column 9 uses the closest parent SO. +##Column 9: +## ID: unique ID for this feature in the genome. +## classification: Same as Column 3 but formatted following the RepeatMasker naming convention. +## sequence_ontology: Sequence Ontology ID of the feature. +## identity: Sequence identity (0-1) between terminal sequences for structurally annotated TIR elements. +## ltr_identity: Sequence identity (0-1) between the left and right LTR regions for structurally annotated LTR elements. +## Name: Repeat family name. Some may be shown as coordinates, which are single-copy and structrually identified elements that are not included in the repeat library. +## method=structural: Indicate this entry is produced by structural annotation. +## motif/TSD/TIR: structural features of structurally annotated LTR and TIR elements. +##For more details about this file, please refer to the EDTA wiki: https://github.com/oushujun/EDTA/wiki/Making-sense-of-EDTA-usage-and-outputs---Q&A +##seqid source sequence_ontology start end score strand phase attributes"; + +# update the family names in the intact.raw.gff3 file +`perl $rename_by_list $genome.EDTA.intact.raw.gff3 $genome.EDTA.intact.fa.cln2.rename.list 1 > $genome.EDTA.intact.raw.gff3.rename`; +`sed 's/.*Name=//; s/;Classifica.*//' $genome.EDTA.intact.raw.gff3.rename | sort -u > $genome.EDTA.intact.raw.gff3.rename.famlist`; + +# get a dirty list of intact.gff +`grep \\> $genome.EDTA.intact.fa | sed 's/>//; s/#.*//' | perl $output_by_list 1 $genome.EDTA.intact.raw.gff3.rename.famlist 1 - -ex | awk '{print "Name\\t"\$1"\\nParent\\t"\$1"\\nID\\t"\$1}' > $genome.EDTA.intact.raw.gff3.rename.dirtlist`; + +# first attempt purging the gff3 +`perl $filter_gff $genome.EDTA.intact.raw.gff3.rename $genome.EDTA.intact.raw.gff3.rename.dirtlist > $genome.EDTA.intact.gff3`; + +# remake the remove list and purge again +`perl -nle 'my \$id = \$1 if /=(repeat_region[0-9]+);/; print "Parent\\t\$id\nName\\t\$id" if defined \$id' $genome.EDTA.intact.raw.gff3.rename.removed >> $genome.EDTA.intact.raw.gff3.rename.dirtlist`; +`echo "##gff-version 3\n##date $date\n##This file contains repeats annotated by EDTA $version based on structural features.\n$intact_gff_head" > $genome.EDTA.intact.gff3`; +`perl $filter_gff $genome.EDTA.intact.raw.gff3.rename $genome.EDTA.intact.raw.gff3.rename.dirtlist >> $genome.EDTA.intact.gff3`; + +# format gff3 +`perl $format_gff3 $genome.EDTA.intact.gff3 > gff3.temp.gff3; mv gff3.temp.gff3 $genome.EDTA.intact.gff3`; + +# add TE_IDs to the intact.fa sequence IDs +`perl $add_id -fa $genome.EDTA.intact.fa -gff $genome.EDTA.intact.gff3 > $genome.EDTA.intact.fa.renamed; mv $genome.EDTA.intact.fa.renamed $genome.EDTA.intact.fa`; + +# check results +die "ERROR: Final TE library not found in $genome.EDTA.TElib.fa" unless -s "$genome.EDTA.TElib.fa"; +die "ERROR: Intact TE annotation not found in $genome.EDTA.intact.gff3" unless -s "$genome.EDTA.intact.gff3"; +copy_file("$genome.EDTA.TElib.fa", ".."); +copy_file("$genome.EDTA.intact.fa", ".."); +copy_file("$genome.EDTA.intact.gff3", ".."); + +# remove intermediate files +`rm $genome.EDTA.intact.fa.cln.* $genome.EDTA.raw.fa.* $genome.EDTA.TElib.fa.* $genome.LTR.TIR.Helitron.fa.stg1.* $genome.masked *.cat.gz 2>/dev/null` if $debug eq 0; + +# report status +chomp ($date = `date`); +print "$date\tEDTA final stage finished! You may check out: + The final EDTA TE library: $genome.EDTA.TElib.fa\n"; +print " Family names of intact TEs have been updated by $HQlib: $genome.EDTA.intact.gff3\n" if $HQlib ne ''; +print " Comparing to the provided library, EDTA found these novel TEs: $genome.EDTA.TElib.novel.fa + The provided library has been incorporated into the final library: $genome.EDTA.TElib.fa\n\n" if $HQlib ne ''; +chdir ".."; + + +##################################### +###### Post-library annotation ###### +##################################### + +ANNO: +if ($anno == 1){ + # report status + chomp ($date = `date`); + print "$date\tPerform post-EDTA analysis for whole-genome annotation:\n\n"; + + # Make the post-library annotation working directory + `mkdir $genome.EDTA.anno` unless -e "$genome.EDTA.anno" && -d "$genome.EDTA.anno"; + chdir "$genome.EDTA.anno"; + `rm ./* 2>/dev/null` if $overwrite == 1; + `rm $genome.EDTA.TElib.fa* 2>/dev/null`; # clean up libraries + `cp ../$genome.EDTA.final/$genome.EDTA.TElib.fa ./`; + `cp ../$genome.EDTA.final/$genome.EDTA.intact.gff3 ./`; + `cp ../$exclude ./` if $exclude ne ''; + `ln -s ../$genome $genome` unless -e $genome; + + my $gff_head = "##This file follows the ENSEMBL standard: https://useast.ensembl.org/info/website/upload/gff3.html +##Column 3: Sequence Ontology of repeat features. Please refer to the SO database for more details: http://www.sequenceontology.org/. In cases where the SO database does not have the repeat feature, tentative SO names are used, with a full list included in EDTA/bin/TE_Sequence_Ontology.txt (Enhancement notes), and the sequence_ontology in Column 9 uses the closest parent SO. +##Column 7: The Smith-Waterman score generated by RepeatMasker, only available for homology entries. +##Column 9: +## ID: unique ID for this feature in the genome. +## classification: Same as Column 3 but formatted following the RepeatMasker naming convention. +## sequence_ontology: Sequence Ontology ID of the feature. +## identity: Sequence identity (0-1) between the library sequence and the target region. +## ltr_identity: Sequence identity (0-1) between the left and right LTR regions for structurally annotated LTR elements. +## Name: Repeat family name. Some may be shown as coordinates, which are single-copy and structrually identified elements that are not included in the repeat library. +## method: Indicate if this entry is produced by structural annotation or homology annotation. +## motif/TSD/TIR: structural features of structurally annotated LTR and TIR elements. +##For more details about this file, please refer to the EDTA wiki: https://github.com/oushujun/EDTA/wiki/Making-sense-of-EDTA-usage-and-outputs---Q&A +##seqid source sequence_ontology start end score strand phase attributes"; + + # annotate TEs using RepeatMasker + if ($rmout ne ''){ + print STDERR "$date\tA RepeatMasker result file $rmout is provided! Will use this file without running RepeatMasker.\n\n"; + if (-e "$genome.out"){ + my $old_rmout = `ls -l $genome.out|perl -nle 'my (\$month, \$day, \$time) = (split)[6,7,8]; \$time =~ s/://; print "\${month}_\${day}_\$time"'`; + chomp $old_rmout; + print "\t$genome.out exists in the $genome.EDTA.anno folder, renamed file to ${genome}_$old_rmout.out\n\n"; + `mv $genome.out ${genome}_$old_rmout.out`; + } + `ln -s $rmout $genome.out`; + } else { + print STDERR "$date\tHomology-based annotation of TEs using $genome.EDTA.TElib.fa from scratch.\n\n"; + `${repeatmasker}RepeatMasker -e ncbi -pa $rm_threads -q -no_is -nolow -div $maxdiv -lib $genome.EDTA.TElib.fa $genome 2>/dev/null`; + } + die "ERROR: RepeatMasker results not found in $genome.out!\n\n" unless -s "$genome.out" or -s "$genome.mod.out"; + + # exclude regions from TE annotation and make whole-genome TE annotation + `perl $make_masked -genome $genome -rmout $genome.out -maxdiv $maxdiv -minscore 300 -minlen 80 -hardmask 1 -misschar N -threads $threads -exclude $exclude`; + # combine RepeatMasker lines that appears to be the same element + `perl $combine_RMrows -rmout $genome.out.new -maxdiv 3.5 -maxgap 35`; + `mv $genome.out.new.cmb $genome.EDTA.RM.out`; + `perl $RMout2bed $genome.EDTA.RM.out > $genome.EDTA.RM.bed`; # a regular enriched bed + `perl $bed2gff $genome.EDTA.RM.bed TE_homo > $genome.EDTA.RM.gff3`; + `perl $gff2bed $genome.EDTA.RM.gff3 homology > $genome.EDTA.RM.bed`; # add the last column to this bed + + # combine homology-based and strutrual-based annotation (partly overlapping) + `perl $gff2bed $genome.EDTA.intact.gff3 structural > $genome.EDTA.intact.bed`; + `perl $combine_overlap $genome.EDTA.intact.bed $genome.EDTA.intact.bed.cmb 5`; + `perl $get_frag $genome.EDTA.RM.bed $genome.EDTA.intact.bed.cmb $threads`; + `perl $keep_nest $genome.EDTA.intact.bed $genome.EDTA.RM.bed $threads`; + `grep homology $genome.EDTA.intact.bed-$genome.EDTA.RM.bed > $genome.EDTA.intact.bed-$genome.EDTA.RM.bed.homo`; + `sort -suV $genome.EDTA.intact.bed-$genome.EDTA.RM.bed.homo $genome.EDTA.RM.bed-$genome.EDTA.intact.bed.cmb > $genome.EDTA.homo.bed`; + `perl $bed2gff $genome.EDTA.homo.bed TE_homo > $genome.EDTA.homo.gff3`; + `cat $genome.EDTA.intact.gff3 $genome.EDTA.homo.gff3 > $genome.EDTA.TEanno.gff3.raw`; + `grep -v '^#' $genome.EDTA.TEanno.gff3.raw | sort -sV -k1,1 -k4,4 | perl -0777 -ne '\$date=\`date\`; \$date=~s/\\s+\$//; print "##gff-version 3\\n##date \$date\\n##This file contains repeats annotated by EDTA $version with both structural and homology methods. Repeats can be overlapping due to nested insertions.\\n$gff_head\\n\$_"' - > $genome.EDTA.TEanno.gff3`; + `perl $format_gff3 $genome.EDTA.TEanno.gff3 > gff3.temp.gff3; mv gff3.temp.gff3 $genome.EDTA.TEanno.gff3`; + `rm $genome.EDTA.TEanno.gff3.raw 2>/dev/null`; + + # make non-overlapping annotation + `perl $gff2bed $genome.EDTA.TEanno.gff3 structural > $genome.EDTA.TEanno.bed`; + `perl $split_overlap $genome.EDTA.TEanno.bed $genome.EDTA.TEanno.split.bed`; + `echo "##gff-version 3\n##date $date\n##This file contains all repeats annotated by EDTA $version in the split format (non-overlapping). Repeats can be broken into pieces by nested insertions.\n$gff_head" > $genome.EDTA.TEanno.split.gff3`; + `perl $bed2gff $genome.EDTA.TEanno.split.bed | grep -v '^#' >> $genome.EDTA.TEanno.split.gff3`; + `perl $format_gff3 $genome.EDTA.TEanno.split.gff3 > gff3.temp.gff3; mv gff3.temp.gff3 $genome.EDTA.TEanno.split.gff3`; + `perl $gff2RMout $genome.EDTA.TEanno.split.gff3 $genome.EDTA.TEanno.split.out`; + + # make plots + `perl $div_table $genome.EDTA.TEanno.bed $genome $genome`; + `Rscript $div_plot $genome.div_long $genome 2>/dev/null`; + `python $density_table -genome $genome -gff $genome.EDTA.TEanno.split.gff3 > $genome.EDTA.TEanno.split.density`; + `Rscript $density_plot $genome.EDTA.TEanno.split.density 2>/dev/null`; + `mv chromosome_density_plots.pdf $genome.EDTA.TEanno.density_plots.pdf`; + + # make summary table for the non-overlapping annotation + `perl $count_base $genome > $genome.stats`; + `perl -nle 'my (\$chr, \$s, \$e, \$anno, \$dir, \$supfam)=(split)[0,1,2,3,8,12]; print "10000 0.001 0.001 0.001 \$chr \$s \$e NA \$dir \$anno \$supfam"' $genome.EDTA.TEanno.split.bed > $genome.EDTA.TEanno.out`; + `perl $buildSummary -maxDiv 40 -stats $genome.stats $genome.EDTA.TEanno.out > $genome.EDTA.TEanno.sum 2>/dev/null`; + my $tot_TE = `grep Total $genome.EDTA.TEanno.sum|grep %|awk '{print \$4}'`; + chomp $tot_TE; + + # make low-threshold masked genome for MAKER + `perl $make_masked -genome $genome -rmout $genome.out -maxdiv 30 -minscore 1000 -minlen 1000 -hardmask 1 -misschar N -threads $threads -exclude $exclude` unless (-s "$genome.MAKER.masked" and $overwrite == 0); + `mv $genome.new.masked $genome.MAKER.masked`; + my $maker_TE = `perl $count_base $genome.MAKER.masked`; + $maker_TE = (split /\s+/, $maker_TE)[3]; + $maker_TE = sprintf("%.2f%%", $maker_TE*100); + + # check results and report status + die "ERROR: TE annotation results not found in $genome.EDTA.TEanno.gff3!\n\n" unless -s "$genome.EDTA.TEanno.gff3"; + print "ERROR: The masked genome for MAKER annotation is not found in $genome.MAKER.masked!\n\n" unless -s "$genome.MAKER.masked"; + chomp ($date = `date`); + print "$date\tTE annotation using the EDTA library has finished! Check out:\n"; + print "\t\tWhole-genome TE annotation (total TE: $tot_TE): $genome.EDTA.TEanno.gff3\n"; + print "\t\tWhole-genome TE annotation summary: $genome.EDTA.TEanno.sum\n"; + print "\t\tWhole-genome TE divergence plot: ${genome}_divergence_plot.pdf\n"; + print "\t\tWhole-genome TE density plot: $genome.EDTA.TEanno.density_plots.pdf\n"; + print "\t\tLow-threshold TE masking for MAKER gene annotation (masked: $maker_TE): $genome.MAKER.masked\n\n"; + + # copy results out + `cp $genome.MAKER.masked ../`; # make no backup for this file + copy_file("$genome.EDTA.TEanno.gff3", ".."); + copy_file("$genome.EDTA.TEanno.sum", ".."); + copy_file("${genome}_divergence_plot.pdf", ".."); + copy_file("$genome.EDTA.TEanno.density_plots.pdf", ".."); + + # evaluate the annotation consistency + if ($evaluate == 1){ + # report status + chomp ($date = `date`); + print "$date\tEvaluate the level of inconsistency for whole-genome TE annotation:\n\n"; + + # extract whole-genome TE and perform all-v-all blast, then summarize the results + `awk '{if (\$5~/[0-9]+/ && \$1>300 && \$7-\$6>80) print \$11"\t"\$5":"\$6".."\$7}' $genome.EDTA.TEanno.out | perl $call_seq - -C $genome > $genome.EDTA.TE.fa`; + `perl $cleanup_nested -in $genome.EDTA.TE.fa -threads $threads -minlen 80 -miniden 80 -cov 0.95 -blastplus $blastplus -iter 1 -maxcount 100000 2>/dev/null`; + `for i in nested all redun; do perl $count_nested -in $genome.EDTA.TE.fa.stat -cat \$i > $genome.EDTA.TE.fa.stat.\$i.sum; done`; + + # check results and report status + die "ERROR: TE annotation stats results not found in $genome.EDTA.TE.fa.stat!\n\n" unless -s "$genome.EDTA.TE.fa.stat"; + chomp ($date = `date`); + print "$date\tEvaluation of TE annotation finished! Check out these files:\n + Overall: $genome.EDTA.TE.fa.stat.all.sum + Nested: $genome.EDTA.TE.fa.stat.nested.sum + Non-nested: $genome.EDTA.TE.fa.stat.redun.sum\n\n"; + } + + print "\t\tIf you want to learn more about the formatting and information of these files, please visit: + \t\thttps://github.com/oushujun/EDTA/wiki/Making-sense-of-EDTA-usage-and-outputs---Q&A\n\n"; + + } + + +########################## +###### Subroutines ####### +########################## + +sub copy_file { + my ($file, $path) = ($_[0], $_[1]); + # Generate new name with the last modified date and time + my $mod_time = (stat($file))[9]; + my $new_name = $file . "_" . strftime("%Y%m%d_%H%M%S", localtime($mod_time)); + + # resolve symlinks and existing files + if (-l "$path/$file") { + # File is a symbolic link + unlink "$path/$file" or die "ERROR: Failed to remove symbolic link for $path/$file\n\n"; + } elsif (-f "$path/$file") { + # File is a regular file + rename "$path/$file", "$path/$new_name" or die "ERROR: Failed to rename file: $path/$file\n\n"; + } + + # copy file to the path if it's not the current path + `cp $file $path` if abs_path($path) ne abs_path('.'); + } diff --git a/bin/setup_RepeatModeler_postprocess.sh b/bin/setup_RepeatModeler_postprocess.sh index a601979..c015fbe 100755 --- a/bin/setup_RepeatModeler_postprocess.sh +++ b/bin/setup_RepeatModeler_postprocess.sh @@ -18,5 +18,5 @@ EOF sed -n 567,579p "$script_path/EDTA_raw.pl" \ >> input/RepeatModeler_postprocess.pl -sed -n 585,586p "$script_path/EDTA_raw.pl" \ +sed -n 585,584p "$script_path/EDTA_raw.pl" \ >> input/RepeatModeler_postprocess.pl diff --git a/bin/setup_combine_intact_TEs.sh b/bin/setup_combine_intact_TEs.sh new file mode 100755 index 0000000..23e3979 --- /dev/null +++ b/bin/setup_combine_intact_TEs.sh @@ -0,0 +1,14 @@ +#!/usr/bin/env bash + +script_path=$(dirname $0) + +cat > raw/combine_intact_TEs.pl << EOF +my \$genome = 'genome'; +my \$threads = $1; + +my \$bed2gff = "$script_path/bed2gff.pl"; + +EOF + +sed -n 495,504p "$script_path/EDTA.pl" \ + >> raw/combine_intact_TEs.pl \ No newline at end of file diff --git a/bin/setup_final_filter.sh b/bin/setup_final_filter.sh new file mode 100755 index 0000000..53cf715 --- /dev/null +++ b/bin/setup_final_filter.sh @@ -0,0 +1,31 @@ +#!/usr/bin/env bash + +script_path=$(dirname $0) + +cat > final/final_filter.pl << EOF + +my \$genome = 'genome'; +my \$threads = $1; +my \$rm_threads = $1; + +my \$overwrite = 1; + +my \$cleanup_nested = "$script_path/cleanup_nested.pl"; +my \$rename_TE = "$script_path/rename_TE.pl"; +my \$reclassify = "$script_path/classify_by_lib_RM.pl"; +my \$output_by_list = '$script_path/output_by_list.pl'; +my \$rename_by_list = "$script_path/rename_by_list.pl"; +my \$filter_gff = "$script_path/filter_gff3.pl"; +my \$format_gff3 = "$script_path/format_gff3.pl"; +my \$add_id = "$script_path/add_id.pl"; + +my \$blastplus = ''; # Assumed on PATH +my \$repeatmasker = ''; # Assumed on PATH + +EOF + +sed -n 607,612p "$script_path/EDTA.pl" \ + >> final/final_filter.pl + +sed -n 629,676p "$script_path/EDTA.pl" \ + >> final/final_filter.pl diff --git a/conf/modules.config b/conf/modules.config index 82cff15..7e9147f 100644 --- a/conf/modules.config +++ b/conf/modules.config @@ -13,7 +13,6 @@ process { } withName: 'EDTA:LTR_RETRIEVER_POSTPROCESS' { - ext.prefix = { "${meta.id}.pp" } publishDir = [ path: { "${params.outdir}/raw/ltr" }, @@ -27,7 +26,6 @@ process { } withName: 'EDTA:ANNOSINE_POSTPROCESS' { - ext.prefix = { "${meta.id}.pp" } publishDir = [ path: { "${params.outdir}/raw/sine" }, @@ -41,7 +39,6 @@ process { } withName: 'EDTA:REPEATMODELER_POSTPROCESS' { - ext.prefix = { "${meta.id}.pp" } publishDir = [ path: { "${params.outdir}/raw/line" }, @@ -51,7 +48,6 @@ process { } withName: 'EDTA:TIR_LEARNER_POSTPROCESS' { - ext.prefix = { "${meta.id}.pp" } publishDir = [ path: { "${params.outdir}/raw/tir" }, @@ -88,7 +84,6 @@ process { } withName: 'EDTA:HELITRONSCANNER_POSTPROCESS' { - ext.prefix = { "${meta.id}.pp" } publishDir = [ path: { "${params.outdir}/raw/helitron" }, @@ -97,8 +92,18 @@ process { ] } + withName: 'COMBINE_INTACT_TES' { + ext.prefix = { "${meta.id}" } + + publishDir = [ + path: { "${params.outdir}/raw/intact" }, + mode: params.publish_dir_mode, + saveAs: { filename -> filename.equals('versions.yml') ? null : filename } + ] + } + withName: 'EDTA:PROCESS_K' { - ext.prefix = { "${meta.id}.stg1" } + ext.prefix = { "${meta.id}" } publishDir = [ path: { "${params.outdir}/stg1" }, @@ -106,5 +111,15 @@ process { saveAs: { filename -> filename.equals('versions.yml') ? null : filename } ] } + + withName: 'EDTA:FINAL_FILTER' { + ext.prefix = { "${meta.id}" } + + publishDir = [ + path: { "${params.outdir}/final" }, + mode: params.publish_dir_mode, + saveAs: { filename -> filename.equals('versions.yml') ? null : filename } + ] + } } \ No newline at end of file diff --git a/modules/local/annosine_postprocess/main.nf b/modules/local/annosine_postprocess/main.nf index 42814ca..29467dc 100644 --- a/modules/local/annosine_postprocess/main.nf +++ b/modules/local/annosine_postprocess/main.nf @@ -12,7 +12,7 @@ process ANNOSINE_POSTPROCESS { path "input/Seed_SINE.fa" output: - tuple val(meta), path('*.SINE.raw.fa') , emit: sine_fa + tuple val(meta), path('*.SINE.fa') , emit: sine_fa path "versions.yml" , emit: versions when: @@ -21,7 +21,6 @@ process ANNOSINE_POSTPROCESS { script: def prefix = task.ext.prefix ?: "${meta.id}" def MDUST_VERSION = '2006.10.17' // WARN: Manually update when changing Bioconda assets - if ( "$prefix" == 'genome' ) error "Input and output names are the same, use \"task.ext.prefix\" to disambiguate!" """ setup_AnnoSINE_postprocess.sh \\ $task.cpus @@ -34,7 +33,7 @@ process ANNOSINE_POSTPROCESS { mv \\ input/genome.SINE.raw.fa \\ - ${prefix}.SINE.raw.fa + ${prefix}.SINE.fa cat <<-END_VERSIONS > versions.yml "${task.process}": @@ -48,9 +47,8 @@ process ANNOSINE_POSTPROCESS { stub: def prefix = task.ext.prefix ?: "${meta.id}" def MDUST_VERSION = '2006.10.17' // WARN: Manually update when changing Bioconda assets - if ( "$prefix" == 'genome' ) error "Input and output names are the same, use \"task.ext.prefix\" to disambiguate!" """ - touch ${prefix}.SINE.raw.fa + touch ${prefix}.SINE.fa cat <<-END_VERSIONS > versions.yml "${task.process}": diff --git a/modules/local/combine_intact_tes/environment.yml b/modules/local/combine_intact_tes/environment.yml new file mode 100644 index 0000000..6f8a4fc --- /dev/null +++ b/modules/local/combine_intact_tes/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: + - bioconda::mdust=2006.10.17 + - bioconda::tesorter=1.4.7 + - bioconda::trf=4.09.1 + - conda-forge::perl=5.32.1 \ No newline at end of file diff --git a/modules/local/combine_intact_tes/main.nf b/modules/local/combine_intact_tes/main.nf new file mode 100644 index 0000000..663d33e --- /dev/null +++ b/modules/local/combine_intact_tes/main.nf @@ -0,0 +1,64 @@ +process COMBINE_INTACT_TES { + tag "$meta.id" + label 'process_single' + + conda "${moduleDir}/environment.yml" + container "${ workflow.containerEngine == 'singularity' && !task.ext.singularity_pull_docker_container ? + 'https://community-cr-prod.seqera.io/docker/registry/v2/blobs/sha256/cb/cb105ba7d586ab31c1f39feb04c0255a39cc5a55ae7f6ea53f4bf76cdba8a3e5/data': + 'community.wave.seqera.io/library/mdust_tesorter_trf_perl:3424609103d3b065' }" + + input: + tuple val(meta), path(genome, name: 'raw/genome') + path "raw/genome.LTR.intact.raw.fa" + path "raw/genome.LTR.intact.raw.gff3" + path "raw/genome.TIR.intact.raw.fa" + path "raw/genome.TIR.intact.raw.bed" + path "raw/genome.Helitron.intact.raw.fa" + path "raw/genome.Helitron.intact.raw.bed" + + output: + tuple val(meta), path('*.intact.fa') , emit: intact_raw_fa + tuple val(meta), path('*.intact.gff3') , emit: intact_raw_gff + path "versions.yml" , emit: versions + + when: + task.ext.when == null || task.ext.when + + script: + def prefix = task.ext.prefix ?: "${meta.id}" + """ + setup_combine_intact_TEs.sh \\ + $task.cpus + + cd raw + + perl combine_intact_TEs.pl + + cd - + + mv \\ + raw/genome.EDTA.intact.raw.fa \\ + ${prefix}.intact.fa + + mv \\ + raw/genome.EDTA.intact.raw.gff3 \\ + ${prefix}.intact.gff3 + + cat <<-END_VERSIONS > versions.yml + "${task.process}": + perl: \$(perl -v | sed -n 's|This is perl.*(\\(.*\\)).*|\\1|p') + END_VERSIONS + """ + + stub: + def prefix = task.ext.prefix ?: "${meta.id}" + """ + touch ${prefix}.intact.fa + touch ${prefix}.intact.gff3 + + cat <<-END_VERSIONS > versions.yml + "${task.process}": + perl: \$(perl -v | sed -n 's|This is perl.*(\\(.*\\)).*|\\1|p') + END_VERSIONS + """ +} \ No newline at end of file diff --git a/modules/local/final_filter/environment.yml b/modules/local/final_filter/environment.yml new file mode 100644 index 0000000..e03f80c --- /dev/null +++ b/modules/local/final_filter/environment.yml @@ -0,0 +1,8 @@ +--- +# yaml-language-server: $schema=https://raw.githubusercontent.com/nf-core/modules/master/modules/environment-schema.json +channels: + - conda-forge + - bioconda +dependencies: + - bioconda::blast=2.16.0 + - bioconda::repeatmasker=4.1.7 \ No newline at end of file diff --git a/modules/local/final_filter/main.nf b/modules/local/final_filter/main.nf new file mode 100644 index 0000000..3f8c1c7 --- /dev/null +++ b/modules/local/final_filter/main.nf @@ -0,0 +1,69 @@ +process FINAL_FILTER { + tag "$meta.id" + label 'process_medium' + + conda "${moduleDir}/environment.yml" + container "${ workflow.containerEngine == 'singularity' && !task.ext.singularity_pull_docker_container ? + 'https://community-cr-prod.seqera.io/docker/registry/v2/blobs/sha256/91/91c1946f5edb90aa9d2c26eb14e7348f8f4e94dda03bdb2185b72cb3c2d54932/data': + 'community.wave.seqera.io/library/blast_repeatmasker:816962ca420d1d16' }" + + input: + tuple val(meta), path(genome, name: 'final/genome') + path('final/genome.EDTA.raw.fa.cln') + path('final/genome.EDTA.intact.fa.cln2') + path('final/genome.EDTA.intact.raw.gff3') + + output: + tuple val(meta), path('*.intact.fa') , emit: intact_fa + tuple val(meta), path('*.intact.gff3') , emit: intact_gff + tuple val(meta), path('*.TElib.fa') , emit: telib_fa + path "versions.yml" , emit: versions + + when: + task.ext.when == null || task.ext.when + + script: + def prefix = task.ext.prefix ?: "${meta.id}" + """ + setup_final_filter.sh \\ + $task.cpus + + cd final + + perl final_filter.pl + + cd - + + mv \\ + final/genome.EDTA.intact.fa \\ + ${prefix}.intact.fa + + mv \\ + final/genome.EDTA.intact.gff3 \\ + ${prefix}.intact.gff3 + + mv \\ + final/genome.EDTA.TElib.fa \\ + ${prefix}.TElib.fa + + cat <<-END_VERSIONS > versions.yml + "${task.process}": + perl: \$(perl -v | sed -n 's|This is perl.*(\\(.*\\)).*|\\1|p') + repeatmasker: \$(RepeatMasker -v | sed 's/RepeatMasker version //1') + END_VERSIONS + """ + + stub: + def prefix = task.ext.prefix ?: "${meta.id}" + """ + touch ${prefix}.intact.fa + touch ${prefix}.intact.gff3 + touch ${prefix}.TElib.fa + + cat <<-END_VERSIONS > versions.yml + "${task.process}": + perl: \$(perl -v | sed -n 's|This is perl.*(\\(.*\\)).*|\\1|p') + repeatmasker: \$(RepeatMasker -v | sed 's/RepeatMasker version //1') + END_VERSIONS + """ +} \ No newline at end of file diff --git a/modules/local/helitronscanner_postprocess/main.nf b/modules/local/helitronscanner_postprocess/main.nf index d0eb6a3..a1e8b30 100644 --- a/modules/local/helitronscanner_postprocess/main.nf +++ b/modules/local/helitronscanner_postprocess/main.nf @@ -13,10 +13,10 @@ process HELITRONSCANNER_POSTPROCESS { path "input/genome.HelitronScanner.filtered.ext.fa" output: - tuple val(meta), path('*.Helitron.intact.raw.fa.anno.list') , emit: anno_list - tuple val(meta), path('*.Helitron.intact.raw.bed') , emit: raw_bed - tuple val(meta), path('*.Helitron.intact.raw.gff3') , emit: raw_gff - tuple val(meta), path('*.Helitron.intact.raw.fa') , emit: raw_fa + tuple val(meta), path('*.Helitron.intact.anno.list') , emit: anno_list + tuple val(meta), path('*.Helitron.intact.bed') , emit: raw_bed + tuple val(meta), path('*.Helitron.intact.gff3') , emit: raw_gff + tuple val(meta), path('*.Helitron.intact.fa') , emit: raw_fa path "versions.yml" , emit: versions when: @@ -25,7 +25,6 @@ process HELITRONSCANNER_POSTPROCESS { script: def prefix = task.ext.prefix ?: "${meta.id}" def MDUST_VERSION = '2006.10.17' // WARN: Manually update when changing Bioconda assets - if ( "$prefix" == 'genome' ) error "Input and output names are the same, use \"task.ext.prefix\" to disambiguate!" """ setup_HelitronScanner_postprocess.sh \\ $task.cpus @@ -38,19 +37,19 @@ process HELITRONSCANNER_POSTPROCESS { mv \\ genome.Helitron.intact.raw.fa.anno.list \\ - ${prefix}.Helitron.intact.raw.fa.anno.list + ${prefix}.Helitron.intact.anno.list mv \\ genome.Helitron.intact.raw.bed \\ - ${prefix}.Helitron.intact.raw.bed + ${prefix}.Helitron.intact.bed mv \\ genome.Helitron.intact.raw.gff3 \\ - ${prefix}.Helitron.intact.raw.gff3 + ${prefix}.Helitron.intact.gff3 mv \\ genome.Helitron.intact.raw.fa \\ - ${prefix}.Helitron.intact.raw.fa + ${prefix}.Helitron.intact.fa cat <<-END_VERSIONS > versions.yml "${task.process}": @@ -64,12 +63,11 @@ process HELITRONSCANNER_POSTPROCESS { stub: def prefix = task.ext.prefix ?: "${meta.id}" def MDUST_VERSION = '2006.10.17' // WARN: Manually update when changing Bioconda assets - if ( "$prefix" == 'genome' ) error "Input and output names are the same, use \"task.ext.prefix\" to disambiguate!" """ - touch ${prefix}.Helitron.intact.raw.fa.anno.list - touch ${prefix}.Helitron.intact.raw.bed - touch ${prefix}.Helitron.intact.raw.gff3 - touch ${prefix}.Helitron.intact.raw.fa + touch ${prefix}.Helitron.intact.anno.list + touch ${prefix}.Helitron.intact.bed + touch ${prefix}.Helitron.intact.gff3 + touch ${prefix}.Helitron.intact.fa cat <<-END_VERSIONS > versions.yml "${task.process}": diff --git a/modules/local/ltr_retriever_postprocess/main.nf b/modules/local/ltr_retriever_postprocess/main.nf index e950b59..dd79654 100644 --- a/modules/local/ltr_retriever_postprocess/main.nf +++ b/modules/local/ltr_retriever_postprocess/main.nf @@ -15,10 +15,10 @@ process LTR_RETRIEVER_POSTPROCESS { path "input/genome.LTRlib.fa" output: - tuple val(meta), path('*.LTR.raw.fa') , emit: raw_fa - tuple val(meta), path('*.LTR.intact.raw.fa') , emit: intact_raw_fa - tuple val(meta), path('*.LTR.intact.raw.gff3') , emit: intact_raw_gff3 - tuple val(meta), path('*.LTR.intact.raw.fa.anno.list') , emit: anno_list + tuple val(meta), path('*.LTR.fa') , emit: raw_fa + tuple val(meta), path('*.LTR.intact.fa') , emit: intact_raw_fa + tuple val(meta), path('*.LTR.intact.gff3') , emit: intact_raw_gff3 + tuple val(meta), path('*.LTR.intact.anno.list') , emit: anno_list path "versions.yml" , emit: versions when: @@ -27,7 +27,6 @@ process LTR_RETRIEVER_POSTPROCESS { script: def prefix = task.ext.prefix ?: "${meta.id}" def MDUST_VERSION = '2006.10.17' // WARN: Manually update when changing Bioconda assets - if ( "$prefix" == 'genome' ) error "Input and output names are the same, use \"task.ext.prefix\" to disambiguate!" """ setup_LTR_retriever_postprocess.sh \\ $task.cpus @@ -40,19 +39,19 @@ process LTR_RETRIEVER_POSTPROCESS { mv \\ genome.LTR.raw.fa \\ - ${prefix}.LTR.raw.fa + ${prefix}.LTR.fa mv \\ genome.LTR.intact.raw.fa \\ - ${prefix}.genome.LTR.intact.raw.fa + ${prefix}.LTR.intact.fa mv \\ genome.LTR.intact.raw.gff3 \\ - ${prefix}.LTR.intact.raw.gff3 + ${prefix}.LTR.intact.gff3 mv \\ genome.LTR.intact.raw.fa.anno.list \\ - ${prefix}.LTR.intact.raw.fa.anno.list + ${prefix}.LTR.intact.anno.list cat <<-END_VERSIONS > versions.yml "${task.process}": @@ -66,12 +65,11 @@ process LTR_RETRIEVER_POSTPROCESS { stub: def prefix = task.ext.prefix ?: "${meta.id}" def MDUST_VERSION = '2006.10.17' // WARN: Manually update when changing Bioconda assets - if ( "$prefix" == 'genome' ) error "Input and output names are the same, use \"task.ext.prefix\" to disambiguate!" """ - touch ${prefix}.LTR.raw.fa - touch ${prefix}.genome.LTR.intact.raw.fa - touch ${prefix}.LTR.intact.raw.gff3 - touch ${prefix}.LTR.intact.raw.fa.anno.list + touch ${prefix}.LTR.fa + touch ${prefix}.LTR.intact.fa + touch ${prefix}.LTR.intact.gff3 + touch ${prefix}.LTR.intact.anno.list cat <<-END_VERSIONS > versions.yml "${task.process}": diff --git a/modules/local/process_k/main.nf b/modules/local/process_k/main.nf index e385899..dc62ed7 100644 --- a/modules/local/process_k/main.nf +++ b/modules/local/process_k/main.nf @@ -17,16 +17,16 @@ process PROCESS_K { path(helitron , name: "genome.EDTA.raw/genome.Helitron.intact.raw.fa") output: - tuple val(meta), path('*.fasta') , emit: stg1_fa + tuple val(meta), path("${prefix}.fasta"), emit: stg1_fa + tuple val(meta), path('*.intact.fasta') , emit: intact_fa path "versions.yml" , emit: versions when: task.ext.when == null || task.ext.when script: - def prefix = task.ext.prefix ?: "${meta.id}" + prefix = task.ext.prefix ?: "${meta.id}" def args = task.ext.args ?: '' - if ( "$prefix" == 'genome' ) error "Input and output names are the same, use \"task.ext.prefix\" to disambiguate!" def touch_ltr = ltr ? '' : "touch genome.EDTA.raw/genome.LTR.raw.fa" def touch_ltrint = ltrint ? '' : "touch genome.EDTA.raw/genome.LTR.intact.raw.fa" @@ -64,6 +64,10 @@ process PROCESS_K { mv \\ genome.EDTA.combine/genome.EDTA.fa.stg1 \\ ${prefix}.fasta + + mv \\ + genome.EDTA.combine/genome.EDTA.intact.fa.cln \\ + ${prefix}.intact.fasta cat <<-END_VERSIONS > versions.yml "${task.process}": @@ -73,10 +77,10 @@ process PROCESS_K { """ stub: - def prefix = task.ext.prefix ?: "${meta.id}" - if ( "$prefix" == 'genome' ) error "Input and output names are the same, use \"task.ext.prefix\" to disambiguate!" + prefix = task.ext.prefix ?: "${meta.id}" """ touch ${prefix}.fasta + touch ${prefix}.intact.fasta cat <<-END_VERSIONS > versions.yml "${task.process}": diff --git a/modules/local/repeatmodeler_postprocess/main.nf b/modules/local/repeatmodeler_postprocess/main.nf index c1f6459..d79221f 100644 --- a/modules/local/repeatmodeler_postprocess/main.nf +++ b/modules/local/repeatmodeler_postprocess/main.nf @@ -13,7 +13,7 @@ process REPEATMODELER_POSTPROCESS { output: tuple val(meta), path('*.RM2.fa') , emit: rm2_fa - tuple val(meta), path('*.LINE.raw.fa') , emit: line_raw , optional: true + tuple val(meta), path('*.LINE.fa') , emit: line_raw , optional: true path "versions.yml" , emit: versions when: @@ -22,7 +22,6 @@ process REPEATMODELER_POSTPROCESS { script: def prefix = task.ext.prefix ?: "${meta.id}" def MDUST_VERSION = '2006.10.17' // WARN: Manually update when changing Bioconda assets - if ( "$prefix" == 'genome' ) error "Input and output names are the same, use \"task.ext.prefix\" to disambiguate!" """ setup_RepeatModeler_postprocess.sh \\ $task.cpus @@ -34,15 +33,13 @@ process REPEATMODELER_POSTPROCESS { cd - mv \\ - genome.RM2.fa \\ + input/genome.RM2.fa \\ ${prefix}.RM2.fa - if [[ -s genome.LINE.raw.fa ]]; then + if [[ -s input/genome.LINE.raw.fa ]]; then mv \\ - genome.LINE.raw.fa \\ - ${prefix}.LINE.raw.fa - else - rm genome.LINE.raw.fa + input/genome.LINE.raw.fa \\ + ${prefix}.LINE.fa fi cat <<-END_VERSIONS > versions.yml @@ -57,7 +54,6 @@ process REPEATMODELER_POSTPROCESS { stub: def prefix = task.ext.prefix ?: "${meta.id}" def MDUST_VERSION = '2006.10.17' // WARN: Manually update when changing Bioconda assets - if ( "$prefix" == 'genome' ) error "Input and output names are the same, use \"task.ext.prefix\" to disambiguate!" """ touch ${prefix}.RM2.fa diff --git a/modules/local/tir_learner_postprocess/main.nf b/modules/local/tir_learner_postprocess/main.nf index 275fc5a..d4001db 100644 --- a/modules/local/tir_learner_postprocess/main.nf +++ b/modules/local/tir_learner_postprocess/main.nf @@ -13,9 +13,9 @@ process TIR_LEARNER_POSTPROCESS { path "input/TIR-Learner-Result/TIR-Learner_FinalAnn.gff3" output: - tuple val(meta), path('*.TIR.intact.raw.fa') , emit: intact_raw_fa - tuple val(meta), path('*.TIR.intact.raw.gff3') , emit: intact_raw_gff3 - tuple val(meta), path('*.TIR.intact.raw.bed') , emit: intact_raw_bed + tuple val(meta), path('*.TIR.intact.fa') , emit: intact_raw_fa + tuple val(meta), path('*.TIR.intact.gff3') , emit: intact_raw_gff3 + tuple val(meta), path('*.TIR.intact.bed') , emit: intact_raw_bed path "versions.yml" , emit: versions when: @@ -24,7 +24,6 @@ process TIR_LEARNER_POSTPROCESS { script: def prefix = task.ext.prefix ?: "${meta.id}" def MDUST_VERSION = '2006.10.17' // WARN: Manually update when changing Bioconda assets - if ( "$prefix" == 'genome' ) error "Input and output names are the same, use \"task.ext.prefix\" to disambiguate!" """ setup_TIR-Learner_postprocess.sh \\ $task.cpus @@ -37,15 +36,15 @@ process TIR_LEARNER_POSTPROCESS { mv \\ genome.TIR.intact.raw.fa \\ - ${prefix}.TIR.intact.raw.fa + ${prefix}.TIR.intact.fa mv \\ genome.TIR.intact.raw.gff3 \\ - ${prefix}.TIR.intact.raw.gff3 + ${prefix}.TIR.intact.gff3 mv \\ genome.TIR.intact.raw.bed \\ - ${prefix}.TIR.intact.raw.bed + ${prefix}.TIR.intact.bed cat <<-END_VERSIONS > versions.yml "${task.process}": @@ -59,11 +58,10 @@ process TIR_LEARNER_POSTPROCESS { stub: def prefix = task.ext.prefix ?: "${meta.id}" def MDUST_VERSION = '2006.10.17' // WARN: Manually update when changing Bioconda assets - if ( "$prefix" == 'genome' ) error "Input and output names are the same, use \"task.ext.prefix\" to disambiguate!" """ - touch ${prefix}.TIR.intact.raw.fa - touch ${prefix}.TIR.intact.raw.gff3 - touch ${prefix}.TIR.intact.raw.bed + touch ${prefix}.TIR.intact.fa + touch ${prefix}.TIR.intact.gff3 + touch ${prefix}.TIR.intact.bed cat <<-END_VERSIONS > versions.yml "${task.process}": diff --git a/tests/nf-test/small/main.nf.test b/tests/nf-test/small/main.nf.test index e293c39..43e207c 100644 --- a/tests/nf-test/small/main.nf.test +++ b/tests/nf-test/small/main.nf.test @@ -19,16 +19,18 @@ nextflow_pipeline { false, [ 'pipeline_info/*.{html,json,txt,yml}', - 'raw/ltr/*.pp.LTR.intact.raw.gff3', - 'raw/ltr/*.pp.LTR.raw.fa', - 'raw/tir/*.pp.TIR.intact.raw.fa', - 'raw/tir/*.pp.TIR.intact.raw.gff3', - 'raw/line/*.pp.LINE.raw.fa', - 'raw/line/*.pp.RM2.fa', - 'raw/helitron/*.Helitron.intact.raw.fa', - 'raw/helitron/*.Helitron.intact.raw.fa.anno.list', - 'raw/helitron/*.Helitron.intact.raw.gff3', - 'stg1/*.stg1.fasta' + 'raw/ltr/*.LTR.intact.gff3', + 'raw/ltr/*.LTR.fa', + 'raw/tir/*.TIR.intact.fa', + 'raw/tir/*.TIR.intact.gff3', + 'raw/line/*.LINE.fa', + 'raw/line/*.RM2.fa', + 'raw/helitron/*.Helitron.intact.fa', + 'raw/helitron/*.Helitron.intact.anno.list', + 'raw/helitron/*.Helitron.intact.gff3', + 'raw/intact/*', + 'stg1/*.fasta', + 'final/*' ], null, ['**'] diff --git a/tests/nf-test/small/main.nf.test.snap b/tests/nf-test/small/main.nf.test.snap index 8bd6b84..dccc812 100644 --- a/tests/nf-test/small/main.nf.test.snap +++ b/tests/nf-test/small/main.nf.test.snap @@ -2,7 +2,7 @@ "small genome": { "content": [ { - "successful tasks": 22, + "successful tasks": 24, "versions": { "ANNOSINE": { "annosine": "2.0.7" @@ -10,6 +10,13 @@ "CAT_CAT": { "pigz": "2.3.4" }, + "COMBINE_INTACT_TES": { + "perl": "v5.32.1" + }, + "FINAL_FILTER": { + "perl": "v5.32.1", + "repeatmasker": "4.1.7-p1" + }, "FORMAT_HELITRONSCANNER_OUT": { "perl": "v5.32.1" }, @@ -87,32 +94,40 @@ } }, "stable paths": [ - "genome.pp.Helitron.intact.raw.bed:md5,a8b170a1ee8d36c7f8e3af5ba1e43cb6", - "genome.pp.LTR.intact.raw.fa.anno.list:md5,51261f020350b448acdf89d8fb625448", - "genome.pp.genome.LTR.intact.raw.fa:md5,ae2336f498897d1af4e8d3be28bb3f25", - "genome.pp.TIR.intact.raw.bed:md5,12406184046b11edc2ddadbc82765966" + "genome.Helitron.intact.bed:md5,a8b170a1ee8d36c7f8e3af5ba1e43cb6", + "genome.LTR.intact.anno.list:md5,51261f020350b448acdf89d8fb625448", + "genome.LTR.intact.fa:md5,ae2336f498897d1af4e8d3be28bb3f25", + "genome.TIR.intact.bed:md5,12406184046b11edc2ddadbc82765966" ], "stable names": [ + "final", + "final/genome.TElib.fa", + "final/genome.intact.fa", + "final/genome.intact.gff3", "pipeline_info", "raw", "raw/helitron", - "raw/helitron/genome.pp.Helitron.intact.raw.bed", - "raw/helitron/genome.pp.Helitron.intact.raw.fa", - "raw/helitron/genome.pp.Helitron.intact.raw.fa.anno.list", - "raw/helitron/genome.pp.Helitron.intact.raw.gff3", + "raw/helitron/genome.Helitron.intact.anno.list", + "raw/helitron/genome.Helitron.intact.bed", + "raw/helitron/genome.Helitron.intact.fa", + "raw/helitron/genome.Helitron.intact.gff3", + "raw/intact", + "raw/intact/genome.intact.fa", + "raw/intact/genome.intact.gff3", "raw/line", - "raw/line/genome.pp.RM2.fa", + "raw/line/genome.RM2.fa", "raw/ltr", - "raw/ltr/genome.pp.LTR.intact.raw.fa.anno.list", - "raw/ltr/genome.pp.LTR.intact.raw.gff3", - "raw/ltr/genome.pp.LTR.raw.fa", - "raw/ltr/genome.pp.genome.LTR.intact.raw.fa", + "raw/ltr/genome.LTR.fa", + "raw/ltr/genome.LTR.intact.anno.list", + "raw/ltr/genome.LTR.intact.fa", + "raw/ltr/genome.LTR.intact.gff3", "raw/tir", - "raw/tir/genome.pp.TIR.intact.raw.bed", - "raw/tir/genome.pp.TIR.intact.raw.fa", - "raw/tir/genome.pp.TIR.intact.raw.gff3", + "raw/tir/genome.TIR.intact.bed", + "raw/tir/genome.TIR.intact.fa", + "raw/tir/genome.TIR.intact.gff3", "stg1", - "stg1/genome.stg1.fasta" + "stg1/genome.fasta", + "stg1/genome.intact.fasta" ] } ], @@ -120,6 +135,6 @@ "nf-test": "0.9.2", "nextflow": "24.04.4" }, - "timestamp": "2024-12-13T15:35:15.109085" + "timestamp": "2024-12-17T14:02:22.882318" } } \ No newline at end of file diff --git a/workflows/edta.nf b/workflows/edta.nf index 70d70de..1f13998 100644 --- a/workflows/edta.nf +++ b/workflows/edta.nf @@ -21,7 +21,9 @@ include { FORMAT_HELITRONSCANNER_OUT } from '../modules/local/for include { FORMAT_HELITRONSCANNER_OUT as FORMAT_HELITRONSCANNER_OUT_EXT } from '../modules/local/format_helitronscanner_out/main' include { HELITRONSCANNER_POSTPROCESS } from '../modules/local/helitronscanner_postprocess/main' +include { COMBINE_INTACT_TES } from '../modules/local/combine_intact_tes/main' include { PROCESS_K } from '../modules/local/process_k/main' +include { FINAL_FILTER } from '../modules/local/final_filter/main' include { softwareVersionsToYAML } from '../modules/local/utils/main' include { idFromFileName } from '../modules/local/utils/main' @@ -251,6 +253,36 @@ workflow EDTA { ch_versions = ch_versions.mix(HELITRONSCANNER_POSTPROCESS.out.versions.first()) + // MODULE: COMBINE_INTACT_TES + ch_combine_intact_tes_inputs = ch_sanitized_fasta + | join ( LTR_RETRIEVER_POSTPROCESS.out.intact_raw_fa ) + | join ( LTR_RETRIEVER_POSTPROCESS.out.intact_raw_gff3 ) + | join ( TIR_LEARNER_POSTPROCESS.out.intact_raw_fa ) + | join ( TIR_LEARNER_POSTPROCESS.out.intact_raw_bed ) + | join ( HELITRONSCANNER_POSTPROCESS.out.raw_fa ) + | join ( HELITRONSCANNER_POSTPROCESS.out.raw_bed ) + | multiMap { meta, genome, ltr_fa, ltr_gff, tir_fa, tir_bed, helitron_fa, helitron_bed -> + genome : [ meta, genome ] + ltr_fa : ltr_fa + ltr_gff : ltr_gff + tir_fa : tir_fa + tir_bed : tir_bed + helitron_fa : helitron_fa + helitron_bed: helitron_bed + } + + COMBINE_INTACT_TES ( + ch_combine_intact_tes_inputs.genome, + ch_combine_intact_tes_inputs.ltr_fa, + ch_combine_intact_tes_inputs.ltr_gff, + ch_combine_intact_tes_inputs.tir_fa, + ch_combine_intact_tes_inputs.tir_bed, + ch_combine_intact_tes_inputs.helitron_fa, + ch_combine_intact_tes_inputs.helitron_bed, + ) + + ch_versions = ch_versions.mix(COMBINE_INTACT_TES.out.versions.first()) + // MODULE: PROCESS_K ch_process_k_inputs = ch_sanitized_fasta | join ( LTR_RETRIEVER_POSTPROCESS.out.raw_fa ) @@ -294,6 +326,26 @@ workflow EDTA { } } + // MODULE: FINAL_FILTER + ch_final_filter_inputs = ch_sanitized_fasta + | join(PROCESS_K.out.stg1_fa) + | join(PROCESS_K.out.intact_fa) + | join(COMBINE_INTACT_TES.out.intact_raw_gff) + | multiMap { meta, fasta, stg1_fa, intact_fa, intact_gff -> + genome : [ meta, fasta ] + stg1_fa : stg1_fa + intact_fa : intact_fa + intact_gff : intact_gff + } + FINAL_FILTER ( + ch_final_filter_inputs.genome, + ch_final_filter_inputs.stg1_fa, + ch_final_filter_inputs.intact_fa, + ch_final_filter_inputs.intact_gff, + ) + + ch_versions = ch_versions.mix(FINAL_FILTER.out.versions.first()) + // Function: Save versions ch_versions = ch_versions | unique From ecdaccf2d1961612da3c3263966dd2b5033146f5 Mon Sep 17 00:00:00 2001 From: Usman Rashid Date: Tue, 17 Dec 2024 22:38:50 +1300 Subject: [PATCH 45/49] Updated the sanitize process --- bin/fffx | Bin 1555504 -> 0 bytes conf/modules.config | 80 ++---- conf/test.config | 6 +- modules.json | 12 +- .../custom/restoregffids/environment.yml | 6 + modules/gallvp/custom/restoregffids/main.nf | 35 +++ modules/gallvp/custom/restoregffids/meta.yml | 60 +++++ .../templates/restore_gff_ids.py | 47 ++++ .../custom/restoregffids/tests/main.nf.test | 63 +++++ .../restoregffids/tests/main.nf.test.snap | 61 +++++ .../custom/shortenfastaids/environment.yml | 7 + modules/gallvp/custom/shortenfastaids/main.nf | 38 +++ .../gallvp/custom/shortenfastaids/meta.yml | 66 +++++ .../templates/shorten_fasta_ids.py | 168 +++++++++++++ .../custom/shortenfastaids/tests/main.nf.test | 123 ++++++++++ .../shortenfastaids/tests/main.nf.test.snap | 227 ++++++++++++++++++ modules/gallvp/tirlearner/main.nf | 5 +- .../gallvp/tirlearner/tests/main.nf.test.snap | 20 +- modules/local/sanitize/main.nf | 38 --- tests/nf-test/small/main.nf.test | 26 +- tests/nf-test/small/main.nf.test.snap | 47 +--- tests/nf-test/tiny/main.nf.test | 6 +- tests/nf-test/tiny/main.nf.test.snap | 15 +- workflows/edta.nf | 58 ++++- 24 files changed, 1039 insertions(+), 175 deletions(-) delete mode 100755 bin/fffx create mode 100644 modules/gallvp/custom/restoregffids/environment.yml create mode 100644 modules/gallvp/custom/restoregffids/main.nf create mode 100644 modules/gallvp/custom/restoregffids/meta.yml create mode 100755 modules/gallvp/custom/restoregffids/templates/restore_gff_ids.py create mode 100644 modules/gallvp/custom/restoregffids/tests/main.nf.test create mode 100644 modules/gallvp/custom/restoregffids/tests/main.nf.test.snap create mode 100644 modules/gallvp/custom/shortenfastaids/environment.yml create mode 100644 modules/gallvp/custom/shortenfastaids/main.nf create mode 100644 modules/gallvp/custom/shortenfastaids/meta.yml create mode 100755 modules/gallvp/custom/shortenfastaids/templates/shorten_fasta_ids.py create mode 100644 modules/gallvp/custom/shortenfastaids/tests/main.nf.test create mode 100644 modules/gallvp/custom/shortenfastaids/tests/main.nf.test.snap delete mode 100644 modules/local/sanitize/main.nf diff --git a/bin/fffx b/bin/fffx deleted file mode 100755 index f1bfeea8b61acf4f1de4b08c16665dd91fa73e9d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1555504 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 =v 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

${group}

\n" + summary_section += "
\n" + group_params + .keySet() + .sort() + .each { param -> + summary_section += "
${param}
${group_params.get(param) ?: 'N/A'}
\n" + } + summary_section += "
\n" + } + } + + def yaml_file_text = "id: '${workflow.manifest.name.replace('/', '-')}-summary'\n" as String + yaml_file_text += "description: ' - this information is collected when the pipeline is started.'\n" + yaml_file_text += "section_name: '${workflow.manifest.name} Workflow Summary'\n" + yaml_file_text += "section_href: 'https://github.com/${workflow.manifest.name}'\n" + yaml_file_text += "plot_type: 'html'\n" + yaml_file_text += "data: |\n" + yaml_file_text += "${summary_section}" + + return yaml_file_text +} + +// +// ANSII colours used for terminal logging +// +def logColours(monochrome_logs=true) { + def colorcodes = [:] as Map + + // Reset / Meta + colorcodes['reset'] = monochrome_logs ? '' : "\033[0m" + colorcodes['bold'] = monochrome_logs ? '' : "\033[1m" + colorcodes['dim'] = monochrome_logs ? '' : "\033[2m" + colorcodes['underlined'] = monochrome_logs ? '' : "\033[4m" + colorcodes['blink'] = monochrome_logs ? '' : "\033[5m" + colorcodes['reverse'] = monochrome_logs ? '' : "\033[7m" + colorcodes['hidden'] = monochrome_logs ? '' : "\033[8m" + + // Regular Colors + colorcodes['black'] = monochrome_logs ? '' : "\033[0;30m" + colorcodes['red'] = monochrome_logs ? '' : "\033[0;31m" + colorcodes['green'] = monochrome_logs ? '' : "\033[0;32m" + colorcodes['yellow'] = monochrome_logs ? '' : "\033[0;33m" + colorcodes['blue'] = monochrome_logs ? '' : "\033[0;34m" + colorcodes['purple'] = monochrome_logs ? '' : "\033[0;35m" + colorcodes['cyan'] = monochrome_logs ? '' : "\033[0;36m" + colorcodes['white'] = monochrome_logs ? '' : "\033[0;37m" + + // Bold + colorcodes['bblack'] = monochrome_logs ? '' : "\033[1;30m" + colorcodes['bred'] = monochrome_logs ? '' : "\033[1;31m" + colorcodes['bgreen'] = monochrome_logs ? '' : "\033[1;32m" + colorcodes['byellow'] = monochrome_logs ? '' : "\033[1;33m" + colorcodes['bblue'] = monochrome_logs ? '' : "\033[1;34m" + colorcodes['bpurple'] = monochrome_logs ? '' : "\033[1;35m" + colorcodes['bcyan'] = monochrome_logs ? '' : "\033[1;36m" + colorcodes['bwhite'] = monochrome_logs ? '' : "\033[1;37m" + + // Underline + colorcodes['ublack'] = monochrome_logs ? '' : "\033[4;30m" + colorcodes['ured'] = monochrome_logs ? '' : "\033[4;31m" + colorcodes['ugreen'] = monochrome_logs ? '' : "\033[4;32m" + colorcodes['uyellow'] = monochrome_logs ? '' : "\033[4;33m" + colorcodes['ublue'] = monochrome_logs ? '' : "\033[4;34m" + colorcodes['upurple'] = monochrome_logs ? '' : "\033[4;35m" + colorcodes['ucyan'] = monochrome_logs ? '' : "\033[4;36m" + colorcodes['uwhite'] = monochrome_logs ? '' : "\033[4;37m" + + // High Intensity + colorcodes['iblack'] = monochrome_logs ? '' : "\033[0;90m" + colorcodes['ired'] = monochrome_logs ? '' : "\033[0;91m" + colorcodes['igreen'] = monochrome_logs ? '' : "\033[0;92m" + colorcodes['iyellow'] = monochrome_logs ? '' : "\033[0;93m" + colorcodes['iblue'] = monochrome_logs ? '' : "\033[0;94m" + colorcodes['ipurple'] = monochrome_logs ? '' : "\033[0;95m" + colorcodes['icyan'] = monochrome_logs ? '' : "\033[0;96m" + colorcodes['iwhite'] = monochrome_logs ? '' : "\033[0;97m" + + // Bold High Intensity + colorcodes['biblack'] = monochrome_logs ? '' : "\033[1;90m" + colorcodes['bired'] = monochrome_logs ? '' : "\033[1;91m" + colorcodes['bigreen'] = monochrome_logs ? '' : "\033[1;92m" + colorcodes['biyellow'] = monochrome_logs ? '' : "\033[1;93m" + colorcodes['biblue'] = monochrome_logs ? '' : "\033[1;94m" + colorcodes['bipurple'] = monochrome_logs ? '' : "\033[1;95m" + colorcodes['bicyan'] = monochrome_logs ? '' : "\033[1;96m" + colorcodes['biwhite'] = monochrome_logs ? '' : "\033[1;97m" + + return colorcodes +} + +// Return a single report from an object that may be a Path or List +// +def getSingleReport(multiqc_reports) { + if (multiqc_reports instanceof Path) { + return multiqc_reports + } else if (multiqc_reports instanceof List) { + if (multiqc_reports.size() == 0) { + log.warn("[${workflow.manifest.name}] No reports found from process 'MULTIQC'") + return null + } else if (multiqc_reports.size() == 1) { + return multiqc_reports.first() + } else { + log.warn("[${workflow.manifest.name}] Found multiple reports from process 'MULTIQC', will use only one") + return multiqc_reports.first() + } + } else { + return null + } +} + +// +// Construct and send completion email +// +def completionEmail(summary_params, email, email_on_fail, plaintext_email, outdir, monochrome_logs=true, multiqc_report=null) { + + // Set up the e-mail variables + def subject = "[${workflow.manifest.name}] Successful: ${workflow.runName}" + if (!workflow.success) { + subject = "[${workflow.manifest.name}] FAILED: ${workflow.runName}" + } + + def summary = [:] + summary_params + .keySet() + .sort() + .each { group -> + summary << summary_params[group] + } + + def misc_fields = [:] + misc_fields['Date Started'] = workflow.start + misc_fields['Date Completed'] = workflow.complete + misc_fields['Pipeline script file path'] = workflow.scriptFile + misc_fields['Pipeline script hash ID'] = workflow.scriptId + if (workflow.repository) { + misc_fields['Pipeline repository Git URL'] = workflow.repository + } + if (workflow.commitId) { + misc_fields['Pipeline repository Git Commit'] = workflow.commitId + } + if (workflow.revision) { + misc_fields['Pipeline Git branch/tag'] = workflow.revision + } + misc_fields['Nextflow Version'] = workflow.nextflow.version + misc_fields['Nextflow Build'] = workflow.nextflow.build + misc_fields['Nextflow Compile Timestamp'] = workflow.nextflow.timestamp + + def email_fields = [:] + email_fields['version'] = getWorkflowVersion() + email_fields['runName'] = workflow.runName + email_fields['success'] = workflow.success + email_fields['dateComplete'] = workflow.complete + email_fields['duration'] = workflow.duration + email_fields['exitStatus'] = workflow.exitStatus + email_fields['errorMessage'] = (workflow.errorMessage ?: 'None') + email_fields['errorReport'] = (workflow.errorReport ?: 'None') + email_fields['commandLine'] = workflow.commandLine + email_fields['projectDir'] = workflow.projectDir + email_fields['summary'] = summary << misc_fields + + // On success try attach the multiqc report + def mqc_report = getSingleReport(multiqc_report) + + // Check if we are only sending emails on failure + def email_address = email + if (!email && email_on_fail && !workflow.success) { + email_address = email_on_fail + } + + // Render the TXT template + def engine = new groovy.text.GStringTemplateEngine() + def tf = new File("${workflow.projectDir}/assets/email_template.txt") + def txt_template = engine.createTemplate(tf).make(email_fields) + def email_txt = txt_template.toString() + + // Render the HTML template + def hf = new File("${workflow.projectDir}/assets/email_template.html") + def html_template = engine.createTemplate(hf).make(email_fields) + def email_html = html_template.toString() + + // Render the sendmail template + def max_multiqc_email_size = (params.containsKey('max_multiqc_email_size') ? params.max_multiqc_email_size : 0) as MemoryUnit + def smail_fields = [email: email_address, subject: subject, email_txt: email_txt, email_html: email_html, projectDir: "${workflow.projectDir}", mqcFile: mqc_report, mqcMaxSize: max_multiqc_email_size.toBytes()] + def sf = new File("${workflow.projectDir}/assets/sendmail_template.txt") + def sendmail_template = engine.createTemplate(sf).make(smail_fields) + def sendmail_html = sendmail_template.toString() + + // Send the HTML e-mail + def colors = logColours(monochrome_logs) as Map + if (email_address) { + try { + if (plaintext_email) { + new org.codehaus.groovy.GroovyException('Send plaintext e-mail, not HTML') + } + // Try to send HTML e-mail using sendmail + def sendmail_tf = new File(workflow.launchDir.toString(), ".sendmail_tmp.html") + sendmail_tf.withWriter { w -> w << sendmail_html } + ['sendmail', '-t'].execute() << sendmail_html + log.info("-${colors.purple}[${workflow.manifest.name}]${colors.green} Sent summary e-mail to ${email_address} (sendmail)-") + } + catch (Exception msg) { + log.debug(msg.toString()) + log.debug("Trying with mail instead of sendmail") + // Catch failures and try with plaintext + def mail_cmd = ['mail', '-s', subject, '--content-type=text/html', email_address] + mail_cmd.execute() << email_html + log.info("-${colors.purple}[${workflow.manifest.name}]${colors.green} Sent summary e-mail to ${email_address} (mail)-") + } + } + + // Write summary e-mail HTML to a file + def output_hf = new File(workflow.launchDir.toString(), ".pipeline_report.html") + output_hf.withWriter { w -> w << email_html } + nextflow.extension.FilesEx.copyTo(output_hf.toPath(), "${outdir}/pipeline_info/pipeline_report.html") + output_hf.delete() + + // Write summary e-mail TXT to a file + def output_tf = new File(workflow.launchDir.toString(), ".pipeline_report.txt") + output_tf.withWriter { w -> w << email_txt } + nextflow.extension.FilesEx.copyTo(output_tf.toPath(), "${outdir}/pipeline_info/pipeline_report.txt") + output_tf.delete() +} + +// +// Print pipeline summary on completion +// +def completionSummary(monochrome_logs=true) { + def colors = logColours(monochrome_logs) as Map + if (workflow.success) { + if (workflow.stats.ignoredCount == 0) { + log.info("-${colors.purple}[${workflow.manifest.name}]${colors.green} Pipeline completed successfully${colors.reset}-") + } + else { + log.info("-${colors.purple}[${workflow.manifest.name}]${colors.yellow} Pipeline completed successfully, but with errored process(es) ${colors.reset}-") + } + } + else { + log.info("-${colors.purple}[${workflow.manifest.name}]${colors.red} Pipeline completed with errors${colors.reset}-") + } +} + +// +// Construct and send a notification to a web server as JSON e.g. Microsoft Teams and Slack +// +def imNotification(summary_params, hook_url) { + def summary = [:] + summary_params + .keySet() + .sort() + .each { group -> + summary << summary_params[group] + } + + def misc_fields = [:] + misc_fields['start'] = workflow.start + misc_fields['complete'] = workflow.complete + misc_fields['scriptfile'] = workflow.scriptFile + misc_fields['scriptid'] = workflow.scriptId + if (workflow.repository) { + misc_fields['repository'] = workflow.repository + } + if (workflow.commitId) { + misc_fields['commitid'] = workflow.commitId + } + if (workflow.revision) { + misc_fields['revision'] = workflow.revision + } + misc_fields['nxf_version'] = workflow.nextflow.version + misc_fields['nxf_build'] = workflow.nextflow.build + misc_fields['nxf_timestamp'] = workflow.nextflow.timestamp + + def msg_fields = [:] + msg_fields['version'] = getWorkflowVersion() + msg_fields['runName'] = workflow.runName + msg_fields['success'] = workflow.success + msg_fields['dateComplete'] = workflow.complete + msg_fields['duration'] = workflow.duration + msg_fields['exitStatus'] = workflow.exitStatus + msg_fields['errorMessage'] = (workflow.errorMessage ?: 'None') + msg_fields['errorReport'] = (workflow.errorReport ?: 'None') + msg_fields['commandLine'] = workflow.commandLine.replaceFirst(/ +--hook_url +[^ ]+/, "") + msg_fields['projectDir'] = workflow.projectDir + msg_fields['summary'] = summary << misc_fields + + // Render the JSON template + def engine = new groovy.text.GStringTemplateEngine() + // Different JSON depending on the service provider + // Defaults to "Adaptive Cards" (https://adaptivecards.io), except Slack which has its own format + def json_path = hook_url.contains("hooks.slack.com") ? "slackreport.json" : "adaptivecard.json" + def hf = new File("${workflow.projectDir}/assets/${json_path}") + def json_template = engine.createTemplate(hf).make(msg_fields) + def json_message = json_template.toString() + + // POST + def post = new URL(hook_url).openConnection() + post.setRequestMethod("POST") + post.setDoOutput(true) + post.setRequestProperty("Content-Type", "application/json") + post.getOutputStream().write(json_message.getBytes("UTF-8")) + def postRC = post.getResponseCode() + if (!postRC.equals(200)) { + log.warn(post.getErrorStream().getText()) + } +} diff --git a/subworkflows/nf-core/utils_nfcore_pipeline/meta.yml b/subworkflows/nf-core/utils_nfcore_pipeline/meta.yml new file mode 100644 index 0000000..d08d243 --- /dev/null +++ b/subworkflows/nf-core/utils_nfcore_pipeline/meta.yml @@ -0,0 +1,24 @@ +# yaml-language-server: $schema=https://raw.githubusercontent.com/nf-core/modules/master/subworkflows/yaml-schema.json +name: "UTILS_NFCORE_PIPELINE" +description: Subworkflow with utility functions specific to the nf-core pipeline template +keywords: + - utility + - pipeline + - initialise + - version +components: [] +input: + - nextflow_cli_args: + type: list + description: | + Nextflow CLI positional arguments +output: + - success: + type: boolean + description: | + Dummy output to indicate success +authors: + - "@adamrtalbot" +maintainers: + - "@adamrtalbot" + - "@maxulysse" diff --git a/subworkflows/nf-core/utils_nfcore_pipeline/tests/main.function.nf.test b/subworkflows/nf-core/utils_nfcore_pipeline/tests/main.function.nf.test new file mode 100644 index 0000000..f117040 --- /dev/null +++ b/subworkflows/nf-core/utils_nfcore_pipeline/tests/main.function.nf.test @@ -0,0 +1,126 @@ + +nextflow_function { + + name "Test Functions" + script "../main.nf" + config "subworkflows/nf-core/utils_nfcore_pipeline/tests/nextflow.config" + tag "subworkflows" + tag "subworkflows_nfcore" + tag "utils_nfcore_pipeline" + tag "subworkflows/utils_nfcore_pipeline" + + test("Test Function checkConfigProvided") { + + function "checkConfigProvided" + + then { + assertAll( + { assert function.success }, + { assert snapshot(function.result).match() } + ) + } + } + + test("Test Function checkProfileProvided") { + + function "checkProfileProvided" + + when { + function { + """ + input[0] = [] + """ + } + } + + then { + assertAll( + { assert function.success }, + { assert snapshot(function.result).match() } + ) + } + } + + test("Test Function without logColours") { + + function "logColours" + + when { + function { + """ + input[0] = true + """ + } + } + + then { + assertAll( + { assert function.success }, + { assert snapshot(function.result).match() } + ) + } + } + + test("Test Function with logColours") { + function "logColours" + + when { + function { + """ + input[0] = false + """ + } + } + + then { + assertAll( + { assert function.success }, + { assert snapshot(function.result).match() } + ) + } + } + + test("Test Function getSingleReport with a single file") { + function "getSingleReport" + + when { + function { + """ + input[0] = file(params.modules_testdata_base_path + '/generic/tsv/test.tsv', checkIfExists: true) + """ + } + } + + then { + assertAll( + { assert function.success }, + { assert function.result.contains("test.tsv") } + ) + } + } + + test("Test Function getSingleReport with multiple files") { + function "getSingleReport" + + when { + function { + """ + input[0] = [ + file(params.modules_testdata_base_path + '/generic/tsv/test.tsv', checkIfExists: true), + file(params.modules_testdata_base_path + '/generic/tsv/network.tsv', checkIfExists: true), + file(params.modules_testdata_base_path + '/generic/tsv/expression.tsv', checkIfExists: true) + ] + """ + } + } + + then { + assertAll( + { assert function.success }, + { assert function.result.contains("test.tsv") }, + { assert !function.result.contains("network.tsv") }, + { assert !function.result.contains("expression.tsv") } + ) + } + } +} diff --git a/subworkflows/nf-core/utils_nfcore_pipeline/tests/main.function.nf.test.snap b/subworkflows/nf-core/utils_nfcore_pipeline/tests/main.function.nf.test.snap new file mode 100644 index 0000000..02c6701 --- /dev/null +++ b/subworkflows/nf-core/utils_nfcore_pipeline/tests/main.function.nf.test.snap @@ -0,0 +1,136 @@ +{ + "Test Function checkProfileProvided": { + "content": null, + "meta": { + "nf-test": "0.8.4", + "nextflow": "23.10.1" + }, + "timestamp": "2024-02-28T12:03:03.360873" + }, + "Test Function checkConfigProvided": { + "content": [ + true + ], + "meta": { + "nf-test": "0.8.4", + "nextflow": "23.10.1" + }, + "timestamp": "2024-02-28T12:02:59.729647" + }, + "Test Function without logColours": { + "content": [ + { + "reset": "", + "bold": "", + "dim": "", + "underlined": "", + "blink": "", + "reverse": "", + "hidden": "", + "black": "", + "red": "", + "green": "", + "yellow": "", + "blue": "", + "purple": "", + "cyan": "", + "white": "", + "bblack": "", + "bred": "", + "bgreen": "", + "byellow": "", + "bblue": "", + "bpurple": "", + "bcyan": "", + "bwhite": "", + "ublack": "", + "ured": "", + "ugreen": "", + "uyellow": "", + "ublue": "", + "upurple": "", + "ucyan": "", + "uwhite": "", + "iblack": "", + "ired": "", + "igreen": "", + "iyellow": "", + "iblue": "", + "ipurple": "", + "icyan": "", + "iwhite": "", + "biblack": "", + "bired": "", + "bigreen": "", + "biyellow": "", + "biblue": "", + "bipurple": "", + "bicyan": "", + "biwhite": "" + } + ], + "meta": { + "nf-test": "0.8.4", + "nextflow": "23.10.1" + }, + "timestamp": "2024-02-28T12:03:17.969323" + }, + "Test Function with logColours": { + "content": [ + { + "reset": "\u001b[0m", + "bold": "\u001b[1m", + "dim": "\u001b[2m", + "underlined": "\u001b[4m", + "blink": "\u001b[5m", + "reverse": "\u001b[7m", + "hidden": "\u001b[8m", + "black": "\u001b[0;30m", + "red": "\u001b[0;31m", + "green": "\u001b[0;32m", + "yellow": "\u001b[0;33m", + "blue": "\u001b[0;34m", + "purple": "\u001b[0;35m", + "cyan": "\u001b[0;36m", + "white": "\u001b[0;37m", + "bblack": "\u001b[1;30m", + "bred": "\u001b[1;31m", + "bgreen": "\u001b[1;32m", + "byellow": "\u001b[1;33m", + "bblue": "\u001b[1;34m", + "bpurple": "\u001b[1;35m", + "bcyan": "\u001b[1;36m", + "bwhite": "\u001b[1;37m", + "ublack": "\u001b[4;30m", + "ured": "\u001b[4;31m", + "ugreen": "\u001b[4;32m", + "uyellow": "\u001b[4;33m", + "ublue": "\u001b[4;34m", + "upurple": "\u001b[4;35m", + "ucyan": "\u001b[4;36m", + "uwhite": "\u001b[4;37m", + "iblack": "\u001b[0;90m", + "ired": "\u001b[0;91m", + "igreen": "\u001b[0;92m", + "iyellow": "\u001b[0;93m", + "iblue": "\u001b[0;94m", + "ipurple": "\u001b[0;95m", + "icyan": "\u001b[0;96m", + "iwhite": "\u001b[0;97m", + "biblack": "\u001b[1;90m", + "bired": "\u001b[1;91m", + "bigreen": "\u001b[1;92m", + "biyellow": "\u001b[1;93m", + "biblue": "\u001b[1;94m", + "bipurple": "\u001b[1;95m", + "bicyan": "\u001b[1;96m", + "biwhite": "\u001b[1;97m" + } + ], + "meta": { + "nf-test": "0.8.4", + "nextflow": "23.10.1" + }, + "timestamp": "2024-02-28T12:03:21.714424" + } +} \ No newline at end of file diff --git a/subworkflows/nf-core/utils_nfcore_pipeline/tests/main.workflow.nf.test b/subworkflows/nf-core/utils_nfcore_pipeline/tests/main.workflow.nf.test new file mode 100644 index 0000000..8940d32 --- /dev/null +++ b/subworkflows/nf-core/utils_nfcore_pipeline/tests/main.workflow.nf.test @@ -0,0 +1,29 @@ +nextflow_workflow { + + name "Test Workflow UTILS_NFCORE_PIPELINE" + script "../main.nf" + config "subworkflows/nf-core/utils_nfcore_pipeline/tests/nextflow.config" + workflow "UTILS_NFCORE_PIPELINE" + tag "subworkflows" + tag "subworkflows_nfcore" + tag "utils_nfcore_pipeline" + tag "subworkflows/utils_nfcore_pipeline" + + test("Should run without failures") { + + when { + workflow { + """ + input[0] = [] + """ + } + } + + then { + assertAll( + { assert workflow.success }, + { assert snapshot(workflow.out).match() } + ) + } + } +} diff --git a/subworkflows/nf-core/utils_nfcore_pipeline/tests/main.workflow.nf.test.snap b/subworkflows/nf-core/utils_nfcore_pipeline/tests/main.workflow.nf.test.snap new file mode 100644 index 0000000..859d103 --- /dev/null +++ b/subworkflows/nf-core/utils_nfcore_pipeline/tests/main.workflow.nf.test.snap @@ -0,0 +1,19 @@ +{ + "Should run without failures": { + "content": [ + { + "0": [ + true + ], + "valid_config": [ + true + ] + } + ], + "meta": { + "nf-test": "0.8.4", + "nextflow": "23.10.1" + }, + "timestamp": "2024-02-28T12:03:25.726491" + } +} \ No newline at end of file diff --git a/subworkflows/nf-core/utils_nfcore_pipeline/tests/nextflow.config b/subworkflows/nf-core/utils_nfcore_pipeline/tests/nextflow.config new file mode 100644 index 0000000..d0a926b --- /dev/null +++ b/subworkflows/nf-core/utils_nfcore_pipeline/tests/nextflow.config @@ -0,0 +1,9 @@ +manifest { + name = 'nextflow_workflow' + author = """nf-core""" + homePage = 'https://127.0.0.1' + description = """Dummy pipeline""" + nextflowVersion = '!>=23.04.0' + version = '9.9.9' + doi = 'https://doi.org/10.5281/zenodo.5070524' +} diff --git a/subworkflows/nf-core/utils_nfcore_pipeline/tests/tags.yml b/subworkflows/nf-core/utils_nfcore_pipeline/tests/tags.yml new file mode 100644 index 0000000..ac8523c --- /dev/null +++ b/subworkflows/nf-core/utils_nfcore_pipeline/tests/tags.yml @@ -0,0 +1,2 @@ +subworkflows/utils_nfcore_pipeline: + - subworkflows/nf-core/utils_nfcore_pipeline/** diff --git a/subworkflows/nf-core/utils_nfschema_plugin/main.nf b/subworkflows/nf-core/utils_nfschema_plugin/main.nf new file mode 100644 index 0000000..4994303 --- /dev/null +++ b/subworkflows/nf-core/utils_nfschema_plugin/main.nf @@ -0,0 +1,46 @@ +// +// Subworkflow that uses the nf-schema plugin to validate parameters and render the parameter summary +// + +include { paramsSummaryLog } from 'plugin/nf-schema' +include { validateParameters } from 'plugin/nf-schema' + +workflow UTILS_NFSCHEMA_PLUGIN { + + take: + input_workflow // workflow: the workflow object used by nf-schema to get metadata from the workflow + validate_params // boolean: validate the parameters + parameters_schema // string: path to the parameters JSON schema. + // this has to be the same as the schema given to `validation.parametersSchema` + // when this input is empty it will automatically use the configured schema or + // "${projectDir}/nextflow_schema.json" as default. This input should not be empty + // for meta pipelines + + main: + + // + // Print parameter summary to stdout. This will display the parameters + // that differ from the default given in the JSON schema + // + if(parameters_schema) { + log.info paramsSummaryLog(input_workflow, parameters_schema:parameters_schema) + } else { + log.info paramsSummaryLog(input_workflow) + } + + // + // Validate the parameters using nextflow_schema.json or the schema + // given via the validation.parametersSchema configuration option + // + if(validate_params) { + if(parameters_schema) { + validateParameters(parameters_schema:parameters_schema) + } else { + validateParameters() + } + } + + emit: + dummy_emit = true +} + diff --git a/subworkflows/nf-core/utils_nfschema_plugin/meta.yml b/subworkflows/nf-core/utils_nfschema_plugin/meta.yml new file mode 100644 index 0000000..f7d9f02 --- /dev/null +++ b/subworkflows/nf-core/utils_nfschema_plugin/meta.yml @@ -0,0 +1,35 @@ +# yaml-language-server: $schema=https://raw.githubusercontent.com/nf-core/modules/master/subworkflows/yaml-schema.json +name: "utils_nfschema_plugin" +description: Run nf-schema to validate parameters and create a summary of changed parameters +keywords: + - validation + - JSON schema + - plugin + - parameters + - summary +components: [] +input: + - input_workflow: + type: object + description: | + The workflow object of the used pipeline. + This object contains meta data used to create the params summary log + - validate_params: + type: boolean + description: Validate the parameters and error if invalid. + - parameters_schema: + type: string + description: | + Path to the parameters JSON schema. + This has to be the same as the schema given to the `validation.parametersSchema` config + option. When this input is empty it will automatically use the configured schema or + "${projectDir}/nextflow_schema.json" as default. The schema should not be given in this way + for meta pipelines. +output: + - dummy_emit: + type: boolean + description: Dummy emit to make nf-core subworkflows lint happy +authors: + - "@nvnieuwk" +maintainers: + - "@nvnieuwk" diff --git a/subworkflows/nf-core/utils_nfschema_plugin/tests/main.nf.test b/subworkflows/nf-core/utils_nfschema_plugin/tests/main.nf.test new file mode 100644 index 0000000..8fb3016 --- /dev/null +++ b/subworkflows/nf-core/utils_nfschema_plugin/tests/main.nf.test @@ -0,0 +1,117 @@ +nextflow_workflow { + + name "Test Subworkflow UTILS_NFSCHEMA_PLUGIN" + script "../main.nf" + workflow "UTILS_NFSCHEMA_PLUGIN" + + tag "subworkflows" + tag "subworkflows_nfcore" + tag "subworkflows/utils_nfschema_plugin" + tag "plugin/nf-schema" + + config "./nextflow.config" + + test("Should run nothing") { + + when { + + params { + test_data = '' + } + + workflow { + """ + validate_params = false + input[0] = workflow + input[1] = validate_params + input[2] = "" + """ + } + } + + then { + assertAll( + { assert workflow.success } + ) + } + } + + test("Should validate params") { + + when { + + params { + test_data = '' + outdir = null + } + + workflow { + """ + validate_params = true + input[0] = workflow + input[1] = validate_params + input[2] = "" + """ + } + } + + then { + assertAll( + { assert workflow.failed }, + { assert workflow.stdout.any { it.contains('ERROR ~ Validation of pipeline parameters failed!') } } + ) + } + } + + test("Should run nothing - custom schema") { + + when { + + params { + test_data = '' + } + + workflow { + """ + validate_params = false + input[0] = workflow + input[1] = validate_params + input[2] = "${projectDir}/subworkflows/nf-core/utils_nfschema_plugin/tests/nextflow_schema.json" + """ + } + } + + then { + assertAll( + { assert workflow.success } + ) + } + } + + test("Should validate params - custom schema") { + + when { + + params { + test_data = '' + outdir = null + } + + workflow { + """ + validate_params = true + input[0] = workflow + input[1] = validate_params + input[2] = "${projectDir}/subworkflows/nf-core/utils_nfschema_plugin/tests/nextflow_schema.json" + """ + } + } + + then { + assertAll( + { assert workflow.failed }, + { assert workflow.stdout.any { it.contains('ERROR ~ Validation of pipeline parameters failed!') } } + ) + } + } +} diff --git a/subworkflows/nf-core/utils_nfschema_plugin/tests/nextflow.config b/subworkflows/nf-core/utils_nfschema_plugin/tests/nextflow.config new file mode 100644 index 0000000..0907ac5 --- /dev/null +++ b/subworkflows/nf-core/utils_nfschema_plugin/tests/nextflow.config @@ -0,0 +1,8 @@ +plugins { + id "nf-schema@2.1.0" +} + +validation { + parametersSchema = "${projectDir}/subworkflows/nf-core/utils_nfschema_plugin/tests/nextflow_schema.json" + monochromeLogs = true +} \ No newline at end of file diff --git a/subworkflows/nf-core/utils_nfschema_plugin/tests/nextflow_schema.json b/subworkflows/nf-core/utils_nfschema_plugin/tests/nextflow_schema.json new file mode 100644 index 0000000..331e0d2 --- /dev/null +++ b/subworkflows/nf-core/utils_nfschema_plugin/tests/nextflow_schema.json @@ -0,0 +1,96 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://raw.githubusercontent.com/./master/nextflow_schema.json", + "title": ". pipeline parameters", + "description": "", + "type": "object", + "$defs": { + "input_output_options": { + "title": "Input/output options", + "type": "object", + "fa_icon": "fas fa-terminal", + "description": "Define where the pipeline should find input data and save output data.", + "required": ["outdir"], + "properties": { + "validate_params": { + "type": "boolean", + "description": "Validate parameters?", + "default": true, + "hidden": true + }, + "outdir": { + "type": "string", + "format": "directory-path", + "description": "The output directory where the results will be saved. You have to use absolute paths to storage on Cloud infrastructure.", + "fa_icon": "fas fa-folder-open" + }, + "test_data_base": { + "type": "string", + "default": "https://raw.githubusercontent.com/nf-core/test-datasets/modules", + "description": "Base for test data directory", + "hidden": true + }, + "test_data": { + "type": "string", + "description": "Fake test data param", + "hidden": true + } + } + }, + "generic_options": { + "title": "Generic options", + "type": "object", + "fa_icon": "fas fa-file-import", + "description": "Less common options for the pipeline, typically set in a config file.", + "help_text": "These options are common to all nf-core pipelines and allow you to customise some of the core preferences for how the pipeline runs.\n\nTypically these options would be set in a Nextflow config file loaded for all pipeline runs, such as `~/.nextflow/config`.", + "properties": { + "help": { + "type": "boolean", + "description": "Display help text.", + "fa_icon": "fas fa-question-circle", + "hidden": true + }, + "version": { + "type": "boolean", + "description": "Display version and exit.", + "fa_icon": "fas fa-question-circle", + "hidden": true + }, + "logo": { + "type": "boolean", + "default": true, + "description": "Display nf-core logo in console output.", + "fa_icon": "fas fa-image", + "hidden": true + }, + "singularity_pull_docker_container": { + "type": "boolean", + "description": "Pull Singularity container from Docker?", + "hidden": true + }, + "publish_dir_mode": { + "type": "string", + "default": "copy", + "description": "Method used to save pipeline results to output directory.", + "help_text": "The Nextflow `publishDir` option specifies which intermediate files should be saved to the output directory. This option tells the pipeline what method should be used to move these files. See [Nextflow docs](https://www.nextflow.io/docs/latest/process.html#publishdir) for details.", + "fa_icon": "fas fa-copy", + "enum": ["symlink", "rellink", "link", "copy", "copyNoFollow", "move"], + "hidden": true + }, + "monochrome_logs": { + "type": "boolean", + "description": "Use monochrome_logs", + "hidden": true + } + } + } + }, + "allOf": [ + { + "$ref": "#/$defs/input_output_options" + }, + { + "$ref": "#/$defs/generic_options" + } + ] +} diff --git a/workflows/edta.nf b/workflows/edta.nf index 1d9bb26..97b91fd 100644 --- a/workflows/edta.nf +++ b/workflows/edta.nf @@ -27,24 +27,20 @@ include { COMBINE_INTACT_TES } from '../modules/local/com include { PROCESS_K } from '../modules/local/process_k/main' include { FINAL_FILTER } from '../modules/local/final_filter/main' -include { softwareVersionsToYAML } from '../modules/local/utils/main' -include { idFromFileName } from '../modules/local/utils/main' +include { softwareVersionsToYAML } from '../subworkflows/nf-core/utils_nfcore_pipeline/main' workflow EDTA { + take: + ch_genome_gz // Channel: < val(meta), path(fasta|fasta.gz) > + main: // Versions channel ch_versions = Channel.empty() - ch_genome_branch = Channel.fromPath(params.genome) - | map { genome -> - def meta = [:] - meta.id = idFromFileName ( genome.baseName ) - - [ meta, genome ] - } + ch_genome_branch = ch_genome_gz | branch { _meta, archive -> gz: "$archive".endsWith('.gz') rest: ! "$archive".endsWith('.gz') From ac93561de5adab542b0cdc9ea725bc7303a33cfb Mon Sep 17 00:00:00 2001 From: Usman Rashid Date: Wed, 18 Dec 2024 11:21:04 +1300 Subject: [PATCH 47/49] Added pre-commit hook for version check --- .github/version_checks.sh | 11 +++++++++++ .pre-commit-config.yaml | 8 ++++++++ nextflow.config | 2 +- 3 files changed, 20 insertions(+), 1 deletion(-) create mode 100755 .github/version_checks.sh diff --git a/.github/version_checks.sh b/.github/version_checks.sh new file mode 100755 index 0000000..a49deef --- /dev/null +++ b/.github/version_checks.sh @@ -0,0 +1,11 @@ +#!/usr/bin/env bash + +set -euo pipefail + +config_version=$(sed -n "/^\s*version\s*=\s*'/s/version//p" nextflow.config | tr -d "=[:space:]'") +perl_version=$(sed -n 's|^my $version = "\(.*\)";|\1|p' EDTA.pl | tr -d '[:space:]') + +if [[ "v$config_version" != $perl_version ]]; then + echo "config_version (v$config_version) != perl_version ($perl_version)" + exit 1 +fi diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 650f6a9..c559076 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -9,3 +9,11 @@ repos: always_run: true fail_fast: true pass_filenames: false + - id: version_checks + name: Version checks + language: system + entry: > + .github/version_checks.sh + always_run: true + fail_fast: true + pass_filenames: false diff --git a/nextflow.config b/nextflow.config index c9c7aa1..8f8fd40 100644 --- a/nextflow.config +++ b/nextflow.config @@ -173,7 +173,7 @@ dag { manifest { name = 'oushujun/EDTA' description = 'Extensive de-novo TE Annotator' - version = '3.0.0dev' + version = '2.2.2' nextflowVersion = '!>=24.10.3' contributors = [ [ From 8e48bd5446a03fa0bb875dd6c8451c3e7634ea4b Mon Sep 17 00:00:00 2001 From: Usman Rashid Date: Wed, 18 Dec 2024 11:27:48 +1300 Subject: [PATCH 48/49] Updated snapshots --- .github/workflows/ci.yml | 2 +- tests/nf-test/small/main.nf.test.snap | 6 +++--- tests/nf-test/tiny/main.nf.test.snap | 6 +++--- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 3a33d22..05fa7bd 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -72,7 +72,7 @@ jobs: - uses: nf-core/setup-nf-test@v1.1.2 with: - version: 0.9.0 + version: 0.9.2 - name: Setup apptainer if: matrix.profile == 'singularity' diff --git a/tests/nf-test/small/main.nf.test.snap b/tests/nf-test/small/main.nf.test.snap index 06527a9..676abd8 100644 --- a/tests/nf-test/small/main.nf.test.snap +++ b/tests/nf-test/small/main.nf.test.snap @@ -94,7 +94,7 @@ "mdust": "2006.10.17" }, "Workflow": { - "oushujun/EDTA": "v0.1.0dev" + "oushujun/EDTA": "v2.2.2" } }, "stable paths": [ @@ -112,8 +112,8 @@ ], "meta": { "nf-test": "0.9.2", - "nextflow": "24.04.4" + "nextflow": "24.10.3" }, - "timestamp": "2024-12-17T22:28:58.604502" + "timestamp": "2024-12-18T11:26:59.305705" } } \ No newline at end of file diff --git a/tests/nf-test/tiny/main.nf.test.snap b/tests/nf-test/tiny/main.nf.test.snap index 40e5592..c224e45 100644 --- a/tests/nf-test/tiny/main.nf.test.snap +++ b/tests/nf-test/tiny/main.nf.test.snap @@ -53,7 +53,7 @@ "TIR-Learner": "v3.0.2" }, "Workflow": { - "oushujun/EDTA": "v0.1.0dev" + "oushujun/EDTA": "v2.2.2" } }, "stable paths": [ @@ -68,8 +68,8 @@ ], "meta": { "nf-test": "0.9.2", - "nextflow": "24.04.4" + "nextflow": "24.10.3" }, - "timestamp": "2024-12-17T17:06:09.728972" + "timestamp": "2024-12-18T11:22:27.159514" } } \ No newline at end of file From c5098279c5213bdf782c3e70f263d422fd573547 Mon Sep 17 00:00:00 2001 From: Usman Rashid Date: Wed, 18 Dec 2024 17:02:35 +1300 Subject: [PATCH 49/49] Updated README --- README.md | 92 +++++++++++++++++++++++++++++++++++++------------------ 1 file changed, 63 insertions(+), 29 deletions(-) diff --git a/README.md b/README.md index 5aff5d4..b0523b3 100644 --- a/README.md +++ b/README.md @@ -1,29 +1,34 @@ [![install with bioconda](https://anaconda.org/bioconda/edta/badges/platforms.svg)](https://anaconda.org/bioconda/edta) [![Anaconda-Server Badge](https://anaconda.org/bioconda/edta/badges/license.svg)](https://github.com/oushujun/EDTA/blob/master/LICENSE) [![Anaconda-Server Badge](https://anaconda.org/bioconda/edta/badges/version.svg)](https://anaconda.org/bioconda/edta) [![Anaconda-Server Badge](https://anaconda.org/bioconda/edta/badges/downloads.svg)](https://anaconda.org/bioconda/edta) - -# The Extensive *de novo* TE Annotator (EDTA) - -## Table of Contents - - * [Introduction](#introduction) - * [Installation](#installation) - * [Quick installation using conda/mamba](#install-with-condamamba-linux64) - * [Quick installation using Singularity](#install-with-singularity-good-for-hpc-users) - * [Quick installation using Docker](#install-with-docker-good-for-rootmacosapple-m-chip-users) - * [Testing](#testing) - * [Inputs](#inputs) - * [Outputs](#outputs) - * [EDTA usage](#edta-usage) - * [From head to toe](#from-head-to-toe) - * [Divide and conquer](#divide-and-conquer) - * [Protips and self-diagnosis](#protips-and-self-diagnosis) - * [panEDTA usage](#panedta-usage) - * [Benchmark](#benchmark) - * [Citations](#citations) - * [Other resources](#other-resources) - * [Questions and Issues](#questions-and-issues) - * [Acknowledgements](#acknowledgements) - +[![Nextflow](https://img.shields.io/badge/nextflow%20DSL2-%E2%89%A524.10.3-23aa62.svg)](https://www.nextflow.io/) +[![run with conda](http://img.shields.io/badge/run%20with-conda%20-3EB049?labelColor=000000&logo=anaconda)](https://docs.conda.io/en/latest/) +[![run with docker](https://img.shields.io/badge/run%20with-docker-0db7ed?labelColor=000000&logo=docker)](https://www.docker.com/) +[![run with singularity](https://img.shields.io/badge/run%20with-singularity-1d355c.svg?labelColor=000000)](https://sylabs.io/docs/) +[![nf-test](https://img.shields.io/badge/unit_tests-nf--test-337ab7.svg)](https://www.nf-test.com) + +# The Extensive *de novo* TE Annotator (EDTA) + +## Table of Contents + +- [Introduction](#introduction) +- [Installation](#installation) + - [Install with conda/mamba (Linux64)](#install-with-condamamba-linux64) + - [Install with Singularity (good for HPC users)](#install-with-singularity-good-for-hpc-users) + - [Install with Docker (good for root/macOS/Apple M-chip users)](#install-with-docker-good-for-rootmacosapple-m-chip-users) +- [Testing](#testing) +- [Inputs](#inputs) +- [Outputs](#outputs) +- [EDTA Usage](#edta-usage) + - [From head to toe](#from-head-to-toe) + - [Divide and conquer](#divide-and-conquer) + - [Protips and self-diagnosis](#protips-and-self-diagnosis) + - [Run with Nextflow](#run-with-nextflow) +- [panEDTA usage](#panedta-usage) +- [Benchmark](#benchmark) +- [Citations](#citations) +- [Other resources](#other-resources) +- [Questions and Issues](#questions-and-issues) +- [Acknowledgements](#acknowledgements) ## Introduction This package is developed for automated whole-genome *de-novo* TE annotation and benchmarking the annotation performance of TE libraries. @@ -41,7 +46,6 @@ To benchmark the annotation quality of a new library/method, I have provided the For pan-genome annotations, you need to annotate each genome with EDTA, generate a pan-genome library, then reannotate each genome with the pan-genome library. Please refer to this [example](https://github.com/HuffordLab/NAM-genomes/tree/master/te-annotation) for details. A sequential version of panEDTA is also included in this package. - ## Installation There are several ways to install EDTA. You just need to find the one that works for your system. If you are not using macOS, you may try the conda approach before the Singularity approach. @@ -120,8 +124,8 @@ Visit [BioContainers](https://quay.io/repository/biocontainers/edta?tab=tags) re Note: Because only the current directory is mounted to the EDTA docker container, you have to copy all needed files to the current directory and provide them to EDTA without path specifications. Even providing the absolute path to the file located in this folder won't work. Softlinked files are considered "with path" and won't work. Similarily, specifying your own versions of dependency programs (i.e., repeatmasker, repeatmodeler) won't work because they have paths. - ## Testing + You should test the EDTA pipeline with a 1-Mb toy genome, which takes about five mins. If your test finishs without any errors (warnings are OK), then EDTA should be correctly installed. If the test is OK but you encounter errors with your data, you should check your own data for any formating/naming mistakes. ``` @@ -131,8 +135,8 @@ perl ../EDTA.pl --genome genome.fa --cds genome.cds.fa --curatedlib ../database/ If your test fails, you may check out this [collection of issues](https://github.com/oushujun/EDTA/wiki/Installations,-builds,-and-tests-Q&A) for possible reasons and solutions. If none works, you may open a new issue. - ## Inputs + Required: The genome file [FASTA]. Please make sure sequence names are short (<=13 characters) and simple (i.e, letters, numbers, and underscore). Optional: @@ -140,8 +144,8 @@ Optional: 2. Known gene positions of this version of the genome assembly [BED]. Coordinates specified in this file will be excluded from TE annotation to avoid over-masking. 3. Curated TE library of the species [FASTA]. This file is trusted 100%. Please make sure it's curated. If you only have a couple of curated sequences, that's also good. It doesn't need to be complete. Providing curated TE sequences, especially for those under-annotated TE types (i.e., SINEs and LINEs), will greatly improve the annotation quality. For more information, please visit this wiki page: [How to prepare a curated library to maximize the efficacy of EDTA](https://github.com/oushujun/EDTA/wiki/How-to-prepare-a-curated-library-to-maximize-the-efficacy-of-EDTA) - ## Outputs + A non-redundant TE library: $genome.mod.EDTA.TElib.fa. The curated library will be included in this file if provided. The [rice library](./database/rice7.0.0.liban) will be (partially) included if `--force 1` is specified. TEs are classified into the superfamily level and using the three-letter naming system reported in [Wicker et al. (2007)](https://www.nature.com/articles/nrg2165). Each sequence can be considered as a TE family. To convert between classification systems, please refer to the [TE sequence ontology file](./bin/TE_Sequence_Ontology.txt). Optional 1: @@ -155,10 +159,10 @@ Optional 2, when you specify the `--anno 1` parameter, you will get: 6. Annotation inconsistency for nested TEs: $genome.mod.EDTA.TE.fa.stat.nested.sum. 7. Oveall annotation inconsistency: $genome.mod.EDTA.TE.fa.stat.all.sum. - ## EDTA Usage ### From head to toe + *You got a genome and you want to get a high-quality TE annotation:* perl EDTA.pl [options] @@ -189,6 +193,7 @@ Optional 2, when you specify the `--anno 1` parameter, you will get: --help|-h Display this help info ### Divide and conquer + *Identify intact elements of a paticular TE type*: 1.Get raw TEs from a genome (specify `-type ltr|tir|helitron` in different runs) @@ -207,11 +212,35 @@ Optional 2, when you specify the `--anno 1` parameter, you will get: perl EDTA.pl --overwrite 0 [options] ### Protips and self-diagnosis + 1. It's never said enough. You should tidy up all your sequence names before ANY analysis. Keep them short, simple, and unique. 2. Run it in a fast drive (i.e., SSD) because RepeatMasker/RepeatModeler is I/O intense. 3. Check out the [Wiki page](https://github.com/oushujun/EDTA/wiki) for more information and frequently asked questions. +### Run with [Nextflow](https://www.nextflow.io/) + +A limited beta version of the pipeline implemented in Nextflow is also available. The pipeline can be launched with Nextflow without the need to download it. Nextflow takes care of the pipeline download and dependency setup through the chosen execution engine such as Docker, Singularity, Conda, Mamba, Podman, etc. + +```bash +nextflow run oushujun/EDTA \ + -revision \ + -profile \ + --genome \ + --outdir +``` + +Where, `--genome` is either a path to a fasta file or a text file which lists fasta files, and `--outdir` is a path to a directory where the pipeline outputs should be stored. + +If you are new to Nextflow and nf-core, please refer to [this page](https://nf-co.re/docs/usage/installation) on how to set-up Nextflow. Make sure to [test your setup](https://nf-co.re/docs/usage/introduction#how-to-run-a-pipeline) with `-profile test` before running the workflow on actual data. For example when using Conda for dependency management, make sure Conda is installed and then test the pipeline on the included test data with, + +```bash +nextflow run oushujun/EDTA \ + -profile conda,test + --outdir results +``` + ## panEDTA usage + This is the serial version of panEDTA. Each genome will be annotated sequentially and then combined with the panEDTA functionality. Existing EDTA annotation of genomes (EDTA run with --anno 1) will be recognized and reused. A way to acclerate the pan-genome annotation is to execute EDTA annotation of each genomes separately and in parallel, then execute panEDTA to finish the remaining of the runs. You may want to save the GFF files and the sum file of the EDTA results of each genome because they will be overwritten by panEDTA. To help filtering out gene-related sequences, at least one CDS file is required. Please read [wiki](https://github.com/oushujun/EDTA/wiki/Making-sense-of-EDTA-usage-and-outputs---Q&A) for the CDS requirement. You may want to check out the toy example in the ./test folder to get familiarized. sh panEDTA.sh -g genome_list.txt -c cds.fasta -t 10 @@ -231,6 +260,7 @@ This is the serial version of panEDTA. Each genome will be annotated sequentiall ## Benchmark + If you developed a new TE method/got a TE library and want to compare it's annotation performance to the methods we have tested, you can: 1.annotate the rice genome with your test library: @@ -256,6 +286,7 @@ eg. Note: the -std and -tst files should be named differently even they are placed in different folders. ## Citations + Please cite our paper if you find EDTA useful: Ou S., Su W., Liao Y., Chougule K., Agda J. R. A., Hellinga A. J., Lugo C. S. B., Elliott T. A., Ware D., Peterson T., Jiang N.✉, Hirsch C. N.✉ and Hufford M. B.✉ (2019). Benchmarking Transposable Element Annotation Methods for Creation of a Streamlined, Comprehensive Pipeline. [Genome Biol. 20(1): 275.](https://genomebiology.biomedcentral.com/articles/10.1186/s13059-019-1905-y) @@ -267,10 +298,13 @@ Ou S., Collins T., Qiu Y., Seetharam A., Menard C., Manchanda N., Gent J., Schat Please also cite the software packages that were used in EDTA, listed in the [EDTA/bin](./bin) directory. ## Other resources + You may download the [rice genome here](http://rice.uga.edu/pub/data/Eukaryotic_Projects/o_sativa/annotation_dbs/pseudomolecules/version_7.0/all.dir/) (the "all.con" file). ## Questions and Issues + You may want to check out this [Q&A page](https://github.com/oushujun/EDTA/wiki) for best practices and get answered. If you have other issues with installation and usage, please check if similar issues have been reported in [Issues](https://github.com/oushujun/EDTA/issues) or open a new issue. If you are (looking for) happy users, please read or write successful cases [here](https://github.com/oushujun/EDTA/issues/15). ## Acknowledgements + I want to thank [Jacques Dainat](https://github.com/Juke34) for contribution of the EDTA conda recipe as well as improving the codes. I also want to thank [Qiushi Li](https://github.com/QiushiLi), [Zhigui Bao](https://github.com/baozg), [Philipp Bayer](https://github.com/philippbayer), [Nick Carleson](https://github.com/Neato-Nick), [@aderzelle](https://github.com/aderzelle), [Sanzhen Liu](https://github.com/liu3zhenlab), [Zhougeng Xu](https://github.com/xuzhougeng), [Shun Wang](https://github.com/wangshun1121), [Nancy Manchanda](https://github.com/nm100), [Eric Burgueño](https://github.com/eburgueno), [Sergei Ryazansky](https://github.com/DrHogart), and many more others for testing, debugging, and improving the EDTA pipeline.

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ⅆkT3+3Gw}=MrxOJeVWvOCo=T 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
brLqC0dQ#5&4vAi5O*bcS-i$6}|=$I|Cy?Q$L% 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⋘}&`kg^`ZFgV_8iggK=*Ndk7tfXg*#utg)F~n$gVuH-x-5BteY3T2K5ABOi(Y^0K&Sm8ZtGL`<-28 zKWxU9|6)93LXjetIjarpd^hCsbNb!yuZcYkrZ0^5Q)K}GFVl5P z>?mO}qD~e-t~9sb`HLC*5`OGuykoZb5Sl`w{zE47#D3>brVq_JdAysJJ65CITF7@@%i|~x5I~ zz-$7>vk#H&m6@_FBW4+1&uL-;rW(+dx z&kXG4E)js{mv0bz(BwH@SDR_L4uEi`N%4Of9v|4x6OPT?=eQ&nQs9X4ZSM!7| z%c2+Wtv~lyGNA(O)Kk!30Q)}kX{C!_7ik=eM3#@k&u=UnKX+pD*hs;ciEGEso!B~d z5zhD?``GY>zLw0vSc>`=Lx1P({mwF1{~YFPrkgLJe;W0dLTe;z4Adq6G{TGt;g=_F z7<X<2#&)snp*A{h{ynJD0io6MjqBFZ2t)z2j?b@dSMCcvI%wiJh5?aGnMa zLw5{+Ij3TLyQ1Iu39jj%@3UMQS+2{YP@fR1M9EBIc&{N|%bb0*WA%`~B}!qhi+!c~TLS>J~szZCL|ls%1M zQT~uW7p0E|4m)N}xK39D(a)uyhWZB?U4QRkKEKa$YDAjZ%g4^1muVCpo?)=!f*3r z-M}XkXHP7>0p)}bPNLAI7w4j^Jp|~X4^pP#`C=o+HXJ|UV7@XztGuUQelH{ZGx5Dc z((^!1xz|f)tSJG#74$(Cf{d*bGnTQghRbB(A!@{2 z_<@46M4N-)dHQ-WU@IWA6*AW{J=m{ez7-o$pkYMq+*x+0UksuZ|L08z-zcd_7!6pK z|2$>mPNcWvp?+s=l8x87`9UOvjYYypsCr|V{<*GbXtcUH(qv8vt z_&94gxp9S&uWVEjx4p6ND}%mj=v)6J^s&N*FQ1rpPk0uCDhzL=!sYDLmyL^jFh+eP z9pAHEHg5UY#_0F?8%TG`O6nHlbgHStUpw@+J=gDSAVJg{_*nI+s3oe;R()P!9eO;Z zW7U*d6N*4DPNL5UptBCKjTFp;b6)9}d-{>Kr;R-1mgCF`3*75jG?THb4ze2|yNQ8V z*bR56F-}rB)`UOw?W(6u5V|%(SMf&7cU&K!i}lwv55F3N+UDZ~6jTmONt=cJwD0sg zpPYdkJw7bDH*!z(-0zQV9Jg$IWZjs(G1@~I6Fw*eKIgCj`OOOC7Jn#}i_pn&XwFkZp$SF1L=MuWV%dSRNHI5VB*!ORe}cb3*v$iSaQd6Nko>;yewQ zVs(GEx`%G&Wf1xcV*}0w)Q@#iPx<@-WimswtYW&2bv|`A+V>Fy&j0R<8&M`=e3oci zV%(|f($Kg_FUM!e)rqbV`YNDrICsD~d_S!Z^;gx+V!LiOXGd;~`sza|he9#@wLo9{ zv;p}oG5p@ZvhW2HbHlUu7QeHH_)r8)ComP$2b?nHQ+K?R8=m3TLJslVo7oe>9Gb|X z5Sy#0QyIm0zA|HEJqukubEO^`-sNBgx;S>Mg02lM1A*(w4T{fcv|Hz<$tgt^NNd1f z3;DxLKBu{yTq_*Y=>V=7xS)0K?YrS~zLz>j%AahegKOH54PI@>$j`e;%6I{I8mP@wTQcvy8dHe5ReR_?|KYo#UV~@XCKWAitS$shUIUwVv-HZ}_X{cOPdZ3JWOM0`SsQ)Pu81U~nr0q1+TcXgY1y*8}y zsq-+u2VRT`v@It7F4(U0GoJ;(r)?M*)%Odkfh_`dAJY~-d&NEdxdQw=@Wpo@@RNV) zm2<&c177@pH_JJUwhJ=3_-jIYA6z%!oDTm*CqI|izhJC?g#VGSn||B~nVqPkSO&zN zA4A4ny9)nKOAx;U_>|`c#2Rw+vu6`&xcJBvy`Dpxve3@zo*!@?qyAn4Z`c$$RZGZx zltX4~>wt4A#;?&A17vn4WLU@BO2&lhQC^-pI8FML{6X+*0{Co$yTGpm|0A>R$!E^) zP8a4s3(n1cdBEvl#nS#a*L}JsYIlixS_b^4H_^7)`26tW_wnExfG>^@I6bTke)vbn zd+_Ul-#Ikk{48qVP2V1xSo5L%TY+Ek!GQC;nNI^B=FR|BuUUTSXP~@49B{5O@nPcwh06YXdKYcHzg1gBJxa-$!l-c-i1FKD&OxCDThg z3-il2H@m;FqQ5Oe|K@<#4*#BJ)??GZ$FnxezPcRP?oS5fH$ils+&xt{1IN&Du3L61 zfR7FjI6pCMGIU?6BpA2X1H0-kh<~_t?HBEd`eY!Bfi(zR?qAXFh+C+tA)i-G2kf=i zMf?bzjrai!rV0sNM!MBTx;el$e>xz4m-TF=PsAFr3xJIl z{x$-;Hg!<`u9R*=s%>zW2Ti zrc?1V%zE$U^J=A4e3MZ0_-t!YEb?vIZ_s(0dB(b?hy~&2L|*8-Eb#009~AHOxW1da zjQio=PzuF3rv$jd1FSNW@$q*W$9hl;TuS<&bA)N5p7Z@lBUS$g?CRIBU!1n-vc)C) zsy6V3p~tV^jDuGjAh!d&>;wJf($2&4@&LIU@G1l3iowf*oc&!Q+F1o&F?hC}^ld$O zOQAExY_Btyi7%%|)VD^7TMJy(L4(d;qqxz2aN|wn4;=B+x1GQhWDbh&3WTs0Bg&Fv zv)5;!HsSlHKjyXc-!5QdJ~lJ9XPl4qN$8nn?$ywY9E0{11Hbi%LGj)Me+yHj zE6P5yicyN4BYjZ=-1c83f18WEM(`>p4LS##c?6yLS_@v)(7D)b z$7Ub6p6yo}$F^1j{I25%z2kR`NfrRbd^Rcm=eTxl*ZF+kRVBv5ZNPR;3Dz~J_$Qim zORd9T8P`?DbWAR{`PkNcC#A1v|lFkSl>2)*8shq@jJ&$><-mBR}8ozmHz#>cKs=~^vNIi@$#?OEJBUMZUwgP(!P!H!zqK#Js$hM2m55-mka(N z_{W*C8|4`}g6d=+RR+wqQ_+r1jQX*Y81&YXf7+mYwrJFe$l(bctAN>2=&yrzZXkc! zpm$B#n1jX@Kh4a?Y+<5kJ3GPO3VGH+x6I{If#7FcDEWA0C-I#?Hd4e+_OtomN2gnT zSomQ8==E@sy5LSYQLwbR0PV4U!)B)(*HP!;#5x-8_0c+pPD5N#XH9p{En)=`lVN#o zRp+6f$orT&rzv{rvy$GV&S_VQ72!~!;^(S!Oyi%E`1V;6H>~eFL`w^;`7R%hghFCY zg)^xf2l>(I+r5{2@kfK^N2gCoFB~iLkE$dNeeQgePbLsg#w-7bJH#z@Za+lQOBG*{ zLz0SlwBpB_u{%jt7xro7_!JbA>#{|WgT#cz*DzSgVHrS%M~cwQO6 zZw*ViAbzuwe?<9nzDoZW5aXER`a7kcrI$jXJ3-^P-KIB!cBl`X?fn@1-J|qKYeJ!W zt$SPF4|6vo{fU$3s8{uRoT82tm#&5aKYOkP-OvBEp!@m%2GCQHrd>~N z0-Z6;?r)YWJHZymNbS2GKtDTg2i?zacYyBax4S_1tB3c2?w8+I(2s?McD`N${UXrs zRQZsjspR;%Qt@d!4?CYvC_a71!{$Gy_`1Kh`7bNJct!*HR4bluR{V(2o~*x9@pXN% z^{1$DpdR1Y{4GjekF)-)?l}eFF|6+4E^++D;`{k^0gyI*0~GktdqMXr&uyTyJU>ZeTg6bAE0wzjje~nSwH+GptGIX_>)1WO{GoJ{t{gCcmNfN z^ZHl*OVTe8)KKVooD1+*bDyMh9>wDzc?vZu`W$ut6e=wB{6*0#6#XrnS=OC#Sp%^I zC=OkJThEj&p-{X)1z)OjWuHGjBo^f{U$OAHJrZ9p)w}bH){CV6T!jy*b87%Tbhgw} zW8q`U{!UAO+5)M+QsIYxBITO{@S$mv-(=xydL_Ns@=s-j)L*LbF?H??z=!5aJ@poT z`~9*UFIV=n+}Iv%`drYC@S$G}8v7X=&vwMRd(KL!|9o8YxDS7`@Yk%$QDw{RA1mLL zcEdoc#Sh9) z>nT#MN#TZ+U(QngN-BSa(|*umZQ&>1gn`drYD@}b`YdZ7=UVU0G}dVU4^xjyvc(fHZ^ZTzVqG9KG>?tdkI`wykx z#IrBl*Wm92{GI)nq|d=Mk32a1n>gF_Whj5nS}{>JQ>K>vsIO)LRK% zJTAr`^OXCfr0aHZGH9#^Hhp#g{T$FI``|gAVnqCbrJwaz#M+mo-FDnfdsWhh6~E>+ zd4Jo}(oQ=L4U3OgK&d)VeB$U-^6x0Qy%oJr-RpVMAJn}r=ajWFoqUBaRQD)OaZFeD zx?P^5?sY%*gu2)Myb4mREA=WLc7EIWQCA$zim&sV@{F|O3c;28`;W%8O)mrODEwWk z@DmjN7In|H6dpfN_qtxO%-CLr6W9_c4tzWUr6m-Hb1u;Oc6{5;uDhxp-e95HnsR{AwPP0`O;C-bG- z|1On}^Mo|=1vQMP`cQTiHWebU;i#M@&rRxlgaUS6An9q}mgih`mUn1>m*^`g(60E5 z={yQ6{rNIikUvl9T_Dee>ORDeCE~E_p&su9@$K>=F`e_?=r(*5w20s2Yz!(SAD zC*2RvZ+)_FvHfrZ=o}aL(HSFb`i;Q*`Hgfxd|d#ZbmCj~;wV;L^K7Sg7jSt#^t(Z4 zEVc2Z&-bC<8K8&X#PHKY`l-`INqun;0{R{hY+n&qO>3q|Nz65keJ{w;H zI>(uQbiep={W#fg+xQO9PxsL?2m0Cf+W3n=|C$edA?Qc=&~FBPk`MhB&^cDH^)Cbc z7|@-)WqGrVcx*!a;(RFfqU=xfcvPQ5ik@8}<;sOrDAbEH>-h>rhk9|?xM5Jgg1_w3 zr98`s$I0scLU|jSi!*iP?k6?b{aV`5l0KXz&o%qV5zDJ*NWJ!WvMEjCl==|56LEAZ z`6uQ}IaD)ooS^QnRQD%=&2svYqQgz%uyLmV$1&K`3a8UqYu(#&4?~Xa%BKGibk<9) zM9=G&8R0<#tjkEQfi%y39Ow%v@{W$z`9$ft9M?Rk zBNu-*eGX_GH`;W@og;kc7lX!fw((0qV}5P=qo8q|Y11D9?OQ(d*Fekhp?|38ol|9t zv*TdXw1*nReTxc5!WpIQ|eyVQ(Jzm;>*;N<)2f0_B}jo zdHenih1c`#chx=X3=bP`-+!#|vKJT`zeC}*p6I#KUwS^M=`!_XeP300onB1ck2b${ z`X?#*uc-t_O5c{RR`R;Mu2A=k`8-C_x2OJFs`Rrh@)$|K&Ht&A=NO8I&A0EHRCxvU zD>nX%-oK;lWBlMTlD-cVUblyl@HYP=C9l^UY`%T}4~5tDbx-9dE|B$|YnePMm4795 z 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 diff --git a/conf/modules.config b/conf/modules.config index 7e9147f..0bce9d9 100644 --- a/conf/modules.config +++ b/conf/modules.config @@ -1,4 +1,13 @@ process { + + withName: 'EDTA:CUSTOM_SHORTENFASTAIDS' { + publishDir = [ + path: { "${params.outdir}/${meta.id}" }, + mode: params.publish_dir_mode, + saveAs: { filename -> ( filename.equals('versions.yml') || filename.endsWith('fasta') ) ? null : filename } + ] + } + withName: 'EDTA:LTRHARVEST' { ext.args = '-size 1000000 -time 300' ext.prefix = { "${meta.id}_ltrharvest" } @@ -12,48 +21,17 @@ process { ext.prefix = { "${meta.id}_ltrharvest_ltrfinder.tabout" } } - withName: 'EDTA:LTR_RETRIEVER_POSTPROCESS' { - - publishDir = [ - path: { "${params.outdir}/raw/ltr" }, - mode: params.publish_dir_mode, - saveAs: { filename -> filename.equals('versions.yml') ? null : filename } - ] - } - withName: 'EDTA:ANNOSINE' { + ext.prefix = { "${meta.id}.annosine" } ext.args = params.annosine_ext_args ?: '-a 2 --num_alignments 50000 -rpm 0 --copy_number 3 --shift 100 -auto 1' } - withName: 'EDTA:ANNOSINE_POSTPROCESS' { - - publishDir = [ - path: { "${params.outdir}/raw/sine" }, - mode: params.publish_dir_mode, - saveAs: { filename -> filename.equals('versions.yml') ? null : filename } - ] - } - withName: 'EDTA:REPEATMODELER_REPEATMODELER' { ext.args = '-engine ncbi' } - withName: 'EDTA:REPEATMODELER_POSTPROCESS' { - - publishDir = [ - path: { "${params.outdir}/raw/line" }, - mode: params.publish_dir_mode, - saveAs: { filename -> filename.equals('versions.yml') ? null : filename } - ] - } - - withName: 'EDTA:TIR_LEARNER_POSTPROCESS' { - - publishDir = [ - path: { "${params.outdir}/raw/tir" }, - mode: params.publish_dir_mode, - saveAs: { filename -> filename.equals('versions.yml') ? null : filename } - ] + withName: 'EDTA:TIRLEARNER' { + ext.prefix = { "${meta.id}.tirlearner" } } withName: 'EDTA:FASTA_HELITRONSCANNER_SCAN_DRAW:HELITRONSCANNER_DRAW' { @@ -83,40 +61,24 @@ process { ext.args = '-sitefilter 1 -minscore 12 -keepshorter 1 -extlen 30 -extout 1' } - withName: 'EDTA:HELITRONSCANNER_POSTPROCESS' { - + withName: 'EDTA:FINAL_FILTER' { publishDir = [ - path: { "${params.outdir}/raw/helitron" }, + path: { "${params.outdir}/${meta.id}" }, mode: params.publish_dir_mode, - saveAs: { filename -> filename.equals('versions.yml') ? null : filename } - ] - } - - withName: 'COMBINE_INTACT_TES' { - ext.prefix = { "${meta.id}" } + saveAs: { filename -> - publishDir = [ - path: { "${params.outdir}/raw/intact" }, - mode: params.publish_dir_mode, - saveAs: { filename -> filename.equals('versions.yml') ? null : filename } - ] - } + if ( filename.equals('versions.yml') ) { return null } - withName: 'EDTA:PROCESS_K' { - ext.prefix = { "${meta.id}" } + if ( filename.endsWith('gff3') && meta.changed_ids ) { return null } - publishDir = [ - path: { "${params.outdir}/stg1" }, - mode: params.publish_dir_mode, - saveAs: { filename -> filename.equals('versions.yml') ? null : filename } + filename + } ] } - withName: 'EDTA:FINAL_FILTER' { - ext.prefix = { "${meta.id}" } - + withName: 'EDTA:CUSTOM_RESTOREGFFIDS' { publishDir = [ - path: { "${params.outdir}/final" }, + path: { "${params.outdir}/${meta.id}" }, mode: params.publish_dir_mode, saveAs: { filename -> filename.equals('versions.yml') ? null : filename } ] diff --git a/conf/test.config b/conf/test.config index 7f76e47..6d0509d 100644 --- a/conf/test.config +++ b/conf/test.config @@ -1,9 +1,5 @@ params { - genome = 'https://raw.githubusercontent.com/nf-core/test-datasets/modules/data/genomics/eukaryotes/actinidia_chinensis/genome/chr1/genome.fasta.gz' - - // Following parameters for ANNOSINE are not recommended. - // These are used here with the test data to produce testable outputs - annosine_ext_args = '-a 2 -rpm 0 --copy_number 1 --shift 200' + genome = 'https://raw.githubusercontent.com/oushujun/EDTA/4f0424b8a3ce2caccdd51b4021c61a536fa9ead9/test/genome.fa' } process { diff --git a/modules.json b/modules.json index 5d34d4e..b012c3e 100644 --- a/modules.json +++ b/modules.json @@ -10,6 +10,16 @@ "git_sha": "a8939d36280e7d9037c7cf164eeede19e46546a4", "installed_by": ["modules"] }, + "custom/restoregffids": { + "branch": "main", + "git_sha": "072113038b5591f8244ce76b2c9dd8100c912b18", + "installed_by": ["modules"] + }, + "custom/shortenfastaids": { + "branch": "main", + "git_sha": "072113038b5591f8244ce76b2c9dd8100c912b18", + "installed_by": ["modules"] + }, "gunzip": { "branch": "main", "git_sha": "ae9714c21ede9199a3118e3c20b65484aa73e232", @@ -27,7 +37,7 @@ }, "tirlearner": { "branch": "main", - "git_sha": "a8939d36280e7d9037c7cf164eeede19e46546a4", + "git_sha": "072113038b5591f8244ce76b2c9dd8100c912b18", "installed_by": ["modules"] } } diff --git a/modules/gallvp/custom/restoregffids/environment.yml b/modules/gallvp/custom/restoregffids/environment.yml new file mode 100644 index 0000000..68eaa88 --- /dev/null +++ b/modules/gallvp/custom/restoregffids/environment.yml @@ -0,0 +1,6 @@ +channels: + - conda-forge + - bioconda + +dependencies: + - "python=3.10.2" diff --git a/modules/gallvp/custom/restoregffids/main.nf b/modules/gallvp/custom/restoregffids/main.nf new file mode 100644 index 0000000..39c8fde --- /dev/null +++ b/modules/gallvp/custom/restoregffids/main.nf @@ -0,0 +1,35 @@ +process CUSTOM_RESTOREGFFIDS { + tag "$meta.id" + label 'process_single' + + conda "${moduleDir}/environment.yml" + container "${ workflow.containerEngine == 'singularity' && !task.ext.singularity_pull_docker_container ? + 'https://depot.galaxyproject.org/singularity/python:3.10.2': + 'biocontainers/python:3.10.2' }" + + input: + tuple val(meta), path(gff3) + path(ids_tsv) + + output: + tuple val(meta), path("*.restored.ids.gff3") , emit: restored_ids_gff3 + path "versions.yml" , emit: versions + + when: + task.ext.when == null || task.ext.when + + script: + prefix = task.ext.prefix ?: "${meta.id}" + template 'restore_gff_ids.py' + + stub: + prefix = task.ext.prefix ?: "${meta.id}" + """ + touch "${prefix}.restored.ids.gff3" + + cat <<-END_VERSIONS > versions.yml + "${task.process}": + python: \$(python --version | cut -d' ' -f2) + END_VERSIONS + """ +} diff --git a/modules/gallvp/custom/restoregffids/meta.yml b/modules/gallvp/custom/restoregffids/meta.yml new file mode 100644 index 0000000..dea7577 --- /dev/null +++ b/modules/gallvp/custom/restoregffids/meta.yml @@ -0,0 +1,60 @@ +# yaml-language-server: $schema=https://raw.githubusercontent.com/nf-core/modules/master/modules/meta-schema.json +name: "custom_restoregffids" +description: | + Restores IDs in a gff3 file based on a TSV table + consisting of original (first column) and new IDs (second column). + This module is helpful when some tools like EDTA implicitly shorten + the IDs without producing the ID map, leading to downstream mismatch + in IDs across files. +keywords: + - genome + - gff + - ID + - shorten + - restore +tools: + - "python": + description: | + Python is a programming language that lets you work quickly + and integrate systems more effectively + homepage: "https://www.python.org" + documentation: "https://docs.python.org/3/" + tool_dev_url: "https://github.com/python/cpython" + licence: ["MIT"] + identifier: "" +input: + - - meta: + type: map + description: | + Groovy Map containing sample information + e.g. `[ id:'test' ]` + - gff3: + type: file + description: Input gff3 file + pattern: "*.{gff,gff3}" + - - ids_tsv: + type: file + description: | + A TSV file with original (first column) and new ids (second column) + if id change was required + pattern: "*.tsv" +output: + - restored_ids_gff3: + - meta: + type: map + description: | + Groovy Map containing sample information + e.g. `[ id:'test' ]` + - "*.restored.ids.gff3": + type: file + description: GFF3 file with restored ids + pattern: "*.restored.ids.gff3" + - versions: + - versions.yml: + type: file + description: File containing software versions + pattern: "versions.yml" +authors: + - "@GallVp" +maintainers: + - "@GallVp" diff --git a/modules/gallvp/custom/restoregffids/templates/restore_gff_ids.py b/modules/gallvp/custom/restoregffids/templates/restore_gff_ids.py new file mode 100755 index 0000000..2bde7ab --- /dev/null +++ b/modules/gallvp/custom/restoregffids/templates/restore_gff_ids.py @@ -0,0 +1,47 @@ +#!/usr/bin/env python3 + +from platform import python_version + +ids_tsv = "$ids_tsv" +input_gff3 = "$gff3" +output_prefix = "$prefix" + + +def create_name_mapping_from_tsv(file_path): + dictionary = {} + + with open(file_path) as tsv_file: + for line in tsv_file: + columns = line.strip().split("\\t") + if len(columns) != 2: + raise ValueError(f"{file_path} should be a two column TSV file") + + orig_id, new_id = columns[0], columns[1] + dictionary[new_id] = orig_id + + return dictionary + + +def restore_gff3_ids(new_to_orig_ids, file_path, output_file_name): + # Write versions + with open("versions.yml", "w") as f_versions: + f_versions.write('"${task.process}":\\n') + f_versions.write(f" python: {python_version()}\\n") + + with open(file_path) as input_gff3_file: + input_lines = input_gff3_file.readlines() + + with open(output_file_name, "w") as output_gff_file: + for line in input_lines: + if line.startswith("##"): + output_gff_file.write(line) + continue + + new_id = line.split("\\t")[0] + orig_id = new_to_orig_ids[new_id] + output_gff_file.write("\\t".join([orig_id] + line.split("\\t")[1:])) + + +if __name__ == "__main__": + new_to_orig_ids = create_name_mapping_from_tsv(ids_tsv) + restore_gff3_ids(new_to_orig_ids, input_gff3, f"{output_prefix}.restored.ids.gff3") diff --git a/modules/gallvp/custom/restoregffids/tests/main.nf.test b/modules/gallvp/custom/restoregffids/tests/main.nf.test new file mode 100644 index 0000000..2c248c9 --- /dev/null +++ b/modules/gallvp/custom/restoregffids/tests/main.nf.test @@ -0,0 +1,63 @@ +nextflow_process { + + name "Test Process CUSTOM_RESTOREGFFIDS" + script "../main.nf" + process "CUSTOM_RESTOREGFFIDS" + + tag "modules" + tag "modules_gallvp" + tag "custom" + tag "custom/restoregffids" + + test("sarscov2-genome_gff3-success") { + when { + process { + """ + input[0] = [ + [ id:'test', single_end:false ], // meta map + file(params.modules_testdata_base_path + 'genomics/sarscov2/genome/genome.gff3', checkIfExists: true) + ] + input[1] = Channel.of('Chr1\tMT192765.1').collectFile(name: 'id_map.tsv', newLine: true) + """ + } + } + + then { + assertAll( + { assert process.success }, + { assert snapshot(process.out).match() }, + { assert path(process.out.restored_ids_gff3.get(0).get(1)).getText().contains("Chr1") }, + { assert !path(process.out.restored_ids_gff3.get(0).get(1)).getText().contains("MT192765.1") }, + { assert snapshot(process.out.versions).match("versions") } + ) + } + + } + + test("stub") { + + options '-stub' + + when { + process { + """ + input[0] = [ + [ id:'test', single_end:false ], // meta map + file(params.modules_testdata_base_path + 'genomics/sarscov2/genome/genome.gff3', checkIfExists: true) + ] + input[1] = Channel.of('Chr1\tMT192765.1').collectFile(name: 'id_map.tsv', newLine: true) + """ + } + } + + then { + assertAll( + { assert process.success }, + { assert process.out.restored_ids_gff3 != null }, + { assert snapshot(process.out.versions).match("versions_stub") } + ) + } + + } + +} \ No newline at end of file diff --git a/modules/gallvp/custom/restoregffids/tests/main.nf.test.snap b/modules/gallvp/custom/restoregffids/tests/main.nf.test.snap new file mode 100644 index 0000000..ebe850a --- /dev/null +++ b/modules/gallvp/custom/restoregffids/tests/main.nf.test.snap @@ -0,0 +1,61 @@ +{ + "sarscov2-genome_gff3-success": { + "content": [ + { + "0": [ + [ + { + "id": "test", + "single_end": false + }, + "test.restored.ids.gff3:md5,2c294938b9eb4e52d19e14725c1d92a9" + ] + ], + "1": [ + "versions.yml:md5,32d31c4f1da9a3d1be013fd163e5867e" + ], + "restored_ids_gff3": [ + [ + { + "id": "test", + "single_end": false + }, + "test.restored.ids.gff3:md5,2c294938b9eb4e52d19e14725c1d92a9" + ] + ], + "versions": [ + "versions.yml:md5,32d31c4f1da9a3d1be013fd163e5867e" + ] + } + ], + "meta": { + "nf-test": "0.8.4", + "nextflow": "23.10.1" + }, + "timestamp": "2023-12-07T13:49:30.047425" + }, + "versions_stub": { + "content": [ + [ + "versions.yml:md5,32d31c4f1da9a3d1be013fd163e5867e" + ] + ], + "meta": { + "nf-test": "0.8.4", + "nextflow": "23.10.1" + }, + "timestamp": "2024-05-10T15:26:11.66528" + }, + "versions": { + "content": [ + [ + "versions.yml:md5,32d31c4f1da9a3d1be013fd163e5867e" + ] + ], + "meta": { + "nf-test": "0.8.4", + "nextflow": "23.10.1" + }, + "timestamp": "2023-12-07T13:49:30.071175" + } +} \ No newline at end of file diff --git a/modules/gallvp/custom/shortenfastaids/environment.yml b/modules/gallvp/custom/shortenfastaids/environment.yml new file mode 100644 index 0000000..04ccfc9 --- /dev/null +++ b/modules/gallvp/custom/shortenfastaids/environment.yml @@ -0,0 +1,7 @@ +channels: + - conda-forge + - bioconda + +dependencies: + - biopython==1.75 + - python==3.8.13 diff --git a/modules/gallvp/custom/shortenfastaids/main.nf b/modules/gallvp/custom/shortenfastaids/main.nf new file mode 100644 index 0000000..886a626 --- /dev/null +++ b/modules/gallvp/custom/shortenfastaids/main.nf @@ -0,0 +1,38 @@ +process CUSTOM_SHORTENFASTAIDS { + tag "$meta.id" + label 'process_single' + + conda "${moduleDir}/environment.yml" + container "${ workflow.containerEngine == 'singularity' && !task.ext.singularity_pull_docker_container ? + 'https://depot.galaxyproject.org/singularity/biopython:1.75': + 'biocontainers/biopython:1.75' }" + + input: + tuple val(meta), path(fasta) + + output: + tuple val(meta), path("*.short.ids.fasta") , emit: short_ids_fasta , optional: true + tuple val(meta), path("*.short.ids.tsv") , emit: short_ids_tsv + path "versions.yml" , emit: versions + + when: + task.ext.when == null || task.ext.when + + script: + prefix = task.ext.prefix ?: "${meta.id}" + template 'shorten_fasta_ids.py' + + stub: + prefix = task.ext.prefix ?: "${meta.id}" + """ + echo \\ + 'IDs have acceptable length and character. No change required.' \\ + > ${meta.id}.short.ids.tsv + + cat <<-END_VERSIONS > versions.yml + "${task.process}": + python: \$(python --version | cut -d' ' -f2) + biopython: \$(pip list | grep "biopython" | cut -d' ' -f3) + END_VERSIONS + """ +} diff --git a/modules/gallvp/custom/shortenfastaids/meta.yml b/modules/gallvp/custom/shortenfastaids/meta.yml new file mode 100644 index 0000000..bb14b0a --- /dev/null +++ b/modules/gallvp/custom/shortenfastaids/meta.yml @@ -0,0 +1,66 @@ +# yaml-language-server: $schema=https://raw.githubusercontent.com/nf-core/modules/master/modules/meta-schema.json +name: "custom_shortenfastaids" +description: | + Shortens fasta IDs and produces a new fasta along with a TSV table + consisting of original (first column) and new IDs (second column). + This module is helpful when some tools like EDTA implicitly shorten + the IDs without producing the ID map, leading to downstream mismatch + in IDs across files. +keywords: + - genome + - fasta + - ID + - shorten +tools: + - "biopython": + description: | + Biopython is a set of freely available tools for biological computation written in Python by + an international team of developers. + homepage: "https://biopython.org" + documentation: "https://biopython.org/wiki/Documentation" + tool_dev_url: "https://github.com/biopython/biopython" + doi: "10.1093/bioinformatics/btp163" + licence: ["MIT"] + identifier: "" +input: + - - meta: + type: map + description: | + Groovy Map containing sample information + e.g. `[ id:'test' ]` + - fasta: + type: file + description: Input fasta file + pattern: "*.{fsa,fa,fasta}" +output: + - short_ids_fasta: + - meta: + type: map + description: | + Groovy Map containing sample information + e.g. `[ id:'test' ]` + - "*.short.ids.fasta": + type: file + description: Fasta file with shortened ids if id change is required + pattern: "*.{fsa,fa,fasta}" + - short_ids_tsv: + - meta: + type: map + description: | + Groovy Map containing sample information + e.g. `[ id:'test' ]` + - "*.short.ids.tsv": + type: file + description: | + A TSV file with original (first column) and new ids (second column) + if id change is required + pattern: "*.tsv" + - versions: + - versions.yml: + type: file + description: File containing software versions + pattern: "versions.yml" +authors: + - "@GallVp" +maintainers: + - "@GallVp" diff --git a/modules/gallvp/custom/shortenfastaids/templates/shorten_fasta_ids.py b/modules/gallvp/custom/shortenfastaids/templates/shorten_fasta_ids.py new file mode 100755 index 0000000..9d9c6e1 --- /dev/null +++ b/modules/gallvp/custom/shortenfastaids/templates/shorten_fasta_ids.py @@ -0,0 +1,168 @@ +#!/usr/bin/env python3 + +import re +from importlib.metadata import version +from platform import python_version + +from Bio import SeqIO + +# The input fasta file path +fasta_file_path = "$fasta" +output_files_prefix = "$prefix" + + +def extract_fasta_ids_and_descriptions(fasta_file_path): + fasta_file_obj = SeqIO.parse(fasta_file_path, "fasta") + + ids = [] + for record in fasta_file_obj: + ids.append((record.id, record.description)) + return ids + + +def write_fasta_with_new_ids(fasta_file_path, id_mapping, file_prefix): + old_fasta_file_obj = SeqIO.parse(fasta_file_path, "fasta") + id_map = dict(id_mapping) + + replaced_records = [] + for record in old_fasta_file_obj: + old_id = record.id + + new_id = id_map[old_id] + record.id = new_id + record.description = "" + + replaced_records.append(record) + + SeqIO.write(replaced_records, f"{file_prefix}.short.ids.fasta", "fasta") + + +def do_id_need_to_change(id_and_description, silent=False): + id = id_and_description[0] + description = id_and_description[1] + if len(id) > 13: + if not silent: + print(f"{id} has length greater than 13") + return True + + if not re.match(r"^[a-zA-Z0-9_]+\$", id): + if not silent: + print(f"{id} does not match '^[a-zA-Z0-9_]+\$'") + return True + + if description != id and description != "": + if not silent: + print(f"{id} contains a comment: {description.replace(id, '')}") + return True + + if not silent: + print(f"{id} is acceptable") + return False + + +def do_ids_need_to_change(ids_and_descriptions, silent=False): + return any([do_id_need_to_change(id_and_description, silent) for id_and_description in ids_and_descriptions]) + + +def extract_common_patterns(ids): + pattern_counts = {} + for id in ids: + patterns = re.findall(r"[A-Za-z0_]{4,}", id) + for pattern in set(patterns): + pattern_counts[pattern] = pattern_counts.get(pattern, 0) + 1 + + common_patterns = [pattern for pattern, count in pattern_counts.items() if count >= 2] + + if len(common_patterns) < 1: + return {} + + return {pattern: pattern[:3] for pattern in common_patterns} + + +def shorten_ids(input_ids_and_descriptions, patterns_dict): + shortened_ids = [] + + for id_and_description in input_ids_and_descriptions: + id = id_and_description[0] + description = "" # Treat description as absent as it will be removed by write_fasta_with_new_ids + if not do_id_need_to_change((id, description), silent=True): + shortened_ids.append(id) + continue + + shortened_id = shorten_id_by_pattern_replacement(patterns_dict, id) + + if not do_id_need_to_change((shortened_id, description), silent=True): + shortened_ids.append(shortened_id) + continue + + shortened_id = f"Ctg{generate_hash(id)}" + + if not do_id_need_to_change((shortened_id, description), silent=True): + shortened_ids.append(shortened_id) + continue + + raise ValueError(f"Failed to shorten id: {id} ({shortened_id})") + + return shortened_ids + + +def shorten_id_by_pattern_replacement(patterns_dict, id): + if patterns_dict == {}: + return id + + shortened_id = id + matches_for_id = match_substrings(patterns_dict.keys(), shortened_id) + + for pattern in matches_for_id: + shortened_id = re.sub( + rf"({re.escape(pattern)})", + patterns_dict[pattern], + shortened_id, + ) + return shortened_id if shortened_id[len(shortened_id) - 1] != "_" else shortened_id[0 : (len(shortened_id) - 1)] + + +def match_substrings(substrings, target_string): + pattern = "|".join(map(re.escape, substrings)) + matches = re.findall(pattern, target_string) + return matches + + +def generate_hash(string): + import hashlib + + hash_object = hashlib.sha1(string.encode()) + full_hash = hash_object.hexdigest() + short_hash = full_hash[:10] + return short_hash + + +def fail_if_new_ids_not_valid(ids): + if len(ids) != len(set(ids)): + raise ValueError("Th new IDs are not unique") + + +if __name__ == "__main__": + input_ids_and_descriptions = extract_fasta_ids_and_descriptions(fasta_file_path) + input_ids = [x[0] for x in input_ids_and_descriptions] + + # Write versions + with open("versions.yml", "w") as f_versions: + f_versions.write('"${task.process}":\\n') + f_versions.write(f" python: {python_version()}\\n") + f_versions.write(f" biopython: {version('biopython')}\\n") + + if not do_ids_need_to_change(input_ids_and_descriptions): + print("IDs have acceptable length and character. No change required.") + with open(f"{output_files_prefix}.short.ids.tsv", "w") as f: + f.write("IDs have acceptable length and character. No change required.") + exit(0) + + new_ids = shorten_ids(input_ids_and_descriptions, extract_common_patterns(input_ids)) + fail_if_new_ids_not_valid(new_ids) + + with open(f"{output_files_prefix}.short.ids.tsv", "w") as f: + for input_id, new_id in zip(input_ids, new_ids): + f.write(f"{input_id}\\t{new_id}\\n") + + write_fasta_with_new_ids(fasta_file_path, zip(input_ids, new_ids), output_files_prefix) diff --git a/modules/gallvp/custom/shortenfastaids/tests/main.nf.test b/modules/gallvp/custom/shortenfastaids/tests/main.nf.test new file mode 100644 index 0000000..8eb2099 --- /dev/null +++ b/modules/gallvp/custom/shortenfastaids/tests/main.nf.test @@ -0,0 +1,123 @@ +nextflow_process { + + name "Test Process CUSTOM_SHORTENFASTAIDS" + script "../main.nf" + process "CUSTOM_SHORTENFASTAIDS" + + tag "modules" + tag "modules_gallvp" + tag "custom" + tag "custom/shortenfastaids" + + test("homo_sapiens-genome_fasta-no_change") { + + when { + process { + """ + input[0] = [ + [ id:'test' ], + file(params.modules_testdata_base_path + 'genomics/homo_sapiens/genome/genome.fasta', checkIfExists: true) + ] + """ + } + } + + then { + assertAll( + { assert process.success }, + { assert snapshot(process.out).match() } + ) + } + + } + + test("sarscov2-genome_fasta-pattern_change") { + + when { + process { + """ + input[0] = [ + [ id:'test' ], + file(params.modules_testdata_base_path + 'genomics/sarscov2/genome/genome.fasta', checkIfExists: true) + ] + """ + } + } + + then { + assertAll( + { assert process.success }, + { assert snapshot(process.out).match() } + ) + } + + } + + test("homo_sapiens-genome2_fasta-length_change") { + + when { + process { + """ + input[0] = [ + [ id:'test' ], + file(params.modules_testdata_base_path + 'genomics/homo_sapiens/genome/genome2.fasta', checkIfExists: true) + ] + """ + } + } + + then { + assertAll( + { assert process.success }, + { assert snapshot(process.out).match() } + ) + } + + } + + test("custom_fasta-comment_change") { + + when { + process { + """ + input[0] = Channel.of('>Chr1 This is a test comment', 'AGCTAGCT') + | collectFile(name: 'sample.fasta', newLine: true) + | map { file -> [ [ id:'test' ], file ] } + """ + } + } + + then { + assertAll( + { assert process.success }, + { assert snapshot(process.out).match() } + ) + } + + } + + test("stub") { + + options "-stub" + + when { + process { + """ + input[0] = [ + [ id:'test' ], + file(params.modules_testdata_base_path + 'genomics/homo_sapiens/genome/genome.fasta', checkIfExists: true) + ] + """ + } + } + + then { + assertAll( + { assert process.success }, + { assert snapshot(process.out).match() } + ) + } + + } + +} \ No newline at end of file diff --git a/modules/gallvp/custom/shortenfastaids/tests/main.nf.test.snap b/modules/gallvp/custom/shortenfastaids/tests/main.nf.test.snap new file mode 100644 index 0000000..2506ebd --- /dev/null +++ b/modules/gallvp/custom/shortenfastaids/tests/main.nf.test.snap @@ -0,0 +1,227 @@ +{ + "custom_fasta-comment_change": { + "content": [ + { + "0": [ + [ + { + "id": "test" + }, + "test.short.ids.fasta:md5,c861b9d46a4d9bdba66953cff572fc5d" + ] + ], + "1": [ + [ + { + "id": "test" + }, + "test.short.ids.tsv:md5,8762f2bffbdff75c2812bad72ba52bba" + ] + ], + "2": [ + "versions.yml:md5,e5704a53ebea373dac3a93ae800d48ba" + ], + "short_ids_fasta": [ + [ + { + "id": "test" + }, + "test.short.ids.fasta:md5,c861b9d46a4d9bdba66953cff572fc5d" + ] + ], + "short_ids_tsv": [ + [ + { + "id": "test" + }, + "test.short.ids.tsv:md5,8762f2bffbdff75c2812bad72ba52bba" + ] + ], + "versions": [ + "versions.yml:md5,e5704a53ebea373dac3a93ae800d48ba" + ] + } + ], + "meta": { + "nf-test": "0.8.4", + "nextflow": "23.10.1" + }, + "timestamp": "2023-12-07T13:33:05.523745" + }, + "stub": { + "content": [ + { + "0": [ + + ], + "1": [ + [ + { + "id": "test" + }, + "test.short.ids.tsv:md5,fcf920d9a7b57a1e3c29a9e88673330f" + ] + ], + "2": [ + "versions.yml:md5,e5704a53ebea373dac3a93ae800d48ba" + ], + "short_ids_fasta": [ + + ], + "short_ids_tsv": [ + [ + { + "id": "test" + }, + "test.short.ids.tsv:md5,fcf920d9a7b57a1e3c29a9e88673330f" + ] + ], + "versions": [ + "versions.yml:md5,e5704a53ebea373dac3a93ae800d48ba" + ] + } + ], + "meta": { + "nf-test": "0.8.4", + "nextflow": "24.04.2" + }, + "timestamp": "2024-06-04T13:58:30.161542" + }, + "homo_sapiens-genome_fasta-no_change": { + "content": [ + { + "0": [ + + ], + "1": [ + [ + { + "id": "test" + }, + "test.short.ids.tsv:md5,642382addc4beba37088b1ebe09d38cf" + ] + ], + "2": [ + "versions.yml:md5,e5704a53ebea373dac3a93ae800d48ba" + ], + "short_ids_fasta": [ + + ], + "short_ids_tsv": [ + [ + { + "id": "test" + }, + "test.short.ids.tsv:md5,642382addc4beba37088b1ebe09d38cf" + ] + ], + "versions": [ + "versions.yml:md5,e5704a53ebea373dac3a93ae800d48ba" + ] + } + ], + "meta": { + "nf-test": "0.8.4", + "nextflow": "24.04.1" + }, + "timestamp": "2024-06-02T20:54:17.945233" + }, + "homo_sapiens-genome2_fasta-length_change": { + "content": [ + { + "0": [ + [ + { + "id": "test" + }, + "test.short.ids.fasta:md5,1382acd98d4cd233a8062ef01b2aaa6d" + ] + ], + "1": [ + [ + { + "id": "test" + }, + "test.short.ids.tsv:md5,99c0f2a529cb595b2d8530024ed2880e" + ] + ], + "2": [ + "versions.yml:md5,e5704a53ebea373dac3a93ae800d48ba" + ], + "short_ids_fasta": [ + [ + { + "id": "test" + }, + "test.short.ids.fasta:md5,1382acd98d4cd233a8062ef01b2aaa6d" + ] + ], + "short_ids_tsv": [ + [ + { + "id": "test" + }, + "test.short.ids.tsv:md5,99c0f2a529cb595b2d8530024ed2880e" + ] + ], + "versions": [ + "versions.yml:md5,e5704a53ebea373dac3a93ae800d48ba" + ] + } + ], + "meta": { + "nf-test": "0.8.4", + "nextflow": "23.10.1" + }, + "timestamp": "2023-12-07T13:33:01.924483" + }, + "sarscov2-genome_fasta-pattern_change": { + "content": [ + { + "0": [ + [ + { + "id": "test" + }, + "test.short.ids.fasta:md5,14d6f587b6d28889c5c0f985e78d602f" + ] + ], + "1": [ + [ + { + "id": "test" + }, + "test.short.ids.tsv:md5,d7a2af88e8549586e5616bff6a88bd71" + ] + ], + "2": [ + "versions.yml:md5,e5704a53ebea373dac3a93ae800d48ba" + ], + "short_ids_fasta": [ + [ + { + "id": "test" + }, + "test.short.ids.fasta:md5,14d6f587b6d28889c5c0f985e78d602f" + ] + ], + "short_ids_tsv": [ + [ + { + "id": "test" + }, + "test.short.ids.tsv:md5,d7a2af88e8549586e5616bff6a88bd71" + ] + ], + "versions": [ + "versions.yml:md5,e5704a53ebea373dac3a93ae800d48ba" + ] + } + ], + "meta": { + "nf-test": "0.8.4", + "nextflow": "23.10.1" + }, + "timestamp": "2023-12-07T13:32:58.12885" + } +} \ No newline at end of file diff --git a/modules/gallvp/tirlearner/main.nf b/modules/gallvp/tirlearner/main.nf index c253245..2e0809d 100644 --- a/modules/gallvp/tirlearner/main.nf +++ b/modules/gallvp/tirlearner/main.nf @@ -50,12 +50,11 @@ process TIRLEARNER { cat <<-END_VERSIONS > versions.yml "${task.process}": - TIR-Learner: \$(TIR-Learner -v | head -n 1 | sed 's/TIR-Learner //') + TIR-Learner: \$(TIR-Learner -v | sed -n 's|TIR-Learner \\(.*\\) by .*|\\1|p') END_VERSIONS """ stub: - def args = task.ext.args ?: '' prefix = task.ext.prefix ?: "${meta.id}" if ( "$fasta" == "${prefix}.fa" ) error "Input and output names are the same, use \"task.ext.prefix\" to disambiguate!" """ @@ -69,7 +68,7 @@ process TIRLEARNER { cat <<-END_VERSIONS > versions.yml "${task.process}": - TIR-Learner: \$(TIR-Learner -v | head -n 1 | sed 's/TIR-Learner //') + TIR-Learner: \$(TIR-Learner -v | sed -n 's|TIR-Learner \\(.*\\) by .*|\\1|p') END_VERSIONS """ } diff --git a/modules/gallvp/tirlearner/tests/main.nf.test.snap b/modules/gallvp/tirlearner/tests/main.nf.test.snap index dd1a6d0..e6b52a0 100644 --- a/modules/gallvp/tirlearner/tests/main.nf.test.snap +++ b/modules/gallvp/tirlearner/tests/main.nf.test.snap @@ -2,14 +2,14 @@ "sarscov2-genome": { "content": [ [ - "versions.yml:md5,9db74013377c09d8452530315592acdf" + "versions.yml:md5,054eb77998e320f8b70cae832f7bff59" ] ], "meta": { - "nf-test": "0.8.4", + "nf-test": "0.9.2", "nextflow": "24.04.4" }, - "timestamp": "2024-08-22T13:03:25.394783" + "timestamp": "2024-12-17T15:23:12.222704" }, "actinidia_chinensis-genome_1_fasta_gz-success": { "content": [ @@ -46,14 +46,14 @@ ] ], [ - "versions.yml:md5,9db74013377c09d8452530315592acdf" + "versions.yml:md5,054eb77998e320f8b70cae832f7bff59" ] ], "meta": { - "nf-test": "0.8.4", + "nf-test": "0.9.2", "nextflow": "24.04.4" }, - "timestamp": "2024-08-22T13:21:08.136874" + "timestamp": "2024-12-17T15:22:50.978766" }, "stub": { "content": [ @@ -99,7 +99,7 @@ ] ], "5": [ - "versions.yml:md5,9db74013377c09d8452530315592acdf" + "versions.yml:md5,054eb77998e320f8b70cae832f7bff59" ], "fasta": [ [ @@ -142,14 +142,14 @@ ] ], "versions": [ - "versions.yml:md5,9db74013377c09d8452530315592acdf" + "versions.yml:md5,054eb77998e320f8b70cae832f7bff59" ] } ], "meta": { - "nf-test": "0.8.4", + "nf-test": "0.9.2", "nextflow": "24.04.4" }, - "timestamp": "2024-08-22T12:56:16.022839" + "timestamp": "2024-12-17T15:23:25.302007" } } \ No newline at end of file diff --git a/modules/local/sanitize/main.nf b/modules/local/sanitize/main.nf deleted file mode 100644 index c1519af..0000000 --- a/modules/local/sanitize/main.nf +++ /dev/null @@ -1,38 +0,0 @@ -// Rename FASTA headers (just makes everything easier later) - -process SANITIZE_HEADERS { - tag "$meta.id" - label 'process_single' - - // Eventually port fffx (pronounced f3x) to bioconda - // conda "${moduleDir}/environment.yml" - // container "docker.io/gallvp/edta-components:v0.1" - - container "${ workflow.containerEngine == 'singularity' || workflow.containerEngine == 'apptainer' - ? 'https://depot.galaxyproject.org/singularity/ubuntu:20.04' - : 'nf-core/ubuntu:20.04' }" - - input: - tuple val(meta), path(fasta) - - output: - tuple val(meta), path('*.sanitized.fasta'), emit: fasta - tuple val(meta), path('*.sanitized.translation_table.tsv'), emit: translation_table - eval('fffx --version'), topic: versions - - - when: - task.ext.when == null || task.ext.when - - script: - """ - fffx length-filter ${fasta} filtered.fa 1000 - fffx sanitize filtered.fa ${fasta.baseName}.sanitized - """ - - stub: - """ - touch ${fasta.baseName}.sanitized.fasta - touch ${fasta.baseName}.sanitized.translation_table.tsv - """ -} \ No newline at end of file diff --git a/tests/nf-test/small/main.nf.test b/tests/nf-test/small/main.nf.test index 43e207c..87b44cb 100644 --- a/tests/nf-test/small/main.nf.test +++ b/tests/nf-test/small/main.nf.test @@ -7,7 +7,7 @@ nextflow_pipeline { when { params { - genome = "https://raw.githubusercontent.com/jguhlin/EDTA/a2cd9a0777e4ac6e39545bacc3e752f94eb2f389/test/genome.fa" + genome = "https://raw.githubusercontent.com/oushujun/EDTA/4f0424b8a3ce2caccdd51b4021c61a536fa9ead9/test/genome.fa" outdir = "$outputDir" } } @@ -19,18 +19,9 @@ nextflow_pipeline { false, [ 'pipeline_info/*.{html,json,txt,yml}', - 'raw/ltr/*.LTR.intact.gff3', - 'raw/ltr/*.LTR.fa', - 'raw/tir/*.TIR.intact.fa', - 'raw/tir/*.TIR.intact.gff3', - 'raw/line/*.LINE.fa', - 'raw/line/*.RM2.fa', - 'raw/helitron/*.Helitron.intact.fa', - 'raw/helitron/*.Helitron.intact.anno.list', - 'raw/helitron/*.Helitron.intact.gff3', - 'raw/intact/*', - 'stg1/*.fasta', - 'final/*' + 'genome/*.intact.fa', + 'genome/*.intact.gff3', + 'genome/*.TElib.fa' ], null, ['**'] @@ -46,6 +37,9 @@ nextflow_pipeline { ['**'] ) + def te_lib_count = file("$outputDir/genome/genome.TElib.fa").readLines().findAll { it.startsWith('>') }.size() + def intact_lib_count = file("$outputDir/genome/genome.intact.fa").readLines().findAll { it.startsWith('>') }.size() + assertAll( { assert workflow.success}, { assert snapshot( @@ -53,9 +47,11 @@ nextflow_pipeline { 'successful tasks': workflow.trace.succeeded().size(), 'versions': removeNextflowVersion("$outputDir/pipeline_info/software_versions.yml"), 'stable paths': stable_path, - 'stable names': getRelativePath(stable_name, outputDir), + 'stable names': getRelativePath(stable_name, outputDir) ] - ).match() } + ).match() }, + { assert Math.abs(te_lib_count - 45) <= 5.0 }, + { assert Math.abs(intact_lib_count - 42) <= 5.0 } ) } diff --git a/tests/nf-test/small/main.nf.test.snap b/tests/nf-test/small/main.nf.test.snap index dccc812..06527a9 100644 --- a/tests/nf-test/small/main.nf.test.snap +++ b/tests/nf-test/small/main.nf.test.snap @@ -13,6 +13,10 @@ "COMBINE_INTACT_TES": { "perl": "v5.32.1" }, + "CUSTOM_SHORTENFASTAIDS": { + "python": "3.8.13", + "biopython": 1.75 + }, "FINAL_FILTER": { "perl": "v5.32.1", "repeatmasker": "4.1.7-p1" @@ -81,7 +85,7 @@ "repeatmodeler": "2.0.5" }, "TIRLEARNER": { - "TIR-Learner": "v3.0.2 by Tianyu (Sky) Lu (tlu83@wisc.edu) published under GPLv3" + "TIR-Learner": "v3.0.2" }, "TIR_LEARNER_POSTPROCESS": { "perl": "v5.32.1", @@ -94,40 +98,15 @@ } }, "stable paths": [ - "genome.Helitron.intact.bed:md5,a8b170a1ee8d36c7f8e3af5ba1e43cb6", - "genome.LTR.intact.anno.list:md5,51261f020350b448acdf89d8fb625448", - "genome.LTR.intact.fa:md5,ae2336f498897d1af4e8d3be28bb3f25", - "genome.TIR.intact.bed:md5,12406184046b11edc2ddadbc82765966" + "genome.short.ids.tsv:md5,642382addc4beba37088b1ebe09d38cf" ], "stable names": [ - "final", - "final/genome.TElib.fa", - "final/genome.intact.fa", - "final/genome.intact.gff3", - "pipeline_info", - "raw", - "raw/helitron", - "raw/helitron/genome.Helitron.intact.anno.list", - "raw/helitron/genome.Helitron.intact.bed", - "raw/helitron/genome.Helitron.intact.fa", - "raw/helitron/genome.Helitron.intact.gff3", - "raw/intact", - "raw/intact/genome.intact.fa", - "raw/intact/genome.intact.gff3", - "raw/line", - "raw/line/genome.RM2.fa", - "raw/ltr", - "raw/ltr/genome.LTR.fa", - "raw/ltr/genome.LTR.intact.anno.list", - "raw/ltr/genome.LTR.intact.fa", - "raw/ltr/genome.LTR.intact.gff3", - "raw/tir", - "raw/tir/genome.TIR.intact.bed", - "raw/tir/genome.TIR.intact.fa", - "raw/tir/genome.TIR.intact.gff3", - "stg1", - "stg1/genome.fasta", - "stg1/genome.intact.fasta" + "genome", + "genome/genome.TElib.fa", + "genome/genome.intact.fa", + "genome/genome.intact.gff3", + "genome/genome.short.ids.tsv", + "pipeline_info" ] } ], @@ -135,6 +114,6 @@ "nf-test": "0.9.2", "nextflow": "24.04.4" }, - "timestamp": "2024-12-17T14:02:22.882318" + "timestamp": "2024-12-17T22:28:58.604502" } } \ No newline at end of file diff --git a/tests/nf-test/tiny/main.nf.test b/tests/nf-test/tiny/main.nf.test index 0a92bd5..2b90162 100644 --- a/tests/nf-test/tiny/main.nf.test +++ b/tests/nf-test/tiny/main.nf.test @@ -13,7 +13,8 @@ nextflow_pipeline { } then { - def stable_path = getAllFilesFromDir(params.outdir, false, ['pipeline_info/*.{html,json,txt,yml}'], null, ['**']) + def stable_path = getAllFilesFromDir ( params.outdir, false, ['pipeline_info/*.{html,json,txt,yml}'], null, ['**'] ) + def stable_name = getAllFilesFromDir ( params.outdir, true, [ 'pipeline_info/*.{html,json,txt,yml}' ], null, ['**'] ) assertAll( { assert workflow.success}, @@ -21,7 +22,8 @@ nextflow_pipeline { [ 'successful tasks': workflow.trace.succeeded().size(), 'versions': removeNextflowVersion("$outputDir/pipeline_info/software_versions.yml"), - 'stable paths': stable_path + 'stable paths': stable_path, + 'stable names': getRelativePath(stable_name, outputDir) ] ).match() } ) diff --git a/tests/nf-test/tiny/main.nf.test.snap b/tests/nf-test/tiny/main.nf.test.snap index 896e0b8..40e5592 100644 --- a/tests/nf-test/tiny/main.nf.test.snap +++ b/tests/nf-test/tiny/main.nf.test.snap @@ -10,6 +10,10 @@ "CAT_CAT": { "pigz": "2.3.4" }, + "CUSTOM_SHORTENFASTAIDS": { + "python": "3.8.13", + "biopython": 1.75 + }, "FORMAT_HELITRONSCANNER_OUT": { "perl": "v5.32.1" }, @@ -46,14 +50,19 @@ "LTR_retriever": "v2.9.9" }, "TIRLEARNER": { - "TIR-Learner": "v3.0.2 by Tianyu (Sky) Lu (tlu83@wisc.edu) published under GPLv3" + "TIR-Learner": "v3.0.2" }, "Workflow": { "oushujun/EDTA": "v0.1.0dev" } }, "stable paths": [ - + "genome.short.ids.tsv:md5,d7a2af88e8549586e5616bff6a88bd71" + ], + "stable names": [ + "genome", + "genome/genome.short.ids.tsv", + "pipeline_info" ] } ], @@ -61,6 +70,6 @@ "nf-test": "0.9.2", "nextflow": "24.04.4" }, - "timestamp": "2024-12-13T09:54:27.064885" + "timestamp": "2024-12-17T17:06:09.728972" } } \ No newline at end of file diff --git a/workflows/edta.nf b/workflows/edta.nf index 1f13998..1d9bb26 100644 --- a/workflows/edta.nf +++ b/workflows/edta.nf @@ -1,4 +1,6 @@ -include { SANITIZE_HEADERS } from '../modules/local/sanitize/main' +include { GUNZIP } from '../modules/gallvp/gunzip/main' +include { CUSTOM_SHORTENFASTAIDS } from '../modules/gallvp/custom/shortenfastaids/main' +include { CUSTOM_RESTOREGFFIDS } from '../modules/gallvp/custom/restoregffids/main' include { LTRHARVEST } from '../modules/nf-core/ltrharvest/main' include { LTRFINDER } from '../modules/nf-core/ltrfinder/main' @@ -36,18 +38,49 @@ workflow EDTA { ch_versions = Channel.empty() - ch_genome = Channel.fromPath(params.genome) + ch_genome_branch = Channel.fromPath(params.genome) | map { genome -> def meta = [:] meta.id = idFromFileName ( genome.baseName ) [ meta, genome ] } + | branch { _meta, archive -> + gz: "$archive".endsWith('.gz') + rest: ! "$archive".endsWith('.gz') + } + + // MODULE: GUNZIP + GUNZIP ( ch_genome_branch.gz ) - // MODULE: SANITIZE_HEADERS - SANITIZE_HEADERS ( ch_genome ) + ch_genome = GUNZIP.out.gunzip.mix(ch_genome_branch.rest) + ch_versions = ch_versions.mix(GUNZIP.out.versions.first()) - ch_sanitized_fasta = SANITIZE_HEADERS.out.fasta + // MODULE: CUSTOM_SHORTENFASTAIDS + CUSTOM_SHORTENFASTAIDS ( ch_genome ) + + ch_short_ids_tsv = CUSTOM_SHORTENFASTAIDS.out.short_ids_tsv + ch_versions = ch_versions.mix(CUSTOM_SHORTENFASTAIDS.out.versions.first()) + + + ch_shortenfastaids_branch = ch_short_ids_tsv + | branch { _meta, tsv -> + change: ! tsv.text.contains('IDs have acceptable length and character') + nochange: tsv.text.contains('IDs have acceptable length and character') + } + + ch_sanitized_fasta = ch_shortenfastaids_branch.nochange + | join( + ch_genome + ) + | map { meta, _tsv, fasta -> [ meta + [ changed_ids: false ], fasta ] } + | mix( + ch_shortenfastaids_branch.change + | join( + CUSTOM_SHORTENFASTAIDS.out.short_ids_fasta + ) + | map { meta, _tsv, fasta -> [ meta + [ changed_ids: true ], fasta ] } + ) // MODULE: LTRHARVEST LTRHARVEST ( ch_sanitized_fasta ) @@ -344,8 +377,23 @@ workflow EDTA { ch_final_filter_inputs.intact_gff, ) + ch_intact_gff = FINAL_FILTER.out.intact_gff ch_versions = ch_versions.mix(FINAL_FILTER.out.versions.first()) + // MODULE: CUSTOM_RESTOREGFFIDS + ch_gff_tsv_branch = ch_intact_gff.join(ch_short_ids_tsv) + | branch { meta, _gff, _tsv -> + change: meta.changed_ids + nochange: ! meta.changed_ids + } + + CUSTOM_RESTOREGFFIDS ( + ch_gff_tsv_branch.change.map { meta, gff, _tsv -> [ meta, gff ] }, + ch_gff_tsv_branch.change.map { _meta, _gff, tsv -> tsv } + ) + + ch_versions = ch_versions.mix(CUSTOM_RESTOREGFFIDS.out.versions.first()) + // Function: Save versions ch_versions = ch_versions | unique From 438b78f94eb2fe3e4d67d6656a293292e908f925 Mon Sep 17 00:00:00 2001 From: Usman Rashid Date: Wed, 18 Dec 2024 11:11:09 +1300 Subject: [PATCH 46/49] Added parameter validation by nf-schema --- .github/PULL_REQUEST_TEMPLATE.md | 12 +- .github/workflows/ci.yml | 2 +- .gitignore | 1 + cleanNXF.sh | 3 + conf/base.config | 49 ++ conf/modules.config | 30 +- conf/test.config | 4 +- conf/test_full.config | 11 + main.nf | 25 +- modules.json | 19 + modules/local/utils/main.nf | 65 --- nextflow.config | 255 ++++++++--- nextflow_schema.json | 93 ++++ .../local/utils_nfcore_edta_pipeline/main.nf | 114 +++++ .../nf-core/utils_nextflow_pipeline/main.nf | 126 ++++++ .../nf-core/utils_nextflow_pipeline/meta.yml | 38 ++ .../tests/main.function.nf.test | 54 +++ .../tests/main.function.nf.test.snap | 20 + .../tests/main.workflow.nf.test | 113 +++++ .../tests/nextflow.config | 9 + .../utils_nextflow_pipeline/tests/tags.yml | 2 + .../nf-core/utils_nfcore_pipeline/main.nf | 419 ++++++++++++++++++ .../nf-core/utils_nfcore_pipeline/meta.yml | 24 + .../tests/main.function.nf.test | 126 ++++++ .../tests/main.function.nf.test.snap | 136 ++++++ .../tests/main.workflow.nf.test | 29 ++ .../tests/main.workflow.nf.test.snap | 19 + .../tests/nextflow.config | 9 + .../utils_nfcore_pipeline/tests/tags.yml | 2 + .../nf-core/utils_nfschema_plugin/main.nf | 46 ++ .../nf-core/utils_nfschema_plugin/meta.yml | 35 ++ .../utils_nfschema_plugin/tests/main.nf.test | 117 +++++ .../tests/nextflow.config | 8 + .../tests/nextflow_schema.json | 96 ++++ workflows/edta.nf | 14 +- 35 files changed, 1948 insertions(+), 177 deletions(-) create mode 100644 conf/base.config create mode 100644 conf/test_full.config delete mode 100644 modules/local/utils/main.nf create mode 100644 nextflow_schema.json create mode 100644 subworkflows/local/utils_nfcore_edta_pipeline/main.nf create mode 100644 subworkflows/nf-core/utils_nextflow_pipeline/main.nf create mode 100644 subworkflows/nf-core/utils_nextflow_pipeline/meta.yml create mode 100644 subworkflows/nf-core/utils_nextflow_pipeline/tests/main.function.nf.test create mode 100644 subworkflows/nf-core/utils_nextflow_pipeline/tests/main.function.nf.test.snap create mode 100644 subworkflows/nf-core/utils_nextflow_pipeline/tests/main.workflow.nf.test create mode 100644 subworkflows/nf-core/utils_nextflow_pipeline/tests/nextflow.config create mode 100644 subworkflows/nf-core/utils_nextflow_pipeline/tests/tags.yml create mode 100644 subworkflows/nf-core/utils_nfcore_pipeline/main.nf create mode 100644 subworkflows/nf-core/utils_nfcore_pipeline/meta.yml create mode 100644 subworkflows/nf-core/utils_nfcore_pipeline/tests/main.function.nf.test create mode 100644 subworkflows/nf-core/utils_nfcore_pipeline/tests/main.function.nf.test.snap create mode 100644 subworkflows/nf-core/utils_nfcore_pipeline/tests/main.workflow.nf.test create mode 100644 subworkflows/nf-core/utils_nfcore_pipeline/tests/main.workflow.nf.test.snap create mode 100644 subworkflows/nf-core/utils_nfcore_pipeline/tests/nextflow.config create mode 100644 subworkflows/nf-core/utils_nfcore_pipeline/tests/tags.yml create mode 100644 subworkflows/nf-core/utils_nfschema_plugin/main.nf create mode 100644 subworkflows/nf-core/utils_nfschema_plugin/meta.yml create mode 100644 subworkflows/nf-core/utils_nfschema_plugin/tests/main.nf.test create mode 100644 subworkflows/nf-core/utils_nfschema_plugin/tests/nextflow.config create mode 100644 subworkflows/nf-core/utils_nfschema_plugin/tests/nextflow_schema.json diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index 7919df0..eb3ed74 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -9,12 +9,6 @@ These are the most common things requested on pull requests (PRs). ## PR checklist -For Nextflow implementation, - -- [ ] `conda` and `container` directives are included for each process -- [ ] Docker container + singularity container (optional) are included for each process -- [ ] Flow `meta.id` with each data channel -- [ ] Use nf-core resource labels such as `process_high` -- [ ] Used nf-core module -- [ ] Use `versions.yml` or versions topic -- [ ] No process in the `main.nf`. We can have a process in a sub-workflow file +- [ ] This comment contains a description of changes (with reason). +- [ ] If you've fixed a bug or added code that should be tested, add tests! +- [ ] Ensure the test suite passes (`nextflow run . -profile test,docker --outdir `). diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index d53140c..3a33d22 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -46,7 +46,7 @@ jobs: fail-fast: false matrix: NXF_VER: - - "24.04.2" + - "24.10.3" nf_test_files: ["${{ fromJson(needs.nf-test-changes.outputs.nf_test_files) }}"] profile: [conda, docker, singularity] diff --git a/.gitignore b/.gitignore index f636970..3a9b5c2 100644 --- a/.gitignore +++ b/.gitignore @@ -5,3 +5,4 @@ work/ .nextflow/* results/ .nf-test* +null/ diff --git a/cleanNXF.sh b/cleanNXF.sh index 81b8d85..9752c09 100755 --- a/cleanNXF.sh +++ b/cleanNXF.sh @@ -13,3 +13,6 @@ echo "Cleaned work..." rm -f .nf-test.log rm -rf .nf-test echo "Cleaned nf-test..." + +rm -rf null +echo "Cleaned null..." diff --git a/conf/base.config b/conf/base.config new file mode 100644 index 0000000..643430d --- /dev/null +++ b/conf/base.config @@ -0,0 +1,49 @@ +process { + + cpus = { 1 * task.attempt } + memory = { 6.GB * task.attempt } + time = { 4.h * task.attempt } + + errorStrategy = { task.exitStatus in ((130..145) + 104) ? 'retry' : 'finish' } + maxRetries = 1 + maxErrors = '-1' + + // Process-specific resource requirements + // NOTE - Please try and re-use the labels below as much as possible. + // These labels are used and recognised by default in DSL2 files hosted on nf-core/modules. + // If possible, it would be nice to keep the same label naming convention when + // adding in your local modules too. + withLabel:process_single { + cpus = { 1 } + memory = { 6.GB * task.attempt } + time = { 4.h * task.attempt } + } + withLabel:process_low { + cpus = { 2 * task.attempt } + memory = { 12.GB * task.attempt } + time = { 4.h * task.attempt } + } + withLabel:process_medium { + cpus = { 6 * task.attempt } + memory = { 36.GB * task.attempt } + time = { 8.h * task.attempt } + } + withLabel:process_high { + cpus = { 12 * task.attempt } + memory = { 72.GB * task.attempt } + time = { 16.h * task.attempt } + } + withLabel:process_long { + time = { 20.h * task.attempt } + } + withLabel:process_high_memory { + memory = { 200.GB * task.attempt } + } + withLabel:error_ignore { + errorStrategy = 'ignore' + } + withLabel:error_retry { + errorStrategy = 'retry' + maxRetries = 2 + } +} \ No newline at end of file diff --git a/conf/modules.config b/conf/modules.config index 0bce9d9..47b5804 100644 --- a/conf/modules.config +++ b/conf/modules.config @@ -1,6 +1,6 @@ process { - withName: 'EDTA:CUSTOM_SHORTENFASTAIDS' { + withName: 'OUSHUJUN_EDTA:EDTA:CUSTOM_SHORTENFASTAIDS' { publishDir = [ path: { "${params.outdir}/${meta.id}" }, mode: params.publish_dir_mode, @@ -8,60 +8,60 @@ process { ] } - withName: 'EDTA:LTRHARVEST' { + withName: 'OUSHUJUN_EDTA:EDTA:LTRHARVEST' { ext.args = '-size 1000000 -time 300' ext.prefix = { "${meta.id}_ltrharvest" } } - withName: 'EDTA:LTRFINDER' { + withName: 'OUSHUJUN_EDTA:EDTA:LTRFINDER' { ext.args = '-harvest_out -size 1000000 -time 300' } - withName: 'EDTA:CAT_CAT' { + withName: 'OUSHUJUN_EDTA:EDTA:CAT_CAT' { ext.prefix = { "${meta.id}_ltrharvest_ltrfinder.tabout" } } - withName: 'EDTA:ANNOSINE' { + withName: 'OUSHUJUN_EDTA:EDTA:ANNOSINE' { ext.prefix = { "${meta.id}.annosine" } ext.args = params.annosine_ext_args ?: '-a 2 --num_alignments 50000 -rpm 0 --copy_number 3 --shift 100 -auto 1' } - withName: 'EDTA:REPEATMODELER_REPEATMODELER' { + withName: 'OUSHUJUN_EDTA:EDTA:REPEATMODELER_REPEATMODELER' { ext.args = '-engine ncbi' } - withName: 'EDTA:TIRLEARNER' { + withName: 'OUSHUJUN_EDTA:EDTA:TIRLEARNER' { ext.prefix = { "${meta.id}.tirlearner" } } - withName: 'EDTA:FASTA_HELITRONSCANNER_SCAN_DRAW:HELITRONSCANNER_DRAW' { + withName: 'OUSHUJUN_EDTA:EDTA:FASTA_HELITRONSCANNER_SCAN_DRAW:HELITRONSCANNER_DRAW' { ext.args = '-pure_helitron' } - withName: 'EDTA:FASTA_HELITRONSCANNER_SCAN_DRAW:HELITRONSCANNER_SCAN_HEAD_RC' { + withName: 'OUSHUJUN_EDTA:EDTA:FASTA_HELITRONSCANNER_SCAN_DRAW:HELITRONSCANNER_SCAN_HEAD_RC' { ext.prefix = { "${meta.id}.rc" } ext.args = '--rc' } - withName: 'EDTA:FASTA_HELITRONSCANNER_SCAN_DRAW:HELITRONSCANNER_SCAN_TAIL_RC' { + withName: 'OUSHUJUN_EDTA:EDTA:FASTA_HELITRONSCANNER_SCAN_DRAW:HELITRONSCANNER_SCAN_TAIL_RC' { ext.prefix = { "${meta.id}.rc" } ext.args = '--rc' } - withName: 'EDTA:FASTA_HELITRONSCANNER_SCAN_DRAW:HELITRONSCANNER_DRAW_RC' { + withName: 'OUSHUJUN_EDTA:EDTA:FASTA_HELITRONSCANNER_SCAN_DRAW:HELITRONSCANNER_DRAW_RC' { ext.prefix = { "${meta.id}.rc" } ext.args = '-pure_helitron' } - withName: 'EDTA:FORMAT_HELITRONSCANNER_OUT' { + withName: 'OUSHUJUN_EDTA:EDTA:FORMAT_HELITRONSCANNER_OUT' { ext.args = '-sitefilter 1 -minscore 12 -keepshorter 1 -extout 0' } - withName: 'EDTA:FORMAT_HELITRONSCANNER_OUT_EXT' { + withName: 'OUSHUJUN_EDTA:EDTA:FORMAT_HELITRONSCANNER_OUT_EXT' { ext.args = '-sitefilter 1 -minscore 12 -keepshorter 1 -extlen 30 -extout 1' } - withName: 'EDTA:FINAL_FILTER' { + withName: 'OUSHUJUN_EDTA:EDTA:FINAL_FILTER' { publishDir = [ path: { "${params.outdir}/${meta.id}" }, mode: params.publish_dir_mode, @@ -76,7 +76,7 @@ process { ] } - withName: 'EDTA:CUSTOM_RESTOREGFFIDS' { + withName: 'OUSHUJUN_EDTA:EDTA:CUSTOM_RESTOREGFFIDS' { publishDir = [ path: { "${params.outdir}/${meta.id}" }, mode: params.publish_dir_mode, diff --git a/conf/test.config b/conf/test.config index 6d0509d..ce76591 100644 --- a/conf/test.config +++ b/conf/test.config @@ -4,8 +4,8 @@ params { process { resourceLimits = [ - cpus: 10, - memory: '32.GB', + cpus: 4, + memory: '15.GB', time: '1.h' ] } diff --git a/conf/test_full.config b/conf/test_full.config new file mode 100644 index 0000000..6d0509d --- /dev/null +++ b/conf/test_full.config @@ -0,0 +1,11 @@ +params { + genome = 'https://raw.githubusercontent.com/oushujun/EDTA/4f0424b8a3ce2caccdd51b4021c61a536fa9ead9/test/genome.fa' +} + +process { + resourceLimits = [ + cpus: 10, + memory: '32.GB', + time: '1.h' + ] +} diff --git a/main.nf b/main.nf index a50616d..3a9c8ce 100755 --- a/main.nf +++ b/main.nf @@ -2,8 +2,27 @@ nextflow.enable.dsl = 2 -include { EDTA } from './workflows/edta.nf' +include { PIPELINE_INITIALISATION } from './subworkflows/local/utils_nfcore_edta_pipeline' +include { EDTA } from './workflows/edta.nf' -workflow { - EDTA() +workflow OUSHUJUN_EDTA { + + take: + ch_genome + + main: + EDTA ( ch_genome ) } + +workflow { + + main: + PIPELINE_INITIALISATION ( + params.version, + args, + params.outdir, + params.genome + ) + + OUSHUJUN_EDTA ( PIPELINE_INITIALISATION.out.genome ) +} \ No newline at end of file diff --git a/modules.json b/modules.json index b012c3e..772b7c4 100644 --- a/modules.json +++ b/modules.json @@ -91,6 +91,25 @@ "installed_by": ["modules"] } } + }, + "subworkflows": { + "nf-core": { + "utils_nextflow_pipeline": { + "branch": "master", + "git_sha": "c2b22d85f30a706a3073387f30380704fcae013b", + "installed_by": ["subworkflows"] + }, + "utils_nfcore_pipeline": { + "branch": "master", + "git_sha": "51ae5406a030d4da1e49e4dab49756844fdd6c7a", + "installed_by": ["subworkflows"] + }, + "utils_nfschema_plugin": { + "branch": "master", + "git_sha": "2fd2cd6d0e7b273747f32e465fdc6bcc3ae0814e", + "installed_by": ["subworkflows"] + } + } } } } diff --git a/modules/local/utils/main.nf b/modules/local/utils/main.nf deleted file mode 100644 index dea2b26..0000000 --- a/modules/local/utils/main.nf +++ /dev/null @@ -1,65 +0,0 @@ -// -// Generate workflow version string -// -def getWorkflowVersion() { - def version_string = "" as String - if (workflow.manifest.version) { - def prefix_v = workflow.manifest.version[0] != 'v' ? 'v' : '' - version_string += "${prefix_v}${workflow.manifest.version}" - } - - if (workflow.commitId) { - def git_shortsha = workflow.commitId.substring(0, 7) - version_string += "-g${git_shortsha}" - } - - return version_string -} - -// -// Get software versions for pipeline -// -def processVersionsFromYAML(yaml_file) { - def yaml = new org.yaml.snakeyaml.Yaml() - def versions = yaml.load(yaml_file).collectEntries { k, v -> [k.tokenize(':')[-1], v] } - return yaml.dumpAsMap(versions).trim() -} - -// -// Get workflow version for pipeline -// -def workflowVersionToYAML() { - return """ - Workflow: - ${workflow.manifest.name}: ${getWorkflowVersion()} - Nextflow: ${workflow.nextflow.version} - """.stripIndent().trim() -} - -// -// Get channel of software versions used in pipeline in YAML format -// -def softwareVersionsToYAML(ch_versions) { - return ch_versions.unique().map { version -> processVersionsFromYAML(version) }.unique().mix(Channel.of(workflowVersionToYAML())) -} - -// -// Gen a working sample ID from a file name -// -def idFromFileName(fileName) { - - def trial = ( fileName - ).replaceFirst( - /\.f(ast)?q$/, '' - ).replaceFirst( - /\.f(asta|sa|a|as|aa|na)?$/, '' - ).replaceFirst( - /\.gff(3)?$/, '' - ).replaceFirst( - /\.gz$/, '' - ) - - if ( trial == fileName ) { return fileName } - - return idFromFileName ( trial ) -} diff --git a/nextflow.config b/nextflow.config index 25a0b75..c9c7aa1 100644 --- a/nextflow.config +++ b/nextflow.config @@ -1,111 +1,220 @@ +// Global default params, used in configs params { - genome = 'genomes/*' - species = 'others' - outdir = 'results' + // Input/output options + genome = null + species = 'others' + outdir = null - annosine_ext_args = null + // CLI arguments for tools + annosine_ext_args = null - publish_dir_mode = 'copy' -} - -process { - - cpus = { 1 * task.attempt } - memory = { 6.GB * task.attempt } - time = { 4.h * task.attempt } - - errorStrategy = { task.exitStatus in ((130..145) + 104) ? 'retry' : 'finish' } - maxRetries = 1 - maxErrors = '-1' - - withLabel:process_single { - cpus = { 1 } - memory = { 6.GB * task.attempt } - time = { 4.h * task.attempt } - } - withLabel:process_low { - cpus = { 2 * task.attempt } - memory = { 12.GB * task.attempt } - time = { 4.h * task.attempt } - } - withLabel:process_medium { - cpus = { 6 * task.attempt } - memory = { 36.GB * task.attempt } - time = { 8.h * task.attempt } - } - withLabel:process_high { - cpus = { 12 * task.attempt } - memory = { 72.GB * task.attempt } - time = { 16.h * task.attempt } - } - withLabel:process_long { - time = { 20.h * task.attempt } - } - withLabel:process_high_memory { - memory = { 200.GB * task.attempt } - } - withLabel:error_ignore { - errorStrategy = 'ignore' - } - withLabel:error_retry { - errorStrategy = 'retry' - maxRetries = 2 - } + // Boilerplate options + version = false + publish_dir_mode = 'copy' } // Max resources process { resourceLimits = [ - cpus: 12, + cpus: 16, memory: '72.GB', - time: '7.days' + time: '7.day' ] } +// Load base.config by default for all pipelines +includeConfig 'conf/base.config' + profiles { - singularity { - singularity.enabled = true - singularity.autoMounts = true - } - apptainer { - apptainer.enabled = true - apptainer.autoMounts = true + debug { + dumpHashes = true + process.beforeScript = 'echo $HOSTNAME' + cleanup = false + nextflow.enable.configProcessNamesValidation = true } conda { conda.enabled = true + docker.enabled = false + singularity.enabled = false + podman.enabled = false + shifter.enabled = false + charliecloud.enabled = false + conda.channels = ['conda-forge', 'bioconda'] + apptainer.enabled = false } mamba { conda.enabled = true conda.useMamba = true - } - podman { - podman.enabled = true - podman.userEmulation = true - podman.runOptions = "--runtime crun --platform linux/x86_64 --systemd=always" + docker.enabled = false + singularity.enabled = false + podman.enabled = false + shifter.enabled = false + charliecloud.enabled = false + apptainer.enabled = false } docker { docker.enabled = true + conda.enabled = false + singularity.enabled = false + podman.enabled = false + shifter.enabled = false + charliecloud.enabled = false + apptainer.enabled = false + docker.runOptions = '-u $(id -u):$(id -g)' + } + arm { docker.runOptions = '-u $(id -u):$(id -g) --platform=linux/amd64' } - test { includeConfig './conf/test.config' } + singularity { + singularity.enabled = true + singularity.autoMounts = true + conda.enabled = false + docker.enabled = false + podman.enabled = false + shifter.enabled = false + charliecloud.enabled = false + apptainer.enabled = false + } + podman { + podman.enabled = true + conda.enabled = false + docker.enabled = false + singularity.enabled = false + shifter.enabled = false + charliecloud.enabled = false + apptainer.enabled = false + } + shifter { + shifter.enabled = true + conda.enabled = false + docker.enabled = false + singularity.enabled = false + podman.enabled = false + charliecloud.enabled = false + apptainer.enabled = false + } + charliecloud { + charliecloud.enabled = true + conda.enabled = false + docker.enabled = false + singularity.enabled = false + podman.enabled = false + shifter.enabled = false + apptainer.enabled = false + } + apptainer { + apptainer.enabled = true + apptainer.autoMounts = true + conda.enabled = false + docker.enabled = false + singularity.enabled = false + podman.enabled = false + shifter.enabled = false + charliecloud.enabled = false + } + wave { + apptainer.ociAutoPull = true + singularity.ociAutoPull = true + wave.enabled = true + wave.freeze = true + wave.strategy = 'conda,container' + } + gitpod { + executor.name = 'local' + executor.cpus = 4 + executor.memory = 8.GB + } + test { includeConfig 'conf/test.config' } + test_full { includeConfig 'conf/test_full.config' } } +apptainer.registry = 'quay.io' docker.registry = 'quay.io' podman.registry = 'quay.io' singularity.registry = 'quay.io' -apptainer.registry = 'quay.io' +charliecloud.registry = 'quay.io' -// Increase time available to build Conda environment -conda { - createTimeout = "120 min" +env { + PYTHONNOUSERSITE = 1 + R_PROFILE_USER = "/.Rprofile" + R_ENVIRON_USER = "/.Renviron" + JULIA_DEPOT_PATH = "/usr/local/share/julia" +} + +process.shell = """\ +bash + +set -e # Exit if a tool returns a non-zero status/exit code +set -u # Treat unset variables and parameters as an error +set -o pipefail # Returns the status of the last command to exit with a non-zero status or zero if all successfully execute +set -C # No clobber - prevent output redirection from overwriting files. +""" + +nextflow.enable.configProcessNamesValidation = false + +def trace_timestamp = new java.util.Date().format( 'yyyy-MM-dd_HH-mm-ss') +timeline { + enabled = true + file = "${params.outdir}/pipeline_info/execution_timeline_${trace_timestamp}.html" +} +report { + enabled = true + file = "${params.outdir}/pipeline_info/execution_report_${trace_timestamp}.html" +} +trace { + enabled = true + file = "${params.outdir}/pipeline_info/execution_trace_${trace_timestamp}.txt" +} +dag { + enabled = true + file = "${params.outdir}/pipeline_info/pipeline_dag_${trace_timestamp}.html" } manifest { name = 'oushujun/EDTA' - description = 'Extensive de-novo TE Annotator on Nextflow' - author = 'Shujun Ou, Usman Rashid & Joseph Guhlin' - version = '0.1.0dev' - nextflowVersion = '!>=23.04.0' + description = 'Extensive de-novo TE Annotator' + version = '3.0.0dev' + nextflowVersion = '!>=24.10.3' + contributors = [ + [ + name : 'Shujun Ou', + affiliation : 'Ohio State University', + github : 'https://github.com/oushujun', + contribution : 'author', + orcid : 'https://orcid.org/0000-0001-5938-7180' + ], + [ + name : 'Usman Rashid', + affiliation : 'Plant & Food Research', + github : 'https://github.com/GallVp', + contribution : 'contributor', + orcid : 'https://orcid.org/0000-0002-1109-5493' + ], + [ + name : 'Joseph Guhlin', + affiliation : 'Dunedin, New Zealand', + github : 'https://github.com/jguhlin', + contribution : 'contributor', + orcid : 'https://orcid.org/0000-0003-3264-7178' + ] + ] +} + +// Nextflow plugins +plugins { + id 'nf-schema@2.1.1' // Validation of pipeline parameters and creation of an input channel from a sample sheet +} + +validation { + defaultIgnoreParams = ["genomes"] + help { + enabled = true + command = "nextflow run $manifest.name -profile --genome --outdir " + fullParameter = "help_full" + showHiddenParameter = "show_hidden" + + } } +// Load modules.config for DSL2 module specific options includeConfig 'conf/modules.config' diff --git a/nextflow_schema.json b/nextflow_schema.json new file mode 100644 index 0000000..825a9fb --- /dev/null +++ b/nextflow_schema.json @@ -0,0 +1,93 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://raw.githubusercontent.com/EDTA/master/nextflow_schema.json", + "title": "EDTA pipeline parameters", + "description": "Extensive de-novo TE Annotator", + "type": "object", + "$defs": { + "input_output_options": { + "title": "Input/output options", + "type": "object", + "fa_icon": "fas fa-terminal", + "description": "Define where the pipeline should find input data and save output data.", + "required": ["genome", "outdir"], + "properties": { + "genome": { + "type": "string", + "fa_icon": "fas fa-dna", + "description": "Path to a genome Fasta file or to a text file containing paths to Fasta files", + "pattern": "^\\S+\\.f(a|asta|as|sa|na)(\\.gz)?$|^\\S+\\.txt$", + "format": "file-path" + }, + "species": { + "type": "string", + "default": "others", + "fa_icon": "fas fa-otter", + "description": "Specify the species for identification of TIR candidates", + "enum": ["Rice", "Maize", "others"] + }, + "outdir": { + "type": "string", + "format": "directory-path", + "description": "The output directory where the results will be saved. You have to use absolute paths to storage on Cloud infrastructure.", + "fa_icon": "fas fa-folder-open" + } + } + }, + "cli_arguments_for_tools": { + "title": "CLI arguments for tools", + "type": "object", + "description": "Arguments to pass to tools", + "default": "", + "properties": { + "annosine_ext_args": { + "type": "string", + "fa_icon": "fas fa-terminal", + "description": "Arguments for AnnoSINE" + } + }, + "fa_icon": "fas fa-wrench" + }, + "generic_options": { + "title": "Generic options", + "type": "object", + "fa_icon": "fas fa-file-import", + "description": "Less common options for the pipeline, typically set in a config file.", + "help_text": "These options are common to all nf-core pipelines and allow you to customise some of the core preferences for how the pipeline runs.\n\nTypically these options would be set in a Nextflow config file loaded for all pipeline runs, such as `~/.nextflow/config`.", + "properties": { + "version": { + "type": "boolean", + "description": "Display version and exit.", + "fa_icon": "fas fa-question-circle", + "hidden": true + }, + "publish_dir_mode": { + "type": "string", + "default": "copy", + "description": "Method used to save pipeline results to output directory.", + "fa_icon": "fas fa-copy", + "enum": [ + "symlink", + "rellink", + "link", + "copy", + "copyNoFollow", + "move" + ], + "hidden": true + } + } + } + }, + "allOf": [ + { + "$ref": "#/$defs/input_output_options" + }, + { + "$ref": "#/$defs/cli_arguments_for_tools" + }, + { + "$ref": "#/$defs/generic_options" + } + ] +} diff --git a/subworkflows/local/utils_nfcore_edta_pipeline/main.nf b/subworkflows/local/utils_nfcore_edta_pipeline/main.nf new file mode 100644 index 0000000..77e5a66 --- /dev/null +++ b/subworkflows/local/utils_nfcore_edta_pipeline/main.nf @@ -0,0 +1,114 @@ +include { UTILS_NFSCHEMA_PLUGIN } from '../../nf-core/utils_nfschema_plugin' +include { paramsSummaryMap } from 'plugin/nf-schema' +include { samplesheetToList } from 'plugin/nf-schema' +include { completionEmail } from '../../nf-core/utils_nfcore_pipeline' +include { completionSummary } from '../../nf-core/utils_nfcore_pipeline' +include { imNotification } from '../../nf-core/utils_nfcore_pipeline' +include { UTILS_NFCORE_PIPELINE } from '../../nf-core/utils_nfcore_pipeline' +include { UTILS_NEXTFLOW_PIPELINE } from '../../nf-core/utils_nextflow_pipeline' + +workflow PIPELINE_INITIALISATION { + + take: + version // boolean: Display version and exit + nextflow_cli_args // array: List of positional nextflow CLI args + outdir // string: The output directory where the results will be saved + genome // string: Path to a genome fasta or a text file with a list of fasta files + + main: + + // + // Print version and exit if required and dump pipeline parameters to JSON file + // + UTILS_NEXTFLOW_PIPELINE ( + version, + true, + outdir, + workflow.profile.tokenize(',').intersect(['conda', 'mamba']).size() >= 1 + ) + + // + // Validate parameters and generate parameter summary to stdout + // + UTILS_NFSCHEMA_PLUGIN ( + workflow, + true, // validate params + null + ) + + // + // Create input channels + // + ch_genome = genome.find(/^\S+\.f(a|asta|as|sa|na)(\.gz)?$/) + ? ( + // Fasta + Channel.fromPath(genome) + | map { fasta -> + def fa_file = file(fasta, checkIfExists: true) + + def meta = [:] + meta.id = idFromFileName ( fa_file.baseName ) + + [ meta, fa_file ] + } + ) + : ( + // Text file + Channel.fromPath(genome) + | map { text -> + def file_list = file(text, checkIfExists: true).readLines() + + if ( file_list == [] ) { + error "File list provided by --genome is empty" + } + + file_list + .collect { fasta -> + def fa_file = file(fasta, checkIfExists: true) + + def meta = [:] + meta.id = idFromFileName ( fa_file.baseName ) + + [ meta, fa_file ] + } + } + | flatten + | buffer ( size: 2 ) + ) + | groupTuple + | view + | map { meta, fastas -> validateFastaMetadata ( meta, fastas ) } + + emit: + genome = ch_genome +} + +// +// Additional validation +// +def idFromFileName(fileName) { + + def trial = ( fileName + ).replaceFirst( + /\.f(ast)?q$/, '' + ).replaceFirst( + /\.f(asta|sa|a|as|aa|na)?$/, '' + ).replaceFirst( + /\.gff(3)?$/, '' + ).replaceFirst( + /\.gz$/, '' + ) + + if ( trial == fileName ) { return fileName } + + return idFromFileName ( trial ) +} + +def validateFastaMetadata(meta, fastas) { + + if ( fastas.size() > 1 ) { + error "Multiple Fasta files have the same ID '${meta.id}'. Make sure all the files are unique and change file names to avoid this conflict!" + } + + [ meta, fastas.first() ] +} diff --git a/subworkflows/nf-core/utils_nextflow_pipeline/main.nf b/subworkflows/nf-core/utils_nextflow_pipeline/main.nf new file mode 100644 index 0000000..d6e593e --- /dev/null +++ b/subworkflows/nf-core/utils_nextflow_pipeline/main.nf @@ -0,0 +1,126 @@ +// +// Subworkflow with functionality that may be useful for any Nextflow pipeline +// + +/* +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + SUBWORKFLOW DEFINITION +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +*/ + +workflow UTILS_NEXTFLOW_PIPELINE { + take: + print_version // boolean: print version + dump_parameters // boolean: dump parameters + outdir // path: base directory used to publish pipeline results + check_conda_channels // boolean: check conda channels + + main: + + // + // Print workflow version and exit on --version + // + if (print_version) { + log.info("${workflow.manifest.name} ${getWorkflowVersion()}") + System.exit(0) + } + + // + // Dump pipeline parameters to a JSON file + // + if (dump_parameters && outdir) { + dumpParametersToJSON(outdir) + } + + // + // When running with Conda, warn if channels have not been set-up appropriately + // + if (check_conda_channels) { + checkCondaChannels() + } + + emit: + dummy_emit = true +} + +/* +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + FUNCTIONS +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +*/ + +// +// Generate version string +// +def getWorkflowVersion() { + def version_string = "" as String + if (workflow.manifest.version) { + def prefix_v = workflow.manifest.version[0] != 'v' ? 'v' : '' + version_string += "${prefix_v}${workflow.manifest.version}" + } + + if (workflow.commitId) { + def git_shortsha = workflow.commitId.substring(0, 7) + version_string += "-g${git_shortsha}" + } + + return version_string +} + +// +// Dump pipeline parameters to a JSON file +// +def dumpParametersToJSON(outdir) { + def timestamp = new java.util.Date().format('yyyy-MM-dd_HH-mm-ss') + def filename = "params_${timestamp}.json" + def temp_pf = new File(workflow.launchDir.toString(), ".${filename}") + def jsonStr = groovy.json.JsonOutput.toJson(params) + temp_pf.text = groovy.json.JsonOutput.prettyPrint(jsonStr) + + nextflow.extension.FilesEx.copyTo(temp_pf.toPath(), "${outdir}/pipeline_info/params_${timestamp}.json") + temp_pf.delete() +} + +// +// When running with -profile conda, warn if channels have not been set-up appropriately +// +def checkCondaChannels() { + def parser = new org.yaml.snakeyaml.Yaml() + def channels = [] + try { + def config = parser.load("conda config --show channels".execute().text) + channels = config.channels + } + catch (NullPointerException e) { + log.debug(e) + log.warn("Could not verify conda channel configuration.") + return null + } + catch (IOException e) { + log.debug(e) + log.warn("Could not verify conda channel configuration.") + return null + } + + // Check that all channels are present + // This channel list is ordered by required channel priority. + def required_channels_in_order = ['conda-forge', 'bioconda'] + def channels_missing = ((required_channels_in_order as Set) - (channels as Set)) as Boolean + + // Check that they are in the right order + def channel_priority_violation = required_channels_in_order != channels.findAll { ch -> ch in required_channels_in_order } + + if (channels_missing | channel_priority_violation) { + log.warn """\ + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + There is a problem with your Conda configuration! + You will need to set-up the conda-forge and bioconda channels correctly. + Please refer to https://bioconda.github.io/ + The observed channel order is + ${channels} + but the following channel order is required: + ${required_channels_in_order} + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~" + """.stripIndent(true) + } +} diff --git a/subworkflows/nf-core/utils_nextflow_pipeline/meta.yml b/subworkflows/nf-core/utils_nextflow_pipeline/meta.yml new file mode 100644 index 0000000..e5c3a0a --- /dev/null +++ b/subworkflows/nf-core/utils_nextflow_pipeline/meta.yml @@ -0,0 +1,38 @@ +# yaml-language-server: $schema=https://raw.githubusercontent.com/nf-core/modules/master/subworkflows/yaml-schema.json +name: "UTILS_NEXTFLOW_PIPELINE" +description: Subworkflow with functionality that may be useful for any Nextflow pipeline +keywords: + - utility + - pipeline + - initialise + - version +components: [] +input: + - print_version: + type: boolean + description: | + Print the version of the pipeline and exit + - dump_parameters: + type: boolean + description: | + Dump the parameters of the pipeline to a JSON file + - output_directory: + type: directory + description: Path to output dir to write JSON file to. + pattern: "results/" + - check_conda_channel: + type: boolean + description: | + Check if the conda channel priority is correct. +output: + - dummy_emit: + type: boolean + description: | + Dummy emit to make nf-core subworkflows lint happy +authors: + - "@adamrtalbot" + - "@drpatelh" +maintainers: + - "@adamrtalbot" + - "@drpatelh" + - "@maxulysse" diff --git a/subworkflows/nf-core/utils_nextflow_pipeline/tests/main.function.nf.test b/subworkflows/nf-core/utils_nextflow_pipeline/tests/main.function.nf.test new file mode 100644 index 0000000..68718e4 --- /dev/null +++ b/subworkflows/nf-core/utils_nextflow_pipeline/tests/main.function.nf.test @@ -0,0 +1,54 @@ + +nextflow_function { + + name "Test Functions" + script "subworkflows/nf-core/utils_nextflow_pipeline/main.nf" + config "subworkflows/nf-core/utils_nextflow_pipeline/tests/nextflow.config" + tag 'subworkflows' + tag 'utils_nextflow_pipeline' + tag 'subworkflows/utils_nextflow_pipeline' + + test("Test Function getWorkflowVersion") { + + function "getWorkflowVersion" + + then { + assertAll( + { assert function.success }, + { assert snapshot(function.result).match() } + ) + } + } + + test("Test Function dumpParametersToJSON") { + + function "dumpParametersToJSON" + + when { + function { + """ + // define inputs of the function here. Example: + input[0] = "$outputDir" + """.stripIndent() + } + } + + then { + assertAll( + { assert function.success } + ) + } + } + + test("Test Function checkCondaChannels") { + + function "checkCondaChannels" + + then { + assertAll( + { assert function.success }, + { assert snapshot(function.result).match() } + ) + } + } +} diff --git a/subworkflows/nf-core/utils_nextflow_pipeline/tests/main.function.nf.test.snap b/subworkflows/nf-core/utils_nextflow_pipeline/tests/main.function.nf.test.snap new file mode 100644 index 0000000..e3f0baf --- /dev/null +++ b/subworkflows/nf-core/utils_nextflow_pipeline/tests/main.function.nf.test.snap @@ -0,0 +1,20 @@ +{ + "Test Function getWorkflowVersion": { + "content": [ + "v9.9.9" + ], + "meta": { + "nf-test": "0.8.4", + "nextflow": "23.10.1" + }, + "timestamp": "2024-02-28T12:02:05.308243" + }, + "Test Function checkCondaChannels": { + "content": null, + "meta": { + "nf-test": "0.8.4", + "nextflow": "23.10.1" + }, + "timestamp": "2024-02-28T12:02:12.425833" + } +} \ No newline at end of file diff --git a/subworkflows/nf-core/utils_nextflow_pipeline/tests/main.workflow.nf.test b/subworkflows/nf-core/utils_nextflow_pipeline/tests/main.workflow.nf.test new file mode 100644 index 0000000..02dbf09 --- /dev/null +++ b/subworkflows/nf-core/utils_nextflow_pipeline/tests/main.workflow.nf.test @@ -0,0 +1,113 @@ +nextflow_workflow { + + name "Test Workflow UTILS_NEXTFLOW_PIPELINE" + script "../main.nf" + config "subworkflows/nf-core/utils_nextflow_pipeline/tests/nextflow.config" + workflow "UTILS_NEXTFLOW_PIPELINE" + tag 'subworkflows' + tag 'utils_nextflow_pipeline' + tag 'subworkflows/utils_nextflow_pipeline' + + test("Should run no inputs") { + + when { + workflow { + """ + print_version = false + dump_parameters = false + outdir = null + check_conda_channels = false + + input[0] = print_version + input[1] = dump_parameters + input[2] = outdir + input[3] = check_conda_channels + """ + } + } + + then { + assertAll( + { assert workflow.success } + ) + } + } + + test("Should print version") { + + when { + workflow { + """ + print_version = true + dump_parameters = false + outdir = null + check_conda_channels = false + + input[0] = print_version + input[1] = dump_parameters + input[2] = outdir + input[3] = check_conda_channels + """ + } + } + + then { + expect { + with(workflow) { + assert success + assert "nextflow_workflow v9.9.9" in stdout + } + } + } + } + + test("Should dump params") { + + when { + workflow { + """ + print_version = false + dump_parameters = true + outdir = 'results' + check_conda_channels = false + + input[0] = false + input[1] = true + input[2] = outdir + input[3] = false + """ + } + } + + then { + assertAll( + { assert workflow.success } + ) + } + } + + test("Should not create params JSON if no output directory") { + + when { + workflow { + """ + print_version = false + dump_parameters = true + outdir = null + check_conda_channels = false + + input[0] = false + input[1] = true + input[2] = outdir + input[3] = false + """ + } + } + + then { + assertAll( + { assert workflow.success } + ) + } + } +} diff --git a/subworkflows/nf-core/utils_nextflow_pipeline/tests/nextflow.config b/subworkflows/nf-core/utils_nextflow_pipeline/tests/nextflow.config new file mode 100644 index 0000000..a09572e --- /dev/null +++ b/subworkflows/nf-core/utils_nextflow_pipeline/tests/nextflow.config @@ -0,0 +1,9 @@ +manifest { + name = 'nextflow_workflow' + author = """nf-core""" + homePage = 'https://127.0.0.1' + description = """Dummy pipeline""" + nextflowVersion = '!>=23.04.0' + version = '9.9.9' + doi = 'https://doi.org/10.5281/zenodo.5070524' +} diff --git a/subworkflows/nf-core/utils_nextflow_pipeline/tests/tags.yml b/subworkflows/nf-core/utils_nextflow_pipeline/tests/tags.yml new file mode 100644 index 0000000..f847611 --- /dev/null +++ b/subworkflows/nf-core/utils_nextflow_pipeline/tests/tags.yml @@ -0,0 +1,2 @@ +subworkflows/utils_nextflow_pipeline: + - subworkflows/nf-core/utils_nextflow_pipeline/** diff --git a/subworkflows/nf-core/utils_nfcore_pipeline/main.nf b/subworkflows/nf-core/utils_nfcore_pipeline/main.nf new file mode 100644 index 0000000..bfd2587 --- /dev/null +++ b/subworkflows/nf-core/utils_nfcore_pipeline/main.nf @@ -0,0 +1,419 @@ +// +// Subworkflow with utility functions specific to the nf-core pipeline template +// + +/* +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + SUBWORKFLOW DEFINITION +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +*/ + +workflow UTILS_NFCORE_PIPELINE { + take: + nextflow_cli_args + + main: + valid_config = checkConfigProvided() + checkProfileProvided(nextflow_cli_args) + + emit: + valid_config +} + +/* +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + FUNCTIONS +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +*/ + +// +// Warn if a -profile or Nextflow config has not been provided to run the pipeline +// +def checkConfigProvided() { + def valid_config = true as Boolean + if (workflow.profile == 'standard' && workflow.configFiles.size() <= 1) { + log.warn( + "[${workflow.manifest.name}] You are attempting to run the pipeline without any custom configuration!\n\n" + "This will be dependent on your local compute environment but can be achieved via one or more of the following:\n" + " (1) Using an existing pipeline profile e.g. `-profile docker` or `-profile singularity`\n" + " (2) Using an existing nf-core/configs for your Institution e.g. `-profile crick` or `-profile uppmax`\n" + " (3) Using your own local custom config e.g. `-c /path/to/your/custom.config`\n\n" + "Please refer to the quick start section and usage docs for the pipeline.\n " + ) + valid_config = false + } + return valid_config +} + +// +// Exit pipeline if --profile contains spaces +// +def checkProfileProvided(nextflow_cli_args) { + if (workflow.profile.endsWith(',')) { + error( + "The `-profile` option cannot end with a trailing comma, please remove it and re-run the pipeline!\n" + "HINT: A common mistake is to provide multiple values separated by spaces e.g. `-profile test, docker`.\n" + ) + } + if (nextflow_cli_args[0]) { + log.warn( + "nf-core pipelines do not accept positional arguments. The positional argument `${nextflow_cli_args[0]}` has been detected.\n" + "HINT: A common mistake is to provide multiple values separated by spaces e.g. `-profile test, docker`.\n" + ) + } +} + +// +// Generate workflow version string +// +def getWorkflowVersion() { + def version_string = "" as String + if (workflow.manifest.version) { + def prefix_v = workflow.manifest.version[0] != 'v' ? 'v' : '' + version_string += "${prefix_v}${workflow.manifest.version}" + } + + if (workflow.commitId) { + def git_shortsha = workflow.commitId.substring(0, 7) + version_string += "-g${git_shortsha}" + } + + return version_string +} + +// +// Get software versions for pipeline +// +def processVersionsFromYAML(yaml_file) { + def yaml = new org.yaml.snakeyaml.Yaml() + def versions = yaml.load(yaml_file).collectEntries { k, v -> [k.tokenize(':')[-1], v] } + return yaml.dumpAsMap(versions).trim() +} + +// +// Get workflow version for pipeline +// +def workflowVersionToYAML() { + return """ + Workflow: + ${workflow.manifest.name}: ${getWorkflowVersion()} + Nextflow: ${workflow.nextflow.version} + """.stripIndent().trim() +} + +// +// Get channel of software versions used in pipeline in YAML format +// +def softwareVersionsToYAML(ch_versions) { + return ch_versions.unique().map { version -> processVersionsFromYAML(version) }.unique().mix(Channel.of(workflowVersionToYAML())) +} + +// +// Get workflow summary for MultiQC +// +def paramsSummaryMultiqc(summary_params) { + def summary_section = '' + summary_params + .keySet() + .each { group -> + def group_params = summary_params.get(group) + // This gets the parameters of that particular group + if (group_params) { + summary_section += "