feat: Implement background depth distance feature for depth processing
- Added `background_depth_distance` property to `ActiveJob` and `RunJob` classes to manage background depth settings. - Updated job execution to handle background depth distance, allowing for fixed negative displacement in depth calculations. - Introduced new functions in the worker module to apply background depth distance to depth frames and manage original background depth. - Enhanced tests to cover new functionality, ensuring correct behavior of background depth processing and reference depth extraction. - Refactored job submission to run in a separate thread, improving responsiveness during execution.
This commit is contained in:
@@ -7,8 +7,9 @@ import bpy
|
||||
|
||||
DEPTH_KEY_CANDIDATES = ("depth", "depths")
|
||||
INTRINSICS_KEY_CANDIDATES = ("intrinsics", "ixts")
|
||||
DISPLACEMENT_STRENGTH = -1.0
|
||||
DISPLACEMENT_MIDLEVEL = 1.0
|
||||
DISPLACEMENT_STRENGTH = 1.0
|
||||
DISPLACEMENT_MIDLEVEL = 0.0
|
||||
REFERENCE_DEPTH_KEY = "snapform_reference_depth"
|
||||
ORIGINAL_DISPLACEMENT_STRENGTH = 1.0
|
||||
ORIGINAL_DISPLACEMENT_MIDLEVEL = 0.0
|
||||
BUMP_BASE_DISTANCE = 0.001
|
||||
@@ -548,12 +549,28 @@ def apply_plane_transform(plane_object, source_object, camera_bounds):
|
||||
(
|
||||
(scale_x, 0.0, 0.0, offset_x),
|
||||
(0.0, scale_y, 0.0, offset_y),
|
||||
(0.0, 0.0, scale_z, 0.0),
|
||||
(0.0, 0.0, 1.0, 0.0),
|
||||
(0.0, 0.0, 0.0, 1.0),
|
||||
)
|
||||
)
|
||||
plane_object.matrix_world = source_object.matrix_world @ fit_matrix
|
||||
return abs(float(plane_object.scale.z))
|
||||
return abs(float(plane_object.scale.z)) * abs(scale_z)
|
||||
|
||||
|
||||
def apply_object_scale_to_mesh(plane_object):
|
||||
"""Bake object scale into the plane while keeping location and rotation."""
|
||||
from mathutils import Matrix
|
||||
|
||||
scale = plane_object.scale.copy()
|
||||
plane_object.data.transform(
|
||||
Matrix.Diagonal((scale.x, scale.y, scale.z, 1.0))
|
||||
)
|
||||
plane_object.data.update()
|
||||
plane_object.scale = (1.0, 1.0, 1.0)
|
||||
|
||||
|
||||
def scale_depth_values(depth, depth_scale):
|
||||
return prepare_depth_values(depth) * depth_scale
|
||||
|
||||
|
||||
def extract_depth_arrays(npz_path):
|
||||
@@ -634,12 +651,31 @@ def extract_intrinsics_array(npz_path):
|
||||
return intrinsics
|
||||
|
||||
|
||||
def extract_reference_depth(npz_path):
|
||||
try:
|
||||
import numpy as np
|
||||
except ImportError as error:
|
||||
raise RuntimeError("Blender Python does not provide NumPy") from error
|
||||
|
||||
try:
|
||||
with np.load(npz_path, allow_pickle=False) as archive:
|
||||
if REFERENCE_DEPTH_KEY not in archive.files:
|
||||
return None
|
||||
reference_depth = float(archive[REFERENCE_DEPTH_KEY])
|
||||
except OSError as error:
|
||||
raise ValueError(f"Unable to read Mini NPZ: {npz_path}") from error
|
||||
|
||||
if not np.isfinite(reference_depth) or reference_depth <= 0.0:
|
||||
raise ValueError("Mini NPZ has an invalid reference depth")
|
||||
return reference_depth
|
||||
|
||||
|
||||
def prepare_depth_values(depth):
|
||||
import numpy as np
|
||||
|
||||
valid = np.isfinite(depth) & (depth > 0.0)
|
||||
valid = np.isfinite(depth)
|
||||
if not valid.any():
|
||||
raise ValueError("Depth array contains no positive finite values")
|
||||
raise ValueError("Depth array contains no finite values")
|
||||
values = depth.astype(np.float32, copy=True)
|
||||
values[~valid] = 0.0
|
||||
return values
|
||||
@@ -650,6 +686,7 @@ def camera_plane_bounds(
|
||||
depth,
|
||||
intrinsics,
|
||||
displacement_strength,
|
||||
reference_depth=None,
|
||||
):
|
||||
import numpy as np
|
||||
|
||||
@@ -657,10 +694,11 @@ def camera_plane_bounds(
|
||||
if scale <= 1e-8:
|
||||
raise ValueError("Displacement Strength must not be zero")
|
||||
|
||||
valid = np.isfinite(depth) & (depth > 0.0)
|
||||
if not valid.any():
|
||||
raise ValueError("Depth array contains no positive finite values")
|
||||
reference_depth = float(np.median(depth[valid]))
|
||||
if reference_depth is None:
|
||||
valid = np.isfinite(depth) & (depth > 0.0)
|
||||
if not valid.any():
|
||||
raise ValueError("Depth array contains no positive finite values")
|
||||
reference_depth = float(np.median(depth[valid]))
|
||||
reference_distance = reference_depth * scale
|
||||
|
||||
height, width = depth.shape
|
||||
@@ -978,6 +1016,7 @@ def build_image_depth_plane(
|
||||
unshaded=False,
|
||||
color_image=None,
|
||||
depth_frames_directory=None,
|
||||
depth_image=None,
|
||||
):
|
||||
source_name = source_object.name
|
||||
frame_count = 1
|
||||
@@ -1001,11 +1040,12 @@ def build_image_depth_plane(
|
||||
frame_count if uses_extracted else source_frame_duration
|
||||
)
|
||||
texture_frame_offset = 0 if uses_extracted else source_frame_offset
|
||||
depth_source_image = source_object.data
|
||||
if frames_image is not None:
|
||||
depth_source_image = frames_image
|
||||
elif not is_animated_image(source_object.data):
|
||||
depth_source_image = copy_depth_image(source_object)
|
||||
depth_source_image = depth_image or source_object.data
|
||||
if depth_image is None:
|
||||
if frames_image is not None:
|
||||
depth_source_image = frames_image
|
||||
elif not is_animated_image(source_object.data):
|
||||
depth_source_image = copy_depth_image(source_object)
|
||||
mesh = create_plane_mesh(
|
||||
source_object,
|
||||
subdivision_level=subdivision_level,
|
||||
@@ -1085,12 +1125,14 @@ def build_depth_plane(
|
||||
depth_arrays = extract_depth_arrays(npz_path)
|
||||
depth = depth_arrays[0]
|
||||
intrinsics = extract_intrinsics_array(npz_path)
|
||||
stored_reference_depth = extract_reference_depth(npz_path)
|
||||
depth_values = prepare_depth_values(depth)
|
||||
bounds, reference_depth = camera_plane_bounds(
|
||||
source_object,
|
||||
depth_values,
|
||||
intrinsics,
|
||||
DISPLACEMENT_STRENGTH,
|
||||
reference_depth=stored_reference_depth,
|
||||
)
|
||||
depth_name = depth_name or depth_data_name(source_object)
|
||||
frame_count = len(depth_arrays)
|
||||
@@ -1100,18 +1142,6 @@ def build_depth_plane(
|
||||
source_frame_duration,
|
||||
source_use_cyclic,
|
||||
) = image_user_settings(source_object)
|
||||
depth_image = create_depth_image(
|
||||
depth_name,
|
||||
depth_values,
|
||||
)
|
||||
if frame_count > 1 and depth_frames_directory is not None:
|
||||
bpy.data.images.remove(depth_image, do_unlink=True)
|
||||
depth_image = create_depth_sequence_image(
|
||||
depth_name,
|
||||
[prepare_depth_values(frame) for frame in depth_arrays],
|
||||
context.scene,
|
||||
depth_frames_directory,
|
||||
)
|
||||
mesh = create_plane_mesh(source_object, bounds, subdivision_level)
|
||||
plane_object = bpy.data.objects.new(
|
||||
source_name,
|
||||
@@ -1123,6 +1153,22 @@ def build_depth_plane(
|
||||
source_object,
|
||||
bounds,
|
||||
)
|
||||
apply_object_scale_to_mesh(plane_object)
|
||||
scaled_depth_arrays = [
|
||||
scale_depth_values(frame, depth_scale) for frame in depth_arrays
|
||||
]
|
||||
depth_image = create_depth_image(
|
||||
depth_name,
|
||||
scaled_depth_arrays[0],
|
||||
)
|
||||
if frame_count > 1 and depth_frames_directory is not None:
|
||||
bpy.data.images.remove(depth_image, do_unlink=True)
|
||||
depth_image = create_depth_sequence_image(
|
||||
depth_name,
|
||||
scaled_depth_arrays,
|
||||
context.scene,
|
||||
depth_frames_directory,
|
||||
)
|
||||
|
||||
subdivision = plane_object.modifiers.new("Subdivision", "SUBSURF")
|
||||
subdivision.subdivision_type = "SIMPLE"
|
||||
@@ -1298,6 +1344,9 @@ class BuildOriginalDepthPlane(bpy.types.Operator):
|
||||
snapform_depth_frames_dir: bpy.props.StringProperty(
|
||||
options={"HIDDEN", "SKIP_SAVE"},
|
||||
)
|
||||
snapform_depth_npz_path: bpy.props.StringProperty(
|
||||
options={"HIDDEN", "SKIP_SAVE"},
|
||||
)
|
||||
snapform_subdivision_level: bpy.props.IntProperty(
|
||||
default=5,
|
||||
min=0,
|
||||
@@ -1342,6 +1391,23 @@ class BuildOriginalDepthPlane(bpy.types.Operator):
|
||||
f"{image_base_name(source_object.data)}.frame",
|
||||
self.snapform_color_frames_dir,
|
||||
)
|
||||
depth_image = None
|
||||
if self.snapform_depth_npz_path:
|
||||
depth_arrays = extract_depth_arrays(
|
||||
self.snapform_depth_npz_path
|
||||
)
|
||||
if len(depth_arrays) == 1:
|
||||
depth_image = create_depth_image(
|
||||
depth_data_name(source_object),
|
||||
depth_arrays[0],
|
||||
)
|
||||
else:
|
||||
depth_image = create_depth_sequence_image(
|
||||
depth_data_name(source_object),
|
||||
depth_arrays,
|
||||
context.scene,
|
||||
self.snapform_depth_frames_dir,
|
||||
)
|
||||
build_image_depth_plane(
|
||||
context,
|
||||
source_object,
|
||||
@@ -1353,6 +1419,7 @@ class BuildOriginalDepthPlane(bpy.types.Operator):
|
||||
depth_frames_directory=(
|
||||
self.snapform_depth_frames_dir or None
|
||||
),
|
||||
depth_image=depth_image,
|
||||
)
|
||||
except (OSError, RuntimeError, ValueError) as error:
|
||||
self.report({"ERROR"}, str(error))
|
||||
@@ -1474,10 +1541,12 @@ class ConvertImageToDepthPlane(bpy.types.Operator):
|
||||
description="Display the source color through an Emission shader",
|
||||
default=False,
|
||||
)
|
||||
snapform_remove_background: bpy.props.BoolProperty(
|
||||
name="Remove Background",
|
||||
description="Remove the color image background with BEN2",
|
||||
default=False,
|
||||
snapform_background_depth_distance: bpy.props.FloatProperty(
|
||||
name="Background Depth Distance",
|
||||
description="Set excluded background to this fixed depth distance; zero disables background exclusion",
|
||||
default=0.0,
|
||||
min=0.0,
|
||||
soft_max=10.0,
|
||||
)
|
||||
snapform_model: bpy.props.EnumProperty(
|
||||
name="Model",
|
||||
@@ -1572,7 +1641,10 @@ class ConvertImageToDepthPlane(bpy.types.Operator):
|
||||
settings_body.prop(self, "snapform_subdivision_level")
|
||||
remove_background_row = settings_body.row()
|
||||
remove_background_row.enabled = model_ready(BEN2_MODEL.key)
|
||||
remove_background_row.prop(self, "snapform_remove_background")
|
||||
remove_background_row.prop(
|
||||
self,
|
||||
"snapform_background_depth_distance",
|
||||
)
|
||||
settings_body.prop(self, "snapform_unshaded")
|
||||
layout.prop(self, "snapform_delete_original")
|
||||
|
||||
@@ -1590,7 +1662,7 @@ class ConvertImageToDepthPlane(bpy.types.Operator):
|
||||
)
|
||||
if (
|
||||
self.snapform_depth_source == "ORIGINAL"
|
||||
and not self.snapform_remove_background
|
||||
and self.snapform_background_depth_distance <= 0.0
|
||||
):
|
||||
if video_total_frames(source_object.data) > 1:
|
||||
try:
|
||||
@@ -1657,6 +1729,9 @@ class ConvertImageToDepthPlane(bpy.types.Operator):
|
||||
snapform_delete_input=delete_input,
|
||||
snapform_displacement_via=self.snapform_displacement_via,
|
||||
snapform_unshaded=self.snapform_unshaded,
|
||||
snapform_background_depth_distance=(
|
||||
self.snapform_background_depth_distance
|
||||
),
|
||||
)
|
||||
if "CANCELLED" not in result:
|
||||
return {"FINISHED"}
|
||||
@@ -1695,7 +1770,9 @@ class ConvertImageToDepthPlane(bpy.types.Operator):
|
||||
snapform_delete_input=delete_input,
|
||||
snapform_displacement_via=self.snapform_displacement_via,
|
||||
snapform_unshaded=self.snapform_unshaded,
|
||||
snapform_remove_background=self.snapform_remove_background,
|
||||
snapform_background_depth_distance=(
|
||||
self.snapform_background_depth_distance
|
||||
),
|
||||
)
|
||||
if "CANCELLED" in result:
|
||||
if delete_input:
|
||||
|
||||
Reference in New Issue
Block a user