From 6eaf0a40da66818253ebc56c04244a5f2f99ea40 Mon Sep 17 00:00:00 2001 From: "David R. Reich" <43832476+SiQube@users.noreply.github.com> Date: Fri, 12 Jul 2024 12:01:29 +0200 Subject: [PATCH] map gaze data to aois (#747) * map gaze data to aois * add additional position pixel options * change boundary conditions --- src/pymovements/gaze/gaze_dataframe.py | 86 ++++ tests/unit/gaze/gaze_aoi_mapping_test.py | 488 +++++++++++++++++++++++ 2 files changed, 574 insertions(+) create mode 100644 tests/unit/gaze/gaze_aoi_mapping_test.py diff --git a/src/pymovements/gaze/gaze_dataframe.py b/src/pymovements/gaze/gaze_dataframe.py index 46e02089f..4b101d1dc 100644 --- a/src/pymovements/gaze/gaze_dataframe.py +++ b/src/pymovements/gaze/gaze_dataframe.py @@ -813,6 +813,92 @@ def columns(self) -> list[str]: """List of column names.""" return self.frame.columns + def map_to_aois( + self, + aoi_dataframe: pm.stimulus.TextStimulus, + *, + eye: str = 'auto', + gaze_type: str = 'pixel', + ) -> None: + """Map gaze data to aois. + + We map each gaze point to an aoi, considering the boundary still part of the + area of interest. + + Parameters + ---------- + aoi_dataframe: pm.stimulus.TextStimulus + Area of interest dataframe. + eye: str + String specificer for inferring eye components. Supported values are: auto, mono, left + right, cyclops. Default: auto. + gaze_type: str + String specificer for whether to use position or pixel coordinates for + mapping. Default: pixel. + """ + component_suffixes = ['x', 'y', 'xl', 'yl', 'xr', 'yr', 'xa', 'ya'] + self.unnest() + + pix_column_canditates = ['pixel_' + suffix for suffix in component_suffixes] + pixel_columns = [c for c in pix_column_canditates if c in self.frame.columns] + pos_column_canditates = ['position_' + suffix for suffix in component_suffixes] + position_columns = [ + c + for c in pos_column_canditates + if c in self.frame.columns + ] + + if gaze_type == 'pixel': + if eye == 'left': + x_eye = [col for col in pixel_columns if col.endswith('xl')][0] + y_eye = [col for col in pixel_columns if col.endswith('yl')][0] + elif eye == 'right': + x_eye = [col for col in pixel_columns if col.endswith('xr')][0] + y_eye = [col for col in pixel_columns if col.endswith('yr')][0] + elif eye == 'auto': + x_eye = [col for col in pixel_columns if col.endswith('xr')][0] + y_eye = [col for col in pixel_columns if col.endswith('yr')][0] + else: + x_eye = [col for col in pixel_columns if col.endswith('xr')][0] + y_eye = [col for col in pixel_columns if col.endswith('yr')][0] + elif gaze_type == 'position': + if eye == 'left': + x_eye = [col for col in position_columns if col.endswith('xl')][0] + y_eye = [col for col in position_columns if col.endswith('yl')][0] + elif eye == 'right': + x_eye = [col for col in position_columns if col.endswith('xr')][0] + y_eye = [col for col in position_columns if col.endswith('yr')][0] + elif eye == 'auto': + x_eye = [col for col in position_columns if col.endswith('xr')][0] + y_eye = [col for col in position_columns if col.endswith('yr')][0] + else: + x_eye = [col for col in position_columns if col.endswith('xr')][0] + y_eye = [col for col in position_columns if col.endswith('yr')][0] + else: + raise ValueError('neither position nor pixel in gaze dataframe, one needed for mapping') + + def get_aoi(row: pl.DataFrame.row) -> str: + try: + aoi = aoi_dataframe.aois.filter( + (aoi_dataframe.aois['top_left_x'] <= row[x_eye]) & + ( + row[x_eye] < + aoi_dataframe.aois['top_left_x'] + aoi_dataframe.aois['width'] + ) & + (aoi_dataframe.aois['top_left_y'] <= row[y_eye]) & + ( + row[y_eye] < + aoi_dataframe.aois['top_left_y'] + aoi_dataframe.aois['height'] + ), + )[aoi_dataframe.aoi_column].item() + return aoi + except ValueError: + return '' + + self.frame = self.frame.with_columns( + area_of_interest=pl.Series(get_aoi(row) for row in self.frame.iter_rows(named=True)), + ) + def nest( self, input_columns: list[str], diff --git a/tests/unit/gaze/gaze_aoi_mapping_test.py b/tests/unit/gaze/gaze_aoi_mapping_test.py new file mode 100644 index 000000000..897cd2de4 --- /dev/null +++ b/tests/unit/gaze/gaze_aoi_mapping_test.py @@ -0,0 +1,488 @@ +# Copyright (c) 2023-2024 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 GazeDataFrame functionality.""" +import polars as pl +import pytest +from polars.testing import assert_frame_equal + +import pymovements as pm + +EXPECTED_DF = { + 'char_left_pixel': pl.DataFrame( + [ + (1, 1, 8005274, 649.5, 531.1, 640.6, 529.1, 'e'), + (1, 1, 8005275, 649.8, 533.2, 639.7, 528.9, 'e'), + (1, 1, 8005276, 647.7, 534.0, 640.6, 529.3, 'e'), + (1, 1, 8005277, 646.2, 533.0, 642.1, 531.3, 'e'), + (1, 1, 8005278, 646.5, 533.7, 642.9, 531.0, 'e'), + (1, 1, 8005279, 647.2, 534.6, 642.6, 531.6, 'e'), + (1, 1, 8005280, 647.3, 534.0, 642.3, 530.6, 'e'), + (1, 1, 8005281, 647.7, 536.3, 642.2, 529.4, 'e'), + (1, 1, 8005282, 647.5, 537.0, 641.4, 531.3, ''), + (1, 1, 8005283, 648.3, 534.9, 640.9, 529.0, 'e'), + ], + schema=[ + 'trialId', + 'pointId', + 'time', + 'pixel_xl', + 'pixel_yl', + 'pixel_xr', + 'pixel_yr', + 'area_of_interest', + ], + ), + 'char_right_pixel': pl.DataFrame( + [ + (1, 1, 8005274, 649.5, 531.1, 640.6, 529.1, 'e'), + (1, 1, 8005275, 649.8, 533.2, 639.7, 528.9, 'e'), + (1, 1, 8005276, 647.7, 534.0, 640.6, 529.3, 'e'), + (1, 1, 8005277, 646.2, 533.0, 642.1, 531.3, 'e'), + (1, 1, 8005278, 646.5, 533.7, 642.9, 531.0, 'e'), + (1, 1, 8005279, 647.2, 534.6, 642.6, 531.6, 'e'), + (1, 1, 8005280, 647.3, 534.0, 642.3, 530.6, 'e'), + (1, 1, 8005281, 647.7, 536.3, 642.2, 529.4, 'e'), + (1, 1, 8005282, 647.5, 537.0, 641.4, 531.3, 'e'), + (1, 1, 8005283, 648.3, 534.9, 640.9, 529.0, 'e'), + ], + schema=[ + 'trialId', + 'pointId', + 'time', + 'pixel_xl', + 'pixel_yl', + 'pixel_xr', + 'pixel_yr', + 'area_of_interest', + ], + ), + + 'word_left_pixel': pl.DataFrame( + [ + (1, 1, 8005274, 649.5, 531.1, 640.6, 529.1, 'files,'), + (1, 1, 8005275, 649.8, 533.2, 639.7, 528.9, 'files,'), + (1, 1, 8005276, 647.7, 534.0, 640.6, 529.3, 'files,'), + (1, 1, 8005277, 646.2, 533.0, 642.1, 531.3, 'files,'), + (1, 1, 8005278, 646.5, 533.7, 642.9, 531.0, 'files,'), + (1, 1, 8005279, 647.2, 534.6, 642.6, 531.6, 'files,'), + (1, 1, 8005280, 647.3, 534.0, 642.3, 530.6, 'files,'), + (1, 1, 8005281, 647.7, 536.3, 642.2, 529.4, 'files,'), + (1, 1, 8005282, 647.5, 537.0, 641.4, 531.3, ''), + (1, 1, 8005283, 648.3, 534.9, 640.9, 529.0, 'files,'), + ], + schema=[ + 'trialId', + 'pointId', + 'time', + 'pixel_xl', + 'pixel_yl', + 'pixel_xr', + 'pixel_yr', + 'area_of_interest', + ], + ), + 'word_right_pixel': pl.DataFrame( + [ + (1, 1, 8005274, 649.5, 531.1, 640.6, 529.1, 'files,'), + (1, 1, 8005275, 649.8, 533.2, 639.7, 528.9, 'files,'), + (1, 1, 8005276, 647.7, 534.0, 640.6, 529.3, 'files,'), + (1, 1, 8005277, 646.2, 533.0, 642.1, 531.3, 'files,'), + (1, 1, 8005278, 646.5, 533.7, 642.9, 531.0, 'files,'), + (1, 1, 8005279, 647.2, 534.6, 642.6, 531.6, 'files,'), + (1, 1, 8005280, 647.3, 534.0, 642.3, 530.6, 'files,'), + (1, 1, 8005281, 647.7, 536.3, 642.2, 529.4, 'files,'), + (1, 1, 8005282, 647.5, 537.0, 641.4, 531.3, 'files,'), + (1, 1, 8005283, 648.3, 534.9, 640.9, 529.0, 'files,'), + ], + schema=[ + 'trialId', + 'pointId', + 'time', + 'pixel_xl', + 'pixel_yl', + 'pixel_xr', + 'pixel_yr', + 'area_of_interest', + ], + ), + 'char_left_position': pl.DataFrame( + [ + (1, 1, 8005274, 649.5, 531.1, 640.6, 529.1, 'e'), + (1, 1, 8005275, 649.8, 533.2, 639.7, 528.9, 'e'), + (1, 1, 8005276, 647.7, 534.0, 640.6, 529.3, 'e'), + (1, 1, 8005277, 646.2, 533.0, 642.1, 531.3, 'e'), + (1, 1, 8005278, 646.5, 533.7, 642.9, 531.0, 'e'), + (1, 1, 8005279, 647.2, 534.6, 642.6, 531.6, 'e'), + (1, 1, 8005280, 647.3, 534.0, 642.3, 530.6, 'e'), + (1, 1, 8005281, 647.7, 536.3, 642.2, 529.4, 'e'), + (1, 1, 8005282, 647.5, 537.0, 641.4, 531.3, ''), + (1, 1, 8005283, 648.3, 534.9, 640.9, 529.0, 'e'), + ], + schema=[ + 'trialId', + 'pointId', + 'time', + 'position_xl', + 'position_yl', + 'position_xr', + 'position_yr', + 'area_of_interest', + ], + ), + 'char_right_position': pl.DataFrame( + [ + (1, 1, 8005274, 649.5, 531.1, 640.6, 529.1, 'e'), + (1, 1, 8005275, 649.8, 533.2, 639.7, 528.9, 'e'), + (1, 1, 8005276, 647.7, 534.0, 640.6, 529.3, 'e'), + (1, 1, 8005277, 646.2, 533.0, 642.1, 531.3, 'e'), + (1, 1, 8005278, 646.5, 533.7, 642.9, 531.0, 'e'), + (1, 1, 8005279, 647.2, 534.6, 642.6, 531.6, 'e'), + (1, 1, 8005280, 647.3, 534.0, 642.3, 530.6, 'e'), + (1, 1, 8005281, 647.7, 536.3, 642.2, 529.4, 'e'), + (1, 1, 8005282, 647.5, 537.0, 641.4, 531.3, 'e'), + (1, 1, 8005283, 648.3, 534.9, 640.9, 529.0, 'e'), + ], + schema=[ + 'trialId', + 'pointId', + 'time', + 'position_xl', + 'position_yl', + 'position_xr', + 'position_yr', + 'area_of_interest', + ], + ), + + 'word_left_position': pl.DataFrame( + [ + (1, 1, 8005274, 649.5, 531.1, 640.6, 529.1, 'files,'), + (1, 1, 8005275, 649.8, 533.2, 639.7, 528.9, 'files,'), + (1, 1, 8005276, 647.7, 534.0, 640.6, 529.3, 'files,'), + (1, 1, 8005277, 646.2, 533.0, 642.1, 531.3, 'files,'), + (1, 1, 8005278, 646.5, 533.7, 642.9, 531.0, 'files,'), + (1, 1, 8005279, 647.2, 534.6, 642.6, 531.6, 'files,'), + (1, 1, 8005280, 647.3, 534.0, 642.3, 530.6, 'files,'), + (1, 1, 8005281, 647.7, 536.3, 642.2, 529.4, 'files,'), + (1, 1, 8005282, 647.5, 537.0, 641.4, 531.3, ''), + (1, 1, 8005283, 648.3, 534.9, 640.9, 529.0, 'files,'), + ], + schema=[ + 'trialId', + 'pointId', + 'time', + 'position_xl', + 'position_yl', + 'position_xr', + 'position_yr', + 'area_of_interest', + ], + ), + 'word_right_position': pl.DataFrame( + [ + (1, 1, 8005274, 649.5, 531.1, 640.6, 529.1, 'files,'), + (1, 1, 8005275, 649.8, 533.2, 639.7, 528.9, 'files,'), + (1, 1, 8005276, 647.7, 534.0, 640.6, 529.3, 'files,'), + (1, 1, 8005277, 646.2, 533.0, 642.1, 531.3, 'files,'), + (1, 1, 8005278, 646.5, 533.7, 642.9, 531.0, 'files,'), + (1, 1, 8005279, 647.2, 534.6, 642.6, 531.6, 'files,'), + (1, 1, 8005280, 647.3, 534.0, 642.3, 530.6, 'files,'), + (1, 1, 8005281, 647.7, 536.3, 642.2, 529.4, 'files,'), + (1, 1, 8005282, 647.5, 537.0, 641.4, 531.3, 'files,'), + (1, 1, 8005283, 648.3, 534.9, 640.9, 529.0, 'files,'), + ], + schema=[ + 'trialId', + 'pointId', + 'time', + 'position_xl', + 'position_yl', + 'position_xr', + 'position_yr', + 'area_of_interest', + ], + ), + 'char_auto_pixel': pl.DataFrame( + [ + (1, 1, 8005274, 649.5, 531.1, 640.6, 529.1, 'e'), + (1, 1, 8005275, 649.8, 533.2, 639.7, 528.9, 'e'), + (1, 1, 8005276, 647.7, 534.0, 640.6, 529.3, 'e'), + (1, 1, 8005277, 646.2, 533.0, 642.1, 531.3, 'e'), + (1, 1, 8005278, 646.5, 533.7, 642.9, 531.0, 'e'), + (1, 1, 8005279, 647.2, 534.6, 642.6, 531.6, 'e'), + (1, 1, 8005280, 647.3, 534.0, 642.3, 530.6, 'e'), + (1, 1, 8005281, 647.7, 536.3, 642.2, 529.4, 'e'), + (1, 1, 8005282, 647.5, 537.0, 641.4, 531.3, 'e'), + (1, 1, 8005283, 648.3, 534.9, 640.9, 529.0, 'e'), + ], + schema=[ + 'trialId', + 'pointId', + 'time', + 'pixel_xl', + 'pixel_yl', + 'pixel_xr', + 'pixel_yr', + 'area_of_interest', + ], + ), + 'char_auto_position': pl.DataFrame( + [ + (1, 1, 8005274, 649.5, 531.1, 640.6, 529.1, 'e'), + (1, 1, 8005275, 649.8, 533.2, 639.7, 528.9, 'e'), + (1, 1, 8005276, 647.7, 534.0, 640.6, 529.3, 'e'), + (1, 1, 8005277, 646.2, 533.0, 642.1, 531.3, 'e'), + (1, 1, 8005278, 646.5, 533.7, 642.9, 531.0, 'e'), + (1, 1, 8005279, 647.2, 534.6, 642.6, 531.6, 'e'), + (1, 1, 8005280, 647.3, 534.0, 642.3, 530.6, 'e'), + (1, 1, 8005281, 647.7, 536.3, 642.2, 529.4, 'e'), + (1, 1, 8005282, 647.5, 537.0, 641.4, 531.3, 'e'), + (1, 1, 8005283, 648.3, 534.9, 640.9, 529.0, 'e'), + ], + schema=[ + 'trialId', + 'pointId', + 'time', + 'position_xl', + 'position_yl', + 'position_xr', + 'position_yr', + 'area_of_interest', + ], + ), + + 'word_auto_pixel': pl.DataFrame( + [ + (1, 1, 8005274, 649.5, 531.1, 640.6, 529.1, 'files,'), + (1, 1, 8005275, 649.8, 533.2, 639.7, 528.9, 'files,'), + (1, 1, 8005276, 647.7, 534.0, 640.6, 529.3, 'files,'), + (1, 1, 8005277, 646.2, 533.0, 642.1, 531.3, 'files,'), + (1, 1, 8005278, 646.5, 533.7, 642.9, 531.0, 'files,'), + (1, 1, 8005279, 647.2, 534.6, 642.6, 531.6, 'files,'), + (1, 1, 8005280, 647.3, 534.0, 642.3, 530.6, 'files,'), + (1, 1, 8005281, 647.7, 536.3, 642.2, 529.4, 'files,'), + (1, 1, 8005282, 647.5, 537.0, 641.4, 531.3, 'files,'), + (1, 1, 8005283, 648.3, 534.9, 640.9, 529.0, 'files,'), + ], + schema=[ + 'trialId', + 'pointId', + 'time', + 'pixel_xl', + 'pixel_yl', + 'pixel_xr', + 'pixel_yr', + 'area_of_interest', + ], + ), + 'word_auto_position': pl.DataFrame( + [ + (1, 1, 8005274, 649.5, 531.1, 640.6, 529.1, 'files,'), + (1, 1, 8005275, 649.8, 533.2, 639.7, 528.9, 'files,'), + (1, 1, 8005276, 647.7, 534.0, 640.6, 529.3, 'files,'), + (1, 1, 8005277, 646.2, 533.0, 642.1, 531.3, 'files,'), + (1, 1, 8005278, 646.5, 533.7, 642.9, 531.0, 'files,'), + (1, 1, 8005279, 647.2, 534.6, 642.6, 531.6, 'files,'), + (1, 1, 8005280, 647.3, 534.0, 642.3, 530.6, 'files,'), + (1, 1, 8005281, 647.7, 536.3, 642.2, 529.4, 'files,'), + (1, 1, 8005282, 647.5, 537.0, 641.4, 531.3, 'files,'), + (1, 1, 8005283, 648.3, 534.9, 640.9, 529.0, 'files,'), + ], + schema=[ + 'trialId', + 'pointId', + 'time', + 'position_xl', + 'position_yl', + 'position_xr', + 'position_yr', + 'area_of_interest', + ], + ), + 'char_else_pixel': pl.DataFrame( + [ + (1, 1, 8005274, 649.5, 531.1, 640.6, 529.1, 'e'), + (1, 1, 8005275, 649.8, 533.2, 639.7, 528.9, 'e'), + (1, 1, 8005276, 647.7, 534.0, 640.6, 529.3, 'e'), + (1, 1, 8005277, 646.2, 533.0, 642.1, 531.3, 'e'), + (1, 1, 8005278, 646.5, 533.7, 642.9, 531.0, 'e'), + (1, 1, 8005279, 647.2, 534.6, 642.6, 531.6, 'e'), + (1, 1, 8005280, 647.3, 534.0, 642.3, 530.6, 'e'), + (1, 1, 8005281, 647.7, 536.3, 642.2, 529.4, 'e'), + (1, 1, 8005282, 647.5, 537.0, 641.4, 531.3, 'e'), + (1, 1, 8005283, 648.3, 534.9, 640.9, 529.0, 'e'), + ], + schema=[ + 'trialId', + 'pointId', + 'time', + 'pixel_xl', + 'pixel_yl', + 'pixel_xr', + 'pixel_yr', + 'area_of_interest', + ], + ), + 'char_else_position': pl.DataFrame( + [ + (1, 1, 8005274, 649.5, 531.1, 640.6, 529.1, 'e'), + (1, 1, 8005275, 649.8, 533.2, 639.7, 528.9, 'e'), + (1, 1, 8005276, 647.7, 534.0, 640.6, 529.3, 'e'), + (1, 1, 8005277, 646.2, 533.0, 642.1, 531.3, 'e'), + (1, 1, 8005278, 646.5, 533.7, 642.9, 531.0, 'e'), + (1, 1, 8005279, 647.2, 534.6, 642.6, 531.6, 'e'), + (1, 1, 8005280, 647.3, 534.0, 642.3, 530.6, 'e'), + (1, 1, 8005281, 647.7, 536.3, 642.2, 529.4, 'e'), + (1, 1, 8005282, 647.5, 537.0, 641.4, 531.3, 'e'), + (1, 1, 8005283, 648.3, 534.9, 640.9, 529.0, 'e'), + ], + schema=[ + 'trialId', + 'pointId', + 'time', + 'position_xl', + 'position_yl', + 'position_xr', + 'position_yr', + 'area_of_interest', + ], + ), + + 'word_else_pixel': pl.DataFrame( + [ + (1, 1, 8005274, 649.5, 531.1, 640.6, 529.1, 'files,'), + (1, 1, 8005275, 649.8, 533.2, 639.7, 528.9, 'files,'), + (1, 1, 8005276, 647.7, 534.0, 640.6, 529.3, 'files,'), + (1, 1, 8005277, 646.2, 533.0, 642.1, 531.3, 'files,'), + (1, 1, 8005278, 646.5, 533.7, 642.9, 531.0, 'files,'), + (1, 1, 8005279, 647.2, 534.6, 642.6, 531.6, 'files,'), + (1, 1, 8005280, 647.3, 534.0, 642.3, 530.6, 'files,'), + (1, 1, 8005281, 647.7, 536.3, 642.2, 529.4, 'files,'), + (1, 1, 8005282, 647.5, 537.0, 641.4, 531.3, 'files,'), + (1, 1, 8005283, 648.3, 534.9, 640.9, 529.0, 'files,'), + ], + schema=[ + 'trialId', + 'pointId', + 'time', + 'pixel_xl', + 'pixel_yl', + 'pixel_xr', + 'pixel_yr', + 'area_of_interest', + ], + ), + 'word_else_position': pl.DataFrame( + [ + (1, 1, 8005274, 649.5, 531.1, 640.6, 529.1, 'files,'), + (1, 1, 8005275, 649.8, 533.2, 639.7, 528.9, 'files,'), + (1, 1, 8005276, 647.7, 534.0, 640.6, 529.3, 'files,'), + (1, 1, 8005277, 646.2, 533.0, 642.1, 531.3, 'files,'), + (1, 1, 8005278, 646.5, 533.7, 642.9, 531.0, 'files,'), + (1, 1, 8005279, 647.2, 534.6, 642.6, 531.6, 'files,'), + (1, 1, 8005280, 647.3, 534.0, 642.3, 530.6, 'files,'), + (1, 1, 8005281, 647.7, 536.3, 642.2, 529.4, 'files,'), + (1, 1, 8005282, 647.5, 537.0, 641.4, 531.3, 'files,'), + (1, 1, 8005283, 648.3, 534.9, 640.9, 529.0, 'files,'), + ], + schema=[ + 'trialId', + 'pointId', + 'time', + 'position_xl', + 'position_yl', + 'position_xr', + 'position_yr', + 'area_of_interest', + ], + ), +} + + +@pytest.mark.parametrize( + ('eye'), + [ + 'right', + 'left', + 'auto', + 'else', + ], +) +@pytest.mark.parametrize( + ('aoi_column'), + [ + 'word', + 'char', + ], +) +@pytest.mark.parametrize( + ('gaze_type'), + [ + 'pixel', + 'position', + ], +) +def test_gaze_to_aoi_mapping_char(eye, aoi_column, gaze_type): + aoi_df = pm.stimulus.text.from_file( + 'tests/files/toy_text_1_1_aoi.csv', + aoi_column=aoi_column, + pixel_x_column='top_left_x', + pixel_y_column='top_left_y', + width_column='width', + height_column='height', + page_column='page', + ) + if gaze_type == 'pixel': + gaze_df = pm.gaze.io.from_csv( + 'tests/files/judo1000_example.csv', + **{'separator': '\t'}, + pixel_columns=['x_left', 'y_left', 'x_right', 'y_right'], + ) + elif gaze_type == 'position': + gaze_df = pm.gaze.io.from_csv( + 'tests/files/judo1000_example.csv', + **{'separator': '\t'}, + position_columns=['x_left', 'y_left', 'x_right', 'y_right'], + ) + + gaze_df.map_to_aois(aoi_df, eye=eye, gaze_type=gaze_type) + assert_frame_equal(gaze_df.frame, EXPECTED_DF[f'{aoi_column}_{eye}_{gaze_type}']) + + +def test_map_to_aois_raises_value_error(): + aoi_df = pm.stimulus.text.from_file( + 'tests/files/toy_text_1_1_aoi.csv', + aoi_column='char', + pixel_x_column='top_left_x', + pixel_y_column='top_left_y', + width_column='width', + height_column='height', + page_column='page', + ) + gaze_df = pm.gaze.io.from_csv( + 'tests/files/judo1000_example.csv', + **{'separator': '\t'}, + position_columns=['x_left', 'y_left', 'x_right', 'y_right'], + ) + + with pytest.raises(ValueError) as excinfo: + gaze_df.map_to_aois(aoi_df, eye='right', gaze_type='') + msg, = excinfo.value.args + assert msg == 'neither position nor pixel in gaze dataframe, one needed for mapping'