balthou commited on
Commit
bb8764b
·
1 Parent(s): 41dda1b

update tone mapping curve (3 points Hermite Cubic Spline)

Browse files
Files changed (2) hide show
  1. app.py +21 -5
  2. 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(img, shadow_boost, highlight_boost, exposure)
 
 
 
 
 
 
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
- exposure: float = 0.,
 
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 (tangent).
18
- Positive values decreases the steepness in shadows.
19
  highlight_boost : float
20
- Highlight boost parameter (tangent).
21
- Positive values increase the steepness in highlights.
 
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
- # Adjust s and h parameters
32
- s = 1 - shadow_boost
33
- h = 1 + highlight_boost
34
 
35
- # Compute numerator and denominator for the S-curve formula
36
- numerator = x_expo ** s
37
- denominator = numerator + (1 - x_expo) ** h
 
38
 
39
- # Avoid division by zero
40
- with np.errstate(divide='ignore', invalid='ignore'):
41
- y = numerator / denominator
42
- y = np.nan_to_num(y)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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