Skip to content

feat: add the augmentation registry - #67

Open
Hendrik-code wants to merge 1 commit into
hm/one-class-per-augmentationfrom
hm/registry-core
Open

feat: add the augmentation registry#67
Hendrik-code wants to merge 1 commit into
hm/one-class-per-augmentationfrom
hm/registry-core

Conversation

@Hendrik-code

Copy link
Copy Markdown
Collaborator

Stacked on #66. smauglab/registry.py, with no consumers yet — reviewable on its own before anything depends on it.

It answers four questions that today are answered by reading an if ladder: what augmentations exist per backend, which class a config key maps to, what parameters that class accepts, and which backends implement a given concept.

Accepted parameters come from inspect.signature, so there is no second schema to drift out of step with the constructors. Registering a class whose __init__ ends in **kwargs without declaring forwards_to is an error — signature-based validation would otherwise accept anything.

The module is stdlib-only and imports nothing from smauglab.transforms at module scope. That keeps the import graph acyclic (transform modules import register from here) and lets a CLI answer "what exists?" without paying for torch.

On pipeline order — the design question you asked about

Each augmentation's position is data the ladders held implicitly, so the registry has to hold it explicitly. Config key order cannot decide it: the two have never agreed, and honouring the file would silently reorder every pipeline the moment someone tidied a config. (A config will be able to opt into key order — pipeline.order — when config.py lands.)

Per your call, it's one PIPELINE_ORDER tuple per backend, not an order= integer on each @register:

Backend.GPU: (
    "RandomFlipTransformGPU",              # FlipTransform
    "RandomAffineGPU",                     # AffineTransform
    "RandomSynthSegGPU",                   # SynthSeg
    ...
)

The question a reviewer needs to answer is "does the new pipeline run things in the same sequence as the old one?" Against a single list that's a diff against the ladder it came from; against integers spread over eight files it isn't. Every GPU entry carries the ladder key it was derived from as a comment, so the correspondence is checkable line by line against gpu/transforms.py.

Registering a class absent from its backend's tuple is an error, so the table cannot fall out of date.

One entry needs a judgement call: RandomLaplaceGPU has no ladder key. The GPU Laplace kernel was reachable only by setting kernel_type="Laplace" on ScharrTransform, so its position is a new choice — placed next to its sibling. Worth a look.

isolated(order=...)

isolated() without an argument turns the position check off for the block, which is what most tests want — they register throwaway classes whose pipeline position isn't under test. With an explicit table, registration is checked against it, which is how the check itself gets covered.

Also in here

smauglab stops being a PEP 420 namespace package: every directory gets an __init__.py. The registry needs a deterministic import-time population point, and a namespace package lets a stray smauglab/ elsewhere on sys.path silently merge into this one. namespace_packages / explicit_package_bases come out of [tool.mypy] accordingly.

Testing

unit_tests/test_registry.py — 26 tests against synthetic transform classes, deliberately not the real ones (those get registered in the next PR; these are about the mechanism). Covers the registration invariants, signature-derived parameter validation, the "did you mean" suggestions, and the coverage matrix.

Full suite: 131 passed, 237 subtests. The 24 seeded config digests are unchanged — nothing imports the registry yet. ruff and mypy clean.

smauglab/registry.py, with no consumers yet -- reviewable on its own before
anything depends on it.

It answers four questions that are currently answered by reading an `if` ladder:
what augmentations exist per backend, which class a config key maps to, what
parameters that class accepts, and which backends implement a given concept.
Accepted parameters come from inspect.signature, so there is no second schema to
drift out of step with the constructors; registering a class whose __init__ ends
in **kwargs without declaring forwards_to is an error, because signature-based
validation would otherwise accept anything.

The module is stdlib-only and imports nothing from smauglab.transforms at module
scope. That keeps the import graph acyclic -- transform modules import register
from here -- and lets a CLI answer "what exists?" without paying for torch.

On pipeline order. Each augmentation's position is data the ladders held
implicitly, so the registry has to hold it explicitly; config key order cannot
decide it, because the two have never agreed and honouring the file would
silently reorder every pipeline the moment someone tidied a config.

It is one PIPELINE_ORDER tuple per backend rather than an `order=` integer on
each @register. The question a reviewer needs to answer is "does the new pipeline
run things in the same sequence as the old one?", and against a single list that
is a diff against the ladder it was derived from. Against integers spread over
eight files it is not. Each GPU entry carries the ladder key it came from as a
comment, so the correspondence is checkable line by line. Registering a class
absent from its backend's tuple is an error, so the table cannot fall out of
date.

RandomLaplaceGPU is the one GPU entry with no ladder key: the GPU Laplace kernel
was reachable only by setting kernel_type="Laplace" on ScharrTransform, so its
position is a new choice, placed next to its sibling.

isolated() gains an `order` argument. Without one it turns the position check off
for the block, which is what most tests want -- they register throwaway classes
whose pipeline position is not the thing under test. With one, registration is
checked against it, which is how the check itself is covered.

Also: smauglab stops being a PEP 420 namespace package. Every directory gets an
__init__.py, because the registry needs a deterministic import-time population
point and because a namespace package lets a stray smauglab/ elsewhere on
sys.path silently merge into this one. namespace_packages/explicit_package_bases
come out of [tool.mypy] accordingly.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Copilot AI lite review requested due to automatic review settings August 20, 2026 13:08

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants