Note
Go to the end to download the full example code.
Reading Meshes, Lines, and Points#
This example demonstrates how to fetch 3D data assets from the
polyxios-data repository, read them into NumPy arrays using FURY’s
dedicated I/O methods, and render them as distinct colored actors side by side:
a surface mesh, a collection of lines, and a point cloud.
Polyxios is the lightweight, dependency-free I/O backend used by FURY. It
is designed to auto-detect file formats by extension and map complex scientific
surface structures (.vtk, .vtp, .ply, .obj, .mesh)
seamlessly into memory-contiguous NumPy arrays, and its fetch helper downloads
and caches sample assets so tutorials and tests can run without bundling large files.
This example highlights three fundamental data archetypes:
Surface Meshes (via ``read_mesh``): Returns a triple of
(vertices, faces, colors)whereverticesis an(N, 3)float32 array,facesis an(M, 3)int32 array of triangulated indices, andcolorsis an optional(N, 3)float32 color map.Line Streams (via ``read_lines``): Returns a tuple of
(lines, colors). Here,linesis a list of independent(P, 3)float32 arrays (where each array represents an individual continuous stroke or fiber pathway), andcolorscontains a corresponding list of per-vertex color maps.Point Clouds (via ``read_points``): Returns a tuple of
(points, colors)wherepointsis an(N, 3)float32 coordinate block representing un-connected point data, andcolorsis an optional per-point attribute array.
Import the required libraries.
import numpy as np
import polyxios as px
from fury import actor, ui, window
from fury.io import read_lines, read_mesh, read_points
Define helper to process geometry, normalize space, and apply fallback gradients.
def points_to_actor(file_path):
"""Read a point cloud file and build a colored point actor."""
points, colors = read_points(file_path)
if points.size == 0:
raise ValueError(f"No points found in file: {file_path}")
# Center on the origin and normalize the scale to roughly unit size
points = points - points.mean(axis=0)
points = (points / np.max(np.abs(points))).astype(np.float32)
# Use the file's colors when present, otherwise apply a height-based gradient
if colors is None:
height = points[:, 1]
height_range = height.max() - height.min()
y_range = height_range if height_range > 0 else 1.0
t = ((height - height.min()) / y_range)[:, None]
low_color = np.array([0.10, 0.20, 0.70], dtype=np.float32)
high_color = np.array([0.95, 0.75, 0.20], dtype=np.float32)
colors = (low_color * (1.0 - t) + high_color * t).astype(np.float32)
# ``actor.point`` turns an (N, 3) array of point positions and their
# matching per-point colors into an optimized graphic object.
point_actor = actor.point(points, colors=colors)
return point_actor, len(points)
def lines_to_actor(file_path):
"""Read a lines file and build a colored stream/line actor."""
lines, colors = read_lines(file_path)
if not lines:
raise ValueError(f"No line elements found in file: {file_path}")
# Stack all points temporarily to calculate overall centering and scaling metrics
all_points = np.vstack(lines)
center = all_points.mean(axis=0)
max_scale = np.max(np.abs(all_points - center))
# Center on the origin and normalize the scale to roughly unit size
normalized_lines = [
((line - center) / max_scale).astype(np.float32) for line in lines
]
# Use the file's colors when present, otherwise apply a height-based gradient
if colors is None:
# Re-stack lines to compute a global bounding box for the height gradient
all_norm_points = np.vstack(normalized_lines)
y_min = all_norm_points[:, 1].min()
y_max = all_norm_points[:, 1].max()
y_range = y_max - y_min if (y_max - y_min) > 0 else 1.0
low_color = np.array([0.10, 0.20, 0.70], dtype=np.float32)
high_color = np.array([0.95, 0.75, 0.20], dtype=np.float32)
colors = []
for line in normalized_lines:
heights = line[:, 1]
t = ((heights - y_min) / y_range)[:, None]
line_colors = (low_color * (1.0 - t) + high_color * t).astype(np.float32)
colors.append(line_colors)
# ``actor.streamlines`` accepts a list of coordinate arrays and color arrays
line_actor = actor.streamlines(normalized_lines, colors=colors)
# Calculate totals for reporting
total_vertices = sum(len(line) for line in lines)
total_lines = len(lines)
return line_actor, total_vertices, total_lines
def mesh_to_actor(file_path):
"""Read a mesh file and build a colored surface actor."""
vertices, faces, colors = read_mesh(file_path)
# Center on the origin and normalize the scale to roughly unit size.
vertices = vertices - vertices.mean(axis=0)
vertices = (vertices / np.max(np.abs(vertices))).astype(np.float32)
# Use the file's colors when present, otherwise a height-based gradient.
if colors is None:
height = vertices[:, 1]
t = ((height - height.min()) / (height.max() - height.min()))[:, None]
low_color = np.array([0.10, 0.20, 0.70], dtype=np.float32)
high_color = np.array([0.95, 0.75, 0.20], dtype=np.float32)
colors = (low_color * (1.0 - t) + high_color * t).astype(np.float32)
# ``actor.surface`` wraps the geometry, material and mesh creation for us,
# turning the vertices, faces and per-vertex colors into a ready actor.
surf = actor.surface(vertices, faces, colors=colors)
return surf, len(vertices), len(faces)
Fetch the sample data assets via polyxios.
Human.vtpa surface mesh possessing native topological faces and color tracks.hello.vtkcontains line elements forming spatial lettering out of poly-line.star.meshhandles plain point coordinates lacking explicitly structured links.
human_path = px.fetch("Human.vtp")
line_path = px.fetch("hello.vtk")
ball_path = px.fetch("star.mesh")
# Generate the actors and fetch metadata counts
mesh_actor, mesh_nv, mesh_nf = mesh_to_actor(human_path)
line_actor, line_nv, line_nl = lines_to_actor(line_path)
point_actor, point_nv = points_to_actor(ball_path)
print(f"Human.vtp (Mesh): {mesh_nv} vertices, {mesh_nf} faces")
print(f"hello.vtk (Lines): {line_nv} vertices across {line_nl} lines")
print(f"star.mesh (Points): {point_nv} points")
Coordinate alignment transforms.
Human.vtp is modeled lying along its Z axis, so by default it faces the
camera end-on. Every FURY actor exposes transform helpers (rotate,
translate, scale), so we stand it upright with a -90 degrees rotation
about the X axis, mapping its head-to-toe axis to the vertical and apply 180 degrees
so it faces the camera.
mesh_actor.rotate((-90, 180, 0))
Place the three actors side by side (Left, Center, Right) to avoid overlapping.
mesh_actor.local.position = (-3.0, 0.0, 0.0)
line_actor.local.position = (0.0, 0.0, 0.0)
point_actor.local.position = (3.0, 0.0, 0.0)
Add matching 3D text labels directly beneath each of the three objects.
labels_actor = actor.text(
["Mesh (Human.vtp)", "Lines (hello.vtk)", "Points (star.mesh)"],
position=[(-3.0, -1.25, 0.0), (0.0, -1.25, 0.0), (3.0, -1.25, 0.0)],
colors=(0.9, 0.9, 0.95),
font_size=0.16,
anchor="top-center",
)
Set up the 3D scene and register all visual elements.
scene = window.Scene(background=(0.05, 0.05, 0.08))
scene.add(mesh_actor)
scene.add(line_actor)
scene.add(point_actor)
scene.add(labels_actor)
Add a 2D text HUD overlay detailing what each object represents.
info_text = (
f"FURY x Polyxios Geometry Reader\n"
f"Left: Human.vtp (Mesh: {mesh_nv} verts, {mesh_nf} faces)\n"
f"Center: hello.vtk (Lines: {line_nv} total vertices, {line_nl} lines)\n"
f"Right: star.mesh (Points: {point_nv} coordinates)\n"
f"Decoupled data paths processed into native NumPy arrays."
)
hud_label = ui.TextBlock2D(
text=info_text,
position=(20, 20),
font_size=16,
color=(0.9, 0.9, 0.95),
bold=False,
dynamic_bbox=True,
)
scene.add(hud_label)
Initialize the ShowManager, frame the camera to fit the wider 3-element layout, and initialize the rendering window loop.
show_m = window.ShowManager(
scene=scene, size=(1280, 728), title="FURY Multimodal Reader"
)
camera = show_m.screens[0].camera
camera.local.position = (0.0, -0.15, 6.0)
camera.look_at((0.0, -0.15, 0.0))
show_m.start()