From d23b81f9f4f18e84d1fbbda5c0171ed6ee88cae0 Mon Sep 17 00:00:00 2001 From: prassepaul Date: Fri, 15 Sep 2023 09:13:07 +0200 Subject: [PATCH 01/23] added from_csv (#504) --- src/pymovements/gaze/__init__.py | 2 + src/pymovements/gaze/io.py | 157 ++++++++++++++++++++++ tests/gaze/io/csv_test.py | 48 +++++++ tests/gaze/io/files/binocular_example.csv | 11 ++ tests/gaze/io/files/monocular_example.csv | 11 ++ 5 files changed, 229 insertions(+) create mode 100644 src/pymovements/gaze/io.py create mode 100644 tests/gaze/io/csv_test.py create mode 100644 tests/gaze/io/files/binocular_example.csv create mode 100644 tests/gaze/io/files/monocular_example.csv diff --git a/src/pymovements/gaze/__init__.py b/src/pymovements/gaze/__init__.py index b8bcec64d..0e2ceed5a 100644 --- a/src/pymovements/gaze/__init__.py +++ b/src/pymovements/gaze/__init__.py @@ -62,6 +62,7 @@ from pymovements.gaze.gaze_dataframe import GazeDataFrame from pymovements.gaze.integration import from_numpy from pymovements.gaze.integration import from_pandas +from pymovements.gaze.io import from_csv from pymovements.gaze.screen import Screen @@ -73,4 +74,5 @@ 'Screen', 'transforms_numpy', 'transforms', + 'from_csv', ] diff --git a/src/pymovements/gaze/io.py b/src/pymovements/gaze/io.py new file mode 100644 index 000000000..ad59dbd6b --- /dev/null +++ b/src/pymovements/gaze/io.py @@ -0,0 +1,157 @@ +# 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. +"""Functionality to load GazeDataFrame from a csv file.""" +from __future__ import annotations + +from pathlib import Path +from typing import Any + +import polars as pl + +from pymovements.gaze import Experiment +from pymovements.gaze.gaze_dataframe import GazeDataFrame + + +def from_csv( + file: str | Path, + experiment: Experiment | None = None, + *, + trial_columns: list[str] | None = None, + time_column: str | None = None, + pixel_columns: list[str] | None = None, + position_columns: list[str] | None = None, + velocity_columns: list[str] | None = None, + acceleration_columns: list[str] | None = None, + **read_csv_kwargs: Any, +) -> GazeDataFrame: + """Initialize a :py:class:`pymovements.gaze.gaze_dataframe.GazeDataFrame`. + + Parameters + ---------- + file: + Path of gaze file. + experiment : Experiment + The experiment definition. + 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, + the input data frame is assumed to contain multiple trials and the transformation + methods will be applied to each trial separately. + time_column: + The name of the timestamp column in the input data frame. + pixel_columns: + The name of the pixel position columns in the input data frame. These columns will be + nested into the column ``pixel``. If the list is empty or None, the nested ``pixel`` + column will not be created. + position_columns: + The name of the dva position columns in the input data frame. These columns will be + nested into the column ``position``. If the list is empty or None, the nested + ``position`` column will not be created. + velocity_columns: + The name of the velocity columns in the input data frame. These columns will be nested + into the column ``velocity``. If the list is empty or None, the nested ``velocity`` + column will not be created. + acceleration_columns: + The name of the acceleration columns in the input data frame. These columns will be + nested into the column ``acceleration``. If the list is empty or None, the nested + ``acceleration`` column will not be created. + **read_csv_kwargs: + Additional keyword arguments to be passed to polars to read in the csv. + + Notes + ----- + About using the arguments ``pixel_columns``, ``position_columns``, ``velocity_columns``, + and ``acceleration_columns``: + + By passing a list of columns as any of these arguments, these columns will be merged into a + single column with the corresponding name , e.g. using `pixel_columns` will merge the + respective columns into the column `pixel`. + + The supported number of component columns with the expected order are: + + * zero columns: No nested component column will be created. + * two columns: monocular data; expected order: x-component, y-component + * four columns: binocular data; expected order: x-component left eye, y-component left eye, + x-component right eye, y-component right eye, + * six columns: binocular data with additional cyclopian data; expected order: x-component + left eye, y-component left eye, x-component right eye, y-component right eye, + x-component cyclopian eye, y-component cyclopian eye, + + + Examples + -------- + First let's assume a CSV file stored `sample.csv` with the following content: + shape: (10, 3) + ┌──────┬────────────┬────────────┐ + │ time ┆ x_left_pix ┆ y_left_pix │ + │ --- ┆ --- ┆ --- │ + │ i64 ┆ i64 ┆ i64 │ + ╞══════╪════════════╪════════════╡ + │ 0 ┆ 0 ┆ 0 │ + │ 0 ┆ 0 ┆ 0 │ + │ 0 ┆ 0 ┆ 0 │ + │ 0 ┆ 0 ┆ 0 │ + │ … ┆ … ┆ … │ + │ 0 ┆ 0 ┆ 0 │ + │ 0 ┆ 0 ┆ 0 │ + │ 0 ┆ 0 ┆ 0 │ + │ 0 ┆ 0 ┆ 0 │ + └──────┴────────────┴────────────┘ + + We can now load the data into a ``GazeDataFrame`` by specyfing the experimental setting + and the names of the pixel position columns. + + >>> gaze = from_csv(file='sample.csv', + time_column = 'time', + pixel_columns = ['x_left_pix','y_left_pix'],) + >>> gaze.frame + shape: (10, 2) + ┌──────┬───────────┐ + │ time ┆ pixel │ + │ --- ┆ --- │ + │ i64 ┆ list[i64] │ + ╞══════╪═══════════╡ + │ 0 ┆ [0, 0] │ + │ 0 ┆ [0, 0] │ + │ 0 ┆ [0, 0] │ + │ 0 ┆ [0, 0] │ + │ … ┆ … │ + │ 0 ┆ [0, 0] │ + │ 0 ┆ [0, 0] │ + │ 0 ┆ [0, 0] │ + │ 0 ┆ [0, 0] │ + └──────┴───────────┘ + + """ + # read data + gaze_data = pl.read_csv(file, **read_csv_kwargs) + + # create gaze data frame + gaze_df = GazeDataFrame( + gaze_data, + experiment=experiment, + trial_columns=trial_columns, + time_column=time_column, + pixel_columns=pixel_columns, + position_columns=position_columns, + velocity_columns=velocity_columns, + acceleration_columns=acceleration_columns, + ) + return gaze_df diff --git a/tests/gaze/io/csv_test.py b/tests/gaze/io/csv_test.py new file mode 100644 index 000000000..c64264ebe --- /dev/null +++ b/tests/gaze/io/csv_test.py @@ -0,0 +1,48 @@ +# 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 read from csv.""" +import pytest + +import pymovements as pm + + +@pytest.mark.parametrize( + ('kwargs', 'shape'), + [ + pytest.param( + {'file': 'tests/gaze/io/files/monocular_example.csv', + 'time_column': 'time', 'pixel_columns': ['x_left_pix', 'y_left_pix']}, + (10, 2), + id='csv_mono_shape', + ), + pytest.param( + {'file': 'tests/gaze/io/files/binocular_example.csv', + 'time_column': 'time', + 'pixel_columns': ['x_left_pix', 'y_left_pix', 'x_right_pix', 'y_right_pix'], + 'position_columns': ['x_left_pos', 'y_left_pos', 'x_right_pos', 'y_right_pos']}, + (10, 3), + id='csv_bino_shape', + ), + ] +) +def test_shapes(kwargs, shape): + gaze_dataframe = pm.gaze.from_csv(**kwargs) + + assert gaze_dataframe.frame.shape == shape diff --git a/tests/gaze/io/files/binocular_example.csv b/tests/gaze/io/files/binocular_example.csv new file mode 100644 index 000000000..e6672e317 --- /dev/null +++ b/tests/gaze/io/files/binocular_example.csv @@ -0,0 +1,11 @@ +time,x_left_pix,y_left_pix,x_right_pix,y_right_pix,x_left_pos,y_left_pos,x_right_pos,y_right_pos +0,0,0,0,0,-23.104783, -13.489493,-23.104783, -13.489493 +0,0,0,0,0,-23.104783, -13.489493,-23.104783, -13.489493 +0,0,0,0,0,-23.104783, -13.489493,-23.104783, -13.489493 +0,0,0,0,0,-23.104783, -13.489493,-23.104783, -13.489493 +0,0,0,0,0,-23.104783, -13.489493,-23.104783, -13.489493 +0,0,0,0,0,-23.104783, -13.489493,-23.104783, -13.489493 +0,0,0,0,0,-23.104783, -13.489493,-23.104783, -13.489493 +0,0,0,0,0,-23.104783, -13.489493,-23.104783, -13.489493 +0,0,0,0,0,-23.104783, -13.489493,-23.104783, -13.489493 +0,0,0,0,0,-23.104783, -13.489493,-23.104783, -13.489493 \ No newline at end of file diff --git a/tests/gaze/io/files/monocular_example.csv b/tests/gaze/io/files/monocular_example.csv new file mode 100644 index 000000000..87891121b --- /dev/null +++ b/tests/gaze/io/files/monocular_example.csv @@ -0,0 +1,11 @@ +time,x_left_pix,y_left_pix +0,0,0 +0,0,0 +0,0,0 +0,0,0 +0,0,0 +0,0,0 +0,0,0 +0,0,0 +0,0,0 +0,0,0 \ No newline at end of file From bf9f7d860ac88f3e99ae0dabcf2f3dc41dab1f46 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Fri, 15 Sep 2023 07:26:55 +0000 Subject: [PATCH 02/23] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- tests/gaze/io/csv_test.py | 14 +++++++++----- tests/gaze/io/files/binocular_example.csv | 2 +- tests/gaze/io/files/monocular_example.csv | 2 +- 3 files changed, 11 insertions(+), 7 deletions(-) diff --git a/tests/gaze/io/csv_test.py b/tests/gaze/io/csv_test.py index c64264ebe..cd5e35abd 100644 --- a/tests/gaze/io/csv_test.py +++ b/tests/gaze/io/csv_test.py @@ -27,20 +27,24 @@ ('kwargs', 'shape'), [ pytest.param( - {'file': 'tests/gaze/io/files/monocular_example.csv', - 'time_column': 'time', 'pixel_columns': ['x_left_pix', 'y_left_pix']}, + { + 'file': 'tests/gaze/io/files/monocular_example.csv', + 'time_column': 'time', 'pixel_columns': ['x_left_pix', 'y_left_pix'], + }, (10, 2), id='csv_mono_shape', ), pytest.param( - {'file': 'tests/gaze/io/files/binocular_example.csv', + { + 'file': 'tests/gaze/io/files/binocular_example.csv', 'time_column': 'time', 'pixel_columns': ['x_left_pix', 'y_left_pix', 'x_right_pix', 'y_right_pix'], - 'position_columns': ['x_left_pos', 'y_left_pos', 'x_right_pos', 'y_right_pos']}, + 'position_columns': ['x_left_pos', 'y_left_pos', 'x_right_pos', 'y_right_pos'], + }, (10, 3), id='csv_bino_shape', ), - ] + ], ) def test_shapes(kwargs, shape): gaze_dataframe = pm.gaze.from_csv(**kwargs) diff --git a/tests/gaze/io/files/binocular_example.csv b/tests/gaze/io/files/binocular_example.csv index e6672e317..ddfd55ab4 100644 --- a/tests/gaze/io/files/binocular_example.csv +++ b/tests/gaze/io/files/binocular_example.csv @@ -8,4 +8,4 @@ time,x_left_pix,y_left_pix,x_right_pix,y_right_pix,x_left_pos,y_left_pos,x_right 0,0,0,0,0,-23.104783, -13.489493,-23.104783, -13.489493 0,0,0,0,0,-23.104783, -13.489493,-23.104783, -13.489493 0,0,0,0,0,-23.104783, -13.489493,-23.104783, -13.489493 -0,0,0,0,0,-23.104783, -13.489493,-23.104783, -13.489493 \ No newline at end of file +0,0,0,0,0,-23.104783, -13.489493,-23.104783, -13.489493 diff --git a/tests/gaze/io/files/monocular_example.csv b/tests/gaze/io/files/monocular_example.csv index 87891121b..5493f10e4 100644 --- a/tests/gaze/io/files/monocular_example.csv +++ b/tests/gaze/io/files/monocular_example.csv @@ -8,4 +8,4 @@ time,x_left_pix,y_left_pix 0,0,0 0,0,0 0,0,0 -0,0,0 \ No newline at end of file +0,0,0 From 4397cb9480ca05408172dcc25517acdec12786ec Mon Sep 17 00:00:00 2001 From: prassepaul Date: Fri, 15 Sep 2023 09:45:51 +0200 Subject: [PATCH 03/23] fix docstring error --- src/pymovements/gaze/io.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/pymovements/gaze/io.py b/src/pymovements/gaze/io.py index ad59dbd6b..dfbc41b58 100644 --- a/src/pymovements/gaze/io.py +++ b/src/pymovements/gaze/io.py @@ -119,8 +119,8 @@ def from_csv( and the names of the pixel position columns. >>> gaze = from_csv(file='sample.csv', - time_column = 'time', - pixel_columns = ['x_left_pix','y_left_pix'],) + ... time_column = 'time', + ... pixel_columns = ['x_left_pix','y_left_pix'],) >>> gaze.frame shape: (10, 2) ┌──────┬───────────┐ From aa994f008cd464e713d510981690b9bfdbfdbd28 Mon Sep 17 00:00:00 2001 From: prassepaul Date: Fri, 15 Sep 2023 10:04:29 +0200 Subject: [PATCH 04/23] docstring error --- src/pymovements/gaze/io.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/pymovements/gaze/io.py b/src/pymovements/gaze/io.py index dfbc41b58..5a06f9006 100644 --- a/src/pymovements/gaze/io.py +++ b/src/pymovements/gaze/io.py @@ -97,7 +97,7 @@ def from_csv( Examples -------- - First let's assume a CSV file stored `sample.csv` with the following content: + First let's assume a CSV file stored `tests/gaze/io/files/monocular_example.csv` with the following content: shape: (10, 3) ┌──────┬────────────┬────────────┐ │ time ┆ x_left_pix ┆ y_left_pix │ @@ -118,7 +118,7 @@ def from_csv( We can now load the data into a ``GazeDataFrame`` by specyfing the experimental setting and the names of the pixel position columns. - >>> gaze = from_csv(file='sample.csv', + >>> gaze = from_csv(file='tests/gaze/io/files/monocular_example.csv', ... time_column = 'time', ... pixel_columns = ['x_left_pix','y_left_pix'],) >>> gaze.frame From 16e3946f3d286c7bb148c4561e465111148bb21e Mon Sep 17 00:00:00 2001 From: prassepaul Date: Fri, 15 Sep 2023 10:12:19 +0200 Subject: [PATCH 05/23] flake8 issue --- src/pymovements/gaze/io.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/pymovements/gaze/io.py b/src/pymovements/gaze/io.py index 5a06f9006..50d0a6c69 100644 --- a/src/pymovements/gaze/io.py +++ b/src/pymovements/gaze/io.py @@ -97,7 +97,8 @@ def from_csv( Examples -------- - First let's assume a CSV file stored `tests/gaze/io/files/monocular_example.csv` with the following content: + First let's assume a CSV file stored `tests/gaze/io/files/monocular_example.csv` + with the following content: shape: (10, 3) ┌──────┬────────────┬────────────┐ │ time ┆ x_left_pix ┆ y_left_pix │ @@ -118,7 +119,8 @@ def from_csv( We can now load the data into a ``GazeDataFrame`` by specyfing the experimental setting and the names of the pixel position columns. - >>> gaze = from_csv(file='tests/gaze/io/files/monocular_example.csv', + >>> gaze = from_csv( + ... file='tests/gaze/io/files/monocular_example.csv', ... time_column = 'time', ... pixel_columns = ['x_left_pix','y_left_pix'],) >>> gaze.frame From 493efdb5deae0725f65586b3b9cfa8e82ff79d2f Mon Sep 17 00:00:00 2001 From: prassepaul Date: Fri, 15 Sep 2023 11:02:05 +0200 Subject: [PATCH 06/23] cyclic import --- src/pymovements/gaze/io.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/pymovements/gaze/io.py b/src/pymovements/gaze/io.py index 50d0a6c69..0edd6da04 100644 --- a/src/pymovements/gaze/io.py +++ b/src/pymovements/gaze/io.py @@ -25,8 +25,8 @@ import polars as pl -from pymovements.gaze import Experiment -from pymovements.gaze.gaze_dataframe import GazeDataFrame +from pymovements.gaze import Experiment # pylint: disable=cyclic-import +from pymovements.gaze.gaze_dataframe import GazeDataFrame # pylint: disable=cyclic-import def from_csv( From 926e27cc8a203b220094e7f0e50bbc41b55027c6 Mon Sep 17 00:00:00 2001 From: prassepaul Date: Tue, 19 Sep 2023 09:52:53 +0200 Subject: [PATCH 07/23] added import to docstring --- src/pymovements/gaze/io.py | 1 + 1 file changed, 1 insertion(+) diff --git a/src/pymovements/gaze/io.py b/src/pymovements/gaze/io.py index 0edd6da04..81bd391f7 100644 --- a/src/pymovements/gaze/io.py +++ b/src/pymovements/gaze/io.py @@ -119,6 +119,7 @@ def from_csv( We can now load the data into a ``GazeDataFrame`` by specyfing the experimental setting and the names of the pixel position columns. + >>> from pymovements.gaze.io import from_csv >>> gaze = from_csv( ... file='tests/gaze/io/files/monocular_example.csv', ... time_column = 'time', From 9b074ae75612e1fb1e6412fdfe1c3643cf7c31a4 Mon Sep 17 00:00:00 2001 From: prassepaul Date: Thu, 21 Sep 2023 14:35:29 +0200 Subject: [PATCH 08/23] added dataset gaze_on_faces --- docs/source/bibliography.bib | 11 ++ src/pymovements/datasets/__init__.py | 3 + src/pymovements/datasets/gaze_on_faces.py | 148 ++++++++++++++++++++++ tests/datasets/gaze_on_faces_test.py | 79 ++++++++++++ 4 files changed, 241 insertions(+) create mode 100644 src/pymovements/datasets/gaze_on_faces.py create mode 100644 tests/datasets/gaze_on_faces_test.py diff --git a/docs/source/bibliography.bib b/docs/source/bibliography.bib index 0992286e2..c6fec225c 100644 --- a/docs/source/bibliography.bib +++ b/docs/source/bibliography.bib @@ -65,3 +65,14 @@ @article{GazeBaseVR journal = {Scientific Data}, doi = {10.1038/s41597-023-02075-5}, } + +@article{GazeOnFaces, + title={Face exploration dynamics differentiate men and women}, + author={Coutrot, Antoine and Binetti, Nicola and Harrison, Charlotte and Mareschal, Isabelle and Johnston, Alan}, + journal={Journal of vision}, + volume={16}, + number={14}, + pages={16--16}, + year={2016}, + publisher={The Association for Research in Vision and Ophthalmology} +} \ No newline at end of file diff --git a/src/pymovements/datasets/__init__.py b/src/pymovements/datasets/__init__.py index b1eb00ee2..b0f18e6ed 100644 --- a/src/pymovements/datasets/__init__.py +++ b/src/pymovements/datasets/__init__.py @@ -28,6 +28,7 @@ pymovements.datasets.GazeBase pymovements.datasets.GazeBaseVR pymovements.datasets.JuDo1000 + pymovements.datasets.GazeOnFaces .. rubric:: Example Datasets @@ -42,6 +43,7 @@ from pymovements.datasets.gazebase import GazeBase from pymovements.datasets.gazebasevr import GazeBaseVR from pymovements.datasets.judo1000 import JuDo1000 +from pymovements.datasets.gaze_on_faces import GazeOnFaces from pymovements.datasets.toy_dataset import ToyDataset from pymovements.datasets.toy_dataset_eyelink import ToyDatasetEyeLink @@ -50,6 +52,7 @@ 'GazeBase', 'GazeBaseVR', 'JuDo1000', + 'GazeOnFaces', 'ToyDataset', 'ToyDatasetEyeLink', ] diff --git a/src/pymovements/datasets/gaze_on_faces.py b/src/pymovements/datasets/gaze_on_faces.py new file mode 100644 index 000000000..69e6a70e4 --- /dev/null +++ b/src/pymovements/datasets/gaze_on_faces.py @@ -0,0 +1,148 @@ +# Copyright (c) 2022-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. +"""This module provides an interface to the GazeOnFaces dataset.""" +from __future__ import annotations + +from dataclasses import dataclass +from dataclasses import field +from typing import Any +import polars as pl + +from pymovements.dataset.dataset_definition import DatasetDefinition +from pymovements.dataset.dataset_library import register_dataset +from pymovements.gaze.experiment import Experiment + + +@dataclass +@register_dataset +class GazeOnFaces(DatasetDefinition): + """GazeBaseVR dataset :cite:p:`GazeOnFaces`. + + This dataset includes monocular eye tracking data from single participants in a single + session. Eye movements are recorded at a sampling frequency of 60 Hz + using an EyeLink 1000 video-based eye tracker and are provided as pixel coordinates. + + Participants were sat 57 cm away from the screen (19inch LCD monitor, + screen res=1280×1024, 60 Hz). Recordings of the eye movements of one eye in monocular + pupil/corneal reflection tracking mode. + + Check the respective paper for details :cite:p:`GazeOnFaces`. + + Attributes + ---------- + name : str + The name of the dataset. + + mirrors : tuple[str, ...] + A tuple of mirrors of the dataset. Each entry must be of type `str` and end with a '/'. + + resources : tuple[dict[str, str], ...] + A tuple of dataset resources. Each list entry must be a dictionary with the following keys: + - `resource`: The url suffix of the resource. This will be concatenated with the mirror. + - `filename`: The filename under which the file is saved as. + - `md5`: The MD5 checksum of the respective file. + + experiment : Experiment + The experiment definition. + + filename_format : str + Regular expression which will be matched before trying to load the file. Namedgroups will + appear in the `fileinfo` dataframe. + + filename_format_dtypes : dict[str, type], optional + If named groups are present in the `filename_format`, this makes it possible to cast + specific named groups to a particular datatype. + + column_map : dict[str, str] + The keys are the columns to read, the values are the names to which they should be renamed. + + custom_read_kwargs : dict[str, Any], optional + If specified, these keyword arguments will be passed to the file reading function. + + Examples + -------- + Initialize your :py:class:`~pymovements.PublicDataset` object with the + :py:class:`~pymovements.GazeOnFaces` definition: + + >>> import pymovements as pm + >>> + >>> dataset = pm.Dataset("GazeOnFaces", path='data/GazeOnFaces') + + Download the dataset resources resources: + + >>> dataset.download()# doctest: +SKIP + + Load the data into memory: + + >>> dataset.load()# doctest: +SKIP + """ + + # pylint: disable=similarities + # The PublicDatasetDefinition child classes potentially share code chunks for definitions. + + name: str = 'GazeOnFaces' + + mirrors: tuple[str, ...] = ( + 'https://uncloud.univ-nantes.fr/index.php/s/', + ) + + resources: tuple[dict[str, str], ...] = ( + { + 'resource': '8KW6dEdyBJqxpmo/download?path=%2F&files=gaze_csv.zip', + 'filename': 'gaze_csv.zip', + 'md5': 'fe219f07c9253cd9aaee6bd50233c034', + }, + ) + + experiment: Experiment = Experiment( + screen_width_px=1280, + screen_height_px=1024, + screen_width_cm=38, + screen_height_cm=30, + distance_cm=57, + origin='center', + sampling_rate=60, + ) + + filename_format: str = r'gaze_sub{sub_id:d}_trial{trial_id:d}.csv' + + filename_format_dtypes: dict[str, type] = field( + default_factory=lambda: { + 'sub_id': int, + 'trial_id': int, + }, + ) + + trial_columns: list[str] = field(default_factory=lambda: ['sub_id', 'trial_id']) + + time_column: Any = None + + pixel_columns: list[str] = field(default_factory=lambda: ['x', 'y']) + + column_map: dict[str, str] = field(default_factory=lambda: {}) + + custom_read_kwargs: dict[str, Any] = field( + default_factory=lambda: { + 'separator': ',', + 'has_header': False, + 'new_columns': ['x', 'y'], + 'dtypes': [pl.Float32, pl.Float32], + }, + ) diff --git a/tests/datasets/gaze_on_faces_test.py b/tests/datasets/gaze_on_faces_test.py new file mode 100644 index 000000000..86e28e444 --- /dev/null +++ b/tests/datasets/gaze_on_faces_test.py @@ -0,0 +1,79 @@ +# 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 all functionality in pymovements.dataset.gaze_on_faces.""" +from pathlib import Path + +import pytest + +import pymovements as pm + + +@pytest.mark.parametrize( + 'init_path, expected_paths', + [ + pytest.param( + '/data/set/path', + { + 'root': Path('/data/set/path/'), + 'dataset': Path('/data/set/path/'), + 'download': Path('/data/set/path/downloads'), + }, + ), + pytest.param( + pm.DatasetPaths(root='/data/set/path'), + { + 'root': Path('/data/set/path/'), + 'dataset': Path('/data/set/path/GazeOnFaces'), + 'download': Path('/data/set/path/GazeOnFaces/downloads'), + }, + ), + pytest.param( + pm.DatasetPaths(root='/data/set/path', dataset='.'), + { + 'root': Path('/data/set/path/'), + 'dataset': Path('/data/set/path/'), + 'download': Path('/data/set/path/downloads'), + }, + ), + pytest.param( + pm.DatasetPaths(root='/data/set/path', dataset='dataset'), + { + 'root': Path('/data/set/path/'), + 'dataset': Path('/data/set/path/dataset'), + 'download': Path('/data/set/path/dataset/downloads'), + }, + ), + pytest.param( + pm.DatasetPaths(root='/data/set/path', downloads='custom_downloads'), + { + 'root': Path('/data/set/path/'), + 'dataset': Path('/data/set/path/GazeOnFaces'), + 'download': Path('/data/set/path/GazeOnFaces/custom_downloads'), + }, + ), + ], +) +def test_paths(init_path, expected_paths): + dataset = pm.Dataset(pm.datasets.GazeOnFaces, path=init_path) + + assert dataset.paths.root == expected_paths['root'] + assert dataset.path == expected_paths['dataset'] + assert dataset.paths.dataset == expected_paths['dataset'] + assert dataset.paths.downloads == expected_paths['download'] From c026c14b93c2b7b39c80b8adb945ccc28fa141e1 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Fri, 22 Sep 2023 08:29:52 +0000 Subject: [PATCH 09/23] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- src/pymovements/datasets/__init__.py | 1 - 1 file changed, 1 deletion(-) diff --git a/src/pymovements/datasets/__init__.py b/src/pymovements/datasets/__init__.py index b881e9fca..114210cce 100644 --- a/src/pymovements/datasets/__init__.py +++ b/src/pymovements/datasets/__init__.py @@ -45,7 +45,6 @@ from pymovements.datasets.gazebase import GazeBase from pymovements.datasets.gazebasevr import GazeBaseVR from pymovements.datasets.judo1000 import JuDo1000 -from pymovements.datasets.gaze_on_faces import GazeOnFaces from pymovements.datasets.toy_dataset import ToyDataset from pymovements.datasets.toy_dataset_eyelink import ToyDatasetEyeLink From 12f437a9791ad229c7e59ceb8ee28f5589396c68 Mon Sep 17 00:00:00 2001 From: prassepaul Date: Fri, 22 Sep 2023 12:52:55 +0200 Subject: [PATCH 10/23] from_ipc --- src/pymovements/gaze/__init__.py | 2 + src/pymovements/gaze/io.py | 92 +++++++++++++++--- tests/gaze/io/files/binocular_example.csv | 18 ++-- tests/gaze/io/files/binocular_example.feather | Bin 0 -> 2239 bytes tests/gaze/io/files/monocular_example.csv | 18 ++-- tests/gaze/io/files/monocular_example.feather | Bin 0 -> 1295 bytes tests/gaze/io/ipc_test.py | 48 +++++++++ 7 files changed, 146 insertions(+), 32 deletions(-) create mode 100644 tests/gaze/io/files/binocular_example.feather create mode 100644 tests/gaze/io/files/monocular_example.feather create mode 100644 tests/gaze/io/ipc_test.py diff --git a/src/pymovements/gaze/__init__.py b/src/pymovements/gaze/__init__.py index 0e2ceed5a..dc7d3cf18 100644 --- a/src/pymovements/gaze/__init__.py +++ b/src/pymovements/gaze/__init__.py @@ -63,6 +63,7 @@ from pymovements.gaze.integration import from_numpy from pymovements.gaze.integration import from_pandas from pymovements.gaze.io import from_csv +from pymovements.gaze.io import from_ipc from pymovements.gaze.screen import Screen @@ -75,4 +76,5 @@ 'transforms_numpy', 'transforms', 'from_csv', + 'from_ipc', ] diff --git a/src/pymovements/gaze/io.py b/src/pymovements/gaze/io.py index 81bd391f7..09f1effaf 100644 --- a/src/pymovements/gaze/io.py +++ b/src/pymovements/gaze/io.py @@ -106,14 +106,14 @@ def from_csv( │ i64 ┆ i64 ┆ i64 │ ╞══════╪════════════╪════════════╡ │ 0 ┆ 0 ┆ 0 │ - │ 0 ┆ 0 ┆ 0 │ - │ 0 ┆ 0 ┆ 0 │ - │ 0 ┆ 0 ┆ 0 │ + │ 1 ┆ 0 ┆ 0 │ + │ 2 ┆ 0 ┆ 0 │ + │ 3 ┆ 0 ┆ 0 │ │ … ┆ … ┆ … │ - │ 0 ┆ 0 ┆ 0 │ - │ 0 ┆ 0 ┆ 0 │ - │ 0 ┆ 0 ┆ 0 │ - │ 0 ┆ 0 ┆ 0 │ + │ 6 ┆ 0 ┆ 0 │ + │ 7 ┆ 0 ┆ 0 │ + │ 8 ┆ 0 ┆ 0 │ + │ 9 ┆ 0 ┆ 0 │ └──────┴────────────┴────────────┘ We can now load the data into a ``GazeDataFrame`` by specyfing the experimental setting @@ -132,14 +132,14 @@ def from_csv( │ i64 ┆ list[i64] │ ╞══════╪═══════════╡ │ 0 ┆ [0, 0] │ - │ 0 ┆ [0, 0] │ - │ 0 ┆ [0, 0] │ - │ 0 ┆ [0, 0] │ + │ 1 ┆ [0, 0] │ + │ 2 ┆ [0, 0] │ + │ 3 ┆ [0, 0] │ │ … ┆ … │ - │ 0 ┆ [0, 0] │ - │ 0 ┆ [0, 0] │ - │ 0 ┆ [0, 0] │ - │ 0 ┆ [0, 0] │ + │ 6 ┆ [0, 0] │ + │ 7 ┆ [0, 0] │ + │ 8 ┆ [0, 0] │ + │ 9 ┆ [0, 0] │ └──────┴───────────┘ """ @@ -158,3 +158,67 @@ def from_csv( acceleration_columns=acceleration_columns, ) return gaze_df + + +def from_ipc( + file: str | Path, + experiment: Experiment | None = None, + *, + trial_columns: list[str] | None = None, + time_column: str | None = None, + pixel_columns: list[str] | None = None, + position_columns: list[str] | None = None, + velocity_columns: list[str] | None = None, + acceleration_columns: list[str] | None = None, + **read_csv_kwargs: Any, +) -> GazeDataFrame: + """Initialize a :py:class:`pymovements.gaze.gaze_dataframe.GazeDataFrame`. + + Parameters + ---------- + file: + Path of IPC/feather file. + experiment : Experiment + The experiment definition. + **read_csv_kwargs: + Additional keyword arguments to be passed to polars to read in the csv. + + Examples + -------- + First let's assume a IPC file stored `tests/gaze/io/files/monocular_example.feather` + + We can now load the data into a ``GazeDataFrame`` by specyfing the experimental setting + + >>> from pymovements.gaze.io import from_ipc + >>> gaze = from_ipc( + ... file='tests/gaze/io/files/monocular_example.csv', + ... ) + >>> gaze.frame + shape: (10, 2) + ┌──────┬───────────┐ + │ time ┆ pixel │ + │ --- ┆ --- │ + │ i64 ┆ list[i64] │ + ╞══════╪═══════════╡ + │ 0 ┆ [0, 0] │ + │ 1 ┆ [0, 0] │ + │ 2 ┆ [0, 0] │ + │ 3 ┆ [0, 0] │ + │ … ┆ … │ + │ 6 ┆ [0, 0] │ + │ 7 ┆ [0, 0] │ + │ 8 ┆ [0, 0] │ + │ 9 ┆ [0, 0] │ + └──────┴───────────┘ + + """ + # read data + gaze_data = pl.read_ipc(file, **read_csv_kwargs) + + # create gaze data frame + gaze_df = GazeDataFrame( + gaze_data, + experiment=experiment, + trial_columns=trial_columns, + ) + return gaze_df diff --git a/tests/gaze/io/files/binocular_example.csv b/tests/gaze/io/files/binocular_example.csv index ddfd55ab4..a1f3c95f6 100644 --- a/tests/gaze/io/files/binocular_example.csv +++ b/tests/gaze/io/files/binocular_example.csv @@ -1,11 +1,11 @@ time,x_left_pix,y_left_pix,x_right_pix,y_right_pix,x_left_pos,y_left_pos,x_right_pos,y_right_pos 0,0,0,0,0,-23.104783, -13.489493,-23.104783, -13.489493 -0,0,0,0,0,-23.104783, -13.489493,-23.104783, -13.489493 -0,0,0,0,0,-23.104783, -13.489493,-23.104783, -13.489493 -0,0,0,0,0,-23.104783, -13.489493,-23.104783, -13.489493 -0,0,0,0,0,-23.104783, -13.489493,-23.104783, -13.489493 -0,0,0,0,0,-23.104783, -13.489493,-23.104783, -13.489493 -0,0,0,0,0,-23.104783, -13.489493,-23.104783, -13.489493 -0,0,0,0,0,-23.104783, -13.489493,-23.104783, -13.489493 -0,0,0,0,0,-23.104783, -13.489493,-23.104783, -13.489493 -0,0,0,0,0,-23.104783, -13.489493,-23.104783, -13.489493 +1,0,0,0,0,-23.104783, -13.489493,-23.104783, -13.489493 +2,0,0,0,0,-23.104783, -13.489493,-23.104783, -13.489493 +3,0,0,0,0,-23.104783, -13.489493,-23.104783, -13.489493 +4,0,0,0,0,-23.104783, -13.489493,-23.104783, -13.489493 +5,0,0,0,0,-23.104783, -13.489493,-23.104783, -13.489493 +6,0,0,0,0,-23.104783, -13.489493,-23.104783, -13.489493 +7,0,0,0,0,-23.104783, -13.489493,-23.104783, -13.489493 +8,0,0,0,0,-23.104783, -13.489493,-23.104783, -13.489493 +9,0,0,0,0,-23.104783, -13.489493,-23.104783, -13.489493 diff --git a/tests/gaze/io/files/binocular_example.feather b/tests/gaze/io/files/binocular_example.feather new file mode 100644 index 0000000000000000000000000000000000000000..e52e31be25c842e5c9a709640379f21d53dfa3c2 GIT binary patch literal 2239 zcmeHJy-EW?5S}DwyqpJ~Q6Y#$3JVbgt+Wv?ot+_#K7c6>j0u<`r4L|XV=KOhaPN?J zun;S;kRZhG+uf1lh~f`wlR5TgXXe|Py_vh&YqVMihub1!&}Biy6Ol*Em%u%NLIRl+ zA9qilp%0<)anV}By#~mIkwhL~?_qvSJw{rN; z1YA+dxIA1UNx$8p{snq!xRYM@Ea@lRQ|jq=0Dgk;nLX?RS66It@*bYxGX{p)@TFp+ zPTI>1?7d{rW^C@`C%*ehrwuozF8c_qIWrUJZGB1-%A(0z2*Vv>23!P6F^I7gVD14M zp^ooU)0xh55S5Qq&qQR&$+2?eF-x(Nb$yz%tE}s}mbuLDH7~DcgG|8Z?OC`!f^LQH zGKqO6F+Qnmnan!Q_l;Ds?Av#ZqyG3^ks{09YFBges^fLX*Bt-o869NGejx+@Rlmk& zd49OEliu%L4X;NV=~Vt&KF}h^d>!<=W1o&8vsc@mD(I6{e1|++?0I$LnnhEah87=d SNYiuikI%(h6ry2%TwVb)SUQOS literal 0 HcmV?d00001 diff --git a/tests/gaze/io/files/monocular_example.csv b/tests/gaze/io/files/monocular_example.csv index 5493f10e4..461d3e5f9 100644 --- a/tests/gaze/io/files/monocular_example.csv +++ b/tests/gaze/io/files/monocular_example.csv @@ -1,11 +1,11 @@ time,x_left_pix,y_left_pix 0,0,0 -0,0,0 -0,0,0 -0,0,0 -0,0,0 -0,0,0 -0,0,0 -0,0,0 -0,0,0 -0,0,0 +1,0,0 +2,0,0 +3,0,0 +4,0,0 +5,0,0 +6,0,0 +7,0,0 +8,0,0 +9,0,0 diff --git a/tests/gaze/io/files/monocular_example.feather b/tests/gaze/io/files/monocular_example.feather new file mode 100644 index 0000000000000000000000000000000000000000..758a962f8f806f49564c41485f7c3d2ef898cedf GIT binary patch literal 1295 zcmd^JNeGxD4 z?4sXy%bCCZ*W8P4K67qTuV0Y4-X|9UVqb=;bsTE0VLZdFVpyxhiDm`AfZJ#<#eWmm z$+_wUvwfTWsfKKZ#$Y`DIHVzZt|hzsw(qd1@Jt!*yOI0mi8JzDJcq_%6m&SEO>&d( xm`eOt;dyn!+$ZnB7~6XmXI9K%OP{<&aTiBvp5EXXdB&rq?b-JW8Zik!!f%lQb6)@e literal 0 HcmV?d00001 diff --git a/tests/gaze/io/ipc_test.py b/tests/gaze/io/ipc_test.py new file mode 100644 index 000000000..7a91817fa --- /dev/null +++ b/tests/gaze/io/ipc_test.py @@ -0,0 +1,48 @@ +# 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 read from IPC/feather.""" +import pytest + +import pymovements as pm + + +@pytest.mark.parametrize( + ('kwargs', 'shape'), + [ + pytest.param( + { + 'file': 'tests/gaze/io/files/monocular_example.feather', + }, + (10, 2), + id='feather_mono_shape', + ), + pytest.param( + { + 'file': 'tests/gaze/io/files/binocular_example.feather', + }, + (10, 3), + id='feather_bino_shape', + ), + ], +) +def test_shapes(kwargs, shape): + gaze_dataframe = pm.gaze.from_ipc(**kwargs) + + assert gaze_dataframe.frame.shape == shape From 0370b63764e22603acef53aaba3458b59458ebf0 Mon Sep 17 00:00:00 2001 From: prassepaul Date: Fri, 22 Sep 2023 13:02:11 +0200 Subject: [PATCH 11/23] error in path --- src/pymovements/gaze/io.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pymovements/gaze/io.py b/src/pymovements/gaze/io.py index 09f1effaf..cdd7b9fe5 100644 --- a/src/pymovements/gaze/io.py +++ b/src/pymovements/gaze/io.py @@ -191,7 +191,7 @@ def from_ipc( >>> from pymovements.gaze.io import from_ipc >>> gaze = from_ipc( - ... file='tests/gaze/io/files/monocular_example.csv', + ... file='tests/gaze/io/files/monocular_example.feather', ... ) >>> gaze.frame shape: (10, 2) From a45c1f3fa6bb5f097d23040bcba8fb276be4ef7e Mon Sep 17 00:00:00 2001 From: prassepaul Date: Fri, 22 Sep 2023 13:03:47 +0200 Subject: [PATCH 12/23] unused arguments --- src/pymovements/gaze/io.py | 6 ------ 1 file changed, 6 deletions(-) diff --git a/src/pymovements/gaze/io.py b/src/pymovements/gaze/io.py index cdd7b9fe5..ca057a17c 100644 --- a/src/pymovements/gaze/io.py +++ b/src/pymovements/gaze/io.py @@ -164,12 +164,6 @@ def from_ipc( file: str | Path, experiment: Experiment | None = None, *, - trial_columns: list[str] | None = None, - time_column: str | None = None, - pixel_columns: list[str] | None = None, - position_columns: list[str] | None = None, - velocity_columns: list[str] | None = None, - acceleration_columns: list[str] | None = None, **read_csv_kwargs: Any, ) -> GazeDataFrame: """Initialize a :py:class:`pymovements.gaze.gaze_dataframe.GazeDataFrame`. From 1366a756f9aca10e471f6a33690a37c2cc0dd9fc Mon Sep 17 00:00:00 2001 From: prassepaul Date: Fri, 22 Sep 2023 14:10:04 +0200 Subject: [PATCH 13/23] bug fix --- src/pymovements/gaze/io.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/pymovements/gaze/io.py b/src/pymovements/gaze/io.py index ca057a17c..91075f275 100644 --- a/src/pymovements/gaze/io.py +++ b/src/pymovements/gaze/io.py @@ -163,7 +163,6 @@ def from_csv( def from_ipc( file: str | Path, experiment: Experiment | None = None, - *, **read_csv_kwargs: Any, ) -> GazeDataFrame: """Initialize a :py:class:`pymovements.gaze.gaze_dataframe.GazeDataFrame`. @@ -213,6 +212,5 @@ def from_ipc( gaze_df = GazeDataFrame( gaze_data, experiment=experiment, - trial_columns=trial_columns, ) return gaze_df From 3e50091b5c46f71fa7190cc14dea24b5105a69bf Mon Sep 17 00:00:00 2001 From: prassepaul Date: Fri, 22 Sep 2023 14:20:57 +0200 Subject: [PATCH 14/23] fixed time column in feather file --- tests/gaze/io/files/monocular_example.feather | Bin 1295 -> 1295 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/tests/gaze/io/files/monocular_example.feather b/tests/gaze/io/files/monocular_example.feather index 758a962f8f806f49564c41485f7c3d2ef898cedf..cdb352ff82b8cf8a51e2ff5652e445dbc9dc9558 100644 GIT binary patch delta 79 ycmeC@>gU=Zz%+R;BfAv?69WV=LunQ$%?hR2pfo#_=77?ilNp)pH&0-yX955iR0d7} delta 16 XcmeC@>gU=Zz%=;-Q^V#7O!Z6vF>D3E From 6de384b00ece43a0c0267f821b6d603fb0e49045 Mon Sep 17 00:00:00 2001 From: prassepaul Date: Fri, 22 Sep 2023 14:51:29 +0200 Subject: [PATCH 15/23] requested changes --- src/pymovements/datasets/__init__.py | 4 +--- src/pymovements/gaze/io.py | 10 +++++----- 2 files changed, 6 insertions(+), 8 deletions(-) diff --git a/src/pymovements/datasets/__init__.py b/src/pymovements/datasets/__init__.py index 114210cce..db9786c47 100644 --- a/src/pymovements/datasets/__init__.py +++ b/src/pymovements/datasets/__init__.py @@ -29,7 +29,6 @@ pymovements.datasets.GazeBaseVR pymovements.datasets.GazeOnFaces pymovements.datasets.JuDo1000 - pymovements.datasets.GazeOnFaces .. rubric:: Example Datasets @@ -41,9 +40,9 @@ pymovements.datasets.ToyDataset pymovements.datasets.ToyDatasetEyeLink """ -from pymovements.datasets.gaze_on_faces import GazeOnFaces from pymovements.datasets.gazebase import GazeBase from pymovements.datasets.gazebasevr import GazeBaseVR +from pymovements.datasets.gaze_on_faces import GazeOnFaces from pymovements.datasets.judo1000 import JuDo1000 from pymovements.datasets.toy_dataset import ToyDataset from pymovements.datasets.toy_dataset_eyelink import ToyDatasetEyeLink @@ -54,7 +53,6 @@ 'GazeBaseVR', 'GazeOnFaces', 'JuDo1000', - 'GazeOnFaces', 'ToyDataset', 'ToyDatasetEyeLink', ] diff --git a/src/pymovements/gaze/io.py b/src/pymovements/gaze/io.py index 91075f275..277da2815 100644 --- a/src/pymovements/gaze/io.py +++ b/src/pymovements/gaze/io.py @@ -163,7 +163,7 @@ def from_csv( def from_ipc( file: str | Path, experiment: Experiment | None = None, - **read_csv_kwargs: Any, + **read_ipc_kwargs: Any, ) -> GazeDataFrame: """Initialize a :py:class:`pymovements.gaze.gaze_dataframe.GazeDataFrame`. @@ -173,14 +173,14 @@ def from_ipc( Path of IPC/feather file. experiment : Experiment The experiment definition. - **read_csv_kwargs: - Additional keyword arguments to be passed to polars to read in the csv. + **read_ipc_kwargs: + Additional keyword arguments to be passed to polars to read in the ipc file. Examples -------- First let's assume a IPC file stored `tests/gaze/io/files/monocular_example.feather` - We can now load the data into a ``GazeDataFrame`` by specyfing the experimental setting + We can now load the data into a ``GazeDataFrame`` >>> from pymovements.gaze.io import from_ipc >>> gaze = from_ipc( @@ -206,7 +206,7 @@ def from_ipc( """ # read data - gaze_data = pl.read_ipc(file, **read_csv_kwargs) + gaze_data = pl.read_ipc(file, **read_ipc_kwargs) # create gaze data frame gaze_df = GazeDataFrame( From 8bb304736c231b860975e0dc9af4b8809e5a0961 Mon Sep 17 00:00:00 2001 From: prassepaul Date: Mon, 25 Sep 2023 11:36:52 +0200 Subject: [PATCH 16/23] sbset dataset --- docs/source/bibliography.bib | 10 ++ src/pymovements/datasets/__init__.py | 3 + src/pymovements/datasets/gaze_on_faces.py | 2 +- src/pymovements/datasets/sb_sat.py | 147 ++++++++++++++++++++++ tests/datasets/datasets_test.py | 2 + tests/datasets/sbsat_test.py | 79 ++++++++++++ 6 files changed, 242 insertions(+), 1 deletion(-) create mode 100644 src/pymovements/datasets/sb_sat.py create mode 100644 tests/datasets/sbsat_test.py diff --git a/docs/source/bibliography.bib b/docs/source/bibliography.bib index f240e2163..14ebb19f6 100644 --- a/docs/source/bibliography.bib +++ b/docs/source/bibliography.bib @@ -76,3 +76,13 @@ @article{GazeOnFaces year={2016}, publisher={The Association for Research in Vision and Ophthalmology}, } + +@article{SB-SAT, + title = {Towards predicting reading comprehension from gaze behavior}, + year = {2020}, + booktitle = {Proceedings of the ACM Symposium on Eye Tracking Research and Applications}, + author = {Ahn, Seoyoung and Kelton, Conor and Balasubramanian, Aruna and Zelinsky, Greg}, + pages = {1--5}, + publisher = {Association for Computing Machinery}, + address = "Stuttgart, Germany", +} diff --git a/src/pymovements/datasets/__init__.py b/src/pymovements/datasets/__init__.py index db9786c47..2a5547296 100644 --- a/src/pymovements/datasets/__init__.py +++ b/src/pymovements/datasets/__init__.py @@ -29,6 +29,7 @@ pymovements.datasets.GazeBaseVR pymovements.datasets.GazeOnFaces pymovements.datasets.JuDo1000 + pymovements.datasets.SBSAT .. rubric:: Example Datasets @@ -44,6 +45,7 @@ from pymovements.datasets.gazebasevr import GazeBaseVR from pymovements.datasets.gaze_on_faces import GazeOnFaces from pymovements.datasets.judo1000 import JuDo1000 +from pymovements.datasets.sb_sat import SBSAT from pymovements.datasets.toy_dataset import ToyDataset from pymovements.datasets.toy_dataset_eyelink import ToyDatasetEyeLink @@ -53,6 +55,7 @@ 'GazeBaseVR', 'GazeOnFaces', 'JuDo1000', + 'SBSAT', 'ToyDataset', 'ToyDatasetEyeLink', ] diff --git a/src/pymovements/datasets/gaze_on_faces.py b/src/pymovements/datasets/gaze_on_faces.py index 0a5795dfe..7ed3d125f 100644 --- a/src/pymovements/datasets/gaze_on_faces.py +++ b/src/pymovements/datasets/gaze_on_faces.py @@ -34,7 +34,7 @@ @dataclass @register_dataset class GazeOnFaces(DatasetDefinition): - """GazeBaseVR dataset :cite:p:`GazeOnFaces`. + """GazeOnFaces dataset :cite:p:`GazeOnFaces`. This dataset includes monocular eye tracking data from single participants in a single session. Eye movements are recorded at a sampling frequency of 60 Hz diff --git a/src/pymovements/datasets/sb_sat.py b/src/pymovements/datasets/sb_sat.py new file mode 100644 index 000000000..3885f6201 --- /dev/null +++ b/src/pymovements/datasets/sb_sat.py @@ -0,0 +1,147 @@ +# Copyright (c) 2022-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. +"""This module provides an interface to the GazeOnFaces dataset.""" +from __future__ import annotations + +from dataclasses import dataclass +from dataclasses import field +from typing import Any + +import polars as pl + +from pymovements.dataset.dataset_definition import DatasetDefinition +from pymovements.dataset.dataset_library import register_dataset +from pymovements.gaze.experiment import Experiment + + +@dataclass +@register_dataset +class SBSAT(DatasetDefinition): + """SB-SAT dataset :cite:p:`SB-SAT`. + + This dataset includes monocular eye tracking data from a single participants in a single + session. Eye movements are recorded at a sampling frequency of 1,000 Hz using an EyeLink 1000 + eye tracker and are provided as pixel coordinates. + + The participant is instructed to read texts and answer questions. + + Check the respective paper for details :cite:p:`SB-SAT`. + + Attributes + ---------- + name : str + The name of the dataset. + + mirrors : tuple[str, ...] + A tuple of mirrors of the dataset. Each entry must be of type `str` and end with a '/'. + + resources : tuple[dict[str, str], ...] + A tuple of dataset resources. Each list entry must be a dictionary with the following keys: + - `resource`: The url suffix of the resource. This will be concatenated with the mirror. + - `filename`: The filename under which the file is saved as. + - `md5`: The MD5 checksum of the respective file. + + experiment : Experiment + The experiment definition. + + filename_format : str + Regular expression which will be matched before trying to load the file. Namedgroups will + appear in the `fileinfo` dataframe. + + filename_format_dtypes : dict[str, type], optional + If named groups are present in the `filename_format`, this makes it possible to cast + specific named groups to a particular datatype. + + column_map : dict[str, str] + The keys are the columns to read, the values are the names to which they should be renamed. + + custom_read_kwargs : dict[str, Any], optional + If specified, these keyword arguments will be passed to the file reading function. + + Examples + -------- + Initialize your :py:class:`~pymovements.PublicDataset` object with the + :py:class:`~pymovements.GazeOnFaces` definition: + + >>> import pymovements as pm + >>> + >>> dataset = pm.Dataset("SBSAT", path='data/SBSAT') + + Download the dataset resources resources: + + >>> dataset.download()# doctest: +SKIP + + Load the data into memory: + + >>> dataset.load()# doctest: +SKIP + """ + + # pylint: disable=similarities + # The PublicDatasetDefinition child classes potentially share code chunks for definitions. + + name: str = 'SBSAT' + + mirrors: tuple[str, ...] = ( + 'https://files.de-1.osf.io/v1/resources/cdx69/providers/osfstorage/', + ) + + resources: tuple[dict[str, str], ...] = ( + { + 'resource': '64525979230ea6163c031267/?zip=', + 'filename': 'csvs.zip', + 'md5': '3cf074c93266b723437cf887f948c993', + }, + ) + + experiment: Experiment = Experiment( + screen_width_px=768, + screen_height_px=1024, + screen_width_cm=42.4, + screen_height_cm=44.5, + distance_cm=70, + origin='center', + sampling_rate=1000, + ) + + filename_format: str = r'msd{subject_id:d}.csv' + + filename_format_dtypes: dict[str, type] = field( + default_factory=lambda: { + 'subject_id': int, + }, + ) + + trial_columns: list[str] = field(default_factory=lambda: ['book_name', 'screen_id']) + + time_column: str = 'time' + + pixel_columns: list[str] = field(default_factory=lambda: ['x_left', 'y_left']) + + column_map: dict[str, str] = field(default_factory=lambda: {}) + + custom_read_kwargs: dict[str, str] = field( + default_factory=lambda: { + 'separator': '\t', + 'columns': ['time', 'book_name', 'screen_id', + 'x_left', 'y_left', 'pupil_left'], + 'dtypes':[pl.Int64, pl.Utf8, pl.Int64, + pl.Float64, pl.Float64, pl.Float64], + }, + ) diff --git a/tests/datasets/datasets_test.py b/tests/datasets/datasets_test.py index 074002c10..02065e943 100644 --- a/tests/datasets/datasets_test.py +++ b/tests/datasets/datasets_test.py @@ -33,6 +33,7 @@ pytest.param(pm.datasets.GazeBaseVR, 'GazeBaseVR', id='GazeBaseVR'), pytest.param(pm.datasets.GazeOnFaces, 'GazeOnFaces', id='GazeOnFaces'), pytest.param(pm.datasets.JuDo1000, 'JuDo1000', id='JuDo1000'), + pytest.param(pm.datasets.SBSAT, 'SBSAT', id='SBSAT'), ], ) def test_public_dataset_registered(definition_class, dataset_name): @@ -49,6 +50,7 @@ def test_public_dataset_registered(definition_class, dataset_name): pytest.param(pm.datasets.GazeBaseVR, id='GazeBaseVR'), pytest.param(pm.datasets.GazeOnFaces, id='GazeOnFaces'), pytest.param(pm.datasets.JuDo1000, id='JuDo1000'), + pytest.param(pm.datasets.SBSAT, id='SBSAT'), ], ) def test_public_dataset_registered_correct_attributes(dataset_definition_class): diff --git a/tests/datasets/sbsat_test.py b/tests/datasets/sbsat_test.py new file mode 100644 index 000000000..3fcd41b93 --- /dev/null +++ b/tests/datasets/sbsat_test.py @@ -0,0 +1,79 @@ +# 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 all functionality in pymovements.dataset.sb_sat.""" +from pathlib import Path + +import pytest + +import pymovements as pm + + +@pytest.mark.parametrize( + 'init_path, expected_paths', + [ + pytest.param( + '/data/set/path', + { + 'root': Path('/data/set/path/'), + 'dataset': Path('/data/set/path/'), + 'download': Path('/data/set/path/downloads'), + }, + ), + pytest.param( + pm.DatasetPaths(root='/data/set/path'), + { + 'root': Path('/data/set/path/'), + 'dataset': Path('/data/set/path/SBSAT'), + 'download': Path('/data/set/path/SBSAT/downloads'), + }, + ), + pytest.param( + pm.DatasetPaths(root='/data/set/path', dataset='.'), + { + 'root': Path('/data/set/path/'), + 'dataset': Path('/data/set/path/'), + 'download': Path('/data/set/path/downloads'), + }, + ), + pytest.param( + pm.DatasetPaths(root='/data/set/path', dataset='dataset'), + { + 'root': Path('/data/set/path/'), + 'dataset': Path('/data/set/path/dataset'), + 'download': Path('/data/set/path/dataset/downloads'), + }, + ), + pytest.param( + pm.DatasetPaths(root='/data/set/path', downloads='custom_downloads'), + { + 'root': Path('/data/set/path/'), + 'dataset': Path('/data/set/path/SBSAT'), + 'download': Path('/data/set/path/SBSAT/custom_downloads'), + }, + ), + ], +) +def test_paths(init_path, expected_paths): + dataset = pm.Dataset(pm.datasets.SBSAT, path=init_path) + + assert dataset.paths.root == expected_paths['root'] + assert dataset.path == expected_paths['dataset'] + assert dataset.paths.dataset == expected_paths['dataset'] + assert dataset.paths.downloads == expected_paths['download'] From 6515bf6e5812701d9cad69957bb77cc21300dc7c Mon Sep 17 00:00:00 2001 From: prassepaul Date: Mon, 25 Sep 2023 11:50:57 +0200 Subject: [PATCH 17/23] flake, pyling, mypy --- src/pymovements/datasets/sb_sat.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/pymovements/datasets/sb_sat.py b/src/pymovements/datasets/sb_sat.py index 3885f6201..671b56ff7 100644 --- a/src/pymovements/datasets/sb_sat.py +++ b/src/pymovements/datasets/sb_sat.py @@ -119,7 +119,7 @@ class SBSAT(DatasetDefinition): origin='center', sampling_rate=1000, ) - + filename_format: str = r'msd{subject_id:d}.csv' filename_format_dtypes: dict[str, type] = field( @@ -136,12 +136,12 @@ class SBSAT(DatasetDefinition): column_map: dict[str, str] = field(default_factory=lambda: {}) - custom_read_kwargs: dict[str, str] = field( + custom_read_kwargs: dict[str, Any] = field( default_factory=lambda: { 'separator': '\t', 'columns': ['time', 'book_name', 'screen_id', 'x_left', 'y_left', 'pupil_left'], - 'dtypes':[pl.Int64, pl.Utf8, pl.Int64, - pl.Float64, pl.Float64, pl.Float64], + 'dtypes': [pl.Int64, pl.Utf8, pl.Int64, + pl.Float64, pl.Float64, pl.Float64], }, ) From cc09b6f91af33268f1a77b4dbf1d038f48ae2e78 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 25 Sep 2023 09:57:17 +0000 Subject: [PATCH 18/23] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- src/pymovements/datasets/__init__.py | 2 +- src/pymovements/datasets/sb_sat.py | 12 ++++++++---- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/src/pymovements/datasets/__init__.py b/src/pymovements/datasets/__init__.py index 2a5547296..b2f0a543e 100644 --- a/src/pymovements/datasets/__init__.py +++ b/src/pymovements/datasets/__init__.py @@ -41,9 +41,9 @@ pymovements.datasets.ToyDataset pymovements.datasets.ToyDatasetEyeLink """ +from pymovements.datasets.gaze_on_faces import GazeOnFaces from pymovements.datasets.gazebase import GazeBase from pymovements.datasets.gazebasevr import GazeBaseVR -from pymovements.datasets.gaze_on_faces import GazeOnFaces from pymovements.datasets.judo1000 import JuDo1000 from pymovements.datasets.sb_sat import SBSAT from pymovements.datasets.toy_dataset import ToyDataset diff --git a/src/pymovements/datasets/sb_sat.py b/src/pymovements/datasets/sb_sat.py index 671b56ff7..f6e09f968 100644 --- a/src/pymovements/datasets/sb_sat.py +++ b/src/pymovements/datasets/sb_sat.py @@ -139,9 +139,13 @@ class SBSAT(DatasetDefinition): custom_read_kwargs: dict[str, Any] = field( default_factory=lambda: { 'separator': '\t', - 'columns': ['time', 'book_name', 'screen_id', - 'x_left', 'y_left', 'pupil_left'], - 'dtypes': [pl.Int64, pl.Utf8, pl.Int64, - pl.Float64, pl.Float64, pl.Float64], + 'columns': [ + 'time', 'book_name', 'screen_id', + 'x_left', 'y_left', 'pupil_left', + ], + 'dtypes': [ + pl.Int64, pl.Utf8, pl.Int64, + pl.Float64, pl.Float64, pl.Float64, + ], }, ) From 16a0b8bcf8660cb9509874a906ed9ce35a96d176 Mon Sep 17 00:00:00 2001 From: prassepaul Date: Mon, 25 Sep 2023 12:22:13 +0200 Subject: [PATCH 19/23] bib --- docs/source/bibliography.bib | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/source/bibliography.bib b/docs/source/bibliography.bib index 14ebb19f6..0679166a0 100644 --- a/docs/source/bibliography.bib +++ b/docs/source/bibliography.bib @@ -77,7 +77,7 @@ @article{GazeOnFaces publisher={The Association for Research in Vision and Ophthalmology}, } -@article{SB-SAT, +@inproceedings{SB-SAT, title = {Towards predicting reading comprehension from gaze behavior}, year = {2020}, booktitle = {Proceedings of the ACM Symposium on Eye Tracking Research and Applications}, From 61d7ceb325754e67b725e7596ef52d557f8d147f Mon Sep 17 00:00:00 2001 From: prassepaul Date: Mon, 25 Sep 2023 16:00:26 +0200 Subject: [PATCH 20/23] hbn dataset --- docs/source/bibliography.bib | 77 ++++++++++++++ src/pymovements/datasets/__init__.py | 5 +- src/pymovements/datasets/hbn.py | 151 +++++++++++++++++++++++++++ src/pymovements/datasets/sb_sat.py | 14 +-- tests/datasets/datasets_test.py | 2 + tests/datasets/hbn_test.py | 79 ++++++++++++++ 6 files changed, 318 insertions(+), 10 deletions(-) create mode 100644 src/pymovements/datasets/hbn.py create mode 100644 tests/datasets/hbn_test.py diff --git a/docs/source/bibliography.bib b/docs/source/bibliography.bib index 0679166a0..39f7e960c 100644 --- a/docs/source/bibliography.bib +++ b/docs/source/bibliography.bib @@ -86,3 +86,80 @@ @inproceedings{SB-SAT publisher = {Association for Computing Machinery}, address = "Stuttgart, Germany", } + +@article{HBN, + title={An open resource for transdiagnostic research in pediatric mental health and learning disorders}, + author={Alexander, Lindsay M. and + Escalera, Jasmine and + Ai, Lei and + Andreotti, Charissa and + Febre, Karina and + Mangone, Alexander and + Vega-Potler, Natan and + Langer, Nicolas and + Alexander, Alexis and + Kovacs, Meagan and + Litke, Shannon and + O'Hagan, Bridget and + Andersen, Jennifer and + Bronstein, Batya and + Bui, Anastasia and + Bushey, Marijayne and + Butler, Henry and + Castagna, Victoria and + Camacho, Nicolas and + Chan, Elisha and + Citera, Danielle and + Clucas, Jon and + Cohen, Samantha and + Dufek, Sarah and + Eaves, Megan and + Fradera, Brian and + Gardner, Judith and + Grant-Villegas, Natalie and + Green, Gabriella and + Gregory, Camille and + Hart, Emily and + Harris, Shana and + Horton, Megan and + Kahn, Danielle and + Kabotyanski, Katherine and + Karmel, Bernard and + Kelly, Simon P. and + Kleinman, Kayla and + Koo, Bonhwang and + Kramer, Eliza and + Lennon, Elizabeth and + Lord, Catherine and + Mantello, Ginny and + Margolis, Amy and + Merikangas, Kathleen R. and + Milham, Judith and + Minniti, Giuseppe and + Neuhaus, Rebecca and + Levine, Alexandra and + Osman, Yael and + Parra, Lucas C. and + Pugh, Ken R. and + Racanello, Amy and + Restrepo, Anita and + Saltzman, Tian and + Septimus, Batya and + Tobe, Russell and + Waltz, Rachel and + Williams, Anna and + Yeo, Anna and +Castellanos, Francisco X. and +Klein, Arno and +Paus, Tomas and +Leventhal, Bennett L. and +Craddock, R. Cameron and +Koplewicz, Harold S. and +Milham, Michael P.}, + journal={Scientific data}, + volume={4}, + number={1}, + pages={1--26}, + year={2017}, + publisher={Nature Publishing Group} +} \ No newline at end of file diff --git a/src/pymovements/datasets/__init__.py b/src/pymovements/datasets/__init__.py index b2f0a543e..681da600a 100644 --- a/src/pymovements/datasets/__init__.py +++ b/src/pymovements/datasets/__init__.py @@ -28,6 +28,7 @@ pymovements.datasets.GazeBase pymovements.datasets.GazeBaseVR pymovements.datasets.GazeOnFaces + pymovements.datasets.HBN pymovements.datasets.JuDo1000 pymovements.datasets.SBSAT @@ -41,9 +42,10 @@ pymovements.datasets.ToyDataset pymovements.datasets.ToyDatasetEyeLink """ -from pymovements.datasets.gaze_on_faces import GazeOnFaces from pymovements.datasets.gazebase import GazeBase from pymovements.datasets.gazebasevr import GazeBaseVR +from pymovements.datasets.gaze_on_faces import GazeOnFaces +from pymovements.datasets.hbn import HBN from pymovements.datasets.judo1000 import JuDo1000 from pymovements.datasets.sb_sat import SBSAT from pymovements.datasets.toy_dataset import ToyDataset @@ -54,6 +56,7 @@ 'GazeBase', 'GazeBaseVR', 'GazeOnFaces', + 'HBN', 'JuDo1000', 'SBSAT', 'ToyDataset', diff --git a/src/pymovements/datasets/hbn.py b/src/pymovements/datasets/hbn.py new file mode 100644 index 000000000..36c8b8bfa --- /dev/null +++ b/src/pymovements/datasets/hbn.py @@ -0,0 +1,151 @@ +# Copyright (c) 2022-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. +"""This module provides an interface to the HBN dataset.""" +from __future__ import annotations + +from dataclasses import dataclass +from dataclasses import field +from typing import Any + +import polars as pl + +from pymovements.dataset.dataset_definition import DatasetDefinition +from pymovements.dataset.dataset_library import register_dataset +from pymovements.gaze.experiment import Experiment + + +@dataclass +@register_dataset +class HBN(DatasetDefinition): + """HBN dataset :cite:p:`HBN`. + + This dataset consists of recordings from children + watching four different age-appropriate videos: (1) an 41 + educational video clip (Fun with Fractals), (2) a short animated + film (The Present), (3) a short clip of an animated film (Despicable Me), + and (4) a trailer for a feature-length movie (Diary of a Wimpy Kid). + The eye gaze was recorded at a sampling rate of 120 Hz. + + Check the respective paper for details :cite:p:`HBN`. + + Attributes + ---------- + name : str + The name of the dataset. + + mirrors : tuple[str, ...] + A tuple of mirrors of the dataset. Each entry must be of type `str` and end with a '/'. + + resources : tuple[dict[str, str], ...] + A tuple of dataset resources. Each list entry must be a dictionary with the following keys: + - `resource`: The url suffix of the resource. This will be concatenated with the mirror. + - `filename`: The filename under which the file is saved as. + - `md5`: The MD5 checksum of the respective file. + + experiment : Experiment + The experiment definition. + + filename_format : str + Regular expression which will be matched before trying to load the file. Namedgroups will + appear in the `fileinfo` dataframe. + + filename_format_dtypes : dict[str, type], optional + If named groups are present in the `filename_format`, this makes it possible to cast + specific named groups to a particular datatype. + + column_map : dict[str, str] + The keys are the columns to read, the values are the names to which they should be renamed. + + custom_read_kwargs : dict[str, Any], optional + If specified, these keyword arguments will be passed to the file reading function. + + Examples + -------- + Initialize your :py:class:`~pymovements.PublicDataset` object with the + :py:class:`~pymovements.HBN` definition: + + >>> import pymovements as pm + >>> + >>> dataset = pm.Dataset("HBN", path='data/HBN') + + Download the dataset resources resources: + + >>> dataset.download()# doctest: +SKIP + + Load the data into memory: + + >>> dataset.load()# doctest: +SKIP + """ + + # pylint: disable=similarities + # The PublicDatasetDefinition child classes potentially share code chunks for definitions. + + name: str = 'HBN' + + mirrors: tuple[str, ...] = ( + 'https://files.osf.io/v1/resources/qknuv/providers/osfstorage/', + ) + + resources: tuple[dict[str, str], ...] = ( + { + 'resource': '651190031e76a453918a9971', + 'filename': 'data.zip', + 'md5': '2c523e911022ffc0eab700e34e9f7f30', + }, + ) + + experiment: Experiment = Experiment( + screen_width_px=800, + screen_height_px=600, + screen_width_cm=33.8, + screen_height_cm=27.0, + distance_cm=63.5, + origin='center', + sampling_rate=120, + ) + + filename_format: str = r'{subject_id:12}_{video_id}.csv' + + filename_format_dtypes: dict[str, type] = field( + default_factory=lambda: { + 'subject_id': str, + 'video_id': str, + }, + ) + + trial_columns: list[str] = field(default_factory=lambda: []) + + time_column: str = 'time' + + pixel_columns: list[str] = field(default_factory=lambda: ['x_pix', 'y_pix']) + + column_map: dict[str, str] = field(default_factory=lambda: {}) + + custom_read_kwargs: dict[str, Any] = field( + default_factory=lambda: { + 'separator': ',', + 'columns': [ + 'time', 'x_pix', 'y_pix', + ], + 'dtypes': [ + pl.Float64, pl.Float64, pl.Float64, + ], + }, + ) diff --git a/src/pymovements/datasets/sb_sat.py b/src/pymovements/datasets/sb_sat.py index f6e09f968..d3c676b1f 100644 --- a/src/pymovements/datasets/sb_sat.py +++ b/src/pymovements/datasets/sb_sat.py @@ -17,7 +17,7 @@ # 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. -"""This module provides an interface to the GazeOnFaces dataset.""" +"""This module provides an interface to the SB-SAT dataset.""" from __future__ import annotations from dataclasses import dataclass @@ -139,13 +139,9 @@ class SBSAT(DatasetDefinition): custom_read_kwargs: dict[str, Any] = field( default_factory=lambda: { 'separator': '\t', - 'columns': [ - 'time', 'book_name', 'screen_id', - 'x_left', 'y_left', 'pupil_left', - ], - 'dtypes': [ - pl.Int64, pl.Utf8, pl.Int64, - pl.Float64, pl.Float64, pl.Float64, - ], + 'columns': ['time', 'book_name', 'screen_id', + 'x_left', 'y_left', 'pupil_left'], + 'dtypes': [pl.Int64, pl.Utf8, pl.Int64, + pl.Float64, pl.Float64, pl.Float64], }, ) diff --git a/tests/datasets/datasets_test.py b/tests/datasets/datasets_test.py index 02065e943..4d12e5d3f 100644 --- a/tests/datasets/datasets_test.py +++ b/tests/datasets/datasets_test.py @@ -32,6 +32,7 @@ pytest.param(pm.datasets.GazeBase, 'GazeBase', id='GazeBase'), pytest.param(pm.datasets.GazeBaseVR, 'GazeBaseVR', id='GazeBaseVR'), pytest.param(pm.datasets.GazeOnFaces, 'GazeOnFaces', id='GazeOnFaces'), + pytest.param(pm.datasets.HBN, 'HBN', id='HBN'), pytest.param(pm.datasets.JuDo1000, 'JuDo1000', id='JuDo1000'), pytest.param(pm.datasets.SBSAT, 'SBSAT', id='SBSAT'), ], @@ -49,6 +50,7 @@ def test_public_dataset_registered(definition_class, dataset_name): pytest.param(pm.datasets.GazeBase, id='GazeBase'), pytest.param(pm.datasets.GazeBaseVR, id='GazeBaseVR'), pytest.param(pm.datasets.GazeOnFaces, id='GazeOnFaces'), + pytest.param(pm.datasets.HBN, id='HBN'), pytest.param(pm.datasets.JuDo1000, id='JuDo1000'), pytest.param(pm.datasets.SBSAT, id='SBSAT'), ], diff --git a/tests/datasets/hbn_test.py b/tests/datasets/hbn_test.py new file mode 100644 index 000000000..7482732d9 --- /dev/null +++ b/tests/datasets/hbn_test.py @@ -0,0 +1,79 @@ +# 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 all functionality in pymovements.dataset.hbn.""" +from pathlib import Path + +import pytest + +import pymovements as pm + + +@pytest.mark.parametrize( + 'init_path, expected_paths', + [ + pytest.param( + '/data/set/path', + { + 'root': Path('/data/set/path/'), + 'dataset': Path('/data/set/path/'), + 'download': Path('/data/set/path/downloads'), + }, + ), + pytest.param( + pm.DatasetPaths(root='/data/set/path'), + { + 'root': Path('/data/set/path/'), + 'dataset': Path('/data/set/path/HBN'), + 'download': Path('/data/set/path/HBN/downloads'), + }, + ), + pytest.param( + pm.DatasetPaths(root='/data/set/path', dataset='.'), + { + 'root': Path('/data/set/path/'), + 'dataset': Path('/data/set/path/'), + 'download': Path('/data/set/path/downloads'), + }, + ), + pytest.param( + pm.DatasetPaths(root='/data/set/path', dataset='dataset'), + { + 'root': Path('/data/set/path/'), + 'dataset': Path('/data/set/path/dataset'), + 'download': Path('/data/set/path/dataset/downloads'), + }, + ), + pytest.param( + pm.DatasetPaths(root='/data/set/path', downloads='custom_downloads'), + { + 'root': Path('/data/set/path/'), + 'dataset': Path('/data/set/path/HBN'), + 'download': Path('/data/set/path/HBN/custom_downloads'), + }, + ), + ], +) +def test_paths(init_path, expected_paths): + dataset = pm.Dataset(pm.datasets.HBN, path=init_path) + + assert dataset.paths.root == expected_paths['root'] + assert dataset.path == expected_paths['dataset'] + assert dataset.paths.dataset == expected_paths['dataset'] + assert dataset.paths.downloads == expected_paths['download'] From dae47258f1e5af3743f6152f5982bdcdc560db13 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 25 Sep 2023 14:02:25 +0000 Subject: [PATCH 21/23] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- docs/source/bibliography.bib | 2 +- src/pymovements/datasets/__init__.py | 2 +- src/pymovements/datasets/sb_sat.py | 12 ++++++++---- 3 files changed, 10 insertions(+), 6 deletions(-) diff --git a/docs/source/bibliography.bib b/docs/source/bibliography.bib index 39f7e960c..f94d86301 100644 --- a/docs/source/bibliography.bib +++ b/docs/source/bibliography.bib @@ -162,4 +162,4 @@ @article{HBN pages={1--26}, year={2017}, publisher={Nature Publishing Group} -} \ No newline at end of file +} diff --git a/src/pymovements/datasets/__init__.py b/src/pymovements/datasets/__init__.py index 681da600a..5eda26d9d 100644 --- a/src/pymovements/datasets/__init__.py +++ b/src/pymovements/datasets/__init__.py @@ -42,9 +42,9 @@ pymovements.datasets.ToyDataset pymovements.datasets.ToyDatasetEyeLink """ +from pymovements.datasets.gaze_on_faces import GazeOnFaces from pymovements.datasets.gazebase import GazeBase from pymovements.datasets.gazebasevr import GazeBaseVR -from pymovements.datasets.gaze_on_faces import GazeOnFaces from pymovements.datasets.hbn import HBN from pymovements.datasets.judo1000 import JuDo1000 from pymovements.datasets.sb_sat import SBSAT diff --git a/src/pymovements/datasets/sb_sat.py b/src/pymovements/datasets/sb_sat.py index d3c676b1f..774c1ebdb 100644 --- a/src/pymovements/datasets/sb_sat.py +++ b/src/pymovements/datasets/sb_sat.py @@ -139,9 +139,13 @@ class SBSAT(DatasetDefinition): custom_read_kwargs: dict[str, Any] = field( default_factory=lambda: { 'separator': '\t', - 'columns': ['time', 'book_name', 'screen_id', - 'x_left', 'y_left', 'pupil_left'], - 'dtypes': [pl.Int64, pl.Utf8, pl.Int64, - pl.Float64, pl.Float64, pl.Float64], + 'columns': [ + 'time', 'book_name', 'screen_id', + 'x_left', 'y_left', 'pupil_left', + ], + 'dtypes': [ + pl.Int64, pl.Utf8, pl.Int64, + pl.Float64, pl.Float64, pl.Float64, + ], }, ) From fdfb0c27a15e2c042ba2bd1c900db18c49bf3217 Mon Sep 17 00:00:00 2001 From: prassepaul Date: Tue, 26 Sep 2023 13:12:55 +0200 Subject: [PATCH 22/23] added group --- src/pymovements/datasets/hbn.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pymovements/datasets/hbn.py b/src/pymovements/datasets/hbn.py index 36c8b8bfa..a95315a56 100644 --- a/src/pymovements/datasets/hbn.py +++ b/src/pymovements/datasets/hbn.py @@ -130,7 +130,7 @@ class HBN(DatasetDefinition): }, ) - trial_columns: list[str] = field(default_factory=lambda: []) + trial_columns: list[str] = field(default_factory=lambda: ['video_id']) time_column: str = 'time' From d95a5a12ce6bd9ef0d2f916ca4ed7311665e603f Mon Sep 17 00:00:00 2001 From: prassepaul Date: Thu, 28 Sep 2023 15:05:48 +0200 Subject: [PATCH 23/23] requested changes --- src/pymovements/datasets/hbn.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pymovements/datasets/hbn.py b/src/pymovements/datasets/hbn.py index a95315a56..c976e5454 100644 --- a/src/pymovements/datasets/hbn.py +++ b/src/pymovements/datasets/hbn.py @@ -37,7 +37,7 @@ class HBN(DatasetDefinition): """HBN dataset :cite:p:`HBN`. This dataset consists of recordings from children - watching four different age-appropriate videos: (1) an 41 + watching four different age-appropriate videos: (1) an educational video clip (Fun with Fractals), (2) a short animated film (The Present), (3) a short clip of an animated film (Despicable Me), and (4) a trailer for a feature-length movie (Diary of a Wimpy Kid).