Simple picking#

Here we present a tutorial showing how to interact with objects in the 3D world. All objects to be picked are part of a single actor. FURY likes to bundle objects in a few actors to reduce code and increase speed.

When the objects are picked they will change size and color.

import numpy as np

import fury

centers = 0.5 * np.array([[0, 0, 0], [100, 0, 0], [200, 0, 0.0]])
colors = np.array([[0.8, 0, 0], [0, 0.8, 0], [0, 0, 0.8]])
radii = 0.1 * np.array([50, 100, 150.0])

selected = np.zeros(3, dtype=bool)

Let’s create a panel to show what is picked

panel = fury.ui.Panel2D(size=(400, 200), color=(1, 0.5, 0.0), align="right")
panel.center = (150, 200)

text_block = fury.ui.TextBlock2D(text="Left click on object \n")
panel.add_element(text_block, (0.3, 0.3))

Build scene and add an actor with many objects.

scene = fury.window.Scene()

label_actor = fury.actor.vector_text(text="Test")

This actor is made with 3 cubes of different orientation

directions = np.array(
    [
        [np.sqrt(2) / 2, 0, np.sqrt(2) / 2],
        [np.sqrt(2) / 2, np.sqrt(2) / 2, 0],
        [0, np.sqrt(2) / 2, np.sqrt(2) / 2],
    ]
)
fury_actor = fury.actor.cube(centers, directions, colors, scales=radii)
/opt/homebrew/Caskroom/miniforge/base/envs/py39/lib/python3.9/site-packages/sphinx_gallery/gen_rst.py:722: UserWarning: We'll no longer accept the way you call the cube function in future versions of FURY.

Here's how to call the Function cube: cube(centers_value, directions='value', colors='value', scales='value')

  exec(self.code, self.fake_main.__dict__)

Access the memory of the vertices of all the cubes

vertices = fury.utils.vertices_from_actor(fury_actor)
num_vertices = vertices.shape[0]
num_objects = centers.shape[0]

Access the memory of the colors of all the cubes

vcolors = fury.utils.colors_from_actor(fury_actor, "colors")
/opt/homebrew/Caskroom/miniforge/base/envs/py39/lib/python3.9/site-packages/sphinx_gallery/gen_rst.py:722: UserWarning: We'll no longer accept the way you call the colors_from_actor function in future versions of FURY.

Here's how to call the Function colors_from_actor: colors_from_actor(actor_value, array_name='value', as_vtk='value')

  exec(self.code, self.fake_main.__dict__)

Adding an actor showing the axes of the world coordinates

ax = fury.actor.axes(scale=(10, 10, 10))

scene.add(fury_actor)
scene.add(label_actor)
scene.add(ax)
scene.reset_camera()

Create the Picking manager

pickm = fury.pick.PickingManager()

Time to make the callback which will be called when we pick an object

def left_click_callback(obj, event):
    # Get the event position on display and pick

    event_pos = pickm.event_position(showm.iren)
    picked_info = pickm.pick(event_pos, showm.scene)

    vertex_index = picked_info["vertex"]

    # Calculate the objects index

    object_index = int(np.floor((vertex_index / num_vertices) * num_objects))

    # Find how many vertices correspond to each object
    sec = int(num_vertices / num_objects)

    if not selected[object_index]:
        scale = 6 / 5
        color_add = np.array([30, 30, 30], dtype="uint8")
        selected[object_index] = True
    else:
        scale = 5 / 6
        color_add = np.array([-30, -30, -30], dtype="uint8")
        selected[object_index] = False

    # Update vertices positions
    vertices[object_index * sec : object_index * sec + sec] = (
        scale
        * (
            vertices[object_index * sec : object_index * sec + sec]
            - centers[object_index]
        )
        + centers[object_index]
    )

    # Update colors
    vcolors[object_index * sec : object_index * sec + sec] += color_add

    # Tell actor that memory is modified
    fury.utils.update_actor(fury_actor)

    face_index = picked_info["face"]

    # Show some info
    text = "Object " + str(object_index) + "\n"
    text += "Vertex ID " + str(vertex_index) + "\n"
    text += "Face ID " + str(face_index) + "\n"
    text += "World pos " + str(np.round(picked_info["xyz"], 2)) + "\n"
    text += "Actor ID " + str(id(picked_info["actor"]))
    text_block.message = text
    showm.render()

Bind the callback to the actor

fury_actor.AddObserver("LeftButtonPressEvent", left_click_callback, 1)
1

Make the window appear

showm = fury.window.ShowManager(scene, size=(1024, 768), order_transparent=True)

scene.add(panel)
/opt/homebrew/Caskroom/miniforge/base/envs/py39/lib/python3.9/site-packages/sphinx_gallery/gen_rst.py:722: UserWarning: We'll no longer accept the way you call the __init__ function in future versions of FURY.

Here's how to call the Function __init__: __init__(self_value, scene='value', title='value', size='value', png_magnify='value', reset_camera='value', order_transparent='value', interactor_style='value', stereo='value', multi_samples='value', max_peels='value', occlusion_ratio='value')

  exec(self.code, self.fake_main.__dict__)

Change interactive to True to start interacting with the scene

interactive = False

if interactive:
    showm.start()

Save the current framebuffer in a PNG file

fury.window.record(showm.scene, size=(1024, 768), out_path="viz_picking.png")
viz picking
/opt/homebrew/Caskroom/miniforge/base/envs/py39/lib/python3.9/site-packages/sphinx_gallery/gen_rst.py:722: UserWarning: We'll no longer accept the way you call the record function in future versions of FURY.

Here's how to call the Function record: record(scene='value', cam_pos='value', cam_focal='value', cam_view='value', out_path='value', path_numbering='value', n_frames='value', az_ang='value', magnification='value', size='value', reset_camera='value', screen_clip='value', stereo='value', verbose='value')

  exec(self.code, self.fake_main.__dict__)

Total running time of the script: (0 minutes 0.084 seconds)

Gallery generated by Sphinx-Gallery