Bump version to 0.11.2 and update uv version to 0.11.3
- Updated version in pyproject.toml and blender_manifest.toml to 0.11.2. - Enhanced uv executable path documentation in client.py. - Added inference_mode parameter to reconstruct_request function in client.py. - Implemented uv download functionality in install_env.py with improved error handling and checksum verification. - Modified install_uv function to download and extract uv binary instead of using bundled version. - Updated job progress handling in operators.py to track progress stages. - Improved download progress reporting in worker.py and adjusted related tests. - Refactored tests to accommodate changes in status reporting and model downloading. - Removed vendor.lock.toml as part of the cleanup process.
This commit is contained in:
@@ -1,7 +1,7 @@
|
||||
schema_version = "1.0.0"
|
||||
|
||||
id = "snapform"
|
||||
version = "0.11.1"
|
||||
version = "0.11.2"
|
||||
name = "SnapForm"
|
||||
tagline = "Build spatial structures from images, fast"
|
||||
maintainer = "OmooLab"
|
||||
|
||||
@@ -269,7 +269,7 @@ def job_id_from_status(status_path):
|
||||
|
||||
|
||||
def uv_executable():
|
||||
"""Return the bundled uv path inside the shared storage."""
|
||||
"""Return the downloaded uv path inside the shared storage."""
|
||||
return str(uv_cache_path())
|
||||
|
||||
|
||||
@@ -833,6 +833,7 @@ def reconstruct_request(
|
||||
mesh_filter_edges=None,
|
||||
start_frame=1,
|
||||
device=None,
|
||||
inference_mode="joint",
|
||||
):
|
||||
snapform_selected_key = model_key or selected_model_key(settings, export_format)
|
||||
snapform_selected_process_res = (
|
||||
@@ -867,6 +868,7 @@ def reconstruct_request(
|
||||
"device": (device or settings.device).lower(),
|
||||
"use_ray_pose": 1 if snapform_selected_ray_pose else 0,
|
||||
"ref_view_strategy": settings.ref_view_strategy,
|
||||
"inference_mode": inference_mode,
|
||||
"remove_background_model_dir": "",
|
||||
"mesh_filter_edges": bool(
|
||||
getattr(settings, "mesh_filter_edges", True)
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
import argparse
|
||||
import hashlib
|
||||
import http.client
|
||||
import json
|
||||
import os
|
||||
import platform
|
||||
@@ -36,13 +38,30 @@ PILLOW_VERSION = "12.1.1"
|
||||
AV_VERSION = "17.0.0"
|
||||
TRIMESH_VERSION = "4.11.2"
|
||||
HUGGINGFACE_HUB_VERSION = "1.4.1"
|
||||
UV_VERSION = "0.11.3"
|
||||
UV_OFFICIAL_CONNECT_TIMEOUT = 3.0
|
||||
UV_MIRROR_CONNECT_TIMEOUT = 15.0
|
||||
DOWNLOAD_CHUNK_SIZE = 256 * 1024
|
||||
UV_OFFICIAL_BASE_URL = (
|
||||
f"https://github.com/astral-sh/uv/releases/download/{UV_VERSION}"
|
||||
)
|
||||
UV_MIRROR_BASE_URL = f"https://mirror.omoolab.xyz/uv/{UV_VERSION}"
|
||||
UV_ARTIFACTS = {
|
||||
"x86_64-pc-windows-msvc": (
|
||||
"uv-x86_64-pc-windows-msvc.zip",
|
||||
"ae681c0aaec7cc96af184648cb88d73f8393ed60fa5880abdd6bdb910f9b227c",
|
||||
),
|
||||
"aarch64-apple-darwin": (
|
||||
"uv-aarch64-apple-darwin.tar.gz",
|
||||
"2bc3d0c7bf2bd08325b1e170abac6f7e5b3346e1d4eab3370d17cefec934996f",
|
||||
),
|
||||
"x86_64-unknown-linux-gnu": (
|
||||
"uv-x86_64-unknown-linux-gnu.tar.gz",
|
||||
"c0f3236f146e55472663cfbcc9be3042a9f1092275bbe3fe2a56a6cbfd3da5ce",
|
||||
),
|
||||
}
|
||||
_resolved_pypi_index = None
|
||||
DEFAULT_MODEL_KEYS = (MOGE2_MODEL.key, BEN2_MODEL.key)
|
||||
RUNTIME_STAGE_COUNT = 2
|
||||
ENVIRONMENT_SLICE = (0.02, 0.06)
|
||||
AI_COMPONENTS_SLICE = (0.10, 0.85)
|
||||
MODELS_SLICE_START = 0.85
|
||||
MODELS_SLICE_END = 1.0
|
||||
RAMP_RATE = 0.02
|
||||
CREEP_CAP = 0.60
|
||||
CHECK_CODE = (
|
||||
@@ -54,6 +73,48 @@ CHECK_CODE = (
|
||||
)
|
||||
|
||||
|
||||
class _ConnectHTTPSConnection(http.client.HTTPSConnection):
|
||||
"""Limit TCP/TLS setup without limiting artifact reads."""
|
||||
|
||||
connect_timeout = UV_OFFICIAL_CONNECT_TIMEOUT
|
||||
|
||||
def connect(self):
|
||||
try:
|
||||
self.timeout = self.connect_timeout
|
||||
super().connect()
|
||||
finally:
|
||||
self.timeout = None
|
||||
self.sock.settimeout(None)
|
||||
|
||||
|
||||
class _MirrorConnectHTTPSConnection(_ConnectHTTPSConnection):
|
||||
connect_timeout = UV_MIRROR_CONNECT_TIMEOUT
|
||||
|
||||
|
||||
class _ConnectHTTPSHandler(urllib.request.HTTPSHandler):
|
||||
def __init__(self, connection_type):
|
||||
super().__init__()
|
||||
self.connection_type = connection_type
|
||||
|
||||
def https_open(self, request):
|
||||
return self.do_open(self.connection_type, request)
|
||||
|
||||
|
||||
def build_uv_download_opener(connection_type):
|
||||
return urllib.request.build_opener(
|
||||
urllib.request.ProxyHandler(),
|
||||
_ConnectHTTPSHandler(connection_type),
|
||||
)
|
||||
|
||||
|
||||
_uv_download_openers = {
|
||||
UV_OFFICIAL_BASE_URL: build_uv_download_opener(_ConnectHTTPSConnection),
|
||||
UV_MIRROR_BASE_URL: build_uv_download_opener(
|
||||
_MirrorConnectHTTPSConnection
|
||||
),
|
||||
}
|
||||
|
||||
|
||||
def configure_standard_streams():
|
||||
"""Windows 图形进程可能继承 GBK stdout,安装日志统一写为 UTF-8。"""
|
||||
for stream in (sys.stdout, sys.stderr):
|
||||
@@ -484,22 +545,6 @@ def uv_download_target():
|
||||
raise RuntimeError(f"Unsupported platform for uv download: {sys.platform}")
|
||||
|
||||
|
||||
def bundled_vendor_directory():
|
||||
packaged = Path(__file__).with_name("vendor")
|
||||
if packaged.is_dir():
|
||||
return packaged
|
||||
project_root = Path(__file__).resolve().parents[2]
|
||||
if (project_root / "pyproject.toml").is_file():
|
||||
return project_root / "vendor"
|
||||
return packaged
|
||||
|
||||
|
||||
def bundled_uv_archive():
|
||||
target, archive = uv_download_target()
|
||||
path = bundled_vendor_directory() / f"uv-{target}.{archive}"
|
||||
return path if path.is_file() else None
|
||||
|
||||
|
||||
def archive_is_valid(path):
|
||||
if zipfile.is_zipfile(path):
|
||||
return True
|
||||
@@ -526,6 +571,99 @@ def extract_uv(archive_path, uv_path):
|
||||
os.chmod(uv_path, 0o755)
|
||||
|
||||
|
||||
def file_sha256(path):
|
||||
digest = hashlib.sha256()
|
||||
with Path(path).open("rb") as stream:
|
||||
while chunk := stream.read(1024 * 1024):
|
||||
digest.update(chunk)
|
||||
return digest.hexdigest()
|
||||
|
||||
|
||||
def format_download_rate(rate):
|
||||
size = float(rate)
|
||||
units = ("B/s", "KB/s", "MB/s", "GB/s")
|
||||
unit_index = 0
|
||||
while size >= 1024 and unit_index < len(units) - 1:
|
||||
size /= 1024
|
||||
unit_index += 1
|
||||
precision = 0 if unit_index == 0 else 1
|
||||
return f"{size:.{precision}f} {units[unit_index]}"
|
||||
|
||||
|
||||
def download_uv_archive(
|
||||
destination,
|
||||
status_path,
|
||||
start,
|
||||
end,
|
||||
stage_fields,
|
||||
clock=time.monotonic,
|
||||
):
|
||||
target, _archive_type = uv_download_target()
|
||||
artifact = UV_ARTIFACTS.get(target)
|
||||
if artifact is None:
|
||||
raise RuntimeError(f"SnapForm does not provide uv for {target}")
|
||||
filename, expected_checksum = artifact
|
||||
failures = []
|
||||
sources = (
|
||||
(UV_OFFICIAL_BASE_URL, ""),
|
||||
(UV_MIRROR_BASE_URL, " (mirror)"),
|
||||
)
|
||||
for base_url, source_label in sources:
|
||||
destination.unlink(missing_ok=True)
|
||||
url = f"{base_url}/{filename}"
|
||||
write_status(
|
||||
status_path,
|
||||
start,
|
||||
f"Downloading uv{source_label}",
|
||||
**stage_fields,
|
||||
)
|
||||
request = urllib.request.Request(
|
||||
url,
|
||||
headers={"User-Agent": "SnapForm uv bootstrap"},
|
||||
)
|
||||
try:
|
||||
with _uv_download_openers[base_url].open(request) as response:
|
||||
total = int(response.headers.get("Content-Length", 0))
|
||||
downloaded = 0
|
||||
started_at = clock()
|
||||
last_report_at = started_at
|
||||
with destination.open("wb") as output:
|
||||
while chunk := response.read(DOWNLOAD_CHUNK_SIZE):
|
||||
output.write(chunk)
|
||||
downloaded += len(chunk)
|
||||
now = clock()
|
||||
if now - last_report_at < 0.20 and downloaded < total:
|
||||
continue
|
||||
last_report_at = now
|
||||
elapsed = max(now - started_at, 1e-6)
|
||||
rate = downloaded / elapsed
|
||||
message = (
|
||||
f"Downloading uv{source_label} "
|
||||
f"{format_download_rate(rate)}"
|
||||
)
|
||||
progress = start
|
||||
if total:
|
||||
progress += (end - start) * downloaded / total
|
||||
write_status(
|
||||
status_path,
|
||||
min(progress, end),
|
||||
message,
|
||||
**stage_fields,
|
||||
)
|
||||
actual_checksum = file_sha256(destination)
|
||||
if actual_checksum != expected_checksum:
|
||||
raise RuntimeError(
|
||||
f"SHA-256 mismatch: {actual_checksum}"
|
||||
)
|
||||
if not archive_is_valid(destination):
|
||||
raise RuntimeError("Downloaded uv archive is invalid")
|
||||
return destination
|
||||
except Exception as error:
|
||||
destination.unlink(missing_ok=True)
|
||||
failures.append(f"{url}: {error}")
|
||||
raise RuntimeError("Unable to download uv; " + "; ".join(failures))
|
||||
|
||||
|
||||
def install_uv(
|
||||
uv,
|
||||
status_path,
|
||||
@@ -535,29 +673,31 @@ def install_uv(
|
||||
start=0.0,
|
||||
end=1.0,
|
||||
):
|
||||
"""Extract the bundled uv binary; fail clearly when it is missing."""
|
||||
"""Download, verify, and extract the pinned uv binary."""
|
||||
stage_fields = {
|
||||
"stage": stage,
|
||||
"stages": stages,
|
||||
"stage_label": stage_label,
|
||||
}
|
||||
bundled_uv = bundled_uv_archive()
|
||||
if bundled_uv is None:
|
||||
raise RuntimeError(
|
||||
"Bundled uv is missing; reinstall the extension matching your "
|
||||
f"platform (searched {bundled_vendor_directory()})"
|
||||
)
|
||||
if not archive_is_valid(bundled_uv):
|
||||
raise RuntimeError(f"Bundled uv archive is invalid: {bundled_uv}")
|
||||
print(f"Extracting bundled uv from {bundled_uv}")
|
||||
write_status(
|
||||
status_path,
|
||||
start,
|
||||
"extracting bundled uv",
|
||||
**stage_fields,
|
||||
)
|
||||
Path(uv).parent.mkdir(parents=True, exist_ok=True)
|
||||
extract_uv(bundled_uv, uv)
|
||||
archive_path = Path(uv).with_name("uv-download.tmp")
|
||||
try:
|
||||
download_uv_archive(
|
||||
archive_path,
|
||||
status_path,
|
||||
start,
|
||||
end,
|
||||
stage_fields,
|
||||
)
|
||||
write_status(
|
||||
status_path,
|
||||
end,
|
||||
"Extracting uv",
|
||||
**stage_fields,
|
||||
)
|
||||
extract_uv(archive_path, uv)
|
||||
finally:
|
||||
archive_path.unlink(missing_ok=True)
|
||||
|
||||
|
||||
def run_stage(
|
||||
@@ -649,38 +789,30 @@ def install(args):
|
||||
stages = install_stages(args, venv_dir, python, uv)
|
||||
env_stages = [stage for stage in stages if stage[3] != "download"]
|
||||
model_stages = [stage for stage in stages if stage[3] == "download"]
|
||||
model_span = (
|
||||
(MODELS_SLICE_END - MODELS_SLICE_START) / len(model_stages)
|
||||
if model_stages
|
||||
else 0.0
|
||||
)
|
||||
total_stages = int(uv_missing) + 1 + len(model_stages)
|
||||
stage = 1
|
||||
installed_packages = []
|
||||
for index, (label, command, environment, kind) in enumerate(env_stages):
|
||||
if index == 0:
|
||||
stage = 1
|
||||
stage_start, stage_end = ENVIRONMENT_SLICE
|
||||
elif index == 1:
|
||||
stage = 2
|
||||
stage_start, stage_end = AI_COMPONENTS_SLICE
|
||||
else:
|
||||
raise RuntimeError(f"Unexpected runtime stage: {label}")
|
||||
if uv_missing and index == 0:
|
||||
midpoint = stage_start + (stage_end - stage_start) * 0.5
|
||||
install_uv(
|
||||
uv,
|
||||
args.status,
|
||||
stage=stage,
|
||||
stages=RUNTIME_STAGE_COUNT,
|
||||
stage_label=label,
|
||||
start=stage_start,
|
||||
end=midpoint,
|
||||
)
|
||||
stage_start = midpoint
|
||||
if uv_missing:
|
||||
install_uv(
|
||||
uv,
|
||||
args.status,
|
||||
stage=stage,
|
||||
stages=total_stages,
|
||||
stage_label="uv",
|
||||
start=0.0,
|
||||
end=1.0,
|
||||
)
|
||||
stage += 1
|
||||
environment_label = "ONNX Environment"
|
||||
for index, (_label, command, environment, kind) in enumerate(env_stages):
|
||||
if index >= 2:
|
||||
raise RuntimeError("Unexpected ONNX Environment stage")
|
||||
stage_start, stage_end = ((0.0, 0.05) if index == 0 else (0.05, 1.0))
|
||||
stage_installed = run_stage(
|
||||
args.status,
|
||||
stage,
|
||||
RUNTIME_STAGE_COUNT,
|
||||
label,
|
||||
total_stages,
|
||||
environment_label,
|
||||
command,
|
||||
environment,
|
||||
kind,
|
||||
@@ -689,6 +821,7 @@ def install(args):
|
||||
)
|
||||
if isinstance(stage_installed, list):
|
||||
installed_packages.extend(stage_installed)
|
||||
stage += 1
|
||||
devices = installed_devices()
|
||||
venv_dir.mkdir(parents=True, exist_ok=True)
|
||||
manifest_path.parent.mkdir(parents=True, exist_ok=True)
|
||||
@@ -711,44 +844,50 @@ def install(args):
|
||||
),
|
||||
encoding="utf-8",
|
||||
)
|
||||
model_offset = MODELS_SLICE_START
|
||||
failed_models = []
|
||||
for label, command, environment, kind in model_stages:
|
||||
stage_start = model_offset
|
||||
stage_end = stage_start + model_span
|
||||
try:
|
||||
run_stage(
|
||||
args.status,
|
||||
None,
|
||||
None,
|
||||
stage,
|
||||
total_stages,
|
||||
label,
|
||||
command,
|
||||
environment,
|
||||
kind,
|
||||
start=stage_start,
|
||||
end=stage_end,
|
||||
start=0.0,
|
||||
end=1.0,
|
||||
)
|
||||
except Exception as error:
|
||||
print(f"Model download failed: {label}: {error}")
|
||||
failed_models.append(label)
|
||||
write_status(
|
||||
args.status,
|
||||
stage_end,
|
||||
1.0,
|
||||
f"Skipping {label}; continuing",
|
||||
stage=stage,
|
||||
stages=total_stages,
|
||||
stage_label=label,
|
||||
)
|
||||
model_offset = stage_end
|
||||
stage += 1
|
||||
if failed_models:
|
||||
labels = ", ".join(failed_models)
|
||||
write_status(
|
||||
args.status,
|
||||
1.0,
|
||||
f"Environment is installed; retry {labels} from Models",
|
||||
stage=total_stages,
|
||||
stages=total_stages,
|
||||
stage_label=model_stages[-1][0],
|
||||
)
|
||||
return
|
||||
write_status(
|
||||
args.status,
|
||||
1.0,
|
||||
"Environment and recommended models ready",
|
||||
stage=total_stages,
|
||||
stages=total_stages,
|
||||
stage_label=model_stages[-1][0] if model_stages else environment_label,
|
||||
)
|
||||
|
||||
|
||||
|
||||
@@ -108,6 +108,7 @@ class ActiveJob:
|
||||
started: bool = False
|
||||
start_error: str = ""
|
||||
start_thread: threading.Thread | None = None
|
||||
progress_stage: int | None = None
|
||||
|
||||
|
||||
_active_job = None
|
||||
@@ -520,6 +521,11 @@ class RunJob(bpy.types.Operator):
|
||||
and remove_background
|
||||
),
|
||||
device=self._task_device(settings),
|
||||
inference_mode=(
|
||||
"per_frame"
|
||||
if self.job_type in DA3_IMAGE_JOBS
|
||||
else "joint"
|
||||
),
|
||||
),
|
||||
paths,
|
||||
)
|
||||
@@ -612,10 +618,17 @@ class RunJob(bpy.types.Operator):
|
||||
reported_progress = float(
|
||||
status.get("progress", settings.progress)
|
||||
)
|
||||
settings.progress = max(
|
||||
settings.progress,
|
||||
min(max(reported_progress, 0.0), 1.0),
|
||||
)
|
||||
reported_progress = min(max(reported_progress, 0.0), 1.0)
|
||||
reported_stage = status.get("stage")
|
||||
if reported_stage is not None and reported_stage != getattr(
|
||||
job,
|
||||
"progress_stage",
|
||||
None,
|
||||
):
|
||||
job.progress_stage = reported_stage
|
||||
settings.progress = reported_progress
|
||||
else:
|
||||
settings.progress = max(settings.progress, reported_progress)
|
||||
settings.status = self._format_status(status, settings.status)
|
||||
self._redraw_ui(context, force=True)
|
||||
|
||||
@@ -624,38 +637,19 @@ class RunJob(bpy.types.Operator):
|
||||
stage = status.get("stage")
|
||||
stages = status.get("stages")
|
||||
stage_label = status.get("stage_label")
|
||||
parts = []
|
||||
if stage and stages:
|
||||
stage_text = f"{stage}/{stages}"
|
||||
prefix = f"{stage}/{stages}"
|
||||
normalized = str(message or "").strip()
|
||||
if normalized and normalized.lower() != str(
|
||||
stage_label or ""
|
||||
).lower():
|
||||
return f"{prefix} {normalized}"
|
||||
if stage_label:
|
||||
stage_text = f"{stage_text} {stage_label}"
|
||||
parts.append(stage_text)
|
||||
elif stage_label:
|
||||
parts.append(str(stage_label))
|
||||
return f"{prefix} {stage_label}"
|
||||
return prefix
|
||||
if message:
|
||||
normalized = str(message).strip()
|
||||
if normalized.lower() != str(stage_label or "").lower():
|
||||
parts.append(normalized)
|
||||
eta = self._format_eta(status.get("eta_seconds"))
|
||||
if eta:
|
||||
parts.append(eta)
|
||||
return " | ".join(parts) if parts else (message or fallback)
|
||||
|
||||
@staticmethod
|
||||
def _format_eta(seconds):
|
||||
if seconds is None:
|
||||
return ""
|
||||
seconds = max(float(seconds), 0.0)
|
||||
if seconds <= 10:
|
||||
return "A few seconds left"
|
||||
if seconds < 60:
|
||||
rounded = max(5, int((seconds + 2.5) // 5) * 5)
|
||||
return f"About {rounded} sec left"
|
||||
if seconds < 3600:
|
||||
minutes = int((seconds + 59) // 60)
|
||||
return f"About {minutes} min left"
|
||||
hours = int((seconds + 3599) // 3600)
|
||||
return f"About {hours} hr left"
|
||||
return str(message).strip()
|
||||
return str(stage_label or fallback)
|
||||
|
||||
def _finish(self, context, job, return_code):
|
||||
settings = context.scene.snapform_settings
|
||||
|
||||
@@ -49,7 +49,8 @@ EXPORT_FORMATS = (
|
||||
HF_ENDPOINT = "https://huggingface.co"
|
||||
R2_ENDPOINT = "https://models.omoolab.xyz"
|
||||
# 连接(TCP + TLS 握手)超时,源不可达时快速失败切换;下载阶段不设读超时。
|
||||
CONNECT_TIMEOUT = 6.0
|
||||
CONNECT_TIMEOUT = 3.0
|
||||
DOWNLOAD_CHUNK_SIZE = 256 * 1024
|
||||
|
||||
|
||||
class _ShortConnectHTTPSConnection(http.client.HTTPSConnection):
|
||||
@@ -898,7 +899,7 @@ class DownloadProgressReporter:
|
||||
with self.lock:
|
||||
if progress <= self.progress:
|
||||
return
|
||||
if now - self.last_write_time < 0.20 and progress < 0.95:
|
||||
if now - self.last_write_time < 0.20 and factor < 1.0:
|
||||
return
|
||||
self.progress = progress
|
||||
self.last_write_time = now
|
||||
@@ -907,20 +908,10 @@ class DownloadProgressReporter:
|
||||
if not effective_rate and elapsed > 0:
|
||||
effective_rate = current / elapsed
|
||||
speed = format_rate(effective_rate) if effective_rate else ""
|
||||
eta_seconds = None
|
||||
if effective_rate and effective_rate > 0:
|
||||
eta_seconds = max(total - current, 0) / effective_rate
|
||||
if self.stage is not None:
|
||||
message = (
|
||||
f"Downloading packages | {speed}"
|
||||
if speed
|
||||
else "Downloading packages"
|
||||
)
|
||||
else:
|
||||
prefix = self.label or self.repository
|
||||
message = f"Downloading {prefix}"
|
||||
if speed:
|
||||
message = f"{message} | {speed}"
|
||||
prefix = self.label or self.repository
|
||||
message = f"Downloading {prefix}"
|
||||
if speed:
|
||||
message = f"{message} {speed}"
|
||||
write_status(
|
||||
self.status_path,
|
||||
progress,
|
||||
@@ -928,7 +919,6 @@ class DownloadProgressReporter:
|
||||
stage=self.stage,
|
||||
stages=self.stages,
|
||||
stage_label=self.stage_label,
|
||||
eta_seconds=eta_seconds,
|
||||
)
|
||||
|
||||
|
||||
@@ -1063,7 +1053,7 @@ def _download_declared_or_fail(args, model_dir, label, start, span, stage_kwargs
|
||||
(
|
||||
R2_ENDPOINT,
|
||||
download_from_r2,
|
||||
f"Downloading {label} | OmooLab mirror",
|
||||
f"Downloading {label} (mirror)",
|
||||
),
|
||||
)
|
||||
else:
|
||||
@@ -1072,7 +1062,7 @@ def _download_declared_or_fail(args, model_dir, label, start, span, stage_kwargs
|
||||
(
|
||||
R2_ENDPOINT,
|
||||
download_from_r2,
|
||||
f"Downloading {label} | OmooLab mirror",
|
||||
f"Downloading {label} (mirror)",
|
||||
),
|
||||
)
|
||||
failures = []
|
||||
@@ -1209,7 +1199,7 @@ def _download_file_from_url(
|
||||
with opener(request) as response:
|
||||
with open(temporary, "wb") as output:
|
||||
while True:
|
||||
chunk = response.read(1024 * 1024)
|
||||
chunk = response.read(DOWNLOAD_CHUNK_SIZE)
|
||||
if not chunk:
|
||||
break
|
||||
output.write(chunk)
|
||||
@@ -1310,7 +1300,7 @@ def download_from_r2(
|
||||
lambda filename: r2_file_url(args.r2_directory, filename),
|
||||
start,
|
||||
span,
|
||||
label,
|
||||
f"{label} (mirror)" if label else "mirror",
|
||||
stage_kwargs,
|
||||
)
|
||||
|
||||
@@ -1935,6 +1925,25 @@ def run_moge(args, model_cache=None, cancel_check=None):
|
||||
shutil.rmtree(temporary_directory, ignore_errors=True)
|
||||
|
||||
|
||||
def merge_da3_frame_predictions(predictions):
|
||||
"""Combine independent single-frame DA3 results for existing exporters."""
|
||||
import numpy as np
|
||||
|
||||
if not predictions:
|
||||
raise ValueError("DA3 returned no frame predictions")
|
||||
merged = predictions[0]
|
||||
for name in (
|
||||
"depth",
|
||||
"conf",
|
||||
"extrinsics",
|
||||
"intrinsics",
|
||||
"processed_images",
|
||||
):
|
||||
values = [getattr(prediction, name) for prediction in predictions]
|
||||
setattr(merged, name, np.concatenate(values, axis=0))
|
||||
return merged
|
||||
|
||||
|
||||
def reconstruct(args, model_cache=None, cancel_check=None):
|
||||
write_status(args.status, 0.02, "Starting Environment")
|
||||
check_cancelled(cancel_check)
|
||||
@@ -2016,15 +2025,32 @@ def reconstruct(args, model_cache=None, cancel_check=None):
|
||||
0.25,
|
||||
f"Estimating depth for {len(images)} frame(s)",
|
||||
)
|
||||
inference_options = {
|
||||
"process_res": args.process_res,
|
||||
"process_res_method": args.process_res_method,
|
||||
"use_ray_pose": bool(args.use_ray_pose),
|
||||
"ref_view_strategy": args.ref_view_strategy,
|
||||
}
|
||||
try:
|
||||
prediction = infer(
|
||||
model,
|
||||
[str(path) for path in images],
|
||||
process_res=args.process_res,
|
||||
process_res_method=args.process_res_method,
|
||||
use_ray_pose=bool(args.use_ray_pose),
|
||||
ref_view_strategy=args.ref_view_strategy,
|
||||
)
|
||||
if getattr(args, "inference_mode", "joint") == "per_frame":
|
||||
predictions = []
|
||||
for index, image in enumerate(images):
|
||||
check_cancelled(cancel_check)
|
||||
write_status(
|
||||
args.status,
|
||||
0.25 + 0.60 * index / len(images),
|
||||
f"Estimating DA3 depth ({index + 1}/{len(images)})",
|
||||
)
|
||||
predictions.append(
|
||||
infer(model, [str(image)], **inference_options)
|
||||
)
|
||||
prediction = merge_da3_frame_predictions(predictions)
|
||||
else:
|
||||
prediction = infer(
|
||||
model,
|
||||
[str(path) for path in images],
|
||||
**inference_options,
|
||||
)
|
||||
except UnicodeDecodeError as error:
|
||||
# DirectML reports failures with a locale-specific message that
|
||||
# ONNX Runtime cannot always decode, masking the real error.
|
||||
|
||||
Reference in New Issue
Block a user