Repository: aeye-lab/pymovements
Pull request #343 (2023-04-28 03:41:39)

feat(datasets): Add ToyDatasetEyeLink

codecov-commenter: ## [Codecov](https://codecov.io/gh/aeye-lab/pymovements/pull/343?src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=aeye-lab) Report
Patch coverage: **`100.00`**% and no project coverage change.
> Comparison is base [(`08d93dd`)](https://codecov.io/gh/aeye-lab/pymovements/commit/08d93dd879ec97b051f77c4935918a48cc966fc5?el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=aeye-lab) 100.00% compared to head [(`4320aac`)](https://codecov.io/gh/aeye-lab/pymovements/pull/343?src=pr&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=aeye-lab) 100.00%.

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


```diff
@@            Coverage Diff            @@
##              main      #343   +/-   ##
=========================================
  Coverage   100.00%   100.00%           
=========================================
  Files           53        54    +1     
  Lines         1803      1848   +45     
  Branches       386       358   -28     
=========================================
+ Hits          1803      1848   +45     
```


| [Impacted Files](https://codecov.io/gh/aeye-lab/pymovements/pull/343?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=aeye-lab) | Coverage Δ | |
|---|---|---|
| [src/pymovements/plotting/tsplot.py](https://codecov.io/gh/aeye-lab/pymovements/pull/343?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=aeye-lab#diff-c3JjL3B5bW92ZW1lbnRzL3Bsb3R0aW5nL3RzcGxvdC5weQ==) | `100.00% <ø> (ø)` | |
| [src/pymovements/dataset/dataset\_files.py](https://codecov.io/gh/aeye-lab/pymovements/pull/343?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=aeye-lab#diff-c3JjL3B5bW92ZW1lbnRzL2RhdGFzZXQvZGF0YXNldF9maWxlcy5weQ==) | `100.00% <100.00%> (ø)` | |
| [src/pymovements/datasets/\_\_init\_\_.py](https://codecov.io/gh/aeye-lab/pymovements/pull/343?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=aeye-lab#diff-c3JjL3B5bW92ZW1lbnRzL2RhdGFzZXRzL19faW5pdF9fLnB5) | `100.00% <100.00%> (ø)` | |
| [src/pymovements/datasets/toy\_dataset\_eyelink.py](https://codecov.io/gh/aeye-lab/pymovements/pull/343?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=aeye-lab#diff-c3JjL3B5bW92ZW1lbnRzL2RhdGFzZXRzL3RveV9kYXRhc2V0X2V5ZWxpbmsucHk=) | `100.00% <100.00%> (ø)` | |
| [src/pymovements/utils/parsing.py](https://codecov.io/gh/aeye-lab/pymovements/pull/343?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=aeye-lab#diff-c3JjL3B5bW92ZW1lbnRzL3V0aWxzL3BhcnNpbmcucHk=) | `100.00% <100.00%> (ø)` | |


</details>

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


Patch:
diff --git a/src/pymovements/datasets/__init__.py b/src/pymovements/datasets/__init__.py
index 39c31d2..c283ed9 100644
--- a/src/pymovements/datasets/__init__.py
+++ b/src/pymovements/datasets/__init__.py
@@ -25,17 +25,28 @@
    :toctree:
    :template: class.rst
 
-    pymovements.datasets.ToyDataset
     pymovements.datasets.GazeBase
     pymovements.datasets.JuDo1000
+
+
+.. rubric:: Example Datasets
+
+.. autosummary::
+   :toctree:
+   :template: class.rst
+
+    pymovements.datasets.ToyDataset
+    pymovements.datasets.ToyDatasetEyeLink
 """
 from pymovements.datasets.gazebase import GazeBase
 from pymovements.datasets.judo1000 import JuDo1000
 from pymovements.datasets.toy_dataset import ToyDataset
+from pymovements.datasets.toy_dataset_eyelink import ToyDatasetEyeLink
 
 
 __all__ = [
     'GazeBase',
     'JuDo1000',
     'ToyDataset',
+    'ToyDatasetEyeLink',
 ]
diff --git a/src/pymovements/datasets/toy_dataset_eyelink.py b/src/pymovements/datasets/toy_dataset_eyelink.py
new file mode 100644
index 0000000..4868cca
--- /dev/null
+++ b/src/pymovements/datasets/toy_dataset_eyelink.py
@@ -0,0 +1,173 @@
+# 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.
+"""This module provides an interface to the pymovements example eyelink toy 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 ToyDatasetEyeLink(DatasetDefinition):
+    """Example toy dataset with EyeLink data.
+
+    This dataset includes monocular eye tracking data from a single participants in a single
+    session. Eye movements are recorded at a sampling frequency of 1000 Hz using an EyeLink Portable
+    Duo video-based eye tracker and are provided as pixel coordinates.
+
+    The participant is instructed to read a single text and some JuDo trials.
+
+    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.Dataset` object with the
+    :py:class:`~pymovements.ToyDataset` definition:
+
+    >>> import pymovements as pm
+    >>>
+    >>> dataset = pm.Dataset("ToyDatasetEyeLink", path='data/ToyDatasetEyeLink')
+
+    Download the dataset resources resources:
+
+    >>> dataset.download()# doctest: +SKIP
+
+    Load the data into memory:
+
+    >>> dataset.load()# doctest: +SKIP
+    """
+    # pylint: disable=similarities
+    # The DatasetDefinition child classes potentially share code chunks for definitions.
+
+    name: str = 'ToyDatasetEyeLink'
+
+    mirrors: tuple[str, ...] = (
+        'http://github.com/aeye-lab/pymovements-toy-dataset-eyelink/zipball/',
+    )
+
+    resources: tuple[dict[str, str], ...] = (
+        {
+            'resource': 'a970d090588542dad745297866e794ab9dad8795/',
+            'filename': 'pymovements-toy-dataset-eyelink.zip',
+            'md5': 'b1d426751403752c8a154fc48d1670ce',
+        },
+    )
+
+    experiment: Experiment = Experiment(
+        screen_width_px=1280,
+        screen_height_px=1024,
+        screen_width_cm=38,
+        screen_height_cm=30.2,
+        distance_cm=68,
+        origin='lower left',
+        sampling_rate=1000,
+    )
+
+    filename_format: str = r'subject_{subject_id:d}_session_{session_id:d}.asc'
+
+    filename_format_dtypes: dict[str, type] = field(
+        default_factory=lambda: {
+            'subject_id': int,
+            'session_id': int,
+        },
+    )
+
+    column_map: dict[str, str] = field(default_factory=lambda: {})
+
+    custom_read_kwargs: dict[str, Any] = field(
+        default_factory=lambda: {
+            'patterns': [
+                {
+                    'pattern': 'SYNCTIME_READING_SCREEN',
+                    'column': 'task',
+                    'value': 'reading',
+                },
+                {
+                    'pattern': 'SYNCTIME_JUDO',
+                    'column': 'task',
+                    'value': 'judo',
+                },
+                {
+                    'pattern': ('READING[.]STOP', 'JUDO[.]STOP'),
+                    'column': 'task',
+                    'value': None,
+                },
+
+                r'TRIALID (?P<trial_id>\d+)',
+                {
+                    'pattern': 'TRIAL_RESULT',
+                    'column': 'trial_id',
+                    'value': None,
+                },
+
+                r'SYNCTIME_READING_SCREEN_(?P<screen_id>\d+)',
+                {
+                    'pattern': 'READING[.]STOP',
+                    'column': 'screen_id',
+                    'value': None,
+                },
+
+                r'SYNCTIME.P(?P<point_id>\d+)',
+                {
+                    'pattern': r'P\d[.]STOP',
+                    'column': 'point_id',
+                    'value': None,
+                },
+            ],
+            'schema': {'trial_id': pl.Int64},
+        },
+    )


Test patch:
diff --git a/tests/datasets/toy_dataset_eyelink_test.py b/tests/datasets/toy_dataset_eyelink_test.py
new file mode 100644
index 0000000..01c2921
--- /dev/null
+++ b/tests/datasets/toy_dataset_eyelink_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.toy_dataset_eyelink."""
+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/ToyDatasetEyeLink'),
+                'download': Path('/data/set/path/ToyDatasetEyeLink/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/ToyDatasetEyeLink'),
+                'download': Path('/data/set/path/ToyDatasetEyeLink/custom_downloads'),
+            },
+        ),
+    ],
+)
+def test_paths(init_path, expected_paths):
+    dataset = pm.Dataset(pm.datasets.ToyDatasetEyeLink, 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']