dipta007 commited on
Commit
8feebce
·
verified ·
1 Parent(s): af95dfe

Drop the comparison slider, drag the zoom point, allow 8 steps

Browse files
Files changed (1) hide show
  1. app.py +96 -67
app.py CHANGED
@@ -1,4 +1,4 @@
1
- """OracleZoom demo: upload a photo, watch it zoom 4x to 256x."""
2
  import gradio as gr
3
  import spaces
4
  from PIL import Image, ImageDraw
@@ -10,15 +10,15 @@ import zoom
10
  MODELS = zoom.Models()
11
 
12
  UPSCALE = 4
 
13
  ACCENT = "#f5b942"
14
- LABELS = {1: "input", 4: "4x", 16: "16x", 64: "64x", 256: "256x"}
15
  SAMPLES = ["0479", "0064", "0245", "0393", "0457"]
16
 
17
  # Shipped output of a real run, so the page shows the payoff before anyone spends any quota.
 
18
  EXAMPLE_CLIP = "samples/example_zoom.mp4"
19
- EXAMPLE_LEVELS = [("samples/example_1x.png", "input")] + \
20
  [(f"samples/example_{f}x.png", f"{f}x") for f in (4, 16, 64, 256)]
21
- EXAMPLE_COMPARE = ("samples/example_256x_input.png", "samples/example_256x.png")
22
  EXAMPLE_NOTE = "_An example run. Upload a photo above to make your own._"
23
 
24
  HEADER = """
@@ -26,7 +26,7 @@ HEADER = """
26
  <h1 style="margin:0;font-size:2.1em;letter-spacing:-.02em">OracleZoom 🔎</h1>
27
  <p style="margin:.5em 0 .9em;font-size:1.08em;line-height:1.5;opacity:.85">
28
  Zoom into any photo far past what it holds. Four steps of 4x take you to
29
- <b>256x</b>, each one drawn from the last.
30
  </p>
31
  <p style="margin:0;font-size:.95em">
32
  <a href="https://arxiv.org/abs/2609.06490">Paper</a> &nbsp;·&nbsp;
@@ -37,50 +37,76 @@ HEADER = """
37
  </div>
38
  """
39
 
40
- HOW = """
41
- ##### How it goes
 
42
 
43
- 1. Crop your photo to a 512 square.
44
- 2. Zoom 4x into the amber box. That crop is blurry, so a vision language model describes it.
45
- 3. Super-resolve it with that description as the guide.
46
- 4. Repeat on the result. Four rounds reach 256x.
47
-
48
- Runs free on ZeroGPU, so the first zoom after a quiet spell waits for a GPU.
49
- """
50
-
51
- FOOTER = """
52
- ### What you are looking at
53
-
54
- Standard super-resolution models break down well before 16x. OracleZoom gets to 256x by
55
- zooming one 4x step at a time and feeding each result into the next step. At every step a
56
- vision language model writes a short description of the crop, and that description guides
57
- the detail the super-resolution model draws.
58
-
59
- Past the first step or two there is no ground truth to recover, so the deep levels are
60
- **plausible detail, not measured detail**. The zoom point is a crop of your photo, not a
61
- real camera lens moving closer. Read the paper for what we do and do not claim.
62
-
63
- ```bibtex
64
- @inproceedings{dipta2027oraclezoom,
65
- title = {OracleZoom: Reference-Constrained Recursive Super-Resolution},
66
- author = {Roy Dipta, Shubhashis and Saha, Sourajit and Saha, Shaswati and Sarwar, Nobin},
67
- year = {2027}
68
- }
69
- ```
70
- """
71
 
72
  CSS = """
73
  #hero video {border-radius:12px}
74
  .contain {max-width:1400px !important}
75
  footer {display:none !important}
 
 
 
 
76
  """
77
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
78
 
79
  def preview(image, levels, cx, cy):
80
- """Show where the zoom will go, so nobody spends a run to find out."""
81
- if image is None:
82
- return None
83
- canvas = geometry.resize_and_center_crop(image).convert("RGB")
 
 
 
 
84
  rects = geometry.nested_rects(canvas.size, int(levels), UPSCALE, (cx, cy))
85
  dimmed = Image.blend(canvas, Image.new("RGB", canvas.size, (0, 0, 0)), 0.45)
86
  dimmed.paste(canvas.crop(rects[0]), rects[0][:2])
@@ -107,24 +133,22 @@ def _stream(image, levels, cx, cy):
107
  if image is None:
108
  raise gr.Error("Upload a photo first.")
109
  levels = int(levels)
110
- gallery, prompts, frames, compare = [], [], [], None
111
- for step, factor, prompt, blurry, result in zoom.zoom(MODELS, image, levels, UPSCALE, (cx, cy)):
112
  frames.append(result)
113
- gallery.append((result, LABELS.get(factor, f"{factor}x")))
114
  if step:
115
- compare = (blurry, result)
116
- prompts.append(f"**{LABELS[factor]}** &nbsp; {prompt or '_(no prompt)_'}")
117
  left = levels - step
118
  if not step:
119
  status = f"Warmed up. Zooming {levels} step{'s' if levels > 1 else ''}…"
120
  elif left:
121
- status = f"At **{LABELS[factor]}**, {left} step{'s' if left > 1 else ''} to go…"
122
  else:
123
- status = f"At **{LABELS[factor]}**. Rendering the clip…"
124
  # First yield clears any clip left from the previous run; later ones leave it alone.
125
- yield status, (None if not step else gr.skip()), gallery, compare, "\n\n".join(prompts)
126
- yield ("Done. 🔎", video.render(frames, UPSCALE, (cx, cy)), gallery, compare,
127
- "\n\n".join(prompts))
128
 
129
 
130
  @spaces.GPU(duration=estimate_duration)
@@ -140,48 +164,53 @@ def run_example(image):
140
 
141
 
142
  with gr.Blocks(theme=gr.themes.Soft(primary_hue="amber"), css=CSS,
143
- title="OracleZoom: zoom to 256x") as demo:
144
  gr.HTML(HEADER)
145
  with gr.Row():
146
  with gr.Column(scale=4):
147
- image = gr.Image(label="Your photo", type="pil", height=300, sources=["upload", "clipboard"])
 
148
  # Seeded with the example so the box overlay explains itself before any upload.
149
- target = gr.Image(value=preview(Image.open(EXAMPLE_LEVELS[0][0]), 4, 0.5, 0.5),
150
- label="Click to move the zoom point", type="pil",
151
  interactive=False, height=340, show_download_button=False)
152
- levels = gr.Slider(1, 4, value=4, step=1, label="Zoom steps",
153
- info="1 step = 4x, 4 steps = 256x. Fewer steps finish sooner.")
154
- with gr.Accordion("Set the point by hand", open=False):
155
- cx = gr.Slider(0, 1, value=0.5, step=0.01, label="Horizontal")
156
- cy = gr.Slider(0, 1, value=0.5, step=0.01, label="Vertical")
 
 
157
  go = gr.Button("🔎 Zoom in", variant="primary", size="lg")
158
- gr.Markdown(HOW)
159
  with gr.Column(scale=6):
160
  status = gr.Markdown("Upload a photo, then press **Zoom in**.")
161
  clip = gr.Video(value=EXAMPLE_CLIP, label="The zoom", elem_id="hero", autoplay=True,
162
  loop=True, show_share_button=True, height=400)
163
- gallery = gr.Gallery(value=EXAMPLE_LEVELS, label="Every level", columns=5, height=175,
164
- object_fit="cover", show_download_button=True)
165
- compare = gr.ImageSlider(value=EXAMPLE_COMPARE, height=400,
166
- label="256x: plain enlargement (left) vs OracleZoom (right)")
167
- gr.Markdown("##### What the model said it saw, step by step")
168
  prompts = gr.Markdown(EXAMPLE_NOTE)
169
 
170
  controls = [image, levels, cx, cy]
 
171
  for c in controls:
172
  c.change(preview, controls, target, show_api=False)
173
  target.select(pick_point, None, [cx, cy], show_api=False)
174
- go.click(run, controls, [status, clip, gallery, compare, prompts])
175
 
176
  gr.Examples(
177
  examples=[f"samples/{n}.png" for n in SAMPLES],
178
  inputs=[image],
179
- outputs=[status, clip, gallery, compare, prompts],
180
  fn=run_example,
181
  cache_examples=True,
182
  label="Or try one of these (already computed, costs you nothing)",
183
  )
184
- gr.Markdown(FOOTER)
 
 
 
 
185
 
186
  if __name__ == "__main__":
187
  demo.queue(max_size=20).launch()
 
1
+ """OracleZoom demo: upload a photo, watch it zoom 4x and deeper."""
2
  import gradio as gr
3
  import spaces
4
  from PIL import Image, ImageDraw
 
10
  MODELS = zoom.Models()
11
 
12
  UPSCALE = 4
13
+ MAX_STEPS = 8
14
  ACCENT = "#f5b942"
 
15
  SAMPLES = ["0479", "0064", "0245", "0393", "0457"]
16
 
17
  # Shipped output of a real run, so the page shows the payoff before anyone spends any quota.
18
+ EXAMPLE_INPUT = "samples/example_1x.png"
19
  EXAMPLE_CLIP = "samples/example_zoom.mp4"
20
+ EXAMPLE_LEVELS = [(EXAMPLE_INPUT, "input")] + \
21
  [(f"samples/example_{f}x.png", f"{f}x") for f in (4, 16, 64, 256)]
 
22
  EXAMPLE_NOTE = "_An example run. Upload a photo above to make your own._"
23
 
24
  HEADER = """
 
26
  <h1 style="margin:0;font-size:2.1em;letter-spacing:-.02em">OracleZoom 🔎</h1>
27
  <p style="margin:.5em 0 .9em;font-size:1.08em;line-height:1.5;opacity:.85">
28
  Zoom into any photo far past what it holds. Four steps of 4x take you to
29
+ <b>256x</b>, each one drawn from the last. Push it to eight steps if you want.
30
  </p>
31
  <p style="margin:0;font-size:.95em">
32
  <a href="https://arxiv.org/abs/2609.06490">Paper</a> &nbsp;·&nbsp;
 
37
  </div>
38
  """
39
 
40
+ LIMITS = ("Past the first step or two there is no ground truth left to recover, so the deep "
41
+ "levels are **plausible detail, not measured detail**. The zoom is a crop of your "
42
+ "photo, not a real lens moving closer.")
43
 
44
+ BIBTEX = """@misc{dipta2026oraclezoomonpolicyselfdistillationinspired,
45
+ title = {OracleZoom: On-Policy Self-Distillation Inspired Reference-Constrained Recursive Image Super Resolution},
46
+ author = {Shubhashis Roy Dipta and Sourajit Saha and Shaswati Saha and Nobin Sarwar},
47
+ year = {2026},
48
+ eprint = {2609.06490},
49
+ archivePrefix = {arXiv},
50
+ primaryClass = {cs.CV},
51
+ url = {https://arxiv.org/abs/2609.06490}
52
+ }"""
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
53
 
54
  CSS = """
55
  #hero video {border-radius:12px}
56
  .contain {max-width:1400px !important}
57
  footer {display:none !important}
58
+ #zoom-target img {cursor:grab}
59
+ #zoom-target img:active {cursor:grabbing}
60
+ /* a deep run wraps to two rows; keep any scrollbar from rendering light on a dark theme */
61
+ #levels, #levels * {scrollbar-color: var(--border-color-primary) transparent}
62
  """
63
 
64
+ # Drag the zoom point straight on the preview. Throttled, because every move asks the server to
65
+ # redraw. Clicking and the two sliders both still work if this ever stops matching the DOM.
66
+ DRAG_JS = """
67
+ () => {
68
+ if (window.__ozDrag) return; // load can fire more than once; keep one state
69
+ const S = window.__ozDrag = {on: false, last: 0};
70
+ const send = (e, force) => {
71
+ const img = document.querySelector('#zoom-target img');
72
+ if (!img) return;
73
+ const now = Date.now();
74
+ if (!force && now - S.last < 120) return;
75
+ S.last = now;
76
+ const r = img.getBoundingClientRect();
77
+ const at = (v, lo, span) => Math.min(Math.max((v - lo) / span, 0), 1);
78
+ for (const [id, v] of [['zoom-x', at(e.clientX, r.left, r.width)],
79
+ ['zoom-y', at(e.clientY, r.top, r.height)]]) {
80
+ const el = document.querySelector('#' + id + ' input[type=range]');
81
+ if (!el) continue;
82
+ el.value = v.toFixed(2);
83
+ el.dispatchEvent(new Event('input', {bubbles: true}));
84
+ }
85
+ };
86
+ // delegated off document, so Gradio swapping the img or the sliders mid-drag is harmless
87
+ document.addEventListener('mousedown', e => {
88
+ if (!e.target.closest('#zoom-target img')) return;
89
+ S.on = true; e.preventDefault(); send(e, true);
90
+ }, true);
91
+ document.addEventListener('mousemove', e => { if (S.on) send(e, false); });
92
+ document.addEventListener('mouseup', e => { if (S.on) { S.on = false; send(e, true); } });
93
+ }
94
+ """
95
+
96
+
97
+ def label(factor):
98
+ return "input" if factor == 1 else f"{factor}x"
99
+
100
 
101
  def preview(image, levels, cx, cy):
102
+ """Show where the zoom will go, so nobody spends a run to find out.
103
+
104
+ Falls back to the shipped example when nothing is uploaded yet. Returning None here instead
105
+ would blank the box the moment a visitor touched any slider, taking the one thing that
106
+ explains what the amber boxes mean with it.
107
+ """
108
+ canvas = geometry.resize_and_center_crop(
109
+ image if image is not None else Image.open(EXAMPLE_INPUT)).convert("RGB")
110
  rects = geometry.nested_rects(canvas.size, int(levels), UPSCALE, (cx, cy))
111
  dimmed = Image.blend(canvas, Image.new("RGB", canvas.size, (0, 0, 0)), 0.45)
112
  dimmed.paste(canvas.crop(rects[0]), rects[0][:2])
 
133
  if image is None:
134
  raise gr.Error("Upload a photo first.")
135
  levels = int(levels)
136
+ gallery, prompts, frames = [], [], []
137
+ for step, factor, prompt, _blurry, result in zoom.zoom(MODELS, image, levels, UPSCALE, (cx, cy)):
138
  frames.append(result)
139
+ gallery.append((result, label(factor)))
140
  if step:
141
+ prompts.append(f"**{label(factor)}** &nbsp; {prompt or '_(no prompt)_'}")
 
142
  left = levels - step
143
  if not step:
144
  status = f"Warmed up. Zooming {levels} step{'s' if levels > 1 else ''}…"
145
  elif left:
146
+ status = f"At **{label(factor)}**, {left} step{'s' if left > 1 else ''} to go…"
147
  else:
148
+ status = f"At **{label(factor)}**. Rendering the clip…"
149
  # First yield clears any clip left from the previous run; later ones leave it alone.
150
+ yield status, (None if not step else gr.skip()), gallery, "\n\n".join(prompts)
151
+ yield "Done. 🔎", video.render(frames, UPSCALE, (cx, cy)), gallery, "\n\n".join(prompts)
 
152
 
153
 
154
  @spaces.GPU(duration=estimate_duration)
 
164
 
165
 
166
  with gr.Blocks(theme=gr.themes.Soft(primary_hue="amber"), css=CSS,
167
+ title="OracleZoom: zoom past 256x") as demo:
168
  gr.HTML(HEADER)
169
  with gr.Row():
170
  with gr.Column(scale=4):
171
+ image = gr.Image(label="Your photo", type="pil", height=300,
172
+ sources=["upload", "clipboard"])
173
  # Seeded with the example so the box overlay explains itself before any upload.
174
+ target = gr.Image(value=preview(None, 4, 0.5, 0.5), elem_id="zoom-target",
175
+ label="Drag to move the zoom point", type="pil",
176
  interactive=False, height=340, show_download_button=False)
177
+ levels = gr.Slider(1, MAX_STEPS, value=4, step=1, label="Zoom steps",
178
+ info=f"Each step is {UPSCALE}x. 4 steps reach 256x, "
179
+ f"{MAX_STEPS} reach {UPSCALE ** MAX_STEPS}x. "
180
+ f"Fewer steps finish sooner.")
181
+ with gr.Row():
182
+ cx = gr.Slider(0, 1, value=0.5, step=0.01, label="Horizontal", elem_id="zoom-x")
183
+ cy = gr.Slider(0, 1, value=0.5, step=0.01, label="Vertical", elem_id="zoom-y")
184
  go = gr.Button("🔎 Zoom in", variant="primary", size="lg")
 
185
  with gr.Column(scale=6):
186
  status = gr.Markdown("Upload a photo, then press **Zoom in**.")
187
  clip = gr.Video(value=EXAMPLE_CLIP, label="The zoom", elem_id="hero", autoplay=True,
188
  loop=True, show_share_button=True, height=400)
189
+ gallery = gr.Gallery(value=EXAMPLE_LEVELS, label="Every level", columns=5, height=205,
190
+ elem_id="levels", object_fit="cover", show_download_button=True)
191
+ gr.Markdown("##### Vision Language Model at each step:")
 
 
192
  prompts = gr.Markdown(EXAMPLE_NOTE)
193
 
194
  controls = [image, levels, cx, cy]
195
+ outputs = [status, clip, gallery, prompts]
196
  for c in controls:
197
  c.change(preview, controls, target, show_api=False)
198
  target.select(pick_point, None, [cx, cy], show_api=False)
199
+ go.click(run, controls, outputs)
200
 
201
  gr.Examples(
202
  examples=[f"samples/{n}.png" for n in SAMPLES],
203
  inputs=[image],
204
+ outputs=outputs,
205
  fn=run_example,
206
  cache_examples=True,
207
  label="Or try one of these (already computed, costs you nothing)",
208
  )
209
+ gr.Markdown(f"{LIMITS} See the [paper](https://arxiv.org/abs/2609.06490) for the claims we "
210
+ f"do and do not make.\n\n### Cite our work")
211
+ gr.Code(value=BIBTEX, language=None, show_label=False, container=False)
212
+
213
+ demo.load(None, None, None, js=DRAG_JS, show_api=False)
214
 
215
  if __name__ == "__main__":
216
  demo.queue(max_size=20).launch()