From dbef423136a92d27889b2fc17ec9b1e3bb2b1f6f Mon Sep 17 00:00:00 2001 From: unknown Date: Fri, 22 Aug 2025 13:56:11 -0500 Subject: [PATCH 1/3] GUI updatees... --- src/navigate/controller/sub_controllers/acquire_bar.py | 2 +- src/navigate/view/popups/acquire_popup.py | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/navigate/controller/sub_controllers/acquire_bar.py b/src/navigate/controller/sub_controllers/acquire_bar.py index 5fa0f8c00..3159d65b6 100644 --- a/src/navigate/controller/sub_controllers/acquire_bar.py +++ b/src/navigate/controller/sub_controllers/acquire_bar.py @@ -541,7 +541,7 @@ def toggle_bdv_widgets(self, main_widget: str, dependent_widgets: list) -> None: state = self.acquire_pop.tab_frame.inputs[main_widget].get_variable().get() for widget in dependent_widgets: self.acquire_pop.tab_frame.inputs[widget].widget.config( - state="readonly" if state else "disabled" + state="normal" if state else "disabled" ) def update_microscope_mode(self, *args: Iterable) -> None: diff --git a/src/navigate/view/popups/acquire_popup.py b/src/navigate/view/popups/acquire_popup.py index 9d4ed1da9..3ea97c820 100644 --- a/src/navigate/view/popups/acquire_popup.py +++ b/src/navigate/view/popups/acquire_popup.py @@ -426,7 +426,7 @@ def __init__(self, parent: AcquirePopUp, frame: ttk.Frame) -> None: input_class=ValidatedSpinbox, input_var=tk.StringVar(), input_args={ - "from_": 0, + "from_": -360, "to": 360, "increment": 1, }, @@ -443,7 +443,7 @@ def __init__(self, parent: AcquirePopUp, frame: ttk.Frame) -> None: input_class=ValidatedSpinbox, input_var=tk.StringVar(), input_args={ - "from_": 0, + "from_": -360, "to": 360, "increment": 1, }, @@ -455,11 +455,11 @@ def __init__(self, parent: AcquirePopUp, frame: ttk.Frame) -> None: self.inputs["rotate_angle_z"] = LabelInput( parent=rotate_notebook, label_pos="top", - label="Y Angle", + label="Z Angle", input_class=ValidatedSpinbox, input_var=tk.StringVar(), input_args={ - "from_": 0, + "from_": -360, "to": 360, "increment": 1, }, From 95bbfede955611007e9b0ae804cc07934733b3f2 Mon Sep 17 00:00:00 2001 From: unknown Date: Fri, 22 Aug 2025 13:56:43 -0500 Subject: [PATCH 2/3] Camera updates Should move pixel size to the Hamamatsu Base class or investigate further. --- src/navigate/model/devices/camera/base.py | 2 +- src/navigate/model/devices/camera/ximea.py | 41 ++++++++++++++++++---- 2 files changed, 36 insertions(+), 7 deletions(-) diff --git a/src/navigate/model/devices/camera/base.py b/src/navigate/model/devices/camera/base.py index 1f70be377..d2d039a83 100644 --- a/src/navigate/model/devices/camera/base.py +++ b/src/navigate/model/devices/camera/base.py @@ -126,7 +126,7 @@ def __init__( # readout time in model and controller self.camera_parameters["trigger_source"] = 2.0 # external trigger self.camera_parameters["readout_speed"] = 1.0 - self.camera_parameters["pixel_size_in_microns"] = 6.5 + # self.camera_parameters["pixel_size_in_microns"] = 6.5 self.camera_parameters["trigger_active"] = 1.0 self.camera_parameters["trigger_mode"] = 1.0 # standard trigger mode self.camera_parameters["trigger_polarity"] = 2.0 diff --git a/src/navigate/model/devices/camera/ximea.py b/src/navigate/model/devices/camera/ximea.py index 22354ae30..7215c77ed 100644 --- a/src/navigate/model/devices/camera/ximea.py +++ b/src/navigate/model/devices/camera/ximea.py @@ -36,6 +36,7 @@ # Third Party Imports from ximea import xiapi +import numpy as np # Local Imports from navigate.model.devices.camera.base import CameraBase @@ -73,6 +74,7 @@ def __init__( super().__init__(microscope_name, device_connection, configuration) #: str: Name of the microscope + self._frames_received = None self.microscope_name = microscope_name #: object: Camera Object @@ -81,6 +83,15 @@ def __init__( #: dict: Configuration settings self.configuration = configuration + #: bool: Auto restart flag + self.auto_restart = True + + #: int: Auto restart counter + self.auto_restart_counter = 0 + + #: int: Timeout counter + self.timeout_counter = 0 + #: dict: Camera parameters self.camera_parameters["x_pixels"] = self.cam.get_param("width:max") self.camera_parameters["y_pixels"] = self.cam.get_param("height:max") @@ -379,7 +390,7 @@ def set_ROI_and_binning(self, roi_width=2048, roi_height=2048, center_x=1024, ce result = self.set_ROI(roi_width, roi_height, center_x, center_y) return result - def initialize_image_series(self, data_buffer=None, number_of_frames=100): + def initialize_image_series(self, data_buffer=None, number_of_frames=1000): """Initialize Ximea Camera image series. Parameters @@ -395,8 +406,12 @@ def initialize_image_series(self, data_buffer=None, number_of_frames=100): self._number_of_frames = number_of_frames self._frames_received = 0 - # set buffer policy: XI_BP_SAFE - self.cam.set_param("buffer_policy", "XI_BP_SAFE") + # # set buffer policy: XI_BP_SAFE + # self.cam.set_param("buffer_policy", "XI_BP_SAFE") + + # *** use UNSAFE + self.cam.set_param("buffer_policy", "XI_BP_UNSAFE") + # set image data format to XI_MONO16, this value can be set only if acquisition is stopped. self.cam.set_param('imgdataformat', "XI_MONO16") #imgpayloadsize changes automatically after setting imgdataformat @@ -422,15 +437,29 @@ def get_new_frame(self): frame : numpy.ndarray Frame ids from Ximea camera. """ - # attach buffer to image object - self._image.bp = self._data_buffer[self._frames_received].ctypes.data - self._image.bp_size = self._data_buffer[self._frames_received].nbytes + # # attach buffer to image object + # self._image.bp = self._data_buffer[self._frames_received].ctypes.data + # self._image.bp_size = self._data_buffer[self._frames_received].nbytes # get data from camera try: self.cam.get_image(self._image, 500) except xiapi.Xi_error as e: + if e.status == 10: # XI_ERR_TIMEOUT + self.timeout_counter += 1 + if self.auto_restart and self.timeout_counter >= 5 and self.auto_restart_counter < 3: + self.auto_restart_counter += 1 + logger.warning(f"Timeout error while getting image. Restarting acquisition. Auto restart it {self.auto_restart_counter}") + self.cam.stop_acquisition() + self.cam.start_acquisition() + return [-1] logger.error(f"Error getting image from camera: {e}") return [] + self.auto_restart_counter = 0 + self.timeout_counter = 0 + # *** copy image to data buffer + self._data_buffer[self._frames_received][:, :] = np.copy( + self._image.get_image_data_numpy() + ) frames_received = [self._frames_received] From 9ff641a0d09d5e596567a000193dee606dfa878d Mon Sep 17 00:00:00 2001 From: unknown Date: Fri, 22 Aug 2025 13:57:12 -0500 Subject: [PATCH 3/3] Update common_features.py Major changes. Need to discuss with @annie-xd-wang --- .../model/features/common_features.py | 38 ++++++++++++++++++- 1 file changed, 37 insertions(+), 1 deletion(-) diff --git a/src/navigate/model/features/common_features.py b/src/navigate/model/features/common_features.py index 01cf7de7e..bfd0203b8 100644 --- a/src/navigate/model/features/common_features.py +++ b/src/navigate/model/features/common_features.py @@ -37,6 +37,7 @@ from threading import Lock import logging from multiprocessing.managers import ListProxy +from queue import Queue, Empty # Third party imports @@ -1119,7 +1120,7 @@ def __init__( # TODO: distance > 1000 should not be hardcoded and somehow related to # different kinds of stage devices. #: int: The stage distance threshold for pausing the data thread. - self.stage_distance_threshold = 1000 + self.stage_distance_threshold = 200 #: dict: A dictionary of the previous position in the multi-position table. self.pre_position = None @@ -1156,6 +1157,7 @@ def __init__( self.prepare_next_channel = PrepareNextChannel(model) + #: dict: A dictionary defining the configuration for the z-stack acquisition self.config_table = { "signal": { @@ -1172,6 +1174,12 @@ def __init__( "node": {"node_type": "multi-step", "device_related": True}, } + if self.model.active_microscope_name == "Macroscale": + self.data_queue = Queue() + self.config_table["signal"]["response"] = self.signal_response_func + + self.resend_trigger = False + def get_microscope_state(self, microscope_state: dict) -> None: """Get the microscope state from the configuration. @@ -1313,6 +1321,9 @@ def signal_func(self): A boolean value indicating whether to continue the z-stack acquisition process. """ + if self.resend_trigger: + return True + if self.model.stop_acquisition: return False data_thread_is_paused = False @@ -1414,6 +1425,8 @@ def signal_end(self) -> bool: bool A boolean value indicating whether to end the current node. """ + if self.resend_trigger: + return False # end this node if self.model.stop_acquisition: @@ -1480,6 +1493,21 @@ def signal_end(self) -> bool: return False + def signal_response_func(self, *args) -> bool: + try: + r = self.data_queue.get(timeout=100) + if r == -1: + self.resend_trigger = True + else: + self.resend_trigger = False + except Empty: + logger.warning( + "ZStackAcquisition: No data received within the timeout period." + ) + return False + + return True + def update_channel(self) -> None: """Update the active channel during multichannel acquisition. @@ -1516,10 +1544,18 @@ def in_data_func(self, frame_ids: list) -> None: A list of frame IDs received during data acquisition. """ + + if -1 in frame_ids and hasattr(self, "data_queue"): + self.data_queue.put(-1) + self.received_frames += frame_ids.index(-1) + return self.received_frames += len(frame_ids) if self.image_writer is not None: self.image_writer.save_image(frame_ids) + if hasattr(self, "data_queue"): + self.data_queue.put(True) + def end_data_func(self) -> bool: """Check if all expected data frames have been received.