Add comprehensive documentation for SnapForm extension

- Created development.md for development setup and licensing information.
- Added features.md detailing functionalities and usage instructions for developers and users.
- Introduced index.md to serve as a navigation hub for the documentation.
- Developed outputs.md to explain output formats and their specifications.
- Established service.md to describe the AI Service architecture and environment setup.
- Created surface.md to outline the process of converting Image Empty to perspective meshes.
This commit is contained in:
2026-08-13 23:57:09 +08:00
parent caeb6d706c
commit 13fab9ce88
28 changed files with 998 additions and 830 deletions

Binary file not shown.

View File

@@ -1,7 +1,7 @@
schema_version = "1.0.0"
id = "snapform"
version = "0.10.11"
version = "0.10.12"
name = "SnapForm"
tagline = "Build spatial structures from images, fast"
maintainer = "OmooLab"

View File

@@ -167,6 +167,14 @@ def depth_data_name(source_object):
return f"{image_base_name(source_object.data)}.depth"
def normal_data_name(source_object):
return f"{image_base_name(source_object.data)}.normal"
def mask_data_name(source_object):
return f"{image_base_name(source_object.data)}.mask"
def copy_depth_image(source_object):
"""Return a renamed copy of the source image for use as the depth map."""
depth_image = source_object.data.copy()
@@ -818,11 +826,12 @@ def create_moge_mask_image(name, mask_values):
return image
def create_moge_normal_image(name, normal_values):
def create_moge_normal_image(name, normal_values, convert_coordinates=True):
import numpy as np
converted = normal_values.astype(np.float32, copy=True)
converted *= np.array([1.0, -1.0, -1.0], dtype=np.float32)
if convert_coordinates:
converted *= np.array([1.0, -1.0, -1.0], dtype=np.float32)
converted = np.clip(converted * 0.5 + 0.5, 0.0, 1.0)
height, width = converted.shape[:2]
image = bpy.data.images.new(
@@ -1144,6 +1153,9 @@ def material_node_group(depth_plane=False, surface_group_name=None):
("Alpha", "NodeSocketFloat"),
("Bump Scale", "NodeSocketFloat"),
]
if group_name == MOGE_SURFACE_MATERIAL_NODE_GROUP_NAME:
input_sockets.insert(2, ("Normal", "NodeSocketColor"))
input_sockets.insert(3, ("Normal Strength", "NodeSocketFloat"))
if depth_plane:
input_sockets.insert(2, ("Depth", "NodeSocketFloat"))
for name, socket_type in input_sockets:
@@ -1161,6 +1173,10 @@ def material_node_group(depth_plane=False, surface_group_name=None):
socket.default_value = 1.0
socket.min_value = -10000.0
socket.max_value = 10000.0
elif name == "Normal Strength":
socket.default_value = 1.0
socket.min_value = 0.0
socket.max_value = 10.0
output_sockets = [
("Color", "NodeSocketColor"),
("Roughness", "NodeSocketFloat"),
@@ -1191,6 +1207,16 @@ def material_node_group(depth_plane=False, surface_group_name=None):
roughness_curve.location = (-250.0, 350.0)
bump = nodes.new("ShaderNodeBump")
bump.location = (-250.0, 0.0)
if group_name == MOGE_SURFACE_MATERIAL_NODE_GROUP_NAME:
normal_map = nodes.new("ShaderNodeNormalMap")
normal_map.space = "OBJECT"
normal_map.location = (-500.0, -150.0)
links.new(group_input.outputs["Normal"], normal_map.inputs["Color"])
links.new(
group_input.outputs["Normal Strength"],
normal_map.inputs["Strength"],
)
links.new(normal_map.outputs["Normal"], bump.inputs["Normal"])
displacement = None
processed_alpha = group_input.outputs["Alpha"]
@@ -1295,6 +1321,7 @@ def create_material(
depth_frame_duration=None,
use_cyclic=None,
surface_group_name=None,
normal_image=None,
):
material = bpy.data.materials.new(name)
material.use_nodes = True
@@ -1359,6 +1386,14 @@ def create_material(
material_inputs.inputs["Bump Scale"].default_value = depth_scale
links.new(color_texture.outputs["Color"], material_inputs.inputs["Color"])
links.new(alpha, material_inputs.inputs["Alpha"])
if normal_image is not None and "Normal" in material_inputs.inputs:
normal_texture = nodes.new("ShaderNodeTexImage")
normal_texture.image = normal_image
normal_texture.interpolation = "Linear"
normal_texture.extension = "EXTEND"
normal_texture.location = (-1000.0, -50.0)
links.new(uv_map.outputs["UV"], normal_texture.inputs["Vector"])
links.new(normal_texture.outputs["Color"], material_inputs.inputs["Normal"])
if unshaded:
emission = nodes.new("ShaderNodeEmission")
transparent = nodes.new("ShaderNodeBsdfTransparent")
@@ -1923,13 +1958,13 @@ def build_moge2_depth_plane(
depth_scale = apply_plane_transform(plane_object, source_object, bounds)
apply_object_scale_to_mesh(plane_object)
depth_image = create_depth_image(
f"{depth_data_name(source_object)}.moge2",
depth_data_name(source_object),
displacement[0] * depth_scale,
)
if len(depth) > 1 and data_frames_directory is not None:
bpy.data.images.remove(depth_image, do_unlink=True)
depth_image = create_moge_sequence_image(
f"{depth_data_name(source_object)}.moge2",
depth_data_name(source_object),
[frame * depth_scale for frame in displacement],
context.scene,
data_frames_directory,
@@ -2201,7 +2236,7 @@ class ConvertImageToDepthPlane(bpy.types.Operator):
default=False,
)
snapform_model: bpy.props.EnumProperty(
name="Model",
name="Depth From",
description="Model or original image used to build depth",
items=image_depth_model_items,
default=1,

View File

@@ -7,6 +7,7 @@ from .image_depth import (
create_material,
draw_process_resolution,
is_image_empty,
normal_data_name,
plane_bounds,
prepare_image_input,
process_resolution_scale_property,
@@ -18,22 +19,16 @@ from .image_depth import (
POINT_ATTRIBUTES = {
"o_depth": ("FLOAT", "point_depth"),
"o_confidence": ("FLOAT", "point_confidence"),
"o_confidence_percentile": (
"FLOAT",
"point_confidence_percentile",
),
"o_background": ("BOOLEAN", "point_background"),
}
FACE_ATTRIBUTES = {
"o_face_confidence": (
"FLOAT",
"face_confidence_percentile",
),
"o_background_face": ("BOOLEAN", "face_background"),
}
OPTIONAL_FACE_ATTRIBUTES = {
"o_mask": ("BOOLEAN", "face_mask"),
"o_depth_discontinuity": ("FLOAT", "face_depth_discontinuity"),
}
NODE_GROUP_NAME = "O DA3 Surface"
MOGE_NODE_GROUP_NAME = "O MoGe2 Surface"
@@ -61,10 +56,10 @@ def load_image_mesh_data(npz_path, moge2=False):
"point_depth",
]
if moge2:
required.append("face_mask")
required.extend(("face_mask", "face_depth_discontinuity", "normal_map"))
else:
required.extend(
("point_confidence", "point_confidence_percentile", "point_background", "face_confidence_percentile", "face_background")
("face_confidence_percentile", "face_depth_discontinuity")
)
missing = [key for key in required if key not in archive.files]
if missing:
@@ -93,9 +88,9 @@ def load_image_mesh_data(npz_path, moge2=False):
for _name, (_data_type, key) in OPTIONAL_FACE_ATTRIBUTES.items():
if key in archive.files:
attributes[key] = np.asarray(archive[key])
if "vertex_normal" in archive.files:
attributes["vertex_normal"] = np.asarray(
archive["vertex_normal"],
if "normal_map" in archive.files:
attributes["normal_map"] = np.asarray(
archive["normal_map"],
dtype=np.float32,
)
except OSError as error:
@@ -112,6 +107,14 @@ def load_image_mesh_data(npz_path, moge2=False):
raise ValueError(f"Unsupported image size: {image_size}")
if not reference_depth > 0.0:
raise ValueError("Surface reference depth must be positive")
if moge2 and attributes["normal_map"].shape != (
image_size[1],
image_size[0],
3,
):
raise ValueError("Surface normal map and image sizes do not match")
if moge2 and not np.isfinite(attributes["normal_map"]).all():
raise ValueError("Surface normal map contains invalid values")
for _name, (_data_type, key) in POINT_ATTRIBUTES.items():
if key in attributes and len(attributes[key]) != len(vertices):
raise ValueError(f"Point attribute has the wrong length: {key}")
@@ -208,6 +211,13 @@ def assign_snapform_attributes(mesh, attributes, moge2=False):
if moge2:
assign_mesh_attribute(mesh, "o_depth", "FLOAT", "POINT", attributes["point_depth"])
assign_mesh_attribute(mesh, "o_mask", "BOOLEAN", "FACE", attributes["face_mask"])
assign_mesh_attribute(
mesh,
"o_depth_discontinuity",
"FLOAT",
"FACE",
attributes["face_depth_discontinuity"],
)
return
for name, (data_type, key) in POINT_ATTRIBUTES.items():
assign_mesh_attribute(
@@ -234,50 +244,12 @@ def assign_snapform_attributes(mesh, attributes, moge2=False):
"FACE",
attributes[key],
)
assign_edge_depth_difference(mesh, attributes["point_depth"])
def assign_edge_depth_difference(mesh, point_depth):
import numpy as np
edge_vertices = np.empty(len(mesh.edges) * 2, dtype=np.int32)
mesh.edges.foreach_get("vertices", edge_vertices)
edge_vertices = edge_vertices.reshape(-1, 2)
depth = np.asarray(point_depth, dtype=np.float32)
first = depth[edge_vertices[:, 0]]
second = depth[edge_vertices[:, 1]]
denominator = np.maximum(np.minimum(np.abs(first), np.abs(second)), 1e-6)
difference = np.abs(first - second) / denominator
assign_mesh_attribute(
mesh,
"o_edge_stretch",
"o_depth_discontinuity",
"FLOAT",
"EDGE",
difference,
)
def remove_surface_background(vertices, faces, uv, attributes):
"""Remove background faces and compact matching point/face data."""
import numpy as np
background_faces = np.asarray(attributes["face_background"], dtype=bool)
kept_faces = faces[~background_faces]
if not len(kept_faces):
raise ValueError("Background removal deleted every surface face")
used_vertices = np.unique(kept_faces)
index_map = np.full(len(vertices), -1, dtype=np.int32)
index_map[used_vertices] = np.arange(len(used_vertices), dtype=np.int32)
filtered_attributes = {}
for _name, (_data_type, key) in POINT_ATTRIBUTES.items():
filtered_attributes[key] = attributes[key][used_vertices]
for _name, (_data_type, key) in FACE_ATTRIBUTES.items():
filtered_attributes[key] = attributes[key][~background_faces]
return (
vertices[used_vertices],
index_map[kept_faces],
uv[used_vertices],
filtered_attributes,
"FACE",
attributes["face_depth_discontinuity"],
)
@@ -285,7 +257,7 @@ def prepare_moge_surface_archive(results_path, output_path):
import numpy as np
with np.load(results_path, allow_pickle=False) as archive:
required = {"depth", "mask", "points", "intrinsics"}
required = {"depth", "mask", "normal", "points", "intrinsics"}
missing = required.difference(archive.files)
if missing:
raise ValueError(f"MoGe-2 surface data is missing: {', '.join(sorted(missing))}")
@@ -293,11 +265,7 @@ def prepare_moge_surface_archive(results_path, output_path):
mask = np.asarray(archive["mask"], dtype=bool)
points = np.asarray(archive["points"], dtype=np.float32)
intrinsics = np.asarray(archive["intrinsics"], dtype=np.float32)
normal = (
np.asarray(archive["normal"], dtype=np.float32)
if "normal" in archive.files
else None
)
normal = np.asarray(archive["normal"], dtype=np.float32)
if depth.shape[0] != 1 or points.shape[:3] != (*depth.shape,):
raise ValueError("Convert to Surface requires one matching MoGe-2 point grid")
raw_depth = depth[0]
@@ -314,8 +282,9 @@ def prepare_moge_surface_archive(results_path, output_path):
if not len(faces):
raise ValueError("MoGe-2 output does not contain a valid surface")
flat_points = points[0].reshape(-1, 3)
clean_mask = mask[0] & ~depth_discontinuity_mask(raw_depth, 0.04)
face_mask = clean_mask.reshape(-1)[faces].all(axis=1)
face_mask = mask[0].reshape(-1)[faces].all(axis=1)
depth_edge_ratio = depth_discontinuity_ratio(raw_depth)
face_depth_discontinuity = depth_edge_ratio.reshape(-1)[faces].max(axis=1)
used = np.unique(faces)
index_map = np.full(height * width, -1, dtype=np.int32)
index_map[used] = np.arange(len(used), dtype=np.int32)
@@ -341,32 +310,21 @@ def prepare_moge_surface_archive(results_path, output_path):
image_size=np.array((width, height), dtype=np.int32),
reference_depth=np.float32(reference_depth),
point_depth=point_depth,
vertex_normal=(
normal[0].reshape(-1, 3)[used]
if normal is not None
else np.empty((0, 3), dtype=np.float32)
),
normal_map=normal[0],
face_mask=face_mask,
face_depth_discontinuity=face_depth_discontinuity,
)
return output_path
def depth_discontinuity_mask(depth, threshold):
def depth_discontinuity_ratio(depth):
import numpy as np
edges = np.zeros(depth.shape, dtype=bool)
for axis in (0, 1):
first = np.take(depth, range(depth.shape[axis] - 1), axis=axis)
second = np.take(depth, range(1, depth.shape[axis]), axis=axis)
scale = np.maximum(np.minimum(np.abs(first), np.abs(second)), 1e-6)
discontinuity = np.abs(first - second) / scale > threshold
lower = [slice(None), slice(None)]
upper = [slice(None), slice(None)]
lower[axis] = slice(0, -1)
upper[axis] = slice(1, None)
edges[tuple(lower)] |= discontinuity
edges[tuple(upper)] |= discontinuity
return edges
padded = np.pad(depth, 1, mode="edge")
windows = np.lib.stride_tricks.sliding_window_view(padded, (3, 3))
difference = np.nanmax(windows, axis=(-2, -1))
difference -= np.nanmin(windows, axis=(-2, -1))
return difference / np.maximum(depth, 1e-6)
def node_asset_path():
@@ -505,7 +463,6 @@ def build_image_mesh(
delete_source,
create_camera,
unshaded,
remove_background=False,
moge2=False,
):
import numpy as np
@@ -519,13 +476,6 @@ def build_image_mesh(
reference_depth,
attributes,
) = load_image_mesh_data(npz_path, moge2=moge2)
if remove_background:
camera_vertices, faces, uv, attributes = remove_surface_background(
camera_vertices,
faces,
uv,
attributes,
)
vertices, camera_parameters = empty_anchored_vertices(
source_object,
camera_vertices,
@@ -541,24 +491,25 @@ def build_image_mesh(
mesh.update()
for polygon in mesh.polygons:
polygon.use_smooth = True
if moge2 and len(attributes.get("vertex_normal", ())) == len(vertices):
normals = attributes["vertex_normal"].copy()
normals[:, 0] /= camera_parameters["scale_x"]
normals[:, 1] /= -camera_parameters["scale_y"]
normals[:, 2] /= -camera_parameters["scale_z"]
lengths = np.linalg.norm(normals, axis=1, keepdims=True)
normals /= np.maximum(lengths, 1e-6)
set_custom_normals = getattr(
mesh,
"normals_split_custom_set_from_vertices",
None,
)
if callable(set_custom_normals):
set_custom_normals(normals.tolist())
mesh_object = bpy.data.objects.new(source_name, mesh)
context.collection.objects.link(mesh_object)
mesh_object.matrix_world = source_object.matrix_world.copy()
depth_scale = material_depth_scale(mesh_object, camera_parameters)
normal_image = None
if moge2 and attributes.get("normal_map", np.empty(0)).size:
from .image_depth import create_moge_normal_image
normal_map = attributes["normal_map"].copy()
normal_map[..., 0] /= camera_parameters["scale_x"]
normal_map[..., 1] /= -camera_parameters["scale_y"]
normal_map[..., 2] /= -camera_parameters["scale_z"]
lengths = np.linalg.norm(normal_map, axis=-1, keepdims=True)
normal_map /= np.maximum(lengths, 1e-6)
normal_image = create_moge_normal_image(
normal_data_name(source_object),
normal_map,
convert_coordinates=False,
)
material = create_material(
source_name,
source_object.data,
@@ -567,6 +518,7 @@ def build_image_mesh(
surface_group_name=(
MOGE_SURFACE_MATERIAL_NODE_GROUP_NAME if moge2 else None
),
normal_image=normal_image,
)
mesh.materials.append(material)
mesh_object["snapform_reference_depth"] = reference_depth
@@ -620,10 +572,6 @@ class BuildImageMesh(bpy.types.Operator):
default=False,
options={"HIDDEN", "SKIP_SAVE"},
)
snapform_remove_background: bpy.props.BoolProperty(
default=False,
options={"HIDDEN", "SKIP_SAVE"},
)
snapform_moge2: bpy.props.BoolProperty(
default=False,
options={"HIDDEN", "SKIP_SAVE"},
@@ -642,7 +590,6 @@ class BuildImageMesh(bpy.types.Operator):
self.snapform_delete_source,
self.snapform_create_camera,
self.snapform_unshaded,
self.snapform_remove_background,
self.snapform_moge2,
)
except (OSError, RuntimeError, ValueError) as error:
@@ -658,7 +605,7 @@ class ConvertImageToMesh(bpy.types.Operator):
bl_options = {"REGISTER"}
snapform_model: bpy.props.EnumProperty(
name="Model",
name="Depth From",
description="Model used to estimate image depth",
items=image_surface_model_items,
default=1,

View File

@@ -822,7 +822,6 @@ class RunJob(bpy.types.Operator):
snapform_delete_source=job.delete_source,
snapform_create_camera=job.mesh_create_camera,
snapform_unshaded=job.unshaded,
snapform_remove_background=job.remove_background,
)
if "FINISHED" not in result:
raise RuntimeError("Unable to build the perspective surface")
@@ -841,7 +840,6 @@ class RunJob(bpy.types.Operator):
snapform_delete_source=job.delete_source,
snapform_create_camera=job.mesh_create_camera,
snapform_unshaded=job.unshaded,
snapform_remove_background=False,
snapform_moge2=True,
)
if "FINISHED" not in result:

View File

@@ -125,11 +125,12 @@ class ModelsPanel(bpy.types.Panel):
layout = self.layout
running = active_job() is not None or client.service_busy()
for model in MOGE2_MODELS.values():
self._draw_model(layout, model, running)
for model in MODELS.values():
if model.enabled:
self._draw_model(layout, model, running)
for model in MOGE2_MODELS.values():
self._draw_model(layout, model, running)
layout.separator(type="LINE")
self._draw_model(layout, BEN2_MODEL, running)
def _draw_model(self, layout, model, running):
@@ -190,7 +191,7 @@ def draw_da3_input_settings(layout, settings, model_property):
class Da3OutputDebugPanel(bpy.types.Panel):
bl_label = "DA3 Output Test"
bl_label = "DA3"
bl_idname = "SNAPFORM_PT_debug_da3_output"
bl_parent_id = DebugPanel.bl_idname
bl_space_type = "VIEW_3D"
@@ -240,7 +241,7 @@ class Da3OutputDebugPanel(bpy.types.Panel):
class Moge2ApiDebugPanel(bpy.types.Panel):
bl_label = "MoGe-2 API Test"
bl_label = "MoGe-2"
bl_idname = "SNAPFORM_PT_debug_moge2_api"
bl_parent_id = DebugPanel.bl_idname
bl_space_type = "VIEW_3D"
@@ -271,7 +272,7 @@ class Moge2ApiDebugPanel(bpy.types.Panel):
class Ben2RemovalDebugPanel(bpy.types.Panel):
bl_label = "BEN2 Removal Test"
bl_label = "BEN2"
bl_idname = "SNAPFORM_PT_debug_ben2_removal"
bl_parent_id = DebugPanel.bl_idname
bl_space_type = "VIEW_3D"

View File

@@ -110,6 +110,8 @@ class ModelCache:
def get_da3(self, model_dir, device):
key = (str(Path(model_dir).resolve()), device.type)
if self.moge_model is not None:
self.release_moge()
if self.da3_model is not None and self.da3_key == key:
return self.da3_model
self.release_da3()
@@ -132,6 +134,8 @@ class ModelCache:
def get_moge(self, model_dir, device):
key = (str(Path(model_dir).resolve()), device.type)
if self.da3_model is not None:
self.release_da3()
if self.moge_model is not None and self.moge_key == key:
return self.moge_model
self.release_moge()

View File

@@ -347,6 +347,16 @@ def confidence_percentile_map(depth, confidence):
return result
def depth_discontinuity_ratio(depth):
import numpy as np
padded = np.pad(depth, 1, mode="edge")
windows = np.lib.stride_tricks.sliding_window_view(padded, (3, 3))
difference = np.nanmax(windows, axis=(-2, -1))
difference -= np.nanmin(windows, axis=(-2, -1))
return difference / np.maximum(depth, 1e-6)
def triangle_edge_stretch_values(points, triangles):
import numpy as np
@@ -381,39 +391,23 @@ def image_mesh_attributes(
vertices,
faces,
intrinsics,
depth,
confidence,
confidence_percentiles,
foreground_mask,
face_depth_discontinuity,
):
import numpy as np
pixels = project_camera_pixels(vertices, intrinsics)
point_confidence = sample_image_nearest(confidence, pixels).astype(
np.float32
)
point_confidence = np.nan_to_num(point_confidence, nan=0.0)
point_percentile = sample_image_nearest(
confidence_percentiles,
pixels,
project_camera_pixels(vertices, intrinsics),
).astype(np.float32)
point_background = ~sample_image_nearest(foreground_mask, pixels).astype(
bool
)
face_confidence = point_percentile[faces].min(axis=1)
face_edge_stretch = triangle_edge_stretch_values(
vertices,
faces,
).max(axis=1)
face_background = point_background[faces].any(axis=1)
return {
"point_depth": vertices[:, 2].astype(np.float32),
"point_confidence": point_confidence,
"point_confidence_percentile": point_percentile,
"point_background": point_background,
"face_confidence_percentile": face_confidence.astype(np.float32),
"face_edge_stretch": face_edge_stretch.astype(np.float32),
"face_background": face_background,
"face_depth_discontinuity": np.asarray(
face_depth_discontinuity,
dtype=np.float32,
),
}
@@ -507,6 +501,8 @@ def export_image_mesh_data(
"DA3 output does not contain any valid surface faces"
)
progress(5, "Preparing source UV and attributes")
depth_discontinuity = depth_discontinuity_ratio(raw_depth)
face_depth_discontinuity = depth_discontinuity.reshape(-1)[faces].max(axis=1)
vertices, faces = compact_mesh(
points.reshape(-1, 3),
faces,
@@ -517,13 +513,10 @@ def export_image_mesh_data(
vertices,
faces,
intrinsics,
raw_depth,
confidence,
confidence_percentiles,
foreground_mask,
face_depth_discontinuity,
)
valid_depth = raw_depth[np.isfinite(raw_depth) & (raw_depth > 0.0)]
reference_depth = float(np.median(valid_depth))
reference_depth = float(np.median(vertices[:, 2]))
output_directory = Path(output_directory)
output_directory.mkdir(parents=True, exist_ok=True)
progress(6, "Writing source surface data")
@@ -1568,26 +1561,46 @@ def export_moge_surface_archive(
mask,
normal,
intrinsics,
foreground_mask=None,
):
import numpy as np
import utils3d
height, width = depth.shape
clean_mask = mask & ~utils3d.np.depth_map_edge(depth, rtol=0.04)
finite_mask = np.isfinite(depth) & (depth > 0.0)
finite_mask &= np.isfinite(points).all(axis=-1)
if foreground_mask is not None:
finite_mask &= foreground_mask
depth_discontinuity = depth_discontinuity_ratio(depth)
result = utils3d.np.build_mesh_from_map(
points,
utils3d.np.uv_map(height, width),
normal,
mask=clean_mask,
mask.astype(np.float32),
depth_discontinuity.astype(np.float32),
depth,
mask=finite_mask,
tri=True,
)
faces, vertices, vertex_uvs, vertex_normals = result
(
faces,
vertices,
vertex_uvs,
_vertex_normals,
vertex_mask,
vertex_depth_discontinuity,
point_depth,
) = result
vertex_uvs = vertex_uvs * np.array([1.0, -1.0]) + np.array([0.0, 1.0])
pixel_intrinsics = intrinsics.copy()
pixel_intrinsics[0] *= width
pixel_intrinsics[1] *= height
pixel_intrinsics[2] = (0.0, 0.0, 1.0)
point_depth = vertices[:, 2].astype(np.float32)
point_depth = point_depth.astype(np.float32)
face_mask = vertex_mask[faces].all(axis=1)
face_depth_discontinuity = vertex_depth_discontinuity[faces].max(axis=1)
masked_depth = point_depth[vertex_mask.astype(bool)]
reference_depth = np.median(masked_depth if len(masked_depth) else point_depth)
np.savez(
output_path,
vertices=vertices.astype(np.float32),
@@ -1595,10 +1608,11 @@ def export_moge_surface_archive(
uv=vertex_uvs.astype(np.float32),
intrinsics=pixel_intrinsics.astype(np.float32),
image_size=np.array((width, height), dtype=np.int32),
reference_depth=np.float32(np.median(point_depth)),
reference_depth=np.float32(reference_depth),
point_depth=point_depth,
vertex_normal=vertex_normals.astype(np.float32),
face_mask=np.ones(len(faces), dtype=bool),
normal_map=normal.astype(np.float32),
face_mask=face_mask,
face_depth_discontinuity=face_depth_discontinuity,
)
@@ -1703,6 +1717,7 @@ def run_moge(args, model_cache=None, cancel_check=None):
debug_format,
)
foreground_mask = None
remove_background_model_dir = getattr(
args, "remove_background_model_dir", ""
)
@@ -1724,7 +1739,6 @@ def run_moge(args, model_cache=None, cancel_check=None):
foreground_path,
result["depth"][0].shape,
)
result["mask"][0] &= foreground_mask
else:
infer_foreground_frames(
images,
@@ -1749,6 +1763,11 @@ def run_moge(args, model_cache=None, cancel_check=None):
result["mask"][0],
result["normal"][0],
result["intrinsics"][0],
foreground_mask=(
foreground_mask
if remove_background_model_dir
else None
),
)
np.savez(output_directory / "results.npz", **result)
copy_frames(images, output_directory)