Verifying NegPy changes end-to-end
The surface is the PyQt6 GUI; the render pipeline's output is the canvas.
Recipe that works
- Sandbox app state so the run never touches
~/Documents/NegPy:NEGPY_USER_DIR=<scratch>/negpy_home(checked inget_default_user_dir). Create the subdirs from_bootstrap_environment(or call it) beforeStorageRepository.initialize(). - Suppress the first-run tutorial or every window grab is dimmed by the
overlay:
repo.save_global_setting("tutorial_seen", True)before constructingMainWindow. - Headless display:
xvfb-run -a -s "-screen 0 1920x1080x24" uv run python driver.py. wgpu/Vulkan works fine under Xvfb on this machine (real GPU). - Driver skeleton: build the app exactly like
negpy/desktop/main.py(StorageRepository→DesktopSessionManager→AppController→MainWindow), then drive a QTimer state machine:controller.load_file("samples/06.raw")— same method the Files sidebar calls.- advance steps on
controller.image_updated(skipstate.last_metrics["splash"], guard against multiple emissions per render, ~600 ms settle). - change edits like the sidebars do:
session.update_config(replace(state.config, ...))controller.request_render().
- pixel evidence:
window.canvas.grab()(canvas only, overlay-free) → numpy stats;window.grab().save(...)for reviewer screenshots. - exit with a nonzero code on failed assertions; 120 s timeout guard.
A working driver from the dodge/burn + toning verification lives in the
session scratchpad as drive_verify.py — copy its grab/set_cfg/state-machine
parts rather than rewriting.
Gotchas
- No Xvfb installed?
QT_QPA_PLATFORM=offscreenworks, but every widget grab (window.canvas.grab(),window.grab()) comes back black — take pixel evidence fromcontroller.state.last_metrics["base_positive"]instead; it's the exact buffer_on_image_updatedhands the canvas. Call.readback()on it when soft proof is off, where it is still aGPUTexture(the canvas hands those straight to the display shader; only the CPU overlay branch inImageCanvas.update_buffercopies to the host). wgpu compute runs fine offscreen. - A driver-side
controller.load_file(path)does not populatestate.uploaded_files, so_on_image_updatedearly-returns and the canvas never receives a buffer — another reason to probebase_positivedirectly. base_positiveis the post-soft-proof buffer when proofing is active (default export space supplies an output ICC) — good, that's what the user sees; but it means preview-path bugs like thebuffer_to_pilB&W luma collapse show up here and not in engine-level probes.samples/06.rawis a dark graveyard scene — burn/dodge probes need the sky band (top of frame in raw coords) to see roll-off; mid-frame is near black.- Mask vertices are raw-image normalized coords, not canvas coords; analyze
screenshots diff-based (
before - after > threshold) instead of guessing where a mask lands on the canvas. - Status bar process label may lag when configs are set programmatically — don't treat it as ground truth for the active mode.