# Maintaining ScanSlicer This file is a how-to for keeping the fork alive over time. The goals are narrow and deliberate: 1. **Keep the app functional** for its one real job (detect photos in a flatbed scan, correct the boxes, export straightened crops). 2. **Avoid security issues and breakage from end-of-life (EOL) components.** There is **no ambition to develop new features**. Maintenance is manual: when something drifts out of date, you bump a version, re-verify the app, and commit. Do not add features, refactor, or "improve" things while doing maintenance — small, focused changes only. > Scope guard: this repo is a local fork of `macrodev8/ScanSlicer`, published at > `https://code.dgerdesmann.de/dan/ScanSlicer.git`. License is GPL v3. Keep the > upstream credit and the "inspired by PhotoSlicer" note intact. See `README.md` > and `docs/` for project context. --- ## Where things live | Component | File(s) | |---|---| | Package metadata + dependency constraints | `pyproject.toml` | | **Pinned** dependency versions (source of truth) | `uv.lock` | | Runtime container image | `Dockerfile` (ubuntu:24.04 + uv) | | AppImage recipe + build scripts | `packaging/appimage/` | | Bundled third-party license notice | `THIRD_PARTY_NOTICES` | | Dev env (Python 3.12) | `.venv/` (managed by uv) | Dependency management is done with **uv**. `pyproject.toml` declares the requirements (mostly loose), and `uv.lock` pins the *exact* versions that get installed and shipped. `uv.lock` is committed — do not edit it by hand; let uv rewrite it. --- ## Code invariants (orientation fix + bbox order) This fork's one real code change is the mirrored-export orientation fix. Keep these invariants in mind when touching geometry code. - **`PhotoSlice.bbox` is a 4×2 numpy array in the order `[top-left, top-right, bottom-right, bottom-left]`.** `Autoslicer.save_slice()` (`autoslicer.py`) builds the perspective transform assuming that order. Do NOT change this contract without updating every consumer. - **`PhotoSlice.swapped`** (default `False`) is the persistent "user explicitly mirrored left/right" flag. `swap_left_right()` toggles it and reindexes corners `[TL,TR,BR,BL] -> [TR,TL,BL,BR]` (`bbox[[1,0,3,2]]`) — a pure left/right mirror that keeps the top and bottom edges in place. - **Root cause of the original bug:** `normalize_top_left()` (`slices.py`) re-derives "top" from geometry, which is ambiguous, and `squarize()` / `reset_rotation()` both re-run it at the end, silently overwriting manual corrections. The fix makes `normalize_top_left()` apply the same `[1,0,3,2]` reindex when `swapped` is true, so manual orientation survives. (Historical: an earlier `[0,3,2,1]` reindex was wrong — it rotated the quad 90°.) - **UI wiring:** "Swap L/R" button (`ui_builders_preview.py`) → `CanvasStateMixin.swap_active_slice_left_right()` (`canvas_state.py`) → `ScanSlicerCanvasActionsMixin.swap_active_box_left_right()` (`controller_canvas_actions.py`) → `PhotoSlice.swap_left_right()`. - **Visual color coding** (`canvas_render.py`): corners — left = orange, right = cyan; edges — red = top (0→1), green = right (1→2), orange = bottom (2→3), left edge (3→0) follows lock state (blue locked / lightgreen unlocked). --- ## Current pinned versions (snapshot) Update this table whenever you bump something, so the next session has a starting point. | Component | Pinned version | Where | |---|---|---| | PySide6 / PySide6-Addons / -Essentials / shiboken6 | 6.10.2 | `uv.lock` | | opencv-python-headless | 4.13.0.92 | `uv.lock` (constraint `~=4.13.0` in `pyproject.toml`) | | numpy | 2.2.6 (Py<3.11) / 2.4.2 (Py≥3.11) | `uv.lock` | | Python (dev env) | 3.12 | `.venv` | | Python floor | `>=3.10` | `pyproject.toml` | | uv (both Dockerfiles) | 0.10.2 | `Dockerfile`, `packaging/appimage/Dockerfile` | | appimage-builder | 1.1.0 | `packaging/appimage/Dockerfile` | | AppImage base OS | Ubuntu **noble** (24.04) | `packaging/appimage/AppImageBuilder.yml` | | Runtime image base | Ubuntu 24.04 | `Dockerfile` | --- ## What can go stale, and the priority order Review these roughly in this order — the first items are the real security/EOL surface; the last ones are packaging plumbing that only matters at build time. 1. **PySide6 / Qt** — the largest attack surface and the component with the most aggressive EOL cadence. Qt 6.x minor releases stop receiving updates quickly; PySide6 tracks Qt. Keep this on a recent 6.x. 2. **opencv-python-headless** — image processing; bump to the latest 4.x. 3. **numpy** — bump to the latest 2.x. 4. **Python** — the *shipped* runtime is what matters. The AppImage bundles its own Python (from Ubuntu noble, currently 3.12), so the app does not run on the host's Python. The `requires-python = ">=3.10"` floor only affects development resolution. Watch Python 3.10's EOL: when it goes EOL, raise the floor (e.g. to `>=3.11`) so no EOL interpreter is ever considered valid. 5. **Packaging toolchain** — `uv` and `appimage-builder` versions pinned in the two Dockerfiles. Only bump these when you have a reason (build breaks, or a notable security fix in the tool itself). 6. **Ubuntu base images** — `noble` is an LTS (supported to ~2029). Revisit the apt source and GPG key in `AppImageBuilder.yml` and the `FROM` line in both Dockerfiles when noble approaches EOL. Suggested cadence: check PyPI/upstream releases every ~3–6 months, or promptly when a security advisory is published for any of the above. --- ## Updating Python dependencies All commands run from the repo root. The dev env is `.venv` (Python 3.12). ### 1. Bring the environment up to the committed lock (baseline) ```bash uv sync --frozen ``` ### 2. Bump a single dependency to its latest ```bash uv lock --upgrade-package PySide6 uv lock --upgrade-package opencv-python-headless uv lock --upgrade-package numpy ``` `--dry-run` previews the change without touching anything: ```bash uv lock --upgrade-package PySide6 --dry-run ``` To bump *everything* at once (use sparingly — harder to bisect if something breaks): ```bash uv lock --upgrade ``` ### 3. Install the updated set and verify ```bash uv sync uv run scanslicer /path/to/test/scans ``` Then run the smoke-test checklist below. ### Special case: opencv major/minor bump `opencv-python-headless` is the **only** dependency with a constraint in `pyproject.toml` (`~=4.13.0`), which pins it to the 4.13.x line. To move to 4.14+, edit that constraint in `pyproject.toml` first, then run `uv lock --upgrade-package opencv-python-headless`. PySide6 and numpy are unpinned in `pyproject.toml`, so uv can move them freely. ### After any version change - Commit `pyproject.toml` (if edited) **and** `uv.lock` together. - Update the version table above. - Update `THIRD_PARTY_NOTICES` — it lists concrete PySide6/Qt, numpy, and opencv versions, and it ships inside every AppImage. Keep it aligned with `uv.lock`. - Rebuild and smoke-test the AppImage if the runtime stack (PySide6/Qt) changed. --- ## Updating the AppImage packaging toolchain The AppImage build pins its own tool versions in `packaging/appimage/Dockerfile`: - `uv` (`COPY --from=ghcr.io/astral-sh/uv:0.10.2 ...`) - `appimage-builder==1.1.0` (an `ARG` near the top) Bump either by editing that file. If you change `appimage-builder`, the `AppImageBuilder.yml` recipe may need adjusting for new versions (rare). The Ubuntu apt source and GPG key live in `AppImageBuilder.yml` — update those when noble goes EOL or the key rotates. > **Hardcoded Python paths:** `AppImageBuilder.yml` hardcodes `python3.12` in > `QT_PLUGIN_PATH` and `LD_LIBRARY_PATH`. If the AppImage base OS ever moves to > a different Python minor, those two lines must be updated to match. --- ## Updating the runtime Docker image `Dockerfile` (repo root) pins the base (`ubuntu:24.04`) and `uv 0.10.2`. Bump these when needed, then rebuild and run the container smoke test: ```bash docker compose up --build ``` --- ## Verifying a change (smoke-test checklist) There is **no automated test suite**. After any dependency or packaging change, manually confirm the app still works: 1. **Startup** on both Wayland and X11: ```bash uv run scanslicer --backend wayland uv run scanslicer --backend x11 ``` 2. **Detection** on a representative sample scan (boxes appear on load/Detect). 3. **Save flow** — lock a box, export, confirm the output is straight and not mirrored (this fork's orientation fix is the thing most likely to regress). 4. **Auto-tune** — run a profile and confirm feedback is written/read without error. 5. If the runtime stack changed: rebuild the AppImage and confirm it launches and saves. 6. If the Docker image changed: `docker compose up --build` and confirm the app opens. --- ## The Docker gotcha (recap — keep this in mind) The maintainer's Docker points at **Docker Desktop** (`desktop-linux` context). Its bind-mount filesystem forbids `chown`, but `appimage-builder` needs root ownership while extracting `.deb` packages, so the Dockerized AppImage build fails with: ``` dpkg-deb ... Cannot change ownership to uid 0, gid 0: Operation not permitted ``` Workaround: run the build against the **native** daemon instead: ```bash DOCKER_HOST=unix:///var/run/docker.sock bash packaging/appimage/build-appimage-docker.sh ``` (or `docker context use default` first). Output lands in `appimage/dist/ScanSlicer--x86_64.AppImage` (the `appimage/` dir is git-ignored). --- ## Commit / release hygiene - Commit `uv.lock` and `pyproject.toml` (and `THIRD_PARTY_NOTICES` when applicable) together as a single change. - Write a short, factual commit message, e.g. `Bump PySide6 to 6.10.x`. - Push to the Forgejo remote: `git push origin`. - The AppImage build verifies package hashes (`--require-hashes`) from `uv.lock`; publishing the AppImage alongside the matching committed source is already handled by committing before building. --- ## One-line summary Bump PySide6/Qt first and most often; bump opencv and numpy when convenient; treat uv/appimage-builder/Ubuntu as build plumbing you only touch when necessary. After every change, run the smoke-test checklist, rebuild the AppImage if the runtime stack moved, and commit `uv.lock` + `pyproject.toml` together.