Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Add events to gaze integration functions #554

Merged
merged 16 commits into from
Sep 15, 2023
Merged
2 changes: 2 additions & 0 deletions src/pymovements/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
from pymovements import _version
from pymovements import datasets
from pymovements import events
from pymovements import exceptions
from pymovements import gaze
from pymovements import plotting
from pymovements import synthetic
Expand Down Expand Up @@ -56,6 +57,7 @@
'Screen',
'GazeDataFrame',

'exceptions',
'plotting',
'synthetic',
'utils',
Expand Down
4 changes: 2 additions & 2 deletions src/pymovements/events/processing.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,10 +26,10 @@

import polars as pl

import pymovements as pm # pylint: disable=cyclic-import
from pymovements.events.frame import EventDataFrame
from pymovements.events.properties import EVENT_PROPERTIES
from pymovements.exceptions import InvalidProperty
from pymovements.gaze.gaze_dataframe import GazeDataFrame


class EventProcessor:
Expand Down Expand Up @@ -140,7 +140,7 @@ def __init__(
def process(
self,
events: EventDataFrame,
gaze: GazeDataFrame,
gaze: pm.GazeDataFrame,
identifiers: str | list[str],
name: str | None = None,
) -> pl.DataFrame:
Expand Down
9 changes: 9 additions & 0 deletions src/pymovements/gaze/gaze_dataframe.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@

import polars as pl

import pymovements as pm # pylint: disable=cyclic-import
from pymovements.gaze import transforms
from pymovements.gaze.experiment import Experiment
from pymovements.utils import checks
Expand Down Expand Up @@ -79,6 +80,7 @@ def __init__(
self,
data: pl.DataFrame | None = None,
experiment: Experiment | None = None,
events: pm.EventDataFrame | None = None,
*,
trial_columns: str | list[str] | None = None,
time_column: str | None = None,
Expand All @@ -95,6 +97,8 @@ def __init__(
A dataframe to be transformed to a polars dataframe.
experiment : Experiment
The experiment definition.
events: EventDataFrame
A dataframe of events in the gaze signal.
trial_columns:
The name of the trial columns in the input data frame. If the list is empty or None,
the input data frame is assumed to contain only one trial. If the list is not empty,
Expand Down Expand Up @@ -214,6 +218,11 @@ def __init__(
self.n_components = _infer_n_components(self.frame, column_specifiers)
self.experiment = experiment

if events is None:
self.events = pm.EventDataFrame()
else:
self.events = events.copy()

def transform(
self,
transform_method: str | Callable[..., pl.Expr],
Expand Down
19 changes: 15 additions & 4 deletions src/pymovements/gaze/integration.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,20 +26,23 @@
import pandas as pd
import polars as pl

from pymovements.events.frame import EventDataFrame
from pymovements.gaze.experiment import Experiment
from pymovements.gaze.gaze_dataframe import GazeDataFrame
from pymovements.utils import checks


def from_numpy(
data: np.ndarray | None = None,
experiment: Experiment | None = None,
events: EventDataFrame | None = None,
*,
time: np.ndarray | None = None,
pixel: np.ndarray | None = None,
position: np.ndarray | None = None,
velocity: np.ndarray | None = None,
acceleration: np.ndarray | None = None,
schema: list[str] | None = None,
experiment: Experiment | None = None,
orient: Literal['col', 'row'] = 'col',
time_column: str | None = None,
pixel_columns: list[str] | None = None,
Expand All @@ -63,6 +66,10 @@ def from_numpy(
----------
data:
Two-dimensional data represented as a numpy ndarray.
experiment : Experiment
The experiment definition.
events: EventDataFrame
A dataframe of events in the gaze signal.
time:
Array of timestamps.
pixel:
Expand All @@ -77,9 +84,6 @@ def from_numpy(
A list of column names.
orient:
Whether to interpret the two-dimensional data as columns or as rows.
experiment : Experiment
The experiment definition.
time_column: str | None = None,
time_column:
The name of the timestamp column in the input data frame.
pixel_columns:
Expand Down Expand Up @@ -200,6 +204,7 @@ def from_numpy(
return GazeDataFrame(
data=df,
experiment=experiment,
events=events,
time_column=time_column,
pixel_columns=pixel_columns,
position_columns=position_columns,
Expand Down Expand Up @@ -245,6 +250,7 @@ def from_numpy(
return GazeDataFrame(
data=df,
experiment=experiment,
events=events,
time_column=time_column,
pixel_columns=pixel_columns,
position_columns=position_columns,
Expand All @@ -256,6 +262,8 @@ def from_numpy(
def from_pandas(
data: pd.DataFrame,
experiment: Experiment | None = None,
events: EventDataFrame | None = None,
*,
time_column: str | None = None,
pixel_columns: list[str] | None = None,
position_columns: list[str] | None = None,
Expand All @@ -270,6 +278,8 @@ def from_pandas(
Data represented as a pandas DataFrame.
experiment : Experiment
The experiment definition.
events: EventDataFrame
A dataframe of events in the gaze signal.
time_column:
The name of the timestamp column in the input data frame.
pixel_columns:
Expand All @@ -289,6 +299,7 @@ def from_pandas(
return GazeDataFrame(
data=df,
experiment=experiment,
events=events,
time_column=time_column,
pixel_columns=pixel_columns,
position_columns=position_columns,
Expand Down
72 changes: 67 additions & 5 deletions tests/gaze/gaze_init_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@
import pytest
from polars.testing import assert_frame_equal

from pymovements.gaze.gaze_dataframe import GazeDataFrame
import pymovements as pm


@pytest.mark.parametrize(
Expand Down Expand Up @@ -704,7 +704,7 @@
],
)
def test_init_gaze_dataframe_has_expected_attrs(init_kwargs, expected_frame, expected_n_components):
gaze = GazeDataFrame(**init_kwargs)
gaze = pm.GazeDataFrame(**init_kwargs)
assert_frame_equal(gaze.frame, expected_frame)
assert gaze.n_components == expected_n_components

Expand Down Expand Up @@ -1175,7 +1175,7 @@ def test_init_gaze_dataframe_has_expected_attrs(init_kwargs, expected_frame, exp
)
def test_gaze_dataframe_init_exceptions(init_kwargs, exception, exception_msg):
with pytest.raises(exception) as excinfo:
GazeDataFrame(**init_kwargs)
pm.GazeDataFrame(**init_kwargs)

msg, = excinfo.value.args
assert msg == exception_msg
Expand All @@ -1187,9 +1187,71 @@ def test_gaze_copy_init_has_same_n_components():
Refers to issue #514.
"""
df_orig = pl.from_numpy(np.zeros((2, 1000)), orient='col', schema=['x', 'y'])
gaze = GazeDataFrame(df_orig, position_columns=['x', 'y'])
gaze = pm.GazeDataFrame(df_orig, position_columns=['x', 'y'])

df_copy = gaze.frame.clone()
gaze_copy = GazeDataFrame(df_copy)
gaze_copy = pm.GazeDataFrame(df_copy)

assert gaze.n_components == gaze_copy.n_components


@pytest.mark.parametrize(
('events', 'init_kwargs'),
[
pytest.param(
None,
{
'data': pl.from_dict(
{'x': [1.23], 'y': [4.56]}, schema={'x': pl.Float64, 'y': pl.Float64},
),
'position_columns': ['x', 'y'],
},
id='data_with_no_events',
),

pytest.param(
pm.EventDataFrame(),
{
'data': pl.from_dict(
{'x': [1.23], 'y': [4.56]}, schema={'x': pl.Float64, 'y': pl.Float64},
),
'position_columns': ['x', 'y'],
},
id='data_empty_events',
),

pytest.param(
pm.EventDataFrame(),
{},
id='no_data_empty_events',
),

pytest.param(
pm.EventDataFrame(name='saccade', onsets=[0], offsets=[10]),
{},
id='no_data_with_saccades',
),

pytest.param(
pm.EventDataFrame(name='fixation', onsets=[100], offsets=[910]),
{
'data': pl.from_dict(
{'x': [1.23], 'y': [4.56]}, schema={'x': pl.Float64, 'y': pl.Float64},
),
'position_columns': ['x', 'y'],
},
id='data_with_fixations',
),
],
)
def test_gaze_init_events(events, init_kwargs):
if events is None:
expected_events = pm.EventDataFrame().frame
else:
expected_events = events.frame

gaze = pm.GazeDataFrame(events=events, **init_kwargs)

assert_frame_equal(gaze.events.frame, expected_events)
# We don't want the events point to the same reference.
assert gaze.events.frame is not expected_events
41 changes: 40 additions & 1 deletion tests/gaze/integration_numpy_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
"""Test from gaze.from_numpy."""
import numpy as np
import polars as pl
import pytest
from polars.testing import assert_frame_equal

import pymovements as pm
Expand Down Expand Up @@ -164,7 +165,7 @@ def test_from_numpy_explicit_columns():
assert gaze.n_components == 2


def test_init_all_none():
def test_from_numpy_all_none():
gaze = pm.gaze.from_numpy(
data=None,
schema=None,
Expand All @@ -185,3 +186,41 @@ def test_init_all_none():

assert_frame_equal(gaze.frame, expected)
assert gaze.n_components is None


@pytest.mark.parametrize(
'events',
[
pytest.param(
None,
id='events_none',
),

pytest.param(
pm.EventDataFrame(),
id='events_empty',
),

pytest.param(
pm.EventDataFrame(name='fixation', onsets=[123], offsets=[345]),
id='fixation',
),

pytest.param(
pm.EventDataFrame(name='saccade', onsets=[34123], offsets=[67345]),
id='saccade',
),

],
)
def test_from_numpy_events(events):
if events is None:
expected_events = pm.EventDataFrame().frame
else:
expected_events = events.frame

gaze = pm.gaze.from_numpy(events=events)

assert_frame_equal(gaze.events.frame, expected_events)
# We don't want the events point to the same reference.
assert gaze.events.frame is not expected_events
Loading