Repository: aeye-lab/pymovements
Pull request #340 (2023-04-27 09:57:22)

feat(dataset): Add support for parsing dataset asc files just like other filetypes

codecov-commenter: ## [Codecov](https://codecov.io/gh/aeye-lab/pymovements/pull/340?src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=aeye-lab) Report
Patch coverage: **`94.44`**% and project coverage change: **`-0.12`** :warning:
> Comparison is base [(`80336ae`)](https://codecov.io/gh/aeye-lab/pymovements/commit/80336ae43e7b812f1bf654edde5787d5092af627?el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=aeye-lab) 100.00% compared to head [(`597f0ca`)](https://codecov.io/gh/aeye-lab/pymovements/pull/340?src=pr&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=aeye-lab) 99.88%.

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


```diff
@@             Coverage Diff             @@
##              main     #340      +/-   ##
===========================================
- Coverage   100.00%   99.88%   -0.12%     
===========================================
  Files           53       53              
  Lines         1814     1803      -11     
  Branches       386      386              
===========================================
- Hits          1814     1801      -13     
- Misses           0        1       +1     
- Partials         0        1       +1     
```


| [Impacted Files](https://codecov.io/gh/aeye-lab/pymovements/pull/340?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=aeye-lab) | Coverage Δ | |
|---|---|---|
| [src/pymovements/dataset/dataset\_files.py](https://codecov.io/gh/aeye-lab/pymovements/pull/340?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=aeye-lab#diff-c3JjL3B5bW92ZW1lbnRzL2RhdGFzZXQvZGF0YXNldF9maWxlcy5weQ==) | `98.30% <88.23%> (-1.70%)` | :arrow_down: |
| [src/pymovements/utils/parsing.py](https://codecov.io/gh/aeye-lab/pymovements/pull/340?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/340?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/dataset/dataset_files.py b/src/pymovements/dataset/dataset_files.py
index f47e942..9602fd8 100644
--- a/src/pymovements/dataset/dataset_files.py
+++ b/src/pymovements/dataset/dataset_files.py
@@ -30,6 +30,7 @@ from pymovements.dataset.dataset_definition import DatasetDefinition
 from pymovements.dataset.dataset_paths import DatasetPaths
 from pymovements.events.events import EventDataFrame
 from pymovements.gaze.gaze_dataframe import GazeDataFrame
+from pymovements.utils.parsing import parse_eyelink
 from pymovements.utils.paths import match_filepaths
 from pymovements.utils.strings import curly_to_regex
 
@@ -204,15 +205,11 @@ def load_gaze_files(
                 extension=extension,
             )
 
-        if filepath.suffix == '.csv':
-            if preprocessed:
-                gaze_df = pl.read_csv(filepath)
-            else:
-                gaze_df = pl.read_csv(filepath, **definition.custom_read_kwargs)
-        elif filepath.suffix == '.feather':
-            gaze_df = pl.read_ipc(filepath)
-        else:
-            raise RuntimeError(f'data files of type {filepath.suffix} are not supported')
+        gaze_df = load_gaze_file(
+            filepath=filepath,
+            preprocessed=preprocessed,
+            custom_read_kwargs=definition.custom_read_kwargs,
+        )
 
         # Add fileinfo columns to dataframe.
         gaze_df = add_fileinfo(
@@ -226,6 +223,56 @@ def load_gaze_files(
     return gaze_dfs
 
 
+def load_gaze_file(
+        filepath: Path,
+        preprocessed: bool = False,
+        custom_read_kwargs: dict[str, Any] | None = None,
+) -> pl.DataFrame:
+    """Load a gaze data file as a polars DataFrame.
+
+    Parameters
+    ----------
+    filepath:
+        Path of gaze file.
+    preprocessed:
+        If ``True``, saved preprocessed data will be loaded, otherwise raw data will be loaded.
+    custom_read_kwargs:
+        Dictionary of keyword arguments for reading gaze file.
+
+    Returns
+    -------
+    pl.DataFrame
+        The resulting polars.DataFrame
+
+    Raises
+    ------
+    RuntimeError
+        If file type of gaze file is not supported.
+    ValueError
+        If extension is not in list of valid extensions.
+    """
+    if custom_read_kwargs is None:
+        custom_read_kwargs = {}
+
+    if filepath.suffix == '.csv':
+        if preprocessed:
+            gaze_df = pl.read_csv(filepath)
+        else:
+            gaze_df = pl.read_csv(filepath, **custom_read_kwargs)
+    elif filepath.suffix == '.feather':
+        gaze_df = pl.read_ipc(filepath)
+    elif filepath.suffix == '.asc':
+        gaze_df = parse_eyelink(filepath, **custom_read_kwargs)
+    else:
+        valid_extensions = ['csv', 'feather', 'asc']
+        raise ValueError(
+            f'unsupported file format "{filepath.suffix}".'
+            f'Supported formats are: {valid_extensions}',
+        )
+
+    return gaze_df
+
+
 def add_fileinfo(
         definition: DatasetDefinition,
         df: pl.DataFrame,
@@ -389,7 +436,7 @@ def save_preprocessed(
         elif extension == 'csv':
             gaze_df_out.write_csv(preprocessed_filepath)
         else:
-            valid_extensions = ['csv', 'feather']
+            valid_extensions = ['csv', 'feather', 'asc']
             raise ValueError(
                 f'unsupported file format "{extension}".'
                 f'Supported formats are: {valid_extensions}',
diff --git a/src/pymovements/utils/parsing.py b/src/pymovements/utils/parsing.py
index b2369d2..3942c79 100755
--- a/src/pymovements/utils/parsing.py
+++ b/src/pymovements/utils/parsing.py
@@ -20,7 +20,6 @@
 """ Converts ascii to csv files."""
 from __future__ import annotations
 
-import os
 import re
 from pathlib import Path
 
@@ -29,22 +28,14 @@ import polars as pl
 
 EYE_TRACKING_SAMPLE = re.compile(
     r'(?P<time>(\d{7}|\d{8}|\d{9}|\d{10}))\s+'
-    r'(?P<x_eye>[-]?\d*[.]\d*)\s+'
-    r'(?P<y_eye>[-]?\d*[.]\d*)\s+'
-    r'(?P<pupil_eye>\d*[.]\d*)\s+'
+    r'(?P<x_pix>[-]?\d*[.]\d*)\s+'
+    r'(?P<y_pix>[-]?\d*[.]\d*)\s+'
+    r'(?P<pupil>\d*[.]\d*)\s+'
     r'(?P<dummy>\d*[.]\d*)\s+'
-    r'((?P<dots>[A-Za-z.]{5}))?\s*',
+    r'(?P<dots>[A-Za-z.]{5})?\s*',
 )
 
 
-class scolor:
-    """Implements colors for conversion"""
-    created: str = '\x1b[1;37;42m'
-    skipped: str = '\x1b[1;37;44m'
-    rewritten: str = '\x1b[1;37;43m'
-    end: str = '\x1b[0m'
-
-
 def check_nan(sample_location: str) -> float:
     """Returns position as float or np.nan depending on validity of sample
 
@@ -61,30 +52,16 @@ def check_nan(sample_location: str) -> float:
     return ret
 
 
-def get_eye_tracking_sample_feature(eye_tracking_sample_match: re.Match, feature: str) -> str:
-    """Extract sample information from line.
-
-    Parameters
-    ----------
-    eye_tracking_sample_match: re.Match
-        Matched eye tracking sample pattern.
-    feature: str
-        feature to extract from matched eye tracking sample pattern.
-    """
-    return eye_tracking_sample_match.group(feature)
-
-
-def process_asc2csv(
-        file_name: Path,
+def parse_eyelink(
+        filepath: Path,
         sync_msg_start_pattern: None | str = None,
         sync_msg_stop_pattern: None | str = None,
-        overwrite_existing: bool = False,
 ) -> pl.DataFrame:
     """Processes ascii files to csv.
 
     Parameters
     ----------
-    file_name: Path
+    filepath: Path
         file name of ascii file to convert
     sync_msg_start_pattern: str,
         Optional starting pattern of trial as sync message,
@@ -92,8 +69,6 @@ def process_asc2csv(
     sync_msg_stop_pattern: str,
         Optional stopping pattern of trial as sync message,
         if None is given reverts back to previous TRIALID
-    overwrite_existing: bool,
-        Decide whether to overwrite an existing csv file.
     """
     if sync_msg_start_pattern is None:
         # if no specific type pattern revert to trial ids
@@ -109,35 +84,18 @@ def process_asc2csv(
         _sync_msg_stop_pattern = re.compile(
             fr'MSG\t\d+\ \d+\ \S+(?P<stoppattern>{sync_msg_stop_pattern})\n',
         )
-    csv_file_name = f"{Path(*file_name.parts[:-1])}/{file_name.parts[-1].split('.')[0]}.csv"
-    df = pl.DataFrame(
-        schema=[
-            ('time', pl.Int64),
-            ('x_eye', pl.Float64),
-            ('y_eye', pl.Float64),
-            ('pupil_eye', pl.Float64),
-            ('task', pl.Utf8),
-        ],
-    )
-    if not os.path.exists(csv_file_name):
-        status_msg = 'Created'
-        status_clr = scolor.created
-    elif os.path.exists(csv_file_name) and not overwrite_existing:
-        status_msg = 'Skipped'
-        status_clr = scolor.skipped
-        written_msg = f'Processing csv for {file_name}'.ljust(105 - len(status_msg), '.')
-        print(written_msg, end='', flush=True)
-        print(f'{status_clr}{status_msg}{scolor.end}', flush=True)
-        return df
-    else:
-        status_msg = 'Rewritten'
-        status_clr = scolor.rewritten
 
-    written_msg = f'Processing csv for {file_name}'.ljust(105 - len(status_msg), '.')
-    print(written_msg, end='', flush=True)
-    with open(file_name, encoding='ascii') as asc_file:
+    samples: dict[str, list] = {
+        'time': [],
+        'x_pix': [],
+        'y_pix': [],
+        'pupil': [],
+        'task': [],
+    }
+
+    with open(filepath, encoding='ascii') as asc_file:
         lines = asc_file.readlines()
-    cur_task_type = None
+    current_task = None
     during_task_recording = False
     for line in lines:
 
@@ -149,9 +107,9 @@ def process_asc2csv(
             assert start_matched is not None
             start_id = start_matched.group('startpattern')
             if sync_msg_start_pattern is not None:
-                cur_task_type = str(sync_msg_start_pattern) + start_id
+                current_task = str(sync_msg_start_pattern) + start_id
             else:
-                cur_task_type = f'TRIALID_{start_id}'
+                current_task = f'TRIALID_{start_id}'
             during_task_recording = True
         if during_task_recording:
             if EYE_TRACKING_SAMPLE.match(line):
@@ -159,39 +117,31 @@ def process_asc2csv(
                 # mypy is unaware that 'eye_tracking_sample_match' can never be None (l.156)
                 assert eye_tracking_sample_match is not None
 
-                timestamp_s = get_eye_tracking_sample_feature(
-                    eye_tracking_sample_match,
-                    'time',
-                )
-                x_eye_s = get_eye_tracking_sample_feature(
-                    eye_tracking_sample_match,
-                    'x_eye',
-                )
-                y_eye_s = get_eye_tracking_sample_feature(
-                    eye_tracking_sample_match,
-                    'y_eye',
-                )
-                pupil_eye_s = get_eye_tracking_sample_feature(
-                    eye_tracking_sample_match,
-                    'pupil_eye',
-                )
+                timestamp_s = eye_tracking_sample_match.group('time')
+                x_pix_s = eye_tracking_sample_match.group('x_pix')
+                y_pix_s = eye_tracking_sample_match.group('y_pix')
+                pupil_s = eye_tracking_sample_match.group('pupil')
 
                 timestamp = int(timestamp_s)
-                x_eye = check_nan(x_eye_s)
-                y_eye = check_nan(y_eye_s)
-                pupil_eye = check_nan(pupil_eye_s)
-                df = df.extend(
-                    pl.DataFrame(
-                        {
-                            'time': [timestamp],
-                            'x_eye': [x_eye],
-                            'y_eye': [y_eye],
-                            'pupil_eye': [pupil_eye],
-                            'task': [cur_task_type],
-                        },
-                    ),
-                )
-
-    df.write_csv(csv_file_name, separator='\t')
-    print(f'{status_clr}{status_msg}{scolor.end}', flush=True)
+                x_pix = check_nan(x_pix_s)
+                y_pix = check_nan(y_pix_s)
+                pupil = check_nan(pupil_s)
+
+                samples['time'].append(timestamp)
+                samples['x_pix'].append(x_pix)
+                samples['y_pix'].append(y_pix)
+                samples['pupil'].append(pupil)
+                samples['task'].append(current_task)
+
+    df = pl.from_dict(
+        data=samples,
+        schema={
+            'time': pl.Int64,
+            'x_pix': pl.Float64,
+            'y_pix': pl.Float64,
+            'pupil': pl.Float64,
+            'task': pl.Utf8,
+        },
+    )
+
     return df


Test patch:
diff --git a/tests/dataset/dataset_files_test.py b/tests/dataset/dataset_files_test.py
new file mode 100644
index 0000000..d605a81
--- /dev/null
+++ b/tests/dataset/dataset_files_test.py
@@ -0,0 +1,269 @@
+# 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.
+""" Tests pymovements asc to csv processing"""
+# flake8: noqa: E101, W191, E501
+# pylint: disable=duplicate-code
+import numpy as np
+import polars as pl
+import pytest
+from polars.testing import assert_frame_equal
+
+import pymovements as pm
+
+
+ASC_TEXT = r"""\
+** CONVERTED FROM D:\SamplePymovements\results\sub_1\sub_1.edf using edfapi 4.2.1 Win32  EyeLink Dataviewer Sub ComponentApr 01 1990 on Wed Sep 20 13:47:57 1989
+** DATE: Wed Sep  20 13:47:20 1989
+** TYPE: EDF_FILE BINARY EVENT SAMPLE TAGGED
+** VERSION: EYELINK II 1
+** SOURCE: EYELINK CL
+** EYELINK II CL v6.12 Feb  1 2018 (EyeLink Portable Duo)
+** CAMERA: EyeLink USBCAM Version 1.01
+** SERIAL NUMBER: CLU-DAB50
+** CAMERA_CONFIG: DAB50200.SCD
+** RECORDED BY SleepAlc
+** SREB2.2.299 WIN32 LID:20A87A96 Mod:2023.03.08 11:03 MEZ
+**
+
+MSG	2091650 !CMD 1 select_parser_configuration 0
+MSG	2091659 !CMD 0 fixation_update_interval = 50
+MSG	2091659 !CMD 0 fixation_update_accumulate = 50
+MSG	2091681 !CMD 1 auto_calibration_messages = YES
+MSG	2095865 DISPLAY_COORDS 0 0 1279 1023
+MSG	2095865 RETRACE_INTERVAL  16.646125144
+MSG	2095865 ENVIRONMENT   OpenGL on Windows (6, 2, 9200, 2, '')
+MSG	2095980 TRACKER_TIME 0 2095980.470
+MSG	2096367 -4 SYNCTIME 766 0
+MSG	2100624 SYNCTIME_READING
+INPUT	2117909	0
+INPUT	2126823	0
+MSG	2135819 !CAL
+>>>>>>> CALIBRATION (HV9,P-CR) FOR LEFT: <<<<<<<<<
+MSG	2135819 !CAL Calibration points:
+MSG	2135819 !CAL -32.6, -47.7        -0,    227
+MSG	2135819 !CAL -32.2, -63.6        -0,  -2267
+MSG	2135819 !CAL -32.6, -31.7        -0,   2624
+MSG	2135819 !CAL -58.0, -47.3     -3291,    227
+MSG	2135819 !CAL -7.7, -46.8      3291,    227
+MSG	2135820 !CAL -58.8, -65.2     -3358,  -2267
+MSG	2135820 !CAL -7.9, -61.4      3358,  -2267
+MSG	2135820 !CAL -55.5, -31.2     -3227,   2624
+MSG	2135820 !CAL -9.0, -31.3      3227,   2624
+MSG	2135820 !CAL  0.0,  0.0         0,      0
+MSG	2135820 !CAL eye check box: (L,R,T,B)
+	  -65     6   -72     7
+MSG	2135820 !CAL href cal range: (L,R,T,B)
+	-5037  5037 -3489  3847
+MSG	2135820 !CAL Cal coeff:(X=a+bx+cy+dxx+eyy,Y=f+gx+goaly+ixx+jyy)
+  -0.00043008  131.07  1.437  0.051949 -0.1007
+   227.35 -1.5024  153.47 -0.1679 -0.22845
+MSG	2135820 !CAL Prenormalize: offx, offy = -32.583 -47.715
+MSG	2135820 !CAL Quadrant center: centx, centy =
+  -0.00043025  227.35
+MSG	2135820 !CAL Corner correction:
+   9.5364e-06,  3.4194e-05
+  -1.6932e-05,  2.9132e-05
+   3.3933e-05,  3.5e-06
+   1.5902e-05,  8.6479e-06
+MSG	2135820 !CAL Gains: cx:152.074 lx:170.107 rx:152.936
+MSG	2135820 !CAL Gains: cy:128.550 ty:155.848 by:116.611
+MSG	2135820 !CAL Resolution (upd) at screen center: X=1.7, Y=2.0
+MSG	2135820 !CAL Gain Change Proportion: X: 0.112 Y: 0.336
+MSG	2135821 !CAL Gain Ratio (Gy/Gx) = 0.845
+MSG	2135821 !CAL Bad Y/X gain ratio: 0.845
+MSG	2135821 !CAL PCR gain ratio(x,y) = 2.507, 2.179
+MSG	2135821 !CAL CR gain match(x,y) = 1.010, 1.010
+MSG	2135821 !CAL Slip rotation correction OFF
+MSG	2135821 !CAL CALIBRATION HV9 L LEFT    GOOD
+INPUT	2137650	0
+MSG	2148587 !CAL VALIDATION HV9 L LEFT  GOOD ERROR 0.27 avg. 0.83 max  OFFSET 0.11 deg. 3.7,2.4 pix.
+MSG	2148587 VALIDATE L POINT 0  LEFT  at 640,512  OFFSET 0.19 deg.  7.2,1.0 pix.
+MSG	2148587 VALIDATE L POINT 1  LEFT  at 640,159  OFFSET 0.12 deg.  3.9,-2.2 pix.
+MSG	2148587 VALIDATE L POINT 2  LEFT  at 640,864  OFFSET 0.42 deg.  -15.8,0.9 pix.
+MSG	2148587 VALIDATE L POINT 3  LEFT  at 172,512  OFFSET 0.83 deg.  26.3,17.5 pix.
+MSG	2148587 VALIDATE L POINT 4  LEFT  at 1107,512  OFFSET 0.19 deg.  -4.3,5.7 pix.
+MSG	2148587 VALIDATE L POINT 5  LEFT  at 228,201  OFFSET 0.06 deg.  1.3,1.9 pix.
+MSG	2148587 VALIDATE L POINT 6  LEFT  at 1051,201  OFFSET 0.33 deg.  -3.0,-12.5 pix.
+MSG	2148587 VALIDATE L POINT 7  LEFT  at 228,822  OFFSET 0.18 deg.  -6.8,0.2 pix.
+MSG	2148587 VALIDATE L POINT 8  LEFT  at 1051,822  OFFSET 0.18 deg.  3.8,5.5 pix.
+INPUT	2153108	0
+MSG	2154447 DRIFTCORRECT L LEFT  at 133,133  OFFSET 0.38 deg.  12.5,7.9 pix.
+MSG	2154540 TRIALID 0
+MSG	2154555 RECCFG CR 1000 2 1 L
+MSG	2154555 ELCLCFG BTABLER
+MSG	2154555 GAZE_COORDS 0.00 0.00 1279.00 1023.00
+MSG	2154555 THRESHOLDS L 102 242
+MSG	2154555 ELCL_WINDOW_SIZES 176 188 0 0
+MSG	2154555 CAMERA_LENS_FOCAL_LENGTH 27.00
+MSG	2154555 PUPIL_DATA_TYPE RAW_AUTOSLIP
+MSG	2154555 ELCL_PROC CENTROID (3)
+MSG	2154555 ELCL_PCR_PARAM 5 3.0
+START	2154556 	LEFT	SAMPLES	EVENTS
+PRESCALER	1
+VPRESCALER	1
+PUPIL	AREA
+EVENTS	GAZE	LEFT	RATE	1000.00	TRACKING	CR	FILTER	2
+SAMPLES	GAZE	LEFT	RATE	1000.00	TRACKING	CR	FILTER	2	INPUT
+INPUT	2154556	0
+MSG	2154557 !MODE RECORD CR 1000 2 1 L
+2154557	  139.6	  132.1	  784.0	    0.0	...
+MSG	2154558 -11 SYNCTIME_READING_SCREEN_0
+2154558	  139.5	  131.9	  784.0	    0.0	...
+MSG	2154559 -11 !V DRAW_LIST ../../runtime/dataviewer/sub_1/graphics/VC_1.vcl
+2154560	   .	   .	    0.0	    0.0	...
+2154561	  850.7	  717.5	  714.0	    0.0	...
+MSG	2154562 TRACKER_TIME 1 2222195.987
+MSG	2154563 0 READING_SCREEN_0.STOP
+MSG	2154447 DRIFTCORRECT L LEFT  at 133,133  OFFSET 0.38 deg.  12.5,7.9 pix.
+MSG	2154540 TRIALID 1
+MSG	2154555 RECCFG CR 1000 2 1 L
+MSG	2154555 ELCLCFG BTABLER
+MSG	2154555 GAZE_COORDS 0.00 0.00 1279.00 1023.00
+MSG	2154555 THRESHOLDS L 102 242
+MSG	2154555 ELCL_WINDOW_SIZES 176 188 0 0
+MSG	2154555 CAMERA_LENS_FOCAL_LENGTH 27.00
+MSG	2154555 PUPIL_DATA_TYPE RAW_AUTOSLIP
+MSG	2154555 ELCL_PROC CENTROID (3)
+MSG	2154555 ELCL_PCR_PARAM 5 3.0
+MSG	2154564 -11 SYNCTIME_READING_SCREEN_1
+2154565	  139.5	  131.9	  784.0	    0.0	...
+MSG	2154566 -11 !V DRAW_LIST ../../runtime/dataviewer/sub_1/graphics/VC_2.vcl
+2154567	   .	   .	    0.0	    0.0	...
+2154568	  850.7	  717.5	  714.0	    0.0	...
+MSG	2154569 TRACKER_TIME 1 2222195.987
+MSG	2154570 0 READING_SCREEN_1.STOP
+"""
+
+EXPECTED_DF_NO_SYNC = pl.from_dict(
+    {
+        'time': [
+            2154557,
+            2154558,
+            2154560,
+            2154561,
+            2154565,
+            2154567,
+            2154568,
+        ], 'x_pix': [
+            139.6,
+            139.5,
+            np.nan,
+            850.7,
+            139.5,
+            np.nan,
+            850.7,
+        ], 'y_pix': [
+            132.1,
+            131.9,
+            np.nan,
+            717.5,
+            131.9,
+            np.nan,
+            717.5,
+        ], 'pupil': [
+            784.0,
+            784.0,
+            0.0,
+            714.0,
+            784.0,
+            0.0,
+            714.0,
+        ], 'task': [
+            'TRIALID_0',
+            'TRIALID_0',
+            'TRIALID_0',
+            'TRIALID_0',
+            'TRIALID_1',
+            'TRIALID_1',
+            'TRIALID_1',
+        ],
+    },
+)
+
+EXPECTED_DF_SYNC = pl.from_dict(
+    {
+        'time': [
+            2154558,
+            2154560,
+            2154561,
+            2154565,
+            2154567,
+            2154568,
+        ], 'x_pix': [
+            139.5,
+            np.nan,
+            850.7,
+            139.5,
+            np.nan,
+            850.7,
+        ], 'y_pix': [
+            131.9,
+            np.nan,
+            717.5,
+            131.9,
+            np.nan,
+            717.5,
+        ], 'pupil': [
+            784.0,
+            0.0,
+            714.0,
+            784.0,
+            0.0,
+            714.0,
+        ], 'task': [
+            'SYNCTIME_READING_SCREEN_0',
+            'SYNCTIME_READING_SCREEN_0',
+            'SYNCTIME_READING_SCREEN_0',
+            'SYNCTIME_READING_SCREEN_1',
+            'SYNCTIME_READING_SCREEN_1',
+            'SYNCTIME_READING_SCREEN_1',
+        ],
+    },
+)
+
+
+@pytest.mark.parametrize(
+    'read_kwargs',
+    [
+        pytest.param(
+            {
+                'sync_msg_start_pattern': 'SYNCTIME_READING_SCREEN_',
+                'sync_msg_stop_pattern': 'STOP',
+            },
+            id='read_kwargs_dict',
+        ),
+        pytest.param(
+            None,
+            id='read_kwargs_none',
+        ),
+    ],
+)
+def test_load_eyelink_file(tmp_path, read_kwargs):
+    filepath = tmp_path / 'sub.asc'
+    filepath.write_text(ASC_TEXT)
+
+    df = pm.dataset.dataset_files.load_gaze_file(filepath, custom_read_kwargs=read_kwargs)
+
+    if read_kwargs is not None:
+        expected_df = EXPECTED_DF_SYNC
+    else:
+        expected_df = EXPECTED_DF_NO_SYNC
+
+    assert_frame_equal(df, expected_df)
diff --git a/tests/dataset/dataset_test.py b/tests/dataset/dataset_test.py
index 65ba06e..39114cf 100644
--- a/tests/dataset/dataset_test.py
+++ b/tests/dataset/dataset_test.py
@@ -455,7 +455,7 @@ def test_load_no_files_raises_exception(dataset_configuration):
 @pytest.mark.parametrize(
     'exception',
     [
-        pytest.param(RuntimeError, id='matlab_dataset'),
+        pytest.param(ValueError, id='matlab_dataset'),
     ],
 )
 @pytest.mark.parametrize('dataset_configuration', ['ToyMat'], indirect=['dataset_configuration'])
diff --git a/tests/utils/parsing_test.py b/tests/utils/parsing_test.py
index 0a7928b..850d699 100644
--- a/tests/utils/parsing_test.py
+++ b/tests/utils/parsing_test.py
@@ -17,17 +17,15 @@
 # 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.
-# flake8: noqa: E101, W191, E501
 """ Tests pymovements asc to csv processing"""
-import os
-
+# flake8: noqa: E101, W191, E501
+# pylint: disable=duplicate-code
 import numpy as np
 import polars as pl
 import pytest
 from polars.testing import assert_frame_equal
 
-from pymovements.utils.parsing import process_asc2csv
-from pymovements.utils.parsing import scolor
+import pymovements as pm
 
 
 ASC_TEXT = r"""\
@@ -163,7 +161,7 @@ EXPECTED_DF_NO_SYNC = pl.from_dict(
             2154565,
             2154567,
             2154568,
-        ], 'x_eye': [
+        ], 'x_pix': [
             139.6,
             139.5,
             np.nan,
@@ -171,7 +169,7 @@ EXPECTED_DF_NO_SYNC = pl.from_dict(
             139.5,
             np.nan,
             850.7,
-        ], 'y_eye': [
+        ], 'y_pix': [
             132.1,
             131.9,
             np.nan,
@@ -179,7 +177,7 @@ EXPECTED_DF_NO_SYNC = pl.from_dict(
             131.9,
             np.nan,
             717.5,
-        ], 'pupil_eye': [
+        ], 'pupil': [
             784.0,
             784.0,
             0.0,
@@ -208,21 +206,21 @@ EXPECTED_DF_SYNC = pl.from_dict(
             2154565,
             2154567,
             2154568,
-        ], 'x_eye': [
+        ], 'x_pix': [
             139.5,
             np.nan,
             850.7,
             139.5,
             np.nan,
             850.7,
-        ], 'y_eye': [
+        ], 'y_pix': [
             131.9,
             np.nan,
             717.5,
             131.9,
             np.nan,
             717.5,
-        ], 'pupil_eye': [
+        ], 'pupil': [
             784.0,
             0.0,
             714.0,
@@ -249,59 +247,19 @@ EXPECTED_DF_SYNC = pl.from_dict(
     'sync_msg_stop_pattern',
     [None, 'STOP'],
 )
-@pytest.mark.parametrize(
-    'overwrite_existing',
-    [False, True],
-)
-@pytest.mark.parametrize(
-    'create_overwrite_existing',
-    [False, True],
-)
-def test_process_asc2csv(
-    tmp_path, sync_msg_start_pattern, sync_msg_stop_pattern,
-    overwrite_existing, create_overwrite_existing, capsys,
-):
+def test_parse_eyelink(tmp_path, sync_msg_start_pattern, sync_msg_stop_pattern):
     filepath = tmp_path / 'sub.asc'
     filepath.write_text(ASC_TEXT)
-    if create_overwrite_existing:
-        status_msg = 'Rewritten'
-        start_color = scolor.rewritten
-        csv_filepath = tmp_path / 'sub.csv'
-        csv_filepath.write_text('test')
-    else:
-        status_msg = 'Created'
-        start_color = scolor.created
-    df = process_asc2csv(
+
+    df = pm.utils.parsing.parse_eyelink(
         filepath,
         sync_msg_start_pattern,
         sync_msg_stop_pattern,
-        overwrite_existing,
     )
-    captured = capsys.readouterr()
-    if sync_msg_start_pattern:
+
+    if sync_msg_start_pattern is not None:
         expected_df = EXPECTED_DF_SYNC
     else:
         expected_df = EXPECTED_DF_NO_SYNC
-    if create_overwrite_existing and not overwrite_existing:
-        status_msg = 'Skipped'
-        msg_begin = f"Processing csv for {os.path.join(tmp_path, 'sub.asc')}".ljust(
-            105 - len(status_msg), '.',
-        )
-        expected_out = msg_begin + f'{scolor.skipped}{status_msg}{scolor.end}\n'
-        expected_df = pl.DataFrame(
-            schema=[
-                ('time', pl.Int64),
-                ('x_eye', pl.Float64),
-                ('y_eye', pl.Float64),
-                ('pupil_eye', pl.Float64),
-                ('task', pl.Utf8),
-            ],
-        )
-        assert_frame_equal(df, expected_df)
-        assert captured.out == expected_out
-    else:
-        msg_begin = f"Processing csv for {os.path.join(tmp_path, 'sub.asc')}".ljust(
-            105 - len(status_msg), '.',
-        )
-        expected_out = msg_begin + f'{start_color}{status_msg}{scolor.end}\n'
-        assert captured.out == expected_out
+
+    assert_frame_equal(df, expected_df)