wip

parent 2079081d
Pipeline #170352 failed with stages
in 5 minutes and 33 seconds
......@@ -10,8 +10,7 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm
* `draw_line_segments_on_image`
* Boxes.scale now accepts `about` keyword arg (can use to scale about center).
* Boxes.warp now accepts matrices and does inexact corner warping
* `kwimage.warp_points` now accepts `homog_mode`
* Add `_warp_func`, which warps points using a general function to structures
* kwimage structures `warp` function now accepts a generic callable for mapping array-based points.
### Changed
......
......@@ -783,8 +783,7 @@ class _BoxTransformMixins(object):
return new
# @profile
def warp(self, transform, input_dims=None, output_dims=None, inplace=False,
homog_mode='divide'):
def warp(self, transform, input_dims=None, output_dims=None, inplace=False):
"""
Generalized coordinate transform. Note that transformations that are
not axis-aligned will lose information (and also may not be
......@@ -815,12 +814,29 @@ class _BoxTransformMixins(object):
Example:
>>> import kwimage
>>> # Can warp corners with a transformation matrix
>>> self = kwimage.Boxes.random(3).scale(100).round(0)
>>> transform = np.array([[ 0.98412825, 0.0577905 , 0.16778511],
>>> [-0.05968319, 0.99819777, 0.00625538],
>>> [-0.16712122, -0.01617005, 0.98580375]])
>>> matrix = transform
>>> self.warp(transform)
>>> new = self.warp(transform)
>>> assert np.all(new.area != self.area)
Example:
>>> import kwimage
>>> # can use a generic function to warp corners
>>> self = kwimage.Boxes.random(3).scale(100).round(0)
>>> def func(xy):
... return xy * 2
>>> new = self.warp(func)
>>> assert np.allclose(new.data, self.scale(2).to_tlbr().data)
>>> # If the box is distorted, the operation is not invertable
>>> self = kwimage.Boxes.random(3).scale(100).round(0)
>>> def func(xy):
... return np.zeros_like(xy)
>>> new = self.warp(func)
>>> assert np.all(new.area == 0)
"""
if inplace:
......@@ -840,6 +856,7 @@ class _BoxTransformMixins(object):
scale = 0
translation = 0
matrix = None
func = None
if isinstance(transform, skimage.transform.AffineTransform):
rotation = transform.rotation
......@@ -867,6 +884,9 @@ class _BoxTransformMixins(object):
if isinstance(transform, imgaug.augmenters.Augmenter):
aug = new._warp_imgaug(transform, input_dims=input_dims, inplace=True)
return aug
elif callable(transform):
func = transform
raise NeedsWarpCorners
else:
raise TypeError(type(transform))
......@@ -889,46 +909,41 @@ class _BoxTransformMixins(object):
new.translate(translation, inplace=True)
except NeedsWarpCorners:
if matrix is None:
raise NotImplementedError('Corner warping is not implemented yet for non-matrix')
import kwimage
corners = []
x1, y1, x2, y2 = [a.ravel() for a in self.to_tlbr().components]
stacked = np.array([
[x1, y1],
[x1, y2],
[x2, y2],
[x2, y1],
])
corners = stacked.transpose(2, 0, 1).reshape(-1, 2)
corners = np.ascontiguousarray(corners)
# apply the operation to warp the corner points
if matrix is not None:
corners_new = kwimage.warp_points(matrix, corners)
elif func is not None:
corners_new = func(corners)
else:
import kwimage
corners = []
x1, y1, x2, y2 = [a.ravel() for a in self.to_tlbr().components]
stacked = np.array([
[x1, y1],
[x1, y2],
[x2, y2],
[x2, y1],
])
corners = stacked.transpose(2, 0, 1).reshape(-1, 2)
corners = np.ascontiguousarray(corners)
# homog_mode = 'drop'
corners_new = kwimage.warp_points(
matrix, corners, homog_mode=homog_mode)
# kwimage.warp_points(matrix, corners, homog_mode='drop')
# kwimage.warp_points(matrix, corners, homog_mode='keep')
# kwimage.warp_points(matrix, corners, homog_mode='divide')
# corners_homog = kwimage.add_homog(corners)
# corners_homog_new = np.matmul(matrix, corners_homog.T).T
# corners_new = kwimage.remove_homog(corners_homog_new, mode='drop')
x_pts_new = corners_new[..., 0].reshape(-1, 4)
y_pts_new = corners_new[..., 1].reshape(-1, 4)
x1_new = x_pts_new.min(axis=1)
x2_new = x_pts_new.max(axis=1)
y1_new = y_pts_new.min(axis=1)
y2_new = y_pts_new.max(axis=1)
data_new = np.hstack([
x1_new[:, None], y1_new[:, None],
x2_new[:, None], y2_new[:, None],
])
new.data = data_new
new.format = 'tlbr'
raise NotImplementedError(
'Corner warping is not implemented yet for transform={}'.format(transform))
x_pts_new = corners_new[..., 0].reshape(-1, 4)
y_pts_new = corners_new[..., 1].reshape(-1, 4)
x1_new = x_pts_new.min(axis=1)
x2_new = x_pts_new.max(axis=1)
y1_new = y_pts_new.min(axis=1)
y2_new = y_pts_new.max(axis=1)
data_new = np.hstack([
x1_new[:, None], y1_new[:, None],
x2_new[:, None], y2_new[:, None],
])
new.data = data_new
new.format = 'tlbr'
return new
......
......@@ -293,9 +293,10 @@ class Coords(_generic.Spatial, ub.NiceRepr):
Generalized coordinate transform.
Args:
transform (GeometricTransform | ArrayLike | Augmenter):
scikit-image tranform, a transformation matrix, or
an imgaug Augmenter.
transform (GeometricTransform | ArrayLike | Augmenter | callable):
scikit-image tranform, a transformation matrix,
an imgaug Augmenter, or callable that maps the ndarray of
coords.
input_dims (Tuple): shape of the image these objects correspond to
(only needed / used when transform is an imgaug augmenter)
......@@ -325,15 +326,32 @@ class Coords(_generic.Spatial, ub.NiceRepr):
>>> assert np.all(self.warp(np.eye(3)).data == self.data)
>>> assert np.all(self.warp(np.eye(2)).data == self.data)
Ignore:
>>> # xdoctest: +SKIP
Doctest:
>>> # xdoctest: +REQUIRES(module:osr)
>>> import osr
>>> wgs84_crs = osr.SpatialReference()
>>> wgs84_crs.ImportFromEPSG(4326)
>>> transform = osr.CoordinateTransformation(wgs84_crs, wgs84_crs)
>>> dst_crs = osr.SpatialReference()
>>> dst_crs.ImportFromEPSG(2927)
>>> transform = osr.CoordinateTransformation(wgs84_crs, dst_crs)
>>> self = Coords.random(10, rng=0)
>>> new = self.warp(transform)
>>> assert np.all(new.data == self.data)
>>> assert np.all(new.data != self.data)
>>> # Alternative using generic func
>>> def _gdal_coord_tranform(pts):
... return np.array([transform.TransformPoint(x, y, 0)[0:2]
... for x, y in pts])
>>> alt = self.warp(_gdal_coord_tranform)
>>> assert np.all(alt.data != self.data)
>>> assert np.all(alt.data == new.data)
Doctest:
>>> # can use a generic function
>>> def func(xy):
... return np.zeros_like(xy)
>>> self = Points.random(10, rng=0)
>>> assert np.all(self.warp(func).xy == 0)
"""
import kwimage
impl = self._impl
......@@ -369,24 +387,13 @@ class Coords(_generic.Spatial, ub.NiceRepr):
new_pts.append((x, y))
new.data = np.array(new_pts, dtype=new.data.dtype)
return new
elif callable(transform):
new.data = transform(new.data)
return new
raise TypeError(type(transform))
new.data = kwimage.warp_points(matrix, new.data)
return new
def _warp_func(self, func, inplace=False):
"""
Warp using a function that transforms points
Args:
func (callable): maps a single coordinate to a new coordinate
"""
impl = self._impl
new = self if inplace else self.__class__(impl.copy(self.data), self.meta)
dtype = self.data.dtype
new.data = np.array([func(pt) for pt in new.data], dtype=dtype)
return new
# @profile
def _warp_imgaug(self, augmenter, input_dims, inplace=False):
"""
Warps by applying an augmenter from the imgaug library
......
......@@ -82,9 +82,10 @@ class _PointsWarpMixin:
Generalized coordinate transform.
Args:
transform (GeometricTransform | ArrayLike | Augmenter):
scikit-image tranform, a 3x3 transformation matrix, or
an imgaug Augmenter.
transform (GeometricTransform | ArrayLike | Augmenter | callable):
scikit-image tranform, a 3x3 transformation matrix,
an imgaug Augmenter, or generic callable (which works on 1
point at a time).
input_dims (Tuple): shape of the image these objects correspond to
(only needed / used when transform is an imgaug augmenter)
......@@ -125,6 +126,9 @@ class _PointsWarpMixin:
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']
......
......@@ -188,21 +188,6 @@ class _PolyWarpMixin:
]
return new
def _warp_func(self, func, inplace=False):
"""
Warp using a function that transforms points
Args:
func (callable): maps a single coordinate to a new coordinate
"""
new = self if inplace else self.__class__(self.data.copy())
new.data['exterior'] = new.data['exterior']._warp_func(func, inplace)
new.data['interiors'] = [
p._warp_func(func, inplace)
for p in new.data['interiors']
]
return new
def scale(self, factor, output_dims=None, inplace=False):
"""
Scale a polygon by a factor
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment