Files
FishServer/fish_api/README.md
2026-04-14 22:05:52 +08:00

104 lines
6.2 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.
# Fish API
FastAPI 网关:分块接收 **SVO2**FishMeasure**MP4**FishAction在后台调用现有脚本对外提供文档约定的 **GET** 轮询接口,并托管预览视频 URL。
## 依赖环境
- Python 3.11+uv 可管理 3.13 等版本)
- **FishMeasure**:需与本机 `FishMeasure/` 一致的环境ZED SDK、`pyzed`、CUDA、YOLO/SAM 权重等)
- **FishAction**:需可运行 `FishAction/predict_video_x3d_3class.py`PyTorch、PyTorchVideo、checkpoint
若 FishMeasure 与 FishAction 使用不同虚拟环境,可设置:
- `PYTHON_FISH_MEASURE` — 运行 `predict_weigth_from_svo2.py` 的解释器路径
- `PYTHON_FISH_ACTION` — 运行 `predict_video_x3d_3class.py` 的解释器路径
**单一 Conda 环境**:若 FishMeasure 与 FishAction 已与网关停在同一个 env例如仓库根目录 [`packaging/conda-fishserver.yaml`](../packaging/conda-fishserver.yaml) 定义的 `fishserver`),则**不要**设置上述两个变量,子进程会使用当前 `uvicorn` 的 Python。可用 [`scripts/start_fresh.sh`](../scripts/start_fresh.sh)(清空后启动)或 [`scripts/start_no_fresh.sh`](../scripts/start_no_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` |
可在 `fish_api/.env` 中填写上述变量(`pydantic-settings` 会读取)。
## 安装与启动
```bash
cd fish_api
uv sync
# 可选:包含 httpx便于本地用 FastAPI TestClient 做冒烟测试
# uv sync --group dev
bash start_fresh.sh # 默认仅重置 client_id 投递进度,保留 SQLite 历史与快照
# CLEAR_SQLITE_DATABASE=1 bash start_fresh.sh # 需要时才彻底清 SQLite
# 或uv run uvicorn app.main:app --host 0.0.0.0 --port 8000需自行 prestart
```
OpenAPI`http://127.0.0.1:8000/docs`
## 对外 GET由其它系统轮询
- `GET /api/v1/biomass/real/camera/` — 每次 GET 消费**该客户端**下一条未投递的称重快照SQLite **按客户端独立游标**);无新数据时 `data.result` 为空,响应头 `X-Fish-Biomass-New: 0`
- `GET /api/v1/biomass/health/result/` — 同上,行为 / 健康快照队列
**客户端区分**:请求头 `X-Fish-Client-Id: <字符串>` 或查询参数 `client_id=<字符串>`**优先头**)。未携带时等价于 `default`,与旧版「全局一条游标」行为一致。不同 `client_id` 各自从当前队列消费,互不影响。
`result` 中每条鱼含算法字段:`id`(跟踪 ID`weight`g`length`mm。不可交付或失败的推理不会进入客户端队列。
## 流式输入(分块上传)
1. `POST /api/v1/ingest/svo/session``POST /api/v1/ingest/mp4/session``session_id`
2. `PUT /api/v1/ingest/{svo|mp4}/session/{session_id}?offset=0`(多次)
- **追加语义**`offset` 必须等于当前已写入字节数(从 0 开始顺序上传)
3. `POST /api/v1/ingest/{svo|mp4}/session/{session_id}/finalize``202 Accepted`,后台开始跑对应算法
示例(小文件一次性,`$API_KEY` 可选):
```bash
BASE=http://127.0.0.1:8000
H=()
# H=(-H "X-API-Key: your-secret")
sid=$(curl -sS "${H[@]}" -X POST "$BASE/api/v1/ingest/svo/session" | jq -r .session_id)
curl -sS "${H[@]}" -T sample.svo2 -X PUT "$BASE/api/v1/ingest/svo/session/$sid?offset=0"
curl -sS "${H[@]}" -X POST "$BASE/api/v1/ingest/svo/session/$sid/finalize"
curl -sS "$BASE/api/v1/biomass/real/camera/"
```
MP4 将 `svo` 换成 `mp4`,本地文件换成 `clip.mp4`,轮询 `GET /api/v1/biomass/health/result/`
**说明**`curl -T` 发送 PUT 时 offset 为 0 且一次性传完整文件适合单块场景;多块时请自行递增 `offset`
## 行为与健康映射
- X3D 输出 `feeding` / `normal` / `scared` → 中文 **吃饵** / **正常游行** / **惊吓**
- 健康:`scared`**不健康**,其余 → **健康**(启发式,可后续换专用模型)
## 视频 URL
FishMeasure 跑完后在输出目录查找 `*preview*.mp4`,复制到 `MEDIA_ROOT/`,文件名为 `{UTC时间戳}_{svo_stem}_left.mp4` / `_right.mp4`(每次测量不覆盖;仅一个预览文件时可能左右 URL 指向同一逻辑源经 SBS 拆分)。确保 `PUBLIC_BASE_URL` 与前端/文档中的域名端口一致。
## 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 帧candidates < 5 用 max 否则用 mean`pred_weight_rule = "top_k_aggregate"`
DGCNN 明细中同时输出 `mean_all_pred_g_after_filters``avg_topk_mean_pred_g` 等供对比参考。
## 演进建议
- RTSP`ffmpeg` 切段写入 MP4 后调用现有 `finalize` 逻辑
- 任务状态:`finalize` 返回 `job_id`,增加 `GET /jobs/{id}` 查询进度