- Ignore .venv/, .posit/, and .scanslicer/ (runtime state) - Untrack .scanslicer/ defaults/presets JSON files - Add AGENTS.md project guide for LLM-assisted work
5.5 KiB
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 (Francesco Vannini). Runs on Linux over Wayland or X11.
This repo is a local fork of 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
- Fix a mirrored-export orientation bug and add an explicit left/right orientation workflow (see below). This is the primary patch.
- Keep the app usable on the maintainer's own photo scans; afterwards only minimal maintenance is expected.
- 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()andreset_rotation()re-runnormalize_top_left(), which re-derives "top" from geometry and silently overwrites the manual fix.
Desired outcome:
- A visual indication of left vs. right edges/corners (in addition to the existing red top edge).
- A "swap left/right" button for the active slice.
- The corrected orientation must persist through
squarize()/reset_rotation().
Key implementation facts
PhotoSlice.bboxis a 4×2 numpy array in the order[top-left, top-right, bottom-right, bottom-left].PhotoSlice.normalize_top_left()(inslices.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()andreset_rotation()both callnormalize_top_left()at the end, discarding any manual orientation.Autoslicer.save_slice()(inautoslicer.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 aQMainWindowcomposed from UI builder mixins and controller mixins.QtSlicingCanvas(canvas.py) = interaction + render + state mixins; holds the image, zoom/pan state, and the list ofPhotoSliceobjects.- 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 callsAutoslicer.save_slice(bbox, path, params)for each.
Run / build
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).