diff --git a/software/control/_def.py b/software/control/_def.py index 73099f023..ee4d8cb6c 100644 --- a/software/control/_def.py +++ b/software/control/_def.py @@ -7,7 +7,7 @@ import json import csv import squid.logging -from enum import Enum, auto +from enum import Enum, IntEnum, auto log = squid.logging.get_logger(__name__) @@ -1107,7 +1107,7 @@ class SOFTWARE_POS_LIMIT: DISPLAY_TOUPCAMER_BLACKLEVEL_SETTINGS = False -class HardwareTriggerMode: +class HardwareTriggerMode(IntEnum): EDGE = 0 # Fixed pulse width (TRIGGER_PULSE_LENGTH_us) LEVEL = 1 # Variable pulse width (illumination_on_time) diff --git a/software/control/camera_toupcam.py b/software/control/camera_toupcam.py index d82b0b6bd..3470b2c04 100644 --- a/software/control/camera_toupcam.py +++ b/software/control/camera_toupcam.py @@ -16,6 +16,7 @@ CameraFrame, ) from squid.config import CameraConfig, ToupcamCameraModel +import control._def from control._def import * import threading @@ -833,7 +834,7 @@ def _set_acquisition_mode_imp(self, acquisition_mode: CameraAcquisitionMode): self._camera.put_Option(toupcam.TOUPCAM_OPTION_TRIGGER, trigger_option_value) if acquisition_mode == CameraAcquisitionMode.HARDWARE_TRIGGER: - if HARDWARE_TRIGGER_MODE == HardwareTriggerMode.LEVEL: + if control._def.HARDWARE_TRIGGER_MODE == HardwareTriggerMode.LEVEL: try: self._camera.put_Option(toupcam.TOUPCAM_OPTION_TRIGGER, 2) except toupcam.HRESULTException as ex: diff --git a/software/control/core/live_controller.py b/software/control/core/live_controller.py index 7d69c174c..c5915d43b 100644 --- a/software/control/core/live_controller.py +++ b/software/control/core/live_controller.py @@ -8,6 +8,7 @@ from control.microcontroller import Microcontroller from squid.abc import CameraAcquisitionMode, AbstractCamera +import control._def from control._def import * from control.core.config.utils import apply_confocal_override from control.models import merge_channel_configs @@ -428,7 +429,7 @@ def set_trigger_mode(self, mode): if self.is_live and self.use_internal_timer_for_hardware_trigger: self._start_triggerred_acquisition() - self.microscope.low_level_drivers.microcontroller.set_trigger_mode(HARDWARE_TRIGGER_MODE) + self.microscope.low_level_drivers.microcontroller.set_trigger_mode(control._def.HARDWARE_TRIGGER_MODE) if mode == TriggerMode.CONTINUOUS: if (self.trigger_mode == TriggerMode.SOFTWARE) or ( diff --git a/software/control/gui_hcs.py b/software/control/gui_hcs.py index cc69e8ccd..ceb780798 100644 --- a/software/control/gui_hcs.py +++ b/software/control/gui_hcs.py @@ -845,7 +845,7 @@ def setup_hardware(self, skip_init: bool = False): if DEFAULT_TRIGGER_MODE == TriggerMode.HARDWARE: print("Setting acquisition mode to HARDWARE_TRIGGER") self.camera.set_acquisition_mode(squid.abc.CameraAcquisitionMode.HARDWARE_TRIGGER) - self.microcontroller.set_trigger_mode(HARDWARE_TRIGGER_MODE) + self.microcontroller.set_trigger_mode(control._def.HARDWARE_TRIGGER_MODE) else: self.camera.set_acquisition_mode(squid.abc.CameraAcquisitionMode.SOFTWARE_TRIGGER) self.camera.add_frame_callback(self.streamHandler.get_frame_callback()) diff --git a/software/control/widgets.py b/software/control/widgets.py index 4dee98ac5..2a7ccd31b 100644 --- a/software/control/widgets.py +++ b/software/control/widgets.py @@ -1346,6 +1346,28 @@ def _create_advanced_tab(self): self.illumination_factor.setValue(self._get_config_float("GENERAL", "illumination_intensity_factor", 0.6)) hw_layout.addRow("Illumination Intensity Factor:", self.illumination_factor) + self.hw_trigger_mode_combo = QComboBox() + for member in HardwareTriggerMode: + self.hw_trigger_mode_combo.addItem(member.name.title(), member.value) + self.hw_trigger_mode_combo.setToolTip( + "Edge: Fixed pulse width (TRIGGER_PULSE_LENGTH_us)\n" "Level: Variable pulse width (illumination_on_time)" + ) + try: + hw_trigger_value = HardwareTriggerMode(self._get_config_int("GENERAL", "hardware_trigger_mode", 0)) + except ValueError: + raw = self._get_config_int("GENERAL", "hardware_trigger_mode", 0) + logger.warning("Invalid hardware_trigger_mode=%d in INI, defaulting to EDGE", raw) + hw_trigger_value = HardwareTriggerMode.EDGE + self.hw_trigger_mode_combo.setCurrentIndex(hw_trigger_value) + self.hw_trigger_mode_label = QLabel("Hardware Trigger Mode:") + hw_layout.addRow(self.hw_trigger_mode_label, self.hw_trigger_mode_combo) + + # Only show for cameras that support level trigger + camera_type = self._get_config_value("GENERAL", "camera_type", "Default") + supports_level_trigger = camera_type in ("Toupcam", "Tucsen") + self.hw_trigger_mode_label.setVisible(supports_level_trigger) + self.hw_trigger_mode_combo.setVisible(supports_level_trigger) + hw_group.content.addLayout(hw_layout) layout.addWidget(hw_group) @@ -1837,6 +1859,8 @@ def _apply_settings(self) -> bool: self.config.set("GENERAL", "led_matrix_g_factor", str(self.led_g_factor.value())) self.config.set("GENERAL", "led_matrix_b_factor", str(self.led_b_factor.value())) self.config.set("GENERAL", "illumination_intensity_factor", str(self.illumination_factor.value())) + self.config.set("GENERAL", "hardware_trigger_mode", str(self.hw_trigger_mode_combo.currentIndex())) + control._def.HARDWARE_TRIGGER_MODE = HardwareTriggerMode(self.hw_trigger_mode_combo.currentIndex()) # Advanced - Development Settings self.config.set( @@ -2202,6 +2226,16 @@ def _get_changes(self): if not self._floats_equal(old_val, new_val): changes.append(("Illumination Intensity Factor", str(old_val), str(new_val), False)) + try: + old_mode = HardwareTriggerMode(self._get_config_int("GENERAL", "hardware_trigger_mode", 0)) + except ValueError: + raw = self._get_config_int("GENERAL", "hardware_trigger_mode", 0) + logger.warning("Invalid hardware_trigger_mode=%d in INI, defaulting to EDGE", raw) + old_mode = HardwareTriggerMode.EDGE + new_mode = HardwareTriggerMode(self.hw_trigger_mode_combo.currentIndex()) + if old_mode != new_mode: + changes.append(("Hardware Trigger Mode", old_mode.name.title(), new_mode.name.title(), False)) + # Advanced - Development Settings # Enable/disable requires restart (for warning banner/dialog), but speed/compression # take effect on next acquisition since each acquisition starts a fresh subprocess