fix(render): OME-Zarr scale metadata must describe the render level, not level 0 - #1229
fix(render): OME-Zarr scale metadata must describe the render level, not level 0#1229Bullo27 wants to merge 1 commit into
Conversation
vc_render_tifxyz renders from pyramid level --group-idx, so one output voxel spans 2^g native voxels. The TIFF path accounts for this (voxelSizeAtRenderLevelUm = voxelSizeUm / ds_scale) but both writeZarrAttrs call sites passed the raw native base_voxel_size, and writeZarrAttrs receives groupIdx only to record it as "source_group" -- never for the scale. The two outputs of a single run therefore contradicted each other. Same segment, same volume, --group-idx 2, output 470x470: --tif-output XResolution 139.46 DPI -> 182.128 um/px (correct) --zarr-output .zattrs scale -> 45.532 um/px (4x off) Every group-idx reported 45.532 regardless of render size, so anything reading the multiscale metadata -- napari, neuroglancer, downstream measurement -- got physical dimensions wrong by 2^g. This is independent of remote volumes; a local volume at --group-idx > 0 is affected identically. Compute the rendered voxel size once and pass it to both call sites. The buildOffsetList comment already states the intent: the stack is isotropic in level-g voxels, so .zattrs has to describe level-g spacing. Verified against the live open-data bucket (PHercParis4, 45.532 um native), zarr and TIFF now agreeing at every level: g0 1880px 45.532 um g1 940px 91.064 um g2 470px 182.128 um g3 235px 364.256 um Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WKT3CfY4msFhzjd1cXzbrp
|
@Bullo27 is attempting to deploy a commit to the scroll Team on Vercel. A member of the Team first needs to authorize it. |
|
One implication I should state explicitly, since it isn't only forward-looking: any OME-Zarr already rendered at Scoping it as far as I can from outside:
What makes this worth flagging rather than just fixing is the size of the error. A factor of 2 or 4 is plausible enough to pass a sanity check — unlike, say, the 45,532× discrepancy in #1228, which would be obvious the moment anyone looked at it. A 2× error in a sheet measurement or an ink-density-per-cm² number would very likely go unnoticed. I have no way to tell from outside how many people render downsampled into zarr, so I'm not in a position to judge whether that warrants any wider notice — you'd know. If it does, the affected outputs are identifiable without re-rendering: Claude Code (Opus 5). |
|
Concrete numbers for the "full suite passing" line above, since CI doesn't run on fork PRs here: Ubuntu 24.04, gcc, Ninja/Release, Claude Code (Opus 5). |
|
This appears to be a metadata only issue, but worth fixing |
|
Agreed — metadata only in the strict sense that matters most: no pixel is affected, so nothing already rendered needs re-rendering. And since The one thing I'd still like a call on is the Claude Code (Opus 5). |
vc_render_tifxyzrenders from pyramid level--group-idx, so one output voxel spans 2^g native voxels. The TIFF path accounts for that; the OME-Zarr path does not, and the two outputs of a single run contradict each other.The contradiction
Same segment, same volume, same invocation,
--group-idx 2, output 470×470:--tif-output--zarr-output.zattrsscale → 45.532 µm/pxEvery
--group-idxreported 45.532 regardless of render size (1880² / 940² / 470²), so anything reading the multiscale metadata — napari, neuroglancer, any downstream measurement — gets physical dimensions wrong by 2^g.This is not specific to remote volumes. A local volume rendered at
--group-idx > 0is affected identically; it's purely about the pyramid level.Why
The TIFF branch computes the rendered voxel size:
but both
writeZarrAttrs(...)call sites pass the raw nativebase_voxel_size.writeZarrAttrsdoes receivegroupIdx, and uses it only to recordattrs["source_group"]— never for the scale, which is computed assYX = baseVoxelSize * 2^l,sZ = baseVoxelSize.The intent is already documented in
buildOffsetList:So the stack is deliberately isotropic in level-g voxels, and
.zattrshas to describe level-g spacing. It was describing level-0.The change
Compute the rendered voxel size once, next to the TIFF calculation, and pass it to both call sites.
Verification
Against the live open-data bucket (PHercParis4, 45.532 µm native), rendering the same segment at each level and reading back both outputs:
--group-idx.zattrsµmBefore the change the
.zattrscolumn read 45.532 at every level. Built on Ubuntu 24.04 (gcc, Ninja/Release); full suite passing.One related thing I left alone
sZstill ignoresslice_step. With the default (1.0) that's correct, but--slice-step 2puts layers two level-g voxels apart while.zattrsstill claims one.writeZarrAttrsalready receivessliceStepand records it inattrs["slice_step"], so the information is there. I kept it out of this diff to keep the change focused — happy to fold it in if you'd like it fixed here.Found by differential-testing the renderer against the raw zarr (which, separately, came out exact — corr 0.9999 on pixel values at levels 0 and 1). Investigation and patch with Claude Code (Opus 5); every number above is from an actual run.