Repository: matchms/matchms
Pull request #451 (2023-06-28 07:58:42)

Require annotation
@florian-huber In addition to checking for valid_inchi, valid_inchikey, valid_smiles I also check if both the smiles and inchi match the inchikey. On the GNPS set this removes maybe a few hundred structures. I don't plan to write a repair function, since these are so little spectra and it will be hard to determine which one of the two is correct.


Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 7adce3b9..de877cd2 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -18,7 +18,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
   - added function to combine this in repair_parent_mass_match_smiles_wrapper
 - Added repair_smiles_from_compound_name [#448](https://github.com/matchms/matchms/pull/448)
 - Added require_correct_ionmode [#449](https://github.com/matchms/matchms/pull/449)
-
+- Added require_valid_annotation [#451](https://github.com/matchms/matchms/pull/451)
 ### Changed
 - Use pandas for loading adducts dict
 - Moved functions from add_parent_mass to derive_precursor_mz_and_parent_mass from
diff --git a/matchms/filtering/__init__.py b/matchms/filtering/__init__.py
index f1516deb..db36ea1b 100644
--- a/matchms/filtering/__init__.py
+++ b/matchms/filtering/__init__.py
@@ -82,6 +82,7 @@ from .require_minimum_number_of_peaks import require_minimum_number_of_peaks
 from .require_minimum_of_high_peaks import require_minimum_of_high_peaks
 from .require_precursor_below_mz import require_precursor_below_mz
 from .require_precursor_mz import require_precursor_mz
+from .require_valid_annotation import require_valid_annotation
 from .select_by_intensity import select_by_intensity
 from .select_by_mz import select_by_mz
 from .select_by_relative_intensity import select_by_relative_intensity
@@ -126,6 +127,7 @@ __all__ = [
     "repair_precursor_is_parent_mass",
     "repair_smiles_of_salts",
     "repair_smiles_from_compound_name",
+    "require_valid_annotation",
     "require_parent_mass_match_smiles",
     "require_minimum_number_of_peaks",
     "require_minimum_of_high_peaks",
diff --git a/matchms/filtering/require_valid_annotation.py b/matchms/filtering/require_valid_annotation.py
new file mode 100644
index 00000000..4e3eeb6e
--- /dev/null
+++ b/matchms/filtering/require_valid_annotation.py
@@ -0,0 +1,51 @@
+import logging
+from matchms import Spectrum
+from matchms.metadata_utils import (convert_inchi_to_inchikey,
+                                    convert_smiles_to_inchi, is_valid_inchi,
+                                    is_valid_inchikey, is_valid_smiles)
+
+
+logger = logging.getLogger("matchms")
+
+
+def require_valid_annotation(spectrum: Spectrum):
+    """Removes spectra that are not fully annotated (correct and matching, smiles, inchi and inchikey)"""
+    if spectrum is None:
+        return None
+    smiles = spectrum.get("smiles")
+    inchi = spectrum.get("inchi")
+    inchikey = spectrum.get("inchikey")
+    if not is_valid_smiles(smiles):
+        logger.info("Removed spectrum since smiles is not valid. Incorrect smiles = %s", smiles)
+        return None
+    if not is_valid_inchikey(inchikey):
+        logger.info("Removed spectrum since inchikey is not valid. Incorrect inchikey = %s", inchikey)
+        return None
+    if not is_valid_inchi(inchi):
+        logger.info("Removed spectrum since inchi is not valid. Incorrect inchi = %s", inchi)
+        return None
+    if not check_smiles_inchi_inchikey_match(spectrum):
+        return None
+    return spectrum
+
+
+def check_smiles_inchi_inchikey_match(spectrum) -> bool:
+    """Checks if a spectrum"""
+    if spectrum is None:
+        return False
+    smiles = spectrum.get("smiles")
+    inchi = spectrum.get("inchi")
+    inchikey = spectrum.get("inchikey")
+    # check if inchi matches the inchikey
+    if not inchikey[:14] == convert_inchi_to_inchikey(inchi)[:14]:
+        logger.warning("Removed spectrum since inchi and inchikey do not match. "
+                       "inchi = %s, inchikey = %s, expected_inchikey = %s",
+                       inchi, inchikey, convert_inchi_to_inchikey(inchi))
+        return False
+    # check if smiles matches the inchikey (first convert to inchi followed by converting to inchikey)
+    if not inchikey[:14] == convert_inchi_to_inchikey(convert_smiles_to_inchi(smiles))[:14]:
+        logger.warning("Removed spectrum since smiles does not match the inchikey. "
+                       "inchikey = %s, smiles = %s, expected_inchikey = %s",
+                       inchikey, smiles, convert_inchi_to_inchikey(convert_smiles_to_inchi(smiles)))
+        return False
+    return True


Test patch:
diff --git a/tests/filtering/test_require_valid_annotation.py b/tests/filtering/test_require_valid_annotation.py
new file mode 100644
index 00000000..4888bdd9
--- /dev/null
+++ b/tests/filtering/test_require_valid_annotation.py
@@ -0,0 +1,24 @@
+import pytest
+from matchms.filtering import require_valid_annotation
+from ..builder_Spectrum import SpectrumBuilder
+
+
+@pytest.mark.parametrize("smile, inchi, inchikey, correct", [
+    ("CCC", "InChI=1S/C3H8/c1-3-2/h3H2,1-2H3", "ATUOYWHBWRKTHZ-UHFFFAOYSA-N", True),
+    ("CCChello", "InChI=1S/C3H8/c1-3-2/h3H2,1-2H3", "ATUOYWHBWRKTHZ-UHFFFAOYSA-N", False),
+    ("CCC", "InChI=1S/C3H8/c1-3-2/h3H2,1-2", "ATUOYWHBWRKTHZ-UHFFFAOYSA-N", False),
+    ("CCC", "InChI=1S/C3H8/c1-3-2/h3H2,1-2H3", "ATUOYWHBWRKTHZUHFFFAOYSA-N", False),
+    ("CCC", "InChI=1S/C3H8/c1-3-2/h3H2,1-2H3", "FDHFJXKRMIVNCQ-RRNMINROSA-N", False),
+    ("CCC", "InChI=1S/C4H10/c1-3-4-2/h3-4H2,1-2H3", "ATUOYWHBWRKTHZ-UHFFFAOYSA-N", False),
+])
+def test_clean_compound_name(smile, inchi, inchikey, correct):
+    pytest.importorskip("rdkit")
+    builder = SpectrumBuilder()
+    spectrum_in = builder.with_metadata({"smiles": smile,
+                                         "inchi": inchi,
+                                         "inchikey": inchikey}).build()
+    spectrum_out = require_valid_annotation(spectrum_in)
+    if correct is True:
+        assert spectrum_out == spectrum_in
+    else:
+        assert spectrum_out is None