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

5.5 KiB
Raw Blame History

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

  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

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).