Source code for kwimage.structs.points

# -*- coding: utf-8 -*-
import numpy as np
import ubelt as ub
import skimage
import kwarray
from distutils.version import LooseVersion  # NOQA
import warnings
from kwimage.structs import _generic


[docs]class _PointsWarpMixin:
[docs] def _warp_imgaug(self, augmenter, input_dims, inplace=False): """ Warps by applying an augmenter from the imgaug library Args: augmenter (imgaug.augmenters.Augmenter): input_dims (Tuple): h/w of the input image inplace (bool, default=False): if True, modifies data inplace Example: >>> # xdoctest: +REQUIRES(module:imgaug) >>> from kwimage.structs.points import * # NOQA >>> import imgaug >>> input_dims = (10, 10) >>> self = Points.random(10).scale(input_dims) >>> augmenter = imgaug.augmenters.Fliplr(p=1) >>> new = self._warp_imgaug(augmenter, input_dims) >>> self = Points(xy=(np.random.rand(10, 2) * 10).astype(int)) >>> augmenter = imgaug.augmenters.Fliplr(p=1) >>> new = self._warp_imgaug(augmenter, input_dims) >>> # xdoc: +REQUIRES(--show) >>> import kwplot >>> plt = kwplot.autoplt() >>> kwplot.figure(fnum=1, doclf=True) >>> ax = plt.gca() >>> ax.set_xlim(0, 10) >>> ax.set_ylim(0, 10) >>> self.draw(color='red', alpha=.4, radius=0.1) >>> new.draw(color='blue', alpha=.4, radius=0.1) """ new = self if inplace else self.__class__(self.data.copy(), self.meta) new.data['xy'] = new.data['xy']._warp_imgaug(augmenter, input_dims, inplace=inplace) if 'tf_data_to_img' in self.meta: # warping via imgaug invalidates the tf_data_to_img transform self.meta = self.meta.copy() self.meta.pop('tf_data_to_img') return new
[docs] def to_imgaug(self, input_dims): """ Example: >>> # xdoctest: +REQUIRES(module:imgaug) >>> from kwimage.structs.points import * # NOQA >>> pts = Points.random(10) >>> input_dims = (10, 10) >>> kpoi = pts.to_imgaug(input_dims) """ return self.data['xy'].to_imgaug(input_dims)
@classmethod
[docs] def from_imgaug(cls, kpoi): import kwimage data = kwimage.Coords.from_imgaug(kpoi) self = cls(data) return self
@property
[docs] def dtype(self): try: return self.data.dtype except Exception: print('kwimage.mask: no dtype for ' + str(type(self.data))) raise
[docs] def warp(self, transform, input_dims=None, output_dims=None, inplace=False): """ Generalized coordinate transform. Args: transform (GeometricTransform | ArrayLike | Augmenter | callable): scikit-image tranform, a 3x3 transformation matrix, an imgaug Augmenter, or generic callable which transforms an NxD ndarray. input_dims (Tuple): shape of the image these objects correspond to (only needed / used when transform is an imgaug augmenter) output_dims (Tuple): unused, only exists for compatibility inplace (bool, default=False): if True, modifies data inplace Example: >>> from kwimage.structs.points import * # NOQA >>> self = Points.random(10, rng=0) >>> transform = skimage.transform.AffineTransform(scale=(2, 2)) >>> new = self.warp(transform) >>> assert np.all(new.xy == self.scale(2).xy) Doctest: >>> self = Points.random(10, rng=0) >>> assert np.all(self.warp(np.eye(3)).xy == self.xy) >>> assert np.all(self.warp(np.eye(2)).xy == self.xy) """ import kwimage new = self if inplace else self.__class__(self.data.copy(), self.meta) if transform is None: return new if not isinstance(transform, (np.ndarray, skimage.transform._geometric.GeometricTransform, kwimage.Affine)): try: import imgaug except ImportError: pass # warnings.warn('imgaug is not installed') # raise TypeError(type(transform)) else: if isinstance(transform, imgaug.augmenters.Augmenter): return new._warp_imgaug(transform, input_dims, inplace=True) # else: # raise TypeError(type(transform)) new.data['xy'] = new.data['xy'].warp(transform, input_dims, output_dims, inplace) if 'tf_data_to_img' in new.meta: # if we are maintaining a transform to img space, we need to update it new.meta = new.meta.copy() tf = transform if isinstance(tf, np.ndarray): tf = skimage.transform.AffineTransform(matrix=transform) elif callable(tf): raise NotImplementedError( 'callables cant transform linear data_to_img yet') inv_tf = skimage.transform.AffineTransform(matrix=tf._inv_matrix) # new.meta['tf_data_to_img'] = new.meta['tf_data_to_img'] + inv_tf new.meta['tf_data_to_img'] = inv_tf + new.meta['tf_data_to_img'] return new
[docs] def scale(self, factor, output_dims=None, inplace=False): """ Scale a points by a factor Args: factor (float or Tuple[float, float]): scale factor as either a scalar or a (sf_x, sf_y) tuple. output_dims (Tuple): unused in non-raster spatial structures Example: >>> from kwimage.structs.points import * # NOQA >>> self = Points.random(10, rng=0) >>> new = self.scale(10) >>> assert new.xy.max() <= 10 """ new = self if inplace else self.__class__(self.data.copy(), self.meta) new.data['xy'] = new.data['xy'].scale(factor, output_dims=output_dims, inplace=inplace) if 'tf_data_to_img' in new.meta: # if we are maintaining a transform to img space, we need to update it new.meta = new.meta.copy() tf = skimage.transform.AffineTransform(scale=factor) inv_tf = skimage.transform.AffineTransform(matrix=tf._inv_matrix) new.meta['tf_data_to_img'] = (inv_tf + new.meta['tf_data_to_img']) return new
[docs] def translate(self, offset, output_dims=None, inplace=False): """ Shift the points Args: factor (float or Tuple[float]): transation amount as either a scalar or a (t_x, t_y) tuple. output_dims (Tuple): unused in non-raster spatial structures Example: >>> from kwimage.structs.points import * # NOQA >>> self = Points.random(10, rng=0) >>> new = self.translate(10) >>> assert new.xy.min() >= 10 >>> assert new.xy.max() <= 11 """ new = self if inplace else self.__class__(self.data.copy(), self.meta) new.data['xy'] = new.data['xy'].translate(offset, output_dims, inplace) if 'tf_data_to_img' in new.meta: # if we are maintaining a transform to img space, we need to update it new.meta = new.meta.copy() tf = skimage.transform.AffineTransform(translation=offset) inv_tf = skimage.transform.AffineTransform(matrix=tf._inv_matrix) new.meta['tf_data_to_img'] = (inv_tf + new.meta['tf_data_to_img']) return new
[docs]class Points(_generic.Spatial, _PointsWarpMixin): """ Stores multiple keypoints for a single object. This stores both the geometry and the class metadata if available Ignore: meta = { "names" = ['head', 'nose', 'tail'], "skeleton" = [(0, 1), (0, 2)], } Example: >>> from kwimage.structs.points import * # NOQA >>> xy = np.random.rand(10, 2) >>> pts = Points(xy=xy) >>> print('pts = {!r}'.format(pts)) """ # __slots__ = ('data', 'meta',) # Pre-registered keys for the data dictionary
[docs] __datakeys__ = ['xy', 'class_idxs', 'visible']
# Pre-registered keys for the meta dictionary
[docs] __metakeys__ = ['classes']
def __init__(self, data=None, meta=None, datakeys=None, metakeys=None, **kwargs): if kwargs: if data or meta: raise ValueError('Cannot specify kwargs AND data/meta dicts') _datakeys = self.__datakeys__ _metakeys = self.__metakeys__ # Allow the user to specify custom data and meta keys if datakeys is not None: _datakeys = _datakeys + list(datakeys) if metakeys is not None: _metakeys = _metakeys + list(metakeys) # Perform input checks whenever kwargs is given data = {key: kwargs.pop(key) for key in _datakeys if key in kwargs} meta = {key: kwargs.pop(key) for key in _metakeys if key in kwargs} if kwargs: raise ValueError( 'Unknown kwargs: {}'.format(sorted(kwargs.keys()))) if 'xy' in data: if isinstance(data['xy'], _generic.ARRAY_TYPES): import kwimage data['xy'] = kwimage.Coords(data['xy']) elif isinstance(data, self.__class__): # Avoid runtime checks and assume the user is doing the right thing # if data and meta are explicitly specified meta = data.meta data = data.data if meta is None: meta = {} self.data = data self.meta = meta
[docs] def __nice__(self): data_repr = repr(self.xy) if '\n' in data_repr: data_repr = ub.indent('\n' + data_repr.lstrip('\n'), ' ') return 'xy={}'.format(data_repr)
[docs] __repr__ = ub.NiceRepr.__str__
[docs] def __len__(self): return len(self.data['xy'])
@property
[docs] def shape(self): return self.data['xy'].shape
@property
[docs] def xy(self): return self.data['xy'].data
@classmethod
[docs] def random(Points, num=1, classes=None, rng=None): """ Makes random points; typically for testing purposes Example: >>> import kwimage >>> self = kwimage.Points.random(classes=[1, 2, 3]) >>> self.data >>> print('self.data = {!r}'.format(self.data)) """ rng = kwarray.ensure_rng(rng) if ub.iterable(num): shape = tuple(num) + (2,) else: shape = (num, 2) self = Points(xy=rng.rand(*shape)) self.data['visible'] = np.full(len(self), fill_value=2) if classes is not None: class_idxs = (rng.rand(len(self)) * len(classes)).astype(int) self.data['class_idxs'] = class_idxs self.meta['classes'] = classes return self
[docs] def is_numpy(self): return self.data['xy'].is_numpy()
[docs] def is_tensor(self): return self.data['xy'].is_tensor()
@ub.memoize_property
[docs] def _impl(self): return self.data['xy']._impl
[docs] def tensor(self, device=ub.NoParam): """ Example: >>> # xdoctest: +REQUIRES(module:torch) >>> from kwimage.structs.points import * # NOQA >>> self = Points.random(10) >>> self.tensor() """ impl = self._impl newdata = {k: v.tensor(device) if hasattr(v, 'tensor') else impl.tensor(v, device) for k, v in self.data.items()} new = self.__class__(newdata, self.meta) return new
[docs] def round(self, inplace=False): """ Rounds data to the nearest integer Args: inplace (bool, default=False): if True, modifies this object Example: >>> import kwimage >>> self = kwimage.Points.random(3).scale(10) >>> self.round() """ new = self if inplace else self.__class__(self.data, self.meta) new.data['xy'] = self.data['xy'].round() return new
[docs] def numpy(self): """ Example: >>> # xdoctest: +REQUIRES(module:torch) >>> from kwimage.structs.points import * # NOQA >>> self = Points.random(10) >>> self.tensor().numpy().tensor().numpy() """ impl = self._impl newdata = {k: v.numpy() if hasattr(v, 'numpy') else impl.numpy(v) for k, v in self.data.items()} new = self.__class__(newdata, self.meta) return new
[docs] 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
[docs] 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
[docs] def compress(self, flags, axis=0, inplace=False): """ Filters items based on a boolean criterion Example: >>> from kwimage.structs.points import * # NOQA >>> self = Points.random(4) >>> flags = [1, 0, 1, 1] >>> other = self.compress(flags) >>> assert len(self) == 4 >>> assert len(other) == 3 >>> # xdoctest: +REQUIRES(module:torch) >>> other = self.tensor().compress(flags) >>> assert len(other) == 3 """ new = self if inplace else self.__class__(self.data.copy(), self.meta) for k, v in self.data.items(): try: new.data[k] = _generic._safe_compress(v, flags, axis=axis) except Exception: print('FAILED TO COMPRESS k={!r}, v={!r}'.format(k, v)) raise return new
[docs] def take(self, indices, axis=0, inplace=False): """ Takes a subset of items at specific indices Example: >>> from kwimage.structs.points import * # NOQA >>> self = Points.random(4) >>> indices = [1, 3] >>> other = self.take(indices) >>> assert len(self) == 4 >>> assert len(other) == 2 >>> # xdoctest: +REQUIRES(module:torch) >>> other = self.tensor().take(indices) >>> assert len(other) == 2 """ new = self if inplace else self.__class__(self.data.copy(), self.meta) for k, v in self.data.items(): new.data[k] = _generic._safe_take(v, indices, axis=axis) return new
@classmethod
[docs] def concatenate(cls, points, axis=0): if len(points) == 0: raise ValueError('need at least one box to concatenate') if axis != 0: raise ValueError('can only concatenate along axis=0') import kwimage first = points[0] datas = [p.data['xy'] for p in points] newxy = kwimage.Coords.concatenate(datas) new = cls({'xy': newxy}, first.meta) return new
[docs] def to_coco(self, style='orig'): """ Converts to an mscoco-like representation Note: items that are usually id-references to other objects may need to be rectified. Args: style (str): either orig, new, new-id, or new-name Returns: Dict: mscoco-like representation Example: >>> from kwimage.structs.points import * # NOQA >>> self = Points.random(4, classes=['a', 'b']) >>> orig = self._to_coco(style='orig') >>> print('orig = {!r}'.format(orig)) >>> new_name = self._to_coco(style='new-name') >>> print('new_name = {}'.format(ub.repr2(new_name, nl=-1))) >>> # xdoctest: +REQUIRES(module:ndsampler) >>> import ndsampler >>> self.meta['classes'] = ndsampler.CategoryTree.coerce(self.meta['classes']) >>> new_id = self._to_coco(style='new-id') >>> print('new_id = {}'.format(ub.repr2(new_id, nl=-1))) """ if self.xy.size == 0: return [] if len(self.xy.shape) == 2: return self._to_coco(style=style) else: raise NotImplementedError('dim > 2, dense case todo')
[docs] def _to_coco(self, style='orig'): """ See to_coco """ if style == 'orig': visible = self.data.get('visible', None) assert len(self.xy.shape) == 2 if visible is None: visible = np.full((len(self), 1), fill_value=2) else: visible = visible.reshape(-1, 1) # TODO: ensure these are in the right order for the classes flat_pts = np.hstack([self.xy, visible]).reshape(-1) return flat_pts.tolist() elif style.startswith('new'): if style == 'new-id': use_id = True elif style == 'new-name': use_id = False elif style == 'new': use_id = False else: raise KeyError(style) new_kpts = [] for i, xy in enumerate(self.data['xy'].data.tolist()): kpdict = {'xy': xy} if 'visible' in self.data: kpdict['visible'] = int(self.data['visible'][i]) if 'class_idxs' in self.data: cidx = self.data['class_idxs'][i] if use_id: cid = self.meta['classes'].idx_to_id[cidx] kpdict['keypoint_category_id'] = int(cid) else: cname = self.meta['classes'][cidx] kpdict['keypoint_category'] = cname new_kpts.append(kpdict) return new_kpts else: raise KeyError(style)
@classmethod
[docs] def coerce(cls, data): """ Attempt to coerce data into a Points object """ if isinstance(data, cls): return data elif isinstance(data, (list, dict)): # TODO: determine if coco or geojson return cls.from_coco(data) elif isinstance(data, _generic.ARRAY_TYPES): return cls(data) else: raise TypeError(type(data))
@classmethod
[docs] def _from_coco(cls, coco_kpts, class_idxs=None, classes=None): # backwards compatibility return cls.from_coco(coco_kpts, class_idxs=class_idxs, classes=classes)
@classmethod
[docs] def from_coco(cls, coco_kpts, class_idxs=None, classes=None, warn=False): """ Args: coco_kpts (list | dict): either the original list keypoint encoding or the new dict keypoint encoding. class_idxs (list): only needed if using old style classes (list | CategoryTree): list of all keypoint category names warn (bool, default=False): if True raise warnings Example: >>> ## >>> classes = ['mouth', 'left-hand', 'right-hand'] >>> coco_kpts = [ >>> {'xy': (0, 0), 'visible': 2, 'keypoint_category': 'left-hand'}, >>> {'xy': (1, 2), 'visible': 2, 'keypoint_category': 'mouth'}, >>> ] >>> Points.from_coco(coco_kpts, classes=classes) >>> # Test without classes >>> Points.from_coco(coco_kpts) >>> # Test without any category info >>> coco_kpts2 = [ub.dict_diff(d, {'keypoint_category'}) for d in coco_kpts] >>> Points.from_coco(coco_kpts2) >>> # Test without category instead of keypoint_category >>> coco_kpts3 = [ub.map_keys(lambda x: x.replace('keypoint_', ''), d) for d in coco_kpts] >>> Points.from_coco(coco_kpts3) >>> # >>> # Old style >>> coco_kpts = [0, 0, 2, 0, 1, 2] >>> Points.from_coco(coco_kpts) >>> # Fail case >>> coco_kpts4 = [{'xy': [4686.5, 1341.5], 'category': 'dot'}] >>> Points.from_coco(coco_kpts4, classes=[]) Example: >>> # xdoctest: +REQUIRES(module:ndsampler) >>> import ndsampler >>> classes = ndsampler.CategoryTree.from_coco([ >>> {'name': 'mouth', 'id': 2}, {'name': 'left-hand', 'id': 3}, {'name': 'right-hand', 'id': 5} >>> ]) >>> coco_kpts = [ >>> {'xy': (0, 0), 'visible': 2, 'keypoint_category_id': 5}, >>> {'xy': (1, 2), 'visible': 2, 'keypoint_category_id': 2}, >>> ] >>> pts = Points.from_coco(coco_kpts, classes=classes) >>> assert pts.data['class_idxs'].tolist() == [2, 0] """ if coco_kpts is None: return None if len(coco_kpts) and isinstance(ub.peek(coco_kpts), dict): # new style xy = [] visible = [] cidx_list = [] if class_idxs is not None: if warn: warnings.warn('class_idxs should not be specified for new-style') class_idxs = None # raise NotImplementedError( # ''' # Needs to have extra information available to map # between keypoint category ids and idxs. # ''') if classes is None or not bool(classes): # See if we can infer the classes. # This may cause compatiblity issues. inferred_classes = [kpdict.get('keypoint_category', kpdict.get('category', None)) for kpdict in coco_kpts] if all(inferred_classes): if warn: warnings.warn( 'Inferring keypoint classes in Points.from_coco. ' 'It would be better to specify them explicitly') classes = sorted(set(inferred_classes)) for kpdict in coco_kpts: if classes is not None: if 'keypoint_category_id' in kpdict: cid = kpdict['keypoint_category_id'] try: cidx = classes.id_to_idx[cid] except AttributeError: raise TypeError('classes needs to be a ndsampler.CategoryTree to parse keypoint_category_id') elif 'keypoint_category' in kpdict: assert classes is not None cname = kpdict['keypoint_category'] cidx = classes.index(cname) elif 'category_name' in kpdict: assert classes is not None cname = kpdict['category_name'] cidx = classes.index(cname) ### Legacy support, these are not prefered names ### elif 'category_id' in kpdict: if warn: warnings.warn('Keypoints got category_id, but we would prefer keypoint_category_id') cid = kpdict['category_id'] try: cidx = classes.id_to_idx[cid] except AttributeError: raise TypeError('classes needs to be a ndsampler.CategoryTree to parse keypoint_category_id') elif 'category' in kpdict: if warn: warnings.warn('Keypoints got category, but we would prefer keypoint_category') assert classes is not None cname = kpdict['category'] cidx = classes.index(cname) # else: # raise Exception('Keypoint category was not specified') cidx_list.append(cidx) else: if 'keypoint_category_id' in kpdict or 'keypoint_category' in kpdict: # warnings.warn('classes should be specified for new-style') raise Exception('classes should be specified for new-style') xy.append(kpdict['xy']) visible.append(kpdict.get('visible', 2)) if cidx_list: assert len(cidx_list) == len(xy), 'missing category indices' else: cidx_list = None cidx_list = np.array(cidx_list) xy = np.array(xy) visible = np.array(visible) self = cls(xy=xy, visible=visible, class_idxs=cidx_list, classes=classes) else: # original style kp = np.array(coco_kpts).reshape(-1, 3) xy = kp[:, 0:2] visible = kp[:, 2] if class_idxs is not None: if len(class_idxs) == 0: if len(kp) > 0: if warn: warnings.warn('Creating keypoints with unknown class information') # raise Exception('Creating keypoints with unknown class information') class_idxs = [-1] * len(xy) else: class_idxs = [] else: assert len(class_idxs) == len(xy), '{} {}'.format( len(class_idxs), len(xy)) self = cls(xy=xy, visible=visible, class_idxs=class_idxs, classes=classes) return self
[docs]class PointsList(_generic.ObjectList):
""" Stores a list of Points, each item usually corresponds to a different object. Notes: # TODO: when the data is homogenous we can use a more efficient # representation, otherwise we have to use heterogenous storage. """