no more cp1252 problems
This commit is contained in:
@@ -106,10 +106,10 @@ def split_panorama_image_gpu(image: np.ndarray, extrinsics: np.ndarray, intrinsi
|
||||
|
||||
grid_t = torch.from_numpy(grid).to(device) # (N, R, R, 2) fp32
|
||||
|
||||
# Image to (1, C, H, W) → broadcast to (N, C, H, W) for batched sample.
|
||||
# Image to (1, C, H, W) -> broadcast to (N, C, H, W) for batched sample.
|
||||
img_chw = torch.from_numpy(np.ascontiguousarray(image)).to(device)
|
||||
if img_chw.ndim == 2:
|
||||
img_chw = img_chw.unsqueeze(-1) # (H, W) → (H, W, 1)
|
||||
img_chw = img_chw.unsqueeze(-1) # (H, W) -> (H, W, 1)
|
||||
img_chw = img_chw.permute(2, 0, 1).contiguous().to(torch.float32) # (C, H, W)
|
||||
img_batched = img_chw.unsqueeze(0).expand(N, -1, -1, -1) # (N, C, H, W)
|
||||
|
||||
@@ -185,7 +185,7 @@ def grad_equation(width: int, height: int, wrap_x: bool = False, wrap_y: bool =
|
||||
|
||||
|
||||
def merge_panorama_depth(width: int, height: int, distance_maps: List[np.ndarray], pred_masks: List[np.ndarray], extrinsics: List[np.ndarray], intrinsics: List[np.ndarray], *, pbar=None, _top_size=None):
|
||||
"""Recursive multi-resolution merge of N rectilinear face depth maps → equirect depth.
|
||||
"""Recursive multi-resolution merge of N rectilinear face depth maps -> equirect depth.
|
||||
|
||||
Optional `pbar` is a ComfyUI ProgressBar-like object (any obj with
|
||||
`update_absolute(value, total)`). Progress is weighted by `pixels^1.5`
|
||||
@@ -209,7 +209,7 @@ def merge_panorama_depth(width: int, height: int, distance_maps: List[np.ndarray
|
||||
if max(w_, h_) <= 256:
|
||||
break
|
||||
w_, h_ = w_ // 2, h_ // 2
|
||||
levels.reverse() # smallest → largest, matches recursion unwind order
|
||||
levels.reverse() # smallest -> largest, matches recursion unwind order
|
||||
weights = np.array([(lw * lh) ** 1.5 for lw, lh in levels], dtype=np.float64)
|
||||
weights = weights / weights.sum()
|
||||
pbar._wn_levels = levels
|
||||
@@ -238,10 +238,10 @@ def merge_panorama_depth(width: int, height: int, distance_maps: List[np.ndarray
|
||||
width // 2, height // 2, distance_maps, pred_masks, extrinsics, intrinsics,
|
||||
pbar=pbar, _top_size=_top_size,
|
||||
)
|
||||
_p(f"recursion returned; upsampling {width // 2}x{height // 2} → {width}x{height}")
|
||||
_p(f"recursion returned; upsampling {width // 2}x{height // 2} -> {width}x{height}")
|
||||
panorama_depth_init = cv2.resize(panorama_depth_init, (width, height), cv2.INTER_LINEAR)
|
||||
else:
|
||||
_p(f"base case (≤256 on each side) → no recursion, no init x0 for LSMR")
|
||||
_p(f"base case (<=256 on each side) -> no recursion, no init x0 for LSMR")
|
||||
panorama_depth_init = None
|
||||
_bump(0.05)
|
||||
|
||||
|
||||
@@ -61,7 +61,7 @@ def subdivide_icosahedron(subdivisions: int = 1) -> np.ndarray:
|
||||
Returns:
|
||||
vertices: (N, 3) Subdivided vertex coordinates on the unit sphere.
|
||||
"""
|
||||
# utils3d renamed `icosahedron()` → `create_icosahedron_mesh()`.
|
||||
# utils3d renamed `icosahedron()` -> `create_icosahedron_mesh()`.
|
||||
vertices, faces = utils3d.numpy.create_icosahedron_mesh()
|
||||
|
||||
# Convert to a list so new vertices can be appended dynamically.
|
||||
@@ -316,10 +316,10 @@ def solve_lsmr_gpu(A, b, x0=None, atol=1e-5, btol=1e-5, conlim=0.0,
|
||||
scipy.sparse.linalg.lsmr (Fong & Saunders 2011), but the entire iteration
|
||||
runs through torch tensors — the big work vectors u, v, h, hbar, x live
|
||||
on the GPU; only the ~6 scalar Givens reductions per iteration come back
|
||||
to the host. Eliminates the per-iteration host↔device round-trip that
|
||||
the old `_TorchSparseLinearOperator` paid (matvec → .cpu().numpy() →
|
||||
scipy numpy ops → as_tensor → next matvec). For a 1920×960 panorama
|
||||
solve that's ~600 iterations × 4 transfers × 7 MB ≈ 30 GB of avoided
|
||||
to the host. Eliminates the per-iteration host<->device round-trip that
|
||||
the old `_TorchSparseLinearOperator` paid (matvec -> .cpu().numpy() ->
|
||||
scipy numpy ops -> as_tensor -> next matvec). For a 1920×960 panorama
|
||||
solve that's ~600 iterations × 4 transfers × 7 MB ~= 30 GB of avoided
|
||||
PCIe traffic.
|
||||
|
||||
Numerical behaviour vs scipy fp64 CPU: SpMV runs in fp32 on cuSPARSE
|
||||
@@ -384,10 +384,10 @@ def solve_lsmr_gpu(A, b, x0=None, atol=1e-5, btol=1e-5, conlim=0.0,
|
||||
size=AT_csr.shape,
|
||||
)
|
||||
|
||||
def _matvec(vec): # vec: (n,) → (m,)
|
||||
def _matvec(vec): # vec: (n,) -> (m,)
|
||||
return torch.sparse.mm(A_gpu, vec.unsqueeze(-1)).squeeze(-1)
|
||||
|
||||
def _rmatvec(vec): # vec: (m,) → (n,)
|
||||
def _rmatvec(vec): # vec: (m,) -> (n,)
|
||||
return torch.sparse.mm(AT_gpu, vec.unsqueeze(-1)).squeeze(-1)
|
||||
|
||||
# --- Upload b (and x0 if given). ---
|
||||
@@ -614,7 +614,7 @@ def merge_panorama_depth_gpu(width: int, height: int, distance_maps: List[np.nda
|
||||
ext_gpu = torch.from_numpy(np.stack(extrinsics).astype(np.float32)).to(device) # (N, 4, 4)
|
||||
intr_gpu = torch.from_numpy(np.stack(intrinsics).astype(np.float32)).to(device) # (N, 3, 3)
|
||||
|
||||
# --- Batched projection: equirect rays → per-face image plane. ---
|
||||
# --- Batched projection: equirect rays -> per-face image plane. ---
|
||||
# p_cam = R @ dirs + t. Then K @ p_cam, perspective divide.
|
||||
R = ext_gpu[:, :3, :3] # (N, 3, 3)
|
||||
t = ext_gpu[:, :3, 3] # (N, 3)
|
||||
@@ -718,7 +718,7 @@ def merge_panorama_depth_gpu(width: int, height: int, distance_maps: List[np.nda
|
||||
any_mask_lap = mask_laplacian.any(0) # (H, W)
|
||||
panorama_pred_mask_final = panorama_pred_mask_per_face.any(0) # (H, W)
|
||||
|
||||
# --- Single device→host transfer of the data we need on CPU for the
|
||||
# --- Single device->host transfer of the data we need on CPU for the
|
||||
# sparse-system build below. ---
|
||||
avg_grad_x_np = avg_grad_x.cpu().numpy()
|
||||
avg_grad_y_np = avg_grad_y.cpu().numpy()
|
||||
@@ -836,7 +836,7 @@ def pred_pano_depth(model, image: Image.Image, scale=1.0, resize_to=1920, remove
|
||||
else:
|
||||
skip_ratio_info = f"{skipped_count / (pred_count + skipped_count):.2%}"
|
||||
|
||||
print(f"\t 🔍 Predicted {pred_count} splitted images, skipped {skipped_count} splitted images. Skip ratio: {skip_ratio_info}")
|
||||
print(f"\t Predicted {pred_count} splitted images, skipped {skipped_count} splitted images. Skip ratio: {skip_ratio_info}")
|
||||
|
||||
# merge moge depth
|
||||
merging_width, merging_height = width, height
|
||||
@@ -896,7 +896,7 @@ def compute_spherical_ray_derivatives(spherical_directions: np.ndarray):
|
||||
ray = spherical_directions
|
||||
|
||||
# Azimuth theta and elevation phi.
|
||||
# ray = (cos(φ)sin(θ), sin(φ), cos(φ)cos(θ))
|
||||
# ray = (cos(phi)sin(theta), sin(phi), cos(phi)cos(theta))
|
||||
# Or adapt to the coordinate-system definition in use.
|
||||
|
||||
# Method 1: numerical computation, which is more robust.
|
||||
@@ -904,15 +904,15 @@ def compute_spherical_ray_derivatives(spherical_directions: np.ndarray):
|
||||
v = (np.arange(H) + 0.5) / H # [0, 1]
|
||||
u_grid, v_grid = np.meshgrid(u, v)
|
||||
|
||||
theta = (u_grid - 0.5) * 2 * np.pi # [-π, π]
|
||||
phi = (0.5 - v_grid) * np.pi # [π/2, -π/2]
|
||||
theta = (u_grid - 0.5) * 2 * np.pi # [-pi, pi]
|
||||
phi = (0.5 - v_grid) * np.pi # [pi/2, -pi/2]
|
||||
|
||||
cos_phi = np.cos(phi)
|
||||
sin_phi = np.sin(phi)
|
||||
cos_theta = np.cos(theta)
|
||||
sin_theta = np.sin(theta)
|
||||
|
||||
# ∂ray/∂θ, assuming standard equirectangular mapping.
|
||||
# dray/dtheta, assuming standard equirectangular mapping.
|
||||
# Adjust according to how spherical_directions is actually computed.
|
||||
dray_dtheta = np.stack([
|
||||
cos_phi * cos_theta,
|
||||
@@ -920,7 +920,7 @@ def compute_spherical_ray_derivatives(spherical_directions: np.ndarray):
|
||||
-cos_phi * sin_theta
|
||||
], axis=-1)
|
||||
|
||||
# ∂ray/∂φ
|
||||
# dray/dphi
|
||||
dray_dphi = np.stack([
|
||||
-sin_phi * sin_theta,
|
||||
cos_phi,
|
||||
@@ -941,9 +941,9 @@ def normal_to_log_distance_gradient(
|
||||
|
||||
Derivation:
|
||||
Surface point P = d * ray
|
||||
Tangent vector ∂P/∂θ = (∂d/∂θ) * ray + d * (∂ray/∂θ)
|
||||
The normal is perpendicular to the tangent: n · ∂P/∂θ = 0
|
||||
=> ∂(log d)/∂θ = -(n · ∂ray/∂θ) / (n · ray)
|
||||
Tangent vector dP/dtheta = (dd/dtheta) * ray + d * (dray/dtheta)
|
||||
The normal is perpendicular to the tangent: n · dP/dtheta = 0
|
||||
=> d(log d)/dtheta = -(n · dray/dtheta) / (n · ray)
|
||||
|
||||
Args:
|
||||
panorama_normal: (H, W, 3) Panorama normal map in world coordinates.
|
||||
@@ -973,14 +973,14 @@ def normal_to_log_distance_gradient(
|
||||
n_dot_ray_safe = np.where(valid_mask, n_dot_ray, 1.0)
|
||||
|
||||
# Continuous-space log(d) gradients with respect to angles.
|
||||
# ∂(log d)/∂θ = -(n · ∂ray/∂θ) / (n · ray)
|
||||
# d(log d)/dtheta = -(n · dray/dtheta) / (n · ray)
|
||||
dlogd_dtheta = -n_dot_dray_dtheta / n_dot_ray_safe # (H, W)
|
||||
dlogd_dphi = -n_dot_dray_dphi / n_dot_ray_safe # (H, W)
|
||||
|
||||
# Convert to discrete pixel gradients.
|
||||
# In this code, grad_x = log_d[j] - log_d[j+1] = -∂(log d)/∂θ * Δθ.
|
||||
# Δθ = 2π / W, the θ change per pixel.
|
||||
# grad_y = log_d[i] - log_d[i+1] = -∂(log d)/∂φ * Δφ = ∂(log d)/∂φ * (π/H)
|
||||
# In this code, grad_x = log_d[j] - log_d[j+1] = -d(log d)/dtheta * Deltatheta.
|
||||
# Deltatheta = 2pi / W, the theta change per pixel.
|
||||
# grad_y = log_d[i] - log_d[i+1] = -d(log d)/dphi * Deltaphi = d(log d)/dphi * (pi/H)
|
||||
|
||||
delta_theta = 2 * np.pi / W
|
||||
delta_phi = np.pi / H
|
||||
@@ -1257,7 +1257,7 @@ def convert_rgbd2pcd_multi_scale_panorama(
|
||||
else:
|
||||
interval_mask = ((median_distance * depth_intervals[i - 1]) < distance_nchw) & (distance_nchw <= (median_distance * depth_intervals[i]))
|
||||
|
||||
# pointclouds number ∝ depth^2
|
||||
# pointclouds number prop depth^2
|
||||
resize_scale = depth_intervals[i]
|
||||
if interval_mask.sum() == 0:
|
||||
continue
|
||||
@@ -2075,7 +2075,7 @@ def erp_distance_ray_to_normal(distance_map, ray_directions,
|
||||
normal_map = np.stack([normal_x, normal_y, normal_z], axis=-1)
|
||||
|
||||
# 7. Convert to an RGB visualization.
|
||||
# [-1, 1] → [0, 255]
|
||||
# [-1, 1] -> [0, 255]
|
||||
normal_rgb = ((normal_map + 1.0) / 2.0 * 255).astype(np.uint8)
|
||||
|
||||
return normal_map, normal_rgb
|
||||
|
||||
@@ -9,7 +9,7 @@ Channel order is fixed by `SharpPredictGaussianAttrs.GAUSS_ATTR_CHANNEL_NAMES`:
|
||||
0: position_x (NDC)
|
||||
1: position_y (NDC)
|
||||
2: position_z (NDC / camera-space z)
|
||||
3: scale_x (Σ-singular value, world units after unprojection)
|
||||
3: scale_x (Sigma-singular value, world units after unprojection)
|
||||
4: scale_y
|
||||
5: scale_z
|
||||
6: quaternion_w
|
||||
@@ -24,7 +24,7 @@ Channel order is fixed by `SharpPredictGaussianAttrs.GAUSS_ATTR_CHANNEL_NAMES`:
|
||||
We rebuild a `Gaussians3D` namedtuple in NDC, then call SHARP's
|
||||
`unproject_gaussians` to transform to world coordinates using the
|
||||
per-face extrinsics + intrinsics. Output convention matches SharpPredict:
|
||||
single batch → one .ply; N>1 batch → folder of NNN.ply.
|
||||
single batch -> one .ply; N>1 batch -> folder of NNN.ply.
|
||||
"""
|
||||
|
||||
import logging
|
||||
@@ -61,7 +61,7 @@ OPACITY = 13
|
||||
|
||||
|
||||
class SharpImageAttrsToPLY(io.ComfyNode):
|
||||
"""MULTIBAND_IMAGE of gaussian attrs + camera → PLY file."""
|
||||
"""MULTIBAND_IMAGE of gaussian attrs + camera -> PLY file."""
|
||||
|
||||
@classmethod
|
||||
def define_schema(cls):
|
||||
@@ -171,7 +171,7 @@ class SharpImageAttrsToPLY(io.ComfyNode):
|
||||
[0.0, 0.0, 1.0],
|
||||
], dtype=torch.float32)
|
||||
intr = K_default.unsqueeze(0).repeat(B, 1, 1)
|
||||
_p(f"intrinsics not wired → using identity-style K (fx=fy={f_px_default:.1f}, "
|
||||
_p(f"intrinsics not wired -> using identity-style K (fx=fy={f_px_default:.1f}, "
|
||||
f"cx={W/2:.1f}, cy={H/2:.1f}). Pass `intrinsics` from "
|
||||
f"SharpPredictGaussianAttrs for correct geometry.")
|
||||
else:
|
||||
@@ -212,11 +212,11 @@ class SharpImageAttrsToPLY(io.ComfyNode):
|
||||
t_start = time.time()
|
||||
|
||||
for b in range(B):
|
||||
# ---- extract per-pixel attrs → Gaussians3D in NDC ------------
|
||||
# ---- extract per-pixel attrs -> Gaussians3D in NDC ------------
|
||||
s = samples[b] # [14, H, W]
|
||||
n_pixels = H * W
|
||||
|
||||
# Multiband stores [C, H, W]; flatten spatial dims → [C, N], then
|
||||
# Multiband stores [C, H, W]; flatten spatial dims -> [C, N], then
|
||||
# transpose to [N, C] for the per-channel slices below.
|
||||
flat = s.reshape(C, n_pixels).T # [N, C]
|
||||
mean_ndc = flat[:, POS].contiguous() # [N, 3]
|
||||
@@ -252,7 +252,7 @@ class SharpImageAttrsToPLY(io.ComfyNode):
|
||||
# for (H, W) (per the rescale done in SharpPredictGaussianAttrs).
|
||||
image_shape = (H, W)
|
||||
|
||||
# ---- NDC → world ---------------------------------------------
|
||||
# ---- NDC -> world ---------------------------------------------
|
||||
gaussians_world = unproject_gaussians(
|
||||
gaussians_ndc, ext_b, K_b, image_shape,
|
||||
)
|
||||
@@ -278,7 +278,7 @@ class SharpImageAttrsToPLY(io.ComfyNode):
|
||||
loc = output_folder if is_batch else output_path
|
||||
total_n = B * H * W
|
||||
_p(
|
||||
f"{B} multiband(s) {H}×{W} → {total_n/1e6:.2f}M gaussians; "
|
||||
f"{B} multiband(s) {H}×{W} -> {total_n/1e6:.2f}M gaussians; "
|
||||
f"saved to {loc}; {elapsed:.1f}s"
|
||||
)
|
||||
|
||||
|
||||
@@ -305,11 +305,11 @@ class MergeGaussians(io.ComfyNode):
|
||||
|
||||
elapsed = time.time() - t_start
|
||||
_p(
|
||||
f"merged {len(ply_files)} file(s) → {num_gaussians/1e6:.2f}M gaussians "
|
||||
f"→ {output_path}{size_str}; "
|
||||
f"merged {len(ply_files)} file(s) -> {num_gaussians/1e6:.2f}M gaussians "
|
||||
f"-> {output_path}{size_str}; "
|
||||
f"save {save_elapsed:.1f}s, total {elapsed:.1f}s"
|
||||
)
|
||||
_p(f"field PLY (positions + face_id only) → {field_output_path}")
|
||||
_p(f"field PLY (positions + face_id only) -> {field_output_path}")
|
||||
|
||||
return io.NodeOutput(
|
||||
str(ply_folder_in), str(output_path),
|
||||
|
||||
+2
-2
@@ -193,9 +193,9 @@ class SharpPredict(io.ComfyNode):
|
||||
# E` chain both want an UNBATCHED matrix.
|
||||
if img_intrinsics.dim() == 3 and img_intrinsics.shape[0] == 1:
|
||||
img_intrinsics = img_intrinsics[0]
|
||||
# Detect NORMALIZED K (fx, fy ∈ [0, 1]) and rescale to
|
||||
# Detect NORMALIZED K (fx, fy in [0, 1]) and rescale to
|
||||
# pixel-K at this image's resolution. PanoramaSplit emits
|
||||
# normalized K by default (fx≈0.5 for a 90° FOV face);
|
||||
# normalized K by default (fx~=0.5 for a 90° FOV face);
|
||||
# SHARP's downstream `intrinsics_resized = K * (internal /
|
||||
# width)` plus `f_px = K[0, 0].item()` both assume pixel-K
|
||||
# at the INPUT image's resolution. Same sniff pattern HYWM2
|
||||
|
||||
@@ -6,7 +6,7 @@ practical). Two selection modes:
|
||||
|
||||
- **closest** (default): per pixel, keep the gaussian with the smaller
|
||||
world-space z (closer to camera). SHARP's monodepth output IS sorted
|
||||
near→far at the head (`model.py:1122-1125`: `disparity.max` for layer
|
||||
near->far at the head (`model.py:1122-1125`: `disparity.max` for layer
|
||||
0, `disparity.min` for layer 1), but the gaussian decoder adds an
|
||||
unconstrained delta_z per layer — so layer 0 isn't reliably the front
|
||||
post-decode. `closest` looks at the actual z and picks per-pixel,
|
||||
|
||||
@@ -36,7 +36,7 @@ Multiband channel order (per layer):
|
||||
1: position_y (ndc, in [-1, 1])
|
||||
2: position_z (ndc / camera z, same as depth_refined output;
|
||||
duplicated here so multiband is self-contained)
|
||||
3: scale_x (Σ-singular value)
|
||||
3: scale_x (Sigma-singular value)
|
||||
4: scale_y
|
||||
5: scale_z
|
||||
6: quaternion_w
|
||||
@@ -254,7 +254,7 @@ class SharpPredictGaussianAttrs(io.ComfyNode):
|
||||
"depth map — wire with `extrinsics_mdepth + "
|
||||
"intrinsics_mdepth` into SharpDepthMerge for a "
|
||||
"seam-free LSMR merge that matches MoGe2-style "
|
||||
"smoothness. Memory: 42 faces × 1536² fp32 ≈ 395 MB."),
|
||||
"smoothness. Memory: 42 faces × 1536² fp32 ~= 395 MB."),
|
||||
io.Custom("EXTRINSICS").Output(
|
||||
display_name="extrinsics_mdepth",
|
||||
tooltip="Pass-through of input extrinsics. Extrinsics are "
|
||||
@@ -338,7 +338,7 @@ class SharpPredictGaussianAttrs(io.ComfyNode):
|
||||
intrinsics = _K_default.unsqueeze(0).repeat(B, 1, 1)
|
||||
_fov_deg = 2 * _math.degrees(_math.atan((_img_H / 2.0) / _f_px_default))
|
||||
_p(
|
||||
f"intrinsics not wired → using identity-style K "
|
||||
f"intrinsics not wired -> using identity-style K "
|
||||
f"(focal={_f_px_default:.1f}px, image={_img_W}×{_img_H}, "
|
||||
f"~{_fov_deg:.1f}° FOV); pass intrinsics from "
|
||||
f"SharpPanoramaIcosahedronSplit for accurate geometry."
|
||||
@@ -406,10 +406,10 @@ class SharpPredictGaussianAttrs(io.ComfyNode):
|
||||
# produces the same gaussians.
|
||||
#
|
||||
# Convention sniff: PanoPack's PanoramaSplit emits NORMALIZED K
|
||||
# (fx≈0.5, cx≈0.5 for 90° fov via utils3d.np.intrinsics_from_fov,
|
||||
# (fx~=0.5, cx~=0.5 for 90° fov via utils3d.np.intrinsics_from_fov,
|
||||
# units in [0,1]) whereas Sharp's predict path assumes pixel-K
|
||||
# (fx in the hundreds). Rescale to pixel-K once before computing
|
||||
# f_px so the disparity → depth math doesn't collapse to ~0.
|
||||
# f_px so the disparity -> depth math doesn't collapse to ~0.
|
||||
if intrinsics is not None:
|
||||
intr_b = intrinsics[b] if intrinsics.dim() == 3 else intrinsics
|
||||
if float(intr_b[0, 0]) < 2.0:
|
||||
@@ -492,7 +492,7 @@ class SharpPredictGaussianAttrs(io.ComfyNode):
|
||||
f"{'layer0 rgb':>22} | {'layer1 rgb':>22} | {'drgb (l1-l0)':>22}"
|
||||
)
|
||||
_dzs = []
|
||||
_drgbs_l1 = [] # ‖rgb1 - rgb0‖₁ per sampled pixel
|
||||
_drgbs_l1 = [] # ||rgb1 - rgb0||1 per sampled pixel
|
||||
for _y, _x in zip(_ys, _xs):
|
||||
_p0 = attrs0_batch[0, _y, _x, 0:3].tolist() # position xyz (NDC)
|
||||
_p1 = attrs1_batch[0, _y, _x, 0:3].tolist()
|
||||
@@ -514,8 +514,8 @@ class SharpPredictGaussianAttrs(io.ComfyNode):
|
||||
_dz_med = float(sorted(_dzs)[len(_dzs)//2])
|
||||
_drgb_med = float(sorted(_drgbs_l1)[len(_drgbs_l1)//2])
|
||||
_verdict_z = "layer0 in front" if _dz_med > 0 else "layer1 in front (decoder swapped at the sampled pixels)"
|
||||
_p(f" median dz = {_dz_med:+.4f} → {_verdict_z}")
|
||||
_p(f" median ‖rgb1 - rgb0‖₁ = {_drgb_med:+.4f} (sum of abs RGB deltas; 0 = layers share color)")
|
||||
_p(f" median dz = {_dz_med:+.4f} -> {_verdict_z}")
|
||||
_p(f" median ||rgb1 - rgb0||1 = {_drgb_med:+.4f} (sum of abs RGB deltas; 0 = layers share color)")
|
||||
except Exception as _e:
|
||||
_p(f"layer-slice sanity check failed: {_e!r}")
|
||||
|
||||
@@ -561,7 +561,7 @@ class SharpPredictGaussianAttrs(io.ComfyNode):
|
||||
# Rescale intrinsics so pixel-K matches the emitted depth grid for
|
||||
# each output pair. The downstream merger normalizes K by the depth
|
||||
# tensor's W; if K's native res doesn't match that W, the effective
|
||||
# FOV is wrong → visible seams. We emit TWO K versions:
|
||||
# FOV is wrong -> visible seams. We emit TWO K versions:
|
||||
# - intrinsics : K rescaled to (H_grid, W_grid) = 768²,
|
||||
# pairs with gaussian-z outputs.
|
||||
# - intrinsics_mdepth: K rescaled to (1536, 1536),
|
||||
@@ -606,11 +606,11 @@ class SharpPredictGaussianAttrs(io.ComfyNode):
|
||||
d1_med = float(d1_batch.median())
|
||||
m_med = float(metric_batch.median())
|
||||
k_str = (
|
||||
f", K_{H_grid} fx→{k_fx_dbg:.1f} K_{internal_shape[0]} fx→{k_fx_md_dbg:.1f}"
|
||||
f", K_{H_grid} fx->{k_fx_dbg:.1f} K_{internal_shape[0]} fx->{k_fx_md_dbg:.1f}"
|
||||
if k_fx_dbg is not None else ""
|
||||
)
|
||||
_p(
|
||||
f"{B} face(s) → {n_gaussians_total/1e6:.2f}M gaussians "
|
||||
f"{B} face(s) -> {n_gaussians_total/1e6:.2f}M gaussians "
|
||||
f"({B}×{num_layers}×{H_grid}²); "
|
||||
f"metric/layer0/layer1 depth median={m_med:.2f}m/{d0_med:.2f}m/{d1_med:.2f}m"
|
||||
f"{k_str}; {elapsed:.1f}s"
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
"""SharpPredictGaussiansFromMetricDepth — image + external depth → PLY paths.
|
||||
"""SharpPredictGaussiansFromMetricDepth — image + external depth -> PLY paths.
|
||||
|
||||
Like SharpPredict, but **directly overrides SHARP's monodepth** with an
|
||||
externally-provided depth (e.g. from SharpDepthMerge after LSMR-merging
|
||||
@@ -14,27 +14,27 @@ panorama-merging workflows where SHARP's per-face monodepth has scale
|
||||
ambiguity that the depth_alignment module wasn't trained to handle.
|
||||
|
||||
Output convention matches SharpPredict:
|
||||
- Single image → single PLY file at OUTPUT_DIR/{prefix}_{ts}.ply
|
||||
- Batch (>1) → folder OUTPUT_DIR/{prefix}_{ts}/ with NNN.ply files
|
||||
- Single image -> single PLY file at OUTPUT_DIR/{prefix}_{ts}.ply
|
||||
- Batch (>1) -> folder OUTPUT_DIR/{prefix}_{ts}/ with NNN.ply files
|
||||
|
||||
Wire the batch folder straight into `MergeGaussians (PLY Files)` for a
|
||||
single merged scene PLY.
|
||||
|
||||
Typical pipeline:
|
||||
1. SharpPanoramaIcosahedronSplit → face images
|
||||
2. SharpPredictMetricDepth (per face) → raw per-face metric depths
|
||||
3. SharpDepthMerge → seam-free equirect depth (LSMR)
|
||||
1. SharpPanoramaIcosahedronSplit -> face images
|
||||
2. SharpPredictMetricDepth (per face) -> raw per-face metric depths
|
||||
3. SharpDepthMerge -> seam-free equirect depth (LSMR)
|
||||
4. SharpPanoramaIcosahedronSplit (on the merged depth IMAGE)
|
||||
→ re-cropped face depths in
|
||||
-> re-cropped face depths in
|
||||
equirect ray-distance convention
|
||||
5. SharpPredictGaussiansFromMetricDepth(face_images, consistent_face_depths,
|
||||
depth_convention="ray_distance")
|
||||
→ folder of seam-aligned PLYs
|
||||
6. MergeGaussians → final unified PLY
|
||||
-> folder of seam-aligned PLYs
|
||||
6. MergeGaussians -> final unified PLY
|
||||
|
||||
When step 4 returns ray-distance depth (the equirect convention), set
|
||||
`depth_convention="ray_distance"` so the node applies the
|
||||
ray-distance → planar-Z cos-map conversion using the per-face intrinsics.
|
||||
ray-distance -> planar-Z cos-map conversion using the per-face intrinsics.
|
||||
For depths that are already in SHARP's native planar-Z convention
|
||||
(e.g. straight from `SharpPredictMetricDepth` on a single face),
|
||||
leave it at the default `"planar_z"`.
|
||||
@@ -71,7 +71,7 @@ def _p(msg: str) -> None:
|
||||
|
||||
|
||||
class SharpPredictGaussiansFromMetricDepth(io.ComfyNode):
|
||||
"""SHARP gaussian decoder driven by external depth → PLY paths."""
|
||||
"""SHARP gaussian decoder driven by external depth -> PLY paths."""
|
||||
|
||||
@classmethod
|
||||
def define_schema(cls):
|
||||
@@ -85,7 +85,7 @@ class SharpPredictGaussiansFromMetricDepth(io.ComfyNode):
|
||||
"becomes the geometric scaffold for `init_model`'s gaussian "
|
||||
"base values, so gaussians share a consistent surface across "
|
||||
"multiple views — fixes per-face seams.\n\n"
|
||||
"Saves PLYs (single image → one .ply; batch → folder of "
|
||||
"Saves PLYs (single image -> one .ply; batch -> folder of "
|
||||
"NNN.ply). Pipe into MergeGaussians to combine into one PLY."
|
||||
),
|
||||
is_output_node=True,
|
||||
@@ -145,7 +145,7 @@ class SharpPredictGaussiansFromMetricDepth(io.ComfyNode):
|
||||
)),
|
||||
io.Combo.Input(
|
||||
"snap_to_external_depth",
|
||||
options=["none", "z_only", "xyz_full"],
|
||||
options=["none", "z_only", "z_only_firstlayer", "xyz_full", "xyz_full_firstlayer"],
|
||||
default="none",
|
||||
tooltip=(
|
||||
"Force gaussian positions to exactly match the external "
|
||||
@@ -156,11 +156,22 @@ class SharpPredictGaussiansFromMetricDepth(io.ComfyNode):
|
||||
"the prediction head's sub-pixel xy drift survives — "
|
||||
"layer 1 still varies in appearance from layer 0), but "
|
||||
"depth is forced to match external.\n\n"
|
||||
" z_only_firstlayer: snap ONLY layer 0 to the external "
|
||||
"depth (same proportional z rescale as z_only); leave "
|
||||
"layer 1 (and any further layers) at the decoder's "
|
||||
"predicted z. The front surface is pinned to your depth "
|
||||
"while the back gaussians stay where the model put them "
|
||||
"-- preserves SHARP's hallucinated occlusion backplate.\n\n"
|
||||
" xyz_full: reset xy to the pixel-grid identity AND "
|
||||
"set depth to external. Every gaussian sits exactly "
|
||||
"on the back-projected ray of its source pixel at the "
|
||||
"external depth — no learned drift at all. Most rigid; "
|
||||
"layer 0 and layer 1 collapse to identical positions.\n\n"
|
||||
" xyz_full_firstlayer: apply the xyz_full reset to "
|
||||
"layer 0 only (front surface sits exactly on the "
|
||||
"pixel-grid ray at the external depth); leave layer 1+ "
|
||||
"at the decoder's predicted position (occlusion "
|
||||
"backplate preserved).\n\n"
|
||||
" none: skip the snap entirely — trust the gaussian "
|
||||
"decoder's predicted z (the direct-monodepth-override "
|
||||
"behavior is in effect but not enforced)."
|
||||
@@ -180,7 +191,7 @@ class SharpPredictGaussiansFromMetricDepth(io.ComfyNode):
|
||||
" ray_distance: Euclidean distance from the camera "
|
||||
"center along each pixel's ray — the equirect "
|
||||
"convention. Pick this when the depth was extracted "
|
||||
"via SharpDepthMerge → SharpPanoramaIcosahedronSplit, "
|
||||
"via SharpDepthMerge -> SharpPanoramaIcosahedronSplit, "
|
||||
"i.e. when each face was cut out of an equirect "
|
||||
"depth panorama. The node multiplies by the per-face "
|
||||
"cos-map `1 / sqrt(((u-cx)/fx)² + ((v-cy)/fy)² + 1)` "
|
||||
@@ -293,10 +304,10 @@ class SharpPredictGaussiansFromMetricDepth(io.ComfyNode):
|
||||
f"metric_depth_layer1 batch {metric_depth_l1.shape[0]} != "
|
||||
f"image batch {B}"
|
||||
)
|
||||
_p("metric_depth_layer1 wired → layer-0 + layer-1 will get "
|
||||
_p("metric_depth_layer1 wired -> layer-0 + layer-1 will get "
|
||||
"DIFFERENT depths through init_model")
|
||||
else:
|
||||
_p("metric_depth_layer1 unwired → layer 1 will duplicate layer 0 "
|
||||
_p("metric_depth_layer1 unwired -> layer 1 will duplicate layer 0 "
|
||||
"(legacy behavior)")
|
||||
|
||||
# Sanity-check the alignment is wired through the model.
|
||||
@@ -332,7 +343,7 @@ class SharpPredictGaussiansFromMetricDepth(io.ComfyNode):
|
||||
# Per-face denormalized PIXEL-K, captured so the `intrinsics`
|
||||
# output socket emits the same convention SharpPredict does
|
||||
# (pixel-K), regardless of whether the input was normalized
|
||||
# (PanoramaSplit, fx≈0.5) or already pixel-K.
|
||||
# (PanoramaSplit, fx~=0.5) or already pixel-K.
|
||||
all_intr_px = []
|
||||
|
||||
pbar = comfy.utils.ProgressBar(B)
|
||||
@@ -396,10 +407,10 @@ class SharpPredictGaussiansFromMetricDepth(io.ComfyNode):
|
||||
# intr_b -- the f_px formula below, the cos_map for ray_distance,
|
||||
# and the unprojection K -- sees the same convention.
|
||||
# `intr_b_px` is the per-batch K in PIXEL-K convention. We
|
||||
# reuse it for f_px, the ray→planar cos_map, AND the
|
||||
# reuse it for f_px, the ray->planar cos_map, AND the
|
||||
# unprojection K further down — previously the unprojection
|
||||
# step re-fetched from the raw `intrinsics` tensor, which
|
||||
# silently bypassed the normalized→pixel correction and
|
||||
# silently bypassed the normalized->pixel correction and
|
||||
# produced garbage when PanoramaSplit's normalized K was wired.
|
||||
intr_b_px = None
|
||||
if intrinsics is not None:
|
||||
@@ -424,13 +435,13 @@ class SharpPredictGaussiansFromMetricDepth(io.ComfyNode):
|
||||
f_px = float(_cfl(width, height, max(0.1, float(focal_length_mm or 30.0))))
|
||||
disparity_factor = torch.tensor([f_px / width]).float().to(device)
|
||||
|
||||
# External depth → 1536², broadcast to BOTH layers.
|
||||
# External depth -> 1536², broadcast to BOTH layers.
|
||||
md_b = metric_depth[b]
|
||||
if md_b.dim() == 3:
|
||||
md_b = md_b[..., 0]
|
||||
md_b = md_b.to(device).float().unsqueeze(0).unsqueeze(0)
|
||||
|
||||
# Optional ray-distance → planar-Z conversion. Apply at the
|
||||
# Optional ray-distance -> planar-Z conversion. Apply at the
|
||||
# depth's native resolution (before the resize to 1536²) so the
|
||||
# convention boundary doesn't get smeared by bilinear interp.
|
||||
# cos_map = 1 / sqrt(((u-cx)/fx)² + ((v-cy)/fy)² + 1), built from
|
||||
@@ -463,7 +474,7 @@ class SharpPredictGaussiansFromMetricDepth(io.ComfyNode):
|
||||
cos_map = 1.0 / torch.sqrt(x_cam * x_cam + y_cam * y_cam + 1.0)
|
||||
md_b = md_b * cos_map.unsqueeze(0).unsqueeze(0)
|
||||
if b == 0:
|
||||
_p(f"depth_convention=ray_distance → applying cos_map "
|
||||
_p(f"depth_convention=ray_distance -> applying cos_map "
|
||||
f"(min={float(cos_map.min()):.4f} "
|
||||
f"max={float(cos_map.max()):.4f}) per face")
|
||||
|
||||
@@ -472,7 +483,7 @@ class SharpPredictGaussiansFromMetricDepth(io.ComfyNode):
|
||||
)
|
||||
|
||||
# Layer-1 path. When wired, apply the SAME conversions
|
||||
# (ray-distance → planar-Z if requested) to keep both layers
|
||||
# (ray-distance -> planar-Z if requested) to keep both layers
|
||||
# in the convention the model expects.
|
||||
if metric_depth_l1 is not None:
|
||||
md_b1 = metric_depth_l1[b]
|
||||
@@ -509,12 +520,12 @@ class SharpPredictGaussiansFromMetricDepth(io.ComfyNode):
|
||||
# Layer 0 = your front depth (e.g. plane-snapped HYWM2),
|
||||
# Layer 1 = your back depth (e.g. SHARP's own predicted
|
||||
# layer 1, or a +offset back-plate). Prediction head still
|
||||
# adds per-layer Δs on top of these distinct base surfaces.
|
||||
# adds per-layer Deltas on top of these distinct base surfaces.
|
||||
monodepth_override = torch.cat(
|
||||
[external_depth, external_depth_l1], dim=1,
|
||||
) # [1, 2, 1536, 1536] with layer 0 ≠ layer 1
|
||||
) # [1, 2, 1536, 1536] with layer 0 != layer 1
|
||||
else:
|
||||
# Legacy behavior: both monodepth layers = external depth →
|
||||
# Legacy behavior: both monodepth layers = external depth ->
|
||||
# no back-surface hallucination. Layer 1's gaussians collapse
|
||||
# onto the same surface as layer 0; the prediction head's
|
||||
# per-layer deltas can still drift them sub-pixel in xy and
|
||||
@@ -526,8 +537,8 @@ class SharpPredictGaussiansFromMetricDepth(io.ComfyNode):
|
||||
# map UNet). The paper's recommended inference path passes
|
||||
# depth=None, leaving alignment as identity and using SHARP's
|
||||
# own monodepth. Here we instead replace monodepth entirely
|
||||
# with the user's external depth — same code path init_model →
|
||||
# feature_model → prediction_head → gaussian_composer that the
|
||||
# with the user's external depth — same code path init_model ->
|
||||
# feature_model -> prediction_head -> gaussian_composer that the
|
||||
# official `decode()` runs, just with external monodepth
|
||||
# instead of `disparity_factor / disparity`.
|
||||
init_output = predictor.init_model(image_resized_pt, monodepth_override)
|
||||
@@ -646,12 +657,26 @@ class SharpPredictGaussiansFromMetricDepth(io.ComfyNode):
|
||||
|
||||
if snap_to_external_depth == "z_only":
|
||||
# Proportional rescale: mean_vectors *= target_z/current_z.
|
||||
# (xx*z, yy*z, z) → (xx*target_z, yy*target_z, target_z).
|
||||
# (xx*z, yy*z, z) -> (xx*target_z, yy*target_z, target_z).
|
||||
scale = (target_z_flat / current_z).unsqueeze(0).unsqueeze(-1)
|
||||
new_mean_vectors = gaussians_ndc.mean_vectors * scale
|
||||
gaussians_ndc = gaussians_ndc._replace(
|
||||
mean_vectors=new_mean_vectors,
|
||||
)
|
||||
elif snap_to_external_depth == "z_only_firstlayer":
|
||||
# Snap ONLY layer 0 to the external depth; leave layer 1+ at the
|
||||
# decoder's predicted z (the hallucinated occlusion backplate).
|
||||
# Gaussians are flattened (layer, h, w), so layer 0 is the first
|
||||
# H_grid*W_grid entries. Per-gaussian scale = 1.0 everywhere
|
||||
# except layer 0, where it is target_z/current_z.
|
||||
per_scale = torch.ones_like(current_z)
|
||||
L0 = H_grid * W_grid
|
||||
per_scale[:L0] = target_z_flat[:L0] / current_z[:L0]
|
||||
scale = per_scale.unsqueeze(0).unsqueeze(-1)
|
||||
new_mean_vectors = gaussians_ndc.mean_vectors * scale
|
||||
gaussians_ndc = gaussians_ndc._replace(
|
||||
mean_vectors=new_mean_vectors,
|
||||
)
|
||||
elif snap_to_external_depth == "xyz_full":
|
||||
# Reset xy to pixel-grid identity AND set z to target.
|
||||
new_mean_vectors = torch.stack(
|
||||
@@ -665,7 +690,17 @@ class SharpPredictGaussiansFromMetricDepth(io.ComfyNode):
|
||||
gaussians_ndc = gaussians_ndc._replace(
|
||||
mean_vectors=new_mean_vectors,
|
||||
)
|
||||
# else: "none" → leave gaussians_ndc as-is.
|
||||
elif snap_to_external_depth == "xyz_full_firstlayer":
|
||||
# xyz_full applied to layer 0 only; layer 1+ left untouched.
|
||||
new_mean_vectors = gaussians_ndc.mean_vectors.clone()
|
||||
L0 = H_grid * W_grid
|
||||
new_mean_vectors[0, :L0, 0] = base_xx_flat[:L0] * target_z_flat[:L0]
|
||||
new_mean_vectors[0, :L0, 1] = base_yy_flat[:L0] * target_z_flat[:L0]
|
||||
new_mean_vectors[0, :L0, 2] = target_z_flat[:L0]
|
||||
gaussians_ndc = gaussians_ndc._replace(
|
||||
mean_vectors=new_mean_vectors,
|
||||
)
|
||||
# else: "none" -> leave gaussians_ndc as-is.
|
||||
|
||||
# Build extrinsics + intrinsics_resized for unprojection.
|
||||
if extrinsics is not None:
|
||||
@@ -677,7 +712,7 @@ class SharpPredictGaussiansFromMetricDepth(io.ComfyNode):
|
||||
if intrinsics is not None:
|
||||
# Reuse the per-batch PIXEL-K computed at the top of the
|
||||
# loop. Re-fetching from `intrinsics` here would silently
|
||||
# bypass the normalized→pixel correction and give garbage
|
||||
# bypass the normalized->pixel correction and give garbage
|
||||
# `intrinsics_resized` (fx_resized = 0.5 * internal/width).
|
||||
K = intr_b_px.to(device).float().clone()
|
||||
# Promote to 4x4 if it came as 3x3 (unproject_gaussians wants 4x4).
|
||||
@@ -752,7 +787,7 @@ class SharpPredictGaussiansFromMetricDepth(io.ComfyNode):
|
||||
n_after = int(gaussians.mean_vectors.shape[1])
|
||||
if b == 0: # log once per batch to avoid spam on 42 faces
|
||||
_p(
|
||||
f"edge_crop={edge_crop:.3f} → crop_h={crop_h} crop_w={crop_w} "
|
||||
f"edge_crop={edge_crop:.3f} -> crop_h={crop_h} crop_w={crop_w} "
|
||||
f"per side; kept {n_after}/{n_before} gaussians "
|
||||
f"({100.0 * n_after / max(n_before, 1):.1f}%) per face"
|
||||
)
|
||||
@@ -783,7 +818,7 @@ class SharpPredictGaussiansFromMetricDepth(io.ComfyNode):
|
||||
)
|
||||
n_after_mask = int(gaussians.mean_vectors.shape[1])
|
||||
if b == 0:
|
||||
_p(f"mask → kept {n_after_mask}/{n_before_mask} gaussians "
|
||||
_p(f"mask -> kept {n_after_mask}/{n_before_mask} gaussians "
|
||||
f"({100.0 * n_after_mask / max(n_before_mask, 1):.1f}%)")
|
||||
|
||||
# Save PLY.
|
||||
@@ -805,7 +840,7 @@ class SharpPredictGaussiansFromMetricDepth(io.ComfyNode):
|
||||
if edge_crop and edge_crop > 0.0:
|
||||
layer_str += f", edge_crop={edge_crop:.3f}"
|
||||
_p(
|
||||
f"{B} face(s) → {n_gaussians_total/1e6:.2f}M gaussians total "
|
||||
f"{B} face(s) -> {n_gaussians_total/1e6:.2f}M gaussians total "
|
||||
f"({layer_str}, snap={snap_to_external_depth}); "
|
||||
f"saved to {loc}; {elapsed:.1f}s"
|
||||
)
|
||||
@@ -822,7 +857,7 @@ class SharpPredictGaussiansFromMetricDepth(io.ComfyNode):
|
||||
#
|
||||
# - intrinsics: forward the DENORMALIZED pixel-K we captured
|
||||
# per face (`all_intr_px`), NOT the raw input. PanoramaSplit
|
||||
# emits normalized K (fx≈0.5); SharpPredict's `intrinsics`
|
||||
# emits normalized K (fx~=0.5); SharpPredict's `intrinsics`
|
||||
# output is pixel-K (fx in the hundreds) because it stores
|
||||
# the denormalized form in `metadata["intrinsic"]`. Matching
|
||||
# that convention keeps downstream consumers (preview
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
"""SharpPredictMetricDepth — image → disparity-head metric depth only.
|
||||
"""SharpPredictMetricDepth — image -> disparity-head metric depth only.
|
||||
|
||||
Tap SHARP's encoder + disparity head; skip the gaussian decoder entirely.
|
||||
Fastest path to a clean per-pixel depth map at SHARP's native 1536²
|
||||
@@ -56,7 +56,7 @@ def _rescale_K(intr_in, target_w, target_h, src_w, src_h):
|
||||
|
||||
|
||||
class SharpPredictMetricDepth(io.ComfyNode):
|
||||
"""SHARP encoder + disparity head only → per-pixel metric depth at 1536²."""
|
||||
"""SHARP encoder + disparity head only -> per-pixel metric depth at 1536²."""
|
||||
|
||||
@classmethod
|
||||
def define_schema(cls):
|
||||
@@ -99,7 +99,7 @@ class SharpPredictMetricDepth(io.ComfyNode):
|
||||
tooltip="[B, 1536, 1536, 3] LAYER-1 (back/occluded) metric "
|
||||
"depth — SHARP's hallucinated backplate surface. "
|
||||
"Typically tracks layer 0 in flat regions and "
|
||||
"diverges at occlusion boundaries (column edges → "
|
||||
"diverges at occlusion boundaries (column edges -> "
|
||||
"sky depth behind, etc.). Feeds layer-1 gaussian "
|
||||
"base values."),
|
||||
io.Custom("EXTRINSICS").Output(
|
||||
@@ -169,17 +169,17 @@ class SharpPredictMetricDepth(io.ComfyNode):
|
||||
)
|
||||
intrinsics = _K_default.unsqueeze(0).repeat(B, 1, 1)
|
||||
_p(
|
||||
f"intrinsics not wired → using identity-style K "
|
||||
f"intrinsics not wired -> using identity-style K "
|
||||
f"(focal={_f_px_default:.1f}px @ image {_img_W}×{_img_H}, "
|
||||
f"35mm-equiv {focal_length_mm:.1f}mm). Pass intrinsics from "
|
||||
f"PanoramaSplit for per-face accurate K."
|
||||
)
|
||||
else:
|
||||
# Convention sniff: PanoPack's PanoramaSplit emits NORMALIZED K
|
||||
# (fx≈0.5, cx≈0.5 for 90° fov via utils3d.np.intrinsics_from_fov,
|
||||
# (fx~=0.5, cx~=0.5 for 90° fov via utils3d.np.intrinsics_from_fov,
|
||||
# units in [0,1]), whereas Sharp's predict path assumes pixel-K
|
||||
# (fx in the hundreds). Without this conversion, `f_px = K[0,0] *
|
||||
# (1536/width)` resolves to 0.5 instead of ~768, the disparity →
|
||||
# (1536/width)` resolves to 0.5 instead of ~768, the disparity ->
|
||||
# depth math collapses, and every face comes out as a blank (~0)
|
||||
# depth map. Detect normalized (fx < 2.0) and rescale once to
|
||||
# pixel-K for the face image's (width, height). All downstream
|
||||
@@ -314,7 +314,7 @@ class SharpPredictMetricDepth(io.ComfyNode):
|
||||
pts_min = float(pts_norm.min())
|
||||
pts_max = float(pts_norm.max())
|
||||
elapsed = time.time() - t_start
|
||||
k_str = f", K_{internal_shape[0]} fx→{k_fx_md_dbg:.1f}" if k_fx_md_dbg is not None else ""
|
||||
k_str = f", K_{internal_shape[0]} fx->{k_fx_md_dbg:.1f}" if k_fx_md_dbg is not None else ""
|
||||
_p(
|
||||
f"{B} face(s) @ {internal_shape[0]}²; "
|
||||
f"layer0/layer1 depth median={m_med_l0:.2f}/{m_med_l1:.2f}m{k_str}; "
|
||||
|
||||
@@ -25,8 +25,8 @@ Inputs:
|
||||
extrinsics (EXTRINSICS) — pass-through (not used in the math)
|
||||
intrinsics (INTRINSICS) — per-face K; needed to build the cos-map.
|
||||
Accepts normalized K (PanoPack convention,
|
||||
fx≈0.5) or pixel K (Sharp convention,
|
||||
fx≈hundreds) — auto-detected and rescaled.
|
||||
fx~=0.5) or pixel K (Sharp convention,
|
||||
fx~=hundreds) — auto-detected and rescaled.
|
||||
|
||||
Outputs:
|
||||
image (IMAGE) — per-face depth in planar-Z convention
|
||||
@@ -55,7 +55,7 @@ class SharpRayToPlanarDepth(io.ComfyNode):
|
||||
def define_schema(cls):
|
||||
return io.Schema(
|
||||
node_id="SharpRayToPlanarDepth",
|
||||
display_name="SHARP Ray-Distance → Planar Depth",
|
||||
display_name="SHARP Ray-Distance -> Planar Depth",
|
||||
category="SHARP",
|
||||
description=(
|
||||
"Per-face depth convention converter. Multiplies "
|
||||
@@ -79,7 +79,7 @@ class SharpRayToPlanarDepth(io.ComfyNode):
|
||||
io.Custom("INTRINSICS").Input(
|
||||
"intrinsics",
|
||||
tooltip="Per-face camera intrinsics. Accepts either "
|
||||
"normalized K (fx≈0.5 for 90° fov, PanoPack "
|
||||
"normalized K (fx~=0.5 for 90° fov, PanoPack "
|
||||
"convention) or pixel K (fx in the hundreds, "
|
||||
"Sharp convention) — auto-detected. The "
|
||||
"output intrinsics socket emits pixel-K so "
|
||||
@@ -188,7 +188,7 @@ class SharpRayToPlanarDepth(io.ComfyNode):
|
||||
planar = depth_2d_batch * cos_map.unsqueeze(0)
|
||||
cos_min_log = f"min={float(cos_map.min()):.4f} max={float(cos_map.max()):.4f}"
|
||||
|
||||
_p(f"ray→planar: B={B} {H}×{W}, cos_map {cos_min_log}, "
|
||||
_p(f"ray->planar: B={B} {H}×{W}, cos_map {cos_min_log}, "
|
||||
f"intrinsics{'_rescaled' if rescaled else ''} fx={fx:.1f} cx={cx:.1f}")
|
||||
|
||||
# Restore channel dim if input had one.
|
||||
@@ -199,4 +199,4 @@ class SharpRayToPlanarDepth(io.ComfyNode):
|
||||
|
||||
|
||||
NODE_CLASS_MAPPINGS = {"SharpRayToPlanarDepth": SharpRayToPlanarDepth}
|
||||
NODE_DISPLAY_NAME_MAPPINGS = {"SharpRayToPlanarDepth": "SHARP Ray-Distance → Planar Depth"}
|
||||
NODE_DISPLAY_NAME_MAPPINGS = {"SharpRayToPlanarDepth": "SHARP Ray-Distance -> Planar Depth"}
|
||||
|
||||
@@ -72,7 +72,7 @@ class ScaleImageValues(io.ComfyNode):
|
||||
_p(
|
||||
f"{B} image(s) {tuple(image.shape[1:])}: "
|
||||
f"in [{float(img_min.min()):.4f}, {float(img_max.max()):.4f}] "
|
||||
f"→ out [0, 1]"
|
||||
f"-> out [0, 1]"
|
||||
)
|
||||
return io.NodeOutput(scaled)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user