Show AI denoise in the manual, on a night frame with no faces

A section after Looking closer: what it is for, the switch and its wait,
Keep grain, which cameras it takes and where its noise figures come from.
The scene opens the Brooklyn Bridge at ISO 8000 from the face-free demo
set at 1:1, switches it on, waits for the result to land and keeps some
grain, with a still before and after. It waits on the app's own log line
rather than a fixed time: the network takes seconds on a GPU and more on
the CPU, which is where the recording X server leaves it (13 s).
This commit is contained in:
2026-10-03 12:05:26 -04:00
parent 4eb7cf77f5
commit ababd628ed
6 changed files with 125 additions and 0 deletions
+29
View File
@@ -205,6 +205,35 @@ the sensor recorded.
![Zooming to 1:1 with a double-click, panning, then further in with the wheel](media/develop-zoom.gif) ![Zooming to 1:1 with a double-click, panning, then further in with the wheel](media/develop-zoom.gif)
### AI denoise
For a photograph taken in poor light at a high ISO. `AI Denoise`, in the
Detail group, replaces how the camera's raw data is turned into colour: a
network trained on this library's own photographs removes the noise and
the blotches of colour that come with it, while keeping the fine detail.
Look at it at 1:1, where noise lives.
Switch it on with `Apply`. The photograph keeps showing as it was while
the network works, with its progress in the bar at the top, and changes
when it is done — a few seconds on a computer with a graphics card, about
fifteen on its processor alone, longer on the tablet. `Keep grain` puts
back some of what was removed, as grain without colour, for a picture that
does not look too smooth.
![An ISO 8000 night frame at 1:1, AI Denoise switched on, then some grain kept](media/develop-denoise.gif)
| Before | After |
|---|---|
| ![The railing and the lamp at ISO 8000, as the camera recorded them](media/develop-denoise-before.png) | ![The same, with AI Denoise](media/develop-denoise-after.png) |
It works on raw files from any camera with the usual colour pattern of
red, green and blue squares — not on JPEGs, and not yet on Fujifilm's
X-Trans. How noisy the camera is at each ISO was measured for the Canon
EOS 6D; for other cameras it is read from a DNG's own figures or
estimated from the photograph, and the finished job in the activity list
says which. An export uses
it whenever the photograph has it switched on.
### Moving between photographs ### Moving between photographs
The roll along the foot of the canvas holds the photographs the grid was The roll along the foot of the canvas holds the photographs the grid was
+24
View File
@@ -117,6 +117,7 @@ th { color: var(--ink-dim); font-weight: 600; }
<ul> <ul>
<li><a href="#light">Light</a></li> <li><a href="#light">Light</a></li>
<li><a href="#looking-closer">Looking closer</a></li> <li><a href="#looking-closer">Looking closer</a></li>
<li><a href="#ai-denoise">AI denoise</a></li>
<li><a href="#moving-between-photographs">Moving between photographs</a></li> <li><a href="#moving-between-photographs">Moving between photographs</a></li>
<li><a href="#white-balance-from-the-photograph">White balance from the photograph</a></li> <li><a href="#white-balance-from-the-photograph">White balance from the photograph</a></li>
<li><a href="#composing">Composing</a></li> <li><a href="#composing">Composing</a></li>
@@ -287,6 +288,29 @@ wheel zooms to any amount in between. Past 1:1 the file's own pixels are
drawn as hard-edged blocks rather than smoothed, so what you see is what drawn as hard-edged blocks rather than smoothed, so what you see is what
the sensor recorded.</p> the sensor recorded.</p>
<figure><img loading="lazy" src="media/develop-zoom.gif" alt="Zooming to 1:1 with a double-click, panning, then further in with the wheel"><figcaption>Zooming to 1:1 with a double-click, panning, then further in with the wheel</figcaption></figure> <figure><img loading="lazy" src="media/develop-zoom.gif" alt="Zooming to 1:1 with a double-click, panning, then further in with the wheel"><figcaption>Zooming to 1:1 with a double-click, panning, then further in with the wheel</figcaption></figure>
<h3 id="ai-denoise">AI denoise</h3>
<p>For a photograph taken in poor light at a high ISO. <code>AI Denoise</code>, in the
Detail group, replaces how the camera's raw data is turned into colour: a
network trained on this library's own photographs removes the noise and
the blotches of colour that come with it, while keeping the fine detail.
Look at it at 1:1, where noise lives.</p>
<p>Switch it on with <code>Apply</code>. The photograph keeps showing as it was while
the network works, with its progress in the bar at the top, and changes
when it is done — a few seconds on a computer with a graphics card, about
fifteen on its processor alone, longer on the tablet. <code>Keep grain</code> puts
back some of what was removed, as grain without colour, for a picture that
does not look too smooth.</p>
<figure><img loading="lazy" src="media/develop-denoise.gif" alt="An ISO 8000 night frame at 1:1, AI Denoise switched on, then some grain kept"><figcaption>An ISO 8000 night frame at 1:1, AI Denoise switched on, then some grain kept</figcaption></figure>
<table><thead><tr><th>Before</th><th>After</th></tr></thead><tbody>
<tr><td><img src="media/develop-denoise-before.png" alt="The railing and the lamp at ISO 8000, as the camera recorded them" /></td><td><img src="media/develop-denoise-after.png" alt="The same, with AI Denoise" /></td></tr>
</tbody></table>
<p>It works on raw files from any camera with the usual colour pattern of
red, green and blue squares — not on JPEGs, and not yet on Fujifilm's
X-Trans. How noisy the camera is at each ISO was measured for the Canon
EOS 6D; for other cameras it is read from a DNG's own figures or
estimated from the photograph, and the finished job in the activity list
says which. An export uses
it whenever the photograph has it switched on.</p>
<h3 id="moving-between-photographs">Moving between photographs</h3> <h3 id="moving-between-photographs">Moving between photographs</h3>
<p>The roll along the foot of the canvas holds the photographs the grid was <p>The roll along the foot of the canvas holds the photographs the grid was
showing; click one to open it. The right arrow, <code>D</code> or space opens the next, showing; click one to open it. The right arrow, <code>D</code> or space opens the next,
Binary file not shown.
Binary file not shown.
Binary file not shown.
+63
View File
@@ -63,6 +63,7 @@ ALPS_SKY = '_MG_8330' # an upright alpine frame, its top third sky and clo
TOWER = '_MG_8693' # towers shot looking up: verticals that converge TOWER = '_MG_8693' # towers shot looking up: verticals that converge
ROAD = '_MG_8672' # a road under a sky, with marks on it: masks and repair ROAD = '_MG_8672' # a road under a sky, with marks on it: masks and repair
ROAD_MARK = (0.305, 0.794) # a dark mark on its tarmac ROAD_MARK = (0.305, 0.794) # a dark mark on its tarmac
DENOISE = '_MG_8862' # Brooklyn Bridge at ISO 8000: a lit railing, a lamp and a camera against the night
# --- the registry ----------------------------------------------------------- # --- the registry -----------------------------------------------------------
SCENES = {} SCENES = {}
@@ -841,6 +842,68 @@ def develop_zoom():
pause(1.2) pause(1.2)
@scene(media=['develop-denoise.gif', 'develop-denoise-before.png', 'develop-denoise-after.png'],
sources=DEVELOP_SRC + ['ui/dr-ui/src/develop/denoise.rs', 'core/dr-denoise/**',
'core/dr-gpu/src/grain.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."""
at_develop(DENOISE)
a = dr.photo(0.45, 0.55) # the lit lamp, the railing and the skyline over the water
dr.move(*a)
pause(0.3)
dr.x('click', '--repeat', 2, '--delay', 80, 1)
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()
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 log_size():
try:
return os.path.getsize(f'{dr.HOME}/app.log')
except OSError:
return 0
def denoise_landed(since):
"""The app has logged the result landing since `since` bytes into its
log. The activity bar's progress has no name to wait on, and the time
it takes is the device's — seconds on a GPU, more on the CPU."""
try:
with open(f'{dr.HOME}/app.log', 'rb') as f:
f.seek(since)
return b'learned denoise:' in f.read()
except OSError:
return False
@scene(media=['develop-wb.gif'], sources=DEVELOP_SRC + ['ui/dr-ui/src/develop/white_balance.rs']) @scene(media=['develop-wb.gif'], sources=DEVELOP_SRC + ['ui/dr-ui/src/develop/white_balance.rs'])
def develop_wb(): def develop_wb():
at_develop() at_develop()