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), against zmesh 1.15.0 from PyPI. Each figure is the median of three runs, one process per implementation so peak RSS is not contaminated:

serra (1 thread) serra (14 threads) zmesh
mesh() — traverse the volume 1.86 s 0.47 s 0.83 s
get() — extract all 2524 objects 1.05 s 1.02 s 5.43 s
peak RSS 3.0 GB 4.0 GB 3.4 GB
output 44.8M vertices / 89.2M faces same 45.0M / 89.5M

zmesh has no threading, so the honest single-threaded comparison is the first column, and zmesh traverses the volume about 2.2× faster there. serra needs more than one core to match it, reaching 0.47 s on 14. What serra buys for that is a vertex placement that marching cubes cannot express — see accuracy — and about 5× on extraction.

The extraction gap is architectural rather than a matter of tuning. 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. On this volume that is 268.6M soup vertices collapsing to 45.0M unique — a 6× redundancy. The soup also 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.

Both therefore finish with about 45M unique vertices, which is the output row above. They differ in how many they touch on the way: zmesh generates 268.6M and collapses them, serra emits 44.8M and is done. Per vertex processed the two are within 15% of each other — roughly 20 ns for zmesh against 23 ns for serra — so the ~5× in the table is the 6× of intermediate vertices that only one of them ever creates, not a slower inner loop.

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.86 s 1.0× 3.0 GB
2 1.45 s 1.3× 3.2 GB
4 0.83 s 2.2× 3.2 GB
8 0.61 s 3.0× 3.6 GB
14 0.47 s 4.0× 4.0 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.2.0

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.2.0
File Size Uploaded
serra_mesh-0.2.0.tar.gz 67.9 MB Details

Built distributions (wheels)

Table of built distributions (wheels) for serra-mesh 0.2.0
File
serra_mesh-0.2.0-cp39-abi3-win_amd64.whl CPython 3.9 abi3 Windows x86-64 Details
serra_mesh-0.2.0-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.2.0-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl CPython 3.9 abi3 Linux glibc 2.17+ ARM64 Details
serra_mesh-0.2.0-cp39-abi3-macosx_11_0_arm64.whl CPython 3.9 abi3 macOS 11.0+ ARM64 Details

Total release size: 69.7 MB

Release files / serra_mesh-0.2.0.tar.gz

Download URL serra_mesh-0.2.0.tar.gz
Size 67.9 MB
Tags Source
SHA-256 checksum
How to use checksums
890bc1912d6e4af1de680f2eab681d85aeae39d99106a2c6ef2cd900d5c8db1a
BLAKE2b-256 checksum
How to use checksums
65c654231f9d1668e67595752457071ddb329763fc15a2b675c1d2e1ae961b70
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 3, 2026.

Transparency log

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

Download URL serra_mesh-0.2.0-cp39-abi3-win_amd64.whl
Size 385.3 kB
Tags CPython 3.9 Windows x86-64 abi3
SHA-256 checksum
How to use checksums
9dd8e4f2b5b8a58186518b876063f93f3a7cc78a07fb7aa2999329a813b28d4d
BLAKE2b-256 checksum
How to use checksums
ffe466258316586cc15f2e04ab15d5f4288d77d73d45d7ccdf4016963e6d7d8a
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 3, 2026.

Transparency log

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

Download URL serra_mesh-0.2.0-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
Size 517.2 kB
Tags CPython 3.9 Linux glibc 2.17+ x86-64 abi3
SHA-256 checksum
How to use checksums
9e5c007b93b109a0fda5bd8f587694e1cfc3515db841ae0c04a86bd6ba985bc5
BLAKE2b-256 checksum
How to use checksums
6cf1d0ae82878056a5855274ff6ae98cf432496f2b919cdb4507c3d4052326df
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 3, 2026.

Transparency log

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

Download URL serra_mesh-0.2.0-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl
Size 498.1 kB
Tags CPython 3.9 Linux glibc 2.17+ ARM64 abi3
SHA-256 checksum
How to use checksums
5b4590cb12f9e66ef777ed79dfda0f9bf24135582b981e47fbffdc69b74b21a7
BLAKE2b-256 checksum
How to use checksums
96ce7fca0ac71820eeac9871d52157d09688f8ec760e1e49fa69cbdbabb6488a
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 3, 2026.

Transparency log

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

Download URL serra_mesh-0.2.0-cp39-abi3-macosx_11_0_arm64.whl
Size 456.2 kB
Tags CPython 3.9 abi3 macOS 11.0+ ARM64
SHA-256 checksum
How to use checksums
732db613e211cbef4a1ead4176a7a0cd2b81633db01fc1c0b7f39c076f3bfb0b
BLAKE2b-256 checksum
How to use checksums
2ed727c1664d352ae3a395fc4f7f9f6e19d22ddbfc1b433e405b42cc347ea99c
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 3, 2026.

Transparency log

Release history Release notifications | RSS feed

This release

0.2.0 This release

5 release files

0.0.1

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