🚀 Replace Julia/ROMEO backend with pure C++ port - #16
Conversation
Replace the Julia-backed ROMEO unwrapping pipeline with a pure C++ port that builds and runs without Julia installed. The Julia backend had been broken by upstream API drift and was blocking development. Scope delivered: - Header-only C++ port of ROMEO.jl v1.0.0's unwrap + voxelquality algorithms under include/romeo/ (bucket queue, weight calculation, seed selection, MST region growing, 3D + 4D unwrap, voxel quality) - Strip every trace of Julia from the build: CMakeLists.txt, setup.py, Dockerfile, CI workflows, cibuildwheel before-all, FindJulia.cmake - New Romeo<T> pybind11 class replacing JuliaContext<T>, with a transitional Python alias preserving existing call sites until phase 6 - Vendor ROMEO v1.0.0 source + test data under third_party/ROMEO/ (replaces the dead wustl.box.com BIDS download) - tests/test_romeo.py ports Julia's dsp_tests / specialcases / mri / voxelquality assertions, driving the vendored Phase.nii/Mag.nii through the bindings — all 10 cases pass end-to-end - Migrate tooling: uv for deps (uv.lock committed), ruff + pyright replacing black/isort/pycodestyle, pre-commit hooks pointing at upstream ruff + pyright-python repos - Bump requires-python to 3.11, drop 3.8-3.10 from wheel matrix, re-enable macOS wheels now that Julia isn't blocking Phase 6 (cleanup of transitional JuliaContext aliases and warpkit/ julia.py) is the next commit. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
With the C++ port landed and all tests green, remove the last Julia-shaped scaffolding: - warpkit/unwrap.py now imports Romeo directly from warpkit_cpp and instantiates it as `romeo = Romeo()` at each call site (was `JULIA = JuliaContext()`); also prune the unused `new_voxel_prop` local that ruff surfaced alongside the port edits - Delete warpkit/julia.py (the PID-keyed singleton — unnecessary now that there's no Julia runtime to keep one-per-process) - Drop the `JuliaContext = Romeo` alias from warpkit/__init__.py - Strip the Julia install section from README.md; point the from-source path at `uv sync --group dev` instead of `pip install` - Demote pyright reportArgument/Assignment/Return/GeneralTypeIssues to "warning" — pre-existing numpy/nibabel typing debt was surfaced when phase-6 edits triggered re-analysis of unwrap.py and its imports. Revisit in a follow-up PR dedicated to typing fixes. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The vendored Julia source and test scripts served their purpose during
the port; with phases 1-6 done they're just dead weight. Prune to what
actually earns its keep in the repo:
- Move third_party/ROMEO/test/data/small/{Phase,Mag}.nii to
tests/data/romeo/ alongside the rest of the test fixtures (2.5 MB,
still needed by three passing tests)
- Move third_party/ROMEO/LICENSE to include/romeo/LICENSE — the MIT
text remains verbatim, attribution requirement satisfied
- Add include/romeo/README.md calling out the provenance (upstream
URL, commit SHA, which code paths we deliberately omitted)
- Rewrite header comments that referenced third_party/ROMEO/src/... to
point at "ROMEO.jl src/..." (the LICENSE + README now anchor the
upstream link)
- Delete everything else under third_party/
The tests/__init__.py → tests/conftest.py move (fixture definition
relocated to conftest, the idiomatic pytest spot) is picked up here
too.
Full test suite still green: 13 passed, 1 skipped.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
upload-artifact v3 was deprecated Jan 2025 and now hard-fails. Bumped it to v4 (plus matching renames for unique artifact names per matrix cell and merge-multiple on the publish-side download), and updated the rest of the actions in the workflow while here: checkout v4→v5, setup-uv v5→v6, codecov v3→v5, cibuildwheel v2.16→v3.2, build-push-action v5→v6. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- pybind11 v2.11.1 -> v3.0.4 and ITK v5.4rc01 -> v5.4.5 - CI: ubuntu-22.04 -> ubuntu-latest; add Python 3.14 to wheel matrix - Rework architecture compile flags: separate Apple-arm64 (-mcpu=apple-m1) from Linux aarch64 (-march=armv8.2-a, Graviton2-safe), drop dead gcc/clang version branches, fix bug where arm64 had its compile-options line commented out, scope -Wall to warpkit_cpp only - Replace BIDS-backed test fixture (data hosted at unreachable wustl.box link) with local tests/data/test_data/, un-skip test_medic - Expand ruff lint rule set, add formatter config Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- Drop hardcoded project.version; let setuptools-scm derive it from git tags - Write warpkit/_version.py at build time; re-export __version__ from warpkit and add --version to medic and extract_field_from_maps CLIs - Pin build-system requires to current floors (setuptools>=77, setuptools-scm>=8, wheel>=0.45, cmake-build-extension>=0.6.1) - CI: fetch-depth=0 on all checkouts so setuptools-scm sees tags - Fix B028/SIM118 lints exposed by expanded ruff rules; demote nibabel attribute-access pyright complaints alongside existing typing debt Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- Drop conda/ recipe and bootstrap script (legacy Julia path; no longer needed with the C++ ROMEO port). - Overhaul README: trim intro, drop conda section, restructure install paths (PyPI / Docker / from-source), refresh CLI examples. - medic CLI: make --out-prefix required; let users pass --TEs / --total-readout-time / --phase-encoding-direction directly as an alternative to --metadata; switch all flags to dash-form. - extract_field_from_maps CLI: switch flags to dash-form. - Generate warpkit/warpkit_cpp.pyi via pybind11-stubgen and add warpkit/py.typed marker; ship both via MANIFEST.in + setuptools package-data; add pybind11-stubgen to dev deps. - Resolve all pyright errors (cast nib.load -> Nifti1Image, narrow .affine for np.allclose, fix Optional default, widen DummyExecutor.shutdown signature, etc.). - Resolve all ruff errors: rename uppercase MR-physics names to lowercase across the Python tree (TEs->tes, TE0->te0, TE1->te1, X/Y/W matrices, A/B/A_mA/B_mB/ssA/ssB, U/S/VT, B0, TR_in_sec). Rename pybind11 binding names accordingly (romeo_unwrap3D -> romeo_unwrap3d, romeo_unwrap4D -> romeo_unwrap4d, TEs kwarg -> tes); regenerate .pyi. CLI keeps --TEs as the user-facing flag (mapped to args.tes via dest=). - Skip free-threaded cibuildwheel builds (cp313t-*, cp314t-*) — cibuildwheel was producing two cp314 wheels causing the test-wheel install step to fail with conflicting URLs. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- Add CLAUDE.md documenting the repo: layout, dev workflow, code conventions (lowercase MR-physics names, no Julia, modern Python syntax), commit/PR style with gitmoji shortcode table, and CI specifics (cibuildwheel matrix, free-threaded skip). - Add a commit-msg pre-commit hook (scripts/check-gitmoji.sh) that rejects commit subjects not starting with a gitmoji shortcode. Merge / Revert / fixup! / squash! / amend! commits are exempt. - Set default_install_hook_types: [pre-commit, commit-msg] so pre-commit install registers both stages automatically. Existing contributors should re-run uv run pre-commit install once. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
Pull request overview
This PR removes the Julia-based ROMEO dependency and replaces it with a header-only C++17 ROMEO port (unwrap + voxelquality) exposed via pybind11, while also modernizing packaging/tooling (uv, ruff, pyright, Python ≥3.11) and updating CLI/test infrastructure accordingly.
Changes:
- Replace Julia ROMEO backend with a pure C++ ROMEO port under
include/romeo/, and update pybind11 bindings/types. - Remove Julia from build/packaging/CI/Docker and migrate Python tooling to uv + ruff + pyright with Python 3.11+.
- Add ROMEO parity/behavior tests and refresh MEDIC test fixtures and CLI metadata handling.
Reviewed changes
Copilot reviewed 54 out of 65 changed files in this pull request and generated 4 comments.
Show a summary per file
| File | Description |
|---|---|
| warpkit/warpkit_cpp.pyi | Adds/updates type stubs for the compiled extension, including new ROMEO APIs. |
| warpkit/utilities.py | Typing + formatting updates; minor refactors around orientation transforms and ITK-backed helpers. |
| warpkit/unwrap.py | Switches unwrapping backend from JuliaContext to C++ Romeo bindings; updates naming/types and iterator plumbing. |
| warpkit/scripts/medic.py | CLI updated to support either BIDS sidecars or direct acquisition args; adds --version and renames flags. |
| warpkit/scripts/extract_field_from_maps.py | CLI polish: --version, renamed flags to kebab-case, small typing improvements. |
| warpkit/py.typed | Marks package as typed for downstream type checkers. |
| warpkit/model.py | Modernizes typing and variable naming (x/y/w) for weighted regression. |
| warpkit/julia.py | Removes JuliaContext shim (Julia dependency eliminated). |
| warpkit/distortion.py | Updates typing and echo-time naming; small formatting and cast narrowings. |
| warpkit/concurrency.py | Updates typing imports and refines DummyExecutor shutdown signature. |
| warpkit/init.py | Removes Julia auto-install preflight; uses setuptools-scm versioning; keeps WARPKIT_DEV behavior. |
| tests/test_utilities.py | Updates variable names to match new style. |
| tests/test_romeo.py | Adds comprehensive ROMEO port validation tests (goldens + properties + behavior). |
| tests/test_model.py | Updates variable names to match new style. |
| tests/test_julia.py | Removes JuliaContext test (Julia removed). |
| tests/test_distortion.py | Switches MEDIC pipeline test to use new fixture name/source. |
| tests/test_concurrency.py | Removes unused pytest import. |
| tests/data/test_data/sub-a01_task-rest_acq-tr1800_echo-3_part-phase_bold.json | Adds local test sidecar for echo 3 phase. |
| tests/data/test_data/sub-a01_task-rest_acq-tr1800_echo-3_part-mag_bold.json | Adds local test sidecar for echo 3 magnitude. |
| tests/data/test_data/sub-a01_task-rest_acq-tr1800_echo-2_part-phase_bold.json | Adds local test sidecar for echo 2 phase. |
| tests/data/test_data/sub-a01_task-rest_acq-tr1800_echo-2_part-mag_bold.json | Adds local test sidecar for echo 2 magnitude. |
| tests/data/test_data/sub-a01_task-rest_acq-tr1800_echo-1_part-phase_bold.json | Adds local test sidecar for echo 1 phase. |
| tests/data/test_data/sub-a01_task-rest_acq-tr1800_echo-1_part-mag_bold.json | Adds local test sidecar for echo 1 magnitude. |
| tests/data/download_bids_testdata.sh | Removes dead download script for old BIDS dataset. |
| tests/conftest.py | Introduces a new local test-data fixture loader for MEDIC tests. |
| tests/init.py | Removes old pybids-based fixture wiring. |
| src/warpkit.cpp | Replaces JuliaContext bindings with C++ ROMEO facade + new method names. |
| setup.py | Removes Julia preflight and adjusts build config for new toolchain expectations. |
| setup.cfg | Removes pycodestyle config (ruff now used). |
| scripts/check-gitmoji.sh | Adds commit-msg hook enforcing gitmoji shortcode convention. |
| pyproject.toml | Migrates to Python ≥3.11, uv dependency groups, ruff/pyright config, setuptools-scm versioning, updated cibuildwheel config. |
| notes/ROMEO_port_plan.md | Adds detailed plan/provenance for the ROMEO C++ port and rollout phases. |
| include/romeo/weights.h | Implements ROMEO :romeo weight computation and rescaling logic in C++. |
| include/romeo/voxel_quality.h | Implements voxel quality calculation and aggregation logic. |
| include/romeo/volume_view.h | Adds minimal column-major volume view declarations. |
| include/romeo/utility.h | Adds ROMEO utility helpers (gamma_fold, rem2pi_nearest, NBINS). |
| include/romeo/unwrap.h | Implements 3D/4D unwrap orchestration and global correction logic. |
| include/romeo/seed.h | Implements single-echo and multi-echo seed correction. |
| include/romeo/romeo.h | Provides Python-facing C++ Romeo facade implementing pybind-callable entry points. |
| include/romeo/priority_queue.h | Implements bucket priority queue matching ROMEO.jl dequeue semantics. |
| include/romeo/algorithm.h | Implements MST region-growing and unwrap edge logic. |
| include/romeo/README.md | Documents ROMEO port scope and provenance. |
| include/romeo/LICENSE | Includes upstream ROMEO MIT license. |
| include/romeo.h | Removes old Julia-based ROMEO bridge header. |
| conda/warpkit-conda-install.sh | Removes conda installer script. |
| conda/meta.yaml | Removes conda recipe (Julia-based). |
| cmake_aux/FindJulia.cmake | Removes Julia CMake discovery module. |
| README.md | Updates installation/build/docs to reflect Julia removal and new CLI behavior. |
| MANIFEST.in | Ensures py.typed and .pyi ship in sdists. |
| Dockerfile | Removes Julia stages and switches to uv-managed Python + deps. |
| CMakeLists.txt | Removes Julia linkage, updates pybind11/ITK pins, and adjusts compile flags. |
| CLAUDE.md | Adds repository conventions and workflow notes (tooling, naming, CI expectations). |
| .python-version | Pins Python 3.11 for local dev (uv-managed). |
| .pre-commit-config.yaml | Replaces black/isort with ruff + adds pyright + commit-msg hook. |
| .gitignore | Adjusts ignores (keep .python-version tracked; ignore generated _version.py; scratch dir tweak). |
| .github/workflows/build.yml | Removes Julia from CI, moves to uv, updates wheel matrix and actions versions. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Add tests across all the under-covered modules. Total: 14 -> 71 passing. - test_utilities: normalize, rescale_phase, corr2_coeff edge cases, get_largest_connected_component, create_brain_mask, orientation transform identity/round-trip, build_low_pass_filter, fmap<->dmap round trip (parameterized over all 6 PE directions), flip_sign, displacement_map_to_field axis insertion, invert_displacement_field / invert_displacement_maps zero, compute_hausdorff_distance identity + symmetry, convert_warp itk/fsl conversions and validation paths. Coverage 51% -> 83%. - test_distortion: affine mismatch, shape mismatch, and None-affine error paths in medic. Coverage 65% -> 88%. - test_concurrency: DummyExecutor submit/exception/post-shutdown paths, run_executor parameterized over all three backends, unknown type rejection, initializer call in single-cpu path. Coverage 87% -> 100%. - test_unwrap (new): reject_outliers (inliers / outlier / zero-MAD fallback), compute_offset (perfect fit -> 0, known 2pi shift -> -1), and Romeo binding shape (lowercase names exist, weights preset validation). - test_scripts (new): medic CLI --help, --version, either-or validation, mutex with --metadata, TE/phase count match (both direct-args and --metadata paths), PE direction choices. extract_field_from_maps --help and choice validation. - test_model: split into three focused tests (lstsq match, known-slope recovery, zero-weight column safety). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- unwrap.py: phase_iterator was being called with border_size as the argument that gets forwarded as automask_dilation, silently using the border size (default 5) for the automask dilation iterations instead of the intended automask_dilation default (3). Add an automask_dilation parameter to unwrap_and_compute_field_maps and pass it through correctly. - conftest.py: switch the test_data fixture from Nifti1Image.load(p) to cast(Nifti1Image, nib.load(str(p))) so the loader call is idiomatic nibabel and pyright keeps the concrete type. - warpkit_cpp.pyi: rewrite numpy.bool to numpy.bool_ (numpy.bool was removed in NumPy 1.24). Add scripts/regen-stub.sh that runs pybind11-stubgen and applies the rewrite + ruff fix-up so regenerations don't reintroduce the deprecated alias. Update CLAUDE.md to reference the wrapper. - CMakeLists.txt: drop -march=x86-64-v3 in favor of plain -march=x86-64 on x86_64. v3 (AVX2/BMI/...) is too aggressive for PyPI wheels — pre-Haswell CPUs wouldn't run them — and some toolchains don't recognize the v3 alias. Consumers who want a specific microarch can set CXXFLAGS at build time. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The Test and Coverage job was running coverage but only producing the binary .coverage file plus a stdout report. codecov/codecov-action@v6's auto-discovery requires coverage.xml (or another known format) and was logging: warning -- coverage.py is not installed or can't be found. info -- Found 0 coverage files to report Error: No coverage reports found. Add `uv run coverage xml` after the report step and pin `files: ./coverage.xml` on the action so it picks the XML up reliably without re-invoking coverage outside uv's venv. coverage.xml is already gitignored. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Match the "above baseline" stance taken on aarch64 (-march=armv8.2-a). v2 enables SSE3/SSE4.1/SSE4.2/popcnt — the Nehalem 2008 feature set — which is universal in anything capable of installing a current Linux distro and matters for ITK/numpy-adjacent inner loops. Consumers needing to target older hardware or a higher microarch level can override via CXXFLAGS at build time. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Lets ITK's templated hot paths (Hausdorff, displacement-field inversion, resample) inline into the pybind11 module instead of crossing a static- library function-call boundary. Cold rebuild on macOS arm64 + Clang took ~1m25s and all 71 tests pass. Linux GCC + the cross-platform wheel matrix get to vet this on CI; CheckIPOSupported gates it on toolchain support so anything that does not advertise IPO falls back transparently. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Reverts c02ea8a. Holding off on LTO for now. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## main #16 +/- ##
===========================================
+ Coverage 70.05% 83.54% +13.48%
===========================================
Files 7 9 +2
Lines 551 632 +81
===========================================
+ Hits 386 528 +142
+ Misses 165 104 -61 ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
Build native manylinux_aarch64 wheels on the GHA arm runner alongside the existing x86_64 and macOS universal2 builds. Switch the linux cibuildwheel archs to "auto" so each runner builds its native arch. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Removes the standalone test job that gated the wheel matrix and sdist build. Coverage instrumentation + Codecov upload move into sdist-build (also a single ubuntu-latest job with an editable install). The wheel matrix now starts on push instead of waiting for the dev test, and the per-cell wheel test step continues to exercise the built artifact. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Switch the Docker job to a per-platform matrix (linux/amd64 on ubuntu-latest, linux/arm64 on ubuntu-24.04-arm) so each arch builds natively — no QEMU emulation. The build now runs on every push for Dockerfile rot detection; only the push-by-digest + manifest merge are gated on release. A new docker-publish job downloads the digests and combines them into a multi-arch manifest tagged with semver/latest. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Picks up newer build-essential/cmake/glibc. All apt packages used here (build-essential, cmake, curl, git, unzip, ca-certificates) are available on 24.04; Python still comes from uv-managed so the base bump doesn't change the runtime Python. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
26.04 LTS shipped 2026-04-15; both amd64 and arm64 manifests are on Docker Hub. Supersedes the 24.04 bump from acd157f. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Replaces vanandrew@wustl.edu in the four places it appears: Dockerfile maintainer LABEL, pyproject.toml authors, README authors line, and the CLI epilog signature in warpkit/scripts/__init__.py. Vahdeta Suljic's @wustl.edu address is unchanged. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Runs ruff check, ruff format --check, and pyright on every push and PR, mirroring the pre-commit hooks. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
uv run --with pyright (and --with ruff) layers an overlay env on top of the project venv that reinstalls warpkit from source — so each invocation re-ran the CMake build. uvx runs the tool in isolation; pyright still reads the populated .venv via venvPath/venv in pyproject.toml. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Summary
Replaces the Julia-backed ROMEO phase-unwrapping path with a self-contained C++17 port (header-only under
include/romeo/), then layers on the polish that fell out of the migration: modernized tooling, typed CLI, type stubs for the compiled extension, comprehensive tests, and docs/conventions.Core change — Julia → C++
unwrap_3d/unwrap_4d/voxelqualityported from ROMEO.jl v1.0.0. Self-contained, no runtime Julia dependency. Plan and provenance documented in notes/ROMEO_port_plan.md.CMakeLists.txt,setup.py,Dockerfile, GitHub Actions workflow, cibuildwheel, conda recipe (the conda recipe was deleted entirely — it only existed to bootstrap Julia). macOS wheels re-enabled now that Julia isn't blocking.Tooling modernization
TEs,TE0,X,Y,Wetc.) — ruff'sN803/N806selected with no per-file ignore.X | None,list[T],collections.abcimports).commit-msggitmoji enforcer (scripts/check-gitmoji.sh).default_install_hook_types: [pre-commit, commit-msg]sopre-commit installwires both stages automatically.Type stubs
warpkit/warpkit_cpp.pyigenerated bypybind11-stubgen,warpkit/py.typedmarker shipped viaMANIFEST.in+[tool.setuptools.package-data]. Downstream consumers see proper types for the compiled extension.scripts/regen-stub.shwraps stubgen + thenumpy.bool→numpy.bool_rewrite + ruff fix-up so regenerations stay clean.# type: ignoreblanket suppressions on cpp imports dropped; pre-existing pyright debt cleaned up (no remainingreportArgumentType/reportAssignmentType/reportReturnTypedemotions).CLI improvements
medic:--out-prefixnow required. Acquisition parameters can come from BIDS sidecars (--metadata) or be passed directly (--TEs,--total-readout-time,--phase-encoding-direction); the two are mutually exclusive. All long flags switched to dash form (--out-prefix,--total-readout-time, etc.) while keeping--TEsfor MR convention.extract_field_from_maps: dash-form flags.--version.CI / cibuildwheel
*musllinux*and free-threaded builds (cp313t-*,cp314t-*) — the latter was producing twocp314wheels and breaking the test-wheel install step's glob.-march=x86-64-v2baseline on x86_64 wheels (SSE3/SSE4.2/POPCNT — nov3aggression, pre-Haswell CPUs would SIGILL).ubuntu-24.04-armGHA runner alongside x86_64 and macOS universal2. Linuxarchs = ["auto"]so each runner builds its native arch — tests exercise the aarch64 wheel on real hardware (no QEMU).testjob that ran the dev install + pytest before the wheel matrix has been removed; the wheel matrix and sdist+coverage now run in parallel from the start of each push. Coverage instrumentation + Codecov upload moved intosdist-build(still ubuntu-latest, still editable install). Per-cell wheel test step continues to exercise the built artifact.Docker
linux/amd64+linux/arm64) built via per-platform matrix on native runners (ubuntu-latestandubuntu-24.04-arm) — no QEMU emulation. Distributed-build → digest-export →docker buildx imagetools createmanifest-merge pattern.release.published. Newdocker-publishjob handles the manifest merge under thereleaseenvironment.Tests
tests/test_romeo.py(port-validation goldens + property tests against ROMEO.jl's vendored Phase.nii/Mag.nii, MIT-attributed undertests/data/romeo/).tests/test_unwrap.py(reject_outliers,compute_offsetwith known 2π shift recovery).tests/test_scripts.py(CLI argparse paths for both scripts).test_utilities.pycovers normalize, rescale_phase, mask helpers, orientation transforms, fmap↔dmap round trips parameterized over all 6 PE directions, displacement field inversion, Hausdorff identity/symmetry, convert_warp validation.test_distortion.pycovers affine/shape mismatch + None-affine paths.test_concurrency.pycovers DummyExecutor + all three executor backends.Repo conventions
CLAUDE.mddocuments layout, dev workflow, conventions (lowercase naming, no Julia, modern Python, no blanket type-ignore), gitmoji commit/PR style with shortcode table, and CI specifics.Test plan
uv sync --group dev --config-setting editable_mode=strictsucceeds on a machine with no Julia installeduv run pytest -q→ 71 passeduv run --with ruff ruff check→ cleanuv run --with ruff ruff format --check→ cleanuv run --with pyright pyright→ 0 errorsuv run pre-commit run --all-files→ all hooks pass (ruff check, ruff format, pyright)docker build .succeeds without fetching Juliamedic --helpagainst the installed entrypoint shows the new dash-form flags + either-or acquisition arg structuregrep -ri julia --exclude-dir=.venv --exclude-dir=tests/datareturns only algorithm-provenance references in C++ headers (documentation, not a runtime dep)ubuntu-24.04-arm) for Python 3.11–3.14 — added in 4651f99, first run pendingsdist-buildlinux/amd64andlinux/arm64build on every PR push; manifest publish only fires onrelease.published🤖 Generated with Claude Code