예제 #1
0
def draw_polylines(obj_input, line_colour="blue", line_width="auto"):
    """
    Draw polylines onto an image. 
    
    Parameters
    ----------
    obj_input : array or container
        input object
    line_colour: {"blue", "red", "green", "black", "white"} str, optional
        polyline colour
    line_width: int, optional
        polyline width

    Returns
    -------
    image: array or container
        image with polylines
    """

    ## kwargs
    line_colour = colours[line_colour]

    ## load image
    if obj_input.__class__.__name__ == "ndarray":
        image = obj_input
    elif obj_input.__class__.__name__ == "container":
        image = obj_input.canvas
        df_polylines = obj_input.df_polylines

    ## more kwargs
    if line_width == "auto":
        line_width = _auto_line_width(image)

    ## visualize
    for polyline in df_polylines["polyline"].unique():
        sub = df_polylines.groupby(["polyline"])
        sub = sub.get_group(polyline)
        coords = list(sub[["x", "y"]].itertuples(index=False, name=None))
        cv2.polylines(image, np.array([coords]), False, line_colour,
                      line_width)

    ## return
    if obj_input.__class__.__name__ == "ndarray":
        return image
    elif obj_input.__class__.__name__ == "container":
        obj_input.canvas = image
예제 #2
0
def draw(obj_input,
         overwrite=False,
         tool="line",
         line_colour="black",
         line_width="auto",
         **kwargs):
    """
    Draw lines, rectangles or polygons onto a colour or binary image. Can be 
    used to connect. disconnect or erase contours. 

    Parameters
    ----------
    obj_input : array or container
        input object
    overwrite : bool, optional
        if a container is supplied, or when working from the pype, should any 
        exsting drawings be overwritten
    tool : {line, polygon, rectangle} str, optional
        type of tool to use for drawing
    line_colour : {"black", "white", "green", "red", "blue"} str, optional
        line or filling (for rectangle and polygon) colour
    line_width : int, optional
        line width

    Returns
    -------
    image : array or container
        image with drawings

    """
    ## kwargs
    flag_overwrite = overwrite
    test_params = kwargs.get("test_params", {})

    ## load image
    df_draw, df_image_data = None, None
    if obj_input.__class__.__name__ == "ndarray":
        image = obj_input
        if df_image_data.__class__.__name__ == "NoneType":
            df_image_data = pd.DataFrame({"filename": "unknown"}, index=[0])
    elif obj_input.__class__.__name__ == "container":
        image = copy.deepcopy(obj_input.image)
        df_image_data = obj_input.df_image_data
        if hasattr(obj_input, "df_draw"):
            df_draw = obj_input.df_draw
    else:
        print("wrong input format.")
        return

    ## more kwargs
    if line_width == "auto":
        line_width = _auto_line_width(image)
    if tool in ["rect", "rectangle", "poly", "polygon"]:
        line_width = -1

    while True:
        ## check if exists
        if not df_draw.__class__.__name__ == "NoneType" and flag_overwrite == False:
            print("- polylines already drawn (overwrite=False)")
            break
        elif not df_draw.__class__.__name__ == "NoneType" and flag_overwrite == True:
            print("- draw polylines (overwriting)")
            pass
        ## future option: edit drawings
        # elif not df_draw.__class__.__name__ == "NoneType" and flag_edit == True:
        #     print("- draw polylines (editing)")
        #     pass
        elif df_draw.__class__.__name__ == "NoneType":
            print("- draw polylines")
            pass

        ## method
        out = _image_viewer(
            image,
            tool=tool,
            draw=True,
            line_width=line_width,
            line_colour=line_colour,
            previous=test_params,
        )

        ## abort
        if not out.done:
            if obj_input.__class__.__name__ == "ndarray":
                print("terminated polyline creation")
                return
            elif obj_input.__class__.__name__ == "container":
                print("- terminated polyline creation")
                return True

        ## create df
        df_draw = pd.DataFrame({"tool": tool}, index=[0])
        df_draw["line_width"] = line_width
        df_draw["colour"] = line_colour
        df_draw["coords"] = str(out.point_list)

        break

    ## draw
    for idx, row in df_draw.iterrows():
        coord_list = eval(row["coords"])
        for coords in coord_list:
            if row["tool"] in ["line", "lines"]:
                cv2.polylines(
                    image,
                    np.array([coords]),
                    False,
                    colours[row["colour"]],
                    row["line_width"],
                )
            elif row["tool"] in ["rect", "rectangle", "poly", "polygon"]:
                cv2.fillPoly(image, np.array([coords]), colours[row["colour"]])

    ## return
    if obj_input.__class__.__name__ == "ndarray":
        df_draw = pd.concat(
            [
                pd.concat(
                    [df_image_data] * len(df_draw)).reset_index(drop=True),
                df_draw.reset_index(drop=True),
            ],
            axis=1,
        )
        return image
    elif obj_input.__class__.__name__ == "container":
        obj_input.df_draw = df_draw
        obj_input.image = image
예제 #3
0
def polylines(obj_input,
              df_image_data=None,
              overwrite=False,
              line_width="auto",
              line_colour="blue",
              **kwargs):
    """
    Set points, draw a connected line between them, and measure its length. 

    Parameters
    ----------
    obj_input : array or container
        input object
    df_image_data : DataFrame, optional
        an existing DataFrame containing image metadata, will be added to
        output DataFrame
    overwrite: bool, optional
        if working using a container, or from a phenopype project directory, 
        should existing polylines be overwritten
    line_width: int, optional
        width of polyline

    Returns
    -------
    df_polylines : DataFrame or container
        contains the drawn polylines

    """
    ## kwargs
    flag_overwrite = overwrite

    ## load image
    df_polylines = None
    if obj_input.__class__.__name__ == "ndarray":
        image = obj_input
        if df_image_data.__class__.__name__ == "NoneType":
            df_image_data = pd.DataFrame({"filename": "unknown"}, index=[0])
    elif obj_input.__class__.__name__ == "container":
        image = copy.deepcopy(obj_input.image_copy)
        df_image_data = obj_input.df_image_data
        if hasattr(obj_input, "df_polylines"):
            df_polylines = obj_input.df_polylines
    else:
        print("wrong input format.")
        return

    ## more kwargs
    if line_width == "auto":
        line_width = _auto_line_width(image)
    test_params = kwargs.get("test_params", {})

    while True:
        ## check if exists
        if not df_polylines.__class__.__name__ == "NoneType" and flag_overwrite == False:
            df_polylines = df_polylines[df_polylines.columns.intersection(
                ["polyline", "length", "x", "y"])]
            print("- polylines already drawn (overwrite=False)")
            break
        elif not df_polylines.__class__.__name__ == "NoneType" and flag_overwrite == True:
            print("- draw polylines (overwriting)")
            pass
        elif df_polylines.__class__.__name__ == "NoneType":
            print("- draw polylines")
            pass

        ## method
        out = _image_viewer(
            image,
            tool="polyline",
            line_width=line_width,
            line_colour=line_colour,
            previous=test_params,
        )
        coords = out.point_list

        ## abort
        if not out.done:
            if obj_input.__class__.__name__ == "ndarray":
                print("terminated polyline creation")
                return
            elif obj_input.__class__.__name__ == "container":
                print("- terminated polyline creation")
                return True

        ## create df
        df_polylines = pd.DataFrame(columns=["polyline", "length", "x", "y"])
        idx = 0
        for point_list in coords:
            idx += 1
            arc_length = int(cv2.arcLength(np.array(point_list), closed=False))
            df_sub = pd.DataFrame(point_list, columns=["x", "y"])
            df_sub["polyline"] = idx
            df_sub["length"] = arc_length
            df_polylines = df_polylines.append(df_sub,
                                               ignore_index=True,
                                               sort=False)
        break

    ## merge with existing image_data frame
    df_polylines = pd.concat(
        [
            pd.concat(
                [df_image_data] * len(df_polylines)).reset_index(drop=True),
            df_polylines.reset_index(drop=True),
        ],
        axis=1,
    )

    ## return
    if obj_input.__class__.__name__ == "ndarray":
        return df_polylines
    elif obj_input.__class__.__name__ == "container":
        obj_input.df_polylines = df_polylines
예제 #4
0
def draw_contours(
    obj_input,
    df_contours=None,
    offset_coords=None,
    label=True,
    fill=0.3,
    mark_holes=True,
    level=3,
    line_colour="green",
    label_size="auto",
    label_colour="black",
    line_width="auto",
    label_width="auto",
    skeleton=True,
    watershed=False,
    bounding_box=False,
    bounding_box_ext=20,
):
    """
    Draw contours and their labels onto a canvas. Can be filled or empty, offset
    coordinates can be supplied. This will also draw the skeleton, if the argument
    "skeleton=True" and the supplied "df_contour" contains a "skeleton_coords"
    column.

    Parameters
    ----------
    obj_input : array or container
        input object
    df_contours : DataFrame, optional
        contains the contours
    offset_coords : tuple, optional
        offset coordinates, will be added to all contours
    label : bool, optional
        draw contour label
    fill : float, optional
        background transparency for contour fill (0=no fill).
    mark_holes : bool, optional
        contours located inside other contours (i.e. their holes) will be 
        highlighted in red
    level : int, optional
        the default is 3.
    line_colour: {"green", "red", "blue", "black", "white"} str, optional
        contour line colour
    line_width: int, optional
        contour line width
    label_colour : {"black", "white", "green", "red", "blue"} str, optional
        contour label colour.
    label_size: int, optional
        contour label font size (scaled to image)
    label_width: int, optional
        contour label font thickness 
    watershed: bool, optional
        indicates if a watershed-procedure has been performed. formats the
        coordinate colours accordingly (excludes "mark_holes option")
    bounding_box: bool, optional
        draw bounding box around the contour
    bounding_box_ext: in, optional
        value in pixels by which the bounding box should be extended
        
    Returns
    -------
    image: array or container
        image with contours

    """
    ## kwargs
    flag_bounding_box = bounding_box
    if flag_bounding_box:
        q = bounding_box_ext
    flag_label = label
    flag_fill = fill
    flag_mark_holes = mark_holes
    flag_skeleton = skeleton
    flag_watershed = watershed
    if flag_watershed:
        flag_mark_holes = True
    line_colour_sel = colours[line_colour]
    label_colour = colours[label_colour]

    ## load image
    if obj_input.__class__.__name__ == "ndarray":
        image = copy.deepcopy(obj_input)
        if df_contours.__class__.__name__ == "NoneType":
            print("No df provided - cannot draw contours.")
            return
    elif obj_input.__class__.__name__ == "container":
        image = obj_input.canvas
        df_contours = obj_input.df_contours
    else:
        print("wrong input format.")
        return

    ## more kwargs
    if line_width == "auto":
        line_width = _auto_line_width(image)
    if label_size == "auto":
        label_size = _auto_text_size(image)
    if label_width == "auto":
        label_width = _auto_text_width(image)

    ## method
    idx = 0
    colour_mask = copy.deepcopy(image)
    for index, row in df_contours.iterrows():
        if flag_mark_holes:
            if row["order"] == "child":
                if flag_watershed:
                    fill_colour = line_colour_sel
                    line_colour = line_colour_sel
                else:
                    fill_colour = colours["red"]
                    line_colour = colours["red"]
            elif row["order"] == "parent":
                if flag_watershed:
                    continue
                else:
                    fill_colour = line_colour_sel
                    line_colour = line_colour_sel
        else:
            fill_colour = line_colour_sel
            line_colour = line_colour_sel
        if flag_fill > 0:
            cv2.drawContours(
                image=colour_mask,
                contours=[row["coords"]],
                contourIdx=idx,
                thickness=-1,
                color=fill_colour,
                maxLevel=level,
                offset=offset_coords,
            )
        if line_width > 0:
            cv2.drawContours(
                image=image,
                contours=[row["coords"]],
                contourIdx=idx,
                thickness=line_width,
                color=line_colour,
                maxLevel=level,
                offset=offset_coords,
            )
        if flag_skeleton and "skeleton_coords" in df_contours:
            cv2.drawContours(
                image=image,
                contours=[row["skeleton_coords"]],
                contourIdx=idx,
                thickness=line_width,
                color=colours["red"],
                maxLevel=level,
                offset=offset_coords,
            )
        if flag_label:
            cv2.putText(
                image,
                str(row["contour"]),
                (row["center"]),
                cv2.FONT_HERSHEY_SIMPLEX,
                label_size,
                label_colour,
                label_width,
                cv2.LINE_AA,
            )
            cv2.putText(
                colour_mask,
                str(row["contour"]),
                (row["center"]),
                cv2.FONT_HERSHEY_SIMPLEX,
                label_size,
                label_colour,
                label_width,
                cv2.LINE_AA,
            )
        if flag_bounding_box:
            rx, ry, rw, rh = cv2.boundingRect(row["coords"])
            cv2.rectangle(
                image,
                (rx - q, ry - q),
                (rx + rw + q, ry + rh + q),
                fill_colour,
                line_width,
            )

    image = cv2.addWeighted(image, 1 - flag_fill, colour_mask, flag_fill, 0)

    # df_contours= df_contours.drop("skeleton_coords", axis=1)

    ## return
    if obj_input.__class__.__name__ == "ndarray":
        return image
    elif obj_input.__class__.__name__ == "container":
        obj_input.canvas = image
예제 #5
0
def draw_masks(
    obj_input,
    select=None,
    df_masks=None,
    line_colour="blue",
    line_width="auto",
    label=False,
    label_size="auto",
    label_colour="black",
    label_width="auto",
):
    """
    Draw masks into an image. This function is also used to draw the perimeter 
    of a created or detected reference scale card.
    
    Parameters
    ----------        
    obj_input : array or container
        input object
    select: str or list
        select a subset of masks to display
    df_masks: DataFrame, optional
        contains mask coordinates and label
    line_colour: {"blue", "red", "green", "black", "white"} str, optional
        mask line colour
    line_width: int, optional
        mask line width
    label_colour : {"black", "white", "green", "red", "blue"} str, optional
        mask label colour.
    label_size: int, optional
        mask label font size (scaled to image)
    label_width: int, optional
        mask label font width  (scaled to image)

    Returns
    -------
    image: array or container
        image with masks
    """
    ## kwargs
    flag_label = label
    line_colour_sel = colours[line_colour]
    label_colour = colours[label_colour]
    if not select.__class__.__name__ == "NoneType":
        if not select.__class__.__name__ == "list":
            select = [select]

    ## load image
    if obj_input.__class__.__name__ == "ndarray":
        image = copy.deepcopy(obj_input)
        if df_masks.__class__.__name__ == "NoneType":
            print("No df provided - cannot draw masks.")
            return
    elif obj_input.__class__.__name__ == "container":
        image = obj_input.canvas
        df_masks = obj_input.df_masks
    else:
        print("wrong input format.")
        return

    ## more kwargs
    if line_width == "auto":
        line_width = _auto_line_width(image)
    if label_size == "auto":
        label_size = _auto_text_size(image)
    if label_width == "auto":
        label_width = _auto_text_width(image)

    ## draw masks from mask obect
    for index, row in df_masks.iterrows():
        if not select.__class__.__name__ == "NoneType":
            if row["mask"] in select:
                pass
            else:
                continue
        else:
            pass
        print(" - show mask: " + row["mask"] + ".")
        coords = eval(row["coords"])
        if coords[0].__class__.__name__ == "list":
            coords = coords[0]
        if row["mask"] == "scale":
            line_colour = colours["red"]
        else:
            line_colour = line_colour_sel
        cv2.polylines(image, np.array([coords]), False, line_colour,
                      line_width)
        if flag_label:
            cv2.putText(
                image,
                row["mask"],
                coords[0],
                cv2.FONT_HERSHEY_SIMPLEX,
                label_size,
                label_colour,
                label_width,
                cv2.LINE_AA,
            )

    ## return
    if obj_input.__class__.__name__ == "ndarray":
        return image
    elif obj_input.__class__.__name__ == "container":
        obj_input.canvas = image