修正 mini_npz 深度数据职责
This commit is contained in:
@@ -9,7 +9,6 @@ DEPTH_KEY_CANDIDATES = ("depth", "depths")
|
|||||||
INTRINSICS_KEY_CANDIDATES = ("intrinsics", "ixts")
|
INTRINSICS_KEY_CANDIDATES = ("intrinsics", "ixts")
|
||||||
DISPLACEMENT_STRENGTH = 1.0
|
DISPLACEMENT_STRENGTH = 1.0
|
||||||
DISPLACEMENT_MIDLEVEL = 0.0
|
DISPLACEMENT_MIDLEVEL = 0.0
|
||||||
REFERENCE_DEPTH_KEY = "snapform_reference_depth"
|
|
||||||
ORIGINAL_DISPLACEMENT_STRENGTH = 1.0
|
ORIGINAL_DISPLACEMENT_STRENGTH = 1.0
|
||||||
ORIGINAL_DISPLACEMENT_MIDLEVEL = 0.0
|
ORIGINAL_DISPLACEMENT_MIDLEVEL = 0.0
|
||||||
BUMP_BASE_DISTANCE = 0.001
|
BUMP_BASE_DISTANCE = 0.001
|
||||||
@@ -695,25 +694,6 @@ def extract_intrinsics_array(npz_path):
|
|||||||
return intrinsics
|
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):
|
def prepare_depth_values(depth):
|
||||||
import numpy as np
|
import numpy as np
|
||||||
|
|
||||||
@@ -725,6 +705,13 @@ def prepare_depth_values(depth):
|
|||||||
return values
|
return values
|
||||||
|
|
||||||
|
|
||||||
|
def depth_displacement_values(depth):
|
||||||
|
"""Convert raw DA3 camera distance to positive outward displacement."""
|
||||||
|
import numpy as np
|
||||||
|
|
||||||
|
return 1.0 - np.asarray(depth, dtype=np.float32)
|
||||||
|
|
||||||
|
|
||||||
def camera_plane_bounds(
|
def camera_plane_bounds(
|
||||||
source_object,
|
source_object,
|
||||||
depth,
|
depth,
|
||||||
@@ -1407,18 +1394,19 @@ def build_depth_plane(
|
|||||||
depth_frames_directory=None,
|
depth_frames_directory=None,
|
||||||
):
|
):
|
||||||
source_name = source_object.name
|
source_name = source_object.name
|
||||||
depth_arrays = extract_depth_arrays(npz_path)
|
raw_depth_arrays = extract_depth_arrays(npz_path)
|
||||||
depth = depth_arrays[0]
|
raw_depth = raw_depth_arrays[0]
|
||||||
intrinsics = extract_intrinsics_array(npz_path)
|
intrinsics = extract_intrinsics_array(npz_path)
|
||||||
stored_reference_depth = extract_reference_depth(npz_path)
|
raw_depth_values = prepare_depth_values(raw_depth)
|
||||||
depth_values = prepare_depth_values(depth)
|
|
||||||
bounds, reference_depth = camera_plane_bounds(
|
bounds, reference_depth = camera_plane_bounds(
|
||||||
source_object,
|
source_object,
|
||||||
depth_values,
|
raw_depth_values,
|
||||||
intrinsics,
|
intrinsics,
|
||||||
DISPLACEMENT_STRENGTH,
|
DISPLACEMENT_STRENGTH,
|
||||||
reference_depth=stored_reference_depth,
|
|
||||||
)
|
)
|
||||||
|
depth_arrays = [
|
||||||
|
depth_displacement_values(frame) for frame in raw_depth_arrays
|
||||||
|
]
|
||||||
depth_name = depth_name or depth_data_name(source_object)
|
depth_name = depth_name or depth_data_name(source_object)
|
||||||
frame_count = len(depth_arrays)
|
frame_count = len(depth_arrays)
|
||||||
foreground_image = (
|
foreground_image = (
|
||||||
|
|||||||
@@ -36,7 +36,6 @@ HF_ENDPOINT = "https://huggingface.co"
|
|||||||
MODELSCOPE_ENDPOINT = "https://modelscope.cn"
|
MODELSCOPE_ENDPOINT = "https://modelscope.cn"
|
||||||
MODELSCOPE_REVISION = "master"
|
MODELSCOPE_REVISION = "master"
|
||||||
MODELSCOPE_TIMEOUT = 60.0
|
MODELSCOPE_TIMEOUT = 60.0
|
||||||
REFERENCE_DEPTH_KEY = "snapform_reference_depth"
|
|
||||||
|
|
||||||
|
|
||||||
class TaskCancelled(RuntimeError):
|
class TaskCancelled(RuntimeError):
|
||||||
@@ -48,37 +47,6 @@ def check_cancelled(cancel_check):
|
|||||||
raise TaskCancelled("Task cancelled")
|
raise TaskCancelled("Task cancelled")
|
||||||
|
|
||||||
|
|
||||||
def convert_ai_depth_for_displacement(prediction):
|
|
||||||
"""Convert DA3 distance into Blender's positive displacement direction."""
|
|
||||||
import numpy as np
|
|
||||||
|
|
||||||
prediction.depth = 1.0 - np.asarray(prediction.depth)
|
|
||||||
|
|
||||||
|
|
||||||
def depth_reference_value(depth):
|
|
||||||
"""Return the unmasked depth used to size the imported camera plane."""
|
|
||||||
import numpy as np
|
|
||||||
|
|
||||||
values = np.asarray(depth)
|
|
||||||
valid = np.isfinite(values) & (values > 0.0)
|
|
||||||
if not valid.any():
|
|
||||||
raise ValueError("Depth array contains no positive finite values")
|
|
||||||
return float(np.median(values[valid]))
|
|
||||||
|
|
||||||
|
|
||||||
def write_npz_reference_depth(npz_path, reference_depth):
|
|
||||||
"""Add SnapForm metadata without changing exported prediction arrays."""
|
|
||||||
import numpy as np
|
|
||||||
|
|
||||||
npz_path = Path(npz_path)
|
|
||||||
temporary_path = npz_path.with_name(f"{npz_path.stem}.tmp.npz")
|
|
||||||
with np.load(npz_path, allow_pickle=False) as archive:
|
|
||||||
values = {key: archive[key] for key in archive.files}
|
|
||||||
values[REFERENCE_DEPTH_KEY] = np.asarray(reference_depth, dtype=np.float32)
|
|
||||||
np.savez_compressed(temporary_path, **values)
|
|
||||||
temporary_path.replace(npz_path)
|
|
||||||
|
|
||||||
|
|
||||||
def write_status(
|
def write_status(
|
||||||
path,
|
path,
|
||||||
progress,
|
progress,
|
||||||
@@ -1640,10 +1608,6 @@ def reconstruct(args, model_cache=None, cancel_check=None):
|
|||||||
ref_view_strategy=args.ref_view_strategy,
|
ref_view_strategy=args.ref_view_strategy,
|
||||||
)
|
)
|
||||||
check_cancelled(cancel_check)
|
check_cancelled(cancel_check)
|
||||||
reference_depth = None
|
|
||||||
if args.export_format == "mini_npz":
|
|
||||||
reference_depth = depth_reference_value(prediction.depth[0])
|
|
||||||
convert_ai_depth_for_displacement(prediction)
|
|
||||||
write_status(args.status, 0.88, f"Exporting {args.export_format}")
|
write_status(args.status, 0.88, f"Exporting {args.export_format}")
|
||||||
if args.export_format == "mesh":
|
if args.export_format == "mesh":
|
||||||
export_textured_mesh_glb(
|
export_textured_mesh_glb(
|
||||||
@@ -1701,10 +1665,6 @@ def reconstruct(args, model_cache=None, cancel_check=None):
|
|||||||
result_paths,
|
result_paths,
|
||||||
cancel_check=cancel_check,
|
cancel_check=cancel_check,
|
||||||
)
|
)
|
||||||
if args.export_format == "mini_npz":
|
|
||||||
result_path = output_directory / "results.npz"
|
|
||||||
if reference_depth is not None:
|
|
||||||
write_npz_reference_depth(result_path, reference_depth)
|
|
||||||
check_cancelled(cancel_check)
|
check_cancelled(cancel_check)
|
||||||
copy_frames(images, output_directory)
|
copy_frames(images, output_directory)
|
||||||
write_status(
|
write_status(
|
||||||
|
|||||||
@@ -87,21 +87,6 @@ class ImageDepthTest(unittest.TestCase):
|
|||||||
|
|
||||||
self.assertEqual(depth.shape, (2, 3))
|
self.assertEqual(depth.shape, (2, 3))
|
||||||
|
|
||||||
def test_extracts_optional_reference_depth(self):
|
|
||||||
with tempfile.TemporaryDirectory() as directory:
|
|
||||||
npz_path = Path(directory) / "results.npz"
|
|
||||||
np.savez_compressed(
|
|
||||||
npz_path,
|
|
||||||
depth=np.ones((1, 2, 2), dtype=np.float32),
|
|
||||||
snapform_reference_depth=np.float32(2.5),
|
|
||||||
)
|
|
||||||
|
|
||||||
reference_depth = self.image_depth.extract_reference_depth(
|
|
||||||
npz_path
|
|
||||||
)
|
|
||||||
|
|
||||||
self.assertEqual(reference_depth, 2.5)
|
|
||||||
|
|
||||||
def test_is_animated_image_accepts_movie_and_sequence(self):
|
def test_is_animated_image_accepts_movie_and_sequence(self):
|
||||||
self.assertTrue(
|
self.assertTrue(
|
||||||
self.image_depth.is_animated_image(
|
self.image_depth.is_animated_image(
|
||||||
@@ -384,6 +369,23 @@ class ImageDepthTest(unittest.TestCase):
|
|||||||
|
|
||||||
self.assertEqual(depth.shape, (2, 3))
|
self.assertEqual(depth.shape, (2, 3))
|
||||||
|
|
||||||
|
def test_raw_da3_depth_is_converted_only_for_displacement(self):
|
||||||
|
raw_depth = np.array(
|
||||||
|
((0.25, 1.0), (1.5, 2.0)),
|
||||||
|
dtype=np.float32,
|
||||||
|
)
|
||||||
|
|
||||||
|
displacement = self.image_depth.depth_displacement_values(raw_depth)
|
||||||
|
|
||||||
|
np.testing.assert_array_equal(
|
||||||
|
displacement,
|
||||||
|
np.array(((0.75, 0.0), (-0.5, -1.0)), dtype=np.float32),
|
||||||
|
)
|
||||||
|
np.testing.assert_array_equal(
|
||||||
|
raw_depth,
|
||||||
|
np.array(((0.25, 1.0), (1.5, 2.0)), dtype=np.float32),
|
||||||
|
)
|
||||||
|
|
||||||
def test_extracts_camera_intrinsics(self):
|
def test_extracts_camera_intrinsics(self):
|
||||||
expected = np.array(
|
expected = np.array(
|
||||||
((100.0, 0.0, 50.0), (0.0, 120.0, 40.0), (0.0, 0.0, 1.0)),
|
((100.0, 0.0, 50.0), (0.0, 120.0, 40.0), (0.0, 0.0, 1.0)),
|
||||||
|
|||||||
@@ -157,40 +157,6 @@ class WorkerTest(unittest.TestCase):
|
|||||||
)
|
)
|
||||||
self.assertEqual(args.remove_background_model_dir, "ben2")
|
self.assertEqual(args.remove_background_model_dir, "ben2")
|
||||||
|
|
||||||
def test_ai_depth_is_converted_for_positive_displacement(self):
|
|
||||||
prediction = SimpleNamespace(
|
|
||||||
depth=np.array([[[0.25, 1.0], [1.5, 2.0]]]),
|
|
||||||
conf=np.array([[[0.1, 0.2], [0.3, 0.4]]]),
|
|
||||||
)
|
|
||||||
|
|
||||||
self.worker.convert_ai_depth_for_displacement(prediction)
|
|
||||||
|
|
||||||
np.testing.assert_array_equal(
|
|
||||||
prediction.depth,
|
|
||||||
[[[0.75, 0.0], [-0.5, -1.0]]],
|
|
||||||
)
|
|
||||||
np.testing.assert_array_equal(
|
|
||||||
prediction.conf,
|
|
||||||
[[[0.1, 0.2], [0.3, 0.4]]],
|
|
||||||
)
|
|
||||||
|
|
||||||
def test_npz_reference_depth_preserves_prediction_arrays(self):
|
|
||||||
with tempfile.TemporaryDirectory() as directory:
|
|
||||||
npz_path = Path(directory) / "results.npz"
|
|
||||||
depth = np.array([[[1.0, 2.0]]], dtype=np.float32)
|
|
||||||
confidence = np.array([[[0.2, 0.8]]], dtype=np.float32)
|
|
||||||
np.savez_compressed(npz_path, depth=depth, conf=confidence)
|
|
||||||
|
|
||||||
self.worker.write_npz_reference_depth(npz_path, 1.5)
|
|
||||||
|
|
||||||
with np.load(npz_path, allow_pickle=False) as archive:
|
|
||||||
np.testing.assert_array_equal(archive["depth"], depth)
|
|
||||||
np.testing.assert_array_equal(archive["conf"], confidence)
|
|
||||||
self.assertEqual(
|
|
||||||
float(archive[self.worker.REFERENCE_DEPTH_KEY]),
|
|
||||||
1.5,
|
|
||||||
)
|
|
||||||
|
|
||||||
def test_prepares_a_single_image_file(self):
|
def test_prepares_a_single_image_file(self):
|
||||||
with tempfile.TemporaryDirectory() as directory:
|
with tempfile.TemporaryDirectory() as directory:
|
||||||
image_path = Path(directory) / "input.png"
|
image_path = Path(directory) / "input.png"
|
||||||
@@ -1013,12 +979,9 @@ class WorkerTest(unittest.TestCase):
|
|||||||
_export_dir,
|
_export_dir,
|
||||||
options,
|
options,
|
||||||
) in export_calls:
|
) in export_calls:
|
||||||
expected_depth = (
|
|
||||||
-1.0 if export_format == "mini_npz" else 2.0
|
|
||||||
)
|
|
||||||
np.testing.assert_array_equal(
|
np.testing.assert_array_equal(
|
||||||
_prediction.depth,
|
_prediction.depth,
|
||||||
np.full((1, 2, 2), expected_depth, dtype=np.float32),
|
np.full((1, 2, 2), 2.0, dtype=np.float32),
|
||||||
)
|
)
|
||||||
if export_format == "glb":
|
if export_format == "glb":
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
|
|||||||
Reference in New Issue
Block a user