Source code for mrdja.sampling

import random
import math
import numpy as np
from typing import List, Tuple, Optional, FrozenSet, Union
import open3d as o3d

def __set_random_seed(seed):
    """
    Set the random seed for reproducibility.

    :param seed: int
        The seed value for the random number generator.
    """
    random.seed(seed)

def __sample_point_circle_2d(center: Tuple[float, float], radius: float=1) -> Tuple[float, float]:
    """
    Generate a random point on a 2D circle with a specified radius and center, using rejection sampling.

    :param center: tuple
        A tuple (x, y) representing the center of the circle.
    :param radius: float, optional
        The radius of the circle. Default is 1.
    :return: tuple
        A tuple (x, y) representing the coordinates of the sampled point.
    """
    if radius <= 0:
        raise ValueError("Radius must be positive.")
    while True:
        # Generate a random point on the circle with radius 1 centered at (0,0)
        x = random.uniform(-1, 1)
        y = random.uniform(-1, 1)
        
        # Scale the coordinates to the desired radius
        x *= radius
        y *= radius
        
        # Translate the point to the desired center
        x += center[0]
        y += center[1]
        
        # Check if the point is within the circle
        if (x - center[0])**2 + (y - center[1])**2 <= radius**2:
            return x, y

[docs]def sampling_circle_2d(n_samples:int=1, center:Tuple[float, float]=(0,0), radius:float=1, seed:Optional[int]=None): """ Generate random samples on a 2D circle with a specified radius and center. :param n_samples: The number of random samples to generate on the circle. :type n_samples: int :param center: A tuple (x, y) representing the center of the circle. Default is (0,0). :type center: Tuple[float, float] :param radius: The radius of the circle. Default is 1. :type radius: float :param seed: The seed value for the random number generator. Default is None. :type seed: int :return: A list of tuples (x, y) representing the coordinates of the sampled points. :rtype: List[Tuple[float, float]] .. note:: The samples are uniformly distributed on the circle. Examples -------- Generate 100 random samples on a circle with center (2,3) and radius 5: Required imports: >>> import mrdja.sampling as sampling >>> import mrdja.geometry as geometry >>> import matplotlib.pyplot as plt Define the limits of the parallelogram and the number of points to sample: >>> n_samples = 100 >>> center = (2,3) >>> radius = 5 Generate the samples: >>> samples = sampling.sampling_circle_2d(n_samples=100, center=center, radius=radius, seed=42) >>> # list the first 5 samples >>> samples[:5] [(3.3942679845788373, -1.7498924477733304), (-0.24970681630880742, 0.23210738148822774), (4.364712141640124, 4.766994874229113), (1.2192181968527043, -1.7020278056192968), (-0.8136202519639664, 3.0535528810336237)] Plot the samples: >>> fig, ax = plt.subplots() >>> xlim_min = center[0] - radius >>> xlim_max = center[0] + radius >>> ylim_min = center[1] - radius >>> ylim_max = center[1] + radius >>> graph_limits = geometry.get_limits_of_graph_from_limits_of_object(xlim_min, xlim_max, ylim_min, ylim_max) >>> ax.set_xlim(graph_limits[0], graph_limits[1]) # Set x-axis limits >>> ax.set_ylim(graph_limits[2], graph_limits[3]*1.1) # Set y-axis limits >>> ax.scatter(*zip(*samples)) >>> ax.set_aspect('equal') >>> # create title from n_samples, center, and radius, using f-string >>> title = (f'{n_samples} Samples on a Circle with Center {center} and Radius {radius}') >>> ax.set_title(title) >>> # draw also the circle in red >>> circle = plt.Circle(center, radius, color='r', fill=False) >>> ax.add_artist(circle) >>> # draw the X and Y axes in dotted lines >>> ax.axhline(0, linestyle='dotted', color='black') >>> ax.axvline(0, linestyle='dotted', color='black') >>> ax.set_xlabel('X') >>> ax.set_ylabel('Y') >>> plt.show() |sampling_circle_2d| .. |sampling_circle_2d| image:: ../../_static/images/sampling_circle_2d.png """ # Set the random seed for reproducibility if provided if seed is not None: __set_random_seed(seed) # Generate the specified number of samples on the circle samples = [__sample_point_circle_2d(center, radius) for _ in range(n_samples)] return samples
def __sample_point_circle_3d(radius: float = 1.0, center: np.ndarray =np.array([0, 0, 0]), normal: np.ndarray=np.array([0, 0, 1])) -> np.ndarray: ''' Sample a point from a 3D circle using the rejection sampling method. :param radius: The radius of the circle. :type radius: float :param center: The center of the circle. :type center: np.ndarray :param normal: The normal vector of the plane on which the circle lies. :type normal: np.ndarray :return: A list of tuples (x, y, z) representing the coordinates of the sampled points. :rtype: List[Tuple[float, float, float]] .. note:: The samples are uniformly distributed on the circle. ''' normal = normal / np.linalg.norm(normal) while True: # Generate a random point within the bounding box point = center + np.array([random.uniform(-radius, radius) for _ in range(3)]) # Project the point onto the plane defined by the circle projected_point = point - np.dot(point - center, normal) * normal # Check if the projected point lies within the circle if np.linalg.norm(projected_point - center) <= radius: return projected_point
[docs]def sampling_circle_3d(n_samples, radius: float = 1.0, center: np.ndarray =np.array([0, 0, 0]), normal: np.ndarray=np.array([0, 0, 1]), seed: Optional[int]=None) -> List[np.ndarray]: ''' Sample a n_samples number of points from a 3D circle using the rejection sampling method. :param n_samples: The number of samples to generate. :type n_samples: int :param radius: The radius of the circle. :type radius: float :param center: The center of the circle. :type center: np.ndarray :param normal: The normal vector of the plane on which the circle lies. :type normal: np.ndarray :return: A list of tuples (x, y, z) representing the coordinates of the sampled points. :rtype: List[Tuple[float, float, float]] .. note:: The samples are uniformly distributed on the circle. :Example: :: Sample 100 points from a circle in an arbitrary position and with arbitrary normal vector: Required imports: >>> import mrdja.sampling as sampling >>> import mrdja.geometry as geom >>> import mrdja.matplot3d as plt3d >>> import matplotlib.pyplot as plt >>> import numpy as np Define the parameters of the circle and the number of points to sample: >>> n_samples = 100 >>> radius = 5 >>> center = np.array([1, 2, 0]) >>> normal = np.array([1, 1, 0]) / np.linalg.norm(np.array([1, 1, 0])) Sample the points: >>> samples = sampling.sampling_circle_3d(n_samples=n_samples, radius=radius, center=center, normal=normal, seed=42) >>> # list the first 5 samples >>> samples[:5] [array([ 4.07208022, -1.07208022, -2.24970682]), array([-1.56630238, 4.56630238, 1.76699487]), array([0.05579622, 2.94420378, 0.05355288]), array([0.13849159, 2.86150841, 1.49884438]), array([2.62250429, 0.37749571, 0.89265684])] Plot the 3D circle and the samples: >>> fig = plt.figure() >>> ax = fig.add_subplot(111, projection='3d') >>> plt3d.draw_circumference(center=center, radius=radius, normal=normal, color="blue", alpha=0.2, ax=ax) >>> xlim_min = np.min([sample[0] for sample in samples]) >>> xlim_max = np.max([sample[0] for sample in samples]) >>> ylim_min = np.min([sample[1] for sample in samples]) >>> ylim_max = np.max([sample[1] for sample in samples]) >>> zlim_min = np.min([sample[2] for sample in samples]) >>> zlim_max = np.max([sample[2] for sample in samples]) >>> graph_limits = geom.get_limits_of_3d_graph_from_limits_of_object(xlim_min, xlim_max, ylim_min, ylim_max, zlim_min, zlim_max) >>> ax.set_xlim(graph_limits[0], graph_limits[1]) # Set x-axis limits >>> ax.set_ylim(graph_limits[2], graph_limits[3]) # Set y-axis limits >>> ax.set_zlim(graph_limits[4], graph_limits[5]) # Set z-axis limits >>> ax.scatter(*zip(*samples)) >>> # create title from n_samples, center, and radius, using f-string >>> title = (f'{n_samples} Samples on a Circle with Center {center} and Radius {radius}') >>> ax.set_title(title) >>> ax.set_xlabel('X') >>> ax.set_ylabel('Y') >>> ax.set_zlabel('Z') >>> plt.show() |sampling_circle_3d| .. |sampling_circle_3d| image:: ../../_static/images/sampling_circle_3d.png ''' if seed is not None: random.seed(seed) samples = [__sample_point_circle_3d(radius, center, normal) for _ in range(n_samples)] return samples
def __sample_point_parallelogram_2d(normal1: Tuple[float, float], normal2: Tuple[float, float], center: Tuple[float, float], length1: float, length2: float) -> Tuple[float, float]: ''' Sample a point from a parallelogram with sides parallel to the vectors normal1 and normal2. :param normal1: Tuple[float, float] The first vector normal to the sides of the parallelogram. :param normal2: Tuple[float, float] The second vector normal to the sides of the parallelogram. :param center: Tuple[float, float] The center of the parallelogram. :param length1: float The length of the first side of the parallelogram. :param length2: float The length of the second side of the parallelogram. :return: Tuple[float, float] A tuple (x, y) representing the coordinates of the sampled point. ''' # Generate two random numbers between -length1/2 and length1/2 and -length2/2 and length2/2 respectively x = random.uniform(-length1/2, length1/2) y = random.uniform(-length2/2, length2/2) # Those numbers represent the coordinates of the sampled point in the normal coordinate system # Transform the coordinates to the global coordinate system projected_point = (x * normal1[0] + y * normal2[0] + center[0], x * normal1[1] + y * normal2[1] + center[1]) return projected_point
[docs]def sampling_parallelogram_2d(n_samples: int, normal1: Tuple[float, float], normal2: Tuple[float, float], center: Tuple[float, float], length1: float, length2: float, seed: Optional[int] = None) -> List[Tuple[float, float]]: """ Sample a n_samples number of points from a 2D parallelogram. The parallelogram has sides parallel to the vectors normal1 and normal2, with lengths length1 and length2 respectively, and centered at center. :param n_samples: The number of samples to generate. :type n_samples: int :param normal1: The first vector normal to the sides of the parallelogram. :type normal1: Tuple[float, float] :param normal2: The second vector normal to the sides of the parallelogram. :type normal2: Tuple[float, float] :param center: The center of the parallelogram. :type center: Tuple[float, float] :param length1: The length of the first side of the parallelogram. :type length1: float :param length2: The length of the second side of the parallelogram. :type length2: float :return: A list of tuples (x, y) representing the coordinates of the sampled points. :rtype: List[Tuple[float, float]] .. note:: The samples are uniformly distributed on the parallelogram. Examples -------- Sample 100 points from a parallelogram in an arbitrary position and with arbitrary sides: Required imports: >>> import mrdja.sampling as sampling >>> import mrdja.geometry as geometry >>> import matplotlib.pyplot as plt >>> import numpy as np Define the parameters of the parallelogram and the number of points to sample: >>> n_samples = 100 >>> normal1 = (1, 1) >>> normal2 = (-2, 1) >>> center = (1, 2) >>> length1 = 5 >>> length2 = 4 Sample the points: >>> samples = sampling.sampling_parallelogram_2d(n_samples=n_samples, normal1=normal1, normal2=normal2, center=center, length1=length1, length2=length2, seed=42) >>> # list the first 5 samples >>> samples[:5] [(5.497047950508083, 0.7971770131800864), (2.0894606866550145, -0.23201045555911293), (0.7687601714367718, 3.8891540205117074), (6.265387177488898, 2.3086531690418917), (4.3712313429217895, -0.2712020238213664)] Plot the samples: >>> fig, ax = plt.subplots() >>> ax.scatter(*zip(*samples)) >>> ax.set_aspect('equal') >>> center = np.array(center) >>> normal1 = np.array(normal1) >>> normal2 = np.array(normal2) >>> vertex1 = center + normal1 * length1 / 2 + normal2 * length2 / 2 >>> vertex2 = center - normal1 * length1 / 2 + normal2 * length2 / 2 >>> vertex3 = center - normal1 * length1 / 2 - normal2 * length2 / 2 >>> vertex4 = center + normal1 * length1 / 2 - normal2 * length2 / 2 >>> xlim_min = min(vertex1[0], vertex2[0], vertex3[0], vertex4[0]) >>> xlim_max = max(vertex1[0], vertex2[0], vertex3[0], vertex4[0]) >>> ylim_min = min(vertex1[1], vertex2[1], vertex3[1], vertex4[1]) >>> ylim_max = max(vertex1[1], vertex2[1], vertex3[1], vertex4[1]) >>> graph_limits = geometry.get_limits_of_graph_from_limits_of_object(xlim_min, xlim_max, ylim_min, ylim_max) >>> ax.set_xlim(graph_limits[0], graph_limits[1]) # Set x-axis limits >>> ax.set_ylim(graph_limits[2], graph_limits[3]) # Set y-axis limits >>> # create title from n_samples, center, and radius, using f-string >>> title = (f'{n_samples} Samples on a Parallelogram with normal vectors ({normal1[0]}, {normal1[1]}) ' >>> f'and ({normal2[0]}, {normal2[1]}), center ({center[0]}, {center[1]}), length1 of {length1}, ' >>> f'and length2 of {length2}' >>> ) >>> ax.set_title(title) >>> # draw also the parallelogram in red >>> vertices = geometry.get_parallelogram_2d_vertices(center, normal1, normal2, length1, length2) >>> ax.plot([vertices[0][0], vertices[1][0]], [vertices[0][1], vertices[1][1]], color='r') >>> ax.plot([vertices[1][0], vertices[2][0]], [vertices[1][1], vertices[2][1]], color='r') >>> ax.plot([vertices[2][0], vertices[3][0]], [vertices[2][1], vertices[3][1]], color='r') >>> ax.plot([vertices[3][0], vertices[0][0]], [vertices[3][1], vertices[0][1]], color='r') >>> # Draw the X and Y axes in dotted lines >>> ax.axhline(0, linestyle='dotted', color='black') >>> ax.axvline(0, linestyle='dotted', color='black') >>> # Draw the normals at a quarter of their corresponding length >>> quarter_length1 = length1 / 8 >>> quarter_length2 = length2 / 8 >>> arrow_length1 = quarter_length1 / 2 >>> arrow_length2 = quarter_length2 / 2 >>> ax.arrow(center[0], center[1], normal1[0] * quarter_length1, normal1[1] * quarter_length1, >>> head_width=arrow_length1, head_length=arrow_length2, fc='b', ec='b') >>> ax.arrow(center[0], center[1], normal2[0] * quarter_length2, normal2[1] * quarter_length2, >>> head_width=arrow_length2, head_length=arrow_length1, fc='b', ec='b') >>> plt.show() |sampling_parallelogram_2d| .. |sampling_parallelogram_2d| image:: ../../_static/images/sampling_parallelogram_2d.png """ if seed is not None: random.seed(seed) normal1 = np.array(normal1) normal2 = np.array(normal2) center = np.array(center) samples = [__sample_point_parallelogram_2d(normal1, normal2, center, length1, length2) for _ in range(n_samples)] return samples
def __sample_point_alligned_parallelogram_2d(min_x: float, max_x: float, min_y: float, max_y: float) -> Tuple[float, float]: ''' Sample a point from a parallelogram with sides parallel to the x and y axes. :param min_x: float The minimum x coordinate of the parallelogram. :param max_x: float The maximum x coordinate of the parallelogram. :param min_y: float The minimum y coordinate of the parallelogram. :param max_y: float The maximum y coordinate of the parallelogram. :return: Tuple[float, float] A tuple (x, y) representing the coordinates of the sampled point. .. note:: The samples are uniformly distributed on the parallelogram. Examples -------- Sample a point from a parallelogram with sides parallel to the x and y axes: >>> sample_point_alligned_parallelogram(-1, 1, -1, 1) (-0.5, 0.5) ''' x = random.uniform(min_x, max_x) y = random.uniform(min_y, max_y) return x, y
[docs]def sampling_alligned_parallelogram_2d(n_samples: int, min_x: float, max_x: float, min_y: float, max_y: float, seed: Optional[int] = None) -> List[Tuple[float, float]]: ''' Sample points from a parallelogram with sides parallel to the x and y axes. :param n_samples: The number of samples to generate. :type n_samples: int :param min_x: The minimum x coordinate of the parallelogram. :type min_x: float :param max_x: The maximum x coordinate of the parallelogram. :type max_x: float :param min_y: The minimum y coordinate of the parallelogram. :type min_y: float :param max_y: The maximum y coordinate of the parallelogram. :type max_y: float :param seed: The seed to use for the random number generator. :type seed: Optional[int] :return: A list of tuples (x, y) representing the coordinates of the sampled points. :rtype: List[Tuple[float, float]] .. note:: The samples are uniformly distributed on the parallelogram. Examples -------- Sample 100 points from a parallelogram with sides parallel to the x and y axes: Required imports: >>> import mrdja.sampling as sampling >>> import mrdja.geometry as geometry >>> import matplotlib.pyplot as plt Define the limits of the parallelogram and the number of points to sample: >>> n_samples = 100 >>> min_x = -3 >>> max_x = 2 >>> min_y = -1 >>> max_y = 5 Sample the points: >>> samples = sampling.sampling_alligned_parallelogram_2d(n_samples=n_samples, min_x=min_x, >>> max_x=max_x, min_y=min_y, max_y=max_y, seed=42) >>> # list the first 5 samples >>> samples[:5] [(0.19713399228941864, -0.8499354686639984), (-1.6248534081544037, 0.3392644288929365), (0.6823560708200622, 3.0601969245374683), (1.460897838524227, -0.4783670042235031), (-0.8903909015736478, -0.8212166833715779)] Plot the samples: >>> fig, ax = plt.subplots() >>> graph_limits = geometry.get_limits_of_graph_from_limits_of_object(min_x, max_x, min_y, max_y) >>> ax.set_xlim(graph_limits[0], graph_limits[1]) # Set x-axis limits >>> ax.set_ylim(graph_limits[2], graph_limits[3]*1.1) # Set y-axis limits >>> ax.scatter(*zip(*samples)) >>> ax.set_aspect('equal') >>> # create title from n_samples, center, and radius, usign fstring >>> title = (f'{n_samples} Samples on an axes alligned Parallelogram with bottom left corner ' >>> f'({min_x}, {min_y}) and top right corner ({max_x}, {max_y})' >>> ) >>> ax.set_title(title) >>> # draw also the parallelogram in red >>> ax.plot([min_x, max_x], [min_y, min_y], color='r') >>> ax.plot([min_x, max_x], [max_y, max_y], color='r') >>> ax.plot([min_x, min_x], [min_y, max_y], color='r') >>> ax.plot([max_x, max_x], [min_y, max_y], color='r') >>> ax.set_xlabel('X') >>> ax.set_ylabel('Y') >>> # Draw the X and Y axes in dotted lines >>> ax.axhline(0, linestyle='dotted', color='black') >>> ax.axvline(0, linestyle='dotted', color='black') >>> plt.show() |sampling_alligned_parallelogram_2d| .. |sampling_alligned_parallelogram_2d| image:: ../../_static/images/sampling_alligned_parallelogram_2d.png ''' if seed is not None: random.seed(seed) samples = [__sample_point_alligned_parallelogram_2d(min_x, max_x, min_y, max_y) for _ in range(n_samples)] return samples
def __sample_point_parallelogram_3d(normal1: Tuple[float, float, float], normal2: Tuple[float, float, float], center: Tuple[float, float, float], length1: float, length2: float) -> Tuple[float, float, float]: ''' Sample a point from a parallelogram with sides parallel to the vectors normal1 and normal2. :param normal1: Tuple[float, float, float] The first vector normal to the sides of the parallelogram. :param normal2: Tuple[float, float, float] The second vector normal to the sides of the parallelogram. :param center: Tuple[float, float, float] The center of the parallelogram. :param length1: float The length of the first side of the parallelogram. :param length2: float The length of the second side of the parallelogram. :return: Tuple[float, float, float] A tuple (x, y, z) representing the coordinates of the sampled point. ''' # Generate two random numbers between -length1/2 and length1/2 and -length2/2 and length2/2 respectively x = random.uniform(-length1/2, length1/2) y = random.uniform(-length2/2, length2/2) # Those numbers represent the coordinates of the sampled point in the normal coordinate system # Transform the coordinates to the global coordinate system projected_point = x * np.array(normal1) + y * np.array(normal2) + np.array(center) return projected_point def __sample_point_parallelepiped_3d(normal1: Tuple[float, float, float], normal2: Tuple[float, float, float], normal3: Tuple[float, float, float], center: Tuple[float, float, float], length1: float, length2: float, length3: float) -> Tuple[float, float, float]: ''' Sample a point from a parallelepiped with sides parallel to the vectors normal1, normal2 and normal3. :param normal1: Tuple[float, float, float] The first vector normal to the sides of the parallelepiped. :param normal2: Tuple[float, float, float] The second vector normal to the sides of the parallelepiped. :param normal3: Tuple[float, float, float] The third vector normal to the sides of the parallelepiped. :param center: Tuple[float, float, float] The center of the parallelepiped. :param length1: float The length of the first side of the parallelepiped. :param length2: float The length of the second side of the parallelepiped. :param length3: float The length of the third side of the parallelepiped. :return: Tuple[float, float, float] A tuple (x, y, z) representing the coordinates of the sampled point. ''' # Generate two random numbers between -length1/2 and length1/2 and -length2/2 and length2/2 respectively x = random.uniform(-length1/2, length1/2) y = random.uniform(-length2/2, length2/2) z = random.uniform(-length3/2, length3/2) # Those numbers represent the coordinates of the sampled point in the normal coordinate system # Transform the coordinates to the global coordinate system projected_point = x * np.array(normal1) + y * np.array(normal2) + z * np.array(normal3) + np.array(center) return projected_point
[docs]def sampling_parallelogram_3d(n_samples: int, normal1: Tuple[float, float, float], normal2: Tuple[float, float, float], center: Tuple[float, float, float], length1: float, length2: float, seed: Optional[int] = None) -> List[Tuple[float, float, float]]: ''' Sample n_samples points from a parallelogram The parallelogram is defined by the vectors normal1 and normal2, the center and the lengths of the sides. :param n_samples: The number of samples to generate. :type n_samples: int :param normal1: The first vector normal to the sides of the parallelepiped. :type normal1: Tuple[float, float, float] :param normal2: The second vector normal to the sides of the parallelepiped. :type normal2: Tuple[float, float, float] :param center: The center of the parallelepiped. :type center: Tuple[float, float, float] :param length1: The length of the first side of the parallelepiped. :type length1: float :param length2: The length of the second side of the parallelepiped. :type length2: float :param seed: The seed to use for the random number generator. :type seed: Optional[int] :return: A list of tuples (x, y, z) representing the coordinates of the sampled points. :rtype: List[Tuple[float, float, float]] .. note:: The samples are uniformly distributed on the parallelogram. Examples -------- Sample 100 points from a parallelogram in an arbitrary position and with arbitrary sides: Required imports: >>> import mrdja.sampling as sampling >>> import mrdja.geometry as geometry >>> import matplotlib.pyplot as plt >>> import numpy as np Define the parameters of the parallelogram and the number of points to sample: >>> n_samples = 100 >>> normal1 = (1, 1, 0) >>> normal2 = (-2, 1, 1) >>> center = (1, 2, 0) >>> length1 = 5 >>> length2 = 4 Sample the points: >>> samples = sampling.sampling_parallelogram_3d(n_samples=n_samples, normal1=normal1, normal2=normal2, ... center=center, length1=length1, length2=length2, seed=42) >>> # list the first 5 samples >>> samples[:5] [array([ 5.49704795, 0.79717701, -1.89995698]), array([ 2.08946069, -0.23201046, -1.10715705]), array([0.76876017, 3.88915402, 0.70679795]), array([ 6.26538718, 2.30865317, -1.65224467]), array([ 4.37123134, -0.27120202, -1.88081112])] Plot the 3D parallelogram and the samples: >>> samples = np.array(samples) >>> fig = plt.figure() >>> ax = fig.add_subplot(111, projection='3d') >>> ax.scatter(samples[:, 0], samples[:, 1], samples[:, 2]) >>> # create title from n_samples, center, and radius, using fstring >>> title = (f'{n_samples} Samples on a 3D Parallelogram with normal vectors {normal1} and {normal2}, ' >>> f'center {center}, length1 of {length1} and length2 of {length2}') >>> ax.set_title(title) >>> # Draw the parallelogram >>> vertices = geometry.get_parallelogram_3d_vertices(center, normal1, normal2, length1, length2) >>> # Define the edges of the 3d parallelogram >>> edges = [(0, 1), (1, 2), (2, 3), (3, 0)] >>> # Plot the edges >>> for edge in edges: >>> ax.plot([vertices[edge[0]][0], vertices[edge[1]][0]], >>> [vertices[edge[0]][1], vertices[edge[1]][1]], >>> [vertices[edge[0]][2], vertices[edge[1]][2]], color='red') >>> # Draw the normals at a quarter of their corresponding length >>> quarter_length1 = length1 / 4 >>> quarter_length2 = length2 / 4 >>> arrow_length1 = quarter_length1 / 2 >>> arrow_length2 = quarter_length2 / 2 >>> ax.quiver(center[0], center[1], center[2], normal1[0], normal1[1], normal1[2], length=arrow_length1, normalize=False, color='red') >>> ax.quiver(center[0], center[1], center[2], normal2[0], normal2[1], normal2[2], length=arrow_length2, normalize=False, color='red') >>> ax.set_xlabel('X') >>> ax.set_ylabel('Y') >>> ax.set_zlabel('Z') >>> plt.show() |sampling_parallelogram_3d| .. |sampling_parallelogram_3d| image:: ../../_static/images/sampling_parallelogram_3d.png ''' if seed is not None: random.seed(seed) normal1 = np.array(normal1) normal2 = np.array(normal2) center = np.array(center) samples = [__sample_point_parallelogram_3d(normal1, normal2, center, length1, length2) for _ in range(n_samples)] return samples
[docs]def sampling_parallelepiped_3d(n_samples: int, normal1: Tuple[float, float, float], normal2: Tuple[float, float, float], normal3: Tuple[float, float, float], center: Tuple[float, float, float], length1: float, length2: float, length3: float, seed: Optional[int] = None) -> List[Tuple[float, float, float]]: ''' Sample n_samples points from a parallelepiped The parallelogram is defined by the vectors normal1, normal2 and normal3, the center and the lengths of the sides. :param n_samples: The number of samples to generate. :type n_samples: int :param normal1: The first vector normal to the sides of the parallelepiped. :type normal1: Tuple[float, float, float] :param normal2: The second vector normal to the sides of the parallelepiped. :type normal2: Tuple[float, float, float] :param normal3: The third vector normal to the sides of the parallelepiped. :type normal3: Tuple[float, float, float] :param center: The center of the parallelepiped. :type center: Tuple[float, float, float] :param length1: The length of the first side of the parallelepiped. :type length1: float :param length2: The length of the second side of the parallelepiped. :type length2: float :param length3: The length of the third side of the parallelepiped. :type length3: float :param seed: The seed to use for the random number generator. :type seed: Optional[int] :return: A list of tuples (x, y, z) representing the coordinates of the sampled points. :rtype: List[Tuple[float, float, float]] .. note:: The samples are uniformly distributed on the parallelepiped. Examples -------- Sample 100 points from a parallelepiped in an arbitrary position and with arbitrary sides: Required imports: >>> import mrdja.sampling as sampling >>> import mrdja.geometry as geometry >>> import matplotlib.pyplot as plt >>> import numpy as np Define the parameters of the parallelepiped and the number of points to sample: >>> n_samples = 100 >>> normal1 = (1, 1, 0) >>> normal2 = (-2, 1, 1) >>> normal3 = (1, -1, 3) >>> center = (1, 2, 0) >>> length1 = 5 >>> length2 = 4 >>> length3 = 3 Sample the points: >>> samples = sampling.sampling_parallelepiped_3d(n_samples=n_samples, normal1=normal1, normal2=normal2, ... normal3=normal3, center=center, length1=length1, ... length2=length2, length3=length3, seed=42) >>> # list the first 5 samples >>> samples[:5] [array([ 4.82213591, 1.47208906, -3.92469311]), array([-1.74561756, 1.03184009, 2.53618024]), array([ 6.03115264, 2.54288771, -2.35494829]), array([ 0.91594816, -1.49252787, -1.07725051]), array([ 1.49163196, -2.02162286, 0.14431054])] Plot the 3D parallelepiped and the samples: >>> samples = np.array(samples) >>> fig = plt.figure() >>> ax = fig.add_subplot(111, projection='3d') >>> ax.scatter(samples[:, 0], samples[:, 1], samples[:, 2]) >>> # create title from n_samples, center, and radius, using fstring >>> title = (f'{n_samples} Samples on a 3D Parallelepiped with normal vectors {normal1}, {normal2} and {normal3}, ' >>> f'center {center}, length1 of {length1}, length2 of {length2} and length3 of {length3}') >>> ax.set_title(title) >>> # Draw the parallelepiped >>> vertices = geometry.get_parallelepiped_3d_vertices(center, normal1, normal2, normal3, length1, length2, length3) >>> # Define the edges of the 3d parallelepiped >>> edges = [(0, 1), (1, 2), (2, 3), (3, 0), (4, 5), (5, 6), >>> (6, 7), (7, 4), (0, 4), (1, 5), (2, 6), (3, 7)] >>> # Plot the edges >>> for edge in edges: >>> ax.plot([vertices[edge[0]][0], vertices[edge[1]][0]], >>> [vertices[edge[0]][1], vertices[edge[1]][1]], >>> [vertices[edge[0]][2], vertices[edge[1]][2]], color='red') >>> # Draw the normals at a quarter of their corresponding length >>> quarter_length1 = length1 / 4 >>> quarter_length2 = length2 / 4 >>> arrow_length1 = quarter_length1 / 2 >>> arrow_length2 = quarter_length2 / 2 >>> ax.quiver(center[0], center[1], center[2], normal1[0], normal1[1], normal1[2], length=arrow_length1, normalize=False, color='red') >>> ax.quiver(center[0], center[1], center[2], normal2[0], normal2[1], normal2[2], length=arrow_length2, normalize=False, color='red') >>> ax.quiver(center[0], center[1], center[2], normal3[0], normal3[1], normal3[2], length=arrow_length2, normalize=False, color='red') >>> ax.set_xlabel('X') >>> ax.set_ylabel('Y') >>> ax.set_zlabel('Z') >>> plt.show() |sampling_parallelepiped_3d| .. |sampling_parallelepiped_3d| image:: ../../_static/images/sampling_parallelepiped_3d.png ''' if seed is not None: random.seed(seed) normal1 = np.array(normal1) normal2 = np.array(normal2) normal3 = np.array(normal3) center = np.array(center) samples = [__sample_point_parallelepiped_3d(normal1, normal2, normal3, center, length1, length2, length3) for _ in range(n_samples)] return samples
def __sample_point_cuboid(a, b, c, d, h): u = random.random() v = random.random() w = random.random() x = a[0] + u * (b[0] - a[0]) + v * (c[0] - a[0]) + w * (d[0] - a[0]) y = a[1] + u * (b[1] - a[1]) + v * (c[1] - a[1]) + w * (d[1] - a[1]) z = a[2] + u * (b[2] - a[2]) + v * (c[2] - a[2]) + w * (d[2] - a[2]) + h return x, y, z
[docs]def sampling_cuboid(n_samples, a, b, c, d, h): samples = [__sample_point_cuboid(a, b, c, d, h) for _ in range(n_samples)] return samples
def __sample_point_sphere(center:Tuple[float, float, float]=(0, 0, 0), radius:float=1): """ Generate a random point on a sphere with a specified radius and center, using rejection sampling. :param center: tuple A tuple (x, y, z) representing the center of the circle. :param radius: float, optional The radius of the circle. Default is 1. :return: tuple A tuple (x, y, z) representing the coordinates of the sampled point. """ while True: # Generate a random point on the circle with radius 1 centered at (0,0) x = random.uniform(-1, 1) y = random.uniform(-1, 1) z = random.uniform(-1, 1) # Scale the coordinates to the desired radius x *= radius y *= radius z *= radius # Translate the point to the desired center x += center[0] y += center[1] z += center[2] # Check if the point is within the circle if (x - center[0])**2 + (y - center[1])**2 + (z - center[2])**2 <= radius**2: return x, y, z
[docs]def sampling_sphere(n_samples:int=1, center:Tuple[float, float, float]=(0,0,0), radius:float=1, seed:Optional[int]=None) \ -> List[Tuple[float, float, float]]: """ Generate random samples on a sphere with a specified radius and center. :param n_samples: The number of random samples to generate on the circle. :type n_samples: int :param center: A tuple (x, y, z) representing the center of the circle. Default is (0,0). :type center: Tuple[float, float, float] :param radius: The radius of the circle. Default is 1. :type radius: float :param seed: The seed value for the random number generator. Default is None. :type seed: Optional[int] :return: A list of tuples (x, y, z) representing the coordinates of the sampled points. :rtype: List[Tuple[float, float, float]] .. note:: The samples are uniformly distributed on the sphere. Examples -------- Sample 100 points from a sphere: Required imports: >>> import mrdja.sampling as sampling >>> import mrdja.geometry as geometry >>> import matplotlib.pyplot as plt >>> import numpy as np Define the parameters of the sphere and the number of points to sample: >>> n_samples = 100 >>> center = (2, 3, 1) >>> radius = 5 Sample the points: >>> samples = sampling.sampling_sphere(n_samples=n_samples, center=center, radius=radius, seed=42) >>> # list the first 5 samples >>> samples[:5] [(-0.7678926185117723, 5.364712141640124, 2.766994874229113), (2.4494148060321668, 0.204406220406967, 1.8926568387590872), (3.9813939498822686, 1.4025051651799187, -2.4452050018821847), (5.071282732743802, 5.297317866938179, 1.3622809145470074), (6.731157639793706, 1.7853437720835348, 1.52040631273227)] Plot the samples: >>> samples = np.array(samples) >>> fig = plt.figure() >>> ax = fig.add_subplot(111, projection='3d') >>> ax.scatter(samples[:, 0], samples[:, 1], samples[:, 2]) >>> # plot the sphere >>> u, v = np.mgrid[0:2*np.pi:20j, 0:np.pi:10j] >>> x = radius*np.cos(u)*np.sin(v) + center[0] >>> y = radius*np.sin(u)*np.sin(v) + center[1] >>> z = radius*np.cos(v) + center[2] >>> ax.plot_wireframe(x, y, z, color="r") >>> title = f'{n_samples} Samples on a Sphere with Center ({center[0]}, {center[1]}, {center[2]}) and Radius {radius}' >>> ax.set_title(title) >>> ax.set_xlabel('X') >>> ax.set_ylabel('Y') >>> ax.set_zlabel('Z') >>> plt.show() |sampling_sphere| .. |sampling_sphere| image:: ../../_static/images/sampling_sphere.png """ # Set the random seed for reproducibility if provided if seed is not None: __set_random_seed(seed) # Generate the specified number of samples on the circle samples = [__sample_point_sphere(center, radius) for _ in range(n_samples)] return samples
[docs]def sampling_np_array_elements(elements:np.ndarray, num_samplings: int = 1, replacement: bool=False, len_elements: Optional[int]=None, seed: Optional[int]=None) -> np.ndarray: """ Sample elements from a numpy array. :param elements: The array of elements to sample from. :type elements: np.ndarray :param num_samplings: The number of elements to sample. Default is 1. :type num_samplings: int :param replacement: Whether to sample with replacement or not. Default is False. :type replacement: bool :param len_elements: The length of the elements array. Default is None. :type len_elements: int :param seed: The seed value for the random number generator. Default is None. :type seed: int :return: The sampled elements. :rtype: np.ndarray Examples -------- >>> import numpy as np >>> import mrdja.sampling as sampling >>> sampling.sampling_np_array_elements(np.array([1,2,3,4,5]), 3, False, seed=42) array([2, 5, 3]) >>> sampling.sampling_np_array_elements(np.array([(1,2),(3,4),(5,6),(7,8),(9,10)]), 3, False, seed=42) array([[ 3, 4], [ 9, 10], [ 5, 6]]) """ if len_elements is None: len_elements = len(elements) if seed is not None: np.random.seed(seed) random_elements_indices = np.random.choice(range(len_elements), num_samplings, replace=replacement) random_elements = elements[random_elements_indices] return random_elements
[docs]def sampling_pcd_points(pcd: o3d.geometry.PointCloud, num_points: int = 1, seed: Optional[int] = None): """ Sample points from a point cloud. :param pcd: The point cloud to sample from. :type pcd: o3d.geometry.PointCloud :param num_points: The number of points to sample. Default is 1. :type num_points: int :param seed: The seed value for the random number generator. Default is None. :type seed: int :return: The sampled points. :rtype: np.ndarray Examples -------- >>> import open3d as o3d >>> import mrdja.sampling as sampling >>> # Create a point cloud from random points sampled from a 3D parallelogram >>> n_samples = 1000 >>> normal1 = (1, 1, 0) >>> normal2 = (-2, 1, 1) >>> normal3 = (1, -1, 3) >>> center = (1, 2, 0) >>> length1 = 5 >>> length2 = 4 >>> length3 = 3 >>> samples = sampling.sampling_parallelogram_3d(n_samples=n_samples, normal1=normal1, normal2=normal2, normal3=normal3, center=center, length1=length1, length2=length2, length3=length3, seed=42) >>> samples[:5] [array([ 4.82213591, 1.47208906, -3.92469311]), array([-1.74561756, 1.03184009, 2.53618024]), array([ 6.03115264, 2.54288771, -2.35494829]), array([ 0.91594816, -1.49252787, -1.07725051]), array([ 1.49163196, -2.02162286, 0.14431054])] >>> pcd = o3d.geometry.PointCloud() >>> pcd.points = o3d.utility.Vector3dVector(samples) >>> # paint the point cloud in blue >>> pcd.paint_uniform_color([0, 0, 1]) >>> # Sample 300 random points from the point cloud >>> sampled_points = sampling.sampling_pcd_points(pcd, 300, seed=42) >>> sampled_points[:5] array([[-3.29369778, 2.38822478, 3.96820711], [-0.15203634, 3.14597455, 3.11019749], [ 2.41599518, -1.37126689, -3.70481201], [ 3.02922805, -2.15456602, -0.98563083], [ 0.75636732, 5.3772318 , -2.63251376]]) >>> # create a point cloud from the sampled points >>> sampled_pcd = o3d.geometry.PointCloud() >>> sampled_pcd.points = o3d.utility.Vector3dVector(sampled_points) >>> # paint the sampled points in red >>> sampled_pcd.paint_uniform_color([1, 0, 0]) >>> # visualize the point clouds >>> o3d.visualization.draw_geometries([pcd, sampled_pcd]) |sampling_pcd_points| .. |sampling_pcd_points| image:: ../../_static/images/sampling_pcd_points.png """ points = np.asarray(pcd.points) random_points = sampling_np_array_elements(elements=points, num_samplings=num_points, seed=seed) return random_points
[docs]def sampling_np_arrays_from_enumerable(source_list: Union[List, np.ndarray], cardinality_of_np_arrays: int, number_of_np_arrays: int=1, num_source_elems: Optional[int] = None, seed: Optional[int] = None) -> List[np.ndarray]: """ Returns a list with **number_of_np_arrays** numpy arrays of size **cardinality_of_np_arrays** with random elements from a list or numpy array. :param source_list: The list or numpy array to sample from. :type source_list: Union[list, np.ndarray] :param cardinality_of_np_arrays: The cardinality of the numpy arrays to generate. :type cardinality_of_np_arrays: int :param number_of_np_arrays: The number of numpy arrays to generate. Default is 1. :type number_of_np_arrays: int :param num_source_elems: The number of elements in the source list or numpy array. Default is None. :type num_source_elems: int :param seed: The seed value for the random number generator. Default is None. :type seed: int :return: List of numpy arrays containing the sampled arrays. :rtype: List[np.ndarray] :Example: :: >>> import mrdja.sampling as sampling >>> import numpy as np >>> sampling.sampling_np_arrays_from_enumerable(np.array([1,2,3,4,5,6,7,8,9,10]), 3, 2, seed=42) >>> [array([9, 2, 6]), array([1, 8, 3])] >>> np.random.seed(42) >>> random_3d_points = np.random.rand(100, 3) >>> random_np_arrays_of_points = sampling.sampling_np_arrays_from_enumerable(random_3d_points, cardinality_of_np_arrays=3, number_of_np_arrays=4, seed=42) >>> random_np_arrays_of_points >>> [array([[0.85300946, 0.29444889, 0.38509773], >>> [0.72821635, 0.36778313, 0.63230583], >>> [0.54873379, 0.6918952 , 0.65196126]]), >>> array([[0.32320293, 0.51879062, 0.70301896], >>> [0.11986537, 0.33761517, 0.9429097 ], >>> [0.18657006, 0.892559 , 0.53934224]]), >>> array([[0.14092422, 0.80219698, 0.07455064], >>> [0.94045858, 0.95392858, 0.91486439], >>> [0.60754485, 0.17052412, 0.06505159]]), >>> array([[0.37454012, 0.95071431, 0.73199394], >>> [0.59789998, 0.92187424, 0.0884925 ], >>> [0.11959425, 0.71324479, 0.76078505]])] """ if num_source_elems is None: num_source_elems = len(source_list) if seed is not None: np.random.seed(seed) random_elems_indices = np.random.choice(range(num_source_elems), size= cardinality_of_np_arrays * number_of_np_arrays, replace=False) random_elems = np.array(source_list)[random_elems_indices] # Split random_elems into sets sampled_np_arrays = [random_elems[i * cardinality_of_np_arrays:(i + 1) * cardinality_of_np_arrays] for i in range(number_of_np_arrays)] return sampled_np_arrays