"""Surface actors for FURY."""
import logging
import os
import numpy as np
from fury.actor import Group, create_mesh
from fury.geometry import buffer_to_geometry
from fury.io import load_image_texture
from fury.material import _create_mesh_material, validate_opacity
from fury.utils import generate_planar_uvs, voxel_mesh_by_object
[docs]
def surface(
vertices,
faces,
*,
material="phong",
colors=None,
texture=None,
texture_axis="xy",
texture_coords=None,
normals=None,
opacity=1.0,
):
"""
Create a surface mesh actor from vertices and faces.
Parameters
----------
vertices : ndarray, shape (N, 3)
The vertex positions of the surface mesh.
faces : ndarray, shape (M, 3)
The indices of the vertices that form each triangular face.
material : str, optional
The material type for the surface mesh. Options are 'phong' and 'basic'. This
option only works with colors is passed.
colors : ndarray, shape (N, 3) or (N, 4) or tuple (3,) or tuple (4,), optional
RGB or RGBA values in the range [0, 1].
texture : str, optional
Path to the texture image file.
texture_axis : str, optional
The axis to generate UV coordinates for the texture. Options are 'xy', 'yz',
and 'xz'. This option only works with texture is passed.
texture_coords : ndarray, shape (N, 2), optional
Predefined UV coordinates for the texture mapping. If not provided, they will
be generated based on the `texture_axis`.
normals : ndarray, shape (N, 3), optional
The normal vectors for each vertex. If not provided, normals will be
computed automatically.
opacity : float, optional
Takes values from 0 (fully transparent) to 1 (opaque).
Returns
-------
Mesh
A mesh actor containing the generated surface with the specified properties.
"""
geo = None
mat = None
opacity = validate_opacity(opacity)
if colors is not None:
if texture is not None:
logging.warning("Texture will be ignored when colors are provided.")
if isinstance(colors, np.ndarray) and colors.shape[0] == vertices.shape[0]:
geo = buffer_to_geometry(
positions=vertices.astype("float32"),
indices=faces.astype("int32"),
colors=colors,
normals=normals.astype("float32") if normals is not None else None,
)
mat = _create_mesh_material(
material=material, mode="vertex", opacity=opacity
)
elif isinstance(colors, (tuple, list, np.ndarray)) and len(colors) == 3:
geo = buffer_to_geometry(
positions=vertices.astype("float32"),
indices=faces.astype("int32"),
normals=normals.astype("float32") if normals is not None else None,
)
mat = _create_mesh_material(
material=material, mode="auto", opacity=opacity, color=colors
)
else:
raise ValueError(
"Colors must be either an ndarray with shape (N, 3) or (N, 4), "
"or a tuple/list of length 3 for RGB colors."
)
elif texture is not None:
if not os.path.exists(texture):
raise FileNotFoundError(f"Texture file '{texture}' not found.")
tex = load_image_texture(texture)
if texture_coords is None:
logging.warning(
"texture option currently only supports planar projection,"
" if the texture_coords are not provided, the plane can be provided"
" by texture_axis parameter."
)
texture_coords = generate_planar_uvs(vertices, axis=texture_axis)
elif (
texture_coords.shape[0] != vertices.shape[0] or texture_coords.shape[1] != 2
):
raise ValueError(
"texture_coords must be an ndarray with shape (N, 2) "
"where N is the number of vertices."
)
geo = buffer_to_geometry(
positions=vertices.astype("float32"),
indices=faces.astype("int32"),
texcoords=texture_coords.astype("float32"),
normals=normals.astype("float32") if normals is not None else None,
)
mat = _create_mesh_material(
material=material, texture=tex, opacity=opacity, mode="auto"
)
else:
geo = buffer_to_geometry(
positions=vertices.astype("float32"), indices=faces.astype("int32")
)
mat = _create_mesh_material(material=material, opacity=opacity)
obj = create_mesh(geo, mat)
return obj
[docs]
def contour_from_volume(data, *, color=(1, 0, 0), opacity=0.5, material="phong"):
"""
Generate surface actor from a binary ROI.
Parameters
----------
data : ndarray, shape (X, Y, Z)
An ROI file that will be binarized and displayed.
color : tuple, optional
The RGB output color of the contour in the range [0, 1].
opacity : float, optional
The opacity of the contour.
Takes values from 0 (fully transparent) to 1 (opaque).
material : str, optional
The material type for the contour mesh. Options are 'phong' and 'basic'.
Returns
-------
Group
A group of actors containing the generated contours from the volume data.
"""
if color is None or len(color) != 3:
raise ValueError("Color must be a tuple of three values (R, G, B).")
surface_data = voxel_mesh_by_object(data, connectivity=1)
contours = Group()
for surf in surface_data.values():
surface_actor = surface(
surf["verts"],
surf["faces"],
colors=color,
opacity=opacity,
material=material,
)
contours.add(surface_actor)
return contours