Skip to content
Open
Show file tree
Hide file tree
Changes from 7 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions software/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,18 @@ Follow the instructions during the installation.

</details>

<details>
<summary>Installing SDK for VersaLase laser</summary>

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
```

</details>

## 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`)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
10 changes: 6 additions & 4 deletions software/control/_def.py
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
20 changes: 18 additions & 2 deletions software/control/gui_hcs.py
Original file line number Diff line number Diff line change
Expand Up @@ -428,9 +428,9 @@ def loadHardwareObjects(self):

if USE_CELESTA_ETHENET_CONTROL:
try:
import control.celesta
import control.lighting_celesta

self.celesta = control.celesta.CELESTA()
self.celesta = control.lighting_celesta.CELESTA()
self.illuminationController = IlluminationController(
self.microcontroller,
IntensityControlMode.Software,
Expand All @@ -442,6 +442,22 @@ def loadHardwareObjects(self):
self.log.error("Error initializing CELESTA")
raise

if USE_VORTRAN_LASER_USB_CONTROL:
try:
import control.lighting_versalase

self.versalase = control.lighting_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(
Expand Down
File renamed without changes.
172 changes: 172 additions & 0 deletions software/control/lighting_versalase.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,172 @@
from laser_sdk import LaserSDK

from squid.abc import LightSource
from control.lighting 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
Copy link

Copilot AI Jun 17, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The set_intensity_control_mode method is not implemented and only contains a debug log and pass. Consider implementing the functionality or raising a NotImplementedError to clearly indicate that this mode is unsupported.

Suggested change
pass
raise NotImplementedError("Only software intensity control is supported for Vortran laser.")

Copilot uses AI. Check for mistakes.


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()
4 changes: 2 additions & 2 deletions software/control/microscope.py
Original file line number Diff line number Diff line change
Expand Up @@ -260,9 +260,9 @@ def initialize_peripherals(self):

if USE_CELESTA_ETHENET_CONTROL:
try:
import control.celesta
import control.lighting_celesta

self.celesta = control.celesta.CELESTA()
self.celesta = control.lighting_celesta.CELESTA()
self.illuminationController = IlluminationController(
self.microcontroller,
IntensityControlMode.Software,
Expand Down