Implement BiRefNet and HAT Sharper models with background removal capabilities

- Added validation documentation for AI model tiers.
- Introduced `background.py` for background removal logic, including inference functions for alpha and foreground extraction.
- Created `onnx_birefnet.py` to handle BiRefNet model sessions and alpha inference.
- Developed tests for background models, ensuring proper dispatch and session reuse.
- Added tests for BiRefNet ONNX model to validate alpha output and error handling for invalid values.
- Implemented model export scripts for BiRefNet and HAT Sharper, ensuring proper weight compression and licensing.
- Created `deform_sampling.py` for deformable convolution operations used in BiRefNet.
- Added utility functions for compressing ONNX weights without altering graph arithmetic.
- Introduced a reference plane node for depth field adjustments in geometry processing.
This commit is contained in:
2026-09-10 20:50:44 +08:00
parent 8ae3c1d5b7
commit e8129fe495
70 changed files with 1890 additions and 1208 deletions

View File

@@ -235,10 +235,67 @@ def test_surface_interface_and_node_structure(surface):
validate_node_structure(obj.modifiers[0].node_group)
@pytest.mark.parametrize("thickness", [0.0, 0.4])
@pytest.mark.parametrize("direction", [(0.2, -0.1, 1), (-0.2, 0.1, 1), (0.4, -0.2, 2), (0, 0, 0), (1, 0, 0)])
def test_relief_direction_adjusts_depth_and_keeps_base(relief, thickness, direction):
obj, set_value, _, _, image = relief
pixels = np.ones((8, 16, 4), dtype=np.float32)
pixels[..., 2] = 2
image.pixels.foreach_set(pixels.ravel())
image.update()
set_value("Thickness", thickness)
set_value("Reference Depth", 2.0)
layers = evaluated(obj)[0]
weights = np.clip(layers[:, 2] / thickness, 0, 1) if thickness else np.ones(len(layers))
set_value("Depth Scale", 0.5)
base = evaluated(obj)
set_value("Depth Direction", direction)
result = evaluated(obj)
front = base[0][:, 2] > 0
expected = base[0].copy()
if direction[2]:
expected[front, 2] += 0.5 * weights[front] * (expected[front, :2] @ np.array(direction[:2])) / direction[2]
np.testing.assert_allclose(result[0], expected, atol=1e-6)
assert result[1] == base[1]
np.testing.assert_array_equal(result[2], base[2])
@pytest.mark.parametrize("thickness", [0.0, 0.4])
def test_relief_smooth_preserves_topology_uv_and_materials(relief, thickness):
obj, set_value, _, _, image = relief
pixels = np.asarray(image.pixels[:], dtype=np.float32).reshape(8, 16, 4)
pixels[..., 2] += (np.arange(16) % 2) * 0.5
image.pixels.foreach_set(pixels.ravel())
image.update()
set_value("Thickness", thickness)
set_value("Reference Depth", 2.0)
set_value("Depth Scale", 1.0)
set_value("Depth Direction", (0.1, -0.1, 1))
base = evaluated(obj)
set_value("Smooth", 3)
result = evaluated(obj)
assert np.isfinite(result[0]).all()
assert np.max(abs(result[0] - base[0])) > 1e-4
assert result[1] == base[1]
np.testing.assert_array_equal(result[2], base[2])
assert result[3:] == base[3:]
set_value("Smooth Weight", 0.0)
np.testing.assert_allclose(evaluated(obj)[0], base[0], atol=1e-6)
set_value("Smooth Weight", 1.0)
set_value("Smooth", 0)
np.testing.assert_allclose(evaluated(obj)[0], base[0], atol=1e-6)
def test_relief_interface_and_node_structure(relief):
obj, _, _, inputs, _ = relief
assert list(inputs) == ["Geometry", "Subdivide", "Thickness", "Depth Scale",
"Reference Depth", "Uniform Scale", "Depth Image"]
assert list(inputs) == ["Geometry", "Subdivide", "Thickness", "Depth Scale", "Smooth",
"Reference Depth", "Smooth Weight", "Depth Direction", "Uniform Scale", "Depth Image"]
assert inputs["Smooth"].default_value == 0
assert (inputs["Smooth"].min_value, inputs["Smooth"].max_value) == (0, 20)
assert inputs["Smooth Weight"].default_value == 1
assert inputs["Smooth Weight"].subtype == "FACTOR"
assert tuple(inputs["Depth Direction"].default_value) == (0, 0, 1)
assert inputs["Depth Direction"].subtype == "DIRECTION"
assert inputs["Thickness"].default_value == 0.5
assert inputs["Subdivide"].default_value == 6
assert inputs["Depth Scale"].default_value == 0.5
@@ -356,6 +413,8 @@ def test_plane_interface_panels(source, kind):
controls = ["Reference Depth"]
if kind == "DEPTH":
controls += ["Normal Smooth", "Smooth Weight", "Validity Threshold"]
else:
controls += ["Smooth Weight", "Depth Direction"]
assert [item.name for item in panels[0].interface_items] == controls
assert [item.name for item in panels[1].interface_items] == ["Uniform Scale", "Depth Image"]
assert all(panel.default_closed for panel in panels)