Skip to content

feat(preprocessing): Pillow resize and augmentation drivers - #793

Merged
JArmandoAnaya merged 2 commits into
mainfrom
feat/preprocessing-p2-drivers
Aug 26, 2026
Merged

feat(preprocessing): Pillow resize and augmentation drivers#793
JArmandoAnaya merged 2 commits into
mainfrom
feat/preprocessing-p2-drivers

Conversation

@JArmandoAnaya

Copy link
Copy Markdown
Contributor

Position 2 of the pre-processing epic (#785): the pixel side of a recipe, as a package outside the kernel with its own entry-point group.

What changed

  • New src/visionset/preprocessing/ package. registry.py scans the visionset.preprocessing entry-point group on every call, keeps what satisfies the PreprocessingDriver port and keys it by step kind; drivers(), pick(installed, kind), driver_for(installed, step) and driver(kind) mirror the format registry's shape. A kind no installed driver applies is refused with PreprocessingDriverNotFound, whose message lists the installed kinds.
  • pillow/__init__.py ships the two built-in drivers. PillowResizeDriver applies the EXIF turn first — ingest's PillowImageProcessor bakes the orientation in before it measures, so the manifest's dimensions already describe the oriented picture — then stretches with Image.resize (LANCZOS when the pixel count shrinks, BICUBIC when it grows) or letterboxes by pasting the resized content at the kernel's letterbox_fit offsets onto a pad_value canvas. PillowAugmentDriver mirrors, brightens then contrasts, or turns by quarter turns, reading every draw from the kernel's hflip_applied, brightness_contrast_factors and rot90_quarter_turns; variant 0 comes back untouched apart from orientation and re-encoding.
  • PreprocessingDriverNotFound in kernel/errors.py, mapped to 500 PREPROCESSING_DRIVER_NOT_FOUND with an exposed message, with its rows in docs/content/api.md and tests/server/test_errors.py. No route raises it yet; the composition points arrive in the next position.
  • pyproject.toml: the visionset.preprocessing entry-point group with pillow-resize and pillow-augment, and import-linter contract 1's forbidden list gains visionset.preprocessing. The contract is tightened, never relaxed; the fresh-process purity probe now also asserts neither visionset.formats nor visionset.preprocessing is loaded by a kernel import.

Re-encode rules

Bytes come back in the source's own format: a JPEG as a JPEG at quality 95 with its chroma subsampling kept (read off the source with get_sampling before the transform, since the resized image is no longer a JPEG Pillow can "keep" from); a PNG as a lossless PNG; anything else Pillow can open as a PNG. No metadata travels — the EXIF that named the orientation is dropped because the orientation is now in the pixels, and no ICC profile or text chunk is copied. media_type(bytes) in the same module answers image/jpeg or image/png for the preview route to return. Palette, bilevel, 16-bit and CMYK sources are converted to RGB (or RGBA when transparent) before any transform, because Pillow silently resamples a palette image nearest-neighbour.

Determinism scope

The same seed over the same bytes gives identical bytes on one machine with one Pillow and one set of codecs; the tests assert exactly that and nothing wider. Byte stability across environments is not promised, as the epic's decisions say.

Found, not fixed

  • letterbox_fit has no floor of one pixel on the content size, so an extreme aspect ratio — a 1×200 source onto a 32×32 canvas — rounds one side to 0. The driver refuses that case with UnsupportedMedia before Pillow raises a bare ValueError, and the geometry transform still computes a zero-width strip for the same case. The single fix is a floor inside letterbox_fit, which this position does not touch; the kernel domain is the previous position's surface.
  • The stretch resampling rule is decided on total pixel count. A stretch that shrinks one axis and grows the other is one Image.resize call, so it takes one filter; the dispatch's rule names downscaling and upscaling without saying which axis decides.

Stack

Position 2 of the pre-processing stack (#785); based on feat/preprocessing-p1-domain (PR #791).

Checks

Targeted, per commit: uv run pytest tests/preprocessing tests/architecture tests/kernel tests/server/test_errors.py tests/formats/test_registry.py (2119 passed, 6 skipped), uv run lint-imports (4 kept, 0 broken), uv run mypy src/visionset (no issues in 183 files), uv run ruff check . and uv run ruff format.

Full local gate on the final tree, verbatim:

4610 passed, 33 skipped in 38.22s
...
All checks passed.

check.sh: PASSED  ran=python,frontend,generated,browser  skipped=docs

Docs group, verbatim:

check.sh: PASSED  ran=docs  skipped=python,frontend,generated,browser

@JArmandoAnaya
JArmandoAnaya force-pushed the feat/preprocessing-p1-domain branch 4 times, most recently from ba0f33a to 9d324ff Compare August 26, 2026 11:38
Base automatically changed from feat/preprocessing-p1-domain to main August 26, 2026 11:44
PreprocessingDriverNotFound, with its server rule, its api.md rows and its
exposure entry. No route raises it yet; the pre-processing registry does.
A new visionset.preprocessing package outside the kernel: a registry over the
visionset.preprocessing entry-point group, keyed by step kind, and the two
built-in Pillow drivers. Resize orients, then stretches or letterboxes onto a
pad_value canvas at the kernel's letterbox_fit numbers; augmentation reads
every draw from the kernel's seed functions. Bytes are re-encoded in the
source format with no metadata. Import-linter contract 1 now also forbids the
kernel from importing the package.
@JArmandoAnaya
JArmandoAnaya force-pushed the feat/preprocessing-p2-drivers branch from 3962adb to fa747d3 Compare August 26, 2026 11:44
@JArmandoAnaya
JArmandoAnaya merged commit bae6548 into main Aug 26, 2026
15 checks passed
@JArmandoAnaya
JArmandoAnaya deleted the feat/preprocessing-p2-drivers branch August 26, 2026 11:50
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.

1 participant