Show each denoise method in the manual as a close-up

The manual's AI denoise section names the four methods and their measured
times, and shows the lamp and railing of the ISO 8000 frame at 1:1 by each
in place of the film and the before/after pair. The scene clicks each
method and waits for that network's result: the repair now logs its own
"learned denoise:" line first, so the wait matches the result's.
This commit is contained in:
2026-10-04 08:11:24 -04:00
parent 06422a07db
commit b6ca7be185
10 changed files with 99 additions and 55 deletions
+35 -26
View File
@@ -842,13 +842,21 @@ def develop_zoom():
pause(1.2)
@scene(media=['develop-denoise.gif', 'develop-denoise-before.png', 'develop-denoise-after.png'],
# The methods in the order the scene visits them: `Best` is what the
# photograph opens with, then each smaller network, then none.
DENOISE_METHODS = ['Best', 'Medium', 'Fast', 'Bilinear']
DENOISE_CLOSE_UP = 560 # pixels of canvas, square, around the lamp at 1:1
@scene(media=[f'develop-denoise-{m.lower()}.png' for m in DENOISE_METHODS],
sources=DEVELOP_SRC + ['ui/dr-ui/src/develop/denoise.rs', 'core/dr-denoise/**',
'core/dr-gpu/src/grain.rs', 'models/denoise/**'])
'core/dr-pipeline/src/learned_denoise.rs', 'models/denoise/**'])
def develop_denoise():
"""A night frame at ISO 8000 at 1:1, AI Denoise switched on and landed,
then some grain kept. Waits for the network rather than for a fixed
time: on the CPU it takes several times what it does on a GPU."""
"""The lit lamp and railing of an ISO 8000 night frame at 1:1, once per
AI Denoise method, each cut to the same square of the canvas. Waits for
each network rather than for a fixed time: on the CPU, which the demo
profile uses, Best takes several times what Fast does."""
mark = log_size()
at_develop(DENOISE)
a = dr.photo(0.45, 0.55) # the lit lamp, the railing and the skyline over the water
dr.move(*a)
@@ -857,32 +865,31 @@ def develop_denoise():
pause(1.5)
group('Detail')
in_column('AI Denoise@Text')
shot('develop-denoise-before')
rec('develop-denoise')
pause(0.8)
mark = log_size()
dr.click(*denoise_switch())
t0 = time.time()
while time.time() - t0 < 300 and not denoise_landed(mark):
pause(0.5)
pause(1.5)
shot('develop-denoise-after')
slide('Keep grain', 60)
pause(2.0)
cut()
for method in DENOISE_METHODS:
if method != 'Best':
mark = log_size()
dr.click(*in_column(f'{method}@RadioButton'))
if method != 'Bilinear':
t0 = time.time()
while time.time() - t0 < 600 and not denoise_landed(mark):
pause(0.5)
pause(1.5)
close_up(f'develop-denoise-{method.lower()}', a)
undo_all()
dr.move(*a)
dr.x('click', '--repeat', 2, '--delay', 80, 1)
pause(1.2)
def denoise_switch():
"""The `Apply` box under the AI Denoise heading — the lens profile's
switch is also called Apply, so it is found by where it sits."""
head = dr.matches('AI Denoise@Text', within=column())[0]
below = [e for e in dr.matches('Apply@CheckBox', within=column()) if e['y'] > head['y']]
e = min(below, key=lambda e: e['y'])
return int(e['x'] + e['w'] / 2), int(e['y'] + e['h'] / 2)
def close_up(name, p, size=DENOISE_CLOSE_UP):
"""A square of the canvas centred on `p`, kept inside the canvas."""
shot(name)
x0, y0, x1, y1 = dr.rect('id:canvas-image')
half = size // 2
cx = min(max(p[0], x0 + half), x1 - half)
cy = min(max(p[1], y0 + half), y1 - half)
subprocess.run(['mogrify', '-crop', f'{size}x{size}+{cx - half}+{cy - half}', '+repage',
f'{OUT}/{name}.png'], check=True)
def log_size():
@@ -899,7 +906,9 @@ def denoise_landed(since):
try:
with open(f'{dr.HOME}/app.log', 'rb') as f:
f.seek(since)
return b'learned denoise:' in f.read()
# The result's line, "learned denoise: W×H on …", not the
# repair's, which comes first.
return re.search(rb'learned denoise: \d+\xc3\x97', f.read()) is not None
except OSError:
return False