Files
FishServer/fish_api/README.md

121 lines
9.3 KiB
Markdown
Raw Normal View History

2026-04-15 09:02:45 +08:00
# 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
2026-04-15 09:02:45 +08:00
## 配置(环境变量)
| 变量 | 说明 | 默认 |
| -------------------------------------------------------- | -------------------------------------------------------- | -------------------------------------- |
| `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 历史与快照
2026-04-14 22:05:52 +08:00
# 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 已启动)
```
2026-04-14 22:05:52 +08:00
## 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"`
2026-04-14 22:05:52 +08:00
DGCNN 明细中同时输出 `mean_all_pred_g_after_filters``avg_topk_mean_pred_g` 等供对比参考。
2026-04-14 22:06:33 +08:00
## 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()`)。
2026-04-14 22:06:33 +08:00
**必须在 `fish_api` 下执行 `python -m app...`,或从仓库根用下面脚本**;若在仓库根直接运行 `python -m app.measure_debug_cli`,会因找不到 `app` 包报错(`ModuleNotFoundError: No module named 'app'`)。
2026-04-14 22:06:33 +08:00
```bash
# 方式 A仓库根推荐
bash scripts/measure_debug.sh --fish-id 14
# 方式 B先进入 fish_api
2026-04-14 22:06:33 +08:00
cd fish_api
python3 -m pip install -e . # 若尚未安装依赖
2026-04-14 22:06:33 +08:00
# 默认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
2026-04-14 22:06:33 +08:00
# 或等价入口(须在 fish_api 目录,且已 pip install -e .
fish-measure-debug --fish-id 1
2026-04-14 22:06:33 +08:00
# 指定 SVO 目录或输出目录(在 fish_api 目录下)
python3 -m app.measure_debug_cli --batch-folder /path/to/fish1 --fish-id 1 --output-root /path/to/out
2026-04-14 22:06:33 +08:00
```
结束后会在终端打印 `weight_prediction.json` 中的 `pred_weight_g``pred_weight_rule` 等摘要。
## 演进建议
- RTSP`ffmpeg` 切段写入 MP4 后调用现有 `finalize` 逻辑
- 任务状态:`finalize` 返回 `job_id`,增加 `GET /jobs/{id}` 查询进度