diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index 4a9bc5c..b290e09 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -10,19 +10,11 @@ "vscode": { // Set *default* container specific settings.json values on container create. "settings": { - "python.defaultInterpreterPath": "/opt/conda/bin/python", - "python.linting.enabled": true, - "python.linting.pylintEnabled": true, - "python.formatting.autopep8Path": "/opt/conda/bin/autopep8", - "python.formatting.yapfPath": "/opt/conda/bin/yapf", - "python.linting.flake8Path": "/opt/conda/bin/flake8", - "python.linting.pycodestylePath": "/opt/conda/bin/pycodestyle", - "python.linting.pydocstylePath": "/opt/conda/bin/pydocstyle", - "python.linting.pylintPath": "/opt/conda/bin/pylint", + "python.defaultInterpreterPath": "/opt/conda/bin/python" }, // Add the IDs of extensions you want installed when the container is created. - "extensions": ["ms-python.python", "ms-python.vscode-pylance", "nf-core.nf-core-extensionpack"], - }, - }, + "extensions": ["ms-python.python", "ms-python.vscode-pylance", "nf-core.nf-core-extensionpack"] + } + } } diff --git a/.editorconfig b/.editorconfig index b6b3190..6d9b74c 100644 --- a/.editorconfig +++ b/.editorconfig @@ -18,7 +18,20 @@ end_of_line = unset insert_final_newline = unset trim_trailing_whitespace = unset indent_style = unset -indent_size = unset +[/subworkflows/nf-core/**] +charset = unset +end_of_line = unset +insert_final_newline = unset +trim_trailing_whitespace = unset +indent_style = unset [/assets/email*] indent_size = unset + +# ignore python and markdown +[*.{py,md}] +indent_style = unset + +# ignore ro-crate metadata files +[**/ro-crate-metadata.json] +insert_final_newline = unset diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md index 7f7789a..e6c99e0 100644 --- a/.github/CONTRIBUTING.md +++ b/.github/CONTRIBUTING.md @@ -1,4 +1,4 @@ -# nf-core/molkart: Contributing Guidelines +# `nf-core/molkart`: Contributing Guidelines Hi there! Many thanks for taking an interest in improving nf-core/molkart. @@ -9,9 +9,8 @@ Please use the pre-filled template to save time. However, don't be put off by this template - other more general issues and suggestions are welcome! Contributions to the code are even more welcome ;) -:::info -If you need help using or modifying nf-core/molkart then the best place to ask is on the nf-core Slack [#molkart](https://nfcore.slack.com/channels/molkart) channel ([join our Slack here](https://nf-co.re/join/slack)). -::: +> [!NOTE] +> If you need help using or modifying nf-core/molkart then the best place to ask is on the nf-core Slack [#molkart](https://nfcore.slack.com/channels/molkart) channel ([join our Slack here](https://nf-co.re/join/slack)). ## Contribution workflow @@ -20,15 +19,18 @@ If you'd like to write some code for nf-core/molkart, the standard workflow is a 1. Check that there isn't already an issue about your idea in the [nf-core/molkart issues](https://github.com/nf-core/molkart/issues) to avoid duplicating work. If there isn't one already, please create one so that others know you're working on this 2. [Fork](https://help.github.com/en/github/getting-started-with-github/fork-a-repo) the [nf-core/molkart repository](https://github.com/nf-core/molkart) to your GitHub account 3. Make the necessary changes / additions within your forked repository following [Pipeline conventions](#pipeline-contribution-conventions) -4. Use `nf-core schema build` and add any new parameters to the pipeline JSON schema (requires [nf-core tools](https://github.com/nf-core/tools) >= 1.10). +4. Use `nf-core pipelines schema build` and add any new parameters to the pipeline JSON schema (requires [nf-core tools](https://github.com/nf-core/tools) >= 1.10). 5. Submit a Pull Request against the `dev` branch and wait for the code to be reviewed and merged If you're not used to this workflow with git, you can start with some [docs from GitHub](https://help.github.com/en/github/collaborating-with-issues-and-pull-requests) or even their [excellent `git` resources](https://try.github.io/). ## Tests -You can optionally test your changes by running the pipeline locally. Then it is recommended to use the `debug` profile to -receive warnings about process selectors and other debug info. Example: `nextflow run . -profile debug,test,docker --outdir `. +You have the option to test your changes locally by running the pipeline. For receiving warnings about process selectors and other `debug` information, it is recommended to use the debug profile. Execute all the tests with the following command: + +```bash +nf-test test --profile debug,test,docker --verbose +``` When you create a pull request with changes, [GitHub Actions](https://github.com/features/actions) will run automatic tests. Typically, pull-requests are only fully reviewed when these tests are passing, though of course we can help out before then. @@ -38,7 +40,7 @@ There are typically two types of tests that run: ### Lint tests `nf-core` has a [set of guidelines](https://nf-co.re/developers/guidelines) which all pipelines must adhere to. -To enforce these and ensure that all pipelines stay in sync, we have developed a helper tool which runs checks on the pipeline code. This is in the [nf-core/tools repository](https://github.com/nf-core/tools) and once installed can be run locally with the `nf-core lint ` command. +To enforce these and ensure that all pipelines stay in sync, we have developed a helper tool which runs checks on the pipeline code. This is in the [nf-core/tools repository](https://github.com/nf-core/tools) and once installed can be run locally with the `nf-core pipelines lint ` command. If any failures or warnings are encountered, please follow the listed URL for more documentation. @@ -53,9 +55,9 @@ These tests are run both with the latest available version of `Nextflow` and als :warning: Only in the unlikely and regretful event of a release happening with a bug. -- On your own fork, make a new branch `patch` based on `upstream/master`. +- On your own fork, make a new branch `patch` based on `upstream/main` or `upstream/master`. - Fix the bug, and bump version (X.Y.Z+1). -- A PR should be made on `master` from patch to directly this particular bug. +- Open a pull-request from `patch` to `main`/`master` with the changes. ## Getting help @@ -63,17 +65,17 @@ For further information/help, please consult the [nf-core/molkart documentation] ## Pipeline contribution conventions -To make the nf-core/molkart code and processing logic more understandable for new contributors and to ensure quality, we semi-standardise the way the code and other contributions are written. +To make the `nf-core/molkart` code and processing logic more understandable for new contributors and to ensure quality, we semi-standardise the way the code and other contributions are written. ### Adding a new step If you wish to contribute a new step, please use the following coding standards: -1. Define the corresponding input channel into your new process from the expected previous process channel +1. Define the corresponding input channel into your new process from the expected previous process channel. 2. Write the process block (see below). 3. Define the output channel if needed (see below). 4. Add any new parameters to `nextflow.config` with a default (see below). -5. Add any new parameters to `nextflow_schema.json` with help text (via the `nf-core schema build` tool). +5. Add any new parameters to `nextflow_schema.json` with help text (via the `nf-core pipelines schema build` tool). 6. Add sanity checks and validation for all relevant parameters. 7. Perform local tests to validate that the new code works as expected. 8. If applicable, add a new test command in `.github/workflow/ci.yml`. @@ -82,15 +84,15 @@ If you wish to contribute a new step, please use the following coding standards: ### Default values -Parameters should be initialised / defined with default values in `nextflow.config` under the `params` scope. +Parameters should be initialised / defined with default values within the `params` scope in `nextflow.config`. -Once there, use `nf-core schema build` to add to `nextflow_schema.json`. +Once there, use `nf-core pipelines schema build` to add to `nextflow_schema.json`. ### Default processes resource requirements -Sensible defaults for process resource requirements (CPUs / memory / time) for a process should be defined in `conf/base.config`. These should generally be specified generic with `withLabel:` selectors so they can be shared across multiple processes/steps of the pipeline. A nf-core standard set of labels that should be followed where possible can be seen in the [nf-core pipeline template](https://github.com/nf-core/tools/blob/master/nf_core/pipeline-template/conf/base.config), which has the default process as a single core-process, and then different levels of multi-core configurations for increasingly large memory requirements defined with standardised labels. +Sensible defaults for process resource requirements (CPUs / memory / time) for a process should be defined in `conf/base.config`. These should generally be specified generic with `withLabel:` selectors so they can be shared across multiple processes/steps of the pipeline. A nf-core standard set of labels that should be followed where possible can be seen in the [nf-core pipeline template](https://github.com/nf-core/tools/blob/main/nf_core/pipeline-template/conf/base.config), which has the default process as a single core-process, and then different levels of multi-core configurations for increasingly large memory requirements defined with standardised labels. -The process resources can be passed on to the tool dynamically within the process with the `${task.cpu}` and `${task.memory}` variables in the `script:` block. +The process resources can be passed on to the tool dynamically within the process with the `${task.cpus}` and `${task.memory}` variables in the `script:` block. ### Naming schemes @@ -101,7 +103,7 @@ Please use the following naming schemes, to make it easy to understand what is g ### Nextflow version bumping -If you are using a new feature from core Nextflow, you may bump the minimum required version of nextflow in the pipeline with: `nf-core bump-version --nextflow . [min-nf-version]` +If you are using a new feature from core Nextflow, you may bump the minimum required version of nextflow in the pipeline with: `nf-core pipelines bump-version --nextflow . [min-nf-version]` ### Images and figures diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml index 959f14a..ed01393 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.yml +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -9,7 +9,6 @@ body: - [nf-core website: troubleshooting](https://nf-co.re/usage/troubleshooting) - [nf-core/molkart pipeline documentation](https://nf-co.re/molkart/usage) - - type: textarea id: description attributes: diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index 284d2f6..813f41c 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -17,7 +17,7 @@ Learn more about contributing: [CONTRIBUTING.md](https://github.com/nf-core/molk - [ ] If you've fixed a bug or added code that should be tested, add tests! - [ ] If you've added a new tool - have you followed the pipeline conventions in the [contribution docs](https://github.com/nf-core/molkart/tree/master/.github/CONTRIBUTING.md) - [ ] If necessary, also make a PR on the nf-core/molkart _branch_ on the [nf-core/test-datasets](https://github.com/nf-core/test-datasets) repository. -- [ ] Make sure your code lints (`nf-core lint`). +- [ ] Make sure your code lints (`nf-core pipelines lint`). - [ ] Ensure the test suite passes (`nextflow run . -profile test,docker --outdir `). - [ ] Check for unexpected warnings in debug mode (`nextflow run . -profile debug,test,docker --outdir `). - [ ] Usage Documentation in `docs/usage.md` is updated. diff --git a/.github/workflows/awsfulltest.yml b/.github/workflows/awsfulltest.yml index 176bdb5..cd3d10a 100644 --- a/.github/workflows/awsfulltest.yml +++ b/.github/workflows/awsfulltest.yml @@ -1,19 +1,49 @@ name: nf-core AWS full size tests -# This workflow is triggered on published releases. +# This workflow is triggered on PRs opened against the main/master branch. # It can be additionally triggered manually with GitHub actions workflow dispatch button. # It runs the -profile 'test_full' on AWS batch on: - release: - types: [published] + pull_request: + branches: + - main + - master workflow_dispatch: + pull_request_review: + types: [submitted] + jobs: - run-tower: + run-platform: name: Run AWS full tests - if: github.repository == 'nf-core/molkart' + # run only if the PR is approved by at least 2 reviewers and against the master branch or manually triggered + if: github.repository == 'nf-core/molkart' && github.event.review.state == 'approved' && github.event.pull_request.base.ref == 'master' || github.event_name == 'workflow_dispatch' runs-on: ubuntu-latest steps: - - name: Launch workflow via tower + - name: Get PR reviews + uses: octokit/request-action@v2.x + if: github.event_name != 'workflow_dispatch' + id: check_approvals + continue-on-error: true + with: + route: GET /repos/${{ github.repository }}/pulls/${{ github.event.pull_request.number }}/reviews?per_page=100 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + - name: Check for approvals + if: ${{ failure() && github.event_name != 'workflow_dispatch' }} + run: | + echo "No review approvals found. At least 2 approvals are required to run this action automatically." + exit 1 + + - name: Check for enough approvals (>=2) + id: test_variables + if: github.event_name != 'workflow_dispatch' + run: | + JSON_RESPONSE='${{ steps.check_approvals.outputs.data }}' + CURRENT_APPROVALS_COUNT=$(echo $JSON_RESPONSE | jq -c '[.[] | select(.state | contains("APPROVED")) ] | length') + test $CURRENT_APPROVALS_COUNT -ge 2 || exit 1 # At least 2 approvals are required + + - name: Launch workflow via Seqera Platform uses: seqeralabs/action-tower-launch@v2 with: workspace_id: ${{ secrets.TOWER_WORKSPACE_ID }} @@ -28,9 +58,9 @@ jobs: } profiles: test_full - - uses: actions/upload-artifact@v3 + - uses: actions/upload-artifact@v4 with: - name: Tower debug log file + name: Seqera Platform debug log file path: | - tower_action_*.log - tower_action_*.json + seqera_platform_action_*.log + seqera_platform_action_*.json diff --git a/.github/workflows/awstest.yml b/.github/workflows/awstest.yml index e721f23..53668ef 100644 --- a/.github/workflows/awstest.yml +++ b/.github/workflows/awstest.yml @@ -5,13 +5,13 @@ name: nf-core AWS test on: workflow_dispatch: jobs: - run-tower: + run-platform: name: Run AWS tests if: github.repository == 'nf-core/molkart' runs-on: ubuntu-latest steps: - # Launch workflow using Tower CLI tool action - - name: Launch workflow via tower + # Launch workflow using Seqera Platform CLI tool action + - name: Launch workflow via Seqera Platform uses: seqeralabs/action-tower-launch@v2 with: workspace_id: ${{ secrets.TOWER_WORKSPACE_ID }} @@ -25,9 +25,9 @@ jobs: } profiles: test - - uses: actions/upload-artifact@v3 + - uses: actions/upload-artifact@v4 with: - name: Tower debug log file + name: Seqera Platform debug log file path: | - tower_action_*.log - tower_action_*.json + seqera_platform_action_*.log + seqera_platform_action_*.json diff --git a/.github/workflows/branch.yml b/.github/workflows/branch.yml index 3313b62..70e06b1 100644 --- a/.github/workflows/branch.yml +++ b/.github/workflows/branch.yml @@ -1,15 +1,17 @@ name: nf-core branch protection -# This workflow is triggered on PRs to master branch on the repository -# It fails when someone tries to make a PR against the nf-core `master` branch instead of `dev` +# This workflow is triggered on PRs to `main`/`master` branch on the repository +# It fails when someone tries to make a PR against the nf-core `main`/`master` branch instead of `dev` on: pull_request_target: - branches: [master] + branches: + - main + - master jobs: test: runs-on: ubuntu-latest steps: - # PRs to the nf-core repo master branch are only ok if coming from the nf-core repo `dev` or any `patch` branches + # PRs to the nf-core repo main/master branch are only ok if coming from the nf-core repo `dev` or any `patch` branches - name: Check PRs if: github.repository == 'nf-core/molkart' run: | @@ -19,10 +21,10 @@ jobs: # NOTE - this doesn't currently work if the PR is coming from a fork, due to limitations in GitHub actions secrets - name: Post PR comment if: failure() - uses: mshick/add-pr-comment@v1 + uses: mshick/add-pr-comment@b8f338c590a895d50bcbfa6c5859251edc8952fc # v2 with: message: | - ## This PR is against the `master` branch :x: + ## This PR is against the `${{github.event.pull_request.base.ref}}` branch :x: * Do not close this PR * Click _Edit_ and change the `base` to `dev` @@ -32,9 +34,9 @@ jobs: Hi @${{ github.event.pull_request.user.login }}, - It looks like this pull-request is has been made against the [${{github.event.pull_request.head.repo.full_name }}](https://github.com/${{github.event.pull_request.head.repo.full_name }}) `master` branch. - The `master` branch on nf-core repositories should always contain code from the latest release. - Because of this, PRs to `master` are only allowed if they come from the [${{github.event.pull_request.head.repo.full_name }}](https://github.com/${{github.event.pull_request.head.repo.full_name }}) `dev` branch. + It looks like this pull-request is has been made against the [${{github.event.pull_request.head.repo.full_name }}](https://github.com/${{github.event.pull_request.head.repo.full_name }}) ${{github.event.pull_request.base.ref}} branch. + The ${{github.event.pull_request.base.ref}} branch on nf-core repositories should always contain code from the latest release. + Because of this, PRs to ${{github.event.pull_request.base.ref}} are only allowed if they come from the [${{github.event.pull_request.head.repo.full_name }}](https://github.com/${{github.event.pull_request.head.repo.full_name }}) `dev` branch. You do not need to close this PR, you can change the target branch to `dev` by clicking the _"Edit"_ button at the top of this page. Note that even after this, the test will continue to show as failing until you push a new commit. diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index ffca402..d962c99 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1,108 +1,69 @@ -# This workflow runs the pipeline with the minimal test dataset to check that it completes without any syntax errors name: nf-core CI +# This workflow runs the pipeline with the minimal test dataset to check that it completes without any syntax errors on: push: branches: - - "dev" + - dev pull_request: - branches: - - "dev" - - "master" release: types: [published] + workflow_dispatch: env: NXF_ANSI_LOG: false - NFTEST_VER: "0.8.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: - list: - name: List Tests - if: "${{ github.event_name != 'push' || (github.event_name == 'push' && github.repository == 'nf-core/molkart') }}" - outputs: - # Expose matched filters as job 'modules' output variable - tests: ${{ steps.list.outputs.tests }} - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v3 - - - name: Setup Nextflow - uses: nf-core/setup-nextflow@v1 - - - name: Install nf-test - if: steps.cache-software.outputs.cache-hit != 'true' - run: | - wget -qO- https://code.askimed.com/install/nf-test | bash - sudo mv nf-test /usr/local/bin/ - - - name: List nf-test - id: list - run: | - echo "tests=$(nf-test list --silent --format=json)" >> "$GITHUB_OUTPUT" - test: - name: ${{ matrix.tags }} (${{ matrix.profile }}-${{ matrix.NXF_VER }}) + name: "Run pipeline with test data (${{ matrix.NXF_VER }} | ${{ matrix.test_name }} | ${{ matrix.profile }})" # Only run on push if this is the nf-core dev branch (merged PRs) - if: needs.list.outputs.tests != '[]' - needs: [list] + if: "${{ github.event_name != 'push' || (github.event_name == 'push' && github.repository == 'nf-core/molkart') }}" runs-on: ubuntu-latest strategy: matrix: NXF_VER: - - "23.04.0" + - "24.04.2" - "latest-everything" - profile: ["docker"] #TODO add "singularity" - test: ["${{ fromJson(needs.list.outputs.tests) }}"] + profile: + - "conda" + - "docker" + - "singularity" + test_name: + - "test" + isMaster: + - ${{ github.base_ref == 'master' }} + exclude: + - profile: "conda" steps: - name: Check out pipeline code - uses: actions/checkout@v4 - - - name: Cache Nextflow installation - id: cache-software - uses: actions/cache@v3 + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4 with: - path: | - /usr/local/bin/nf-test - /home/runner/.nf-test/nf-test.jar - key: molkart-${{ runner.os }}-${{ matrix.NXF_VER }} + fetch-depth: 0 - - name: Install Nextflow - uses: nf-core/setup-nextflow@v1 + - name: Set up Nextflow + uses: nf-core/setup-nextflow@v2 with: version: "${{ matrix.NXF_VER }}" - - name: Install nf-test - if: steps.cache-software.outputs.cache-hit != 'true' - run: | - wget -qO- https://code.askimed.com/install/nf-test | bash - sudo mv nf-test /usr/local/bin/ + - name: Set up Apptainer + if: matrix.profile == 'singularity' + uses: eWaterCycle/setup-apptainer@main - name: Set up Singularity if: matrix.profile == 'singularity' - uses: eWaterCycle/setup-singularity@v5 - with: - singularity-version: 3.7.1 - - - name: Run nf-test run: | - nf-test test \ - --profile="test,${{ matrix.profile }}" \ - ${{ matrix.test }} \ - --junitxml=${{ matrix.test }}-${{ matrix.profile }}.xml + mkdir -p $NXF_SINGULARITY_CACHEDIR + mkdir -p $NXF_SINGULARITY_LIBRARYDIR - - name: Publish Test Report - uses: mikepenz/action-junit-report@v3 - if: success() || failure() # run this step even if previous step failed - with: - path: ${{ matrix.test }}-${{ matrix.profile }}.xml + - name: Clean up Disk space + uses: jlumbroso/free-disk-space@54081f138730dfa15788a46383842cd2f914a1be # v1.3.1 - - name: Output log on failure - if: failure() + - name: "Run pipeline with test data ${{ matrix.NXF_VER }} | ${{ matrix.test_name }} | ${{ matrix.profile }}" run: | - sudo apt install bat > /dev/null - batcat --decorations=always --color=always ${{ github.workspace }}/.nf-test/tests/*/output/pipeline_info/software_versions.yml + nextflow run ${GITHUB_WORKSPACE} -profile ${{ matrix.test_name }},${{ matrix.profile }} --outdir ./results diff --git a/.github/workflows/clean-up.yml b/.github/workflows/clean-up.yml index 694e90e..0b6b1f2 100644 --- a/.github/workflows/clean-up.yml +++ b/.github/workflows/clean-up.yml @@ -10,7 +10,7 @@ jobs: issues: write pull-requests: write steps: - - uses: actions/stale@v7 + - uses: actions/stale@28ca1036281a5e5922ead5184a1bbf96e5fc984e # v9 with: stale-issue-message: "This issue has been tagged as awaiting-changes or awaiting-feedback by an nf-core contributor. Remove stale label or add a comment otherwise this issue will be closed in 20 days." stale-pr-message: "This PR has been tagged as awaiting-changes or awaiting-feedback by an nf-core contributor. Remove stale label or add a comment if it is still useful." diff --git a/.github/workflows/download_pipeline.yml b/.github/workflows/download_pipeline.yml new file mode 100644 index 0000000..ab06316 --- /dev/null +++ b/.github/workflows/download_pipeline.yml @@ -0,0 +1,134 @@ +name: Test successful pipeline download with 'nf-core pipelines download' + +# Run the workflow when: +# - dispatched manually +# - when a PR is opened or reopened to main/master branch +# - the head branch of the pull request is updated, i.e. if fixes for a release are pushed last minute to dev. +on: + workflow_dispatch: + inputs: + testbranch: + description: "The specific branch you wish to utilize for the test execution of nf-core pipelines download." + required: true + default: "dev" + pull_request: + types: + - opened + - edited + - synchronize + branches: + - main + - master + pull_request_target: + branches: + - main + - master + +env: + NXF_ANSI_LOG: false + +jobs: + configure: + runs-on: ubuntu-latest + outputs: + REPO_LOWERCASE: ${{ steps.get_repo_properties.outputs.REPO_LOWERCASE }} + REPOTITLE_LOWERCASE: ${{ steps.get_repo_properties.outputs.REPOTITLE_LOWERCASE }} + REPO_BRANCH: ${{ steps.get_repo_properties.outputs.REPO_BRANCH }} + steps: + - name: Get the repository name and current branch + id: get_repo_properties + run: | + echo "REPO_LOWERCASE=${GITHUB_REPOSITORY,,}" >> "$GITHUB_OUTPUT" + echo "REPOTITLE_LOWERCASE=$(basename ${GITHUB_REPOSITORY,,})" >> "$GITHUB_OUTPUT" + echo "REPO_BRANCH=${{ github.event.inputs.testbranch || 'dev' }}" >> "$GITHUB_OUTPUT" + + download: + runs-on: ubuntu-latest + needs: configure + steps: + - name: Install Nextflow + uses: nf-core/setup-nextflow@v2 + + - name: Disk space cleanup + uses: jlumbroso/free-disk-space@54081f138730dfa15788a46383842cd2f914a1be # v1.3.1 + + - uses: actions/setup-python@0b93645e9fea7318ecaed2b359559ac225c90a2b # v5 + with: + python-version: "3.12" + architecture: "x64" + + - name: Setup Apptainer + uses: eWaterCycle/setup-apptainer@4bb22c52d4f63406c49e94c804632975787312b3 # v2.0.0 + with: + apptainer-version: 1.3.4 + + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install git+https://github.com/nf-core/tools.git@dev + + - name: Make a cache directory for the container images + run: | + mkdir -p ./singularity_container_images + + - name: Download the pipeline + env: + NXF_SINGULARITY_CACHEDIR: ./singularity_container_images + run: | + nf-core pipelines download ${{ needs.configure.outputs.REPO_LOWERCASE }} \ + --revision ${{ needs.configure.outputs.REPO_BRANCH }} \ + --outdir ./${{ needs.configure.outputs.REPOTITLE_LOWERCASE }} \ + --compress "none" \ + --container-system 'singularity' \ + --container-library "quay.io" -l "docker.io" -l "community.wave.seqera.io/library/" \ + --container-cache-utilisation 'amend' \ + --download-configuration 'yes' + + - name: Inspect download + run: tree ./${{ needs.configure.outputs.REPOTITLE_LOWERCASE }} + + - name: Inspect container images + run: tree ./singularity_container_images | tee ./container_initial + + - name: Count the downloaded number of container images + id: count_initial + run: | + image_count=$(ls -1 ./singularity_container_images | wc -l | xargs) + echo "Initial container image count: $image_count" + echo "IMAGE_COUNT_INITIAL=$image_count" >> "$GITHUB_OUTPUT" + + - name: Run the downloaded pipeline (stub) + id: stub_run_pipeline + continue-on-error: true + env: + NXF_SINGULARITY_CACHEDIR: ./singularity_container_images + NXF_SINGULARITY_HOME_MOUNT: true + run: nextflow run ./${{needs.configure.outputs.REPOTITLE_LOWERCASE }}/$( sed 's/\W/_/g' <<< ${{ needs.configure.outputs.REPO_BRANCH }}) -stub -profile test,singularity --outdir ./results + - name: Run the downloaded pipeline (stub run not supported) + id: run_pipeline + if: ${{ steps.stub_run_pipeline.outcome == 'failure' }} + env: + NXF_SINGULARITY_CACHEDIR: ./singularity_container_images + NXF_SINGULARITY_HOME_MOUNT: true + run: nextflow run ./${{ needs.configure.outputs.REPOTITLE_LOWERCASE }}/$( sed 's/\W/_/g' <<< ${{ needs.configure.outputs.REPO_BRANCH }}) -profile test,singularity --outdir ./results + + - name: Count the downloaded number of container images + id: count_afterwards + run: | + image_count=$(ls -1 ./singularity_container_images | wc -l | xargs) + echo "Post-pipeline run container image count: $image_count" + echo "IMAGE_COUNT_AFTER=$image_count" >> "$GITHUB_OUTPUT" + + - name: Compare container image counts + run: | + if [ "${{ steps.count_initial.outputs.IMAGE_COUNT_INITIAL }}" -ne "${{ steps.count_afterwards.outputs.IMAGE_COUNT_AFTER }}" ]; then + initial_count=${{ steps.count_initial.outputs.IMAGE_COUNT_INITIAL }} + final_count=${{ steps.count_afterwards.outputs.IMAGE_COUNT_AFTER }} + difference=$((final_count - initial_count)) + echo "$difference additional container images were \n downloaded at runtime . The pipeline has no support for offline runs!" + tree ./singularity_container_images > ./container_afterwards + diff ./container_initial ./container_afterwards + exit 1 + else + echo "The pipeline can be downloaded successfully!" + fi diff --git a/.github/workflows/fix-linting.yml b/.github/workflows/fix-linting.yml index 0d832b1..b1bf00f 100644 --- a/.github/workflows/fix-linting.yml +++ b/.github/workflows/fix-linting.yml @@ -4,7 +4,7 @@ on: types: [created] jobs: - deploy: + fix-linting: # Only run if comment is on a PR with the main repo, and if it contains the magic keywords if: > contains(github.event.comment.html_url, '/pull/') && @@ -13,10 +13,17 @@ jobs: runs-on: ubuntu-latest steps: # Use the @nf-core-bot token to check out so we can push later - - uses: actions/checkout@v4 + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4 with: token: ${{ secrets.nf_core_bot_auth_token }} + # indication that the linting is being fixed + - name: React on comment + uses: peter-evans/create-or-update-comment@71345be0265236311c031f5c7866368bd1eff043 # v4 + with: + comment-id: ${{ github.event.comment.id }} + reactions: eyes + # Action runs on the issue comment, so we don't get the PR by default # Use the gh cli to check out the PR - name: Checkout Pull Request @@ -24,32 +31,59 @@ jobs: env: GITHUB_TOKEN: ${{ secrets.nf_core_bot_auth_token }} - - uses: actions/setup-node@v4 + # Install and run pre-commit + - uses: actions/setup-python@0b93645e9fea7318ecaed2b359559ac225c90a2b # v5 + with: + python-version: "3.12" - - name: Install Prettier - run: npm install -g prettier @prettier/plugin-php + - name: Install pre-commit + run: pip install pre-commit - # Check that we actually need to fix something - - name: Run 'prettier --check' - id: prettier_status - run: | - if prettier --check ${GITHUB_WORKSPACE}; then - echo "result=pass" >> $GITHUB_OUTPUT - else - echo "result=fail" >> $GITHUB_OUTPUT - fi + - name: Run pre-commit + id: pre-commit + run: pre-commit run --all-files + continue-on-error: true - - name: Run 'prettier --write' - if: steps.prettier_status.outputs.result == 'fail' - run: prettier --write ${GITHUB_WORKSPACE} + # indication that the linting has finished + - name: react if linting finished succesfully + if: steps.pre-commit.outcome == 'success' + uses: peter-evans/create-or-update-comment@71345be0265236311c031f5c7866368bd1eff043 # v4 + with: + comment-id: ${{ github.event.comment.id }} + reactions: "+1" - name: Commit & push changes - if: steps.prettier_status.outputs.result == 'fail' + id: commit-and-push + if: steps.pre-commit.outcome == 'failure' run: | git config user.email "core@nf-co.re" git config user.name "nf-core-bot" git config push.default upstream git add . git status - git commit -m "[automated] Fix linting with Prettier" + git commit -m "[automated] Fix code linting" git push + + - name: react if linting errors were fixed + id: react-if-fixed + if: steps.commit-and-push.outcome == 'success' + uses: peter-evans/create-or-update-comment@71345be0265236311c031f5c7866368bd1eff043 # v4 + with: + comment-id: ${{ github.event.comment.id }} + reactions: hooray + + - name: react if linting errors were not fixed + if: steps.commit-and-push.outcome == 'failure' + uses: peter-evans/create-or-update-comment@71345be0265236311c031f5c7866368bd1eff043 # v4 + with: + comment-id: ${{ github.event.comment.id }} + reactions: confused + + - name: react if linting errors were not fixed + if: steps.commit-and-push.outcome == 'failure' + uses: peter-evans/create-or-update-comment@71345be0265236311c031f5c7866368bd1eff043 # v4 + with: + issue-number: ${{ github.event.issue.number }} + body: | + @${{ github.actor }} I tried to fix the linting errors, but it didn't work. Please fix them manually. + See [CI log](https://github.com/nf-core/molkart/actions/runs/${{ github.run_id }}) for more details. diff --git a/.github/workflows/linting.yml b/.github/workflows/linting.yml index 905c58e..dbd52d5 100644 --- a/.github/workflows/linting.yml +++ b/.github/workflows/linting.yml @@ -1,6 +1,6 @@ name: nf-core linting # This workflow is triggered on pushes and PRs to the repository. -# It runs the `nf-core lint` and markdown lint tests to ensure +# It runs the `nf-core pipelines lint` and markdown lint tests to ensure # that the code meets the nf-core guidelines. on: push: @@ -11,87 +11,62 @@ on: types: [published] jobs: - EditorConfig: + pre-commit: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4 - - uses: actions/setup-node@v4 - - - name: Install editorconfig-checker - run: npm install -g editorconfig-checker - - - name: Run ECLint check - run: editorconfig-checker -exclude README.md $(find .* -type f | grep -v '.git\|.py\|.md\|json\|yml\|yaml\|html\|css\|work\|.nextflow\|build\|nf_core.egg-info\|log.txt\|Makefile') - - Prettier: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - - - uses: actions/setup-node@v4 - - - name: Install Prettier - run: npm install -g prettier - - - name: Run Prettier --check - run: prettier --check ${GITHUB_WORKSPACE} - - PythonBlack: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - - - name: Check code lints with Black - uses: psf/black@stable - - # If the above check failed, post a comment on the PR explaining the failure - - name: Post PR comment - if: failure() - uses: mshick/add-pr-comment@v1 + - name: Set up Python 3.12 + uses: actions/setup-python@0b93645e9fea7318ecaed2b359559ac225c90a2b # v5 with: - message: | - ## Python linting (`black`) is failing - - To keep the code consistent with lots of contributors, we run automated code consistency checks. - To fix this CI test, please run: - - * Install [`black`](https://black.readthedocs.io/en/stable/): `pip install black` - * Fix formatting errors in your pipeline: `black .` + python-version: "3.12" - Once you push these changes the test should pass, and you can hide this comment :+1: + - name: Install pre-commit + run: pip install pre-commit - We highly recommend setting up Black in your code editor so that this formatting is done automatically on save. Ask about it on Slack for help! - - Thanks again for your contribution! - repo-token: ${{ secrets.GITHUB_TOKEN }} - allow-repeats: false + - name: Run pre-commit + run: pre-commit run --all-files nf-core: runs-on: ubuntu-latest steps: - name: Check out pipeline code - uses: actions/checkout@v4 + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4 - name: Install Nextflow - uses: nf-core/setup-nextflow@v1 + uses: nf-core/setup-nextflow@v2 - - uses: actions/setup-python@v4 + - uses: actions/setup-python@0b93645e9fea7318ecaed2b359559ac225c90a2b # v5 with: - python-version: "3.11" + python-version: "3.12" architecture: "x64" + - name: read .nf-core.yml + uses: pietrobolcato/action-read-yaml@1.1.0 + id: read_yml + with: + config: ${{ github.workspace }}/.nf-core.yml + - name: Install dependencies run: | python -m pip install --upgrade pip - pip install nf-core + pip install nf-core==${{ steps.read_yml.outputs['nf_core_version'] }} + + - name: Run nf-core pipelines lint + if: ${{ github.base_ref != 'master' }} + env: + GITHUB_COMMENTS_URL: ${{ github.event.pull_request.comments_url }} + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + GITHUB_PR_COMMIT: ${{ github.event.pull_request.head.sha }} + run: nf-core -l lint_log.txt pipelines lint --dir ${GITHUB_WORKSPACE} --markdown lint_results.md - - name: Run nf-core lint + - name: Run nf-core pipelines lint --release + if: ${{ github.base_ref == 'master' }} env: GITHUB_COMMENTS_URL: ${{ github.event.pull_request.comments_url }} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} GITHUB_PR_COMMIT: ${{ github.event.pull_request.head.sha }} - run: nf-core -l lint_log.txt lint --dir ${GITHUB_WORKSPACE} --markdown lint_results.md + run: nf-core -l lint_log.txt pipelines lint --release --dir ${GITHUB_WORKSPACE} --markdown lint_results.md - name: Save PR number if: ${{ always() }} @@ -99,7 +74,7 @@ jobs: - name: Upload linting log file artifact if: ${{ always() }} - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4 with: name: linting-logs path: | diff --git a/.github/workflows/linting_comment.yml b/.github/workflows/linting_comment.yml index 0bbcd30..95b6b6a 100644 --- a/.github/workflows/linting_comment.yml +++ b/.github/workflows/linting_comment.yml @@ -11,7 +11,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Download lint results - uses: dawidd6/action-download-artifact@v2 + uses: dawidd6/action-download-artifact@20319c5641d495c8a52e688b7dc5fada6c3a9fbc # v8 with: workflow: linting.yml workflow_conclusion: completed @@ -21,7 +21,7 @@ jobs: run: echo "pr_number=$(cat linting-logs/PR_number.txt)" >> $GITHUB_OUTPUT - name: Post PR comment - uses: marocchino/sticky-pull-request-comment@v2 + uses: marocchino/sticky-pull-request-comment@331f8f5b4215f0445d3c07b4967662a32a2d3e31 # v2 with: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} number: ${{ steps.pr_number.outputs.pr_number }} diff --git a/.github/workflows/release-announcements.yml b/.github/workflows/release-announcements.yml index 6ad3392..76a9e67 100644 --- a/.github/workflows/release-announcements.yml +++ b/.github/workflows/release-announcements.yml @@ -9,6 +9,11 @@ jobs: toot: runs-on: ubuntu-latest steps: + - name: get topics and convert to hashtags + id: get_topics + run: | + echo "topics=$(curl -s https://nf-co.re/pipelines.json | jq -r '.remote_workflows[] | select(.full_name == "${{ github.repository }}") | .topics[]' | awk '{print "#"$0}' | tr '\n' ' ')" | sed 's/-//g' >> $GITHUB_OUTPUT + - uses: rzr/fediverse-action@master with: access-token: ${{ secrets.MASTODON_ACCESS_TOKEN }} @@ -20,43 +25,12 @@ jobs: Please see the changelog: ${{ github.event.release.html_url }} - send-tweet: - runs-on: ubuntu-latest - - steps: - - uses: actions/setup-python@v4 - with: - python-version: "3.10" - - name: Install dependencies - run: pip install tweepy==4.14.0 - - name: Send tweet - shell: python - run: | - import os - import tweepy - - client = tweepy.Client( - access_token=os.getenv("TWITTER_ACCESS_TOKEN"), - access_token_secret=os.getenv("TWITTER_ACCESS_TOKEN_SECRET"), - consumer_key=os.getenv("TWITTER_CONSUMER_KEY"), - consumer_secret=os.getenv("TWITTER_CONSUMER_SECRET"), - ) - tweet = os.getenv("TWEET") - client.create_tweet(text=tweet) - env: - TWEET: | - Pipeline release! ${{ github.repository }} v${{ github.event.release.tag_name }} - ${{ github.event.release.name }}! - - Please see the changelog: ${{ github.event.release.html_url }} - TWITTER_CONSUMER_KEY: ${{ secrets.TWITTER_CONSUMER_KEY }} - TWITTER_CONSUMER_SECRET: ${{ secrets.TWITTER_CONSUMER_SECRET }} - TWITTER_ACCESS_TOKEN: ${{ secrets.TWITTER_ACCESS_TOKEN }} - TWITTER_ACCESS_TOKEN_SECRET: ${{ secrets.TWITTER_ACCESS_TOKEN_SECRET }} + ${{ steps.get_topics.outputs.topics }} #nfcore #openscience #nextflow #bioinformatics bsky-post: runs-on: ubuntu-latest steps: - - uses: zentered/bluesky-post-action@v0.0.2 + - uses: zentered/bluesky-post-action@80dbe0a7697de18c15ad22f4619919ceb5ccf597 # v0.1.0 with: post: | Pipeline release! ${{ github.repository }} v${{ github.event.release.tag_name }} - ${{ github.event.release.name }}! diff --git a/.github/workflows/template_version_comment.yml b/.github/workflows/template_version_comment.yml new file mode 100644 index 0000000..537529b --- /dev/null +++ b/.github/workflows/template_version_comment.yml @@ -0,0 +1,46 @@ +name: nf-core template version comment +# This workflow is triggered on PRs to check if the pipeline template version matches the latest nf-core version. +# It posts a comment to the PR, even if it comes from a fork. + +on: pull_request_target + +jobs: + template_version: + runs-on: ubuntu-latest + steps: + - name: Check out pipeline code + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4 + with: + ref: ${{ github.event.pull_request.head.sha }} + + - name: Read template version from .nf-core.yml + uses: nichmor/minimal-read-yaml@v0.0.2 + id: read_yml + with: + config: ${{ github.workspace }}/.nf-core.yml + + - name: Install nf-core + run: | + python -m pip install --upgrade pip + pip install nf-core==${{ steps.read_yml.outputs['nf_core_version'] }} + + - name: Check nf-core outdated + id: nf_core_outdated + run: echo "OUTPUT=$(pip list --outdated | grep nf-core)" >> ${GITHUB_ENV} + + - name: Post nf-core template version comment + uses: mshick/add-pr-comment@b8f338c590a895d50bcbfa6c5859251edc8952fc # v2 + if: | + contains(env.OUTPUT, 'nf-core') + with: + repo-token: ${{ secrets.NF_CORE_BOT_AUTH_TOKEN }} + allow-repeats: false + message: | + > [!WARNING] + > Newer version of the nf-core template is available. + > + > Your pipeline is using an old version of the nf-core template: ${{ steps.read_yml.outputs['nf_core_version'] }}. + > Please update your pipeline to the latest version. + > + > For more documentation on how to update your pipeline, please see the [nf-core documentation](https://github.com/nf-core/tools?tab=readme-ov-file#sync-a-pipeline-with-the-template) and [Synchronisation documentation](https://nf-co.re/docs/contributing/sync). + # diff --git a/.gitignore b/.gitignore index 5124c9a..05aa7e0 100644 --- a/.gitignore +++ b/.gitignore @@ -6,3 +6,5 @@ results/ testing/ testing* *.pyc +.nf-test.log +.nf-test/ diff --git a/.gitpod.yml b/.gitpod.yml index acf7269..83599f6 100644 --- a/.gitpod.yml +++ b/.gitpod.yml @@ -4,18 +4,7 @@ tasks: command: | pre-commit install --install-hooks nextflow self-update - - name: unset JAVA_TOOL_OPTIONS - command: | - unset JAVA_TOOL_OPTIONS + vscode: - extensions: # based on nf-core.nf-core-extensionpack - - codezombiech.gitignore # Language support for .gitignore files - # - cssho.vscode-svgviewer # SVG viewer - - esbenp.prettier-vscode # Markdown/CommonMark linting and style checking for Visual Studio Code - - eamodio.gitlens # Quickly glimpse into whom, why, and when a line or code block was changed - - EditorConfig.EditorConfig # override user/workspace settings with settings found in .editorconfig files - - Gruntfuggly.todo-tree # Display TODO and FIXME in a tree view in the activity bar - - mechatroner.rainbow-csv # Highlight columns in csv files in different colors - # - nextflow.nextflow # Nextflow syntax highlighting - - oderwat.indent-rainbow # Highlight indentation level - - streetsidesoftware.code-spell-checker # Spelling checker for source code + extensions: + - nf-core.nf-core-extensionpack # https://github.com/nf-core/vscode-extensionpack diff --git a/.nf-core.yml b/.nf-core.yml index 3805dc8..8c4dfee 100644 --- a/.nf-core.yml +++ b/.nf-core.yml @@ -1 +1,19 @@ +lint: + files_unchanged: + - .gitignore + - assets/nf-core-molkart_logo_light.png + - docs/images/nf-core-molkart_logo_dark.png + - docs/images/nf-core-molkart_logo_light.png + files_exist: + - conf/igenomes_ignored.config +nf_core_version: 3.2.0 repository_type: pipeline +template: + author: "@kbestak, @FloWuenne" + description: An analysis pipeline for Molecular Cartography data from Resolve Biosciences. + force: false + is_nfcore: true + name: molkart + org: nf-core + outdir: . + version: 1.1.0 diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 0c31cdb..1dec865 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,5 +1,13 @@ repos: - repo: https://github.com/pre-commit/mirrors-prettier - rev: "v2.7.1" + rev: "v3.1.0" hooks: - id: prettier + additional_dependencies: + - prettier@3.2.5 + + - repo: https://github.com/editorconfig-checker/editorconfig-checker.python + rev: "3.1.2" + hooks: + - id: editorconfig-checker + alias: ec diff --git a/.prettierignore b/.prettierignore index 437d763..edd29f0 100644 --- a/.prettierignore +++ b/.prettierignore @@ -10,3 +10,4 @@ testing/ testing* *.pyc bin/ +ro-crate-metadata.json diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..a33b527 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,3 @@ +{ + "markdown.styles": ["public/vscode_markdown.css"] +} diff --git a/CHANGELOG.md b/CHANGELOG.md index 72f3787..8456964 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,281 +3,35 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). -## v1.0.1dev - [2024.01.16] +## 1.1.0 - Resolution Road -### `Added` +### Added -- mindagap_boxsize, mindagap_loopnum, mindagap_edges parameters -- updated mindagap version and adapted molkart.nf and modules.config to match updated mindagap module version -- CI testing with nf-test -- updated base.config to account for ilastik's high memory use -- updated metromap +- [PR #78](https://github.com/nf-core/molkart/pull/78) - Allow for Mindagap to be skipped using `skip_mindagap` parameter (@kbestak) +- [PR #81](https://github.com/nf-core/molkart/pull/81) - Add Stardist as a segmentation method (@kbestak) -## v1.0.1dev - [2024.01.08] +### Changed -### `Fixed` +- [PR #71](https://github.com/nf-core/molkart/pull/71), [PR #88](https://github.com/nf-core/molkart/pull/88), [PR #94](https://github.com/nf-core/molkart/pull/94) - template updates from 2.11.1 to 3.2.0 (@kbestak) +- [PR #98](https://github.com/nf-core/molkart/pull/98) - Update all nf-core modules (@FloWuenne) +- [PR #99](https://github.com/nf-core/molkart/pull/99) - Clean up code to adhere to language server standards (@kbestak) +- [PR #100](https://github.com/nf-core/molkart/pull/100) - Added author and license information to all bin scripts (@FloWuenne) +- [PR #101](https://github.com/nf-core/molkart/pull/101) - Updated manifest and author information (@FloWuenne) +- [PR #102](https://github.com/nf-core/molkart/pull/102) - Updated documentation (@kbestak) +- [PR #107](https://github.com/nf-core/molkart/pull/107) - Updated nf-tests to use nft-utils (@FloWuenne) -- Fixed a bug in maskfilter for calculating the number of filtered objects. -- Changed naming of columns in multiqc table for filtered cells +### Fixed -## v1.0.1dev - [2024.01.06] +- [PR #76](https://github.com/nf-core/molkart/pull/76) - Fix issue with custom content in MultiQC output (@kbestak) -### `Fixed` +### Dependencies -- Updated version numbers for all local modules to molkart-local container v0.0.4 due to a bug in version v0.0.3 that prevented the modules to be properly run with singularity. +| Tool | Previous version | New version | +| -------- | ---------------- | ----------- | +| Cellpose | 2.2.2 | 3.0.1 | +| Stardist | | 0.9.1 | +| MultiQC | 1.19 | 1.27 | -## v1.0.1dev - [2024.01.04] +## 1.0.0 - Spatial Circuit -### `Added` - -- Added createanndata process to workflow. This process will generate a spatial anndata object from the spot2cell output. The anndata object will be written to /anndata in the output folder. -- added tests for createanndata - -### `Fixed` - -- Updated version numbers for all local modules using the molkart-local container to v0.0.3 -- spot2cell - removed tag, output name now required, output name defined in modules.config -- output documentation for create training subset -- formatting in local modules - -## v1.0.1dev - [2023.12.19] - -### `Fixed` - -Fixed parameter specification for Maskfilter in modules.config, where both min and max area params in python were passed as min_area. - -## v1.0.1dev - [2023.12.18] - -### `Fixed` - -- Changed file prefix used in CLAHE to prevent file naming collisions if user used dots in filenames -- Changed label for CLAHE and MASKFILTER to process_medium to make them pass first try on real life datasets - -## v1.0.1dev - [2023.12.15] - -### `Added` - -- Added config file for full test dataset - -## v1.0.1dev - [2023.12.11] - -Crop overview is provided to Multiqc - now when create_training_subset is run, multiqc and customdumpsoftwareversions are also run. - -### `Added` - -- removed CropSummary.txt from published outputs - it gets collected at multiqc step and published there -- moved crop_overview.png to MultiQC folder -- gitpod container is nf-core/gitpod:dev instead of latest to include new versions of nf-tools and nf-test -- MOLKARTQCPNG process to add name to png for multiqc report, and combine if multiple samples are processed - -## v1.0.1dev - [2023.12.07] - -Local module revamp - all should use the same Docker image to save space. - -### `Added` - -- renamed CREATEILASTIKTRAININGSUBSET to CROPHDF5 -- renamed TIFFTRAININGSUBSET to CROPTIFF -- local modules now use the ghcr.io/schapirolabor/molkart-local:v0.0.1 container -- CREATE_STACK when clause - also applied the size check logic in molkart.nf -- Added crop_hdf5.py script instead of using mcmicro-ilastik container -- pattern to only return cropped images and overview (not versions or full hdf5 image) -- clahe does not use aicsimageio anymore -- create stack outputs a pyramidal tif (Palom) -- updated mesmer module - accordingly added prefix logic (and for maskfilter) - -## v1.0.1dev - [2023.12.05] - -Added MASKFILTER module. - -### `Added` - -- MASKFILTER module with respective script, parameters, qc measures that are passed to MOLKARTQC and MULTIQC -- renamed molcart_qc to MOLKARTQC -- versions to main local modules (MOLKARTQC, SPOT2CELL) -- CREATE_STACK when clause (so that it does not show in the progress when it doesn't run) -- comments in molkart.nf for clarity - -### `Fixed` - -- collect_QC average area is now rounded -- prefix handling in some modules - -### `Removed` - -- SAMPLESHEETCHECK subworkflow and Python script - -## v1.0.1dev - [2023.12.02] - -Replaced local module for mindagap/duplicatefinder with nf-core module. - -### `Added` - -- installed mindagap/duplicatefinder via nf-core tools - -### `Removed` - -- removed local mindagap_duplicatefinder.nf in local modules - -## v1.0.1dev - [2023.11.30.] - -Changes to clahe - more nf-core compliant, script change, versions, updated tests. - -### `Added` - -- Clahe now outputs versions -- --clahe_pyramid_tile parameter (hidden) - -### `Fixed` - -- clahe local module now follows nf-core guidelines with output naming defined through ext.prefix -- In all cases, the same writer will be used for clahe now -- Fixed CLAHE metadata -- renamed process from CLAHE_DASK to CLAHE -- renamed tilesize parameter to mindagap_tilesize for clarity - -### `Removed` - -- clahe_skip_pyramid parameter - -## v1.0.1dev - [2023.11.28.] - -Fixed file naming schema for mindagap and spot2cell. If only mesmer is used for segmentation, create stack does not have to be run. - -### `Fixed` - -- Mindagap outputs, in case the filenames were the same, would overwrite each other. -- spot2cell outputs, in case the filenames and segmentation method were the same, would overwrite each other. -- removed hardcoded memory requirement for CREATEILASTIKTRAININGSUBSET -- if only mesmer is used for segmentation, create stack does not have to be run. - -## v1.0.1dev - [2023.11.24.] - -Added first nf-tests for the pipeline. - -### `Added` - -- nf-test for 3 runs -- main.nf where the input only has the nuclear channel (does not run clahe or ilastik) -- main.nf where the input has both nuclear and membrane image (runs clahe, does not run ilastik) -- main.nf where the input only has the nuclear channel (does not run clahe), creates training subset - -## v1.0.1dev - [2023.11.15] - -Upgraded workflow, fixed multisample cellpose segmentation with custom model. Added options necessary to make testing work on small images. - -### `Added` - -- white background in metromap -- clahe_skip_pyramid parameter to skip pyramid generation in the clahe step - necessary for smaller data - -### `Fixed` - -- Cellpose custom model functions with multiple samples now. - -## v1.0.1dev - [2023.11.13] - -Added documentation - usage.md and output.md - -### `Added` - -- usage.md documentation -- output.md documentation -- segmentation outputs are all moved to a segmentation folder. -- updated nf-core module versions -- CITATIONS.md updated -- README.md updated -- WorkflowMolkart.groovy updated to return citations if tools are used (added commas) - -## v1.0.1dev - [2023.25.10] - -Implemented the tilesize parameter for Mindagap_mindagap and mindagap_duplicatefinder so that smaller representative images can be used as test. - -### `Added` - -- tilesize param -- tilesize passing to mindagap and duplicatefinder in modules.config -- - -### `Fixed` - -### `Dependencies` - -### `Deprecated` - -## v1.0.1dev - [2023.23.10] - -- Replace `PROJECT_SPOTS` and `MCQUANT` modules with spot2cells. This new (for now local) module reduces the RAM requirements drastically, because it doesn't create a multi-channel stack for the spots. Spots are assigned by looking up cell IDs at x,y, positions and iterating over the deduplicated spots table. -- Added process labels to many modules to fix linting warnings -- Added meta map to molcart_qc output to remove linting warning -- adjusted script for multiqc input accordingly -- Added duplicated spots counts to collect_qc.py and multiqc_config.yml so that they also get counted. -- Added tag option to spot2cell so that the output names with same sample id and different segmentation methods can be differentiated (they were overwriting each other previously) -- removed project spots and mcquant from modules.config -- changed pattern for molcart_qc as it was not matching the files (removed {}) -- added meta value to segmethod input in molcart_qc -- spot counts are now int values -- QC metrics rounded to 2 decimals - -## v1.0.1dev - [2023.22.10] - -Replaced the `clahe` param with `skip_clahe` so that the default value for running CLAHE is `False`. - -### `Added` - -- skip_clahe param (default False) -- removed clahe param -- adjusted workflow to check the params.skip_clahe value instead of the params.clahe -- adjusted the ext.when in modules.config - -### `Fixed` - -### `Dependencies` - -### `Deprecated` - -## v1.0dev - [2023.18.10] - -Added barebones version of multiqc output. - -### `Added` - -- emit value for png overview for createtrainingtiff -- molcart-qc: added sampleid-segmentation tag as sample id, as multiqc was only showing the second row if sample id is same - can this be fixed to unique row? -- input for multiqc are the csv files produced by molcart qc - -### `Fixed` - -### `Dependencies` - -### `Deprecated` - -## v1.0.1dev - [2023.12.10] - -Molkart adapted to most nf-core standards with optional parameters, multiple segmentation options, as well as membrane channel handling. Started work on creating training subset functionality. - -### `Added` - -- parameters for pipeline execution -- ext.args logic for almost all modules with external parameters -- channel logic for membrane handling -- create stack process if membrane image present for Cellpose -- optional clahe -- started work on create subset functionality - -### `Fixed` - -### `Dependencies` - -### `Deprecated` - -## v1.0dev - [date] - -Initial release of nf-core/molkart, created with the [nf-core](https://nf-co.re/) template. - -### `Added` - -### `Fixed` - -### `Dependencies` - -### `Deprecated` +First release of nf-core/molkart. diff --git a/CITATIONS.md b/CITATIONS.md index 0248a6d..9ac81fe 100644 --- a/CITATIONS.md +++ b/CITATIONS.md @@ -35,6 +35,10 @@ > Ewels P, Magnusson M, Lundin S, Käller M. MultiQC: summarize analysis results for multiple tools and samples in a single report. Bioinformatics. 2016 Oct 1;32(19):3047-8. doi: 10.1093/bioinformatics/btw354. Epub 2016 Jun 16. PubMed PMID: 27312411; PubMed Central PMCID: PMC5039924. +- [Stardist](https://github.com/stardist/stardist) + + > Schmidt, U., Weigert, M., Broaddus, C., Myers, G. (2018). Cell Detection with Star-Convex Polygons. In: Frangi, A., Schnabel, J., Davatzikos, C., Alberola-López, C., Fichtinger, G. (eds) Medical Image Computing and Computer Assisted Intervention – MICCAI 2018. MICCAI 2018. Lecture Notes in Computer Science(), vol 11071. Springer, Cham. https://doi.org/10.1007/978-3-030-00934-2_30 + ## Software packaging/containerisation tools - [Anaconda](https://anaconda.com) diff --git a/LICENSE b/LICENSE index 41ad6b6..d47a534 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) @kbestak, @FloWuenne +Copyright (c) The nf-core/molkart team Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/README.md b/README.md index 3545ddc..8842524 100644 --- a/README.md +++ b/README.md @@ -1,18 +1,24 @@ -# ![nf-core/molkart](docs/images/nf-core-molkart_logo_light.png#gh-light-mode-only) ![nf-core/molkart](docs/images/nf-core-molkart_logo_dark.png#gh-dark-mode-only) - -[![GitHub Actions CI Status](https://github.com/nf-core/molkart/workflows/nf-core%20CI/badge.svg)](https://github.com/nf-core/molkart/actions?query=workflow%3A%22nf-core+CI%22) -[![GitHub Actions Linting Status](https://github.com/nf-core/molkart/workflows/nf-core%20linting/badge.svg)](https://github.com/nf-core/molkart/actions?query=workflow%3A%22nf-core+linting%22)[![AWS CI](https://img.shields.io/badge/CI%20tests-full%20size-FF9900?labelColor=000000&logo=Amazon%20AWS)](https://nf-co.re/molkart/results)[![Cite with Zenodo](http://img.shields.io/badge/DOI-10.5281/zenodo.10650749-1073c8?labelColor=000000)](https://doi.org/10.5281/zenodo.10650749) - -[![Nextflow](https://img.shields.io/badge/nextflow%20DSL2-%E2%89%A523.04.0-23aa62.svg)](https://www.nextflow.io/) +

+ + + nf-core/molkart + +

+ +[![GitHub Actions CI Status](https://github.com/nf-core/molkart/actions/workflows/ci.yml/badge.svg)](https://github.com/nf-core/molkart/actions/workflows/ci.yml) +[![GitHub Actions Linting Status](https://github.com/nf-core/molkart/actions/workflows/linting.yml/badge.svg)](https://github.com/nf-core/molkart/actions/workflows/linting.yml)[![AWS CI](https://img.shields.io/badge/CI%20tests-full%20size-FF9900?labelColor=000000&logo=Amazon%20AWS)](https://nf-co.re/molkart/results)[![Cite with Zenodo](http://img.shields.io/badge/DOI-10.5281/zenodo.10650748-1073c8?labelColor=000000)](https://doi.org/10.5281/zenodo.10650748) +[![nf-test](https://img.shields.io/badge/unit_tests-nf--test-337ab7.svg)](https://www.nf-test.com) + +[![Nextflow](https://img.shields.io/badge/nextflow%20DSL2-%E2%89%A524.04.2-23aa62.svg)](https://www.nextflow.io/) [![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/) -[![Launch on Nextflow Tower](https://img.shields.io/badge/Launch%20%F0%9F%9A%80-Nextflow%20Tower-%234256e7)](https://tower.nf/launch?pipeline=https://github.com/nf-core/molkart) +[![Launch on Seqera Platform](https://img.shields.io/badge/Launch%20%F0%9F%9A%80-Seqera%20Platform-%234256e7)](https://cloud.seqera.io/launch?pipeline=https://github.com/nf-core/molkart) [![Get help on Slack](http://img.shields.io/badge/slack-nf--core%20%23molkart-4A154B?labelColor=000000&logo=slack)](https://nfcore.slack.com/channels/molkart)[![Follow on Twitter](http://img.shields.io/badge/twitter-%40nf__core-1DA1F2?labelColor=000000&logo=twitter)](https://twitter.com/nf_core)[![Follow on Mastodon](https://img.shields.io/badge/mastodon-nf__core-6364ff?labelColor=FFFFFF&logo=mastodon)](https://mstdn.science/@nf_core)[![Watch on YouTube](http://img.shields.io/badge/youtube-nf--core-FF0000?labelColor=000000&logo=youtube)](https://www.youtube.com/c/nf-core) ## Introduction -**nf-core/molkart** is a pipeline for processing Molecular Cartography data from Resolve Bioscience (combinatorial FISH). It takes as input a table of FISH spot positions (x,y,z,gene), a corresponding DAPI image (`tiff` format) and optionally a membrane staining image in the `tiff` format. nf-core/molkart performs end-to-end processing of the data including image processing, QC filtering of spots, cell segmentation, spot-to-cell assignment and reports quality metrics such as the spot assignment rate, average spots per cell and segmentation mask size ranges. +**nf-core/molkart** is a pipeline for processing Molecular Cartography data from Resolve Bioscience (combinatorial FISH). It takes as input a table of FISH spot positions (x,y,z,gene), a corresponding DAPI image (`TIFF` format) and optionally an additional staining image in the `TIFF` format. nf-core/molkart performs end-to-end processing of the data including image processing, QC filtering of spots, cell segmentation, spot-to-cell assignment and reports quality metrics such as the spot assignment rate, average spots per cell and segmentation mask size ranges.

@@ -26,7 +32,7 @@ Image preprocessing Cell segmentation -- Apply cell segmentation based on provided images, available options are: - [`Cellpose`](https://www.cellpose.org/) - [`Mesmer`](https://deepcell.readthedocs.io/en/master/API/deepcell.applications.html#mesmer) - [`ilastik`](https://www.ilastik.org/) +- Apply cell segmentation based on provided images, available options are: - [`Cellpose`](https://www.cellpose.org/) - [`Mesmer`](https://deepcell.readthedocs.io/en/master/API/deepcell.applications.html#mesmer) - [`ilastik`](https://www.ilastik.org/) - [`Stardist`](https://github.com/stardist/stardist) - Filter cells based on cell size to remove artifacts Spot processing @@ -41,8 +47,11 @@ Quality control ## Usage -> [!NOTE] -> If you are new to Nextflow and nf-core, please refer to [this page](https://nf-co.re/docs/usage/installation) on how to set-up Nextflow. Make sure to [test your setup](https://nf-co.re/docs/usage/introduction#how-to-run-a-pipeline) with `-profile test` before running the workflow on actual data. +:::note +If you are new to Nextflow and nf-core, please refer to [this page](https://nf-co.re/docs/usage/installation) on how +to set-up Nextflow. Make sure to [test your setup](https://nf-co.re/docs/usage/introduction#how-to-run-a-pipeline) +with `-profile test` before running the workflow on actual data. +::: First, prepare a samplesheet with your input data that looks as follows: @@ -53,7 +62,7 @@ sample,nuclear_image,spot_locations,membrane_image sample0,sample0_DAPI.tiff,sample0_spots.txt,sample0_WGA.tiff ``` -Each row represents an FOV (field-of-view). Columns represent the sample ID (all must be unique), the path to the respective nuclear image, the spot table, and optionally the path to the respective membrane image (or any second image). +Each row represents an FOV (field-of-view). Columns represent the sample ID (all must be unique), the path to the respective nuclear image, the spot table, and optionally the path to the respective membrane image (or any additional image to improve segmentation). Now, you can run the pipeline using all default values with: @@ -65,8 +74,7 @@ nextflow run nf-core/molkart \ ``` > [!WARNING] -> Please provide pipeline parameters via the CLI or Nextflow `-params-file` option. Custom config files including those provided by the `-c` Nextflow option can be used to provide any configuration _**except for parameters**_; -> see [docs](https://nf-co.re/usage/configuration#custom-configuration-files). +> Please provide pipeline parameters via the CLI or Nextflow `-params-file` option. Custom config files including those provided by the `-c` Nextflow option can be used to provide any configuration _**except for parameters**_; see [docs](https://nf-co.re/docs/usage/getting_started/configuration#custom-configuration-files). For more details and further functionality, please refer to the [usage documentation](https://nf-co.re/molkart/usage) and the [parameter documentation](https://nf-co.re/molkart/parameters). diff --git a/assets/email_template.html b/assets/email_template.html index 71f948b..c487808 100644 --- a/assets/email_template.html +++ b/assets/email_template.html @@ -12,7 +12,7 @@ -

nf-core/molkart v${version}

+

nf-core/molkart ${version}

Run Name: $runName

<% if (!success){ diff --git a/assets/email_template.txt b/assets/email_template.txt index e191004..6dd4309 100644 --- a/assets/email_template.txt +++ b/assets/email_template.txt @@ -4,7 +4,7 @@ |\\ | |__ __ / ` / \\ |__) |__ } { | \\| | \\__, \\__/ | \\ |___ \\`-._,-`-, `._,._,' - nf-core/molkart v${version} + nf-core/molkart ${version} ---------------------------------------------------- Run Name: $runName diff --git a/assets/methods_description_template.yml b/assets/methods_description_template.yml index 74dd468..a3fbbb7 100644 --- a/assets/methods_description_template.yml +++ b/assets/methods_description_template.yml @@ -3,7 +3,6 @@ description: "Suggested text and references to use when describing pipeline usag section_name: "nf-core/molkart Methods Description" section_href: "https://github.com/nf-core/molkart" plot_type: "html" -## TODO nf-core: Update the HTML below to your preferred methods description, e.g. add publication citation for this pipeline ## You inject any metadata in the Nextflow '${workflow}' object data: |

Methods

diff --git a/assets/multiqc_config.yml b/assets/multiqc_config.yml index 16519ec..fc27cf6 100644 --- a/assets/multiqc_config.yml +++ b/assets/multiqc_config.yml @@ -1,12 +1,16 @@ -custom_logo: "nf-core-molkart_logo_light.png" custom_logo_url: https://github.com/nf-core/molkart/ custom_logo_title: "nf-core/molkart" report_comment: > - This report has been generated by the nf-core/molkart - analysis pipeline. For information about how to interpret these results, please see the - documentation. + This report has been generated by the nf-core/molkart analysis pipeline. For information about how + to interpret these results, please see the documentation. report_section_order: + segmentation_stats: + order: 800 + my_custom_content_image: + order: 800 "nf-core-molkart-methods-description": order: -1000 software_versions: @@ -16,16 +20,20 @@ report_section_order: export_plots: true +run_module: + - custom_content + custom_data: my_custom_content_image: - section_name: "Crop overview" + section_name: "Crop selection overview" segmentation_stats: - id: "segmentation_stats" + file_format: "csv" + plot_type: "table" section_name: "QC statistics from segmentation" - pconfig: - id: "segmentation_stats_table" - namespace: "Segmentation stats" headers: + sample_id: + title: sample_id + Description: "Sample" segmentation_method: title: Segmentation method description: "Segmentation method" @@ -60,9 +68,11 @@ custom_data: title: Number of removed large cells description: "Total number of labels above max_area" sp: + my_custom_content_image: + fn: "*overview.png" segmentation_stats: - fn: "final_QC.all_samples.csv" + fn: "*spot_QC.csv" shared: true - my_custom_content_image: - fn: "*.png" ignore_images: false + +disable_version_detection: true diff --git a/assets/nf-core-molkart_logo_light.png b/assets/nf-core-molkart_logo_light.png index c92f2ae..aa3d434 100644 Binary files a/assets/nf-core-molkart_logo_light.png and b/assets/nf-core-molkart_logo_light.png differ diff --git a/assets/schema_input.json b/assets/schema_input.json index 09c4ef5..2f3c017 100644 --- a/assets/schema_input.json +++ b/assets/schema_input.json @@ -1,5 +1,5 @@ { - "$schema": "http://json-schema.org/draft-07/schema", + "$schema": "https://json-schema.org/draft/2020-12/schema", "$id": "https://raw.githubusercontent.com/nf-core/molkart/master/assets/schema_input.json", "title": "nf-core/molkart pipeline - params.input schema", "description": "Schema for the file provided with params.input", @@ -21,6 +21,7 @@ "spot_table": { "type": "string", "pattern": "^\\S+\\.(txt|tsv)$", + "format": "file-path", "errorMessage": "Spot table must be provided, has to have shape x,y,z,gene with sep = '\t', cannot contain spaces and must have extension '.txt'" }, "membrane_image": { @@ -28,7 +29,8 @@ "anyOf": [ { "type": "string", - "pattern": "^\\S+\\.(tif|tiff)$" + "pattern": "^\\S+\\.(tif|tiff)$", + "format": "file-path" }, { "type": "string", diff --git a/bin/apply_clahe.dask.py b/bin/apply_clahe.dask.py index 8b9676c..ae636e9 100755 --- a/bin/apply_clahe.dask.py +++ b/bin/apply_clahe.dask.py @@ -1,4 +1,5 @@ #!/usr/bin/env python +## Written by Kresimir Bestak and Florian Wuennemann and released under the MIT license. from __future__ import print_function, division from distutils.log import error import time diff --git a/bin/collect_QC.py b/bin/collect_QC.py index cf82441..a18eeef 100755 --- a/bin/collect_QC.py +++ b/bin/collect_QC.py @@ -1,13 +1,14 @@ #!/usr/bin/env python - +#### Written by Kresimir Bestak and Florian Wuennemann and released under the MIT license. #### This script takes regionprops_tabe output from mcquant and the raw spot tables from Resolve bioscience as input #### and calculates some QC metrics for masks and spot assignments -### If png files are provided, it combines them into one +#### If png files are provided, it combines them into one import argparse import pandas as pd from PIL import Image, ImageDraw, ImageFont import os +import numpy as np def combine_png_files(input_paths, output_path): @@ -85,6 +86,13 @@ def summarize_segmasks(cellxgene_table, spots_summary): ## Read in spot table spots = pd.read_table(args.spots, sep="\t", names=["x", "y", "z", "gene"]) + # below code had to be added to account for the spots.txt inputs if mindagap is skipped + if (([val for val in spots.index.values] == [val for val in range(len(spots.index.values))]) == False): + spots["gene"] = spots["z"] + spots["z"] = spots["y"] + spots["y"] = spots["x"] + spots["x"] = spots.index + spots.index = range(len(spots)) duplicated = sum(spots.gene.str.contains("Duplicated")) spots = spots[~spots.gene.str.contains("Duplicated")] diff --git a/bin/create_anndata.py b/bin/create_anndata.py index 5725397..131099d 100755 --- a/bin/create_anndata.py +++ b/bin/create_anndata.py @@ -1,4 +1,5 @@ #!/usr/bin/env python +## Written by Kresimir Bestak and Florian Wuennemann and released under the MIT license. import pandas as pd import numpy as np from anndata import AnnData diff --git a/bin/crop_hdf5.py b/bin/crop_hdf5.py index 99cd510..0251bdb 100755 --- a/bin/crop_hdf5.py +++ b/bin/crop_hdf5.py @@ -1,4 +1,5 @@ #!/usr/bin/env python3 +## Written by Kresimir Bestak and Florian Wuennemann and released under the MIT license import tifffile import numpy as np import h5py diff --git a/bin/crop_tiff.py b/bin/crop_tiff.py index d00115f..93ca932 100755 --- a/bin/crop_tiff.py +++ b/bin/crop_tiff.py @@ -1,4 +1,5 @@ #!/usr/bin/env python3 +## Written by Kresimir Bestak and Florian Wuennemann and released under the MIT license import ast import tifffile as tiff import os diff --git a/bin/maskfilter.py b/bin/maskfilter.py index c47d7b3..3fb2803 100755 --- a/bin/maskfilter.py +++ b/bin/maskfilter.py @@ -1,4 +1,5 @@ #!/usr/bin/env python +## Written by Kresimir Bestak and Florian Wuennemann and released under the MIT license import time import argparse from argparse import ArgumentParser as AP diff --git a/bin/spot2cell.py b/bin/spot2cell.py index 1e5e5a3..336723f 100755 --- a/bin/spot2cell.py +++ b/bin/spot2cell.py @@ -1,4 +1,5 @@ #!/usr/bin/env python +## Written by Kresimir Bestak and Florian Wuennemann and released under the MIT license ## Import packages import pandas as pd diff --git a/bin/stack.py b/bin/stack.py index b1882c1..323fb23 100755 --- a/bin/stack.py +++ b/bin/stack.py @@ -1,4 +1,5 @@ #!/usr/bin/env python +## Written by Kresimir Bestak and Florian Wuennemann and released under the MIT license import numpy as np import argparse import tifffile diff --git a/conf/base.config b/conf/base.config index 8a67abc..4c6782b 100644 --- a/conf/base.config +++ b/conf/base.config @@ -9,44 +9,44 @@ */ 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' // Process-specific resource requirements - // NOTE - Please try and re-use the labels below as much as possible. + // NOTE - Please try and reuse the labels below as much as possible. // These labels are used and recognised by default in DSL2 files hosted on nf-core/modules. // If possible, it would be nice to keep the same label naming convention when // adding in your local modules too. 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' @@ -55,10 +55,6 @@ process { errorStrategy = 'retry' maxRetries = 3 } - withName:CUSTOM_DUMPSOFTWAREVERSIONS { - cache = false - } - withName:'ILASTIK_PIXELCLASSIFICATION|ILASTIK_MULTICUT' { label = "process_high" } diff --git a/conf/modules.config b/conf/modules.config index 93c1b0e..1bcde4a 100644 --- a/conf/modules.config +++ b/conf/modules.config @@ -18,14 +18,6 @@ process { saveAs: { filename -> filename.equals('versions.yml') ? null : filename } ] - withName: CUSTOM_DUMPSOFTWAREVERSIONS { - publishDir = [ - path: { "${params.outdir}/pipeline_info" }, - mode: params.publish_dir_mode, - pattern: '*_versions.yml' - ] - } - withName: 'MULTIQC' { ext.args = { params.multiqc_title ? "--title \"$params.multiqc_title\"" : '' } publishDir = [ @@ -73,11 +65,12 @@ process { publishDir = [ path: { "${params.outdir}/segmentation/filtered_masks" }, mode: params.publish_dir_mode, - pattern: "*.{tiff,tif}" + pattern: "*.{tiff,tif,csv}" ] } withName: "MINDAGAP_MINDAGAP" { + ext.when = { !params.skip_mindagap } ext.args = [ "", params.mindagap_boxsize ? "${params.mindagap_boxsize}" : "", params.mindagap_loopnum ? "${params.mindagap_loopnum}" : "", @@ -91,6 +84,7 @@ process { } withName: "MINDAGAP_DUPLICATEFINDER" { + ext.when = { !params.skip_mindagap } ext.args = [ "", params.mindagap_tilesize ? "${params.mindagap_tilesize}" : "" ].join(" ").trim() @@ -179,6 +173,21 @@ process { ].join(" ").trim() } + + withName: STARDIST { + ext.when = { params.segmentation_method.split(',').contains('stardist') } + ext.args = [ "", + params.stardist_model ? "--model ${params.stardist_model}" : "", + (params.stardist_n_tiles_x && params.stardist_n_tiles_y) ? "--n_tiles ${params.stardist_n_tiles_x} ${params.stardist_n_tiles_y}" : "", + ].join(" ").trim() + ext.prefix = { "${meta.id}_stardist_mask" } + publishDir = [ + path: "${params.outdir}/segmentation/stardist", + pattern: "*.tif", + saveAs: { filename -> "${meta.id}_stardist_mask.tif" } + ] + } + withName: "DEEPCELL_MESMER" { ext.when = { params.segmentation_method.split(',').contains('mesmer') } ext.args = [ "", @@ -194,7 +203,6 @@ process { } withName: "CELLPOSE" { - singularity.runOptions = "--bind $HOME:$HOME" ext.when = { params.segmentation_method.split(',').contains('cellpose') } ext.args = [ "", "--channel_axis 0", diff --git a/conf/test.config b/conf/test.config index f30779e..2f6b2b4 100644 --- a/conf/test.config +++ b/conf/test.config @@ -10,20 +10,28 @@ ---------------------------------------------------------------------------------------- */ +process { + resourceLimits = [ + cpus: 4, + memory: '15.GB', + time: '1.h' + ] +} + params { config_profile_name = 'Test profile' config_profile_description = 'Minimal test dataset to check pipeline function' - // Limit resources so that this can run on GitHub Actions - max_cpus = 4 - max_memory = '8.GB' - max_time = '4.h' - // Input data input = 'https://raw.githubusercontent.com/nf-core/test-datasets/molkart/test_data/samplesheets/samplesheet_membrane.csv' mindagap_tilesize = 90 mindagap_boxsize = 7 mindagap_loopnum = 100 clahe_pyramid_tile = 368 - segmentation_method = "mesmer,cellpose" + segmentation_method = "mesmer,cellpose,stardist" + + // Only used if additionally params.create_training_subset is used + crop_size_x = 30 + crop_size_y = 30 + crop_amount = 2 } diff --git a/conf/test_full.config b/conf/test_full.config index 30c1090..a776d35 100644 --- a/conf/test_full.config +++ b/conf/test_full.config @@ -14,10 +14,12 @@ params { config_profile_name = 'Full test profile' config_profile_description = 'Full test dataset to check pipeline function' - input = 'https://raw.githubusercontent.com/nf-core/test-datasets/molkart/test_data/samplesheets/samplesheet_full_test.csv' + input = params.pipelines_testdata_base_path + 'molkart/test_data/samplesheets/samplesheet_full_test.csv' - segmentation_method = "mesmer,cellpose" + segmentation_method = "mesmer,cellpose,stardist" mindagap_boxsize = 3 mindagap_loopnum = 40 cellpose_pretrained_model = "nuclei" + stardist_n_tiles_x = 20 + stardist_n_tiles_y = 20 } diff --git a/docs/images/molkart_workflow.png b/docs/images/molkart_workflow.png index 2d3de1c..233816c 100644 Binary files a/docs/images/molkart_workflow.png and b/docs/images/molkart_workflow.png differ diff --git a/docs/images/nf-core-molkart_logo_dark.png b/docs/images/nf-core-molkart_logo_dark.png index 6362155..c4528a7 100644 Binary files a/docs/images/nf-core-molkart_logo_dark.png and b/docs/images/nf-core-molkart_logo_dark.png differ diff --git a/docs/images/nf-core-molkart_logo_light.png b/docs/images/nf-core-molkart_logo_light.png index c92f2ae..aa3d434 100644 Binary files a/docs/images/nf-core-molkart_logo_light.png and b/docs/images/nf-core-molkart_logo_light.png differ diff --git a/docs/output.md b/docs/output.md index 2dd3cc0..0fd63d5 100644 --- a/docs/output.md +++ b/docs/output.md @@ -73,15 +73,19 @@ Create stack is a local module used to merge images into a stack as preparation - `*_ilastik_mask.tif`: Segmentation masks created by ilastik's Boundary prediction with Multicut workflow. - `mesmer/`: - `*_mesmer_mask.tif`: Segmentation masks created by Mesmer. + - `stardist/`: + - `*_stardist_mask.tif`: Nuclear segmentation masks created by Stardist. - `filtered_masks/` - `*_method_filtered.tif`: Segmentation masks filtered based on provided area limits. -[Cellpose](https://www.cellpose.org) is a segmentation tool that provides pretrained models as well as additional human-in-the loop training. If additional training is performed, the envisioned way of doing it is creating the training subset (`tiff`), and training the model in the [Cellpose GUI](https://cellpose.readthedocs.io/en/latest/gui.html) on the subset, then giving the trained model as an argument within the pipeline to complete the pipeline run. +[Cellpose](https://www.cellpose.org) is a segmentation tool that provides pretrained models as well as additional human-in-the loop training. If additional training is performed, the envisioned way of doing it is creating the training subset (`TIFF`), and training the model in the [Cellpose GUI](https://cellpose.readthedocs.io/en/latest/gui.html) on the subset, then giving the trained model as an argument within the pipeline to complete the pipeline run. [ilastik](https://www.ilastik.org) is an interactive learning and segmentation toolkit, with its application here envisioned as - create training subset (`hdf5`), create Pixel Classifier and Boundary prediction with Multicut projects with specified parameters. Within Molkart, the project files can be given and batch processing would be applied on the full images. [Mesmer](https://deepcell.readthedocs.io/en/master/API/deepcell.applications.html#mesmer) is a segmentation tool that provides pretrained models for whole-cell and nuclear segmentation. +[Stardist](https://github.com/stardist/stardist) is an object detection tool using star-convex shapes. + ### Spot2cell
@@ -143,13 +147,13 @@ Results generated by MultiQC collate pipeline QC from supported tools e.g. FastQ - `training_subset/` - `hdf5/` - - `*_crop[0-9]+.hdf5`: `hdf5` crops for training Pixel classification and Multicut models with ilastik for segmentation. + - `*_crop[0-9]+.hdf5`: `HDF5` crops for training Pixel classification and Multicut models with ilastik for segmentation. - `tiff/` - - `*_crop[0-9]+.tiff`: `tiff` crops for training Cellpose to create a custom segmentation model. + - `*_crop[0-9]+.tiff`: `TIFF` crops for training Cellpose to create a custom segmentation model.
-Create training subset is an optional group of modules that create crops in `hdf5` and `tiff` formats, as well as provide the crop overview for reusability. +Create training subset is an optional group of modules that create crops in `HDF5` and `TIFF` formats, as well as provide the crop overview for reusability. ### Pipeline information diff --git a/docs/usage.md b/docs/usage.md index ed37e42..d68cb9d 100644 --- a/docs/usage.md +++ b/docs/usage.md @@ -28,12 +28,12 @@ SAMPLE3,SAMPLE3.nucleus.tiff,SAMPLE3.spots.txt,SAMPLE3.membrane.tiff SAMPLE4,SAMPLE4.nucleus.tiff,SAMPLE4.spots.txt,SAMPLE4.membrane.tiff ``` -| Column | Description | -| ---------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------ | -| `sample` | Custom sample name. If multiple field-of-views (FOVs) are being processed for the same sample, their sample tags must be different. Must not contain spaces. | -| `nuclear_image` | Full path to nuclear image (DAPI, Hoechst). | -| `spot_table` | Full path to tsv or txt spot table provided by Resolve. Separator must be `\t`. | -| `membrane_image` | Full path to membrane image (e.g WGA) or second channel to help with segmentation (optional). | +| Column | Description | +| ---------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `sample` | Custom sample name. If multiple field-of-views (FOVs) are being processed for the same sample, their sample tags must be different. Must not contain spaces. | +| `nuclear_image` | Full path to the nuclear stain image (DAPI, Hoechst). Must be in **TIFF** format (`.tiff` or `.tif`). | +| `spot_table` | Full path to the **spot table** (`.tsv` or `.txt`). The table must contain **x, y, z position columns and a gene column**, with **no header** and **tab-separated values (`\t`)**. | +| `membrane_image` | Full path to the membrane stain image (e.g., WGA) or a second channel to assist with segmentation. Must be in **TIFF** format (`.tiff` or `.tif`). _(Optional)_ | An [example samplesheet](../assets/samplesheet.csv) has been provided with the pipeline. @@ -60,9 +60,8 @@ If you wish to repeatedly use the same parameters for multiple runs, rather than Pipeline settings can be provided in a `yaml` or `json` file via `-params-file `. -:::warning -Do not use `-c ` to specify parameters as this will result in errors. Custom config files specified with `-c` must only be used for [tuning process resource specifications](https://nf-co.re/docs/usage/configuration#tuning-workflow-resources), other infrastructural tweaks (such as output directories), or module arguments (args). -::: +> [!WARNING] +> Do not use `-c ` to specify parameters as this will result in errors. Custom config files specified with `-c` must only be used for [tuning process resource specifications](https://nf-co.re/docs/usage/configuration#tuning-workflow-resources), other infrastructural tweaks (such as output directories), or module arguments (args). The above pipeline run specified with a params file in yaml format: @@ -70,18 +69,18 @@ The above pipeline run specified with a params file in yaml format: nextflow run nf-core/molkart -profile docker -params-file params.yaml ``` -with `params.yaml` containing: +with: ```yaml input: "./samplesheet.csv" -outdir: "./results/" +outdir: "./results" ``` Additionally, `params.yaml` can contain optional parameters: ```yaml input: "./samplesheet.csv" -outdir: "./results/" +outdir: "./results" segmentation_method: "mesmer" segmentation_min_area: null segmentation_max_area: null @@ -93,10 +92,14 @@ cellpose_pretrained_model: "cyto" cellpose_custom_model: null cellpose_flow_threshold: 0.4 cellpose_edge_exclude: true +stardist_model: "2D_versatile_fluo" +stardist_n_tiles_x: 3 +stardist_n_tiles_y: 3 mesmer_image_mpp: 0.138 mesmer_compartment: "whole-cell" ilastik_pixel_project: null ilastik_multicut_project: null +skip_mindagap: false mindagap_tilesize: 2144 mindagap_boxsize: 3 mindagap_loopnum: 40 @@ -116,18 +119,48 @@ crop_size_y: 400 You can also generate such `YAML`/`JSON` files via [nf-core/launch](https://nf-co.re/launch). -To run the pipeline so that the training subset is created with default values, run: +### Utilizing the create training feature + +To run the pipeline so that the training subset is created with default values, run the following: ```bash nextflow run nf-core/molkart --input ./samplesheet.csv --outdir ./results -profile docker --create_training_subset ``` -After training a Cellpose 2.0 model, or creating ilastik Pixel Classification and Multicut projects, make sure you match the parameters (e.g cell diameter, flow threshold) in the run to your training and continue the default pipeline run with: +The number of crops, fraction of non-zero pixels in the crops, and crop size can be adjusted through parameters. +This process generates both `TIFF` and `HDF5` files that are compatible with the Cellpose and ilastik GUIs for model training. The trained models can then be fed back into the pipeline for automated processing. + +After training a Cellpose model, or creating ilastik Pixel Classification and Multicut projects, make sure you match the parameters (e.g cell diameter, flow threshold) in the run to your training and continue the default pipeline run with: ```bash nextflow run nf-core/molkart --input ./samplesheet.csv --outdir ./results -profile docker --segmentation_method cellpose,ilastik --cellpose_custom_model /path/to/model --ilastik_pixel_project /path/to/pixel_classifier.ilp --ilastik_multicut_project /path/to/multicut.ilp ``` +### Segmentation approaches + +The four segmentation approaches (Mesmer, Cellpose, Stardist, ilastik) can be chosen using the `segmentation_method` parameter. If multiple are given (comma-separated, no whitespace), the pipeline will apply them in parallel. For parameter-based model options, please check the original tool's documentation. These can be provided with `mesmer_compartment`, `cellpose_pretrained_model`, and `stardist_model` for Mesmer, Cellpose and Stardist respectively. + +:::note +If a custom Cellpose model is provided via the `cellpose_custom_model` parameter as a path, the `cellpose_pretrained_model` parameter is ignored. +::: +:::note +Stardist segmentation currently only supports nuclear segmentation and the additional marker will not be used. +::: +:::note +Stardist is the only tool that natively supports tiling. Make sure to adapt requested resources based on the size of the input image(s). +::: +:::note +ilastik segmentation requires user-provided Pixel Classification and Multicut project files. The user must ensure that the training files have the same axes as the segmentation input files to ensure compatibility. +::: + +### Skipping processes + +By default, both Mindagap and CLAHE are run, however both can be skipped when running the pipeline using the `skip_mindagap` and `skip_clahe` parameters. + +Local contrast enhancement might not be needed for every dataset and parameters should be chosen carefully depending on the data. + +Similarly, if the data does not have the grid pattern characteristic for Molecular Cartography data, Mindagap can be skipped (e.g. for Merscope data) meaning both grid-filling and Duplicatefinder would not be applied to the data. + ### Updating the pipeline When you run the above command, Nextflow automatically pulls the pipeline code from GitHub and stores it as a cached version. When running the pipeline after this, it will always use the cached version if available - even if the pipeline has been updated since. To make sure that you're running the latest version of the pipeline, make sure that you regularly update the cached version of the pipeline: @@ -138,23 +171,21 @@ nextflow pull nf-core/molkart ### Reproducibility -It is a good idea to specify a pipeline version when running the pipeline on your data. This ensures that a specific version of the pipeline code and software are used when you run your pipeline. If you keep using the same tag, you'll be running the same version of the pipeline, even if there have been changes to the code since. +It is a good idea to specify the pipeline version when running the pipeline on your data. This ensures that a specific version of the pipeline code and software are used when you run your pipeline. If you keep using the same tag, you'll be running the same version of the pipeline, even if there have been changes to the code since. First, go to the [nf-core/molkart releases page](https://github.com/nf-core/molkart/releases) and find the latest pipeline version - numeric only (eg. `1.3.1`). Then specify this when running the pipeline with `-r` (one hyphen) - eg. `-r 1.3.1`. Of course, you can switch to another version by changing the number after the `-r` flag. This version number will be logged in reports when you run the pipeline, so that you'll know what you used when you look back in the future. For example, at the bottom of the MultiQC reports. -To further assist in reproducibility, you can use share and re-use [parameter files](#running-the-pipeline) to repeat pipeline runs with the same settings without having to write out a command with every single parameter. +To further assist in reproducibility, you can use share and reuse [parameter files](#running-the-pipeline) to repeat pipeline runs with the same settings without having to write out a command with every single parameter. -:::tip -If you wish to share such profile (such as upload as supplementary material for academic publications), make sure to NOT include cluster specific paths to files, nor institutional specific profiles. -::: +> [!TIP] +> If you wish to share such profile (such as upload as supplementary material for academic publications), make sure to NOT include cluster specific paths to files, nor institutional specific profiles. ## Core Nextflow arguments -:::note -These options are part of Nextflow and use a _single_ hyphen (pipeline parameters use a double-hyphen). -::: +> [!NOTE] +> These options are part of Nextflow and use a _single_ hyphen (pipeline parameters use a double-hyphen) ### `-profile` @@ -162,11 +193,10 @@ Use this parameter to choose a configuration profile. Profiles can give configur Several generic profiles are bundled with the pipeline which instruct the pipeline to use software packaged using different methods (Docker, Singularity, Podman, Shifter, Charliecloud, Apptainer, Conda) - see below. -:::info -We highly recommend the use of Docker or Singularity containers for full pipeline reproducibility -- currently, Conda is not supported for this pipeline. -::: +> [!IMPORTANT] +> We highly recommend the use of Docker or Singularity containers for full pipeline reproducibility, as currently for this pipeline, Conda is not supported. -The pipeline also dynamically loads configurations from [https://github.com/nf-core/configs](https://github.com/nf-core/configs) when it runs, making multiple config profiles for various institutional clusters available at run time. For more information and to see if your system is available in these configs please see the [nf-core/configs documentation](https://github.com/nf-core/configs#documentation). +The pipeline also dynamically loads configurations from [https://github.com/nf-core/configs](https://github.com/nf-core/configs) when it runs, making multiple config profiles for various institutional clusters available at run time. For more information and to check if your system is supported, please see the [nf-core/configs documentation](https://github.com/nf-core/configs#documentation). Note that multiple profiles can be loaded, for example: `-profile test,docker` - the order of arguments is important! They are loaded in sequence, so later profiles can overwrite earlier profiles. @@ -188,6 +218,8 @@ If `-profile` is not specified, the pipeline will run locally and expect all sof - A generic configuration profile to be used with [Charliecloud](https://hpc.github.io/charliecloud/) - `apptainer` - A generic configuration profile to be used with [Apptainer](https://apptainer.org/) +- `wave` + - A generic configuration profile to enable [Wave](https://seqera.io/wave/) containers. Use together with one of the above (requires Nextflow ` 24.03.0-edge` or later). - `conda` - A generic configuration profile to be used with [Conda](https://conda.io/docs/). Please only use Conda as a last resort i.e. when it's not possible to run the pipeline with Docker, Singularity, Podman, Shifter, Charliecloud, or Apptainer. Currently not supported. @@ -205,13 +237,13 @@ Specify the path to a specific config file (this is a core Nextflow command). Se ### Resource requests -Whilst the default requirements set within the pipeline will hopefully work for most people and with most input data, you may find that you want to customise the compute resources that the pipeline requests. Each step in the pipeline has a default set of requirements for number of CPUs, memory and time. For most of the steps in the pipeline, if the job exits with any of the error codes specified [here](https://github.com/nf-core/rnaseq/blob/4c27ef5610c87db00c3c5a3eed10b1d161abf575/conf/base.config#L18) it will automatically be resubmitted with higher requests (2 x original, then 3 x original). If it still fails after the third attempt then the pipeline execution is stopped. +Whilst the default requirements set within the pipeline will hopefully work for most people and with most input data, you may find that you want to customise the compute resources that the pipeline requests. Each step in the pipeline has a default set of requirements for number of CPUs, memory and time. For most of the pipeline steps, if the job exits with any of the error codes specified [here](https://github.com/nf-core/rnaseq/blob/4c27ef5610c87db00c3c5a3eed10b1d161abf575/conf/base.config#L18) it will automatically be resubmitted with higher resources request (2 x original, then 3 x original). If it still fails after the third attempt then the pipeline execution is stopped. To change the resource requests, please see the [max resources](https://nf-co.re/docs/usage/configuration#max-resources) and [tuning workflow resources](https://nf-co.re/docs/usage/configuration#tuning-workflow-resources) section of the nf-core website. ### Custom Containers -In some cases you may wish to change which container or conda environment a step of the pipeline uses for a particular tool. By default nf-core pipelines use containers and software from the [biocontainers](https://biocontainers.pro/) or [bioconda](https://bioconda.github.io/) projects. However in some cases the pipeline specified version maybe out of date. +In some cases, you may wish to change the container or conda environment used by a pipeline steps for a particular tool. By default, nf-core pipelines use containers and software from the [biocontainers](https://biocontainers.pro/) or [bioconda](https://bioconda.github.io/) projects. However, in some cases the pipeline specified version maybe out of date. To use a different container from the default container or conda environment specified in a pipeline, please see the [updating tool versions](https://nf-co.re/docs/usage/configuration#updating-tool-versions) section of the nf-core website. @@ -229,14 +261,6 @@ See the main [Nextflow documentation](https://www.nextflow.io/docs/latest/config If you have any questions or issues please send us a message on [Slack](https://nf-co.re/join/slack) on the [`#configs` channel](https://nfcore.slack.com/channels/configs). -## Azure Resource Requests - -To be used with the `azurebatch` profile by specifying the `-profile azurebatch`. -We recommend providing a compute `params.vm_type` of `Standard_D16_v3` VMs by default but these options can be changed if required. - -Note that the choice of VM size depends on your quota and the overall workload during the analysis. -For a thorough list, please refer the [Azure Sizes for virtual machines in Azure](https://docs.microsoft.com/en-us/azure/virtual-machines/sizes). - ## Running in the background Nextflow handles job submissions and supervises the running jobs. The Nextflow process must run until the pipeline is finished. diff --git a/lib/NfcoreTemplate.groovy b/lib/NfcoreTemplate.groovy deleted file mode 100755 index e248e4c..0000000 --- a/lib/NfcoreTemplate.groovy +++ /dev/null @@ -1,356 +0,0 @@ -// -// This file holds several functions used within the nf-core pipeline template. -// - -import org.yaml.snakeyaml.Yaml -import groovy.json.JsonOutput -import nextflow.extension.FilesEx - -class NfcoreTemplate { - - // - // Check AWS Batch related parameters have been specified correctly - // - public static void awsBatch(workflow, params) { - if (workflow.profile.contains('awsbatch')) { - // Check params.awsqueue and params.awsregion have been set if running on AWSBatch - assert (params.awsqueue && params.awsregion) : "Specify correct --awsqueue and --awsregion parameters on AWSBatch!" - // Check outdir paths to be S3 buckets if running on AWSBatch - assert params.outdir.startsWith('s3:') : "Outdir not on S3 - specify S3 Bucket to run on AWSBatch!" - } - } - - // - // Warn if a -profile or Nextflow config has not been provided to run the pipeline - // - public static void checkConfigProvided(workflow, log) { - 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 " - } - } - - // - // Generate version string - // - public static String version(workflow) { - String version_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 - } - - // - // Construct and send completion email - // - public static void email(workflow, params, summary_params, projectDir, log, multiqc_report=[]) { - - // 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 = [:] - for (group in summary_params.keySet()) { - 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'] = NfcoreTemplate.version(workflow) - 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 = null - try { - if (workflow.success) { - mqc_report = multiqc_report.getVal() - if (mqc_report.getClass() == ArrayList && mqc_report.size() >= 1) { - if (mqc_report.size() > 1) { - log.warn "[$workflow.manifest.name] Found multiple reports from process 'MULTIQC', will use only one" - } - mqc_report = mqc_report[0] - } - } - } catch (all) { - if (multiqc_report) { - log.warn "[$workflow.manifest.name] Could not attach MultiQC report to summary email" - } - } - - // Check if we are only sending emails on failure - def email_address = params.email - if (!params.email && params.email_on_fail && !workflow.success) { - email_address = params.email_on_fail - } - - // Render the TXT template - def engine = new groovy.text.GStringTemplateEngine() - def tf = new File("$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("$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 nextflow.util.MemoryUnit - def smail_fields = [ email: email_address, subject: subject, email_txt: email_txt, email_html: email_html, projectDir: "$projectDir", mqcFile: mqc_report, mqcMaxSize: max_multiqc_email_size.toBytes() ] - def sf = new File("$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 - Map colors = logColours(params.monochrome_logs) - if (email_address) { - try { - if (params.plaintext_email) { throw 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 (all) { - // Catch failures and try with plaintext - def mail_cmd = [ 'mail', '-s', subject, '--content-type=text/html', email_address ] - if ( mqc_report != null && mqc_report.size() <= max_multiqc_email_size.toBytes() ) { - mail_cmd += [ '-A', mqc_report ] - } - 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 } - FilesEx.copyTo(output_hf.toPath(), "${params.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 } - FilesEx.copyTo(output_tf.toPath(), "${params.outdir}/pipeline_info/pipeline_report.txt"); - output_tf.delete() - } - - // - // Construct and send a notification to a web server as JSON - // e.g. Microsoft Teams and Slack - // - public static void IM_notification(workflow, params, summary_params, projectDir, log) { - def hook_url = params.hook_url - - def summary = [:] - for (group in summary_params.keySet()) { - 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'] = NfcoreTemplate.version(workflow) - 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("$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()); - } - } - - // - // Dump pipeline parameters in a json file - // - public static void dump_parameters(workflow, params) { - 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 = JsonOutput.toJson(params) - temp_pf.text = JsonOutput.prettyPrint(jsonStr) - - FilesEx.copyTo(temp_pf.toPath(), "${params.outdir}/pipeline_info/params_${timestamp}.json") - temp_pf.delete() - } - - // - // Print pipeline summary on completion - // - public static void summary(workflow, params, log) { - Map colors = logColours(params.monochrome_logs) - 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}-" - } - } - - // - // ANSII Colours used for terminal logging - // - public static Map logColours(Boolean monochrome_logs) { - Map colorcodes = [:] - - // 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 - } - - // - // Does what is says on the tin - // - public static String dashedLine(monochrome_logs) { - Map colors = logColours(monochrome_logs) - return "-${colors.dim}----------------------------------------------------${colors.reset}-" - } - - // - // nf-core logo - // - public static String logo(workflow, monochrome_logs) { - Map colors = logColours(monochrome_logs) - String workflow_version = NfcoreTemplate.version(workflow) - String.format( - """\n - ${dashedLine(monochrome_logs)} - ${colors.green},--.${colors.black}/${colors.green},-.${colors.reset} - ${colors.blue} ___ __ __ __ ___ ${colors.green}/,-._.--~\'${colors.reset} - ${colors.blue} |\\ | |__ __ / ` / \\ |__) |__ ${colors.yellow}} {${colors.reset} - ${colors.blue} | \\| | \\__, \\__/ | \\ |___ ${colors.green}\\`-._,-`-,${colors.reset} - ${colors.green}`._,._,\'${colors.reset} - ${colors.purple} ${workflow.manifest.name} ${workflow_version}${colors.reset} - ${dashedLine(monochrome_logs)} - """.stripIndent() - ) - } -} diff --git a/lib/Utils.groovy b/lib/Utils.groovy deleted file mode 100644 index 8d030f4..0000000 --- a/lib/Utils.groovy +++ /dev/null @@ -1,47 +0,0 @@ -// -// This file holds several Groovy functions that could be useful for any Nextflow pipeline -// - -import org.yaml.snakeyaml.Yaml - -class Utils { - - // - // When running with -profile conda, warn if channels have not been set-up appropriately - // - public static void checkCondaChannels(log) { - Yaml parser = new Yaml() - def channels = [] - try { - def config = parser.load("conda config --show channels".execute().text) - channels = config.channels - } catch(NullPointerException | IOException e) { - log.warn "Could not verify conda channel configuration." - return - } - - // Check that all channels are present - // This channel list is ordered by required channel priority. - def required_channels_in_order = ['conda-forge', 'bioconda', 'defaults'] - 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 = false - def n = required_channels_in_order.size() - for (int i = 0; i < n - 1; i++) { - channel_priority_violation |= !(channels.indexOf(required_channels_in_order[i]) < channels.indexOf(required_channels_in_order[i+1])) - } - - if (channels_missing | channel_priority_violation) { - log.warn "~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n" + - " There is a problem with your Conda configuration!\n\n" + - " You will need to set-up the conda-forge and bioconda channels correctly.\n" + - " Please refer to https://bioconda.github.io/\n" + - " The observed channel order is \n" + - " ${channels}\n" + - " but the following channel order is required:\n" + - " ${required_channels_in_order}\n" + - "~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~" - } - } -} diff --git a/lib/WorkflowMain.groovy b/lib/WorkflowMain.groovy deleted file mode 100755 index dd21e83..0000000 --- a/lib/WorkflowMain.groovy +++ /dev/null @@ -1,64 +0,0 @@ -// -// This file holds several functions specific to the main.nf workflow in the nf-core/molkart pipeline -// - -import nextflow.Nextflow - -class WorkflowMain { - - // - // Citation string for pipeline - // - public static String citation(workflow) { - return "If you use ${workflow.manifest.name} for your analysis please cite:\n\n" + - "* The pipeline\n" + - " https://doi.org/10.5281/zenodo.10650749\n\n" + - "* The nf-core framework\n" + - " https://doi.org/10.1038/s41587-020-0439-x\n\n" + - "* Software dependencies\n" + - " https://github.com/${workflow.manifest.name}/blob/master/CITATIONS.md" - } - - - // - // Validate parameters and print summary to screen - // - public static void initialise(workflow, params, log) { - - // Print workflow version and exit on --version - if (params.version) { - String workflow_version = NfcoreTemplate.version(workflow) - log.info "${workflow.manifest.name} ${workflow_version}" - System.exit(0) - } - - // Check that a -profile or Nextflow config has been provided to run the pipeline - NfcoreTemplate.checkConfigProvided(workflow, log) - - // Check that conda channels are set-up correctly - if (workflow.profile.tokenize(',').intersect(['conda', 'mamba']).size() >= 1) { - Utils.checkCondaChannels(log) - } - - // Check AWS batch settings - NfcoreTemplate.awsBatch(workflow, params) - - // Check input has been provided - if (!params.input) { - Nextflow.error("Please provide an input samplesheet to the pipeline e.g. '--input samplesheet.csv'") - } - } - // - // Get attribute from genome config file e.g. fasta - // - /* - public static Object getGenomeAttribute(params, attribute) { - if (params.genomes && params.genome && params.genomes.containsKey(params.genome)) { - if (params.genomes[ params.genome ].containsKey(attribute)) { - return params.genomes[ params.genome ][ attribute ] - } - } - return null - } - */ // TODO : -} diff --git a/lib/WorkflowMolkart.groovy b/lib/WorkflowMolkart.groovy deleted file mode 100755 index 55ef7a0..0000000 --- a/lib/WorkflowMolkart.groovy +++ /dev/null @@ -1,127 +0,0 @@ -// -// This file holds several functions specific to the workflow/molkart.nf in the nf-core/molkart pipeline -// - -import nextflow.Nextflow -import groovy.text.SimpleTemplateEngine - -class WorkflowMolkart { - - - // - // Check and validate parameters - // - public static void initialise(params, log) { -/* //TODO : remove - genomeExistsError(params, log) - - - if (!params.fasta) { - Nextflow.error "Genome fasta file not specified with e.g. '--fasta genome.fa' or via a detectable config file." - } -*/ - } - - // - // Get workflow summary for MultiQC - // - public static String paramsSummaryMultiqc(workflow, summary) { - String summary_section = '' - for (group in summary.keySet()) { - def group_params = summary.get(group) // This gets the parameters of that particular group - if (group_params) { - summary_section += "

$group

\n" - summary_section += "
\n" - for (param in group_params.keySet()) { - summary_section += "
$param
${group_params.get(param) ?: 'N/A'}
\n" - } - summary_section += "
\n" - } - } - - String yaml_file_text = "id: '${workflow.manifest.name.replace('/','-')}-summary'\n" - 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 - } - - // - // Generate methods description for MultiQC - // - - public static String toolCitationText(params) { - - // Can use ternary operators to dynamically construct based conditions, e.g. params["run_xyz"] ? "Tool (Foo et al. 2023)" : "", - // Uncomment function in methodsDescriptionText to render in MultiQC report - def citation_text = [ - "Tools used in the workflow included:", - "Mindagap (Guerreiro et al. 2023),", - params.segmentation_method.split(',').contains('mesmer') ? "Mesmer (Greenwald et al. 2021)," : "", - params.segmentation_method.split(',').contains('ilastik') ? "ilastik (Berg et al. 2019)," : "", - params.segmentation_method.split(',').contains('cellpose') ? "Cellpose (Stringer et al. 2021; Pachitariu et al 2022)," : "", - "MultiQC (Ewels et al. 2016)", - "." - ].join(' ').trim() - - return citation_text - } - - public static String toolBibliographyText(params) { - - // Can use ternary operators to dynamically construct based conditions, e.g. params["run_xyz"] ? "
  • Author (2023) Pub name, Journal, DOI
  • " : "", - // Uncomment function in methodsDescriptionText to render in MultiQC report - def reference_text = [ - "
  • Guerreiro R, Wuennemann F & pvtodorov (2023). ViriatoII/MindaGap: v0.0.3 (0.0.3).
  • ", - params.segmentation_method.split(',').contains('mesmer') ? "
  • Greenwald NF, Miller G, Moen E, Kong A, Kagel A, Dougherty T, Fullaway CC, McIntosh BJ, Leow KX, Schwartz MS, Pavelchek C, Cui S, Camplisson I, Bar-Tal O, Singh J, Fong M, Chaudhry G, Abraham Z, Moseley J, Warshawsky S, Soon E, Greenbaum S, Risom T, Hollmann T, Bendall SC, Keren L, Graf W, Angelo M, Van Valen D. Whole-cell segmentation of tissue images with human-level performance using large-scale data annotation and deep learning. Nat Biotechnol. 2022 Apr;40(4):555-565. doi: 10.1038/s41587-021-01094-0. Epub 2021 Nov 18. PMID: 34795433; PMCID: PMC9010346.
  • " : "", - params.segmentation_method.split(',').contains('ilastik') ? "
  • Berg, S., Kutra, D., Kroeger, T. et al. ilastik: interactive machine learning for (bio)image analysis. Nat Methods 16, 1226–1232 (2019). https://doi.org/10.1038/s41592-019-0582-9
  • " : "", - params.segmentation_method.split(',').contains('cellpose') ? "
  • Stringer, C., Wang, T., Michaelos, M. et al. Cellpose: a generalist algorithm for cellular segmentation. Nat Methods 18, 100–106 (2021). https://doi.org/10.1038/s41592-020-01018-x
  • " : "", - "
  • Ewels, P., Magnusson, M., Lundin, S., & Käller, M. (2016). MultiQC: summarize analysis results for multiple tools and samples in a single report. Bioinformatics , 32(19), 3047–3048. doi: /10.1093/bioinformatics/btw354
  • " - ].join(' ').trim() - - return reference_text - } - - public static String methodsDescriptionText(run_workflow, mqc_methods_yaml, params) { - // Convert to a named map so can be used as with familar NXF ${workflow} variable syntax in the MultiQC YML file - def meta = [:] - meta.workflow = run_workflow.toMap() - meta["manifest_map"] = run_workflow.manifest.toMap() - - // Pipeline DOI - meta["doi_text"] = meta.manifest_map.doi ? "(doi: ${meta.manifest_map.doi})" : "" - meta["nodoi_text"] = meta.manifest_map.doi ? "": "
  • If available, make sure to update the text to include the Zenodo DOI of version of the pipeline used.
  • " - - // Tool references - meta["tool_citations"] = "" - meta["tool_bibliography"] = "" - - meta["tool_citations"] = toolCitationText(params).replaceAll(", \\.", ".").replaceAll("\\. \\.", ".").replaceAll(", \\.", ".") - meta["tool_bibliography"] = toolBibliographyText(params) - - - def methods_text = mqc_methods_yaml.text - - def engine = new SimpleTemplateEngine() - def description_html = engine.createTemplate(methods_text).make(meta) - - return description_html - } - - // - // Exit pipeline if incorrect --genome key provided - // - private static void genomeExistsError(params, log) { - if (params.genomes && params.genome && !params.genomes.containsKey(params.genome)) { - def error_string = "~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n" + - " Genome '${params.genome}' not found in any config files provided to the pipeline.\n" + - " Currently, the available genome keys are:\n" + - " ${params.genomes.keySet().join(", ")}\n" + - "~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~" - Nextflow.error(error_string) - } - } -} diff --git a/lib/nfcore_external_java_deps.jar b/lib/nfcore_external_java_deps.jar deleted file mode 100644 index 805c8bb..0000000 Binary files a/lib/nfcore_external_java_deps.jar and /dev/null differ diff --git a/main.nf b/main.nf index 7b0635c..047bef3 100644 --- a/main.nf +++ b/main.nf @@ -9,60 +9,80 @@ ---------------------------------------------------------------------------------------- */ -nextflow.enable.dsl = 2 - /* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - VALIDATE & PRINT PARAMETER SUMMARY + IMPORT FUNCTIONS / MODULES / SUBWORKFLOWS / WORKFLOWS ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ -include { validateParameters; paramsHelp; paramsSummaryLog; fromSamplesheet } from 'plugin/nf-validation' - - -// Print help message if needed -if (params.help) { - def logo = NfcoreTemplate.logo(workflow, params.monochrome_logs) - def citation = '\n' + WorkflowMain.citation(workflow) + '\n' - def String command = "nextflow run ${workflow.manifest.name} --input samplesheet.csv -profile docker" - log.info logo + paramsHelp(command) + citation + NfcoreTemplate.dashedLine(params.monochrome_logs) - System.exit(0) -} - -// Validate input parameters -if (params.validate_params) { - validateParameters() -} - -WorkflowMain.initialise(workflow, params, log) +include { MOLKART } from './workflows/molkart' +include { PIPELINE_INITIALISATION } from './subworkflows/local/utils_nfcore_molkart_pipeline' +include { PIPELINE_COMPLETION } from './subworkflows/local/utils_nfcore_molkart_pipeline' /* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - NAMED WORKFLOW FOR PIPELINE + NAMED WORKFLOWS FOR PIPELINE ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ -include { MOLKART } from './workflows/molkart' - // -// WORKFLOW: Run main nf-core/molkart analysis pipeline +// WORKFLOW: Run main analysis pipeline depending on type of input // workflow NFCORE_MOLKART { - MOLKART () -} + take: + samplesheet // channel: samplesheet read in from --input + + main: + + // + // WORKFLOW: Run pipeline + // + MOLKART ( + samplesheet + ) + emit: + multiqc_report = MOLKART.out.multiqc_report // channel: /path/to/multiqc_report.html +} /* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - RUN ALL WORKFLOWS + RUN MAIN WORKFLOW ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ -// -// WORKFLOW: Execute a single named workflow for the pipeline -// See: https://github.com/nf-core/rnaseq/issues/619 -// workflow { - NFCORE_MOLKART () + + main: + // + // SUBWORKFLOW: Run initialisation tasks + // + PIPELINE_INITIALISATION ( + params.version, + params.validate_params, + params.monochrome_logs, + args, + params.outdir, + params.input + ) + + // + // WORKFLOW: Run main workflow + // + NFCORE_MOLKART ( + PIPELINE_INITIALISATION.out.samplesheet + ) + // + // SUBWORKFLOW: Run completion tasks + // + PIPELINE_COMPLETION ( + params.email, + params.email_on_fail, + params.plaintext_email, + params.outdir, + params.monochrome_logs, + params.hook_url, + NFCORE_MOLKART.out.multiqc_report + ) } /* diff --git a/modules.json b/modules.json index 85bce5c..585d098 100644 --- a/modules.json +++ b/modules.json @@ -7,43 +7,62 @@ "nf-core": { "cellpose": { "branch": "master", - "git_sha": "0975c63a8ce4488c3259f595270b3f0d419abafe", - "installed_by": ["modules"] - }, - "custom/dumpsoftwareversions": { - "branch": "master", - "git_sha": "8ec825f465b9c17f9d83000022995b4f7de6fe93", + "git_sha": "5bca12d66725b17a486edc24d1d2381d4170da25", "installed_by": ["modules"] }, "deepcell/mesmer": { "branch": "master", - "git_sha": "81fcaa73c7f1668a1c289464da88bf5eff582bcd", + "git_sha": "666652151335353eef2fcd58880bcef5bc2928e1", "installed_by": ["modules"] }, "ilastik/multicut": { "branch": "master", - "git_sha": "3f5420aa22e00bd030a2556dfdffc9e164ec0ec5", + "git_sha": "a4d916a39231897a49c903089a548704c09b8ef3", "installed_by": ["modules"] }, "ilastik/pixelclassification": { "branch": "master", - "git_sha": "3f5420aa22e00bd030a2556dfdffc9e164ec0ec5", + "git_sha": "a4d916a39231897a49c903089a548704c09b8ef3", "installed_by": ["modules"] }, "mindagap/duplicatefinder": { "branch": "master", - "git_sha": "cdc42519bf7d6d2cac9b5f14cb56e0060477c69c", + "git_sha": "81880787133db07d9b4c1febd152c090eb8325dc", "installed_by": ["modules"] }, "mindagap/mindagap": { "branch": "master", - "git_sha": "97dbec551f491ee562721008cdecd1e7a9a56b14", + "git_sha": "81880787133db07d9b4c1febd152c090eb8325dc", "installed_by": ["modules"] }, "multiqc": { "branch": "master", - "git_sha": "8ec825f465b9c17f9d83000022995b4f7de6fe93", + "git_sha": "81880787133db07d9b4c1febd152c090eb8325dc", "installed_by": ["modules"] + }, + "stardist": { + "branch": "master", + "git_sha": "666652151335353eef2fcd58880bcef5bc2928e1", + "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/clahe.nf b/modules/local/clahe/main.nf similarity index 100% rename from modules/local/clahe.nf rename to modules/local/clahe/main.nf diff --git a/modules/local/createanndata.nf b/modules/local/createanndata/main.nf similarity index 97% rename from modules/local/createanndata.nf rename to modules/local/createanndata/main.nf index a11e3d7..bf9d64b 100644 --- a/modules/local/createanndata.nf +++ b/modules/local/createanndata/main.nf @@ -17,13 +17,12 @@ process CREATE_ANNDATA { script: def args = task.ext.args ?: '' def prefix = task.ext.prefix ?: "${meta.id}" - """ create_anndata.py \\ --input ${spot2cell} \\ --spatial_cols X_centroid Y_centroid \\ --output ${prefix}.adata \\ - $args + ${args} cat <<-END_VERSIONS > versions.yml "${task.process}": diff --git a/modules/local/createstack.nf b/modules/local/createstack/main.nf similarity index 97% rename from modules/local/createstack.nf rename to modules/local/createstack/main.nf index 993c5a4..417dbf4 100644 --- a/modules/local/createstack.nf +++ b/modules/local/createstack/main.nf @@ -17,12 +17,11 @@ process CREATE_STACK { script: def args = task.ext.args ?: '' def prefix = task.ext.prefix ?: "${meta.id}" - """ stack.py \\ --input ${image} \\ --output ${prefix}.ome.tif \\ - $args + ${args} cat <<-END_VERSIONS > versions.yml "${task.process}": diff --git a/modules/local/crophdf5.nf b/modules/local/crophdf5/main.nf similarity index 78% rename from modules/local/crophdf5.nf rename to modules/local/crophdf5/main.nf index 6d5229a..4b02ff0 100644 --- a/modules/local/crophdf5.nf +++ b/modules/local/crophdf5/main.nf @@ -16,15 +16,13 @@ process CROPHDF5 { task.ext.when == null || task.ext.when script: - def args = task.ext.args ?: '' - def prefix = task.ext.prefix ?: "${meta.id}" - + def args = task.ext.args ?: '' """ crop_hdf5.py \\ - --input $image_stack \\ + --input ${image_stack} \\ --output . \\ - --num_channels $num_channels \\ - $args + --num_channels ${num_channels} \\ + ${args} cat <<-END_VERSIONS > versions.yml "${task.process}": diff --git a/modules/local/croptiff.nf b/modules/local/croptiff/main.nf similarity index 68% rename from modules/local/croptiff.nf rename to modules/local/croptiff/main.nf index 7e3ca43..0a40316 100644 --- a/modules/local/croptiff.nf +++ b/modules/local/croptiff/main.nf @@ -5,8 +5,7 @@ process CROPTIFF { container 'ghcr.io/schapirolabor/molkart-local:v0.0.4' input: - tuple val(meta), path(image_stack) - tuple val(meta), path(crop_summary) + tuple val(meta), path(image_stack), path(crop_summary) output: tuple val(meta), path("*.tiff"), emit: crop_tiff @@ -17,14 +16,12 @@ process CROPTIFF { task.ext.when == null || task.ext.when script: - def args = task.ext.args ?: '' - def prefix = task.ext.prefix ?: "${meta.id}" - + def args = task.ext.args ?: '' """ crop_tiff.py \\ - --input $image_stack \\ - --crop_summary $crop_summary \\ - $args + --input ${image_stack} \\ + --crop_summary ${crop_summary} \\ + ${args} cat <<-END_VERSIONS > versions.yml "${task.process}": diff --git a/modules/local/maskfilter.nf b/modules/local/maskfilter/main.nf similarity index 96% rename from modules/local/maskfilter.nf rename to modules/local/maskfilter/main.nf index 97e73c3..fa8dd2f 100644 --- a/modules/local/maskfilter.nf +++ b/modules/local/maskfilter/main.nf @@ -33,7 +33,6 @@ process MASKFILTER { """ stub: - def args = task.ext.args ?: '' def prefix = task.ext.prefix ?: "${meta.id}" """ diff --git a/modules/local/molkartqc.nf b/modules/local/molkartqc/main.nf similarity index 80% rename from modules/local/molkartqc.nf rename to modules/local/molkartqc/main.nf index 57fe8d4..cfc5d82 100644 --- a/modules/local/molkartqc.nf +++ b/modules/local/molkartqc/main.nf @@ -20,13 +20,13 @@ process MOLKARTQC{ """ collect_QC.py \\ - --cellxgene $cellxgene_table \\ - --spots $spot_table \\ - --sample_id $prefix \\ - --segmentation_method $segmethod \\ - --filterqc $filterqc \\ + --cellxgene ${cellxgene_table} \\ + --spots ${spot_table} \\ + --sample_id ${prefix} \\ + --segmentation_method ${segmethod} \\ + --filterqc ${filterqc} \\ --outdir . \\ - $args + ${args} cat <<-END_VERSIONS > versions.yml "${task.process}": @@ -35,7 +35,6 @@ process MOLKARTQC{ """ stub: - def args = task.ext.args ?: '' def prefix = task.ext.prefix ?: "${meta.id}" """ diff --git a/modules/local/molkartqcpng.nf b/modules/local/molkartqcpng/main.nf similarity index 84% rename from modules/local/molkartqcpng.nf rename to modules/local/molkartqcpng/main.nf index f5a44df..438e27b 100644 --- a/modules/local/molkartqcpng.nf +++ b/modules/local/molkartqcpng/main.nf @@ -18,9 +18,9 @@ process MOLKARTQCPNG { """ collect_QC.py \\ - --png_overview $png \\ + --png_overview ${png} \\ --outdir . \\ - $args + ${args} cat <<-END_VERSIONS > versions.yml "${task.process}": @@ -29,9 +29,7 @@ process MOLKARTQCPNG { """ stub: - def args = task.ext.args ?: '' - def prefix = task.ext.prefix ?: "${meta.id}" - + def prefix = task.ext.prefix ?: ( png.name.toString().tokenize('.')[0] ) """ touch ${prefix}.png diff --git a/modules/local/spot2cell.nf b/modules/local/spot2cell/main.nf similarity index 90% rename from modules/local/spot2cell.nf rename to modules/local/spot2cell/main.nf index 4eab5ba..2bf0f54 100644 --- a/modules/local/spot2cell.nf +++ b/modules/local/spot2cell/main.nf @@ -6,8 +6,7 @@ process SPOT2CELL{ container 'ghcr.io/schapirolabor/molkart-local:v0.0.4' input: - tuple val(meta) , path(spot_table) - tuple val(meta2), path(cell_mask) + tuple val(meta), path(spot_table), path(cell_mask) output: tuple val(meta), path("*.csv"), emit: cellxgene_table diff --git a/modules/local/tiffh5convert.nf b/modules/local/tiffh5convert/main.nf similarity index 77% rename from modules/local/tiffh5convert.nf rename to modules/local/tiffh5convert/main.nf index f3d38ab..8fc03e5 100644 --- a/modules/local/tiffh5convert.nf +++ b/modules/local/tiffh5convert/main.nf @@ -15,15 +15,13 @@ process TIFFH5CONVERT { task.ext.when == null || task.ext.when script: - def args = task.ext.args ?: '' - def prefix = task.ext.prefix ?: "${meta.id}" - + def args = task.ext.args ?: '' """ crop_hdf5.py \\ - --input $image \\ + --input ${image} \\ --output . \\ - --num_channels $num_channels \\ - $args + --num_channels ${num_channels} \\ + ${args} cat <<-END_VERSIONS > versions.yml "${task.process}": diff --git a/modules/nf-core/cellpose/environment.yml b/modules/nf-core/cellpose/environment.yml deleted file mode 100644 index f8ca8bc..0000000 --- a/modules/nf-core/cellpose/environment.yml +++ /dev/null @@ -1,5 +0,0 @@ -name: cellpose -channels: - - conda-forge - - bioconda - - defaults diff --git a/modules/nf-core/cellpose/main.nf b/modules/nf-core/cellpose/main.nf index 8fde76c..967ff34 100644 --- a/modules/nf-core/cellpose/main.nf +++ b/modules/nf-core/cellpose/main.nf @@ -2,7 +2,7 @@ process CELLPOSE { tag "$meta.id" label 'process_medium' - container "docker.io/biocontainers/cellpose:2.2.2_cv2" + container "docker.io/biocontainers/cellpose:3.0.1_cv1" input: tuple val(meta), path(image) @@ -10,7 +10,7 @@ process CELLPOSE { output: tuple val(meta), path("*masks.tif") , emit: mask - tuple val(meta), path("*flows.tif"), emit: flows, optional: true + tuple val(meta), path("*flows.tif") , emit: flows, optional: true path "versions.yml" , emit: versions when: @@ -24,18 +24,18 @@ process CELLPOSE { def args = task.ext.args ?: '' def prefix = task.ext.prefix ?: "${meta.id}" def model_command = model ? "--pretrained_model $model" : "" - def VERSION = '2.2.2' """ - cellpose \ - --image_path $image \ - --save_tif \ - --verbose \ - $model_command \ - $args + export OMP_NUM_THREADS=${task.cpus} + export MKL_NUM_THREADS=${task.cpus} + cellpose \\ + --image_path $image \\ + --save_tif \\ + $model_command \\ + $args cat <<-END_VERSIONS > versions.yml "${task.process}": - cellpose: $VERSION + cellpose: \$(cellpose --version | awk 'NR==2 {print \$3}') END_VERSIONS """ stub: @@ -44,13 +44,14 @@ process CELLPOSE { error "I did not manage to create a cellpose module in Conda that works in all OSes. Please use Docker / Singularity / Podman instead." } def prefix = task.ext.prefix ?: "${meta.id}" - def VERSION = "2.2.2" // WARN: Version information not provided by tool on CLI. Please update this string when bumping container versions. + def name = image.name + def base = name.lastIndexOf('.') != -1 ? name[0..name.lastIndexOf('.') - 1] : name """ - touch ${prefix}_cp_masks.tif + touch ${base}_cp_masks.tif cat <<-END_VERSIONS > versions.yml "${task.process}": - cellpose: $VERSION + cellpose: \$(cellpose --version | awk 'NR==2 {print \$3}') END_VERSIONS """ diff --git a/modules/nf-core/cellpose/meta.yml b/modules/nf-core/cellpose/meta.yml index 32f59f9..5397944 100644 --- a/modules/nf-core/cellpose/meta.yml +++ b/modules/nf-core/cellpose/meta.yml @@ -6,47 +6,58 @@ keywords: - cellpose tools: - "cellpose": - description: "cellpose is an anatomical segmentation algorithm written in Python 3 by Carsen Stringer and Marius Pachitariu" + description: "cellpose is an anatomical segmentation algorithm written in Python + 3 by Carsen Stringer and Marius Pachitariu" homepage: "https://github.com/MouseLand/cellpose" documentation: "https://cellpose.readthedocs.io/en/latest/command.html" tool_dev_url: "https://github.com/MouseLand/cellpose" doi: 10.1038/s41592-022-01663-4 licence: ["BSD 3-Clause"] + identifier: biotools:cellpose input: - - meta: - type: map - description: | - Groovy Map containing sample information - (sample id) - - image: - type: file - description: tif file for ready for segmentation - pattern: "*.{tif,tiff}" - - model: - type: file - description: Optional input file. Cellpose 2 model trained by user using human-in-the-loop approach. + - - meta: + type: map + description: | + Groovy Map containing sample information + (sample id) + - image: + type: file + description: tif file for ready for segmentation + pattern: "*.{tif,tiff}" + - - model: + type: file + description: Optional input file. Cellpose 2 model trained by user using human-in-the-loop + approach. output: - - meta: - type: map - description: | - Groovy Map containing sample information - [sample id] - - versions: - type: file - description: File containing software versions - pattern: "versions.yml" - mask: - type: file - description: labelled mask output from cellpose in tif format - pattern: "*.{tif, tiff}" + - meta: + type: map + description: | + Groovy Map containing sample information + [sample id] + - "*masks.tif": + type: file + description: labelled mask output from cellpose in tif format + pattern: "*.{tif, tiff}" - flows: - type: file - description: cell flow output from cellpose - pattern: "*.{tif}" - + - meta: + type: map + description: | + Groovy Map containing sample information + [sample id] + - "*flows.tif": + type: file + description: cell flow output from cellpose + pattern: "*.{tif}" + - versions: + - versions.yml: + type: file + description: File containing software versions + pattern: "versions.yml" authors: - "@josenimo" - - "@FlowWuenne" + - "@FloWuenne" maintainers: - "@josenimo" - - "@FlowWuenne" + - "@FloWuenne" + - "@kbestak" diff --git a/modules/nf-core/cellpose/tests/main.nf.test b/modules/nf-core/cellpose/tests/main.nf.test new file mode 100644 index 0000000..6a7688b --- /dev/null +++ b/modules/nf-core/cellpose/tests/main.nf.test @@ -0,0 +1,63 @@ +nextflow_process { + + name "Test Process CELLPOSE" + script "../main.nf" + process "CELLPOSE" + + tag "modules" + tag "modules_nfcore" + tag "cellpose" + + test("cellpose - with flows, no model") { + + when { + config "./nextflow_wflows.config" + process { + """ + input[0] = [ + [ id:'test' ], + file(params.modules_testdata_base_path + 'imaging/segmentation/cycif_tonsil_registered.ome.tif', checkIfExists: true) + ] + input[1] = [] + """ + } + } + + then { + assertAll ( + { assert process.success }, + { assert snapshot(process.out.mask).match("mask") }, + { assert snapshot(process.out.flows).match("flows") }, + { assert snapshot(process.out.versions).match("versions") } + ) + } + + } + + + test("cellpose - stub") { + + options "-stub" + + when { + process { + """ + input[0] = [ + [ id:'test' ], + file(params.modules_testdata_base_path + 'imaging/segmentation/cycif_tonsil_registered.ome.tif', checkIfExists: true) + ] + input[1] = [] + """ + } + } + + then { + assertAll( + { assert process.success }, + { assert snapshot(process.out).match() } + ) + } + + } + +} diff --git a/modules/nf-core/cellpose/tests/main.nf.test.snap b/modules/nf-core/cellpose/tests/main.nf.test.snap new file mode 100644 index 0000000..03b4dbf --- /dev/null +++ b/modules/nf-core/cellpose/tests/main.nf.test.snap @@ -0,0 +1,87 @@ +{ + "flows": { + "content": [ + [ + [ + { + "id": "test" + }, + "cycif_tonsil_registered.ome_flows.tif:md5,de79a792d4bebd2f9753ceb47a0de5f7" + ] + ] + ], + "meta": { + "nf-test": "0.8.4", + "nextflow": "23.10.1" + }, + "timestamp": "2024-03-18T14:22:16.855256249" + }, + "versions": { + "content": [ + [ + "versions.yml:md5,ce42208b574084f390cf58b4c19b5717" + ] + ], + "meta": { + "nf-test": "0.8.4", + "nextflow": "23.10.1" + }, + "timestamp": "2024-03-18T14:22:16.875087557" + }, + "cellpose - stub": { + "content": [ + { + "0": [ + [ + { + "id": "test" + }, + "cycif_tonsil_registered.ome_cp_masks.tif:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "1": [ + + ], + "2": [ + "versions.yml:md5,ce42208b574084f390cf58b4c19b5717" + ], + "flows": [ + + ], + "mask": [ + [ + { + "id": "test" + }, + "cycif_tonsil_registered.ome_cp_masks.tif:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "versions": [ + "versions.yml:md5,ce42208b574084f390cf58b4c19b5717" + ] + } + ], + "meta": { + "nf-test": "0.8.4", + "nextflow": "23.10.1" + }, + "timestamp": "2024-03-18T14:22:39.339792992" + }, + "mask": { + "content": [ + [ + [ + { + "id": "test" + }, + "cycif_tonsil_registered.ome_cp_masks.tif:md5,001ad312413f18bc2615741bd3ad12cf" + ] + ] + ], + "meta": { + "nf-test": "0.8.4", + "nextflow": "23.10.1" + }, + "timestamp": "2024-03-18T14:22:16.8369758" + } +} \ No newline at end of file diff --git a/modules/nf-core/cellpose/tests/nextflow_wflows.config b/modules/nf-core/cellpose/tests/nextflow_wflows.config new file mode 100644 index 0000000..773c97b --- /dev/null +++ b/modules/nf-core/cellpose/tests/nextflow_wflows.config @@ -0,0 +1,5 @@ +process { + withName: "CELLPOSE" { + ext.args = '--pretrained_model nuclei --diameter 9 --channel_axis 0 --no_npy --save_flows' + } +} diff --git a/modules/nf-core/cellpose/tests/tags.yml b/modules/nf-core/cellpose/tests/tags.yml new file mode 100644 index 0000000..1280d1f --- /dev/null +++ b/modules/nf-core/cellpose/tests/tags.yml @@ -0,0 +1,2 @@ +cellpose: + - "modules/nf-core/cellpose/**" diff --git a/modules/nf-core/custom/dumpsoftwareversions/environment.yml b/modules/nf-core/custom/dumpsoftwareversions/environment.yml deleted file mode 100644 index 9b3272b..0000000 --- a/modules/nf-core/custom/dumpsoftwareversions/environment.yml +++ /dev/null @@ -1,7 +0,0 @@ -name: custom_dumpsoftwareversions -channels: - - conda-forge - - bioconda - - defaults -dependencies: - - bioconda::multiqc=1.19 diff --git a/modules/nf-core/custom/dumpsoftwareversions/main.nf b/modules/nf-core/custom/dumpsoftwareversions/main.nf deleted file mode 100644 index f218761..0000000 --- a/modules/nf-core/custom/dumpsoftwareversions/main.nf +++ /dev/null @@ -1,24 +0,0 @@ -process CUSTOM_DUMPSOFTWAREVERSIONS { - label 'process_single' - - // Requires `pyyaml` which does not have a dedicated container but is in the MultiQC container - conda "${moduleDir}/environment.yml" - container "${ workflow.containerEngine == 'singularity' && !task.ext.singularity_pull_docker_container ? - 'https://depot.galaxyproject.org/singularity/multiqc:1.19--pyhdfd78af_0' : - 'biocontainers/multiqc:1.19--pyhdfd78af_0' }" - - input: - path versions - - output: - path "software_versions.yml" , emit: yml - path "software_versions_mqc.yml", emit: mqc_yml - path "versions.yml" , emit: versions - - when: - task.ext.when == null || task.ext.when - - script: - def args = task.ext.args ?: '' - template 'dumpsoftwareversions.py' -} diff --git a/modules/nf-core/custom/dumpsoftwareversions/meta.yml b/modules/nf-core/custom/dumpsoftwareversions/meta.yml deleted file mode 100644 index 5f15a5f..0000000 --- a/modules/nf-core/custom/dumpsoftwareversions/meta.yml +++ /dev/null @@ -1,37 +0,0 @@ -# yaml-language-server: $schema=https://raw.githubusercontent.com/nf-core/modules/master/modules/meta-schema.json -name: custom_dumpsoftwareversions -description: Custom module used to dump software versions within the nf-core pipeline template -keywords: - - custom - - dump - - version -tools: - - custom: - description: Custom module used to dump software versions within the nf-core pipeline template - homepage: https://github.com/nf-core/tools - documentation: https://github.com/nf-core/tools - licence: ["MIT"] -input: - - versions: - type: file - description: YML file containing software versions - pattern: "*.yml" -output: - - yml: - type: file - description: Standard YML file containing software versions - pattern: "software_versions.yml" - - mqc_yml: - type: file - description: MultiQC custom content YML file containing software versions - pattern: "software_versions_mqc.yml" - - versions: - type: file - description: File containing software versions - pattern: "versions.yml" -authors: - - "@drpatelh" - - "@grst" -maintainers: - - "@drpatelh" - - "@grst" diff --git a/modules/nf-core/custom/dumpsoftwareversions/templates/dumpsoftwareversions.py b/modules/nf-core/custom/dumpsoftwareversions/templates/dumpsoftwareversions.py deleted file mode 100755 index da03340..0000000 --- a/modules/nf-core/custom/dumpsoftwareversions/templates/dumpsoftwareversions.py +++ /dev/null @@ -1,101 +0,0 @@ -#!/usr/bin/env python - - -"""Provide functions to merge multiple versions.yml files.""" - - -import yaml -import platform -from textwrap import dedent - - -def _make_versions_html(versions): - """Generate a tabular HTML output of all versions for MultiQC.""" - html = [ - dedent( - """\\ - - - - - - - - - - """ - ) - ] - for process, tmp_versions in sorted(versions.items()): - html.append("") - for i, (tool, version) in enumerate(sorted(tmp_versions.items())): - html.append( - dedent( - f"""\\ - - - - - - """ - ) - ) - html.append("") - html.append("
    Process Name Software Version
    {process if (i == 0) else ''}{tool}{version}
    ") - return "\\n".join(html) - - -def main(): - """Load all version files and generate merged output.""" - versions_this_module = {} - versions_this_module["${task.process}"] = { - "python": platform.python_version(), - "yaml": yaml.__version__, - } - - with open("$versions") as f: - versions_by_process = yaml.load(f, Loader=yaml.BaseLoader) | versions_this_module - - # aggregate versions by the module name (derived from fully-qualified process name) - versions_by_module = {} - for process, process_versions in versions_by_process.items(): - module = process.split(":")[-1] - try: - if versions_by_module[module] != process_versions: - raise AssertionError( - "We assume that software versions are the same between all modules. " - "If you see this error-message it means you discovered an edge-case " - "and should open an issue in nf-core/tools. " - ) - except KeyError: - versions_by_module[module] = process_versions - - versions_by_module["Workflow"] = { - "Nextflow": "$workflow.nextflow.version", - "$workflow.manifest.name": "$workflow.manifest.version", - } - - versions_mqc = { - "id": "software_versions", - "section_name": "${workflow.manifest.name} Software Versions", - "section_href": "https://github.com/${workflow.manifest.name}", - "plot_type": "html", - "description": "are collected at run time from the software output.", - "data": _make_versions_html(versions_by_module), - } - - with open("software_versions.yml", "w") as f: - yaml.dump(versions_by_module, f, default_flow_style=False) - with open("software_versions_mqc.yml", "w") as f: - yaml.dump(versions_mqc, f, default_flow_style=False) - - with open("versions.yml", "w") as f: - yaml.dump(versions_this_module, f, default_flow_style=False) - - -if __name__ == "__main__": - main() diff --git a/modules/nf-core/custom/dumpsoftwareversions/tests/main.nf.test b/modules/nf-core/custom/dumpsoftwareversions/tests/main.nf.test deleted file mode 100644 index b1e1630..0000000 --- a/modules/nf-core/custom/dumpsoftwareversions/tests/main.nf.test +++ /dev/null @@ -1,43 +0,0 @@ -nextflow_process { - - name "Test Process CUSTOM_DUMPSOFTWAREVERSIONS" - script "../main.nf" - process "CUSTOM_DUMPSOFTWAREVERSIONS" - tag "modules" - tag "modules_nfcore" - tag "custom" - tag "dumpsoftwareversions" - tag "custom/dumpsoftwareversions" - - test("Should run without failures") { - when { - process { - """ - def tool1_version = ''' - TOOL1: - tool1: 0.11.9 - '''.stripIndent() - - def tool2_version = ''' - TOOL2: - tool2: 1.9 - '''.stripIndent() - - input[0] = Channel.of(tool1_version, tool2_version).collectFile() - """ - } - } - - then { - assertAll( - { assert process.success }, - { assert snapshot( - process.out.versions, - file(process.out.mqc_yml[0]).readLines()[0..10], - file(process.out.yml[0]).readLines()[0..7] - ).match() - } - ) - } - } -} diff --git a/modules/nf-core/custom/dumpsoftwareversions/tests/main.nf.test.snap b/modules/nf-core/custom/dumpsoftwareversions/tests/main.nf.test.snap deleted file mode 100644 index 5f59a93..0000000 --- a/modules/nf-core/custom/dumpsoftwareversions/tests/main.nf.test.snap +++ /dev/null @@ -1,33 +0,0 @@ -{ - "Should run without failures": { - "content": [ - [ - "versions.yml:md5,76d454d92244589d32455833f7c1ba6d" - ], - [ - "data: \"\\n\\n \\n \\n \\n \\n \\n \\n \\n\\", - " \\n\\n\\n \\n \\n\\", - " \\ \\n\\n\\n\\n \\n \\", - " \\ \\n \\n\\n\\n\\n\\", - " \\n\\n \\n \\n\\", - " \\ \\n\\n\\n\\n\\n\\n \\n\\", - " \\ \\n \\n\\n\\n\\n\\", - " \\n\\n \\n \\n\\" - ], - [ - "CUSTOM_DUMPSOFTWAREVERSIONS:", - " python: 3.11.7", - " yaml: 5.4.1", - "TOOL1:", - " tool1: 0.11.9", - "TOOL2:", - " tool2: '1.9'", - "Workflow:" - ] - ], - "timestamp": "2024-01-09T23:01:18.710682" - } -} \ No newline at end of file diff --git a/modules/nf-core/custom/dumpsoftwareversions/tests/tags.yml b/modules/nf-core/custom/dumpsoftwareversions/tests/tags.yml deleted file mode 100644 index 405aa24..0000000 --- a/modules/nf-core/custom/dumpsoftwareversions/tests/tags.yml +++ /dev/null @@ -1,2 +0,0 @@ -custom/dumpsoftwareversions: - - modules/nf-core/custom/dumpsoftwareversions/** diff --git a/modules/nf-core/deepcell/mesmer/main.nf b/modules/nf-core/deepcell/mesmer/main.nf index a62235d..f9b9436 100644 --- a/modules/nf-core/deepcell/mesmer/main.nf +++ b/modules/nf-core/deepcell/mesmer/main.nf @@ -2,7 +2,7 @@ process DEEPCELL_MESMER { tag "$meta.id" label 'process_low' - container "docker.io/vanvalenlab/deepcell-applications:0.4.1" + container "nf-core/deepcell_mesmer:0.4.1_noentry" input: tuple val(meta) , path(img) diff --git a/modules/nf-core/deepcell/mesmer/meta.yml b/modules/nf-core/deepcell/mesmer/meta.yml index dec360b..0292aa7 100644 --- a/modules/nf-core/deepcell/mesmer/meta.yml +++ b/modules/nf-core/deepcell/mesmer/meta.yml @@ -12,42 +12,44 @@ tools: tool_dev_url: "https://githu/b.com/vanvalenlab/deepcell-tf" doi: 10.1038/s41587-021-01094-0 licence: ["APACHE2"] + identifier: biotools:deepcell input: # Only when we have meta - - meta: - type: map - description: | - Groovy Map containing sample information - e.g. [ id:'test', single_end:false ] - - img: - type: file - description: Multichannel image file - pattern: "*.{tiff,tif,h5,hdf5}" - - meta2: - type: map - description: | - Groovy Map containing sample information - e.g. [ id:'test', single_end:false ] - - membrane_img: - type: file - description: Optional membrane image to be provided separately. - pattern: "*.{tiff,tif,h5,hdf5}" + - - meta: + type: map + description: | + Groovy Map containing sample information + e.g. [ id:'test', single_end:false ] + - img: + type: file + description: Multichannel image file + pattern: "*.{tiff,tif,h5,hdf5}" + - - meta2: + type: map + description: | + Groovy Map containing sample information + e.g. [ id:'test', single_end:false ] + - membrane_img: + type: file + description: Optional membrane image to be provided separately. + pattern: "*.{tiff,tif,h5,hdf5}" output: #Only when we have meta - - meta: - type: map - description: | - Groovy Map containing sample information - e.g. [ id:'test', single_end:false ] - mask: - type: file - description: File containing the mask. - pattern: "*.{tif, tiff}" + - meta: + type: map + description: | + Groovy Map containing sample information + e.g. [ id:'test', single_end:false ] + - "*.tif": + type: file + description: File containing the mask. + pattern: "*.{tif, tiff}" - versions: - type: file - description: File containing software versions - pattern: "versions.yml" - + - versions.yml: + type: file + description: File containing software versions + pattern: "versions.yml" authors: - "@migueLib" - "@chiarasch" diff --git a/modules/nf-core/deepcell/mesmer/tests/main.nf.test b/modules/nf-core/deepcell/mesmer/tests/main.nf.test index 9546c1e..d960a54 100644 --- a/modules/nf-core/deepcell/mesmer/tests/main.nf.test +++ b/modules/nf-core/deepcell/mesmer/tests/main.nf.test @@ -17,7 +17,7 @@ nextflow_process { """ input[0] = [ [ id: 'test_img' ], - file(params.test_data['imaging']['segmentation']['image'], checkIfExists: true) + file(params.modules_testdata_base_path + 'imaging/segmentation/cycif_tonsil_registered.ome.tif', checkIfExists: true) ] input[1] = [ [:], diff --git a/modules/nf-core/deepcell/mesmer/tests/main.nf.test.snap b/modules/nf-core/deepcell/mesmer/tests/main.nf.test.snap index e6ac463..0b7eb2a 100644 --- a/modules/nf-core/deepcell/mesmer/tests/main.nf.test.snap +++ b/modules/nf-core/deepcell/mesmer/tests/main.nf.test.snap @@ -5,7 +5,11 @@ "versions.yml:md5,922bf813163d265f8a7f12fa09fc18c2" ] ], - "timestamp": "2023-12-06T11:11:13.513166311" + "meta": { + "nf-test": "0.8.4", + "nextflow": "23.10.1" + }, + "timestamp": "2024-03-18T13:44:19.214421951" }, "mask": { "content": [ @@ -18,6 +22,10 @@ ] ] ], - "timestamp": "2023-12-06T11:04:34.263500517" + "meta": { + "nf-test": "0.8.4", + "nextflow": "23.10.1" + }, + "timestamp": "2024-03-18T13:44:19.190927583" } } \ No newline at end of file diff --git a/modules/nf-core/deepcell/mesmer/tests/nextflow.config b/modules/nf-core/deepcell/mesmer/tests/nextflow.config index b0c3e0f..b55cfa0 100644 --- a/modules/nf-core/deepcell/mesmer/tests/nextflow.config +++ b/modules/nf-core/deepcell/mesmer/tests/nextflow.config @@ -6,6 +6,3 @@ process { } } - -docker.runOptions = '--entrypoint ""' -singularity.runOptions = '-B "$HOME"' diff --git a/modules/nf-core/ilastik/multicut/environment.yml b/modules/nf-core/ilastik/multicut/environment.yml deleted file mode 100644 index ecd6fa3..0000000 --- a/modules/nf-core/ilastik/multicut/environment.yml +++ /dev/null @@ -1,5 +0,0 @@ -name: ilastik_multicut -channels: - - conda-forge - - bioconda - - defaults diff --git a/modules/nf-core/ilastik/multicut/meta.yml b/modules/nf-core/ilastik/multicut/meta.yml index cb2af37..cab8ffa 100644 --- a/modules/nf-core/ilastik/multicut/meta.yml +++ b/modules/nf-core/ilastik/multicut/meta.yml @@ -1,58 +1,66 @@ name: "ilastik_multicut" -description: Ilastik is a tool that utilizes machine learning algorithms to classify pixels, segment, track and count cells in images. Ilastik contains a graphical user interface to interactively label pixels. However, this nextflow module will implement the --headless mode, to apply pixel classification using a pre-trained .ilp file on an input image. +description: Ilastik is a tool that utilizes machine learning algorithms to classify + pixels, segment, track and count cells in images. Ilastik contains a graphical user + interface to interactively label pixels. However, this nextflow module will implement + the --headless mode, to apply pixel classification using a pre-trained .ilp file + on an input image. keywords: - multicut - segmentation - pixel classification tools: - "ilastik": - description: "Ilastik is a user friendly tool that enables pixel classification, segmentation and analysis." + description: "Ilastik is a user friendly tool that enables pixel classification, + segmentation and analysis." homepage: "https://www.ilastik.org/" documentation: "https://www.ilastik.org/documentation/" tool_dev_url: "https://github.com/ilastik/ilastik" - licence: "GPL3" + license: ["GPL3"] + identifier: biotools:ilastik input: - - meta: - type: map - description: | - Groovy Map containing sample information - e.g. [ id:'test', single_end:false ] - - h5: - type: file - description: h5 file containing image stack to classify file - pattern: "*.{h5,hdf5}" - - meta2: - type: map - description: | - Groovy Map containing sample information - e.g. [ id:'test', single_end:false ] - - ilp: - type: file - description: Trained ilastik .ilp project file - pattern: "*.{ilp}" - - meta3: - type: map - description: | - Groovy Map containing sample information - e.g. [ id:'test', single_end:false ] - - probs: - type: file - description: Probability map for boundary based segmentation - pattern: "*.{h5,,hdf5}" + - - meta: + type: map + description: | + Groovy Map containing sample information + e.g. [ id:'test', single_end:false ] + - h5: + type: file + description: h5 file containing image stack to classify file + pattern: "*.{h5,hdf5}" + - - meta2: + type: map + description: | + Groovy Map containing sample information + e.g. [ id:'test', single_end:false ] + - ilp: + type: file + description: Trained ilastik .ilp project file + pattern: "*.{ilp}" + - - meta3: + type: map + description: | + Groovy Map containing sample information + e.g. [ id:'test', single_end:false ] + - probs: + type: file + description: Probability map for boundary based segmentation + pattern: "*.{h5,,hdf5}" output: - - meta: - type: map - description: | - Groovy Map containing sample information - e.g. [ id:'test', single_end:false ] - - versions: - type: file - description: File containing software versions - pattern: "versions.yml" - out_tiff: - type: file - description: Multicut segmentation mask output. - pattern: "*.{tiff}" + - meta: + type: map + description: | + Groovy Map containing sample information + e.g. [ id:'test', single_end:false ] + - "*.tiff": + type: file + description: Multicut segmentation mask output. + pattern: "*.{tiff}" + - versions: + - versions.yml: + type: file + description: File containing software versions + pattern: "versions.yml" authors: - "@FloWuenne" maintainers: diff --git a/modules/nf-core/ilastik/pixelclassification/environment.yml b/modules/nf-core/ilastik/pixelclassification/environment.yml deleted file mode 100644 index a8dafc2..0000000 --- a/modules/nf-core/ilastik/pixelclassification/environment.yml +++ /dev/null @@ -1,5 +0,0 @@ -name: ilastik_pixelclassification -channels: - - conda-forge - - bioconda - - defaults diff --git a/modules/nf-core/ilastik/pixelclassification/meta.yml b/modules/nf-core/ilastik/pixelclassification/meta.yml index 6a9e8ba..5d0403b 100644 --- a/modules/nf-core/ilastik/pixelclassification/meta.yml +++ b/modules/nf-core/ilastik/pixelclassification/meta.yml @@ -1,47 +1,55 @@ name: "ilastik_pixelclassification" -description: Ilastik is a tool that utilizes machine learning algorithms to classify pixels, segment, track and count cells in images. Ilastik contains a graphical user interface to interactively label pixels. However, this nextflow module will implement the --headless mode, to apply pixel classification using a pre-trained .ilp file on an input image. +description: Ilastik is a tool that utilizes machine learning algorithms to classify + pixels, segment, track and count cells in images. Ilastik contains a graphical user + interface to interactively label pixels. However, this nextflow module will implement + the --headless mode, to apply pixel classification using a pre-trained .ilp file + on an input image. keywords: - pixel_classification - segmentation - probability_maps tools: - "ilastik": - description: "Ilastik is a user friendly tool that enables pixel classification, segmentation and analysis." + description: "Ilastik is a user friendly tool that enables pixel classification, + segmentation and analysis." homepage: "https://www.ilastik.org/" documentation: "https://www.ilastik.org/documentation/" tool_dev_url: "https://github.com/ilastik/ilastik" - licence: "GPL3" + licence: ["GPL3"] + identifier: biotools:ilastik input: - - meta: - type: map - description: | - Groovy Map containing sample information for h5 file - e.g. [ id:'test', single_end:false ] - - input_img: - type: file - description: Input img file containing image stack to classify - - meta2: - type: map - description: | - Groovy Map containing sample information for ilp file - e.g. [ id:'test', single_end:false ] - - ilp: - type: file - description: Trained ilastik pixel classification .ilp project file - pattern: "*.{ilp}" + - - meta: + type: map + description: | + Groovy Map containing sample information for h5 file + e.g. [ id:'test', single_end:false ] + - input_img: + type: file + description: Input img file containing image stack to classify + - - meta2: + type: map + description: | + Groovy Map containing sample information for ilp file + e.g. [ id:'test', single_end:false ] + - ilp: + type: file + description: Trained ilastik pixel classification .ilp project file + pattern: "*.{ilp}" output: - - meta: - type: map - description: | - Groovy Map containing sample information - e.g. [ id:'test', single_end:false ] - - versions: - type: file - description: File containing software versions - pattern: "versions.yml" - output: - type: file - description: Output file from ilastik pixel classification. + - meta: + type: map + description: | + Groovy Map containing sample information + e.g. [ id:'test', single_end:false ] + - "*.${suffix}": + type: file + description: Output file from ilastik pixel classification. + - versions: + - versions.yml: + type: file + description: File containing software versions + pattern: "versions.yml" authors: - "@FloWuenne" maintainers: diff --git a/modules/nf-core/mindagap/duplicatefinder/environment.yml b/modules/nf-core/mindagap/duplicatefinder/environment.yml index ad98152..f9b2a1a 100644 --- a/modules/nf-core/mindagap/duplicatefinder/environment.yml +++ b/modules/nf-core/mindagap/duplicatefinder/environment.yml @@ -1,7 +1,7 @@ -name: "MINDAGAP_DUPLICATEFINDER" +--- +# yaml-language-server: $schema=https://raw.githubusercontent.com/nf-core/modules/master/modules/environment-schema.json channels: - conda-forge - bioconda - - defaults dependencies: - - "bioconda::mindagap=0.0.2" + - bioconda::mindagap=0.0.2 diff --git a/modules/nf-core/mindagap/duplicatefinder/meta.yml b/modules/nf-core/mindagap/duplicatefinder/meta.yml index b92d419..437bdcb 100644 --- a/modules/nf-core/mindagap/duplicatefinder/meta.yml +++ b/modules/nf-core/mindagap/duplicatefinder/meta.yml @@ -6,38 +6,42 @@ keywords: - spatial_transcriptomics tools: - "mindagap": - description: "Takes a single panorama image and fills the empty grid lines with neighbour-weighted values." + description: "Takes a single panorama image and fills the empty grid lines with + neighbour-weighted values." homepage: "https://github.com/ViriatoII/MindaGap/blob/main/README.md" documentation: "https://github.com/ViriatoII/MindaGap/blob/main/README.md" tool_dev_url: "https://github.com/ViriatoII/MindaGap" licence: ["BSD 3-clause License"] + identifier: "" input: - - meta: - type: map - description: | - Groovy Map containing sample information - e.g. [ id:'test' ] - - spot_table: - type: file - description: tsv file containing one spot per row with order x,y,z,gene without column header. - pattern: "*.{tsv,txt}" - + - - meta: + type: map + description: | + Groovy Map containing sample information + e.g. [ id:'test' ] + - spot_table: + type: file + description: tsv file containing one spot per row with order x,y,z,gene without + column header. + pattern: "*.{tsv,txt}" output: - - meta: - type: map - description: | - Groovy Map containing sample information - e.g. [ id:'test' ] - - versions: - type: file - description: File containing software versions - pattern: "versions.yml" - marked_dups_spots: - type: file - description: tsv file containing one spot per row, with duplicated spots labeled with "Duplicated" in their gene column. - pattern: "*.{markedDups.txt}" - + - meta: + type: map + description: | + Groovy Map containing sample information + e.g. [ id:'test' ] + - "*markedDups.txt": + type: file + description: tsv file containing one spot per row, with duplicated spots labeled + with "Duplicated" in their gene column. + pattern: "*.{markedDups.txt}" + - versions: + - versions.yml: + type: file + description: File containing software versions + pattern: "versions.yml" authors: - "@FloWuenne" maintainers: diff --git a/modules/nf-core/mindagap/mindagap/environment.yml b/modules/nf-core/mindagap/mindagap/environment.yml index f380fee..f9b2a1a 100644 --- a/modules/nf-core/mindagap/mindagap/environment.yml +++ b/modules/nf-core/mindagap/mindagap/environment.yml @@ -1,7 +1,7 @@ -name: mindagap_mindagap +--- +# yaml-language-server: $schema=https://raw.githubusercontent.com/nf-core/modules/master/modules/environment-schema.json channels: - conda-forge - bioconda - - defaults dependencies: - bioconda::mindagap=0.0.2 diff --git a/modules/nf-core/mindagap/mindagap/meta.yml b/modules/nf-core/mindagap/mindagap/meta.yml index efd0000..ef61740 100644 --- a/modules/nf-core/mindagap/mindagap/meta.yml +++ b/modules/nf-core/mindagap/mindagap/meta.yml @@ -1,40 +1,47 @@ name: "mindagap_mindagap" -description: Takes a single panorama image and fills the empty grid lines with neighbour-weighted values. +description: Takes a single panorama image and fills the empty grid lines with neighbour-weighted + values. keywords: - imaging - resolve_bioscience - spatial_transcriptomics tools: - "mindagap": - description: "Mindagap is a collection of tools to process multiplexed FISH data, such as produced by Resolve Biosciences Molecular Cartography." + description: "Mindagap is a collection of tools to process multiplexed FISH data, + such as produced by Resolve Biosciences Molecular Cartography." homepage: "https://github.com/ViriatoII/MindaGap" documentation: "https://github.com/ViriatoII/MindaGap/blob/main/README.md" tool_dev_url: "https://github.com/ViriatoII/MindaGap" licence: ["BSD-3-Clause license"] + identifier: "" input: - - meta: - type: map - description: | - Groovy Map containing sample information - e.g. [ id:'test', single_end:false ] - - panorama: - type: file - description: A tiff file containing gridlines as produced by Molecular Cartography imaging. - pattern: "*.{tif,tiff}" + - - meta: + type: map + description: | + Groovy Map containing sample information + e.g. [ id:'test', single_end:false ] + - panorama: + type: file + description: A tiff file containing gridlines as produced by Molecular Cartography + imaging. + pattern: "*.{tif,tiff}" output: - - meta: - type: map - description: | - Groovy Map containing sample information - e.g. [ id:'test', single_end:false ] - - versions: - type: file - description: File containing software versions - pattern: "versions.yml" - tiff: - type: file - description: A tiff file with gridlines filled based on consecutive gaussian blurring. - pattern: "*.{tiff}" + - meta: + type: map + description: | + Groovy Map containing sample information + e.g. [ id:'test', single_end:false ] + - "*.{tif,tiff}": + type: file + description: A tiff file with gridlines filled based on consecutive gaussian + blurring. + pattern: "*.{tiff}" + - versions: + - versions.yml: + type: file + description: File containing software versions + pattern: "versions.yml" authors: - "@ViriatoII" - "@flowuenne" diff --git a/modules/nf-core/multiqc/environment.yml b/modules/nf-core/multiqc/environment.yml index 7625b75..c3b3413 100644 --- a/modules/nf-core/multiqc/environment.yml +++ b/modules/nf-core/multiqc/environment.yml @@ -1,7 +1,7 @@ -name: multiqc +--- +# yaml-language-server: $schema=https://raw.githubusercontent.com/nf-core/modules/master/modules/environment-schema.json channels: - conda-forge - bioconda - - defaults dependencies: - - bioconda::multiqc=1.19 + - bioconda::multiqc=1.27 diff --git a/modules/nf-core/multiqc/main.nf b/modules/nf-core/multiqc/main.nf index 1b9f7c4..58d9313 100644 --- a/modules/nf-core/multiqc/main.nf +++ b/modules/nf-core/multiqc/main.nf @@ -3,14 +3,16 @@ process MULTIQC { conda "${moduleDir}/environment.yml" container "${ workflow.containerEngine == 'singularity' && !task.ext.singularity_pull_docker_container ? - 'https://depot.galaxyproject.org/singularity/multiqc:1.19--pyhdfd78af_0' : - 'biocontainers/multiqc:1.19--pyhdfd78af_0' }" + 'https://depot.galaxyproject.org/singularity/multiqc:1.27--pyhdfd78af_0' : + 'biocontainers/multiqc:1.27--pyhdfd78af_0' }" input: path multiqc_files, stageAs: "?/*" path(multiqc_config) path(extra_multiqc_config) path(multiqc_logo) + path(replace_names) + path(sample_names) output: path "*multiqc_report.html", emit: report @@ -23,16 +25,22 @@ process MULTIQC { script: def args = task.ext.args ?: '' + def prefix = task.ext.prefix ? "--filename ${task.ext.prefix}.html" : '' def config = multiqc_config ? "--config $multiqc_config" : '' def extra_config = extra_multiqc_config ? "--config $extra_multiqc_config" : '' - def logo = multiqc_logo ? /--cl-config 'custom_logo: "${multiqc_logo}"'/ : '' + def logo = multiqc_logo ? "--cl-config 'custom_logo: \"${multiqc_logo}\"'" : '' + def replace = replace_names ? "--replace-names ${replace_names}" : '' + def samples = sample_names ? "--sample-names ${sample_names}" : '' """ multiqc \\ --force \\ $args \\ $config \\ + $prefix \\ $extra_config \\ $logo \\ + $replace \\ + $samples \\ . cat <<-END_VERSIONS > versions.yml @@ -44,7 +52,7 @@ process MULTIQC { stub: """ mkdir multiqc_data - touch multiqc_plots + mkdir multiqc_plots touch multiqc_report.html cat <<-END_VERSIONS > versions.yml diff --git a/modules/nf-core/multiqc/meta.yml b/modules/nf-core/multiqc/meta.yml index 45a9bc3..b16c187 100644 --- a/modules/nf-core/multiqc/meta.yml +++ b/modules/nf-core/multiqc/meta.yml @@ -1,5 +1,6 @@ name: multiqc -description: Aggregate results from bioinformatics analyses across many samples into a single report +description: Aggregate results from bioinformatics analyses across many samples into + a single report keywords: - QC - bioinformatics tools @@ -12,40 +13,59 @@ tools: homepage: https://multiqc.info/ documentation: https://multiqc.info/docs/ licence: ["GPL-3.0-or-later"] + identifier: biotools:multiqc input: - - multiqc_files: - type: file - description: | - List of reports / files recognised by MultiQC, for example the html and zip output of FastQC - - multiqc_config: - type: file - description: Optional config yml for MultiQC - pattern: "*.{yml,yaml}" - - extra_multiqc_config: - type: file - description: Second optional config yml for MultiQC. Will override common sections in multiqc_config. - pattern: "*.{yml,yaml}" - - multiqc_logo: - type: file - description: Optional logo file for MultiQC - pattern: "*.{png}" + - - multiqc_files: + type: file + description: | + List of reports / files recognised by MultiQC, for example the html and zip output of FastQC + - - multiqc_config: + type: file + description: Optional config yml for MultiQC + pattern: "*.{yml,yaml}" + - - extra_multiqc_config: + type: file + description: Second optional config yml for MultiQC. Will override common sections + in multiqc_config. + pattern: "*.{yml,yaml}" + - - multiqc_logo: + type: file + description: Optional logo file for MultiQC + pattern: "*.{png}" + - - replace_names: + type: file + description: | + Optional two-column sample renaming file. First column a set of + patterns, second column a set of corresponding replacements. Passed via + MultiQC's `--replace-names` option. + pattern: "*.{tsv}" + - - sample_names: + type: file + description: | + Optional TSV file with headers, passed to the MultiQC --sample_names + argument. + pattern: "*.{tsv}" output: - report: - type: file - description: MultiQC report file - pattern: "multiqc_report.html" + - "*multiqc_report.html": + type: file + description: MultiQC report file + pattern: "multiqc_report.html" - data: - type: directory - description: MultiQC data dir - pattern: "multiqc_data" + - "*_data": + type: directory + description: MultiQC data dir + pattern: "multiqc_data" - plots: - type: file - description: Plots created by MultiQC - pattern: "*_data" + - "*_plots": + type: file + description: Plots created by MultiQC + pattern: "*_data" - versions: - type: file - description: File containing software versions - pattern: "versions.yml" + - versions.yml: + type: file + description: File containing software versions + pattern: "versions.yml" authors: - "@abhi18av" - "@bunop" diff --git a/modules/nf-core/multiqc/tests/main.nf.test b/modules/nf-core/multiqc/tests/main.nf.test index d0438ed..33316a7 100644 --- a/modules/nf-core/multiqc/tests/main.nf.test +++ b/modules/nf-core/multiqc/tests/main.nf.test @@ -3,19 +3,24 @@ nextflow_process { name "Test Process MULTIQC" script "../main.nf" process "MULTIQC" + tag "modules" tag "modules_nfcore" tag "multiqc" + config "./nextflow.config" + test("sarscov2 single-end [fastqc]") { when { process { """ - input[0] = Channel.of([file(params.test_data['sarscov2']['illumina']['test_1_fastq_gz_fastqc_zip'], checkIfExists: true)]) + input[0] = Channel.of(file(params.modules_testdata_base_path + 'genomics/sarscov2/illumina/fastqc/test_fastqc.zip', checkIfExists: true)) input[1] = [] input[2] = [] input[3] = [] + input[4] = [] + input[5] = [] """ } } @@ -25,7 +30,7 @@ nextflow_process { { assert process.success }, { assert process.out.report[0] ==~ ".*/multiqc_report.html" }, { assert process.out.data[0] ==~ ".*/multiqc_data" }, - { assert snapshot(process.out.versions).match("versions") } + { assert snapshot(process.out.versions).match("multiqc_versions_single") } ) } @@ -36,10 +41,12 @@ nextflow_process { when { process { """ - input[0] = Channel.of([file(params.test_data['sarscov2']['illumina']['test_1_fastq_gz_fastqc_zip'], checkIfExists: true)]) + input[0] = Channel.of(file(params.modules_testdata_base_path + 'genomics/sarscov2/illumina/fastqc/test_fastqc.zip', checkIfExists: true)) input[1] = Channel.of(file("https://github.com/nf-core/tools/raw/dev/nf_core/pipeline-template/assets/multiqc_config.yml", checkIfExists: true)) input[2] = [] input[3] = [] + input[4] = [] + input[5] = [] """ } } @@ -49,7 +56,7 @@ nextflow_process { { assert process.success }, { assert process.out.report[0] ==~ ".*/multiqc_report.html" }, { assert process.out.data[0] ==~ ".*/multiqc_data" }, - { assert snapshot(process.out.versions).match("versions") } + { assert snapshot(process.out.versions).match("multiqc_versions_config") } ) } } @@ -61,10 +68,12 @@ nextflow_process { when { process { """ - input[0] = Channel.of([file(params.test_data['sarscov2']['illumina']['test_1_fastq_gz_fastqc_zip'], checkIfExists: true)]) + input[0] = Channel.of(file(params.modules_testdata_base_path + 'genomics/sarscov2/illumina/fastqc/test_fastqc.zip', checkIfExists: true)) input[1] = [] input[2] = [] input[3] = [] + input[4] = [] + input[5] = [] """ } } @@ -75,7 +84,7 @@ nextflow_process { { assert snapshot(process.out.report.collect { file(it).getName() } + process.out.data.collect { file(it).getName() } + process.out.plots.collect { file(it).getName() } + - process.out.versions ).match() } + process.out.versions ).match("multiqc_stub") } ) } diff --git a/modules/nf-core/multiqc/tests/main.nf.test.snap b/modules/nf-core/multiqc/tests/main.nf.test.snap index d37e730..7b7c132 100644 --- a/modules/nf-core/multiqc/tests/main.nf.test.snap +++ b/modules/nf-core/multiqc/tests/main.nf.test.snap @@ -1,21 +1,41 @@ { - "versions": { + "multiqc_versions_single": { "content": [ [ - "versions.yml:md5,14e9a2661241abd828f4f06a7b5c222d" + "versions.yml:md5,8f3b8c1cec5388cf2708be948c9fa42f" ] ], - "timestamp": "2024-01-09T23:02:49.911994" + "meta": { + "nf-test": "0.9.2", + "nextflow": "24.10.4" + }, + "timestamp": "2025-01-27T09:29:57.631982377" }, - "sarscov2 single-end [fastqc] - stub": { + "multiqc_stub": { "content": [ [ "multiqc_report.html", "multiqc_data", "multiqc_plots", - "versions.yml:md5,14e9a2661241abd828f4f06a7b5c222d" + "versions.yml:md5,8f3b8c1cec5388cf2708be948c9fa42f" ] ], - "timestamp": "2024-01-09T23:03:14.524346" + "meta": { + "nf-test": "0.9.2", + "nextflow": "24.10.4" + }, + "timestamp": "2025-01-27T09:30:34.743726958" + }, + "multiqc_versions_config": { + "content": [ + [ + "versions.yml:md5,8f3b8c1cec5388cf2708be948c9fa42f" + ] + ], + "meta": { + "nf-test": "0.9.2", + "nextflow": "24.10.4" + }, + "timestamp": "2025-01-27T09:30:21.44383553" } } \ No newline at end of file diff --git a/modules/nf-core/multiqc/tests/nextflow.config b/modules/nf-core/multiqc/tests/nextflow.config new file mode 100644 index 0000000..c537a6a --- /dev/null +++ b/modules/nf-core/multiqc/tests/nextflow.config @@ -0,0 +1,5 @@ +process { + withName: 'MULTIQC' { + ext.prefix = null + } +} diff --git a/modules/nf-core/stardist/environment.yml b/modules/nf-core/stardist/environment.yml new file mode 100644 index 0000000..33c9530 --- /dev/null +++ b/modules/nf-core/stardist/environment.yml @@ -0,0 +1,9 @@ +channels: + - conda-forge + - bioconda + +dependencies: + - python=3.9 + - stardist==0.9.1 + - tensorflow==2.10.0 + - tifffile<2022.4.22 diff --git a/modules/nf-core/stardist/main.nf b/modules/nf-core/stardist/main.nf new file mode 100644 index 0000000..97600f5 --- /dev/null +++ b/modules/nf-core/stardist/main.nf @@ -0,0 +1,51 @@ +process STARDIST { + tag "$meta.id" + label 'process_medium' + + conda "${moduleDir}/environment.yml" + container "ghcr.io/schapirolabor/stardist:0.9.1" + + input: + tuple val(meta), path(image) + + output: + tuple val(meta), path("*.stardist.tif"), emit: mask + 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}" + + """ + stardist-predict2d \\ + -i $image \\ + -o . \\ + $args + + cat <<-END_VERSIONS > versions.yml + "${task.process}": + stardist: \$( python -m pip show --version stardist | grep "Version" | sed -e "s/Version: //g" ) + python: \$( python --version | sed -e "s/Python //g" ) + tensorflow: \$( python -m pip show --version tensorflow | grep "Version" | sed -e "s/Version: //g" ) + tifffile: \$( python -m pip show --version tifffile | grep "Version" | sed -e "s/Version: //g" ) + END_VERSIONS + """ + + stub: + def args = task.ext.args ?: '' + def prefix = task.ext.prefix ?: "${meta.id}" + """ + touch ${prefix}.stardist.tif + + cat <<-END_VERSIONS > versions.yml + "${task.process}": + stardist: \$( python -m pip show --version stardist | grep "Version" | sed -e "s/Version: //g" ) + python: \$( python --version | sed -e "s/Python //g" ) + tensorflow: \$( python -m pip show --version tensorflow | grep "Version" | sed -e "s/Version: //g" ) + tifffile: \$( python -m pip show --version tifffile | grep "Version" | sed -e "s/Version: //g" ) + END_VERSIONS + """ +} diff --git a/modules/nf-core/stardist/meta.yml b/modules/nf-core/stardist/meta.yml new file mode 100644 index 0000000..70e60b7 --- /dev/null +++ b/modules/nf-core/stardist/meta.yml @@ -0,0 +1,47 @@ +name: "stardist" +description: Cell and nuclear segmentation with star-convex shapes +keywords: + - stardist + - segmentation + - image +tools: + - "stardist": + description: "Stardist is an cell segmentation tool developed in Python by Martin + Weigert and Uwe Schmidt" + homepage: "https://stardist.net/" + documentation: "https://stardist.net/faq/" + tool_dev_url: "https://github.com/stardist/stardist" + doi: "10.1109/ISBIC56247.2022.9854534" + licence: ["BSD 3-Clause"] + identifier: "" + +input: + - - meta: + type: map + description: | + Groovy Map containing sample information + e.g. `[ id:'sample1' ]` + - image: + type: file + description: Single channel nuclear image + pattern: "*.{tiff,tif}" +output: + - mask: + - meta: + type: map + description: | + Groovy Map containing sample information + e.g. `[ id:'sample1' ]` + - "*.stardist.tif": + type: file + description: labelled mask output from stardist in tif format. + pattern: "*.{tiff,tif}" + - versions: + - versions.yml: + type: file + description: File containing software versions + pattern: "versions.yml" +authors: + - "@migueLib" +maintainers: + - "@migueLib" diff --git a/modules/nf-core/stardist/tests/main.nf.test b/modules/nf-core/stardist/tests/main.nf.test new file mode 100644 index 0000000..c511ee6 --- /dev/null +++ b/modules/nf-core/stardist/tests/main.nf.test @@ -0,0 +1,57 @@ +nextflow_process { + name "Test Process STARDIST" + script "../main.nf" + process "STARDIST" + config "./nextflow.config" + + tag "modules" + tag "modules_nfcore" + tag "stardist" + + test("stardist2d - tif") { + + when { + process { + """ + input[0] = [ + [ id:'test' ], // meta map + file(params.modules_testdata_base_path + 'imaging/segmentation/nuclear_image.tif', checkIfExists: true) + ] + """ + } + } + + then { + assertAll( + { assert process.success }, + { assert snapshot(process.out).match() } + ) + } + + } + + test("stardist2d - tif - stub") { + + options "-stub" + + when { + process { + """ + input[0] = [ + [ id:'test' ], // meta map + file(params.modules_testdata_base_path + 'imaging/segmentation/nuclear_image.tif', checkIfExists: true) + ] + """ + } + } + + then { + assertAll( + { assert process.success }, + { assert snapshot(process.out).match() } + ) + } + + } + +} diff --git a/modules/nf-core/stardist/tests/main.nf.test.snap b/modules/nf-core/stardist/tests/main.nf.test.snap new file mode 100644 index 0000000..3a97fc4 --- /dev/null +++ b/modules/nf-core/stardist/tests/main.nf.test.snap @@ -0,0 +1,68 @@ +{ + "stardist2d - tif - stub": { + "content": [ + { + "0": [ + [ + { + "id": "test" + }, + "test.stardist.tif:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "1": [ + "versions.yml:md5,2cf7aba2a90e1053a51e1d58ff25a4bf" + ], + "mask": [ + [ + { + "id": "test" + }, + "test.stardist.tif:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "versions": [ + "versions.yml:md5,2cf7aba2a90e1053a51e1d58ff25a4bf" + ] + } + ], + "meta": { + "nf-test": "0.9.0", + "nextflow": "24.04.4" + }, + "timestamp": "2024-08-08T14:31:08.559997122" + }, + "stardist2d - tif": { + "content": [ + { + "0": [ + [ + { + "id": "test" + }, + "nuclear_image.stardist.tif:md5,9d0f8f98554c914dd55b9ea8818fbc63" + ] + ], + "1": [ + "versions.yml:md5,2cf7aba2a90e1053a51e1d58ff25a4bf" + ], + "mask": [ + [ + { + "id": "test" + }, + "nuclear_image.stardist.tif:md5,9d0f8f98554c914dd55b9ea8818fbc63" + ] + ], + "versions": [ + "versions.yml:md5,2cf7aba2a90e1053a51e1d58ff25a4bf" + ] + } + ], + "meta": { + "nf-test": "0.9.0", + "nextflow": "24.04.4" + }, + "timestamp": "2024-08-08T14:30:57.525654244" + } +} \ No newline at end of file diff --git a/modules/nf-core/stardist/tests/nextflow.config b/modules/nf-core/stardist/tests/nextflow.config new file mode 100644 index 0000000..a251512 --- /dev/null +++ b/modules/nf-core/stardist/tests/nextflow.config @@ -0,0 +1,5 @@ +process { + withName: "STARDIST" { + ext.args = '-m 2D_versatile_fluo' + } +} diff --git a/nextflow.config b/nextflow.config index b7a0dbd..900c616 100644 --- a/nextflow.config +++ b/nextflow.config @@ -16,6 +16,7 @@ params { segmentation_method = 'mesmer' segmentation_min_area = null segmentation_max_area = null + // cellpose cellpose_save_flows = false cellpose_diameter = 30 cellpose_chan = 0 @@ -24,12 +25,19 @@ params { cellpose_custom_model = null cellpose_flow_threshold = 0.4 cellpose_edge_exclude = true + // mesmer mesmer_image_mpp = 0.138 mesmer_compartment = 'whole-cell' + // ilastik ilastik_pixel_project = null ilastik_multicut_project = null + // stardist + stardist_model = '2D_versatile_fluo' + stardist_n_tiles_x = 3 + stardist_n_tiles_y = 3 // Preprocessing command line flags + skip_mindagap = false mindagap_tilesize = 2144 mindagap_boxsize = 3 mindagap_loopnum = 40 @@ -56,164 +64,160 @@ params { multiqc_methods_description = null // Boilerplate options - outdir = null - publish_dir_mode = 'copy' - email = null - email_on_fail = null - plaintext_email = false - monochrome_logs = false - hook_url = null - help = false - version = false - - // Config options + outdir = null + publish_dir_mode = 'copy' + email = null + email_on_fail = null + plaintext_email = false + monochrome_logs = false + hook_url = null + help = false + help_full = false + show_hidden = false + version = false + pipelines_testdata_base_path = 'https://raw.githubusercontent.com/nf-core/test-datasets/' + trace_report_suffix = new java.util.Date().format( 'yyyy-MM-dd_HH-mm-ss')// Config options config_profile_name = null config_profile_description = null + custom_config_version = 'master' custom_config_base = "https://raw.githubusercontent.com/nf-core/configs/${params.custom_config_version}" config_profile_contact = null config_profile_url = null - - // Max resource options - // Defaults only, expecting to be overwritten - max_memory = '128.GB' - max_cpus = 16 - max_time = '240.h' - // Schema validation default options - validationFailUnrecognisedParams = false - validationLenientMode = false - validationSchemaIgnoreParams = 'genomes,igenomes_base' - validationShowHiddenParams = false - validate_params = true - + validate_params = true } // Load base.config by default for all pipelines includeConfig 'conf/base.config' +includeConfig 'conf/modules.config' -// Load nf-core custom profiles from different Institutions -try { - includeConfig "${params.custom_config_base}/nfcore_custom.config" -} catch (Exception e) { - System.err.println("WARNING: Could not load nf-core/config profiles: ${params.custom_config_base}/nfcore_custom.config") -} - -// Load nf-core/molkart custom profiles from different institutions. -// Warning: Uncomment only if a pipeline-specific instititutional config already exists on nf-core/configs! -// try { -// includeConfig "${params.custom_config_base}/pipeline/molkart.config" -// } catch (Exception e) { -// System.err.println("WARNING: Could not load nf-core/config/molkart profiles: ${params.custom_config_base}/pipeline/molkart.config") -// } profiles { debug { - dumpHashes = true - process.beforeScript = 'echo $HOSTNAME' - cleanup = false + dumpHashes = true + process.beforeScript = 'echo $HOSTNAME' + cleanup = false nextflow.enable.configProcessNamesValidation = true } conda { - conda.enabled = true - docker.enabled = false - singularity.enabled = false - podman.enabled = false - shifter.enabled = false - charliecloud.enabled = false - apptainer.enabled = false + conda.enabled = true + docker.enabled = false + singularity.enabled = false + podman.enabled = false + shifter.enabled = false + charliecloud.enabled = false + conda.channels = ['conda-forge', 'bioconda'] + apptainer.enabled = false } mamba { - conda.enabled = true - conda.useMamba = true - docker.enabled = false - singularity.enabled = false - podman.enabled = false - shifter.enabled = false - charliecloud.enabled = false - apptainer.enabled = false + conda.enabled = true + conda.useMamba = true + docker.enabled = false + singularity.enabled = false + podman.enabled = false + shifter.enabled = false + charliecloud.enabled = false + apptainer.enabled = false } docker { - docker.enabled = true - conda.enabled = false - singularity.enabled = false - podman.enabled = false - shifter.enabled = false - charliecloud.enabled = false - apptainer.enabled = false - docker.runOptions = '--entrypoint ""' + 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) --entrypoint ""' } arm { - docker.runOptions = '-u $(id -u):$(id -g) --platform=linux/amd64' + docker.runOptions = '-u $(id -u):$(id -g) --platform=linux/amd64' } singularity { - singularity.enabled = true - singularity.autoMounts = true - conda.enabled = false - docker.enabled = false - podman.enabled = false - shifter.enabled = false - charliecloud.enabled = false - apptainer.enabled = false + singularity.enabled = true + singularity.autoMounts = true + conda.enabled = false + docker.enabled = false + podman.enabled = false + shifter.enabled = false + charliecloud.enabled = false + apptainer.enabled = false } podman { - podman.enabled = true - conda.enabled = false - docker.enabled = false - singularity.enabled = false - shifter.enabled = false - charliecloud.enabled = false - apptainer.enabled = false + podman.enabled = true + conda.enabled = false + docker.enabled = false + singularity.enabled = false + shifter.enabled = false + charliecloud.enabled = false + apptainer.enabled = false } shifter { - shifter.enabled = true - conda.enabled = false - docker.enabled = false - singularity.enabled = false - podman.enabled = false - charliecloud.enabled = false - apptainer.enabled = false + shifter.enabled = true + conda.enabled = false + docker.enabled = false + singularity.enabled = false + podman.enabled = false + charliecloud.enabled = false + apptainer.enabled = false } charliecloud { - charliecloud.enabled = true - conda.enabled = false - docker.enabled = false - singularity.enabled = false - podman.enabled = false - shifter.enabled = false - apptainer.enabled = false + charliecloud.enabled = true + conda.enabled = false + docker.enabled = false + singularity.enabled = false + podman.enabled = false + shifter.enabled = false + apptainer.enabled = false } apptainer { - apptainer.enabled = true - apptainer.autoMounts = true - conda.enabled = false - docker.enabled = false - singularity.enabled = false - podman.enabled = false - shifter.enabled = false - charliecloud.enabled = false + apptainer.enabled = true + apptainer.autoMounts = true + conda.enabled = false + docker.enabled = false + singularity.enabled = false + podman.enabled = false + shifter.enabled = false + charliecloud.enabled = false + } + wave { + apptainer.ociAutoPull = true + singularity.ociAutoPull = true + wave.enabled = true + wave.freeze = true + wave.strategy = 'conda,container' } gitpod { - executor.name = 'local' - executor.cpus = 4 - executor.memory = 8.GB + executor.name = 'local' + executor.cpus = 4 + executor.memory = 8.GB + process { + resourceLimits = [ + memory: 8.GB, + cpus : 4, + time : 1.h + ] + } } test { includeConfig 'conf/test.config' } test_full { includeConfig 'conf/test_full.config' } } -// Set default registry for Apptainer, Docker, Podman and Singularity independent of -profile -// Will not be used unless Apptainer / Docker / Podman / Singularity are enabled +// Load nf-core custom profiles from different Institutions +includeConfig !System.getenv('NXF_OFFLINE') && params.custom_config_base ? "${params.custom_config_base}/nfcore_custom.config" : "/dev/null" + +// Load nf-core/molkart custom profiles from different institutions. +includeConfig !System.getenv('NXF_OFFLINE') && params.custom_config_base ? "${params.custom_config_base}/pipeline/molkart.config" : "/dev/null" + +// Set default registry for Apptainer, Docker, Podman, Charliecloud and Singularity independent of -profile +// Will not be used unless Apptainer / Docker / Podman / Charliecloud / Singularity are enabled // Set to your registry if you have a mirror of containers -apptainer.registry = 'quay.io' -docker.registry = 'quay.io' -podman.registry = 'quay.io' -singularity.registry = 'quay.io' +apptainer.registry = 'quay.io' +docker.registry = 'quay.io' +podman.registry = 'quay.io' +singularity.registry = 'quay.io' +charliecloud.registry = 'quay.io' -// Nextflow plugins -plugins { - id 'nf-validation@1.1.3' // Validation of pipeline parameters and creation of an input channel from a sample sheet -} // Export these variables to prevent local Python/R libraries from conflicting with those in the container // The JULIA depot path has been adjusted to a fixed path `/usr/local/share/julia` that needs to be used for packages in the container. @@ -226,73 +230,102 @@ env { JULIA_DEPOT_PATH = "/usr/local/share/julia" } -// Capture exit codes from upstream processes when piping -process.shell = ['/bin/bash', '-euo', 'pipefail'] +// Set bash options +process.shell = [ + "bash", + "-C", // No clobber - prevent output redirection from overwriting files. + "-e", // Exit if a tool returns a non-zero status/exit code + "-u", // Treat unset variables and parameters as an error + "-o", // Returns the status of the last command to exit.. + "pipefail" // ..with a non-zero status or zero if all successfully execute +] // Disable process selector warnings by default. Use debug profile to enable warnings. nextflow.enable.configProcessNamesValidation = false -def trace_timestamp = new java.util.Date().format( 'yyyy-MM-dd_HH-mm-ss') timeline { enabled = true - file = "${params.outdir}/pipeline_info/execution_timeline_${trace_timestamp}.html" + file = "${params.outdir}/pipeline_info/execution_timeline_${params.trace_report_suffix}.html" } report { enabled = true - file = "${params.outdir}/pipeline_info/execution_report_${trace_timestamp}.html" + file = "${params.outdir}/pipeline_info/execution_report_${params.trace_report_suffix}.html" } trace { enabled = true - file = "${params.outdir}/pipeline_info/execution_trace_${trace_timestamp}.txt" + file = "${params.outdir}/pipeline_info/execution_trace_${params.trace_report_suffix}.txt" } dag { enabled = true - file = "${params.outdir}/pipeline_info/pipeline_dag_${trace_timestamp}.html" + file = "${params.outdir}/pipeline_info/pipeline_dag_${params.trace_report_suffix}.html" } manifest { name = 'nf-core/molkart' - author = """@kbestak, @FloWuenne""" + author = """@kbestak, @FloWuenne""" // The author field is deprecated from Nextflow version 24.10.0, use contributors instead + contributors = [ + [ + name: 'Kresimir Bestak', + affiliation: 'University Hospital Heidelberg', + email: 'kresimir.bestak@uni-heidelberg.de', + github: '@kbestak', + contribution: ['author', 'maintainer'], // List of contribution types ('author', 'maintainer' or 'contributor') + orcid: '0009-0009-8245-9846' + ], + [ + name: ' Florian Wuennemann', + affiliation: 'Seqera', + email: 'flowuenne@gmail.com', + github: '@flowuenne', + contribution: ['author', 'maintainer'], // List of contribution types ('author', 'maintainer' or 'contributor') + orcid: '0000-0002-0978-6076' + ], + ] homePage = 'https://github.com/nf-core/molkart' description = """An analysis pipeline for Molecular Cartography data from Resolve Biosciences.""" mainScript = 'main.nf' - nextflowVersion = '!>=23.04.0' - version = '1.0.0' - doi = '' + defaultBranch = 'master' + nextflowVersion = '!>=24.04.2' + version = '1.1.0' + doi = 'https://doi.org/10.5281/zenodo.10650748' } -// Load modules.config for DSL2 module specific options -includeConfig 'conf/modules.config' +// Nextflow plugins +plugins { + id 'nf-schema@2.3.0' // Validation of pipeline parameters and creation of an input channel from a sample sheet +} -// 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 - } +validation { + defaultIgnoreParams = ["genomes"] + monochromeLogs = params.monochrome_logs + help { + enabled = true + command = "nextflow run nf-core/molkart -profile --input samplesheet.csv --outdir " + fullParameter = "help_full" + showHiddenParameter = "show_hidden" + beforeText = """ +-\033[2m----------------------------------------------------\033[0m- + \033[0;32m,--.\033[0;30m/\033[0;32m,-.\033[0m +\033[0;34m ___ __ __ __ ___ \033[0;32m/,-._.--~\'\033[0m +\033[0;34m |\\ | |__ __ / ` / \\ |__) |__ \033[0;33m} {\033[0m +\033[0;34m | \\| | \\__, \\__/ | \\ |___ \033[0;32m\\`-._,-`-,\033[0m + \033[0;32m`._,._,\'\033[0m +\033[0;35m nf-core/molkart ${manifest.version}\033[0m +-\033[2m----------------------------------------------------\033[0m- +""" + afterText = """${manifest.doi ? "\n* The pipeline\n" : ""}${manifest.doi.tokenize(",").collect { " https://doi.org/${it.trim().replace('https://doi.org/','')}"}.join("\n")}${manifest.doi ? "\n" : ""} +* The nf-core framework + https://doi.org/10.1038/s41587-020-0439-x + +* Software dependencies + https://github.com/nf-core/molkart/blob/master/CITATIONS.md +""" + } + summary { + beforeText = validation.help.beforeText + afterText = validation.help.afterText } } + +// Load modules.config for DSL2 module specific options +includeConfig 'conf/modules.config' diff --git a/nextflow_schema.json b/nextflow_schema.json index 0d94107..29145b0 100644 --- a/nextflow_schema.json +++ b/nextflow_schema.json @@ -1,10 +1,10 @@ { - "$schema": "http://json-schema.org/draft-07/schema", + "$schema": "https://json-schema.org/draft/2020-12/schema", "$id": "https://raw.githubusercontent.com/nf-core/molkart/master/nextflow_schema.json", "title": "nf-core/molkart pipeline parameters", "description": "An analysis pipeline for Molecular Cartography data from Resolve Biosciences.", "type": "object", - "definitions": { + "$defs": { "segmentation_methods_and_options": { "title": "Segmentation methods and options", "type": "object", @@ -24,7 +24,7 @@ }, "segmentation_max_area": { "type": "integer", - "description": "Maximum area size (in pixels) for segmenation masks.", + "description": "Maximum area size (in pixels) for segmentation masks.", "fa_icon": "fas fa-chart-area" }, "cellpose_diameter": { @@ -96,6 +96,25 @@ "description": "Provide ilastik with a multicut project to create segmentation masks.", "format": "file-path", "fa_icon": "fas fa-cut" + }, + "stardist_model": { + "type": "string", + "default": "2D_versatile_fluo", + "fa_icon": "fas fa-user-cog", + "enum": ["2D_versatile_fluo", "2D_paper_dsb2018", "2D_versatile_he"], + "description": "Model to use for segmentation with stardist." + }, + "stardist_n_tiles_x": { + "type": "integer", + "default": 3, + "description": "Number of tiles on the X axis for Stardist.", + "fa_icon": "fas fa-th" + }, + "stardist_n_tiles_y": { + "type": "integer", + "default": 3, + "description": "Number of tiles on the Y axis for Stardist.", + "fa_icon": "fas fa-th" } }, "required": ["segmentation_method"], @@ -108,6 +127,11 @@ "default": "", "fa_icon": "fas fa-adjust", "properties": { + "skip_mindagap": { + "type": "boolean", + "fa_icon": "fas fa-th-large", + "description": "Skip mindagap if your data does not contain gaps between tiles." + }, "mindagap_boxsize": { "type": "integer", "default": 3, @@ -140,7 +164,7 @@ }, "clahe_kernel": { "type": "number", - "default": 25, + "default": 25.0, "description": "Kernel size to be used by CLAHE.", "fa_icon": "far fa-object-group" }, @@ -245,8 +269,7 @@ "description": "MultiQC report title. Printed as page header, used for filename if not otherwise specified.", "fa_icon": "fas fa-file-signature" } - }, - "help_text": "" + } }, "institutional_config_options": { "title": "Institutional config options", @@ -296,41 +319,6 @@ } } }, - "max_job_request_options": { - "title": "Max job request options", - "type": "object", - "fa_icon": "fab fa-acquisitions-incorporated", - "description": "Set the top limit for requested resources for any single job.", - "help_text": "If you are running on a smaller system, a pipeline step requesting more resources than are available may cause the Nextflow to stop the run with an error. These options allow you to cap the maximum resources requested by any single job so that the pipeline will run on your system.\n\nNote that you can not _increase_ the resources requested by any job using these options. For that you will need your own configuration file. See [the nf-core website](https://nf-co.re/usage/configuration) for details.", - "properties": { - "max_cpus": { - "type": "integer", - "description": "Maximum number of CPUs that can be requested for any single job.", - "default": 16, - "fa_icon": "fas fa-microchip", - "hidden": true, - "help_text": "Use to set an upper-limit for the CPU requirement for each process. Should be an integer e.g. `--max_cpus 1`" - }, - "max_memory": { - "type": "string", - "description": "Maximum amount of memory that can be requested for any single job.", - "default": "128.GB", - "fa_icon": "fas fa-memory", - "pattern": "^\\d+(\\.\\d+)?\\.?\\s*(K|M|G|T)?B$", - "hidden": true, - "help_text": "Use to set an upper-limit for the memory requirement for each process. Should be a string in the format integer-unit e.g. `--max_memory '8.GB'`" - }, - "max_time": { - "type": "string", - "description": "Maximum amount of time that can be requested for any single job.", - "default": "240.h", - "fa_icon": "far fa-clock", - "pattern": "^(\\d+\\.?\\s*(s|m|h|d|day)\\s*)+$", - "hidden": true, - "help_text": "Use to set an upper-limit for the time requirement for each process. Should be a string in the format integer-unit e.g. `--max_time '2.h'`" - } - } - }, "generic_options": { "title": "Generic options", "type": "object", @@ -338,12 +326,6 @@ "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.", @@ -419,51 +401,40 @@ "fa_icon": "fas fa-check-square", "hidden": true }, - "validationShowHiddenParams": { - "type": "boolean", - "fa_icon": "far fa-eye-slash", - "description": "Show all params when using `--help`", - "hidden": true, - "help_text": "By default, parameters set as _hidden_ in the schema are not shown on the command line when a user runs with `--help`. Specifying this option will tell the pipeline to show all parameters." - }, - "validationFailUnrecognisedParams": { - "type": "boolean", + "pipelines_testdata_base_path": { + "type": "string", "fa_icon": "far fa-check-circle", - "description": "Validation of parameters fails when an unrecognised parameter is found.", - "hidden": true, - "help_text": "By default, when an unrecognised parameter is found, it returns a warinig." + "description": "Base URL or local path to location of pipeline test dataset files", + "default": "https://raw.githubusercontent.com/nf-core/test-datasets/", + "hidden": true }, - "validationLenientMode": { - "type": "boolean", - "fa_icon": "far fa-check-circle", - "description": "Validation of parameters in lenient more.", - "hidden": true, - "help_text": "Allows string values that are parseable as numbers or booleans. For further information see [JSONSchema docs](https://github.com/everit-org/json-schema#lenient-mode)." + "trace_report_suffix": { + "type": "string", + "fa_icon": "far calendar", + "description": "Suffix to add to the trace report filename. Default is the date and time in the format yyyy-MM-dd_HH-mm-ss.", + "hidden": true } } } }, "allOf": [ { - "$ref": "#/definitions/segmentation_methods_and_options" - }, - { - "$ref": "#/definitions/image_preprocessing" + "$ref": "#/$defs/segmentation_methods_and_options" }, { - "$ref": "#/definitions/training_subset_options" + "$ref": "#/$defs/image_preprocessing" }, { - "$ref": "#/definitions/input_output_options" + "$ref": "#/$defs/training_subset_options" }, { - "$ref": "#/definitions/institutional_config_options" + "$ref": "#/$defs/input_output_options" }, { - "$ref": "#/definitions/max_job_request_options" + "$ref": "#/$defs/institutional_config_options" }, { - "$ref": "#/definitions/generic_options" + "$ref": "#/$defs/generic_options" } ] } diff --git a/nf-test.config b/nf-test.config index 870799d..a9166a3 100644 --- a/nf-test.config +++ b/nf-test.config @@ -1,8 +1,13 @@ config { testsDir "tests" - workDir ".nf-test" - configFile "tests/nextflow.config" + workDir System.getenv("NFT_WORKDIR") ?: ".nf-test" + configFile "" profile "" + // Include plugins + plugins { + load "nft-utils@0.0.3" + } + } diff --git a/pyproject.toml b/pyproject.toml deleted file mode 100644 index 0d62beb..0000000 --- a/pyproject.toml +++ /dev/null @@ -1,10 +0,0 @@ -# Config file for Python. Mostly used to configure linting of bin/check_samplesheet.py with Black. -# Should be kept the same as nf-core/tools to avoid fighting with template synchronisation. -[tool.black] -line-length = 120 -target_version = ["py37", "py38", "py39", "py310"] - -[tool.isort] -profile = "black" -known_first_party = ["nf_core"] -multi_line_output = 3 diff --git a/ro-crate-metadata.json b/ro-crate-metadata.json new file mode 100644 index 0000000..b533282 --- /dev/null +++ b/ro-crate-metadata.json @@ -0,0 +1,355 @@ +{ + "@context": [ + "https://w3id.org/ro/crate/1.1/context", + { + "GithubService": "https://w3id.org/ro/terms/test#GithubService", + "JenkinsService": "https://w3id.org/ro/terms/test#JenkinsService", + "PlanemoEngine": "https://w3id.org/ro/terms/test#PlanemoEngine", + "TestDefinition": "https://w3id.org/ro/terms/test#TestDefinition", + "TestInstance": "https://w3id.org/ro/terms/test#TestInstance", + "TestService": "https://w3id.org/ro/terms/test#TestService", + "TestSuite": "https://w3id.org/ro/terms/test#TestSuite", + "TravisService": "https://w3id.org/ro/terms/test#TravisService", + "definition": "https://w3id.org/ro/terms/test#definition", + "engineVersion": "https://w3id.org/ro/terms/test#engineVersion", + "instance": "https://w3id.org/ro/terms/test#instance", + "resource": "https://w3id.org/ro/terms/test#resource", + "runsOn": "https://w3id.org/ro/terms/test#runsOn" + } + ], + "@graph": [ + { + "@id": "./", + "@type": "Dataset", + "creativeWorkStatus": "Stable", + "datePublished": "2025-02-28T15:48:22+00:00", + "description": "

    \n \n \n \"nf-core/molkart\"\n \n

    \n\n[![GitHub Actions CI Status](https://github.com/nf-core/molkart/actions/workflows/ci.yml/badge.svg)](https://github.com/nf-core/molkart/actions/workflows/ci.yml)\n[![GitHub Actions Linting Status](https://github.com/nf-core/molkart/actions/workflows/linting.yml/badge.svg)](https://github.com/nf-core/molkart/actions/workflows/linting.yml)[![AWS CI](https://img.shields.io/badge/CI%20tests-full%20size-FF9900?labelColor=000000&logo=Amazon%20AWS)](https://nf-co.re/molkart/results)[![Cite with Zenodo](http://img.shields.io/badge/DOI-10.5281/zenodo.10650748-1073c8?labelColor=000000)](https://doi.org/10.5281/zenodo.10650748)\n[![nf-test](https://img.shields.io/badge/unit_tests-nf--test-337ab7.svg)](https://www.nf-test.com)\n\n[![Nextflow](https://img.shields.io/badge/nextflow%20DSL2-%E2%89%A524.04.2-23aa62.svg)](https://www.nextflow.io/)\n[![run with docker](https://img.shields.io/badge/run%20with-docker-0db7ed?labelColor=000000&logo=docker)](https://www.docker.com/)\n[![run with singularity](https://img.shields.io/badge/run%20with-singularity-1d355c.svg?labelColor=000000)](https://sylabs.io/docs/)\n[![Launch on Seqera Platform](https://img.shields.io/badge/Launch%20%F0%9F%9A%80-Seqera%20Platform-%234256e7)](https://cloud.seqera.io/launch?pipeline=https://github.com/nf-core/molkart)\n\n[![Get help on Slack](http://img.shields.io/badge/slack-nf--core%20%23molkart-4A154B?labelColor=000000&logo=slack)](https://nfcore.slack.com/channels/molkart)[![Follow on Twitter](http://img.shields.io/badge/twitter-%40nf__core-1DA1F2?labelColor=000000&logo=twitter)](https://twitter.com/nf_core)[![Follow on Mastodon](https://img.shields.io/badge/mastodon-nf__core-6364ff?labelColor=FFFFFF&logo=mastodon)](https://mstdn.science/@nf_core)[![Watch on YouTube](http://img.shields.io/badge/youtube-nf--core-FF0000?labelColor=000000&logo=youtube)](https://www.youtube.com/c/nf-core)\n\n## Introduction\n\n**nf-core/molkart** is a pipeline for processing Molecular Cartography data from Resolve Bioscience (combinatorial FISH). It takes as input a table of FISH spot positions (x,y,z,gene), a corresponding DAPI image (`TIFF` format) and optionally an additional staining image in the `TIFF` format. nf-core/molkart performs end-to-end processing of the data including image processing, QC filtering of spots, cell segmentation, spot-to-cell assignment and reports quality metrics such as the spot assignment rate, average spots per cell and segmentation mask size ranges.\n\n

    \n \n

    \n\nImage preprocessing\n\n- Fill the grid pattern in provided images ([`Mindagap`](https://github.com/ViriatoII/MindaGap))\n- Optionally apply contrast-limited adaptive histogram equalization\n- If a second (membrane) image is present, combine images into a multichannel stack (if required for segmentation)\n\nCell segmentation\n\n- Apply cell segmentation based on provided images, available options are: - [`Cellpose`](https://www.cellpose.org/) - [`Mesmer`](https://deepcell.readthedocs.io/en/master/API/deepcell.applications.html#mesmer) - [`ilastik`](https://www.ilastik.org/) - [`Stardist`](https://github.com/stardist/stardist)\n- Filter cells based on cell size to remove artifacts\n\nSpot processing\n\n- Find duplicated spots near grid lines ([`Mindagap`](https://github.com/ViriatoII/MindaGap))\n- Assign spots to segmented cells\n\nQuality control\n\n- Create quality-control metrics specific to this pipeline\n- provide them to ([`MultiQC`](http://multiqc.info/)) to create a report\n\n## Usage\n\n:::note\nIf you are new to Nextflow and nf-core, please refer to [this page](https://nf-co.re/docs/usage/installation) on how\nto set-up Nextflow. Make sure to [test your setup](https://nf-co.re/docs/usage/introduction#how-to-run-a-pipeline)\nwith `-profile test` before running the workflow on actual data.\n:::\n\nFirst, prepare a samplesheet with your input data that looks as follows:\n\n`samplesheet.csv`:\n\n```csv\nsample,nuclear_image,spot_locations,membrane_image\nsample0,sample0_DAPI.tiff,sample0_spots.txt,sample0_WGA.tiff\n```\n\nEach row represents an FOV (field-of-view). Columns represent the sample ID (all must be unique), the path to the respective nuclear image, the spot table, and optionally the path to the respective membrane image (or any additional image to improve segmentation).\n\nNow, you can run the pipeline using all default values with:\n\n```bash\nnextflow run nf-core/molkart \\\n -profile \\\n --input samplesheet.csv \\\n --outdir \n```\n\n> [!WARNING]\n> Please provide pipeline parameters via the CLI or Nextflow `-params-file` option. Custom config files including those provided by the `-c` Nextflow option can be used to provide any configuration _**except for parameters**_; see [docs](https://nf-co.re/docs/usage/getting_started/configuration#custom-configuration-files).\n\nFor more details and further functionality, please refer to the [usage documentation](https://nf-co.re/molkart/usage) and the [parameter documentation](https://nf-co.re/molkart/parameters).\n\n## Pipeline output\n\nThe pipeline outputs a matched cell-by-transcript table based on deduplicated spots and segmented cells, as well as preprocessing and segmentation intermediaries.\nTo see the results of an example test run with a full size dataset refer to the [results](https://nf-co.re/molkart/results) tab on the nf-core website pipeline page.\nFor more details about the output files and reports, please refer to the\n[output documentation](https://nf-co.re/molkart/output).\n\n## Credits\n\nnf-core/molkart was originally written by @kbestak, @FloWuenne.\n\nWe thank [Maxime U Garcia](https://github.com/maxulysse) for his assistance and support in the development of this pipeline.\n\n## Contributions and Support\n\nIf you would like to contribute to this pipeline, please see the [contributing guidelines](.github/CONTRIBUTING.md).\n\nFor further information or help, don't hesitate to get in touch on the [Slack `#molkart` channel](https://nfcore.slack.com/channels/molkart) (you can join with [this invite](https://nf-co.re/join/slack)).\n\n## Citations\n\nIf you use nf-core/molkart for your analysis, please cite it using the following doi: [10.5281/zenodo.10650749](https://doi.org/10.5281/zenodo.10650749)\n\nAn extensive list of references for the tools used by the pipeline can be found in the [`CITATIONS.md`](CITATIONS.md) file.\n\nYou can cite the `nf-core` publication as follows:\n\n> **The nf-core framework for community-curated bioinformatics pipelines.**\n>\n> Philip Ewels, Alexander Peltzer, Sven Fillinger, Harshil Patel, Johannes Alneberg, Andreas Wilm, Maxime Ulysse Garcia, Paolo Di Tommaso & Sven Nahnsen.\n>\n> _Nat Biotechnol._ 2020 Feb 13. doi: [10.1038/s41587-020-0439-x](https://dx.doi.org/10.1038/s41587-020-0439-x).\n", + "hasPart": [ + { + "@id": "main.nf" + }, + { + "@id": "assets/" + }, + { + "@id": "bin/" + }, + { + "@id": "conf/" + }, + { + "@id": "docs/" + }, + { + "@id": "docs/images/" + }, + { + "@id": "modules/" + }, + { + "@id": "modules/local/" + }, + { + "@id": "modules/nf-core/" + }, + { + "@id": "workflows/" + }, + { + "@id": "subworkflows/" + }, + { + "@id": "nextflow.config" + }, + { + "@id": "README.md" + }, + { + "@id": "nextflow_schema.json" + }, + { + "@id": "CHANGELOG.md" + }, + { + "@id": "LICENSE" + }, + { + "@id": "CODE_OF_CONDUCT.md" + }, + { + "@id": "CITATIONS.md" + }, + { + "@id": "modules.json" + }, + { + "@id": "docs/usage.md" + }, + { + "@id": "docs/output.md" + }, + { + "@id": ".nf-core.yml" + }, + { + "@id": ".pre-commit-config.yaml" + }, + { + "@id": ".prettierignore" + } + ], + "isBasedOn": "https://github.com/nf-core/molkart", + "license": "MIT", + "mainEntity": { + "@id": "main.nf" + }, + "mentions": [ + { + "@id": "#22fdbc04-b92b-4200-a0e4-b97b5595e380" + } + ], + "name": "nf-core/molkart" + }, + { + "@id": "ro-crate-metadata.json", + "@type": "CreativeWork", + "about": { + "@id": "./" + }, + "conformsTo": [ + { + "@id": "https://w3id.org/ro/crate/1.1" + }, + { + "@id": "https://w3id.org/workflowhub/workflow-ro-crate/1.0" + } + ] + }, + { + "@id": "main.nf", + "@type": [ + "File", + "SoftwareSourceCode", + "ComputationalWorkflow" + ], + "creator": [ + { + "@id": "#flowuenne@gmail.com" + }, + { + "@id": "#86408271+kbestak@users.noreply.github.com" + } + ], + "dateCreated": "", + "dateModified": "2025-02-28T10:48:22Z", + "dct:conformsTo": "https://bioschemas.org/profiles/ComputationalWorkflow/1.0-RELEASE/", + "keywords": [ + "nf-core", + "nextflow", + "fish", + "image-processing", + "imaging", + "molecularcartography", + "segmentation", + "single-cell", + "spatial", + "transcriptomics" + ], + "license": [ + "MIT" + ], + "name": [ + "nf-core/molkart" + ], + "programmingLanguage": { + "@id": "https://w3id.org/workflowhub/workflow-ro-crate#nextflow" + }, + "sdPublisher": { + "@id": "https://nf-co.re/" + }, + "url": [ + "https://github.com/nf-core/molkart", + "https://nf-co.re/molkart/1.1.0/" + ], + "version": [ + "1.1.0" + ] + }, + { + "@id": "https://w3id.org/workflowhub/workflow-ro-crate#nextflow", + "@type": "ComputerLanguage", + "identifier": { + "@id": "https://www.nextflow.io/" + }, + "name": "Nextflow", + "url": { + "@id": "https://www.nextflow.io/" + }, + "version": "!>=24.04.2" + }, + { + "@id": "#22fdbc04-b92b-4200-a0e4-b97b5595e380", + "@type": "TestSuite", + "instance": [ + { + "@id": "#0d7cac50-b452-492c-bc04-d908a963f7a8" + } + ], + "mainEntity": { + "@id": "main.nf" + }, + "name": "Test suite for nf-core/molkart" + }, + { + "@id": "#0d7cac50-b452-492c-bc04-d908a963f7a8", + "@type": "TestInstance", + "name": "GitHub Actions workflow for testing nf-core/molkart", + "resource": "repos/nf-core/molkart/actions/workflows/ci.yml", + "runsOn": { + "@id": "https://w3id.org/ro/terms/test#GithubService" + }, + "url": "https://api.github.com" + }, + { + "@id": "https://w3id.org/ro/terms/test#GithubService", + "@type": "TestService", + "name": "Github Actions", + "url": { + "@id": "https://github.com" + } + }, + { + "@id": "assets/", + "@type": "Dataset", + "description": "Additional files" + }, + { + "@id": "bin/", + "@type": "Dataset", + "description": "Scripts that must be callable from a pipeline process" + }, + { + "@id": "conf/", + "@type": "Dataset", + "description": "Configuration files" + }, + { + "@id": "docs/", + "@type": "Dataset", + "description": "Markdown files for documenting the pipeline" + }, + { + "@id": "docs/images/", + "@type": "Dataset", + "description": "Images for the documentation files" + }, + { + "@id": "modules/", + "@type": "Dataset", + "description": "Modules used by the pipeline" + }, + { + "@id": "modules/local/", + "@type": "Dataset", + "description": "Pipeline-specific modules" + }, + { + "@id": "modules/nf-core/", + "@type": "Dataset", + "description": "nf-core modules" + }, + { + "@id": "workflows/", + "@type": "Dataset", + "description": "Main pipeline workflows to be executed in main.nf" + }, + { + "@id": "subworkflows/", + "@type": "Dataset", + "description": "Smaller subworkflows" + }, + { + "@id": "nextflow.config", + "@type": "File", + "description": "Main Nextflow configuration file" + }, + { + "@id": "README.md", + "@type": "File", + "description": "Basic pipeline usage information" + }, + { + "@id": "nextflow_schema.json", + "@type": "File", + "description": "JSON schema for pipeline parameter specification" + }, + { + "@id": "CHANGELOG.md", + "@type": "File", + "description": "Information on changes made to the pipeline" + }, + { + "@id": "LICENSE", + "@type": "File", + "description": "The license - should be MIT" + }, + { + "@id": "CODE_OF_CONDUCT.md", + "@type": "File", + "description": "The nf-core code of conduct" + }, + { + "@id": "CITATIONS.md", + "@type": "File", + "description": "Citations needed when using the pipeline" + }, + { + "@id": "modules.json", + "@type": "File", + "description": "Version information for modules from nf-core/modules" + }, + { + "@id": "docs/usage.md", + "@type": "File", + "description": "Usage documentation" + }, + { + "@id": "docs/output.md", + "@type": "File", + "description": "Output documentation" + }, + { + "@id": ".nf-core.yml", + "@type": "File", + "description": "nf-core configuration file, configuring template features and linting rules" + }, + { + "@id": ".pre-commit-config.yaml", + "@type": "File", + "description": "Configuration file for pre-commit hooks" + }, + { + "@id": ".prettierignore", + "@type": "File", + "description": "Ignore file for prettier" + }, + { + "@id": "https://nf-co.re/", + "@type": "Organization", + "name": "nf-core", + "url": "https://nf-co.re/" + }, + { + "@id": "#flowuenne@gmail.com", + "@type": "Person", + "email": "flowuenne@gmail.com", + "name": "Florian Wuennemann" + }, + { + "@id": "#86408271+kbestak@users.noreply.github.com", + "@type": "Person", + "email": "86408271+kbestak@users.noreply.github.com", + "name": "Kre\u0161imir Be\u0161tak" + } + ] +} \ No newline at end of file diff --git a/subworkflows/local/utils_nfcore_molkart_pipeline/main.nf b/subworkflows/local/utils_nfcore_molkart_pipeline/main.nf new file mode 100644 index 0000000..d9c37ae --- /dev/null +++ b/subworkflows/local/utils_nfcore_molkart_pipeline/main.nf @@ -0,0 +1,235 @@ +// +// Subworkflow with functionality specific to the nf-core/molkart pipeline +// + +/* +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + IMPORT FUNCTIONS / MODULES / SUBWORKFLOWS +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +*/ + +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' + +/* +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + SUBWORKFLOW TO INITIALISE PIPELINE +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +*/ + +workflow PIPELINE_INITIALISATION { + + take: + version // boolean: Display version and exit + validate_params // boolean: Boolean whether to validate parameters against the schema at runtime + monochrome_logs // boolean: Do not use coloured log outputs + nextflow_cli_args // array: List of positional nextflow CLI args + outdir // string: The output directory where the results will be saved + input // string: Path to input samplesheet + + main: + + ch_versions = Channel.empty() + + // + // 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, + validate_params, + null + ) + + // + // Check config provided to the pipeline + // + UTILS_NFCORE_PIPELINE ( + nextflow_cli_args + ) + + // + // Custom validation for pipeline parameters + // + validateInputParameters() + + // + // Create channel from input file provided through params.input + // + + Channel + .fromList(samplesheetToList(params.input, "${projectDir}/assets/schema_input.json")) + .map { samplesheet -> + validateInputSamplesheet(samplesheet) + } + .map{ + sample, nuclear, spots, membrane -> + def meta = [id: sample] + return [meta, nuclear, spots, membrane] + } + .set { ch_samplesheet } + + emit: + samplesheet = ch_samplesheet + versions = ch_versions +} + +/* +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + SUBWORKFLOW FOR PIPELINE COMPLETION +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +*/ + +workflow PIPELINE_COMPLETION { + + take: + email // string: email address + email_on_fail // string: email address sent on pipeline failure + plaintext_email // boolean: Send plain-text email instead of HTML + outdir // path: Path to output directory where results will be published + monochrome_logs // boolean: Disable ANSI colour codes in log output + hook_url // string: hook URL for notifications + multiqc_report // string: Path to MultiQC report + + main: + summary_params = paramsSummaryMap(workflow, parameters_schema: "nextflow_schema.json") + def multiqc_reports = multiqc_report.toList() + + // + // Completion email and summary + // + workflow.onComplete { + if (email || email_on_fail) { + completionEmail( + summary_params, + email, + email_on_fail, + plaintext_email, + outdir, + monochrome_logs, + multiqc_reports.getVal(), + ) + } + + completionSummary(monochrome_logs) + if (hook_url) { + imNotification(summary_params, hook_url) + } + } + + workflow.onError { + log.error "Pipeline failed. Please refer to troubleshooting docs: https://nf-co.re/docs/usage/troubleshooting" + } +} + +/* +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + FUNCTIONS +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +*/ +// +// Check and validate pipeline parameters +// +def validateInputParameters() { + //genomeExistsError() +} + +// +// Validate channels from input samplesheet +// +def validateInputSamplesheet(input) { + def (meta, files) = input + + // Check that multiple runs of the same sample are of the same datatype i.e. single-end / paired-end + //def endedness_ok = metas.collect{ it.single_end }.unique().size == 1 + //if (!endedness_ok) { + // error("Please check input samplesheet -> Multiple runs of a sample must be of the same datatype i.e. single-end or paired-end: ${metas[0].id}") + //} + + return input +} +// +// Generate methods description for MultiQC +// +def toolCitationText() { + // Can use ternary operators to dynamically construct based conditions, e.g. params["run_xyz"] ? "Tool (Foo et al. 2023)" : "", + // Uncomment function in methodsDescriptionText to render in MultiQC report + def citation_text = [ + "Tools used in the workflow included: ", + params.skip_mindagap ? "" : "Mindagap (Guerreiro et al. 2023),", + params.segmentation_method.split(',').contains('mesmer') ? "Mesmer (Greenwald et al. 2021)," : "", + params.segmentation_method.split(',').contains('ilastik') ? "ilastik (Berg et al. 2019)," : "", + params.segmentation_method.split(',').contains('stardist') ? "Stardist (Schmidt et al. 2018)," : "", + params.segmentation_method.split(',').contains('cellpose') ? "Cellpose (Stringer et al. 2021; Pachitariu et al 2022)," : "", + "MultiQC (Ewels et al. 2016)", + "." + ].join(' ').trim() + return citation_text +} + +def toolBibliographyText() { + // Can use ternary operators to dynamically construct based conditions, e.g. params["run_xyz"] ? "
  • Author (2023) Pub name, Journal, DOI
  • " : "", + // Uncomment function in methodsDescriptionText to render in MultiQC report + def reference_text = [ + params.skip_mindagap ? "" : "
  • Ricardo Guerreiro, Florian Wuennemann, & pvtodorov. (2023). ViriatoII/MindaGap: v0.0.3 (0.0.3). Zenodo. https://doi.org/10.5281/zenodo.8120559", + params.segmentation_method.split(',').contains('mesmer') ? "
  • Greenwald, N.F., Miller, G., Moen, E. et al. Whole-cell segmentation of tissue images with human-level performance using large-scale data annotation and deep learning. Nat Biotechnol 40, 555–565 (2022). https://doi.org/10.1038/s41587-021-01094-0
  • " : "", + params.segmentation_method.split(',').contains('ilastik') ? "
  • Berg, S., Kutra, D., Kroeger, T. et al. ilastik: interactive machine learning for (bio)image analysis. Nat Methods 16, 1226–1232 (2019). https://doi.org/10.1038/s41592-019-0582-9
  • " : "", + params.segmentation_method.split(',').contains('stardist') ? "
  • Schmidt, U., Weigert, M., Broaddus, C., Myers, G. (2018). Cell Detection with Star-Convex Polygons. In: Frangi, A., Schnabel, J., Davatzikos, C., Alberola-López, C., Fichtinger, G. (eds) Medical Image Computing and Computer Assisted Intervention – MICCAI 2018. MICCAI 2018. Lecture Notes in Computer Science(), vol 11071. Springer, Cham. https://doi.org/10.1007/978-3-030-00934-2_30
  • " : "", + params.segmentation_method.split(',').contains('cellpose') ? "
  • Stringer, C., Wang, T., Michaelos, M. et al. Cellpose: a generalist algorithm for cellular segmentation. Nat Methods 18, 100–106 (2021). https://doi.org/10.1038/s41592-020-01018-x
  • " : "", + "
  • Ewels, P., Magnusson, M., Lundin, S., & Käller, M. (2016). MultiQC: summarize analysis results for multiple tools and samples in a single report. Bioinformatics , 32(19), 3047–3048. doi: /10.1093/bioinformatics/btw354
  • ", + ].join(' ').trim() + + return reference_text +} + +def methodsDescriptionText(mqc_methods_yaml) { + // Convert to a named map so can be used as with familiar NXF ${workflow} variable syntax in the MultiQC YML file + def meta = [:] + meta.workflow = workflow.toMap() + meta["manifest_map"] = workflow.manifest.toMap() + + // Pipeline DOI + if (meta.manifest_map.doi) { + // Using a loop to handle multiple DOIs + // Removing `https://doi.org/` to handle pipelines using DOIs vs DOI resolvers + // Removing ` ` since the manifest.doi is a string and not a proper list + def temp_doi_ref = "" + def manifest_doi = meta.manifest_map.doi.tokenize(",") + manifest_doi.each { doi_ref -> + temp_doi_ref += "(doi: ${doi_ref.replace("https://doi.org/", "").replace(" ", "")}), " + } + meta["doi_text"] = temp_doi_ref.substring(0, temp_doi_ref.length() - 2) + } else meta["doi_text"] = "" + meta["nodoi_text"] = meta.manifest_map.doi ? "" : "
  • If available, make sure to update the text to include the Zenodo DOI of version of the pipeline used.
  • " + + // Tool references + meta["tool_citations"] = "" + meta["tool_bibliography"] = "" + + meta["tool_citations"] = toolCitationText().replaceAll(", \\.", ".").replaceAll("\\. \\.", ".").replaceAll(", \\.", ".") + meta["tool_bibliography"] = toolBibliographyText() + + + def methods_text = mqc_methods_yaml.text + + def engine = new groovy.text.SimpleTemplateEngine() + def description_html = engine.createTemplate(methods_text).make(meta) + + return description_html.toString() +} + 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 += "

    ${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/tests/.nftignore b/tests/.nftignore new file mode 100644 index 0000000..b8312c9 --- /dev/null +++ b/tests/.nftignore @@ -0,0 +1,15 @@ +.DS_Store +pipeline_info/*.{html,json,txt,yml} +multiqc/multiqc_data/multiqc_data.json +multiqc/multiqc_data/multiqc.log +multiqc/multiqc_plots/pdf/* +multiqc/multiqc_plots/png/* +multiqc/multiqc_plots/svg/* +multiqc/multiqc_report.html +multiqc/crop_overview.png +multiqc/crop_overview.txt +clahe/*.tiff +stack/*.tif +molkartqc/crop_overview.png +training_subset/hdf5/* +training_subset/tiff/* diff --git a/tests/main.nf.test b/tests/main.nf.test index 48d5546..7c9092e 100644 --- a/tests/main.nf.test +++ b/tests/main.nf.test @@ -2,11 +2,10 @@ nextflow_pipeline { name "Test Workflow main.nf" script "../main.nf" - config "./nextflow.config" tag "pipeline" tag "pipeline_molkart" - test("Nuclear channel, mesmer and cellpose, without clahe") { + test("Nuclear channel, stardist, mesmer and cellpose, without clahe") { when { params { @@ -16,27 +15,29 @@ nextflow_pipeline { mindagap_tilesize = 90 mindagap_boxsize = 7 mindagap_loopnum = 100 - segmentation_method = "cellpose,mesmer" + segmentation_method = "cellpose,mesmer,stardist" } } then { + // stable_name: All files + folders in ${params.outdir}/ with a stable name + def stable_name = getAllFilesFromDir(params.outdir, relative: true, includeDir: true, ignore: ['pipeline_info/*.{html,json,txt}']) + // stable_path: All files in ${params.outdir}/ with stable content + def stable_path = getAllFilesFromDir(params.outdir, ignoreFile: 'tests/.nftignore') assert workflow.success - assert snapshot( - path("$outputDir/mindagap/nuc_only_nuclear_gridfilled.tiff"), - path("$outputDir/mindagap/nuc_only_spots_markedDups.txt"), - path("$outputDir/segmentation/cellpose/nuc_only_cellpose_mask.tif"), - path("$outputDir/segmentation/mesmer/nuc_only_mesmer_mask.tif"), - path("$outputDir/segmentation/filtered_masks/nuc_only_mesmer_filtered.tif"), - path("$outputDir/segmentation/filtered_masks/nuc_only_cellpose_filtered.tif"), - path("$outputDir/spot2cell/cellxgene_nuc_only_cellpose.csv"), - path("$outputDir/spot2cell/cellxgene_nuc_only_mesmer.csv"), - path("$outputDir/anndata/nuc_only_cellpose.adata"), - path("$outputDir/anndata/nuc_only_mesmer.adata"), - path("$outputDir/molkartqc/nuc_only.cellpose.spot_QC.csv"), - path("$outputDir/molkartqc/nuc_only.mesmer.spot_QC.csv") - ).match() - assert file("$outputDir/multiqc/multiqc_report.html").exists() + assertAll( + { assert workflow.success}, + { assert snapshot( + // Number of successful tasks + workflow.trace.succeeded().size(), + // pipeline versions.yml file for multiqc from which Nextflow version is removed because we tests pipelines on multiple Nextflow versions + removeNextflowVersion("$outputDir/pipeline_info/nf_core_molkart_software_mqc_versions.yml"), + // All stable path name, with a relative path + stable_name, + // All files with stable contents + stable_path + ).match() } + ) } } @@ -55,26 +56,58 @@ nextflow_pipeline { } then { + // stable_name: All files + folders in ${params.outdir}/ with a stable name + def stable_name = getAllFilesFromDir(params.outdir, relative: true, includeDir: true, ignore: ['pipeline_info/*.{html,json,txt}']) + // stable_path: All files in ${params.outdir}/ with stable content + def stable_path = getAllFilesFromDir(params.outdir, ignoreFile: 'tests/.nftignore') + assert workflow.success + assertAll( + { assert workflow.success}, + { assert snapshot( + // Number of successful tasks + workflow.trace.succeeded().size(), + // pipeline versions.yml file for multiqc from which Nextflow version is removed because we tests pipelines on multiple Nextflow versions + removeNextflowVersion("$outputDir/pipeline_info/nf_core_molkart_software_mqc_versions.yml"), + // All stable path name, with a relative path + stable_name, + // All files with stable contents + stable_path + ).match() } + ) + } + } + + test("Skip mindagap - clahe - cellpose") { + + when { + params { + input = 'https://raw.githubusercontent.com/nf-core/test-datasets/molkart/test_data/samplesheets/samplesheet_membrane.csv' + outdir = "$outputDir" + clahe_pyramid_tile = 368 + segmentation_method = "cellpose" + skip_mindagap = true + } + } + + then { + // stable_name: All files + folders in ${params.outdir}/ with a stable name + def stable_name = getAllFilesFromDir(params.outdir, relative: true, includeDir: true, ignore: ['pipeline_info/*.{html,json,txt}']) + // stable_path: All files in ${params.outdir}/ with stable content + def stable_path = getAllFilesFromDir(params.outdir, ignoreFile: 'tests/.nftignore') assert workflow.success - assert snapshot( - path("$outputDir/mindagap/mem_only_membrane_gridfilled.tiff"), - path("$outputDir/mindagap/mem_only_nuclear_gridfilled.tiff"), - path("$outputDir/mindagap/mem_only_spots_markedDups.txt"), - path("$outputDir/segmentation/cellpose/mem_only_cellpose_mask.tif"), - path("$outputDir/segmentation/mesmer/mem_only_mesmer_mask.tif"), - path("$outputDir/segmentation/filtered_masks/mem_only_mesmer_filtered.tif"), - path("$outputDir/segmentation/filtered_masks/mem_only_cellpose_filtered.tif"), - path("$outputDir/spot2cell/cellxgene_mem_only_cellpose.csv"), - path("$outputDir/spot2cell/cellxgene_mem_only_mesmer.csv"), - path("$outputDir/anndata/mem_only_cellpose.adata"), - path("$outputDir/anndata/mem_only_mesmer.adata"), - path("$outputDir/molkartqc/mem_only.cellpose.spot_QC.csv"), - path("$outputDir/molkartqc/mem_only.mesmer.spot_QC.csv"), - ).match() - assert file("$outputDir/stack/mem_only_stack.ome.tif").exists() - assert file("$outputDir/clahe/mem_only_membrane_gridfilled_clahe.tiff").exists() - assert file("$outputDir/clahe/mem_only_nuclear_gridfilled_clahe.tiff").exists() - assert file("$outputDir/multiqc/multiqc_report.html").exists() + assertAll( + { assert workflow.success}, + { assert snapshot( + // Number of successful tasks + workflow.trace.succeeded().size(), + // pipeline versions.yml file for multiqc from which Nextflow version is removed because we tests pipelines on multiple Nextflow versions + removeNextflowVersion("$outputDir/pipeline_info/nf_core_molkart_software_mqc_versions.yml"), + // All stable path name, with a relative path + stable_name, + // All files with stable contents + stable_path + ).match() } + ) } } @@ -96,16 +129,24 @@ nextflow_pipeline { } then { + // stable_name: All files + folders in ${params.outdir}/ with a stable name + def stable_name = getAllFilesFromDir(params.outdir, relative: true, includeDir: true, ignore: ['pipeline_info/*.{html,json,txt}']) + // stable_path: All files in ${params.outdir}/ with stable content + def stable_path = getAllFilesFromDir(params.outdir, ignoreFile: 'tests/.nftignore') assert workflow.success - assert snapshot( - path("$outputDir/mindagap/nuc_only_nuclear_gridfilled.tiff"), - ).match() - assert file("$outputDir/clahe/nuc_only_nuclear_gridfilled_clahe.tiff").exists() - assert file("$outputDir/training_subset/hdf5/nuc_only_nuclear_gridfilled_clahe_crop0.hdf5").exists() - assert file("$outputDir/training_subset/hdf5/nuc_only_nuclear_gridfilled_clahe_crop1.hdf5").exists() - assert file("$outputDir/training_subset/tiff/nuc_only_nuclear_gridfilled_clahe_crop0.tiff").exists() - assert file("$outputDir/training_subset/tiff/nuc_only_nuclear_gridfilled_clahe_crop1.tiff").exists() - assert file("$outputDir/multiqc/crop_overview.png").exists() + assertAll( + { assert workflow.success}, + { assert snapshot( + // Number of successful tasks + workflow.trace.succeeded().size(), + // pipeline versions.yml file for multiqc from which Nextflow version is removed because we tests pipelines on multiple Nextflow versions + removeNextflowVersion("$outputDir/pipeline_info/nf_core_molkart_software_mqc_versions.yml"), + // All stable path name, with a relative path + stable_name, + // All files with stable contents + stable_path + ).match() } + ) } } } diff --git a/tests/main.nf.test.snap b/tests/main.nf.test.snap index 58e4a17..4cdda06 100644 --- a/tests/main.nf.test.snap +++ b/tests/main.nf.test.snap @@ -1,43 +1,384 @@ { "Two channels, mesmer and cellpose, with clahe": { "content": [ - "mem_only_membrane_gridfilled.tiff:md5,6e24160d758468d2de07ca200dfb62c2", - "mem_only_nuclear_gridfilled.tiff:md5,123763d54f05b2274690b3b84f9690b1", - "mem_only_spots_markedDups.txt:md5,4562caad05850d7dd7b6e9235e068a8b", - "mem_only_cellpose_mask.tif:md5,590591f541b6d3f17810cd44dd71b252", - "mem_only_mesmer_mask.tif:md5,6f8c792ccc4cca97b5696990fb4e3801", - "mem_only_mesmer_filtered.tif:md5,5751b2e39393a9c8221396f5ef592035", - "mem_only_cellpose_filtered.tif:md5,590591f541b6d3f17810cd44dd71b252", - "cellxgene_mem_only_cellpose.csv:md5,f063c1ce5e93e1e73431af36c6bc1e79", - "cellxgene_mem_only_mesmer.csv:md5,198b187d5f151077437aa591f5991b22", - "mem_only_cellpose.adata:md5,b47ea63ffea0947e43511f17b6920cd8", - "mem_only_mesmer.adata:md5,530649647a3466316ea52dde9dede4ab", - "mem_only.cellpose.spot_QC.csv:md5,df312175498f7942bffc33f2c2d8d1c9", - "mem_only.mesmer.spot_QC.csv:md5,3273bd6fecf93a3d240614d1b38831c9" + 17, + { + "CELLPOSE": { + "cellpose": "3.0.1" + }, + "CLAHE": { + "molkart_clahe": "0.1.0" + }, + "CREATE_ANNDATA": { + "molkart_createanndata": "0.1.0" + }, + "CREATE_STACK": { + "molkart_stack": "0.1.0" + }, + "DEEPCELL_MESMER": { + "deepcell_mesmer": "0.4.1" + }, + "MASKFILTER": { + "molkart_maskfilter": "0.1.0" + }, + "MINDAGAP_DUPLICATEFINDER": { + "mindagap": "0.0.2" + }, + "MINDAGAP_MINDAGAP": { + "mindagap": "0.0.2" + }, + "MOLKARTQC": { + "molkartqc": "0.1.0" + }, + "SPOT2CELL": { + "molkart_spot2cell": "0.1.0" + }, + "Workflow": { + "nf-core/molkart": "v1.1.0" + } + }, + [ + "anndata", + "anndata/mem_only_cellpose.adata", + "anndata/mem_only_mesmer.adata", + "clahe", + "clahe/mem_only_membrane_gridfilled_clahe.tiff", + "clahe/mem_only_nuclear_gridfilled_clahe.tiff", + "mindagap", + "mindagap/mem_only_membrane_gridfilled.tiff", + "mindagap/mem_only_nuclear_gridfilled.tiff", + "mindagap/mem_only_spots_markedDups.txt", + "molkartqc", + "molkartqc/mem_only.cellpose.spot_QC.csv", + "molkartqc/mem_only.mesmer.spot_QC.csv", + "multiqc", + "multiqc/multiqc_data", + "multiqc/multiqc_data/multiqc.log", + "multiqc/multiqc_data/multiqc_citations.txt", + "multiqc/multiqc_data/multiqc_data.json", + "multiqc/multiqc_data/multiqc_segmentation_stats.txt", + "multiqc/multiqc_data/multiqc_software_versions.txt", + "multiqc/multiqc_data/multiqc_sources.txt", + "multiqc/multiqc_plots", + "multiqc/multiqc_plots/pdf", + "multiqc/multiqc_plots/pdf/segmentation_stats.pdf", + "multiqc/multiqc_plots/png", + "multiqc/multiqc_plots/png/segmentation_stats.png", + "multiqc/multiqc_plots/svg", + "multiqc/multiqc_plots/svg/segmentation_stats.svg", + "multiqc/multiqc_report.html", + "pipeline_info", + "pipeline_info/nf_core_molkart_software_mqc_versions.yml", + "segmentation", + "segmentation/cellpose", + "segmentation/cellpose/mem_only_cellpose_mask.tif", + "segmentation/filtered_masks", + "segmentation/filtered_masks/mem_only_cellpose_filtered.csv", + "segmentation/filtered_masks/mem_only_cellpose_filtered.tif", + "segmentation/filtered_masks/mem_only_mesmer_filtered.csv", + "segmentation/filtered_masks/mem_only_mesmer_filtered.tif", + "segmentation/mesmer", + "segmentation/mesmer/mem_only_mesmer_mask.tif", + "spot2cell", + "spot2cell/cellxgene_mem_only_cellpose.csv", + "spot2cell/cellxgene_mem_only_mesmer.csv", + "stack", + "stack/mem_only_stack.ome.tif" + ], + [ + "mem_only_cellpose.adata:md5,834903214d6cdcc7dd2e10b1ee1b22a3", + "mem_only_mesmer.adata:md5,530649647a3466316ea52dde9dede4ab", + "mem_only_membrane_gridfilled.tiff:md5,6e24160d758468d2de07ca200dfb62c2", + "mem_only_nuclear_gridfilled.tiff:md5,123763d54f05b2274690b3b84f9690b1", + "mem_only_spots_markedDups.txt:md5,4562caad05850d7dd7b6e9235e068a8b", + "mem_only.cellpose.spot_QC.csv:md5,2090a0bc4307e9712a6150bafeb38ec5", + "mem_only.mesmer.spot_QC.csv:md5,3273bd6fecf93a3d240614d1b38831c9", + "multiqc_citations.txt:md5,4c806e63a283ec1b7e78cdae3a923d4f", + "multiqc_segmentation_stats.txt:md5,22da6b6457be552df182868d183e70cd", + "multiqc_software_versions.txt:md5,59dce7138f993c2e591590f2a00efedf", + "multiqc_sources.txt:md5,d2a044df39ce3c6abe5cdc2d67473490", + "mem_only_cellpose_mask.tif:md5,d962bae7277cc7ad8cf96708a806b418", + "mem_only_cellpose_filtered.csv:md5,4cd99d408b527fb0e74074cbd1880f52", + "mem_only_cellpose_filtered.tif:md5,33f1f2e79dec6ebb27e2761413c55696", + "mem_only_mesmer_filtered.csv:md5,d489b06c9cd6cc4218f13e17b1fc5ba3", + "mem_only_mesmer_filtered.tif:md5,5751b2e39393a9c8221396f5ef592035", + "mem_only_mesmer_mask.tif:md5,6f8c792ccc4cca97b5696990fb4e3801", + "cellxgene_mem_only_cellpose.csv:md5,dc8349194322a72bd87e5118040c427f", + "cellxgene_mem_only_mesmer.csv:md5,198b187d5f151077437aa591f5991b22" + ] ], - "timestamp": "2024-01-05T11:19:49.882138591" + "meta": { + "nf-test": "0.9.2", + "nextflow": "24.10.5" + }, + "timestamp": "2025-03-05T16:20:54.16030802" }, - "Nuclear channel, mesmer and cellpose, without clahe": { + "Nuclear channel, stardist, mesmer and cellpose, without clahe": { "content": [ - "nuc_only_nuclear_gridfilled.tiff:md5,123763d54f05b2274690b3b84f9690b1", - "nuc_only_spots_markedDups.txt:md5,4562caad05850d7dd7b6e9235e068a8b", - "nuc_only_cellpose_mask.tif:md5,2784d8c6683ad80c24d8df4121e1128f", - "nuc_only_mesmer_mask.tif:md5,e9719f53651b4bf76a37e9374db7f4e4", - "nuc_only_mesmer_filtered.tif:md5,5a6761c80ea65d41ce00d06d3414c96b", - "nuc_only_cellpose_filtered.tif:md5,2784d8c6683ad80c24d8df4121e1128f", - "cellxgene_nuc_only_cellpose.csv:md5,0610a8713457c28acf1bc4c298bee8e3", - "cellxgene_nuc_only_mesmer.csv:md5,425cfb90a60b564e4bfff09ac5ea94c7", - "nuc_only_cellpose.adata:md5,fd52e62711465d754fd36a433761cb3b", - "nuc_only_mesmer.adata:md5,cd20ab6db5274bb85c960ea3bd8d2619", - "nuc_only.cellpose.spot_QC.csv:md5,e77b5973e0997170d0fde5c5901ad551", - "nuc_only.mesmer.spot_QC.csv:md5,f8f4eb85bb8269341ac072ac78962ed4" + 18, + { + "CELLPOSE": { + "cellpose": "3.0.1" + }, + "CREATE_ANNDATA": { + "molkart_createanndata": "0.1.0" + }, + "DEEPCELL_MESMER": { + "deepcell_mesmer": "0.4.1" + }, + "MASKFILTER": { + "molkart_maskfilter": "0.1.0" + }, + "MINDAGAP_DUPLICATEFINDER": { + "mindagap": "0.0.2" + }, + "MINDAGAP_MINDAGAP": { + "mindagap": "0.0.2" + }, + "MOLKARTQC": { + "molkartqc": "0.1.0" + }, + "SPOT2CELL": { + "molkart_spot2cell": "0.1.0" + }, + "STARDIST": { + "stardist": "0.9.1", + "python": "3.9.15", + "tensorflow": "2.10.0", + "tifffile": "2022.4.8" + }, + "Workflow": { + "nf-core/molkart": "v1.1.0" + } + }, + [ + "anndata", + "anndata/nuc_only_cellpose.adata", + "anndata/nuc_only_mesmer.adata", + "anndata/nuc_only_stardist.adata", + "mindagap", + "mindagap/nuc_only_nuclear_gridfilled.tiff", + "mindagap/nuc_only_spots_markedDups.txt", + "molkartqc", + "molkartqc/nuc_only.cellpose.spot_QC.csv", + "molkartqc/nuc_only.mesmer.spot_QC.csv", + "molkartqc/nuc_only.stardist.spot_QC.csv", + "multiqc", + "multiqc/multiqc_data", + "multiqc/multiqc_data/multiqc.log", + "multiqc/multiqc_data/multiqc_citations.txt", + "multiqc/multiqc_data/multiqc_data.json", + "multiqc/multiqc_data/multiqc_segmentation_stats.txt", + "multiqc/multiqc_data/multiqc_software_versions.txt", + "multiqc/multiqc_data/multiqc_sources.txt", + "multiqc/multiqc_plots", + "multiqc/multiqc_plots/pdf", + "multiqc/multiqc_plots/pdf/segmentation_stats.pdf", + "multiqc/multiqc_plots/png", + "multiqc/multiqc_plots/png/segmentation_stats.png", + "multiqc/multiqc_plots/svg", + "multiqc/multiqc_plots/svg/segmentation_stats.svg", + "multiqc/multiqc_report.html", + "pipeline_info", + "pipeline_info/nf_core_molkart_software_mqc_versions.yml", + "segmentation", + "segmentation/cellpose", + "segmentation/cellpose/nuc_only_cellpose_mask.tif", + "segmentation/filtered_masks", + "segmentation/filtered_masks/nuc_only_cellpose_filtered.csv", + "segmentation/filtered_masks/nuc_only_cellpose_filtered.tif", + "segmentation/filtered_masks/nuc_only_mesmer_filtered.csv", + "segmentation/filtered_masks/nuc_only_mesmer_filtered.tif", + "segmentation/filtered_masks/nuc_only_stardist_filtered.csv", + "segmentation/filtered_masks/nuc_only_stardist_filtered.tif", + "segmentation/mesmer", + "segmentation/mesmer/nuc_only_mesmer_mask.tif", + "segmentation/stardist", + "segmentation/stardist/nuc_only_stardist_mask.tif", + "spot2cell", + "spot2cell/cellxgene_nuc_only_cellpose.csv", + "spot2cell/cellxgene_nuc_only_mesmer.csv", + "spot2cell/cellxgene_nuc_only_stardist.csv" + ], + [ + "nuc_only_cellpose.adata:md5,2c08500a579b149ca9fed24508c81d27", + "nuc_only_mesmer.adata:md5,cd20ab6db5274bb85c960ea3bd8d2619", + "nuc_only_stardist.adata:md5,ba86d85b0ba0902dea90c719434a21db", + "nuc_only_nuclear_gridfilled.tiff:md5,123763d54f05b2274690b3b84f9690b1", + "nuc_only_spots_markedDups.txt:md5,4562caad05850d7dd7b6e9235e068a8b", + "nuc_only.cellpose.spot_QC.csv:md5,671d25520018573aaf6d7e5706dc8bc3", + "nuc_only.mesmer.spot_QC.csv:md5,f8f4eb85bb8269341ac072ac78962ed4", + "nuc_only.stardist.spot_QC.csv:md5,9b77da0f85fc445ff36769d28d337898", + "multiqc_citations.txt:md5,4c806e63a283ec1b7e78cdae3a923d4f", + "multiqc_segmentation_stats.txt:md5,7818314ba602541e88a8df30324b117d", + "multiqc_software_versions.txt:md5,f4d4884b7d0b963a23d51516aae7a634", + "multiqc_sources.txt:md5,d2a044df39ce3c6abe5cdc2d67473490", + "nuc_only_cellpose_mask.tif:md5,29947c9cc51a9dd791452710e00ea6d6", + "nuc_only_cellpose_filtered.csv:md5,7f18648607ce3e7c2e80e5d8025ef9c9", + "nuc_only_cellpose_filtered.tif:md5,29947c9cc51a9dd791452710e00ea6d6", + "nuc_only_mesmer_filtered.csv:md5,4cd99d408b527fb0e74074cbd1880f52", + "nuc_only_mesmer_filtered.tif:md5,5a6761c80ea65d41ce00d06d3414c96b", + "nuc_only_stardist_filtered.csv:md5,ffacf083ba2a2aa4f4f767dbc72dbc7d", + "nuc_only_stardist_filtered.tif:md5,bd988411c7c6221d304f4cf398503bf1", + "nuc_only_mesmer_mask.tif:md5,e9719f53651b4bf76a37e9374db7f4e4", + "nuc_only_stardist_mask.tif:md5,89baf7c188fd640f236dbf8b116664e5", + "cellxgene_nuc_only_cellpose.csv:md5,87b0816ae6fca91a789d08528d2deb87", + "cellxgene_nuc_only_mesmer.csv:md5,425cfb90a60b564e4bfff09ac5ea94c7", + "cellxgene_nuc_only_stardist.csv:md5,22be2ce6c3065d58e743fc52168d3da5" + ] ], - "timestamp": "2024-01-05T11:18:48.214995389" + "meta": { + "nf-test": "0.9.2", + "nextflow": "24.10.5" + }, + "timestamp": "2025-03-05T15:39:22.109929284" + }, + "Skip mindagap - clahe - cellpose": { + "content": [ + 9, + { + "CELLPOSE": { + "cellpose": "3.0.1" + }, + "CLAHE": { + "molkart_clahe": "0.1.0" + }, + "CREATE_ANNDATA": { + "molkart_createanndata": "0.1.0" + }, + "CREATE_STACK": { + "molkart_stack": "0.1.0" + }, + "MASKFILTER": { + "molkart_maskfilter": "0.1.0" + }, + "MOLKARTQC": { + "molkartqc": "0.1.0" + }, + "SPOT2CELL": { + "molkart_spot2cell": "0.1.0" + }, + "Workflow": { + "nf-core/molkart": "v1.1.0" + } + }, + [ + "anndata", + "anndata/mem_only_cellpose.adata", + "clahe", + "clahe/mem_only_membrane_clahe.tiff", + "clahe/mem_only_nuclear_clahe.tiff", + "molkartqc", + "molkartqc/mem_only.cellpose.spot_QC.csv", + "multiqc", + "multiqc/multiqc_data", + "multiqc/multiqc_data/multiqc.log", + "multiqc/multiqc_data/multiqc_citations.txt", + "multiqc/multiqc_data/multiqc_data.json", + "multiqc/multiqc_data/multiqc_segmentation_stats.txt", + "multiqc/multiqc_data/multiqc_software_versions.txt", + "multiqc/multiqc_data/multiqc_sources.txt", + "multiqc/multiqc_plots", + "multiqc/multiqc_plots/pdf", + "multiqc/multiqc_plots/pdf/segmentation_stats.pdf", + "multiqc/multiqc_plots/png", + "multiqc/multiqc_plots/png/segmentation_stats.png", + "multiqc/multiqc_plots/svg", + "multiqc/multiqc_plots/svg/segmentation_stats.svg", + "multiqc/multiqc_report.html", + "pipeline_info", + "pipeline_info/nf_core_molkart_software_mqc_versions.yml", + "segmentation", + "segmentation/cellpose", + "segmentation/cellpose/mem_only_cellpose_mask.tif", + "segmentation/filtered_masks", + "segmentation/filtered_masks/mem_only_cellpose_filtered.csv", + "segmentation/filtered_masks/mem_only_cellpose_filtered.tif", + "spot2cell", + "spot2cell/cellxgene_mem_only_cellpose.csv", + "stack", + "stack/mem_only_stack.ome.tif" + ], + [ + "mem_only_cellpose.adata:md5,a0aa767a32929417f7f3253d12278f3a", + "mem_only.cellpose.spot_QC.csv:md5,e393966b17effbc05c1b93bb590d7e80", + "multiqc_citations.txt:md5,4c806e63a283ec1b7e78cdae3a923d4f", + "multiqc_segmentation_stats.txt:md5,df4a397185b42e99cf2e45468a0decb7", + "multiqc_software_versions.txt:md5,b773e956b3d268d4e6a95635415763b3", + "multiqc_sources.txt:md5,d2a044df39ce3c6abe5cdc2d67473490", + "mem_only_cellpose_mask.tif:md5,8faad3e707aba96d3b93d9419311e843", + "mem_only_cellpose_filtered.csv:md5,e4307f10a752d8ff24fdfe33d181ec28", + "mem_only_cellpose_filtered.tif:md5,5c566683d7af62721415329799ad2ac1", + "cellxgene_mem_only_cellpose.csv:md5,925f5fabeeafc520fe839cabc79fa9ce" + ] + ], + "meta": { + "nf-test": "0.9.2", + "nextflow": "24.10.5" + }, + "timestamp": "2025-03-05T16:22:01.728871014" }, "Create training subset": { "content": [ - "nuc_only_nuclear_gridfilled.tiff:md5,123763d54f05b2274690b3b84f9690b1" + 6, + { + "CLAHE": { + "molkart_clahe": "0.1.0" + }, + "CROPHDF5": { + "molkart_crophdf5": "0.1.0" + }, + "CROPTIFF": { + "molkart_croptiff": "0.1.0" + }, + "MINDAGAP_MINDAGAP": { + "mindagap": "0.0.2" + }, + "MOLKARTQCPNG": { + "molkartqc": "0.1.0" + }, + "Workflow": { + "nf-core/molkart": "v1.1.0" + } + }, + [ + "clahe", + "clahe/nuc_only_nuclear_gridfilled_clahe.tiff", + "mindagap", + "mindagap/nuc_only_nuclear_gridfilled.tiff", + "molkartqc", + "molkartqc/crop_overview.png", + "multiqc", + "multiqc/crop_overview.png", + "multiqc/crop_overview.txt", + "multiqc/multiqc_data", + "multiqc/multiqc_data/multiqc.log", + "multiqc/multiqc_data/multiqc_citations.txt", + "multiqc/multiqc_data/multiqc_data.json", + "multiqc/multiqc_data/multiqc_software_versions.txt", + "multiqc/multiqc_data/multiqc_sources.txt", + "multiqc/multiqc_report.html", + "pipeline_info", + "pipeline_info/nf_core_molkart_software_mqc_versions.yml", + "training_subset", + "training_subset/hdf5", + "training_subset/hdf5/nuc_only_nuclear_gridfilled_clahe_crop0.hdf5", + "training_subset/hdf5/nuc_only_nuclear_gridfilled_clahe_crop1.hdf5", + "training_subset/tiff", + "training_subset/tiff/nuc_only_nuclear_gridfilled_clahe_crop0.tiff", + "training_subset/tiff/nuc_only_nuclear_gridfilled_clahe_crop1.tiff" + ], + [ + "nuc_only_nuclear_gridfilled.tiff:md5,123763d54f05b2274690b3b84f9690b1", + "multiqc_citations.txt:md5,4c806e63a283ec1b7e78cdae3a923d4f", + "multiqc_software_versions.txt:md5,ec600978260a45bace115406aefbc779", + "multiqc_sources.txt:md5,d2a044df39ce3c6abe5cdc2d67473490" + ] ], - "timestamp": "2023-11-28T21:40:40.925034909" + "meta": { + "nf-test": "0.9.2", + "nextflow": "24.10.5" + }, + "timestamp": "2025-03-05T17:00:52.037627778" } } \ No newline at end of file diff --git a/tests/nextflow.config b/tests/nextflow.config deleted file mode 100644 index 08dc560..0000000 --- a/tests/nextflow.config +++ /dev/null @@ -1,6 +0,0 @@ -/* -======================================================================================== - Nextflow config file for running tests -======================================================================================== -*/ - diff --git a/workflows/molkart.nf b/workflows/molkart.nf index 99b8fb8..c0e4b37 100644 --- a/workflows/molkart.nf +++ b/workflows/molkart.nf @@ -1,69 +1,32 @@ /* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - PRINT PARAMS SUMMARY + IMPORT MODULES / SUBWORKFLOWS / FUNCTIONS ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ -include { paramsSummaryLog; paramsSummaryMap; fromSamplesheet } from 'plugin/nf-validation' +include { CROPTIFF } from '../modules/local/croptiff/main' +include { CROPHDF5 } from '../modules/local/crophdf5/main' +include { CREATE_ANNDATA } from '../modules/local/createanndata/main' +include { CREATE_STACK } from '../modules/local/createstack/main' +include { CLAHE } from '../modules/local/clahe/main' +include { MASKFILTER } from '../modules/local/maskfilter/main' +include { MOLKARTQC } from '../modules/local/molkartqc/main' +include { MOLKARTQCPNG } from '../modules/local/molkartqcpng/main' +include { SPOT2CELL } from '../modules/local/spot2cell/main' +include { TIFFH5CONVERT } from '../modules/local/tiffh5convert/main' -def logo = NfcoreTemplate.logo(workflow, params.monochrome_logs) -def citation = '\n' + WorkflowMain.citation(workflow) + '\n' -def summary_params = paramsSummaryMap(workflow) - -// Print parameter summary log to screen -log.info logo + paramsSummaryLog(workflow) + citation - -WorkflowMolkart.initialise(params, log) - -/* -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - CONFIG FILES -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -*/ - -ch_multiqc_config = Channel.fromPath("$projectDir/assets/multiqc_config.yml", checkIfExists: true) -ch_multiqc_custom_config = params.multiqc_config ? Channel.fromPath( params.multiqc_config, checkIfExists: true ) : Channel.empty() -ch_multiqc_logo = params.multiqc_logo ? Channel.fromPath( params.multiqc_logo, checkIfExists: true ) : Channel.empty() -ch_multiqc_custom_methods_description = params.multiqc_methods_description ? file(params.multiqc_methods_description, checkIfExists: true) : file("$projectDir/assets/methods_description_template.yml", checkIfExists: true) - -/* -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - IMPORT LOCAL MODULES/SUBWORKFLOWS -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -*/ - -include { CROPTIFF } from '../modules/local/croptiff' -include { CROPHDF5 } from '../modules/local/crophdf5' -include { CREATE_ANNDATA } from '../modules/local/createanndata' -include { CREATE_STACK } from '../modules/local/createstack' -include { CLAHE } from '../modules/local/clahe' -include { MASKFILTER } from '../modules/local/maskfilter' -include { MOLKARTQC } from '../modules/local/molkartqc' -include { MOLKARTQCPNG } from '../modules/local/molkartqcpng' -include { SPOT2CELL } from '../modules/local/spot2cell' -include { TIFFH5CONVERT } from '../modules/local/tiffh5convert' - -// -// SUBWORKFLOW: Consisting of a mix of local and nf-core/modules -// - -/* -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - IMPORT NF-CORE MODULES/SUBWORKFLOWS -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -*/ - -// -// MODULE: Installed directly from nf-core/modules -// include { CELLPOSE } from '../modules/nf-core/cellpose/main' -include { CUSTOM_DUMPSOFTWAREVERSIONS } from '../modules/nf-core/custom/dumpsoftwareversions/main' include { DEEPCELL_MESMER } from '../modules/nf-core/deepcell/mesmer/main' include { ILASTIK_MULTICUT } from '../modules/nf-core/ilastik/multicut/main' include { ILASTIK_PIXELCLASSIFICATION } from '../modules/nf-core/ilastik/pixelclassification/main' include { MINDAGAP_DUPLICATEFINDER } from '../modules/nf-core/mindagap/duplicatefinder/main' include { MINDAGAP_MINDAGAP } from '../modules/nf-core/mindagap/mindagap/main' include { MULTIQC } from '../modules/nf-core/multiqc/main' +include { STARDIST } from '../modules/nf-core/stardist/main' +include { paramsSummaryMultiqc } from '../subworkflows/nf-core/utils_nfcore_pipeline' +include { softwareVersionsToYAML } from '../subworkflows/nf-core/utils_nfcore_pipeline' +include { methodsDescriptionText } from '../subworkflows/local/utils_nfcore_molkart_pipeline' +include { paramsSummaryMap } from 'plugin/nf-schema' /* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -71,34 +34,32 @@ include { MULTIQC } from '../modules/nf-core/multiqc/main' ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ -// Info required for completion email and summary -def multiqc_report = [] - workflow MOLKART { - ch_versions = Channel.empty() + take: + ch_samplesheet // channel: samplesheet read in from --input + main: - // - // SUBWORKFLOW: Read in samplesheet, validate and stage input files - // - //ch_from_samplesheet = Channel.fromSamplesheet("input") - - ch_from_samplesheet = Channel.fromSamplesheet("input") + ch_versions = Channel.empty() + ch_multiqc_files = Channel.empty() // stain: "1" denotes membrane, stain: "0" denotes nuclear image // this is used to preserve the order later - ch_from_samplesheet - .map { - it[3] != [] ? tuple([id:it[0],stain:"1"], it[3]) : null - }.set { membrane_tuple } // if a membrane image is provided, return membrane image channel tuple (meta, path) + membrane_tuple = ch_samplesheet + .filter { it[3] } // filter samples with membrane + .map {meta, _nuclear, _spots, membrane -> + [meta + [stain: '1'], membrane] + } - ch_from_samplesheet - .map { it -> tuple([id:it[0],stain:"0"], it[1]) } - .set { image_tuple } // creates nuclear image channel tuple (meta, path) + image_tuple = ch_samplesheet + .map {meta, nuclear, _spots, _membrane -> + [meta + [stain: '0'], nuclear] + } - ch_from_samplesheet - .map { it -> tuple([id:it[0]], it[2]) } - .set { spot_tuple } // creates spot table channel tuple (meta, path) + spot_tuple = ch_samplesheet + .map {meta, _nuclear, spots, _membrane -> + [meta, spots] + } // // MODULE: Run Mindagap_mindagap @@ -111,35 +72,38 @@ workflow MOLKART { // MODULE: Apply Contrast-limited adaptive histogram equalization (CLAHE) // CLAHE is either applied to all images, or none. // - CLAHE(MINDAGAP_MINDAGAP.out.tiff) + clahe_in = params.skip_mindagap ? mindagap_in : MINDAGAP_MINDAGAP.out.tiff + CLAHE(clahe_in) ch_versions = ch_versions.mix(CLAHE.out.versions) - map_for_stacks = !params.skip_clahe ? CLAHE.out.img_clahe : MINDAGAP_MINDAGAP.out.tiff + map_for_stacks = params.skip_clahe ? clahe_in : CLAHE.out.img_clahe map_for_stacks - .map { - meta, tiff -> [meta.subMap("id"), tiff, meta.stain] // creates a channel containing only the sample id in meta, path to preprocessed image and the stain value ("0" or "1") - }.groupTuple() // combines based on meta + .map { meta, tiff -> + [ meta.subMap("id"), tiff, meta.stain ] + } + .groupTuple(by: 0) + .map { id, tiffs, stains -> + def sorted = [tiffs, stains].transpose().sort { it[1] } + def nuclear = sorted[0] + def membrane = sorted.size() > 1 ? sorted[1] : null + membrane ? [id, nuclear[0], membrane[0]] : [id, nuclear[0]] + } + .set{ grouped_map_stack } + + grouped_map_stack.filter { !it[2] } // for rows without a present membrane image, set channel to no_stack + .set{ no_stack } + + grouped_map_stack.filter{ it[2] } // for rows where the membrane image is present, create a list of images to be stacked .map{ - meta, paths, stains -> [meta, [paths[0], stains[0]], [paths[1], stains[1]]] // reorganizes to match path and stain - }.map{ - meta, stain1, stain2 -> [meta, [stain1, stain2].sort{ it[1] }] // sort by stain index (0 for nuclear, 1 for other) - }.map{ - meta, list -> [meta, list[0], list[1]] // sorted will have null as first list - }.map{ - it[1][0] != null ? [it[0],it[1][0],it[2][0]] : [it[0],it[2][0]] // if null, only return the valid nuclear path value, otherwise return both nuclear and membrane paths - }.set { grouped_map_stack } - - grouped_map_stack.filter{ // for rows without a present membrane image, set channel to no_stack - it[2] == null - }.set{ no_stack } - - grouped_map_stack.filter{ // for rows where the membrane image is present, make it compliant with STACK inputs - it[2] != null - }.map{ - [it[0],tuple(it[1],it[2])] + id, nuclear, membrane -> + [id, tuple(nuclear, membrane)] }.set{ create_stack_in } + grouped_map_stack.map{ + [it[0], it[1]] + }.set{ nuclear_only } // for segmentation options that only accept one channel + // // MODULE: Stack channels if membrane image provided for segmentation // @@ -159,10 +123,7 @@ workflow MOLKART { ch_versions = ch_versions.mix(CROPHDF5.out.versions) // Combine images with crop_summary for making the same training tiff stacks as ilastik tiff_crop = stack_mix.join(CROPHDF5.out.crop_summary) - CROPTIFF( - tiff_crop.map(it -> tuple(it[0],it[1])), - tiff_crop.map(it -> tuple(it[0],it[2])), - ) + CROPTIFF(tiff_crop) ch_versions = ch_versions.mix(CROPTIFF.out.versions) MOLKARTQCPNG(CROPTIFF.out.overview.map{ tuple('matchkey', it[1]) @@ -177,8 +138,7 @@ workflow MOLKART { MINDAGAP_DUPLICATEFINDER(spot_tuple) ch_versions = ch_versions.mix(MINDAGAP_DUPLICATEFINDER.out.versions) - qc_spots = MINDAGAP_DUPLICATEFINDER.out.marked_dups_spots - + qc_spots = params.skip_mindagap ? spot_tuple : MINDAGAP_DUPLICATEFINDER.out.marked_dups_spots // // MODULE: DeepCell Mesmer segmentation // @@ -196,6 +156,18 @@ workflow MOLKART { .combine(Channel.of('mesmer'))) } // + // MODULE: Stardist segmentation + // + if (params.segmentation_method.split(',').contains('stardist')) { + STARDIST( + nuclear_only, + ) + ch_versions = ch_versions.mix(STARDIST.out.versions) + segmentation_masks = segmentation_masks + .mix(STARDIST.out.mask + .combine(Channel.of('stardist'))) + } + // // MODULE: Cellpose segmentation // cellpose_custom_model = params.cellpose_custom_model ? stack_mix.combine(Channel.fromPath(params.cellpose_custom_model)) : [] @@ -252,7 +224,7 @@ workflow MOLKART { } segmentation_masks.map{ meta, mask, segmentation -> - new_meta = meta.clone() + def new_meta = meta.clone() new_meta.segmentation = segmentation [new_meta, mask] }.set { matched_segmasks } @@ -274,16 +246,12 @@ workflow MOLKART { .combine(filtered_masks, by: 0) .map { meta, spots_table, mask, segmethod -> - new_meta = meta.clone() + def new_meta = meta.clone() new_meta.segmentation = segmethod [new_meta, spots_table, mask] } .set { dedup_spots } - - SPOT2CELL( - dedup_spots.map(it -> tuple(it[0],it[1])), - dedup_spots.map(it -> tuple(it[0],it[2])) - ) + SPOT2CELL(dedup_spots) ch_versions = ch_versions.mix(SPOT2CELL.out.versions) // @@ -307,29 +275,53 @@ workflow MOLKART { qc_spots .combine(spot2cell_out, by: 0) .set{ molkartqc } - MOLKARTQC(molkartqc) ch_versions = ch_versions.mix(MOLKARTQC.out.versions) } + // - // MODULE: CUSTOM_DUMPSOFTWAREVERSIONS + // Collate and save software versions // + softwareVersionsToYAML(ch_versions) + .collectFile( + storeDir: "${params.outdir}/pipeline_info", + name: 'nf_core_' + 'molkart_software_' + 'mqc_' + 'versions.yml', + sort: true, + newLine: true + ).set { ch_collated_versions } - CUSTOM_DUMPSOFTWAREVERSIONS ( - ch_versions.unique().collectFile(name: 'collated_versions.yml') - ) // // MODULE: MultiQC // - workflow_summary = WorkflowMolkart.paramsSummaryMultiqc(workflow, summary_params) - ch_workflow_summary = Channel.value(workflow_summary) - methods_description = WorkflowMolkart.methodsDescriptionText(workflow, ch_multiqc_custom_methods_description, params) - ch_methods_description = Channel.value(methods_description) - - ch_multiqc_files = Channel.empty() - + ch_multiqc_config = Channel.fromPath( + "$projectDir/assets/multiqc_config.yml", checkIfExists: true) + ch_multiqc_custom_config = params.multiqc_config ? + Channel.fromPath(params.multiqc_config, checkIfExists: true) : + Channel.empty() + ch_multiqc_logo = params.multiqc_logo ? + Channel.fromPath(params.multiqc_logo, checkIfExists: true) : + Channel.fromPath("$projectDir/assets/nf-core-molkart_logo_light.png", checkIfExists: true) + + summary_params = paramsSummaryMap( + workflow, parameters_schema: "nextflow_schema.json") + ch_workflow_summary = Channel.value(paramsSummaryMultiqc(summary_params)) + ch_multiqc_files = ch_multiqc_files.mix( + ch_workflow_summary.collectFile(name: 'workflow_summary_mqc.yaml')) + ch_multiqc_custom_methods_description = params.multiqc_methods_description ? + file(params.multiqc_methods_description, checkIfExists: true) : + file("$projectDir/assets/methods_description_template.yml", checkIfExists: true) + ch_methods_description = Channel.value( + methodsDescriptionText(ch_multiqc_custom_methods_description)) + + ch_multiqc_files = ch_multiqc_files.mix(ch_collated_versions) + ch_multiqc_files = ch_multiqc_files.mix( + ch_methods_description.collectFile( + name: 'methods_description_mqc.yaml', + sort: true + ) + ) if ( params.create_training_subset ){ ch_multiqc_files = ch_multiqc_files.mix( MOLKARTQCPNG.out.png_overview @@ -341,36 +333,21 @@ workflow MOLKART { } else { ch_multiqc_files = ch_multiqc_files.mix( MOLKARTQC.out.qc.map{it[1]} - .collectFile(name: 'final_QC.all_samples.csv', keepHeader: true, storeDir: "${params.outdir}/multiqc")) + .collectFile()) } - ch_multiqc_files = ch_multiqc_files.mix(ch_workflow_summary.collectFile(name: 'workflow_summary_mqc.yaml')) - ch_multiqc_files = ch_multiqc_files.mix(ch_methods_description.collectFile(name: 'methods_description_mqc.yaml')) - ch_multiqc_files = ch_multiqc_files.mix(CUSTOM_DUMPSOFTWAREVERSIONS.out.mqc_yml.collect()) MULTIQC ( ch_multiqc_files.collect(), ch_multiqc_config.toList(), ch_multiqc_custom_config.toList(), - ch_multiqc_logo.toList() + ch_multiqc_logo.toList(), + [], + [] ) - multiqc_report = MULTIQC.out.report.toList() -} -/* -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - COMPLETION EMAIL AND SUMMARY -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -*/ + emit:multiqc_report = MULTIQC.out.report.toList() // channel: /path/to/multiqc_report.html + versions = ch_versions // channel: [ path(versions.yml) ] -workflow.onComplete { - if (params.email || params.email_on_fail) { - NfcoreTemplate.email(workflow, params, summary_params, projectDir, log, multiqc_report) - } - NfcoreTemplate.dump_parameters(workflow, params) - NfcoreTemplate.summary(workflow, params, log) - if (params.hook_url) { - NfcoreTemplate.IM_notification(workflow, params, summary_params, projectDir, log) - } } /*
    Process Name \\", - " \\ Software Version
    CUSTOM_DUMPSOFTWAREVERSIONSpython3.11.7
    yaml5.4.1
    TOOL1tool10.11.9
    TOOL2tool21.9
    WorkflowNextflow