Files
radar_system/python_app/models/gui_profile_schema.py
T
2026-04-01 22:05:04 +03:00

132 lines
3.9 KiB
Python

"""Dataclass schema for full GUI config profiles."""
from __future__ import annotations
from copy import deepcopy
from dataclasses import dataclass, field
import json
from pathlib import Path
from typing import Any
from python_app.models.run_config_model import RunConfigModel
@dataclass(slots=True)
class GuiSwitchStateModel:
"""UI-only state for switch selection controls."""
combo_mode: str = "text"
combos_text: str = ""
single_input: str = "0"
single_output: str = "0"
@dataclass(slots=True)
class GuiPassThroughStateModel:
"""UI-only defaults for pass-through rendering."""
show_magnitude: bool = True
show_phase: bool = True
fixed_y_enabled: bool = False
y_min_db: float = -100.0
y_max_db: float = 0.0
@dataclass(slots=True)
class GuiBscanStateModel:
"""UI-only defaults for B-scan live settings."""
axis: str = "abs"
cut_m: float = 0.824
max_depth_m: float = 1.0
gain: float = 1.0
start_freq_mhz: float = 100.0
stop_freq_mhz: float = 8800.0
@dataclass(slots=True)
class GuiGprStateModel:
"""UI-only defaults for GPR live settings."""
input_positions: str = ""
output_positions: str = ""
min_depth_m: float = 2.0
max_depth_m: float = 14.0
comp_power: float = 0.2
start_freq_mhz: float = 3000.0
stop_freq_mhz: float = 6000.0
speed_m_s: float = 0.0
look_angle_deg: float = 0.0
background_subtract_enabled: bool = True
background_mean_count: int = 10
@dataclass(slots=True)
class GuiProcessingStateModel:
"""UI-only processing-section defaults."""
selected_mode: str = "pass_through"
pass_through: GuiPassThroughStateModel = field(default_factory=GuiPassThroughStateModel)
bscan: GuiBscanStateModel = field(default_factory=GuiBscanStateModel)
gpr: GuiGprStateModel = field(default_factory=GuiGprStateModel)
@dataclass(slots=True)
class GuiDataActionsStateModel:
"""UI-only defaults for snapshot/export controls."""
save_count: int = 10
save_path: str = ""
save_name: str = "snapshot_manual"
@dataclass(slots=True)
class GuiPreprocessDialogStateModel:
"""UI-only defaults for preprocessing dialog controls."""
set_name: str = "set_001"
@dataclass(slots=True)
class GuiStateModel:
"""GUI-only profile payload stored under top-level `gui`."""
version: int = 1
switches: GuiSwitchStateModel = field(default_factory=GuiSwitchStateModel)
processing: GuiProcessingStateModel = field(default_factory=GuiProcessingStateModel)
data_actions: GuiDataActionsStateModel = field(default_factory=GuiDataActionsStateModel)
preprocess_dialog: GuiPreprocessDialogStateModel = field(default_factory=GuiPreprocessDialogStateModel)
@dataclass(slots=True)
class GuiProfileModel:
"""Full GUI profile with pipeline-compatible root config and optional UI state."""
run_config: RunConfigModel = field(default_factory=RunConfigModel)
gui: GuiStateModel | None = None
@staticmethod
def from_dict(payload: dict[str, Any]) -> GuiProfileModel:
"""Build profile from JSON-like payload using codec layer."""
from python_app.models.gui_profile_codec import gui_profile_from_dict
return gui_profile_from_dict(payload)
@classmethod
def load_from_path(cls, path: Path) -> GuiProfileModel:
"""Load JSON file from disk and decode into profile model."""
payload = json.loads(path.read_text(encoding="utf-8"))
if not isinstance(payload, dict):
raise ValueError(f"Config profile root must be JSON object: {path}")
return cls.from_dict(payload)
def clone(self) -> GuiProfileModel:
"""Create deep copy while preserving whether `gui` is absent."""
return deepcopy(self)
def to_dict(self) -> dict[str, Any]:
"""Encode profile into JSON-serializable dictionary."""
from python_app.models.gui_profile_codec import gui_profile_to_dict
return gui_profile_to_dict(self)