diff --git a/fury/actor.py b/fury/actor.py index e1f314b14..a7e61489c 100644 --- a/fury/actor.py +++ b/fury/actor.py @@ -255,7 +255,7 @@ def cylinder( Parameters ---------- centers : ndarray, shape (N, 3) - Box positions. + cylinder positions. colors : ndarray (N,3) or (N, 4) or tuple (3,) or tuple (4,), optional RGB or RGBA (for opacity) R, G, B and A should be at the range [0, 1]. height: float, optional @@ -270,7 +270,7 @@ def cylinder( Scaling factors for the cylinders in the (x, y, z) dimensions. Default is uniform scaling (1, 1, 1). directions : ndarray, shape (N, 3), optional - The orientation vector of the box. + The orientation vector of the cylinder. capped : bool, optional Whether to add caps (circular ends) to the cylinders. Default is True. opacity : float, optional @@ -278,14 +278,14 @@ def cylinder( If both `opacity` and RGBA are provided, the final alpha will be: final_alpha = alpha_in_RGBA * opacity material : str, optional - The material type for the boxes. Options are 'phong' and 'basic'. + The material type for the cylinders. Options are 'phong' and 'basic'. enable_picking : bool, optional - Whether the boxes should be pickable in a 3D scene. + Whether the cylinders should be pickable in a 3D scene. Returns ------- mesh_actor : Actor - A mesh actor containing the generated boxes, with the specified + A mesh actor containing the generated cylinders, with the specified material and properties. Examples @@ -327,32 +327,32 @@ def square( material="phong", enable_picking=True, ): - """Visualize one or many boxes with different features. + """Visualize one or many squares with different features. Parameters ---------- centers : ndarray, shape (N, 3) - Box positions. + square positions. directions : ndarray, shape (N, 3), optional - The orientation vector of the box. + The orientation vector of the square. colors : ndarray (N,3) or (N, 4) or tuple (3,) or tuple (4,), optional RGB or RGBA (for opacity) R, G, B and A should be at the range [0, 1]. scales : int or ndarray (N,3) or tuple (3,), optional - The size of the box in each dimension. If a single value is provided, - the same size will be used for all boxes. + The size of the square in each dimension. If a single value is provided, + the same size will be used for all squares. opacity : float, optional Takes values from 0 (fully transparent) to 1 (opaque). If both `opacity` and RGBA are provided, the final alpha will be: final_alpha = alpha_in_RGBA * opacity material : str, optional - The material type for the boxes. Options are 'phong' and 'basic'. + The material type for the squares. Options are 'phong' and 'basic'. enable_picking : bool, optional - Whether the boxes should be pickable in a 3D scene. + Whether the squares should be pickable in a 3D scene. Returns ------- mesh_actor : Actor - A mesh actor containing the generated boxes, with the specified + A mesh actor containing the generated squares, with the specified material and properties. Examples @@ -379,3 +379,67 @@ def square( material=material, enable_picking=enable_picking, ) + + +def frustum( + centers, + *, + directions=(0, 0, 0), + colors=(1, 1, 1), + scales=(1, 1, 1), + opacity=None, + material="phong", + enable_picking=True, +): + """Visualize one or many frustums with different features. + + Parameters + ---------- + centers : ndarray, shape (N, 3) + frustum positions. + directions : ndarray, shape (N, 3), optional + The orientation vector of the frustum. + colors : ndarray (N,3) or (N, 4) or tuple (3,) or tuple (4,), optional + RGB or RGBA (for opacity) R, G, B and A should be at the range [0, 1]. + scales : int or ndarray (N,3) or tuple (3,), optional + The size of the frustum in each dimension. If a single value is provided, + the same size will be used for all frustums. + opacity : float, optional + Takes values from 0 (fully transparent) to 1 (opaque). + If both `opacity` and RGBA are provided, the final alpha will be: + final_alpha = alpha_in_RGBA * opacity + material : str, optional + The material type for the frustums. Options are 'phong' and 'basic'. + enable_picking : bool, optional + Whether the frustums should be pickable in a 3D scene. + + Returns + ------- + mesh_actor : Actor + A mesh actor containing the generated frustums, with the specified + material and properties. + + Examples + -------- + >>> from fury import window, actor + >>> import numpy as np + >>> scene = window.Scene() + >>> centers = np.random.rand(5, 3) * 10 + >>> colors = np.random.rand(5, 3) + >>> frustum_actor = actor.frustum(centers=centers, colors=colors) + >>> scene.add(frustum_actor) + >>> show_manager = window.ShowManager(scene=scene, size=(600, 600)) + >>> show_manager.start() + """ + vertices, faces = fp.prim_frustum() + return actor_from_primitive( + vertices, + faces, + centers=centers, + colors=colors, + scales=scales, + directions=directions, + opacity=opacity, + material=material, + enable_picking=enable_picking, + ) diff --git a/fury/tests/test_actor.py b/fury/tests/test_actor.py index 9528e03ad..de5c0c77a 100644 --- a/fury/tests/test_actor.py +++ b/fury/tests/test_actor.py @@ -172,3 +172,60 @@ def test_square(): assert square_actor.prim_count == 1 scene.remove(square_actor) + + +def test_frustum(): + scene = window.Scene() + centers = np.array([[0, 0, 0]]) + colors = np.array([[1, 0, 0]]) + + frustum_actor = actor.frustum(centers=centers, colors=colors) + scene.add(frustum_actor) + + npt.assert_array_equal(frustum_actor.local.position, centers[0]) + + mean_vertex = np.mean(frustum_actor.geometry.positions.view, axis=0) + npt.assert_array_almost_equal(mean_vertex, centers[0]) + + assert frustum_actor.prim_count == 1 + + window.snapshot(scene=scene, fname="frustum_test_1.png") + + img = Image.open("frustum_test_1.png") + img_array = np.array(img) + + mean_r, mean_g, mean_b, mean_a = np.mean( + img_array.reshape(-1, img_array.shape[2]), axis=0 + ) + + assert mean_r > mean_b and mean_r > mean_g + assert 0 < mean_r < 255 and 0 < mean_g < 255 and 0 <= mean_b < 255 + + middle_pixel = img_array[img_array.shape[0] // 2, img_array.shape[1] // 2] + r, g, b, a = middle_pixel + assert r > g and r > b + assert g == b + assert r > 0 and g > 0 and b > 0 + scene.remove(frustum_actor) + + frustum_actor_2 = actor.frustum(centers=centers, colors=colors, material="basic") + scene.add(frustum_actor_2) + window.snapshot(scene=scene, fname="frustum_test_2.png") + + img = Image.open("frustum_test_2.png") + img_array = np.array(img) + + mean_r, mean_g, mean_b, mean_a = np.mean( + img_array.reshape(-1, img_array.shape[2]), axis=0 + ) + + assert mean_r > mean_b and mean_r > mean_g + assert 0 < mean_r < 255 + assert mean_g == 0 and mean_b == 0 + + middle_pixel = img_array[img_array.shape[0] // 2, img_array.shape[1] // 2] + r, g, b, a = middle_pixel + assert r > g and r > b + assert g == 0 and b == 0 + assert r == 255 + scene.remove(frustum_actor_2)