def _broadcast_colors(color, num, img, colorspace): """ Determine if color applies a single color to all ``num`` items, or if it is a list of colors for each item. Return as a list of colors for each item. TODO: - [ ] add as classmethod of kwimage.Color Example: >>> img = (np.random.rand(512, 512, 3) * 255).astype(np.uint8) >>> colorspace = 'rgb' >>> color = color_str_list = ['red', 'green', 'blue'] >>> color_str = 'red' >>> num = 3 >>> print(_broadcast_colors(color_str_list, num, img, colorspace)) >>> print(_broadcast_colors(color_str, num, img, colorspace)) >>> colors_tuple_list = _broadcast_colors(color_str_list, num, img, colorspace) >>> print(_broadcast_colors(colors_tuple_list, num, img, colorspace)) >>> # >>> # FIXME: This case seems broken >>> colors_ndarray_list = np.array(_broadcast_colors(color_str_list, num, img, colorspace)) >>> print(_broadcast_colors(colors_ndarray_list, num, img, colorspace)) """ # Note there is an ambiguity when num=3 and color=[int, int, int] # that must be resolved by checking num channels in the image import kwimage import ubelt as ub import numbers needs_broadcast = True # assume the list wasnt given by default if ub.iterable(color): first = ub.peek(color) if len(color) == num: if len(color) <= 4 and isinstance(first, numbers.Number): # ambiguous case, interpret as a single broadcastable color needs_broadcast = True else: # This is the only case we dont need broadcast needs_broadcast = False if needs_broadcast: color = kwimage.Color(color)._forimage(img, colorspace) colors = [color] * num else: colors = [kwimage.Color(c)._forimage(img, colorspace) for c in color] return colors
def draw(self, color='blue', ax=None, alpha=None, coord_axes=[1, 0], radius=1): """ Note: unlike other methods, the defaults assume x/y internal data Args: coord_axes (Tuple): specify which image axes each coordinate dim corresponds to. For 2D images, if you are storing r/c data, set to [0,1], if you are storing x/y data, set to [1,0]. Example: >>> # xdoc: +REQUIRES(module:kwplot) >>> from kwimage.structs.coords import * # NOQA >>> self = Coords.random(10) >>> # xdoc: +REQUIRES(--show) >>> self.draw(radius=3.0) >>> import kwplot >>> kwplot.autompl() >>> self.draw(radius=3.0) """ import matplotlib as mpl import kwimage from matplotlib import pyplot as plt if ax is None: ax = plt.gca() data = self.data if self.dim != 2: raise NotImplementedError('need 2d for mpl') # More grouped patches == more efficient runtime if alpha is None: alpha = [1.0] * len(data) elif not ub.iterable(alpha): alpha = [alpha] * len(data) ptcolors = [kwimage.Color(color, alpha=a).as01('rgba') for a in alpha] color_groups = ub.group_items(range(len(ptcolors)), ptcolors) default_centerkw = { 'radius': radius, 'fill': True } centerkw = default_centerkw.copy() collections = [] for pcolor, idxs in color_groups.items(): yx_list = [row[coord_axes] for row in data[idxs]] patches = [ mpl.patches.Circle((x, y), ec=None, fc=pcolor, **centerkw) for y, x in yx_list ] col = mpl.collections.PatchCollection(patches, match_original=True) collections.append(col) ax.add_collection(col) return collections
def draw_boxes_on_image(img, boxes, color='blue', thickness=1, box_format=None, colorspace='rgb'): """ Draws boxes on an image. Args: img (ndarray): image to copy and draw on boxes (nh.util.Boxes): boxes to draw colorspace (str): string code of the input image colorspace Example: >>> import kwimage >>> import numpy as np >>> img = np.zeros((10, 10, 3), dtype=np.uint8) >>> color = 'dodgerblue' >>> thickness = 1 >>> boxes = kwimage.Boxes([[1, 1, 8, 8]], 'tlbr') >>> img2 = draw_boxes_on_image(img, boxes, color, thickness) >>> assert tuple(img2[1, 1]) == (30, 144, 255) >>> # xdoc: +REQUIRES(--show) >>> import kwplot >>> kwplot.autompl() # xdoc: +SKIP >>> kwplot.figure(doclf=True, fnum=1) >>> kwplot.imshow(img2) """ import kwimage import cv2 if not isinstance(boxes, kwimage.Boxes): if box_format is None: raise ValueError('specify box_format') boxes = kwimage.Boxes(boxes, box_format) color = kwimage.Color(color)._forimage(img, colorspace) tlbr = boxes.to_tlbr().data img2 = img.copy() for x1, y1, x2, y2 in tlbr: # pt1 = (int(round(x1)), int(round(y1))) # pt2 = (int(round(x2)), int(round(y2))) pt1 = (int(x1), int(y1)) pt2 = (int(x2), int(y2)) # Note cv2.rectangle does work inplace img2 = cv2.rectangle(img2, pt1, pt2, color, thickness=thickness) return img2
def draw_points(xy, color='blue', class_idxs=None, classes=None, ax=None, alpha=None, radius=1, **kwargs): """ Args: xy (ndarray): of points. Example: >>> from kwplot.mpl_draw import * # NOQA >>> import kwimage >>> xy = kwimage.Points.random(10).xy >>> draw_points(xy, radius=0.01) >>> draw_points(xy, class_idxs=np.random.randint(0, 3, 10), >>> radius=0.01, classes=['a', 'b', 'c'], color='classes') Ignore: >>> import kwplot >>> kwplot.autompl() """ import kwimage import matplotlib as mpl from matplotlib import pyplot as plt if ax is None: ax = plt.gca() xy = xy.reshape(-1, 2) # More grouped patches == more efficient runtime if alpha is None: alpha = [1.0] * len(xy) elif not ub.iterable(alpha): alpha = [alpha] * len(xy) if color == 'distinct': colors = kwimage.Color.distinct(len(alpha)) elif color == 'classes': # TODO: read colors from categories if they exist if class_idxs is None or classes is None: raise Exception( 'cannot draw class colors without class_idxs and classes') try: cls_colors = kwimage.Color.distinct(len(classes)) except KeyError: raise Exception( 'cannot draw class colors without class_idxs and classes') import kwarray _keys, _vals = kwarray.group_indices(class_idxs) colors = list(ub.take(cls_colors, class_idxs)) else: colors = [color] * len(alpha) ptcolors = [ kwimage.Color(c, alpha=a).as01('rgba') for c, a in zip(colors, alpha) ] color_groups = ub.group_items(range(len(ptcolors)), ptcolors) circlekw = { 'radius': radius, 'fill': True, 'ec': None, } if 'fc' in kwargs: import warnings warnings.warning('Warning: specifying fc to Points.draw overrides ' 'the color argument. Use color instead') circlekw.update(kwargs) fc = circlekw.pop('fc', None) # hack collections = [] for pcolor, idxs in color_groups.items(): # hack for fc if fc is not None: pcolor = fc patches = [ mpl.patches.Circle((x, y), fc=pcolor, **circlekw) for x, y in xy[idxs] ] col = mpl.collections.PatchCollection(patches, match_original=True) collections.append(col) ax.add_collection(col) return collections
def draw(self, color='blue', ax=None, alpha=1.0, radius=1, setlim=False, border=False, linewidth=2): """ Draws polygon in a matplotlib axes. See `draw_on` for in-memory image modification. Example: >>> # xdoc: +REQUIRES(module:kwplot) >>> from kwimage.structs.polygon import * # NOQA >>> self = Polygon.random(n_holes=1) >>> self = self.scale(100) >>> # xdoc: +REQUIRES(--show) >>> self.draw() >>> import kwplot >>> kwplot.autompl() >>> from matplotlib import pyplot as plt >>> kwplot.figure(fnum=2) >>> self.draw(setlim=True) """ import matplotlib as mpl from matplotlib.patches import Path from matplotlib import pyplot as plt import kwimage if ax is None: ax = plt.gca() color = list(kwimage.Color(color).as01()) data = self.data exterior = data['exterior'].data.tolist() exterior.append(exterior[0]) n = len(exterior) verts = [] verts.extend(exterior) codes = [Path.MOVETO] + ([Path.LINETO] * (n - 2)) + [Path.CLOSEPOLY] interiors = data.get('interiors', []) for hole in interiors: hole = hole.data.tolist() hole.append(hole[0]) n = len(hole) verts.extend(hole) codes += [Path.MOVETO] + ([Path.LINETO] * (n - 2)) + [Path.CLOSEPOLY] verts = np.array(verts) path = Path(verts, codes) kw = {} # TODO: # depricate border kwarg in favor of standard matplotlib args if border: kw['linewidth'] = linewidth try: edgecolor = list(kwimage.Color(border).as01()) except Exception: edgecolor = list(color) # hack to darken edgecolor[0] -= .1 edgecolor[1] -= .1 edgecolor[2] -= .1 edgecolor = [min(1, max(0, c)) for c in edgecolor] kw['edgecolor'] = edgecolor else: kw['linewidth'] = 0 patch = mpl.patches.PathPatch(path, alpha=alpha, facecolor=color, **kw) ax.add_patch(patch) if setlim: x1, y1, x2, y2 = self.to_boxes().to_tlbr().data[0] ax.set_xlim(x1, x2) ax.set_ylim(y1, y2) return patch
def draw_on(self, image, color='blue', fill=True, border=False, alpha=1.0, copy=False): """ Rasterizes a polygon on an image. See `draw` for a vectorized matplotlib version. Args: image (ndarray): image to raster polygon on. color (str | tuple): data coercable to a color fill (bool, default=True): draw the center mass of the polygon border (bool, default=False): draw the border of the polygon alpha (float, default=1.0): polygon transparency (setting alpha < 1 makes this function much slower). copy (bool, default=False): if False only copies if necessary Example: >>> # xdoc: +REQUIRES(module:kwplot) >>> from kwimage.structs.polygon import * # NOQA >>> self = Polygon.random(n_holes=1).scale(128) >>> image = np.zeros((128, 128), dtype=np.float32) >>> image = self.draw_on(image) >>> # xdoc: +REQUIRES(--show) >>> import kwplot >>> kwplot.autompl() >>> kwplot.imshow(image, fnum=1) Example: >>> import kwimage >>> color = 'blue' >>> self = kwimage.Polygon.random(n_holes=1).scale(128) >>> image = np.zeros((128, 128), dtype=np.float32) >>> # Test drawong on all channel + dtype combinations >>> im3 = np.random.rand(128, 128, 3) >>> im_chans = { >>> 'im3': im3, >>> 'im1': kwimage.convert_colorspace(im3, 'rgb', 'gray'), >>> 'im4': kwimage.convert_colorspace(im3, 'rgb', 'rgba'), >>> } >>> inputs = {} >>> for k, im in im_chans.items(): >>> inputs[k + '_01'] = (kwimage.ensure_float01(im.copy()), {'alpha': None}) >>> inputs[k + '_255'] = (kwimage.ensure_uint255(im.copy()), {'alpha': None}) >>> inputs[k + '_01_a'] = (kwimage.ensure_float01(im.copy()), {'alpha': 0.5}) >>> inputs[k + '_255_a'] = (kwimage.ensure_uint255(im.copy()), {'alpha': 0.5}) >>> outputs = {} >>> for k, v in inputs.items(): >>> im, kw = v >>> outputs[k] = self.draw_on(im, color=color, **kw) >>> # xdoc: +REQUIRES(--show) >>> import kwplot >>> kwplot.figure(fnum=2, doclf=True) >>> kwplot.autompl() >>> pnum_ = kwplot.PlotNums(nCols=2, nRows=len(inputs)) >>> for k in inputs.keys(): >>> kwplot.imshow(inputs[k][0], fnum=2, pnum=pnum_(), title=k) >>> kwplot.imshow(outputs[k], fnum=2, pnum=pnum_(), title=k) >>> kwplot.show_if_requested() """ import kwimage # return shape of contours to openCV contours dtype_fixer = _generic._consistent_dtype_fixer(image) # print('--- A') # print('image.dtype = {!r}'.format(image.dtype)) # print('image.max() = {!r}'.format(image.max())) # line_type = cv2.LINE_AA line_type = cv2.LINE_8 cv_contours = self._to_cv_countours() if alpha is None or alpha == 1.0: # image = kwimage.ensure_uint255(image) image = kwimage.atleast_3channels(image, copy=copy) rgba = kwimage.Color(color)._forimage(image) else: image = kwimage.ensure_float01(image) image = kwimage.ensure_alpha_channel(image) rgba = kwimage.Color(color, alpha=alpha)._forimage(image) # print('--- B') # print('image.dtype = {!r}'.format(image.dtype)) # print('image.max() = {!r}'.format(image.max())) # print('rgba = {!r}'.format(rgba)) if fill: if alpha is None or alpha == 1.0: # Modification happens inplace image = cv2.fillPoly(image, cv_contours, rgba, line_type, shift=0) else: orig = image.copy() mask = np.zeros_like(orig) mask = cv2.fillPoly(mask, cv_contours, rgba, line_type, shift=0) # TODO: could use add weighted image = kwimage.overlay_alpha_images(mask, orig) rgba = kwimage.Color(rgba)._forimage(image) # print('--- C') # print('image.dtype = {!r}'.format(image.dtype)) # print('image.max() = {!r}'.format(image.max())) # print('rgba = {!r}'.format(rgba)) if border or True: thickness = 4 contour_idx = -1 image = cv2.drawContours(image, cv_contours, contour_idx, rgba, thickness, line_type) # image = kwimage.ensure_float01(image)[..., 0:3] # print('--- D') # print('image.dtype = {!r}'.format(image.dtype)) # print('image.max() = {!r}'.format(image.max())) image = dtype_fixer(image, copy=False) return image
def draw(self, color='blue', ax=None, alpha=None, coord_axes=[1, 0], radius=1, setlim=False): """ Note: unlike other methods, the defaults assume x/y internal data Args: setlim (bool): if True ensures the limits of the axes contains the polygon coord_axes (Tuple): specify which image axes each coordinate dim corresponds to. For 2D images, if you are storing r/c data, set to [0,1], if you are storing x/y data, set to [1,0]. Returns: List[mpl.collections.PatchCollection]: drawn matplotlib objects Example: >>> # xdoc: +REQUIRES(module:kwplot) >>> from kwimage.structs.coords import * # NOQA >>> self = Coords.random(10) >>> # xdoc: +REQUIRES(--show) >>> self.draw(radius=3.0, setlim=True) >>> import kwplot >>> kwplot.autompl() >>> self.draw(radius=3.0) """ import matplotlib as mpl import kwimage from matplotlib import pyplot as plt if ax is None: ax = plt.gca() data = self.data if self.dim != 2: raise NotImplementedError('need 2d for mpl') # More grouped patches == more efficient runtime if alpha is None: alpha = [1.0] * len(data) elif not ub.iterable(alpha): alpha = [alpha] * len(data) ptcolors = [kwimage.Color(color, alpha=a).as01('rgba') for a in alpha] color_groups = ub.group_items(range(len(ptcolors)), ptcolors) default_centerkw = {'radius': radius, 'fill': True} centerkw = default_centerkw.copy() collections = [] for pcolor, idxs in color_groups.items(): yx_list = [row[coord_axes] for row in data[idxs]] patches = [ mpl.patches.Circle((x, y), ec=None, fc=pcolor, **centerkw) for y, x in yx_list ] col = mpl.collections.PatchCollection(patches, match_original=True) collections.append(col) ax.add_collection(col) if setlim: x1, y1 = self.data.min(axis=0) x2, y2 = self.data.max(axis=0) if setlim == 'grow': # only allow growth x1_, x2_ = ax.get_xlim() y1_, y2_ = ax.get_ylim() x1 = min(x1_, x1) x2 = max(x2_, x2) y1 = min(y1_, y1) y2 = max(y2_, y2) ax.set_xlim(x1, x2) ax.set_ylim(y1, y2) return collections
import kwplot import numpy as np import kwimage kwplot.autompl() f = 8 blank_key = np.zeros((64 * f, 54 * f, 3)) blank_key[:, :, :] = np.array(kwimage.Color('darkgray').as255())[None, None, :] blank_key[0:f * 2, :] = (3, 3, 3) blank_key[-f * 2:, :] = (3, 3, 3) blank_key[:, 0:f * 2] = (3, 3, 3) blank_key[:, -f * 2:] = (3, 3, 3) key = kwimage.draw_text_on_image(blank_key.copy(), text='!\n1', halign='center', valign='center', color='white') kwplot.imshow(key) tab_symbol = '->' left_rows = [] alt_text0 = [None, None, None, None, None, None, None, None] row_text0 = ['esc', 'F1', 'F2', 'F3', 'F4', 'F5', 'F6', 'caps'] left_rows += [(alt_text0, row_text0)] alt_text1 = [None, '~', '!', '@', '#', '$', '%', None] row_text1 = ['tab', '`', '1', '2', '3', '4', '5', 'win'] left_rows += [(alt_text1, row_text1)] alt_text2 = ['|', '?', None, None, None, None, None, None]
def draw_text_on_image(img, text, org, return_info=False, **kwargs): r""" Draws multiline text on an image using opencv Note: This function also exists in kwplot The image is modified inplace. If the image is non-contiguous then this returns a UMat instead of a ndarray, so be carefull with that. Args: img (ndarray): image to draw on (inplace) text (str): text to draw org (tuple): x, y location of the text string in the image. if bottomLeftOrigin=True this is the bottom-left corner of the text otherwise it is the top-left corner (default). return_info (bool, default=False): if True, also returns information about the positions the text was drawn on. **kwargs: color (tuple): default blue thickneess (int): defaults to 2 fontFace (int): defaults to cv2.FONT_HERSHEY_SIMPLEX fontScale (float): defaults to 1.0 valign (str, default='bottom'): either top, center, or bottom halign (str, default='left'): either left, center, or right References: https://stackoverflow.com/questions/27647424/ Example: >>> import kwimage >>> img = kwimage.grab_test_image(space='rgb') >>> img2 = kwimage.draw_text_on_image(img.copy(), 'FOOBAR', org=(0, 0), valign='top') >>> assert img2.shape == img.shape >>> assert np.any(img2 != img) >>> # xdoc: +REQUIRES(--show) >>> import kwplot >>> kwplot.autompl() >>> kwplot.imshow(img2, fontScale=10) >>> kwplot.show_if_requested() Example: >>> import kwimage >>> # Test valign >>> img = kwimage.grab_test_image(space='rgb', dsize=(500, 500)) >>> img2 = kwimage.draw_text_on_image(img, 'FOOBAR\nbazbiz\nspam', org=(0, 0), valign='top', border=2) >>> img2 = kwimage.draw_text_on_image(img, 'FOOBAR\nbazbiz\nspam', org=(150, 0), valign='center', border=2) >>> img2 = kwimage.draw_text_on_image(img, 'FOOBAR\nbazbiz\nspam', org=(300, 0), valign='bottom', border=2) >>> # Test halign >>> img2 = kwimage.draw_text_on_image(img, 'FOOBAR\nbazbiz\nspam', org=(250, 100), halign='right', border=2) >>> img2 = kwimage.draw_text_on_image(img, 'FOOBAR\nbazbiz\nspam', org=(250, 250), halign='center', border=2) >>> img2 = kwimage.draw_text_on_image(img, 'FOOBAR\nbazbiz\nspam', org=(250, 400), halign='left', border=2) >>> # xdoc: +REQUIRES(--show) >>> import kwplot >>> kwplot.autompl() >>> kwplot.imshow(img2, fontScale=10) >>> kwplot.show_if_requested() Example: >>> # Ensure the function works with float01 or uint255 images >>> import kwimage >>> img = kwimage.grab_test_image(space='rgb') >>> img = kwimage.ensure_float01(img) >>> img2 = kwimage.draw_text_on_image(img, 'FOOBAR\nbazbiz\nspam', org=(0, 0), valign='top', border=2) """ import kwimage if 'color' not in kwargs: kwargs['color'] = 'red' # Get the color that is compatible with the input image encoding if img is None: kwargs['color'] = kwimage.Color(kwargs['color']).as255() else: kwargs['color'] = kwimage.Color(kwargs['color'])._forimage(img) if 'thickness' not in kwargs: kwargs['thickness'] = 2 if 'fontFace' not in kwargs: kwargs['fontFace'] = cv2.FONT_HERSHEY_SIMPLEX if 'fontScale' not in kwargs: kwargs['fontScale'] = 1.0 if 'lineType' not in kwargs: kwargs['lineType'] = cv2.LINE_AA if 'bottomLeftOrigin' in kwargs: raise ValueError('Do not use bottomLeftOrigin, use valign instead') border = kwargs.pop('border', None) if border: # recursive call subkw = kwargs.copy() subkw['color'] = 'black' subkw.pop('return_info', None) basis = list(range(-border, border + 1)) for i, j in it.product(basis, basis): if i == 0 and j == 0: continue org = np.array(org) img = draw_text_on_image(img, text, org=org + [i, j], **subkw) valign = kwargs.pop('valign', None) halign = kwargs.pop('halign', None) getsize_kw = { k: kwargs[k] for k in ['fontFace', 'fontScale', 'thickness'] if k in kwargs } x0, y0 = list(map(int, org)) thickness = kwargs.get('thickness', 2) ypad = thickness + 4 lines = text.split('\n') # line_sizes2 = np.array([cv2.getTextSize(line, **getsize_kw) for line in lines]) # print('line_sizes2 = {!r}'.format(line_sizes2)) line_sizes = np.array( [cv2.getTextSize(line, **getsize_kw)[0] for line in lines]) line_org = [] y = y0 for w, h in line_sizes: next_y = y + (h + ypad) line_org.append((x0, y)) y = next_y line_org = np.array(line_org) # the absolute top and bottom position of text all_top_y = line_org[0, 1] all_bottom_y = (line_org[-1, 1] + line_sizes[-1, 1]) first_h = line_sizes[0, 1] total_h = (all_bottom_y - all_top_y) total_w = line_sizes.T[0].max() if valign is not None: if valign == 'bottom': # This is the default for the one-line case # in the multiline case we need to subtract the total # height of all lines but the first to ensure the last # line is on the bottom. line_org[:, 1] -= (total_h - first_h) elif valign == 'center': # Change from bottom to center line_org[:, 1] += first_h - total_h // 2 elif valign == 'top': # Because bottom is the default we just need to add height of the # first line. line_org[:, 1] += first_h else: raise KeyError(valign) if halign is not None: if halign == 'left': # This is the default case, no modification needed pass elif halign == 'center': # When the x-orgin should be the center, subtract half of # the line width to get the leftmost point. line_org[:, 0] = x0 - (line_sizes[:, 0] / 2) elif halign == 'right': # The x-orgin should be the rightmost point, subtract # the width of each line to find the leftmost point. line_org[:, 0] = x0 - line_sizes[:, 0] else: raise KeyError(halign) if img is None: # if image is unspecified allocate just enough space for text total_w = (line_org.T[0] + line_sizes.T[0]).max() # TODO: does not account for origin offset img = np.zeros((total_h + thickness, total_w, 3), dtype=np.uint8) for i, line in enumerate(lines): (x, y) = line_org[i] img = cv2.putText(img, line, (x, y), **kwargs) if return_info: info = { 'line_org': line_org, 'line_sizes': line_sizes, } return img, info else: return img
def draw_vector_field(image, dx, dy, stride=0.02, thresh=0.0, scale=1.0, alpha=1.0, color='red', thickness=1, tipLength=0.1, line_type='aa'): """ Create an image representing a 2D vector field. Args: image (ndarray): image to draw on dx (ndarray): grid of vector x components dy (ndarray): grid of vector y components stride (int | float): sparsity of vectors, int specifies stride step in pixels, a float specifies it as a percentage. thresh (float): only plot vectors with magnitude greater than thres scale (float): multiply magnitude for easier visualization alpha (float): alpha value for vectors. Non-vector regions receive 0 alpha (if False, no alpha channel is used) color (str | tuple | kwimage.Color): RGB color of the vectors thickness (int, default=1): thickness of arrows tipLength (float, default=0.1): fraction of line length line_type (int): either cv2.LINE_4, cv2.LINE_8, or cv2.LINE_AA Returns: ndarray[float32]: The image with vectors overlaid. If image=None, then an rgb/a image is created and returned. Example: >>> import kwimage >>> width, height = 512, 512 >>> image = kwimage.grab_test_image(dsize=(width, height)) >>> x, y = np.meshgrid(np.arange(height), np.arange(width)) >>> dx, dy = x - width / 2, y - height / 2 >>> radians = np.arctan2(dx, dy) >>> mag = np.sqrt(dx ** 2 + dy ** 2) + 1e-3 >>> dx, dy = dx / mag, dy / mag >>> img = kwimage.draw_vector_field(image, dx, dy, scale=10, alpha=False) >>> # xdoctest: +REQUIRES(--show) >>> import kwplot >>> kwplot.autompl() >>> kwplot.imshow(img) >>> kwplot.show_if_requested() """ import cv2 import kwimage if image is None: # Create a default image image = np.zeros(dx.shape + (3, ), dtype=np.uint8) # image = kwimage.atleast_3channels(image) color = kwimage.Color(color)._forimage(image) line_type_lookup = {'aa': cv2.LINE_AA} line_type = line_type_lookup.get(line_type, line_type) height, width = dx.shape[0:2] x_grid = np.arange(0, width, 1) y_grid = np.arange(0, height, 1) # Vector locations and directions X, Y = np.meshgrid(x_grid, y_grid) U, V = dx, dy XYUV = [X, Y, U, V] if isinstance(stride, float): if stride < 0 or stride > 1: raise ValueError('Floating point strides must be between 0 and 1') stride = int(np.ceil(stride * min(width, height))) # stride the points if stride is not None and stride > 1: XYUV = [a[::stride, ::stride] for a in XYUV] # flatten the points XYUV = [a.ravel() for a in XYUV] # Filter out points with low magnitudes if thresh is not None and thresh > 0: M = np.sqrt((XYUV[2]**2) + (XYUV[3]**2)).ravel() XYUV = np.array(XYUV) flags = M > thresh XYUV = [a[flags] for a in XYUV] # Adjust vector magnitude for visibility if scale is not None: XYUV[2] *= scale XYUV[3] *= scale if alpha is not None and alpha is not False and alpha != 1: raise NotImplementedError for (x, y, u, v) in zip(*XYUV): pt1 = (int(x), int(y)) pt2 = tuple(map(int, map(np.round, (x + u, y + v)))) cv2.arrowedLine(image, pt1, pt2, color=color, thickness=thickness, tipLength=tipLength, line_type=line_type) if isinstance(alpha, np.ndarray): # Alpha specified as explicit numpy array image = kwimage.ensure_float01(image) image = kwimage.ensure_alpha_channel(image) image[:, :, 3] = alpha elif alpha is not False and alpha is not None: # Alpha specified as a scale factor image = kwimage.ensure_float01(image) image = kwimage.ensure_alpha_channel(image) # image[:, :, 3] = (image[:, :, 0:3].sum(axis=2) > 0) * alpha image[:, :, 3] = image[:, :, 0:3].sum(axis=2) * alpha return image
def make_vector_field(dx, dy, stride=0.02, thresh=0.0, scale=1.0, alpha=1.0, color='red', thickness=1, tipLength=0.1, line_type='aa'): """ Create an image representing a 2D vector field. Args: dx (ndarray): grid of vector x components dy (ndarray): grid of vector y components stride (int | float): sparsity of vectors, int specifies stride step in pixels, a float specifies it as a percentage. thresh (float): only plot vectors with magnitude greater than thres scale (float): multiply magnitude for easier visualization alpha (float): alpha value for vectors. Non-vector regions receive 0 alpha (if False, no alpha channel is used) color (str | tuple | kwimage.Color): RGB color of the vectors thickness (int, default=1): thickness of arrows tipLength (float, default=0.1): fraction of line length line_type (int): either cv2.LINE_4, cv2.LINE_8, or cv2.LINE_AA Returns: ndarray[float32]: vec_img: an rgb/rgba image in 0-1 space SeeAlso: kwimage.overlay_alpha_images DEPRECATED USE: draw_vector_field instead Example: >>> x, y = np.meshgrid(np.arange(512), np.arange(512)) >>> dx, dy = x - 256.01, y - 256.01 >>> radians = np.arctan2(dx, dy) >>> mag = np.sqrt(dx ** 2 + dy ** 2) >>> dx, dy = dx / mag, dy / mag >>> img = make_vector_field(dx, dy, scale=10, alpha=False) >>> # xdoctest: +REQUIRES(--show) >>> import kwplot >>> kwplot.autompl() >>> kwplot.imshow(img) >>> kwplot.show_if_requested() """ import warnings warnings.warn('Deprecated, use draw_vector_field instead', DeprecationWarning) import cv2 import kwimage color = kwimage.Color(color).as255('rgb') vecmask = np.zeros(dx.shape + (3, ), dtype=np.uint8) line_type_lookup = {'aa': cv2.LINE_AA} line_type = line_type_lookup.get(line_type, line_type) width = dx.shape[1] height = dy.shape[0] x_grid = np.arange(0, width, 1) y_grid = np.arange(0, height, 1) # Vector locations and directions X, Y = np.meshgrid(x_grid, y_grid) U, V = dx, dy XYUV = [X, Y, U, V] if isinstance(stride, float): if stride < 0 or stride > 1: raise ValueError('Floating point strides must be between 0 and 1') stride = int(np.ceil(stride * min(width, height))) # stride the points if stride is not None and stride > 1: XYUV = [a[::stride, ::stride] for a in XYUV] # flatten the points XYUV = [a.ravel() for a in XYUV] # Filter out points with low magnitudes if thresh is not None and thresh > 0: M = np.sqrt((XYUV[2]**2) + (XYUV[3]**2)).ravel() XYUV = np.array(XYUV) flags = M > thresh XYUV = [a[flags] for a in XYUV] # Adjust vector magnitude for visibility if scale is not None: XYUV[2] *= scale XYUV[3] *= scale for (x, y, u, v) in zip(*XYUV): pt1 = (int(x), int(y)) pt2 = tuple(map(int, map(np.round, (x + u, y + v)))) cv2.arrowedLine(vecmask, pt1, pt2, color=color, thickness=thickness, tipLength=tipLength, line_type=line_type) vecmask = kwimage.ensure_float01(vecmask) if isinstance(alpha, np.ndarray): # Alpha specified as explicit numpy array vecmask = kwimage.ensure_alpha_channel(vecmask) vecmask[:, :, 3] = alpha elif alpha is not False and alpha is not None: # Alpha specified as a scale factor vecmask = kwimage.ensure_alpha_channel(vecmask) # vecmask[:, :, 3] = (vecmask[:, :, 0:3].sum(axis=2) > 0) * alpha vecmask[:, :, 3] = vecmask[:, :, 0:3].sum(axis=2) * alpha return vecmask
def make_legend_img(label_to_color, dpi=96, shape=(200, 200), mode='line', transparent=False): """ Makes an image of a categorical legend Args: label_to_color (Dict[str, Color]): mapping from string label to the color. CommandLine: xdoctest -m kwplot.mpl_make make_legend_img --show Example: >>> # xdoctest: +REQUIRES(module:kwplot) >>> import kwplot >>> import kwimage >>> label_to_color = { >>> 'blue': kwimage.Color('blue').as01(), >>> 'red': kwimage.Color('red').as01(), >>> 'green': 'green', >>> 'yellow': 'yellow', >>> 'orangered': 'orangered', >>> } >>> img = make_legend_img(label_to_color, transparent=True) >>> # xdoctest: +REQUIRES(--show) >>> kwplot.autompl() >>> kwplot.imshow(img) >>> kwplot.show_if_requested() """ import kwplot import kwimage plt = kwplot.autoplt() def append_phantom_legend_label(label, color, type_='line', alpha=1.0, ax=None): if ax is None: ax = plt.gca() _phantom_legend_list = getattr(ax, '_phantom_legend_list', None) if _phantom_legend_list is None: _phantom_legend_list = [] setattr(ax, '_phantom_legend_list', _phantom_legend_list) if type_ == 'line': phantom_actor = plt.Line2D((0, 0), (1, 1), color=color, label=label, alpha=alpha) else: phantom_actor = plt.Circle((0, 0), 1, fc=color, label=label, alpha=alpha) _phantom_legend_list.append(phantom_actor) fig = plt.figure(dpi=dpi) w, h = shape[1] / dpi, shape[0] / dpi fig.set_size_inches(w, h) # ax = fig.add_subplot('111') ax = fig.add_subplot(1, 1, 1) for label, color in label_to_color.items(): color = kwimage.Color(color).as01() append_phantom_legend_label(label, color, type_=mode, ax=ax) _phantom_legend_list = getattr(ax, '_phantom_legend_list', None) if _phantom_legend_list is None: _phantom_legend_list = [] setattr(ax, '_phantom_legend_list', _phantom_legend_list) ax.legend(handles=_phantom_legend_list) ax.grid(False) ax.get_xaxis().set_visible(False) ax.get_yaxis().set_visible(False) plt.axis('off') legend_img = render_figure_to_image(fig, dpi=dpi, transparent=transparent) legend_img = kwimage.convert_colorspace(legend_img, src_space='bgr', dst_space='rgb') legend_img = crop_border_by_color(legend_img) plt.close(fig) return legend_img
def draw(self, color='blue', ax=None, alpha=None, radius=1, **kwargs): """ TODO: can use kwplot.draw_points Example: >>> # xdoc: +REQUIRES(module:kwplot) >>> from kwimage.structs.points import * # NOQA >>> pts = Points.random(10) >>> # xdoc: +REQUIRES(--show) >>> pts.draw(radius=0.01) >>> from kwimage.structs.points import * # NOQA >>> self = Points.random(10, classes=['a', 'b', 'c']) >>> self.draw(radius=0.01, color='classes') """ import kwimage import matplotlib as mpl from matplotlib import pyplot as plt if ax is None: ax = plt.gca() xy = self.data['xy'].data.reshape(-1, 2) # More grouped patches == more efficient runtime if alpha is None: alpha = [1.0] * len(xy) elif not ub.iterable(alpha): alpha = [alpha] * len(xy) if color == 'distinct': colors = kwimage.Color.distinct(len(alpha)) elif color == 'classes': # TODO: read colors from categories if they exist try: class_idxs = self.data['class_idxs'] cls_colors = kwimage.Color.distinct(len(self.meta['classes'])) except KeyError: raise Exception('cannot draw class colors without class_idxs and classes') _keys, _vals = kwarray.group_indices(class_idxs) colors = list(ub.take(cls_colors, class_idxs)) else: colors = [color] * len(alpha) ptcolors = [kwimage.Color(c, alpha=a).as01('rgba') for c, a in zip(colors, alpha)] color_groups = ub.group_items(range(len(ptcolors)), ptcolors) circlekw = { 'radius': radius, 'fill': True, 'ec': None, } if 'fc' in kwargs: warnings.warning( 'Warning: specifying fc to Points.draw overrides ' 'the color argument. Use color instead') circlekw.update(kwargs) fc = circlekw.pop('fc', None) # hack collections = [] for pcolor, idxs in color_groups.items(): # hack for fc if fc is not None: pcolor = fc patches = [ mpl.patches.Circle((x, y), fc=pcolor, **circlekw) for x, y in xy[idxs] ] col = mpl.collections.PatchCollection(patches, match_original=True) collections.append(col) ax.add_collection(col) return collections
def draw_on(self, image, color='white', radius=None, copy=False): """ CommandLine: xdoctest -m ~/code/kwimage/kwimage/structs/points.py Points.draw_on --show Example: >>> # xdoc: +REQUIRES(module:kwplot) >>> from kwimage.structs.points import * # NOQA >>> s = 128 >>> image = np.zeros((s, s)) >>> self = Points.random(10).scale(s) >>> image = self.draw_on(image) >>> # xdoc: +REQUIRES(--show) >>> import kwplot >>> kwplot.figure(fnum=1, doclf=True) >>> kwplot.autompl() >>> kwplot.imshow(image) >>> self.draw(radius=3, alpha=.5) >>> kwplot.show_if_requested() Example: >>> # xdoc: +REQUIRES(module:kwplot) >>> from kwimage.structs.points import * # NOQA >>> s = 128 >>> image = np.zeros((s, s)) >>> self = Points.random(10).scale(s) >>> image = self.draw_on(image, radius=3, color='distinct') >>> # xdoc: +REQUIRES(--show) >>> import kwplot >>> kwplot.figure(fnum=1, doclf=True) >>> kwplot.autompl() >>> kwplot.imshow(image) >>> self.draw(radius=3, alpha=.5, color='classes') >>> kwplot.show_if_requested() Example: >>> import kwimage >>> s = 32 >>> self = kwimage.Points.random(10).scale(s) >>> color = 'blue' >>> # Test drawong on all channel + dtype combinations >>> im3 = np.zeros((s, s, 3), dtype=np.float32) >>> im_chans = { >>> 'im3': im3, >>> 'im1': kwimage.convert_colorspace(im3, 'rgb', 'gray'), >>> 'im4': kwimage.convert_colorspace(im3, 'rgb', 'rgba'), >>> } >>> inputs = {} >>> for k, im in im_chans.items(): >>> inputs[k + '_01'] = (kwimage.ensure_float01(im.copy()), {'radius': None}) >>> inputs[k + '_255'] = (kwimage.ensure_uint255(im.copy()), {'radius': None}) >>> outputs = {} >>> for k, v in inputs.items(): >>> im, kw = v >>> outputs[k] = self.draw_on(im, color=color, **kw) >>> # xdoc: +REQUIRES(--show) >>> import kwplot >>> kwplot.figure(fnum=2, doclf=True) >>> kwplot.autompl() >>> pnum_ = kwplot.PlotNums(nCols=2, nRows=len(inputs)) >>> for k in inputs.keys(): >>> kwplot.imshow(inputs[k][0], fnum=2, pnum=pnum_(), title=k) >>> kwplot.imshow(outputs[k], fnum=2, pnum=pnum_(), title=k) >>> kwplot.show_if_requested() """ import kwimage dtype_fixer = _generic._consistent_dtype_fixer(image) if radius is None: if color == 'distinct': raise NotImplementedError image = kwimage.atleast_3channels(image) image = kwimage.ensure_float01(image, copy=copy) # value = kwimage.Color(color).as01() value = kwimage.Color(color)._forimage(image) image = self.data['xy'].fill( image, value, coord_axes=[1, 0], interp='bilinear') else: import cv2 image = kwimage.atleast_3channels(image, copy=copy) # note: ellipse has a different return type (UMat) and does not # work inplace if the input is not contiguous. image = np.ascontiguousarray(image) xy_pts = self.data['xy'].data.reshape(-1, 2) if color == 'distinct': colors = kwimage.Color.distinct(len(xy_pts)) elif color == 'classes': # TODO: read colors from categories if they exist class_idxs = self.data['class_idxs'] _keys, _vals = kwarray.group_indices(class_idxs) cls_colors = kwimage.Color.distinct(len(self.meta['classes'])) colors = list(ub.take(cls_colors, class_idxs)) colors = [kwimage.Color(c)._forimage(image) for c in colors] # if image.dtype.kind == 'f': # colors = [kwimage.Color(c).as01() for c in colors] # else: # colors = [kwimage.Color(c).as255() for c in colors] else: value = kwimage.Color(color)._forimage(image) colors = [value] * len(xy_pts) # image = kwimage.ensure_float01(image) for xy, color_ in zip(xy_pts, colors): # center = tuple(map(int, xy.tolist())) center = tuple(xy.tolist()) axes = (radius / 2, radius / 2) center = tuple(map(int, center)) axes = tuple(map(int, axes)) # print('center = {!r}'.format(center)) # print('axes = {!r}'.format(axes)) cv2.ellipse(image, center, axes, angle=0.0, startAngle=0.0, endAngle=360.0, color=color_, thickness=-1) image = dtype_fixer(image, copy=False) return image