alexnasa commited on
Commit
312d6de
·
verified ·
1 Parent(s): 849f41d

allow click zoom selection

Browse files
Files changed (1) hide show
  1. app.py +103 -67
app.py CHANGED
@@ -46,92 +46,67 @@ def make_preview_with_boxes(
46
  scale_option: str,
47
  cx_norm: float,
48
  cy_norm: float,
49
- ) -> Image.Image:
50
  """
51
- 1) Open the uploaded image, resize & center-crop to 512×512.
52
- 2) Let scale_int = int(scale_option.replace("x","")).
53
- Then the four nested crop‐sizes (in pixels) are:
54
- size[0] = 512 / (scale_int^1),
55
- size[1] = 512 / (scale_int^2),
56
- size[2] = 512 / (scale_int^3),
57
- size[3] = 512 / (scale_int^4).
58
- 3) Iteratively compute each crop’s top-left in “original 512×512” space:
59
- - Start with prev_tl = (0,0), prev_size = 512.
60
- - For i in [0..3]:
61
- center_abs_x = prev_tl_x + cx_norm * prev_size
62
- center_abs_y = prev_tl_y + cy_norm * prev_size
63
- unc_x0 = center_abs_x - (size[i]/2)
64
- unc_y0 = center_abs_y - (size[i]/2)
65
- clamp x0 ∈ [prev_tl_x, prev_tl_x + prev_size - size[i]]
66
- y0 ∈ [prev_tl_y, prev_tl_y + prev_size - size[i]]
67
- Draw a rectangle from (x0, y0) to (x0 + size[i], y0 + size[i]).
68
- Then set prev_tl = (x0, y0), prev_size = size[i].
69
- 4) Return the PIL image with those four truly nested outlines.
70
  """
71
  try:
72
  orig = Image.open(image_path).convert("RGB")
73
  except Exception as e:
74
- # On error, return a gray 512×512 with the error text
75
  fallback = Image.new("RGB", (512, 512), (200, 200, 200))
76
- draw = ImageDraw.Draw(fallback)
77
- draw.text((20, 20), f"Error:\n{e}", fill="red")
78
- return fallback
79
 
80
- # 1) Resize & center-crop to 512×512
81
  base = resize_and_center_crop(orig, 512)
82
 
83
- # 2) Compute the four nested crop‐sizes
84
- scale_int = int(scale_option.replace("x", "")) # e.g. "4x" → 4
85
  if scale_int <= 1:
86
- # If 1×, then all “nested” sizes are 512 (no real nesting)
87
- sizes = [512, 512, 512, 512]
88
  else:
89
- sizes = [
90
- 512 // (scale_int ** (i + 1))
91
- for i in range(4)
92
- ]
93
- # e.g. if scale_int=4 → sizes = [128, 32, 8, 2]
94
 
95
  draw = ImageDraw.Draw(base)
96
  colors = ["red", "lime", "cyan", "yellow"]
97
  width = 3
98
 
99
- # 3) Iteratively compute nested rectangles
100
- prev_tl_x, prev_tl_y = 0.0, 0.0
101
- prev_size = 512.0
102
 
103
- for idx, crop_size in enumerate(sizes):
104
- # 3.a) Where is the “normalized center” in this current 512×512 region?
105
- center_abs_x = prev_tl_x + (cx_norm * prev_size)
106
- center_abs_y = prev_tl_y + (cy_norm * prev_size)
107
 
108
- # 3.b) Unclamped top-left for this crop
109
- unc_x0 = center_abs_x - (crop_size / 2.0)
110
- unc_y0 = center_abs_y - (crop_size / 2.0)
111
 
112
- # 3.c) Clamp so the crop window stays inside [prev_tl .. prev_tl + prev_size]
113
- min_x0 = prev_tl_x
114
- max_x0 = prev_tl_x + prev_size - crop_size
115
- min_y0 = prev_tl_y
116
- max_y0 = prev_tl_y + prev_size - crop_size
117
 
118
- x0 = max(min_x0, min(unc_x0, max_x0))
119
- y0 = max(min_y0, min(unc_y0, max_y0))
120
  x1 = x0 + crop_size
121
  y1 = y0 + crop_size
122
 
123
- # Draw the rectangle (cast to int for pixels)
124
- draw.rectangle(
125
- [(int(x0), int(y0)), (int(x1), int(y1))],
126
- outline=colors[idx % len(colors)],
127
- width=width
128
- )
 
 
 
 
 
 
 
 
129
 
130
- # 3.d) Update for the next iteration
131
- prev_tl_x, prev_tl_y = x0, y0
132
- prev_size = crop_size
133
 
134
- return base
135
 
136
 
137
  # ------------------------------------------------------------------
@@ -186,6 +161,51 @@ def run_with_upload(
186
  # Return the list of PIL images (Gradio Gallery expects a list)
187
  return sr_list
188
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
189
 
190
  # ------------------------------------------------------------------
191
  # BUILD THE GRADIO INTERFACE (two sliders + correct preview)
@@ -200,6 +220,8 @@ css = """
200
 
201
  with gr.Blocks(css=css) as demo:
202
 
 
 
203
  with gr.Column(elem_id="col-container"):
204
 
205
  gr.HTML(
@@ -298,28 +320,42 @@ with gr.Blocks(css=css) as demo:
298
  return None
299
  return make_preview_with_boxes(img_path, scale_opt, cx, cy)
300
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
301
  upload_image.change(
302
  fn=update_preview,
303
  inputs=[upload_image, upscale_radio, center_x, center_y],
304
- outputs=[preview_with_box],
305
  show_api=False
306
  )
307
  upscale_radio.change(
308
  fn=update_preview,
309
  inputs=[upload_image, upscale_radio, center_x, center_y],
310
- outputs=[preview_with_box],
311
  show_api=False
312
  )
313
  center_x.change(
314
  fn=update_preview,
315
  inputs=[upload_image, upscale_radio, center_x, center_y],
316
- outputs=[preview_with_box],
317
  show_api=False
318
  )
319
  center_y.change(
320
  fn=update_preview,
321
  inputs=[upload_image, upscale_radio, center_x, center_y],
322
- outputs=[preview_with_box],
323
  show_api=False
324
  )
325
 
@@ -328,8 +364,8 @@ with gr.Blocks(css=css) as demo:
328
  # ------------------------------------------------------------------
329
 
330
  run_button.click(
331
- fn=run_with_upload,
332
- inputs=[upload_image, upscale_radio, center_x, center_y],
333
  outputs=[output_gallery]
334
  )
335
 
 
46
  scale_option: str,
47
  cx_norm: float,
48
  cy_norm: float,
49
+ ) -> tuple[Image.Image, list[tuple[float, float]]]:
50
  """
51
+ Returns:
52
+ - The preview image with drawn boxes.
53
+ - A list of (cx_norm, cy_norm) for each box (normalized to 512×512).
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
54
  """
55
  try:
56
  orig = Image.open(image_path).convert("RGB")
57
  except Exception as e:
 
58
  fallback = Image.new("RGB", (512, 512), (200, 200, 200))
59
+ ImageDraw.Draw(fallback).text((20, 20), f"Error:\n{e}", fill="red")
60
+ return fallback, []
 
61
 
 
62
  base = resize_and_center_crop(orig, 512)
63
 
64
+ scale_int = int(scale_option.replace("x", ""))
 
65
  if scale_int <= 1:
66
+ sizes = [512.0, 512.0, 512.0, 512.0]
 
67
  else:
68
+ sizes = [512.0 / (scale_int ** (i + 1)) for i in range(4)]
 
 
 
 
69
 
70
  draw = ImageDraw.Draw(base)
71
  colors = ["red", "lime", "cyan", "yellow"]
72
  width = 3
73
 
74
+ abs_cx = cx_norm * 512.0
75
+ abs_cy = cy_norm * 512.0
 
76
 
77
+ prev_x0, prev_y0, prev_size = 0.0, 0.0, 512.0
78
+ centers: list[tuple[float, float]] = []
 
 
79
 
80
+ for i, crop_size in enumerate(sizes):
81
+ x0 = abs_cx - (crop_size / 2.0)
82
+ y0 = abs_cy - (crop_size / 2.0)
83
 
84
+ min_x0 = prev_x0
85
+ max_x0 = prev_x0 + prev_size - crop_size
86
+ min_y0 = prev_y0
87
+ max_y0 = prev_y0 + prev_size - crop_size
 
88
 
89
+ x0 = max(min_x0, min(x0, max_x0))
90
+ y0 = max(min_y0, min(y0, max_y0))
91
  x1 = x0 + crop_size
92
  y1 = y0 + crop_size
93
 
94
+ draw.rectangle([(int(round(x0)), int(round(y0))),
95
+ (int(round(x1)), int(round(y1)))],
96
+ outline=colors[i % len(colors)], width=width)
97
+
98
+ # --- compute normalized center of this box ---
99
+ cx_box = ((x0 - prev_x0) + crop_size / 2.0) / float(prev_size)
100
+ cy_box = ((y0 - prev_y0) + crop_size / 2.0) / float(prev_size)
101
+ centers.append((cx_box, cy_box))
102
+
103
+ prev_x0, prev_y0, prev_size = x0, y0, crop_size
104
+
105
+ print(centers)
106
+
107
+ return base, centers
108
 
 
 
 
109
 
 
110
 
111
 
112
  # ------------------------------------------------------------------
 
161
  # Return the list of PIL images (Gradio Gallery expects a list)
162
  return sr_list
163
 
164
+ @spaces.GPU()
165
+ def magnify(
166
+ uploaded_image_path: str,
167
+ upscale_option: str,
168
+ centres: list
169
+ ):
170
+ """
171
+ Perform chain-of-zoom super-resolution on a given image, using recursive multi-scale upscaling centered on a specific point.
172
+
173
+ This function enhances a given image by progressively zooming into a specific point, using a recursive deep super-resolution model.
174
+
175
+ Args:
176
+ uploaded_image_path (str): Path to the input image file on disk.
177
+ upscale_option (str): The desired upscale factor as a string. Valid options are "1x", "2x", and "4x".
178
+ - "1x" means no upscaling.
179
+ - "2x" means 2× enlargement per zoom step.
180
+ - "4x" means 4× enlargement per zoom step.
181
+ centres (list): Normalized list of X-coordinate, Y-coordinate (0 to 1) of the zoom center.
182
+
183
+ Returns:
184
+ list[PIL.Image.Image]: A list of progressively zoomed-in and super-resolved images at each recursion step (typically 4),
185
+ centered around the user-specified point.
186
+
187
+ Note:
188
+ The center point is repeated for each recursion level to maintain consistency during zooming.
189
+ This function uses a modified version of the `recursive_multiscale_sr` pipeline for inference.
190
+ """
191
+ if uploaded_image_path is None:
192
+ return []
193
+
194
+ upscale_value = int(upscale_option.replace("x", ""))
195
+ rec_num = len(centres)
196
+
197
+ # Call the modified SR function
198
+ sr_list, _ = recursive_multiscale_sr(
199
+ uploaded_image_path,
200
+ upscale=upscale_value,
201
+ rec_num=rec_num,
202
+ centers=centres,
203
+ )
204
+
205
+ # Return the list of PIL images (Gradio Gallery expects a list)
206
+ return sr_list
207
+
208
+
209
 
210
  # ------------------------------------------------------------------
211
  # BUILD THE GRADIO INTERFACE (two sliders + correct preview)
 
220
 
221
  with gr.Blocks(css=css) as demo:
222
 
223
+ session_centres = gr.State()
224
+
225
  with gr.Column(elem_id="col-container"):
226
 
227
  gr.HTML(
 
320
  return None
321
  return make_preview_with_boxes(img_path, scale_opt, cx, cy)
322
 
323
+ def get_select_coords(input_img, evt: gr.SelectData):
324
+ i = evt.index[1]
325
+ j = evt.index[0]
326
+
327
+ print(f'selected coordinates:{i},{j}')#
328
+
329
+ w, h = input_img.size
330
+
331
+ return gr.update(value=j/w), gr.update(value=i/h)
332
+
333
+
334
+ preview_with_box.select(get_select_coords, [preview_with_box], [center_x, center_y])
335
+
336
+
337
  upload_image.change(
338
  fn=update_preview,
339
  inputs=[upload_image, upscale_radio, center_x, center_y],
340
+ outputs=[preview_with_box, session_centres],
341
  show_api=False
342
  )
343
  upscale_radio.change(
344
  fn=update_preview,
345
  inputs=[upload_image, upscale_radio, center_x, center_y],
346
+ outputs=[preview_with_box, session_centres],
347
  show_api=False
348
  )
349
  center_x.change(
350
  fn=update_preview,
351
  inputs=[upload_image, upscale_radio, center_x, center_y],
352
+ outputs=[preview_with_box, session_centres],
353
  show_api=False
354
  )
355
  center_y.change(
356
  fn=update_preview,
357
  inputs=[upload_image, upscale_radio, center_x, center_y],
358
+ outputs=[preview_with_box, session_centres],
359
  show_api=False
360
  )
361
 
 
364
  # ------------------------------------------------------------------
365
 
366
  run_button.click(
367
+ fn=magnify,
368
+ inputs=[upload_image, upscale_radio, session_centres],
369
  outputs=[output_gallery]
370
  )
371