| """Typed configuration for MapGS. |
| |
| Defaults below mirror the values in the design doc (§2-§3). Override via YAML |
| (``configs/*.yaml``, with optional ``_base_`` inheritance) or dotted CLI args |
| (e.g. ``train.iters=1000 model.embed_dim=512``). |
| """ |
|
|
| from __future__ import annotations |
|
|
| import dataclasses |
| from dataclasses import dataclass, field |
| from typing import Any, get_type_hints |
| import os |
|
|
| import yaml |
|
|
|
|
| |
| |
| |
| @dataclass |
| class MapConfig: |
| crop_lateral: float = 100.0 |
| crop_longitudinal: float = 150.0 |
| ground_spacing: float = 0.5 |
| lane_spacing: float = 0.2 |
| boundary_spacing: float = 0.5 |
| ratio_ground: float = 0.6 |
| ratio_lane: float = 0.3 |
| ratio_boundary: float = 0.1 |
| n_anchors: int = 2048 |
| junction_weight: float = 3.0 |
| curvature_weight: float = 1.0 |
| ground_z_below: float = 0.15 |
|
|
|
|
| |
| |
| |
| @dataclass |
| class TokenConfig: |
| n_map: int = 2048 |
| n_free: int = 2048 |
| n_dyn_per_instance: int = 256 |
| gaussians_per_token: int = 64 |
| s0: float = 0.5 |
| s_max: float = 2.0 |
|
|
|
|
| |
| |
| |
| @dataclass |
| class ModelConfig: |
| embed_dim: int = 1024 |
| patch_size: int = 8 |
| enc_depth: int = 6 |
| dec_depth: int = 24 |
| n_heads: int = 16 |
| mlp_ratio: float = 4.0 |
| qk_norm: bool = True |
| layerscale_init: float = 1e-5 |
| shared_decoder_kv: bool = True |
| drop_final_ln: bool = True |
|
|
| |
| feature_color: bool = True |
| feature_dim: int = 128 |
| use_unet: bool = True |
|
|
| |
| causal_dynamic_mask: bool = True |
| st_fusion: bool = False |
|
|
| |
| scale_min: float = 1e-3 |
| scale_max: float = 3.0 |
| scale_init: float = 0.05 |
| opacity_bias: float = -2.0 |
|
|
| tokens: TokenConfig = field(default_factory=TokenConfig) |
|
|
|
|
| |
| |
| |
| @dataclass |
| class LossConfig: |
| lambda_ssim: float = 0.2 |
| lambda_lpips: float = 0.2 |
| lambda_vis: float = 1.0 |
| lambda_md: float = 0.5 |
| lambda_fs: float = 0.1 |
| lambda_ext: float = 0.3 |
| lambda_warp: float = 0.3 |
| lambda_lane: float = 0.1 |
| lambda_vert: float = 0.1 |
| lambda_ground_coupling: float = 0.05 |
|
|
| huber_delta: float = 0.5 |
| warp_tau: float = 0.5 |
| lateral_shift_range: tuple = (1.0, 3.0) |
| yaw_jitter_deg: float = 3.0 |
| pitch_jitter_deg: float = 1.5 |
|
|
| |
| eps0: float = 0.1 |
| eps_max: float = 1.0 |
| temper_gamma: float = 1.0005 |
| md_late_decay: float = 0.5 |
|
|
|
|
| |
| |
| |
| @dataclass |
| class DataConfig: |
| name: str = "synthetic" |
| root: str = "" |
| clip_seconds: float = 2.0 |
| num_frames: int = 20 |
| fps: int = 10 |
| context_times: tuple = (0.0, 0.5, 1.0, 1.5) |
| cameras: tuple = ("front_left", "front", "front_right") |
| height: int = 256 |
| width: int = 448 |
| depth_scale_target: float = 1.0 |
| use_lidar: bool = False |
| use_mono_depth: bool = True |
| use_boxes: bool = True |
| max_instances: int = 8 |
| |
| synth_num_scenes: int = 64 |
| synth_dynamic_actors: int = 3 |
|
|
|
|
| |
| |
| |
| @dataclass |
| class TrainConfig: |
| stage: str = "base" |
| iters: int = 150000 |
| lr: float = 4e-4 |
| warmup: int = 2000 |
| min_lr_ratio: float = 0.01 |
| batch_size: int = 16 |
| grad_accum: int = 1 |
| weight_decay: float = 0.05 |
| grad_clip: float = 1.0 |
| extrap_ramp_iter: int = 10000 |
| deviated_views_per_step: int = 1 |
| amp: bool = True |
| amp_dtype: str = "bf16" |
| grad_checkpoint: bool = False |
| num_workers: int = 4 |
| log_every: int = 50 |
| ckpt_every: int = 5000 |
| val_every: int = 5000 |
| out_dir: str = "runs/mapgs" |
| resume: str = "" |
| init_translation_scale: float = 0.1 |
|
|
|
|
| |
| |
| |
| @dataclass |
| class TTConfig: |
| enabled: bool = False |
| steps: int = 50 |
| lr: float = 1e-4 |
| densify: bool = True |
| densify_perturb_std: float = 0.01 |
|
|
|
|
| |
| |
| |
| @dataclass |
| class EvalConfig: |
| lateral_shifts: tuple = (1.0, 2.0, 3.0, 4.0, 6.0) |
| held_out_lane_change_frames: int = 50 |
| fid_enabled: bool = True |
| lane_miou: bool = True |
|
|
|
|
| @dataclass |
| class MapGSConfig: |
| map: MapConfig = field(default_factory=MapConfig) |
| model: ModelConfig = field(default_factory=ModelConfig) |
| loss: LossConfig = field(default_factory=LossConfig) |
| data: DataConfig = field(default_factory=DataConfig) |
| train: TrainConfig = field(default_factory=TrainConfig) |
| tt: TTConfig = field(default_factory=TTConfig) |
| eval: EvalConfig = field(default_factory=EvalConfig) |
| seed: int = 0 |
| device: str = "cuda" |
|
|
| |
| @property |
| def n_static_tokens(self) -> int: |
| return self.model.tokens.n_map + self.model.tokens.n_free |
|
|
| @property |
| def static_gaussian_budget(self) -> int: |
| return self.n_static_tokens * self.model.tokens.gaussians_per_token |
|
|
| def to_dict(self) -> dict: |
| return dataclasses.asdict(self) |
|
|
|
|
| |
| |
| |
| def _from_dict(cls, d: dict): |
| """Recursively build a (possibly nested) dataclass from a dict.""" |
| if not dataclasses.is_dataclass(cls): |
| return d |
| hints = get_type_hints(cls) |
| kwargs = {} |
| for f in dataclasses.fields(cls): |
| if f.name not in d: |
| continue |
| val = d[f.name] |
| ftype = hints[f.name] |
| if dataclasses.is_dataclass(ftype) and isinstance(val, dict): |
| kwargs[f.name] = _from_dict(ftype, val) |
| elif ftype is tuple and isinstance(val, list): |
| kwargs[f.name] = tuple(val) |
| elif ftype is float and isinstance(val, (str, int)): |
| kwargs[f.name] = float(val) |
| elif ftype is int and isinstance(val, str): |
| kwargs[f.name] = int(float(val)) |
| elif ftype is bool and isinstance(val, str): |
| kwargs[f.name] = val.lower() in ("1", "true", "yes") |
| else: |
| kwargs[f.name] = val |
| return cls(**kwargs) |
|
|
|
|
| def _deep_merge(base: dict, override: dict) -> dict: |
| out = dict(base) |
| for k, v in override.items(): |
| if k in out and isinstance(out[k], dict) and isinstance(v, dict): |
| out[k] = _deep_merge(out[k], v) |
| else: |
| out[k] = v |
| return out |
|
|
|
|
| def _load_yaml_with_base(path: str) -> dict: |
| with open(path, "r") as fh: |
| raw = yaml.safe_load(fh) or {} |
| base_ref = raw.pop("_base_", None) |
| if base_ref is None: |
| return raw |
| base_path = base_ref if os.path.isabs(base_ref) else os.path.join(os.path.dirname(path), base_ref) |
| base = _load_yaml_with_base(base_path) |
| return _deep_merge(base, raw) |
|
|
|
|
| def _set_dotted(d: dict, dotted: str, value: Any) -> None: |
| keys = dotted.split(".") |
| cur = d |
| for k in keys[:-1]: |
| cur = cur.setdefault(k, {}) |
| cur[keys[-1]] = value |
|
|
|
|
| def _coerce(value: str) -> Any: |
| try: |
| return yaml.safe_load(value) |
| except Exception: |
| return value |
|
|
|
|
| def load_config(path: str | None = None, overrides: list[str] | None = None) -> MapGSConfig: |
| """Load a :class:`MapGSConfig` from YAML + dotted overrides. |
| |
| Args: |
| path: optional YAML path (supports ``_base_``). |
| overrides: list of ``a.b.c=value`` strings. |
| """ |
| merged: dict = {} |
| if path: |
| merged = _load_yaml_with_base(path) |
| for ov in overrides or []: |
| if "=" not in ov: |
| raise ValueError(f"override must be key=value, got {ov!r}") |
| key, val = ov.split("=", 1) |
| _set_dotted(merged, key.strip(), _coerce(val.strip())) |
| return _from_dict(MapGSConfig, merged) |
|
|