From 665a69bda9f876959822ac424cbddc455af85011 Mon Sep 17 00:00:00 2001 From: "Daniel G. Krakowczyk" Date: Fri, 12 May 2023 15:19:32 +0100 Subject: [PATCH] feat(events): Add support for custom event names (#408) --- docs/source/tutorials/detecting-events.ipynb | 26 ++++++++++++++----- src/pymovements/events/__init__.py | 8 +++--- src/pymovements/events/detection/idt.py | 5 +++- src/pymovements/events/detection/ivt.py | 5 +++- .../events/detection/microsaccades.py | 5 +++- tests/events/detection/idt_test.py | 14 ++++++++++ tests/events/detection/ivt_test.py | 14 ++++++++++ tests/events/detection/microsaccades_test.py | 19 ++++++++++++++ 8 files changed, 82 insertions(+), 14 deletions(-) diff --git a/docs/source/tutorials/detecting-events.ipynb b/docs/source/tutorials/detecting-events.ipynb index 5c727f147..f09233720 100644 --- a/docs/source/tutorials/detecting-events.ipynb +++ b/docs/source/tutorials/detecting-events.ipynb @@ -154,13 +154,23 @@ "To be able to differentiate between the detected events, we specify custom event names for each algorithm:" ] }, + { + "cell_type": "code", + "execution_count": null, + "id": "32d700e9", + "metadata": {}, + "outputs": [], + "source": [ + "dataset.detect_events('idt', dispersion_threshold=2.7, name='fixation.idt')\n", + "dataset.detect_events('ivt', velocity_threshold=20, name='fixation.ivt')" + ] + }, { "cell_type": "markdown", - "id": "a65bb9dc", + "id": "acdfbf60", "metadata": {}, "source": [ - "dataset.detect_events('idt', dispersion_threshold=2.7)\n", - "dataset.detect_events('ivt', velocity_threshold=20)" + "This has added new rows with the fixation events to the event dataframe." ] }, { @@ -170,15 +180,17 @@ "metadata": {}, "outputs": [], "source": [ - "dataset.events[0].frame.filter(pl.col('name') == 'fixation').head()" + "dataset.events[0].frame.filter(pl.col('name') == 'fixation.idt').head()" ] }, { - "cell_type": "markdown", - "id": "acdfbf60", + "cell_type": "code", + "execution_count": null, + "id": "5ebab23c", "metadata": {}, + "outputs": [], "source": [ - "This has added new rows with the fixation events to the event dataframe." + "dataset.events[0].frame.filter(pl.col('name') == 'fixation.ivt').head()" ] }, { diff --git a/src/pymovements/events/__init__.py b/src/pymovements/events/__init__.py index 78e873877..026447a87 100644 --- a/src/pymovements/events/__init__.py +++ b/src/pymovements/events/__init__.py @@ -68,11 +68,11 @@ from pymovements.events.events import register_event_detection __all__ = [ - 'microsaccades', - 'idt', - 'ivt', + 'EventDataFrame', 'EventGazeProcessor', 'EventProcessor', - 'EventDataFrame', + 'idt', + 'ivt', + 'microsaccades', 'register_event_detection', ] diff --git a/src/pymovements/events/detection/idt.py b/src/pymovements/events/detection/idt.py index 0e711d900..81c5d4897 100644 --- a/src/pymovements/events/detection/idt.py +++ b/src/pymovements/events/detection/idt.py @@ -59,6 +59,7 @@ def idt( minimum_duration: int = 100, dispersion_threshold: float = 1.0, include_nan: bool = False, + name: str = 'fixation', ) -> EventDataFrame: """ Fixation identification based on dispersion threshold. @@ -88,6 +89,8 @@ def idt( Threshold for dispersion for a group of consecutive samples to be identified as fixation include_nan: bool Indicator, whether we want to split events on missing/corrupt value (np.nan) + name: + Name for detected events in EventDataFrame. Returns ------- @@ -210,5 +213,5 @@ def idt( onsets_arr = np.array(onsets).flatten() offsets_arr = np.array(offsets).flatten() - event_df = EventDataFrame(name='fixation', onsets=onsets_arr, offsets=offsets_arr) + event_df = EventDataFrame(name=name, onsets=onsets_arr, offsets=offsets_arr) return event_df diff --git a/src/pymovements/events/detection/ivt.py b/src/pymovements/events/detection/ivt.py index 0a6d39533..736bbb1ea 100644 --- a/src/pymovements/events/detection/ivt.py +++ b/src/pymovements/events/detection/ivt.py @@ -40,6 +40,7 @@ def ivt( minimum_duration: int = 100, velocity_threshold: float = 20.0, include_nan: bool = False, + name: str = 'fixation', ) -> EventDataFrame: """ Identification of fixations based on velocity-threshold @@ -68,6 +69,8 @@ def ivt( velocity is below the threshold, the point is classified as a fixation. include_nan: bool Indicator, whether we want to split events on missing/corrupt value (np.nan) + name: + Name for detected events in EventDataFrame. Returns ------- @@ -127,5 +130,5 @@ def ivt( offsets = timesteps[[candidate_indices[-1] for candidate_indices in candidates]].flatten() # Create event dataframe from onsets and offsets. - event_df = EventDataFrame(name='fixation', onsets=onsets, offsets=offsets) + event_df = EventDataFrame(name=name, onsets=onsets, offsets=offsets) return event_df diff --git a/src/pymovements/events/detection/microsaccades.py b/src/pymovements/events/detection/microsaccades.py index 946fa517a..85e005b08 100644 --- a/src/pymovements/events/detection/microsaccades.py +++ b/src/pymovements/events/detection/microsaccades.py @@ -43,6 +43,7 @@ def microsaccades( threshold_factor: float = 6, minimum_threshold: float = 1e-10, include_nan: bool = False, + name: str = 'saccade', ) -> EventDataFrame: """Detect micro-saccades from velocity gaze sequence. @@ -76,6 +77,8 @@ def microsaccades( Default: 1e-10 include_nan: bool Indicator, whether we want to split events on missing/corrupt value (np.nan) + name: + Name for detected events in EventDataFrame. Returns ------- @@ -144,7 +147,7 @@ def microsaccades( offsets = timesteps[[candidate_indices[-1] for candidate_indices in candidates]].flatten() # Create event dataframe from onsets and offsets. - event_df = EventDataFrame(name='saccade', onsets=onsets, offsets=offsets) + event_df = EventDataFrame(name=name, onsets=onsets, offsets=offsets) return event_df diff --git a/tests/events/detection/idt_test.py b/tests/events/detection/idt_test.py index 1c63d7883..b3e9901d9 100644 --- a/tests/events/detection/idt_test.py +++ b/tests/events/detection/idt_test.py @@ -151,6 +151,20 @@ def test_idt_raises_error(kwargs, expected_error): ), id='constant_position_single_fixation', ), + pytest.param( + { + 'positions': pm.synthetic.step_function(length=100, steps=[0], values=[(0, 0)]), + 'dispersion_threshold': 1, + 'minimum_duration': 2, + 'name': 'custom_fixation', + }, + pm.events.EventDataFrame( + name='custom_fixation', + onsets=[0], + offsets=[99], + ), + id='constant_position_single_fixation_custom_name', + ), pytest.param( { 'positions': pm.synthetic.step_function( diff --git a/tests/events/detection/ivt_test.py b/tests/events/detection/ivt_test.py index 4fef43383..f7f4bbef4 100644 --- a/tests/events/detection/ivt_test.py +++ b/tests/events/detection/ivt_test.py @@ -186,6 +186,20 @@ def test_ivt_raise_error(kwargs, expected_error): ), id='constant_position_single_fixation', ), + pytest.param( + { + 'positions': step_function(length=100, steps=[0], values=[(0, 0)]), + 'velocity_threshold': 1, + 'minimum_duration': 1, + 'name': 'custom_fixation', + }, + EventDataFrame( + name='custom_fixation', + onsets=[0], + offsets=[99], + ), + id='constant_position_single_fixation_custom_name', + ), pytest.param( { 'positions': step_function( diff --git a/tests/events/detection/microsaccades_test.py b/tests/events/detection/microsaccades_test.py index a2d700382..f5a10f711 100644 --- a/tests/events/detection/microsaccades_test.py +++ b/tests/events/detection/microsaccades_test.py @@ -100,6 +100,25 @@ def test_microsaccades_raises_error(kwargs, expected): ), id='two_steps_one_saccade', ), + pytest.param( + { + 'positions': step_function(length=100, steps=[0], values=[(0, 0)]), + 'velocities': step_function( + length=100, + steps=[40, 50], + values=[(9, 9), (0, 0)], + start_value=(0, 0), + ), + 'threshold': 1e-5, + 'name': 'custom_saccade', + }, + EventDataFrame( + name='custom_saccade', + onsets=[40], + offsets=[49], + ), + id='two_steps_one_saccade_custom_name', + ), pytest.param( { 'positions': step_function(length=100, steps=[0], values=[(0, 0)]),