Beispiel #1
0
    def _package_info(self, cname, data, mask, kpts, xy_offset, dims,
                      newstyle):
        """ packages data from _from_elem into coco-like annotation """
        import kwimage

        if newstyle:
            segmentation = kwimage.Mask(mask, 'c_mask').to_multi_polygon()
        else:
            segmentation = kwimage.Mask(mask, 'c_mask').to_array_rle()

        if xy_offset is not None:
            segmentation = segmentation.translate(xy_offset, output_dims=dims)
            kpts = kpts.translate(xy_offset, output_dims=dims)

        if not newstyle:
            segmentation = segmentation.to_mask().to_bytes_rle()
            segmentation.data['counts'] = segmentation.data['counts'].decode(
                'utf8')

        if newstyle:
            segmentation = segmentation.to_coco('new')
            keypoints = kpts.to_coco('new')
        else:
            # old style keypoints
            import kwarray
            keypoints = kwarray.ArrayAPI.tolist(kpts.to_coco('orig'))
            segmentation = segmentation.data

        info = {
            'name': cname,
            'data': data,
            'segmentation': segmentation,
            'keypoints': keypoints,
        }
        return info
Beispiel #2
0
    def to_mask(self, dims=None):
        """
        Convert this polygon to a mask

        TODO:
            - [ ] currently not efficient

        Example:
            >>> from kwimage.structs.polygon import *  # NOQA
            >>> self = Polygon.random(n_holes=1).scale(128)
            >>> mask = self.to_mask((128, 128))
            >>> # xdoc: +REQUIRES(--show)
            >>> import kwplot
            >>> kwplot.autompl()
            >>> kwplot.figure(fnum=1, doclf=True)
            >>> mask.draw(color='blue')
            >>> mask.to_multi_polygon().draw(color='red', alpha=.5)
        """
        import kwimage
        if dims is None:
            raise ValueError('Must specify output raster dimensions')
        c_mask = np.zeros(dims, dtype=np.uint8)
        value = 1
        self.fill(c_mask, value)
        mask = kwimage.Mask(c_mask, 'c_mask')
        return mask
Beispiel #3
0
    def to_mask(self, dims=None):
        """
        Returns a mask object indication regions occupied by this multipolygon

        Example:
            >>> from kwimage.structs.polygon import *  # NOQA
            >>> s = 100
            >>> self = MultiPolygon.random(rng=0).scale(s)
            >>> dims = (s, s)
            >>> mask = self.to_mask(dims)

            >>> # xdoc: +REQUIRES(--show)
            >>> import kwplot
            >>> kwplot.autompl()
            >>> kwplot.figure(fnum=1, doclf=True)
            >>> from matplotlib import pyplot as pl
            >>> ax = plt.gca()
            >>> ax.set_xlim(0, s)
            >>> ax.set_ylim(0, s)
            >>> self.draw(color='red', alpha=.4)
            >>> mask.draw(color='blue', alpha=.4)
        """
        import kwimage
        if dims is None:
            raise ValueError('Must specify output raster dimensions')
        c_mask = np.zeros(dims, dtype=np.uint8)
        for p in self.data:
            if p is not None:
                p.fill(c_mask, value=1)
        mask = kwimage.Mask(c_mask, 'c_mask')
        return mask
Beispiel #4
0
    def _todo_refactor_geometric_info(self, cname, xy_offset, dims):
        """

        This function is used to populate kpts and sseg information in the
        autogenerated coco dataset before rendering. It is redundant with other
        functionality.

        TODO: rectify with _from_elem

        Example:
            >>> self = CategoryPatterns.coerce(['superstar'])
            >>> dims = (64, 64)
            >>> cname = 'superstar'
            >>> xy_offset = None
            >>> self._todo_refactor_geometric_info(cname, xy_offset, dims)
        """
        elem_func = self._category_to_elemfunc[cname]
        x = max(dims)
        # x = int(2 ** np.floor(np.log2(x)))
        elem, kpts_yx = elem_func(x)

        size = tuple(map(int, dims[::-1]))

        if kpts_yx is not None:
            kp_catnames = list(kpts_yx.keys())
            xy = np.array([yx[::-1] for yx in kpts_yx.values()])
            kpts = kwimage.Points(xy=xy,
                                  class_idxs=np.arange(len(xy)),
                                  classes=kp_catnames)
            sf = np.array(size) / np.array(elem.shape[0:2][::-1])
            kpts = kpts.scale(sf)
        else:
            kpts = None
            # center
            kpts = kwimage.Points(xy=np.array([]))
            # kpts = kwimage.Points(xy=np.array([[.5, .5]]))
            kpts = kpts.scale(size)

        template = cv2.resize(elem, size).astype(np.float32)
        mask = (template > 0.05).astype(np.uint8)
        sseg = kwimage.Mask(mask, 'c_mask').to_multi_polygon()

        if xy_offset is not None:
            sseg = sseg.translate(xy_offset, output_dims=dims)
            kpts = kpts.translate(xy_offset, output_dims=dims)

        info = {
            'segmentation': sseg,
            'kpts': kpts,
        }
        return info
Beispiel #5
0
    def main(cls, cmdline=True, **kw):
        """
        CommandLine:
            xdoctest -m make_coco_from_masks.py MakeCocoFromMasksCLI.main

        Example:
            >>> import ubelt as ub
            >>> cls = MakeCocoFromMasksCLI
            >>> cmdline = False
            >>> dpath = _make_intmask_demodata()
            >>> kw = {'src': join(dpath, '*.png'), 'dst': 'masks.mscoco.json'}
            >>> fpath = cls.main(cmdline, **kw)
            >>> # Check validity
            >>> import kwcoco
            >>> dset = kwcoco.CocoDataset(fpath)
            >>> dset._check_integrity()
            >>> # Ensure we can convert to kwimage and access segmentations
            >>> dset.annots().detections.data['segmentations'][0].data
        """
        import kwcoco
        config = cls.CLIConfig(kw, cmdline=cmdline)
        print('config = {}'.format(ub.repr2(dict(config), nl=2)))
        serialization_method = config['serialization']

        # Initialize an empty COCO dataset
        coco_dset = kwcoco.CocoDataset()

        # Path to each mask object
        mask_fpaths = config['src']
        for mask_fpath in mask_fpaths:
            # I assume each mask corresponds to a single image, but I dont know
            # what those images should be at the moment. TODO: if this is going
            # to be a real script, then we should find a nice way of specifying
            # the correspondences between masks and the images to which they
            # belong. For now, I'm going to use the mask itself as a dummy
            # value.

            img_fpath = mask_fpath
            image_id = coco_dset.add_image(file_name=img_fpath)

            # Parse the mask file, and add each object as a new annotation
            multi_mask = kwimage.imread(mask_fpath)
            # I recall there is a better opencv of splitting these sort of
            # masks up into binary masks, maybe it was a connected-component
            # function? I guess it depends if you want disconnected objects
            # represented as separte annotations or not.  I'm just going to do
            # the naive thing for now.
            obj_idxs = np.setdiff1d(np.unique(multi_mask), [0])
            for obj_idx in obj_idxs:
                bin_data = (multi_mask == obj_idx).astype(np.uint8)

                # Create a kwimage object which has nice `to_coco` methods
                bin_mask = kwimage.Mask(bin_data, format='c_mask')

                # We can either save in our coco file as a raster RLE style
                # mask, or we can use a vector polygon style mask. Either of
                # the resulting coco_sseg objects is a valid value for an
                # annotation's "segmentation" field.
                if serialization_method == 'raster':
                    sseg = bin_mask
                    coco_sseg = sseg.to_coco(style='new')
                elif serialization_method == 'vector':
                    bin_poly = bin_mask.to_multi_polygon()
                    sseg = bin_poly
                    coco_sseg = sseg.to_coco(style='new')

                # Now we add this segmentation to the coco dataset as a new
                # annotation. The annotation needs to know which image it
                # belongs to, and ideally it has a category and a bounding box
                # as well.

                # We can make up a dummy category (note that ensure_category
                # will not fail if there is a duplicate entry but add_category
                # will)
                category_id = coco_dset.ensure_category(
                    'class_{}'.format(obj_idx))

                # We can use the kwimage sseg object to get the bounding box
                # FIXME: apparently the MultiPolygon object doesnt implement
                # `to_boxes`, at the moment, so force an inefficient conversion
                # back to a mask as a hack and use its to_boxes method.
                # Technically, the bounding box should not be required, but its
                # a good idea to always include it.
                bbox = list(
                    sseg.to_mask(
                        dims=multi_mask.shape).to_boxes().to_coco())[0]

                METHOD1 = False
                if METHOD1:
                    # We could just add it diretly like this
                    # FIXME: it should be ok to add an annotation without a
                    # category, but it seems like a recent change in kwcoco has
                    # broken that. That will be fixed in the future.
                    annot_id = coco_dset.add_annotation(
                        image_id=image_id,
                        category_id=category_id,
                        bbox=bbox,
                        segmentation=coco_sseg)
                else:
                    # But lets do it as if we were adding a segmentation to an
                    # existing dataset. In this case we access the
                    # CocoDataset's annotation index structure.
                    #
                    # First add the basic annotation
                    annot_id = coco_dset.add_annotation(
                        image_id=image_id, category_id=category_id, bbox=bbox)
                    # Then use the annotation id to look up its coco-dictionary
                    # representation and simply add the segmentation field
                    ann = coco_dset.anns[annot_id]
                    ann['segmentation'] = coco_sseg

        # You dont have to set the fpath attr, but I tend to like it
        coco_dset.fpath = config['dst']
        print('Writing to fpath = {}'.format(ub.repr2(coco_dset.fpath, nl=1)))
        coco_dset.dump(coco_dset.fpath, newlines=True)
        return coco_dset.fpath
Beispiel #6
0
def convert_camvid_raw_to_coco(camvid_raw_info):
    """
    Converts the raw camvid format to an MSCOCO based format, ( which lets use
    use kwcoco's COCO backend).

    Example:
        >>> # xdoctest: +REQUIRES(--download)
        >>> camvid_raw_info = grab_raw_camvid()
        >>> # test with a reduced set of data
        >>> del camvid_raw_info['img_paths'][2:]
        >>> del camvid_raw_info['mask_paths'][2:]
        >>> dset = convert_camvid_raw_to_coco(camvid_raw_info)
        >>> # xdoctest: +REQUIRES(--show)
        >>> import kwplot
        >>> plt = kwplot.autoplt()
        >>> kwplot.figure(fnum=1, pnum=(1, 2, 1))
        >>> dset.show_image(gid=1)
        >>> kwplot.figure(fnum=1, pnum=(1, 2, 2))
        >>> dset.show_image(gid=2)
    """
    import re
    import kwimage
    import kwcoco
    print('Converting CamVid to MS-COCO format')

    dset_root, img_paths, label_path, mask_paths = ub.take(
        camvid_raw_info,
        'dset_root, img_paths, label_path, mask_paths'.split(', '))

    img_infos = {
        'img_fname': img_paths,
        'mask_fname': mask_paths,
    }
    keys = list(img_infos.keys())
    next_vals = list(zip(*img_infos.values()))
    image_items = [{k: v for k, v in zip(keys, vals)} for vals in next_vals]

    dataset = {
        'img_root': dset_root,
        'images': [],
        'categories': [],
        'annotations': [],
    }

    lines = ub.readfrom(label_path).split('\n')
    lines = [line for line in lines if line]
    for line in lines:
        color_text, name = re.split('\t+', line)
        r, g, b = map(int, color_text.split(' '))
        color = (r, g, b)

        # Parse the special camvid format
        cid = (r << 16) + (g << 8) + (b << 0)
        cat = {
            'id': cid,
            'name': name,
            'color': color,
        }
        dataset['categories'].append(cat)

    for gid, img_item in enumerate(image_items, start=1):
        img = {
            'id': gid,
            'file_name': img_item['img_fname'],
            # nonstandard image field
            'segmentation': img_item['mask_fname'],
        }
        dataset['images'].append(img)

    dset = kwcoco.CocoDataset(dataset)
    dset.rename_categories({'Void': 'background'})

    assert dset.name_to_cat['background']['id'] == 0
    dset.name_to_cat['background'].setdefault('alias', []).append('Void')

    if False:
        _define_camvid_class_hierarcy(dset)

    if 1:
        # TODO: Binarize CCs (and efficiently encode if possible)
        import numpy as np

        bad_info = []
        once = False

        # Add images
        dset.remove_annotations(list(dset.index.anns.keys()))
        for gid, img in ub.ProgIter(dset.imgs.items(),
                                    desc='parse label masks'):
            mask_fpath = join(dset_root, img['segmentation'])

            rgb_mask = kwimage.imread(mask_fpath, space='rgb')
            r, g, b = rgb_mask.T.astype(np.int64)
            cid_mask = np.ascontiguousarray(rgb_to_cid(r, g, b).T)

            cids = set(np.unique(cid_mask)) - {0}

            for cid in cids:
                if cid not in dset.cats:
                    if gid == 618:
                        # Handle a known issue with image 618
                        c_mask = (cid == cid_mask).astype(np.uint8)
                        total_bad = c_mask.sum()
                        if total_bad < 32:
                            if not once:
                                print(
                                    'gid 618 has a few known bad pixels, ignoring them'
                                )
                                once = True
                            continue
                        else:
                            raise Exception('more bad pixels than expected')
                    else:
                        raise Exception(
                            'UNKNOWN cid = {!r} in gid={!r}'.format(cid, gid))

                    # bad_rgb = cid_to_rgb(cid)
                    # print('bad_rgb = {!r}'.format(bad_rgb))
                    # print('WARNING UNKNOWN cid = {!r} in gid={!r}'.format(cid, gid))
                    # bad_info.append({
                    #     'gid': gid,
                    #     'cid': cid,
                    # })
                else:
                    ann = {
                        'category_id': cid,
                        'image_id': gid
                        # 'segmentation': mask.to_coco()
                    }
                    assert cid in dset.cats
                    c_mask = (cid == cid_mask).astype(np.uint8)
                    mask = kwimage.Mask(c_mask, 'c_mask')

                    box = kwimage.Boxes([mask.get_xywh()], 'xywh')
                    # box = mask.to_boxes()

                    ann['bbox'] = ub.peek(box.to_coco())
                    ann['segmentation'] = mask.to_coco()
                    dset.add_annotation(**ann)

        if 0:
            bad_cids = [i['cid'] for i in bad_info]
            print(sorted([c['color'] for c in dataset['categories']]))
            print(sorted(set([cid_to_rgb(i['cid']) for i in bad_info])))

            gid = 618
            img = dset.imgs[gid]
            mask_fpath = join(dset_root, img['segmentation'])
            rgb_mask = kwimage.imread(mask_fpath, space='rgb')
            r, g, b = rgb_mask.T.astype(np.int64)
            cid_mask = np.ascontiguousarray(rgb_to_cid(r, g, b).T)
            cid_hist = ub.dict_hist(cid_mask.ravel())

            bad_cid_hist = {}
            for cid in bad_cids:
                bad_cid_hist[cid] = cid_hist.pop(cid)

            import kwplot
            kwplot.autompl()
            kwplot.imshow(rgb_mask)

    if 0:
        import kwplot
        plt = kwplot.autoplt()
        plt.clf()
        dset.show_image(1)

        import xdev
        gid_list = list(dset.imgs)
        for gid in xdev.InteractiveIter(gid_list):
            dset.show_image(gid)
            xdev.InteractiveIter.draw()

    dset._build_index()
    dset._build_hashid()
    return dset
Beispiel #7
0
def _coerce_coco_segmentation(data, dims=None):
    """
    Attempts to auto-inspect the format of segmentation data

    Args:
        data : the data to coerce

             2D-C-ndarray -> C_MASK
             2D-F-ndarray -> F_MASK

             Dict(counts=bytes) -> BYTES_RLE
             Dict(counts=ndarray) -> ARRAY_RLE

             Dict(exterior=ndarray) -> ARRAY_RLE

             # List[List[int]] -> Polygon
             List[int] -> Polygon
             List[Dict] -> MultPolygon

        dims (Tuple): required for certain formats like polygons
            height / width of the source image

    Returns:
        Mask | Polygon | MultiPolygon - depending on which is appropriate

    Example:
        >>> segmentation = {'size': [5, 9], 'counts': ';?1B10O30O4'}
        >>> dims = (9, 5)
        >>> raw_mask = (np.random.rand(32, 32) > .5).astype(np.uint8)
        >>> _coerce_coco_segmentation(segmentation)
        >>> _coerce_coco_segmentation(raw_mask)

        >>> coco_polygon = [
        >>>     np.array([[3, 0],[2, 1],[2, 4],[4, 4],[4, 3],[7, 0]]),
        >>>     np.array([[2, 1],[2, 2],[4, 2],[4, 1]]),
        >>> ]
        >>> self = _coerce_coco_segmentation(coco_polygon, dims)
        >>> print('self = {!r}'.format(self))
        >>> coco_polygon = [
        >>>     np.array([[3, 0],[2, 1],[2, 4],[4, 4],[4, 3],[7, 0]]),
        >>> ]
        >>> self = _coerce_coco_segmentation(coco_polygon, dims)
        >>> print('self = {!r}'.format(self))
    """
    import kwimage
    from kwimage.structs.mask import MaskFormat
    if isinstance(data, np.ndarray):
        # INPUT TYPE: RAW MASK
        if dims is not None:
            assert dims == data.shape[0:2]
        if data.flags['F_CONTIGUOUS']:
            self = kwimage.Mask(data, MaskFormat.F_MASK)
        else:
            self = kwimage.Mask(data, MaskFormat.C_MASK)
    elif isinstance(data, dict):
        if 'counts' in data:
            # INPUT TYPE: COCO RLE DICTIONARY
            if dims is not None:
                data_shape = data.get(
                    'dims', data.get('shape', data.get('size', None)))
                if data_shape is None:
                    data['shape'] = data_shape
                else:
                    assert tuple(map(int, dims)) == tuple(map(
                        int, data_shape)), ('{} {}'.format(dims, data_shape))
            if isinstance(data['counts'], (six.text_type, six.binary_type)):
                self = kwimage.Mask(data, MaskFormat.BYTES_RLE)
            else:
                self = kwimage.Mask(data, MaskFormat.ARRAY_RLE)
        elif 'exterior' in data:
            # TODO: kwimage.Polygon.from_coco
            self = kwimage.Polygon(**data)
            # raise NotImplementedError('explicit polygon coerce')
        else:
            raise TypeError(type(data))
    elif isinstance(data, list):
        # THIS IS NOT AN IDEAL FORMAT. IDEALLY WE WILL MODIFY COCO TO USE
        # DICTIONARIES FOR POLYGONS, WHICH ARE UNAMBIGUOUS
        if len(data) == 0:
            self = None
        else:
            first = ub.peek(data)
            if isinstance(first, dict):
                # TODO: kwimage.MultiPolygon.from_coco
                self = kwimage.MultiPolygon(
                    [kwimage.Polygon(**item) for item in data])
            elif isinstance(first, int):
                # TODO: kwimage.Polygon.from_coco
                exterior = np.array(data).reshape(-1, 2)
                self = kwimage.Polygon(exterior=exterior)
            elif isinstance(first, list):
                # TODO: kwimage.MultiPolygon.from_coco
                poly_list = [
                    kwimage.Polygon(exterior=np.array(item).reshape(-1, 2))
                    for item in data
                ]
                if len(poly_list) == 1:
                    self = poly_list[0]
                else:
                    self = kwimage.MultiPolygon(poly_list)
            elif isinstance(first, np.ndarray):
                poly_list = [
                    kwimage.Polygon(exterior=item.reshape(-1, 2))
                    for item in data
                ]
                if len(poly_list) == 1:
                    self = poly_list[0]
                else:
                    self = kwimage.MultiPolygon(poly_list)
            else:
                raise TypeError(type(data))
    else:
        raise TypeError(type(data))
    return self