diff --git a/CHANGELOG.md b/CHANGELOG.md index 834980a69..6265e9619 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,12 @@ 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.8.0] bigbio/quantms - [Unreleased] + +### `Deprecations` + +- **BREAKING: Deprecated OpenMS experimental design format as input**: The pipeline now only accepts SDRF (Sample to Data Relation Format) files as input. The OpenMS experimental design format (`.tsv` without SDRF structure) is no longer supported. All input files are now treated as SDRF regardless of file extension. Supported file extensions for SDRF input are `.sdrf`, `.tsv`, and `.csv`. This change aligns with nf-core best practices and simplifies input handling for cloud storage interfaces like Seqera Datastudios and Explorer. Users must convert their OpenMS experimental design files to SDRF format. The `--labelling_type` and `--acquisition_method` parameters are no longer used for determining input file type - all information must be specified in the SDRF file. + ## [1.7.0] bigbio/quantms - [08/01/2026] - [Caracas] ### `Added` diff --git a/README.md b/README.md index 0a4be8db4..8b94bc502 100644 --- a/README.md +++ b/README.md @@ -99,6 +99,7 @@ Have a look at public datasets that were already annotated [here](https://github Those SDRFs should be ready for one-command re-analysis and you can just use the URL to the file on GitHub, e.g., `https://raw.githubusercontent.com/bigbio/proteomics-sample-metadata/master/annotated-projects/PXD000396/PXD000396.sdrf.tsv`. If 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. +The SDRF file can have `.sdrf`, `.tsv`, or `.csv` extensions. The 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=*). diff --git a/conf/tests/test_lfq.config b/conf/tests/test_lfq.config index abad4b7a9..a44ad9cd0 100644 --- a/conf/tests/test_lfq.config +++ b/conf/tests/test_lfq.config @@ -26,7 +26,7 @@ params { // Input data labelling_type = "label free sample" - input = 'https://raw.githubusercontent.com/bigbio/quantms-test-datasets/quantms/testdata/lfq_ci/BSA/BSA_design_urls.tsv' + input = 'https://raw.githubusercontent.com/bigbio/quantms-test-datasets/refs/heads/quantms/testdata/lfq_ci/BSA/BSA_design.sdrf.tsv' database = 'https://raw.githubusercontent.com/bigbio/quantms-test-datasets/quantms/testdata/lfq_ci/BSA/18Protein_SoCe_Tr_detergents_trace_target_decoy.fasta' search_engines = "comet,sage" decoy_string= "rev" diff --git a/docs/usage.md b/docs/usage.md index 2afa6e8be..d32427961 100644 --- a/docs/usage.md +++ b/docs/usage.md @@ -14,10 +14,7 @@ The typical command for running the pipeline is as follows: nextflow run bigbio/quantms --input '/url/path/to/your/experiment_design.sdrf.tsv' --database '/url/path/to/your/proteindatabase.fasta' --outdir './results' -profile docker ``` -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) +The input file must be in [Sample-to-data-relationship format (SDRF)](https://pubs.acs.org/doi/abs/10.1021/acs.jproteome.0c00376) and can have `.sdrf`, `.tsv`, or `.csv` file extensions. ### Supported file formats diff --git a/modules/local/samplesheet_check/main.nf b/modules/local/samplesheet_check/main.nf index aaef6d2af..adb97fceb 100644 --- a/modules/local/samplesheet_check/main.nf +++ b/modules/local/samplesheet_check/main.nf @@ -9,29 +9,39 @@ process SAMPLESHEET_CHECK { input: path input_file - val is_sdrf val validate_ontologies output: path "*.log", emit: log - path "${input_file}", emit: checked_file + path "*.sdrf.tsv", includeInputs: true, emit: checked_file path "versions.yml", emit: versions when: task.ext.when == null || task.ext.when - script: // This script is bundled with the pipeline, in bigbio/quantms/bin/ - // TODO validate experimental design file + script: def args = task.ext.args ?: '' def string_skip_sdrf_validation = params.validate_ontologies == false ? "--skip_sdrf_validation" : "" def string_skip_ms_validation = params.skip_ms_validation == true ? "--skip_ms_validation" : "" def string_skip_factor_validation = params.skip_factor_validation == true ? "--skip_factor_validation" : "" def string_skip_experimental_design_validation = params.skip_experimental_design_validation == true ? "--skip_experimental_design_validation" : "" def string_use_ols_cache_only = params.use_ols_cache_only == true ? "--use_ols_cache_only" : "" - def string_is_sdrf = is_sdrf == true ? "--is_sdrf" : "" """ - quantmsutilsc checksamplesheet --exp_design "${input_file}" ${string_is_sdrf} \\ + # Get basename and create output filename + BASENAME=\$(basename "${input_file}") + # Remove .sdrf.tsv, .sdrf.csv, or .sdrf extension (in that order to match longest first) + BASENAME=\$(echo "\$BASENAME" | sed -E 's/\\.sdrf\\.(tsv|csv)\$//' | sed -E 's/\\.sdrf\$//') + OUTPUT_FILE="\${BASENAME}.sdrf.tsv" + + # Convert CSV to TSV if needed using pandas + if [[ "${input_file}" == *.csv ]]; then + python -c "import pandas as pd; df = pd.read_csv('${input_file}'); df.to_csv('\$OUTPUT_FILE', sep='\\t', index=False)" + elif [[ "${input_file}" != "\$OUTPUT_FILE" ]]; then + cp "${input_file}" "\$OUTPUT_FILE" + fi + + quantmsutilsc checksamplesheet --exp_design "\$OUTPUT_FILE" --is_sdrf \\ ${string_skip_sdrf_validation} \\ ${string_skip_ms_validation} \\ ${string_skip_factor_validation} \\ diff --git a/modules/local/samplesheet_check/meta.yml b/modules/local/samplesheet_check/meta.yml index 18ec9e783..28ed5e4de 100644 --- a/modules/local/samplesheet_check/meta.yml +++ b/modules/local/samplesheet_check/meta.yml @@ -11,10 +11,7 @@ input: - meta: input_file type: file description: Input samplesheet or experimental design file - pattern: "*.{tsv,txt,csv}" - - meta: is_sdrf - type: boolean - description: Whether the input file is in SDRF format + pattern: "*.{tsv,csv,sdrf}" - meta: validate_ontologies type: boolean description: Whether to validate ontologies @@ -26,7 +23,7 @@ output: - meta: checked_file type: file description: Validated input file - pattern: "*.{tsv,txt,csv}" + pattern: "*.{sdrf.tsv}" - meta: versions type: file description: File containing software versions diff --git a/nextflow_schema.json b/nextflow_schema.json index f9f191726..5a6adabe3 100644 --- a/nextflow_schema.json +++ b/nextflow_schema.json @@ -16,10 +16,10 @@ "type": "string", "format": "file-path", "exists": true, - "mimetype": "text/tsv", - "pattern": "^\\S+\\.(?:tsv|sdrf)$", - "description": "URI/path to an [SDRF](https://github.com/bigbio/proteomics-metadata-standard/tree/master/annotated-projects) file (.sdrf.tsv) **OR** [OpenMS-style experimental design](https://abibuilder.cs.uni-tuebingen.de/archive/openms/Documentation/release/latest/html/classOpenMS_1_1ExperimentalDesign.html#details) with paths to spectra files (.tsv)", - "help_text": "Input is specified by using a path or URI to a PRIDE Sample to Data Relation Format file (SDRF), e.g. as part of a submitted and\nannotated PRIDE experiment (see [here](https://github.com/bigbio/proteomics-metadata-standard/tree/master/annotated-projects) for examples). Input files will be downloaded and cached from the URIs specified in the SDRF file.\nAn OpenMS-style experimental design will be generated based on the factor columns of the SDRF. The settings for the\nfollowing parameters will currently be overwritten by the ones specified in the SDRF:\n\n * `fixed_mods`,\n * `variable_mods`,\n * `precursor_mass_tolerance`,\n * `precursor_mass_tolerance_unit`,\n * `fragment_mass_tolerance`,\n * `fragment_mass_tolerance_unit`,\n * `fragment_method`,\n * `enzyme`\n You can also specify an [OpenMS-style experimental design](https://abibuilder.cs.uni-tuebingen.de/archive/openms/Documentation/release/latest/html/classOpenMS_1_1ExperimentalDesign.html#details) directly (.tsv ending). In this case, the aforementioned parameters have to be specified or defaults will be used.", + "mimetype": "text/csv", + "pattern": "^\\S+\\.(?:csv|tsv|sdrf)$", + "description": "URI/path to an SDRF file in SDRF format with .sdrf, .tsv, or .csv extension. For more info see help text or docs.", + "help_text": "Input is specified by using a path or URI to a PRIDE Sample to Data Relation Format file (SDRF), e.g. as part of a submitted and annotated PRIDE experiment (see [here](https://github.com/bigbio/proteomics-metadata-standard/tree/master/annotated-projects) for examples). Input files will be downloaded and cached from the URIs specified in the SDRF file.\n\nThe SDRF file can have .sdrf, .tsv, or .csv extensions. An OpenMS-style experimental design will be generated based on the factor columns of the SDRF. The settings for the following parameters will currently be overwritten by the ones specified in the SDRF:\n\n * `fixed_mods`,\n * `variable_mods`,\n * `precursor_mass_tolerance`,\n * `precursor_mass_tolerance_unit`,\n * `fragment_mass_tolerance`,\n * `fragment_mass_tolerance_unit`,\n * `fragment_method`,\n * `enzyme`", "fa_icon": "fas fa-file-csv" }, "outdir": { @@ -43,16 +43,16 @@ }, "root_folder": { "type": "string", - "description": "Root folder in which the spectrum files specified in the SDRF/design are searched", + "description": "Root folder in which the spectrum files specified in the SDRF are searched", "fa_icon": "fas fa-folder", - "help_text": "This optional parameter can be used to specify a root folder in which the spectrum files specified in the SDRF/design are searched.\nIt is usually used if you have a local version of the experiment already. Note that this option does not support recursive\nsearching yet." + "help_text": "This optional parameter can be used to specify a root folder in which the spectrum files specified in the SDRF are searched.\nIt is usually used if you have a local version of the experiment already. Note that this option does not support recursive\nsearching yet." }, "local_input_type": { "type": "string", - "description": "Overwrite the file type/extension of the filename as specified in the SDRF/design", + "description": "Overwrite the file type/extension of the filename as specified in the SDRF", "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', '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." + "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. 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", diff --git a/subworkflows/local/create_input_channel/main.nf b/subworkflows/local/create_input_channel/main.nf index b89143cce..e05eedd07 100644 --- a/subworkflows/local/create_input_channel/main.nf +++ b/subworkflows/local/create_input_channel/main.nf @@ -2,50 +2,34 @@ // Create channel for input file // include { SDRF_PARSING } from '../../../modules/local/sdrf_parsing/main' -include { PREPROCESS_EXPDESIGN } from '../../../modules/local/preprocess_expdesign' workflow CREATE_INPUT_CHANNEL { take: - ch_sdrf_or_design - is_sdrf + ch_sdrf main: ch_versions = channel.empty() - if (is_sdrf.toString().toLowerCase().contains("true")) { - 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_versions = ch_versions.mix(PREPROCESS_EXPDESIGN.out.versions) - - ch_config = PREPROCESS_EXPDESIGN.out.ch_config - ch_expdesign = PREPROCESS_EXPDESIGN.out.ch_expdesign - } + // Always parse as SDRF (OpenMS experimental design format deprecated) + SDRF_PARSING(ch_sdrf) + 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 def Set enzymes = [] def Set files = [] - // TODO remove. We can't use the variable to direct channels anyway def wrapper = [ labelling_type: "", acquisition_method: "", - experiment_id: ch_sdrf_or_design, + experiment_id: ch_sdrf, ] - 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 { row -> create_meta_channel(row, is_sdrf, enzymes, files, wrapper) } + .map { row -> create_meta_channel(row, enzymes, files, wrapper) } .branch { item -> ch_meta_config_dia: item[0].acquisition_method.contains("dia") ch_meta_config_iso: item[0].labelling_type.contains("tmt") || item[0].labelling_type.contains("itraq") @@ -65,20 +49,16 @@ workflow CREATE_INPUT_CHANNEL { } // Function to get list of [meta, [ spectra_files ]] -def create_meta_channel(LinkedHashMap row, is_sdrf, enzymes, files, wrapper) { +def create_meta_channel(LinkedHashMap row, enzymes, files, wrapper) { def meta = [:] def filestr - if (is_sdrf.toString().toLowerCase().contains("false")) { - filestr = row.Spectra_Filepath.toString() + // Always use SDRF format + if (!params.root_folder) { + filestr = row.URI.toString() } else { - if (!params.root_folder) { - filestr = row.URI.toString() - } - else { - filestr = row.Filename.toString() - } + filestr = row.Filename.toString() } meta.mzml_id = file(filestr).name.take(file(filestr).name.lastIndexOf('.')) @@ -92,72 +72,57 @@ def create_meta_channel(LinkedHashMap row, is_sdrf, enzymes, files, wrapper) { : filestr) } - - // existence check if (!file(filestr).exists()) { 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.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 + // Read metadata from SDRF config file + 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")) { + meta.acquisition_method = "dia" } 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")) { - meta.acquisition_method = "dia" - } - else { - log.error("Currently DIA and DDA are supported for the pipeline. Check and Fix your SDRF.") - exit(1) - } + 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 - } + // 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 + } - 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 - - 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) - } + 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 + + 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) } + // Nothing to determine for dia. Only LFQ allowed there. if (!meta.acquisition_method.equals("dia")) { if (wrapper.labelling_type.equals("")) { diff --git a/subworkflows/local/input_check/main.nf b/subworkflows/local/input_check/main.nf index 2f3a709d9..f4e50c731 100644 --- a/subworkflows/local/input_check/main.nf +++ b/subworkflows/local/input_check/main.nf @@ -1,31 +1,21 @@ // -// Check input sdrf and get read channels +// Check input SDRF and get read channels // include { SAMPLESHEET_CHECK } from '../../../modules/local/samplesheet_check' workflow INPUT_CHECK { take: - input_file // file: /path/to/input_file + input_file main: ch_software_versions = channel.empty() - if (input_file.toString().toLowerCase().contains("sdrf")) { - is_sdrf = true - } else { - is_sdrf = false - if (!params.labelling_type || !params.acquisition_method) { - log.error "If no SDRF was given, specifying --labelling_type and --acquisition_method is mandatory." - exit 1 - } - } - SAMPLESHEET_CHECK ( input_file, is_sdrf, params.validate_ontologies ) + SAMPLESHEET_CHECK ( input_file, params.validate_ontologies ) ch_software_versions = ch_software_versions.mix(SAMPLESHEET_CHECK.out.versions) emit: ch_input_file = SAMPLESHEET_CHECK.out.checked_file - is_sdrf = is_sdrf versions = ch_software_versions } diff --git a/subworkflows/local/input_check/meta.yml b/subworkflows/local/input_check/meta.yml index f064610b1..abe2c7f63 100644 --- a/subworkflows/local/input_check/meta.yml +++ b/subworkflows/local/input_check/meta.yml @@ -18,10 +18,6 @@ output: type: file description: | Channel containing validated input files - - is_sdrf: - type: boolean - description: | - Whether the input is in SDRF format - versions: type: file description: | diff --git a/workflows/quantms.nf b/workflows/quantms.nf index 3e7aa1898..2ac6a195f 100644 --- a/workflows/quantms.nf +++ b/workflows/quantms.nf @@ -53,8 +53,7 @@ workflow QUANTMS { // SUBWORKFLOW: Create input channel // CREATE_INPUT_CHANNEL( - INPUT_CHECK.out.ch_input_file, - INPUT_CHECK.out.is_sdrf, + INPUT_CHECK.out.ch_input_file ) ch_versions = ch_versions.mix(CREATE_INPUT_CHANNEL.out.versions)