From d3912f13319bbfe0a4feefe52daf4dfd95e94814 Mon Sep 17 00:00:00 2001 From: Conor McFadden Date: Wed, 16 Oct 2024 08:55:29 -0500 Subject: [PATCH 1/9] Close all NI Tasks I would like to make the __del__ call consistent for all devices, but I will have to go through the stages to do this. Co-Authored-By: Kevin M. Dean --- .../model/devices/camera/hamamatsu.py | 2 +- src/navigate/model/devices/daq/ni.py | 21 +++++++++++++--- src/navigate/model/devices/filter_wheel/ni.py | 10 ++++++++ src/navigate/model/devices/galvo/ni.py | 9 +++++-- src/navigate/model/devices/lasers/ni.py | 15 +++++++++++ .../remote_focus/equipment_solutions.py | 15 ++++------- src/navigate/model/devices/remote_focus/ni.py | 6 +++++ src/navigate/model/devices/shutter/ni.py | 9 ++++++- src/navigate/model/devices/stages/ni.py | 10 ++++++++ src/navigate/model/microscope.py | 25 +++++++++++++------ 10 files changed, 98 insertions(+), 24 deletions(-) diff --git a/src/navigate/model/devices/camera/hamamatsu.py b/src/navigate/model/devices/camera/hamamatsu.py index f71afbbcf..972c0d96f 100644 --- a/src/navigate/model/devices/camera/hamamatsu.py +++ b/src/navigate/model/devices/camera/hamamatsu.py @@ -143,7 +143,7 @@ def __str__(self): def __del__(self): """Delete HamamatsuOrca class.""" - pass + self.close_camera() @property def serial_number(self): diff --git a/src/navigate/model/devices/daq/ni.py b/src/navigate/model/devices/daq/ni.py index 9034d7836..88620a0a4 100644 --- a/src/navigate/model/devices/daq/ni.py +++ b/src/navigate/model/devices/daq/ni.py @@ -110,8 +110,23 @@ def __str__(self) -> str: def __del__(self) -> None: """Destructor.""" - if self.camera_trigger_task is not None: - self.stop_acquisition() + for task in [self.camera_trigger_task, self.master_trigger_task, self.laser_switching_task]: + if task: + try: + task.stop() + task.close() + except Exception: + logger.exception(f"Error stopping task: {traceback.format_exc()}") + + if self.analog_output_tasks: + for k, task in self.analog_output_tasks.items(): + if task: + try: + task.stop() + task.close() + except Exception: + logger.exception(f"Error stopping task: {traceback.format_exc()}") + def set_external_trigger(self, external_trigger=None) -> None: """Set trigger mode. @@ -220,7 +235,7 @@ def wait_for_external_trigger( return False # Create a digital input task and wait until either a trigger is detected, # or the timeout is exceeded. If timeout < 0, wait forever... - external_trigger_task = nidaqmx.Task("WaitDigitalEdge") + external_trigger_task = nidaqmx.Task() external_trigger_task.di_channels.add_di_chan(trigger_channel) total_wait_time = 0.0 diff --git a/src/navigate/model/devices/filter_wheel/ni.py b/src/navigate/model/devices/filter_wheel/ni.py index 7dc1fde4b..fa31da79d 100644 --- a/src/navigate/model/devices/filter_wheel/ni.py +++ b/src/navigate/model/devices/filter_wheel/ni.py @@ -33,6 +33,7 @@ # Standard Library Imports import logging import time +import traceback # Third Party Imports import nidaqmx @@ -150,3 +151,12 @@ def close(self): Sets the filter wheel to the home position and then closes the port. """ pass + + def __del__(self): + """Delete the DAQFilterWheel object.""" + if self.filter_wheel_task: + try: + self.filter_wheel_task.stop() + self.filter_wheel_task.close() + except Exception: + logger.exception(f"Error stopping task: {traceback.format_exc()}") diff --git a/src/navigate/model/devices/galvo/ni.py b/src/navigate/model/devices/galvo/ni.py index 40cfe71b3..2f097c76d 100644 --- a/src/navigate/model/devices/galvo/ni.py +++ b/src/navigate/model/devices/galvo/ni.py @@ -32,6 +32,7 @@ # Standard Library Imports import logging +import traceback from typing import Any, Dict # Third Party Imports @@ -129,5 +130,9 @@ def turn_off(self) -> None: task.write([0], auto_start=True) task.stop() task.close() - except Exception as e: - print(f"Galvo turn_off error: {e}") + except Exception: + logger.exception(f"Error stopping task: {traceback.format_exc()}") + + def __del__(self): + """Close the GalvoNI at exit.""" + self.turn_off() diff --git a/src/navigate/model/devices/lasers/ni.py b/src/navigate/model/devices/lasers/ni.py index 7659cc557..2eee40433 100644 --- a/src/navigate/model/devices/lasers/ni.py +++ b/src/navigate/model/devices/lasers/ni.py @@ -32,6 +32,7 @@ # Standard Library Imports import logging +import traceback from typing import Any, Dict # Third Party Imports @@ -234,3 +235,17 @@ def close(self) -> None: self.laser_do_task.close() except DaqError as e: logger.exception(e) + + def __del__(self): + """Delete the NI Task before exit.""" + if self.laser_ao_task: + try: + self.laser_ao_task.close() + except Exception as e: + logger.exception(f"Error stopping task: {traceback.format_exc()}") + + if self.laser_do_task: + try: + self.laser_do_task.close() + except Exception as e: + logger.exception(f"Error stopping task: {traceback.format_exc()}") \ No newline at end of file diff --git a/src/navigate/model/devices/remote_focus/equipment_solutions.py b/src/navigate/model/devices/remote_focus/equipment_solutions.py index 333de840d..e93feb5ba 100644 --- a/src/navigate/model/devices/remote_focus/equipment_solutions.py +++ b/src/navigate/model/devices/remote_focus/equipment_solutions.py @@ -149,9 +149,11 @@ def __init__( def __del__(self): """Close the RemoteFocusEquipmentSolutions Class""" - logger.debug("Closing RemoteFocusEquipmentSolutions Serial Port") - self.close_connection() - + try: + self.send_command("k0\r") + self.serial.close() + except Exception: + pass def read_bytes(self, num_bytes: int) -> bytes: """Read the specified number of bytes from RemoteFocusEquipmentSolutions. @@ -222,13 +224,6 @@ def send_command(self, message: str): "Error in communicating with Voice Coil via COMPORT", self.comport ) - def close_connection(self): - """Close RemoteFocusEquipmentSolutions class""" - try: - self.send_command("k0\r") - self.serial.close() - except Exception: - pass if __name__ == "__main__": diff --git a/src/navigate/model/devices/remote_focus/ni.py b/src/navigate/model/devices/remote_focus/ni.py index 0d2ddbaca..ec5ac0473 100644 --- a/src/navigate/model/devices/remote_focus/ni.py +++ b/src/navigate/model/devices/remote_focus/ni.py @@ -80,6 +80,12 @@ def __init__( #: str: The board name. self.board_name = self.device_config["hardware"]["channel"].split("/")[0] + def __del__(self): + """Delete the RemoteFocusNI object. + + Deletion of the NIDAQ task is handled by the NIDAQ object.""" + pass + def adjust(self, exposure_times, sweep_times, offset=None): """Adjust the waveform. diff --git a/src/navigate/model/devices/shutter/ni.py b/src/navigate/model/devices/shutter/ni.py index 5021f64ce..ff3329c69 100644 --- a/src/navigate/model/devices/shutter/ni.py +++ b/src/navigate/model/devices/shutter/ni.py @@ -32,6 +32,7 @@ # Standard Library Imports import logging +import traceback from typing import Any, Dict # Third Party Imports @@ -88,7 +89,13 @@ def __init__( def __del__(self): """Close the ShutterTTL at exit.""" - self.shutter_task.close() + if self.shutter_task: + try: + self.shutter_task.stop() + self.shutter_task.close() + except Exception: + logger.exception(f"Error stopping task: {traceback.format_exc()}") + def open_shutter(self): """Open the shutter""" diff --git a/src/navigate/model/devices/stages/ni.py b/src/navigate/model/devices/stages/ni.py index 5db268567..84c11b30b 100644 --- a/src/navigate/model/devices/stages/ni.py +++ b/src/navigate/model/devices/stages/ni.py @@ -32,6 +32,7 @@ # Standard Library Imports import logging +import traceback from multiprocessing.managers import ListProxy import time from typing import Any, Dict @@ -322,3 +323,12 @@ def switch_mode(self, mode="normal", exposure_times=None, sweep_times=None): self.ao_task.stop() self.ao_task.close() self.ao_task = None + + def close(self) -> None: + """Close the Galvo stage.""" + if self.ao_task: + try: + self.ao_task.stop() + self.ao_task.close() + except Exception: + logger.exception(f"Error stopping task: {traceback.format_exc()}") diff --git a/src/navigate/model/microscope.py b/src/navigate/model/microscope.py index 7d96d18bf..6904beeff 100644 --- a/src/navigate/model/microscope.py +++ b/src/navigate/model/microscope.py @@ -99,12 +99,18 @@ def __init__( #: obj: Camera object. self.camera = None + #: Any: Shutter device. + self.shutter = None + #: dict: Dictionary of lasers. self.lasers = {} #: dict: Dictionary of galvanometers. self.galvo = {} + #: Any: Remote focus device. + self.remote_focus_device = None + #: dict: Dictionary of filter_wheels self.filter_wheel = {} @@ -981,16 +987,21 @@ def load_and_start_devices( def terminate(self) -> None: """Close hardware explicitly.""" + self.camera.close_camera() + del self.daq - for k in self.galvo: - self.galvo[k].turn_off() + for key in list(self.lasers.keys()): + del self.lasers[key] - try: - # Currently only for RemoteFocusEquipmentSolutions - self.remote_focus_device.close_connection() - except AttributeError: - pass + for key in list(self.galvo.keys()): + del self.galvo[key] + + # filter wheels too? + + del self.shutter + + del self.remote_focus_device try: for stage, _ in self.stages_list: From 78f32555c326455de2e81150a080610341ffba51 Mon Sep 17 00:00:00 2001 From: Kevin Dean <42547789+AdvancedImagingUTSW@users.noreply.github.com> Date: Wed, 16 Oct 2024 12:01:47 -0500 Subject: [PATCH 2/9] Consistent __del__ approach for all devices. I will clean up some of the close calls when I create an abstract base class later on. --- .../model/devices/filter_wheel/asi.py | 4 ++++ .../model/devices/filter_wheel/base.py | 6 ++++- .../model/devices/filter_wheel/ludl.py | 5 ++++ src/navigate/model/devices/filter_wheel/ni.py | 4 ++-- .../model/devices/filter_wheel/sutter.py | 7 +++++- .../model/devices/filter_wheel/synthetic.py | 4 ++++ src/navigate/model/devices/mirrors/base.py | 6 ++++- src/navigate/model/devices/mirrors/imop.py | 4 ++++ .../model/devices/mirrors/synthetic.py | 6 ++++- .../model/devices/stages/asi_MSTwoThousand.py | 4 ++-- src/navigate/model/devices/stages/base.py | 4 ++++ src/navigate/model/devices/stages/mcl.py | 7 ++++++ src/navigate/model/devices/stages/ni.py | 4 ++++ src/navigate/model/devices/stages/pi.py | 7 +++--- .../model/devices/stages/synthetic.py | 4 ++++ .../model/devices/stages/tl_kcube_inertial.py | 6 ++--- .../devices/stages/tl_kcube_steppermotor.py | 4 ++-- src/navigate/model/devices/zoom/base.py | 6 ++++- src/navigate/model/devices/zoom/dynamixel.py | 5 +++- src/navigate/model/microscope.py | 23 +++++++++++-------- 20 files changed, 92 insertions(+), 28 deletions(-) diff --git a/src/navigate/model/devices/filter_wheel/asi.py b/src/navigate/model/devices/filter_wheel/asi.py index b377f0f10..fe20dcad6 100644 --- a/src/navigate/model/devices/filter_wheel/asi.py +++ b/src/navigate/model/devices/filter_wheel/asi.py @@ -170,6 +170,10 @@ def close(self): logger.debug("ASI Filter Wheel - Closing Device.") self.filter_wheel.disconnect_from_serial() + def __del__(self): + """Destructor for the ASIFilterWheel class.""" + self.close() + @log_initialization class ASICubeSlider(FilterWheelBase): diff --git a/src/navigate/model/devices/filter_wheel/base.py b/src/navigate/model/devices/filter_wheel/base.py index e4a057837..f17568777 100644 --- a/src/navigate/model/devices/filter_wheel/base.py +++ b/src/navigate/model/devices/filter_wheel/base.py @@ -72,10 +72,14 @@ def __init__(self, device_connection, device_config): #: int: index of filter wheel self.filter_wheel_number = device_config["hardware"]["wheel_number"] - def __str__(self): + def __str__(self) -> str: """Return the string representation of the FilterWheelBase class.""" return "FilterWheelBase" + def __del__(self) -> None: + """Destructor for the FilterWheelBase class.""" + pass + def check_if_filter_in_filter_dictionary(self, filter_name: str) -> bool: """Checks if the filter designation (string) given exists in the filter dictionary diff --git a/src/navigate/model/devices/filter_wheel/ludl.py b/src/navigate/model/devices/filter_wheel/ludl.py index 54d247679..d09f17401 100644 --- a/src/navigate/model/devices/filter_wheel/ludl.py +++ b/src/navigate/model/devices/filter_wheel/ludl.py @@ -161,3 +161,8 @@ def close(self): logger.debug("LUDLFilterWheel - Closing the Filter Wheel Serial Port") self.set_filter(list(self.filter_dictionary.keys())[0]) self.serial.close() + + def __del__(self): + """Destructor for the LUDLFilterWheel class.""" + if self.serial.is_open: + self.close() diff --git a/src/navigate/model/devices/filter_wheel/ni.py b/src/navigate/model/devices/filter_wheel/ni.py index fa31da79d..30efec4c4 100644 --- a/src/navigate/model/devices/filter_wheel/ni.py +++ b/src/navigate/model/devices/filter_wheel/ni.py @@ -145,14 +145,14 @@ def set_filter(self, filter_name, wait_until_done=True): except DaqError as e: logger.debug(e) - def close(self): + def close(self) -> None: """Close the DAQ Filter Wheel Sets the filter wheel to the home position and then closes the port. """ pass - def __del__(self): + def __del__(self) -> None: """Delete the DAQFilterWheel object.""" if self.filter_wheel_task: try: diff --git a/src/navigate/model/devices/filter_wheel/sutter.py b/src/navigate/model/devices/filter_wheel/sutter.py index 8e43dd7bf..2500b0d5a 100644 --- a/src/navigate/model/devices/filter_wheel/sutter.py +++ b/src/navigate/model/devices/filter_wheel/sutter.py @@ -285,7 +285,7 @@ def read(self, num_bytes): ) return self.serial.read(num_bytes) - def close(self): + def close(self) -> None: """Close the SutterFilterWheel serial port. Sets the filter wheel to the Empty-Alignment position and then closes the port. @@ -293,3 +293,8 @@ def close(self): logger.debug("SutterFilterWheel - Closing the Filter Wheel Serial Port") self.set_filter(list(self.filter_dictionary.keys())[0]) self.serial.close() + + def __del__(self) -> None: + """Delete the SutterFilterWheel class.""" + if self.serial.is_open(): + self.close() diff --git a/src/navigate/model/devices/filter_wheel/synthetic.py b/src/navigate/model/devices/filter_wheel/synthetic.py index 3ac1be840..8a8fb0533 100644 --- a/src/navigate/model/devices/filter_wheel/synthetic.py +++ b/src/navigate/model/devices/filter_wheel/synthetic.py @@ -108,3 +108,7 @@ def close(self): Sets the filter wheel to the Empty-Alignment position and then closes the port. """ pass + + def __del__(self): + """Delete the SyntheticFilterWheel.""" + pass diff --git a/src/navigate/model/devices/mirrors/base.py b/src/navigate/model/devices/mirrors/base.py index c4b183a97..3035f662d 100644 --- a/src/navigate/model/devices/mirrors/base.py +++ b/src/navigate/model/devices/mirrors/base.py @@ -57,7 +57,7 @@ def __init__(self, microscope_name, device_connection, configuration): Name of microscope in configuration device_connection : object Hardware device to connect to - configuration : multiprocesing.managers.DictProxy + configuration : multiprocessing.managers.DictProxy Global configuration of the microscope """ if microscope_name not in configuration["configuration"]["microscopes"].keys(): @@ -81,3 +81,7 @@ def __init__(self, microscope_name, device_connection, configuration): def __str__(self): """Return the string representation of the mirror.""" return "MirrorBase" + + def __del__(self) -> None: + """Delete the MirrorBase class.""" + pass diff --git a/src/navigate/model/devices/mirrors/imop.py b/src/navigate/model/devices/mirrors/imop.py index 26dba884c..11b593252 100644 --- a/src/navigate/model/devices/mirrors/imop.py +++ b/src/navigate/model/devices/mirrors/imop.py @@ -73,6 +73,10 @@ def __init__(self, microscope_name, device_connection, configuration): # flatten the mirror self.flat() + def __del__(self) -> None: + """Delete the ImagineOpticsMirror class.""" + pass + def flat(self): """Move the mirror to the flat position.""" self.mirror_controller.flat() diff --git a/src/navigate/model/devices/mirrors/synthetic.py b/src/navigate/model/devices/mirrors/synthetic.py index 6a3af4ef7..66549d94c 100644 --- a/src/navigate/model/devices/mirrors/synthetic.py +++ b/src/navigate/model/devices/mirrors/synthetic.py @@ -65,6 +65,10 @@ def __init__(self, microscope_name, device_connection, configuration): #: bool: Is this a synthetic mirror? self.is_synthetic = True - def flat(self): + def flat(self) -> None: """Flat the mirror.""" pass + + def __del__(self) -> None: + """Delete the object.""" + pass diff --git a/src/navigate/model/devices/stages/asi_MSTwoThousand.py b/src/navigate/model/devices/stages/asi_MSTwoThousand.py index 269d07388..9bada2f24 100644 --- a/src/navigate/model/devices/stages/asi_MSTwoThousand.py +++ b/src/navigate/model/devices/stages/asi_MSTwoThousand.py @@ -169,14 +169,14 @@ def __init__( # Speed optimizations - Set speed to 90% of maximum on each axis self.set_speed(percent=0.9) - def __del__(self): + def __del__(self) -> None: """Delete the ASI Stage connection.""" try: if self.ms2000_controller is not None: self.ms2000_controller.disconnect_from_serial() logger.debug("ASI stage connection closed") except (AttributeError, BaseException) as e: - logger.error("ASI Stage Exception", e) + logger.exception("ASI Stage Exception", e) raise e def get_axis_position(self, axis): diff --git a/src/navigate/model/devices/stages/base.py b/src/navigate/model/devices/stages/base.py index cab5c204f..5ad9fa1d1 100644 --- a/src/navigate/model/devices/stages/base.py +++ b/src/navigate/model/devices/stages/base.py @@ -121,6 +121,10 @@ def __init__( #: bool: Whether the stage has limits enabled or not. Default is True. self.stage_limits = True + def __del__(self): + """Destructor for the StageBase class.""" + pass + def __str__(self): """Return a string representation of the stage.""" return "StageBase" diff --git a/src/navigate/model/devices/stages/mcl.py b/src/navigate/model/devices/stages/mcl.py index 31309886f..049a5ee6d 100644 --- a/src/navigate/model/devices/stages/mcl.py +++ b/src/navigate/model/devices/stages/mcl.py @@ -109,6 +109,13 @@ def __init__(self, microscope_name, device_connection, configuration, device_id= axis: axes_mapping[axis] for axis in self.axes if axis in axes_mapping } + def __del__(self): + """Close the connection to the stage.""" + try: + self.mcl_controller.MCL_ReleaseHandle(self.handle) + except self.mcl_controller.MadlibError as e: + logger.exception(f"{e}") + def report_position(self): """Report the position of the stage. diff --git a/src/navigate/model/devices/stages/ni.py b/src/navigate/model/devices/stages/ni.py index 84c11b30b..190a189de 100644 --- a/src/navigate/model/devices/stages/ni.py +++ b/src/navigate/model/devices/stages/ni.py @@ -332,3 +332,7 @@ def close(self) -> None: self.ao_task.close() except Exception: logger.exception(f"Error stopping task: {traceback.format_exc()}") + + def __del__(self) -> None: + """Close the Galvo stage.""" + self.close() diff --git a/src/navigate/model/devices/stages/pi.py b/src/navigate/model/devices/stages/pi.py index cf8a129c1..cc75edadd 100644 --- a/src/navigate/model/devices/stages/pi.py +++ b/src/navigate/model/devices/stages/pi.py @@ -136,7 +136,7 @@ def __init__( #: list: List of PI axes available. self.pi_axes = list(self.axes_mapping.values()) - def __del__(self): + def __del__(self) -> None: """Delete the PI Connection Raises @@ -147,10 +147,11 @@ def __del__(self): try: if hasattr(self, "pi_device"): self.stop() + self.pi_device.CloseConnection() logger.debug("PI connection closed") - except (AttributeError, GCSError) as e: # except BaseException: + except (AttributeError, GCSError) as e: print("Error while disconnecting the PI stage") - logger.error(f"Error while disconnecting the PI stage - {e}") + logger.exception(f"Error while disconnecting the PI stage - {e}") raise e def report_position(self): diff --git a/src/navigate/model/devices/stages/synthetic.py b/src/navigate/model/devices/stages/synthetic.py index dbf3462be..0f9377881 100644 --- a/src/navigate/model/devices/stages/synthetic.py +++ b/src/navigate/model/devices/stages/synthetic.py @@ -79,6 +79,10 @@ def __init__(self, microscope_name, device_connection, configuration, device_id= self.volts_per_micron = "0.1 * x" self.camera_delay = 0.01 + def __del__(self) -> None: + """Destructor.""" + pass + def report_position(self): """Report the current position of the stage. diff --git a/src/navigate/model/devices/stages/tl_kcube_inertial.py b/src/navigate/model/devices/stages/tl_kcube_inertial.py index 8852162d8..d7aa1d495 100644 --- a/src/navigate/model/devices/stages/tl_kcube_inertial.py +++ b/src/navigate/model/devices/stages/tl_kcube_inertial.py @@ -111,7 +111,7 @@ def __init__(self, microscope_name, device_connection, configuration, device_id= self.kim_axes = list(self.axes_mapping.values()) if device_connection is not None: - #: object: Thorlabs KIM Stage controller + #: navigate.model.devices.APIs.thorlabs.kcube_inertial: Thorlabs KIM Stage self.kim_controller = device_connection device_config = configuration["configuration"]["microscopes"][microscope_name][ @@ -128,8 +128,8 @@ def __del__(self): try: self.stop() self.kim_controller.KIM_Close(self.serial_number) - except AttributeError: - pass + except Exception as e: + logger.exception(e) def report_position(self): """Report the position of the stage. diff --git a/src/navigate/model/devices/stages/tl_kcube_steppermotor.py b/src/navigate/model/devices/stages/tl_kcube_steppermotor.py index a8c7d6244..76095e0e6 100644 --- a/src/navigate/model/devices/stages/tl_kcube_steppermotor.py +++ b/src/navigate/model/devices/stages/tl_kcube_steppermotor.py @@ -151,8 +151,8 @@ def __del__(self): try: self.stop() self.kst_controller.KST_Close(self.serial_number) - except AttributeError: - pass + except Exception as e: + logger.exception(e) def report_position(self): """ diff --git a/src/navigate/model/devices/zoom/base.py b/src/navigate/model/devices/zoom/base.py index 6f26c1383..2fbda910f 100644 --- a/src/navigate/model/devices/zoom/base.py +++ b/src/navigate/model/devices/zoom/base.py @@ -72,7 +72,11 @@ def __init__(self, microscope_name, device_controller, configuration): #: float: the desired zoom setting self.zoomvalue = None - def __str__(self): + def __del__(self) -> None: + """Delete the ZoomBase object.""" + pass + + def __str__(self) -> str: """Return the string representation of the ZoomBase object.""" return "ZoomBase" diff --git a/src/navigate/model/devices/zoom/dynamixel.py b/src/navigate/model/devices/zoom/dynamixel.py index 4117e18ee..68dd8427c 100644 --- a/src/navigate/model/devices/zoom/dynamixel.py +++ b/src/navigate/model/devices/zoom/dynamixel.py @@ -143,7 +143,10 @@ def __init__(self, microscope_name, device_connection, configuration): def __del__(self): """Delete the DynamixelZoom Instance""" - self.dynamixel.closePort(self.port_num) + try: + self.dynamixel.closePort(self.port_num) + except Exception as e: + logger.exception(e) def set_zoom(self, zoom, wait_until_done=False): """Change the DynamixelZoom Servo. diff --git a/src/navigate/model/microscope.py b/src/navigate/model/microscope.py index 6904beeff..7cda96f82 100644 --- a/src/navigate/model/microscope.py +++ b/src/navigate/model/microscope.py @@ -988,27 +988,30 @@ def load_and_start_devices( def terminate(self) -> None: """Close hardware explicitly.""" + # TODO: I get a DCAM warning if I call the __del__ method. self.camera.close_camera() + del self.daq - for key in list(self.lasers.keys()): - del self.lasers[key] + for key in list(self.filter_wheel.keys()): + del self.filter_wheel[key] for key in list(self.galvo.keys()): del self.galvo[key] - # filter wheels too? + for key in list(self.lasers.keys()): + del self.lasers[key] - del self.shutter + # mirrors? del self.remote_focus_device - try: - for stage, _ in self.stages_list: - stage.close() - except Exception as e: - print(f"Stage delete failure: {e}") - pass + del self.shutter + + for stage, _ in self.stages_list: + del stage + + # zoom? def run_command(self, command: str, *args) -> None: """Run command. From b778a77711cf24de689d1e2959e94203bd8de534 Mon Sep 17 00:00:00 2001 From: Kevin Dean <42547789+AdvancedImagingUTSW@users.noreply.github.com> Date: Wed, 16 Oct 2024 13:15:07 -0500 Subject: [PATCH 3/9] Test with CT-ASLM-V2 --- .../model/devices/filter_wheel/asi.py | 6 ++-- .../model/devices/filter_wheel/base.py | 13 +++---- .../model/devices/filter_wheel/sutter.py | 36 ++++++++----------- 3 files changed, 24 insertions(+), 31 deletions(-) diff --git a/src/navigate/model/devices/filter_wheel/asi.py b/src/navigate/model/devices/filter_wheel/asi.py index fe20dcad6..6a61a2acd 100644 --- a/src/navigate/model/devices/filter_wheel/asi.py +++ b/src/navigate/model/devices/filter_wheel/asi.py @@ -87,15 +87,15 @@ def __init__(self, device_connection, device_config): Parameters ---------- - device_connection : dict - Dictionary of device connections. + device_connection : TigerController + Communication object for the ASI Filter Wheel. device_config : dict Dictionary of device configuration parameters. """ super().__init__(device_connection, device_config) - #: obj: ASI Tiger Controller object. + #: TigerController: ASI Tiger Controller object. self.filter_wheel = device_connection #: dict: Configuration dictionary. diff --git a/src/navigate/model/devices/filter_wheel/base.py b/src/navigate/model/devices/filter_wheel/base.py index f17568777..7f18cafcb 100644 --- a/src/navigate/model/devices/filter_wheel/base.py +++ b/src/navigate/model/devices/filter_wheel/base.py @@ -32,6 +32,7 @@ # Standard Library Imports import logging +from typing import Any, Dict # Third Party Imports @@ -47,20 +48,20 @@ class FilterWheelBase: """FilterWheelBase - Parent class for controlling filter wheels.""" - def __init__(self, device_connection, device_config): + def __init__(self, device_connection: Any, device_config: Dict[str, Any]) -> None: """Initialize the FilterWheelBase class. Parameters ---------- - device_connection : dict - Dictionary of device connections. - device_config : dict + device_connection : Any + The communication instance with the device. + device_config : Dict[str, Any] Dictionary of device configuration parameters. """ - #: object: Device connection object. + #: Any: Device connection object. self.device_connection = device_connection - #: dict: Dictionary of device configuration parameters. + #: Dict[str, Any]: Dictionary of device configuration parameters. self.device_config = device_config #: dict: Dictionary of filters available on the filter wheel. diff --git a/src/navigate/model/devices/filter_wheel/sutter.py b/src/navigate/model/devices/filter_wheel/sutter.py index 2500b0d5a..eb6c6b2c3 100644 --- a/src/navigate/model/devices/filter_wheel/sutter.py +++ b/src/navigate/model/devices/filter_wheel/sutter.py @@ -62,7 +62,7 @@ def build_filter_wheel_connection(comport, baudrate, timeout=0.25): Returns ------- serial.Serial - Serial port connection to the filter wheel. + The serial port connection to the filter wheel. Raises ------ @@ -94,17 +94,18 @@ def __init__(self, device_connection, device_config): Parameters ---------- - device_connection : dict - Dictionary of device connections. - device_config : dict + device_connection : serial.Serial + The communication instance with the filter wheel. + device_config : Dict[str, Any] Dictionary of device configuration parameters. """ super().__init__(device_connection, device_config) - #: obj: Serial port connection to the filter wheel. + #: serial.Serial: Serial port connection to the filter wheel. self.serial = device_connection - #: dict: Dictionary of filter names and corresponding filter wheel positions. + #: Dict[str, Any]: Dictionary of filter names and corresponding filter wheel + # positions. self.device_config = device_config #: bool: Wait until filter wheel has completed movement. @@ -146,20 +147,11 @@ def __init__(self, device_connection, device_config): # Set filter to the 0th position by default upon initialization. self.set_filter(list(self.filter_dictionary.keys())[0]) - def __str__(self): + def __str__(self) -> str: """String representation of the class.""" return "SutterFilterWheel" - def __enter__(self): - """Enter the SutterFilterWheel context manager.""" - return self - - def __exit__(self, *args, **kwargs): - """Exit the SutterFilterWheel context manager.""" - logger.debug("SutterFilterWheel - Closing Device.") - self.close() - - def filter_change_delay(self, filter_name): + def filter_change_delay(self, filter_name: str) -> None: """Calculate duration of time necessary to change filter wheel positions. Calculate duration of time necessary to change filter wheel positions. @@ -195,7 +187,7 @@ def filter_change_delay(self, filter_name): except IndexError: self.wait_until_done_delay = 0.01 - def set_filter(self, filter_name, wait_until_done=True): + def set_filter(self, filter_name: str, wait_until_done: bool = True): """Change the filter wheel to the filter designated by the filter position argument. @@ -249,7 +241,7 @@ def set_filter(self, filter_name, wait_until_done=True): # read 0D back. self.read(1) - def read(self, num_bytes): + def read(self, num_bytes: int) -> bytes: """Reads the specified number of bytes from the serial port. Parameters @@ -259,8 +251,8 @@ def read(self, num_bytes): Returns ------- - bytes - Bytes read from the serial port. + response : bytes + The bytes read from the serial port. Raises ------ @@ -296,5 +288,5 @@ def close(self) -> None: def __del__(self) -> None: """Delete the SutterFilterWheel class.""" - if self.serial.is_open(): + if self.serial.is_open: self.close() From 118cd4f94f36b52e8734a3fed7474ebbc24c7a83 Mon Sep 17 00:00:00 2001 From: Kevin Dean <42547789+AdvancedImagingUTSW@users.noreply.github.com> Date: Wed, 16 Oct 2024 13:15:08 -0500 Subject: [PATCH 4/9] Update mcl.py --- src/navigate/model/devices/stages/mcl.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/navigate/model/devices/stages/mcl.py b/src/navigate/model/devices/stages/mcl.py index 049a5ee6d..d75687df4 100644 --- a/src/navigate/model/devices/stages/mcl.py +++ b/src/navigate/model/devices/stages/mcl.py @@ -81,8 +81,8 @@ def __init__(self, microscope_name, device_connection, configuration, device_id= ---------- microscope_name : str Name of the microscope. - device_connection : dict - Dictionary containing the connection information for the device. + device_connection : mcl_controller + Communication object for the stage. configuration : dict Dictionary containing the configuration information for the device. device_id : int @@ -94,7 +94,8 @@ def __init__(self, microscope_name, device_connection, configuration, device_id= # Mapping from self.axes to corresponding MCL channels if device_connection is not None: - #: object: MCL controller object. + + #: mcl_controller: MCL controller object. self.mcl_controller = device_connection["controller"] #: int: MCL handle. From 442f5a703812d8c5c5a58e25d7c5bf10295aaffe Mon Sep 17 00:00:00 2001 From: Kevin Dean <42547789+AdvancedImagingUTSW@users.noreply.github.com> Date: Wed, 16 Oct 2024 13:35:51 -0500 Subject: [PATCH 5/9] Update thread_pool.py Eliminate print statements in thread pool... --- src/navigate/controller/thread_pool.py | 17 +++-------------- 1 file changed, 3 insertions(+), 14 deletions(-) diff --git a/src/navigate/controller/thread_pool.py b/src/navigate/controller/thread_pool.py index 1f47c1815..8d06d3bef 100644 --- a/src/navigate/controller/thread_pool.py +++ b/src/navigate/controller/thread_pool.py @@ -97,11 +97,7 @@ def run(self): try: self._target(*self._args, **self._kwargs) except Exception as e: - print( - f"{self.name} thread ended because of exception!: {e}", - traceback.format_exc(), - ) - logger.debug( + logger.exception( f"{self.name} thread ended because of exception!: {e}", traceback.format_exc(), ) @@ -251,13 +247,7 @@ def func(*args, **kwargs): try: target(*args, **kwargs) except Exception as e: - print( - threading.current_thread().name, - "thread exception happened!", - e, - traceback.format_exc(), - ) - logger.debug( + logger.exception( threading.current_thread().name, "thread exception happened!", e, @@ -422,8 +412,7 @@ def localtrace(self, frame, event, arg): """ if event == "exception": - print("****in local trace: exception stops the thread") - logger.debug("****in local trace: exception stops the thread") + logger.exception("****in local trace: exception stops the thread") if os.getenv("GITHUB_ACTIONS") == "true": return raise SystemExit() From 4842a81c7f6aed106cdfbc57696c9a3e42048911 Mon Sep 17 00:00:00 2001 From: Kevin Dean <42547789+AdvancedImagingUTSW@users.noreply.github.com> Date: Wed, 16 Oct 2024 13:38:32 -0500 Subject: [PATCH 6/9] Update thread_pool.py --- src/navigate/controller/thread_pool.py | 1 - 1 file changed, 1 deletion(-) diff --git a/src/navigate/controller/thread_pool.py b/src/navigate/controller/thread_pool.py index 8d06d3bef..77ee98cff 100644 --- a/src/navigate/controller/thread_pool.py +++ b/src/navigate/controller/thread_pool.py @@ -412,7 +412,6 @@ def localtrace(self, frame, event, arg): """ if event == "exception": - logger.exception("****in local trace: exception stops the thread") if os.getenv("GITHUB_ACTIONS") == "true": return raise SystemExit() From 88e8cf24f4ab06502df38797d9713f8507f57502 Mon Sep 17 00:00:00 2001 From: Kevin Dean <42547789+AdvancedImagingUTSW@users.noreply.github.com> Date: Wed, 16 Oct 2024 14:09:06 -0500 Subject: [PATCH 7/9] Update thread_pool.py --- src/navigate/controller/thread_pool.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/navigate/controller/thread_pool.py b/src/navigate/controller/thread_pool.py index 77ee98cff..33bf94ef9 100644 --- a/src/navigate/controller/thread_pool.py +++ b/src/navigate/controller/thread_pool.py @@ -414,6 +414,9 @@ def localtrace(self, frame, event, arg): if event == "exception": if os.getenv("GITHUB_ACTIONS") == "true": return + + # Silence traceback to avoid printing to console. + sys.tracebacklimit = 0 raise SystemExit() return self.localtrace From 62823eeb72df69b566e6c0641f2a072f6f296268 Mon Sep 17 00:00:00 2001 From: Kevin Dean <42547789+AdvancedImagingUTSW@users.noreply.github.com> Date: Tue, 22 Oct 2024 09:43:24 +0200 Subject: [PATCH 8/9] Update test_sutter.py Fix the test... --- test/model/devices/filter_wheel/test_sutter.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/model/devices/filter_wheel/test_sutter.py b/test/model/devices/filter_wheel/test_sutter.py index 73ceb2d77..5b5b45019 100644 --- a/test/model/devices/filter_wheel/test_sutter.py +++ b/test/model/devices/filter_wheel/test_sutter.py @@ -167,7 +167,7 @@ def test_close(self): def test_exit(self): self.mock_device_connection.reset_mock() - self.filter_wheel.__exit__() + del self.filter_wheel self.mock_device_connection.close.assert_called() From a8b9d74a006b9def9289dd5bbefea23e98a24943 Mon Sep 17 00:00:00 2001 From: Kevin Dean <42547789+AdvancedImagingUTSW@users.noreply.github.com> Date: Fri, 8 Nov 2024 12:17:24 -0600 Subject: [PATCH 9/9] Handle DCAM @annie-xd-wang - This seems to be fine now... --- src/navigate/model/devices/camera/hamamatsu.py | 3 ++- src/navigate/model/microscope.py | 18 ++++++------------ 2 files changed, 8 insertions(+), 13 deletions(-) diff --git a/src/navigate/model/devices/camera/hamamatsu.py b/src/navigate/model/devices/camera/hamamatsu.py index 972c0d96f..0419d67ac 100644 --- a/src/navigate/model/devices/camera/hamamatsu.py +++ b/src/navigate/model/devices/camera/hamamatsu.py @@ -143,7 +143,8 @@ def __str__(self): def __del__(self): """Delete HamamatsuOrca class.""" - self.close_camera() + self.camera_controller.dev_close() + # self.close_camera() @property def serial_number(self): diff --git a/src/navigate/model/microscope.py b/src/navigate/model/microscope.py index 3b69f63fe..c35017a1d 100644 --- a/src/navigate/model/microscope.py +++ b/src/navigate/model/microscope.py @@ -1054,12 +1054,14 @@ def load_and_start_devices( self.info[device_name] = device_ref_name def terminate(self) -> None: - """Close hardware explicitly.""" + """Close hardware explicitly. - # TODO: I get a DCAM warning if I call the __del__ method. - self.camera.close_camera() + Closes all devices other than plugin devices and deformable mirrors. + """ - del self.daq + for device in [self.camera, self.daq, self.remote_focus_device, + self.shutter, self.zoom]: + del device for key in list(self.filter_wheel.keys()): del self.filter_wheel[key] @@ -1070,17 +1072,9 @@ def terminate(self) -> None: for key in list(self.lasers.keys()): del self.lasers[key] - # mirrors? - - del self.remote_focus_device - - del self.shutter - for stage, _ in self.stages_list: del stage - # zoom? - def run_command(self, command: str, *args) -> None: """Run command.