diff --git a/tests/__pycache__/__init__.cpython-39.pyc b/tests/__pycache__/__init__.cpython-39.pyc
deleted file mode 100644
index cc9a100..0000000
Binary files a/tests/__pycache__/__init__.cpython-39.pyc and /dev/null differ
diff --git a/tests/__pycache__/conftest.cpython-39-pytest-8.4.2.pyc b/tests/__pycache__/conftest.cpython-39-pytest-8.4.2.pyc
deleted file mode 100644
index 2c00381..0000000
Binary files a/tests/__pycache__/conftest.cpython-39-pytest-8.4.2.pyc and /dev/null differ
diff --git a/tests/data/fastas/Q99536.fasta b/tests/data/fastas/Q99536.fasta
new file mode 100644
index 0000000..400d2ae
--- /dev/null
+++ b/tests/data/fastas/Q99536.fasta
@@ -0,0 +1,8 @@
+>sp|Q99536|VAT1_HUMAN Synaptic vesicle membrane protein VAT-1 homolog OS=Homo sapiens OX=9606 GN=VAT1 PE=1 SV=2
+MSDEREVAEAATGEDASSPPPKTEAASDPQHPAASEGAAAAAASPPLLRCLVLTGFGGYD
+KVKLQSRPAAPPAPGPGQLTLRLRACGLNFADLMARQGLYDRLPPLPVTPGMEGAGVVIA
+VGEGVSDRKAGDRVMVLNRSGMWQEEVTVPSVQTFLIPEAMTFEEAAALLVNYITAYMVL
+FDFGNLQPGHSVLVHMAAGGVGMAAVQLCRTVENVTVFGTASASKHEALKENGVTHPIDY
+HTTDYVDEIKKISPKGVDIVMDPLGGSDTAKGYNLLKPMGKVVTYGMANLLTGPKRNLMA
+LARTWWNQFSVTALQLLQANRAVCGFHLGYLDGEVELVSGVVARLLALYNQGHIKPHIDS
+VWPFEKVADAMKQMQEKKNVGKVLLVPGPEKEN
diff --git a/tests/data/mzml/LQSRPAAPPAPGPGQLTLR.mzML b/tests/data/mzml/LQSRPAAPPAPGPGQLTLR.mzML
new file mode 100644
index 0000000..3dfb847
--- /dev/null
+++ b/tests/data/mzml/LQSRPAAPPAPGPGQLTLR.mzML
@@ -0,0 +1,133 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 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
+
+
+
+
+
+
diff --git a/tests/data/sage_params/sage_config.json b/tests/data/sage_params/sage_config.json
new file mode 100644
index 0000000..a875eae
--- /dev/null
+++ b/tests/data/sage_params/sage_config.json
@@ -0,0 +1,42 @@
+{
+ "database": {
+ "bucket_size": 16384,
+ "fragment_min_mz": 150.0,
+ "fragment_max_mz": 1500.0,
+ "enzyme": {
+ "missed_cleavages": 1,
+ "cleave_at": "KR",
+ "restrict": "P"
+ },
+ "static_mods": {
+ "C": 57.0216
+ },
+ "decoy_tag": "rev_",
+ "generate_decoys": true,
+ "fasta": "tests/data/fastas/Q99536.fasta"
+ },
+ "deisotope": true,
+ "chimera": false,
+ "max_fragment_charge": 1,
+ "report_psms": 1,
+ "precursor_tol": {
+ "ppm": [
+ -50,
+ 50
+ ]
+ },
+ "fragment_tol": {
+ "ppm": [
+ -10,
+ 10
+ ]
+ },
+ "isotope_errors": [
+ -1,
+ 3
+ ],
+ "mzml_paths": [
+ "tests/data/mzml/LQSRPAAPPAPGPGQLTLR.mzML"
+ ],
+ "score_type": "SageHyperScore"
+}
\ No newline at end of file
diff --git a/tests/integrationtests/wrappers/__pycache__/test_thermorawfileparser.cpython-39-pytest-8.4.2.pyc b/tests/integrationtests/wrappers/__pycache__/test_thermorawfileparser.cpython-39-pytest-8.4.2.pyc
deleted file mode 100644
index 59b1eea..0000000
Binary files a/tests/integrationtests/wrappers/__pycache__/test_thermorawfileparser.cpython-39-pytest-8.4.2.pyc and /dev/null differ
diff --git a/tests/integrationtests/wrappers/test_sage.py b/tests/integrationtests/wrappers/test_sage.py
new file mode 100644
index 0000000..ec02286
--- /dev/null
+++ b/tests/integrationtests/wrappers/test_sage.py
@@ -0,0 +1,32 @@
+"""Integration test for Sage."""
+import pandas as pd
+from pathlib import Path
+import urgap
+
+import pytest
+
+
+def test_searchdb_sage(tmp_path: Path) -> None:
+ params = urgap.UFile(uri=f"file:///{urgap._test_folder}/data/sage_params?uftype={urgap.uftypes.proteomics.params.SAGE_JSON}#sage_config.json")
+ mzml = urgap.UFile(uri=f"file:///{urgap._test_folder}/data/mzml?uftype={urgap.uftypes.ms.converter.mzml.THERMORAWPARSER_MZML}#LQSRPAAPPAPGPGQLTLR.mzML")
+ urd = urgap.URunDict(
+ {
+ "parameters": {"Sage:0.14.7": {
+ },
+ },
+ "unode_parameters": {
+ "storage_base_uri": f"file://{tmp_path}",
+ },
+ },
+ )
+ search_node = urgap.init_node("Sage:0.14.7")
+ search_result = search_node.run([params, mzml], urd)
+ assert search_result[0].path.exists() is True
+
+ # check that output is not empty
+ df = pd.read_csv(search_result[0].path, delimiter="\t")
+ assert df.shape[0] == 1
+
+ search_node.remove_output_folder(output_file=search_result[0])
+
+
diff --git a/urgap/resources/Sage/0_14_7/LICENSE b/urgap/resources/Sage/0_14_7/LICENSE
new file mode 100644
index 0000000..4a959cf
--- /dev/null
+++ b/urgap/resources/Sage/0_14_7/LICENSE
@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (c) 2022 Michael Lazear
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
\ No newline at end of file
diff --git a/urgap/resources/Sage/0_14_7/README.md b/urgap/resources/Sage/0_14_7/README.md
new file mode 100644
index 0000000..ea98cdf
--- /dev/null
+++ b/urgap/resources/Sage/0_14_7/README.md
@@ -0,0 +1,67 @@
+
+
+# Sage: proteomics searching so fast it seems like magic
+
+[](https://github.com/lazear/sage/actions/workflows/rust.yml) [](https://anaconda.org/bioconda/sage-proteomics)
+
+
+For more information please read [the online documentation!](https://sage-docs.vercel.app/docs)
+
+
+# Introduction
+
+Sage is, at it's core, a proteomics database search engine -
+ a tool that transforms raw mass spectra from proteomics experiments into peptide identifications
+ via database searching & spectral matching.
+
+However, Sage includes a variety of advanced features that make it a one-stop shop: retention time prediction, quantification (both isobaric & LFQ), peptide-spectrum match rescoring, and FDR control. You can directly use results from Sage without needing to use other tools for these tasks.
+
+Additionally, Sage was designed with cloud computing in mind - massively parallel processing and the ability to directly stream compressed mass spectrometry data to/from AWS S3 enables unprecedented search speeds with minimal cost.
+
+ Sage also runs just as well reading local files from your Mac/PC/Linux device!
+
+## Why use Sage instead of other tools?
+
+Sage is **simple to configure**, **powerful** and **flexible**.
+It also happens to be well-tested, **mind-boggingly fast**, open-source (MIT-licensed) and free.
+
+## Citation
+
+If you use Sage in a scientific publication, please cite the following paper:
+
+[Sage: An Open-Source Tool for Fast Proteomics Searching and Quantification at Scale](https://doi.org/10.1021/acs.jproteome.3c00486)
+
+
+## Features
+
+- Incredible performance out of the box
+- [Effortlessly cross-platform](https://sage-docs.vercel.app/docs/started#download-the-latest-binary-release) (Linux/MacOS/Windows), effortlessly parallel (uses all of your CPU cores)
+- [Fragment indexing strategy](https://sage-docs.vercel.app/docs/how_it_works) allows for blazing fast narrow and open searches (> 500 Da precursor tolerance)
+- [Isobaric quantification](https://sage-docs.vercel.app/docs/how_it_works#tmt-based) (MS2/MS3-TMT, or custom reporter ions)
+- [Label-free quantification](https://sage-docs.vercel.app/docs/how_it_works#label-free): consider all charge states & isotopologues *a la* FlashLFQ
+- Capable of searching for [chimeric/co-fragmenting spectra](https://sage-docs.vercel.app/docs/configuration/additional)
+- Wide-window (dynamic precursor tolerance) search mode - [enables WWA/PRM/DIA searches](https://sage-docs.vercel.app/docs/configuration/tolerance#wide-window-mode)
+- Retention time prediction models fit to each LC/MS run
+- [PSM rescoring](https://sage-docs.vercel.app/docs/how_it_works#machine-learning-for-psm-rescoring) using built-in linear discriminant analysis (LDA)
+- PEP calculation using a non-parametric model (KDE)
+- FDR calculation using target-decoy competition and picked-peptide & picked-protein approaches
+- Percolator/Mokapot [compatible output](https://sage-docs.vercel.app/docs/configuration#env)
+- Configuration by [JSON file](https://sage-docs.vercel.app/docs/configuration#file)
+- Built-in support for reading gzipped-mzML files
+- Support for reading/writing directly from [AWS S3](https://sage-docs.vercel.app/docs/configuration/aws)
+
+## Interoperability
+
+Sage is well-integrated into the open-source proteomics ecosystem. The following projects support analyzing results from Sage (typically in addition to other tools), or redistribute Sage binaries for use in their pipelines.
+
+- [SearchGUI](http://compomics.github.io/projects/searchgui): a graphical user interface for running searches
+- [PeptideShaker](http://compomics.github.io/projects/peptide-shaker): visualize peptide-spectrum matches
+- [MS2Rescore](http://compomics.github.io/projects/ms2rescore): AI-assisted rescoring of results
+- [Picked group FDR](https://github.com/kusterlab/picked_group_fdr): scalable protein group FDR for large-scale experiments
+- [sagepy](https://github.com/theGreatHerrLebert/sagepy): Python bindings to the sage-core library
+- [quantms](https://github.com/bigbio/quantms): nextflow pipeline for running searches with Sage
+- [OpenMS](https://github.com/OpenMS/OpenMS): Sage is included as a "TOPP" tool in OpenMS
+- [sager](https://github.com/UCLouvain-CBIO/sager): R package for analyzing results from Sage searches
+- If your project supports Sage and it's not listed, please open a pull request! If you need help integrating or interfacing with Sage in some way, please reach out.
+
+Check out the (now outdated) [blog post introducing the first version of Sage](https://lazear.github.io/sage/) for more information and full benchmarks!
diff --git a/urgap/resources/Sage/0_14_7/sage b/urgap/resources/Sage/0_14_7/sage
new file mode 100644
index 0000000..3983a33
Binary files /dev/null and b/urgap/resources/Sage/0_14_7/sage differ
diff --git a/urgap/resources/Sage/__init__.py b/urgap/resources/Sage/__init__.py
new file mode 100644
index 0000000..26a84ea
--- /dev/null
+++ b/urgap/resources/Sage/__init__.py
@@ -0,0 +1 @@
+"""Init Sage resources."""
diff --git a/urgap/uftypes/ms.py b/urgap/uftypes/ms.py
index a197c74..cdc4982 100644
--- a/urgap/uftypes/ms.py
+++ b/urgap/uftypes/ms.py
@@ -29,5 +29,5 @@
ms.converter.ANY = "ms.converter.ANY"
ms.converter.mzml = types.SimpleNamespace()
ms.converter.mzml.ANY = "ms.converter.mzml.ANY"
-ms.converter.mzml.THERMORAWPARSER_MZML = ".thermorawparser.mzML"
+ms.converter.mzml.THERMORAWPARSER_MZML = ".thermorawparser.mzml"
ms.converter.mzml.PYMZML_IDXGZ = ".pymzml_idx.gz"
diff --git a/urgap/uftypes/proteomics.py b/urgap/uftypes/proteomics.py
index 8bb8a1e..164f423 100644
--- a/urgap/uftypes/proteomics.py
+++ b/urgap/uftypes/proteomics.py
@@ -8,18 +8,21 @@
proteomics.MODS_XML = ".mods.xml"
proteomics.TMT_CORRECTION_FACTORS = ".tmt_correction_factors.json"
+
proteomics.dbsearch = types.SimpleNamespace()
proteomics.dbsearch.ANY = "proteomics.dbsearch.ANY"
proteomics.dbsearch.COMET_MZID = ".comet.mzid"
proteomics.dbsearch.MASCOT_DAT = ".mascot.dat"
proteomics.dbsearch.MSAMANDA_CSV = ".msamanda.csv"
proteomics.dbsearch.MSFRAGGER_TSV = ".msfragger.tsv"
-proteomics.dbsearch.MSFRAGGER_PARAMS = ".msfragger.params"
proteomics.dbsearch.MSGFPLUS_MZID = ".msgfplus.mzid"
proteomics.dbsearch.OMSSA_CSV = ".omssa.csv"
proteomics.dbsearch.XTANDEM_XML = ".xtandem.xml"
proteomics.dbsearch.DIANN_QUANT = ".diann.quant"
proteomics.dbsearch.DIANN_REPORT = ".diann_report.tsv"
+proteomics.dbsearch.SAGE_TSV = ".sage_report.tsv"
+proteomics.dbsearch.SAGE_JSON = ".sage_report.json"
+
proteomics.diannlibrary = types.SimpleNamespace()
proteomics.diannlibrary.ANY = "proteomics.diannlibrary.ANY"
@@ -61,3 +64,8 @@
proteomics.qc = types.SimpleNamespace()
proteomics.qc.ANY = "proteomics.qc.ANY"
proteomics.qc.OFFSET_CSV = ".offset.csv"
+
+proteomics.params = types.SimpleNamespace()
+proteomics.params.ANY = ".msgfplus.params.ANY"
+proteomics.params.MSGFPLUS_TXT = ".msgfplus_params.txt"
+proteomics.params.SAGE_JSON = ".sage.json"
\ No newline at end of file
diff --git a/urgap/unodes/sage/__init__.py b/urgap/unodes/sage/__init__.py
new file mode 100644
index 0000000..342ab8f
--- /dev/null
+++ b/urgap/unodes/sage/__init__.py
@@ -0,0 +1 @@
+"""Init Sage Unode."""
diff --git a/urgap/unodes/sage/sage.py b/urgap/unodes/sage/sage.py
new file mode 100644
index 0000000..6bd6bcd
--- /dev/null
+++ b/urgap/unodes/sage/sage.py
@@ -0,0 +1,119 @@
+"""Urgap sage wrapper."""
+
+
+import json
+
+import urgap
+
+
+class Sage(urgap.unode.UNodeBase):
+ """Sage wrapper for the Sage search engine.
+
+ Sage is a proteomics database search engine - a tool that transforms raw mass spectra from
+ proteomics experiments into peptide identifications via database searching & spectral matching.
+ """
+
+ META_INFO = {
+ "name": "Sage",
+ "wrapper_version": {"major": 1, "minor": 0, "patch": 0},
+ "versions": [
+ {"version": "0.14.7", "exe_path": "Sage/0_14_7/sage"},
+ ],
+ "parameters_not_triggering_rerun": [],
+ "input_uftypes": {
+ urgap.uftypes.ms.converter.mzml.THERMORAWPARSER_MZML: {"min": 0, "max": -1},
+ urgap.uftypes.proteomics.params.SAGE_JSON: {"min": 1, "max": 1},
+ urgap.uftypes.proteomics.FASTA: {"min": 0, "max": 1},
+ },
+ "output_uftypes": {
+ urgap.uftypes.proteomics.dbsearch.SAGE_TSV: {"min": 1, "max": 1},
+ urgap.uftypes.proteomics.params.SAGE_JSON: {"min": 0, "max": 1},
+
+ },
+ "engine": None,
+ "engine_type": ("identification",),
+ "citation": """Lazear, M. R. (2023). Sage: An open-source tool for fast proteomics searching and quantification at scale. Journal of Proteome Research, 22(11), 3652-3659. https://doi.org/10.1021/acs.jproteome.3c00486""",
+ }
+
+ def __init__(self, *args: object, **kwargs: object) -> None:
+ """Initialize Sage class."""
+ super().__init__(*args, **kwargs)
+
+ def preflight(self, utrace: urgap.UTrace) -> urgap.UTrace:
+ """Preflight routine for Sage wrapper.
+
+ During preflight,
+ - parameters are formatted
+ - mods are mapped and formatted
+ - param file is written
+
+ Args:
+ utrace: Combination of urun_dict, ufile_list and unode.meta.
+
+ Returns:
+ UTrace object, combination of urun_dict, ufile_list and unode.meta.
+
+ Raises:
+ ValueError: If both a parameter file and command-line parameters
+ in the urun_dict are provided. Please provide only one.
+ """
+ params_dict = utrace.urun_dict.parameters[
+ f"{self.META_INFO['name']}:{self.META_INFO['versions'][0]['version']}"
+ ]
+
+ param_files = utrace.input_files.get_path_objects_by_uftype(
+ urgap.uftypes.proteomics.params.SAGE_JSON,
+ )
+ param_file_provided = len(param_files) == 1
+ cmdline_json_provided = "-json" in params_dict
+
+
+ if param_file_provided:
+ param_json_path = param_files[0]
+ elif cmdline_json_provided:
+ param_json_path = params_dict["-json"]
+
+
+ mzml_file = utrace.input_files.get_path_objects_by_uftype(
+ urgap.uftypes.ms.converter.mzml.THERMORAWPARSER_MZML,
+ )[0]
+
+ config_dict = json.loads(param_json_path.read_text())
+ config_dict["output_directory"] = str(mzml_file.parent)
+
+ config_path = param_json_path.parent / "sage_config.json"
+ config_path.write_text(json.dumps(config_dict))
+ utrace.urun_dict.command_list = [
+ str(self.exe_path),
+ str(config_path),
+
+ ]
+ return utrace
+
+
+
+ def postflight(self, utrace: urgap.UTrace) -> urgap.UTrace:
+ """Postflight routine for Sage wrapper.
+
+ During postflight the Sage native .tsv output file is converted into the
+ pre-defined urgap output file, which is of csv format.
+
+ Args:
+ utrace: Combination of urun_dict, ufile_list and unode.meta.
+
+ Returns:
+ UTrace object, combination of urun_dict, ufile_list and unode.meta.
+ """
+ full_path = utrace.input_files.get_path_objects_by_uftype(
+ urgap.uftypes.ms.converter.mzml.THERMORAWPARSER_MZML,
+ )[0]
+ sage_tsv = full_path.parent / "results.sage.tsv"
+ self.tmp_files.append(sage_tsv)
+
+ with (
+ sage_tsv.open() as fin,
+ utrace.output_files[0].path.open("w") as fout,
+ ):
+ for line in fin:
+ fout.write(line)
+ return utrace