Пример #1
0
    def delete_plot_axes(plot_image):

        w = np.size(plot_image, 1)
        h = np.size(plot_image, 0)

        # Mask out all but central ~5% of horizontal and vertical, to prepare to
        # remove axes_size
        mask = np.zeros((h, w, 1), np.uint8)
        mask = np.full(plot_image.shape, 255, np.uint8)

        # Draw axes in the middle (to allow mask to match template on)
        cv2.line(mask, (int(w / 2), 0), (int(w / 2), h), (0), int(w * 0.03))
        cv2.line(mask, (0, int(h / 2)), (w, int(h / 2)), (0), int(h * 0.03))

        # Mask out all but central 5%:
        masked_axes = cv2.bitwise_or(plot_image, mask)

        masked_axes = Image_Utils.delete_stray_marks(masked_axes, 0.0001,
                                                     0.001)

        return_image = cv2.bitwise_or(cv2.bitwise_not(masked_axes), plot_image)

        return return_image
Пример #2
0
    def get_perc_plot_element(plot_element):

        # Declare our return value:
        ret_val = Hvf_Perc_Icon.PERC_NO_VALUE

        # What is the total plot size?
        plot_area = np.size(plot_element, 0) * np.size(plot_element, 1)

        # Delete stray marks; filters out specks based on size compared to global element
        # and relative to largest contour
        plot_threshold = 0.005
        relative_threshold = 0.005
        plot_element = Image_Utils.delete_stray_marks(plot_element,
                                                      plot_threshold,
                                                      relative_threshold)

        # First, crop the white border out of the element to get just the core icon:
        x0, x1, y0, y1 = Image_Utils.crop_white_border(plot_element)

        # Calculate height and width:
        h = y1 - y0
        w = x1 - x0

        # If our bounding indices don't catch any borders (ie, x0 > x1) then its must be an
        # empty element:
        if (w < 0):
            ret_val = Hvf_Perc_Icon.PERC_NO_VALUE

        # Finding 'normal' elements is tricky because the icon is small (scaling doesn't
        # work as easily for it) and it tends to get false matching with other icons
        # However, its size is very different compared to the other icons, so just detect
        # it separately
        # If the cropped bounding box is less than 20% of the overall box, highly likely
        # normal icon
        elif ((h / np.size(plot_element, 0)) < 0.20):

            ret_val = Hvf_Perc_Icon.PERC_NORMAL

        else:

            # Grab our element icon:
            element_cropped = plot_element[y0:1 + y1, x0:1 + x1]

            # Now, we template match against all icons and look for best fit:
            best_match = None
            best_perc = None

            for ii in range(len(Hvf_Perc_Icon.template_perc_list)):

                # Scale up the plot element or perc icon, whichever is smaller
                # (meaning, scale up so they're equal, don't scale down - keep as much
                # data as we can)

                # Grab our perc icon:
                perc_icon = Hvf_Perc_Icon.template_perc_list[ii]

                min_val, max_val, min_loc, max_loc = Hvf_Perc_Icon.do_template_matching(
                    plot_element, w, h, perc_icon)

                # Check to see if this is our best fit yet:
                if (best_match is None or min_val < best_match):
                    # This is best fit - record the value and the icon type
                    best_match = min_val
                    best_perc = Hvf_Perc_Icon.enum_perc_list[ii]

                # Debug strings for matching the enum:
                debug_string = "Matching enum " + str(
                    Hvf_Perc_Icon.enum_perc_list[ii]) + "; match : " + str(
                        min_val)
                Logger.get_logger().log_msg(Logger.DEBUG_FLAG_DEBUG,
                                            debug_string)

            ret_val = best_perc

            # Now we need to ensure that all declared 5-percentile icons are true, because
            # this program often mixes up between 5-percentile and half-percentile

            if (ret_val == Hvf_Perc_Icon.PERC_5_PERCENTILE):

                # Check for contours here - we know that the 5 percentile has multiple small contours
                plot_element = cv2.bitwise_not(plot_element)

                # Find contours. Note we are using RETR_EXTERNAL, meaning no children contours (ie
                # contours within contours)
                contours, hierarchy = cv2.findContours(plot_element,
                                                       cv2.RETR_EXTERNAL,
                                                       cv2.CHAIN_APPROX_SIMPLE)

                # Now add up all the contour area
                total_cnt_area = 0
                for cnt in contours:
                    total_cnt_area = total_cnt_area + cv2.contourArea(cnt)

                # Now compare to our cropped area
                # In optimal scenario, 5-percentile takes up 25% of area; half-percentile essentially 100%
                # Delineate on 50%
                AREA_PERCENTAGE_CUTOFF = 0.5
                area_percentage = total_cnt_area / (w * h)

                Logger.get_logger().log_msg(
                    Logger.DEBUG_FLAG_DEBUG,
                    "Recheck matching betwen 5-percentile and half-percentile")
                Logger.get_logger().log_msg(
                    Logger.DEBUG_FLAG_DEBUG,
                    "Total contour area percentage: " + str(area_percentage))

                # Check to see which is better. Because we are inverting, check max value
                if (area_percentage > AREA_PERCENTAGE_CUTOFF):

                    # Half percentile is a better fit - switch our match
                    ret_val = Hvf_Perc_Icon.PERC_HALF_PERCENTILE

                    # Declare as such:
                    debug_string = "Correction: switching from 5-percentile to half-percentile"
                    Logger.get_logger().log_msg(Logger.DEBUG_FLAG_DEBUG,
                                                debug_string)

            # Debug strings for bounding box:
            debug_bound_box_string = "Bounding box: " + str(x0) + "," + str(
                y0) + " ; " + str(x1) + "," + str(y1)
            Logger.get_logger().log_msg(Logger.DEBUG_FLAG_DEBUG,
                                        debug_bound_box_string)
            debug_bound_box_dim_string = "Bounding box dimensions: " + str(
                w) + " , " + str(h)
            Logger.get_logger().log_msg(Logger.DEBUG_FLAG_DEBUG,
                                        debug_bound_box_dim_string)

            # And debug function for showing the cropped element:
            show_cropped_element_func = (lambda: cv2.imshow(
                'cropped ' + str(Hvf_Perc_Icon.i), element_cropped))
            Logger.get_logger().log_function(Logger.DEBUG_FLAG_DEBUG,
                                             show_cropped_element_func)
            Hvf_Perc_Icon.i = Hvf_Perc_Icon.i + 1

        return ret_val
    def get_value_plot_element(plot_element, plot_element_backup, plot_type):
        # Declare return value
        return_val = 0

        # CV2 just slices images and returns the native image. We mess with the pixels so
        # for cleanliness, just copy it over:
        plot_element = plot_element.copy()

        # First, clean up any small noisy pixels by eliminating small contours
        # Tolerance for stray marks is different depending on plot type

        # Relative to largest contour:
        plot_threshold = 0
        relative_threshold = 0

        if (plot_type == "raw"):
            plot_threshold = 0.005
            relative_threshold = 0.1
        else:
            plot_threshold = 0.005
            relative_threshold = 0.01

        plot_element = Image_Utils.delete_stray_marks(plot_element,
                                                      plot_threshold,
                                                      relative_threshold)
        plot_element_backup = Image_Utils.delete_stray_marks(
            plot_element_backup, plot_threshold, relative_threshold)

        # Now, crop out the borders so we just have the central values - this allows us
        # to standardize size
        x0, x1, y0, y1 = Image_Utils.crop_white_border(plot_element)

        # Now we have bounding x/y coordinates
        # Calculate height and width:
        h = y1 - y0
        w = x1 - x0

        # Sometimes in low quality images, empty cells may have noise - also need to filter
        # based on area of element
        #THRESHOLD_AREA_FRACTION = 0.03;
        #fraction_element_area = (w*h)/(np.size(plot_element, 0)*np.size(plot_element, 1));

        # If this was an empty plot, (or element area is below threshold) we have no value
        #if ((w <= 0) or (h <= 0) or fraction_element_area < THRESHOLD_AREA_FRACTION):
        if (w <= 0) or (h <= 0):
            Logger.get_logger().log_msg(
                Logger.DEBUG_FLAG_DEBUG,
                "Declaring no value because cell is empty/below threshold marks"
            )

            return_val = Hvf_Value.VALUE_NO_VALUE

            Hvf_Value.i = Hvf_Value.i + 1
        else:

            # First, split the slice into a character list:

            list_of_chars = Hvf_Value.chop_into_char_list(
                plot_element[y0:1 + y1, x0:1 + x1])
            list_of_chars_backup = Hvf_Value.chop_into_char_list(
                plot_element[y0:1 + y1, x0:1 + x1])

            # Check for special cases (ie, non-numeric characters)

            # Check if <0 value
            # Can optimize detection accuracy by limiting check to only raw plot values with 2 chars:
            if (plot_type == "raw" and len(list_of_chars) == 2
                    and (Hvf_Value.is_less_than(list_of_chars[0])
                         or Hvf_Value.is_less_than(list_of_chars_backup[0]))):

                Logger.get_logger().log_msg(Logger.DEBUG_FLAG_DEBUG,
                                            "Detected less-than sign")
                return_val = Hvf_Value.VALUE_BELOW_THRESHOLD

            # Check if the above detection worked:
            if (return_val == 0):

                # No, so continue detection for number

                # Determine if we have a minus sign
                is_minus = 1

                # First, look for minus sign - if we have 2 or 3 characters

                # Negative numbers are not present in raw plot
                if not (plot_type == "raw"):

                    if (len(list_of_chars) == 2
                            and Hvf_Value.is_minus(list_of_chars[0])):

                        # Detected minus sign:
                        Logger.get_logger().log_msg(Logger.DEBUG_FLAG_DEBUG,
                                                    "Detected minus sign")

                        # Set our multiplier factor (makes later numeric correction easier)
                        is_minus = -1

                        # Remove the character from the list
                        list_of_chars.pop(0)
                        list_of_chars_backup.pop(0)

                    elif (len(list_of_chars) == 3):
                        # We know there must be a minus sign, so just raise flag
                        Logger.get_logger().log_msg(Logger.DEBUG_FLAG_DEBUG,
                                                    "Assuming minus sign")

                        is_minus = -1
                        # Remove the character from the list
                        list_of_chars.pop(0)
                        list_of_chars_backup.pop(0)

                # Now, look for digits, and calculate running value

                running_value = 0

                for jj in range(len(list_of_chars)):

                    # Pull out our digit to detect, and clean it
                    digit = Hvf_Value.clean_slice(list_of_chars[jj])

                    show_element_func = (lambda: cv2.imshow(
                        'Sub element ' + str(Hvf_Value.i) + "_" + str(jj),
                        digit))
                    Logger.get_logger().log_function(Logger.DEBUG_FLAG_DEBUG,
                                                     show_element_func)

                    Hvf_Value.j = Hvf_Value.j + 1

                    # Search for 0 if it is the trailing 0 of a multi-digit number, or if lone digit and not a minus
                    allow_search_zero = ((jj == len(list_of_chars) - 1) and
                                         (len(list_of_chars) > 1)) or (
                                             (len(list_of_chars) == 1) and
                                             (is_minus == 1))

                    Logger.get_logger().log_msg(
                        Logger.DEBUG_FLAG_DEBUG,
                        "Allow 0 search: " + str(allow_search_zero))
                    Logger.get_logger().log_msg(Logger.DEBUG_FLAG_DEBUG,
                                                "jj: " + str(jj))
                    Logger.get_logger().log_msg(
                        Logger.DEBUG_FLAG_DEBUG,
                        "list_of_chars length: " + str(len(list_of_chars)))

                    best_value, best_loc, best_scale_factor, best_match = Hvf_Value.identify_digit(
                        digit, allow_search_zero)

                    # If not a good match, recheck with alternatively processed image -> may increase yield
                    threshold_match_digit = 0.5

                    if (best_match > 0 and best_match < threshold_match_digit):

                        digit_backup = Hvf_Value.clean_slice(
                            list_of_chars_backup[jj])
                        best_value, best_loc, best_scale_factor, best_match = Hvf_Value.identify_digit(
                            digit_backup, allow_search_zero)

                    running_value = (10 * running_value) + best_value

                Hvf_Value.i = Hvf_Value.i + 1
                Hvf_Value.j = 0

                return_val = running_value * is_minus

        # Debug info string for the best matched value:
        debug_best_match_string = "Best matched value: " + Hvf_Value.get_string_from_value(
            return_val)
        Logger.get_logger().log_msg(Logger.DEBUG_FLAG_INFO,
                                    debug_best_match_string)

        return return_val