Skip to content

Releases: chipi/chemigram

v1.11.0 — Camera-portable vocabulary + visual-proof trust closure

Choose a tag to compare

@chipi chipi released this 24 May 05:53

[1.11.0] — 2026-05-23

Vocabulary maturation, honest visual proofs, camera-aware apply.

v1.11.0's headline is RFC-039: raw-derived parameters in L2
composition. The vocabulary now ports across photographers AND across
camera bodies — an L2 look that says "warm by +1500K" produces three
different op_params on Sony, Canon, and Nikon raws, because the
substitution reads each body's EXIF WB at apply time. Same pattern
extends to denoise (per-ISO threshold scaling), filmic (per-histogram
tone points), and lens correction (per-body lensfun identifier).
Vocabulary entries are reauthored to compose parametric primitives
instead of inlining camera-specific bytes.

The visual-proofs gallery's trust gap (rendered the wrong fixture for
21 entries pre-v1.11.0) was closed in three steps: a chart-verifiable
discriminator (#129), real-raw landscape + portrait fixtures via Git
LFS (#130), and content-aware fixture routing for sky / skin / food /
foliage / landscape / water entries (#138). A formal audit script
(#141) wired to CI now catches direction-of-effect regressions in the
gallery before they reach a release.

Stats

  • 114 vocabulary entries (3 starter + 111 expressive-baseline)
  • 2200+ tests; lab-grade direction-of-change for 88 entries
  • Cross-camera fixtures: Sony DSC-RX10M4 + Sony ZV-E10 + Canon EOS R6
    • Nikon D70 (4 manufacturers, 3 raw formats: ARW / CR3 / NEF)
  • Visual-proof audit: 114 ok / 0 warn / 0 fail

RFC-039 / ADR-091/092/093 — Raw-derived parameters in L2 composition

The architectural shift: a vocabulary entry's claim is portability
across photographers AND across raws. Before v1.11.0, WB-touching
entries carried hardcoded raw-domain coefficients from their authoring
camera — they LOOKED portable but produced foreign-WB casts on any
other body. RFC-039 makes the vocabulary architecturally honest by
computing the WB coefficients per-raw at apply time.

  • ADR-091 — Engine raw-metadata-aware apply API. apply_entry()
    gains a raw_path parameter; parametric decoders that need raw
    metadata declare it; a lazy rawpy reader caches the EXIF WB +
    daylight matrix per workspace. Fallback semantics for non-raw
    fixtures: identity-skip (the temperature op drops from the
    rendered output entirely, so darktable applies its own camera
    default).
  • ADR-092 — Manifest composes field for L2 composition by
    reference. L2 looks gain an optional composes: [{primitive, params}]
    field; the synthesizer resolves references at apply time, splicing
    the parametric primitive's output into the L2 dtstyle's history at
    the correct iop_order. Depth-1 only (L2 composes L3); cross-pack
    resolution works.
  • ADR-093 — Camera-aware parametric temperature semantics.
    Identity preserves the camera's EXIF WB (not 1.0/1.0/1.0); deltas
    apply in coefficient space (red ∝ 1+0.0001·kelvin_delta, blue
    inverse). Empirically verified against three manufacturers
    (#142).

Same RFC-039 engine machinery extends to:

  • denoise (#134) — log2 ISO scaling above reference (ISO 200 →
    1×, ISO 400 → 2×, ISO 800 → 4×). New ADR-094.
  • filmic (#135) — black/white points auto-tuned per raw
    histogram percentiles. New ADR-095.
  • lens correction (#136) — EXIF camera + lens identifiers
    populate the lensfun fields. New ADR-096.

Visual-proof gallery — three trust-gap closures

  • #129 — Chart-verifiable discriminator. Every entry routes to
    one of four modes (chart / real_raw / not_yet_portable /
    manual) by the modules it touches. 38 entries route to real-raw
    fixtures; the rest stay on the synthetic colorchecker / grayscale
    ramp where those are honest fixtures.
  • #130 — Real-raw fixtures via Git LFS. Sony DSC-RX10M4
    landscape ARW + Sony ZV-E10 portrait ARW, both CC BY-SA 4.0 from
    discuss.pixls.us. Per-entry routing landscape / portrait based on
    touches + content.
  • #138 — Content-aware fixture routing. requires_content
    manifest tag (sky / water / foliage / landscape / food / skin)
    picks the matching fixture for entries whose effect needs scene
    content the chart doesn't have.
  • #139 — Clipped-gradient routing for blacks_lifted /
    blacks_crushed. The sigmoid target_black effect is below noise
    on the colorchecker but visible on the synthetic clipped fixture.

#141 — Formalized visual-proof audit

scripts/audit_visual_proofs.py (wired to CI on every push) compares
each rendered after-image to its baseline via pixel-stat signatures
(mean R/G/B, luma, std-luma, mean chroma, clip counts) and emits a
per-entry verdict by subtype-specific direction-of-effect heuristics.
Current state: 114 ok / 0 warn / 0 fail across the 114-entry
vocabulary. 17 smoke tests verify the heuristics themselves can't
silently break. The audit knows about parametric identity-default
entries, requires_content-tagged content-aware masks, and chroma-
shift entries (different metric from R/B mean).

#142 — Cross-camera RFC-039 verification

Two new CC0 fixtures via Git LFS (Canon EOS R6 CR3, Nikon D70 NEF;
~10 MB total, from raw.pixls.us). 7-test integration suite at
tests/integration/core/test_cross_camera_wb.py asserts:

  • Per-body daylight WB probe returns plausible values (Sony 2.39/1.0/
    1.71, Canon 1.81/1.0/1.63, Nikon 2.17/1.0/1.52)
  • +1500K shift anchors on each camera's WB (R ≈ camera_R × 1.15;
    B ≈ camera_B × 0.85)
  • Same logical operation produces 3 distinct op_params across 3
    bodies — the load-bearing portability assertion

#143 — Strengthened parametric assertions

Three _check_render_completes entries upgraded to direction-of-
change:

  • grain_strength — new _check_pixel_std_increase LabCheck;
    PatchSample.std_luma_8bit field reads pixel-level luma std.
    grain_strength=50 lifts std by +1.57; grain_strength=8 by +0.12.
  • highlights_clip_threshold — parametrized
    test_clipped_fixture_validation.py to cover clip_0.85 (≥5%
    clipped reduction) AND clip_0.95 (≥1%).
  • toneequalizer — stays at render-completes after empirical
    finding that band centers don't align with linear grayscale ramp
    indices; direction-of-bytes is exhaustively covered by
    test_toneequal.py. Documented in the entry's inline comment.

Side-cleanup: wb_warm_subtle moved to SKIP_REASONS — the
RFC-039 composed entry can't substitute on a non-raw chart;
direction asserted by #142's cross-body suite and the byte-level
kelvin-delta test.

#144 — Inline CI-assertion annotations in the gallery

Every gallery entry now surfaces what CI mechanically verifies for
it alongside the photographic-intent description. Four cases:

  • EXPECTED_EFFECTS direct coverage → check_name(threshold) +
    fixture name
  • PARAMETERIZED_EFFECTS → one bullet per (label, params)
  • SKIP_REASONS → the documented skip reason inline
  • L2 composite (auto-skipped by structural rule) → "verified by
    visual review" inline

53 entries get direct assertions; 14 get skip reasons; 47 get the
L2-composite line. Reader never wonders.

#145 — Cross-pack composition stress test

Three synthetic test packs at tests/fixtures/vocabulary/ exercise
N=3-pack scenarios that the v1.10.0 [starter, expressive-baseline]
default didn't cover:

  • Cross-pack composes reference (a 3rd pack composes starter's
    primitive)
  • Cross-pack name collision (ManifestError naming both source
    packs)
  • Modversion drift warning fires per emit_drift_signals
  • 3-pack-together load (starter + compose + drift) in one
    VocabularyIndex construction

RFC-028 (deferred to v1.12.0) will be the architectural answer to
N-pack semantics; #145 is the engineering safety net under the
v1.11.0 loader.

Other engineering changes

  • #100 — Darkroom-session umbrella opened with structured
    checklist (Sub-tasks A-D: tone-curve baselines #94, EXIF probes,
    curves #96, colorzones #98). Estimated ~3-4 hours.
  • #119 — Hypothesis property-based fuzz tests for 18
    parameterize decoders (round-trip, idempotence, size-invariance).
  • #129#140 — Visual-proofs trust-gap closures, content-aware
    routing, AGENTS.md inversion (now canonical; CLAUDE.md +
    .cursorrules are thin pointers).
  • Post-push CI verification — codified in AGENTS.md as a
    workflow rule (never walk away from a red CI).

Carryover from v1.10.0 release

The ADR-088/089/090 trio shipped Draft pending darkroom validation
in v1.10.0; they remain Draft. The new ADR-091/092/093 (RFC-039)
and ADR-094/095/096 (RFC-039 follow-ups) ship the same way.

What's next (v1.12.0)

  • RFC-027 — Multi-photographer review phase plan
  • RFC-028 — Pack management / multi-pack composition / vendor packs
  • Possibly: the deferred darkroom-session work (tonecurve mv5
    decoder #94, colorzones spline fallback #98, EXIF baselines)
    promoted from #100 — depending on whether you sit down with the
    GUI.

v1.10.0 — Photographer-survey vocabulary + workflow primitives

Choose a tag to compare

@chipi chipi released this 10 May 16:18

Photographer-survey vocabulary + workflow primitives + 4 stabilization rounds + new docs.

A 6-genre photographer-workflow survey (36 photographers) drove a vocabulary expansion across portrait / landscape / wedding / B&W / nature-wildlife / food-product. The vocabulary delta + three workflow primitives implement the gaps that survey identified. Post-tag, 4 rounds of audit work stabilized CLI/MCP/docs alignment, and a docs prominence pass added a cookbook (~60 recipes), an onboarding guide, and four Mermaid architecture diagrams.

Three doors in

If you want to... Start here Time
Run chemigram on a real photo docs/getting-started.md 30–60 min
Know "I want X look — how?" docs/guides/cookbook.md as needed
Understand chemigram deeply docs/onboarding.md 2.5–3 hours

Visual companion: docs/diagrams/ — four Mermaid one-pagers (stack / mask trilogy / vocabulary layers / phase timeline).

Vocabulary

  • 29 new L2 looks across 6 genres + bw_convert v2 (colorequal-based 8-axis Adams-school B&W primitive).
  • Parametric L2 strength (RFC-035 / ADR-088 Draft) — --strength 0.6 interpolates each parameterized field from identity to authored.
  • Mixed-op apply_per_region (RFC-036 / ADR-089 Draft) — composite moves like eye-detail land as one snapshot.
  • propagate_state MCP+CLI (RFC-037 / ADR-090 Draft) — Lightroom-Sync analog with framing-bound auto-exclusion.
  • apply-spot CLI verb — closes the ADR-069 verb-for-verb gap from v1.9.0.

Stats

  • Tests: 1868 unit+integration + ~190 e2e (2058 total)
  • ADRs: 90 (ADR-088/089/090 all Draft pending darkroom validation)
  • RFCs Decided: 30 + RFC-038 Mode B drafted (v1.11+ pick)
  • Vocabulary: 114 entries (2 starter + 112 expressive-baseline) + 9 named maskdefs
  • 40 parameterized entries across 18 modules; 19 total modules touched
  • MCP: 27 tools / CLI: 28 verbs / 23 mirrored / 0 unmirrored gaps

Stabilization (4 audit rounds)

Round 1 — holistic drift audit. 6 drifts closed including the critical pyproject.toml claiming 1.9.0 after v1.10.0 was tagged, generator allowlist hard-coded at v1.3.0 (silently lost 18 verbs across two releases).

Round 2 — CLI ↔ MCP ↔ docs alignment. 3 new audit tests + 7 TA.md MCP signature fixes + the canonical CLI ↔ MCP name mapping locked.

Round 3 — coverage audit. 4 coverage gaps closed: visual-proof generator silently dropping 10 named-mask entries (rescued); vocab show-mask / list-masks CLI tests added; apply_spot MCP wrapper unit tests added.

Round 4 — visual-proof coverage lock. New test prevents the silent-drop bug class from recurring.

Docs prominence

  • guides/cookbook.md — ~60 intent-driven recipes by genre
  • onboarding.md — 2.5–3h reading order for new contributors
  • diagrams/ — 4 Mermaid one-pagers
  • "Three doors in" table on README + docs/index landing pages
  • doc-tree.md adds guides/ + diagrams/ tiers; mkdocs nav surfaces onboarding + diagrams

What's next

ADR-088/089/090 ship as Draft pending darkroom-session validation against real raws (docs/guides/darkroom-session-debt.md items 7-11). Phase 2 vocabulary maturation continues; open issues live on v1.11.0 milestone. RFC-038 Mode B is the v1.11+ pick.

See CHANGELOG.md. Built on the shoulders of darktable.

v1.9.0 — Mask + retouch architecture trilogy

Choose a tag to compare

@chipi chipi released this 08 May 21:42

Headline

The mask + retouch architecture is structurally complete. Photographers can now compose spatial masks (drawn shapes), parametric range filters (luminance + HSL color), LLM-vision-derived content masks (chat-client native), and spot heal/clone retouch — all through one unified mask_spec wire and one new MCP tool. Lightroom's range-mask + retouch + AI-subject workflows all map cleanly into the chemigram surface.

Stats

  • Tests: 1451 → 1811 passing (+360)
  • ADRs: 83 → 87 (ADR-084..087)
  • RFCs Decided: 23 → 27 (RFC-024 / RFC-025 / RFC-026 / RFC-029)
  • RFCs Drafted: +1 (RFC-030, deferred — deployed sibling-provider scaffolding)
  • Vocabulary entries: 78 → 83 (+5 compositional-mask L2 looks)
  • New MCP tool: apply_spot (sister to apply_primitive)
  • New CLI verbs: vocab validate, cache {list, size, clear}

The mask trilogy

Layer RFC / ADR What it does
Spatial — drawn masks (build-by-words) RFC-029 / ADR-084 "Bottom third", "circle on subject" → dt_form + dt_params. Path geometry added (N-vertex polygons).
Pixel-level refinement RFC-024 / ADR-085 "Only the dark pixels", "only blue hues" → range_filter field. Composes with drawn via AND-intersection.
Content-derived (coarse) RFC-026 / ADR-086 The chat-client's vision-capable LLM looks at render_preview and constructs mask_spec. Zero deployment cost.
Spot removal / heal / clone RFC-025 / ADR-087 New MCP tool apply_spot(image_id, kind, x, y, radius, source_x?, source_y?). HEAL + CLONE on circle geometry.
Precision-tier (deferred) RFC-030 (Draft) Deployed sibling-provider scaffolding for SAM-class subjects, depth, AI auto-spot detection.

Highlights

RFC-029 / ADR-084 — Compositional masks at apply time (#113).
The wire was already there (apply_primitive accepts inline mask_spec); v1.9.0 formalizes it as the canonical build-by-words workflow. New docs guide mask-shapes-from-words.md maps spatial English to spec dicts: "bottom third" → gradient with anchor_y=0.67, rotation=180, compression=0.5. 30+ phrase→spec examples and 12 visual proofs against the synthetic grayscale ramp. The deterministic-hash mask_id provides reuse semantics for free.

RFC-024 / ADR-085 — Parametric range-filter masks.
mask_spec gains optional range_filter field: {kind: "luminance"|"color_h"|"color_s"|"color_l", min, max, feather, invert}. Encoded directly into darktable's blendif byte fields. Three valid combinations: drawn only, parametric only, drawn + parametric (intersection — the canonical "dark pixels in this region" workflow). 13 unit tests + 3 e2e tests verifying the wire end-to-end.

RFC-026 / ADR-086 — LLM-vision-as-provider for AI masks.
Reframed from RFC-026 v0.1's deployed-provider-only architecture: the chat-client (Claude.ai / ChatGPT / Claude Code) already has vision in the conversation. When the agent calls render_preview, the LLM sees the JPEG and constructs mask_spec from spatial reasoning. Zero deployment cost; covers ~70% of content-derived masking workflows. New docs guide llm-vision-for-masks.md with 6 workflow patterns (subject region, polygon trace, sky/foreground split, color-range estimation, subject-vs-background routing, iterative refinement). The deployed-provider precision tier moved to RFC-030 (Draft, deferred).

RFC-025 / ADR-087 — Retouch byte encoding + apply_spot MCP tool (#108).
New MCP tool sister to apply_primitive for the structurally different primitive class of pixel replacement. Byte encoders verified against darktable 5.4.1 src/iop/retouch.c: 13260-byte op_params (300-form fixed array of 44 bytes each + 60-byte global tail), 16-byte circle mask form, 8-byte clone mask_src. v1.9.0 scope: HEAL + CLONE on CIRCLE geometry, single form per call. 12 unit tests + 4 e2e tests.

5 compositional-mask L2 looks demonstrating the trilogy:

  • look_subject_lift_dark_only — drawn ellipse + luminance shadows
  • look_sky_blue_deepen — gradient (top half) + color_h cyan-blue
  • look_horizon_warm_glow — horizontal gradient + warm color_h
  • look_subject_brighten_highlights — drawn ellipse + luminance highlights
  • look_dark_pixels_global_lift — pure parametric range_filter

CLI ergonomics polish.

  • chemigram vocab validate <name> — runs 6 consistency checks per entry.
  • chemigram cache {list, size, clear} — preview cache management.

Drift cleanup. Comprehensive doc-alignment pass: CLAUDE.md PNG-mask references retired (ADR-021 historical), RFC-024 cross-references updated to RFC-026 / RFC-030 split, capability-survey § 10 + § 13 reflect closures, apply_with_drawn_mask renamed to apply_with_mask (backcompat alias preserved), new docs guides for spatial English and LLM-vision workflows.

Lightroom parity

51/52 daily-use controls (98%) — unchanged from v1.8.0. The mask trilogy adds workflow surfaces orthogonal to the panel-mapping count. Only #94 manual tone curve remains, blocked on the darktable-session empirical-baseline work under #100.

Backward compatibility

  • apply_with_drawn_mask retained as a module-level alias for apply_with_mask; existing callers continue to work.
  • All v1.8.0 mask-bound vocabulary entries continue to function; the mask_spec schema is purely additive (added range_filter, dt_form: "path").
  • CLI verbs and MCP tools are additive only.

Deferred to v1.10.0+

  • RFC-027 (multi-photographer review phase plan) — not yet drafted.
  • RFC-028 (pack management / vendor packs) — not yet drafted.
  • RFC-030 (deployed sibling-provider scaffolding) — Draft v0.1.
  • Recipe book, onboarding guide, architecture diagrams, fuzz tests for parameterize decoders.
  • #94 manual tone curve, #95 lens EXIF auto-binding, #96 denoise wavelet baseline, #98 colorzones HSL precision — all under the #100 darktable-session umbrella.

🤖 Generated with Claude Code

v1.8.0 — Lightroom daily-use parity (51/52, 98%)

Choose a tag to compare

@chipi chipi released this 08 May 01:06

Headline

Lightroom daily-use parity at 98% across 6 panels (Light / Color / Color Mixer / Color Grading / Effects / Transform). Only manual tone curve (#94) remains, blocked on darktable-session empirical-baseline work tracked under #100.

Stats

  • Tests: 1127 → 1451 passing (+324)
  • Path C decoders: 11 → 18
  • Parameterized vocabulary entries: 18 → 38
  • ADRs: 82 → 83 (ADR-083 closes RFC-023)
  • Visual-proofs: 553 fresh chart renders + documented placeholders for HSL

Lightroom panels covered

Panel Coverage
Light 6/7 (only #94 tone curve deferred)
Color 4/4 (incl. #102 WB Kelvin UX wrapper)
Color Mixer 24/24 (HSL via colorequal — RFC-023 / ADR-083)
Color Grading 7/7 (per-zone H/S/L + blending + balance)
Effects 5/5 (incl. #92 Texture, #90 A.2 Dehaze)
Transform 5/5 NEW (#101 ashift)

Highlights

RFC-023 / ADR-083 — HSL Color Mixer parity (#93).
Backed HSL with the modern colorequal mv4 (128-byte flat-scalar struct) instead of the older colorzones spline-curve module. 3 multi-axis vocabulary entries — hsl_hue, hsl_saturation, hsl_luminance — with 8 per-color axes each (24 axes total). First formal Tier 3 → Tier 2 promotion under ADR-081.

Bucket A.5 — Color Grading panel completion (#91).
9 new colorbalancergb axes: per-zone hue × 3, per-zone saturation × 3, blending × 2 (shadows_weight, highlights_weight), balance × 1 (white_fulcrum).

Tier 3 promotions to Tier 2.

  • Lens correction (lens mv10, #95) — 10 magnitude axes for manual TCA / vignette / per-correction-type strengths. EXIF auto-binding for camera/lens identifiers tracked under #100.
  • Denoise (denoiseprofile mv12, #96) — 4 magnitude axes parameterized; pinned at NLMEANS mode for chart-pipeline compatibility (wavelet-mode baseline capture under #100 task C).
  • Filmic v6 (filmicrgb mv6, #97) — 8 magnitude axes parallel to sigmoid for users wanting explicit log-encoding control.

Bucket A.7 — Transform panel (#101).
darktable's ashift module mv5 (892-byte struct). 5 magnitude axes covering Lightroom's Transform panel sliders.

WB Kelvin UX wrapper (#102).
Photographic-units axes (kelvin_delta, tint_delta) layered on the existing temperature decoder.

Visual proofs verified

The visual-proof sweep ran post-tag against all 38 parameterized + 24 discrete entries. All v1.8.0 entries verified visually correct, with one documented exception: HSL via colorequal needs real-raw input (the synthetic chart pipeline doesn't provide the working-profile chain colorequal expects). The 3 HSL entries get documented placeholder rows in the gallery; their byte-level apply path is verified by the 5-layer test coverage (1451 tests pass).

Deferred

Tracked under #100 darktable-session empirical verification umbrella:

  • #94 manual tone curve — 520-byte spline-curve struct needs darktable-GUI baseline capture
  • #95 lens EXIF auto-binding extension — populate lensfun camera/lens strings from raw EXIF at apply time
  • #96 denoise wavelet-curve baseline verification — confirm or fix the constructed x[6][7]/y[6][7] baseline (NLMEANS mode shipped as fallback)
  • HSL real-raw visual proofs — capture per-zone HSL effect renders against a real raw fixture (v1.9.0 work)
  • #98 colorzones spline-curve HSL precision fallback — discrete-only presets for the 5% HSL workflow needing Lightroom Range slider precision

Full notes

See CHANGELOG.md and docs/capability-survey.md § 10 + § 13 for the complete picture.

v1.7.0 — Phase 4 close + RFC-022 Tier 2 + brilliance + B&W + L2 looks

Choose a tag to compare

@chipi chipi released this 07 May 14:36

Architecture

  • RFC-022 closed → ADR-081 (parameterization tiering policy; explicitly amends ADR-008)
  • RFC-007 closed → ADR-082 (modversion drift handling: warn-loud at load, hard-fail at apply)
  • IMPLEMENTATION.md Phase 5 (conditional Path C extension) retired — the question it asked is answered by ADR-081's tiered policy

Vocabulary (expressive-baseline pack: 28 → 39 entries)

Phase 4 closure — 6 magnitude-ladder modules collapsed (closes the v1.6.0 plan):

  • saturation_global, sigmoid_contrast, bilat_clarity_strength, grain_strength, highlights_clip_threshold, temperature (multi-axis — first 2-parameter ship)

RFC-022 Tier 2 expansion — 4 brand-new modules / 6 entries:

  • crop (4-axis workflow primitive), sharpen, vibrance / chroma_global / hue_angle (colorbalancergb extras), toneequalizer (9-axis)

#86 Brilliance axes — 4 colorbalancergb per-zone luminance entries

#63 B&W triobw_convert (Rec. 709), bw_sky_drama (red-emphasis), bw_foliage (green-emphasis); channelmixerrgb mv3 reverse-engineered programmatically

#87 L2 lookslook_portrait, look_landscape, look_vintage_film — multi-plugin composites

Final pack: 18 parameterized + 4 mask-bound + 3 L2 composites + 14 plain discrete = 39 entries.

Test infrastructure

  • 5-layer parameterized coverage CI gate (ADR-080) — every parameterized entry must have unit + integration + lab-grade global + lab-grade masked + visual-proof sweep coverage
  • Manifest-modversion consistency CI gate (#85) — manifest's declared modversions checked against decoder's _STRUCT_SIZE at unit-test time
  • Runtime modversion drift detection (ADR-082) — warns / raises at vocab load when manifest disagrees with decoder. Caught 6 real pre-existing fixture bugs as a side-effect.

CLI / MCP discoverability (#89)

  • chemigram vocab show / vocab list surface parameterized flag + parameters block (name, type, range, default, module, modversion, offset) per axis
  • MCP list_vocabulary tool serializes parameters identically — agents can introspect without runtime trial-and-error

Visual-proofs gallery (#84)

  • Multi-axis sweep grid: every multi-parameter entry now shows one sweep row per axis (toneequalizer 9 / crop 4 / temperature 2). Total sweep rows: 12 → 26.

Test counts

  • 1127 unit + integration tests passing (was ~767 at v1.6.0)
  • 13/13 lab-grade masked tests pass against real darktable 5.4.1
  • ruff / mypy / mkdocs --strict all clean

ADRs added in this milestone

ADR-077, ADR-078, ADR-079, ADR-080 (RFC-021 closure), ADR-081 (RFC-022 closure), ADR-082 (RFC-007 closure).

Issues closed

#63, #80, #81, #82, #83, #84, #85, #86, #87, #88, #89 (11 issues).

v1.6.0 — parameterized vocabulary magnitudes

Choose a tag to compare

@chipi chipi released this 05 May 09:30

The discrete-magnitude-ladder vocabulary trap is escaped. Parameterized vocabulary entries (RFC-021 / ADR-077..080) replace the per-strength enumeration with continuous values supplied at apply time.

Highlights

  • exposure --value V — continuous EV in [-3.0, +3.0]. Replaces the v1.5.x discrete expo_+0.5 / expo_-0.5 / expo_+0.3 / expo_-0.3 / shadows_global_+/- ladder. Any value the photographer wants.
  • vignette --value V — continuous brightness in [-1.0, +1.0]. Replaces vignette_subtle / vignette_medium / vignette_heavy.
  • CLI: chemigram apply-primitive --entry exposure --value 0.7 (single-param shorthand) or --param NAME=V (repeatable, for multi-parameter modules).
  • MCP: apply_primitive tool gains optional value arg (number for single-param entries, dict for multi-parameter).
  • Composition: --value and --mask-spec are orthogonal axes — apply-primitive --entry exposure --value 0.5 --mask-spec '<json>' brightens the masked region by exactly 0.5 EV.

Architecture

  • Path C as default for parameterized modules (ADR-077; partially supersedes ADR-008 for explicitly-declared parameterizable modules).
  • Manifest schema gains parameters array (ADR-078; multi-parameter from day one). Each parameter declares name / type / range / default plus byte-level field location in the module's op_params blob.
  • apply_primitive value argument (ADR-079; hard-reject range validation).
  • 5-layer test-coverage CI gate (ADR-080): every parameterized entry must have unit + integration + lab-grade global + lab-grade masked + visual-proof-sweep coverage. Manifest-reading linter enforces it.

What's in scope for v1.6.0

  • exposure (mv7) and vignette (mv4) — two single-axis modules to validate the architecture isn't tied to one module's quirks.
  • 9 magnitude-ladder entries removed: expo_+0.5/-0.5/+0.3/-0.3, shadows_global_+/-, vignette_subtle/medium/heavy. No deprecation aliases per ADR-078 (RFC-021 §Q4 — no users yet, no migration burden).
  • Vocabulary count: 39 → 31 (-9 ladder + 2 parameterized).

Phase 4 (deferred)

Parameterizing additional modules — temperature (multi-axis), colorbalancergb saturation, sigmoid contrast, bilat clarity, grain, highlights. Each ~half a day per ADR-077 now that the architecture is in place.

Other v1.6.0 work

  • #77 — Lab-grade primitive isolation tests (chart-based per-patch assertion)
  • #78--mask-spec flag for ad-hoc masked primitive application
  • #79 — Synthetic clipped-gradient fixture for highlights_recovery and grain validation
  • Visual-proofs gallery extended with parameter-sweep rows for parameterized entries
  • Mask-applicable controls compatibility matrix doc (docs/guides/mask-applicable-controls.md)
  • Capability survey doc (docs/capability-survey.md) — the honest catalog of what chemigram supports today vs what darktable offers

Tests

734 unit + integration tests passing. Lab-grade e2e suite runs against real darktable. Lint, mypy, mkdocs strict all clean.

Closed

v1.5.0 — drawn-mask only

Choose a tag to compare

@chipi chipi released this 03 May 17:47

Mask architecture cleanup — drawn-mask only

While shipping path 4a (drawn-mask XMP serialization) in v1.4.0 we discovered the previous PNG-mask path was a silent no-op: darktable never reads external PNG files for raster masks. Verified against darktable 5.4.1 source — src/develop/blend.c resolves raster masks from in-pipeline pointers (self->raster_mask.sink.source), not the filesystem. The bundled MaskingProvider Protocol, CoarseAgentProvider, geometric providers, mask registry, materialize_mask_for_dt helper, and the generate_mask/regenerate_mask/list_masks/tag_mask/invalidate_mask MCP tools were all infrastructure for a path that never connected to pixels.

This release rips that dead infrastructure entirely. Breaking changes.

ADR-076 (supersedes ADR-021/022/055/057/058/074): drawn-mask only. Mask-bound vocabulary entries declare mask_spec and route through apply_with_drawn_mask automatically — no providers, no registry, no PNG.

Removed

  • Production code: chemigram.core.masking.MaskingProvider Protocol + result/error types, CoarseAgentProvider, Gradient/Radial/RectangleMaskProvider, chemigram.core.versioning.masks (entire mask registry), chemigram.core.helpers.materialize_mask_for_dt / serialize_mask_entry / ensure_preview_render.
  • MCP tools: generate_mask, regenerate_mask, list_masks, tag_mask, invalidate_mask.
  • CLI sub-app: chemigram masks ... (list / generate / regenerate / tag / invalidate).
  • Vocabulary schema: mask_kind, mask_ref fields. mask_spec is now the only mask declaration.
  • Tool surface: apply_primitive's mask_override argument (CLI flag and MCP arg).
  • Server wiring: build_server(masker=...), ToolContext.masker.
  • Workspace layout: Workspace.masks_dir property and masks/ subdir.
  • Runtime dep: numpy>=1.26 (was for the now-removed geometric providers).

Vocabulary changes

  • tone_lifted_shadows_subject (#62) retired from the starter pack — was doubly broken (wrong dtstyle content awaiting a real darktable session AND the raster path it depended on never worked). Starter drops 5 → 4 entries.
  • The four expressive-baseline mask-bound entries — gradient_top_dampen_highlights, gradient_bottom_lift_shadows, radial_subject_lift, rectangle_subject_band_dim — survive unchanged. Their mask_spec was already the load-bearing field; the redundant mask_kind: "drawn" marker was stripped.

Prompt template

mode_a/system_v4.j2 is the new active version per ADR-043 (append-only). Drops references to the removed tools and the mask_kind: "raster" schema; rewrites the "Local adjustments" section around the built-in geometric mask types.

Architecture seam

The Phase 4 sibling project (working name chemigram-masker-sam) for content-aware masking will produce darktable drawn-form geometry — not PNG bytes. The wire format is the same one v1.5.0 uses today; the apply path stays unchanged when it lands. ADR-076 documents the rationale.

Audit follow-up (commit 5937d7a)

After the initial v1.5.0 ship, a four-angle deep audit (code, tests, docs, architecture-seam) surfaced a handful of stale references and one coverage gap. All closed in commit 5937d7a and included in this tag:

  • Live docs corrected: CONTRIBUTING.md (vocabulary schema), vocabulary/starter/README.md (entry count + dropped row), docs/adr/TA.md (MCP component description: 27 → 22 tools with category breakdown), docs/testing.md (tool count + example primitive + dead-category drop), docs/concept/02-project-concept.md (masking framing rewritten), docs/IMPLEMENTATION.md (Slice 4 supersession status note).
  • Append-only ADRs marked partial-superseded: ADR-056 (5 mask tools + mask_override gone) and ADR-070 (masks sub-app gone), mirroring what we already did for ADR-021/022/055/057/058/074.
  • Coverage gap closed: added test_apply_primitive_with_mask_spec_routes_through_drawn_mask and test_apply_primitive_with_invalid_mask_spec_returns_masking_error (MCP unit) plus the matching CLI integration test. Without these, only the e2e suite (real darktable) exercised the drawn-mask dispatch branch.

Stats

  • 66 production / test files in commit 155784b; 11 doc files in commit 76b0cb7; 10 audit-fix files in commit 5937d7a.
  • Net –4135 lines code/tests (cleanup) + ~50 net lines docs (compression) + 122 lines audit fixes.
  • CI: 440 unit + 206 integration + 46 e2e all pass against real darktable 5.4.1. 692 tests total.
  • Pre-existing tests/integration/cli/test_stdin_batch.py import bug fixed in passing.

Carry to v1.5.x

  • #63 — three channelmixerrgb B&W vocabulary entries still want a hand-authored darktable seed.

No PyPI (deferred per the v1.4.0 ship direction; will revisit after MVP review).

🤖 Generated with Claude Code

v1.4.0 — vocabulary + masks + CLI ergonomics + infra cleanup

Choose a tag to compare

@chipi chipi released this 03 May 13:56

v1.4.0

The "ambitious 1.4" milestone: closes the v1.4.0 extension plan with 2 new ADRs, 1 amended ADR, 3 RFC closures, and 11 GH issues. Total 23 new tests (700 → 723) across 14 commits in 7 batches.

New capabilities

  • Built-in geometric mask providers (ADR-074). GradientMaskProvider, RadialMaskProvider, RectangleMaskProvider in chemigram.core.masking.geometric — deterministic, parameter-driven, BYOA-compatible (no agent required). Adds numpy>=1.26 to runtime deps.
  • CLI mask integration (#73). chemigram masks generate <id> --target <name> --provider gradient --config '{...}' works with all 3 built-in providers. Without --provider, the v1.3.0 MASKING_ERROR surface is preserved.
  • Vocabulary mask_spec field. Optional declaration of which provider + config should generate a raster mask if it isn't yet registered. v1.4.0 stores; v1.5.x will auto-generate at apply time.

Vocabulary completion

  • 31 expressive-baseline entries authored programmatically across 9 darktable iop modules (exposure, temperature, sigmoid, localcontrast, colorbalancergb, grain, vignette, highlights, channelmixerrgb deferred). 22 e2e direction-of-change tests passing against real darktable 5.4.1.
  • ADR-073 formalizes Path C (programmatic vocabulary authoring via reverse-engineered iop struct layouts) as an accepted complement to hand-authoring. Closes RFC-012.

CLI ergonomics

  • Shell completion (#67). chemigram --install-completion / --show-completion (bash/zsh/fish/PowerShell). Closes RFC-020 §Q1.
  • Workspace auto-discovery (#69). cd ~/Pictures/Chemigram/iguana && chemigram get-state . works without flags. Closes RFC-020 §Q3.

Infrastructure

  • Linux CI matrix (#70). ubuntu-latest joins macos-latest. ADR-075 amends ADR-040.
  • Manifest↔dtstyle audit (#71). Catches the tone_lifted_shadows_subject-style content drift at CI time.
  • Helpers refactor (#66). 6 MCP-private helpers lifted to chemigram.core.helpers. Closes the ADR-071 follow-up.

RFC closures

  • RFC-012 (programmatic vocabulary generation / Path C) → Decided via ADR-073.
  • RFC-018 (vocabulary expansion for expressive taste) → Decided via ADR-063, ADR-064, ADR-073.
  • RFC-019 (reference-image validation baseline) → Decided via ADR-066, ADR-067, ADR-068.

Known deferrals

  • #62 (starter tone_lifted_shadows_subject content bug) and #63 (channelmixerrgb B&W ×3) carry to v1.4.x — both blocked on hand-authored darktable sessions.
  • #68 (CLI stdin support) deferred — needs verb-signature changes across 8+ files.

Distribution

No PyPI per project decision. Install via pip install git+https://github.com/chipi/chemigram@v1.4.0.

Full changelog

https://github.com/chipi/chemigram/blob/main/CHANGELOG.md#140--2026-05-02

v1.3.0 — chemigram CLI

Choose a tag to compare

@chipi chipi released this 03 May 07:52

chemigram CLI shipped. 22 verbs in chemigram.cli mirroring the MCP tool surface verb-for-verb, alongside the existing chemigram-mcp server. Same engine, same workspace state on disk; the choice between MCP and CLI is about workflow shape (conversational vs programmatic), not capability.

PRD-005 frames the user-value case; RFC-020 (now Decided) locks the design via four closing ADRs (069/070/071/072).

What ships

  • chemigram binary on $PATH alongside chemigram-mcp after pip install
  • 22 verbs total:
    • Diagnostic: status
    • Lifecycle: ingest
    • Edit/state: apply-primitive, remove-module, reset, get-state
    • Versioning: snapshot, branch, tag, checkout, log, diff
    • Layer binding: bind-layers
    • Render/export: render-preview, compare, export-final
    • Masks: masks list / generate / regenerate / tag / invalidate
    • Context: read-context, log-vocabulary-gap, apply-taste-update, apply-notes-update
    • Vocab: vocab list / show
  • Output: human-readable text by default; NDJSON via --json. Output schema versioned independently of package SemVer (locked at 1.0; surfaced via chemigram status).
  • Stable exit codes: 11-value ExitCode IntEnum mapped from chemigram.mcp.errors.ErrorCode. Mypy-exhaustive mapping; runtime audit test as a safety net.
  • Auto-generated CLI reference: docs/guides/cli-reference.md regenerated from chemigram --help via make docs-cli; CI checks for drift.

Why two adapters

Both MCP and CLI are thin wrappers over chemigram.core (per ADR-071, lint-enforced). Same engine, two invocation surfaces:

  • MCP (conversational): Long-lived session with an agent in an MCP-capable client (Claude Code, Cursor, Continue, Cline, Zed, Claude Desktop). Best for one-image-deep collaborative editing.
  • CLI (programmatic): Subprocess calls from shell scripts, custom Python loops, batch jobs, watch-folder daemons, CI pipelines. No session lifecycle; each invocation is one operation.

See docs/index.md § Two planes of control and docs/getting-started.md § Driving Chemigram from a script or agent loop.

Notable design decisions

  • propose-taste-update / confirm-taste-update are MCP-only by design. The conversational propose/confirm protocol requires per-process state across two tool calls. The CLI ships direct apply-taste-update / apply-notes-update verbs instead because parallel batch invocations would race on a shared proposal store.
  • masks generate / regenerate exit MASKING_ERROR (7) because the subprocess CLI has no provider-injection path equivalent to MCP's build_server(masker=...). List/tag/invalidate work fully without a provider. Future RFC may add config-driven provider selection.
  • chemigram status always exits 0 (diagnostic; reports missing components in fields/warnings rather than failing). Scripts that need a hard check for darktable can branch on payload["darktable_cli_path"] is None in the JSON output.

Architectural anchors

  • ADR-069 — CLI alongside MCP, won't replace it
  • ADR-070 — CLI framework: Typer
  • ADR-071 — Thin-wrapper discipline (lint-enforced)
  • ADR-072 — Output format (NDJSON) + exit codes
  • RFC-020 — Decided, closes here

Verification

  • 697 unit + integration tests pass; ruff + mypy + audit + cli-reference sync clean
  • 4 e2e session tests against real darktable: ingest → apply → render → export → compare round-trip
  • Verb-parity audit _KNOWN_PENDING_VERBS allowlist down to 4 (the conversational propose/confirm tools, MCP-only by design)

Full changelog: CHANGELOG.md

MCP server unchanged. All existing MCP integrations (Claude Code, Cursor, Claude Desktop, etc.) continue to work without modification.

v1.2.0 — Engine unblock + reference-image validation

Choose a tag to compare

@chipi chipi released this 02 May 14:15

Engine unblock + reference-image validation infrastructure. Closes the synthesizer's Path B gap (RFC-018 v0.2) and ships a CI-safe colorimetric assertion library (RFC-019 v0.2) — the two pieces that gate the future 35-entry expressive-baseline vocabulary authoring sprint (now v1.4.0). The CLI work (RFC-020 / PRD-005) ships next as v1.3.0.

Engine — Path B unblocked

  • synthesize_xmp now appends new-instance history entries for vocabulary primitives whose (operation, multi_priority) tuple isn't in the baseline (closes RFC-001's open Path B question; ADR-063). Empirical evidence in tests/fixtures/preflight-evidence/ showed darktable 5.4.1 resolves pipeline order from iop_order_version + the internal iop_list regardless of per-entry iop_order, so appended entries ship with iop_order=None — no probe script, no manifest schema fields. Supersedes ADR-051's "Path B deferred" stance.
  • HistoryEntry.iop_order type widened from int | None to float | None so rendered XMP sidecars (which can carry float values) round-trip cleanly.
  • VocabularyIndex now accepts Path | list[Path] and ships load_packs(["starter", "expressive-baseline"]) for multi-pack loading. Cross-pack name collisions raise a clear error naming both pack roots.

Reference-image validation (RFC-019 v0.2)

  • chemigram.core.assertions — hand-rolled CIE DE2000, sRGB ↔ Lab D50 (Lindbloom Bradford adaptation), patch extraction, and high-level helpers (assert_color_accuracy, assert_tonal_response, assert_exposure_shift, assert_wb_shift). Validated against the Sharma/Wu/Dalal 2005 reference test pair (DE2000 = 2.0425).
  • tests/fixtures/reference-targets/ — synthetic CC24 + grayscale ramp PNGs paired with X-Rite published L*a*b* D50 ground truth. CI-safe (no darktable, no real RAW). Tier B (real-RAW reference shooting) is deferred per ADR-068.

Prompts

  • Mode A system_v3.j2 is now active. Filter-first vocabulary navigation, multi-pack enumeration, explicit end_session orchestration. Adds vocabulary_packs to required context. v1 + v2 stay loadable for eval reproducibility (ADR-045).

Vocabulary scaffolding

  • vocabulary/packs/expressive-baseline/ skeleton (empty manifest, README, L3 module subdirectories). Authoring of the 35 entries is hands-on darktable work in v1.4.0.

Closing ADRs

063, 064, 066, 067, 068. ADR-065 deliberately gapped — see docs/adr/index.md Conventions.

Tests

556 unit + integration green. New: 34 unit tests for the assertion library, 9 integration tests for the synthetic CC24 round-trip, 16 e2e scaffolds for expressive-baseline (auto-skip until entries are authored).

Phase plan reshaped (2026-05-02)

Phase 1.2 originally bundled engine + 35-entry authoring under v1.2.0. Authoring is hands-on darktable work; it was decoupled from the engine slice. Engine + assertion library ship now as v1.2.0; CLI ships as v1.3.0; the 35-entry authoring sprint ships as v1.4.0. Phase 2 begins post-v1.4.0.


Full changelog: CHANGELOG.md