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

add color wheel image choice

Browse files
Files changed (5) hide show
  1. app.py +51 -10
  2. color_conversions.py +100 -0
  3. image_sample.jpg +0 -0
  4. image_sample.png +0 -0
  5. synthetic_charts.py +40 -0
app.py CHANGED
@@ -2,6 +2,8 @@ from interactive_pipe import interactive_pipeline, interactive
2
  from interactive_pipe.data_objects.curves import SingleCurve, Curve
3
  from interactive_pipe.data_objects.image import Image
4
  from global_tone_mapping import apply_s_curve_tone_mapping
 
 
5
  import numpy as np
6
 
7
 
@@ -9,10 +11,18 @@ def histogram(img: np.ndarray) -> Curve:
9
  hist_curves = []
10
  for ch in range(img.shape[-1]):
11
  hist, bins = np.histogram(
12
- img[..., ch].flatten(), bins=128, range=(0, 1))
13
- hist_curves.append(SingleCurve(bins[:-1], hist, style='rgb'[ch]+"-"))
14
- hist_curve = Curve(hist_curves,
15
- xlabel="Intensity", ylabel="Frequency")
 
 
 
 
 
 
 
 
16
  return hist_curve
17
 
18
 
@@ -28,16 +38,26 @@ def set_tone_mapping_params(
28
  global_params["exposure"] = exposure
29
 
30
 
31
- def s_curve_tone_mapping(img: np.ndarray, global_params={}) -> np.ndarray:
 
 
 
 
 
32
  exposure = global_params.get("exposure", 0.)
33
  shadow_boost = global_params.get("shadow_boost", 0.)
34
  highlight_boost = global_params.get("highlight_boost", 0.)
35
- return apply_s_curve_tone_mapping(
36
- img,
 
 
 
37
  shadow_boost,
38
  highlight_boost,
39
  exposure
40
  )
 
 
41
 
42
 
43
  def s_curve_visualization(global_params={}) -> Curve:
@@ -57,8 +77,29 @@ def s_curve_visualization(global_params={}) -> Curve:
57
  )
58
 
59
 
60
- @ interactive_pipeline(gui="gradio")
61
- def image_editing_pipeline(input_image):
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
62
  set_tone_mapping_params()
63
  tc_image = s_curve_tone_mapping(input_image)
64
  tone_curve = s_curve_visualization()
@@ -67,5 +108,5 @@ def image_editing_pipeline(input_image):
67
 
68
 
69
  if __name__ == "__main__":
70
- img = Image.load_image("image_sample.png")
71
  image_editing_pipeline(img)
 
2
  from interactive_pipe.data_objects.curves import SingleCurve, Curve
3
  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
 
 
11
  hist_curves = []
12
  for ch in range(img.shape[-1]):
13
  hist, bins = np.histogram(
14
+ img[..., ch].flatten(),
15
+ bins=128,
16
+ range=(0, 1)
17
+ )
18
+ hist_curves.append(
19
+ SingleCurve(bins[:-1], hist, style='rgb'[ch]+"-")
20
+ )
21
+ hist_curve = Curve(
22
+ hist_curves,
23
+ xlabel="Intensity",
24
+ ylabel="Frequency"
25
+ )
26
  return hist_curve
27
 
28
 
 
38
  global_params["exposure"] = exposure
39
 
40
 
41
+ @interactive(apply_tone_curve_on_luma=(True,))
42
+ def s_curve_tone_mapping(
43
+ img: np.ndarray,
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
60
+ return hsv_to_rgb(hsv)
61
 
62
 
63
  def s_curve_visualization(global_params={}) -> Curve:
 
77
  )
78
 
79
 
80
+ @interactive(
81
+ input_image=("sample_image", ["sample_image", "color_wheel"]),
82
+ )
83
+ def pick_image(
84
+ default_image: np.ndarray,
85
+ input_image: str = "sample_image",
86
+ global_params={}
87
+ ) -> np.ndarray:
88
+ if input_image == "sample_image":
89
+ return default_image
90
+ elif input_image == "color_wheel":
91
+ color_wheel = global_params.get("color_wheel", None)
92
+ if color_wheel is None:
93
+ color_wheel = generate_color_wheel(resolution=496)
94
+ global_params["color_wheel"] = color_wheel
95
+ return color_wheel
96
+ else:
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()
104
  tc_image = s_curve_tone_mapping(input_image)
105
  tone_curve = s_curve_visualization()
 
108
 
109
 
110
  if __name__ == "__main__":
111
+ img = Image.load_image("image_sample.jpg")
112
  image_editing_pipeline(img)
color_conversions.py ADDED
@@ -0,0 +1,100 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import numpy as np
2
+
3
+
4
+ def rgb_to_hsv(rgb):
5
+ # Ensure the input is a float numpy array and in the range [0, 1]
6
+ rgb = np.asarray(rgb)
7
+
8
+ # Separate the R, G, B channels
9
+ r, g, b = rgb[..., 0], rgb[..., 1], rgb[..., 2]
10
+
11
+ # Max and min of RGB values
12
+ max_rgb = np.max(rgb, axis=-1)
13
+ min_rgb = np.min(rgb, axis=-1)
14
+ delta = max_rgb - min_rgb
15
+
16
+ # Hue calculation
17
+ hue = np.zeros_like(max_rgb)
18
+ mask = delta != 0
19
+
20
+ # Red is max
21
+ idx = (max_rgb == r) & mask
22
+ hue[idx] = (60 * ((g - b) / delta % 6))[idx]
23
+
24
+ # Green is max
25
+ idx = (max_rgb == g) & mask
26
+ hue[idx] = (60 * ((b - r) / delta + 2))[idx]
27
+
28
+ # Blue is max
29
+ idx = (max_rgb == b) & mask
30
+ hue[idx] = (60 * ((r - g) / delta + 4))[idx]
31
+
32
+ # Saturation calculation
33
+ saturation = np.zeros_like(max_rgb)
34
+ saturation[mask] = delta[mask] / max_rgb[mask]
35
+
36
+ # Value calculation
37
+ value = max_rgb
38
+
39
+ # Stack the HSV components together
40
+ hsv = np.stack((hue, saturation, value), axis=-1)
41
+
42
+ return hsv
43
+
44
+
45
+ def hsv_to_rgb(hsv):
46
+ # Ensure the input is a float numpy array and in the range [0, 1]
47
+ hsv = np.asarray(hsv)
48
+
49
+ # Separate the H, S, V channels
50
+ h, s, v = hsv[..., 0], hsv[..., 1], hsv[..., 2]
51
+
52
+ # Chromaticity component
53
+ c = v * s
54
+ x = c * (1 - np.abs((h / 60) % 2 - 1))
55
+ m = v - c
56
+
57
+ # Initialize RGB
58
+ r, g, b = np.zeros_like(h), np.zeros_like(h), np.zeros_like(h)
59
+
60
+ # Compute the RGB components based on the hue range
61
+ h0_60 = (h >= 0) & (h < 60)
62
+ r[h0_60], g[h0_60], b[h0_60] = c[h0_60], x[h0_60], 0
63
+
64
+ h60_120 = (h >= 60) & (h < 120)
65
+ r[h60_120], g[h60_120], b[h60_120] = x[h60_120], c[h60_120], 0
66
+
67
+ h120_180 = (h >= 120) & (h < 180)
68
+ r[h120_180], g[h120_180], b[h120_180] = 0, c[h120_180], x[h120_180]
69
+
70
+ h180_240 = (h >= 180) & (h < 240)
71
+ r[h180_240], g[h180_240], b[h180_240] = 0, x[h180_240], c[h180_240]
72
+
73
+ h240_300 = (h >= 240) & (h < 300)
74
+ r[h240_300], g[h240_300], b[h240_300] = x[h240_300], 0, c[h240_300]
75
+
76
+ h300_360 = (h >= 300) & (h < 360)
77
+ r[h300_360], g[h300_360], b[h300_360] = c[h300_360], 0, x[h300_360]
78
+
79
+ # Add m to each component and stack them together
80
+ rgb = np.stack((r + m, g + m, b + m), axis=-1)
81
+
82
+ return rgb
83
+
84
+
85
+ def test_conversion():
86
+ # RGB array example (range [0, 1])
87
+ rgb = np.array([[[0.5, 0.4, 0.7], [0.1, 0.2, 0.3]],
88
+ [[0.8, 0.7, 0.6], [0.9, 0.1, 0.2]]])
89
+ # rgb = np.random.rand(1000, 100, 3)
90
+ # Convert RGB to HSV
91
+ hsv = rgb_to_hsv(rgb)
92
+ print(hsv.shape)
93
+
94
+ # Convert HSV back to RGB
95
+ rgb_back = hsv_to_rgb(hsv)
96
+ assert np.allclose(rgb, rgb_back), "Conversion failed!"
97
+
98
+
99
+ if __name__ == "__main__":
100
+ test_conversion()
image_sample.jpg ADDED
image_sample.png DELETED
Binary file (899 kB)
 
synthetic_charts.py ADDED
@@ -0,0 +1,40 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+
2
+ import numpy as np
3
+ from color_conversions import hsv_to_rgb
4
+
5
+
6
+ def generate_color_wheel(resolution=500) -> np.ndarray:
7
+ """
8
+ Generate a color wheel image using HSV to RGB conversion.
9
+
10
+ :param resolution: The width/height of the square image
11
+ :return: RGB image of the color wheel
12
+ """
13
+ # Create a 2D grid of indices (X, Y) to calculate the angle and radius
14
+ x = np.linspace(-1, 1, resolution)
15
+ y = np.linspace(-1, 1, resolution)
16
+ xv, yv = np.meshgrid(x, y)
17
+
18
+ # Calculate angle (hue) in degrees
19
+ angle = np.arctan2(yv, xv) * (180 / np.pi) + 180 # Range from 0 to 360
20
+
21
+ # Calculate radius from the center (used for saturation)
22
+ radius = np.sqrt(xv**2 + yv**2)
23
+
24
+ # Normalize radius to range [0, 1]
25
+ radius = np.clip(radius, 0, 1)
26
+
27
+ # HSV components
28
+ hue = angle # Hue is the angle
29
+ saturation = radius
30
+ # Saturation is proportional to the distance from the center
31
+ # Value is constant (1) for maximum brightness
32
+ value = np.ones_like(radius)
33
+
34
+ # Stack the HSV components into a single array
35
+ hsv_image = np.stack((hue, saturation, value), axis=-1)
36
+
37
+ # Convert the HSV image to RGB
38
+ rgb_image = hsv_to_rgb(hsv_image)
39
+
40
+ return rgb_image