diff --git a/gui/src/aaregui/main_window.py b/gui/src/aaregui/main_window.py index 96b501fc..f70ed74e 100644 --- a/gui/src/aaregui/main_window.py +++ b/gui/src/aaregui/main_window.py @@ -22,6 +22,7 @@ from aaregui.panels.manual_sample_panel import ManualSamplePanel from aaregui.panels.reference_tools_panel import ReferenceToolsPanel from aaregui.panels.sample_queue_panel import SampleQueuePanel from aaregui.panels.tell_sample_panel import TellSamplePanel +from aaregui.panels.face_detection_panel import FaceDetectionPanel from aaregui.scan_logic.raster_grid_manager import RasterGridManager from aaregui.scan_logic.rotation_scan_manager import RotationScanManager from aaregui.scan_logic.sample_mount_logic import SampleMountLogic @@ -154,6 +155,12 @@ class MainWindow(QMainWindow): self.manual_sample_dock.setAllowedAreas(Qt.DockWidgetArea.BottomDockWidgetArea) self.addDockWidget(Qt.DockWidgetArea.BottomDockWidgetArea, self.manual_sample_dock) + self.face_panel = FaceDetectionPanel(api_base_url=self.__base_url, token=self.__token) + self.face_panel_dock = QDockWidget("Face detection", self) + self.face_panel_dock.setWidget(self.face_panel) + self.face_panel_dock.setAllowedAreas(Qt.DockWidgetArea.RightDockWidgetArea | Qt.DockWidgetArea.LeftDockWidgetArea) + self.addDockWidget(Qt.DockWidgetArea.RightDockWidgetArea, self.face_panel_dock) + self.setCentralWidget(top_widget) self.setWindowTitle("AareGUI") @@ -297,6 +304,8 @@ class MainWindow(QMainWindow): self.daq.raster_scan_completed.connect(self.raster.grid_scan_completed) self.manual_sample_panel.sample_manual.connect(self.daq.sample_manual) + self.face_panel.face_detection.connect(self.daq.face_detection) + self.daq.face_detection_result.connect(self.face_panel.update_plot) def create_menu_bar(self): """Create a menu bar with File->Quit and Help->About.""" @@ -332,8 +341,18 @@ class MainWindow(QMainWindow): show_manual_sample_action.setChecked(True) show_manual_sample_action.triggered.connect(self.manual_sample_dock.setVisible) # When dock visibility changes, update the action's checked state - show_manual_sample_action.triggered.connect(lambda: self.manual_sample_dock.setVisible(show_job_list_action.isChecked())) + show_manual_sample_action.triggered.connect(lambda: self.manual_sample_dock.setVisible(show_manual_sample_action.isChecked())) view_menu.addAction(show_manual_sample_action) + + show_face_panel_action = QAction("Show face detection", self) + show_face_panel_action.setCheckable(True) + show_face_panel_action.setChecked(True) + show_face_panel_action.triggered.connect(self.face_panel_dock.setVisible) + # When dock visibility changes, update the action's checked state + # show_face_panel_action.triggered.connect( + # lambda: self.face_panel_dock.setVisible(show_face_panel_action.isChecked())) + self.face_panel_dock.visibilityChanged.connect(show_face_panel_action.setChecked) + view_menu.addAction(show_face_panel_action) # Help menu help_menu = menu_bar.addMenu("Help") about_action = QAction("About", self) # Action for 'About' diff --git a/gui/src/aaregui/panels/face_detection_panel.py b/gui/src/aaregui/panels/face_detection_panel.py new file mode 100644 index 00000000..0c4a0242 --- /dev/null +++ b/gui/src/aaregui/panels/face_detection_panel.py @@ -0,0 +1,95 @@ +# Python +from PySide6.QtCore import Slot, Qt, Signal +from PySide6.QtWidgets import QWidget, QGridLayout, QLabel, QFrame, QPushButton, QSpacerItem, QSizePolicy, QVBoxLayout, \ + QHBoxLayout +from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg as FigureCanvas +from matplotlib.figure import Figure +import numpy as np + +from aaregui.widgets.title_label import TitleLabel + + +class FaceDetectionPanel(QWidget): + + face_detection = Signal() + + def __init__(self, parent=None, api_base_url: str | None = None, token: str | None = None): + super().__init__(parent) + self.api_base_url = api_base_url or "" + self.token = token or "" + + self.fig = Figure(figsize=(5, 4), tight_layout=True) + self.canvas = FigureCanvas(self.fig) + self.ax1 = self.fig.add_subplot(2, 1, 1) # Height vs angle + self.ax2 = self.fig.add_subplot(2, 1, 2) # Area vs angle + + + _top_layout = QHBoxLayout() + _top_layout.addWidget(TitleLabel("TELL sample changer", self)) + self.status_lbl = QLabel("") + _top_layout.addWidget(self.status_lbl) + + self.face_detection_button = QPushButton("Face Detection") + self.face_detection_button.setStyleSheet("color: rgb(78, 154, 6);") + self.face_detection_button.clicked.connect(self.run_and_refresh) + _top_layout.addWidget(self.face_detection_button) + + _layout = QVBoxLayout(self) + _layout.addLayout(_top_layout) + _layout.addWidget(self.canvas) + self.setLayout(_layout) + + def run_and_refresh(self): + try: + self.face_detection.emit() + except Exception as e: + self.status_lbl.setText(f"Error: {e}") + + def update_plot(self, data): + samples = data.get("samples", []) + if not samples: + self.ax1.clear() + self.ax2.clear() + self.ax1.text(0.5, 0.5, "No data", ha="center", va="center") + self.ax2.text(0.5, 0.5, "No data", ha="center", va="center") + self.canvas.draw_idle() + return + + angles = np.array([s["angle_deg"] for s in samples], dtype=float) + heights = np.array([s["height"] for s in samples], dtype=float) + areas = np.array([s["area"] for s in samples], dtype=float) + self.ax1.clear() + self.ax2.clear() + + self.ax1.scatter(angles, heights, s=16, c="tab:blue", label="Height") + self.ax2.scatter(angles, areas, s=16, c="tab:green", label="Area") + + ang_grid = np.linspace(angles.min(), angles.max(), 400) + + hf = data.get("height_fit", {}) + if {"A", "B", "phi_rad"} <= hf.keys(): + A, B, phi = hf["A"], hf["B"], hf["phi_rad"] + height_fit = A + B * np.cos(np.deg2rad(ang_grid) - phi) + self.ax1.plot(ang_grid, height_fit, color="tab:orange", label="Height fit") + if "best_angle_deg" in hf: + self.ax1.axvline(hf["best_angle_deg"], color="tab:orange", ls="--", alpha=0.6) + + af = data.get("area_fit", {}) + if {"A", "B", "phi_rad"} <= af.keys(): + A2, B2, phi2 = af["A"], af["B"], af["phi_rad"] + area_fit = A2 + B2 * np.cos(np.deg2rad(ang_grid) - phi2) + self.ax2.plot(ang_grid, area_fit, color="tab:red", label="Area fit") + if "best_angle_deg" in af: + self.ax2.axvline(af["best_angle_deg"], color="tab:red", ls="--", alpha=0.6) + + self.ax1.set_xlabel("Angle (deg)") + self.ax1.set_ylabel("Height") + self.ax1.legend() + self.ax1.grid(True, alpha=0.3) + + self.ax2.set_xlabel("Angle (deg)") + self.ax2.set_ylabel("Area") + self.ax2.legend() + self.ax2.grid(True, alpha=0.3) + + self.canvas.draw_idle() \ No newline at end of file