# app.py # Hugging Face Space (Gradio) for color correction: # Takes two images: (1) image to correct, (2) reference image. # Returns a single, color-corrected image. import gradio as gr from PIL import Image import numpy as np import cv2 from skimage import exposure def _to_rgb_np(img: Image.Image) -> np.ndarray: """PIL -> RGB numpy uint8""" if img.mode != "RGB": img = img.convert("RGB") return np.array(img) def _lab_histogram_match(src_rgb: np.ndarray, ref_rgb: np.ndarray) -> np.ndarray: """ Match histogram of src to ref in LAB space and return as RGB uint8. """ src_lab = cv2.cvtColor(src_rgb, cv2.COLOR_RGB2LAB) ref_lab = cv2.cvtColor(ref_rgb, cv2.COLOR_RGB2LAB) matched_lab = exposure.match_histograms(src_lab, ref_lab, channel_axis=2) matched_lab = np.clip(matched_lab, 0, 255).astype(np.uint8) matched_rgb = cv2.cvtColor(matched_lab, cv2.COLOR_LAB2RGB) return matched_rgb def _luminosity_blend(base_rgb: np.ndarray, blend_rgb: np.ndarray) -> np.ndarray: """ Photoshop-like 'Luminosity' blend: keep hue/saturation from base, take luminance from blend. Implemented via HLS space. """ base_hls = cv2.cvtColor(base_rgb, cv2.COLOR_RGB2HLS) blend_hls = cv2.cvtColor(blend_rgb, cv2.COLOR_RGB2HLS) out_hls = base_hls.copy() out_hls[..., 1] = blend_hls[..., 1] # replace Lightness channel out_rgb = cv2.cvtColor(out_hls, cv2.COLOR_HLS2RGB) return out_rgb def color_correct(img_to_correct: Image.Image, reference_img: Image.Image) -> Image.Image: """ Color-correct `img_to_correct` to match the look of `reference_img`. Steps: 1) Histogram match in LAB space. 2) Luminosity blend to preserve original hue/saturation. """ if img_to_correct is None or reference_img is None: return None src = _to_rgb_np(img_to_correct) ref = _to_rgb_np(reference_img) matched = _lab_histogram_match(src, ref) result = _luminosity_blend(src, matched) return Image.fromarray(result) title = "Color Correction (Histogram Match + Luminosity Blend)" description = ( "Upload the image you want to correct (left) and a reference image (right). " "The output transfers the overall tonal feel of the reference while preserving " "the original image's colors via a luminosity blend." ) with gr.Blocks() as demo: gr.Markdown(f"# {title}\n{description}") with gr.Row(): with gr.Column(): img_a = gr.Image(type="pil", label="Image to Correct") with gr.Column(): img_b = gr.Image(type="pil", label="Reference Image") out = gr.Image(type="pil", label="Corrected Image") btn = gr.Button("Run Color Correction") btn.click(color_correct, inputs=[img_a, img_b], outputs=[out]) if __name__ == "__main__": demo.launch()