Exemplo n.º 1
0
 def handle_keyboard(self, key_mapper: KeyMapper):
     par = self.par
     if key_mapper.consume("n"):
         par.context.mark_is_dashcam(not par.context.is_dashcam)
         self.log("Marked video as {0}".format(
             "dashcam" if par.context.is_dashcam else "not dashcam"))
     elif key_mapper.consume("t"):
         window = AdditionalTagWindow()
         tags = window.get_user_tags()
         par.context.set_additional_tags(tags)
         self.log("Additional tags set")
     elif key_mapper.consume("v"):
         window = MultiSelectPopup("Select custom enum tags",
                                   "video_enum_tags",
                                   self.par.context.enum_tags)
         enum_tags = window.run()
         if enum_tags is not None:
             self.log("Updated from {0}".format(self.par.context.enum_tags))
             self.log("Now: {0}".format(enum_tags))
             self.par.context.enum_tags = enum_tags
             self.log("Remember to commit any new tags!")
         else:
             self.log("Enum tag update cancelled")
     elif key_mapper.consume("l"):
         ind = self.par.vcm.get_frame_index()
         if self.par.bbm.has_accident_location(ind):
             self.par.bbm.remove_accident_location(ind)
             self.log("Accident location removed:{0}".format(ind))
         else:
             self.par.bbm.add_accident_location(ind)
             self.log("Accident location added:{0}".format(ind))
         self.log("Is now {0}".format(
             self.par.bbm.get_accident_locations()))
Exemplo n.º 2
0
 def handle_keyboard(self, key_mapper: KeyMapper):
     par = self.par
     if key_mapper.consume("n"):
         par.context.mark_is_dashcam(not par.context.is_dashcam)
         self.log("Marked video as {0}".format(
             "dashcam" if par.context.is_dashcam else "not dashcam"))
     elif key_mapper.consume("t"):
         window = AdditionalTagWindow()
         tags = window.get_user_tags()
         par.context.set_additional_tags(tags)
         self.log("Additional tags set")
Exemplo n.º 3
0
 def handle_keyboard(self, key_mapper: KeyMapper):
     par = self.par
     if key_mapper.consume("n"):
         par.context.mark_is_dashcam(not par.context.is_dashcam)
         self.log("Marked video as {0}".format(
             "dashcam" if par.context.is_dashcam else "not dashcam"))
     elif key_mapper.consume("t"):
         window = AdditionalTagWindow()
         tags = window.get_user_tags()
         par.context.set_additional_tags(tags)
         self.log("Additional tags set")
     elif key_mapper.consume("v"):
         window = MultiSelectPopup("Select custom enum tags",
                                   "video_enum_tags",
                                   self.par.context.enum_tags)
         enum_tags = window.run()
         if enum_tags is not None:
             self.log("Updated from {0}".format(self.par.context.enum_tags))
             self.log("Now: {0}".format(enum_tags))
             self.par.context.enum_tags = enum_tags
             self.log("Remember to commit any new tags!")
         else:
             self.log("Enum tag update cancelled")
     elif key_mapper.consume("l"):
         ind = self.par.vcm.get_frame_index()
         if self.par.bbm.has_collision_location(ind):
             self.par.bbm.remove_collision_location(ind)
             self.log("collision location removed:{0}".format(ind))
         else:
             self.par.bbm.add_collision_location(ind)
             self.log("collision location added:{0}".format(ind))
         self.log("Is now {0}".format(
             self.par.bbm.get_collision_locations()))
     elif key_mapper.consume("i"):
         ind = self.par.vcm.get_frame_index()
         if self.par.bbm.has_reckless_start_frame():
             self.par.bbm.set_reckless_end_frame(ind)
             self.log("Reckless interval created:({0}, {1})"\
                     .format(self.par.bbm.get_reckless_start_frame(), ind))
             self.par.bbm.clear_reckless_start()
         else:
             self.par.bbm.set_reckless_start_frame(ind)
             self.log("Reckless start frame set:{0}".format(ind))
     elif key_mapper.consume("u"):
         ind = self.par.vcm.get_frame_index()
         removed = self.par.bbm.clear_reckless_frames(ind)
         self.log("Remove reckless frames:{0}".format(removed))
     elif key_mapper.consume("k"):
         par.context.mark_to_be_deleted(not par.context.to_be_deleted)
         self.log("Marked video to {0}".format(
             "be deleted" if par.context.to_be_deleted else "not be deleted"
         ))
     elif key_mapper.consume("o"):
         new_location = SelectPopup("Enter location of accident",
                                    "video_locations", 10,
                                    VIDEO_LOCATION_DEFAULT_OPTIONS).run()
         if new_location is not None:
             par.context.set_location(new_location)
             self.log(f"Updated Location to {new_location}")
Exemplo n.º 4
0
    def __init__(self, context: VideoTaggingContext):
        self.context = context
        self.vcm = self.context.vcm
        self.frame_rate = 25
        self.logger = RotatingLog(self.LOG_LINES)
        self.ignore_index_change_interval = self.vcm.get_frames_count() // 200

        self.bbm = BoundingBoxManager(self.vcm.get_frames_count())
        self.bbm.set_to(*context.get_bbox_fields_as_list())

        self.mode_handlers = [
            InternaSelectionMode(self),
            InternalBBoxMode(self)
        ]
        self.mode_handler_i = 0

        self.instructions = GENERAL_INSTRUCTIONS
        self.key_mapper = KeyMapper()
Exemplo n.º 5
0
    def __init__(self, context: BBContext, mode_handlers: list):
        self.context = context
        self.vcm = self.context.vcm
        self.frame_rate = 25
        self.logger = RotatingLog(self.LOG_LINES)
        self.ignore_index_change_interval = self.vcm.get_frames_count() // 200

        self.mode_handlers = mode_handlers
        self.mode_handler_i = 0

        self.key_mapper = KeyMapper()
        self.bbm = BoundingBoxManager(self.vcm.get_frames_count())
        self.bbm.set_to(
            context,
            selected=[
                obj.id for obj in context.bbox_fields.objects.values()
                if obj.has_collision
            ],
        )
Exemplo n.º 6
0
    def handle_keyboard(self, key_mapper: KeyMapper):
        par = self.par
        bbm = par.bbm
        if key_mapper.consume("i"):  # Select id
            id = TextPopup(
                "Enter ID. If ID exists, will work on original").run()
            if id is None or id == "":
                self.cancel("Select ID",
                            "Still on {0}".format(self.selected_id))
            else:
                try:
                    id = int(id)
                    self.log("Switched id from {0} to {1}".format(
                        self.selected_id, id))
                    self.selected_id = id
                    self.reset_task()
                except:
                    self.error("Input ID not valid. Still on {0}".format(
                        self.selected_id))
        elif key_mapper.consume("u"):  # Overwrite class
            if not bbm.has_id(self.selected_id):
                self.error(
                    "Could not update class. ID {0} does not exist".format(
                        self.selected_id))
            else:
                cls = SelectPopup("Enter new class for the selected object",
                                  "bb_classes", 10,
                                  BB_CLASS_DEFAULT_OPTIONS).run()
                prev = bbm.get_cls(self.selected_id)
                if cls == None:
                    self.cancel(
                        "Class change", "{0} still on class {1}".format(
                            self.selected_id, prev))
                else:
                    bbm.add_or_update_id(self.selected_id, cls)
                    self.log("Class for ID {0} updated from {1} to {2}".format(
                        self.selected_id, prev, cls))
        elif key_mapper.consume("r"):
            self.reset_task()
            self.log("Selected bounding boxes reset".format(self.selected_id))
        elif key_mapper.consume("p"):
            deleted_ids = bbm.remove_unused_ids()
            self.log("Removed {0} ids without bounding boxes: {1}".format(
                len(deleted_ids), list(deleted_ids)))
        elif key_mapper.consume("z"):
            removed = self.im.remove_last()
            if removed is None:
                self.error("No input to remove")
            else:
                self.log("Removed last bounding box input on frame {0}".format(
                    removed.i))
        elif key_mapper.consume("x"):
            removed = self.im.remove_last()
            if removed is None:
                self.error("No input to change")
            else:
                prev_i = removed.i
                removed.i = self.par.vcm.get_frame_index()
                self.im.add(removed)
                self.log(
                    "Moved last bounding box input from frame {0} to frame {1}"
                    .format(prev_i, removed.i))
        elif key_mapper.consume("q"):
            index = self.par.vcm.get_frame_index()
            retrived = bbm.get_last_bounding_box_i(self.selected_id, index)
            if retrived is None:
                self.error(
                    "Retrieving previous frame with bounding box failed")
            else:
                self.im.add(bbm.get_ir(self.selected_id, retrived))
        elif key_mapper.consume("e"):
            index = self.par.vcm.get_frame_index()
            retrived = bbm.get_next_bounding_box_i(self.selected_id, index)
            if retrived is None:
                self.error("Retrieving next frame with bounding box failed")
            else:
                self.im.add(bbm.get_ir(self.selected_id, retrived))
        elif key_mapper.consume("b"):
            if self.im.has_n(2):
                if not bbm.has_id(self.selected_id):
                    bbm.add_or_update_id(self.selected_id, self.DEFAULT_CLASS)
                    self.log("New id {0} for class {1} added".format(
                        self.selected_id, self.DEFAULT_CLASS))
                bbm.replace_in_range(self.selected_id, *self.im.get_2_sorted())
                self.log(
                    "Bounding box for ID {0} set over index range [{1}, {2}]".
                    format(self.selected_id, self.im[0].i, self.im[1].i))
            elif self.im.has_n(1):
                if not bbm.has_id(self.selected_id):
                    bbm.add_or_update_id(self.selected_id, self.DEFAULT_CLASS)
                    self.log("New id {0} for class {1} added".format(
                        self.selected_id, self.DEFAULT_CLASS))
                bbm.replace_in_range(self.selected_id, self.im.get_last(),
                                     self.im.get_last())
                self.log("Single bounding box for ID {0} set at {1}".format(
                    self.selected_id,
                    self.im.get_last().i))
            else:
                self.error(
                    "Not enough inputs. Command ignored. Please draw at least 1 bounding box"
                )
        elif key_mapper.consume("c"):
            if not self.im.has_n(2):
                self.error(
                    "Not enough inputs. Command ignored. Please click on 2 frames"
                )
            elif not bbm.has_id(self.selected_id):
                self.error(
                    "Cannot clear bboxes - ID {0} does not exist".format(
                        self.selected_id))
            else:
                i1 = self.im[0].i
                i2 = self.im[1].i
                bbm.clear_in_range(self.selected_id, i1, i2)
                self.log("Bbox for ID {0} cleared over index range [{1}, {2}]".
                         format(self.selected_id, i1, i2))

                unused_ids = bbm.get_unused_ids()
                if len(unused_ids) > 0:
                    self.warn("Exists ids without bounding box: {0}".format(
                        list(unused_ids)))
                    self.hint("Press p to remove")
        elif key_mapper.consume("v"):
            if not bbm.has_id(self.selected_id):
                self.error(
                    "Cannot clear bboxes - ID {0} does not exist".format(
                        self.selected_id))
            else:
                i = self.par.vcm.get_frame_index()
                bbm.clear_in_range(self.selected_id, i, i)
                self.log("Bbox for ID {0} cleared for a single frame".format(
                    self.selected_id, i))

                unused_ids = bbm.get_unused_ids()
                if len(unused_ids) > 0:
                    self.warn("Exists ids without bounding box: {0}".format(
                        list(unused_ids)))
                    self.hint("Press p to remove")
Exemplo n.º 7
0
class VideoPlayerGUIManager(object):
    PROGRESS_BAR_NAME = "progress"
    FRAME_RATE_BAR_NAME = "frame_delay"
    PAUSE_BUTTON_NAME = "pause"
    WINDOW_NAME = 'overwriteThis'

    LOG_LINES = 6
    LOG_LINE_HEIGHT = 17
    LOG_LINE_MARGIN = 2
    LOG_START_X = 250
    IMG_STARTING_Y = LOG_LINE_HEIGHT * LOG_LINES + LOG_LINE_MARGIN * (
        LOG_LINES + 1) + 3

    INSTRUCTIONS = [
        ["m or tab", "Switch mode"],
        ["h", "Open instructions page"],
        ["a", "1 back"],
        ["s", "10 back"],
        ["d", "1 forward"],
        ["w", "10 forward"],
        ["Space", "Pause/unpause"],
        ["Enter * 2", "Finish and continue"],
        [
            "Esc * 2",
            "Abort and restart tagging. Will raise ManualTaggingAbortedException"
        ],
    ]

    def __init__(self, context: BBContext, mode_handlers: list):
        self.context = context
        self.vcm = self.context.vcm
        self.frame_rate = 25
        self.logger = RotatingLog(self.LOG_LINES)
        self.ignore_index_change_interval = self.vcm.get_frames_count() // 200

        self.mode_handlers = mode_handlers
        self.mode_handler_i = 0

        self.key_mapper = KeyMapper()
        self.bbm = BoundingBoxManager(self.vcm.get_frames_count())
        self.bbm.set_to(
            context,
            selected=[
                obj.id for obj in context.bbox_fields.objects.values()
                if obj.has_collision
            ],
        )

    def start(self):
        self.set_GUI()
        try:
            self.play_video()
        except ManualTaggingAbortedException:
            raise
        finally:
            self.cleanup()

    def set_GUI(self):
        cv2.namedWindow(self.WINDOW_NAME)
        cv2.resizeWindow(self.WINDOW_NAME, self.vcm.get_width(),
                         self.vcm.get_height() + self.IMG_STARTING_Y)
        cv2.setMouseCallback(
            self.WINDOW_NAME,
            lambda event, x, y, flags, param: self.handleClick(
                event, x, y, flags, param))

        def set_frame_rate_callback(value):
            self.frame_rate = max(1, value)

        def set_progress_rate_callback(value):
            if abs(value - self.vcm.get_frame_index()) > self.ignore_index_change_interval or \
                    value == 0 or value == self.vcm.get_frames_count()-1:
                self.vcm.start_from(value)

        def set_paused_callback(value):
            if self.vcm is not None:
                self.vcm.set_paused(value)

        cv2.createTrackbar(self.PROGRESS_BAR_NAME, self.WINDOW_NAME, 0,
                           max(0,
                               self.vcm.get_frames_count() - 1),
                           set_progress_rate_callback)
        cv2.createTrackbar(self.FRAME_RATE_BAR_NAME, self.WINDOW_NAME,
                           self.frame_rate, 200, set_frame_rate_callback)
        cv2.createTrackbar(self.PAUSE_BUTTON_NAME, self.WINDOW_NAME, False, 1,
                           set_paused_callback)

    def play_video(self):
        shown_for_first_time = False
        while True:
            if shown_for_first_time and cv2.getWindowProperty(
                    self.WINDOW_NAME,
                    cv2.WND_PROP_VISIBLE) <= 0:  # Window closed. Abort
                raise ManualTaggingExitedException(
                    "Tagging operation aborted by closing window")

            frame = self.vcm.next().copy()
            frame_index = self.vcm.get_frame_index()
            frame = self.modify_frame(frame, frame_index)
            frame = self.get_mode_handler().modify_frame(frame, frame_index)
            cv2.imshow(self.WINDOW_NAME, self.build_frame(frame))
            shown_for_first_time = True

            cv2.setTrackbarPos(self.PROGRESS_BAR_NAME, self.WINDOW_NAME,
                               frame_index)

            self.key_mapper.append(cv2.waitKey(self.frame_rate) & 0xFF)
            if self.key_mapper.consume(("esc", "esc")):  # Escape key
                res = ButtonPopup(
                    "Confirm restart",
                    "Hitting confirm will destroy all progress. You will have to restart. Continue?",
                    ["Confirm", "Cancel"],
                ).run()
                if res == "Confirm":
                    raise ManualTaggingAbortedException(
                        "Tagging operation aborted")
            elif self.key_mapper.consume(("enter", "enter")):  # Enter
                if not self.can_commit():
                    self.logger.log("[ERROR] Commit operation failed")
                else:
                    res = ButtonPopup("Confirm commit",
                                      self.get_commit_message(),
                                      ["Confirm", "Cancel"]).run()
                    if res == "Confirm":
                        break
            elif self.key_mapper.consume("h"):
                window = HelpPopup(
                    "GUI controls reference",
                    self.INSTRUCTIONS + [["", ""]] +
                    self.get_mode_handler().INSTRUCTIONS,
                )
                window.run()
            elif self.key_mapper.consume("a"):
                self.vcm.shift_frame_index(-1)
            elif self.key_mapper.consume("s"):
                self.vcm.shift_frame_index(-10)
            elif self.key_mapper.consume("d"):
                self.vcm.shift_frame_index(1)
            elif self.key_mapper.consume("w"):
                self.vcm.shift_frame_index(10)
            elif self.key_mapper.consume(" "):
                cv2.setTrackbarPos(self.PAUSE_BUTTON_NAME, self.WINDOW_NAME,
                                   0 if self.vcm.get_paused() else 1)

            if self.key_mapper.consume("m") or self.key_mapper.consume("tab"):
                self.mode_handler_i += 1
                self.mode_handler_i %= len(self.mode_handlers)
                self.logger.log("Changed mode")
            else:
                self.get_mode_handler().handle_keyboard(self.key_mapper)

    def build_frame(self, frame):
        img = np.zeros(
            (self.context.file_height + self.IMG_STARTING_Y,
             self.context.file_width, 3),
            np.uint8,
        )

        def write_top_text():
            font = cv2.FONT_HERSHEY_SIMPLEX
            font_scale = 0.5
            font_color = (255, 255, 255)
            for i, msg in enumerate(self.logger.get_logs()):
                starting_index = (self.LOG_START_X, self.LOG_LINE_HEIGHT *
                                  (i + 1) + self.LOG_LINE_MARGIN * i)
                cv2.putText(img, msg, starting_index, font, font_scale,
                            font_color)

            for i, msg in enumerate(
                    self.get_mode_handler().get_state_message()):
                starting_index = (0, self.LOG_LINE_HEIGHT * (i + 1) +
                                  self.LOG_LINE_MARGIN * i)
                cv2.putText(img, msg, starting_index, font, font_scale,
                            font_color)

        write_top_text()
        displayed = cv2.cvtColor(frame, cv2.IMREAD_COLOR)
        img[self.IMG_STARTING_Y:, 0:] = displayed
        return img

    def handleClick(self, event, x, y, flags, param):
        y = y - self.IMG_STARTING_Y
        self.get_mode_handler().handle_click(event, x, y, flags, param)

    def get_mode_handler(self):
        return self.mode_handlers[self.mode_handler_i]

    def cleanup(self):
        self.vcm.release()
        cv2.destroyAllWindows()

    def modify_frame(self, frame, frame_index):
        raise NotImplementedError()

    def can_commit(self):
        raise NotImplementedError()

    def get_commit_message(self):
        return "Hitting confirm will commit all changes. You will not be able to undo any changes afterwards. Continue?"
Exemplo n.º 8
0
class VideoPlayerGUIManager(object):
    PROGRESS_BAR_NAME = "progress"
    FRAME_RATE_BAR_NAME = "frame_delay"
    PAUSE_BUTTON_NAME = "pause"
    WINDOW_NAME = 'tagger'

    LOG_LINES = 6
    LOG_LINE_HEIGHT = 17
    LOG_LINE_MARGIN = 2
    LOG_START_X = 250
    IMG_STARTING_Y = LOG_LINE_HEIGHT * LOG_LINES + LOG_LINE_MARGIN * (
        LOG_LINES + 1) + 3

    def __init__(self, context: VideoTaggingContext):
        self.context = context
        self.vcm = self.context.vcm
        self.frame_rate = 25
        self.logger = RotatingLog(self.LOG_LINES)
        self.ignore_index_change_interval = self.vcm.get_frames_count() // 200

        self.bbm = BoundingBoxManager(self.vcm.get_frames_count())
        self.bbm.set_to(*context.get_bbox_fields_as_list())

        self.mode_handlers = [
            InternaSelectionMode(self),
            InternalBBoxMode(self)
        ]
        self.mode_handler_i = 0

        self.instructions = GENERAL_INSTRUCTIONS
        self.key_mapper = KeyMapper()

    def start(self):
        self.set_GUI()
        try:
            self.logger.log("Starting with: {0} bounding box ids".format(
                self.bbm.get_n_ids()))
            self.play_video()
        except ManualTaggingAbortedException:
            raise
        finally:
            self.cleanup()

        self.context.set_bbox_fields_from_list(self.bbm.extract())

    def set_GUI(self):
        cv2.namedWindow(self.WINDOW_NAME)
        cv2.setMouseCallback(
            self.WINDOW_NAME,
            lambda event, x, y, flags, param: self.handleClick(
                event, x, y, flags, param))

        def set_frame_rate_callback(value):
            self.frame_rate = max(1, value)

        def set_progress_rate_callback(value):
            if abs(value - self.vcm.get_frame_index()) > self.ignore_index_change_interval or \
                    value == 0 or value == self.vcm.get_frames_count()-1:
                self.vcm.start_from(value)

        def set_paused_callback(value):
            if self.vcm is not None:
                self.vcm.set_paused(value)

        cv2.createTrackbar(self.PROGRESS_BAR_NAME, self.WINDOW_NAME, 0,
                           max(0,
                               self.vcm.get_frames_count() - 1),
                           set_progress_rate_callback)
        cv2.createTrackbar(self.FRAME_RATE_BAR_NAME, self.WINDOW_NAME,
                           self.frame_rate, 200, set_frame_rate_callback)
        cv2.createTrackbar(self.PAUSE_BUTTON_NAME, self.WINDOW_NAME, False, 1,
                           set_paused_callback)

    def play_video(self):
        shown_for_first_time = False
        while True:
            if shown_for_first_time and cv2.getWindowProperty(
                    self.WINDOW_NAME,
                    cv2.WND_PROP_VISIBLE) <= 0:  # Window closed. Abort
                raise ManualTaggingExitedException(
                    "Tagging operation aborted by closing window")

            frame = self.vcm.next().copy()
            frame_index = self.vcm.get_frame_index()
            frame = self.bbm.modify_frame(frame, frame_index)
            frame = self.get_mode_handler().modify_frame(frame, frame_index)
            cv2.imshow(self.WINDOW_NAME, self.build_frame(frame))
            shown_for_first_time = True

            cv2.setTrackbarPos(self.PROGRESS_BAR_NAME, self.WINDOW_NAME,
                               frame_index)

            self.key_mapper.append(cv2.waitKey(self.frame_rate) & 0xFF)
            if self.key_mapper.consume("esc"):  # Escape key
                res = ButtonPopup(
                    "Confirm restart",
                    "Hitting confirm will destroy all progress. You will have to restart. Continue?",
                    ["Confirm", "Cancel"]).run()
                if res == "Confirm":
                    raise ManualTaggingAbortedException(
                        "Tagging operation aborted")
            elif self.key_mapper.consume("enter"):  # Enter
                res = ButtonPopup(
                    "Confirm commit",
                    "Hitting confirm will commit all changes. You will not be able to undo any changes afterwards. Continue?",
                    ["Confirm", "Cancel"]).run()
                if res == "Confirm":
                    break
            elif self.key_mapper.consume("h"):
                window = HelpPopup(
                    "GUI controls reference", self.instructions + [["", ""]] +
                    self.get_mode_handler().instructions)
                window.run()
            elif self.key_mapper.consume("a"):
                self.vcm.shift_frame_index(-1)
            elif self.key_mapper.consume("s"):
                self.vcm.shift_frame_index(-10)
            elif self.key_mapper.consume("d"):
                self.vcm.shift_frame_index(1)
            elif self.key_mapper.consume("w"):
                self.vcm.shift_frame_index(10)
            elif self.key_mapper.consume(" "):
                cv2.setTrackbarPos(self.PAUSE_BUTTON_NAME, self.WINDOW_NAME,
                                   0 if self.vcm.get_paused() else 1)

            if self.key_mapper.consume("m"):
                self.mode_handler_i += 1
                self.mode_handler_i %= len(self.mode_handlers)
                self.logger.log("Changed mode")
            else:
                self.get_mode_handler().handle_keyboard(self.key_mapper)

    def build_frame(self, frame):
        img = np.zeros((self.context.file_height + self.IMG_STARTING_Y,
                        self.context.file_width, 3), np.uint8)

        def write_top_text():
            font = cv2.FONT_HERSHEY_DUPLEX
            font_scale = 0.5
            font_color = (255, 255, 255)
            for i, msg in enumerate(self.logger.get_logs()):
                starting_index = (self.LOG_START_X, self.LOG_LINE_HEIGHT *
                                  (i + 1) + self.LOG_LINE_MARGIN * i)
                cv2.putText(img,
                            msg,
                            starting_index,
                            font,
                            font_scale,
                            font_color,
                            lineType=cv2.LINE_AA)

            for i, msg in enumerate(
                    self.get_mode_handler().get_state_message()):
                starting_index = (0, self.LOG_LINE_HEIGHT * (i + 1) +
                                  self.LOG_LINE_MARGIN * i)
                cv2.putText(img,
                            msg,
                            starting_index,
                            font,
                            font_scale,
                            font_color,
                            lineType=cv2.LINE_AA)

        write_top_text()
        displayed = cv2.cvtColor(frame, cv2.IMREAD_COLOR)
        img[self.IMG_STARTING_Y:, 0:] = displayed
        return img

    def handleClick(self, event, x, y, flags, param):
        y = y - self.IMG_STARTING_Y
        self.get_mode_handler().handle_click(event, x, y, flags, param)

    def get_mode_handler(self):
        return self.mode_handlers[self.mode_handler_i]

    def cleanup(self):
        self.vcm.release()
        cv2.destroyAllWindows()