From 16c1da10e9f9c52fd8d2335856a12ae9a64491f8 Mon Sep 17 00:00:00 2001 From: Matt McCormick Date: Mon, 26 Jan 2026 16:04:55 -0500 Subject: [PATCH 1/3] build(docker): add support for using a local ITK repository Better support for generating fixes in ITK. --- src/docker/RELEASE.md | 36 ++++++++++++++ src/docker/itk-wasm-base/Dockerfile | 15 +++++- src/docker/itk-wasm-base/build.sh | 75 +++++++++++++++++++++++++---- 3 files changed, 115 insertions(+), 11 deletions(-) diff --git a/src/docker/RELEASE.md b/src/docker/RELEASE.md index 7bc242f8d..6b3f2d5ad 100644 --- a/src/docker/RELEASE.md +++ b/src/docker/RELEASE.md @@ -20,3 +20,39 @@ pnpm clean && pnpm install && pnpm build && pnpm test git add -- packages/core/typescript/itk-wasm/src/cli/default-image-tag.js git commit -m "feat(itk-wasm-cli): update default Docker image for $(date '+%Y%m%d')-$(git rev-parse --short HEAD)" ``` + +## Building with a Local ITK Repository + +To test ITK bug fixes or develop new features for the wasm toolchains, you can build Docker images using a local ITK source directory instead of cloning from the remote repository. + +### Using the `--local-itk` Argument + +```bash +# Clone ITK locally (if not already present) +git clone https://github.com/InsightSoftwareConsortium/ITK.git /path/to/local/ITK +cd /path/to/local/ITK +git checkout + +# Build Docker images with local ITK source +./src/docker/build.sh --local-itk /path/to/local/ITK +# Or for individual image builds: +./src/docker/itk-wasm-base/build.sh --local-itk /path/to/local/ITK --with-wasi +``` + +### Using the `ITK_WASM_LOCAL_ITK_SOURCE` Environment Variable + +Alternatively, set the environment variable for scripting or CI workflows: + +```bash +export ITK_WASM_LOCAL_ITK_SOURCE=/path/to/local/ITK +./src/docker/build.sh +``` + +### Notes + +- The local ITK source is copied into the Docker build context, so changes to the local directory after starting the build will not be reflected. +- DCMTK patches are automatically applied to the local ITK source during the Docker build. +- This is useful for: + - Testing ITK bug fixes before they are merged upstream + - Developing new ITK features for wasm toolchains + - Debugging ITK issues specific to Emscripten or WASI builds diff --git a/src/docker/itk-wasm-base/Dockerfile b/src/docker/itk-wasm-base/Dockerfile index c212c7592..2cad7b68e 100644 --- a/src/docker/itk-wasm-base/Dockerfile +++ b/src/docker/itk-wasm-base/Dockerfile @@ -35,7 +35,20 @@ RUN curl -L https://api.github.com/repos/facebook/zstd/tarball/${zstd_GIT_TAG} | cd .. && \ rm -rf zstd-build zstd -RUN . /itk_wasm_env_vars.sh && git clone --branch $ITK_WASM_ITK_BRANCH --single-branch --depth 1 $ITK_WASM_ITK_REPOSITORY && \ +# Support using a local ITK source directory instead of cloning from git +ARG USE_LOCAL_ITK=0 +COPY ITKLocalCopy /ITKLocalCopy + +# Either use local ITK source or clone from git repository +RUN . /itk_wasm_env_vars.sh && \ + if [ "$USE_LOCAL_ITK" = "1" ] && [ -d /ITKLocalCopy ] && [ "$(ls -A /ITKLocalCopy 2>/dev/null | grep -v .gitkeep)" ]; then \ + echo "Using local ITK source" && \ + mv /ITKLocalCopy /ITK; \ + else \ + echo "Cloning ITK from $ITK_WASM_ITK_REPOSITORY branch $ITK_WASM_ITK_BRANCH" && \ + rm -rf /ITKLocalCopy && \ + git clone --branch $ITK_WASM_ITK_BRANCH --single-branch --depth 1 $ITK_WASM_ITK_REPOSITORY; \ + fi && \ sed -i -e '/^option(OPJ_USE_THREAD/c\option(OPJ_USE_THREAD "use threads" OFF)' \ /ITK/Modules/ThirdParty/GDCM/src/gdcm/Utilities/gdcmopenjpeg/src/lib/openjp2/CMakeLists.txt diff --git a/src/docker/itk-wasm-base/build.sh b/src/docker/itk-wasm-base/build.sh index d4c6cef91..f0e498987 100755 --- a/src/docker/itk-wasm-base/build.sh +++ b/src/docker/itk-wasm-base/build.sh @@ -14,22 +14,41 @@ BUILD_DATE=$(date -u +"%Y-%m-%dT%H:%M:%SZ") debug=false wasi=false version_tag=false +local_itk="" build_cmd="build" tag_flag="--tag" host_arch=$(uname -m | sed -e 's/x86_64/amd64/' -e 's/aarch64/arm64/') -for param; do - if [[ $param == '--with-debug' ]]; then - debug=true - elif [[ $param == '--with-wasi' ]]; then - wasi=true - elif [[ $param == '--version-tag' ]]; then - version_tag=true - else - newparams+=("$param") - fi +while [[ $# -gt 0 ]]; do + case $1 in + --with-debug) + debug=true + shift + ;; + --with-wasi) + wasi=true + shift + ;; + --version-tag) + version_tag=true + shift + ;; + --local-itk) + local_itk="$2" + shift 2 + ;; + *) + newparams+=("$1") + shift + ;; + esac done set -- "${newparams[@]}" # overwrites the original positional params +# Support ITK_WASM_LOCAL_ITK_SOURCE environment variable as fallback +if [[ -z "$local_itk" && -n "${ITK_WASM_LOCAL_ITK_SOURCE:-}" ]]; then + local_itk="$ITK_WASM_LOCAL_ITK_SOURCE" +fi + # Note: also need to set in wasi-sdk-pthread-itkwasm.cmake wasi_ld_flags="-flto -lwasi-emulated-process-clocks -lwasi-emulated-signal -lc-printscan-long-double" wasi_c_flags="-flto -msimd128 -D_WASI_EMULATED_PROCESS_CLOCKS -D_WASI_EMULATED_SIGNAL" @@ -46,6 +65,30 @@ emscripten_threads_c_flags="-pthread -msimd128 -flto -Wno-warn-absolute-paths -D emscripten_threads_debug_ld_flags="-pthread -s MALLOC=mimalloc -s PTHREAD_POOL_SIZE=navigator.hardwareConcurrency -fno-lto -s ALLOW_MEMORY_GROWTH=1 -s MAXIMUM_MEMORY=4GB" emscripten_threads_debug_c_flags="-pthread -msimd128 -fno-lto -Wno-warn-absolute-paths" +# Handle local ITK source +local_itk_build_arg="" +if [[ -n "$local_itk" ]]; then + if [[ ! -d "$local_itk" ]]; then + echo "Error: Local ITK directory does not exist: $local_itk" + exit 1 + fi + echo "Using local ITK source from: $local_itk" + # Clear placeholder and copy local ITK source + rm -rf "$script_dir/ITKLocalCopy"/* + cp_exe=$(which rsync 2>/dev/null || which cp) + $cp_exe -a "$local_itk"/* "$script_dir/ITKLocalCopy/" + local_itk_build_arg="--build-arg USE_LOCAL_ITK=1" +fi + +# Cleanup function to restore ITKLocalCopy to placeholder state +cleanup_local_itk() { + if [[ -n "$local_itk" ]]; then + rm -rf "$script_dir/ITKLocalCopy"/* + touch "$script_dir/ITKLocalCopy/.gitkeep" + fi +} +trap cleanup_local_itk EXIT + $exe $build_cmd $tag_flag quay.io/itkwasm/emscripten-base:latest-$host_arch \ --build-arg IMAGE=quay.io/itkwasm/emscripten-base \ --build-arg HOST_ARCH=$host_arch \ @@ -53,6 +96,7 @@ $exe $build_cmd $tag_flag quay.io/itkwasm/emscripten-base:latest-$host_arch \ --build-arg VCS_REF=${VCS_REF} \ --build-arg VCS_URL=${VCS_URL} \ --build-arg BUILD_DATE=${BUILD_DATE} \ + $local_itk_build_arg \ $script_dir $@ if $version_tag; then $exe $build_cmd $tag_flag quay.io/itkwasm/emscripten-base:${TAG}-$host_arch \ @@ -63,6 +107,7 @@ if $version_tag; then --build-arg VCS_REF=${VCS_REF} \ --build-arg VCS_URL=${VCS_URL} \ --build-arg BUILD_DATE=${BUILD_DATE} \ + $local_itk_build_arg \ $script_dir $@ fi @@ -76,6 +121,7 @@ $exe $build_cmd $tag_flag quay.io/itkwasm/emscripten-base:latest-threads-$host_a --build-arg BUILD_DATE=${BUILD_DATE} \ --build-arg LDFLAGS="${emscripten_threads_ld_flags}" \ --build-arg CFLAGS="${emscripten_threads_c_flags}" \ + $local_itk_build_arg \ $script_dir $@ if $version_tag; then $exe $build_cmd $tag_flag quay.io/itkwasm/emscripten-base:${TAG}-threads-$host_arch \ @@ -88,6 +134,7 @@ if $version_tag; then --build-arg BUILD_DATE=${BUILD_DATE} \ --build-arg LDFLAGS="${emscripten_threads_ld_flags}" \ --build-arg CFLAGS="${emscripten_threads_c_flags}" \ + $local_itk_build_arg \ $script_dir $@ fi @@ -102,6 +149,7 @@ if $wasi; then --build-arg BASE_IMAGE=docker.io/dockcross/web-wasi-emulated-threads \ --build-arg LDFLAGS="${wasi_ld_flags}" \ --build-arg CFLAGS="${wasi_c_flags}" \ + $local_itk_build_arg \ $script_dir $@ if $version_tag; then $exe $build_cmd $tag_flag quay.io/itkwasm/wasi-base:${TAG}-$host_arch \ @@ -115,6 +163,7 @@ if $wasi; then --build-arg BASE_IMAGE=docker.io/dockcross/web-wasi-emulated-threads \ --build-arg LDFLAGS="${wasi_ld_flags}" \ --build-arg CFLAGS="${wasi_c_flags}" \ + $local_itk_build_arg \ $script_dir $@ fi fi @@ -132,6 +181,7 @@ if $debug; then --build-arg BUILD_DATE=${BUILD_DATE} \ --build-arg LDFLAGS="${emscripten_debug_ld_flags}" \ --build-arg CFLAGS="${emscripten_debug_c_flags}" \ + $local_itk_build_arg \ $script_dir $@ if $version_tag; then $exe $build_cmd $tag_flag quay.io/itkwasm/emscripten-base:${TAG}-debug-$host_arch \ @@ -145,6 +195,7 @@ if $debug; then --build-arg BUILD_DATE=${BUILD_DATE} \ --build-arg LDFLAGS="${emscripten_debug_ld_flags}" \ --build-arg CFLAGS="${emscripten_debug_c_flags}" \ + $local_itk_build_arg \ $script_dir $@ fi $exe $build_cmd $tag_flag quay.io/itkwasm/emscripten-base:latest-threads-debug-$host_arch \ @@ -158,6 +209,7 @@ if $debug; then --build-arg BUILD_DATE=${BUILD_DATE} \ --build-arg LDFLAGS="${emscripten_threads_debug_ld_flags}" \ --build-arg CFLAGS="${emscripten_threads_debug_c_flags}" \ + $local_itk_build_arg \ $script_dir $@ if $version_tag; then $exe $build_cmd $tag_flag quay.io/itkwasm/emscripten-base:${TAG}-threads-debug-$host_arch \ @@ -171,6 +223,7 @@ if $debug; then --build-arg BUILD_DATE=${BUILD_DATE} \ --build-arg LDFLAGS="${emscripten_threads_debug_ld_flags}" \ --build-arg CFLAGS="${emscripten_threads_debug_c_flags}" \ + $local_itk_build_arg \ $script_dir $@ fi if $wasi; then @@ -184,6 +237,7 @@ if $debug; then --build-arg BASE_IMAGE=docker.io/dockcross/web-wasi-emulated-threads \ --build-arg LDFLAGS="${wasi_debug_ld_flags}" \ --build-arg CFLAGS="${wasi_debug_c_flags}" \ + $local_itk_build_arg \ $script_dir $@ if $version_tag; then $exe $build_cmd $tag_flag quay.io/itkwasm/wasi-base:${TAG}-debug-$host_arch \ @@ -197,6 +251,7 @@ if $debug; then --build-arg BASE_IMAGE=docker.io/dockcross/web-wasi-emulated-threads \ --build-arg LDFLAGS="${wasi_debug_ld_flags}" \ --build-arg CFLAGS="${wasi_debug_c_flags}" \ + $local_itk_build_arg \ $script_dir $@ fi fi From c75824ebed970993b130cb03d3a43feaee7ae3fb Mon Sep 17 00:00:00 2001 From: Matt McCormick Date: Mon, 26 Jan 2026 16:22:39 -0500 Subject: [PATCH 2/3] docs: add initial AGENTS.md --- AGENTS.md | 96 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 96 insertions(+) create mode 100644 AGENTS.md diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 000000000..762e3e0d5 --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,96 @@ +# ITK-Wasm AI Agent Instructions + +## Project Overview + +ITK-Wasm compiles [ITK](https://itk.org/) C++ image processing to WebAssembly for browser, Node.js, and Python execution. The architecture spans: + +- **C++ Core** (`include/`, `src/`): ITK pipeline interface with `itkPipeline.h` CLI parsing +- **Docker Build Images** (`src/docker/`): Emscripten and WASI toolchain containers +- **CLI** (`packages/core/typescript/itk-wasm/src/cli/`): `itk-wasm` build/bindgen commands +- **Language Bindings**: TypeScript (`packages/*/typescript/`) and Python (`packages/*/python/`) +- **Example Packages** (`packages/`): image-io, mesh-io, dicom, downsample, etc. + +## Development Workflow + +```bash +# Full build +pnpm install +pnpm build +pnpm test + +# Individual package development +cd packages/ +pnpm build:emscripten # or build:wasi +pnpm build:gen:typescript +pnpm build:gen:python +pnpm test +``` + +### Debug WebAssembly Builds + +```bash +pixi shell +pnpm build:emscripten:debug && pnpm build:wasi:debug +pnpm build && pnpm test +``` + +### Docker Image Development + +```bash +# Build with local ITK source for bug fixes +./src/docker/build.sh --local-itk /path/to/ITK +# Or via environment variable +export ITK_WASM_LOCAL_ITK_SOURCE=/path/to/ITK +./src/docker/build.sh +``` + +## Package Structure Convention + +Each `packages//` contains: +- C++ pipelines (`.cxx` files) with `CMakeLists.txt` +- `package.json` with `itk-wasm` config section for test data, Docker images, package names +- `typescript/` and `python/` directories for generated bindings +- `test/data/` with input/baseline files (downloaded via `test:data:download`) + +## Key Patterns + +### C++ Pipeline Structure + +Use `ITK_WASM_PARSE` macro and typed inputs/outputs from `include/`: +```cpp +#include "itkPipeline.h" +#include "itkInputImage.h" +#include "itkOutputImage.h" + +int main(int argc, char * argv[]) { + itk::wasm::Pipeline pipeline("operation-name", "Description", argc, argv); + // Add options, parse with ITK_WASM_PARSE(pipeline), process, return +} +``` + +### Test Data Management + +Test data uses content-addressed storage. To add/modify: +```bash +# In package directory +pnpm test:data:pack # Creates test/data.tar.gz, outputs CID +# Ask user to upload to IPFS host, update package.json itk-wasm.test-data-hash and test-data-urls +``` + +### Binding Generation + +The CLI generates TypeScript/Python bindings from WASI builds: +```bash +itk-wasm bindgen --interface typescript # or python +``` + +## Environment Variables + +Key variables in `itk_wasm_env.bash`: +- `ITK_WASM_ITK_REPOSITORY`, `ITK_WASM_ITK_BRANCH`: ITK source for Docker builds +- `ITK_WASM_LOCAL_ITK_SOURCE`: Use local ITK directory in Docker builds +- `ITK_WASM_*_TEST_DATA_HASH/URLS`: Per-package test data configuration + +## Commit Convention + +Uses [conventional commits](https://www.conventionalcommits.org/): `feat:`, `fix:`, `docs:`, etc. From 032429ed99ca65588e92dff21eb1c7aee6166e51 Mon Sep 17 00:00:00 2001 From: Matt McCormick Date: Mon, 26 Jan 2026 17:05:31 -0500 Subject: [PATCH 3/3] build(docker): add .gitkeep for ITKLocalCopy dir To complete the docker COPY without a local copy. --- src/docker/itk-wasm-base/ITKLocalCopy/.gitkeep | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 src/docker/itk-wasm-base/ITKLocalCopy/.gitkeep diff --git a/src/docker/itk-wasm-base/ITKLocalCopy/.gitkeep b/src/docker/itk-wasm-base/ITKLocalCopy/.gitkeep new file mode 100644 index 000000000..e69de29bb