Example #1
0
    def __init__(self, eyeloop):

        self.live = True  #Access this to check if Core is running.

        self.eyeloop = eyeloop
        self.model = config.arguments.model  #   Used for assigning appropriate circular model.

        if config.arguments.markers == False:  #   Markerless. -m 0 (default)
            self.place_markers = lambda: None
        else:  #   Enables markers to remove artifacts. -m 1
            self.place_markers = self.real_place_markers
        self.marks = []

        if config.arguments.tracking == 0:  #   Recording mode. --tracking 0
            self.iterate = self.record
        else:  #   Tracking mode. --tracking 1 (default)
            self.iterate = self.track

        self.angle = 0
        self.extractors = []

        self.std = -1  #    Used for infering blinking.
        self.mean = -1  #    Used for infering blinking.

        max_cr_processor = 3
        self.cr_processors = [Shape(type=2) for _ in range(max_cr_processor)]
        self.pupil_processor = Shape()

        #   Via "gui", assign "refresh_pupil" to function "processor.refresh_source"
        #   when the pupil has been selected.
        self.refresh_pupil = lambda x: None
Example #2
0
class Engine:
    def __init__(self, eyeloop):

        self.live = True  #Access this to check if Core is running.

        self.eyeloop = eyeloop
        self.model = config.arguments.model  #   Used for assigning appropriate circular model.

        if config.arguments.markers == False:  #   Markerless. -m 0 (default)
            self.place_markers = lambda: None
        else:  #   Enables markers to remove artifacts. -m 1
            self.place_markers = self.real_place_markers
        self.marks = []

        if config.arguments.tracking == 0:  #   Recording mode. --tracking 0
            self.iterate = self.record
        else:  #   Tracking mode. --tracking 1 (default)
            self.iterate = self.track

        self.angle = 0
        self.extractors = []

        self.std = -1  #    Used for infering blinking.
        self.mean = -1  #    Used for infering blinking.

        max_cr_processor = 3
        self.cr_processors = [Shape(type=2) for _ in range(max_cr_processor)]
        self.pupil_processor = Shape()

        #   Via "gui", assign "refresh_pupil" to function "processor.refresh_source"
        #   when the pupil has been selected.
        self.refresh_pupil = lambda x: None

    def load_extractors(self, extractors: list = []) -> None:
        self.extractors = extractors

    def real_place_markers(self) -> None:
        """
        Circumvents artifacts (crudely) via demarcations for the pupil processor.
        """

        for i, mark in enumerate(self.marks):
            if (i % 2) == 0:
                try:
                    self.pupil_processor.area[
                        mark[1] -
                        self.pupil_processor.corners[0][1]:self.marks[i +
                                                                      1][1] -
                        self.pupil_processor.corners[0][1], mark[0] -
                        self.pupil_processor.corners[0][0]:self.marks[i +
                                                                      1][0] -
                        self.pupil_processor.corners[0][0]] = 100
                except:
                    #odd marks or no pupil yet.
                    break

    def run_extractors(self) -> None:
        """
        Calls all extractors at the end of each time-step.
        Assign additional extractors to core engine via eyeloop.py.
        """

        for extractor in self.extractors:
            try:
                extractor.fetch(self)
            except Exception as e:
                print("Error in module class: {}".format(extractor.__name__))
                print("Error message: ", e)

    def record(self) -> None:
        """
        Runs Core engine in record mode. Timestamps all frames in data output log.
        Runs gui update_record function with no tracking.
        Argument -s 1
        """

        timestamp = time.time()

        self.dataout = {
            "time": timestamp,
            "frame": config.importer.frame,
            "blink": -1,
            "cr_dim": -1,
            "cr_cen": -1,
            "cr_ang": -1,
            "pupil_dim": -1,
            "pupil_cen": -1,
            "pupil_ang": -1
        }

        config.graphical_user_interface.update_record(self.source)

        self.run_extractors()

    def arm(self, width, height, image) -> None:
        self.norm = (width + height) * .003
        self.norm_cr_artefact = int(3 * self.norm)

        self.width, self.height = width, height
        config.graphical_user_interface.arm(width, height)

        self.update_feed(image)

        self.pupil_processor.binarythreshold = float(np.min(self.source)) * .7
        for cr_processor in self.cr_processors:
            cr_processor.binarythreshold = float(np.min(self.source)) * .7

    def check_blink(self, threshold=5) -> bool:
        """
        Analyzes the monochromatic distribution of the frame,
        to infer blinking. Change in mean during blinking is very distinct.
        """

        mean = np.mean(self.source)
        delta = int(mean - self.mean)
        self.mean = mean

        if delta > 3:  # normalize to frame rate qqqq
            return False  #BLINK

        return True

    def track(self) -> None:
        """
        Executes the tracking algorithm on the pupil and corneal reflections.
        First, blinking is analyzed.
        Second, corneal reflections are detected.
        Third, corneal reflections are inverted at pupillary overlap.
        Fourth, pupil is detected.
        Finally, data is logged and extractors are run.
        """

        timestamp = time.time()
        cr_width = cr_height = cr_center = cr_angle = pupil_center = pupil_width = pupil_height = pupil_angle = -1
        blink = 0

        if self.check_blink():
            try:
                pupil_area = self.pupil_processor.area
                offsetx, offsety = -self.pupil_processor.corners[0]
            except:
                offsetx, offsety = 0, 0
                _, pupil_area = cv2.threshold(
                    cv2.GaussianBlur(
                        self.pupil_source,
                        (self.pupil_processor.blur, self.pupil_processor.blur),
                        0), 60 + self.pupil_processor.binarythreshold, 255,
                    cv2.THRESH_BINARY_INV)

            for cr_processor in self.cr_processors:
                if cr_processor.active:
                    cr_processor.refresh_source(self.source)

                    if cr_processor.track():
                        self.cr_artifacts(cr_processor, offsetx, offsety,
                                          pupil_area)
                        cr_center, cr_width, cr_height, cr_angle, cr_dimensions_int = cr_processor.ellipse.parameters(
                        )

            self.refresh_pupil(
                self.pupil_source
            )  #lambda _: None when pupil not selected in gui.
            self.place_markers()  #lambda: None when markerless (-m 0).

            if self.pupil_processor.track():
                self.pupil = self.pupil_processor.center
                pupil_center, pupil_width, pupil_height, pupil_angle, pupil_dimensions_int = self.pupil_processor.ellipse.parameters(
                )

        else:
            blink = 1

        try:
            config.graphical_user_interface.update_track()
        except Exception as e:
            print(
                "Error! Did you assign the graphical user interface (GUI) correctly?"
            )
            print("Error message: ", e)
            self.release()
            return

        self.dataout = {
            "time": timestamp,
            "frame": config.importer.frame,
            "blink": blink,
            "cr_dim": (cr_width, cr_height),
            "cr_cen": cr_center,
            "cr_ang": cr_angle,
            "pupil_dim": (pupil_width, pupil_height),
            "pupil_cen": pupil_center,
            "pupil_ang": pupil_angle
        }

        self.run_extractors()

    def activate(self) -> None:
        """
        Ativates all extractors.
        The extractor activate() function is optional.
        """

        for extractor in self.extractors:
            try:
                extractor.activate()
            except:
                pass

    def release(self) -> None:
        """
        Releases/deactivates all running process, i.e., importers, extractors.
        """

        self.live = False

        try:
            config.importer.release()
        except:
            pass

        for extractor in self.extractors:
            try:
                extractor.release()
            except:
                pass

    def update_feed(self, img) -> None:

        self.source = img.copy()
        self.pupil_source = img.copy()

        self.iterate()

    def cr_artifacts(self, cr_processor, offsetx: int, offsety: int,
                     pupil_area) -> None:
        """
        Computes pupillary overlaps and acts to remove these artifacts.
        """

        cr_center, cr_width, cr_height, cr_angle, cr_dimensions_int = cr_processor.ellipse.parameters(
        )

        cr_center_int = tuple_int(cr_center)
        larger_width, larger_height = larger_radius = tuple(
            int(1.2 * element) for element in cr_dimensions_int)

        cr_width_norm = larger_width * self.norm
        cr_height_norm = larger_height * self.norm

        dimensional_product = larger_width * larger_height

        arc = [
            dimensional_product /
            np.sqrt((cr_width_norm * anglesteps_cos[i])**2 +
                    (cr_width_norm * anglesteps_sin[i])**2)
            for i in angular_range
        ]
        cos_sin_arc = [(to_int(anglesteps_cos[i] * arc[i]),
                        to_int(anglesteps_sin[i] * arc[i]))
                       for i in angular_range]

        hit_list = zeros.copy()

        for i, arc_element in enumerate(cos_sin_arc):
            cos, sin = arc_element
            x = cr_center_int[0] + offsetx + cos
            y = cr_center_int[1] + offsety + sin
            n = 1
            strike = 0

            while n < self.norm_cr_artefact:  #normalize qqqq
                n += 1
                try:
                    if pupil_area[y, x] != 0:
                        hit_list[i] = i + 1
                        break
                    else:
                        x += cos
                        y += sin
                except:
                    break

        if np.any(hit_list):
            delta = np.count_nonzero(number_row - hit_list)

            if delta < self.norm_cr_artefact:

                cv2.ellipse(self.pupil_source, cr_center_int, larger_radius,
                            cr_angle, 0, 360, 0, -1)
            else:

                for element in hit_list:
                    if element != 0:
                        cos, sin = cos_sin_arc[element - 1]
                        x = cr_center_int[0] + cos
                        y = cr_center_int[1] + sin
                        cv2.ellipse(self.pupil_source, cr_center_int,
                                    larger_radius, cr_angle, element * 40 - 40,
                                    element * 40, 0, 4)  #normalize qqqq