Statistical Stretch

Take linear data to a non-linear display stretch by placing the background median exactly where you ask — analytically, in closed form. No iterative midtone search, no guess-and-check: the same inputs always produce the same result, bit for bit.

1. Using the tool

Adjust the controls, use Preview (or enable Auto-refresh preview) to see the result, then Apply. The preview supports zoom/pan; Clip stats reports how many pixels sit at or below the black point.

ControlWhat it does
Target medianThe background level you want after the stretch (0.001–0.990, default 0.25). It is placed exactly — see §2.4.
Black point σHow many robust sigmas below the median to set the black point. Higher = darker background / more contrast (can crush faint dust); lower = preserves faint background. Start ~2.7–5.0.
No black clippingUse the image minimum as the black point instead (old Stat-Stretch behaviour) — preserves faint background but can look flatter.
Linked channelsOn: one black point/median for all of RGB (keeps colour balance). Off: each channel stretched independently (removes a colour cast, can shift balance).
Luminance-only (+ Mode, Luma blend)Stretch only the luminance and re-apply it to the original colour by ratio (preserves/boosts saturation). The blend slider mixes a normal linked stretch (0.00) with the luma result (1.00).
HDR highlight compress (Amount, Knee)Soft-compress highlights after the stretch to tame cores and bright stars. Use lightly.
Curves boost (Strength)An optional gentle S-curve for extra contrast, pinned at black/median/white.
Normalize to [0..1]Rescale so the brightest pixel is 1.0.
Store / Recall 1–3Snapshot and restore full control sets for quick A/B comparison.
Grip (bottom-left)Drag to the canvas to mint a shortcut with these settings, or drop on an image to apply headlessly.

2. Technical details

Per channel the data is black-point subtracted, rescaled to [0,1], then passed through one closed-form transfer curve whose single parameter is solved analytically so the median lands on your target.

2.1 The midtones-transfer family

The stretch belongs to a one-parameter midtones-transfer family. For a pixel x ∈ [0,1] and a real-valued amount a:

f_a(x)=\frac{3^{a}\,x}{(3^{a}-1)\,x+1}

Here a analytically continues the effect of applying a midtones operator a times (the base 3 sets the per-unit strength). It is pinned at f_a(0)=0 and f_a(1)=1, and a=0 is the identity. This is not an arcsinh stretch — it is an analytic midtones transfer aimed precisely at a target median.

2.2 The amount that lands the median — solved, not searched

Given the measured (rescaled) median M and your requested target T, the amount that maps M → T is closed-form:

a=\frac{\ln\left(\dfrac{T}{1-T}\cdot\dfrac{1-M}{M}\right)}{\ln 3}

Because both M (measured) and T (chosen) are known before a single pixel is transformed, a is computed once in closed form — deterministic, perfectly reproducible, with no iterative midtone search and no recursive convergence. The median lands where you asked, the first time.

2.3 Black point (robust)

The background noise is estimated robustly from the lower half of the distribution (values at or below the median), so stars and bright structure can't inflate it:

\mathrm{noise}=1.4826\cdot\mathrm{MAD}\left(x\leq\mathrm{median}(x)\right)

The black point is then placed a chosen number of sigmas below the median (or, with No black clipping, at the image minimum):

b=\mathrm{clip}\left(\mathrm{median}(x)-\sigma\cdot\mathrm{noise},\;\min(x),\;0.99\right)

2.4 Rescale and exact median placement

Pixels are shifted/rescaled so the black point maps to 0 and 1 stays at 1, and the median is carried into the same space:

r=\frac{x-b}{1-b}\qquad M=\frac{\mathrm{median}-b}{1-b}

The evaluated kernel (the same family as §2.1, with the solved amount folded in) is:

S(r)=\frac{(M-1)\,T\,r}{M\,(T+r-1)-T\,r}

It is pinned through three points — and the middle one is the whole point of the tool:

S(0) = 0 (black stays black) S(1) = 1 (white stays white) S(M) = T (the measured median lands on your target, exactly)

Why S(M)=T holds exactly, by construction:

at r = M: numerator = (M - 1)·T·M denominator = M·(T + M - 1) - T·M = M² - M = M·(M - 1) S(M) = (M - 1)·T·M / [ M·(M - 1) ] = T

2.5 Colour handling

2.6 Optional post-steps

Applied, in order, after the core stretch:

2.7 Notes