Skip to main content

serra

Analytical multi-material meshes from voxelized segmentations. It is named after the artist Richard Serra, who is known for making beautiful smooth geometric forms out of rusted metal.

serra turns a 3-D array of integer labels into one triangle mesh per label. It is built for connectomics-scale data, where a single chunk may contain hundreds of thousands of distinct objects, so it makes one pass over the volume regardless of how many labels are present.

Documentation at [https://alleninstitute.github.io/serra/]

pip install serra-mesh

Building from source needs a Rust toolchain: see the developer guide.

import serra_mesh

mesher = serra_mesh.Mesher(voxel_resolution=[4, 4, 40])
mesher.mesh(cutout)          # a 3-D array of integer labels
mesh = mesher.get(504)       # mesh.vertices, mesh.faces

What makes it different

serra uses multi-label surface nets (dual contouring) rather than marching cubes, following Frisken (2022) — see docs/references.md. Vertices sit inside cells at a position determined by where the label boundary crosses the cell, instead of being pinned to voxel-edge midpoints. This removes the staircase artifact, which shows up most clearly in surface area.

Measured on analytically-known spheres (isotropic voxels), against zmesh:

sphere radius zmesh area error serra area error
10 voxels +8.52% +2.89%
20 voxels +9.31% +2.97%
40 voxels +8.77% +2.80%

Marching-cubes area error does not shrink as resolution rises — it is a systematic bias, not a sampling error. With the optional relaxation pass enabled (relaxation=3) serra's area error drops to +0.38%, while volume stays within 0.2% of analytic in every case.

It also includes simplification and smoothing routines which maintain watertight and produce no non-manifold vertices or edges, while maintaining reproducible vertices at chunk boundaries to facilitate large scale mesh generation via chunking. Dual contouring reads two cell layers per face, so one voxel of halo is not enough — see docs/chunked.md.)

Smoothing is optional, bounded by max_deviation, and safe to run per chunk because seam vertices are pinned:

serra_mesh.Mesher(fairing=20, fairing_taubin=True)   # recommended
serra_mesh.Mesher(taubin=20)                         # same filter, per label

Prefer fairing. It is Frisken's surface fairing: one position per cell, shared by every label present there, rather than a private copy per label. The accuracy is identical — 0.068 voxels of mean surface error against an analytic tube either way — but smoothing each label separately pulls the two copies of a wall between touching objects apart, by up to 2.2 voxels on real neuropil, and the segmentation stops being a partition of space. Sharing the cell makes that impossible: the copies are one number, not two that started equal.

There is also relaxation=k, a plain Laplacian. It is not recommended: it shrinks, losing 2.5% of an object's volume at k=3, 7% at k=10, and 41% of a two-voxel-radius tube. It is kept because it is cheap and harmless on large objects. See docs/accuracy.md.

How it looks

Three objects from the 512³ MICrONS test volume, meshed by zmesh and by serra and rendered from an identical camera with flat shading. Flat shading is deliberate — it makes individual triangles visible, which is exactly what distinguishes a staircased surface from a smooth one.

The objects are sampled around the 80th size percentile rather than taken from the top: the largest object in a cutout is a cell body or a trunk crossing the whole box, and says little about the surfaces most objects get. The top row of each figure is the whole object, the bottom row a close-up spanning 1400 nm of the same surface.

object 28927963 38K voxels, 47K faces
object 79445759 42K voxels, 54K faces
object 60033456 47K voxels, 57K faces

Marching cubes produces axis-aligned terraces because its vertices are pinned to voxel-edge midpoints. serra's are placed inside each cell from where the label boundary actually crosses it, so the terracing is gone even before relaxation; relaxation=3 removes the remaining faceting. Face counts are within 1% across all three, so this is not a resolution difference.

The same objects decimated 10× — the regime PyChunkedGraph actually stores, and the one where the input surface matters most, since a quadric simplifier keeps whatever the extractor gave it:

object 28927963 simplified 47K → 4.7K faces
object 79445759 simplified 54K → 5.4K faces
object 60033456 simplified 57K → 5.7K faces

Against the mesh MICrONS publishes

A 5 µm cutout around segment 864691136144674612 at 32×32×40 nm, with both meshers decimated to the face count of the LOD-0 mesh the dataset actually serves for that segment — so all four panels are drawn on the same budget:

segment 864691136144674612

Regenerate with:

python bench/render_comparison.py --zmesh ../zmesh --out docs/images
python bench/render_comparison.py --zmesh ../zmesh --out docs/images --simplify 10
python bench/render_segment.py --zmesh ../zmesh --out docs/images

Performance

On the 512³ connectomics volume (2524 objects), Apple M4 Pro (14 cores):

serra (1 thread) serra (14 threads) zmesh
mesh() — traverse the volume 1.52 s 0.29 s 1.22 s
get() — extract all 2524 objects 0.81 s 0.81 s 23.2 s
peak RSS 2.0 GB 2.8 GB 3.2 GB
output 44.8M vertices / 89.2M faces same 45.0M / 89.5M

zmesh has no threading, so the fair single-threaded comparison is the first column: it is about 20% faster at traversing the volume there. Traversal scales to 5.2× on 14 cores, at the cost of about 0.8 GB for the merge. serra is roughly 29× faster at extraction, which needs explaining because it is an architectural difference rather than a tuning one.

Marching cubes emits a triangle soup: every triangle carries its own three vertices with no sharing. Turning that into an indexed mesh means deduplicating them, and zmesh does it per object with a hash map keyed on packed coordinates. Measured on this volume, that is 268.6M soup vertices collapsing to 45.0M unique — a 6× redundancy — at 86 ns each, which is simply what a hash-map insertion costs. The soup is also what drives the memory: 268.6M packed vertices is 2.1 GB held live while the map is being built.

serra never creates the duplicates. The extractor assigns one vertex per cell per connected component up front and quads reference those indices directly, so get() is a coordinate conversion and a triangulation — 19 ns per vertex, or memcpy territory.

Reproduce with:

python bench/compare_zmesh.py serra
python bench/compare_zmesh.py zmesh

Controlling parallelism

serra_mesh.Mesher(threads=0)   # default: every core
serra_mesh.Mesher(threads=1)   # fully sequential
serra_mesh.Mesher(threads=4)   # exactly four

Set threads=1 if you are already parallelising at a higher level — one chunk per process in a pipeline, say — otherwise every process tries to claim every core and they fight each other.

Any value above 1 gets 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. mesher.effective_threads reports what will actually be used.

Scaling on the volume above, and peak memory measured in a fresh process each time:

threads mesh() speedup peak RSS
1 1.52 s 1.0× 2.0 GB
2 0.95 s 1.6×
4 0.54 s 2.8×
8 0.33 s 4.6×
14 0.29 s 5.2× 2.8 GB

Output is byte-identical at every thread count, which the test suite checks directly rather than assuming. The volume is split into bands along one axis; a band cannot emit its own first cell layer's quads, since those read the layer below, so a short serial pass produces them afterwards and splices them into each label's face list in the position a single traversal would have put them.

Chunked meshing

Each chunk owns a disjoint range of voxels and is passed to mesh() with a 1-voxel halo on every side — so neighbouring input arrays overlap by 2 voxels. This is what makes the dual cells along a seam shared between both chunks, and therefore what makes their vertices identical.

One voxel of halo is enough at any relaxation setting. Iterative smoothing normally propagates one cell per iteration, which would mean k iterations need k + 1 voxels of halo. serra instead holds the outermost layer of cells fixed — precisely the vertices whose one-ring the chunk does not fully contain — so relaxation never reads past the halo. A chunk's mesh is therefore reproducible from that chunk's own array alone, whatever k is.

The trade-off is deliberate: a chunk's interior smooths 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.

Naming

The distribution is serra-mesh and the module is serra_mesh, because the name serra on PyPI was taken.

Status

Under active development. See docs/ for the user guide and the developer guide to the module layout.

Citing

The method is Frisken, S. F. (2022), SurfaceNets for Multi-Label Segmentations with Preservation of Sharp Boundaries, Journal of Computer Graphics Techniques 11(1), 34-54. For this implementation see CITATION.cff; the full reference list is in docs/references.md.

License

MIT

Metadata

Release files for serra-mesh 0.0.1

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for serra-mesh 0.0.1
File Size Uploaded
serra_mesh-0.0.1.tar.gz 67.9 MB Details

Built distributions (wheels)

Table of built distributions (wheels) for serra-mesh 0.0.1
File
serra_mesh-0.0.1-cp39-abi3-win_amd64.whl CPython 3.9 abi3 Windows x86-64 Details
serra_mesh-0.0.1-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl CPython 3.9 abi3 Linux glibc 2.17+ x86-64 Details
serra_mesh-0.0.1-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl CPython 3.9 abi3 Linux glibc 2.17+ ARM64 Details
serra_mesh-0.0.1-cp39-abi3-macosx_11_0_arm64.whl CPython 3.9 abi3 macOS 11.0+ ARM64 Details

Total release size: 69.6 MB

Release files / serra_mesh-0.0.1.tar.gz

Download URL serra_mesh-0.0.1.tar.gz
Size 67.9 MB
Tags Source
SHA-256 checksum
How to use checksums
5b6024857a9ec2bd3855ca6edaf043974af89826a0777deb3f591987856cbdf2
BLAKE2b-256 checksum
How to use checksums
045fd0443c8897192e6da149ba7b5f075eefe6fee4a7a4b9b0c19d3ec7704d8a
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Sep 2, 2026.

Transparency log

Release files / serra_mesh-0.0.1-cp39-abi3-win_amd64.whl

Download URL serra_mesh-0.0.1-cp39-abi3-win_amd64.whl
Size 359.4 kB
Tags CPython 3.9 Windows x86-64 abi3
SHA-256 checksum
How to use checksums
d5cc6dcbabb64fd8053aa74492b0ff8fa5db0cf0e72b50b22fc7517a11c75dc4
BLAKE2b-256 checksum
How to use checksums
1b47507886d1a4fb2dfdf2713dc5bcf6d47827a1dd6832d68cf2fd5c10ef074e
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Sep 2, 2026.

Transparency log

Release files / serra_mesh-0.0.1-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl

Download URL serra_mesh-0.0.1-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
Size 490.7 kB
Tags CPython 3.9 Linux glibc 2.17+ x86-64 abi3
SHA-256 checksum
How to use checksums
9e40ac3997dd370c33ccfbf484cfea85d2dd27a50ba54c7fccfe8d817b7a5a46
BLAKE2b-256 checksum
How to use checksums
089c88459fcd66508740542cb3abff362866a73a245815642e5d3e3ff897aed2
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Sep 2, 2026.

Transparency log

Release files / serra_mesh-0.0.1-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl

Download URL serra_mesh-0.0.1-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl
Size 473.6 kB
Tags CPython 3.9 Linux glibc 2.17+ ARM64 abi3
SHA-256 checksum
How to use checksums
88bda9137c7d83d48378306ad032d236a29b58909d1a01ea4f852fbf75ff611c
BLAKE2b-256 checksum
How to use checksums
7894355be79da9803803121c51d92dab8e892c6336a9ef9acfe9a6a88307dd8d
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Sep 2, 2026.

Transparency log

Release files / serra_mesh-0.0.1-cp39-abi3-macosx_11_0_arm64.whl

Download URL serra_mesh-0.0.1-cp39-abi3-macosx_11_0_arm64.whl
Size 432.9 kB
Tags CPython 3.9 abi3 macOS 11.0+ ARM64
SHA-256 checksum
How to use checksums
cfc7ddc1ad99c1dee6133232944591e499803eebeae8bffd7a8b0e085e28ea66
BLAKE2b-256 checksum
How to use checksums
d0722c4471497fc4111d8b7ff4a40d6384b6fb4388b8b3b6e89b1dd40166547d
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Sep 2, 2026.

Transparency log

Release history Release notifications | RSS feed

0.2.0

5 release files

This release

0.0.1 This release

5 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page