* fix(image): normalize batched input along the channel axis
normalize() advertises 4D (N, C, H, W) support via its num_channels
branch and the channel-count validation, but the actual math used
((image.T - mean) / std).T. Transpose reverses every axis, so on 4D
input the channels no longer line up with mean/std: it raises when
N != C and silently normalizes along the batch axis when N == C.
Reshape mean/std to broadcast on the real channel axis instead; the
(C, H, W) path is unchanged.
* test(image): cover channel-wise normalize for 3D and batched input
* refactor
---------
Co-authored-by: George Panchuk <george.panchuk@qdrant.tech>
* fix: pass (width, height) to Pillow in the Resize transform
`resize()` handed a tuple size straight to `PIL.Image.resize()`. fastembed
keeps sizes as (height, width) — `Transform.from_config` builds the tuple
as `(size["height"], size["width"])` — while Pillow takes (width, height),
so a non-square image processor configuration produced a transposed image:
Resize(size=(100, 200))(Image.new("RGB", (300, 300)))[0].size
# (100, 200), expected (200, 100)
Square sizes are unaffected, which is why this went unnoticed. The int
branch of `resize()` already emits Pillow order and is untouched, as are
`resize_ndarray()`'s callers, which pass (width, height) explicitly.
`Resize.__call__` is the only caller of this function and always supplies
fastembed's height-first order, so converting here is safe.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* tests: simplify tests
---------
Co-authored-by: George Panchuk <george.panchuk@qdrant.tech>