live sonar feed, and incremental action feed
This commit is contained in:
77
fish_api/app/services/sonar_optical_flow.py
Normal file
77
fish_api/app/services/sonar_optical_flow.py
Normal file
@@ -0,0 +1,77 @@
|
||||
"""声呐视频发布前:在 FishMeasure ``optical_flow/visualize_optical_flow.py`` 中做稠密光流 overlay。"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import importlib.util
|
||||
import threading
|
||||
import time
|
||||
from pathlib import Path
|
||||
from types import ModuleType
|
||||
from typing import Optional
|
||||
|
||||
from loguru import logger
|
||||
|
||||
from app.settings import Settings
|
||||
|
||||
_mod_lock = threading.Lock()
|
||||
_cached_mod: Optional[ModuleType] = None
|
||||
_cached_mod_path: Optional[str] = None
|
||||
|
||||
|
||||
def _load_optical_flow_module(settings: Settings) -> Optional[ModuleType]:
|
||||
global _cached_mod, _cached_mod_path
|
||||
path = settings.fish_measure_root / "optical_flow" / "visualize_optical_flow.py"
|
||||
path_str = str(path)
|
||||
with _mod_lock:
|
||||
if _cached_mod is not None and _cached_mod_path == path_str:
|
||||
return _cached_mod
|
||||
if not path.is_file():
|
||||
logger.warning("[sonar-video] optical flow module not found: {}", path)
|
||||
return None
|
||||
spec = importlib.util.spec_from_file_location("fishmeasure_optical_flow_viz", path)
|
||||
if spec is None or spec.loader is None:
|
||||
return None
|
||||
mod = importlib.util.module_from_spec(spec)
|
||||
spec.loader.exec_module(mod)
|
||||
_cached_mod = mod
|
||||
_cached_mod_path = path_str
|
||||
return mod
|
||||
|
||||
|
||||
def run_sonar_optical_flow_overlay(
|
||||
src: Path,
|
||||
flow_out: Path,
|
||||
settings: Settings,
|
||||
) -> bool:
|
||||
"""将 ``src`` 处理为 overlay 光流视频写入 ``flow_out``。失败返回 False(调用方应回退直转码)。"""
|
||||
if not settings.biomass_sonar_optical_flow:
|
||||
return False
|
||||
mod = _load_optical_flow_module(settings)
|
||||
if mod is None:
|
||||
return False
|
||||
run_fn = getattr(mod, "run_optical_flow_video", None)
|
||||
if not callable(run_fn):
|
||||
logger.warning("[sonar-video] run_optical_flow_video missing in optical flow module")
|
||||
return False
|
||||
src_size_mb = src.stat().st_size / (1024 * 1024) if src.is_file() else 0
|
||||
logger.info("[sonar-video] optical flow starting: {} ({:.1f} MB), resize={}", src.name, src_size_mb, settings.biomass_sonar_optical_flow_resize)
|
||||
t0 = time.monotonic()
|
||||
try:
|
||||
ok = run_fn(
|
||||
src,
|
||||
flow_out,
|
||||
mode="overlay",
|
||||
resize=float(settings.biomass_sonar_optical_flow_resize),
|
||||
progress_log=False,
|
||||
)
|
||||
elapsed = time.monotonic() - t0
|
||||
if ok:
|
||||
out_size_mb = flow_out.stat().st_size / (1024 * 1024) if flow_out.is_file() else 0
|
||||
logger.info("[sonar-video] optical flow done in {:.1f}s: {} -> {} ({:.1f} MB)", elapsed, src.name, flow_out.name, out_size_mb)
|
||||
else:
|
||||
logger.warning("[sonar-video] optical flow returned False after {:.1f}s for {}", elapsed, src.name)
|
||||
return bool(ok)
|
||||
except Exception:
|
||||
elapsed = time.monotonic() - t0
|
||||
logger.exception("[sonar-video] optical flow raised exception after {:.1f}s for {}", elapsed, src.name)
|
||||
return False
|
||||
Reference in New Issue
Block a user