Repository: aeye-lab/pymovements
Pull request #1044 (2025-03-14 09:18:49)

dataset: add potsdam binge pvt datasets
## Description

This PR adds two new datasets


Insert a description of the changes implemented in the pull request.

- [x] PotsdamBingeWearablePVT added
- [x] PotsdamBingeRemotePVT added

## 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
- [x] I have added tests that prove my fix is effective or that my feature works
- [x] New and existing unit tests pass locally with my changes
- [x] Any dependent changes have been merged and published in downstream modules
- [x] I have checked my code and corrected any misspellings


SiQube: @prassepaul there seems to be a merge conflict.

can you also check how it works with `@register_dataset` and a yaml definition. I deleted all `@register_dataset` in #914 not to conflict the `DatasetLibrary`. did not check for both being present

Patch:
diff --git a/docs/source/bibliography.bib b/docs/source/bibliography.bib
index 6fda5f6..f200fc6 100644
--- a/docs/source/bibliography.bib
+++ b/docs/source/bibliography.bib
@@ -392,6 +392,21 @@ doi = {10.1016/j.dib.2021.106909}
     doi = {10.3758/s13428-012-0313-y},
 }
 
+@inproceedings{PotsdamBingePVT,
+    author = {
+        Prasse, Paul and
+        Reich, David R. and
+        Chwastek, Jakob and
+        Makowski, Silvia and
+        Jäger, Lena A. and
+        Scheffer, Tobias
+    },
+	booktitle = {Proceedings of the ACM Symposium on Eye Tracking Research and Applications},
+    year = {2025},
+    title = {Detection of Alcohol Inebriation from Eye Movements using Remote and Wearable Eye Trackers},
+    doi = {10.1145/3715669.3723109},
+}
+
 @article{MouseCursor,
     title = {Gaze tracking dataset for comparison of smooth and saccadic eye tracking},
     journal = {Data in Brief},
diff --git a/docs/source/datasets/public_datasets.csv b/docs/source/datasets/public_datasets.csv
index bbc824b..69010b5 100644
--- a/docs/source/datasets/public_datasets.csv
+++ b/docs/source/datasets/public_datasets.csv
@@ -16,6 +16,8 @@ Dataset,Citation,Samples,Events,Reading Measures,# Subjects,# Sessions per Subje
 :py:class:`~pymovements.datasets.JuDo1000`,:cite:p:`JuDo1000`,X,,,150,4,1000 Hz,Random Dots
 :py:class:`~pymovements.datasets.MouseCursor`,:cite:p:`MouseCursor`,X,,,2,1,3 Hz,Mouse Cursor
 :py:class:`~pymovements.datasets.PoTeC`,:cite:p:`PoTeC`,X,,,75,1,1000 Hz,Text (documents)
+:py:class:`~pymovements.datasets.PotsdamBingeRemotePVT`,:cite:p:`PotsdamBingePVT`,X,,,57,2,1000 Hz,Dots (PVT test)
+:py:class:`~pymovements.datasets.PotsdamBingeWearablePVT`,:cite:p:`PotsdamBingePVT`,X,,,57,2,200 Hz (upsampled to 1000 Hz),Dots (PVT test)
 :py:class:`~pymovements.datasets.Provo`,:cite:p:`Provo`,,X,,470,1,1000 Hz,Text (documents)
 :py:class:`~pymovements.datasets.SBSAT`,:cite:p:`SB-SAT`,X,X,,95,1,1000 Hz,Text (documents)
 :py:class:`~pymovements.datasets.UCL`,:cite:p:`UCL`,,X,X,117,1,500 Hz,Text (sentences)
diff --git a/src/pymovements/datasets/__init__.py b/src/pymovements/datasets/__init__.py
index 9fc72b2..f71b456 100644
--- a/src/pymovements/datasets/__init__.py
+++ b/src/pymovements/datasets/__init__.py
@@ -42,6 +42,8 @@
     JuDo1000
     MouseCursor
     PoTeC
+    PotsdamBingeRemotePVT
+    PotsdamBingeWearablePVT
     Provo
     SBSAT
     UCL
@@ -73,6 +75,8 @@ from pymovements.datasets.interead import InteRead
 from pymovements.datasets.judo1000 import JuDo1000
 from pymovements.datasets.mousecursor import MouseCursor
 from pymovements.datasets.potec import PoTeC
+from pymovements.datasets.potsdam_binge_remote_pvt import PotsdamBingeRemotePVT
+from pymovements.datasets.potsdam_binge_wearable_pvt import PotsdamBingeWearablePVT
 from pymovements.datasets.provo import Provo
 from pymovements.datasets.sb_sat import SBSAT
 from pymovements.datasets.toy_dataset import ToyDataset
@@ -98,6 +102,8 @@ __all__ = [
     'JuDo1000',
     'MouseCursor',
     'PoTeC',
+    'PotsdamBingeRemotePVT',
+    'PotsdamBingeWearablePVT',
     'Provo',
     'SBSAT',
     'ToyDataset',
diff --git a/src/pymovements/datasets/datasets.yaml b/src/pymovements/datasets/datasets.yaml
index af34671..68c5094 100644
--- a/src/pymovements/datasets/datasets.yaml
+++ b/src/pymovements/datasets/datasets.yaml
@@ -15,6 +15,8 @@
 - judo1000
 - mousecursor
 - potec
+- potsdam_binge_remote_pvt
+- potsdam_binge_wearable_pvt
 - provo
 - sb_sat
 - toy_dataset
diff --git a/src/pymovements/datasets/potsdam_binge_remote_pvt.py b/src/pymovements/datasets/potsdam_binge_remote_pvt.py
new file mode 100644
index 0000000..3955e30
--- /dev/null
+++ b/src/pymovements/datasets/potsdam_binge_remote_pvt.py
@@ -0,0 +1,254 @@
+# Copyright (c) 2022-2025 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.
+"""Provides a definition for the PotsdamBingeRemotePVT 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.gaze.experiment import Experiment
+from pymovements.gaze.eyetracker import EyeTracker
+from pymovements.gaze.screen import Screen
+
+
+@dataclass
+class PotsdamBingeRemotePVT(DatasetDefinition):
+    """PotsdamBingeRemotePVT dataset :cite:p:`PotsdamBingePVT`.
+
+    This dataset includes monocular eye tracking data from 57 participants in two sessions with an
+    interval of at least one week between two sessions. Eye movements are recorded at a sampling
+    frequency of 1000 Hz using an EyeLink 1000 Plus video-based eye tracker and are provided as
+    pixel coordinates. Participants are instructed to perform a PVT trial.
+
+    Check the respective `repository <https://osf.io/qf7e6/>`_ for details.
+
+    Attributes
+    ----------
+    name: str
+        The name of the dataset.
+
+    has_files: dict[str, bool]
+        Indicate whether the dataset contains 'gaze', 'precomputed_events', and
+        'precomputed_reading_measures'.
+
+    mirrors: dict[str, list[str]]
+        A tuple of mirrors of the dataset. Each entry must be of type `str` and end with a '/'.
+
+    resources: dict[str, list[dict[str, str]]]
+        A tuple of dataset gaze_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.
+
+    extract: dict[str, bool]
+        Decide whether to extract the data.
+
+    experiment: Experiment
+        The experiment definition.
+
+    filename_format: dict[str, str]
+        Regular expression which will be matched before trying to load the file. Namedgroups will
+        appear in the `fileinfo` dataframe.
+
+    filename_format_schema_overrides: dict[str, dict[str, type]]
+        If named groups are present in the `filename_format`, this makes it possible to cast
+        specific named groups to a particular datatype.
+
+    trial_columns: list[str]
+            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: str
+        The name of the timestamp column in the input data frame. This column will be renamed to
+        ``time``.
+
+    time_unit: str
+        The unit of the timestamps in the timestamp column in the input data frame. Supported
+        units are 's' for seconds, 'ms' for milliseconds and 'step' for steps. If the unit is
+        'step' the experiment definition must be specified. All timestamps will be converted to
+        milliseconds.
+
+    distance_column: str
+        The name of the distance column in the input data frame. These column will be
+        used to convert pixel coordinates into degrees of visual angle.
+
+    pixel_columns: list[str]
+        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.
+
+    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, dict[str, Any]]
+        If specified, these keyword arguments will be passed to the file reading function.
+
+
+    Examples
+    --------
+    Initialize your :py:class:`~pymovements.dataset.Dataset` object with the
+    :py:class:`~pymovements.datasets.PotsdamBingeRemotePVT` definition:
+
+    >>> import pymovements as pm
+    >>>
+    >>> dataset = pm.Dataset("PotsdamBingeRemotePVT", path='data/PotsdamBingeRemotePVT')
+
+    Download the dataset 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 = 'PotsdamBingeRemotePVT'
+
+    has_files: dict[str, bool] = field(
+        default_factory=lambda: {
+            'gaze': True,
+            'precomputed_events': False,
+            'precomputed_reading_measures': False,
+        },
+    )
+    mirrors: dict[str, list[str]] = field(
+        default_factory=lambda: {
+            'gaze': ['https://osf.io/download/'],
+        },
+    )
+
+    resources: dict[str, list[dict[str, str]]] = field(
+        default_factory=lambda: {
+            'gaze': [
+                {
+                    'resource': '9vbs8/',
+                    'filename': 'a.zip',
+                    'md5': '87c6c74a9a17cbd093b91f9415e8dd9d',
+                },
+                {
+                    'resource': 'yqukn/',
+                    'filename': 'b.zip',
+                    'md5': '54038547b1a373253b38999a227dde63',
+                },
+                {
+                    'resource': 'yf2xa/',
+                    'filename': 'e.zip',
+                    'md5': 'a0d0203cbb273f6908c1b52a42750551',
+                },
+            ],
+        },
+    )
+    extract: dict[str, bool] = field(default_factory=lambda: {'gaze': True})
+
+    experiment: Experiment = field(
+        default_factory=lambda: Experiment(
+            screen=Screen(
+                width_px=1920,
+                height_px=1080,
+                width_cm=59.76,
+                height_cm=33.615,
+                distance_cm=None,
+                origin='center',
+            ),
+            eyetracker=EyeTracker(
+                sampling_rate=1000,
+                left=False,
+                right=True,
+                model='EyeLink 1000 Plus',
+                vendor='EyeLink',
+                mount='Remote',
+            ),
+        ),
+    )
+
+    filename_format: dict[str, str] = field(
+        default_factory=lambda: {
+            'gaze': r'{subject_id:d}_{session_id:d}_{condition:s}_{trial_id:d}_{block_id:d}.csv',
+        },
+    )
+
+    filename_format_schema_overrides: dict[str, dict[str, type]] = field(
+        default_factory=lambda: {
+            'gaze': {
+                'subject_id': int,
+                'trial_id': int,
+                'block_id': int,
+            },
+        },
+    )
+
+    trial_columns: list[str] = field(
+        default_factory=lambda: [
+            'trial_id',
+            'subject_id',
+        ],
+    )
+
+    time_column: str = 'eyelink_timestamp'
+
+    time_unit: str = 'ms'
+
+    distance_column: str = 'target_distance'
+
+    pixel_columns: list[str] = field(
+        default_factory=lambda: [
+            'x_pix_eyelink',
+            'y_pix_eyelink',
+        ],
+    )
+
+    column_map: dict[str, str] = field(
+        default_factory=lambda: {},
+    )
+
+    custom_read_kwargs: dict[str, dict[str, Any]] = field(
+        default_factory=lambda: {
+            'gaze': {
+                'schema_overrides': {
+                    'trial_id': pl.Float32,
+                    'block_id': pl.Float32,
+                    'x_pix_eyelink': pl.Float32,
+                    'y_pix_eyelink': pl.Float32,
+                    'eyelink_timestamp': pl.Int64,
+                    'x_pix_pupilcore_interpolated': pl.Float32,
+                    'y_pix_pupilcore_interpolated': pl.Float32,
+                    'pupil_size_eyelink': pl.Float32,
+                    'target_distance': pl.Float32,
+                    'pupil_size_pupilcore_interpolated': pl.Float32,
+                    'pupil_confidence_interpolated': pl.Float32,
+                    'time_to_prev_bac': pl.Float32,
+                    'time_to_next_bac': pl.Float32,
+                    'prev_bac': pl.Float32,
+                    'next_bac': pl.Float32,
+                },
+                'separator': ',',
+            },
+        },
+    )
diff --git a/src/pymovements/datasets/potsdam_binge_remote_pvt.yaml b/src/pymovements/datasets/potsdam_binge_remote_pvt.yaml
new file mode 100644
index 0000000..bfc6a5f
--- /dev/null
+++ b/src/pymovements/datasets/potsdam_binge_remote_pvt.yaml
@@ -0,0 +1,85 @@
+name: "PotsdamBingeRemotePVT"
+
+has_files:
+  gaze: true
+  precomputed_events: false
+  precomputed_reading_measures: false
+
+mirrors:
+  gaze:
+    - "https://osf.io/download/"
+
+resources:
+  gaze:
+    - resource: "9vbs8/"
+      filename: "a.zip"
+      md5: "87c6c74a9a17cbd093b91f9415e8dd9d"
+    - resource: "yqukn/"
+      filename: "b.zip"
+      md5: "54038547b1a373253b38999a227dde63"
+    - resource: "yf2xa/"
+      filename: "e.zip"
+      md5: "a0d0203cbb273f6908c1b52a42750551"
+
+extract:
+  gaze: true
+
+experiment:
+  screen:
+    width_px: 1920
+    height_px: 1080
+    width_cm: 59.76
+    height_cm: 33.615
+    origin: "center"
+  eyetracker:
+    sampling_rate: 1000
+    left: false
+    right: true
+    model: "EyeLink 1000 Plus"
+    vendor: "EyeLink"
+    mount: "Remote"
+
+filename_format:
+  gaze: '{subject_id:d}_{session_id:d}_{condition:s}_{trial_id:d}_{block_id:d}.csv'
+
+filename_format_schema_overrides:
+  gaze:
+    subject_id: !int
+    trial_id: !int
+    block_id: !int
+
+trial_columns:
+  - "trial_id"
+  - "subject_id"
+
+time_column: "eyelink_timestamp"
+
+time_unit: "ms"
+
+distance_column: "target_distance"
+
+pixel_columns:
+  - "x_pix_eyelink"
+  - "y_pix_eyelink"
+
+column_map: {}
+
+custom_read_kwargs:
+  gaze:
+    schema_overrides:
+      trial_id: !polars.Float32
+      block_id: !polars.Float32
+      x_pix_eyelink: !polars.Float32
+      y_pix_eyelink: !polars.Float32
+      eyelink_timestamp: !polars.Int64
+      x_pix_pupilcore_interpolated: !polars.Float32
+      y_pix_pupilcore_interpolated: !polars.Float32
+      pupil_size_eyelink: !polars.Float32
+      target_distance: !polars.Float32
+      pupil_size_pupilcore_interpolated: !polars.Float32
+      pupil_confidence_interpolated: !polars.Float32
+      time_to_prev_bac: !polars.Float32
+      time_to_next_bac: !polars.Float32
+      prev_bac: !polars.Float32
+      next_bac: !polars.Float32
+    separator: ","
diff --git a/src/pymovements/datasets/potsdam_binge_wearable_pvt.py b/src/pymovements/datasets/potsdam_binge_wearable_pvt.py
new file mode 100644
index 0000000..0d8225c
--- /dev/null
+++ b/src/pymovements/datasets/potsdam_binge_wearable_pvt.py
@@ -0,0 +1,254 @@
+# Copyright (c) 2022-2025 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.
+"""Provides a definition for the PotsdamBingeWearablePVT 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.gaze.experiment import Experiment
+from pymovements.gaze.eyetracker import EyeTracker
+from pymovements.gaze.screen import Screen
+
+
+@dataclass
+class PotsdamBingeWearablePVT(DatasetDefinition):
+    """PotsdamBingeWearablePVT dataset :cite:p:`PotsdamBingePVT`.
+
+    This dataset includes monocular eye tracking data from 57 participants in two sessions with an
+    interval of at least one week between two sessions. Eye movements are recorded at a sampling
+    frequency of ~200 Hz (upsampled to 1000 Hz and synchronised with the EyeLink 1000 Plus
+    tracking the right eye) using Pupil Core eye-tracking glasses and are provided as
+    pixel coordinates. Participants are instructed to perform a PVT trial.
+
+    Check the respective `repository <https://osf.io/qf7e6/>`_ for details.
+
+    Attributes
+    ----------
+    name: str
+        The name of the dataset.
+
+    has_files: dict[str, bool]
+        Indicate whether the dataset contains 'gaze', 'precomputed_events', and
+        'precomputed_reading_measures'.
+
+    mirrors: dict[str, list[str]]
+        A tuple of mirrors of the dataset. Each entry must be of type `str` and end with a '/'.
+
+    resources: dict[str, list[dict[str, str]]]
+        A tuple of dataset gaze_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.
+
+    extract: dict[str, bool]
+        Decide whether to extract the data.
+
+    experiment: Experiment
+        The experiment definition.
+
+    filename_format: dict[str, str]
+        Regular expression which will be matched before trying to load the file. Namedgroups will
+        appear in the `fileinfo` dataframe.
+
+    filename_format_schema_overrides: dict[str, dict[str, type]]
+        If named groups are present in the `filename_format`, this makes it possible to cast
+        specific named groups to a particular datatype.
+
+    trial_columns: list[str]
+            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: str
+        The name of the timestamp column in the input data frame. This column will be renamed to
+        ``time``.
+
+    time_unit: str
+        The unit of the timestamps in the timestamp column in the input data frame. Supported
+        units are 's' for seconds, 'ms' for milliseconds and 'step' for steps. If the unit is
+        'step' the experiment definition must be specified. All timestamps will be converted to
+        milliseconds.
+
+    distance_column: str
+        The name of the distance column in the input data frame. These column will be
+        used to convert pixel coordinates into degrees of visual angle.
+
+    pixel_columns: list[str]
+        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.
+
+    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, dict[str, Any]]
+        If specified, these keyword arguments will be passed to the file reading function.
+
+
+    Examples
+    --------
+    Initialize your :py:class:`~pymovements.dataset.Dataset` object with the
+    :py:class:`~pymovements.datasets.PotsdamBingeWearablePVT` definition:
+
+    >>> import pymovements as pm
+    >>>
+    >>> dataset = pm.Dataset("PotsdamBingeWearablePVT", path='data/PotsdamBingeWearablePVT')
+
+    Download the dataset 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 = 'PotsdamBingeWearablePVT'
+
+    has_files: dict[str, bool] = field(
+        default_factory=lambda: {
+            'gaze': True,
+            'precomputed_events': False,
+            'precomputed_reading_measures': False,
+        },
+    )
+    mirrors: dict[str, list[str]] = field(
+        default_factory=lambda: {
+            'gaze': ['https://osf.io/download/'],
+        },
+    )
+
+    resources: dict[str, list[dict[str, str]]] = field(
+        default_factory=lambda: {
+            'gaze': [
+                {
+                    'resource': '9vbs8/',
+                    'filename': 'a.zip',
+                    'md5': '87c6c74a9a17cbd093b91f9415e8dd9d',
+                },
+                {
+                    'resource': 'yqukn/',
+                    'filename': 'b.zip',
+                    'md5': '54038547b1a373253b38999a227dde63',
+                },
+                {
+                    'resource': 'yf2xa/',
+                    'filename': 'e.zip',
+                    'md5': 'a0d0203cbb273f6908c1b52a42750551',
+                },
+            ],
+        },
+    )
+    extract: dict[str, bool] = field(default_factory=lambda: {'gaze': True})
+
+    experiment: Experiment = field(
+        default_factory=lambda: Experiment(
+            screen=Screen(
+                width_px=1920,
+                height_px=1080,
+                width_cm=59.76,
+                height_cm=33.615,
+                origin='center',
+            ),
+            eyetracker=EyeTracker(
+                sampling_rate=1000,
+                left=True,
+                right=False,
+                model='Pupil Core eye-tracking glasses',
+                vendor='Pupil Labs',
+                mount='Wearable',
+            ),
+        ),
+    )
+
+    filename_format: dict[str, str] = field(
+        default_factory=lambda: {
+            'gaze': r'{subject_id:d}_{session_id:d}_{condition:s}_{trial_id:d}_{block_id:d}.csv',
+        },
+    )
+
+    filename_format_schema_overrides: dict[str, dict[str, type]] = field(
+        default_factory=lambda: {
+            'gaze': {
+                'subject_id': int,
+                'trial_id': int,
+                'block_id': int,
+            },
+        },
+    )
+
+    trial_columns: list[str] = field(
+        default_factory=lambda: [
+            'trial_id',
+            'subject_id',
+        ],
+    )
+
+    time_column: str = 'eyelink_timestamp'
+
+    time_unit: str = 'ms'
+
+    distance_column: str = 'target_distance'
+
+    pixel_columns: list[str] = field(
+        default_factory=lambda: [
+            'x_pix_pupilcore_interpolated',
+            'y_pix_pupilcore_interpolated',
+        ],
+    )
+
+    column_map: dict[str, str] = field(
+        default_factory=lambda: {},
+    )
+
+    custom_read_kwargs: dict[str, dict[str, Any]] = field(
+        default_factory=lambda: {
+            'gaze': {
+                'schema_overrides': {
+                    'trial_id': pl.Float32,
+                    'block_id': pl.Float32,
+                    'x_pix_eyelink': pl.Float32,
+                    'y_pix_eyelink': pl.Float32,
+                    'eyelink_timestamp': pl.Int64,
+                    'x_pix_pupilcore_interpolated': pl.Float32,
+                    'y_pix_pupilcore_interpolated': pl.Float32,
+                    'pupil_size_eyelink': pl.Float32,
+                    'target_distance': pl.Float32,
+                    'pupil_size_pupilcore_interpolated': pl.Float32,
+                    'pupil_confidence_interpolated': pl.Float32,
+                    'time_to_prev_bac': pl.Float32,
+                    'time_to_next_bac': pl.Float32,
+                    'prev_bac': pl.Float32,
+                    'next_bac': pl.Float32,
+                },
+                'separator': ',',
+            },
+        },
+    )
diff --git a/src/pymovements/datasets/potsdam_binge_wearable_pvt.yaml b/src/pymovements/datasets/potsdam_binge_wearable_pvt.yaml
new file mode 100644
index 0000000..063f45d
--- /dev/null
+++ b/src/pymovements/datasets/potsdam_binge_wearable_pvt.yaml
@@ -0,0 +1,85 @@
+name: "PotsdamBingeWearablePVT"
+
+has_files:
+  gaze: true
+  precomputed_events: false
+  precomputed_reading_measures: false
+
+mirrors:
+  gaze:
+    - "https://osf.io/download/"
+
+resources:
+  gaze:
+    - resource: "9vbs8/"
+      filename: "a.zip"
+      md5: "87c6c74a9a17cbd093b91f9415e8dd9d"
+    - resource: "yqukn/"
+      filename: "b.zip"
+      md5: "54038547b1a373253b38999a227dde63"
+    - resource: "yf2xa/"
+      filename: "e.zip"
+      md5: "a0d0203cbb273f6908c1b52a42750551"
+
+extract:
+  gaze: true
+
+experiment:
+  screen:
+    width_px: 1920
+    height_px: 1080
+    width_cm: 59.76
+    height_cm: 33.615
+    origin: "center"
+  eyetracker:
+    sampling_rate: 1000
+    left: true
+    right: false
+    model: "Pupil Core eye-tracking glasses"
+    vendor: "Pupil Labs"
+    mount: "Wearable"
+
+filename_format:
+  gaze: '{subject_id:d}_{session_id:d}_{condition:s}_{trial_id:d}_{block_id:d}.csv'
+
+filename_format_schema_overrides:
+  gaze:
+    subject_id: !int
+    trial_id: !int
+    block_id: !int
+
+trial_columns:
+  - "trial_id"
+  - "subject_id"
+
+time_column: "eyelink_timestamp"
+
+time_unit: "ms"
+
+distance_column: "target_distance"
+
+pixel_columns:
+  - "x_pix_pupilcore_interpolated"
+  - "y_pix_pupilcore_interpolated"
+
+column_map: {}
+
+custom_read_kwargs:
+  gaze:
+    schema_overrides:
+      trial_id: !polars.Float32
+      block_id: !polars.Float32
+      x_pix_eyelink: !polars.Float32
+      y_pix_eyelink: !polars.Float32
+      eyelink_timestamp: !polars.Int64
+      x_pix_pupilcore_interpolated: !polars.Float32
+      y_pix_pupilcore_interpolated: !polars.Float32
+      pupil_size_eyelink: !polars.Float32
+      target_distance: !polars.Float32
+      pupil_size_pupilcore_interpolated: !polars.Float32
+      pupil_confidence_interpolated: !polars.Float32
+      time_to_prev_bac: !polars.Float32
+      time_to_next_bac: !polars.Float32
+      prev_bac: !polars.Float32
+      next_bac: !polars.Float32
+    separator: ","


Test patch:
diff --git a/tests/files/potsdam_binge_pvt_example.csv b/tests/files/potsdam_binge_pvt_example.csv
new file mode 100644
index 0000000..d2be879
--- /dev/null
+++ b/tests/files/potsdam_binge_pvt_example.csv
@@ -0,0 +1,30 @@
+trial_id,block_id,x_pix_eyelink,y_pix_eyelink,eyelink_timestamp,x_pix_pupilcore_interpolated,y_pix_pupilcore_interpolated,pupil_size_eyelink,target_distance,pupil_size_pupilcore_interpolated,pupil_confidence_interpolated,time_to_prev_bac,time_to_next_bac,prev_bac,next_bac
+6.0,1.0,991.9,485.2,18126651,966.642569317413,573.6626048616831,1550.0,772.7,34.64164352416992,0.7500827312469482,113139.0,812840.0,0.0463,0.045
+6.0,1.0,991.8,484.9,18126652,967.5480377702872,574.8472287830976,1550.0,772.7,34.676652908325195,0.7351271361112595,113140.0,812839.0,0.0463,0.045
+6.0,1.0,991.5,484.8,18126653,968.4535062231612,576.0318527045122,1550.0,772.7,34.71166229248047,0.7201715409755707,113141.0,812838.0,0.0463,0.045
+6.0,1.0,991.2,484.6,18126654,969.3589746760356,577.2164766259268,1550.0,772.7,34.74667167663574,0.7052159458398819,113142.0,812837.0,0.0463,0.045
+6.0,1.0,990.9,484.9,18126655,970.2644431289096,578.4011005473413,1550.0,772.7,34.781681060791016,0.6902603507041931,113143.0,812836.0,0.0463,0.045
+6.0,1.0,990.6,485.0,18126656,969.7488556118012,578.4369715347565,1550.0,772.7,34.76906967163086,0.732580413420995,113144.0,812835.0,0.0463,0.045
+6.0,1.0,990.8,485.1,18126657,969.2332680946932,578.4728425221717,1550.0,772.7,34.7564582824707,0.774900476137797,113145.0,812834.0,0.0463,0.045
+6.0,1.0,991.1,484.8,18126658,968.7176805775848,578.5087135095869,1550.0,772.7,34.74384689331055,0.817220538854599,113146.0,812833.0,0.0463,0.045
+6.0,1.0,991.2,484.6,18126659,968.2020930604764,578.544584497002,1552.0,772.7,34.73123550415039,0.8595406015714009,113147.0,812832.0,0.0463,0.045
+6.0,1.0,991.4,484.2,18126660,967.6865055433684,578.5804554844173,1553.0,772.7,34.718624114990234,0.9018606642882028,113148.0,812831.0,0.0463,0.045
+6.0,1.0,991.4,483.8,18126661,967.17091802626,578.6163264718324,1553.0,772.7,34.70601272583008,0.9441807270050048,113149.0,812830.0,0.0463,0.045
+6.0,1.0,991.3,483.4,18126662,967.8652707632932,578.0493909241591,1554.0,772.7,34.72298049926758,0.935360252857208,113150.0,812829.0,0.0463,0.045
+6.0,1.0,991.1,483.2,18126663,968.5596235003268,577.4824553764857,1555.0,772.7,34.73994827270508,0.9265397787094116,113151.0,812828.0,0.0463,0.045
+6.0,1.0,990.8,482.9,18126664,969.25397623736,576.9155198288123,1555.0,772.7,34.75691604614258,0.917719304561615,113152.0,812827.0,0.0463,0.045
+6.0,1.0,990.5,482.7,18126665,969.9483289743936,576.348584281139,1556.0,772.7,34.77388381958008,0.9088988304138184,113153.0,812826.0,0.0463,0.045
+6.0,1.0,990.2,482.6,18126666,970.6426817114268,575.7816487334657,1555.0,772.7,34.79085159301758,0.9000783562660217,113154.0,812825.0,0.0463,0.045
+6.0,1.0,989.8,482.9,18126667,970.0842197723862,576.6529213474716,1555.0,772.7,34.781462987264,0.8872625728448232,113155.0,812824.0,0.0463,0.045
+6.0,1.0,989.5,483.1,18126668,969.5257578333456,577.5241939614775,1555.0,772.7,34.772074381510414,0.8744467894236246,113156.0,812823.0,0.0463,0.045
+6.0,1.0,989.1,483.4,18126669,968.9672958943048,578.3954665754834,1554.0,772.7,34.76268577575684,0.8616310060024261,113157.0,812822.0,0.0463,0.045
+6.0,1.0,989.0,483.6,18126670,968.4088339552644,579.2667391894894,1554.0,772.7,34.75329717000326,0.8488152225812277,113158.0,812821.0,0.0463,0.045
+6.0,1.0,989.5,483.9,18126671,967.8503720162236,580.1380118034954,1554.0,772.7,34.74390856424967,0.8359994391600291,113159.0,812820.0,0.0463,0.045
+6.0,1.0,989.9,484.3,18126672,967.291910077183,581.0092844175012,1553.0,772.7,34.734519958496094,0.8231836557388306,113160.0,812819.0,0.0463,0.045
+6.0,1.0,990.4,484.6,18126673,968.0410653977916,579.9311453628943,1552.0,772.7,34.73237228393555,0.7933196276426315,113161.0,812818.0,0.0463,0.045
+6.0,1.0,991.0,484.8,18126674,968.7902207184002,578.8530063082873,1552.0,772.7,34.730224609375,0.7634555995464325,113162.0,812817.0,0.0463,0.045
+6.0,1.0,991.5,484.9,18126675,969.5393760390087,577.7748672536802,1553.0,772.7,34.72807693481445,0.7335915714502335,113163.0,812816.0,0.0463,0.045
+6.0,1.0,992.0,484.9,18126676,970.2885313596172,576.6967281990733,1553.0,772.7,34.725929260253906,0.7037275433540344,113164.0,812815.0,0.0463,0.045
+6.0,1.0,992.5,484.7,18126677,970.420367515256,576.9247130493401,1554.0,772.7,34.73989168802897,0.7128233214219412,113165.0,812814.0,0.0463,0.045
+6.0,1.0,992.4,484.1,18126678,970.552203670895,577.1526978996069,1555.0,772.7,34.75385411580404,0.7219190994898478,113166.0,812813.0,0.0463,0.045
+6.0,1.0,992.4,483.5,18126679,970.6840398265338,577.3806827498737,1556.0,772.7,34.7678165435791,0.7310148775577545,113167.0,812812.0,0.0463,0.045
diff --git a/tests/functional/dataset_processing_test.py b/tests/functional/dataset_processing_test.py
index 1b0b4f8..7a68efa 100644
--- a/tests/functional/dataset_processing_test.py
+++ b/tests/functional/dataset_processing_test.py
@@ -40,6 +40,8 @@ import pymovements as pm
         'gazegraph',
         'judo1000',
         'potec',
+        'potsdam_binge_remote_pvt',
+        'potsdam_binge_wearable_pvt',
     ],
 )
 def fixture_dataset_init_kwargs(request):
@@ -195,6 +197,26 @@ def fixture_dataset_init_kwargs(request):
             filename_format_schema_overrides={'gaze': {}},
             trial_columns=None,
         ),
+        'potsdam_binge_remote_pvt': pm.datasets.PotsdamBingeRemotePVT(
+            has_files={
+                'gaze': True,
+                'precomputed_events': False,
+                'precomputed_reading_measures': False,
+            },
+            filename_format={'gaze': 'potsdam_binge_pvt_example.csv'},
+            filename_format_schema_overrides={'gaze': {}},
+            trial_columns=None,
+        ),
+        'potsdam_binge_wearable_pvt': pm.datasets.PotsdamBingeWearablePVT(
+            has_files={
+                'gaze': True,
+                'precomputed_events': False,
+                'precomputed_reading_measures': False,
+            },
+            filename_format={'gaze': 'potsdam_binge_pvt_example.csv'},
+            filename_format_schema_overrides={'gaze': {}},
+            trial_columns=None,
+        ),
     }
     yield pm.dataset.Dataset(
         definition=init_param_dict[request.param],
diff --git a/tests/unit/datasets/datasets_test.py b/tests/unit/datasets/datasets_test.py
index 071431a..012b554 100644
--- a/tests/unit/datasets/datasets_test.py
+++ b/tests/unit/datasets/datasets_test.py
@@ -46,6 +46,16 @@ from pymovements import DatasetLibrary
         pytest.param(pm.datasets.JuDo1000, 'JuDo1000', id='JuDo1000'),
         pytest.param(pm.datasets.MouseCursor, 'MouseCursor', id='MouseCursor'),
         pytest.param(pm.datasets.PoTeC, 'PoTeC', id='PoTeC'),
+        pytest.param(
+            pm.datasets.PotsdamBingeRemotePVT,
+            'PotsdamBingeRemotePVT',
+            id='PotsdamBingeRemotePVT',
+        ),
+        pytest.param(
+            pm.datasets.PotsdamBingeWearablePVT,
+            'PotsdamBingeWearablePVT',
+            id='PotsdamBingeWearablePVT',
+        ),
         pytest.param(pm.datasets.Provo, 'Provo', id='Provo'),
         pytest.param(pm.datasets.SBSAT, 'SBSAT', id='SBSAT'),
         pytest.param(pm.datasets.ToyDataset, 'ToyDataset', id='ToyDataset'),