API reference
Extracts one mesh per label from a 3-D array of integer labels.
The whole volume is traversed once, however many labels it contains, so cost is driven by boundary area rather than by object count.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
voxel_resolution
|
Sequence[float]
|
Physical size of a voxel along each array axis. The units are yours; nanometres are the usual choice for EM data. |
(1.0, 1.0, 1.0)
|
axis_order
|
str
|
Which physical axis each array axis runs along. |
'XYZ'
|
y_down
|
bool
|
Set for the image convention where Y increases downward. |
False
|
relaxation
|
int
|
Iterations of constrained Laplacian smoothing. Zero (the default) uses
purely local vertex placement. Raising it gives a smoother surface, more
accurate area and better normals, at some cost in speed. Mutually
exclusive with |
0
|
max_deviation
|
float
|
How far smoothing may move a vertex from where local placement put it, in voxels. This bounds how far the surface can stray from the data and stops smoothing from shrinking objects away. Applies to both filters. |
0.5
|
relaxation_step
|
float
|
Fraction of the way to the neighbour average per iteration, in (0, 1]. |
0.5
|
taubin
|
int
|
Iterations of Taubin smoothing, each a shrinking pass followed by a
larger expanding one. Mutually exclusive with Smoothing runs inside :meth: |
0
|
taubin_pass_band
|
float
|
Graph frequency the filter leaves at unit gain, in (0, 1). Smaller
smooths more. The default of |
0.1
|
fairing
|
int
|
Sweeps of Frisken's surface fairing, which smooths in the cell
domain: one position per cell, shared by every label present there.
Mutually exclusive with The reason to prefer it is correctness rather than smoothness. The other two fair each label's mesh independently, so the two copies of a wall between touching objects drift apart and the segmentation stops being a partition of space. Here they are the same number and cannot disagree, at any iteration count. |
0
|
fairing_step
|
float
|
Fraction of the way to the neighbour average per sweep, in (0, 1]. |
0.5
|
fairing_junction_rule
|
bool
|
Restrict cells where three or more labels meet to their junction neighbours, so those vertices slide along the junction curve rather than being pulled off it by the ordinary walls that also meet there. |
True
|
fairing_taubin
|
bool
|
Alternate Taubin's shrink and unshrink steps instead of repeating
|
False
|
fairing_pass_band
|
float
|
As |
0.1
|
fairing_lambda
|
float
|
As |
0.1
|
taubin_lambda
|
float
|
The positive step, in (0, 1). The negative step follows from this and
|
0.63
|
threads
|
int
|
How many threads to use. Set Values above 1 get a private thread pool, so the setting is honoured
exactly, is not overridden by Output is byte-identical whatever this is set to. |
0
|
Notes
Chunked meshing. To mesh a large volume in pieces, give each chunk a one-voxel halo on every side, so neighbouring input arrays overlap by two voxels. Vertices on a shared seam are then bit-identical between chunks and the pieces stitch together by vertex deduplication alone. A smaller overlap leaves the dual cells along the seam unshared, and the surfaces will not meet.
The halo does not grow with the smoothing setting, for either filter. Both hold the outermost layer of cells fixed, so neither ever reads past the halo and a chunk's mesh stays reproducible from that chunk's array alone. The trade-off is that a chunk's interior is smoothed slightly more than the band around its seams, so a stitched surface is self-consistent and watertight but not identical to the same volume meshed in one piece.
Examples:
>>> mesher = Mesher(voxel_resolution=[4, 4, 40])
>>> mesher.mesh(cutout)
>>> mesh = mesher.get(504)
>>> mesh.vertices.shape, mesh.faces.shape
effective_threads
property
Threads actually used: the configured count, or every core when 0.
clear()
Drop every stored surface.
erase(label)
Drop one object's surface, freeing its memory.
get(label, normals=False, reduction_factor=0, max_error=None)
The mesh for one object.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
normals
|
bool
|
Also compute unit vertex normals. |
False
|
reduction_factor
|
int
|
Ask for this many times fewer faces. |
0
|
max_error
|
Optional[float]
|
Cap on how far simplification may move a vertex, in the same units
as |
None
|
Notes
Simplification preserves topology: it applies the link condition, so a closed 2-manifold stays a closed 2-manifold, and rejects collapses that would duplicate a face or flip a normal.
It also leaves the chunk seam alone. Vertices pinned during extraction are never collapsed, which keeps not just their positions but the edges between them intact — so chunks still stitch after simplification. A consequence is that the band along a chunk seam stays at full resolution.
Raises:
| Type | Description |
|---|---|
KeyError
|
If the label is not present. Use |
get_all(normals=False, **kwargs)
Every object's mesh, in ascending label order.
Yields one at a time so the caller can process and discard, rather than holding every mesh in memory at once.
ids()
Labels present in the volume, ascending. Excludes background.
mesh(data, close=False, owned_shape=None)
Extract every object's surface from data.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
data
|
ndarray
|
3-D array of unsigned integer labels (uint8/16/32/64). C- and Fortran-ordered arrays give identical results. Label 0 is background and is never meshed. |
required |
close
|
bool
|
Treat the volume as surrounded by background, so objects touching the array edge come back sealed. The border is virtual, so this costs no extra memory. |
False
|
owned_shape
|
Optional[Sequence[int]]
|
When meshing one chunk of a larger volume, how many voxels along each axis this chunk owns, counted from index 0. Faces are emitted only for the owned region, so each one belongs to exactly one chunk: concatenating the chunks and deduplicating vertices then reproduces the whole-volume mesh with no duplicate faces. Wherever a neighbouring chunk exists, the array must extend two voxels past the owned region along that axis. Dual contouring reads two cell layers per face, so one voxel of halo — enough for marching cubes — leaves a hole along the seam. Only the positive side needs it, which keeps the fetch within the next chunk. At the far edge of the volume there is no neighbour and no halo is needed, so the last chunk simply owns everything it holds. Leave it |
None
|
Returns:
| Type | Description |
|---|---|
The mesher, so calls can be chained.
|
|
A triangle mesh for a single object.
Attributes:
| Name | Type | Description |
|---|---|---|
vertices |
ndarray
|
|
faces |
ndarray
|
|
normals |
Optional[ndarray]
|
|
id |
Optional[int]
|
The label this mesh was extracted from. |
nbytes
property
Total size of the underlying arrays.
area()
Total surface area.
count_boundary_edges()
Number of undirected edges not shared by exactly two triangles.
Nonzero means the surface is open — expected where an object runs off
the edge of the volume and close was not used.
from_precomputed(data, id=None)
classmethod
Parse a Neuroglancer precomputed mesh fragment.
is_closed()
Whether every edge is shared by exactly two triangles.
save(path)
Write to path, choosing the format from its extension.
to_obj()
Wavefront OBJ. Indices are 1-based, per the format.
to_ply()
Binary little-endian PLY.
to_precomputed()
Neuroglancer precomputed mesh fragment.
triangles()
(M, 3, 3) array of triangle corner coordinates.
volume()
Signed enclosed volume, via the divergence theorem.
Only meaningful for a closed mesh. A negative result means the winding is inverted.
Cutting a finished mesh into a regular grid of cells.
dice(mesh, chunk_shape, grid_origin=None, grid_size=None, quantization_bits=None)
Cut mesh into the cells of a regular grid.
Returns {(i, j, k): Mesh} for the cells that hold anything. Every
triangle lands in exactly one cell and lies wholly inside it, and two cells
that meet agree on the geometry along the plane between them bit for
bit -- so concatenating the pieces and deduplicating vertices by exact
equality reproduces the input mesh, with no tolerance and no repair pass.
This is what the neuroglancer multi-resolution mesh format needs: it quantizes each fragment over the cell it is filed under, so anything outside is clamped flat onto the cell face, and it has thrown the original coordinates away by the time a weld could reconcile two sides that disagree.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
chunk_shape
|
Sequence[float]
|
Cell size along each axis, in the mesh's own units. |
required |
grid_origin
|
Optional[Sequence[float]]
|
The grid's lower corner. Defaults to the mesh's own minimum, so cell
|
None
|
grid_size
|
Optional[Sequence[int]]
|
How many cells the grid holds along each axis. Indices are clamped into it, so a vertex lying exactly on the far face of the last cell stays in that cell rather than starting a new one. Defaults to whatever covers the mesh. |
None
|
quantization_bits
|
Optional[int]
|
Return vertices on the octree format's integer lattice -- integers in
It also makes the fragments agree exactly rather than very nearly.
Two cells sharing a vertex otherwise agree only as closely as float32
can represent it, and at coordinates of a thousand voxels one float32
step is about a nanometre; rounding onto the lattice before vertices
are shared within a cell collapses that difference instead of keeping
it. The rounding uses one grid spanning the whole volume with the
cell's offset subtracted afterwards, so a shared vertex comes out as
|
None
|
Notes
Classification against a cutting plane is exact -- a vertex is below, above, or on it, with no epsilon. That is what makes a T-junction impossible: a plane crosses the interior of a shared edge only when one end is strictly below and the other strictly above, and both triangles holding that edge see the same two coordinates, so either both split it or neither does. Treating a vertex within some tolerance of a plane as lying on it is what tears a surface -- the triangle whose corner was snapped stops being split while its neighbour across the far edge still is, leaving a vertex in the middle of an edge the first triangle spans.
The cost is a sliver triangle where a vertex sits a hair off a plane. That is harmless: quantization collapses it and the decoder drops it, which leaves its edges held by exactly the two triangles either side.
Examples:
>>> pieces = dice(mesh, chunk_shape=[64, 64, 64])
>>> sorted(pieces)[:2]
[(0, 0, 0), (0, 0, 1)]
Joining meshes from adjacent chunks.
stitch(pieces, dedup_faces=True, id=None)
Join per-chunk meshes of one object into a single mesh.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
pieces
|
Iterable[Tuple[Mesh, Sequence[float]]]
|
|
required |
dedup_faces
|
bool
|
Also drop faces that appear more than once. Needed only when the
chunks were meshed without |
True
|
id
|
Optional[int]
|
Label to record on the result. |
None
|
Returns:
| Type | Description |
|---|---|
A single mesh whose seam vertices have been welded.
|
|
Notes
Welding is by exact coordinate equality, with no tolerance. That is sound because serra derives vertex positions from integers in units of 1/256 of a voxel, and a cell straddling a seam is present in both chunks and computed identically by both. It is also why the chunks must have been meshed with two voxels of halo: with one, the cells along the seam are split between the chunks and the faces there are produced by neither, which no amount of welding can repair.