def correlate_and_coordinate_transform( gaze_pos, ref_pos, intrinsics) -> CorrelatedAndCoordinateTransformedResult: # reuse closest_matches_monocular to correlate one label to each prediction # correlated['ref']: prediction, correlated['pupil']: label location # NOTE the switch of the ref and pupil keys! This effects mostly hmd data. correlated = closest_matches_monocular(gaze_pos, ref_pos) # [[pred.x, pred.y, label.x, label.y], ...], shape: n x 4 if not correlated: raise CorrelationError("No correlation possible") try: return Accuracy_Visualizer._coordinate_transform_ref_in_norm_space( correlated, intrinsics) except KeyError as err: if "norm_pos" in err.args: return Accuracy_Visualizer._coordinate_transform_ref_in_camera_space( correlated, intrinsics) else: raise
def calc_acc_prec_errlines( g_pool, gazer_class, gazer_params, pupil_list, ref_list, intrinsics, outlier_threshold, succession_threshold=np.cos(np.deg2rad(0.5)), ): gazer = gazer_class(g_pool, params=gazer_params) gaze_pos = gazer.map_pupil_to_gaze(pupil_list) ref_pos = ref_list width, height = intrinsics.resolution # reuse closest_matches_monocular to correlate one label to each prediction # correlated['ref']: prediction, correlated['pupil']: label location correlated = closest_matches_monocular(gaze_pos, ref_pos) # [[pred.x, pred.y, label.x, label.y], ...], shape: n x 4 locations = np.array([(*e["ref"]["norm_pos"], *e["pupil"]["norm_pos"]) for e in correlated]) if locations.size == 0: accuracy_result = Calculation_Result(0.0, 0, 0) precision_result = Calculation_Result(0.0, 0, 0) error_lines = np.array([]) return accuracy_result, precision_result, error_lines error_lines = locations.copy() # n x 4 locations[:, ::2] *= width locations[:, 1::2] = (1.0 - locations[:, 1::2]) * height locations.shape = -1, 2 # Accuracy is calculated as the average angular # offset (distance) (in degrees of visual angle) # between fixations locations and the corresponding # locations of the fixation targets. undistorted_3d = intrinsics.unprojectPoints(locations, normalize=True) # Cosine distance of A and B: (A @ B) / (||A|| * ||B||) # No need to calculate norms, since A and B are normalized in our case. # np.einsum('ij,ij->i', A, B) equivalent to np.diagonal(A @ B.T) but faster. angular_err = np.einsum("ij,ij->i", undistorted_3d[::2, :], undistorted_3d[1::2, :]) # Good values are close to 1. since cos(0) == 1. # Therefore we look for values greater than cos(outlier_threshold) selected_indices = angular_err > np.cos(np.deg2rad(outlier_threshold)) selected_samples = angular_err[selected_indices] num_used, num_total = selected_samples.shape[0], angular_err.shape[0] error_lines = error_lines[selected_indices].reshape( -1, 2) # shape: num_used x 2 accuracy = np.rad2deg( np.arccos(selected_samples.clip(-1.0, 1.0).mean())) accuracy_result = Calculation_Result(accuracy, num_used, num_total) # lets calculate precision: (RMS of distance of succesive samples.) # This is a little rough as we do not compensate headmovements in this test. # Precision is calculated as the Root Mean Square (RMS) # of the angular distance (in degrees of visual angle) # between successive samples during a fixation undistorted_3d.shape = -1, 6 # shape: n x 6 succesive_distances_gaze = np.einsum("ij,ij->i", undistorted_3d[:-1, :3], undistorted_3d[1:, :3]) succesive_distances_ref = np.einsum("ij,ij->i", undistorted_3d[:-1, 3:], undistorted_3d[1:, 3:]) # if the ref distance is to big we must have moved to a new fixation or there is headmovement, # if the gaze dis is to big we can assume human error # both times gaze data is not valid for this mesurement selected_indices = np.logical_and( succesive_distances_gaze > succession_threshold, succesive_distances_ref > succession_threshold, ) succesive_distances = succesive_distances_gaze[selected_indices] num_used, num_total = ( succesive_distances.shape[0], succesive_distances_gaze.shape[0], ) precision = np.sqrt( np.mean( np.rad2deg(np.arccos(succesive_distances.clip(-1.0, 1.0)))**2)) precision_result = Calculation_Result(precision, num_used, num_total) return accuracy_result, precision_result, error_lines