Skip to content
Snippets Groups Projects
Commit 16c5a7cb authored by s212246's avatar s212246 Committed by fima
Browse files

Mesh pygel3d

parent 3553c935
No related branches found
No related tags found
1 merge request!156Mesh pygel3d
docs/assets/screenshots/pygel3d_visualization.png

197 KiB

......@@ -44,7 +44,7 @@ The command line interface allows you to run graphical user interfaces directly
!!! Example
Here's an example of how to open the [Data Explorer](gui.md#qim3d.gui.data_explorer)
Here's an example of how to open the [Data Explorer](../gui/gui.md#qim3d.gui.data_explorer)
``` title="Command"
qim3d gui --data-explorer
......
......@@ -8,5 +8,5 @@
- Downloader
- export_ome_zarr
- import_ome_zarr
- save_mesh
- load_mesh
\ No newline at end of file
- load_mesh
- save_mesh
\ No newline at end of file
......@@ -21,7 +21,7 @@ The `qim3d` library provides a set of custom made GUIs that ease the interaction
```
In general, the GUIs can be launched directly from the command line.
For details see [here](cli.md#qim3d-gui).
For details see [here](../cli/cli.md#qim3d-gui).
::: qim3d.gui.data_explorer
options:
......
......@@ -11,7 +11,7 @@ hide:
Below, you'll find details about the version history of `qim3d`.
Remember to keep your pip installation [up to date](index.md/#get-the-latest-version) so that you have the latest features!
Remember to keep your pip installation [up to date](../../index.md/#get-the-latest-version) so that you have the latest features!
### v1.0.0 (21/01/2025)
......
......@@ -9,7 +9,7 @@ Documentation available at https://platform.qim.dk/qim3d/
"""
__version__ = '1.0.0'
__version__ = '1.1.0'
import importlib as _importlib
......
import numpy as np
import trimesh
import qim3d
import qim3d.processing
from qim3d.utils._logger import log
import qim3d
from pygel3d import hmesh
def volume(obj: np.ndarray | trimesh.Trimesh, **mesh_kwargs) -> float:
def volume(obj: np.ndarray|hmesh.Manifold) -> float:
"""
Compute the volume of a 3D volume or mesh.
Compute the volume of a 3D mesh using the Pygel3D library.
Args:
obj (np.ndarray or trimesh.Trimesh): Either a np.ndarray volume or a mesh object of type trimesh.Trimesh.
**mesh_kwargs (Any): Additional arguments for mesh creation if the input is a volume.
obj (numpy.ndarray or pygel3d.hmesh.Manifold): Either a np.ndarray volume or a mesh object of type pygel3d.hmesh.Manifold.
Returns:
volume (float): The volume of the object.
Example:
Compute volume from a mesh:
```python
......@@ -26,8 +23,8 @@ def volume(obj: np.ndarray | trimesh.Trimesh, **mesh_kwargs) -> float:
mesh = qim3d.io.load_mesh('path/to/mesh.obj')
# Compute the volume of the mesh
vol = qim3d.features.volume(mesh)
print('Volume:', vol)
volume = qim3d.features.volume(mesh)
print(f'Volume: {volume}')
```
Compute volume from a np.ndarray:
......@@ -36,33 +33,30 @@ def volume(obj: np.ndarray | trimesh.Trimesh, **mesh_kwargs) -> float:
# Generate a 3D blob
synthetic_blob = qim3d.generate.noise_object(noise_scale = 0.015)
synthetic_blob = qim3d.generate.noise_object(noise_scale = 0.015)
# Compute the volume of the blob
volume = qim3d.features.volume(synthetic_blob, level=0.5)
volume = qim3d.features.volume(synthetic_blob, level=0.5)
print('Volume:', volume)
volume = qim3d.features.volume(synthetic_blob)
print(f'Volume: {volume}')
```
"""
if isinstance(obj, np.ndarray):
log.info('Converting volume to mesh.')
obj = qim3d.mesh.from_volume(obj, **mesh_kwargs)
return obj.volume
if isinstance(obj, np.ndarray):
log.info("Converting volume to mesh.")
obj = qim3d.mesh.from_volume(obj)
return hmesh.volume(obj)
def area(obj: np.ndarray | trimesh.Trimesh, **mesh_kwargs) -> float:
def area(obj: np.ndarray|hmesh.Manifold) -> float:
"""
Compute the surface area of a 3D volume or mesh.
Compute the surface area of a 3D mesh using the Pygel3D library.
Args:
obj (np.ndarray or trimesh.Trimesh): Either a np.ndarray volume or a mesh object of type trimesh.Trimesh.
**mesh_kwargs (Any): Additional arguments for mesh creation if the input is a volume.
obj (numpy.ndarray or pygel3d.hmesh.Manifold): Either a np.ndarray volume or a mesh object of type pygel3d.hmesh.Manifold.
Returns:
area (float): The surface area of the object.
area (float): The surface area of the object.
Example:
Compute area from a mesh:
```python
......@@ -73,8 +67,7 @@ def area(obj: np.ndarray | trimesh.Trimesh, **mesh_kwargs) -> float:
# Compute the surface area of the mesh
area = qim3d.features.area(mesh)
area = qim3d.features.area(mesh)
print(f"Area: {area}")
print(f'Area: {area}')
```
Compute area from a np.ndarray:
......@@ -83,38 +76,30 @@ def area(obj: np.ndarray | trimesh.Trimesh, **mesh_kwargs) -> float:
# Generate a 3D blob
synthetic_blob = qim3d.generate.noise_object(noise_scale = 0.015)
synthetic_blob = qim3d.generate.noise_object(noise_scale = 0.015)
# Compute the surface area of the blob
volume = qim3d.features.area(synthetic_blob, level=0.5)
volume = qim3d.features.area(synthetic_blob, level=0.5)
print('Area:', volume)
area = qim3d.features.area(synthetic_blob)
print(f'Area: {area}')
```
"""
if isinstance(obj, np.ndarray):
log.info('Converting volume to mesh.')
obj = qim3d.mesh.from_volume(obj, **mesh_kwargs)
obj = qim3d.mesh.from_volume(obj, **mesh_kwargs)
return obj.area
if isinstance(obj, np.ndarray):
log.info("Converting volume to mesh.")
obj = qim3d.mesh.from_volume(obj)
return hmesh.area(obj)
def sphericity(obj: np.ndarray | trimesh.Trimesh, **mesh_kwargs) -> float:
def sphericity(obj: np.ndarray|hmesh.Manifold) -> float:
"""
Compute the sphericity of a 3D volume or mesh.
Sphericity is a measure of how spherical an object is. It is defined as the ratio
of the surface area of a sphere with the same volume as the object to the object's
actual surface area.
Compute the sphericity of a 3D mesh using the Pygel3D library.
Args:
obj (np.ndarray or trimesh.Trimesh): Either a np.ndarray volume or a mesh object of type trimesh.Trimesh.
**mesh_kwargs (Any): Additional arguments for mesh creation if the input is a volume.
obj (numpy.ndarray or pygel3d.hmesh.Manifold): Either a np.ndarray volume or a mesh object of type pygel3d.hmesh.Manifold.
Returns:
sphericity (float): A float value representing the sphericity of the object.
sphericity (float): The sphericity of the object.
Example:
Compute sphericity from a mesh:
```python
......@@ -125,7 +110,7 @@ def sphericity(obj: np.ndarray | trimesh.Trimesh, **mesh_kwargs) -> float:
# Compute the sphericity of the mesh
sphericity = qim3d.features.sphericity(mesh)
sphericity = qim3d.features.sphericity(mesh)
print(f'Sphericity: {sphericity}')
```
Compute sphericity from a np.ndarray:
......@@ -134,33 +119,25 @@ def sphericity(obj: np.ndarray | trimesh.Trimesh, **mesh_kwargs) -> float:
# Generate a 3D blob
synthetic_blob = qim3d.generate.noise_object(noise_scale = 0.015)
synthetic_blob = qim3d.generate.noise_object(noise_scale = 0.015)
# Compute the sphericity of the blob
sphericity = qim3d.features.sphericity(synthetic_blob, level=0.5)
sphericity = qim3d.features.sphericity(synthetic_blob, level=0.5)
sphericity = qim3d.features.sphericity(synthetic_blob)
print(f'Sphericity: {sphericity}')
```
!!! info "Limitations due to pixelation"
Sphericity is particularly sensitive to the resolution of the mesh, as it directly impacts the accuracy of surface area and volume calculations.
Since the mesh is generated from voxel-based 3D volume data, the discrete nature of the voxels leads to pixelation effects that reduce the precision of sphericity measurements.
Higher resolution meshes may mitigate these errors but often at the cost of increased computational demands.
"""
if isinstance(obj, np.ndarray):
log.info('Converting volume to mesh.')
obj = qim3d.mesh.from_volume(obj, **mesh_kwargs)
obj = qim3d.mesh.from_volume(obj, **mesh_kwargs)
log.info("Converting volume to mesh.")
obj = qim3d.mesh.from_volume(obj)
volume = qim3d.features.volume(obj)
area = qim3d.features.area(obj)
volume = qim3d.features.volume(obj)
area = qim3d.features.area(obj)
if area == 0:
log.warning('Surface area is zero, sphericity is undefined.')
return np.nan
sphericity = (np.pi ** (1 / 3) * (6 * volume) ** (2 / 3)) / area
log.info(f'Sphericity: {sphericity}')
return sphericity
# log.info(f"Sphericity: {sphericity}")
return sphericity
\ No newline at end of file
......@@ -334,7 +334,6 @@ def gaussian(
sigma (float or sequence of floats): The standard deviations of the Gaussian filter are given for each axis as a sequence, or as a single number, in which case it is equal for all axes.
dask (bool, optional): Whether to use Dask for the Gaussian filter.
chunks (int or tuple or "'auto'", optional): Defines how to divide the array into blocks when using Dask. Can be an integer, tuple, size in bytes, or "auto" for automatic sizing.
*args (Any): Additional positional arguments for the Gaussian filter.
**kwargs (Any): Additional keyword arguments for the Gaussian filter.
Returns:
......
......@@ -27,8 +27,11 @@ import qim3d
from qim3d.utils import Memory, log
from qim3d.utils._misc import get_file_size, sizeof, stringify_path
from qim3d.utils._progress_bar import FileLoadingProgressBar
import trimesh
from pygel3d import hmesh
from typing import Optional, Dict
dask.config.set(scheduler='processes')
dask.config.set(scheduler="processes")
class DataLoader:
......@@ -891,15 +894,23 @@ def load(
return data
def load_mesh(filename: str) -> trimesh.Trimesh:
def load_mesh(filename: str) -> hmesh.Manifold:
"""
Load a mesh from an .obj file using trimesh.
Load a mesh from a specific file.
This function is based on the [PyGEL3D library's loading function implementation](https://www2.compute.dtu.dk/projects/GEL/PyGEL/pygel3d/hmesh.html#load).
Supported formats:
- `X3D`
- `OBJ`
- `OFF`
- `PLY`
Args:
filename (str or os.PathLike): The path to the .obj file.
filename (str or os.PathLike): The path to the file.
Returns:
mesh (trimesh.Trimesh): A trimesh object containing the mesh data (vertices and faces).
mesh (hmesh.Manifold or None): A hmesh object containing the mesh data or None if loading failed.
Example:
```python
......@@ -909,5 +920,6 @@ def load_mesh(filename: str) -> trimesh.Trimesh:
```
"""
mesh = trimesh.load(filename)
return mesh
mesh = hmesh.load(filename)
return mesh
\ No newline at end of file
......@@ -37,7 +37,8 @@ import trimesh
import zarr
from pydicom.dataset import FileDataset, FileMetaDataset
from pydicom.uid import UID
import trimesh
from pygel3d import hmesh
from qim3d.utils import log
from qim3d.utils._misc import stringify_path
......@@ -484,29 +485,54 @@ def save(
).save(path, data)
def save_mesh(filename: str, mesh: trimesh.Trimesh) -> None:
# def save_mesh(
# filename: str,
# mesh: trimesh.Trimesh
# ) -> None:
# """
# Save a trimesh object to an .obj file.
# Args:
# filename (str or os.PathLike): The name of the file to save the mesh.
# mesh (trimesh.Trimesh): A trimesh.Trimesh object representing the mesh.
# Example:
# ```python
# import qim3d
# vol = qim3d.generate.noise_object(base_shape=(32, 32, 32),
# final_shape=(32, 32, 32),
# noise_scale=0.05,
# order=1,
# gamma=1.0,
# max_value=255,
# threshold=0.5)
# mesh = qim3d.mesh.from_volume(vol)
# qim3d.io.save_mesh("mesh.obj", mesh)
# ```
# """
# # Export the mesh to the specified filename
# mesh.export(filename)
def save_mesh(filename: str, mesh: hmesh.Manifold) -> None:
"""
Save a trimesh object to an .obj file.
Save a mesh object to a specific file.
This function is based on the [PyGEL3D library's saving function implementation](https://www2.compute.dtu.dk/projects/GEL/PyGEL/pygel3d/hmesh.html#save).
Args:
filename (str or os.PathLike): The name of the file to save the mesh.
mesh (trimesh.Trimesh): A trimesh.Trimesh object representing the mesh.
filename (str or os.PathLike): The path to save file to. File format is chosen based on the extension. Supported extensions are: '.x3d', '.obj', '.off'.
mesh (pygel3d.hmesh.Manifold): A hmesh.Manifold object representing the mesh.
Example:
```python
import qim3d
vol = qim3d.generate.noise_object(base_shape=(32, 32, 32),
final_shape=(32, 32, 32),
noise_scale=0.05,
order=1,
gamma=1.0,
max_value=255,
threshold=0.5)
mesh = qim3d.mesh.from_volume(vol)
qim3d.io.save_mesh("mesh.obj", mesh)
synthetic_blob = qim3d.generate.noise_object(noise_scale = 0.015)
mesh = qim3d.mesh.from_volume(synthetic_blob)
qim3d.io.save_mesh("mesh.obj", mesh)
```
"""
# Export the mesh to the specified filename
mesh.export(filename)
hmesh.save(filename, mesh)
\ No newline at end of file
from typing import Any, Tuple
import numpy as np
import trimesh
from skimage import filters, measure
from skimage import measure, filters
from pygel3d import hmesh
from typing import Tuple, Any
from qim3d.utils._logger import log
def from_volume(
volume: np.ndarray,
level: float = None,
step_size: int = 1,
allow_degenerate: bool = False,
padding: Tuple[int, int, int] = (2, 2, 2),
**kwargs: Any,
) -> trimesh.Trimesh:
"""
Convert a volume to a mesh using the Marching Cubes algorithm, with optional thresholding and padding.
**kwargs: any
) -> hmesh.Manifold:
""" Convert a 3D numpy array to a mesh object using the [volumetric_isocontour](https://www2.compute.dtu.dk/projects/GEL/PyGEL/pygel3d/hmesh.html#volumetric_isocontour) function from Pygel3D.
Args:
volume (np.ndarray): The 3D numpy array representing the volume.
level (float, optional): The threshold value for Marching Cubes. If None, Otsu's method is used.
step_size (int, optional): The step size for the Marching Cubes algorithm.
allow_degenerate (bool, optional): Whether to allow degenerate (i.e. zero-area) triangles in the end-result. If False, degenerate triangles are removed, at the cost of making the algorithm slower. Default False.
padding (tuple of ints, optional): Padding to add around the volume.
**kwargs: Additional keyword arguments to pass to `skimage.measure.marching_cubes`.
volume (np.ndarray): A 3D numpy array representing a volume.
**kwargs: Additional arguments to pass to the Pygel3D volumetric_isocontour function.
Raises:
ValueError: If the input volume is not a 3D numpy array or if the input volume is empty.
Returns:
mesh (trimesh.Trimesh): The generated mesh.
hmesh.Manifold: A Pygel3D mesh object representing the input volume.
Example:
Convert a 3D numpy array to a Pygel3D mesh object:
```python
import qim3d
vol = qim3d.generate.noise_object(base_shape=(128,128,128),
final_shape=(128,128,128),
noise_scale=0.03,
order=1,
gamma=1,
max_value=255,
threshold=0.5,
dtype='uint8'
)
mesh = qim3d.mesh.from_volume(vol, step_size=3)
qim3d.viz.mesh(mesh.vertices, mesh.faces)
```
<iframe src="https://platform.qim.dk/k3d/mesh_visualization.html" width="100%" height="500" frameborder="0"></iframe>
# Generate a 3D blob
synthetic_blob = qim3d.generate.noise_object(noise_scale = 0.015)
# Convert the 3D numpy array to a Pygel3D mesh object
mesh = qim3d.mesh.from_volume(synthetic_blob)
```
"""
if volume.ndim != 3:
raise ValueError('The input volume must be a 3D numpy array.')
# Compute the threshold level if not provided
if level is None:
level = filters.threshold_otsu(volume)
log.info(f"Computed level using Otsu's method: {level}")
# Apply padding to the volume
if padding is not None:
pad_z, pad_y, pad_x = padding
padding_value = np.min(volume)
volume = np.pad(
volume,
((pad_z, pad_z), (pad_y, pad_y), (pad_x, pad_x)),
mode='constant',
constant_values=padding_value,
)
log.info(f'Padded volume with {padding} to shape: {volume.shape}')
# Call skimage.measure.marching_cubes with user-provided kwargs
verts, faces, normals, values = measure.marching_cubes(
volume,
level=level,
step_size=step_size,
allow_degenerate=allow_degenerate,
**kwargs,
)
# Create the Trimesh object
mesh = trimesh.Trimesh(vertices=verts, faces=faces)
# Fix face orientation to ensure normals point outwards
trimesh.repair.fix_inversion(mesh, multibody=True)
raise ValueError("The input volume must be a 3D numpy array.")
if volume.size == 0:
raise ValueError("The input volume must not be empty.")
return mesh
mesh = hmesh.volumetric_isocontour(volume, **kwargs)
return mesh
\ No newline at end of file
import pytest
import numpy as np
from pygel3d import hmesh
import qim3d
def test_from_volume_valid_input():
"""Test that from_volume returns a hmesh.Manifold object for a valid 3D input."""
volume = np.random.rand(50, 50, 50).astype(np.float32) # Generate a random 3D volume
mesh = qim3d.mesh.from_volume(volume)
assert isinstance(mesh, hmesh.Manifold) # Check if output is a Manifold object
def test_from_volume_invalid_input():
"""Test that from_volume raises ValueError for non-3D input."""
volume = np.random.rand(50, 50) # A 2D array
with pytest.raises(ValueError, match="The input volume must be a 3D numpy array."):
qim3d.mesh.from_volume(volume)
def test_from_volume_empty_array():
"""Test how from_volume handles an empty 3D array."""
volume = np.empty((0, 0, 0)) # Empty 3D array
with pytest.raises(ValueError): # It should fail because it doesn't make sense to generate a mesh from empty data
qim3d.mesh.from_volume(volume)
def test_from_volume_with_kwargs():
"""Test that from_volume correctly passes kwargs."""
volume = np.random.rand(50, 50, 50).astype(np.float32)
# Mock volumetric_isocontour to check if kwargs are passed
def mock_volumetric_isocontour(vol, **kwargs):
assert "isovalue" in kwargs
assert kwargs["isovalue"] == 0.5
return hmesh.Manifold()
# Replace the function temporarily
original_function = hmesh.volumetric_isocontour
hmesh.volumetric_isocontour = mock_volumetric_isocontour
try:
qim3d.mesh.from_volume(volume, isovalue=0.5)
finally:
hmesh.volumetric_isocontour = original_function # Restore original function
......@@ -13,7 +13,10 @@ from matplotlib.colors import Colormap
from qim3d.utils._logger import log
from qim3d.utils._misc import downscale_img, scale_to_float16
from pygel3d import hmesh
from pygel3d import jupyter_display as jd
import k3d
from typing import Optional
def volumetric(
img: np.ndarray,
......@@ -82,7 +85,6 @@ def volumetric(
```
"""
import k3d
pixel_count = img.shape[0] * img.shape[1] * img.shape[2]
# target is 60fps on m1 macbook pro, using test volume: https://data.qim.dk/pages/foam.html
......@@ -172,88 +174,106 @@ def volumetric(
else:
return plot
def mesh(
verts: np.ndarray,
faces: np.ndarray,
mesh,
backend: str = "pygel3d",
wireframe: bool = True,
flat_shading: bool = True,
grid_visible: bool = False,
show: bool = True,
save: bool = False,
**kwargs,
):
"""
Visualizes a 3D mesh using K3D.
)-> Optional[k3d.Plot]:
"""Visualize a 3D mesh using `pygel3d` or `k3d`.
Args:
verts (numpy.ndarray): A 2D array (Nx3) containing the vertices of the mesh.
faces (numpy.ndarray): A 2D array (Mx3) containing the indices of the mesh faces.
wireframe (bool, optional): If True, the mesh is rendered as a wireframe. Defaults to True.
flat_shading (bool, optional): If True, flat shading is applied to the mesh. Defaults to True.
grid_visible (bool, optional): If True, the grid is visible in the plot. Defaults to False.
show (bool, optional): If True, displays the visualization inline. Defaults to True.
mesh (pygel3d.hmesh.Manifold): The input mesh object.
backend (str, optional): The visualization backend to use.
Choose between `pygel3d` (default) and `k3d`.
wireframe (bool, optional): If True, displays the mesh as a wireframe.
Works both with `pygel3d` and `k3d`. Defaults to True.
flat_shading (bool, optional): If True, applies flat shading to the mesh.
Works only with `k3d`. Defaults to True.
grid_visible (bool, optional): If True, shows a grid in the visualization.
Works only with `k3d`. Defaults to False.
show (bool, optional): If True, displays the visualization inline.
Works only with `k3d`. Defaults to True.
save (bool or str, optional): If True, saves the visualization as an HTML file.
If a string is provided, it's interpreted as the file path where the HTML
file will be saved. Defaults to False.
**kwargs (Any): Additional keyword arguments to be passed to the `k3d.plot` function.
file will be saved. Works only with `k3d`. Defaults to False.
**kwargs (Any): Additional keyword arguments specific to the chosen backend:
- `k3d.plot` kwargs: Arguments that customize the [`k3d.plot`](https://k3d-jupyter.org/reference/factory.plot.html) visualization.
- `pygel3d.display` kwargs: Arguments that customize the [`pygel3d.display`](https://www2.compute.dtu.dk/projects/GEL/PyGEL/pygel3d/jupyter_display.html#display) visualization.
Returns:
plot (k3d.plot): If `show=False`, returns the K3D plot object.
k3d.Plot or None:
- If `backend="k3d"`, returns a `k3d.Plot` object.
- If `backend="pygel3d"`, the function displays the mesh but does not return a plot object.
Raises:
ValueError: If `backend` is not `pygel3d` or `k3d`.
Example:
```python
import qim3d
vol = qim3d.generate.noise_object(base_shape=(128,128,128),
final_shape=(128,128,128),
noise_scale=0.03,
order=1,
gamma=1,
max_value=255,
threshold=0.5,
dtype='uint8'
)
mesh = qim3d.mesh.from_volume(vol, step_size=3)
qim3d.viz.mesh(mesh.vertices, mesh.faces)
synthetic_blob = qim3d.generate.noise_object(noise_scale = 0.015)
mesh = qim3d.mesh.from_volume(synthetic_blob)
qim3d.viz.mesh(mesh, backend="pygel3d") # or qim3d.viz.mesh(mesh, backend="k3d")
```
<iframe src="https://platform.qim.dk/k3d/mesh_visualization.html" width="100%" height="500" frameborder="0"></iframe>
![pygel3d_visualization](../../assets/screenshots/pygel3d_visualization.png)
"""
import k3d
# Validate the inputs
if verts.shape[1] != 3:
raise ValueError('Vertices array must have shape (N, 3)')
if faces.shape[1] != 3:
raise ValueError('Faces array must have shape (M, 3)')
# Ensure the correct data types and memory layout
verts = np.ascontiguousarray(
verts.astype(np.float32)
) # Cast and ensure C-contiguous layout
faces = np.ascontiguousarray(
faces.astype(np.uint32)
) # Cast and ensure C-contiguous layout
# Create the mesh plot
plt_mesh = k3d.mesh(
vertices=verts,
indices=faces,
wireframe=wireframe,
flat_shading=flat_shading,
)
# Create plot
plot = k3d.plot(grid_visible=grid_visible, **kwargs)
plot += plt_mesh
if save:
# Save html to disk
with open(str(save), 'w', encoding='utf-8') as fp:
fp.write(plot.get_snapshot())
if backend not in ["k3d", "pygel3d"]:
raise ValueError("Invalid backend. Choose 'pygel3d' or 'k3d'.")
if show:
plot.display()
else:
return plot
# Extract vertex positions and face indices
face_indices = list(mesh.faces())
vertices_array = np.array(mesh.positions())
# Extract face vertex indices
face_vertices = [
list(mesh.circulate_face(int(fid), mode="v"))[:3] for fid in face_indices
]
face_vertices = np.array(face_vertices, dtype=np.uint32)
# Validate the mesh structure
if vertices_array.shape[1] != 3 or face_vertices.shape[1] != 3:
raise ValueError("Vertices must have shape (N, 3) and faces (M, 3)")
# Separate valid kwargs for each backend
valid_k3d_kwargs = {k: v for k, v in kwargs.items() if k not in ["smooth", "data"]}
valid_pygel_kwargs = {k: v for k, v in kwargs.items() if k in ["smooth", "data"]}
if backend == "k3d":
vertices_array = np.ascontiguousarray(vertices_array.astype(np.float32))
face_vertices = np.ascontiguousarray(face_vertices)
mesh_plot = k3d.mesh(
vertices=vertices_array,
indices=face_vertices,
wireframe=wireframe,
flat_shading=flat_shading,
)
# Create plot
plot = k3d.plot(grid_visible=grid_visible, **valid_k3d_kwargs)
plot += mesh_plot
if save:
# Save html to disk
with open(str(save), "w", encoding="utf-8") as fp:
fp.write(plot.get_snapshot())
if show:
plot.display()
else:
return plot
elif backend == "pygel3d":
jd.set_export_mode(True)
return jd.display(mesh, wireframe=wireframe, **valid_pygel_kwargs)
......@@ -31,3 +31,4 @@ ome_zarr>=0.9.0
dask-image>=2024.5.3
trimesh>=4.4.9
slgbuilder>=0.2.1
PyGEL3D>=0.5.2
\ No newline at end of file
......@@ -71,7 +71,8 @@ setup(
"ome_zarr>=0.9.0",
"dask-image>=2024.5.3",
"scikit-image>=0.24.0",
"trimesh>=4.4.9"
"trimesh>=4.4.9",
"PyGEL3D>=0.5.2"
],
extras_require={
"deep-learning": [
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment