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 1Recognize it
- Galaxy arms/dust lanes, nebular filaments/shells, or compact planetary-nebula structure survived the stretch but read as flat or muted relative to their immediate neighborhood.
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
- A geometrically stable, globally balanced nonlinear image with major gradients/color problems already handled, noise sufficiently controlled that the operation will not primarily enhance the noise floor, and star/starless state, masks, and prior sharpening known. `seti_astro.clahe()` can technically run on linear or stretched data, but current NOVA treatment places CLAHE among nonlinear adaptive finishing steps -- implementation capability should not be read as a workflow recommendation for linear use.
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
| Measurements | Acceptance criteria |
|---|---|
|
|
Cross-tool matrix
| Tool | Equivalence | Expected result | Evidence |
|---|---|---|---|
| nova | Exact replay | Improved visibility of real intermediate-scale structure with background continuity, star profiles, and highlight headroom preserved. | nova-clahe-source, none-control-dispatch-gap-clahe |
| pixinsight | Functional alternative | A local histogram remapping via a moving/circular neighborhood, matching the effective radius/slope/amount actually forwarded. | nova-clahe-source |
| saspro | Same engine, adapted host | Local histogram-equalized luminance with clipped amplification, retained chroma, converted back to RGB. | saspro-clahe-upstream-p24, nova-clahe-source |
| siril | Functional alternative | A 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
- 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.
- 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.
- 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
- 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).
- 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
- 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.
- `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
- Treat as the same conceptual family -- local tonal remapping -- with different implementation than NOVA's SASpro CLAHE or PixInsight LHE paths.
- 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
- Phase-2 research packet P24, 2026-08-28
packet:P24:2026-08-28— NOVA source-confirmed - NOVA CLAHE/LHE implementation, ontology candidates, and the LHE dispatch gap
nas_server/seti_astro.py:clahe; nas_server/tool_params.py:compute_clahe,compute_lhe; nas_server/processing_ontology.json clahe; nas_server/experiments.py _run_variant,_adapt_nonlinear_variants— NOVA source-confirmed - Seti Astro Suite Pro current upstream CLAHE source (version-bound, not the pinned 1.18.0 install)
setiastro/setiastrosuitepro src/setiastro/saspro/clahe.py, ref c2c941b88335c82cc42ced4a2016c43261610918— Vendor-documented - Karel Zuiderveld, Contrast Limited Adaptive Histogram Equalization, Graphics Gems IV (1994)
Graphics Gems IV, 1994— Primary-research-supported - Ontology 'none' control candidates fail dispatch instead of running a true copy-through (issue #669)
nas_server/experiments.py:_run_variant; SeeStar-db#669— NOVA source-confirmed
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.