gazesim/code/parallax_analysis.py

907 lines
36 KiB
Python

from __future__ import division
import os, sys
# import seaborn
from pylab import rcParams
import cv2
import numpy as np
from numpy import linalg as LA
from time import time
from itertools import combinations
import matplotlib.pyplot as plt
from matplotlib.pyplot import legend
import matplotlib.patches as mpatches
from minimize import findInitialW, _q, g, minimizeEnergy, g3D3D, findW3D3D
from minimize import g as gaze_ray
from geom import getSphericalCoords, getAngularDiff
from vector import Vector as v
from sim import GazeSimulation
from recording.util.tools import is_outlier
from recording.tracker import readCameraParams
from geom import getRotationMatrix
import pdb
PARTICIPANTS = ['p10', 'p16', 'p13', 'p24', 'p5', 'p14', 'p26', 'p12', 'p20', 'p7', 'p15', 'p11', 'p21', 'p25']
class Experiment:
def performExperiment(self):
print 'Running Experiment...'
start = time()
self.__run__()
self.runningTime = time() - start
print 'Running Time:', self.runningTime
def __run__(self):
pass
class Parallax2Dto2DMapping(Experiment):
def __run__(self):
sim = GazeSimulation(log = False)
sim.place_eyeball_on_scene_camera = False
sim.setEyeRelativeToSceneCamera(v(-65, -33, -73))
sim.setCalibrationDepth(1 * 1000) # mm, wrt scene camera
sim.setTestDepth(1.5 * 1000)
sim.calibration_grid = True
sim.calibration_random_depth = False
sim.test_grid = True
sim.test_random_depth = False
sim.test_random_fixed_depth = False
sim.reset()
print 'scene_camera', sim.scene_camera.t
print 'calibration'
print len(sim.calibration_points)
print min(np.array(sim.calibration_points)[:,0]) - sim.scene_camera.t[0], max(np.array(sim.calibration_points)[:,0]) - sim.scene_camera.t[0]
print min(np.array(sim.calibration_points)[:,1]) - sim.scene_camera.t[1], max(np.array(sim.calibration_points)[:,1]) - sim.scene_camera.t[1]
print 'depths', set(np.array(sim.calibration_points)[:,2])
print len(sim.test_points)
print min(np.array(sim.test_points)[:,0]) - sim.scene_camera.t[0], max(np.array(sim.test_points)[:,0]) - sim.scene_camera.t[0]
print min(np.array(sim.test_points)[:,1]) - sim.scene_camera.t[1], max(np.array(sim.test_points)[:,1]) - sim.scene_camera.t[1]
print 'depths', set(np.array(sim.test_points)[:,2])
plt.ylabel('Y (mm)')
plt.xlabel('X (mm)')
plt.plot(np.array(sim.calibration_points)[:,0], np.array(sim.calibration_points)[:,1], 'bo')
plt.plot(np.array(sim.test_points)[:,0], np.array(sim.test_points)[:,1], 'ro')
plt.show()
# self.sim = sim
# visualize the setting in windows
class Parallax2Dto3DMapping(Experiment):
'''
IMPORTANT!
In all experiments, scene camera's rvec = (0, 0, 0) i.e. the corresponding rotation matrix is the identity
matrix therefore I have not included the dot production with this rotation matrix to convert points in world
coordinates into scene camera coordinates. however, one should know that if the scene camera is rotated
differently this transformation is essential.
'''
def __run__(self):
sim = GazeSimulation(log = False)
sim.place_eyeball_on_scene_camera = False
sim.setEyeRelativeToSceneCamera(v(-65, -33, -73)) # based on actual measurements in our study
sim.setCalibrationDepth(1 * 1000) # mm, wrt scene camera
sim.setTestDepth(1.5 * 1000)
sim.calibration_grid = True
sim.calibration_random_depth = False
sim.test_grid = True
sim.test_random_depth = False
sim.test_random_fixed_depth = False
depths = map(lambda d:d*1000, [1, 1.25, 1.5, 1.75, 2.0])
print '> Computing results for multiple calibration depths...'
results, results_std = [], []
for num_of_calibration_depths in xrange(1, 6): # from 1 calibration depths to 5
print '> Considering only %s calibration depth(s)...' %num_of_calibration_depths
sim.reset()
aae_2ds_aae = []
aae_2ds_phe = []
aae_3ds_aae = []
aae_3ds_phe = []
aae_3D3Ds = [] # angular error
for calibs in combinations(depths, num_of_calibration_depths):
# Now calibs is a set of depths from each of which we need calibration data
print 'Current calibration depths', calibs
calibs = list(calibs)
cp, ct = [], []
sim.reset()
sim.setCalibrationDepth(calibs)
# Perform calibration
sim.runCalibration()
cp, ct, p3d = sim.tr_pupil_locations, sim.calibration_points, sim.tr_3d_pupil_locations
# target positions are computed relative to the scene CCS
ti = map(lambda target: v(target) - v(sim.scene_camera.t), ct)
# Computing pupil pose for each gaze
ni = map(lambda p: (v(p)-v(sim.sclera_pos)).norm(), p3d) # ground truth gaze vectors
w, e, w0 = minimizeEnergy(cp, ti)
e = v(e)
# transforming pupil pose to eye camera CS
eyeR = np.array(sim.eye_camera.R[:3])
ni = map(lambda pose: eyeR.dot(np.array(pose)), ni)
R, e3d3d = minimizeEnergy(ni, ti, pose_given=True)
# R = LA.inv(R)
e3d3d = v(e3d3d)
# Now we have calibration data from multiple depths, we can test on all depths
for test_depth in depths:
sim.setTestDepth(test_depth)
aae_2d_aae, aae_2d_phe, aae_2d_std, _ = sim.runTest() # last one is PHE std
aae_2ds_aae.append((aae_2d_aae, aae_2d_std))
aae_2ds_phe.append(aae_2d_phe)
# Fetching test points
t, p, p3d = sim.test_points, sim.te_pupil_locations, sim.te_3d_pupil_locations
t = map(lambda target: v(target) - v(sim.scene_camera.t), t) # target coords in scene CCS
# 3D3D
t_3d3d = t[:]
ni = map(lambda p: v(v(p)-v(sim.sclera_pos)).norm(), p3d) # ground truth gaze vectors
# transforming pupil pose to eye camera CS
ni = map(lambda r: v(eyeR.dot(np.array(r))), ni)
# applying estimated rotation to pose vector in eye camera coordinates (Rn)
# R is estimated rotation between scene camera and eye coordinate system (not eye camera!)
# in other words, R is the rotation part of e
Rni = map(lambda n: v(R.dot(np.array(n))), ni) # now ready to compare Rn with t-e
# Intersecting gaze rays originating from the eye with the planes defined by each
# target. then we can simply compute angular error between each intersection and
# the corresponding 3D target
gis = map(lambda vec: v(vec), Rni) # gaze rays originating from eyeball
# we multiply g such that it hits t's z-plane i.e. multiply all coordinates by factor (t.z-e.z)/g.z
# then we add e to the final g so that it originates from scene camera. now both g and t are in the
# same coordinate system and originate from the same point, so we can compare them
gprimes = map(lambda tg: v(((tg[0].z - e3d3d.z)/tg[1].z)*tg[1] + e3d3d), zip(t_3d3d, gis))
AE = list(np.degrees(np.arctan((v(p[0]).cross(p[1])/(v(p[0]).dot(p[1]))).mag)) for p in zip(gprimes, t_3d3d))
N = len(t)
AAE = np.mean(AE)
STD = np.std(AE)
m, M = min(AE), max(AE)
aae_3D3Ds.append((AAE, STD))
qi = map(_q, p) # computing feature vectors from raw pupil coordinates in 2D
# computing unit gaze vectors corresponding to pupil positions
# here we use the computed mapping matrix w
gis = map(lambda q: g(q, w), qi)
# Intersecting gaze rays originating from the eye with the planes defined by each
# target. then we can simply compute angular error between each intersection and
# the corresponding 3D target
t = map(lambda vec: v(vec), t)
gis = map(lambda vec: v(vec), gis)
gprimes = map(lambda tg: v(((tg[0].z - e.z)/tg[1].z)*tg[1] + e), zip(t, gis))
AE = list(np.degrees(np.arctan((v(p[0]).cross(p[1])/(v(p[0]).dot(p[1]))).mag)) for p in zip(gprimes, t))
N = len(t)
AAE = np.mean(AE)
STD = np.std(AE)
m, M = min(AE), max(AE)
# Computing physical distance error (in meters)
PHE = list((u-v).mag/1000 for u,v in zip(t, gprimes))
N = len(t)
APHE = np.mean(PHE)
PHE_STD = np.std(PHE)
PHE_m, PHE_M = min(PHE), max(PHE)
aae_3ds_aae.append((AAE, STD))
aae_3ds_phe.append((PHE, PHE_STD))
# results only contains AAE
results.append([np.mean(np.array(aae_2ds_aae)[:,0]), np.mean(np.array(aae_3ds_aae)[:,0]), np.mean(np.array(aae_3D3Ds)[:,0])])
results_std.append([np.std(np.array(aae_2ds_aae)[:,0]), np.std(np.array(aae_3ds_aae)[:,0]), np.std(np.array(aae_3D3Ds)[:,0])])
######################################################################################################
# plot code based on EffectNumberofClusters.py
mean2D2D = [res[0] for res in results]
mean2D3D = [res[1] for res in results]
mean3D3D = [res[2] for res in results]
std2D2D = [res[0] for res in results_std]
std2D3D = [res[1] for res in results_std]
std3D3D = [res[2] for res in results_std]
N = 5
ind = np.asarray([0.25,1.25,2.25,3.25,4.25])
width = 0.5 # the width of the bars
x2 = [0.45,1.45,2.45,3.45,4.45]
x4 = [0.55,1.55,2.55,3.55,4.55]
x6 = [0.50,1.50,2.50,3.50,4.50]
fig = plt.figure(figsize=(14.0, 10.0))
ax = fig.add_subplot(111)
rects1 = ax.errorbar(x2, mean2D2D,yerr=[std2D2D,std2D2D],fmt='o',color='red',ecolor='red',lw=3, capsize=5, capthick=2)
plt.plot(x2, mean2D2D, marker="o", linestyle='-',lw=3,color='red',label = r'2D-to-2D')
rects2 =ax.errorbar(x4, mean2D3D,yerr=[std2D3D,std2D3D],fmt='o',color='blue',ecolor='blue',lw=3, capsize=5, capthick=2)
plt.plot(x4, mean2D3D, marker="o", linestyle='-',lw=3,color='blue', label = r'2D-to-3D')
rects3 =ax.errorbar(x6, mean3D3D,yerr=[std3D3D,std3D3D],fmt='o',color='blue',ecolor='blue',lw=3, capsize=5, capthick=2)
plt.plot(x6, mean3D3D, marker="o", linestyle='-',lw=3,color='blue', label = r'3D-to-3D')
legend(fontsize=20,loc='upper right')
ax.set_ylabel(r'Angular Error',fontsize=22)
ax.set_xlabel(r'Number of Calibration Depths',fontsize=22)
ax.set_xticks(ind+0.25)
ax.set_xticklabels( ('D1', 'D2', 'D3','D4', 'D5') ,fontsize=18)
TOPICs = [0.0,0.5,1.5,2.5,3.5,4.5,5.0]
print TOPICs
LABELs = ["",r'1',r'2', r'3', r'4', r'5', ""]
plt.xticks(TOPICs, LABELs,fontsize=18)
TOPICS = [0.5,1,1.5,2,2.5,3,3.5,4,4.5,5]
print TOPICS
LABELS = [r'0.5', r'1',r'1.5', r'2',r'2.5', r'3',r'3.5', r'4',r'4.5',r'5']
plt.yticks(TOPICS, LABELS,fontsize=18)
def autolabel(rects):
# attach some text labels
for rect in rects:
height = rect.get_height()
ax.text(0.26+rect.get_x()+rect.get_width()/2., height +0.35, "%.2f"%float(height),
ha='center', va='bottom',fontweight='bold',fontsize=13.5)
left = 0.1 # the left side of the subplots of the figure
right = 0.975 # the right side of the subplots of the figure
bottom = 0.075 # the bottom of the subplots of the figure
top = 0.925 # the top of the subplots of the figure
wspace = 0.2 # the amount of width reserved for blank space between subplots
hspace = 0.4 # the amount of height reserved for white space between subplots
plt.subplots_adjust(left=left, bottom=bottom, right=right, top=top, wspace=wspace, hspace=hspace)
plt.show()
######################################################################################################
self.sim = sim
class Parallax3Dto3DMapping(Experiment): # GT pupil pose instead of estimating the pose
def __run__(self):
sim = GazeSimulation(log = False)
sim.place_eyeball_on_scene_camera = False
sim.setEyeRelativeToSceneCamera(v(-65, -33, -73))
sim.setCalibrationDepth(1 * 1000) # mm, wrt scene camera
sim.setTestDepth(1.5 * 1000)
sim.calibration_grid = True
sim.calibration_random_depth = False
sim.test_grid = True
sim.test_random_depth = False
sim.test_random_fixed_depth = False
cdepths = [1, 1.5, 2.0]
tdepths = np.linspace(1, 2, 5)
results = []
for i, cdepth in enumerate(cdepths):
sim.setCalibrationDepth(cdepth * 1000)
# Performing calibration
sim.runCalibration()
aae_2Ds = []
aae_3Ds = []
aae_3D3Ds = []
std_2Ds = []
std_3Ds = []
std_3D3Ds = []
# Fetching calibration points
t, p3d, p2d = sim.calibration_points, sim.tr_3d_pupil_locations, sim.tr_pupil_locations
# target positions are computed relative to the scene CCS
ti = map(lambda target: v(target) - v(sim.scene_camera.t), t)
# Computing pupil pose for each gaze
ni = map(lambda p: (v(p)-v(sim.sclera_pos)).norm(), p3d) # ground truth gaze vectors
eye_scene_diff = v(sim.sclera_pos) - v(sim.scene_camera.t)
e = np.array(eye_scene_diff) # eyeball coords in scene CCS
e_org = v(e[:])
# transforming pose vector to eye camera coordinates
eyeR = np.array(sim.eye_camera.R[:3]) # result is identical to cv2.Rodrigues(np.array([0., np.pi, 0.]))[0]
ni = map(lambda pose: eyeR.dot(np.array(pose)), ni)
R, e3d3d = minimizeEnergy(ni, ti, pose_given=True)
e3d3d = v(e3d3d)
# R = LA.inv(R)
w2d3d, e2d3d, w0 = minimizeEnergy(p2d, ti)
e2d3d = v(e2d3d)
print
print R
print 'e3d3d', e3d3d, 'distance =', (e3d3d-e_org).mag
print 'e2d3d', e2d3d, 'distance =', (e2d3d-e_org).mag
print 'real e', e_org
print
for j, tdepth in enumerate(tdepths):
sim.setTestDepth(tdepth * 1000)
aae_2d, _, std_2d, _ = sim.runTest()
aae_2Ds.append(aae_2d)
std_2Ds.append(std_2d)
# Fetching test points
t, p3d, p2d = sim.test_points, sim.te_3d_pupil_locations, sim.te_pupil_locations
ti = map(lambda target: v(target) - v(sim.scene_camera.t), t) # target coords in scene CCS
ni = map(lambda p: v(v(p)-v(sim.sclera_pos)).norm(), p3d) # ground truth gaze vectors
# transforming pose vector to eye camera coordinates
ni = map(lambda r: v(eyeR.dot(np.array(r))), ni)
# applying estimated rotation to pose vector in eye camera coordinates (Rn)
ni = map(lambda n: v(R.dot(np.array(n))), ni) # now ready to compare Rn with t-e
# 3D to 3D
# Intersecting gaze rays originating from the eye with the planes defined by each
# target. then we can simply compute angular error between each intersection and
# the corresponding 3D target
gis = map(lambda vec: v(vec), ni)
gprimes = map(lambda tg: v(((tg[0].z - e3d3d.z)/tg[1].z)*tg[1] + e3d3d), zip(ti, gis))
AE = list(np.degrees(np.arctan((v(p[0]).cross(p[1])/(v(p[0]).dot(p[1]))).mag)) for p in zip(gprimes, ti))
# AE = list(np.degrees(np.arccos(v(p[0]).dot(p[1])/v(p[0]).mag/v(p[1]).mag)) for p in zip(gprimes, ti))
N = len(ti)
AAE = np.mean(AE)
STD_3D3D = np.std(AE)
m, M = min(AE), max(AE)
aae_3D3Ds.append(AAE)
std_3D3Ds.append(STD_3D3D)
# 2D to 3D
qi = map(_q, p2d) # computing feature vectors from raw pupil coordinates in 2D
# computing unit gaze vectors corresponding to pupil positions
# here we use the computed mapping matrix w
gis = map(lambda q: gaze_ray(q, w2d3d), qi)
# Intersecting gaze rays originating from the eye with the planes defined by each
# target. then we can simply compute angular error between each intersection and
# the corresponding 3D target
gis = map(lambda vec: v(vec), gis)
gprimes = map(lambda tg: v(((tg[0].z - e2d3d.z)/tg[1].z)*tg[1] + e2d3d), zip(ti, gis))
AE = list(np.degrees(np.arctan((v(p[0]).cross(p[1])/(v(p[0]).dot(p[1]))).mag)) for p in zip(gprimes, ti))
N = len(t)
AAE = np.mean(AE)
STD_2D3D = np.std(AE)
m, M = min(AE), max(AE)
# Computing physical distance error (in meters)
PHE = list((u-v).mag for u,v in zip(ti, gprimes))
N = len(ti)
APHE = np.mean(PHE)
PHE_STD = np.std(PHE)
PHE_m, PHE_M = min(PHE), max(PHE)
aae_3Ds.append(AAE)
std_3Ds.append(STD_2D3D)
print 'depth', cdepth, 'finished.'
results.append([aae_2Ds, aae_3Ds, aae_3D3Ds, std_2Ds, std_3Ds, std_3D3Ds])
######################################################################################################
## Plotting part
clrs = ['r', 'g', 'b', 'k', 'o']
colors = ['blue', 'orange', 'red', 'black', 'orange']
patches = []
fig = plt.figure(figsize=(14.0, 10.0))
ax = fig.add_subplot(111)
dmap = {0: '1', 1:'3', 2:'5'}
x1 = [0.375,1.375,2.375,3.375,4.375]
x2 = [0.425,1.425,2.425,3.425,4.425]
x3 = [0.475,1.475,2.475,3.475,4.475]
x4 = [0.525,1.525,2.525,3.525,4.525]
x5 = [0.575,1.575,2.575,3.575,4.575]
x6 = [0.625,1.625,2.625,3.625,4.625]
xrange_2d2d = [x1, x3, x5]
xrange_2d3d = [x2, x4, x6]
for i in [0, 1, 2]:
cdepth_results = results[i]
_xrange = xrange_2d2d[i]
aae_2d2d = cdepth_results[0]
std_2d2d = cdepth_results[3]
rects1 = ax.errorbar(_xrange, aae_2d2d,yerr=[std_2d2d, std_2d2d],fmt='o',color=colors[i],ecolor=colors[i],lw=3, capsize=5, capthick=2)
plt.plot(_xrange, aae_2d2d, marker="o", linestyle='-',lw=3,color=colors[i],label = '2D-to-2D Calibration Depth ' + dmap[i])
for i in [0, 1, 2]:
cdepth_results = results[i]
_xrange = xrange_2d3d[i]
aae_2d3d = cdepth_results[1]
std_2d3d = cdepth_results[4]
rects2 = ax.errorbar(_xrange, aae_2d3d,yerr=[std_2d3d, std_2d3d],fmt='o',color=colors[i],ecolor=colors[i],lw=3, capsize=5, capthick=2)
plt.plot(_xrange, aae_2d3d, marker="o", linestyle='--',lw=3,color=colors[i],label = '2D-to-3D Calibration Depth ' + dmap[i])
for i in [0, 1, 2]:
cdepth_results = results[i]
_xrange = xrange_2d2d[i]
aae_3d3d = cdepth_results[2]
std_3d3d = cdepth_results[5]
rects3 = ax.errorbar(_xrange, aae_3d3d,yerr=[std_3d3d, std_3d3d],fmt='o',color=colors[i],ecolor=colors[i],lw=3, capsize=5, capthick=2)
plt.plot(_xrange, aae_3d3d, marker="o", linestyle='-.',lw=3,color=colors[i],label = '3D-to-3D Calibration Depth ' + dmap[i])
ax.set_ylabel(r'\textbf{Angular Error}',fontsize=22)
ax.set_xlabel(r'\textbf{Depth}',fontsize=22)
TOPICS = [-0.2, 0, 0.2, 0.4,0.6,0.8,1.0,1.2,1.4,1.6,1.8,2.0,2.2,2.4]#,110]#,120]
LABELS = [r'', r'0', r'0.2',r'0.4',r'0.6', r'0.8', r'1.0', r'1.2', r'1.4', r'1.6', r'1.8', r'2.0', r'2.2', r'2.4']#, ""]#, ""]
plt.yticks(TOPICS, LABELS,fontsize=18)
plt.xlabel('Depth')
plt.ylabel('Angular Error')
plt.legend(bbox_to_anchor=(0., 1.02, 1., .102), loc=3,
ncol=3, mode="expand", borderaxespad=0., fontsize=18)
TOPICs = [0.0,0.5,1.5,2.5,3.5,4.5,5.0]
LABELs = ['', 'D1 - 1m', 'D2 - 1.25m', 'D3 - 1.5m', 'D4 - 1.75m', 'D5 - 2.0m', '']
plt.xticks(TOPICs, LABELs,fontsize=18)
left = 0.1 # the left side of the subplots of the figure
right = 0.975 # the right side of the subplots of the figure
bottom = 0.075 # the bottom of the subplots of the figure
top = 0.925 # the top of the subplots of the figure
wspace = 0.2 # the amount of width reserved for blank space between subplots
hspace = 0.4 # the amount of height reserved for white space between subplots
plt.subplots_adjust(left=left, bottom=bottom, right=right, top=top, wspace=wspace, hspace=hspace)
plt.show()
######################################################################################################
# self.sim = sim
# This should be root path to the participants folder (including subfolders for each participant)
ROOT_DATA_DIR = '/home/mmbrian/HiWi/etra2016_mohsen/code/recording/data/participants'
class Parallax2Dto2DRealData(Experiment):
'''
First it runs single calibration depth vs test depth over all combinations of depths per participant (25 cases)
Then we perform estimation with data from multiple calibration depths for each test depth
'''
def __run__(self):
sim = GazeSimulation(log = False)
camera_matrix, dist_coeffs = readCameraParams()
root_result_path = 'results/2d2d/'
if not os.path.exists(root_result_path):
os.makedirs(root_result_path)
errors = []
for d1 in os.listdir(ROOT_DATA_DIR):
if d1.startswith('p'): # every participant
if not d1 in PARTICIPANTS:
continue
participant_label = d1
participant_results = []
d2 = os.path.join(ROOT_DATA_DIR, d1) # .../pi/
d2 = os.path.join(d2, os.listdir(d2)[0]) # .../pi/../
print '> Processing participant', d1
participant_experiment = {}
for d3 in os.listdir(d2): # every recording
d4 = os.path.join(d2, d3) # .../pi/../00X/
intervals_dir = os.path.join(d4, 'gaze_intervals.npy')
if not os.path.isfile(intervals_dir):
# Participant not completely processed
print '> One or more recordings need processing...'
break
else:
p = np.load(os.path.join(d4, 'p.npy'))
break_participant = False
for point in p:
if np.isnan(point[0]):
# For this gaze position, not input with nonzero confidence exist
break_participant = True
break
if break_participant:
print '> One or more recordings miss gaze coords...'
break
# print p
t2d = np.load(os.path.join(d4, 't2d.npy'))
t3d = np.load(os.path.join(d4, 't3d.npy'))
participant_experiment[d3] = [p, t2d, t3d]
if len(participant_experiment) == 10:
print '> All recordings processed'
keys = sorted(participant_experiment.keys())
depths = zip(keys[::2], keys[1::2])
for calib_depth in depths:
cp, ct = participant_experiment[calib_depth[0]][0], participant_experiment[calib_depth[0]][1]
cdepth_value = getDepth(calib_depth)
# Perform calibration
sim.perform2D2DCalibrationOnReadData(cp, ct, (1280, 720))
for test_depth in depths:
tdepth_value = getDepth(test_depth)
tp, tt, tt3d = participant_experiment[test_depth[1]][0], participant_experiment[test_depth[1]][1], participant_experiment[test_depth[1]][2]
error = sim.run2D2DTestOnRealData(tp, tt, (1280, 720), tt3d, camera_matrix, dist_coeffs)
participant_results.append([cdepth_value, tdepth_value] + error)
print len(participant_results), 'combinations processed...'
np.save('results/2d2d/%s_2d2d_all.npy' % participant_label, np.array(participant_results))
np.savetxt('results/2d2d/%s_2d2d_all.csv' % participant_label, np.array(participant_results), delimiter=",")
print '> Computing results for multiple calibration depths...'
for num_of_calibration_depths in xrange(2, 6): # from 2 calibration depths to 5
participant_results = []
print '> Computing results for combining %s calibration depths...' % num_of_calibration_depths
for calibs in combinations(depths, num_of_calibration_depths):
# Now calibs is a set of depths, from each of those we need calibration data
cp, ct = [], []
calib_depths_label = []
for calib in calibs:
if len(cp):
cp = np.concatenate((cp, participant_experiment[calib[0]][0]), axis=0)
ct = np.concatenate((ct, participant_experiment[calib[0]][1]), axis=0)
else:
cp = participant_experiment[calib[0]][0]
ct = participant_experiment[calib[0]][1]
calib_depths_label.append(getDepth(calib))
# Perform calibration
sim.perform2D2DCalibrationOnReadData(cp, ct, (1280, 720))
# Now we have calibration data from multiple depths, we can test on all depths
for test_depth in depths:
tdepth_value = getDepth(test_depth)
tp, tt, tt3d = participant_experiment[test_depth[1]][0], participant_experiment[test_depth[1]][1], participant_experiment[test_depth[1]][2]
error = sim.run2D2DTestOnRealData(tp, tt, (1280, 720), tt3d, camera_matrix, dist_coeffs)
participant_results.append(calib_depths_label + [tdepth_value] + error)
print len(participant_results), 'combinations processed...'
result_path = 'results/2d2d/%s_calibration_depths/' % num_of_calibration_depths
if not os.path.exists(result_path):
os.makedirs(result_path)
np.save(result_path + '%s.npy' % participant_label, np.array(participant_results))
np.savetxt(result_path + '%s.csv' % participant_label, np.array(participant_results), delimiter=",")
print 'done.'
def getDepth(depth_experiments):
_map = {'000': 1, '002': 1.25, '004': 1.5, '006': 1.75, '008': 2.0}
return _map[depth_experiments[0]]
class Parallax2Dto3DRealData(Experiment):
def __run__(self):
sim = GazeSimulation(log = False)
aae_3ds = []
# Path to whatever directory you want the results to be stored in
root_result_path = '/home/mmbrian/3D_Gaze_Tracking/work/results/2D3D/'
if not os.path.exists(root_result_path):
os.makedirs(root_result_path)
for d1 in os.listdir(ROOT_DATA_DIR):
if d1.startswith('p'): # every participant
if not d1 in PARTICIPANTS:
continue
participant_label = d1
participant_results = []
d2 = os.path.join(ROOT_DATA_DIR, d1) # .../pi/
d2 = os.path.join(d2, os.listdir(d2)[0]) # .../pi/../
print '> Processing participant', d1
participant_experiment = {}
for d3 in os.listdir(d2): # every recording
d4 = os.path.join(d2, d3) # .../pi/../00X/
intervals_dir = os.path.join(d4, 'gaze_intervals.npy')
if not os.path.isfile(intervals_dir):
# Participant not completely processed
print '> One or more recordings need processing...'
break
else:
p = np.load(os.path.join(d4, 'p.npy'))
# p = np.load(os.path.join(d4, 'p_mean.npy'))
break_participant = False
for point in p:
if np.isnan(point[0]):
# For this gaze position, not input with nonzero confidence exist
break_participant = True
break
if break_participant:
print '> One or more recordings miss gaze coords...'
break
# print p
t2d = np.load(os.path.join(d4, 't2d.npy'))
t3d = np.load(os.path.join(d4, 't3d.npy'))
# t2d = np.load(os.path.join(d4, 't2d_mean.npy'))
# t3d = np.load(os.path.join(d4, 't3d_mean.npy'))
participant_experiment[d3] = [p, t2d, t3d]
if len(participant_experiment) == 10:
print '> All recordings processed'
keys = sorted(participant_experiment.keys())
depths = zip(keys[::2], keys[1::2])
for calib_depth in depths:
cp, ct3d = participant_experiment[calib_depth[0]][0], participant_experiment[calib_depth[0]][2]
cdepth_value = getDepth(calib_depth)
# Performing calibration
w, e, w0 = minimizeEnergy(cp, ct3d)
e = v(e)
for test_depth in depths:
tdepth_value = getDepth(test_depth)
tp, tt3d = participant_experiment[test_depth[1]][0], participant_experiment[test_depth[1]][2]
t, p = tt3d, tp
qi = map(_q, p) # computing feature vectors from raw pupil coordinates in 2D
# computing unit gaze vectors corresponding to pupil positions
# here we use the computed mapping matrix w
gis = map(lambda q: g(q, w), qi)
# Intersecting gaze rays originating from the eye with the planes defined by each
# target. then we can simply compute angular error between each intersection and
# the corresponding 3D target
t = map(lambda vec: v(vec), t)
gis = map(lambda vec: v(vec), gis)
gprimes = map(lambda tg: v(((tg[0].z - e.z)/tg[1].z)*tg[1] + e), zip(t, gis))
AE = list(np.degrees(np.arctan((v(p[0]).cross(p[1])/(v(p[0]).dot(p[1]))).mag)) for p in zip(gprimes, t))
N = len(t)
AAE = sum(AE)/N
VAR = sum((ae - AAE)**2 for ae in AE)/N
STD = np.sqrt(VAR)
m, M = min(AE), max(AE)
# Computing physical distance error (in meters)
PHE = list((u-v).mag for u,v in zip(t, gprimes))
N = len(t)
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)
participant_results.append([cdepth_value, tdepth_value] + [AAE, VAR, STD, m, M, APHE, PHE_VAR, PHE_STD, PHE_m, PHE_M])
print len(participant_results), 'combinations processed...'
np.save(os.path.join(root_result_path, '%s_2d3d_all.npy' % participant_label), np.array(participant_results))
np.savetxt(os.path.join(root_result_path, '%s_2d3d_all.csv' % participant_label), np.array(participant_results), delimiter=",")
print '> Computing results for multiple calibration depths...'
for num_of_calibration_depths in xrange(2, 6): # from 2 calibration depths to 5
participant_results = []
print '> Computing results for combining %s calibration depths...' % num_of_calibration_depths
for calibs in combinations(depths, num_of_calibration_depths):
# Now calibs is a set of depths, from each of those we need calibration data
cp, ct3d = [], []
calib_depths_label = []
for calib in calibs:
if len(cp):
cp = np.concatenate((cp, participant_experiment[calib[0]][0]), axis=0)
ct3d = np.concatenate((ct3d, participant_experiment[calib[0]][2]), axis=0)
else:
cp = participant_experiment[calib[0]][0]
ct3d = participant_experiment[calib[0]][2]
calib_depths_label.append(getDepth(calib))
# Performing calibration
w, e, w0 = minimizeEnergy(cp, ct3d)
e = v(e)
# Now we have calibration data from multiple depths, we can test on all depths
for test_depth in depths:
tdepth_value = getDepth(test_depth)
tp, tt3d = participant_experiment[test_depth[1]][0], participant_experiment[test_depth[1]][2]
t, p = tt3d, tp
qi = map(_q, p)
gis = map(lambda q: g(q, w), qi)
t = map(lambda vec: v(vec), t)
gis = map(lambda vec: v(vec), gis)
gprimes = map(lambda tg: v(((tg[0].z - e.z)/tg[1].z)*tg[1] + e), zip(t, gis))
AE = list(np.degrees(np.arctan((v(p[0]).cross(p[1])/(v(p[0]).dot(p[1]))).mag)) for p in zip(gprimes, t))
N = len(t)
AAE = sum(AE)/N
VAR = sum((ae - AAE)**2 for ae in AE)/N
STD = np.sqrt(VAR)
m, M = min(AE), max(AE)
# Computing physical distance error (in meters)
PHE = list((u-v).mag for u,v in zip(t, gprimes))
N = len(t)
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)
participant_results.append(calib_depths_label + [tdepth_value] + [AAE, VAR, STD, m, M, APHE, PHE_VAR, PHE_STD, PHE_m, PHE_M])
print len(participant_results), 'combinations processed...'
result_path = os.path.join(root_result_path, '%s_calibration_depths/' % num_of_calibration_depths)
if not os.path.exists(result_path):
os.makedirs(result_path)
np.save(result_path + '%s.npy' % participant_label, np.array(participant_results))
np.savetxt(result_path + '%s.csv' % participant_label, np.array(participant_results), delimiter=",")
print 'done.'
# class Parallax3Dto3DRealData(Experiment):
# def __run__(self):
# sim = GazeSimulation(log = False)
# aae_3ds = []
# root_result_path = '/home/mmbrian/3D_Gaze_Tracking/work/results/3D3D/'
# root_pose_path = '/home/mmbrian/3D_Gaze_Tracking/work/Marker_Eye_Images/ImagesUndist/'
# root_data_path = '/home/mmbrian/HiWi/etra2016_mohsen/code/recording/data/participants/'
# take_only_nearest_neighbor_for_calibration = True
# participants = ['p14']
# if not os.path.exists(root_result_path):
# os.makedirs(root_result_path)
# for d1 in os.listdir(root_data_path):
# if d1.startswith('p'): # every participant
# if not d1 in participants:
# # if not d1 in PARTICIPANTS:
# continue
# participant_label = d1
# participant_results = []
# d2 = os.path.join(root_data_path, d1) # .../pi/
# d2 = os.path.join(d2, os.listdir(d2)[0]) # .../pi/../
# print '> Processing participant', d1
# participant_experiment = {}
# for d3 in os.listdir(d2): # every recording
# d4 = os.path.join(d2, d3) # .../pi/../00X/
# # pose_info = np.loadtxt(open(os.path.join(root_pose_path+d1+'/'+d3, "null_pupils.csv"),"rb"),delimiter=";")
# pose_info = np.loadtxt(open(os.path.join(root_pose_path+d1+'/'+d3, "simple_pupils.csv"),"rb"),delimiter=";")
# frames_numbers = pose_info[:, 0]
# pose_estimates = pose_info[:,4:7]
# pose_info = dict(zip(frames_numbers, pose_estimates))
# p_frames = np.load(os.path.join(d4, 'p_frames.npy'))
# # print d4
# # Fetching pose information for every target
# poses = []
# for target in p_frames:
# pose = []
# for fn in target: # all frames corresponding to this pupil
# # first fn corresponds to the nearest neighbor
# # for test use all correspondents from these 3 or 2 estimates
# # i.e. each pose-marker creates a correspondence so 3*16=48 correspondents for test
# # for calibration compare two cases, one similar to above take all the 75 correspondents
# # and the other taking only the pose corresponding to nearest neighbor which results in
# # the same number of correspondents as target markers
# try:
# pose.append(pose_info[fn])
# except KeyError, err:
# print err
# poses.append(pose)
# t2d = np.load(os.path.join(d4, 't2d.npy'))
# t3d = np.load(os.path.join(d4, 't3d.npy'))
# participant_experiment[d3] = [poses, t2d, t3d]
# keys = sorted(participant_experiment.keys())
# depths = zip(keys[::2], keys[1::2])
# for calib_depth in depths:
# pose_data, ct3d = participant_experiment[calib_depth[0]][0], participant_experiment[calib_depth[0]][2]
# cdepth_value = getDepth(calib_depth)
# if take_only_nearest_neighbor_for_calibration:
# pose = np.array(list(p[0] for p in pose_data))
# calib_3ds = ct3d[:]
# else:
# calib_3ds = []
# pose = []
# for i, p3d in enumerate(ct3d):
# for p in pose_data[i]:
# pose.append(p)
# calib_3ds.append(p3d)
# # Performing calibration
# # First we convert gaze rays to actual pupil pose in our right hand coordinate system
# # _pose = [(np.arctan(g.x/g.z), np.arctan(g.y/g.z)) for g in map(v, pose)]
# _pose = map(v, pose)
# print '> Running tests for calibration depth', cdepth_value
# if any(g.z == 0 for g in _pose):
# print 'Calibration is flawed'
# # print pose
# else:
# print 'Calibration data is okay'
# # print [g.mag for g in map(v, pose)]
# # w, e, w0 = minimizeEnergy(_pose, calib_3ds, pose_given=True)
# R, e = minimizeEnergy(pose, calib_3ds, pose_given=True)
# # R = LA.inv(R)
# print 'R', R
# print 'e', e
# e = v(e)
# for test_depth in depths:
# tdepth_value = getDepth(test_depth)
# tpose_data, tt3d = participant_experiment[test_depth[1]][0], participant_experiment[test_depth[1]][2]
# test_3ds = []
# tpose = []
# for i, p3d in enumerate(tt3d):
# for p in tpose_data[i]:
# tpose.append(p)
# test_3ds.append(p3d)
# # applying estimated rotation to bring pose vectors to scene camera coordinates
# tpose = map(lambda p: v(R.dot(np.array(p))), tpose)
# if any(g.z == 0 for g in map(v, tpose)):
# print 'Test depth', tdepth_value, 'is flawed'
# gis = map(lambda vec: v(vec), tpose)
# t = map(lambda vec: v(vec), test_3ds)
# gprimes = map(lambda tg: v(((tg[0].z - e.z)/tg[1].z)*tg[1] + e), zip(t, gis))
# # AE = list(np.degrees(np.arctan((v(p[0]).cross(p[1])/(v(p[0]).dot(p[1]))).mag)) for p in zip(gprimes, t))
# AE = list(np.degrees(np.arccos(v(p[0]).dot(p[1])/v(p[0]).mag/v(p[1]).mag)) for p in zip(gprimes, t))
# AAE = np.mean(AE)
# STD = np.std(AE)
# m, M = min(AE), max(AE)
# # Computing physical distance error (in meters)
# PHE = list((u-v).mag for u,v in zip(t, gprimes))
# APHE = np.mean(PHE)
# PHE_STD = np.std(PHE)
# PHE_m, PHE_M = min(PHE), max(PHE)
# print 'Calibration', cdepth_value, 'Test', tdepth_value, AAE, 'degrees', APHE, 'meters'
# participant_results.append([cdepth_value, tdepth_value] + [AAE, STD, m, M, APHE, PHE_STD, PHE_m, PHE_M])
# print len(participant_results), 'combinations processed...'
# np.save(os.path.join(root_result_path, '%s_3d3d_all.npy' % participant_label), np.array(participant_results))
# np.savetxt(os.path.join(root_result_path, '%s_3d3d_all.csv' % participant_label), np.array(participant_results), delimiter=",")
def main():
if len(sys.argv) <= 1:
print 'Please select a mode.'
return
mode = sys.argv[1]
if mode == 'pts':
# Performs an experiment by fixing calibration depth and testing for different test depths
# to investigate parallax error in 2D to 2D mapping
ex = Parallax2Dto2DMapping()
ex.performExperiment()
if mode == '2d3d':
# This also does 3D gaze estimation and plots estimation results for both 3D and 2D estimation
ex = Parallax2Dto3DMapping()
ex.performExperiment()
if mode == '2d2d_2d3d':
ex = Parallax3Dto3DMapping()
ex.performExperiment()
# ex = Parallax2Dto2DRealData()
# ex.performExperiment()
# ex = Parallax2Dto3DRealData()
# ex.performExperiment()
if mode == '3d3d_real':
ex = Parallax3Dto3DRealData()
ex.performExperiment()
if __name__ == '__main__':
main()