Repository: matchms/matchms
Pull request #292 (2022-01-05 15:46:12)

add parent_mass cleaning


Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 8676c433..aaa14618 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -10,6 +10,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
 ### Changed
 
 - Spikes object now has different getitem method that allows to extract specific peaks as mz/intensity pair (or array) [#291](https://github.com/matchms/matchms/pull/291)
+- `add_parent_mass()` filter now better handles existing entries (including fields "parent_mass", "exact_mass" and "parentmass") [#292](https://github.com/matchms/matchms/pull/292)
 - minor improvement of compound name cleaning in `derive_adduct_from_name()` filter [#280](https://github.com/matchms/matchms/pull/280)
 
 ### Fixed
diff --git a/matchms/filtering/add_parent_mass.py b/matchms/filtering/add_parent_mass.py
index ac557149..fbd28d31 100644
--- a/matchms/filtering/add_parent_mass.py
+++ b/matchms/filtering/add_parent_mass.py
@@ -3,11 +3,18 @@ from ..constants import PROTON_MASS
 from ..importing import load_adducts_dict
 from ..typing import SpectrumType
 from ..utils import clean_adduct
+from ..utils import get_first_common_element
 
 
 logger = logging.getLogger("matchms")
 
 
+_default_key = "parent_mass"
+_accepted_keys = ["parentmass", "exact_mass"]
+_accepted_types = (float, str, int)
+_accepted_missing_entries = ["", "N/A", "NA", "n/a"]
+
+
 def add_parent_mass(spectrum_in: SpectrumType, estimate_from_adduct: bool = True,
                     overwrite_existing_entry: bool = False) -> SpectrumType:
     """Add estimated parent mass to metadata (if not present yet).
@@ -36,7 +43,9 @@ def add_parent_mass(spectrum_in: SpectrumType, estimate_from_adduct: bool = True
     spectrum = spectrum_in.clone()
     adducts_dict = load_adducts_dict()
 
-    if spectrum.get("parent_mass", None) and not overwrite_existing_entry:
+    parent_mass = _get_parent_mass(spectrum.metadata)
+    if parent_mass is not None and not overwrite_existing_entry:
+        spectrum.set("parent_mass", parent_mass)
         return spectrum
 
     parent_mass = None
@@ -65,6 +74,34 @@ def add_parent_mass(spectrum_in: SpectrumType, estimate_from_adduct: bool = True
     return spectrum
 
 
+def _get_parent_mass(metadata):
+    parent_mass_key = get_first_common_element([_default_key] + _accepted_keys,
+                                               metadata.keys())
+    parent_mass = metadata.get(parent_mass_key)
+    parent_mass = _convert_entry_to_num(parent_mass)
+    if parent_mass not in _accepted_missing_entries:
+        return parent_mass
+    return None
+
+
+def _convert_entry_to_num(entry):
+    """Convert precursor_mz to number if possible. Otherwise return None."""
+    if entry is None:
+        return None
+    if isinstance(entry, str) and entry in _accepted_missing_entries:
+        return None
+    if not isinstance(entry, _accepted_types):
+        logger.warning("Found parent_mass of undefined type.")
+        return None
+    if isinstance(entry, str):
+        try:
+            return float(entry.strip())
+        except ValueError:
+            logger.warning("%s can't be converted to float.", entry)
+            return None
+    return entry
+
+
 def _is_valid_charge(charge):
     return (charge is not None) and (charge != 0)
 


Test patch:
diff --git a/tests/test_add_parent_mass.py b/tests/test_add_parent_mass.py
index a7663eab..ebbb40b4 100644
--- a/tests/test_add_parent_mass.py
+++ b/tests/test_add_parent_mass.py
@@ -111,6 +111,29 @@ def test_add_parent_mass_overwrite(overwrite, expected):
         "Expected parent mass to be replaced by new value."
 
 
+@pytest.mark.parametrize("parent_mass_field, parent_mass, expected",
+                         [("parent_mass", 400., 400.),
+                          ("parent_mass", "400.", 400.),
+                          ("exact_mass", 200, 200.),
+                          ("parentmass", 200, 200.),
+                          ("parent_mass", "n/a", 442.992724)])
+def test_add_parent_mass_already_present(parent_mass_field, parent_mass, expected):
+    """Test if parent mass is correctly derived from adduct information."""
+    mz = numpy.array([], dtype='float')
+    intensities = numpy.array([], dtype='float')
+    metadata = {"precursor_mz": 444.0,
+                parent_mass_field: parent_mass,
+                "charge": +1}
+    spectrum_in = Spectrum(mz=mz,
+                           intensities=intensities,
+                           metadata=metadata)
+
+    spectrum = add_parent_mass(spectrum_in)
+
+    assert numpy.allclose(spectrum.get("parent_mass"), expected, atol=1e-4), f"Expected parent mass of about {expected}."
+    assert isinstance(spectrum.get("parent_mass"), (float, int)), "Expected parent mass to be float."
+
+
 def test_add_parent_mass_not_sufficient_data(caplog):
     """Test when there is not enough information to derive parent_mass."""
     mz = numpy.array([], dtype='float')