Files
operating-room-monitor-server/backend/app/algo_host/subprocess_runner.py

181 lines
4.9 KiB
Python
Raw Normal View History

2026-05-22 09:35:41 +08:00
"""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",
)