Files
style_code_lisad/code-style/reference/gin_examples.md
Pikaliov 98e98f591c Add code-style skill (gin-config strict pattern)
- code-style/SKILL.md — skill spec for the LISAD code-style enforcement
- code-style/reference/gin_examples.md — gin-config canonical examples

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-04 10:33:43 +03:00

7.1 KiB

Gin-Config Strict Pattern: Reference Examples

Example 1: Config class + loader + .gin file

src/conf/pipeline_conf.py

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

# 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

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: Central config loader (src/conf/config_loader.py)

from __future__ import annotations

import logging
from pathlib import Path
from typing import Any

import gin

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

logger = logging.getLogger(__name__)


def load_all_configs(path2cfg: str) -> dict[str, Any]:
    """Parse ALL .gin files at once and return all config objects.

    CRITICAL: calls gin.clear_config() to reset global state.

    Args:
        path2cfg: Path to config directory (WITH trailing slash).

    Returns:
        Dict with config objects keyed by name.
    """
    cfg_dir = Path(path2cfg)
    gin_files = sorted(cfg_dir.glob("*.gin"))

    gin.clear_config()  # MUST reset before loading
    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(),
    }


# Individual loaders — TESTING ONLY (always clear_config first):
def get_hardware_cfg(path2cfg: str) -> HardwareConfig:
    gin.clear_config()
    gin.parse_config_file(f"{path2cfg}hardware.gin")
    return HardwareConfig()

Example 4: Main entry point (uses load_all_configs)

src/main.py

from __future__ import annotations

import logging

import numpy as np
import torch

from conf.config_loader import load_all_configs
from utils.utils_file_dir import get_proj_dir

logger = logging.getLogger(__name__)


def main() -> None:
    """Entry point: load ALL configs at once, then run pipeline."""
    proj_dir = get_proj_dir()
    path2cfg = f"{proj_dir}in/config_files/"

    # ONE call loads everything:
    configs = load_all_configs(path2cfg)

    # Set seeds:
    torch.manual_seed(42)
    np.random.seed(42)

    # Pass configs explicitly:
    run_pipeline(
        configs["pipeline"],
        configs["hardware"],
        configs["models"],
        configs["input"],
        configs["seg"],
    )


if __name__ == "__main__":
    main()

Example 4: Model loading with config object

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()

Example 5: Model loading with config objects (not hardcoded IDs)

# 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