diff --git a/software/README.md b/software/README.md index 483a574fc..d3a314d23 100644 --- a/software/README.md +++ b/software/README.md @@ -130,6 +130,20 @@ Follow the instructions during the installation. +
+Installing SDK for VersaLase laser + +Link: https://github.com/sthronevlt/VersaLSS + +Run the following commands: +``` +curl -L -O https://raw.githubusercontent.com/sthronevlt/VersaLSS/main/dist/laser_sdk-0.1.1-py3-none-any.whl +pip3 install laser_sdk-0.1.1-py3-none-any.whl +rm laser_sdk-0.1.1-py3-none-any.whl +``` + +
+ ## Configuring the software Copy the .ini file associated with the microscope configuration to the software folder. Make modifications as needed (e.g. `camera_type`, `support_laser_autofocus`,`focus_camera_exposure_time_ms`) diff --git a/software/configurations/configuration_Squid+_Cicero_LDI.ini b/software/configurations/configuration_Squid+_Cicero_LDI.ini index 675b766a8..29c9132cf 100644 --- a/software/configurations/configuration_Squid+_Cicero_LDI.ini +++ b/software/configurations/configuration_Squid+_Cicero_LDI.ini @@ -1,5 +1,7 @@ [GENERAL] -camera_type = Hamamatsu +version = 2.0 + +camera_type = Toupcam _camera_type_options = [Default, FLIR, Toupcam, Hamamatsu, Tucsen] controller_sn = None support_scimicroscopy_led_array = False @@ -13,7 +15,7 @@ LDI_INTENSITY_MODE = "EXT" LDI_SHUTTER_MODE = "EXT" enable_spinning_disk_confocal = True xlight_emission_filter_mapping = {405: 1, 470: 2, 555: 3, 640: 4, 730: 5} -xlight_serial_number = "AB0PKT2R" +xlight_serial_number = "B000DAO6" xlight_sleep_time_for_wheel = 0.25 xlight_validate_wheel_pos = False use_napari_for_live_view = False @@ -134,11 +136,11 @@ wellplate_offset_y_mm = 0 focus_measure_operator = GLVA controller_version = Teensy -support_laser_autofocus = True +support_laser_autofocus = False focus_camera_type = Default _focus_camera_type_options = [Default, FLIR, Toupcam] _support_laser_autofocus_options = [True, False] -main_camera_model = C15440-20UP +main_camera_model = ITR3CMOS26000KMA focus_camera_model = MER2-630-60U3M focus_camera_exposure_time_ms = 0.8 @@ -157,7 +159,8 @@ default_multipoint_ny = 1 inverted_objective = True _inverted_objective_options = [True, False] -filter_controller_enable = False +filter_controller_enable = Falsefilter_controller_enable = False + _filter_controller_enable_options = [True, False] illumination_intensity_factor = 1 @@ -166,14 +169,24 @@ Z_MOTOR_CONFIG = "STEPPER" _Z_MOTOR_CONFIG_OPTIONS = ["STEPPER", "STEPPER + PIEZO", "PIEZO", "LINEAR"] [CAMERA_CONFIG] +roi_offset_x_default = 0 +roi_offset_y_default = 0 +roi_width_default = 6208 +roi_height_default = 4168 rotate_image_angle = None flip_image = None _flip_image_options = [Vertical, Horizontal, Both, None] -crop_width_unbinned = 2304 -crop_height_unbinned = 2304 -binning_factor_default = 1 -pixel_format_default = MONO16 -_default_pixel_format_options = [MONO8, MONO16] +crop_width = 4168 +crop_height = 4168 +binning_factor_default = 2 +pixel_format_default = MONO8 +_default_pixel_format_options = [MONO8, MONO12, MONO14, MONO16, BAYER_RG8, BAYER_RG12] +temperature_default = 20 +fan_speed_default = 1 +blacklevel_value_default = 3 +awb_ratios_r = 1.375 +awb_ratios_g = 1 +awb_ratios_b = 1.4141 [LIMIT_SWITCH_POLARITY] x_home = 1 diff --git a/software/configurations/configuration_Squid+_Kinetix_LDI_XLight_Xeryon.ini b/software/configurations/configuration_Squid+_Kinetix_LDI_XLight_Xeryon.ini index b47b52473..436df6cdb 100644 --- a/software/configurations/configuration_Squid+_Kinetix_LDI_XLight_Xeryon.ini +++ b/software/configurations/configuration_Squid+_Kinetix_LDI_XLight_Xeryon.ini @@ -137,6 +137,8 @@ trackers = ["csrt", "kcf", "mil", "tld", "medianflow", "mosse", "daSiamRPN"] tracking_show_microscope_configurations = False _tracking_show_microscope_configurations_options = [True, False] +tube_lens_mm = 180 + wellplate_format = 384 _wellplate_format_options = [1536, 384, 96, 24, 12, 6] x_mm_384_wellplate_upperleft = 12.41 diff --git a/software/control/_def.py b/software/control/_def.py index 2c4c639c0..c298da381 100644 --- a/software/control/_def.py +++ b/software/control/_def.py @@ -610,13 +610,15 @@ class SOFTWARE_POS_LIMIT: CAMERA_TYPE = "Default" FOCUS_CAMERA_TYPE = "Default" -# Spinning disk confocal integration -ENABLE_SPINNING_DISK_CONFOCAL = False USE_LDI_SERIAL_CONTROL = False -LDI_INTENSITY_MODE = "PC" -LDI_SHUTTER_MODE = "PC" +LDI_INTENSITY_MODE = "PC" # "PC" or "EXT" +LDI_SHUTTER_MODE = "PC" # "PC" or "EXT" USE_CELESTA_ETHENET_CONTROL = False +USE_VORTRAN_LASER_USB_CONTROL = False +VORTRAN_SHUTTER_CONTROL_MODE = "PC" # "PC" or "EXT" +# Spinning disk confocal integration +ENABLE_SPINNING_DISK_CONFOCAL = False XLIGHT_EMISSION_FILTER_MAPPING = { 405: 1, 470: 1, diff --git a/software/control/core/core.py b/software/control/core/core.py index c8b495e26..47e9a1642 100644 --- a/software/control/core/core.py +++ b/software/control/core/core.py @@ -2151,7 +2151,7 @@ def get_acquisition_image_count(self): try: # We have Nt timepoints. For each timepoint, we capture images at all the regions. Each # region has a list of coordinates that we capture at, and at each coordinate we need to - # do a capture for each requested camera + lighting + other configuration selected. So + # do a capture for each requested camera + illumination + other configuration selected. So # total image count is: coords_per_region = [ len(region_coords) for (region_id, region_coords) in self.scanCoordinates.region_fov_coordinates.items() diff --git a/software/control/gui_hcs.py b/software/control/gui_hcs.py index d0db15480..e5f33ed93 100644 --- a/software/control/gui_hcs.py +++ b/software/control/gui_hcs.py @@ -1,7 +1,7 @@ # set QT_API environment variable import os -import control.lighting +import control.illumination os.environ["QT_API"] = "pyqt5" import serial @@ -23,7 +23,7 @@ import squid.config import squid.stage.utils import control.microscope -from control.lighting import LightSourceType, IntensityControlMode, ShutterControlMode, IlluminationController +from control.illumination import LightSourceType, IntensityControlMode, ShutterControlMode, IlluminationController import squid.camera.utils log = squid.logging.get_logger(__name__) @@ -343,7 +343,7 @@ def loadSimulationObjects(self): if USE_LDI_SERIAL_CONTROL: self.ldi = serial_peripherals.LDI_Simulation() - self.illuminationController = control.lighting.IlluminationController( + self.illuminationController = control.illumination.IlluminationController( self.microcontroller, self.ldi.intensity_mode, self.ldi.shutter_mode, LightSourceType.LDI, self.ldi ) if USE_ZABER_EMISSION_FILTER_WHEEL: @@ -428,9 +428,9 @@ def loadHardwareObjects(self): if USE_CELESTA_ETHENET_CONTROL: try: - import control.celesta + import control.illumination_celesta - self.celesta = control.celesta.CELESTA() + self.celesta = control.illumination_celesta.CELESTA() self.illuminationController = IlluminationController( self.microcontroller, IntensityControlMode.Software, @@ -442,6 +442,22 @@ def loadHardwareObjects(self): self.log.error("Error initializing CELESTA") raise + if USE_VORTRAN_LASER_USB_CONTROL: + try: + import control.illumination_versalase + + self.versalase = control.illumination_versalase.VersaLase() + self.illuminationController = IlluminationController( + self.microcontroller, + IntensityControlMode.Software, + ShutterControlMode.TTL if VORTRAN_SHUTTER_CONTROL_MODE == "EXT" else ShutterControlMode.Software, + LightSourceType.VersaLase, + self.versalase, + ) + except Exception: + self.log.error("Error initializing VersaLase") + raise + if USE_ZABER_EMISSION_FILTER_WHEEL: try: self.emission_filter_wheel = serial_peripherals.FilterController( diff --git a/software/control/lighting.py b/software/control/illumination.py similarity index 100% rename from software/control/lighting.py rename to software/control/illumination.py diff --git a/software/control/celesta.py b/software/control/illumination_celesta.py similarity index 99% rename from software/control/celesta.py rename to software/control/illumination_celesta.py index 459a84b28..fc03c74c3 100755 --- a/software/control/celesta.py +++ b/software/control/illumination_celesta.py @@ -10,7 +10,7 @@ import urllib.request import traceback from squid.abc import LightSource -from control.lighting import ShutterControlMode +from control.illumination import ShutterControlMode import squid.logging diff --git a/software/control/illumination_versalase.py b/software/control/illumination_versalase.py new file mode 100644 index 000000000..4451d1a88 --- /dev/null +++ b/software/control/illumination_versalase.py @@ -0,0 +1,172 @@ +from laser_sdk import LaserSDK + +from squid.abc import LightSource +from control.illumination import ShutterControlMode, IntensityControlMode + +import squid.logging + + +class VersaLase(LightSource): + def __init__(self, **kwds): + self._log = squid.logging.get_logger(__name__) + + self.sdk = LaserSDK() + self.sdk.discover() + + self.channel_mappings = { + 405: None, + 470: None, + 488: None, + 545: None, + 550: None, + 555: None, + 561: None, + 638: None, + 640: None, + 730: None, + 735: None, + 750: None, + } + + try: + self.initialize() + except Exception as e: + self._log.error(f"Failed to initialize Vortran laser: {e}") + + def initialize(self) -> bool: + """ + Initialize the connection and settings for the Vortran laser. + Returns True if successful, False otherwise. + """ + try: + # Query information about installed lasers + for laser in self.sdk.get_lasers(): + self._log.info(f"Found laser {laser.wavelength}: {laser.max_power}") + laser.disable() + if laser.wavelength == 405: + self.channel_mappings[405] = laser.id + elif laser.wavelength == 488: + self.channel_mappings[470] = laser.id + self.channel_mappings[488] = laser.id + elif laser.wavelength == 545: + self.channel_mappings[545] = laser.id + self.channel_mappings[550] = laser.id + self.channel_mappings[555] = laser.id + self.channel_mappings[561] = laser.id + elif laser.wavelength == 638: + self.channel_mappings[638] = laser.id + self.channel_mappings[640] = laser.id + return True + + except Exception as e: + self._log.error(f"Initialization failed: {e}") + return False + + def set_intensity_control_mode(self, mode: IntensityControlMode): + """ + Set intensity control mode. Only software intensity control is supported for Vortran laser. + + Args: + mode: IntensityControlMode.Software or IntensityControlMode.External + """ + self._log.debug("Only software intensity control is supported for Vortran laser") + pass + + def get_intensity_control_mode(self) -> IntensityControlMode: + """ + Get current intensity control mode. Only software intensity control is supported for Vortran laser. + + Returns: + IntensityControlMode enum value + """ + return IntensityControlMode.Software + + def set_shutter_control_mode(self, mode: ShutterControlMode): + """ + Set shutter control mode for all lasers. + + Args: + mode: ShutterControlMode enum + """ + for laser in self.sdk.get_lasers(): + laser.set_digital_mode(mode == ShutterControlMode.TTL) + + self.shutter_mode = mode + + def get_shutter_control_mode(self) -> ShutterControlMode: + """ + Get current shutter control mode. + + Returns: + ShutterControlMode enum value + """ + # The lasers in the VersaLase may have different shutter control states. + # We call set_shutter_control_mode() on initialize so they should all be the same. + # Raise an error here if they are not. + digital_mode = None + for laser in self.sdk.get_lasers(): + if digital_mode is None: + digital_mode = laser.digital_mode + elif digital_mode != laser.digital_mode: + raise ValueError("Laser shutter control modes are not consistent") + if digital_mode is None: + raise ValueError("No lasers found") + + return ShutterControlMode.TTL if digital_mode else ShutterControlMode.Software + + def set_shutter_state(self, channel: int, state: bool): + """ + Turn a specific channel on or off. + + Args: + channel: Channel ID (letter or wavelength) + state: True to turn on, False to turn off + """ + laser = self.sdk.get_laser_by_id(self.channel_mappings[channel]) + laser.enable(state) + + def get_shutter_state(self, channel: int) -> bool: + """ + Get the current shutter state of a specific channel. + + Args: + channel: Channel ID (letter or wavelength) + + Returns: + bool: True if channel is on, False if off + """ + laser = self.sdk.get_laser_by_id(self.channel_mappings[channel]) + return laser.get_emission_status() + + def set_intensity(self, channel: int, intensity: float): + """ + Set the intensity for a specific channel. + + Args: + channel: Channel ID (letter or wavelength) + intensity: Intensity value (0-100 percent) + """ + laser = self.sdk.get_laser_by_id(self.channel_mappings[channel]) + laser.set_power(laser.max_power * intensity / 100.0) + + def get_intensity(self, channel: int) -> float: + """ + Get the current intensity of a specific channel. + + Args: + channel: Channel ID (letter or wavelength) + + Returns: + float: Current intensity value (0-100 percent) + """ + # For Vortran laser, we are able to get the actual intensity of the lasers. + # To keep consistency with other light sources, we return the set power/intensity here. + laser = self.sdk.get_laser_by_id(self.channel_mappings[channel]) + laser_info = laser.get_op2() + return laser_info["LaserSetPower"] / laser.max_power * 100.0 + + def shut_down(self): + """Safely shut down the Vortran laser.""" + for laser in self.sdk.get_lasers(): + laser.disable() + laser.disconnect() diff --git a/software/control/microscope.py b/software/control/microscope.py index a922cd11b..bc5929f1f 100644 --- a/software/control/microscope.py +++ b/software/control/microscope.py @@ -16,7 +16,7 @@ import squid.stage.utils import control.microcontroller as microcontroller -from control.lighting import LightSourceType, IntensityControlMode, ShutterControlMode, IlluminationController +from control.illumination import LightSourceType, IntensityControlMode, ShutterControlMode, IlluminationController from control.piezo import PiezoStage import control.serial_peripherals as serial_peripherals import control.filterwheel as filterwheel @@ -260,9 +260,9 @@ def initialize_peripherals(self): if USE_CELESTA_ETHENET_CONTROL: try: - import control.celesta + import control.illumination_celesta - self.celesta = control.celesta.CELESTA() + self.celesta = control.illumination_celesta.CELESTA() self.illuminationController = IlluminationController( self.microcontroller, IntensityControlMode.Software, diff --git a/software/control/serial_peripherals.py b/software/control/serial_peripherals.py index 9ac941083..a49a1ce24 100644 --- a/software/control/serial_peripherals.py +++ b/software/control/serial_peripherals.py @@ -3,7 +3,7 @@ import time from typing import Tuple, Optional import struct -from control.lighting import LightSourceType, IntensityControlMode, ShutterControlMode +from control.illumination import LightSourceType, IntensityControlMode, ShutterControlMode from control._def import * from squid.abc import LightSource diff --git a/software/tests/control/gui_test_stubs.py b/software/tests/control/gui_test_stubs.py index b7b6b8669..286bdd051 100644 --- a/software/tests/control/gui_test_stubs.py +++ b/software/tests/control/gui_test_stubs.py @@ -2,7 +2,7 @@ import control.core.core import control.microcontroller -import control.lighting +import control.illumination import squid.abc import control._def @@ -34,11 +34,11 @@ def get_test_configuration_manager() -> control.core.core.ConfigurationManager: def get_test_illumination_controller( microcontroller: control.microcontroller.Microcontroller, -) -> control.lighting.IlluminationController: - return control.lighting.IlluminationController( +) -> control.illumination.IlluminationController: + return control.illumination.IlluminationController( microcontroller=microcontroller, - intensity_control_mode=control.lighting.IntensityControlMode.Software, - shutter_control_mode=control.lighting.ShutterControlMode.Software, + intensity_control_mode=control.illumination.IntensityControlMode.Software, + shutter_control_mode=control.illumination.ShutterControlMode.Software, ) diff --git a/software/tools/evaluate_intensity_calibration.py b/software/tools/evaluate_intensity_calibration.py index 79693d5ce..a9f70c8a5 100644 --- a/software/tools/evaluate_intensity_calibration.py +++ b/software/tools/evaluate_intensity_calibration.py @@ -12,7 +12,7 @@ os.chdir(software_dir) from PM16 import PM16 -from control.lighting import IlluminationController, IntensityControlMode, ShutterControlMode +from control.illumination import IlluminationController, IntensityControlMode, ShutterControlMode import control.microcontroller as microcontroller from control._def import * diff --git a/software/tools/generate_intensity_calibrations.py b/software/tools/generate_intensity_calibrations.py index f1f46dcbd..29dd95270 100644 --- a/software/tools/generate_intensity_calibrations.py +++ b/software/tools/generate_intensity_calibrations.py @@ -12,7 +12,7 @@ os.chdir(software_dir) from PM16 import PM16 -from control.lighting import IlluminationController, IntensityControlMode, ShutterControlMode +from control.illumination import IlluminationController, IntensityControlMode, ShutterControlMode import control.microcontroller as microcontroller from control._def import *