← Handbook

Local Contrast Enhancement

Increase tonal separation between nearby structures so real intermediate-scale detail that survived the main stretch, but remains visually compressed against its immediate surroundings, becomes easier to see. This is a finishing operation over already-captured tonal relationships, not a way to restore blur (deconvolution/sharpening) or recover clipped/never-captured signal.

schema 1 · revision 1

Recognize it

Intended output

Improved visibility of real local-scale structure while preserving background continuity, faint diffuse signal, star profiles/halos, highlight headroom, and color relationships. A no-op is a valid outcome -- local contrast is enhancement, not required calibration.

Required input state

What NOVA does

`seti_astro.clahe(clip_limit=2.0, tile_size=8)` loads the FITS through the shared normalizing loader, calls SASpro's `apply_clahe(..., tile_grid_size=(tile_size, tile_size))`, clips to [0,1], and saves. `tool_params.compute_clahe()` derives `clip_limit` from a P02 SNR-like proxy (clamped 1.0-5.0, capped lower for nebulae and boosted slightly for galaxies) and picks `tile_size=4` for sharp galaxies, else 8 -- these are engineering heuristics, not calibrated physical laws. PixInsight LocalHistogramEqualization (LHE) is the method alternative: `tool_params.compute_lhe()` picks kernel radius from roughly 1/12 of the shorter image dimension (clamped 32-128px), and slope limit by object type (1.5 nebula / 2.5 galaxy / 2.0 other), with amount fixed at 0.5. The PI path can also sample pre-LHE background for a post-operation anchor correction.

Use when

  • Meaningful structure survived the stretch but remains visually compressed relative to its immediate surroundings -- galaxy arms/dust lanes, nebular filaments/shells, compact planetary-nebula structure, or high-SNR starless detail.

Skip when

  • The actual problem is a residual gradient, the data are very noisy, stars dominate the field, smooth faint signal matters more than microcontrast, a previous sharpening/local-contrast pass already produced crunchy texture, mosaic seams/resampling artifacts are present, or bright-core dynamic range -- not local tonal separation -- is the actual problem.

Limits

  • More microcontrast is not recovered spatial resolution. Entropy, high-frequency power, generic sharpness, global variance, or a visually crunchier result can all improve while noise, ringing, seams, or interpolation artifacts simultaneously worsen -- none of these is a standalone success metric.
  • NOVA's `seti_astro.clahe()` docstring says `tile_size` is a tile size in pixels and that smaller values mean finer adaptation, but the wrapper passes `(tile_size, tile_size)` straight through as SASpro/OpenCV `tileGridSize` -- which is the number of tiles in the grid, not the pixel width of a tile. For a fixed image, (8,8) means more/smaller tiles and (4,4) means fewer/larger tiles, so `tool_params.compute_clahe()` choosing `tile_size=4` for supposedly "finer" galaxy detail is directionally opposite to current upstream OpenCV grid semantics. This is recorded as likely comment/adaptation semantic drift, version-bound until the exact pinned SASpro 1.18.0 source (not just current upstream) is checked.
  • `_load_fits()` performs a global min/max normalization to [0,1] before CLAHE runs, so a NOVA CLAHE result is not simply the original amplitude scale plus local equalization -- that preprocessing is part of the effective treatment and confounds a direct CLAHE-vs-PixInsight-LHE comparison unless controlled.
  • In the reviewed generic PixInsight dispatch (`experiments.py`, `_run_variant()`'s `fn_name == "lhe"` branch), only `lhe_amount` is forwarded into the PI call -- `lhe_kernel_r` and `lhe_slope_limit` are not. Separately, `_adapt_nonlinear_variants()` does compute and store adapted `lhe_kernel_r`/`lhe_slope_limit` into the candidate's own params dict. So the adapted radius/slope values are calculated but never actually reach the PixInsight call -- only the amount does. An LHE experiment that appears to vary radius or slope may not be exercising those settings at all.
  • Confirmed CLAHE candidates (`clahe_whisper`, `clahe_mild`, `clahe_standard`, `clahe_fine`) are parameter/scale probes within one CLAHE engine, not four independent algorithms. The ontology does declare a `none` control alongside them, but it does not currently dispatch as a real copy-through -- `_run_variant()` only recognizes a no-op via an explicit `engine == "none"` key or `fn is None`, and the ontology's candidate sets `fn: "none"` as a string, which falls through to a nonexistent `seti_astro.none` and returns `ok: False` (issue #669). Comparisons cannot currently rely on this declared baseline until that dispatch gap is fixed.
  • Current upstream SASpro CLAHE source converts through 8-bit CIELAB and applies OpenCV CLAHE to the L channel before converting back -- an 8-bit internal precision path, not a high-bit-depth luminance-curve equivalent. This is version-bound context from current upstream source, not confirmed against the exact pinned 1.18.0 install.

Scientific and aesthetic notes

  • A method comparison (CLAHE vs. PixInsight LHE) must predefine approximate feature scale and effective strength, verify actual grid semantics rather than trusting current comments, and evaluate on untouched test ROIs. If each engine is independently auto-tuned to its own best-looking result, that is a strategy comparison, not a controlled engine comparison.
  • A within-CLAHE clip-limit probe should hold parent FITS, normalization, grid semantics, mask, object metadata, and preview transform fixed and persist the effective post-adaptation clip limit and grid rather than just the candidate ID.
  • Human/AI perceptual assessment can legitimately judge whether structure reads as easier to see or looks artificial. It cannot prove recovered physical resolution or the astrophysical reality of newly visible faint texture.

Evidence and measurements

MeasurementsAcceptance criteria
  • Matched target-ROI local contrast on known structures (arm/inter-arm, filament/adjacent nebula, shell edge/interior).
  • Robust noise change in matched low-signal background ROIs.
  • Background/seam continuity at multiple spatial scales.
  • Unsaturated-star radial profiles, halo/core ratio, and headroom.
  • Channel-wise near-black/near-white pile-up and high percentiles.
  • Before/after difference images or multiscale residuals showing where the change actually occurred.
  • Structure that survived the stretch becomes easier to read without introducing noise crunch, halos, false filamentation, or seam amplification.
  • Effective clip limit and grid (not just candidate ID) are recorded, with grid semantics verified rather than assumed.
  • Entropy, high-frequency power, generic sharpness, or global standard deviation are never presented as standalone proof of benefit.
  • A CLAHE-vs-LHE result is labeled a strategy comparison unless normalization, feature scale, and mask state were actually controlled.
  • No claim relies on the ontology's declared `none` control until issue #669 confirms it dispatches as a real copy-through.

Cross-tool matrix

Tool EquivalenceExpected resultEvidence
novaExact replayImproved visibility of real intermediate-scale structure with background continuity, star profiles, and highlight headroom preserved.nova-clahe-source, none-control-dispatch-gap-clahe
pixinsightFunctional alternativeA local histogram remapping via a moving/circular neighborhood, matching the effective radius/slope/amount actually forwarded.nova-clahe-source
sasproSame engine, adapted hostLocal histogram-equalized luminance with clipped amplification, retained chroma, converted back to RGB.saspro-clahe-upstream-p24, nova-clahe-source
sirilFunctional alternativeA local tonal remapping via Siril's own local-contrast tooling.nova-clahe-source

NOVA Python pipeline SASpro CLAHE wrapper + PixInsight LHE dispatch; adaptive tile/clip/radius/slope heuristics

  1. Record effective clip limit, effective grid (both the tile-count tuple and the resulting approximate tile pixel size once grid semantics are verified), mask state, and input normalization -- candidate ID alone is not sufficient provenance.
  2. Do not present an LHE radius/slope experiment as testing those parameters until the generic-dispatch forwarding gap is fixed or the effective PI call arguments are directly confirmed.
  3. Do not rely on the ontology's declared `none` CLAHE control as a working baseline until issue #669 is fixed -- use a manual copied-input comparison in the meantime.

Controls and starting ranges

clahe_whisper / clahe_mild / clahe_standard
clip 1.0 / 1.5 / 2.0, tile grid (8,8) -- parameter probes
clahe_fine
clip 1.5, tile grid (4,4) -- fewer/larger tiles per current OpenCV grid semantics, despite the wrapper's "finer" comment
none
declared control, currently non-dispatching -- see limits

Mask behavior: Not currently masked through the generic nonlinear adaptation path in the reviewed source; PI LHE can use a luminance mask when explicitly configured.

Failure modes

  • Noise crunch / false texture from excessive clip limit or an inappropriately fine grid scale.
  • Star halos or rings from local treatment near point sources.
  • Artificial filamentation that looks convincing but is not confirmed against a difference image or noise-only ROI.

Recovery

  • Reduce clip limit; use a broader tile scale; mask low-SNR regions; denoise first; or skip.

PixInsight LocalHistogramEqualization LocalHistogramEqualization (LHE); project runtime pin 1.9.3 Lockhart

  1. LHE is a functional alternative to CLAHE, not an exact algorithmic equivalent -- different runtime model (moving/circular neighborhood vs. tiled grid) and different control semantics (radius, slope limit, amount).
  2. Verify the effective radius/slope actually used before citing an LHE experiment result -- current generic dispatch may only be forwarding amount.

Controls and starting ranges

radius
~1/12 of shorter image dimension, clamped 32-128px -- a feature-scale parameter, not a fixed universal default
slope limit
1.5 nebula / 2.5 galaxy / 2.0 other -- NOVA heuristics, plausible direction, not validation thresholds
amount
0.5 default

Mask behavior: Supported natively; NOVA's PI path can also sample pre-LHE background for a corrective post-anchor.

Failure modes

  • Assuming a candidate's declared radius/slope was the value actually executed.

Recovery

  • Confirm effective PI call arguments directly (e.g. via logged JS parameters) before drawing conclusions; otherwise treat only amount as controlled.

Seti Astro Suite Pro CLAHE CLAHE via OpenCV tileGridSize; project pin 1.18.0

  1. Current upstream source converts through 8-bit CIELAB and applies OpenCV CLAHE to the L channel, retaining chroma -- treat this as version-bound context, not confirmed proof of the pinned 1.18.0 install's exact numeric path.
  2. `tileGridSize` is a tile COUNT, not a pixel size -- verify before describing a smaller `tile_size` argument as "finer".

Controls and starting ranges

clip_limit
roughly 1.0 weak probe, 1.5-2.5 sensible moderate range in NOVA's current parameterization; cross-library numeric equality is not guaranteed
tileGridSize
grid tile COUNT, e.g. (8,8) or (4,4) -- fewer tiles is coarser, more tiles is finer

Mask behavior: Not exercised by the current default seti_astro.clahe() wrapper call.

Failure modes

  • Precision/posterization risk from the 8-bit internal CLAHE path (version-bound, unverified against the pinned runtime).

Recovery

  • Verify pinned 1.18.0 source before presenting precision/grid details as production-runtime fact.

Siril local-contrast tools native local-contrast/AHE-family tools; exact version not independently confirmed in this repo

  1. Treat as the same conceptual family -- local tonal remapping -- with different implementation than NOVA's SASpro CLAHE or PixInsight LHE paths.
  2. Apply the same target-ROI contrast + noise + star-profile measurement discipline used for NOVA's own candidates.

Controls and starting ranges

local contrast / AHE tools
typically applied post-stretch

Mask behavior: Not independently confirmed in this repo.

Failure modes

  • Treating a Siril result as evidence about NOVA's SASpro/PixInsight candidates.

Recovery

  • Prefer a manually copied unchanged baseline if the result damages the target.

Validation status

Not yet Jeff-validated. Use this as sourced guidance, not a certification.

Sources

Related evidence

The M66 recipe and Jeff-validation ledger will link here when their static exports are available. Until then, this article remains explicitly unvalidated.