C5060WZ, C7070WZ, SP-510UZ, SP-550UZ, SP-565UZ and SP-570UZ files say
uncompressed 12-bit with many strips whose byte counts sum to exactly
width x height x 1.5. main refused them because the 12-bit branch wanted a
single strip, so they fell to "Olympus compressed ORF" and opened
preview-only. The samples are packed MSB-first, the strips hold two fields
(even output rows, then odd ones) with one gap at the seam, and the last row
of the second field sits right after the last strip. Accepted only when all of
that holds exactly. CFA comes from the Exif tag, white balance from maker-note
entries 0x1017/0x1018, black 0. RENDER_CACHE_VERSION 27 (adjust at merge).
Lightroom Classic 15.6 imports an XMP packet's channel curves only when it holds
ToneCurvePV2012 and all three of ToneCurvePV2012Red/Green/Blue; any partial set
(master + one or two channels, channels without the master) imports without
channel curves. #416 read them whenever the master curve was present.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Fitted with `lightcraft-cli calibrate` to 125 usable ILCE-7CR ARWs (of 131,
September 2025 to September 2026, Standard creative style) and their
in-camera JPEGs; aggregate colour statistics only.
Without it, 8 of the 131 photos fail the fit gates and open with the
neutral matrix (dE76 6-26 against the camera JPEG). On 26 withheld photos
(profile fitted to the other 105) mean dE76 goes 4.07 -> 3.29 and both
withheld rejections are accepted (26.0 -> 6.1, 6.2 -> 5.5); the 24
withheld photos whose own fit was accepted are about unchanged (12
improve, mean 3.06 -> 3.08, worst regression 1.8). With the final profile
all 131 are accepted. The nine public raw.pixls.us ILCE-7CR codings stay
accepted (mean 1.59 -> 1.99).
A corpus test checks the public lossless-L sample's fit keeps exactly the
built-in profile's matrix and table.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Three CC0 raw.pixls.us files of one ILCE-7CR (61 MP) scene: lossless
compressed L and M (subsampled YCbCr) and compressed ARW2. A raw corpus
test pins model, colour-filter layout, black level, white level, as-shot
gains and default crop, and checks that the lossless and compressed
mosaics agree inside the crop (green block correlation 0.9976). An engine
corpus test checks each gets a fitted camera look, not the neutral
fallback. All nine public codings decode and fit (mean dE76 1.59 against
their camera JPEGs).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Reuse the landed current-view deletion selection logic for photo.deletePermanently, retaining survivors or choosing the next neighbour and the previous at the end. Preserve catalog-only deletion and existing undo/persistence behavior.
Refs #441, #451, #545.
Demo scene pixels were generated inside `OnceLock::get_or_init` on the
caller's thread, and the render runs on rayon. A rayon worker that waits
for rows another worker took runs queued jobs on top of itself; when one
of those asked for the same scene, it waited in `get_or_init` on the
generation further down its own stack, and every other request for that
scene waited behind it, forever. The faces scan's pool threads hit it (two
tests_face_recognize tests sat in `Once::wait` for 30 minutes); any rayon
worker loading demo photos could.
Each (scene, size) slot now has one generation at a time: the first caller
renders, with no lock held, and later callers wait on a condvar for that
generation's outcome. The render never needs a thread that waits:
- a rayon worker (the faces scan, merges) renders on its own thread alone,
without rayon (`Scene::render_fit_serial`, the same pixels), so it never
runs other jobs mid-render, and the work stays within its pool and that
pool's limits (scan pace, LIGHTCRAFT_FACE_THREADS);
- other callers (the render workers, plain threads) render in parallel on a
pool of rayon's default size (RAYON_NUM_THREADS, else the available
parallelism), whose threads only render; where it can't start, they
render alone too (no rayon, so no deadlock). Generating a scene never
touches the global rayon pool (asking its size would start it, which
panics where its threads can't start).
The scan's first render of a demo scene now takes 1 to 2.3 s on one
thread instead of 0.5 to 1.3 s on its light pace's two (M3 Max, 2560 px
preview source); interactive loads are unchanged.
A generation number gives each waiter the outcome of the generation it
waited for, even when a retry starts first. A render that panics is an
error for its callers (`scene_pixels` returns a Result) and the next
request tries again. No duplicates, no unbounded threads.
Tests (each waits with a timeout, so a deadlock fails instead of hanging):
rayon workers keep asking a 4-thread pool for a scene while it is generated
(24 replies counted, at least 4 overlapping; times out at 120 s on the
OnceLock code); a scene is rendered once however many ask (the first
version of this fix rendered 5 copies at once); a rayon worker renders
alone on its own thread; callers wait for the generation running; a failed
generation is an error and the next request retries; a retry never changes
the outcome waited for (state-level, and a racing retrier: 60 of 60
waiters got the retry's pixels on the previous version); in a process
whose global pool failed to start, a scene is still generated; the serial
render is the parallel one, pixel for pixel.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A catalog written before the lens data was read at import (RW2/RWL from
v0.4.0) has none stored, and the lens correction stays off. Reload now
stores the fresh probe's lens data (new journaled, undoable
Op::SetEmbeddedLens; catalog format 4) and, for photos still at import
settings, switches the correction on as import does. Edited photos and
photos with a default-preset look keep their settings.
A Custom white balance from a Lightroom catalog or XMP sidecar was taken as absolute Kelvin for every
raw. Raws LightCraft develops relative to their as-shot look (ARW, NEF, RW2, RAF, CR3, CR2, PEF; 6500 K
means as shot) then read a small tweak as a large blue shift: ΔE 22 against Lightroom's export on the
photos of issue #510.
The crs mapper now takes a `Target` (preset, rendered, raw with a measured illuminant, relative-WB raw)
instead of a bool. For relative-WB raws, and rendered files with only `Temperature`, a Kelvin value is
converted to the same mired shift from the packet's `AsShotTemperature` / `AsShotTint`. Without that
reference (Lightroom catalogs keep none for Custom photos) the photo stays As Shot and the import
report lists `Temperature, Tint` as not carried over. Import, sidecar read and catalog import decide
the target the same way through `lightcraft_catalog::relative_wb_format`.
Tests: shift from the issue's numbers (3650 → 3578 K lands at ~6275 K), the As Shot fallback with its
report entry, target selection per file, and the catalog mapper's Custom-WB case.
proxies() fitted the decoded camera JPEG into the sensor proxy's box,
which keeps the JPEG's own aspect and can round a pixel short: an
ILCE-7RM4's 1616x1080 preview, decoded at 384x257, fits a 192x128
profile proxy as 191x128, and collect_pairs rejected the mismatch. So
`lightcraft-cli calibrate` skipped every file from this camera and no
profile could be written.
Resize the JPEG to exactly the sensor proxy's size instead; proxies()
has already rejected references whose aspect differs by more than 2%.
Where fitting already landed on the proxy's size the result is
identical (fit is resize at a rounded size), so working renders don't
change: 34 real ILCE-7RM4 ARWs render bit-identically. On 923 files
from one trip calibrate goes from 0 to 843 accepted.
Refs #515
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A batch ran its photos strictly one after another: each render is
row-parallel inside, but a photo's decode, its PNG/TIFF/WebP encode and
the wait on a GPU render are mostly serial, so a 32-core machine averaged
~9.5 busy cores on the CPU path and 1-2 with the GPU.
run_batch now keeps export_parallelism() photos in flight (cores / 4,
1..=4; one on wasm32) on scoped lane threads plus the calling thread.
Each holds an estimate of its working memory from a new export_gate
(twice the memory budget, separate from the decode gate it nests over),
so full-size 24 MP exports (~1.2 GB each) run two at a time by default.
Lanes run at most `lanes` photos past the last one placed, which also
bounds the finished files waiting for their turn. Paths, conflict
handling, original protection, writes, results and progress stay in
batch order (Placer), so the files are the same as one at a time; a
panic in a lane becomes that photo's error.
render_bench's batch scenario now goes through export_batch. 8 x 24 MP
JPEGs, 32 cores + RTX 4080 Laptop: CPU 3.87 s -> 2.35 s, GPU 4.21 s ->
1.82 s.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
With a CropAngle, Lightroom's CropLeft/CropTop and CropRight/CropBottom
are the top-left and bottom-right corners of the tilted crop on the
stored image, and its angle turns the other way to LightCraft's. The
import treated the edges as an axis-aligned rectangle in the rotated
frame and kept the angle's sign, so straightened crops came out the
wrong shape and size.
Carry the two corners through the orientation and then LightCraft's
straighten rotation (angle = -CropAngle, mirrored orientations flip it
back), using the stored image's aspect from fileWidth/fileHeight. On a
real catalog every one of 223 straightened crops now matches the frame
of Lightroom's export, and the content difference falls to that of
unstraightened crops.
Refs #483
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>