Spaces:
Running on Zero
Running on Zero
Clearer layout: header with logo and links, a first-try tip, numbered steps, status callout
Browse files- .gitignore +3 -0
- app.py +53 -32
- flashrefocus/rendering/depth_edits.py +4 -3
.gitignore
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
weights/
|
| 2 |
+
__pycache__/
|
| 3 |
+
*.pyc
|
app.py
CHANGED
|
@@ -15,7 +15,7 @@ from PIL import Image, ImageDraw, ImageFont
|
|
| 15 |
|
| 16 |
import engine
|
| 17 |
|
| 18 |
-
SINGLE, DUAL, TILT = "One focal plane", "Two focal planes", "Tilt-shift"
|
| 19 |
EXAMPLES = ["examples/portrait_city.jpg", "examples/portrait_aquarium.jpg", "examples/flower.jpg",
|
| 20 |
"examples/dock.jpg", "examples/sculpture.jpg"]
|
| 21 |
FONT = "/usr/share/fonts/truetype/dejavu/DejaVuSans-Bold.ttf"
|
|
@@ -193,55 +193,76 @@ def on_reset(state):
|
|
| 193 |
return controls + [aif_view(state, r["mode"]), out, depth_view, state, note + msg]
|
| 194 |
|
| 195 |
|
| 196 |
-
|
| 197 |
-
|
| 198 |
-
|
| 199 |
-
|
| 200 |
-
<
|
| 201 |
-
<
|
| 202 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 203 |
</div>
|
|
|
|
| 204 |
|
| 205 |
-
|
| 206 |
-
|
| 207 |
-
|
| 208 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 209 |
"""
|
| 210 |
|
| 211 |
-
with gr.Blocks(title="FlashReFocus", theme=gr.themes.Soft()) as demo:
|
| 212 |
-
gr.
|
| 213 |
state = gr.State(None)
|
| 214 |
with gr.Row():
|
| 215 |
with gr.Column():
|
| 216 |
-
|
| 217 |
-
|
|
|
|
|
|
|
| 218 |
long_side = gr.Slider(512, 1536, value=1024, step=64, label="Long side (pixels)")
|
| 219 |
omega = gr.Slider(0.0, 1.5, value=1.25, step=0.25, label="Detail weight Ο",
|
| 220 |
info="0: pixel LoRA only (most faithful); higher restores more detail.")
|
| 221 |
-
btn = gr.Button("Restore", variant="primary")
|
| 222 |
-
gr.
|
| 223 |
depth = gr.Image(label="Depth used for rendering (near is bright): click to focus", type="pil",
|
| 224 |
-
interactive=False, height=
|
| 225 |
-
with gr.Accordion("Depth
|
| 226 |
gamma = gr.Slider(0.4, 2.5, value=1.0, step=0.05, label="Depth curve Ξ³ (D β D^Ξ³)",
|
| 227 |
info="< 1 spreads out the near range, > 1 the far range; blur follows the new spacing.")
|
| 228 |
band = gr.Slider(0.02, 0.3, value=0.06, step=0.01, label="Tilt-shift: half-height of the sharp band")
|
| 229 |
user_depth = gr.Image(label="Your own depth map (optional; near is bright, any size)", type="numpy",
|
| 230 |
-
sources=["upload", "clipboard"], height=
|
| 231 |
with gr.Column():
|
| 232 |
-
|
| 233 |
-
|
| 234 |
-
|
| 235 |
-
|
| 236 |
-
|
| 237 |
-
strength = gr.Slider(0.0, 1.5, value=0.5, step=0.05, label="Aperture (blur strength K)",
|
| 238 |
info="0: everything sharp; 1: the widest aperture seen in training; above 1 extrapolates.")
|
| 239 |
with gr.Row():
|
| 240 |
-
show_marker = gr.Checkbox(value=False, label="Show focal markers
|
| 241 |
-
clear = gr.Button("Clear focal points")
|
| 242 |
-
reset = gr.Button("
|
| 243 |
-
|
| 244 |
-
|
| 245 |
controls = [state, strength, mode, gamma, band, show_marker]
|
| 246 |
outputs = [aif, out, depth, state, status]
|
| 247 |
btn.click(restore, [inp, long_side, omega, gamma], [aif, depth, out, state, status])
|
|
|
|
| 15 |
|
| 16 |
import engine
|
| 17 |
|
| 18 |
+
SINGLE, DUAL, TILT = "π One focal plane", "π₯ Two focal planes", "ποΈ Tilt-shift"
|
| 19 |
EXAMPLES = ["examples/portrait_city.jpg", "examples/portrait_aquarium.jpg", "examples/flower.jpg",
|
| 20 |
"examples/dock.jpg", "examples/sculpture.jpg"]
|
| 21 |
FONT = "/usr/share/fonts/truetype/dejavu/DejaVuSans-Bold.ttf"
|
|
|
|
| 193 |
return controls + [aif_view(state, r["mode"]), out, depth_view, state, note + msg]
|
| 194 |
|
| 195 |
|
| 196 |
+
LOGO = """<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64" width="64" height="64"><circle cx="32" cy="32" r="30" fill="#FFFFFF" stroke="#2E7D32" stroke-width="3"/><g fill="#48A43F" stroke="#FFFFFF" stroke-width="1.6" stroke-linejoin="round"><path d="M32 4 A28 28 0 0 1 56.2 18 L40.6 27 Z" transform="rotate(0 32 32)"/><path d="M32 4 A28 28 0 0 1 56.2 18 L40.6 27 Z" transform="rotate(60 32 32)"/><path d="M32 4 A28 28 0 0 1 56.2 18 L40.6 27 Z" transform="rotate(120 32 32)"/><path d="M32 4 A28 28 0 0 1 56.2 18 L40.6 27 Z" transform="rotate(180 32 32)"/><path d="M32 4 A28 28 0 0 1 56.2 18 L40.6 27 Z" transform="rotate(240 32 32)"/><path d="M32 4 A28 28 0 0 1 56.2 18 L40.6 27 Z" transform="rotate(300 32 32)"/></g><path d="M35.5 19 L25.5 34 H31.5 L28.5 45 L39 29 H33 Z" fill="#FFD21E" stroke="#363636" stroke-width="1.4" stroke-linejoin="round"/></svg>
|
| 197 |
+
"""
|
| 198 |
+
|
| 199 |
+
HEADER = f"""
|
| 200 |
+
<div id="header">
|
| 201 |
+
<div class="title">{LOGO}<div><h1>FlashReFocus</h1><p>Interactive image refocusing in seconds: restore a photo once,
|
| 202 |
+
then click to refocus, open the aperture, or keep two subjects sharp.</p></div></div>
|
| 203 |
+
<div class="links">
|
| 204 |
+
<a href="https://ming053l.github.io/FlashReFocus/" target="_blank">π Project page</a>
|
| 205 |
+
<a href="https://ming053l.github.io/FlashReFocus/assets/FlashReFocus.pdf" target="_blank">π Paper</a>
|
| 206 |
+
<a href="https://github.com/ming053l/FlashReFocus" target="_blank">π» Code</a>
|
| 207 |
+
<a href="https://youtu.be/g032M7moWQ4" target="_blank">π¬ Video</a>
|
| 208 |
+
</div>
|
| 209 |
+
<div class="tip">π <b>Try this first:</b> pick the aquarium example β <b>Restore</b> β choose <b>π₯ Two focal planes</b>
|
| 210 |
+
β click the face, then the shark. Every click after Restore runs only the 1.3M-parameter renderer.</div>
|
| 211 |
</div>
|
| 212 |
+
"""
|
| 213 |
|
| 214 |
+
CSS = """
|
| 215 |
+
#header {margin-bottom: 4px}
|
| 216 |
+
#header .title {display: flex; align-items: center; justify-content: center; gap: 16px}
|
| 217 |
+
#header h1 {margin: 0; font-size: 2.1em; color: #2E7D32}
|
| 218 |
+
#header .title p {margin: 2px 0 0; color: var(--body-text-color-subdued)}
|
| 219 |
+
#header .links {display: flex; flex-wrap: wrap; justify-content: center; gap: 8px; margin: 12px 0}
|
| 220 |
+
#header .links a {padding: 5px 14px; border-radius: 999px; border: 1px solid #48A43F; color: #2E7D32;
|
| 221 |
+
text-decoration: none; font-weight: 600}
|
| 222 |
+
#header .links a:hover {background: #48A43F; color: white}
|
| 223 |
+
#header .tip {background: #f1f8ef; border-left: 4px solid #48A43F; padding: 10px 14px; border-radius: 8px;
|
| 224 |
+
color: #1f3d1f}
|
| 225 |
+
.step h3 {margin: 6px 0 2px; color: #2E7D32}
|
| 226 |
+
#status {background: #f1f8ef; border-left: 4px solid #48A43F; padding: 8px 14px; border-radius: 8px; min-height: 2.4em}
|
| 227 |
+
#status p {margin: 0; color: #1f3d1f}
|
| 228 |
"""
|
| 229 |
|
| 230 |
+
with gr.Blocks(title="FlashReFocus", theme=gr.themes.Soft(primary_hue="green"), css=CSS) as demo:
|
| 231 |
+
gr.HTML(HEADER)
|
| 232 |
state = gr.State(None)
|
| 233 |
with gr.Row():
|
| 234 |
with gr.Column():
|
| 235 |
+
gr.Markdown("### β Upload a photo and restore it once", elem_classes="step")
|
| 236 |
+
inp = gr.Image(label="Input photograph", type="pil", sources=["upload", "clipboard"], height=320)
|
| 237 |
+
gr.Examples(EXAMPLES, inputs=inp, label="Examples (Unsplash portraits and our own photographs)")
|
| 238 |
+
with gr.Accordion("Restoration settings", open=False):
|
| 239 |
long_side = gr.Slider(512, 1536, value=1024, step=64, label="Long side (pixels)")
|
| 240 |
omega = gr.Slider(0.0, 1.5, value=1.25, step=0.25, label="Detail weight Ο",
|
| 241 |
info="0: pixel LoRA only (most faithful); higher restores more detail.")
|
| 242 |
+
btn = gr.Button("β¨ Restore", variant="primary", size="lg")
|
| 243 |
+
gr.Markdown("### β’ Depth map", elem_classes="step")
|
| 244 |
depth = gr.Image(label="Depth used for rendering (near is bright): click to focus", type="pil",
|
| 245 |
+
interactive=False, height=280)
|
| 246 |
+
with gr.Accordion("Depth controls", open=False):
|
| 247 |
gamma = gr.Slider(0.4, 2.5, value=1.0, step=0.05, label="Depth curve Ξ³ (D β D^Ξ³)",
|
| 248 |
info="< 1 spreads out the near range, > 1 the far range; blur follows the new spacing.")
|
| 249 |
band = gr.Slider(0.02, 0.3, value=0.06, step=0.01, label="Tilt-shift: half-height of the sharp band")
|
| 250 |
user_depth = gr.Image(label="Your own depth map (optional; near is bright, any size)", type="numpy",
|
| 251 |
+
sources=["upload", "clipboard"], height=150)
|
| 252 |
with gr.Column():
|
| 253 |
+
gr.Markdown("### β‘ Click the restored image to focus", elem_classes="step")
|
| 254 |
+
aif = gr.Image(label="Restored all-in-focus image (click to focus)", type="pil", interactive=False,
|
| 255 |
+
height=320)
|
| 256 |
+
mode = gr.Radio([SINGLE, DUAL, TILT], value=SINGLE, label="Focus mode",
|
| 257 |
+
info="Two planes: the first click sets A, the second B. Tilt-shift: click the row to keep sharp.")
|
| 258 |
+
strength = gr.Slider(0.0, 1.5, value=0.5, step=0.05, label="π Aperture (blur strength K)",
|
| 259 |
info="0: everything sharp; 1: the widest aperture seen in training; above 1 extrapolates.")
|
| 260 |
with gr.Row():
|
| 261 |
+
show_marker = gr.Checkbox(value=False, label="Show focal markers")
|
| 262 |
+
clear = gr.Button("Clear focal points", size="sm")
|
| 263 |
+
reset = gr.Button("βΊ Recommended settings", size="sm")
|
| 264 |
+
status = gr.Markdown("Upload a photo or pick an example, then press β¨ Restore.", elem_id="status")
|
| 265 |
+
out = gr.Image(label="Refocused result", type="pil", interactive=False, height=430, format="png")
|
| 266 |
controls = [state, strength, mode, gamma, band, show_marker]
|
| 267 |
outputs = [aif, out, depth, state, status]
|
| 268 |
btn.click(restore, [inp, long_side, omega, gamma], [aif, depth, out, state, status])
|
flashrefocus/rendering/depth_edits.py
CHANGED
|
@@ -11,11 +11,12 @@ def depth_at(depth, x, y):
|
|
| 11 |
|
| 12 |
def two_focal_planes(depth, focal_depth_a, focal_depth_b):
|
| 13 |
"""D' = D_A + min(|D - D_A|, |D - D_B|): every pixel is blurred by its distance to the nearer of two focal planes.
|
| 14 |
-
Render D' with focal depth D_A.
|
| 15 |
-
(adding it to
|
| 16 |
da, db = float(focal_depth_a), float(focal_depth_b)
|
| 17 |
distance = torch.minimum((depth - da).abs(), (depth - db).abs())
|
| 18 |
-
|
|
|
|
| 19 |
|
| 20 |
|
| 21 |
def tilt_shift(height, width, row, slope=3.0, band=0.06, device="cpu"):
|
|
|
|
| 11 |
|
| 12 |
def two_focal_planes(depth, focal_depth_a, focal_depth_b):
|
| 13 |
"""D' = D_A + min(|D - D_A|, |D - D_B|): every pixel is blurred by its distance to the nearer of two focal planes.
|
| 14 |
+
Render D' with focal depth D_A. If adding the distance would clip more pixels at 1 than subtracting it would clip at
|
| 15 |
+
0 (a far plane A), it is subtracted instead: adding it to D_A = 1 would make the whole image sharp."""
|
| 16 |
da, db = float(focal_depth_a), float(focal_depth_b)
|
| 17 |
distance = torch.minimum((depth - da).abs(), (depth - db).abs())
|
| 18 |
+
subtract = int((da - distance < 0).sum()) < int((da + distance > 1).sum())
|
| 19 |
+
return (da - distance if subtract else da + distance).clamp(0, 1)
|
| 20 |
|
| 21 |
|
| 22 |
def tilt_shift(height, width, row, slope=3.0, band=0.06, device="cpu"):
|