Files
FishServer/fish_api/README.md
2026-05-14 16:49:05 +08:00

127 lines
9.5 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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
CLEAR_SQLITE_DATABASE=1 CLEAR_MEASURE_OUTPUT=1 CLEAR_ACTION_OUTPUT=1 CLEAR_MEDIA=1 CLEAR_STREAM_TMP=1 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` | 分块上传临时目录 | `<repo>/fish_api/.data/ingest` |
| `MEDIA_ROOT` | 对外托管每次测量生成的 `*_left.mp4` / `*_right.mp4` | `<repo>/fish_api/.data/media` |
| `FISH_MEASURE_ROOT` | `FishMeasure` 根目录 | 自动相对仓库 |
| `FISH_ACTION_ROOT` | `FishAction` 根目录 | 自动相对仓库 |
| `MEASURE_OUTPUT_ROOT` | 传给 `--save-output` 的目录 | `<repo>/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 . # 或python3 -m pip install -r requirements.txt
bash 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}` 查询进度