EgoForce / app.py
Christen Millerdurai
reduced gpu time
25bbe66
from __future__ import annotations
import importlib.util
import os
import re
import runpy
import shlex
import shutil
import site
import subprocess
import sys
from pathlib import Path
SPACE_ROOT = Path(__file__).resolve().parent
EGOFORCE_REPO_URL = os.environ.get("EGOFORCE_REPO_URL", "https://github.com/dfki-av/EgoForce")
EGOFORCE_REF = os.environ.get("EGOFORCE_REF", "main")
EGOFORCE_ROOT = Path(os.environ.get("EGOFORCE_ROOT", SPACE_ROOT / "EgoForce")).resolve()
EGOFORCE_ASSETS_REPO_ID = os.environ.get("EGOFORCE_ASSETS_REPO_ID", "chris10/EgoForce")
ZEROGPU_DURATION_SECONDS = os.environ.get("ZEROGPU_DURATION_SECONDS", "210")
ZEROGPU_SIZE = os.environ.get("ZEROGPU_SIZE", "large")
HERO_CSS_SPACE_PATCH_MARKER = "/* EgoForce Space patch: dual-theme hero styling */"
HERO_CSS_SPACE_PATCH = f"""
{HERO_CSS_SPACE_PATCH_MARKER}
:root,
.gradio-container {{
--egoforce-bg-start: #fcf8f1;
--egoforce-bg-end: #f3ead8;
--egoforce-bg-accent: rgba(201, 95, 73, 0.14);
--egoforce-card-bg: linear-gradient(168deg, rgba(255, 251, 245, 0.96), rgba(246, 238, 225, 0.94));
--egoforce-card-border: rgba(173, 128, 93, 0.28);
--egoforce-shadow: 0 20px 52px rgba(111, 76, 44, 0.14);
--egoforce-text-primary: #1f2937;
--egoforce-text-secondary: #374151;
--egoforce-text-muted: #4b5563;
--egoforce-link-bg: rgba(255, 255, 255, 0.82);
--egoforce-link-border: rgba(128, 91, 58, 0.35);
--egoforce-link-text: #1f2937;
--egoforce-link-hover-bg: rgba(201, 95, 73, 0.15);
--egoforce-link-hover-border: rgba(201, 95, 73, 0.48);
}}
@media (prefers-color-scheme: dark) {{
:root,
.gradio-container {{
--egoforce-bg-start: #0b1020;
--egoforce-bg-end: #121a2f;
--egoforce-bg-accent: rgba(56, 189, 248, 0.18);
--egoforce-card-bg: linear-gradient(165deg, rgba(17, 24, 39, 0.94), rgba(30, 41, 59, 0.9));
--egoforce-card-border: rgba(125, 211, 252, 0.26);
--egoforce-shadow: 0 24px 56px rgba(2, 8, 23, 0.52);
--egoforce-text-primary: #e5e7eb;
--egoforce-text-secondary: #d1d5db;
--egoforce-text-muted: #cbd5e1;
--egoforce-link-bg: rgba(15, 23, 42, 0.62);
--egoforce-link-border: rgba(125, 211, 252, 0.4);
--egoforce-link-text: #e2e8f0;
--egoforce-link-hover-bg: rgba(14, 116, 144, 0.35);
--egoforce-link-hover-border: rgba(125, 211, 252, 0.65);
}}
}}
html.dark,
body.dark,
.dark,
html[data-theme="dark"],
body[data-theme="dark"] {{
--egoforce-bg-start: #0b1020;
--egoforce-bg-end: #121a2f;
--egoforce-bg-accent: rgba(56, 189, 248, 0.18);
--egoforce-card-bg: linear-gradient(165deg, rgba(17, 24, 39, 0.94), rgba(30, 41, 59, 0.9));
--egoforce-card-border: rgba(125, 211, 252, 0.26);
--egoforce-shadow: 0 24px 56px rgba(2, 8, 23, 0.52);
--egoforce-text-primary: #e5e7eb;
--egoforce-text-secondary: #d1d5db;
--egoforce-text-muted: #cbd5e1;
--egoforce-link-bg: rgba(15, 23, 42, 0.62);
--egoforce-link-border: rgba(125, 211, 252, 0.4);
--egoforce-link-text: #e2e8f0;
--egoforce-link-hover-bg: rgba(14, 116, 144, 0.35);
--egoforce-link-hover-border: rgba(125, 211, 252, 0.65);
}}
body,
.gradio-container {{
background-image:
radial-gradient(circle at center top, var(--egoforce-bg-accent), rgba(0, 0, 0, 0) 36%),
linear-gradient(180deg, var(--egoforce-bg-start) 0%, var(--egoforce-bg-end) 100%) !important;
color: var(--egoforce-text-primary) !important;
}}
.egoforce-hero-card,
.prose .egoforce-hero .egoforce-hero-card {{
background: var(--block-background-fill) !important;
background-image: linear-gradient(
168deg,
color-mix(in srgb, var(--block-background-fill) 94%, #ffffff 6%),
color-mix(in srgb, var(--block-background-fill) 96%, #000000 4%)
) !important;
border: 1px solid var(--block-border-color) !important;
box-shadow: var(--egoforce-shadow) !important;
}}
.egoforce-hero-title,
.prose .egoforce-hero .egoforce-hero-title {{
font-size: clamp(2.6rem, 6vw, 4.8rem) !important;
line-height: 1 !important;
color: var(--egoforce-text-primary) !important;
}}
.egoforce-brand-black,
.prose .egoforce-hero .egoforce-brand-black {{
color: var(--body-text-color) !important;
font-variant-caps: small-caps;
letter-spacing: 0.015em;
}}
.egoforce-brand-force,
.prose .egoforce-hero .egoforce-brand-force {{
color: #b42018 !important;
font-variant-caps: small-caps;
letter-spacing: 0.015em;
}}
.egoforce-hero-subtitle,
.egoforce-hero-authors,
.egoforce-hero-venue,
.prose .egoforce-hero .egoforce-hero-subtitle,
.prose .egoforce-hero .egoforce-hero-authors,
.prose .egoforce-hero .egoforce-hero-venue {{
color: var(--body-text-color) !important;
}}
.egoforce-hero-affiliations,
.egoforce-hero-caption,
.prose .egoforce-hero .egoforce-hero-affiliations,
.prose .egoforce-hero .egoforce-hero-caption {{
color: var(--body-text-color-subdued) !important;
}}
.egoforce-hero-icon,
.prose .egoforce-hero img.egoforce-hero-icon {{
height: clamp(3.2rem, 7vw, 5.4rem) !important;
width: auto !important;
max-width: none !important;
}}
.egoforce-hero-link,
.prose .egoforce-hero .egoforce-hero-link {{
background: var(--egoforce-link-bg) !important;
border-color: var(--egoforce-link-border) !important;
color: var(--egoforce-link-text) !important;
}}
.egoforce-hero-link:hover,
.prose .egoforce-hero .egoforce-hero-link:hover {{
background: var(--egoforce-link-hover-bg) !important;
border-color: var(--egoforce-link-hover-border) !important;
}}
.egoforce-hero-link svg,
.prose .egoforce-hero .egoforce-hero-link svg {{
color: var(--egoforce-link-text) !important;
}}
@media (prefers-color-scheme: dark) {{
body,
.gradio-container {{
background-image:
radial-gradient(circle at center top, rgba(56, 189, 248, 0.18), rgba(0, 0, 0, 0) 36%),
linear-gradient(180deg, #0b1020 0%, #121a2f 100%) !important;
color: #e5e7eb !important;
}}
.egoforce-hero-card,
.prose .egoforce-hero .egoforce-hero-card {{
background: linear-gradient(165deg, rgba(17, 24, 39, 0.94), rgba(30, 41, 59, 0.9)) !important;
border-color: rgba(125, 211, 252, 0.26) !important;
box-shadow: 0 24px 56px rgba(2, 8, 23, 0.52) !important;
}}
.egoforce-hero-title,
.egoforce-hero-subtitle,
.egoforce-hero-authors,
.egoforce-hero-affiliations,
.egoforce-hero-venue,
.egoforce-hero-caption,
.prose .egoforce-hero .egoforce-hero-title,
.prose .egoforce-hero .egoforce-hero-subtitle,
.prose .egoforce-hero .egoforce-hero-authors,
.prose .egoforce-hero .egoforce-hero-affiliations,
.prose .egoforce-hero .egoforce-hero-venue,
.prose .egoforce-hero .egoforce-hero-caption {{
color: #e5e7eb !important;
}}
.egoforce-brand-black,
.prose .egoforce-hero .egoforce-brand-black {{
color: #e5e7eb !important;
}}
.egoforce-brand-force,
.prose .egoforce-hero .egoforce-brand-force {{
color: #b42018 !important;
}}
.egoforce-hero-link,
.prose .egoforce-hero .egoforce-hero-link {{
background: rgba(15, 23, 42, 0.62) !important;
border-color: rgba(125, 211, 252, 0.4) !important;
color: #e2e8f0 !important;
}}
.egoforce-hero-link:hover,
.prose .egoforce-hero .egoforce-hero-link:hover {{
background: rgba(14, 116, 144, 0.35) !important;
border-color: rgba(125, 211, 252, 0.65) !important;
}}
.egoforce-hero-link svg,
.prose .egoforce-hero .egoforce-hero-link svg {{
color: #e2e8f0 !important;
}}
}}
html.dark .egoforce-hero-card,
body.dark .egoforce-hero-card,
html[data-theme="dark"] .egoforce-hero-card,
body[data-theme="dark"] .egoforce-hero-card,
.dark .egoforce-hero-card,
html.dark .prose .egoforce-hero .egoforce-hero-card,
body.dark .prose .egoforce-hero .egoforce-hero-card,
html[data-theme="dark"] .prose .egoforce-hero .egoforce-hero-card,
body[data-theme="dark"] .prose .egoforce-hero .egoforce-hero-card,
.dark .prose .egoforce-hero .egoforce-hero-card {{
background: linear-gradient(165deg, rgba(17, 24, 39, 0.94), rgba(30, 41, 59, 0.9)) !important;
border-color: rgba(125, 211, 252, 0.26) !important;
box-shadow: 0 24px 56px rgba(2, 8, 23, 0.52) !important;
}}
html.dark .gradio-container,
body.dark .gradio-container,
html[data-theme="dark"] .gradio-container,
body[data-theme="dark"] .gradio-container,
.dark .gradio-container {{
background-image:
radial-gradient(circle at center top, rgba(56, 189, 248, 0.18), rgba(0, 0, 0, 0) 36%),
linear-gradient(180deg, #0b1020 0%, #121a2f 100%) !important;
color: #e5e7eb !important;
}}
html.dark .egoforce-hero-title,
html.dark .egoforce-hero-subtitle,
html.dark .egoforce-hero-authors,
html.dark .egoforce-hero-affiliations,
html.dark .egoforce-hero-venue,
html.dark .egoforce-hero-caption,
body.dark .egoforce-hero-title,
body.dark .egoforce-hero-subtitle,
body.dark .egoforce-hero-authors,
body.dark .egoforce-hero-affiliations,
body.dark .egoforce-hero-venue,
body.dark .egoforce-hero-caption,
html[data-theme="dark"] .egoforce-hero-title,
html[data-theme="dark"] .egoforce-hero-subtitle,
html[data-theme="dark"] .egoforce-hero-authors,
html[data-theme="dark"] .egoforce-hero-affiliations,
html[data-theme="dark"] .egoforce-hero-venue,
html[data-theme="dark"] .egoforce-hero-caption,
body[data-theme="dark"] .egoforce-hero-title,
body[data-theme="dark"] .egoforce-hero-subtitle,
body[data-theme="dark"] .egoforce-hero-authors,
body[data-theme="dark"] .egoforce-hero-affiliations,
body[data-theme="dark"] .egoforce-hero-venue,
body[data-theme="dark"] .egoforce-hero-caption,
.dark .egoforce-hero-title,
.dark .egoforce-hero-subtitle,
.dark .egoforce-hero-authors,
.dark .egoforce-hero-affiliations,
.dark .egoforce-hero-venue,
.dark .egoforce-hero-caption {{
color: #e5e7eb !important;
}}
html.dark .egoforce-brand-black,
body.dark .egoforce-brand-black,
html[data-theme="dark"] .egoforce-brand-black,
body[data-theme="dark"] .egoforce-brand-black,
.dark .egoforce-brand-black {{
color: #e5e7eb !important;
}}
html.dark .egoforce-brand-force,
body.dark .egoforce-brand-force,
html[data-theme="dark"] .egoforce-brand-force,
body[data-theme="dark"] .egoforce-brand-force,
.dark .egoforce-brand-force {{
color: #b42018 !important;
}}
html.dark .egoforce-hero-link,
body.dark .egoforce-hero-link,
html[data-theme="dark"] .egoforce-hero-link,
body[data-theme="dark"] .egoforce-hero-link,
.dark .egoforce-hero-link {{
background: rgba(15, 23, 42, 0.62) !important;
border-color: rgba(125, 211, 252, 0.4) !important;
color: #e2e8f0 !important;
}}
""".strip()
def run_command(command: list[str], cwd: Path | None = None) -> None:
print(f"+ {shlex.join(command)}", flush=True)
subprocess.run(command, cwd=str(cwd) if cwd else None, check=True)
def configure_runtime_environment() -> None:
os.environ.setdefault("EGOFORCE_ROOT", str(EGOFORCE_ROOT))
configure_cuda_environment()
os.environ.setdefault("FORCE_CUDA", "1")
configure_torch_cuda_arch_list()
os.environ.setdefault("MAX_JOBS", "1")
os.environ.setdefault("CMAKE_BUILD_PARALLEL_LEVEL", "1")
os.environ.setdefault("NINJAFLAGS", "-j1")
os.environ.setdefault("MAKEFLAGS", "-j1")
os.environ.setdefault("PYOPENGL_PLATFORM", "egl")
os.environ.setdefault("MPLBACKEND", "Agg")
os.environ.setdefault("TOKENIZERS_PARALLELISM", "false")
os.environ.setdefault("TORCH_FORCE_NO_WEIGHTS_ONLY_LOAD", "1")
def configure_torch_cuda_arch_list() -> None:
if os.environ.get("TORCH_CUDA_ARCH_LIST"):
return
accelerator = os.environ.get("ACCELERATOR", "").lower()
if "zero" in accelerator or "h200" in accelerator:
arch_list = "9.0"
elif "t4" in accelerator:
arch_list = "7.5"
elif "a100" in accelerator:
arch_list = "8.0"
elif "a10g" in accelerator:
arch_list = "8.6"
elif "l4" in accelerator or "l40" in accelerator:
arch_list = "8.9"
else:
arch_list = "9.0"
os.environ["TORCH_CUDA_ARCH_LIST"] = arch_list
def candidate_site_packages() -> list[Path]:
paths = [Path(path) for path in site.getsitepackages()]
user_site = site.getusersitepackages()
if user_site:
paths.append(Path(user_site))
return paths
def find_python_cuda_home() -> Path | None:
for site_packages in candidate_site_packages():
cuda_home = site_packages / "nvidia" / "cuda_nvcc"
if (cuda_home / "bin" / "nvcc").exists():
return cuda_home
return None
def configure_cuda_environment() -> None:
current_cuda_home = os.environ.get("CUDA_HOME")
if current_cuda_home and (Path(current_cuda_home) / "bin" / "nvcc").exists():
cuda_home = Path(current_cuda_home)
else:
cuda_home = find_python_cuda_home() or Path(current_cuda_home or "/usr/local/cuda")
os.environ["CUDA_HOME"] = str(cuda_home)
cuda_bin = cuda_home / "bin"
if cuda_bin.exists():
os.environ["PATH"] = f"{cuda_bin}:{os.environ.get('PATH', '')}"
def ensure_egoforce_repo() -> Path:
demo_entrypoint = EGOFORCE_ROOT / "demo" / "run_app.py"
if demo_entrypoint.exists():
patch_upstream_gradio_for_zerogpu(demo_entrypoint)
patch_upstream_tensorrt_fallback(EGOFORCE_ROOT)
patch_upstream_gradio_hero_css(EGOFORCE_ROOT)
return EGOFORCE_ROOT
if EGOFORCE_ROOT.exists() and any(EGOFORCE_ROOT.iterdir()):
raise RuntimeError(
f"{EGOFORCE_ROOT} exists, but demo/run_app.py was not found. "
"Delete that directory or set EGOFORCE_ROOT to a clean location."
)
EGOFORCE_ROOT.parent.mkdir(parents=True, exist_ok=True)
command = ["git", "clone", "--depth", "1"]
if EGOFORCE_REF:
command.extend(["--branch", EGOFORCE_REF])
command.extend([EGOFORCE_REPO_URL, str(EGOFORCE_ROOT)])
run_command(command)
if (EGOFORCE_ROOT / ".gitmodules").exists():
run_command(["git", "submodule", "update", "--init", "--recursive"], cwd=EGOFORCE_ROOT)
if not demo_entrypoint.exists():
raise RuntimeError(f"EgoForce demo entrypoint not found at {demo_entrypoint}")
patch_upstream_gradio_for_zerogpu(demo_entrypoint)
patch_upstream_tensorrt_fallback(EGOFORCE_ROOT)
patch_upstream_gradio_hero_css(EGOFORCE_ROOT)
return EGOFORCE_ROOT
def patch_upstream_gradio_for_zerogpu(demo_entrypoint: Path) -> None:
source = demo_entrypoint.read_text(encoding="utf-8")
if "from egoforce_runtime_patches import apply_runtime_patches\n" not in source:
if "import torch\n" not in source:
raise RuntimeError(f"Could not insert runtime patches in {demo_entrypoint}")
source = source.replace(
"import torch\n",
"import torch\nfrom egoforce_runtime_patches import apply_runtime_patches\napply_runtime_patches()\n",
1,
)
if "import spaces\n" not in source:
if "import torch\n" not in source:
raise RuntimeError(f"Could not insert ZeroGPU import in {demo_entrypoint}")
source = source.replace("import torch\n", "import spaces\nimport torch\n", 1)
if "def process_video(\n" not in source:
raise RuntimeError(f"Could not locate process_video in {demo_entrypoint}")
source = re.sub(
r"@spaces\.GPU\([^\n]*\)\n(?=def process_video\()",
"",
source,
count=1,
)
desired_decorator = (
"@spaces.GPU("
f"duration={int(ZEROGPU_DURATION_SECONDS)}, "
f"size={ZEROGPU_SIZE!r}"
")\n"
)
if desired_decorator not in source:
source = source.replace("def process_video(\n", f"{desired_decorator}def process_video(\n", 1)
injected_css_loader = (
"\n"
"@lru_cache(maxsize=1)\n"
"def load_gradio_hero_css():\n"
" if not GRADIO_HERO_CSS_PATH.exists():\n"
" return None\n"
" return GRADIO_HERO_CSS_PATH.read_text(encoding=\"utf-8\")\n"
)
if injected_css_loader in source:
source = source.replace(injected_css_loader, "", 1)
source = source.replace(" css=load_gradio_hero_css(),\n", "")
demo_entrypoint.write_text(source, encoding="utf-8")
def patch_upstream_gradio_hero_css(repo_root: Path) -> None:
css_path = repo_root / "assets" / "css" / "gradio_hero.css"
if not css_path.exists():
return
css_source = css_path.read_text(encoding="utf-8")
if HERO_CSS_SPACE_PATCH_MARKER in css_source:
base_css = css_source.split(HERO_CSS_SPACE_PATCH_MARKER, 1)[0].rstrip()
css_path.write_text(f"{base_css}\n\n{HERO_CSS_SPACE_PATCH}\n", encoding="utf-8")
return
css_path.write_text(f"{css_source.rstrip()}\n\n{HERO_CSS_SPACE_PATCH}\n", encoding="utf-8")
def patch_upstream_tensorrt_fallback(repo_root: Path) -> None:
inference_path = repo_root / "demo" / "inference.py"
demo_utils_path = repo_root / "demo" / "demo_utils.py"
inference_source = inference_path.read_text(encoding="utf-8")
if "TORCH_TENSORRT_IMPORT_ERROR = None\n" not in inference_source:
import_marker = "import torch\nimport torch_tensorrt\n\n"
if import_marker not in inference_source:
raise RuntimeError(f"Could not locate torch_tensorrt import in {inference_path}")
inference_source = inference_source.replace(
import_marker,
(
"import torch\n"
"\n"
"try:\n"
" import torch_tensorrt\n"
" TORCH_TENSORRT_IMPORT_ERROR = None\n"
"except Exception as exc:\n"
" torch_tensorrt = None\n"
" TORCH_TENSORRT_IMPORT_ERROR = exc\n"
" print(f\"Torch-TensorRT unavailable: {exc}. Falling back to PyTorch inference.\", flush=True)\n"
"\n"
),
1,
)
runtime_marker = (
"torch_tensorrt.runtime.set_multi_device_safe_mode(True)\n"
"torch_tensorrt.runtime.set_cudagraphs_mode(True)\n"
)
if runtime_marker in inference_source:
inference_source = inference_source.replace(
runtime_marker,
(
"if torch_tensorrt is not None:\n"
" torch_tensorrt.runtime.set_multi_device_safe_mode(True)\n"
" torch_tensorrt.runtime.set_cudagraphs_mode(True)\n"
),
1,
)
inference_path.write_text(inference_source, encoding="utf-8")
demo_utils_source = demo_utils_path.read_text(encoding="utf-8")
if "Torch-TensorRT backend unavailable" not in demo_utils_source:
old_compile_function = """def compile_to_tensorrt(model, device):
x1, x2, x3, x4 = torch.rand([2, 1, 3, 224, 224]), torch.rand([2, 1, 3, 6, 2]), torch.rand([2, 1, 3, 224, 224]), torch.rand([2, 1, 3, 6, 2])
x1, x2, x3, x4 = x1.to(device), x2.to(device), x3.to(device), x4.to(device)
with torch.inference_mode():
model = model.to(device).half()
x1, x2, x3, x4 = x1.half(), x2.half(), x3.half(), x4.half()
model = torch.jit.trace(model, (x1, x2, x3, x4), strict=False)
backend_kwargs = {
"enabled_precisions": {torch.half},
"min_block_size": 2,
"torch_executed_ops": {"torch.ops.aten.sub.Tensor"},
"optimization_level": 5,
"use_python_runtime": False,
}
model = torch.compile(model, backend="torch_tensorrt", options=backend_kwargs, dynamic=False,)
with torch.no_grad():
model(x1, x2, x3, x4) # compiled on first run
return model
"""
new_compile_function = """def compile_to_tensorrt(model, device):
try:
import torch_tensorrt # noqa: F401
except Exception as exc:
print(f"Torch-TensorRT backend unavailable: {exc}. Using PyTorch model.", flush=True)
return model.to(device).half()
x1, x2, x3, x4 = torch.rand([2, 1, 3, 224, 224]), torch.rand([2, 1, 3, 6, 2]), torch.rand([2, 1, 3, 224, 224]), torch.rand([2, 1, 3, 6, 2])
x1, x2, x3, x4 = x1.to(device), x2.to(device), x3.to(device), x4.to(device)
with torch.inference_mode():
fallback_model = model.to(device).half()
x1, x2, x3, x4 = x1.half(), x2.half(), x3.half(), x4.half()
traced_model = torch.jit.trace(fallback_model, (x1, x2, x3, x4), strict=False)
backend_kwargs = {
"enabled_precisions": {torch.half},
"min_block_size": 2,
"torch_executed_ops": {"torch.ops.aten.sub.Tensor"},
"optimization_level": 5,
"use_python_runtime": False,
}
try:
compiled_model = torch.compile(traced_model, backend="torch_tensorrt", options=backend_kwargs, dynamic=False,)
with torch.no_grad():
compiled_model(x1, x2, x3, x4) # compiled on first run
return compiled_model
except Exception as exc:
print(f"Torch-TensorRT compile failed: {exc}. Using PyTorch model.", flush=True)
return fallback_model
"""
if old_compile_function not in demo_utils_source:
raise RuntimeError(f"Could not locate compile_to_tensorrt in {demo_utils_path}")
demo_utils_source = demo_utils_source.replace(old_compile_function, new_compile_function, 1)
demo_utils_path.write_text(demo_utils_source, encoding="utf-8")
def package_available(module_name: str) -> bool:
return importlib.util.find_spec(module_name) is not None
def pip_install(requirement: str, *extra_args: str) -> None:
command = [
sys.executable,
"-m",
"pip",
"install",
"--no-cache-dir",
"--disable-pip-version-check",
requirement,
*extra_args,
]
run_command(command)
def ensure_runtime_python_packages(repo_root: Path) -> None:
datapipes_path = repo_root / "thirdparty" / "datapipes"
install_plan = [
("anycalib", "git+https://github.com/javrtg/AnyCalib.git", ("--no-build-isolation",)),
("chumpy", "git+https://github.com/mattloper/chumpy.git", ("--no-build-isolation",)),
("datapipes", str(datapipes_path), ()),
("mmdet", str(repo_root / "thirdparty" / "mmdetection"), ("--no-build-isolation", "--no-deps")),
]
for module_name, requirement, extra_args in install_plan:
if package_available(module_name):
continue
pip_install(requirement, *extra_args)
def validate_assets(repo_root: Path) -> None:
data_dir = repo_root / "_DATA"
required_paths = [
data_dir / "model_weights.pth",
data_dir / "epoch_460.pth",
data_dir / "detector.torchscript",
data_dir / "mano",
]
missing = [str(path) for path in required_paths if not path.exists()]
if missing:
raise RuntimeError(f"Missing required EgoForce assets: {missing}")
def ensure_egoforce_assets(repo_root: Path) -> None:
from huggingface_hub import snapshot_download
try:
validate_assets(repo_root)
print(f"Using existing EgoForce assets at {repo_root / '_DATA'}", flush=True)
return
except RuntimeError:
pass
target_data_dir = repo_root / "_DATA"
cache_root = repo_root / ".hf-download"
if cache_root.exists():
shutil.rmtree(cache_root)
print(f"Downloading EgoForce assets from {EGOFORCE_ASSETS_REPO_ID}", flush=True)
snapshot_path = Path(
snapshot_download(
repo_id=EGOFORCE_ASSETS_REPO_ID,
repo_type="model",
allow_patterns=["_DATA/**"],
local_dir=cache_root,
)
)
source_data_dir = snapshot_path / "_DATA"
if not source_data_dir.exists():
raise RuntimeError(f"Downloaded snapshot did not contain {source_data_dir}")
shutil.copytree(source_data_dir, target_data_dir, dirs_exist_ok=True)
shutil.rmtree(cache_root, ignore_errors=True)
validate_assets(repo_root)
print(f"Downloaded EgoForce assets to {target_data_dir}", flush=True)
def launch_upstream_gradio(repo_root: Path) -> None:
demo_entrypoint = repo_root / "demo" / "run_app.py"
demo_dir = demo_entrypoint.parent
server_name = os.environ.get("GRADIO_SERVER_NAME", "0.0.0.0")
server_port = os.environ.get("PORT") or os.environ.get("GRADIO_SERVER_PORT") or "7860"
os.chdir(repo_root)
for import_path in (repo_root, demo_dir):
import_path_string = str(import_path)
if import_path_string in sys.path:
sys.path.remove(import_path_string)
sys.path.insert(0, import_path_string)
sys.argv = [
str(demo_entrypoint),
"--server-name",
server_name,
"--server-port",
str(server_port),
]
if os.environ.get("GRADIO_SHARE", "").lower() in {"1", "true", "yes"}:
sys.argv.append("--share")
runpy.run_path(str(demo_entrypoint), run_name="__main__")
def main() -> None:
configure_runtime_environment()
repo_root = ensure_egoforce_repo()
ensure_runtime_python_packages(repo_root)
ensure_egoforce_assets(repo_root)
launch_upstream_gradio(repo_root)
if __name__ == "__main__":
main()