Exemple #1
0
    def best_fit(cls, points: array_like) -> Plane:
        """
        Return the plane of best fit for a set of 3D points.

        Parameters
        ----------
        points : array_like
             Input 3D points.

        Returns
        -------
        Plane
            The plane of best fit.

        Raises
        ------
        ValueError
            If the points are collinear or are not 3D.

        Examples
        --------
        >>> from skspatial.objects import Plane

        >>> points = [[0, 0, 0], [1, 0, 0], [0, 1, 0], [0, 0, 1]]
        >>> plane = Plane.best_fit(points)

        The point on the plane is the centroid of the points.

        >>> plane.point
        Point([0.25, 0.25, 0.25])

        The plane normal is a unit vector.

        >>> plane.normal.round(3)
        Vector([-0.577, -0.577, -0.577])

        >>> Plane.best_fit([[0, 0, 0], [1, 0, 0], [0, 1, 0], [1, 1, 0]])
        Plane(point=Point([0.5, 0.5, 0. ]), normal=Vector([0., 0., 1.]))

        """
        points = Points(points)

        if points.dimension != 3:
            raise ValueError("The points must be 3D.")

        if points.are_collinear(tol=0):
            raise ValueError("The points must not be collinear.")

        points_centered, centroid = points.mean_center(return_centroid=True)

        u, _, _ = np.linalg.svd(points_centered.T)
        normal = Vector(u[:, -1])

        return cls(centroid, normal)
Exemple #2
0
    def from_vectors(cls, point: array_like, vector_a: array_like,
                     vector_b: array_like, **kwargs) -> Plane:
        """
        Instantiate a plane from a point and two vectors.

        The two vectors span the plane.

        Parameters
        ----------
        point : array_like
            Point on the plane.
        vector_a, vector_b : array_like
            Input vectors.
        kwargs : dict, optional
            Additional keywords passed to :meth:`Vector.is_parallel`.

        Returns
        -------
        Plane
            Plane containing input point and spanned by the two input vectors.

        Raises
        ------
        ValueError
            If the vectors are parallel.

        Examples
        --------
        >>> from skspatial.objects import Plane

        >>> Plane.from_vectors([0, 0], [1, 0], [0, 1])
        Plane(point=Point([0, 0, 0]), normal=Vector([0, 0, 1]))

        >>> Plane.from_vectors([0, 0], [1, 0], [2, 0])
        Traceback (most recent call last):
        ...
        ValueError: The vectors must not be parallel.

        """
        vector_a = Vector(vector_a)

        if vector_a.is_parallel(vector_b, **kwargs):
            raise ValueError("The vectors must not be parallel.")

        # The cross product returns a 3D vector.
        vector_normal = vector_a.cross(vector_b)

        # Convert the point to 3D so that it matches the vector dimension.
        point = Point(point).set_dimension(3)

        return cls(point, vector_normal)
Exemple #3
0
    def best_fit(cls, points: array_like) -> Sphere:
        """
        Return the sphere of best fit for a set of 3D points.

        Parameters
        ----------
        points : array_like
             Input 3D points.

        Returns
        -------
        Sphere
            The sphere of best fit.

        Raises
        ------
        ValueError
            If the points are not 3D.
            If there are fewer than four points.
            If the points lie in a plane.

        Examples
        --------
        >>> import numpy as np

        >>> from skspatial.objects import Sphere

        >>> points = [[1, 0, 1], [0, 1, 1], [1, 2, 1], [1, 1, 2]]
        >>> sphere = Sphere.best_fit(points)

        >>> sphere.point
        Point([1., 1., 1.])

        >>> np.round(sphere.radius, 2)
        1.0

        """
        points = Points(points)

        if points.dimension != 3:
            raise ValueError("The points must be 3D.")

        if points.shape[0] < 4:
            raise ValueError("There must be at least 4 points.")

        if points.affine_rank() != 3:
            raise ValueError("The points must not be in a plane.")

        n = points.shape[0]
        A = np.hstack((2 * points, np.ones((n, 1))))
        b = (points**2).sum(axis=1)

        c, _, _, _ = np.linalg.lstsq(A, b, rcond=None)

        center = c[:3]
        radius = float(np.sqrt(np.dot(center, center) + c[3]))

        return cls(center, radius)
Exemple #4
0
    def best_fit(cls, points: array_like, **kwargs) -> 'Plane':
        """
        Return the plane of best fit for a set of 3D points.

        Parameters
        ----------
        points : array_like
             Input 3D points.
        kwargs : dict, optional
            Additional keywords passed to :func:`numpy.linalg.svd`

        Returns
        -------
        Plane
            The plane of best fit.

        Raises
        ------
        ValueError
            If the points are collinear or are not 3D.

        Examples
        --------
        >>> from skspatial.objects import Plane

        >>> points = [[0, 0, 0], [1, 0, 0], [0, 1, 0], [0, 0, 1]]
        >>> plane = Plane.best_fit(points)

        The point on the plane is the centroid of the points.

        >>> plane.point
        Point([0.25, 0.25, 0.25])

        The plane normal is a unit vector.

        >>> plane.normal.round(3)
        Vector([-0.577, -0.577, -0.577])

        >>> points = [[0, 0, 0], [1, 0, 0], [0, 1, 0], [1, 1, 0]]

        >>> Plane.best_fit(points)
        Plane(point=Point([0.5, 0.5, 0. ]), normal=Vector([0., 0., 1.]))

        >>> Plane.best_fit(points, full_matrices=False)
        Plane(point=Point([0.5, 0.5, 0. ]), normal=Vector([0., 0., 1.]))

        """
        points = Points(points)

        if points.dimension != 3:
            raise ValueError("The points must be 3D.")

        if points.are_collinear(tol=0):
            raise ValueError("The points must not be collinear.")

        points_centered, centroid = points.mean_center(return_centroid=True)

        u, sigma, _ = np.linalg.svd(points_centered.T, **kwargs)
        error = min(sigma)
        normal = Vector(u[:, -1])

        return cls(centroid, normal, error=error)
Exemple #5
0
    def best_fit(cls, points: array_like) -> Circle:
        """
        Return the sphere of best fit for a set of 2D points.

        Parameters
        ----------
        points : array_like
             Input 2D points.

        Returns
        -------
        Circle
            The circle of best fit.

        Raises
        ------
        ValueError
            If the points are not 2D.
            If there are fewer than three points.
            If the points are collinear.

        Reference
        ---------
        https://meshlogic.github.io/posts/jupyter/curve-fitting/fitting-a-circle-to-cluster-of-3d-points/

        Examples
        --------
        >>> import numpy as np

        >>> from skspatial.objects import Circle

        >>> points = [[1, 1], [2, 2], [3, 1]]
        >>> circle = Circle.best_fit(points)

        >>> circle.point
        Point([2., 1.])

        >>> np.round(circle.radius, 2)
        1.0

        """
        points = Points(points)

        if points.dimension != 2:
            raise ValueError("The points must be 2D.")

        if points.shape[0] < 3:
            raise ValueError("There must be at least 3 points.")

        if points.affine_rank() != 2:
            raise ValueError("The points must not be collinear.")

        n = points.shape[0]
        A = np.hstack((2 * points, np.ones((n, 1))))
        b = (points**2).sum(axis=1)
        c = np.linalg.lstsq(A, b, rcond=None)[0]

        center = c[:2]
        radius = np.sqrt(c[2] + c[0] ** 2 + c[1] ** 2)

        return cls(center, radius)
Exemple #6
0
    def best_fit(cls,
                 points: array_like,
                 tol: Optional[float] = None,
                 **kwargs) -> Plane:
        """
        Return the plane of best fit for a set of 3D points.

        Parameters
        ----------
        points : array_like
             Input 3D points.
        tol : float | None, optional
            Keyword passed to :meth:`Points.are_collinear` (default None).
        kwargs : dict, optional
            Additional keywords passed to :func:`numpy.linalg.svd`

        Returns
        -------
        Plane
            The plane of best fit.

        Raises
        ------
        ValueError
            If the points are collinear or are not 3D.

        References
        ----------
        Using SVD for some fitting problems
        Inge Söderkvist
        Algorithm 3.1
        https://www.ltu.se/cms_fs/1.51590!/svd-fitting.pdf

        Examples
        --------
        >>> from skspatial.objects import Plane

        >>> points = [[0, 0, 0], [1, 0, 0], [0, 1, 0], [0, 0, 1]]
        >>> plane = Plane.best_fit(points)

        The point on the plane is the centroid of the points.

        >>> plane.point
        Point([0.25, 0.25, 0.25])

        The plane normal is a unit vector.

        >>> plane.normal.round(3)
        Vector([-0.577, -0.577, -0.577])

        >>> points = [[0, 0, 0], [1, 0, 0], [0, 1, 0], [1, 1, 0]]

        >>> Plane.best_fit(points)
        Plane(point=Point([0.5, 0.5, 0. ]), normal=Vector([0., 0., 1.]))

        >>> Plane.best_fit(points, full_matrices=False)
        Plane(point=Point([0.5, 0.5, 0. ]), normal=Vector([0., 0., 1.]))

        """
        points = Points(points)

        if points.dimension != 3:
            raise ValueError("The points must be 3D.")

        if points.are_collinear(tol=tol):
            raise ValueError("The points must not be collinear.")

        points_centered, centroid = points.mean_center(return_centroid=True)

        u, _, _ = np.linalg.svd(points_centered.T, **kwargs)
        normal = Vector(u[:, 2])

        return cls(centroid, normal)