Repository: matchms/matchms

Issue:
don't load data from file in unit test
(copied from https://github.com/matchms/matchms/pull/73#pullrequestreview-388954200)

data file should be single spectrum since it supposed to bring to light a single error. Ideally, the datafile has only the minimum set of properties to be useful. We may not even need a data file, we could instantiate a Spectrum for this (fewer moving parts is better). For example like I did here https://github.com/matchms/matchms/blob/9fb639f43f0cf1fa242e2e5cdf0eb5e5efa0f7e7/tests/test_normalize_intensities.py#L8-L16



Discussion:
florian-huber: Thanks Juriaan for the review!

Patch:
diff --git a/.zenodo.json b/.zenodo.json
index 41fa3ac7..74921c46 100644
--- a/.zenodo.json
+++ b/.zenodo.json
@@ -54,6 +54,11 @@
          "affiliation": "University of Glasgow", 
          "name": "Villanueva Castilla, Efrain Manuel", 
          "orcid": "0000-0001-7665-3575"
+      },
+      {
+         "affiliation": "University of Glasgow", 
+         "name": "Ashouritaklimi, Kianoosh", 
+         "orcid": "0000-0001-6317-3193"
       }
    ], 
    "description": "Python library for fuzzy comparison of mass spectrum data and other Python objects", 
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 5c5a1e00..d675c629 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
 
 ## [Unreleased]
 
+### Added
+
+- Four new peak filtering functions [#119](https://github.com/matchms/matchms/pull/119)
+
 ## [0.5.2] - 2020-08-26
 
 ### Changed
diff --git a/CITATION.cff b/CITATION.cff
index 0f91c432..f0f93b30 100644
--- a/CITATION.cff
+++ b/CITATION.cff
@@ -58,6 +58,11 @@ authors:
     family-names: Villanueva Castilla
     given-names: Efrain Manuel
     orcid: "https://orcid.org/0000-0001-7665-3575"
+  -
+    affiliation: "University of Glasgow"
+    family-names: Ashouritaklimi
+    given-names: Kianoosh
+    orcid: "https://orcid.org/0000-0001-6317-3193"
 
 cff-version: "1.1.0"
 keywords:
diff --git a/matchms/filtering/__init__.py b/matchms/filtering/__init__.py
index 7f07a986..c57911f3 100644
--- a/matchms/filtering/__init__.py
+++ b/matchms/filtering/__init__.py
@@ -57,8 +57,12 @@ from .make_charge_scalar import make_charge_scalar
 from .make_ionmode_lowercase import make_ionmode_lowercase
 from .normalize_intensities import normalize_intensities
 from .reduce_to_number_of_peaks import reduce_to_number_of_peaks
+from .remove_peaks_around_precursor_mz import remove_peaks_around_precursor_mz
+from .remove_peaks_outside_top_k import remove_peaks_outside_top_k
 from .repair_inchi_inchikey_smiles import repair_inchi_inchikey_smiles
 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 .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
@@ -88,8 +92,12 @@ __all__ = [
     "make_ionmode_lowercase",
     "normalize_intensities",
     "reduce_to_number_of_peaks",
+    "remove_peaks_around_precursor_mz",
+    "remove_peaks_outside_top_k",
     "repair_inchi_inchikey_smiles",
     "require_minimum_number_of_peaks",
+    "require_minimum_of_high_peaks",
+    "require_precursor_below_mz",
     "select_by_intensity",
     "select_by_mz",
     "select_by_relative_intensity",
diff --git a/matchms/filtering/remove_peaks_around_precursor_mz.py b/matchms/filtering/remove_peaks_around_precursor_mz.py
new file mode 100644
index 00000000..cfd94f06
--- /dev/null
+++ b/matchms/filtering/remove_peaks_around_precursor_mz.py
@@ -0,0 +1,35 @@
+import numpy
+from ..Spikes import Spikes
+from ..typing import SpectrumType
+
+
+def remove_peaks_around_precursor_mz(spectrum_in: SpectrumType, mz_tolerance: float = 17) -> SpectrumType:
+
+    """Remove peaks that are within mz_tolerance (in Da) of
+       the precursor mz, exlcuding the precursor peak.
+
+    Parameters
+    ----------
+    spectrum_in:
+        Input spectrum.
+    mz_tolerance:
+        Tolerance of mz values that are not allowed to lie
+        within the precursor mz. Default is 17 Da.
+    """
+    if spectrum_in is None:
+        return None
+
+    spectrum = spectrum_in.clone()
+
+    precursor_mz = spectrum.get("precursor_mz", None)
+    assert precursor_mz is not None, "Precursor mz absent."
+    assert isinstance(precursor_mz, (float, int)), ("Expected 'precursor_mz' to be a scalar number.",
+                                                    "Consider applying 'add_precursor_mz' filter first.")
+    assert mz_tolerance >= 0, "mz_tolerance must be a positive scalar."
+
+    mzs, intensities = spectrum.peaks
+    peaks_to_remove = ((numpy.abs(precursor_mz-mzs) <= mz_tolerance) & (mzs != precursor_mz))
+    new_mzs, new_intensities = mzs[~peaks_to_remove], intensities[~peaks_to_remove]
+    spectrum.peaks = Spikes(mz=new_mzs, intensities=new_intensities)
+
+    return spectrum
diff --git a/matchms/filtering/remove_peaks_outside_top_k.py b/matchms/filtering/remove_peaks_outside_top_k.py
new file mode 100644
index 00000000..b1715d18
--- /dev/null
+++ b/matchms/filtering/remove_peaks_outside_top_k.py
@@ -0,0 +1,44 @@
+import numpy
+from ..Spikes import Spikes
+from ..typing import SpectrumType
+
+
+def remove_peaks_outside_top_k(spectrum_in: SpectrumType, k: int = 6,
+                               mz_window: float = 50) -> SpectrumType:
+
+    """Remove all peaks which are not within *mz_window* of at least one
+       of the *k* highest intensity peaks of the spectrum.
+
+    Parameters
+    ----------
+    spectrum_in:
+        Input spectrum.
+    k:
+        The number of most intense peaks to compare to. Default is 6.
+    mz_window:
+        Window of mz values (in Da) that are allowed to lie within
+        the top k peaks. Default is 50 Da.
+    """
+    if spectrum_in is None:
+        return None
+
+    spectrum = spectrum_in.clone()
+
+    assert k >= 1, "k must be a positive nonzero integer."
+    assert mz_window >= 0, "mz_window must be a positive scalar."
+    mzs, intensities = spectrum.peaks
+    top_k = intensities.argsort()[::-1][0:k]
+    k_ordered_mzs = mzs[top_k]
+    indices = [i for i in range(len(mzs)) if i not in top_k]
+    keep_idx = top_k.tolist()
+    for i in indices:
+
+        compare = abs(mzs[i]-k_ordered_mzs) <= mz_window
+        if numpy.any(compare):
+            keep_idx.append(i)
+
+    keep_idx.sort()
+    new_mzs, new_intensities = mzs[keep_idx], intensities[keep_idx]
+    spectrum.peaks = Spikes(mz=new_mzs, intensities=new_intensities)
+
+    return spectrum
diff --git a/matchms/filtering/require_minimum_of_high_peaks.py b/matchms/filtering/require_minimum_of_high_peaks.py
new file mode 100644
index 00000000..47000ee8
--- /dev/null
+++ b/matchms/filtering/require_minimum_of_high_peaks.py
@@ -0,0 +1,34 @@
+from ..typing import SpectrumType
+from .select_by_relative_intensity import select_by_relative_intensity
+
+
+def require_minimum_of_high_peaks(spectrum_in: SpectrumType, no_peaks: int = 5,
+                                  intensity_percent: float = 2.0) -> SpectrumType:
+
+    """Returns None if the number of peaks with relative intensity
+       above or equal to intensity_percent is less than no_peaks.
+
+    Parameters
+    ----------
+    spectrum_in:
+        Input spectrum.
+    no_peaks:
+        Minimum number of peaks allowed to have relative intensity
+        above intensity_percent. Less peaks will return none.
+        Default is 5.
+    intensity_percent:
+        Minimum relative intensity (as a percentage between 0-100) for
+        peaks that are searched. Default is 2
+    """
+    if spectrum_in is None:
+        return None
+
+    spectrum = spectrum_in.clone()
+
+    assert no_peaks >= 1, "no_peaks must be a positive nonzero integer."
+    assert 0 <= intensity_percent <= 100, "intensity_percent must be a scalar between 0-100."
+    intensities_above_p = select_by_relative_intensity(spectrum, intensity_from=intensity_percent/100, intensity_to=1.0)
+    if len(intensities_above_p.peaks) < no_peaks:
+        return None
+
+    return spectrum
diff --git a/matchms/filtering/require_precursor_below_mz.py b/matchms/filtering/require_precursor_below_mz.py
new file mode 100644
index 00000000..45e0f92c
--- /dev/null
+++ b/matchms/filtering/require_precursor_below_mz.py
@@ -0,0 +1,31 @@
+from ..typing import SpectrumType
+
+
+def require_precursor_below_mz(spectrum_in: SpectrumType, max_mz: float = 1000) -> SpectrumType:
+
+    """Returns None if the precursor_mz of a spectrum is above
+       max_mz.
+
+    Parameters
+    ----------
+    spectrum_in:
+        Input spectrum.
+    max_mz:
+        Maximum mz value for the precursor mz of a spectrum.
+        All precursor mz values greater or equal to this
+        will return none. Default is 1000.
+    """
+    if spectrum_in is None:
+        return None
+
+    spectrum = spectrum_in.clone()
+
+    precursor_mz = spectrum.get("precursor_mz", None)
+    assert precursor_mz is not None, "Precursor mz absent."
+    assert isinstance(precursor_mz, (float, int)), ("Expected 'precursor_mz' to be a scalar number.",
+                                                    "Consider applying 'add_precursor_mz' filter first.")
+    assert max_mz >= 0, "max_mz must be a positive scalar."
+    if precursor_mz >= max_mz:
+        return None
+
+    return spectrum


Test patch:
diff --git a/tests/test_remove_peaks_around_precursor_mz.py b/tests/test_remove_peaks_around_precursor_mz.py
new file mode 100644
index 00000000..816d2f3b
--- /dev/null
+++ b/tests/test_remove_peaks_around_precursor_mz.py
@@ -0,0 +1,73 @@
+import numpy
+import pytest
+from matchms import Spectrum
+from matchms.filtering import remove_peaks_around_precursor_mz
+
+
+def test_remove_peaks_around_precursor_mz_no_params():
+    """Using defaults with precursor mz present."""
+    mz = numpy.array([10, 20, 30, 40], dtype="float")
+    intensities = numpy.array([0, 1, 10, 100], dtype="float")
+    spectrum_in = Spectrum(mz=mz, intensities=intensities)
+    spectrum_in.set("precursor_mz", 60.)
+
+    spectrum = remove_peaks_around_precursor_mz(spectrum_in)
+
+    assert spectrum == spectrum_in, "Expected no changes."
+
+
+def test_remove_peaks_around_precursor_mz_tolerance_20():
+    """Set mz_tolerance to 20."""
+    mz = numpy.array([10, 20, 30, 40], dtype="float")
+    intensities = numpy.array([0, 1, 10, 100], dtype="float")
+    spectrum_in = Spectrum(mz=mz, intensities=intensities)
+    spectrum_in.set("precursor_mz", 60.)
+
+    spectrum = remove_peaks_around_precursor_mz(spectrum_in, mz_tolerance=20)
+
+    assert len(spectrum.peaks) == 3, "Expected 3 peaks to remain."
+    assert spectrum.peaks.mz.tolist() == [10., 20., 30.], "Expected different peaks to remain."
+
+
+def test_if_spectrum_is_cloned():
+    """Test if filter is correctly cloning the input spectrum."""
+    mz = numpy.array([], dtype="float")
+    intensities = numpy.array([], dtype="float")
+    spectrum_in = Spectrum(mz=mz, intensities=intensities)
+    spectrum_in.set("precursor_mz", 1.)
+
+    spectrum = remove_peaks_around_precursor_mz(spectrum_in)
+    spectrum.set("testfield", "test")
+
+    assert not spectrum_in.get("testfield"), "Expected input spectrum to remain unchanged."
+
+
+def test_remove_peaks_around_precursor_without_precursor_mz():
+    """Test if correct assert error is raised for missing precursor-mz."""
+    spectrum_in = Spectrum(mz=numpy.array([10, 20, 30, 40], dtype="float"),
+                           intensities=numpy.array([0, 1, 10, 100], dtype="float"),
+                           metadata={})
+
+    with pytest.raises(AssertionError) as msg:
+        _ = remove_peaks_around_precursor_mz(spectrum_in)
+
+    assert str(msg.value) == "Precursor mz absent.", "Expected different error message."
+
+
+def test_remove_peaks_around_precursor_with_wrong_precursor_mz():
+    """Test if correct assert error is raised for precursor-mz as string."""
+    spectrum_in = Spectrum(mz=numpy.array([10, 20, 30, 40], dtype="float"),
+                           intensities=numpy.array([0, 1, 10, 100], dtype="float"),
+                           metadata={"precursor_mz": "445.0"})
+
+    with pytest.raises(AssertionError) as msg:
+        _ = remove_peaks_around_precursor_mz(spectrum_in)
+
+    assert "Expected 'precursor_mz' to be a scalar number." in str(msg.value)
+
+
+def test_with_input_none():
+    """Test if input spectrum is None."""
+    spectrum_in = None
+    spectrum = remove_peaks_around_precursor_mz(spectrum_in)
+    assert spectrum is None
diff --git a/tests/test_remove_peaks_outside_top_k.py b/tests/test_remove_peaks_outside_top_k.py
new file mode 100644
index 00000000..f4cf2283
--- /dev/null
+++ b/tests/test_remove_peaks_outside_top_k.py
@@ -0,0 +1,93 @@
+import numpy
+from matchms import Spectrum
+from matchms.filtering import remove_peaks_outside_top_k
+
+
+def test_remove_peaks_outside_top_k_no_params():
+    """No parameters provided."""
+    mz = numpy.array([1, 25, 60, 70, 80, 90, 100, 110], dtype="float")
+    intensities = numpy.array([10, 25, 50, 75, 100, 125, 150, 175], dtype="float")
+    spectrum_in = Spectrum(mz=mz, intensities=intensities)
+
+    spectrum = remove_peaks_outside_top_k(spectrum_in)
+
+    assert len(spectrum.peaks) == 7, "Expected 7 peaks to remain."
+    assert spectrum.peaks.mz.tolist() == [25., 60., 70., 80., 90., 100., 110.], "Expected different peaks to remain."
+
+
+def test_remove_peaks_outside_top_k_no_params_peaks_less_than_k():
+    """Check case when the number of peaks is less than k with default parameters."""
+    mz = numpy.array([1, 25, 60, 70, 80], dtype="float")
+    intensities = numpy.array([10, 25, 50, 75, 100], dtype="float")
+    spectrum_in = Spectrum(mz=mz, intensities=intensities)
+
+    spectrum = remove_peaks_outside_top_k(spectrum_in)
+
+    assert spectrum == spectrum_in, "Expected no changes as the number of peaks (5) is less than k (6)."
+
+
+def test_remove_peaks_outside_top_k_no_params_peaks_equal_to_k():
+    """Check case when the number of peaks is equal to k with default parameters."""
+    mz = numpy.array([1, 25, 60, 70, 80, 90], dtype="float")
+    intensities = numpy.array([10, 25, 50, 75, 100, 125], dtype="float")
+    spectrum_in = Spectrum(mz=mz, intensities=intensities)
+
+    spectrum = remove_peaks_outside_top_k(spectrum_in)
+
+    assert spectrum == spectrum_in, "Expected no changes as the number of peaks (6) is equal to k (6)."
+
+
+def test_remove_peaks_outside_top_k_no_params_check_sorting():
+    """Check sorting of mzs and intensities with default parameters."""
+    mz = numpy.array([100, 270, 300, 400, 500, 600, 700, 710, 720, 1000], dtype="float")
+    intensities = numpy.array([100, 200, 50, 175, 10, 125, 150, 75, 25, 1], dtype="float")
+    spectrum_in = Spectrum(mz=mz, intensities=intensities)
+
+    spectrum = remove_peaks_outside_top_k(spectrum_in)
+
+    assert len(spectrum.peaks) == 8, "Expected 8 peaks to remain."
+    assert spectrum.peaks.mz.tolist() == [100., 270., 300., 400., 600., 700., 710., 720.], "Expected different mzs to remain."
+    assert spectrum.peaks.intensities.tolist() == [100., 200., 50., 175., 125., 150., 75., 25.], "Expected different intensities to remain."
+
+
+def test_remove_peaks_outside_top_k_3():
+    """Set k to 3."""
+    mz = numpy.array([1, 20, 30, 50, 55, 90, 100, 110], dtype="float")
+    intensities = numpy.array([10, 25, 50, 75, 100, 125, 150, 175], dtype="float")
+    spectrum_in = Spectrum(mz=mz, intensities=intensities)
+
+    spectrum = remove_peaks_outside_top_k(spectrum_in, k=3)
+
+    assert len(spectrum.peaks) == 5, "Expected 5 peaks to remain."
+    assert spectrum.peaks.mz.tolist() == [50., 55., 90., 100., 110.], "Expected different peaks to remain."
+
+
+def test_remove_peaks_outside_top_k_mz_window_10():
+    """Set mz_window to 10."""
+    mz = numpy.array([1, 10, 30, 40, 50, 60, 70, 80], dtype="float")
+    intensities = numpy.array([10, 25, 50, 75, 100, 125, 150, 175], dtype="float")
+    spectrum_in = Spectrum(mz=mz, intensities=intensities)
+
+    spectrum = remove_peaks_outside_top_k(spectrum_in, mz_window=10)
+
+    assert len(spectrum.peaks) == 6, "Expected 6 peaks to remain."
+    assert spectrum.peaks.mz.tolist() == [30., 40., 50., 60., 70., 80.], "Expected different peaks to remain."
+
+
+def test_if_spectrum_is_cloned():
+    """Test if filter is correctly cloning the input spectrum."""
+    mz = numpy.array([], dtype="float")
+    intensities = numpy.array([], dtype="float")
+    spectrum_in = Spectrum(mz=mz, intensities=intensities)
+
+    spectrum = remove_peaks_outside_top_k(spectrum_in)
+    spectrum.set("testfield", "test")
+
+    assert not spectrum_in.get("testfield"), "Expected input spectrum to remain unchanged."
+
+
+def test_with_input_none():
+    """Test if input spectrum is None."""
+    spectrum_in = None
+    spectrum = remove_peaks_outside_top_k(spectrum_in)
+    assert spectrum is None
diff --git a/tests/test_require_minimum_of_high_peaks.py b/tests/test_require_minimum_of_high_peaks.py
new file mode 100644
index 00000000..a27796ed
--- /dev/null
+++ b/tests/test_require_minimum_of_high_peaks.py
@@ -0,0 +1,55 @@
+import numpy
+from matchms import Spectrum
+from matchms.filtering import require_minimum_of_high_peaks
+
+
+def test_require_minimum_of_high_peaks_no_params():
+    """No parameters provided."""
+    mz = numpy.array([10, 20, 30, 40], dtype="float")
+    intensities = numpy.array([0, 1, 10, 100], dtype="float")
+    spectrum_in = Spectrum(mz=mz, intensities=intensities)
+
+    spectrum = require_minimum_of_high_peaks(spectrum_in)
+
+    assert spectrum is None, "Expected None as the number of peaks (4) is less than the default of 5 for no_peaks."
+
+
+def test_require_minimum_of_high_peaks_no_peaks_2():
+    """Set no_peaks to 2."""
+    mz = numpy.array([10, 20, 30, 40], dtype="float")
+    intensities = numpy.array([0, 1, 10, 100], dtype="float")
+    spectrum_in = Spectrum(mz=mz, intensities=intensities)
+
+    spectrum = require_minimum_of_high_peaks(spectrum_in, no_peaks=2)
+
+    assert spectrum == spectrum_in, "Expected no changes."
+
+
+def test_require_minimum_of_high_peaks_intensity_percent_10():
+    """Set intensity_percent to 10."""
+    mz = numpy.array([10, 20, 30, 40, 50, 60, 70], dtype="float")
+    intensities = numpy.array([0, 1, 10, 25, 50, 75, 100], dtype="float")
+    spectrum_in = Spectrum(mz=mz, intensities=intensities)
+
+    spectrum = require_minimum_of_high_peaks(spectrum_in, intensity_percent=10)
+
+    assert spectrum == spectrum_in, "Expected no changes."
+
+
+def test_if_spectrum_is_cloned():
+    """Test if filter is correctly cloning the input spectrum."""
+    mz = numpy.array([10, 20, 30, 40], dtype="float")
+    intensities = numpy.array([0, 1, 10, 100], dtype="float")
+    spectrum_in = Spectrum(mz=mz, intensities=intensities)
+
+    spectrum = require_minimum_of_high_peaks(spectrum_in, no_peaks=2)
+    spectrum.set("testfield", "test")
+
+    assert not spectrum_in.get("testfield"), "Expected input spectrum to remain unchanged."
+
+
+def test_with_input_none():
+    """Test if input spectrum is None."""
+    spectrum_in = None
+    spectrum = require_minimum_of_high_peaks(spectrum_in)
+    assert spectrum is None
diff --git a/tests/test_require_precursor_below_mz.py b/tests/test_require_precursor_below_mz.py
new file mode 100644
index 00000000..8180bd63
--- /dev/null
+++ b/tests/test_require_precursor_below_mz.py
@@ -0,0 +1,72 @@
+import numpy
+import pytest
+from matchms import Spectrum
+from matchms.filtering import require_precursor_below_mz
+
+
+def test_require_precursor_below_mz_no_params():
+    """Using default parameterse with precursor mz present."""
+    mz = numpy.array([10, 20, 30, 40], dtype="float")
+    intensities = numpy.array([0, 1, 10, 100], dtype="float")
+    spectrum_in = Spectrum(mz=mz, intensities=intensities)
+    spectrum_in.set("precursor_mz", 60.)
+
+    spectrum = require_precursor_below_mz(spectrum_in)
+
+    assert spectrum == spectrum_in, "Expected no changes."
+
+
+def test_require_precursor_below_mz_max_50():
+    """Set max_mz to 50."""
+    mz = numpy.array([10, 20, 30, 40], dtype="float")
+    intensities = numpy.array([0, 1, 10, 100], dtype="float")
+    spectrum_in = Spectrum(mz=mz, intensities=intensities)
+    spectrum_in.set("precursor_mz", 60.)
+
+    spectrum = require_precursor_below_mz(spectrum_in, max_mz=50)
+
+    assert spectrum is None, "Expected spectrum to be None."
+
+
+def test_if_spectrum_is_cloned():
+    """Test if filter is correctly cloning the input spectrum."""
+    mz = numpy.array([10, 20, 30, 40], dtype="float")
+    intensities = numpy.array([0, 1, 10, 100], dtype="float")
+    spectrum_in = Spectrum(mz=mz, intensities=intensities)
+    spectrum_in.set("precursor_mz", 1.)
+
+    spectrum = require_precursor_below_mz(spectrum_in)
+    spectrum.set("testfield", "test")
+
+    assert not spectrum_in.get("testfield"), "Expected input spectrum to remain unchanged."
+
+
+def test_require_precursor_below_without_precursor_mz():
+    """Test if correct assert error is raised for missing precursor-mz."""
+    spectrum_in = Spectrum(mz=numpy.array([10, 20, 30, 40], dtype="float"),
+                           intensities=numpy.array([0, 1, 10, 100], dtype="float"),
+                           metadata={})
+
+    with pytest.raises(AssertionError) as msg:
+        _ = require_precursor_below_mz(spectrum_in)
+
+    assert str(msg.value) == "Precursor mz absent.", "Expected different error message."
+
+
+def test_require_precursor_below_with_wrong_precursor_mz():
+    """Test if correct assert error is raised for precursor-mz as string."""
+    spectrum_in = Spectrum(mz=numpy.array([10, 20, 30, 40], dtype="float"),
+                           intensities=numpy.array([0, 1, 10, 100], dtype="float"),
+                           metadata={"precursor_mz": "445.0"})
+
+    with pytest.raises(AssertionError) as msg:
+        _ = require_precursor_below_mz(spectrum_in)
+
+    assert "Expected 'precursor_mz' to be a scalar number." in str(msg.value)
+
+
+def test_with_input_none():
+    """Test if input spectrum is None."""
+    spectrum_in = None
+    spectrum = require_precursor_below_mz(spectrum_in)
+    assert spectrum is None