Spaces:
Sleeping
Sleeping
update tone mapping curve (3 points Hermite Cubic Spline)
Browse files- app.py +21 -5
- global_tone_mapping.py +55 -16
app.py
CHANGED
|
@@ -4,6 +4,7 @@ from interactive_pipe.data_objects.image import Image
|
|
| 4 |
from global_tone_mapping import apply_s_curve_tone_mapping
|
| 5 |
from color_conversions import rgb_to_hsv, hsv_to_rgb
|
| 6 |
from synthetic_charts import generate_color_wheel
|
|
|
|
| 7 |
import numpy as np
|
| 8 |
|
| 9 |
|
|
@@ -31,10 +32,12 @@ def set_tone_mapping_params(
|
|
| 31 |
shadow_boost: float = (0., [-1., 1.]),
|
| 32 |
highlight_boost: float = (0., [-1., 1.]),
|
| 33 |
exposure: float = (0., [-1., 1.]),
|
|
|
|
| 34 |
global_params: dict = {}
|
| 35 |
) -> None:
|
| 36 |
global_params["shadow_boost"] = shadow_boost
|
| 37 |
global_params["highlight_boost"] = highlight_boost
|
|
|
|
| 38 |
global_params["exposure"] = exposure
|
| 39 |
|
| 40 |
|
|
@@ -44,16 +47,24 @@ def s_curve_tone_mapping(
|
|
| 44 |
apply_tone_curve_on_luma: bool = True,
|
| 45 |
global_params={}
|
| 46 |
) -> np.ndarray:
|
| 47 |
-
exposure = global_params.get("exposure", 0.)
|
| 48 |
shadow_boost = global_params.get("shadow_boost", 0.)
|
| 49 |
highlight_boost = global_params.get("highlight_boost", 0.)
|
|
|
|
|
|
|
| 50 |
if not apply_tone_curve_on_luma:
|
| 51 |
-
return apply_s_curve_tone_mapping(
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 52 |
hsv = rgb_to_hsv(img)
|
| 53 |
luma_tone_mapped = apply_s_curve_tone_mapping(
|
| 54 |
hsv[..., -1],
|
| 55 |
shadow_boost,
|
| 56 |
highlight_boost,
|
|
|
|
| 57 |
exposure
|
| 58 |
)
|
| 59 |
hsv[..., -1] = luma_tone_mapped
|
|
@@ -61,12 +72,14 @@ def s_curve_tone_mapping(
|
|
| 61 |
|
| 62 |
|
| 63 |
def s_curve_visualization(global_params={}) -> Curve:
|
| 64 |
-
exposure = global_params.get("exposure", 0.)
|
| 65 |
shadow_boost = global_params.get("shadow_boost", 0.)
|
| 66 |
highlight_boost = global_params.get("highlight_boost", 0.)
|
|
|
|
|
|
|
| 67 |
x = np.linspace(0, 1, 256)
|
| 68 |
y = apply_s_curve_tone_mapping(x, shadow_boost,
|
| 69 |
highlight_boost,
|
|
|
|
| 70 |
exposure)
|
| 71 |
return Curve(
|
| 72 |
[
|
|
@@ -97,7 +110,6 @@ def pick_image(
|
|
| 97 |
return default_image
|
| 98 |
|
| 99 |
|
| 100 |
-
@interactive_pipeline(gui="gradio")
|
| 101 |
def image_editing_pipeline(sample_image):
|
| 102 |
input_image = pick_image(sample_image)
|
| 103 |
set_tone_mapping_params()
|
|
@@ -108,5 +120,9 @@ def image_editing_pipeline(sample_image):
|
|
| 108 |
|
| 109 |
|
| 110 |
if __name__ == "__main__":
|
|
|
|
|
|
|
|
|
|
|
|
|
| 111 |
img = Image.load_image("image_sample.jpg")
|
| 112 |
-
image_editing_pipeline(img)
|
|
|
|
| 4 |
from global_tone_mapping import apply_s_curve_tone_mapping
|
| 5 |
from color_conversions import rgb_to_hsv, hsv_to_rgb
|
| 6 |
from synthetic_charts import generate_color_wheel
|
| 7 |
+
import argparse
|
| 8 |
import numpy as np
|
| 9 |
|
| 10 |
|
|
|
|
| 32 |
shadow_boost: float = (0., [-1., 1.]),
|
| 33 |
highlight_boost: float = (0., [-1., 1.]),
|
| 34 |
exposure: float = (0., [-1., 1.]),
|
| 35 |
+
contrast: float = (0., [-1., 1.]),
|
| 36 |
global_params: dict = {}
|
| 37 |
) -> None:
|
| 38 |
global_params["shadow_boost"] = shadow_boost
|
| 39 |
global_params["highlight_boost"] = highlight_boost
|
| 40 |
+
global_params["contrast"] = contrast
|
| 41 |
global_params["exposure"] = exposure
|
| 42 |
|
| 43 |
|
|
|
|
| 47 |
apply_tone_curve_on_luma: bool = True,
|
| 48 |
global_params={}
|
| 49 |
) -> np.ndarray:
|
|
|
|
| 50 |
shadow_boost = global_params.get("shadow_boost", 0.)
|
| 51 |
highlight_boost = global_params.get("highlight_boost", 0.)
|
| 52 |
+
contrast = global_params.get("contrast", 0.)
|
| 53 |
+
exposure = global_params.get("exposure", 0.)
|
| 54 |
if not apply_tone_curve_on_luma:
|
| 55 |
+
return apply_s_curve_tone_mapping(
|
| 56 |
+
img,
|
| 57 |
+
shadow_boost,
|
| 58 |
+
highlight_boost,
|
| 59 |
+
contrast,
|
| 60 |
+
exposure
|
| 61 |
+
)
|
| 62 |
hsv = rgb_to_hsv(img)
|
| 63 |
luma_tone_mapped = apply_s_curve_tone_mapping(
|
| 64 |
hsv[..., -1],
|
| 65 |
shadow_boost,
|
| 66 |
highlight_boost,
|
| 67 |
+
contrast,
|
| 68 |
exposure
|
| 69 |
)
|
| 70 |
hsv[..., -1] = luma_tone_mapped
|
|
|
|
| 72 |
|
| 73 |
|
| 74 |
def s_curve_visualization(global_params={}) -> Curve:
|
|
|
|
| 75 |
shadow_boost = global_params.get("shadow_boost", 0.)
|
| 76 |
highlight_boost = global_params.get("highlight_boost", 0.)
|
| 77 |
+
contrast = global_params.get("contrast", 0.)
|
| 78 |
+
exposure = global_params.get("exposure", 0.)
|
| 79 |
x = np.linspace(0, 1, 256)
|
| 80 |
y = apply_s_curve_tone_mapping(x, shadow_boost,
|
| 81 |
highlight_boost,
|
| 82 |
+
contrast,
|
| 83 |
exposure)
|
| 84 |
return Curve(
|
| 85 |
[
|
|
|
|
| 110 |
return default_image
|
| 111 |
|
| 112 |
|
|
|
|
| 113 |
def image_editing_pipeline(sample_image):
|
| 114 |
input_image = pick_image(sample_image)
|
| 115 |
set_tone_mapping_params()
|
|
|
|
| 120 |
|
| 121 |
|
| 122 |
if __name__ == "__main__":
|
| 123 |
+
parser = argparse.ArgumentParser()
|
| 124 |
+
parser.add_argument("-b", "--backend", type=str,
|
| 125 |
+
choices=["gradio", "qt", "mpl"], default="gradio")
|
| 126 |
+
args = parser.parse_args()
|
| 127 |
img = Image.load_image("image_sample.jpg")
|
| 128 |
+
interactive_pipeline(gui=args.backend)(image_editing_pipeline)(img)
|
global_tone_mapping.py
CHANGED
|
@@ -5,7 +5,8 @@ def apply_s_curve_tone_mapping(
|
|
| 5 |
x: np.ndarray,
|
| 6 |
shadow_boost: float = 0.,
|
| 7 |
highlight_boost: float = 0.,
|
| 8 |
-
|
|
|
|
| 9 |
) -> np.ndarray:
|
| 10 |
"""
|
| 11 |
Apply an S-curve tone mapping to input x with given parameters.
|
|
@@ -14,31 +15,69 @@ def apply_s_curve_tone_mapping(
|
|
| 14 |
x : array-like
|
| 15 |
Input values in the range [0, 1].
|
| 16 |
shadow_boost : float
|
| 17 |
-
Shadow boost parameter
|
| 18 |
-
Positive values decreases the steepness in shadows.
|
| 19 |
highlight_boost : float
|
| 20 |
-
Highlight boost parameter
|
| 21 |
-
|
|
|
|
| 22 |
exposure : float
|
| 23 |
Exposure adjustment parameter. When exposure=0, 0.5 maps to 0.5.
|
|
|
|
| 24 |
|
| 25 |
Returns:
|
| 26 |
y : array-like
|
| 27 |
Tone-mapped output values in the range [0, 1].
|
| 28 |
"""
|
| 29 |
-
x_expo = np.clip(x * 2 ** exposure, 0, 1)
|
| 30 |
|
| 31 |
-
#
|
| 32 |
-
|
| 33 |
-
|
| 34 |
|
| 35 |
-
#
|
| 36 |
-
|
| 37 |
-
|
|
|
|
| 38 |
|
| 39 |
-
#
|
| 40 |
-
|
| 41 |
-
|
| 42 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 43 |
|
| 44 |
return y
|
|
|
|
| 5 |
x: np.ndarray,
|
| 6 |
shadow_boost: float = 0.,
|
| 7 |
highlight_boost: float = 0.,
|
| 8 |
+
contrast: float = 0.,
|
| 9 |
+
exposure: float = 0.
|
| 10 |
) -> np.ndarray:
|
| 11 |
"""
|
| 12 |
Apply an S-curve tone mapping to input x with given parameters.
|
|
|
|
| 15 |
x : array-like
|
| 16 |
Input values in the range [0, 1].
|
| 17 |
shadow_boost : float
|
| 18 |
+
Shadow boost parameter. Positive values increase the steepness in shadows.
|
|
|
|
| 19 |
highlight_boost : float
|
| 20 |
+
Highlight boost parameter. Positive values increase the steepness in highlights.
|
| 21 |
+
contrast : float
|
| 22 |
+
Contrast parameter. Positive values increase the steepness at midtones.
|
| 23 |
exposure : float
|
| 24 |
Exposure adjustment parameter. When exposure=0, 0.5 maps to 0.5.
|
| 25 |
+
Exposure defines the mapping of 0.5 to 0.5 * (2 ** exposure).
|
| 26 |
|
| 27 |
Returns:
|
| 28 |
y : array-like
|
| 29 |
Tone-mapped output values in the range [0, 1].
|
| 30 |
"""
|
|
|
|
| 31 |
|
| 32 |
+
# Compute the midtone mapped value based on exposure
|
| 33 |
+
midtone_mapped_value = 0.5 * (2 ** exposure)
|
| 34 |
+
midtone_mapped_value = np.clip(midtone_mapped_value, 0, 1)
|
| 35 |
|
| 36 |
+
# Define the slopes (derivatives) at the three key points
|
| 37 |
+
s0 = 1 + shadow_boost # Slope at x = 0 (shadows)
|
| 38 |
+
s1 = 1 + contrast # Slope at x = 0.5 (midtone)
|
| 39 |
+
s2 = 1 + highlight_boost # Slope at x = 1 (highlights)
|
| 40 |
|
| 41 |
+
# Initialize the output array
|
| 42 |
+
y = np.zeros_like(x)
|
| 43 |
+
|
| 44 |
+
# Segment 1: x in [0, 0.5]
|
| 45 |
+
idx1 = x <= 0.5
|
| 46 |
+
x0, x1 = 0.0, 0.5
|
| 47 |
+
y0, y1 = 0.0, midtone_mapped_value
|
| 48 |
+
m0, m1 = s0, s1
|
| 49 |
+
delta_x = x1 - x0
|
| 50 |
+
|
| 51 |
+
t = (x[idx1] - x0) / delta_x # Normalize x to parameter t in [0, 1]
|
| 52 |
+
|
| 53 |
+
# Hermite basis functions
|
| 54 |
+
h00 = 2 * t ** 3 - 3 * t ** 2 + 1
|
| 55 |
+
h10 = t ** 3 - 2 * t ** 2 + t
|
| 56 |
+
h01 = -2 * t ** 3 + 3 * t ** 2
|
| 57 |
+
h11 = t ** 3 - t ** 2
|
| 58 |
+
|
| 59 |
+
# Calculate the spline for the first segment
|
| 60 |
+
y[idx1] = (h00 * y0 + h10 * m0 * delta_x + h01 * y1 + h11 * m1 * delta_x)
|
| 61 |
+
|
| 62 |
+
# Segment 2: x in (0.5, 1]
|
| 63 |
+
idx2 = x > 0.5
|
| 64 |
+
x0, x1 = 0.5, 1.0
|
| 65 |
+
y0, y1 = midtone_mapped_value, 1.0
|
| 66 |
+
m0, m1 = s1, s2
|
| 67 |
+
delta_x = x1 - x0
|
| 68 |
+
|
| 69 |
+
t = (x[idx2] - x0) / delta_x # Normalize x to parameter t in [0, 1]
|
| 70 |
+
|
| 71 |
+
# Hermite basis functions for the second segment
|
| 72 |
+
h00 = 2 * t ** 3 - 3 * t ** 2 + 1
|
| 73 |
+
h10 = t ** 3 - 2 * t ** 2 + t
|
| 74 |
+
h01 = -2 * t ** 3 + 3 * t ** 2
|
| 75 |
+
h11 = t ** 3 - t ** 2
|
| 76 |
+
|
| 77 |
+
# Calculate the spline for the second segment
|
| 78 |
+
y[idx2] = (h00 * y0 + h10 * m0 * delta_x + h01 * y1 + h11 * m1 * delta_x)
|
| 79 |
+
|
| 80 |
+
# Ensure the output is within [0, 1]
|
| 81 |
+
y = np.clip(y, 0, 1)
|
| 82 |
|
| 83 |
return y
|