Local Contrast Enhancement¶
Purpose and expected result¶
Local Contrast Enhancement applies a high-radius unsharp-mask-style transform. It separates each selected channel into a Gaussian base and a local residual, scales bright and dark residuals independently, and then blends the filtered result back with the source. It is a local transition enhancer, not deconvolution and not histogram equalization.
The expected result is a same-size still or replacement sequence with stronger
mid- to broad-scale texture. Large Weight or tone-level factors can amplify
noise and produce bright/dark halos around stars. Amount is a separate final
crossfade: 0 preserves the source and 1 commits the complete G'MIC result.
At a glance¶
| Item | Current behavior |
|---|---|
| Menu path | Process > Details > Local Contrast Enhancement |
| Enabled when | An image or sequence is active, the same process is not already attached to it, and no attached process holds exclusive access |
| Inputs | One grayscale or RGB still or sequence; optional active mask with matching geometry; a CFA still is treated as one grayscale mosaic plane, while a CFA sequence is auto-debayered to RGB first when its view has sequence auto-debayer enabled |
| Outputs | Destructively replaces the still or, after successful temporary processing, the sequence; Take snapshot creates an auxiliary still; no file is written directly |
| Recommended image state | Usually a finite, normalized nonlinear working copy after noise reduction and global stretch; use on linear data only with deliberate low settings and matched display conditions |
| Preview support | Asynchronous movable-area preview, original/result switching, snapshot, display-channel selection, and a separate source/result histogram; no execution autostretch |
| Sequence support | Yes; numeric parameters and process Channel are captured once when Apply starts, CFA frames can be auto-debayered first, and temporary output replaces the sequence only after success |
| Mask support | Yes; the active mask blends the Amount-mixed result with the original after filtering |
| External dependencies | Bundled G'MIC 3.5.4/cgmic, run through AfterNight's optional G'MIC adapter, with Arto Huotari's fx_LCE definitions built into the process; no separate installation, script file, or network access. In a build without G'MIC, Preview and Apply are disabled and their tooltip says why |
| Non-finite samples | An image holding a NaN or infinite sample is refused before G'MIC sees it, and left unchanged |
Before you start¶
- Complete calibration, registration, integration, gradient correction, color calibration, stretching, and appropriate noise reduction first. This process boosts noise and residual defects along with structure.
- Debayer CFA stills before color enhancement. For CFA sequences, confirm the view's sequence auto-debayer setting: enabled frames enter this process as RGB; disabled frames remain a single mosaic plane that can reinforce the sensor pattern.
- Duplicate the target or confirm Image History is available. Apply replaces the target only after the full operation succeeds.
- Use finite, normalized samples. G'MIC works in float32 and its result is
clamped to
[0,1]. An image holding a NaN or infinite sample is refused with "the image holds a sample that is not a finite number, which G'MIC cannot process" and left unchanged. - This process needs G'MIC, which AfterNight normally includes. In a build without it, Preview and Apply are disabled and their tooltip reads "This build of AfterNight carries no G'MIC library, so this process cannot run."
- Decide whether independent RGB or a luminance/lightness component matches the goal. Independent RGB can create colored rims.
- Include smooth background, small stars, a bright edge, and faint structure in the Preview area, then recheck full-frame context before Apply.
Procedure¶
- Select the target and open Process > Details > Local Contrast Enhancement. Confirm Target Image and choose RGB or one deliberate luminance/lightness Channel.
- Keep Preview selected and start at Spatial Radius
80, Weight0.5, Darkness Level1, Lightness Level1, and Amount1. - Set Spatial Radius for the widest structure to enhance. Inspect at Zoom 100%; use the histogram only as a clipping diagnostic.
- Adjust Weight for residual strength. Then balance Darkness Level and Lightness Level only if negative and positive residuals need different gain.
- Reduce Amount to crossfade the entire filtered result toward the source. Use Switch image to compare without changing samples.
- Check noise, colored edges, black/white clipping, and star halos with Preview area off. Apply only after confirming target, Channel, mask, and inversion.
Complete parameter and action reference¶
| UI label/action | Type and unit | Runtime default | Persisted-default exception | Valid values, step, or constraints | Effect of increasing, decreasing, or selecting | Dependencies and enable/visibility rules | Quality, artifact, runtime, memory, or disk trade-off | Scope: preview/apply, channel, mask, still/sequence | Persistence |
|---|---|---|---|---|---|---|---|---|---|
| Spatial Radius | Double value plus linear slider, Gaussian standard deviation in pixels | 80.00 |
None | 30.00..200.00; spin uses adaptive decimal stepping; slider maps 300..2000 to tenths |
Widens the Gaussian base and therefore the scale of transitions represented by the residual | Enabled while idle; disabled during Apply, and the running job keeps its captured value | Larger values create broader halos and require more blur work/support | Preview/Apply; selected process Channel; before Amount and mask; still/sequence | Captured per execution; Reset and successful Apply restore 80 |
| Weight | Double value plus linear slider, residual multiplier | 0.50 |
None | 0.00..5.00; spin default step 0.01; slider maps 0..500 to hundredths |
Multiplies the high-radius residual inside bundled fx_LCE; 0 suppresses its detail term |
Enabled while idle; captured for execution | High values amplify texture/noise and can clip or halo; 0 should preserve the script input apart from float/type round-trip |
Preview/Apply; selected process Channel; before tone gains, Amount, and mask | Captured per execution; Reset/success restore 0.5 |
| Darkness Level | Double value plus linear slider, negative-residual gain | 1.00 |
None | 0.00..4.00; spin step 0.05 with adaptive stepping; slider maps 0..400 to hundredths |
Scales the negative residual branch; 0 suppresses dark-side local contrast |
Has no visible effect when Weight is 0; captured for execution |
High values deepen dark edge shoulders and can clip shadows; it does not change blur cost | Preview/Apply; selected Channel; before final Amount/mask | Captured per execution; Reset/success restore 1 |
| Lightness Level | Double value plus linear slider, positive-residual gain | 1.00 |
None | 0.00..4.00; spin step 0.05 with adaptive stepping; slider maps 0..400 to hundredths |
Scales the positive residual branch; 0 suppresses bright-side local contrast |
Has no visible effect when Weight is 0; captured for execution |
High values brighten edge shoulders and can clip highlights; it does not change blur cost | Preview/Apply; selected Channel; before final Amount/mask | Captured per execution; Reset/success restore 1 |
| Amount | Double value plus linear slider, final normalized blend | 1.00 |
None | 0.00..1.00; spin step 0.05 with adaptive stepping; slider maps 0..1000 to thousandths |
Crossfades source and complete G'MIC result after channel processing; 0 is the source, 1 is full result |
Independent of Weight; captured for execution | Reducing it controls all filter artifacts without changing expensive G'MIC work | Preview/Apply; reconstructed selected Channel; before mask; still/sequence | Captured per execution; Reset/success restore 1 |
| Histogram plot | Interactive source/result histogram | Source initially, preview result after execution | None | Mouse wheel/drag and toolbar actions below | Displays distribution and clipping diagnostics; never changes samples | Updated from target/preview; separate from preview-view display stretch | High bin counts add analysis/rendering cost; a histogram cannot diagnose spatial halos | Display only | Window lifetime |
| Zoom in | Histogram-toolbar action | Current histogram range | None | Click | Narrows the displayed histogram range | Histogram shown | Diagnostic only | Histogram display only | Window lifetime |
| Zoom out | Histogram-toolbar action | Current histogram range | None | Click | Widens the displayed histogram range | Histogram shown | Diagnostic only | Histogram display only | Window lifetime |
| Zoom reset | Histogram-toolbar action | Full histogram range | None | Click | Restores full plot range | Histogram shown | Diagnostic only | Histogram display only | Window lifetime |
| Show grid | Checkable histogram action | Checked | None | Checked/unchecked | Shows or hides plot grid | Histogram shown | Display only | Histogram display only | Window lifetime |
| Show clipping info | Checkable histogram action | Checked | None | Checked/unchecked | Shows/hides endpoint clipping readouts | Histogram shown | Binned diagnostic, not proof that spatial artifacts are absent | Histogram display only | Window lifetime |
| Absolute scales | Checkable histogram action | Unchecked | None | Checked/unchecked | Uses shared absolute vertical scaling instead of local per-channel normalization | Histogram shown | Makes channel population/height comparisons easier but can hide a low-population channel | Histogram display only | Window lifetime |
| Draw histogram areas | Checkable histogram action | Unchecked | None | Checked/unchecked | Fills areas under plotted channels | Histogram shown | Display only; overlap can obscure channel detail | Histogram display only | Window lifetime |
| Show histogram | Checkable histogram action | Checked | None | Checked/unchecked | Hides/clears the graph and disables its toolbar, or reloads source/preview | Always available while idle | Hiding saves graph work, not filter work | Display only | Window lifetime |
| Plot resolution | Combo box, histogram bins | 8-bit (256) | None | 6-bit (64), 7-bit (128), 8-bit (256), 9-bit (512), 10-bit (1024), 12-bit (4096), 14-bit (16K), 16-bit (64K), 100 levels, 1000 levels, 10000 levels | Changes diagnostic binning only | Disabled when histogram is hidden | More bins cost graph memory/rendering; output precision is unchanged | Histogram display only | Window lifetime |
| Target Image | Image selector | Attached/active target | None | Open eligible image or sequence | Changes source, Channel list, histogram, Preview, active mask, and Apply destination | Active target required; duplicate/exclusive launch gates apply; disabled during Apply | A wrong selection before Apply replaces the wrong target | Preview/Apply source and active mask | Target lifetime; Reset unchanged |
| Channel | Combo box | RGB for RGB; disabled Grayscale for mono | None | RGB, Red, Green, Blue, CIE L* (Lightness), CIE a, CIE b, CIE c, CIE h, CIE X, CIE Y (Luminance), CIE Z, HSL Hue, HSL Saturation, HSL Lightness, HSV Hue, HSV Saturation, HSV Value, YCbCr Luma, YCbCr Blue Chroma, YCbCr Red Chroma | RGB lets fx_LCE process all stored channels together; a component is extracted, filtered, and reconstructed |
RGB target required for components; disabled during Apply; the selection is captured when Apply starts | RGB can create color halos; component round trips add buffers and, in an integer image, clip at its type's range | Preview/Apply; mask post-blend; still/sequence | Dialog-local; Reset/success restore RGB for RGB targets |
| Preview | Checkbox | Checked | None | Checked/unchecked | Enables/disables asynchronous full-image or area execution and preview pane | Process idle | Runs the complete G'MIC command after each parameter change | Preview only; mask-aware; still/current displayed frame | Window lifetime; Reset unchanged |
| Preview pane | Image viewer | Filtered result in Linear mode; initial selected area is centered 600×600 or full image if smaller |
Execution autostretch is not enabled | Pan/scroll and toolbar actions below | Shows mask-aware result without modifying target | Exists only with Preview | Holds source/result/display buffers in addition to G'MIC buffers | Preview only | Window lifetime |
| Fit To Window | Preview-toolbar action | Preview fitted initially | None | Click or Ctrl+Shift+F | Fits whole image uniformly | Preview image required | Broad context; resampling can hide noise/rims | Preview display only | Viewer lifetime |
| Zoom In | Preview-toolbar action | Current scale | None | Click; 1.25× relative zoom |
Enlarges around viewport center | Preview image required | Improves local inspection | Preview display only | Viewer lifetime |
| Zoom Out | Preview-toolbar action | Current scale | None | Click; 0.8× relative zoom |
Reduces around viewport center | Preview image required | Shows context but hides fine artifacts | Preview display only | Viewer lifetime |
| Zoom 100% | Preview-toolbar action | Current scale | None | Click; absolute scale 1.0 |
Shows one-pixel viewer scale | Preview image required | Preferred for noise, ringing, and color rims | Preview display only | Viewer lifetime |
| View Channel | Preview-toolbar combo box | RGB for RGB; disabled Grayscale for mono | None | Stored and derived RGB/CIE/HSL/HSV/HSI/YCbCr display components | Selects displayed component only | Separate from process Channel | Helps diagnose colored residuals without changing pixels | Preview display only | Viewer lifetime |
| Switch image | Preview-toolbar action | Result shown | None | Click; Space; right-click; Ctrl+Shift+O | Toggles result/original | Preview required | Comparison only; no recomputation | Preview display; still/current frame | Preview-window lifetime |
| Preview area | Preview-toolbar checkbox | Checked with centered 600×600 area when possible |
None | Checked/unchecked; move/resize overlay | Processes only selected area and composites it over original preview | Preview required | Reduces tuning work but can hide broad-radius halos; Apply remains full-frame | Preview and snapshot; mask cropped consistently | Preview-window lifetime |
| Take snapshot | Preview-toolbar action | No snapshot | None | Click or Ctrl+Shift+K | Re-executes and opens <target> - Local Contrast Enhancement - SNAPSHOT |
Preview required | Adds a complete execution and auxiliary still | New still; target unchanged; area/mask honored | Each action creates a new view |
| Apply | Button/action | Enabled | None | One action | Captures numeric values, requests a processing lock on the current target, processes the full still or every frame, then blends Amount/mask and commits on success | Target, Channel, and process controls lock during Apply | G'MIC blur, float conversion, component reconstruction, masks, and sequence encoding add cost | Destructive still/sequence replacement after success | History records five numeric values and, for a colour image, the selected Channel text; then Reset runs |
| Reset | Button/action | Enabled while idle | None | One action | Restores 80/0.5/1/1/1 and RGB on RGB targets |
Leaves Target, Preview, histogram, mask, and preview toolbar unchanged; disabled during Apply | Checked Preview schedules another full execution | Configuration only | Immediate; also after successful Apply |
| Close | Button/action | Enabled while idle | None | One action | Closes without applying current settings | Disabled during Apply | Requests preview cancellation; G'MIC observes a shared abort flag but may stop only at its next check | Window/execution | Settings discarded at destruction |
| Sequence progress / Cancel | Shared indeterminate process/frame progress and Cancel | Created for sequence Apply | None | Cooperative Cancel | Optionally auto-debayers configured CFA frames, processes them into a temporary sequence, and replaces the target sequence only after success | Sequence target only | Numeric values and Channel are captured once for every frame; temporary disk and encode cost apply | Full sequence; mask and Amount applied per frame | Cancel removes temporary output; success records one history step and resets settings |
How the algorithm works¶
Conceptual pipeline¶
- AfterNight extracts the selected process Channel when necessary, checks it
for non-finite samples, and hands it through its G'MIC adapter, one G'MIC
session per run, as planar float32 fractions of full scale, together with
the
fx_LCEdefinitions, which the process carries itself. - Bundled
fx_LCEforms a Gaussian base with Spatial Radius and a signed local residual. Its script describes the method as high-radius unsharp masking. - Weight scales the residual. A fixed zero threshold keeps nonzero residual
locations, Darkness Level scales its negative branch, and Lightness Level
scales its positive branch. The script adds the branches to the source and
clips to its
0..255working convention. - AfterNight clamps the result to normalized
[0,1], converts back to the original pixel type, and reconstructs a selected color component; the result keeps the source metadata. - The shared framework crossfades the complete result by Amount, then blends the active mask. Still Apply swaps only the completed destination; sequence Apply promotes only a completed temporary sequence.
Model and equations¶
Let I(p) be a selected normalized sample at position p, and let
B_r(p) be the G'MIC Gaussian blur at Spatial Radius r. The signed residual is
where w is Weight. Split it into positive and negative branches,
D_+(p)=max(D(p),0) and D_-(p)=min(D(p),0). The script-level result is
conceptually
where l is Lightness Level and d is Darkness Level. This equation
expresses the bundled command's source/blur/residual split and branch gains; the
actual G'MIC command evaluates in its 0–255 channel convention before AfterNight
clamps the result.
AfterNight then applies the separate Amount a:
Thus Amount 0 is an identity even though the expensive filter still runs;
Weight 0 makes D=0 and should also make the mathematical result the source.
With effective mask weight m(p), the final sample is
Mask inversion substitutes 1-m. Clipping happens inside the filter before
Amount and mask blending, so those blends can hide but cannot restore clipped
residual detail.
Color, boundaries, and precision¶
- RGB passes all stored channels to
fx_LCE; the Gaussian is spatial and the arithmetic is per channel. A component choice extracts one scalar component, filters it, and reconstructs RGB. A derived component's reconstruction saturates an integer image at its type's range and keeps a floating image's values outside[0, 1]. - G'MIC
blurdefaults to Neumann boundary conditions and a Gaussian kernel in the reviewed command contract. Broad radii can therefore behave differently near image edges from an explicitly mirrored or periodically padded model. - All storage types enter G'MIC as float32. Integer types are normalized;
double narrows to float. Every result is clamped to
[0,1]and returned in the original storage type, rounded to nearest, so integer quantization and float clipping apply. - The filter itself does not mark the result nonlinear. The result keeps the input processing state even though the operation is normally used after stretch; treat that state as incomplete provenance.
Parameter interactions and starting points¶
| Combination | Conservative starting point | Too weak looks like | Too strong looks like | Adjustment order |
|---|---|---|---|---|
| Spatial Radius × feature size | Radius 80, Weight 0.5 on a representative area |
Desired broad texture remains unchanged | Broad star/nebula shoulders and gradients become halos | Radius for scale, then Weight for strength |
| Weight × Amount | Keep Amount 1 while finding a modest Weight; lower Amount last |
Low Weight and low Amount hide whether the scale is correct | High Weight clips internally even if low Amount makes the final blend look mild | Radius, Weight, tone gains, then Amount |
| Darkness × Lightness | Start 1/1 |
One side of transitions lacks desired separation | Dark troughs or bright rims become asymmetric/clipped | Adjust only the branch with the problem |
| RGB × luminance goal | Compare RGB with CIE Y or L* | Luminance-only result may be subtle | Independent RGB produces colored halos | Choose Channel before strength tuning |
| Radius × Preview area | Use area for tuning, full frame for final check | Small area misses large-scale context | Edge/halo behavior surprises on Apply | Tune locally, then disable Preview area |
| Amount × mask | Tune unmasked Amount first | Double attenuation makes effect disappear | Hard mask boundaries reveal abrupt contrast change | Filter parameters, Amount, then mask feathering |
These are source-derived workflow checkpoints, not executed astronomical quality measurements.
Inputs, outputs, and metadata¶
- Output dimensions, channel count, pixel format, and source pixel type match the processed still/frame. Integer output is quantized; float/double output is still clipped and double has passed through float32.
- Still Apply modifies the selected image only after success. Take snapshot creates one new still; display channel and histogram settings are not baked in.
- The result keeps the source image's header cards and metadata, but LCE does not record a nonlinear processing state. The inherited state may therefore describe the source rather than the enhanced result.
- History records Spatial Radius, Weight, Darkness Level,
Lightness Level, Amount, and, for a colour image,
in channel: <channel>. It does not record mask, inversion, G'MIC version, float hand-off, or clipping. - Successful still or sequence Apply resets numeric settings and RGB selection; Target, Preview, histogram, mask, and preview toolbar remain.
- Sequence Apply writes a temporary sequence and promotes it only on success. Cancel removes temporary output. No direct output name/path is exposed.
- CFA sequence frames are auto-debayered before filtering when sequence auto-debayer is enabled; the resulting temporary sequence follows that RGB processing path rather than preserving the mosaic-plane interpretation.
Performance¶
- The dominant work is a large-radius G'MIC Gaussian plus several full-image arithmetic passes. Cost grows with pixel count, stored/selected channel work, and Gaussian radius/support.
- Every run opens one G'MIC session and parses the built-in
fx_LCEdefinitions before the command, because no session is kept between runs. Rapid Preview changes repeat that overhead. - Memory includes float32 G'MIC input/result and internal blur/list images, source/destination, optional color-conversion planes, mask, preview, and histogram buffers. Sequence Apply also needs temporary encoded storage.
- Preview area reduces filter work while tuning; Amount
0and Weight0do not bypass the G'MIC call. Apply always processes the full target. - G'MIC receives a cancellation flag. Response depends on when the current command checks it; a broad blur can delay completion.
- No approved astronomical workload was timed, and peak memory was not measured.
Worked example¶
- Evidence mode: Source-and-equation-derived; not executed on approved astronomical data.
- Input condition and settings: A finite normalized nonlinear image with
smooth background and one broad transition; Radius
80, Weight0.5, both tone levels1, Amount1. - Identity checkpoints: Weight
0givesD=0, so the mathematical filter result is the source. Amount0givesM=Iregardless of the filtered result. - Blend checkpoint: If a source sample is
0.40, the clipped filter result is0.60, and Amount is0.25, thenM=0.45. With mask weight0.5, the committed sample is0.425. - Inspect at 100%/history: Check smooth-noise amplification, colored rims, broad bright/dark shoulders, endpoint bins, frame edges, and the five recorded numeric values after Apply.
- Limitations: No visual improvement, sequence-output, production runtime, or memory claim is made.
Warnings and limitations¶
- This is high-radius unsharp masking, not adaptive histogram equalization and not deconvolution. It cannot recover information lost to blur.
- Weight is the internal residual strength. Amount is a later whole-result blend. Reducing Amount does not prevent internal clipping or save filter work.
- Darkness/Lightness factors can make opposite edge sides asymmetric. High settings create dark troughs, bright rims, and clipped endpoints.
- RGB channels are processed independently enough to produce colored halos; use a deliberate luminance/lightness component when appropriate.
- Every storage type is clamped to
[0,1]after G'MIC. Out-of-range float data is lost and double precision narrows. An image holding NaN or infinity is refused by name; without G'MIC in the build, the process cannot run and Preview and Apply stay disabled. - The operation does not mark nonlinear processing state, and history omits mask and hand-off details.
Troubleshooting¶
| Symptom or exact error | Verified cause/validation path | Recovery | Evidence source |
|---|---|---|---|
| Result is unchanged | Weight 0, Amount 0, tone gains 0, protective mask, or scale mismatch |
Check Weight/Amount/mask first; choose Radius for visible feature scale | LCE and blend equations |
| Bright/dark halos surround stars | High-radius residual plus Weight/tone gains | Reduce Weight first, then Radius; lower only the offending tone branch | Bundled fx_LCE pipeline |
| Colored rims appear | RGB path processes color channels through the local residual model | Compare CIE Y/L*; reduce Weight/Amount | Shared Channel dispatcher |
| Background becomes coarse | The residual boosts noise at selected scales | Denoise first; reduce Weight; protect smooth background with a feathered mask | Residual equation |
| Preview differs from Apply | Preview area, Channel, mask/inversion, or display conditions changed | Match controls; disable Preview area for final check | Shared preview/Apply paths |
| Histogram clips although low Amount looks mild | G'MIC clips before final Amount blend | Reduce Weight/tone levels; Amount cannot restore clipped detail | Pipeline order |
| Edge behavior differs near frame boundary | G'MIC Gaussian uses its default Neumann boundary condition | Judge away from border or crop/plan boundary handling before LCE | Official G'MIC blur contract |
| Cancel waits | Current G'MIC command has not reached an abort check | Wait; tune on a smaller area or radius | G'MIC adapter abort flag |
| Error: the image holds a sample that is not a finite number | The image has a NaN or infinite sample, which G'MIC cannot process | Repair or crop the non-finite samples, then retry; the image was left unchanged | GmicProcessSupport.cpp |
| Preview and Apply are disabled; tooltip says the build carries no G'MIC library | This build was made without G'MIC | Use a build that includes G'MIC, or another contrast process | GmicProcessSupport.cpp |
| A G'MIC error is reported | G'MIC failure, invalid data, or memory exhaustion | Verify finite normalized data and memory; retry on a duplicate | Process error path |
Related workflow steps¶
Complete noise reduction before local contrast enhancement. Compare Unsharp Mask for a simpler single residual, Local Histogram Equalization for neighborhood-CDF equalization, and CLAHE for tiled contrast-limited equalization. Use Image History to revert Apply and Masks to protect smooth regions.
References and provenance¶
- G'MIC
blurreference — official standard-deviation, Gaussian/Deriche, and boundary-condition command contract; accessed 2026-07-14 and applied to bundled G'MIC 3.5.4 semantics. - G'MIC
apply_channelsreference — official channel working-range and selection semantics used by bundledfx_LCE; accessed 2026-07-14. - The repository's
scripts/arto_huotari.gmiccredits Arto Huotari and PhotoComiX and notes original unsharp-mask code by David Tschumperlé; the process carries itsfx_LCEand_fx_LCEdefinitions inGmicProcessSupport.cpp, which is authoritative for the shipped fixed-zero-threshold residual/tone pipeline. - AfterNight sources and tests are authoritative for the menu, live controls, Weight/Amount split, G'MIC hand-off, channels, masks, metadata/history, preview, sequence, and cancellation behavior.