kwimage.structs.polygon module¶
A class to represent a polygon (and a MultiPolygon)
Defines Polygon
and MultiPolygon
Todo
[ ] Make function mask -> polygon list
[ ] Make function multipolygon -> polygon list
[ ] Make function PolygonList -> Boxes
[ ] Make function SegmentationList -> Boxes
[ ] First class shapely support (format=’shapely’ to mitigate format conversion cost) (or use shapely as the primary format).
- class kwimage.structs.polygon._ShapelyMixin[source]¶
Bases:
object
Extends
Polygon
andMultiPolygon
with methods that duck-type shapely objects.References
[WikiBoolPolygon]https://en.wikipedia.org/wiki/Boolean_operations_on_polygons
[WikiDe91M]Example
>>> from kwimage.structs.polygon import * # NOQA >>> import itertools as it >>> import kwimage >>> poly1 = kwimage.Polygon.random() >>> poly2 = kwimage.Polygon.random() >>> mpoly1 = kwimage.MultiPolygon.random() >>> mpoly2 = kwimage.MultiPolygon.random().buffer(0) >>> for self, other in it.combinations([poly1, poly2, mpoly1, mpoly2], 2): >>> self.iou(other) >>> self.iooa(other) >>> self.intersection(other) >>> self.union(other) >>> self.difference(other) >>> self.symmetric_difference(other) >>> self.oriented_bounding_box()
- oriented_bounding_box()[source]¶
Example
>>> import kwimage >>> self = kwimage.Polygon.random().scale(100, 100).round() >>> obox = self.oriented_bounding_box() >>> print(f'obox={obox}')
- property area¶
Computes area via shapley conversion
- Returns:
float
- property convex_hull¶
- class kwimage.structs.polygon._PolyArrayBackend[source]¶
Bases:
object
Extends
Polygon
andMultiPolygon
with methods related to array representations of polygons.
- class kwimage.structs.polygon._PolyWarpMixin[source]¶
Bases:
object
Extends
Polygon
andMultiPolygon
with methods for warping their geometry.- _warp_imgaug(augmenter, input_dims, inplace=False)[source]¶
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) – if True, modifies data inplace
Example
>>> # xdoctest: +REQUIRES(module:imgaug) >>> from kwimage.structs.polygon import * # NOQA >>> import imgaug >>> input_dims = np.array((10, 10)) >>> self = Polygon.random(10, n_holes=1, rng=0).scale(input_dims) >>> augmenter = imgaug.augmenters.Fliplr(p=1) >>> new = self._warp_imgaug(augmenter, input_dims) >>> assert np.allclose(self.data['exterior'].data[:, 1], new.data['exterior'].data[:, 1]) >>> assert np.allclose(input_dims[0] - self.data['exterior'].data[:, 0], new.data['exterior'].data[:, 0])
>>> # xdoctest: +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, 10) >>> ax.set_ylim(0, 10) >>> self.draw(color='red', alpha=.4) >>> new.draw(color='blue', alpha=.4)
- warp(transform, input_dims=None, output_dims=None, inplace=False)[source]¶
Generalized coordinate transform.
- Parameters:
transform (SKImageGeometricTransform | 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) – if True, modifies data inplace
Example
>>> from kwimage.structs.polygon import * # NOQA >>> import skimage >>> self = Polygon.random() >>> transform = skimage.transform.AffineTransform(scale=(2, 2)) >>> new = self.warp(transform)
Doctest
>>> # xdoctest: +REQUIRES(module:imgaug) >>> self = Polygon.random() >>> import imgaug >>> augmenter = imgaug.augmenters.Fliplr(p=1) >>> new = self.warp(augmenter, input_dims=(1, 1)) >>> print('new = {!r}'.format(new.data)) >>> print('self = {!r}'.format(self.data)) >>> #assert np.all(self.warp(np.eye(3)).exterior == self.exterior) >>> #assert np.all(self.warp(np.eye(2)).exterior == self.exterior)
- scale(factor, about=None, output_dims=None, inplace=False)[source]¶
Scale a polygon by a factor
- Parameters:
factor (float | Tuple[float, float]) – scale factor as either a scalar or a (sf_x, sf_y) tuple.
about (Tuple | None) – if unspecified scales about the origin (0, 0), otherwise the scaling is about this point. Can be “centroid” and will use centroid of polygon Using “ymin,xmin” will be the topmost,leftmost point on the polygon (wrt image coords). See
_PolyWarpMixin._rectify_about()
for details bout codes.output_dims (Tuple) – unused in non-raster spatial structures
inplace (bool) – if True, modifies data inplace
Example
>>> from kwimage.structs.polygon import * # NOQA >>> self = Polygon.random(10, rng=0) >>> new = self.scale(10)
Example
>>> from kwimage.structs.polygon import * # NOQA >>> self = Polygon.random(10, rng=0).translate((0.5)) >>> new = self.scale(1.5, about='centroid') >>> # xdoctest: +REQUIRES(--show) >>> import kwplot >>> kwplot.figure(fnum=1, doclf=True) >>> kwplot.autompl() >>> self.draw(color='red', alpha=0.5) >>> new.draw(color='blue', alpha=0.5, setlim=True)
- translate(offset, output_dims=None, inplace=False)[source]¶
Shift the polygon up/down left/right
- Parameters:
factor (float | Tuple[float]) – transation amount as either a scalar or a (t_x, t_y) tuple.
output_dims (Tuple) – unused in non-raster spatial structures
inplace (bool) – if True, modifies data inplace
Example
>>> from kwimage.structs.polygon import * # NOQA >>> self = Polygon.random(10, rng=0) >>> new = self.translate(10)
- rotate(theta, about=None, output_dims=None, inplace=False)[source]¶
Rotate the polygon
- Parameters:
theta (float) – rotation angle in radians
about (Tuple | None | str) – if unspecified rotates about the origin (0, 0). If “center” then rotate around the center of this polygon. Otherwise the rotation is about a custom specified point.
output_dims (Tuple) – unused in non-raster spatial structures
Example
>>> from kwimage.structs.polygon import * # NOQA >>> self = Polygon.random(10, rng=0) >>> new = self.rotate(np.pi / 2, about='center') >>> new2 = self.rotate(np.pi / 2) >>> # xdoctest: +REQUIRES(--show) >>> import kwplot >>> kwplot.figure(fnum=1, doclf=True) >>> kwplot.autompl() >>> self.draw(color='red', alpha=0.5) >>> new.draw(color='blue', alpha=0.5)
- _rectify_about(about)[source]¶
Ensures that about returns a specified point. Allows for special keys like center to be used.
- Parameters:
about (str | Tuple) – either a numeric coordinate or a string code that specifies one. Valid string codes are
“origin” - maps to (0, 0)
“centroid” - maps to the polygon centroid
“center” - alias of centroid
“ymin,xmin-bound” - the top-left (img coords) of the polygon bounding box
“ymin,xmax-bound” - the top-right (img coords) of the polygon bounding box
“ymax,xmin-bound” - the bottom-right (img coords) of the polygon bounding box
“ymax,xmax-bound” - the bottom-left (img coords) of the polygon bounding box
A comma separated code illustrated in the TextArt adapted from [SO67822179].
- Returns:
Tuple[int, int] - the x, y about position
References
TextArt
(0, 0) + ---------> +x | | ymin,left ─────────►xxxxxxxxxx◄──────── ymin,xmax | xxxx xx V xmin,ymin ────►xxx xx y+ x xx◄──── xmax,ymin x x xmin,ymax ────►xx x xx xx◄──── xmax,ymax x xx ymax,left ──────►xxxxxxxxxxxxxxx◄────── ymax,xmax
Example
>>> import kwimage >>> mask = kwimage.Mask.from_text(ub.codeblock( >>> ''' >>> xxxxxxxxxx >>> xxxx xx >>> xxx xx >>> x xx >>> x x >>> xx x >>> xx xx >>> x xx >>> xxxxxxxxxxxxxxx >>> '''), zero_chr=' ') >>> poly = mask.to_multi_polygon().data[0] >>> print(poly._rectify_about('ymin,xmin')) [5 0] >>> print(poly._rectify_about('xmin,ymin')) [0 2] >>> print(poly._rectify_about('xmin,ymax')) [0 5] >>> print(poly._rectify_about('ymax,xmin')) [2 8] >>> print(poly._rectify_about('ymin,xmax')) [14 0] >>> print(poly._rectify_about('xmax,ymin')) [18 3] >>> print(poly._rectify_about('xmax,ymax')) [18 6] >>> print(poly._rectify_about('ymax,xmax')) [16 8]
Example
>>> from kwimage.structs.polygon import * # NOQA >>> self = Polygon.random(10, rng=0).scale(10).round().astype(np.int32) >>> print(self._rectify_about('centroid')) >>> print(self._rectify_about('ymax,xmin')) >>> print(self._rectify_about('xmin,ymax')) >>> print(self._rectify_about('ymax,xmin-bounds')) >>> print(self._rectify_about('xmin,ymax-bounds')) (4.325, 3.9) [5 8] [1 6] [1 8] [1 8]
- round(decimals=0, inplace=False)[source]¶
Rounds data to the specified decimal place. This may make the polygon invalid.
- Parameters:
inplace (bool) – if True, modifies this object
decimals (int) – number of decimal places to round to
- Returns:
modified polygon
- Return type:
Example
>>> import kwimage >>> self = kwimage.Polygon.random(3).scale(10) >>> new = self.round() >>> assert np.any(self.exterior.data != new.exterior.data) >>> assert np.all(self.exterior.data.round() == new.exterior.data) >>> # demo a case that makes the polygon invalid >>> self = kwimage.Polygon.random(6).scale(0.1) >>> new = self.round() >>> assert np.any(self.exterior.data != new.exterior.data) >>> assert np.all(self.exterior.data.round() == new.exterior.data)
- astype(dtype, inplace=False)[source]¶
Changes the data type
- Parameters:
dtype – new type
inplace (bool) – if True, modifies this object
- Returns:
modified polygon
- Return type:
Example
>>> import kwimage >>> self = kwimage.Polygon.random(3, rng=0).scale(10) >>> new = self.astype(np.int32) >>> assert np.any(self.exterior.data != new.exterior.data) >>> assert np.all(self.exterior.data.astype(np.int32) == new.exterior.data)
- class kwimage.structs.polygon.Polygon(data=None, meta=None, datakeys=None, metakeys=None, **kwargs)[source]¶
Bases:
Spatial
,_PolyArrayBackend
,_PolyWarpMixin
,_ShapelyMixin
,NiceRepr
Represents a single polygon as set of exterior boundary points and a list of internal polygons representing holes.
By convention exterior boundaries should be counterclockwise and interior holes should be clockwise.
Example
>>> import kwimage >>> poly1 = kwimage.Polygon(exterior=[[ 5., 10.], [ 1., 8.], [ 3., 4.], [ 5., 3.], [ 8., 9.], [ 6., 10.]]) >>> poly2 = kwimage.Polygon.random(rng=34214, n_holes=2).scale(10).round() >>> # xdoctest: +REQUIRES(--show) >>> import kwplot >>> kwplot.autompl() >>> kwplot.figure(doclf=True) >>> poly1.draw(setlim=1.4, vertex=0.2, vertexcolor='kw_orange', color='kw_blue', edgecolor='kw_green', alpha=0.5) >>> poly2.draw(setlim=2.9, vertex=0.2, vertexcolor='kw_red', color='kw_darkgreen', edgecolor='kw_darkblue', alpha=0.5) >>> kwplot.show_if_requested()
Example
>>> import kwimage >>> data = { >>> 'exterior': np.array([[13, 1], [13, 19], [25, 19], [25, 1]]), >>> 'interiors': [ >>> np.array([[14, 13], [15, 12], [23, 12], [24, 13], [24, 18], >>> [23, 19], [13, 19], [12, 18]]), >>> np.array([[13, 2], [14, 1], [24, 1], [25, 2], [25, 11], >>> [24, 12], [14, 12], [13, 11]])] >>> } >>> self = kwimage.Polygon(**data) >>> # xdoctest: +REQUIRES(--show) >>> import kwplot >>> kwplot.autompl() >>> self.draw(setlim=1.4, vertex=0.2, vertexcolor='kw_orange', color='kw_blue', edgecolor='kw_green')
Example
>>> import kwimage >>> data = { >>> 'exterior': np.array([[13, 1], [13, 19], [25, 19], [25, 1]]), >>> 'interiors': [ >>> np.array([[14, 13], [15, 12], [23, 12], [24, 13], [24, 18], >>> [23, 19], [13, 19], [12, 18]]), >>> np.array([[13, 2], [14, 1], [24, 1], [25, 2], [25, 11], >>> [24, 12], [14, 12], [13, 11]])] >>> } >>> self = kwimage.Polygon(**data) >>> # xdoctest: +REQUIRES(--show) >>> import kwplot >>> kwplot.autompl() >>> self.draw(setlim=1.4, vertex=0.2, vertexcolor='kw_orange', color='kw_blue', edgecolor='kw_green')
Example
>>> import kwimage >>> self = kwimage.Polygon.random( >>> n=5, n_holes=1, convex=False, rng=0) >>> print('self = {}'.format(self)) self = <Polygon({ 'exterior': <Coords(data= array([[0.30371392, 0.97195856], [0.24372304, 0.60568445], [0.21408694, 0.34884262], [0.5799477 , 0.44020379], [0.83720288, 0.78367234]]))>, 'interiors': [<Coords(data= array([[0.50164209, 0.83520279], [0.25835064, 0.40313428], [0.28778562, 0.74758761], [0.30341266, 0.93748088]]))>], })> >>> # xdoctest: +REQUIRES(--show) >>> import kwplot >>> kwplot.autompl() >>> self.draw(setlim=True)
Example
>>> # Test empty polygon >>> import kwimage >>> data = { >>> 'exterior': np.array([]), >>> 'interiors': [],} >>> self = kwimage.Polygon(**data) >>> geos = self.to_geojson() >>> kwimage.Polygon.from_geojson(geos) >>> geom = self.to_shapely() >>> kwimage.Polygon.from_shapely(geom) >>> # xdoctest: +REQUIRES(--show) >>> import kwplot >>> kwplot.autompl() >>> self.draw(setlim=True)
- property exterior¶
Returns: kwimage.Coords
- property interiors¶
Returns: List[kwimage.Coords]
- classmethod circle(xy=(0, 0), r=1.0, resolution=64)[source]¶
Create a circular or elliptical polygon.
Might rename to ellipse later?
- Parameters:
xy (Iterable[Number]) – x and y center coordinate
r (float | Number | Tuple[Number, Number]) – circular radius or major and minor elliptical radius
resolution (int) – number of sides
- Returns:
Polygon
Example
>>> import kwimage >>> xy = (0.5, 0.5) >>> r = .3 >>> # Demo with circle >>> circle = kwimage.Polygon.circle(xy, r, resolution=6) >>> # Demo with ellipse >>> xy = (0.5, 0.5) >>> r = (.4, .7) >>> ellipse1 = kwimage.Polygon.circle(xy, r, resolution=12) >>> ellipse2 = kwimage.Polygon.circle(xy, (.7, .4), resolution=12) >>> # xdoctest: +REQUIRES(--show) >>> import kwplot >>> plt = kwplot.autoplt() >>> kwplot.figure(fnum=1, doclf=True) >>> circle.draw(setlim=True, border=1, fill=0, color='kitware_orange') >>> ellipse1.draw(setlim=True, border=1, fill=0, color='kitware_blue') >>> ellipse2.draw(setlim=True, border=1, fill=0, color='kitware_green') >>> plt.gca().set_xlim(-0.5, 1.5) >>> plt.gca().set_ylim(-0.5, 1.5) >>> plt.gca().set_aspect('equal')
kwimage.Polygon.circle(xy, r, resolution=10).draw()
- classmethod regular(num, xy=(0, 0), r=1)[source]¶
Make a regular polygon with
num
sides.Example
>>> import kwimage >>> n_polys = [ >>> kwimage.Polygon.regular(n) >>> for n in range(3, 11) >>> ] >>> # xdoctest: +REQUIRES(--show) >>> import kwplot >>> plt = kwplot.autoplt() >>> fig =kwplot.figure(fnum=1, doclf=True) >>> ax = fig.gca() >>> for i, poly in enumerate(n_polys): >>> poly.translate((i * 2.5, 0), inplace=True) >>> poly.draw(border=True, fill=False) >>> ax.set_aspect('equal') >>> ax.set_xlim(-1, 8 * 2.5) >>> ax.set_ylim(-1, 1)
- classmethod star(xy=(0, 0), r=1)[source]¶
Make a star polygon
Example
>>> import kwimage >>> poly = kwimage.Polygon.star() >>> # xdoctest: +REQUIRES(--show) >>> import kwplot >>> plt = kwplot.autoplt() >>> fig = kwplot.figure(fnum=1, doclf=True) >>> ax = fig.gca() >>> poly.draw() >>> ax.set_aspect('equal') >>> ax.set_xlim(-1, 1) >>> ax.set_ylim(-1, 1)
- classmethod random(n=6, n_holes=0, convex=True, tight=False, rng=None)[source]¶
- Parameters:
n (int) – number of points in the polygon (must be 3 or more)
n_holes (int) – number of holes
tight (bool) – fits the minimum and maximum points between 0 and 1
convex (bool) – force resulting polygon will be convex (may remove exterior points)
- Returns:
Polygon
CommandLine
xdoctest -m kwimage.structs.polygon Polygon.random
Example
>>> import kwimage >>> self = kwimage.Polygon.random(n=4, rng=None, n_holes=2, convex=1) >>> # xdoctest: +REQUIRES(--show) >>> import kwplot >>> kwplot.autompl() >>> kwplot.figure(fnum=1, doclf=True) >>> self.draw(color='kw_green', edgecolor='kw_blue', vertexcolor='kw_darkblue', vertex=0.01)
References
https://gis.stackexchange.com/questions/207731/random-multipolygon https://stackoverflow.com/questions/8997099/random-polygon https://stackoverflow.com/questions/27548363/from-voronoi-tessellation-to-shapely-polygons https://stackoverflow.com/questions/8997099/algorithm-to-generate-random-2d-polygon
- property _impl¶
- to_mask(dims=None, pixels_are='points')[source]¶
Convert this polygon to a mask
Todo
[ ] currently not efficient
- Parameters:
dims (Tuple) – height and width of the output mask
pixels_are (str) – either “points” or “areas”
- Returns:
kwimage.Mask
Example
>>> from kwimage.structs.polygon import * # NOQA >>> self = Polygon.random(n_holes=1).scale(128) >>> mask = self.to_mask((128, 128)) >>> # xdoctest: +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)
- to_relative_mask(return_offset=False)[source]¶
Returns a translated mask such the mask dimensions are minimal.
In other words, we move the polygon all the way to the top-left and return a mask just big enough to fit the polygon.
- Returns:
kwimage.Mask
Example
>>> from kwimage.structs.polygon import * # NOQA >>> self = Polygon.random().scale(8).translate(100, 100) >>> mask = self.to_relative_mask() >>> assert mask.shape <= (8, 8) >>> # xdoctest: +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)
- _to_cv_countours()[source]¶
OpenCV polygon representation, which is a list of points. Holes are implicitly represented. When another polygon is drawn over an existing polyon via cv2.fillPoly
- Returns:
- where each ndarray is of shape [N, 1, 2],
where N is the number of points on the boundary, the middle dimension is always 1, and the trailing dimension represents x and y coordinates respectively.
- Return type:
List[ndarray]
- classmethod coerce(data)[source]¶
Routes the input to the proper constructor
Try to autodetermine format of input polygon and coerce it into a kwimage.Polygon.
- Parameters:
data (object) – some type of data that can be interpreted as a polygon.
- Returns:
kwimage.Polygon
Example
>>> import kwimage >>> self = kwimage.Polygon.random() >>> kwimage.Polygon.coerce(self) >>> kwimage.Polygon.coerce(self.exterior) >>> kwimage.Polygon.coerce(self.exterior.data) >>> kwimage.Polygon.coerce(self.data) >>> kwimage.Polygon.coerce(self.to_geojson()) >>> kwimage.Polygon.coerce('POLYGON ((0.11 0.61, 0.07 0.588, 0.015 0.50, 0.11 0.61))')
- classmethod from_shapely(geom)[source]¶
Convert a shapely polygon to a kwimage.Polygon
- Parameters:
geom (shapely.geometry.polygon.Polygon) – a shapely polygon
- Returns:
kwimage.Polygon
- classmethod from_wkt(data)[source]¶
Convert a WKT string to a kwimage.Polygon
- Parameters:
data (str) – a WKT polygon string
- Returns:
kwimage.Polygon
Example
>>> import kwimage >>> data = 'POLYGON ((0.11 0.61, 0.07 0.588, 0.015 0.50, 0.11 0.61))' >>> self = kwimage.Polygon.from_wkt(data) >>> assert len(self.exterior) == 4
- classmethod from_geojson(data_geojson)[source]¶
Convert a geojson polygon to a kwimage.Polygon
- Parameters:
data_geojson (dict) – geojson data
- Returns:
Polygon
References
https://geojson.org/geojson-spec.html
Example
>>> from kwimage.structs.polygon import * # NOQA >>> self = Polygon.random(n_holes=2) >>> data_geojson = self.to_geojson() >>> new = Polygon.from_geojson(data_geojson)
- to_shapely(fix=False)[source]¶
- Parameters:
fix (bool) – if True, will check for validity and if any simple fixes can be applied, otherwise it returns the data as is.
- Returns:
shapely.geometry.polygon.Polygon
Example
>>> # xdoctest: +REQUIRES(module:kwplot) >>> # xdoctest: +REQUIRES(module:shapely) >>> from kwimage.structs.polygon import * # NOQA >>> self = Polygon.random(n_holes=1) >>> self = self.scale(100) >>> geom = self.to_shapely() >>> print('geom = {!r}'.format(geom))
- to_geojson()[source]¶
Converts polygon to a geojson structure
- Returns:
Dict[str, object]
Example
>>> import kwimage >>> self = kwimage.Polygon.random() >>> print(self.to_geojson())
- to_wkt()[source]¶
Convert a kwimage.Polygon to WKT string
- Returns:
str
Example
>>> import kwimage >>> self = kwimage.Polygon.random() >>> print(self.to_wkt())
- classmethod from_coco(data, dims=None)[source]¶
Accepts either new-style or old-style coco polygons
- Parameters:
data (List[Number] | Dict) – A new or old-style coco polygon
dims (None | Tuple[int, …]) – the shape dimensions of the canvas. Unused. Exists for compatibility with masks.
- Returns:
Polygon
- to_coco(style='orig')[source]¶
- Parameters:
style (str) – can be “orig” or “new”
- Returns:
coco-style polygons
- Return type:
List | Dict
- property centroid¶
Returns: Tuple[Number, Number]
- to_box()[source]¶
## DEPRECATED: Use
box()
instead. ## Do we deprecate this? Should we stick to the to_ / from_ convention?- Returns:
kwimage.Box
- bounding_box()[source]¶
Returns an axis-aligned bounding box for the segmentation
DEPRECATED: Use singular
box()
instead.- Returns:
kwimage.Boxes
- box()[source]¶
Returns an axis-aligned bounding box for the segmentation
- Returns:
kwimage.Box
Example
>>> import kwimage >>> poly = kwimage.Polygon.random() >>> box = poly.box() >>> print('box = {}'.format(ub.urepr(box, nl=1)))
- bounding_box_polygon()[source]¶
Returns an axis-aligned bounding polygon for the segmentation.
Note
This Polygon will be a Box, not a convex hull! Use shapely for convex hulls.
- Returns:
kwimage.Polygon
- clip(x_min, y_min, x_max, y_max, inplace=False)[source]¶
Clip polygon to specified boundaries.
- Returns:
clipped polygon
- Return type:
Example
>>> from kwimage.structs.polygon import * >>> self = Polygon.random().scale(10).translate(-1) >>> self2 = self.clip(1, 1, 3, 3) >>> # xdoctest: +REQUIRES(--show) >>> import kwplot >>> kwplot.autompl() >>> self2.draw(setlim=True)
- fill(image, value=1, pixels_are='points', assert_inplace=False)[source]¶
Fill in an image based on this polyon.
- Parameters:
image (ndarray) – image to draw on
value (int | Tuple[int]) – value fill in with. Defaults to 1.
pixels_are (str) – either points or areas
assert_inplace (bool) – if True then the function will error if the modification cannot happen inplace.
- Returns:
the image that has been modified in place
- Return type:
ndarray
Example
>>> # xdoctest: +REQUIRES(module:rasterio) >>> import kwimage >>> mask = kwimage.Mask.random(rng=0) >>> self = mask.to_multi_polygon(pixels_are='areas').data[0] >>> image = np.zeros_like(mask.data) >>> self.fill(image, pixels_are='areas')
Example
>>> # Test case where there are multiple channels >>> import kwimage >>> mask = kwimage.Mask.random(shape=(4, 4), rng=0) >>> self = mask.to_multi_polygon() >>> image = np.zeros(mask.shape[0:2] + (2,), dtype=np.float32) >>> fill_v1 = self.fill(image.copy(), value=1) >>> fill_v2 = self.fill(image.copy(), value=(1, 2)) >>> assert np.all((fill_v1 > 0) == (fill_v2 > 0))
Example
>>> import kwimage >>> # Test dtype with inplace vs not >>> mask = kwimage.Mask.random(shape=(32, 32), rng=0) >>> self = mask.to_multi_polygon() >>> native_dtypes = [] >>> native_dtypes += [np.uint8, np.uint16] >>> native_dtypes += [np.int8, np.int16, np.int32] >>> native_dtypes += [np.float32] >>> for dtype in native_dtypes: >>> image = np.zeros(mask.shape[0:2] + (2,), dtype=dtype) >>> image1 = self.fill(image, value=1, assert_inplace=True) >>> assert image1.sum() > 0 >>> assert image.sum() > 0 >>> print(f'dtype: {dtype} inplace') >>> needfix_dtypes = [np.uint32, np.uint64, np.int64, np.float16, np.float64] >>> for dtype in needfix_dtypes: >>> image = np.zeros(mask.shape[0:2] + (2,), dtype=dtype) >>> image1 = self.fill(image, value=1, assert_inplace=False) >>> assert image1.sum() > 0 >>> assert image.sum() == 0 >>> print(f'dtype: {dtype} not inplace')
- draw_on(image, color='blue', fill=True, border=False, alpha=1.0, edgecolor=None, facecolor=None, copy=False)[source]¶
Rasterizes a polygon on an image. See draw for a vectorized matplotlib version.
- Parameters:
image (ndarray) – image to raster polygon on.
color (str | tuple) – data coercable to a color
fill (bool) – draw the center mass of the polygon. Note: this will be deprecated. Use facecolor instead.
border (bool) – draw the border of the polygon Note: this will be deprecated. Use edgecolor instead.
alpha (float) – polygon transparency (setting alpha < 1 makes this function much slower). Defaults to 1.0
copy (bool) – if False only copies if necessary
edgecolor (str | tuple) – color for the border
facecolor (str | tuple) – color for the fill
- Returns:
np.ndarray
Note
This function will only be inplace if alpha=1.0 and the input has 3 or 4 channels. Otherwise the output canvas is coerced so colors can be drawn on it. In the case where alpha < 1.0,
Example
>>> # xdoctest: +REQUIRES(module:kwplot) >>> from kwimage.structs.polygon import * # NOQA >>> self = Polygon.random(n_holes=1).scale(128) >>> image_in = np.zeros((128, 128), dtype=np.float32) >>> image_out = self.draw_on(image_in) >>> # xdoctest: +REQUIRES(--show) >>> import kwplot >>> kwplot.autompl() >>> kwplot.imshow(image_out, fnum=1)
Example
>>> # xdoctest: +REQUIRES(module:kwplot) >>> # Demo drawing on a RGBA canvas >>> # If you initialize an zero rgba canvas, the alpha values are >>> # filled correctly. >>> from kwimage.structs.polygon import * # NOQA >>> s = 16 >>> self = Polygon.random(n_holes=1, rng=32).scale(s) >>> image_in = np.zeros((s, s, 4), dtype=np.float32) >>> image_out = self.draw_on(image_in, color='black') >>> assert np.all(image_out[..., 0:3] == 0) >>> assert not np.all(image_out[..., 3] == 1) >>> assert not np.all(image_out[..., 3] == 0)
Example
>>> import kwimage >>> color = 'blue' >>> self = kwimage.Polygon.random(n_holes=1).scale(128) >>> image = np.zeros((128, 128), dtype=np.float32) >>> # Test drawing on all channel + dtype combinations >>> im3 = np.random.rand(128, 128, 3) >>> im_chans = { >>> 'im3': im3, >>> 'im1': kwimage.convert_colorspace(im3, 'rgb', 'gray'), >>> #'im0': im3[..., 0], >>> 'im4': kwimage.convert_colorspace(im3, 'rgb', 'rgba'), >>> } >>> inputs = {} >>> for k, im in im_chans.items(): >>> inputs[k + '_f01'] = (kwimage.ensure_float01(im.copy()), {'alpha': None}) >>> inputs[k + '_u255'] = (kwimage.ensure_uint255(im.copy()), {'alpha': None}) >>> inputs[k + '_f01_a'] = (kwimage.ensure_float01(im.copy()), {'alpha': 0.5}) >>> inputs[k + '_u255_a'] = (kwimage.ensure_uint255(im.copy()), {'alpha': 0.5}) >>> # Check cases when image is/isnot written inplace Construct images >>> # with different dtypes / channels and run a draw_on with different >>> # keyword args. For each combination, demo if that results in an >>> # implace operation or not. >>> rows = [] >>> outputs = {} >>> for k, v in inputs.items(): >>> im, kw = v >>> outputs[k] = self.draw_on(im, color=color, **kw) >>> inplace = outputs[k] is im >>> rows.append({'key': k, 'inplace': inplace}) >>> # xdoctest: +REQUIRES(module:pandas) >>> import pandas as pd >>> df = pd.DataFrame(rows).sort_values('inplace') >>> print(df.to_string()) >>> # xdoctest: +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()
Example
>>> # Test empty polygon draw >>> from kwimage.structs.polygon import * # NOQA >>> self = Polygon.from_coco([]) >>> image_in = np.zeros((128, 128), dtype=np.float32) >>> image_out = self.draw_on(image_in)
Example
>>> # Test stupid large polygon draw >>> from kwimage.structs.polygon import * # NOQA >>> from kwimage.structs.polygon import _generic >>> import kwimage >>> self = kwimage.Polygon.random().scale(2e11) >>> image = np.zeros((128, 128), dtype=np.float32) >>> image_out = self.draw_on(image)
- draw(color='blue', ax=None, alpha=1.0, radius=1, setlim=False, border=None, linewidth=None, edgecolor=None, facecolor=None, fill=True, vertex=False, vertexcolor=None)[source]¶
Draws polygon in a matplotlib axes. See draw_on for in-memory image modification.
- Parameters:
color (str | Tuple) – coercable color. Default color if specific colors are not given.
alpha (float) – fill transparency
fill (bool) – if True fill the polygon with facecolor, otherwise just draw the border if linewidth > 0
setlim (bool | str) – if True, modify the x and y limits of the matplotlib axes such that the polygon is can be seen. Can also be a string “grow”, which only allows growth of the viewport to accomidate the new polyogn.
border (bool) – if True, draws an edge border on the polygon. DEPRECATED. Use linewidth instead.
linewidth (bool) – width of the border
edgecolor (None | Any) – if None, uses the value of
color
. Otherwise the color of the border when linewidth > 0. Extended types Coercible[kwimage.Color].facecolor (None | Any) – if None, uses the value of
color
. Otherwise, color of the border when fill=True. Extended types Coercible[kwimage.Color].vertex (float) – if non-zero, draws vertexes on the polygon with this radius.
vertexcolor (Any) – color of vertexes Extended types Coercible[kwimage.Color].
- Returns:
None for am empty polygon
- Return type:
matplotlib.patches.PathPatch | None
Todo
[ ] Rework arguments in favor of matplotlib standards
Example
>>> # xdoctest: +REQUIRES(module:kwplot) >>> from kwimage.structs.polygon import * # NOQA >>> self = Polygon.random(n_holes=1) >>> self = self.scale(100) >>> # xdoctest: +REQUIRES(--show) >>> kwargs = dict(edgecolor='orangered', facecolor='dodgerblue', linewidth=10) >>> self.draw(**kwargs) >>> import kwplot >>> kwplot.autompl() >>> from matplotlib import pyplot as plt >>> kwplot.figure(fnum=2) >>> self.draw(setlim=True, **kwargs)
Example
>>> # xdoctest: +REQUIRES(module:kwplot) >>> # xdoctest: +REQUIRES(--show) >>> from kwimage.structs.polygon import * # NOQA >>> self = Polygon.random(n_holes=1, rng=33202) >>> import textwrap >>> # Test over a range of parameters >>> basis = { >>> 'linewidth': [0, 4], >>> 'edgecolor': [None, 'gold'], >>> 'facecolor': ['purple'], >>> 'fill': [True, False], >>> 'alpha': [1.0, 0.5], >>> 'vertex': [0, 0.01], >>> 'vertexcolor': ['green'], >>> } >>> grid = list(ub.named_product(basis)) >>> import kwplot >>> kwplot.autompl() >>> pnum_ = kwplot.PlotNums(nSubplots=len(grid)) >>> for kwargs in grid: >>> fig = kwplot.figure(fnum=1, pnum=pnum_()) >>> ax = fig.gca() >>> self.draw(ax=ax, **kwargs) >>> title = ub.urepr(kwargs, compact=True) >>> title = '\n'.join(textwrap.wrap( >>> title.replace(',', ' '), break_long_words=False, >>> width=60)) >>> ax.set_title(title, fontdict={'fontsize': 8}) >>> ax.grid(False) >>> ax.set_xticks([]) >>> ax.set_yticks([]) >>> fig.subplots_adjust(wspace=0.5, hspace=0.3, bottom=0.001, top=0.97) >>> kwplot.show_if_requested()
- _ensure_vertex_order(inplace=False)[source]¶
Fixes vertex ordering so the exterior ring is CCW and the interior rings are CW.
Example
>>> import kwimage >>> self = kwimage.Polygon.random(n=3, n_holes=2, rng=0) >>> print('self = {!r}'.format(self)) >>> new = self._ensure_vertex_order() >>> print('new = {!r}'.format(new))
>>> self = kwimage.Polygon.random(n=3, n_holes=2, rng=0).swap_axes() >>> print('self = {!r}'.format(self)) >>> new = self._ensure_vertex_order() >>> print('new = {!r}'.format(new))
- morph(other, alpha)[source]¶
Perform polygon-to-polygon morphing.
Note
This current algorithm is very basic and does not yet prevent self-intersections in intermediate polygons.
- Parameters:
other (kwimage.Polygon) – the other polygon to morph into
alpha (float | List[float]) – A value between 0 and 1, indicating the fractional position of the new interpolated polygon between
self
andother
. If given as a list multiple interpolations are returned.
- Returns:
one ore more interpolated polygons
- Return type:
Todo
[ ] Implement level set method [LevelSet] which rasterizes each
polygon, interpolates the raster, and computes the interpolated polygon as contours in that interpolated raster.
[ ] Implement methods from [Albrecht2006] and [PolygonMorph]
References
[HaudrenShapes]https://github.com/haudren/shapes/blob/master/shapes/shapes.py
[KamvysselisMorph]Example
>>> import kwimage >>> self = kwimage.Polygon.random(3, convex=0) >>> other = kwimage.Polygon.random(4, convex=0).translate((2, 2)) >>> results = self.morph(other, np.linspace(0, 1, 5)) >>> # xdoctest: +REQUIRES(--show) >>> import kwplot >>> plt = kwplot.autoplt() >>> kwplot.figure(doclf=1) >>> self.draw(setlim='grow', color='kw_blue', alpha=0.5, vertex=0.02) >>> other.draw(setlim='grow', color='kw_green', alpha=0.5, vertex=0.02) >>> colors = kwimage.Color('kw_blue').morph( >>> 'kw_green', np.linspace(0, 1, 5)) >>> for new, c in zip(results, colors): >>> pt = new.exterior.data[0] >>> new.draw(color=c, alpha=0.5, vertex=0.01) >>> intepolation_lines = np.array([new.exterior.data for new in results]) >>> for interp_line in intepolation_lines.transpose(1, 0, 2)[::8]: >>> plt.plot(*interp_line.T, '--x')
- class kwimage.structs.polygon.MultiPolygon(data, meta=None)[source]¶
Bases:
ObjectList
,_ShapelyMixin
Data structure for storing multiple polygons (typically related to the same underlying but potentitally disjoing object)
- Variables:
data (List[Polygon]) –
- classmethod random(n=3, n_holes=0, rng=None, tight=False)[source]¶
Create a random MultiPolygon
- Returns:
MultiPolygon
- fill(image, value=1, pixels_are='points', assert_inplace=False)[source]¶
Inplace fill in an image based on this multi-polyon.
- Parameters:
image (ndarray) – image to draw on (inplace)
value (int | Tuple[int, …]) – value fill in with. Defaults to 1.0
- Returns:
the image that has been modified in place
- Return type:
ndarray
- bounding_box()[source]¶
Return the bounding box of the multi polygon
DEPRECATED: Use singular
box()
instead.- Returns:
- a Boxes object with one box that encloses all
polygons
- Return type:
Example
>>> from kwimage.structs.polygon import * # NOQA >>> self = MultiPolygon.random(rng=0, n=10) >>> boxes = self.to_boxes() >>> sub_boxes = [d.to_boxes() for d in self.data] >>> areas1 = np.array([s.intersection(boxes).area[0] for s in sub_boxes]) >>> areas2 = np.array([s.area[0] for s in sub_boxes]) >>> assert np.allclose(areas1, areas2)
- box()[source]¶
Returns an axis-aligned bounding box for the segmentation
- Returns:
kwimage.Box
Example
>>> from kwimage.structs.polygon import * # NOQA >>> self = MultiPolygon.random(rng=0, n=10) >>> boxes = self.box() >>> sub_boxes = [d.box() for d in self.data] >>> areas1 = np.array([s.intersection(boxes).area for s in sub_boxes]) >>> areas2 = np.array([s.area for s in sub_boxes]) >>> assert np.allclose(areas1, areas2)
- to_mask(dims=None, pixels_are='points')[source]¶
Returns a mask object indication regions occupied by this multipolygon
- Returns:
kwimage.Mask
Example
>>> from kwimage.structs.polygon import * # NOQA >>> s = 100 >>> self = MultiPolygon.random(rng=0).scale(s) >>> dims = (s, s) >>> mask = self.to_mask(dims) >>> # xdoctest: +REQUIRES(--show) >>> # xdoctest: +REQUIRES(module:kwplot) >>> import kwplot >>> plt = kwplot.autoplt() >>> kwplot.figure(fnum=1, doclf=True) >>> 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)
- to_relative_mask(return_offset=False)[source]¶
Returns a translated mask such the mask dimensions are minimal.
In other words, we move the polygon all the way to the top-left and return a mask just big enough to fit the polygon.
- Returns:
kwimage.Mask
- classmethod coerce(data, dims=None)[source]¶
Attempts to construct a MultiPolygon instance from the input data
See Segmentation.coerce
- Returns:
None | MultiPolygon
Example
>>> import kwimage >>> dims = (32, 32) >>> kw_poly = kwimage.Polygon.random().scale(dims) >>> kw_multi_poly = kwimage.MultiPolygon.random().scale(dims) >>> forms = [kw_poly, kw_multi_poly] >>> forms.append(kw_poly.to_shapely()) >>> forms.append(kw_poly.to_mask((32, 32))) >>> forms.append(kw_poly.to_geojson()) >>> forms.append(kw_poly.to_coco(style='orig')) >>> forms.append(kw_poly.to_coco(style='new')) >>> forms.append(kw_multi_poly.to_shapely()) >>> forms.append(kw_multi_poly.to_mask((32, 32))) >>> forms.append(kw_multi_poly.to_geojson()) >>> forms.append(kw_multi_poly.to_coco(style='orig')) >>> forms.append(kw_multi_poly.to_coco(style='new')) >>> for data in forms: >>> result = kwimage.MultiPolygon.coerce(data, dims=dims) >>> assert isinstance(result, kwimage.MultiPolygon)
- to_shapely(fix=False)[source]¶
- Parameters:
fix (bool) – if True, will check for validity and if any simple fixes can be applied, otherwise it returns the data as is.
- Returns:
shapely.geometry.MultiPolygon
Example
>>> # xdoctest: +REQUIRES(module:kwplot) >>> # xdoctest: +REQUIRES(module:shapely) >>> from kwimage.structs.polygon import * # NOQA >>> self = MultiPolygon.random(rng=0) >>> geom = self.to_shapely() >>> print('geom = {!r}'.format(geom))
- classmethod from_shapely(geom)[source]¶
Convert a shapely polygon or multipolygon to a kwimage.MultiPolygon
- Parameters:
geom (shapely.geometry.MultiPolygon | shapely.geometry.Polygon)
- Returns:
MultiPolygon
Example
>>> import kwimage >>> sh_poly = kwimage.Polygon.random().to_shapely() >>> sh_multi_poly = kwimage.MultiPolygon.random().to_shapely() >>> kwimage.MultiPolygon.from_shapely(sh_poly) >>> kwimage.MultiPolygon.from_shapely(sh_multi_poly)
- classmethod from_geojson(data_geojson)[source]¶
Convert a geojson polygon or multipolygon to a kwimage.MultiPolygon
- Parameters:
data_geojson (Dict) – geojson data
- Returns:
MultiPolygon
Example
>>> import kwimage >>> orig = kwimage.MultiPolygon.random() >>> data_geojson = orig.to_geojson() >>> self = kwimage.MultiPolygon.from_geojson(data_geojson)
- classmethod from_coco(data, dims=None)[source]¶
Accepts either new-style or old-style coco multi-polygons
- Parameters:
data (List[List[Number] | Dict]) – a new or old style coco multi polygon
dims (None | Tuple[int, …]) – the shape dimensions of the canvas. Unused. Exists for compatibility with masks.
- Returns:
MultiPolygon
- to_coco(style='orig')[source]¶
- Parameters:
style (str) – can be “orig” or “new”
Example
>>> from kwimage.structs.polygon import * # NOQA >>> self = MultiPolygon.random(1, rng=0) >>> self.to_coco()
- class kwimage.structs.polygon.PolygonList(data, meta=None)[source]¶
Bases:
ObjectList
Stores and allows manipluation of multiple polygons, usually within the same image.
- to_mask_list(dims=None, pixels_are='points')[source]¶
Converts all items to masks
- Returns:
kwimage.MaskList
- to_segmentation_list()[source]¶
Converts all items to segmentation objects
- Returns:
kwimage.SegmentationList
- to_geojson(as_collection=False)[source]¶
Converts a list of polygons/multipolygons to a geojson structure
- Parameters:
as_collection (bool) – if True, wraps the polygon geojson items in a geojson feature collection, otherwise just return a list of items.
- Returns:
items or geojson data
- Return type:
List[Dict] | Dict
Example
>>> import kwimage >>> data = [kwimage.Polygon.random(), >>> kwimage.Polygon.random(n_holes=1), >>> kwimage.MultiPolygon.random(n_holes=1), >>> kwimage.MultiPolygon.random()] >>> self = kwimage.PolygonList(data) >>> geojson = self.to_geojson(as_collection=True) >>> items = self.to_geojson(as_collection=False) >>> print('geojson = {}'.format(ub.urepr(geojson, nl=-2, precision=1))) >>> print('items = {}'.format(ub.urepr(items, nl=-2, precision=1)))
- kwimage.structs.polygon._kwimage_from_shapely(geom)[source]¶
- Parameters:
geom (shapely.geometry.base.BaseGeometry)
- Returns:
Polygon | MultiPolygon
- kwimage.structs.polygon._is_clockwise(verts)[source]¶
Test if points are in clockwise order [SO1165647].
- Parameters:
verts (ndarray)
- Returns:
bool
References
- kwimage.structs.polygon._order_vertices(verts)[source]¶
Reorder vertices to be clockwise [SO1709283].
- Parameters:
verts (ndarray)
- Returns:
ndarray
References