diff --git a/.devcontainer/setup.sh b/.devcontainer/setup.sh new file mode 100755 index 000000000..02272fe96 --- /dev/null +++ b/.devcontainer/setup.sh @@ -0,0 +1,13 @@ +#!/usr/bin/env bash + +# Customise the terminal command prompt +echo "export PROMPT_DIRTRIM=2" >> $HOME/.bashrc +echo "export PS1='\[\e[3;36m\]\w ->\[\e[0m\\] '" >> $HOME/.bashrc +export PROMPT_DIRTRIM=2 +export PS1='\[\e[3;36m\]\w ->\[\e[0m\\] ' + +# Update Nextflow +nextflow self-update + +# Update welcome message +echo "Welcome to the bigbio/quantms devcontainer!" > /usr/local/etc/vscode-dev-containers/first-run-notice.txt diff --git a/.editorconfig b/.editorconfig deleted file mode 100644 index 72dda289a..000000000 --- a/.editorconfig +++ /dev/null @@ -1,33 +0,0 @@ -root = true - -[*] -charset = utf-8 -end_of_line = lf -insert_final_newline = true -trim_trailing_whitespace = true -indent_size = 4 -indent_style = space - -[*.{md,yml,yaml,html,css,scss,js}] -indent_size = 2 - -# These files are edited and tested upstream in nf-core/modules -[/modules/nf-core/**] -charset = unset -end_of_line = unset -insert_final_newline = unset -trim_trailing_whitespace = unset -indent_style = 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 diff --git a/.github/actions/nf-test/action.yml b/.github/actions/nf-test/action.yml index bf44d9612..3b9724c76 100644 --- a/.github/actions/nf-test/action.yml +++ b/.github/actions/nf-test/action.yml @@ -25,9 +25,9 @@ runs: version: "${{ env.NXF_VERSION }}" - name: Set up Python - uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5 + uses: actions/setup-python@e797f83bcb11b83ae66e0230d6156d7c80228e7c # v6 with: - python-version: "3.13" + python-version: "3.14" - name: Install nf-test uses: nf-core/setup-nf-test@v1 @@ -52,6 +52,8 @@ runs: with: auto-update-conda: true conda-solver: libmamba + channels: conda-forge + channel-priority: strict conda-remove-defaults: true - name: Run nf-test diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 7ffffc171..d78de8d33 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -34,8 +34,17 @@ jobs: fail-fast: false matrix: NXF_VER: - - "24.10.5" - test_profile: ["test_lfq", "test_dia", "test_localize", "test_tmt", "test_dda_id"] + - "25.04.0" + test_profile: + [ + "test_lfq", + "test_dia", + "test_localize", + "test_tmt", + "test_dda_id_alphapeptdeep", + "test_dda_id_ms2pip", + "test_dda_id_fine_tuning", + ] exec_profile: ["docker"] # extended ci tests singularity. steps: diff --git a/.github/workflows/clean-up.yml b/.github/workflows/clean-up.yml index ac030fd58..6adb0fff4 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@5bef64f19d7facfb25b37b414482c7164d639639 # v9 + - uses: actions/stale@5f858e3efba33a5ca4407a664cc011ad407f2008 # v10 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/diann_private.yml b/.github/workflows/diann_private.yml index f744121cc..deb7e8a2d 100644 --- a/.github/workflows/diann_private.yml +++ b/.github/workflows/diann_private.yml @@ -38,7 +38,7 @@ jobs: - name: Set up Nextflow uses: nf-core/setup-nextflow@v2 with: - version: "24.10.5" # Or the Nextflow version you prefer + version: "25.04.0" # Or the Nextflow version you prefer - name: Log in to GitHub Container Registry uses: docker/login-action@v3 diff --git a/.github/workflows/extended_ci.yml b/.github/workflows/extended_ci.yml index 4dd1fda2c..9d0740f2e 100644 --- a/.github/workflows/extended_ci.yml +++ b/.github/workflows/extended_ci.yml @@ -28,25 +28,34 @@ jobs: fail-fast: false matrix: NXF_VER: - - "24.10.5" + - "25.04.0" - "latest-everything" - test_profile: ["test_lfq", "test_dia", "test_localize", "test_tmt", "test_dda_id", "test_tmt_corr"] + test_profile: + [ + "test_lfq", + "test_dia", + "test_localize", + "test_tmt", + "test_dda_id_alphapeptdeep", + "test_dda_id_fine_tuning", + "test_dda_id_ms2pip", + "test_tmt_corr", + ] env: NXF_ANSI_LOG: false CAPSULE_LOG: none TEST_PROFILE: ${{ matrix.test_profile }} EXEC_PROFILE: docker + steps: - - name: Check out pipeline code + - name: Checkout repository uses: actions/checkout@v4 - with: - fetch-depth: 0 - - uses: actions/setup-java@v4 + - name: Set up Java 17 + uses: actions/setup-java@v4 with: distribution: "temurin" java-version: "17" - - name: Set up Nextflow uses: nf-core/setup-nextflow@v2 with: @@ -111,9 +120,19 @@ jobs: fail-fast: false matrix: NXF_VER: - - "24.10.5" + - "25.04.0" - "latest-everything" - test_profile: ["test_lfq", "test_dia", "test_localize", "test_tmt", "test_dda_id", "test_tmt_corr"] + test_profile: + [ + "test_lfq", + "test_dia", + "test_localize", + "test_tmt", + "test_dda_id_ms2pip", + "test_dda_id_alphapeptdeep", + "test_tmt_corr", + "test_dda_id_fine_tuning", + ] env: NXF_ANSI_LOG: false CAPSULE_LOG: none diff --git a/.github/workflows/fix_linting.yml b/.github/workflows/fix_linting.yml index 19ecd66f2..471fd48c9 100644 --- a/.github/workflows/fix_linting.yml +++ b/.github/workflows/fix_linting.yml @@ -13,13 +13,13 @@ jobs: runs-on: ubuntu-latest steps: # Use the @nf-core-bot token to check out so we can push later - - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4 + - uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5 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 + uses: peter-evans/create-or-update-comment@e8674b075228eee787fea43ef493e45ece1004c9 # v5 with: comment-id: ${{ github.event.comment.id }} reactions: eyes @@ -32,9 +32,9 @@ jobs: GITHUB_TOKEN: ${{ secrets.nf_core_bot_auth_token }} # Install and run pre-commit - - uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5 + - uses: actions/setup-python@e797f83bcb11b83ae66e0230d6156d7c80228e7c # v6 with: - python-version: "3.13" + python-version: "3.14" - name: Install pre-commit run: pip install pre-commit @@ -47,7 +47,7 @@ jobs: # 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 + uses: peter-evans/create-or-update-comment@e8674b075228eee787fea43ef493e45ece1004c9 # v5 with: comment-id: ${{ github.event.comment.id }} reactions: "+1" @@ -67,21 +67,21 @@ jobs: - 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 + uses: peter-evans/create-or-update-comment@e8674b075228eee787fea43ef493e45ece1004c9 # v5 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 + uses: peter-evans/create-or-update-comment@e8674b075228eee787fea43ef493e45ece1004c9 # v5 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 + uses: peter-evans/create-or-update-comment@e8674b075228eee787fea43ef493e45ece1004c9 # v5 with: issue-number: ${{ github.event.issue.number }} body: | diff --git a/.github/workflows/linting.yml b/.github/workflows/linting.yml index 8b0f88c36..7a527a346 100644 --- a/.github/workflows/linting.yml +++ b/.github/workflows/linting.yml @@ -11,12 +11,12 @@ jobs: pre-commit: runs-on: ubuntu-latest steps: - - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4 + - uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5 - - name: Set up Python 3.13 - uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5 + - name: Set up Python 3.14 + uses: actions/setup-python@e797f83bcb11b83ae66e0230d6156d7c80228e7c # v6 with: - python-version: "3.13" + python-version: "3.14" - name: Install pre-commit run: pip install pre-commit @@ -28,14 +28,14 @@ jobs: runs-on: ubuntu-latest steps: - name: Check out pipeline code - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4 + uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5 - name: Install Nextflow uses: nf-core/setup-nextflow@v2 - - uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5 + - uses: actions/setup-python@e797f83bcb11b83ae66e0230d6156d7c80228e7c # v6 with: - python-version: "3.13" + python-version: "3.14" architecture: "x64" - name: read .nf-core.yml @@ -71,7 +71,7 @@ jobs: - name: Upload linting log file artifact if: ${{ always() }} - uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4 + uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5 with: name: linting-logs path: | diff --git a/.github/workflows/linting_comment.yml b/.github/workflows/linting_comment.yml index d43797d9d..e6e9bc269 100644 --- a/.github/workflows/linting_comment.yml +++ b/.github/workflows/linting_comment.yml @@ -21,7 +21,7 @@ jobs: run: echo "pr_number=$(cat linting-logs/PR_number.txt)" >> $GITHUB_OUTPUT - name: Post PR comment - uses: marocchino/sticky-pull-request-comment@52423e01640425a022ef5fd42c6fb5f633a02728 # v2 + uses: marocchino/sticky-pull-request-comment@773744901bac0e8cbb5a0dc842800d45e9b2b405 # v2 with: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} number: ${{ steps.pr_number.outputs.pr_number }} diff --git a/.github/workflows/template-version-comment.yml b/.github/workflows/template-version-comment.yml index beb5c77fb..e8560fc7c 100644 --- a/.github/workflows/template-version-comment.yml +++ b/.github/workflows/template-version-comment.yml @@ -9,7 +9,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Check out pipeline code - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4 + uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5 with: ref: ${{ github.event.pull_request.head.sha }} diff --git a/.gitignore b/.gitignore index 965861ff9..6c86bf8a4 100644 --- a/.gitignore +++ b/.gitignore @@ -10,3 +10,8 @@ null/ .idea/ /lint_log.txt /lint_results.md + + +#Ignore cursor AI rules +.cursor/rules/codacy.mdc +.codacy/ diff --git a/.gitpod.yml b/.gitpod.yml deleted file mode 100644 index 83599f633..000000000 --- a/.gitpod.yml +++ /dev/null @@ -1,10 +0,0 @@ -image: nfcore/gitpod:latest -tasks: - - name: Update Nextflow and setup pre-commit - command: | - pre-commit install --install-hooks - nextflow self-update - -vscode: - extensions: - - nf-core.nf-core-extensionpack # https://github.com/nf-core/vscode-extensionpack diff --git a/.nf-core.yml b/.nf-core.yml index 36f11a1fa..0f14d5cbf 100644 --- a/.nf-core.yml +++ b/.nf-core.yml @@ -20,11 +20,10 @@ lint: - .github/workflows/download_pipeline.yml - conf/modules.config - conf/igenomes_ignored.config - included_configs: false + modules_config: false multiqc_config: false nextflow_config: false - modules_config: false -nf_core_version: 3.3.2 +nf_core_version: 3.5.1 repository_type: pipeline template: author: Yasset Perez-Riverol @@ -34,4 +33,4 @@ template: name: quantms org: bigbio outdir: . - version: 1.6.0 + version: 1.7.0 diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index bb41beec1..d06777a8f 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -6,7 +6,7 @@ repos: additional_dependencies: - prettier@3.6.2 - repo: https://github.com/pre-commit/pre-commit-hooks - rev: v5.0.0 + rev: v6.0.0 hooks: - id: trailing-whitespace args: [--markdown-linebreak-ext=md] diff --git a/.prettierignore b/.prettierignore index edd29f01e..dd749d43d 100644 --- a/.prettierignore +++ b/.prettierignore @@ -10,4 +10,7 @@ testing/ testing* *.pyc bin/ +.nf-test/ ro-crate-metadata.json +modules/nf-core/ +subworkflows/nf-core/ diff --git a/CHANGELOG.md b/CHANGELOG.md index 996953c67..834980a69 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,37 @@ 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). +## [1.7.0] bigbio/quantms - [08/01/2026] - [Caracas] + +### `Added` + +- [626](https://github.com/bigbio/quantms/pull/626) Added onsite module to quantms +- [629](https://github.com/bigbio/quantms/pull/629) Added transfer learning module for MS2 intensity prediction + +### `Changed` + +- [627](https://github.com/bigbio/quantms/pull/627) Move thermorawfileparser and onsite modules from local to bigbio/nf-modules +- [629](https://github.com/bigbio/quantms/pull/629) Update quantms-rescoring to 0.0.13 to support transfer learning +- [615](https://github.com/bigbio/quantms/pull/615) Update quantms-utils 0.0.24 and pmultiqc 0.0.39 +- [614](https://github.com/bigbio/quantms/pull/614) Changed enable_diann_mztab default from true to false. For DIA pipelines the mzTab will not be generated unless specified by a parameter `--enable_diann_mztab true`. +- [635](https://github.com/bigbio/quantms/pull/635) Minimum Nextflow version requirement updated to `>=25.04.0` + +### `Fixed` + +- [619](https://github.com/bigbio/quantms/pull/619) quantms-rescoring bug fixed +- [614](https://github.com/bigbio/quantms/pull/614) Multiple bugs fixed about diann, ptms and pmultiqc + +### `Dependencies` + +- Updated quantms-rescoring==0.0.13 +- Updated pmultiqc==0.0.39 +- Updated pyonsite==0.0.2 +- Updated quantms-utils==0.0.24 + +### `Deprecations` + +- [626](https://github.com/bigbio/quantms/pull/626) Removed luciphor-specific parameters: `luciphor_neutral_losses`, `luciphor_decoy_mass`, `luciphor_decoy_neutral_losses`. These have been replaced with onsite parameters: `onsite_neutral_losses`, `onsite_decoy_mass`, `onsite_decoy_neutral_losses`. The new onsite module provides support for multiple PTM localization algorithms (AScore, PhosphoRS, and LucXor) with unified parameter naming. See the [Migration Guide](docs/usage.md#migrating-from-luciphor_-to-onsite_-parameters-version-170) in the documentation for a complete parameter mapping table and migration instructions. + ## [1.6.0] bigbio/quantms - [13/08/2025] - Munich ### `Added` diff --git a/README.md b/README.md index 453eba31d..0a4be8db4 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,8 @@ [![Cite with Zenodo](https://zenodo.org/badge/DOI/10.5281/zenodo.15573386.svg)](https://doi.org/10.5281/zenodo.15573386) [![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.10.5-23aa62.svg)](https://www.nextflow.io/) +[![Nextflow](https://img.shields.io/badge/version-%E2%89%A525.04.0-green?style=flat&logo=nextflow&logoColor=white&color=%230DC09D&link=https%3A%2F%2Fnextflow.io)](https://www.nextflow.io/) +[![nf-core template version](https://img.shields.io/badge/nf--core_template-3.4.1-green?style=flat&logo=nfcore&logoColor=white&color=%2324B064&link=https%3A%2F%2Fnf-co.re)](https://github.com/nf-core/tools/releases/tag/3.4.1) [![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 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/quantms) @@ -33,10 +34,10 @@ On release, automated continuous integration tests run the pipeline on a full-si 1. RAW file conversion to mzML ([`thermorawfileparser`](https://github.com/compomics/ThermoRawFileParser)) 2. Peptide identification using [`comet`](https://uwpr.github.io/Comet/) and/or [`msgf+`](https://github.com/MSGFPlus/msgfplus) -3. (Optional) Add extra PSM features using [`ms2rescore`](https://github.com/compomics/ms2rescore) +3. (Optional) Add extra PSM features using [`quantms-rescoring`](https://github.com/bigbio/quantms-rescoring) 4. Re-scoring peptide identifications [`percolator`](https://github.com/percolator/percolator) 5. Peptide identification FDR [`openms fdr tool`](https://github.com/bigbio/quantms/blob/HEAD/modules/local/openms/false_discovery_rate/main.nf) -6. Modification localization [`luciphor`](https://github.com/dfermin/lucXor) +6. Modification localization [`onsite`](https://github.com/bigbio/onsite) 7. Quantification: Feature detection [`proteomicsLFQ`](https://abibuilder.cs.uni-tuebingen.de/archive/openms/Documentation/nightly/html/TOPP_ProteomicsLFQ.html) 8. Protein inference and quantification [`proteomicsLFQ`](https://abibuilder.cs.uni-tuebingen.de/archive/openms/Documentation/nightly/html/TOPP_ProteomicsLFQ.html) 9. QC report generation [`pmultiqc`](https://github.com/bigbio/pmultiqc) @@ -46,10 +47,10 @@ On release, automated continuous integration tests run the pipeline on a full-si 1. RAW file conversion to mzML ([`thermorawfileparser`](https://github.com/compomics/ThermoRawFileParser)) 2. Peptide identification using [`comet`](https://uwpr.github.io/Comet/) and/or [`msgf+`](https://github.com/MSGFPlus/msgfplus) -3. (Optional) Add extra PSM features using [`ms2rescore`](https://github.com/compomics/ms2rescore) +3. (Optional) Add extra PSM features using [`quantms-rescoring`](https://github.com/bigbio/quantms-rescoring) 4. Re-scoring peptide identifications [`percolator`](https://github.com/percolator/percolator) 5. Peptide identification FDR [`openms fdr tool`](https://github.com/bigbio/quantms/blob/HEAD/modules/local/openms/false_discovery_rate/main.nf) -6. Modification localization [`luciphor`](https://github.com/dfermin/lucXor) +6. Modification localization [`onsite`](https://github.com/bigbio/onsite) 7. Extracts and normalizes isobaric labeling [`IsobaricAnalyzer`](https://abibuilder.cs.uni-tuebingen.de/archive/openms/Documentation/nightly/html/TOPP_IsobaricAnalyzer.html) 8. Protein inference [`ProteinInference`](https://abibuilder.cs.uni-tuebingen.de/archive/openms/Documentation/nightly/html/TOPP_ProteinInference.html) or [`Epifany`](https://abibuilder.cs.uni-tuebingen.de/archive/openms/Documentation/nightly/html/TOPP_Epifany.html) for bayesian inference. 9. Protein Quantification [`ProteinQuantifier`](https://abibuilder.cs.uni-tuebingen.de/archive/openms/Documentation/nightly/html/TOPP_ProteinQuantifier.html) @@ -77,6 +78,22 @@ A graphical overview of suggested routes through the pipeline depending on conte > [!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. +### Supported file formats + +The pipeline supports the following mass spectrometry data file formats: + +- **`.raw`** - Thermo RAW files (automatically converted to mzML) +- **`.mzML`** - Open standard mzML files +- **`.d`** - Bruker timsTOF files (optionally converted to mzML when `--convert_dotd` is set) +- **`.dia`** - DIA-NN native binary format (passed through without conversion) + +Compressed variants are supported for `.raw`, `.mzML`, and `.d` formats: + +- `.gz` (gzip compressed) +- `.tar` (tar archive) +- `.tar.gz` or `.tgz` (tar gzip compressed) +- `.zip` (zip compressed) + First, find or create a sample-to-data relationship file ([SDRF](https://github.com/bigbio/proteomics-sample-metadata)). Have a look at public datasets that were already annotated [here](https://github.com/bigbio/proteomics-sample-metadata/tree/master/annotated-projects). Those SDRFs should be ready for one-command re-analysis and you can just use the URL to the file on GitHub, @@ -87,8 +104,6 @@ The second requirement is a protein sequence database. We suggest downloading a Now, you can run the pipeline using: - - ```bash nextflow run bigbio/quantms \ -profile \ diff --git a/assets/methods_description_template.yml b/assets/methods_description_template.yml index 70ee8b50b..89919dfe7 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: "bigbio/quantms Methods Description" section_href: "https://github.com/bigbio/quantms" 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 3b02c4530..4b9131d57 100644 --- a/assets/multiqc_config.yml +++ b/assets/multiqc_config.yml @@ -1,5 +1,5 @@ report_comment: > - This report has been generated by the bigbio/quantms + This report has been generated by the bigbio/quantms analysis pipeline. For information about how to interpret these results, please see the documentation. report_section_order: diff --git a/assets/nf-core-quantms_logo_light.png b/assets/nf-core-quantms_logo_light.png index e5cd4222a..eca702178 100644 Binary files a/assets/nf-core-quantms_logo_light.png and b/assets/nf-core-quantms_logo_light.png differ diff --git a/assets/schema_input.json b/assets/schema_input.json index 67e72b3af..4fa756dd0 100644 --- a/assets/schema_input.json +++ b/assets/schema_input.json @@ -17,6 +17,12 @@ "format": "file-path", "errorMessage": "Spectrum files must be provided", "meta": ["id"] + }, + "assay name": { + "type": "string", + "pattern": "^\\S+$", + "errorMessage": "Assay name must be provided and cannot contain spaces", + "meta": ["assay"] } } } diff --git a/assets/tmt18plex_default_correction.matrix b/assets/tmt18plex_default_correction.matrix new file mode 100644 index 000000000..79563f28d --- /dev/null +++ b/assets/tmt18plex_default_correction.matrix @@ -0,0 +1,20 @@ +# comment: tmt18plex default +channel/<-2C13>/<-N15-C13>/<-C13>/<-N15>/<+N15>/<+C13>/<+N15+C13>/<+2C13> +126/0.00/0.00/0.00/0.00/0.10/10.40/0.00/0.30 +127N/0.00/0.00/0.00/0.60/0.00/9.80/0.00/0.30 +127C/0.00/0.00/0.80/0.00/0.20/8.40/0.00/0.30 +128N/0.00/0.00/0.70/0.50/0.00/5.20/0.00/0.20 +128C/0.00/0.00/1.40/0.00/0.30/6.30/0.00/0.20 +129N/0.00/0.10/1.30/0.90/0.00/7.50/0.00/0.10 +129C/0.10/0.00/2.60/0.00/0.30/6.10/0.00/0.10 +130N/0.00/0.00/2.40/0.50/0.00/5.50/0.00/0.00 +130C/0.30/0.00/3.20/0.00/0.30/5.10/0.00/0.10 +131N/0.00/0.00/2.70/0.50/0.00/3.10/0.00/0.10 +131C/0.10/0.00/4.00/0.00/1.20/3.60/0.00/0.00 +132N/0.00/0.00/3.10/0.70/0.00/3.40/0.00/0.00 +132C/0.10/0.00/5.10/0.00/1.20/1.90/0.00/0.00 +133N/0.10/0.00/3.40/0.60/0.00/1.20/0.00/0.00 +133C/0.20/0.00/4.10/0.00/0.40/1.10/0.00/0.00 +134N/0.30/0.10/5.50/0.40/0.00/1.10/0.00/0.00 +134C/0.00/0.00/5.50/0.00/0.30/0.00/0.00/0.00 +135N/0.20/0.00/5.80/0.30/0.00/0.00/0.00/0.00 diff --git a/bin/msstats_tmt.R b/bin/msstats_tmt.R index c6b6dbff8..ffc8bfbcd 100755 --- a/bin/msstats_tmt.R +++ b/bin/msstats_tmt.R @@ -83,7 +83,7 @@ parse_contrasts <- function(l, contrast_str, lvls) { comparison <- rep(0, l) comparison[i] <- 1 comparison[j] <- -1 - contrast_mat[c,] <- comparison + contrast_mat[c, ] <- comparison rownames(contrast_mat)[c] <- paste0(lvls[i], "-", lvls[j]) c <- c + 1 } @@ -91,16 +91,16 @@ parse_contrasts <- function(l, contrast_str, lvls) { } else { control <- which(as.character(lvls) == control_str) if (length(control) == 0) { - stop("Control condition not part of found levels.n", call. = FALSE) + stop("Control condition not part of found levels.\n", call. = FALSE) } contrast_mat <- matrix(nrow = l - 1, ncol = l, dimnames = list(Contrasts = rep(NA, l - 1), Levels = lvls)) c <- 1 for (j in setdiff(1:l, control)) { comparison <- rep(0, l) - comparison[i] <- -1 + comparison[control] <- -1 # Fixed: use 'control' instead of 'i' comparison[j] <- 1 - contrast_mat[c,] <- comparison - rownames(contrast_mat)[c] <- paste0(lvls[i], "-", lvls[j]) + contrast_mat[c, ] <- comparison + rownames(contrast_mat)[c] <- paste0(lvls[j], "-", lvls[control]) # Fixed: compare j vs. control c <- c + 1 } } @@ -120,9 +120,9 @@ parse_contrasts <- function(l, contrast_str, lvls) { #' #' @return make_contrasts <- function(contrasts, levels) { - #helper function - indicatorRow <- function(pos,len){ - row <- rep(0,len) + # helper function + indicatorRow <- function(pos, len) { + row <- rep(0, len) row[pos] <- 1 return(row) } @@ -131,24 +131,24 @@ make_contrasts <- function(contrasts, levels) { if (!is.character(levels)) levels <- colnames(levels) l <- length(levels) - if (l < 1){ + if (l < 1) { stop("No levels given") } ncontr <- length(contrasts) - if (ncontr < 1){ + if (ncontr < 1) { stop("No contrasts given") } levelsenv <- new.env() for (i in 1:l) { - assign(levels[i], indicatorRow(i,l), pos=levelsenv) + assign(levels[i], indicatorRow(i, l), pos = levelsenv) } - contrastmat <- matrix(0, l, ncontr, dimnames=list(Levels=levels,Contrasts=contrasts)) + contrastmat <- matrix(0, l, ncontr, dimnames = list(Levels = levels, Contrasts = contrasts)) for (j in 1:ncontr) { - contrastsj <- parse(text=contrasts[j]) - contrastmat[,j] <- eval(contrastsj, envir=levelsenv) + contrastsj <- parse(text = contrasts[j]) + contrastmat[, j] <- eval(contrastsj, envir = levelsenv) } return(t(contrastmat)) } @@ -159,19 +159,21 @@ make_contrasts <- function(contrasts, levels) { #' #' @return get_missing_in_condition <- function(processedData) { - p <- processedData - n_samples <- aggregate(p$SUBJECT, by = list(p$GROUP), FUN = function(x) {return(length(unique(as.numeric(x))))}) - colnames(n_samples) <- c("GROUP", "n_samples") - p <- p[complete.cases(p["LogIntensities"]),][,c("Protein", "GROUP", "SUBJECT")] - p_dup <- p[!duplicated(p),] - p_dup_agg <- aggregate(p_dup$SUBJECT, by = list(p_dup$Protein, p_dup$GROUP), length) - colnames(p_dup_agg) <- c("Protein", "GROUP", "non_na") - agg_join <- merge(p_dup_agg, n_samples, by = "GROUP") - agg_join$missingInCondition <- 1 - agg_join$non_na / agg_join$n_samples - - p <- dcast(setDT(agg_join), Protein~GROUP, value.var = "missingInCondition") - return(p) - } + p <- processedData + n_samples <- aggregate(p$SUBJECT, by = list(p$GROUP), FUN = function(x) { + return(length(unique(as.numeric(x)))) + }) + colnames(n_samples) <- c("GROUP", "n_samples") + p <- p[complete.cases(p["LogIntensities"]), ][, c("Protein", "GROUP", "SUBJECT")] + p_dup <- p[!duplicated(p), ] + p_dup_agg <- aggregate(p_dup$SUBJECT, by = list(p_dup$Protein, p_dup$GROUP), length) + colnames(p_dup_agg) <- c("Protein", "GROUP", "non_na") + agg_join <- merge(p_dup_agg, n_samples, by = "GROUP") + agg_join$missingInCondition <- 1 - agg_join$non_na / agg_join$n_samples + + p <- dcast(setDT(agg_join), Protein ~ GROUP, value.var = "missingInCondition") + return(p) +} #' Check groupComparisonPlots parameters #' @param type type of a plot: HEATMAP/VOLCANOPLOT/COMPARISONPLOT @@ -179,33 +181,37 @@ get_missing_in_condition <- function(processedData) { #' @param selected_labels character vector of contrast labels #' @param all_labels character vector of all contrast labels #' @keywords internal -.checkGCPlotsInput = function(type, log_base, selected_labels, all_labels) { +.checkGCPlotsInput <- function(type, log_base, selected_labels, all_labels) { checkmate::assertChoice(type, c("HEATMAP", "VOLCANOPLOT", "COMPARISONPLOT")) checkmate::assertChoice(log_base, c(2, 10)) if (selected_labels != "all") { if (is.character(selected_labels)) { - chosen_labels = selected_labels - wrong_labels = setdiff(chosen_labels, all_labels) + chosen_labels <- selected_labels + wrong_labels <- setdiff(chosen_labels, all_labels) if (length(wrong_labels) > 0) { - msg_1 = paste("Please check labels of comparisons.", - "Result does not have the following comparisons:") - msg_2 = paste(wrong_labels, sep = ", ", collapse = ", ") - msg = paste(msg_1, msg_2) + msg_1 <- paste( + "Please check labels of comparisons.", + "Result does not have the following comparisons:" + ) + msg_2 <- paste(wrong_labels, sep = ", ", collapse = ", ") + msg <- paste(msg_1, msg_2) stop(msg) } } if (is.numeric(selected_labels)) { - n_labels = length(all_labels) + n_labels <- length(all_labels) if (n_labels < max(selected_labels)) { - msg = paste("Please check your selection of comparisons. There are", - n_labels, "comparisons in this result.") + msg <- paste( + "Please check your selection of comparisons. There are", + n_labels, "comparisons in this result." + ) stop(msg) } else { - chosen_labels = all_labels[selected_labels] + chosen_labels <- all_labels[selected_labels] } } } else { - chosen_labels = all_labels + chosen_labels <- all_labels } chosen_labels } @@ -213,16 +219,18 @@ get_missing_in_condition <- function(processedData) { #' @importFrom stats quantile dist #' @keywords internal -.getOrderedMatrix = function(input, type) { - input_tmp = input - input_tmp[is.na(input)] = 50 +.getOrderedMatrix <- function(input, type) { + input_tmp <- input + input_tmp[is.na(input)] <- 50 if (toupper(type) == "PROTEIN") { - input = input[hclust(dist(input_tmp), method = "ward.D")$order, ] + input <- input[hclust(dist(input_tmp), method = "ward.D")$order, ] } else if (toupper(type) == "COMPARISON") { - input = input[, hclust(dist(t(input_tmp)), method = "ward.D")$order] + input <- input[, hclust(dist(t(input_tmp)), method = "ward.D")$order] } else if (toupper(type) == "BOTH") { - input = input[hclust(dist(input_tmp), method = "ward.D")$order, - hclust(dist(t(input)), method = "ward.D")$order] + input <- input[ + hclust(dist(input_tmp), method = "ward.D")$order, + hclust(dist(t(input)), method = "ward.D")$order + ] } input } @@ -232,17 +240,18 @@ get_missing_in_condition <- function(processedData) { #' @param input data.table #' @inheritParams groupComparisonPlots #' @keywords internal -.makeHeatmap = function(input, my.colors, my.breaks, x.axis.size, y.axis.size) { +.makeHeatmap <- function(input, my.colors, my.breaks, x.axis.size, y.axis.size) { par(oma = c(3, 0, 0, 4)) heatmap.2(as.matrix(input), - col = my.colors, - Rowv = FALSE, Colv = FALSE, - dendrogram = "none", breaks = my.breaks, - trace = "none", na.color = "grey", - cexCol = (x.axis.size / 10), - cexRow = (y.axis.size / 10), - key = FALSE, - lhei = c(0.1, 0.9), lwid = c(0.1, 0.9)) + col = my.colors, + Rowv = FALSE, Colv = FALSE, + dendrogram = "none", breaks = my.breaks, + trace = "none", na.color = "grey", + cexCol = (x.axis.size / 10), + cexRow = (y.axis.size / 10), + key = FALSE, + lhei = c(0.1, 0.9), lwid = c(0.1, 0.9) + ) } @@ -253,96 +262,136 @@ get_missing_in_condition <- function(processedData) { #' @param log_base_FC 2 or 10 #' @param log_base_pval 2 or 10 #' @keywords internal -.makeVolcano = function( +.makeVolcano <- function( input, label_name, log_base_FC, log_base_pval, x.lim, ProteinName, dot.size, y.limdown, y.limup, text.size, FCcutoff, sig, x.axis.size, y.axis.size, - legend.size, log_adjp -) { - Protein = NULL - - plot = ggplot(aes_string(x = "logFC", - y = log_adjp, - color = "colgroup", - label = "Protein"), - data = input) + + legend.size, log_adjp) { + Protein <- NULL + + plot <- ggplot( + aes_string( + x = "logFC", + y = log_adjp, + color = "colgroup", + label = "Protein" + ), + data = input + ) + geom_point(size = dot.size) + - scale_colour_manual(values = c("gray65", "blue", "red"), - limits = c("black", "blue", "red"), - breaks = c("black", "blue", "red"), - labels = c("No regulation", "Down-regulated", "Up-regulated")) + + scale_colour_manual( + values = c("gray65", "blue", "red"), + limits = c("black", "blue", "red"), + breaks = c("black", "blue", "red"), + labels = c("No regulation", "Down-regulated", "Up-regulated") + ) + scale_y_continuous(paste0("-Log", log_base_pval, " (adjusted p-value)"), - limits = c(y.limdown, y.limup)) + + limits = c(y.limdown, y.limup) + ) + labs(title = unique(label_name)) - plot = plot + + plot <- plot + scale_x_continuous(paste0("Log", log_base_pval, " fold change"), - limits = c(-x.lim, x.lim)) + limits = c(-x.lim, x.lim) + ) if (ProteinName) { if (!(length(unique(input$colgroup)) == 1 & any(unique(input$colgroup) == "black"))) { - plot = plot + - geom_text_repel(data = input[input$colgroup != "black", ], - aes(label = Protein), - size = text.size, - col = "black") + plot <- plot + + geom_text_repel( + data = input[input$colgroup != "black", ], + aes(label = Protein), + size = text.size, + col = "black" + ) } } if (!FCcutoff | is.numeric(FCcutoff)) { - l = ifelse(!FCcutoff, 20, 10) - sigcut = data.table::setnames( - data.table::data.table("sigline", - seq(-x.lim, x.lim, length.out = l), - (-log(sig, base = log_base_pval)), - "twodash"), - c("Protein", "logFC", log_adjp, "line")) + l <- ifelse(!FCcutoff, 20, 10) + sigcut <- data.table::setnames( + data.table::data.table( + "sigline", + seq(-x.lim, x.lim, length.out = l), + (-log(sig, base = log_base_pval)), + "twodash" + ), + c("Protein", "logFC", log_adjp, "line") + ) } if (!FCcutoff) { - plot = plot + - geom_line(data = sigcut, - aes_string(x = "logFC", y = log_adjp, linetype = "line"), - colour = "darkgrey", - size = 0.6, - show.legend = TRUE) + - scale_linetype_manual(values = c("twodash" = 6), - labels = c(paste0("Adj p-value cutoff (", sig, ")"))) + - guides(colour = guide_legend(override.aes = list(linetype = 0)), - linetype = guide_legend()) + plot <- plot + + geom_line( + data = sigcut, + aes_string(x = "logFC", y = log_adjp, linetype = "line"), + colour = "darkgrey", + size = 0.6, + show.legend = TRUE + ) + + scale_linetype_manual( + values = c("twodash" = 6), + labels = c(paste0("Adj p-value cutoff (", sig, ")")) + ) + + guides( + colour = guide_legend(override.aes = list(linetype = 0)), + linetype = guide_legend() + ) } if (is.numeric(FCcutoff)) { - FCcutpos = data.table::setnames(data.table("sigline", - log(FCcutoff, log_base_pval), - seq(y.limdown, y.limup, length.out = 10), - "dotted"), - c("Protein", "logFC", log_adjp, "line")) - FCcutneg = data.table::setnames(data.table("sigline", - (-log(FCcutoff, log_base_pval)), - seq(y.limdown, y.limup, length.out = 10), - "dotted"), - c("Protein", "logFC", log_adjp, "line")) - plot = plot + - geom_line(data = sigcut, - aes_string(x = "logFC", y = log_adjp, linetype = "line"), - colour = "darkgrey", - size = 0.6, - show.legend = TRUE) + - geom_line(data = FCcutpos, - aes_string(x = "logFC", y = log_adjp, linetype = "line"), - colour = "darkgrey", - size = 0.6, - show.legend = TRUE) + - geom_line(data = FCcutneg, - aes_string(x = "logFC", y = log_adjp, linetype = "line"), - colour = "darkgrey", - size = 0.6) + - scale_linetype_manual(values = c("dotted" = 3, "twodash" = 6), - labels = c(paste0("Fold change cutoff (", FCcutoff, ")"), - paste0("Adj p-value cutoff (", sig, ")"))) + - guides(colour = guide_legend(override.aes = list(linetype = 0)), - linetype = guide_legend()) - } - plot = plot + + FCcutpos <- data.table::setnames( + data.table( + "sigline", + log(FCcutoff, log_base_pval), + seq(y.limdown, y.limup, length.out = 10), + "dotted" + ), + c("Protein", "logFC", log_adjp, "line") + ) + FCcutneg <- data.table::setnames( + data.table( + "sigline", + (-log(FCcutoff, log_base_pval)), + seq(y.limdown, y.limup, length.out = 10), + "dotted" + ), + c("Protein", "logFC", log_adjp, "line") + ) + plot <- plot + + geom_line( + data = sigcut, + aes_string(x = "logFC", y = log_adjp, linetype = "line"), + colour = "darkgrey", + size = 0.6, + show.legend = TRUE + ) + + geom_line( + data = FCcutpos, + aes_string(x = "logFC", y = log_adjp, linetype = "line"), + colour = "darkgrey", + size = 0.6, + show.legend = TRUE + ) + + geom_line( + data = FCcutneg, + aes_string(x = "logFC", y = log_adjp, linetype = "line"), + colour = "darkgrey", + size = 0.6 + ) + + scale_linetype_manual( + values = c("dotted" = 3, "twodash" = 6), + labels = c( + paste0("Fold change cutoff (", FCcutoff, ")"), + paste0("Adj p-value cutoff (", sig, ")") + ) + ) + + guides( + colour = guide_legend(override.aes = list(linetype = 0)), + linetype = guide_legend() + ) + } + plot <- plot + theme_msstats("VOLCANOPLOT", x.axis.size, y.axis.size, - legend.size, strip_background = element_rect(), - strip_text_x = element_text(), - legend_position = "bottom", legend.title = element_blank()) + legend.size, + strip_background = element_rect(), + strip_text_x = element_text(), + legend_position = "bottom", legend.title = element_blank() + ) plot } @@ -352,70 +401,83 @@ get_missing_in_condition <- function(processedData) { #' @param log_base 2 or 10 #' @inheritParams groupComparisonPlots #' @keywords internal -.makeComparison = function( +.makeComparison <- function( input, log_base, dot.size, x.axis.size, y.axis.size, - text.angle, hjust, vjust, y.limdown, y.limup -) { - logFC = ciw = NULL + text.angle, hjust, vjust, y.limdown, y.limup) { + logFC <- ciw <- NULL - protein = unique(input$Protein) - plot = ggplot(input, aes_string(x = 'Label', y = 'logFC')) + + protein <- unique(input$Protein) + plot <- ggplot(input, aes_string(x = "Label", y = "logFC")) + geom_errorbar(aes(ymax = logFC + ciw, ymin = logFC - ciw), - data = input, - width = 0.1, - colour = "red") + - geom_point(size = dot.size, - colour = "darkred") + - scale_x_discrete('Comparison') + - geom_hline(yintercept = 0, - linetype = "twodash", - colour = "darkgrey", - size = 0.6) + + data = input, + width = 0.1, + colour = "red" + ) + + geom_point( + size = dot.size, + colour = "darkred" + ) + + scale_x_discrete("Comparison") + + geom_hline( + yintercept = 0, + linetype = "twodash", + colour = "darkgrey", + size = 0.6 + ) + labs(title = protein) + theme_msstats("COMPARISONPLOT", x.axis.size, y.axis.size, - text_angle = text.angle, text_hjust = hjust, - text_vjust = vjust) - plot = plot + + text_angle = text.angle, text_hjust = hjust, + text_vjust = vjust + ) + plot <- plot + scale_y_continuous(paste0("Log", log_base, "-Fold Change"), - limits = c(y.limdown, y.limup)) + limits = c(y.limdown, y.limup) + ) plot } -groupComparisonPlots = function( +groupComparisonPlots <- function( data, type, sig = 0.05, FCcutoff = FALSE, logBase.pvalue = 10, ylimUp = FALSE, ylimDown = FALSE, xlimUp = FALSE, x.axis.size = 10, y.axis.size = 10, dot.size = 3, text.size = 4, text.angle = 0, legend.size = 13, ProteinName = TRUE, colorkey = TRUE, numProtein = 100, clustering = "both", width = 10, height = 10, which.Comparison = "all", which.Protein = "all", - address = "" -) { - Label = Protein = NULL - - type = toupper(type) - input = data.table::as.data.table(data) - all_labels = as.character(unique(data$Label)) - log_base_FC = ifelse(is.element("log2FC", colnames(data)), 2, 10) - - chosen_labels = .checkGCPlotsInput(type, logBase.pvalue, which.Comparison, - all_labels) - input = input[Label %in% chosen_labels] + address = "") { + Label <- Protein <- NULL + + type <- toupper(type) + input <- data.table::as.data.table(data) + all_labels <- as.character(unique(data$Label)) + log_base_FC <- ifelse(is.element("log2FC", colnames(data)), 2, 10) + + chosen_labels <- .checkGCPlotsInput( + type, logBase.pvalue, which.Comparison, + all_labels + ) + input <- input[Label %in% chosen_labels] input[, Protein := factor(Protein)] input[, Label := factor(Label)] if (type == "HEATMAP") { - .plotHeatmap(input, logBase.pvalue, ylimUp, FCcutoff, sig, clustering, - numProtein, colorkey, width, height, log_base_FC, - x.axis.size, y.axis.size, address) + .plotHeatmap( + input, logBase.pvalue, ylimUp, FCcutoff, sig, clustering, + numProtein, colorkey, width, height, log_base_FC, + x.axis.size, y.axis.size, address + ) } if (type == "VOLCANOPLOT") { - .plotVolcano(input, which.Comparison, address, width, height, logBase.pvalue, - ylimUp, ylimDown, FCcutoff, sig, xlimUp, ProteinName, dot.size, - text.size, legend.size, x.axis.size, y.axis.size, log_base_FC) + .plotVolcano( + input, which.Comparison, address, width, height, logBase.pvalue, + ylimUp, ylimDown, FCcutoff, sig, xlimUp, ProteinName, dot.size, + text.size, legend.size, x.axis.size, y.axis.size, log_base_FC + ) } if (type == "COMPARISONPLOT") { - .plotComparison(input, which.Protein, address, width, height, sig, ylimUp, - ylimDown, text.angle, dot.size, x.axis.size, y.axis.size, - log_base_FC) + .plotComparison( + input, which.Protein, address, width, height, sig, ylimUp, + ylimDown, text.angle, dot.size, x.axis.size, y.axis.size, + log_base_FC + ) } } @@ -426,11 +488,10 @@ groupComparisonPlots = function( #' @param log_base_pval log base for p-values #' @param log_base_FC log base for log-fold changes - 2 or 10 #' @keywords internal -.plotHeatmap = function( +.plotHeatmap <- function( input, log_base_pval, ylimUp, FCcutoff, sig, clustering, numProtein, colorkey, - width, height, log_base_FC, x.axis.size, y.axis.size, address -) { - adj.pvalue = heat_val = NULL + width, height, log_base_FC, x.axis.size, y.axis.size, address) { + adj.pvalue <- heat_val <- NULL if (length(unique(input$Protein)) <= 1) { stop("At least two proteins are needed for heatmaps.") @@ -440,47 +501,51 @@ groupComparisonPlots = function( } if (is.numeric(ylimUp)) { - y.limUp = ylimUp + y.limUp <- ylimUp } else { - y.limUp = ifelse(log_base_pval == 2, 30, 10) - input[adj.pvalue < log_base_pval ^ (-y.limUp), adj.pvalue := log_base_pval ^ (-y.limUp)] + y.limUp <- ifelse(log_base_pval == 2, 30, 10) + input[adj.pvalue < log_base_pval^(-y.limUp), adj.pvalue := log_base_pval^(-y.limUp)] } if (is.numeric(FCcutoff)) { - input$adj.pvalue = ifelse(input[, 3] < log(FCcutoff, log_base_FC) & input[, 3] > -log(FCcutoff, log_base_FC), - 1, input$adj.pvalue) + input$adj.pvalue <- ifelse(input[, 3] < log(FCcutoff, log_base_FC) & input[, 3] > -log(FCcutoff, log_base_FC), + 1, input$adj.pvalue + ) } input[, heat_val := -log(adj.pvalue, log_base_pval) * sign(input[, 3])] - wide = data.table::dcast(input, Protein ~ Label, - value.var = "heat_val") - proteins = wide$Protein - wide = as.matrix(wide[, -1]) - rownames(wide) = proteins - wide = wide[rowSums(!is.na(wide)) != 0, colSums(!is.na(wide)) != 0] - wide = .getOrderedMatrix(wide, clustering) - - blue.red.18 = maPalette(low = "blue", high = "red", mid = "black", k = 12) - my.colors = blue.red.18 - my.colors = c(my.colors, "grey") # for NA - up = 10 - temp = 10 ^ (-sort(ceiling(seq(2, up, length = 10)[c(1, 2, 3, 5, 10)]), decreasing = TRUE)) - breaks = c(temp, sig) - neg.breaks = log(breaks, log_base_pval) - my.breaks = c(neg.breaks, 0, -neg.breaks[6:1], 101) - blocks = c(-breaks, 1, breaks[6:1]) - x.at = seq(-0.05, 1.05, length.out = 13) - namepro = rownames(wide) - totalpro = length(namepro) - numheatmap = totalpro %/% numProtein + 1 + wide <- data.table::dcast(input, Protein ~ Label, + value.var = "heat_val" + ) + proteins <- wide$Protein + wide <- as.matrix(wide[, -1]) + rownames(wide) <- proteins + wide <- wide[rowSums(!is.na(wide)) != 0, colSums(!is.na(wide)) != 0] + wide <- .getOrderedMatrix(wide, clustering) + + blue.red.18 <- maPalette(low = "blue", high = "red", mid = "black", k = 12) + my.colors <- blue.red.18 + my.colors <- c(my.colors, "grey") # for NA + up <- 10 + temp <- 10^(-sort(ceiling(seq(2, up, length = 10)[c(1, 2, 3, 5, 10)]), decreasing = TRUE)) + breaks <- c(temp, sig) + neg.breaks <- log(breaks, log_base_pval) + my.breaks <- c(neg.breaks, 0, -neg.breaks[6:1], 101) + blocks <- c(-breaks, 1, breaks[6:1]) + x.at <- seq(-0.05, 1.05, length.out = 13) + namepro <- rownames(wide) + totalpro <- length(namepro) + numheatmap <- totalpro %/% numProtein + 1 if (colorkey) { par(mar = c(3, 3, 3, 3), mfrow = c(3, 1), oma = c(3, 0, 3, 0)) plot.new() - image(z = matrix(seq(seq_len(length(my.colors) - 1)), ncol = 1), - col = my.colors[-length(my.colors)], - xaxt = "n", - yaxt = "n") - mtext("Color Key", side = 3,line = 1, cex = 3) + image( + z = matrix(seq(seq_len(length(my.colors) - 1)), ncol = 1), + col = my.colors[-length(my.colors)], + xaxt = "n", + yaxt = "n" + ) + mtext("Color Key", side = 3, line = 1, cex = 3) mtext("(sign) Adjusted p-value", side = 1, line = 3, at = 0.5, cex = 1.7) mtext(blocks, side = 1, line = 1, at = x.at, cex = 1) } @@ -488,11 +553,11 @@ groupComparisonPlots = function( savePlot(address, "Heatmap", width, height) for (j in seq_len(numheatmap)) { if (j != numheatmap) { - partial_wide = wide[((j - 1) * numProtein + 1):(j * numProtein), ] + partial_wide <- wide[((j - 1) * numProtein + 1):(j * numProtein), ] } else { - partial_wide = wide[((j - 1) * numProtein + 1):nrow(wide), ] + partial_wide <- wide[((j - 1) * numProtein + 1):nrow(wide), ] } - heatmap = .makeHeatmap(partial_wide, my.colors, my.breaks, x.axis.size, y.axis.size) + heatmap <- .makeHeatmap(partial_wide, my.colors, my.breaks, x.axis.size, y.axis.size) } if (address != FALSE) { dev.off() @@ -510,18 +575,18 @@ groupComparisonPlots = function( #' #' @export #' -savePlot = function(name_base, file_name, width, height) { +savePlot <- function(name_base, file_name, width, height) { if (name_base != FALSE) { - all_files = list.files(".") - if(file_name == 'ProfilePlot'){ - num_same_name = sum(grepl(paste0("^", name_base, file_name, "_[0-9]?"), all_files)) + all_files <- list.files(".") + if (file_name == "ProfilePlot") { + num_same_name <- sum(grepl(paste0("^", name_base, file_name, "_[0-9]?"), all_files)) } else { - num_same_name = sum(grepl(paste0("^", name_base, file_name, "[0-9]?"), all_files)) + num_same_name <- sum(grepl(paste0("^", name_base, file_name, "[0-9]?"), all_files)) } if (num_same_name > 0) { - file_name = paste(file_name, num_same_name + 1, sep = "_") + file_name <- paste(file_name, num_same_name + 1, sep = "_") } - file_path = paste0(name_base, file_name, ".pdf") + file_path <- paste0(name_base, file_name, ".pdf") pdf(file_path, width = width, height = height) } NULL @@ -545,16 +610,15 @@ savePlot = function(name_base, file_name, width, height) { #' @import ggplot2 #' @export #' -theme_msstats = function( +theme_msstats <- function( type, x.axis.size = 10, y.axis.size = 10, legend_size = 13, strip_background = element_rect(fill = "gray95"), strip_text_x = element_text(colour = c("black"), size = 14), legend_position = "top", legend_box = "vertical", text_angle = 0, text_hjust = NULL, text_vjust = NULL, - ... -) { + ...) { if (type %in% c("CONDITIONPLOT", "COMPARISONPLOT")) { ggplot2::theme( - panel.background = element_rect(fill = 'white', colour = "black"), + panel.background = element_rect(fill = "white", colour = "black"), axis.title.x = element_text(size = x.axis.size + 5, vjust = -0.4), axis.title.y = element_text(size = y.axis.size + 5, vjust = 0.3), axis.ticks = element_line(colour = "black"), @@ -562,15 +626,17 @@ theme_msstats = function( panel.grid.major.y = element_line(colour = "grey95"), panel.grid.minor.y = element_blank(), axis.text.y = element_text(size = y.axis.size, colour = "black"), - axis.text.x = element_text(size = x.axis.size, colour = "black", - angle = text_angle, hjust = text_hjust, - vjust = text_vjust), + axis.text.x = element_text( + size = x.axis.size, colour = "black", + angle = text_angle, hjust = text_hjust, + vjust = text_vjust + ), ... ) } else { ggplot2::theme( - panel.background = element_rect(fill = 'white', colour = "black"), - legend.key = element_rect(fill = 'white', colour = 'white'), + panel.background = element_rect(fill = "white", colour = "black"), + legend.key = element_rect(fill = "white", colour = "white"), panel.grid.minor = element_blank(), strip.background = strip_background, axis.text.x = element_text(size = x.axis.size, colour = "black"), @@ -596,20 +662,24 @@ theme_msstats = function( #' @return character #' #' @export -getSelectedProteins = function(chosen_proteins, all_proteins) { +getSelectedProteins <- function(chosen_proteins, all_proteins) { if (is.character(chosen_proteins)) { - selected_proteins = chosen_proteins - missing_proteins = setdiff(selected_proteins, all_proteins) + selected_proteins <- chosen_proteins + missing_proteins <- setdiff(selected_proteins, all_proteins) if (length(missing_proteins) > 0) { stop(paste("Please check protein name. Dataset does not have this protein. -", - toString(missing_proteins), sep = " ")) + toString(missing_proteins), + sep = " " + )) } } if (is.numeric(chosen_proteins)) { selected_proteins <- all_proteins[chosen_proteins] if (length(all_proteins) < max(chosen_proteins)) { - stop(paste("Please check your selection of proteins. There are ", - length(all_proteins)," proteins in this dataset.")) + stop(paste( + "Please check your selection of proteins. There are ", + length(all_proteins), " proteins in this dataset." + )) } } selected_proteins @@ -618,74 +688,80 @@ getSelectedProteins = function(chosen_proteins, all_proteins) { #' Preprocess data for volcano plots and create them #' @inheritParams groupComparisonPlots #' @keywords internal -.plotVolcano = function( +.plotVolcano <- function( input, which.Comparison, address, width, height, log_base_pval, ylimUp, ylimDown, FCcutoff, sig, xlimUp, ProteinName, dot.size, - text.size, legend.size, x.axis.size, y.axis.size, log_base_FC -) { - adj.pvalue = colgroup = logFC = Protein = issue = Label = newlogFC = NULL - - log_adjp = paste0("log", log_base_pval, "adjp") - all_labels = unique(input$Label) - input = input[!is.na(adj.pvalue), ] - colname_log_fc = intersect(colnames(input), c("log2FC", "log10FC")) + text.size, legend.size, x.axis.size, y.axis.size, log_base_FC) { + adj.pvalue <- colgroup <- logFC <- Protein <- issue <- Label <- newlogFC <- NULL + + log_adjp <- paste0("log", log_base_pval, "adjp") + all_labels <- unique(input$Label) + input <- input[!is.na(adj.pvalue), ] + colname_log_fc <- intersect(colnames(input), c("log2FC", "log10FC")) data.table::setnames(input, colname_log_fc, c("logFC")) if (address == FALSE) { - if (which.Comparison == 'all') { + if (which.Comparison == "all") { if (length(unique(input$Label)) > 1) { - stop('** Cannnot generate all volcano plots in a screen. Please set one comparison at a time.') + stop("** Cannnot generate all volcano plots in a screen. Please set one comparison at a time.") } } else if (length(which.Comparison) > 1) { - stop( '** Cannnot generate multiple volcano plots in a screen. Please set one comparison at a time.' ) + stop("** Cannnot generate multiple volcano plots in a screen. Please set one comparison at a time.") } } if (is.numeric(ylimUp)) { - y.limUp = ylimUp + y.limUp <- ylimUp } else { - y.limUp = ifelse(log_base_pval == 2, 30, 10) + y.limUp <- ifelse(log_base_pval == 2, 30, 10) } - input[, adj.pvalue := ifelse(adj.pvalue < log_base_pval ^ (-y.limUp), - log_base_pval ^ (-y.limUp), adj.pvalue)] + input[, adj.pvalue := ifelse(adj.pvalue < log_base_pval^(-y.limUp), + log_base_pval^(-y.limUp), adj.pvalue + )] if (!FCcutoff) { - logFC_cutoff = 0 + logFC_cutoff <- 0 } else { - logFC_cutoff = log(FCcutoff, log_base_FC) + logFC_cutoff <- log(FCcutoff, log_base_FC) } input[, colgroup := ifelse(adj.pvalue >= sig, "black", - ifelse(logFC > logFC_cutoff, - "red", "blue"))] + ifelse(logFC > logFC_cutoff, + "red", "blue" + ) + )] input[, colgroup := factor(colgroup, levels = c("black", "blue", "red"))] input[, Protein := as.character(Protein)] - input[!is.na(issue) & issue == "oneConditionMissing", - Protein := paste0("*", Protein)] + input[ + !is.na(issue) & issue == "oneConditionMissing", + Protein := paste0("*", Protein) + ] savePlot(address, "VolcanoPlot", width, height) for (i in seq_along(all_labels)) { - label_name = all_labels[i] - single_label = input[Label == label_name, ] + label_name <- all_labels[i] + single_label <- input[Label == label_name, ] - y.limup = ceiling(max(-log(single_label[!is.na(single_label$adj.pvalue), "adj.pvalue"], log_base_pval))) + y.limup <- ceiling(max(-log(single_label[!is.na(single_label$adj.pvalue), "adj.pvalue"], log_base_pval))) if (y.limup < (-log(sig, log_base_pval))) { - y.limup = (-log(sig, log_base_pval) + 1) ## for too small y.lim + y.limup <- (-log(sig, log_base_pval) + 1) ## for too small y.lim } - y.limdown = ifelse(is.numeric(ylimDown), ylimDown, 0) - x_ceiling = ceiling(max(abs(single_label[!is.na(single_label$logFC) & is.finite(single_label$logFC), logFC]))) - x.lim = ifelse(is.numeric(xlimUp), xlimUp, ifelse((x_ceiling < 3), 3, x_ceiling)) + y.limdown <- ifelse(is.numeric(ylimDown), ylimDown, 0) + x_ceiling <- ceiling(max(abs(single_label[!is.na(single_label$logFC) & is.finite(single_label$logFC), logFC]))) + x.lim <- ifelse(is.numeric(xlimUp), xlimUp, ifelse((x_ceiling < 3), 3, x_ceiling)) - single_label[[log_adjp]] = -log(single_label$adj.pvalue, log_base_pval) - single_label$newlogFC = single_label$logFC + single_label[[log_adjp]] <- -log(single_label$adj.pvalue, log_base_pval) + single_label$newlogFC <- single_label$logFC single_label[!is.na(issue) & - issue == "oneConditionMissing" & - logFC == Inf, newlogFC := (x.lim - 0.2)] + issue == "oneConditionMissing" & + logFC == Inf, newlogFC := (x.lim - 0.2)] single_label[!is.na(issue) & - issue == "oneConditionMissing" & - logFC == (-Inf), newlogFC := (x.lim - 0.2) * (-1)] - plot = .makeVolcano(single_label, label_name, log_base_FC, log_base_pval, x.lim, ProteinName, dot.size, - y.limdown, y.limup, text.size, FCcutoff, sig, x.axis.size, y.axis.size, - legend.size, log_adjp) + issue == "oneConditionMissing" & + logFC == (-Inf), newlogFC := (x.lim - 0.2) * (-1)] + plot <- .makeVolcano( + single_label, label_name, log_base_FC, log_base_pval, x.lim, ProteinName, dot.size, + y.limdown, y.limup, text.size, FCcutoff, sig, x.axis.size, y.axis.size, + legend.size, log_adjp + ) print(plot) } if (address != FALSE) { @@ -699,14 +775,13 @@ getSelectedProteins = function(chosen_proteins, all_proteins) { #' @param input data.table #' @param log_base_FC log base for log-fold changes - 2 or 10 #' @keywords internal -.plotComparison = function( +.plotComparison <- function( input, proteins, address, width, height, sig, ylimUp, ylimDown, - text.angle, dot.size, x.axis.size, y.axis.size, log_base_FC -) { - adj.pvalue = Protein = ciw = NULL + text.angle, dot.size, x.axis.size, y.axis.size, log_base_FC) { + adj.pvalue <- Protein <- ciw <- NULL - input = input[!is.na(adj.pvalue), ] - all_proteins = unique(input$Protein) + input <- input[!is.na(adj.pvalue), ] + all_proteins <- unique(input$Protein) if (address == FALSE) { if (proteins == "all" | length(proteins) > 1) { @@ -714,26 +789,28 @@ getSelectedProteins = function(chosen_proteins, all_proteins) { } } if (proteins != "all") { - selected_proteins = getSelectedProteins(proteins, all_proteins) - input = input[Protein %in% selected_proteins, ] + selected_proteins <- getSelectedProteins(proteins, all_proteins) + input <- input[Protein %in% selected_proteins, ] } - all_proteins = unique(input$Protein) - input$Protein = factor(input$Protein) + all_proteins <- unique(input$Protein) + input$Protein <- factor(input$Protein) savePlot(address, "ComparisonPlot", width, height) - log_fc_column = intersect(colnames(input), c("log2FC", "log10FC")) + log_fc_column <- intersect(colnames(input), c("log2FC", "log10FC")) for (i in seq_along(all_proteins)) { - single_protein = input[Protein == all_proteins[i], ] + single_protein <- input[Protein == all_proteins[i], ] single_protein[, ciw := qt(1 - sig / (2 * nrow(single_protein)), single_protein$DF) * single_protein$SE] data.table::setnames(single_protein, log_fc_column, "logFC") - y.limup = ifelse(is.numeric(ylimUp), ylimUp, ceiling(max(single_protein$logFC + single_protein$ciw))) - y.limdown = ifelse(is.numeric(ylimDown), ylimDown, floor(min(single_protein$logFC - single_protein$ciw))) - hjust = ifelse(text.angle != 0, 1, 0.5) - vjust = ifelse(text.angle != 0, 1, 0.5) - - plot = .makeComparison(single_protein, log_base_FC, dot.size, x.axis.size, - y.axis.size, text.angle, hjust, vjust, y.limdown, - y.limup) + y.limup <- ifelse(is.numeric(ylimUp), ylimUp, ceiling(max(single_protein$logFC + single_protein$ciw))) + y.limdown <- ifelse(is.numeric(ylimDown), ylimDown, floor(min(single_protein$logFC - single_protein$ciw))) + hjust <- ifelse(text.angle != 0, 1, 0.5) + vjust <- ifelse(text.angle != 0, 1, 0.5) + + plot <- .makeComparison( + single_protein, log_base_FC, dot.size, x.axis.size, + y.axis.size, text.angle, hjust, vjust, y.limdown, + y.limup + ) print(plot) } if (address != FALSE) { @@ -743,78 +820,78 @@ getSelectedProteins = function(chosen_proteins, all_proteins) { ### End Function Sections -char_to_boolean <- c("true"=TRUE, "false"=FALSE) +char_to_boolean <- c("true" = TRUE, "false" = FALSE) usage <- "Rscript msstats_tmt.R input.csv [list of contrasts or 'pairwise'] [default control condition or '']... [normalization based reference channel]" args <- initialize_msstats(usage = usage) rmProtein_with1Feature <- args[4] -if(typeof(rmProtein_with1Feature) == 'character'){ +if (typeof(rmProtein_with1Feature) == "character") { rmProtein_with1Feature <- char_to_boolean[rmProtein_with1Feature] } -if (length(args)<5) { +if (length(args) < 5) { # use unique peptide args[5] <- TRUE } useUniquePeptide <- args[5] -if(typeof(useUniquePeptide) == 'character'){ +if (typeof(useUniquePeptide) == "character") { useUniquePeptide <- char_to_boolean[useUniquePeptide] } -if (length(args)<6) { +if (length(args) < 6) { # remove the features that have 1 or 2 measurements within each Run. args[6] <- TRUE } rmPSM_withfewMea_withinRun <- args[6] -if(typeof(rmPSM_withfewMea_withinRun) == 'character'){ +if (typeof(rmPSM_withfewMea_withinRun) == "character") { rmPSM_withfewMea_withinRun <- char_to_boolean[rmPSM_withfewMea_withinRun] } -if (length(args)<7) { +if (length(args) < 7) { # sum or max - when there are multiple measurements for certain feature in certain Run. - args[7] <- 'sum' + args[7] <- "sum" } -if (length(args)<8) { +if (length(args) < 8) { # summarization methods to protein-level can be performed: "msstats(default)" args[8] <- "msstats" } -if (length(args)<9) { +if (length(args) < 9) { # Global median normalization on peptide level data args[9] <- TRUE } global_norm <- args[9] -if(typeof(global_norm) == 'character'){ +if (typeof(global_norm) == "character") { global_norm <- char_to_boolean[global_norm] } -if (length(args)<10) { +if (length(args) < 10) { # Remove norm channel args[10] <- TRUE } remove_norm_channel <- args[10] -if(typeof(remove_norm_channel) == 'character'){ +if (typeof(remove_norm_channel) == "character") { remove_norm_channel <- char_to_boolean[remove_norm_channel] } -if (length(args)<11) { +if (length(args) < 11) { # default Reference channel based normalization between MS runs on protein level data. # Reference Channel annotated by 'Norm' in Condition. args[11] <- TRUE } reference_norm <- args[11] -if(typeof(reference_norm) == 'character'){ +if (typeof(reference_norm) == "character") { reference_norm <- char_to_boolean[reference_norm] } -if (length(args)<12) { +if (length(args) < 12) { # outputPrefix - args[12] <- './msstats' + args[12] <- "./msstats" } -if (length(args)<13) { +if (length(args) < 13) { # adjusted p-value threshold args[13] <- 0.05 } @@ -824,7 +901,7 @@ if (length(args) < 14) { args[14] <- FALSE } plot_profile_all <- args[14] -if(typeof(plot_profile_all) == 'character'){ +if (typeof(plot_profile_all) == "character") { plot_profile_all <- char_to_boolean[plot_profile_all] } @@ -834,18 +911,20 @@ control_str <- args[3] # read dataframe into MSstatsTMT data <- read.csv(csv_input) -quant <- OpenMStoMSstatsTMTFormat(data, useUniquePeptide=useUniquePeptide, rmPSM_withfewMea_withinRun=rmPSM_withfewMea_withinRun, - rmProtein_with1Feature=rmProtein_with1Feature +quant <- OpenMStoMSstatsTMTFormat(data, + useUniquePeptide = useUniquePeptide, rmPSM_withfewMea_withinRun = rmPSM_withfewMea_withinRun, + rmProtein_with1Feature = rmProtein_with1Feature ) # protein summarization -processed.quant <- proteinSummarization(quant, method=args[8],remove_empty_channel=TRUE, global_norm=global_norm, - reference_norm=reference_norm, remove_norm_channel=remove_norm_channel +processed.quant <- proteinSummarization(quant, + method = args[8], remove_empty_channel = TRUE, global_norm = global_norm, + reference_norm = reference_norm, remove_norm_channel = remove_norm_channel ) if (plot_profile_all) { - dataProcessPlotsTMT(processed.quant, "ProfilePlot", width=12, height=12, which.Protein="all") - dataProcessPlotsTMT(processed.quant, "QCPlot", width=12, height=12, which.Protein="allonly") + dataProcessPlotsTMT(processed.quant, "ProfilePlot", width = 12, height = 12, which.Protein = "all") + dataProcessPlotsTMT(processed.quant, "QCPlot", width = 12, height = 12, which.Protein = "allonly") } lvls <- levels(as.factor(processed.quant$ProteinLevelData$Condition)) @@ -855,30 +934,32 @@ if (l == 1) { print("Only one condition found. No contrasts to be tested. If this is not the case, please check your experimental design.") } else { contrast_mat <- parse_contrasts(l = l, contrast_str = contrast_str, lvls = lvls) - print ("Contrasts to be tested:") - print (contrast_mat) - #TODO allow for user specified contrasts - test.MSstatsTMT <- groupComparisonTMT(contrast.matrix=contrast_mat, data=processed.quant) + print("Contrasts to be tested:") + print(contrast_mat) + # TODO allow for user specified contrasts + test.MSstatsTMT <- groupComparisonTMT(contrast.matrix = contrast_mat, data = processed.quant) - #TODO allow manual input (e.g. proteins of interest) - write.table(test.MSstatsTMT$ComparisonResult, file=paste0(args[12],"_comparisons.csv"), quote=FALSE, sep='\t', row.names = FALSE) + # TODO allow manual input (e.g. proteins of interest) + write.table(test.MSstatsTMT$ComparisonResult, file = paste0(args[12], "_comparisons.csv"), quote = FALSE, sep = "\t", row.names = FALSE) valid_comp_data <- test.MSstatsTMT$ComparisonResult[!is.na(test.MSstatsTMT$ComparisonResult$pvalue), ] - if (nrow(valid_comp_data[!duplicated(valid_comp_data$Protein),]) < 2) { + if (nrow(valid_comp_data[!duplicated(valid_comp_data$Protein), ]) < 2) { warning("Warning: Not enough proteins with valid p-values for comparison. Skipping groupComparisonPlots step!") } else { + groupComparisonPlots(data = test.MSstatsTMT$ComparisonResult, type = "ComparisonPlot", sig = as.numeric(args[13]), width = 12, height = 12, dot.size = 2) - groupComparisonPlots(data=test.MSstatsTMT$ComparisonResult, type="ComparisonPlot", sig=as.numeric(args[13]), width=12, height=12, dot.size = 2) - - groupComparisonPlots(data=valid_comp_data, type="VolcanoPlot", sig=as.numeric(args[13]), - width=12, height=12, dot.size = 2) + groupComparisonPlots( + data = valid_comp_data, type = "VolcanoPlot", sig = as.numeric(args[13]), + width = 12, height = 12, dot.size = 2 + ) # Otherwise it fails since the behavior is undefined if (nrow(contrast_mat) > 1) { - groupComparisonPlots(data=test.MSstatsTMT$ComparisonResult, type="Heatmap", sig=as.numeric(args[13]), - width=12, height=12, dot.size = 2) + groupComparisonPlots( + data = test.MSstatsTMT$ComparisonResult, type = "Heatmap", sig = as.numeric(args[13]), + width = 12, height = 12, dot.size = 2 + ) } } - } diff --git a/conf/dev.config b/conf/dev.config index 72b7aa829..cafcbe19a 100644 --- a/conf/dev.config +++ b/conf/dev.config @@ -21,4 +21,9 @@ process { // Conda is no longer supported container = {"${ ( workflow.containerEngine == 'singularity' || workflow.containerEngine == 'apptainer' ) && !task.ext.singularity_pull_docker_container ? 'oras://ghcr.io/openms/openms-tools-thirdparty-sif:latest' : 'ghcr.io/openms/openms-tools-thirdparty:latest' }"} } + + // ONSITE uses its own container (not OpenMS thirdparty) + withName: '.*:PHOSPHO_SCORING:ONSITE' { + container = {"${ ( workflow.containerEngine == 'singularity' || workflow.containerEngine == 'apptainer' ) && !task.ext.singularity_pull_docker_container ? 'https://depot.galaxyproject.org/singularity/pyonsite:0.0.2--pyhdfd78af_0' : 'quay.io/biocontainers/pyonsite:0.0.2--pyhdfd78af_0' }"} + } } diff --git a/conf/modules/modules.config b/conf/modules/modules.config index 79e4196c0..979da993a 100644 --- a/conf/modules/modules.config +++ b/conf/modules/modules.config @@ -109,7 +109,7 @@ if (params.search_engines.split(",").size() > 1) { if (params.enable_mod_localization) { process { - // ID_SCORE_SWITCHER_LUCIPHOR + // ID_SCORE_SWITCHER_ONSITE withName: '.*:ID:PHOSPHO_SCORING:ID_SCORE_SWITCHER' { ext.args = [ "-new_score_orientation lower_better", @@ -119,9 +119,9 @@ if (params.enable_mod_localization) { ].join(' ').trim() } - // LuciphorAdapter - withName: '.*:ID:PHOSPHO_SCORING:LUCIPHOR' { - ext.args = "-debug $params.luciphor_debug" + // onsite + withName: '.*:ID:PHOSPHO_SCORING:ONSITE' { + ext.args = "-debug $params.onsite_debug" } } } @@ -216,9 +216,9 @@ process { // MS2RESCORE withName: 'MSRESCORE_FEATURES' { ext.args = [ - "--ms2_model ${params.ms2_model}", - "--calibration_set_size ${params.calibration_set_size}", - params.feature_generators.trim() ? "--feature_generators ${params.feature_generators}" : '' + "--ms2_model ${params.ms2features_model}", + "--calibration_set_size ${params.ms2features_calibration}", + params.ms2features_generators.trim() ? "--feature_generators ${params.ms2features_generators}" : '' ].join(' ').trim() } diff --git a/conf/modules/verbose_modules.config b/conf/modules/verbose_modules.config index 5df719dd3..e9a5503e9 100644 --- a/conf/modules/verbose_modules.config +++ b/conf/modules/verbose_modules.config @@ -157,9 +157,9 @@ process { } // Set the default publish for PTM processing steps - withName: '.*:LUCIPHOR' { + withName: '.*:ONSITE' { publishDir = [ - path: { "${params.outdir}/ptm_localization/luciphor" }, + path: { "${params.outdir}/ptm_localization/onsite" }, mode: params.publish_dir_mode, saveAs: { filename -> filename.equals('versions.yml') ? null : filename } ] diff --git a/conf/tests/test_dda_id.config b/conf/tests/test_dda_id_alphapeptdeep.config similarity index 72% rename from conf/tests/test_dda_id.config rename to conf/tests/test_dda_id_alphapeptdeep.config index 9217e9ebe..df9df1c76 100644 --- a/conf/tests/test_dda_id.config +++ b/conf/tests/test_dda_id_alphapeptdeep.config @@ -19,10 +19,10 @@ process { } params { - config_profile_name = 'Real full-size test profile for DDA ID' - config_profile_description = 'Real full-size test dataset to check pipeline function of the DDA identification branch of the pipeline' + config_profile_name = 'Real full-size test profile for DDA ID (alphapeptdeep)' + config_profile_description = 'Real full-size test dataset to check pipeline function of the DDA identification branch using alphapeptdeep' - outdir = "./results_lfq_dda_id" + outdir = "./results_lfq_dda_id_alphapeptdeep" // Input data input = 'https://raw.githubusercontent.com/bigbio/quantms-test-datasets/quantms/testdata/tmt_ci/PXD000001.sdrf.tsv' @@ -35,10 +35,13 @@ params { pmultiqc_idxml_skip = false id_only = true enable_pmultiqc = true - ms2rescore = true - add_snr_feature_percolator = true - ms2pip_model = 'TMT' - force_model = true - ms2rescore_fragment_tolerance = 0.3 + ms2features_enable = true + ms2features_snr = true + ms2features_generators = 'deeplc,alphapeptdeep' // Using alphapeptdeep for this test + ms2features_model = 'generic' // Generic model for alphapeptdeep + ms2features_force = true + ms2features_best = false + ms2features_tolerance = 0.3 + ms2features_debug = true mzml_features = true } diff --git a/conf/tests/test_dda_id_fine_tuning.config b/conf/tests/test_dda_id_fine_tuning.config new file mode 100644 index 000000000..f266d8558 --- /dev/null +++ b/conf/tests/test_dda_id_fine_tuning.config @@ -0,0 +1,46 @@ +/* +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + Nextflow config file for running real full-size tests +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + Defines input files and everything required to run a real and full-size test. + + Use as follows: + nextflow run bigbio/quantms -profile test_dda_id, [--outdir ] + +------------------------------------------------------------------------------------------------ +*/ + +process { + resourceLimits = [ + cpus: 4, + memory: '6.GB', + time: '48.h' + ] +} + +params { + config_profile_name = 'Real full-size test profile for DDA ID fine tuning' + config_profile_description = 'Real full-size test dataset to check pipeline function of the DDA identification branch using alphapeptdeep fine tuning' + + outdir = "./results_lfq_dda_id_fine_tuning" + + // Input data + input = 'https://raw.githubusercontent.com/bigbio/quantms-test-datasets/quantms/testdata/lfq_ci_phospho/test_phospho.sdrf' + database = 'https://raw.githubusercontent.com/bigbio/quantms-test-datasets/quantms/testdata/lfq_ci_phospho/pools_crap_targetdecoy.fasta' + search_engines = 'comet,msgf' + protein_level_fdr_cutoff = 0.20 + psm_level_fdr_cutoff = 0.50 + pmultiqc_idxml_skip = false + id_only = true + enable_pmultiqc = true + ms2features_enable = true + ms2features_snr = true + ms2features_generators = 'deeplc,alphapeptdeep' // Using alphapeptdeep for this test + ms2features_model = 'generic' // Generic model for alphapeptdeep + ms2features_force = true + ms2features_best = false + ms2features_tolerance = 0.3 + ms2features_fine_tuning = true + ms2features_debug = true + mzml_features = true +} diff --git a/conf/tests/test_dda_id_ms2pip.config b/conf/tests/test_dda_id_ms2pip.config new file mode 100644 index 000000000..ee469f426 --- /dev/null +++ b/conf/tests/test_dda_id_ms2pip.config @@ -0,0 +1,47 @@ +/* +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + Nextflow config file for running real full-size tests +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + Defines input files and everything required to run a real and full-size test. + + Use as follows: + nextflow run bigbio/quantms -profile test_dda_id_ms2pip, [--outdir ] + +------------------------------------------------------------------------------------------------ +*/ + +process { + resourceLimits = [ + cpus: 4, + memory: '6.GB', + time: '48.h' + ] +} + +params { + config_profile_name = 'Real full-size test profile for DDA ID (ms2pip)' + config_profile_description = 'Real full-size test dataset to check pipeline function of the DDA identification branch using ms2pip' + + outdir = "./results_lfq_dda_id_ms2pip" + + // Input data + input = 'https://raw.githubusercontent.com/bigbio/quantms-test-datasets/quantms/testdata/tmt_ci/PXD000001.sdrf.tsv' + database = 'https://raw.githubusercontent.com/bigbio/quantms-test-datasets/quantms/testdata/tmt_ci/erwinia_carotovora.fasta' + search_engines = "msgf" + add_decoys = true + decoy_string = "rev" + protein_level_fdr_cutoff = 0.01 + psm_level_fdr_cutoff = 1.0 + pmultiqc_idxml_skip = false + id_only = true + enable_pmultiqc = false + ms2features_enable = true + ms2features_snr = true + ms2features_generators = 'deeplc,ms2pip' // Using ms2pip for this test + ms2features_model = 'TMT' + ms2features_force = true + ms2features_tolerance = 0.3 + ms2features_best = false + ms2features_debug = true + mzml_features = true +} diff --git a/docs/images/full-DDA.svg b/docs/images/full-DDA.svg index d7c3d3a27..36edfc697 100644 --- a/docs/images/full-DDA.svg +++ b/docs/images/full-DDA.svg @@ -34,8 +34,8 @@ ID Comet - - + + mix @@ -54,12 +54,12 @@ merge - + - + - Percolator + Percolator @@ -68,21 +68,7 @@ ID MSGF - - - - - FDR - - - - - - Distribution-based - PEP - + - Luciphor + onsite @@ -154,12 +140,6 @@ - - - if !multi-engine - - - if localize @@ -206,10 +186,10 @@ ID Sage - - MS2Rescore - + + quantms-rescoring + diff --git a/docs/images/id-dda-pipeline.png b/docs/images/id-dda-pipeline.png index 7092020e6..fd87862a7 100644 Binary files a/docs/images/id-dda-pipeline.png and b/docs/images/id-dda-pipeline.png differ diff --git a/docs/images/id_pipeline.png b/docs/images/id_pipeline.png index 7092020e6..fd87862a7 100644 Binary files a/docs/images/id_pipeline.png and b/docs/images/id_pipeline.png differ diff --git a/docs/images/id_pipeline.svg b/docs/images/id_pipeline.svg index 9640716bd..36edfc697 100644 --- a/docs/images/id_pipeline.svg +++ b/docs/images/id_pipeline.svg @@ -1 +1,228 @@ -id_pipelineID Cometmixif multi-engineif single-enginemergePercolatorID MSGFFDRDistribution-basedPEPConsensusIDLuciphorcombined FDRQuantification +...Switch toq-value/FDRIDFilterMSstatsPMultiQCRaw fileconversion/...if !multi-engineif localizeif necessaryDecoy generationif requestedDatabaseInput+DesignSpectraorID SageMS2RescorefastamzMLrawsdrf + + + + + + + + + + + + + + ID Comet + + + + + + mix + + + + + if multi-engine + + + + + if single-engine + + + + merge + + + + + + Percolator + + + + + + ID MSGF + + + + + + + ConsensusID + + + + + + onsite + + + + + + combined FDR + + + + + + + + Quantification +... + + + + + + Switch to + q-value/FDR + + + + + + IDFilter + + + + MSstats + + + + PMultiQC + + + + + + Raw file + conversion/... + + + + + + + + + + + + if localize + + + + if necessary + + + + Decoy generation + + + + if requested + + + + Database + + + + Input + + + + + + + + Design + + + + Spectra + + + + or + + + + ID Sage + + quantms-rescoring + + + + + +fasta + +mzML + + +raw + + + +sdrf + + + + + + diff --git a/docs/images/quantms_metro.drawio.svg b/docs/images/quantms_metro.drawio.svg index ae65788c3..78266fb82 100644 --- a/docs/images/quantms_metro.drawio.svg +++ b/docs/images/quantms_metro.drawio.svg @@ -1,59 +1,240 @@ - + - + +.st0 { + fill: none; + stroke: #53a567; + stroke-width: 10; + stroke-miterlimit: 10; +} +.st1 { + fill: none; +} +.st2 { + fill: none; + stroke: #cda2be; + stroke-width: 0.5; + stroke-linecap: round; + stroke-linejoin: round; + stroke-miterlimit: 10; +} +.st3 { + fill: none; + stroke: #9673a6; + stroke-linecap: round; + stroke-linejoin: round; + stroke-miterlimit: 10; + stroke-dasharray: 3, 3; +} +.st4 { + font-family: "ArialMT"; +} +.st5 { + font-size: 12.8756px; +} +.st6 { + fill: none; + stroke: #56a8cb; + stroke-width: 10; + stroke-miterlimit: 10; +} +.st7 { + fill: none; + stroke: #ffffff; + stroke-width: 13; + stroke-miterlimit: 10; +} +.st8 { + fill: none; + stroke: #da291c; + stroke-width: 11; + stroke-miterlimit: 10; +} +.st9 { + fill: none; + stroke: #e1d5e7; + stroke-width: 0.5; + stroke-linecap: round; + stroke-linejoin: round; + stroke-miterlimit: 10; +} +.st10 { + fill: #ffffff; + stroke: #000000; +} +.st11 { + fill: #ffbe7a; + stroke: #000000; +} +.st12 { + fill: none; + stroke: #da291c; + stroke-width: 10; + stroke-miterlimit: 10; +} +.st13 { + fill: #ffbe7a; + stroke: #000000; + stroke-width: 1.5; + stroke-dasharray: 4.5, 4.5; +} +.st14 { + fill: #ffffff; +} +.st15 { + fill: none; + stroke: #000000; + stroke-linecap: round; + stroke-linejoin: round; + stroke-miterlimit: 10; + stroke-dasharray: 3, 3; +} +.st16 { + font-family: "Arial-BoldMT"; +} +.st17 { + fill: none; + stroke: #53a567; + stroke-width: 10; + stroke-linecap: round; + stroke-linejoin: round; + stroke-miterlimit: 10; +} +.st18 { + fill: none; + stroke: #da291c; + stroke-width: 11; + stroke-linecap: round; + stroke-linejoin: round; + stroke-miterlimit: 10; +} +.st19 { + fill: none; + stroke: #56a8cb; + stroke-width: 10; + stroke-linecap: round; + stroke-linejoin: round; + stroke-miterlimit: 10; +} +.st20 { + font-family: "ArialNarrow-Bold"; +} +.st21 { + letter-spacing: 1.1; +} +.st22 { + fill: #ffffff; + stroke: #000000; + stroke-dasharray: 3, 3; +} +.st23 { + fill: none; + stroke: #ffffff; + stroke-width: 12; + stroke-miterlimit: 10; +} +.st24 { + font-size: 12.1693px; +} +.st25 { + letter-spacing: 29.4; +} +.st26 { + fill: none; + stroke: #cda2be; + stroke-linecap: round; + stroke-linejoin: round; + stroke-miterlimit: 10; + stroke-dasharray: 3, 3; +} +.st27 { + font-size: 10.7297px; +} +.st28 { + clip-path: url(#SVGID_2_); +} +.st29 { + fill-rule: evenodd; + clip-rule: evenodd; + fill: #ffffff; + stroke: #2f528f; + stroke-width: 1.3333; + stroke-miterlimit: 8; +} +.st30 { + fill-rule: evenodd; + clip-rule: evenodd; + stroke: #2f528f; + stroke-width: 1.3333; + stroke-miterlimit: 8; +} +.st31 { + font-size: 8.5714px; +} +.st32 { + clip-path: url(#SVGID_4_); +} +.st33 { + clip-path: url(#SVGID_6_); +} +.st34 { + clip-path: url(#SVGID_8_); +} +.st35 { + font-size: 6.1224px; +} +.st36 { + clip-path: url(#SVGID_10_); +} +.st37 { + font-size: 6.4378px; +} +.st38 { + clip-path: url(#SVGID_12_); +} +.st39 { + font-size: 6.5691px; +} +.st40 { + letter-spacing: 33.3; +} +.st41 { + font-size: 10.0751px; +} + - - - + + - + + c1.7-50.1,0.7-97.9,0-169.5" + /> Search engines (at least one) - - - - - - - - - - - - + + + + + + + + + + + - + + M825.5,218.4c-43.9-0.4-88.9-1.2-188,0 M637.5,218.4c2.1-48.6-2.3-97.5,0-169.5 M637.5,218.4c1.3-42,0.1-81.7,0-169.5" + /> Rescoring (exactly one) - - - - - - - - - - - - - - + + + + + + + + + + + + + + - ThermoRaw - FileParser - - - - - - - + ThermoRaw + FileParser + + + + + + + Comet - - - - - + + + + + ConsensusID - - - + + + Distribution fitting - - - - - + + + + + FDR control - + Isobaric Analyzer - - - - - - - - + + + + + + + + Mod. localization - + - MS2Rescore + Percolator + FDR - - - - - - quantms-rescoring + Percolator + FDR + + + + + + - + - + c8.3-0.8,10.1-2.8,10.4-11.2 M326.4,576.8c-0.1-32.1,0-65,0-113.3 M326.4,576.8c-0.3-39.4,0.8-79.5,0-113.3" + /> + Legend - - - + + + DDA-LFQ @@ -292,42 +726,78 @@ Parallel step - - - + + + IDMapper - + Protein inference - - - + + + - bigbio/quantms v1.3 + bigbio/quantms v1.3 + Example analysis pathways mzML indexing - - + + DIA-NN insilico lib. predict - + preanalysis @@ -335,125 +805,268 @@ DIA-NN empirical lib. gen. - + Add decoys - - - - - + + + + + Proteomics LFQ - + mzTab - + Protein Quantifier - - + + Triqler - - + + MSstats - + DIA-NN summary - - + + DIA-NN individual final analysis - + MSGF+ - - - - - - - + + + + + + + - tdf2mzml + tdf2mzml - + - - Identification subworkflow + + Identification subworkflow - + - - - - - - - - -Job n -In Parallel - -Job 1 + + + + + + + + +Job n +In Parallel + +Job 1 ...... - - - - - - - - - - mzML - + + + + + + + + + + + mzML + + @@ -465,18 +1078,24 @@ - - - - - - - - - - raw - + + + + + + + + + + + raw + + @@ -488,18 +1107,24 @@ - - - - - - - - - - fasta - + + + + + + + + + + + fasta + + @@ -511,18 +1136,24 @@ - - - - - - - - - - tsv - + + + + + + + + + + + tsv + + @@ -534,18 +1165,24 @@ - - - - - - - - - - mzTab - + + + + + + + + + + + mzTab + + @@ -557,252 +1194,436 @@ - - - - - - - - - - tsv - + + + + + + + + + + + tsv + + - - - -Job 1 + + + +Job 1 ...... -Job n - In Parallel - - - - - - - - - - - - - - +Job n + In Parallel + + + + + + + + + + + + + + - + Sage - - - - - - - - - - - + + + + + + + + + + + speclib skip - + - + V-1565.6L-1708.6-1565.6z M-1677-1512.1h-31.6v-11.1h31.6V-1512.1L-1677-1512.1z" + /> - + - + V-1553.9z M-1673-1500.4h-31.6v-11.1h31.6V-1500.4L-1673-1500.4z" + /> - + - - + V-1542.2L-1700.6-1542.2z M-1669-1488.7h-31.6v-11.1h31.6V-1488.7L-1669-1488.7z" + /> + - raw + raw - + - + V-1718.2L-1708.6-1718.2z M-1677-1664.7h-31.6v-11.1h31.6V-1664.7L-1677-1664.7z" + /> - + - + V-1706.5z M-1673-1653h-31.6v-11.1h31.6V-1653L-1673-1653z" + /> - + - - + V-1694.9L-1700.6-1694.9z M-1669-1641.3h-31.6v-11.1h31.6V-1641.3L-1669-1641.3z" + /> + - mzML + mzML - + - + V-1346.6L-1705.2-1346.6z M-1673.6-1293.1h-31.6v-11.1h31.6V-1293.1L-1673.6-1293.1z" + /> - + - + C-1663.8-1314.8-1665-1316.3-1666.5-1316.3L-1666.5-1316.3z M-1701.3-1335h22.2v11.8c0,0.6,0.4,1.1,0.9,1.1h8.5v5.8h-31.6V-1335 + z M-1669.6-1281.5h-31.6v-11.1h31.6V-1281.5L-1669.6-1281.5z" + /> - + - - + V-1323.3L-1697.3-1323.3z M-1665.6-1269.7h-31.6v-11.1h31.6V-1269.7L-1665.6-1269.7z" + /> + - fasta + fasta - + - + V-1462.3L-1708.6-1462.3z M-1677-1408.8h-31.6v-11.1h31.6V-1408.8L-1677-1408.8z" + /> - + - + C-1667.2-1430.5-1668.4-1432-1669.9-1432L-1669.9-1432z M-1704.6-1450.7h22.2v11.8c0,0.6,0.4,1.1,0.9,1.1h8.5v5.8h-31.6V-1450.7 + z M-1673-1397.2h-31.6v-11.1h31.6V-1397.2L-1673-1397.2z" + /> - + - - + V-1439L-1700.6-1439z M-1669-1385.5h-31.6v-11.1h31.6V-1385.5L-1669-1385.5z" + /> + - .d + .d - - + + diff --git a/docs/images/quantms_metro.png b/docs/images/quantms_metro.png index 02c22ef64..36ac2b325 100644 Binary files a/docs/images/quantms_metro.png and b/docs/images/quantms_metro.png differ diff --git a/docs/output.md b/docs/output.md index 2410764d9..d1e31dee1 100644 --- a/docs/output.md +++ b/docs/output.md @@ -20,7 +20,7 @@ The pipeline is built using [Nextflow](https://www.nextflow.io/) and processes d 8. If multiple search engines were chosen, the results are combined with OpenMS' ConsensusID 9. If multiple search engines were chosen, a combined FDR is calculated 10. Single run PSM/Peptide-level FDR filtering -11. If localization of modifications was requested, Luciphor2 is applied. +11. If localization of modifications was requested, onsite is applied. 12. (**DDA-LFQ**) Protein inference and label-free quantification based on spectral counting or MS1 feature detection, alignment and integration with OpenMS' ProteomicsLFQ. Performs an additional experiment-wide FDR filter on protein (and if requested peptide/PSM-level). 13. (**DDA-ISO**) Extracts and normalizes isobaric labeling 14. (**DDA-ISO**) Protein inference using the OpenMS ProteinInference tool. In addition, protein FDR filtering is performed in this step for Isobaric datasets (TMT, iTRAQ). @@ -243,10 +243,29 @@ The specific files include:
Output files --`pipeline_info/` - Reports generated by Nextflow: `execution_report.html`, `execution_timeline.html`, `execution_trace.txt` and `pipeline_dag.dot`/`pipeline_dag.svg`. - Reports generated by the pipeline: `pipeline_report.html`, `pipeline_report.txt` and `software_versions.yml`. The `pipeline_report*` files will only be present if the `--email` / `--email_on_fail` parameter's are used when running the pipeline. - Reformatted samplesheet files used as input to the pipeline: `samplesheet.valid.csv`. +`pipeline_info/`: + +- Reports generated by Nextflow: `execution_report.html`, `execution_timeline.html`, `execution_trace.txt` and `pipeline_dag.dot`/`pipeline_dag.svg`. +- Reports generated by the pipeline: `pipeline_report.html`, `pipeline_report.txt` and `software_versions.yml`. The `pipeline_report*` files will only be present if the `--email` / `--email_on_fail` parameter's are used when running the pipeline. +- Reformatted samplesheet files used as input to the pipeline: `samplesheet.valid.csv`. +- `params_.json` containing the command line provided parameters. Some parameters are overwritten if an SDRF file is used.
+> Note. These parameters are overwritten if an SDRF file is used. Even if they are set +> in the parameters, the SDRF file has precedence. +> +> - labelling_type +> - dissociationmethod +> - fixedmodifications +> - variablemodifications +> - precursormasstolerance +> - precursormasstoleranceunit +> - fragmentmasstolerance +> - fragmentmasstoleranceunit +> - enzyme +> - acquisition_method + ### File types #### Spectra diff --git a/docs/usage.md b/docs/usage.md index 6c85d3f92..2afa6e8be 100644 --- a/docs/usage.md +++ b/docs/usage.md @@ -19,7 +19,23 @@ where the experimental design file has to be one of: - [Sample-to-data-relationship format](https://pubs.acs.org/doi/abs/10.1021/acs.jproteome.0c00376) (.sdrf.tsv) - [OpenMS experimental design format](https://abibuilder.cs.uni-tuebingen.de/archive/openms/Documentation/release/latest/html/classOpenMS_1_1ExperimentalDesign.html#details) (.tsv) -In the respective "comment[file uri]" or "Spectra_Filepath" columns, the raw or mzML files with the mass spectra to be staged have to be listed. URIs are possible, +### Supported file formats + +The pipeline supports the following mass spectrometry data file formats: + +- **`.raw`** - Thermo RAW files (automatically converted to mzML) +- **`.mzML`** - Open standard mzML files +- **`.d`** - Bruker timsTOF files (optionally converted to mzML when `--convert_dotd` is set) +- **`.dia`** - DIA-NN native binary format (passed through without conversion) + +Compressed variants are supported for `.raw`, `.mzML`, and `.d` formats: + +- `.gz` (gzip compressed) +- `.tar` (tar archive) +- `.tar.gz` or `.tgz` (tar gzip compressed) +- `.zip` (zip compressed) + +In the respective "comment[file uri]" or "Spectra_Filepath" columns, the mass spectra files to be processed have to be listed. URIs are possible, and the root folder as well as the file endings can be changed in the options in case of previously downloaded, moved or converted experiments. This will launch the pipeline with the `docker` configuration profile. See below for more information about profiles. @@ -65,6 +81,31 @@ When you run the above command, Nextflow automatically pulls the pipeline code f nextflow pull bigbio/quantms ``` +## Migration Guide + +### Migrating from luciphor*\* to onsite*\* parameters (Version 1.7.0) + +Starting with version 1.7.0, luciphor-specific parameters have been replaced with the unified `onsite_*` parameter naming scheme. The new onsite module supports multiple PTM localization algorithms (AScore, PhosphoRS, and LucXor). + +#### Onsite Parameters + +| Parameter | Default | Description | +| -------------------------------- | ---------- | ----------------------------------------------------------------------------------------------------- | +| `onsite_algorithm` | `'lucxor'` | PTM localization algorithm: `'ascore'`, `'phosphors'`, or `'lucxor'` | +| `onsite_fragment_method` | `'CID'` | Fragmentation method: `'CID'` or `'HCD'` | +| `onsite_fragment_tolerance` | `0.5` | Fragment mass tolerance | +| `onsite_fragment_error_units` | `'Da'` | Fragment error units: `'Da'` or `'ppm'` | +| `onsite_add_decoys` | `false` | Add decoy modifications for validation | +| `onsite_neutral_losses` | `null` | List of neutral losses to consider for modification localization (replaces `luciphor_neutral_losses`) | +| `onsite_decoy_mass` | `null` | Mass to add to an amino acid to make it a decoy (replaces `luciphor_decoy_mass`) | +| `onsite_decoy_neutral_losses` | `null` | List of neutral losses for decoy sequences (replaces `luciphor_decoy_neutral_losses`) | +| `onsite_threads` | `1` | Number of threads for onsite processing | +| `onsite_min_psms` | `5` | Minimum number of high-scoring PSMs for lucxor model training | +| `onsite_disable_split_by_charge` | `false` | Disable splitting PSMs by charge state for lucxor | +| `onsite_compute_all_scores` | `false` | Compute all scores for all candidate sites | + +**Note:** The old `luciphor_*` parameters are no longer supported. Update your configuration files to use the `onsite_*` parameters above. The default algorithm is `'lucxor'`, which provides the same functionality as the previous luciphor module. + ### Reproducibility It is a good idea to specify the pipeline version when running the pipeline on your data. This ensures that a specific version of the pipeline code and software are used when you run your pipeline. If you keep using the same tag, you'll be running the same version of the pipeline, even if there have been changes to the code since. @@ -111,7 +152,7 @@ If `-profile` is not specified, the pipeline will run locally and expect all sof - `shifter` - A generic configuration profile to be used with [Shifter](https://nersc.gitlab.io/development/shifter/how-to-use/) - `charliecloud` - - A generic configuration profile to be used with [Charliecloud](https://hpc.github.io/charliecloud/) + - A generic configuration profile to be used with [Charliecloud](https://charliecloud.io/) - `apptainer` - A generic configuration profile to be used with [Apptainer](https://apptainer.org/) - `wave` diff --git a/main.nf b/main.nf index 2c95d02d0..4f4a03272 100644 --- a/main.nf +++ b/main.nf @@ -16,7 +16,6 @@ */ include { QUANTMS } from './workflows/quantms' -include { PIPELINE_INITIALISATION } from './subworkflows/local/utils_nfcore_quantms_pipeline' include { PIPELINE_COMPLETION } from './subworkflows/local/utils_nfcore_quantms_pipeline' include { UTILS_NEXTFLOW_PIPELINE } from './subworkflows/nf-core/utils_nextflow_pipeline' @@ -57,7 +56,7 @@ workflow { false ) - + // could take UTILS_NEXTFLOW_PIPELINE.out.samplesheet channel as parsed input BIGBIO_QUANTMS () // diff --git a/modules.json b/modules.json index 7159529ba..ba6ed93ea 100644 --- a/modules.json +++ b/modules.json @@ -2,12 +2,33 @@ "name": "bigbio/quantms", "homePage": "https://github.com/bigbio/quantms", "repos": { + "https://github.com/bigbio/nf-modules.git": { + "modules": { + "bigbio": { + "onsite": { + "branch": "main", + "git_sha": "ffe3c99ee867c4d7d6400f95d0b5658eccf16212", + "installed_by": ["modules"] + }, + "thermorawfileparser": { + "branch": "main", + "git_sha": "a1a4a11ff508b2b5c23c9fb21c51c3327b748d4d", + "installed_by": ["modules"] + } + } + } + }, "https://github.com/nf-core/modules.git": { "modules": { "nf-core": { "custom/dumpsoftwareversions": { "branch": "master", - "git_sha": "41dfa3f7c0ffabb96a6a813fe321c6d1cc5b6e46", + "git_sha": "e753770db613ce014b3c4bc94f6cba443427b726", + "installed_by": ["modules"] + }, + "multiqc": { + "branch": "master", + "git_sha": "8deffd6a402233ebf905c66dbf804bd4c2637946", "installed_by": ["modules"] } } @@ -21,12 +42,12 @@ }, "utils_nfcore_pipeline": { "branch": "master", - "git_sha": "05954dab2ff481bcb999f24455da29a5828af08d", + "git_sha": "271e7fc14eb1320364416d996fb077421f3faed2", "installed_by": ["subworkflows"] }, "utils_nfschema_plugin": { "branch": "master", - "git_sha": "dcd088f483cede0c3df4034d405126f05a764cc7", + "git_sha": "e753770db613ce014b3c4bc94f6cba443427b726", "installed_by": ["subworkflows"] } } diff --git a/modules/bigbio/onsite/environment.yml b/modules/bigbio/onsite/environment.yml new file mode 100644 index 000000000..f1d5c7885 --- /dev/null +++ b/modules/bigbio/onsite/environment.yml @@ -0,0 +1,7 @@ +name: onsite +channels: + - conda-forge + - bioconda + - defaults +dependencies: + - bioconda::pyonsite=0.0.2 diff --git a/modules/bigbio/onsite/main.nf b/modules/bigbio/onsite/main.nf new file mode 100644 index 000000000..4101b3e25 --- /dev/null +++ b/modules/bigbio/onsite/main.nf @@ -0,0 +1,119 @@ +process ONSITE { + tag "$meta.mzml_id" + label 'process_medium' + label 'onsite' + + container "${ workflow.containerEngine == 'singularity' && !task.ext.singularity_pull_docker_container ? + 'https://depot.galaxyproject.org/singularity/pyonsite:0.0.2--pyhdfd78af_0' : + 'quay.io/biocontainers/pyonsite:0.0.2--pyhdfd78af_0' }" + + input: + tuple val(meta), path(mzml_file), path(id_file) + + output: + tuple val(meta), path("${id_file.baseName}_*.idXML"), emit: ptm_in_id_onsite + path "versions.yml", emit: versions + path "*.log", emit: log + + script: + def args = task.ext.args ?: '' + def prefix = task.ext.prefix ?: "${meta.mzml_id}" + + // Algorithm selection: lucxor (default), ascore, or phosphors + def algorithm = params.onsite_algorithm ?: 'lucxor' + + // Common parameters for all algorithms + def fragment_tolerance = params.onsite_fragment_tolerance ?: '0.05' + def compute_all_scores = params.onsite_compute_all_scores ? '--compute-all-scores' : '' + + // Set default value for add_decoys (can be overridden by setting params.onsite_add_decoys = false) + def onsite_add_decoys = params.containsKey('onsite_add_decoys') ? params.onsite_add_decoys : true + + // Algorithm-specific parameters + def fragment_unit = '' + def add_decoys = onsite_add_decoys ? '--add-decoys' : '' + def debug = params.onsite_debug ? '--debug' : '' + + // Build algorithm-specific command + def algorithm_cmd = '' + + if (algorithm == 'ascore') { + // AScore: uses -in, -id, -out, --fragment-mass-unit + fragment_unit = params.onsite_fragment_unit ?: 'Da' + def optional_flags = [add_decoys, compute_all_scores, debug].findAll { it }.join(' \\\n ') + algorithm_cmd = """ + onsite ascore \\ + -in ${mzml_file} \\ + -id ${id_file} \\ + -out ${id_file.baseName}_ascore.idXML \\ + --fragment-mass-tolerance ${fragment_tolerance} \\ + --fragment-mass-unit ${fragment_unit}${optional_flags ? ' \\\n ' + optional_flags : ''} + """ + } else if (algorithm == 'phosphors') { + // PhosphoRS: uses -in, -id, -out, --fragment-mass-unit + fragment_unit = params.onsite_fragment_unit ?: 'Da' + def optional_flags = [add_decoys, compute_all_scores, debug].findAll { it }.join(' \\\n ') + algorithm_cmd = """ + onsite phosphors \\ + -in ${mzml_file} \\ + -id ${id_file} \\ + -out ${id_file.baseName}_phosphors.idXML \\ + --fragment-mass-tolerance ${fragment_tolerance} \\ + --fragment-mass-unit ${fragment_unit}${optional_flags ? ' \\\n ' + optional_flags : ''} + """ + } else if (algorithm == 'lucxor') { + // LucXor: uses -in, -id, -out, --fragment-error-units (note: error-units not mass-unit) + fragment_unit = params.onsite_fragment_error_units ?: 'Da' + def fragment_method = params.onsite_fragment_method ?: 'CID' + def min_mz = params.onsite_min_mz ?: '150.0' + def max_charge = params.onsite_max_charge_state ?: '5' + def max_peptide_len = params.onsite_max_peptide_length ?: '40' + def max_num_perm = params.onsite_max_num_perm ?: '16384' + def modeling_threshold = params.onsite_modeling_score_threshold ?: '0.95' + def scoring_threshold = params.onsite_scoring_threshold ?: '0.0' + def min_num_psms = params.onsite_min_num_psms_model ?: '5' + def rt_tolerance = params.onsite_rt_tolerance ?: '0.01' + def disable_split_by_charge = params.onsite_disable_split_by_charge ? '--disable-split-by-charge' : '' + + // Optional target modifications - default for LucXor includes decoy + def target_mods = params.onsite_target_modifications ? "--target-modifications ${params.onsite_target_modifications}" : "--target-modifications 'Phospho(S),Phospho(T),Phospho(Y),PhosphoDecoy(A)'" + def neutral_losses = params.onsite_neutral_losses ? "--neutral-losses ${params.onsite_neutral_losses}" : "--neutral-losses 'sty -H3PO4 -97.97690'" + def decoy_mass = params.onsite_decoy_mass ? "--decoy-mass ${params.onsite_decoy_mass}" : "--decoy-mass 79.966331" + def decoy_losses = params.onsite_decoy_neutral_losses ? "--decoy-neutral-losses ${params.onsite_decoy_neutral_losses}" : "--decoy-neutral-losses 'X -H3PO4 -97.97690'" + + def optional_flags = [disable_split_by_charge, compute_all_scores, debug].findAll { it }.join(' \\\n ') + algorithm_cmd = """ + onsite lucxor \\ + -in ${mzml_file} \\ + -id ${id_file} \\ + -out ${id_file.baseName}_lucxor.idXML \\ + --fragment-method ${fragment_method} \\ + --fragment-mass-tolerance ${fragment_tolerance} \\ + --fragment-error-units ${fragment_unit} \\ + --min-mz ${min_mz} \\ + ${target_mods} \\ + ${neutral_losses} \\ + ${decoy_mass} \\ + ${decoy_losses} \\ + --max-charge-state ${max_charge} \\ + --max-peptide-length ${max_peptide_len} \\ + --max-num-perm ${max_num_perm} \\ + --modeling-score-threshold ${modeling_threshold} \\ + --scoring-threshold ${scoring_threshold} \\ + --min-num-psms-model ${min_num_psms} \\ + --rt-tolerance ${rt_tolerance}${optional_flags ? ' \\\n ' + optional_flags : ''} + """ + } else { + error "Unknown onsite algorithm: ${algorithm}. Supported algorithms: ascore, phosphors, lucxor" + } + + """ + ${algorithm_cmd.trim()} 2>&1 | tee ${id_file.baseName}_${algorithm}.log + + cat <<-END_VERSIONS > versions.yml + "${task.process}": + onsite: \$(onsite --version 2>&1 | grep -oP 'version \\K[0-9.]+' || echo "unknown") + algorithm: ${algorithm} + END_VERSIONS + """ +} diff --git a/modules/bigbio/onsite/meta.yml b/modules/bigbio/onsite/meta.yml new file mode 100644 index 000000000..62a06aa4f --- /dev/null +++ b/modules/bigbio/onsite/meta.yml @@ -0,0 +1,57 @@ +name: onsite +description: Post-translational modification (PTM) localization using onsite algorithms (AScore, PhosphoRS, LucXor) +keywords: + - onsite + - PTM + - phosphorylation + - AScore + - PhosphoRS + - LucXor + - modification + - localization +tools: + - onsite: + description: | + Comprehensive Python package for mass spectrometry post-translational modification (PTM) localization. + Provides algorithms for confident phosphorylation site localization and scoring, including + implementations of AScore, PhosphoRS, and LucXor (LuciPHOr2). + homepage: https://github.com/bigbio/onsite + documentation: https://github.com/bigbio/onsite/blob/main/README.md + tool_dev_url: https://github.com/bigbio/onsite + doi: "" + licence: ["MIT"] +input: + - meta: + type: map + description: | + Groovy Map containing sample information + e.g. [ id:'test', mzml_id:'sample1' ] + - mzml_file: + type: file + description: Input spectrum file in mzML format + pattern: "*.mzML" + - id_file: + type: file + description: Protein/peptide identifications file in idXML format + pattern: "*.idXML" +output: + - meta: + type: map + description: | + Groovy Map containing sample information + e.g. [ id:'test', mzml_id:'sample1' ] + - ptm_in_id_onsite: + type: file + description: Protein/peptide identifications file with PTM localization scores + pattern: "*_{ascore,phosphors,lucxor}.idXML" + - log: + type: file + description: Log file from onsite execution + pattern: "*.log" + - versions: + type: file + description: File containing software versions + pattern: "versions.yml" +authors: + - "@ypriverol" + - "@weizhongchun" diff --git a/modules/bigbio/onsite/tests/main.nf.test b/modules/bigbio/onsite/tests/main.nf.test new file mode 100644 index 000000000..8e4520638 --- /dev/null +++ b/modules/bigbio/onsite/tests/main.nf.test @@ -0,0 +1,112 @@ +nextflow_process { + + name "Test Process ONSITE" + script "../main.nf" + process "ONSITE" + tag "modules" + tag "modules_onsite" + tag "onsite" + + test("Should run AScore algorithm") { + + when { + process { + """ + input[0] = [ + [ id: 'test', mzml_id: 'test_sample' ], + file(params.test_data['proteomics']['onsite']['mzml'], checkIfExists: true), + file(params.test_data['proteomics']['onsite']['idxml'], checkIfExists: true) + ] + """ + } + params { + onsite_algorithm = 'ascore' + } + } + + then { + assert process.success + assert snapshot(process.out.versions).match("versions_ascore") + assert process.out.ptm_in_id_onsite.size() == 1 + assert process.out.log.size() == 1 + // Check output file has correct naming + assert process.out.ptm_in_id_onsite[0][1].toString().endsWith('_ascore.idXML') + } + } + + test("Should run PhosphoRS algorithm") { + + when { + process { + """ + input[0] = [ + [ id: 'test', mzml_id: 'test_sample' ], + file(params.test_data['proteomics']['onsite']['mzml'], checkIfExists: true), + file(params.test_data['proteomics']['onsite']['idxml'], checkIfExists: true) + ] + """ + } + params { + onsite_algorithm = 'phosphors' + } + } + + then { + assert process.success + assert snapshot(process.out.versions).match("versions_phosphors") + assert process.out.ptm_in_id_onsite.size() == 1 + assert process.out.log.size() == 1 + // Check output file has correct naming + assert process.out.ptm_in_id_onsite[0][1].toString().endsWith('_phosphors.idXML') + } + } + + test("Should run LucXor algorithm") { + + when { + process { + """ + input[0] = [ + [ id: 'test', mzml_id: 'test_sample' ], + file(params.test_data['proteomics']['onsite']['mzml'], checkIfExists: true), + file(params.test_data['proteomics']['onsite']['idxml'], checkIfExists: true) + ] + """ + } + params { + onsite_algorithm = 'lucxor' + } + } + + then { + assert process.success + assert snapshot(process.out.versions).match("versions_lucxor") + assert process.out.ptm_in_id_onsite.size() == 1 + assert process.out.log.size() == 1 + // Check output file has correct naming + assert process.out.ptm_in_id_onsite[0][1].toString().endsWith('_lucxor.idXML') + } + } + + test("Should run stub mode") { + + options "-stub" + + when { + process { + """ + input[0] = [ + [ id: 'test', mzml_id: 'test_sample' ], + file(params.test_data['proteomics']['onsite']['mzml'], checkIfExists: true), + file(params.test_data['proteomics']['onsite']['idxml'], checkIfExists: true) + ] + """ + } + } + + then { + assert process.success + assert snapshot(process.out.versions).match("versions_stub") + } + } +} diff --git a/modules/bigbio/onsite/tests/nextflow.config b/modules/bigbio/onsite/tests/nextflow.config new file mode 100644 index 000000000..0293c16f9 --- /dev/null +++ b/modules/bigbio/onsite/tests/nextflow.config @@ -0,0 +1,3 @@ +process { + publishDir = { "${params.outdir}/${task.process.tokenize(':')[-1].tokenize('_')[0].toLowerCase()}" } +} diff --git a/modules/bigbio/thermorawfileparser/environment.yml b/modules/bigbio/thermorawfileparser/environment.yml new file mode 100644 index 000000000..63b8fc64e --- /dev/null +++ b/modules/bigbio/thermorawfileparser/environment.yml @@ -0,0 +1,7 @@ +name: thermorawfileparser +channels: + - conda-forge + - bioconda + - defaults +dependencies: + - bioconda::thermorawfileparser=1.4.5 diff --git a/modules/bigbio/thermorawfileparser/main.nf b/modules/bigbio/thermorawfileparser/main.nf new file mode 100644 index 000000000..31ce4d0b2 --- /dev/null +++ b/modules/bigbio/thermorawfileparser/main.nf @@ -0,0 +1,67 @@ +process THERMORAWFILEPARSER { + tag "$meta.mzml_id" + label 'process_low' + label 'process_single' + label 'error_retry' + + conda "${moduleDir}/environment.yml" + container "${ workflow.containerEngine == 'singularity' && !task.ext.singularity_pull_docker_container ? + 'https://depot.galaxyproject.org/singularity/thermorawfileparser:1.4.5--h05cac1d_1' : + 'biocontainers/thermorawfileparser:1.4.5--h05cac1d_1' }" + + stageInMode { + if (task.attempt == 1) { + if (task.executor == "awsbatch") { + 'symlink' + } else { + 'link' + } + } else if (task.attempt == 2) { + if (task.executor == "awsbatch") { + 'copy' + } else { + 'symlink' + } + } else { + 'copy' + } + } + input: + tuple val(meta), path(rawfile) + + output: + tuple val(meta), path("*.{mzML,mgf,parquet}"), emit: convert_files + path "versions.yml", emit: versions + path "*.log", emit: log + + script: + def args = task.ext.args ?: '' + // Default to indexed mzML format (-f=2) if not specified in args + def formatArg = args.contains('-f=') ? '' : '-f=2' + + """ + ThermoRawFileParser.sh -i='${rawfile}' ${formatArg} ${args} -o=./ 2>&1 | tee '${rawfile.baseName}_conversion.log' + + cat <<-END_VERSIONS > versions.yml + "${task.process}": + ThermoRawFileParser: \$(ThermoRawFileParser.sh --version) + END_VERSIONS + """ + + stub: + def prefix = task.ext.prefix ?: "${meta.mzml_id}" + def args = task.ext.args ?: '' + // Determine output format from args, default to mzML + // Format 0 = MGF, formats 1-2 = mzML, format 3 = Parquet, format 4 = None + def outputExt = (args =~ /-f=0\b/).find() ? 'mgf' : 'mzML' + + """ + touch '${prefix}.${outputExt}' + touch '${prefix}_conversion.log' + + cat <<-END_VERSIONS > versions.yml + "${task.process}": + ThermoRawFileParser: \$(ThermoRawFileParser.sh --version) + END_VERSIONS + """ +} diff --git a/modules/bigbio/thermorawfileparser/meta.yml b/modules/bigbio/thermorawfileparser/meta.yml new file mode 100644 index 000000000..b6f99c9a3 --- /dev/null +++ b/modules/bigbio/thermorawfileparser/meta.yml @@ -0,0 +1,49 @@ +name: thermorawfileparser +description: Convert RAW file to mzML or MGF files +keywords: + - raw + - mzML + - MGF + - OpenMS +tools: + - thermorawfileparser: + description: | + ThermoRawFileParser converts Thermo RAW files to open standard formats like mzML, producing indexed output files. + Use `task.ext.args` to pass additional arguments, e.g.: + - `-f=0` for MGF output, `-f=1` for mzML, `-f=2` for indexed mzML (default), `-f=3` for Parquet, `-f=4` for None + - `-L` or `--msLevel=VALUE` to select MS levels (e.g., `-L=1,2` or `--msLevel=1-3`) + homepage: https://github.com/compomics/ThermoRawFileParser + documentation: https://github.com/compomics/ThermoRawFileParser +input: + - meta: + type: map + description: | + Groovy Map containing sample information + - rawfile: + type: file + description: | + Thermo RAW file + pattern: "*.{raw,RAW}" +output: + - meta: + type: map + description: | + Groovy Map containing sample information + e.g. [ id:'sample1', mzml_id:'UPS1_50amol_R3' ] + - convert_files: + type: file + description: | + Converted files in mzML or MGF format depending on the format parameter (-f). + Format options: 0 for MGF, 1 for mzML, 2 for indexed mzML (default), 3 for Parquet, 4 for None. + pattern: "*.{mzML,mgf,parquet}" + - log: + type: file + description: log file + pattern: "*.log" + - versions: + type: file + description: File containing software version + pattern: "versions.yml" +authors: + - "@daichengxin" + - "@ypriverol" diff --git a/modules/bigbio/thermorawfileparser/tests/main.nf.test b/modules/bigbio/thermorawfileparser/tests/main.nf.test new file mode 100644 index 000000000..355fbb150 --- /dev/null +++ b/modules/bigbio/thermorawfileparser/tests/main.nf.test @@ -0,0 +1,53 @@ +nextflow_process { + + name "Test Process THERMORAWFILEPARSER" + script "../main.nf" + process "THERMORAWFILEPARSER" + tag "modules" + tag "modules_bigbio" + tag "thermorawfileparser" + + test("Should convert RAW to mzML") { + + when { + process { + """ + input[0] = [ + [ id: 'test', mzml_id: 'UPS1_50amol_R3' ], + file(params.test_data['proteomics']['msspectra']['ups1_50amol_r3'], checkIfExists: false) + ] + """ + } + } + + then { + assert process.success + assert snapshot(process.out.versions).match("versions") + assert new File(process.out.convert_files[0][1]).name == 'TMT_Erwinia_1uLSike_Top10HCD_isol2_45stepped_60min_01.mzML' + assert process.out.log.size() == 1 + } + } + + test("Should run stub mode") { + + options "-stub" + + when { + process { + """ + input[0] = [ + [ id: 'test', mzml_id: 'test_sample' ], + file(params.test_data['proteomics']['msspectra']['ups1_50amol_r3'], checkIfExists: false) + ] + """ + } + } + + then { + assert process.success + assert snapshot(process.out.versions).match("versions_stub") + assert new File(process.out.convert_files[0][1]).name == 'test_sample.mzML' + assert process.out.log.size() == 1 + } + } +} diff --git a/modules/bigbio/thermorawfileparser/tests/main.nf.test.snap b/modules/bigbio/thermorawfileparser/tests/main.nf.test.snap new file mode 100644 index 000000000..6562491ed --- /dev/null +++ b/modules/bigbio/thermorawfileparser/tests/main.nf.test.snap @@ -0,0 +1,26 @@ +{ + "versions": { + "content": [ + [ + "versions.yml:md5,dc9625538c025d615109ef8cac3a86ab" + ] + ], + "meta": { + "nf-test": "0.9.3", + "nextflow": "25.04.8" + }, + "timestamp": "2025-12-11T06:27:00.000000" + }, + "versions_stub": { + "content": [ + [ + "versions.yml:md5,dc9625538c025d615109ef8cac3a86ab" + ] + ], + "meta": { + "nf-test": "0.9.3", + "nextflow": "25.04.8" + }, + "timestamp": "2025-12-11T06:27:00.000000" + } +} diff --git a/modules/bigbio/thermorawfileparser/tests/nextflow.config b/modules/bigbio/thermorawfileparser/tests/nextflow.config new file mode 100644 index 000000000..0293c16f9 --- /dev/null +++ b/modules/bigbio/thermorawfileparser/tests/nextflow.config @@ -0,0 +1,3 @@ +process { + publishDir = { "${params.outdir}/${task.process.tokenize(':')[-1].tokenize('_')[0].toLowerCase()}" } +} diff --git a/modules/local/diann/convert_results/main.nf b/modules/local/diann/convert_results/main.nf index f7cfae68e..37011791d 100644 --- a/modules/local/diann/convert_results/main.nf +++ b/modules/local/diann/convert_results/main.nf @@ -3,8 +3,8 @@ process CONVERT_RESULTS { label 'process_medium' container "${ workflow.containerEngine == 'singularity' && !task.ext.singularity_pull_docker_container ? - 'https://depot.galaxyproject.org/singularity/quantms-utils:0.0.23--pyh7e72e81_0' : - 'biocontainers/quantms-utils:0.0.23--pyh7e72e81_0' }" + 'https://depot.galaxyproject.org/singularity/quantms-utils:0.0.24--pyh7e72e81_0' : + 'biocontainers/quantms-utils:0.0.24--pyh7e72e81_0' }" input: path(report) @@ -19,7 +19,7 @@ process CONVERT_RESULTS { output: path "*msstats_in.csv", emit: out_msstats path "*triqler_in.tsv", emit: out_triqler - path "*.mzTab", emit: out_mztab + path "*.mzTab", optional: true, emit: out_mztab path "*.log", emit: log path "versions.yml", emit: versions diff --git a/modules/local/diann/generate_cfg/main.nf b/modules/local/diann/generate_cfg/main.nf index e76d48f7f..7706e9198 100644 --- a/modules/local/diann/generate_cfg/main.nf +++ b/modules/local/diann/generate_cfg/main.nf @@ -3,8 +3,8 @@ process GENERATE_CFG { label 'process_tiny' container "${ workflow.containerEngine == 'singularity' && !task.ext.singularity_pull_docker_container ? - 'https://depot.galaxyproject.org/singularity/quantms-utils:0.0.23--pyh7e72e81_0' : - 'biocontainers/quantms-utils:0.0.23--pyh7e72e81_0' }" + 'https://depot.galaxyproject.org/singularity/quantms-utils:0.0.24--pyh7e72e81_0' : + 'biocontainers/quantms-utils:0.0.24--pyh7e72e81_0' }" input: val(meta) diff --git a/modules/local/msstats/msstats_lfq/main.nf b/modules/local/msstats/msstats_lfq/main.nf index 7663cbe56..a05b28e5c 100644 --- a/modules/local/msstats/msstats_lfq/main.nf +++ b/modules/local/msstats/msstats_lfq/main.nf @@ -12,7 +12,7 @@ process MSSTATS_LFQ { output: // The generation of the PDFs from MSstats are very unstable, especially with auto-contrasts. // And users can easily fix anything based on the csv and the included script -> make optional - path "*.pdf" optional true + path "*.pdf", optional: true path "*.csv", emit: msstats_csv path "*.log", emit: log path "versions.yml" , emit: versions diff --git a/modules/local/msstats/msstats_tmt/main.nf b/modules/local/msstats/msstats_tmt/main.nf index 8467109cf..11587332b 100644 --- a/modules/local/msstats/msstats_tmt/main.nf +++ b/modules/local/msstats/msstats_tmt/main.nf @@ -12,7 +12,7 @@ process MSSTATS_TMT { output: // The generation of the PDFs from MSstatsTMT are very unstable, especially with auto-contrasts. // And users can easily fix anything based on the csv and the included script -> make optional - path "*.pdf" optional true + path "*.pdf", optional: true path "*.csv", emit: msstats_csv path "*.log" path "versions.yml" , emit: versions diff --git a/modules/local/openms/id_merger/main.nf b/modules/local/openms/id_merger/main.nf index c4f5b32c2..8e06d4f82 100644 --- a/modules/local/openms/id_merger/main.nf +++ b/modules/local/openms/id_merger/main.nf @@ -19,7 +19,7 @@ process ID_MERGER { def args = task.ext.args ?: '' def prefix = task.ext.prefix ?: "${groupkey}" - if (params.rescore_range == "by_project") { + if (params.ms2features_range == "by_project") { if (id_files[0].baseName.contains('sage')){ prefix = "${groupkey}_sage" } else if (id_files[0].baseName.contains('comet')){ @@ -27,7 +27,7 @@ process ID_MERGER { } else { prefix = "${groupkey}_msgf" } - } else if (params.rescore_range == "by_sample") { + } else if (params.ms2features_range == "by_sample") { if (id_files[0].baseName.contains('sage')){ prefix = "${groupkey}_sage" } else if (id_files[0].baseName.contains('comet')){ diff --git a/modules/local/openms/id_ripper/main.nf b/modules/local/openms/id_ripper/main.nf index 96763c3ad..65976628a 100644 --- a/modules/local/openms/id_ripper/main.nf +++ b/modules/local/openms/id_ripper/main.nf @@ -8,12 +8,11 @@ process ID_RIPPER { 'ghcr.io/bigbio/openms-tools-thirdparty:2025.04.14' }" input: - tuple val(meta), path(id_file), val(qval_score) + tuple val(meta), path(id_file) output: val(meta), emit: meta path("*.idXML"), emit: id_rippers - val("MS:1001491"), emit: qval_score path "versions.yml", emit: versions path "*.log", emit: log diff --git a/modules/local/openms/isobaric_analyzer/main.nf b/modules/local/openms/isobaric_analyzer/main.nf index 51316e54d..7fb54abef 100644 --- a/modules/local/openms/isobaric_analyzer/main.nf +++ b/modules/local/openms/isobaric_analyzer/main.nf @@ -40,7 +40,12 @@ process ISOBARIC_ANALYZER { .drop(1) // Assuming the first non-comment line is a header .collect { line -> def values = line.split('/') - return "\"${values[1]}/${values[2]}/${values[3]}/${values[4]}\"" + // Handle different labelling types + if (meta.labelling_type == 'tmt18plex' || meta.labelling_type == 'tmt16plex') { + return "\"${values[1]}/${values[2]}/${values[3]}/${values[4]}/${values[5]}/${values[6]}/${values[7]}/${values[8]}\"" + } else { + return "\"${values[1]}/${values[2]}/${values[3]}/${values[4]}\"" + } } // Join the matrix lines into a format for the C++ tool diff --git a/modules/local/openms/protein_quantifier/main.nf b/modules/local/openms/protein_quantifier/main.nf index 47f47d3b7..5fb585d38 100644 --- a/modules/local/openms/protein_quantifier/main.nf +++ b/modules/local/openms/protein_quantifier/main.nf @@ -22,7 +22,7 @@ process PROTEIN_QUANTIFIER { def args = task.ext.args ?: '' include_all = params.include_all ? "-top:include_all" : "" - fix_peptides = params.fix_peptides ? "-fix_peptides" : "" + fix_peptides = params.fix_peptides ? "-consensus:fix_peptides" : "" normalize = params.normalize ? "-consensus:normalize" : "" export_mztab = params.export_mztab ? "-mztab ${pro_quant_exp.baseName}_openms.mzTab" : "" diff --git a/modules/local/openms/proteomicslfq/main.nf b/modules/local/openms/proteomicslfq/main.nf index 275511162..6480649ea 100644 --- a/modules/local/openms/proteomicslfq/main.nf +++ b/modules/local/openms/proteomicslfq/main.nf @@ -16,14 +16,14 @@ process PROTEOMICSLFQ { output: path "${expdes.baseName}_openms.mzTab", emit: out_mztab path "${expdes.baseName}_openms.consensusXML", emit: out_consensusXML - path "*msstats_in.csv", emit: out_msstats optional true - path "*triqler_in.tsv", emit: out_triqler optional true - path "debug_mergedIDs.idXML", emit: debug_mergedIDs optional true - path "debug_mergedIDs_inference.idXML", emit: debug_mergedIDs_inference optional true - path "debug_mergedIDsGreedyResolved.idXML", emit: debug_mergedIDsGreedyResolved optional true - path "debug_mergedIDsGreedyResolvedFDR.idXML", emit: debug_mergedIDsGreedyResolvedFDR optional true - path "debug_mergedIDsGreedyResolvedFDRFiltered.idXML", emit: debug_mergedIDsGreedyResolvedFDRFiltered optional true - path "debug_mergedIDsFDRFilteredStrictlyUniqueResolved.idXML", emit: debug_mergedIDsFDRFilteredStrictlyUniqueResolved optional true + path "*msstats_in.csv", emit: out_msstats, optional: true + path "*triqler_in.tsv", emit: out_triqler, optional: true + path "debug_mergedIDs.idXML", emit: debug_mergedIDs, optional: true + path "debug_mergedIDs_inference.idXML", emit: debug_mergedIDs_inference, optional: true + path "debug_mergedIDsGreedyResolved.idXML", emit: debug_mergedIDsGreedyResolved, optional: true + path "debug_mergedIDsGreedyResolvedFDR.idXML", emit: debug_mergedIDsGreedyResolvedFDR, optional: true + path "debug_mergedIDsGreedyResolvedFDRFiltered.idXML", emit: debug_mergedIDsGreedyResolvedFDRFiltered, optional: true + path "debug_mergedIDsFDRFilteredStrictlyUniqueResolved.idXML", emit: debug_mergedIDsFDRFilteredStrictlyUniqueResolved, optional: true path "*.log", emit: log path "versions.yml", emit: versions diff --git a/modules/local/pmultiqc/main.nf b/modules/local/pmultiqc/main.nf index 7be18f58c..d63efc7a9 100644 --- a/modules/local/pmultiqc/main.nf +++ b/modules/local/pmultiqc/main.nf @@ -2,8 +2,8 @@ process PMULTIQC { label 'process_high' container "${ workflow.containerEngine == 'singularity' && !task.ext.singularity_pull_docker_container ? - 'https://depot.galaxyproject.org/singularity/pmultiqc:0.0.33--pyhdfd78af_0' : - 'biocontainers/pmultiqc:0.0.33--pyhdfd78af_0' }" + 'https://depot.galaxyproject.org/singularity/pmultiqc:0.0.39--pyhdfd78af_0' : + 'biocontainers/pmultiqc:0.0.39--pyhdfd78af_0' }" input: path 'results/*' @@ -17,9 +17,10 @@ process PMULTIQC { script: def args = task.ext.args ?: '' - def disable_pmultiqc = (params.enable_pmultiqc) && (params.export_mztab) ? "" : "--disable_plugin" + def disable_pmultiqc = (params.enable_pmultiqc) && (params.export_mztab) ? "--quantms_plugin" : "" def disable_table_plots = (params.enable_pmultiqc) && (params.skip_table_plots) ? "--disable_table" : "" def disable_idxml_index = (params.enable_pmultiqc) && (params.pmultiqc_idxml_skip) ? "--ignored_idxml" : "" + def contaminant_affix = params.contaminant_string ? "--contaminant_affix ${params.contaminant_string}" : "" """ set -x @@ -32,11 +33,12 @@ process PMULTIQC { multiqc \\ -f \\ + ${disable_pmultiqc} \\ --config ./results/multiqc_config.yml \\ ${args} \\ - ${disable_pmultiqc} \\ ${disable_table_plots} \\ ${disable_idxml_index} \\ + ${contaminant_affix} \\ --quantification_method $params.quantification_method \\ ./results \\ -o . diff --git a/modules/local/preprocess_expdesign/main.nf b/modules/local/preprocess_expdesign/main.nf index 0ef6a8240..1880fd112 100644 --- a/modules/local/preprocess_expdesign/main.nf +++ b/modules/local/preprocess_expdesign/main.nf @@ -7,8 +7,8 @@ process PREPROCESS_EXPDESIGN { label 'process_tiny' container "${ workflow.containerEngine == 'singularity' && !task.ext.singularity_pull_docker_container ? - 'https://depot.galaxyproject.org/singularity/quantms-utils:0.0.23--pyh7e72e81_0' : - 'biocontainers/quantms-utils:0.0.23--pyh7e72e81_0' }" + 'https://depot.galaxyproject.org/singularity/quantms-utils:0.0.24--pyh7e72e81_0' : + 'biocontainers/quantms-utils:0.0.24--pyh7e72e81_0' }" input: path design @@ -33,7 +33,7 @@ process PREPROCESS_EXPDESIGN { cat <<-END_VERSIONS > versions.yml "${task.process}": - sdrf-pipelines: \$(parse_sdrf --version 2>&1 | awk -F ' ' '{print \$2}') + sdrf-pipelines: \$(parse_sdrf --version 2>/dev/null | awk -F ' ' '{print \$2}') END_VERSIONS """ } diff --git a/modules/local/samplesheet_check/main.nf b/modules/local/samplesheet_check/main.nf index 3d87424a4..42cb9b893 100644 --- a/modules/local/samplesheet_check/main.nf +++ b/modules/local/samplesheet_check/main.nf @@ -4,8 +4,8 @@ process SAMPLESHEET_CHECK { label 'process_tiny' container "${ workflow.containerEngine == 'singularity' && !task.ext.singularity_pull_docker_container ? - 'https://depot.galaxyproject.org/singularity/quantms-utils:0.0.23--pyh7e72e81_0' : - 'biocontainers/quantms-utils:0.0.23--pyh7e72e81_0' }" + 'https://depot.galaxyproject.org/singularity/quantms-utils:0.0.24--pyh7e72e81_0' : + 'biocontainers/quantms-utils:0.0.24--pyh7e72e81_0' }" input: path input_file diff --git a/modules/local/sdrf_parsing/main.nf b/modules/local/sdrf_parsing/main.nf index eee498f92..e379facd0 100644 --- a/modules/local/sdrf_parsing/main.nf +++ b/modules/local/sdrf_parsing/main.nf @@ -3,8 +3,8 @@ process SDRF_PARSING { label 'process_tiny' container "${ workflow.containerEngine == 'singularity' && !task.ext.singularity_pull_docker_container ? - 'https://depot.galaxyproject.org/singularity/quantms-utils:0.0.23--pyh7e72e81_0' : - 'biocontainers/quantms-utils:0.0.23--pyh7e72e81_0' }" + 'https://depot.galaxyproject.org/singularity/sdrf-pipelines:0.0.33--pyhdfd78af_0' : + 'biocontainers/sdrf-pipelines:0.0.33--pyhdfd78af_0' }" input: path sdrf @@ -39,7 +39,7 @@ process SDRF_PARSING { cat <<-END_VERSIONS > versions.yml "${task.process}": - sdrf-pipelines: \$(parse_sdrf --version 2>&1 | awk -F ' ' '{print \$2}') + sdrf-pipelines: \$(parse_sdrf --version 2>/dev/null | awk -F ' ' '{print \$2}') END_VERSIONS """ } diff --git a/modules/local/thermorawfileparser/main.nf b/modules/local/thermorawfileparser/main.nf deleted file mode 100644 index 9fce05438..000000000 --- a/modules/local/thermorawfileparser/main.nf +++ /dev/null @@ -1,49 +0,0 @@ -process THERMORAWFILEPARSER { - tag "$meta.mzml_id" - label 'process_low' - label 'process_single' - label 'error_retry' - - container "${ workflow.containerEngine == 'singularity' && !task.ext.singularity_pull_docker_container ? - 'https://depot.galaxyproject.org/singularity/thermorawfileparser:1.3.4--ha8f3691_0' : - 'biocontainers/thermorawfileparser:1.3.4--ha8f3691_0' }" - - stageInMode { - if (task.attempt == 1) { - if (executor == "awsbatch") { - 'symlink' - } else { - 'link' - } - } else if (task.attempt == 2) { - if (executor == "awsbatch") { - 'copy' - } else { - 'symlink' - } - } else { - 'copy' - } - } - - input: - tuple val(meta), path(rawfile) - - output: - tuple val(meta), path("*.mzML"), emit: mzmls_converted - path "versions.yml", emit: versions - path "*.log", emit: log - - script: - def args = task.ext.args ?: '' - def prefix = task.ext.prefix ?: "${meta.mzml_id}" - - """ - ThermoRawFileParser.sh -i=${rawfile} -f=2 -o=./ 2>&1 | tee ${rawfile.baseName}_conversion.log - - cat <<-END_VERSIONS > versions.yml - "${task.process}": - ThermoRawFileParser: \$(ThermoRawFileParser.sh --version) - END_VERSIONS - """ -} diff --git a/modules/local/thermorawfileparser/meta.yml b/modules/local/thermorawfileparser/meta.yml deleted file mode 100644 index f39488036..000000000 --- a/modules/local/thermorawfileparser/meta.yml +++ /dev/null @@ -1,42 +0,0 @@ -name: thermorawfileparser -description: convert raw file to mzml files -keywords: - - raw - - mzML - - OpenMS -tools: - - thermorawfileparser: - description: | - It takes a thermo RAW file as input and outputs indexed mzML - homepage: https://github.com/compomics/ThermoRawFileParser - documentation: https://github.com/compomics/ThermoRawFileParser -input: - - meta: - type: map - description: | - Groovy Map containing sample information - - rawfile: - type: file - description: | - thermo RAW file - pattern: "*.{raw,RAW}" -output: - - meta: - type: map - description: | - Groovy Map containing sample information - e.g. [ id:'MD5', enzyme:trypsin ] - - mzml: - type: file - description: indexed mzML - pattern: "*.mzML" - - log: - type: file - description: log file - pattern: "*.log" - - version: - type: file - description: File containing software version - pattern: "versions.yml" -authors: - - "@daichengxin" diff --git a/modules/local/utils/extract_sample/main.nf b/modules/local/utils/extract_sample/main.nf index 5bafac712..3a08b88f8 100644 --- a/modules/local/utils/extract_sample/main.nf +++ b/modules/local/utils/extract_sample/main.nf @@ -3,8 +3,8 @@ process GET_SAMPLE { label 'process_low' container "${ workflow.containerEngine == 'singularity' && !task.ext.singularity_pull_docker_container ? - 'https://depot.galaxyproject.org/singularity/quantms-utils:0.0.23--pyh7e72e81_0' : - 'biocontainers/quantms-utils:0.0.23--pyh7e72e81_0' }" + 'https://depot.galaxyproject.org/singularity/quantms-utils:0.0.24--pyh7e72e81_0' : + 'biocontainers/quantms-utils:0.0.24--pyh7e72e81_0' }" input: @@ -23,7 +23,7 @@ process GET_SAMPLE { cat <<-END_VERSIONS > versions.yml "${task.process}": - sdrf-pipelines: \$(parse_sdrf --version 2>&1 | awk -F ' ' '{print \$2}') + sdrf-pipelines: \$(parse_sdrf --version 2>/dev/null | awk -F ' ' '{print \$2}') END_VERSIONS """ } diff --git a/modules/local/utils/msrescore_features/main.nf b/modules/local/utils/msrescore_features/main.nf index 51401ef61..06226e912 100644 --- a/modules/local/utils/msrescore_features/main.nf +++ b/modules/local/utils/msrescore_features/main.nf @@ -3,20 +3,17 @@ process MSRESCORE_FEATURES { label 'process_high' container "${ workflow.containerEngine == 'singularity' && !task.ext.singularity_pull_docker_container ? - 'oras://ghcr.io/bigbio/quantms-rescoring-sif:0.0.12' : - 'ghcr.io/bigbio/quantms-rescoring:0.0.12' }" - - // userEmulation settings when docker is specified - containerOptions = (workflow.containerEngine == 'docker') ? '-u $(id -u) -e "HOME=${HOME}" -v /etc/passwd:/etc/passwd:ro -v /etc/shadow:/etc/shadow:ro -v /etc/group:/etc/group:ro -v $HOME:$HOME' : '' + 'oras://ghcr.io/bigbio/quantms-rescoring-sif:0.0.14' : + 'ghcr.io/bigbio/quantms-rescoring:0.0.14' }" input: - tuple val(meta), path(idxml), path(mzml) + tuple val(meta), path(idxml), path(mzml), path(model_weight), val(search_engine) output: - tuple val(meta), path("*ms2rescore.idXML") , emit: idxml - tuple val(meta), path("*.html" ) , optional:true, emit: html - path "versions.yml" , emit: versions - path "*.log" , emit: log + tuple val(meta), path("*ms2rescore.idXML"), val(search_engine) , emit: idxml + tuple val(meta), path("*.html" ) , optional:true, emit: html + path "versions.yml" , emit: versions + path "*.log" , emit: log when: task.ext.when == null || task.ext.when @@ -25,14 +22,42 @@ process MSRESCORE_FEATURES { def args = task.ext.args ?: '' def prefix = task.ext.prefix ?: "${meta.mzml_id}_ms2rescore" - def ms2_model_dir = params.ms2_model_dir ? "--ms2_model_dir ${params.ms2_model_dir}" : "" - - // ms2rescore only supports Da unit. https://ms2rescore.readthedocs.io/en/v3.0.2/userguide/configuration/ - if (meta['fragmentmasstoleranceunit'].toLowerCase().endsWith('da')) { - ms2_tolerance = meta['fragmentmasstolerance'] + // Only add ms2_model_dir if it's actually set and not empty + // Handle cases where parameter might be empty string, null, boolean true, or whitespace + // When --ms2features_model_dir is passed with no value, Nextflow may set it to boolean true + if (params.ms2features_fine_tuning) { + ms2_model_dir = '--ms2_model_dir ./' + } else if (params.ms2features_model_dir && params.ms2features_model_dir != true){ + ms2_model_dir = "--ms2_model_dir ${model_weight}" } else { - log.info "Warning: MS2Rescore only supports Da unit. Set ms2 tolerance in nextflow config!" - ms2_tolerance = params.ms2rescore_fragment_tolerance + ms2_model_dir = "--ms2_model_dir ./" + } + + // Determine if using ms2pip or alphapeptdeep based on ms2features_generators + def using_ms2pip = params.ms2features_generators.toLowerCase().contains('ms2pip') + def using_alphapeptdeep = params.ms2features_generators.toLowerCase().contains('alphapeptdeep') + + // Initialize tolerance variables + def ms2_tolerance = null + def ms2_tolerance_unit = null + + // ms2pip only supports Da unit, but alphapeptdeep supports both Da and ppm + ms2_tolerance = meta['fragmentmasstolerance'] + ms2_tolerance_unit = meta['fragmentmasstoleranceunit'] + if (using_ms2pip) { + // ms2pip only supports Da unit + ms2_tolerance_unit = 'Da' + ms2_tolerance = params.ms2features_tolerance + def fragment_unit_lower = meta['fragmentmasstoleranceunit'].toLowerCase() + if (fragment_unit_lower.endsWith('da')) { + ms2_tolerance = meta['fragmentmasstolerance'] + } else if (fragment_unit_lower == 'ppm' || params.ms2features_tolerance_unit == 'ppm') { + log.warn "Warning: MS2pip only supports Da unit. Using default from config!" + ms2_tolerance = params.ms2features_tolerance + } else { + log.warn "Warning: MS2pip only supports Da unit. Fragment mass tolerance unit '${meta['fragmentmasstoleranceunit']}' is not supported. Using default from config! In the future, please use 'Da' or 'ppm'." + ms2_tolerance = params.ms2features_tolerance + } } if (params.decoy_string_position == "prefix") { @@ -41,35 +66,43 @@ process MSRESCORE_FEATURES { decoy_pattern = "${params.decoy_string}\$" } - if (params.find_best_model) { + if (params.ms2features_best) { find_best_model = "--find_best_model" } else { find_best_model = "" } - if (params.force_model) { + if (params.ms2features_force) { force_model = "--force_model" } else { force_model = "" } - if (params.consider_modloss) { + if (params.ms2features_modloss) { consider_modloss = "--consider_modloss" } else { consider_modloss = "" } + if (params.ms2features_debug) { + debug_log_level = "--log_level DEBUG" + } else { + debug_log_level = "" + } + """ rescoring msrescore2feature \\ --idxml $idxml \\ --mzml $mzml \\ --ms2_tolerance $ms2_tolerance \\ + --ms2_tolerance_unit $ms2_tolerance_unit \\ --output ${idxml.baseName}_ms2rescore.idXML \\ ${ms2_model_dir} \\ --processes $task.cpus \\ ${find_best_model} \\ ${force_model} \\ ${consider_modloss} \\ + ${debug_log_level} \\ $args \\ 2>&1 | tee ${idxml.baseName}_ms2rescore.log diff --git a/modules/local/utils/msrescore_fine_tuning/main.nf b/modules/local/utils/msrescore_fine_tuning/main.nf new file mode 100644 index 000000000..f6bc6f88a --- /dev/null +++ b/modules/local/utils/msrescore_fine_tuning/main.nf @@ -0,0 +1,75 @@ +process MSRESCORE_FINE_TUNING { + tag "$meta.mzml_id" + label 'process_high' + + container "${ workflow.containerEngine == 'singularity' && !task.ext.singularity_pull_docker_container ? + 'oras://ghcr.io/bigbio/quantms-rescoring-sif:0.0.14' : + 'ghcr.io/bigbio/quantms-rescoring:0.0.14' }" + + input: + tuple val(meta), path(idxml), path(mzml), val(groupkey), path(ms2_model_dir) + + output: + tuple val(groupkey), path("retained_ms2.pth") , emit: model_weight + path "versions.yml" , emit: versions + path "*.log" , emit: log + + when: + task.ext.when == null || task.ext.when + + script: + def args = task.ext.args ?: '' + def prefix = task.ext.prefix ?: "${groupkey}_fine_tuning" + + // Initialize tolerance variables + def ms2_tolerance = null + def ms2_tolerance_unit = null + + // ms2pip only supports Da unit, but alphapeptdeep supports both Da and ppm + ms2_tolerance = meta[0]['fragmentmasstolerance'] + ms2_tolerance_unit = meta[0]['fragmentmasstoleranceunit'] + + if (params.ms2features_model_dir && params.ms2features_model_dir != true) { + model_dir = ms2_model_dir + } else { + model_dir = "./" + } + + if (params.force_transfer_learning) { + force_transfer_learning = "--force_transfer_learning" + } else { + force_transfer_learning = "" + } + + if (params.ms2features_modloss) { + consider_modloss = "--consider_modloss" + } else { + consider_modloss = "" + } + + """ + rescoring transfer_learning \\ + --idxml ./ \\ + --mzml ./ \\ + --save_model_dir ./ \\ + --ms2_tolerance $ms2_tolerance \\ + --ms2_tolerance_unit $ms2_tolerance_unit \\ + --processes $task.cpus \\ + --ms2_model_dir ${model_dir} \\ + --calibration_set_size ${params.ms2features_calibration} \\ + --epoch_to_train_ms2 ${params.epoch_to_train_ms2} \\ + --transfer_learning_test_ratio ${params.transfer_learning_test_ratio} \\ + ${force_transfer_learning} \\ + ${consider_modloss} \\ + $args \\ + 2>&1 | tee ${groupkey}_fine_tuning.log + + cat <<-END_VERSIONS > versions.yml + "${task.process}": + quantms-rescoring: \$(rescoring --version 2>&1 | grep -Eo '[0-9]+\\.[0-9]+\\.[0-9]+') + ms2pip: \$(ms2pip --version 2>&1 | grep -Eo '[0-9]+\\.[0-9]+\\.[0-9]+') + deeplc: \$(deeplc --version 2>&1 | grep -Eo '[0-9]+\\.[0-9]+\\.[0-9]+') + MS2Rescore: \$(ms2rescore --version 2>&1 | grep -Eo '[0-9]+\\.[0-9]+\\.[0-9]+' | head -n 1) + END_VERSIONS + """ +} diff --git a/modules/local/utils/msrescore_fine_tuning/meta.yml b/modules/local/utils/msrescore_fine_tuning/meta.yml new file mode 100644 index 000000000..dd98ac3fa --- /dev/null +++ b/modules/local/utils/msrescore_fine_tuning/meta.yml @@ -0,0 +1,39 @@ +name: msrescore_fine_tuning +description: A module to perform AlphaPeptDeep fine-tuning +keywords: + - MS2 + - rescoring + - fine tuning +tools: + - custom: + description: | + A custom module for AlphaPeptDeep fine tuning. + homepage: https://github.com/bigbio/quantms + documentation: https://github.com/bigbio/quantms/tree/readthedocs +input: + - ms2_model_dir: + type: path + description: ms2 model dir + pattern: "*" + - idxml_file: + type: file + description: idXML identification file + pattern: "*.idXML" + - mzml: + type: file + description: spectrum data file + pattern: "*.mzML" + - meta: + type: map + description: Groovy Map containing sample information +output: + - idxml: + type: file + description: idXML identification file after MS2 rescoring + pattern: "*.idXML" + - version: + type: file + description: File containing software version + pattern: "versions.yml" +authors: + - "@daichengxin" diff --git a/modules/local/utils/mzml_statistics/main.nf b/modules/local/utils/mzml_statistics/main.nf index 7de4fa51d..4ddebe34d 100644 --- a/modules/local/utils/mzml_statistics/main.nf +++ b/modules/local/utils/mzml_statistics/main.nf @@ -4,8 +4,8 @@ process MZML_STATISTICS { label 'process_single' container "${ workflow.containerEngine == 'singularity' && !task.ext.singularity_pull_docker_container ? - 'https://depot.galaxyproject.org/singularity/quantms-utils:0.0.23--pyh7e72e81_0' : - 'biocontainers/quantms-utils:0.0.23--pyh7e72e81_0' }" + 'https://depot.galaxyproject.org/singularity/quantms-utils:0.0.24--pyh7e72e81_0' : + 'biocontainers/quantms-utils:0.0.24--pyh7e72e81_0' }" input: tuple val(meta), path(ms_file) diff --git a/modules/local/utils/psm_clean/main.nf b/modules/local/utils/psm_clean/main.nf index 40b512af3..6f93de903 100644 --- a/modules/local/utils/psm_clean/main.nf +++ b/modules/local/utils/psm_clean/main.nf @@ -3,11 +3,8 @@ process PSM_CLEAN { label 'process_high' container "${ workflow.containerEngine == 'singularity' && !task.ext.singularity_pull_docker_container ? - 'https://depot.galaxyproject.org/singularity/quantms-rescoring:0.0.10--pyhdfd78af_0' : - 'biocontainers/quantms-rescoring:0.0.10--pyhdfd78af_0' }" - - // userEmulation settings when docker is specified - containerOptions = (workflow.containerEngine == 'docker') ? '-u $(id -u) -e "HOME=${HOME}" -v /etc/passwd:/etc/passwd:ro -v /etc/shadow:/etc/shadow:ro -v /etc/group:/etc/group:ro -v $HOME:$HOME' : '' + 'oras://ghcr.io/bigbio/quantms-rescoring-sif:0.0.13' : + 'ghcr.io/bigbio/quantms-rescoring:0.0.13' }" input: tuple val(meta), path(idxml), path(mzml) diff --git a/modules/local/utils/psm_conversion/main.nf b/modules/local/utils/psm_conversion/main.nf index c1fc7270d..8e67d4453 100644 --- a/modules/local/utils/psm_conversion/main.nf +++ b/modules/local/utils/psm_conversion/main.nf @@ -3,8 +3,8 @@ process PSM_CONVERSION { label 'process_medium' container "${ workflow.containerEngine == 'singularity' && !task.ext.singularity_pull_docker_container ? - 'https://depot.galaxyproject.org/singularity/quantms-utils:0.0.23--pyh7e72e81_0' : - 'biocontainers/quantms-utils:0.0.23--pyh7e72e81_0' }" + 'https://depot.galaxyproject.org/singularity/quantms-utils:0.0.24--pyh7e72e81_0' : + 'biocontainers/quantms-utils:0.0.24--pyh7e72e81_0' }" input: diff --git a/modules/local/utils/spectrum_features/main.nf b/modules/local/utils/spectrum_features/main.nf index 4b18770ab..83cc150e4 100644 --- a/modules/local/utils/spectrum_features/main.nf +++ b/modules/local/utils/spectrum_features/main.nf @@ -3,17 +3,14 @@ process SPECTRUM_FEATURES { label 'process_low' container "${ workflow.containerEngine == 'singularity' && !task.ext.singularity_pull_docker_container ? - 'https://depot.galaxyproject.org/singularity/quantms-rescoring:0.0.10--pyhdfd78af_0' : - 'biocontainers/quantms-rescoring:0.0.10--pyhdfd78af_0' }" - - // userEmulation settings when docker is specified - containerOptions = (workflow.containerEngine == 'docker') ? '-u $(id -u) -e "HOME=${HOME}" -v /etc/passwd:/etc/passwd:ro -v /etc/shadow:/etc/shadow:ro -v /etc/group:/etc/group:ro -v $HOME:$HOME' : '' + 'oras://ghcr.io/bigbio/quantms-rescoring-sif:0.0.13' : + 'ghcr.io/bigbio/quantms-rescoring:0.0.13' }" input: - tuple val(meta), path(id_file), path(ms_file) + tuple val(meta), path(id_file), val(search_engine), path(ms_file) output: - tuple val(meta), path("${id_file.baseName}_snr.idXML"), emit: id_files_snr + tuple val(meta), path("${id_file.baseName}_snr.idXML"), val(search_engine), emit: id_files_snr path "versions.yml", emit: versions path "*.log", emit: log diff --git a/modules/nf-core/custom/dumpsoftwareversions/templates/dumpsoftwareversions.py b/modules/nf-core/custom/dumpsoftwareversions/templates/dumpsoftwareversions.py index b83b32c4d..aa40e5e36 100755 --- a/modules/nf-core/custom/dumpsoftwareversions/templates/dumpsoftwareversions.py +++ b/modules/nf-core/custom/dumpsoftwareversions/templates/dumpsoftwareversions.py @@ -37,7 +37,7 @@ def _make_versions_html(versions): dedent( f"""\\ - {process if (i == 0) else ''} + {process if (i == 0) else ""} {tool} {version} diff --git a/modules/nf-core/multiqc/environment.yml b/modules/nf-core/multiqc/environment.yml new file mode 100644 index 000000000..d02016a00 --- /dev/null +++ b/modules/nf-core/multiqc/environment.yml @@ -0,0 +1,7 @@ +--- +# yaml-language-server: $schema=https://raw.githubusercontent.com/nf-core/modules/master/modules/environment-schema.json +channels: + - conda-forge + - bioconda +dependencies: + - bioconda::multiqc=1.32 diff --git a/modules/nf-core/multiqc/main.nf b/modules/nf-core/multiqc/main.nf new file mode 100644 index 000000000..abbef4566 --- /dev/null +++ b/modules/nf-core/multiqc/main.nf @@ -0,0 +1,64 @@ +process MULTIQC { + label 'process_single' + + conda "${moduleDir}/environment.yml" + container "${ workflow.containerEngine == 'singularity' && !task.ext.singularity_pull_docker_container ? + 'https://community-cr-prod.seqera.io/docker/registry/v2/blobs/sha256/8c/8c6c120d559d7ee04c7442b61ad7cf5a9e8970be5feefb37d68eeaa60c1034eb/data' : + 'community.wave.seqera.io/library/multiqc:1.32--d58f60e4deb769bf' }" + + input: + path multiqc_files, stageAs: "?/*" + path(multiqc_config) + path(extra_multiqc_config) + path(multiqc_logo) + path(replace_names) + path(sample_names) + + output: + path "*.html" , emit: report + path "*_data" , emit: data + path "*_plots" , optional:true, emit: plots + path "versions.yml", emit: versions + + when: + task.ext.when == null || task.ext.when + + 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 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 + "${task.process}": + multiqc: \$( multiqc --version | sed -e "s/multiqc, version //g" ) + END_VERSIONS + """ + + stub: + """ + mkdir multiqc_data + touch multiqc_data/.stub + mkdir multiqc_plots + touch multiqc_report.html + + cat <<-END_VERSIONS > versions.yml + "${task.process}": + multiqc: \$( multiqc --version | sed -e "s/multiqc, version //g" ) + END_VERSIONS + """ +} diff --git a/modules/nf-core/multiqc/meta.yml b/modules/nf-core/multiqc/meta.yml new file mode 100644 index 000000000..9ac86fd11 --- /dev/null +++ b/modules/nf-core/multiqc/meta.yml @@ -0,0 +1,92 @@ +name: multiqc +description: Aggregate results from bioinformatics analyses across many samples into + a single report +keywords: + - QC + - bioinformatics tools + - Beautiful stand-alone HTML report +tools: + - multiqc: + description: | + MultiQC searches a given directory for analysis logs and compiles a HTML report. + It's a general use tool, perfect for summarising the output from numerous bioinformatics tools. + homepage: https://multiqc.info/ + documentation: https://multiqc.info/docs/ + licence: ["GPL-3.0-or-later"] + identifier: biotools:multiqc +input: + - multiqc_files: + type: file + description: | + List of reports / files recognised by MultiQC, for example the html and zip output of FastQC + ontologies: [] + - multiqc_config: + type: file + description: Optional config yml for MultiQC + pattern: "*.{yml,yaml}" + ontologies: + - edam: http://edamontology.org/format_3750 # YAML + - extra_multiqc_config: + type: file + description: Second optional config yml for MultiQC. Will override common sections + in multiqc_config. + pattern: "*.{yml,yaml}" + ontologies: + - edam: http://edamontology.org/format_3750 # YAML + - multiqc_logo: + type: file + description: Optional logo file for MultiQC + pattern: "*.{png}" + ontologies: [] + - replace_names: + type: file + description: | + Optional two-column sample renaming file. First column a set of + patterns, second column a set of corresponding replacements. Passed via + MultiQC's `--replace-names` option. + pattern: "*.{tsv}" + ontologies: + - edam: http://edamontology.org/format_3475 # TSV + - sample_names: + type: file + description: | + Optional TSV file with headers, passed to the MultiQC --sample_names + argument. + pattern: "*.{tsv}" + ontologies: + - edam: http://edamontology.org/format_3475 # TSV +output: + report: + - "*.html": + type: file + description: MultiQC report file + pattern: ".html" + ontologies: [] + data: + - "*_data": + type: directory + description: MultiQC data dir + pattern: "multiqc_data" + plots: + - "*_plots": + type: file + description: Plots created by MultiQC + pattern: "*_data" + ontologies: [] + versions: + - versions.yml: + type: file + description: File containing software versions + pattern: "versions.yml" + ontologies: + - edam: http://edamontology.org/format_3750 # YAML +authors: + - "@abhi18av" + - "@bunop" + - "@drpatelh" + - "@jfy133" +maintainers: + - "@abhi18av" + - "@bunop" + - "@drpatelh" + - "@jfy133" diff --git a/modules/nf-core/multiqc/tests/custom_prefix.config b/modules/nf-core/multiqc/tests/custom_prefix.config new file mode 100644 index 000000000..b30b1358b --- /dev/null +++ b/modules/nf-core/multiqc/tests/custom_prefix.config @@ -0,0 +1,5 @@ +process { + withName: 'MULTIQC' { + ext.prefix = "custom_prefix" + } +} diff --git a/modules/nf-core/multiqc/tests/main.nf.test b/modules/nf-core/multiqc/tests/main.nf.test new file mode 100644 index 000000000..0745e25ab --- /dev/null +++ b/modules/nf-core/multiqc/tests/main.nf.test @@ -0,0 +1,118 @@ +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.modules_testdata_base_path + 'genomics/sarscov2/illumina/fastqc/test_fastqc.zip', checkIfExists: true)) + input[1] = [] + input[2] = [] + input[3] = [] + input[4] = [] + input[5] = [] + """ + } + } + + then { + assertAll( + { assert process.success }, + { assert process.out.report[0] ==~ ".*/multiqc_report.html" }, + { assert process.out.data[0] ==~ ".*/multiqc_data" }, + { assert snapshot(process.out.versions).match("multiqc_versions_single") } + ) + } + + } + + test("sarscov2 single-end [fastqc] - custom prefix") { + config "./custom_prefix.config" + + when { + process { + """ + 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] = [] + """ + } + } + + then { + assertAll( + { assert process.success }, + { assert process.out.report[0] ==~ ".*/custom_prefix.html" }, + { assert process.out.data[0] ==~ ".*/custom_prefix_data" } + ) + } + + } + + test("sarscov2 single-end [fastqc] [config]") { + + when { + process { + """ + 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] = [] + """ + } + } + + then { + assertAll( + { assert process.success }, + { assert process.out.report[0] ==~ ".*/multiqc_report.html" }, + { assert process.out.data[0] ==~ ".*/multiqc_data" }, + { assert snapshot(process.out.versions).match("multiqc_versions_config") } + ) + } + } + + test("sarscov2 single-end [fastqc] - stub") { + + options "-stub" + + when { + process { + """ + 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] = [] + """ + } + } + + then { + assertAll( + { assert process.success }, + { 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("multiqc_stub") } + ) + } + + } +} diff --git a/modules/nf-core/multiqc/tests/main.nf.test.snap b/modules/nf-core/multiqc/tests/main.nf.test.snap new file mode 100644 index 000000000..a88bafd67 --- /dev/null +++ b/modules/nf-core/multiqc/tests/main.nf.test.snap @@ -0,0 +1,41 @@ +{ + "multiqc_versions_single": { + "content": [ + [ + "versions.yml:md5,737bb2c7cad54ffc2ec020791dc48b8f" + ] + ], + "meta": { + "nf-test": "0.9.3", + "nextflow": "24.10.4" + }, + "timestamp": "2025-10-27T13:33:24.356715" + }, + "multiqc_stub": { + "content": [ + [ + "multiqc_report.html", + "multiqc_data", + "multiqc_plots", + "versions.yml:md5,737bb2c7cad54ffc2ec020791dc48b8f" + ] + ], + "meta": { + "nf-test": "0.9.3", + "nextflow": "24.10.4" + }, + "timestamp": "2025-10-27T13:34:11.103619" + }, + "multiqc_versions_config": { + "content": [ + [ + "versions.yml:md5,737bb2c7cad54ffc2ec020791dc48b8f" + ] + ], + "meta": { + "nf-test": "0.9.3", + "nextflow": "24.10.4" + }, + "timestamp": "2025-10-27T13:34:04.615233" + } +} \ 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 000000000..c537a6a3e --- /dev/null +++ b/modules/nf-core/multiqc/tests/nextflow.config @@ -0,0 +1,5 @@ +process { + withName: 'MULTIQC' { + ext.prefix = null + } +} diff --git a/nextflow.config b/nextflow.config index 8208eb7f4..df333b893 100644 --- a/nextflow.config +++ b/nextflow.config @@ -45,7 +45,7 @@ params { percolator_debug = 0 consensusid_debug = 0 idmapper_debug = 0 - luciphor_debug = 0 + onsite_debug = 0 protein_inference_debug = 0 plfq_debug = 0 protein_quant_debug = 0 @@ -109,20 +109,25 @@ params { min_fr_mz = 100 max_fr_mz = 1800 - // MSRESCORE flags - ms2rescore = false - ms2_model_dir = null - rescore_range = 'independent_run' - ms2_model = 'HCD2021' - feature_generators = 'deeplc,ms2pip' - calibration_set_size = 0.15 - ms2rescore_fragment_tolerance = 0.05 // Unit Da - find_best_model = true - force_model = false - consider_modloss = false - - // SNR features flags - add_snr_feature_percolator = false + // MS2Features flags (MS2-based features for Percolator) + ms2features_enable = false + ms2features_model_dir = null + ms2features_range = 'independent_run' + ms2features_model = 'generic' // Default model for alphapeptdeep + ms2features_generators = 'deeplc,alphapeptdeep' + ms2features_calibration = 0.15 + ms2features_tolerance = 0.05 // Unit Da (fallback when unit conversion needed) + ms2features_tolerance_unit = 'Da' // Unit for ms2features_tolerance + ms2features_best = true + ms2features_force = false + ms2features_modloss = false + ms2features_snr = false // SNR features flags + ms2features_fine_tuning = false + fine_tuning_sample_run = 1 + force_transfer_learning = false + epoch_to_train_ms2 = 20 + transfer_learning_test_ratio = 0.3 + ms2features_debug = false // Enable debug logging for quantms-rescoring // PeptideIndexer flags IL_equivalent = true @@ -144,10 +149,19 @@ params { min_consensus_support = 0 consensusid_considered_top_hits = 0 - // Luciphor options - luciphor_neutral_losses = null - luciphor_decoy_mass = null - luciphor_decoy_neutral_losses = null + // onsite options + onsite_algorithm = 'lucxor' // Options: 'ascore', 'phosphors', 'lucxor' + onsite_fragment_method = 'CID' // CID or HCD + onsite_fragment_tolerance = 0.5 + onsite_fragment_error_units = 'Da' // Da or ppm + onsite_add_decoys = false // Add decoy modifications + onsite_neutral_losses = null + onsite_decoy_mass = null + onsite_decoy_neutral_losses = null + onsite_threads = 1 + onsite_min_psms = 5 // Minimum number of high-scoring PSMs for lucxor model training + onsite_disable_split_by_charge = false // Disable splitting PSMs by charge state for lucxor + onsite_compute_all_scores = false // Compute all scores for all candidate sites // Epifany top_PSMs = 1 @@ -207,7 +221,7 @@ params { random_preanalysis = false random_preanalysis_seed = 42 empirical_assembly_ms_n = 200 - enable_diann_mztab = true + enable_diann_mztab = false // MSstats general options @@ -235,6 +249,7 @@ params { // pmultiqc options enable_pmultiqc = true pmultiqc_idxml_skip = true + contaminant_string = 'CONT' // MultiQC options multiqc_config = null @@ -251,11 +266,15 @@ params { email_on_fail = null plaintext_email = false monochrome_logs = false - hook_url = null + hook_url = System.getenv('HOOK_URL') 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 + trace_report_suffix = new java.util.Date().format( 'yyyy-MM-dd_HH-mm-ss') + + // Config options config_profile_name = null config_profile_description = null @@ -290,7 +309,18 @@ profiles { apptainer.enabled = false docker.runOptions = '-u $(id -u):$(id -g)' } - arm { + arm64 { + process.arch = 'arm64' + // see discussion: https://github.com/nf-core/modules/issues/6694 + // For now if you're using arm64 you have to use wave for the sake of the maintainers + // wave profile + apptainer.ociAutoPull = true + singularity.ociAutoPull = true + wave.enabled = true + wave.freeze = true + wave.strategy = 'conda,container' + } + emulate_amd64 { docker.runOptions = '-u $(id -u):$(id -g) --platform=linux/amd64' } singularity { @@ -348,24 +378,11 @@ profiles { wave.freeze = true wave.strategy = 'conda,container' } - gitpod { - executor.name = 'local' - executor.cpus = 4 - executor.memory = 8.GB - process { - resourceLimits = [ - memory: 8.GB, - cpus : 4, - time : 1.h - ] - } - } gpu { docker.runOptions = '-u $(id -u):$(id -g) --gpus all' apptainer.runOptions = '--nv' singularity.runOptions = '--nv' } - // Micromamba profile removed - Conda is no longer supported test { includeConfig 'conf/tests/test_lfq.config' } test_localize { includeConfig 'conf/tests/test_localize.config' } @@ -379,7 +396,9 @@ profiles { test_full_tmt { includeConfig 'conf/tests/test_full_tmt.config' } test_full_dia { includeConfig 'conf/tests/test_full_dia.config' } test_full { includeConfig 'conf/tests/test_full_lfq.config' } - test_dda_id { includeConfig 'conf/tests/test_dda_id.config' } + test_dda_id_alphapeptdeep { includeConfig 'conf/tests/test_dda_id_alphapeptdeep.config' } + test_dda_id_ms2pip { includeConfig 'conf/tests/test_dda_id_ms2pip.config' } + test_dda_id_fine_tuning { includeConfig 'conf/tests/test_dda_id_fine_tuning.config' } dev { includeConfig 'conf/dev.config' } pride_slurm { includeConfig 'conf/pride_codon_slurm.config' } manual_wave { includeConfig 'conf/wave.config' } @@ -395,8 +414,7 @@ includeConfig params.custom_config_base && (!System.getenv('NXF_OFFLINE') || !pa // Load bigbio/quantms custom profiles from different institutions. -// TODO nf-core: Optionally, you can add a pipeline-specific nf-core config at https://github.com/nf-core/configs -// includeConfig params.custom_config_base && (!System.getenv('NXF_OFFLINE') || !params.custom_config_base.startsWith('http')) ? "${params.custom_config_base}/pipeline/quantms.config" : "/dev/null" +includeConfig params.custom_config_base && (!System.getenv('NXF_OFFLINE') || !params.custom_config_base.startsWith('http')) ? "${params.custom_config_base}/pipeline/quantms.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 @@ -452,7 +470,6 @@ manifest { name = 'bigbio/quantms' homePage = 'https://github.com/bigbio/quantms' contributors = [ - // TODO nf-core: Update the field with the details of the contributors to your pipeline. New with Nextflow version 24.10.0 [ name: 'Yasset Perez-Riverol', affiliation: 'European Bioinformatics Institute (EMBL-EBI), Cambridge, UK', @@ -504,46 +521,19 @@ manifest { description = """Quantitative Mass Spectrometry nf-core workflow""" mainScript = 'main.nf' defaultBranch = 'master' - nextflowVersion = '!>=24.10.5' - version = '1.6.0' + nextflowVersion = '!>=25.04.0' + version = '1.7.0' doi = '10.5281/zenodo.15573386' } // Nextflow plugins plugins { - id 'nf-schema@2.4.2' // Validation of pipeline parameters and creation of an input channel from a sample sheet + id 'nf-schema@2.5.1' // Validation of pipeline parameters and creation of an input channel from a sample sheet } validation { defaultIgnoreParams = ["genomes"] monochromeLogs = params.monochrome_logs - help { - enabled = true - command = "nextflow run bigbio/quantms -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 bigbio/quantms ${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/bigbio/quantms/blob/master/CITATIONS.md -""" - } - summary { - beforeText = validation.help.beforeText - afterText = validation.help.afterText - } } // Load modules.config for DSL2 module specific options diff --git a/nextflow_schema.json b/nextflow_schema.json index 9cef1e6a7..f9f191726 100644 --- a/nextflow_schema.json +++ b/nextflow_schema.json @@ -26,7 +26,8 @@ "type": "string", "description": "The output directory where the results will be saved.", "default": "./results", - "fa_icon": "fas fa-folder-open" + "fa_icon": "fas fa-folder-open", + "format": "directory-path" }, "email": { "type": "string", @@ -51,7 +52,7 @@ "description": "Overwrite the file type/extension of the filename as specified in the SDRF/design", "fa_icon": "fas fa-file-invoice", "default": "mzML", - "help_text": "If the above [`--root_folder`](#root_folder) was given to load local input files, this overwrites the file type/extension of\nthe filename as specified in the SDRF/design. Usually used in case you have an mzML-converted version of the files already. Needs to be\none of 'mzML' or 'raw' (the letter cases should match your files exactly)." + "help_text": "If the above [`--root_folder`](#root_folder) was given to load local input files, this overwrites the file type/extension of\nthe filename as specified in the SDRF/design. Usually used in case you have an mzML-converted version of the files already. Needs to be\none of 'mzML', 'raw', 'd', or 'dia' (the letter cases should match your files exactly). Compressed variants (.gz, .tar, .tar.gz, .zip) are supported for 'mzML', 'raw', and 'd' formats." }, "acquisition_method": { "type": "string", @@ -69,7 +70,8 @@ "type": "boolean", "description": "Whether export PSM from decoy in final identification results", "fa_icon": "far fa-check-square", - "help_text": "Whether export PSM from decoy in final identification results for dda_id subworkflow for specific cases." + "help_text": "Whether export PSM from decoy in final identification results for dda_id subworkflow for specific cases.", + "default": true } } }, @@ -83,7 +85,8 @@ "type": "boolean", "description": "Check that ontology terms in an input SDRF file exist.", "fa_icon": "far fa-check-square", - "help_text": "If false, only a basic readability check is performed on an input SDRF file. This option is useful when ontology providers are inaccessible." + "help_text": "If false, only a basic readability check is performed on an input SDRF file. This option is useful when ontology providers are inaccessible.", + "default": true }, "skip_ms_validation": { "type": "boolean", @@ -95,7 +98,8 @@ "type": "boolean", "description": "Skip validation of factor columns.", "fa_icon": "far fa-check-square", - "help_text": "Skip validation of factor columns in the SDRF. Only useful if your factor values are correct but the sdrf-validation library does not recognize them." + "help_text": "Skip validation of factor columns in the SDRF. Only useful if your factor values are correct but the sdrf-validation library does not recognize them.", + "default": true }, "skip_experimental_design_validation": { "type": "boolean", @@ -107,7 +111,8 @@ "type": "boolean", "description": "Use cached version of the Ontology Lookup Service (OLS).", "fa_icon": "far fa-check-square", - "help_text": "Use only the cached version of the Ontology Lookup Service (OLS) for ontology term validation. This is useful if you don't want the pipeline to query internet services." + "help_text": "Use only the cached version of the Ontology Lookup Service (OLS) for ontology term validation. This is useful if you don't want the pipeline to query internet services.", + "default": true } } }, @@ -216,7 +221,6 @@ }, "mzml_features": { "type": "boolean", - "default": false, "description": "Compute with mzmlstatistics step the features at MS1 level and output to a RAW file, only available for mzML files", "help_text": "Compute with mzmlstatistics step the features at MS1 level and output to a RAW file, only available for mzML files", "fa_icon": "far fa-check-square" @@ -382,26 +386,29 @@ "default": 10, "fa_icon": "fas fa-sliders-h" }, - "min_pr_mz": { "type": "number", "description": "The minimum precursor m/z for the in silico library generation or library-free search", - "fa_icon": "fas fa-filter" + "fa_icon": "fas fa-filter", + "default": 400.0 }, "max_pr_mz": { "type": "number", "description": "The maximum precursor m/z for the in silico library generation or library-free search", - "fa_icon": "fas fa-filter" + "fa_icon": "fas fa-filter", + "default": 2400.0 }, "min_fr_mz": { "type": "number", "description": "The minimum fragment m/z for the in silico library generation or library-free search", - "fa_icon": "fas fa-filter" + "fa_icon": "fas fa-filter", + "default": 100.0 }, "max_fr_mz": { "type": "number", "description": "The maximum fragment m/z for the in silico library generation or library-free search", - "fa_icon": "fas fa-filter" + "fa_icon": "fas fa-filter", + "default": 1800.0 }, "db_debug": { "type": "integer", @@ -430,30 +437,97 @@ "default": "Phospho (S),Phospho (T),Phospho (Y)", "fa_icon": "fas fa-tasks" }, - "luciphor_neutral_losses": { + "onsite_algorithm": { + "type": "string", + "description": "Algorithm to use for PTM localization (ascore, phosphors, or lucxor)", + "default": "lucxor", + "enum": ["ascore", "phosphors", "lucxor"], + "fa_icon": "fas fa-cogs", + "hidden": true + }, + "onsite_fragment_method": { + "type": "string", + "description": "Fragmentation method (CID or HCD)", + "default": "CID", + "enum": ["CID", "HCD"], + "fa_icon": "fas fa-atom", + "hidden": true + }, + "onsite_fragment_tolerance": { + "type": "number", + "description": "Fragment mass tolerance", + "default": 0.5, + "fa_icon": "fas fa-ruler", + "hidden": true + }, + "onsite_fragment_error_units": { + "type": "string", + "description": "Fragment error units (Da or ppm)", + "default": "Da", + "enum": ["Da", "ppm"], + "fa_icon": "fas fa-balance-scale", + "hidden": true + }, + "onsite_add_decoys": { + "type": "boolean", + "description": "Add decoy modifications for validation", + "default": false, + "fa_icon": "fas fa-shield-alt", + "help_text": "When enabled, adds decoy modifications for validation. For AScore/PhosphoRS, adds --add-decoys flag. For LucXor, adds PhosphoDecoy(A) to target modifications.", + "hidden": true + }, + "onsite_neutral_losses": { "type": "string", "description": "List of neutral losses to consider for mod. localization.", "fa_icon": "fas fa-font", "help_text": "List the types of neutral losses that you want to consider. The residue field is case sensitive. For example: lower case 'sty' implies that the neutral loss can only occur if the specified modification is present.\nSyntax: 'NL = - '\n(default: '[sty -H3PO4 -97.97690]')", "hidden": true }, - "luciphor_decoy_mass": { + "onsite_decoy_mass": { "type": "number", "description": "How much to add to an amino acid to make it a decoy for mod. localization.", "fa_icon": "fas fa-font", "hidden": true }, - "luciphor_decoy_neutral_losses": { + "onsite_decoy_neutral_losses": { "type": "string", "description": "List of neutral losses to consider for mod. localization from an internally generated decoy sequence.", "fa_icon": "fas fa-font", "help_text": "For handling the neutral loss from a decoy sequence. The syntax for this is identical to that of the normal neutral losses given above except that the residue is always 'X'. Syntax: DECOY_NL = X - (default: '[X -H3PO4 -97.97690]')", "hidden": true }, - "luciphor_debug": { + "onsite_threads": { + "type": "integer", + "description": "Number of threads for onsite processing", + "default": 1, + "fa_icon": "fas fa-microchip", + "hidden": true + }, + "onsite_min_psms": { + "type": "integer", + "description": "Minimum number of high-scoring PSMs required for model training in LucXor", + "default": 5, + "fa_icon": "fas fa-chart-line", + "hidden": true + }, + "onsite_disable_split_by_charge": { + "type": "boolean", + "description": "Disable splitting PSMs by charge state for lucxor", + "default": false, + "fa_icon": "fas fa-ban", + "hidden": true + }, + "onsite_compute_all_scores": { + "type": "boolean", + "description": "Compute all scores for all candidate sites", + "default": false, + "fa_icon": "fas fa-calculator", + "hidden": true + }, + "onsite_debug": { "type": "integer", "fa_icon": "fas fa-bug", - "description": "Debug level for Luciphor step. Increase for verbose logging and keeping temp files.", + "description": "Debug level for onsite step. Increase for verbose logging and keeping temp files.", "hidden": true } }, @@ -490,34 +564,45 @@ "skip_rescoring": { "type": "boolean", "description": "Skip PSM rescoring steps for specific cases, such as studying pure search engine results and search engine ranks", - "default": false, "fa_icon": "far fa-check-square" }, - "ms2rescore": { + "ms2features_enable": { "type": "boolean", - "description": "Whether performing peptide identification rescoring with LC-MS predictors such as MS²PIP and DeepLC.", - "default": false, + "description": "Whether to enable MS2-based features for Percolator using deep learning predictors such as ms2pip, alphapeptdeep, and DeepLC.", "fa_icon": "far fa-check-square" }, - "add_snr_feature_percolator": { + "ms2features_snr": { "type": "boolean", - "description": "Whether add signal-to-noise ratio features for identification rescoring in percolator", - "default": false, + "description": "Whether to add signal-to-noise ratio features for identification rescoring in percolator", + "fa_icon": "far fa-check-square" + }, + "ms2features_fine_tuning": { + "type": "boolean", + "description": "Whether to fine tune AlphaPeptDeep for identification rescoring", "fa_icon": "far fa-check-square" }, - "rescore_range": { + "fine_tuning_sample_run": { + "type": "integer", + "description": "The number of sample ms run", + "default": 1, + "fa_icon": "fas fa-sliders-h" + }, + "ms2features_range": { "type": "string", - "description": "Rescoring for independent run, Sample or whole experiments", + "description": "MS2Features processing range: independent run, Sample or whole experiments", + "help_text": "independent_run: Process each run separately (default, fastest). by_sample: Group runs by sample for better statistics. by_project: Use all runs together for maximum statistical power (slowest but most accurate).", "fa_icon": "fas fa-font", "default": "independent_run", "enum": ["independent_run", "by_sample", "by_project"] }, - "ms2_model": { + "ms2features_model": { "type": "string", - "description": "Which deep learning model to generate feature.", + "description": "Deep learning model for MS2 prediction. Choose based on your feature generator: alphapeptdeep uses 'generic' model, ms2pip uses instrument-specific models.", + "help_text": "alphapeptdeep models: 'generic' (default, works for most instruments). ms2pip models: 'HCD2021' (modern HCD), 'TMT' (TMT labeling), 'CID' (collision-induced dissociation), 'iTRAQ' (iTRAQ labeling), etc. Select the model that best matches your instrument and fragmentation method.", "fa_icon": "fas fa-font", - "default": "HCD2021", + "default": "generic", "enum": [ + "generic", "HCD2021", "HCD2019", "CID", @@ -533,47 +618,82 @@ "timsTOF2024" ] }, - "ms2_model_dir": { + "ms2features_model_dir": { "type": "string", "description": "The path of ms2 prediction model files. Providing model file to avoid repeated download and slow internet connection", "fa_icon": "fas fa-font" }, - "feature_generators": { + "ms2features_generators": { "type": "string", - "description": "Which feature generator to generate feature.", + "description": "Feature generators for MS2-based rescoring. Comma-separated list of tools to use.", + "help_text": "Available generators: 'deeplc' (retention time prediction, always recommended), 'alphapeptdeep' (MS2 prediction, supports both Da and ppm), 'ms2pip' (MS2 prediction, Da only). Order matters: deeplc should be first. Examples: 'deeplc,alphapeptdeep' (default), 'deeplc,ms2pip', 'deeplc' (only retention time).", "fa_icon": "fas fa-font", - "default": "deeplc,ms2pip" + "default": "deeplc,alphapeptdeep" }, - "calibration_set_size": { + "ms2features_calibration": { "type": "number", - "description": "Percentage of number of calibration set for DeepLC", + "description": "Percentage of PSMs used for DeepLC calibration set (retention time prediction)", + "help_text": "DeepLC uses a subset of high-confidence PSMs to calibrate retention time prediction. Higher values (0.2-0.3) provide better calibration but slower processing. Lower values (0.1-0.15) are faster but may reduce accuracy. Recommended: 0.15 for most datasets.", "default": 0.15, "fa_icon": "fas fa-filter" }, - "ms2rescore_fragment_tolerance": { + "ms2features_tolerance": { "type": "number", - "description": "Fragment mass tolerance used for MS2PIP", + "description": "Fragment mass tolerance for MS2Features (fallback when SDRF values are incompatible)", + "help_text": "Fallback tolerance value used when SDRF fragment tolerance cannot be used (e.g., ppm values with ms2pip). Should match your instrument's fragment mass accuracy. Typical values: 0.02-0.05 Da for high-resolution instruments, 0.1-0.5 Da for low-resolution instruments.", "default": 0.05, "fa_icon": "fas fa-sliders-h" }, - "force_model": { + "ms2features_tolerance_unit": { + "type": "string", + "description": "Unit for ms2features_tolerance (Da or ppm)", + "default": "Da", + "enum": ["Da", "ppm"], + "fa_icon": "fas fa-sliders-h" + }, + "ms2features_force": { "type": "boolean", - "description": "Force to run with provided MS2PIP model. Don't look for the best model and validation", - "default": false, + "description": "Force use of specified MS2 model without validation or best model selection", + "help_text": "When enabled, uses the exact model specified in ms2features_model without checking if it's the best available model or validating its performance. Faster but may use suboptimal models. Use with caution.", "fa_icon": "far fa-check-square" }, - "consider_modloss": { + "ms2features_modloss": { "type": "boolean", - "description": "If modloss ions are masked to zeros in the ms2 model. modloss ions are mostly useful for phospho MS2 prediction model.", - "default": false, + "description": "Enable modification loss ion features for MS2 prediction", + "help_text": "When enabled, includes modification loss ions (e.g., phosphate loss from phosphopeptides) in MS2 prediction. Essential for phosphoproteomics and other PTM studies. Disable for standard proteomics to reduce noise.", "fa_icon": "far fa-check-square" }, - "find_best_model": { + "ms2features_best": { "type": "boolean", - "description": "If find best MS2 model.", + "description": "Automatically find and use the best available MS2 model for your data", + "help_text": "When enabled, automatically selects the best performing model from available options and validates its performance. This is the recommended approach for optimal results. Disable only if you want to use a specific model (see ms2features_force).", "default": true, "fa_icon": "far fa-check-square" }, + "force_transfer_learning": { + "type": "boolean", + "description": "Force save fine-tuning model", + "help_text": "When enabled, Force save fine-tuning model even if retrained model is not better than pretrained model", + "default": false, + "fa_icon": "far fa-check-square" + }, + "epoch_to_train_ms2": { + "type": "integer", + "description": "The number of fine-tuning epoch", + "fa_icon": "fas fa-sliders-h", + "default": 20 + }, + "transfer_learning_test_ratio": { + "type": "number", + "description": "The proportion of test data used for comparing fine-tuned models with pre-trained models", + "default": 0.3, + "fa_icon": "fas fa-sliders-h" + }, + "ms2features_debug": { + "type": "boolean", + "description": "Enable debug logging for quantms-rescoring tool.", + "fa_icon": "fas fa-bug" + }, "run_fdr_cutoff": { "type": "number", "description": "FDR cutoff on PSM level (or peptide level; see Percolator options) *per run* before going into feature finding, map alignment and inference. This can be seen as a pre-filter. See ", @@ -612,7 +732,6 @@ "type": "boolean", "description": "Whether remove invalid PSM with empty spectra and invalid feature values", "fa_icon": "far fa-check-square", - "default": false, "hidden": true } }, @@ -717,7 +836,8 @@ "type": "string", "description": "Operate only on MSn scans where any of its precursors features a certain activation method. Set to empty to disable.", "fa_icon": "fas fa-font", - "enum": ["HCD", "CID", "ETD", "ECD"] + "enum": ["HCD", "CID", "ETD", "ECD"], + "default": "HCD" }, "reporter_mass_shift": { "type": "number", @@ -753,7 +873,6 @@ "isotope_correction": { "type": "boolean", "description": "Enable isotope correction (highly recommended)", - "default": false, "fa_icon": "fas fa-toggle-on" }, "plex_corr_matrix_file": { @@ -764,7 +883,6 @@ "iso_normalization": { "type": "boolean", "description": "Enable normalization of the channel intensities", - "default": false, "fa_icon": "fas fa-toggle-on", "help_text": "The normalization is done by using the Median of Ratios. Also the ratios the medians is provided as control measure." }, @@ -841,7 +959,6 @@ "update_PSM_probabilities": { "type": "boolean", "description": "[Bayesian-only; Experimental] Update PSM probabilities with their posteriors under consideration of the protein probabilities.", - "default": false, "fa_icon": "fas fa-list-ol", "hidden": true }, @@ -917,19 +1034,16 @@ "ratios": { "type": "boolean", "description": "Add the log2 ratios of the abundance values to the output.", - "default": "false", "fa_icon": "fas fa-list-ol" }, "normalize": { "type": "boolean", "description": "Scale peptide abundances so that medians of all samples are equal.(Default false)", - "default": "false", "fa_icon": "far fa-check-square" }, "fix_peptides": { "type": "boolean", "description": "Use the same peptides for protein quantification across all samples.(Default false)", - "default": "false", "fa_icon": "fas fa-bug" }, "include_all": { @@ -1001,10 +1115,10 @@ }, "lfq_intensity_threshold": { "type": "number", - "description": "The minimum intensity for a feature to be considered for quantification. (default: '10000')", - "default": 1000, + "description": "The minimum intensity for a feature to be considered for quantification. (default: '1000')", + "default": 1000.0, "fa_icon": "fas fa-filter", - "help_text": "The minimum intensity for a feature to be considered for quantification. (default: '10000')" + "help_text": "The minimum intensity for a feature to be considered for quantification. (default: '1000')" }, "alignment_order": { "type": "string", @@ -1015,7 +1129,6 @@ }, "quantify_decoys": { "type": "boolean", - "default": false, "description": "Also quantify decoys? (Usually only needed for Triqler post-processing output with [`--add_triqler_output`](#add_triqler_output), where it is auto-enabled)", "fa_icon": "far fa-check-square" }, @@ -1076,8 +1189,7 @@ "species_genes": { "type": "boolean", "description": "Instructs DIA-NN to add the organism identifier to the gene names", - "fa_icon": "far fa-check-square", - "default": false + "fa_icon": "far fa-check-square" }, "diann_speclib": { "type": "string", @@ -1090,21 +1202,18 @@ "type": "boolean", "description": "Save decoy PSMs to the main .parquet report for DIA-NN 2.0.*", "fa_icon": "far fa-check-square", - "default": false, "hidden": false }, "diann_export_xic": { "type": "boolean", "description": "instructs DIA-NN to extract MS1/fragment chromatograms for identified precursors within X seconds from the elution apex, with X set to 10s if not provided;equivalent to the 'XICs' option in the GUI", "fa_icon": "far fa-check-square", - "default": false, "hidden": false }, "skip_preliminary_analysis": { "type": "boolean", "description": "Skip the preliminary analysis step, thus use the passed spectral library as-is insted of generating a local concensus library.", "fa_icon": "fas fa-forward", - "default": false, "hidden": false }, "empirical_assembly_log": { @@ -1131,14 +1240,13 @@ "random_preanalysis": { "type": "boolean", "description": "Enable random selection of spectrum files to generate empirical library.", - "default": false, "fa_icon": "far fa-check-square" }, "random_preanalysis_seed": { "type": "integer", "description": "Set the random seed for the random selection of spectrum files to generate the empirical library.", "default": 42, - "fa_icon": false + "fa_icon": "fas fa-filter" }, "empirical_assembly_ms_n": { "type": "integer", @@ -1150,7 +1258,7 @@ "enable_diann_mztab": { "type": "boolean", "description": "Export the DIA-NN and DIA results to mzTab", - "default": true, + "default": false, "fa_icon": "far fa-check-square" } }, @@ -1175,7 +1283,8 @@ "contrasts": { "type": "string", "description": "Experimental: Allows full control over contrasts by specifying a set of contrasts in a semicolon separated list of R-compatible contrasts with the condition names/numbers as variables (e.g. `1-2;1-3;2-3`). Overwrites [`--ref_condition`](#ref_condition).", - "fa_icon": "fas fa-font" + "fa_icon": "fas fa-font", + "default": "pairwise" }, "msstats_threshold": { "type": "number", @@ -1186,7 +1295,6 @@ "add_triqler_output": { "type": "boolean", "description": "Also create an output in Triqler's format for an alternative manual post-processing with that tool", - "default": false, "fa_icon": "far fa-check-square" }, "msstatslfq_feature_subset_protein": { @@ -1262,8 +1370,7 @@ "msstats_plot_profile_qc": { "type": "boolean", "description": "Export MSstats profile QC plots including all proteins", - "fa_icon": "far fa-check-square", - "default": false + "fa_icon": "far fa-check-square" } }, "fa_icon": "fab fa-r-project" @@ -1276,13 +1383,20 @@ "properties": { "enable_pmultiqc": { "type": "boolean", - "description": "Enable generation of pmultiqc report? default: 'false'", - "fa_icon": "fas fa-toggle-on" + "description": "Enable generation of pmultiqc report? default: true", + "fa_icon": "fas fa-toggle-on", + "default": true }, "pmultiqc_idxml_skip": { "type": "boolean", "description": "Skip idXML files (do not generate search engine scores) in pmultiqc report? default: 'true'", - "fa_icon": "fas fa-toggle-on" + "fa_icon": "fas fa-toggle-on", + "default": true + }, + "contaminant_string": { + "type": "string", + "description": "Contaminant affix string for pmultiqc report. This parameter maps to --contaminant_affix in pmultiqc. default: 'CONT'", + "default": "CONT" } }, "fa_icon": "fas fa-file-medical-alt" @@ -1402,8 +1516,7 @@ "skip_table_plots": { "type": "boolean", "description": "Skip protein/peptide table plots with pmultiqc for large dataset.", - "fa_icon": "fas fa-toggle-on", - "default": false + "fa_icon": "fas fa-toggle-on" }, "multiqc_logo": { "type": "string", @@ -1435,6 +1548,18 @@ "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 + }, + "help": { + "type": ["boolean", "string"], + "description": "Display the help message." + }, + "help_full": { + "type": "boolean", + "description": "Display the full detailed help message." + }, + "show_hidden": { + "type": "boolean", + "description": "Display hidden parameters in the help message (only works when --help or --help_full are provided)." } } } diff --git a/ro-crate-metadata.json b/ro-crate-metadata.json index 9fe2e897d..88fbef08a 100644 --- a/ro-crate-metadata.json +++ b/ro-crate-metadata.json @@ -6,7 +6,7 @@ "@type": "Dataset", "creativeWorkStatus": "Stable", "datePublished": "2025-08-13T13:06:56+00:00", - "description": "# ![bigbio/quantms](docs/images/nf-core-quantms_logo_light.png#gh-light-mode-only) ![bigbio/quantms](docs/images/nf-core-quantms_logo_dark.png#gh-dark-mode-only)\n\n[![GitHub Actions CI Status](https://github.com/bigbio/quantms/actions/workflows/ci.yml/badge.svg)](https://github.com/bigbio/quantms/actions/workflows/ci.yml)\n[![GitHub Actions Linting Status](https://github.com/bigbio/quantms/actions/workflows/linting.yml/badge.svg)](https://github.com/bigbio/quantms/actions/workflows/linting.yml)\n[![AWS CI](https://img.shields.io/badge/CI%20tests-full%20size-FF9900?labelColor=000000&logo=Amazon%20AWS)](https://nf-co.re/quantms/results)\n[![Cite with Zenodo](https://zenodo.org/badge/DOI/10.5281/zenodo.15573386.svg)](https://doi.org/10.5281/zenodo.15573386)\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.10.5-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/quantms)\n\n[![Get help on Slack](https://img.shields.io/badge/slack-nf--core%20%23quantms-4A154B?labelColor=000000&logo=slack)](https://nfcore.slack.com/channels/quantms)[![Follow on Twitter](https://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](https://img.shields.io/badge/youtube-nf--core-FF0000?labelColor=000000&logo=youtube)](https://www.youtube.com/c/nf-core)\n\n## Introduction\n\n**bigbio/quantms** is a bioinformatics best-practice analysis pipeline for Quantitative Mass Spectrometry (MS). Currently, the workflow supports three major MS-based analytical methods: (i) Data dependant acquisition (DDA) label-free and Isobaric quantitation (e.g. TMT, iTRAQ); (ii) Data independent acquisition (DIA) label-free quantification (for details see our in-depth documentation on [quantms](https://quantms.readthedocs.io/en/latest/)).\n\n

\n \"bigbio/quantms\n

\n\nThe pipeline is built using [Nextflow](https://www.nextflow.io), a workflow tool to run tasks across multiple compute infrastructures in a very portable manner. It uses Docker/Singularity containers making installation trivial and results highly reproducible. The [Nextflow DSL2](https://www.nextflow.io/docs/latest/dsl2.html) implementation of this pipeline uses one container per process which makes it much easier to maintain and update software dependencies. Where possible, these processes have been submitted to and installed from [nf-core/modules](https://github.com/nf-core/modules) in order to make them available to all nf-core pipelines, and to everyone within the Nextflow community!\n\nOn release, automated continuous integration tests run the pipeline on a full-sized dataset on the AWS cloud infrastructure. This ensures that the pipeline runs on AWS, has sensible resource allocation defaults set to run on real-world datasets, and permits the persistent storage of results to benchmark between pipeline releases and other analysis sources. The results obtained from the full-sized test can be viewed on the [nf-core website](https://nf-co.re/quantms/results). This gives you a hint on which reports and file types are produced by the pipeline in a standard run. The automatic continuous integration tests on every pull request evaluate different workflows, including peptide identification, quantification for LFQ, LFQ-DIA, and TMT test datasets.\n\n## Pipeline summary\n\n**bigbio/quantms** allows uses to perform analyses of three main types of analytical mass spectrometry-based quantitative methods: DDA-LFQ, DDA-ISO, DIA-LFQ. Each of these workflows share some processes but also includes their own steps. In summary:\n\n### DDA-LFQ (data-dependent label-free quantification)\n\n1. RAW file conversion to mzML ([`thermorawfileparser`](https://github.com/compomics/ThermoRawFileParser))\n2. Peptide identification using [`comet`](https://uwpr.github.io/Comet/) and/or [`msgf+`](https://github.com/MSGFPlus/msgfplus)\n3. (Optional) Add extra PSM features using [`ms2rescore`](https://github.com/compomics/ms2rescore)\n4. Re-scoring peptide identifications [`percolator`](https://github.com/percolator/percolator)\n5. Peptide identification FDR [`openms fdr tool`](https://github.com/bigbio/quantms/blob/HEAD/modules/local/openms/false_discovery_rate/main.nf)\n6. Modification localization [`luciphor`](https://github.com/dfermin/lucXor)\n7. Quantification: Feature detection [`proteomicsLFQ`](https://abibuilder.cs.uni-tuebingen.de/archive/openms/Documentation/nightly/html/TOPP_ProteomicsLFQ.html)\n8. Protein inference and quantification [`proteomicsLFQ`](https://abibuilder.cs.uni-tuebingen.de/archive/openms/Documentation/nightly/html/TOPP_ProteomicsLFQ.html)\n9. QC report generation [`pmultiqc`](https://github.com/bigbio/pmultiqc)\n10. Normalization, imputation, significance testing with [`MSstats`](https://github.com/VitekLab/MSstats)\n\n### DDA-ISO (data-dependent quantification via isobaric labelling)\n\n1. RAW file conversion to mzML ([`thermorawfileparser`](https://github.com/compomics/ThermoRawFileParser))\n2. Peptide identification using [`comet`](https://uwpr.github.io/Comet/) and/or [`msgf+`](https://github.com/MSGFPlus/msgfplus)\n3. (Optional) Add extra PSM features using [`ms2rescore`](https://github.com/compomics/ms2rescore)\n4. Re-scoring peptide identifications [`percolator`](https://github.com/percolator/percolator)\n5. Peptide identification FDR [`openms fdr tool`](https://github.com/bigbio/quantms/blob/HEAD/modules/local/openms/false_discovery_rate/main.nf)\n6. Modification localization [`luciphor`](https://github.com/dfermin/lucXor)\n7. Extracts and normalizes isobaric labeling [`IsobaricAnalyzer`](https://abibuilder.cs.uni-tuebingen.de/archive/openms/Documentation/nightly/html/TOPP_IsobaricAnalyzer.html)\n8. Protein inference [`ProteinInference`](https://abibuilder.cs.uni-tuebingen.de/archive/openms/Documentation/nightly/html/TOPP_ProteinInference.html) or [`Epifany`](https://abibuilder.cs.uni-tuebingen.de/archive/openms/Documentation/nightly/html/TOPP_Epifany.html) for bayesian inference.\n9. Protein Quantification [`ProteinQuantifier`](https://abibuilder.cs.uni-tuebingen.de/archive/openms/Documentation/nightly/html/TOPP_ProteinQuantifier.html)\n10. QC report generation [`pmultiqc`](https://github.com/bigbio/pmultiqc)\n11. Normalization, imputation, significance testing with [`MSstats`](https://github.com/VitekLab/MSstats)\n\n### DIA-LFQ (data-independent label-free quantification)\n\n1. RAW file conversion to mzML when RAW as input([`thermorawfileparser`](https://github.com/compomics/ThermoRawFileParser))\n2. Performing an [optional step](https://github.com/bigbio/quantms/blob/HEAD/modules/local/utils/tdf2mzml/main.nf): Converting .d to mzML when bruker data as input and set `convert_dotd` to true\n3. DIA-NN analysis [`dia-nn`](https://github.com/vdemichev/DiaNN/)\n4. Generation of output files (msstats)\n5. QC reports generation [`pmultiqc`](https://github.com/bigbio/pmultiqc)\n\n### Functionality overview\n\nA graphical overview of suggested routes through the pipeline depending on context can be seen below.\n\n

\n \"bigbio/quantms\n

\n\n## Usage\n\n> [!NOTE]\n> If you are new to Nextflow and nf-core, please refer to [this page](https://nf-co.re/docs/usage/installation) on how to set-up Nextflow. Make sure to [test your setup](https://nf-co.re/docs/usage/introduction#how-to-run-a-pipeline) with `-profile test` before running the workflow on actual data.\n\nFirst, find or create a sample-to-data relationship file ([SDRF](https://github.com/bigbio/proteomics-sample-metadata)).\nHave a look at public datasets that were already annotated [here](https://github.com/bigbio/proteomics-sample-metadata/tree/master/annotated-projects).\nThose SDRFs should be ready for one-command re-analysis and you can just use the URL to the file on GitHub,\ne.g., `https://raw.githubusercontent.com/bigbio/proteomics-sample-metadata/master/annotated-projects/PXD000396/PXD000396.sdrf.tsv`.\nIf you create your own, please adhere to the specifications and point the pipeline to your local folder or a remote location where you uploaded it to.\n\nThe second requirement is a protein sequence database. We suggest downloading a database for the organism(s)/proteins of interest from [Uniprot](https://www.uniprot.org/proteomes?query=*).\n\nNow, you can run the pipeline using:\n\n\n\n```bash\nnextflow run bigbio/quantms \\\n -profile \\\n --input project.sdrf.tsv \\\n --database database.fasta \\\n --outdir \n```\n\n> [!NOTE]\n> Conda is no longer supported in this pipeline. Please use Docker, Singularity, or other container-based profiles.\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**_;\n> see [docs](https://nf-co.re/usage/configuration#custom-configuration-files).\n\nFor more details and further functionality, please refer to the [usage documentation](https://nf-co.re/quantms/usage) and the [parameter documentation](https://nf-co.re/quantms/parameters).\n\n## Additional documentation and tutorial\n\nThe **bigbio/quantms** pipeline comes with a stand-alone [full documentation](https://quantms.readthedocs.io/en/latest/) including examples, benchmarks, and detailed explanation about the data analysis of proteomics data using quantms.\n\n## Pipeline output\n\nTo see the results of an example test run with a full size dataset refer to the [results](https://nf-co.re/quantms/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/quantms/output).\n\n## Credits\n\nbigbio/quantms was originally written by: Chengxin Dai ([@daichengxin](https://github.com/daichengxin)), Julianus Pfeuffer ([@jpfeuffer](https://github.com/jpfeuffer)) and Yasset Perez-Riverol ([@ypriverol](https://github.com/ypriverol)).\n\nWe thank the following people for their extensive assistance in the development of this pipeline:\n\n- Timo Sachsenberg ([@timosachsenberg](https://github.com/timosachsenberg))\n- Wang Hong ([@WangHong007](https://github.com/WangHong007))\n- Henry Webel ([@enryH](https://github.com/enryH))\n- Fabian Egli ([@fabianegli](https://github.com/fabianegli))\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 `#quantms` channel](https://nfcore.slack.com/channels/quantms) (you can join with [this invite](https://nf-co.re/join/slack)).\n\n## How to cite\n\nIf you use **bigbio/quantms** for your analysis, please cite it using the following citation:\n\n> **quantms: a cloud-based pipeline for quantitative proteomics enables the reanalysis of public proteomics data**\n>\n> Chengxin Dai, Julianus Pfeuffer, Hong Wang, Ping Zheng, Lukas K\u00e4ll, Timo Sachsenberg, Vadim Demichev, Mingze Bai, Oliver Kohlbacher & Yasset Perez-Riverol\n>\n> _Nat Methods._ 2024 July 4. doi: [10.1038/s41592-024-02343-1](https://doi.org/10.1038/s41592-024-02343-1).\n\n## How to cite nf-core\n\nAn extensive list of references for the tools used by the pipeline can be found in the [`CITATIONS.md`](CITATIONS.md) file.\n\nYou can cite the `nf-core` publication as follows:\n\n> **The nf-core framework for community-curated bioinformatics pipelines.**\n>\n> Philip Ewels, Alexander Peltzer, Sven Fillinger, Harshil Patel, Johannes Alneberg, Andreas Wilm, Maxime Ulysse Garcia, Paolo Di Tommaso & Sven Nahnsen.\n>\n> _Nat Biotechnol._ 2020 Feb 13. doi: [10.1038/s41587-020-0439-x](https://dx.doi.org/10.1038/s41587-020-0439-x).\n", + "description": "# ![bigbio/quantms](docs/images/nf-core-quantms_logo_light.png#gh-light-mode-only) ![bigbio/quantms](docs/images/nf-core-quantms_logo_dark.png#gh-dark-mode-only)\n\n[![GitHub Actions CI Status](https://github.com/bigbio/quantms/actions/workflows/ci.yml/badge.svg)](https://github.com/bigbio/quantms/actions/workflows/ci.yml)\n[![GitHub Actions Linting Status](https://github.com/bigbio/quantms/actions/workflows/linting.yml/badge.svg)](https://github.com/bigbio/quantms/actions/workflows/linting.yml)\n[![AWS CI](https://img.shields.io/badge/CI%20tests-full%20size-FF9900?labelColor=000000&logo=Amazon%20AWS)](https://nf-co.re/quantms/results)\n[![Cite with Zenodo](https://zenodo.org/badge/DOI/10.5281/zenodo.15573386.svg)](https://doi.org/10.5281/zenodo.15573386)\n[![nf-test](https://img.shields.io/badge/unit_tests-nf--test-337ab7.svg)](https://www.nf-test.com)\n\n[![Nextflow](https://img.shields.io/badge/version-%E2%89%A525.04.0-green?style=flat&logo=nextflow&logoColor=white&color=%230DC09D&link=https%3A%2F%2Fnextflow.io)](https://www.nextflow.io/)\n[![nf-core template version](https://img.shields.io/badge/nf--core_template-3.4.1-green?style=flat&logo=nfcore&logoColor=white&color=%2324B064&link=https%3A%2F%2Fnf-co.re)](https://github.com/nf-core/tools/releases/tag/3.4.1)\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/quantms)\n\n[![Get help on Slack](https://img.shields.io/badge/slack-nf--core%20%23quantms-4A154B?labelColor=000000&logo=slack)](https://nfcore.slack.com/channels/quantms)[![Follow on Twitter](https://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](https://img.shields.io/badge/youtube-nf--core-FF0000?labelColor=000000&logo=youtube)](https://www.youtube.com/c/nf-core)\n\n## Introduction\n\n**bigbio/quantms** is a bioinformatics best-practice analysis pipeline for Quantitative Mass Spectrometry (MS). Currently, the workflow supports three major MS-based analytical methods: (i) Data dependant acquisition (DDA) label-free and Isobaric quantitation (e.g. TMT, iTRAQ); (ii) Data independent acquisition (DIA) label-free quantification (for details see our in-depth documentation on [quantms](https://quantms.readthedocs.io/en/latest/)).\n\n

\n \"bigbio/quantms\n

\n\nThe pipeline is built using [Nextflow](https://www.nextflow.io), a workflow tool to run tasks across multiple compute infrastructures in a very portable manner. It uses Docker/Singularity containers making installation trivial and results highly reproducible. The [Nextflow DSL2](https://www.nextflow.io/docs/latest/dsl2.html) implementation of this pipeline uses one container per process which makes it much easier to maintain and update software dependencies. Where possible, these processes have been submitted to and installed from [nf-core/modules](https://github.com/nf-core/modules) in order to make them available to all nf-core pipelines, and to everyone within the Nextflow community!\n\nOn release, automated continuous integration tests run the pipeline on a full-sized dataset on the AWS cloud infrastructure. This ensures that the pipeline runs on AWS, has sensible resource allocation defaults set to run on real-world datasets, and permits the persistent storage of results to benchmark between pipeline releases and other analysis sources. The results obtained from the full-sized test can be viewed on the [nf-core website](https://nf-co.re/quantms/results). This gives you a hint on which reports and file types are produced by the pipeline in a standard run. The automatic continuous integration tests on every pull request evaluate different workflows, including peptide identification, quantification for LFQ, LFQ-DIA, and TMT test datasets.\n\n## Pipeline summary\n\n**bigbio/quantms** allows uses to perform analyses of three main types of analytical mass spectrometry-based quantitative methods: DDA-LFQ, DDA-ISO, DIA-LFQ. Each of these workflows share some processes but also includes their own steps. In summary:\n\n### DDA-LFQ (data-dependent label-free quantification)\n\n1. RAW file conversion to mzML ([`thermorawfileparser`](https://github.com/compomics/ThermoRawFileParser))\n2. Peptide identification using [`comet`](https://uwpr.github.io/Comet/) and/or [`msgf+`](https://github.com/MSGFPlus/msgfplus)\n3. (Optional) Add extra PSM features using [`quantms-rescoring`](https://github.com/bigbio/quantms-rescoring)\n4. Re-scoring peptide identifications [`percolator`](https://github.com/percolator/percolator)\n5. Peptide identification FDR [`openms fdr tool`](https://github.com/bigbio/quantms/blob/HEAD/modules/local/openms/false_discovery_rate/main.nf)\n6. Modification localization [`onsite`](https://github.com/bigbio/onsite)\n7. Quantification: Feature detection [`proteomicsLFQ`](https://abibuilder.cs.uni-tuebingen.de/archive/openms/Documentation/nightly/html/TOPP_ProteomicsLFQ.html)\n8. Protein inference and quantification [`proteomicsLFQ`](https://abibuilder.cs.uni-tuebingen.de/archive/openms/Documentation/nightly/html/TOPP_ProteomicsLFQ.html)\n9. QC report generation [`pmultiqc`](https://github.com/bigbio/pmultiqc)\n10. Normalization, imputation, significance testing with [`MSstats`](https://github.com/VitekLab/MSstats)\n\n### DDA-ISO (data-dependent quantification via isobaric labelling)\n\n1. RAW file conversion to mzML ([`thermorawfileparser`](https://github.com/compomics/ThermoRawFileParser))\n2. Peptide identification using [`comet`](https://uwpr.github.io/Comet/) and/or [`msgf+`](https://github.com/MSGFPlus/msgfplus)\n3. (Optional) Add extra PSM features using [`quantms-rescoring`](https://github.com/bigbio/quantms-rescoring)\n4. Re-scoring peptide identifications [`percolator`](https://github.com/percolator/percolator)\n5. Peptide identification FDR [`openms fdr tool`](https://github.com/bigbio/quantms/blob/HEAD/modules/local/openms/false_discovery_rate/main.nf)\n6. Modification localization [`onsite`](https://github.com/bigbio/onsite)\n7. Extracts and normalizes isobaric labeling [`IsobaricAnalyzer`](https://abibuilder.cs.uni-tuebingen.de/archive/openms/Documentation/nightly/html/TOPP_IsobaricAnalyzer.html)\n8. Protein inference [`ProteinInference`](https://abibuilder.cs.uni-tuebingen.de/archive/openms/Documentation/nightly/html/TOPP_ProteinInference.html) or [`Epifany`](https://abibuilder.cs.uni-tuebingen.de/archive/openms/Documentation/nightly/html/TOPP_Epifany.html) for bayesian inference.\n9. Protein Quantification [`ProteinQuantifier`](https://abibuilder.cs.uni-tuebingen.de/archive/openms/Documentation/nightly/html/TOPP_ProteinQuantifier.html)\n10. QC report generation [`pmultiqc`](https://github.com/bigbio/pmultiqc)\n11. Normalization, imputation, significance testing with [`MSstats`](https://github.com/VitekLab/MSstats)\n\n### DIA-LFQ (data-independent label-free quantification)\n\n1. RAW file conversion to mzML when RAW as input([`thermorawfileparser`](https://github.com/compomics/ThermoRawFileParser))\n2. Performing an [optional step](https://github.com/bigbio/quantms/blob/HEAD/modules/local/utils/tdf2mzml/main.nf): Converting .d to mzML when bruker data as input and set `convert_dotd` to true\n3. DIA-NN analysis [`dia-nn`](https://github.com/vdemichev/DiaNN/)\n4. Generation of output files (msstats)\n5. QC reports generation [`pmultiqc`](https://github.com/bigbio/pmultiqc)\n\n### Functionality overview\n\nA graphical overview of suggested routes through the pipeline depending on context can be seen below.\n\n

\n \"bigbio/quantms\n

\n\n## Usage\n\n> [!NOTE]\n> If you are new to Nextflow and nf-core, please refer to [this page](https://nf-co.re/docs/usage/installation) on how to set-up Nextflow. Make sure to [test your setup](https://nf-co.re/docs/usage/introduction#how-to-run-a-pipeline) with `-profile test` before running the workflow on actual data.\n\n### Supported file formats\n\nThe pipeline supports the following mass spectrometry data file formats:\n\n- **`.raw`** - Thermo RAW files (automatically converted to mzML)\n- **`.mzML`** - Open standard mzML files\n- **`.d`** - Bruker timsTOF files (optionally converted to mzML when `--convert_dotd` is set)\n- **`.dia`** - DIA-NN native binary format (passed through without conversion)\n\nCompressed variants are supported for `.raw`, `.mzML`, and `.d` formats:\n\n- `.gz` (gzip compressed)\n- `.tar` (tar archive)\n- `.tar.gz` or `.tgz` (tar gzip compressed)\n- `.zip` (zip compressed)\n\nFirst, find or create a sample-to-data relationship file ([SDRF](https://github.com/bigbio/proteomics-sample-metadata)).\nHave a look at public datasets that were already annotated [here](https://github.com/bigbio/proteomics-sample-metadata/tree/master/annotated-projects).\nThose SDRFs should be ready for one-command re-analysis and you can just use the URL to the file on GitHub,\ne.g., `https://raw.githubusercontent.com/bigbio/proteomics-sample-metadata/master/annotated-projects/PXD000396/PXD000396.sdrf.tsv`.\nIf you create your own, please adhere to the specifications and point the pipeline to your local folder or a remote location where you uploaded it to.\n\nThe second requirement is a protein sequence database. We suggest downloading a database for the organism(s)/proteins of interest from [Uniprot](https://www.uniprot.org/proteomes?query=*).\n\nNow, you can run the pipeline using:\n\n```bash\nnextflow run bigbio/quantms \\\n -profile \\\n --input project.sdrf.tsv \\\n --database database.fasta \\\n --outdir \n```\n\n> [!NOTE]\n> Conda is no longer supported in this pipeline. Please use Docker, Singularity, or other container-based profiles.\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**_;\n> see [docs](https://nf-co.re/usage/configuration#custom-configuration-files).\n\nFor more details and further functionality, please refer to the [usage documentation](https://nf-co.re/quantms/usage) and the [parameter documentation](https://nf-co.re/quantms/parameters).\n\n## Additional documentation and tutorial\n\nThe **bigbio/quantms** pipeline comes with a stand-alone [full documentation](https://quantms.readthedocs.io/en/latest/) including examples, benchmarks, and detailed explanation about the data analysis of proteomics data using quantms.\n\n## Pipeline output\n\nTo see the results of an example test run with a full size dataset refer to the [results](https://nf-co.re/quantms/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/quantms/output).\n\n## Credits\n\nbigbio/quantms was originally written by: Chengxin Dai ([@daichengxin](https://github.com/daichengxin)), Julianus Pfeuffer ([@jpfeuffer](https://github.com/jpfeuffer)) and Yasset Perez-Riverol ([@ypriverol](https://github.com/ypriverol)).\n\nWe thank the following people for their extensive assistance in the development of this pipeline:\n\n- Timo Sachsenberg ([@timosachsenberg](https://github.com/timosachsenberg))\n- Wang Hong ([@WangHong007](https://github.com/WangHong007))\n- Henry Webel ([@enryH](https://github.com/enryH))\n- Fabian Egli ([@fabianegli](https://github.com/fabianegli))\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 `#quantms` channel](https://nfcore.slack.com/channels/quantms) (you can join with [this invite](https://nf-co.re/join/slack)).\n\n## How to cite\n\nIf you use **bigbio/quantms** for your analysis, please cite it using the following citation:\n\n> **quantms: a cloud-based pipeline for quantitative proteomics enables the reanalysis of public proteomics data**\n>\n> Chengxin Dai, Julianus Pfeuffer, Hong Wang, Ping Zheng, Lukas K\u00e4ll, Timo Sachsenberg, Vadim Demichev, Mingze Bai, Oliver Kohlbacher & Yasset Perez-Riverol\n>\n> _Nat Methods._ 2024 July 4. doi: [10.1038/s41592-024-02343-1](https://doi.org/10.1038/s41592-024-02343-1).\n\n## How to cite nf-core\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" @@ -186,7 +186,7 @@ "url": { "@id": "https://www.nextflow.io/" }, - "version": "!>=24.10.5" + "version": "!>=25.04.0" }, { "@id": "assets/", diff --git a/subworkflows/local/create_input_channel/main.nf b/subworkflows/local/create_input_channel/main.nf index 6b4ac3cdd..aeba504d3 100644 --- a/subworkflows/local/create_input_channel/main.nf +++ b/subworkflows/local/create_input_channel/main.nf @@ -1,14 +1,10 @@ // // Create channel for input file // -include { SDRF_PARSING } from '../../../modules/local/sdrf_parsing/main' +include { SDRF_PARSING } from '../../../modules/local/sdrf_parsing/main' include { PREPROCESS_EXPDESIGN } from '../../../modules/local/preprocess_expdesign' -class Wrapper { - def labelling_type = "" - def acquisition_method = "" - def experiment_id = "" -} + workflow CREATE_INPUT_CHANNEL { take: @@ -19,158 +15,172 @@ workflow CREATE_INPUT_CHANNEL { ch_versions = Channel.empty() if (is_sdrf.toString().toLowerCase().contains("true")) { - SDRF_PARSING ( ch_sdrf_or_design ) + SDRF_PARSING(ch_sdrf_or_design) ch_versions = ch_versions.mix(SDRF_PARSING.out.versions) ch_config = SDRF_PARSING.out.ch_sdrf_config_file - ch_expdesign = SDRF_PARSING.out.ch_expdesign - } else { - PREPROCESS_EXPDESIGN( ch_sdrf_or_design ) + ch_expdesign = SDRF_PARSING.out.ch_expdesign + } + else { + PREPROCESS_EXPDESIGN(ch_sdrf_or_design) ch_versions = ch_versions.mix(PREPROCESS_EXPDESIGN.out.versions) ch_config = PREPROCESS_EXPDESIGN.out.ch_config ch_expdesign = PREPROCESS_EXPDESIGN.out.ch_expdesign } - Set enzymes = [] - Set files = [] + def Set enzymes = [] + def Set files = [] // TODO remove. We can't use the variable to direct channels anyway - wrapper = new Wrapper() - wrapper.labelling_type = "" - wrapper.acquisition_method = "" - wrapper.experiment_id = ch_sdrf_or_design + def wrapper = [ + labelling_type: "", + acquisition_method: "", + experiment_id: ch_sdrf_or_design, + ] - if(is_sdrf.toString().toLowerCase().contains("false")) { - log.info "No SDRF given. Using parameters to determine tolerance, enzyme, mod. and labelling settings" + if (is_sdrf.toString().toLowerCase().contains("false")) { + log.info("No SDRF given. Using parameters to determine tolerance, enzyme, mod. and labelling settings") } - ch_config.splitCsv(header: true, sep: '\t') - .map { create_meta_channel(it, is_sdrf, enzymes, files, wrapper) } - .branch { - ch_meta_config_dia: it[0].acquisition_method.contains("dia") - ch_meta_config_iso: it[0].labelling_type.contains("tmt") || it[0].labelling_type.contains("itraq") - ch_meta_config_lfq: it[0].labelling_type.contains("label free") - } - .set{result} + ch_config + .splitCsv(header: true, sep: '\t') + .map { create_meta_channel(it, is_sdrf, enzymes, files, wrapper) } + .branch { + ch_meta_config_dia: it[0].acquisition_method.contains("dia") + ch_meta_config_iso: it[0].labelling_type.contains("tmt") || it[0].labelling_type.contains("itraq") + ch_meta_config_lfq: it[0].labelling_type.contains("label free") + } + .set { result } ch_meta_config_iso = result.ch_meta_config_iso ch_meta_config_lfq = result.ch_meta_config_lfq ch_meta_config_dia = result.ch_meta_config_dia emit: - ch_meta_config_iso // [meta, [spectra_files ]] - ch_meta_config_lfq // [meta, [spectra_files ]] - ch_meta_config_dia // [meta, [spectra files ]] + ch_meta_config_iso // [meta, [spectra_files ]] + ch_meta_config_lfq // [meta, [spectra_files ]] + ch_meta_config_dia // [meta, [spectra files ]] ch_expdesign - versions = ch_versions + versions = ch_versions } // Function to get list of [meta, [ spectra_files ]] def create_meta_channel(LinkedHashMap row, is_sdrf, enzymes, files, wrapper) { def meta = [:] - def array = [] + def filestr if (is_sdrf.toString().toLowerCase().contains("false")) { - filestr = row.Spectra_Filepath.toString() - } else { + filestr = row.Spectra_Filepath.toString() + } + else { if (!params.root_folder) { - filestr = row.URI.toString() - } else { - filestr = row.Filename.toString() + filestr = row.URI.toString() + } + else { + filestr = row.Filename.toString() } } - meta.mzml_id = file(filestr).name.take(file(filestr).name.lastIndexOf('.')) - meta.experiment_id = file(wrapper.experiment_id.toString()).baseName + meta.mzml_id = file(filestr).name.take(file(filestr).name.lastIndexOf('.')) + meta.experiment_id = file(wrapper.experiment_id.toString()).baseName // apply transformations given by specified root_folder and type if (params.root_folder) { filestr = params.root_folder + File.separator + filestr - filestr = (params.local_input_type ? filestr.take(filestr.lastIndexOf('.')) - + '.' + params.local_input_type - : filestr) + filestr = (params.local_input_type + ? filestr.take(filestr.lastIndexOf('.')) + '.' + params.local_input_type + : filestr) } - // existance check + // existence check if (!file(filestr).exists()) { - exit 1, "ERROR: Please check input file -> File Uri does not exist!\n${filestr}" + exit(1, "ERROR: Please check input file -> File Uri does not exist!\n${filestr}") } // for sdrf read from config file, without it, read from params if (is_sdrf.toString().toLowerCase().contains("false")) { - meta.labelling_type = params.labelling_type - meta.dissociationmethod = params.ms2_fragment_method - meta.fixedmodifications = params.fixed_mods - meta.variablemodifications = params.variable_mods - meta.precursormasstolerance = params.precursor_mass_tolerance + meta.labelling_type = params.labelling_type + meta.dissociationmethod = params.ms2_fragment_method + meta.fixedmodifications = params.fixed_mods + meta.variablemodifications = params.variable_mods + meta.precursormasstolerance = params.precursor_mass_tolerance meta.precursormasstoleranceunit = params.precursor_mass_tolerance_unit - meta.fragmentmasstolerance = params.fragment_mass_tolerance - meta.fragmentmasstoleranceunit = params.fragment_mass_tolerance_unit - meta.enzyme = params.enzyme - meta.acquisition_method = params.acquisition_method - } else { + meta.fragmentmasstolerance = params.fragment_mass_tolerance + meta.fragmentmasstoleranceunit = params.fragment_mass_tolerance_unit + meta.enzyme = params.enzyme + meta.acquisition_method = params.acquisition_method + } + else { if (row["Proteomics Data Acquisition Method"].toString().toLowerCase().contains("data-dependent acquisition")) { meta.acquisition_method = "dda" - } else if (row["Proteomics Data Acquisition Method"].toString().toLowerCase().contains("data-independent acquisition")){ + } + else if (row["Proteomics Data Acquisition Method"].toString().toLowerCase().contains("data-independent acquisition")) { meta.acquisition_method = "dia" - } else { - log.error "Currently DIA and DDA are supported for the pipeline. Check and Fix your SDRF." - exit 1 + } + else { + log.error("Currently DIA and DDA are supported for the pipeline. Check and Fix your SDRF.") + exit(1) } // dissociation method conversion - if (row.DissociationMethod == "COLLISION-INDUCED DISSOCIATION"){ - meta.dissociationmethod = "CID" - } else if (row.DissociationMethod == "HIGHER ENERGY BEAM-TYPE COLLISION-INDUCED DISSOCIATION"){ - meta.dissociationmethod = "HCD" - } else if (row.DissociationMethod == "ELECTRON TRANSFER DISSOCIATION"){ - meta.dissociationmethod = "ETD" - } else if (row.DissociationMethod == "ELECTRON CAPTURE DISSOCIATION"){ - meta.dissociationmethod = "ECD" - } else { - meta.dissociationmethod = row.DissociationMethod + if (row.DissociationMethod == "COLLISION-INDUCED DISSOCIATION") { + meta.dissociationmethod = "CID" + } + else if (row.DissociationMethod == "HIGHER ENERGY BEAM-TYPE COLLISION-INDUCED DISSOCIATION") { + meta.dissociationmethod = "HCD" + } + else if (row.DissociationMethod == "ELECTRON TRANSFER DISSOCIATION") { + meta.dissociationmethod = "ETD" + } + else if (row.DissociationMethod == "ELECTRON CAPTURE DISSOCIATION") { + meta.dissociationmethod = "ECD" + } + else { + meta.dissociationmethod = row.DissociationMethod } - wrapper.acquisition_method = meta.acquisition_method - meta.labelling_type = row.Label - meta.fixedmodifications = row.FixedModifications - meta.variablemodifications = row.VariableModifications - meta.precursormasstolerance = Double.parseDouble(row.PrecursorMassTolerance) + wrapper.acquisition_method = meta.acquisition_method + meta.labelling_type = row.Label + meta.fixedmodifications = row.FixedModifications + meta.variablemodifications = row.VariableModifications + meta.precursormasstolerance = Double.parseDouble(row.PrecursorMassTolerance) meta.precursormasstoleranceunit = row.PrecursorMassToleranceUnit - meta.fragmentmasstolerance = Double.parseDouble(row.FragmentMassTolerance) - meta.fragmentmasstoleranceunit = row.FragmentMassToleranceUnit - meta.enzyme = row.Enzyme + meta.fragmentmasstolerance = Double.parseDouble(row.FragmentMassTolerance) + meta.fragmentmasstoleranceunit = row.FragmentMassToleranceUnit + meta.enzyme = row.Enzyme enzymes += row.Enzyme if (enzymes.size() > 1) { - log.error "Currently only one enzyme is supported for the whole experiment. Specified was '${enzymes}'. Check or split your SDRF." - log.error filestr - exit 1 + log.error("Currently only one enzyme is supported for the whole experiment. Specified was '${enzymes}'. Check or split your SDRF.") + log.error(filestr) + exit(1) } } - // Nothing to determing for dia. Only LFQ allowed there. + // Nothing to determine for dia. Only LFQ allowed there. if (!meta.acquisition_method.equals("dia")) { if (wrapper.labelling_type.equals("")) { if (meta.labelling_type.contains("tmt") || meta.labelling_type.contains("itraq") || meta.labelling_type.contains("label free")) { wrapper.labelling_type = meta.labelling_type - } else { - log.error "Unsupported quantification type '${meta.labelling_type}'." - exit 1 } - } else { + else { + log.error("Unsupported quantification type '${meta.labelling_type}'.") + exit(1) + } + } + else { if (meta.labelling_type != wrapper.labelling_type) { - log.error "Currently, only one label type per design is supported: was '${wrapper.labelling_type}', now is '${meta.labelling_type}'." - exit 1 + log.error("Currently, only one label type per design is supported: was '${wrapper.labelling_type}', now is '${meta.labelling_type}'.") + exit(1) } } } if (wrapper.labelling_type.contains("label free") || meta.acquisition_method == "dia") { if (filestr in files) { - log.error "Currently only one search engine setting/DIA-NN setting per file is supported for the whole experiment. ${filestr} has multiple entries in your SDRF. Maybe you have a (isobaric) labelled experiment? Otherwise, consider splitting your design into multiple experiments." - exit 1 + log.error("Currently only one search engine setting/DIA-NN setting per file is supported for the whole experiment. ${filestr} has multiple entries in your SDRF. Maybe you have a (isobaric) labelled experiment? Otherwise, consider splitting your design into multiple experiments.") + exit(1) } files += filestr } diff --git a/subworkflows/local/dda_id/main.nf b/subworkflows/local/dda_id/main.nf index 211734b40..eb07c3b41 100644 --- a/subworkflows/local/dda_id/main.nf +++ b/subworkflows/local/dda_id/main.nf @@ -3,13 +3,9 @@ // include { CONSENSUSID } from '../../../modules/local/openms/consensusid/main' include { PERCOLATOR } from '../../../modules/local/openms/percolator/main' -include { ID_MERGER } from '../../../modules/local/openms/id_merger/main' include { ID_RIPPER } from '../../../modules/local/openms/id_ripper/main' include { PSM_CONVERSION } from '../../../modules/local/utils/psm_conversion/main' -include { MSRESCORE_FEATURES } from '../../../modules/local/utils/msrescore_features/main' -include { GET_SAMPLE } from '../../../modules/local/utils/extract_sample/main' -include { SPECTRUM_FEATURES } from '../../../modules/local/utils/spectrum_features/main' -include { PSM_CLEAN } from '../../../modules/local/utils/psm_clean/main' +include { PHOSPHO_SCORING } from '../phospho_scoring/main' // // SUBWORKFLOW: Consisting of a mix of local and nf-core/modules @@ -33,10 +29,11 @@ workflow DDA_ID { // PEPTIDE_DATABASE_SEARCH ( ch_file_preparation_results, - ch_database_wdecoy + ch_database_wdecoy, + ch_expdesign ) - ch_software_versions = ch_software_versions.mix(PEPTIDE_DATABASE_SEARCH.out.versions.ifEmpty(null)) - ch_id_files = PEPTIDE_DATABASE_SEARCH.out.ch_id_files_idx + ch_software_versions = ch_software_versions.mix(PEPTIDE_DATABASE_SEARCH.out.versions) + ch_id_files_feats = PEPTIDE_DATABASE_SEARCH.out.ch_id_files_idx ch_pmultiqc_consensus = Channel.empty() ch_pmultiqc_ids = Channel.empty() @@ -45,102 +42,22 @@ workflow DDA_ID { // SUBWORKFLOW: Rescoring // if (params.skip_rescoring == false) { - - if (params.ms2rescore == true) { - MSRESCORE_FEATURES(ch_id_files.combine(ch_file_preparation_results, by: 0)) - ch_software_versions = ch_software_versions.mix(MSRESCORE_FEATURES.out.versions) - ch_id_files_feats = MSRESCORE_FEATURES.out.idxml - } else { - PSM_CLEAN(ch_id_files.combine(ch_file_preparation_results, by: 0)) - ch_id_files_feats = PSM_CLEAN.out.idxml - ch_software_versions = ch_software_versions.mix(PSM_CLEAN.out.versions) - } - - // Add SNR features to percolator - if (params.add_snr_feature_percolator) { - SPECTRUM_FEATURES(ch_id_files_feats.combine(ch_file_preparation_results, by: 0)) - ch_id_files_feats = SPECTRUM_FEATURES.out.id_files_snr - ch_software_versions = ch_software_versions.mix(SPECTRUM_FEATURES.out.versions) - } - // Rescoring for independent run, Sample or whole experiments - if (params.rescore_range == "independent_run") { + if (params.ms2features_range == "independent_run") { PERCOLATOR(ch_id_files_feats) ch_software_versions = ch_software_versions.mix(PERCOLATOR.out.versions) ch_consensus_input = PERCOLATOR.out.id_files_perc - } else if (params.rescore_range == "by_sample") { - // Sample map - GET_SAMPLE(ch_expdesign) - ch_software_versions = ch_software_versions.mix(GET_SAMPLE.out.versions) - - ch_expdesign_sample = GET_SAMPLE.out.ch_expdesign_sample - ch_expdesign_sample.splitCsv(header: true, sep: '\t') - .map { get_sample_map(it) }.set{ sample_map_idv } - - ch_id_files_feats.map {[it[0].mzml_id, it[0], it[1]]} - .combine(sample_map_idv, by: 0) - .map {[it[1], it[2], it[3]]} - .set{ch_id_files_feats_sample} - - // Group by search_engines and sample - ch_id_files_feats_sample.branch{ meta, filename, sample -> - sage: filename.name.contains('sage') - return [meta, filename, sample] - msgf: filename.name.contains('msgf') - return [meta, filename, sample] - comet: filename.name.contains('comet') - return [meta, filename, sample] - }.set{ch_id_files_feat_branched} - - // IDMERGER for samples group - ID_MERGER(ch_id_files_feat_branched.comet.groupTuple(by: 2) - .mix(ch_id_files_feat_branched.msgf.groupTuple(by: 2)) - .mix(ch_id_files_feat_branched.sage.groupTuple(by: 2))) - ch_software_versions = ch_software_versions.mix(ID_MERGER.out.versions) - - PERCOLATOR(ID_MERGER.out.id_merged) - ch_software_versions = ch_software_versions.mix(PERCOLATOR.out.versions) - - // Currently only ID runs on exactly one mzML file are supported in CONSENSUSID. Split idXML by runs - ID_RIPPER(PERCOLATOR.out.id_files_perc) - ch_file_preparation_results.map{[it[0].mzml_id, it[0]]}.set{meta} - ID_RIPPER.out.id_rippers.flatten().map { add_file_prefix (it)}.set{id_rippers} - meta.combine(id_rippers, by: 0) - .map{ [it[1], it[2], "MS:1001491"]} - .set{ ch_consensus_input } - ch_software_versions = ch_software_versions.mix(ID_RIPPER.out.versions) - - } else if (params.rescore_range == "by_project"){ - ch_id_files_feats.map {[it[0].experiment_id, it[0], it[1]]}.set { ch_id_files_feats} - - // Split ch_id_files_feats by search_engines - ch_id_files_feats.branch{ experiment_id, meta, filename -> - sage: filename.name.contains('sage') - return [meta, filename, experiment_id] - msgf: filename.name.contains('msgf') - return [meta, filename, experiment_id] - comet: filename.name.contains('comet') - return [meta, filename, experiment_id] - }.set{ch_id_files_feat_branched} - - // IDMERGER for whole experiments - ID_MERGER(ch_id_files_feat_branched.comet.groupTuple(by: 2) - .mix(ch_id_files_feat_branched.msgf.groupTuple(by: 2)) - .mix(ch_id_files_feat_branched.sage.groupTuple(by: 2))) - ch_software_versions = ch_software_versions.mix(ID_MERGER.out.versions) - - PERCOLATOR(ID_MERGER.out.id_merged) + } else { + PERCOLATOR(ch_id_files_feats) ch_software_versions = ch_software_versions.mix(PERCOLATOR.out.versions) - // Currently only ID runs on exactly one mzML file are supported in CONSENSUSID. Split idXML by runs ID_RIPPER(PERCOLATOR.out.id_files_perc) ch_file_preparation_results.map{[it[0].mzml_id, it[0]]}.set{meta} ID_RIPPER.out.id_rippers.flatten().map { add_file_prefix (it)}.set{id_rippers} meta.combine(id_rippers, by: 0) - .map{ [it[1], it[2], "MS:1001491"]} + .map{ [it[1], it[2]]} .set{ ch_consensus_input } ch_software_versions = ch_software_versions.mix(ID_RIPPER.out.versions) - } ch_rescoring_results = ch_consensus_input @@ -153,7 +70,7 @@ workflow DDA_ID { // see comments in id.nf if (params.search_engines.tokenize(",").unique().size() > 1) { CONSENSUSID(ch_consensus_input.groupTuple(size: params.search_engines.tokenize(",").unique().size())) - ch_software_versions = ch_software_versions.mix(CONSENSUSID.out.versions.ifEmpty(null)) + ch_software_versions = ch_software_versions.mix(CONSENSUSID.out.versions) ch_psmfdrcontrol = CONSENSUSID.out.consensusids ch_psmfdrcontrol .map { it -> it[1] } @@ -163,12 +80,12 @@ workflow DDA_ID { } PSM_FDR_CONTROL(ch_psmfdrcontrol) - ch_software_versions = ch_software_versions.mix(PSM_FDR_CONTROL.out.versions.ifEmpty(null)) + ch_software_versions = ch_software_versions.mix(PSM_FDR_CONTROL.out.versions) if (params.enable_mod_localization) { PHOSPHO_SCORING(ch_file_preparation_results, PSM_FDR_CONTROL.out.id_filtered) ch_software_versions = ch_software_versions.mix(PHOSPHO_SCORING.out.versions.ifEmpty(null)) - ch_id_results = PHOSPHO_SCORING.out.id_luciphor + ch_id_results = PHOSPHO_SCORING.out.id_onsite } else { ch_id_results = PSM_FDR_CONTROL.out.id_filtered } @@ -181,7 +98,7 @@ workflow DDA_ID { .map { it -> it[1] } .set { ch_pmultiqc_ids } } else { - PSM_CONVERSION(ch_id_files.combine(ch_ms2_statistics, by: 0)) + PSM_CONVERSION(ch_id_files_feats.combine(ch_ms2_statistics, by: 0)) } @@ -203,15 +120,3 @@ def add_file_prefix(file_path) { file_name = file(file_path).name.take(position) return [file_name, file_path] } - -// Function to get sample map -def get_sample_map(LinkedHashMap row) { - def sample_map = [:] - - filestr = row.Spectra_Filepath - file_name = file(filestr).name.take(file(filestr).name.lastIndexOf('.')) - sample = row.Sample - - return [file_name, sample] - -} diff --git a/subworkflows/local/file_preparation/main.nf b/subworkflows/local/file_preparation/main.nf index a97c2eba9..c33bf16fb 100644 --- a/subworkflows/local/file_preparation/main.nf +++ b/subworkflows/local/file_preparation/main.nf @@ -2,7 +2,7 @@ // Raw file conversion and mzml indexing // -include { THERMORAWFILEPARSER } from '../../../modules/local/thermorawfileparser/main' +include { THERMORAWFILEPARSER } from '../../../modules/bigbio/thermorawfileparser/main' include { TDF2MZML } from '../../../modules/local/utils/tdf2mzml/main' include { DECOMPRESS } from '../../../modules/local/utils/decompress_dotd/main' include { MZML_INDEXING } from '../../../modules/local/openms/mzml_indexing/main' @@ -43,8 +43,26 @@ workflow FILE_PREPARATION { raw: hasExtension(it[1], '.raw') mzML: hasExtension(it[1], '.mzML') dotd: hasExtension(it[1], '.d') + dia: hasExtension(it[1], '.dia') + unsupported: true }.set { ch_branched_input } + // Warn about unsupported file formats + ch_branched_input.unsupported + .collect() + .subscribe { files -> + if (files.size() > 0) { + log.warn "=" * 80 + log.warn "WARNING: ${files.size()} file(s) with unsupported format(s) detected and will be SKIPPED from processing:" + files.each { meta, file -> + log.warn " - ${file}" + } + log.warn "\nSupported formats: .raw, .mzML, .d (Bruker), .dia" + log.warn "Compressed variants (.gz, .tar, .tar.gz, .zip) are also supported." + log.warn "=" * 80 + } + } + // Note: we used to always index mzMLs if not already indexed but due to // either a bug or limitation in nextflow // peeking into a remote file consumes a lot of RAM @@ -63,13 +81,13 @@ workflow FILE_PREPARATION { THERMORAWFILEPARSER( ch_branched_input.raw ) // Output is - // {'mzmls_converted': Tuple[val(meta), path(mzml)], + // {'convert_files': Tuple[val(meta), path(mzml)], // 'version': Path(versions.yml), // 'log': Path(*.txt)} // Where meta is the same as the input meta ch_versions = ch_versions.mix(THERMORAWFILEPARSER.out.versions) - ch_results = ch_results.mix(THERMORAWFILEPARSER.out.mzmls_converted) + ch_results = ch_results.mix(THERMORAWFILEPARSER.out.convert_files) ch_results.map{ it -> [it[0], it[1]] }.set{ indexed_mzml_bundle } @@ -83,13 +101,16 @@ workflow FILE_PREPARATION { ch_results = indexed_mzml_bundle.mix(ch_branched_input.dotd) } - MZML_STATISTICS(ch_results) ch_statistics = ch_statistics.mix(MZML_STATISTICS.out.ms_statistics.collect()) ch_ms2_statistics = ch_statistics.mix(MZML_STATISTICS.out.ms2_statistics) ch_feature_statistics = ch_statistics.mix(MZML_STATISTICS.out.feature_statistics.collect()) ch_versions = ch_versions.mix(MZML_STATISTICS.out.versions) + // Pass through .dia files without conversion (DIA-NN handles them natively) + // Note: .dia files bypass peak picking and mzML statistics (when enabled) as they are only used with DIA-NN + ch_results = ch_results.mix(ch_branched_input.dia) + if (params.openms_peakpicking) { // If the peak picker is enabled, it will over-write not bypass the .d files OPENMS_PEAK_PICKER ( diff --git a/subworkflows/local/id/main.nf b/subworkflows/local/id/main.nf index 2cde42441..87acc62b0 100644 --- a/subworkflows/local/id/main.nf +++ b/subworkflows/local/id/main.nf @@ -26,15 +26,16 @@ workflow ID { // PEPTIDE_DATABASE_SEARCH ( ch_file_preparation_results, - ch_database_wdecoy + ch_database_wdecoy, + ch_expdesign ) - ch_software_versions = ch_software_versions.mix(PEPTIDE_DATABASE_SEARCH.out.versions.ifEmpty(null)) + ch_software_versions = ch_software_versions.mix(PEPTIDE_DATABASE_SEARCH.out.versions) // // SUBWORKFLOW: PSMReScoring // PSM_RESCORING (ch_file_preparation_results, PEPTIDE_DATABASE_SEARCH.out.ch_id_files_idx, ch_expdesign) - ch_software_versions = ch_software_versions.mix(PSM_RESCORING.out.versions.ifEmpty(null)) + ch_software_versions = ch_software_versions.mix(PSM_RESCORING.out.versions) // // SUBWORKFLOW: PSM_FDR_CONTROL @@ -48,7 +49,7 @@ workflow ID { // if the 'size' is not matched, an empty channel will be returned and // nothing will be run for the 'CONSENSUSID' process CONSENSUSID(PSM_RESCORING.out.results.groupTuple(size: n_unique_search_engines)) - ch_software_versions = ch_software_versions.mix(CONSENSUSID.out.versions.ifEmpty(null)) + ch_software_versions = ch_software_versions.mix(CONSENSUSID.out.versions) ch_psmfdrcontrol = CONSENSUSID.out.consensusids ch_consensus_results = CONSENSUSID.out.consensusids } else { @@ -56,7 +57,7 @@ workflow ID { } PSM_FDR_CONTROL(ch_psmfdrcontrol) - ch_software_versions = ch_software_versions.mix(PSM_FDR_CONTROL.out.versions.ifEmpty(null)) + ch_software_versions = ch_software_versions.mix(PSM_FDR_CONTROL.out.versions) // // SUBWORKFLOW:PHOSPHOSCORING @@ -64,7 +65,7 @@ workflow ID { if (params.enable_mod_localization) { PHOSPHO_SCORING(ch_file_preparation_results, PSM_FDR_CONTROL.out.id_filtered) ch_software_versions = ch_software_versions.mix(PHOSPHO_SCORING.out.versions.ifEmpty(null)) - ch_id_results = PHOSPHO_SCORING.out.id_luciphor + ch_id_results = PHOSPHO_SCORING.out.id_onsite } else { ch_id_results = PSM_FDR_CONTROL.out.id_filtered } diff --git a/subworkflows/local/peptide_database_search/main.nf b/subworkflows/local/peptide_database_search/main.nf index 924c629cd..f936a0b65 100644 --- a/subworkflows/local/peptide_database_search/main.nf +++ b/subworkflows/local/peptide_database_search/main.nf @@ -3,12 +3,18 @@ include { MSGF_DB_INDEXING } from '../../../modules/local/utils/msgf_db_indexing include { MSGF } from '../../../modules/local/openms/msgf/main' include { COMET } from '../../../modules/local/openms/comet/main' include { SAGE } from '../../../modules/local/openms/sage/main' - +include { PSM_CLEAN } from '../../../modules/local/utils/psm_clean/main' +include { MSRESCORE_FINE_TUNING} from '../../../modules/local/utils/msrescore_fine_tuning/main' +include { MSRESCORE_FEATURES } from '../../../modules/local/utils/msrescore_features/main' +include { GET_SAMPLE } from '../../../modules/local/utils/extract_sample/main' +include { SPECTRUM_FEATURES } from '../../../modules/local/utils/spectrum_features/main' +include { ID_MERGER } from '../../../modules/local/openms/id_merger/main' workflow PEPTIDE_DATABASE_SEARCH { take: ch_mzmls_search ch_searchengine_in_db + ch_expdesign main: (ch_id_msgf, ch_id_comet, ch_id_sage, ch_versions) = [ Channel.empty(), Channel.empty(), Channel.empty(), Channel.empty() ] @@ -66,7 +72,183 @@ workflow PEPTIDE_DATABASE_SEARCH { ch_id_sage = ch_id_sage.mix(SAGE.out.id_files_sage.transpose()) } + (ch_id_files_msgf_feats, ch_id_files_comet_feats, ch_id_files_sage_feats) = [ Channel.empty(), Channel.empty(), Channel.empty() ] + + if (params.skip_rescoring != true) { + + if (params.ms2features_enable == true){ + // Only add ms2_model_dir if it's actually set and not empty + // Handle cases where parameter might be empty string, null, boolean true, or whitespace + // When --ms2features_model_dir is passed with no value, Nextflow may set it to boolean true + if (params.ms2features_model_dir && params.ms2features_model_dir != true) { + ms2_model_dir = Channel.from(file(params.ms2features_model_dir, checkIfExists: true)) + } else { + // create a fake channel when don't specify model dir + ms2_model_dir = Channel.from(file("pretrained_models")) + } + + if (params.ms2features_fine_tuning == true) { + if (params.ms2features_generators.toLowerCase().contains('ms2pip')) { + error('Fine tuning only supports AlphaPeptdeep. Please set --ms2features_generators to include "alphapeptdeep" instead of "ms2pip".') + } else { + + // Preparing train datasets and fine tuning MS2 model + sage_train_datasets = ch_id_sage + .combine(ch_mzmls_search, by: 0) + .toSortedList() + .flatMap() + .randomSample(params.fine_tuning_sample_run, 2025) + .combine(Channel.value("sage")) + .groupTuple(by: 3) + + msgf_train_datasets = ch_id_msgf + .combine(ch_mzmls_search, by: 0) + .toSortedList() + .flatMap() + .randomSample(params.fine_tuning_sample_run, 2025) + .combine(Channel.value("msgf")) + .groupTuple(by: 3) + + comet_train_datasets = ch_id_comet + .combine(ch_mzmls_search, by: 0) + .toSortedList() + .flatMap() + .randomSample(params.fine_tuning_sample_run, 2025) + .combine(Channel.value("comet")) + .groupTuple(by: 3) + + sage_train_datasets.mix(msgf_train_datasets) + .mix(comet_train_datasets) + .combine(ms2_model_dir) + .set { train_datasets } + MSRESCORE_FINE_TUNING(train_datasets) + ch_versions = ch_versions.mix(MSRESCORE_FINE_TUNING.out.versions) + + Channel.value("msgf").combine(ch_id_msgf.combine(ch_mzmls_search, by: 0)) + .combine(MSRESCORE_FINE_TUNING.out.model_weight, by:0) + .map { [it[1], it[2], it[3], it[4], it[0] ] } + .set { msgf_features_input } + + Channel.value("sage").combine(ch_id_sage.combine(ch_mzmls_search, by: 0)) + .combine(MSRESCORE_FINE_TUNING.out.model_weight, by:0) + .map { [it[1], it[2], it[3], it[4], it[0] ] } + .set { sage_features_input } + + Channel.value("comet").combine(ch_id_comet.combine(ch_mzmls_search, by: 0)) + .combine(MSRESCORE_FINE_TUNING.out.model_weight, by:0) + .map { [it[1], it[2], it[3], it[4], it[0] ] } + .set { comet_features_input } + + MSRESCORE_FEATURES(msgf_features_input.mix(sage_features_input).mix(comet_features_input)) + ch_versions = ch_versions.mix(MSRESCORE_FEATURES.out.versions) + ch_id_files_feats = MSRESCORE_FEATURES.out.idxml + + + } + } else{ + ch_id_msgf.combine(ch_mzmls_search, by: 0) + .combine(ms2_model_dir) + .combine(Channel.value("msgf")).set{ ch_id_msgf } + ch_id_comet.combine(ch_mzmls_search, by: 0) + .combine(ms2_model_dir) + .combine(Channel.value("comet")).set{ ch_id_comet } + ch_id_sage.combine(ch_mzmls_search, by: 0) + .combine(ms2_model_dir) + .combine(Channel.value("sage")).set{ ch_id_sage } + + MSRESCORE_FEATURES(ch_id_msgf.mix(ch_id_comet).mix(ch_id_sage)) + ch_versions = ch_versions.mix(MSRESCORE_FEATURES.out.versions) + ch_id_files_feats = MSRESCORE_FEATURES.out.idxml + } + + // Add SNR features to percolator + if (params.ms2features_snr) { + SPECTRUM_FEATURES(ch_id_files_feats.combine(ch_mzmls_search, by: 0)) + ch_id_files_feats_snr = SPECTRUM_FEATURES.out.id_files_snr + ch_versions = ch_versions.mix(SPECTRUM_FEATURES.out.versions) + } else { + ch_id_files_feats_snr = ch_id_files_feats + } + + ch_id_files_feats_snr + .branch { meta, file_name, engine_name -> + msgf: engine_name == "msgf" + comet: engine_name == "comet" + sage: engine_name == "sage" + } + .set {ch_id_files_feats_branch} + ch_id_files_feats_branch.msgf.map {it -> [it[0], it[1]]}.set {ch_id_files_msgf_feats} + ch_id_files_feats_branch.comet.map {it -> [it[0], it[1]]}.set {ch_id_files_comet_feats} + ch_id_files_feats_branch.sage.map {it -> [it[0], it[1]]}.set {ch_id_files_sage_feats} + + } else { + ch_id_files_msgf_feats = ch_id_msgf + ch_id_files_comet_feats = ch_id_comet + ch_id_files_sage_feats = ch_id_sage + } + + if (params.ms2features_range == "by_sample") { + // Sample map + GET_SAMPLE(ch_expdesign) + ch_versions = ch_versions.mix(GET_SAMPLE.out.versions) + ch_expdesign_sample = GET_SAMPLE.out.ch_expdesign_sample + ch_expdesign_sample.splitCsv(header: true, sep: '\t') + .map { get_sample_map(it) }.set{ sample_map_idv } + + ch_id_files_msgf_feats.map {[it[0].mzml_id, it[0], it[1]]}.set { ch_id_files_msgf_feats } + ch_id_files_msgf_feats.combine(sample_map_idv, by: 0).map {[it[1], it[2], it[3]]}.set{ ch_id_files_msgf_feats } + + ch_id_files_comet_feats.map {[it[0].mzml_id, it[0], it[1]]}.set { ch_id_files_comet_feats } + ch_id_files_comet_feats.combine(sample_map_idv, by: 0).map {[it[1], it[2], it[3]]}.set{ ch_id_files_comet_feats } + + ch_id_files_sage_feats.map {[it[0].mzml_id, it[0], it[1]]}.set { ch_id_files_sage_feats } + ch_id_files_sage_feats.combine(sample_map_idv, by: 0).map {[it[1], it[2], it[3]]}.set{ ch_id_files_sage_feats } + + // ID_MERGER for samples group + ID_MERGER(ch_id_files_msgf_feats.groupTuple(by: 2) + .mix(ch_id_files_comet_feats.groupTuple(by: 2)) + .mix(ch_id_files_sage_feats.groupTuple(by: 2)) + ) + ch_versions = ch_versions.mix(ID_MERGER.out.versions) + ch_id_files_out = ID_MERGER.out.id_merged + + } else if (params.ms2features_range == "by_project") { + ch_id_files_msgf_feats.map {[it[0].experiment_id, it[0], it[1]]}.set { ch_id_files_msgf_feats } + ch_id_files_comet_feats.map {[it[0].experiment_id, it[0], it[1]]}.set { ch_id_files_comet_feats } + ch_id_files_sage_feats.map {[it[0].experiment_id, it[0], it[1]]}.set { ch_id_files_sage_feats } + + // ID_MERGER for whole experiments + ID_MERGER(ch_id_files_msgf_feats.groupTuple(by: 2) + .mix(ch_id_files_comet_feats.groupTuple(by: 2)) + .mix(ch_id_files_sage_feats.groupTuple(by: 2))) + ch_versions = ch_versions.mix(ID_MERGER.out.versions) + ch_id_files_out = ID_MERGER.out.id_merged + } else { + ch_id_files_out = ch_id_files_msgf_feats.mix(ch_id_files_comet_feats).mix(ch_id_files_sage_feats) + } + + + } else if (params.psm_clean == true) { + ch_id_files = ch_id_msgf.mix(ch_id_comet).mix(ch_id_sage) + PSM_CLEAN(ch_id_files.combine(ch_mzmls_search, by: 0)) + ch_id_files_out = PSM_CLEAN.out.idxml + ch_versions = ch_versions.mix(PSM_CLEAN.out.versions) + } else { + ch_id_files_out = ch_id_msgf.mix(ch_id_comet).mix(ch_id_sage) + } + emit: - ch_id_files_idx = ch_id_msgf.mix(ch_id_comet).mix(ch_id_sage) + ch_id_files_idx = ch_id_files_out versions = ch_versions } + +// Function to get sample map +def get_sample_map(LinkedHashMap row) { + + filestr = row.Spectra_Filepath + file_name = file(filestr).name.take(file(filestr).name.lastIndexOf('.')) + sample = row.Sample + + return [file_name, sample] + +} diff --git a/subworkflows/local/phospho_scoring/main.nf b/subworkflows/local/phospho_scoring/main.nf index a7895e534..26ac839cc 100644 --- a/subworkflows/local/phospho_scoring/main.nf +++ b/subworkflows/local/phospho_scoring/main.nf @@ -3,7 +3,7 @@ // include { ID_SCORE_SWITCHER } from '../../../modules/local/openms/id_score_switcher/main' -include { LUCIPHOR } from '../../../modules/local/openms/luciphor/main' +include { ONSITE } from '../../../modules/bigbio/onsite/main' workflow PHOSPHO_SCORING { take: @@ -15,17 +15,17 @@ workflow PHOSPHO_SCORING { if (params.search_engines.split(",").size() != 1){ ID_SCORE_SWITCHER(ch_id_files.combine(Channel.value("\"Posterior Error Probability_score\""))) ch_version = ch_version.mix(ID_SCORE_SWITCHER.out.versions) - LUCIPHOR(ch_mzml_files.join(ID_SCORE_SWITCHER.out.id_score_switcher)) - ch_version = ch_version.mix(LUCIPHOR.out.versions) + ONSITE(ch_mzml_files.join(ID_SCORE_SWITCHER.out.id_score_switcher)) + ch_version = ch_version.mix(ONSITE.out.versions) } else{ - LUCIPHOR(ch_mzml_files.join(ch_id_files)) - ch_version = ch_version.mix(LUCIPHOR.out.versions) + ONSITE(ch_mzml_files.join(ch_id_files)) + ch_version = ch_version.mix(ONSITE.out.versions) } emit: - id_luciphor = LUCIPHOR.out.ptm_in_id_luciphor + id_onsite = ONSITE.out.ptm_in_id_onsite versions = ch_version } diff --git a/subworkflows/local/phospho_scoring/meta.yml b/subworkflows/local/phospho_scoring/meta.yml index 3bcc0d354..5267d592e 100644 --- a/subworkflows/local/phospho_scoring/meta.yml +++ b/subworkflows/local/phospho_scoring/meta.yml @@ -8,7 +8,7 @@ keywords: - proteomics components: - id/score/switcher - - luciphor + - onsite input: - ch_input: type: file diff --git a/subworkflows/local/psm_fdr_control/main.nf b/subworkflows/local/psm_fdr_control/main.nf index 7e12838ea..e21a39168 100644 --- a/subworkflows/local/psm_fdr_control/main.nf +++ b/subworkflows/local/psm_fdr_control/main.nf @@ -3,7 +3,7 @@ // include { FALSE_DISCOVERY_RATE as FDR_CONSENSUSID } from '../../../modules/local/openms/false_discovery_rate/main' -include { ID_FILTER as ID_FILTER } from '../../../modules/local/openms/id_filter/main' +include { ID_FILTER } from '../../../modules/local/openms/id_filter/main' workflow PSM_FDR_CONTROL { diff --git a/subworkflows/local/psm_fdr_control/meta.yml b/subworkflows/local/psm_fdr_control/meta.yml index 286eab546..b73294ca6 100644 --- a/subworkflows/local/psm_fdr_control/meta.yml +++ b/subworkflows/local/psm_fdr_control/meta.yml @@ -7,7 +7,9 @@ keywords: - discovery - rate - proteomics -components: [] +components: + - false/discovery/rate + - id/filter input: - ch_input: type: file diff --git a/subworkflows/local/psm_rescoring/main.nf b/subworkflows/local/psm_rescoring/main.nf index 75b46d847..86cb44ba4 100644 --- a/subworkflows/local/psm_rescoring/main.nf +++ b/subworkflows/local/psm_rescoring/main.nf @@ -3,17 +3,14 @@ // include { PERCOLATOR } from '../../../modules/local/openms/percolator/main' -include { MSRESCORE_FEATURES } from '../../../modules/local/utils/msrescore_features/main' -include { GET_SAMPLE } from '../../../modules/local/utils/extract_sample/main' -include { ID_MERGER } from '../../../modules/local/openms/id_merger/main' + include { ID_RIPPER } from '../../../modules/local/openms/id_ripper/main' -include { SPECTRUM_FEATURES } from '../../../modules/local/utils/spectrum_features/main' -include { PSM_CLEAN } from '../../../modules/local/utils/psm_clean/main' + workflow PSM_RESCORING { take: ch_file_preparation_results - ch_id_files + ch_id_files_feats ch_expdesign main: @@ -21,58 +18,13 @@ workflow PSM_RESCORING { ch_results = Channel.empty() ch_fdridpep = Channel.empty() - if (params.ms2rescore == true) { - MSRESCORE_FEATURES(ch_id_files.combine(ch_file_preparation_results, by: 0)) - ch_software_versions = ch_software_versions.mix(MSRESCORE_FEATURES.out.versions) - ch_id_files_feats = MSRESCORE_FEATURES.out.idxml - } else if (params.psm_clean == true) { - PSM_CLEAN(ch_id_files.combine(ch_file_preparation_results, by: 0)) - ch_id_files_feats = PSM_CLEAN.out.idxml - ch_software_versions = ch_software_versions.mix(PSM_CLEAN.out.versions) - } else { - ch_id_files_feats = ch_id_files - } - - // Add SNR features to percolator - if (params.add_snr_feature_percolator) { - SPECTRUM_FEATURES(ch_id_files_feats.combine(ch_file_preparation_results, by: 0)) - ch_id_files_feats = SPECTRUM_FEATURES.out.id_files_snr - ch_software_versions = ch_software_versions.mix(SPECTRUM_FEATURES.out.versions) - } - // Rescoring for independent run, Sample or whole experiments - if (params.rescore_range == "independent_run") { + if (params.ms2features_range == "independent_run") { PERCOLATOR(ch_id_files_feats) ch_software_versions = ch_software_versions.mix(PERCOLATOR.out.versions) ch_consensus_input = PERCOLATOR.out.id_files_perc - } else if (params.rescore_range == "by_sample") { - // Sample map - GET_SAMPLE(ch_expdesign) - ch_software_versions = ch_software_versions.mix(GET_SAMPLE.out.versions) - ch_expdesign_sample = GET_SAMPLE.out.ch_expdesign_sample - ch_expdesign_sample.splitCsv(header: true, sep: '\t') - .map { get_sample_map(it) }.set{ sample_map_idv } - - ch_id_files_feats.map {[it[0].mzml_id, it[0], it[1]]}.set { ch_id_files_feats} - ch_id_files_feats.combine(sample_map_idv, by: 0).map {[it[1], it[2], it[3]]}.set{ch_id_files_feats} - - // Group by search_engines and convert meta - ch_id_files_feats.branch{ meta, filename, sample -> - sage: filename.name.contains('sage') - return [meta, filename, sample] - msgf: filename.name.contains('msgf') - return [meta, filename, sample] - comet: filename.name.contains('comet') - return [meta, filename, sample] - }.set{ch_id_files_feat_branched} - - // ID_MERGER for samples group - ID_MERGER(ch_id_files_feat_branched.comet.groupTuple(by: 2) - .mix(ch_id_files_feat_branched.msgf.groupTuple(by: 2)) - .mix(ch_id_files_feat_branched.sage.groupTuple(by: 2))) - ch_software_versions = ch_software_versions.mix(ID_MERGER.out.versions) - - PERCOLATOR(ID_MERGER.out.id_merged) + } else if (params.ms2features_range == "by_sample") { + PERCOLATOR(ch_id_files_feats) ch_software_versions = ch_software_versions.mix(PERCOLATOR.out.versions) // Currently only ID runs on exactly one mzML file are supported in CONSENSUSID. Split idXML by runs @@ -80,30 +32,12 @@ workflow PSM_RESCORING { ch_file_preparation_results.map{[it[0].mzml_id, it[0]]}.set{meta} ID_RIPPER.out.id_rippers.flatten().map { add_file_prefix (it)}.set{id_rippers} meta.combine(id_rippers, by: 0) - .map{ [it[1], it[2], "MS:1001491"]} + .map{ [it[1], it[2]]} .set{ ch_consensus_input } ch_software_versions = ch_software_versions.mix(ID_RIPPER.out.versions) - } else if (params.rescore_range == "by_project"){ - ch_id_files_feats.map {[it[0].experiment_id, it[0], it[1]]}.set { ch_id_files_feats} - - // Split ch_id_files_feats by search_engines - ch_id_files_feats.branch{ experiment_id, meta, filename -> - sage: filename.name.contains('sage') - return [meta, filename, experiment_id] - msgf: filename.name.contains('msgf') - return [meta, filename, experiment_id] - comet: filename.name.contains('comet') - return [meta, filename, experiment_id] - }.set{ch_id_files_feat_branched} - - // ID_MERGER for whole experiments - ID_MERGER(ch_id_files_feat_branched.comet.groupTuple(by: 2) - .mix(ch_id_files_feat_branched.msgf.groupTuple(by: 2)) - .mix(ch_id_files_feat_branched.sage.groupTuple(by: 2))) - ch_software_versions = ch_software_versions.mix(ID_MERGER.out.versions) - - PERCOLATOR(ID_MERGER.out.id_merged) + } else if (params.ms2features_range == "by_project"){ + PERCOLATOR(ch_id_files_feats) ch_software_versions = ch_software_versions.mix(PERCOLATOR.out.versions) // Currently only ID runs on exactly one mzML file are supported in CONSENSUSID. Split idXML by runs @@ -111,7 +45,7 @@ workflow PSM_RESCORING { ch_file_preparation_results.map{[it[0].mzml_id, it[0]]}.set{meta} ID_RIPPER.out.id_rippers.flatten().map { add_file_prefix (it)}.set{id_rippers} meta.combine(id_rippers, by: 0) - .map{ [it[1], it[2], "MS:1001491"]} + .map{ [it[1], it[2]]} .set{ ch_consensus_input } ch_software_versions = ch_software_versions.mix(ID_RIPPER.out.versions) } @@ -133,15 +67,3 @@ def add_file_prefix(file_path) { file_name = file(file_name).name.take(position) return [file_name, file_path] } - -// Function to get sample map -def get_sample_map(LinkedHashMap row) { - def sample_map = [:] - - filestr = row.Spectra_Filepath - file_name = file(filestr).name.take(file(filestr).name.lastIndexOf('.')) - sample = row.Sample - - return [file_name, sample] - -} diff --git a/subworkflows/local/utils_nfcore_quantms_pipeline/main.nf b/subworkflows/local/utils_nfcore_quantms_pipeline/main.nf index bd3097771..f7e6b6d8d 100644 --- a/subworkflows/local/utils_nfcore_quantms_pipeline/main.nf +++ b/subworkflows/local/utils_nfcore_quantms_pipeline/main.nf @@ -8,14 +8,11 @@ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ -include { UTILS_NFSCHEMA_PLUGIN } from '../../nf-core/utils_nfschema_plugin' +// Plugin function from nf-schema@2.5.1 (version specified in nextflow.config) 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' /* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -23,80 +20,6 @@ include { UTILS_NEXTFLOW_PIPELINE } from '../../nf-core/utils_nextflow_pipelin ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ -// ! This workflow is not used -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, - false // Conda is no longer supported - ) - - // - // 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 { - meta, fastq_1, fastq_2 -> - if (!fastq_2) { - return [ meta.id, meta + [ single_end:true ], [ fastq_1 ] ] - } else { - return [ meta.id, meta + [ single_end:false ], [ fastq_1, fastq_2 ] ] - } - } - .groupTuple() - .map { samplesheet -> - validateInputSamplesheet(samplesheet) - } - .map { - meta, fastqs -> - return [ meta, fastqs.flatten() ] - } - .set { ch_samplesheet } - - emit: - samplesheet = ch_samplesheet - versions = ch_versions -} /* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ diff --git a/subworkflows/local/utils_nfcore_quantms_pipeline/meta.yml b/subworkflows/local/utils_nfcore_quantms_pipeline/meta.yml new file mode 100644 index 000000000..99dbafcbd --- /dev/null +++ b/subworkflows/local/utils_nfcore_quantms_pipeline/meta.yml @@ -0,0 +1,32 @@ +# yaml-language-server: $schema=https://raw.githubusercontent.com/nf-core/modules/master/subworkflows/yaml-schema.json +name: "pipeline_completion" +description: Subworkflow for utils in the nf-core quantms pipeline +keywords: + - utils + - nf-core + - quantms +components: + - completionemail + - completionsummary + - imnotification + - utils_nextflow_pipeline + - utils_nfcore_pipeline + - utils_nfschema_plugin +input: + - ch_input: + type: file + description: | + Channel containing input files for FDR control +output: + - ch_fdr_results: + type: file + description: | + Channel containing FDR-controlled results + - versions: + type: file + description: | + Software versions used in this subworkflow +authors: + - "@bigbio" +maintainers: + - "@bigbio" diff --git a/subworkflows/nf-core/utils_nextflow_pipeline/tests/tags.yml b/subworkflows/nf-core/utils_nextflow_pipeline/tests/tags.yml deleted file mode 100644 index f84761125..000000000 --- a/subworkflows/nf-core/utils_nextflow_pipeline/tests/tags.yml +++ /dev/null @@ -1,2 +0,0 @@ -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 index bfd258760..2f30e9a46 100644 --- a/subworkflows/nf-core/utils_nfcore_pipeline/main.nf +++ b/subworkflows/nf-core/utils_nfcore_pipeline/main.nf @@ -98,7 +98,7 @@ def workflowVersionToYAML() { // 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())) + return ch_versions.unique().map { version -> processVersionsFromYAML(version) }.unique().mix(channel.of(workflowVersionToYAML())) } // diff --git a/subworkflows/nf-core/utils_nfcore_pipeline/tests/tags.yml b/subworkflows/nf-core/utils_nfcore_pipeline/tests/tags.yml deleted file mode 100644 index ac8523c9a..000000000 --- a/subworkflows/nf-core/utils_nfcore_pipeline/tests/tags.yml +++ /dev/null @@ -1,2 +0,0 @@ -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 index 4994303ea..acb397241 100644 --- a/subworkflows/nf-core/utils_nfschema_plugin/main.nf +++ b/subworkflows/nf-core/utils_nfschema_plugin/main.nf @@ -4,6 +4,7 @@ include { paramsSummaryLog } from 'plugin/nf-schema' include { validateParameters } from 'plugin/nf-schema' +include { paramsHelp } from 'plugin/nf-schema' workflow UTILS_NFSCHEMA_PLUGIN { @@ -15,32 +16,58 @@ workflow UTILS_NFSCHEMA_PLUGIN { // 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 + help // boolean: show help message + help_full // boolean: show full help message + show_hidden // boolean: show hidden parameters in help message + before_text // string: text to show before the help message and parameters summary + after_text // string: text to show after the help message and parameters summary + command // string: an example command of the pipeline main: + if(help || help_full) { + help_options = [ + beforeText: before_text, + afterText: after_text, + command: command, + showHidden: show_hidden, + fullHelp: help_full, + ] + if(parameters_schema) { + help_options << [parametersSchema: parameters_schema] + } + log.info paramsHelp( + help_options, + params.help instanceof String ? params.help : "", + ) + exit 0 + } + // // Print parameter summary to stdout. This will display the parameters // that differ from the default given in the JSON schema // + + summary_options = [:] if(parameters_schema) { - log.info paramsSummaryLog(input_workflow, parameters_schema:parameters_schema) - } else { - log.info paramsSummaryLog(input_workflow) + summary_options << [parametersSchema: parameters_schema] } + log.info before_text + log.info paramsSummaryLog(summary_options, input_workflow) + log.info after_text // // Validate the parameters using nextflow_schema.json or the schema // given via the validation.parametersSchema configuration option // if(validate_params) { + validateOptions = [:] if(parameters_schema) { - validateParameters(parameters_schema:parameters_schema) - } else { - validateParameters() + validateOptions << [parametersSchema: parameters_schema] } + validateParameters(validateOptions) } emit: dummy_emit = true } - diff --git a/subworkflows/nf-core/utils_nfschema_plugin/tests/main.nf.test b/subworkflows/nf-core/utils_nfschema_plugin/tests/main.nf.test index 8fb301648..c977917aa 100644 --- a/subworkflows/nf-core/utils_nfschema_plugin/tests/main.nf.test +++ b/subworkflows/nf-core/utils_nfschema_plugin/tests/main.nf.test @@ -25,6 +25,12 @@ nextflow_workflow { input[0] = workflow input[1] = validate_params input[2] = "" + input[3] = false + input[4] = false + input[5] = false + input[6] = "" + input[7] = "" + input[8] = "" """ } } @@ -51,6 +57,12 @@ nextflow_workflow { input[0] = workflow input[1] = validate_params input[2] = "" + input[3] = false + input[4] = false + input[5] = false + input[6] = "" + input[7] = "" + input[8] = "" """ } } @@ -77,6 +89,12 @@ nextflow_workflow { input[0] = workflow input[1] = validate_params input[2] = "${projectDir}/subworkflows/nf-core/utils_nfschema_plugin/tests/nextflow_schema.json" + input[3] = false + input[4] = false + input[5] = false + input[6] = "" + input[7] = "" + input[8] = "" """ } } @@ -103,6 +121,12 @@ nextflow_workflow { input[0] = workflow input[1] = validate_params input[2] = "${projectDir}/subworkflows/nf-core/utils_nfschema_plugin/tests/nextflow_schema.json" + input[3] = false + input[4] = false + input[5] = false + input[6] = "" + input[7] = "" + input[8] = "" """ } } @@ -114,4 +138,36 @@ nextflow_workflow { ) } } + + test("Should create a help message") { + + 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" + input[3] = true + input[4] = false + input[5] = false + input[6] = "Before" + input[7] = "After" + input[8] = "nextflow run test/test" + """ + } + } + + then { + assertAll( + { assert workflow.success } + ) + } + } } diff --git a/subworkflows/nf-core/utils_nfschema_plugin/tests/nextflow.config b/subworkflows/nf-core/utils_nfschema_plugin/tests/nextflow.config index 09ef842ae..8d8c73718 100644 --- a/subworkflows/nf-core/utils_nfschema_plugin/tests/nextflow.config +++ b/subworkflows/nf-core/utils_nfschema_plugin/tests/nextflow.config @@ -1,8 +1,8 @@ plugins { - id "nf-schema@2.4.2" + id "nf-schema@2.5.1" } validation { parametersSchema = "${projectDir}/subworkflows/nf-core/utils_nfschema_plugin/tests/nextflow_schema.json" monochromeLogs = true -} \ No newline at end of file +} diff --git a/tests/.nftignore b/tests/.nftignore index 158c83c59..e128a1284 100644 --- a/tests/.nftignore +++ b/tests/.nftignore @@ -1,10 +1,11 @@ .DS_Store multiqc/multiqc_data/fastqc_top_overrepresented_sequences_table.txt -multiqc/multiqc_data/BETA-multiqc.parquet +multiqc/multiqc_data/multiqc.parquet multiqc/multiqc_data/multiqc.log multiqc/multiqc_data/multiqc_data.json multiqc/multiqc_data/multiqc_sources.txt multiqc/multiqc_data/multiqc_software_versions.txt +multiqc/multiqc_data/llms-full.txt multiqc/multiqc_plots/{svg,pdf,png}/*.{svg,pdf,png} multiqc/multiqc_report.html fastqc/*_fastqc.{html,zip} diff --git a/tests/default.nf.test b/tests/default.nf.test index 5e019cda2..ee3179e5c 100644 --- a/tests/default.nf.test +++ b/tests/default.nf.test @@ -20,8 +20,6 @@ nextflow_pipeline { assertAll( { assert workflow.success}, { assert snapshot( - // Number of successful tasks - workflow.trace.succeeded().size(), // pipeline versions.yml file for multiqc from which Nextflow version is removed because we test pipelines on multiple Nextflow versions removeNextflowVersion("$outputDir/pipeline_info/nf_core_quantms_software_mqc_versions.yml"), // All stable path name, with a relative path diff --git a/workflows/dia.nf b/workflows/dia.nf index 0d9db5ec1..5ba20728f 100644 --- a/workflows/dia.nf +++ b/workflows/dia.nf @@ -50,7 +50,7 @@ workflow DIA { GENERATE_CFG(meta) ch_software_versions = ch_software_versions - .mix(GENERATE_CFG.out.versions.ifEmpty(null)) + .mix(GENERATE_CFG.out.versions) // // MODULE: SILICOLIBRARYGENERATION @@ -89,7 +89,7 @@ workflow DIA { PRELIMINARY_ANALYSIS(ch_file_preparation_results.combine(speclib)) } ch_software_versions = ch_software_versions - .mix(PRELIMINARY_ANALYSIS.out.versions.ifEmpty(null)) + .mix(PRELIMINARY_ANALYSIS.out.versions) // // MODULE: ASSEMBLE_EMPIRICAL_LIBRARY @@ -102,7 +102,7 @@ workflow DIA { speclib ) ch_software_versions = ch_software_versions - .mix(ASSEMBLE_EMPIRICAL_LIBRARY.out.versions.ifEmpty(null)) + .mix(ASSEMBLE_EMPIRICAL_LIBRARY.out.versions) indiv_fin_analysis_in = ch_file_preparation_results .combine(ch_searchdb) .combine(ASSEMBLE_EMPIRICAL_LIBRARY.out.log) @@ -116,7 +116,7 @@ workflow DIA { // INDIVIDUAL_ANALYSIS(indiv_fin_analysis_in) ch_software_versions = ch_software_versions - .mix(INDIVIDUAL_ANALYSIS.out.versions.ifEmpty(null)) + .mix(INDIVIDUAL_ANALYSIS.out.versions) // // MODULE: DIANNSUMMARY @@ -139,7 +139,7 @@ workflow DIA { ch_searchdb) ch_software_versions = ch_software_versions.mix( - FINAL_QUANTIFICATION.out.versions.ifEmpty(null) + FINAL_QUANTIFICATION.out.versions ) // @@ -156,7 +156,7 @@ workflow DIA { FINAL_QUANTIFICATION.out.versions ) ch_software_versions = ch_software_versions - .mix(CONVERT_RESULTS.out.versions.ifEmpty(null)) + .mix(CONVERT_RESULTS.out.versions) // // MODULE: MSSTATS @@ -165,7 +165,7 @@ workflow DIA { MSSTATS_LFQ(CONVERT_RESULTS.out.out_msstats) ch_msstats_out = MSSTATS_LFQ.out.msstats_csv ch_software_versions = ch_software_versions.mix( - MSSTATS_LFQ.out.versions.ifEmpty(null) + MSSTATS_LFQ.out.versions ) } diff --git a/workflows/lfq.nf b/workflows/lfq.nf index c41f5d4e0..f569fde3b 100644 --- a/workflows/lfq.nf +++ b/workflows/lfq.nf @@ -38,7 +38,7 @@ workflow LFQ { // SUBWORKFLOWS: ID // ID(ch_file_preparation_results, ch_database_wdecoy, ch_expdesign) - ch_software_versions = ch_software_versions.mix(ID.out.versions.ifEmpty(null)) + ch_software_versions = ch_software_versions.mix(ID.out.versions) // // SUBWORKFLOW: PROTEOMICSLFQ @@ -54,7 +54,7 @@ workflow LFQ { ch_expdesign, ch_database_wdecoy ) - ch_software_versions = ch_software_versions.mix(PROTEOMICSLFQ.out.versions.ifEmpty(null)) + ch_software_versions = ch_software_versions.mix(PROTEOMICSLFQ.out.versions) // // MODULE: MSSTATS @@ -63,7 +63,7 @@ workflow LFQ { if(!params.skip_post_msstats && params.quantification_method == "feature_intensity"){ MSSTATS_LFQ(PROTEOMICSLFQ.out.out_msstats) ch_msstats_out = MSSTATS_LFQ.out.msstats_csv - ch_software_versions = ch_software_versions.mix(MSSTATS_LFQ.out.versions.ifEmpty(null)) + ch_software_versions = ch_software_versions.mix(MSSTATS_LFQ.out.versions) } ID.out.psmrescoring_results diff --git a/workflows/quantms.nf b/workflows/quantms.nf index af39f6b07..62f9b5d53 100644 --- a/workflows/quantms.nf +++ b/workflows/quantms.nf @@ -4,8 +4,8 @@ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ -include { paramsSummaryMap } from 'plugin/nf-schema' -include { paramsSummaryMultiqc } from '../subworkflows/nf-core/utils_nfcore_pipeline' +include { paramsSummaryMap } from 'plugin/nf-schema' +include { paramsSummaryMultiqc } from '../subworkflows/nf-core/utils_nfcore_pipeline' include { softwareVersionsToYAML } from '../subworkflows/nf-core/utils_nfcore_pipeline' include { methodsDescriptionText } from '../subworkflows/local/utils_nfcore_quantms_pipeline' @@ -41,7 +41,7 @@ workflow QUANTMS { // // SUBWORKFLOW: Read in samplesheet, validate and stage input files // - INPUT_CHECK ( + INPUT_CHECK( file(params.input) ) ch_versions = ch_versions.mix(INPUT_CHECK.out.versions) @@ -52,28 +52,28 @@ workflow QUANTMS { // // SUBWORKFLOW: Create input channel // - CREATE_INPUT_CHANNEL ( + CREATE_INPUT_CHANNEL( INPUT_CHECK.out.ch_input_file, - INPUT_CHECK.out.is_sdrf + INPUT_CHECK.out.is_sdrf, ) - ch_versions = ch_versions.mix(CREATE_INPUT_CHANNEL.out.versions.ifEmpty(null)) + ch_versions = ch_versions.mix(CREATE_INPUT_CHANNEL.out.versions) // // SUBWORKFLOW: File preparation // - FILE_PREPARATION ( + FILE_PREPARATION( CREATE_INPUT_CHANNEL.out.ch_meta_config_iso.mix(CREATE_INPUT_CHANNEL.out.ch_meta_config_lfq).mix(CREATE_INPUT_CHANNEL.out.ch_meta_config_dia) ) - ch_versions = ch_versions.mix(FILE_PREPARATION.out.versions.ifEmpty(null)) + ch_versions = ch_versions.mix(FILE_PREPARATION.out.versions) FILE_PREPARATION.out.results - .branch { - dia: it[0].acquisition_method.contains("dia") - iso: it[0].labelling_type.contains("tmt") || it[0].labelling_type.contains("itraq") - lfq: it[0].labelling_type.contains("label free") - } - .set{ch_fileprep_result} + .branch { + dia: it[0].acquisition_method.contains("dia") + iso: it[0].labelling_type.contains("tmt") || it[0].labelling_type.contains("itraq") + lfq: it[0].labelling_type.contains("label free") + } + .set { ch_fileprep_result } // // WORKFLOW: Run main bigbio/quantms analysis pipeline based on the quantification type // @@ -85,13 +85,20 @@ workflow QUANTMS { // // MODULE: Generate decoy database // - if (params.database) { ch_db_for_decoy_creation = Channel.from(file(params.database, checkIfExists: true)) } else { exit 1, 'No protein database provided' } + if (params.database) { + ch_db_for_decoy_creation = Channel.from(file(params.database, checkIfExists: true)) + } + else { + exit(1, 'No protein database provided') + } - CREATE_INPUT_CHANNEL.out.ch_meta_config_iso.mix(CREATE_INPUT_CHANNEL.out.ch_meta_config_lfq).first() // Only run if iso or lfq have at least one file - | combine( ch_db_for_decoy_creation ) // Combine it so now the channel has elements like [potential_trigger_channel_element, actual_db], [potential_trigger_channel_element, actual_db2], etc (there should only be one DB though) - | map { it[-1] } // Remove the "trigger" part - | set {ch_db_for_decoy_creation_or_null} + CREATE_INPUT_CHANNEL.out.ch_meta_config_iso.mix( + CREATE_INPUT_CHANNEL.out.ch_meta_config_lfq + ).first() + | combine(ch_db_for_decoy_creation) + | map { it[-1] } + | set { ch_db_for_decoy_creation_or_null } ch_searchengine_in_db = params.add_decoys ? Channel.empty() : Channel.fromPath(params.database) if (params.add_decoys) { @@ -99,87 +106,109 @@ workflow QUANTMS { ch_db_for_decoy_creation_or_null ) ch_searchengine_in_db = GENERATE_DECOY_DATABASE.out.db_decoy - ch_versions = ch_versions.mix(GENERATE_DECOY_DATABASE.out.versions.ifEmpty(null)) + ch_versions = ch_versions.mix(GENERATE_DECOY_DATABASE.out.versions) } // Check that there is no duplicated search engines if (params.search_engines) { search_engines = params.search_engines.tokenize(',') if (search_engines.size() != search_engines.unique().size()) { - error( "Duplicated search engines in the search_engines parameter: ${params.search_engines}" ) + error("Duplicated search engines in the search_engines parameter: ${params.search_engines}") } } // Only performing id_only subworkflows . if (params.id_only) { - DDA_ID(FILE_PREPARATION.out.results, ch_searchengine_in_db, FILE_PREPARATION.out.ms2_statistics, CREATE_INPUT_CHANNEL.out.ch_expdesign) - ch_versions = ch_versions.mix(DDA_ID.out.versions.ifEmpty(null)) + DDA_ID( + FILE_PREPARATION.out.results, + ch_searchengine_in_db, + FILE_PREPARATION.out.ms2_statistics, + CREATE_INPUT_CHANNEL.out.ch_expdesign, + ) + ch_versions = ch_versions.mix(DDA_ID.out.versions) ch_ids_pmultiqc = ch_ids_pmultiqc.mix(DDA_ID.out.ch_pmultiqc_ids) ch_consensus_pmultiqc = ch_consensus_pmultiqc.mix(DDA_ID.out.ch_pmultiqc_consensus) - } else { - TMT(ch_fileprep_result.iso, CREATE_INPUT_CHANNEL.out.ch_expdesign, ch_searchengine_in_db) + } + else { + TMT( + ch_fileprep_result.iso, + CREATE_INPUT_CHANNEL.out.ch_expdesign, + ch_searchengine_in_db, + ) ch_ids_pmultiqc = ch_ids_pmultiqc.mix(TMT.out.ch_pmultiqc_ids) ch_consensus_pmultiqc = ch_consensus_pmultiqc.mix(TMT.out.ch_pmultiqc_consensus) ch_pipeline_results = ch_pipeline_results.mix(TMT.out.final_result) ch_msstats_in = ch_msstats_in.mix(TMT.out.msstats_in) - ch_versions = ch_versions.mix(TMT.out.versions.ifEmpty(null)) + ch_versions = ch_versions.mix(TMT.out.versions) - LFQ(ch_fileprep_result.lfq, CREATE_INPUT_CHANNEL.out.ch_expdesign, ch_searchengine_in_db) + LFQ( + ch_fileprep_result.lfq, + CREATE_INPUT_CHANNEL.out.ch_expdesign, + ch_searchengine_in_db, + ) ch_ids_pmultiqc = ch_ids_pmultiqc.mix(LFQ.out.ch_pmultiqc_ids) ch_consensus_pmultiqc = ch_consensus_pmultiqc.mix(LFQ.out.ch_pmultiqc_consensus) ch_pipeline_results = ch_pipeline_results.mix(LFQ.out.final_result) ch_msstats_in = ch_msstats_in.mix(LFQ.out.msstats_in) - ch_versions = ch_versions.mix(LFQ.out.versions.ifEmpty(null)) + ch_versions = ch_versions.mix(LFQ.out.versions) - DIA(ch_fileprep_result.dia, CREATE_INPUT_CHANNEL.out.ch_expdesign, FILE_PREPARATION.out.statistics) + DIA( + ch_fileprep_result.dia, + CREATE_INPUT_CHANNEL.out.ch_expdesign, + FILE_PREPARATION.out.statistics, + ) ch_pipeline_results = ch_pipeline_results.mix(DIA.out.diann_report) ch_pipeline_results = ch_pipeline_results.mix(DIA.out.diann_report_parquet) ch_msstats_in = ch_msstats_in.mix(DIA.out.msstats_in) - ch_versions = ch_versions.mix(DIA.out.versions.ifEmpty(null)) + ch_versions = ch_versions.mix(DIA.out.versions) } // Other subworkflow will return null when performing another subworkflow due to unknown reason. - ch_versions = ch_versions.filter{ it != null } + ch_versions = ch_versions.filter { it != null } softwareVersionsToYAML(ch_versions) .collectFile( storeDir: "${params.outdir}/pipeline_info", - name: 'nf_core_' + 'quantms_software_' + 'mqc_' + 'versions.yml', + name: 'nf_core_' + 'quantms_software_' + 'mqc_' + 'versions.yml', sort: true, - newLine: true - ).set { ch_collated_versions } - - 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.empty() - summary_params = paramsSummaryMap(workflow, parameters_schema: "nextflow_schema.json") - ch_workflow_summary = Channel.value(paramsSummaryMultiqc(summary_params)) - 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_multiqc_config) - ch_multiqc_files = ch_multiqc_files.mix(ch_workflow_summary.collectFile(name: 'workflow_summary_mqc.yaml')) - ch_multiqc_files = ch_multiqc_files.mix(FILE_PREPARATION.out.statistics) - 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: false)) - ch_multiqc_quantms_logo = file("$projectDir/assets/nf-core-quantms_logo_light.png") - - SUMMARY_PIPELINE ( - CREATE_INPUT_CHANNEL.out.ch_expdesign - .combine(ch_pipeline_results.ifEmpty([]).combine(ch_multiqc_files.collect()) - .combine(ch_ids_pmultiqc.collect().ifEmpty([])) - .combine(ch_consensus_pmultiqc.collect().ifEmpty([]))) - .combine(ch_msstats_in.ifEmpty([])), - ch_multiqc_quantms_logo + newLine: true, + ) + .set { ch_collated_versions } + + + 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() + summary_params = paramsSummaryMap(workflow, parameters_schema: "nextflow_schema.json") + ch_workflow_summary = Channel.value(paramsSummaryMultiqc(summary_params)) + 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)) + // concatenate multiqc input files + ch_multiqc_files = Channel.empty() + ch_multiqc_files = ch_multiqc_files.mix(ch_multiqc_config) + ch_multiqc_files = ch_multiqc_files.mix(ch_workflow_summary.collectFile(name: 'workflow_summary_mqc.yaml')) + ch_multiqc_files = ch_multiqc_files.mix(FILE_PREPARATION.out.statistics) + 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: false)) + ch_multiqc_quantms_logo = file("${projectDir}/assets/nf-core-quantms_logo_light.png") + + // create cross product of all inputs + multiqc_inputs = CREATE_INPUT_CHANNEL.out.ch_expdesign + .mix(ch_pipeline_results.ifEmpty([])) + .mix(ch_multiqc_files.collect()) + .mix(ch_ids_pmultiqc.collect().ifEmpty([])) + .mix(ch_consensus_pmultiqc.collect().ifEmpty([])) + .mix(ch_msstats_in.ifEmpty([])) + .collect() + + SUMMARY_PIPELINE( + multiqc_inputs, + ch_multiqc_quantms_logo, ) emit: - multiqc_report = SUMMARY_PIPELINE.out.ch_pmultiqc_report.toList() - versions = ch_versions + multiqc_report = SUMMARY_PIPELINE.out.ch_pmultiqc_report.toList() + versions = ch_versions } - -/* -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - THE END -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -*/ diff --git a/workflows/tmt.nf b/workflows/tmt.nf index 6ddc06315..97fd5a67c 100644 --- a/workflows/tmt.nf +++ b/workflows/tmt.nf @@ -38,31 +38,31 @@ workflow TMT { // SUBWORKFLOWS: ID // ID(ch_file_preparation_results, ch_database_wdecoy, ch_expdesign) - ch_software_versions = ch_software_versions.mix(ID.out.versions.ifEmpty(null)) + ch_software_versions = ch_software_versions.mix(ID.out.versions) // // SUBWORKFLOW: FEATUREMAPPER // FEATURE_MAPPER(ch_file_preparation_results, ID.out.id_results) - ch_software_versions = ch_software_versions.mix(FEATURE_MAPPER.out.versions.ifEmpty(null)) + ch_software_versions = ch_software_versions.mix(FEATURE_MAPPER.out.versions) // // MODULE: FILEMERGE // FILE_MERGE(FEATURE_MAPPER.out.id_map.collect()) - ch_software_versions = ch_software_versions.mix(FILE_MERGE.out.versions.ifEmpty(null)) + ch_software_versions = ch_software_versions.mix(FILE_MERGE.out.versions) // // SUBWORKFLOW: PROTEININFERENCE // PROTEIN_INFERENCE(FILE_MERGE.out.id_merge) - ch_software_versions = ch_software_versions.mix(PROTEIN_INFERENCE.out.versions.ifEmpty(null)) + ch_software_versions = ch_software_versions.mix(PROTEIN_INFERENCE.out.versions) // // SUBWORKFLOW: PROTEINQUANT // PROTEIN_QUANT(PROTEIN_INFERENCE.out.epi_idfilter, ch_expdesign) - ch_software_versions = ch_software_versions.mix(PROTEIN_QUANT.out.versions.ifEmpty(null)) + ch_software_versions = ch_software_versions.mix(PROTEIN_QUANT.out.versions) // // MODULE: MSSTATSTMT @@ -71,7 +71,7 @@ workflow TMT { if(!params.skip_post_msstats){ MSSTATS_TMT(PROTEIN_QUANT.out.msstats_csv) ch_msstats_out = MSSTATS_TMT.out.msstats_csv - ch_software_versions = ch_software_versions.mix(MSSTATS_TMT.out.versions.ifEmpty(null)) + ch_software_versions = ch_software_versions.mix(MSSTATS_TMT.out.versions) } ID.out.psmrescoring_results