Source code for ascribe_link.parametric
"""Parametric specimen generation functions.
These functions generate 3D data from parameters, suitable for dynamic specimens.
"""
from __future__ import annotations
import numpy as np
import pyvista as pv
from ascribe_link.models import MeshResult, VolumeResult
[docs]
def generate_sphere(radius: float = 1.0, resolution: int = 32) -> MeshResult:
"""Generate a parametric sphere mesh.
Parameters
----------
radius : float
Sphere radius (0.5 to 5.0)
resolution : int
Number of divisions (8 to 128)
Returns
-------
MeshResult
Sphere mesh with vertices, indices, and normals
"""
sphere = pv.Sphere(
radius=radius,
theta_resolution=resolution,
phi_resolution=resolution,
)
return MeshResult.from_pyvista(sphere)
[docs]
def generate_torus(
major_radius: float = 1.0,
minor_radius: float = 0.3,
segments: int = 32,
) -> MeshResult:
"""Generate a parametric torus mesh.
Parameters
----------
major_radius : float
Distance from center to tube center (0.5 to 3.0)
minor_radius : float
Tube radius (0.1 to 1.0)
segments : int
Number of divisions (8 to 128)
Returns
-------
MeshResult
Torus mesh with vertices, indices, and normals
"""
torus = pv.ParametricTorus(
ringradius=major_radius,
crosssectionradius=minor_radius,
)
# Resample to target resolution
torus = torus.subdivide(segments // 16, subfilter="butterfly")
return MeshResult.from_pyvista(torus)
[docs]
def generate_gaussian_volume(resolution: int = 64, sigma: float = 0.3) -> VolumeResult:
"""Generate a 3D Gaussian blob centered in a unit cube.
Parameters
----------
resolution : int
Number of voxels per axis (clamped to [32, 256]).
sigma : float
Standard deviation of the Gaussian relative to the cube edge
(clamped to [0.05, 1.0]).
Returns
-------
VolumeResult
float32 volume, shape [resolution]*3, normalized so peak == 1.0.
"""
resolution = max(32, min(256, int(resolution)))
sigma = max(0.05, min(1.0, float(sigma)))
axis = np.linspace(-0.5, 0.5, resolution, dtype=np.float32)
z, y, x = np.meshgrid(axis, axis, axis, indexing="ij")
r2 = x * x + y * y + z * z
volume = np.exp(-r2 / (2.0 * sigma * sigma)).astype(np.float32)
# Normalize so peak == 1.0 exactly (the discrete grid may not sample
# the true continuous peak at r=0, especially for even resolutions).
volume /= volume.max()
return VolumeResult.from_numpy(
volume,
spacing=[1.0 / resolution, 1.0 / resolution, 1.0 / resolution],
origin=[0.0, 0.0, 0.0],
)