2026-04-15 09:02:45 +08:00
|
|
|
|
# Recalculate Everything:
|
|
|
|
|
|
|
2026-04-16 11:38:30 +08:00
|
|
|
|
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
|
|
|
|
|
2026-05-14 16:49:05 +08:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
CLEAR_SQLITE_DATABASE=1 CLEAR_MEASURE_OUTPUT=1 CLEAR_ACTION_OUTPUT=1 CLEAR_MEDIA=1 CLEAR_STREAM_TMP=1 bash scripts/start_fresh.sh
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
2026-04-16 14:53:01 +08:00
|
|
|
|
## 配置(环境变量)
|
2026-04-08 19:32:23 +08:00
|
|
|
|
|
|
|
|
|
|
|
2026-04-16 14:53:01 +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` 会读取)。 | | |
|
2026-04-08 19:32:23 +08:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
## 安装与启动
|
|
|
|
|
|
|
|
|
|
|
|
```bash
|
|
|
|
|
|
cd fish_api
|
2026-04-16 14:53:01 +08:00
|
|
|
|
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
|
2026-04-16 11:38:30 +08:00
|
|
|
|
# python3 -m uvicorn app.main:app --host 0.0.0.0 --port 8000 # 需自行 prestart
|
2026-04-08 19:32:23 +08:00
|
|
|
|
```
|
2026-04-16 11:38:30 +08:00
|
|
|
|
|
2026-04-16 14:53:01 +08:00
|
|
|
|
## SVO 输入与 `measure_watch`(两种来源,同一套目录逻辑)
|
|
|
|
|
|
|
|
|
|
|
|
后台 `**[measure_watch](app/services/measure_watch.py)**` 只认 `**MEASURE_WATCH_DIR**` 下一层的 `**fish{N}/**` 子目录及其中的 `**.svo2**`(见 `iter_svo2_folders`)。与入口无关:
|
2026-04-16 11:38:30 +08:00
|
|
|
|
|
|
|
|
|
|
|
2026-04-16 14:53:01 +08:00
|
|
|
|
| 方式 | 说明 |
|
|
|
|
|
|
| ------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
|
|
|
|
| **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}` 即可) |
|
2026-04-16 11:38:30 +08:00
|
|
|
|
|
|
|
|
|
|
|
2026-04-16 14:53:01 +08:00
|
|
|
|
**逐段测量与齐套 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` 规则不变。
|
2026-04-16 11:38:30 +08:00
|
|
|
|
|
2026-04-16 14:53:01 +08:00
|
|
|
|
**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)。
|
2026-04-16 11:38:30 +08:00
|
|
|
|
|
|
|
|
|
|
## ZED 分段录制 CLI
|
|
|
|
|
|
|
2026-04-16 14:53:01 +08:00
|
|
|
|
每次 **开始/停止** 录制会在 SQLite 表 `zed_recording_sessions` 中记录一行(`fish_id`、`started_at` / `stopped_at`、`output_dir`;分配规则见上表)。
|
2026-04-16 11:38:30 +08:00
|
|
|
|
|
|
|
|
|
|
在 `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"`。
|
2026-04-16 14:53:01 +08:00
|
|
|
|
3. `**--average-all-after-filter`**(默认关):全部 candidates 均值作为最终值,`pred_weight_rule = "mean_all_filtered"`。
|
2026-04-15 09:01:45 +08:00
|
|
|
|
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}` 与线上一致)。
|
|
|
|
|
|
|
2026-04-16 14:53:01 +08:00
|
|
|
|
调用 `[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
|
|
|
|
|
2026-04-15 09:01:45 +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
|
2026-04-15 09:01:45 +08:00
|
|
|
|
# 方式 A:仓库根(推荐)
|
|
|
|
|
|
bash scripts/measure_debug.sh --fish-id 14
|
|
|
|
|
|
|
|
|
|
|
|
# 方式 B:先进入 fish_api
|
2026-04-14 22:06:33 +08:00
|
|
|
|
cd fish_api
|
2026-04-16 11:38:30 +08:00
|
|
|
|
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})
|
2026-04-16 11:38:30 +08:00
|
|
|
|
python3 -m app.measure_debug_cli --fish-id 1
|
2026-04-14 22:06:33 +08:00
|
|
|
|
|
2026-04-16 11:38:30 +08:00
|
|
|
|
# 或等价入口(须在 fish_api 目录,且已 pip install -e .)
|
|
|
|
|
|
fish-measure-debug --fish-id 1
|
2026-04-14 22:06:33 +08:00
|
|
|
|
|
2026-04-15 09:01:45 +08:00
|
|
|
|
# 指定 SVO 目录或输出目录(在 fish_api 目录下)
|
2026-04-16 11:38:30 +08:00
|
|
|
|
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` 等摘要。
|
|
|
|
|
|
|
2026-04-08 19:32:23 +08:00
|
|
|
|
## 演进建议
|
|
|
|
|
|
|
|
|
|
|
|
- RTSP:用 `ffmpeg` 切段写入 MP4 后调用现有 `finalize` 逻辑
|
|
|
|
|
|
- 任务状态:`finalize` 返回 `job_id`,增加 `GET /jobs/{id}` 查询进度
|
2026-04-16 14:53:01 +08:00
|
|
|
|
|