use the same rule in fishmeasure
This commit is contained in:
@@ -97,6 +97,27 @@ FishMeasure 跑完后在输出目录查找 `*preview*.mp4`,复制到 `MEDIA_RO
|
||||
|
||||
DGCNN 明细中同时输出 `mean_all_pred_g_after_filters`、`avg_topk_mean_pred_g` 等供对比参考。
|
||||
|
||||
## Debug:单条鱼测量(与 fish_api 同逻辑)
|
||||
|
||||
不启动 uvicorn、**不写 SQLite**、**不发布** `MEDIA_ROOT`(与 `run_full_measure_batch` 相比仅少快照与媒体发布;FishMeasure 子进程与 `measure_output/fish{N}` 与线上一致)。
|
||||
|
||||
调用 [`app/services/measure.py`](app/services/measure.py) 中的 `run_measure_batch_subprocess`,配置与 `fish_api/.env` 相同(`get_settings()`)。
|
||||
|
||||
```bash
|
||||
cd fish_api
|
||||
uv sync
|
||||
# 默认:MEASURE_WATCH_DIR/fish{N}/ 下所有 .svo2 → 输出到 MEASURE_OUTPUT_ROOT/fish{N}(默认 fish_api/.data/measure_output/fish{N})
|
||||
uv run python -m app.measure_debug_cli --fish-id 1
|
||||
|
||||
# 或等价入口
|
||||
uv run fish-measure-debug --fish-id 1
|
||||
|
||||
# 指定 SVO 目录或输出目录
|
||||
uv run python -m app.measure_debug_cli --batch-folder /path/to/fish1 --fish-id 1 --output-root /path/to/out
|
||||
```
|
||||
|
||||
结束后会在终端打印 `weight_prediction.json` 中的 `pred_weight_g`、`pred_weight_rule` 等摘要。
|
||||
|
||||
## 演进建议
|
||||
|
||||
- RTSP:用 `ffmpeg` 切段写入 MP4 后调用现有 `finalize` 逻辑
|
||||
|
||||
127
fish_api/app/measure_debug_cli.py
Normal file
127
fish_api/app/measure_debug_cli.py
Normal file
@@ -0,0 +1,127 @@
|
||||
"""CLI:对单条 fish 跑与 fish_api / measure-watch 相同的 FishMeasure 子进程(不写 SQLite、不发布 /media)。
|
||||
|
||||
使用 ``run_measure_batch_subprocess``,与 ``run_full_measure_batch`` 中的推理步骤一致。
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import json
|
||||
import sys
|
||||
from pathlib import Path
|
||||
from typing import Any, Dict, List
|
||||
|
||||
from app.services.measure import run_measure_batch_subprocess
|
||||
from app.settings import get_settings
|
||||
|
||||
|
||||
def _sorted_svo2_in_folder(folder: Path) -> List[Path]:
|
||||
"""与 measure_watch.iter_svo2_folders 中单目录内 .svo2 列表一致(按文件名排序)。"""
|
||||
return sorted(
|
||||
[p for p in folder.iterdir() if p.is_file() and p.suffix.lower() == ".svo2"],
|
||||
key=lambda p: p.name,
|
||||
)
|
||||
|
||||
|
||||
def _print_weight_summary(output_root: Path) -> None:
|
||||
combined = output_root / "weight_prediction.json"
|
||||
if not combined.is_file():
|
||||
print(f"[measure-debug] no combined file: {combined}", file=sys.stderr)
|
||||
return
|
||||
try:
|
||||
with open(combined, encoding="utf-8") as f:
|
||||
data: Dict[str, Any] = json.load(f)
|
||||
except (OSError, json.JSONDecodeError) as e:
|
||||
print(f"[measure-debug] failed to read {combined}: {e}", file=sys.stderr)
|
||||
return
|
||||
|
||||
summary = data.get("dgcnn_summary") or data.get("weight_summary") or {}
|
||||
pred_g = data.get("pred_weight_g")
|
||||
rule = data.get("pred_weight_rule")
|
||||
print("[measure-debug] --- weight_prediction.json (summary) ---")
|
||||
if pred_g is not None:
|
||||
print(f" pred_weight_g: {pred_g}")
|
||||
if rule is not None:
|
||||
print(f" pred_weight_rule: {rule}")
|
||||
if summary:
|
||||
top_k = summary.get("top_k")
|
||||
tbl = summary.get("top_by_length")
|
||||
print(f" dgcnn_summary.top_k: {top_k}")
|
||||
print(f" dgcnn_summary.top_by_length: {tbl}")
|
||||
mean_all = data.get("mean_all_pred_g_after_filters")
|
||||
if mean_all is not None:
|
||||
print(f" mean_all_pred_g_after_filters: {mean_all}")
|
||||
|
||||
|
||||
def main() -> None:
|
||||
parser = argparse.ArgumentParser(
|
||||
description="Run FishMeasure for one fish folder (same subprocess as fish_api)."
|
||||
)
|
||||
parser.add_argument(
|
||||
"--fish-id",
|
||||
default="1",
|
||||
help='Fish id used for output dir measure_output/fish{N} (default: 1).',
|
||||
)
|
||||
parser.add_argument(
|
||||
"--batch-folder",
|
||||
type=Path,
|
||||
default=None,
|
||||
help="Folder containing .svo2 files (overrides MEASURE_WATCH_DIR/fish{N}).",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--watch-dir",
|
||||
type=Path,
|
||||
default=None,
|
||||
help="Override MEASURE_WATCH_DIR when resolving fish{N}/ (ignored if --batch-folder is set).",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--output-root",
|
||||
type=Path,
|
||||
default=None,
|
||||
help="Override save-output directory (default: MEASURE_OUTPUT_ROOT/fish{N}).",
|
||||
)
|
||||
args = parser.parse_args()
|
||||
|
||||
settings = get_settings()
|
||||
fish_id = str(args.fish_id).strip() or "1"
|
||||
|
||||
if args.batch_folder is not None:
|
||||
batch_folder = args.batch_folder.expanduser().resolve()
|
||||
else:
|
||||
watch = args.watch_dir if args.watch_dir is not None else settings.measure_watch_dir
|
||||
if watch is None:
|
||||
print(
|
||||
"未配置输入目录:请设置 --batch-folder,或在 .env 中设置 MEASURE_WATCH_DIR",
|
||||
file=sys.stderr,
|
||||
)
|
||||
raise SystemExit(2)
|
||||
watch = watch.expanduser().resolve()
|
||||
batch_folder = watch / f"fish{fish_id}"
|
||||
|
||||
if not batch_folder.is_dir():
|
||||
print(f"不是目录或不存在: {batch_folder}", file=sys.stderr)
|
||||
raise SystemExit(2)
|
||||
|
||||
svo_paths = _sorted_svo2_in_folder(batch_folder)
|
||||
if not svo_paths:
|
||||
print(f"目录下无 .svo2 文件: {batch_folder}", file=sys.stderr)
|
||||
raise SystemExit(2)
|
||||
|
||||
if args.output_root is not None:
|
||||
out_root = args.output_root.expanduser().resolve()
|
||||
else:
|
||||
out_root = settings.measure_output_root / f"fish{fish_id}"
|
||||
out_root.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
print(f"[measure-debug] batch_folder={batch_folder}")
|
||||
print(f"[measure-debug] output_root={out_root}")
|
||||
print(f"[measure-debug] {len(svo_paths)} SVO(s): {', '.join(p.name for p in svo_paths)}")
|
||||
|
||||
run_measure_batch_subprocess(svo_paths, settings, output_root=out_root)
|
||||
|
||||
_print_weight_summary(out_root)
|
||||
print("[measure-debug] done.")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -16,3 +16,4 @@ dev = ["httpx>=0.28.1"]
|
||||
|
||||
[project.scripts]
|
||||
fish-action-watch = "app.action_watch_cli:main"
|
||||
fish-measure-debug = "app.measure_debug_cli:main"
|
||||
|
||||
Reference in New Issue
Block a user