Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

NF: Sphere, Geometry & Material #946

Open
wants to merge 3 commits into
base: v2
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
99 changes: 99 additions & 0 deletions fury/actor.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
import numpy as np

from fury.geometry import buffer_to_geometry, create_mesh
from fury.material import _create_mesh_material
import fury.primitive as fp


def sphere(
centers,
colors,
*,
radii=1.0,
phi=16,
theta=16,
opacity=None,
material="phong",
enable_picking=True,
):
"""
Visualize one or many spheres with different colors and radii.

Parameters
----------
centers : ndarray, shape (N, 3)
Spheres positions.
colors : ndarray, shape (N, 3) or (N, 4) or tuple (3,) or tuple (4,)
RGB or RGBA (for opacity) R, G, B, and A should be in the range [0, 1].
radii : float or ndarray, shape (N,)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

, optional to add

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

you just missed this one

Sphere radius. Can be a single value for all spheres or an array of
radii for each sphere.
phi : int, optional
The number of segments in the longitude direction.
theta : int, optional
The number of segments in the latitude direction.
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 spheres. Options are 'phong' and 'basic'.
enable_picking : bool, optional
Whether the spheres should be pickable in a 3D scene.

Returns
-------
mesh_actor : Actor
A mesh actor containing the generated spheres, with the specified
material and properties.

Examples
--------
>>> from fury import window, actor
>>> scene = window.Scene()
>>> centers = np.random.rand(5, 3)
>>> colors = np.random.rand(5, 3)
>>> sphere_actor = actor.sphere(centers, colors, radii=0.5)
>>> scene.add(sphere_actor)
>>> # window.show(scene)
"""

scales = radii
directions = (1, 0, 0)

vertices, faces = fp.prim_sphere(phi=phi, theta=theta)

res = fp.repeat_primitive(
vertices,
faces,
directions=directions,
centers=centers,
colors=colors,
scales=scales,
)
big_vertices, big_faces, big_colors, _ = res

prim_count = len(centers)

big_colors = big_colors / 255.0

if isinstance(opacity, (int, float)):
if big_colors.shape[1] == 3:
big_colors = np.hstack(
(big_colors, np.full((big_colors.shape[0], 1), opacity))
)
else:
big_colors[:, 3] *= opacity

geo = buffer_to_geometry(
indices=big_faces.astype("int32"),
positions=big_vertices.astype("float32"),
texcoords=big_vertices.astype("float32"),
colors=big_colors.astype("float32"),
)

mat = _create_mesh_material(material=material, enable_picking=enable_picking)
obj = create_mesh(geometry=geo, material=mat)
obj.local.position = centers[0]
obj.prim_count = prim_count
return obj
43 changes: 43 additions & 0 deletions fury/geometry.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
from pygfx import Geometry, Mesh


def buffer_to_geometry(positions, **kwargs):
"""
Convert a buffer to a geometry object.

Parameters
----------
positions : array_like
The positions buffer.
kwargs : dict
A dict of attributes to define on the geometry object. Keys can be
"colors", "normals", "texcoords",
"indices", ...
skoudoro marked this conversation as resolved.
Show resolved Hide resolved

Returns
-------
geo : Geometry
The geometry object.
"""
geo = Geometry(positions=positions, **kwargs)
return geo


def create_mesh(geometry, material):
"""
Create a mesh object.

Parameters
----------
geometry : Geometry
The geometry object.
material : Material
The material object.
skoudoro marked this conversation as resolved.
Show resolved Hide resolved

Returns
-------
mesh : Mesh
The mesh object.
"""
mesh = Mesh(geometry=geometry, material=material)
return mesh
64 changes: 64 additions & 0 deletions fury/material.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
import pygfx as gfx


def _create_mesh_material(
*, material="phong", enable_picking=True, color=None, opacity=1.0, mode="vertex"
):
"""
Create a mesh material.

Parameters
----------
material : str, optional
The type of material to create. Options are 'phong' (default) and
skoudoro marked this conversation as resolved.
Show resolved Hide resolved
'basic'.
enable_picking : bool, optional
Whether the material should be pickable in a scene.
color : tuple or None, optional
The color of the material, represented as an RGBA tuple. If None, the
default color is used.
opacity : float, optional
The opacity of the material, from 0 (transparent) to 1 (opaque).
If RGBA is provided, the final alpha will be:
final_alpha = alpha_in_RGBA * opacity
mode : str, optional
The color mode of the material. Options are 'auto' and 'vertex'.

Returns
-------
gfx.MeshMaterial
A mesh material object of the specified type with the given properties.
"""

if not (0 <= opacity <= 1):
raise ValueError("Opacity must be between 0 and 1.")

if color is None and mode == "auto":
raise ValueError("Color must be specified when mode is 'auto'.")

elif color is not None:
if len(color) == 3:
color = (*color, opacity)
elif len(color) == 4:
color = color
color[3] *= opacity
else:
raise ValueError("Color must be a tuple of length 3 or 4.")

if mode == "vertex":
color = (1, 1, 1)

if material == "phong":
return gfx.MeshPhongMaterial(
pick_write=enable_picking,
color_mode=mode,
color=color,
)
elif material == "basic":
return gfx.MeshBasicMaterial(
pick_write=enable_picking,
color_mode=mode,
color=color,
)
skoudoro marked this conversation as resolved.
Show resolved Hide resolved
else:
raise ValueError(f"Unsupported material type: {material}")
6 changes: 2 additions & 4 deletions fury/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,7 @@ def map_coordinates_3d_4d(input_array, indices):
if input_array.ndim == 4:
values_4d = []
for i in range(input_array.shape[-1]):
values_tmp = map_coordinates(
input_array[..., i], indices.T, order=1)
values_tmp = map_coordinates(input_array[..., i], indices.T, order=1)
values_4d.append(values_tmp)
return np.ascontiguousarray(np.array(values_4d).T)

Expand Down Expand Up @@ -152,8 +151,7 @@ def get_grid_cells_position(shapes, *, aspect_ratio=16 / 9.0, dim=None):

# Use indexing="xy" so the cells are in row-major (C-order). Also,
# the Y coordinates are negative so the cells are order from top to bottom.
X, Y, Z = np.meshgrid(np.arange(n_cols), -
np.arange(n_rows), [0], indexing="xy")
X, Y, Z = np.meshgrid(np.arange(n_cols), -np.arange(n_rows), [0], indexing="xy")
return cell_shape * np.array([X.flatten(), Y.flatten(), Z.flatten()]).T


Expand Down
Loading