Files
wildminder-ComfyUI-KEEP/tests/test_multiface_sequence.py
T
WildAi bae74a47e8 Round 19: the paste's last whole-clip transients are gone (measured attribution)
The owner's live profile on the 30 s clip (17 -> 32 -> ~52 GB) is now
attributed by measurement (a per-stage RAM sampler outside the tree:
psutil RSS/commit + StageBar marks, 128-frame CPU fixture, x6.07):

- the paste stage's gradual per-tick growth is the round-18 output
  buffer's lazy page commit (16 x 19.44 MB per 16-frame group) - the
  contract floor being paid, not a leak;
- the first stage's +15 GB is the caller-owned input IMAGE materializing
  into the working set as the detection loop reads it (dRSS +2.3 GB,
  dCommit +0 B on the raw-input run) - not pack-owned;
- the static audit found no whole-clip float32 copy of the input in the
  pack (the only advanced-indexing gather of the input is the
  max_clip_length-bounded :1132).

Two pack-owned O(N) items remained and are removed:

- FIX 1: the mixed-case faces np.stack gather (~611 MB owner-scale) is
  replaced by LazyFaceSource - the caller's face stack served per group
  (a view when contiguous, a group-sized copy when scattered) through a
  faces_provider seam mirroring frame_provider; the all-eligible
  pass-through gate keeps handing the raw stack.
- FIX 2: the fallback tail converts per gray group (_flush_fallback_group,
  the indexed write) - one group's stack + transient (<= 20 x 4.86 MB),
  independent of the fallback count, instead of a whole-fallback stack
  plus its ~10x conversion transient; the device-canvas and host tails
  keep their whole-stack form.

Gate: 6 failed, 580 passed, 16 skipped, 7 deselected - the 6 are the
owner's own asset/example edits, unchanged; 11 new pins (byte identity,
no-copy censuses, the cross-group boundary, the one-whole-clip-buffer
count).
2026-11-07 21:21:31 +03:00

1510 lines
64 KiB
Python

"""The multi-face sequence pipeline (C2, design sections 3.2-3.6).
``only_center_face=False`` on a non-aligned **sequence** restores every
detected face of every frame. The pins encode the facts the round
contracts:
1. the detector call keeps **all** faces (``only_keep_largest=False`` - the
second collapse site: the vendored selector reduces to the largest face
first, ``face_restoration_helper.py:247-255``, so the argument change is
the fix, PM risk #1) while the ``only_center_face=True`` toggle keeps
today's exact single-face sequence byte-for-byte;
2. the output of every frame is the **vendored all-faces composite** of that
frame's faces - a real vendored ``FaceRestoreHelper`` driven by hand over
the same landmarks and crops (the harness of
``tests/test_multiface_image.py``) - for the frames the batched paste
takes and for the vendored-fallback frames alike, with faces appearing
and disappearing across the clip;
3. the KEEP batch is **flattened over the faces** (``(1, total_faces, 3,
512, 512)``), chunked by ``max_clip_length`` faces with the
duplicate-to-2 trick for the 1-face chunk;
4. the guards: a ``None`` affine drops its face, an all-failed crop set
returns the input, zero faces in the whole clip return the input;
5. a frame with an ineligible face (gray here) goes **entirely** to the
vendored fallback with **all** its faces, and ``draw_box=True`` does the
same for every frame - one box per pasted face, the vendored tail;
6. the progress total counts the restore in faces
(``num_input_frames * 3 + total_faces``, PM risk #6);
7. the cancel polls fire in the four long loops and the k-th check aborts
the run with no KEEP work after it;
8. no temporal landmark smoothing runs in the multi-face branch.
The stubs follow the established patterns: the real vendored helper with
stubbed model inits (``tests/test_multiface_image.py:make_helper``), the
``_KeepNet`` call log and the per-frame detector the image-path pins use.
"""
from __future__ import annotations
import importlib
import importlib.util
import weakref
from pathlib import Path
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 frame size of the pins: the minimum side is 512, so the vendored
#: ``read_image`` does not upscale and the paste canvas is the frame size.
FRAME_HW = (512, 640)
#: The two faces of the recurring layout - an **overlapping** pair on the
#: content the sequential per-frame accumulator exists for.
FACE_A = (FACE_TEMPLATE - 256.0) * 0.3 + np.array([300.0, 240.0])
FACE_B = (FACE_TEMPLATE - 256.0) * 0.3 + np.array([308.0, 252.0])
#: The single face of the second frame layout.
FACE_C = (FACE_TEMPLATE - 256.0) * 0.3 + np.array([200.0, 380.0])
@pytest.fixture(scope="module")
def processor_module():
return importlib.import_module("ComfyUI_KEEP.src.keep_processor")
@pytest.fixture(scope="module")
def keep_interrupt():
return importlib.import_module("ComfyUI_KEEP.src.keep_interrupt")
@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 gpu_ops():
return importlib.import_module("ComfyUI_KEEP.src.keep_gpu_ops")
@pytest.fixture(scope="module")
def image_utils():
return importlib.import_module("ComfyUI_KEEP.src.keep_image_utils")
@pytest.fixture
def model_management():
"""The interrupt module (the mock of the gate env, the real one headless).
The flag is session-global (one module): every test starts and ends
with it cleared, so a cancelled run cannot leak into the next test.
"""
module = importlib.import_module("comfy.model_management")
module.interrupt_current_processing(False)
yield module
module.interrupt_current_processing(False)
# ---------------------------------------------------------------------------
# The stubs
# ---------------------------------------------------------------------------
class _StubParseNet:
"""A parse net with the vendored contract (``[0]`` are the logits).
A 1x1 convolution over the color channels: deterministic and identical
across instances of the same seed, so the processor's helper and the
reference's helper parse the faces with the very same mask.
"""
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)
class _KeepNet:
"""A KEEP net that returns its input (the restored face is the crop)."""
def __init__(self) -> None:
self.calls: list[tuple] = []
def __call__(self, frames, need_upscale=False):
self.calls.append((tuple(frames.shape), need_upscale))
return frames
class _SequenceDetector:
"""A face detector with per-frame face sets (the appearing clip).
The vendored detection scales the input to ``resize=640`` and divides
the bboxes back afterwards (``face_restoration_helper.py:210-229``) -
the rows here are pre-multiplied with that scale. Row layout:
``[x1, y1, x2, y2, score, lx1, ly1, ..., lx5, ly5]``.
``detect_faces`` is called once per ``get_face_landmarks_5`` and the
detection loop calls that once per frame, in frame order - the call
index is therefore the frame index.
"""
def __init__(self, per_frame_faces: list[list[np.ndarray]]) -> None:
scale = max(1, 640 / min(FRAME_HW))
self._per_frame: list[np.ndarray | None] = []
for faces in per_frame_faces:
rows = []
for landmarks in faces:
(x1, y1), (x2, y2) = landmarks.min(axis=0), landmarks.max(axis=0)
rows.append(np.concatenate(([x1, y1, x2, y2, 0.99], landmarks.reshape(-1))))
self._per_frame.append(np.stack(rows) * scale if rows else None)
self._index = 0
def detect_faces(self, image, confidence):
rows = self._per_frame[self._index]
self._index += 1
return rows
def _make_pack(keep_net, face_helper):
"""A model pack the processor can consume."""
return type(
"Pack",
(),
{
"keep_net": keep_net,
"face_helper": face_helper,
"bg_upscale_model": None,
"face_upscale_model": None,
"device": torch.device("cpu"),
"model_type_str": "KEEP",
},
)()
# ---------------------------------------------------------------------------
# The rig: a real vendored helper with stubbed model inits
# ---------------------------------------------------------------------------
@pytest.fixture
def make_helper(helper_module, monkeypatch):
"""A factory of real ``FaceRestoreHelper`` instances without weights.
The detection / parsing initializations are stubbed (the weights are
not on this machine), so every helper runs the real vendored code.
"""
monkeypatch.setattr(helper_module, "init_detection_model", lambda *a, **k: object())
monkeypatch.setattr(helper_module, "init_parsing_model", lambda *a, **k: object())
def _make(use_parse: bool = False):
return helper_module.FaceRestoreHelper(
upscale_factor=1, face_size=512, crop_ratio=(1, 1),
det_model="retinaface_resnet50", save_ext="png", use_parse=use_parse,
device="cpu", model_rootpath="unused",
)
return _make
def _smooth_frames(num_frames: int, seed: int = 41) -> np.ndarray:
"""A clip of smooth frames (the warp residual stays inside its bound)."""
rng = np.random.default_rng(seed)
frames = []
for _ in range(num_frames):
small = rng.uniform(0.0, 255.0, size=(32, 32, 3)).astype(np.float32)
smooth = cv2.resize(small, (FRAME_HW[1], FRAME_HW[0]), interpolation=cv2.INTER_CUBIC)
frames.append(np.clip(smooth, 0, 255).astype(np.uint8))
return np.stack(frames)
def _sequence_tensor(image_utils, frames_bgr: np.ndarray) -> torch.Tensor:
"""The BGR frames as the ComfyUI IMAGE tensor the node hands over."""
return image_utils.bgr_u8_batch_to_comfy(frames_bgr)
def _attach_detector(face_helper, detector):
face_helper.face_detector = detector
def _record_detection(face_helper) -> dict:
"""Wrap ``get_face_landmarks_5`` to record the call arguments."""
recorded: dict = {}
real = face_helper.get_face_landmarks_5
def recording(**kwargs):
recorded.update(kwargs)
return real(**kwargs)
face_helper.get_face_landmarks_5 = recording
return recorded
def _run_sequence(processor_module, keep_net, face_helper, frames, **kwargs):
"""One ``process_image_sequence`` run through the multi-face branch by default."""
processor = processor_module.KEEPFaceProcessor(_make_pack(keep_net, face_helper))
return processor.process_image_sequence(
image_sequence_tensor=frames,
final_upscale_factor=kwargs.pop("final_upscale_factor", 1.0),
has_aligned_frames=kwargs.pop("has_aligned_frames", False),
only_center_face=kwargs.pop("only_center_face", False),
draw_box=kwargs.pop("draw_box", False),
max_clip_length=kwargs.pop("max_clip_length", 20),
)
def _background(image: np.ndarray, upscale_factor: float) -> np.ndarray:
"""The paste background: the optional bg model (none here), then the size."""
target_h, target_w = int(image.shape[0] * upscale_factor), int(image.shape[1] * upscale_factor)
if image.shape[0] != target_h or image.shape[1] != target_w:
return cv2.resize(image, (target_w, target_h), interpolation=cv2.INTER_LANCZOS4)
return image
def _vendored_frame_reference(
ref_helper, frame_bgr, landmarks, upscale_factor: float = 1.0,
draw_box: bool = False,
) -> np.ndarray:
"""The vendored composite of one frame's faces (the pin's reference).
The helper's own multi-face flow, driven by hand: ``align_warp_face``
over the frame's landmarks, one restored face appended per crop (the
KEEP stub restores the crop - the uint8 round trip through
``img2tensor`` / ``tensor2img`` is bit-exact), one
``get_inverse_affine``, one ``paste_faces_to_input_image``. With an
empty landmark list the paste returns the background at the target size
- the composite of the frames without a face.
"""
ref_helper.clean_all()
ref_helper.upscale_factor = upscale_factor
ref_helper.read_image(frame_bgr)
ref_helper.all_landmarks_5 = [np.asarray(lm, dtype=np.float64) for lm in landmarks]
ref_helper.align_warp_face()
for face in ref_helper.cropped_faces:
ref_helper.add_restored_face(face.astype("uint8"))
ref_helper.get_inverse_affine(None)
return ref_helper.paste_faces_to_input_image(
upsample_img=_background(frame_bgr, upscale_factor),
draw_box=draw_box,
face_upsampler=None,
)
def _cancel_on_call(model_management, cancel_at: int):
"""A ``check_interrupted`` stand-in that raises on the ``cancel_at``-th call."""
counter = {"calls": 0}
def check_interrupted():
counter["calls"] += 1
if counter["calls"] >= cancel_at:
raise model_management.InterruptProcessingException()
return check_interrupted, counter
# ---------------------------------------------------------------------------
# Pin 1 - the detector call (the second collapse site, PM risk #1)
# ---------------------------------------------------------------------------
@pytest.mark.unit
def test_the_multiface_sequence_detector_call_keeps_all_faces(
processor_module, make_helper
):
"""``only_center_face=False`` detects with ``only_keep_largest=False``.
The argument change **is** the fix: behind ``only_keep_largest=True``
the vendored selector reduces every detection to the largest face
(``face_restoration_helper.py:247-255``) and no downstream indexing
could ever see the others. All three faces of the clip reach the KEEP
net as one flattened batch - the time axis is the faces, not the frames.
"""
image_utils = importlib.import_module("ComfyUI_KEEP.src.keep_image_utils")
frames = _sequence_tensor(image_utils, _smooth_frames(3))
frames_in = image_utils.comfy_batch_to_bgr_u8(frames)
face_helper = make_helper(use_parse=True)
face_helper.face_parse = _StubParseNet(seed=101)
_attach_detector(face_helper, _SequenceDetector([[FACE_A, FACE_B], [FACE_C], []]))
recorded = _record_detection(face_helper)
keep_net = _KeepNet()
_run_sequence(processor_module, keep_net, face_helper, frames)
assert recorded == {
"only_center_face": False, "resize": 640,
"eye_dist_threshold": 5, "only_keep_largest": False,
}, recorded
assert keep_net.calls == [((1, 3, 3, 512, 512), False)], (
f"the flattened batch is (1, total_faces, 3, 512, 512), got {keep_net.calls}"
)
assert frames_in.shape == (3, *FRAME_HW, 3)
@pytest.mark.unit
def test_the_toggle_keeps_the_single_face_sequence_path(processor_module, make_helper):
"""``only_center_face=True`` keeps today's collapsed detector call.
The toggle's contract is byte-for-byte today (design section 3.1): the
largest face wins at the detector and one face per **frame** reaches the
net - no flattening, and the multi-face branch is not entered.
"""
image_utils = importlib.import_module("ComfyUI_KEEP.src.keep_image_utils")
frames = _sequence_tensor(image_utils, _smooth_frames(3))
layout = [[FACE_A, FACE_B], [FACE_A, FACE_B], [FACE_A, FACE_B]]
toggle_helper = make_helper(use_parse=True)
toggle_helper.face_parse = _StubParseNet(seed=103)
_attach_detector(toggle_helper, _SequenceDetector(layout))
recorded = _record_detection(toggle_helper)
toggle_net = _KeepNet()
toggle = _run_sequence(processor_module, toggle_net, toggle_helper, frames,
only_center_face=True)
assert recorded == {
"only_center_face": True, "resize": 640,
"eye_dist_threshold": 5, "only_keep_largest": True,
}, recorded
assert toggle_net.calls == [((1, 3, 3, 512, 512), False)], toggle_net.calls
multi_helper = make_helper(use_parse=True)
multi_helper.face_parse = _StubParseNet(seed=103)
_attach_detector(multi_helper, _SequenceDetector(layout))
multi = _run_sequence(processor_module, _KeepNet(), multi_helper, frames)
assert not torch.equal(multi, toggle), (
"the multi-face run must restore more than the single-face toggle"
)
# ---------------------------------------------------------------------------
# Pin 2 - the end-to-end output is the vendored all-faces composite
# ---------------------------------------------------------------------------
@pytest.mark.unit
def test_the_multiface_sequence_output_is_the_vendored_all_faces_composite(
processor_module, make_helper, image_utils
):
"""Faces appearing and disappearing across the clip, vendored composite.
Frame 0 carries two **overlapping** faces - the sequential per-frame
accumulator the t3 paste core exists for - frame 1 one face and frame 2
none. Every frame comes back as the vendored all-faces composite of
that frame's faces: the batched frames within the t3 residual bound
(3/255 on smooth content) and the no-face frame bit-exact (the vendored
paste of zero faces returns the background at the target size).
"""
frames_bgr = _smooth_frames(3)
frames = _sequence_tensor(image_utils, frames_bgr)
frames_in = image_utils.comfy_batch_to_bgr_u8(frames)
face_helper = make_helper(use_parse=True)
face_helper.face_parse = _StubParseNet(seed=107)
_attach_detector(face_helper, _SequenceDetector([[FACE_A, FACE_B], [FACE_C], []]))
output = _run_sequence(processor_module, _KeepNet(), face_helper, frames)
assert tuple(output.shape) == (3, *FRAME_HW, 3), output.shape
ref_helper = make_helper(use_parse=True)
ref_helper.face_parse = _StubParseNet(seed=107)
references = [
_vendored_frame_reference(ref_helper, frames_in[0], [FACE_A, FACE_B]),
_vendored_frame_reference(ref_helper, frames_in[1], [FACE_C]),
_vendored_frame_reference(ref_helper, frames_in[2], []),
]
expected = image_utils.bgr_u8_batch_to_comfy(np.stack(references))
for row in (0, 1):
drift = (output[row] - expected[row]).abs().max().item()
assert drift <= 3.0 / 255.0 + 1e-6, (
f"frame {row}: the multi-face sequence drifted {drift * 255:.2f}/255 "
"from the vendored all-faces composite"
)
assert torch.equal(output[2], expected[2]), (
"the frame without a face must come back as the background, bit-exact"
)
# ---------------------------------------------------------------------------
# Pin 3 - the flattened KEEP batch
# ---------------------------------------------------------------------------
@pytest.mark.unit
def test_the_flattened_keep_batch_chunks_over_faces(processor_module, make_helper, image_utils):
"""The restore chunks over **faces**, the 1-face chunk duplicated to 2.
Four faces (two per frame) with ``max_clip_length=2`` are two chunks of
two faces; three faces with the same clip size are one chunk of two and
one **single-face** chunk - the duplicate-to-2 trick of the KEEP
contract, now in face units; without the chunk cap the whole clip is one
flattened batch.
"""
frames = _sequence_tensor(image_utils, _smooth_frames(2))
def run(faces_per_frame, max_clip_length):
keep_net = _KeepNet()
face_helper = make_helper(use_parse=True)
face_helper.face_parse = _StubParseNet(seed=109)
_attach_detector(face_helper, _SequenceDetector(faces_per_frame))
_run_sequence(processor_module, keep_net, face_helper, frames,
max_clip_length=max_clip_length)
return keep_net.calls
assert run([[FACE_A, FACE_B], [FACE_A, FACE_B]], 20) == [
((1, 4, 3, 512, 512), False)], "the whole clip is one flattened face batch"
assert run([[FACE_A, FACE_B], [FACE_A, FACE_B]], 2) == [
((1, 2, 3, 512, 512), False), ((1, 2, 3, 512, 512), False)], (
"the chunk boundaries are counted in faces, not frames"
)
assert run([[FACE_A, FACE_B], [FACE_C, FACE_C]], 2) == [
((1, 2, 3, 512, 512), False), ((1, 2, 3, 512, 512), False)]
# three faces, clip of two: the second chunk is the duplicated 1-face one
assert run([[FACE_A, FACE_B], [FACE_C], []], 2) == [
((1, 2, 3, 512, 512), False), ((1, 2, 3, 512, 512), False)], (
"the 1-face chunk keeps the duplicate-to-2 trick"
)
# ---------------------------------------------------------------------------
# Pin 4 - the guards
# ---------------------------------------------------------------------------
@pytest.mark.unit
def test_a_none_affine_drops_its_face_from_the_sequence(
processor_module, make_helper, image_utils, monkeypatch
):
"""A ``None`` affine (the LMEDS failure) drops the face before the crop.
The failure is forced deterministically for the NaN-marked landmarks (a
real LMEDS failure mode - the detector can emit them, and the
eye-distance gate cannot filter a NaN). The dropped face never reaches
the batch and the surviving faces keep the vendored composite.
"""
frames_bgr = _smooth_frames(2)
frames = _sequence_tensor(image_utils, frames_bgr)
frames_in = image_utils.comfy_batch_to_bgr_u8(frames)
broken = FACE_B.copy()
broken[4] = np.nan # the LMEDS-killer marker (the eye points stay finite)
real_estimate = cv2.estimateAffinePartial2D
def estimate(src, dst, *args, **kwargs):
if not np.all(np.isfinite(src)):
return (None, None)
return real_estimate(src, dst, *args, **kwargs)
monkeypatch.setattr(cv2, "estimateAffinePartial2D", estimate)
face_helper = make_helper(use_parse=True)
face_helper.face_parse = _StubParseNet(seed=113)
_attach_detector(face_helper, _SequenceDetector([[FACE_A, broken], [FACE_C]]))
keep_net = _KeepNet()
output = _run_sequence(processor_module, keep_net, face_helper, frames)
assert keep_net.calls == [((1, 2, 3, 512, 512), False)], (
f"only the two surviving faces reach the net, got {keep_net.calls}"
)
ref_helper = make_helper(use_parse=True)
ref_helper.face_parse = _StubParseNet(seed=113)
expected = image_utils.bgr_u8_batch_to_comfy(np.stack([
_vendored_frame_reference(ref_helper, frames_in[0], [FACE_A]),
_vendored_frame_reference(ref_helper, frames_in[1], [FACE_C]),
]))
drift = (output - expected).abs().max().item()
assert drift <= 3.0 / 255.0 + 1e-6, (
f"the dropped face must not shift the pairing, drifted {drift * 255:.2f}/255"
)
@pytest.mark.unit
def test_zero_faces_in_the_clip_return_the_input_tensor(
processor_module, make_helper, image_utils
):
"""No face anywhere in the clip - the input tensor comes back (design 3.2)."""
frames = _sequence_tensor(image_utils, _smooth_frames(3))
face_helper = make_helper(use_parse=True)
face_helper.face_parse = _StubParseNet()
_attach_detector(face_helper, _SequenceDetector([[], [], []]))
keep_net = _KeepNet()
output = _run_sequence(processor_module, keep_net, face_helper, frames)
assert output is frames, "the whole-clip zero-face guard returns the input"
assert keep_net.calls == [], keep_net.calls
@pytest.mark.unit
def test_an_all_failed_crop_set_returns_the_input_tensor(
processor_module, make_helper, image_utils
):
"""Every affine failed - there is nothing to crop, restore or paste."""
frames = _sequence_tensor(image_utils, _smooth_frames(2))
broken = FACE_A.copy()
broken[4] = np.nan
face_helper = make_helper(use_parse=True)
face_helper.face_parse = _StubParseNet()
_attach_detector(face_helper, _SequenceDetector([[broken], [broken]]))
keep_net = _KeepNet()
monkey = cv2.estimateAffinePartial2D
def estimate(src, dst, *args, **kwargs):
if not np.all(np.isfinite(src)):
return (None, None)
return monkey(src, dst, *args, **kwargs)
cv2.estimateAffinePartial2D = estimate
try:
output = _run_sequence(processor_module, keep_net, face_helper, frames)
finally:
cv2.estimateAffinePartial2D = monkey
assert output is frames, "the all-failed crop set returns the input unchanged"
assert keep_net.calls == [], keep_net.calls
# ---------------------------------------------------------------------------
# Pin 5 - the ineligible frame goes entirely to the vendored fallback
# ---------------------------------------------------------------------------
@pytest.mark.unit
def test_a_gray_frame_rides_the_batched_core_with_all_its_faces(
processor_module, make_helper, image_utils
):
"""A grayscale frame rides the batched core (round 10, the owner's fix 4).
The gray skip of the multiface eligibility is gone (the owner's fix 4,
2026-10-31, matching the sibling's fix 1.2): the gray frame is
device-batched with its two faces like a color frame - no vendored
``paste_faces_to_input_image`` call happens at all - and both frames'
outputs stay the vendored all-faces composite within the batched-crop
warp residual (measured **0/255** on the gray frame, **1/255** on the
color one, the documented 3/255 class bound). The measured behavior
replacing the old
``test_a_gray_frame_goes_to_the_vendored_fallback_with_all_its_faces``
pin (which routed the gray frame to the vendored paste).
"""
gray = np.full((*FRAME_HW, 3), 102, dtype=np.uint8)
frames_bgr = np.stack([gray, _smooth_frames(1)[0]])
frames = _sequence_tensor(image_utils, frames_bgr)
frames_in = image_utils.comfy_batch_to_bgr_u8(frames)
face_helper = make_helper(use_parse=True)
face_helper.face_parse = _StubParseNet(seed=127)
_attach_detector(face_helper, _SequenceDetector([[FACE_A, FACE_B], [FACE_A, FACE_B]]))
paste_calls: list[int] = []
real_paste = face_helper.paste_faces_to_input_image
def recording_paste(*args, **kwargs):
paste_calls.append(len(face_helper.restored_faces))
return real_paste(*args, **kwargs)
face_helper.paste_faces_to_input_image = recording_paste
output = _run_sequence(processor_module, _KeepNet(), face_helper, frames)
assert paste_calls == [], (
f"the gray frame rides the batched core like a color frame: {paste_calls}"
)
ref_helper = make_helper(use_parse=True)
ref_helper.face_parse = _StubParseNet(seed=127)
expected = image_utils.bgr_u8_batch_to_comfy(np.stack([
_vendored_frame_reference(ref_helper, frames_in[0], [FACE_A, FACE_B]),
_vendored_frame_reference(ref_helper, frames_in[1], [FACE_A, FACE_B]),
]))
for row in range(2):
drift = (output[row] - expected[row]).abs().max().item()
assert drift <= 3.0 / 255.0 + 1e-6, (
f"frame {row} drifted {drift * 255:.2f}/255 from the vendored composite"
)
@pytest.mark.unit
def test_draw_box_rides_the_batched_core_and_the_host_draws_no_box(
processor_module, make_helper, image_utils
):
"""``draw_box=True`` rides the batched core (round 10, the owner's fix 4).
The global draw_box gate of the batched paste is gone (the owner's
fix 4, 2026-10-31): the boxes are drawn on the device-output canvases
(:meth:`_draw_boxes_on_canvas`), not through the vendored masks. On
the CPU device the host branch draws none - the fix's disclosed
limitation - so the run's output is **bit-identical** to the
``draw_box=False`` run and no vendored paste call happens (the
measured behavior replacing the old
``test_draw_box_pastes_every_frame_through_the_vendored_fallback``
pin, which compared the boxed vendored composites).
"""
frames_bgr = _smooth_frames(3)
frames = _sequence_tensor(image_utils, frames_bgr)
face_helper = make_helper(use_parse=True)
face_helper.face_parse = _StubParseNet(seed=131)
_attach_detector(face_helper, _SequenceDetector([[FACE_A, FACE_B], [FACE_C], []]))
keep_net = _KeepNet()
paste_calls: list[int] = []
real_paste = face_helper.paste_faces_to_input_image
def recording_paste(*args, **kwargs):
paste_calls.append(len(face_helper.restored_faces))
return real_paste(*args, **kwargs)
face_helper.paste_faces_to_input_image = recording_paste
output = _run_sequence(processor_module, keep_net, face_helper, frames, draw_box=True)
assert keep_net.calls == [((1, 3, 3, 512, 512), False)], keep_net.calls
assert paste_calls == [], (
f"draw_box no longer routes to the vendored paste: {paste_calls}"
)
plain_helper = make_helper(use_parse=True)
plain_helper.face_parse = _StubParseNet(seed=131)
_attach_detector(
plain_helper, _SequenceDetector([[FACE_A, FACE_B], [FACE_C], []]))
plain = _run_sequence(
processor_module, _KeepNet(), plain_helper, frames, draw_box=False)
assert torch.equal(output, plain), (
"the CPU draw_box run drew on the host path - the fix draws the "
"boxes on the device canvases only"
)
# ---------------------------------------------------------------------------
# Pin 6 - the progress counts the restore in faces
# ---------------------------------------------------------------------------
@pytest.mark.unit
def test_the_multiface_sequence_progress_counts_faces(
processor_module, make_helper, image_utils, monkeypatch
):
"""The total is ``num_input_frames * 3 + total_faces`` (design section 3.6).
Three frames, three faces: convert-in + detection spend ``2 * 3``, the
restore ``3`` (one face-unit chunk) and the paste ``3``.
"""
events: list[tuple] = []
created: list = []
class _RecordingProgress:
def __init__(self, total):
events.append(("total", total))
created.append(self)
def update(self, value):
events.append(("update", value))
monkeypatch.setattr(processor_module, "ProgressReporter", _RecordingProgress)
frames = _sequence_tensor(image_utils, _smooth_frames(3))
face_helper = make_helper(use_parse=True)
face_helper.face_parse = _StubParseNet(seed=137)
_attach_detector(face_helper, _SequenceDetector([[FACE_A, FACE_B], [FACE_C], []]))
_run_sequence(processor_module, _KeepNet(), face_helper, frames)
assert events == [
("total", 12), # the frames-only estimate of the reporter
("update", 3), # the batched convert-in
("update", 3), # the detection
("update", 3), # the restore, one chunk of three faces
("update", 3), # the paste stage's second convert-in (D-R2.3.1)
("update", 1), # the paste, frame by frame
("update", 1),
("update", 1),
], events
assert created and created[0].total == 4 * 3 + 3, (
f"the final total counts the restore in faces, got {created[0].total}"
)
# ---------------------------------------------------------------------------
# Pin 7 - the cancel polls
# ---------------------------------------------------------------------------
@pytest.mark.unit
def test_the_multiface_sequence_polls_the_cancel_flag(
processor_module, make_helper, image_utils, keep_interrupt, monkeypatch
):
"""The four long loops poll - and a no-op poll changes nothing.
The detection per frame, the crop per face, the restore per chunk and
the paste per frame (the paste module's own group polls add
environment-sized groups on top). The counting patch run must equal
the unpatched run: the wiring does no harm.
"""
frames = _sequence_tensor(image_utils, _smooth_frames(3))
def run():
keep_net = _KeepNet()
face_helper = make_helper(use_parse=True)
face_helper.face_parse = _StubParseNet(seed=139)
_attach_detector(
face_helper, _SequenceDetector([[FACE_A, FACE_B], [FACE_C], []]))
return _run_sequence(processor_module, keep_net, face_helper, frames), keep_net
unpatched, unpatched_net = run()
counter = {"calls": 0}
def noop_check():
counter["calls"] += 1
monkeypatch.setattr(keep_interrupt, "check_interrupted", noop_check)
patched, patched_net = run()
assert counter["calls"] >= 10, (
f"the four loops must poll (3 detection + 3 crop + 1 restore + 3 paste), "
f"saw {counter['calls']}"
)
assert torch.equal(unpatched, patched), "a no-op poll must not change the pixels"
assert unpatched_net.calls == patched_net.calls, "a no-op poll must not change the KEEP calls"
@pytest.mark.unit
def test_the_kth_cancel_check_aborts_the_multiface_sequence(
processor_module, make_helper, image_utils, model_management, keep_interrupt,
monkeypatch
):
"""The k-th check escapes the multi-face branch with no KEEP work after it.
Four faces over two frames with ``max_clip_length=1``: the fused crop +
restore loop polls at the chunk top and per face (round 4, D-R2.2) - the
checks fire 2x in the detection loop, then per chunk 1x at the top and
1x per face (calls 3-4 = chunk 1, 5-6 = chunk 2, 7 = chunk 3's top).
Raising on the 7th call lets exactly two 1-face chunks restore and kills
everything after - the third chunk's KEEP forward never happens.
"""
check, counter = _cancel_on_call(model_management, 7)
monkeypatch.setattr(keep_interrupt, "check_interrupted", check)
frames = _sequence_tensor(
image_utils, _smooth_frames(2))
face_helper = make_helper(use_parse=True)
face_helper.face_parse = _StubParseNet(seed=149)
_attach_detector(
face_helper, _SequenceDetector([[FACE_A, FACE_B], [FACE_A, FACE_B]]))
keep_net = _KeepNet()
with pytest.raises(model_management.InterruptProcessingException):
_run_sequence(processor_module, keep_net, face_helper, frames,
max_clip_length=1)
assert counter["calls"] == 7, (
f"2 detection + (chunk top + face) of two chunks before the raise, "
f"saw {counter['calls']}"
)
assert keep_net.calls == [
((1, 2, 3, 512, 512), False), ((1, 2, 3, 512, 512), False),
], f"exactly the first two 1-face chunks may reach the KEEP net, got {keep_net.calls}"
# ---------------------------------------------------------------------------
# Pin 8 - no temporal smoothing
# ---------------------------------------------------------------------------
@pytest.mark.unit
def test_the_multiface_sequence_does_not_smooth_the_landmarks(
processor_module, make_helper, image_utils, monkeypatch
):
"""The single-track temporal smoothing is not applicable to a face set.
The ``(frames, 10)`` model ``interpolate_sequence`` /
``_gaussian_smooth`` blend the landmarks of *different faces* into each
other, so the multi-face branch must not run them (per-frame
independent detection, design section 3.2).
"""
smoothed = {"count": 0}
interpolated = {"count": 0}
def counting_smooth(*args, **kwargs):
smoothed["count"] += 1
return processor_module._gaussian_smooth(*args, **kwargs)
def counting_interpolate(*args, **kwargs):
interpolated["count"] += 1
return processor_module.interpolate_sequence(*args, **kwargs)
monkeypatch.setattr(processor_module, "_gaussian_smooth", counting_smooth)
monkeypatch.setattr(processor_module, "interpolate_sequence", counting_interpolate)
frames = _sequence_tensor(image_utils, _smooth_frames(3))
face_helper = make_helper(use_parse=True)
face_helper.face_parse = _StubParseNet(seed=151)
_attach_detector(face_helper, _SequenceDetector([[FACE_A, FACE_B], [FACE_C], []]))
_run_sequence(processor_module, _KeepNet(), face_helper, frames)
assert smoothed["count"] == 0 and interpolated["count"] == 0, (
"the multi-face branch must not run the temporal landmark smoothing"
)
# ---------------------------------------------------------------------------
# P-M1 - the golden fixtures (the round-6 pixel-policy baseline)
# ---------------------------------------------------------------------------
#: The fixture directory (``tests/fixtures``, the tool's default output).
GOLDEN_FIXTURE_DIR = Path(__file__).resolve().parent / "fixtures"
def _load_golden_capture():
"""The capture tool module - the single source of the golden cases."""
source = Path(__file__).resolve().parent.parent / "tools" / "capture_round4_golden.py"
spec = importlib.util.spec_from_file_location("_round4_golden_capture", source)
module = importlib.util.module_from_spec(spec)
spec.loader.exec_module(module)
return module
@pytest.mark.unit
@pytest.mark.parametrize("case_name", ["multiface_seq", "single_seq"])
def test_the_sequence_golden_fixtures_are_reproduced_bit_for_bit(
processor_module, helper_module, image_utils, monkeypatch, case_name
):
"""P-M1: the streamed re-run of a captured case is bitwise identical.
The fixtures were captured by ``tools/capture_round4_golden.py`` and
re-captured in round 6, after the owner-authorized pixel changes (Opt 1
- the GPU crop - and Opt 3 - the float32 normalization and smoothing),
on 2026-10-30 after keep-fix3 (fix 2.1 - the vectorized
``_paste_chunk_roi`` - moved the pasted pixels, and fix 1.2 made
``single_seq``'s gray frame batch-eligible, moving its
``output_frames`` and ``parse_calls``), and on 2026-10-31 after
keep-fix4 (round 10: the per-face ROI ``grid_sample`` chunks and the
plain quantize tail moved the pasted pixels of both sequence cases -
the multiface gray face-less frame keeps its pixels, the paste canvas
resize of the adapted background short-circuit is the identity on its
constant content; the two image cases are byte-identical to the
round-6 capture, the image paths never enter the batched paste).
They are the keep-fix4 pixel-policy baseline. This pin re-runs the
case through the current code and demands the same bytes - the
returned IMAGE tensor, the KEEP call shapes (the chunk boundaries are
a quality contract) and the parse-call count. ``single_seq`` is all
sub-512 (every frame carries the vendored upscale, so the re-derived
crop source of the fused loop is exercised) and its gray frame rides
the batched core since fix 1.2, ``multiface_seq`` has the gray
face-less frame (the background short-circuit) and the 3/3/1 restore
chunks (the 1-face duplicate-to-2).
"""
monkeypatch.setattr(helper_module, "init_detection_model", lambda *a, **k: object())
monkeypatch.setattr(helper_module, "init_parsing_model", lambda *a, **k: object())
capture = _load_golden_capture()
stubs = capture._load_test_stubs()
case = capture.SEQUENCE_CASES[case_name]
golden = np.load(GOLDEN_FIXTURE_DIR / f"round4_golden_{case_name}.npz")
faces = {key: capture.golden_face(stubs, key) for key in capture.FACE_OFFSETS}
per_frame = [[faces[key] for key in frame] for frame in case["faces"]]
tensor = image_utils.bgr_u8_batch_to_comfy(capture.sequence_frames(case))
assert np.array_equal(tensor.numpy(), golden["input_frames"]), (
"the re-run input drifted from the captured one"
)
face_helper = capture._make_helper(
stubs, helper_module, per_frame, capture.PARSE_SEEDS[case_name])
keep_net = stubs._KeepNet()
output = _run_sequence(
processor_module, keep_net, face_helper, tensor,
only_center_face=case["only_center_face"],
max_clip_length=case["max_clip_length"],
)
assert np.array_equal(output.numpy(), golden["output_frames"]), (
"the output IMAGE tensor is not bitwise the captured one"
)
shapes, flags = capture._keep_records(keep_net)
assert np.array_equal(shapes, golden["keep_net_shapes"]), (
f"the KEEP call shapes moved: {shapes} vs {golden['keep_net_shapes']}"
)
assert np.array_equal(flags, golden["keep_net_need_upscale"])
assert face_helper.face_parse.calls == int(golden["parse_calls"]), (
f"the parse-call count moved: {face_helper.face_parse.calls} "
f"vs {int(golden['parse_calls'])}"
)
# ---------------------------------------------------------------------------
# P-M2 - the crop staging stays bounded by the clip length (D-R2.2)
# ---------------------------------------------------------------------------
def _ten_faces(frame_row: int) -> list[np.ndarray]:
"""Ten template faces on a 5x2 grid, shifted a little per frame."""
return [
(FACE_TEMPLATE - 256.0) * 0.25
+ np.array([64.0 + 128.0 * col, 128.0 + 256.0 * grid_row + 2.0 * frame_row])
for grid_row in range(2)
for col in range(5)
]
@pytest.mark.unit
def test_the_crop_staging_stays_bounded_by_the_clip_length(
processor_module, make_helper, image_utils, monkeypatch
):
"""P-M2: the fused loop's staging never scales with the face count.
4 frames x 10 faces through the real helper and the multi-face branch
(``max_clip_length=10``): every ``np.empty`` / ``np.stack`` allocation
of the run is recorded. The 4-D ``(…, 3)`` uint8 ``np.empty`` calls
must stay at or below one chunk of rows - the single exception is
D-R2.2.3's preallocated restored stack (the result, one allocation),
told apart by its ``shape[0] == total_faces``. Since the batched crop
(Opt 1) stages the **detection frames** (pre-warp), the staging rows
carry the detection image's shape - the filter no longer assumes the
512-crop shape (the frames sit at the 512 floor, so the staging is
``(rows, 512, 640, 3)`` here). No ``np.stack`` of crops exists at all
(the old crop list plus its stack, the 2x staging peak, is gone).
Every frame carries faces and none is gray, so both paste pass-through
gates hold (D-R2.3.2) and even the paste faces stack disappears from
the record.
"""
faces_per_frame = [_ten_faces(row) for row in range(4)]
face_helper = make_helper(use_parse=True)
face_helper.face_parse = _StubParseNet(seed=202)
_attach_detector(face_helper, _SequenceDetector(faces_per_frame))
frames = _sequence_tensor(image_utils, _smooth_frames(4))
keep_net = _KeepNet()
allocs: list[tuple[str, tuple, np.dtype]] = []
real_empty, real_stack = np.empty, np.stack
def recording_empty(*args, **kwargs):
shape = args[0] if args else kwargs["shape"]
dtype = kwargs.get("dtype", args[1] if len(args) > 1 else np.float64)
allocs.append(("empty", tuple(np.atleast_1d(shape).tolist()), np.dtype(dtype)))
return real_empty(*args, **kwargs)
def recording_stack(arrays, *args, **kwargs):
arrays = list(arrays)
allocs.append(("stack", tuple(np.asarray(arrays).shape), np.dtype(np.uint8)))
return real_stack(arrays, *args, **kwargs)
monkeypatch.setattr(np, "empty", recording_empty)
monkeypatch.setattr(np, "stack", recording_stack)
_run_sequence(processor_module, keep_net, face_helper, frames, max_clip_length=10)
total_faces = 40
crops = [
(kind, shape) for kind, shape, dtype in allocs
if kind == "empty" and len(shape) == 4 and shape[-1] == 3
and dtype == np.uint8
]
# the preallocated restored stack (D-R2.2.3) is the only unbounded one
restored = [shape for kind, shape in crops if shape[0] == total_faces]
assert len(restored) == 1, crops
staging = [(kind, shape) for kind, shape in crops if shape[0] != total_faces]
assert staging, "the fused loop must allocate its chunk staging through np.empty"
for kind, shape in staging:
assert shape[0] <= 10, (kind, shape)
# the old list + np.stack of the crops is gone: no stacked crops at all
stacked_crops = [
shape for kind, shape, dtype in allocs
if kind == "stack" and len(shape) == 4 and shape[1:] == (512, 512, 3)
]
assert stacked_crops == [], stacked_crops
# ---------------------------------------------------------------------------
# P-M3 - the detection pass converts at most a group at a time (D-R2.1)
# ---------------------------------------------------------------------------
@pytest.mark.unit
def test_the_detection_pass_converts_at_most_a_group_at_a_time(
processor_module, make_helper, image_utils, monkeypatch
):
"""P-M3 (multi-face leg): no whole-clip convert before the first KEEP call.
The streamed detection pass converts a group of at most
``max_clip_length`` rows at a time and the fused loop re-derives the
chunk's involved frames in one group conversion per chunk (the grouped
D-R2.2.1); the whole-clip uint8 stack of the old detection pass is
gone. Recording the converter's batch sizes: everything before the
first KEEP forward stays at or below the clip length. The frames sit
at the 512 floor, so the pin measures exactly the group conversions
(the sub-512 case rides the golden fixtures instead).
"""
conversions: list[int] = []
real_convert = processor_module.comfy_batch_to_bgr_u8
def recording_convert(tensor):
conversions.append(int(tensor.shape[0]))
return real_convert(tensor)
monkeypatch.setattr(processor_module, "comfy_batch_to_bgr_u8", recording_convert)
keep_at: list[int] = []
class _ProbeKeepNet(_KeepNet):
def __call__(self, frames, need_upscale=False):
keep_at.append(len(conversions))
return super().__call__(frames, need_upscale=need_upscale)
face_helper = make_helper()
_attach_detector(
face_helper, _SequenceDetector([[FACE_A], [FACE_C], [FACE_A], [FACE_C]]))
frames = _sequence_tensor(image_utils, _smooth_frames(4))
_run_sequence(
processor_module, _ProbeKeepNet(), face_helper, frames, max_clip_length=2)
assert keep_at, "the probe net must be called"
before_the_first_keep = conversions[: keep_at[0]]
assert before_the_first_keep, (
"the detection pass must convert through the module function"
)
assert max(before_the_first_keep) <= 2, before_the_first_keep
# The fused crop loop re-derives the chunk's involved frames in ONE
# group conversion per chunk (the grouped D-R2.2.1) - the per-frame
# re-derivations of the first round-4 cut are gone: across the whole
# run only the detection groups, the crop groups and the paste stage's
# convert-ins may call the converter.
#
# Round 17 (the final stage's RAM) moved the paste stage's convert-in:
# it was ONE whole-clip conversion (4 rows - the uint8 stack of the
# clip, ~3.78 GB on the owner's 777-frame clip), and it is now the
# **fallback groups** (the lazy source, one group of
# ``max_clip_length`` rows per group). The count therefore moves UP
# (5 -> 6 here: the detection groups [2, 2], the crop groups [2, 2]
# and the two fallback groups [2, 2]) while the LARGEST conversion
# moves DOWN (4 -> 2). The ceiling follows the movement: the paste
# stage's convert-ins are the fallback groups now (ceil(4 / 2) = 2 of
# them), so the budget is 2 * ceil(4 / 2) + 2 = 6.
assert len(conversions) <= 6, conversions
@pytest.mark.unit
def test_the_paste_stage_never_converts_the_whole_clip(
processor_module, make_helper, image_utils, monkeypatch
):
"""Round 17 (multi-face leg): the paste stage converts group by group.
The sibling of the single-face pin (``tests/test_keep_processor.py``):
the multi-face paste stage used to materialize the clip's uint8 BGR
stack in one conversion (``input_frames_bgr = comfy_batch_to_bgr_u8(
image_sequence_tensor)``) - ~3.78 GB of host RAM on the owner's
777-frame 1500x1080 clip. Round 17 replaced it with the lazy
:class:`~.keep_image_utils.BgrFrameSource`: the paste core asks the
source for the rows of the group it is about to paste, and the
fallback rows arrive in groups of their own.
The pin drives the **chunked** mode - the production mode of a long
clip (the full mode needs the whole device canvas and the host mode
is the no-CUDA fallback; both materialize the eligible rows, the
disclosed limitation of the design) - with a group size of 2 over a
4-frame clip, and records the batch size of every conversion of the
run:
* no conversion covers the whole clip (4 rows): the stack is never
built;
* the paste stage's conversions are group-sized (at most
``paste_chunk_size`` rows): it converts the rows it pastes, not the
clip.
The measured run (the pin's fixture): the paste stage converts
``[2, 2, 0]`` - the core's two eligible groups of 2 frames and the
**empty** fallback group (every frame was batched, so the fallback
generator runs its defensive empty pass).
"""
paste_mod = importlib.import_module("ComfyUI_KEEP.src.keep_gpu_paste")
monkeypatch.setattr(
paste_mod, "device_output_mode", lambda *args, **kwargs: "chunked")
monkeypatch.setattr(paste_mod, "paste_chunk_size", lambda *args, **kwargs: 2)
conversions: list[int] = []
real_convert = processor_module.comfy_batch_to_bgr_u8
def recording_convert(tensor):
conversions.append(int(tensor.shape[0]))
return real_convert(tensor)
monkeypatch.setattr(processor_module, "comfy_batch_to_bgr_u8", recording_convert)
# The paste stage's conversions are the ones the core makes (the core
# is the first consumer of the lazy source) plus the fallback groups
# the vendored paste pulls afterwards.
paste_at: list[int] = []
real_chunked = paste_mod.paste_faces_batch_chunked
def recording_chunked(*args, **kwargs):
paste_at.append(len(conversions))
return real_chunked(*args, **kwargs)
monkeypatch.setattr(paste_mod, "paste_faces_batch_chunked", recording_chunked)
face_helper = make_helper(use_parse=True)
face_helper.face_parse = _StubParseNet(seed=211)
_attach_detector(
face_helper, _SequenceDetector([[FACE_A], [FACE_C], [FACE_A], [FACE_C]]))
frames = _sequence_tensor(image_utils, _smooth_frames(4))
_run_sequence(
processor_module, _KeepNet(), face_helper, frames, max_clip_length=2)
assert conversions, "the run must convert through the module function"
assert paste_at, "the chunked paste core must run"
assert 4 not in conversions, (
f"no conversion may cover the whole clip, got {conversions}"
)
paste_stage = conversions[paste_at[0]:]
assert paste_stage, "the paste stage must convert its groups"
assert max(paste_stage) <= 2, (
f"the paste stage must convert group by group, got {paste_stage}"
)
# ---------------------------------------------------------------------------
# P-M5 - the sequence paths release the helper after the run (D-R2.4)
# ---------------------------------------------------------------------------
@pytest.mark.unit
def test_the_sequence_paths_release_the_helper_after_the_run(
processor_module, make_helper, image_utils
):
"""P-M5: both sequence branches leave no detection image behind (D-R2.4).
The vendored ``clean_all`` clears the face lists but not ``input_img``
(``face_restoration_helper.py:530-537``) - the leaked whole frame of
the OOM report. The ``finally`` of the redesign releases both on the
success path, for the single-face branch and the multi-face branch
alike, real helper end to end.
"""
for only_center_face, layout in (
(True, [[FACE_A], [FACE_C]]),
(False, [[FACE_A, FACE_B], [FACE_C]]),
):
face_helper = make_helper(use_parse=True)
face_helper.face_parse = _StubParseNet(seed=203)
_attach_detector(face_helper, _SequenceDetector(layout))
frames = _sequence_tensor(image_utils, _smooth_frames(2))
_run_sequence(
processor_module, _KeepNet(), face_helper, frames,
only_center_face=only_center_face, max_clip_length=20,
)
assert face_helper.input_img is None, only_center_face
for name in (
"cropped_faces", "restored_faces", "affine_matrices",
"inverse_affine_matrices", "all_landmarks_5",
):
assert getattr(face_helper, name) == [], (only_center_face, name)
# ---------------------------------------------------------------------------
# Round 18 - the mixed case holds ONE whole-clip buffer
# ---------------------------------------------------------------------------
@pytest.mark.unit
def test_the_multiface_mixed_case_holds_one_whole_clip_buffer(
processor_module, make_helper, image_utils, monkeypatch
):
"""Round 18: the mixed case holds **one** whole-clip float32 buffer.
The sibling of the single-face pin (``tests/test_keep_processor.py``):
the paste stage's output used to be assembled in two steps - the paste
core allocated its own buffer (the eligible rows) and the tail
allocated a **second** whole-clip buffer and copied the eligible rows
into it, so the mixed case held two whole-clip float32 tensors at the
same time. Round 18 hands the tail's buffer to the core: the eligible
rows land in their final rows and exactly one whole-clip float32
buffer exists.
The rig is the recurring one - ``[[FACE_A], [], [FACE_C]]``: the middle
frame has **no face**, so it rides the vendored fallback and the
eligible rows are **scattered** (``[0, 2]``). The pin drives the
**chunked** mode (the production mode of a long clip) and records
every ``torch.empty`` of the run: exactly one 4-D float32 allocation
carries the canvas's trailing dims (the core's own eligible-sized
buffer would carry them too - the census sees both), and that tensor
**is** the returned IMAGE (the weakref identity - the alias, not a
copy).
"""
paste_mod = importlib.import_module("ComfyUI_KEEP.src.keep_gpu_paste")
monkeypatch.setattr(
paste_mod, "device_output_mode", lambda *args, **kwargs: "chunked")
empties: list[tuple[tuple, torch.dtype, weakref.ref]] = []
real_empty = torch.empty
def recording_empty(*args, **kwargs):
tensor = real_empty(*args, **kwargs)
empties.append((tuple(tensor.shape), tensor.dtype, weakref.ref(tensor)))
return tensor
monkeypatch.setattr(torch, "empty", recording_empty)
face_helper = make_helper(use_parse=True)
face_helper.face_parse = _StubParseNet(seed=217)
_attach_detector(face_helper, _SequenceDetector([[FACE_A], [], [FACE_C]]))
frames = _sequence_tensor(image_utils, _smooth_frames(3))
output = _run_sequence(processor_module, _KeepNet(), face_helper, frames)
canvas_shape = tuple(int(value) for value in output.shape[1:])
float_buffers = [
(shape, ref) for shape, dtype, ref in empties
if dtype == torch.float32 and len(shape) == 4
and shape[1:] == canvas_shape
]
assert len(float_buffers) == 1, (
f"the mixed case must hold one canvas-shaped float32 buffer, got "
f"{[shape for shape, _ in float_buffers]}"
)
assert float_buffers[0][0][0] == 3, (
f"the buffer must carry the whole clip's rows, got {float_buffers[0][0]}"
)
# the returned IMAGE is the paste buffer itself (the alias, no copy)
assert float_buffers[0][1]() is output, (
"the returned IMAGE must be the paste buffer itself"
)
@pytest.mark.unit
def test_the_multiface_mixed_case_draws_the_boxes_on_the_final_rows(
processor_module, make_helper, image_utils, monkeypatch
):
"""Round 18: the boxes land on the **final** rows of the mixed case.
``draw_box`` moved from the eligible-space rows to the final buffer
(the float32 IMAGE rows - the uint8 colour casts on assignment, the
documented 0.0 / 255.0 / 0.0). The translation is the point of the
pin: the eligible rows of a mixed clip are **scattered** (here
``[0, 2]`` - the middle frame has no face), so a box addressed to the
eligible-space row would land on the wrong final row.
The pin compares two runs of the same clip - ``draw_box=True`` and
``draw_box=False``: the eligible rows differ (the box is on them) and
the fallback row is **identical** (a frame without a face never gets a
box - in either design).
"""
paste_mod = importlib.import_module("ComfyUI_KEEP.src.keep_gpu_paste")
monkeypatch.setattr(
paste_mod, "device_output_mode", lambda *args, **kwargs: "chunked")
frames = _sequence_tensor(image_utils, _smooth_frames(3))
def run(draw_box):
face_helper = make_helper(use_parse=True)
face_helper.face_parse = _StubParseNet(seed=219)
_attach_detector(
face_helper, _SequenceDetector([[FACE_A], [], [FACE_C]]))
return _run_sequence(
processor_module, _KeepNet(), face_helper, frames,
draw_box=draw_box)
boxed = run(True)
plain = run(False)
assert tuple(boxed.shape) == (3, *FRAME_HW, 3), boxed.shape
for row in (0, 2):
assert not torch.equal(boxed[row], plain[row]), (
f"the eligible row {row} must carry the box"
)
assert torch.equal(boxed[1], plain[1]), (
"the fallback row must not carry a box"
)
# ---------------------------------------------------------------------------
# Round 19 (FIX 1) - the multi-face gate keeps the raw stack
# ---------------------------------------------------------------------------
@pytest.mark.unit
def test_the_multiface_gate_keeps_the_raw_stack(
processor_module, make_helper, image_utils, monkeypatch
):
"""Round 19 (FIX 1): the multi-face gate keeps the raw stack, no gather.
The multi-face pass-through gate (the round-4 D-R2.3.2 split of the
sibling's) keeps the mixed-case change object-wise out of the
all-eligible case: the raw restored stack is handed to the core **as
the ndarray it is** - the lazy source only enters through the mixed
branch. The branch itself is not reachable through the full sequence
today - the crop stage drops the ineligible faces **before** the
restored stack is built, so ``batch_faces`` always covers the stack -
and the core-level byte identity of the mixed branch is pinned in
``tests/test_multiface_paste.py``.
The pin drives the full multi-face sequence in the **chunked** mode
over the round-18 rig (the middle frame has no face, so the eligible
frames are scattered ``[0, 2]`` and the restored stack carries both
faces) and spies the hand-off plus every ``np.stack`` of the run: the
core receives the raw ``(total_faces, 512, 512, 3)`` ndarray and no
``np.stack`` carries the eligible-sized faces shape.
"""
paste_mod = importlib.import_module("ComfyUI_KEEP.src.keep_gpu_paste")
monkeypatch.setattr(
paste_mod, "device_output_mode", lambda *args, **kwargs: "chunked")
handed: list = []
real_chunked = paste_mod.paste_faces_batch_chunked
def recording_chunked(*args, **kwargs):
handed.append(kwargs.get("faces_bgr"))
return real_chunked(*args, **kwargs)
monkeypatch.setattr(paste_mod, "paste_faces_batch_chunked", recording_chunked)
stacks: list[tuple] = []
real_stack = np.stack
def recording_stack(arrays, *args, **kwargs):
result = real_stack(arrays, *args, **kwargs)
stacks.append(tuple(int(value) for value in result.shape))
return result
monkeypatch.setattr(np, "stack", recording_stack)
face_helper = make_helper(use_parse=True)
face_helper.face_parse = _StubParseNet(seed=217)
_attach_detector(face_helper, _SequenceDetector([[FACE_A], [], [FACE_C]]))
frames = _sequence_tensor(image_utils, _smooth_frames(3))
output = _run_sequence(processor_module, _KeepNet(), face_helper, frames)
assert tuple(output.shape) == (3, *FRAME_HW, 3), output.shape
assert len(handed) == 1 and isinstance(handed[0], np.ndarray), (
"the multi-face gate pin must hand the raw restored stack, got "
f"{type(handed[0]) if handed else None}"
)
assert not isinstance(handed[0], paste_mod.LazyFaceSource), (
"the gate path must not build the lazy source"
)
assert tuple(handed[0].shape) == (2, 512, 512, 3), handed[0].shape
gathers = [shape for shape in stacks if shape == (2, 512, 512, 3)]
assert gathers == [], (
f"no eligible-sized gather may exist on the gate path, got {gathers}"
)
# ---------------------------------------------------------------------------
# Round 19 (FIX 2) - the fallback tail converts per gray group (the twin)
# ---------------------------------------------------------------------------
@pytest.mark.unit
def test_the_multiface_fallback_rows_convert_per_gray_group_across_the_boundary(
processor_module, make_helper, image_utils, monkeypatch
):
"""Round 19 (FIX 2), pin 4 (the multi-face twin): per-gray-group flush.
The sibling of the single-face pin (``tests/test_keep_processor.py``):
the multi-face chunks_mode tail used to stack **all** fallback frames
and convert the whole stack in one call; it now flushes every gray
group the loop advances past plus the last group at the tail - the
group's stack (at most ``max_clip_length`` rows), the same elementwise
conversion, the indexed write into ``final_batch``.
The rig is the recurring no-face fallback with a boundary in it: 6
frames whose faces sit only on the frames 0 and 5 (the ``_SequenceDetector``
per-frame sets) fall back on rows [1, 2, 3, 4]; with
``max_clip_length=2`` the gray groups are [1, 2] and [3, 4]. The spy
records the frames of every canvas-shaped ``np.stack`` of the run (the
fixture frames are built **before** the spy - their own stack is
canvas-shaped); the reference is the one-shot whole-stack conversion
the old tail produced, computed from the recorded frames. Every
fallback row of the output must be byte-equal to its row of the
reference.
"""
paste_mod = importlib.import_module("ComfyUI_KEEP.src.keep_gpu_paste")
monkeypatch.setattr(
paste_mod, "device_output_mode", lambda *args, **kwargs: "chunked")
# the fixture frames first - the spy below would record their stack
frames = _sequence_tensor(image_utils, _smooth_frames(6))
groups: list[list[np.ndarray]] = []
real_stack = np.stack
def recording_stack(arrays, *args, **kwargs):
result = real_stack(arrays, *args, **kwargs)
if result.ndim == 4 and result.shape[1:] == (*FRAME_HW, 3):
groups.append([np.array(frame) for frame in arrays])
return result
monkeypatch.setattr(np, "stack", recording_stack)
face_helper = make_helper(use_parse=True)
face_helper.face_parse = _StubParseNet(seed=217)
_attach_detector(
face_helper,
_SequenceDetector([[FACE_A], [], [], [], [], [FACE_C]]))
output = _run_sequence(
processor_module, _KeepNet(), face_helper, frames, max_clip_length=2)
assert tuple(output.shape) == (6, *FRAME_HW, 3), output.shape
# the boundary was crossed: two group stacks of two frames each - the
# whole-fallback stack would be one record of four
assert [len(group) for group in groups] == [2, 2], (
f"the fallback frames must flush per gray group, got "
f"{[len(group) for group in groups]}")
# the reference: the one-shot whole-stack conversion the old tail ran
# (the snapshot first - the conversion below rides the same spy)
recorded = [list(group) for group in groups]
all_frames = [frame for group in recorded for frame in group]
reference = image_utils.bgr_u8_batch_to_comfy(np.stack(all_frames))
assert tuple(reference.shape) == (4, *FRAME_HW, 3), reference.shape
for row, ref_row in zip((1, 2, 3, 4), range(4)):
assert torch.equal(output[row], reference[ref_row]), (
f"the fallback row {row} must be byte-equal to the one-shot "
f"whole-stack conversion's row {ref_row}")
@pytest.mark.unit
def test_the_multiface_chunks_mode_tail_builds_no_whole_fallback_stack(
processor_module, make_helper, image_utils, monkeypatch
):
"""Round 19 (FIX 2), pin 5 (the multi-face twin): no whole-fallback stack.
The census pin of the sibling: the same boundary rig as the twin's
pin 4 (6 frames, the fallback rows [1, 2, 3, 4],
``max_clip_length=2``), with every ``np.stack`` and every
``torch.empty`` of the run recorded. No stack may carry more rows than
one gray group's fallback rows (2 here - the old tail's whole-fallback
record would be a 4), and the round-18 contract stays: exactly **one**
canvas-shaped whole-clip float32 ``torch.empty`` exists and it **is**
the returned IMAGE.
"""
paste_mod = importlib.import_module("ComfyUI_KEEP.src.keep_gpu_paste")
monkeypatch.setattr(
paste_mod, "device_output_mode", lambda *args, **kwargs: "chunked")
# the fixture frames first - the spy below would record their stack
frames = _sequence_tensor(image_utils, _smooth_frames(6))
stacks: list[tuple] = []
real_stack = np.stack
def recording_stack(arrays, *args, **kwargs):
result = real_stack(arrays, *args, **kwargs)
stacks.append(tuple(int(value) for value in result.shape))
return result
monkeypatch.setattr(np, "stack", recording_stack)
empties: list[tuple[tuple, torch.dtype, weakref.ref]] = []
real_empty = torch.empty
def recording_empty(*args, **kwargs):
tensor = real_empty(*args, **kwargs)
empties.append((tuple(tensor.shape), tensor.dtype, weakref.ref(tensor)))
return tensor
monkeypatch.setattr(torch, "empty", recording_empty)
face_helper = make_helper(use_parse=True)
face_helper.face_parse = _StubParseNet(seed=217)
_attach_detector(
face_helper,
_SequenceDetector([[FACE_A], [], [], [], [], [FACE_C]]))
output = _run_sequence(
processor_module, _KeepNet(), face_helper, frames, max_clip_length=2)
assert tuple(output.shape) == (6, *FRAME_HW, 3), output.shape
canvas_shape = (*FRAME_HW, 3)
tail_stacks = [
shape for shape in stacks
if len(shape) == 4 and shape[1:] == canvas_shape
]
assert tail_stacks, "the flush must stack the fallback frames"
assert max(shape[0] for shape in tail_stacks) <= 2, (
f"no tail stack may exceed one gray group's fallback rows "
f"(2 here), got {tail_stacks}")
whole_fallback = [shape for shape in tail_stacks if shape[0] == 4]
assert whole_fallback == [], (
f"the whole-fallback stack is gone from the tail, got {whole_fallback}")
# the round-18 conjunction: one whole-clip float32 buffer, and it is
# the returned IMAGE
float_buffers = [
(shape, ref) for shape, dtype, ref in empties
if dtype == torch.float32 and len(shape) == 4
and shape[1:] == canvas_shape
]
assert len(float_buffers) == 1, (
f"the mixed case must hold one canvas-shaped float32 buffer, got "
f"{[shape for shape, _ in float_buffers]}")
assert float_buffers[0][0][0] == 6, (
f"the buffer must carry the whole clip's rows, got {float_buffers[0][0]}")
assert float_buffers[0][1]() is output, (
"the returned IMAGE must be the paste buffer itself")