Source code for stouputils.data_science.data_processing.image.histogram_equalization


# pyright: reportUnusedImport=false
# ruff: noqa: F401

# Imports
from typing import Literal

from .common import Any, NDArray, check_image, cv2, np

# Constants
VALID_SPACES: list[str] = ["lab", "ycbcr", "hsv"]

# Color space conversion constants
COLOR_SPACE_CONSTANTS: dict[str, tuple[int, int, int]] = {
    "lab":    (cv2.COLOR_BGR2LAB, cv2.COLOR_LAB2BGR, 0),      # L channel index is 0
    "ycbcr":  (cv2.COLOR_BGR2YCrCb, cv2.COLOR_YCrCb2BGR, 0),  # Y channel index is 0
    "hsv":    (cv2.COLOR_BGR2HSV, cv2.COLOR_HSV2BGR, 2),      # V channel index is 2
}


# Functions
[docs] def histogram_equalization_image( image: NDArray[Any], color_space: Literal["lab", "ycbcr", "hsv"] = "lab", ignore_dtype: bool = False, ) -> NDArray[Any]: """ Apply standard histogram equalization to an image. Histogram equalization improves the contrast in images by stretching the intensity range to utilize the full range of intensity values. Args: image (NDArray[Any]): Image to apply histogram equalization to color_space (str): Color space to use for equalization ("lab", "ycbcr", or "hsv") "lab": CIELab color space (perceptually uniform, best visual fidelity) "ycbcr": YCbCr color space (fast, good balance) "hsv": HSV color space (intuitive, may cause color shifts) ignore_dtype (bool): Ignore the dtype check Returns: NDArray[Any]: Image with histogram equalization applied >>> ## Basic tests >>> image = np.array([[1, 2, 3], [4, 5, 6], [7, 8, 9]]) >>> histogram_equalization_image(image.astype(np.uint8)).tolist() [[0, 32, 64], [96, 128, 159], [191, 223, 255]] >>> img = np.full((5,5), 128, dtype=np.uint8) >>> img[1:3, 1:3] = 200 # Create a bright region >>> histogram_equalization_image(img).tolist() [[0, 0, 0, 0, 0], [0, 255, 255, 0, 0], [0, 255, 255, 0, 0], [0, 0, 0, 0, 0], [0, 0, 0, 0, 0]] >>> rgb = np.full((3,3,3), 128, dtype=np.uint8) >>> rgb[1, 1, :] = 50 # Create a dark region >>> equalized_rgb = histogram_equalization_image(rgb) >>> bool(np.std(equalized_rgb) > np.std(rgb)) # Should enhance contrast True >>> equalized_rgb.tolist() [[[255, 255, 255], [255, 255, 255], [255, 255, 255]], [[255, 255, 255], [0, 0, 0], [255, 255, 255]], [[255, 255, 255], [255, 255, 255], [255, 255, 255]]] >>> ## Test each color space >>> test_img = np.zeros((20, 20, 3), dtype=np.uint8) >>> test_img[5:15, 5:15] = 200 # Add contrast region >>> # Test LAB color space >>> lab_result = histogram_equalization_image(test_img, color_space="lab") >>> isinstance(lab_result, np.ndarray) and lab_result.shape == test_img.shape True >>> bool(np.std(lab_result) > np.std(test_img)) # Verify contrast enhancement True >>> # Test YCbCr color space >>> ycbcr_result = histogram_equalization_image(test_img, color_space="ycbcr") >>> isinstance(ycbcr_result, np.ndarray) and ycbcr_result.shape == test_img.shape True >>> bool(np.std(ycbcr_result) > np.std(test_img)) # Verify contrast enhancement True >>> # Test HSV color space >>> hsv_result = histogram_equalization_image(test_img, color_space="hsv") >>> isinstance(hsv_result, np.ndarray) and hsv_result.shape == test_img.shape True >>> bool(np.std(hsv_result) > np.std(test_img)) # Verify contrast enhancement True >>> ## Test invalid inputs >>> histogram_equalization_image("not an image") Traceback (most recent call last): ... AssertionError: Image must be a numpy array >>> histogram_equalization_image(rgb, "invalid_space") Traceback (most recent call last): ... AssertionError: color_space must be one of: lab, ycbcr, hsv """ # noqa: E501 # Check input data check_image(image, ignore_dtype=ignore_dtype) lowered_color_space = color_space.lower() assert lowered_color_space in VALID_SPACES, f"color_space must be one of: {', '.join(VALID_SPACES)}" # Handle different image types if len(image.shape) == 2: # Grayscale image - just apply histogram equalization directly return cv2.equalizeHist(image) else: # Color image - apply equalization based on selected color space convert_to, convert_from, channel_idx = COLOR_SPACE_CONSTANTS[lowered_color_space] # Convert to target color space converted: NDArray[Any] = cv2.cvtColor(image, convert_to) # Split channels channels: list[NDArray[Any]] = list(cv2.split(converted)) # Apply histogram equalization to the appropriate channel channels[channel_idx] = cv2.equalizeHist(channels[channel_idx]) # Merge channels result: NDArray[Any] = cv2.merge(channels) # Convert back to BGR return cv2.cvtColor(result, convert_from)