VietCat commited on
Commit
6468f09
·
1 Parent(s): a0d96db

Simplify zoom to bare minimum - Gradio handles scroll natively

Browse files

- Removed all custom CSS and HTML attempts
- Simple approach: zoom slider updates image size via cv2.resize
- Gradio Image component automatically shows scroll when needed
- Side-by-side zoom inspector with both detected and preprocessed
- Clean, minimal code - let Gradio handle the rendering

Files changed (1) hide show
  1. app.py +37 -107
app.py CHANGED
@@ -2,10 +2,6 @@ import gradio as gr
2
  import cv2
3
  import numpy as np
4
  from model import TrafficSignDetector
5
- import sys
6
- import io
7
- from PIL import Image
8
- import base64
9
 
10
  # Load the detector
11
  detector = TrafficSignDetector('config.yaml')
@@ -17,15 +13,10 @@ original_preprocessed_image = None
17
  def detect_traffic_signs(image, confidence_threshold):
18
  """
19
  Process the uploaded image and return the image with detected signs.
20
- :param image: PIL Image or numpy array
21
- :param confidence_threshold: confidence threshold from slider
22
- :return: tuple of (detected image, preprocessed image)
23
  """
24
  global original_output_image, original_preprocessed_image
25
 
26
- # Redirect stdout to capture all logs
27
  print(f"Received image type: {type(image)}")
28
- # Convert PIL to numpy if necessary
29
  if hasattr(image, 'convert'):
30
  image = np.array(image)
31
  print(f"Converted PIL to numpy array, shape: {image.shape}")
@@ -34,86 +25,38 @@ def detect_traffic_signs(image, confidence_threshold):
34
  image = cv2.cvtColor(image, cv2.COLOR_RGB2BGR)
35
  print(f"Converted to BGR, shape: {image.shape}")
36
 
37
- # Perform detection with the slider's confidence threshold
38
  result_image, preprocessed_image = detector.detect(image, confidence_threshold=confidence_threshold)
39
 
40
  # Convert back to RGB for Gradio
41
  result_image = cv2.cvtColor(result_image, cv2.COLOR_BGR2RGB)
42
  preprocessed_image = cv2.cvtColor(preprocessed_image, cv2.COLOR_BGR2RGB)
43
 
44
- # Store originals as numpy arrays
45
  original_output_image = result_image.copy()
46
  original_preprocessed_image = preprocessed_image.copy()
47
 
48
  return result_image, preprocessed_image
49
 
50
- def array_to_base64(image_array):
51
- """Convert numpy array to base64 PNG string"""
52
- # Convert to PIL
53
- pil_image = Image.fromarray(image_array.astype('uint8'))
54
-
55
- # Save to bytes
56
- import io as io_module
57
- buffered = io_module.BytesIO()
58
- pil_image.save(buffered, format="PNG")
59
- buffered.seek(0)
60
-
61
- # Convert to base64
62
- img_base64 = base64.b64encode(buffered.getvalue()).decode()
63
- return img_base64
64
-
65
- def apply_zoom_output(zoom_level):
66
- """Apply zoom to output image"""
67
- if original_output_image is None:
68
  return None
69
 
70
  zoom_factor = zoom_level / 100.0
71
- h, w = original_output_image.shape[:2]
72
  new_w = int(w * zoom_factor)
73
  new_h = int(h * zoom_factor)
74
 
75
  if new_w > 0 and new_h > 0:
76
- zoomed = cv2.resize(original_output_image, (new_w, new_h), interpolation=cv2.INTER_LINEAR)
77
- else:
78
- zoomed = original_output_image
79
-
80
- return zoomed
81
-
82
- def apply_zoom_preprocessed(zoom_level):
83
- """Apply zoom to preprocessed image"""
84
- if original_preprocessed_image is None:
85
- return None
86
-
87
- zoom_factor = zoom_level / 100.0
88
- h, w = original_preprocessed_image.shape[:2]
89
- new_w = int(w * zoom_factor)
90
- new_h = int(h * zoom_factor)
91
-
92
- if new_w > 0 and new_h > 0:
93
- zoomed = cv2.resize(original_preprocessed_image, (new_w, new_h), interpolation=cv2.INTER_LINEAR)
94
- else:
95
- zoomed = original_preprocessed_image
96
-
97
- return zoomed
98
-
99
- # Custom CSS for better scrolling
100
- custom_css = """
101
- .zoom-container {
102
- overflow-x: auto !important;
103
- overflow-y: auto !important;
104
- max-height: 600px;
105
- border: 1px solid #ccc;
106
- border-radius: 4px;
107
- padding: 10px;
108
- background-color: #f9f9f9;
109
- display: block !important;
110
- }
111
- """
112
 
113
  # Create Gradio interface
114
- with gr.Blocks(title="Traffic Sign Detector", css=custom_css) as demo:
115
  gr.Markdown("# Traffic Sign Detector")
116
- gr.Markdown("Upload an image to detect traffic signs using YOLOv8. Detection runs automatically when you upload or adjust the threshold.")
117
 
118
  with gr.Row():
119
  input_image = gr.Image(label="Upload Image", type="pil")
@@ -128,41 +71,28 @@ with gr.Blocks(title="Traffic Sign Detector", css=custom_css) as demo:
128
  maximum=0.9,
129
  value=0.30,
130
  step=0.01,
131
- label="Confidence Threshold",
132
- info="Lower values show more detections (less confident). Adjust to find optimal balance."
133
  )
134
 
135
  with gr.Row():
136
  detect_btn = gr.Button("Detect Traffic Signs", variant="primary")
137
  reset_btn = gr.Button("Clear")
138
 
139
- gr.Markdown("### Zoom & Inspect (Use scroll bars for inspection)")
140
-
141
- with gr.Row():
142
- with gr.Column():
143
- gr.Markdown("**Zoom Detected Image**")
144
- zoom_slider_output = gr.Slider(
145
- minimum=50,
146
- maximum=300,
147
- value=100,
148
- step=10,
149
- label="Zoom Level (%)"
150
- )
151
- output_zoomed = gr.Image(label="Zoomed Detected Image", interactive=False)
152
 
153
  with gr.Row():
154
- with gr.Column():
155
- gr.Markdown("**Zoom Preprocessed Image**")
156
- zoom_slider_preprocessed = gr.Slider(
157
- minimum=50,
158
- maximum=300,
159
- value=100,
160
- step=10,
161
- label="Zoom Level (%)"
162
- )
163
- preprocessed_zoomed = gr.Image(label="Zoomed Preprocessed Image", interactive=False)
164
-
165
- # Auto-detect when image is uploaded
166
  input_image.change(
167
  fn=detect_traffic_signs,
168
  inputs=[input_image, confidence_threshold],
@@ -170,7 +100,7 @@ with gr.Blocks(title="Traffic Sign Detector", css=custom_css) as demo:
170
  queue=True
171
  )
172
 
173
- # Auto-detect when threshold is changed
174
  confidence_threshold.change(
175
  fn=detect_traffic_signs,
176
  inputs=[input_image, confidence_threshold],
@@ -178,7 +108,7 @@ with gr.Blocks(title="Traffic Sign Detector", css=custom_css) as demo:
178
  queue=True
179
  )
180
 
181
- # Manual detection button
182
  detect_btn.click(
183
  fn=detect_traffic_signs,
184
  inputs=[input_image, confidence_threshold],
@@ -186,26 +116,26 @@ with gr.Blocks(title="Traffic Sign Detector", css=custom_css) as demo:
186
  queue=True
187
  )
188
 
189
- # Zoom output image
190
- zoom_slider_output.change(
191
- fn=apply_zoom_output,
192
- inputs=[zoom_slider_output],
193
  outputs=[output_zoomed],
194
  queue=False
195
  )
196
 
197
- # Zoom preprocessed image
198
- zoom_slider_preprocessed.change(
199
- fn=apply_zoom_preprocessed,
200
- inputs=[zoom_slider_preprocessed],
201
  outputs=[preprocessed_zoomed],
202
  queue=False
203
  )
204
 
205
- # Clear button
206
  reset_btn.click(
207
  fn=lambda: (None, None, None, 100, 100, None, None),
208
- outputs=[input_image, output_image, preprocessed_image, zoom_slider_output, zoom_slider_preprocessed, output_zoomed, preprocessed_zoomed]
209
  )
210
 
211
  if __name__ == "__main__":
 
2
  import cv2
3
  import numpy as np
4
  from model import TrafficSignDetector
 
 
 
 
5
 
6
  # Load the detector
7
  detector = TrafficSignDetector('config.yaml')
 
13
  def detect_traffic_signs(image, confidence_threshold):
14
  """
15
  Process the uploaded image and return the image with detected signs.
 
 
 
16
  """
17
  global original_output_image, original_preprocessed_image
18
 
 
19
  print(f"Received image type: {type(image)}")
 
20
  if hasattr(image, 'convert'):
21
  image = np.array(image)
22
  print(f"Converted PIL to numpy array, shape: {image.shape}")
 
25
  image = cv2.cvtColor(image, cv2.COLOR_RGB2BGR)
26
  print(f"Converted to BGR, shape: {image.shape}")
27
 
28
+ # Perform detection
29
  result_image, preprocessed_image = detector.detect(image, confidence_threshold=confidence_threshold)
30
 
31
  # Convert back to RGB for Gradio
32
  result_image = cv2.cvtColor(result_image, cv2.COLOR_BGR2RGB)
33
  preprocessed_image = cv2.cvtColor(preprocessed_image, cv2.COLOR_BGR2RGB)
34
 
35
+ # Store originals
36
  original_output_image = result_image.copy()
37
  original_preprocessed_image = preprocessed_image.copy()
38
 
39
  return result_image, preprocessed_image
40
 
41
+ def apply_zoom(image, zoom_level):
42
+ """Resize image based on zoom level"""
43
+ if image is None:
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
44
  return None
45
 
46
  zoom_factor = zoom_level / 100.0
47
+ h, w = image.shape[:2]
48
  new_w = int(w * zoom_factor)
49
  new_h = int(h * zoom_factor)
50
 
51
  if new_w > 0 and new_h > 0:
52
+ zoomed = cv2.resize(image, (new_w, new_h), interpolation=cv2.INTER_LINEAR)
53
+ return zoomed
54
+ return image
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
55
 
56
  # Create Gradio interface
57
+ with gr.Blocks(title="Traffic Sign Detector") as demo:
58
  gr.Markdown("# Traffic Sign Detector")
59
+ gr.Markdown("Upload an image to detect traffic signs using YOLOv8.")
60
 
61
  with gr.Row():
62
  input_image = gr.Image(label="Upload Image", type="pil")
 
71
  maximum=0.9,
72
  value=0.30,
73
  step=0.01,
74
+ label="Confidence Threshold"
 
75
  )
76
 
77
  with gr.Row():
78
  detect_btn = gr.Button("Detect Traffic Signs", variant="primary")
79
  reset_btn = gr.Button("Clear")
80
 
81
+ gr.Markdown("### Zoom Inspector")
82
+ gr.Markdown("**Note:** Use these sliders to zoom in and inspect details. Scroll within the image view.")
 
 
 
 
 
 
 
 
 
 
 
83
 
84
  with gr.Row():
85
+ with gr.Column(scale=1):
86
+ gr.Markdown("**Detected Image**")
87
+ zoom_output = gr.Slider(50, 300, value=100, step=10, label="Zoom %")
88
+ output_zoomed = gr.Image(label="", interactive=False, show_download_button=False)
89
+
90
+ with gr.Column(scale=1):
91
+ gr.Markdown("**Preprocessed Image**")
92
+ zoom_preprocessed = gr.Slider(50, 300, value=100, step=10, label="Zoom %")
93
+ preprocessed_zoomed = gr.Image(label="", interactive=False, show_download_button=False)
94
+
95
+ # Auto-detect on upload
 
96
  input_image.change(
97
  fn=detect_traffic_signs,
98
  inputs=[input_image, confidence_threshold],
 
100
  queue=True
101
  )
102
 
103
+ # Auto-detect on threshold change
104
  confidence_threshold.change(
105
  fn=detect_traffic_signs,
106
  inputs=[input_image, confidence_threshold],
 
108
  queue=True
109
  )
110
 
111
+ # Manual detect button
112
  detect_btn.click(
113
  fn=detect_traffic_signs,
114
  inputs=[input_image, confidence_threshold],
 
116
  queue=True
117
  )
118
 
119
+ # Zoom output
120
+ zoom_output.change(
121
+ fn=lambda z: apply_zoom(original_output_image, z),
122
+ inputs=[zoom_output],
123
  outputs=[output_zoomed],
124
  queue=False
125
  )
126
 
127
+ # Zoom preprocessed
128
+ zoom_preprocessed.change(
129
+ fn=lambda z: apply_zoom(original_preprocessed_image, z),
130
+ inputs=[zoom_preprocessed],
131
  outputs=[preprocessed_zoomed],
132
  queue=False
133
  )
134
 
135
+ # Clear
136
  reset_btn.click(
137
  fn=lambda: (None, None, None, 100, 100, None, None),
138
+ outputs=[input_image, output_image, preprocessed_image, zoom_output, zoom_preprocessed, output_zoomed, preprocessed_zoomed]
139
  )
140
 
141
  if __name__ == "__main__":