Spaces:
Running
on
Zero
Running
on
Zero
allow click zoom selection
Browse files
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 |
-
|
52 |
-
|
53 |
-
|
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 |
-
|
77 |
-
|
78 |
-
return fallback
|
79 |
|
80 |
-
# 1) Resize & center-crop to 512×512
|
81 |
base = resize_and_center_crop(orig, 512)
|
82 |
|
83 |
-
|
84 |
-
scale_int = int(scale_option.replace("x", "")) # e.g. "4x" → 4
|
85 |
if scale_int <= 1:
|
86 |
-
|
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 |
-
|
100 |
-
|
101 |
-
prev_size = 512.0
|
102 |
|
103 |
-
|
104 |
-
|
105 |
-
center_abs_x = prev_tl_x + (cx_norm * prev_size)
|
106 |
-
center_abs_y = prev_tl_y + (cy_norm * prev_size)
|
107 |
|
108 |
-
|
109 |
-
|
110 |
-
|
111 |
|
112 |
-
|
113 |
-
|
114 |
-
|
115 |
-
|
116 |
-
max_y0 = prev_tl_y + prev_size - crop_size
|
117 |
|
118 |
-
x0 = max(min_x0, min(
|
119 |
-
y0 = max(min_y0, min(
|
120 |
x1 = x0 + crop_size
|
121 |
y1 = y0 + crop_size
|
122 |
|
123 |
-
|
124 |
-
|
125 |
-
|
126 |
-
|
127 |
-
|
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=
|
332 |
-
inputs=[upload_image, upscale_radio,
|
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 |
|