Skip to content

Read a scalar CLAHE clip_limit as a range, matching albumentations - #1350

Open
adhavan18 wants to merge 4 commits into
roboflow:developfrom
adhavan18:fix/clahe-scalar-clip-limit
Open

Read a scalar CLAHE clip_limit as a range, matching albumentations#1350
adhavan18 wants to merge 4 commits into
roboflow:developfrom
adhavan18:fix/clahe-scalar-clip-limit

Conversation

@adhavan18

Copy link
Copy Markdown
Contributor

Fixes #1349.

The bug

Albumentations applies to_tuple(clip_limit, low=1), so a scalar v means the range (1, v) and it samples from it per call. _make_clahe routed the value through _as_range, which expands a scalar to the degenerate (v, v).

4.0 is the default on both sides, so this applied with no user configuration at all:

aug_config before after albumentations
{} (default) (4.0, 4.0) (1.0, 4.0) (1.0, 4.0)
{"clip_limit": 4.0} (4.0, 4.0) (1.0, 4.0) (1.0, 4.0)
{"clip_limit": 2.0} (2.0, 2.0) (1.0, 2.0) (1.0, 2.0)
{"clip_limit": (2.0, 6.0)} (2.0, 6.0) (2.0, 6.0) (2.0, 6.0)

Only the explicit-pair form agreed. Every scalar form, including the default, pinned the GPU path to maximum contrast enhancement on every sample while the CPU path varied it — a silent train/serve divergence with no warning, since from the code's point of view nothing was being collapsed.

This came in with #1277, which added the CLAHE mapping. Mine.

The fix

A dedicated _as_clahe_clip_limit rather than a special case inside _as_range: the helper is correct about what it does and is used by transforms whose scalar semantics really are (v, v). Changing it there would have fixed CLAHE and broken the others.

A pair is used as given, so only the scalar form changes.

Scope

I checked the other _as_range call sites for the same class of mismatch:

  • Sharpen (alpha) and GaussNoise (std_range) — albumentations rejects a scalar for both with ValueError, so there is no scalar form to disagree about. Safe.
  • GaussianBlur (sigma) — albumentations expands a scalar sigma_limit to (0, v), so the same gap exists, but only when a user passes a scalar; the configured default is already a pair. Left out of this PR deliberately — happy to fold it in here or do it separately, whichever you prefer.

Verification

Nine assertions, all five configured forms plus direct parity against albumentations:

ok  param=None       -> (1.0, 4.0)
ok  param=4.0        -> (1.0, 4.0)
ok  param=2.0        -> (1.0, 2.0)
ok  param=(1.0, 4.0) -> (1.0, 4.0)
ok  param=(2.0, 6.0) -> (2.0, 6.0)
--- parity with albumentations ---
ok  clip_limit=4.0        kornia=(1.0, 4.0) albu=(1.0, 4.0)
ok  clip_limit=2.0        kornia=(1.0, 2.0) albu=(1.0, 2.0)
ok  clip_limit=(2.0, 6.0) kornia=(2.0, 6.0) albu=(2.0, 6.0)

The three scalar cases fail on the parent commit:

FAIL param=None  -> (4.0, 4.0)  expected (1.0, 4.0)
FAIL param=4.0   -> (4.0, 4.0)  expected (1.0, 4.0)
FAIL param=2.0   -> (2.0, 2.0)  expected (1.0, 2.0)

test_clahe_maps_both_parameters still passes — grid_size and the explicit pair are unaffected.

ruff check and ruff format --check clean on both files. pytest cannot collect the module in my environment (transformers is missing BackboneConfigMixin, unrelated to this diff), so I exercised the factory directly; CI here will be the better check.

@codecov

codecov Bot commented Aug 17, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 86%. Comparing base (6674d85) to head (c2f8891).

Additional details and impacted files
@@           Coverage Diff           @@
##           develop   #1350   +/-   ##
=======================================
  Coverage       86%     86%           
=======================================
  Files          114     114           
  Lines        14880   14886    +6     
=======================================
+ Hits         12835   12841    +6     
  Misses        2045    2045           
🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

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.

Pull request overview

Fixes CLAHE scalar clip_limit handling so Kornia matches Albumentations.

Changes:

  • Adds dedicated CLAHE range normalization.
  • Adds scalar, pair, default, and backend-parity tests.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 3 comments.

File Description
src/rfdetr/datasets/kornia_transforms.py Normalizes scalar CLAHE limits to (1, value).
tests/datasets/test_kornia_transforms.py Tests normalization and backend parity.

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread tests/datasets/test_kornia_transforms.py Outdated
Comment thread tests/datasets/test_kornia_transforms.py Outdated
Comment thread src/rfdetr/datasets/kornia_transforms.py Outdated
@adhavan18

Copy link
Copy Markdown
Contributor Author

all three addressed in e01abf0. the first one is a real behaviour bug and the reviewer is right about the mechanism.

the one-element sequence

confirmed against the pinned albumentations before changing anything:

4.0            -> ACCEPTED, normalised to (1.0, 4.0)
(1.0, 4.0)     -> ACCEPTED, normalised to (1.0, 4.0)
[1.0, 4.0]     -> ACCEPTED, normalised to (1.0, 4.0)
[4.0]          -> REJECTED: ValueError: 2 validation errors for InitSchema
(4.0,)         -> REJECTED: ValueError: 2 validation errors for InitSchema

so [4.0] is refused on the CPU path, while this helper routed it through _as_range, got the degenerate (4.0, 4.0), and turned it into (1.0, 4.0). that is the same class of divergence the PR set out to remove, just pointing the other way: a config that trains on the GPU backend and raises on the CPU one.

_as_clahe_clip_limit now raises on any sequence that is not exactly two elements, and says why, naming the CPU backend so the message is actionable rather than just a type complaint. scalars are handled directly instead of borrowing _as_range, since the degenerate pair was only ever an intermediate step and it is what made the one-element case look scalar.

the two test conventions

  • annotated both parametrized arguments.
  • split test_clahe_clip_limit_matches_albumentations into three independent cases with ids (scalar-default-value, scalar, pair), so a disagreement on one value no longer hides the other two behind a single reported failure.
  • added test_clahe_rejects_sequences_that_albumentations_rejects, parametrized over [4.0], (4.0,) and a three-element sequence.

118 passed, ruff check and ruff format --check clean.

adhavan18 and others added 3 commits August 31, 2026 10:17
Albumentations applies to_tuple(clip_limit, low=1), so a scalar v means the
range (1, v) and it samples from it. _as_range expands a scalar to the
degenerate (v, v), so the Kornia path pinned every sample to maximum
contrast enhancement while the CPU path varied it.

4.0 is the default on both sides, so this applied to the default
configuration rather than only to unusual ones, and nothing was logged.

A pair is already a range and is used as given, so only the scalar form
changes. Introduced in roboflow#1277.

Fixes roboflow#1349
A one-element sequence is not a scalar. Albumentations validates clip_limit
as a float or an exact 2-tuple and raises on [4.0], while this helper read
it through _as_range and turned it into (1.0, 4.0). That accepted a config
the CPU backend refuses, which is the divergence the helper exists to
remove.

Also annotates the parametrized arguments and splits the cross-backend
comparison into independent cases, so one failure no longer hides the rest.
@adhavan18
adhavan18 force-pushed the fix/clahe-scalar-clip-limit branch from e65d1e9 to 6d2fbfc Compare August 31, 2026 04:48
@adhavan18

Copy link
Copy Markdown
Contributor Author

Bumping this since it's been quiet for a couple weeks — just rebased onto current develop, no conflicts. Happy to address anything if it needs another look.

@Borda

Borda commented Aug 31, 2026

Copy link
Copy Markdown
Member

Hi, apologies for delay, I was off last week but I have on my radar for later this week...

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.

CLAHE clip_limit: Kornia backend pins a scalar where Albumentations samples a range

3 participants