Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
39 commits
Select commit Hold shift + click to select a range
629a41a
feat(data): one Resample — a stored transform decoded and streamed
vboussot Aug 5, 2026
f98bd4f
fix(data): the correctness pass over the streamed resample path
vboussot Aug 5, 2026
0bc7775
feat(transform): route by cost, and a console cut to decisions
vboussot Aug 5, 2026
b970688
refactor(data): one home per rule; docs, tests and changelog follow
vboussot Aug 5, 2026
f49a00f
fix(data): what the pre-release review caught
vboussot Aug 5, 2026
5907865
feat(impact-reg): give the CLI its working directory, as the other ap…
vboussot Aug 4, 2026
f6b2684
fix(impact-reg): forward the workspace to every nested konfai-apps co…
vboussot Aug 4, 2026
a511cbc
feat(impact-reg): let a preset declare only its field, and derive the…
vboussot Aug 4, 2026
f647c56
refactor(impact-reg): read and write through Dataset, not a two-forma…
vboussot Aug 4, 2026
c090453
refactor(impact-reg): the preset owes a field, and nothing else
vboussot Aug 4, 2026
6bf61bd
fix(impact-reg): count the cases konfai-apps produced, not the argume…
vboussot Aug 4, 2026
668ca09
fix(impact-reg): expand eval's inputs the way register's are
vboussot Aug 5, 2026
e8b18e5
refactor(impact-reg): read through Dataset by building the dataset it…
vboussot Aug 5, 2026
ef702f6
feat(impact-reg): let a caller ask for the fields and nothing else
vboussot Aug 5, 2026
3ac59c3
feat(data): a reference that follows the case
vboussot Aug 5, 2026
5df9455
feat(data): Std reduction and Magnitude transform
vboussot Aug 5, 2026
c6ca8e4
feat(api): the workflows as Python callables
vboussot Aug 5, 2026
79ef557
feat(impact-reg): every derivation through konfai's own engine
vboussot Aug 5, 2026
a7b429f
docs(mcp): plan_transform's verdict list gains LOAD
vboussot Aug 5, 2026
59fa477
feat(data): size a field's windows from the field itself at run
vboussot Aug 5, 2026
9c94f89
feat(data): drop max_displacement -- the field itself is the bound
vboussot Aug 6, 2026
7a809b3
feat(data)!: one Resample, no other spelling
vboussot Aug 6, 2026
d0ae5ab
fix(impact-reg): one staging root per group, so mixed forms stay read…
vboussot Aug 6, 2026
3a2458c
perf(impact-reg): fill Transform.h5 region by region
vboussot Aug 6, 2026
b7a39f2
docs: migrate the gallery and say the old Resample names are gone
vboussot Aug 6, 2026
b0ff7f2
feat(data): an ITK transform file is a Dataset backend
vboussot Aug 6, 2026
05cfac1
refactor(impact-reg): Transform.h5 is a Write like any other
vboussot Aug 6, 2026
d4d3083
refactor(impact-reg): a registration engine returns its field, not an…
vboussot Aug 6, 2026
be3bdda
feat(data)!: drop the recorded field bound
vboussot Aug 6, 2026
4b0f669
feat(data): the itktransform backend reads by regions
vboussot Aug 6, 2026
3210e80
docs(data): the backend table says what streams, and the transform ba…
vboussot Aug 6, 2026
20489a9
feat(data): stream uncompressed NIfTI writes, like the reads
vboussot Aug 6, 2026
2f61718
docs: the Python page; the changelog is written at the tag
vboussot Aug 6, 2026
995fc4b
feat(impact-reg): the preset names its output, and nothing else is de…
vboussot Aug 6, 2026
9b93e0c
fix(data)!: a backend token is not an extension, and h5py is not opti…
vboussot Aug 6, 2026
4337e9c
fix(data): a text transform and a stepped region read through the bac…
vboussot Aug 6, 2026
f7c8c0e
fix(api): spell numpy scalars, and copy a caller's config file
vboussot Aug 6, 2026
a77b59b
fix(data): the backend rewrite lands on .h5, and Std prices its buffers
vboussot Aug 6, 2026
95bc466
fix(impact-reg): numeric case order past P999, and the Moved name guard
vboussot Aug 6, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -132,7 +132,7 @@ Use to PACKAGE a model trained in the current session (the train-from-scratch br

### `plan_transform`

Use BEFORE run_transform, always: it is the dry run, and it writes no data. This plans every (case, chain) from the session Transform.yml. The plan is a measurement, not an estimate -- it opens and removes a real region-write on each destination -- so its verdict is the one the run will act on: STREAM (bounded memory), WHOLE-VOLUME (the case is assembled whole, with the stage that refused it named), SKIP (already written), REDUCE, REFUSED. Outputs: {ok, report, verdict_counts, budget, needs_attention[], over_budget[]}. A non-empty over_budget means run_transform would refuse before writing anything. Next: fix what needs_attention names, or run_transform.
Use BEFORE run_transform, always: it is the dry run, and it produces none of the deliverable. This plans every (case, chain) from the session Transform.yml. The plan is a measurement, not an estimate -- it opens and removes a real region-write on each destination -- so its verdict is the one the run will act on: STREAM (bounded memory), LOAD (the case fits the budget and streaming would reread the source past its worth -- a cost choice, not a fallback), WHOLE-VOLUME (the case is assembled whole, with the stage that refused it named), SKIP (already written), REDUCE, REFUSED. That probe TOUCHES the output locations, which are the user's own stores: an entry is created and removed, and a single-file store (h5) is created if it did not exist. Outputs: {ok, config_path, world_size, report, verdict_counts, budget, budget_bytes, needs_attention[], over_budget[]}. A non-empty over_budget means run_transform would refuse before writing anything; needs_attention lists at most 50 entries, so trust verdict_counts for the totals. Next: fix what needs_attention names, or run_transform.

### `prepare_dataset_aliases`

Expand Down
4 changes: 2 additions & 2 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -61,10 +61,10 @@ Every extension point is **"subclass a base, reference it by classpath in YAML"*
- **Model:** subclass `network.Network`, build the graph in `__init__` via `add_module`. Reference `classpath: module.MyNet`, a local `Model:MyNet`, a `.yml`, or `default|<Name>.yml` for the shipped catalog.
- **Pretrained weights:** `utils/pretrained.py:transfer_weights_by_execution_order` pairs weighted leaves in forward-execution order (no key map). It fills **every** target tensor or raises — a tensor held by a parent module (`torch.nn.MultiheadAttention` owns `in_proj_weight` beside its `out_proj` child) or by a submodule the forward skips cannot be paired. Unreached *source* branches (nnU-Net deep-supervision heads) are ignored on purpose.
- **Loss / metric:** subclass `metric.measure.Criterion`; `forward` returns a `Tensor` (loss) or a `(value, dict)` tuple (metric — consumers `isinstance`-branch). Attach under `outputs_criterions`/`metrics` to a **named module output**. Optional-dep criteria import lazily via `_require_optional(...)` and raise an actionable `MeasureError` — never a bare top-level import.
- **Transform:** subclass `data.transform.Transform`; implement `__call__` **and** `transform_shape()` (must predict the output spatial shape *exactly* — patch planning depends on it). Declare `patch_locality()` (a `LocalityKind`: `POINTWISE`/`HALO`/`ORIENTATION`/`CROP`/`GLOBAL_STAT`/`RESCALE`/`SLAB`/`WHOLE_VOLUME`) or the base default makes it `WHOLE_VOLUME`; a `WHOLE_VOLUME` that is a property of the *configuration* rather than of the stage must carry `reason=`, which the plan prints — without it the reader has nothing to change. Pair `inverse()` if `apply_inverse`; override `prepare(konfai_args)` only when the stage builds a sub-object from configuration of its own (`Reduce` → its operator).
- **Transform:** subclass `data.transform.Transform`; implement `__call__` **and** `transform_shape()` (must predict the output spatial shape *exactly* — patch planning depends on it). Declare `patch_locality()` (a `LocalityKind`: `POINTWISE`/`HALO`/`ORIENTATION`/`CROP`/`GLOBAL_STAT`/`REGRID`/`SLAB`/`WHOLE_VOLUME`) or the base default makes it `WHOLE_VOLUME`; a `WHOLE_VOLUME` that is a property of the *configuration* rather than of the stage must carry `reason=`, which the plan prints — without it the reader has nothing to change. Pair `inverse()` if `apply_inverse`; override `prepare(konfai_args)` only when the stage builds a sub-object from configuration of its own (`Reduce` → its operator).
- **Augmentation:** subclass `data.augmentation.DataAugmentation`; `_state_init` (sample params per case index) + `_compute` (apply lazily). Only `Mask`/`Permute` may change shape. A draw is also a **chain stage**: `TransformLoader` resolves a bare name against `data.transform` first and `data.augmentation` second, so a `transforms:` block may interleave draws and transforms — which is how TRANSFORM declares per-copy draws after an `Expand`.
- **Reduction:** subclass `data.reduction.Reduction`; implement `__call__(list[Tensor]) -> Tensor` over the `[1, K, C, *spatial]` layout both engines hand over. Two consumers, one vocabulary: the predictor folds one case's copies (ensemble/TTA), `data.transform.Reduce` folds N **cases** into one. Declare `voxel_local = True` only if every output voxel reads the same voxel of each input (**a wrong `True` corrupts a streamed output** — the gate checks nothing else), `incremental = True` if `accumulate` can fold one at a time, and override `output_channels(channels, cases)` when the fold changes the channel count (`Concat` does). `Reduce` refuses a non-`voxel_local` operator outright.
- **Imaging format:** add a `Dataset.AbstractFile` backend, dispatch it in `File.__enter__`, register aliases in `SUPPORTED_EXTENSIONS`; import-guard the heavy lib.
- **Imaging format:** add a `Dataset.AbstractFile` backend, dispatch it in `File.__enter__`, register aliases in `SUPPORTED_EXTENSIONS` — or in `SUPPORTED_BACKEND_FORMATS` when the token is not a suffix any file carries (`:itktransform` writes `<group>.h5`), since only the extensions are probed on disk; import-guard the heavy lib and raise a `DatasetManagerError` naming the extra rather than degrading in silence.

**Classpaths:** a bare name (e.g. `Dice`) resolves inside that kind's package; `module:Class` imports *any* module — a local file (`Loss:MyWrapper`) or an installed library (`monai.losses:DiceLoss`, `torch:nn:L1Loss`). Model classpaths resolve against `konfai.models.python`. The pre-1.6.0 absolute form `konfai.models.<kind>.<file>:<Class>` still resolves via a rewrite + `DeprecationWarning`; new code uses the relative or `default|` form.

Expand Down
4 changes: 2 additions & 2 deletions apps/impact_reg/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ The CLI is organised into sub-commands, matching the registration workflow:

| Sub-command | Purpose |
|---|---|
| `register` | Register a moving image onto a fixed image with one or more presets. Several presets are ensembled (their displacement fields are averaged). Writes the moved image, the displacement field (`DVF`), the transform, and the per-preset fields (kept for `uncertainty`). |
| `register` | Register a moving image onto a fixed image with one or more presets. Several presets are ensembled (their displacement fields are averaged). Writes the transform under the name and in the form the preset declared, the moved image derived from it, and — with `--keep_dvf` — the per-preset fields (kept for `uncertainty`). |
| `eval` | Evaluate a registration on any subset of modalities — image (MAE), segmentation (Dice), landmarks (TRE). At least one modality is required. |
| `uncertainty` | Voxel-wise spread map from an ensemble of displacement fields. |

Expand All @@ -98,7 +98,7 @@ Evaluate a registration — any subset of modalities; the transform comes from a

```bash
impact-reg-konfai eval \
--transform ./Output/P000/Transform.h5 \
--transform ./Output/P000/DVF.mha \
-f fixed.nii.gz -m moving.nii.gz --mask roi.nii.gz \
--gt-fixed-seg fixed_seg.nii.gz --gt-moving-seg moving_seg.nii.gz \
--gt-fixed-fid fixed.fcsv --gt-moving-fid moving.fcsv \
Expand Down
46 changes: 46 additions & 0 deletions apps/impact_reg/impact_reg_konfai/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
"""

import argparse
import sys
from pathlib import Path

from impact_reg_konfai.impact_reg import ImpactRegKonfAIApp, get_available_presets
Expand All @@ -44,6 +45,24 @@ def _default_preset() -> str:
return presets[0]


def _add_tmp_dir(parser: argparse.ArgumentParser) -> None:
"""Add ``--tmp-dir``, the option every other KonfAI app CLI exposes through ``build_app_cli``.

Left unset the command stages under the system temporary directory, as before. It is worth naming
when that directory is the wrong medium: what is staged is volume-sized (the moved image and the
displacement field, written before being collected into ``--output``), so a tmpfs TMPDIR charges it
to RAM. Pointing this beside ``--output`` also puts the intermediates on the results' own filesystem.
"""
parser.add_argument(
"--tmp-dir",
"--tmp_dir",
dest="tmp_dir",
type=_paths,
default=None,
help="Directory for intermediates (default: the system temporary directory).",
)


def _add_device(parser: argparse.ArgumentParser, download: bool = True) -> None:
"""Add the shared device / verbosity / download options to a sub-parser."""
device = parser.add_mutually_exclusive_group()
Expand Down Expand Up @@ -108,7 +127,16 @@ def main() -> None:
help="Tune a preset parameter, forwarded to 'konfai-apps infer --set' (applies to every preset), "
"e.g. --set iterations=300 (repeatable).",
)
reg.add_argument(
"--fields-only",
"--fields_only",
dest="fields_only",
action="store_true",
help="Write the transforms only: skip the moved image, which is derived from them. For a "
"caller that composes the transform itself and would delete it.",
)
_add_device(reg)
_add_tmp_dir(reg)

# eval -------------------------------------------------------------------
ev = subparsers.add_parser(
Expand Down Expand Up @@ -145,6 +173,7 @@ def main() -> None:
)
ev.add_argument("-o", "--output", type=_paths, default=Path("./Output").resolve(), help="Output directory.")
_add_device(ev)
_add_tmp_dir(ev)

# uncertainty ------------------------------------------------------------
unc = subparsers.add_parser(
Expand All @@ -166,12 +195,25 @@ def main() -> None:
)
unc.add_argument("-o", "--output", type=_paths, default=Path("./Output").resolve(), help="Output directory.")
_add_device(unc)
_add_tmp_dir(unc)

args = parser.parse_args()
app = ImpactRegKonfAIApp(
download=getattr(args, "download", False), force_update=getattr(args, "force_update", False)
)

# konfai's Python API raises designed refusals (message + remedy); the CLI's job is to print
# them and exit 1 -- the same contract the konfai CLI itself offers.
from konfai.utils.errors import KonfAIError

try:
_dispatch(args, app, ev)
except KonfAIError as error:
print(str(error).strip(), file=sys.stderr)
sys.exit(1)


def _dispatch(args: argparse.Namespace, app: ImpactRegKonfAIApp, ev: argparse.ArgumentParser) -> None:
if args.command == "register":
gpu = [] if args.cpu is not None else args.gpu
app.register(
Expand All @@ -187,6 +229,8 @@ def main() -> None:
tta=args.tta,
keep_dvf=args.uncertainty,
config_overrides=args.config_overrides,
tmp_dir=args.tmp_dir,
fields_only=args.fields_only,
)

elif args.command == "eval":
Expand Down Expand Up @@ -214,6 +258,7 @@ def main() -> None:
gpu=gpu,
cpu=args.cpu,
quiet=args.quiet,
tmp_dir=args.tmp_dir,
)

elif args.command == "uncertainty":
Expand All @@ -225,6 +270,7 @@ def main() -> None:
gpu=gpu,
cpu=args.cpu,
quiet=args.quiet,
tmp_dir=args.tmp_dir,
)


Expand Down
Loading
Loading