예제 #1
0
    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
예제 #2
0
    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