Spaces:
Running
Running
Ship gallery assets as LFS tar (fix broken images)
Browse files- app.py +2 -0
- pipeline.py +15 -3
- static/index.html +53 -12
- static_assets.tar +2 -2
app.py
CHANGED
|
@@ -122,6 +122,7 @@ async def process(
|
|
| 122 |
tone_strength: float = Form(0.7),
|
| 123 |
grain_mult: float = Form(1.0),
|
| 124 |
film_tone_strength: float = Form(0.7),
|
|
|
|
| 125 |
x_access_code: str = Header(default=''),
|
| 126 |
):
|
| 127 |
if not _check_access(x_access_code):
|
|
@@ -163,6 +164,7 @@ async def process(
|
|
| 163 |
tone_strength=tone_strength,
|
| 164 |
grain_mult=grain_mult,
|
| 165 |
film_tone_strength=film_tone_strength,
|
|
|
|
| 166 |
reference_filename=reference.filename or '',
|
| 167 |
target_filename=target.filename or '',
|
| 168 |
)
|
|
|
|
| 122 |
tone_strength: float = Form(0.7),
|
| 123 |
grain_mult: float = Form(1.0),
|
| 124 |
film_tone_strength: float = Form(0.7),
|
| 125 |
+
halation_mult: float = Form(1.0),
|
| 126 |
x_access_code: str = Header(default=''),
|
| 127 |
):
|
| 128 |
if not _check_access(x_access_code):
|
|
|
|
| 164 |
tone_strength=tone_strength,
|
| 165 |
grain_mult=grain_mult,
|
| 166 |
film_tone_strength=film_tone_strength,
|
| 167 |
+
halation_mult=halation_mult,
|
| 168 |
reference_filename=reference.filename or '',
|
| 169 |
target_filename=target.filename or '',
|
| 170 |
)
|
pipeline.py
CHANGED
|
@@ -360,6 +360,7 @@ class DeepAnalogPipeline:
|
|
| 360 |
tone_strength: float = 0.7,
|
| 361 |
grain_mult: float = 1.0,
|
| 362 |
film_tone_strength: float = 0.7,
|
|
|
|
| 363 |
reference_filename: str = '',
|
| 364 |
target_filename: str = '',
|
| 365 |
) -> dict:
|
|
@@ -375,10 +376,20 @@ class DeepAnalogPipeline:
|
|
| 375 |
grain_size_t = torch.tensor([[fp['grain_size']]], device=self.device)
|
| 376 |
lum_params_t = torch.tensor([[fp['lum_a'], fp['lum_b'], fp['lum_c']]],
|
| 377 |
device=self.device)
|
|
|
|
|
|
|
|
|
|
|
|
|
| 378 |
h_threshold_t = torch.tensor([[fp['h_threshold']]], device=self.device)
|
| 379 |
-
h_radius_t = torch.tensor([[fp['h_radius']
|
| 380 |
-
|
| 381 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 382 |
|
| 383 |
# 1. Color grading via predicted LUT
|
| 384 |
graded, predicted_lut, _ = self.style_lut(target, ref_224)
|
|
@@ -411,6 +422,7 @@ class DeepAnalogPipeline:
|
|
| 411 |
'tone_strength': round(float(tone_strength), 4),
|
| 412 |
'film_tone_strength': round(float(film_tone_strength), 4),
|
| 413 |
'grain_mult': round(float(grain_mult), 4),
|
|
|
|
| 414 |
})
|
| 415 |
|
| 416 |
return {
|
|
|
|
| 360 |
tone_strength: float = 0.7,
|
| 361 |
grain_mult: float = 1.0,
|
| 362 |
film_tone_strength: float = 0.7,
|
| 363 |
+
halation_mult: float = 1.0,
|
| 364 |
reference_filename: str = '',
|
| 365 |
target_filename: str = '',
|
| 366 |
) -> dict:
|
|
|
|
| 376 |
grain_size_t = torch.tensor([[fp['grain_size']]], device=self.device)
|
| 377 |
lum_params_t = torch.tensor([[fp['lum_a'], fp['lum_b'], fp['lum_c']]],
|
| 378 |
device=self.device)
|
| 379 |
+
# Halation control: scales the red-orange glow around bright lights.
|
| 380 |
+
# Intensity scales linearly; radius grows gently; pushing above 1x
|
| 381 |
+
# also warms the glow color for the classic red film bloom.
|
| 382 |
+
hm = max(0.0, float(halation_mult))
|
| 383 |
h_threshold_t = torch.tensor([[fp['h_threshold']]], device=self.device)
|
| 384 |
+
h_radius_t = torch.tensor([[fp['h_radius'] * (1.0 + 0.35 * max(hm - 1.0, 0.0))]],
|
| 385 |
+
device=self.device)
|
| 386 |
+
h_intensity_t = torch.tensor([[fp['h_intensity'] * hm]], device=self.device)
|
| 387 |
+
bias = list(fp['h_color_bias'])
|
| 388 |
+
if hm > 1.0:
|
| 389 |
+
bias = [bias[0] + 0.5 * (hm - 1.0), # more red
|
| 390 |
+
bias[1],
|
| 391 |
+
bias[2] - 0.3 * (hm - 1.0)] # less blue
|
| 392 |
+
h_color_bias_t = torch.tensor([bias], device=self.device)
|
| 393 |
|
| 394 |
# 1. Color grading via predicted LUT
|
| 395 |
graded, predicted_lut, _ = self.style_lut(target, ref_224)
|
|
|
|
| 422 |
'tone_strength': round(float(tone_strength), 4),
|
| 423 |
'film_tone_strength': round(float(film_tone_strength), 4),
|
| 424 |
'grain_mult': round(float(grain_mult), 4),
|
| 425 |
+
'halation_mult': round(hm, 4),
|
| 426 |
})
|
| 427 |
|
| 428 |
return {
|
static/index.html
CHANGED
|
@@ -144,8 +144,11 @@
|
|
| 144 |
}
|
| 145 |
.controls.visible { display: block; }
|
| 146 |
.controls h3 { font-size: 12px; letter-spacing: .14em; text-transform: uppercase; color: var(--text-dim); margin-bottom: 16px; }
|
| 147 |
-
.sliders { display: grid; grid-template-columns: repeat(
|
| 148 |
@media (max-width: 720px) { .sliders { grid-template-columns: 1fr; } }
|
|
|
|
|
|
|
|
|
|
| 149 |
.slider label { display: flex; justify-content: space-between; font-size: 13px; margin-bottom: 8px; color: var(--text); }
|
| 150 |
.slider label output { color: var(--accent); }
|
| 151 |
input[type=range] {
|
|
@@ -373,29 +376,42 @@
|
|
| 373 |
}
|
| 374 |
.look-card .ov .pname { font-size: 12px; font-weight: 600; color: #fff; line-height: 1.3; }
|
| 375 |
.look-card .ov .pmeta { font-size: 10.5px; color: rgba(255,255,255,.72); margin-top: 1px; }
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 376 |
.look-card a.can-btn {
|
| 377 |
-
position: absolute; right:
|
| 378 |
display: block; line-height: 0;
|
| 379 |
filter: drop-shadow(0 1px 3px rgba(0,0,0,.6));
|
| 380 |
transition: transform .12s;
|
| 381 |
}
|
| 382 |
.look-card a.can-btn:hover { transform: scale(1.12); }
|
| 383 |
.look-card.sel::after {
|
| 384 |
-
content: "✓"; position: absolute; top: 7px; right:
|
| 385 |
width: 20px; height: 20px; border-radius: 50%;
|
| 386 |
background: var(--accent); color: #17110a;
|
| 387 |
font-size: 12px; font-weight: 700;
|
| 388 |
display: flex; align-items: center; justify-content: center;
|
| 389 |
}
|
| 390 |
-
.input-grid {
|
|
|
|
|
|
|
|
|
|
| 391 |
.input-grid .in-card {
|
| 392 |
-
position: relative; border-radius:
|
| 393 |
border: 1.5px solid var(--line); cursor: pointer;
|
| 394 |
-
transition: border-color .12s;
|
| 395 |
}
|
| 396 |
-
.input-grid .in-card:hover { border-color: var(--text-dim); }
|
| 397 |
.input-grid .in-card.sel { border-color: var(--accent); }
|
| 398 |
-
.input-grid .in-card img { width: 100%; height:
|
| 399 |
.input-grid .in-card.sel::after {
|
| 400 |
content: "✓"; position: absolute; top: 5px; right: 5px;
|
| 401 |
width: 17px; height: 17px; border-radius: 50%;
|
|
@@ -629,17 +645,38 @@
|
|
| 629 |
<h3>Rendering controls</h3>
|
| 630 |
<div class="sliders">
|
| 631 |
<div class="slider">
|
| 632 |
-
<label>Tone match
|
|
|
|
|
|
|
|
|
|
|
|
|
| 633 |
<input type="range" id="toneRange" min="0" max="1" step="0.05" value="0.7">
|
| 634 |
</div>
|
| 635 |
<div class="slider">
|
| 636 |
-
<label>Film tone
|
|
|
|
|
|
|
|
|
|
|
|
|
| 637 |
<input type="range" id="filmRange" min="0" max="1" step="0.05" value="0.7">
|
| 638 |
</div>
|
| 639 |
<div class="slider">
|
| 640 |
-
<label>Grain
|
|
|
|
|
|
|
|
|
|
|
|
|
| 641 |
<input type="range" id="grainRange" min="0" max="2" step="0.1" value="1.0">
|
| 642 |
</div>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 643 |
</div>
|
| 644 |
</div>
|
| 645 |
|
|
@@ -891,7 +928,8 @@
|
|
| 891 |
// ------------------------------------------------ sliders
|
| 892 |
[['toneRange', 'toneOut', v => (+v).toFixed(2)],
|
| 893 |
['filmRange', 'filmOut', v => (+v).toFixed(2)],
|
| 894 |
-
['grainRange', 'grainOut', v => (+v).toFixed(1) + '×']
|
|
|
|
| 895 |
.forEach(([r, o, fmt]) => {
|
| 896 |
$(r).addEventListener('input', () => { $(o).textContent = fmt($(r).value); });
|
| 897 |
});
|
|
@@ -905,6 +943,7 @@
|
|
| 905 |
fd.append('tone_strength', $('toneRange').value);
|
| 906 |
fd.append('film_tone_strength', $('filmRange').value);
|
| 907 |
fd.append('grain_mult', $('grainRange').value);
|
|
|
|
| 908 |
}
|
| 909 |
setBusy(true, tgtFile ? 'Rendering — this can take up to a minute for large photos…'
|
| 910 |
: 'Predicting LUT…');
|
|
@@ -1617,6 +1656,8 @@
|
|
| 1617 |
card.className = 'look-card';
|
| 1618 |
card.innerHTML =
|
| 1619 |
'<img class="bg" loading="lazy" alt="">' +
|
|
|
|
|
|
|
| 1620 |
'<div class="ov"><div class="pname"></div><div class="pmeta"></div></div>';
|
| 1621 |
card.querySelector('.bg').src = imgSrc;
|
| 1622 |
card.querySelector('.bg').alt = name;
|
|
|
|
| 144 |
}
|
| 145 |
.controls.visible { display: block; }
|
| 146 |
.controls h3 { font-size: 12px; letter-spacing: .14em; text-transform: uppercase; color: var(--text-dim); margin-bottom: 16px; }
|
| 147 |
+
.sliders { display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: 22px; }
|
| 148 |
@media (max-width: 720px) { .sliders { grid-template-columns: 1fr; } }
|
| 149 |
+
.help .tip img { display: block; width: 100%; border-radius: 6px; margin-top: 9px; }
|
| 150 |
+
.slider .help { width: 15px; height: 15px; font-size: 10px; margin-left: 6px; }
|
| 151 |
+
.slider .tip { width: min(356px, 82vw); }
|
| 152 |
.slider label { display: flex; justify-content: space-between; font-size: 13px; margin-bottom: 8px; color: var(--text); }
|
| 153 |
.slider label output { color: var(--accent); }
|
| 154 |
input[type=range] {
|
|
|
|
| 376 |
}
|
| 377 |
.look-card .ov .pname { font-size: 12px; font-weight: 600; color: #fff; line-height: 1.3; }
|
| 378 |
.look-card .ov .pmeta { font-size: 10.5px; color: rgba(255,255,255,.72); margin-top: 1px; }
|
| 379 |
+
.look-card .sprk {
|
| 380 |
+
position: absolute; top: 0; bottom: 0; width: 13px; z-index: 2;
|
| 381 |
+
background-color: #0b0b0e;
|
| 382 |
+
background-image: radial-gradient(rgba(255,255,255,.17) 2.1px, transparent 2.6px);
|
| 383 |
+
background-size: 13px 12px;
|
| 384 |
+
background-position: center;
|
| 385 |
+
}
|
| 386 |
+
.look-card .sprk.l { left: 0; border-right: 1px solid rgba(255,255,255,.05); }
|
| 387 |
+
.look-card .sprk.r { right: 0; border-left: 1px solid rgba(255,255,255,.05); }
|
| 388 |
+
.look-card .ov { padding-left: 22px; padding-right: 22px; }
|
| 389 |
.look-card a.can-btn {
|
| 390 |
+
position: absolute; right: 17px; bottom: 7px; z-index: 3;
|
| 391 |
display: block; line-height: 0;
|
| 392 |
filter: drop-shadow(0 1px 3px rgba(0,0,0,.6));
|
| 393 |
transition: transform .12s;
|
| 394 |
}
|
| 395 |
.look-card a.can-btn:hover { transform: scale(1.12); }
|
| 396 |
.look-card.sel::after {
|
| 397 |
+
content: "✓"; position: absolute; top: 7px; right: 17px;
|
| 398 |
width: 20px; height: 20px; border-radius: 50%;
|
| 399 |
background: var(--accent); color: #17110a;
|
| 400 |
font-size: 12px; font-weight: 700;
|
| 401 |
display: flex; align-items: center; justify-content: center;
|
| 402 |
}
|
| 403 |
+
.input-grid {
|
| 404 |
+
display: grid; grid-template-columns: 1fr; gap: 10px;
|
| 405 |
+
max-height: 640px; overflow-y: auto; padding-right: 5px;
|
| 406 |
+
}
|
| 407 |
.input-grid .in-card {
|
| 408 |
+
position: relative; border-radius: 9px; overflow: hidden;
|
| 409 |
border: 1.5px solid var(--line); cursor: pointer;
|
| 410 |
+
transition: border-color .12s, transform .1s;
|
| 411 |
}
|
| 412 |
+
.input-grid .in-card:hover { border-color: var(--text-dim); transform: translateY(-1px); }
|
| 413 |
.input-grid .in-card.sel { border-color: var(--accent); }
|
| 414 |
+
.input-grid .in-card img { width: 100%; height: 118px; object-fit: cover; display: block; }
|
| 415 |
.input-grid .in-card.sel::after {
|
| 416 |
content: "✓"; position: absolute; top: 5px; right: 5px;
|
| 417 |
width: 17px; height: 17px; border-radius: 50%;
|
|
|
|
| 645 |
<h3>Rendering controls</h3>
|
| 646 |
<div class="sliders">
|
| 647 |
<div class="slider">
|
| 648 |
+
<label><span>Tone match<span class="help">?<span class="tip">Matches your photo’s
|
| 649 |
+
brightness distribution to the reference — shadows, mids and highlights are moved
|
| 650 |
+
to where the film puts them. Left: off · right: full strength.
|
| 651 |
+
<img src="static/help/tone.jpg" alt="Tone match comparison"></span></span></span>
|
| 652 |
+
<output id="toneOut">0.70</output></label>
|
| 653 |
<input type="range" id="toneRange" min="0" max="1" step="0.05" value="0.7">
|
| 654 |
</div>
|
| 655 |
<div class="slider">
|
| 656 |
+
<label><span>Film tone<span class="help">?<span class="tip">Film’s tonal signature:
|
| 657 |
+
lifted blacks (no pure black), a soft highlight roll-off, and the subtle color
|
| 658 |
+
tint film leaves in shadows and highlights. Left: off · right: full strength.
|
| 659 |
+
<img src="static/help/filmtone.jpg" alt="Film tone comparison"></span></span></span>
|
| 660 |
+
<output id="filmOut">0.70</output></label>
|
| 661 |
<input type="range" id="filmRange" min="0" max="1" step="0.05" value="0.7">
|
| 662 |
</div>
|
| 663 |
<div class="slider">
|
| 664 |
+
<label><span>Grain<span class="help">?<span class="tip">How much film grain is
|
| 665 |
+
layered onto the result. 0 = clean, 2 = gritty high-ISO texture.
|
| 666 |
+
Left: 0 · right: 2×.
|
| 667 |
+
<img src="static/help/grain.jpg" alt="Grain comparison"></span></span></span>
|
| 668 |
+
<output id="grainOut">1.0×</output></label>
|
| 669 |
<input type="range" id="grainRange" min="0" max="2" step="0.1" value="1.0">
|
| 670 |
</div>
|
| 671 |
+
<div class="slider">
|
| 672 |
+
<label><span>Halation<span class="help">?<span class="tip">The warm red glow film
|
| 673 |
+
creates around bright lights — light scatters off the film base and re-exposes
|
| 674 |
+
the red-sensitive layer. Push past 1× for the classic red night-street bloom.
|
| 675 |
+
Left: off · right: 3×.
|
| 676 |
+
<img src="static/help/halation.jpg" alt="Halation comparison"></span></span></span>
|
| 677 |
+
<output id="halOut">1.0×</output></label>
|
| 678 |
+
<input type="range" id="halRange" min="0" max="3" step="0.1" value="1.0">
|
| 679 |
+
</div>
|
| 680 |
</div>
|
| 681 |
</div>
|
| 682 |
|
|
|
|
| 928 |
// ------------------------------------------------ sliders
|
| 929 |
[['toneRange', 'toneOut', v => (+v).toFixed(2)],
|
| 930 |
['filmRange', 'filmOut', v => (+v).toFixed(2)],
|
| 931 |
+
['grainRange', 'grainOut', v => (+v).toFixed(1) + '×'],
|
| 932 |
+
['halRange', 'halOut', v => (+v).toFixed(1) + '×']]
|
| 933 |
.forEach(([r, o, fmt]) => {
|
| 934 |
$(r).addEventListener('input', () => { $(o).textContent = fmt($(r).value); });
|
| 935 |
});
|
|
|
|
| 943 |
fd.append('tone_strength', $('toneRange').value);
|
| 944 |
fd.append('film_tone_strength', $('filmRange').value);
|
| 945 |
fd.append('grain_mult', $('grainRange').value);
|
| 946 |
+
fd.append('halation_mult', $('halRange').value);
|
| 947 |
}
|
| 948 |
setBusy(true, tgtFile ? 'Rendering — this can take up to a minute for large photos…'
|
| 949 |
: 'Predicting LUT…');
|
|
|
|
| 1656 |
card.className = 'look-card';
|
| 1657 |
card.innerHTML =
|
| 1658 |
'<img class="bg" loading="lazy" alt="">' +
|
| 1659 |
+
'<span class="sprk l" aria-hidden="true"></span>' +
|
| 1660 |
+
'<span class="sprk r" aria-hidden="true"></span>' +
|
| 1661 |
'<div class="ov"><div class="pname"></div><div class="pmeta"></div></div>';
|
| 1662 |
card.querySelector('.bg').src = imgSrc;
|
| 1663 |
card.querySelector('.bg').alt = name;
|
static_assets.tar
CHANGED
|
@@ -1,3 +1,3 @@
|
|
| 1 |
version https://git-lfs.github.com/spec/v1
|
| 2 |
-
oid sha256:
|
| 3 |
-
size
|
|
|
|
| 1 |
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:32ead0e949236298f20f1ddae551e4d2e97d84052a6b8463e7dd489152181ed6
|
| 3 |
+
size 70062080
|