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