mirror of
https://github.com/wiltodelta/remove-ai-watermarks.git
synced 2026-08-06 22:18:36 +02:00
136 lines
5.5 KiB
Python
136 lines
5.5 KiB
Python
"""Tests for the RunningHub ("RunningHub AI生成") visible-watermark engine.
|
|
|
|
Every tuned constant in ``runninghub_engine`` was measured on the 73-frame
|
|
vendor cohort (2026-07-22, ``scripts/vendor_cohort_harvest.py`` +
|
|
``scripts/vendor_mark_calibrate.py``); these tests pin the load-bearing ones:
|
|
the top-left corner, the gray front-end, the exact-size tight ladder, the
|
|
strict-only gate, and the mask/coverage parity regression (the partial-blob
|
|
"Runni" miss).
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import cv2
|
|
import numpy as np
|
|
import pytest
|
|
|
|
from remove_ai_watermarks import watermark_registry as registry
|
|
from remove_ai_watermarks.runninghub_engine import (
|
|
_ALPHA_HEIGHT_FRAC,
|
|
_ALPHA_WIDTH_FRAC,
|
|
RunningHubEngine,
|
|
_alpha_template,
|
|
)
|
|
|
|
_MARK_FRAC = 0.32 # measured mark width, fraction of the frame WIDTH
|
|
|
|
|
|
def _compose(w: int, h: int, mode: float = _MARK_FRAC, bg: float = 100.0):
|
|
"""Composite the RunningHub silhouette at the measured size, top-left."""
|
|
img = np.full((h, w, 3), bg, np.float32)
|
|
at = _alpha_template()
|
|
gw = int(mode * w)
|
|
gh = max(4, int(mode * (_ALPHA_HEIGHT_FRAC / _ALPHA_WIDTH_FRAC) * w))
|
|
ax, ay = int(0.008 * w), int(0.006 * h)
|
|
amap = np.zeros((h, w), np.float32)
|
|
amap[ay : ay + gh, ax : ax + gw] = cv2.resize(at, (gw, gh))
|
|
a3 = amap[:, :, None]
|
|
wm = (a3 * 255.0 + (1 - a3) * img).clip(0, 255).astype(np.uint8)
|
|
return wm, (ax, ay, gw, gh)
|
|
|
|
|
|
class TestLocate:
|
|
def test_box_anchored_top_left(self):
|
|
eng = RunningHubEngine()
|
|
img = np.zeros((2048, 1536, 3), np.uint8)
|
|
loc = eng.locate(img)
|
|
assert loc.x < 40 # hugs the left edge
|
|
assert loc.y < 40 # hugs the top edge (corner="tl")
|
|
|
|
def test_box_scales_with_width(self):
|
|
# scale_basis="width" (measured: mark width is 0.32 of the frame width).
|
|
eng = RunningHubEngine()
|
|
narrow = eng.locate(np.zeros((2048, 1024, 3), np.uint8))
|
|
wide = eng.locate(np.zeros((2048, 2048, 3), np.uint8))
|
|
assert wide.w == pytest.approx(narrow.w * 2, rel=0.05)
|
|
|
|
|
|
class TestConfig:
|
|
def test_gray_frontend(self):
|
|
# The mark is a faint mid-gray the top-hat suppresses to clean-arm levels;
|
|
# the raw-grayscale front-end is what separates (measured 2026-07-22).
|
|
assert RunningHubEngine().config.detect_frontend == "gray"
|
|
|
|
def test_tight_ladder(self):
|
|
# The NCC comb is razor-sharp in size (0.537 on-size, 0.223 at +5.6%), so
|
|
# the nominal sits exactly on the measured 0.32 with +-5% rungs.
|
|
assert RunningHubEngine().config.ladder == (0.95, 1.0, 1.05)
|
|
assert RunningHubEngine().config.alpha_width_frac == pytest.approx(0.32)
|
|
|
|
def test_strict_only_no_provenance_relaxation(self):
|
|
assert RunningHubEngine().config.provenance_ncc_factor == 1.0
|
|
|
|
def test_gate_above_clean_arm_max(self):
|
|
# Clean arm scored p99 0.273 / max 0.295 on 286 hand-labelled frames.
|
|
assert RunningHubEngine().config.detect_ncc_threshold > 0.295
|
|
|
|
def test_registry_row(self):
|
|
mark = registry.get_mark("runninghub")
|
|
assert mark.location == "top-left"
|
|
assert mark.in_auto
|
|
|
|
|
|
class TestDetectAndMask:
|
|
def test_detects_composed_mark(self):
|
|
eng = RunningHubEngine()
|
|
wm, _ = _compose(1080, 1620)
|
|
det = eng.detect(wm)
|
|
assert det.detected, f"composed mark missed (conf={det.confidence:.3f})"
|
|
|
|
def test_clean_frame_stays_quiet(self):
|
|
eng = RunningHubEngine()
|
|
img = np.full((1620, 1080, 3), 100, np.uint8)
|
|
assert not eng.detect(img).detected
|
|
|
|
def test_mask_covers_the_whole_mark(self):
|
|
"""Regression (2026-07-22): the binary blob under-segments the faint head
|
|
glyphs, so a blob-bbox mask left "Runni" unremoved. The gray front-end's
|
|
mask must come from the detector's own match box and cover the mark."""
|
|
eng = RunningHubEngine()
|
|
wm, (ax, ay, gw, gh) = _compose(1080, 1620)
|
|
mask = eng.footprint_mask(wm)
|
|
assert mask is not None
|
|
ys, xs = np.where(mask > 0)
|
|
assert xs.min() <= ax + int(0.05 * gw) # covers the LEFT edge of the mark
|
|
assert xs.max() >= ax + gw - int(0.05 * gw)
|
|
assert ys.min() <= ay + gh // 2 <= ys.max()
|
|
|
|
def test_no_mask_on_clean_frame(self):
|
|
eng = RunningHubEngine()
|
|
img = np.full((1620, 1080, 3), 100, np.uint8)
|
|
assert eng.footprint_mask(img) is None
|
|
|
|
def test_anchor_window_rejects_off_corner_match(self):
|
|
"""The raw-gray front-end false-fires on text-like structure ANYWHERE in
|
|
the box during compatibility testing; the
|
|
anchor window is what keeps it about THIS mark. A composed mark placed
|
|
off the measured corner anchor must NOT be detected."""
|
|
eng = RunningHubEngine()
|
|
wm, _ = _compose(1080, 1620)
|
|
det = eng.detect(wm)
|
|
assert det.detected # on-anchor control
|
|
# the same mark shifted right/down, off the anchor window
|
|
shifted = np.full((1620, 1080, 3), 100, np.uint8)
|
|
region = wm[10:60, 12:360]
|
|
shifted[100 : 100 + region.shape[0], 200 : 200 + region.shape[1]] = region
|
|
assert not eng.detect(shifted).detected
|
|
|
|
|
|
class TestPillInteraction:
|
|
def test_confident_runninghub_detection_suppresses_the_jimeng_pill(self):
|
|
# A RunningHub frame names its own product; its detection must veto the
|
|
# Jimeng pill the same way Doubao/Qwen/Kling do (``_keep_pill``).
|
|
from remove_ai_watermarks.watermark_registry import _keep_pill
|
|
|
|
assert not _keep_pill({"runninghub"}, provenance=frozenset({"jimeng"}), footprint_flat=1.0)
|