Skip to content

Commit

Permalink
Merge pull request #2142 from pupil-labs/develop
Browse files Browse the repository at this point in the history
Pupil v3.3 Release Candidate 1
  • Loading branch information
papr authored May 12, 2021
2 parents 2735e95 + c6efbce commit 48eeb2f
Show file tree
Hide file tree
Showing 29 changed files with 664 additions and 165 deletions.
11 changes: 9 additions & 2 deletions pupil_src/launchables/player.py
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,7 @@ def player(
import player_methods as pm
from pupil_recording import PupilRecording
from csv_utils import write_key_value_file
from hotkey import Hotkey

# Plug-ins
from plugin import Plugin, Plugin_List, import_runtime_plugins
Expand All @@ -113,7 +114,11 @@ def player(
from annotations import Annotation_Player
from raw_data_exporter import Raw_Data_Exporter
from log_history import Log_History
from pupil_producers import Pupil_From_Recording, Offline_Pupil_Detection
from pupil_producers import (
DisabledPupilProducer,
Pupil_From_Recording,
Offline_Pupil_Detection,
)
from gaze_producer.gaze_from_recording import GazeFromRecording
from gaze_producer.gaze_from_offline_calibration import (
GazeFromOfflineCalibration,
Expand Down Expand Up @@ -180,6 +185,7 @@ def interrupt_handler(sig, frame):
Raw_Data_Exporter,
Annotation_Player,
Log_History,
DisabledPupilProducer,
Pupil_From_Recording,
Offline_Pupil_Detection,
GazeFromRecording,
Expand Down Expand Up @@ -562,7 +568,7 @@ def set_window_size():
label=chr(0xE2C5),
getter=lambda: False,
setter=do_export,
hotkey="e",
hotkey=Hotkey.EXPORT_START_PLAYER_HOTKEY(),
label_font="pupil_icons",
)
g_pool.quickbar.extend([g_pool.export_button])
Expand All @@ -576,6 +582,7 @@ def set_window_size():
# In priority order (first is default)
("Pupil_From_Recording", {}),
("Offline_Pupil_Detection", {}),
("DisabledPupilProducer", {}),
]
_pupil_producer_plugins = list(reversed(_pupil_producer_plugins))
_gaze_producer_plugins = [
Expand Down
20 changes: 11 additions & 9 deletions pupil_src/launchables/world.py
Original file line number Diff line number Diff line change
Expand Up @@ -100,12 +100,13 @@ def start_stop_eye(eye_id, make_alive):
else:
stop_eye_process(eye_id)

def detection_enabled_getter() -> bool:
return g_pool.pupil_detection_enabled
def detection_enabled_getter() -> int:
return int(g_pool.pupil_detection_enabled)

def detection_enabled_setter(is_on: bool):
def detection_enabled_setter(value: int):
is_on = bool(value)
g_pool.pupil_detection_enabled = is_on
n = {"subject": "set_pupil_detection_enabled", "value": is_on}
n = {"subject": "pupil_detector.set_enabled", "value": is_on}
ipc_pub.notify(n)

try:
Expand Down Expand Up @@ -416,7 +417,8 @@ def on_resize(window, w, h):
)

# Needed, to update the window buffer while resizing
consume_events_and_render_buffer()
with gl_utils.current_context(main_window):
consume_events_and_render_buffer()

def on_window_key(window, key, scancode, action, mods):
g_pool.gui.update_key(key, scancode, action, mods)
Expand Down Expand Up @@ -466,8 +468,8 @@ def get_dt():
g_pool.min_calibration_confidence = session_settings.get(
"min_calibration_confidence", 0.8
)
g_pool.pupil_detection_enabled = session_settings.get(
"pupil_detection_enabled", True
g_pool.pupil_detection_enabled = bool(
session_settings.get("pupil_detection_enabled", True)
)
g_pool.active_gaze_mapping_plugin = None
g_pool.capture = None
Expand All @@ -478,7 +480,7 @@ def get_dt():

def handle_notifications(noti):
subject = noti["subject"]
if subject == "set_pupil_detection_enabled":
if subject == "pupil_detector.set_enabled":
g_pool.pupil_detection_enabled = noti["value"]
elif subject == "start_plugin":
try:
Expand All @@ -494,7 +496,7 @@ def handle_notifications(noti):
g_pool.plugins.clean()
elif subject == "eye_process.started":
noti = {
"subject": "set_pupil_detection_enabled",
"subject": "pupil_detector.set_enabled",
"value": g_pool.pupil_detection_enabled,
}
ipc_pub.notify(noti)
Expand Down
49 changes: 36 additions & 13 deletions pupil_src/shared_modules/accuracy_visualizer.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,29 +10,27 @@
"""

import logging
from collections import namedtuple
import traceback
import typing as T

import OpenGL.GL as gl
import numpy as np
import OpenGL.GL as gl
import scipy.spatial
from pyglui import ui
from pyglui.cygl.utils import draw_points_norm, draw_polyline_norm, RGBA
from scipy.spatial import ConvexHull
from pyglui.cygl.utils import RGBA, draw_points_norm, draw_polyline_norm

from calibration_choreography import (
ChoreographyAction,
ChoreographyMode,
ChoreographyNotification,
)
from plugin import Plugin

from gaze_mapping import gazer_classes_by_class_name, registered_gazer_classes
from gaze_mapping.notifications import (
CalibrationSetupNotification,
CalibrationResultNotification,
CalibrationSetupNotification,
)
from gaze_mapping.utils import closest_matches_monocular

from plugin import Plugin

logger = logging.getLogger(__name__)

Expand Down Expand Up @@ -60,6 +58,13 @@ def empty() -> "CorrelatedAndCoordinateTransformedResult":
camera_space=np.ndarray([]),
)

@property
def is_valid(self) -> bool:
if len(self.norm_space.shape) != 2:
return False
# TODO: Make validity check exhaustive
return True


class CorrelationError(ValueError):
pass
Expand All @@ -80,6 +85,13 @@ def failed() -> "AccuracyPrecisionResult":
correlation=CorrelatedAndCoordinateTransformedResult.empty(),
)

@property
def is_valid(self) -> bool:
if not self.correlation.is_valid:
return False
# TODO: Make validity check exhaustive
return True


class ValidationInput:
def __init__(self):
Expand Down Expand Up @@ -356,10 +368,12 @@ def __handle_validation_data_notification(self, note_dict: dict) -> bool:
return True

def recalculate(self):
NOT_ENOUGH_DATA_COLLECTED_ERR_MSG = (
"Did not collect enough data to estimate gaze mapping accuracy."
)

if not self.recent_input.is_complete:
logger.info(
"Did not collect enough data to estimate gaze mapping accuracy."
)
logger.warning(NOT_ENOUGH_DATA_COLLECTED_ERR_MSG)
return

results = self.calc_acc_prec_errlines(
Expand All @@ -373,6 +387,10 @@ def recalculate(self):
succession_threshold=self.succession_threshold,
)

if not results.is_valid:
logger.warning(NOT_ENOUGH_DATA_COLLECTED_ERR_MSG)
return

accuracy = results.accuracy.result
if np.isnan(accuracy):
self.accuracy = None
Expand Down Expand Up @@ -400,8 +418,13 @@ def recalculate(self):
self.error_lines = results.error_lines
ref_locations = results.correlation.norm_space[1::2, :]
if len(ref_locations) >= 3:
hull = ConvexHull(ref_locations) # requires at least 3 points
self.calibration_area = hull.points[hull.vertices, :]
try:
# requires at least 3 points
hull = scipy.spatial.ConvexHull(ref_locations)
self.calibration_area = hull.points[hull.vertices, :]
except scipy.spatial.qhull.QhullError:
logger.warning("Calibration area could not be calculated")
logger.debug(traceback.format_exc())

@staticmethod
def calc_acc_prec_errlines(
Expand Down
8 changes: 6 additions & 2 deletions pupil_src/shared_modules/annotations.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@
import player_methods as pm
import zmq_tools
from plugin import Plugin
from hotkey import Hotkey


logger = logging.getLogger(__name__)

Expand Down Expand Up @@ -63,12 +65,14 @@ def __init__(self, g_pool, annotation_definitions=None):
self._annotation_list_menu = None

if annotation_definitions is None:
annotation_definitions = [["My annotation", "E"]]
annotation_definitions = [
["My annotation", Hotkey.ANNOTATION_EVENT_DEFAULT_HOTKEY()]
]
self._initial_annotation_definitions = annotation_definitions
self._definition_to_buttons = {}

self._new_annotation_label = "new annotation label"
self._new_annotation_hotkey = "E"
self._new_annotation_hotkey = Hotkey.ANNOTATION_EVENT_DEFAULT_HOTKEY()

def get_init_dict(self):
annotation_definitions = list(self._definition_to_buttons.keys())
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
import audio
from pyglui import ui
from plugin import Plugin
from hotkey import Hotkey

from gaze_mapping.gazer_base import GazerBase
from gaze_mapping import default_gazer_class
Expand Down Expand Up @@ -422,7 +423,7 @@ def validation_setter(should_be_on):
"is_active",
self,
label="C",
hotkey="c",
hotkey=Hotkey.GAZE_CALIBRATION_CAPTURE_HOTKEY(),
setter=calibration_setter,
on_color=self._THUMBNAIL_COLOR_ON,
)
Expand All @@ -431,7 +432,7 @@ def validation_setter(should_be_on):
"is_active",
self,
label="T",
hotkey="t",
hotkey=Hotkey.GAZE_VALIDATION_CAPTURE_HOTKEY(),
setter=validation_setter,
on_color=self._THUMBNAIL_COLOR_ON,
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@
from pyglui.cygl.utils import draw_points
from pyglui.cygl.utils import RGBA

from gl_utils import draw_circle_filled_func_builder

from .gui_monitor import GUIMonitor
from .gui_window import GUIWindow

Expand Down Expand Up @@ -101,6 +103,8 @@ def __init__(self, marker_scale: float):
self.__glfont.set_size(32)
self.__glfont.set_color_float((0.2, 0.5, 0.9, 1.0))
self.__glfont.set_align_string(v_align="center")
# Private helper
self.__draw_circle_filled = draw_circle_filled_func_builder(cache_size=4)

# Public - Marker Management

Expand Down Expand Up @@ -316,22 +320,22 @@ def __draw_circle_marker(
# TODO: adjust num_points such that circles look smooth; smaller circles need less points
# TODO: compare runtimes with `draw_points`

_draw_circle_filled(
self.__draw_circle_filled(
screen_point,
size=self._MARKER_CIRCLE_SIZE_OUTER * radius,
color=RGBA(*self._MARKER_CIRCLE_RGB_OUTER, alpha),
)
_draw_circle_filled(
self.__draw_circle_filled(
screen_point,
size=self._MARKER_CIRCLE_SIZE_MIDDLE * radius,
color=RGBA(*self._MARKER_CIRCLE_RGB_MIDDLE, alpha),
)
_draw_circle_filled(
self.__draw_circle_filled(
screen_point,
size=self._MARKER_CIRCLE_SIZE_INNER * radius,
color=RGBA(*self._MARKER_CIRCLE_RGB_INNER, alpha),
)
_draw_circle_filled(
self.__draw_circle_filled(
screen_point,
size=self._MARKER_CIRCLE_SIZE_FEEDBACK * radius,
color=RGBA(*marker_circle_rgb_feedback, alpha),
Expand Down Expand Up @@ -528,37 +532,3 @@ def _interp_fn(t, b, c, d, start_sample=15.0, stop_sample=55.0):
return 1 - _easeInOutQuad(t - stop_sample, b, c, d - stop_sample)
else:
return 1.0


@functools.lru_cache(4) # 4 circles needed to draw calibration marker
def _circle_points_around_zero(radius: float, num_points: int) -> np.ndarray:
t = np.linspace(0, 2 * np.pi, num_points, dtype=np.float64)
t.shape = -1, 1
points = np.hstack([np.cos(t), np.sin(t)])
points *= radius
return points


@functools.lru_cache(4) # 4 circles needed to draw calibration marker
def _circle_points_offset(
offset: T.Tuple[float, float], radius: float, num_points: int, flat: bool = True
) -> np.ndarray:
# NOTE: .copy() to avoid modifying the cached result
points = _circle_points_around_zero(radius, num_points).copy()
points[:, 0] += offset[0]
points[:, 1] += offset[1]
if flat:
points.shape = -1
return points


def _draw_circle_filled(
screen_point: T.Tuple[float, float], size: float, color: RGBA, num_points: int = 50
):
points = _circle_points_offset(
screen_point, radius=size, num_points=num_points, flat=False
)
gl.glColor4f(color.r, color.g, color.b, color.a)
gl.glEnableClientState(gl.GL_VERTEX_ARRAY)
gl.glVertexPointer(2, gl.GL_DOUBLE, 0, points)
gl.glDrawArrays(gl.GL_POLYGON, 0, points.shape[0])
8 changes: 7 additions & 1 deletion pupil_src/shared_modules/camera_intrinsics_estimation.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,8 @@

from plugin import Plugin

from hotkey import Hotkey

# logging
import logging

Expand Down Expand Up @@ -153,7 +155,11 @@ def get_monitors_idx_list():
)

self.button = ui.Thumb(
"collect_new", self, setter=self.advance, label="I", hotkey="i"
"collect_new",
self,
setter=self.advance,
label="I",
hotkey=Hotkey.CAMERA_INTRINSIC_ESTIMATOR_COLLECT_NEW_CAPTURE_HOTKEY(),
)
self.button.on_color[:] = (0.3, 0.2, 1.0, 0.9)
self.g_pool.quickbar.insert(0, self.button)
Expand Down
Loading

0 comments on commit 48eeb2f

Please sign in to comment.