示例#1
0
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
示例#2
0
    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
示例#3
0
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
示例#4
0
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
示例#5
0
    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
示例#6
0
    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
示例#7
0
    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
示例#8
0
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]
示例#9
0
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
示例#10
0
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
示例#11
0
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
示例#12
0
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
示例#13
0
    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
示例#14
0
    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