Repository: aeye-lab/pymovements
Pull request #406 (2023-05-12 11:05:23)

feat(dataset): Support strings for specifying event detection method
## Description

This makes it possible to specify event detection methods by using a string instead of the real class, like this:

```python

dataset.detect_events('microsaccades')
```

instead of 

```python

dataset.detect_events(pm.events.microsaccades)
```

## Type of change

- [x] New feature (non-breaking change which adds functionality)
- [x] This change is or requires a documentation update

## How Has This Been Tested?

- [x] Test if detection methods are registered correctly
- [x] Test if strings for dataset.detect_events work

## Checklist:

- [x] My code follows the style guidelines of this project
- [x] I have performed a self-review of my own code
- [x] I have commented my code, particularly in hard-to-understand areas
- [x] I have made corresponding changes to the documentation
- [x] My changes generate no new warnings
- [x] I have added tests that prove my fix is effective or that my feature works
- [x] New and existing unit tests pass locally with my changes
- [x] Any dependent changes have been merged and published in downstream modules
- [x] I have checked my code and corrected any misspellings


codecov[bot]: ## [Codecov](https://app.codecov.io/gh/aeye-lab/pymovements/pull/406?src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=aeye-lab) Report
Patch coverage: **`100.00`**% and no project coverage change.
> Comparison is base [(`ccc8ee8`)](https://app.codecov.io/gh/aeye-lab/pymovements/commit/ccc8ee8d5d3893a3453b6324a47092e95eeaa4db?el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=aeye-lab) 100.00% compared to head [(`65a0157`)](https://app.codecov.io/gh/aeye-lab/pymovements/pull/406?src=pr&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=aeye-lab) 100.00%.

> :exclamation: Current head 65a0157 differs from pull request most recent head 15265fd. Consider uploading reports for the commit 15265fd to get more accurate results

<details><summary>Additional details and impacted files</summary>


```diff
@@            Coverage Diff            @@
##              main      #406   +/-   ##
=========================================
  Coverage   100.00%   100.00%           
=========================================
  Files           55        55           
  Lines         1879      1898   +19     
  Branches       413       419    +6     
=========================================
+ Hits          1879      1898   +19     
```


| [Impacted Files](https://app.codecov.io/gh/aeye-lab/pymovements/pull/406?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=aeye-lab) | Coverage Δ | |
|---|---|---|
| [src/pymovements/dataset/dataset\_files.py](https://app.codecov.io/gh/aeye-lab/pymovements/pull/406?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=aeye-lab#diff-c3JjL3B5bW92ZW1lbnRzL2RhdGFzZXQvZGF0YXNldF9maWxlcy5weQ==) | `100.00% <ø> (ø)` | |
| [src/pymovements/events/detection/\_\_init\_\_.py](https://app.codecov.io/gh/aeye-lab/pymovements/pull/406?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=aeye-lab#diff-c3JjL3B5bW92ZW1lbnRzL2V2ZW50cy9kZXRlY3Rpb24vX19pbml0X18ucHk=) | `100.00% <ø> (ø)` | |
| [src/pymovements/dataset/dataset.py](https://app.codecov.io/gh/aeye-lab/pymovements/pull/406?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=aeye-lab#diff-c3JjL3B5bW92ZW1lbnRzL2RhdGFzZXQvZGF0YXNldC5weQ==) | `100.00% <100.00%> (ø)` | |
| [src/pymovements/events/\_\_init\_\_.py](https://app.codecov.io/gh/aeye-lab/pymovements/pull/406?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=aeye-lab#diff-c3JjL3B5bW92ZW1lbnRzL2V2ZW50cy9fX2luaXRfXy5weQ==) | `100.00% <100.00%> (ø)` | |
| [src/pymovements/events/detection/engbert.py](https://app.codecov.io/gh/aeye-lab/pymovements/pull/406?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=aeye-lab#diff-c3JjL3B5bW92ZW1lbnRzL2V2ZW50cy9kZXRlY3Rpb24vZW5nYmVydC5weQ==) | `100.00% <100.00%> (ø)` | |
| [src/pymovements/events/detection/idt.py](https://app.codecov.io/gh/aeye-lab/pymovements/pull/406?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=aeye-lab#diff-c3JjL3B5bW92ZW1lbnRzL2V2ZW50cy9kZXRlY3Rpb24vaWR0LnB5) | `100.00% <100.00%> (ø)` | |
| [src/pymovements/events/detection/ivt.py](https://app.codecov.io/gh/aeye-lab/pymovements/pull/406?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=aeye-lab#diff-c3JjL3B5bW92ZW1lbnRzL2V2ZW50cy9kZXRlY3Rpb24vaXZ0LnB5) | `100.00% <100.00%> (ø)` | |
| [src/pymovements/events/events.py](https://app.codecov.io/gh/aeye-lab/pymovements/pull/406?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=aeye-lab#diff-c3JjL3B5bW92ZW1lbnRzL2V2ZW50cy9ldmVudHMucHk=) | `100.00% <100.00%> (ø)` | |


</details>

[:umbrella: View full report in Codecov by Sentry](https://app.codecov.io/gh/aeye-lab/pymovements/pull/406?src=pr&el=continue&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=aeye-lab).   
:loudspeaker: Do you have feedback about the report comment? [Let us know in this issue](https://about.codecov.io/codecov-pr-comment-feedback/?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=aeye-lab).


Patch:
diff --git a/docs/source/tutorials/detecting-events.ipynb b/docs/source/tutorials/detecting-events.ipynb
index e45d042..5c727f1 100644
--- a/docs/source/tutorials/detecting-events.ipynb
+++ b/docs/source/tutorials/detecting-events.ipynb
@@ -15,8 +15,8 @@
    "source": [
     "## What you will learn in this tutorial:\n",
     "\n",
-    "* how to detect fixations using the I-VT algorithm\n",
-    "* how to detect saccades using the microsaccades algorithm"
+    "* how to detect saccades using the microsaccades algorithm\n",
+    "* how to detect fixations using the I-DT and I-VT algorithms"
    ]
   },
   {
@@ -105,19 +105,25 @@
     "\n",
     "See the reference for [pymovements.events](https://pymovements.readthedocs.io/en/latest/reference/pymovements.events.html) to get an overview of all the supported methods.\n",
     "\n",
-    "For this tutorial we will use the I-VT algorithm for detecting fixations and the `microsaccades` algorithm for detecting saccades.\n",
-    "\n",
-    "We start with the I-VT algorithm to detect fixations using the default parameteres:"
+    "For this tutorial we will use the `microsaccades` algorithm for detecting saccades and the I-DT and I-VT algorithms for detecting fixations."
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "id": "b10421cf",
+   "metadata": {},
+   "source": [
+    "We start out by detecting saccades."
    ]
   },
   {
    "cell_type": "code",
    "execution_count": null,
-   "id": "25b7d79a",
+   "id": "a2e450ea",
    "metadata": {},
    "outputs": [],
    "source": [
-    "dataset.detect_events(pm.events.ivt)"
+    "dataset.detect_events('microsaccades', minimum_duration=12)"
    ]
   },
   {
@@ -125,7 +131,7 @@
    "id": "5a0b1bc1",
    "metadata": {},
    "source": [
-    "The detected events are added to the event dataframe:"
+    "The detected events are added as rows with the name `saccade` to the event dataframe:"
    ]
   },
   {
@@ -143,19 +149,28 @@
    "id": "a9968885",
    "metadata": {},
    "source": [
-    "Next we detect some saccades. This time we don't use the default parameters but specify our own:"
+    "Next we will detect fixations using the I-DT and I-VT algorithms.\n",
+    "\n",
+    "To be able to differentiate between the detected events, we specify custom event names for each algorithm:"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "id": "a65bb9dc",
+   "metadata": {},
+   "source": [
+    "dataset.detect_events('idt', dispersion_threshold=2.7)\n",
+    "dataset.detect_events('ivt', velocity_threshold=20)"
    ]
   },
   {
    "cell_type": "code",
    "execution_count": null,
-   "id": "a2e450ea",
+   "id": "8f3ea55b",
    "metadata": {},
    "outputs": [],
    "source": [
-    "dataset.detect_events(pm.events.microsaccades, minimum_duration=12)\n",
-    "\n",
-    "dataset.events[0].frame.filter(pl.col('name') == 'saccade').head()"
+    "dataset.events[0].frame.filter(pl.col('name') == 'fixation').head()"
    ]
   },
   {
@@ -163,7 +178,7 @@
    "id": "acdfbf60",
    "metadata": {},
    "source": [
-    "This has added new rows with the saccade events to the event dataframe."
+    "This has added new rows with the fixation events to the event dataframe."
    ]
   },
   {
@@ -181,8 +196,8 @@
    "source": [
     "## What you have learned in this tutorial:\n",
     "\n",
-    "* detecting fixations by using `Dataset.detect_events(pm.events.fixations)`\n",
-    "* detecting saccades by using `Dataset.detect_events(pm.events.microsaccades)`\n"
+    "* detecting saccades by using the microsaccades algorithm\n",
+    "* detecting fixations by using the I-DT and I-VT algorithms"
    ]
   }
  ],
diff --git a/docs/source/tutorials/event-properties.ipynb b/docs/source/tutorials/event-properties.ipynb
index d996808..1d6df8a 100644
--- a/docs/source/tutorials/event-properties.ipynb
+++ b/docs/source/tutorials/event-properties.ipynb
@@ -82,7 +82,7 @@
     "dataset.pix2deg()\n",
     "dataset.pos2vel('smooth')\n",
     "\n",
-    "dataset.detect_events(pm.events.microsaccades)\n",
+    "dataset.detect_events('microsaccades')\n",
     "\n",
     "dataset.events[0].frame.head()"
    ]
diff --git a/docs/source/tutorials/plot-main-sequence.ipynb b/docs/source/tutorials/plot-main-sequence.ipynb
index d36d8c4..7192cc7 100644
--- a/docs/source/tutorials/plot-main-sequence.ipynb
+++ b/docs/source/tutorials/plot-main-sequence.ipynb
@@ -130,7 +130,7 @@
    "metadata": {},
    "outputs": [],
    "source": [
-    "dataset.detect_events(pm.events.microsaccades)"
+    "dataset.detect_events('microsaccades')"
    ]
   },
   {
diff --git a/docs/source/tutorials/pymovements-in-10-minutes.ipynb b/docs/source/tutorials/pymovements-in-10-minutes.ipynb
index 95d3bb6..4213fad 100644
--- a/docs/source/tutorials/pymovements-in-10-minutes.ipynb
+++ b/docs/source/tutorials/pymovements-in-10-minutes.ipynb
@@ -19,7 +19,7 @@
     "* how to load a subset of the data into your memory\n",
     "* how to transform pixel coordinates into degrees of visual angle\n",
     "* how to transform positional data into velocity data\n",
-    "* how to detect fixations by using the I-DT algorithm\n",
+    "* how to detect fixations by using the I-VT algorithm\n",
     "* how to detect saccades by using the microsaccades algorithm\n",
     "* how to compute additional event properties for your analysis\n",
     "* how to save your preprocessed data\n",
@@ -308,7 +308,7 @@
    "metadata": {},
    "outputs": [],
    "source": [
-    "dataset.detect_events(method=pm.events.ivt)\n",
+    "dataset.detect_events('ivt')\n",
     "\n",
     "dataset.events[0].frame.head()"
    ]
@@ -328,7 +328,7 @@
    "metadata": {},
    "outputs": [],
    "source": [
-    "dataset.detect_events(pm.events.microsaccades, minimum_duration=12)\n",
+    "dataset.detect_events('microsaccades', minimum_duration=12)\n",
     "\n",
     "dataset.events[0].frame.filter(pl.col('name') == 'saccade').head()"
    ]
diff --git a/docs/source/tutorials/saving-loading-events.ipynb b/docs/source/tutorials/saving-loading-events.ipynb
index 35a2d95..d428567 100644
--- a/docs/source/tutorials/saving-loading-events.ipynb
+++ b/docs/source/tutorials/saving-loading-events.ipynb
@@ -101,7 +101,7 @@
    "metadata": {},
    "outputs": [],
    "source": [
-    "dataset.detect_events(pm.events.ivt)\n",
+    "dataset.detect_events('ivt')\n",
     "\n",
     "dataset.events[0].frame.head()"
    ]
@@ -113,7 +113,7 @@
    "metadata": {},
    "outputs": [],
    "source": [
-    "dataset.detect_events(pm.events.microsaccades)\n",
+    "dataset.detect_events('microsaccades')\n",
     "\n",
     "dataset.events[0].frame.head()"
    ]
diff --git a/src/pymovements/dataset/dataset.py b/src/pymovements/dataset/dataset.py
index cedf429..18e94c2 100644
--- a/src/pymovements/dataset/dataset.py
+++ b/src/pymovements/dataset/dataset.py
@@ -20,6 +20,7 @@
 """This module provides the base dataset class."""
 from __future__ import annotations
 
+from collections.abc import Callable
 from copy import deepcopy
 from pathlib import Path
 from typing import Any
@@ -34,7 +35,7 @@ from pymovements.dataset.dataset_library import DatasetLibrary
 from pymovements.dataset.dataset_paths import DatasetPaths
 from pymovements.events.event_processing import EventGazeProcessor
 from pymovements.events.events import EventDataFrame
-from pymovements.events.events import EventDetectionCallable
+from pymovements.events.events import EventDetectionLibrary
 from pymovements.gaze import GazeDataFrame
 
 
@@ -302,7 +303,8 @@ class Dataset:
 
     def detect_events(
             self,
-            method: EventDetectionCallable,
+            method: Callable[..., EventDataFrame] | str,
+            *,
             eye: str | None = 'auto',
             clear: bool = False,
             verbose: bool = True,
@@ -338,6 +340,9 @@ class Dataset:
         """
         self._check_gaze_dataframe()
 
+        if isinstance(method, str):
+            method = EventDetectionLibrary.get(method)
+
         # Automatically infer eye to use for event detection.
         if eye == 'auto':
             if 'x_right_pos' in self.gaze[0].columns:
diff --git a/src/pymovements/events/__init__.py b/src/pymovements/events/__init__.py
index de5dda1..78e8738 100644
--- a/src/pymovements/events/__init__.py
+++ b/src/pymovements/events/__init__.py
@@ -27,7 +27,6 @@
    :template: class.rst
 
     pymovements.events.EventDataFrame
-    pymovements.events.EventDetectionCallable
 
 .. rubric:: Processing
 
@@ -66,8 +65,7 @@ from pymovements.events.detection.microsaccades import microsaccades
 from pymovements.events.event_processing import EventGazeProcessor
 from pymovements.events.event_processing import EventProcessor
 from pymovements.events.events import EventDataFrame
-from pymovements.events.events import EventDetectionCallable
-
+from pymovements.events.events import register_event_detection
 
 __all__ = [
     'microsaccades',
@@ -76,5 +74,5 @@ __all__ = [
     'EventGazeProcessor',
     'EventProcessor',
     'EventDataFrame',
-    'EventDetectionCallable',
+    'register_event_detection',
 ]
diff --git a/src/pymovements/events/detection/__init__.py b/src/pymovements/events/detection/__init__.py
index e17db86..b4527fc 100644
--- a/src/pymovements/events/detection/__init__.py
+++ b/src/pymovements/events/detection/__init__.py
@@ -34,7 +34,6 @@ from pymovements.events.detection.idt import idt
 from pymovements.events.detection.ivt import ivt
 from pymovements.events.detection.microsaccades import microsaccades
 
-
 __all__ = [
     'microsaccades',
     'idt',
diff --git a/src/pymovements/events/detection/idt.py b/src/pymovements/events/detection/idt.py
index 057cd81..0e711d9 100644
--- a/src/pymovements/events/detection/idt.py
+++ b/src/pymovements/events/detection/idt.py
@@ -25,6 +25,7 @@ from __future__ import annotations
 import numpy as np
 
 from pymovements.events.events import EventDataFrame
+from pymovements.events.events import register_event_detection
 from pymovements.utils import checks
 from pymovements.utils.filters import events_split_nans
 from pymovements.utils.filters import filter_candidates_remove_nans
@@ -50,6 +51,7 @@ def dispersion(positions: list[list[float]] | np.ndarray) -> float:
     return sum(np.nanmax(positions, axis=0) - np.nanmin(positions, axis=0))
 
 
+@register_event_detection
 def idt(
         positions: list[list[float]] | list[tuple[float, float]] | np.ndarray,
         velocities: list[list[float]] | list[tuple[float, float]] | np.ndarray,
diff --git a/src/pymovements/events/detection/ivt.py b/src/pymovements/events/detection/ivt.py
index cae227a..0a6d395 100644
--- a/src/pymovements/events/detection/ivt.py
+++ b/src/pymovements/events/detection/ivt.py
@@ -25,12 +25,14 @@ from __future__ import annotations
 import numpy as np
 
 from pymovements.events.events import EventDataFrame
+from pymovements.events.events import register_event_detection
 from pymovements.gaze.transforms import consecutive
 from pymovements.gaze.transforms import norm
 from pymovements.utils import checks
 from pymovements.utils.filters import filter_candidates_remove_nans
 
 
+@register_event_detection
 def ivt(
         positions: list[list[float]] | list[tuple[float, float]] | np.ndarray,
         velocities: list[list[float]] | list[tuple[float, float]] | np.ndarray,
diff --git a/src/pymovements/events/detection/microsaccades.py b/src/pymovements/events/detection/microsaccades.py
index 312a98c..946fa51 100644
--- a/src/pymovements/events/detection/microsaccades.py
+++ b/src/pymovements/events/detection/microsaccades.py
@@ -27,11 +27,13 @@ from collections.abc import Sized
 import numpy as np
 
 from pymovements.events.events import EventDataFrame
+from pymovements.events.events import register_event_detection
 from pymovements.gaze.transforms import consecutive
 from pymovements.utils import checks
 from pymovements.utils.filters import filter_candidates_remove_nans
 
 
+@register_event_detection
 def microsaccades(
         positions: list[list[float]] | list[tuple[float, float]] | np.ndarray,
         velocities: list[list[float]] | list[tuple[float, float]] | np.ndarray,
diff --git a/src/pymovements/events/events.py b/src/pymovements/events/events.py
index 4bc0704..f32bfeb 100644
--- a/src/pymovements/events/events.py
+++ b/src/pymovements/events/events.py
@@ -22,12 +22,12 @@ This module holds all the main Event classes used for event detection.
 """
 from __future__ import annotations
 
+from collections.abc import Callable
 from collections.abc import Sequence
 from typing import Any
 
 import numpy as np
 import polars as pl
-from typing_extensions import Protocol
 
 from pymovements.events.event_properties import duration
 from pymovements.utils import checks
@@ -174,36 +174,43 @@ class EventDataFrame:
         return df
 
 
-class EventDetectionCallable(Protocol):
-    """Minimal interface to be implemented by all event detection methods."""
+class EventDetectionLibrary:
+    """Provides access by name to event detection methods.
 
-    def __call__(
-            self,
-            positions: list[list[float]] | list[tuple[float, float]] | np.ndarray,
-            velocities: list[list[float]] | list[tuple[float, float]] | np.ndarray,
-            timesteps: list[int] | np.ndarray | None = None,
-            minimum_duration: int = 0,
-            **kwargs: int | str | float,
-    ) -> EventDataFrame:
-        """Minimal interface to be implemented by all event detection methods.
+    Attributes
+    ----------
+    methods:
+        Dictionary of event detection methods.
+    """
 
-        Parameters
-        ----------
-        positions: array-like, shape (N, 2)
-            Continuous 2D position time series
-        velocities: array-like, shape (N, 2)
-            Corresponding continuous 2D velocity time series.
-        timesteps: array-like, shape (N, )
-            Corresponding continuous 1D timestep time series. If None, sample based timesteps are
-            assumed.
-        minimum_duration: int
-            Minimum event duration. The duration is specified in the units used in ``timesteps``.
-            If ``timesteps`` is None, then ``minimum_duration`` is specified in numbers of samples.
-        **kwargs:
-            Additional keyword arguments for the specific event detection method.
-
-        Returns
-        -------
-        EventDataFrame
-            A dataframe with detected events as rows.
+    methods: dict[str, Callable[..., EventDataFrame]] = {}
+
+    @classmethod
+    def add(cls, method: Callable[..., EventDataFrame]) -> None:
+        """Add an event detection method to the library.
+
+        Parameter
+        ---------
+        method
+            The event detection method to add to the library.
         """
+        cls.methods[method.__name__] = method
+
+    @classmethod
+    def get(cls, name: str) -> Callable[..., EventDataFrame]:
+        """Get event detection method py name.
+
+        Parameter
+        ---------
+        name
+            Name of the event detection method in the library.
+        """
+        return cls.methods[name]
+
+
+def register_event_detection(
+        method: Callable[..., EventDataFrame],
+) -> Callable[..., EventDataFrame]:
+    """Register an event detection method."""
+    EventDetectionLibrary.add(method)
+    return method


Test patch:
diff --git a/tests/dataset/dataset_test.py b/tests/dataset/dataset_test.py
index 39114cf..15ed719 100644
--- a/tests/dataset/dataset_test.py
+++ b/tests/dataset/dataset_test.py
@@ -516,7 +516,15 @@ def test_pos2vel(dataset_configuration):
                 'threshold': 1,
                 'eye': 'auto',
             },
-            id='microsaccades',
+            id='microsaccades_class',
+        ),
+        pytest.param(
+            {
+                'method': 'microsaccades',
+                'threshold': 1,
+                'eye': 'auto',
+            },
+            id='microsaccades_string',
         ),
     ],
 )
diff --git a/tests/events/detection/event_detection_library_test.py b/tests/events/detection/event_detection_library_test.py
new file mode 100644
index 0000000..3e6f201
--- /dev/null
+++ b/tests/events/detection/event_detection_library_test.py
@@ -0,0 +1,40 @@
+# Copyright (c) 2023 The pymovements Project Authors
+#
+# 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.
+"""Test event detection library."""
+from __future__ import annotations
+
+import pytest
+
+import pymovements as pm
+import pymovements.events.events
+
+
+@pytest.mark.parametrize(
+    ('method', 'name'),
+    [
+        pytest.param(pm.events.idt, 'idt', id='idt'),
+        pytest.param(pm.events.ivt, 'ivt', id='ivt'),
+        pytest.param(pm.events.microsaccades, 'microsaccades', id='microsaccades'),
+    ],
+)
+def test_transform_registered(method, name):
+    assert name in pymovements.events.events.EventDetectionLibrary.methods
+    assert pymovements.events.events.EventDetectionLibrary.get(name) == method
+    assert pymovements.events.events.EventDetectionLibrary.get(name).__name__ == name