wykonos commited on
Commit
287b4fe
·
verified ·
1 Parent(s): 9a13f24

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +15 -89
app.py CHANGED
@@ -1,4 +1,5 @@
1
 
 
2
  import gradio as gr
3
  from PIL import Image, ImageEnhance, ImageFilter, ImageOps
4
  import numpy as np
@@ -6,6 +7,7 @@ import random
6
  import cv2
7
  import os
8
 
 
9
 
10
  def apply_transformations(
11
  image,
@@ -31,22 +33,17 @@ def apply_transformations(
31
  cutout_ratio
32
  ):
33
  """
34
- Applies a series of transformations to an input image. This function is now
35
- called ONLY when the 'Apply Transformations' button is clicked.
36
  """
37
- # Handle the dictionary input from gr.ImageEditor, which contains the background image
38
  if isinstance(image, dict) and image.get("background") is not None:
39
  image_data = image["background"]
40
  elif isinstance(image, np.ndarray):
41
  image_data = image
42
  else:
43
- # If there's no image data, return None for output and reset mosaic trigger
44
  return None, False
45
 
46
- # Convert NumPy array to PIL Image
47
  img = Image.fromarray(image_data).convert("RGB")
48
 
49
- # Enhanced Augmentation: Mosaic
50
  if apply_mosaic_trigger:
51
  w, h = img.size
52
  cx, cy = w // 2, h // 2
@@ -59,7 +56,6 @@ def apply_transformations(
59
  mosaic_img.paste(crops[3].resize((cx, cy), Image.Resampling.LANCZOS), (cx, cy))
60
  img = mosaic_img
61
 
62
- # Geometric Augmentation: Crop (uses the stored state)
63
  if crop_box is not None:
64
  try:
65
  x1, y1, x2, y2 = map(int, crop_box)
@@ -68,7 +64,6 @@ def apply_transformations(
68
  except (ValueError, TypeError):
69
  pass
70
 
71
- # Apply all other transformations...
72
  if scale_factor != 1.0: img = img.resize((int(img.width * scale_factor), int(img.height * scale_factor)), Image.Resampling.LANCZOS)
73
  if shear_x != 0 or shear_y != 0: img = img.transform(img.size, Image.Transform.AFFINE, (1, shear_x, 0, shear_y, 1, 0), Image.Resampling.BICUBIC)
74
  if rotation_angle != 0: img = img.rotate(rotation_angle, expand=True, fillcolor=(128, 128, 128))
@@ -110,13 +105,10 @@ def apply_transformations(
110
  if grayscale: img = ImageOps.grayscale(img)
111
  if invert: img = ImageOps.invert(img.convert('RGB') if img.mode != 'L' else img)
112
 
113
- # Return final image for the output component and reset the mosaic trigger
114
  return img, False
115
 
116
  # --- UI Helper Functions ---
117
-
118
  def process_selection(evt: gr.SelectData):
119
- """Callback function to CAPTURE the coordinates of a user's selection, to be used later."""
120
  return (evt.index[0], evt.index[1], evt.index[2], evt.index[3])
121
 
122
  def update_slider(min_val, max_val, current_val):
@@ -125,7 +117,6 @@ def update_slider(min_val, max_val, current_val):
125
  return gr.update(minimum=min_val, maximum=max_val, value=new_val)
126
 
127
  def reset_all_controls():
128
- """Returns a tuple of default values to reset all UI components to their initial state."""
129
  return (
130
  False, None, 1.0, 0, False, False, 0.0, 0.0, 1.0, 1.0, 1.0, 0, 1.0, False, False, 0.0, 0, 0, 0, 0.0,
131
  0.1, 3.0, -180, 180, -0.5, 0.5, -0.5, 0.5, 0.0, 3.0, 0.0, 3.0, 0.0, 3.0,
@@ -134,25 +125,21 @@ def reset_all_controls():
134
  )
135
 
136
  def on_upload():
137
- """Returns a partial reset tuple, specifically clearing the output image and resetting controls, but leaves the input image alone."""
138
- # Slices the full reset tuple to exclude the last two items (image_input, image_output)
139
- # and then adds a None for image_output.
140
  return reset_all_controls()[:-2] + (None,)
141
 
142
-
143
  # --- Gradio UI Layout ---
144
 
145
- with gr.Blocks(theme=gr.themes.Soft(), css="footer {display: none !important}") as demo:
146
  gr.Markdown("# Advanced Image Augmentation Tool (Manual Control)")
147
  gr.Markdown("Set your parameters on the left, then click **Apply Transformations** to see the result.")
148
 
 
149
  crop_box_state = gr.State(None)
150
  mosaic_trigger = gr.State(False)
151
 
152
  with gr.Row(variant="panel"):
153
  with gr.Column(scale=1, min_width=400):
154
  gr.Markdown("### Control Panel")
155
- # All control panels remain the same...
156
  with gr.Accordion("Geometric Transformations", open=True):
157
  scale_slider = gr.Slider(0.1, 3.0, 1.0, step=0.05, label="Scale")
158
  rotation_slider = gr.Slider(-180, 180, 0, step=1, label="Rotation Angle")
@@ -174,7 +161,6 @@ with gr.Blocks(theme=gr.themes.Soft(), css="footer {display: none !important}")
174
  mosaic_btn = gr.Button("Apply Mosaic")
175
 
176
  with gr.Accordion("Parameter Range Control (Advanced)", open=False):
177
- # This panel remains unchanged
178
  with gr.Tabs():
179
  with gr.TabItem("Geometric"):
180
  scale_min, scale_max, rotation_min, rotation_max = gr.Number(0.1, label="Scale Min"), gr.Number(3.0, label="Scale Max"), gr.Number(-180, label="Rotation Min"), gr.Number(180, label="Rotation Max")
@@ -191,88 +177,28 @@ with gr.Blocks(theme=gr.themes.Soft(), css="footer {display: none !important}")
191
 
192
  with gr.Column(scale=3):
193
  apply_btn = gr.Button("Apply Transformations", variant="primary")
194
-
195
  image_input = gr.ImageEditor(type="numpy", label="Original Image (Select an area to crop)", interactive=True)
196
  image_output = gr.Image(label="Transformed Image", interactive=False)
197
- gr.Examples(
198
- examples=[os.path.join(os.path.dirname(__file__), f) for f in ["cat.jpg", "cheetah.jpg", "lion.jpg"] if os.path.exists(os.path.join(os.path.dirname(__file__), f))],
199
- inputs=image_input, label="Example Images"
200
- )
201
 
202
- # --- LIST OF ALL INPUTS AND RESETTABLE COMPONENTS ---
203
- all_inputs_for_transform = [
204
- image_input, mosaic_trigger, crop_box_state, scale_slider, rotation_slider, h_flip_check, v_flip_check,
205
- shear_x_slider, shear_y_slider, brightness_slider, contrast_slider, saturation_slider,
206
- hue_slider, gamma_slider, grayscale_check, invert_check, blur_slider, sharpen_slider, noise_slider,
207
- cutout_n_slider, cutout_ratio_slider
208
- ]
209
-
210
- all_resettable_components = [
211
- mosaic_trigger, crop_box_state, scale_slider, rotation_slider, h_flip_check, v_flip_check,
212
- shear_x_slider, shear_y_slider, brightness_slider, contrast_slider, saturation_slider,
213
- hue_slider, gamma_slider, grayscale_check, invert_check, blur_slider, sharpen_slider, noise_slider,
214
- cutout_n_slider, cutout_ratio_slider,
215
- scale_min, scale_max, rotation_min, rotation_max, shear_x_min, shear_x_max, shear_y_min, shear_y_max,
216
- brightness_min, brightness_max, contrast_min, contrast_max, saturation_min, saturation_max,
217
- hue_min, hue_max, gamma_min, gamma_max, blur_min, blur_max, noise_min, noise_max,
218
- cutout_n_min, cutout_n_max, cutout_ratio_min, cutout_ratio_max,
219
- image_input, image_output
220
- ]
221
-
222
  partial_resettable_components = all_resettable_components[:-2] + [image_output]
223
 
224
- # --- STABLE EVENT HANDLING (MANUAL TRIGGER) ---
225
-
226
- # 1. THE MAIN TRIGGER: Apply button click
227
- apply_btn.click(
228
- fn=apply_transformations,
229
- inputs=all_inputs_for_transform,
230
- outputs=[image_output, mosaic_trigger]
231
- )
232
 
233
- # 2. Selecting a crop area ONLY updates the crop state silently. No transformation is run.
234
- image_input.select(
235
- fn=process_selection,
236
- inputs=None,
237
- outputs=[crop_box_state]
238
- )
239
-
240
- # 3. Use SPECIFIC events for upload/clear to reset the UI in a controlled way.
241
- # These do NOT trigger any transformations.
242
- image_input.upload(
243
- fn=on_upload,
244
- inputs=None,
245
- outputs=partial_resettable_components
246
- )
247
- image_input.clear(
248
- fn=reset_all_controls,
249
- inputs=None,
250
- outputs=all_resettable_components
251
- )
252
- reset_btn.click(
253
- fn=reset_all_controls,
254
- inputs=None,
255
- outputs=all_resettable_components
256
- )
257
 
258
- # 4. Other helper buttons also have no effect on images until "Apply" is clicked.
259
  rotate_90_btn.click(lambda x: (x + 90) % 360, inputs=[rotation_slider], outputs=[rotation_slider])
260
  rotate_180_btn.click(lambda: 180, inputs=None, outputs=[rotation_slider])
261
  rotate_270_btn.click(lambda: -90, inputs=None, outputs=[rotation_slider])
262
-
263
- # Mosaic button only updates the state, to be used when "Apply" is clicked.
264
  mosaic_btn.click(lambda: True, None, mosaic_trigger)
265
 
266
- # 5. Range control handlers update the sliders, but do not trigger transformations.
267
- range_map = {
268
- (scale_min, scale_max): scale_slider, (rotation_min, rotation_max): rotation_slider,
269
- (shear_x_min, shear_x_max): shear_x_slider, (shear_y_min, shear_y_max): shear_y_slider,
270
- (brightness_min, brightness_max): brightness_slider, (contrast_min, contrast_max): contrast_slider,
271
- (saturation_min, saturation_max): saturation_slider, (hue_min, hue_max): hue_slider,
272
- (gamma_min, gamma_max): gamma_slider, (blur_min, blur_max): blur_slider,
273
- (noise_min, noise_max): noise_slider, (cutout_n_min, cutout_n_max): cutout_n_slider,
274
- (cutout_ratio_min, cutout_ratio_max): cutout_ratio_slider
275
- }
276
  for (min_comp, max_comp), slider in range_map.items():
277
  min_comp.change(fn=update_slider, inputs=[min_comp, max_comp, slider], outputs=[slider])
278
  max_comp.change(fn=update_slider, inputs=[min_comp, max_comp, slider], outputs=[slider])
 
1
 
2
+
3
  import gradio as gr
4
  from PIL import Image, ImageEnhance, ImageFilter, ImageOps
5
  import numpy as np
 
7
  import cv2
8
  import os
9
 
10
+ # --- Core Transformation Logic ---
11
 
12
  def apply_transformations(
13
  image,
 
33
  cutout_ratio
34
  ):
35
  """
36
+ Applies a series of transformations to an input image.
 
37
  """
 
38
  if isinstance(image, dict) and image.get("background") is not None:
39
  image_data = image["background"]
40
  elif isinstance(image, np.ndarray):
41
  image_data = image
42
  else:
 
43
  return None, False
44
 
 
45
  img = Image.fromarray(image_data).convert("RGB")
46
 
 
47
  if apply_mosaic_trigger:
48
  w, h = img.size
49
  cx, cy = w // 2, h // 2
 
56
  mosaic_img.paste(crops[3].resize((cx, cy), Image.Resampling.LANCZOS), (cx, cy))
57
  img = mosaic_img
58
 
 
59
  if crop_box is not None:
60
  try:
61
  x1, y1, x2, y2 = map(int, crop_box)
 
64
  except (ValueError, TypeError):
65
  pass
66
 
 
67
  if scale_factor != 1.0: img = img.resize((int(img.width * scale_factor), int(img.height * scale_factor)), Image.Resampling.LANCZOS)
68
  if shear_x != 0 or shear_y != 0: img = img.transform(img.size, Image.Transform.AFFINE, (1, shear_x, 0, shear_y, 1, 0), Image.Resampling.BICUBIC)
69
  if rotation_angle != 0: img = img.rotate(rotation_angle, expand=True, fillcolor=(128, 128, 128))
 
105
  if grayscale: img = ImageOps.grayscale(img)
106
  if invert: img = ImageOps.invert(img.convert('RGB') if img.mode != 'L' else img)
107
 
 
108
  return img, False
109
 
110
  # --- UI Helper Functions ---
 
111
  def process_selection(evt: gr.SelectData):
 
112
  return (evt.index[0], evt.index[1], evt.index[2], evt.index[3])
113
 
114
  def update_slider(min_val, max_val, current_val):
 
117
  return gr.update(minimum=min_val, maximum=max_val, value=new_val)
118
 
119
  def reset_all_controls():
 
120
  return (
121
  False, None, 1.0, 0, False, False, 0.0, 0.0, 1.0, 1.0, 1.0, 0, 1.0, False, False, 0.0, 0, 0, 0, 0.0,
122
  0.1, 3.0, -180, 180, -0.5, 0.5, -0.5, 0.5, 0.0, 3.0, 0.0, 3.0, 0.0, 3.0,
 
125
  )
126
 
127
  def on_upload():
 
 
 
128
  return reset_all_controls()[:-2] + (None,)
129
 
 
130
  # --- Gradio UI Layout ---
131
 
132
+ with gr.Blocks(theme=gr.themes.Soft()) as demo:
133
  gr.Markdown("# Advanced Image Augmentation Tool (Manual Control)")
134
  gr.Markdown("Set your parameters on the left, then click **Apply Transformations** to see the result.")
135
 
136
+ # State and Control Panels remain the same...
137
  crop_box_state = gr.State(None)
138
  mosaic_trigger = gr.State(False)
139
 
140
  with gr.Row(variant="panel"):
141
  with gr.Column(scale=1, min_width=400):
142
  gr.Markdown("### Control Panel")
 
143
  with gr.Accordion("Geometric Transformations", open=True):
144
  scale_slider = gr.Slider(0.1, 3.0, 1.0, step=0.05, label="Scale")
145
  rotation_slider = gr.Slider(-180, 180, 0, step=1, label="Rotation Angle")
 
161
  mosaic_btn = gr.Button("Apply Mosaic")
162
 
163
  with gr.Accordion("Parameter Range Control (Advanced)", open=False):
 
164
  with gr.Tabs():
165
  with gr.TabItem("Geometric"):
166
  scale_min, scale_max, rotation_min, rotation_max = gr.Number(0.1, label="Scale Min"), gr.Number(3.0, label="Scale Max"), gr.Number(-180, label="Rotation Min"), gr.Number(180, label="Rotation Max")
 
177
 
178
  with gr.Column(scale=3):
179
  apply_btn = gr.Button("Apply Transformations", variant="primary")
 
180
  image_input = gr.ImageEditor(type="numpy", label="Original Image (Select an area to crop)", interactive=True)
181
  image_output = gr.Image(label="Transformed Image", interactive=False)
182
+ gr.Examples(examples=[os.path.join(os.path.dirname(__file__), f) for f in ["cat.jpg", "cheetah.jpg", "lion.jpg"] if os.path.exists(os.path.join(os.path.dirname(__file__), f))], inputs=image_input, label="Example Images")
 
 
 
183
 
184
+ all_inputs_for_transform = [image_input, mosaic_trigger, crop_box_state, scale_slider, rotation_slider, h_flip_check, v_flip_check, shear_x_slider, shear_y_slider, brightness_slider, contrast_slider, saturation_slider, hue_slider, gamma_slider, grayscale_check, invert_check, blur_slider, sharpen_slider, noise_slider, cutout_n_slider, cutout_ratio_slider]
185
+ all_resettable_components = [mosaic_trigger, crop_box_state, scale_slider, rotation_slider, h_flip_check, v_flip_check, shear_x_slider, shear_y_slider, brightness_slider, contrast_slider, saturation_slider, hue_slider, gamma_slider, grayscale_check, invert_check, blur_slider, sharpen_slider, noise_slider, cutout_n_slider, cutout_ratio_slider, scale_min, scale_max, rotation_min, rotation_max, shear_x_min, shear_x_max, shear_y_min, shear_y_max, brightness_min, brightness_max, contrast_min, contrast_max, saturation_min, saturation_max, hue_min, hue_max, gamma_min, gamma_max, blur_min, blur_max, noise_min, noise_max, cutout_n_min, cutout_n_max, cutout_ratio_min, cutout_ratio_max, image_input, image_output]
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
186
  partial_resettable_components = all_resettable_components[:-2] + [image_output]
187
 
188
+ # FIX 2: Added `api_name="predict"` to the main button's click event.
189
+ apply_btn.click(fn=apply_transformations, inputs=all_inputs_for_transform, outputs=[image_output, mosaic_trigger], api_name="predict")
 
 
 
 
 
 
190
 
191
+ image_input.select(fn=process_selection, inputs=None, outputs=[crop_box_state])
192
+ image_input.upload(fn=on_upload, inputs=None, outputs=partial_resettable_components)
193
+ image_input.clear(fn=reset_all_controls, inputs=None, outputs=all_resettable_components)
194
+ reset_btn.click(fn=reset_all_controls, inputs=None, outputs=all_resettable_components)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
195
 
 
196
  rotate_90_btn.click(lambda x: (x + 90) % 360, inputs=[rotation_slider], outputs=[rotation_slider])
197
  rotate_180_btn.click(lambda: 180, inputs=None, outputs=[rotation_slider])
198
  rotate_270_btn.click(lambda: -90, inputs=None, outputs=[rotation_slider])
 
 
199
  mosaic_btn.click(lambda: True, None, mosaic_trigger)
200
 
201
+ range_map = {(scale_min, scale_max): scale_slider, (rotation_min, rotation_max): rotation_slider, (shear_x_min, shear_x_max): shear_x_slider, (shear_y_min, shear_y_max): shear_y_slider, (brightness_min, brightness_max): brightness_slider, (contrast_min, contrast_max): contrast_slider, (saturation_min, saturation_max): saturation_slider, (hue_min, hue_max): hue_slider, (gamma_min, gamma_max): gamma_slider, (blur_min, blur_max): blur_slider, (noise_min, noise_max): noise_slider, (cutout_n_min, cutout_n_max): cutout_n_slider, (cutout_ratio_min, cutout_ratio_max): cutout_ratio_slider}
 
 
 
 
 
 
 
 
 
202
  for (min_comp, max_comp), slider in range_map.items():
203
  min_comp.change(fn=update_slider, inputs=[min_comp, max_comp, slider], outputs=[slider])
204
  max_comp.change(fn=update_slider, inputs=[min_comp, max_comp, slider], outputs=[slider])