aboutsummaryrefslogtreecommitdiffstats
path: root/AGENTS.md
blob: 51747241a5936fc116634ecc21330500a295cced (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
# AGENTS.md

Guidance for AI coding agents working in this repository.

## What this is

`upscale-qt`: single-file PyQt6 desktop app (`upscale-qt`, no `.py`
extension) to upscale an image with an ESRGAN model through `sd-cli`
(stable-diffusion.cpp), compare before/after with a slider wipe, then save
as a new file or overwrite the original. The model dropdown shows a
description per model from an optional `models.txt` in the upscalers
folder. The only Python dependency is PyQt6; everything else is stdlib plus
`sd-cli`. Keep it a single file and do not add dependencies.

On the maintainer's machine the script and menu entry are symlinks into
the repo, so edits are live on the next launch:

    ~/bin/upscale-qt -> upscale-qt
    ~/.local/share/applications/upscale-qt.desktop -> upscale-qt.desktop
    ~/.local/share/file-manager/actions/upscale-qt.desktop -> upscale-qt-action.desktop

The last one is the pcmanfm-qt right-click action (freedesktop
file-manager actions format, single selection only since the app opens one
image).

Design spec: `docs/superpowers/specs/2026-10-30-upscale-qt-design.md`.

## External tools

- Upscale: `sd-cli -M upscale --upscale-model <model> -i <in> -o <out.png>`.
  Models are `.pth`/`.safetensors`/`.gguf` files in the upscalers dir.
- `sd-cli` runs through `QProcess` so the UI never blocks.
- sd-cli (stb_image) ignores EXIF orientation. `load()` applies it with
  `QImageReader.setAutoTransform` and feeds sd-cli an upright temp PNG.
- `models.txt` lives with the models, not in the repo: `filename = what it
  is for`, `#` comments. Model names stay out of the repo on purpose, the
  personal-data hook flags at least one of them as a real name.
- `sd-cli` loads ESRGAN (RRDBNet) and MoSR models only, detected from the
  weights. SwinIR, HAT, DAT, SPAN, Compact and face restorers do not load.
- Model choice, measured on 4x crops of a clean portrait, a landscape and a
  noisy 2009 phone JPEG: the 4x foolhardy model is best on clean photos
  (people and nature), `RealESRGAN_x4plus` on noisy or compressed sources
  (smooth, slightly waxy skin), `RealESRGAN_x4plus_anime_6B` for anime and
  art only. Rejected after testing: 4xNomos2_hq_mosr (amplifies JPEG
  blocks), 4xNomos8kSC (artifacts on noisy input), 4xLSDIRplusR (plastic
  on clean input), 4xLSDIRplusC (close to the foolhardy model, not better).
- Timing: an RRDBNet model takes about 25 s per megapixel of input, so a
  10 MP photo takes about 4 minutes and outputs a 160 MP image.
- Model suggestion by a local vision model (imajev 4B via
  `llama-mtmd-cli`) was built and removed. Measured on four photos it
  picked the right model about half the time. It is a thinking model and
  `llama-mtmd-cli` has no switch to turn thinking off (no
  `--chat-template-kwargs`, `/no_think` ignored); with `--temp 0` it loops
  on `</think> <answer>` until `-n`, without `--jinja` it emits an empty
  answer. Do not bring it back without a better model or a server that
  can disable thinking.

## Conventions

- Images are held in a list of jobs internally even though the UI shows one
  at a time, so batch mode can be added without a rewrite.
- Non-GUI logic lives in module-level functions so it can be tested without
  a display.
- Overwrite always asks for confirmation and keeps the original's format.
- Placeholders only in code, docs, tests and commits.

## Testing

    python3 test_upscale_qt.py

Plain asserts for the helpers plus offscreen smoke tests of `CompareView`
and `Main`. The test sets `XDG_CONFIG_HOME` to a temp dir, so it never
touches the real settings. For an end-to-end check with the real
`sd-cli`, load the script with `SourceFileLoader` under
`QT_QPA_PLATFORM=offscreen`, call `load()` and `upscale()`, wait on
`proc.finished` with a `QEventLoop`, and `grab()` a screenshot. Use a small
image: about 25 s per input megapixel.

## Repo facts

- License: GPLv2 only (`LICENSE`, header in each source file).
- Work directly on master.