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
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
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
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
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