Skip to content

Selection utilities

napari_threedee.utils.selection_utils

distance_between_point_and_line_segment_2d(p, p1, p2)

Calculate the distance between a point p and a line segment p1, p2

Source code in src/napari_threedee/utils/selection_utils.py
46
47
48
49
50
51
52
53
54
55
56
57
58
59
def distance_between_point_and_line_segment_2d(p, p1, p2):
    """Calculate the distance between a point p and a line segment p1, p2
    """
    x0 = p[0]
    y0 = p[1]
    x1 = p1[0]
    y1 = p1[1]
    x2 = p2[0]
    y2 = p2[1]

    numerator = np.linalg.norm((x2 - x1) * (y1 - y0) - (x1 - x0) * (y2 - y1))
    denominator = np.sqrt((x2 - x1) ** 2 + (y2 - y1) ** 2)

    return numerator / denominator

select_triangle_from_click(click_point: np.ndarray, view_direction: np.ndarray, triangles: np.ndarray)

Determine if a line goes through any of a set of triangles.

For example, this could be used to determine if a click was in a triangle of a mesh.

PARAMETER DESCRIPTION
click_point

(3,) array containing the location that was clicked. This should be in the same coordinate system as the vertices.

TYPE: ndarray

view_direction

(3,) array describing the direction camera is pointing in the scene. This should be in the same coordinate system as the vertices.

TYPE: ndarray

triangles

(n, 3, 3) array containing the coordinates for the 3 corners of n triangles.

TYPE: ndarray

RETURNS DESCRIPTION
in_triangles

(n,) boolean array that is True of the ray intersects the triangle

TYPE: ndarray

Source code in src/napari_threedee/utils/selection_utils.py
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
def select_triangle_from_click(
        click_point: np.ndarray, view_direction: np.ndarray, triangles: np.ndarray
):
    """Determine if a line goes through any of a set of triangles.

    For example, this could be used to determine if a click was
    in a triangle of a mesh.

    Parameters
    ----------
    click_point : np.ndarray
        (3,) array containing the location that was clicked. This
        should be in the same coordinate system as the vertices.
    view_direction : np.ndarray
        (3,) array describing the direction camera is pointing in
        the scene. This should be in the same coordinate system as
        the vertices.
    triangles : np.ndarray
        (n, 3, 3) array containing the coordinates for the 3 corners
        of n triangles.

    Returns
    -------
    in_triangles : np.ndarray
        (n,) boolean array that is True of the ray intersects the triangle
    """
    vertices = triangles.reshape((-1, triangles.shape[2]))
    # project the vertices of the bound region on to the view plane
    vertices_plane, signed_distance_to_plane = project_points_onto_plane(
        points=vertices, plane_point=click_point, plane_normal=view_direction
    )

    # rotate the plane to make the triangles 2D
    rotation_matrix = rotation_matrix_from_vectors_3d(view_direction, [0, 0, 1])
    rotated_vertices = vertices_plane @ rotation_matrix.T

    rotated_vertices_2d = rotated_vertices[:, :2]
    rotated_triangles_2d = rotated_vertices_2d.reshape(-1, 3, 2)
    line_pos_2D = rotation_matrix.dot(click_point)[:2]

    candidate_matches = inside_triangles(rotated_triangles_2d - line_pos_2D)

    candidate_match_indices = np.argwhere(candidate_matches)

    n_matches = len(candidate_match_indices)
    if n_matches == 0:
        triangle_index = None
    elif n_matches == 1:
        triangle_index = candidate_match_indices[0]
    else:
        potential_match_distances = signed_distance_to_plane[candidate_match_indices]
        triangle_index = candidate_match_indices[np.argmin(potential_match_distances)]

    return triangle_index

select_sphere_from_click(click_point: np.ndarray, view_direction: np.ndarray, sphere_centroids: np.ndarray, sphere_diameter: float) -> Optional[int]

Determine which, if any spheres are intersected by a click ray.

If multiple spheres are intersected, the closest sphere to the click point (ray start) will be returned.

PARAMETER DESCRIPTION
click_point

The point where the click ray originates.

TYPE: ndarray

view_direction

The unit vector pointing in the direction the viewer is looking.

TYPE: ndarray

sphere_centroids

The (n, 3) array of center points for the n points.

TYPE: ndarray

sphere_diameter

The diameter of all spheres. Must the same diameter for all spheres.

TYPE: float

RETURNS DESCRIPTION
selection

The index for the sphere that was intersected. Returns None if no spheres are intersected.

TYPE: Optional[int]

Source code in src/napari_threedee/utils/selection_utils.py
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
def select_sphere_from_click(
    click_point: np.ndarray, view_direction: np.ndarray, sphere_centroids: np.ndarray, sphere_diameter: float
) -> Optional[int]:
    """Determine which, if any spheres are intersected by a click ray.

    If multiple spheres are intersected, the closest sphere to the click point
    (ray start) will be returned.

    Parameters
    ----------
    click_point : np.ndarray
        The point where the click ray originates.
    view_direction : np.ndarray
        The unit vector pointing in the direction the viewer is looking.
    sphere_centroids : np.ndarray
        The (n, 3) array of center points for the n points.
    sphere_diameter : float
        The diameter of all spheres. Must the same diameter for all spheres.

    Returns
    -------
    selection : Optional[int]
        The index for the sphere that was intersected.
        Returns None if no spheres are intersected.
    """
    # project the in view points onto the camera plane
    projected_points, projection_distances = project_points_onto_plane(
        points=sphere_centroids,
        plane_point=click_point,
        plane_normal=view_direction,
    )

    # rotate points and plane to be axis aligned with normal [0, 0, 1]
    rotated_points, rotation_matrix = rotate_points(
        points=projected_points,
        current_plane_normal=view_direction,
        new_plane_normal=[0, 0, 1],
    )
    rotated_click_point = np.dot(rotation_matrix, click_point)

    # find the points the click intersects
    n_spheres = len(sphere_centroids)
    handle_sizes = np.tile(sphere_diameter, (n_spheres, 1))
    distances = abs(rotated_points[:, :2] - rotated_click_point[:2])

    in_slice_matches = np.all(
        distances <= (handle_sizes / 2),
        axis=1,
    )
    indices = np.where(in_slice_matches)[0]

    if len(indices) > 0:
        # find the point that is most in the foreground
        candidate_point_distances = projection_distances[indices]
        min_distance_index = np.argmin(candidate_point_distances)
        selection = indices[min_distance_index]
    else:
        selection = None

    return selection