mirror of
https://github.com/snapotter-hq/SnapOtter.git
synced 2026-08-03 07:46:42 +02:00
fix: repair docker validation QA tooling, dispatcher crash-accounting, and image-enhancement RAW hang (#391)
Found and fixed during a full local Docker build validation (amd64/arm64, all four fleet targets, AI bundle installs, QA harness) and the follow-up bug sweep requested afterward. None of the affected scripts run in CI, so these had been silently broken indefinitely. - docker/feature-manifest.json: pythonVersion was a flat "3.11", but the amd64 base (Ubuntu 24.04) ships Python 3.12 while arm64 (Debian bookworm) ships 3.11. Changed to a per-arch object matching the file's existing convention. - tests/qa/api-sweep.mts and verify-ai.mts: bare "@snapotter/shared" import can't resolve since tests/ is not a pnpm workspace member, making both silently unrunnable via their own documented command on any fresh checkout. Switched to a relative import. - tests/qa/generate-ledger.mts: wrote to docs/qa/ without creating the directory first; docs/ is gitignored except COMMUNITY_GUIDE.md, so a fresh checkout threw ENOENT. - Seven QA Playwright spec files (input-preview, settings, settings-extended, multifile, output-preview, pipeline-ui, smoke) had ~115 fixture() calls using directory names that don't exist. Resolved every call programmatically against the real fixture tree. - packages/ai/src/bridge.ts: AI dispatcher restart (happens on every bundle install) was falsely counted as a crash, risking permanent dispatcher disable after enough legitimate restarts within the crash window. Added a shuttingDown flag checked at all three recordCrash() call sites. - packages/image-engine/src/operations/auto-enhance.ts: image-enhancement hung 40+ seconds on large RAW photos (confirmed on a real 20.2MP file) in Sharp's .clahe() step, whose cost scales with total pixel count regardless of tile size. Added a 16-megapixel cap above which CLAHE is skipped; verified against the real file (40+s -> 2.0s) with no regression to other RAW formats or normal-sized images. Fixing this surfaced a second, smaller bug where the saturation step's CLAHE compensation boost was keyed off the raw toggle instead of whether CLAHE actually ran. - Two QA-harness robustness gaps closed per "fix everything, even the small bugs": the passport-photo/erase-object input-preview tests now skip cleanly with a clear reason on a container without their AI bundle installed, and docker-compose.qa.yml's hardcoded project/container name (the actual root cause of a mid-validation container swap between two concurrent sessions) is now parameterized via QA_PROJECT_NAME. Full validation report is local-only per repo convention.
This commit is contained in:
@@ -9,6 +9,12 @@ import type {
|
||||
SharpMetadata,
|
||||
} from "../types.js";
|
||||
|
||||
/**
|
||||
* Above this pixel count, CLAHE is skipped in applyCorrections() -- see the
|
||||
* comment at its call site for why.
|
||||
*/
|
||||
const MAX_CLAHE_PIXELS = 16_000_000;
|
||||
|
||||
/**
|
||||
* Preset multipliers applied to auto-computed corrections.
|
||||
* Each value scales the corresponding correction (1.0 = unchanged).
|
||||
@@ -223,17 +229,28 @@ export function applyCorrections(
|
||||
const scale = intensity / 50;
|
||||
|
||||
let result = image;
|
||||
// Tracks whether .clahe() actually ran (not just whether the toggle allowed
|
||||
// it) -- Step 5 below applies a compensation boost keyed off this, and it
|
||||
// needs to stay correct now that CLAHE can also be skipped by image size.
|
||||
let claheApplied = false;
|
||||
|
||||
// Step 1: CLAHE - adaptive local contrast enhancement
|
||||
// maxSlope must be an integer (Sharp requirement); skip for tiny images
|
||||
// maxSlope must be an integer (Sharp requirement); skip for tiny images.
|
||||
// CLAHE's cost scales with total pixel count regardless of tile size (tile
|
||||
// size only bounds granularity, not the per-pixel histogram/interpolation
|
||||
// work), so it's also skipped above MAX_CLAHE_PIXELS -- a 5504x3672 (20MP)
|
||||
// real-world RAW photo measured 40+ seconds in this step alone versus ~1s
|
||||
// for every other correction combined. The other six corrections below
|
||||
// still apply at full resolution regardless of size.
|
||||
if (toggles.contrast !== false) {
|
||||
const maxSlope = clamp(Math.round(1.0 + (intensity / 100) * 4.0 * presets.clahe), 1, 10);
|
||||
const w = imageSize?.width ?? 64;
|
||||
const h = imageSize?.height ?? 64;
|
||||
const tileW = clamp(Math.round(w / 8), 8, 256);
|
||||
const tileH = clamp(Math.round(h / 8), 8, 256);
|
||||
if (maxSlope >= 2 && w >= tileW && h >= tileH) {
|
||||
if (maxSlope >= 2 && w >= tileW && h >= tileH && w * h <= MAX_CLAHE_PIXELS) {
|
||||
result = result.clahe({ width: tileW, height: tileH, maxSlope });
|
||||
claheApplied = true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -272,7 +289,7 @@ export function applyCorrections(
|
||||
// Step 5: Saturation (with small CLAHE compensation boost)
|
||||
if (toggles.saturation !== false) {
|
||||
const adj = corrections.saturation * presets.saturation * scale;
|
||||
const claheCompensation = toggles.contrast !== false && intensity > 10 ? 0.05 : 0;
|
||||
const claheCompensation = claheApplied && intensity > 10 ? 0.05 : 0;
|
||||
const satMul = 1 + adj / 100 + claheCompensation;
|
||||
if (Math.abs(satMul - 1) > 0.02) {
|
||||
result = result.modulate({ saturation: clamp(satMul, 0.2, 3.0) });
|
||||
|
||||
Reference in New Issue
Block a user