fix(downsample): shift output origin to coarse-grid pixel centers - #1556
Merged
thewtex merged 5 commits intoJul 21, 2026
Merged
Conversation
The downsample and downsample-label-image pipelines kept the input origin (plus an axis-aligned crop-radius offset), so the coarse-grid pixel centers did not coincide with the centers of the fine-pixel neighborhoods they aggregate. Under ITK's pixel-center convention, downsampling by factor k must shift the origin by (k - 1) / 2 * inputSpacing along each axis, applied through the direction matrix. Compute the output origin as the physical point of the continuous input index inputStartIndex + cropRadius + (k - 1) / 2 via TransformContinuousIndexToPhysicalPoint, composing the half-pixel shift with the existing crop-radius offset. This also makes the crop-radius offset direction-aware; previously it was added axis-by-axis directly to the origin, ignoring the direction matrix. The convention matches itk::BinShrinkImageFilter, which places the output origin at the physical point of continuous input index (shrinkFactor - 1) / 2, so downsample-bin-shrink needed no change, and downsample with cropRadius = 0 now produces an origin identical to downsample-bin-shrink for the same factors. Fixes InsightSoftwareConsortium#1409 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ines The origin fix changes both the metadata and the pixel values of the downsample and downsample-label-image outputs, so the affected content-addressed baselines were regenerated with the fixed pipelines: - cthead1-downsample.nrrd (origin [0, 0] -> [0.5, 0.5]) - apple-downsample.mha (origin shifted by 0.5 * the fractional input spacing per axis, to [0.17638888888888887, 0.17638888888888887]) - 2th_cthead1-downsample-label-image.nrrd (origin [0, 0] -> [0.5, 0.5]) The downsample-bin-shrink and resample* baselines are byte-identical to before; those pipelines are unchanged. The re-packed test/data.tar.gz has content ID bafkreie3b4xdvf4cqykvagruaivwtb3oi55hwt4egzlhppu2o3hw2eudhi referenced from package.json in test-data-hash, test-data-urls, and the test:data:download script. New regression assertions in the Node ava and Python pytest suites pin the grid convention from issue InsightSoftwareConsortium#1409. The baseline tests now also assert the output origin (approximately [0.5, 0.5] for the unit-spacing inputs, and computed from the input metadata for apple.jpg). New tests assert that downsample and downsample-bin-shrink produce identical origin, spacing, and size, pinning the itk::BinShrinkImageFilter convention as the contract, and that a cropRadius of [8, 8] composes with the half-pixel shift (origin [8.5, 8.5], size [120, 120] with shrink factors [2, 2]). Node suite: 23/23 pass; Python WASI suite: 33/33 pass. test/data.tar.gz is not yet pinned: a maintainer still needs to upload it to the IPFS pinning service (itk.mypinata.cloud). Until then, CI and other machines cannot download this package's test data. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Minor version bump for the user-visible behavior change in the downsample and downsample-label-image pipelines: the output origin now shifts to the coarse-grid pixel centers, matching ITK's pixel-center convention and itk::BinShrinkImageFilter (issue InsightSoftwareConsortium#1409). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The new test data tarball CID introduced with the origin-shift baselines was never pinned to the itk.mypinata.cloud gateway, so test:data:download failed with HTTP 403 across the WASI build-test job, the Python Wasm matrix, and Node.js test jobs. Upload the tarball as a test-data release asset, following the transform-io convention, and list the GitHub release URL first with the Pinata gateway URL retained as a fallback. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The emscripten pyodide tests fetched pipeline modules from the jsDelivr CDN at the package version baked into js_package.py. On a version bump that version is not yet published to npm, so every pipeline fetch 404s and CI fails, a chicken-and-egg between publish and merge. Build the emscripten pipelines in test:python:emscripten, copy them into the pytest-pyodide dist directory, and point js_package.config.pipelines_base_url at the test server so the tests exercise the pipelines built from the branch instead of the published package. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Fixes the output-origin defect in the
downsampleanddownsample-label-imagepipelines reported in #1409. Both pipelines resampled onto a coarser grid but kept the input origin unchanged, so the downsampled image was misaligned by half a coarse pixel relative to the physically correct grid — and relative todownsample-bin-shrink, which was already correct.The problem
Under ITK's pixel-center convention, a pixel's origin is the physical location of its center. When you downsample by a factor k, each output pixel covers k input pixels, so its center sits at the physical location of continuous input index
(k − 1) / 2. The old code ignored this:This kept the origin at the first input pixel's center (plus any crop offset) with no half-pixel shift and no awareness of the image direction matrix.
itk::BinShrinkImageFilter(used bydownsample-bin-shrink) already does the right thing, which is why the two pipelines disagreed.The fix
At all three affected sites —
downsample.cxxscalar path,downsample.cxxvector-image path, anddownsample-label-image.cxx— the origin is now computed through a continuous index so ITK applies the direction matrix for us:Key points:
(shrinkFactors[i] - 1.0) / 2.0term moves the origin to the center of the first coarse bin — the(k − 1) / 2conventionBinShrinkImageFilteruses.itk::ContinuousIndexand callingTransformContinuousIndexToPhysicalPointroutes the shift through the image direction matrix, instead of the old per-axis arithmetic that silently assumed an identity direction.cropRadiusand the origin shift now stack (e.g.cropRadius = [8, 8]withshrinkFactors = [2, 2]→ origin[8.5, 8.5]).downsample-bin-shrink.cxxwas already correct and is unchanged; the fix makesdownsamplematch it exactly for the same inputs.Verification & tests
Added regression tests (Node + Python, WASI and emscripten) that pin the corrected grid convention so it cannot silently regress:
downsample(cthead1, shrinkFactors=[2, 2])→ origin[0.5, 0.5], spacing[2, 2], size[128, 128]downsampleorigin/spacing/size now exactly equaldownsample-bin-shrinkfor the same input and factors (directly pins the downsample pipeline should result in a shift in origin #1409 convention)cropRadius = [8, 8]composes with the shift → origin[8.5, 8.5], size[120, 120]Baselines were regenerated for the corrected origins and the test-data hash bumped accordingly. The package version is bumped
1.8.1 → 1.9.0(behavior change to the output geometry).Fixes #1409