From 1a4ba1c23f2af0da5beea38018b55c3dfae388a9 Mon Sep 17 00:00:00 2001 From: maharshi-gor Date: Sun, 28 Jun 2026 09:49:57 -0400 Subject: [PATCH 1/2] NF: Polyxios Integration. - Introduce reading of mesh, lines and point files. - Supports vtk extensions and standarad obj, ply, stl, xml. - Testcases for the same are introduced. - Added polyxios as dependency. - Example for using function with some learning. --- docs/examples/_valid_examples.toml | 1 + docs/examples/viz_read_mesh.py | 148 +++++++++++++++++++++++++++++ fury/io.py | 124 +++++++++++++++++++++++- fury/tests/test_io.py | 81 ++++++++++++++++ pyproject.toml | 3 +- requirements/default.txt | 1 + 6 files changed, 352 insertions(+), 6 deletions(-) create mode 100644 docs/examples/viz_read_mesh.py diff --git a/docs/examples/_valid_examples.toml b/docs/examples/_valid_examples.toml index b037dee0e8..ba59b483bc 100644 --- a/docs/examples/_valid_examples.toml +++ b/docs/examples/_valid_examples.toml @@ -44,6 +44,7 @@ files = [ "viz_contour.py", "viz_brownian_motion.py", "viz_network.py", + "viz_read_mesh.py", ] [ui] diff --git a/docs/examples/viz_read_mesh.py b/docs/examples/viz_read_mesh.py new file mode 100644 index 0000000000..c3b266ef60 --- /dev/null +++ b/docs/examples/viz_read_mesh.py @@ -0,0 +1,148 @@ +""" +============== +Reading a Mesh +============== + +This example demonstrates how to fetch 3D mesh assets from the +``polyxios-data`` repository, read them into NumPy arrays with +:func:`fury.io.read_mesh`, and render them as colored surface actors. We load +two files of different formats side by side -- a ``.vtp`` surface and a ``.obj`` +mesh -- to highlight that the same code handles both. + +Polyxios is the lightweight, dependency-free mesh I/O backend used by FURY. It +reads and writes the common scientific surface formats (``.vtk``, ``.vtp``, +``.ply``, ``.obj`` and the VTK XML family) straight into NumPy arrays, and its +``fetch`` helper downloads and caches sample assets so tutorials and tests can +run without bundling large files. + +``read_mesh`` always returns the same simple triple regardless of the source +format: + +* ``vertices`` -- ``(N, 3)`` float32 point coordinates, +* ``faces`` -- ``(M, 3)`` int32 triangle indices (surfaces are triangulated + for you), and +* ``colors`` -- ``(N, 3)`` float32 per-vertex RGB in ``[0, 1]`` (or ``None``). +""" + +######################################################################################### +# Import the required libraries. +import numpy as np +import polyxios as px + +from fury import actor, ui, window +from fury.io import read_mesh + +######################################################################################### +# Define a small helper that reads a mesh and turns it into a FURY actor with +# :func:`fury.actor.surface`. It centers and normalizes the geometry and falls +# back to a height-based color gradient when the file has no colors. +# +# ``read_mesh`` returns the same ``(vertices, faces, colors)`` triple for every +# supported format, so the exact same code path loads ``.obj`` and ``.vtp`` +# files alike. + + +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 two assets from the ``polyxios-data`` release. ``fetch`` returns the +# absolute path to the locally cached file, downloading it on first use. +# +# * ``Human.vtp`` is a VTK XML PolyData surface that already carries per-vertex +# colors. +# * ``stanford-bunny.obj`` is the classic Stanford bunny stored as a Wavefront +# OBJ with plain geometry (no colors). +human_path = px.fetch("Human.vtp") +bunny_path = px.fetch("stanford-bunny.obj") + +human_actor, human_nv, human_nf = mesh_to_actor(human_path) +bunny_actor, bunny_nv, bunny_nf = mesh_to_actor(bunny_path) + +print(f"Human.vtp: {human_nv} vertices, {human_nf} faces") +print(f"stanford-bunny.obj: {bunny_nv} vertices, {bunny_nf} faces") + +######################################################################################### +# ``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. +human_actor.rotate((-90, 180, 0)) + +######################################################################################### +# Place the two meshes side by side so both formats are visible at once. +human_actor.local.position = (-1.3, 0.0, 0.0) +bunny_actor.local.position = (1.3, 0.0, 0.0) + +######################################################################################### +# Add a 3D text label beneath each mesh. ``actor.text`` accepts a list of +# strings with matching positions and returns a Group of 3D Text actors that +# live in the scene (unlike the 2D HUD overlay, these are part of the world and +# move with the camera). +labels_actor = actor.text( + ["Human.vtp", "stanford-bunny.obj"], + position=[(-1.3, -1.25, 0.0), (1.3, -1.25, 0.0)], + colors=(0.9, 0.9, 0.95), + font_size=0.18, + anchor="top-center", +) + +######################################################################################### +# Set up the 3D scene and add both mesh actors and their labels. +scene = window.Scene(background=(0.05, 0.05, 0.08)) +scene.add(human_actor) +scene.add(bunny_actor) +scene.add(labels_actor) + +######################################################################################### +# Add a 2D text overlay describing what is being shown. The same few lines of +# code load OBJ, PLY, VTK and VTP files because ``read_mesh`` normalizes every +# format to the same NumPy arrays. +info_text = ( + f"FURY x Polyxios mesh reader\n" + f"Left: Human.vtp ({human_nv} verts, {human_nf} faces, file colors)\n" + f"Right: stanford-bunny.obj ({bunny_nv} verts, {bunny_nf} faces)\n" + f"Both read via fury.io.read_mesh -> (vertices, faces, colors)\n" + f"Supported: .vtk .vtp .ply .obj (auto-detected by extension)" +) + +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, position the virtual camera to frame both meshes, +# and launch the rendering loop. +show_m = window.ShowManager(scene=scene, size=(1024, 768), title="FURY Mesh Reader") + +camera = show_m.screens[0].camera +camera.local.position = (0.0, -0.15, 5.0) +camera.look_at((0.0, -0.15, 0.0)) + +show_m.start() diff --git a/fury/io.py b/fury/io.py index f098f5db3e..38ec0c6af6 100644 --- a/fury/io.py +++ b/fury/io.py @@ -1,18 +1,14 @@ """I/O functions for loading and saving images, textures.""" import os - -# from tempfile import TemporaryDirectory as InTemporaryDirectory from urllib.request import urlretrieve # import warnings from PIL import Image import numpy as np +import polyxios as px -# from fury.decorators import warn_on_args_to_kwargs from fury.lib import Texture, wgpu - -# from fury.utils import set_input from fury.network.parser import parse_network, stringify_network @@ -341,6 +337,124 @@ def save_network(network_data, file_path, format=None): f.write(data) +def read_mesh(file_path, *, format=None): + """ + Read a mesh from a file using polyxios. + + Supported formats include the ones handled by polyxios, such as VTK, VTP, + PLY, OBJ and the VTK XML family. + + Parameters + ---------- + file_path : str + The path to the mesh file. + format : str, optional + The specific file format override (e.g. '.vtk'). Inferred from the file + extension when None. + + Returns + ------- + tuple + A tuple containing: + + - vertices (np.ndarray): Shape (N, 3) float32 array of vertex positions. + - faces (np.ndarray or None): Shape (M, 3) int32 array of triangle face + indices, or None when the mesh has no surface elements. + - colors (np.ndarray or None): Shape (N, 3) float32 array of per-vertex + RGB colors in [0, 1], or None when no vertex colors are present. + """ + poly = px.read(file_path, fmt=format) + + vertices = np.asarray(poly.vertices, dtype=np.float32) + + faces = poly.faces + if faces is not None: + faces = np.asarray(faces, dtype=np.int32) + + colors = px.transforms.vertex_colors(poly) + if colors is not None: + colors = np.asarray(colors, dtype=np.float32) + + return vertices, faces, colors + + +def read_points(file_path, *, format=None): + """ + Read point coordinates from a file using polyxios. + + Parameters + ---------- + file_path : str + The path to the mesh file. + format : str, optional + The specific file format override (e.g. '.vtk'). Inferred from the file + extension when None. + + Returns + ------- + tuple + A tuple containing: + + - points (np.ndarray): Shape (N, 3) float32 array of point positions. + - colors (np.ndarray or None): Shape (N, 3) float32 array of per-point + RGB colors in [0, 1], or None when no vertex colors are present. + """ + poly = px.read(file_path, fmt=format) + + points = np.asarray(poly.vertices, dtype=np.float32) + + colors = px.transforms.vertex_colors(poly) + if colors is not None: + colors = np.asarray(colors, dtype=np.float32) + + return points, colors + + +def read_lines(file_path, *, format=None): + """ + Read line segments from a file using polyxios. + + Each line or poly_line element is translated into an array of its vertex + positions, ready to be consumed by FURY line actors. + + Parameters + ---------- + file_path : str + The path to the mesh file. + format : str, optional + The specific file format override (e.g. '.vtk'). Inferred from the file + extension when None. + + Returns + ------- + tuple + A tuple containing: + + - lines (list of np.ndarray): One Shape (P, 3) float32 array of vertex + positions per line. Empty list when the file has no line elements. + - colors (list of np.ndarray or None): One Shape (P, 3) float32 array of + per-vertex RGB colors in [0, 1] per line, or None when no vertex + colors are present. + """ + poly = px.read(file_path, fmt=format) + + line_indices = poly.lines + if line_indices is None: + return [], None + + vertices = np.asarray(poly.vertices, dtype=np.float32) + lines = [vertices[idx] for idx in line_indices] + + vertex_colors = px.transforms.vertex_colors(poly) + if vertex_colors is None: + colors = None + else: + vertex_colors = np.asarray(vertex_colors, dtype=np.float32) + colors = [vertex_colors[idx] for idx in line_indices] + + return lines, colors + + # def load_polydata(file_name): # """Load a vtk polydata to a supported format file. diff --git a/fury/tests/test_io.py b/fury/tests/test_io.py index 5f945ba72c..8a79a34a67 100644 --- a/fury/tests/test_io.py +++ b/fury/tests/test_io.py @@ -7,6 +7,8 @@ import numpy.testing as npt # import pytest +import polyxios as px + # from fury.decorators import skip_osx from fury.data import fetch_viz_cubemaps, read_viz_cubemap from fury.io import ( @@ -19,6 +21,9 @@ # load_polydata, # load_sprite_sheet, # load_text, + read_lines, + read_mesh, + read_points, save_image, save_network, ) @@ -207,6 +212,82 @@ def test_save_and_load_network(): ) +def test_read_mesh(): + vertices = np.array([[0, 0, 0], [1, 0, 0], [0, 1, 0], [1, 1, 0]], dtype=np.float64) + faces = np.array([[0, 1, 2], [1, 3, 2]], dtype=np.int32) + colors = np.array([[1, 0, 0], [0, 1, 0], [0, 0, 1], [1, 1, 0]], dtype=np.float32) + + poly = px.make_polydata( + vertices, + [("triangle", faces)], + vertex_attrs={"colors": colors}, + ) + + with InTemporaryDirectory() as odir: + fname_path = pjoin(odir, "temp-mesh.vtk") + px.write(poly, fname_path) + + out_vertices, out_faces, out_colors = read_mesh(fname_path) + + npt.assert_equal(out_vertices.dtype, np.float32) + npt.assert_equal(out_faces.dtype, np.int32) + npt.assert_array_almost_equal(out_vertices, vertices) + npt.assert_array_equal(out_faces, faces) + npt.assert_array_almost_equal(out_colors, colors) + + +def test_read_points(): + vertices = np.array([[0, 0, 0], [1, 2, 3], [4, 5, 6], [7, 8, 9]], dtype=np.float64) + connectivity = np.array([[0], [1], [2], [3]], dtype=np.int32) + + poly = px.make_polydata(vertices, [("vertex", connectivity)]) + + with InTemporaryDirectory() as odir: + fname_path = pjoin(odir, "temp-points.vtk") + px.write(poly, fname_path) + + out_points, out_colors = read_points(fname_path) + + npt.assert_equal(out_points.dtype, np.float32) + npt.assert_equal(out_points.shape, (4, 3)) + npt.assert_array_almost_equal(out_points, vertices) + npt.assert_equal(out_colors, None) + + +def test_read_lines(): + vertices = np.array([[0, 0, 0], [1, 0, 0], [2, 0, 0], [3, 0, 0]], dtype=np.float64) + line = np.array([[0, 1, 2, 3]], dtype=np.int32) + + poly = px.make_polydata(vertices, [("poly_line", line)]) + + with InTemporaryDirectory() as odir: + fname_path = pjoin(odir, "temp-lines.vtk") + px.write(poly, fname_path) + + out_lines, out_colors = read_lines(fname_path) + + npt.assert_equal(len(out_lines), 1) + npt.assert_equal(out_lines[0].dtype, np.float32) + npt.assert_array_almost_equal(out_lines[0], vertices) + npt.assert_equal(out_colors, None) + + +def test_read_lines_without_line_elements(): + vertices = np.array([[0, 0, 0], [1, 0, 0], [0, 1, 0]], dtype=np.float64) + faces = np.array([[0, 1, 2]], dtype=np.int32) + + poly = px.make_polydata(vertices, [("triangle", faces)]) + + with InTemporaryDirectory() as odir: + fname_path = pjoin(odir, "temp-no-lines.vtk") + px.write(poly, fname_path) + + out_lines, out_colors = read_lines(fname_path) + + npt.assert_equal(out_lines, []) + npt.assert_equal(out_colors, None) + + def test_save_load_image(): l_ext = ["png", "jpeg", "jpg", "bmp", "tiff"] fury_logo_link = ( diff --git a/pyproject.toml b/pyproject.toml index b2eb86e687..79656b3bd1 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -50,7 +50,8 @@ dependencies = [ "lazy_loader>=0.4", "pygfx>=0.16.0", "glfw>=2.7.0", - "aiohttp" + "aiohttp", + "polyxios" ] dynamic = ["version"] diff --git a/requirements/default.txt b/requirements/default.txt index 7663e5103c..3ed5cdc820 100644 --- a/requirements/default.txt +++ b/requirements/default.txt @@ -8,3 +8,4 @@ lazy_loader>=0.4 pygfx>=0.16.0 glfw>=2.7.0 aiohttp +polyxios From 2bf6c841656a7e5beca6f24c5d4f429a1bf5b1cc Mon Sep 17 00:00:00 2001 From: Praneeth Shetty Date: Tue, 30 Jun 2026 01:03:22 +0530 Subject: [PATCH 2/2] NF: updating tutorial to show mesh, line and point demo --- docs/examples/viz_read_mesh.py | 212 ++++++++++++++++++++++----------- fury/io.py | 3 - pyproject.toml | 2 +- requirements/default.txt | 2 +- 4 files changed, 146 insertions(+), 73 deletions(-) diff --git a/docs/examples/viz_read_mesh.py b/docs/examples/viz_read_mesh.py index c3b266ef60..18f0a09188 100644 --- a/docs/examples/viz_read_mesh.py +++ b/docs/examples/viz_read_mesh.py @@ -1,27 +1,34 @@ """ -============== -Reading a Mesh -============== - -This example demonstrates how to fetch 3D mesh assets from the -``polyxios-data`` repository, read them into NumPy arrays with -:func:`fury.io.read_mesh`, and render them as colored surface actors. We load -two files of different formats side by side -- a ``.vtp`` surface and a ``.obj`` -mesh -- to highlight that the same code handles both. - -Polyxios is the lightweight, dependency-free mesh I/O backend used by FURY. It -reads and writes the common scientific surface formats (``.vtk``, ``.vtp``, -``.ply``, ``.obj`` and the VTK XML family) straight into NumPy arrays, and its -``fetch`` helper downloads and caches sample assets so tutorials and tests can -run without bundling large files. - -``read_mesh`` always returns the same simple triple regardless of the source -format: - -* ``vertices`` -- ``(N, 3)`` float32 point coordinates, -* ``faces`` -- ``(M, 3)`` int32 triangle indices (surfaces are triangulated - for you), and -* ``colors`` -- ``(N, 3)`` float32 per-vertex RGB in ``[0, 1]`` (or ``None``). +================================= +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)`` where ``vertices`` is an ``(N, 3)`` float32 array, + ``faces`` is an ``(M, 3)`` int32 array of triangulated indices, and ``colors`` is an + optional ``(N, 3)`` float32 color map. + +* **Line Streams (via ``read_lines``):** Returns a tuple of ``(lines, colors)``. Here, + ``lines`` is a list of independent ``(P, 3)`` float32 arrays (where each array + represents an individual continuous stroke or fiber pathway), and ``colors`` contains + a corresponding list of per-vertex color maps. + +* **Point Clouds (via ``read_points``):** Returns a tuple of ``(points, colors)`` where + ``points`` is an ``(N, 3)`` float32 coordinate block representing un-connected point + data, and ``colors`` is an optional per-point attribute array. """ ######################################################################################### @@ -30,16 +37,83 @@ import polyxios as px from fury import actor, ui, window -from fury.io import read_mesh +from fury.io import read_lines, read_mesh, read_points ######################################################################################### -# Define a small helper that reads a mesh and turns it into a FURY actor with -# :func:`fury.actor.surface`. It centers and normalizes the geometry and falls -# back to a height-based color gradient when the file has no colors. -# -# ``read_mesh`` returns the same ``(vertices, faces, colors)`` triple for every -# supported format, so the exact same code path loads ``.obj`` and ``.vtp`` -# files alike. +# 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): @@ -65,65 +139,65 @@ def mesh_to_actor(file_path): ######################################################################################### -# Fetch two assets from the ``polyxios-data`` release. ``fetch`` returns the -# absolute path to the locally cached file, downloading it on first use. +# Fetch the sample data assets via polyxios. # -# * ``Human.vtp`` is a VTK XML PolyData surface that already carries per-vertex -# colors. -# * ``stanford-bunny.obj`` is the classic Stanford bunny stored as a Wavefront -# OBJ with plain geometry (no colors). +# * ``Human.vtp`` a surface mesh possessing native topological faces and color tracks. +# * ``hello.vtk`` contains line elements forming spatial lettering out of poly-line. +# * ``star.mesh`` handles plain point coordinates lacking explicitly structured links. human_path = px.fetch("Human.vtp") -bunny_path = px.fetch("stanford-bunny.obj") +line_path = px.fetch("hello.vtk") +ball_path = px.fetch("star.mesh") -human_actor, human_nv, human_nf = mesh_to_actor(human_path) -bunny_actor, bunny_nv, bunny_nf = mesh_to_actor(bunny_path) +# 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: {human_nv} vertices, {human_nf} faces") -print(f"stanford-bunny.obj: {bunny_nv} vertices, {bunny_nf} faces") +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. -human_actor.rotate((-90, 180, 0)) +mesh_actor.rotate((-90, 180, 0)) ######################################################################################### -# Place the two meshes side by side so both formats are visible at once. -human_actor.local.position = (-1.3, 0.0, 0.0) -bunny_actor.local.position = (1.3, 0.0, 0.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 a 3D text label beneath each mesh. ``actor.text`` accepts a list of -# strings with matching positions and returns a Group of 3D Text actors that -# live in the scene (unlike the 2D HUD overlay, these are part of the world and -# move with the camera). +# Add matching 3D text labels directly beneath each of the three objects. labels_actor = actor.text( - ["Human.vtp", "stanford-bunny.obj"], - position=[(-1.3, -1.25, 0.0), (1.3, -1.25, 0.0)], + ["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.18, + font_size=0.16, anchor="top-center", ) ######################################################################################### -# Set up the 3D scene and add both mesh actors and their labels. +# Set up the 3D scene and register all visual elements. scene = window.Scene(background=(0.05, 0.05, 0.08)) -scene.add(human_actor) -scene.add(bunny_actor) +scene.add(mesh_actor) +scene.add(line_actor) +scene.add(point_actor) scene.add(labels_actor) ######################################################################################### -# Add a 2D text overlay describing what is being shown. The same few lines of -# code load OBJ, PLY, VTK and VTP files because ``read_mesh`` normalizes every -# format to the same NumPy arrays. +# Add a 2D text HUD overlay detailing what each object represents. info_text = ( - f"FURY x Polyxios mesh reader\n" - f"Left: Human.vtp ({human_nv} verts, {human_nf} faces, file colors)\n" - f"Right: stanford-bunny.obj ({bunny_nv} verts, {bunny_nf} faces)\n" - f"Both read via fury.io.read_mesh -> (vertices, faces, colors)\n" - f"Supported: .vtk .vtp .ply .obj (auto-detected by extension)" + 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( @@ -137,12 +211,14 @@ def mesh_to_actor(file_path): scene.add(hud_label) ######################################################################################### -# Initialize the ShowManager, position the virtual camera to frame both meshes, -# and launch the rendering loop. -show_m = window.ShowManager(scene=scene, size=(1024, 768), title="FURY Mesh Reader") +# 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, 5.0) +camera.local.position = (0.0, -0.15, 6.0) camera.look_at((0.0, -0.15, 0.0)) show_m.start() diff --git a/fury/io.py b/fury/io.py index 38ec0c6af6..03a939040b 100644 --- a/fury/io.py +++ b/fury/io.py @@ -341,9 +341,6 @@ def read_mesh(file_path, *, format=None): """ Read a mesh from a file using polyxios. - Supported formats include the ones handled by polyxios, such as VTK, VTP, - PLY, OBJ and the VTK XML family. - Parameters ---------- file_path : str diff --git a/pyproject.toml b/pyproject.toml index 79656b3bd1..bf54422201 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -51,7 +51,7 @@ dependencies = [ "pygfx>=0.16.0", "glfw>=2.7.0", "aiohttp", - "polyxios" + "polyxios>=0.2.0", ] dynamic = ["version"] diff --git a/requirements/default.txt b/requirements/default.txt index 3ed5cdc820..d99a400bff 100644 --- a/requirements/default.txt +++ b/requirements/default.txt @@ -8,4 +8,4 @@ lazy_loader>=0.4 pygfx>=0.16.0 glfw>=2.7.0 aiohttp -polyxios +polyxios>=0.2.0