GeoNeXt / GeoNeXt-Wan /inference.py
happy0612's picture
Deploy GeoNeXt inference on ZeroGPU
a61978a verified
Raw History Blame Contribute Delete
4.45 kB
"""GeoNeXt-Wan inference adapter."""
import subprocess
import shutil
import sys
import tempfile
from pathlib import Path
import numpy as np
from PIL import Image
from utils.common import list_images
GEONEXT_REPO = "happy0612/GeoNeXt"
GEONEXT_WAN_FILE = "GeoNeXt-Wan/geonext_wan.safetensors"
WAN_BASE_MODEL = "Wan-AI/Wan2.1-T2V-1.3B"
def _resolve_models(args):
from huggingface_hub import hf_hub_download, snapshot_download
checkpoint = args.checkpoint
if checkpoint is None:
print("Downloading GeoNeXt-Wan checkpoint from %s" % GEONEXT_REPO)
checkpoint = hf_hub_download(repo_id=GEONEXT_REPO, filename=GEONEXT_WAN_FILE)
base_model = args.base_model or WAN_BASE_MODEL
if not Path(base_model).exists():
print("Downloading Wan base model from %s" % base_model)
base_model = snapshot_download(repo_id=base_model)
return checkpoint, base_model
def run(args):
from utils.geometry import Camera, disparity_to_relative_depth, export_geometry
aligner = None
if args.align_space == "moge":
from utils.moge import MoGeAligner
aligner = MoGeAligner(args.moge_model)
root, images = list_images(args.input)
checkpoint, base_model = _resolve_models(args)
processing_res = 768 if args.processing_res is None else args.processing_res
processing_side = args.processing_res_side or "long"
temporary_input = None
input_dir = root
if Path(args.input).is_file():
# The underlying Wan batch runner consumes a directory. Stage a single
# input transparently so the public CLI accepts the same inputs as SVD.
temporary_input = tempfile.TemporaryDirectory(prefix="geonext-wan-input-")
input_dir = Path(temporary_input.name)
shutil.copy2(args.input, input_dir / Path(args.input).name)
try:
command = [
sys.executable, str(Path(__file__).with_name("model_inference.py")),
"--checkpoint", checkpoint,
"--wan_model_dir", base_model,
"--input_dir", str(input_dir),
"--output_dir", args.output,
"--target_modalities", "depth,normal",
"--rgb_condition_mode", "concat",
"--num_inference_steps", str(args.steps),
"--cfg_scale", "1.0",
"--processing_res", str(processing_res),
"--processing_res_side", processing_side,
"--temporal_rope_scale", "8",
"--seed", str(args.seed),
]
subprocess.run(command, check=True)
finally:
if temporary_input is not None:
temporary_input.cleanup()
if not args.export:
return
output = Path(args.output)
for image_path in images:
stem = image_path.stem
depth_path = output / "depth_raw" / (stem + ".npy")
normal_raw_path = output / "normal_raw" / (stem + ".npy")
normal_path = output / "normal_vis" / (stem + ".png")
if not depth_path.exists() or (not normal_raw_path.exists() and not normal_path.exists()):
print("warning: missing prediction for %s; geometry skipped" % image_path)
continue
disparity = np.load(depth_path)
if normal_raw_path.exists():
normal = np.load(normal_raw_path).astype(np.float32)
else:
normal = np.asarray(Image.open(normal_path).convert("RGB"), dtype=np.float32) / 127.5 - 1.0
rgb = Image.open(image_path).convert("RGB")
mask = None
if aligner is not None:
depth, mask, camera, intrinsics, scale, shift = aligner.align_disparity(disparity, rgb)
if not args.use_mask:
mask = None
else:
depth = disparity_to_relative_depth(disparity)
camera = Camera.from_fov_x(depth.shape[1], depth.shape[0], args.fov_x)
case = output / "geometry" / stem
case.mkdir(parents=True, exist_ok=True)
np.save(case / "depth.npy", depth)
if aligner is not None:
np.save(case / "intrinsics.npy", intrinsics)
export_geometry(case, rgb, depth, camera,
normal=normal, mask=mask, exports=args.export, stride=args.stride,
edge_threshold=args.edge_threshold,
normal_angle_threshold=args.normal_angle_threshold,
normal_bypass_depth_threshold=args.normal_bypass_depth_threshold)
print("geometry: %s" % case)