Skip to content

feat(loaders): combine Pentax Pixel Shift RAWs - #1584

Open
Nikoh77 wants to merge 2 commits into
marcinz606:mainfrom
Nikoh77:feat/pentax-pixel-shift
Open

Nikoh77 wants to merge 2 commits into
marcinz606:mainfrom
Nikoh77:feat/pentax-pixel-shift

Conversation

@Nikoh77

@Nikoh77 Nikoh77 commented Oct 31, 2026 •

Copy link
Copy Markdown

Closes #1499

Pentax Pixel Shift PEF/DNG files hold four Bayer shots, one pixel apart. NegPy only read the first one, without saying so. This PR combines them, so every pixel gets measured R, G and B and no demosaic runs.

What's in it

  • Detection from the TIFF structure (several same-size CFA images) plus the Pentax makernote Quality tag: 7 is Pixel Shift, 6 is an in-camera HDR, which is not combined. Any TIFF-based RAW with several shots gets the shot choice; only Pentax Pixel Shift combines.
  • Each shot opens with shot_select set before open_file. rawpy.imread sets it after the open, so it always gives shot 1.
  • The merge follows RawTherapee's rtengine/pixelshift.cc, without motion correction. On a PEF and a DNG it matches RawTherapee 5.13 within 16-bit rounding (max diff 0.7/65535). Credit to the RawTherapee folks.
  • Motion check: the two green samples of each pixel come from two different shots, so I compare them against a noise model. If the shots don't agree (shake, something moving, light flicker) the card and a toast warn. The merge still happens.
  • New Shots row in the Raw Decode card: Pixel Shift (Auto) or one shot. For an in-camera HDR RAW it lists the three exposures.
  • The combined counts are cached in memory per file, so canvas, HQ preview and export don't merge again.
  • Linear output, Trichrome triplets, HDR brackets and stitches use the combined source. A composite warns if any of its parts fails or moves.
  • Thumbnails stay on shot 1, on purpose: merging every frame of a roll for thumbnails is too slow.
  • Bumps BOUNDS_VERSION and THUMBNAIL_RENDER_VERSION. Bounds saved before this came from shot 1. On my test scenes they differ from the combined source by 0.4 to 2.2% of the span, per channel, so a slight cast.

Second commit: a sliders.py fix. The GC can clear a CompactSlider before Qt deletes its children; a visible child then sends Hide to the event filter, which raises inside a Qt virtual, and PyQt6 aborts. c72d66f avoids it in the Crosstalk test, this fixes the widget. Two getattrs plus a test.

Numbers (K-3 III, my machine)

  • Read + merge: about 3 to 4 s per 26 MP file, once per session.
  • Peak memory of the merge prototype: 760 to 880 MB. I haven't measured it again on the final code.

Tested

  • make all is green (13,323 tests).
  • In the app with my K-3 III files: Pixel Shift vs Shot 1 at 100%, the warning on a shot taken by pressing the shutter button, an in-camera HDR, and Linear output (demosaic: Pixel Shift in the description).
  • Not yet: a real Trichrome triplet of Pixel Shift files. I don't have the narrowband light yet.

Open questions

  • The merge is numpy for now. Happy to move it to negpy_core if you prefer.
  • The BOUNDS_VERSION bump re-meters everyone's frames once and flags roll baselines. The alternative was a per-edit marker for edits saved before this, but it needed about eight touch points (undo, work prints, diptych halves, Merge to TIFF, paste...). The bump looked simpler and follows your rule. Your call.
  • I wrote the German strings and the German guide panel myself. A native speaker hasn't reviewed them.

🤖 Generated with Claude Code

@Nikoh77
Nikoh77 force-pushed the feat/pentax-pixel-shift branch from 8ee6c76 to 21d2ade Compare October 31, 2026 17:56
Nikoh77 and others added 2 commits October 31, 2026 18:57
A Pentax Pixel Shift RAW holds four Bayer shots, one photosite apart.
NegPy decoded only the first shot and gave no warning.

- Find a multi-shot RAW from its TIFF structure: count the CFA images
  of the same size. The Pentax makernote Quality tag (7) tells a Pixel
  Shift from an in-camera HDR.
- Open each shot with shot_select set before open_file. LibRaw selects
  the shot at open, so rawpy.imread always gives the first shot.
- Combine the shots with the mapping of RawTherapee
  (rtengine/pixelshift.cc), without motion correction. Each pixel gets
  one R, two G and one B sample, scaled as LibRaw scales one shot on
  each path. The result is the same as RawTherapee 5.13 within 16-bit
  rounding (max 0.7/65535 on a PEF and a DNG).
- Compare the two green samples of each pixel with a noise model, and
  warn when the shots do not agree (motion, vibration, light change).
- Add a Shots choice to the Raw Decode card: Pixel Shift (Auto) or one
  shot. The choice is part of the source token and of each cache key.
- Keep the combined counts in an in-memory cache, one entry per file.
- Linear Output, Trichrome triplets, HDR brackets and stitches use the
  combined source. A composite warns for each of its parts.
- Bump BOUNDS_VERSION and THUMBNAIL_RENDER_VERSION: a Pixel Shift file
  now measures and renders from the combined source.

Refs marcinz606#1499

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The GC can clear a slider before Qt deletes its children. A visible
child then sends Hide to the event filter, which read self.label or
self.slider, raised inside a Qt virtual, and PyQt6 aborted.

c72d66f avoids it in the Crosstalk test; this fixes the widget.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@Nikoh77
Nikoh77 force-pushed the feat/pentax-pixel-shift branch from 21d2ade to b2d2077 Compare October 31, 2026 18:12

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug]: Pentax Pixel Shift RAW opens as frame 0 only, with no warning

1 participant