Files
wildminder-ComfyUI-KEEP/tests/test_multiface_paste.py
T
WildAi 317f503c6d feat(keep): make Cancel work and restore every detected face
Two owner-reported bugs, both verified as gaps that never closed (the old
code on master had them too).

Cancel: the node never polled ComfyUI's interrupt flag, so the Cancel
button had no effect. A new src/keep_interrupt.py wraps
comfy.model_management.throw_exception_if_processing_interrupted() (lazy
import, a no-op outside ComfyUI); it is called at every long-loop
boundary - detection, crop, restore chunks, paste frames, and the paste
module's group/chunk loops. InterruptProcessingException is a
BaseException, so it escapes the pipeline untouched.

Multi-face: the pipeline collapsed to one face per frame - the detector
call passed only_keep_largest=True (the vendored selector reduces to the
largest face) and the processor then took all_landmarks_5[0]. With
only_center_face=False the new branches keep every detected face:
detection returns all faces, the crops/restore/paste flatten to the
ragged per-frame face count, the device canvas composites several ROIs
per row, and the vendored fallback hands the helper all of a frame's
faces. The near-black tail follows the real vendored reference (one
convert per image after a forward face loop). only_center_face=True and
the aligned path keep their bodies byte-for-byte.

44 new pins: cancel (the exception escapes the sequence and the node, no
work after it, unchanged output without a cancel), multi-face image and
video (all faces restored and pasted, faces appearing/disappearing,
None-affine and black-face drops, per-row near-black tail against the
vendored composite), and the paste core (face-to-frame map, row
compositing, chunk boundaries that never split a frame's faces).

Verified on the CPU device only - no model loads, no GPU runs. Gate: 505
passed, 6 skipped, 7 deselected, 0 failed (461 baseline + 44 new pins).
2026-10-28 18:11:25 +03:00

819 lines
31 KiB
Python

"""The multi-face paste core: several faces per canvas row (the t3 round).
The batched paste of ``src/keep_gpu_paste.py`` gains the ``face_frame``
map: the faces, the affines and the ROIs are **face-indexed**, the
background, the canvas and the output stay **frame-indexed**, and a row's
faces composite **sequentially** into a per-row float32 accumulator - the
vendored op order (``face_restoration_helper.py:495``) - with the
quantizing tail once per row (``:497-507``).
The pins:
1. the multi-face rows match the **vendored all-faces composite** - the
real ``FaceRestoreHelper`` with all of the row's faces and affines in
one ``paste_faces_to_input_image`` call (the reference of design
section 4) - on the smooth and the noise corpus, with overlapping and
with disjoint ROIs, at the upscale factors 1 and 2;
2. the 1-face degeneration is **bit-exact** against the shipped identity
path (``face_frame=None``), in the host, device-output and chunked
assemblies;
3. the device-output canvas and the chunked assembly are **bit-exact**
against the host stack for a ragged multi-face layout, across group
boundaries (a group never splits a frame's faces);
4. the near-black tail is per **row**: a fully near-black row takes the
full-canvas fallback with all its faces, a near-black face inside a
bright row does not;
5. the cancel poll runs once per group (the C1 contract of the new
loops);
6. the map is validated loudly (wrong length, out-of-range row, the
full-canvas chunk rejection, a ``num_frames`` mismatch).
"""
from __future__ import annotations
import importlib
import cv2
import numpy as np
import pytest
import torch
import torch.nn.functional as F
#: The vendored 5-point 512 template (``face_restoration_helper.py:52-53``).
FACE_TEMPLATE = np.array(
[[192.98138, 239.94708], [318.90277, 240.1936], [256.63416, 314.01935],
[201.26117, 371.41043], [313.08905, 371.15118]]
)
#: The canvas of the multi-face pins - big enough for two overlapping
#: faces, small enough for a fast test.
MULTI_CANVAS_HW = (360, 640)
@pytest.fixture(scope="module")
def gpu_paste():
return importlib.import_module("ComfyUI_KEEP.src.keep_gpu_paste")
@pytest.fixture(scope="module")
def gpu_ops():
return importlib.import_module("ComfyUI_KEEP.src.keep_gpu_ops")
@pytest.fixture(scope="module")
def helper_module():
return importlib.import_module("ComfyUI_KEEP.modules.deps.facelib.utils.face_restoration_helper")
@pytest.fixture(scope="module")
def image_utils():
return importlib.import_module("ComfyUI_KEEP.src.keep_image_utils")
@pytest.fixture()
def face_helper(helper_module, monkeypatch):
"""A vendored ``FaceRestoreHelper`` without its weight-backed models.
The detection / parsing initializations are stubbed (the weights are
not on this machine), so the reference paste runs the real vendored
code - the same fixture the e2e pins of
``tests/test_gpu_paste_equivalence.py`` run.
"""
monkeypatch.setattr(helper_module, "init_detection_model", lambda *a, **k: object())
monkeypatch.setattr(helper_module, "init_parsing_model", lambda *a, **k: object())
return helper_module.FaceRestoreHelper(
upscale_factor=1, face_size=512, crop_ratio=(1, 1),
det_model="retinaface_resnet50", save_ext="png", use_parse=False,
device="cpu", model_rootpath="unused",
)
class _StubParseNet:
"""A parse net with the vendored contract (``[0]`` are the logits).
A 1x1 convolution over the color channels: the class map is a function
of the pixels alone, so the forward is batch invariant by construction
- the vendored reference's per-face forwards and the batched paste's
chunked forwards see the same masks.
"""
def __init__(self, seed: int = 5) -> None:
generator = torch.Generator().manual_seed(seed)
self.weight = torch.randn(19, 3, 1, 1, generator=generator) * 0.35
def __call__(self, samples):
return (F.conv2d(samples, self.weight), None)
def _face_u8(corpus: str, seed: int) -> np.ndarray:
"""One 512x512 BGR face of a corpus (the smooth and the noise one)."""
rng = np.random.default_rng(seed)
if corpus == "noise":
return rng.integers(0, 256, size=(512, 512, 3), dtype=np.uint8)
small = rng.uniform(0.0, 255.0, size=(32, 32, 3)).astype(np.float32)
smooth = cv2.resize(small, (512, 512), interpolation=cv2.INTER_CUBIC)
return np.clip(smooth, 0, 255).astype(np.uint8)
def _multiface_corpus(
gpu_ops,
spec: list[list[tuple[float, float, float, str]]],
canvas_hw: tuple[int, int] = MULTI_CANVAS_HW,
face_seed: int = 311,
bg_seed: int = 313,
):
"""The faces, the crop affines, the map and the background of a layout.
``spec`` names, per canvas row, the faces of the row as
``(x-fraction, y-fraction, scale, corpus)``. The paste maps are
derived from landmarks through the vendored
``estimateAffinePartial2D`` (the e2e pattern of
``tests/test_gpu_paste_equivalence.py``), so the reference paste and
the batched paste ride the same affines: the crop affine goes to the
vendored helper (which inverts it itself) and its
``cv2.invertAffineTransform * upscale`` product to the batched paste.
"""
rng = np.random.default_rng(bg_seed)
height, width = canvas_hw
faces: list[np.ndarray] = []
landmarks: list[np.ndarray] = []
face_frame: list[int] = []
for row, descriptors in enumerate(spec):
for x_fraction, y_fraction, scale, corpus in descriptors:
index = len(faces)
faces.append(_face_u8(corpus, face_seed + 2 * index))
landmarks.append(
(FACE_TEMPLATE - 256.0) * scale
+ np.array([width * x_fraction, height * y_fraction])
)
face_frame.append(row)
affines = gpu_ops.estimate_affines(landmarks, FACE_TEMPLATE)
background = rng.integers(
0, 256, size=(len(spec), height, width, 3), dtype=np.uint8
)
return np.stack(faces), affines, face_frame, background
def _row_faces(face_frame: list[int]) -> dict[int, list[int]]:
"""The face indices of every canvas row, in face (detection) order."""
rows: dict[int, list[int]] = {}
for index, row in enumerate(face_frame):
rows.setdefault(row, []).append(index)
return rows
def _vendored_multi_paste(
helper,
frame_bgr: np.ndarray,
faces: list[np.ndarray],
crop_affines: list[np.ndarray],
upscale_factor: float,
parse_net,
background: np.ndarray | None = None,
) -> np.ndarray:
"""The vendored composite of one row's faces (``:355-528``).
The helper's own multi-face flow: all of the row's faces and affines
in **one** ``paste_faces_to_input_image`` call - the forward ``:495``
loop blends every face into the accumulated image and the ``:497-507``
tail runs once, over the final composite.
"""
helper.clean_all()
helper.use_parse = True
helper.face_parse = parse_net
helper.input_img = frame_bgr
helper.is_gray = False
helper.restored_faces = [face.copy() for face in faces]
helper.affine_matrices = [matrix.copy() for matrix in crop_affines]
helper.upscale_factor = upscale_factor
helper.get_inverse_affine(None)
height, width = frame_bgr.shape[:2]
target_h, target_w = int(height * upscale_factor), int(width * upscale_factor)
if background is None:
background = frame_bgr
if background.shape[0] != target_h or background.shape[1] != target_w:
background = cv2.resize(
background, (target_w, target_h), interpolation=cv2.INTER_LANCZOS4
)
return helper.paste_faces_to_input_image(
upsample_img=background, draw_box=False, face_upsampler=None
)
# ---------------------------------------------------------------------------
# The multi-face rows against the vendored all-faces composite
# ---------------------------------------------------------------------------
@pytest.mark.unit
def test_the_multi_face_rows_match_the_vendored_all_faces_composite(
gpu_paste, gpu_ops, face_helper
):
"""The multi-face rows vs the vendored all-faces composite: smooth content.
The reference is the real vendored ``paste_faces_to_input_image`` with
all of the row's faces and affines in one call. Row 0 carries two
**overlapping** faces - the case the sequential per-row accumulator
exists for: the second face has to blend against the first one's
result, like the vendored ``:495`` loop does - and row 2 two disjoint
ones; row 1 is the 1-face degeneration.
Measured on this content: **3/255** at worst (the disjoint pair of row
2), ~0.2% of the pixels differing at all - the two-warp residual of
the single-face pins, carried through the sequential blend - and the
1-face row **0/255** on this content.
"""
faces, affines, face_frame, background = _multiface_corpus(
gpu_ops,
[
[(0.38, 0.45, 0.28, "smooth"), (0.52, 0.45, 0.28, "smooth")],
[(0.5, 0.5, 0.25, "smooth")],
[(0.25, 0.35, 0.22, "smooth"), (0.75, 0.65, 0.22, "smooth")],
],
)
parse_net = _StubParseNet(seed=23)
inverses = gpu_ops.inverse_affines(affines, upscale_factor=1.0)
pasted = gpu_paste.paste_faces_batch(
faces_bgr=faces,
inverse_affines=inverses,
background_bgr=background,
device=torch.device("cpu"),
parse_net=parse_net,
face_frame=face_frame,
)
assert len(pasted) == 3
assert gpu_paste.PASTE_TRANSFER_STATS["frames"] == 3
for row, indices in _row_faces(face_frame).items():
reference = _vendored_multi_paste(
face_helper, background[row],
[faces[i] for i in indices], [affines[i] for i in indices],
1.0, parse_net,
)
diff = np.abs(reference.astype(int) - pasted[row].astype(int))
assert diff.max() <= 3, (
f"row {row}: the multi-face paste drifted {diff.max()}/255 from "
"the vendored all-faces composite"
)
@pytest.mark.unit
def test_the_multi_face_rows_match_the_vendored_composite_on_noise_content(
gpu_paste, gpu_ops, face_helper
):
"""The multi-face rows vs the vendored composite: adversarial content.
Per-pixel noise is the worst case for the fixed-point sampling (the
gradients reach 255 per pixel); the overlapping pair rides the same
bound the single-face noise pin measures.
Measured: **2/255** at worst, ~0.1% of the pixels differing at all -
below the single-face noise bound, the sequential blend propagating
the warp residual with a weight below one.
"""
faces, affines, face_frame, background = _multiface_corpus(
gpu_ops,
[
[(0.4, 0.42, 0.3, "noise"), (0.55, 0.55, 0.3, "noise")],
[(0.3, 0.6, 0.26, "noise"), (0.75, 0.3, 0.26, "noise")],
],
)
parse_net = _StubParseNet(seed=37)
inverses = gpu_ops.inverse_affines(affines, upscale_factor=1.0)
pasted = gpu_paste.paste_faces_batch(
faces_bgr=faces,
inverse_affines=inverses,
background_bgr=background,
device=torch.device("cpu"),
parse_net=parse_net,
face_frame=face_frame,
)
for row, indices in _row_faces(face_frame).items():
reference = _vendored_multi_paste(
face_helper, background[row],
[faces[i] for i in indices], [affines[i] for i in indices],
1.0, parse_net,
)
diff = np.abs(reference.astype(int) - pasted[row].astype(int))
assert diff.max() <= 2, (
f"row {row}: the multi-face paste drifted {diff.max()}/255 on noise"
)
@pytest.mark.unit
def test_the_multi_face_composite_matches_the_vendored_at_upscale_two(
gpu_paste, gpu_ops, face_helper
):
"""The multi-face paste at an upscale factor of two.
The inverse warps of the paste are scaled by the upscale factor (the
vendored ``get_inverse_affine``), so the canvas is the double one and
the background is the LANCZOS4 resize of the frames - the same
background the vendored reference receives.
"""
canvas_hw = (240, 320)
faces, affines, face_frame, frames = _multiface_corpus(
gpu_ops,
[
[(0.4, 0.45, 0.3, "smooth"), (0.62, 0.5, 0.3, "smooth")],
[(0.5, 0.5, 0.26, "smooth")],
],
canvas_hw=canvas_hw,
)
parse_net = _StubParseNet(seed=47)
background = gpu_paste.batched_bg_frames(frames, 2.0, None, torch.device("cpu"))
inverses = gpu_ops.inverse_affines(affines, upscale_factor=2.0)
pasted = gpu_paste.paste_faces_batch(
faces_bgr=faces,
inverse_affines=inverses,
background_bgr=background,
device=torch.device("cpu"),
parse_net=parse_net,
face_frame=face_frame,
)
assert background.shape == (2, 480, 640, 3), background.shape
for row, indices in _row_faces(face_frame).items():
reference = _vendored_multi_paste(
face_helper, frames[row],
[faces[i] for i in indices], [affines[i] for i in indices],
2.0, parse_net, background=background[row],
)
diff = np.abs(reference.astype(int) - pasted[row].astype(int))
assert diff.max() <= 2, (
f"row {row}: the upscaled multi-face paste drifted {diff.max()}/255"
)
# ---------------------------------------------------------------------------
# The bit-exact bookkeeping pins (CPU: every op is batch-invariant)
# ---------------------------------------------------------------------------
@pytest.mark.unit
def test_a_one_face_per_row_map_is_bit_identical_to_the_identity_path(
gpu_paste, gpu_ops
):
"""``face_frame=[0, 1, 2]`` on one-face rows == the shipped identity path.
The multi-face chunk has to degenerate to the single-face math
exactly: the union box of a 1-face row is the face's ROI, the
accumulator is its background patch and the tail decides on the same
row maximum. All three assemblies - the host stack, the
device-output canvas and the chunked IMAGE rows - come out identical
on the CPU device, where every op of the paste is batch-invariant
(measured, the design's D3).
"""
faces, affines, face_frame, background = _multiface_corpus(
gpu_ops,
[
[(0.4, 0.45, 0.26, "smooth")],
[(0.55, 0.5, 0.3, "noise")],
[(0.35, 0.55, 0.24, "smooth")],
],
)
assert face_frame == [0, 1, 2]
parse_net = _StubParseNet(seed=23)
inverses = gpu_ops.inverse_affines(affines, upscale_factor=1.0)
batch_kwargs = dict(
faces_bgr=faces,
inverse_affines=inverses,
background_bgr=background,
device=torch.device("cpu"),
parse_net=parse_net,
frame_chunk=2,
)
chunked_kwargs = dict(
upscale_factor=1.0,
bg_upscale_model=None,
canvas_hw=None,
)
identity = gpu_paste.paste_faces_batch(**batch_kwargs)
mapped = gpu_paste.paste_faces_batch(face_frame=face_frame, **batch_kwargs)
for row, (identity_frame, mapped_frame) in enumerate(zip(identity, mapped)):
assert np.array_equal(identity_frame, mapped_frame), (
f"row {row}: the mapped host stack drifted from the identity path"
)
identity_canvas = gpu_paste.paste_faces_batch(device_output=True, **batch_kwargs)
mapped_canvas = gpu_paste.paste_faces_batch(
device_output=True, face_frame=face_frame, **batch_kwargs
)
assert torch.equal(identity_canvas, mapped_canvas), (
"the mapped device canvas drifted from the identity path"
)
chunked_identity = gpu_paste.paste_faces_batch_chunked(
**chunked_kwargs, **batch_kwargs
)
chunked_mapped = gpu_paste.paste_faces_batch_chunked(
**chunked_kwargs, face_frame=face_frame, **batch_kwargs
)
assert torch.equal(chunked_identity, chunked_mapped), (
"the mapped chunked assembly drifted from the identity path"
)
@pytest.mark.unit
def test_the_device_output_canvas_is_bit_identical_to_the_host_stack_multi_face(
gpu_paste, gpu_ops
):
"""The device-output canvas is the multi-face host stack, byte for byte.
The device-output mode moves the assembly of the output stack onto the
device; with the map the scatter targets are the mapped rows. The
canvas has to equal the list of the host rows exactly - on every
pixel, for every row of the ragged layout - and the counters keep
their row semantics.
"""
faces, affines, face_frame, background = _multiface_corpus(
gpu_ops,
[
[(0.38, 0.45, 0.28, "smooth"), (0.52, 0.45, 0.28, "noise")],
[(0.5, 0.5, 0.25, "smooth")],
[(0.25, 0.35, 0.22, "noise"), (0.75, 0.65, 0.22, "smooth")],
],
)
parse_net = _StubParseNet(seed=131)
inverses = gpu_ops.inverse_affines(affines, upscale_factor=1.0)
kwargs = dict(
faces_bgr=faces,
inverse_affines=inverses,
background_bgr=background,
device=torch.device("cpu"),
parse_net=parse_net,
frame_chunk=2,
face_frame=face_frame,
)
host_rows = gpu_paste.paste_faces_batch(**kwargs)
device_canvas = gpu_paste.paste_faces_batch(device_output=True, **kwargs)
assert isinstance(device_canvas, torch.Tensor), type(device_canvas)
assert tuple(device_canvas.shape) == (3, *MULTI_CANVAS_HW, 3), device_canvas.shape
for row, host_frame in enumerate(host_rows):
assert np.array_equal(device_canvas[row].numpy(), host_frame), (
f"row {row}: the device-output canvas drifted from the host stack"
)
stats = gpu_paste.PASTE_TRANSFER_STATS
assert stats["h2d_bytes"] == 0 and stats["d2h_bytes"] == 0, stats
assert stats["roi_staged_bytes"] > 0, stats
assert stats["frames"] == 3 and stats["roi_frames"] == 3, stats
assert stats["fallback_frames"] == 0, stats
@pytest.mark.unit
def test_the_chunked_multi_face_assembly_matches_the_host_stack_and_the_full_mode(
gpu_paste, gpu_ops, image_utils
):
"""The chunked IMAGE rows == the host stack == the full device canvas.
The helper pastes in ``frame_chunk=1`` groups - one row per group,
with all of the row's faces - while the full mode runs the whole batch
and the host stack is the mapped list return. On the CPU device
every op of the paste is batch-invariant (measured), so all three
assemblies come out as the same float32 IMAGE batch, byte for byte.
"""
faces, affines, face_frame, background = _multiface_corpus(
gpu_ops,
[
[(0.38, 0.45, 0.28, "smooth"), (0.52, 0.45, 0.28, "smooth")],
[(0.5, 0.5, 0.25, "noise")],
[(0.25, 0.35, 0.22, "smooth"), (0.75, 0.65, 0.22, "noise")],
],
)
parse_net = _StubParseNet(seed=233)
inverses = gpu_ops.inverse_affines(affines, upscale_factor=1.0)
kwargs = dict(
faces_bgr=faces,
inverse_affines=inverses,
background_bgr=background,
device=torch.device("cpu"),
parse_net=parse_net,
face_frame=face_frame,
)
host_rows = gpu_paste.paste_faces_batch(frame_chunk=3, **kwargs)
full_canvas = gpu_paste.paste_faces_batch(
frame_chunk=3, device_output=True, **kwargs)
chunked = gpu_paste.paste_faces_batch_chunked(
upscale_factor=1.0,
bg_upscale_model=None,
canvas_hw=None,
frame_chunk=1,
**kwargs,
)
assert chunked.dtype == torch.float32 and chunked.device.type == "cpu"
assert tuple(chunked.shape) == (3, *MULTI_CANVAS_HW, 3), chunked.shape
assert torch.equal(
chunked, image_utils.bgr_u8_batch_to_comfy(np.stack(host_rows))
), "the chunked helper drifted from the host stack"
assert torch.equal(chunked, gpu_paste.comfy_convert_out(full_canvas)), (
"the chunked helper drifted from the full device mode"
)
stats = gpu_paste.PASTE_TRANSFER_STATS
assert stats["frames"] == 3, stats
@pytest.mark.unit
def test_the_chunked_multi_face_assembly_is_independent_of_the_group_boundaries(
gpu_paste, gpu_ops
):
"""Ragged face counts across every group boundary: the same IMAGE rows.
Four rows of 1, 3, 2 and 1 faces in groups of one and two rows - the
group boundaries never split a frame's faces (a group is a set of
whole rows), and the group size must not change a pixel: on the CPU
device the parse forward, the warps, the blend and the convert chain
are batch-invariant (measured), so every grouping comes out identical.
"""
faces, affines, face_frame, background = _multiface_corpus(
gpu_ops,
[
[(0.4, 0.45, 0.26, "smooth")],
[(0.35, 0.4, 0.26, "noise"), (0.55, 0.5, 0.26, "smooth"),
(0.72, 0.62, 0.26, "noise")],
[(0.3, 0.55, 0.24, "smooth"), (0.68, 0.4, 0.24, "noise")],
[(0.5, 0.5, 0.25, "smooth")],
],
)
assert [len(v) for v in _row_faces(face_frame).values()] == [1, 3, 2, 1]
parse_net = _StubParseNet(seed=241)
inverses = gpu_ops.inverse_affines(affines, upscale_factor=1.0)
kwargs = dict(
faces_bgr=faces,
inverse_affines=inverses,
background_bgr=background,
device=torch.device("cpu"),
parse_net=parse_net,
face_frame=face_frame,
)
full_canvas = gpu_paste.paste_faces_batch(
frame_chunk=4, device_output=True, **kwargs)
chunked_two = gpu_paste.paste_faces_batch_chunked(
upscale_factor=1.0, bg_upscale_model=None, canvas_hw=None,
frame_chunk=2, **kwargs)
chunked_one = gpu_paste.paste_faces_batch_chunked(
upscale_factor=1.0, bg_upscale_model=None, canvas_hw=None,
frame_chunk=1, **kwargs)
assert torch.equal(chunked_two, gpu_paste.comfy_convert_out(full_canvas)), (
"the two-row groups changed the pasted pixels"
)
assert torch.equal(chunked_one, chunked_two), (
"the one-row groups changed the pasted pixels"
)
# ---------------------------------------------------------------------------
# The per-row near-black tail
# ---------------------------------------------------------------------------
@pytest.mark.unit
def test_a_near_black_row_takes_the_full_canvas_fallback_with_all_its_faces(
gpu_paste, gpu_ops, face_helper
):
"""A row that is near black **everywhere** falls back with all faces.
The vendored tail scales the **whole canvas** by 255 when the blended
frame is near black (``:497-507``) - a row-global operation the ROI
cannot reproduce. With two faces on the row the fallback has to
re-blend **all** of them at the full canvas (the vendored ``:495``
order) before the tail. The pin holds the fallback row bit-identical
between the host and the device-output modes and bound-checked
against the vendored composite, which takes the same branch.
Measured against the vendored: **1/255** at worst, ~0.1% of the pixels
differing at all - the parse-mask drift, amplified once by the x255
tail.
"""
canvas_hw = MULTI_CANVAS_HW
rng = np.random.default_rng(277)
faces = np.stack([
rng.integers(0, 2, size=(512, 512, 3), dtype=np.uint8),
rng.integers(0, 2, size=(512, 512, 3), dtype=np.uint8),
])
landmarks = [
(FACE_TEMPLATE - 256.0) * 0.26 + np.array([230.0, 160.0]),
(FACE_TEMPLATE - 256.0) * 0.26 + np.array([400.0, 200.0]),
]
affines = gpu_ops.estimate_affines(landmarks, FACE_TEMPLATE)
background = rng.integers(0, 2, size=(1, *canvas_hw, 3), dtype=np.uint8)
parse_net = _StubParseNet(seed=281)
inverses = gpu_ops.inverse_affines(affines, upscale_factor=1.0)
kwargs = dict(
faces_bgr=faces,
inverse_affines=inverses,
background_bgr=background,
device=torch.device("cpu"),
parse_net=parse_net,
frame_chunk=1,
face_frame=[0, 0],
)
host_rows = gpu_paste.paste_faces_batch(**kwargs)
stats = gpu_paste.PASTE_TRANSFER_STATS
assert stats["fallback_frames"] == 1, stats
device_canvas = gpu_paste.paste_faces_batch(device_output=True, **kwargs)
stats = gpu_paste.PASTE_TRANSFER_STATS
assert stats["fallback_frames"] == 1 and stats["roi_frames"] == 1, stats
assert np.array_equal(
device_canvas[0].numpy(), host_rows[0]
), "the near-black fallback drifted between the host and the device modes"
reference = _vendored_multi_paste(
face_helper, background[0], list(faces), list(affines), 1.0, parse_net,
)
diff = np.abs(reference.astype(int) - host_rows[0].astype(int))
assert diff.max() <= 1, (
f"the near-black fallback drifted {diff.max()}/255 from the vendored tail"
)
@pytest.mark.unit
def test_a_near_black_face_in_a_bright_row_keeps_the_row_global_branch(
gpu_paste, gpu_ops, face_helper
):
"""Near-black faces do not drag a bright row into the fallback.
Row 0: two near-black faces whose union is near black while a bright
pixel far outside stays bright - the frame-global branch of the tail
(``maximum = max(union, outside)``), which must quantize with clip,
not scale by 255. Row 1: one smooth face next to a near-black one -
the tail is per **row**, so the bright face keeps the row in the
normal branch. Neither row may take the fallback, and both match the
vendored composite.
"""
canvas_hw = MULTI_CANVAS_HW
rng = np.random.default_rng(283)
height, width = canvas_hw
faces = np.stack([
rng.integers(0, 2, size=(512, 512, 3), dtype=np.uint8),
rng.integers(0, 2, size=(512, 512, 3), dtype=np.uint8),
_face_u8("smooth", 289),
rng.integers(0, 2, size=(512, 512, 3), dtype=np.uint8),
])
landmarks = [
(FACE_TEMPLATE - 256.0) * 0.24 + np.array([width * 0.3, height * 0.4]),
(FACE_TEMPLATE - 256.0) * 0.24 + np.array([width * 0.62, height * 0.45]),
(FACE_TEMPLATE - 256.0) * 0.26 + np.array([width * 0.35, height * 0.5]),
(FACE_TEMPLATE - 256.0) * 0.26 + np.array([width * 0.7, height * 0.55]),
]
affines = gpu_ops.estimate_affines(landmarks, FACE_TEMPLATE)
background = rng.integers(64, 256, size=(2, *canvas_hw, 3), dtype=np.uint8)
background[0, height - 1, width - 1] = 200 # the bright pixel far outside
parse_net = _StubParseNet(seed=293)
inverses = gpu_ops.inverse_affines(affines, upscale_factor=1.0)
pasted = gpu_paste.paste_faces_batch(
faces_bgr=faces,
inverse_affines=inverses,
background_bgr=background,
device=torch.device("cpu"),
parse_net=parse_net,
frame_chunk=2,
face_frame=[0, 0, 1, 1],
)
stats = gpu_paste.PASTE_TRANSFER_STATS
assert stats["fallback_frames"] == 0, stats
assert stats["roi_frames"] == 2, stats
rows = _row_faces([0, 0, 1, 1])
# measured: 3/255 on the near-black pair's row, 1/255 on the mixed row
bounds = {0: 3, 1: 1}
for row, indices in rows.items():
reference = _vendored_multi_paste(
face_helper, background[row],
[faces[i] for i in indices], [affines[i] for i in indices],
1.0, parse_net,
)
diff = np.abs(reference.astype(int) - pasted[row].astype(int))
assert diff.max() <= bounds[row], (
f"row {row}: the frame-global branch drifted {diff.max()}/255"
)
# ---------------------------------------------------------------------------
# The cancel polls of the new loops (the C1 contract)
# ---------------------------------------------------------------------------
class _Cancel(Exception):
"""The stub cancel of the poll pins."""
@pytest.mark.unit
def test_the_multi_face_groups_poll_the_cancel_flag(gpu_paste, gpu_ops, monkeypatch):
"""One cancel poll per group - the C1 contract of the new loops.
The poll is the module-qualified patch point (``src/keep_interrupt.py``):
a stub that raises on the second call must abort both the batched and
the chunked multi-face loops at the second group, and a counting no-op
must see exactly one call per group (3 rows in ``frame_chunk=1``
groups -> 3 polls).
"""
faces, affines, face_frame, background = _multiface_corpus(
gpu_ops,
[
[(0.4, 0.45, 0.26, "smooth")],
[(0.35, 0.4, 0.26, "smooth"), (0.6, 0.5, 0.26, "smooth")],
[(0.5, 0.55, 0.24, "smooth")],
],
)
parse_net = _StubParseNet(seed=307)
inverses = gpu_ops.inverse_affines(affines, upscale_factor=1.0)
batch_kwargs = dict(
faces_bgr=faces,
inverse_affines=inverses,
background_bgr=background,
device=torch.device("cpu"),
parse_net=parse_net,
frame_chunk=1,
)
calls: list[int] = []
def cancel_on_second():
calls.append(len(calls))
if len(calls) == 2:
raise _Cancel()
monkeypatch.setattr(gpu_paste.keep_interrupt, "check_interrupted", cancel_on_second)
with pytest.raises(_Cancel):
gpu_paste.paste_faces_batch(face_frame=face_frame, **batch_kwargs)
assert len(calls) == 2, calls
calls.clear()
with pytest.raises(_Cancel):
gpu_paste.paste_faces_batch_chunked(
upscale_factor=1.0,
bg_upscale_model=None,
canvas_hw=None,
face_frame=face_frame,
**batch_kwargs,
)
assert len(calls) == 2, calls
monkeypatch.setattr(
gpu_paste.keep_interrupt, "check_interrupted", lambda: calls.append(1)
)
calls.clear()
gpu_paste.paste_faces_batch(face_frame=face_frame, **batch_kwargs)
assert len(calls) == 3, calls
# ---------------------------------------------------------------------------
# The validation of the map
# ---------------------------------------------------------------------------
@pytest.mark.unit
def test_the_face_frame_map_is_validated_loudly(gpu_paste):
"""A broken map fails loudly instead of mis-scattering a face."""
faces = np.zeros((2, 512, 512, 3), dtype=np.uint8)
inverses = [np.eye(2, 3)] * 2
background = np.zeros((2, 64, 64, 3), dtype=np.uint8)
parse_net = _StubParseNet()
batch_kwargs = dict(
faces_bgr=faces,
inverse_affines=inverses,
background_bgr=background,
device=torch.device("cpu"),
parse_net=parse_net,
)
chunked_kwargs = dict(
upscale_factor=1.0,
bg_upscale_model=None,
device=torch.device("cpu"),
canvas_hw=None,
parse_net=parse_net,
)
with pytest.raises(ValueError): # the wrong length
gpu_paste.paste_faces_batch(face_frame=[0], **batch_kwargs)
with pytest.raises(ValueError): # a row outside the background stack
gpu_paste.paste_faces_batch(face_frame=[0, 5], **batch_kwargs)
with pytest.raises(ValueError): # the full-canvas chunk has no map
gpu_paste.paste_faces_batch(face_frame=[0, 1], use_roi=False, **batch_kwargs)
with pytest.raises(ValueError): # the row count does not match the stack
gpu_paste.paste_faces_batch(
face_frame=[0, 1], num_frames=3, **batch_kwargs)
with pytest.raises(ValueError): # the chunked helper: the wrong length
gpu_paste.paste_faces_batch_chunked(
faces_bgr=faces, inverse_affines=inverses, background_bgr=background,
face_frame=[0, 1, 0], **chunked_kwargs)
with pytest.raises(ValueError): # the chunked helper: a row outside
gpu_paste.paste_faces_batch_chunked(
faces_bgr=faces, inverse_affines=inverses, background_bgr=background,
face_frame=[0, 7], **chunked_kwargs)