Spektrafilm darktable module implementation discussion

It already versions the LUT and film/paper data with hashes. Introspection versions migrate old edits to newer versions of the module. This is how other modules also do this.

We still need a “migrate to newer spektrafilm data” option though. Also what needs to be discussed is when important things like grain or halation code change. Do we keep the old like e.g. “filmic color science v X” or do we automatically move to the new implementation.

Maybe some “spektrafilm version” drop down menu will be necessary.

1 Like

Minor issue: the “format” selection relates to film, not print paper - correct? If so, it might move up to below the film selection, as its current position suggest a relationship to print paper.

It would also be nice to have presets there for APS-C and MFT sensors.

1 Like

Format relates to grain, halation, etc. But it might also need a header to make a more clear distiction.

Are there even APS-C or MFT analog cameras?

edit due to my misreading

No specific settings were used. Tho they look similar, these are edited with various settings and films. I was mostly toying around with leaving “auto print exposure” checked and then just creating a look manually. I’m finding I like the Fuji print setting and typically either gold 200 or portra 400/800

1 Like

@Pascal_Obry has been clear about the importance of not breaking old edits and I strongly agree with him. Perhaps he has some ideas about how this is best managed for Spektrafilm.

Sorry for my ignorance, but why are the data files updating so often even when new films and papers aren’t added?

:raised_hands: Travelling, Will hopefully test tomorrow!

Is this adressed to me? Nothing is being updated actually, just adapted to new use cases of the module and information that needs to be read put into the json files until a final definition has been reached that fulfills all requirements. We are in early development so layout of the packs changed so often (including fixes for bw paper/films that were missing before and thus breaking rendering of them).

Hopefully the time of changes is over now and we will only need updates when spektrafilm stuff is updated.

To anyone who can answer :smile: Not at all meant as a critique. Just curious.

1 Like

I edited the post above to add more info. And basically because I am still leaning what I am doing here :smiley:

For pack data (that is spectral upsampling and film/paper definitions) the current module will use the version the edit was created with and will also offer to download that data if missing from darktable.

Yet if something more deeper into the internals like DIR couplers, grain definition or halation calculation changes this would need updated code in the module thus needing a way to either use the “old” algorithms or transition to the newer ones. That’s why I was thinking about offering a drop down menu in the spirit of the color science version in the filmic module. But this also means keeping a lot of old code to keep old edits the way they were when they were created.

1 Like

No MFT (I think), but APS-C originally was an analog format.

Some more feedback as of 27b5243f52:

  1. I think the spektrafilm module needs to be in the “tone” tab along with other tone mappers, currently it’s in “color” for me with “modules: all”
  2. The preflash exposure seems to be too sensitive, sometimes I wish I could set it to 0.05 or so. The jump from 0 to 0.1 is often too drastic in my experience
  3. I retract my previous suggestion to set DIR couplers to 0.5, maybe they were broken back in that version, as currently having them set at 1.0 the image looks quite nice
  4. I think the “auto print exposure” should be located in the “film” tab because ultimately it acts on the way the “film” tab controls act on the image, and really doesn’t change the functionality of the “print” tab in any way
  5. Not sure how meaningful this sort of feedback is, but I found the default scanner sharpness of 1.0 to meet my sharpness expectations more often than at 0.7, because along with halation the image becomes too peter pan whimsical when it’s 0.7, but maybe it’s just my taste
  6. My idea with line by line film stock and print paper makes it easier to read, but the header looks a bit disorderly this way in general, feels like either the previous implementation was better or the film format should also be split into 2 lines

So far I’m very pleased with the results. There are so many small things here and there that make it look very organic, and you really begin to notice the difference between these organic qualities and a regular edit with split toning and what have you. Really interesting. I’m still somewhat lost as to how I’d like to edit my photos, though

3 Likes

I’ve been following this thread with great interest since it started. Just created a profile to post this. I’m a simple hobbyist photographer who focuses on my family and travel pics. I loved the rendering from Andrea’s original spektrafilm project that I figured out how to install that from source on MacOS (there’s a first time for everything). I’ve been experimenting with a bunch of ways to include it in my workflow. I’m currently a Lightroom Classic user… BUT this darktable module is AMAZING. I’m at the point where the edit I get in dt is nicer than the one in LR for equal time spent on the photo. I’m converting to darktable specifically for this module. I may miss the quick people masking and gen remove but I’ll find ways of getting around that. 1) I’ll need to learn dt masking and 2) I may use Apple Photos’s clean up tool for the gen erase stuff.

Keep up the good work folks and THANK YOU!

5 Likes

Welcome! I suspect you will not be the last one making this move for Spektrafilm. :slightly_smiling_face:

3 Likes

This is absolutely necessary as I see it. Destroying old edits with darktable updates is not an option, I hope @Pascal_Obry agrees. Sorry for bringing up your name Pascal, but this one’s important. :sweat_smile:

That sounds good. I think it’s quite important that the user does not have to manually choose a matching pack for their Spektrafilm module version.

But also… If Spektrafilm will be a module that ships with darktable, is there a reason why all Spektrafilm data is not shipped with darktable? Including it would definitely introduce less friction for the user.

One film/paper data pack version currently sits at 3 MB. Once we reach 10 versions of the pack data, that’ll be 30 MB. Too much to ship? If it needs a download I think I would personally prefer to have a feature that downloads the current version and all previous versions of the film/paper pack. Say one is traveling where internet availability is limited and one wants to open an old edit that needs an older film/paper pack… Perhaps I’ve read too much 100R. :slight_smile:

Also, as much as I love new film stocks, I think it would be good if there’s only one film/paper pack version per version of the module (and darktable). Multiple versions of the film/paper pack per darktable version could possibly lead to a mess? This means we would need to wait 6 months between each film/paper pack release (if we want to stay on stable darktable). Please tell me if my thinking is flawed!

EDIT: what structure for the film/paper data pack versions do you have in mind @piratenpanda? .config/darktable/spektrafilm/[VERSIONNUMBER]/?

1 Like

I’m going to be somewhat busy this week, and I’m not following through the latest additions you Benjamin @piratenpanda have done, but if there is a new build that needs to be compiled, I can do it as soon as you say it. I saw something about the data pack being able to download right from the module. If that’s the case and I need to update the README of my builds’ repo, let me know!

The average film stock is 90kb uncompressed. Zip (deflate) is aprox 36kb. The largest we have is kodak_2302 and doublex, sitting at 135kb uncompressed, 51kb compressed.

They are pretty lightweight! It is the spectra_lut.f32 the one that weights something like 6mb. Just one file that the module needs, for every film and print, present and future. Although, it could get newer revisions coming from spektrafilm original author itself, which already happened a couple of times. And it could contribute to some changes in the photos across different, newer versions.

A possible 2D-delta encode applied to spectra_lut.f32 will cut its size by half and make any compression algorithm (like deflate for ZIP files) work even better.

I’m leaving some snippets for experimentation. This one goes into export_pack.py replacing the LUT write block

      # FNV-1a over the raw bytes; the engine reads this value, it does not
      # recompute it, so any stable hash will do. The hash MUST stay over the
      # ORIGINAL values -- it is the table's identity, and the engine compares
      # it against the one stamped in edit params, so it must not change just
      # because the storage encoding changed (v3 delta vs v2 raw).
      h = 2166136261
      for b in lut.tobytes():
          h = ((h ^ b) * 16777619) & 0xFFFFFFFF
      lut_id = f"{lut_name}@{version}".encode("utf-8")[:255]

      # SFS2 v3: the float16 payload is stored as a lossless 2-D delta code --
      # 2nd-order deltas along the flattened chromaticity grid, then 1st-order
      # deltas along wavelengths within each spectrum (anchor: wl index 0 of
      # every spectrum, first two spectra raw). Neighbouring spectra and
      # wavelengths are near-identical, so deltas are tiny and the payload
      # deflates to ~1/3 of the raw float16 bytes. Pure modular arithmetic:
      # exactly reversible, decoded payload is bit-identical to v2.
      flat = lut.view(np.uint16).reshape(-1).astype(np.int32)
      n, s = flat.size, lut.shape[-1]
      d = flat.copy()                                   # pass 1: grid deltas
      d[2 * s:] = flat[2 * s:] - 2 * flat[s:n - s] + flat[:n - 2 * s]
      wl = np.arange(n, dtype=np.intp) % s != 0         # pass 2: wl deltas
      d[wl] = d[wl] - d[np.arange(n, dtype=np.intp)[wl] - 1]
      delta = (d & 0xFFFF).astype(np.uint16)

      with open(out / "spectra_lut.f32", "wb") as fh:
          fh.write(b"SFS2")
          fh.write(struct.pack("<i", 3))                 # header version (3 = delta float16)
          fh.write(struct.pack("<iii", *lut.shape))
          fh.write(struct.pack("<i", 1))                 # 0 = float32, 1 = float16
          fh.write(struct.pack("<I", h))                 # content hash of ORIGINAL values
          fh.write(struct.pack("<i", len(lut_id)))
          fh.write(lut_id)
          fh.write(delta.tobytes())
      print(f"wrote {out / 'spectra_lut.f32'} shape {lut.shape} float16 v3-delta "
            f"id={lut_id.decode()} hash={h:08x}")

And these two go into spektra_sim.c. Keep in mind I’m assuming a bump and conditionals for a new version (v3) while keeping it working with previous spectra_lut.f32 file. Else, just skip it and simplify these conditionals deleting the f32 branch (:

a) Accept v3 in the header validation (line ~651):

         || fread(&hdr_version, 4, 1, fh) != 1 || (hdr_version != 2 && hdr_version != 3)

b) Decode the deltas before the f16→f32 loop (the dtype == 1 block, line ~681):

      if(dtype == 1) /* float16 -> float32 */
      {
        uint16_t *h16 = malloc(count * sizeof(uint16_t));
        ok = h16 && fread(h16, sizeof(uint16_t), count, fh) == count;
        if(ok)
        {
          if(hdr_version == 3)
          {
            /* SFS2 v3: delta-coded float16. The exporter wrote 2nd-order deltas
               along the flattened chromaticity grid, then 1st-order deltas along
               wavelengths within each spectrum, all modulo 2^16 -- exactly
               reversible. Undo in reverse order; both passes are sequential. */
            const size_t stride = (size_t)dims[2]; /* SF_NWL */
            for(size_t i = 1; i < count; i++)      /* undo wavelength deltas */
              if(i % stride) h16[i] = (uint16_t)(h16[i] + h16[i - 1]);
            for(size_t i = 2 * stride; i < count; i++) /* undo 2nd-order grid */
              h16[i] = (uint16_t)(h16[i] + 2 * h16[i - stride] - h16[i - 2 * stride]);
          }
          for(size_t i = 0; i < count; i++) pack->spectra[i] = _sf_half_to_float(h16[i]);
        }
        free(h16);
      }

All this to save ~4mb of disk space and download! Although, arguably so, if there’s going to be newer versions of this spectra_lut, and backwards compatibility is needed, better be sure we don’t waste space.

Speaking of new film and paper pack releases, I’m pretty sure we as a community we will start creating them and sharing them in some way, so installing them will be a matter of download, copy and paste onto the install folders. Maybe they can be submitted to the designated repository or wherever this data-pack is going to live, so they will be globally available at some point if/when approved for everyone. This approval is something to think about, as it won’t be a good idea to just accept new packs just because. We need them to be used and tested by community members, for quality purposes.

It’s .config/darktable/spektrafilm/packs/[hash] right now

I don’t think this module will “officially support” downloading user generated packs. We should stick to spektrafilm as the only upstream source. What people put in their config folders is another story though. With an possible asset manager in the future this might change though.

Thanks for the code but I don’t see myself experimenting here until we have decided on a final way to solve this.

Thanks again. I also preferred the old way with label dropdown. Will revert this now.

Just tried a few times to build the spektrafilm banch of your repo…

Process fails…I deleted everything…recloned the repo and tried again still fails… Building for WIndows…

In file included from C:/msys64/home/xxx/spectrafilm/build/lib/darktable/plugins/introspection_spektrafilm.c:318:
C:/msys64/home/xxx/spectrafilm/src/iop/spektrafilm.c: In function ‘_film_changed’:
C:/msys64/home/xxx/spectrafilm/src/iop/spektrafilm.c:2400:3: error: implicit declaration of function ‘dt_bauhaus_toggle_set_default’; did you mean ‘dt_bauhaus_slider_set_default’? [-Wimplicit-function-declaration]
2400 | dt_bauhaus_toggle_set_default(g->scan_film, e->positive);
| ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~
| dt_bauhaus_slider_set_default
compi[ 70%] Built target overlay
lation terminated due to -Wfatal-erro[ 70%] Linking C shared module libvignette.dll
rs.
[ 70%] Built target rawoverexposed
make[2]: *** [lib/darktable/plugins/CMakeFiles/spektrafilm.dir/build.make:86: lib/darktable/plugins/CMakeFiles/spektrafilm.dir/introspection_spektrafilm.c.obj] Error 1
make[1]: *** [CMakeFiles/Makefile2:6921: lib/darktable/plugins/CMakeFiles/spektrafilm.dir/all] Error 2
make[1]: *** Waiting for unfinished jobs…
[ 70%] Building C object lib/darktable/plugins/CMakeFiles/graduatednd.dir/introspection_graduatednd.c.obj
[ 70%] Generating introspection_relight.c
[ 70%] Linking C shared module libsplittoning.dll
[ 70%] Generating introspection_zonesystem.c
[ 70%] Linking C shared module libgrain.dll
[ 70%] Built target colorzones
[ 70%] Linking C shared module libhighlights.dll
[ 70%] Built target velvia
Detaching debug infos for vignette.
[ 70%] Linking C shared module libclahe.dll
Detaching debug infos for splittoning.
Detaching debug infos for grain.
[ 70%] Building C object lib/darktable/plugins/CMakeFiles/relight.dir/introspection_relight.c.obj
[ 70%] Building C object lib/darktable/plugins/CMakeFiles/zonesystem.dir/introspection_zonesystem.c.obj
Detaching debug infos for highlights.
[ 70%] Built target vignette
Detaching debug infos for clahe.
[ 70%] Built target splittoning
[ 70%] Built target grain
[ 70%] Built target highlights
[ 70%] Linking C shared module libcolortransfer.dll
[ 70%] Linking C shared module libprofile_gamma.dll
[ 70%] Built target clahe
Detaching debug infos for colortransfer.
Detaching debug infos for profile_gamma.
[ 70%] Linking C shared module libchannelmixer.dll
[ 70%] Linking C shared module libcolormapping.dll
[ 70%] Built target colortransfer
Detaching debug infos for channelmixer.
[ 70%] Built target profile_gamma
Detaching debug infos for colormapping.
[ 70%] Built target channelmixer
[ 70%] Linking C shared module libgraduatednd.dll
[ 70%] Linking CXX shared module libbilateral.dll
[ 70%] Built target colormapping
[ 71%] Linking C shared module librelight.dll
Detaching debug infos for graduatednd.
Detaching debug infos for bilateral.
Detaching debug infos for relight.
[ 72%] Linking C shared module libzonesystem.dll
[ 72%] Built target graduatednd
[ 72%] Built target bilateral
[ 72%] Built target relight
Detaching debug infos for zonesystem.
[ 72%] Built target zonesystem
make: *** [Makefile:156: all] Error 2

1 Like

I don’t think building from my branch works as it’s missing a lot of merged PRs in dt master this code relies on.

Try git apply 21534.diff on dt master and this should work. Just use diff instead of patch in the link in the first post to obtain it.

1 Like

Hello, bad luck for me, my Macbock Pro have x86_64 (Intel) version 12.7.6 (21H1320)

The x86_64 build for macOS is there. Or you mean isn’t compatible with your macOS version?