Example #1
0
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
Example #2
0
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
Example #3
0
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