ScanSlicer/AGENTS.md
dan 047ea60fa5 chore: fork setup — tidy ignores, untrack runtime state, add AGENTS.md
- Ignore .venv/, .posit/, and .scanslicer/ (runtime state)
- Untrack .scanslicer/ defaults/presets JSON files
- Add AGENTS.md project guide for LLM-assisted work
2026-09-11 20:23:00 +02:00

122 lines
5.5 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# ScanSlicer (fork) — Project Guide
Purpose of this file: give any LLM working in this repo enough context to be
effective without re-reading everything. Keep it current as the code changes.
## What this project is
ScanSlicer is a desktop Qt application (Python/PySide6) that detects multiple
photos in a flatbed scan image, lets the user correct each box manually, then
exports perspective-corrected crops. Inspired by
[PhotoSlicer](https://github.com/francescovannini/photoslicer) (Francesco
Vannini). Runs on Linux over Wayland or X11.
This repo is a **local fork** of
[macrodev8/ScanSlicer](https://github.com/macrodev8/ScanSlicer) (upstream owner
inactive). License: GNU GPL v3 (must be preserved). Attribution: keep upstream
credit and the "inspired by PhotoSlicer" note.
## Goals of this fork
1. Fix a mirrored-export orientation bug and add an explicit left/right
orientation workflow (see below). This is the primary patch.
2. Keep the app usable on the maintainer's own photo scans; afterwards only
minimal maintenance is expected.
3. Push to the maintainer's Forgejo instance at `code.dgerdesmann.de` (no
GitHub fork). Keep licensing, attribution, and repository description proper.
The maintainer is primarily an R user and new to Python/Git. Changes should be
small, self-checked, clean, and secure; do not assume the maintainer can vet
Python code.
## The orientation bug (primary work item)
- Exported slices can come out mirrored (left/right swapped).
- Manual workaround: drag-switch the two corners of the top edge so the red
edge (edge 0) becomes the true top.
- Problem: `squarize()` and `reset_rotation()` re-run `normalize_top_left()`,
which re-derives "top" from geometry and silently overwrites the manual fix.
Desired outcome:
1. A visual indication of left vs. right edges/corners (in addition to the
existing red top edge).
2. A "swap left/right" button for the active slice.
3. The corrected orientation must persist through `squarize()` /
`reset_rotation()`.
## Key implementation facts
- `PhotoSlice.bbox` is a 4×2 numpy array in the order
`[top-left, top-right, bottom-right, bottom-left]`.
- `PhotoSlice.normalize_top_left()` (in `slices.py`) picks a top-left corner and
resolves winding so edge 0→1 is the "most horizontal top" edge. This is the
source of the ambiguity.
- `PhotoSlice.squarize()` and `reset_rotation()` both call
`normalize_top_left()` at the end, discarding any manual orientation.
- `Autoslicer.save_slice()` (in `autoslicer.py`) builds the perspective
transform assuming the `[TL,TR,BR,BL]` order; mirroring arises upstream of
this from a wrongly "normalized" quad.
- Edge 0 is drawn red in `CanvasRenderMixin.paintEvent()` (`canvas_render.py`).
A persistent orientation flag on `PhotoSlice` (respected by
`normalize_top_left`/`squarize`/`reset_rotation`) plus a swap action is the
likely shape of the fix. Do not change the `[TL,TR,BR,BL]` contract of
`save_slice` without updating every consumer.
## Repository structure
```
scanslicer/ application source (package)
entry.py CLI parsing, Wayland/X11 backend selection
main.py QtScanSlicer main window, theme, app bootstrap
controller*.py UI action handlers (status, canvas, detection,
image I/O, params)
ui_builders*.py widget construction (controls, preview, shared)
canvas*.py interactive canvas (interaction/render/state mixins)
slices.py PhotoSlice geometry model + polys_iou
autoslicer.py detection/export engine wrapper (save_slice)
autoslicer_detection.py detection pipelines, contour→box extraction
autoslicer_feedback.py auto-tune profiles, scoring, feedback persistence
params.py parameter model, defaults, presets
packaging/appimage/ AppImage recipe + build scripts (preferred deploy)
Dockerfile container runtime image
docker-compose.yml GUI container wiring
pyproject.toml package metadata + dependencies (uv.lock pinned)
docs/ user-guide, architecture, parameters, autotune,
runtime-config, appimage, development
```
## Architecture in brief
- `QtScanSlicer` (`main.py`) is a `QMainWindow` composed from UI builder mixins
and controller mixins.
- `QtSlicingCanvas` (`canvas.py`) = interaction + render + state mixins; holds
the image, zoom/pan state, and the list of `PhotoSlice` objects.
- Background work (detection, auto-tune) runs on a single-worker `QThreadPool`;
results are delivered back to the UI via Qt signals.
- Export flow: `save_all()` (`controller_image_io.py`) loops over locked slices
and calls `Autoslicer.save_slice(bbox, path, params)` for each.
## Run / build
```bash
uv sync --frozen
uv run scanslicer /path/to/scans # or: python -m scanslicer
```
- AppImage (preferred): `bash packaging/appimage/build-appimage.sh`
- Docker: `docker compose up --build`
- Dependencies (from `pyproject.toml`): PySide6, opencv-python, numpy, Pillow.
- Python >= 3.10; dev env is Python 3.12 in `.venv`.
## Config / persistence
Runtime files live in `~/.config/scanslicer/` (or `$XDG_CONFIG_HOME/scanslicer/`)
by default: `defaults.json`, `presets.json`, `autotune-feedback.json`,
`status.log`. Paths are overridable via `SCANSLICER_*_PATH` env vars. In the
Docker setup these land in `./.scanslicer/` on the host.
## Notes
- No automated test suite exists; validate changes by running the app on sample scans (startup, detection, save, auto-tune).