CLAHE

Contrast-Limited Adaptive Histogram Equalization. Boosts local contrast by equalising the histogram of small overlapping tiles across the image instead of the frame as a whole, with a clip limit that prevents noise amplification in flat areas. In astrophotography: pulls out texture in dust lanes, loosens nebula filaments from their background, and reveals subtle brightness variation in galaxy arms without the blown highlights that straight histogram equalization would produce. SASpro's take wraps OpenCV's CLAHE implementation with a pixel-based tile-size slider (more intuitive than tile count), a global blend slider for strength control, document-mask integration, and live preview.

1. Opening the dialog

The active image window is the target — CLAHE runs on its luminance (L* channel in Lab for colour, intensity directly for mono) and the result replaces the source on Apply. The dialog opens with a live preview showing the effect at the default settings (clip = 2.0, tile = 128 px, blend = 100%). It's non-modal and stays open after Apply so you can run it on another document with the same (or edited) settings.

2. Clip Limit

Slider, 0.1–4.0, default 2.0. The maximum height any single histogram bin can reach within a tile before being clipped and its excess mass redistributed uniformly. This is what distinguishes CLAHE from plain adaptive histogram equalisation.

3. Tile Size (px)

Slider, 8–512 px, step 8, default 128. The size of each equalisation tile in image pixels, not tile count — which is how OpenCV itself takes the parameter. SASpro converts automatically (§ 6.2) so the slider behaves the same way regardless of image resolution: a 128 px tile on a 24 MP frame means the same thing as a 128 px tile on a 6 MP crop.

4. Blend Amount

Slider, 0–100%, default 100%. Final linear mix between the original image and the CLAHE-processed result:

This is cleaner than lowering clip limit to achieve the same visual strength, because clip limit also changes which features are enhanced; blend only changes how much.

5. Live preview

Every slider change triggers a 250 ms-debounced re-render of the preview so you can judge the result interactively without each tiny slider nudge triggering a full computation. The preview shows the final blended, mask-gated result — exactly what Apply will commit.

6. Zoom controls

Zoom In, Zoom Out, and Fit to Preview buttons sit above the Apply row. The preview view also supports cursor-anchored mouse-wheel and trackpad zoom with the standard SASpro behaviour. The preview auto-fits to the viewport on first open.

7. Masks

If the active document carries a mask, CLAHE runs on the whole image and then blends through the mask so only masked regions receive the effect, with original pixels preserved everywhere else. The global Blend Amount is applied first, then the mask gating on top; both compose cleanly (§ 6.5).

8. Apply / Reset / Cancel

Apply runs the final computation on the full-resolution original and commits the result to the document as an undoable edit named “CLAHE”. The three slider values are recorded in the preset so Replay Last Action and the preset grip reproduce the exact run. The dialog stays open so you can switch to another open image and apply with the same settings.

Reset returns the three sliders to their defaults (clip 2.0, tile 128 px, blend 100%) and shows the untouched original in the preview. Cancel discards everything.

9. Preset grip

The handle at the bottom-left of the dialog carries the current three slider values and can be dragged to the canvas to make a shortcut tile, or dropped directly on an image to apply those settings headlessly. Double-clicking the tile reopens this dialog pre-seeded with those values.

10. Technical details

All processing on float32 [0, 1]. Colour inputs converted to Lab for L*-only processing; mono processed directly.

10.1 CLAHE algorithm (OpenCV)

SASpro calls cv2.createCLAHE(clipLimit, tileGridSize) with the slider values. Per tile, OpenCV builds the local 256-bin histogram h(v), clips bins above the limit:

h'(v) \;=\; \min(h(v),\;C)

and redistributes the clipped excess uniformly across all bins so the histogram still integrates to the tile's pixel count:

h''(v) \;=\; h'(v) \;+\; \frac{E}{N}\,,\qquad E \;=\; \sum_{v}\max(0,\;h(v) - C)

The redistributed histogram's CDF, scaled to [0, 255], becomes the per-tile transform:

T(v) \;=\; \mathrm{round}\!\left(255 \cdot \frac{\sum_{u \leq v} h''(u)}{\sum_{u} h''(u)}\right)

and the output for a given pixel is the bilinear interpolation of the four nearest tile transforms, which keeps tile boundaries from showing as visible seams:

T_{\mathrm{eff}}(x,y) \;=\; \sum_{\mathrm{4 tiles}} w_k(x,y) \,T_k(v)

For colour inputs, SASpro converts the image to CIE Lab via OpenCV's COLOR_RGB2LAB, applies CLAHE only to the L* channel, and converts back — so chroma (a*, b*) is preserved and only luminance structure changes.

10.2 Pixel-based tile sizing

OpenCV's native parameter is tileGridSize = (n, n) — the number of tiles across each axis. That means a given n covers a totally different physical area on a 24 MP frame vs a 6 MP crop, which is a confusing user experience: “tile count 8” doesn't tell you how large each tile actually is. SASpro's slider instead takes the tile size in image pixels and derives the count from the image dimensions:

n \;=\; \mathrm{clip}\!\left(\mathrm{round}\!\left(\frac{\min(H,W)}{t_{\mathrm{px}}}\right),\;2,\;128\right)

so a 128 px slider setting always produces roughly 128 px tiles regardless of image resolution. The clamp to [2, 128] handles edge cases (tile sizes larger than the image, or small enough to blow past OpenCV's internal limits).

10.3 Backward-compatible preset decoding

Older SASpro presets stored tile count directly as "tile": N. The current decoder accepts both the new "tile_px": px and the legacy "tile": N keys, preferring the former when both are present. Older shortcut tiles continue to work with their tile-count interpretation; new ones get pixel-based sizing. The written preset metadata always uses the new key, so Replay Last Action after a seed-from-old-preset promotes the entry forward.

10.4 Blend

Global blend is a straight linear mix between the untouched input and the CLAHE output:

\mathrm{out} \;=\; (1 - \beta)\,\mathrm{orig} \;+\; \beta\,\mathrm{CLAHE}(\mathrm{orig})

applied before any mask gating. Because it rides on top of the same CLAHE result, the character of the effect (which features get enhanced, which are suppressed) stays locked across blend values — only the amplitude changes. This is a strictly better user experience than lowering the clip limit to weaken the effect, which would also redistribute mass differently and change which features win the equalisation.

10.5 Mask gating

If the active document carries a mask m ∈ [0, 1], it's resampled to the processed image's dimensions and used as a second blend:

\mathrm{out} \;=\; (1 - m)\,\mathrm{orig} \;+\; m\,\mathrm{out}_{\mathrm{blended}}

which composes with the global blend from § 10.4 so the final per-pixel effect strength is the product of β (global strength) and m (local shape). Set a 50% blend plus a half-transparent mask and the effect lands at 25% in the masked region, 0% outside. The order matters: global blend first, then mask gating, so the mask gates the already-blended result rather than a full-strength CLAHE output.

Credits. The core CLAHE algorithm is Karel Zuiderveld's “Contrast Limited Adaptive Histogram Equalisation” (Graphics Gems IV, 1994). The implementation used here is OpenCV's cv2.createCLAHE with its bilinear tile interpolation. SASpro's additions are the pixel-based tile-size slider with its automatic conversion to OpenCV's tile-count parameter, the global Blend Amount slider, the live-preview pipeline with its 250 ms debouncing, document-mask integration with the described global-then-mask compositing order, and the backward-compatible preset decoder.