diff --git a/mucapy/main.py b/mucapy/main.py index ed4fba0..ebe7f7e 100644 --- a/mucapy/main.py +++ b/mucapy/main.py @@ -1302,6 +1302,14 @@ class MainWindow(QMainWindow): # Initialize configuration self.config = Config() + # Initialize default values + self.current_layout = 0 # Default to single camera layout + self.detector = MultiCamYOLODetector() + self.camera_settings = {} + + # Load saved settings first + self.load_saved_settings() + # Set dark theme style self.setStyleSheet(""" QMainWindow, QWidget { @@ -1401,16 +1409,18 @@ class MainWindow(QMainWindow): palette.setColor(palette.HighlightedText, Qt.black) self.setPalette(palette) - self.detector = MultiCamYOLODetector() - self.camera_settings = {} - - # Load saved settings - self.load_saved_settings() - - self.create_menus() + # Initialize UI elements self.init_ui() + + # Create menus + self.create_menus() + + # Initialize timer self.init_timer() - + + # Apply saved settings to UI + self.apply_saved_settings() + def load_saved_settings(self): """Load saved settings from configuration""" # Load model directory @@ -1426,11 +1436,16 @@ class MainWindow(QMainWindow): # Load layout setting self.current_layout = int(self.config.load_setting('layout', 0)) - def save_settings(self): - """Save current settings to configuration""" - self.config.save_setting('model_dir', self.detector.model_dir) - self.config.save_setting('fps', self.fps_spin.value()) - self.config.save_setting('layout', self.layout_combo.currentIndex()) + def apply_saved_settings(self): + """Apply loaded settings to UI elements""" + if hasattr(self, 'fps_spin'): + self.fps_spin.setValue(self.detector.target_fps) + + if hasattr(self, 'layout_combo'): + self.layout_combo.setCurrentIndex(self.current_layout) + + if hasattr(self, 'model_label') and self.detector.model_dir: + self.model_label.setText(f"Model: {os.path.basename(self.detector.model_dir)}") def create_menus(self): menubar = self.menuBar() @@ -1452,6 +1467,7 @@ class MainWindow(QMainWindow): # Add Camera Selector action select_cameras_action = QAction('Select Cameras...', self) + select_cameras_action.setIcon(QIcon.fromTheme('camera-web')) select_cameras_action.triggered.connect(self.show_camera_selector) self.camera_menu.addAction(select_cameras_action) @@ -1459,41 +1475,240 @@ class MainWindow(QMainWindow): # Add Network Camera Settings action network_camera_action = QAction('Network Camera Settings...', self) + network_camera_action.setIcon(QIcon.fromTheme('network-wireless')) network_camera_action.triggered.connect(self.show_network_camera_dialog) self.camera_menu.addAction(network_camera_action) self.camera_menu.addSeparator() - self.camera_action_group = QActionGroup(self) - self.camera_action_group.setExclusive(False) + # Create camera groups + self.local_camera_menu = QMenu('Local Cameras', self) + self.network_camera_menu = QMenu('Network Cameras', self) + self.camera_menu.addMenu(self.local_camera_menu) + self.camera_menu.addMenu(self.network_camera_menu) + + # Create action groups for each camera type + self.local_camera_group = QActionGroup(self) + self.local_camera_group.setExclusive(False) + self.network_camera_group = QActionGroup(self) + self.network_camera_group.setExclusive(False) + + # Initial population self.populate_camera_menu() def populate_camera_menu(self): """Populate the camera menu with available cameras""" - # Clear existing camera actions (except refresh and network camera settings) - for action in self.camera_menu.actions()[3:]: - self.camera_menu.removeAction(action) + # Clear existing camera actions + self.local_camera_menu.clear() + self.network_camera_menu.clear() + + # Add refresh action to both menus + refresh_action = QAction('Refresh List', self) + refresh_action.triggered.connect(self.populate_camera_menu) + self.local_camera_menu.addAction(refresh_action) + self.local_camera_menu.addSeparator() available_cams = self.detector.scan_for_cameras() - for cam_path in available_cams: - # Display friendly name - if cam_path.startswith('net:'): - name = cam_path[4:] # Use the camera name directly - display_name = f"{name}" - elif cam_path.startswith('/dev/'): - display_name = os.path.basename(cam_path) - else: - display_name = f"Camera {cam_path}" - - action = QAction(display_name, self, checkable=True) - action.setData(cam_path) - self.camera_action_group.addAction(action) - self.camera_menu.addAction(action) + local_cams_found = False + network_cams_found = False - if not available_cams: - no_cam_action = QAction('No cameras found', self) - no_cam_action.setEnabled(False) - self.camera_menu.addAction(no_cam_action) + for cam_path in available_cams: + if cam_path.startswith('net:'): + # Network camera + name = cam_path[4:] + action = QAction(name, self) + action.setCheckable(True) + action.setData(cam_path) + self.network_camera_group.addAction(action) + self.network_camera_menu.addAction(action) + network_cams_found = True + else: + # Local camera + if cam_path.startswith('/dev/'): + display_name = os.path.basename(cam_path) + else: + display_name = f"Camera {cam_path}" + + action = QAction(display_name, self) + action.setCheckable(True) + action.setData(cam_path) + self.local_camera_group.addAction(action) + self.local_camera_menu.addAction(action) + local_cams_found = True + + # Add placeholder text if no cameras found + if not local_cams_found: + no_local = QAction('No local cameras found', self) + no_local.setEnabled(False) + self.local_camera_menu.addAction(no_local) + + if not network_cams_found: + no_net = QAction('No network cameras found', self) + no_net.setEnabled(False) + self.network_camera_menu.addAction(no_net) + + # Update the camera label + self.update_selection_labels() + + def update_selection_labels(self): + """Update the model and camera selection labels""" + selected_cams = [] + + # Check local cameras + for action in self.local_camera_group.actions(): + if action.isChecked(): + selected_cams.append(action.text()) + + # Check network cameras + for action in self.network_camera_group.actions(): + if action.isChecked(): + selected_cams.append(action.text()) + + if selected_cams: + self.cameras_label.setText(f"Selected Cameras: {', '.join(selected_cams)}") + else: + self.cameras_label.setText("Selected Cameras: None") + + def start_detection(self): + """Start the detection process""" + if not self.detector.model_dir: + QMessageBox.critical(self, "Error", "No model directory selected!") + return + + # Get selected cameras + selected_cameras = [] + + # Get local cameras + for action in self.local_camera_group.actions(): + if action.isChecked(): + selected_cameras.append(action.data()) + + # Get network cameras + for action in self.network_camera_group.actions(): + if action.isChecked(): + selected_cameras.append(action.data()) + + if not selected_cameras: + QMessageBox.critical(self, "Error", "No cameras selected!") + return + + # Set FPS + self.detector.target_fps = self.fps_spin.value() + self.detector.frame_interval = 1.0 / self.detector.target_fps + + # Connect to cameras + if not self.detector.connect_cameras(selected_cameras): + QMessageBox.critical(self, "Error", "Failed to connect to cameras!") + return + + # Update UI + self.update_selection_labels() + self.start_btn.setEnabled(False) + self.stop_btn.setEnabled(True) + self.fps_spin.setEnabled(False) + + # Start timer + self.timer.start(int(1000 / self.detector.target_fps)) + + def show_camera_selector(self): + """Show a simplified camera selector dialog""" + dialog = QDialog(self) + dialog.setWindowTitle("Select Cameras") + dialog.setModal(True) + layout = QVBoxLayout(dialog) + + # Create tabs for different camera types + tabs = QTabWidget() + local_tab = QWidget() + network_tab = QWidget() + + # Local cameras tab + local_layout = QVBoxLayout(local_tab) + local_list = QListWidget() + local_layout.addWidget(QLabel("Available Local Cameras:")) + local_layout.addWidget(local_list) + + # Network cameras tab + network_layout = QVBoxLayout(network_tab) + network_list = QListWidget() + network_layout.addWidget(QLabel("Available Network Cameras:")) + network_layout.addWidget(network_list) + + # Add tabs + tabs.addTab(local_tab, "Local Cameras") + tabs.addTab(network_tab, "Network Cameras") + layout.addWidget(tabs) + + # Populate lists + available_cams = self.detector.scan_for_cameras() + for cam_path in available_cams: + if cam_path.startswith('net:'): + name = cam_path[4:] + item = QListWidgetItem(name) + item.setData(Qt.UserRole, cam_path) + item.setFlags(item.flags() | Qt.ItemIsUserCheckable) + item.setCheckState(Qt.Unchecked) + network_list.addItem(item) + else: + display_name = os.path.basename(cam_path) if cam_path.startswith('/dev/') else f"Camera {cam_path}" + item = QListWidgetItem(display_name) + item.setData(Qt.UserRole, cam_path) + item.setFlags(item.flags() | Qt.ItemIsUserCheckable) + item.setCheckState(Qt.Unchecked) + local_list.addItem(item) + + # Check currently selected cameras + for action in self.local_camera_group.actions(): + if action.isChecked(): + for i in range(local_list.count()): + item = local_list.item(i) + if item.data(Qt.UserRole) == action.data(): + item.setCheckState(Qt.Checked) + + for action in self.network_camera_group.actions(): + if action.isChecked(): + for i in range(network_list.count()): + item = network_list.item(i) + if item.data(Qt.UserRole) == action.data(): + item.setCheckState(Qt.Checked) + + # Buttons + btn_layout = QHBoxLayout() + ok_btn = QPushButton("OK") + cancel_btn = QPushButton("Cancel") + btn_layout.addWidget(ok_btn) + btn_layout.addWidget(cancel_btn) + layout.addLayout(btn_layout) + + ok_btn.clicked.connect(dialog.accept) + cancel_btn.clicked.connect(dialog.reject) + + if dialog.exec_() == QDialog.Accepted: + # Update camera selections + for action in self.local_camera_group.actions(): + action.setChecked(False) + for action in self.network_camera_group.actions(): + action.setChecked(False) + + # Update local camera selections + for i in range(local_list.count()): + item = local_list.item(i) + if item.checkState() == Qt.Checked: + cam_path = item.data(Qt.UserRole) + for action in self.local_camera_group.actions(): + if action.data() == cam_path: + action.setChecked(True) + + # Update network camera selections + for i in range(network_list.count()): + item = network_list.item(i) + if item.checkState() == Qt.Checked: + cam_path = item.data(Qt.UserRole) + for action in self.network_camera_group.actions(): + if action.data() == cam_path: + action.setChecked(True) + + self.update_selection_labels() def load_model_directory(self): """Open file dialog to select model directory""" @@ -1631,12 +1846,6 @@ class MainWindow(QMainWindow): # Start with sidebar expanded self.sidebar.expand() - - # Set saved FPS value - self.fps_spin.setValue(self.detector.target_fps) - - # Set saved layout - self.layout_combo.setCurrentIndex(self.current_layout) def change_camera_layout(self, index): """Change the camera display layout""" @@ -1674,40 +1883,6 @@ class MainWindow(QMainWindow): self.timer = QTimer() self.timer.timeout.connect(self.update_feeds) - def start_detection(self): - """Start the detection process""" - if not self.detector.model_dir: - QMessageBox.critical(self, "Error", "No model directory selected!") - return - - # Get selected cameras - selected_cameras = [] - for action in self.camera_action_group.actions(): - if action.isChecked(): - selected_cameras.append(action.data()) - - if not selected_cameras: - QMessageBox.critical(self, "Error", "No cameras selected!") - return - - # Set FPS - self.detector.target_fps = self.fps_spin.value() - self.detector.frame_interval = 1.0 / self.detector.target_fps - - # Connect to cameras - if not self.detector.connect_cameras(selected_cameras): - QMessageBox.critical(self, "Error", "Failed to connect to cameras!") - return - - # Update UI - self.update_selection_labels() - self.start_btn.setEnabled(False) - self.stop_btn.setEnabled(True) - self.fps_spin.setEnabled(False) - - # Start timer - self.timer.start(int(1000 / self.detector.target_fps)) - def stop_detection(self): """Stop the detection process""" self.timer.stop() @@ -1730,15 +1905,6 @@ class MainWindow(QMainWindow): } """) - def update_selection_labels(self): - """Update the model and camera selection labels""" - # Update cameras label - selected_cams = [] - for action in self.camera_action_group.actions(): - if action.isChecked(): - selected_cams.append(action.text()) - self.cameras_label.setText(f"Selected Cameras: {', '.join(selected_cams) or 'None'}") - def update_feeds(self): """Update the camera feeds in the display""" frames = self.detector.get_frames() @@ -1793,30 +1959,6 @@ class MainWindow(QMainWindow): dialog.exec_() # Refresh camera list after dialog closes self.populate_camera_menu() - - def show_camera_selector(self): - """Show the camera selector dialog""" - dialog = CameraSelectorDialog(self) - if dialog.exec_() == QDialog.Accepted and dialog.selected_cameras: - # Stop current detection if running - was_running = False - if self.stop_btn.isEnabled(): - was_running = True - self.stop_detection() - - # Update selected cameras - for action in self.camera_action_group.actions(): - action.setChecked(action.data() in dialog.selected_cameras) - - # Restart detection if it was running - if was_running: - self.start_detection() - - def closeEvent(self, event): - """Handle window close event""" - self.stop_detection() - self.save_settings() - super().closeEvent(event) if __name__ == "__main__": app = QApplication(sys.argv)