feat(downsample): add resample-bounding-box pipeline (metadata-only, C++/TS/Python) - #26
Closed
thewtex wants to merge 4 commits into
Closed
feat(downsample): add resample-bounding-box pipeline (metadata-only, C++/TS/Python)#26thewtex wants to merge 4 commits into
thewtex wants to merge 4 commits into
Conversation
Add a metadata-only ITK-Wasm pipeline that computes the padded moving-image region needed to resample a fixed image grid through a spatial transform, emitting a JSON bounding box. - resampleBoundingBox.h: reusable ResampleBoundingBoxComputer<TTransform> (enumerates all fixed-grid boundary pixels, accumulates tight physical and moving continuous-index min/max, pads outward). Metadata only. - resample-bounding-box.cxx: thin WASM wrapper dispatching over float/double x dims 2/3/4; emits paddedStartIndex/paddedSize/ paddedCorners/corners JSON via rapidjson. - resample-bounding-box-generate-inputs.cxx: self-contained generator of fixed/moving .iwi metadata images and a translation .iwt. - CMakeLists.txt: ITKTransform/ITKTransformIO, new targets, and DEPENDS-linked CTests (generate inputs, then run the pipeline). Verified end-to-end under pnpm build:wasi / CTest (wasmtime): a (10,5) translation with padding 1 yields the hand-checked bounding box. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…nding-box
Generate and validate the TypeScript/JavaScript bindings for the
resample-bounding-box pipeline, proving it is callable from Node with
metadata-only images (empty pixel buffers) and a transform.
- Bindings: resampleBoundingBoxNode(transform, fixed, moving, { padding })
and the browser resampleBoundingBox(...); result field renamed from the
generic "output" to "boundingBox" at the source (the .cxx option name).
- Exclude the test-only resample-bounding-box-generate-inputs generator from
the Emscripten build (CMake `if(NOT EMSCRIPTEN)`) so bindgen does not expose
it; it is still built for the WASI/native C++ CTest.
- resampleReadInputTransform.h: read a spatial transform generically into the
abstract itk::Transform base via the ITK object factory, from the wasm memory
store under --memory-io or the filesystem otherwise (always double precision).
- Dispatch on transform dimension in main() instead of SupportInputTransformTypes,
whose memory-IO type detection mis-parses a TransformList (JSON array) as a
single transform and throws for every in-memory transform.
- bindgen: forward falsy-but-valid scalar options (e.g. padding: 0) by using a
presence check instead of truthiness in function-module.js.
- Node tests for padding 1 and padding 0 (region shrinks one pixel per side),
both with empty-data metadata-only images; auto-discovered by the ava glob.
- Browser demo controller wired into the demo-app.
Verified: `pnpm test:node` passes the new tests; `pnpm test:wasi` still passes
all 7 C++ CTests (no regression to the filesystem read path).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ng-box Generate the itkwasm-downsample Python bindings (wasi, emscripten, dispatch) for the resample-bounding-box pipeline and add a WASI pytest validating the padded moving-image region for a 2D translation with metadata-only images. Framework changes: - cli/bindgen.js: honor an opt-in `itk-wasm.bindgen-exclude` list so build-only helper executables (the resample-bounding-box-generate-inputs CTest fixture, which must be built under WASI) are not emitted as public language bindings. - python bindgen (wasi/emscripten function modules): forward numeric scalar options with a presence check (`is not None`) instead of truthiness, so a valid `padding=0` is no longer dropped to the C++ default of 1 (the Python analogue of the Phase-02 TypeScript falsy-0 fix). pnpm test:python:wasi -> 13 passed (2 new: padding=1 exact region, padding=0 shrinks one pixel per side). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
… and docs for resample-bounding-box Phase 04 of the resample-bounding-box pipeline: multi-dimension coverage, robustness, and a documentation artifact. C++ 12/12 CTests, Node 8/8, and Python wasi 14/14 all pass, with identical regions across the three surfaces. resampleBoundingBox.h (hardening): - Guard a degenerate fixed image (any zero-length axis): report an empty region instead of floor/ceil-ing sentinel min/max into garbage indices. - Make boundary-point storage reuse-safe via clear()+reserve()+push_back so the vector holds exactly the current call's points -- no stale points leak when one ResampleBoundingBoxComputer instance is reused across differing boundary counts (shrinking then growing). - Add a diagnostic (unserialized) numberOfBoundaryPoints so tests can confirm full-boundary (not corners-only) sampling. paddedSize stays clamped to >= 0. resample-bounding-box-generate-inputs.cxx: - Add a --case selector (2d-translation default, 3d-translation, 2d-rotation) emitting each self-contained fixed/moving/transform set; the rotation uses an itk::AffineTransform (cos .8/sin .6 about center) -> non-axis-aligned corners. resample-bounding-box-test.cxx (new): in-process unit test asserting the exact region for the 2D/3D translation and 2D rotation cases at double precision, plus the hardening (padding symmetry, padding-independent corners, degenerate axis, instance reuse vs fresh). Built/CTest-run only outside Emscripten. CMakeLists.txt: 3D and rotation integration CTests (generate -> run, with DEPENDS) and the unit-test CTest. TypeScript + Python: parametrized case tables (2D/3D translation, 2D affine rotation) with dimension-generic image/transform helpers and an affine helper; assert regions identical to the C++ results, including the padding-0 padded corners (which equal the tight corners only when those fall on grid lines). docs/resample-bounding-box.md (new): structured reference (YAML front matter + [[downsample]]/[[affine-ops]] wiki-links) documenting the inputs, algorithm, exact JSON schema, and intended downstream sub-region fetch. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
thewtex
force-pushed
the
resample-bounding-box
branch
from
July 3, 2026 15:02
0aa848d to
3117fa6
Compare
Owner
Author
|
Superseded by the upstream PR against InsightSoftwareConsortium/ITK-Wasm: InsightSoftwareConsortium#1549 |
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
Adds a new
resample-bounding-boxpipeline to the@itk-wasm/downsample/itkwasm-downsamplepackage. Given a spatialtransform, afixedimage, and amovingimage, it computes the padded sub-region of the moving image that a caller must fetch in order to resample the fixed image's grid through that transform — using image metadata only (size, spacing, origin, direction). No pixel buffer is ever dereferenced, so both images may be passed with emptydata.The pipeline is implemented once in C++ and shipped across all three runtimes — C++ (WASI/native CTest), TypeScript (browser + Node), and Python (WASI + Emscripten) — producing identical results.
Why
Resampling a fixed grid into a moving image only ever reads moving-image samples inside the transformed footprint of the fixed grid. When the moving image is large, remote, or chunked, materializing all of it just to resample a small overlapping region is wasteful. This pipeline answers "which moving-image indices will the resample actually read?" from arithmetic on metadata alone, so a caller can fetch only that block (or skip non-overlapping tiles entirely) before moving any real pixels. It complements the existing resampling pipelines in this package and the affine-transform tooling used to build the transforms fed into it.
What changed
New pipeline (C++)
resample-bounding-box.cxx— pipeline entry point; parsestransform,fixed,moving,bounding-box(output JSON) and a--paddingoption, and serializes the result.resampleBoundingBox.h— the metadata-only bounding-box computation.resampleReadInputTransform.h— generic transform reading/dimension peeking.resample-bounding-box-test.cxx+resample-bounding-box-generate-inputs.cxx— CTest coverage and a self-contained test-input generator.CMakeLists.txtwired up for the new targets.Language bindings & tests
resampleBoundingBox(browser + Node) with generated options/result interfaces, exported from the package index; Node tests and a browser demo-app controller.resample_bounding_box(sync + async) for WASI and Emscripten packages, with WASI pytest coverage.Documentation
docs/resample-bounding-box.md— motivation, inputs, the algorithm, the output JSON schema, edge cases, and intended downstream use.Core
itk-wasmbindgen improvements (needed by this pipeline, but general fixes)0for a numeric option (e.g.--padding 0). They now use a presence check (typeof !== "undefined"/is not None) for numeric options while keeping the truthiness guard forTEXT/BOOL.itk-wasm.bindgen-excludeconfig inpackage.jsonso build-only helper executables (here, theresample-bounding-box-generate-inputstest-input generator) are not emitted as public language bindings.Implementation details
itk::wasm::SupportInputTransformTypes, because that helper deserializes the transform input (aTransformList/ JSON array) as a single transform object and throws for every in-memory transform.--help/--interface-json/--versiondispatch to a default 2D functor so bindgen can extract the interface.itk::Transformbase at double precision — lossless for float32/float64 inputs and robust to.iwtscalar-type detection that can misreport a float64 transform as float32.2^Ncorners) is transformed to a moving-image continuous index; interior pixels are skipped efficiently. For affine transforms the transformed rectangle is convex so corners would suffice, but for nonlinear transforms an interior edge pixel can map outside the corner hull — sampling the full boundary avoids under-bounding the region.--padding(default1, covering linear interpolation's one-neighbor read) expands the integer region symmetrically per side; sizes are clamped to ≥ 0; a degenerate fixed image with a zero-length axis yields an empty all-zero region. The unpaddedcornersare the tight transformed-point extremes regardless of padding.Output JSON
{ "paddedStartIndex": [int, ...], "paddedSize": [uint, ...], "paddedCorners": { "min": [double, ...], "max": [double, ...] }, "corners": { "min": [double, ...], "max": [double, ...] } }Test plan
--padding 0.