From 047ea60fa58a4d1c01c50ff3b261605563517238 Mon Sep 17 00:00:00 2001 From: dan Date: Fri, 11 Sep 2026 20:23:00 +0200 Subject: [PATCH] =?UTF-8?q?chore:=20fork=20setup=20=E2=80=94=20tidy=20igno?= =?UTF-8?q?res,=20untrack=20runtime=20state,=20add=20AGENTS.md?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Ignore .venv/, .posit/, and .scanslicer/ (runtime state) - Untrack .scanslicer/ defaults/presets JSON files - Add AGENTS.md project guide for LLM-assisted work --- .gitignore | 3 + .scanslicer/defaults.json | 23 ------- .scanslicer/presets.json | 1 - AGENTS.md | 122 ++++++++++++++++++++++++++++++++++++++ 4 files changed, 125 insertions(+), 24 deletions(-) delete mode 100644 .scanslicer/defaults.json delete mode 100644 .scanslicer/presets.json create mode 100644 AGENTS.md diff --git a/.gitignore b/.gitignore index 90cff83..e956ef0 100644 --- a/.gitignore +++ b/.gitignore @@ -3,6 +3,9 @@ /.idea/ /out/ /venv/ +.venv/ +.posit/ +.scanslicer/ /__pycache__/ /scanslicer/__pycache__/ /scanslicer.egg-info/ diff --git a/.scanslicer/defaults.json b/.scanslicer/defaults.json deleted file mode 100644 index 0b58dc8..0000000 --- a/.scanslicer/defaults.json +++ /dev/null @@ -1,23 +0,0 @@ -{ - "aspect_ratio_max": 600, - "aspect_ratio_min": 100, - "bbox_fill_thresh": 4, - "bbox_max_size": 50, - "bbox_min_size": 8, - "border_ignore": 0, - "bw_adaptive_c": 2, - "bw_gauss": 0, - "bw_method": 0, - "bw_thresh": 210, - "canny_high": 150, - "canny_low": 50, - "clahe_clip": 0, - "contour_approx_pct": 0, - "detect_pipeline": 0, - "dilate_kernel": 14, - "expected_count": 0, - "gaussian": 0, - "invert_mode": 0, - "iou_merge_thresh": 3, - "morph_op": 3 -} \ No newline at end of file diff --git a/.scanslicer/presets.json b/.scanslicer/presets.json deleted file mode 100644 index 9e26dfe..0000000 --- a/.scanslicer/presets.json +++ /dev/null @@ -1 +0,0 @@ -{} \ No newline at end of file diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000..4f2c201 --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,122 @@ +# 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).