fix(gemini): self-verify repair for under-removed sparkles

After reverse-alpha, re-detect the sparkle; when one survives at or above the
registry fail line (conf >= 0.5) -- an alpha mismatch the per-image gain estimate
could not fully correct -- inpaint the footprint and keep that only when it lowers
the re-detect confidence. The footprint inpaint reconstructs the slot from its
darker surroundings, so it physically removes the bright sparkle; purely additive,
the common clean removal re-detects below 0.5 and is returned untouched.

Measured on the spaces visible-removal audit: gemini removal-audit failures drop
15 -> 11 (4 genuine rescues), doubao 65/65 and jimeng 11/11 unchanged, zero
regressions on the 468 already-clean removals.

An offset+scale alignment search was prototyped on the remaining 11 fails and
rejected: an audit "ceiling" suggested +4 more, but those were NCC-gaming -- the
lower-scoring placement left the sparkle as bright or brighter, just reshaping the
residual so the contrast-invariant shape-NCC scored lower (a5a9: first-pass slot
~76 at background level vs the "aligned win" ~164). A brightness sanity check
rejected every one, so it contributed nothing and was removed.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Victor Kuznetsov
2026-06-04 16:45:18 -07:00
co-authored by Claude Opus 4.8
parent 6d11c11b52
commit 2c0b174dfa
3 changed files with 101 additions and 2 deletions
+55
View File
@@ -346,6 +346,61 @@ class TestUnderSubtractionGain:
assert abs(float(footprint.mean()) - 80.0) < 20.0
class TestVerifyAndRepair:
"""Self-verify fallback: a sparkle that survives reverse-alpha is inpaint-repaired,
but only when that lowers the re-detect confidence (so it can never regress).
The detector NCC is degenerate on flat synthetic backgrounds, so the keep-best
control flow is driven through a stubbed ``detect_watermark`` rather than a real
re-detect (mirroring the reasoning in TestUnderSubtractionGain).
"""
@pytest.fixture(autouse=True)
def _setup_engine(self):
self.engine = GeminiEngine()
self.alpha = self.engine.get_interpolated_alpha(96)
self.pos = (200, 200)
def _stub_detect(self, confidences):
"""detect_watermark stub yielding the given confidences in order."""
seq = iter(confidences)
def fake(image, force_size=None):
return DetectionResult(detected=True, confidence=next(seq))
return fake
def _repair(self, result):
return self.engine._verify_and_repair(result, self.alpha, self.pos, WatermarkSize.LARGE)
def test_clean_removal_returned_untouched(self, monkeypatch):
"""Below the fallback threshold, the input is returned byte-identical."""
img = np.full((600, 600, 3), 90, dtype=np.uint8)
monkeypatch.setattr(self.engine, "detect_watermark", self._stub_detect([0.2]))
out = self._repair(img)
assert out is img # no copy, no inpaint
def test_keeps_footprint_inpaint_when_it_helps(self, monkeypatch):
"""A surviving sparkle is footprint-inpainted when that re-detects lower; the
footprint pixels change."""
img = np.full((600, 600, 3), 90, dtype=np.uint8)
# Bright residual block over the footprint so the inpaint visibly changes it.
img[self.pos[1] : self.pos[1] + 96, self.pos[0] : self.pos[0] + 96] = 240
# residual 0.7 (>= 0.5 triggers), candidate re-detects 0.2 (< residual -> keep).
monkeypatch.setattr(self.engine, "detect_watermark", self._stub_detect([0.7, 0.2]))
out = self._repair(img)
assert out is not img
assert not np.array_equal(out, img) # footprint was inpainted from surroundings
def test_repair_rejected_when_inpaint_does_not_help(self, monkeypatch):
"""When the inpaint does not lower the re-detect confidence, keep the original."""
img = np.full((600, 600, 3), 90, dtype=np.uint8)
# residual 0.7, candidate re-detects 0.75 (>= residual -> reject the inpaint).
monkeypatch.setattr(self.engine, "detect_watermark", self._stub_detect([0.7, 0.75]))
out = self._repair(img)
assert out is img
class TestSparkleFalsePositiveGate:
"""False-positive gate: a low-confidence shape match whose core is NOT brighter
than its surroundings (ornate/flat content, not a white sparkle overlay) is