594 lines
No EOL
24 KiB
Python
594 lines
No EOL
24 KiB
Python
from __future__ import division
|
|
'''
|
|
This is the core part of the simulation framework.
|
|
I am basically decoupling logic from visualization, this module covers the logic.
|
|
'''
|
|
|
|
# import visual as vs
|
|
# from visual import vector as v # for vector operations
|
|
from vector import Vector as v # for vector operations
|
|
import numpy as np
|
|
import cv2, cv
|
|
|
|
# from sklearn.neighbors import KNeighborsRegressor as knn
|
|
# ## (u, v, u^2, v^2, uv, u^2*v^2, 1)
|
|
# _q = lambda p: (p[0], p[1], p[0]*p[0], p[1]*p[1], p[0]*p[1], p[0]*p[1]*p[0]*p[1], 1)
|
|
# _qi = lambda q: (q[0], q[1])
|
|
|
|
# from util import *
|
|
from geom import *
|
|
# from svis import Visualizable # Uncomment if you want visualization
|
|
|
|
# 2D to 2D calibration method from the pupil project
|
|
from pupil.calibrate import get_map_from_cloud
|
|
|
|
point_scale_factor = 10
|
|
|
|
# class GazeSimulation(Visualizable):
|
|
class GazeSimulation:
|
|
_log = True
|
|
def log(self, msg):
|
|
if self._log: print msg
|
|
################################################################################
|
|
## Eye Specific Variables and Methods
|
|
################################################################################
|
|
# difference between center of cornea sphera and the plane containing limbus (based on anatomical data)
|
|
cornea_limbus_offset = 5.25
|
|
# difference between centers of spheres representing sclera and cornea (based on anatomical data)
|
|
sclera_cornea_center_offset = 4.7
|
|
# (based on anatomical data)
|
|
sclera_radius = 11.5
|
|
# (based on anatomical data)
|
|
cornea_radius = 7.8
|
|
# *unit for measurement is millimeter
|
|
|
|
# This direction should always be towards the target 3D point
|
|
# this is just the default value
|
|
pupil_direction = v(1, 0, 0)
|
|
pupil_position = None # computed whenever target is changed
|
|
pupil_positions = [] # 3D positions of the pupil to be projected onto the eye camera's image plane
|
|
|
|
sclera_pos = v(-15, 20, -25) # wrt world coordinate system
|
|
cornea_pos = sclera_pos + pupil_direction * sclera_cornea_center_offset
|
|
|
|
target_position = v(0, 0, 0)
|
|
def recomputeEyeInner(self):
|
|
self.cornea_pos = self.sclera_pos + self.pupil_direction * self.sclera_cornea_center_offset
|
|
|
|
place_eyeball_on_scene_camera = False
|
|
scene_eye_distance_factor = 1.0
|
|
def moveEyeToSceneCamera(self):
|
|
'''
|
|
distance_factor is between 0 and 1, controls where on the
|
|
difference vector between scene camera and eyeball to place
|
|
the eyeball (1 exactly on the target position, 0 does not change anything)
|
|
'''
|
|
if self.place_eyeball_on_scene_camera:
|
|
_diff = v(self.scene_camera.t) - self.sclera_pos
|
|
_diff = _diff * self.scene_eye_distance_factor
|
|
self.sclera_pos = self.sclera_pos + _diff
|
|
eye_camera_pos = v(self.eye_camera.t) + _diff
|
|
x, y, z = eye_camera_pos.x, eye_camera_pos.y, eye_camera_pos.z
|
|
self.eye_camera.setT((x, y, z))
|
|
self.recomputeEyeInner()
|
|
|
|
def setEyeRelativeToSceneCamera(self, xyz):
|
|
'''
|
|
places the eye in xyz relative to scene camera as the origin
|
|
(moves eye camera accordingly)
|
|
xyz: Vector object
|
|
'''
|
|
dest = v(self.scene_camera.t) + xyz # new dest originating from scene camera
|
|
_diff = dest - self.sclera_pos # difference from current pos to dest
|
|
self.sclera_pos = self.sclera_pos + _diff
|
|
eye_camera_pos = v(self.eye_camera.t) + _diff
|
|
x, y, z = eye_camera_pos.x, eye_camera_pos.y, eye_camera_pos.z
|
|
self.eye_camera.setT((x, y, z))
|
|
self.recomputeEyeInner()
|
|
|
|
def get_target_dir(self):
|
|
'''
|
|
Returns the direction of target from the eye
|
|
'''
|
|
return (self.target_position - self.sclera_pos).norm()
|
|
|
|
def adjust_eye(self):
|
|
'''
|
|
Adjusts eye in a way that it gazes at the target
|
|
'''
|
|
self.pupil_direction = self.get_target_dir()
|
|
cornea_pos = self.sclera_pos + self.pupil_direction * self.sclera_cornea_center_offset
|
|
# self.eye_inner.pos = cornea_pos
|
|
|
|
## Old approach which considers pupil center as the point positioned on the surface of
|
|
## the cornea sphere in direction of the gaze
|
|
# self.pupil_position = cornea_pos + self.pupil_direction * self.cornea_radius
|
|
## Now we consider pupil center on the same ray but closer to the cornea sphere's center
|
|
## their displacement is defined by average anatomical data and pupil is consider as the
|
|
## center of the limbus circle
|
|
self.pupil_position = cornea_pos + self.pupil_direction * self.cornea_limbus_offset
|
|
################################################################################
|
|
################################################################################
|
|
## calibration and Test Data
|
|
num_calibration = 25 # calibration points
|
|
num_test = 16
|
|
# TODO: consider the least number of calibration points required, also investigate if accuracy is
|
|
# significantly improved with more points (my guess is that it will not)
|
|
|
|
# TODO: considering measurement unit is mm, adjust these parameters accordingly to simulate more
|
|
# realistic settings
|
|
# TODO: also consider the range of eye movement (50x50 degrees) to compute realistic boundaries
|
|
# for position of gaze targets
|
|
min_xyz = -55 * point_scale_factor, -45 * point_scale_factor, 1000
|
|
max_xyz = 55 * point_scale_factor, 45 * point_scale_factor, 2000
|
|
grid_x_width = 22 * point_scale_factor
|
|
grid_y_width = 18 * point_scale_factor
|
|
calibration_depth, test_depth = None, None
|
|
|
|
calibration_grid = True
|
|
calibration_random_depth = False
|
|
test_grid = False
|
|
test_random_depth = False
|
|
test_random_fixed_depth = False
|
|
def generateCalibrationPts(self, ofr = 0):
|
|
# For now we're considering 25 calibration points (5x5 grid) and 16 test points (inner 4x4 grid)
|
|
# as per the real world experiment. these parameters could be given though, they're hard coded for
|
|
# simplicity
|
|
self.calibration_points = generatePoints(25, self.min_xyz, self.max_xyz,
|
|
grid=self.calibration_grid,
|
|
randomZ=self.calibration_random_depth,
|
|
depth=self.calibration_depth, offset=ofr,
|
|
xoffset=self.scene_camera.t[0],
|
|
yoffset=self.scene_camera.t[1],
|
|
zoffset=self.scene_camera.t[2])
|
|
# Test point for checking spherical coordinates
|
|
# self.calibration_points.append(np.array(v(self.scene_camera.t)+120*v(self.scene_camera.direction) + v(-10, 0, 0)))
|
|
def generateTestPts(self, ofr = 0): # TODO
|
|
min_xyz, max_xyz = self.min_xyz, self.max_xyz
|
|
# if self.num_calibration == 25 and self.num_test == 16 and self.test_grid and self.calibration_grid:
|
|
min_xyz = -55 * point_scale_factor+self.grid_x_width/2., -45 * point_scale_factor+self.grid_y_width/2., 1000
|
|
max_xyz = 55 * point_scale_factor-self.grid_x_width/2., 45 * point_scale_factor-self.grid_y_width/2., 2000
|
|
# The above two lines are also currently hard coded only to match the inner grid with our setting
|
|
|
|
self.test_points = generatePoints(16, min_xyz, max_xyz,
|
|
grid=self.test_grid,
|
|
randomZ=self.test_random_depth,
|
|
randFixedZ=self.test_random_fixed_depth,
|
|
depth=self.test_depth, offset=ofr,
|
|
xoffset=self.scene_camera.t[0],
|
|
yoffset=self.scene_camera.t[1],
|
|
zoffset=self.scene_camera.t[2])
|
|
# self.test_points = map(lambda p: v(p)-v(self.scene_camera.t), self.test_points)
|
|
def setCalibrationDepth(self, depth):
|
|
'''
|
|
sets calibration depth wrt to scene camera
|
|
'''
|
|
self.calibration_random_depth = False
|
|
if isinstance(depth, list):
|
|
# adding scene camera depth to each calibration depth
|
|
self.calibration_depth = map(lambda d: d+self.scene_camera.t[2], depth)
|
|
else:
|
|
self.calibration_depth = self.scene_camera.t[2] + depth
|
|
self.reset()
|
|
|
|
def setTestDepth(self, depth):
|
|
'''
|
|
sets test depth wrt to scene camera
|
|
'''
|
|
self.test_random_depth = False
|
|
self.test_random_fixed_depth = False
|
|
self.test_depth = self.scene_camera.t[2] + depth
|
|
self.reset()
|
|
|
|
|
|
active_gaze_point = None
|
|
################################################################################
|
|
################################################################################
|
|
## Camera Setup
|
|
def setupCameras(self):
|
|
# Eye Camera
|
|
self.eye_camera = Camera(label='Eye Camera', f = 13, fov=np.radians(50)) # TODO: f and fov must be realistic
|
|
# self.eye_camera.setT((-15, 20, 0))
|
|
self.eye_camera.setT((self.sclera_pos + v(0, 0,
|
|
self.sclera_cornea_center_offset + \
|
|
self.cornea_radius + \
|
|
45)).tuple()) # 35mm in front of the surface of the eye
|
|
# rotating around y axis, camera now pointing towards negative Z
|
|
# with this rotation, also x becomes inverted
|
|
self.eye_camera.setR((0, np.pi, 0))
|
|
# self.eye_camera.setDir((0, 0, -1)) # deprecated
|
|
|
|
# Scene Camera
|
|
self.scene_camera = Camera(label='Scene Camera', f = 50, fov=np.radians(100)) # TODO: f and fov must be realistic
|
|
# self.scene_camera.setT((0, 20, 0))
|
|
self.scene_camera.setT((self.sclera_pos + v(-25, 25, 25)).tuple()) # sclera_pos is v(-15, 20, -25)
|
|
self.scene_camera.setR((0, 0, 0)) # camera pointing towards positive Z
|
|
# self.scene_camera.setDir((0, 0, 1)) # deprecated
|
|
################################################################################
|
|
################################################################################
|
|
## Simulations Steps
|
|
|
|
def init(self):
|
|
self.setupCameras()
|
|
self.reset()
|
|
# self.moveEyeToSceneCamera() # Uncomment to place eyeball center on scene camera
|
|
|
|
def reset(self, ofr=0.5):
|
|
self.pupil_positions = []
|
|
self.c = 0
|
|
self.calibration_points = []
|
|
self.test_points = []
|
|
self.generateCalibrationPts(ofr = ofr)
|
|
self.generateTestPts(ofr = ofr)
|
|
|
|
# these might change if points are generated on a grid
|
|
self.num_calibration = len(self.calibration_points)
|
|
self.num_test = len(self.test_points)
|
|
if self.place_eyeball_on_scene_camera:
|
|
self.moveEyeToSceneCamera()
|
|
|
|
def gazeAtNextPoint(self, calibration = True):
|
|
'''
|
|
Gazes at the next calibration/Test target point and records both pupil and
|
|
target positions.
|
|
'''
|
|
if calibration:
|
|
if self.c >= self.num_calibration: # finished processing calibration points
|
|
self.log('Processed all calibration points...')
|
|
return False
|
|
else:
|
|
self.target_position = v(self.calibration_points[self.c])
|
|
self.active_gaze_point = self.target_position
|
|
else:
|
|
if self.c >= self.num_test: # finished processing calibration points
|
|
self.log('Processed all test points...')
|
|
return False
|
|
else:
|
|
self.target_position = v(self.test_points[self.c])
|
|
self.active_gaze_point = self.target_position
|
|
|
|
self.c+=1
|
|
self.adjust_eye()
|
|
self.pupil_positions.append(self.pupil_position)
|
|
return True
|
|
|
|
def processData(self, calibration = True):
|
|
proceed = self.gazeAtNextPoint(calibration)
|
|
while proceed:
|
|
proceed = self.gazeAtNextPoint(calibration)
|
|
|
|
tr_target_projections_3d = None
|
|
tr_pupil_projections_3d = None
|
|
def computeGazeMap(self):
|
|
targets = map(lambda p:v(p), self.calibration_points)
|
|
targets = map(lambda p: self.scene_camera.project((p.x, p.y, p.z, 1)), targets)
|
|
pupil_locations = map(lambda p: self.eye_camera.project((p.x, p.y, p.z, 1)), self.pupil_positions)
|
|
# Filtering out points outside of the image plane
|
|
old = len(targets)
|
|
invalid = []
|
|
hw = self.scene_camera.image_width/2.
|
|
for i in xrange(len(targets)):
|
|
p = targets[i]
|
|
if abs(p[0])>hw or abs(p[1])>hw:
|
|
invalid.append(i)
|
|
for i in invalid[::-1]:
|
|
targets = targets[:i] + targets[i+1:]
|
|
pupil_locations = pupil_locations[:i] + pupil_locations[i+1:]
|
|
self.calibration_points = self.calibration_points[:i] + self.calibration_points[i+1:]
|
|
self.log('Removed %s invalid calibration points...' % (old-len(targets)))
|
|
# print 'Removed %s invalid target points...' % (old-len(targets))
|
|
|
|
# to get calibration points p and t, refer to self.calibration_points and
|
|
# self.pupil_locations (also target_projections has the corresponding projection
|
|
# points for the targets)
|
|
# NOTE: calibration points are in world CCS
|
|
self.tr_3d_pupil_locations = self.pupil_positions[:]
|
|
self.tr_pupil_locations = pupil_locations[:] # for future use
|
|
self.tr_target_projections = targets[:]
|
|
|
|
# 3D position of target point projections
|
|
base = v(self.scene_camera.t) + v(self.scene_camera.direction) * self.scene_camera.f
|
|
pts = map(lambda p: np.array((p[0], p[1], 0)), targets) # adding extra z = 0
|
|
self.tr_target_projections_3d = map(lambda p:p+np.array([base.x, base.y, base.z]), pts)
|
|
|
|
# 3D position of pupil point projections
|
|
base = v(self.eye_camera.t) + v(self.eye_camera.direction) * self.eye_camera.f
|
|
pts = map(lambda p: np.array((p[0], p[1], 0)), pupil_locations) # adding extra z = 0
|
|
# TODO: apply back transformation to world CS
|
|
# temporary solution with our setting: x <> -x
|
|
pts = map(lambda p: np.array((-p[0], p[1], 0)), pupil_locations)
|
|
|
|
self.tr_pupil_projections_3d = map(lambda p:p+np.array([base.x, base.y, base.z]), pts)
|
|
|
|
# Normalizing points
|
|
self.log('Normalizing points...')
|
|
targets = self.scene_camera.getNormalizedPts(targets)
|
|
pupil_locations = self.eye_camera.getNormalizedPts(pupil_locations)
|
|
# Computing polynomial map
|
|
self.log('Computing polynomial map...')
|
|
|
|
self.mp = get_map_from_cloud(np.array([(pupil_locations[i][0], pupil_locations[i][1],
|
|
targets[i][0], targets[i][1]) for i in xrange(len(targets))]))
|
|
# ngh = knn(n_neighbors = 1)
|
|
# ngh.fit(map(_q, pupil_locations), map(_q, targets))
|
|
# self.mp = lambda p: _qi(ngh.predict(_q(p))[0])
|
|
|
|
self.log('Successfully computed map...')
|
|
# Converting these to numpy arrays to support nice operands
|
|
targets = np.array(targets)
|
|
pupil_locations = np.array(pupil_locations)
|
|
|
|
self.log('Scene Camera Image Width: %s' % self.scene_camera.image_width)
|
|
|
|
self.c=0
|
|
self.pupil_positions = []
|
|
|
|
|
|
te_target_projections_3d = None
|
|
te_pupil_projections_3d = None
|
|
def projectAndMap(self):
|
|
targets_3d = map(lambda vec: v(vec), self.test_points)
|
|
# Processing Projections
|
|
targets = map(lambda p: v(p), self.test_points)
|
|
targets = map(lambda p: self.scene_camera.project((p.x, p.y, p.z, 1)), targets)
|
|
pupil_locations = map(lambda p: self.eye_camera.project((p.x, p.y, p.z, 1)), self.pupil_positions)
|
|
# Filtering out points outside of the image plane
|
|
old = len(targets)
|
|
invalid = []
|
|
hw = self.scene_camera.image_width/2.
|
|
for i in xrange(len(targets)):
|
|
p = targets[i]
|
|
if abs(p[0])>hw or abs(p[1])>hw:
|
|
invalid.append(i)
|
|
for i in invalid[::-1]:
|
|
targets = targets[:i] + targets[i+1:]
|
|
pupil_locations = pupil_locations[:i] + pupil_locations[i+1:]
|
|
self.test_points = self.test_points[:i] + self.test_points[i+1:]
|
|
self.log('Removed %s invalid test points...' % (old-len(targets)))
|
|
|
|
self.te_3d_pupil_locations = self.pupil_positions[:]
|
|
self.te_pupil_locations = pupil_locations[:] # for future use
|
|
self.te_target_projections = targets[:]
|
|
|
|
# 3D position of target point projections
|
|
base = v(self.scene_camera.t) + v(self.scene_camera.direction) * self.scene_camera.f
|
|
pts = map(lambda p: np.array((p[0], p[1], 0)), targets) # adding extra z = 0
|
|
self.te_target_projections_3d = map(lambda p:p+np.array([base.x, base.y, base.z]), pts)
|
|
|
|
# 3D position of pupil point projections
|
|
base = v(self.eye_camera.t) + v(self.eye_camera.direction) * self.eye_camera.f
|
|
pts = map(lambda p: np.array((p[0], p[1], 0)), pupil_locations) # adding extra z = 0
|
|
# TODO: apply back transformation to world CS
|
|
# temporary solution: x <> -x
|
|
pts = map(lambda p: np.array((-p[0], p[1], 0)), pupil_locations)
|
|
self.te_pupil_projections_3d = map(lambda p:p+np.array([base.x, base.y, base.z]), pts)
|
|
|
|
if not len(targets): # all points are invalid, terminate simulation!
|
|
return -1
|
|
|
|
# Normalizing points
|
|
self.log('Normalizing points...')
|
|
targets = np.array(self.scene_camera.getNormalizedPts(targets))
|
|
pupil_locations = np.array(self.eye_camera.getNormalizedPts(pupil_locations))
|
|
# Compute mapped points
|
|
self.log('Computing %s mapped points...' % len(pupil_locations))
|
|
mapped_targets = np.array(map(self.mp, np.array(pupil_locations)))
|
|
self.log('Computed %s mapped points...' % len(mapped_targets))
|
|
|
|
C = v(self.scene_camera.t)
|
|
|
|
# Denormalize points and add camera plane's z to each
|
|
base = C + v(self.scene_camera.direction) * self.scene_camera.f
|
|
targets = self.scene_camera.getDenormalizedPts(targets)
|
|
targets = np.array(map(lambda p: np.concatenate((p + np.array([base.x, base.y]), np.array([base.z])), axis=0), targets))
|
|
|
|
mapped_targets = self.scene_camera.getDenormalizedPts(mapped_targets)
|
|
mapped_targets = np.array(map(lambda p: np.concatenate((p + np.array([base.x, base.y]), np.array([base.z])), axis=0), mapped_targets))
|
|
|
|
N = len(targets_3d)
|
|
AE = [getAngularDiff(v(targets[i]), v(mapped_targets[i]), C) for i in xrange(N)]
|
|
AAE = np.mean(AE)
|
|
AE_STD = np.std(AE)
|
|
self.log('AE = %s, AAE = %s' % (AE, AAE))
|
|
|
|
targets_3d = map(lambda vec: vec-v(self.scene_camera.t), targets_3d) # originating from scene camera
|
|
mapped_targets = map(lambda vec: vec-v(self.scene_camera.t), mapped_targets)
|
|
mapped_targets = map(lambda pt: pt/pt[2], mapped_targets) # making z = 1
|
|
mapped_3d = map(lambda mt: v(v(mt[0])*mt[1].z), zip(mapped_targets, targets_3d))
|
|
# Computing physical distance error (in meters)
|
|
PHE = list((u-v).mag/1000. for u,v in zip(targets_3d, mapped_3d))
|
|
N = len(targets_3d)
|
|
APHE = np.mean(PHE)
|
|
PHE_STD = np.std(PHE)
|
|
PHE_m, PHE_M = min(PHE), max(PHE)
|
|
# ret.extend([APHE, PHE_VAR, PHE_STD, PHE_m, PHE_M])
|
|
return AAE, PHE, AE_STD, PHE_STD
|
|
|
|
################################################################################
|
|
################################################################################
|
|
def __init__(self, log=True):
|
|
self._log=log
|
|
self.init()
|
|
|
|
def runCalibration(self):
|
|
self.reset()
|
|
self.processData(calibration = True)
|
|
self.computeGazeMap()
|
|
|
|
def runTest(self):
|
|
self.processData(calibration = False)
|
|
# AAE = self.projectAndMap()
|
|
return self.projectAndMap()
|
|
|
|
def runSimulation(self):
|
|
'''
|
|
Runs a single simulation with default parameters and logging enabled
|
|
'''
|
|
self.runCalibration()
|
|
return self.runTest()
|
|
|
|
|
|
def computeGazeMapFromRealData(self, pupil_locations, targets, scene_camera_dimensions):
|
|
'''
|
|
pupil_locations are gaze positions obtained from pupil tracker. they are assumed to be normalized
|
|
targets are 2D projections of markers on scene camera. they are not assumed to be normalized.
|
|
(would be normalized here)
|
|
scene_camera_dimensions should be width and height of scene_camera images
|
|
'''
|
|
# Normalizing points
|
|
self.log('Normalizing target points...')
|
|
px, py = np.array(scene_camera_dimensions)/2.
|
|
w, h = np.array(scene_camera_dimensions)
|
|
targets = map(lambda p:np.array([(p[0] - px + w/2)/w,
|
|
(p[1] - py + h/2)/h]), targets)
|
|
# Computing polynomial map
|
|
self.log('Computing polynomial map...')
|
|
self.mp = get_map_from_cloud(np.array([(pupil_locations[i][0], pupil_locations[i][1],
|
|
targets[i][0], targets[i][1]) for i in xrange(len(targets))]))
|
|
self.log('Successfully computed map...')
|
|
|
|
def projectAndMapRealData(self, pupil_locations, targets, scene_camera_dimensions, targets_3d, camera_matrix, dist_coeffs):
|
|
ret = []
|
|
# Normalizing points
|
|
# self.log('Normalizing points...')
|
|
# px, py = np.array(scene_camera_dimensions)/2.
|
|
# w, h = np.array(scene_camera_dimensions)
|
|
# targets = map(lambda p:np.array([(p[0] - px + w/2)/w,
|
|
# (p[1] - py + h/2)/h]), targets)
|
|
# Compute mapped points
|
|
self.log('Computing %s mapped points...' % len(pupil_locations))
|
|
mapped_targets = np.array(map(self.mp, np.array(pupil_locations)))
|
|
self.log('Computed %s mapped points...' % len(mapped_targets))
|
|
|
|
self.log('Denormalizing estimated points...')
|
|
px, py = np.array(scene_camera_dimensions)/2.
|
|
w, h = np.array(scene_camera_dimensions)
|
|
offset = np.array([w/2-px, h/2-py])
|
|
mapped_targets = map(lambda p:(p*np.array([w, h]))-offset, mapped_targets)
|
|
|
|
|
|
N = len(targets)
|
|
PE = list(np.linalg.norm(b-a) for a,b in zip(targets, mapped_targets))
|
|
APE = sum(PE)/N
|
|
VAR = sum((pe - APE)**2 for pe in PE)/N
|
|
STD = np.sqrt(VAR)
|
|
m, M = min(PE), max(PE)
|
|
|
|
# ret.append(['pixel_error: APE, VAR, STD, min, MAX, data', APE, VAR, STD, m, M, PE])
|
|
ret.extend([APE, VAR, STD, m, M])
|
|
|
|
self.log('Computing 3D back projection of estimated points...')
|
|
# mapped_3d = cv2.undistortPoints(np.array([mapped_targets]), cameraMatrix=camera_matrix, distCoeffs=np.array([0, 0, 0, 0, 0]))
|
|
mapped_3d = cv2.undistortPoints(np.array([mapped_targets]), cameraMatrix=camera_matrix, distCoeffs=dist_coeffs)
|
|
mapped_3d = map(lambda p: [p[0], p[1], 1], mapped_3d[0])
|
|
# print v(mapped_3d[0]).mag, np.sqrt(mapped_3d[0][0]**2 + mapped_3d[0][1]**2)
|
|
|
|
AE = list(np.degrees(np.arctan((v(p[0]).cross(v(p[1]))/(v(p[0]).dot(v(p[1])))).mag)) for p in zip(mapped_3d, targets_3d))
|
|
N = len(targets_3d)
|
|
AAE = sum(AE)/N
|
|
VAR = sum((ae - AAE)**2 for ae in AE)/N
|
|
STD = np.sqrt(VAR)
|
|
m, M = min(AE), max(AE)
|
|
ret.extend([AAE, VAR, STD, m, M])
|
|
# multiplying corresponding ray with depth of ground truth target to get intersection
|
|
# with its depth plane
|
|
targets_3d = map(lambda vec: v(vec), targets_3d)
|
|
mapped_3d = map(lambda mt: v(v(mt[0])*mt[1].z), zip(mapped_3d, targets_3d))
|
|
# Computing physical distance error (in meters)
|
|
PHE = list((u-v).mag for u,v in zip(targets_3d, mapped_3d))
|
|
N = len(targets_3d)
|
|
APHE = sum(PHE)/N
|
|
PHE_VAR = sum((phe - APHE)**2 for phe in PHE)/N
|
|
PHE_STD = np.sqrt(VAR)
|
|
PHE_m, PHE_M = min(PHE), max(PHE)
|
|
ret.extend([APHE, PHE_VAR, PHE_STD, PHE_m, PHE_M])
|
|
|
|
return ret
|
|
|
|
|
|
def perform2D2DCalibrationOnReadData(self, cp, ct, scene_camera_dimensions):
|
|
'''
|
|
cp, ct are calibration pupil points (normalized) and target points respectively
|
|
'''
|
|
self.computeGazeMapFromRealData(cp, ct, scene_camera_dimensions)
|
|
|
|
def run2D2DTestOnRealData(self, tp, tt, scene_camera_dimensions, targets_3d, camera_matrix, dist_coeffs):
|
|
'''
|
|
tp, tt are test pupil points (normalized) and target points respectively
|
|
'''
|
|
return self.projectAndMapRealData(tp, tt, scene_camera_dimensions, targets_3d, camera_matrix, dist_coeffs)
|
|
|
|
def runCustomSimulation(self, args):
|
|
self.calibration_grid, self.calibration_random_depth, self.test_grid, self.test_random_depth, self.test_random_fixed_depth = args
|
|
return self.runSimulation()
|
|
|
|
|
|
display_pupil_position = True
|
|
display_calibration_points = True
|
|
display_test_points = False
|
|
display_eye_camera = True
|
|
display_scene_camera = True
|
|
calibration = False #
|
|
display_test_point_rays = False #
|
|
display_active_gaze_point = False
|
|
rays = []
|
|
|
|
def draw(self):
|
|
from util import drawAxes, drawLine
|
|
from svis import drawCameraFrame
|
|
## World Axes
|
|
drawAxes(None, vs.color.white, 13, (0, 0, 0))
|
|
## Eyeball component (outer sphere)
|
|
eye_outer = vs.sphere(pos=self.sclera_pos,
|
|
radius=self.sclera_radius,
|
|
color=vs.color.red)
|
|
## Eyeball component (inner sphere)
|
|
eye_inner = vs.sphere(pos=self.cornea_pos,
|
|
radius = self.cornea_radius,
|
|
color=vs.color.white)
|
|
self.recomputeEyeInner()
|
|
eye_inner.pos = self.cornea_pos
|
|
|
|
## Pupil position
|
|
if self.display_pupil_position:
|
|
vs.points(pos=[self.pupil_position], size=5, color=vs.color.black)
|
|
## calibration points
|
|
if self.display_calibration_points:
|
|
vs.points(pos=self.calibration_points, size=10, color=vs.color.yellow)
|
|
## Test points
|
|
if self.display_test_points:
|
|
vs.points(pos=self.test_points, size=10, color=vs.color.blue)
|
|
## Eye camera
|
|
if self.display_eye_camera:
|
|
drawCameraFrame(self.eye_camera)
|
|
## Scene camera
|
|
if self.display_scene_camera:
|
|
drawCameraFrame(self.scene_camera)
|
|
# Display rays from scene camera towards calibration points
|
|
if self.display_calibration_point_rays:
|
|
# Cast rays from scene camera towards calibration points
|
|
for point in self.calibration_points:
|
|
diff = vs.vector(point) - vs.vector(self.scene_camera.t)
|
|
drawLine(None, vs.vector(self.scene_camera.t), diff.mag, diff.norm())
|
|
# Display rays from scene camera towards test points
|
|
if self.display_test_point_rays:
|
|
# Cast rays from scene camera towards calibration points
|
|
for point in self.test_points:
|
|
diff = vs.vector(point) - vs.vector(self.scene_camera.t)
|
|
drawLine(None, vs.vector(self.scene_camera.t), diff.mag, diff.norm())
|
|
|
|
# active gaze point
|
|
if self.display_active_gaze_point and self.active_gaze_point:
|
|
vs.points(pos=[self.active_gaze_point], size=10, color=vs.color.red)
|
|
|
|
if self.tr_target_projections_3d:
|
|
vs.points(pos=self.tr_target_projections_3d, size=7, color=vs.color.red)
|
|
if self.tr_pupil_projections_3d:
|
|
vs.points(pos=self.tr_pupil_projections_3d, size=7, color=vs.color.red)
|
|
|
|
if self.te_target_projections_3d:
|
|
vs.points(pos=self.te_target_projections_3d, size=7, color=vs.color.blue)
|
|
|
|
if self.rays:
|
|
for ray in self.rays:
|
|
drawLine(None, tuple(ray.position), ray.length, tuple(ray.direction), ray.color) |