# Recalculate Everything: CLEAR_SQLITE_DATABASE=1 CLEAR_MEASURE_OUTPUT=1 CLEAR_ACTION_OUTPUT=1 CLEAR_MEDIA=1 CLEAR_STREAM_TMP=1 LD_PRELOAD=/lib/aarch64-linux-gnu/libGLdispatch.so.0 bash scripts/start_fresh.sh ## 配置(环境变量) | 变量 | 说明 | 默认 | |------|------|------| | `PUBLIC_BASE_URL` | 返回 JSON 中 `video_left` / `video_right` 的前缀(勿带末尾 `/`) | `http://127.0.0.1:8000` | | `INGEST_API_KEY` | 非空时,`/api/v1/ingest/*` 需请求头 `X-API-Key` | 空(不校验) | | `STREAM_TMP_DIR` | 分块上传临时目录 | `/fish_api/.data/ingest` | | `MEDIA_ROOT` | 对外托管每次测量生成的 `*_left.mp4` / `*_right.mp4` | `/fish_api/.data/media` | | `FISH_MEASURE_ROOT` | `FishMeasure` 根目录 | 自动相对仓库 | | `FISH_ACTION_ROOT` | `FishAction` 根目录 | 自动相对仓库 | | `MEASURE_OUTPUT_ROOT` | 传给 `--save-output` 的目录 | `/fish_api/.data/measure_output` | | `YOLO_MODEL` / `WEIGHT_CHECKPOINT` / `ACTION_CHECKPOINT` | 模型路径 | 与仓库内脚本默认一致 | | `SAM_DEVICE` | `cuda` 或 `cpu` | `cuda` | | `MEASURE_FINAL_AGGREGATE_MODE` | 齐套后对各段 former 体重/体长聚合:`median` / `mean` / `trimmed_mean` | `median` | 可在 `fish_api/.env` 中填写上述变量(`pydantic-settings` 会读取)。 ## 安装与启动 ```bash cd fish_api python3 -m pip install -e . # 安装 pyproject 依赖(无 venv 时可用 --user 或系统 site-packages) ./scripts/start_fresh.sh # 默认仅重置 client_id 投递进度,保留 SQLite 历史与快照 # CLEAR_SQLITE_DATABASE=1 bash start_fresh.sh # 需要时才彻底清 SQLite # python3 -m uvicorn app.main:app --host 0.0.0.0 --port 8000 # 需自行 prestart ``` ## SVO 输入与 ``measure_watch``(两种来源,同一套目录逻辑) 后台 **[``measure_watch``](app/services/measure_watch.py)** 只认 **`MEASURE_WATCH_DIR`** 下一层的 **`fish{N}/`** 子目录及其中的 **``.svo2``**(见 ``iter_svo2_folders``)。与入口无关: | 方式 | 说明 | |------|------| | **ZED 分段录制** | 每次会话分配 ``fish_id``:取库表(``fish_id`` 与 ``output_dir`` 路径)、父目录下 ``fish``+数字 子目录名、以及 ``fish{N}/`` 下已有 ``.svo2`` 路径 四者编号的最大值再加 1(磁盘有数据而库未记时也不冲突);文件写入 ``{MEASURE_WATCH_DIR}/fish{N}/``(若未配置 ``MEASURE_WATCH_DIR`` 则 ``{STREAM_TMP_DIR}/zed_svo2/fish{N}/``,此时**不会**启用 ``measure_watch``,除非把 ``MEASURE_WATCH_DIR`` 指到 ``…/ingest/zed_svo2``) | | **手工拷贝** | 将 ``.svo2`` 放入 ``MEASURE_WATCH_DIR/fish{N}/``(自建 ``fish{N}`` 即可) | **逐段测量与齐套 final**:对每个 ``.svo2`` 稳定后轮询跑 FishMeasure,写入 SQLite;服务端可在 ``calculation_log`` 中区分 segment/final。``GET /api/v1/biomass/real/camera/`` 的 ``data.result[]`` **仅含** ``id``、``type``、``length``、``weight``、``date``(与历史客户端约定一致);按投递顺序先可能收到多段再收到聚合行。``video_left`` / ``video_right`` 规则不变。 **ingest** 的 ``/api/v1/ingest/svo/...`` 为分块上传流程,落盘与上述 ``fish{N}`` 路径独立;``fish{N}`` 目录的测量以 ``measure_watch`` 为准。 **ZED 相机**:**fish_api 启动/停止不会自动开关相机录制**;录制由独立服务或仓库根 ``start_recording.sh`` / ``zed_record_cli`` 等自行管理,写入 ``MEASURE_WATCH_DIR/fish{N}/`` 后由 ``measure_watch`` 消费。需要时也可调 ``/api/v1/zed/recording/start|stop``(不经过 uvicorn lifespan)。 ## ZED 分段录制 CLI 每次 **开始/停止** 录制会在 SQLite 表 ``zed_recording_sessions`` 中记录一行(``fish_id``、``started_at`` / ``stopped_at``、``output_dir``;分配规则见上表)。 在 `fish_api` 目录下执行(与 `app` 包路径一致;依赖已 `pip install -e .`): ```bash # 本机直连相机:前台录制,终端不关,Ctrl+C 结束(终端会打印分配的 fish_id) python3 -m app.zed_record_cli start [--segment-sec SEC] # 仅向已运行的 fish_api 发请求(不阻塞) python3 -m app.zed_record_cli start --remote [--segment-sec SEC] # 停止 / 查询状态(HTTP,需 uvicorn 已监听;基址见 FISH_API_BASE_URL 或 PUBLIC_BASE_URL) python3 -m app.zed_record_cli stop python3 -m app.zed_record_cli status ``` 若已通过 `pip install -e .` 安装了控制台脚本,也可使用 `fish-zed-record`(与上表等价)。 也可使用仓库根目录下的封装脚本: ```bash # 在 FishServer 仓库根目录执行 ./start_recording.sh # 本地前台;可选 --remote / --segment-sec ./stop_recording.sh # HTTP 停止(需 API 已启动) ``` ## Weight Rule (Current) 最终体重 `pred_weight_g` 由以下规则链决定(按优先级从高到低): 1. **440g 全池均值保护**(规则 B):若 `avg_g_filtered`(所有 candidates 均值)> `--mean-pool-fallback-max-if-over-g`(默认 440g),则 `pred_weight_g = max_predicted_weight_g_after_filter`,`pred_weight_rule = "max_after_filter_high_mean_pool_over_g"`。 2. **400g mean-all fallback**(规则 A,仅 `--average-all-after-filter` 开启时):若全池 mean > `--average-all-fallback-max-if-mean-over-g`(默认 400g),则 `pred_weight_g = max_predicted_weight_g_after_filter`,`pred_weight_rule = "max_after_filter_high_mean_all"`。 3. **`--average-all-after-filter`**(默认关):全部 candidates 均值作为最终值,`pred_weight_rule = "mean_all_filtered"`。 4. **Top-K 聚合**(默认路径):按 `--top-by-length`(默认开)选 top-K 帧,对选中帧的预测重量取**算术平均**(与日志中 `top{k}_avg` 一致),`pred_weight_rule = "top_k_aggregate"`。 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()`)。 **必须在 `fish_api` 下执行 `python -m app...`,或从仓库根用下面脚本**;若在仓库根直接运行 `python -m app.measure_debug_cli`,会因找不到 `app` 包报错(`ModuleNotFoundError: No module named 'app'`)。 ```bash # 方式 A:仓库根(推荐) bash scripts/measure_debug.sh --fish-id 14 # 方式 B:先进入 fish_api cd fish_api python3 -m pip install -e . # 若尚未安装依赖 # 默认:MEASURE_WATCH_DIR/fish{N}/ 下所有 .svo2 → 输出到 MEASURE_OUTPUT_ROOT/fish{N}(默认 fish_api/.data/measure_output/fish{N}) python3 -m app.measure_debug_cli --fish-id 1 # 或等价入口(须在 fish_api 目录,且已 pip install -e .) fish-measure-debug --fish-id 1 # 指定 SVO 目录或输出目录(在 fish_api 目录下) python3 -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` 逻辑 - 任务状态:`finalize` 返回 `job_id`,增加 `GET /jobs/{id}` 查询进度