mirror of
https://github.com/storytold/lightcraft.git
synced 2026-11-12 17:00:37 -06:00
bb0f0d5e113aeda52fc7d3bb421908718b742a92
18
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
61005f3dc1 |
Culling: the loupe uses built previews; photos prepared ahead and in the background (#3077)
* PERF-CULL-0: ui.inspect reports how the loupe showed each photo it was stepped to
perf.loupeSteps records, per step, the ms from the step to the first frame
with anything of the photo, with a full-size picture (camera JPEG, cached
or rendered) and with LightCraft's own render, measured on the frames the
app draws, so culling benchmarks exclude the control channel's round trip.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
(cherry picked from commit 95f0343848fc0a238f80f07ceb292eaf6779aeaa)
* PERF-CULL-1: browsing a folder shows its first photos within a second
The Local view read and hashed every file of the folder before showing any
of them (2,933 EOS R5 C CR3s: 30.6 s to the first photo). It now hands the
files the library lacks straight to the import, ordered the way the view
sorts them (file time for the date sorts), which adds them batch by batch:
first photo 0.9 s, twelve thumbnails 1.4 s, the whole folder in the same
~34 s as before, in the background.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
(cherry picked from commit b766558e7ef4f802919a144da53e444f70e0186b)
* PERF-CULL-2: the loupe makes the next photos' camera previews ahead of the step
Culling steps through photos, and each step waited for the next raw's
embedded JPEG to be read and decoded (p50 187 ms, p95 332 ms on EOS R5 C
CR3s) and then for a full raw develop. Now:
- the loupe's stand-ins for the next five photos in the direction of
travel and the one behind are made ahead (Slot::QuickAhead, priority
just below the loupe's own render) and taken over at the step, so a
step shows the camera JPEG at once (p50 0 ms);
- a slot keeps its photo while the photo stays wanted, so photos that
shift places as the user steps are not made twice;
- decoded camera JPEGs stay in the memory LRU (never on disk), so going
back shows them again without a decode;
- while an arrow key is held (a photo left within 0.25 s), the raw
develop waits until the photo has stayed 0.12 s, and a render queued
for a photo already left is dropped, so the workers make the previews
instead of renders nobody sees.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
(cherry picked from commit 0430b95636d2b6adce8887af7b9464687c95549e)
* PERF-CULL-3: CR3 previews take the smallest embedded JPEG that covers the view; plain sRGB JPEGs decode straight to 8-bit
Upstream's embedded_preview_covering (
|
||
|
|
fc97c72864 |
LR-BEHAV-RENDER-FIDELITY: calibrated Contrast and Blacks in process V3 (#523) (#1383)
* LR-BEHAV-RENDER-FIDELITY: split V3 Contrast and Blacks Port calibrated global Contrast and Blacks with shared CPU and GPU tables. Preserve V2 Whites, local adjustments, other source paths, and all V1 and V2 renders and persistent cache keys. Generate the constants from public Contrast and Blacks fits. Cover numeric references, legacy renders, keys that include the resolved process, windows and GPU parity. Document the measurements, limits and provisional number. Co-Authored-By: Codex <noreply@openai.com> * LR-BEHAV-RENDER-FIDELITY: reduce V3 preview cost and sanitize fits Compose calibrated CPU preview operators over linear input and skip unused mix endpoints. Keep high-bit-depth exports on the original operator path. Require three distinct photos per published error group and band, regenerate the fit hashes, and cover the sanitizer with synthetic tests. Document release measurements and identify the separate measurement tool proposed in #1181. Co-Authored-By: Codex <noreply@openai.com> * LR-BEHAV-RENDER-FIDELITY: preserve V3 luminance evaluation Retain the original operator evaluation for luminance mixes, including preceding Contrast, so preview interpolation cannot change the cutoff. Keep the fused per-channel path and exact deep exports. Add CPU and GPU regressions for both uniform cutoff fixtures. Document the measured CPU cost of positive Blacks and the retained fused path. Co-Authored-By: Codex <noreply@openai.com> * LR-BEHAV-RENDER-FIDELITY: finish split-module integration Import the persistent process-key helper directly in the session module. Keep the existing V3 operator selection and evaluation order in an inline helper so the combined finish-stage closure stays within the complexity limit. Refresh the V1/V2 golden hashes only after the exact fixture produces identical hashes on plain current main. Keep the cross-platform legacy samples unchanged. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * LR-BEHAV-RENDER-FIDELITY: date the calibrated Light gap summary Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Codex <noreply@openai.com> Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> Co-authored-by: FredApps <153173854+FredApps@users.noreply.github.com> |
||
|
|
0852c21a9d |
Merge remote-tracking branch 'origin/main' into pr-3144
# Conflicts: # crates/engine/src/media/source.rs # docs/gaps.md # docs/parity-checklist.md # docs/raw-parity.md # docs/whats-new.md |
||
|
|
06c1b9ae07 |
Use crw craftlib (#3503)
* CRW-LIB: decode Canon CRW through the shared craft-crw crate LightCraft decodes Canon CRW (the CIFF raws of the EOS D30, D60, 10D, 300D and the PowerShot G1/G6) through craft-libs' `craft-crw`, the clean-room decoder derived and verified on the `clean-room-crw-format-decode` branch and moved there with its frozen tables and derivation record. `crates/raw/src/vendor/crw.rs` is now the adapter: it maps the crate's header and samples onto `RawImage` (CFA, levels, active area, orientation, as-shot white balance, the make/model/time records a CRW keeps instead of Exif) and converts the capture time through chrono. The dependency is a local path (`../craft-libs/crates/crw`) until the craft-libs branch merges; then it becomes a git revision. Everything LightCraft-side from that branch comes along unchanged: `probe_bytes` fills metadata gaps from the decoder's records so CRW imports name their camera; the EOS D60 bundled camera profile and its chart-measured dual-illuminant matrices (`chart_table.rs`, `spectral::Camera` carries its illuminant pair) give the D60 a Kelvin white-balance scale; CRW joins the JPEG-fitted starting look, the catalog's raw formats and `lightcraft-cli calibrate`; `RENDER_CACHE_VERSION` 71; the CC0 CRW corpus entries for `cargo xtask corpus`; coverage, parity, gaps, raw-parity, target-app-parity and ROADMAP updates. The G5 stays preview-only (the crate refuses it with the reason) and the robustness test keeps its CRW sample. What does not come along: the decoder, its tables and `crw_oracle.rs` (now craft-crw's digest-pinned sample test) and the derivation record, which lives in craft-libs `crates/crw/clean-room/`. `clean-room/crw/` here keeps only the EOS D60 chart-calibration document and its capture list, with a README pointing at the record. Gates: fmt, clippy -D warnings (raw all targets; engine, catalog, cli), lightcraft-raw tests (475 + 134, the corpus files decoding through the adapter), catalog tests, engine lib tests (1,300 pass; the two failures, `tests_import::recursive_discovery_…` and `originals::tests::write_targets_pin_absent_paths_…`, are in files unchanged from main), `cargo xtask parity`, `assets`, `layers`, `coverage.py --check`. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * no message * no message * Update documentation after moving code to craft-libs * Review coment fixes * CRW: pin craft-crw f3b62ec, a truncated entropy stream is Corrupt craft-libs #472 makes craft_crw::decode refuse a stream that ends more than Limits::max_stream_end_fill (64 samples, one block) before the frame does, instead of filling the rest with predictor values and returning success. The adapter already maps craft_crw::Error::Corrupt to RawError::Corrupt; a regression test proves a truncated CRW now fails at this boundary (it decoded as a flat frame on the old pin) while its header still reads. Only craft-crw moves; craft-ui/docpath/update/window stay on c5a1cbf. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Code review comment fix * Add some tests --------- Co-authored-by: Robert Woldberg <314159+rwoldberg@users.noreply.github.com> Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com> |
||
|
|
453a0f2a3b |
LR-EDIT-OPTICS-PROFILE: address review on the ILCE-7M3 distortion PR
- docs/parity-checklist.md: the row's closing summary also lists the ILCE-7M3. - docs/sony-lens-corrections.md, parity-checklist.md: state the validation set as the 26 tabled shots plus the two left out, instead of a total that did not add up. - docs/whats-new.md: the sub-pixel claim is the median; corners reach about 1.5 px. - tools/sony_distortion_check.py: re-render when the CLI or ARW is newer than the output, report the CLI's stderr on failure, keep renders of same-named files from different folders apart, and docstrings for every function. |
||
|
|
03eda48402 |
LR-EDIT-OPTICS-PROFILE: keep the distortion check with the other Python tools
Moves it to tools/ with a pinned requirements file, as tools/spectral_camera_table.py does. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015gRtdWF1E9358FfzhzYqCS |
||
|
|
23f54803e6 |
fix(tools): keep install-sam3.sh POSIX sh (no arrays for curl auth options) (#2760)
The script runs under #!/bin/sh; on dash the array assignment was a syntax error. Pass the auth option and its value as two plain words. Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
4ae3089420 |
security: bounded keyword, repair and journal-recovery work (CodeRabbit) (#2763)
* security: harden dev-tool scripts and the testkit skip-log test (CodeRabbit) - tools/install-sam3.sh: the HF bearer token went through curl's argv, where `ps` exposes it to other local accounts (CWE-214); it now travels through a private 0600 temp config file removed on exit. - packaging/macos/import-cert.sh: the decoded .p12 was written to a predictable path in a shared TMPDIR, where a precreated attacker-readable file would receive it (CWE-377); both cert and keychain are created inside a mktemp -d 0700 directory now. - docs/showcase/run.py: the reply reader copied whole responses with no size cap (obvious quadratic-copy and unbounded-memory DoS). It accumulates in a bytearray now and closes the connection past 128 MiB. - tools/spectral_camera_table.py: alias/make/model strings and comments from the rawtoaces-data checkout moved directly into Rust source; one newline in a comment became Rust code. External text is now escaped as Rust literal content and comments are normalized to one printable line. - crates/testkit: appended_lines_accumulate reused a pip-predictable temp dir by creating it with create_dir_all and carried on after failing to remove its log path, so the append would follow an attacker-planted symlink (CWE-59); it now fails the test if the directory exists instead of testing through it, and removes any existing log path only after clearly checking it, always failing the test instead of proceeding when removal fails. Findings #10080/#10078 PR (rows 2, 6, 8, 9, 15 of the storytold-lightcraft-agent-findings report). 🤖 Generated with Codebuff Co-Authored-By: Codebuff <noreply@codebuff.com> * security: bounded catalog keyword, repair, journal recovery work (CodeRabbit) - keywords: from_file searched every hierarchical level for each flat keyword and `same` lower-cased two strings per comparison — one lowered level set instead (CWE-407, row 97). - keywords: parse_keyword_list scanned each keyword's growing synonym list per line — a lowered-synonym set per keyword instead (CWE-407, row 100). - keywords: keyword_tree stored every level's prefix twice, so a photo XMP keyword nested deeper than a list file accepts made stored bytes quadratic in the level count; the tree caps at MAX_LEVELS, and keyword_list_text names whatever lies deeper from the photos' and list's own spellings, so the file still reads back (CWE-400, row 103). - catalog: repair validated a folder chain once per album — memoized by chain now, so a valid chain in a flat snapshot costs one walk overall (CWE-407, row 101). - store: journal append and truncate follow a planted catalog.log symlink; both refuse any non-regular file at the name before writing through it (CWE-59, row 107), like the staging writes since #1502. - journal: resync re-validated the whole remaining suffix UTF-8 for every candidate record start; a whole line is validated once (covering every candidate in it), and an invalid line's re-checks stop at a 64 MiB budget, with the tail then staying a torn one the damaged path keeps and reports (CWE-407, row 109). New tests: from_file case-insensitive level match, synonyms kept once, a 4096-level keyword cut at MAX_LEVELS, a link at catalog.log refused, and resync over a fragment of 1024 record starts. Findings rows 97, 100, 101, 103, 107, 109 of the storytold-lightcraft-agent-findings report. 🤖 Generated with Codebuff Co-Authored-By: Codebuff <noreply@codebuff.com> --------- Co-authored-by: Codebuff <noreply@codebuff.com> |
||
|
|
f3bb7a9f30 |
CI: one test binary per crate, line tables only, one WASM check (#2710)
* CI: dev profile keeps line tables for workspace crates and no debug info for dependencies * Control server tests: persistent_requests_and_slow_replies_outlive_the_idle_read_timeout no longer races the idle timeout Each request (and the closing half-shutdown) reaches the socket before the worker reads, so on a slow machine only the reply waits outlast the 100 ms idle timeout, as the test intends. The idle test sends its input the same way, and the no-frames test allows 5 s instead of 2 s to give up. * CI: one integration-test binary per crate Each crate's tests/*.rs become modules of tests/it/main.rs (51 test files into 13 binaries, plus 6 that stay their own). Inner #![cfg] attributes move to the mod lines. Files that change state for the whole process keep a binary of their own: codecs jxl_tail_heap and raw sr2_heap (dhat global allocator), gpu shutdown, shutdown_first and memory, ui-egui shutdown. Docs, comments and the GPU probe name the new paths and targets (--test it <module>::); the CI report names an it binary's skips after the crate. * CI: tests under nextest, doctests apart, one wasm check xtask ci runs the tests with cargo nextest run --lib --bins --tests when cargo-nextest is installed, else cargo test with the same targets and a one-line notice; doctests run after them with cargo test --workspace --doc. The heif and hardware GPU steps use the same runner. Examples stay out of the test build (clippy --all-targets still checks them). Concurrent tests under nextest follow the RUST_TEST_THREADS cap (NEXTEST_TEST_THREADS). The wasm check is one cargo check --keep-going over the same crates, and clippy runs with --keep-going. * CI: tests run under cargo test by default, nextest with LIGHTCRAFT_NEXTEST=1 (per-process scene cache) |
||
|
|
d5e639b3d1 |
packaging: bash tests, named positional parameters and stderr errors (SonarCloud S7688/S7679/S7682/S7677) (#2655)
Bash scripts use [[ ]] for tests, functions name their positional parameters and end with an explicit return, and the app-specific packaging check writes its ::error:: annotations to stderr. No behaviour change. |
||
|
|
beb044bf35 |
Merge pull request #2494 from storytold/sonar-fix/path-cmd-hardening
security: validate CLI paths/args in dev tools (SonarCloud S2083/S8707/S8705) |
||
|
|
ac2d7623e3 | security: tighten readable/regular file checks in CLI tools | ||
|
|
d79b1f33e3 |
security: enforce HTTPS on curl downloads (SonarCloud S6506) (#2493)
Add --proto '=https' to curl invocations so redirects cannot downgrade to plaintext HTTP. Resolves SonarCloud S6506 (shell/docker/githubactions). |
||
|
|
68d5118a9d |
security: validate CLI paths/args in dev tools (SonarCloud S2083/S8707/S8705/S7044)
Resolve and validate command-line path arguments before use, enforce HTTPS at download sinks, and confirm path containment. Hardens local codegen/fetch tools against path-traversal and command-argument-injection findings. |
||
|
|
90a248edad |
M12: Describe selections from SAM 3's semantic mask: sure inside, no lines between instances
Describe merged the detector's per-instance masks. Those stay near 0 even well inside what they found: a sky came out about 87 % selected, a volleyball net about 70 %, a person about 94 %. Two instances that touch both fade out where they meet, so a line showed between people standing side by side. - The detector's semantic head is now ported: semantic_projection over the pixel decoder, the transformers implementation's semantic_seg. It matches the reference to about 1e-3, like the instance masks. tools/sam3_reference.py writes it and the reference test checks it; older reference files still pass. - Each instance found (score above the threshold, as before) takes the semantic logits where it is the strongest instance and near (above -4). The semantic mask is sure inside and runs on across the boundary between two instances. Far from every instance nothing changes, so Describe selects nothing it didn't find before. On 16 prompts over three photos (a mountain landscape, two gym photos), the median coverage inside each selection rises, e.g.: - sky 0.87 -> 1.00 - mountains 0.81 -> 0.99 - net 0.71 -> 0.99 - floor 0.92 -> 1.00 - person 0.94 -> 0.99 Each selection gains 0 to 1.9 % of the photo at its soft rim and loses none. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
82ff68d4f3 |
M11.4: raws from 52 camera models start from matrices fitted to measured spectral sensitivities
A raw without colour matrices of its own now gets DNG ColorMatrix1/2 and ForwardMatrix1/2 for its model when the model is one of the 52 cameras in rawtoaces-data (Academy Software Foundation, Apache-2.0): 22 Canon, 11 Nikon, 11 Sony, 3 Fujifilm and 5 others, plus the other names its alias list gives (Rebel T3i and Kiss X5 for the EOS 600D; bodies it lists as sharing a sensor). tools/spectral_camera_table.py fits, under Standard Illuminant A and D65, a white-preserving 3x3 to the measured spectral sensitivities over rawtoaces-data's 190 training reflectances and writes crates/raw/src/spectral_table.rs. No number comes from Adobe or from a raw decoder. lightcraft_raw::spectral looks a camera up by Exif make and model; the DNG colour model does the rest. camera_preview::starting_colour takes the first available of: the file's own matrices, a camera profile for the model (fitted to the JPEG for tone), the spectral matrices, the file-local JPEG fit, the neutral fallback. A profiled model whose profile fit fails takes its spectral matrices. White balance stays relative to the as-shot look for every raw without matrices of its own, so Photo::relative_wb and the controls are unchanged; calibrate still pools covered models. Tests: one per precedence step (synthetic), the table's lookup, aliases and DNG shape, and a corpus test of the order on ILCE-7M4 (profile), EOS 5D Mark III and D5100 (spectral), EOS 7D (own fit) and a DNG. The ILCE-7RM2 and GFX100S corpus tests now expect the spectral matrices. Attribution in assets/ATTRIBUTION.md and NOTICE. RENDER_CACHE_VERSION 23. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
d379e780cc |
M12: AI masks never require SAM 3 — background worker, consented download, review fixes
Engine (crates/engine/src/segment/):
- One worker thread owns the model and the encoded photo; the session
sends jobs (with the render job for the model input) and gets results
over channels, never locking what the model holds. In the app
(Segmenter::background) clicks, descriptions, prepares and detail
passes return at once and Session::segment_poll applies results (one
undo step per settled selection, pending clicks shown on the photo);
the CLI/MCP wait for them. Newer requests for the same selection
supersede queued ones.
- Every job runs under catch_unwind: a candle panic becomes an error and
the model is reloaded next time; busy counters are reset by drop
guards; a dead worker is restarted. The model unloads after 10 min idle.
- segment.model.status / .download {acknowledged: true} / .cancel: the
download runs on its own thread; status via atomics (try_lock only).
Without the model every AI request fails at once with "The SAM 3 model
is not installed…"; renders/exports never need it; `seg` params make
AI masks from stored segmentations without it.
- Hostile documents: at most 4 detail patches per component (capped on
deserialize and in the renderer), at most 64 Object clicks.
UI: Object / Describe without the model open "Download the SAM 3
Model?" (size, folder, SAM License link; Not Now / Download, progress,
cancel, Try Again with the reason, Continue once installed); progress in
the Masking panel when the dialog is closed. Reverted the PR's unrelated
default change (mask overlay off + hover-red) to main's behaviour.
tools/install-sam3.*: --fail so error pages never land in files, 416
restarts instead of looping, the weights are verified before they are
moved into place (a bad file is deleted, never resumed from).
Docs: docs/ai-masks.md (in-app download, mirrors, SAM License summary,
no weights in the repo), NOTICE, parity LR-MASK-OBJECTS, What's New,
ROADMAP.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
||
|
|
6468bfc06b |
M12: SAM 3 Object and Describe masks (pure Rust, Metal)
AI masks from Meta's SAM 3, run in LightCraft itself: a new
`lightcraft-segment` crate ports the Hugging Face transformers
implementation (Apache-2.0) to candle, on the GPU through Metal on macOS
(CPU elsewhere), with no C/C++ and no `unsafe`.
- Object: click to include, ⌥-click to leave a part out (SAM 3's
interactive prompt encoder + two-way mask decoder).
- Describe: a text prompt selects every instance ("sky", "the red car";
"car, road" merges several) via the CLIP text encoder and DETR detector.
- Both as new masks and as Add / Subtract / Intersect components; + / −
buttons under the selected mask combine selections.
- The segmentation is stored with the mask (288² quantized logits, ~10-30
KB), so renders, exports and the web build never need the model.
- A zoomed-in detail pass (background, ~5 s on an M4 Pro) re-segments the
area around a selection from a sharper source: 5-10x finer edges on
small objects.
- Per-selection Edge slider (hard ↔ soft); hovering a mask in the list
shows it in red (the always-on overlay is now off by default, O toggles).
- The engine feature `sam` (on in the desktop app) gates the model; the
image is encoded once per photo in the background, clicks take ~35 ms.
- Weights are not bundled (SAM License): tools/install-sam3.sh / .ps1
download and verify facebook/sam3; docs/ai-masks.md.
- Clearer errors (missing file, missing model) and a stderr logger in the
app (LIGHTCRAFT_LOG=info).
candle is pinned at 0.9.2: later releases make candle-core depend on
tokenizers with the Oniguruma C library. Every stage is checked against
the Python reference (tests/reference.rs, tools/sam3_reference.py): on
Metal the backbone matches to 7e-5 relative error, masks to ~5e-5.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01C8BkS7JDqa1EMcjW6spDGA
|