Skip to content

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" (the default) means the array is indexed [x, y, z]; "ZYX" means [z, y, x]. No copy or transpose is made — this only changes how coordinates are emitted.

'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 taubin.

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 relaxation, and generally the better choice: Laplacian iteration is diffusion and drains volume, whereas Taubin is a low-pass filter that leaves the shape's low frequencies alone. It smooths considerably harder for the same volume loss, at two passes per iteration rather than one.

Smoothing runs inside :meth:mesh, so a mesh is always smoothed before :meth:get simplifies it — which is the order that measures best. See bench/taubin.py.

0
taubin_pass_band float

Graph frequency the filter leaves at unit gain, in (0, 1). Smaller smooths more. The default of 0.1 matches VTK's convention.

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 relaxation and taubin.

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 fairing_step. The cell domain and the shrinkage are independent problems: sharing a cell's vertex stops adjacent objects drifting apart but does nothing about volume loss, because Frisken's fairing is a plain Laplacian. Setting this fixes the second without giving up the first.

False
fairing_pass_band float

As taubin_pass_band and taubin_lambda, for that pair.

0.1
fairing_lambda float

As taubin_pass_band and taubin_lambda, for that pair.

0.1
taubin_lambda float

The positive step, in (0, 1). The negative step follows from this and taubin_pass_band; a pass band too wide for the chosen lambda is rejected, because the filter would amplify rather than smooth.

0.63
threads int

How many threads to use. 0 (the default) uses every core; 1 runs fully sequentially; any other value uses exactly that many.

Set threads=1 when you are already parallelising at a higher level — running one chunk per process in a pipeline, for instance — otherwise each process will try to claim every core and they will fight.

Values above 1 get a private thread pool, so the setting is honoured exactly, is not overridden by RAYON_NUM_THREADS, and does not disturb other users of rayon in the same process. Only threads=0 defers to RAYON_NUM_THREADS.

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 or 1 means no simplification.

0
max_error Optional[float]

Cap on how far simplification may move a vertex, in the same units as voxel_resolution. Defaults to the largest voxel dimension, matching zmesh.

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 label in mesher to check.

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 when the array is the whole volume.

None

Returns:

Type Description
The mesher, so calls can be chained.

A triangle mesh for a single object.

Attributes:

Name Type Description
vertices ndarray

(N, 3) float32 array of physical coordinates.

faces ndarray

(M, 3) uint32 array of indices into vertices.

normals Optional[ndarray]

(N, 3) float32 array of unit vertex normals, or None.

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 (0, 0, 0) starts where the mesh does.

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 [0, 2**bits - 1] across the cell that holds them -- rather than in the mesh's own coordinates. This is where cloudvolume.to_stored_model_space would put them, so a caller writing the multi-resolution format can skip that step.

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 2**bits - 1 in the lower cell and 0 in the upper one, exactly.

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]]]

(mesh, offset) pairs, where offset is the chunk's origin in the same units as the mesh's vertices. Meshes are translated by their offset before joining.

required
dedup_faces bool

Also drop faces that appear more than once. Needed only when the chunks were meshed without owned_shape, in which case both sides of a seam emit the wall between them. Harmless otherwise.

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.