|
import logging |
|
|
|
from gaze_mapping import Gazer3D, Gazer2D |
|
from gaze_mapping.gazer_base import ( |
|
NotEnoughPupilDataError, |
|
NotEnoughReferenceDataError, |
|
NotEnoughDataError, |
|
) |
|
from gaze_mapping.gazer_2d import Model2D_Monocular, Model2D_Binocular |
|
|
|
logger = logging.getLogger(__name__) |
|
|
|
|
|
class GazerDualMonocular3D(Gazer3D): |
|
label = "Dual-monocular 3D" |
|
|
|
def init_matcher(self): |
|
self.matcher = DummyMatcher() |
|
|
|
def fit_on_calib_data(self, calib_data): |
|
# extract reference data |
|
ref_data = calib_data["ref_list"] |
|
# extract and filter pupil data |
|
pupil_data = calib_data["pupil_list"] |
|
pupil_data = self.filter_pupil_data( |
|
pupil_data, self.g_pool.min_calibration_confidence |
|
) |
|
if not pupil_data: |
|
raise NotEnoughPupilDataError |
|
if not ref_data: |
|
raise NotEnoughReferenceDataError |
|
# match pupil to reference data (left, right, and binocular) |
|
matches = self.match_pupil_to_ref(pupil_data, ref_data) |
|
if matches.right[0]: |
|
self._fit_monocular_model(self.right_model, matches.right) |
|
else: |
|
logger.warning("Not enough matching data to fit right model") |
|
if matches.left[0]: |
|
self._fit_monocular_model(self.left_model, matches.left) |
|
else: |
|
logger.warning("Not enough matching data to fit left model") |
|
if not self.right_model.is_fitted and not self.left_model.is_fitted: |
|
raise NotEnoughDataError |
|
|
|
|
|
class GazerDualMonocular2D(Gazer2D): |
|
label = "Dual-monocular 2D" |
|
|
|
def init_matcher(self): |
|
self.matcher = DummyMatcher() |
|
|
|
def fit_on_calib_data(self, calib_data): |
|
# extract reference data |
|
ref_data = calib_data["ref_list"] |
|
# extract and filter pupil data |
|
pupil_data = calib_data["pupil_list"] |
|
pupil_data = self.filter_pupil_data( |
|
pupil_data, self.g_pool.min_calibration_confidence |
|
) |
|
if not pupil_data: |
|
raise NotEnoughPupilDataError |
|
if not ref_data: |
|
raise NotEnoughReferenceDataError |
|
# match pupil to reference data (left, right, and binocular) |
|
matches = self.match_pupil_to_ref(pupil_data, ref_data) |
|
if matches.right[0]: |
|
self._fit_monocular_model(self.right_model, matches.right) |
|
else: |
|
logger.warning("Not enough matching data to fit right model") |
|
if matches.left[0]: |
|
self._fit_monocular_model(self.left_model, matches.left) |
|
else: |
|
logger.warning("Not enough matching data to fit left model") |
|
if not self.right_model.is_fitted and not self.left_model.is_fitted: |
|
raise NotEnoughDataError |
|
|
|
# overwrite model init functions in case frame size is not available, |
|
# e.g. external HMD use case |
|
|
|
def _init_left_model(self): |
|
try: |
|
screen_size = self.g_pool.capture.frame_size |
|
except AttributeError: |
|
screen_size = (1.0, 1.0) |
|
return Model2D_Monocular(screen_size=screen_size) |
|
|
|
def _init_right_model(self): |
|
try: |
|
screen_size = self.g_pool.capture.frame_size |
|
except AttributeError: |
|
screen_size = (1.0, 1.0) |
|
return Model2D_Monocular(screen_size=screen_size) |
|
|
|
def _init_binocular_model(self): |
|
try: |
|
screen_size = self.g_pool.capture.frame_size |
|
except AttributeError: |
|
screen_size = (1.0, 1.0) |
|
return Model2D_Binocular(screen_size=screen_size) |
|
|
|
|
|
class DummyMatcher: |
|
"""Dummy matcher that simply returns the input pupil datum. |
|
|
|
Matching is only required if you want to build binocular pairs. |
|
""" |
|
|
|
def map_batch(self, pupil_list): |
|
results = [] |
|
for p in pupil_list: |
|
results.extend(self.on_pupil_datum(p)) |
|
return results |
|
|
|
def on_pupil_datum(self, p): |
|
yield [p] |