Repository: aeye-lab/pymovements
Pull request #619 (2023-11-06 12:40:41)

feat: add new dataset GazeGraph
## Description

Add new dataset: GazeGraph


## Type of change

- [x] New feature (non-breaking change which adds functionality)

## How Has This Been Tested?


## Checklist:

- [x] My code follows the style guidelines of this project
- [x] I have performed a self-review of my own code
- [x] I have commented my code, particularly in hard-to-understand areas
- [x] I have made corresponding changes to the documentation
- [x] My changes generate no new warnings

codecov[bot]: ## [Codecov](https://app.codecov.io/gh/aeye-lab/pymovements/pull/619?src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=aeye-lab) Report
All modified and coverable lines are covered by tests :white_check_mark:
> Comparison is base [(`d666422`)](https://app.codecov.io/gh/aeye-lab/pymovements/commit/d6664224f8720212fe8cd25fc505659ceb869eac?el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=aeye-lab) 100.00% compared to head [(`009ed75`)](https://app.codecov.io/gh/aeye-lab/pymovements/pull/619?src=pr&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=aeye-lab) 100.00%.

> :exclamation: Current head 009ed75 differs from pull request most recent head fd7de43. Consider uploading reports for the commit fd7de43 to get more accurate results

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


```diff
@@            Coverage Diff            @@
##              main      #619   +/-   ##
=========================================
  Coverage   100.00%   100.00%           
=========================================
  Files           52        53    +1     
  Lines         2352      2376   +24     
  Branches       590       597    +7     
=========================================
+ Hits          2352      2376   +24     
```


| [Files](https://app.codecov.io/gh/aeye-lab/pymovements/pull/619?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=aeye-lab) | Coverage Δ | |
|---|---|---|
| [src/pymovements/datasets/\_\_init\_\_.py](https://app.codecov.io/gh/aeye-lab/pymovements/pull/619?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/gaze\_graph.py](https://app.codecov.io/gh/aeye-lab/pymovements/pull/619?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=aeye-lab#diff-c3JjL3B5bW92ZW1lbnRzL2RhdGFzZXRzL2dhemVfZ3JhcGgucHk=) | `100.00% <100.00%> (ø)` | |


</details>

[:umbrella: View full report in Codecov by Sentry](https://app.codecov.io/gh/aeye-lab/pymovements/pull/619?src=pr&el=continue&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=aeye-lab).   
:loudspeaker: Have feedback on the report? [Share it here](https://about.codecov.io/codecov-pr-comment-feedback/?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=aeye-lab).


Patch:
diff --git a/docs/source/bibliography.bib b/docs/source/bibliography.bib
index f94d863..9b43a31 100644
--- a/docs/source/bibliography.bib
+++ b/docs/source/bibliography.bib
@@ -163,3 +163,10 @@ Milham, Michael P.},
   year={2017},
   publisher={Nature Publishing Group}
 }
+
+@inproceedings{GazeGraph,
+  title={{GazeGraph}: Graph-based few-shot cognitive context sensing from human visual behavior},
+  author={Lan, Guohao and Heit, Bailey and Scargill, Tim and Gorlatova, Maria},
+  booktitle={Proceedings of the 18th ACM Conference on Embedded Networked Sensor Systems (SenSys)},
+  year={2020}
+}
diff --git a/src/pymovements/datasets/__init__.py b/src/pymovements/datasets/__init__.py
index 5eda26d..0bc602c 100644
--- a/src/pymovements/datasets/__init__.py
+++ b/src/pymovements/datasets/__init__.py
@@ -27,6 +27,7 @@
 
     pymovements.datasets.GazeBase
     pymovements.datasets.GazeBaseVR
+    pymovements.datasets.GazeGraph
     pymovements.datasets.GazeOnFaces
     pymovements.datasets.HBN
     pymovements.datasets.JuDo1000
@@ -45,6 +46,7 @@
 from pymovements.datasets.gaze_on_faces import GazeOnFaces
 from pymovements.datasets.gazebase import GazeBase
 from pymovements.datasets.gazebasevr import GazeBaseVR
+from pymovements.datasets.gaze_graph import GazeGraph
 from pymovements.datasets.hbn import HBN
 from pymovements.datasets.judo1000 import JuDo1000
 from pymovements.datasets.sb_sat import SBSAT
@@ -55,6 +57,7 @@ from pymovements.datasets.toy_dataset_eyelink import ToyDatasetEyeLink
 __all__ = [
     'GazeBase',
     'GazeBaseVR',
+    'GazeGraph',
     'GazeOnFaces',
     'HBN',
     'JuDo1000',
diff --git a/src/pymovements/datasets/gaze_graph.py b/src/pymovements/datasets/gaze_graph.py
new file mode 100644
index 0000000..f98bb8c
--- /dev/null
+++ b/src/pymovements/datasets/gaze_graph.py
@@ -0,0 +1,152 @@
+# 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 GazeGraph 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 GazeGraph(DatasetDefinition):
+    """GazeGraph dataset :cite:p:`GazeGraph`.
+
+    The dataset is collected from eight subjects (four female and four male,
+    aged between 24 and 35) using the Pupil Core eye tracker. During data collection,
+    the subjects wear the eye tracker and sit in front of the computer screen
+    (a 34-inch display) at a distance of approximately 50cm. We conduct the
+    manufacturer's default on-screen five-points calibration for each of
+    the subjects.
+    Note that we have done only one calibration per subject, and the subjects
+    can move their heads and upper bodies freely during the experiment.
+    The gaze is recorded at a 30Hz sampling rate.
+
+    Check the respective paper for details :cite:p:`GazeGraph`.
+
+    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.GazeGraph` definition:
+
+    >>> import pymovements as pm
+    >>>
+    >>> dataset = pm.Dataset("GazeGraph", path='data/GazeGraph')
+
+    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 = 'GazeGraph'
+
+    mirrors: tuple[str, ...] = (
+        'https://codeload.github.com/GazeGraphResource/GazeGraph/zip/refs/heads/',
+    )
+
+    resources: tuple[dict[str, str], ...] = (
+        {
+            'resource': 'master',
+            'filename': 'gaze_graph_data.zip',
+            'md5': '181f4b79477cee6e0267482d989610b0',
+        },
+    )
+
+    # no information about the resolution and screen size given. only 34-inch monitor
+    experiment: Experiment = Experiment(
+        screen_width_px=3440,
+        screen_height_px=1440,
+        screen_width_cm=79.375,
+        screen_height_cm=34.0106,
+        distance_cm=50,
+        origin='center',
+        sampling_rate=30,
+    )
+
+    filename_format: str = r'P{subject_id}_{task}.csv'
+
+    filename_format_dtypes: dict[str, type] = field(
+        default_factory=lambda: {
+            'subject_id': int,
+            'task': str,
+        },
+    )
+
+    trial_columns: list[str] = field(default_factory=lambda: ['task'])
+
+    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],
+        },
+    )


Test patch:
diff --git a/tests/files/gazegraph_example.csv b/tests/files/gazegraph_example.csv
new file mode 100644
index 0000000..4784cd5
--- /dev/null
+++ b/tests/files/gazegraph_example.csv
@@ -0,0 +1,10 @@
+0.59146,0.3877
+0.59078,0.38569
+0.59092,0.39018
+0.5899,0.38922
+0.59022,0.39084
+0.58929,0.39159
+0.58968,0.39583
+0.59004,0.40196
+0.58836,0.3927
+0.59123,0.39639
diff --git a/tests/functional/dataset_processing_test.py b/tests/functional/dataset_processing_test.py
index c55a082..fa44287 100644
--- a/tests/functional/dataset_processing_test.py
+++ b/tests/functional/dataset_processing_test.py
@@ -36,6 +36,7 @@ import pymovements as pm
         'gaze_on_faces',
         'gazebase',
         'gazebase_vr',
+        'gazegraph',
         'judo1000',
     ],
 )
@@ -87,6 +88,11 @@ def fixture_dataset_init_kwargs(request):
             filename_format_dtypes={},
             trial_columns=None,
         ),
+        'gazegraph': pm.datasets.GazeGraph(
+            filename_format='gazegraph_example.csv',
+            filename_format_dtypes={},
+            trial_columns=None,
+        ),
         'judo1000': pm.datasets.JuDo1000(
             filename_format='judo1000_example.csv',
             filename_format_dtypes={},