Files
Ewald a4154ccf05 Fix P0 bugs, remove Append V2/Lens Artifacts, and fix crop reload reset
- Fix batch bypass tuple handling, memory budget device-awareness, grain
  RNG device, roto payload limits, preview temp-file cleanup, and route
  double-registration.
- Add native VIDEO output (fps/audio) to effect nodes that previously
  only returned IMAGE/MASK when fed a VIDEO input.
- Split the oversized src/preview/ops/geometry.ts and add a starting
  golden-fixture test harness (spherize, transform) comparing backend
  and frontend math.
- Remove ImageOpsAppendV2 (Autogrow) and ImageOpsLensArtifacts; inline
  Append's helper back into its single caller.
- Collapse the Color Correct curve widget behind a toggle so the node
  no longer grows too tall.
- Fix Crop/generic-node video preview playback running at the RAF rate
  instead of the source video's real fps.
- Fix Crop losing its saved output size after a workflow reload:
  syncCropWidgets recomputed height from width and a preset aspect
  ratio on every resync (including onConfigure on load), discarding
  the restored value. It now only refreshes aspect-ratio bookkeeping
  on a resync.
2026-09-30 12:49:42 +02:00

93 lines
3.2 KiB
Python

"""Unit tests for ImageOpsSpherize verifying mask grid warping and bypass."""
from __future__ import annotations
import json
from pathlib import Path
import pytest
import torch
from nodes.spherize import ImageOpsSpherize, _spherize_frame
def _unwrap(result):
if isinstance(result, dict):
return result["result"]
return result
def test_spherize_frame_matches_golden_fixture_used_for_frontend_parity():
"""Regression guard for tests/golden/spherize.json, which
tests/frontend/spherize.test.cjs uses to check applySpherize for parity.
If this test needs updating, regenerate the golden and re-check the JS side."""
golden_path = Path(__file__).resolve().parents[1] / "golden" / "spherize.json"
golden = json.loads(golden_path.read_text(encoding="utf-8"))
meta = golden["_meta"]
width, height = meta["width"], meta["height"]
frame = torch.tensor(golden["input_rgb"], dtype=torch.float32).unsqueeze(0) / 255.0
out = _spherize_frame(frame, **golden["params"])
actual = (out[0] * 255.0).round().clamp(0, 255).byte()
expected = torch.tensor(golden["expected_rgb"], dtype=torch.uint8)
assert actual.shape == (height, width, 3)
max_diff = (actual.int() - expected.int()).abs().max().item()
# tolerance=1: the fixture round-trips through uint8 storage, so re-deriving
# the input tensor from it reintroduces ~1/255 of quantization noise versus
# the exact floats used when the golden was generated.
assert max_diff <= 1, f"_spherize_frame no longer matches its own golden fixture (max diff {max_diff})"
class TestImageOpsSpherize:
def test_spherize_bypass_preserves_resolution(self):
node = ImageOpsSpherize()
# Input image is 120x80
image = torch.rand(1, 80, 120, 3, dtype=torch.float32)
mask = torch.rand(1, 80, 120, dtype=torch.float32)
# Custom size is set to 256x256, but bypass=True should NEVER resize
result = _unwrap(node.execute(
image=image,
mask=mask,
bypass=True,
size_mode="custom",
width=256,
height=256,
))
out_image = result[0]
out_mask = result[1]
assert out_image.shape == (1, 80, 120, 3)
assert out_mask.shape == (1, 80, 120)
assert torch.allclose(out_image, image)
assert torch.allclose(out_mask, mask)
def test_spherize_mask_warped_with_image(self):
node = ImageOpsSpherize()
# Put a spot at center
image = torch.zeros(1, 64, 64, 3, dtype=torch.float32)
image[0, 30:34, 30:34, :] = 1.0
mask = torch.zeros(1, 64, 64, dtype=torch.float32)
mask[0, 30:34, 30:34] = 1.0
result = _unwrap(node.execute(
image=image,
mask=mask,
mode="spherize",
strength=1.0,
size_mode="from_input",
))
out_image = result[0]
out_mask = result[1]
assert out_image.shape == (1, 64, 64, 3)
assert out_mask.shape == (1, 64, 64)
assert torch.isfinite(out_image).all()
assert torch.isfinite(out_mask).all()
# Center spot should still be present and warped in both image and mask
assert out_image[0, 32, 32, 0] > 0.5
assert out_mask[0, 32, 32] > 0.5