Initial commit — gin-config strict-pattern coding standard
Code-style guide and reference patterns for DL/CV research at the ЛИСАД laboratory (NADEZHDA / SOFIA CVGL projects). Files: - Стандарт написания кода для DL CV исследований (CVGL).md - Правила написания Python-кода (Gin-Config Strict Pattern).md - REQUIREMENTS_GIN_STYLE.md - Gin-Config Strict Pattern Reference Examples.md - Переход от argparse и dataclass к gin-config.md - gin-parse.md - Рекомендуемые gin-config категории.md - config_loader_reference.py - README.md (this commit) - .gitignore (Python artifacts)
This commit is contained in:
58
.gitignore
vendored
Normal file
58
.gitignore
vendored
Normal file
@@ -0,0 +1,58 @@
|
|||||||
|
# Python build artifacts
|
||||||
|
__pycache__/
|
||||||
|
*.py[cod]
|
||||||
|
*$py.class
|
||||||
|
*.egg-info/
|
||||||
|
*.egg
|
||||||
|
.eggs/
|
||||||
|
build/
|
||||||
|
dist/
|
||||||
|
|
||||||
|
# Virtual environments
|
||||||
|
.venv/
|
||||||
|
venv/
|
||||||
|
env/
|
||||||
|
ENV/
|
||||||
|
.python-version
|
||||||
|
|
||||||
|
# Coverage / testing
|
||||||
|
.coverage
|
||||||
|
.coverage.*
|
||||||
|
htmlcov/
|
||||||
|
.tox/
|
||||||
|
.nox/
|
||||||
|
.pytest_cache/
|
||||||
|
.mypy_cache/
|
||||||
|
.ruff_cache/
|
||||||
|
.dmypy.json
|
||||||
|
|
||||||
|
# IDE / OS
|
||||||
|
.idea/
|
||||||
|
.vscode/
|
||||||
|
!.vscode/settings.json
|
||||||
|
!.vscode/launch.json
|
||||||
|
!.vscode/extensions.json
|
||||||
|
*.swp
|
||||||
|
*.swo
|
||||||
|
*~
|
||||||
|
.DS_Store
|
||||||
|
Thumbs.db
|
||||||
|
desktop.ini
|
||||||
|
|
||||||
|
# Jupyter
|
||||||
|
.ipynb_checkpoints/
|
||||||
|
|
||||||
|
# Secrets — never commit
|
||||||
|
.env
|
||||||
|
.env.local
|
||||||
|
*.key
|
||||||
|
*.pem
|
||||||
|
credentials.json
|
||||||
|
|
||||||
|
# Temp
|
||||||
|
*.log
|
||||||
|
*.tmp
|
||||||
|
*.bak
|
||||||
|
*.backup
|
||||||
|
*.orig
|
||||||
|
~$*
|
||||||
513
Gin-Config Strict Pattern Reference Examples.md
Normal file
513
Gin-Config Strict Pattern Reference Examples.md
Normal file
@@ -0,0 +1,513 @@
|
|||||||
|
# Gin-Config Strict Pattern: Reference Examples
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## Example 1: Config class + loader + .gin file
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
### `src/conf/pipeline_conf.py`
|
||||||
|
|
||||||
|
```python
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
import gin
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@gin.configurable
|
||||||
|
|
||||||
|
class PipelineConfig:
|
||||||
|
|
||||||
|
"""Configuration for the augmentation pipeline stages and output."""
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
def __init__(
|
||||||
|
|
||||||
|
self,
|
||||||
|
|
||||||
|
input_root: str = "/data/UAV-GeoLoc",
|
||||||
|
|
||||||
|
output_root: str = "/data/UAV-GeoLoc-aug",
|
||||||
|
|
||||||
|
stages: list[str] | None = None,
|
||||||
|
|
||||||
|
save_npy: bool = True,
|
||||||
|
|
||||||
|
save_vis: bool = True,
|
||||||
|
|
||||||
|
save_concat: bool = False,
|
||||||
|
|
||||||
|
resume: bool = True,
|
||||||
|
|
||||||
|
subset: str | None = None,
|
||||||
|
|
||||||
|
source: str | None = None,
|
||||||
|
|
||||||
|
log_level: str = "INFO",
|
||||||
|
|
||||||
|
) -> None:
|
||||||
|
|
||||||
|
self.input_root = input_root
|
||||||
|
|
||||||
|
self.output_root = output_root
|
||||||
|
|
||||||
|
self.stages = stages or ["depth", "edges", "segmentation"]
|
||||||
|
|
||||||
|
self.save_npy = save_npy
|
||||||
|
|
||||||
|
self.save_vis = save_vis
|
||||||
|
|
||||||
|
self.save_concat = save_concat
|
||||||
|
|
||||||
|
self.resume = resume
|
||||||
|
|
||||||
|
self.subset = subset
|
||||||
|
|
||||||
|
self.source = source
|
||||||
|
|
||||||
|
self.log_level = log_level
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
def get_pipeline_cfg(path2cfg: str) -> PipelineConfig:
|
||||||
|
|
||||||
|
"""Load pipeline config from gin file.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
Args:
|
||||||
|
|
||||||
|
path2cfg: Path to config directory (with trailing slash).
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
|
||||||
|
Instantiated PipelineConfig with values from gin file.
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
gin.parse_config_file(f"{path2cfg}pipeline.gin")
|
||||||
|
|
||||||
|
return PipelineConfig()
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
### `in/config_files/pipeline.gin`
|
||||||
|
|
||||||
|
```gin
|
||||||
|
|
||||||
|
# Pipeline configuration
|
||||||
|
|
||||||
|
PipelineConfig.input_root = '/data/UAV-GeoLoc'
|
||||||
|
|
||||||
|
PipelineConfig.output_root = '/data/UAV-GeoLoc-aug'
|
||||||
|
|
||||||
|
PipelineConfig.stages = ['depth', 'edges', 'segmentation']
|
||||||
|
|
||||||
|
PipelineConfig.save_npy = True
|
||||||
|
|
||||||
|
PipelineConfig.save_vis = True
|
||||||
|
|
||||||
|
PipelineConfig.save_concat = False
|
||||||
|
|
||||||
|
PipelineConfig.resume = True
|
||||||
|
|
||||||
|
PipelineConfig.subset = 'Rot'
|
||||||
|
|
||||||
|
PipelineConfig.source = None
|
||||||
|
|
||||||
|
PipelineConfig.log_level = 'INFO'
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## Example 2: Model config with fallback IDs
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
### `src/conf/models_conf.py`
|
||||||
|
|
||||||
|
```python
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
import gin
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@gin.configurable
|
||||||
|
|
||||||
|
class ModelsConfig:
|
||||||
|
|
||||||
|
"""Model identifiers and fallback strategy."""
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
def __init__(
|
||||||
|
|
||||||
|
self,
|
||||||
|
|
||||||
|
depth_model_id: str = "depth-anything/DA3-BASE",
|
||||||
|
|
||||||
|
depth_fallback_id: str = "depth-anything/Depth-Anything-V2-Large-hf",
|
||||||
|
|
||||||
|
seg_model_type: str = "segearth-ov3",
|
||||||
|
|
||||||
|
seg_fallback_type: str = "segformer-b5",
|
||||||
|
|
||||||
|
seg_fallback_id: str = "nvidia/segformer-b5-finetuned-ade-640-640",
|
||||||
|
|
||||||
|
) -> None:
|
||||||
|
|
||||||
|
self.depth_model_id = depth_model_id
|
||||||
|
|
||||||
|
self.depth_fallback_id = depth_fallback_id
|
||||||
|
|
||||||
|
self.seg_model_type = seg_model_type
|
||||||
|
|
||||||
|
self.seg_fallback_type = seg_fallback_type
|
||||||
|
|
||||||
|
self.seg_fallback_id = seg_fallback_id
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
def get_models_cfg(path2cfg: str) -> ModelsConfig:
|
||||||
|
|
||||||
|
"""Load models config from gin file."""
|
||||||
|
|
||||||
|
gin.parse_config_file(f"{path2cfg}models.gin")
|
||||||
|
|
||||||
|
return ModelsConfig()
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## Example 3: Main entry point
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
### `src/main.py`
|
||||||
|
|
||||||
|
```python
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
import gc
|
||||||
|
|
||||||
|
import logging
|
||||||
|
|
||||||
|
import time
|
||||||
|
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
import numpy as np
|
||||||
|
|
||||||
|
import torch
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
from conf.pipeline_conf import get_pipeline_cfg, PipelineConfig
|
||||||
|
|
||||||
|
from conf.hardware_conf import get_hardware_cfg, HardwareConfig
|
||||||
|
|
||||||
|
from conf.models_conf import get_models_cfg, ModelsConfig
|
||||||
|
|
||||||
|
from conf.input_conf import get_input_cfg, InputConfig
|
||||||
|
|
||||||
|
from conf.seg_conf import get_seg_cfg, SegConfig
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
def get_proj_dir() -> str:
|
||||||
|
|
||||||
|
"""Return project root directory with trailing slash."""
|
||||||
|
|
||||||
|
return str(Path(__file__).resolve().parent.parent) + "/"
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
def run_pipeline(
|
||||||
|
|
||||||
|
pipeline_conf: PipelineConfig,
|
||||||
|
|
||||||
|
hardware_conf: HardwareConfig,
|
||||||
|
|
||||||
|
models_conf: ModelsConfig,
|
||||||
|
|
||||||
|
input_conf: InputConfig,
|
||||||
|
|
||||||
|
seg_conf: SegConfig,
|
||||||
|
|
||||||
|
) -> None:
|
||||||
|
|
||||||
|
"""Execute the full augmentation pipeline.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
Args:
|
||||||
|
|
||||||
|
pipeline_conf: Pipeline stage configuration.
|
||||||
|
|
||||||
|
hardware_conf: GPU hardware profile.
|
||||||
|
|
||||||
|
models_conf: Model identifiers and fallbacks.
|
||||||
|
|
||||||
|
input_conf: Image preprocessing parameters.
|
||||||
|
|
||||||
|
seg_conf: Segmentation prompts and thresholds.
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
torch.manual_seed(42)
|
||||||
|
|
||||||
|
np.random.seed(42)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
for stage in pipeline_conf.stages:
|
||||||
|
|
||||||
|
logger.info("Running stage: %s", stage)
|
||||||
|
|
||||||
|
t0 = time.perf_counter()
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
if stage == "depth":
|
||||||
|
|
||||||
|
run_depth_stage(pipeline_conf, hardware_conf, models_conf, input_conf, device)
|
||||||
|
|
||||||
|
elif stage == "edges":
|
||||||
|
|
||||||
|
run_edges_stage(pipeline_conf, input_conf)
|
||||||
|
|
||||||
|
elif stage == "segmentation":
|
||||||
|
|
||||||
|
run_seg_stage(pipeline_conf, hardware_conf, models_conf, seg_conf, device)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
logger.info("Stage %s done in %.1f s", stage, time.perf_counter() - t0)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
def main() -> None:
|
||||||
|
|
||||||
|
"""Entry point: load gin configs and run pipeline."""
|
||||||
|
|
||||||
|
proj_dir = get_proj_dir()
|
||||||
|
|
||||||
|
path2cfg = f"{proj_dir}in/config_files/"
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
pipeline_conf = get_pipeline_cfg(path2cfg)
|
||||||
|
|
||||||
|
hardware_conf = get_hardware_cfg(path2cfg)
|
||||||
|
|
||||||
|
models_conf = get_models_cfg(path2cfg)
|
||||||
|
|
||||||
|
input_conf = get_input_cfg(path2cfg)
|
||||||
|
|
||||||
|
seg_conf = get_seg_cfg(path2cfg)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
run_pipeline(pipeline_conf, hardware_conf, models_conf, input_conf, seg_conf)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
|
||||||
|
main()
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## Example 4: Model loading with config object
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
```python
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
import gc
|
||||||
|
|
||||||
|
import logging
|
||||||
|
|
||||||
|
from typing import Any
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
import torch
|
||||||
|
|
||||||
|
import torch.nn as nn
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
def load_depth_model(
|
||||||
|
|
||||||
|
models_conf: ModelsConfig,
|
||||||
|
|
||||||
|
hardware_conf: HardwareConfig,
|
||||||
|
|
||||||
|
device: torch.device,
|
||||||
|
|
||||||
|
) -> nn.Module:
|
||||||
|
|
||||||
|
"""Load depth estimation model based on config.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
Args:
|
||||||
|
|
||||||
|
models_conf: Model IDs from gin config.
|
||||||
|
|
||||||
|
hardware_conf: FP16 and device settings.
|
||||||
|
|
||||||
|
device: Target CUDA device.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
|
||||||
|
Loaded depth model on device.
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
model_id = models_conf.depth_model_id
|
||||||
|
|
||||||
|
logger.info("Loading depth: %s", model_id)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
try:
|
||||||
|
|
||||||
|
from depth_anything_3 import DepthAnything3
|
||||||
|
|
||||||
|
model = DepthAnything3.from_pretrained(model_id)
|
||||||
|
|
||||||
|
if hardware_conf.use_fp16:
|
||||||
|
|
||||||
|
model = model.half()
|
||||||
|
|
||||||
|
return model.to(device).eval()
|
||||||
|
|
||||||
|
except ImportError:
|
||||||
|
|
||||||
|
logger.warning("DA3 not found, falling back to %s", models_conf.depth_fallback_id)
|
||||||
|
|
||||||
|
from transformers import AutoModelForDepthEstimation
|
||||||
|
|
||||||
|
dtype = torch.float16 if hardware_conf.use_fp16 else torch.float32
|
||||||
|
|
||||||
|
model = AutoModelForDepthEstimation.from_pretrained(
|
||||||
|
|
||||||
|
models_conf.depth_fallback_id, torch_dtype=dtype,
|
||||||
|
|
||||||
|
)
|
||||||
|
|
||||||
|
return model.to(device).eval()
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
def unload_model(model: Any) -> None:
|
||||||
|
|
||||||
|
"""Free GPU memory after model use."""
|
||||||
|
|
||||||
|
del model
|
||||||
|
|
||||||
|
gc.collect()
|
||||||
|
|
||||||
|
if torch.cuda.is_available():
|
||||||
|
|
||||||
|
torch.cuda.empty_cache()
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## Anti-patterns (DO NOT)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
```python
|
||||||
|
|
||||||
|
# BAD: dataclass + gin
|
||||||
|
|
||||||
|
@gin.configurable
|
||||||
|
|
||||||
|
@dataclass # ← FORBIDDEN
|
||||||
|
|
||||||
|
class Config:
|
||||||
|
|
||||||
|
param: int = 1
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# BAD: argparse
|
||||||
|
|
||||||
|
parser = argparse.ArgumentParser() # ← FORBIDDEN, use gin
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# BAD: global gin state inside function
|
||||||
|
|
||||||
|
def process():
|
||||||
|
|
||||||
|
val = gin.query_parameter("Config.param") # ← FORBIDDEN
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# BAD: gin.constant / macros
|
||||||
|
|
||||||
|
LEARNING_RATE = gin.constant("lr", 0.001) # ← FORBIDDEN
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# BAD: hardcoded model ID
|
||||||
|
|
||||||
|
model = AutoModel.from_pretrained("depth-anything/DA3-BASE") # ← move to gin
|
||||||
|
|
||||||
|
```
|
||||||
48
README.md
Normal file
48
README.md
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
# style_code_lisad
|
||||||
|
|
||||||
|
Code-style guide and reference patterns for DL/CV research at the **ЛИСАД laboratory** (CVGL projects: NADEZHDA / SOFIA).
|
||||||
|
|
||||||
|
## Contents
|
||||||
|
|
||||||
|
| File | Purpose |
|
||||||
|
|------|---------|
|
||||||
|
| `Стандарт написания кода для DL CV исследований (CVGL).md` | Master coding standard for the lab |
|
||||||
|
| `Правила написания Python-кода (Gin-Config Strict Pattern).md` | Quick-reference rule sheet |
|
||||||
|
| `REQUIREMENTS_GIN_STYLE.md` | Detailed gin-config requirements |
|
||||||
|
| `Gin-Config Strict Pattern Reference Examples.md` | Annotated example files |
|
||||||
|
| `Переход от argparse и dataclass к gin-config.md` | Migration guide |
|
||||||
|
| `gin-parse.md` | Notes on gin parsing semantics |
|
||||||
|
| `Рекомендуемые gin-config категории.md` | Suggested groupings of gin-configurable classes |
|
||||||
|
| `config_loader_reference.py` | Reference implementation of `get_*_cfg()` loader pattern |
|
||||||
|
|
||||||
|
## Core principles
|
||||||
|
|
||||||
|
1. **Configuration via `gin-config`** (no `argparse`, no `dataclass`).
|
||||||
|
2. `@gin.configurable` on **classes only** — all defaults live in `__init__`.
|
||||||
|
3. Loader pattern: `get_<name>_cfg(path: str) -> Instance` that calls
|
||||||
|
`gin.parse_config_file(path)` then constructs the class.
|
||||||
|
4. Pass configs **explicitly** via constructor arguments, never via global
|
||||||
|
gin state.
|
||||||
|
5. Strict typing on all public APIs, Google-style docstrings, English code.
|
||||||
|
|
||||||
|
See the full standard in `Стандарт написания кода...md`.
|
||||||
|
|
||||||
|
## Layout for derived projects
|
||||||
|
|
||||||
|
```
|
||||||
|
project_root/
|
||||||
|
├── in/
|
||||||
|
│ └── config_files/ # one .gin file per config class
|
||||||
|
├── src/
|
||||||
|
│ ├── conf/ # config classes + get_*_cfg() loaders
|
||||||
|
│ ├── augmentor/ # business logic (or other domain modules)
|
||||||
|
│ └── ...
|
||||||
|
├── tests/
|
||||||
|
└── README.md
|
||||||
|
```
|
||||||
|
|
||||||
|
## License & usage
|
||||||
|
|
||||||
|
Internal lab reference. Apply when writing new CVGL research code (e.g.
|
||||||
|
`code_sofia_v71/` should follow this standard — current dataclass-based
|
||||||
|
config is a known deviation that will be migrated in a separate refactor).
|
||||||
278
REQUIREMENTS_GIN_STYLE.md
Normal file
278
REQUIREMENTS_GIN_STYLE.md
Normal file
@@ -0,0 +1,278 @@
|
|||||||
|
# Требования к адаптации кода под gin-config стиль
|
||||||
|
|
||||||
|
Референсный проект: `test_bb_uav`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 1. Структура директорий
|
||||||
|
|
||||||
|
Создать директорию для конфигурационных файлов:
|
||||||
|
|
||||||
|
```
|
||||||
|
depth_edges_annotate_worlduav/
|
||||||
|
├── in/
|
||||||
|
│ └── config_files/
|
||||||
|
│ ├── pipeline.gin # Стадии пайплайна, пути, форматы вывода
|
||||||
|
│ ├── hardware.gin # Профили GPU, batch size, FP16, workers
|
||||||
|
│ ├── models.gin # Идентификаторы моделей, fallback-стратегии
|
||||||
|
│ ├── input.gin # Размер изображений, нормализация, аугментации
|
||||||
|
│ └── segmentation.gin # Текстовые промпты, параметры сегментации
|
||||||
|
├── src/
|
||||||
|
│ ├── conf/
|
||||||
|
│ │ ├── __init__.py
|
||||||
|
│ │ ├── pipeline_conf.py # PipelineConfig (пути, стадии, resume, вывод)
|
||||||
|
│ │ ├── hardware_conf.py # HardwareConfig (GPU, batch, FP16, workers)
|
||||||
|
│ │ ├── models_conf.py # ModelsConfig (model IDs, fallback)
|
||||||
|
│ │ ├── input_conf.py # InputConfig (размер, нормализация)
|
||||||
|
│ │ └── seg_conf.py # SegConfig (промпты, параметры)
|
||||||
|
│ ├── augmentor/
|
||||||
|
│ │ ├── ... # Существующие модули (без изменения структуры)
|
||||||
|
│ └── augment_dataset.py # Точка входа (адаптировать)
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2. Паттерн конфигурационных классов
|
||||||
|
|
||||||
|
Каждый конфиг-класс следует единому шаблону из `test_bb_uav`:
|
||||||
|
|
||||||
|
```python
|
||||||
|
import gin
|
||||||
|
|
||||||
|
@gin.configurable
|
||||||
|
class HardwareConfig:
|
||||||
|
def __init__(self, profile_name='rtx4090', use_fp16=True,
|
||||||
|
batch_size=None, num_workers=4, reserve_gb=2.0):
|
||||||
|
self.profile_name = profile_name
|
||||||
|
self.use_fp16 = use_fp16
|
||||||
|
self.batch_size = batch_size
|
||||||
|
self.num_workers = num_workers
|
||||||
|
self.reserve_gb = reserve_gb
|
||||||
|
```
|
||||||
|
|
||||||
|
### Правила:
|
||||||
|
- Декоратор `@gin.configurable` только на классах (не на функциях)
|
||||||
|
- Все параметры имеют значения по умолчанию в `__init__`
|
||||||
|
- Класс хранит параметры как атрибуты экземпляра (`self.param = param`)
|
||||||
|
- Не использовать dataclass совместно с gin — заменить dataclass на обычные классы
|
||||||
|
- Допустима пост-обработка в `__init__` (вычисление производных значений)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3. Загрузка конфигов
|
||||||
|
|
||||||
|
### 3.1 Центральный загрузчик (ПРОДАКШЕН)
|
||||||
|
|
||||||
|
Один файл `src/conf/config_loader.py` содержит единственную функцию, которая парсит **все** .gin файлы за один вызов:
|
||||||
|
|
||||||
|
```python
|
||||||
|
def load_all_configs(path2cfg: str) -> dict[str, Any]:
|
||||||
|
"""Parse ALL .gin files at once and return all config objects."""
|
||||||
|
gin.clear_config() # ОБЯЗАТЕЛЬНО: сброс глобального состояния gin
|
||||||
|
|
||||||
|
gin_files = sorted(Path(path2cfg).glob("*.gin"))
|
||||||
|
gin.parse_config_files_and_bindings(
|
||||||
|
config_files=[str(f) for f in gin_files],
|
||||||
|
bindings=[],
|
||||||
|
)
|
||||||
|
|
||||||
|
return {
|
||||||
|
"pipeline": PipelineConfig(),
|
||||||
|
"hardware": HardwareConfig(),
|
||||||
|
"models": ModelsConfig(),
|
||||||
|
"input": InputConfig(),
|
||||||
|
"seg": SegConfig(),
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Правила:
|
||||||
|
- `gin.clear_config()` **обязателен** перед каждой загрузкой — без него параметры от предыдущих вызовов накапливаются (gin имеет глобальное состояние)
|
||||||
|
- Все .gin файлы парсятся **одним вызовом** `gin.parse_config_files_and_bindings()`
|
||||||
|
- Конфиг-объекты создаются **после** полной загрузки всех .gin
|
||||||
|
- В main() — **один вызов** `load_all_configs()`, не 5 отдельных `get_*_cfg()`
|
||||||
|
|
||||||
|
### 3.2 Индивидуальные загрузчики (ТОЛЬКО ДЛЯ ТЕСТИРОВАНИЯ)
|
||||||
|
|
||||||
|
Для unit-тестов и отладки — отдельные `get_<name>_cfg()` функции в том же файле:
|
||||||
|
|
||||||
|
```python
|
||||||
|
def get_hardware_cfg(path2cfg: str) -> HardwareConfig:
|
||||||
|
"""Load ONLY hardware config (for isolated testing)."""
|
||||||
|
gin.clear_config() # Обязательно!
|
||||||
|
gin.parse_config_file(f"{path2cfg}hardware.gin")
|
||||||
|
return HardwareConfig()
|
||||||
|
```
|
||||||
|
|
||||||
|
### Правила:
|
||||||
|
- Имя функции: `get_<name>_cfg(path2cfg: str) -> ConfigClass`
|
||||||
|
- **Обязательно** `gin.clear_config()` перед `gin.parse_config_file()`
|
||||||
|
- Используются **только** в тестах и notebooks, не в main()
|
||||||
|
|
||||||
|
### Референс: `develop_style_prompt/config_loader_reference.py`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 4. Формат .gin файлов
|
||||||
|
|
||||||
|
Плоский формат `ClassName.param = value`:
|
||||||
|
|
||||||
|
```gin
|
||||||
|
# hardware.gin
|
||||||
|
HardwareConfig.profile_name = 'rtx4090'
|
||||||
|
HardwareConfig.use_fp16 = True
|
||||||
|
HardwareConfig.batch_size = 8
|
||||||
|
HardwareConfig.num_workers = 4
|
||||||
|
HardwareConfig.reserve_gb = 2.0
|
||||||
|
```
|
||||||
|
|
||||||
|
### Правила:
|
||||||
|
- Одна строка — один параметр
|
||||||
|
- Комментарии через `#`
|
||||||
|
- Строки в одинарных или двойных кавычках
|
||||||
|
- Списки в квадратных скобках: `[1, 2, 3]`
|
||||||
|
- Не использовать макросы, ссылки, `gin.constant()` или другие продвинутые фичи gin
|
||||||
|
- Каждый `.gin` файл соответствует одному конфиг-классу
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 5. Точка входа (main)
|
||||||
|
|
||||||
|
Адаптировать `augment_dataset.py` — использовать центральный загрузчик:
|
||||||
|
|
||||||
|
```python
|
||||||
|
from src.conf.config_loader import load_all_configs
|
||||||
|
from src.utils.utils_file_dir import get_proj_dir
|
||||||
|
|
||||||
|
def main() -> None:
|
||||||
|
proj_dir = get_proj_dir()
|
||||||
|
path2cfg = f"{proj_dir}in/config_files/"
|
||||||
|
|
||||||
|
# ОДИН вызов загружает ВСЕ конфиги:
|
||||||
|
configs = load_all_configs(path2cfg)
|
||||||
|
|
||||||
|
# Явная передача в пайплайн:
|
||||||
|
run_pipeline(
|
||||||
|
configs["pipeline"],
|
||||||
|
configs["hardware"],
|
||||||
|
configs["models"],
|
||||||
|
configs["input"],
|
||||||
|
configs["seg"],
|
||||||
|
)
|
||||||
|
```
|
||||||
|
|
||||||
|
```python
|
||||||
|
# Ищем pyproject.toml или .git как корень проекта:
|
||||||
|
MARKERS = ("pyproject.toml", ".git", "in")
|
||||||
|
|
||||||
|
def get_proj_dir() -> str:
|
||||||
|
current = Path(__file__).resolve().parent
|
||||||
|
for _ in range(10):
|
||||||
|
if any((current / m).exists() for m in MARKERS):
|
||||||
|
return str(current) + "/"
|
||||||
|
current = current.parent
|
||||||
|
raise RuntimeError("Project root not found")
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
### Правила:
|
||||||
|
- Убрать `argparse` — все параметры из gin-файлов
|
||||||
|
- **Один вызов** `load_all_configs()` вместо 5 отдельных `get_*_cfg()`
|
||||||
|
- `gin.clear_config()` вызывается **внутри** `load_all_configs()` автоматически
|
||||||
|
- Конфиг-объекты передаются **явно** через аргументы (не через глобальный gin)
|
||||||
|
- Функция `get_proj_dir()` определяет корень проекта автоматически
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 6. Распределение параметров по конфиг-классам
|
||||||
|
|
||||||
|
### PipelineConfig (`pipeline.gin`)
|
||||||
|
| Параметр | Тип | По умолчанию | Описание |
|
||||||
|
|---|---|---|---|
|
||||||
|
| `input_root` | str | — | Путь к входным данным |
|
||||||
|
| `output_root` | str | — | Путь для выходных данных |
|
||||||
|
| `stages` | list | `['depth', 'edges', 'segmentation']` | Стадии пайплайна |
|
||||||
|
| `save_npy` | bool | `True` | Сохранять .npy файлы |
|
||||||
|
| `save_vis` | bool | `True` | Сохранять визуализации |
|
||||||
|
| `save_concat` | bool | `True` | Сохранять 6-канальный результат |
|
||||||
|
| `resume` | bool | `True` | Пропускать уже обработанные |
|
||||||
|
| `subset` | str | `None` | Фильтр по подмножеству |
|
||||||
|
| `source` | str | `None` | Фильтр по источнику (query/db) |
|
||||||
|
| `log_level` | str | `'INFO'` | Уровень логирования |
|
||||||
|
|
||||||
|
### HardwareConfig (`hardware.gin`)
|
||||||
|
| Параметр | Тип | По умолчанию | Описание |
|
||||||
|
|---|---|---|---|
|
||||||
|
| `profile_name` | str | `'rtx4090'` | Имя профиля GPU |
|
||||||
|
| `use_fp16` | bool | `True` | Использовать half precision |
|
||||||
|
| `batch_size` | int | `None` | Размер батча (None = авто) |
|
||||||
|
| `num_workers` | int | `4` | Число DataLoader workers |
|
||||||
|
| `reserve_gb` | float | `2.0` | Резерв VRAM в ГБ |
|
||||||
|
|
||||||
|
### ModelsConfig (`models.gin`)
|
||||||
|
| Параметр | Тип | По умолчанию | Описание |
|
||||||
|
|---|---|---|---|
|
||||||
|
| `depth_model_id` | str | `'depth-anything/...'` | HuggingFace ID модели глубины |
|
||||||
|
| `depth_fallback_id` | str | `'...'` | Fallback модель глубины |
|
||||||
|
| `seg_model_id` | str | `'...'` | ID модели сегментации |
|
||||||
|
| `seg_fallback_id` | str | `'...'` | Fallback модель сегментации |
|
||||||
|
| `depth_model_size` | str | `'Large'` | Размер модели глубины |
|
||||||
|
| `seg_model_size` | str | `'b5'` | Размер модели сегментации |
|
||||||
|
|
||||||
|
### InputConfig (`input.gin`)
|
||||||
|
| Параметр | Тип | По умолчанию | Описание |
|
||||||
|
|---|---|---|---|
|
||||||
|
| `image_size` | int | `256` | Размер изображения |
|
||||||
|
| `sobel_blur_ksize` | int | `3` | Ядро Sobel для edges |
|
||||||
|
| `edge_normalize` | bool | `True` | Нормализация карты краев |
|
||||||
|
|
||||||
|
### SegConfig (`segmentation.gin`)
|
||||||
|
| Параметр | Тип | По умолчанию | Описание |
|
||||||
|
|---|---|---|---|
|
||||||
|
| `prompts` | list | `[...]` | Текстовые промпты для OV-сегментации |
|
||||||
|
| `threshold` | float | `0.3` | Порог сегментации |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 7. Миграция существующих модулей
|
||||||
|
|
||||||
|
### Что удалить:
|
||||||
|
- `argparse` из `augment_dataset.py`
|
||||||
|
- `@dataclass` из `AugmentConfig` и `HardwareProfile` в `config.py`
|
||||||
|
- Словарь `PROFILES` с хардкодом профилей (перенести в gin)
|
||||||
|
- `DEFAULT_GEO_PROMPTS` из `models.py` (перенести в gin)
|
||||||
|
|
||||||
|
### Что сохранить:
|
||||||
|
- Структуру `src/augmentor/` (dataset, inference, models, io_utils)
|
||||||
|
- Логику fallback при загрузке моделей (но model ID из конфига)
|
||||||
|
- Логику discover_images, AugmentDataset
|
||||||
|
- Функции сохранения (save_depth, save_edges, save_segmentation)
|
||||||
|
|
||||||
|
### Что адаптировать:
|
||||||
|
- Все функции, принимающие `AugmentConfig`, должны принимать отдельные конфиг-объекты
|
||||||
|
- Захардкоженные значения (model IDs, промпты, размер 256) — в gin-файлы
|
||||||
|
- `pipeline_256.py` — удалить или адаптировать под новый стиль
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 8. Ограничения по использованию gin
|
||||||
|
|
||||||
|
Использовать **минимальный** набор возможностей gin:
|
||||||
|
|
||||||
|
- **Да**: `@gin.configurable`, `gin.parse_config_file()`
|
||||||
|
- **Нет**: `gin.register()`, `gin.constant()`, `gin.query_parameter()`, макросы, ссылки между конфигами
|
||||||
|
- Значения конфигов можно менять в runtime после загрузки (`config.param = new_value`)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 9. Порядок выполнения миграции
|
||||||
|
|
||||||
|
1. Создать `src/conf/` с конфиг-классами и функциями загрузки
|
||||||
|
2. Создать `in/config_files/` с `.gin` файлами
|
||||||
|
3. Адаптировать `augment_dataset.py` — заменить argparse на gin-загрузку
|
||||||
|
4. Адаптировать `augmentor/models.py` — model IDs из конфига
|
||||||
|
5. Адаптировать `augmentor/inference.py` — параметры из конфига
|
||||||
|
6. Адаптировать `augmentor/dataset.py` — принимать конфиг-объекты
|
||||||
|
7. Удалить старый `config.py` (dataclass-версию)
|
||||||
|
8. Проверить, что `depth_edges_gen.py` и `depth_anything_v2_generate.py` используют новые конфиги
|
||||||
|
9. Удалить или адаптировать `pipeline_256.py`
|
||||||
200
config_loader_reference.py
Normal file
200
config_loader_reference.py
Normal file
@@ -0,0 +1,200 @@
|
|||||||
|
"""Centralized gin-config loader for multi-config pipelines.
|
||||||
|
|
||||||
|
Reference implementation of the recommended pattern:
|
||||||
|
- One function `load_all_configs()` parses ALL .gin files at once
|
||||||
|
- `gin.clear_config()` resets global state before loading
|
||||||
|
- Individual `get_*_cfg()` kept ONLY for isolated testing
|
||||||
|
|
||||||
|
This file serves as a TEMPLATE — copy and adapt for each project.
|
||||||
|
"""
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import logging
|
||||||
|
from pathlib import Path
|
||||||
|
from typing import Any
|
||||||
|
|
||||||
|
import gin
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# Import config classes (adapt to your project)
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# from conf.pipeline_conf import PipelineConfig
|
||||||
|
# from conf.hardware_conf import HardwareConfig
|
||||||
|
# from conf.models_conf import ModelsConfig
|
||||||
|
# from conf.input_conf import InputConfig
|
||||||
|
# from conf.seg_conf import SegConfig
|
||||||
|
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# Central loader (PRODUCTION USE)
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
def load_all_configs(path2cfg: str) -> dict[str, Any]:
|
||||||
|
"""Parse ALL .gin files at once and instantiate all config objects.
|
||||||
|
|
||||||
|
This is the **only** function that should call gin.parse in production.
|
||||||
|
It clears gin global state first, then loads all .gin files in sorted
|
||||||
|
order, then creates config instances from the fully-populated state.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
path2cfg: Path to config directory (WITH trailing slash).
|
||||||
|
Example: ``"/home/servml/project/in/config_files/"``
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Dictionary with config objects keyed by short name::
|
||||||
|
|
||||||
|
{
|
||||||
|
"pipeline": PipelineConfig(),
|
||||||
|
"hardware": HardwareConfig(),
|
||||||
|
"models": ModelsConfig(),
|
||||||
|
"input": InputConfig(),
|
||||||
|
"seg": SegConfig(),
|
||||||
|
}
|
||||||
|
|
||||||
|
Raises:
|
||||||
|
FileNotFoundError: If path2cfg does not exist or contains no .gin files.
|
||||||
|
|
||||||
|
Example::
|
||||||
|
|
||||||
|
configs = load_all_configs("/home/servml/project/in/config_files/")
|
||||||
|
run_pipeline(configs["pipeline"], configs["hardware"], ...)
|
||||||
|
"""
|
||||||
|
cfg_dir = Path(path2cfg)
|
||||||
|
if not cfg_dir.is_dir():
|
||||||
|
raise FileNotFoundError(f"Config directory not found: {cfg_dir}")
|
||||||
|
|
||||||
|
gin_files = sorted(cfg_dir.glob("*.gin"))
|
||||||
|
if not gin_files:
|
||||||
|
raise FileNotFoundError(f"No .gin files in {cfg_dir}")
|
||||||
|
|
||||||
|
# CRITICAL: clear global gin state before loading.
|
||||||
|
# Without this, parameters from previous calls accumulate silently.
|
||||||
|
gin.clear_config()
|
||||||
|
|
||||||
|
# Parse ALL .gin files in one call — order is alphabetical (sorted).
|
||||||
|
gin.parse_config_files_and_bindings(
|
||||||
|
config_files=[str(f) for f in gin_files],
|
||||||
|
bindings=[], # No CLI overrides; add if needed.
|
||||||
|
)
|
||||||
|
logger.info("Loaded %d gin files from %s", len(gin_files), cfg_dir)
|
||||||
|
|
||||||
|
# Instantiate all configs from the fully-populated gin state.
|
||||||
|
# Each class picks its parameters from gin via @gin.configurable.
|
||||||
|
configs = {
|
||||||
|
"pipeline": PipelineConfig(),
|
||||||
|
"hardware": HardwareConfig(),
|
||||||
|
"models": ModelsConfig(),
|
||||||
|
"input": InputConfig(),
|
||||||
|
"seg": SegConfig(),
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.info("Created %d config objects: %s", len(configs), list(configs.keys()))
|
||||||
|
return configs
|
||||||
|
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# Individual loaders (TESTING / DEBUGGING ONLY)
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# Use these when you need a single config in isolation (unit tests, notebooks).
|
||||||
|
# ALWAYS call gin.clear_config() first to avoid state leaks.
|
||||||
|
|
||||||
|
def get_pipeline_cfg(path2cfg: str) -> Any: # -> PipelineConfig
|
||||||
|
"""Load ONLY pipeline config (for isolated testing)."""
|
||||||
|
gin.clear_config()
|
||||||
|
gin.parse_config_file(f"{path2cfg}pipeline.gin")
|
||||||
|
return PipelineConfig()
|
||||||
|
|
||||||
|
|
||||||
|
def get_hardware_cfg(path2cfg: str) -> Any: # -> HardwareConfig
|
||||||
|
"""Load ONLY hardware config (for isolated testing)."""
|
||||||
|
gin.clear_config()
|
||||||
|
gin.parse_config_file(f"{path2cfg}hardware.gin")
|
||||||
|
return HardwareConfig()
|
||||||
|
|
||||||
|
|
||||||
|
def get_models_cfg(path2cfg: str) -> Any: # -> ModelsConfig
|
||||||
|
"""Load ONLY models config (for isolated testing)."""
|
||||||
|
gin.clear_config()
|
||||||
|
gin.parse_config_file(f"{path2cfg}models.gin")
|
||||||
|
return ModelsConfig()
|
||||||
|
|
||||||
|
|
||||||
|
def get_input_cfg(path2cfg: str) -> Any: # -> InputConfig
|
||||||
|
"""Load ONLY input config (for isolated testing)."""
|
||||||
|
gin.clear_config()
|
||||||
|
gin.parse_config_file(f"{path2cfg}input.gin")
|
||||||
|
return InputConfig()
|
||||||
|
|
||||||
|
|
||||||
|
def get_seg_cfg(path2cfg: str) -> Any: # -> SegConfig
|
||||||
|
"""Load ONLY segmentation config (for isolated testing)."""
|
||||||
|
gin.clear_config()
|
||||||
|
gin.parse_config_file(f"{path2cfg}segmentation.gin")
|
||||||
|
return SegConfig()
|
||||||
|
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# Project root discovery
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
# Markers that indicate the project root directory.
|
||||||
|
# Checked in order: first match wins.
|
||||||
|
_ROOT_MARKERS = ("pyproject.toml", ".git", "in")
|
||||||
|
|
||||||
|
|
||||||
|
def get_proj_dir() -> str:
|
||||||
|
"""Return project root directory with trailing slash.
|
||||||
|
|
||||||
|
Walks up from this file's location until a directory containing
|
||||||
|
one of the root markers is found. More robust than the legacy
|
||||||
|
``split('src')[0]`` approach which breaks when ``src`` appears
|
||||||
|
elsewhere in the path (e.g. ``/home/user/resources/project/src/``).
|
||||||
|
|
||||||
|
Root markers (checked in order): ``pyproject.toml``, ``.git``, ``in/``.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Absolute path to project root with trailing ``/``.
|
||||||
|
|
||||||
|
Raises:
|
||||||
|
RuntimeError: If no marker is found within 10 parent levels.
|
||||||
|
"""
|
||||||
|
current = Path(__file__).resolve().parent
|
||||||
|
for _ in range(10):
|
||||||
|
if any((current / marker).exists() for marker in _ROOT_MARKERS):
|
||||||
|
return str(current) + "/"
|
||||||
|
current = current.parent
|
||||||
|
raise RuntimeError(
|
||||||
|
f"Cannot find project root: none of {_ROOT_MARKERS} found "
|
||||||
|
f"in ancestors of {Path(__file__).resolve()}"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def main() -> None:
|
||||||
|
"""Template entry point: load all configs -> run pipeline."""
|
||||||
|
proj_dir = get_proj_dir()
|
||||||
|
path2cfg = f"{proj_dir}in/config_files/"
|
||||||
|
|
||||||
|
configs = load_all_configs(path2cfg)
|
||||||
|
|
||||||
|
# Unpack and pass explicitly to pipeline:
|
||||||
|
# run_pipeline(
|
||||||
|
# configs["pipeline"],
|
||||||
|
# configs["hardware"],
|
||||||
|
# configs["models"],
|
||||||
|
# configs["input"],
|
||||||
|
# configs["seg"],
|
||||||
|
# )
|
||||||
|
|
||||||
|
# Print loaded configs for verification:
|
||||||
|
for name, cfg in configs.items():
|
||||||
|
print(f"\n[{name}]")
|
||||||
|
for k, v in vars(cfg).items():
|
||||||
|
print(f" {k} = {v}")
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
101
gin-parse.md
Normal file
101
gin-parse.md
Normal file
@@ -0,0 +1,101 @@
|
|||||||
|
|
||||||
|
|
||||||
|
Хороший вопрос. Вижу проблему в текущем паттерне — каждый `get_*_cfg()` вызывает `gin.parse_config_file()` отдельно, а gin имеет **глобальное состояние**. Если вызвать два загрузчика подряд — второй `gin.parse_config_file()` **добавляет** параметры к первому, не очищая.
|
||||||
|
|
||||||
|
## Проблемы текущего подхода
|
||||||
|
|
||||||
|
```python
|
||||||
|
# Текущий паттерн — каждый загрузчик парсит свой .gin:
|
||||||
|
fdir_conf = get_fdir_cfg(path2cfg) # gin.parse_config_file("file_dir.gin")
|
||||||
|
input_conf = get_inputval_cfg(path2cfg) # gin.parse_config_file("input.gin")
|
||||||
|
train_conf = get_train_cfg(path2cfg) # gin.parse_config_file("train.gin")
|
||||||
|
```
|
||||||
|
|
||||||
|
|Проблема|Почему|
|
||||||
|
|:--|:--|
|
||||||
|
|**Gin state accumulates**|Второй `parse_config_file` не очищает первый — параметры накапливаются|
|
||||||
|
|**Порядок зависимость**|Если `input.gin` случайно переопределит параметр из `file_dir.gin` — тихая ошибка|
|
||||||
|
|**Нет единой точки загрузки**|Разбросанные `gin.parse_config_file()` в каждом файле conf/|
|
||||||
|
|**Дублирование кода**|Каждый `get_*_cfg()` — одинаковые 3 строки|
|
||||||
|
|
||||||
|
## Рекомендуемое решение
|
||||||
|
|
||||||
|
**Один раз загрузить все .gin файлы, один раз создать все конфиги:**
|
||||||
|
|
||||||
|
```python
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import gin
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
|
||||||
|
def load_all_configs(path2cfg: str) -> dict[str, object]:
|
||||||
|
"""Load all gin configs at once and return all config objects.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
path2cfg: Path to config directory (with trailing slash).
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Dict with config objects keyed by name.
|
||||||
|
"""
|
||||||
|
gin.clear_config() # Reset global state — critical!
|
||||||
|
|
||||||
|
# Parse ALL .gin files in one call:
|
||||||
|
gin_files = sorted(Path(path2cfg).glob("*.gin"))
|
||||||
|
gin.parse_config_files_and_bindings(
|
||||||
|
[str(f) for f in gin_files],
|
||||||
|
bindings=[], # No CLI overrides
|
||||||
|
)
|
||||||
|
|
||||||
|
# Instantiate all configs (gin state is now fully populated):
|
||||||
|
return {
|
||||||
|
"pipeline": PipelineConfig(),
|
||||||
|
"hardware": HardwareConfig(),
|
||||||
|
"models": ModelsConfig(),
|
||||||
|
"input": InputConfig(),
|
||||||
|
"seg": SegConfig(),
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Использование в main:**
|
||||||
|
|
||||||
|
```python
|
||||||
|
def main() -> None:
|
||||||
|
proj_dir = get_proj_dir()
|
||||||
|
path2cfg = f"{proj_dir}in/config_files/"
|
||||||
|
|
||||||
|
configs = load_all_configs(path2cfg)
|
||||||
|
|
||||||
|
run_pipeline(
|
||||||
|
configs["pipeline"],
|
||||||
|
configs["hardware"],
|
||||||
|
configs["models"],
|
||||||
|
configs["input"],
|
||||||
|
configs["seg"],
|
||||||
|
)
|
||||||
|
```
|
||||||
|
|
||||||
|
## Сохранить ли отдельные `get_*_cfg()` ?
|
||||||
|
|
||||||
|
**Да, но как обёртки для тестирования** (когда нужен один конфиг в изоляции):
|
||||||
|
|
||||||
|
```python
|
||||||
|
def get_hardware_cfg(path2cfg: str) -> HardwareConfig:
|
||||||
|
"""Load ONLY hardware config (for testing/debugging)."""
|
||||||
|
gin.clear_config()
|
||||||
|
gin.parse_config_file(f"{path2cfg}hardware.gin")
|
||||||
|
return HardwareConfig()
|
||||||
|
```
|
||||||
|
|
||||||
|
Ключевое: добавить `gin.clear_config()` перед каждым `parse_config_file()`.
|
||||||
|
|
||||||
|
## Итого: что изменить
|
||||||
|
|
||||||
|
|Было|Стало|
|
||||||
|
|:--|:--|
|
||||||
|
|Каждый `get_*_cfg()` парсит свой .gin|**`load_all_configs()`** парсит все .gin за один вызов|
|
||||||
|
|Нет `gin.clear_config()`|**Обязательный** `gin.clear_config()` перед загрузкой|
|
||||||
|
|5 отдельных вызовов в main|**1 вызов** `load_all_configs()`|
|
||||||
|
|Отдельные `get_*_cfg()` для продакшена|Оставить **только для тестирования**|
|
||||||
|
|
||||||
|
Хотите, чтобы я создал файл `src/conf/config_loader.py` с этим паттерном и обновил `REQUIREMENTS_GIN_STYLE.md`?
|
||||||
163
Переход от `argparse` и `dataclass` к `gin-config`.md
Normal file
163
Переход от `argparse` и `dataclass` к `gin-config`.md
Normal file
@@ -0,0 +1,163 @@
|
|||||||
|
Отличная инициатива. Переход от `argparse` и `dataclass` к `gin-config` в научно-исследовательских проектах по глубокому обучению — это мощный шаг, который сильно упрощает управление гиперпараметрами и версионирование экспериментов.
|
||||||
|
|
||||||
|
Ваш подход в `REQUIREMENTS_GIN_STYLE.md`, где вы используете `gin` для конфигурации классов, но **явно передаете объекты конфигов** (избегая глобального состояния `gin`), — это лучшая практика, которая решает главную проблему этого фреймворка.
|
||||||
|
|
||||||
|
Ниже представлены разработанный **SKILL (набор правил)**, **Prompt-шаблон** для ИИ, а также **сравнение конфигураций** с финальными рекомендациями.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 1. SKILL: Стандарт написания кода для DL/CV исследований (Cross-View Geo-Localization)
|
||||||
|
|
||||||
|
Этот свод правил объединяет ваши базовые требования со спецификой задачи кросс-вью геолокализации и выбранным стилем `gin-config`.
|
||||||
|
|
||||||
|
**Общие требования к Python-коду:**
|
||||||
|
|
||||||
|
- **Версии:** Python 3.10+, PyTorch 2.x.
|
||||||
|
|
||||||
|
- **Импорты:** Обязательно `from __future__ import annotations` первой строкой в каждом файле.
|
||||||
|
|
||||||
|
- **Язык:** Весь код, переменные и комментарии строго на **английском языке**.
|
||||||
|
|
||||||
|
- **Типизация (Type Hints):** Строгая типизация для всех аргументов функций и возвращаемых значений (`-> None`, `-> torch.Tensor` и т.д.).
|
||||||
|
|
||||||
|
- **Документация:** Docstrings в формате **Google-style** для всех публичных классов и функций.
|
||||||
|
|
||||||
|
|
||||||
|
**Архитектура и управление ресурсами (CV/DL специфика):**
|
||||||
|
|
||||||
|
- **Воспроизводимость:** Обязательная фиксация seed (`torch.manual_seed`, `np.random.seed`) в главном скрипте.
|
||||||
|
|
||||||
|
- **Управление VRAM:** Из-за ограничений в 24 GB VRAM (RTX 4090) модели должны загружаться последовательно (`sequential model loading`). После использования модель выгружается из памяти (`del model`, `gc.collect()`, `torch.cuda.empty_cache()`).
|
||||||
|
|
||||||
|
- **Отказоустойчивость:** Запись файлов (например, `.npy`) должна быть атомарной (сохранение во временный файл `tmp` с последующим `os.replace`), чтобы пайплайн мог безопасно возобновить работу (`--resume`) после сбоя.
|
||||||
|
|
||||||
|
- **Разделение логики:** Строгое разделение на `dataset` (загрузка данных), `models` (инициализация), `inference` (прогон батчей) и `io_utils` (сохранение).
|
||||||
|
|
||||||
|
|
||||||
|
**Конфигурация (Gin-Config Style):**
|
||||||
|
|
||||||
|
- **Инкапсуляция:** Конфигурация осуществляется через классы, декорированные `@gin.configurable`. Запрещено использовать `dataclass` совместно с gin.
|
||||||
|
|
||||||
|
- **Инициализация:** Все параметры должны иметь значения по умолчанию в методе `__init__` класса.
|
||||||
|
|
||||||
|
- **Формат .gin:** Плоский формат файлов (одна строка — один параметр `ClassName.param = value`). Запрещены сложные конструкции gin (макросы, ссылки, `gin.constant()`).
|
||||||
|
|
||||||
|
- **Логика загрузки:** Использование паттерна функций-загрузчиков `get_<name>_cfg(path2cfg)`, которые вызывают `gin.parse_config_file()` и возвращают экземпляр конфигурационного класса.
|
||||||
|
|
||||||
|
- **Явная передача:** Объекты конфигурации передаются в функции пайплайна явно через аргументы, использование глобального состояния gin для получения параметров внутри функций запрещено.
|
||||||
|
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 2. Prompt-шаблон для генерации кода (System Prompt)
|
||||||
|
|
||||||
|
Вы можете использовать этот промпт для постановки задач нейросети (например, при рефакторинге старого кода или написании новых модулей).
|
||||||
|
|
||||||
|
Markdown
|
||||||
|
|
||||||
|
```
|
||||||
|
You are an expert Machine Learning Engineer and Python Developer specializing in Computer Vision and Deep Learning (specifically Cross-View Geo-Localization).
|
||||||
|
Your task is to write, refactor, or review Python code according to the strict guidelines defined below.
|
||||||
|
|
||||||
|
### THE SKILL (Coding Standards)
|
||||||
|
|
||||||
|
1. **Environment & Language**:
|
||||||
|
- Target Python 3.10+ and PyTorch 2.x.
|
||||||
|
- Start all files with `from __future__ import annotations`.
|
||||||
|
- All code, variable names, and comments MUST be in English.
|
||||||
|
|
||||||
|
2. **Typing & Documentation**:
|
||||||
|
- Use strict type hints for all function arguments and return types.
|
||||||
|
- Use Google-style docstrings for all classes and functions.
|
||||||
|
|
||||||
|
3. **Configuration (Gin-Config Strict Pattern)**:
|
||||||
|
- Use `gin-config` for managing hyperparameters. Do NOT use `argparse` or `dataclass`.
|
||||||
|
- Apply `@gin.configurable` ONLY to classes, not functions.
|
||||||
|
- Provide default values for all parameters inside the class `__init__` method. Store parameters as instance attributes (`self.param = param`).
|
||||||
|
- Define dedicated loader functions named `get_<name>_cfg(path2cfg)` that call `gin.parse_config_file(gin_file)` and return the instantiated class object.
|
||||||
|
- Configuration objects must be passed explicitly as arguments to functions/pipelines. Never rely on global gin state inside execution logic.
|
||||||
|
|
||||||
|
4. **Deep Learning / CV Best Practices**:
|
||||||
|
- **VRAM Management**: Models must be explicitly loaded and unloaded (`del model`, `torch.cuda.empty_cache()`) when doing sequential multi-model processing to fit within 24GB VRAM limits.
|
||||||
|
- **Reproducibility**: Ensure deterministic behavior (e.g., set seeds using `torch.manual_seed`, `np.random.seed`).
|
||||||
|
- **Fault Tolerance**: Use atomic file writes (save to `.tmp` then `os.replace`) to allow safe resumption of interrupted pipelines.
|
||||||
|
|
||||||
|
### Task Description:
|
||||||
|
[INSERT YOUR SPECIFIC TASK HERE. Example: Refactor the provided `augment_dataset.py` script to remove argparse and integrate the Gin-Config Strict Pattern described above using the 5 config classes.]
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 3. Примеры конфигов и сравнение подходов
|
||||||
|
|
||||||
|
Для вашей задачи (автоматическое аннотирование датасета генераторами) нам нужно конфигурировать пути, параметры железа, идентификаторы моделей и настройки аугментации.
|
||||||
|
|
||||||
|
#### Вариант А: Обычный `argparse` + `dataclass` (То, что у вас было)
|
||||||
|
|
||||||
|
- **Плюсы:** Не требует сторонних библиотек, отлично подходит для простых скриптов.
|
||||||
|
|
||||||
|
- **Минусы:** Когда параметров становится больше 15-20 (как у вас в 5 классах), командная строка превращается в хаос (`python script.py --depth-model xyz --use-fp16 --batch-size 8 ...`). Сложно отслеживать, с какими параметрами был запущен эксперимент неделю назад.
|
||||||
|
|
||||||
|
|
||||||
|
#### Вариант Б: YAML + Hydra / OmegaConf
|
||||||
|
|
||||||
|
YAML
|
||||||
|
|
||||||
|
```
|
||||||
|
# hardware.yaml
|
||||||
|
profile_name: rtx4090
|
||||||
|
use_fp16: true
|
||||||
|
batch_size: 8
|
||||||
|
num_workers: 4
|
||||||
|
reserve_gb: 2.0
|
||||||
|
```
|
||||||
|
|
||||||
|
- **Плюсы:** Иерархическая структура, легко читается, де-факто стандарт индустрии для многих фреймворков.
|
||||||
|
|
||||||
|
- **Минусы:** Требует написания шаблонного кода для парсинга (чтение файла, конвертация словаря в `dataclass`). Если использовать Hydra, то она "захватывает" архитектуру приложения (требует декоратора `@hydra.main`), что усложняет дебаг.
|
||||||
|
|
||||||
|
|
||||||
|
#### Вариант В: Ваш плоский `gin-config` стиль
|
||||||
|
|
||||||
|
Code snippet
|
||||||
|
|
||||||
|
```
|
||||||
|
# hardware.gin
|
||||||
|
HardwareConfig.profile_name = 'rtx4090'
|
||||||
|
HardwareConfig.use_fp16 = True
|
||||||
|
HardwareConfig.batch_size = 8
|
||||||
|
HardwareConfig.num_workers = 4
|
||||||
|
HardwareConfig.reserve_gb = 2.0
|
||||||
|
```
|
||||||
|
|
||||||
|
Python
|
||||||
|
|
||||||
|
```
|
||||||
|
# Загрузка
|
||||||
|
@gin.configurable
|
||||||
|
class HardwareConfig:
|
||||||
|
def __init__(self, profile_name='rtx4090', ...): ...
|
||||||
|
|
||||||
|
def get_hardware_cfg(path2cfg):
|
||||||
|
gin.parse_config_file(f"{path2cfg}hardware.gin")
|
||||||
|
return HardwareConfig()
|
||||||
|
```
|
||||||
|
|
||||||
|
- **Плюсы:** * **Нулевой бойлерплейт при парсинге:** Вы просто парсите файл, и gin автоматически подставляет значения в параметры `__init__` класса.
|
||||||
|
|
||||||
|
- **Легкость логирования:** Сохранить весь конфиг эксперимента можно вызвав `gin.config_str()`.
|
||||||
|
|
||||||
|
- **Защита от опечаток:** Если вы напишете в конфиге `HardwarConfig.batch = 8`, `gin` упадет с ошибкой еще до запуска тяжелых вычислений, потому что такого аргумента нет.
|
||||||
|
|
||||||
|
- **Минусы:**
|
||||||
|
|
||||||
|
- Не поддерживает вложенные иерархии (сложно группировать очень сложные структуры, но для ваших 5 конфигов это не критично).
|
||||||
|
|
||||||
|
- Многие используют `gin` как антипаттерн "глобального состояния" (когда функции по всему коду неявно тянут параметры из эфира).
|
||||||
|
|
||||||
|
|
||||||
|
### 📝 Финальная рекомендация
|
||||||
|
|
||||||
|
**Строго придерживайтесь вашего `REQUIREMENTS_GIN_STYLE.md`.** Вы выбрали идеальный компромисс: вы используете `gin-config` для того, в чем он силен (красивая и лаконичная привязка параметров текстового файла к аргументам класса без парсеров словарей), но при этом **запрещаете его главную слабость** — неявное глобальное состояние. Загрузив параметры в объект конфигурации (например, `hardware_conf = get_hardware_cfg(...)`), вы дальше передаете его в пайплайн явно. Это делает код тестируемым, читаемым и идеально подходящим для глубокого обучения.
|
||||||
|
|
||||||
|
Подсказать ли вам, как должен выглядеть итоговый рефакторинг `augment_dataset.py` с применением этих правил?
|
||||||
71
Правила написания Python-кода (Gin-Config Strict Pattern).md
Normal file
71
Правила написания Python-кода (Gin-Config Strict Pattern).md
Normal file
@@ -0,0 +1,71 @@
|
|||||||
|
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
paths:
|
||||||
|
|
||||||
|
- "**/*.py"
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# Правила написания Python-кода (Gin-Config Strict Pattern)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## Окружение
|
||||||
|
|
||||||
|
- Python 3.10+, PyTorch 2.x
|
||||||
|
|
||||||
|
- `from __future__ import annotations` — первая строка каждого файла
|
||||||
|
|
||||||
|
- Код, переменные, комментарии — на **английском языке**
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## Типизация и документация
|
||||||
|
|
||||||
|
- Strict type hints на всех аргументах и return types
|
||||||
|
|
||||||
|
- Google-style docstrings на всех публичных классах и функциях
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## Конфигурация: gin-config (не argparse, не dataclass)
|
||||||
|
|
||||||
|
- `@gin.configurable` только на классах
|
||||||
|
|
||||||
|
- Все параметры со значениями по умолчанию в `__init__`
|
||||||
|
|
||||||
|
- Загрузка: `get_<name>_cfg(path2cfg)` → `gin.parse_config_file()` → return instance
|
||||||
|
|
||||||
|
- Передача конфигов **явно** через аргументы (не через глобальный gin state)
|
||||||
|
|
||||||
|
- Формат .gin: плоский (`ClassName.param = value`), без макросов
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## DL/CV практики
|
||||||
|
|
||||||
|
- VRAM: sequential model loading, `del model` + `gc.collect()` + `empty_cache()` после использования
|
||||||
|
|
||||||
|
- Reproducibility: `torch.manual_seed(42)`, `np.random.seed(42)`
|
||||||
|
|
||||||
|
- Atomic writes: temp file + `os.replace()` для .npy/.json
|
||||||
|
|
||||||
|
- Inference: `@torch.inference_mode()`, результат на `.cpu()`
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## Структура
|
||||||
|
|
||||||
|
- `in/config_files/` — .gin файлы (1 файл = 1 конфиг-класс)
|
||||||
|
|
||||||
|
- `src/conf/` — конфиг-классы + get_*_cfg() загрузчики
|
||||||
|
|
||||||
|
- `src/augmentor/` или `src/` — бизнес-логика (dataset, models, inference, io_utils)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
Полный стандарт: skill `/code-style`
|
||||||
99
Рекомендуемые gin-config категории.md
Normal file
99
Рекомендуемые gin-config категории.md
Normal file
@@ -0,0 +1,99 @@
|
|||||||
|
В REQUIREMENTS_GIN_STYLE.md уже есть 5 конфигов. Но для задачи аннотирования World-UAV я бы рекомендовал **именно эти 5** — они хорошо разделены по ответственности. Вот мой анализ:
|
||||||
|
|
||||||
|
## Рекомендуемые gin-config категории
|
||||||
|
|
||||||
|
**5 конфигов — оптимальное число.** Каждый отвечает за одну ось изменчивости:
|
||||||
|
|
||||||
|
|#|Конфиг|Ось изменчивости|Когда меняется|
|
||||||
|
|:--|:--|:--|:--|
|
||||||
|
|1|**PipelineConfig**|Что обрабатывать и куда сохранять|При смене датасета или запуске на новом subset'е|
|
||||||
|
|2|**HardwareConfig**|На чём запускать|При переходе между GPU (4090 → A100 → Jetson)|
|
||||||
|
|3|**ModelsConfig**|Какими моделями генерировать|При замене DA3→DA2, SegEarth→SegFormer|
|
||||||
|
|4|**InputConfig**|Как предобрабатывать вход|При изменении resolution (256→384), edge метода|
|
||||||
|
|5|**SegConfig**|Что сегментировать|При изменении OV-промптов для другого региона|
|
||||||
|
|
||||||
|
**Принцип разделения:** если два параметра меняются **вместе** — в одном конфиге. Если **независимо** — в разных. Например: `batch_size` и `use_fp16` меняются вместе (при смене GPU), поэтому оба в `HardwareConfig`. А `prompts` и `image_size` независимы — разные конфиги.
|
||||||
|
|
||||||
|
### Содержимое каждого конфига:
|
||||||
|
|
||||||
|
**1. `pipeline.gin` → PipelineConfig**
|
||||||
|
|
||||||
|
```gin
|
||||||
|
# ЧТО и КУДА
|
||||||
|
PipelineConfig.input_root = '/data/UAV-GeoLoc'
|
||||||
|
PipelineConfig.output_root = '/data/UAV-GeoLoc-aug'
|
||||||
|
PipelineConfig.stages = ['depth', 'edges', 'segmentation']
|
||||||
|
PipelineConfig.subset = 'Rot' # Country | Terrain | Rot | None
|
||||||
|
PipelineConfig.source = None # query | db | None
|
||||||
|
PipelineConfig.save_npy = True
|
||||||
|
PipelineConfig.save_vis = True
|
||||||
|
PipelineConfig.save_concat = False # 6ch concat (экономия 1.4TB)
|
||||||
|
PipelineConfig.resume = True # пропуск обработанных
|
||||||
|
PipelineConfig.log_level = 'INFO'
|
||||||
|
```
|
||||||
|
|
||||||
|
**2. `hardware.gin` → HardwareConfig**
|
||||||
|
|
||||||
|
```gin
|
||||||
|
# НА ЧЁМ
|
||||||
|
HardwareConfig.profile_name = 'rtx4090'
|
||||||
|
HardwareConfig.total_ram_gb = 24.0
|
||||||
|
HardwareConfig.reserve_gb = 2.0
|
||||||
|
HardwareConfig.use_fp16 = True
|
||||||
|
HardwareConfig.batch_size = 32 # None = auto
|
||||||
|
HardwareConfig.num_workers = 4
|
||||||
|
```
|
||||||
|
|
||||||
|
**3. `models.gin` → ModelsConfig**
|
||||||
|
|
||||||
|
```gin
|
||||||
|
# КАКИМИ МОДЕЛЯМИ
|
||||||
|
ModelsConfig.depth_model_id = 'depth-anything/DA3-BASE'
|
||||||
|
ModelsConfig.depth_fallback_id = 'depth-anything/Depth-Anything-V2-Large-hf'
|
||||||
|
ModelsConfig.seg_model_type = 'segearth-ov3'
|
||||||
|
ModelsConfig.seg_fallback_type = 'segformer-b5'
|
||||||
|
ModelsConfig.seg_fallback_id = 'nvidia/segformer-b5-finetuned-ade-640-640'
|
||||||
|
```
|
||||||
|
|
||||||
|
**4. `input.gin` → InputConfig**
|
||||||
|
|
||||||
|
```gin
|
||||||
|
# КАК ПРЕДОБРАБАТЫВАТЬ
|
||||||
|
InputConfig.image_size = 256
|
||||||
|
InputConfig.sobel_kernel_size = 3
|
||||||
|
InputConfig.edge_normalize = True
|
||||||
|
InputConfig.imagenet_mean = [0.485, 0.456, 0.406]
|
||||||
|
InputConfig.imagenet_std = [0.229, 0.224, 0.225]
|
||||||
|
```
|
||||||
|
|
||||||
|
**5. `segmentation.gin` → SegConfig**
|
||||||
|
|
||||||
|
```gin
|
||||||
|
# ЧТО СЕГМЕНТИРОВАТЬ
|
||||||
|
SegConfig.prompts = ['background', 'building', 'road', 'tree', 'water', 'vegetation', 'bare soil', 'grass', 'vehicle', 'shadow']
|
||||||
|
SegConfig.threshold = 0.3
|
||||||
|
SegConfig.default_resolution = 1008 # SAM3 input size
|
||||||
|
```
|
||||||
|
|
||||||
|
### Что НЕ стоит выносить в gin
|
||||||
|
|
||||||
|
|Параметр|Почему НЕ в gin|
|
||||||
|
|:--|:--|
|
||||||
|
|`INCOMPLETE_SCENES` (16 сцен)|Привязано к конкретному датасету, не параметр — константа в коде|
|
||||||
|
|`EXCLUDE_DIRS`|Инфраструктурная константа, не меняется между экспериментами|
|
||||||
|
|`EXTENSIONS` (.png, .jpg)|Константа формата данных|
|
||||||
|
|Sobel-ядра (матрицы)|Математическая константа, не параметр|
|
||||||
|
|Palette seed (42)|Определённость визуализации, не влияет на результат|
|
||||||
|
|
||||||
|
### Когда какой .gin менять
|
||||||
|
|
||||||
|
|Сценарий|Какие .gin трогать|
|
||||||
|
|:--|:--|
|
||||||
|
|Запуск на Rot для smoke test|`pipeline.gin` (subset='Rot')|
|
||||||
|
|Переход на A100|`hardware.gin` (total_ram_gb=80, batch_size=128)|
|
||||||
|
|Замена DA3 на DA V2|`models.gin` (depth_model_id)|
|
||||||
|
|Обработка только query (дрон)|`pipeline.gin` (source='query')|
|
||||||
|
|Добавить класс "parking" в сегментацию|`segmentation.gin` (prompts)|
|
||||||
|
|Изменить resolution на 384×384|`input.gin` (image_size=384)|
|
||||||
|
|
||||||
|
Эта схема из REQUIREMENTS_GIN_STYLE.md корректна — я рекомендую её без изменений.
|
||||||
439
Стандарт написания кода для DL CV исследований (CVGL).md
Normal file
439
Стандарт написания кода для DL CV исследований (CVGL).md
Normal file
@@ -0,0 +1,439 @@
|
|||||||
|
---
|
||||||
|
|
||||||
|
name: code-style
|
||||||
|
|
||||||
|
description: "Enforce gin-config coding standards for DL/CV research code (Cross-View Geo-Localization). Auto-activates when writing or reviewing Python code. Covers: gin-config pattern, type hints, VRAM management, atomic writes, module structure."
|
||||||
|
|
||||||
|
user-invocable: true
|
||||||
|
|
||||||
|
allowed-tools: Read Write Edit Glob Grep Bash
|
||||||
|
|
||||||
|
argument-hint: "[write|review|refactor] [file-or-module-path]"
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# Стандарт написания кода для DL/CV исследований (CVGL)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
Ты — Machine Learning Engineer, специализирующийся на Computer Vision и Deep Learning (Cross-View Geo-Localization). Пиши, рефактори или ревью код строго по правилам ниже.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## Режимы
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
- `/code-style write <path>` — написать новый модуль по стандарту
|
||||||
|
|
||||||
|
- `/code-style review <path>` — проверить существующий код на соответствие
|
||||||
|
|
||||||
|
- `/code-style refactor <path>` — привести существующий код к стандарту
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## 1. Окружение и язык
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
- **Python 3.10+**, **PyTorch 2.x**
|
||||||
|
|
||||||
|
- Первая строка каждого файла: `from __future__ import annotations`
|
||||||
|
|
||||||
|
- Весь код, переменные, комментарии — **строго на английском**
|
||||||
|
|
||||||
|
- Импорты: stdlib → third-party → local, разделены пустыми строками
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## 2. Типизация и документация
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
- **Strict type hints** на всех аргументах функций и return types
|
||||||
|
|
||||||
|
- `-> None`, `-> torch.Tensor`, `-> dict[str, Any]` и т.д.
|
||||||
|
|
||||||
|
- **Google-style docstrings** на всех публичных классах и функциях:
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
```python
|
||||||
|
|
||||||
|
def infer_depth(model: nn.Module, images: torch.Tensor) -> torch.Tensor:
|
||||||
|
|
||||||
|
"""Run monocular depth estimation on a batch.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
Args:
|
||||||
|
|
||||||
|
model: Loaded depth model (DA3 or DA V2).
|
||||||
|
|
||||||
|
images: Input RGB tensor [B, 3, H, W] float32 [0, 1].
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
|
||||||
|
Depth maps [B, 1, H, W] float32 [0, 1], per-frame normalized.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
Raises:
|
||||||
|
|
||||||
|
RuntimeError: If model inference fails.
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## 3. Конфигурация: Gin-Config Strict Pattern
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
### 3.1 Классы конфигурации
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
- Декоратор `@gin.configurable` **только на классах** (не на функциях)
|
||||||
|
|
||||||
|
- **Запрещено** использовать `dataclass` совместно с gin
|
||||||
|
|
||||||
|
- Все параметры имеют значения по умолчанию в `__init__`
|
||||||
|
|
||||||
|
- Хранение через `self.param = param`
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
```python
|
||||||
|
|
||||||
|
import gin
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@gin.configurable
|
||||||
|
|
||||||
|
class HardwareConfig:
|
||||||
|
|
||||||
|
"""GPU hardware profile for the augmentation pipeline."""
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
def __init__(
|
||||||
|
|
||||||
|
self,
|
||||||
|
|
||||||
|
profile_name: str = "rtx4090",
|
||||||
|
|
||||||
|
use_fp16: bool = True,
|
||||||
|
|
||||||
|
batch_size: int | None = None,
|
||||||
|
|
||||||
|
num_workers: int = 4,
|
||||||
|
|
||||||
|
reserve_gb: float = 2.0,
|
||||||
|
|
||||||
|
) -> None:
|
||||||
|
|
||||||
|
self.profile_name = profile_name
|
||||||
|
|
||||||
|
self.use_fp16 = use_fp16
|
||||||
|
|
||||||
|
self.batch_size = batch_size
|
||||||
|
|
||||||
|
self.num_workers = num_workers
|
||||||
|
|
||||||
|
self.reserve_gb = reserve_gb
|
||||||
|
|
||||||
|
# Derived values OK in __init__:
|
||||||
|
|
||||||
|
self.total_ram_gb = 24.0 # RTX 4090
|
||||||
|
|
||||||
|
self.available_gb = self.total_ram_gb - self.reserve_gb
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
### 3.2 Функции-загрузчики
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
```python
|
||||||
|
|
||||||
|
def get_hardware_cfg(path2cfg: str) -> HardwareConfig:
|
||||||
|
|
||||||
|
"""Load hardware config from gin file."""
|
||||||
|
|
||||||
|
gin.parse_config_file(f"{path2cfg}hardware.gin")
|
||||||
|
|
||||||
|
return HardwareConfig()
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
- Имя: `get_<name>_cfg(path2cfg: str) -> <ConfigClass>`
|
||||||
|
|
||||||
|
- Принимает путь к директории с конфигами (со слешем)
|
||||||
|
|
||||||
|
- Вызывает `gin.parse_config_file()` + создаёт экземпляр
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
### 3.3 Формат .gin файлов
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
```gin
|
||||||
|
|
||||||
|
# hardware.gin
|
||||||
|
|
||||||
|
HardwareConfig.profile_name = 'rtx4090'
|
||||||
|
|
||||||
|
HardwareConfig.use_fp16 = True
|
||||||
|
|
||||||
|
HardwareConfig.batch_size = 32
|
||||||
|
|
||||||
|
HardwareConfig.num_workers = 4
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
- **Одна строка — один параметр** (`ClassName.param = value`)
|
||||||
|
|
||||||
|
- **Запрещено:** макросы, ссылки, `gin.constant()`, `gin.register()`
|
||||||
|
|
||||||
|
- Каждый `.gin` → один конфиг-класс
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
### 3.4 Передача конфигов
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
```python
|
||||||
|
|
||||||
|
def main() -> None:
|
||||||
|
|
||||||
|
path2cfg = f"{get_proj_dir()}in/config_files/"
|
||||||
|
|
||||||
|
pipeline_conf = get_pipeline_cfg(path2cfg)
|
||||||
|
|
||||||
|
hardware_conf = get_hardware_cfg(path2cfg)
|
||||||
|
|
||||||
|
run_pipeline(pipeline_conf, hardware_conf)
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
- Конфиги загружаются в `main()` через `get_*_cfg()`
|
||||||
|
|
||||||
|
- Передаются **явно** как аргументы (не через глобальный gin state)
|
||||||
|
|
||||||
|
- **Запрещён argparse** — все параметры из .gin файлов
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## 4. DL/CV практики
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
### 4.1 Управление VRAM (24 GB RTX 4090)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
```python
|
||||||
|
|
||||||
|
# Sequential model loading pattern:
|
||||||
|
|
||||||
|
model = load_model(device)
|
||||||
|
|
||||||
|
try:
|
||||||
|
|
||||||
|
process_all_images(model, dataset)
|
||||||
|
|
||||||
|
finally:
|
||||||
|
|
||||||
|
del model
|
||||||
|
|
||||||
|
gc.collect()
|
||||||
|
|
||||||
|
torch.cuda.empty_cache()
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
- Одновременно на GPU — **только 1 модель**
|
||||||
|
|
||||||
|
- После обработки — `del` + `gc.collect()` + `empty_cache()`
|
||||||
|
|
||||||
|
- FP16 по умолчанию (`torch_dtype=torch.float16`)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
### 4.2 Воспроизводимость
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
```python
|
||||||
|
|
||||||
|
torch.manual_seed(42)
|
||||||
|
|
||||||
|
np.random.seed(42)
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
- Seed = 42 (фиксированный) в главном скрипте
|
||||||
|
|
||||||
|
- Deterministic DataLoader: `shuffle=False` для inference
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
### 4.3 Атомарная запись файлов
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
```python
|
||||||
|
|
||||||
|
import tempfile, os
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
def atomic_save_npy(arr: np.ndarray, path: Path) -> None:
|
||||||
|
|
||||||
|
"""Write .npy atomically via temp file + rename."""
|
||||||
|
|
||||||
|
path.parent.mkdir(parents=True, exist_ok=True)
|
||||||
|
|
||||||
|
fd, tmp = tempfile.mkstemp(suffix=".npy.tmp", dir=path.parent)
|
||||||
|
|
||||||
|
os.close(fd)
|
||||||
|
|
||||||
|
try:
|
||||||
|
|
||||||
|
np.save(tmp, arr)
|
||||||
|
|
||||||
|
os.replace(tmp, path)
|
||||||
|
|
||||||
|
except BaseException:
|
||||||
|
|
||||||
|
if os.path.exists(tmp):
|
||||||
|
|
||||||
|
os.remove(tmp)
|
||||||
|
|
||||||
|
raise
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
- Все .npy/.json сохраняются через temp → `os.replace()`
|
||||||
|
|
||||||
|
- Позволяет безопасный `--resume` после сбоя
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
### 4.4 Inference-декоратор
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
```python
|
||||||
|
|
||||||
|
@torch.inference_mode()
|
||||||
|
|
||||||
|
def infer_batch(model: nn.Module, images: torch.Tensor, device: torch.device) -> torch.Tensor:
|
||||||
|
|
||||||
|
...
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
- Всегда `@torch.inference_mode()` (не `torch.no_grad()`)
|
||||||
|
|
||||||
|
- Результат возвращать на CPU: `.cpu()`
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## 5. Структура модулей
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
project/
|
||||||
|
|
||||||
|
├── in/config_files/ # .gin файлы (1 файл = 1 конфиг-класс)
|
||||||
|
|
||||||
|
├── src/
|
||||||
|
|
||||||
|
│ ├── conf/ # Конфиг-классы + get_*_cfg() загрузчики
|
||||||
|
|
||||||
|
│ │ ├── pipeline_conf.py
|
||||||
|
|
||||||
|
│ │ ├── hardware_conf.py
|
||||||
|
|
||||||
|
│ │ ├── models_conf.py
|
||||||
|
|
||||||
|
│ │ └── ...
|
||||||
|
|
||||||
|
│ ├── augmentor/ # Бизнес-логика (dataset, models, inference, io_utils)
|
||||||
|
|
||||||
|
│ └── main.py # Точка входа: load configs → run pipeline
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
- **Разделение:** conf / dataset / models / inference / io_utils
|
||||||
|
|
||||||
|
- **Запрещено** смешивать логику конфигурации и инференса в одном файле
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## 6. При ревью кода — чеклист
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
При `/code-style review`:
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
- [ ] `from __future__ import annotations` первой строкой?
|
||||||
|
|
||||||
|
- [ ] Все функции/методы имеют type hints?
|
||||||
|
|
||||||
|
- [ ] Google-style docstrings на публичных классах/функциях?
|
||||||
|
|
||||||
|
- [ ] `@gin.configurable` только на классах?
|
||||||
|
|
||||||
|
- [ ] Нет `dataclass` + gin?
|
||||||
|
|
||||||
|
- [ ] Нет `argparse`?
|
||||||
|
|
||||||
|
- [ ] Нет захардкоженных model ID / промптов / размеров?
|
||||||
|
|
||||||
|
- [ ] Модели выгружаются после использования?
|
||||||
|
|
||||||
|
- [ ] Файлы сохраняются атомарно (temp + replace)?
|
||||||
|
|
||||||
|
- [ ] Seed установлен?
|
||||||
|
|
||||||
|
- [ ] `@torch.inference_mode()` на inference-функциях?
|
||||||
|
|
||||||
|
- [ ] Код и комментарии на английском?
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
Подробные примеры: [reference/gin_examples.md](reference/gin_examples.md)
|
||||||
Reference in New Issue
Block a user