From 58d8b11ade01a5553ab779bdf98fa58f8fca3f1b Mon Sep 17 00:00:00 2001 From: WildAi <2853742+wildminder@users.noreply.github.com> Date: Sat, 31 Oct 2026 13:51:39 +0300 Subject: [PATCH] Round 11: resize the single-face no-face short-circuit to the paste canvas Round-11 upscaler-pipeline audit (owner task: check whether the bg/face upscaler inputs are applied correctly or erroneous, and how they compose with final_upscale_factor): - VERDICT: the two upscaler models are quality operators only - their output scales are always absorbed by resizes (batched_bg_frames / vendored paste / aligned path); final_upscale_factor alone fixes the output geometry (the paste canvas x factor), exactly as the node tooltip states. Slowdown with x4 models is the documented design (a face upscaler disqualifies the batched paste; bg model runs per fallback frame; the canvas is model-widened x factor). - CONFIRMED ERROR (the one real bug found, reproduced CPU-only in three configurations): the single-face no-face/affine-None short-circuit appended the raw _run_upscaler output without the factor/canvas resize, so a bg model with scale != factor or a sub-512 clip mixed row sizes and crashed the convert-out (np.stack ValueError). The multiface sibling already resized (round 10); this applies the same paste_canvas_hw + LANCZOS4 resize to the single-face site. - The twin size check (:1352) is dead code (all_restored_faces_cv2 is preallocated to num_input_frames) - no fix required. - One pin's paste stub corrected to the real 512x512 canvas with a dated docstring; no fixture re-capture needed (only rows that crashed the run change pixels). Gate: 555 passed, 6 skipped, 7 deselected, 0 failed (baseline 555). --- src/keep_processor.py | 14 ++++++++++++++ tests/test_keep_processor.py | 8 +++++++- 2 files changed, 21 insertions(+), 1 deletion(-) diff --git a/src/keep_processor.py b/src/keep_processor.py index 288bfb4..b88df32 100644 --- a/src/keep_processor.py +++ b/src/keep_processor.py @@ -1342,6 +1342,20 @@ class KEEPFaceProcessor: or np.isnan(avg_landmarks_sequence[i]).any() ): final_pasted_frame = self._run_upscaler(self.bg_upscale_model, original_frame_bgr) + # Round 11 (2026-10-31): the fix-1.1 snippet's raw + # upscaler output cannot join the IMAGE batch when + # frame x model.scale != canvas x factor (the same + # disclosed deviation as the multiface + # short-circuit's resize): the output size is the + # paste canvas, reached with the same LANCZOS4 + # resize the vendored paste applied to its no-face + # frames. + canvas_h, canvas_w = paste_mod.paste_canvas_hw( + original_frame_bgr.shape[:2], final_upscale_factor, canvas_hw) + if final_pasted_frame.shape[0] != canvas_h or final_pasted_frame.shape[1] != canvas_w: + final_pasted_frame = cv2.resize( + final_pasted_frame, (canvas_w, canvas_h), + interpolation=cv2.INTER_LANCZOS4) output_frames_cv2.append(final_pasted_frame) # the same bookkeeping as the sibling fallback rows: # the device-output tails index the fallback rows by diff --git a/tests/test_keep_processor.py b/tests/test_keep_processor.py index 939fd8a..3362775 100644 --- a/tests/test_keep_processor.py +++ b/tests/test_keep_processor.py @@ -495,6 +495,12 @@ def test_every_console_write_of_the_facelib_reaches_the_logger( patched to ``[None, valid]``) so the paste-back print keeps firing; the stub's paste return is canvas-sized (the background-only row and the vendored row must stack). + + Round 11 (2026-10-31): the short-circuit row is canvas-sized since the + short-circuit resize (the fix-1.1 snippet's raw output cannot join the + IMAGE batch at a mismatched shape), so the stub's return is the real + 512x512 canvas the vendored paste assembles from ``input_img`` - the + pin's routing assertions are unchanged. """ real_estimate = processor_module.estimate_affines @@ -513,7 +519,7 @@ def test_every_console_write_of_the_facelib_reaches_the_logger( # row and the vendored row stack; the print stays (it is the routing probe) def canvas_sized_paste(*args, **kwargs): print("Warning: Mismatch between restored_faces and inverse_affine_matrices.") - return np.zeros((16, 16, 3), dtype=np.uint8) + return np.zeros((512, 512, 3), dtype=np.uint8) face_helper.paste_faces_to_input_image = canvas_sized_paste frames = torch.rand((2, 16, 16, 3), dtype=torch.float32)