Ejemplo n.º 1
0
def remove_background(
    *,
    image: Image,
    smooth_kernel_size: int = 2,
    threshold_value: int = 100,
    background_kernel_size: int = 5,
    debug: bool = False
) -> None:
    """Remove the background milimeter pattern."""
    if debug:
        debug_path = get_debug_path("remove_background")
        save(np.ndarray, debug_path, "input")

    image.invert()              # We want the main features to be white
    image.blur(smooth_kernel_size)
    image.threshold(threshold_value)        # Removes most of the milimiter markers

    remove_structured_background(
        image_array=image.image,
        background_kernel_size=(background_kernel_size, background_kernel_size),
        smoothing_kernel_size=(smooth_kernel_size, smooth_kernel_size),
        debug=debug
    )
    if debug:
        save(np.ndarray, debug_path, "output")

    image.invert()
    image.checkpoint("preprocessed")
Ejemplo n.º 2
0
def remove_background(
    *,
    image: Image,
    smooth_kernel_size: int = 2,
    threshold_value: int = 100,
    background_kernel_size: int = 5,
) -> None:
    image.invert()  # We want the main features to be white
    image.blur(smooth_kernel_size)
    image.threshold(threshold_value)  # Removes most of the milimiter markers

    remove_structured_background(
        image_array=image.image,
        background_kernel_size=(background_kernel_size,
                                background_kernel_size),
        smoothing_kernel_size=(smooth_kernel_size, smooth_kernel_size))

    image.invert()
    image.checkpoint("preprocessed")
Ejemplo n.º 3
0
def remove_contours(image: Image,
                    contours: tp.Sequence[np.ndarray],
                    fill_value: int = None):
    """Remove the pixels inside the contours."""
    if len(contours) == 0:
        assert False, "No contours"
    shape = m, n, *_ = image.image.shape

    _fill_value = fill_value
    if _fill_value is None:
        _fill_value = np.argmax(np.bincount(image.image.ravel()))

    image.image = cv2.drawContours(image.image, contours, -2, int(_fill_value),
                                   cv2.FILLED)
Ejemplo n.º 4
0
def split_image(image: Image,
                *,
                kernel_length: int = 5,
                blur_kernel_size: int = 9,
                threshold_value: float = 150,
                num_iterations: int = 4) -> tp.List[Image]:
    """Split an EEG scan into manageable parts.

    Detect the black square fiduciary markers and split the scan into parts such that each
    part contains two such markers.

    kernel_length:
        Govern how aggressive to be when removing horisontal and vertical background structures.
    blur_kernel_size:
        The size of the blur kernel for the initial blur then threshold operation.
    threshold_value:
        Thresholding is done right after the blurring.
    num_iterations:
        Number of iterations in the erode and dilate transformations.
    """
    image.bgr_to_gray()
    rectangles = markers(
        image,
        kernel_length=kernel_length,
        blur_kernel_size=blur_kernel_size,
        threshold_value=threshold_value,
        num_iterations=num_iterations,
    )

    rectangle_array = np.zeros((len(rectangles), 4, 2))
    for i, rec in enumerate(rectangles):
        for n, vertex in enumerate(rec):
            rectangle_array[i, n, :] = vertex[0]

    centres = np.mean(rectangle_array, axis=1)
    max_dx = np.max(centres[:, 0])
    max_dy = np.max(centres[:, 1])

    new_image_list = []

    horisontal_image = max_dx >= max_dy
    if horisontal_image:
        sorted_indices = np.argsort(centres[:, 0])
        sorted_rectangle_vertices = rectangle_array[sorted_indices, :, 0]
    else:
        sorted_indices = np.argsort(centres[:, 1])
        sorted_rectangle_vertices = rectangle_array[sorted_indices, :, 1]

    rectangle_indices = np.arange(2)
    for i in range(sorted_rectangle_vertices.shape[0] - 1):
        square1, square2 = sorted_rectangle_vertices[rectangle_indices]
        min_index = math.floor(min(map(np.min, (square1, square2))))
        max_index = math.ceil(max(map(np.max, (square1, square2))))

        if i == 0:
            min_index = 0
        if i == sorted_rectangle_vertices.shape[0] - 2:
            max_index = None  # include the last index

        if horisontal_image:
            new_image = image.image[:, min_index:max_index]
        else:
            new_image = image.image[min_index:max_index, :]
        new_image_list.append(Image(new_image))
        rectangle_indices += 1
    return new_image_list
Ejemplo n.º 5
0
def markers(image: Image,
            *,
            kernel_length: int = 5,
            blur_kernel_size: int = 9,
            threshold_value: float = 150,
            num_iterations: int = 4,
            debug: bool = False) -> tp.List[np.ndarray]:
    """Return the contours of the black square markers."""

    from .plots import plot
    # cv2.imwrite("foo.png", image.image)
    # assert False, image.image.dtype

    if debug:
        debug_path = get_debug_path("markers")
        save(image.image, debug_path, "input")

    assert len(image.image.shape) == 2, f"Expecting binary image"
    image.invert()
    image.blur(blur_kernel_size)
    image.threshold(threshold_value)

    if debug:
        save(image.image, debug_path, "threshold")

    if kernel_length > 0 and num_iterations > 0:
        vertical_kernel = cv2.getStructuringElement(cv2.MORPH_RECT,
                                                    (1, kernel_length))
        horisontal_kernel = cv2.getStructuringElement(cv2.MORPH_RECT,
                                                      (kernel_length, 1))

        # vertial lines
        vertical_image = cv2.erode(image.image,
                                   vertical_kernel,
                                   iterations=num_iterations)
        cv2.dilate(vertical_image,
                   vertical_kernel,
                   iterations=num_iterations,
                   dst=vertical_image)

        # Horisontal lines
        horisontal_image = cv2.erode(image.image,
                                     horisontal_kernel,
                                     iterations=num_iterations)
        cv2.dilate(image.image,
                   horisontal_kernel,
                   iterations=num_iterations,
                   dst=vertical_image)

        # Compute intersection of horisontal and vertical
        cv2.bitwise_and(horisontal_image, vertical_image, dst=image.image)

    contours = get_contours(image=image, min_size=4)
    if debug:
        image_draw = Image(image.copy_image())
        image_draw.gray_to_bgr()
        image_draw = cv2.drawContours(image_draw.image, contours, -2,
                                      (0, 255, 0), 2)
        save(image_draw, debug_path, "morphed")

    features = match_contours(matcher=get_marker_matcher(image=image),
                              contours=contours)
    if debug:
        image_draw = Image(image.copy_image())
        image_draw.gray_to_bgr()
        image_draw = cv2.drawContours(image_draw.image, features, -2,
                                      (0, 0, 255), 2)
        save(image_draw, debug_path, "features")
    image.reset_image()
    return features
Ejemplo n.º 6
0
    image_copy = image.image.copy()
    image.gaussian_blur(5)
    image.threshold()
    features = match_contours(matcher=image.match_graph_candidate,
                              contours=contours)
    image.filter_contours(features)
    image.blur(3)
    contours = cv2.findContours(blur, contour_mode, contour_method)
    contours = imutils.grab_contours(contours)
    contours = list(filter(lambda c: c.size > 6, contours))
    features = match_contours(matcher=image.match_graph_candidate,
                              contours=contours)

    # Restore colors
    image.reset_image()
    image.bgr_to_gray()
    image.filter_contours(features)
    image.gray_to_bgr()
    image.invert()
    image.draw(features, image.image)


if __name__ == "__main__":
    image = Image()
    filepath = Path("../data/scan1.png")
    image.read_image(filepath)

    preprocess(image)
    markers(image)
    graphs(image)
Ejemplo n.º 7
0
def run(
    *,
    input_image_path: Path,
    output_directory: Path,
    identifier: str,
    scale: float = None,
    debug: bool = False,
):
    """Segment the contours from a black and white image and save the segmented lines."""
    image = read_image(input_image_path)
    if debug:
        debug_path = get_debug_path("remove_background")
        save(image.image, debug_path, "input")
    image.bgr_to_gray()

    if debug:
        save(image.iamge, debug_path, "match_contours")
    contours = get_contours(image=image)
    features = match_contours(matcher=get_graph_matcher(), contours=contours)

    if debug:
        debug_path = get_debug_path("extract_contours_bw")
        save(image.image, debug_path, "input")

    output_directory.mkdir(exist_ok=True, parents=True)

    for i, c in enumerate(features):
        tmp_image = image.copy_image()
        filter_contours(image_array=tmp_image, contours=[c])
        clipped_contour = tmp_image[~np.all(tmp_image == 0, axis=1)]
        save_image(output_directory / f"{identifier}_trace{i}.png",
                   Image(clipped_contour))

    ############################
    ### Make annotated image ###
    ############################

    fig, ax = plt.subplots(1, figsize=(15, 10), dpi=500)
    tmp_image = image.image_orig

    color_iterator = itertools.cycle(mtableau_brg())

    for i, c in enumerate(features):
        color = next(color_iterator)
        tmp_image = cv2.drawContours(tmp_image, features, i,
                                     color_to_256_RGB(color), cv2.FILLED)
        polygon = Polygon(c.reshape(-1, 2))
        x0, y0, x1, y1 = polygon.bounds

        ann = ax.annotate(
            f"Contour {i}",
            xy=(x1, y1),  # (x0, y1)
            xycoords="data",
            xytext=(0, 35),
            textcoords="offset points",
            size=10,
            bbox=dict(
                boxstyle="round",
                fc=color  # normalised color
            ))

    ax.imshow(tmp_image)
    ax.set_title("A digitised paper strip")
    if scale is not None:  # multiply by distance between black sqaures?
        ax.set_xticklabels(
            ["{:.1f} cm".format(15 * i / scale) for i in ax.get_xticks()])
        ax.set_yticklabels(
            ["{:.1f} cm".format(15 * i / scale) for i in ax.get_yticks()])
    ax.set_ylabel("Voltage")
    ax.set_xlabel("Time")
    fig.savefig(output_directory / f"{identifier}_annotated.png", dpi=500)
Ejemplo n.º 8
0
def extract_contours(
        *,
        image: Image,
        blur_kernel_size: int = 3,
        dilate_kernel_size: int = 3,
        num_dilate_iterations: int = 3) -> tp.Sequence[np.ndarray]:
    # Remove initial guess at contours. This should leave the text blocks.
    image.invert()
    contours = get_contours(image=image)
    features = match_contours(
        matcher=get_graph_matcher(approximation_tolerance=1e-2),
        contours=contours)

    remove_contours(image, features)

    # Remove all the remaining text blobs
    image.blur(blur_kernel_size)
    image.threshold(100)
    image.morph(cv2.MORPH_DILATE, (dilate_kernel_size, dilate_kernel_size),
                num_dilate_iterations)

    contours = get_contours(image=image, min_size=6)

    # Compute all the bounding boxes and filter based on the aspect ratio?
    features = match_contours(
        matcher=get_bounding_rectangle_matcher(min_solidity=0.7),
        contours=contours)

    filter_contours(image_array=image.image, contours=features)
    image.morph(cv2.MORPH_DILATE, (dilate_kernel_size, dilate_kernel_size),
                num_dilate_iterations)
    image_mask = image.copy_image()

    image.reset_image("preprocessed")
    filter_image(image_array=image.image, binary_mask=image_mask == 255)
    image.checkpoint("preprocessed")

    # Match the remaining graph candidates, and remove everything else
    image.invert()
    image.blur(blur_kernel_size)
    image.threshold()
    image.morph(cv2.MORPH_DILATE, (dilate_kernel_size, dilate_kernel_size),
                num_dilate_iterations)

    contours = get_contours(image=image)
    features = match_contours(matcher=get_graph_matcher(), contours=contours)
    filter_contours(image_array=image.image, contours=features)

    image.reset_image("resampled")

    # TODO: Why invert?
    image.invert()
    filter_contours(image_array=image.image, contours=features)
    image.invert()

    image.blur(blur_kernel_size)
    image.threshold(100)

    image.invert()
    contours = get_contours(image=image)
    features = match_contours(matcher=get_graph_matcher(), contours=contours)
    return features
Ejemplo n.º 9
0
def markers(image: Image, blur_kernel_size=9, kernel_length=5):
    image.bgr_to_gray()
    image.invert()
    image.blur(blur_kernel_size)
    image.threshold(150)

    vertical_kernel = cv2.getStructuringElement(cv2.MORPH_RECT,
                                                (1, kernel_length))
    horisontal_kernel = cv2.getStructuringElement(cv2.MORPH_RECT,
                                                  (kernel_length, 1))

    # vertial lines
    vertical_image = cv2.erode(image.image, vertical_kernel, iterations=4)
    cv2.dilate(vertical_image,
               vertical_kernel,
               iterations=4,
               dst=vertical_image)

    # Horisontal lines
    horisontal_image = cv2.erode(image.image, horisontal_kernel, iterations=4)
    cv2.dilate(image.image,
               horisontal_kernel,
               iterations=4,
               dst=vertical_image)

    # Compute intersection of horisontal and vertical
    cv2.bitwise_and(horisontal_image, vertical_image, dst=image.image)

    contours = get_contours(image=image)
    features = match_contours(matcher=get_marker_matcher(image=image),
                              contours=contours)

    axis, scale = get_axis(image, features)
    image.set_axis(axis)
    image.set_scale(scale)
Ejemplo n.º 10
0
def extract_contours(
    *,
    image: Image,
    blur_kernel_size: int = 3,
    dilate_kernel_size: int = 3,
    num_dilate_iterations: int= 3,
    debug: bool = True
) -> tp.List[np.ndarray]:
    """Segment the EEG traces.

    Use a series of convolutions, filtering and edge tracking to extract the contours.

    blur_kernel_size:
        Used in conjunction with thresholding to binarise the image.
    dilate_kernel_size:
        Dilation is used with filtering to be aggressive in removing elements.
    num_dilate_iterations:
        THe number of dilate iterations.
    """
    if debug:
        debug_path = get_debug_path("extract_contours")
        save(np.ndarray, debug_path, "input")
    # Remove initial guess at contours. This should leave the text blocks.
    image.invert()
    contours = get_contours(image=image)
    features = match_contours(matcher=get_graph_matcher(approximation_tolerance=1e-2), contours=contours)
    remove_contours(image, features)
    if debug:
        save(np.ndarray, debug_path, "remove_contours")

    # Remove all the remaining text blobs
    image.blur(blur_kernel_size)
    image.threshold(100)
    image.morph(cv2.MORPH_DILATE, (dilate_kernel_size, dilate_kernel_size), num_dilate_iterations)
    contours = get_contours(image=image, min_size=6)

    # Compute all the bounding boxes and filter based on the aspect ratio?
    features = match_contours(
        matcher=get_bounding_rectangle_matcher(min_solidity=0.7),
        contours=contours
    )
    filter_contours(image_array=image.image, contours=features)
    image.morph(cv2.MORPH_DILATE, (dilate_kernel_size, dilate_kernel_size), num_dilate_iterations)
    image_mask = image.copy_image()

    image.reset_image("preprocessed")
    filter_image(image_array=image.image, binary_mask=image_mask == 255)
    image.checkpoint("preprocessed")
    if debug:
        save(np.ndarray, debug_path, "filter_contours1")

    # Match the remaining graph candidates, and remove everything else
    image.invert()
    image.blur(blur_kernel_size)
    image.threshold()
    image.morph(cv2.MORPH_DILATE, (dilate_kernel_size, dilate_kernel_size), num_dilate_iterations)

    contours = get_contours(image=image)
    features = match_contours(matcher=get_graph_matcher(), contours=contours)
    filter_contours(image_array=image.image, contours=features)
    if debug:
        save(np.ndarray, debug_path, "filter_contours2")

    image.reset_image("resampled")

    # TODO: Why invert? Has something to do with the fill color in filter_contours
    image.invert()
    filter_contours(image_array=image.image, contours=features)
    image.invert()
    if debug:
        save(np.ndarray, debug_path, "filter_contours3")

    image.blur(blur_kernel_size)
    image.threshold(100)

    image.invert()
    contours = get_contours(image=image)
    features = match_contours(matcher=get_graph_matcher(), contours=contours)
    return features
Ejemplo n.º 11
0
def run(
    input_image_path: Path,
    output_directory: Path,
    identifier: str,
    scale: float = None,
    debug:bool
):
    """Remove the background, segment the contours and save the segmented lines as pngs."""
    image = read_image(input_image_path)
    if debug:
        debug_path = get_debug_path("prepare_lines")
        save(np.ndarray, debug_path, "input")

    image.bgr_to_gray()
    image.checkpoint("resampled")

    remove_background(image=image, smooth_kernel_size=3)
    features = extract_contours(image=image, blur_kernel_size=3)
    if debug:
        save(np.ndarray, debug_path, "remove_background")
    image.invert()


    image.reset_image("resampled")
    image.invert()

    output_directory.mkdir(exist_ok=True, parents=True)

    for i, c in enumerate(features):
        tmp_image = image.copy_image()
        filter_contours(image_array=tmp_image, contours=[c])
        clipped_contour = tmp_image[~np.all(tmp_image == 0, axis=1)]
        save_image(output_directory / f"{identifier}_trace{i}.png", Image(clipped_contour))

    ##################
    ### Make image ###
    ##################

    tmp_image = np.ones((*image.image.shape, 3), dtype=np.uint8)
    tmp_image[:] = (255, 255, 255)      # White

    fig, ax = plt.subplots(1)
    ax.imshow(tmp_image)

    color_iterator = itertools.cycle(mtableau_brg())

    for i, c in enumerate(features):
        color = next(color_iterator)
        tmp_image = cv2.drawContours(tmp_image, features, i, color_to_256_RGB(color), cv2.FILLED)
        polygon = Polygon(c.reshape(-1, 2))
        x0, y0, x1, y1 = polygon.bounds

        tmp_image2 = np.zeros(tuple(map(math.ceil, (y1, x1))), dtype=np.uint8)
        tmp_image2 = cv2.drawContours(tmp_image2, features, i, 255, cv2.FILLED)

        ann = ax.annotate(
            f"Contour {i}",
            xy=(x0, y1),
            xycoords="data",
            xytext=(0, 35),
            textcoords="offset points",
            size=10,
            bbox=dict(
                boxstyle="round",
                fc=color       # normalised color
            )
        )

    ax.imshow(tmp_image)
    ax.set_title("A digitised paper strip")
    if scale is not None:       # multiply by distance between black sqaures?
        ax.set_xticklabels(["{:.1f} cm".format(15*i/scale) for i in ax.get_xticks()])
        ax.set_yticklabels(["{:.1f} cm".format(15*i/scale) for i in ax.get_yticks()])
    ax.set_ylabel("Voltage")
    ax.set_xlabel("Time")
    fig.savefig(output_directory / f"{identifier}_annotated.png")
Ejemplo n.º 12
0
def run(*,
        input_image_path: Path,
        output_directory: Path,
        identifier: str,
        smooth_kernel_size: int = 3,
        threshold_value: int = 100,
        background_kernel_size=5,
        scale: float = None,
        debug: bool = False,
        blue_color_filter: tp.Sequence[int] = None,
        red_color_filter: tp.Sequence[int] = None,
        horisontal_kernel_length: int = None,
        x_interval: tp.Tuple[int, int] = None):
    """Remove the background, segment the contours and save the segmented lines as pngs."""
    image = read_image(input_image_path)

    if debug:
        debug_path = get_debug_path("prepare_lines")
        save(image.image, debug_path, "input")

    if blue_color_filter is not None:
        lower = tuple(map(int, blue_color_filter[:3]))
        upper = tuple(map(int, blue_color_filter[3:]))
        print(lower, upper)
        blue_mask = cv2.inRange(image.image, lower, upper)
        image.image[blue_mask == 255] = 255
    if red_color_filter is not None:
        lower = tuple(map(int, red_color_filter[:3]))
        upper = tuple(map(int, red_color_filter[3:]))
        red_mask = cv2.inRange(image.image, lower, upper)
        image.image[red_mask == 255] = 255

    image.invert()
    image.bgr_to_gray()
    image.checkpoint("resampled")

    if horisontal_kernel_length is not None:
        horisontal_kernel = cv2.getStructuringElement(
            cv2.MORPH_RECT, (horisontal_kernel_length, 1))

        x0, x1 = x_interval
        if x1 == -1:  # Set -1 to be inclusive last element
            x1 = None
        detected_lines = cv2.morphologyEx(image.image[x0:x1, :],
                                          cv2.MORPH_OPEN,
                                          horisontal_kernel,
                                          iterations=1)

        # findContours returns (contours, hierarchy)
        contours = cv2.findContours(detected_lines, cv2.RETR_EXTERNAL,
                                    cv2.CHAIN_APPROX_SIMPLE)[0]
        for c in contours:
            cv2.drawContours(image.image[x0:x1, :], [c], -1, 0, -10)

    remove_background(image=image,
                      smooth_kernel_size=smooth_kernel_size,
                      threshold_value=threshold_value,
                      background_kernel_size=background_kernel_size,
                      debug=debug)
    image.checkpoint("remove_background")

    if debug:
        save(image.image, debug_path, "match_contours")
    contours = get_contours(image=image)
    features = match_contours(matcher=get_graph_matcher(), contours=contours)
    if features is None:  # In case of a blank image
        return

    quality_control_image = image.draw(features, show=False)
    cv2.imwrite(str(output_directory / f"QC_{identifier}.png"),
                quality_control_image)

    if debug:
        save(image.image, debug_path, "remove_background")

    # image.reset_image("resampled")
    image.reset_image("remove_background")
    # image.invert()

    output_directory.mkdir(exist_ok=True, parents=True)

    for i, c in enumerate(features):
        tmp_image = image.copy_image()
        filter_contours(image_array=tmp_image, contours=[c])
        clipped_contour = tmp_image[~np.all(tmp_image == 0, axis=1)]
        save_image(output_directory / f"{identifier}_trace{i}.png",
                   Image(clipped_contour))

    ############################
    ### Make annotated image ###
    ############################

    fig, ax = plt.subplots(1, figsize=(15, 10), dpi=500)
    tmp_image = image.image_orig

    color_iterator = itertools.cycle(mtableau_brg())

    for i, c in enumerate(features):
        color = next(color_iterator)
        tmp_image = cv2.drawContours(tmp_image, features, i,
                                     color_to_256_RGB(color), cv2.FILLED)
        polygon = Polygon(c.reshape(-1, 2))
        x0, y0, x1, y1 = polygon.bounds

        ann = ax.annotate(
            f"Contour {i}",
            xy=((x0 + x1) / 2, y1),  # (x0, y1)
            size=5,
            color=color,
            arrowprops=dict(arrowstyle='->',
                            connectionstyle="arc3,rad=-0.5",
                            color=color))

    ax.imshow(tmp_image)
    ax.set_title("A digitised paper strip")
    if scale is not None:  # multiply by distance between black sqaures?
        ax.set_xticklabels(
            ["{:.1f} cm".format(15 * i / scale) for i in ax.get_xticks()])
        ax.set_yticklabels(
            ["{:.1f} cm".format(15 * i / scale) for i in ax.get_yticks()])
    ax.set_ylabel("Voltage")
    ax.set_xlabel("Time")
    fig.savefig(output_directory / f"{identifier}_annotated.png", dpi=500)