Axis and Channel Order¶
Spatial axis order (xyz vs zyx) is the single piece of metadata
most likely to silently corrupt a connectomics pipeline. Different
consumers in this stack disagree by convention, and affinity channel
order is coupled to spatial order through the affinity offsets — not
an independent knob.
This page is the convention reference. The normative rules live in code docstrings (linked below); this page exists so a new contributor can build the right mental model in one read.
Two Orders, Two Camps¶
The codebase touches volumes in two orders:
Order |
Used by |
|---|---|
|
BANIS zarr on disk; |
|
PyTorch / MONAI training tensors ( |
Neither is “right.” They are conventions of the respective ecosystems. The pipeline must declare which order a given array is in and convert exactly once at each consumer boundary.
The Coupling: Channel Order Follows Spatial Order¶
connectomics.data.processing.affinity.seg_to_affinity is the
authoritative affinity producer in the framework, and it is
positional: offset (a, b, c) shifts along the input array’s
axes 0, 1, 2 — regardless of how the caller labels those
axes. The returned tensor has shape (C, A0, A1, A2) and channel
i is the affinity along spatial axis i.
Concretely:
If the input
segis storedxyz(axis 0 = X, axis 1 = Y, axis 2 = Z), then the offsets["1-0-0", "0-1-0", "0-0-1"]produce channels[X-aff, Y-aff, Z-aff]inxyzorder.If the input
segis storedzyx(axis 0 = Z, axis 1 = Y, axis 2 = X), the same offset strings produce channels[Z-aff, Y-aff, X-aff]inzyxorder.
The implication is the rule that should never be violated:
Channel order is not an independent setting. Given the spatial axis order of the array and the affinity offset list, the channel semantics are determined.
When a tool exposes a separate channel_order knob (waterz,
h5_to_precomputed_cloud.py), that knob exists to express the
destination’s expectation; the source order is implicit and must
match the spatial order the array was actually written with.
Consumers and Their Required Orders¶
Consumer |
Spatial |
Channels (for affinity) |
Notes |
|---|---|---|---|
PyTorch / Lightning training |
|
|
Tensors are |
|
|
|
|
|
|
|
No internal reorder. |
|
|
|
|
CloudVolume precomputed / Neuroglancer |
|
|
Output info layout is |
The split is therefore: training and waterz are zyx;
everything else is xyz.
Where Order Is Currently Declared¶
There is no single declared source of truth in the codebase
today — the V3 refactor surfaced this and the unification work is
tracked in .agents/features/xyz-order/plan.md. The current
moving parts are:
connectomics.inference.artifact.PredictionArtifactMetadata(layout="CZYX"and optionalchannel_order) — stamped on the raw-prediction artifact.connectomics.decoding.decoders.waterz.decode_waterz(channel_order: str = "xyz",edge_offset: int = 1) — decoder-local transpose plus the BANIS source→destinationnp.roll(+1)foredge_offset=0.output_transposeinconnectomics.decoding.stage— manual axis permutation applied to decode output.The
axis_orderHDF5 attribute (currently only"XYZ"is accepted), read inconnectomics.decoding.stageandconnectomics.decoding.qc.affinity.--src-axesand--reverse-channelsflags onscripts/h5_to_precomputed_cloud.py.
These overlap and can disagree. Until the unification plan lands, the safest workflow is to trace the array’s order from disk all the way to the consumer by hand and convert at exactly one boundary.
Worked Example: BANIS HDF5 → CloudVolume Precomputed¶
This is the case that motivated this page. The same reasoning applies to any external converter.
Source. BANIS stores zarr volumes xyz on disk and reads them
without a spatial transpose. seg_to_affinity is then called on an
xyz array, so the produced affinity has shape
(C, X, Y, Z) and channel order ch0=X-aff, ch1=Y-aff,
ch2=Z-aff.
Sink. CloudVolume precomputed expects xyz spatial order
(X, Y, Z, C) and xyz channels.
Conversion. With
scripts/h5_to_precomputed_cloud.py:
python scripts/h5_to_precomputed_cloud.py \
input.h5 gs://bucket/path/ \
--src-axes cxyz \
# NO --reverse-channels: source channels are already xyz
The --src-axes cxyz transposes (C, X, Y, Z) → (X, Y, Z, C),
preserving the X, Y, Z spatial order. --reverse-channels
would flip the already-correct xyz channels into zyx,
producing an apparently valid but semantically wrong volume that
only fails at decode time.
The same script with a zyx-on-disk source (e.g. a tensor
exported directly from a training step) requires
--src-axes czyx and --reverse-channels to land on the
same xyz-channel precomputed output.
The Cost of Getting It Wrong¶
The May 2026 BANIS upload (slurm jobs 2505811 / 2505812) ran for
~2 hours each, then required a complete re-upload (jobs 2517273 /
2517274, another ~2 hours each) because a misleading
"z-y-x format" string in an unrelated parser caused
--reverse-channels to be set when it should not have been. The
data was structurally valid at every checkpoint; the bug only
surfaced when a downstream block-read was compared against the
source.
This is the failure mode that motivates the unification work: a quietly inconsistent axis convention produces ~150 GB of plausible garbage, not a crash.
Rules of Thumb¶
When reading or writing a volume:
State the order explicitly in a comment or filename when the array is not flowing through a typed container that already carries it.
Do not introduce a separate channel-order knob for affinity. If you need to express channel order, derive it from
(spatial_axis_order, offsets).Convert at one boundary, with one helper. If the same array gets transposed in two places, one of them is wrong.
Verify against the source after any large external upload (read back a block, compare to the input under the declared transpose, without reversing channels). This is what catches the silent-corruption class of bugs.
See Also¶
connectomics.data.processing.affinity.seg_to_affinity— the authoritative docstring for the positional offset convention.connectomics.decoding.decoders.waterz.decode_waterz— thechannel_order/edge_offsetknobs and their semantics.scripts/h5_to_precomputed_cloud.py— the standalone converter with--src-axesand--reverse-channelsflags..agents/features/xyz-order/plan.md— proposal to consolidate axis-order handling onto declared metadata and a singlereorder_affinityhelper.