Skip to main content

Color Constancy Photo Enhancement

License: MIT CI PyPI version PyPI Downloads PyPI pyversions DOI

A Python implementation of color constancy algorithms for photo enhancement, featuring both classical methods (Foster, 2011) and the novel Selective Midtone Enhancement (SME) — a perceptually-guided adaptive enhancer (Semoglou, 2026).

Features

Eight color constancy and enhancement algorithms, a composable pipeline API, quantitative evaluation metrics, a full CLI, presets for common scenarios, and a benchmark harness for datasets:

  • Selective Midtone Enhancement (SME): Novel auto-adaptive algorithm with dynamic S-curve contrast, conditional saturation via Color Definition Confidence (CDC), and asymptotic highlight preservation
  • Gray World Assumption: Corrects color cast by assuming the spatial average of scene reflectances is neutral
  • White Patch / Max-RGB: Normalizes colors based on the brightest surface in the image
  • Von Kries Adaptation: Applies a diagonal cone-response transformation to simulate chromatic adaptation
  • Retinex Enhancement (SSR): Uses center-surround processing for local contrast enhancement (Single-Scale Retinex)
  • Multi-Scale Retinex (MSR): Averages SSR at three scales (15, 80, 250) for balanced dynamic range (Jobson et al., 1997)
  • MSRCR: MSR with color restoration for vivid output without desaturation (Jobson et al., 1997)
  • Spatial Color Correction: Estimates a per-pixel local illuminant using a vectorized Gaussian neighborhood mean
  • Combined Pipeline: Sequential Gray World → MSRCR for combined cast removal and local contrast enhancement
  • Benchmark Harness: Evaluate illuminant estimators on standard CSV datasets with angular-error statistics
  • Named Presets: night, indoor_tungsten, sunset, high_contrast, vivid, subtle for quick configuration

Installation

git clone https://github.com/MichailSemoglou/color-constancy-photo-enhancement.git
cd color-constancy-photo-enhancement

Create an isolated environment (recommended):

# macOS / Linux
python -m venv .venv && source .venv/bin/activate

# Windows
python -m venv .venv && .venv\Scripts\activate

Then install:

pip install -e .

To install with development tools (pytest, ruff, mypy):

pip install -e ".[dev]"

Usage

CLI

# Combined pipeline (default): Gray World -> MSRCR
color-constancy-enhance input.jpg --output enhanced.jpg

# Single-Scale Retinex
color-constancy-enhance input.jpg --method retinex --output retinex.jpg
# Tune SSR sigma
color-constancy-enhance input.jpg --method retinex --sigma 30.0 --output retinex.jpg

# Multi-Scale Retinex
color-constancy-enhance input.jpg --method msr --output msr.jpg
# Custom MSR scales
color-constancy-enhance input.jpg --method msr --sigmas 10,60,200 --output msr.jpg

# MSRCR (Multi-Scale Retinex with Color Restoration)
color-constancy-enhance input.jpg --method msrcr --output msrcr.jpg
# Stronger color restoration
color-constancy-enhance input.jpg --method msrcr --cr-alpha 200 --output vivid.jpg

# Other single algorithms
color-constancy-enhance input.jpg --method gray_world --output gray_world.jpg
color-constancy-enhance input.jpg --method white_patch --output white_patch.jpg
color-constancy-enhance input.jpg --method von_kries --output von_kries.jpg
# Tune Von Kries
color-constancy-enhance input.jpg --method von_kries --adaptation-strength 0.8 --output vk.jpg
color-constancy-enhance input.jpg --method spatial --output spatial.jpg

# Named presets for common scenarios
color-constancy-enhance input.jpg --preset night --output night.jpg
color-constancy-enhance input.jpg --preset indoor_tungsten --output indoor.jpg
color-constancy-enhance input.jpg --preset sunset --output sunset.jpg
color-constancy-enhance input.jpg --preset high_contrast --output hc.jpg
color-constancy-enhance input.jpg --preset vivid --output vivid.jpg

# Side-by-side comparison
color-constancy-enhance input.jpg --show
color-constancy-enhance input.jpg --comparison before_after.jpg --show

# Selective Midtone Enhancement (SME) — auto-adaptive mode
color-constancy-enhance input.jpg --method sme --output sme.jpg
# Manual parameter control (requires auto=false)
color-constancy-enhance input.jpg --method sme --param auto=false,contrast_strength=1.2,saturation_gain=1.4 --output sme_custom.jpg

# Channel statistics
color-constancy-enhance input.jpg --stats

# Illuminant diagnostic chart (for algorithms with estimate_illuminant())
color-constancy-enhance input.jpg --method gray_world --debug

# All options at once
color-constancy-enhance photo.jpg \
  --method combined \
  --output enhanced.jpg \
  --comparison before_after.jpg \
  --show \
  --stats

Benchmarking

# Run all algorithms against a CSV dataset
color-constancy-benchmark dataset.csv --image-dir ./images

# Subset of algorithms, Markdown output
color-constancy-benchmark dataset.csv --image-dir ./images --method GrayWorld WhitePatch --format markdown

# Custom column names, save to file
color-constancy-benchmark dataset.csv \
  --image-dir ./images \
  --illuminant-cols r_gt,g_gt,b_gt \
  --image-col img_name \
  --format csv \
  --output results.csv

Python API

Composable pipeline (recommended)

from color_constancy import build_combined_pipeline, load_image, save_image
import numpy as np

image = load_image("photo.jpg") # uint8 RGB
pipeline = build_combined_pipeline()
enhanced = (pipeline.process(image.astype("float32") / 255.0) * 255).astype("uint8")
save_image(enhanced, "enhanced.jpg")

Individual algorithms

from color_constancy import GrayWorldCorrection, VonKriesAdaptation
from color_constancy import load_image, save_image
import numpy as np

image = load_image("photo.jpg")
img_f = image.astype("float32") / 255.0

# Apply algorithms individually or in any order
gw = GrayWorldCorrection()
vk = VonKriesAdaptation()

corrected = vk.process(gw.process(img_f))
save_image((corrected * 255).astype("uint8"), "corrected.jpg")

Custom pipeline

from color_constancy import AlgorithmPipeline, GrayWorldCorrection, MSRCR, MultiScaleRetinex, RetinexEnhancement

# SSR pipeline
pipeline_ssr = AlgorithmPipeline([
    GrayWorldCorrection(),
    RetinexEnhancement(surround_sigma=20.0, blend_alpha=0.7),
])

# MSR pipeline
pipeline_msr = AlgorithmPipeline([
    GrayWorldCorrection(),
    MultiScaleRetinex(sigmas=(15.0, 80.0, 250.0), blend_alpha=0.7),
])

# MSRCR pipeline (what build_combined_pipeline() returns)
pipeline_msrcr = AlgorithmPipeline([
    GrayWorldCorrection(),
    MSRCR(sigmas=(15.0, 80.0, 250.0), blend_alpha=0.7),
])

Backward-compatible facade

The original single-class interface still works:

from color_constancy_enhancer import ColorConstancyEnhancer

enhancer = ColorConstancyEnhancer()
enhanced = enhancer.enhance_image("input.jpg", method="combined", output_path="output.jpg")
stats = enhancer.analyze_color_statistics(enhanced)
enhancer.display_results()

Evaluation Metrics

The color_constancy.metrics module provides standard quantitative metrics for academic evaluation:

from color_constancy.metrics import angular_error, psnr, ssim, color_statistics
import numpy as np

# Angular error between illuminant estimates (degrees; lower is better)
estimated   = np.array([0.30, 0.33, 0.37])
ground_truth = np.array([0.33, 0.33, 0.34])
err = angular_error(estimated, ground_truth)

# PSNR and SSIM between float32 images in [0, 1]
score_psnr = psnr(reference_f, enhanced_f)
score_ssim = ssim(reference_f, enhanced_f)

# Per-channel statistics and color cast
stats = color_statistics(enhanced_f)
print(stats["red_cast"], stats["mean_r"])

Benchmark API

The color_constancy.benchmark module provides a dataset evaluation harness for illuminant estimators. Only algorithms that expose estimate_illuminant() are scored; enhancement methods such as MSR and MSRCR estimate no illuminant and are excluded from angular-error tables.

from color_constancy.benchmark import load_dataset, run_benchmark
from color_constancy.algorithms import GrayWorldCorrection, VonKriesAdaptation

entries = load_dataset("dataset.csv", image_dir="./images")
results = run_benchmark(entries, algorithms={
    "GrayWorld": GrayWorldCorrection(),
    "VonKries": VonKriesAdaptation(),
})

print(results.summary_table())
#  Algorithm               Mean   Median  Trimean  Best25   Worst5    N
#  ---------------------------------------------------------------------
#  GrayWorld              4.32     3.87     3.91    1.23    12.45    568
#  VonKries               4.10     3.65     3.72    1.15    11.87    568

# Export as Markdown or CSV
print(results.to_markdown())
print(results.to_csv())

Methods Explained

Gray World Assumption

Assumes the spatial average of scene reflectances is achromatic. Estimates the illuminant as the per-channel mean and scales each channel toward a neutral mean brightness (Buchsbaum, 1980). Unreliable for images dominated by a single hue.

White Patch / Max-RGB

Assumes the brightest surface in the image reflects maximally across all wavelengths. Normalizes each channel by its spatial maximum. Unreliable when specular highlights are chromatically colored.

Von Kries Adaptation

Implements the von Kries coefficient rule via a diagonal per-channel scaling of cone responses (von Kries, 1902). The illuminant is estimated as a weighted blend of the Grey World and specular-highlight estimates. A configurable adaptation_strength parameter blends the correction toward the identity for natural-looking results.

Retinex Enhancement (SSR)

Based on Land's Retinex theory (Land & McCann, 1971). Computes a log-domain image and subtracts a Gaussian-smoothed surround (slowly-varying illumination estimate) to enhance local contrast.

Note: this is Single-Scale Retinex (SSR) using a single surround sigma. For more advanced results, use the MSR or MSRCR methods.

Multi-Scale Retinex (MSR)

Averages SSR outputs at three surround scales — typically 15, 80, and 250 — to balance dynamic range compression and tonal rendition (Jobson et al., 1997). By combining a small, medium, and large scale, MSR avoids the single-scale trade-off between local contrast and color fidelity.

from color_constancy import MultiScaleRetinex

msr = MultiScaleRetinex(sigmas=(15.0, 80.0, 250.0), blend_alpha=0.7)
# Or via CLI: color-constancy-enhance input.jpg --method msr

MSRCR (Multi-Scale Retinex with Color Restoration)

Extends MSR with a per-channel color restoration step that compensates for the desaturation MSR can introduce (Jobson et al., 1997). The cr_alpha parameter shapes the restored colors; the canonical outer constants (cr_beta, cr_gain, cr_bias) are retained but absorbed by the output percentile normalization.

from color_constancy import MSRCR

msrcr = MSRCR(sigmas=(15.0, 80.0, 250.0), blend_alpha=0.7, cr_alpha=125.0)
# Or via CLI: color-constancy-enhance input.jpg --method msrcr --cr-alpha 200

The default combined pipeline (Gray World → MSRCR) uses MSRCR internally.

Spatial Color Correction

Estimates a per-pixel local illuminant using scipy.ndimage.gaussian_filter — fully vectorised, no Python loops. Each pixel is corrected toward the global mean relative to its local neighborhood. Corrections are clipped to prevent over-saturation.

Combined Pipeline

Sequentially applies Gray World correction and MSRCR: the first stage removes the gross global cast, the second enhances local contrast while preserving color fidelity.

Selective Midtone Enhancement (SME)

A novel, perceptually-guided enhancement algorithm with three stages:

  1. Dynamic S-curve contrast — Targets the interquartile range of luminance for midtone expansion while protecting shadows, highlights, and near-neutral tones via soft blend zones.
  2. Conditional saturation boost — Computes a per-pixel Color Definition Confidence (CDC) metric combining chroma magnitude with local hue/chroma variance. Only vivid, locally-uniform colors receive saturation gain; muted and near-neutral regions are left untouched.
  3. Asymptotic highlight guard — Soft-compresses values above 250/255 so pure white is never produced.

Auto-adaptive mode (default): contrast_strength and saturation_gain are derived per-image from luminance IQR spread and chroma distribution. No manual tuning needed — a vivid photo gets a subtle touch while a flat/muted scene gets more aggressive treatment.

from color_constancy import SelectiveMidtoneEnhancement, load_image
import numpy as np

# Auto-adaptive (recommended)
sme = SelectiveMidtoneEnhancement()  # auto=True is default
result = sme.process(image.astype("float32") / 255.0)

# Manual control
sme = SelectiveMidtoneEnhancement(
    auto=False,
    contrast_strength=1.2,
    saturation_gain=1.4,
    chroma_threshold=10.0,
)
Parameter Default Range Description
auto True bool Derive contrast/saturation per-image
contrast_strength 1.0 0.0–2.0 Midtone expansion intensity
saturation_gain 1.25 1.0–1.5 Max chroma multiplier for vivid colors
shadow_protection 0.10 0.0–0.3 L fraction blended to identity (shadows)
highlight_protection 0.10 0.0–0.3 L fraction blended to identity (highlights)
chroma_threshold 12.0 5.0–40.0 Chroma midpoint for CDC sigmoid
cdc_threshold 0.5 0.0–1.0 CDC cutoff for saturation gain activation

Visual Examples

OriginalSME Enhanced (auto)
Forest landscape original Forest landscape SME enhanced
Outdoor water original Outdoor water SME enhanced
Bright landscape original Bright landscape SME enhanced

Running Tests

pytest tests/ -v
# With coverage (same scope as CI):
pytest tests/ --cov=color_constancy --cov=color_constancy_enhancer --cov-report=term-missing

Requirements

  • Python 3.10+
  • OpenCV (opencv-python >= 4.8.1.78)
  • NumPy (>= 1.24)
  • SciPy (>= 1.10)
  • Matplotlib (>= 3.7)

Scientific Background

This implementation is based on the comprehensive review:

Foster, D. H. (2011). Color constancy. Vision Research, 51(7), 674–700. https://doi.org/10.1016/j.visres.2010.09.006

Additional references:

  • Buchsbaum, G. (1980). A spatial processor model for object color perception. Journal of the Franklin Institute, 310(1), 1–26.

  • von Kries, J. (1902). Theoretische Studien über die Umstimmung des Sehorgans. Festschrift der Albrecht-Ludwigs-Universität, 143–158.

  • Land, E. H., & McCann, J. J. (1971). Lightness and retinex theory. Journal of the Optical Society of America, 61(1), 1–11.

  • Jobson, D. J., Rahman, Z., & Woodell, G. A. (1997). A multiscale retinex for bridging the gap between color images and the human observation of scenes. IEEE Transactions on Image Processing, 6(7), 965–976.

  • Hordley, S. D., & Finlayson, G. D. (2006). Reevaluation of color constancy algorithm performance. Journal of the Optical Society of America A, 23(5), 1008–1020.

  • Semoglou, M. (2026). Selective Midtone Enhancement: Dynamic Contrast Adjustment with Conditional Saturation Control. Proposed algorithm. Included in this package.

License

MIT License — see LICENSE for details.

Acknowledgments

  • David H. Foster for the comprehensive review that inspired this implementation
  • The computer vision community for advancing color constancy research
  • Contributors to OpenCV, NumPy, and SciPy

Release files for color-constancy-enhancement 1.3.2

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for color-constancy-enhancement 1.3.2
File Size Uploaded
color_constancy_enhancement-1.3.2.tar.gz 51.9 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for color-constancy-enhancement 1.3.2
File Interpreter ABI Platform
color_constancy_enhancement-1.3.2-py3-none-any.whl Python 3 none any Details

Total release size: 94.3 kB

Release files / color_constancy_enhancement-1.3.2.tar.gz

Download URL color_constancy_enhancement-1.3.2.tar.gz
Size 51.9 kB
Tags Source
SHA-256 checksum
How to use checksums
cdf25ae2bc33b2cebbe11707036281e6510db9e95dd6993381537a4554c62672
BLAKE2b-256 checksum
How to use checksums
7cdf574685150001c2076b6138e734b93350581f242e9bceae4e92dc4efd058f
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Sep 7, 2026.

Transparency log

Release files / color_constancy_enhancement-1.3.2-py3-none-any.whl

Download URL color_constancy_enhancement-1.3.2-py3-none-any.whl
Size 42.4 kB
Tags Python 3
SHA-256 checksum
How to use checksums
853675d756a5aeb55e2f5fb386914269c935708e5476f34c6f48a9af509ab8d6
BLAKE2b-256 checksum
How to use checksums
3d5d2fa3f862310fb93dff21a8ac0e9f7fe258d5f44a6ef8343a73638aa9cf4f
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Sep 7, 2026.

Transparency log

Release history Release notifications | RSS feed

This release

1.3.2 This release

2 release files

1.3.1

2 release files

1.3.0

2 release files

1.1.2

2 release files

1.1.1

2 release files

1.1.0

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page