dipta007 commited on
Commit
736e070
·
verified ·
1 Parent(s): 1349d21

Inputs left, results right; scale picker for the comparison

Browse files
Files changed (4) hide show
  1. README.md +0 -17
  2. app.py +27 -38
  3. dev_ui.py +52 -0
  4. test_app_smoke.py +28 -7
README.md CHANGED
@@ -12,7 +12,6 @@ pinned: true
12
  license: mit
13
  short_description: Zoom any photo to 256x, one 4x step at a time
14
  tags:
15
- - arxiv:2609.06490
16
  - super-resolution
17
  - image-to-image
18
  - diffusion
@@ -34,8 +33,6 @@ from the last.
34
  - Code: https://github.com/dipta007/OracleZoom
35
  - Project page: https://dipta007.github.io/OracleZoom/
36
  - Model: https://huggingface.co/dipta007/OracleZoom
37
- - Training data: https://huggingface.co/datasets/dipta007/OracleZoom-4KLSDB-train
38
- - Collection (everything in one place): https://huggingface.co/collections/dipta007/oraclezoom
39
 
40
  ## Running it yourself
41
 
@@ -60,17 +57,3 @@ on three `torch.load` calls, whose default flipped in torch 2.6.
60
  Past the first step or two there is no ground truth left to recover, so the deep levels are
61
  plausible detail rather than measured detail. The zoom is a crop of your photo, not a real
62
  lens moving closer. The paper says which claims we do and do not make.
63
-
64
- ## Citation
65
-
66
- ```bibtex
67
- @misc{dipta2026oraclezoomonpolicyselfdistillationinspired,
68
- title={OracleZoom: On-Policy Self-Distillation Inspired Reference-Constrained Recursive Image Super Resolution},
69
- author={Shubhashis Roy Dipta and Sourajit Saha and Shaswati Saha and Nobin Sarwar},
70
- year={2026},
71
- eprint={2609.06490},
72
- archivePrefix={arXiv},
73
- primaryClass={cs.CV},
74
- url={https://arxiv.org/abs/2609.06490}
75
- }
76
- ```
 
12
  license: mit
13
  short_description: Zoom any photo to 256x, one 4x step at a time
14
  tags:
 
15
  - super-resolution
16
  - image-to-image
17
  - diffusion
 
33
  - Code: https://github.com/dipta007/OracleZoom
34
  - Project page: https://dipta007.github.io/OracleZoom/
35
  - Model: https://huggingface.co/dipta007/OracleZoom
 
 
36
 
37
  ## Running it yourself
38
 
 
57
  Past the first step or two there is no ground truth left to recover, so the deep levels are
58
  plausible detail rather than measured detail. The zoom is a crop of your photo, not a real
59
  lens moving closer. The paper says which claims we do and do not make.
 
 
 
 
 
 
 
 
 
 
 
 
 
 
app.py CHANGED
@@ -53,8 +53,12 @@ CSS = """
53
  .contain {max-width:1400px !important}
54
  footer {display:none !important}
55
  #zoom-target img {cursor:crosshair}
56
- /* a deep run wraps to two rows; keep any scrollbar from rendering light on a dark theme */
57
- #levels, #levels * {scrollbar-color: var(--border-color-primary) transparent}
 
 
 
 
58
  """
59
 
60
 
@@ -88,13 +92,10 @@ def pick_point(evt: gr.SelectData):
88
 
89
 
90
  def _gallery_paths(gallery):
91
- """The (path, label) pairs Gradio hands back for a Gallery value."""
92
- out = []
93
- for item in gallery or []:
94
- img = item.get("image", item) if isinstance(item, dict) else item
95
- path = img.get("path") if isinstance(img, dict) else img
96
- out.append((path, (item.get("caption") if isinstance(item, dict) else None) or ""))
97
- return out
98
 
99
 
100
  def pick_scale(gallery, cx, cy, choice):
@@ -120,14 +121,14 @@ def scale_choices(levels):
120
  return [label(UPSCALE ** n) for n in range(1, int(levels) + 1)]
121
 
122
 
123
- GALLERY_ROW_H = 245
124
 
125
 
126
  def gallery_shape(levels):
127
- """One row up to five levels, two beyond. Tall enough that neither row ever scrolls."""
128
  items = int(levels) + 1
129
  rows = 1 if items <= 5 else 2
130
- return gr.update(columns=-(-items // rows), rows=rows, height=GALLERY_ROW_H * rows)
131
 
132
 
133
  def estimate_duration(image, levels, cx, cy):
@@ -183,12 +184,16 @@ def _example_scale_options():
183
  with gr.Blocks(theme=gr.themes.Soft(primary_hue="amber"), css=CSS,
184
  title="OracleZoom: zoom past 256x") as demo:
185
  gr.HTML(HEADER)
186
- # Three wide rows, not one tall stack: set it up, watch it, then inspect it. Each row uses
187
- # the full width, which is what keeps four large visuals from queueing up vertically.
188
  with gr.Row(equal_height=False):
189
  with gr.Column(scale=4):
190
  image = gr.Image(label="Your photo", type="pil", height=300,
191
  sources=["upload", "clipboard"])
 
 
 
 
192
  # Seeded with the example so the box overlay explains itself before any upload.
193
  target = gr.Image(value=preview(None, 4, 0.5, 0.5), elem_id="zoom-target",
194
  label="Click to move the zoom point", type="pil",
@@ -202,37 +207,21 @@ with gr.Blocks(theme=gr.themes.Soft(primary_hue="amber"), css=CSS,
202
  f"Fewer steps finish sooner.")
203
  go = gr.Button("🔎 Zoom in", variant="primary", size="lg")
204
  status = gr.Markdown("Showing an example. Upload a photo, then press **Zoom in**.")
 
 
205
  with gr.Column(scale=6):
206
  clip = gr.Video(value=EXAMPLE_CLIP, label="The zoom", elem_id="hero", autoplay=True,
207
- loop=True, show_share_button=True, height=470)
208
-
209
- with gr.Row():
210
- gallery = gr.Gallery(value=EXAMPLE_LEVELS, label="Every level, start to finish",
211
- elem_id="levels", object_fit="cover", show_download_button=True,
212
- **{k: v for k, v in gallery_shape(len(EXAMPLE_LEVELS) - 1).items()
213
- if k != "__type__"})
214
-
215
- with gr.Row(equal_height=False):
216
- with gr.Column(scale=5):
217
  scale = gr.Radio(_example_scale_options(), value=EXAMPLE_LEVELS[-1][1],
218
  label="Compare at",
219
  info="Left is the plain enlargement that step began from, "
220
  "right is what OracleZoom drew. Switching is free.")
221
- compare = gr.ImageSlider(value=EXAMPLE_COMPARE, height=480, elem_id="compare",
222
  show_label=False)
223
- with gr.Column(scale=5):
224
- gr.Markdown("##### Vision Language Model at each step:")
225
- prompts = gr.Markdown(EXAMPLE_NOTE)
226
-
227
- with gr.Row():
228
- gr.Examples(
229
- examples=[f"samples/{n}.png" for n in SAMPLES],
230
- inputs=[image],
231
- outputs=[status, clip, gallery, compare, prompts, scale],
232
- fn=run_example,
233
- cache_examples=True,
234
- label="Or try one of these (already computed)",
235
- )
236
 
237
  controls = [image, levels, cx, cy]
238
  for c in controls:
 
53
  .contain {max-width:1400px !important}
54
  footer {display:none !important}
55
  #zoom-target img {cursor:crosshair}
56
+ /* Let the level strip size itself. Any fixed height is wrong at some window width or depth:
57
+ too short and it scrolls, too tall and it leaves a dead band. .gallery-container is the one
58
+ that actually carries the height; styling only the outer block leaves the band behind. */
59
+ #levels, #levels .gallery-container, #levels .grid-wrap {
60
+ height:auto !important; max-height:none !important; min-height:0 !important;
61
+ overflow:visible !important; flex-grow:0 !important}
62
  """
63
 
64
 
 
92
 
93
 
94
  def _gallery_paths(gallery):
95
+ """Gradio hands a Gallery back to a handler as (media, caption) tuples, media being a path
96
+ because the component is filepath-typed. Not dicts, which is what the payload looks like."""
97
+ return [(item[0], item[1] or "") for item in gallery or []
98
+ if isinstance(item, (tuple, list)) and len(item) == 2]
 
 
 
99
 
100
 
101
  def pick_scale(gallery, cx, cy, choice):
 
121
  return [label(UPSCALE ** n) for n in range(1, int(levels) + 1)]
122
 
123
 
124
+ CLIP_H = 480 # tuned so the right column ends level with the Zoom in button
125
 
126
 
127
  def gallery_shape(levels):
128
+ """One row up to five levels, two beyond. Height is left to CSS, which hugs the content."""
129
  items = int(levels) + 1
130
  rows = 1 if items <= 5 else 2
131
+ return gr.update(columns=-(-items // rows), rows=rows)
132
 
133
 
134
  def estimate_duration(image, levels, cx, cy):
 
184
  with gr.Blocks(theme=gr.themes.Soft(primary_hue="amber"), css=CSS,
185
  title="OracleZoom: zoom past 256x") as demo:
186
  gr.HTML(HEADER)
187
+ # Inputs down the left, results down the right. Both columns carry real content the whole
188
+ # way, which is what stops one of them stretching a component into an empty band.
189
  with gr.Row(equal_height=False):
190
  with gr.Column(scale=4):
191
  image = gr.Image(label="Your photo", type="pil", height=300,
192
  sources=["upload", "clipboard"])
193
+ # Inputs only, no fn: the outputs it would fill live further down the page, and
194
+ # Gradio can only wire components that already exist.
195
+ gr.Examples(examples=[f"samples/{n}.png" for n in SAMPLES], inputs=[image],
196
+ label="Or try any of these")
197
  # Seeded with the example so the box overlay explains itself before any upload.
198
  target = gr.Image(value=preview(None, 4, 0.5, 0.5), elem_id="zoom-target",
199
  label="Click to move the zoom point", type="pil",
 
207
  f"Fewer steps finish sooner.")
208
  go = gr.Button("🔎 Zoom in", variant="primary", size="lg")
209
  status = gr.Markdown("Showing an example. Upload a photo, then press **Zoom in**.")
210
+ gr.Markdown("##### Vision Language Model at each step:")
211
+ prompts = gr.Markdown(EXAMPLE_NOTE)
212
  with gr.Column(scale=6):
213
  clip = gr.Video(value=EXAMPLE_CLIP, label="The zoom", elem_id="hero", autoplay=True,
214
+ loop=True, show_share_button=True, height=CLIP_H)
215
+ gallery = gr.Gallery(value=EXAMPLE_LEVELS, label="Every level, start to finish",
216
+ elem_id="levels", object_fit="cover", show_download_button=True,
217
+ **{k: v for k, v in gallery_shape(len(EXAMPLE_LEVELS) - 1).items()
218
+ if k != "__type__"})
 
 
 
 
 
219
  scale = gr.Radio(_example_scale_options(), value=EXAMPLE_LEVELS[-1][1],
220
  label="Compare at",
221
  info="Left is the plain enlargement that step began from, "
222
  "right is what OracleZoom drew. Switching is free.")
223
+ compare = gr.ImageSlider(value=EXAMPLE_COMPARE, height=470, elem_id="compare",
224
  show_label=False)
 
 
 
 
 
 
 
 
 
 
 
 
 
225
 
226
  controls = [image, levels, cx, cy]
227
  for c in controls:
dev_ui.py ADDED
@@ -0,0 +1,52 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Run the interface locally with the models stubbed out, for laying out the UI.
2
+
3
+ No GPU, no weights, no downloads. The fake zoom just bicubic-enlarges, so the pictures are
4
+ meant to be blurry: this is for arranging the page, not for judging output.
5
+
6
+ uv run --no-project --python 3.12 --with "gradio==5.50.0" --with spaces --with pillow \
7
+ --with numpy --with imageio --with imageio-ffmpeg python space/dev_ui.py
8
+ """
9
+ import os
10
+ import sys
11
+ import types
12
+
13
+ from PIL import Image
14
+
15
+ HERE = os.path.dirname(os.path.abspath(__file__))
16
+ sys.path.insert(0, HERE)
17
+ os.chdir(HERE)
18
+
19
+ import geometry # noqa: E402 (after the path insert, on purpose)
20
+
21
+ FAKE_PROMPTS = [
22
+ "spotted fur, whiskers, amber eye, fine hairs",
23
+ "dense guard hairs, rosette edge, warm tones",
24
+ "single hair shafts, follicle, soft focus",
25
+ "fine keratin fibre, grain, pale strands",
26
+ "fibre bundle, faint striations",
27
+ "surface texture, low contrast",
28
+ "granular detail, muted",
29
+ "flat texture, little structure",
30
+ ]
31
+
32
+
33
+ def fake_zoom(models, image, levels=4, upscale=4, center=(0.5, 0.5)):
34
+ cur = geometry.resize_and_center_crop(image)
35
+ yield 0, 1, "", cur, cur
36
+ for i in range(levels):
37
+ blurry = geometry.zoom_window(cur, upscale, center).resize(cur.size, Image.BICUBIC)
38
+ yield i + 1, upscale ** (i + 1), FAKE_PROMPTS[i % len(FAKE_PROMPTS)], blurry, blurry
39
+ cur = blurry
40
+
41
+
42
+ stub = types.ModuleType("zoom")
43
+ stub.Models = type("Models", (), {"__init__": lambda self, weights=None: None})
44
+ stub.zoom = fake_zoom
45
+ sys.modules["zoom"] = stub
46
+
47
+ import app # noqa: E402 (must follow the stub)
48
+
49
+ if __name__ == "__main__":
50
+ port = int(os.environ.get("PORT", "7861"))
51
+ print(f"stubbed UI on http://127.0.0.1:{port} (blurry output is expected)")
52
+ app.demo.queue().launch(server_port=port, quiet=True)
test_app_smoke.py CHANGED
@@ -113,20 +113,36 @@ def test_the_scale_picker_offers_one_option_per_step(app):
113
  assert "input" not in app.scale_choices(app.MAX_STEPS)
114
 
115
 
 
 
 
 
 
 
 
 
 
 
 
116
  def test_switching_scale_rebuilds_the_pair_without_the_gpu(app):
117
  """Runs off the level images already on screen, so it must not touch zoom.zoom."""
118
- gallery = [{"image": {"path": p}, "caption": c} for p, c in app.EXAMPLE_LEVELS]
119
- left, right = app.pick_scale(gallery, 0.5, 0.5, "16x")
120
- assert left.size == right.size == (512, 512)
121
- from PIL import Image
122
  import numpy as np
 
 
 
123
  expected = np.asarray(Image.open("samples/example_16x.png").convert("RGB"))
124
  assert np.array_equal(np.asarray(right), expected)
125
 
126
 
 
 
 
 
 
 
127
  def test_switching_to_a_scale_that_was_not_run_changes_nothing(app):
128
  import gradio as gr
129
- gallery = [{"image": {"path": p}, "caption": c} for p, c in app.EXAMPLE_LEVELS]
130
  assert app.pick_scale(gallery, 0.5, 0.5, "65536x") == gr.skip()
131
  assert app.pick_scale(gallery, 0.5, 0.5, "input") == gr.skip()
132
 
@@ -136,16 +152,21 @@ def test_the_level_strip_wraps_to_two_rows_instead_of_scrolling(app):
136
  for steps in (1, 2, 3, 4):
137
  shape = app.gallery_shape(steps)
138
  assert shape["rows"] == 1 and shape["columns"] == steps + 1
139
- assert shape["height"] == app.GALLERY_ROW_H
140
  for steps in (5, 6, 7, 8):
141
  shape = app.gallery_shape(steps)
142
  items = steps + 1
143
  assert shape["rows"] == 2
144
- assert shape["height"] == 2 * app.GALLERY_ROW_H
145
  # every item has a cell, and no more than one cell goes spare
146
  assert shape["columns"] * 2 >= items > shape["columns"] * 2 - 2
147
 
148
 
 
 
 
 
 
 
 
149
  def test_a_deep_run_labels_every_level(app, photo):
150
  _, clip, gallery, _, _, scale = list(app.run(photo, app.MAX_STEPS, 0.5, 0.5))[-1]
151
  try:
 
113
  assert "input" not in app.scale_choices(app.MAX_STEPS)
114
 
115
 
116
+ def as_gallery_input(levels):
117
+ """What Gradio actually hands a handler for a filepath Gallery: (path, caption) tuples.
118
+ Passing dicts here instead is what hid a real bug: the picker silently did nothing."""
119
+ return [(path, caption) for path, caption in levels]
120
+
121
+
122
+ def test_a_gallery_input_is_parsed_as_media_caption_tuples(app):
123
+ got = app._gallery_paths(as_gallery_input(app.EXAMPLE_LEVELS))
124
+ assert got == [(p, c) for p, c in app.EXAMPLE_LEVELS]
125
+
126
+
127
  def test_switching_scale_rebuilds_the_pair_without_the_gpu(app):
128
  """Runs off the level images already on screen, so it must not touch zoom.zoom."""
 
 
 
 
129
  import numpy as np
130
+ from PIL import Image
131
+ left, right = app.pick_scale(as_gallery_input(app.EXAMPLE_LEVELS), 0.5, 0.5, "16x")
132
+ assert left.size == right.size == (512, 512)
133
  expected = np.asarray(Image.open("samples/example_16x.png").convert("RGB"))
134
  assert np.array_equal(np.asarray(right), expected)
135
 
136
 
137
+ def test_every_offered_scale_can_be_switched_to(app):
138
+ for _, caption in app.EXAMPLE_LEVELS[1:]:
139
+ pair = app.pick_scale(as_gallery_input(app.EXAMPLE_LEVELS), 0.5, 0.5, caption)
140
+ assert pair[0].size == pair[1].size == (512, 512), caption
141
+
142
+
143
  def test_switching_to_a_scale_that_was_not_run_changes_nothing(app):
144
  import gradio as gr
145
+ gallery = as_gallery_input(app.EXAMPLE_LEVELS)
146
  assert app.pick_scale(gallery, 0.5, 0.5, "65536x") == gr.skip()
147
  assert app.pick_scale(gallery, 0.5, 0.5, "input") == gr.skip()
148
 
 
152
  for steps in (1, 2, 3, 4):
153
  shape = app.gallery_shape(steps)
154
  assert shape["rows"] == 1 and shape["columns"] == steps + 1
 
155
  for steps in (5, 6, 7, 8):
156
  shape = app.gallery_shape(steps)
157
  items = steps + 1
158
  assert shape["rows"] == 2
 
159
  # every item has a cell, and no more than one cell goes spare
160
  assert shape["columns"] * 2 >= items > shape["columns"] * 2 - 2
161
 
162
 
163
+ def test_the_strip_never_pins_a_height(app):
164
+ """A fixed height is wrong at some width: too short scrolls, too tall leaves a dead band.
165
+ CSS hugs the content instead, so nothing here may set height."""
166
+ assert all("height" not in app.gallery_shape(n) for n in range(1, app.MAX_STEPS + 1))
167
+ assert "#levels" in app.CSS and "height:auto !important" in app.CSS
168
+
169
+
170
  def test_a_deep_run_labels_every_level(app, photo):
171
  _, clip, gallery, _, _, scale = list(app.run(photo, app.MAX_STEPS, 0.5, 0.5))[-1]
172
  try: