04 May 2022

# Darktable for Fujifilm Cameras

You know what I like to see when I import photos from my Fujifilm camera into Darktable?

However, that is not the default. Darktable, like most raw developers, is camera-agnostic.

### agnostic

ăg-nŏs′tĭk
noun
[...]
1. One who is doubtful or noncommittal about something.

Which means that Darktable does not know about any Fujifilm-specific raw file metadata, such as crop, dynamic range modes, or film simulations. Thus what you'd normally see in Darktable is more like this:

Notice how all the DR200/DR400 images are underexposed by one and two stops, how the first JPG is a square crop, but the RAF is 3:2, how the color of the grass and the train are subtly different in RAF and JPG.

But thankfully, Darktable has a scripting interface for automating things. And what I've done here is a little script that uses exiftool to read the missing metadata from the RAF file and apply appropriate styles to get Darktable's default rendering close to the JPG.

Here's the lua script in its entirety:

--[[ fujifilm_auto_settings-0.2

Apply Fujifilm film simulations, in-camera crop mode, and dynamic range.

Copyright (C) 2022 Bastian Bechtold <bastibe.dev@mailbox.org>

This program is free software; you can redistribute it and/or modify
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License along
with this program; if not, write to the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
--]]

Automatically applies styles that load Fujifilm film simulation LUTs,
copy crop ratios from the JPG, and correct exposure according to the
chosen dynamic range setting in camera.

Dependencies:
- exiftool (https://www.sno.phy.queensu.ca/~phil/exiftool/)
- Fuji LUTs (https://blog.sowerby.me/fuji-film-simulation-profiles/)

Based on fujifim_dynamic_range by Dan Torop.

Film Simulations
----------------

Fujifilm cameras are famous for their film simulations, such as Provia
or Velvia or Classic Chrome. Indeed it is my experience that they rely
on these film simulations for accurate colors.

Darktable however does not know about or implement these film
3DL LUTs. (PNG LUTs are also available, but they show a strange
posterization artifact when loaded in Darktable, which the 3DLs do
not).

In order to use this plugin, you must prepare a number of styles:
- provia
- astia
- velvia
- classic_chrome
- pro_neg_standard
- pro_neg_high
- eterna
- acros_green
- acros_red
- acros_yellow
- acros
- mono_green
- mono_red
- mono_yellow
- mono
- sepia

These styles should apply the according film simulation in a method of

This plugin checks the image's "Film Mode" exif parameter, and applies
the appropriate style. If no matching style exists, no action is taken
and no harm is done.

Crop Factor
-----------

Fujifilm cameras allow in-camera cropping to one of three aspect
ratios: 2:3 (default), 16:9, and 1:1.

This plugin checks the image's "Raw Image Aspect Ratio" exif
parameter, and applies the appropriate style.

To use, prepare another four styles:
- square_crop_portrait
- square_crop_landscape
- sixteen_by_nine_crop_portrait
- sixteen_by_nine_crop_landscape

These styles should apply a square crop and a 16:9 crop to
portrait/landscape images. If no matching style exists, no action is
taken and no harm is done.

Dynamic Range
-------------

Fujifilm cameras have a built-in dynamic range compensation, which
(optionally automatically) reduce exposure by one or two stops, and
compensate by raising the tone curve by one or two stops. These modes
are called DR200 and DR400, respectively.

The plugin reads the raw file's "Auto Dynamic Range" or "Development
Dynamic Range" parameter, and applies one of two styles:
- DR200
- DR400

These styles should raise exposure by one and two stops, respectively,
and expand highlight latitude to make room for additional highlights.
I like to implement them with the tone equalizer in eigf mode, raising
exposure by one/two stops over the lower half of the sliders, then
ramping to zero at 0 EV. If no matching styles exist, no action is
taken and no harm is done.

These tags have been checked on a Fujifilm X-T3 and X-Pro2. Other
cameras may behave in other ways.

--]]

local dt = require "darktable"
local du = require "lib/dtutils"
local df = require "lib/dtutils.file"

du.check_min_api_version("7.0.0", "fujifilm_auto_settings")

-- return data structure for script_manager

local script_data = {}

script_data.destroy = nil -- function to destory the script
script_data.destroy_method = nil -- set to hide for libs since we can't destroy them completely yet, otherwise leave as nil
script_data.restart = nil -- how to restart the (lib) script after it's been hidden - i.e. make it visible again

local function exiftool_get(exiftool_command, RAF_filename, flag)
local command = exiftool_command .. " " .. flag .. " -t " .. RAF_filename
dt.print_log(command)
local output = io.popen(command)
output:close()
if #exiftool_result == 0 then
dt.print_error("[fujifilm_auto_settings] no output returned by exiftool")
return
end
local exiftool_result = string.match(exiftool_result, "\t(.*)")
if not exiftool_result then
dt.print_error("[fujifilm_auto_settings] could not parse exiftool output")
return
end
exiftool_result = exiftool_result:match("^%s*(.-)%s*$") -- strip whitespace return exiftool_result end local function apply_style(image, style_name) for _, s in ipairs(dt.styles) do if s.name == style_name then dt.styles.apply(s, image) return end end dt.print_error("[fujifilm_auto_settings] could not find style " .. style_name) end local function apply_tag(image, tag_name) local tagnum = dt.tags.find(tag_name) if tagnum == nil then -- create tag if it doesn't exist tagnum = dt.tags.create(tag_name) dt.print_log("[fujifilm_auto_settings] creating tag " .. tag_name) end dt.tags.attach(tagnum, image) end local function detect_auto_settings(event, image) if image.exif_maker ~= "FUJIFILM" then dt.print_log("[fujifilm_auto_settings] ignoring non-Fujifilm image") return end -- it would be nice to check image.is_raw but this appears to not yet be set if not string.match(image.filename, "%.RAF$") then
dt.print_log("[fujifilm_auto_settings] ignoring non-raw image")
return
end
local exiftool_command = df.check_if_bin_exists("exiftool")
if not exiftool_command then
return
end
local RAF_filename = df.sanitize_filename(tostring(image))

-- dynamic range mode
-- if in DR Auto, the value is saved to Auto Dynamic Range, with a % suffix:
local auto_dynamic_range = exiftool_get(exiftool_command, RAF_filename, "-AutoDynamicRange")
-- if manually chosen DR, the value is saved to Development Dynamic Range:
if auto_dynamic_range == nil then
auto_dynamic_range = exiftool_get(exiftool_command, RAF_filename, "-DevelopmentDynamicRange") .. '%'
end
if auto_dynamic_range == "100%" then
apply_tag(image, "DR100")
-- default; no need to change style
elseif auto_dynamic_range == "200%" then
apply_style(image, "DR200")
apply_tag(image, "DR200")
dt.print_log("[fujifilm_auto_settings] DR200")
elseif auto_dynamic_range == "400%" then
apply_style(image, "DR400")
apply_tag(image, "DR400")
dt.print_log("[fujifilm_auto_settings] DR400")
end

-- cropmode
local raw_aspect_ratio = exiftool_get(exiftool_command, RAF_filename, "-RawImageAspectRatio")
if raw_aspect_ratio == "3:2" then
apply_tag(image, "3:2")
-- default; no need to apply style
elseif raw_aspect_ratio == "1:1" then
if image.width > image.height then
apply_style(image, "square_crop_landscape")
else
apply_style(image, "square_crop_portrait")
end
apply_tag(image, "1:1")
dt.print_log("[fujifilm_auto_settings] square crop")
elseif raw_aspect_ratio == "16:9" then
if image.width > image.height then
apply_style(image, "sixteen_by_nine_crop_landscape")
else
apply_style(image, "sixteen_by_nine_crop_portrait")
end
apply_tag(image, "16:9")
dt.print_log("[fujifilm_auto_settings] 16:9 crop")
end

-- filmmode
local raw_filmmode = exiftool_get(exiftool_command, RAF_filename, "-FilmMode")
local style_map = {
["Provia"] = "provia",
["Astia"] = "astia",
["Classic Chrome"] = "classic_chrome",
["Eterna"] = "eterna",
["Acros+G"] = "acros_green",
["Acros+R"] = "acros_red",
["Acros+Ye"] = "acros_yellow",
["Acros"] = "acros",
["Mono+G"] = "mono_green",
["Mono+R"] = "mono_red",
["Mono+Ye"] = "mono_yellow",
["Mono"] = "mono",
["Pro Neg Hi"] = "pro_neg_high",
["Pro Neg Std"] = "pro_neg_standard",
["Sepia"] = "sepia",
["Velvia"] = "velvia",
}
for key, value in pairs(style_map) do
if string.find(raw_filmmode, key) then
apply_style(image, value)
apply_tag(image, key)
dt.print_log("[fujifilm_auto_settings] film simulation " .. key)
end
end
end

local function detect_auto_settings_multi(event, shortcut)
local images = dt.gui.selection()
if #images == 0 then
else
for _, image in ipairs(images) do
detect_auto_settings(event, image)
end
end
end

local function destroy()
dt.destroy_event("fujifilm_auto_settings", "post-import-image")
dt.destroy_event("fujifilm_auto_settings", "shortcut")
end

if not df.check_if_bin_exists("exiftool") then
dt.print_log("Please install exiftool to use fujifilm_auto_settings")
end

dt.register_event("fujifilm_auto_settings", "post-import-image", detect_auto_settings)

dt.register_event("fujifilm_auto_settings", "shortcut", detect_auto_settings_multi, "fujifilm_auto_settings")

script_data.destroy = destroy

return script_data


However, there's a catch: Scripts in Darktable can not modify darkroom state directly. But they can load styles. So to make the script work, we need to define a number of styles that do the heavy lifting here:

• Two styles DR200 and DR400 for the dynamic range modes that brighten the image by one and two stops, respectively (I like to use the Tone Equalizer like this).
• Four styles square ̲crop_landscape and square ̲crop_portrait and sixteen ̲by ̲nine ̲crop_landscape and sixteen ̲by ̲nine ̲crop_portrait that crop landscape/portrait images to 1:1 and 16:9 ratio.
• One style for each film simulation you use: provia, astia, velvia, classic ̲chrome, pro ̲neg ̲standard, pro ̲neg ̲high, eterna, acros, acros ̲green, acros ̲red, acros ̲yellow, mono, mono ̲green, mono ̲red, mono ̲yellow, sepia. The linked styles use Stuart Sowerby's Film Simulation LUTs as film simulations, which must be installed in $LUTs/Fujifilm XTrans III/$lut.3dl.

Download a zip file with all the above styles here, and appropriately-renamed LUTs here. (This section will be revised once I finish building my own set of LUTs).

Then copy the lua script to ~/.config/darktable/lua/contrib/, activate it in the script manager (bottom left in the lighttable), and it should automatically run whenever you import new Fujifilm raf files! (Start Darktable with darktable -d opencl to see debug messages, and bind a keyboard shortcut to lua scripts/fujifilm_auto_settings to trigger the script manually.)

Tags: photography fujifilm darktable

# Converting Capture one Presets to LUTs

A while ago, I bought an RNI film pack for Capture One. That's a set of presets that makes your digital photos look similar to analog film scans. However, since then my other image editor, Darktable just released a new version, I'm now back to using Darktable instead of Capture One, thus without access to those presets.

Here's how to export Capture One presets to LUTs, to make them accessable to other programs.

The fun thing is, LUTs are just PNG files that contain a table of colors. You know, a "Look Up Table", of sorts. So, in order convert a preset to a LUT, all we need to do is apply the preset to a pristine "identity" LUT, and export it as a new PNG.

1. Get yourself an identity LUT.
For example, the one included in Stuart Sowerby's Fuji Film Simulation Profiles. Choose the sRGB PNG LUTs, for RawTherapee and Affinity Photo.
2. Open the LUT PNG in Capture One.
3. Apply the preset you want.
Optionally lower saturation by -15, see below.
4. Export as PNG.
Make sure the color space is sRGB, just like the original file.

As easy as that.

A few more adjustments: many Capture One presets expect to be working on raw data, which is less saturated than Darktable's default. So I export with -15 saturation. Also, many presets include spacial adjustments such as Highlights or Shadows that are bound to not play well with the LUT PNG. To disable them, delete the offending lines from the *.costyle files1, or compensate the values with opposite slider movements.

When applying the LUTs in Darktable's lut 3D module, there are a few more things you can do to fit them into your workflow. For example, you can lower the opacity of the lut 3D module to vary their effect. Or you can choose chromaticity as blend more to only apply their color transformation, but keep Darktable's tonal rendering. In normal blend mode, some LUTs prefer a flat rendering as their input, so lower contrast in filmic rgb to zero and use the auto-pickers to set the image black and white point.

## Footnotes:

1

don't do this for RNI LUTs, it's forbidden by the User License Agreement that is hidden quite well in dark-grey-on-black at the very bottom of their website

Tags: photography

# Books of 2021

## Hologrammatica

In the last years, I have almost exclusively read books in English. Science fiction, in particular, seems to happen exclusively in English (and perhaps Chinese). So much so, that I have come to associate German only with bad translations and personal communications, whereas English was the language of science, engineering, and fiction. In 2021 however, to my surprise, I stumbled upon Tom Hillenbrand's Hologrammatica, an science fiction thriller in German. It was a peculiar experience, reading the familiar tropes of the genre in a different language. Somehow it made the story feel more immediate and approachable to me. Strange, what effect language can have.

In the book, humanity has decided to hide reality behind holograms. Clothes and hairstyles can be altered on the fly, street lighting is replaced with projected advertisements. The twist is that in this semi-dystopian world, everyone knows that the holograms hide the truth, which is a collapsed society and dilapidated infrastructure. It is a smart, very current backdrop for a detective story with mind uploads, space elevators, and all the modern trappings of science fiction. A thoroughly enjoyable read!

Sci-Fi honorable mentions: Andy Weir's pop sci-fi Project Hail Mary and Martha Well's first novel-length murderbot entry Fugitive Telemetry.

## Not Much of an Engineer

I have always been fascinated with aviation, and the technology of warfare. But most of the non-fiction I have read about these topics focuses on the stories of pilots and companies and soldiers, not engineers. In these stories some of the most important plot points came from advances in technology, yet they didn't describe how those changes came about. In 2021, I finally found a good history of aviation technology: Sir Stanley Hooker's Not Much of an Engineer describes the work of the author as the principal engineer at Bristol and Rolls-Royce from the Merlin-era second world war engines to modern turbofans. I guess you need to be a bit of an aviation/engineering geek to enjoy this, but to me this book was an important missing link that I had always looked for.

Non-fiction honorable mentions: David Goodsell's The Machinery of Life wonderfully illustrates the inner workings of cells.

## Crafting Interpreters

How do computers work? This question is surprisingly hard to answer. For me, the answer came in three books: Charles Petzold's Code: The Hidden Language of Computer Hardware and Software explained to me how processors work. The Arpaci-Dusseau's Operating Systems: Three Easy Pieces describes the infrastructure of operating systems that make processors and storage and network available to programs. And now, finally, Robert Nystrom's Crafting Interpreters filled in the last step, how a programming language is built.

The book describes two implementations of a simple programming language. The first one is a high-level introductory scripting language implemented in Java, the second a high-performance reimplementation in C. Fascinatingly, the entire source code for these implementations is included in the book's text, such that you can entirely follow along (or program along) and have a working programming language at all times. A truly eye-opening glimpse into the innards of the tools we are using every day. The book requires familiarity with Java and C to enjoy.

Tags: books

# Fixing AMD OpenCL on Windows

When I recently installed Windows 11 on my desktop, my photo editor Darktable suddenly got much slower than it used to be. When I looked into its preferences, I noticed OpenCL was no longer available.

As it turns out, some versions of the AMD graphics driver apparently no longer ship with OpenCL support on Windows. However, they do ship with the necessary libraries, it's just that these libraries are not registered any longer.

To register them, open the Registry Editor (aka regedit.exe), navigate to the key HKEY_LOCAL_MACHINE\SOFTWARE\Khronos\OpenCL\Vendors (if the key does not exist, create it), and create a new DWORD of Value 0. Now rename the DWORD to be the path to your amdocl64.dll. Mine is C:\Windows\System32\DriverStore\FileRepository\u0344717.inf_amd64_d38cec78c83eee99\B343886\amdocl64.dll. Search C:\Windows\System32\ for amdocl64.dll to find the correct path on your computer.

With that, OpenCL was once again recognized by Darktable and the rest of my photo editing programs.

(source)

Tags: computers windows

# Comparing the Fujifilm XF 16‑80 with the XF 18‑138 Travel Zoom Lenses

When I bought into the Fuji system, I selected the XF 18‑135 f/3.5‑5.6 R LM OIS WR as my main zoom lens. This is a lens with a very wide focal range, that is commonly called a “travel zoom” because you could travel the world with just this one lens. And indeed I happily did. In 2019, Fujifilm released a second travel zoom lens, the XF 16‑80 f/4 R OIS WR. Ever since, I have wondered how this new 16‑80 compares to my 18‑135. But given that these lenses are somewhat similar, few people on the internet were ever able to compare them side by side. This blog post will change that.

The 18‑135 has served me very well indeed. Of course it is not the world's brightest lens, nor sharpest, nor smallest. But so long as I can get the shot, these limitations don't bother me. Except for two things: The transition between in-focus and out-of-focus can be a bit rough, and my lens extends on its own when carried on a sling. My hope is that the 16‑80 has a nicer rendering, especially for people photos with out-of-focus backgrounds, and a stiffer zoom ring that doesn't creep.

## Physical

Physically, the 16‑80 is a slight bit smaller (89 mm vs 98 mm) and ligher (440 g vs 490 g) than the 18‑135, but the difference is negligible on camera. If anything, the 16‑80 actually feels a bit bigger to me due to its larger front element. Some people claim that the 16‑80 feels better built than the 18‑135. To that end, the rings on my 16‑80 turn more smoothly than my 18‑135's, but then that is comparing a new-ish lens to a well-used one. Autofocus speeds are also reputably different, but they feel similar to me.

The 16‑80 has a numbered aperture ring that allows for adjustments while the camera is turned off. On the other hand, the 18‑135 can switch to and from auto-aperture without losing its preset aperture, which is useful as well. The ideal lens for me would have both the auto-aperture switch and the numbered aperture ring. Oh well.

The 16‑80 has a 72 mm filter thread, while the 18‑135 uses 67 mm filters. This is somewhat annoying for me, as my filters are all 67 mm, which also fit on my 70‑300. I'd imagine users of the 72 mm 10‑24 see this differently. Anyway, using a thin polarizer with a 72‑to‑67 step-down ring works without issue on the 16‑80. My inch-thick macro filter however does vignette heavily until 23 mm.

## Rendering

In terms of rendering, I find the 16‑80 to have a gentler transition from in-focus to out-of-focus, and indeed render out-of-focus backgrounds more smoothly than the 18‑135. At the long end, the 16-80 is actually a useful portrait lens, which is an unexpected but welcome feature for my photography.

At the wide end, the 16‑80 exhibits some significant distortion. While the camera or post processing programs can easily correct this, it leaves the image corners stretched, and renders the 16‑80's nice round bokeh balls as ugly ovals. Thus large-aperture shots at the wide end can be somewhat problematic.

## Resolution

Next, let's compare the resolution of the two lenses. To do that, I printed a test chart, and took some test images. All images were taken in identical illumination, on a tripod, with the chart covering half the sensor height/width. The following graphic shows a resolution scale near the center of the frame and near the left edge of the frame. And just for fun, I've thrown in a similar analysis from two prime lenses as well. The resolution scales are in 200x line widths per picture height. On my 6000×4000-pixel sensor, the theoretical maximum resolution would therefore be a resolution number of ⁴⁰⁰⁰∕₂₀₀ = 20. All test images are straight crops from original images, reproduced at their original resolution.

Center images were focused in the center, and side images were focused on the side. A two-second timer was used to eliminate camera shake. Due to the geometry of my room and the size of my printer, all pictures were taken near the close-focusing distance of the lenses. A red line indicates the limit of resolution of these lines, as judged by my eyes. The corresponding resolution numbers are generally consistent with the ones published by opticallimits and lenstip1, albeit I judged them a bit more conservatively.

The results of this test are surprising to me: the 16‑80 is actually my sharpest lens. It not only bests the 18‑135 at all settings, but also the 35 f/1.4. Only the 60 f/2.4 can reach similar resolution at the same f‑numbers. Generally, the 16‑80 is perfectly sharp right from f/4, while the 18‑135 has to be stopped down to f/8 to reach a similar resolution, especially on the image edges. Only at the long end does the 16‑80 benefit from stopping down.

That said, take these resolution measurements with a grain of salt. For instance, a “great” result of 15 (3000 LW/PH) translates to a blur radius of 1.3 pixels, while a “mediocre” result of 10 (2000 LW/PH) is instead 2 pixels. This is scientifically significant, but not at all relevant to (my) photography.

Additionally, the fact that the 60 f/2.4 macro lens scores so highly but the 35 f/1.4 does not is an indication that these measurements might be biased by being taken near the close focusing distance of the lenses. Thus the next set of images compares these lenses at more natural distances.

At this farther distance, and with a more natural subject, the differences are no longer as easily visible. What differences there are this time favor the 18‑135 instead of the 16‑80. Interestingly, I didn't see any significant differences between these pictures when looking at them “merely” side-by-side in Capture One. Only when I actually assembled these here graphics and looked at them at 200% did the differences become apparant.

Nevertheless, it remains curious that there would be such a difference between the two lenses. Then, someone mentioned that the 16‑80 might suffer from shutter shock, where the camera's mechanical shutter jolts the camera enough to upset the image stabilization system and induce a slight bit of motion blur. An issue such as this might explain the 16‑80's slightly reduced resolution in my test shots. So I created another series of images, but this time both, with the mechanical shutter, and with electronic shutter. In electronic shutter mode, nothing moves in the camera and there can be no shutter shock.

From this comparison, I can see no evidence of shutter shock. It might have been an issue on earlier firmware versions of the 16‑80, but my camera (an X-T2) and lens (at firmware 1.05) does not does not exhibit shutter shock. Furthermore, this series of pictures shows the 16‑80 and 18‑135 essentially matched in image resolution.

All of that said, I must add that all of these comparisons used extremely tight crops of high-contrast geometrical features. Most of the differences here are all but invisible in actual photographs. From these resolution experiments, I see no reason to prefer one lens over the other. Both of them are perfectly sharp in everyday use.

## Conclusion

So, how to choose between the Fujifilm XF 16‑80 f/4 R OIS WR and the XF 18‑135 f/3.5‑5.6 R LM OIS WR? My 16‑80 has tighter aperture and zoom rings, does not creep, and has a smoother rendering of out-of-focus background. On the other hand, I do find the increased telephoto of the 18‑135 very useful, and it doesn't suffer from wide-angle distortion as much as the 16‑80.

In terms of resolution, I did not find fault with either lens. Both are very sharp across their entire focal range and the entire frame. That said, the 18‑135 does benefit from stopping down for optimum resolution, while the 16‑80 is sharp right from f/4, and the 16‑80 might be sharper for closer subjects.

My tentative conclusion from these experiments is therefore that I would slightly prefer the 16‑80 for people pictures, where the close-focus sharpness and nicer background rendering are advantageous, and the larger aperture at 80 mm might make a difference. And I would prefer the 18-135 for landscapes, where stopping down is usually easy and the longer focal length comes in handy.

That said, the differences in rendering and resolution are really very minor, and the choice most importantly comes down to the focal range. Which is as it should be with modern lenses. And both lenses are of course very well-built, weather sealed, and have fantastic image stabilization. But you probably knew that already.

## Footnotes:

1

multiply lenstip numbers by 2×16.7 mm to convert from lpmm (lines per millimeter) to LW/PH (line width per picture height)