예제 #1
0
    def recalculate(self):
        assert self.recent_input and self.recent_labels

        width, height = self.g_pool.capture.frame_size
        prediction = self.g_pool.active_gaze_mapping_plugin.map_batch(
            self.recent_input)

        # reuse closest_matches_monocular to correlate one label to each prediction
        # correlated['ref']: prediction, correlated['pupil']: label location
        correlated = closest_matches_monocular(prediction, self.recent_labels)
        # [[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])
        self.error_lines = locations.copy()  # n x 4
        locations[:, ::2] *= width
        locations[:, 1::2] = (1. - locations[:, 1::2]) * height

        # 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 = self.g_pool.capture.intrinsics.undistortPoints(locations)
        undistorted.shape = -1, 2
        # append column with z=1
        # using idea from https://stackoverflow.com/questions/8486294/how-to-add-an-extra-column-to-an-numpy-array
        undistorted_3d = np.ones((undistorted.shape[0], 3))  # shape: 2n x 3
        undistorted_3d[:, :-1] = undistorted
        # normalize vectors:
        undistorted_3d /= np.linalg.norm(undistorted_3d, axis=1)[:, np.newaxis]

        # 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(self.outlier_threshold))
        selected_samples = angular_err[selected_indices]
        num_used, num_total = selected_samples.shape[0], angular_err.shape[0]

        self.error_lines = self.error_lines[selected_indices].reshape(
            -1, 2)  # shape: num_used x 2
        self.accuracy = np.rad2deg(np.arccos(selected_samples.mean()))
        logger.info('Angular accuracy: {}. Used {} of {} samples.'.format(
            self.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 > self.succession_threshold,
            succesive_distances_ref > self.succession_threshold)
        succesive_distances = succesive_distances_gaze[selected_indices]
        num_used, num_total = succesive_distances.shape[
            0], succesive_distances_gaze.shape[0]
        self.precision = np.sqrt(np.mean(np.arccos(succesive_distances)**2))
        logger.info("Angular precision: {}. Used {} of {} samples.".format(
            self.precision, num_used, num_total))
예제 #2
0
    def calc_acc_prec_errlines(self, gaze_pos, ref_pos, intrinsics):
        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])
        error_lines = locations.copy()  # n x 4
        locations[:, ::2] *= width
        locations[:, 1::2] = (1. - 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(self.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.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 > self.succession_threshold,
            succesive_distances_ref > self.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.arccos(succesive_distances)**2))
        precision_result = Calculation_Result(precision, num_used, num_total)

        return accuracy_result, precision_result, error_lines
예제 #3
0
    def calc_acc_prec_errlines(
        gaze_pos,
        ref_pos,
        intrinsics,
        outlier_threshold,
        succession_threshold=np.cos(np.deg2rad(0.5)),
    ):
        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