Files
AnyImage/tests/anyimage/operators/test_hdr_background.py
nan 938d95db0f Add new functionality for image rectification and depth metadata handling
- Implemented a preview tool for rectifying images with interactive aspect controls in `preview.py`.
- Added tests for depth metadata loading to ensure consistent schema usage in `test_depth_metadata.py`.
- Developed comprehensive tests for cutout calibration processes in `test_cutout_calibration.py`, including stability checks and depth calibration.
- Created tests for frame compositing operations in `test_frame_compositing.py`, ensuring correct handling of depth and transparency.
- Introduced material slot inheritance functionality in `material_slots.py` to manage geometry material assignments.
- Built shared shader nodes for material processing in `shader.py`, including roughness and bump mapping setups.
2026-09-12 19:20:06 +08:00

116 lines
5.2 KiB
Python

from pathlib import Path
from types import SimpleNamespace
from unittest.mock import patch
import bpy
import numpy as np
import pytest
from anyimage.common.hdr_image import HdrBackgroundInput
from anyimage.common.image import cleanup_image_input, image_rgba
from anyimage.common.image_target import ImageEditTarget
from anyimage.operators import remove_background as operators
from anyimage.runtime import JobOperatorBase
from tests.support.hdr_image import hdr_texture
from tests.support.image_texture import texture, texture_context
@pytest.mark.parametrize("owner_kind", ["texture", "empty"])
@pytest.mark.parametrize("shared", [False, True])
def test_hdr_background_undo_redo(hdr_texture, tmp_path, owner_kind, shared):
node = hdr_texture
source = node.image
before = image_rgba(source).copy()
if owner_kind == "empty":
owner = bpy.data.objects.new("HDR Empty", None)
owner.empty_display_type = "IMAGE"
owner.data = source
bpy.context.scene.collection.objects.link(owner)
node.image = source if shared else None
elif shared:
other = node.id_data.nodes.new("ShaderNodeTexImage")
other.image = source
alpha_path = tmp_path / "alpha.npy"
np.save(alpha_path, np.full((3, 4), 0.37123, np.float32))
def current_owner():
if owner_kind == "empty":
return bpy.data.objects["HDR Empty"]
return bpy.data.materials["Texture material"].node_tree.nodes["Source texture"]
def current_image():
owner = current_owner()
return owner.data if owner_kind == "empty" else owner.image
def execute(operator, context):
owner = current_owner()
target_context = SimpleNamespace(object=owner, space_data=None) if owner_kind == "empty" else texture_context(owner)
operator._image_target = ImageEditTarget.capture(target_context)
operator._hdr_input = HdrBackgroundInput.prepare(current_image())
operator.input_path, operator.delete_input = str(operator._hdr_input.path), True
try:
operator.response(context, SimpleNamespace(file=lambda key: alpha_path))
finally:
operator.cleanup()
return {"FINISHED"}
operation = operators.RunBackgroundRemoval
bpy.context.preferences.edit.use_global_undo = True
with patch.object(operation, "execute", execute), patch.object(operation, "poll", classmethod(lambda cls, context: True), create=True):
bpy.utils.register_class(operation)
try:
bpy.ops.ed.undo_push(message="Before HDR removal")
assert bpy.ops.anyimage.run_background_removal("EXEC_DEFAULT", True) == {"FINISHED"}
after = image_rgba(current_image()).copy()
np.testing.assert_array_equal(after[..., :3], before[..., :3])
np.testing.assert_allclose(after[..., 3], before[..., 3] * np.float32(0.37123), atol=1e-7)
assert bpy.ops.ed.undo() == {"FINISHED"}
np.testing.assert_array_equal(image_rgba(current_image()), before)
assert bpy.ops.ed.redo() == {"FINISHED"}
np.testing.assert_array_equal(image_rgba(current_image()), after)
assert current_image().is_float
finally:
bpy.utils.unregister_class(operation)
@pytest.mark.parametrize("outcome", ["cancel", "raise"])
def test_hdr_submission_failure_releases_preview_and_snapshot(hdr_texture, outcome):
target = ImageEditTarget.capture(texture_context(hdr_texture))
paths = []
def submit(operator, context):
paths.append(Path(operator.input_path))
assert paths[-1].is_file()
if outcome == "raise":
raise RuntimeError("Submission failed")
return {"CANCELLED"}
# Use the registered type so super().execute follows the real Operator inheritance.
operation = operators.RunBackgroundRemoval
with patch.object(ImageEditTarget, "capture", return_value=target), patch.object(JobOperatorBase, "execute", submit):
bpy.utils.register_class(operation)
try:
if outcome == "raise":
with pytest.raises(RuntimeError, match="Submission failed"):
bpy.ops.anyimage.run_background_removal("EXEC_DEFAULT")
else:
assert bpy.ops.anyimage.run_background_removal("EXEC_DEFAULT") == {"CANCELLED"}
finally:
bpy.utils.unregister_class(operation)
assert paths and not paths[0].parent.exists()
def test_hdr_request_uses_alpha_output_and_current_still(hdr_texture):
operation = operators.RunBackgroundRemoval
snapshot = HdrBackgroundInput.prepare(hdr_texture.image)
operator = SimpleNamespace(input_path=str(snapshot.path), _hdr_input=snapshot,
_image_target=SimpleNamespace())
context = SimpleNamespace(scene=SimpleNamespace(anyimage_settings=SimpleNamespace()))
try:
with patch.object(operators, "require_environment"), patch.object(operators, "require_model"), patch.object(operators, "production_device", return_value="CPU"):
parameters = operation.request(operator, context)
assert parameters["output_kind"] == "alpha"
assert "start_frame" not in parameters and "video_frames" not in parameters
finally:
cleanup_image_input(snapshot.path, True)