Repository: matchms/matchms
Pull request #93 (2020-07-02 19:38:52)

Metabolomics usi
Description from @sdrogers:

Added a new method to allow a `Spectrum` object to be created from the json returned in a post request from the  metabolomics USI (https://metabolomics-usi.ucsd.edu).
The USI provides a single access point for a large number of metabolomics MS2 data resources including both experimental and library spectra.

jspaaks: PR #98

Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index cfe2d073..8de04a40 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -11,6 +11,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
 
 - Peak weighting option for cosine score and modified cosine score [#96](https://github.com/matchms/matchms/issues/96)
 - Similarity score based on comparing parent masses [#79](https://github.com/matchms/matchms/pull/79)
+- Method for instantiating a spectrum from the metabolomics USI [#93](https://github.com/matchms/matchms/pull/93)
 
 ### Changed
 
diff --git a/conda/environment-dev.yml b/conda/environment-dev.yml
index 07005f6c..6873fc55 100644
--- a/conda/environment-dev.yml
+++ b/conda/environment-dev.yml
@@ -10,6 +10,7 @@ dependencies:
   - numpy
   - pip
   - pyteomics >=4.2
+  - requests
   - python >=3.7,<3.8
   - pyyaml
   - rdkit >=2020.03.1
diff --git a/conda/environment.yml b/conda/environment.yml
index 5af9b494..69310935 100644
--- a/conda/environment.yml
+++ b/conda/environment.yml
@@ -9,6 +9,7 @@ dependencies:
   - numba >=0.47
   - numpy
   - pyteomics >=4.2
+  - requests
   - python >=3.7,<3.8
   - pyyaml
   - rdkit >=2020.03.1
diff --git a/conda/meta.yaml b/conda/meta.yaml
index 1226c1ec..bfad6561 100644
--- a/conda/meta.yaml
+++ b/conda/meta.yaml
@@ -40,6 +40,7 @@ requirements:
     - numpy
     - pip
     - pyteomics >=4.2
+    - requests
     - python >=3.7,<3.8
     - pyyaml
     - rdkit >=2020.03.1
diff --git a/matchms/importing/__init__.py b/matchms/importing/__init__.py
index ea37e299..8a4acc8d 100644
--- a/matchms/importing/__init__.py
+++ b/matchms/importing/__init__.py
@@ -1,10 +1,12 @@
 from .load_adducts import load_adducts
 from .load_from_json import load_from_json
 from .load_from_mgf import load_from_mgf
+from .load_from_usi import load_from_usi
 
 
 __all__ = [
     "load_from_json",
     "load_from_mgf",
+    "load_from_usi",
     "load_adducts"
 ]
diff --git a/matchms/importing/load_from_usi.py b/matchms/importing/load_from_usi.py
new file mode 100644
index 00000000..5ab547c3
--- /dev/null
+++ b/matchms/importing/load_from_usi.py
@@ -0,0 +1,55 @@
+import json
+import numpy as np
+import requests
+from ..Spectrum import Spectrum
+
+
+def load_from_usi(usi: str, server: str = "https://metabolomics-usi.ucsd.edu"):
+    """Load spectrum from metabolomics USI.
+
+    USI returns JSON data with keys "peaks", "n_peaks" and "precuror_mz"
+
+    .. code-block:: python
+
+        from matchms.importing import load_from_usi
+
+        spectrum = load_from_usi("mzspec:MASSBANK::accession:SM858102")
+        print(f"Found spectrum with precursor m/z of {spectrum.get("precursor_mz"):.2f}.")
+
+    Args:
+    ----
+    usi:
+        Provide the usi.
+
+    server: string
+        USI server
+    """
+
+    # Create the url
+    url = server + "/json/?usi=" + usi
+    metadata = {"usi": usi, "server": server}
+    response = requests.get(url)
+
+    if response.status_code == 404:
+        return None
+    # Extract data and create Spectrum object
+    try:
+        spectral_data = response.json()
+        if spectral_data is None or "peaks" not in spectral_data:
+            return None
+        peaks = spectral_data["peaks"]
+        if len(peaks) == 0:
+            return None
+        mz_list, intensity_list = zip(*peaks)
+        mz_array = np.array(mz_list)
+        intensity_array = np.array(intensity_list)
+
+        metadata["precursor_mz"] = spectral_data.get("precursor_mz", None)
+
+        s = Spectrum(mz_array, intensity_array, metadata)
+
+        return s
+
+    except json.decoder.JSONDecodeError:
+        # failed to unpack json
+        return None


Test patch:
diff --git a/tests/test_load_from_usi.py b/tests/test_load_from_usi.py
new file mode 100644
index 00000000..e228ec02
--- /dev/null
+++ b/tests/test_load_from_usi.py
@@ -0,0 +1,34 @@
+from unittest.mock import Mock
+from unittest.mock import patch
+import numpy as np
+from matchms import Spectrum
+from matchms.importing import load_from_usi
+
+
+@patch("requests.get")
+def test_normal(mock_get):
+    mock_get.return_value = Mock(ok=True)
+    mock_get.return_value.json.return_value = {"peaks": [[1., 2.], [3., 4.]]}
+    spec = load_from_usi("something")
+    expected_metadata = {"usi": "something", "server": "https://metabolomics-usi.ucsd.edu", "precursor_mz": None}
+    expected = Spectrum(np.array([1., 3.]), np.array([2., 4.]), expected_metadata)
+    assert spec == expected
+
+
+@patch("requests.get")
+def test_404(mock_get):
+    mock_get.return_value = Mock(ok=True)
+    mock_get.return_value.status_code = 404
+    mock_get.return_value.json.return_value = None
+    spec = load_from_usi("something")
+    expected = None
+    assert spec == expected
+
+
+@patch("requests.get")
+def test_no_peaks(mock_get):
+    mock_get.return_value = Mock(ok=True)
+    mock_get.return_value.json.return_value = {"peaks": []}
+    spec = load_from_usi("something")
+    expected = None
+    assert spec == expected