181 lines
4.9 KiB
Python
181 lines
4.9 KiB
Python
"""Spawn reference bundle child processes (main.py, visualize_result_video.py)."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import os
|
|
import signal
|
|
import subprocess
|
|
import sys
|
|
from pathlib import Path
|
|
|
|
from loguru import logger
|
|
|
|
from app.algo_host.bundle import load_reference_default_config, resolve_bundle_relative_path
|
|
from app.algo_host.transcode import VISUALIZATION_MAX_WIDTH
|
|
|
|
|
|
def build_reference_env() -> dict[str, str]:
|
|
env = os.environ.copy()
|
|
env["PYTHONFAULTHANDLER"] = "1"
|
|
env["PYTHONUNBUFFERED"] = "1"
|
|
return env
|
|
|
|
|
|
def build_batch_main_command(*, bundle_dir: Path, config_path: Path) -> list[str]:
|
|
return [
|
|
"uv",
|
|
"run",
|
|
"python",
|
|
"-X",
|
|
"faulthandler",
|
|
str(bundle_dir / "main.py"),
|
|
"--config",
|
|
str(config_path),
|
|
]
|
|
|
|
|
|
def build_visualization_command(
|
|
*,
|
|
bundle_dir: Path,
|
|
video_path: Path,
|
|
result_path: Path,
|
|
output_video_path: Path,
|
|
) -> list[str]:
|
|
cfg = load_reference_default_config(bundle_dir)
|
|
weights = cfg.get("weights") if isinstance(cfg.get("weights"), dict) else {}
|
|
device_cfg = cfg.get("device") if isinstance(cfg.get("device"), dict) else {}
|
|
hand_raw = str((weights or {}).get("hand") or "weights/hand_detect.pt").strip()
|
|
hand_model = resolve_bundle_relative_path(bundle_dir, hand_raw)
|
|
return [
|
|
sys.executable,
|
|
"-X",
|
|
"faulthandler",
|
|
str((bundle_dir / "visualize_result_video.py").resolve()),
|
|
"--video",
|
|
str(video_path.resolve()),
|
|
"--result-txt",
|
|
str(result_path.resolve()),
|
|
"--hand-model",
|
|
str(hand_model),
|
|
"--out-video",
|
|
str(output_video_path.resolve()),
|
|
"--device",
|
|
str(device_cfg.get("type") or "cuda"),
|
|
"--max-width",
|
|
str(VISUALIZATION_MAX_WIDTH),
|
|
]
|
|
|
|
|
|
def _signal_name(signum: int) -> str:
|
|
try:
|
|
return signal.Signals(signum).name
|
|
except ValueError:
|
|
return f"signal {signum}"
|
|
|
|
|
|
def describe_batch_returncode(returncode: int) -> str:
|
|
if returncode < 0:
|
|
signum = -returncode
|
|
return f"terminated by {_signal_name(signum)} ({signum})"
|
|
if returncode > 128:
|
|
wrapped = returncode - 256
|
|
if wrapped < 0:
|
|
signum = -wrapped
|
|
return f"exit={returncode} (possibly propagated {wrapped}/{_signal_name(signum)})"
|
|
return f"exit={returncode}"
|
|
|
|
|
|
def format_batch_failure(
|
|
returncode: int,
|
|
*,
|
|
stdout: str,
|
|
stderr: str,
|
|
work_dir: Path,
|
|
output_path: Path,
|
|
) -> str:
|
|
chunks: list[str] = [
|
|
describe_batch_returncode(returncode),
|
|
f"work_dir={work_dir}",
|
|
f"output={output_path}",
|
|
]
|
|
stdout = stdout.strip()
|
|
stderr = stderr.strip()
|
|
if stdout:
|
|
chunks.append(f"stdout:\n{stdout[-3000:]}")
|
|
if stderr:
|
|
chunks.append(f"stderr:\n{stderr[-3000:]}")
|
|
return "\n".join(chunks)
|
|
|
|
|
|
def _log_subprocess_output(prefix: str, stdout: str, stderr: str, *, max_lines: int = 40) -> None:
|
|
for label, text in (("stdout", stdout), ("stderr", stderr)):
|
|
lines = [ln for ln in (text or "").splitlines() if ln.strip()]
|
|
if not lines:
|
|
continue
|
|
tail = lines[-max_lines:] if len(lines) > max_lines else lines
|
|
for line in tail:
|
|
logger.info("{} {}", prefix, line)
|
|
|
|
|
|
def run_subprocess(
|
|
cmd: list[str],
|
|
*,
|
|
cwd: Path,
|
|
work_dir: Path,
|
|
output_path: Path,
|
|
log_label: str,
|
|
) -> None:
|
|
proc = subprocess.run(
|
|
cmd,
|
|
cwd=str(cwd),
|
|
check=False,
|
|
text=True,
|
|
capture_output=True,
|
|
env=build_reference_env(),
|
|
)
|
|
if proc.returncode != 0:
|
|
msg = format_batch_failure(
|
|
proc.returncode,
|
|
stdout=proc.stdout or "",
|
|
stderr=proc.stderr or "",
|
|
work_dir=work_dir,
|
|
output_path=output_path,
|
|
)
|
|
raise RuntimeError(f"{log_label} failed {msg}")
|
|
_log_subprocess_output(log_label, proc.stdout or "", proc.stderr or "")
|
|
|
|
|
|
def run_batch_main(*, bundle_dir: Path, config_path: Path, work_dir: Path, output_path: Path) -> None:
|
|
cmd = build_batch_main_command(bundle_dir=bundle_dir, config_path=config_path)
|
|
logger.info("reference batch starting: {}", " ".join(cmd))
|
|
run_subprocess(
|
|
cmd,
|
|
cwd=bundle_dir,
|
|
work_dir=work_dir,
|
|
output_path=output_path,
|
|
log_label="reference batch",
|
|
)
|
|
|
|
|
|
def run_visualization_script(
|
|
*,
|
|
bundle_dir: Path,
|
|
video_path: Path,
|
|
result_path: Path,
|
|
raw_output_video_path: Path,
|
|
) -> None:
|
|
cmd = build_visualization_command(
|
|
bundle_dir=bundle_dir,
|
|
video_path=video_path,
|
|
result_path=result_path,
|
|
output_video_path=raw_output_video_path,
|
|
)
|
|
logger.info("reference visualization starting: {}", " ".join(cmd))
|
|
run_subprocess(
|
|
cmd,
|
|
cwd=bundle_dir,
|
|
work_dir=raw_output_video_path.parent,
|
|
output_path=raw_output_video_path,
|
|
log_label="visualize",
|
|
)
|