From 2b98646f2e2959c5fe9e2684aa474c307b015d47 Mon Sep 17 00:00:00 2001 From: Patrick Faion Date: Thu, 13 Feb 2020 16:17:14 +0100 Subject: [PATCH 01/75] Add minimal Pye3D plugin --- .../pupil_detector_plugins/__init__.py | 7 +-- .../detector_2d_plugin.py | 2 +- .../detector_3d_plugin.py | 2 +- .../detector_base_plugin.py | 10 ++-- .../pupil_detector_plugins/pye3d_plugin.py | 52 +++++++++++++++++++ 5 files changed, 64 insertions(+), 9 deletions(-) create mode 100644 pupil_src/shared_modules/pupil_detector_plugins/pye3d_plugin.py diff --git a/pupil_src/shared_modules/pupil_detector_plugins/__init__.py b/pupil_src/shared_modules/pupil_detector_plugins/__init__.py index 149cc3cdc1..c67bd7343b 100644 --- a/pupil_src/shared_modules/pupil_detector_plugins/__init__.py +++ b/pupil_src/shared_modules/pupil_detector_plugins/__init__.py @@ -13,7 +13,8 @@ from .detector_2d_plugin import Detector2DPlugin from .detector_3d_plugin import Detector3DPlugin -from .detector_base_plugin import PupilDetectorPlugin, EVENT_KEY +from .detector_base_plugin import PupilDetectorPlugin +from .pye3d_plugin import Pye3DPlugin logger = logging.getLogger(__name__) @@ -26,9 +27,9 @@ def available_detector_plugins() -> T.Tuple[ Returns tuple of default2D, default3D, and list of all detectors. """ - all_plugins = [Detector2DPlugin, Detector3DPlugin] + all_plugins = [Detector2DPlugin, Detector3DPlugin, Pye3DPlugin] default2D = Detector2DPlugin - default3D = Detector3DPlugin + default3D = Pye3DPlugin try: from py3d import Detector3DRefractionPlugin diff --git a/pupil_src/shared_modules/pupil_detector_plugins/detector_2d_plugin.py b/pupil_src/shared_modules/pupil_detector_plugins/detector_2d_plugin.py index f58b900048..271ad67e8e 100644 --- a/pupil_src/shared_modules/pupil_detector_plugins/detector_2d_plugin.py +++ b/pupil_src/shared_modules/pupil_detector_plugins/detector_2d_plugin.py @@ -46,7 +46,7 @@ def __init__( self.detector_2d = detector_2d or Detector2D(namespaced_properties or {}) self.proxy = PropertyProxy(self.detector_2d) - def detect(self, frame): + def detect(self, frame, pupil_data): # convert roi-plugin to detector roi roi = Roi(*self.g_pool.roi.bounds) diff --git a/pupil_src/shared_modules/pupil_detector_plugins/detector_3d_plugin.py b/pupil_src/shared_modules/pupil_detector_plugins/detector_3d_plugin.py index 008b54596d..d7e411e7a3 100644 --- a/pupil_src/shared_modules/pupil_detector_plugins/detector_3d_plugin.py +++ b/pupil_src/shared_modules/pupil_detector_plugins/detector_3d_plugin.py @@ -48,7 +48,7 @@ def __init__( # debug window self.debugVisualizer3D = Eye_Visualizer(g_pool, self.detector_3d.focal_length()) - def detect(self, frame): + def detect(self, frame, pupil_data): # convert roi-plugin to detector roi roi = Roi(*self.g_pool.roi.bounds) diff --git a/pupil_src/shared_modules/pupil_detector_plugins/detector_base_plugin.py b/pupil_src/shared_modules/pupil_detector_plugins/detector_base_plugin.py index 4133d33410..28cfc5a57a 100644 --- a/pupil_src/shared_modules/pupil_detector_plugins/detector_base_plugin.py +++ b/pupil_src/shared_modules/pupil_detector_plugins/detector_base_plugin.py @@ -92,16 +92,18 @@ def recent_events(self, event): self.on_resolution_change(self._last_frame_size, frame_size) self._last_frame_size = frame_size - detection_result = self.detect(frame=frame) + detection_result = self.detect(frame=frame, pupil_data=event.get(EVENT_KEY, [])) + self._recent_detection_result = detection_result + if detection_result is None: + return + if EVENT_KEY in event: event[EVENT_KEY].append(detection_result) else: event[EVENT_KEY] = [detection_result] - self._recent_detection_result = detection_result - @abc.abstractmethod - def detect(self, frame): + def detect(self, frame, pupil_data): pass def on_notify(self, notification): diff --git a/pupil_src/shared_modules/pupil_detector_plugins/pye3d_plugin.py b/pupil_src/shared_modules/pupil_detector_plugins/pye3d_plugin.py new file mode 100644 index 0000000000..9405eb57a5 --- /dev/null +++ b/pupil_src/shared_modules/pupil_detector_plugins/pye3d_plugin.py @@ -0,0 +1,52 @@ +""" +(*)~--------------------------------------------------------------------------- +Pupil - eye tracking platform +Copyright (C) 2012-2020 Pupil Labs + +Distributed under the terms of the GNU +Lesser General Public License (LGPL v3.0). +See COPYING and COPYING.LESSER for license details. +---------------------------------------------------------------------------~(*) +""" +import logging + +from pye3d.eyemodel import EyeModel + +from .detector_base_plugin import PupilDetectorPlugin + +logger = logging.getLogger(__name__) + + +class Pye3DPlugin(PupilDetectorPlugin): + uniqueness = "by_class" + icon_font = "pupil_icons" + icon_chr = chr(0xEC19) + + label = "Pye3D" + + def __init__(self, g_pool): + super().__init__(g_pool) + self.detector = EyeModel() + + def detect(self, frame, pupil_data): + for datum in pupil_data: + if datum.get("method", "") == "2d c++": + datum_2d = datum + break + else: + return None + + datum_2d["raw_edges"] = [] + result = self.detector.update_and_detect(datum_2d) + + eye_id = self.g_pool.eye_id + result["timestamp"] = frame.timestamp + result["topic"] = f"pupil.{eye_id}" + result["id"] = eye_id + result["method"] = "3d c++" + + return result + + @classmethod + def parse_pretty_class_name(cls) -> str: + return "Pye3D Detector" From 4bd3db39c597b7a3690ec3c8d6d6e7cc836e9f79 Mon Sep 17 00:00:00 2001 From: Patrick Faion Date: Thu, 13 Feb 2020 17:02:58 +0100 Subject: [PATCH 02/75] Use EyeModel_V2 and fix EVENT_KEY bug --- pupil_src/shared_modules/pupil_detector_plugins/__init__.py | 2 +- .../shared_modules/pupil_detector_plugins/pye3d_plugin.py | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/pupil_src/shared_modules/pupil_detector_plugins/__init__.py b/pupil_src/shared_modules/pupil_detector_plugins/__init__.py index c67bd7343b..637ab857b9 100644 --- a/pupil_src/shared_modules/pupil_detector_plugins/__init__.py +++ b/pupil_src/shared_modules/pupil_detector_plugins/__init__.py @@ -13,7 +13,7 @@ from .detector_2d_plugin import Detector2DPlugin from .detector_3d_plugin import Detector3DPlugin -from .detector_base_plugin import PupilDetectorPlugin +from .detector_base_plugin import PupilDetectorPlugin, EVENT_KEY from .pye3d_plugin import Pye3DPlugin logger = logging.getLogger(__name__) diff --git a/pupil_src/shared_modules/pupil_detector_plugins/pye3d_plugin.py b/pupil_src/shared_modules/pupil_detector_plugins/pye3d_plugin.py index 9405eb57a5..6842819047 100644 --- a/pupil_src/shared_modules/pupil_detector_plugins/pye3d_plugin.py +++ b/pupil_src/shared_modules/pupil_detector_plugins/pye3d_plugin.py @@ -10,7 +10,8 @@ """ import logging -from pye3d.eyemodel import EyeModel +from pye3d.eyemodel import EyeModel_V2 as EyeModel + from .detector_base_plugin import PupilDetectorPlugin From 772ec4b3a0d86b0fbb4555dafcb7bc6c13d240ec Mon Sep 17 00:00:00 2001 From: Patrick Faion Date: Thu, 13 Feb 2020 17:21:24 +0100 Subject: [PATCH 03/75] Add old debug window logic --- .../pupil_detector_plugins/pye3d_plugin.py | 66 ++++++++++++++++++- 1 file changed, 64 insertions(+), 2 deletions(-) diff --git a/pupil_src/shared_modules/pupil_detector_plugins/pye3d_plugin.py b/pupil_src/shared_modules/pupil_detector_plugins/pye3d_plugin.py index 6842819047..6f3bec2c1e 100644 --- a/pupil_src/shared_modules/pupil_detector_plugins/pye3d_plugin.py +++ b/pupil_src/shared_modules/pupil_detector_plugins/pye3d_plugin.py @@ -11,9 +11,11 @@ import logging from pye3d.eyemodel import EyeModel_V2 as EyeModel - +from pyglui import ui from .detector_base_plugin import PupilDetectorPlugin +from .visualizer_2d import draw_eyeball_outline, draw_pupil_outline +from .visualizer_3d import Eye_Visualizer logger = logging.getLogger(__name__) @@ -28,6 +30,9 @@ class Pye3DPlugin(PupilDetectorPlugin): def __init__(self, g_pool): super().__init__(g_pool) self.detector = EyeModel() + self.debugVisualizer3D = Eye_Visualizer( + g_pool, self.detector.settings["focal_length"] + ) def detect(self, frame, pupil_data): for datum in pupil_data: @@ -37,8 +42,12 @@ def detect(self, frame, pupil_data): else: return None + return None + datum_2d["raw_edges"] = [] - result = self.detector.update_and_detect(datum_2d) + result = self.detector.update_and_detect( + datum_2d, debug_toggle=self.is_debug_window_open + ) eye_id = self.g_pool.eye_id result["timestamp"] = frame.timestamp @@ -51,3 +60,56 @@ def detect(self, frame, pupil_data): @classmethod def parse_pretty_class_name(cls) -> str: return "Pye3D Detector" + + def init_ui(self): + super().init_ui() + self.menu.label = self.pretty_class_name + + self.menu.append(ui.Button("Reset 3D model", self.reset_model)) + self.menu.append(ui.Button("Open debug window", self.debug_window_toggle)) + + # self.menu.append( + # ui.Switch(TODO, label="Freeze model") + # ) + + def gl_display(self): + self.debug_window_update() + if self._recent_detection_result: + draw_eyeball_outline(self._recent_detection_result) + draw_pupil_outline(self._recent_detection_result) + + def cleanup(self): + self.debug_window_close() # if we change detectors, be sure debug window is also closed + + # Public + + def reset_model(self): + self.detector.reset_model() + + # Debug window management + + @property + def is_debug_window_open(self) -> bool: + return self.debugVisualizer3D.window is not None + + def debug_window_toggle(self): + if not self.is_debug_window_open: + self.debug_window_open() + else: + self.debug_window_close() + + def debug_window_open(self): + if not self.is_debug_window_open: + self.debugVisualizer3D.open_window() + + def debug_window_close(self): + if self.is_debug_window_open: + self.debugVisualizer3D.close_window() + + def debug_window_update(self): + if self.is_debug_window_open: + pass + # TODO + # self.debugVisualizer3D.update_window( + # self.g_pool, self.detector_3d.debug_result + # ) From 572fd09022f7b3f65d33cd82139dda06fe86ca41 Mon Sep 17 00:00:00 2001 From: euryalus Date: Fri, 14 Feb 2020 14:14:26 +0100 Subject: [PATCH 04/75] Added visualizer for pye3d --- .../pupil_detector_plugins/pye3d_plugin.py | 16 +- .../visualizer_pye3d.py | 268 ++++++++++++++++++ 2 files changed, 274 insertions(+), 10 deletions(-) create mode 100644 pupil_src/shared_modules/pupil_detector_plugins/visualizer_pye3d.py diff --git a/pupil_src/shared_modules/pupil_detector_plugins/pye3d_plugin.py b/pupil_src/shared_modules/pupil_detector_plugins/pye3d_plugin.py index 6f3bec2c1e..117484eeb1 100644 --- a/pupil_src/shared_modules/pupil_detector_plugins/pye3d_plugin.py +++ b/pupil_src/shared_modules/pupil_detector_plugins/pye3d_plugin.py @@ -10,12 +10,12 @@ """ import logging -from pye3d.eyemodel import EyeModel_V2 as EyeModel +from pye3d.eyemodel import EyeModel from pyglui import ui from .detector_base_plugin import PupilDetectorPlugin from .visualizer_2d import draw_eyeball_outline, draw_pupil_outline -from .visualizer_3d import Eye_Visualizer +from .visualizer_pye3d import Eye_Visualizer logger = logging.getLogger(__name__) @@ -42,8 +42,6 @@ def detect(self, frame, pupil_data): else: return None - return None - datum_2d["raw_edges"] = [] result = self.detector.update_and_detect( datum_2d, debug_toggle=self.is_debug_window_open @@ -84,7 +82,7 @@ def cleanup(self): # Public def reset_model(self): - self.detector.reset_model() + self.detector.reset() # Debug window management @@ -108,8 +106,6 @@ def debug_window_close(self): def debug_window_update(self): if self.is_debug_window_open: - pass - # TODO - # self.debugVisualizer3D.update_window( - # self.g_pool, self.detector_3d.debug_result - # ) + self.debugVisualizer3D.update_window( + self.g_pool, self.detector.debug_result + ) diff --git a/pupil_src/shared_modules/pupil_detector_plugins/visualizer_pye3d.py b/pupil_src/shared_modules/pupil_detector_plugins/visualizer_pye3d.py new file mode 100644 index 0000000000..0181a4bcfd --- /dev/null +++ b/pupil_src/shared_modules/pupil_detector_plugins/visualizer_pye3d.py @@ -0,0 +1,268 @@ +""" +(*)~--------------------------------------------------------------------------- +Pupil - eye tracking platform +Copyright (C) 2012-2019 Pupil Labs + +Distributed under the terms of the GNU +Lesser General Public License (LGPL v3.0). +See COPYING and COPYING.LESSER for license details. +---------------------------------------------------------------------------~(*) +""" +import math + +import numpy as np +from OpenGL.GL import * +from gl_utils.trackball import Trackball +from pyglui.cygl import utils as glutils +from pyglui.cygl.utils import RGBA +from visualizer import Visualizer + +from pye3d.geometry.eye import LeGrandEye + +from collections import deque + + +class Eye_Visualizer(Visualizer): + def __init__(self, g_pool, focal_length): + super().__init__(g_pool, "Debug Visualizer", False) + + self.focal_length = focal_length + self.image_width = 192 # right values are assigned in update + self.image_height = 192 + + camera_fov = math.degrees( + 2.0 * math.atan(self.image_height / (2.0 * self.focal_length)) + ) + self.trackball = Trackball(camera_fov) + + # self.residuals_single_means = deque(np.zeros(50), 50) + # self.residuals_single_stds = deque(np.zeros(50), 50) + # self.residuals_means = deque(np.zeros(50), 50) + # self.residuals_stds = deque(np.zeros(50), 50) + + self.eye = LeGrandEye() + self.cost_history = deque([], maxlen=200) + self.optimization_number = 0 + + ############## MATRIX FUNCTIONS ############ + + def get_anthropomorphic_matrix(self): + temp = np.identity(4) + temp[2, 2] *= -1 + return temp + + def get_adjusted_pixel_space_matrix(self, scale): + # returns a homoegenous matrix + temp = self.get_anthropomorphic_matrix() + temp[3, 3] *= scale + return temp + + def get_image_space_matrix(self, scale=1.): + temp = self.get_adjusted_pixel_space_matrix(scale) + temp[1, 1] *= -1 # image origin is top left + temp[0, 3] = -self.image_width / 2.0 + temp[1, 3] = self.image_height / 2.0 + temp[2, 3] = -self.focal_length + return temp.T + + ############## DRAWING FUNCTIONS ########### + + def draw_eye(self, result): + + self.eye.pupil_radius = result["circle_3d_refraction"]["radius"] + self.eye.move_to_point( + [ + result["sphere_refraction_corrected"]["center"][0], + -result["sphere_refraction_corrected"]["center"][1], + -result["sphere_refraction_corrected"]["center"][2], + ] + ) + if result["confidence"] > 0.0 and not np.isnan( + result["circle_3d_refraction"]["normal"][0] + ): + self.eye.update_from_gaze_vector( + [ + result["circle_3d_refraction"]["normal"][0], + -result["circle_3d_refraction"]["normal"][1], + result["circle_3d_refraction"]["normal"][2], + ] + ) + self.eye.draw_gl(alpha=0.7) + + def draw_debug_info(self, result): + + sphere_center = result["sphere"]["center"] + sphere_center_refraction_corrected = result["sphere_refraction_corrected"]["center"] + gaze_vector = result["circle_3d"]["normal"] + gaze_vector_refraction_corrected = result["circle_3d_refraction"]["normal"] + pupil_radius = result["circle_3d"]["radius"] + + status = ( + "Eyeball center : X: %.2fmm Y: %.2fmm Z: %.2fmm \n" + "Eyeball center - Refraction corrected: X: %.2fmm Y: %.2fmm Z: %.2fmm \n" + "Gaze vector: X: %.2f Y: %.2f Z: %.2f\n" + "Gaze vector - Refraction corrected: X: %.2f Y: %.2f Z: %.2f\n" + "Pupil Diameter: %.2fmm\n" + "Supporting pupils: %i\n" + % ( + sphere_center[0], + sphere_center[1], + sphere_center[2], + sphere_center_refraction_corrected[0], + sphere_center_refraction_corrected[1], + sphere_center_refraction_corrected[2], + gaze_vector[0], + gaze_vector[1], + gaze_vector[2], + gaze_vector_refraction_corrected[0], + gaze_vector_refraction_corrected[1], + gaze_vector_refraction_corrected[2], + pupil_radius * 2, + len(result["debug_info"]["unprojected_circles"]), + ) + ) + + self.glfont.push_state() + self.glfont.set_color_float((0, 0, 0, 1)) + self.glfont.draw_multi_line_text(7, 20, status) + self.glfont.pop_state() + + def draw_residuals(self, result): + + glMatrixMode(GL_PROJECTION) + glPushMatrix() + glLoadIdentity() + glOrtho(0, 1, 0, 1, -1, 1) # gl coord convention + + glMatrixMode(GL_MODELVIEW) + glPushMatrix() + glLoadIdentity() + + glTranslatef(0.01, 0.01, 0) + + glScale(1.5, 1.5, 1) + + glColor4f(1, 1, 1, 0.3) + glBegin(GL_QUADS) + glVertex3f(0, 0, 0) + glVertex3f(0, 0.15, 0) + glVertex3f(0.15, 0.15, 0) + glVertex3f(0.15, 0, 0) + glEnd() + + glTranslatef(0.01, 0.01, 0) + + glScale(0.13, 0.13, 1) + + vertices = [[0, 0], [0, 1]] + glutils.draw_polyline(vertices, thickness=2, color=RGBA(1.0, 1.0, 1.0, 0.9)) + vertices = [[0, 0], [1, 0]] + glutils.draw_polyline(vertices, thickness=2, color=RGBA(1.0, 1.0, 1.0, 0.9)) + + glScale(1, 0.33, 1) + + vertices = [[0, 1], [1, 1]] + glutils.draw_polyline(vertices, thickness=1, color=RGBA(1.0, 1.0, 1.0, 0.9)) + vertices = [[0, 2], [1, 2]] + glutils.draw_polyline(vertices, thickness=1, color=RGBA(1.0, 1.0, 1.0, 0.9)) + vertices = [[0, 3], [1, 3]] + glutils.draw_polyline(vertices, thickness=1, color=RGBA(1.0, 1.0, 1.0, 0.9)) + + try: + + vertices = list( + zip( + np.clip(np.asarray(result["debug_info"]["angles"]) / 60.0, 0, 1), + np.clip( + np.log10(np.array(result["debug_info"]["residuals"])) + 3, + 0.1, + 2.9, + ), + ) + ) + + alpha = 0.8 / (len(result["debug_info"]["angles"]) ** 0.2) + size = 4 + 10 / (len(result["debug_info"]["angles"]) ** 0.1) + + glutils.draw_points( + vertices, size=size, color=RGBA(255 / 255, 165 / 255, 0, alpha) + ) + + except: + + pass + + glPopMatrix() + glMatrixMode(GL_PROJECTION) + glPopMatrix() + + def draw_dierkes_lines(self, result): + + glPushMatrix() + glMatrixMode(GL_MODELVIEW) + glColor4f(1.0, 0., 0., 0.1) + glLineWidth(1.0) + for line in result["debug_info"]["unprojected_circles"]: + glBegin(GL_LINES) + glVertex3f(line[0], line[1], line[2]) + glVertex3f(line[3], line[4], line[5]) + glEnd() + glPopMatrix() + + def update_window(self, g_pool, result): + + if not result: + return + + if not self.window: + return + + self.begin_update_window() + self.image_width, self.image_height = g_pool.capture.frame_size + self.clear_gl_screen() + self.trackball.push() + + glLoadMatrixf(self.get_image_space_matrix(15)) + + g_pool.image_tex.draw( + quad=( + (0, self.image_height), + (self.image_width, self.image_height), + (self.image_width, 0), + (0, 0), + ), + alpha=1.0, + ) + + glLoadMatrixf(self.get_adjusted_pixel_space_matrix(15)) + self.draw_frustum(self.image_width, self.image_height, self.focal_length) + glLoadMatrixf(self.get_anthropomorphic_matrix()) + + self.eye.pose = self.get_anthropomorphic_matrix() + + self.draw_coordinate_system(4) + self.draw_eye(result) + self.draw_dierkes_lines(result) + self.trackball.pop() + + self.draw_debug_info(result) + self.draw_residuals(result) + + self.end_update_window() + + return True + + ############ WINDOW CALLBACKS ############### + + def on_resize(self, window, w, h): + Visualizer.on_resize(self, window, w, h) + self.trackball.set_window_size(w, h) + + def on_window_char(self, window, char): + if char == ord("r"): + self.trackball.distance = [0, 0, -0.1] + self.trackball.pitch = 0 + self.trackball.roll = 0 + + def on_scroll(self, window, x, y): + self.trackball.zoom_to(y) From 6aba4acf48b952e101c9e81a1139dba34f94daa9 Mon Sep 17 00:00:00 2001 From: euryalus Date: Mon, 17 Feb 2020 16:11:01 +0100 Subject: [PATCH 05/75] Added visualizer for new detector3d. --- .../pupil_detector_plugins/pye3d_plugin.py | 4 ++-- .../pupil_detector_plugins/visualizer_pye3d.py | 18 +++++++++--------- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/pupil_src/shared_modules/pupil_detector_plugins/pye3d_plugin.py b/pupil_src/shared_modules/pupil_detector_plugins/pye3d_plugin.py index 117484eeb1..a3cc4661ad 100644 --- a/pupil_src/shared_modules/pupil_detector_plugins/pye3d_plugin.py +++ b/pupil_src/shared_modules/pupil_detector_plugins/pye3d_plugin.py @@ -10,7 +10,7 @@ """ import logging -from pye3d.eyemodel import EyeModel +from pye3d.detector_3d import Detector3D from pyglui import ui from .detector_base_plugin import PupilDetectorPlugin @@ -29,7 +29,7 @@ class Pye3DPlugin(PupilDetectorPlugin): def __init__(self, g_pool): super().__init__(g_pool) - self.detector = EyeModel() + self.detector = Detector3D() self.debugVisualizer3D = Eye_Visualizer( g_pool, self.detector.settings["focal_length"] ) diff --git a/pupil_src/shared_modules/pupil_detector_plugins/visualizer_pye3d.py b/pupil_src/shared_modules/pupil_detector_plugins/visualizer_pye3d.py index 0181a4bcfd..3bfe0254c9 100644 --- a/pupil_src/shared_modules/pupil_detector_plugins/visualizer_pye3d.py +++ b/pupil_src/shared_modules/pupil_detector_plugins/visualizer_pye3d.py @@ -118,7 +118,7 @@ def draw_debug_info(self, result): gaze_vector_refraction_corrected[1], gaze_vector_refraction_corrected[2], pupil_radius * 2, - len(result["debug_info"]["unprojected_circles"]), + len(result["debug_info"]["Dierkes_lines"]), ) ) @@ -172,17 +172,17 @@ def draw_residuals(self, result): vertices = list( zip( - np.clip(np.asarray(result["debug_info"]["angles"]) / 60.0, 0, 1), + np.clip((np.asarray(result["debug_info"]["angles"]) - 10 ) / 40.0 , 0, 1), np.clip( - np.log10(np.array(result["debug_info"]["residuals"])) + 3, + np.log10(np.array(result["debug_info"]["residuals"])) + 2, 0.1, - 2.9, + 3.9, ), ) ) - alpha = 0.8 / (len(result["debug_info"]["angles"]) ** 0.2) - size = 4 + 10 / (len(result["debug_info"]["angles"]) ** 0.1) + alpha = 0.2 / (len(result["debug_info"]["angles"]) ** 0.2) + size = 2 + 10 / (len(result["debug_info"]["angles"]) ** 0.1) glutils.draw_points( vertices, size=size, color=RGBA(255 / 255, 165 / 255, 0, alpha) @@ -196,13 +196,13 @@ def draw_residuals(self, result): glMatrixMode(GL_PROJECTION) glPopMatrix() - def draw_dierkes_lines(self, result): + def draw_Dierkes_lines(self, result): glPushMatrix() glMatrixMode(GL_MODELVIEW) glColor4f(1.0, 0., 0., 0.1) glLineWidth(1.0) - for line in result["debug_info"]["unprojected_circles"]: + for line in result["debug_info"]["Dierkes_lines"][::4]: glBegin(GL_LINES) glVertex3f(line[0], line[1], line[2]) glVertex3f(line[3], line[4], line[5]) @@ -242,7 +242,7 @@ def update_window(self, g_pool, result): self.draw_coordinate_system(4) self.draw_eye(result) - self.draw_dierkes_lines(result) + self.draw_Dierkes_lines(result) self.trackball.pop() self.draw_debug_info(result) From 654b71ba54afa05cd3840bb4d6f4afed675db3bb Mon Sep 17 00:00:00 2001 From: euryalus Date: Mon, 17 Feb 2020 16:19:04 +0100 Subject: [PATCH 06/75] Added visualizer for new detector3d. --- .../shared_modules/pupil_detector_plugins/visualizer_pye3d.py | 1 - 1 file changed, 1 deletion(-) diff --git a/pupil_src/shared_modules/pupil_detector_plugins/visualizer_pye3d.py b/pupil_src/shared_modules/pupil_detector_plugins/visualizer_pye3d.py index 3bfe0254c9..a87882b055 100644 --- a/pupil_src/shared_modules/pupil_detector_plugins/visualizer_pye3d.py +++ b/pupil_src/shared_modules/pupil_detector_plugins/visualizer_pye3d.py @@ -21,7 +21,6 @@ from collections import deque - class Eye_Visualizer(Visualizer): def __init__(self, g_pool, focal_length): super().__init__(g_pool, "Debug Visualizer", False) From 3a729cd806b4275c19ac80b98f35876a7981080a Mon Sep 17 00:00:00 2001 From: euryalus Date: Wed, 19 Feb 2020 18:22:01 +0100 Subject: [PATCH 07/75] Detector is not getting the frame on update and detect. Needed for the 3d search. --- .../shared_modules/pupil_detector_plugins/pye3d_plugin.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/pupil_src/shared_modules/pupil_detector_plugins/pye3d_plugin.py b/pupil_src/shared_modules/pupil_detector_plugins/pye3d_plugin.py index a3cc4661ad..7052d1d36e 100644 --- a/pupil_src/shared_modules/pupil_detector_plugins/pye3d_plugin.py +++ b/pupil_src/shared_modules/pupil_detector_plugins/pye3d_plugin.py @@ -44,7 +44,7 @@ def detect(self, frame, pupil_data): datum_2d["raw_edges"] = [] result = self.detector.update_and_detect( - datum_2d, debug_toggle=self.is_debug_window_open + datum_2d, frame.gray, debug_toggle=self.is_debug_window_open ) eye_id = self.g_pool.eye_id @@ -55,6 +55,9 @@ def detect(self, frame, pupil_data): return result + def customize_menu(self): + pass + @classmethod def parse_pretty_class_name(cls) -> str: return "Pye3D Detector" From 453d788a3ed43d89c6ecfa3a38c16b65588cbf70 Mon Sep 17 00:00:00 2001 From: euryalus Date: Tue, 17 Mar 2020 12:37:39 +0100 Subject: [PATCH 08/75] Refactor --- .../visualizer_pye3d.py | 29 +++++++------------ 1 file changed, 10 insertions(+), 19 deletions(-) diff --git a/pupil_src/shared_modules/pupil_detector_plugins/visualizer_pye3d.py b/pupil_src/shared_modules/pupil_detector_plugins/visualizer_pye3d.py index a87882b055..e188b54bbf 100644 --- a/pupil_src/shared_modules/pupil_detector_plugins/visualizer_pye3d.py +++ b/pupil_src/shared_modules/pupil_detector_plugins/visualizer_pye3d.py @@ -21,6 +21,7 @@ from collections import deque + class Eye_Visualizer(Visualizer): def __init__(self, g_pool, focal_length): super().__init__(g_pool, "Debug Visualizer", False) @@ -68,22 +69,22 @@ def get_image_space_matrix(self, scale=1.): def draw_eye(self, result): - self.eye.pupil_radius = result["circle_3d_refraction"]["radius"] + self.eye.pupil_radius = result["circle_3d"]["radius"] self.eye.move_to_point( [ - result["sphere_refraction_corrected"]["center"][0], - -result["sphere_refraction_corrected"]["center"][1], - -result["sphere_refraction_corrected"]["center"][2], + result["sphere"]["center"][0], + -result["sphere"]["center"][1], + -result["sphere"]["center"][2], ] ) if result["confidence"] > 0.0 and not np.isnan( - result["circle_3d_refraction"]["normal"][0] + result["circle_3d"]["normal"][0] ): self.eye.update_from_gaze_vector( [ - result["circle_3d_refraction"]["normal"][0], - -result["circle_3d_refraction"]["normal"][1], - result["circle_3d_refraction"]["normal"][2], + result["circle_3d"]["normal"][0], + -result["circle_3d"]["normal"][1], + result["circle_3d"]["normal"][2], ] ) self.eye.draw_gl(alpha=0.7) @@ -91,31 +92,21 @@ def draw_eye(self, result): def draw_debug_info(self, result): sphere_center = result["sphere"]["center"] - sphere_center_refraction_corrected = result["sphere_refraction_corrected"]["center"] gaze_vector = result["circle_3d"]["normal"] - gaze_vector_refraction_corrected = result["circle_3d_refraction"]["normal"] pupil_radius = result["circle_3d"]["radius"] status = ( "Eyeball center : X: %.2fmm Y: %.2fmm Z: %.2fmm \n" - "Eyeball center - Refraction corrected: X: %.2fmm Y: %.2fmm Z: %.2fmm \n" "Gaze vector: X: %.2f Y: %.2f Z: %.2f\n" - "Gaze vector - Refraction corrected: X: %.2f Y: %.2f Z: %.2f\n" "Pupil Diameter: %.2fmm\n" - "Supporting pupils: %i\n" + "No. of supporting pupil observations: %i\n" % ( sphere_center[0], sphere_center[1], sphere_center[2], - sphere_center_refraction_corrected[0], - sphere_center_refraction_corrected[1], - sphere_center_refraction_corrected[2], gaze_vector[0], gaze_vector[1], gaze_vector[2], - gaze_vector_refraction_corrected[0], - gaze_vector_refraction_corrected[1], - gaze_vector_refraction_corrected[2], pupil_radius * 2, len(result["debug_info"]["Dierkes_lines"]), ) From ee4be7aec144acd009a46985eb9e8beed14989f9 Mon Sep 17 00:00:00 2001 From: Chris Kay Baumann Date: Mon, 20 Jul 2020 18:00:24 +0200 Subject: [PATCH 09/75] Break reference cycle in ObservableMethodWrapper --- pupil_src/shared_modules/observable.py | 28 +++++++++++++++++++------ pupil_src/tests/test_observable.py | 29 +++++++++++++++++++++++++- 2 files changed, 50 insertions(+), 7 deletions(-) diff --git a/pupil_src/shared_modules/observable.py b/pupil_src/shared_modules/observable.py index e646f46c5e..ec8b23fbeb 100644 --- a/pupil_src/shared_modules/observable.py +++ b/pupil_src/shared_modules/observable.py @@ -147,6 +147,7 @@ def _get_wrapper_and_create_if_not_exists(obj, method_name): "Attribute {} of object {} is a class method and, thus, " "cannot be observed!".format(method_name, obj) ) + # TODO: Sanity check for observed method else: return _ObservableMethodWrapper(obj, method_name) @@ -195,21 +196,35 @@ def _get_wrapper_or_raise_if_not_exists(obj, method_name): class _ObservableMethodWrapper: def __init__(self, obj, method_name): - self._obj = obj - self._original_method = getattr(obj, method_name) + # the wrapper should not block garbage collection by reference counting for the + # observable object. Hence, we need to avoid strong references to the object, + # which would lead to cyclic references: + # - The object is only referenced weakly + # - The original wrapped method is only stored as an unbound method + self._obj_ref = weakref.ref(obj) + self._wrapped_func = getattr(obj.__class__, method_name) self._method_name = method_name self._observers = [] self._was_removed = False self._patch_method_to_call_wrapper_instead() def _patch_method_to_call_wrapper_instead(self): - functools.update_wrapper(self, self._original_method) - setattr(self._obj, self._method_name, self) + functools.update_wrapper(self, self._wrapped_func) + # functools adds a reference to the wrapped method that we need to delete + # to avoid cyclic references + self.__wrapped__ = None + setattr(self._obj_ref(), self._method_name, self) def remove_wrapper(self): - setattr(self._obj, self._method_name, self._original_method) + setattr(obj, self._method_name, self._get_wrapped_bound_method()) self._was_removed = True + def _get_wrapped_bound_method(self): + obj = self._obj_ref() + # see https://stackoverflow.com/a/1015405 + original_bound_method = self._wrapped_func.__get__(obj, obj.__class__) + return original_bound_method + def add_observer(self, observer): # Observers that are bound methods are referenced weakly. That means, # they might get deleted together with their object if the object is not @@ -243,8 +258,9 @@ def __call__(self, *args, **kwargs): "elsewhere and called via this reference after you " "removed the wrapper!" ) + wrapped_bound_method = self._get_wrapped_bound_method() try: - return self._original_method(*args, **kwargs) + return wrapped_bound_method(*args, **kwargs) except Exception: raise finally: diff --git a/pupil_src/tests/test_observable.py b/pupil_src/tests/test_observable.py index 630e8802c2..6b81bf699c 100644 --- a/pupil_src/tests/test_observable.py +++ b/pupil_src/tests/test_observable.py @@ -9,9 +9,11 @@ ---------------------------------------------------------------------------~(*) """ -import pytest +import gc from unittest import mock +import pytest + from observable import Observable, ObserverError @@ -30,6 +32,9 @@ def static_method(): def class_method(cls): pass + def __del__(self): + pass + @pytest.fixture() def observable(): @@ -289,6 +294,28 @@ def __call__(self, *args, **kwargs): mock_function.assert_any_call() +@pytest.fixture() +def disable_generational_garbage_collection(): + gc_enabled = gc.isenabled() + gc.disable() + yield + if gc_enabled: + gc.enable() + + +class TestDeletingObservableObjects: + def test_observable_object_can_be_freed_by_reference_counting( + self, disable_generational_garbage_collection + ): + # do not use our fixture `observable` here, for some reason pytest keeps + # additional references to the object which makes the following test impossible + observable = FakeObservable() + with mock.patch.object(FakeObservable, "__del__") as mock_function: + observable.add_observer("bound_method", mock.Mock()) + del observable + mock_function.assert_any_call() + + def test_observers_can_be_observed(observable): # This test is more delicate than it might seem at first sight. # When adding the first observer, the method is replaced by a wrapper of the From c64ee7424c04c7f934560fb9196c365bc3374723 Mon Sep 17 00:00:00 2001 From: Chris Kay Baumann Date: Mon, 20 Jul 2020 18:37:01 +0200 Subject: [PATCH 10/75] Add check for modified method attributes --- pupil_src/shared_modules/observable.py | 23 ++++++++++++++++++++++- pupil_src/tests/test_observable.py | 11 +++++++++++ 2 files changed, 33 insertions(+), 1 deletion(-) diff --git a/pupil_src/shared_modules/observable.py b/pupil_src/shared_modules/observable.py index ec8b23fbeb..f424da53b8 100644 --- a/pupil_src/shared_modules/observable.py +++ b/pupil_src/shared_modules/observable.py @@ -13,6 +13,8 @@ import inspect import weakref +from attr import has + class ObserverError(Exception): pass @@ -147,7 +149,12 @@ def _get_wrapper_and_create_if_not_exists(obj, method_name): "Attribute {} of object {} is a class method and, thus, " "cannot be observed!".format(method_name, obj) ) - # TODO: Sanity check for observed method + elif _method_was_modified(obj, method_name): + raise TypeError( + f"Attribute '{method_name}' of object {obj} cannot be observed because it " + "is not part of the original class definition or has been assigned to a " + "method of a different object!" + ) else: return _ObservableMethodWrapper(obj, method_name) @@ -160,6 +167,20 @@ def _is_classmethod(obj, method_name): return False +def _method_was_modified(obj, method_name): + method_in_original_class = hasattr(obj.__class__, method_name) + if not method_in_original_class: + return True + expected_func = getattr(obj.__class__, method_name) + expected_self = obj + + method = getattr(obj, method_name) + bound_func = method.__func__ + bound_self = method.__self__ + + return expected_self is not bound_self or expected_func is not bound_func + + def remove_observer(obj, method_name, observer): """ Removes an observer from a bound method of an arbitrary object. diff --git a/pupil_src/tests/test_observable.py b/pupil_src/tests/test_observable.py index 6b81bf699c..c863381c5c 100644 --- a/pupil_src/tests/test_observable.py +++ b/pupil_src/tests/test_observable.py @@ -57,6 +57,17 @@ def test_class_method_is_not_observable(self, observable, observer): with pytest.raises(TypeError): observable.add_observer("class_method", observer) + def test_modified_methods_are_not_observable(self, observable, observer): + class FakeClass: + def fake_method(self): + pass + + fake_class_instance = FakeClass() + observable.modified_method = fake_class_instance.fake_method + + with pytest.raises(TypeError): + observable.add_observer("modified_method", observer) + class TestDifferentKindsOfObservers: def test_bound_method_without_arguments_can_be_observer(self, observable): From e87d7edfae08f60d589df208defa5fae3276b035 Mon Sep 17 00:00:00 2001 From: Chris Kay Baumann Date: Mon, 20 Jul 2020 18:40:41 +0200 Subject: [PATCH 11/75] Use fstrings instead of format --- pupil_src/shared_modules/observable.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/pupil_src/shared_modules/observable.py b/pupil_src/shared_modules/observable.py index f424da53b8..006b7ba00b 100644 --- a/pupil_src/shared_modules/observable.py +++ b/pupil_src/shared_modules/observable.py @@ -141,13 +141,13 @@ def _get_wrapper_and_create_if_not_exists(obj, method_name): return observed_method elif not inspect.ismethod(observed_method): raise TypeError( - "Attribute {} of object {} is not a method but {} and, thus, " - "cannot be observed!".format(method_name, obj, observed_method) + f"Attribute '{method_name}' of object {obj} is not a method but " + f"{observed_method} and, thus, cannot be observed!" ) elif _is_classmethod(obj, method_name): raise TypeError( - "Attribute {} of object {} is a class method and, thus, " - "cannot be observed!".format(method_name, obj) + f"Attribute '{method_name}' of object {obj} is a class method and, thus, " + "cannot be observed!" ) elif _method_was_modified(obj, method_name): raise TypeError( @@ -209,8 +209,8 @@ def _get_wrapper_or_raise_if_not_exists(obj, method_name): observable_wrapper = getattr(obj, method_name) if not isinstance(observable_wrapper, _ObservableMethodWrapper): raise TypeError( - "Attribute {} of object {} is not observable. You never added an " - "observer to this!".format(method_name, obj) + f"Attribute '{method_name}' of object {obj} is not observable. You never " + "added an observer to this!" ) return observable_wrapper @@ -265,7 +265,7 @@ def remove_observer(self, observer): self._observers.remove(observer) except ValueError: raise ValueError( - "No observer {} found that could be removed!".format(observer) + f"No observer {observer} found that could be removed!" ) from None def remove_all_observers(self): From d9a6ba4c3a1910a2cfb4a0493a0ccfb1e6d02c7b Mon Sep 17 00:00:00 2001 From: Chris Kay Baumann Date: Tue, 21 Jul 2020 11:49:30 +0200 Subject: [PATCH 12/75] Add tests for wrapped method functionality --- pupil_src/tests/test_observable.py | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/pupil_src/tests/test_observable.py b/pupil_src/tests/test_observable.py index c863381c5c..458bb2b708 100644 --- a/pupil_src/tests/test_observable.py +++ b/pupil_src/tests/test_observable.py @@ -176,6 +176,34 @@ def test_multiple_observers_are_called(self, observable): assert observer1_called and observer2_called and observer3_called +class TestWrappedMethodCalls: + def test_wrapped_functions_are_called_with_right_arguments(self): + mock_function = mock.Mock() + + class FakeObservable(Observable): + def method(self, arg1, arg2): + mock_function(arg1, arg2) + + observable = FakeObservable() + observable.add_observer("method", lambda arg1, arg2: None) + + observable.method(1, 2) + + mock_function.assert_called_once_with(1, 2) + + def test_wrapped_functions_return_values(self): + class FakeObservable(Observable): + def method(self): + return 1 + + observable = FakeObservable() + observable.add_observer("method", lambda: None) + + ret_val = observable.method() + + assert ret_val == 1 + + class TestRemovingObservers: def test_observers_that_are_functions_can_be_removed(self, observable): observer = mock.Mock() From 37816d42f5ace519097652a4656d8d77188fdf01 Mon Sep 17 00:00:00 2001 From: Chris Kay Baumann Date: Tue, 21 Jul 2020 12:01:59 +0200 Subject: [PATCH 13/75] Delete useless import --- pupil_src/shared_modules/observable.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/pupil_src/shared_modules/observable.py b/pupil_src/shared_modules/observable.py index 006b7ba00b..94396781d8 100644 --- a/pupil_src/shared_modules/observable.py +++ b/pupil_src/shared_modules/observable.py @@ -13,8 +13,6 @@ import inspect import weakref -from attr import has - class ObserverError(Exception): pass From 90c1928d2483a5ebfaa1479d780593e09d0eaad5 Mon Sep 17 00:00:00 2001 From: Chris Kay Baumann Date: Tue, 21 Jul 2020 12:06:20 +0200 Subject: [PATCH 14/75] Move gc fixture into corresponding class --- pupil_src/tests/test_observable.py | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/pupil_src/tests/test_observable.py b/pupil_src/tests/test_observable.py index 458bb2b708..d80d50b3ea 100644 --- a/pupil_src/tests/test_observable.py +++ b/pupil_src/tests/test_observable.py @@ -333,16 +333,15 @@ def __call__(self, *args, **kwargs): mock_function.assert_any_call() -@pytest.fixture() -def disable_generational_garbage_collection(): - gc_enabled = gc.isenabled() - gc.disable() - yield - if gc_enabled: - gc.enable() - - class TestDeletingObservableObjects: + @pytest.fixture() + def disable_generational_garbage_collection(self): + gc_enabled = gc.isenabled() + gc.disable() + yield + if gc_enabled: + gc.enable() + def test_observable_object_can_be_freed_by_reference_counting( self, disable_generational_garbage_collection ): From a011083b9fb340a525989601ed7181bf1b6275b5 Mon Sep 17 00:00:00 2001 From: Chris Kay Baumann Date: Mon, 27 Jul 2020 13:44:20 +0200 Subject: [PATCH 15/75] Fix remove_wrapper() and add corresponding test --- pupil_src/shared_modules/observable.py | 2 +- pupil_src/tests/test_observable.py | 8 ++++++++ 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/pupil_src/shared_modules/observable.py b/pupil_src/shared_modules/observable.py index 94396781d8..dc9b50a416 100644 --- a/pupil_src/shared_modules/observable.py +++ b/pupil_src/shared_modules/observable.py @@ -235,7 +235,7 @@ def _patch_method_to_call_wrapper_instead(self): setattr(self._obj_ref(), self._method_name, self) def remove_wrapper(self): - setattr(obj, self._method_name, self._get_wrapped_bound_method()) + setattr(self._obj_ref(), self._method_name, self._get_wrapped_bound_method()) self._was_removed = True def _get_wrapped_bound_method(self): diff --git a/pupil_src/tests/test_observable.py b/pupil_src/tests/test_observable.py index d80d50b3ea..c49616354e 100644 --- a/pupil_src/tests/test_observable.py +++ b/pupil_src/tests/test_observable.py @@ -260,6 +260,14 @@ def test_remove_all_from_empty_observer_list_is_fine(self, observable): # at this point the list of observers is empty observable.remove_all_observers("bound_method") + def test_wrapper_can_be_removed(self, observable): + original_method = observable.bound_method + observable.add_observer("bound_method", lambda: None) + observable.bound_method.remove_wrapper() + method_after_removal = observable.bound_method + + assert method_after_removal == original_method + class TestExceptionThrowingMethods: class FakeException(Exception): From 5dc122424bd0ff7d467193e81ae1f46d852d7a6f Mon Sep 17 00:00:00 2001 From: Chris Kay Baumann Date: Fri, 7 Aug 2020 13:43:58 +0200 Subject: [PATCH 16/75] Add descriptor that protects wrapper from being replaced --- pupil_src/shared_modules/observable.py | 64 +++++++++++++++++++++++++- pupil_src/tests/test_observable.py | 14 +++++- 2 files changed, 76 insertions(+), 2 deletions(-) diff --git a/pupil_src/shared_modules/observable.py b/pupil_src/shared_modules/observable.py index dc9b50a416..fee169a17a 100644 --- a/pupil_src/shared_modules/observable.py +++ b/pupil_src/shared_modules/observable.py @@ -18,6 +18,10 @@ class ObserverError(Exception): pass +class ReplaceWrapperError(Exception): + pass + + class Observable: """ An object that allows to add observers to any of its methods. See @@ -128,6 +132,7 @@ def add_observer(obj, method_name, observer): """ observable = _get_wrapper_and_create_if_not_exists(obj, method_name) observable.add_observer(observer) + _install_protection_descriptor_if_not_exists(obj, method_name) def _get_wrapper_and_create_if_not_exists(obj, method_name): @@ -179,6 +184,58 @@ def _method_was_modified(obj, method_name): return expected_self is not bound_self or expected_func is not bound_func +def _install_protection_descriptor_if_not_exists(obj, method_name): + type_ = type(obj) + already_installed = isinstance( + getattr(type_, method_name), _WrapperProtectionDescriptor + ) + if not already_installed: + setattr(type_, method_name, _WrapperProtectionDescriptor(type_, method_name)) + + +class _WrapperProtectionDescriptor: + """ + Protects wrappers from being replaced which would silently disable observers. + Besides this, default python behavior is emulated. + """ + + def __init__(self, type, name): + self.original = getattr(type, name) + assert not inspect.isdatadescriptor(self.original) + self.name = name + + def __get__(self, obj, type=None): + # This is a data discriptor, so it's called first. + # Emulate python lookup chain: + # 1.) original is data discriptor: Cannot happen, we only allow methods + # (= non-data descriptors) to be wrapped. + # 2.) original is in object's __dict__ + # 3.) original is in object's class or some of its parents: We don't need to + # worry about if it's in the class or some parent, this is covered by + # getattr() in __init__ + if obj is not None and self.name in obj.__dict__: + return obj.__dict__[self.name] + else: + return self.original.__get__(obj, type) + + def __set__(self, obj, value): + instance_method_wrapped = isinstance( + getattr(obj, self.name), _ObservableMethodWrapper + ) + + obj.__dict__[self.name] = value + + # Raise error after the attribute is set. Makes it possible to change the + # attribute and ignore the error by catching it + if instance_method_wrapped: + raise ReplaceWrapperError( + f"Cannot set attribute '{self.name}' of object {obj} because it is an " + "ObservableMethodWrapper. Replacing it would silently disable observer " + "functionality. If you really want to, you can catch and then ignore " + "this error." + ) + + def remove_observer(obj, method_name, observer): """ Removes an observer from a bound method of an arbitrary object. @@ -235,7 +292,12 @@ def _patch_method_to_call_wrapper_instead(self): setattr(self._obj_ref(), self._method_name, self) def remove_wrapper(self): - setattr(self._obj_ref(), self._method_name, self._get_wrapped_bound_method()) + try: + setattr( + self._obj_ref(), self._method_name, self._get_wrapped_bound_method() + ) + except ReplaceWrapperError: + pass self._was_removed = True def _get_wrapped_bound_method(self): diff --git a/pupil_src/tests/test_observable.py b/pupil_src/tests/test_observable.py index c49616354e..c6c7594b30 100644 --- a/pupil_src/tests/test_observable.py +++ b/pupil_src/tests/test_observable.py @@ -14,7 +14,7 @@ import pytest -from observable import Observable, ObserverError +from observable import Observable, ObserverError, ReplaceWrapperError class FakeObservable(Observable): @@ -391,3 +391,15 @@ def on_bound_method(self): mock_function.assert_has_calls( [mock.call("First"), mock.call("Second")], any_order=False ) + + +class TestWrapperProtectionDescriptor: + def test_wrapped_functions_cannot_be_set(self, observable): + observable.add_observer("bound_method", lambda: None) + with pytest.raises(ReplaceWrapperError): + observable.bound_method = 42 + + def test_other_instances_without_wrapper_can_be_set(self, observable): + observable.add_observer("bound_method", lambda: None) + unwrapped_observable = FakeObservable() + unwrapped_observable.bound_method = 42 From d5cac6d57466f07323eda9927c7fc5dbfdc78677 Mon Sep 17 00:00:00 2001 From: Chris Kay Baumann Date: Fri, 7 Aug 2020 13:44:31 +0200 Subject: [PATCH 17/75] Improve error message --- pupil_src/shared_modules/observable.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pupil_src/shared_modules/observable.py b/pupil_src/shared_modules/observable.py index fee169a17a..2ec64c88c6 100644 --- a/pupil_src/shared_modules/observable.py +++ b/pupil_src/shared_modules/observable.py @@ -145,7 +145,7 @@ def _get_wrapper_and_create_if_not_exists(obj, method_name): elif not inspect.ismethod(observed_method): raise TypeError( f"Attribute '{method_name}' of object {obj} is not a method but " - f"{observed_method} and, thus, cannot be observed!" + f"{type(observed_method)} and, thus, cannot be observed!" ) elif _is_classmethod(obj, method_name): raise TypeError( From 1a8987a389561c6a49709e809fe62391253c96f6 Mon Sep 17 00:00:00 2001 From: Chris Kay Baumann Date: Fri, 7 Aug 2020 13:49:07 +0200 Subject: [PATCH 18/75] Add test for observing methods from parent classes --- pupil_src/tests/test_observable.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/pupil_src/tests/test_observable.py b/pupil_src/tests/test_observable.py index c6c7594b30..59f8e6aa28 100644 --- a/pupil_src/tests/test_observable.py +++ b/pupil_src/tests/test_observable.py @@ -49,6 +49,13 @@ def observer(self): def test_bound_method_is_observable(self, observable, observer): observable.add_observer("bound_method", observer) + def test_bound_method_from_parent_class_is_observable(self, observer): + class FakeObservableChild(FakeObservable): + pass + + observable_child = FakeObservableChild() + observable_child.add_observer("bound_method", observer) + def test_static_method_is_not_observable(self, observable, observer): with pytest.raises(TypeError): observable.add_observer("static_method", observer) From 689628c82a453907d25e5bcaf60a761de43c43b6 Mon Sep 17 00:00:00 2001 From: Patrick Faion Date: Thu, 13 Aug 2020 16:53:39 +0200 Subject: [PATCH 19/75] Adapt pye3D plugin for pupil v2 changes --- .../pupil_detector_plugins/detector_base_plugin.py | 7 ++++++- .../pupil_detector_plugins/pye3d_plugin.py | 9 ++++++--- 2 files changed, 12 insertions(+), 4 deletions(-) diff --git a/pupil_src/shared_modules/pupil_detector_plugins/detector_base_plugin.py b/pupil_src/shared_modules/pupil_detector_plugins/detector_base_plugin.py index a2b3925494..a2e46fd2cb 100644 --- a/pupil_src/shared_modules/pupil_detector_plugins/detector_base_plugin.py +++ b/pupil_src/shared_modules/pupil_detector_plugins/detector_base_plugin.py @@ -98,7 +98,10 @@ def recent_events(self, event): # this is only revelant when running the 3D detector, for 2D we just ignore the # additional parameter detection_result = self.detect( - frame=frame, internal_raw_2d_data=event.get("internal_2d_raw_data", None) + frame=frame, + internal_raw_2d_data=event.get("internal_2d_raw_data", None), + # TODO: workaround to get 2D data into pye3D for now + previous_detection_results=event.get(EVENT_KEY, []), ) # if we are running the 2D detector, we might get internal data that we don't @@ -111,6 +114,8 @@ def recent_events(self, event): else: event[EVENT_KEY] = [detection_result] + self._recent_detection_result = detection_result + @abc.abstractmethod def detect(self, frame, pupil_data): pass diff --git a/pupil_src/shared_modules/pupil_detector_plugins/pye3d_plugin.py b/pupil_src/shared_modules/pupil_detector_plugins/pye3d_plugin.py index 7052d1d36e..9acffd4774 100644 --- a/pupil_src/shared_modules/pupil_detector_plugins/pye3d_plugin.py +++ b/pupil_src/shared_modules/pupil_detector_plugins/pye3d_plugin.py @@ -26,6 +26,7 @@ class Pye3DPlugin(PupilDetectorPlugin): icon_chr = chr(0xEC19) label = "Pye3D" + order = 0.101 def __init__(self, g_pool): super().__init__(g_pool) @@ -34,13 +35,15 @@ def __init__(self, g_pool): g_pool, self.detector.settings["focal_length"] ) - def detect(self, frame, pupil_data): - for datum in pupil_data: + def detect(self, frame, **kwargs): + previous_detection_results = kwargs.get("previous_detection_results", []) + for datum in previous_detection_results: if datum.get("method", "") == "2d c++": datum_2d = datum break else: - return None + # TODO: make this more stable! + raise RuntimeError("No 2D detection result! Needed for pye3D!") datum_2d["raw_edges"] = [] result = self.detector.update_and_detect( From 02ed062fd733c71d78e19fbdffe0a4830a47877a Mon Sep 17 00:00:00 2001 From: Patrick Faion Date: Wed, 19 Aug 2020 17:09:03 +0200 Subject: [PATCH 20/75] Add eye camera intrinsics --- pupil_src/shared_modules/camera_models.py | 79 +++++++++++++++++-- .../pupil_recording/update/update_utils.py | 4 +- 2 files changed, 75 insertions(+), 8 deletions(-) diff --git a/pupil_src/shared_modules/camera_models.py b/pupil_src/shared_modules/camera_models.py index e02a0c157a..104c98b3de 100644 --- a/pupil_src/shared_modules/camera_models.py +++ b/pupil_src/shared_modules/camera_models.py @@ -22,9 +22,9 @@ logger = logging.getLogger(__name__) __version__ = 1 -# These are world camera intrinsics that we recorded. They are estimates and generalize -# our setup. Its always better to estimate intrinsics for each camera again. -default_world_intrinsics = { +# These are camera intrinsics that we recorded. They are estimates and generalize our +# setup. Its always better to estimate intrinsics for each camera again. +default_intrinsics = { "Pupil Cam1 ID2": { "(640, 480)": { "dist_coefs": [ @@ -152,6 +152,73 @@ }, } +# Add measured intrinsics for the eyes (once for each ID for easy lookup) +for eye_id in (0, 1): + default_intrinsics.update( + { + f"Pupil Cam1 ID{eye_id}": { + "(320, 240)": { + "dist_coefs": [[0.0, 0.0, 0.0, 0.0, 0.0]], + "camera_matrix": [ + [338.456035, 0.0, 160], + [0.0, 339.871543, 120], + [0.0, 0.0, 1.0], + ], + "cam_type": "radial", + }, + "(640, 480)": { + "dist_coefs": [[0.0, 0.0, 0.0, 0.0, 0.0]], + "camera_matrix": [ + [670.785555, 0.0, 320], + [0.0, 670.837798, 240], + [0.0, 0.0, 1.0], + ], + "cam_type": "radial", + }, + }, + f"Pupil Cam2 ID{eye_id}": { + "(192, 192)": { + "dist_coefs": [[0.0, 0.0, 0.0, 0.0, 0.0]], + "camera_matrix": [ + [282.976877, 0.0, 96], + [0.0, 283.561467, 96], + [0.0, 0.0, 1.0], + ], + "cam_type": "radial", + }, + "(400, 400)": { + "dist_coefs": [[0.0, 0.0, 0.0, 0.0, 0.0]], + "camera_matrix": [ + [561.471804, 0.0, 200], + [0.0, 562.494105, 200], + [0.0, 0.0, 1.0], + ], + "cam_type": "radial", + }, + }, + f"Pupil Cam3 ID{eye_id}": { + "(192, 192)": { + "dist_coefs": [[0.0, 0.0, 0.0, 0.0, 0.0]], + "camera_matrix": [ + [140.0, 0.0, 96], + [0.0, 140.0, 96], + [0.0, 0.0, 1.0], + ], + "cam_type": "radial", + }, + "(400, 400)": { + "dist_coefs": [[0.0, 0.0, 0.0, 0.0, 0.0]], + "camera_matrix": [ + [278.50, 0.0, 200], + [0.0, 278.55, 200], + [0.0, 0.0, 1.0], + ], + "cam_type": "radial", + }, + }, + } + ) + class Camera_Model(abc.ABC): cam_type = ... # overwrite in subclasses, used for saving/loading @@ -305,11 +372,11 @@ def from_file(directory, cam_name, resolution): ) if ( - cam_name in default_world_intrinsics - and str(resolution) in default_world_intrinsics[cam_name] + cam_name in default_intrinsics + and str(resolution) in default_intrinsics[cam_name] ): logger.info("Loading default intrinsics!") - intrinsics = default_world_intrinsics[cam_name][str(resolution)] + intrinsics = default_intrinsics[cam_name][str(resolution)] else: logger.warning( "No default intrinsics available! Loading dummy intrinsics!" diff --git a/pupil_src/shared_modules/pupil_recording/update/update_utils.py b/pupil_src/shared_modules/pupil_recording/update/update_utils.py index 5c3ed07ce4..abf86b4a78 100644 --- a/pupil_src/shared_modules/pupil_recording/update/update_utils.py +++ b/pupil_src/shared_modules/pupil_recording/update/update_utils.py @@ -31,7 +31,7 @@ def _try_patch_world_instrinsics_file(rec_dir: str, videos: T.Sequence[Path]) -> # Make sure the default value always correlates to the frame size of BrokenStream frame_size = (1280, 720) # TODO: Due to the naming conventions for multipart-recordings, we can't - # easily lookup 'any' video name in the default_world_intrinsics, since it + # easily lookup 'any' video name in the default_intrinsics, since it # might be a multipart recording. Therefore we need to compute a hint here # for the lookup. This could be improved. camera_hint = "" @@ -41,7 +41,7 @@ def _try_patch_world_instrinsics_file(rec_dir: str, videos: T.Sequence[Path]) -> except av.AVError: continue - for camera in cm.default_world_intrinsics: + for camera in cm.default_intrinsics: if camera in video.name: camera_hint = camera break From 78461d9688685c9fe02ab8720ad20d91687d94e7 Mon Sep 17 00:00:00 2001 From: Patrick Faion Date: Fri, 21 Aug 2020 10:24:27 +0200 Subject: [PATCH 21/75] Add ui_available property to Plugin base Often plugins need to know whether the UI exists or now. It does not exist in __init__ or when the menu was removed before. This property makes this information available for all plugins. --- pupil_src/shared_modules/plugin.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/pupil_src/shared_modules/plugin.py b/pupil_src/shared_modules/plugin.py index 02e4e5391a..2016c55671 100644 --- a/pupil_src/shared_modules/plugin.py +++ b/pupil_src/shared_modules/plugin.py @@ -299,6 +299,13 @@ def remove_menu(self): self.menu = None self.menu_icon = None + @property + def ui_available(self): + try: + return self.menu is not None + except AttributeError: + return False + def __monkeypatch_gl_display_error_checking(self): # Monkeypatch gl_display functions to include error checking. This is because we # often receive OpenGL errors as results of buggy pyglui code that gets called From a85c69d44f734adbbbdb29d706bb74034008755b Mon Sep 17 00:00:00 2001 From: Patrick Faion Date: Fri, 21 Aug 2020 10:28:24 +0200 Subject: [PATCH 22/75] Expose focal_length property from camera models --- pupil_src/shared_modules/camera_models.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/pupil_src/shared_modules/camera_models.py b/pupil_src/shared_modules/camera_models.py index 104c98b3de..10ddc7521f 100644 --- a/pupil_src/shared_modules/camera_models.py +++ b/pupil_src/shared_modules/camera_models.py @@ -235,6 +235,12 @@ def update_camera_matrix(self, camera_matrix): def update_dist_coefs(self, dist_coefs): self.D = np.asanyarray(dist_coefs).reshape(self.D.shape) + @property + def focal_length(self): + fx = self.K[0, 0] + fy = self.K[1, 1] + return (fx + fy) / 2 + @abc.abstractmethod def undistort(self, img: np.ndarray) -> np.ndarray: ... From 8a43dc5d45311766baa7d0d516d11e71b6ee7736 Mon Sep 17 00:00:00 2001 From: Patrick Faion Date: Fri, 21 Aug 2020 10:29:05 +0200 Subject: [PATCH 23/75] Reinit 3D detector on focal length changes --- .../detector_3d_plugin.py | 38 +++++++++++++++++-- 1 file changed, 35 insertions(+), 3 deletions(-) diff --git a/pupil_src/shared_modules/pupil_detector_plugins/detector_3d_plugin.py b/pupil_src/shared_modules/pupil_detector_plugins/detector_3d_plugin.py index 918563b646..414db530c9 100644 --- a/pupil_src/shared_modules/pupil_detector_plugins/detector_3d_plugin.py +++ b/pupil_src/shared_modules/pupil_detector_plugins/detector_3d_plugin.py @@ -55,12 +55,40 @@ def __init__( self, g_pool=None, namespaced_properties=None, detector_3d: Detector3D = None ): super().__init__(g_pool=g_pool) - self.detector_3d = detector_3d or Detector3D(namespaced_properties or {}) + detector = detector_3d or Detector3D(namespaced_properties or {}) + self._initialize(detector) + + @property + def detector_3d(self): + return self._detector_internal + + def _initialize(self, detector: Detector3D): + # initialize plugin with a detector instance, safe to call multiple times + self._detector_internal = detector self.proxy = PropertyProxy(self.detector_3d) - # debug window - self.debugVisualizer3D = Eye_Visualizer(g_pool, self.detector_3d.focal_length()) + self.debugVisualizer3D = Eye_Visualizer( + self.g_pool, self.detector_3d.focal_length() + ) + self._last_focal_length = self.detector_3d.focal_length() + if self.ui_available: + # ui was wrapped around old detector, need to re-init for new one + self._reinit_ui() + + def _process_focal_length_changes(self): + focal_length = self.g_pool.capture.intrinsics.focal_length + if focal_length != self._last_focal_length: + logger.debug( + f"Focal length change detected: {focal_length}." + " Re-initializing 3D detector." + ) + # reinitialize detector with same properties but updated focal length + properties = self.detector_3d.get_properties() + new_detector = Detector3D(properties=properties, focal_length=focal_length) + self._initialize(new_detector) def detect(self, frame, **kwargs): + self._process_focal_length_changes() + # convert roi-plugin to detector roi roi = Roi(*self.g_pool.roi.bounds) @@ -96,6 +124,10 @@ def parse_pretty_class_name(cls) -> str: def init_ui(self): super().init_ui() + self._reinit_ui() + + def _reinit_ui(self): + self.menu.elements.clear() self.menu.label = self.pretty_class_name self.menu.append( ui.Info_Text( From 256c3f4d2214e90f8319ee3f196a752281529a6f Mon Sep 17 00:00:00 2001 From: Patrick Faion Date: Fri, 21 Aug 2020 11:48:32 +0200 Subject: [PATCH 24/75] Properly cleanup and reopen 3D debug window --- .../pupil_detector_plugins/detector_3d_plugin.py | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/pupil_src/shared_modules/pupil_detector_plugins/detector_3d_plugin.py b/pupil_src/shared_modules/pupil_detector_plugins/detector_3d_plugin.py index 414db530c9..171c283609 100644 --- a/pupil_src/shared_modules/pupil_detector_plugins/detector_3d_plugin.py +++ b/pupil_src/shared_modules/pupil_detector_plugins/detector_3d_plugin.py @@ -66,9 +66,22 @@ def _initialize(self, detector: Detector3D): # initialize plugin with a detector instance, safe to call multiple times self._detector_internal = detector self.proxy = PropertyProxy(self.detector_3d) + + # In case of re-initialization, we need to close the debug window or else we + # leak the opengl window. We can open the new one again afterwards. + try: + debug_window_was_open = self.is_debug_window_open + except AttributeError: + # debug window does not exist yet + debug_window_was_open = False + if debug_window_was_open: + self.debug_window_close() self.debugVisualizer3D = Eye_Visualizer( self.g_pool, self.detector_3d.focal_length() ) + if debug_window_was_open: + self.debug_window_open() + self._last_focal_length = self.detector_3d.focal_length() if self.ui_available: # ui was wrapped around old detector, need to re-init for new one From 6dbd1260c753fb8df3f4e2cd8138947092c841bc Mon Sep 17 00:00:00 2001 From: Patrick Faion Date: Fri, 21 Aug 2020 14:14:57 +0200 Subject: [PATCH 25/75] Save eye intrinsics in recording --- pupil_src/launchables/eye.py | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/pupil_src/launchables/eye.py b/pupil_src/launchables/eye.py index 3b09caf8db..f3eb77d6a3 100644 --- a/pupil_src/launchables/eye.py +++ b/pupil_src/launchables/eye.py @@ -503,6 +503,7 @@ def set_window_size(): toggle_general_settings(True) g_pool.writer = None + g_pool.rec_path = None # Register callbacks main_window glfw.glfwSetFramebufferSizeCallback(main_window, on_resize) @@ -567,12 +568,12 @@ def window_should_update(): break elif subject == "recording.started": if notification["record_eye"] and g_pool.capture.online: - record_path = notification["rec_path"] + g_pool.rec_path = notification["rec_path"] raw_mode = notification["compression"] start_time_synced = notification["start_time_synced"] - logger.info("Will save eye video to: {}".format(record_path)) + logger.info(f"Will save eye video to: {g_pool.rec_path}") video_path = os.path.join( - record_path, "eye{}.mp4".format(eye_id) + g_pool.rec_path, "eye{}.mp4".format(eye_id) ) if raw_mode and frame and g_pool.capture.jpeg_support: g_pool.writer = JPEG_Writer(video_path, start_time_synced) @@ -592,7 +593,13 @@ def window_should_update(): g_pool.writer.release() except RuntimeError: logger.error("No eye video recorded") - g_pool.writer = None + else: + # TODO: wrap recording logic into plugin + g_pool.capture.intrinsics.save( + g_pool.rec_path, custom_name=f"eye{eye_id}" + ) + finally: + g_pool.writer = None elif subject.startswith("meta.should_doc"): ipc_socket.notify( { From 96a062f94c9829b595ef7a156812435f00499ca1 Mon Sep 17 00:00:00 2001 From: Roman Roibu Date: Mon, 24 Aug 2020 09:21:01 +0200 Subject: [PATCH 26/75] Add parse_version function for creating a comparable version object --- pupil_src/shared_modules/version_utils.py | 41 ++++++++++++++--------- 1 file changed, 26 insertions(+), 15 deletions(-) diff --git a/pupil_src/shared_modules/version_utils.py b/pupil_src/shared_modules/version_utils.py index 3e4f06fbe7..5c7e800b8a 100644 --- a/pupil_src/shared_modules/version_utils.py +++ b/pupil_src/shared_modules/version_utils.py @@ -5,15 +5,15 @@ from subprocess import check_output, CalledProcessError, STDOUT import os, sys -from distutils.version import LooseVersion as VersionFormat - +import distutils.version +import typing as T import logging logger = logging.getLogger(__name__) -def get_tag_commit(): +def get_tag_commit() -> T.Optional[T.AnyStr]: """ returns string: 'tag'-'commits since tag'-'7 digit commit id' """ @@ -34,7 +34,18 @@ def get_tag_commit(): return None -def pupil_version(): +_Version = distutils.version.LooseVersion + + +def parse_version(vstring) -> _Version: + return distutils.version.LooseVersion(vstring) + + +def pupil_version() -> _Version: + return parse_version(pupil_version_string()) + + +def pupil_version_string() -> str: """ [major].[minor].[trailing-untagged-commits] """ @@ -58,23 +69,23 @@ def get_version(): if getattr(sys, "frozen", False): version_file = os.path.join(sys._MEIPASS, "_version_string_") with open(version_file, "r") as f: - version = f.read() + version_string = f.read() else: - version = pupil_version() - version = VersionFormat(version) - logger.debug("Running version: {}".format(version)) - return version + version_string = pupil_version_string() + logger.debug(f"Running version: {version_string}") + return parse_version(version_string) def write_version_file(target_dir): - version = pupil_version() - print("Current version of Pupil: ", version) + version_string = pupil_version_string() + print(f"Current version of Pupil: {version_string}") - with open(os.path.join(target_dir, "_version_string_"), "w") as f: - f.write(version) - print("Wrote version into: {}".format(os.path.join(target_dir, "_version_string_"))) + version_file = os.path.join(target_dir, "_version_string_") + with open(version_file, "w") as f: + f.write(version_string) + print(f"Wrote version into: {version_file}") if __name__ == "__main__": print(get_tag_commit()) - print(pupil_version()) + print(pupil_version_string()) From 4cfa8541b2ae0778d8ce6dcab0e26fad2b6ab11e Mon Sep 17 00:00:00 2001 From: Roman Roibu Date: Mon, 24 Aug 2020 09:21:30 +0200 Subject: [PATCH 27/75] Use parse_version to assert and compare version numbers --- pupil_src/launchables/eye.py | 4 +- pupil_src/launchables/player.py | 10 ++-- pupil_src/launchables/service.py | 4 +- pupil_src/launchables/world.py | 6 +- pupil_src/shared_modules/audio_playback.py | 4 +- pupil_src/shared_modules/os_utils.py | 6 +- .../detector_3d_plugin.py | 4 +- .../pupil_recording/__init__.py | 2 +- .../pupil_recording/update/old_style.py | 59 +++++++++---------- .../video_capture/ndsi_backend.py | 4 +- .../video_capture/uvc_backend.py | 4 +- 11 files changed, 53 insertions(+), 54 deletions(-) diff --git a/pupil_src/launchables/eye.py b/pupil_src/launchables/eye.py index 3b09caf8db..84f490bae6 100644 --- a/pupil_src/launchables/eye.py +++ b/pupil_src/launchables/eye.py @@ -141,7 +141,7 @@ def eye( # helpers/utils from uvc import get_time_monotonic from file_methods import Persistent_Dict - from version_utils import VersionFormat + from version_utils import parse_version from methods import normalize, denormalize, timer from av_writer import JPEG_Writer, MPEG_Writer, NonMonotonicTimestampError from ndsi import H264Writer @@ -362,7 +362,7 @@ def on_drop(window, count, paths): session_settings = Persistent_Dict( os.path.join(g_pool.user_dir, "user_settings_eye{}".format(eye_id)) ) - if VersionFormat(session_settings.get("version", "0.0")) != g_pool.version: + if parse_version(session_settings.get("version", "0.0")) != g_pool.version: logger.info( "Session setting are from a different version of this app. I will not use those." ) diff --git a/pupil_src/launchables/player.py b/pupil_src/launchables/player.py index 90f88c14bf..fb26e5822c 100644 --- a/pupil_src/launchables/player.py +++ b/pupil_src/launchables/player.py @@ -83,7 +83,7 @@ def player( from video_capture import File_Source # helpers/utils - from version_utils import VersionFormat + from version_utils import parse_version from methods import normalize, denormalize, delta_t, get_system_info import player_methods as pm from pupil_recording import PupilRecording @@ -131,7 +131,7 @@ def player( InvalidRecordingException, ) - assert VersionFormat(pyglui_version) >= VersionFormat( + assert parse_version(pyglui_version) >= parse_version( "1.28" ), "pyglui out of date, please upgrade to newest version" @@ -346,7 +346,7 @@ def get_dt(): session_settings = Persistent_Dict( os.path.join(user_dir, "user_settings_player") ) - if VersionFormat(session_settings.get("version", "0.0")) != app_version: + if parse_version(session_settings.get("version", "0.0")) != app_version: logger.info( "Session setting are a different version of this app. I will not use those." ) @@ -793,7 +793,7 @@ def player_drop( import glfw import gl_utils from OpenGL.GL import glClearColor - from version_utils import VersionFormat + from version_utils import parse_version from file_methods import Persistent_Dict from pyglui.pyfontstash import fontstash from pyglui.ui import get_roboto_font_path @@ -830,7 +830,7 @@ def on_drop(window, count, paths): session_settings = Persistent_Dict( os.path.join(user_dir, "user_settings_player") ) - if VersionFormat(session_settings.get("version", "0.0")) != app_version: + if parse_version(session_settings.get("version", "0.0")) != app_version: logger.info( "Session setting are from a different version of this app. I will not use those." ) diff --git a/pupil_src/launchables/service.py b/pupil_src/launchables/service.py index 6666c21c51..1b7c6b23e4 100644 --- a/pupil_src/launchables/service.py +++ b/pupil_src/launchables/service.py @@ -88,7 +88,7 @@ def stop_eye_process(eye_id): # helpers/utils from file_methods import Persistent_Dict from methods import delta_t, get_system_info - from version_utils import VersionFormat + from version_utils import parse_version import audio from uvc import get_time_monotonic @@ -183,7 +183,7 @@ def get_dt(): session_settings = Persistent_Dict( os.path.join(g_pool.user_dir, "user_settings_service") ) - if session_settings.get("version", VersionFormat("0.0")) < g_pool.version: + if parse_version(session_settings.get("version", "0.0")) < g_pool.version: logger.info( "Session setting are from older version of this app. I will not use those." ) diff --git a/pupil_src/launchables/world.py b/pupil_src/launchables/world.py index da465533b0..4f3bbfd55f 100644 --- a/pupil_src/launchables/world.py +++ b/pupil_src/launchables/world.py @@ -118,10 +118,10 @@ def detection_enabled_setter(is_on: bool): # display import glfw - from version_utils import VersionFormat + from version_utils import parse_version from pyglui import ui, cygl, __version__ as pyglui_version - assert VersionFormat(pyglui_version) >= VersionFormat( + assert parse_version(pyglui_version) >= parse_version( "1.27" ), "pyglui out of date, please upgrade to newest version" from pyglui.cygl.utils import Named_Texture @@ -441,7 +441,7 @@ def get_dt(): session_settings = Persistent_Dict( os.path.join(g_pool.user_dir, "user_settings_world") ) - if VersionFormat(session_settings.get("version", "0.0")) != g_pool.version: + if parse_version(session_settings.get("version", "0.0")) != g_pool.version: logger.info( "Session setting are from a different version of this app. I will not use those." ) diff --git a/pupil_src/shared_modules/audio_playback.py b/pupil_src/shared_modules/audio_playback.py index 85545a9758..08fa2b80ef 100644 --- a/pupil_src/shared_modules/audio_playback.py +++ b/pupil_src/shared_modules/audio_playback.py @@ -26,10 +26,10 @@ import gl_utils from audio_utils import Audio_Viz_Transform, NoAudioLoadedError, load_audio from plugin import System_Plugin_Base -from version_utils import VersionFormat +from version_utils import parse_version -assert VersionFormat(av.__version__) >= VersionFormat("0.4.4") +assert parse_version(av.__version__) >= parse_version("0.4.4") logger = logging.getLogger(__name__) diff --git a/pupil_src/shared_modules/os_utils.py b/pupil_src/shared_modules/os_utils.py index 1514493e1b..aca3e32aef 100644 --- a/pupil_src/shared_modules/os_utils.py +++ b/pupil_src/shared_modules/os_utils.py @@ -10,8 +10,8 @@ """ import platform, sys, os, time, traceback -from distutils.version import LooseVersion as VersionFormat import subprocess as sp +from version_utils import parse_version import logging @@ -19,8 +19,8 @@ os_name = platform.system() if os_name == "Darwin": - mac_version = VersionFormat(platform.mac_ver()[0]) - min_version = VersionFormat("10.11.0") + mac_version = parse_version(platform.mac_ver()[0]) + min_version = parse_version("10.11.0") if os_name == "Darwin" and mac_version >= min_version: diff --git a/pupil_src/shared_modules/pupil_detector_plugins/detector_3d_plugin.py b/pupil_src/shared_modules/pupil_detector_plugins/detector_3d_plugin.py index 918563b646..9d59c303f1 100644 --- a/pupil_src/shared_modules/pupil_detector_plugins/detector_3d_plugin.py +++ b/pupil_src/shared_modules/pupil_detector_plugins/detector_3d_plugin.py @@ -9,7 +9,7 @@ ---------------------------------------------------------------------------~(*) """ import logging -from distutils.version import LooseVersion as VersionFormat +from version_utils import parse_version import pupil_detectors from pupil_detectors import Detector3D, DetectorBase, Roi @@ -32,7 +32,7 @@ logger = logging.getLogger(__name__) -if VersionFormat(pupil_detectors.__version__) < VersionFormat("1.0.5"): +if parse_version(pupil_detectors.__version__) < parse_version("1.0.5"): msg = ( f"This version of Pupil requires pupil_detectors >= 1.0.5." f" You are running with pupil_detectors == {pupil_detectors.__version__}." diff --git a/pupil_src/shared_modules/pupil_recording/__init__.py b/pupil_src/shared_modules/pupil_recording/__init__.py index 4868d69411..1d15e500db 100644 --- a/pupil_src/shared_modules/pupil_recording/__init__.py +++ b/pupil_src/shared_modules/pupil_recording/__init__.py @@ -9,7 +9,7 @@ ---------------------------------------------------------------------------~(*) """ -from packaging.version import Version +from packaging.version import Version # TODO: Should this use version_utils._Version ? from .info import RecordingInfoFile from .recording import PupilRecording diff --git a/pupil_src/shared_modules/pupil_recording/update/old_style.py b/pupil_src/shared_modules/pupil_recording/update/old_style.py index 60c2c33056..d24e5b9194 100644 --- a/pupil_src/shared_modules/pupil_recording/update/old_style.py +++ b/pupil_src/shared_modules/pupil_recording/update/old_style.py @@ -24,7 +24,7 @@ import csv_utils import file_methods as fm from camera_models import Camera_Model -from version_utils import VersionFormat +from version_utils import parse_version from .. import Version from ..info import RecordingInfoFile @@ -106,60 +106,60 @@ def _update_recording_to_old_style_v1_16(rec_dir): rec_version = _read_rec_version_legacy(meta_info) # Convert python2 to python3 - if rec_version <= VersionFormat("0.8.7"): + if rec_version <= parse_version("0.8.7"): update_recording_bytes_to_unicode(rec_dir) - if rec_version >= VersionFormat("0.7.4"): + if rec_version >= parse_version("0.7.4"): pass - elif rec_version >= VersionFormat("0.7.3"): + elif rec_version >= parse_version("0.7.3"): update_recording_v073_to_v074(rec_dir) - elif rec_version >= VersionFormat("0.5"): + elif rec_version >= parse_version("0.5"): update_recording_v05_to_v074(rec_dir) - elif rec_version >= VersionFormat("0.4"): + elif rec_version >= parse_version("0.4"): update_recording_v04_to_v074(rec_dir) - elif rec_version >= VersionFormat("0.3"): + elif rec_version >= parse_version("0.3"): update_recording_v03_to_v074(rec_dir) else: logger.error("This recording is too old. Sorry.") return # Incremental format updates - if rec_version < VersionFormat("0.8.2"): + if rec_version < parse_version("0.8.2"): update_recording_v074_to_v082(rec_dir) - if rec_version < VersionFormat("0.8.3"): + if rec_version < parse_version("0.8.3"): update_recording_v082_to_v083(rec_dir) - if rec_version < VersionFormat("0.8.6"): + if rec_version < parse_version("0.8.6"): update_recording_v083_to_v086(rec_dir) - if rec_version < VersionFormat("0.8.7"): + if rec_version < parse_version("0.8.7"): update_recording_v086_to_v087(rec_dir) - if rec_version < VersionFormat("0.9.1"): + if rec_version < parse_version("0.9.1"): update_recording_v087_to_v091(rec_dir) - if rec_version < VersionFormat("0.9.3"): + if rec_version < parse_version("0.9.3"): update_recording_v091_to_v093(rec_dir) - if rec_version < VersionFormat("0.9.4"): + if rec_version < parse_version("0.9.4"): update_recording_v093_to_v094(rec_dir) - if rec_version < VersionFormat("0.9.13"): + if rec_version < parse_version("0.9.13"): update_recording_v094_to_v0913(rec_dir) - if rec_version < VersionFormat("1.3"): + if rec_version < parse_version("1.3"): update_recording_v0913_to_v13(rec_dir) - if rec_version < VersionFormat("1.4"): + if rec_version < parse_version("1.4"): update_recording_v13_v14(rec_dir) - if rec_version < VersionFormat("1.8"): + if rec_version < parse_version("1.8"): update_recording_v14_v18(rec_dir) - if rec_version < VersionFormat("1.9"): + if rec_version < parse_version("1.9"): update_recording_v18_v19(rec_dir) - if rec_version < VersionFormat("1.11"): + if rec_version < parse_version("1.11"): update_recording_v19_v111(rec_dir) - if rec_version < VersionFormat("1.13"): + if rec_version < parse_version("1.13"): update_recording_v111_v113(rec_dir) - if rec_version < VersionFormat("1.14"): + if rec_version < parse_version("1.14"): update_recording_v113_v114(rec_dir) - if rec_version < VersionFormat("1.16"): + if rec_version < parse_version("1.16"): update_recording_v114_v116(rec_dir) # How to extend: - # if rec_version < VersionFormat('FUTURE FORMAT'): + # if rec_version < parse_version('FUTURE FORMAT'): # update_recording_v081_to_FUTURE(rec_dir) @@ -725,12 +725,11 @@ def update_recording_v03_to_v074(rec_dir): def _read_rec_version_legacy(meta_info): - version = meta_info.get( + version_string = meta_info.get( "Data Format Version", meta_info["Capture Software Version"] ) - version = "".join( - [c for c in version if c in "1234567890.-"] + version_string = "".join( + [c for c in version_string if c in "1234567890.-"] ) # strip letters in case of legacy version format - version = VersionFormat(version) - logger.debug("Recording version: {}".format(version)) - return version + logger.debug(f"Recording version: {version_string}") + return parse_version(version_string) diff --git a/pupil_src/shared_modules/video_capture/ndsi_backend.py b/pupil_src/shared_modules/video_capture/ndsi_backend.py index 10d9c7100b..20c2116ed0 100644 --- a/pupil_src/shared_modules/video_capture/ndsi_backend.py +++ b/pupil_src/shared_modules/video_capture/ndsi_backend.py @@ -13,8 +13,8 @@ import time import ndsi -from packaging.version import Version from pyglui import ui +from version_utils import parse_version import os_utils from camera_models import Camera_Model @@ -24,7 +24,7 @@ try: from ndsi import __version__ - assert Version(__version__) >= Version("1.3") + assert parse_version(__version__) >= parse_version("1.3") from ndsi import __protocol_version__ except (ImportError, AssertionError): raise Exception("pyndsi version is too old. Please upgrade!") from None diff --git a/pupil_src/shared_modules/video_capture/uvc_backend.py b/pupil_src/shared_modules/video_capture/uvc_backend.py index 1db492a34d..59985dac76 100644 --- a/pupil_src/shared_modules/video_capture/uvc_backend.py +++ b/pupil_src/shared_modules/video_capture/uvc_backend.py @@ -24,13 +24,13 @@ import gl_utils import uvc from camera_models import Camera_Model -from version_utils import VersionFormat +from version_utils import parse_version from .base_backend import Base_Manager, Base_Source, InitialisationError, SourceInfo from .utils import Check_Frame_Stripes, Exposure_Time # check versions for our own depedencies as they are fast-changing -assert VersionFormat(uvc.__version__) >= VersionFormat("0.13") +assert parse_version(uvc.__version__) >= parse_version("0.13") # logging logger = logging.getLogger(__name__) From ea8f966470d3b5a42020638941dcd309f14c7509 Mon Sep 17 00:00:00 2001 From: Patrick Faion Date: Mon, 24 Aug 2020 10:29:33 +0200 Subject: [PATCH 28/75] Remove unused code --- pupil_src/shared_modules/camera_models.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/pupil_src/shared_modules/camera_models.py b/pupil_src/shared_modules/camera_models.py index 10ddc7521f..25f0dbe46c 100644 --- a/pupil_src/shared_modules/camera_models.py +++ b/pupil_src/shared_modules/camera_models.py @@ -289,10 +289,6 @@ def solvePnP( ): ... - @abc.abstractmethod - def save(self, directory: str, custom_name: typing.Optional[str] = None): - ... - subclass_by_cam_type = dict() def __init_subclass__(cls, *args, **kwargs): From c97ed9e9663d7f2979a99c74ba5d854ad95d6e60 Mon Sep 17 00:00:00 2001 From: Patrick Faion Date: Mon, 24 Aug 2020 11:46:55 +0200 Subject: [PATCH 29/75] Cleanup camera_models log messages --- pupil_src/shared_modules/camera_models.py | 21 ++++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) diff --git a/pupil_src/shared_modules/camera_models.py b/pupil_src/shared_modules/camera_models.py index 25f0dbe46c..d03a3c8885 100644 --- a/pupil_src/shared_modules/camera_models.py +++ b/pupil_src/shared_modules/camera_models.py @@ -356,7 +356,7 @@ def from_file(directory, cam_name, resolution): if intrinsics_dict["version"] < __version__: logger.warning("Deprecated camera intrinsics found.") logger.info( - "Please recalculate the camera intrinsics using the Camera " + "Please recalculate the camera intrinsics using the Camera" " Intrinsics Estimation." ) os.rename( @@ -365,12 +365,11 @@ def from_file(directory, cam_name, resolution): ) intrinsics = intrinsics_dict[str(resolution)] - logger.info("Previously recorded intrinsics found and loaded!") + logger.info("Loading previously recorded intrinsics!") except Exception: - logger.info( - "No recorded intrinsics found for camera {} at resolution {}".format( - cam_name, resolution - ) + logger.debug( + f"No recorded intrinsics found for camera {cam_name} at resolution" + f" {resolution}" ) if ( @@ -381,7 +380,15 @@ def from_file(directory, cam_name, resolution): intrinsics = default_intrinsics[cam_name][str(resolution)] else: logger.warning( - "No default intrinsics available! Loading dummy intrinsics!" + f"No camera intrinsics available for camera {cam_name} at" + f" resolution {resolution}!" + ) + logger.warning( + "Loading dummy intrinsics, which might decrease accuracy!" + ) + logger.warning( + "Consider selecting a different resolution, or running the Camera" + " Instrinsics Estimation!" ) return Dummy_Camera(resolution, cam_name) From 76ef15664a7a88717217bdfd2c4c81f80fa5e478 Mon Sep 17 00:00:00 2001 From: Patrick Faion Date: Mon, 24 Aug 2020 18:40:41 +0200 Subject: [PATCH 30/75] Split camera model loading into from file/default --- pupil_src/shared_modules/camera_models.py | 75 +++++++++++++---------- 1 file changed, 44 insertions(+), 31 deletions(-) diff --git a/pupil_src/shared_modules/camera_models.py b/pupil_src/shared_modules/camera_models.py index d03a3c8885..b1aaabf1a1 100644 --- a/pupil_src/shared_modules/camera_models.py +++ b/pupil_src/shared_modules/camera_models.py @@ -12,7 +12,7 @@ import abc import logging import os -import typing +import typing as T import cv2 import numpy as np @@ -255,8 +255,8 @@ def unprojectPoints( def projectPoints( self, object_points, - rvec: typing.Optional[np.ndarray] = None, - tvec: typing.Optional[np.ndarray] = None, + rvec: T.Optional[np.ndarray] = None, + tvec: T.Optional[np.ndarray] = None, use_distortion: bool = True, ): ... @@ -284,8 +284,8 @@ def solvePnP( xy, flags: int = cv2.SOLVEPNP_ITERATIVE, useExtrinsicGuess: bool = False, - rvec: typing.Optional[np.ndarray] = None, - tvec: typing.Optional[np.ndarray] = None, + rvec: T.Optional[np.ndarray] = None, + tvec: T.Optional[np.ndarray] = None, ): ... @@ -338,14 +338,16 @@ def save(self, directory, custom_name=None): ) @staticmethod - def from_file(directory, cam_name, resolution): + def from_file( + directory: str, cam_name: str, resolution: T.Tuple[int] + ) -> "Camera_Model": """ Loads recorded intrinsics for the given camera and resolution. If no recorded - intrinsics are available we fall back to default values. - :param directory: The directory in which to look for the intrinsincs file - :param cam_name: Name of the camera, e.g. 'Pupil Cam 1 ID2' - :param resolution: Camera resolution given as a tuple. - :return: Camera Model Object + intrinsics are available we fall back to default values. If no default values + are available, we use dummy intrinsics. + :param directory: The directory in which to look for the intrinsincs file. + :param cam_name: Name of the camera, e.g. 'Pupil Cam 1 ID2'. + :param resolution: Camera resolution. """ file_path = os.path.join( directory, "{}.intrinsics".format(cam_name.replace(" ", "_")) @@ -365,33 +367,44 @@ def from_file(directory, cam_name, resolution): ) intrinsics = intrinsics_dict[str(resolution)] - logger.info("Loading previously recorded intrinsics!") + logger.info("Loading previously recorded intrinsics...") + return Camera_Model._from_raw_intrinsics(cam_name, resolution, intrinsics) except Exception: logger.debug( f"No recorded intrinsics found for camera {cam_name} at resolution" f" {resolution}" ) + return Camera_Model.from_default(cam_name, resolution) - if ( - cam_name in default_intrinsics - and str(resolution) in default_intrinsics[cam_name] - ): - logger.info("Loading default intrinsics!") - intrinsics = default_intrinsics[cam_name][str(resolution)] - else: - logger.warning( - f"No camera intrinsics available for camera {cam_name} at" - f" resolution {resolution}!" - ) - logger.warning( - "Loading dummy intrinsics, which might decrease accuracy!" - ) - logger.warning( - "Consider selecting a different resolution, or running the Camera" - " Instrinsics Estimation!" - ) - return Dummy_Camera(resolution, cam_name) + @staticmethod + def from_default(cam_name: str, resolution: T.Tuple[int]) -> "Camera_Model": + """ + Loads default intrinsics for the given camera and resolution. If no default + values are available, we use dummy intrinsics. + :param cam_name: Name of the camera, e.g. 'Pupil Cam 1 ID2'. + :param resolution: Camera resolution. + """ + if ( + cam_name in default_intrinsics + and str(resolution) in default_intrinsics[cam_name] + ): + logger.info("Loading default intrinsics!") + intrinsics = default_intrinsics[cam_name][str(resolution)] + return Camera_Model._from_raw_intrinsics(cam_name, resolution, intrinsics) + else: + logger.warning( + f"No camera intrinsics available for camera {cam_name} at" + f" resolution {resolution}!" + ) + logger.warning("Loading dummy intrinsics, which might decrease accuracy!") + logger.warning( + "Consider selecting a different resolution, or running the Camera" + " Instrinsics Estimation!" + ) + return Dummy_Camera(resolution, cam_name) + @staticmethod + def _from_raw_intrinsics(cam_name, resolution, intrinsics): cam_type = intrinsics["cam_type"] if cam_type not in Camera_Model.subclass_by_cam_type: logger.warning( From aa92a0c101017c88e01a3cad6f6321ebacd30505 Mon Sep 17 00:00:00 2001 From: Patrick Faion Date: Mon, 24 Aug 2020 18:41:55 +0200 Subject: [PATCH 31/75] Patch eye intrinsics in existing recordings Introduces new RecordingInfo version v2.3 --- .../pupil_recording/info/__init__.py | 2 + .../info/recording_info_2_3.py | 28 ++++++++++++ .../pupil_recording/update/new_style.py | 43 +++++++++++++++++++ 3 files changed, 73 insertions(+) create mode 100644 pupil_src/shared_modules/pupil_recording/info/recording_info_2_3.py diff --git a/pupil_src/shared_modules/pupil_recording/info/__init__.py b/pupil_src/shared_modules/pupil_recording/info/__init__.py index 986a57f2c7..4f323a2142 100644 --- a/pupil_src/shared_modules/pupil_recording/info/__init__.py +++ b/pupil_src/shared_modules/pupil_recording/info/__init__.py @@ -14,7 +14,9 @@ from .recording_info_2_0 import _RecordingInfoFile_2_0 from .recording_info_2_1 import _RecordingInfoFile_2_1 from .recording_info_2_2 import _RecordingInfoFile_2_2 +from .recording_info_2_3 import _RecordingInfoFile_2_3 RecordingInfoFile.register_child_class(Version("2.0"), _RecordingInfoFile_2_0) RecordingInfoFile.register_child_class(Version("2.1"), _RecordingInfoFile_2_1) RecordingInfoFile.register_child_class(Version("2.2"), _RecordingInfoFile_2_2) +RecordingInfoFile.register_child_class(Version("2.3"), _RecordingInfoFile_2_3) diff --git a/pupil_src/shared_modules/pupil_recording/info/recording_info_2_3.py b/pupil_src/shared_modules/pupil_recording/info/recording_info_2_3.py new file mode 100644 index 0000000000..96f671eb4d --- /dev/null +++ b/pupil_src/shared_modules/pupil_recording/info/recording_info_2_3.py @@ -0,0 +1,28 @@ +""" +(*)~--------------------------------------------------------------------------- +Pupil - eye tracking platform +Copyright (C) 2012-2020 Pupil Labs + +Distributed under the terms of the GNU +Lesser General Public License (LGPL v3.0). +See COPYING and COPYING.LESSER for license details. +---------------------------------------------------------------------------~(*) +""" + +from . import RecordingInfoFile, Version +from .recording_info_2_2 import _RecordingInfoFile_2_2 +from . import recording_info_utils as utils + + +class _RecordingInfoFile_2_3(_RecordingInfoFile_2_2): + @property + def meta_version(self) -> Version: + return Version("2.3") + + @property + def _private_key_schema(self) -> RecordingInfoFile._KeyValueSchema: + return { + **super()._private_key_schema, + # overwrite meta_version key from parent + "meta_version": (utils.validator_version_string, lambda _: "2.3"), + } diff --git a/pupil_src/shared_modules/pupil_recording/update/new_style.py b/pupil_src/shared_modules/pupil_recording/update/new_style.py index 6af0b994d6..6327fc36c4 100644 --- a/pupil_src/shared_modules/pupil_recording/update/new_style.py +++ b/pupil_src/shared_modules/pupil_recording/update/new_style.py @@ -10,8 +10,10 @@ """ import logging +import re from pathlib import Path +import camera_models as cm import file_methods as fm from version_utils import get_version @@ -33,6 +35,8 @@ def recording_update_to_latest_new_style(rec_dir: str): info_file = update_newstyle_20_21(rec_dir) if info_file.meta_version < Version("2.2"): info_file = update_newstyle_21_22(rec_dir) + if info_file.meta_version < Version("2.3"): + info_file = update_newstyle_22_23(rec_dir) def check_min_player_version(info_file: RecordingInfoFile): @@ -100,3 +104,42 @@ def update_newstyle_21_22(rec_dir: str): new_info_file.update_writeable_properties_from(old_info_file) new_info_file.save_file() return new_info_file + + +def update_newstyle_22_23(rec_dir: str): + # Pupil detectors now use eye camera intrinsics. Previously we did not include those + # in the recording, so now the dummy intrinsics would be used. These do not have an + # appropriate focal length value for the 3D pupil detector. Instead we will just add + # all default intrinsics for Cam1 and Cam2 eye cameras to the recording. The correct + # one will be picked according to the resolution later on. This isnot optimal, if + # the recording uses Cam3, but there is no way of inferring this and we just assume + # the probability that it is Cam2 is much higher. + + # Make sure we don't overwrite any existing eye intrinsics, although there should be + # none at this point, except if someone copied them over from a later recording into + # an un-updated recording! + if any((Path(rec_dir) / f"eye{eye_id}.intrinsics").exists() for eye_id in (0, 1)): + logger.error( + "Found recorded eye intrinsics! These must have been copied over manually!" + " Will not patch eye intrinsics automatically!" + ) + else: + for cam, data in cm.default_intrinsics.items(): + match = re.match(r"Pupil Cam[12] ID(?P[01])", cam) + if match is None: + continue + + eye_id = match.group("eye_id") + for resolution in data.keys(): + logger.info(f"Patching eye intrinsics for {cam}{resolution}.") + intrinsics = cm.Camera_Model.from_default(cam, resolution) + intrinsics.save(rec_dir, f"eye{eye_id}") + + # update info file + old_info_file = RecordingInfoFile.read_file_from_recording(rec_dir) + new_info_file = RecordingInfoFile.create_empty_file( + rec_dir, fixed_version=Version("2.3") + ) + new_info_file.update_writeable_properties_from(old_info_file) + new_info_file.save_file() + return new_info_file From 11c5f6feb5951abc689509a9b48cf2cd76c84d65 Mon Sep 17 00:00:00 2001 From: Roman Roibu Date: Tue, 25 Aug 2020 00:41:18 +0200 Subject: [PATCH 32/75] Use regex to match gaze timestamp files --- pupil_src/shared_modules/video_capture/utils.py | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/pupil_src/shared_modules/video_capture/utils.py b/pupil_src/shared_modules/video_capture/utils.py index f814ba35a1..40443bc624 100644 --- a/pupil_src/shared_modules/video_capture/utils.py +++ b/pupil_src/shared_modules/video_capture/utils.py @@ -13,6 +13,7 @@ import pathlib as pl import typing as T from pathlib import Path +import re import av @@ -498,10 +499,12 @@ def load_worn_data(path): # This pattern will match any filename that: # - starts with "gaze ps" # - is followed by one or more digits - # - is followed by "_timestamps.npy" - gaze_timestamp_pattern = "gaze ps[0-9]*_timestamps.npy" + # - ends with "_timestamps.npy" + gaze_timestamp_paths = match_contents_by_name_pattern( + pl.Path(root_dir), "^gaze ps[0-9]+_timestamps.npy$" + ) - for timestamps_path in pl.Path(root_dir).glob(gaze_timestamp_pattern): + for timestamps_path in gaze_timestamp_paths: raw_path = find_raw_path(timestamps_path) timestamps = load_timestamps_data(timestamps_path) raw_data = load_raw_data(raw_path) @@ -526,3 +529,10 @@ def load_worn_data(path): conf_data = (1.0 for _ in range(len(timestamps))) yield from zip(raw_data, timestamps, conf_data) + + +def match_contents_by_name_pattern(parent_dir: Path, name_pattern) -> T.List[Path]: + # Get all non-recursive directory contents + contents = parent_dir.absolute().glob("*") + # Filter content that matches the name by regex pattern + return [c for c in contents if re.match(name_pattern, c.name) is not None] From cd69fbd0fc260936e91c703adfe47ee8200bc456 Mon Sep 17 00:00:00 2001 From: Roman Roibu Date: Tue, 25 Aug 2020 00:42:26 +0200 Subject: [PATCH 33/75] Update version_utils to use packaging.version --- pupil_src/shared_modules/version_utils.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pupil_src/shared_modules/version_utils.py b/pupil_src/shared_modules/version_utils.py index 5c7e800b8a..6c93ff327a 100644 --- a/pupil_src/shared_modules/version_utils.py +++ b/pupil_src/shared_modules/version_utils.py @@ -5,7 +5,7 @@ from subprocess import check_output, CalledProcessError, STDOUT import os, sys -import distutils.version +import packaging.version import typing as T import logging @@ -34,11 +34,11 @@ def get_tag_commit() -> T.Optional[T.AnyStr]: return None -_Version = distutils.version.LooseVersion +_Version = T.Union[packaging.version.LegacyVersion, packaging.version.Version] def parse_version(vstring) -> _Version: - return distutils.version.LooseVersion(vstring) + return packaging.version.parse(vstring) def pupil_version() -> _Version: From 885a7da9fa1f5e2dacc19b0f7a733093760d0547 Mon Sep 17 00:00:00 2001 From: Roman Roibu Date: Tue, 25 Aug 2020 00:43:24 +0200 Subject: [PATCH 34/75] Use version_utils in pupil_recording --- .../pupil_recording/__init__.py | 3 --- .../pupil_recording/info/__init__.py | 9 +++---- .../pupil_recording/info/recording_info.py | 25 ++++++++----------- .../info/recording_info_2_0.py | 11 ++++---- .../info/recording_info_2_1.py | 7 +++--- .../info/recording_info_2_2.py | 11 ++++---- .../info/recording_info_utils.py | 7 +++--- .../pupil_recording/update/invisible.py | 9 ++++--- .../pupil_recording/update/mobile.py | 7 +++--- .../pupil_recording/update/new_style.py | 16 ++++++------ .../pupil_recording/update/old_style.py | 3 +-- 11 files changed, 52 insertions(+), 56 deletions(-) diff --git a/pupil_src/shared_modules/pupil_recording/__init__.py b/pupil_src/shared_modules/pupil_recording/__init__.py index 1d15e500db..760856105d 100644 --- a/pupil_src/shared_modules/pupil_recording/__init__.py +++ b/pupil_src/shared_modules/pupil_recording/__init__.py @@ -8,9 +8,6 @@ See COPYING and COPYING.LESSER for license details. ---------------------------------------------------------------------------~(*) """ - -from packaging.version import Version # TODO: Should this use version_utils._Version ? - from .info import RecordingInfoFile from .recording import PupilRecording from .recording_utils import InvalidRecordingException, assert_valid_recording_type diff --git a/pupil_src/shared_modules/pupil_recording/info/__init__.py b/pupil_src/shared_modules/pupil_recording/info/__init__.py index 986a57f2c7..4aa73d09db 100644 --- a/pupil_src/shared_modules/pupil_recording/info/__init__.py +++ b/pupil_src/shared_modules/pupil_recording/info/__init__.py @@ -8,13 +8,12 @@ See COPYING and COPYING.LESSER for license details. ---------------------------------------------------------------------------~(*) """ - -from .. import Version from .recording_info import RecordingInfoFile from .recording_info_2_0 import _RecordingInfoFile_2_0 from .recording_info_2_1 import _RecordingInfoFile_2_1 from .recording_info_2_2 import _RecordingInfoFile_2_2 +from version_utils import parse_version -RecordingInfoFile.register_child_class(Version("2.0"), _RecordingInfoFile_2_0) -RecordingInfoFile.register_child_class(Version("2.1"), _RecordingInfoFile_2_1) -RecordingInfoFile.register_child_class(Version("2.2"), _RecordingInfoFile_2_2) +RecordingInfoFile.register_child_class(parse_version("2.0"), _RecordingInfoFile_2_0) +RecordingInfoFile.register_child_class(parse_version("2.1"), _RecordingInfoFile_2_1) +RecordingInfoFile.register_child_class(parse_version("2.2"), _RecordingInfoFile_2_2) diff --git a/pupil_src/shared_modules/pupil_recording/info/recording_info.py b/pupil_src/shared_modules/pupil_recording/info/recording_info.py index 99bd380e1d..4a7eb7ddc3 100644 --- a/pupil_src/shared_modules/pupil_recording/info/recording_info.py +++ b/pupil_src/shared_modules/pupil_recording/info/recording_info.py @@ -18,11 +18,10 @@ import typing as T import uuid -from version_utils import get_version +from version_utils import get_version, parse_version, _Version -from .. import Version -__all__ = ["RecordingInfo", "RecordingInfoFile", "RecordingInfoInvalidError", "Version"] +__all__ = ["RecordingInfo", "RecordingInfoFile", "RecordingInfoInvalidError"] logger = logging.getLogger(__name__) @@ -72,12 +71,12 @@ def __len__(self): @property @abc.abstractmethod - def meta_version(self) -> Version: + def meta_version(self) -> _Version: pass @property @abc.abstractmethod - def min_player_version(self) -> Version: + def min_player_version(self) -> _Version: pass @property @@ -377,11 +376,11 @@ def does_recording_contain_info_file(rec_dir: str) -> bool: return os.path.isfile(file_path) @staticmethod - def detect_recording_info_file_version(rec_dir: str) -> Version: + def detect_recording_info_file_version(rec_dir: str) -> _Version: file_path = RecordingInfoFile._info_file_path(rec_dir=rec_dir) with open(file_path, "r") as file: read_dict = RecordingInfoFile._read_dict_from_file(file=file) - return Version(read_dict["meta_version"]) + return parse_version(read_dict["meta_version"]) @staticmethod def read_file_from_recording(rec_dir: str) -> "RecordingInfoFile": @@ -403,7 +402,7 @@ def read_file_from_recording(rec_dir: str) -> "RecordingInfoFile": info_file_path = RecordingInfoFile._info_file_path(rec_dir) with open(info_file_path, "r") as f: info_dict = RecordingInfoFile._read_dict_from_file(f) - min_player_version = Version(info_dict["min_player_version"]) + min_player_version = parse_version(info_dict["min_player_version"]) except Exception as e: # Catching BaseException since at this point we don't know anything logger.error( @@ -415,9 +414,7 @@ def read_file_from_recording(rec_dir: str) -> "RecordingInfoFile": f"Recording is too new to be opened with this version of Player!" ) - # TODO: get_version() returns a LooseVersion, but we are using - # packaging.Version now, need to adjust this across the codebase - if min_player_version > Version(get_version().vstring): + if min_player_version > get_version(): raise RecordingInfoInvalidError( f"This recording requires Player version >= {min_player_version}!" ) @@ -440,7 +437,7 @@ def read_file_from_recording(rec_dir: str) -> "RecordingInfoFile": @staticmethod def create_empty_file( - rec_dir: str, fixed_version: T.Optional[Version] = None + rec_dir: str, fixed_version: T.Optional[_Version] = None ) -> "RecordingInfoFile": """ Creates a new `RecordingInfoFile` instance using the latest meta format version, @@ -483,7 +480,7 @@ def validate(self): _info_file_versions = {} @classmethod - def register_child_class(cls, version: Version, child_class: type): + def register_child_class(cls, version: _Version, child_class: type): """Use this to register interface implementations for specific versions.""" # NOTE: This is dependency inversion to avoids circular imports, because we # don't need to know our child classes. @@ -491,7 +488,7 @@ def register_child_class(cls, version: Version, child_class: type): cls._info_file_versions[version] = child_class @classmethod - def get_latest_info_file_version(cls) -> Version: + def get_latest_info_file_version(cls) -> _Version: if not cls._info_file_versions: raise ValueError( "RecordingInfoFile not correctly initialized! No templates registered." diff --git a/pupil_src/shared_modules/pupil_recording/info/recording_info_2_0.py b/pupil_src/shared_modules/pupil_recording/info/recording_info_2_0.py index 9b5ffb67f5..ecbeba0f21 100644 --- a/pupil_src/shared_modules/pupil_recording/info/recording_info_2_0.py +++ b/pupil_src/shared_modules/pupil_recording/info/recording_info_2_0.py @@ -11,8 +11,9 @@ import uuid -from . import RecordingInfoFile, Version +from . import RecordingInfoFile from . import recording_info_utils as utils +from version_utils import parse_version, _Version class _RecordingInfoFile_2_0(RecordingInfoFile): @@ -20,12 +21,12 @@ class _RecordingInfoFile_2_0(RecordingInfoFile): # RecordingInfo @property - def meta_version(self) -> Version: - return Version("2.0") + def meta_version(self) -> _Version: + return parse_version("2.0") @property - def min_player_version(self) -> Version: - return Version("1.16") + def min_player_version(self) -> _Version: + return parse_version("1.16") @property def recording_uuid(self) -> uuid.UUID: diff --git a/pupil_src/shared_modules/pupil_recording/info/recording_info_2_1.py b/pupil_src/shared_modules/pupil_recording/info/recording_info_2_1.py index d564034878..d8b25654b6 100644 --- a/pupil_src/shared_modules/pupil_recording/info/recording_info_2_1.py +++ b/pupil_src/shared_modules/pupil_recording/info/recording_info_2_1.py @@ -9,9 +9,10 @@ ---------------------------------------------------------------------------~(*) """ -from . import RecordingInfoFile, Version +from . import RecordingInfoFile from .recording_info_2_0 import _RecordingInfoFile_2_0 from . import recording_info_utils as utils +from version_utils import parse_version, _Version class _RecordingInfoFile_2_1(_RecordingInfoFile_2_0): @@ -23,8 +24,8 @@ class _RecordingInfoFile_2_1(_RecordingInfoFile_2_0): # meta version 2.0 have to be re-transformed. @property - def meta_version(self) -> Version: - return Version("2.1") + def meta_version(self) -> _Version: + return parse_version("2.1") @property def _private_key_schema(self) -> RecordingInfoFile._KeyValueSchema: diff --git a/pupil_src/shared_modules/pupil_recording/info/recording_info_2_2.py b/pupil_src/shared_modules/pupil_recording/info/recording_info_2_2.py index f6db8df61b..93223ee412 100644 --- a/pupil_src/shared_modules/pupil_recording/info/recording_info_2_2.py +++ b/pupil_src/shared_modules/pupil_recording/info/recording_info_2_2.py @@ -9,9 +9,10 @@ ---------------------------------------------------------------------------~(*) """ -from . import RecordingInfoFile, Version +from . import RecordingInfoFile from .recording_info_2_0 import _RecordingInfoFile_2_0 from . import recording_info_utils as utils +from version_utils import parse_version, _Version class _RecordingInfoFile_2_2(_RecordingInfoFile_2_0): @@ -19,12 +20,12 @@ class _RecordingInfoFile_2_2(_RecordingInfoFile_2_0): # Used to make Pupil v2.0 recordings backwards incompatible with v1.* @property - def meta_version(self) -> Version: - return Version("2.2") + def meta_version(self) -> _Version: + return parse_version("2.2") @property - def min_player_version(self) -> Version: - return Version("2.0") + def min_player_version(self) -> _Version: + return parse_version("2.0") @property def _private_key_schema(self) -> RecordingInfoFile._KeyValueSchema: diff --git a/pupil_src/shared_modules/pupil_recording/info/recording_info_utils.py b/pupil_src/shared_modules/pupil_recording/info/recording_info_utils.py index f3d631a58c..1bea45042c 100644 --- a/pupil_src/shared_modules/pupil_recording/info/recording_info_utils.py +++ b/pupil_src/shared_modules/pupil_recording/info/recording_info_utils.py @@ -17,10 +17,11 @@ import csv_utils from methods import get_system_info -from .recording_info import RecordingInfoFile, Version +from .recording_info import RecordingInfoFile +from version_utils import parse_version, _Version -def string_from_recording_version(value: Version) -> str: +def string_from_recording_version(value: _Version) -> str: return str(value) # TODO: Make sure this conversion is correct @@ -53,7 +54,7 @@ def default_system_info(info) -> str: def validator_version_string(value: str): - _ = Version(value) + _ = parse_version(value) def validator_uuid_string(value: str): diff --git a/pupil_src/shared_modules/pupil_recording/update/invisible.py b/pupil_src/shared_modules/pupil_recording/update/invisible.py index 1136509b52..3db1b74135 100644 --- a/pupil_src/shared_modules/pupil_recording/update/invisible.py +++ b/pupil_src/shared_modules/pupil_recording/update/invisible.py @@ -19,22 +19,23 @@ import methods as m from video_capture.utils import pi_gaze_items -from .. import Version from ..info import RecordingInfoFile from ..info import recording_info_utils as utils from ..recording import PupilRecording from ..recording_utils import InvalidRecordingException from . import update_utils +from version_utils import parse_version + logger = logging.getLogger(__name__) -NEWEST_SUPPORTED_VERSION = Version("1.3") +NEWEST_SUPPORTED_VERSION = parse_version("1.3") def transform_invisible_to_corresponding_new_style(rec_dir: str): logger.info("Transform Pupil Invisible to new style recording...") info_json = utils.read_info_json_file(rec_dir) - pi_version = Version(info_json["data_format_version"]) + pi_version = parse_version(info_json["data_format_version"]) if pi_version > NEWEST_SUPPORTED_VERSION: raise InvalidRecordingException( @@ -86,7 +87,7 @@ def _generate_pprf_2_1_info_file(rec_dir: str) -> RecordingInfoFile: # Create a recording info file with the new format, # fill out the information, validate, and return. - new_info_file = RecordingInfoFile.create_empty_file(rec_dir, Version("2.1")) + new_info_file = RecordingInfoFile.create_empty_file(rec_dir, parse_version("2.1")) new_info_file.recording_uuid = recording_uuid new_info_file.start_time_system_ns = start_time_system_ns new_info_file.start_time_synced_ns = start_time_synced_ns diff --git a/pupil_src/shared_modules/pupil_recording/update/mobile.py b/pupil_src/shared_modules/pupil_recording/update/mobile.py index 73dcd181a4..dfae939071 100644 --- a/pupil_src/shared_modules/pupil_recording/update/mobile.py +++ b/pupil_src/shared_modules/pupil_recording/update/mobile.py @@ -14,18 +14,19 @@ import uuid from pathlib import Path -from .. import Version from ..info import RecordingInfoFile from ..info import recording_info_utils as utils from ..recording import PupilRecording from ..recording_utils import InvalidRecordingException from . import update_utils +from version_utils import parse_version + # NOTE: Due to Pupil Mobile not having a data format version, we are using the software # version here. The idea is to use Major.Minor specifically. This means that the # software version of Pupil Mobile should can be increased in the patch version part # only if this won't need additional update methods here. -NEXT_UNSUPPORTED_VERSION = Version("1.3") +NEXT_UNSUPPORTED_VERSION = parse_version("1.3") logger = logging.getLogger(__name__) @@ -34,7 +35,7 @@ def transform_mobile_to_corresponding_new_style(rec_dir: str) -> RecordingInfoFi logger.info("Transform Pupil Mobile to new style recording...") info_csv = utils.read_info_csv_file(rec_dir) - mobile_version = Version(info_csv["Capture Software Version"]) + mobile_version = parse_version(info_csv["Capture Software Version"]) if mobile_version >= NEXT_UNSUPPORTED_VERSION: raise InvalidRecordingException( diff --git a/pupil_src/shared_modules/pupil_recording/update/new_style.py b/pupil_src/shared_modules/pupil_recording/update/new_style.py index 6af0b994d6..9a4e5d72fa 100644 --- a/pupil_src/shared_modules/pupil_recording/update/new_style.py +++ b/pupil_src/shared_modules/pupil_recording/update/new_style.py @@ -13,14 +13,14 @@ from pathlib import Path import file_methods as fm -from version_utils import get_version +from version_utils import get_version, parse_version -from .. import Version from ..info import RecordingInfoFile from ..recording import PupilRecording from ..recording_utils import InvalidRecordingException from . import invisible + logger = logging.getLogger(__name__) @@ -29,16 +29,14 @@ def recording_update_to_latest_new_style(rec_dir: str): check_min_player_version(info_file) # incremental upgrade ... - if info_file.meta_version < Version("2.1"): + if info_file.meta_version < parse_version("2.1"): info_file = update_newstyle_20_21(rec_dir) - if info_file.meta_version < Version("2.2"): + if info_file.meta_version < parse_version("2.2"): info_file = update_newstyle_21_22(rec_dir) def check_min_player_version(info_file: RecordingInfoFile): - # TODO: get_version() returns a LooseVersion, but we are using packaging.Version - # now, need to adjust this across the codebase - if info_file.min_player_version > Version(get_version().vstring): + if info_file.min_player_version > get_version(): player_out_of_date = ( "Recording requires a newer version of Player: " f"{info_file.min_player_version}" @@ -82,7 +80,7 @@ def update_newstyle_20_21(rec_dir: str): invisible._convert_gaze(PupilRecording(rec_dir)) # Bump info file version to 2.1 new_info_file = RecordingInfoFile.create_empty_file( - rec_dir, fixed_version=Version("2.1") + rec_dir, fixed_version=parse_version("2.1") ) new_info_file.update_writeable_properties_from(info_file) info_file = new_info_file @@ -95,7 +93,7 @@ def update_newstyle_21_22(rec_dir: str): # Used to make Pupil v2.0 recordings backwards incompatible with v1.x old_info_file = RecordingInfoFile.read_file_from_recording(rec_dir) new_info_file = RecordingInfoFile.create_empty_file( - rec_dir, fixed_version=Version("2.2") + rec_dir, fixed_version=parse_version("2.2") ) new_info_file.update_writeable_properties_from(old_info_file) new_info_file.save_file() diff --git a/pupil_src/shared_modules/pupil_recording/update/old_style.py b/pupil_src/shared_modules/pupil_recording/update/old_style.py index d24e5b9194..1549ff0623 100644 --- a/pupil_src/shared_modules/pupil_recording/update/old_style.py +++ b/pupil_src/shared_modules/pupil_recording/update/old_style.py @@ -26,7 +26,6 @@ from camera_models import Camera_Model from version_utils import parse_version -from .. import Version from ..info import RecordingInfoFile from ..info import recording_info_utils as rec_info_utils from ..recording_utils import InvalidRecordingException @@ -79,7 +78,7 @@ def _generate_pprf_2_0_info_file(rec_dir): # Create a recording info file with the new format, # fill out the information, validate, and return. new_info_file = RecordingInfoFile.create_empty_file( - rec_dir, fixed_version=Version("2.0") + rec_dir, fixed_version=parse_version("2.0") ) new_info_file.recording_uuid = recording_uuid new_info_file.start_time_system_s = start_time_system_s From 6cc275b776a5926f2516c1a92011d58049550b7d Mon Sep 17 00:00:00 2001 From: Roman Roibu Date: Tue, 25 Aug 2020 00:44:26 +0200 Subject: [PATCH 35/75] Remove unused Version import --- pupil_src/shared_modules/recorder.py | 1 - 1 file changed, 1 deletion(-) diff --git a/pupil_src/shared_modules/recorder.py b/pupil_src/shared_modules/recorder.py index bd85813abe..883b72d9dd 100644 --- a/pupil_src/shared_modules/recorder.py +++ b/pupil_src/shared_modules/recorder.py @@ -28,7 +28,6 @@ from methods import get_system_info, timer from video_capture.ndsi_backend import NDSI_Source -from pupil_recording.info import Version from pupil_recording.info import RecordingInfoFile from gaze_mapping.notifications import ( From 91882de9ddd522c12abd62d39cf7fc1084ba8568 Mon Sep 17 00:00:00 2001 From: Patrick Faion Date: Tue, 25 Aug 2020 11:35:46 +0200 Subject: [PATCH 36/75] Use pye3d only if available --- .../pupil_detector_plugins/__init__.py | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/pupil_src/shared_modules/pupil_detector_plugins/__init__.py b/pupil_src/shared_modules/pupil_detector_plugins/__init__.py index 637ab857b9..b4fd357825 100644 --- a/pupil_src/shared_modules/pupil_detector_plugins/__init__.py +++ b/pupil_src/shared_modules/pupil_detector_plugins/__init__.py @@ -14,7 +14,6 @@ from .detector_2d_plugin import Detector2DPlugin from .detector_3d_plugin import Detector3DPlugin from .detector_base_plugin import PupilDetectorPlugin, EVENT_KEY -from .pye3d_plugin import Pye3DPlugin logger = logging.getLogger(__name__) @@ -27,15 +26,17 @@ def available_detector_plugins() -> T.Tuple[ Returns tuple of default2D, default3D, and list of all detectors. """ - all_plugins = [Detector2DPlugin, Detector3DPlugin, Pye3DPlugin] + all_plugins = [Detector2DPlugin, Detector3DPlugin] default2D = Detector2DPlugin - default3D = Pye3DPlugin + default3D = Detector3DPlugin try: - from py3d import Detector3DRefractionPlugin - - all_plugins.append(Detector3DRefractionPlugin) + from .pye3d_plugin import Pye3DPlugin except ImportError: - logging.info("Refraction corrected 3D pupil detector not available") + logging.info("Refraction corrected 3D pupil detector not available!") + else: + logging.info("Using refraction corrected 3D pupil detector.") + all_plugins.append(Pye3DPlugin) + default3D = Pye3DPlugin return default2D, default3D, all_plugins From 3a8bb6cb860c20dc60b87bc2bce90290a03d23f4 Mon Sep 17 00:00:00 2001 From: Patrick Faion Date: Tue, 25 Aug 2020 11:55:10 +0200 Subject: [PATCH 37/75] Fix incorrect interface for detector plugin base --- .../pupil_detector_plugins/detector_base_plugin.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pupil_src/shared_modules/pupil_detector_plugins/detector_base_plugin.py b/pupil_src/shared_modules/pupil_detector_plugins/detector_base_plugin.py index a2e46fd2cb..c7ca6d2e24 100644 --- a/pupil_src/shared_modules/pupil_detector_plugins/detector_base_plugin.py +++ b/pupil_src/shared_modules/pupil_detector_plugins/detector_base_plugin.py @@ -117,7 +117,7 @@ def recent_events(self, event): self._recent_detection_result = detection_result @abc.abstractmethod - def detect(self, frame, pupil_data): + def detect(self, frame, **kwargs): pass def on_notify(self, notification): From 802544a2b9ef60deeba09aa9f52482c11c01bdbe Mon Sep 17 00:00:00 2001 From: Patrick Faion Date: Tue, 25 Aug 2020 14:14:08 +0200 Subject: [PATCH 38/75] Apply black formatting --- .../pupil_detector_plugins/visualizer_pye3d.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/pupil_src/shared_modules/pupil_detector_plugins/visualizer_pye3d.py b/pupil_src/shared_modules/pupil_detector_plugins/visualizer_pye3d.py index e188b54bbf..357834cf43 100644 --- a/pupil_src/shared_modules/pupil_detector_plugins/visualizer_pye3d.py +++ b/pupil_src/shared_modules/pupil_detector_plugins/visualizer_pye3d.py @@ -57,7 +57,7 @@ def get_adjusted_pixel_space_matrix(self, scale): temp[3, 3] *= scale return temp - def get_image_space_matrix(self, scale=1.): + def get_image_space_matrix(self, scale=1.0): temp = self.get_adjusted_pixel_space_matrix(scale) temp[1, 1] *= -1 # image origin is top left temp[0, 3] = -self.image_width / 2.0 @@ -162,7 +162,9 @@ def draw_residuals(self, result): vertices = list( zip( - np.clip((np.asarray(result["debug_info"]["angles"]) - 10 ) / 40.0 , 0, 1), + np.clip( + (np.asarray(result["debug_info"]["angles"]) - 10) / 40.0, 0, 1 + ), np.clip( np.log10(np.array(result["debug_info"]["residuals"])) + 2, 0.1, @@ -190,7 +192,7 @@ def draw_Dierkes_lines(self, result): glPushMatrix() glMatrixMode(GL_MODELVIEW) - glColor4f(1.0, 0., 0., 0.1) + glColor4f(1.0, 0.0, 0.0, 0.1) glLineWidth(1.0) for line in result["debug_info"]["Dierkes_lines"][::4]: glBegin(GL_LINES) From 5fb60b15bd49f8e6566ea98af1ad3ce6a3574bc1 Mon Sep 17 00:00:00 2001 From: Pablo Prietz Date: Tue, 25 Aug 2020 17:15:33 +0200 Subject: [PATCH 39/75] Use r-strings for regex Co-authored-by: Patrick Faion --- pupil_src/shared_modules/video_capture/utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pupil_src/shared_modules/video_capture/utils.py b/pupil_src/shared_modules/video_capture/utils.py index 40443bc624..72250295dc 100644 --- a/pupil_src/shared_modules/video_capture/utils.py +++ b/pupil_src/shared_modules/video_capture/utils.py @@ -501,7 +501,7 @@ def load_worn_data(path): # - is followed by one or more digits # - ends with "_timestamps.npy" gaze_timestamp_paths = match_contents_by_name_pattern( - pl.Path(root_dir), "^gaze ps[0-9]+_timestamps.npy$" + pl.Path(root_dir), r"^gaze ps[0-9]+_timestamps.npy$" ) for timestamps_path in gaze_timestamp_paths: From e348ce27d62ac0063a6931076e814733b6913efd Mon Sep 17 00:00:00 2001 From: Patrick Faion Date: Wed, 26 Aug 2020 09:30:35 +0200 Subject: [PATCH 40/75] Add comment about preliminary eye intrinsics --- pupil_src/shared_modules/camera_models.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/pupil_src/shared_modules/camera_models.py b/pupil_src/shared_modules/camera_models.py index b1aaabf1a1..d79f375474 100644 --- a/pupil_src/shared_modules/camera_models.py +++ b/pupil_src/shared_modules/camera_models.py @@ -153,6 +153,10 @@ } # Add measured intrinsics for the eyes (once for each ID for easy lookup) +# TODO: From these intrinsics only the focal lengths were measured. The principal points +# are just default values (half the resolution) and there's no distortion model for now. +# At some later point we should measure the full intrinsics and replace existing +# intrinsics with a recording upgrade. for eye_id in (0, 1): default_intrinsics.update( { From c584b832d9c9a6e2c03d94a9dca2a7fba04db39c Mon Sep 17 00:00:00 2001 From: Patrick Faion Date: Wed, 26 Aug 2020 14:23:05 +0200 Subject: [PATCH 41/75] Bump pupil-detectors requirement to 1.1.1 --- .../pupil_detector_plugins/detector_3d_plugin.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pupil_src/shared_modules/pupil_detector_plugins/detector_3d_plugin.py b/pupil_src/shared_modules/pupil_detector_plugins/detector_3d_plugin.py index 171c283609..eb004a474e 100644 --- a/pupil_src/shared_modules/pupil_detector_plugins/detector_3d_plugin.py +++ b/pupil_src/shared_modules/pupil_detector_plugins/detector_3d_plugin.py @@ -32,9 +32,9 @@ logger = logging.getLogger(__name__) -if VersionFormat(pupil_detectors.__version__) < VersionFormat("1.0.5"): +if VersionFormat(pupil_detectors.__version__) < VersionFormat("1.1.1"): msg = ( - f"This version of Pupil requires pupil_detectors >= 1.0.5." + f"This version of Pupil requires pupil_detectors >= 1.1.1." f" You are running with pupil_detectors == {pupil_detectors.__version__}." f" Please upgrade to a newer version!" ) From 25686a40665f30f5268741ed48092b057ba33faf Mon Sep 17 00:00:00 2001 From: Roman Roibu Date: Wed, 26 Aug 2020 15:00:27 +0200 Subject: [PATCH 42/75] Rename version_utils._Version to SemanticVersion --- .../pupil_recording/info/recording_info.py | 14 +++++++------- .../pupil_recording/info/recording_info_2_0.py | 6 +++--- .../pupil_recording/info/recording_info_2_1.py | 4 ++-- .../pupil_recording/info/recording_info_2_2.py | 6 +++--- .../pupil_recording/info/recording_info_utils.py | 4 ++-- pupil_src/shared_modules/version_utils.py | 6 +++--- 6 files changed, 20 insertions(+), 20 deletions(-) diff --git a/pupil_src/shared_modules/pupil_recording/info/recording_info.py b/pupil_src/shared_modules/pupil_recording/info/recording_info.py index 4a7eb7ddc3..c98b95295a 100644 --- a/pupil_src/shared_modules/pupil_recording/info/recording_info.py +++ b/pupil_src/shared_modules/pupil_recording/info/recording_info.py @@ -18,7 +18,7 @@ import typing as T import uuid -from version_utils import get_version, parse_version, _Version +from version_utils import get_version, parse_version, SemanticVersion __all__ = ["RecordingInfo", "RecordingInfoFile", "RecordingInfoInvalidError"] @@ -71,12 +71,12 @@ def __len__(self): @property @abc.abstractmethod - def meta_version(self) -> _Version: + def meta_version(self) -> SemanticVersion: pass @property @abc.abstractmethod - def min_player_version(self) -> _Version: + def min_player_version(self) -> SemanticVersion: pass @property @@ -376,7 +376,7 @@ def does_recording_contain_info_file(rec_dir: str) -> bool: return os.path.isfile(file_path) @staticmethod - def detect_recording_info_file_version(rec_dir: str) -> _Version: + def detect_recording_info_file_version(rec_dir: str) -> SemanticVersion: file_path = RecordingInfoFile._info_file_path(rec_dir=rec_dir) with open(file_path, "r") as file: read_dict = RecordingInfoFile._read_dict_from_file(file=file) @@ -437,7 +437,7 @@ def read_file_from_recording(rec_dir: str) -> "RecordingInfoFile": @staticmethod def create_empty_file( - rec_dir: str, fixed_version: T.Optional[_Version] = None + rec_dir: str, fixed_version: T.Optional[SemanticVersion] = None ) -> "RecordingInfoFile": """ Creates a new `RecordingInfoFile` instance using the latest meta format version, @@ -480,7 +480,7 @@ def validate(self): _info_file_versions = {} @classmethod - def register_child_class(cls, version: _Version, child_class: type): + def register_child_class(cls, version: SemanticVersion, child_class: type): """Use this to register interface implementations for specific versions.""" # NOTE: This is dependency inversion to avoids circular imports, because we # don't need to know our child classes. @@ -488,7 +488,7 @@ def register_child_class(cls, version: _Version, child_class: type): cls._info_file_versions[version] = child_class @classmethod - def get_latest_info_file_version(cls) -> _Version: + def get_latest_info_file_version(cls) -> SemanticVersion: if not cls._info_file_versions: raise ValueError( "RecordingInfoFile not correctly initialized! No templates registered." diff --git a/pupil_src/shared_modules/pupil_recording/info/recording_info_2_0.py b/pupil_src/shared_modules/pupil_recording/info/recording_info_2_0.py index ecbeba0f21..c548a835ee 100644 --- a/pupil_src/shared_modules/pupil_recording/info/recording_info_2_0.py +++ b/pupil_src/shared_modules/pupil_recording/info/recording_info_2_0.py @@ -13,7 +13,7 @@ from . import RecordingInfoFile from . import recording_info_utils as utils -from version_utils import parse_version, _Version +from version_utils import parse_version, SemanticVersion class _RecordingInfoFile_2_0(RecordingInfoFile): @@ -21,11 +21,11 @@ class _RecordingInfoFile_2_0(RecordingInfoFile): # RecordingInfo @property - def meta_version(self) -> _Version: + def meta_version(self) -> SemanticVersion: return parse_version("2.0") @property - def min_player_version(self) -> _Version: + def min_player_version(self) -> SemanticVersion: return parse_version("1.16") @property diff --git a/pupil_src/shared_modules/pupil_recording/info/recording_info_2_1.py b/pupil_src/shared_modules/pupil_recording/info/recording_info_2_1.py index d8b25654b6..1af62defe6 100644 --- a/pupil_src/shared_modules/pupil_recording/info/recording_info_2_1.py +++ b/pupil_src/shared_modules/pupil_recording/info/recording_info_2_1.py @@ -12,7 +12,7 @@ from . import RecordingInfoFile from .recording_info_2_0 import _RecordingInfoFile_2_0 from . import recording_info_utils as utils -from version_utils import parse_version, _Version +from version_utils import parse_version, SemanticVersion class _RecordingInfoFile_2_1(_RecordingInfoFile_2_0): @@ -24,7 +24,7 @@ class _RecordingInfoFile_2_1(_RecordingInfoFile_2_0): # meta version 2.0 have to be re-transformed. @property - def meta_version(self) -> _Version: + def meta_version(self) -> SemanticVersion: return parse_version("2.1") @property diff --git a/pupil_src/shared_modules/pupil_recording/info/recording_info_2_2.py b/pupil_src/shared_modules/pupil_recording/info/recording_info_2_2.py index 93223ee412..15f5d384b1 100644 --- a/pupil_src/shared_modules/pupil_recording/info/recording_info_2_2.py +++ b/pupil_src/shared_modules/pupil_recording/info/recording_info_2_2.py @@ -12,7 +12,7 @@ from . import RecordingInfoFile from .recording_info_2_0 import _RecordingInfoFile_2_0 from . import recording_info_utils as utils -from version_utils import parse_version, _Version +from version_utils import parse_version, SemanticVersion class _RecordingInfoFile_2_2(_RecordingInfoFile_2_0): @@ -20,11 +20,11 @@ class _RecordingInfoFile_2_2(_RecordingInfoFile_2_0): # Used to make Pupil v2.0 recordings backwards incompatible with v1.* @property - def meta_version(self) -> _Version: + def meta_version(self) -> SemanticVersion: return parse_version("2.2") @property - def min_player_version(self) -> _Version: + def min_player_version(self) -> SemanticVersion: return parse_version("2.0") @property diff --git a/pupil_src/shared_modules/pupil_recording/info/recording_info_utils.py b/pupil_src/shared_modules/pupil_recording/info/recording_info_utils.py index 1bea45042c..f5995058ff 100644 --- a/pupil_src/shared_modules/pupil_recording/info/recording_info_utils.py +++ b/pupil_src/shared_modules/pupil_recording/info/recording_info_utils.py @@ -18,10 +18,10 @@ from methods import get_system_info from .recording_info import RecordingInfoFile -from version_utils import parse_version, _Version +from version_utils import parse_version, SemanticVersion -def string_from_recording_version(value: _Version) -> str: +def string_from_recording_version(value: SemanticVersion) -> str: return str(value) # TODO: Make sure this conversion is correct diff --git a/pupil_src/shared_modules/version_utils.py b/pupil_src/shared_modules/version_utils.py index 6c93ff327a..bc3e8cf4c2 100644 --- a/pupil_src/shared_modules/version_utils.py +++ b/pupil_src/shared_modules/version_utils.py @@ -34,14 +34,14 @@ def get_tag_commit() -> T.Optional[T.AnyStr]: return None -_Version = T.Union[packaging.version.LegacyVersion, packaging.version.Version] +SemanticVersion = T.Union[packaging.version.LegacyVersion, packaging.version.Version] -def parse_version(vstring) -> _Version: +def parse_version(vstring) -> SemanticVersion: return packaging.version.parse(vstring) -def pupil_version() -> _Version: +def pupil_version() -> SemanticVersion: return parse_version(pupil_version_string()) From 66c975ad41d21a54700a3d1cbd929b4a1cd968d0 Mon Sep 17 00:00:00 2001 From: Roman Roibu Date: Wed, 26 Aug 2020 15:14:26 +0200 Subject: [PATCH 43/75] Use Path.is_file and Path.iterdir to iterate directory file paths --- pupil_src/shared_modules/video_capture/utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pupil_src/shared_modules/video_capture/utils.py b/pupil_src/shared_modules/video_capture/utils.py index 72250295dc..3843997a33 100644 --- a/pupil_src/shared_modules/video_capture/utils.py +++ b/pupil_src/shared_modules/video_capture/utils.py @@ -533,6 +533,6 @@ def load_worn_data(path): def match_contents_by_name_pattern(parent_dir: Path, name_pattern) -> T.List[Path]: # Get all non-recursive directory contents - contents = parent_dir.absolute().glob("*") + contents = filter(Path.is_file, parent_dir.iterdir()) # Filter content that matches the name by regex pattern return [c for c in contents if re.match(name_pattern, c.name) is not None] From 9ba46806eafbf7a7d5c540bff4f69faff1d05448 Mon Sep 17 00:00:00 2001 From: Roman Roibu Date: Wed, 26 Aug 2020 15:14:41 +0200 Subject: [PATCH 44/75] Rename match_contents_by_name_pattern to matched_files_by_name_pattern --- pupil_src/shared_modules/video_capture/utils.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pupil_src/shared_modules/video_capture/utils.py b/pupil_src/shared_modules/video_capture/utils.py index 3843997a33..f4879f660c 100644 --- a/pupil_src/shared_modules/video_capture/utils.py +++ b/pupil_src/shared_modules/video_capture/utils.py @@ -500,7 +500,7 @@ def load_worn_data(path): # - starts with "gaze ps" # - is followed by one or more digits # - ends with "_timestamps.npy" - gaze_timestamp_paths = match_contents_by_name_pattern( + gaze_timestamp_paths = matched_files_by_name_pattern( pl.Path(root_dir), r"^gaze ps[0-9]+_timestamps.npy$" ) @@ -531,7 +531,7 @@ def load_worn_data(path): yield from zip(raw_data, timestamps, conf_data) -def match_contents_by_name_pattern(parent_dir: Path, name_pattern) -> T.List[Path]: +def matched_files_by_name_pattern(parent_dir: Path, name_pattern: str) -> T.List[Path]: # Get all non-recursive directory contents contents = filter(Path.is_file, parent_dir.iterdir()) # Filter content that matches the name by regex pattern From 93ccc706af66bc055104b246432824818b858b59 Mon Sep 17 00:00:00 2001 From: Roman Roibu Date: Mon, 31 Aug 2020 09:31:35 +0200 Subject: [PATCH 45/75] Render timeline when calibration selection changes --- .../gaze_producer/controller/gaze_mapper_controller.py | 3 +++ .../shared_modules/gaze_producer/ui/gaze_mapper_menu.py | 6 ++++++ .../shared_modules/gaze_producer/ui/gaze_mapper_timeline.py | 6 ++++++ 3 files changed, 15 insertions(+) diff --git a/pupil_src/shared_modules/gaze_producer/controller/gaze_mapper_controller.py b/pupil_src/shared_modules/gaze_producer/controller/gaze_mapper_controller.py index cbed9189d1..53c5d0b5c7 100644 --- a/pupil_src/shared_modules/gaze_producer/controller/gaze_mapper_controller.py +++ b/pupil_src/shared_modules/gaze_producer/controller/gaze_mapper_controller.py @@ -47,6 +47,9 @@ def set_mapping_range_from_current_trim_marks(self, gaze_mapper): def set_validation_range_from_current_trim_marks(self, gaze_mapper): gaze_mapper.validation_index_range = self._get_current_trim_mark_range() + def set_calibration_unique_id(self, gaze_mapper, calibration_unique_id): + gaze_mapper.calibration_unique_id = calibration_unique_id + def calculate(self, gaze_mapper): self._reset_gaze_mapper_results(gaze_mapper) calibration = self.get_valid_calibration_or_none(gaze_mapper) diff --git a/pupil_src/shared_modules/gaze_producer/ui/gaze_mapper_menu.py b/pupil_src/shared_modules/gaze_producer/ui/gaze_mapper_menu.py index 6c4f1cee72..4f97feecd4 100644 --- a/pupil_src/shared_modules/gaze_producer/ui/gaze_mapper_menu.py +++ b/pupil_src/shared_modules/gaze_producer/ui/gaze_mapper_menu.py @@ -95,12 +95,18 @@ def _create_calibration_selector(self, gaze_mapper): labels.append("[Invalid Calibration]") selection.append(gaze_mapper.calibration_unique_id) + def calibration_setter(calibration_unique_id): + self._gaze_mapper_controller.set_calibration_unique_id( + gaze_mapper, calibration_unique_id + ) + return ui.Selector( "calibration_unique_id", gaze_mapper, label="Calibration", selection=selection, labels=labels, + setter=calibration_setter, ) def _create_mapping_range_selector(self, gaze_mapper): diff --git a/pupil_src/shared_modules/gaze_producer/ui/gaze_mapper_timeline.py b/pupil_src/shared_modules/gaze_producer/ui/gaze_mapper_timeline.py index 78fd47673c..0953e603f6 100644 --- a/pupil_src/shared_modules/gaze_producer/ui/gaze_mapper_timeline.py +++ b/pupil_src/shared_modules/gaze_producer/ui/gaze_mapper_timeline.py @@ -48,6 +48,9 @@ def __init__( self._gaze_mapper_controller.add_observer( "publish_all_enabled_mappers", self._on_publish_enabled_mappers ) + self._gaze_mapper_controller.add_observer( + "set_calibration_unique_id", self._on_calibration_unique_id_changed, + ) self._calibration_controller.add_observer( "set_calibration_range_from_current_trim_marks", @@ -120,3 +123,6 @@ def _on_calibration_range_changed(self, _): def _on_calibration_deleted(self, _): # the deleted calibration might be used by one of the gaze mappers self.render_parent_timeline() + + def _on_calibration_unique_id_changed(self, _1, _2): + self.render_parent_timeline() From 39f6f4ec104f110106ba78bba39147678de62008 Mon Sep 17 00:00:00 2001 From: Roman Roibu Date: Tue, 1 Sep 2020 09:56:11 +0200 Subject: [PATCH 46/75] Rename SemanticVersion to ParsedVersion --- .../pupil_recording/info/recording_info.py | 14 +++++++------- .../pupil_recording/info/recording_info_2_0.py | 6 +++--- .../pupil_recording/info/recording_info_2_1.py | 4 ++-- .../pupil_recording/info/recording_info_2_2.py | 6 +++--- .../pupil_recording/info/recording_info_utils.py | 4 ++-- pupil_src/shared_modules/version_utils.py | 6 +++--- 6 files changed, 20 insertions(+), 20 deletions(-) diff --git a/pupil_src/shared_modules/pupil_recording/info/recording_info.py b/pupil_src/shared_modules/pupil_recording/info/recording_info.py index c98b95295a..29fafd96bb 100644 --- a/pupil_src/shared_modules/pupil_recording/info/recording_info.py +++ b/pupil_src/shared_modules/pupil_recording/info/recording_info.py @@ -18,7 +18,7 @@ import typing as T import uuid -from version_utils import get_version, parse_version, SemanticVersion +from version_utils import get_version, parse_version, ParsedVersion __all__ = ["RecordingInfo", "RecordingInfoFile", "RecordingInfoInvalidError"] @@ -71,12 +71,12 @@ def __len__(self): @property @abc.abstractmethod - def meta_version(self) -> SemanticVersion: + def meta_version(self) -> ParsedVersion: pass @property @abc.abstractmethod - def min_player_version(self) -> SemanticVersion: + def min_player_version(self) -> ParsedVersion: pass @property @@ -376,7 +376,7 @@ def does_recording_contain_info_file(rec_dir: str) -> bool: return os.path.isfile(file_path) @staticmethod - def detect_recording_info_file_version(rec_dir: str) -> SemanticVersion: + def detect_recording_info_file_version(rec_dir: str) -> ParsedVersion: file_path = RecordingInfoFile._info_file_path(rec_dir=rec_dir) with open(file_path, "r") as file: read_dict = RecordingInfoFile._read_dict_from_file(file=file) @@ -437,7 +437,7 @@ def read_file_from_recording(rec_dir: str) -> "RecordingInfoFile": @staticmethod def create_empty_file( - rec_dir: str, fixed_version: T.Optional[SemanticVersion] = None + rec_dir: str, fixed_version: T.Optional[ParsedVersion] = None ) -> "RecordingInfoFile": """ Creates a new `RecordingInfoFile` instance using the latest meta format version, @@ -480,7 +480,7 @@ def validate(self): _info_file_versions = {} @classmethod - def register_child_class(cls, version: SemanticVersion, child_class: type): + def register_child_class(cls, version: ParsedVersion, child_class: type): """Use this to register interface implementations for specific versions.""" # NOTE: This is dependency inversion to avoids circular imports, because we # don't need to know our child classes. @@ -488,7 +488,7 @@ def register_child_class(cls, version: SemanticVersion, child_class: type): cls._info_file_versions[version] = child_class @classmethod - def get_latest_info_file_version(cls) -> SemanticVersion: + def get_latest_info_file_version(cls) -> ParsedVersion: if not cls._info_file_versions: raise ValueError( "RecordingInfoFile not correctly initialized! No templates registered." diff --git a/pupil_src/shared_modules/pupil_recording/info/recording_info_2_0.py b/pupil_src/shared_modules/pupil_recording/info/recording_info_2_0.py index c548a835ee..be8d2513be 100644 --- a/pupil_src/shared_modules/pupil_recording/info/recording_info_2_0.py +++ b/pupil_src/shared_modules/pupil_recording/info/recording_info_2_0.py @@ -13,7 +13,7 @@ from . import RecordingInfoFile from . import recording_info_utils as utils -from version_utils import parse_version, SemanticVersion +from version_utils import parse_version, ParsedVersion class _RecordingInfoFile_2_0(RecordingInfoFile): @@ -21,11 +21,11 @@ class _RecordingInfoFile_2_0(RecordingInfoFile): # RecordingInfo @property - def meta_version(self) -> SemanticVersion: + def meta_version(self) -> ParsedVersion: return parse_version("2.0") @property - def min_player_version(self) -> SemanticVersion: + def min_player_version(self) -> ParsedVersion: return parse_version("1.16") @property diff --git a/pupil_src/shared_modules/pupil_recording/info/recording_info_2_1.py b/pupil_src/shared_modules/pupil_recording/info/recording_info_2_1.py index 1af62defe6..459e7cadca 100644 --- a/pupil_src/shared_modules/pupil_recording/info/recording_info_2_1.py +++ b/pupil_src/shared_modules/pupil_recording/info/recording_info_2_1.py @@ -12,7 +12,7 @@ from . import RecordingInfoFile from .recording_info_2_0 import _RecordingInfoFile_2_0 from . import recording_info_utils as utils -from version_utils import parse_version, SemanticVersion +from version_utils import parse_version, ParsedVersion class _RecordingInfoFile_2_1(_RecordingInfoFile_2_0): @@ -24,7 +24,7 @@ class _RecordingInfoFile_2_1(_RecordingInfoFile_2_0): # meta version 2.0 have to be re-transformed. @property - def meta_version(self) -> SemanticVersion: + def meta_version(self) -> ParsedVersion: return parse_version("2.1") @property diff --git a/pupil_src/shared_modules/pupil_recording/info/recording_info_2_2.py b/pupil_src/shared_modules/pupil_recording/info/recording_info_2_2.py index 15f5d384b1..2fb6b0c3a0 100644 --- a/pupil_src/shared_modules/pupil_recording/info/recording_info_2_2.py +++ b/pupil_src/shared_modules/pupil_recording/info/recording_info_2_2.py @@ -12,7 +12,7 @@ from . import RecordingInfoFile from .recording_info_2_0 import _RecordingInfoFile_2_0 from . import recording_info_utils as utils -from version_utils import parse_version, SemanticVersion +from version_utils import parse_version, ParsedVersion class _RecordingInfoFile_2_2(_RecordingInfoFile_2_0): @@ -20,11 +20,11 @@ class _RecordingInfoFile_2_2(_RecordingInfoFile_2_0): # Used to make Pupil v2.0 recordings backwards incompatible with v1.* @property - def meta_version(self) -> SemanticVersion: + def meta_version(self) -> ParsedVersion: return parse_version("2.2") @property - def min_player_version(self) -> SemanticVersion: + def min_player_version(self) -> ParsedVersion: return parse_version("2.0") @property diff --git a/pupil_src/shared_modules/pupil_recording/info/recording_info_utils.py b/pupil_src/shared_modules/pupil_recording/info/recording_info_utils.py index f5995058ff..43952b2a5c 100644 --- a/pupil_src/shared_modules/pupil_recording/info/recording_info_utils.py +++ b/pupil_src/shared_modules/pupil_recording/info/recording_info_utils.py @@ -18,10 +18,10 @@ from methods import get_system_info from .recording_info import RecordingInfoFile -from version_utils import parse_version, SemanticVersion +from version_utils import parse_version, ParsedVersion -def string_from_recording_version(value: SemanticVersion) -> str: +def string_from_recording_version(value: ParsedVersion) -> str: return str(value) # TODO: Make sure this conversion is correct diff --git a/pupil_src/shared_modules/version_utils.py b/pupil_src/shared_modules/version_utils.py index bc3e8cf4c2..064d48f9fc 100644 --- a/pupil_src/shared_modules/version_utils.py +++ b/pupil_src/shared_modules/version_utils.py @@ -34,14 +34,14 @@ def get_tag_commit() -> T.Optional[T.AnyStr]: return None -SemanticVersion = T.Union[packaging.version.LegacyVersion, packaging.version.Version] +ParsedVersion = T.Union[packaging.version.LegacyVersion, packaging.version.Version] -def parse_version(vstring) -> SemanticVersion: +def parse_version(vstring) -> ParsedVersion: return packaging.version.parse(vstring) -def pupil_version() -> SemanticVersion: +def pupil_version() -> ParsedVersion: return parse_version(pupil_version_string()) From 672db2f5486dc115e4f9d9417fe56f6fbea2490d Mon Sep 17 00:00:00 2001 From: Patrick Faion Date: Wed, 2 Sep 2020 09:25:49 +0200 Subject: [PATCH 47/75] Remove dead code --- pupil_src/shared_modules/camera_models.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/pupil_src/shared_modules/camera_models.py b/pupil_src/shared_modules/camera_models.py index e02a0c157a..8203ad7774 100644 --- a/pupil_src/shared_modules/camera_models.py +++ b/pupil_src/shared_modules/camera_models.py @@ -216,10 +216,6 @@ def solvePnP( ): ... - @abc.abstractmethod - def save(self, directory: str, custom_name: typing.Optional[str] = None): - ... - subclass_by_cam_type = dict() def __init_subclass__(cls, *args, **kwargs): From 3033674fc12de867af9774168c9fb106fc3626cf Mon Sep 17 00:00:00 2001 From: Patrick Faion Date: Wed, 2 Sep 2020 09:26:34 +0200 Subject: [PATCH 48/75] Make camera model constructors consistent This fixes a bug where loading dummy intrinsics from file would crash because the Dummy_Camera constructor does not reasonably overwrite the parent constructor. I moved name and resolution to from for all constructors as these are the least likely to change and adjusted the Dummy_Camera contructor to be consistent with the others. --- .../camera_intrinsics_estimation.py | 4 ++-- pupil_src/shared_modules/camera_models.py | 20 +++++++++---------- .../video_capture/hmd_streaming.py | 4 ++-- 3 files changed, 14 insertions(+), 14 deletions(-) diff --git a/pupil_src/shared_modules/camera_intrinsics_estimation.py b/pupil_src/shared_modules/camera_intrinsics_estimation.py index 5ab0f01352..7758b6a5f7 100644 --- a/pupil_src/shared_modules/camera_intrinsics_estimation.py +++ b/pupil_src/shared_modules/camera_intrinsics_estimation.py @@ -277,7 +277,7 @@ def calculate(self): (cv2.TERM_CRITERIA_EPS + cv2.TERM_CRITERIA_MAX_ITER, max_iter, eps), ) camera_model = Fisheye_Dist_Camera( - camera_matrix, dist_coefs, img_shape, self.g_pool.capture.name + self.g_pool.capture.name, img_shape, camera_matrix, dist_coefs ) elif self.dist_mode == "Radial": rms, camera_matrix, dist_coefs, rvecs, tvecs = cv2.calibrateCamera( @@ -288,7 +288,7 @@ def calculate(self): None, ) camera_model = Radial_Dist_Camera( - camera_matrix, dist_coefs, img_shape, self.g_pool.capture.name + self.g_pool.capture.name, img_shape, camera_matrix, dist_coefs ) else: raise ValueError("Unkown distortion model: {}".format(self.dist_mode)) diff --git a/pupil_src/shared_modules/camera_models.py b/pupil_src/shared_modules/camera_models.py index 8203ad7774..77de78b36e 100644 --- a/pupil_src/shared_modules/camera_models.py +++ b/pupil_src/shared_modules/camera_models.py @@ -156,11 +156,11 @@ class Camera_Model(abc.ABC): cam_type = ... # overwrite in subclasses, used for saving/loading - def __init__(self, K, D, resolution, name): + def __init__(self, name, resolution, K, D): + self.name = name + self.resolution = resolution self.K = np.array(K) self.D = np.array(D) - self.resolution = resolution - self.name = name def update_camera_matrix(self, camera_matrix): self.K = np.asanyarray(camera_matrix).reshape(self.K.shape) @@ -310,7 +310,7 @@ def from_file(directory, cam_name, resolution): logger.warning( "No default intrinsics available! Loading dummy intrinsics!" ) - return Dummy_Camera(resolution, cam_name) + return Dummy_Camera(cam_name, resolution) cam_type = intrinsics["cam_type"] if cam_type not in Camera_Model.subclass_by_cam_type: @@ -318,11 +318,11 @@ def from_file(directory, cam_name, resolution): f"Trying to load unknown camera type intrinsics: {cam_type}! Using " " dummy intrinsics!" ) - return Dummy_Camera(resolution, cam_name) + return Dummy_Camera(cam_name, resolution) camera_model_class = Camera_Model.subclass_by_cam_type[cam_type] return camera_model_class( - intrinsics["camera_matrix"], intrinsics["dist_coefs"], resolution, cam_name + cam_name, resolution, intrinsics["camera_matrix"], intrinsics["dist_coefs"] ) @@ -625,11 +625,11 @@ class Dummy_Camera(Radial_Dist_Camera): cam_type = "dummy" - def __init__(self, resolution, name): - camera_matrix = [ + def __init__(self, name, resolution, K=None, D=None): + camera_matrix = K or [ [1000, 0.0, resolution[0] / 2.0], [0.0, 1000, resolution[1] / 2.0], [0.0, 0.0, 1.0], ] - dist_coefs = [[0.0, 0.0, 0.0, 0.0, 0.0]] - super().__init__(camera_matrix, dist_coefs, resolution, name) + dist_coefs = D or [[0.0, 0.0, 0.0, 0.0, 0.0]] + super().__init__(name, resolution, camera_matrix, dist_coefs) diff --git a/pupil_src/shared_modules/video_capture/hmd_streaming.py b/pupil_src/shared_modules/video_capture/hmd_streaming.py index 809850bc02..8b05ebdb4c 100644 --- a/pupil_src/shared_modules/video_capture/hmd_streaming.py +++ b/pupil_src/shared_modules/video_capture/hmd_streaming.py @@ -116,10 +116,10 @@ def intrinsics(self): if self.projection_matrix is not None: distortion = [[0.0, 0.0, 0.0, 0.0, 0.0]] self._intrinsics = Radial_Dist_Camera( - self.projection_matrix, distortion, self.frame_size, self.name + self.name, self.frame_size, self.projection_matrix, distortion ) else: - self._intrinsics = Dummy_Camera(self.frame_size, self.name) + self._intrinsics = Dummy_Camera(self.name, self.frame_size) return self._intrinsics @intrinsics.setter From 12d86b6a246ad6e494f040c437364af667d33100 Mon Sep 17 00:00:00 2001 From: Patrick Faion Date: Wed, 2 Sep 2020 10:29:52 +0200 Subject: [PATCH 49/75] Fix previous merging error --- pupil_src/shared_modules/pupil_recording/update/new_style.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pupil_src/shared_modules/pupil_recording/update/new_style.py b/pupil_src/shared_modules/pupil_recording/update/new_style.py index 70f8bd49dd..57ed2b62c6 100644 --- a/pupil_src/shared_modules/pupil_recording/update/new_style.py +++ b/pupil_src/shared_modules/pupil_recording/update/new_style.py @@ -35,7 +35,7 @@ def recording_update_to_latest_new_style(rec_dir: str): info_file = update_newstyle_20_21(rec_dir) if info_file.meta_version < parse_version("2.2"): info_file = update_newstyle_21_22(rec_dir) - if info_file.meta_version < Version("2.3"): + if info_file.meta_version < parse_version("2.3"): info_file = update_newstyle_22_23(rec_dir) @@ -136,7 +136,7 @@ def update_newstyle_22_23(rec_dir: str): # update info file old_info_file = RecordingInfoFile.read_file_from_recording(rec_dir) new_info_file = RecordingInfoFile.create_empty_file( - rec_dir, fixed_version=Version("2.3") + rec_dir, fixed_version=parse_version("2.3") ) new_info_file.update_writeable_properties_from(old_info_file) new_info_file.save_file() From e553c6742d1168d1aa53aa963c69820f1ede709d Mon Sep 17 00:00:00 2001 From: Patrick Faion Date: Wed, 2 Sep 2020 14:02:21 +0200 Subject: [PATCH 50/75] Apply black formatter version 20.8 --- .../shared_modules/accuracy_visualizer.py | 3 +- pupil_src/shared_modules/annotations.py | 2 +- pupil_src/shared_modules/audio/__init__.py | 3 +- pupil_src/shared_modules/audio_playback.py | 10 +++- pupil_src/shared_modules/batch_exporter.py | 6 +- .../calibration_choreography/base_plugin.py | 6 +- .../natural_feature_plugin.py | 2 +- .../screen_marker_plugin.py | 4 +- .../single_marker_plugin.py | 4 +- .../camera_intrinsics_estimation.py | 4 +- pupil_src/shared_modules/file_methods.py | 5 +- pupil_src/shared_modules/fixation_detector.py | 6 +- .../gaze_mapping/gazer_3d/utils.py | 4 +- .../gaze_mapping/notifications.py | 6 +- .../gaze_producer/model/calibration.py | 4 +- .../model/legacy/calibration_v1.py | 9 ++- .../gaze_producer/ui/calibration_menu.py | 12 ++-- .../gaze_producer/ui/gaze_mapper_timeline.py | 3 +- .../gaze_producer/worker/map_gaze.py | 5 +- .../gaze_producer/worker/validate_gaze.py | 4 +- pupil_src/shared_modules/gprof2dot.py | 11 ++-- .../function/bundle_adjustment.py | 12 ++-- .../worker/detection_worker.py | 2 +- pupil_src/shared_modules/hololens_relay.py | 2 +- .../shared_modules/marker_auto_trim_marks.py | 3 +- .../math_helper/intersections.py | 6 +- .../math_helper/transformations.py | 4 +- pupil_src/shared_modules/methods.py | 4 +- .../controller/pupil_remote_controller.py | 2 +- .../network_api/network_api_plugin.py | 4 +- pupil_src/shared_modules/os_utils.py | 2 +- pupil_src/shared_modules/player_methods.py | 10 +++- pupil_src/shared_modules/plugin.py | 4 +- pupil_src/shared_modules/pupil_data_relay.py | 3 +- .../pupil_detector_plugins/__init__.py | 2 +- .../detector_2d_plugin.py | 4 +- .../pupil_detector_plugins/visualizer_2d.py | 5 +- .../pupil_detector_plugins/visualizer_3d.py | 57 +++++++++---------- pupil_src/shared_modules/pupil_producers.py | 5 +- .../pupil_recording/update/old_style.py | 11 +++- pupil_src/shared_modules/raw_data_exporter.py | 2 +- pupil_src/shared_modules/recorder.py | 2 +- pupil_src/shared_modules/roi.py | 7 ++- .../shared_modules/surface_tracker/cache.py | 12 ++-- .../video_capture/base_backend.py | 6 +- pupil_src/shared_modules/zmq_tools.py | 2 +- 46 files changed, 167 insertions(+), 119 deletions(-) diff --git a/pupil_src/shared_modules/accuracy_visualizer.py b/pupil_src/shared_modules/accuracy_visualizer.py index fc2c957465..3ec2e9e9cd 100644 --- a/pupil_src/shared_modules/accuracy_visualizer.py +++ b/pupil_src/shared_modules/accuracy_visualizer.py @@ -293,7 +293,8 @@ def __handle_calibration_result_notification(self, note_dict: dict) -> bool: return False self.recent_input.update( - gazer_class_name=note.gazer_class_name, gazer_params=note.params, + gazer_class_name=note.gazer_class_name, + gazer_params=note.params, ) self.recalculate() diff --git a/pupil_src/shared_modules/annotations.py b/pupil_src/shared_modules/annotations.py index 44311ac47f..af750308b1 100644 --- a/pupil_src/shared_modules/annotations.py +++ b/pupil_src/shared_modules/annotations.py @@ -29,7 +29,7 @@ def create_annotation(label, timestamp, duration=0.0, **custom_fields): """ Returns a dictionary in the format needed to send annotations to an annotation plugin via the ICP. - + See python/remote_annotations.py in pupil-helpers for an example. :param custom_fields: diff --git a/pupil_src/shared_modules/audio/__init__.py b/pupil_src/shared_modules/audio/__init__.py index e60b623c9d..3c76373a62 100644 --- a/pupil_src/shared_modules/audio/__init__.py +++ b/pupil_src/shared_modules/audio/__init__.py @@ -41,8 +41,7 @@ def get_audio_mode(): def set_audio_mode(new_mode): - """a save way to set the audio mode - """ + """a save way to set the audio mode""" if new_mode in get_audio_mode_list(): global _audio_mode _audio_mode = new_mode diff --git a/pupil_src/shared_modules/audio_playback.py b/pupil_src/shared_modules/audio_playback.py index 08fa2b80ef..892969a95a 100644 --- a/pupil_src/shared_modules/audio_playback.py +++ b/pupil_src/shared_modules/audio_playback.py @@ -191,7 +191,8 @@ def _setup_filter_graph(self): self.filter_graph_list[-2].link_to(self.filter_graph_list[-1]) self.filter_graph_list.append( self.filter_graph.add( - "aresample", "osf={}".format(self.audio.stream.format.packed.name), + "aresample", + "osf={}".format(self.audio.stream.format.packed.name), ) ) self.filter_graph_list[-2].link_to(self.filter_graph_list[-1]) @@ -421,7 +422,12 @@ def set_volume(val): self.menu.append( ui.Slider( - "req_audio_volume", self, step=0.05, min=0.0, max=1.0, label="Volume", + "req_audio_volume", + self, + step=0.05, + min=0.0, + max=1.0, + label="Volume", ) ) self.menu.append( diff --git a/pupil_src/shared_modules/batch_exporter.py b/pupil_src/shared_modules/batch_exporter.py index 2441ef999b..1cb0c28eca 100644 --- a/pupil_src/shared_modules/batch_exporter.py +++ b/pupil_src/shared_modules/batch_exporter.py @@ -36,9 +36,9 @@ def get_recording_dirs(data_dir): """ - You can supply a data folder or any folder - - all folders within will be checked for necessary files - - in order to make a visualization + You can supply a data folder or any folder + - all folders within will be checked for necessary files + - in order to make a visualization """ if is_pupil_rec_dir(data_dir): yield data_dir diff --git a/pupil_src/shared_modules/calibration_choreography/base_plugin.py b/pupil_src/shared_modules/calibration_choreography/base_plugin.py index b43f183a97..1037218e63 100644 --- a/pupil_src/shared_modules/calibration_choreography/base_plugin.py +++ b/pupil_src/shared_modules/calibration_choreography/base_plugin.py @@ -297,7 +297,8 @@ def ref_list(self, value): def on_choreography_started(self, mode: ChoreographyMode): self.notify_all( ChoreographyNotification( - mode=mode, action=ChoreographyAction.STARTED, + mode=mode, + action=ChoreographyAction.STARTED, ).to_dict() ) @@ -452,8 +453,7 @@ def update_ui(self): ) def deinit_ui(self): - """Gets called when the plugin get terminated, either voluntarily or forced. - """ + """Gets called when the plugin get terminated, either voluntarily or forced.""" if self.is_active: self._perform_stop() self.remove_menu() diff --git a/pupil_src/shared_modules/calibration_choreography/natural_feature_plugin.py b/pupil_src/shared_modules/calibration_choreography/natural_feature_plugin.py index 26be92fddc..b9952749f5 100644 --- a/pupil_src/shared_modules/calibration_choreography/natural_feature_plugin.py +++ b/pupil_src/shared_modules/calibration_choreography/natural_feature_plugin.py @@ -34,7 +34,7 @@ class NaturalFeatureChoreographyPlugin(CalibrationChoreographyPlugin): """Calibrate using natural features in a scene. - Features are selected by a user by clicking on + Features are selected by a user by clicking on """ label = "Natural Feature Calibration" diff --git a/pupil_src/shared_modules/calibration_choreography/screen_marker_plugin.py b/pupil_src/shared_modules/calibration_choreography/screen_marker_plugin.py index c59b12e163..054b57ba37 100644 --- a/pupil_src/shared_modules/calibration_choreography/screen_marker_plugin.py +++ b/pupil_src/shared_modules/calibration_choreography/screen_marker_plugin.py @@ -188,8 +188,8 @@ def recent_events(self, events): if isinstance(state, MarkerWindowStateIdle): assert self.__currently_shown_marker_position is None # Sanity check if self.__current_list_of_markers_to_show: - self.__currently_shown_marker_position = self.__current_list_of_markers_to_show.pop( - 0 + self.__currently_shown_marker_position = ( + self.__current_list_of_markers_to_show.pop(0) ) logger.debug( f"Moving screen marker to site at {self.__currently_shown_marker_position}" diff --git a/pupil_src/shared_modules/calibration_choreography/single_marker_plugin.py b/pupil_src/shared_modules/calibration_choreography/single_marker_plugin.py index 3713872ad4..076e33c030 100644 --- a/pupil_src/shared_modules/calibration_choreography/single_marker_plugin.py +++ b/pupil_src/shared_modules/calibration_choreography/single_marker_plugin.py @@ -82,8 +82,8 @@ class SingleMarkerChoreographyPlugin( MonitorSelectionMixin, CalibrationChoreographyPlugin ): """Calibrate using a single marker. - Move your head for example in a spiral motion while gazing - at the marker to quickly sample a wide range gaze angles. + Move your head for example in a spiral motion while gazing + at the marker to quickly sample a wide range gaze angles. """ label = "Single Marker Calibration" diff --git a/pupil_src/shared_modules/camera_intrinsics_estimation.py b/pupil_src/shared_modules/camera_intrinsics_estimation.py index 7758b6a5f7..41b4d68970 100644 --- a/pupil_src/shared_modules/camera_intrinsics_estimation.py +++ b/pupil_src/shared_modules/camera_intrinsics_estimation.py @@ -45,8 +45,8 @@ def on_resize(window, w, h): class Camera_Intrinsics_Estimation(Plugin): """Camera_Intrinsics_Calibration - This method is not a gaze calibration. - This method is used to calculate camera intrinsics. + This method is not a gaze calibration. + This method is used to calculate camera intrinsics. """ icon_chr = chr(0xEC06) diff --git a/pupil_src/shared_modules/file_methods.py b/pupil_src/shared_modules/file_methods.py index c916ec7060..81b3c4800c 100644 --- a/pupil_src/shared_modules/file_methods.py +++ b/pupil_src/shared_modules/file_methods.py @@ -359,7 +359,10 @@ def unpacking_ext_hook(self, code, data): return msgpack.ExtType(code, data) return msgpack.unpackb( - self._ser_data, raw=False, use_list=False, ext_hook=unpacking_ext_hook, + self._ser_data, + raw=False, + use_list=False, + ext_hook=unpacking_ext_hook, ) diff --git a/pupil_src/shared_modules/fixation_detector.py b/pupil_src/shared_modules/fixation_detector.py index 81f95f74a1..f06ec3331c 100644 --- a/pupil_src/shared_modules/fixation_detector.py +++ b/pupil_src/shared_modules/fixation_detector.py @@ -226,7 +226,11 @@ def detect_fixations( # binary search while left_idx < right_idx - 1: middle_idx = (left_idx + right_idx) // 2 - dispersion = gaze_dispersion(capture, slicable[: middle_idx + 1], method,) + dispersion = gaze_dispersion( + capture, + slicable[: middle_idx + 1], + method, + ) if dispersion <= max_dispersion: left_idx = middle_idx else: diff --git a/pupil_src/shared_modules/gaze_mapping/gazer_3d/utils.py b/pupil_src/shared_modules/gaze_mapping/gazer_3d/utils.py index 1df4249859..e4af038228 100644 --- a/pupil_src/shared_modules/gaze_mapping/gazer_3d/utils.py +++ b/pupil_src/shared_modules/gaze_mapping/gazer_3d/utils.py @@ -155,7 +155,7 @@ def calculate_nearest_points_to_targets( def _clamp_norm_point(pos): """realistic numbers for norm pos should be in this range. - Grossly bigger or smaller numbers are results bad exrapolation - and can cause overflow erorr when denormalized and cast as int32. + Grossly bigger or smaller numbers are results bad exrapolation + and can cause overflow erorr when denormalized and cast as int32. """ return min(100.0, max(-100.0, pos[0])), min(100.0, max(-100.0, pos[1])) diff --git a/pupil_src/shared_modules/gaze_mapping/notifications.py b/pupil_src/shared_modules/gaze_mapping/notifications.py index 27828695f4..731af37c8d 100644 --- a/pupil_src/shared_modules/gaze_mapping/notifications.py +++ b/pupil_src/shared_modules/gaze_mapping/notifications.py @@ -38,7 +38,11 @@ def sanitize_serialized_dict(cls, dict_: dict) -> dict: @classmethod def _assert_static_property_matches_dict( - cls, dict_: dict, key_name: str, key_type: T.Any, field_name: str = None, + cls, + dict_: dict, + key_name: str, + key_type: T.Any, + field_name: str = None, ): field_name = field_name if field_name is not None else key_name defaults = cls._field_defaults diff --git a/pupil_src/shared_modules/gaze_producer/model/calibration.py b/pupil_src/shared_modules/gaze_producer/model/calibration.py index 76c74a833b..f82156e191 100644 --- a/pupil_src/shared_modules/gaze_producer/model/calibration.py +++ b/pupil_src/shared_modules/gaze_producer/model/calibration.py @@ -65,7 +65,9 @@ def params(self) -> T.Optional[T.Any]: return self.calib_params def update( - self, is_offline_calibration: bool = ..., calib_params: T.Optional[T.Any] = ..., + self, + is_offline_calibration: bool = ..., + calib_params: T.Optional[T.Any] = ..., ): if is_offline_calibration is not ...: self.__is_offline_calibration = is_offline_calibration diff --git a/pupil_src/shared_modules/gaze_producer/model/legacy/calibration_v1.py b/pupil_src/shared_modules/gaze_producer/model/legacy/calibration_v1.py index 60185c3dcc..235bc3811d 100644 --- a/pupil_src/shared_modules/gaze_producer/model/legacy/calibration_v1.py +++ b/pupil_src/shared_modules/gaze_producer/model/legacy/calibration_v1.py @@ -114,7 +114,10 @@ def _gazer_class_and_params_from_gaze_mapper_result(gaze_mapper_result): if gaze_mapper_args is not None: assert set(gaze_mapper_args.keys()).issuperset( - {"eye_camera_to_world_matrix0", "eye_camera_to_world_matrix1",} + { + "eye_camera_to_world_matrix0", + "eye_camera_to_world_matrix1", + } ) # sanity check gazer_params = {"binocular_model": gaze_mapper_args} @@ -123,7 +126,9 @@ def _gazer_class_and_params_from_gaze_mapper_result(gaze_mapper_result): if gaze_mapper_args is not None: assert set(gaze_mapper_args.keys()).issuperset( - {"eye_camera_to_world_matrix",} + { + "eye_camera_to_world_matrix", + } ) # sanity check # Since there is no way to know which eye the mapper belongs to, # we use the arguments as the parameters for both eye models. diff --git a/pupil_src/shared_modules/gaze_producer/ui/calibration_menu.py b/pupil_src/shared_modules/gaze_producer/ui/calibration_menu.py index be3631e295..79e2d180ae 100644 --- a/pupil_src/shared_modules/gaze_producer/ui/calibration_menu.py +++ b/pupil_src/shared_modules/gaze_producer/ui/calibration_menu.py @@ -183,12 +183,16 @@ def _on_click_delete(self): def _on_change_current_item(self, item): super()._on_change_current_item(item) if self._ui_button_duplicate: - self._ui_button_duplicate.read_only = not self.__check_duplicate_button_click_is_allowed( - should_log_reason_as_error=False + self._ui_button_duplicate.read_only = ( + not self.__check_duplicate_button_click_is_allowed( + should_log_reason_as_error=False + ) ) if self._ui_button_delete: - self._ui_button_delete.read_only = not self.__check_delete_button_click_is_allowed( - should_log_reason_as_error=False + self._ui_button_delete.read_only = ( + not self.__check_delete_button_click_is_allowed( + should_log_reason_as_error=False + ) ) def _on_name_change(self, new_name): diff --git a/pupil_src/shared_modules/gaze_producer/ui/gaze_mapper_timeline.py b/pupil_src/shared_modules/gaze_producer/ui/gaze_mapper_timeline.py index 0953e603f6..e2d6aeab76 100644 --- a/pupil_src/shared_modules/gaze_producer/ui/gaze_mapper_timeline.py +++ b/pupil_src/shared_modules/gaze_producer/ui/gaze_mapper_timeline.py @@ -49,7 +49,8 @@ def __init__( "publish_all_enabled_mappers", self._on_publish_enabled_mappers ) self._gaze_mapper_controller.add_observer( - "set_calibration_unique_id", self._on_calibration_unique_id_changed, + "set_calibration_unique_id", + self._on_calibration_unique_id_changed, ) self._calibration_controller.add_observer( diff --git a/pupil_src/shared_modules/gaze_producer/worker/map_gaze.py b/pupil_src/shared_modules/gaze_producer/worker/map_gaze.py index 5183cf8ab4..97ed29af7d 100644 --- a/pupil_src/shared_modules/gaze_producer/worker/map_gaze.py +++ b/pupil_src/shared_modules/gaze_producer/worker/map_gaze.py @@ -46,7 +46,10 @@ def create_task(gaze_mapper, calibration): ) name = f"Create gaze mapper {gaze_mapper.name}" return tasklib.background.create( - name, _map_gaze, args=args, pass_shared_memory=True, + name, + _map_gaze, + args=args, + pass_shared_memory=True, ) diff --git a/pupil_src/shared_modules/gaze_producer/worker/validate_gaze.py b/pupil_src/shared_modules/gaze_producer/worker/validate_gaze.py index 7d650ca301..fe7dbd753c 100644 --- a/pupil_src/shared_modules/gaze_producer/worker/validate_gaze.py +++ b/pupil_src/shared_modules/gaze_producer/worker/validate_gaze.py @@ -48,7 +48,9 @@ def create_bg_task(gaze_mapper, calibration, reference_location_storage): ) return tasklib.background.create( - f"validate gaze mapper '{gaze_mapper.name}'", validate, args=args, + f"validate gaze mapper '{gaze_mapper.name}'", + validate, + args=args, ) diff --git a/pupil_src/shared_modules/gprof2dot.py b/pupil_src/shared_modules/gprof2dot.py index 6d8f676e62..b574cc4429 100644 --- a/pupil_src/shared_modules/gprof2dot.py +++ b/pupil_src/shared_modules/gprof2dot.py @@ -216,7 +216,7 @@ def __setitem__(self, event, value): class Call(Object): """A call between functions. - + There should be at most one call object for every pair of functions. """ @@ -1694,7 +1694,7 @@ def parse(self): class CallgrindParser(LineParser): """Parser for valgrind's callgrind tool. - + See also: - http://valgrind.org/docs/manual/cl-format.html """ @@ -2134,7 +2134,7 @@ def parse_call(self): class OprofileParser(LineParser): """Parser for oprofile callgraph output. - + See also: - http://oprofile.sourceforge.net/doc/opreport.html#opreport-callgraph """ @@ -2305,7 +2305,7 @@ def match_secondary(self): class HProfParser(LineParser): """Parser for java hprof output - + See also: - http://java.sun.com/developer/technicalArticles/Programming/HPROF.html """ @@ -2531,8 +2531,7 @@ def build_profile(self, objects, nodes): class XPerfParser(Parser): - """Parser for CSVs generted by XPerf, from Microsoft Windows Performance Tools. - """ + """Parser for CSVs generted by XPerf, from Microsoft Windows Performance Tools.""" def __init__(self, stream): Parser.__init__(self) diff --git a/pupil_src/shared_modules/head_pose_tracker/function/bundle_adjustment.py b/pupil_src/shared_modules/head_pose_tracker/function/bundle_adjustment.py index 5f93b40d74..ebf8fba47d 100644 --- a/pupil_src/shared_modules/head_pose_tracker/function/bundle_adjustment.py +++ b/pupil_src/shared_modules/head_pose_tracker/function/bundle_adjustment.py @@ -44,7 +44,7 @@ def __init__(self, camera_intrinsics, optimize_camera_intrinsics): self._frame_ids = [] def calculate(self, initial_guess_result): - """ run bundle adjustment given the initial guess and then check the result of + """run bundle adjustment given the initial guess and then check the result of markers_3d_model """ @@ -126,8 +126,8 @@ def _prepare_parameters(self, camera_extrinsics_array, marker_extrinsics_array): return initial_guess_array, bounds, sparsity_matrix def _calculate_bounds(self, eps=np.finfo(np.float).eps, scale=np.inf): - """ calculate the lower and upper bounds on independent variables - fix the first marker at the origin of the coordinate system + """calculate the lower and upper bounds on independent variables + fix the first marker at the origin of the coordinate system """ camera_extrinsics_lower_bound = np.full(self._camera_extrinsics_shape, -scale) camera_extrinsics_upper_bound = np.full(self._camera_extrinsics_shape, scale) @@ -256,7 +256,7 @@ def _get_result(self, least_sq_result): return bundle_adjustment_result def _function_compute_residuals(self, variables): - """ Function which computes the vector of residuals, + """Function which computes the vector of residuals, i.e., the minimization proceeds with respect to params """ camera_extrinsics_array, marker_extrinsics_array = self._get_extrinsics_arrays( @@ -274,7 +274,7 @@ def _function_compute_residuals(self, variables): return residuals.ravel() def _get_extrinsics_arrays(self, variables): - """ reshape 1-dimensional vector into the original shape of + """reshape 1-dimensional vector into the original shape of camera_extrinsics_array and marker_extrinsics_array """ @@ -310,7 +310,7 @@ def _project_markers(self, camera_extrinsics_array, marker_extrinsics_array): return markers_points_2d_projected def _find_failed_indices(self, residuals, thres_frame=8, thres_marker=8): - """ find out those frame_indices and marker_indices which cause large + """find out those frame_indices and marker_indices which cause large reprojection errors """ diff --git a/pupil_src/shared_modules/head_pose_tracker/worker/detection_worker.py b/pupil_src/shared_modules/head_pose_tracker/worker/detection_worker.py index c7031a9aab..6bfca6d750 100644 --- a/pupil_src/shared_modules/head_pose_tracker/worker/detection_worker.py +++ b/pupil_src/shared_modules/head_pose_tracker/worker/detection_worker.py @@ -43,7 +43,7 @@ def calc_perimeter(corners): def dedupliciate_markers(marker_old, marker_new): """Deduplicate markers by returning marker with bigger perimeter - + This heuristic is useful to remove "echos", i.e. markers that are being detected within the scene video preview instead of the scene itself. """ diff --git a/pupil_src/shared_modules/hololens_relay.py b/pupil_src/shared_modules/hololens_relay.py index b262b97e50..7709cb134d 100644 --- a/pupil_src/shared_modules/hololens_relay.py +++ b/pupil_src/shared_modules/hololens_relay.py @@ -464,6 +464,6 @@ def get_init_dict(self): def cleanup(self): """gets called when the plugin get terminated. - This happens either voluntarily or forced. + This happens either voluntarily or forced. """ self.stop_server() diff --git a/pupil_src/shared_modules/marker_auto_trim_marks.py b/pupil_src/shared_modules/marker_auto_trim_marks.py index 2a5866eaf0..8442f3dd5b 100644 --- a/pupil_src/shared_modules/marker_auto_trim_marks.py +++ b/pupil_src/shared_modules/marker_auto_trim_marks.py @@ -311,8 +311,7 @@ def gl_display(self): self.gl_display_cache_bars() def gl_display_cache_bars(self): - """ - """ + """""" padding = 20.0 frame_max = len( self.g_pool.timestamps diff --git a/pupil_src/shared_modules/math_helper/intersections.py b/pupil_src/shared_modules/math_helper/intersections.py index b19d48c564..2d088a6682 100644 --- a/pupil_src/shared_modules/math_helper/intersections.py +++ b/pupil_src/shared_modules/math_helper/intersections.py @@ -14,8 +14,7 @@ def nearest_intersection_points(line0, line1): - """ Calculates the two nearst points, and its distance to each other on line0 and line1. - """ + """Calculates the two nearst points, and its distance to each other on line0 and line1.""" p1 = line0[0] p2 = line0[1] @@ -61,8 +60,7 @@ def normalise(p1, p2): def nearest_intersection(line0, line1): - """ Calculates the nearest intersection point, and the shortest distance of line0 and line1. - """ + """Calculates the nearest intersection point, and the shortest distance of line0 and line1.""" Pa, Pb, intersection_dist = nearest_intersection_points(line0, line1) if Pa is not None: diff --git a/pupil_src/shared_modules/math_helper/transformations.py b/pupil_src/shared_modules/math_helper/transformations.py index 9ce1a87d74..8bf9a2b8bb 100644 --- a/pupil_src/shared_modules/math_helper/transformations.py +++ b/pupil_src/shared_modules/math_helper/transformations.py @@ -1307,9 +1307,7 @@ def quaternion_matrix(quaternion): def quaternion_rotation_matrix(quaternion): - """Return rotation matrix from quaternion. - - """ + """Return rotation matrix from quaternion.""" return quaternion_matrix(quaternion)[:3, :3] diff --git a/pupil_src/shared_modules/methods.py b/pupil_src/shared_modules/methods.py index cc9faec692..997ca65c6c 100644 --- a/pupil_src/shared_modules/methods.py +++ b/pupil_src/shared_modules/methods.py @@ -38,7 +38,7 @@ def timer(dt): def delta_t(): - """ return time between each call like so: + """return time between each call like so: tick = delta_t() def get_dt(): @@ -134,7 +134,7 @@ def equalize(image, image_lower=0.0, image_upper=255.0): def erase_specular(image, lower_threshold=0.0, upper_threshold=150.0): """erase_specular: removes specular reflections - within given threshold using a binary mask (hi_mask) + within given threshold using a binary mask (hi_mask) """ thresh = cv2.inRange(image, np.asarray(float(lower_threshold)), np.asarray(256.0)) diff --git a/pupil_src/shared_modules/network_api/controller/pupil_remote_controller.py b/pupil_src/shared_modules/network_api/controller/pupil_remote_controller.py index 6a16030ad1..d1edc97126 100644 --- a/pupil_src/shared_modules/network_api/controller/pupil_remote_controller.py +++ b/pupil_src/shared_modules/network_api/controller/pupil_remote_controller.py @@ -147,7 +147,7 @@ def restart_server(self, host: str, port: int): def cleanup(self): """gets called when the plugin get terminated. - This happens either voluntarily or forced. + This happens either voluntarily or forced. """ self.__stop_server() diff --git a/pupil_src/shared_modules/network_api/network_api_plugin.py b/pupil_src/shared_modules/network_api/network_api_plugin.py index 2047dbd93e..0a38fc2dbf 100644 --- a/pupil_src/shared_modules/network_api/network_api_plugin.py +++ b/pupil_src/shared_modules/network_api/network_api_plugin.py @@ -80,8 +80,8 @@ def deinit_ui(self): def recent_events(self, events): frame = events.get("frame") if frame: - world_frame_dicts = self.__frame_publisher.create_world_frame_dicts_from_frame( - frame + world_frame_dicts = ( + self.__frame_publisher.create_world_frame_dicts_from_frame(frame) ) if world_frame_dicts: events["frame.world"] = world_frame_dicts diff --git a/pupil_src/shared_modules/os_utils.py b/pupil_src/shared_modules/os_utils.py index aca3e32aef..b5ce5d3e2d 100644 --- a/pupil_src/shared_modules/os_utils.py +++ b/pupil_src/shared_modules/os_utils.py @@ -60,7 +60,7 @@ def __exit__(self, etype, value, tb): def patch_pyre_zhelper_cdll(): """Fixes https://github.com/pupil-labs/pupil/issues/1919 - + When running the v2.0 bundle on macOS 10.14, `ctypes.CDLL("libSystem.dylib")` fails to load which is required by pyre.zhelper. `libSystem.dylib` is not part of the bundle on purpose, as `ctypes.CDLL` is usually able to fallback to the system- diff --git a/pupil_src/shared_modules/player_methods.py b/pupil_src/shared_modules/player_methods.py index eb4800a64f..0057e11a30 100644 --- a/pupil_src/shared_modules/player_methods.py +++ b/pupil_src/shared_modules/player_methods.py @@ -172,7 +172,8 @@ def create(topic: str, pupil_datum: dict) -> str: if detector_tag in PupilTopic._legacy_method_to_detector_tag: detector_tag = PupilTopic._legacy_method_to_detector_tag[detector_tag] return PupilTopic._FORMAT_STRING_V2.format( - eye_id=match_v1.group("eye_id"), detector_tag=detector_tag, + eye_id=match_v1.group("eye_id"), + detector_tag=detector_tag, ) regex_v2 = PupilTopic._match_regex_v2() match_v2 = re.match(regex_v2, topic) @@ -213,7 +214,8 @@ def _match_regex_v2( detector_tag = r"[^\.]+" pattern = PupilTopic._MATCH_FORMAT_STRING_V2.format( - eye_id=eye_id, detector_tag=detector_tag, + eye_id=eye_id, + detector_tag=detector_tag, ) return re.compile(pattern) @@ -224,7 +226,9 @@ def _match_regex_v1(eye_id: T.Optional[str] = None): if eye_id is None: eye_id = "[01]" - pattern = PupilTopic._MATCH_FORMAT_STRING_V1.format(eye_id=eye_id,) + pattern = PupilTopic._MATCH_FORMAT_STRING_V1.format( + eye_id=eye_id, + ) return re.compile(pattern) diff --git a/pupil_src/shared_modules/plugin.py b/pupil_src/shared_modules/plugin.py index 02e4e5391a..a8bafe3751 100644 --- a/pupil_src/shared_modules/plugin.py +++ b/pupil_src/shared_modules/plugin.py @@ -328,8 +328,8 @@ def wrapper(_self): # Plugin manager classes and fns class Plugin_List(object): """This is the Plugin Manager - It is a self sorting list with a few functions to manage adding and - removing Plugins and lacking most other list methods. + It is a self sorting list with a few functions to manage adding and + removing Plugins and lacking most other list methods. """ def __init__(self, g_pool, plugin_initializers): diff --git a/pupil_src/shared_modules/pupil_data_relay.py b/pupil_src/shared_modules/pupil_data_relay.py index e13612afc8..8f5c590d88 100644 --- a/pupil_src/shared_modules/pupil_data_relay.py +++ b/pupil_src/shared_modules/pupil_data_relay.py @@ -14,8 +14,7 @@ class Pupil_Data_Relay(System_Plugin_Base): - """ - """ + """""" def __init__(self, g_pool): super().__init__(g_pool) diff --git a/pupil_src/shared_modules/pupil_detector_plugins/__init__.py b/pupil_src/shared_modules/pupil_detector_plugins/__init__.py index 149cc3cdc1..103739c75c 100644 --- a/pupil_src/shared_modules/pupil_detector_plugins/__init__.py +++ b/pupil_src/shared_modules/pupil_detector_plugins/__init__.py @@ -22,7 +22,7 @@ def available_detector_plugins() -> T.Tuple[ PupilDetectorPlugin, PupilDetectorPlugin, T.List[PupilDetectorPlugin] ]: """Load and list available plugins, including default - + Returns tuple of default2D, default3D, and list of all detectors. """ diff --git a/pupil_src/shared_modules/pupil_detector_plugins/detector_2d_plugin.py b/pupil_src/shared_modules/pupil_detector_plugins/detector_2d_plugin.py index ab3c29d240..418af9da11 100644 --- a/pupil_src/shared_modules/pupil_detector_plugins/detector_2d_plugin.py +++ b/pupil_src/shared_modules/pupil_detector_plugins/detector_2d_plugin.py @@ -53,7 +53,9 @@ def detect(self, frame, **kwargs): debug_img = frame.bgr if self.g_pool.display_mode == "algorithm" else None result = self.detector_2d.detect( - gray_img=frame.gray, color_img=debug_img, roi=roi, + gray_img=frame.gray, + color_img=debug_img, + roi=roi, ) eye_id = self.g_pool.eye_id location = result["location"] diff --git a/pupil_src/shared_modules/pupil_detector_plugins/visualizer_2d.py b/pupil_src/shared_modules/pupil_detector_plugins/visualizer_2d.py index 4252e87a0c..c33c797490 100644 --- a/pupil_src/shared_modules/pupil_detector_plugins/visualizer_2d.py +++ b/pupil_src/shared_modules/pupil_detector_plugins/visualizer_2d.py @@ -42,7 +42,10 @@ def draw_ellipse( draw_polyline(pts, thickness, RGBA(*rgba)) if draw_center: draw_points( - [ellipse["center"]], size=20, color=RGBA(*rgba), sharpness=1.0, + [ellipse["center"]], + size=20, + color=RGBA(*rgba), + sharpness=1.0, ) diff --git a/pupil_src/shared_modules/pupil_detector_plugins/visualizer_3d.py b/pupil_src/shared_modules/pupil_detector_plugins/visualizer_3d.py index 6117e1faea..e70a225786 100644 --- a/pupil_src/shared_modules/pupil_detector_plugins/visualizer_3d.py +++ b/pupil_src/shared_modules/pupil_detector_plugins/visualizer_3d.py @@ -83,25 +83,25 @@ def get_pupil_transformation_matrix( self, circle_normal, circle_center, circle_scale=1.0 ): """ - OpenGL matrix convention for typical GL software - with positive Y=up and positive Z=rearward direction - RT = right - UP = up - BK = back - POS = position/translation - US = uniform scale - - float transform[16]; - - [0] [4] [8 ] [12] - [1] [5] [9 ] [13] - [2] [6] [10] [14] - [3] [7] [11] [15] - - [RT.x] [UP.x] [BK.x] [POS.x] - [RT.y] [UP.y] [BK.y] [POS.y] - [RT.z] [UP.z] [BK.z] [POS.Z] - [ ] [ ] [ ] [US ] + OpenGL matrix convention for typical GL software + with positive Y=up and positive Z=rearward direction + RT = right + UP = up + BK = back + POS = position/translation + US = uniform scale + + float transform[16]; + + [0] [4] [8 ] [12] + [1] [5] [9 ] [13] + [2] [6] [10] [14] + [3] [7] [11] [15] + + [RT.x] [UP.x] [BK.x] [POS.x] + [RT.y] [UP.y] [BK.y] [POS.y] + [RT.z] [UP.z] [BK.z] [POS.Z] + [ ] [ ] [ ] [US ] """ temp = self.get_anthropomorphic_matrix() right = temp[:3, 0] @@ -132,17 +132,14 @@ def draw_debug_info(self, result): direction = result["circle"][1] pupil_radius = result["circle"][2] - status = ( - " Eyeball center : X: %.2fmm Y: %.2fmm Z: %.2fmm\n Pupil direction: X: %.2f Y: %.2f Z: %.2f\n Pupil Diameter: %.2fmm\n " - % ( - eye[0][0], - eye[0][1], - eye[0][2], - direction[0], - direction[1], - direction[2], - pupil_radius * 2, - ) + status = " Eyeball center : X: %.2fmm Y: %.2fmm Z: %.2fmm\n Pupil direction: X: %.2f Y: %.2f Z: %.2f\n Pupil Diameter: %.2fmm\n " % ( + eye[0][0], + eye[0][1], + eye[0][2], + direction[0], + direction[1], + direction[2], + pupil_radius * 2, ) self.glfont.push_state() diff --git a/pupil_src/shared_modules/pupil_producers.py b/pupil_src/shared_modules/pupil_producers.py index 678b67ddb9..89f7a9a129 100644 --- a/pupil_src/shared_modules/pupil_producers.py +++ b/pupil_src/shared_modules/pupil_producers.py @@ -399,7 +399,10 @@ def detection_progress(self) -> float: # TODO: Figure out the number of frames independent of 3d detection detected = self._pupil_data_store.count_collected(detector_tag="3d") if total: - return min(detected / total, 1.0,) + return min( + detected / total, + 1.0, + ) else: return 0.0 diff --git a/pupil_src/shared_modules/pupil_recording/update/old_style.py b/pupil_src/shared_modules/pupil_recording/update/old_style.py index 1549ff0623..a1dcbe6be3 100644 --- a/pupil_src/shared_modules/pupil_recording/update/old_style.py +++ b/pupil_src/shared_modules/pupil_recording/update/old_style.py @@ -213,8 +213,8 @@ def update_recording_v086_to_v087(rec_dir): def _clamp_norm_point(pos): """realisitic numbers for norm pos should be in this range. - Grossly bigger or smaller numbers are results bad exrapolation - and can cause overflow erorr when denormalized and cast as int32. + Grossly bigger or smaller numbers are results bad exrapolation + and can cause overflow erorr when denormalized and cast as int32. """ return min(100.0, max(-100.0, pos[0])), min(100.0, max(-100.0, pos[1])) @@ -666,7 +666,12 @@ def update_recording_v04_to_v074(rec_dir): pupil_by_ts = dict([(p["timestamp"], p) for p in pupil_list]) for datum in gaze_array: - ts, confidence, x, y, = datum + ( + ts, + confidence, + x, + y, + ) = datum gaze_list.append( { "timestamp": ts, diff --git a/pupil_src/shared_modules/raw_data_exporter.py b/pupil_src/shared_modules/raw_data_exporter.py index 88e2fe6f18..a138fa127e 100644 --- a/pupil_src/shared_modules/raw_data_exporter.py +++ b/pupil_src/shared_modules/raw_data_exporter.py @@ -103,7 +103,7 @@ class Raw_Data_Exporter(Plugin): gaze_normal1_x - x normal of the visual axis for eye 1 in the world camera coordinate system (not avaible for monocular setups.). The visual axis goes through the eye ball center and the object thats looked at. gaze_normal1_y - y normal of the visual axis for eye 1 gaze_normal1_z - z normal of the visual axis for eye 1 - """ + """ icon_chr = chr(0xE873) icon_font = "pupil_icons" diff --git a/pupil_src/shared_modules/recorder.py b/pupil_src/shared_modules/recorder.py index 0d8385f14e..5c61705617 100644 --- a/pupil_src/shared_modules/recorder.py +++ b/pupil_src/shared_modules/recorder.py @@ -521,7 +521,7 @@ def stop(self): def cleanup(self): """gets called when the plugin get terminated. - either volunatily or forced. + either volunatily or forced. """ if self.running: self.stop() diff --git a/pupil_src/shared_modules/roi.py b/pupil_src/shared_modules/roi.py index 6bc1b3d31d..3df3505290 100644 --- a/pupil_src/shared_modules/roi.py +++ b/pupil_src/shared_modules/roi.py @@ -155,7 +155,7 @@ def on_change(self, callback: ChangeCallback) -> None: def on_changed(self) -> None: """Called when the model changes. - + Observe this method to be notified of any changes. """ pass @@ -188,7 +188,10 @@ class Roi(Plugin): outline_color = cygl_rgba(0.8, 0.8, 0.8, 0.9) def __init__( - self, g_pool, frame_size: Vec2 = (0, 0), bounds: Bounds = (0, 0, 0, 0), + self, + g_pool, + frame_size: Vec2 = (0, 0), + bounds: Bounds = (0, 0, 0, 0), ) -> None: super().__init__(g_pool) self.model = RoiModel(frame_size) diff --git a/pupil_src/shared_modules/surface_tracker/cache.py b/pupil_src/shared_modules/surface_tracker/cache.py index 22e1e50312..8b518f0df8 100644 --- a/pupil_src/shared_modules/surface_tracker/cache.py +++ b/pupil_src/shared_modules/surface_tracker/cache.py @@ -17,12 +17,12 @@ class Cache(list): """Cache list is a list of False - [False,False,False] - with update() 'False' can be overwritten with a result (anything not 'False') - self.visited_ranges show ranges where the cache content is False - self.positive_ranges show ranges where the cache does not evaluate as 'False' using eval_fn - this allows to use ranges a a way of showing where no caching has happed (default) or whatever you do with eval_fn - self.complete indicated that the cache list has no unknowns aka False + [False,False,False] + with update() 'False' can be overwritten with a result (anything not 'False') + self.visited_ranges show ranges where the cache content is False + self.positive_ranges show ranges where the cache does not evaluate as 'False' using eval_fn + this allows to use ranges a a way of showing where no caching has happed (default) or whatever you do with eval_fn + self.complete indicated that the cache list has no unknowns aka False """ def __init__(self, init_list): diff --git a/pupil_src/shared_modules/video_capture/base_backend.py b/pupil_src/shared_modules/video_capture/base_backend.py index 8a805fa739..08ee9cd34c 100644 --- a/pupil_src/shared_modules/video_capture/base_backend.py +++ b/pupil_src/shared_modules/video_capture/base_backend.py @@ -76,7 +76,11 @@ def pretty_class_name(self): return "Video Source" def __init__( - self, g_pool, *, source_mode: T.Optional[SourceMode] = None, **kwargs, + self, + g_pool, + *, + source_mode: T.Optional[SourceMode] = None, + **kwargs, ): super().__init__(g_pool) self.g_pool.capture = self diff --git a/pupil_src/shared_modules/zmq_tools.py b/pupil_src/shared_modules/zmq_tools.py index f9a93ccf7a..c19420175e 100644 --- a/pupil_src/shared_modules/zmq_tools.py +++ b/pupil_src/shared_modules/zmq_tools.py @@ -145,7 +145,7 @@ def __init__(self, ctx, url, hwm=None): def send(self, payload, deprecated=()): """Send a message with topic, payload -` + Topic is a unicode string. It will be sent as utf-8 encoded byte array. Payload is a python dict. It will be sent as a msgpack serialized dict. From bc9351e80565842e58336986d6a898c5081bae2b Mon Sep 17 00:00:00 2001 From: Patrick Faion Date: Fri, 4 Sep 2020 10:44:40 +0200 Subject: [PATCH 51/75] Remove dead code --- .../shared_modules/pupil_detector_plugins/pye3d_plugin.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/pupil_src/shared_modules/pupil_detector_plugins/pye3d_plugin.py b/pupil_src/shared_modules/pupil_detector_plugins/pye3d_plugin.py index 9acffd4774..9a5dff959e 100644 --- a/pupil_src/shared_modules/pupil_detector_plugins/pye3d_plugin.py +++ b/pupil_src/shared_modules/pupil_detector_plugins/pye3d_plugin.py @@ -58,9 +58,6 @@ def detect(self, frame, **kwargs): return result - def customize_menu(self): - pass - @classmethod def parse_pretty_class_name(cls) -> str: return "Pye3D Detector" From 1e5894043b56808a1d01f3d7e97f2f92e0563522 Mon Sep 17 00:00:00 2001 From: Roman Roibu Date: Fri, 4 Sep 2020 14:22:48 +0200 Subject: [PATCH 52/75] Use gl_utils.WindowPositionManager to adjust new window position --- pupil_src/launchables/eye.py | 6 ++- pupil_src/launchables/player.py | 14 ++++++- pupil_src/launchables/world.py | 6 ++- pupil_src/shared_modules/gl_utils/__init__.py | 1 + .../gl_utils/window_position_manager.py | 40 +++++++++++++++++++ pupil_src/shared_modules/service_ui.py | 8 +++- 6 files changed, 70 insertions(+), 5 deletions(-) create mode 100644 pupil_src/shared_modules/gl_utils/window_position_manager.py diff --git a/pupil_src/launchables/eye.py b/pupil_src/launchables/eye.py index ad98a35810..f7abccceb0 100644 --- a/pupil_src/launchables/eye.py +++ b/pupil_src/launchables/eye.py @@ -407,7 +407,11 @@ def toggle_general_settings(collapsed): width, height = session_settings.get("window_size", default_window_size) main_window = glfw.glfwCreateWindow(width, height, title, None, None) - window_pos = session_settings.get("window_position", window_position_default) + window_position_manager = gl_utils.WindowPositionManager() + window_pos = window_position_manager.new_window_position( + default_position=window_position_default, + previous_position=session_settings.get("window_position", None), + ) glfw.glfwSetWindowPos(main_window, window_pos[0], window_pos[1]) glfw.glfwMakeContextCurrent(main_window) cygl.utils.init() diff --git a/pupil_src/launchables/player.py b/pupil_src/launchables/player.py index fb26e5822c..c6ff9efb5e 100644 --- a/pupil_src/launchables/player.py +++ b/pupil_src/launchables/player.py @@ -356,7 +356,12 @@ def get_dt(): width += icon_bar_width width, height = session_settings.get("window_size", (width, height)) - window_pos = session_settings.get("window_position", window_position_default) + window_position_manager = gl_utils.WindowPositionManager() + window_pos = window_position_manager.new_window_position( + default_position=window_position_default, + previous_position=session_settings.get("window_position", None), + ) + window_name = f"Pupil Player: {meta_info.recording_name} - {rec_dir}" glfw.glfwInit() @@ -836,7 +841,12 @@ def on_drop(window, count, paths): ) session_settings.clear() w, h = session_settings.get("window_size", (1280, 720)) - window_pos = session_settings.get("window_position", window_position_default) + + window_position_manager = gl_utils.WindowPositionManager() + window_pos = window_position_manager.new_window_position( + default_position=window_position_default, + previous_position=session_settings.get("window_position", None), + ) glfw.glfwInit() glfw.glfwWindowHint(glfw.GLFW_SCALE_TO_MONITOR, glfw.GLFW_TRUE) diff --git a/pupil_src/launchables/world.py b/pupil_src/launchables/world.py index 4f3bbfd55f..9453c23284 100644 --- a/pupil_src/launchables/world.py +++ b/pupil_src/launchables/world.py @@ -514,7 +514,11 @@ def handle_notifications(noti): if hide_ui: glfw.glfwWindowHint(glfw.GLFW_VISIBLE, 0) # hide window main_window = glfw.glfwCreateWindow(width, height, "Pupil Capture - World") - window_pos = session_settings.get("window_position", window_position_default) + window_position_manager = gl_utils.WindowPositionManager() + window_pos = window_position_manager.new_window_position( + default_position=window_position_default, + previous_position=session_settings.get("window_position", None), + ) glfw.glfwSetWindowPos(main_window, window_pos[0], window_pos[1]) glfw.glfwMakeContextCurrent(main_window) cygl.utils.init() diff --git a/pupil_src/shared_modules/gl_utils/__init__.py b/pupil_src/shared_modules/gl_utils/__init__.py index 91a57e2e23..6473d08d3e 100644 --- a/pupil_src/shared_modules/gl_utils/__init__.py +++ b/pupil_src/shared_modules/gl_utils/__init__.py @@ -11,3 +11,4 @@ from .utils import * from .trackball import * +from .window_position_manager import WindowPositionManager diff --git a/pupil_src/shared_modules/gl_utils/window_position_manager.py b/pupil_src/shared_modules/gl_utils/window_position_manager.py new file mode 100644 index 0000000000..2606379c1a --- /dev/null +++ b/pupil_src/shared_modules/gl_utils/window_position_manager.py @@ -0,0 +1,40 @@ +""" +(*)~--------------------------------------------------------------------------- +Pupil - eye tracking platform +Copyright (C) 2012-2020 Pupil Labs + +Distributed under the terms of the GNU +Lesser General Public License (LGPL v3.0). +See COPYING and COPYING.LESSER for license details. +---------------------------------------------------------------------------~(*) +""" +import logging +import platform +import typing as T + +import glfw + + +class WindowPositionManager: + def __init__(self): + pass + + @staticmethod + def new_window_position( + default_position: T.Tuple[int, int], + previous_position: T.Optional[T.Tuple[int, int]], + ) -> T.Tuple[int, int]: + + if previous_position is None: + return default_position + + os_name = platform.system() + + if os_name == "Darwin": + return previous_position + elif os_name == "Linux": + return previous_position + elif os_name == "Windows": + return previous_position + else: + raise NotImplementedError(f"Unsupported system: {os_name}") diff --git a/pupil_src/shared_modules/service_ui.py b/pupil_src/shared_modules/service_ui.py index c1c9a29c12..e7bb56e70a 100644 --- a/pupil_src/shared_modules/service_ui.py +++ b/pupil_src/shared_modules/service_ui.py @@ -40,7 +40,7 @@ def __init__( self, g_pool, window_size=window_size_default, - window_position=window_position_default, + window_position=None, gui_scale=1.0, ui_config={}, ): @@ -48,6 +48,12 @@ def __init__( self.texture = np.zeros((1, 1, 3), dtype=np.uint8) + 128 + window_position_manager = gl_utils.WindowPositionManager() + window_position = window_position_manager.new_window_position( + default_position=window_position_default, + previous_position=window_position, + ) + glfw.glfwInit() glfw.glfwWindowHint(glfw.GLFW_SCALE_TO_MONITOR, glfw.GLFW_TRUE) if g_pool.hide_ui: From 22028889005e4585325b1ba8721c5a1f74945906 Mon Sep 17 00:00:00 2001 From: Patrick Faion Date: Fri, 4 Sep 2020 10:44:03 +0200 Subject: [PATCH 53/75] Allow resetting 3D model via network notification --- .../shared_modules/pupil_detector_plugins/pye3d_plugin.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/pupil_src/shared_modules/pupil_detector_plugins/pye3d_plugin.py b/pupil_src/shared_modules/pupil_detector_plugins/pye3d_plugin.py index 9a5dff959e..f0ace70e9e 100644 --- a/pupil_src/shared_modules/pupil_detector_plugins/pye3d_plugin.py +++ b/pupil_src/shared_modules/pupil_detector_plugins/pye3d_plugin.py @@ -58,6 +58,14 @@ def detect(self, frame, **kwargs): return result + def on_notify(self, notification): + super().on_notify(notification) + + subject = notification["subject"] + if subject == "pupil_detector.3d.reset_model": + if "id" not in notification or notification["id"] == self.g_pool.eye_id: + self.reset_model() + @classmethod def parse_pretty_class_name(cls) -> str: return "Pye3D Detector" From 17948e5cf34030953adc26661421f6b465ba48da Mon Sep 17 00:00:00 2001 From: Patrick Faion Date: Mon, 7 Sep 2020 10:38:16 +0200 Subject: [PATCH 54/75] Clarify logic for reset_model via network message --- .../shared_modules/pupil_detector_plugins/pye3d_plugin.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/pupil_src/shared_modules/pupil_detector_plugins/pye3d_plugin.py b/pupil_src/shared_modules/pupil_detector_plugins/pye3d_plugin.py index f0ace70e9e..ec9ab2c66c 100644 --- a/pupil_src/shared_modules/pupil_detector_plugins/pye3d_plugin.py +++ b/pupil_src/shared_modules/pupil_detector_plugins/pye3d_plugin.py @@ -63,7 +63,11 @@ def on_notify(self, notification): subject = notification["subject"] if subject == "pupil_detector.3d.reset_model": - if "id" not in notification or notification["id"] == self.g_pool.eye_id: + if "id" not in notification: + # simply apply to all eye processes + self.reset_model() + elif notification["id"] == self.g_pool.eye_id: + # filter for specific eye processes self.reset_model() @classmethod From 3c8b21318f918c87e29ab4d1bb3188d4c6cde49f Mon Sep 17 00:00:00 2001 From: Patrick Faion Date: Mon, 7 Sep 2020 14:09:35 +0200 Subject: [PATCH 55/75] Add support for offline detection in pye3D --- .../shared_modules/pupil_detector_plugins/pye3d_plugin.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/pupil_src/shared_modules/pupil_detector_plugins/pye3d_plugin.py b/pupil_src/shared_modules/pupil_detector_plugins/pye3d_plugin.py index ec9ab2c66c..fc0663eca7 100644 --- a/pupil_src/shared_modules/pupil_detector_plugins/pye3d_plugin.py +++ b/pupil_src/shared_modules/pupil_detector_plugins/pye3d_plugin.py @@ -26,6 +26,7 @@ class Pye3DPlugin(PupilDetectorPlugin): icon_chr = chr(0xEC19) label = "Pye3D" + identifier = "3d" order = 0.101 def __init__(self, g_pool): @@ -52,7 +53,7 @@ def detect(self, frame, **kwargs): eye_id = self.g_pool.eye_id result["timestamp"] = frame.timestamp - result["topic"] = f"pupil.{eye_id}" + result["topic"] = f"pupil.{eye_id}.{self.identifier}" result["id"] = eye_id result["method"] = "3d c++" From c2e1f1a1072f545a0186a9cd4cf7467939b3b83e Mon Sep 17 00:00:00 2001 From: Roman Roibu Date: Mon, 7 Sep 2020 16:27:24 +0200 Subject: [PATCH 56/75] Fix recording software version string formatting --- pupil_src/shared_modules/recorder.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pupil_src/shared_modules/recorder.py b/pupil_src/shared_modules/recorder.py index 5c61705617..06d29f0697 100644 --- a/pupil_src/shared_modules/recorder.py +++ b/pupil_src/shared_modules/recorder.py @@ -333,7 +333,7 @@ def start(self): self.meta_info.recording_software_name = ( RecordingInfoFile.RECORDING_SOFTWARE_NAME_PUPIL_CAPTURE ) - self.meta_info.recording_software_version = self.g_pool.version.vstring + self.meta_info.recording_software_version = str(self.g_pool.version) self.meta_info.recording_name = self.session_name self.meta_info.start_time_synced_s = start_time_synced self.meta_info.start_time_system_s = self.start_time From 9fca8eb1d8719200bb6fc16d0b671b4f2806ea32 Mon Sep 17 00:00:00 2001 From: Patrick Faion Date: Tue, 8 Sep 2020 11:21:32 +0200 Subject: [PATCH 57/75] Prevent eye window crash from ellipse2Poly issues There was a new error type which could happen here, so I extended to logging. Additionally the previous error handling was not correct, as we would still crash when running the code below without `pts` being computed. --- .../pupil_detector_plugins/visualizer_2d.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/pupil_src/shared_modules/pupil_detector_plugins/visualizer_2d.py b/pupil_src/shared_modules/pupil_detector_plugins/visualizer_2d.py index c33c797490..c0381e712e 100644 --- a/pupil_src/shared_modules/pupil_detector_plugins/visualizer_2d.py +++ b/pupil_src/shared_modules/pupil_detector_plugins/visualizer_2d.py @@ -33,11 +33,16 @@ def draw_ellipse( # Known issues: # - There are reports of negative eye_ball axes when drawing the 3D eyeball # outline, which will raise cv2.error. TODO: Investigate cause in detectors. + # - There was a case where all values in the ellipse where 'NaN', which raises + # ValueError: cannot convert float NaN to integer. TODO: Investigate how we + # even got here, since calls to this function are confidence-gated! logger.debug( "Error drawing ellipse! Skipping...\n" - f"ellipse: {ellipse}\n" - f"{type(e)}: {e}" + f"Ellipse: {ellipse}\n" + f"Color: {rgba}\n" + f"Error: {type(e)}: {e}" ) + return draw_polyline(pts, thickness, RGBA(*rgba)) if draw_center: From e38c164404127e475dc5acea992240e562045483 Mon Sep 17 00:00:00 2001 From: Roman Roibu Date: Tue, 8 Sep 2020 12:23:39 +0200 Subject: [PATCH 58/75] Add glfwGetMonitorWorkarea function to glfw --- pupil_src/shared_modules/glfw.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/pupil_src/shared_modules/glfw.py b/pupil_src/shared_modules/glfw.py index 80da04953a..5d152ce3f7 100644 --- a/pupil_src/shared_modules/glfw.py +++ b/pupil_src/shared_modules/glfw.py @@ -424,6 +424,7 @@ class GLFWmonitor(Structure): glfwGetPrimaryMonitor = _glfw.glfwGetPrimaryMonitor glfwGetPrimaryMonitor.restype = POINTER(GLFWmonitor) # glfwGetMonitorPos = _glfw.glfwGetMonitorPos +# glfwGetMonitorWorkarea = _glfw.glfwGetMonitorWorkarea # glfwGetMonitorPhysicalSize = _glfw.glfwGetMonitorPhysicalSize glfwGetMonitorName = _glfw.glfwGetMonitorName glfwGetMonitorName.restype = c_char_p @@ -656,6 +657,14 @@ def glfwGetMonitorPos(monitor): return xpos.value, ypos.value +def glfwGetMonitorWorkarea(monitor): + xpos, ypos, width, height = c_int(0), c_int(0), c_int(0), c_int(0) + _glfw.glfwGetMonitorWorkarea( + monitor, byref(xpos), byref(ypos), byref(width), byref(height) + ) + return xpos.value, ypos.value, width.value, height.value + + def glfwGetMonitorPhysicalSize(monitor): width, height = c_int(0), c_int(0) _glfw.glfwGetMonitorPhysicalSize(monitor, byref(width), byref(height)) From 29e7685b93751aa32bc3facd51fad52bd021350b Mon Sep 17 00:00:00 2001 From: Roman Roibu Date: Tue, 8 Sep 2020 12:24:55 +0200 Subject: [PATCH 59/75] Test window position validity with _is_point_within_monitor --- .../gl_utils/window_position_manager.py | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/pupil_src/shared_modules/gl_utils/window_position_manager.py b/pupil_src/shared_modules/gl_utils/window_position_manager.py index 2606379c1a..7ed03b3e8a 100644 --- a/pupil_src/shared_modules/gl_utils/window_position_manager.py +++ b/pupil_src/shared_modules/gl_utils/window_position_manager.py @@ -35,6 +35,19 @@ def new_window_position( elif os_name == "Linux": return previous_position elif os_name == "Windows": - return previous_position + monitors = glfw.glfwGetMonitors() + if any(_is_point_within_monitor(m, previous_position) for m in monitors): + return previous_position + else: + return default_position else: raise NotImplementedError(f"Unsupported system: {os_name}") + + +def _is_point_within_monitor(monitor, point) -> bool: + x, y, w, h = glfw.glfwGetMonitorWorkarea(monitor) + + is_within_horizontally = x <= point[0] < x + w + is_within_vertically = y <= point[1] < y + h + + return is_within_horizontally and is_within_vertically From 355572e69a3d2e0884fff465f423c080c91bcab3 Mon Sep 17 00:00:00 2001 From: Roman Roibu Date: Tue, 8 Sep 2020 12:25:17 +0200 Subject: [PATCH 60/75] Improve format and add comments for window_position_manager.py --- pupil_src/shared_modules/gl_utils/window_position_manager.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/pupil_src/shared_modules/gl_utils/window_position_manager.py b/pupil_src/shared_modules/gl_utils/window_position_manager.py index 7ed03b3e8a..6548d8f09b 100644 --- a/pupil_src/shared_modules/gl_utils/window_position_manager.py +++ b/pupil_src/shared_modules/gl_utils/window_position_manager.py @@ -31,15 +31,20 @@ def new_window_position( os_name = platform.system() if os_name == "Darwin": + # The OS handle re-positioning windows with invalid positions return previous_position + elif os_name == "Linux": + # The OS handle re-positioning windows with invalid positions return previous_position + elif os_name == "Windows": monitors = glfw.glfwGetMonitors() if any(_is_point_within_monitor(m, previous_position) for m in monitors): return previous_position else: return default_position + else: raise NotImplementedError(f"Unsupported system: {os_name}") From c1aaa437a7e065d0a77daf3accbab85dcda3768d Mon Sep 17 00:00:00 2001 From: Roman Roibu Date: Tue, 8 Sep 2020 16:12:51 +0200 Subject: [PATCH 61/75] Update glfwGetMonitorWorkarea to return a named tuple --- pupil_src/shared_modules/glfw.py | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/pupil_src/shared_modules/glfw.py b/pupil_src/shared_modules/glfw.py index 5d152ce3f7..67a390cb76 100644 --- a/pupil_src/shared_modules/glfw.py +++ b/pupil_src/shared_modules/glfw.py @@ -34,6 +34,7 @@ # ----------------------------------------------------------------------------- import sys, os +import collections import ctypes from ctypes import ( c_int, @@ -52,6 +53,7 @@ from ctypes.util import find_library import logging +import typing as T logger = logging.getLogger(__name__) @@ -625,6 +627,9 @@ def glfwGetFramebufferSize(window): return width.value, height.value +_Rectangle = collections.namedtuple("_Rectangle", ["x", "y", "width", "height"]) + + def glfwGetMonitors(): count = c_int(0) _glfw.glfwGetMonitors.restype = POINTER(POINTER(GLFWmonitor)) @@ -657,12 +662,14 @@ def glfwGetMonitorPos(monitor): return xpos.value, ypos.value -def glfwGetMonitorWorkarea(monitor): +def glfwGetMonitorWorkarea(monitor) -> _Rectangle: xpos, ypos, width, height = c_int(0), c_int(0), c_int(0), c_int(0) _glfw.glfwGetMonitorWorkarea( monitor, byref(xpos), byref(ypos), byref(width), byref(height) ) - return xpos.value, ypos.value, width.value, height.value + return _Rectangle( + x=xpos.value, y=ypos.value, width=width.value, height=height.value + ) def glfwGetMonitorPhysicalSize(monitor): From 10bbda1d0e13db96b0beb1c39ddc216670faa6ea Mon Sep 17 00:00:00 2001 From: Roman Roibu Date: Tue, 8 Sep 2020 16:13:08 +0200 Subject: [PATCH 62/75] Add glfwGetWindowFrameSize implementation --- pupil_src/shared_modules/glfw.py | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/pupil_src/shared_modules/glfw.py b/pupil_src/shared_modules/glfw.py index 67a390cb76..41d48c44b0 100644 --- a/pupil_src/shared_modules/glfw.py +++ b/pupil_src/shared_modules/glfw.py @@ -453,6 +453,7 @@ class GLFWmonitor(Structure): glfwSetWindowSizeLimits = _glfw.glfwSetWindowSizeLimits glfwSetWindowSize = _glfw.glfwSetWindowSize # glfwGetFramebufferSize = _glfw.glfwGetFramebufferSize +# glfwGetWindowFrameSize = _glfw.glfwGetWindowFrameSize glfwIconifyWindow = _glfw.glfwIconifyWindow glfwRestoreWindow = _glfw.glfwRestoreWindow glfwShowWindow = _glfw.glfwShowWindow @@ -628,6 +629,18 @@ def glfwGetFramebufferSize(window): _Rectangle = collections.namedtuple("_Rectangle", ["x", "y", "width", "height"]) +_Margins = collections.namedtuple("_Margins", ["left", "top", "right", "bottom"]) + + +def glfwGetWindowFrameSize(window) -> _Margins: + """""" + left, top, right, bottom = c_int(0), c_int(0), c_int(0), c_int(0) + _glfw.glfwGetWindowFrameSize( + window, byref(left), byref(top), byref(right), byref(bottom) + ) + return _Margins( + left=left.value, top=top.value, right=right.value, bottom=bottom.value + ) def glfwGetMonitors(): From a27b7505d9fd3ae855de762ac6f4f316a6847781 Mon Sep 17 00:00:00 2001 From: Roman Roibu Date: Tue, 8 Sep 2020 16:13:27 +0200 Subject: [PATCH 63/75] Add helper functions to glfw module --- pupil_src/shared_modules/glfw.py | 43 ++++++++++++++++++++++++++++++++ 1 file changed, 43 insertions(+) diff --git a/pupil_src/shared_modules/glfw.py b/pupil_src/shared_modules/glfw.py index 41d48c44b0..de082f99e9 100644 --- a/pupil_src/shared_modules/glfw.py +++ b/pupil_src/shared_modules/glfw.py @@ -790,3 +790,46 @@ def get_framebuffer_scale(window) -> float: def window_coordinate_to_framebuffer_coordinate(window, x, y, cached_scale=None): scale = cached_scale or get_framebuffer_scale(window) return x * scale, y * scale + + +def get_window_content_rect(window) -> _Rectangle: + x, y = glfwGetWindowPos(window) + w, h = glfwGetWindowSize(window) + return _Rectangle(x=x, y=y, width=w, height=h) + + +def get_window_frame_rect(window) -> _Rectangle: + content_rect = get_window_content_rect(window) + frame_edges = glfwGetWindowFrameSize(window) + return _Rectangle( + x=content_rect.x - frame_edges.left, + y=content_rect.y - frame_edges.top, + width=content_rect.width + frame_edges.left + frame_edges.right, + height=content_rect.height + frame_edges.top + frame_edges.bottom, + ) + + +def get_window_title_bar_rect(window) -> _Rectangle: + frame_rect = get_window_frame_rect(window) + frame_edges = glfwGetWindowFrameSize(window) + return _Rectangle( + x=frame_rect.x, y=frame_rect.y, width=frame_rect.width, height=frame_edges.top + ) + + +def rectangle_intersection(r1: _Rectangle, r2: _Rectangle) -> T.Optional[_Rectangle]: + in_min_x = max(r1.x, r2.x) + in_min_y = max(r1.y, r2.y) + + in_max_x = min(r1.x + r1.width, r2.x + r2.width) + in_max_y = min(r1.y + r1.height, r2.y + r2.height) + + if in_min_x < in_max_x and in_min_y < in_max_y: + return _Rectangle( + x=in_min_x, + y=in_min_y, + width=in_max_x - in_min_x, + height=in_max_y - in_min_y, + ) + else: + return None From 83b129283f34c5dae22928fa2e3413d6aef477e1 Mon Sep 17 00:00:00 2001 From: Roman Roibu Date: Tue, 8 Sep 2020 16:15:00 +0200 Subject: [PATCH 64/75] Update WindowPositionManager.new_window_position The new implementation checks if the window's title bar is visible enough to make sure the window can be moved. --- .../gl_utils/window_position_manager.py | 28 ++++++++++++++----- 1 file changed, 21 insertions(+), 7 deletions(-) diff --git a/pupil_src/shared_modules/gl_utils/window_position_manager.py b/pupil_src/shared_modules/gl_utils/window_position_manager.py index 6548d8f09b..06b66e7a24 100644 --- a/pupil_src/shared_modules/gl_utils/window_position_manager.py +++ b/pupil_src/shared_modules/gl_utils/window_position_manager.py @@ -21,6 +21,7 @@ def __init__(self): @staticmethod def new_window_position( + window, default_position: T.Tuple[int, int], previous_position: T.Optional[T.Tuple[int, int]], ) -> T.Tuple[int, int]: @@ -39,8 +40,15 @@ def new_window_position( return previous_position elif os_name == "Windows": - monitors = glfw.glfwGetMonitors() - if any(_is_point_within_monitor(m, previous_position) for m in monitors): + + def validate_previous_position(monitor) -> bool: + return _will_window_be_visible_in_monitor( + window=window, + monitor=monitor, + window_position=previous_position, + ) + + if any(validate_previous_position(m) for m in glfw.glfwGetMonitors()): return previous_position else: return default_position @@ -49,10 +57,16 @@ def new_window_position( raise NotImplementedError(f"Unsupported system: {os_name}") -def _is_point_within_monitor(monitor, point) -> bool: - x, y, w, h = glfw.glfwGetMonitorWorkarea(monitor) +def _will_window_be_visible_in_monitor( + window, monitor, window_position, min_visible_width=10, min_visible_height=5 +) -> bool: + monitor_rect = glfw.glfwGetMonitorWorkarea(monitor) + title_bar_rect = glfw.get_window_title_bar_rect(window) + visible_rect = glfw.rectangle_intersection(monitor_rect, title_bar_rect) + return ( + visible_rect is not None + and min_visible_width <= visible_rect.width + and min_visible_height <= visible_rect.height + ) - is_within_horizontally = x <= point[0] < x + w - is_within_vertically = y <= point[1] < y + h - return is_within_horizontally and is_within_vertically From 522ecba2df2bc7a482e5c265a8cb161c44f26abb Mon Sep 17 00:00:00 2001 From: Roman Roibu Date: Tue, 8 Sep 2020 16:15:39 +0200 Subject: [PATCH 65/75] Update launchables to consistenly calculate restored position after the window is created --- pupil_src/launchables/eye.py | 3 +++ pupil_src/launchables/player.py | 28 +++++++++++++++----------- pupil_src/launchables/world.py | 3 +++ pupil_src/shared_modules/service_ui.py | 14 +++++++------ 4 files changed, 30 insertions(+), 18 deletions(-) diff --git a/pupil_src/launchables/eye.py b/pupil_src/launchables/eye.py index f7abccceb0..2bb94f8985 100644 --- a/pupil_src/launchables/eye.py +++ b/pupil_src/launchables/eye.py @@ -407,12 +407,15 @@ def toggle_general_settings(collapsed): width, height = session_settings.get("window_size", default_window_size) main_window = glfw.glfwCreateWindow(width, height, title, None, None) + window_position_manager = gl_utils.WindowPositionManager() window_pos = window_position_manager.new_window_position( + window=main_window, default_position=window_position_default, previous_position=session_settings.get("window_position", None), ) glfw.glfwSetWindowPos(main_window, window_pos[0], window_pos[1]) + glfw.glfwMakeContextCurrent(main_window) cygl.utils.init() diff --git a/pupil_src/launchables/player.py b/pupil_src/launchables/player.py index c6ff9efb5e..8a3fc28e0f 100644 --- a/pupil_src/launchables/player.py +++ b/pupil_src/launchables/player.py @@ -356,18 +356,20 @@ def get_dt(): width += icon_bar_width width, height = session_settings.get("window_size", (width, height)) - window_position_manager = gl_utils.WindowPositionManager() - window_pos = window_position_manager.new_window_position( - default_position=window_position_default, - previous_position=session_settings.get("window_position", None), - ) - window_name = f"Pupil Player: {meta_info.recording_name} - {rec_dir}" glfw.glfwInit() glfw.glfwWindowHint(glfw.GLFW_SCALE_TO_MONITOR, glfw.GLFW_TRUE) main_window = glfw.glfwCreateWindow(width, height, window_name, None, None) + + window_position_manager = gl_utils.WindowPositionManager() + window_pos = window_position_manager.new_window_position( + window=main_window, + default_position=window_position_default, + previous_position=session_settings.get("window_position", None), + ) glfw.glfwSetWindowPos(main_window, window_pos[0], window_pos[1]) + glfw.glfwMakeContextCurrent(main_window) cygl.utils.init() g_pool.main_window = main_window @@ -842,12 +844,6 @@ def on_drop(window, count, paths): session_settings.clear() w, h = session_settings.get("window_size", (1280, 720)) - window_position_manager = gl_utils.WindowPositionManager() - window_pos = window_position_manager.new_window_position( - default_position=window_position_default, - previous_position=session_settings.get("window_position", None), - ) - glfw.glfwInit() glfw.glfwWindowHint(glfw.GLFW_SCALE_TO_MONITOR, glfw.GLFW_TRUE) glfw.glfwWindowHint(glfw.GLFW_RESIZABLE, 0) @@ -855,7 +851,15 @@ def on_drop(window, count, paths): glfw.glfwWindowHint(glfw.GLFW_RESIZABLE, 1) glfw.glfwMakeContextCurrent(window) + + window_position_manager = gl_utils.WindowPositionManager() + window_pos = window_position_manager.new_window_position( + window=window, + default_position=window_position_default, + previous_position=session_settings.get("window_position", None), + ) glfw.glfwSetWindowPos(window, window_pos[0], window_pos[1]) + glfw.glfwSetDropCallback(window, on_drop) glfont = fontstash.Context() diff --git a/pupil_src/launchables/world.py b/pupil_src/launchables/world.py index 9453c23284..d1860edb9c 100644 --- a/pupil_src/launchables/world.py +++ b/pupil_src/launchables/world.py @@ -514,12 +514,15 @@ def handle_notifications(noti): if hide_ui: glfw.glfwWindowHint(glfw.GLFW_VISIBLE, 0) # hide window main_window = glfw.glfwCreateWindow(width, height, "Pupil Capture - World") + window_position_manager = gl_utils.WindowPositionManager() window_pos = window_position_manager.new_window_position( + window=main_window, default_position=window_position_default, previous_position=session_settings.get("window_position", None), ) glfw.glfwSetWindowPos(main_window, window_pos[0], window_pos[1]) + glfw.glfwMakeContextCurrent(main_window) cygl.utils.init() g_pool.main_window = main_window diff --git a/pupil_src/shared_modules/service_ui.py b/pupil_src/shared_modules/service_ui.py index e7bb56e70a..611fde4f46 100644 --- a/pupil_src/shared_modules/service_ui.py +++ b/pupil_src/shared_modules/service_ui.py @@ -48,18 +48,20 @@ def __init__( self.texture = np.zeros((1, 1, 3), dtype=np.uint8) + 128 - window_position_manager = gl_utils.WindowPositionManager() - window_position = window_position_manager.new_window_position( - default_position=window_position_default, - previous_position=window_position, - ) - glfw.glfwInit() glfw.glfwWindowHint(glfw.GLFW_SCALE_TO_MONITOR, glfw.GLFW_TRUE) if g_pool.hide_ui: glfw.glfwWindowHint(glfw.GLFW_VISIBLE, 0) # hide window main_window = glfw.glfwCreateWindow(*window_size, "Pupil Service") + + window_position_manager = gl_utils.WindowPositionManager() + window_position = window_position_manager.new_window_position( + window=main_window, + default_position=window_position_default, + previous_position=window_position, + ) glfw.glfwSetWindowPos(main_window, *window_position) + glfw.glfwMakeContextCurrent(main_window) cygl.utils.init() g_pool.main_window = main_window From 2ac144abc708e8a44953090919d2c014047faeb3 Mon Sep 17 00:00:00 2001 From: Roman Roibu Date: Tue, 8 Sep 2020 16:44:21 +0200 Subject: [PATCH 66/75] Fix calculating the title bar rect based on the proposed position instead of the actual position --- .../gl_utils/window_position_manager.py | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/pupil_src/shared_modules/gl_utils/window_position_manager.py b/pupil_src/shared_modules/gl_utils/window_position_manager.py index 06b66e7a24..169833ccd6 100644 --- a/pupil_src/shared_modules/gl_utils/window_position_manager.py +++ b/pupil_src/shared_modules/gl_utils/window_position_manager.py @@ -60,13 +60,26 @@ def validate_previous_position(monitor) -> bool: def _will_window_be_visible_in_monitor( window, monitor, window_position, min_visible_width=10, min_visible_height=5 ) -> bool: + # Get the current window size and edges, and monitor rect + window_size = glfw.glfwGetWindowSize(window) + window_edges = glfw.glfwGetWindowFrameSize(window) monitor_rect = glfw.glfwGetMonitorWorkarea(monitor) - title_bar_rect = glfw.get_window_title_bar_rect(window) + + # Calculate what the title bar rect would be + # if the proposed `window_position` would be the actual window position + title_bar_rect = glfw._Rectangle( + x=window_position[0] - window_edges.left, + y=window_position[1] - window_edges.top, + width=window_size[0] + window_edges.left + window_edges.right, + height=window_edges.top, + ) + + # Calculate the part of the title bar that is visible in the monitor, if any visible_rect = glfw.rectangle_intersection(monitor_rect, title_bar_rect) + + # Return true if the visible title bar rect is big enough return ( visible_rect is not None and min_visible_width <= visible_rect.width and min_visible_height <= visible_rect.height ) - - From 6ee3f0c68195c9044e7a7edb6c6c6db352469e15 Mon Sep 17 00:00:00 2001 From: Roman Roibu Date: Tue, 8 Sep 2020 16:58:53 +0200 Subject: [PATCH 67/75] Increase the minimal title bar area needed to validate restored window position --- pupil_src/shared_modules/gl_utils/window_position_manager.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pupil_src/shared_modules/gl_utils/window_position_manager.py b/pupil_src/shared_modules/gl_utils/window_position_manager.py index 169833ccd6..5a7c62b6c7 100644 --- a/pupil_src/shared_modules/gl_utils/window_position_manager.py +++ b/pupil_src/shared_modules/gl_utils/window_position_manager.py @@ -58,7 +58,7 @@ def validate_previous_position(monitor) -> bool: def _will_window_be_visible_in_monitor( - window, monitor, window_position, min_visible_width=10, min_visible_height=5 + window, monitor, window_position, min_visible_width=30, min_visible_height=20 ) -> bool: # Get the current window size and edges, and monitor rect window_size = glfw.glfwGetWindowSize(window) From 198f5e501e806b57a6be2162eb0d7fe3ac1fc606 Mon Sep 17 00:00:00 2001 From: Roman Roibu Date: Tue, 8 Sep 2020 18:03:37 +0200 Subject: [PATCH 68/75] Only call glViewport if g_pool.camera_render_size is not negative --- pupil_src/launchables/eye.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/pupil_src/launchables/eye.py b/pupil_src/launchables/eye.py index ad98a35810..4df94afcd9 100644 --- a/pupil_src/launchables/eye.py +++ b/pupil_src/launchables/eye.py @@ -236,9 +236,10 @@ def consume_events_and_render_buffer(): glfw.glfwMakeContextCurrent(main_window) clear_gl_screen() - glViewport(0, 0, *g_pool.camera_render_size) - for p in g_pool.plugins: - p.gl_display() + if 0 <= g_pool.camera_render_size[0] and g_pool.camera_render_size[1]: + glViewport(0, 0, *g_pool.camera_render_size) + for p in g_pool.plugins: + p.gl_display() glViewport(0, 0, *window_size) # render graphs From c32e2b21b6d83ba3495b3ca9a0cf3155ec8f0dce Mon Sep 17 00:00:00 2001 From: Roman Roibu Date: Wed, 9 Sep 2020 11:04:14 +0200 Subject: [PATCH 69/75] Use typing.NamedTuple instead of collections.namedtuple --- pupil_src/shared_modules/glfw.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/pupil_src/shared_modules/glfw.py b/pupil_src/shared_modules/glfw.py index de082f99e9..f4d946886e 100644 --- a/pupil_src/shared_modules/glfw.py +++ b/pupil_src/shared_modules/glfw.py @@ -34,7 +34,6 @@ # ----------------------------------------------------------------------------- import sys, os -import collections import ctypes from ctypes import ( c_int, @@ -628,8 +627,12 @@ def glfwGetFramebufferSize(window): return width.value, height.value -_Rectangle = collections.namedtuple("_Rectangle", ["x", "y", "width", "height"]) -_Margins = collections.namedtuple("_Margins", ["left", "top", "right", "bottom"]) +_Rectangle = T.NamedTuple( + "_Rectangle", [("x", int), ("y", int), ("width", int), ("height", int)] +) +_Margins = T.NamedTuple( + "_Margins", [("left", int), ("top", int), ("right", int), ("bottom", int)] +) def glfwGetWindowFrameSize(window) -> _Margins: From 525aa9d70ea827e111aed96b108ab8ffde6841e7 Mon Sep 17 00:00:00 2001 From: Roman Roibu Date: Wed, 9 Sep 2020 11:11:47 +0200 Subject: [PATCH 70/75] Make rectangle_intersetion function into _Rectangle.intersection method --- .../gl_utils/window_position_manager.py | 2 +- pupil_src/shared_modules/glfw.py | 55 ++++++++++--------- 2 files changed, 30 insertions(+), 27 deletions(-) diff --git a/pupil_src/shared_modules/gl_utils/window_position_manager.py b/pupil_src/shared_modules/gl_utils/window_position_manager.py index 5a7c62b6c7..60b26f38ab 100644 --- a/pupil_src/shared_modules/gl_utils/window_position_manager.py +++ b/pupil_src/shared_modules/gl_utils/window_position_manager.py @@ -75,7 +75,7 @@ def _will_window_be_visible_in_monitor( ) # Calculate the part of the title bar that is visible in the monitor, if any - visible_rect = glfw.rectangle_intersection(monitor_rect, title_bar_rect) + visible_rect = title_bar_rect.intersection(monitor_rect) # Return true if the visible title bar rect is big enough return ( diff --git a/pupil_src/shared_modules/glfw.py b/pupil_src/shared_modules/glfw.py index f4d946886e..5574fa6b7b 100644 --- a/pupil_src/shared_modules/glfw.py +++ b/pupil_src/shared_modules/glfw.py @@ -520,6 +520,35 @@ class GLFWmonitor(Structure): __py_callbacks__ = {} +_Margins = T.NamedTuple( + "_Margins", [("left", int), ("top", int), ("right", int), ("bottom", int)] +) + + +_Rectangle_Tuple = T.NamedTuple( + "_Rectangle", [("x", int), ("y", int), ("width", int), ("height", int)] +) + + +class _Rectangle(_Rectangle_Tuple): + def intersection(self, other: "_Rectangle") -> T.Optional["_Rectangle"]: + in_min_x = max(self.x, other.x) + in_min_y = max(self.y, other.y) + + in_max_x = min(self.x + self.width, other.x + other.width) + in_max_y = min(self.y + self.height, other.y + other.height) + + if in_min_x < in_max_x and in_min_y < in_max_y: + return _Rectangle( + x=in_min_x, + y=in_min_y, + width=in_max_x - in_min_x, + height=in_max_y - in_min_y, + ) + else: + return None + + def glfwGetError(): _glfwGetError = _glfw.glfwGetError _glfwGetError.argtypes = [POINTER(c_char_p)] @@ -627,14 +656,6 @@ def glfwGetFramebufferSize(window): return width.value, height.value -_Rectangle = T.NamedTuple( - "_Rectangle", [("x", int), ("y", int), ("width", int), ("height", int)] -) -_Margins = T.NamedTuple( - "_Margins", [("left", int), ("top", int), ("right", int), ("bottom", int)] -) - - def glfwGetWindowFrameSize(window) -> _Margins: """""" left, top, right, bottom = c_int(0), c_int(0), c_int(0), c_int(0) @@ -818,21 +839,3 @@ def get_window_title_bar_rect(window) -> _Rectangle: return _Rectangle( x=frame_rect.x, y=frame_rect.y, width=frame_rect.width, height=frame_edges.top ) - - -def rectangle_intersection(r1: _Rectangle, r2: _Rectangle) -> T.Optional[_Rectangle]: - in_min_x = max(r1.x, r2.x) - in_min_y = max(r1.y, r2.y) - - in_max_x = min(r1.x + r1.width, r2.x + r2.width) - in_max_y = min(r1.y + r1.height, r2.y + r2.height) - - if in_min_x < in_max_x and in_min_y < in_max_y: - return _Rectangle( - x=in_min_x, - y=in_min_y, - width=in_max_x - in_min_x, - height=in_max_y - in_min_y, - ) - else: - return None From bf69d69c66205557f47ba2a07e13e2651ba9a2db Mon Sep 17 00:00:00 2001 From: Roman Roibu Date: Wed, 9 Sep 2020 11:14:49 +0200 Subject: [PATCH 71/75] Fix loging that ensures g_pool.camera_render_size is non-negative --- pupil_src/launchables/eye.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pupil_src/launchables/eye.py b/pupil_src/launchables/eye.py index 4df94afcd9..4ddb686ff9 100644 --- a/pupil_src/launchables/eye.py +++ b/pupil_src/launchables/eye.py @@ -236,7 +236,7 @@ def consume_events_and_render_buffer(): glfw.glfwMakeContextCurrent(main_window) clear_gl_screen() - if 0 <= g_pool.camera_render_size[0] and g_pool.camera_render_size[1]: + if all(c > 0 for c in g_pool.camera_render_size): glViewport(0, 0, *g_pool.camera_render_size) for p in g_pool.plugins: p.gl_display() From c3f0ef719083b9b6e32caba8bd80bb14c8f0cdca Mon Sep 17 00:00:00 2001 From: Roman Roibu Date: Wed, 9 Sep 2020 11:21:25 +0200 Subject: [PATCH 72/75] Only call glClear in eye on_resize only if not minimized --- pupil_src/launchables/eye.py | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/pupil_src/launchables/eye.py b/pupil_src/launchables/eye.py index 4ddb686ff9..95f530609e 100644 --- a/pupil_src/launchables/eye.py +++ b/pupil_src/launchables/eye.py @@ -290,10 +290,14 @@ def on_resize(window, w, h): nonlocal window_size nonlocal content_scale - # Always clear buffers on resize to make sure that there are no overlapping - # artifacts from previous frames. - gl_utils.glClear(gl_utils.GL_COLOR_BUFFER_BIT) - gl_utils.glClearColor(0, 0, 0, 1) + framebuffer_size = glfw.glfwGetFramebufferSize(window) + is_minimized = framebuffer_size is not None and 0 in framebuffer_size + + if not is_minimized: + # Always clear buffers on resize to make sure that there are no overlapping + # artifacts from previous frames. + gl_utils.glClear(gl_utils.GL_COLOR_BUFFER_BIT) + gl_utils.glClearColor(0, 0, 0, 1) active_window = glfw.glfwGetCurrentContext() glfw.glfwMakeContextCurrent(window) From 8a85c534a170d945b9fe2cdb30260d71415b9679 Mon Sep 17 00:00:00 2001 From: Roman Roibu Date: Wed, 9 Sep 2020 14:19:40 +0200 Subject: [PATCH 73/75] Use more compact version of NamedTuple definition --- pupil_src/shared_modules/glfw.py | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/pupil_src/shared_modules/glfw.py b/pupil_src/shared_modules/glfw.py index 5574fa6b7b..0ea6e6acb6 100644 --- a/pupil_src/shared_modules/glfw.py +++ b/pupil_src/shared_modules/glfw.py @@ -520,17 +520,19 @@ class GLFWmonitor(Structure): __py_callbacks__ = {} -_Margins = T.NamedTuple( - "_Margins", [("left", int), ("top", int), ("right", int), ("bottom", int)] -) - +class _Margins(T.NamedTuple): + left: int + top: int + right: int + bottom: int -_Rectangle_Tuple = T.NamedTuple( - "_Rectangle", [("x", int), ("y", int), ("width", int), ("height", int)] -) +class _Rectangle(T.NamedTuple): + x: int + y: int + width: int + height: int -class _Rectangle(_Rectangle_Tuple): def intersection(self, other: "_Rectangle") -> T.Optional["_Rectangle"]: in_min_x = max(self.x, other.x) in_min_y = max(self.y, other.y) From 01e19173f67a394fbff8655f4e23ab60a7b37568 Mon Sep 17 00:00:00 2001 From: Roman Roibu Date: Mon, 14 Sep 2020 12:29:59 +0200 Subject: [PATCH 74/75] Fix is_minimized expression in eye.py on_resize --- pupil_src/launchables/eye.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pupil_src/launchables/eye.py b/pupil_src/launchables/eye.py index 95f530609e..64bb454b15 100644 --- a/pupil_src/launchables/eye.py +++ b/pupil_src/launchables/eye.py @@ -291,7 +291,7 @@ def on_resize(window, w, h): nonlocal content_scale framebuffer_size = glfw.glfwGetFramebufferSize(window) - is_minimized = framebuffer_size is not None and 0 in framebuffer_size + is_minimized = any(c <= 0 for c in framebuffer_size) if not is_minimized: # Always clear buffers on resize to make sure that there are no overlapping From bd95f361b6841f1f0faaa1bd586b96aa7b4c7e7f Mon Sep 17 00:00:00 2001 From: Roman Roibu Date: Mon, 14 Sep 2020 13:14:25 +0200 Subject: [PATCH 75/75] Skip on_resize callback if window is minimized (iconified) --- pupil_src/launchables/eye.py | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/pupil_src/launchables/eye.py b/pupil_src/launchables/eye.py index 64bb454b15..6df8f7f1b2 100644 --- a/pupil_src/launchables/eye.py +++ b/pupil_src/launchables/eye.py @@ -290,14 +290,15 @@ def on_resize(window, w, h): nonlocal window_size nonlocal content_scale - framebuffer_size = glfw.glfwGetFramebufferSize(window) - is_minimized = any(c <= 0 for c in framebuffer_size) - - if not is_minimized: - # Always clear buffers on resize to make sure that there are no overlapping - # artifacts from previous frames. - gl_utils.glClear(gl_utils.GL_COLOR_BUFFER_BIT) - gl_utils.glClearColor(0, 0, 0, 1) + is_minimized = bool(glfw.glfwGetWindowAttrib(window, glfw.GLFW_ICONIFIED)) + + if is_minimized: + return + + # Always clear buffers on resize to make sure that there are no overlapping + # artifacts from previous frames. + gl_utils.glClear(gl_utils.GL_COLOR_BUFFER_BIT) + gl_utils.glClearColor(0, 0, 0, 1) active_window = glfw.glfwGetCurrentContext() glfw.glfwMakeContextCurrent(window)