diff --git a/src/pc/gui/main_window.py b/src/pc/gui/main_window.py index 9509ee8..5c19934 100644 --- a/src/pc/gui/main_window.py +++ b/src/pc/gui/main_window.py @@ -28,7 +28,11 @@ from pc.steering.auto_params import ( load_control, load_detect_params, + load_overlay, + load_pursuit, save_control, + save_overlay, + save_pursuit, ) from pc.steering.base import SteeringBase from pc.steering.pd_control import PdControl, PdParams @@ -75,18 +79,22 @@ self._steer: float = 0.0 # 前回のパラメータを復元 - pd_params, last_method = load_control() + pd_params, last_method, last_steering = ( + load_control() + ) image_params = load_detect_params(last_method) self._pd_control = PdControl( params=pd_params, image_params=image_params, ) + pursuit_params = load_pursuit() self._pursuit_control = PursuitControl( + params=pursuit_params, image_params=image_params, ) # 現在の制御手法("pd" or "pursuit") - self._steering_method: str = "pd" + self._steering_method: str = last_steering # 最新フレームの保持(自動操縦で使用) self._latest_frame: np.ndarray | None = None @@ -197,6 +205,7 @@ self._control_panel = ControlParamPanel( self._pd_control.params, self._pursuit_control.params, + self._steering_method, ) self._control_panel.pd_params_changed.connect( self._on_pd_params_changed, @@ -210,7 +219,11 @@ control_layout.addWidget(self._control_panel) # デバッグ表示パネル - self._overlay_panel = OverlayPanel() + overlay_flags = load_overlay() + self._overlay_panel = OverlayPanel(overlay_flags) + self._overlay_panel.overlay_flags_changed.connect( + self._on_overlay_flags_changed, + ) control_layout.addWidget(self._overlay_panel) # 操作ガイド @@ -255,26 +268,40 @@ def _on_method_changed(self, method: str) -> None: """検出手法の変更に合わせて制御設定を保存する""" - save_control(self._pd_control.params, method) + save_control( + self._pd_control.params, method, + self._steering_method, + ) def _on_pd_params_changed(self, p: PdParams) -> None: """PD パラメータの変更を制御クラスに反映して保存する""" self._pd_control.params = p save_control( p, self._pd_control.image_params.method, + self._steering_method, ) def _on_pursuit_params_changed( self, p: PursuitParams, ) -> None: - """Pursuit パラメータの変更を制御クラスに反映する""" + """Pursuit パラメータの変更を制御クラスに反映して保存する""" self._pursuit_control.params = p + save_pursuit(p) + + def _on_overlay_flags_changed(self) -> None: + """オーバーレイフラグの変更を保存する""" + save_overlay(self._overlay_panel.get_flags()) def _on_steering_method_changed( self, method: str, ) -> None: - """制御手法の切替を反映する""" + """制御手法の切替を反映して保存する""" self._steering_method = method + save_control( + self._pd_control.params, + self._pd_control.image_params.method, + method, + ) # ── 接続 ────────────────────────────────────────────── diff --git a/src/pc/gui/panels/control_param_panel.py b/src/pc/gui/panels/control_param_panel.py index 133b7fa..9c824a0 100644 --- a/src/pc/gui/panels/control_param_panel.py +++ b/src/pc/gui/panels/control_param_panel.py @@ -46,10 +46,12 @@ self, pd_params: PdParams, pursuit_params: PursuitParams, + steering_method: str = "pd", ) -> None: super().__init__("制御パラメータ") self._pd_params = pd_params self._pursuit_params = pursuit_params + self._initial_steering_method = steering_method self._auto_save_enabled = False self._pd_presets: list[PdPreset] = [] @@ -75,6 +77,11 @@ self._steering_combo.addItem( "2点パシュート", "pursuit", ) + idx = self._steering_combo.findData( + self._initial_steering_method, + ) + if idx >= 0: + self._steering_combo.setCurrentIndex(idx) layout.addWidget(self._steering_combo) # --- PD パラメータ --- diff --git a/src/pc/gui/panels/overlay_panel.py b/src/pc/gui/panels/overlay_panel.py index 4814d21..6be26d8 100644 --- a/src/pc/gui/panels/overlay_panel.py +++ b/src/pc/gui/panels/overlay_panel.py @@ -3,6 +3,7 @@ デバッグ表示の切替チェックボックスを提供するパネル """ +from PySide6.QtCore import Signal from PySide6.QtWidgets import QCheckBox, QVBoxLayout from pc.gui.panels.collapsible_group_box import ( @@ -14,9 +15,13 @@ class OverlayPanel(CollapsibleGroupBox): """デバッグ表示の切替チェックボックス群""" - def __init__(self) -> None: + overlay_flags_changed = Signal() + + def __init__( + self, flags: OverlayFlags | None = None, + ) -> None: super().__init__("デバッグ表示") - self._flags = OverlayFlags() + self._flags = flags or OverlayFlags() self._setup_ui() def _setup_ui(self) -> None: @@ -35,13 +40,21 @@ ] for label, attr in items: cb = QCheckBox(label) + cb.setChecked(getattr(self._flags, attr)) cb.toggled.connect( - lambda checked, a=attr: setattr( - self._flags, a, checked, + lambda checked, a=attr: self._on_toggled( + a, checked, ), ) layout.addWidget(cb) + def _on_toggled( + self, attr: str, checked: bool, + ) -> None: + """チェックボックスの切替を反映してシグナルを発火する""" + setattr(self._flags, attr, checked) + self.overlay_flags_changed.emit() + def get_flags(self) -> OverlayFlags: """現在のオーバーレイフラグを返す""" return self._flags diff --git a/src/pc/steering/auto_params.py b/src/pc/steering/auto_params.py index 24be64e..d4f666d 100644 --- a/src/pc/steering/auto_params.py +++ b/src/pc/steering/auto_params.py @@ -6,6 +6,8 @@ ファイル構成: params/ ├── control.json PD 制御 + 最後に使用した手法 + ├── pursuit.json パシュート制御パラメータ + ├── overlay.json オーバーレイ表示フラグ ├── detect_current.json 現行手法の画像処理パラメータ ├── detect_blackhat.json 案A の画像処理パラメータ ├── detect_dual_norm.json 案B の画像処理パラメータ @@ -16,11 +18,19 @@ from common.json_utils import PARAMS_DIR, read_json, write_json from pc.steering.pd_control import PdParams +from pc.steering.pursuit_control import PursuitParams from pc.vision.line_detector import ImageParams +from pc.vision.overlay import OverlayFlags # PD 制御パラメータファイル _CONTROL_FILE = PARAMS_DIR / "control.json" +# パシュート制御パラメータファイル +_PURSUIT_FILE = PARAMS_DIR / "pursuit.json" + +# オーバーレイ表示フラグファイル +_OVERLAY_FILE = PARAMS_DIR / "overlay.json" + # 検出手法ごとのファイル名 _DETECT_FILES: dict[str, str] = { "current": "detect_current.json", @@ -32,36 +42,97 @@ def save_control( - params: PdParams, method: str, + params: PdParams, + method: str, + steering_method: str = "pd", ) -> None: """PD 制御パラメータと最後に使用した手法を保存する Args: params: PD 制御パラメータ method: 最後に使用した検出手法の識別子 + steering_method: 最後に使用した制御手法("pd" or "pursuit") """ data = asdict(params) data["last_method"] = method + data["last_steering_method"] = steering_method write_json(_CONTROL_FILE, data) -def load_control() -> tuple[PdParams, str]: +def load_control() -> tuple[PdParams, str, str]: """PD 制御パラメータと最後に使用した手法を読み込む Returns: - (PD 制御パラメータ, 最後に使用した手法の識別子) + (PD 制御パラメータ, 検出手法の識別子, 制御手法の識別子) """ if not _CONTROL_FILE.exists(): - return PdParams(), "current" + return PdParams(), "current", "pd" data = read_json(_CONTROL_FILE) method = data.pop("last_method", "current") + steering_method = data.pop( + "last_steering_method", "pd", + ) known = PdParams.__dataclass_fields__ filtered = { k: v for k, v in data.items() if k in known } - return PdParams(**filtered), method + return PdParams(**filtered), method, steering_method + + +def save_pursuit(params: PursuitParams) -> None: + """パシュート制御パラメータを保存する + + Args: + params: パシュート制御パラメータ + """ + write_json(_PURSUIT_FILE, asdict(params)) + + +def load_pursuit() -> PursuitParams: + """パシュート制御パラメータを読み込む + + Returns: + パシュート制御パラメータ(ファイルがない場合はデフォルト) + """ + if not _PURSUIT_FILE.exists(): + return PursuitParams() + + data = read_json(_PURSUIT_FILE) + known = PursuitParams.__dataclass_fields__ + filtered = { + k: v for k, v in data.items() + if k in known + } + return PursuitParams(**filtered) + + +def save_overlay(flags: OverlayFlags) -> None: + """オーバーレイ表示フラグを保存する + + Args: + flags: オーバーレイ表示フラグ + """ + write_json(_OVERLAY_FILE, asdict(flags)) + + +def load_overlay() -> OverlayFlags: + """オーバーレイ表示フラグを読み込む + + Returns: + オーバーレイ表示フラグ(ファイルがない場合はデフォルト) + """ + if not _OVERLAY_FILE.exists(): + return OverlayFlags() + + data = read_json(_OVERLAY_FILE) + known = OverlayFlags.__dataclass_fields__ + filtered = { + k: v for k, v in data.items() + if k in known + } + return OverlayFlags(**filtered) def save_detect_params(