Files
FishServer/fish_api/app/settings.py
zaiun xu 7baaa72965 feat(fish_api): add SVO2 folder watch for FishMeasure
Add MEASURE_WATCH_* settings and measure_watch background loop parallel
to action watch. Gitignore SAM 2.4GB weight and fix corrupted DGCNN
checkpoint. Clear stale outputs for fresh rerun.

Made-with: Cursor
2026-04-08 20:35:55 +08:00

126 lines
3.9 KiB
Python
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.
from __future__ import annotations
from functools import lru_cache
from pathlib import Path
from typing import Optional
from pydantic import Field, field_validator, model_validator
from pydantic_settings import BaseSettings, SettingsConfigDict
def fish_repo_root() -> Path:
# fish_api/app/settings.py -> parent[2] = repo root (contains FishMeasure/, fish_api/)
return Path(__file__).resolve().parents[2]
def _default_stream_tmp() -> Path:
return fish_repo_root() / "fish_api" / ".data" / "ingest"
def _default_media_root() -> Path:
return fish_repo_root() / "fish_api" / ".data" / "media"
class Settings(BaseSettings):
model_config = SettingsConfigDict(
env_file=".env",
env_file_encoding="utf-8",
extra="ignore",
)
public_base_url: str = "http://127.0.0.1:8000"
ingest_api_key: str = ""
stream_tmp_dir: Path = Field(default_factory=_default_stream_tmp)
media_root: Path = Field(default_factory=_default_media_root)
fish_measure_root: Path = fish_repo_root() / "FishMeasure"
fish_action_root: Path = fish_repo_root() / "FishAction"
measure_output_root: Path = fish_repo_root() / "FishMeasure" / "output_weight_estimator"
python_fish_measure: str = ""
python_fish_action: str = ""
yolo_model: Optional[str] = None
weight_checkpoint: Optional[str] = None
sam_device: str = "cuda"
predict_conf: float = 0.5
predict_imgsz: int = 640
predict_max_frames: int = 0
predict_frame_stride: int = 1
action_checkpoint: Optional[str] = None
action_clips_per_video: int = 8
action_batch_size: int = 4
action_num_workers: int = 2
#: 非空时由 fish_api 在后台持续扫描该目录中的新 MP4 并跑 FishAction与 ingest 共用 app_state
action_watch_dir: Optional[Path] = None
action_watch_poll_interval: float = Field(default=2.0, ge=0.1)
action_watch_stable_polls: int = Field(default=3, ge=1)
action_watch_recursive: bool = False
#: 默认:<action_watch_dir>/.fishaction_watch_processed.json
action_watch_state_file: Optional[Path] = None
action_watch_use_state_file: bool = True
#: 非空时后台持续扫描该目录中的新 .svo2 并跑 FishMeasure与 ingest 共用 app_state
measure_watch_dir: Optional[Path] = None
measure_watch_poll_interval: float = Field(default=2.0, ge=0.1)
measure_watch_stable_polls: int = Field(default=3, ge=1)
measure_watch_recursive: bool = False
measure_watch_state_file: Optional[Path] = None
measure_watch_use_state_file: bool = True
default_fish_species: str = "大黄鱼"
@field_validator(
"action_watch_dir",
"action_watch_state_file",
"measure_watch_dir",
"measure_watch_state_file",
mode="before",
)
@classmethod
def _empty_str_path_none(cls, v: object) -> object:
if v is None:
return None
if isinstance(v, str) and not v.strip():
return None
return v
@model_validator(mode="after")
def _default_paths(self) -> "Settings":
if not self.yolo_model:
object.__setattr__(
self,
"yolo_model",
str(
self.fish_measure_root
/ "runs/train/fish_detection_20251127_104658/weights/best.pt"
),
)
if not self.weight_checkpoint:
object.__setattr__(
self,
"weight_checkpoint",
str(
self.fish_measure_root
/ "weight_estimator/runs/dgcnn_20260312_171043/best.pt"
),
)
if not self.action_checkpoint:
object.__setattr__(
self,
"action_checkpoint",
str(self.fish_action_root / "checkpoints/ptv_x3d_m/checkpoint_best.pt"),
)
return self
@lru_cache
def get_settings() -> Settings:
return Settings()