diff --git a/docs/manual/README.md b/docs/manual/README.md index 15c6008..4b368be 100644 --- a/docs/manual/README.md +++ b/docs/manual/README.md @@ -205,6 +205,35 @@ the sensor recorded.  +### 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. + + + +| Before | After | +|---|---| +|  |  | + +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 The roll along the foot of the canvas holds the photographs the grid was diff --git a/docs/manual/index.html b/docs/manual/index.html index cc903d8..3939c00 100644 --- a/docs/manual/index.html +++ b/docs/manual/index.html @@ -117,6 +117,7 @@ th { color: var(--ink-dim); font-weight: 600; }

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.

| Before | After |
|---|---|
![]() | ![]() |
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.
The roll along the foot of the canvas holds the photographs the grid was
showing; click one to open it. The right arrow, D or space opens the next,
diff --git a/docs/manual/media/develop-denoise-after.png b/docs/manual/media/develop-denoise-after.png
new file mode 100644
index 0000000..68f5751
--- /dev/null
+++ b/docs/manual/media/develop-denoise-after.png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:d68ed4c943ff7848f4010f1ac64d5ff25e0dbdc6afd7de0780e4d44c31a3217a
+size 1018071
diff --git a/docs/manual/media/develop-denoise-before.png b/docs/manual/media/develop-denoise-before.png
new file mode 100644
index 0000000..44f0784
--- /dev/null
+++ b/docs/manual/media/develop-denoise-before.png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:30be3acd23e36208bd536096b964560f822fae39f80ce5125fd179000e8d1da4
+size 2394740
diff --git a/docs/manual/media/develop-denoise.gif b/docs/manual/media/develop-denoise.gif
new file mode 100644
index 0000000..b650b53
--- /dev/null
+++ b/docs/manual/media/develop-denoise.gif
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:5f2fecb1a22fdba6745ed0e208ae69528fc9999316acffe5ac780fc580e26faf
+size 1186287
diff --git a/tools/manual/scenes.py b/tools/manual/scenes.py
index a0d0030..c0403a9 100755
--- a/tools/manual/scenes.py
+++ b/tools/manual/scenes.py
@@ -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
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
+DENOISE = '_MG_8862' # Brooklyn Bridge at ISO 8000: a lit railing, a lamp and a camera against the night
# --- the registry -----------------------------------------------------------
SCENES = {}
@@ -841,6 +842,68 @@ def develop_zoom():
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'])
def develop_wb():
at_develop()