2026-04-16 11:38:30 +08:00
|
|
|
|
"""CLI:对单条 fish 跑 FishMeasure 子进程(不写 SQLite、不发布 /media)。
|
2026-04-14 22:06:33 +08:00
|
|
|
|
|
2026-04-16 11:38:30 +08:00
|
|
|
|
使用 ``run_measure_batch_subprocess``(合并点云)。线上 ``measure_watch`` 为逐段 ``run_full_measure``,齐套后再 aggregate final。
|
2026-04-14 22:06:33 +08:00
|
|
|
|
"""
|
|
|
|
|
|
|
|
|
|
|
|
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()
|