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 |
|