# -*- coding: utf-8 -*-
# LICENCE
from __future__ import absolute_import, division, print_function, unicode_literals
from six.moves import zip
import numpy as np
import utool as ut
[docs]def bboxes_from_vert_list(verts_list, castint=False):
"""Fit the bounding polygon inside a rectangle"""
return [bbox_from_verts(verts, castint=castint) for verts in verts_list]
[docs]def verts_list_from_bboxes_list(bboxes_list):
"""Create a four-vertex polygon from the bounding rectangle"""
return [verts_from_bbox(bbox) for bbox in bboxes_list]
[docs]def verts_from_bbox(bbox, close=False):
r"""
Args:
bbox (tuple): bounding box in the format (x, y, w, h)
close (bool): (default = False)
Returns:
list: verts
CommandLine:
python -m vtool.geometry --test-verts_from_bbox
Example:
>>> # ENABLE_DOCTEST
>>> from vtool.geometry import * # NOQA
>>> bbox = (10, 10, 50, 50)
>>> close = False
>>> verts = verts_from_bbox(bbox, close)
>>> result = ('verts = %s' % (str(verts),))
>>> print(result)
verts = ((10, 10), (60, 10), (60, 60), (10, 60))
"""
x1, y1, w, h = bbox
x2 = x1 + w
y2 = y1 + h
if close:
# Close the verticies list (for drawing lines)
verts = ((x1, y1), (x2, y1), (x2, y2), (x1, y2), (x1, y1))
else:
verts = ((x1, y1), (x2, y1), (x2, y2), (x1, y2))
return verts
[docs]def bbox_from_verts(verts, castint=False):
x = min(x[0] for x in verts)
y = min(y[1] for y in verts)
w = max(x[0] for x in verts) - x
h = max(y[1] for y in verts) - y
if castint:
return (int(x), int(y), int(w), int(h))
else:
return (x, y, w, h)
[docs]def draw_border(img_in, color=(0, 128, 255), thickness=2, out=None):
r"""
Args:
img_in (ndarray[uint8_t, ndim=2]): image data
color (tuple): in bgr
thickness (int):
out (None):
CommandLine:
python -m vtool.geometry --test-draw_border --show
Example:
>>> # ENABLE_DOCTEST
>>> from vtool.geometry import * # NOQA
>>> import vtool as vt
>>> img_in = vt.imread(ut.grab_test_imgpath('carl.jpg'))
>>> color = (0, 128, 255)
>>> thickness = 20
>>> out = None
>>> # xdoctest: +REQUIRES(module:plottool)
>>> img = draw_border(img_in, color, thickness, out)
>>> # xdoctest: +REQUIRES(--show)
>>> import wbia.plottool as pt
>>> pt.imshow(img)
>>> pt.show_if_requested()
"""
h, w = img_in.shape[0:2]
# verts = verts_from_bbox((0, 0, w, h))
# verts = verts_from_bbox((0, 0, w - 1, h - 1))
half_thickness = thickness // 2
verts = verts_from_bbox(
(half_thickness, half_thickness, w - thickness, h - thickness)
)
# FIXME: adjust verts and draw lines here to fill in the corners correctly
img = draw_verts(img_in, verts, color=color, thickness=thickness, out=out)
return img
[docs]def draw_verts(img_in, verts, color=(0, 128, 255), thickness=2, out=None):
r"""
Args:
img_in (?):
verts (?):
color (tuple):
thickness (int):
Returns:
ndarray[uint8_t, ndim=2]: img - image data
CommandLine:
python -m vtool.geometry --test-draw_verts --show
python -m vtool.geometry --test-draw_verts:0 --show
python -m vtool.geometry --test-draw_verts:1 --show
References:
http://docs.opencv.org/modules/core/doc/drawing_functions.html#line
Example:
>>> # ENABLE_DOCTEST
>>> from vtool.geometry import * # NOQA
>>> # xdoctest: +REQUIRES(--show)
>>> import wbia.plottool as pt
>>> import vtool as vt
>>> # build test data
>>> img_in = vt.imread(ut.grab_test_imgpath('carl.jpg'))
>>> verts = ((10, 10), (10, 100), (100, 100), (100, 10))
>>> color = (0, 128, 255)
>>> thickness = 2
>>> # execute function
>>> out = None
>>> img = draw_verts(img_in, verts, color, thickness, out)
>>> assert img_in is not img
>>> assert out is not img
>>> assert out is not img_in
>>> # verify results
>>> # xdoctest: +REQUIRES(--show)
>>> pt.imshow(img)
>>> pt.show_if_requested()
Example:
>>> # ENABLE_DOCTEST
>>> from vtool.geometry import * # NOQA
>>> # xdoctest: +REQUIRES(--show)
>>> import wbia.plottool as pt
>>> import vtool as vt
>>> # build test data
>>> img_in = vt.imread(ut.grab_test_imgpath('carl.jpg'))
>>> verts = ((10, 10), (10, 100), (100, 100), (100, 10))
>>> color = (0, 128, 255)
>>> thickness = 2
>>> out = img_in
>>> # execute function
>>> img = draw_verts(img_in, verts, color, thickness, out)
>>> assert img_in is img, 'should be in place'
>>> assert out is img, 'should be in place'
>>> # verify results
>>> # xdoctest: +REQUIRES(--show)
>>> pt.imshow(img)
>>> pt.show_if_requested()
out = img_in = np.zeros((500, 500, 3), dtype=np.uint8)
"""
import cv2
if out is None:
out = np.copy(img_in)
if isinstance(verts, np.ndarray):
verts = verts.tolist()
connect = True
if connect:
line_list_sequence = zip(verts[:-1], verts[1:])
line_tuple_sequence = (
(tuple(p1_), tuple(p2_)) for (p1_, p2_) in line_list_sequence
)
cv2.line(out, tuple(verts[0]), tuple(verts[-1]), color, thickness)
for (p1, p2) in line_tuple_sequence:
cv2.line(out, p1, p2, color, thickness)
# print('p1, p2: (%r, %r)' % (p1, p2))
else:
for count, p in enumerate(verts, start=1):
cv2.circle(out, tuple(p), count, color, thickness=1)
return out
[docs]def closest_point_on_line_segment(p, e1, e2):
"""
Finds the closet point from p on line segment (e1, e2)
Args:
p (ndarray): and xy point
e1 (ndarray): the first xy endpoint of the segment
e2 (ndarray): the second xy endpoint of the segment
Returns:
ndarray: pt_on_seg - the closest xy point on (e1, e2) from p
References:
http://en.wikipedia.org/wiki/Distance_from_a_point_to_a_line
http://stackoverflow.com/questions/849211/shortest-distance-between-a-point-and-a-line-segment
CommandLine:
python -m vtool.geometry --exec-closest_point_on_line_segment --show
Example:
>>> # ENABLE_DOCTEST
>>> from vtool.geometry import * # NOQA
>>> import vtool as vt
>>> #bbox = np.array([10, 10, 10, 10], dtype=np.float)
>>> #verts_ = np.array(vt.verts_from_bbox(bbox, close=True))
>>> #R = vt.rotation_around_bbox_mat3x3(vt.TAU / 3, bbox)
>>> #verts = vt.transform_points_with_homography(R, verts_.T).T
>>> verts = np.array([[ 21.83012702, 13.16987298],
>>> [ 16.83012702, 21.83012702],
>>> [ 8.16987298, 16.83012702],
>>> [ 13.16987298, 8.16987298],
>>> [ 21.83012702, 13.16987298]])
>>> rng = np.random.RandomState(0)
>>> p_list = rng.rand(64, 2) * 20 + 5
>>> close_pts = np.array([closest_point_on_vert_segments(p, verts) for p in p_list])
>>> # xdoctest: +REQUIRES(--show)
>>> import wbia.plottool as pt
>>> pt.ensureqt()
>>> pt.plt.plot(p_list.T[0], p_list.T[1], 'ro', label='original point')
>>> pt.plt.plot(close_pts.T[0], close_pts.T[1], 'rx', label='closest point on shape')
>>> for x, y in list(zip(p_list, close_pts)):
>>> z = np.array(list(zip(x, y)))
>>> pt.plt.plot(z[0], z[1], 'r--')
>>> pt.plt.legend()
>>> pt.plt.plot(verts.T[0], verts.T[1], 'b-')
>>> pt.plt.xlim(0, 30)
>>> pt.plt.ylim(0, 30)
>>> pt.plt.axis('equal')
>>> ut.show_if_requested()
"""
# shift e1 to origin
de = (dx, dy) = e2 - e1
# make point vector wrt orgin
pv = p - e1
# Project pv onto de
mag = np.linalg.norm(de)
pt_on_line_ = pv.dot(de / mag) * de / mag
# Check if normalized dot product is between 0 and 1
# Determines if pt is between 0,0 and de
t = de.dot(pt_on_line_) / mag ** 2
# t is an interpolation factor indicating how far past the line segment we
# are. We are on the line segment if it is in the range 0 to 1.
if t < 0:
pt_on_seg = e1
elif t > 1:
pt_on_seg = e2
else:
pt_on_seg = pt_on_line_ + e1
return pt_on_seg
[docs]def distance_to_lineseg(p, e1, e2):
import vtool as vt
close_pt = vt.closest_point_on_line_segment(p, e1, e2)
dist_to_lineseg = vt.L2(p, close_pt)
return dist_to_lineseg
[docs]def closest_point_on_line(p, e1, e2):
"""
e1 and e2 define two points on the line.
Does not clip to the segment.
CommandLine:
python -m vtool.geometry closest_point_on_line --show
Example:
>>> # ENABLE_DOCTEST
>>> from vtool.geometry import * # NOQA
>>> import vtool as vt
>>> verts = np.array([[ 21.83012702, 13.16987298],
>>> [ 16.83012702, 21.83012702],
>>> [ 8.16987298, 16.83012702],
>>> [ 13.16987298, 8.16987298],
>>> [ 21.83012702, 13.16987298]])
>>> rng = np.random.RandomState(0)
>>> p_list = rng.rand(64, 2) * 20 + 5
>>> close_pts = []
>>> for p in p_list:
>>> candidates = [closest_point_on_line(p, e1, e2) for e1, e2 in ut.itertwo(verts)]
>>> dists = np.array([vt.L2_sqrd(p, new_pt) for new_pt in candidates])
>>> close_pts.append(candidates[dists.argmin()])
>>> close_pts = np.array(close_pts)
>>> # xdoctest: +REQUIRES(--show)
>>> import wbia.plottool as pt
>>> pt.ensureqt()
>>> pt.plt.plot(p_list.T[0], p_list.T[1], 'ro', label='original point')
>>> pt.plt.plot(close_pts.T[0], close_pts.T[1], 'rx', label='closest point on shape')
>>> for x, y in list(zip(p_list, close_pts)):
>>> z = np.array(list(zip(x, y)))
>>> pt.plt.plot(z[0], z[1], 'r--')
>>> pt.plt.legend()
>>> pt.plt.plot(verts.T[0], verts.T[1], 'b-')
>>> pt.plt.xlim(0, 30)
>>> pt.plt.ylim(0, 30)
>>> pt.plt.axis('equal')
>>> ut.show_if_requested()
"""
# shift e1 to origin
de = (dx, dy) = e2 - e1
# make point vector wrt orgin
pv = p - e1
# Project pv onto de
mag = np.linalg.norm(de)
pt_on_line_ = pv.dot(de / mag) * de / mag
pt_on_line = pt_on_line_ + e1
return pt_on_line
[docs]def closest_point_on_vert_segments(p, verts):
import vtool as vt
candidates = [
closest_point_on_line_segment(p, e1, e2) for e1, e2 in ut.itertwo(verts)
]
dists = np.array([vt.L2_sqrd(p, new_pt) for new_pt in candidates])
new_pts = candidates[dists.argmin()]
return new_pts
[docs]def closest_point_on_bbox(p, bbox):
"""
Example:
>>> # ENABLE_DOCTEST
>>> from vtool.geometry import * # NOQA
>>> p_list = np.array([[19, 7], [7, 14], [14, 11], [8, 7], [23, 21]], dtype=np.float)
>>> bbox = np.array([10, 10, 10, 10], dtype=np.float)
>>> [closest_point_on_bbox(p, bbox) for p in p_list]
"""
import vtool as vt
verts = np.array(vt.verts_from_bbox(bbox, close=True))
new_pts = closest_point_on_vert_segments(p, verts)
return new_pts
[docs]def bbox_from_xywh(xy, wh, xy_rel_pos=[0, 0]):
"""need to specify xy_rel_pos if xy is not in tl already"""
to_tlx = xy_rel_pos[0] * wh[0]
to_tly = xy_rel_pos[1] * wh[1]
tl_x = xy[0] - to_tlx
tl_y = xy[1] - to_tly
bbox = [tl_x, tl_y, wh[0], wh[1]]
return bbox
[docs]def extent_from_verts(verts):
bbox = bbox_from_verts(verts)
extent = extent_from_bbox(bbox)
return extent
[docs]def union_extents(extents):
extents = np.array(extents)
xmin = extents.T[0].min()
xmax = extents.T[1].max()
ymin = extents.T[2].min()
ymax = extents.T[3].max()
return (xmin, xmax, ymin, ymax)
[docs]def extent_from_bbox(bbox):
"""
Args:
bbox (ndarray): tl_x, tl_y, w, h
Returns:
extent (ndarray): tl_x, br_x, tl_y, br_y
CommandLine:
xdoctest -m ~/code/vtool/vtool/geometry.py extent_from_bbox
Example:
>>> # ENABLE_DOCTEST
>>> from vtool.geometry import * # NOQA
>>> import ubelt as ub
>>> bbox = [0, 0, 10, 10]
>>> extent = extent_from_bbox(bbox)
>>> result = ('extent = %s' % (ub.repr2(extent, nl=0),))
>>> print(result)
extent = [0, 10, 0, 10]
"""
tl_x, tl_y, w, h = bbox
br_x = tl_x + w
br_y = tl_y + h
extent = [tl_x, br_x, tl_y, br_y]
return extent
# def tlbr_from_bbox(bbox):
[docs]def bbox_from_extent(extent):
"""
Args:
extent (ndarray): tl_x, br_x, tl_y, br_y
Returns:
bbox (ndarray): tl_x, tl_y, w, h
Example:
>>> # ENABLE_DOCTEST
>>> from vtool.geometry import * # NOQA
>>> import ubelt as ub
>>> extent = [0, 10, 0, 10]
>>> bbox = bbox_from_extent(extent)
>>> result = ('bbox = %s' % (ub.repr2(bbox, nl=0),))
>>> print(result)
bbox = [0, 0, 10, 10]
"""
tl_x, br_x, tl_y, br_y = extent
w = br_x - tl_x
h = br_y - tl_y
bbox = [tl_x, tl_y, w, h]
return bbox
[docs]def bbox_from_center_wh(center_xy, wh):
return bbox_from_xywh(center_xy, wh, xy_rel_pos=[0.5, 0.5])
[docs]def bbox_center(bbox):
(x, y, w, h) = bbox
centerx = x + (w / 2)
centery = y + (h / 2)
return centerx, centery
[docs]def get_pointset_extents(pts):
minx, miny = pts.min(axis=0)
maxx, maxy = pts.max(axis=0)
bounds = minx, maxx, miny, maxy
return bounds
[docs]def get_pointset_extent_wh(pts):
minx, miny = pts.min(axis=0)
maxx, maxy = pts.max(axis=0)
extent_w = maxx - minx
extent_h = maxy - miny
return extent_w, extent_h
[docs]def cvt_bbox_xywh_to_pt1pt2(xywh, sx=1.0, sy=1.0, round_=True):
"""Converts bbox to thumb format with a scale factor"""
import vtool as vt
(x1, y1, _w, _h) = xywh
x2 = x1 + _w
y2 = y1 + _h
if round_:
pt1 = (vt.iround(x1 * sx), vt.iround(y1 * sy))
pt2 = (vt.iround(x2 * sx), vt.iround(y2 * sy))
else:
pt1 = ((x1 * sx), (y1 * sy))
pt2 = ((x2 * sx), (y2 * sy))
return (pt1, pt2)
[docs]def scale_bbox(bbox, sx, sy=None):
if sy is None:
sy = sx
from vtool import linalg
centerx, centery = bbox_center(bbox)
S = linalg.scale_around_mat3x3(sx, sy, centerx, centery)
verts = np.array(verts_from_bbox(bbox))
vertsT = linalg.transform_points_with_homography(S, verts.T).T
bboxT = bbox_from_verts(vertsT)
return bboxT
[docs]def scale_extents(extents, sx, sy=None):
"""
Args:
extent (ndarray): tl_x, br_x, tl_y, br_y
"""
bbox = bbox_from_extent(extents)
bboxT = scale_bbox(bbox, sx, sy)
extentsT = extent_from_bbox(bboxT)
return extentsT
[docs]def scaled_verts_from_bbox_gen(bbox_list, theta_list, sx=1, sy=1):
r"""
Helps with drawing scaled bbounding boxes on thumbnails
Args:
bbox_list (list): bboxes in x,y,w,h format
theta_list (list): rotation of bounding boxes
sx (float): x scale factor
sy (float): y scale factor
Yeilds:
new_verts - vertices of scaled bounding box for every input
CommandLine:
python -m vtool.image --test-scaled_verts_from_bbox_gen
Example:
>>> # ENABLE_DOCTEST
>>> from vtool.geometry import * # NOQA
>>> # build test data
>>> bbox_list = [(10, 10, 100, 100)]
>>> theta_list = [0]
>>> sx = .5
>>> sy = .5
>>> # execute function
>>> new_verts_list = list(scaled_verts_from_bbox_gen(bbox_list, theta_list, sx, sy))
>>> result = str(new_verts_list)
>>> # verify results
>>> print(result)
[[[5, 5], [55, 5], [55, 55], [5, 55], [5, 5]]]
"""
# TODO: input verts support and better name
for bbox, theta in zip(bbox_list, theta_list):
new_verts = scaled_verts_from_bbox(bbox, theta, sx, sy)
yield new_verts
[docs]def scaled_verts_from_bbox(bbox, theta, sx, sy):
"""
Helps with drawing scaled bbounding boxes on thumbnails
"""
if bbox is None:
return None
from vtool import linalg
# Transformation matrixes
R = linalg.rotation_around_bbox_mat3x3(theta, bbox)
S = linalg.scale_mat3x3(sx, sy)
# Get verticies of the annotation polygon
verts = verts_from_bbox(bbox, close=True)
# Rotate and transform to thumbnail space
xyz_pts = linalg.add_homogenous_coordinate(np.array(verts).T)
trans_pts = linalg.remove_homogenous_coordinate(S.dot(R).dot(xyz_pts))
new_verts = np.round(trans_pts).astype(np.int32).T.tolist()
return new_verts
[docs]def point_inside_bbox(point, bbox):
r"""
Flags points that are strictly inside a bounding box.
Points on the boundary are not considered inside.
Args:
point (ndarray): one or more points to test (2xN)
bbox (tuple): a bounding box in (x, y, w, h) format
Returns:
bool or ndarray: True if the point is in the bbox
CommandLine:
python -m vtool.geometry point_inside_bbox --show
Example:
>>> # ENABLE_DOCTEST
>>> from vtool.geometry import * # NOQA
>>> import ubelt as ub
>>> point = np.array([
>>> [3, 2], [4, 1], [2, 3], [1, 1], [0, 0],
>>> [4, 9.5], [9, 9.5], [7, 2], [7, 8], [9, 3]
>>> ]).T
>>> bbox = (3, 2, 5, 7)
>>> flag = point_inside_bbox(point, bbox)
>>> flag = flag.astype(np.int)
>>> result = ('flag = %s' % (ub.repr2(flag),))
>>> print(result)
>>> # xdoctest: +REQUIRES(--show)
>>> import wbia.plottool as pt
>>> verts = np.array(verts_from_bbox(bbox, close=True))
>>> pt.plot(verts.T[0], verts.T[1], 'b-')
>>> pt.plot(point[0][flag], point[1][flag], 'go')
>>> pt.plot(point[0][~flag], point[1][~flag], 'rx')
>>> pt.plt.xlim(0, 10); pt.plt.ylim(0, 10)
>>> pt.show_if_requested()
flag = np.array([0, 0, 0, 0, 0, 0, 0, 0, 1, 0])
"""
x, y = point
tl_x, br_x, tl_y, br_y = extent_from_bbox(bbox)
inside_x = np.logical_and(tl_x < x, x < br_x)
inside_y = np.logical_and(tl_y < y, y < br_y)
flag = np.logical_and(inside_x, inside_y)
return flag
if __name__ == '__main__':
"""
CommandLine:
xdoctest -m vtool.geometry
"""
import xdoctest
xdoctest.doctest_module(__file__)