:py:mod:`kwimage.structs.points` ================================ .. py:module:: kwimage.structs.points Module Contents --------------- Classes ~~~~~~~ .. autoapisummary:: kwimage.structs.points._PointsWarpMixin kwimage.structs.points.Points kwimage.structs.points.PointsList .. py:class:: _PointsWarpMixin .. py:method:: _warp_imgaug(self, augmenter, input_dims, inplace=False) Warps by applying an augmenter from the imgaug library :Parameters: * **augmenter** (*imgaug.augmenters.Augmenter*) * **input_dims** (*Tuple*) -- h/w of the input image * **inplace** (*bool, default=False*) -- if True, modifies data inplace .. rubric:: 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) .. py:method:: to_imgaug(self, input_dims) .. rubric:: 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) .. py:method:: from_imgaug(cls, kpoi) :classmethod: .. py:method:: dtype(self) :property: .. py:method:: warp(self, transform, input_dims=None, output_dims=None, inplace=False) Generalized coordinate transform. :Parameters: * **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 .. rubric:: 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) .. py:method:: scale(self, factor, output_dims=None, inplace=False) Scale a points by a factor :Parameters: * **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 .. rubric:: Example >>> from kwimage.structs.points import * # NOQA >>> self = Points.random(10, rng=0) >>> new = self.scale(10) >>> assert new.xy.max() <= 10 .. py:method:: translate(self, offset, output_dims=None, inplace=False) Shift the points :Parameters: * **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 .. rubric:: 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 .. py:class:: Points(data=None, meta=None, datakeys=None, metakeys=None, **kwargs) Bases: :py:obj:`kwimage.structs._generic.Spatial`, :py:obj:`_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)], } .. rubric:: Example >>> from kwimage.structs.points import * # NOQA >>> xy = np.random.rand(10, 2) >>> pts = Points(xy=xy) >>> print('pts = {!r}'.format(pts)) .. py:attribute:: __datakeys__ :annotation: = ['xy', 'class_idxs', 'visible'] .. py:attribute:: __metakeys__ :annotation: = ['classes'] .. py:attribute:: __repr__ .. py:method:: __nice__(self) .. py:method:: __len__(self) .. py:method:: shape(self) :property: .. py:method:: xy(self) :property: .. py:method:: random(Points, num=1, classes=None, rng=None) :classmethod: Makes random points; typically for testing purposes .. rubric:: Example >>> import kwimage >>> self = kwimage.Points.random(classes=[1, 2, 3]) >>> self.data >>> print('self.data = {!r}'.format(self.data)) .. py:method:: is_numpy(self) .. py:method:: is_tensor(self) .. py:method:: _impl(self) .. py:method:: tensor(self, device=ub.NoParam) .. rubric:: Example >>> # xdoctest: +REQUIRES(module:torch) >>> from kwimage.structs.points import * # NOQA >>> self = Points.random(10) >>> self.tensor() .. py:method:: round(self, inplace=False) Rounds data to the nearest integer :Parameters: **inplace** (*bool, default=False*) -- if True, modifies this object .. rubric:: Example >>> import kwimage >>> self = kwimage.Points.random(3).scale(10) >>> self.round() .. py:method:: numpy(self) .. rubric:: Example >>> # xdoctest: +REQUIRES(module:torch) >>> from kwimage.structs.points import * # NOQA >>> self = Points.random(10) >>> self.tensor().numpy().tensor().numpy() .. py:method:: draw_on(self, image, color='white', radius=None, copy=False) CommandLine: xdoctest -m ~/code/kwimage/kwimage/structs/points.py Points.draw_on --show .. rubric:: 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() .. rubric:: 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() .. rubric:: 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() .. py:method:: draw(self, color='blue', ax=None, alpha=None, radius=1, **kwargs) TODO: can use kwplot.draw_points .. rubric:: 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') .. py:method:: compress(self, flags, axis=0, inplace=False) Filters items based on a boolean criterion .. rubric:: 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 .. py:method:: take(self, indices, axis=0, inplace=False) Takes a subset of items at specific indices .. rubric:: 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 .. py:method:: concatenate(cls, points, axis=0) :classmethod: .. py:method:: 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. :Parameters: **style** (*str*) -- either orig, new, new-id, or new-name :returns: mscoco-like representation :rtype: Dict .. rubric:: 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))) .. py:method:: _to_coco(self, style='orig') See to_coco .. py:method:: coerce(cls, data) :classmethod: Attempt to coerce data into a Points object .. py:method:: _from_coco(cls, coco_kpts, class_idxs=None, classes=None) :classmethod: .. py:method:: from_coco(cls, coco_kpts, class_idxs=None, classes=None, warn=False) :classmethod: :Parameters: * **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 .. rubric:: 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=[]) .. rubric:: 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] .. py:class:: PointsList Bases: :py:obj:`kwimage.structs._generic.ObjectList` Stores a list of Points, each item usually corresponds to a different object. .. rubric:: Notes # TODO: when the data is homogenous we can use a more efficient # representation, otherwise we have to use heterogenous storage.