def bestfit_circle_numpy(points): """Fit a circle through a set of points. Parameters ---------- points : list XYZ coordinates of the points. Returns ------- tuple XYZ coordinates of the center of the circle, the normal vector of the local frame, and the radius of the circle. Notes ----- For more information see [1]_. References ---------- .. [1] Scipy. *Least squares circle*. Available at: http://scipy-cookbook.readthedocs.io/items/Least_Squares_Circle.html. Examples -------- .. code-block:: python # """ o, uvw, _ = pca_numpy(points) rst = local_coords_numpy(o, uvw, points) x = rst[:, 0] y = rst[:, 1] def dist(xc, yc): return sqrt((x - xc) ** 2 + (y - yc) ** 2) def f(c): Ri = dist(*c) return Ri - Ri.mean() xm = mean(x) ym = mean(y) c0 = xm, ym c, ier = leastsq(f, c0) Ri = dist(*c) R = Ri.mean() residu = sum((Ri - R) ** 2) print(residu) xyz = global_coords_numpy(o, uvw, [[c[0], c[1], 0.0]])[0] o = xyz.tolist() u, v, w = uvw.tolist() return o, w, R
def oriented_bounding_box_numpy(points): """Compute the oriented minimum bounding box of a set of points in 3D space. Notes ----- The implementation is based on the convex hull of the points. Parameters ---------- points : list XYZ coordinates of the points. Returns ------- list The XYZ coordinates of the corners of the bounding box. Examples -------- .. plot:: :include-source: from numpy.random import randint from numpy.random import rand import matplotlib.pyplot as plt from compas.plotters import Bounds from compas.plotters import Cloud3D from compas.plotters import Box from compas.plotters import create_axes_3d from compas.geometry import matrix_from_axis_and_angle from compas.geometry import transform_points from compas.geometry import oriented_bounding_box_numpy clouds = [] for i in range(8): a = randint(1, high=8) * 10 * 3.14159 / 180 d = [1, 1, 1] cloud = rand(100, 3) if i in (1, 2, 5, 6): cloud[:, 0] *= - 10.0 cloud[:, 0] -= 3.0 d[0] = -1 else: cloud[:, 0] *= 10.0 cloud[:, 0] += 3.0 if i in (2, 3, 6, 7): cloud[:, 1] *= - 3.0 cloud[:, 1] -= 3.0 d[1] = -1 else: cloud[:, 1] *= 3.0 cloud[:, 1] += 3.0 if i in (4, 5, 6, 7): cloud[:, 2] *= - 6.0 cloud[:, 2] -= 3.0 d[2] = -1 else: cloud[:, 2] *= 6.0 cloud[:, 2] += 3.0 R = matrix_from_axis_and_angle(d, a) cloud[:] = transform_points(cloud, R) clouds.append(cloud.tolist()) axes = create_axes_3d() bounds = Bounds([point for points in clouds for point in points]) bounds.plot(axes) for cloud in clouds: bbox = oriented_bounding_box_numpy(cloud) Cloud3D(cloud).plot(axes) Box(bbox[1]).plot(axes) plt.show() """ points = asarray(points) n, dim = points.shape assert 2 < dim, "The point coordinates should be at least 3D: %i" % dim points = points[:, :3] hull = ConvexHull(points) volume = None bbox = [] # this can be vectorised! for simplex in hull.simplices: abc = points[simplex] uvw = local_axes(abc[0], abc[1], abc[2]) xyz = points[hull.vertices] rst = local_coords_numpy(abc[0], uvw, xyz) dr, ds, dt = ptp(rst, axis=0) v = dr * ds * dt if volume is None or v < volume: rmin, smin, tmin = argmin(rst, axis=0) rmax, smax, tmax = argmax(rst, axis=0) bbox = [ [rst[rmin, 0], rst[smin, 1], rst[tmin, 2]], [rst[rmax, 0], rst[smin, 1], rst[tmin, 2]], [rst[rmax, 0], rst[smax, 1], rst[tmin, 2]], [rst[rmin, 0], rst[smax, 1], rst[tmin, 2]], [rst[rmin, 0], rst[smin, 1], rst[tmax, 2]], [rst[rmax, 0], rst[smin, 1], rst[tmax, 2]], [rst[rmax, 0], rst[smax, 1], rst[tmax, 2]], [rst[rmin, 0], rst[smax, 1], rst[tmax, 2]], ] bbox = global_coords_numpy(abc[0], uvw, bbox) volume = v return hull, bbox, volume
def oriented_bounding_box_numpy(points): """Compute the oriented minimum bounding box of a set of points in 3D space. Parameters ---------- points : array-like XYZ coordinates of the points. Returns ------- array XYZ coordinates of 8 points defining a box. Raises ------ QhullError If the data is essentially 2D. Notes ----- The *oriented (minimum) bounding box* (OBB) of a given set of points is computed using the following procedure: 1. Compute the convex hull of the points. 2. For each of the faces on the hull: 1. Compute face frame. 2. Compute coordinates of other points in face frame. 3. Find "peak-to-peak" (PTP) values of point coordinates along local axes. 4. Compute volume of box formed with PTP values. 3. Select the box with the smallest volume. Examples -------- Generate a random set of points with :math:`x \in [0, 10]`, :math:`y \in [0, 1]` and :math:`z \in [0, 3]`. Add the corners of the box such that we now the volume is supposed to be :math:`30.0`. >>> points = numpy.random.rand(10000, 3) >>> bottom = numpy.array([[0.0, 0.0, 0.0], [1.0, 0.0, 0.0], [0.0, 1.0, 0.0], [1.0, 1.0, 0.0]]) >>> top = numpy.array([[0.0, 0.0, 1.0], [1.0, 0.0, 1.0], [0.0, 1.0, 1.0], [1.0, 1.0, 1.0]]) >>> points = numpy.concatenate((points, bottom, top)) >>> points[:, 0] *= 10 >>> points[:, 2] *= 3 Rotate the points around an arbitrary axis by an arbitrary angle. >>> from compas.geometry import Rotation >>> from compas.geometry import transform_points_numpy >>> R = Rotation.from_axis_and_angle([1.0, 1.0, 0.0], 0.3 * 3.14159) >>> points = transform_points_numpy(points, R) Compute the volume of the oriented bounding box. >>> bbox = oriented_bounding_box_numpy(points) >>> a = length_vector(subtract_vectors(bbox[1], bbox[0])) >>> b = length_vector(subtract_vectors(bbox[3], bbox[0])) >>> c = length_vector(subtract_vectors(bbox[4], bbox[0])) >>> a * b * c 30.0 """ points = asarray(points) n, dim = points.shape assert 2 < dim, "The point coordinates should be at least 3D: %i" % dim points = points[:, :3] # try: # hull = ConvexHull(points) # except Exception as e: # if 'QH6154' in str(e): # hull = ConvexHull(points, qhull_options='Qb2:0B2:0') # else: # raise e hull = ConvexHull(points) volume = None bbox = [] # this can be vectorised! for simplex in hull.simplices: a, b, c = points[simplex] uvw = local_axes(a, b, c) xyz = points[hull.vertices] rst = local_coords_numpy(a, uvw, xyz) dr, ds, dt = ptp(rst, axis=0) v = dr * ds * dt if volume is None or v < volume: rmin, smin, tmin = amin(rst, axis=0) rmax, smax, tmax = amax(rst, axis=0) bbox = [ [rmin, smin, tmin], [rmax, smin, tmin], [rmax, smax, tmin], [rmin, smax, tmin], [rmin, smin, tmax], [rmax, smin, tmax], [rmax, smax, tmax], [rmin, smax, tmax], ] bbox = global_coords_numpy(a, uvw, bbox) volume = v return bbox