Example #1
0
    def __str__(self):
        content = "ChangeRegion -> ID: " + str(self.number_id) + "\n"
        content += " Time: " + TimeHelper.stampToStr(self.creation_time)
        content += " - " + TimeHelper.stampToStr(self.last_modified)
        if self.locked_time >= 0.0:
            content += " - " + TimeHelper.stampToStr(self.locked_time)

        if self.erased_time is None:
            content += "\n"
        else:
            content += " - " + TimeHelper.stampToStr(self.erased_time) + "\n"
        content += " Region: X: [" + str(self.min_x) + ", " + str(
            self.max_x) + "]"
        content += ", Y: [" + str(self.min_y) + ", " + str(self.max_y) + "]\n"
        content += " Cells: " + str(len(self.cells))

        return content
    def update_selected_keyframe(self, new_selected):
        if 0 <= new_selected < len(self.keyframe_annotations):
            self.selected_keyframe = new_selected
        else:
            return

        self.lbl_nav_keyframe.set_text("Key-Frame: " + str(self.selected_keyframe + 1) + " / " +
                                       str(len(self.keyframe_annotations)))

        time_str = TimeHelper.stampToStr(self.keyframe_annotations[self.selected_keyframe].time)
        self.lbl_nav_time.set_text(time_str)

        self.update_current_view()
    def start_input_processing(self, process_function):
        for lecture in self.database.lectures:
            self.current_lecture = lecture
            m_videos, lecture_file, skip = self.get_lecture_params(lecture)

            if skip:
                continue

            # read temporal file
            if self.input_temp_prefix is None:
                # null-input process (convenient way to process lectures)
                input_data = None
            else:
                if not isinstance(self.input_temp_prefix, list):
                    input_data = MiscHelper.dump_load(self.temp_dir + '/' +
                                                      self.input_temp_prefix +
                                                      lecture_file)
                else:
                    input_data = []
                    for temp_prefix in self.input_temp_prefix:
                        input_data.append(
                            MiscHelper.dump_load(self.temp_dir + '/' +
                                                 temp_prefix + lecture_file))

            # execute the actual process ....
            timer = TimeHelper()
            timer.startTimer()
            results = process_function(self, input_data)
            timer.endTimer()

            print("Process Finished in: " + timer.totalElapsedStamp())

            # save results
            if self.output_temp_prefix is not None:
                if not isinstance(self.output_temp_prefix, list):
                    MiscHelper.dump_save(
                        results, self.temp_dir + '/' +
                        self.output_temp_prefix + lecture_file)
                else:
                    for out_idx, temp_prefix in enumerate(
                            self.output_temp_prefix):
                        MiscHelper.dump_save(
                            results[out_idx],
                            self.temp_dir + '/' + temp_prefix + lecture_file)
Example #4
0
    def doProcessing(self, video_worker, limit=0, verbose=False):
        # initially....
        width = None
        height = None

        offset_frame = -1
        absolute_frame = 0
        absolute_time = 0.0
        last_frame = None

        next_sample_frame_idx = 0

        if verbose:
            print("Video processing for " + video_worker.getWorkName() +
                  " has begun")

        # for timer...
        timer = TimeHelper()
        timer.startTimer()

        # open video...
        for video_idx, video_file in enumerate(self.file_list):
            try:
                capture = cv2.VideoCapture(video_file)
            except Exception as e:
                # error loading
                raise Exception("The file <" + video_file +
                                "> could not be opened")

            capture_width = int(capture.get(cv2.CAP_PROP_FRAME_WIDTH))
            capture_height = int(capture.get(cv2.CAP_PROP_FRAME_HEIGHT))

            # ...check size ...
            forced_resizing = False
            if width is None:
                # first video....
                # initialize local parameters....
                if self.forced_width is not None:
                    # ...size...
                    width = self.forced_width
                    height = self.forced_height

                    if capture_width != self.forced_width or capture_height != self.forced_height:
                        forced_resizing = True
                else:
                    width = capture_width
                    height = capture_height

                # on the worker class...
                video_worker.initialize(width, height)
            else:
                if self.forced_width is not None:
                    forced_resizing = (capture_width != self.forced_width
                                       or capture_height != self.forced_height)
                else:
                    if (width != capture_width) or (height != capture_height):
                        # invalid, all main video files must be the same resolution...
                        raise Exception(
                            "All video files on the list must have the same resolution"
                        )

            # Read video until the end or until limit has been reached
            while (limit == 0 or offset_frame < limit
                   ) and not next_sample_frame_idx >= len(self.frame_list):

                if offset_frame == self.frame_list[next_sample_frame_idx]:
                    # grab and decode next frame since it is in the list
                    flag, frame = capture.read()
                else:
                    # just grab to move forward in the list
                    flag = capture.grab()

                # print("Grab time: " + str(capture.get(cv2.CAP_PROP_POS_FRAMES)))
                # print((valid_grab, flag, type(frame), selection_step, jump_frames))
                if not flag:
                    # end of video reached...
                    break

                if offset_frame == self.frame_list[next_sample_frame_idx]:
                    # continue ..
                    current_time = capture.get(cv2.CAP_PROP_POS_MSEC)
                    current_frame = capture.get(cv2.CAP_PROP_POS_FRAMES)

                    if forced_resizing:
                        frame = cv2.resize(
                            frame, (self.forced_width, self.forced_height))

                    frame_time = absolute_time + current_time
                    frame_idx = int(absolute_frame + current_frame)
                    video_worker.handleFrame(frame, last_frame, video_idx,
                                             frame_time, current_time,
                                             frame_idx)

                    if verbose:
                        print("Frames Processed = {0:d}, Video Time = {1:s}".
                              format(offset_frame,
                                     TimeHelper.stampToStr(frame_time)))

                    last_frame = frame

                    next_sample_frame_idx += 1
                    if next_sample_frame_idx >= len(self.frame_list):
                        # end of sample reached ...
                        break

                offset_frame += 1

            # at the end of the processing of current video
            capture.set(cv2.CAP_PROP_POS_AVI_RATIO, 1.0)
            video_length = capture.get(cv2.CAP_PROP_POS_MSEC)
            video_frames = capture.get(cv2.CAP_PROP_POS_FRAMES)

            absolute_time += video_length
            absolute_frame += video_frames

        # processing finished...
        video_worker.finalize()

        # end time counter...
        timer.endTimer()

        if verbose:
            print("Video processing for " + video_worker.getWorkName() +
                  " completed: " +
                  TimeHelper.stampToStr(timer.lastElapsedTime() * 1000.0))
    def __init__(self, size, db_name, lecture_title, output_path):
        Screen.__init__(self, "Formula Ground Truth Annotation Interface",
                        size)

        general_background = (50, 80, 160)
        text_color = (255, 255, 255)
        button_text_color = (20, 20, 50)
        button_back_color = (228, 228, 228)

        self.elements.back_color = general_background

        self.db_name = db_name
        self.lecture_title = lecture_title

        self.output_path = output_path

        export_filename = self.output_path + "/segments.xml"
        export_image_prefix = self.output_path + "/keyframes/"
        # load including segment information
        self.keyframe_annotations, self.segments = KeyFrameAnnotation.LoadExportedKeyframes(
            export_filename, export_image_prefix, True)

        if len(self.keyframe_annotations) > 0:
            print("Key-frames Loaded: " + str(len(self.keyframe_annotations)))
        else:
            raise Exception("Cannot start with 0 key-frames")

        portions_filename = self.output_path + "/portions.xml"
        portions_path = self.output_path + "/portions/"
        if os.path.exists(portions_filename):
            # Saved data detected, loading
            print("Previously saved portion data detected, loading")
            KeyFrameAnnotation.LoadKeyframesPortions(portions_filename,
                                                     self.keyframe_annotations,
                                                     portions_path)
        else:
            raise Exception("No saved portion data detected, cannot continue")

        print("Original Key-frames: " + str(len(self.keyframe_annotations)))
        print("Segments: " + str(len(self.segments)))

        self.keyframe_annotations = KeyFrameAnnotation.CombineKeyframesPerSegment(
            self.keyframe_annotations, self.segments, True)
        print("Key-frames after combination per segment" +
              str(len(self.keyframe_annotations)))

        # other CC/group elements
        self.unique_groups = None
        self.cc_group = None
        self.cc_total = 0
        for kf_idx, keyframe in enumerate(self.keyframe_annotations):
            self.cc_total += len(keyframe.binary_cc)

        unique_cc_filename = self.output_path + "/unique_ccs.xml"
        if os.path.exists(unique_cc_filename):
            # Saved data detected, loading
            print("Previously saved unique CC data detected, loading")

            self.cc_group, self.unique_groups = UniqueCCGroup.GroupsFromXML(
                self.keyframe_annotations, unique_cc_filename)
        else:
            # no previous data, build default index (all CCs are unique)
            raise Exception(
                "No unique CC data found for lecture. Must label Unique CC first"
            )

        self.view_mode = GTFormulaAnnotator.ViewModeColored
        self.edition_mode = GTFormulaAnnotator.ModeNavigate
        self.view_scale = 1.0
        self.selected_keyframe = 0

        self.selected_formula = 0
        self.adding_groups = []
        self.formulas_per_frame = [
            [] for idx in range(len(self.keyframe_annotations))
        ]

        saved_filename = self.output_path + "/formula_ccs.xml"
        if os.path.exists(saved_filename):
            # load saved data ...
            self.formulas_ccs = FormulaCCs.FormulasFromXML(
                self.unique_groups, saved_filename)
            # add to index per frame ...
            for formula in self.formulas_ccs:
                for frame_idx in range(formula.first_visible,
                                       formula.last_visible + 1):
                    self.formulas_per_frame[frame_idx].append(formula)

            print("Loaded: " + saved_filename)
        else:
            self.formulas_ccs = []

        # update draw cache of highlighted formulae ...
        self.colored_cache = [None] * len(self.keyframe_annotations)
        for idx in range(len(self.keyframe_annotations)):
            self.update_colored_cache(idx)

        # add elements....
        container_top = 10
        container_width = 330

        button_2_width = 150
        button_2_left = int(container_width * 0.25) - button_2_width / 2
        button_2_right = int(container_width * 0.75) - button_2_width / 2

        # Navigation panel to move accross frames
        self.container_nav_buttons = ScreenContainer(
            "container_nav_buttons", (container_width, 70),
            back_color=general_background)
        self.container_nav_buttons.position = (
            self.width - self.container_nav_buttons.width - 10, container_top)
        self.elements.append(self.container_nav_buttons)

        self.lbl_nav_keyframe = ScreenLabel(
            "lbl_nav_keyframe",
            "Key-Frame: 1 / " + str(len(self.keyframe_annotations)), 21, 290,
            1)
        self.lbl_nav_keyframe.position = (5, 5)
        self.lbl_nav_keyframe.set_background(general_background)
        self.lbl_nav_keyframe.set_color(text_color)
        self.container_nav_buttons.append(self.lbl_nav_keyframe)

        time_str = TimeHelper.stampToStr(
            self.keyframe_annotations[self.selected_keyframe].time)
        self.lbl_nav_time = ScreenLabel("lbl_nav_time", time_str, 21, 290, 1)
        self.lbl_nav_time.position = (5,
                                      self.lbl_nav_keyframe.get_bottom() + 20)
        self.lbl_nav_time.set_background(general_background)
        self.lbl_nav_time.set_color(text_color)
        self.container_nav_buttons.append(self.lbl_nav_time)

        self.btn_nav_keyframe_prev = ScreenButton("btn_nav_keyframe_prev",
                                                  "Prev", 21, 90)
        self.btn_nav_keyframe_prev.set_colors(button_text_color,
                                              button_back_color)
        self.btn_nav_keyframe_prev.position = (
            10, self.lbl_nav_keyframe.get_bottom() + 10)
        self.btn_nav_keyframe_prev.click_callback = self.btn_nav_keyframe_prev_click
        self.container_nav_buttons.append(self.btn_nav_keyframe_prev)

        self.btn_nav_keyframe_next = ScreenButton("btn_nav_keyframe_next",
                                                  "Next", 21, 90)
        self.btn_nav_keyframe_next.set_colors(button_text_color,
                                              button_back_color)
        self.btn_nav_keyframe_next.position = (
            self.container_nav_buttons.width -
            self.btn_nav_keyframe_next.width - 10,
            self.lbl_nav_keyframe.get_bottom() + 10)
        self.btn_nav_keyframe_next.click_callback = self.btn_nav_keyframe_next_click
        self.container_nav_buttons.append(self.btn_nav_keyframe_next)

        # ================================================================================

        # confirmation panel
        self.container_confirm_buttons = ScreenContainer(
            "container_confirm_buttons", (container_width, 70),
            back_color=general_background)
        self.container_confirm_buttons.position = (
            self.width - self.container_confirm_buttons.width - 10,
            container_top)
        self.elements.append(self.container_confirm_buttons)
        self.container_confirm_buttons.visible = False

        self.lbl_confirm_message = ScreenLabel(
            "lbl_confirm_message", "Confirmation message goes here?", 21, 290,
            1)
        self.lbl_confirm_message.position = (5, 5)
        self.lbl_confirm_message.set_background(general_background)
        self.lbl_confirm_message.set_color(text_color)
        self.container_confirm_buttons.append(self.lbl_confirm_message)

        self.btn_confirm_cancel = ScreenButton("btn_confirm_cancel", "Cancel",
                                               21, 130)
        self.btn_confirm_cancel.set_colors(button_text_color,
                                           button_back_color)
        self.btn_confirm_cancel.position = (
            10, self.lbl_nav_keyframe.get_bottom() + 10)
        self.btn_confirm_cancel.click_callback = self.btn_confirm_cancel_click
        self.container_confirm_buttons.append(self.btn_confirm_cancel)

        self.btn_confirm_accept = ScreenButton("btn_confirm_accept", "Accept",
                                               21, 130)
        self.btn_confirm_accept.set_colors(button_text_color,
                                           button_back_color)
        self.btn_confirm_accept.position = (
            self.container_confirm_buttons.width -
            self.btn_confirm_accept.width - 10,
            self.lbl_confirm_message.get_bottom() + 10)
        self.btn_confirm_accept.click_callback = self.btn_confirm_accept_click
        self.container_confirm_buttons.append(self.btn_confirm_accept)

        # ================================================================================

        # View panel with view control buttons
        self.container_view_buttons = ScreenContainer(
            "container_view_buttons", (container_width, 165),
            back_color=general_background)
        self.container_view_buttons.position = (
            self.width - self.container_view_buttons.width - 10,
            self.container_nav_buttons.get_bottom() + 10)
        self.elements.append(self.container_view_buttons)

        button_width = 190
        button_left = (self.container_view_buttons.width - button_width) / 2

        # zoom ....
        self.lbl_zoom = ScreenLabel("lbl_zoom", "Zoom: 100%", 21,
                                    container_width - 10, 1)
        self.lbl_zoom.position = (5, 5)
        self.lbl_zoom.set_background(general_background)
        self.lbl_zoom.set_color(text_color)
        self.container_view_buttons.append(self.lbl_zoom)

        self.btn_zoom_reduce = ScreenButton("btn_zoom_reduce", "[ - ]", 21, 90)
        self.btn_zoom_reduce.set_colors(button_text_color, button_back_color)
        self.btn_zoom_reduce.position = (10, self.lbl_zoom.get_bottom() + 10)
        self.btn_zoom_reduce.click_callback = self.btn_zoom_reduce_click
        self.container_view_buttons.append(self.btn_zoom_reduce)

        self.btn_zoom_increase = ScreenButton("btn_zoom_increase", "[ + ]", 21,
                                              90)
        self.btn_zoom_increase.set_colors(button_text_color, button_back_color)
        self.btn_zoom_increase.position = (self.container_view_buttons.width -
                                           self.btn_zoom_increase.width - 10,
                                           self.lbl_zoom.get_bottom() + 10)
        self.btn_zoom_increase.click_callback = self.btn_zoom_increase_click
        self.container_view_buttons.append(self.btn_zoom_increase)

        self.btn_zoom_zero = ScreenButton("btn_zoom_zero", "100%", 21, 90)
        self.btn_zoom_zero.set_colors(button_text_color, button_back_color)
        self.btn_zoom_zero.position = (
            (self.container_view_buttons.width - self.btn_zoom_zero.width) / 2,
            self.lbl_zoom.get_bottom() + 10)
        self.btn_zoom_zero.click_callback = self.btn_zoom_zero_click
        self.container_view_buttons.append(self.btn_zoom_zero)

        self.btn_view_raw = ScreenButton("btn_view_raw", "Raw View", 21,
                                         button_2_width)
        self.btn_view_raw.set_colors(button_text_color, button_back_color)
        self.btn_view_raw.position = (button_2_left,
                                      self.btn_zoom_zero.get_bottom() + 10)
        self.btn_view_raw.click_callback = self.btn_view_raw_click
        self.container_view_buttons.append(self.btn_view_raw)

        self.btn_view_gray = ScreenButton("btn_view_gray", "Grayscale View",
                                          21, button_2_width)
        self.btn_view_gray.set_colors(button_text_color, button_back_color)
        self.btn_view_gray.position = (button_2_right,
                                       self.btn_zoom_zero.get_bottom() + 10)
        self.btn_view_gray.click_callback = self.btn_view_gray_click
        self.container_view_buttons.append(self.btn_view_gray)

        self.btn_view_binary = ScreenButton("btn_view_binary", "Binary View",
                                            21, button_2_width)
        self.btn_view_binary.set_colors(button_text_color, button_back_color)
        self.btn_view_binary.position = (button_2_left,
                                         self.btn_view_gray.get_bottom() + 10)
        self.btn_view_binary.click_callback = self.btn_view_binary_click
        self.container_view_buttons.append(self.btn_view_binary)

        self.btn_view_colored = ScreenButton("btn_view_colored",
                                             "Colored View", 21,
                                             button_2_width)
        self.btn_view_colored.set_colors(button_text_color, button_back_color)
        self.btn_view_colored.position = (button_2_right,
                                          self.btn_view_gray.get_bottom() + 10)
        self.btn_view_colored.click_callback = self.btn_view_colored_click
        self.container_view_buttons.append(self.btn_view_colored)

        # =================================================================================

        # Panel with action buttons (Add/Remove links)
        self.container_action_buttons = ScreenContainer(
            "container_action_buttons", (container_width, 240),
            general_background)
        self.container_action_buttons.position = (
            self.container_view_buttons.get_left(),
            self.container_view_buttons.get_bottom() + 5)
        self.elements.append(self.container_action_buttons)

        self.lbl_nav_formula = ScreenLabel("lbl_nav_formula", "X / X", 21,
                                           container_width - 10, 1)
        self.lbl_nav_formula.position = (5, 5)
        self.lbl_nav_formula.set_background(general_background)
        self.lbl_nav_formula.set_color(text_color)
        self.container_action_buttons.append(self.lbl_nav_formula)

        self.btn_nav_formula_prev = ScreenButton("btn_nav_formula_prev",
                                                 "Prev", 21, button_2_width)
        self.btn_nav_formula_prev.set_colors(button_text_color,
                                             button_back_color)
        self.btn_nav_formula_prev.position = (
            button_2_left, self.lbl_nav_formula.get_bottom() + 10)
        self.btn_nav_formula_prev.click_callback = self.btn_nav_formula_prev_click
        self.container_action_buttons.append(self.btn_nav_formula_prev)

        self.btn_nav_formula_next = ScreenButton("btn_nav_formula_next",
                                                 "Next", 21, button_2_width)
        self.btn_nav_formula_next.set_colors(button_text_color,
                                             button_back_color)
        self.btn_nav_formula_next.position = (
            button_2_right, self.lbl_nav_formula.get_bottom() + 10)
        self.btn_nav_formula_next.click_callback = self.btn_nav_formula_next_click
        self.container_action_buttons.append(self.btn_nav_formula_next)

        self.btn_formulas_add = ScreenButton("btn_formulas_add", "Add Formula",
                                             21, button_2_width)
        self.btn_formulas_add.set_colors(button_text_color, button_back_color)
        self.btn_formulas_add.position = (
            button_2_left, self.btn_nav_formula_next.get_bottom() + 20)
        self.btn_formulas_add.click_callback = self.btn_formulas_add_click
        self.container_action_buttons.append(self.btn_formulas_add)

        self.btn_formulas_del = ScreenButton("btn_formulas_del",
                                             "Del. Formula", 21,
                                             button_2_width)
        self.btn_formulas_del.set_colors(button_text_color, button_back_color)
        self.btn_formulas_del.position = (
            button_2_right, self.btn_nav_formula_next.get_bottom() + 20)
        self.btn_formulas_del.click_callback = self.btn_formulas_del_click
        self.container_action_buttons.append(self.btn_formulas_del)

        self.btn_formula_update_tag = ScreenButton("btn_formula_update_tag",
                                                   "Update Tag", 21,
                                                   button_width)
        self.btn_formula_update_tag.set_colors(button_text_color,
                                               button_back_color)
        self.btn_formula_update_tag.position = (
            button_left, self.btn_formulas_del.get_bottom() + 20)
        self.btn_formula_update_tag.click_callback = self.btn_formula_update_tag_click
        self.container_action_buttons.append(self.btn_formula_update_tag)

        self.lbl_formula_tag = ScreenLabel("lbl_formula_tag", "Tag: ?", 21,
                                           container_width - 10, 1)
        self.lbl_formula_tag.position = (
            5, self.btn_formula_update_tag.get_bottom() + 20)
        self.lbl_formula_tag.set_background(general_background)
        self.lbl_formula_tag.set_color(text_color)
        self.container_action_buttons.append(self.lbl_formula_tag)

        # ===============================================
        stats_background = (60, 50, 40)
        self.container_stats = ScreenContainer("container_stats",
                                               (container_width, 70),
                                               back_color=stats_background)
        self.container_stats.position = (
            self.width - container_width - 10,
            self.container_action_buttons.get_bottom() + 5)
        self.elements.append(self.container_stats)

        self.lbl_cc_stats = ScreenLabel("lbl_cc_stats",
                                        "Connected Component Stats", 21,
                                        container_width - 10, 1)
        self.lbl_cc_stats.position = (5, 5)
        self.lbl_cc_stats.set_background(stats_background)
        self.lbl_cc_stats.set_color(text_color)
        self.container_stats.append(self.lbl_cc_stats)

        self.lbl_cc_raw = ScreenLabel("lbl_cc_raw",
                                      "Total Raw CC:\n" + str(self.cc_total),
                                      21, button_2_width, 1)
        self.lbl_cc_raw.position = (button_2_left,
                                    self.lbl_cc_stats.get_bottom() + 10)
        self.lbl_cc_raw.set_background(stats_background)
        self.lbl_cc_raw.set_color(text_color)
        self.container_stats.append(self.lbl_cc_raw)

        self.lbl_cc_unique = ScreenLabel(
            "lbl_cc_unique",
            "Total Unique CC:\n" + str(len(self.unique_groups)), 21,
            button_2_width, 1)
        self.lbl_cc_unique.position = (button_2_right,
                                       self.lbl_cc_stats.get_bottom() + 10)
        self.lbl_cc_unique.set_background(stats_background)
        self.lbl_cc_unique.set_color(text_color)
        self.container_stats.append(self.lbl_cc_unique)

        #=============================================================
        # Panel with state buttons (Undo, Redo, Save)
        self.container_state_buttons = ScreenContainer(
            "container_state_buttons", (container_width, 200),
            general_background)
        self.container_state_buttons.position = (
            self.container_view_buttons.get_left(),
            self.container_stats.get_bottom() + 10)
        self.elements.append(self.container_state_buttons)

        self.btn_undo = ScreenButton("btn_undo", "Undo", 21, button_width)
        self.btn_undo.set_colors(button_text_color, button_back_color)
        self.btn_undo.position = (button_left, 5)
        self.btn_undo.click_callback = self.btn_undo_click
        self.container_state_buttons.append(self.btn_undo)

        self.btn_redo = ScreenButton("btn_redo", "Redo", 21, button_width)
        self.btn_redo.set_colors(button_text_color, button_back_color)
        self.btn_redo.position = (button_left, self.btn_undo.get_bottom() + 10)
        self.btn_redo.click_callback = self.btn_redo_click
        self.container_state_buttons.append(self.btn_redo)

        self.btn_save = ScreenButton("btn_save", "Save", 21, button_width)
        self.btn_save.set_colors(button_text_color, button_back_color)
        self.btn_save.position = (button_left, self.btn_redo.get_bottom() + 10)
        self.btn_save.click_callback = self.btn_save_click
        self.container_state_buttons.append(self.btn_save)

        self.btn_exit = ScreenButton("btn_exit", "Exit", 21, button_width)
        self.btn_exit.set_colors(button_text_color, button_back_color)
        self.btn_exit.position = (button_left, self.btn_save.get_bottom() + 30)
        self.btn_exit.click_callback = self.btn_exit_click
        self.container_state_buttons.append(self.btn_exit)

        # ==============================================================
        image_width = self.width - self.container_nav_buttons.width - 30
        image_height = self.height - container_top - 10
        self.container_images = ScreenContainer("container_images",
                                                (image_width, image_height),
                                                back_color=(0, 0, 0))
        self.container_images.position = (10, container_top)
        self.elements.append(self.container_images)

        # ... image objects ...
        tempo_blank = np.zeros((50, 50, 3), np.uint8)
        tempo_blank[:, :, :] = 255
        self.img_main = ScreenImage("img_main", tempo_blank, 0, 0, True,
                                    cv2.INTER_NEAREST)
        self.img_main.position = (0, 0)
        self.img_main.mouse_button_down_callback = self.img_mouse_down
        self.container_images.append(self.img_main)

        # canvas used for annotations
        self.canvas_select = ScreenCanvas("canvas_select", 100, 100)
        self.canvas_select.position = (0, 0)
        self.canvas_select.locked = False
        # self.canvas_select.object_edited_callback = self.canvas_object_edited
        # self.canvas_select.object_selected_callback = self.canvas_selection_changed
        self.container_images.append(self.canvas_select)

        self.canvas_select.add_element("selection_rectangle", 10, 10, 40, 40)
        self.canvas_select.elements["selection_rectangle"].visible = False

        self.undo_stack = []
        self.redo_stack = []

        self.update_selected_formula(0)

        self.update_current_view(True)
Example #6
0
    def computeVisualAlignment(m_videos, a_videos, time_offset, motionless,
                               save_frames, extraction_method_id):
        #distribute the selection of motionless frames...
        selected = MiscHelper.distribute_values(Aligner.ALIGNMENT_SAMPLE, 0,
                                                len(motionless) - 1)

        #create the list..
        frame_list = []
        for idx in selected:
            frame_list.append(motionless[idx])

        #extract the motionless frames from main videos
        frames = Loader.extractFramesRelative(m_videos, frame_list)
        if save_frames:
            for idx, f in enumerate(frames):
                abs_time, frame = f
                cv2.imwrite("out/main_" + str(idx) + ".jpg", frame)

        #calculate the absolute time for the corresponding frames
        #on the auxiliar video. Consider the time difference between videos
        times = [(abs_time - time_offset) for abs_time, frame in frames]

        #extract the motionless frames from auxiliar videos
        aux_frames = Loader.extractFramesAbsolute(a_videos, times)
        if save_frames:
            for idx, frame in enumerate(aux_frames):
                cv2.imwrite("out/auxiliar_" + str(idx) + ".jpg", frame)

        #find the visual correspondence between pairs of key frames
        matches_aux = []
        matches_main = []
        aux_boxes = []
        main_boxes = []

        all_content_main = []
        all_content_aux = []
        #...first... extract the content from each pair of frames...
        for i in range(min(Aligner.ALIGNMENT_SAMPLE, len(frames))):

            #get the current key frames
            abs_time, frame_main = frames[i]
            frame_aux = aux_frames[i]

            print("Extracting content #" + str(i + 1) + " ... (Main: " +
                  TimeHelper.stampToStr(abs_time) + " - Aux: " +
                  TimeHelper.stampToStr(times[i]) + ")")

            #from the main key frame, extract content on the board
            main_box, content_main = Binarizer.frameContentBinarization(
                frame_main, extraction_method_id)
            main_boxes.append(main_box)

            #from the auxiliary key frame, extract content on the board
            aux_box, content_aux = Binarizer.frameContentBinarization(
                frame_aux, extraction_method_id)
            aux_boxes.append(aux_box)

            #add to list...
            all_content_main.append(content_main)
            all_content_aux.append(content_aux)

        #...then, extract the alignment.... keep highest score...
        all_scores = []
        for i in range(min(Aligner.ALIGNMENT_SAMPLE, len(frames))):
            print("Testing Alignment #" + str(i + 1) + " ... ")

            #corresponding frames....
            content_aux = all_content_aux[i]
            content_main = all_content_main[i]

            #Extract a set of good matches between these two images....
            # where object = aux content from mimio, to align with main content
            #       scene = main content to which the change regions will be projected
            aux_list, main_list = VisualAlignment.getSURFMatchingPoints(
                content_aux, content_main, Aligner.SURF_THRESHOLD)

            #generate projection based on these points...
            current_projection, mask = VisualAlignment.generateProjection(
                aux_list, main_list)
            #calculate score...
            score = VisualAlignment.getProjectionScore(current_projection,
                                                       all_content_main,
                                                       all_content_aux)

            #print( str(i) + " => " + str(score) )
            all_scores.append((score, i, current_projection))

            #add to the total list of points...
            matches_aux.append(aux_list)
            matches_main.append(main_list)

            #print( "ON " + str(i) + " where found " +  str(len(aux_list) ) + " matches" )

        all_scores = sorted(all_scores, reverse=True)

        #current best projection is the one with the top score...
        max_score = all_scores[0][0]
        all_matches_aux = matches_aux[all_scores[0][1]]
        all_matches_main = matches_main[all_scores[0][1]]
        best_projection = all_scores[0][2]

        #now, try to improve the quality of the projection by adding some keypoints from
        #candidate alignments with high scores and computing a new combined projection
        #for the list of combined keypoint matches...
        new_score = max_score
        pos = 1
        while new_score >= max_score and pos < len(all_scores):
            #add keypoints to the combined list...
            current_aux = all_matches_aux + matches_aux[all_scores[pos][1]]
            current_main = all_matches_main + matches_main[all_scores[pos][1]]

            #generate the new projection...
            current_projection, mask = VisualAlignment.generateProjection(
                current_aux, current_main)

            #get score for combined projection...
            new_score = VisualAlignment.getProjectionScore(
                current_projection, all_content_main, all_content_aux)

            #check if score improved...
            if new_score >= max_score:
                #new best projection found....
                max_score = new_score
                all_matches_aux += aux_list[all_scores[pos][1]]
                all_matches_main += main_list[all_scores[pos][1]]

                best_projection = current_projection
                pos += 1

        #Get the final alignment
        projection = best_projection

        print("Best Alignment Score: " + str(max_score))
        """
        # Un-comment to output alignment images
        for i in range(len(all_content_main)):
            content_main = all_content_main[i]
            content_aux = all_content_aux[i]

            proj_img = np.zeros( (content_main.shape[0], content_main.shape[1]), dtype=content_main.dtype )
            cv.WarpPerspective( cv.fromarray( content_aux ), cv.fromarray(proj_img), cv.fromarray( projection ) )

            result_image = np.zeros( (content_main.shape[0], content_main.shape[1], 3) )
            result_image[:,:,2] = content_main
            result_image[:,:,1] = proj_img

            #cv2.imshow('img',result_image)
            cv2.imwrite( 'DEBUG_MAIN_' + str(i) + '.bmp', content_main )
            cv2.imwrite( 'DEBUG_AUX_' + str(i) + '.bmp', content_aux )
            cv2.imwrite( 'DEBUG_PROJECTION_' + str(i) + '.bmp' , result_image )
        """

        #average of the boxes of the whiteboard
        main_box = MiscHelper.averageBoxes(main_boxes)
        aux_box = MiscHelper.averageBoxes(aux_boxes)

        #store them in a single object...
        visual_alignment = VisualAlignment()
        # ... main size...
        visual_alignment.main_width = frames[0][1].shape[1]
        visual_alignment.main_height = frames[0][1].shape[0]
        #.... main board box ...
        visual_alignment.main_box = main_box
        # ... aux size ....
        visual_alignment.aux_width = aux_frames[0].shape[1]
        visual_alignment.aux_height = aux_frames[0].shape[0]
        #... aux board box...
        visual_alignment.aux_box = aux_box
        #... projection ....
        visual_alignment.projection = projection

        return visual_alignment
 def __repr__(self):
     lect_str = self.database + " - " + self.lecture
     loc_str = str(self.idx) + " at " + TimeHelper.stampToStr(self.time)
     return "{Keyframe: [" + lect_str + "], [" + loc_str + "]}\n"
    def doProcessing(self,
                     video_worker,
                     limit=0,
                     verbose=False,
                     force_no_seek=False):
        #initially....
        width = None
        height = None

        offset_frame = -1
        absolute_frame = 0
        absolute_time = 0.0

        if verbose:
            print("Video processing for " + video_worker.getWorkName() +
                  " has begun")

        #for timer...
        timer = TimeHelper()
        timer.startTimer()

        #open video...
        for video_idx, video_file in enumerate(self.file_list):
            try:
                capture = cv2.VideoCapture(video_file)
            except Exception as e:
                # error loading
                raise Exception("The file <" + video_file +
                                "> could not be opened")

            capture_width = int(capture.get(cv2.CAP_PROP_FRAME_WIDTH))
            capture_height = int(capture.get(cv2.CAP_PROP_FRAME_HEIGHT))

            # ...check size ...
            forced_resizing = False
            if width is None:
                # first video....
                # initialize local parameters....
                if self.forced_width is not None:
                    # ...size...
                    width = self.forced_width
                    height = self.forced_height

                    if capture_width != self.forced_width or capture_height != self.forced_height:
                        forced_resizing = True
                else:
                    width = capture_width
                    height = capture_height

                # on the worker class...
                video_worker.initialize(width, height)
            else:
                if self.forced_width is not None:
                    forced_resizing = (capture_width != self.forced_width
                                       or capture_height != self.forced_height)
                else:
                    if (width != capture_width) or (height != capture_height):
                        # invalid, all main video files must be the same resolution...
                        raise Exception(
                            "All video files on the list must have the same resolution"
                        )

            # get current FPS
            video_fps = capture.get(cv2.CAP_PROP_FPS)
            # will use some frames per second
            jump_frames = int(video_fps / self.frames_per_second)

            # Read video until the end or until limit has been reached
            selection_step = 4 if force_no_seek else 1
            timer_1 = TimeHelper()
            timer_2 = TimeHelper()

            while limit == 0 or offset_frame < limit:

                if selection_step == 2 or selection_step == 5:
                    # jump to frame in single step
                    timer_2.startTimer()

                    target_frame = capture.get(
                        cv2.CAP_PROP_POS_FRAMES) + jump_frames - 1
                    valid_grab = capture.set(cv2.CAP_PROP_POS_FRAMES,
                                             target_frame)

                    timer_2.endTimer()

                    if selection_step == 2:
                        selection_step = 3

                if selection_step == 1 or selection_step == 4:
                    timer_1.startTimer()

                    # jump to frame by grabbing frames ...
                    valid_grab = True
                    for x in range(jump_frames - 1):
                        valid_grab = capture.grab()
                        if not valid_grab:
                            break

                    timer_1.endTimer()

                    if selection_step == 1:
                        selection_step = 2

                if selection_step == 3:
                    # decide which sampling grabbing method is faster
                    if timer_1.totalElapsedTime() < timer_2.totalElapsedTime():
                        print("Grabbing frames to jump")
                        selection_step = 4
                    else:
                        print("Jumping to frames directly")
                        selection_step = 5

                # get frame..
                if valid_grab:
                    flag, frame = capture.read()
                else:
                    flag, frame = False, None

                #print("Grab time: " + str(capture.get(cv2.CAP_PROP_POS_FRAMES)))
                #print((valid_grab, flag, type(frame), selection_step, jump_frames))
                if not flag:
                    # end of video reached...
                    break
                else:
                    offset_frame += 1
                    current_time = capture.get(cv2.CAP_PROP_POS_MSEC)
                    current_frame = capture.get(cv2.CAP_PROP_POS_FRAMES)

                if forced_resizing:
                    frame = cv2.resize(frame,
                                       (self.forced_width, self.forced_height))

                if offset_frame > 0:
                    frame_time = absolute_time + current_time
                    frame_idx = int(absolute_frame + current_frame)
                    video_worker.handleFrame(frame, last_frame, video_idx,
                                             frame_time, current_time,
                                             frame_idx)

                    if verbose and offset_frame % 50 == 0:
                        print( "Frames Processed = " + str(offset_frame) + \
                               ", Video Time = " + TimeHelper.stampToStr( frame_time ) )

                last_frame = frame
                last_time = current_time

            #at the end of the processing of current video
            capture.set(cv2.CAP_PROP_POS_AVI_RATIO, 1.0)
            video_length = capture.get(cv2.CAP_PROP_POS_MSEC)
            video_frames = capture.get(cv2.CAP_PROP_POS_FRAMES)

            absolute_time += video_length
            absolute_frame += video_frames

        #processing finished...
        video_worker.finalize()

        #end time counter...
        timer.endTimer()

        if verbose:
            print("Video processing for " + video_worker.getWorkName() +
                  " completed: " +
                  TimeHelper.stampToStr(timer.lastElapsedTime() * 1000.0))
Example #9
0
    def extractRegions(main_videos, speaker_detector, change_detector,
                       alignment, time_offset):
        #sort the input regions
        #by last time modified...
        regions = change_detector.getAllRegions()
        sorted_regions = [(r.last_modified, idx, r)
                          for idx, r in enumerate(regions)]
        sorted_regions = sorted(sorted_regions)

        open_capture = None
        open_index = None

        extracted_parts = {}
        extracted_boxes = {}

        w_cells = change_detector.getWidthCells()
        h_cells = change_detector.getHeightCells()

        motions = speaker_detector.getMotionDetected()

        #now extract them from the videos
        for time_modified, idx, r in sorted_regions:
            #for the region r, check one frame between
            #last time modified and time locked where
            #it is assumed to be not obstructed...
            real_time_modified = time_modified + time_offset

            init_index = speaker_detector.findMotionByTime(real_time_modified)
            if init_index == len(motions):
                init_index -= 1

            candidates = []

            #find the corresponding region of interest in original video...
            #...first... from cells to rectangle in original frame....
            cx_min = (r.min_x / float(w_cells)) * alignment.aux_width
            cx_max = ((r.max_x + 1) / float(w_cells)) * alignment.aux_width
            cy_min = (r.min_y / float(h_cells)) * alignment.aux_height
            cy_max = ((r.max_y + 1) / float(h_cells)) * alignment.aux_height
            #...then... use scaling information to obtain corresponding
            #... rectangle in main video....
            main_region = alignment.alignRegion(cx_min, cx_max, cy_min, cy_max)

            #<THE MASK>
            """
            #generate the mask
            mask = r.getMask(h_cells, w_cells)

            #resize...
            new_size = (int(mask.shape[1] * ChangeDetector.CELL_SIZE), int(mask.shape[0] * ChangeDetector.CELL_SIZE) )
            mask = cv2.resize(mask, new_size)

            #project the mask
            proj_mask = np.zeros( (alignment.main_height, alignment.main_width) , dtype='uint8' )
            cv.WarpPerspective( cv.fromarray( mask ), cv.fromarray(proj_mask), cv.fromarray( alignment.projection ) )

            #dilate the mask
            #....create structuring element...
            expand_cells = 4
            strel = cv2.getStructuringElement(cv2.MORPH_RECT, (int(ChangeDetector.CELL_SIZE * expand_cells), int(ChangeDetector.CELL_SIZE * expand_cells)))

            #....now dilate mask...
            final_mask = cv2.dilate(proj_mask, strel)
            """
            #</THE MASK>

            #now, find a frame where the found region has no motion around
            intervals = speaker_detector.getNonblockedIntervals(
                main_region, alignment.main_width, alignment.main_height,
                init_index, r.locked_time + time_offset)

            #check....
            if len(intervals) == 0:
                #cannot be extracted on lock time...
                #now do a second attempt to extract it based on erasing time...
                if r.lock_type == ChangeRegion.COMPLETE_ON_OVERWRITE and \
                   r.erased_time != None:
                    intervals = speaker_detector.getNonblockedIntervals(
                        main_region, alignment.main_width,
                        alignment.main_height, init_index,
                        r.erased_time + time_offset)
            #check....
            if len(intervals) == 0:
                #it simply could not be extracted....
                extracted_parts[r.number_id] = None
                extracted_boxes[r.number_id] = None
                print( "CHECK = "  + str(r.number_id ) + ", from " + \
                       TimeHelper.stampToStr(r.creation_time + time_offset) + " - " + \
                       TimeHelper.stampToStr(motions[init_index].absolute_time) + " to " + \
                       TimeHelper.stampToStr(r.locked_time + time_offset) )
            else:

                #find the best interval....
                best_interval = 0
                best_length = (intervals[0][1] - intervals[0][0] + 1)
                for i in range(1, len(intervals)):
                    length = (intervals[i][1] - intervals[i][0] + 1)
                    if length > best_length:
                        best_length = length
                        best_interval = i

                #now, from best interval pick frame in the middle..
                best_frame = int(init_index +
                                 ((intervals[best_interval][0] +
                                   intervals[best_interval][1]) / 2.0))

                #finally do extraction
                #... open video ...
                if open_index == None or open_index != motions[
                        best_frame].video_index:
                    #close current video
                    if open_index != None:
                        open_capture.release()

                    open_index = motions[best_frame].video_index
                    open_capture = cv2.VideoCapture(main_videos[open_index])

                #... set time position ...
                open_capture.set(cv.CV_CAP_PROP_POS_MSEC,
                                 motions[best_frame].time)
                #... get frame ...
                flag, frame = open_capture.read()

                if not flag:
                    #error?
                    extracted_parts[r.number_id] = None
                    extracted_boxes[r.number_id] = None
                    print("Region <" + str(r.number_id) +
                          "> Could not be extracted from video")
                else:
                    #extract the region ...
                    margin = 5
                    min_x = int(max(0, math.floor(main_region[0] - margin)))
                    max_x = int(
                        min(alignment.main_width - 1,
                            math.ceil(main_region[1] + margin)))
                    min_y = int(max(0, math.floor(main_region[2] - margin)))
                    max_y = int(
                        min(alignment.main_height - 1,
                            math.ceil(main_region[3] + margin)))

                    #get the part of the image...
                    part = frame[min_y:max_y, min_x:max_x, :]
                    #<THE MASK>
                    """
                    part_mask = final_mask[min_y:max_y, min_x:max_x]

                    size_mask = int((part_mask.shape[0] * part_mask.shape[1]) - np.count_nonzero(part_mask))

                    if size_mask > 0:
                        original_part = part.copy()

                        blured_part = cv2.GaussianBlur(original_part, (41, 41), 4.0)

                        part[part_mask == 0, :] = blured_part[part_mask == 0, :]

                    """
                    #</THE MASK>

                    extracted_parts[r.number_id] = part
                    extracted_boxes[r.number_id] = (min_x, max_x, min_y, max_y)

                    print("Region <" + str(r.number_id) +
                          "> extracted succesfully!")

        return extracted_parts, extracted_boxes
    def doProcessing(self, video_worker, limit=0, verbose=False):
        # initially....
        width = None
        height = None

        offset_frame = -1
        absolute_frame = 0
        absolute_time = 0.0

        if verbose:
            print("Video processing for " + video_worker.getWorkName() +
                  " has begun")

        # for timer...
        timer = TimeHelper()
        timer.startTimer()

        # open video...
        try:
            print(self.src_dir)
            capture = ImageListGenerator(
                '{}/{}'.format(self.src_dir, 'JPEGImages'), self.img_extension)
        except Exception as e:
            # error loading
            print(e)
            raise Exception(
                "The directory <" + self.src_dir +
                "> is not in the correct export format, check index.json")

        last_frame = None
        capture_width = capture.width
        capture_height = capture.height
        # print(capture_width, capture_height)

        # ...check size ...
        forced_resizing = False
        if width is None:
            # first video....
            # initialize local parameters....
            if self.forced_width is not None:
                # ...size...
                width = self.forced_width
                height = self.forced_height

                if capture_width != self.forced_width or capture_height != self.forced_height:
                    forced_resizing = True
            else:
                width = capture_width
                height = capture_height

            # on the worker class...
            video_worker.initialize(width, height)
        else:
            if self.forced_width is not None:
                forced_resizing = (capture_width != self.forced_width
                                   or capture_height != self.forced_height)
            else:
                if (width != capture_width) or (height != capture_height):
                    # invalid, all main video files must be the same resolution...
                    raise Exception(
                        "All files on the list must have the same resolution")

        while limit == 0 or offset_frame < limit:
            # get frame..
            flag, frame = capture.read()
            if not flag:
                # end of video reached...
                print('end of video reached...')
                break
            else:
                offset_frame += 1
                current_time = capture.get('abs_time')
                current_frame = capture.index2frameID()
            if forced_resizing:
                frame = cv2.resize(frame,
                                   (self.forced_width, self.forced_height))

            if offset_frame >= 0:
                frame_time = absolute_time + current_time
                frame_idx = int(absolute_frame + current_frame)
                video_worker.handleFrame(frame, last_frame, 0, frame_time,
                                         current_time, frame_idx)

                if verbose and offset_frame % 50 == 0:
                    print("Frames Processed = " + str(offset_frame) +
                          ", Video Time = " +
                          TimeHelper.stampToStr(frame_time))

            last_frame = frame
            last_time = current_time

        # processing finished...
        video_worker.finalize()

        # end time counter...
        timer.endTimer()

        if verbose:
            print("Video processing for " + video_worker.getWorkName() +
                  " completed: " +
                  TimeHelper.stampToStr(timer.lastElapsedTime() * 1000.0))
Example #11
0
def main():
    if len(sys.argv) < 2:
        print("Usage:")
        print("\tpython train_ml_binarizer.py config [force_update] [classifier_file] [patch_size]")
        print("")
        print("Where")
        print("\tconfig\t\t\tPath to config file")
        print("\tforce_update \t\tOptional, force to update the sampled Patch file")
        print("\tclassifier_file \tOptional, force classifier path diff. from Config")
        print("\tpatch_size \t\tOptional, override patch size")
        return

    # read the configuration file ....
    config = Configuration.from_file(sys.argv[1])

    # load the database
    try:
        database = MetaDataDB.from_file(config.get_str("VIDEO_DATABASE_PATH"))
    except:
        print("Invalid database file")
        return

    # <Parameters>
    run_crossvalidation = config.get("ML_BINARIZER_TRAIN_RUN_CROSSVALIDATION", True)

    if not config.contains("ML_BINARIZER_PATCHES_FILENAME"):
        print("Must specificy a file to store sampled patches")
        return

    output_dir = config.get_str("OUTPUT_PATH")
    ml_binarizer_dir = output_dir + "/" + config.get_str("ML_BINARIZER_DIR")
    patch_filename = ml_binarizer_dir + "/" + config.get_str("ML_BINARIZER_PATCHES_FILENAME")

    # For debugging/comparsion, use OTSU binarization
    OTSU_mode = config.get("ML_BINARIZER_TRAIN_OTSU_MODE", False)

    # baseline_mode = True # Train Random Forest instead .
    retrain_classifier = config.get("ML_BINARIZER_TRAIN_RETRAIN", True)

    if not config.get("ML_BINARIZER_OVERRIDE_PARAMETERS", False):
        # Sampling mode #1: Distribution of proportions
        #
        # of 100% pixels, we sample fg_proportion from GT Foreground pixels (handwriting pixels)
        #    handwriting pixels = fg_proportion
        #    all background     = (1 - fg_proportion)
        #
        # The remaining background pixels are sampled as close or far from foreground
        #    Close to Foreground pixels = (1 - fg_proportion) * bg_close_prop
        #    Remaining background pixels = (1 - fg_proportion) * (1 - bg_close_prop)
        #
        # The last proportion of pixels can be obtained from whiteboard or background objects, we separate them as
        #    Not Close Whiteboard background pixels = (1 - fg_proportion) * (1 - bg_close_prop) * bg_board_prop
        #    Not Whiteboard background pixels       = (1 - fg_proportion) * (1 - bg_close_prop) * (1 - bg_board_prop)
        #
        # Sampling mode #2: Distribution of proportions
        #
        # of 100% pixels, we sample fg_proportion from GT Foreground pixels (handwriting pixels)
        #    handwriting pixels = fg_proportion
        #    all background     = (1 - fg_proportion)
        #
        # The remaining background pixels by average intensity of the window, the greater the average, the more likely they
        #   are to be sampled. This is consistent with sampling mode 1, but is less discrete and requires less parameters
        sampling_mode = Parameters.MLBin_sampling_mode

        patch_size = Parameters.MLBin_patch_size
        patches_per_frame = Parameters.MLBin_sampling_patches_per_frame
        fg_proportion = Parameters.MLBin_sampling_fg_proportion
        bg_close_prop = Parameters.MLBin_sampling_bg_close_prop
        bg_board_prop = Parameters.MLBin_sampling_bg_board_prop

        mlbin_sigma_color = Parameters.MLBin_sigma_color
        mlbin_sigma_space = Parameters.MLBin_sigma_space
        mlbin_median_blur_k = Parameters.MLBin_median_blur_k
        mlbin_dark_background = Parameters.MLBin_dark_background

        feature_workers = Parameters.MLBin_train_workers

        # Random Forest
        rf_n_trees = Parameters.MLBin_rf_n_trees           # 16
        rf_max_depth = Parameters.MLBin_rf_max_depth       # 12
        rf_max_features = Parameters.MLBin_rf_max_features # 32

    else:
        print("Reading ML Binarizer parameters from config ...")

        sampling_mode = config.get_int("ML_BINARIZER_SAMPLING_MODE", 2)

        patch_size = config.get_int("ML_BINARIZER_PATCH_SIZE", 7)
        patches_per_frame = config.get_int("ML_BINARIZER_SAMPLING_PATCHES_PER_FRAME", 20000)
        fg_proportion = config.get_float("ML_BINARIZER_SAMPLING_FG_PROPORTION", 0.5)
        bg_close_prop = config.get_float("ML_BINARIZER_SAMPLING_BG_CLOSE_PROPORTION", 0.9)
        bg_board_prop = config.get_float("ML_BINARIZER_SAMPLING_BG_BOARD_PROPORTION", 1.0)

        mlbin_sigma_color = config.get_float("ML_BINARIZER_SIGMA_COLOR", 13.5)
        mlbin_sigma_space = config.get_float("ML_BINARIZER_SIGMA_SPACE", 4.0)
        mlbin_median_blur_k = config.get_int("ML_BINARIZER_MEDIAN_BLUR_K", 33)
        mlbin_dark_background = config.get("ML_BINARIZER_DARK_BACKGROUND")

        feature_workers = config.get_int("ML_BINARIZER_TRAIN_WORKERS", 7)

        # Random Forest
        rf_n_trees = config.get_int("ML_BINARIZER_RF_N_TREES", 16)  # 16
        rf_max_depth = config.get_int("ML_BINARIZER_RF_MAX_DEPTH", 12)  # 12
        rf_max_features = config.get_int("ML_BINARIZER_RF_MAX_FEATURES", 32) # 32


    if len(sys.argv) >= 4:
        # user specified location
        classifier_file = sys.argv[3]
    else:
        # by default, store at the place specified in the configuration or parameters file ...
        if not config.get("ML_BINARIZER_OVERRIDE_PARAMETERS", False):
            classifier_file = Parameters.MLBin_classifier_file
        else:
            classifier_file = ml_binarizer_dir + "/" + config.get_str("ML_BINARIZER_CLASSIFIER_FILENAME")

    feature_function = get_patch_features_raw_values

    # </Parameters>

    if len(sys.argv) >= 3:
        try:
            force_update = int(sys.argv[2]) > 0
        except:
            print("Invalid value for force_udpate")
            return
    else:
        force_update = False

    if len(sys.argv) >= 5:
        try:
            patch_size = int(sys.argv[4])
        except:
            print("Invalid value for patch_size")
            return

    assert (patch_size - 1) % 2 == 0
    bg_close_neighborhood = int((patch_size - 1) / 2) + 1
    
    print("Classifier Path: " + classifier_file)
    ml_binarizer = MLBinarizer(None, patch_size, mlbin_sigma_color, mlbin_sigma_space, mlbin_median_blur_k,
                               mlbin_dark_background)

    print("... loading data ...")
    start_loading = time.time()
    all_keyframes, binarized_keyframes = load_keyframes(output_dir, database)
    fake_unique_groups, fake_cc_group, fake_segments = generate_fake_keyframe_info(all_keyframes)

    print("Total Training keyframes: " + str(len(all_keyframes)))

    end_loading = time.time()
    start_preprocessing = time.time()

    print("Pre-processing key-frames", flush=True)
    all_preprocessed = []
    for kf_idx, kf in enumerate(all_keyframes):
        all_preprocessed.append(ml_binarizer.preprocessing(kf.raw_image))
        # cv2.imwrite("DELETE_NOW_tempo_bin_input_" + str(kf_idx) + ".png", all_preprocessed[-1])

    end_preprocessing = time.time()
    start_patch_extraction = time.time()

    # Extracting/Loading patches used for training (only if not on OTSU's mode)
    if not OTSU_mode:
        # generate the patch-based training set ...
        # check if patch file exists ...
        if not os.path.exists(patch_filename) or force_update:
            print("Extracting patches...")

            if sampling_mode == 1:
                # SampleEdgeFixBg()
                patches = PatchSampling.SampleEdgeFixBg(all_keyframes, all_preprocessed, patch_size, patches_per_frame,
                                                        fg_proportion, bg_close_prop, bg_board_prop, bg_close_neighborhood)
            elif sampling_mode == 2:
                # SampleEdgeContBg
                patches = PatchSampling.SampleEdgeContBg(all_keyframes, all_preprocessed, patch_size, patches_per_frame,
                                                         fg_proportion)
            else:
                patches = (None, None)

            patches_images, patches_labels = patches

            # generate features
            print("\nGenerating features ...", flush=True)
            all_features = []
            with ProcessPoolExecutor(max_workers=feature_workers) as executor:
                for lect_idx, lecture_images in enumerate(patches_images):
                    print("Processing patches from lecture {0:d} out of {1:d}".format(lect_idx + 1, len(patches_images)))
                    lecture_features = []
                    for i, patch_features in enumerate(executor.map(feature_function, lecture_images)):
                        lecture_features.append(patch_features)

                    all_features.append(lecture_features)

            print("\nSaving patches and features to file")
            out_file = open(patch_filename, "wb")
            pickle.dump(patches_labels, out_file, pickle.HIGHEST_PROTOCOL)
            pickle.dump(patches_images, out_file, pickle.HIGHEST_PROTOCOL)
            pickle.dump(all_features, out_file, pickle.HIGHEST_PROTOCOL)
            out_file.close()
        else:
            # load patches from file ....
            print("Loading patches and features from file")
            in_file = open(patch_filename, "rb")
            patches_labels = pickle.load(in_file)
            patches_images = pickle.load(in_file)
            all_features = pickle.load(in_file)
            in_file.close()

    end_patch_extraction = time.time()
    
    total_training_time = 0.0
    total_binarization_time = 0.0
    total_evaluation_time = 0.0

    cross_validated_classifiers = []
    
    if not OTSU_mode:
        start_training = time.time()

        # train classifier using training patches ...
        count_all_patches = sum([len(lecture_images) for lecture_images in patches_images])
        print("Total patches available for training: " + str(count_all_patches))

        n_features = len(all_features[0][0])
        print("Total Features: " + str(n_features))

        # check local performance using cross-validation based on leaving one lecture out
        conf_matrix = np.zeros((2, 2), dtype=np.int32)
        avg_train_accuracy = 0.0
        rf_max_features = min(rf_max_features, n_features)

        if run_crossvalidation:
            for i in range(len(patches_images)):
                print("Cross-validation fold #" + str(i + 1))

                training_data = []
                training_labels = []
                testing_data = []
                testing_labels = []
                for k in range(len(patches_images)):
                    if i == k:
                        testing_data += all_features[k]
                        testing_labels += patches_labels[k]
                    else:
                        training_data += all_features[k]
                        training_labels += patches_labels[k]

                training_data = np.array(training_data)
                testing_data = np.array(testing_data)

                print("-> Training Samples: " + str(training_data.shape[0]))
                print("-> Testing Samples: " + str(testing_data.shape[0]))

                # classification mode ...
                # random forest ...
                classifier = RandomForestClassifier(rf_n_trees, max_features=rf_max_features, max_depth=rf_max_depth, n_jobs=-1)
                classifier.fit(training_data, training_labels)
                
                # keep reference to the n-th fold classifier
                cross_validated_classifiers.append(classifier)

                pred_labels = classifier.predict(training_data)
                train_conf_matrix = np.zeros((2, 2), dtype=np.int32)
                for train_idx in range(len(training_labels)):
                    train_conf_matrix[training_labels[train_idx], pred_labels[train_idx]] += 1
                pixel_accuracy = (train_conf_matrix[0, 0] + train_conf_matrix[1, 1]) / len(training_labels)
                print("-> Train pixel accuracy: " + str(pixel_accuracy * 100.0))
                avg_train_accuracy += pixel_accuracy

                pred_labels = classifier.predict(testing_data)

                for test_idx in range(len(testing_labels)):
                    conf_matrix[testing_labels[test_idx], pred_labels[test_idx]] += 1


            pixel_accuracy = (conf_matrix[0, 0] + conf_matrix[1, 1]) / count_all_patches
            avg_train_accuracy /= len(all_features)

            print("Combined testing confusion matrix: ")
            print(conf_matrix)
            print("Final training pixel accuracy: " + str(avg_train_accuracy * 100.0))
            print("Final testing pixel accuracy: " + str(pixel_accuracy * 100.0))

        # now, use all data to train a classifier for binarization of all frames ...
        if not os.path.exists(classifier_file) or force_update or retrain_classifier:
            print("Training classifier using all patches", flush=True)
            # classification
            training_data = []
            training_labels = []
            for k in range(len(patches_images)):
                training_data += all_features[k]
                training_labels += patches_labels[k]

            training_data = np.array(training_data)

            # Train Random Forest
            classifier = RandomForestClassifier(rf_n_trees, max_features=rf_max_features, max_depth=rf_max_depth, n_jobs=-1)
            classifier.fit(training_data, training_labels)
            
            print("Saving classifier to file")
            out_file = open(classifier_file, "wb")
            pickle.dump(classifier, out_file, pickle.HIGHEST_PROTOCOL)
            out_file.close()
        else:
            print("Loading classifier from file")
            in_file = open(classifier_file, "rb")
            classifier = pickle.load(in_file)
            in_file.close()

        # release memory (a lot) of elements that will not be used after this point ...
        all_features = None
        patches_labels = None
        training_data = None
        training_labels = None
        testing_data = None
        testing_labels = None

        end_training = time.time()
        total_training_time += end_training - start_training
    
    # binarize using parameter combination...
    start_binarizing = time.time()

    last_lecture = None
    lecture_offset = -1
    training_set = database.get_dataset("training")
    
    for idx, bin_kf in enumerate(binarized_keyframes):
        if bin_kf.lecture != last_lecture:
            last_lecture = bin_kf.lecture
            lecture_offset += 1

        print("binarizing kf #" + str(idx) + ", from " + training_set[lecture_offset].title, end="\r", flush=True)

        if OTSU_mode:
            # ideal BG removal ...
            #strel = cv2.getStructuringElement(cv2.MORPH_RECT, (int(patch_size), int(patch_size)))
            #bg_mask = all_keyframes[idx].object_mask > 0
            #all_preprocessed[idx][bg_mask] = 0

            otsu_t, bin_res = cv2.threshold(all_preprocessed[idx].astype(np.uint8), 0, 255, cv2.THRESH_BINARY + cv2.THRESH_OTSU)
            
            bin_kf.binary_image = np.zeros((bin_res.shape[0], bin_res.shape[1], 3), dtype=np.uint8)
            bin_kf.binary_image[:, :, 0] = 255 - bin_res.copy()
            bin_kf.binary_image[:, :, 1] = bin_kf.binary_image[:, :, 0].copy()
            bin_kf.binary_image[:, :, 2] = bin_kf.binary_image[:, :, 0].copy()
        else:
            # set classifier for binarization ....
            if run_crossvalidation:
                # use the classifier that has not seen this image ...
                ml_binarizer.classifier = cross_validated_classifiers[lecture_offset]
            else:
                # use the globally train classifier
                ml_binarizer.classifier = classifier

            # ... binarize the pre-processed image ... 
            binary_image = ml_binarizer.preprocessed_binarize(all_preprocessed[idx])
            
            # Do hystheresis filtering ...
            otsu_t, high_bin = cv2.threshold(all_preprocessed[idx].astype(np.uint8), 0, 255, cv2.THRESH_BINARY + cv2.THRESH_OTSU)
            low_bin = binary_image
            
            filtered_bin = 255 - MLBinarizer.binary_hysteresis(low_bin, high_bin)
            
            bin_kf.binary_image = np.zeros((filtered_bin.shape[0], filtered_bin.shape[1], 3), dtype=np.uint8)
            bin_kf.binary_image[:, :, 0] = filtered_bin
            bin_kf.binary_image[:, :, 1] = filtered_bin
            bin_kf.binary_image[:, :, 2] = filtered_bin

        bin_kf.update_binary_cc(False)

        if config.get("ML_BINARIZER_SAVE_BINARY", True):
            if OTSU_mode:
                out_name = "TEMPO_OTSU_baseline_binarized_" + str(idx) + ".png"
            else:
                out_name = "TEMPO_rf_baseline_binarized_" + str(idx) + ".png"

            cv2.imwrite(out_name, bin_kf.binary_image)
        
    end_binarizing = time.time()
    total_binarization_time += end_binarizing - start_binarizing

    # run evaluation metrics ...
    print("Computing final evaluation metrics....")
    
    # Summary level metrics ....
    start_evaluation = time.time()
    
    EvalParameters.UniqueCC_global_tran_window = 1
    EvalParameters.UniqueCC_min_precision = [0.50, 0.55, 0.60, 0.65, 0.70, 0.75, 0.80, 0.90, 0.95]
    EvalParameters.UniqueCC_min_recall = [0.50, 0.55, 0.60, 0.65, 0.70, 0.75, 0.80, 0.90, 0.95]
    EvalParameters.Report_Summary_Show_Counts = False
    EvalParameters.Report_Summary_Show_AVG_per_frame = False
    EvalParameters.Report_Summary_Show_Globals = True

    all_scope_metrics, scopes = Evaluator.compute_summary_metrics(fake_segments, all_keyframes, fake_unique_groups,
                                                            fake_cc_group, fake_segments, binarized_keyframes,
                                                            False)


    for scope in scopes:
        print("")
        print("Metrics for scope: " + scope)
        print("      \t      \tRecall\t      \t       \tPrecision")
        print("Min R.\tMin P.\tE + P\tE. Only\tP. Only\tE + P\tE. Only\tP. Only\tBG. %\tNo BG P.")
        scope_metrics = all_scope_metrics[scope]

        recall_percent_row = "{0:.2f}\t{1:.2f}\t{2:.2f}\t{3:.2f}\t{4:.2f}"
        prec_percent_row = "{0:.2f}\t{1:.2f}\t{2:.2f}\t{3:.2f}\t{4:.2f}"

        for all_metrics in scope_metrics:
            metrics = all_metrics["recall_metrics"]

            recall_str = recall_percent_row.format(all_metrics["min_cc_recall"] * 100.0,
                                                   all_metrics["min_cc_precision"] * 100.0,
                                                   metrics["recall"] * 100.0, metrics["only_exact_recall"] * 100.0,
                                                   metrics["only_partial_recall"] * 100.0)

            metrics = all_metrics["precision_metrics"]

            prec_str = prec_percent_row.format(metrics["precision"] * 100.0, metrics["only_exact_precision"] * 100.0,
                                               metrics["only_partial_precision"] * 100.0,
                                               metrics["global_bg_unmatched"] * 100.0,
                                               metrics["no_bg_precision"] * 100.0)

            print(recall_str + "\t" + prec_str)

    # pixel level metrics
    pixel_metrics = Evaluator.compute_pixel_binary_metrics(all_keyframes, binarized_keyframes)
    print("Pixel level metrics")
    for key in sorted(pixel_metrics.keys()):
        print("{0:s}\t{1:.2f}".format(key, pixel_metrics[key] *100.0))
    
    end_evaluation = time.time()
    total_evaluation_time += end_evaluation - start_evaluation
    end_everything = time.time()

    print("Total loading time: " + TimeHelper.secondsToStr(end_loading - start_loading))
    print("Total preprocessing time: " + TimeHelper.secondsToStr(end_preprocessing - start_preprocessing))
    print("Total patch extraction time: " + TimeHelper.secondsToStr(end_patch_extraction - start_patch_extraction))
    print("Total training time: " + TimeHelper.secondsToStr(total_training_time))
    print("Total binarization time: " + TimeHelper.secondsToStr(total_binarization_time))
    print("Total evaluation time: " + TimeHelper.secondsToStr(total_evaluation_time))
    print("Total Time: " + TimeHelper.secondsToStr(end_everything - start_loading))
    def __init__(self, size, db_name, lecture_title, output_path):
        Screen.__init__(self, "Unique CC Ground Truth Annotation Interface", size)

        general_background = (100, 90, 80)
        text_color = (255, 255, 255)
        button_text_color = (35, 50, 20)
        button_back_color = (228, 228, 228)
        self.elements.back_color = general_background

        self.db_name = db_name
        self.lecture_title = lecture_title

        self.output_path = output_path

        export_filename = self.output_path + "/segments.xml"
        export_image_prefix = self.output_path + "/keyframes/"
        # load including segment information
        self.keyframe_annotations, self.segments = KeyFrameAnnotation.LoadExportedKeyframes(export_filename, export_image_prefix, True)

        if len(self.keyframe_annotations) > 0:
            print("Key-frames Loaded: " + str(len(self.keyframe_annotations)))
        else:
            raise Exception("Cannot start with 0 key-frames")

        portions_filename = self.output_path + "/portions.xml"
        portions_path = self.output_path + "/portions/"
        if os.path.exists(portions_filename):
            # Saved data detected, loading
            print("Previously saved portion data detected, loading")
            KeyFrameAnnotation.LoadKeyframesPortions(portions_filename, self.keyframe_annotations, portions_path)
        else:
            raise Exception("No saved portion data detected, cannot continue")

        print("Original Key-frames: " + str(len(self.keyframe_annotations)))
        print("Segments: " + str(len(self.segments)))

        self.keyframe_annotations = KeyFrameAnnotation.CombineKeyframesPerSegment(self.keyframe_annotations, self.segments, True)
        print("Key-frames after combination per segment" + str(len(self.keyframe_annotations)))

        # other CC/group elements
        self.unique_groups = None
        self.cc_group = None
        self.colored_cache = []
        self.cc_total = 0
        for kf_idx, keyframe in enumerate(self.keyframe_annotations):
            self.cc_total += len(keyframe.binary_cc)

        unique_cc_filename = self.output_path + "/unique_ccs.xml"
        if os.path.exists(unique_cc_filename):
            # Saved data detected, loading
            print("Previously saved unique CC data detected, loading")

            self.cc_group, self.unique_groups = UniqueCCGroup.GroupsFromXML(self.keyframe_annotations, unique_cc_filename)
        else:
            # no previous data, build default index (all CCs are unique)
            self.unique_groups = []
            self.cc_group = []
            for kf_idx, keyframe in enumerate(self.keyframe_annotations):
                self.cc_group.append({})

                for cc in keyframe.binary_cc:
                    new_group = UniqueCCGroup(cc, kf_idx)
                    self.unique_groups.append(new_group)
                    self.cc_group[kf_idx][cc.strID()] = new_group

        self.update_colored_cache(0)

        self.view_mode = GTUniqueCCAnnotator.ViewModeColored
        self.edition_mode = GTUniqueCCAnnotator.ModeNavigate
        self.view_scale = 1.0
        self.selected_keyframe = 0

        self.matching_delta_x = 0
        self.matching_delta_y = 0
        self.matching_scores = None
        self.matching_min_recall = 0.99
        self.matching_min_precision = 0.99
        self.base_matching = None

        # add elements....
        container_top = 10
        container_width = 330

        button_2_width = 150
        button_2_left = int(container_width * 0.25) - button_2_width / 2
        button_2_right = int(container_width * 0.75) - button_2_width / 2

        # Navigation panel to move accross frames
        self.container_nav_buttons = ScreenContainer("container_nav_buttons", (container_width, 70), back_color=general_background)
        self.container_nav_buttons.position = (self.width - self.container_nav_buttons.width - 10, container_top)
        self.elements.append(self.container_nav_buttons)

        self.lbl_nav_keyframe = ScreenLabel("lbl_nav_keyframe", "Key-Frame: 1 / " + str(len(self.keyframe_annotations)), 21, 290, 1)
        self.lbl_nav_keyframe.position = (5, 5)
        self.lbl_nav_keyframe.set_background(general_background)
        self.lbl_nav_keyframe.set_color(text_color)
        self.container_nav_buttons.append(self.lbl_nav_keyframe)

        time_str = TimeHelper.stampToStr(self.keyframe_annotations[self.selected_keyframe].time)
        self.lbl_nav_time = ScreenLabel("lbl_nav_time", time_str, 21, 290, 1)
        self.lbl_nav_time.position = (5, self.lbl_nav_keyframe.get_bottom() + 20)
        self.lbl_nav_time.set_background(general_background)
        self.lbl_nav_time.set_color(text_color)
        self.container_nav_buttons.append(self.lbl_nav_time)

        self.btn_nav_keyframe_prev = ScreenButton("btn_nav_keyframe_prev", "Prev", 21, 90)
        self.btn_nav_keyframe_prev.set_colors(button_text_color, button_back_color)
        self.btn_nav_keyframe_prev.position = (10, self.lbl_nav_keyframe.get_bottom() + 10)
        self.btn_nav_keyframe_prev.click_callback = self.btn_nav_keyframe_prev_click
        self.container_nav_buttons.append(self.btn_nav_keyframe_prev)

        self.btn_nav_keyframe_next = ScreenButton("btn_nav_keyframe_next", "Next", 21, 90)
        self.btn_nav_keyframe_next.set_colors(button_text_color, button_back_color)
        self.btn_nav_keyframe_next.position = (self.container_nav_buttons.width - self.btn_nav_keyframe_next.width - 10,
                                               self.lbl_nav_keyframe.get_bottom() + 10)
        self.btn_nav_keyframe_next.click_callback = self.btn_nav_keyframe_next_click
        self.container_nav_buttons.append(self.btn_nav_keyframe_next)

        # confirmation panel
        self.container_confirm_buttons = ScreenContainer("container_confirm_buttons", (container_width, 70), back_color=general_background)
        self.container_confirm_buttons.position = (self.width - self.container_confirm_buttons.width - 10, container_top)
        self.elements.append(self.container_confirm_buttons)
        self.container_confirm_buttons.visible = False

        self.lbl_confirm_message = ScreenLabel("lbl_confirm_message", "Confirmation message goes here?", 21, 290, 1)
        self.lbl_confirm_message.position = (5, 5)
        self.lbl_confirm_message.set_background(general_background)
        self.lbl_confirm_message.set_color(text_color)
        self.container_confirm_buttons.append(self.lbl_confirm_message)

        self.btn_confirm_cancel = ScreenButton("btn_confirm_cancel", "Cancel", 21, 130)
        self.btn_confirm_cancel.set_colors(button_text_color, button_back_color)
        self.btn_confirm_cancel.position = (10, self.lbl_nav_keyframe.get_bottom() + 10)
        self.btn_confirm_cancel.click_callback = self.btn_confirm_cancel_click
        self.container_confirm_buttons.append(self.btn_confirm_cancel)

        self.btn_confirm_accept = ScreenButton("btn_confirm_accept", "Accept", 21, 130)
        self.btn_confirm_accept.set_colors(button_text_color, button_back_color)
        self.btn_confirm_accept.position = (self.container_confirm_buttons.width - self.btn_confirm_accept.width - 10,
                                            self.lbl_confirm_message.get_bottom() + 10)
        self.btn_confirm_accept.click_callback = self.btn_confirm_accept_click
        self.container_confirm_buttons.append(self.btn_confirm_accept)

        # View panel with view control buttons
        self.container_view_buttons = ScreenContainer("container_view_buttons", (container_width, 165), back_color=general_background)
        self.container_view_buttons.position = (self.width - self.container_view_buttons.width - 10,
                                                self.container_nav_buttons.get_bottom() + 10)
        self.elements.append(self.container_view_buttons)


        button_width = 190
        button_left = (self.container_view_buttons.width - button_width) / 2

        # zoom ....
        self.lbl_zoom = ScreenLabel("lbl_zoom", "Zoom: 100%", 21, container_width - 10, 1)
        self.lbl_zoom.position = (5, 5)
        self.lbl_zoom.set_background(general_background)
        self.lbl_zoom.set_color(text_color)
        self.container_view_buttons.append(self.lbl_zoom)

        self.btn_zoom_reduce = ScreenButton("btn_zoom_reduce", "[ - ]", 21, 90)
        self.btn_zoom_reduce.set_colors(button_text_color, button_back_color)
        self.btn_zoom_reduce.position = (10, self.lbl_zoom.get_bottom() + 10)
        self.btn_zoom_reduce.click_callback = self.btn_zoom_reduce_click
        self.container_view_buttons.append(self.btn_zoom_reduce)

        self.btn_zoom_increase = ScreenButton("btn_zoom_increase", "[ + ]", 21, 90)
        self.btn_zoom_increase.set_colors(button_text_color, button_back_color)
        self.btn_zoom_increase.position = (self.container_view_buttons.width - self.btn_zoom_increase.width - 10,
                                           self.lbl_zoom.get_bottom() + 10)
        self.btn_zoom_increase.click_callback = self.btn_zoom_increase_click
        self.container_view_buttons.append(self.btn_zoom_increase)

        self.btn_zoom_zero = ScreenButton("btn_zoom_zero", "100%", 21, 90)
        self.btn_zoom_zero.set_colors(button_text_color, button_back_color)
        self.btn_zoom_zero.position = ((self.container_view_buttons.width - self.btn_zoom_zero.width) / 2,
                                       self.lbl_zoom.get_bottom() + 10)
        self.btn_zoom_zero.click_callback = self.btn_zoom_zero_click
        self.container_view_buttons.append(self.btn_zoom_zero)

        self.btn_view_raw = ScreenButton("btn_view_raw", "Raw View", 21, button_2_width)
        self.btn_view_raw.set_colors(button_text_color, button_back_color)
        self.btn_view_raw.position = (button_2_left, self.btn_zoom_zero.get_bottom() + 10)
        self.btn_view_raw.click_callback = self.btn_view_raw_click
        self.container_view_buttons.append(self.btn_view_raw)

        self.btn_view_gray = ScreenButton("btn_view_gray", "Grayscale View", 21, button_2_width)
        self.btn_view_gray.set_colors(button_text_color, button_back_color)
        self.btn_view_gray.position = (button_2_right, self.btn_zoom_zero.get_bottom() + 10)
        self.btn_view_gray.click_callback = self.btn_view_gray_click
        self.container_view_buttons.append(self.btn_view_gray)

        self.btn_view_binary = ScreenButton("btn_view_binary", "Binary View", 21, button_2_width)
        self.btn_view_binary.set_colors(button_text_color, button_back_color)
        self.btn_view_binary.position = (button_2_left, self.btn_view_gray.get_bottom() + 10)
        self.btn_view_binary.click_callback = self.btn_view_binary_click
        self.container_view_buttons.append(self.btn_view_binary)

        self.btn_view_colored = ScreenButton("btn_view_colored", "Colored View", 21, button_2_width)
        self.btn_view_colored.set_colors(button_text_color, button_back_color)
        self.btn_view_colored.position = (button_2_right, self.btn_view_gray.get_bottom() + 10)
        self.btn_view_colored.click_callback = self.btn_view_colored_click
        self.container_view_buttons.append(self.btn_view_colored)

        # Panel with action buttons (Add/Remove links)
        self.container_action_buttons = ScreenContainer("container_action_buttons", (container_width, 45),
                                                        general_background)
        self.container_action_buttons.position = (self.container_view_buttons.get_left(), self.container_view_buttons.get_bottom() + 5)
        self.elements.append(self.container_action_buttons)

        self.btn_matches_add = ScreenButton("btn_matches_add", "Add Matches", 21, button_2_width)
        self.btn_matches_add.set_colors(button_text_color, button_back_color)
        self.btn_matches_add.position = (button_2_left, 5)
        self.btn_matches_add.click_callback = self.btn_matches_add_click
        self.container_action_buttons.append(self.btn_matches_add)

        self.btn_matches_del = ScreenButton("btn_matches_del", "Del. Matches", 21, button_2_width)
        self.btn_matches_del.set_colors(button_text_color, button_back_color)
        self.btn_matches_del.position = (button_2_right, 5)
        self.btn_matches_del.click_callback = self.btn_matches_del_click
        self.container_action_buttons.append(self.btn_matches_del)

        # ===============================================
        # Panel with matching parameters for step 1 (Matching Translation)
        self.container_matching_translation = ScreenContainer("container_matching_translation", (container_width, 150), general_background)
        self.container_matching_translation.position = (self.container_view_buttons.get_left(), self.container_view_buttons.get_bottom() + 5)
        self.elements.append(self.container_matching_translation)

        self.lbl_translation_title = ScreenLabel("lbl_translation_title", "Translation Parameters", 21, container_width - 10, 1)
        self.lbl_translation_title.position = (5, 5)
        self.lbl_translation_title.set_background(general_background)
        self.lbl_translation_title.set_color(text_color)
        self.container_matching_translation.append(self.lbl_translation_title)

        self.lbl_delta_x = ScreenLabel("lbl_delta_x", "Delta X: " + str(self.matching_delta_x), 21, container_width - 10, 1)
        self.lbl_delta_x.position = (5, self.lbl_translation_title.get_bottom() + 20)
        self.lbl_delta_x.set_background(general_background)
        self.lbl_delta_x.set_color(text_color)
        self.container_matching_translation.append(self.lbl_delta_x)

        max_delta = GTUniqueCCAnnotator.ParamsMaxTranslation
        self.scroll_delta_x = ScreenHorizontalScroll("scroll_delta_x", -max_delta, max_delta, 0, 1)
        self.scroll_delta_x.position = (5, self.lbl_delta_x.get_bottom() + 10)
        self.scroll_delta_x.width = container_width - 10
        self.scroll_delta_x.scroll_callback = self.scroll_delta_x_change
        self.container_matching_translation.append(self.scroll_delta_x)

        self.lbl_delta_y = ScreenLabel("lbl_delta_y", "Delta Y: " + str(self.matching_delta_y), 21, container_width - 10, 1)
        self.lbl_delta_y.position = (5, self.scroll_delta_x.get_bottom() + 20)
        self.lbl_delta_y.set_background(general_background)
        self.lbl_delta_y.set_color(text_color)
        self.container_matching_translation.append(self.lbl_delta_y)

        self.scroll_delta_y = ScreenHorizontalScroll("scroll_delta_y", -max_delta, max_delta, 0, 1)
        self.scroll_delta_y.position = (5, self.lbl_delta_y.get_bottom() + 10)
        self.scroll_delta_y.width = container_width - 10
        self.scroll_delta_y.scroll_callback = self.scroll_delta_y_change
        self.container_matching_translation.append(self.scroll_delta_y)

        self.container_matching_translation.visible = False

        # ===============================================
        # Panel with matching parameters for step 2 (Matching Strictness)
        self.container_matching_strictness = ScreenContainer("container_matching_strictness", (container_width, 150),
                                                              general_background)
        self.container_matching_strictness.position = (self.container_view_buttons.get_left(), self.container_view_buttons.get_bottom() + 5)
        self.elements.append(self.container_matching_strictness)

        self.lbl_matching_title = ScreenLabel("lbl_matching_title", "Matching Parameters", 21,
                                                 container_width - 10, 1)
        self.lbl_matching_title.position = (5, 5)
        self.lbl_matching_title.set_background(general_background)
        self.lbl_matching_title.set_color(text_color)
        self.container_matching_strictness.append(self.lbl_matching_title)

        str_recall = "Minimum Recall: " + str(int(self.matching_min_recall * 100))
        self.lbl_min_recall = ScreenLabel("lbl_min_recall", str_recall, 21, container_width - 10, 1)
        self.lbl_min_recall.position = (5, self.lbl_matching_title.get_bottom() + 20)
        self.lbl_min_recall.set_background(general_background)
        self.lbl_min_recall.set_color(text_color)
        self.container_matching_strictness.append(self.lbl_min_recall)

        min_recall = GTUniqueCCAnnotator.ParamsMinRecall
        self.scroll_min_recall = ScreenHorizontalScroll("scroll_min_recall", min_recall, 100, 99, 1)
        self.scroll_min_recall.position = (5, self.lbl_min_recall.get_bottom() + 10)
        self.scroll_min_recall.width = container_width - 10
        self.scroll_min_recall.scroll_callback = self.scroll_min_recall_change
        self.container_matching_strictness.append(self.scroll_min_recall)

        str_precision = "Minimum Precision: " + str(int(self.matching_min_precision * 100))
        self.lbl_min_precision = ScreenLabel("lbl_min_precision", str_precision, 21, container_width - 10, 1)
        self.lbl_min_precision.position = (5, self.scroll_min_recall.get_bottom() + 20)
        self.lbl_min_precision.set_background(general_background)
        self.lbl_min_precision.set_color(text_color)
        self.container_matching_strictness.append(self.lbl_min_precision)

        min_precision = GTUniqueCCAnnotator.ParamsMinPrecision
        self.scroll_min_precision = ScreenHorizontalScroll("scroll_min_precision", min_precision, 100, 99, 1)
        self.scroll_min_precision.position = (5, self.lbl_min_precision.get_bottom() + 10)
        self.scroll_min_precision.width = container_width - 10
        self.scroll_min_precision.scroll_callback = self.scroll_min_precision_change
        self.container_matching_strictness.append(self.scroll_min_precision)

        self.container_matching_strictness.visible = False

        # ===============================================
        stats_background = (60, 50, 40)
        self.container_stats = ScreenContainer("container_stats", (container_width, 70), back_color=stats_background)
        self.container_stats.position = (self.width - container_width - 10, self.container_action_buttons.get_bottom() + 5)
        self.elements.append(self.container_stats)

        self.lbl_cc_stats = ScreenLabel("lbl_cc_stats", "Connected Component Stats", 21, container_width - 10, 1)
        self.lbl_cc_stats.position = (5, 5)
        self.lbl_cc_stats.set_background(stats_background)
        self.lbl_cc_stats.set_color(text_color)
        self.container_stats.append(self.lbl_cc_stats)

        self.lbl_cc_raw = ScreenLabel("lbl_cc_raw", "Total Raw CC:\n" + str(self.cc_total), 21, button_2_width, 1)
        self.lbl_cc_raw.position = (button_2_left, self.lbl_cc_stats.get_bottom() + 10)
        self.lbl_cc_raw.set_background(stats_background)
        self.lbl_cc_raw.set_color(text_color)
        self.container_stats.append(self.lbl_cc_raw)

        self.lbl_cc_unique = ScreenLabel("lbl_cc_unique", "Total Unique CC:\n" + str(len(self.unique_groups)), 21, button_2_width, 1)
        self.lbl_cc_unique.position = (button_2_right, self.lbl_cc_stats.get_bottom() + 10)
        self.lbl_cc_unique.set_background(stats_background)
        self.lbl_cc_unique.set_color(text_color)
        self.container_stats.append(self.lbl_cc_unique)

        #=============================================================
        # Panel with state buttons (Undo, Redo, Save)
        self.container_state_buttons = ScreenContainer("container_state_buttons", (container_width, 200),
                                                       general_background)
        self.container_state_buttons.position = (
        self.container_view_buttons.get_left(), self.container_stats.get_bottom() + 10)
        self.elements.append(self.container_state_buttons)

        self.btn_undo = ScreenButton("btn_undo", "Undo", 21, button_width)
        self.btn_undo.set_colors(button_text_color, button_back_color)
        self.btn_undo.position = (button_left, 5)
        self.btn_undo.click_callback = self.btn_undo_click
        self.container_state_buttons.append(self.btn_undo)

        self.btn_redo = ScreenButton("btn_redo", "Redo", 21, button_width)
        self.btn_redo.set_colors(button_text_color, button_back_color)
        self.btn_redo.position = (button_left, self.btn_undo.get_bottom() + 10)
        self.btn_redo.click_callback = self.btn_redo_click
        self.container_state_buttons.append(self.btn_redo)

        self.btn_save = ScreenButton("btn_save", "Save", 21, button_width)
        self.btn_save.set_colors(button_text_color, button_back_color)
        self.btn_save.position = (button_left, self.btn_redo.get_bottom() + 10)
        self.btn_save.click_callback = self.btn_save_click
        self.container_state_buttons.append(self.btn_save)

        self.btn_exit = ScreenButton("btn_exit", "Exit", 21, button_width)
        self.btn_exit.set_colors(button_text_color, button_back_color)
        self.btn_exit.position = (button_left, self.btn_save.get_bottom() + 30)
        self.btn_exit.click_callback = self.btn_exit_click
        self.container_state_buttons.append(self.btn_exit)

        # print("MAKE CONTAINER STATS VISIBLE AGAIN!!!")
        # self.container_stats.visible = False
        # self.container_state_buttons.visible = False

        # ==============================================================
        image_width = self.width - self.container_nav_buttons.width - 30
        image_height = self.height - container_top - 10
        self.container_images = ScreenContainer("container_images", (image_width, image_height), back_color=(0, 0, 0))
        self.container_images.position = (10, container_top)
        self.elements.append(self.container_images)

        # ... image objects ...
        tempo_blank = np.zeros((50, 50, 3), np.uint8)
        tempo_blank[:, :, :] = 255
        self.img_main = ScreenImage("img_main", tempo_blank, 0, 0, True, cv2.INTER_NEAREST)
        self.img_main.position = (0, 0)
        #self.img_main.mouse_button_down_callback = self.img_mouse_down
        self.container_images.append(self.img_main)

        # canvas used for annotations
        self.canvas_select = ScreenCanvas("canvas_select", 100, 100)
        self.canvas_select.position = (0, 0)
        self.canvas_select.locked = False
        # self.canvas_select.object_edited_callback = self.canvas_object_edited
        # self.canvas_select.object_selected_callback = self.canvas_selection_changed
        self.container_images.append(self.canvas_select)

        self.canvas_select.add_element("selection_rectangle", 10, 10, 40, 40)
        self.canvas_select.elements["selection_rectangle"].visible = False

        self.undo_stack = []
        self.redo_stack = []

        self.update_current_view(True)