Esempio n. 1
0
    def best_fit(cls,
                 points: array_like,
                 tol: Optional[float] = None,
                 **kwargs) -> Line:
        """
        Return the line of best fit for a set of points.

        Parameters
        ----------
        points : array_like
             Input 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
        -------
        Line
            The line of best fit.

        Raises
        ------
        ValueError
            If the points are concurrent.

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

        >>> points = [[0, 0], [1, 2], [2, 1], [2, 3], [3, 2]]
        >>> line = Line.best_fit(points)

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

        >>> line.point
        Point([1.6, 1.6])

        The line direction is a unit vector.

        >>> line.direction.round(3)
        Vector([0.707, 0.707])

        """
        points_spatial = Points(points)

        if points_spatial.are_concurrent(tol=tol):
            raise ValueError("The points must not be concurrent.")

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

        _, _, vh = np.linalg.svd(points_centered, **kwargs)
        direction = vh[0, :]

        return cls(centroid, direction)
Esempio n. 2
0
    def best_fit(cls, points: array_like) -> Line:
        """
        Return the line of best fit for a set of points.

        Parameters
        ----------
        points : array_like
             Input points.

        Returns
        -------
        Line
            The line of best fit.

        Raises
        ------
        ValueError
            If the points are concurrent.

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

        >>> points = [[0, 0], [1, 2], [2, 1], [2, 3], [3, 2]]
        >>> line = Line.best_fit(points)

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

        >>> line.point
        Point([1.6, 1.6])

        The line direction is a unit vector.

        >>> line.direction.round(3)
        Vector([0.707, 0.707])

        """
        points_spatial = Points(points)

        if points_spatial.are_concurrent(tol=0):
            raise ValueError("The points must not be concurrent.")

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

        _, _, vh = np.linalg.svd(points_centered)
        direction = vh[0, :]

        return cls(centroid, direction)
Esempio n. 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)
Esempio n. 4
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)
Esempio n. 5
0
    def from_points(cls, point_a: array_like, point_b: array_like,
                    point_c: array_like, **kwargs) -> Plane:
        """
        Instantiate a plane from three points.

        The three points lie on the plane.

        Parameters
        ----------
        point_a, point_b, point_c: array_like
            Three points defining the plane.
        kwargs: dict, optional
            Additional keywords passed to :meth:`Points.are_collinear`.

        Returns
        -------
        Plane
            Plane containing the three input points.

        Raises
        ------
        ValueError
            If the points are collinear.

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

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

        The order of the points affects the direction of the normal vector.

        >>> Plane.from_points([0, 0], [3, 3], [1, 0])
        Plane(point=Point([0, 0, 0]), normal=Vector([ 0,  0, -3]))

        >>> Plane.from_points([0, 0], [0, 1], [0, 3])
        Traceback (most recent call last):
        ...
        ValueError: The points must not be collinear.

        """
        if Points([point_a, point_b, point_c]).are_collinear(**kwargs):
            raise ValueError("The points must not be collinear.")

        vector_ab = Vector.from_points(point_a, point_b)
        vector_ac = Vector.from_points(point_a, point_c)

        return Plane.from_vectors(point_a, vector_ab, vector_ac)
Esempio n. 6
0
    def __init__(self, point_a: array_like, point_b: array_like,
                 point_c: array_like):

        self.point_a = Point(point_a)
        self.point_b = Point(point_b)
        self.point_c = Point(point_c)

        if not (self.point_a.dimension == self.point_b.dimension ==
                self.point_c.dimension):
            raise ValueError("The points must have the same dimension.")

        if Points([self.point_a, self.point_b, self.point_c]).are_collinear():
            raise ValueError("The points must not be collinear.")

        self.dimension = self.point_a.dimension
Esempio n. 7
0
    def to_points(self, n_angles: int = 30) -> Points:
        """
        Return points on the surface of the sphere.

        Parameters
        ----------
        n_angles: int
            Number of angles used to generate the points.

        Returns
        -------
        Points
            Points on the surface of the sphere.

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

        >>> sphere = Sphere([0, 0, 0], 1)

        >>> sphere.to_points(n_angles=3).round().unique()
        Points([[ 0., -1.,  0.],
                [ 0.,  0., -1.],
                [ 0.,  0.,  1.],
                [ 0.,  1.,  0.]])

        >>> sphere.to_points(n_angles=4).round(3).unique()
        Points([[-0.75 , -0.433, -0.5  ],
                [-0.75 , -0.433,  0.5  ],
                [ 0.   ,  0.   , -1.   ],
                [ 0.   ,  0.   ,  1.   ],
                [ 0.   ,  0.866, -0.5  ],
                [ 0.   ,  0.866,  0.5  ],
                [ 0.75 , -0.433, -0.5  ],
                [ 0.75 , -0.433,  0.5  ]])

        """
        X, Y, Z = self.to_mesh(n_angles)

        points = _mesh_to_points(X, Y, Z)

        return Points(points)
Esempio n. 8
0
    def to_points(self, **kwargs) -> Points:
        """
        Return points on the surface of the object.

        Parameters
        ----------
        kwargs: dict, optional
            Additional keywords passed to the `to_mesh` method of the class.

        Returns
        -------
        Points
            Points on the surface of the object.

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

        >>> sphere = Sphere([0, 0, 0], 1)

        >>> sphere.to_points(n_angles=3).round().unique()
        Points([[ 0., -1.,  0.],
                [ 0.,  0., -1.],
                [ 0.,  0.,  1.],
                [ 0.,  1.,  0.]])

        >>> sphere.to_points(n_angles=4).round(3).unique()
        Points([[-0.75 , -0.433, -0.5  ],
                [-0.75 , -0.433,  0.5  ],
                [ 0.   ,  0.   , -1.   ],
                [ 0.   ,  0.   ,  1.   ],
                [ 0.   ,  0.866, -0.5  ],
                [ 0.   ,  0.866,  0.5  ],
                [ 0.75 , -0.433, -0.5  ],
                [ 0.75 , -0.433,  0.5  ]])

        """
        X, Y, Z = self.to_mesh(**kwargs)
        points = _mesh_to_points(X, Y, Z)

        return Points(points)
Esempio n. 9
0
    def centroid(self) -> Point:
        """
        Return the centroid of the triangle.

        Returns
        -------
        Point
            Centroid of the triangle.

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

        >>> Triangle([0, 0], [0, 1], [1, 0]).centroid().round(3)
        Point([0.333, 0.333])

        >>> Triangle([0, 0, 0], [1, 2, 3], [4, 5, 6]).centroid().round(3)
        Point([1.667, 2.333, 3.   ])

        """
        return Points([self.point_a, self.point_b, self.point_c]).centroid()
Esempio n. 10
0
    def to_points(
        self, lims_x: array_like = (-1, 1), lims_y: array_like = (-1, 1)
    ) -> Points:
        """
        Return four points on the plane.

        The coordinate matrices used for 3D plotting are converted to points.

        Parameters
        ----------
        lims_x, lims_y : (2,) tuple
            x and y limits of the plane.
            Tuple of form (min, max). The default is (-1, 1).
            The point on the plane is used as the origin.

        Returns
        -------
        Points
            Four 3D points on the plane.

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

        >>> Plane([0, 0, 0], [0, 0, 1]).to_points()
        Points([[-1., -1.,  0.],
                [ 1., -1.,  0.],
                [-1.,  1.,  0.],
                [ 1.,  1.,  0.]])

        """
        X, Y, Z = self.to_mesh(lims_x, lims_y)

        points = _mesh_to_points(X, Y, Z)

        return Points(points)
Esempio n. 11
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)
Esempio n. 12
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)
Esempio n. 13
0
    def is_coplanar(self, other: 'Line', **kwargs: float) -> bool:
        """
        Check if the line is coplanar with another.

        Parameters
        ----------
        other : Line
            Other line.
        kwargs : dict, optional
            Additional keywords passed to :func:`numpy.linalg.matrix_rank`

        Returns
        -------
        bool
            True if the line is coplanar; false otherwise.

        Raises
        ------
        TypeError
            If the input is not a line.

        References
        ----------
        http://mathworld.wolfram.com/Coplanar.html

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

        >>> line_a = Line(point=[0, 0, 0], direction=[1, 0, 0])
        >>> line_b = Line([-5, 3, 0], [7, 1, 0])
        >>> line_c = Line([0, 0, 0], [0, 0, 1])

        >>> line_a.is_coplanar(line_b)
        True

        >>> line_a.is_coplanar(line_c)
        True

        >>> line_b.is_coplanar(line_c)
        False

        The input must be another line.

        >>> from skspatial.objects import Plane

        >>> line_a.is_coplanar(Plane(line_a.point, line_a.vector))
        Traceback (most recent call last):
        ...
        TypeError: The input must also be a line.

        """
        if not isinstance(other, type(self)):
            raise TypeError("The input must also be a line.")

        point_1 = self.point
        point_2 = self.to_point()
        point_3 = other.point
        point_4 = other.to_point()

        points = Points([point_1, point_2, point_3, point_4])

        return points.are_coplanar(**kwargs)
Esempio n. 14
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)