picturesq 0.0.1__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (51) hide show
  1. picturesq-0.0.1/.github/ISSUE_TEMPLATE/bug.yml +20 -0
  2. picturesq-0.0.1/.github/ISSUE_TEMPLATE/config.yml +1 -0
  3. picturesq-0.0.1/.github/ISSUE_TEMPLATE/task.yml +32 -0
  4. picturesq-0.0.1/.github/pull_request_template.md +13 -0
  5. picturesq-0.0.1/.github/workflows/ci.yml +39 -0
  6. picturesq-0.0.1/.github/workflows/claude.yml +50 -0
  7. picturesq-0.0.1/.github/workflows/release.yml +20 -0
  8. picturesq-0.0.1/.gitignore +41 -0
  9. picturesq-0.0.1/.pre-commit-config.yaml +15 -0
  10. picturesq-0.0.1/AGENTS.md +62 -0
  11. picturesq-0.0.1/CLAUDE.md +1 -0
  12. picturesq-0.0.1/LICENSE +21 -0
  13. picturesq-0.0.1/PKG-INFO +55 -0
  14. picturesq-0.0.1/README.md +35 -0
  15. picturesq-0.0.1/docs/decisions/0001-rawpy-decoder-no-darktable.md +15 -0
  16. picturesq-0.0.1/docs/decisions/0002-no-camera-conversion-lut.md +14 -0
  17. picturesq-0.0.1/docs/decisions/0003-agx-display-transform.md +14 -0
  18. picturesq-0.0.1/docs/decisions/0004-yaml-machine-owned.md +16 -0
  19. picturesq-0.0.1/docs/decisions/0005-web-ui-no-build.md +15 -0
  20. picturesq-0.0.1/docs/decisions/0006-glossary-and-public-repo.md +19 -0
  21. picturesq-0.0.1/docs/decisions/README.md +13 -0
  22. picturesq-0.0.1/docs/glossary.md +71 -0
  23. picturesq-0.0.1/docs/learning-log.md +30 -0
  24. picturesq-0.0.1/docs/project-plan.md +362 -0
  25. picturesq-0.0.1/docs/setup-connectors.md +30 -0
  26. picturesq-0.0.1/pyproject.toml +64 -0
  27. picturesq-0.0.1/reference/README.md +29 -0
  28. picturesq-0.0.1/reference/samples.json +4 -0
  29. picturesq-0.0.1/scripts/fetch_samples.py +51 -0
  30. picturesq-0.0.1/skills/picturesq/SKILL.md +12 -0
  31. picturesq-0.0.1/src/picturesq/__init__.py +3 -0
  32. picturesq-0.0.1/src/picturesq/__main__.py +3 -0
  33. picturesq-0.0.1/src/picturesq/cli/__init__.py +90 -0
  34. picturesq-0.0.1/src/picturesq/colour/__init__.py +4 -0
  35. picturesq-0.0.1/src/picturesq/config/__init__.py +1 -0
  36. picturesq-0.0.1/src/picturesq/decode/__init__.py +1 -0
  37. picturesq-0.0.1/src/picturesq/export/__init__.py +1 -0
  38. picturesq-0.0.1/src/picturesq/grade/__init__.py +4 -0
  39. picturesq-0.0.1/src/picturesq/grade/looks/.gitkeep +0 -0
  40. picturesq-0.0.1/src/picturesq/normalise/__init__.py +4 -0
  41. picturesq-0.0.1/src/picturesq/pipeline/__init__.py +1 -0
  42. picturesq-0.0.1/src/picturesq/preview/__init__.py +1 -0
  43. picturesq-0.0.1/src/picturesq/ui/__init__.py +1 -0
  44. picturesq-0.0.1/src/picturesq/ui/static/.gitkeep +0 -0
  45. picturesq-0.0.1/src/picturesq/ui/templates/.gitkeep +0 -0
  46. picturesq-0.0.1/tests/__init__.py +0 -0
  47. picturesq-0.0.1/tests/golden/.gitkeep +0 -0
  48. picturesq-0.0.1/tests/integration/__init__.py +0 -0
  49. picturesq-0.0.1/tests/unit/__init__.py +0 -0
  50. picturesq-0.0.1/tests/unit/test_cli.py +34 -0
  51. picturesq-0.0.1/uv.lock +358 -0
@@ -0,0 +1,20 @@
1
+ name: Bug
2
+ description: Something produces wrong output or crashes
3
+ labels: ["bug"]
4
+ body:
5
+ - type: textarea
6
+ id: what
7
+ attributes:
8
+ label: What happened vs expected
9
+ validations:
10
+ required: true
11
+ - type: textarea
12
+ id: repro
13
+ attributes:
14
+ label: Repro (command, config, camera/RAW type)
15
+ validations:
16
+ required: true
17
+ - type: textarea
18
+ id: env
19
+ attributes:
20
+ label: Environment (`picturesq doctor --json`)
@@ -0,0 +1 @@
1
+ blank_issues_enabled: true
@@ -0,0 +1,32 @@
1
+ name: Task
2
+ description: A small (30–60 min) unit of work with clear acceptance criteria
3
+ labels: ["task"]
4
+ body:
5
+ - type: textarea
6
+ id: context
7
+ attributes:
8
+ label: Context
9
+ description: Why this task exists; links to the project plan section / ADR.
10
+ validations:
11
+ required: true
12
+ - type: textarea
13
+ id: ac
14
+ attributes:
15
+ label: Acceptance criteria
16
+ description: Checklist of observable outcomes.
17
+ value: |
18
+ - [ ]
19
+ validations:
20
+ required: true
21
+ - type: textarea
22
+ id: test
23
+ attributes:
24
+ label: Test plan
25
+ description: How it is verified (unit test, golden image, contact sheet review, …).
26
+ validations:
27
+ required: true
28
+ - type: textarea
29
+ id: learn
30
+ attributes:
31
+ label: Learning note
32
+ description: Concept(s) involved, for docs/learning-log.md (optional).
@@ -0,0 +1,13 @@
1
+ ## Summary
2
+ <!-- What and why. Closes #… -->
3
+
4
+ ## Test plan
5
+ <!-- Commands run, tests added, contact sheet reviewed, … -->
6
+
7
+ ## Learning note
8
+ <!-- New concept? Add an entry to docs/learning-log.md -->
9
+
10
+ ## Checklist
11
+ - [ ] Tests added/updated (maths needs tests)
12
+ - [ ] No new runtime dependency (or approved in the issue)
13
+ - [ ] Docs/ADR updated if a decision changed
@@ -0,0 +1,39 @@
1
+ name: CI
2
+
3
+ on:
4
+ push:
5
+ branches: [main]
6
+ pull_request:
7
+
8
+ concurrency:
9
+ group: ci-${{ github.ref }}
10
+ cancel-in-progress: true
11
+
12
+ jobs:
13
+ lint:
14
+ runs-on: ubuntu-latest
15
+ steps:
16
+ - uses: actions/checkout@v4
17
+ - uses: astral-sh/setup-uv@v6
18
+ with:
19
+ enable-cache: true
20
+ - run: uv sync --locked
21
+ - run: uv run ruff check .
22
+ - run: uv run ruff format --check .
23
+ - run: uv run pyright
24
+
25
+ test:
26
+ strategy:
27
+ fail-fast: false
28
+ matrix:
29
+ os: [ubuntu-latest, windows-latest]
30
+ python: ["3.11", "3.13"]
31
+ runs-on: ${{ matrix.os }}
32
+ steps:
33
+ - uses: actions/checkout@v4
34
+ - uses: astral-sh/setup-uv@v6
35
+ with:
36
+ enable-cache: true
37
+ python-version: ${{ matrix.python }}
38
+ - run: uv sync --locked
39
+ - run: uv run pytest
@@ -0,0 +1,50 @@
1
+ name: Claude Code
2
+
3
+ on:
4
+ issue_comment:
5
+ types: [created]
6
+ pull_request_review_comment:
7
+ types: [created]
8
+ issues:
9
+ types: [opened, assigned]
10
+ pull_request_review:
11
+ types: [submitted]
12
+
13
+ jobs:
14
+ claude:
15
+ if: |
16
+ (github.event_name == 'issue_comment' && contains(github.event.comment.body, '@claude')) ||
17
+ (github.event_name == 'pull_request_review_comment' && contains(github.event.comment.body, '@claude')) ||
18
+ (github.event_name == 'pull_request_review' && contains(github.event.review.body, '@claude')) ||
19
+ (github.event_name == 'issues' && (contains(github.event.issue.body, '@claude') || contains(github.event.issue.title, '@claude')))
20
+ runs-on: ubuntu-latest
21
+ permissions:
22
+ contents: read
23
+ pull-requests: read
24
+ issues: read
25
+ id-token: write
26
+ actions: read # Required for Claude to read CI results on PRs
27
+ steps:
28
+ - name: Checkout repository
29
+ uses: actions/checkout@v4
30
+ with:
31
+ fetch-depth: 1
32
+
33
+ - name: Run Claude Code
34
+ id: claude
35
+ uses: anthropics/claude-code-action@v1
36
+ with:
37
+ claude_code_oauth_token: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }}
38
+
39
+ # This is an optional setting that allows Claude to read CI results on PRs
40
+ additional_permissions: |
41
+ actions: read
42
+
43
+ # Optional: Give a custom prompt to Claude. If this is not specified, Claude will perform the instructions specified in the comment that tagged it.
44
+ # prompt: 'Update the pull request description to include a summary of changes.'
45
+
46
+ # Optional: Add claude_args to customize behavior and configuration
47
+ # See https://github.com/anthropics/claude-code-action/blob/main/docs/usage.md
48
+ # or https://code.claude.com/docs/en/cli-reference for available options
49
+ # claude_args: '--allowed-tools Bash(gh pr *)'
50
+
@@ -0,0 +1,20 @@
1
+ # Publishes to PyPI on version tags (trusted publishing; no API token).
2
+ # The `pypi` environment only accepts `v*` tags. Bump `version` in pyproject.toml to match the tag.
3
+ name: Release
4
+
5
+ on:
6
+ push:
7
+ tags: ["v*"]
8
+
9
+ jobs:
10
+ publish:
11
+ runs-on: ubuntu-latest
12
+ environment: pypi
13
+ permissions:
14
+ id-token: write
15
+ contents: read
16
+ steps:
17
+ - uses: actions/checkout@v4
18
+ - uses: astral-sh/setup-uv@v6
19
+ - run: uv build
20
+ - run: uv publish --trusted-publishing always
@@ -0,0 +1,41 @@
1
+ # Python
2
+ __pycache__/
3
+ *.py[cod]
4
+ .venv/
5
+ *.egg-info/
6
+ build/
7
+ dist/
8
+ .pytest_cache/
9
+ .ruff_cache/
10
+ .benchmarks/
11
+ .coverage
12
+ htmlcov/
13
+
14
+ # picturesq run artefacts (never commit outputs or caches)
15
+ .picturesq/
16
+ exports/
17
+
18
+ # RAW and large image files: reference RAWs stay local
19
+ reference/raw/
20
+ reference/samples/
21
+ *.DNG
22
+ *.dng
23
+ *.CR2
24
+ *.CR3
25
+ *.NEF
26
+ *.ARW
27
+ *.RAF
28
+ *.RW2
29
+ *.ORF
30
+ *.tif
31
+ *.tiff
32
+ # Golden test images are small PNGs and ARE committed (tests/golden/)
33
+
34
+ # Editors / OS
35
+ .vscode/
36
+ .idea/
37
+ .DS_Store
38
+ Thumbs.db
39
+
40
+ # Local agent settings
41
+ .claude/settings.local.json
@@ -0,0 +1,15 @@
1
+ repos:
2
+ - repo: https://github.com/astral-sh/ruff-pre-commit
3
+ rev: v0.6.9
4
+ hooks:
5
+ - id: ruff
6
+ args: [--fix]
7
+ - id: ruff-format
8
+ - repo: https://github.com/pre-commit/pre-commit-hooks
9
+ rev: v5.0.0
10
+ hooks:
11
+ - id: check-yaml
12
+ - id: end-of-file-fixer
13
+ - id: trailing-whitespace
14
+ - id: check-added-large-files
15
+ args: [--maxkb=1024]
@@ -0,0 +1,62 @@
1
+ # AGENTS.md — picturesq
2
+
3
+ Instructions for coding agents (Claude Code, Codex, …) working on this repo.
4
+ **Read this first, then `docs/project-plan.md` and `docs/glossary.md`. Don't re-explore the whole repo each session.**
5
+
6
+ ## What we're building
7
+ `picturesq`: a CLI that turns a folder of RAW photos into consistently graded exports.
8
+ Pipeline stages: **decode** (rawpy → scene-linear) → **normalise** (adaptive, per photo) → **grade** (look + adjustments + AgX display transform) → **export** (outputs, sRGB JPEG).
9
+ The local web UI (`picturesq ui`) is the only way humans edit config. Agents edit the YAML or use the CLI.
10
+
11
+ - Scope, decisions, milestones: `docs/project-plan.md`
12
+ - Decision rationale: `docs/decisions/` (ADRs; add one when you make a significant decision)
13
+ - Vocabulary (binding): `docs/glossary.md`
14
+ - Concepts: `docs/learning-log.md`
15
+ - Current milestone and tasks: [project board](https://github.com/users/eddiemg10/projects/1) / GitHub issues (the only tracker)
16
+
17
+ ## Hard rules
18
+ - **Vocabulary:** use only glossary terms in code, CLI, config, docs, issues and UI text. British spelling everywhere (`colour`, `normalise`, `grey`), identifiers included. New term? Add it to the glossary in the same PR.
19
+ - **Never modify originals.** Outputs go to `<dir>/.picturesq/`, `<dir>/exports/`, or `--out`.
20
+ - **Precision:** float32 scene-linear internally (linear Rec.2020 primaries); 16-bit for intermediates on disk; 8-bit only at final export. Exposure/WB maths in linear only.
21
+ - **Config over constants:** tunable values live in config models (`picturesq.config`), not inline in code.
22
+ - **Pure image operations:** `f(image: float32 HxWx3, params) -> image`, with no IO inside `colour/`, `normalise/`, `grade/`.
23
+ - **Memory:** never hold more than the needed full-res arrays; prefer in-place numpy (`out=`); interactive paths use proxies only.
24
+ - **Reproducible:** same originals + config + version = same output. Seed anything random (grain).
25
+ - **Dependencies:** ask before adding a runtime dependency (the list is in project plan §12).
26
+ - **Tests:** maths is not merged without tests. Every PR states how it was tested.
27
+ - **Small steps:** one issue per PR; propose a plan before large changes.
28
+ - **Be honest about limits:** if something is a guess about the camera, a colour space or a LUT, say so and ask.
29
+
30
+ ## Working with the user (teaching protocol)
31
+ The user is an experienced engineer but **new to image science**. Don't explain general programming. For each milestone:
32
+ 1. A short concept primer (2–4 concepts, plain language, one concrete example each).
33
+ 2. State trade-offs when choosing a library, colour space or algorithm.
34
+ 3. Show, don't just tell: small visual/numeric demos (before/after, histograms).
35
+ 4. Flag classic gotchas (wrong input space, 8-bit clipping, exposure in gamma space, …).
36
+ 5. Append to `docs/learning-log.md` (concept, one paragraph, why it mattered in code).
37
+ 6. Occasionally one quick understanding check at the end of a milestone; don't quiz constantly.
38
+
39
+ ## Commands
40
+ ```bash
41
+ uv sync # install env
42
+ uv run pytest # tests
43
+ uv run ruff check . && uv run ruff format --check .
44
+ uv run pyright
45
+ uv run picturesq --help
46
+ ```
47
+
48
+ ## Repo map
49
+ ```
50
+ src/picturesq/
51
+ cli/ config/ colour/ decode/ normalise/ grade/ export/ pipeline/ preview/ ui/
52
+ tests/unit tests/integration tests/golden
53
+ reference/ # reference set shot list + CC0 samples manifest (RAWs are git-ignored)
54
+ scripts/ # dev utilities (fetch samples, compare, update goldens)
55
+ skills/ # Claude skill shipped for end users' agents
56
+ ```
57
+
58
+ ## Git / PR conventions
59
+ - Branch from `main`: `feat/…`, `fix/…`, `docs/…`, `chore/…`. Squash-merge via PR; `main` is protected and CI must be green.
60
+ - PR title in conventional-commit style; body: summary, linked issue, test plan, learning note (if any).
61
+ - Update `AGENTS.md` / the project plan / glossary / ADRs when decisions change.
62
+ - The repo is **public**: never commit personal photos, absolute home paths or secrets.
@@ -0,0 +1 @@
1
+ @AGENTS.md
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Eddie MG
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,55 @@
1
+ Metadata-Version: 2.5
2
+ Name: picturesq
3
+ Version: 0.0.1
4
+ Summary: Turn a folder of RAW photos into consistently graded exports, driven by looks and YAML.
5
+ Project-URL: Homepage, https://github.com/eddiemg10/picturesq
6
+ Project-URL: Repository, https://github.com/eddiemg10/picturesq
7
+ Project-URL: Issues, https://github.com/eddiemg10/picturesq/issues
8
+ Project-URL: Roadmap, https://github.com/users/eddiemg10/projects/1
9
+ Author: Eddie MG
10
+ License-Expression: MIT
11
+ License-File: LICENSE
12
+ Classifier: Development Status :: 2 - Pre-Alpha
13
+ Classifier: Environment :: Console
14
+ Classifier: Intended Audience :: End Users/Desktop
15
+ Classifier: Programming Language :: Python :: 3
16
+ Classifier: Topic :: Multimedia :: Graphics :: Graphics Conversion
17
+ Requires-Python: >=3.11
18
+ Requires-Dist: typer>=0.12
19
+ Description-Content-Type: text/markdown
20
+
21
+ # picturesq
22
+
23
+ Turn a folder of RAW photos into consistently graded exports. No photo editor needed.
24
+
25
+ ```bash
26
+ picturesq run ./lisbon # decode → normalise → grade → export
27
+ picturesq ui ./lisbon # choose looks and set their intensity in a tiny local web UI
28
+ ```
29
+
30
+ **Status:** pre-alpha (M0, setup). Plan: [`docs/project-plan.md`](https://github.com/eddiemg10/picturesq/blob/main/docs/project-plan.md) · Roadmap: [project board](https://github.com/users/eddiemg10/projects/1).
31
+
32
+ - Any camera LibRaw supports; built and tested on DJI Osmo Pocket 4 DNGs
33
+ - Runs on an 8 GB, CPU-only laptop (Linux/WSL2, Windows)
34
+ - Looks in four categories (B/W · Cold · Warm · Cinematic), each with adjustable intensity
35
+ - Config is YAML, written by the web UI and editable by coding agents
36
+
37
+ ## Development
38
+ ```bash
39
+ uv sync
40
+ uv run pytest
41
+ ```
42
+ See [`AGENTS.md`](https://github.com/eddiemg10/picturesq/blob/main/AGENTS.md) for conventions and [`docs/glossary.md`](https://github.com/eddiemg10/picturesq/blob/main/docs/glossary.md) for vocabulary.
43
+
44
+ ## Releasing
45
+ Releases are manual for now and triggered by a `v*` tag. Pushing the tag runs the [`Release`](https://github.com/eddiemg10/picturesq/blob/main/.github/workflows/release.yml) workflow, which builds with `uv build` and publishes to PyPI via trusted publishing (no API token).
46
+
47
+ 1. Set `version` in `pyproject.toml` (e.g. `0.0.1`) and merge it to `main` via PR.
48
+ 2. Tag the merge commit and push the tag:
49
+ ```bash
50
+ git tag v0.0.1
51
+ git push origin v0.0.1
52
+ ```
53
+ 3. Check the workflow run succeeded and the new version appears on PyPI.
54
+
55
+ The tag must match `version` in `pyproject.toml`. The `pypi` environment only accepts `v*` tags.
@@ -0,0 +1,35 @@
1
+ # picturesq
2
+
3
+ Turn a folder of RAW photos into consistently graded exports. No photo editor needed.
4
+
5
+ ```bash
6
+ picturesq run ./lisbon # decode → normalise → grade → export
7
+ picturesq ui ./lisbon # choose looks and set their intensity in a tiny local web UI
8
+ ```
9
+
10
+ **Status:** pre-alpha (M0, setup). Plan: [`docs/project-plan.md`](https://github.com/eddiemg10/picturesq/blob/main/docs/project-plan.md) · Roadmap: [project board](https://github.com/users/eddiemg10/projects/1).
11
+
12
+ - Any camera LibRaw supports; built and tested on DJI Osmo Pocket 4 DNGs
13
+ - Runs on an 8 GB, CPU-only laptop (Linux/WSL2, Windows)
14
+ - Looks in four categories (B/W · Cold · Warm · Cinematic), each with adjustable intensity
15
+ - Config is YAML, written by the web UI and editable by coding agents
16
+
17
+ ## Development
18
+ ```bash
19
+ uv sync
20
+ uv run pytest
21
+ ```
22
+ See [`AGENTS.md`](https://github.com/eddiemg10/picturesq/blob/main/AGENTS.md) for conventions and [`docs/glossary.md`](https://github.com/eddiemg10/picturesq/blob/main/docs/glossary.md) for vocabulary.
23
+
24
+ ## Releasing
25
+ Releases are manual for now and triggered by a `v*` tag. Pushing the tag runs the [`Release`](https://github.com/eddiemg10/picturesq/blob/main/.github/workflows/release.yml) workflow, which builds with `uv build` and publishes to PyPI via trusted publishing (no API token).
26
+
27
+ 1. Set `version` in `pyproject.toml` (e.g. `0.0.1`) and merge it to `main` via PR.
28
+ 2. Tag the merge commit and push the tag:
29
+ ```bash
30
+ git tag v0.0.1
31
+ git push origin v0.0.1
32
+ ```
33
+ 3. Check the workflow run succeeded and the new version appears on PyPI.
34
+
35
+ The tag must match `version` in `pyproject.toml`. The `pypi` environment only accepts `v*` tags.
@@ -0,0 +1,15 @@
1
+ # ADR-0001: rawpy as the only decoder; no darktable dependency
2
+
3
+ **Status:** Accepted · 2026-10-11
4
+
5
+ ## Context
6
+ The decode stage turns RAW originals into scene-linear images. Options: wrap darktable-cli or RawTherapee-cli (mature, excellent quality), or decode with rawpy (LibRaw) and own the rest of the pipeline.
7
+
8
+ ## Decision
9
+ Use rawpy (LibRaw) as the only decoder. darktable is not a dependency. It is used only by hand during development, as a quality yardstick.
10
+
11
+ ## Consequences
12
+ - + A pip-only install (~50 MB); works on Linux and Windows; 1000+ cameras.
13
+ - + Everything after decode is our numpy code, so YAML maps directly to parameters and proxies render in milliseconds.
14
+ - − darktable's XMP/styles store module params as versioned binary blobs, which can't be generated from YAML. Its per-render process startup also prevents live previews. Avoiding it removes both problems.
15
+ - − Denoise and highlight reconstruction are now our responsibility. Chroma NR is in v1; luma NR gets re-evaluated in M2 on night shots.
@@ -0,0 +1,14 @@
1
+ # ADR-0002: No camera conversion LUT; use DNG colour matrices
2
+
3
+ **Status:** Accepted · 2026-10-11
4
+
5
+ ## Context
6
+ The original brief assumed a "camera conversion LUT" stage. The DJI Osmo Pocket 4 shoots DNG stills; DJI's official LUTs convert **D-Log video** (log-encoded, wide gamut) to Rec.709.
7
+
8
+ ## Decision
9
+ No camera LUT for stills. The DNG's embedded ColorMatrix/ForwardMatrix (applied by LibRaw) maps sensor RGB to a standard space. Proprietary RAWs use LibRaw's built-in per-camera matrices.
10
+
11
+ ## Consequences
12
+ - + Removes the biggest risk in the brief (LUT input-space mismatch).
13
+ - + Works for every LibRaw camera with no per-camera assets.
14
+ - − Users who want "the DJI look" get it from a look, not a vendor LUT. A D-Log encode path could be added later if DJI publishes the D-Log M curve.
@@ -0,0 +1,14 @@
1
+ # ADR-0003: AgX-style display transform as the neutral base
2
+
3
+ **Status:** Accepted · 2026-10-11
4
+
5
+ ## Context
6
+ Scene-linear data has a far larger range than a display. A display transform (tone mapping) compresses it, and its quality defines how highlights, skies and saturated colours look. Naive per-channel curves cause hue skews (blue skies to cyan, orange to yellow).
7
+
8
+ ## Decision
9
+ Implement an AgX-style transform (inset primaries → log encode → sigmoid → outset), from the published maths, as the default neutral rendering. Parameters live in config.
10
+
11
+ ## Consequences
12
+ - + Film-like highlight roll-off, natural path to white, good base for every look.
13
+ - − We must implement and test it ourselves. Reference implementations (Blender, darktable, AgXc) go through a licence check before any code is ported. Default: implement from the maths.
14
+ - Alternative kept in reserve: OpenDRT.
@@ -0,0 +1,16 @@
1
+ # ADR-0004: YAML config is machine-owned; web UI is the human editor
2
+
3
+ **Status:** Accepted · 2026-10-11
4
+
5
+ ## Context
6
+ Config must be portable, reproducible and editable by coding agents. The user does not want to hand-edit YAML.
7
+
8
+ ## Decision
9
+ - Pydantic models are the source of truth; YAML is their serialisation; `picturesq schema` exports JSON Schema.
10
+ - Humans edit through the UI (`picturesq ui`); agents edit the file or use CLI commands.
11
+ - Writes are atomic (temp file + rename); the UI watches the file and reloads on external change.
12
+ - Comments in YAML are not preserved (nobody hand-writes them).
13
+
14
+ ## Consequences
15
+ - + Simple serializer; strict validation; agents get a schema.
16
+ - − Every user-tunable setting needs a UI control before humans can change it, so the UI scope must be managed.
@@ -0,0 +1,15 @@
1
+ # ADR-0005: No-build web UI with Python-rendered proxies
2
+
3
+ **Status:** Accepted · 2026-10-11
4
+
5
+ ## Context
6
+ We need a colour-accurate visual loop: photo × look grid and live controls, on an 8 GB CPU-only laptop, shipped via pip.
7
+
8
+ ## Decision
9
+ - Starlette + uvicorn server, htmx + small vanilla JS, no Node build. Assets ship in the wheel.
10
+ - On each control change, Python renders an ~800–1200 px proxy (target < 150 ms). There is one implementation of the image maths.
11
+ - Terminal previews (sixel/kitty) are view-only and point to the UI.
12
+
13
+ ## Consequences
14
+ - + Trivial install; previews are guaranteed to match exports (same code).
15
+ - − Not "buttery" like GPU editors. A WebGL path (with parity tests) is the documented upgrade if latency disappoints.
@@ -0,0 +1,19 @@
1
+ # ADR-0006: Binding glossary, British spelling, public repo
2
+
3
+ **Status:** Accepted · 2026-10-11
4
+
5
+ ## Context
6
+ Early docs mixed vocabulary (develop/decode, strength/intensity/amount, recipe/look, profile for three different things) and spelling (colour vs `color`). The repo was planned to stay private until v0.1.
7
+
8
+ ## Decision
9
+ - `docs/glossary.md` is binding for code, CLI, config, docs, issues and UI text.
10
+ - British spelling everywhere, identifiers included (`colour`, `normalise`, `grey`).
11
+ - Pipeline stages: **decode → normalise → grade → export**. A **look** is the thing applied in the grade stage; **intensity** is its strength; **adjustments** are the universal controls; **outputs** are export targets; "profile" means only ICC profile.
12
+ - Packages mirror the stages (`decode/`, `normalise/`, `grade/`, `export/`) around a shared `colour/` core. This also removes `picturesq.io`, which shadowed the stdlib name.
13
+ - The repo is public from day one (MIT), with `main` protected.
14
+
15
+ ## Consequences
16
+ - + One word per concept; agents and contributors can't drift.
17
+ - + Free branch protection and required checks on a public repo.
18
+ - − British spelling is unusual for a Python CLI, and contributors need the glossary rule spelled out (AGENTS.md does this).
19
+ - − Public repo: personal RAWs must never be committed (git-ignored; goldens from CC0 samples only).
@@ -0,0 +1,13 @@
1
+ # Architecture Decision Records
2
+
3
+ Short records of significant decisions. Format: Context → Decision → Consequences.
4
+ New ADRs get the next number; superseded ones are marked, not deleted.
5
+
6
+ | ADR | Title | Status |
7
+ |---|---|---|
8
+ | [0001](0001-rawpy-decoder-no-darktable.md) | rawpy as the only decoder; no darktable dependency | Accepted |
9
+ | [0002](0002-no-camera-conversion-lut.md) | No camera conversion LUT; use DNG colour matrices | Accepted |
10
+ | [0003](0003-agx-display-transform.md) | AgX-style display transform as the neutral base | Accepted |
11
+ | [0004](0004-yaml-machine-owned.md) | YAML config is machine-owned; web UI is the human editor | Accepted |
12
+ | [0005](0005-web-ui-no-build.md) | No-build web UI (Starlette + htmx) with Python-rendered proxies | Accepted |
13
+ | [0006](0006-glossary-and-public-repo.md) | Binding glossary, British spelling, public repo | Accepted |
@@ -0,0 +1,71 @@
1
+ # Glossary
2
+
3
+ **The single source of vocabulary for picturesq.** Code, CLI, config keys, docs, issues and UI labels use these words and only these words. If you need a new term, add it here in the same PR.
4
+
5
+ ## Language rules
6
+ - **British spelling everywhere**: *colour, normalise, grey, behaviour, licence* (noun) / *license* (verb). This applies to identifiers too: `colour/`, `normalise()`, `grey_point`.
7
+ Exceptions: third-party APIs we call (`rawpy ... output_color`), CSS properties, and quoted product names.
8
+ - Config keys and identifiers are `snake_case`; CLI flags are `--kebab-case`.
9
+ - Prefer the industry term when one exists (sources: ACES/OpenColorIO, darktable, Lightroom, Capture One).
10
+
11
+ ## Things
12
+ | Term | Meaning | Not to be called |
13
+ |---|---|---|
14
+ | **project** | A folder of RAW files plus its `picturesq.yaml`. The unit every command works on. | trip, batch, session, album |
15
+ | **photo** | One input RAW file and everything derived from it. Used in the UI, CLI output and config (`photos:`). | image (in user-facing text), shot, frame, picture |
16
+ | **image** | An in-memory pixel array (code only): `image: NDArray[float32]` of shape H×W×3. | photo (in code) |
17
+ | **original** | The RAW file on disk. Never written. | source, input file |
18
+ | **proxy** | A small cached scene-linear version of a photo (2048 px, float16) used for all interactive work. | thumbnail, preview (that's the rendered result) |
19
+ | **preview** | A display-ready render of a proxy, shown in the terminal or UI. | proxy, thumbnail |
20
+ | **contact sheet** | A grid of previews (photos × looks, or photos only). | gallery, mosaic |
21
+ | **look** | A named, reusable creative grade, e.g. *cold/nordic*. Defined by a **look file**. | style, preset, filter, recipe, profile |
22
+ | **look file** | YAML defining a look: metadata, **parameters**, and a list of **operations**, which may include a `lut` operation referencing a `.cube`. | recipe |
23
+ | **category** | A grouping of looks: `bw`, `cold`, `warm`, `cinematic`. | family, collection, group |
24
+ | **look library** | All available looks: built-in + user-installed. | catalogue, pack |
25
+ | **LUT** | A 3D lookup table file (`.cube`). Always used *through* a look file that declares its **input space**. | filter, profile |
26
+ | **operation (op)** | One step inside a look file, e.g. `contrast`, `split_tone`, `lut`. | module, node, tool, effect |
27
+ | **parameter** | A named, adjustable value a look exposes to the UI (e.g. *shadow tint*). | knob, setting, control |
28
+ | **intensity** | How strongly a look applies: 0 % = neutral render, 100 % = as designed, up to 150 %. The only word for this. | strength, amount, opacity, mix |
29
+ | **amount** | The magnitude parameter *inside* an operation (`contrast.amount`). Never used for looks. | strength |
30
+ | **adjustments** | The fixed set of global controls available for every look: exposure, warmth, tint, contrast, saturation, vibrance. | tweaks, fine-tune layer, basics |
31
+ | **override** | A per-photo value that replaces the project default (look, intensity, normalisation values). | exception, manual edit |
32
+ | **output** | A named export target in config, e.g. `web`, `archive` (size, quality, sharpening). | export profile, preset, recipe |
33
+ | **ICC profile** | The colour profile embedded in an exported file. The only use of "profile". | — |
34
+ | **stats** | Per-photo measurements (luminance percentiles, clipping, …), stored as JSON. | metrics, analysis |
35
+ | **flag** | A per-photo warning raised by a stage (e.g. `exposure_clamped`, `wb_disagreement`). | warning, alert, issue |
36
+ | **outlier** | A photo whose stats fall outside configured ranges; one kind of flag. | — |
37
+ | **reference set** | The user's local Pocket 4 RAWs (scenarios R01–R10) used for acceptance. | test images |
38
+ | **samples** | CC0 RAWs from raw.pixls.us used in CI. | fixtures (fixture = pytest) |
39
+ | **golden** | A stored expected output used by a regression test. | snapshot (that's for text/JSON) |
40
+
41
+ ## Pipeline
42
+ | Term | Meaning | Not to be called |
43
+ |---|---|---|
44
+ | **pipeline** | The full chain: decode → normalise → grade → export. | workflow, process |
45
+ | **stage** | One of the four pipeline steps below. | phase (that's project management), step |
46
+ | **decode** | Stage 1: original → scene-linear image in the working space (rawpy/LibRaw), plus proxy. | develop, import, convert, ingest |
47
+ | **normalise** | Stage 2: adaptive per-photo correction to a common baseline (exposure, WB check, chroma NR). | auto, correct, balance |
48
+ | **grade** | Stage 3: apply the photo's look + adjustments, then the display transform. The *act*; a look is the *thing*. | style, edit, look (as a verb) |
49
+ | **export** | Stage 4: encode, resize and write each enabled output with sidecar. | render, save |
50
+ | **render** | Producing pixels from a proxy or original through stages 2–3 (used by preview and export). | process |
51
+
52
+ ## Colour science
53
+ | Term | Meaning |
54
+ |---|---|
55
+ | **scene-linear** | Pixel values proportional to light in the scene. All maths before the display transform happens here. |
56
+ | **working space** | The colour space of scene-linear images inside picturesq: linear Rec.2020 primaries, float32. |
57
+ | **display transform** | Maps scene-linear to display range with highlight roll-off. Ours is AgX-style. (OCIO: *view transform*; ACES: *output transform*.) |
58
+ | **display-referred** | Values after the display transform (0–1, encoded for a screen). LUT operations usually run here. |
59
+ | **input space** | The colour space + transfer curve a LUT expects as input (e.g. `srgb`, `rec709`). |
60
+ | **transfer function** | The curve between linear and encoded values (sRGB curve, log). "Gamma" only in explanations. |
61
+ | **key** | Overall brightness character of a scene: `low_key`, `normal`, `high_key`. |
62
+ | **EV / stop** | A doubling or halving of light. `exposure_ev: +1` = ×2 in scene-linear. |
63
+ | **white balance (WB)** | Per-channel gains making neutral objects neutral; **as-shot** = camera's recorded values. |
64
+ | **chroma NR / luma NR** | Noise reduction on colour / brightness detail. |
65
+
66
+ ## Project management
67
+ | Term | Meaning |
68
+ |---|---|
69
+ | **milestone** | M0–M5 (and later). "Phase" is used only in the original brief. |
70
+ | **task** | One GitHub issue, 30–60 min, with acceptance criteria and a test plan. |
71
+ | **ADR** | Architecture decision record in `docs/decisions/`. |
@@ -0,0 +1,30 @@
1
+ # Learning log
2
+
3
+ Short entries: **concept**, a one-paragraph explanation, and **why it mattered in the code**.
4
+ Agents append here as phases introduce new concepts.
5
+
6
+ ---
7
+
8
+ ## 2026-10-11 · RAW files and decoding
9
+ A RAW file is not an image. It holds one brightness number per sensor pixel, where each pixel sits under a red, green or blue filter in a checkerboard (Bayer mosaic), plus metadata on how to interpret them. A **decoder** subtracts the sensor's black level, scales to the white level, **demosaics** (estimates the two missing colours per pixel from neighbours), applies white-balance multipliers, and converts the camera's colour primaries to a standard space using a **colour matrix**. The result is a linear RGB image.
10
+ **Why it mattered:** this is the only camera-specific step. After decoding, everything is camera-agnostic, which is why picturesq supports any LibRaw camera (ADR-0001).
11
+
12
+ ## 2026-10-11 · D-Log LUTs don't apply to DNG
13
+ DJI's "conversion LUTs" turn D-Log *video* into normal-looking Rec.709. D-Log is a log curve plus a wide gamut that the camera bakes into video files. A DNG still is linear sensor data with no log encoding. Applying a D-Log LUT to it means feeding the LUT inputs it was never designed for, and the colours and contrast come out wrong. A LUT only gives correct results when the input is in exactly the colour space and transfer curve it was built for.
14
+ **Why it mattered:** we dropped the camera-LUT stage entirely (ADR-0002), and every user `.cube` file must declare its input space.
15
+
16
+ ## 2026-10-11 · Linear vs display encoding
17
+ In **scene-linear** data, a value is proportional to light: double the light, double the number. Displays and JPEGs use a **non-linear encoding** (sRGB "gamma"), which spends more code values on shadows because human vision is more sensitive there. Maths that mimics physics (exposure, white balance, blending light) must happen in linear. Doing it on gamma-encoded values distorts contrast and colour, a classic mistake.
18
+ **Why it mattered:** the whole pipeline works in float32 linear and converts to sRGB only at export.
19
+
20
+ ## 2026-10-11 · Display transforms (tone mapping)
21
+ A sunny scene can span 15+ stops; a screen shows about 6–8. A **display transform** compresses scene-linear values into the 0–1 display range: it decides how highlights roll off, how contrast feels, and how bright saturated colours fade toward white. Naive per-channel curves skew hues (skies turn cyan, sunsets yellow). AgX and OpenDRT are open designs that avoid this.
22
+ **Why it mattered:** AgX is our neutral base (ADR-0003); every look builds on it.
23
+
24
+ ## 2026-10-11 · The memory maths of a 33 MP photo
25
+ 7680 × 4320 ≈ 33 M pixels. As float32 RGB that's 33M × 3 × 4 bytes ≈ **400 MB per array**. A numpy expression like `a * b + c` allocates temporaries, so a naive chain can peak at 1–2 GB per image. On an 8 GB laptop, memory, not CPU, limits how many photos run in parallel. Per-pixel ops on a 1000 px proxy (~0.5 MP) are ~60× cheaper, which is why interactive previews always use proxies.
26
+ **Why it mattered:** proxy-first design, in-place ops, memory-capped worker pool, and memory budget tests (project plan §5).
27
+
28
+ ## 2026-10-11 · Proxies
29
+ A **proxy** is a small (2048 px long edge), cached, **scene-linear** float16 copy of a photo; the term comes from video editing. It has ~14× fewer pixels than a 33 MP original, so operations take milliseconds. It's scene-linear rather than a JPEG thumbnail, so normalise and grade behave exactly as they will on the original: what you preview is what you export. Float16 halves storage versus float32 while still holding values above 1.0 (bright skies), which 8-bit can't. The cache key hashes the original, the decode config and the version, so a proxy is never stale. A **preview** is the display-ready render *of* a proxy.
30
+ **Why it mattered:** every interactive feature (contact sheets, the UI, normalise stats) uses proxies; only export touches originals. This is what makes an 8 GB, CPU-only laptop enough.