From 50f1bbbb116637e44d81c322f5a2036117f0e224 Mon Sep 17 00:00:00 2001 From: Ivan Ivanov Date: Thu, 3 Oct 2024 14:14:25 -0700 Subject: [PATCH 01/24] add calibration script --- .../acquisition/scripts/calibrate_O3_piezo.py | 80 +++++++++++++++++++ 1 file changed, 80 insertions(+) create mode 100644 mantis/acquisition/scripts/calibrate_O3_piezo.py diff --git a/mantis/acquisition/scripts/calibrate_O3_piezo.py b/mantis/acquisition/scripts/calibrate_O3_piezo.py new file mode 100644 index 00000000..0d9af558 --- /dev/null +++ b/mantis/acquisition/scripts/calibrate_O3_piezo.py @@ -0,0 +1,80 @@ +### Methods +# Prepare a sample with dense beads immobilized dry on glass coverslip +# Image with epi illumination and LS detection +# As O3 moves back and forth the in-focus plate will shift up and down on the image + +# %% +import numpy as np +from pycromanager import Core +from mantis.acquisition.microscope_operations import ( + acquire_defocus_stack, + setup_kim101_stage, +) +import matplotlib.pyplot as plt + +LS_KIM101_SN = 74000291 + +def smooth(data, window_size=5): + cumsum_vec = np.cumsum(np.insert(data, 0, 0)) + data = (cumsum_vec[window_size:] - cumsum_vec[:-window_size]) / window_size + return data + +# %% Calibrate background correction distance +# Acquire a number of defocus stacks and check that the stack starting and ending +# points don't drift up or down. Adjust the backlash correction distance if needed. +# This step is more important than adjusting the compensation factor below. + +mmc = Core() +o3_stage = setup_kim101_stage(LS_KIM101_SN) + +repeats = 10 +z_range = np.arange(-165, 165, 15) +data = [] +for _ in range(repeats): + data.append( + acquire_defocus_stack( + mmc, + z_stage=o3_stage, + z_range=z_range, + backlash_correction_distance=10, + ) + ) +data = np.concatenate(data) +o3_stage.close() + +data_std = data.std(axis=-1) +peaks = [] +for d in data_std: + peaks.append(np.argmax(smooth(d, window_size=5))) + +plt.plot(peaks) +plt.grid() + +# %% Calibrate compensation factor +# Acquire a number of defocus stacks and check that the slope of the lines going +# in the forward and reverse directions is the same. Adjust the KIM101_COMPENSATION_FACTOR +# in mantis/acquisition/microscope_operations.py if needed + +data_std = data.std(axis=-1) +peaks = [] +for d in data_std: + peaks.append(np.argmax(smooth(d, window_size=5))) + +z_range = z_range.reshape(4, -1) +peaks = np.array(peaks).reshape(4, -1) +plt.plot( + z_range.T, + peaks.T +) + +slope, intercept = [], [] +for x, y in zip(z_range, peaks): + p = np.polyfit(x, y, 1) + slope.append(p[0]) + intercept.append(p[1]) + +print(f'Positive direction slope: {np.mean(slope[0::2])}') +print(f'Negative direction slope: {np.mean(slope[1::2])}') + + +# %% From decded6d4ba82bab68abf9cf73e3935153526251 Mon Sep 17 00:00:00 2001 From: Ivan Ivanov Date: Thu, 3 Oct 2024 14:14:48 -0700 Subject: [PATCH 02/24] add backlash correction and fix exp time bug --- mantis/acquisition/acq_engine.py | 37 +++++++++++++++++---- mantis/acquisition/microscope_operations.py | 7 ++-- 2 files changed, 36 insertions(+), 8 deletions(-) diff --git a/mantis/acquisition/acq_engine.py b/mantis/acquisition/acq_engine.py index ad9a53d0..6a66e58f 100644 --- a/mantis/acquisition/acq_engine.py +++ b/mantis/acquisition/acq_engine.py @@ -61,6 +61,7 @@ LS_CHANGE_TIME = 200 # time needed to change LS filter wheel, in ms LS_KIM101_SN = 74000291 LF_KIM101_SN = 74000565 +KIM101_BACKLASH = 10 # backlash correction distance, in steps VORTRAN_488_COM_PORT = 'COM6' VORTRAN_561_COM_PORT = 'COM13' VORTRAN_639_COM_PORT = 'COM12' @@ -732,6 +733,7 @@ def acquire_ls_defocus_stack( galvo_range: Iterable, config_group: str = None, config_name: str = None, + exposure_time: float = None, ): """Acquire defocus stacks at different galvo positions and return image data @@ -758,6 +760,10 @@ def acquire_ls_defocus_stack( mmc.set_config(config_group, config_name) mmc.wait_for_config(config_group, config_name) + # Set exposure time + if exposure_time is not None: + mmc.set_exposure(exposure_time) + # Open shutter auto_shutter_state, shutter_state = microscope_operations.get_shutter_state(mmc) microscope_operations.open_shutter(mmc) @@ -777,7 +783,12 @@ def acquire_ls_defocus_stack( mmc.set_position(galvo, p0 + p) # acquire defocus stack - z_stack = microscope_operations.acquire_defocus_stack(mmc, z_stage, z_range) + z_stack = microscope_operations.acquire_defocus_stack( + mmc, + z_stage, + z_range, + backlash_correction_distance=KIM101_BACKLASH + ) data.append(z_stack) # Reset camera triggering @@ -793,8 +804,9 @@ def acquire_ls_defocus_stack( return np.asarray(data) - def refocus_ls_path(self): + def refocus_ls_path(self) -> bool: logger.info('Running O3 refocus algorithm on light-sheet arm') + success = False # Define O3 z range # 1 step is approx 20 nm, 15 steps are 300 nm which is sub-Nyquist sampling @@ -826,16 +838,25 @@ def refocus_ls_path(self): ] # Acquire defocus stacks at several galvo positions + config_group = self.ls_acq.microscope_settings.o3_refocus_config.config_group + config_name = self.ls_acq.microscope_settings.o3_refocus_config.config_name + config_idx = self.ls_acq.channel_settings.channels.index(config_name) + exposure_time = self.ls_acq.channel_settings.default_exposure_times_ms[config_idx] + data = self.acquire_ls_defocus_stack( mmc=self.ls_acq.mmc, z_stage=o3_z_stage, z_range=o3_z_range, galvo=self.ls_acq.slice_settings.z_stage_name, galvo_range=galvo_range, - config_group=self.ls_acq.microscope_settings.o3_refocus_config.config_group, - config_name=self.ls_acq.microscope_settings.o3_refocus_config.config_name, + config_group=config_group, + config_name=config_name, + exposure_time=exposure_time, ) + # Discount O3 backlash compensation from true position count + o3_z_stage.true_position -= KIM101_BACKLASH * len(galvo_range) + # Save acquired stacks in logs timestamp = datetime.now().strftime("%Y%m%dT%H%M%S") tifffile.imwrite( @@ -877,11 +898,14 @@ def refocus_ls_path(self): microscope_operations.set_relative_kim101_position( self.ls_acq.o3_stage, o3_displacement ) + success = True else: logger.error( 'Could not determine the correct O3 in-focus position. O3 will not move' ) + return success + def run_autoexposure( self, acq: BaseChannelSliceAcquisition, @@ -1077,8 +1101,9 @@ def acquire(self): or current_time - ls_o3_refocus_time > self.ls_acq.microscope_settings.o3_refocus_interval_min * 60 ): - self.refocus_ls_path() - ls_o3_refocus_time = current_time + success = self.refocus_ls_path() + if success: + ls_o3_refocus_time = current_time # autoexposure if well_id != previous_well_id: diff --git a/mantis/acquisition/microscope_operations.py b/mantis/acquisition/microscope_operations.py index 324aa679..35dd361f 100644 --- a/mantis/acquisition/microscope_operations.py +++ b/mantis/acquisition/microscope_operations.py @@ -17,7 +17,7 @@ logger = logging.getLogger(__name__) -KIM101_COMPENSATION_FACTOR = 1.035 +KIM101_COMPENSATION_FACTOR = 1.0 def _try_mmc_call(mmc, mmc_call_name, *mmc_carr_args): @@ -334,6 +334,7 @@ def acquire_defocus_stack( datastore=None, channel_ind: int = 0, position_ind: int = 0, + backlash_correction_distance: int = 0, ): """Snap image at every z position and put image in a Micro-manager datastore @@ -351,6 +352,8 @@ def acquire_defocus_stack( Channel index of acquired images in the Micro-manager datastore, by default 0 position_ind : int, optional Position index of acquired images in the Micro-manager datastore, by default 0 + backlash_correction: int, optional + Distance to add to the homing move of the stage to correct for backlash, by default 0 Returns ------- @@ -397,7 +400,7 @@ def acquire_defocus_stack( datastore.put_image(image) # reset z stage - move_z(-relative_z_steps.sum()) + move_z(-relative_z_steps.sum() + backlash_correction_distance) return np.asarray(data) From 2505c1d539ff914ac66d82f70db2ad79f40e50dc Mon Sep 17 00:00:00 2001 From: Ivan Ivanov Date: Fri, 11 Oct 2024 14:54:39 -0700 Subject: [PATCH 03/24] remove O3 travel limits --- mantis/acquisition/acq_engine.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/mantis/acquisition/acq_engine.py b/mantis/acquisition/acq_engine.py index 6a66e58f..3117eba7 100644 --- a/mantis/acquisition/acq_engine.py +++ b/mantis/acquisition/acq_engine.py @@ -819,8 +819,8 @@ def refocus_ls_path(self) -> bool: # Define relative travel limits, in steps o3_z_stage = self.ls_acq.o3_stage target_z_position = o3_z_stage.true_position + o3_z_range - max_z_position = 750 # O3 is allowed to travel ~15 um towards O2 - min_z_position = -1500 # O3 is allowed to travel ~30 um away from O2 + max_z_position = np.inf # O3 is allowed to travel ~15 um towards O2 + min_z_position = -np.inf # O3 is allowed to travel ~30 um away from O2 if np.any(target_z_position > max_z_position) or np.any( target_z_position < min_z_position ): From b104dafb55fd9a98a4506ed620b82a50005ce1b2 Mon Sep 17 00:00:00 2001 From: Ivan Ivanov Date: Tue, 15 Oct 2024 17:40:20 -0700 Subject: [PATCH 04/24] remove backlash correction --- mantis/acquisition/acq_engine.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mantis/acquisition/acq_engine.py b/mantis/acquisition/acq_engine.py index 3117eba7..f0045710 100644 --- a/mantis/acquisition/acq_engine.py +++ b/mantis/acquisition/acq_engine.py @@ -61,7 +61,7 @@ LS_CHANGE_TIME = 200 # time needed to change LS filter wheel, in ms LS_KIM101_SN = 74000291 LF_KIM101_SN = 74000565 -KIM101_BACKLASH = 10 # backlash correction distance, in steps +KIM101_BACKLASH = 0 # backlash correction distance, in steps VORTRAN_488_COM_PORT = 'COM6' VORTRAN_561_COM_PORT = 'COM13' VORTRAN_639_COM_PORT = 'COM12' From 02562348bbc952caefa2a09da9d81820637c18b5 Mon Sep 17 00:00:00 2001 From: Ivan Ivanov Date: Tue, 15 Oct 2024 17:40:39 -0700 Subject: [PATCH 05/24] expand O3 calibration script --- .../acquisition/scripts/calibrate_O3_piezo.py | 27 ++++++++++++++++--- 1 file changed, 24 insertions(+), 3 deletions(-) diff --git a/mantis/acquisition/scripts/calibrate_O3_piezo.py b/mantis/acquisition/scripts/calibrate_O3_piezo.py index 0d9af558..897bef25 100644 --- a/mantis/acquisition/scripts/calibrate_O3_piezo.py +++ b/mantis/acquisition/scripts/calibrate_O3_piezo.py @@ -14,21 +14,38 @@ LS_KIM101_SN = 74000291 +mmc = Core() +mmc.set_roi(768, 768, 512, 512) + def smooth(data, window_size=5): cumsum_vec = np.cumsum(np.insert(data, 0, 0)) data = (cumsum_vec[window_size:] - cumsum_vec[:-window_size]) / window_size return data +def snap_image(mmc) -> np.ndarray: + mmc.snap_image() + tagged_image = mmc.get_tagged_image() + + # get image data + image_data = np.reshape( + tagged_image.pix, (tagged_image.tags['Height'], tagged_image.tags['Width']) + ) + + return image_data.astype('uint16') + + # %% Calibrate background correction distance # Acquire a number of defocus stacks and check that the stack starting and ending # points don't drift up or down. Adjust the backlash correction distance if needed. # This step is more important than adjusting the compensation factor below. -mmc = Core() o3_stage = setup_kim101_stage(LS_KIM101_SN) +img0 = snap_image(mmc) +peak0 = np.argmax(smooth(img0.std(axis=-1), window_size=5)) + repeats = 10 -z_range = np.arange(-165, 165, 15) +z_range = np.arange(-165, 165+15, 15) data = [] for _ in range(repeats): data.append( @@ -36,7 +53,7 @@ def smooth(data, window_size=5): mmc, z_stage=o3_stage, z_range=z_range, - backlash_correction_distance=10, + backlash_correction_distance=0, ) ) data = np.concatenate(data) @@ -46,8 +63,12 @@ def smooth(data, window_size=5): peaks = [] for d in data_std: peaks.append(np.argmax(smooth(d, window_size=5))) +n = len(z_range) +z0_std = peaks[n//2::n] plt.plot(peaks) +plt.axhline(y=peak0, color='r', linestyle='--') +plt.plot(np.arange(n//2, n*repeats, n), z0_std, 'o') plt.grid() # %% Calibrate compensation factor From aee383c50e24ccfc33b5cdc1f47d5865ced43c14 Mon Sep 17 00:00:00 2001 From: Ivan Ivanov Date: Tue, 15 Oct 2024 18:03:57 -0700 Subject: [PATCH 06/24] retry O3 autofocus with extended range --- mantis/acquisition/acq_engine.py | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/mantis/acquisition/acq_engine.py b/mantis/acquisition/acq_engine.py index f0045710..f06d960f 100644 --- a/mantis/acquisition/acq_engine.py +++ b/mantis/acquisition/acq_engine.py @@ -804,7 +804,7 @@ def acquire_ls_defocus_stack( return np.asarray(data) - def refocus_ls_path(self) -> bool: + def refocus_ls_path(self, extend_range:bool = False) -> bool: logger.info('Running O3 refocus algorithm on light-sheet arm') success = False @@ -814,6 +814,9 @@ def refocus_ls_path(self) -> bool: o3_z_start = -165 o3_z_end = 165 o3_z_step = 15 + if extend_range: + o3_z_start -= 6 * o3_z_step + o3_z_end += 6 * o3_z_step o3_z_range = np.arange(o3_z_start, o3_z_end + o3_z_step, o3_z_step) # Define relative travel limits, in steps @@ -1102,6 +1105,10 @@ def acquire(self): > self.ls_acq.microscope_settings.o3_refocus_interval_min * 60 ): success = self.refocus_ls_path() + # If autofocus fails, try again with extended range + if not success: + success = self.refocus_ls_path(extend_range=True) + # If it failed again, retry at the next position if success: ls_o3_refocus_time = current_time From 3e2d2bc8c906cdf0bf417ccd99955517ad2387da Mon Sep 17 00:00:00 2001 From: Ivan Ivanov Date: Tue, 15 Oct 2024 18:04:27 -0700 Subject: [PATCH 07/24] small tweaks to O3 calibration script --- mantis/acquisition/scripts/calibrate_O3_piezo.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/mantis/acquisition/scripts/calibrate_O3_piezo.py b/mantis/acquisition/scripts/calibrate_O3_piezo.py index 897bef25..bcb3e81a 100644 --- a/mantis/acquisition/scripts/calibrate_O3_piezo.py +++ b/mantis/acquisition/scripts/calibrate_O3_piezo.py @@ -44,7 +44,7 @@ def snap_image(mmc) -> np.ndarray: img0 = snap_image(mmc) peak0 = np.argmax(smooth(img0.std(axis=-1), window_size=5)) -repeats = 10 +repeats = 5 z_range = np.arange(-165, 165+15, 15) data = [] for _ in range(repeats): @@ -64,11 +64,10 @@ def snap_image(mmc) -> np.ndarray: for d in data_std: peaks.append(np.argmax(smooth(d, window_size=5))) n = len(z_range) -z0_std = peaks[n//2::n] plt.plot(peaks) plt.axhline(y=peak0, color='r', linestyle='--') -plt.plot(np.arange(n//2, n*repeats, n), z0_std, 'o') +plt.plot(np.arange(n//2, n*repeats, n), peaks[n//2::n], 'o') plt.grid() # %% Calibrate compensation factor From 8a849ed0cc79f656182cab1fdd781015ac72d79d Mon Sep 17 00:00:00 2001 From: Ivan Ivanov Date: Wed, 30 Oct 2024 15:09:35 -0700 Subject: [PATCH 08/24] increase O3 autofocus FWHM threshold --- mantis/acquisition/acq_engine.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mantis/acquisition/acq_engine.py b/mantis/acquisition/acq_engine.py index f06d960f..e3f0f3ac 100644 --- a/mantis/acquisition/acq_engine.py +++ b/mantis/acquisition/acq_engine.py @@ -871,7 +871,7 @@ def refocus_ls_path(self, extend_range:bool = False) -> bool: wavelength = 0.55 # in um, approx # works well to distinguish between noise and sample when using z_step = 15 # the idea is that true features in the sample will come in focus slowly - threshold_FWHM = 3.0 + threshold_FWHM = 4.5 focus_indices = [] for stack_idx, stack in enumerate(data): From 5c6558895f7279631e9dd4ad830ea80a592e2560 Mon Sep 17 00:00:00 2001 From: Ivan Ivanov Date: Wed, 30 Oct 2024 17:06:44 -0700 Subject: [PATCH 09/24] adjust backlash and increase O3 extended range search --- mantis/acquisition/acq_engine.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/mantis/acquisition/acq_engine.py b/mantis/acquisition/acq_engine.py index e3f0f3ac..f21919bb 100644 --- a/mantis/acquisition/acq_engine.py +++ b/mantis/acquisition/acq_engine.py @@ -61,7 +61,7 @@ LS_CHANGE_TIME = 200 # time needed to change LS filter wheel, in ms LS_KIM101_SN = 74000291 LF_KIM101_SN = 74000565 -KIM101_BACKLASH = 0 # backlash correction distance, in steps +KIM101_BACKLASH = -15 # backlash correction distance, in steps VORTRAN_488_COM_PORT = 'COM6' VORTRAN_561_COM_PORT = 'COM13' VORTRAN_639_COM_PORT = 'COM12' @@ -815,8 +815,9 @@ def refocus_ls_path(self, extend_range:bool = False) -> bool: o3_z_end = 165 o3_z_step = 15 if extend_range: - o3_z_start -= 6 * o3_z_step - o3_z_end += 6 * o3_z_step + logger.info('Running O3 refocus with extended range') + o3_z_start *= 2 + o3_z_end *= 2 o3_z_range = np.arange(o3_z_start, o3_z_end + o3_z_step, o3_z_step) # Define relative travel limits, in steps From 063905228488e7260de80418c8fb3672bd5b341c Mon Sep 17 00:00:00 2001 From: Ivan Ivanov Date: Wed, 30 Oct 2024 18:18:46 -0700 Subject: [PATCH 10/24] improve O3 autoexposure with better extended range scanning --- mantis/acquisition/acq_engine.py | 37 ++++++++++++++++++++++---------- 1 file changed, 26 insertions(+), 11 deletions(-) diff --git a/mantis/acquisition/acq_engine.py b/mantis/acquisition/acq_engine.py index f21919bb..87ce524c 100644 --- a/mantis/acquisition/acq_engine.py +++ b/mantis/acquisition/acq_engine.py @@ -804,7 +804,7 @@ def acquire_ls_defocus_stack( return np.asarray(data) - def refocus_ls_path(self, extend_range:bool = False) -> bool: + def refocus_ls_path(self, scan_left: bool = False, scan_right: bool = False) -> tuple[bool, bool, bool]: logger.info('Running O3 refocus algorithm on light-sheet arm') success = False @@ -814,9 +814,11 @@ def refocus_ls_path(self, extend_range:bool = False) -> bool: o3_z_start = -165 o3_z_end = 165 o3_z_step = 15 - if extend_range: - logger.info('Running O3 refocus with extended range') + if scan_left: + logger.info('O3 refocus will scan further to the left') o3_z_start *= 2 + if scan_right: + logger.info('O3 refocus will scan further to the right') o3_z_end *= 2 o3_z_range = np.arange(o3_z_start, o3_z_end + o3_z_step, o3_z_step) @@ -875,16 +877,18 @@ def refocus_ls_path(self, extend_range:bool = False) -> bool: threshold_FWHM = 4.5 focus_indices = [] + peak_FWHM = [] for stack_idx, stack in enumerate(data): - idx = focus_from_transverse_band( + idx, fwhm = focus_from_transverse_band( stack, NA_det=NA_DETECTION, lambda_ill=wavelength, pixel_size=LS_PIXEL_SIZE, - threshold_FWHM=threshold_FWHM, + threshold_FWHM=0, # we'll discard invalid peaks below plot_path=self._logs_dir / f'ls_refocus_plot_{timestamp}_Pos{stack_idx}.png', ) focus_indices.append(idx) + peak_FWHM.append(fwhm) logger.debug( 'Stacks at galvo positions %s are in focus at slice %s', np.round(galvo_range, 3), @@ -893,7 +897,7 @@ def refocus_ls_path(self, extend_range:bool = False) -> bool: # Refocus O3 # Some focus_indices may be None, e.g. if there is no sample - valid_focus_indices = [idx for idx in focus_indices if idx is not None] + valid_focus_indices = [idx for (idx, fwhm) in zip(focus_indices, peak_FWHM) if fwhm > threshold_FWHM] if valid_focus_indices: focus_idx = int(np.median(valid_focus_indices)) o3_displacement = int(o3_z_range[focus_idx]) @@ -907,8 +911,19 @@ def refocus_ls_path(self, extend_range:bool = False) -> bool: logger.error( 'Could not determine the correct O3 in-focus position. O3 will not move' ) + if not any((scan_left, scan_right)): + # Only do this if we are not already scanning at an extended range + focus_indices = np.asarray(focus_indices) + max_idx = len(o3_z_range) - 1 + if all(focus_indices < 0.2*max_idx): + scan_left = True + logger.info('O3 autofocus will scan further to the left at the next iteration') + if all(focus_indices > 0.8*max_idx): + scan_right = True + logger.info('O3 autofocus will scan further to the right at the next iteration') - return success + + return success, scan_left, scan_right def run_autoexposure( self, @@ -1105,10 +1120,10 @@ def acquire(self): or current_time - ls_o3_refocus_time > self.ls_acq.microscope_settings.o3_refocus_interval_min * 60 ): - success = self.refocus_ls_path() - # If autofocus fails, try again with extended range - if not success: - success = self.refocus_ls_path(extend_range=True) + success, scan_left, scan_right = self.refocus_ls_path() + # If autofocus fails, try again with extended range if we know which way to go + if not success and any((scan_left, scan_right)): + success, _, _ = self.refocus_ls_path(scan_left, scan_right) # If it failed again, retry at the next position if success: ls_o3_refocus_time = current_time From 4275ae5c5b6f9d69c09262f98fddef94d38bb466 Mon Sep 17 00:00:00 2001 From: Ivan Ivanov Date: Thu, 31 Oct 2024 11:22:38 -0700 Subject: [PATCH 11/24] Refactor acquire_ls_defocus_stack --- mantis/acquisition/acq_engine.py | 28 +++++++++------------------- 1 file changed, 9 insertions(+), 19 deletions(-) diff --git a/mantis/acquisition/acq_engine.py b/mantis/acquisition/acq_engine.py index 87ce524c..7e4a0b75 100644 --- a/mantis/acquisition/acq_engine.py +++ b/mantis/acquisition/acq_engine.py @@ -61,7 +61,7 @@ LS_CHANGE_TIME = 200 # time needed to change LS filter wheel, in ms LS_KIM101_SN = 74000291 LF_KIM101_SN = 74000565 -KIM101_BACKLASH = -15 # backlash correction distance, in steps +KIM101_BACKLASH = -5 # backlash correction distance, in steps VORTRAN_488_COM_PORT = 'COM6' VORTRAN_561_COM_PORT = 'COM13' VORTRAN_639_COM_PORT = 'COM12' @@ -724,16 +724,10 @@ def go_to_position(self, position_index: int): self.lf_acq.microscope_settings.autofocus_stage, ) - @staticmethod def acquire_ls_defocus_stack( - mmc: Core, - z_stage, + self, z_range: Iterable, - galvo: str, galvo_range: Iterable, - config_group: str = None, - config_name: str = None, - exposure_time: float = None, ): """Acquire defocus stacks at different galvo positions and return image data @@ -754,6 +748,13 @@ def acquire_ls_defocus_stack( """ data = [] + mmc = self.ls_acq.mmc + config_group = self.ls_acq.microscope_settings.o3_refocus_config.config_group + config_name = self.ls_acq.microscope_settings.o3_refocus_config.config_name + config_idx = self.ls_acq.channel_settings.channels.index(config_name) + exposure_time = self.ls_acq.channel_settings.default_exposure_times_ms[config_idx] + z_stage = self.ls_acq.o3_stage + galvo = self.ls_acq.slice_settings.z_stage_name # Set config if config_name is not None: @@ -844,20 +845,9 @@ def refocus_ls_path(self, scan_left: bool = False, scan_right: bool = False) -> ] # Acquire defocus stacks at several galvo positions - config_group = self.ls_acq.microscope_settings.o3_refocus_config.config_group - config_name = self.ls_acq.microscope_settings.o3_refocus_config.config_name - config_idx = self.ls_acq.channel_settings.channels.index(config_name) - exposure_time = self.ls_acq.channel_settings.default_exposure_times_ms[config_idx] - data = self.acquire_ls_defocus_stack( - mmc=self.ls_acq.mmc, - z_stage=o3_z_stage, z_range=o3_z_range, - galvo=self.ls_acq.slice_settings.z_stage_name, galvo_range=galvo_range, - config_group=config_group, - config_name=config_name, - exposure_time=exposure_time, ) # Discount O3 backlash compensation from true position count From 885348266ef711ac5b7d54b80d62d9c2510d4f17 Mon Sep 17 00:00:00 2001 From: Ivan Ivanov Date: Tue, 19 Nov 2024 16:40:26 -0800 Subject: [PATCH 12/24] run autoexposure before O3 autofocus --- mantis/acquisition/acq_engine.py | 36 ++++++++++++++++---------------- 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/mantis/acquisition/acq_engine.py b/mantis/acquisition/acq_engine.py index 7e4a0b75..00317f7d 100644 --- a/mantis/acquisition/acq_engine.py +++ b/mantis/acquisition/acq_engine.py @@ -1100,24 +1100,6 @@ def acquire(self): ) continue - # O3 refocus - # Failing to refocus O3 will not abort the acquisition at the current PT index - if self.ls_acq.microscope_settings.use_o3_refocus: - current_time = time.time() - # Always refocus at the start - if ( - (t_idx == 0 and p_idx == 0) - or current_time - ls_o3_refocus_time - > self.ls_acq.microscope_settings.o3_refocus_interval_min * 60 - ): - success, scan_left, scan_right = self.refocus_ls_path() - # If autofocus fails, try again with extended range if we know which way to go - if not success and any((scan_left, scan_right)): - success, _, _ = self.refocus_ls_path(scan_left, scan_right) - # If it failed again, retry at the next position - if success: - ls_o3_refocus_time = current_time - # autoexposure if well_id != previous_well_id: globals.new_well = True @@ -1139,6 +1121,24 @@ def acquire(self): self.ls_acq.channel_settings.laser_powers_per_well[well_id] ) + # O3 refocus + # Failing to refocus O3 will not abort the acquisition at the current PT index + if self.ls_acq.microscope_settings.use_o3_refocus: + current_time = time.time() + # Always refocus at the start + if ( + (t_idx == 0 and p_idx == 0) + or current_time - ls_o3_refocus_time + > self.ls_acq.microscope_settings.o3_refocus_interval_min * 60 + ): + success, scan_left, scan_right = self.refocus_ls_path() + # If autofocus fails, try again with extended range if we know which way to go + if not success and any((scan_left, scan_right)): + success, _, _ = self.refocus_ls_path(scan_left, scan_right) + # If it failed again, retry at the next position + if success: + ls_o3_refocus_time = current_time + # update events dictionaries lf_events = deepcopy(lf_cz_events) for _event in lf_events: From 8e6373077b2cc3340d0d884555b879e750083a81 Mon Sep 17 00:00:00 2001 From: Ivan Ivanov Date: Mon, 25 Nov 2024 17:33:25 -0800 Subject: [PATCH 13/24] set laser power right after autoexposure --- mantis/acquisition/acq_engine.py | 22 ++++++++++++----- .../post_hardware_hook_functions.py | 24 +++++++------------ 2 files changed, 25 insertions(+), 21 deletions(-) diff --git a/mantis/acquisition/acq_engine.py b/mantis/acquisition/acq_engine.py index 00317f7d..63ef7e05 100644 --- a/mantis/acquisition/acq_engine.py +++ b/mantis/acquisition/acq_engine.py @@ -40,6 +40,7 @@ from mantis.acquisition.hook_functions.post_hardware_hook_functions import ( log_acquisition_start, update_ls_hardware, + update_laser_power, ) from mantis.acquisition.hook_functions.post_camera_hook_functions import ( start_daq_counters, @@ -1109,17 +1110,26 @@ def acquire(self): well_id=well_id, method=self.ls_acq.autoexposure_settings.autoexposure_method, ) + + globals.ls_slice_acquisition_rates = ( + self.ls_acq.slice_settings.acquisition_rate + ) + globals.ls_laser_powers = ( + self.ls_acq.channel_settings.laser_powers_per_well[well_id] + ) + + # This is a bit of a hack, laser powers should be set in update_ls_hardware + for c_idx in range(self.ls_acq.channel_settings.num_channels): + update_laser_power( + self.ls_acq.channel_settings.light_sources, + c_idx + ) + # Acq rate needs to be updated even if autoexposure was not rerun in this well # Only do that if we are using autoexposure? self.update_ls_acquisition_rates( self.ls_acq.channel_settings.exposure_times_per_well[well_id] ) - globals.ls_slice_acquisition_rates = ( - self.ls_acq.slice_settings.acquisition_rate - ) - globals.ls_laser_powers = ( - self.ls_acq.channel_settings.laser_powers_per_well[well_id] - ) # O3 refocus # Failing to refocus O3 will not abort the acquisition at the current PT index diff --git a/mantis/acquisition/hook_functions/post_hardware_hook_functions.py b/mantis/acquisition/hook_functions/post_hardware_hook_functions.py index a7f61ce5..fcfc6b95 100644 --- a/mantis/acquisition/hook_functions/post_hardware_hook_functions.py +++ b/mantis/acquisition/hook_functions/post_hardware_hook_functions.py @@ -25,10 +25,7 @@ def log_acquisition_start(events): return events -def update_daq_freq(z_ctr_task, channels: list, events): - _event = get_first_acquisition_event(events) - - c_idx = channels.index(_event['axes']['channel']) +def update_daq_freq(z_ctr_task, c_idx: int): if z_ctr_task.is_task_done(): z_ctr_task.stop() # Counter needs to be stopped first z_ctr = z_ctr_task.co_channels[0] @@ -38,13 +35,8 @@ def update_daq_freq(z_ctr_task, channels: list, events): logger.debug(f'Updating {z_ctr.name} pulse frequency to {acq_rates[c_idx]}') z_ctr.co_pulse_freq = acq_rates[c_idx] - return events - - -def update_laser_power(lasers, channels: list, events): - _event = get_first_acquisition_event(events) - c_idx = channels.index(_event['axes']['channel']) +def update_laser_power(lasers, c_idx: int): laser = lasers[c_idx] # will be None if this channel does not use autoexposure if laser and globals.new_well: @@ -55,15 +47,17 @@ def update_laser_power(lasers, channels: list, events): # Note, setting laser power takes ~1 s which is slow laser.pulse_power = laser_power - return events - -def update_ls_hardware(z_ctr_task, lasers, channels, events): +def update_ls_hardware(z_ctr_task, lasers, channels: list, events): if not events: logger.debug('Acquisition events are not valid.') return + + _event = get_first_acquisition_event(events) + c_idx = channels.index(_event['axes']['channel']) - events = update_daq_freq(z_ctr_task, channels, events) - events = update_laser_power(lasers, channels, events) + update_daq_freq(z_ctr_task, c_idx) + # As a hack, setting laser power after call to `run_autoexposure` + # update_laser_power(lasers, c_idx) return events From df96795669e82d62b2ed8c34a7c58083c87b8c98 Mon Sep 17 00:00:00 2001 From: Ivan Ivanov Date: Tue, 3 Dec 2024 17:51:17 -0800 Subject: [PATCH 14/24] style --- mantis/acquisition/acq_engine.py | 29 ++++++++++--------- .../post_hardware_hook_functions.py | 2 +- 2 files changed, 17 insertions(+), 14 deletions(-) diff --git a/mantis/acquisition/acq_engine.py b/mantis/acquisition/acq_engine.py index 63ef7e05..0959154c 100644 --- a/mantis/acquisition/acq_engine.py +++ b/mantis/acquisition/acq_engine.py @@ -786,10 +786,7 @@ def acquire_ls_defocus_stack( # acquire defocus stack z_stack = microscope_operations.acquire_defocus_stack( - mmc, - z_stage, - z_range, - backlash_correction_distance=KIM101_BACKLASH + mmc, z_stage, z_range, backlash_correction_distance=KIM101_BACKLASH ) data.append(z_stack) @@ -806,7 +803,9 @@ def acquire_ls_defocus_stack( return np.asarray(data) - def refocus_ls_path(self, scan_left: bool = False, scan_right: bool = False) -> tuple[bool, bool, bool]: + def refocus_ls_path( + self, scan_left: bool = False, scan_right: bool = False + ) -> tuple[bool, bool, bool]: logger.info('Running O3 refocus algorithm on light-sheet arm') success = False @@ -888,7 +887,9 @@ def refocus_ls_path(self, scan_left: bool = False, scan_right: bool = False) -> # Refocus O3 # Some focus_indices may be None, e.g. if there is no sample - valid_focus_indices = [idx for (idx, fwhm) in zip(focus_indices, peak_FWHM) if fwhm > threshold_FWHM] + valid_focus_indices = [ + idx for (idx, fwhm) in zip(focus_indices, peak_FWHM) if fwhm > threshold_FWHM + ] if valid_focus_indices: focus_idx = int(np.median(valid_focus_indices)) o3_displacement = int(o3_z_range[focus_idx]) @@ -906,13 +907,16 @@ def refocus_ls_path(self, scan_left: bool = False, scan_right: bool = False) -> # Only do this if we are not already scanning at an extended range focus_indices = np.asarray(focus_indices) max_idx = len(o3_z_range) - 1 - if all(focus_indices < 0.2*max_idx): + if all(focus_indices < 0.2 * max_idx): scan_left = True - logger.info('O3 autofocus will scan further to the left at the next iteration') - if all(focus_indices > 0.8*max_idx): + logger.info( + 'O3 autofocus will scan further to the left at the next iteration' + ) + if all(focus_indices > 0.8 * max_idx): scan_right = True - logger.info('O3 autofocus will scan further to the right at the next iteration') - + logger.info( + 'O3 autofocus will scan further to the right at the next iteration' + ) return success, scan_left, scan_right @@ -1121,8 +1125,7 @@ def acquire(self): # This is a bit of a hack, laser powers should be set in update_ls_hardware for c_idx in range(self.ls_acq.channel_settings.num_channels): update_laser_power( - self.ls_acq.channel_settings.light_sources, - c_idx + self.ls_acq.channel_settings.light_sources, c_idx ) # Acq rate needs to be updated even if autoexposure was not rerun in this well diff --git a/mantis/acquisition/hook_functions/post_hardware_hook_functions.py b/mantis/acquisition/hook_functions/post_hardware_hook_functions.py index fcfc6b95..38cab77a 100644 --- a/mantis/acquisition/hook_functions/post_hardware_hook_functions.py +++ b/mantis/acquisition/hook_functions/post_hardware_hook_functions.py @@ -52,7 +52,7 @@ def update_ls_hardware(z_ctr_task, lasers, channels: list, events): if not events: logger.debug('Acquisition events are not valid.') return - + _event = get_first_acquisition_event(events) c_idx = channels.index(_event['axes']['channel']) From b984cc919c3eff3941c91eb9621b2402416552eb Mon Sep 17 00:00:00 2001 From: Ivan Ivanov Date: Tue, 3 Dec 2024 17:52:42 -0800 Subject: [PATCH 15/24] move `github-markdown.css` to `mantis.acquisition.templates` --- mantis/acquisition/{scripts => templates}/github-markdown.css | 0 mantis/analysis/analyze_psf.py | 3 ++- 2 files changed, 2 insertions(+), 1 deletion(-) rename mantis/acquisition/{scripts => templates}/github-markdown.css (100%) diff --git a/mantis/acquisition/scripts/github-markdown.css b/mantis/acquisition/templates/github-markdown.css similarity index 100% rename from mantis/acquisition/scripts/github-markdown.css rename to mantis/acquisition/templates/github-markdown.css diff --git a/mantis/analysis/analyze_psf.py b/mantis/analysis/analyze_psf.py index 1d1641c7..2eb0275c 100644 --- a/mantis/analysis/analyze_psf.py +++ b/mantis/analysis/analyze_psf.py @@ -21,6 +21,7 @@ from scipy.signal import peak_widths import mantis.acquisition.scripts +import mantis.acquisition.templates def _make_plots( @@ -141,7 +142,7 @@ def generate_report( df_gaussian_fit.to_csv(output_path / 'psf_gaussian_fit.csv', index=False) df_1d_peak_width.to_csv(output_path / 'psf_1d_peak_width.csv', index=False) - with pkg_resources.path(mantis.acquisition.scripts, 'github-markdown.css') as css_path: + with pkg_resources.path(mantis.acquisition.templates, 'github-markdown.css') as css_path: shutil.copy(css_path, output_path) html_file_path = output_path / ('psf_analysis_report.html') with open(html_file_path, 'w') as file: From a8e2997a6b3f9eb61e3cd5d0dd8b186059875006 Mon Sep 17 00:00:00 2001 From: Ivan Ivanov Date: Tue, 3 Dec 2024 17:56:15 -0800 Subject: [PATCH 16/24] move out scripts --- mantis/acquisition/scripts/__init__.py | 0 pyproject.toml | 1 + {mantis/acquisition/scripts => scripts}/calibrate_O3_piezo.py | 0 {mantis/acquisition/scripts => scripts}/measure_psf.py | 2 +- 4 files changed, 2 insertions(+), 1 deletion(-) delete mode 100644 mantis/acquisition/scripts/__init__.py rename {mantis/acquisition/scripts => scripts}/calibrate_O3_piezo.py (100%) rename {mantis/acquisition/scripts => scripts}/measure_psf.py (99%) diff --git a/mantis/acquisition/scripts/__init__.py b/mantis/acquisition/scripts/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/pyproject.toml b/pyproject.toml index f94a53ff..24eda25f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -94,6 +94,7 @@ exclude = ''' | notebooks | ignore | examples + | scripts )/ ) ''' diff --git a/mantis/acquisition/scripts/calibrate_O3_piezo.py b/scripts/calibrate_O3_piezo.py similarity index 100% rename from mantis/acquisition/scripts/calibrate_O3_piezo.py rename to scripts/calibrate_O3_piezo.py diff --git a/mantis/acquisition/scripts/measure_psf.py b/scripts/measure_psf.py similarity index 99% rename from mantis/acquisition/scripts/measure_psf.py rename to scripts/measure_psf.py index 5584f5be..64c566cf 100644 --- a/mantis/acquisition/scripts/measure_psf.py +++ b/scripts/measure_psf.py @@ -189,7 +189,7 @@ def check_acquisition_directory(root_dir: Path, acq_name: str, suffix='', idx=1) raw = True patch_size = (scale[0] * 30, scale[1] * 36, scale[2] * 18) -# %% Characterize peaks +## %% Characterize peaks peaks = _characterize_psf( zyx_data=zyx_data, From b97330cf3889c5459a0d26a0f6807a938f71b6d0 Mon Sep 17 00:00:00 2001 From: Ivan Ivanov Date: Tue, 3 Dec 2024 18:01:08 -0800 Subject: [PATCH 17/24] skip sorting in scripts --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 24eda25f..bd262598 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -106,7 +106,7 @@ lines_between_types = 1 default_section = "THIRDPARTY" no_lines_before = ["STDLIB"] ensure_newline_before_comments = true -skip_glob = ["examples/*"] +skip_glob = ["examples/*", "scripts/*"] [tool.pylint] # disable all conventions, refactors, warnings (C, R, W) and the following: From 92716f1a3b8bb6074ba523766b3c2b496a891bca Mon Sep 17 00:00:00 2001 From: Ivan Ivanov Date: Wed, 4 Dec 2024 13:00:18 -0800 Subject: [PATCH 18/24] Add option to exclude wells from O3 refocus and adjust how acquisition rates are updates after autoexposure --- mantis/acquisition/AcquisitionSettings.py | 1 + mantis/acquisition/acq_engine.py | 41 ++++++++++++----------- 2 files changed, 23 insertions(+), 19 deletions(-) diff --git a/mantis/acquisition/AcquisitionSettings.py b/mantis/acquisition/AcquisitionSettings.py index d1225c58..c46065f0 100644 --- a/mantis/acquisition/AcquisitionSettings.py +++ b/mantis/acquisition/AcquisitionSettings.py @@ -141,6 +141,7 @@ class MicroscopeSettings: use_o3_refocus: bool = False o3_refocus_config: Optional[ConfigSettings] = None o3_refocus_interval_min: Optional[int] = None + o3_refocus_skip_wells: List[str] = field(default_factory=list) @dataclass diff --git a/mantis/acquisition/acq_engine.py b/mantis/acquisition/acq_engine.py index 63ef7e05..7ae11ad9 100644 --- a/mantis/acquisition/acq_engine.py +++ b/mantis/acquisition/acq_engine.py @@ -62,7 +62,7 @@ LS_CHANGE_TIME = 200 # time needed to change LS filter wheel, in ms LS_KIM101_SN = 74000291 LF_KIM101_SN = 74000565 -KIM101_BACKLASH = -5 # backlash correction distance, in steps +KIM101_BACKLASH = 0 # backlash correction distance, in steps VORTRAN_488_COM_PORT = 'COM6' VORTRAN_561_COM_PORT = 'COM13' VORTRAN_639_COM_PORT = 'COM12' @@ -1111,13 +1111,10 @@ def acquire(self): method=self.ls_acq.autoexposure_settings.autoexposure_method, ) - globals.ls_slice_acquisition_rates = ( - self.ls_acq.slice_settings.acquisition_rate - ) + # needs to be set before calling update_laser_power globals.ls_laser_powers = ( self.ls_acq.channel_settings.laser_powers_per_well[well_id] ) - # This is a bit of a hack, laser powers should be set in update_ls_hardware for c_idx in range(self.ls_acq.channel_settings.num_channels): update_laser_power( @@ -1130,24 +1127,30 @@ def acquire(self): self.update_ls_acquisition_rates( self.ls_acq.channel_settings.exposure_times_per_well[well_id] ) + # needs to be set after calling update_ls_acquisition_rates + globals.ls_slice_acquisition_rates = ( + self.ls_acq.slice_settings.acquisition_rate + ) # O3 refocus # Failing to refocus O3 will not abort the acquisition at the current PT index if self.ls_acq.microscope_settings.use_o3_refocus: - current_time = time.time() - # Always refocus at the start - if ( - (t_idx == 0 and p_idx == 0) - or current_time - ls_o3_refocus_time - > self.ls_acq.microscope_settings.o3_refocus_interval_min * 60 - ): - success, scan_left, scan_right = self.refocus_ls_path() - # If autofocus fails, try again with extended range if we know which way to go - if not success and any((scan_left, scan_right)): - success, _, _ = self.refocus_ls_path(scan_left, scan_right) - # If it failed again, retry at the next position - if success: - ls_o3_refocus_time = current_time + # O3 refocus can be skipped for certain wells + if well_id not in self.ls_acq.microscope_settings.o3_refocus_skip_wells: + current_time = time.time() + # Always refocus at the start + if ( + (t_idx == 0 and p_idx == 0) + or current_time - ls_o3_refocus_time + > self.ls_acq.microscope_settings.o3_refocus_interval_min * 60 + ): + success, scan_left, scan_right = self.refocus_ls_path() + # If autofocus fails, try again with extended range if we know which way to go + if not success and any((scan_left, scan_right)): + success, _, _ = self.refocus_ls_path(scan_left, scan_right) + # If it failed again, retry at the next position + if success: + ls_o3_refocus_time = current_time # update events dictionaries lf_events = deepcopy(lf_cz_events) From 4091a6899390d471e63df582da131ec33d1b62aa Mon Sep 17 00:00:00 2001 From: Ivan Ivanov Date: Wed, 4 Dec 2024 13:02:04 -0800 Subject: [PATCH 19/24] style --- mantis/acquisition/acq_engine.py | 29 ++++++++++--------- .../post_hardware_hook_functions.py | 2 +- 2 files changed, 17 insertions(+), 14 deletions(-) diff --git a/mantis/acquisition/acq_engine.py b/mantis/acquisition/acq_engine.py index 7ae11ad9..f5fb0a74 100644 --- a/mantis/acquisition/acq_engine.py +++ b/mantis/acquisition/acq_engine.py @@ -786,10 +786,7 @@ def acquire_ls_defocus_stack( # acquire defocus stack z_stack = microscope_operations.acquire_defocus_stack( - mmc, - z_stage, - z_range, - backlash_correction_distance=KIM101_BACKLASH + mmc, z_stage, z_range, backlash_correction_distance=KIM101_BACKLASH ) data.append(z_stack) @@ -806,7 +803,9 @@ def acquire_ls_defocus_stack( return np.asarray(data) - def refocus_ls_path(self, scan_left: bool = False, scan_right: bool = False) -> tuple[bool, bool, bool]: + def refocus_ls_path( + self, scan_left: bool = False, scan_right: bool = False + ) -> tuple[bool, bool, bool]: logger.info('Running O3 refocus algorithm on light-sheet arm') success = False @@ -888,7 +887,9 @@ def refocus_ls_path(self, scan_left: bool = False, scan_right: bool = False) -> # Refocus O3 # Some focus_indices may be None, e.g. if there is no sample - valid_focus_indices = [idx for (idx, fwhm) in zip(focus_indices, peak_FWHM) if fwhm > threshold_FWHM] + valid_focus_indices = [ + idx for (idx, fwhm) in zip(focus_indices, peak_FWHM) if fwhm > threshold_FWHM + ] if valid_focus_indices: focus_idx = int(np.median(valid_focus_indices)) o3_displacement = int(o3_z_range[focus_idx]) @@ -906,13 +907,16 @@ def refocus_ls_path(self, scan_left: bool = False, scan_right: bool = False) -> # Only do this if we are not already scanning at an extended range focus_indices = np.asarray(focus_indices) max_idx = len(o3_z_range) - 1 - if all(focus_indices < 0.2*max_idx): + if all(focus_indices < 0.2 * max_idx): scan_left = True - logger.info('O3 autofocus will scan further to the left at the next iteration') - if all(focus_indices > 0.8*max_idx): + logger.info( + 'O3 autofocus will scan further to the left at the next iteration' + ) + if all(focus_indices > 0.8 * max_idx): scan_right = True - logger.info('O3 autofocus will scan further to the right at the next iteration') - + logger.info( + 'O3 autofocus will scan further to the right at the next iteration' + ) return success, scan_left, scan_right @@ -1118,8 +1122,7 @@ def acquire(self): # This is a bit of a hack, laser powers should be set in update_ls_hardware for c_idx in range(self.ls_acq.channel_settings.num_channels): update_laser_power( - self.ls_acq.channel_settings.light_sources, - c_idx + self.ls_acq.channel_settings.light_sources, c_idx ) # Acq rate needs to be updated even if autoexposure was not rerun in this well diff --git a/mantis/acquisition/hook_functions/post_hardware_hook_functions.py b/mantis/acquisition/hook_functions/post_hardware_hook_functions.py index fcfc6b95..38cab77a 100644 --- a/mantis/acquisition/hook_functions/post_hardware_hook_functions.py +++ b/mantis/acquisition/hook_functions/post_hardware_hook_functions.py @@ -52,7 +52,7 @@ def update_ls_hardware(z_ctr_task, lasers, channels: list, events): if not events: logger.debug('Acquisition events are not valid.') return - + _event = get_first_acquisition_event(events) c_idx = channels.index(_event['axes']['channel']) From f5ce8ccd21370c6f174a0bed1b3fe4bb689104e2 Mon Sep 17 00:00:00 2001 From: Ivan Ivanov Date: Wed, 4 Dec 2024 13:06:29 -0800 Subject: [PATCH 20/24] remove unused scripts import --- mantis/analysis/analyze_psf.py | 1 - 1 file changed, 1 deletion(-) diff --git a/mantis/analysis/analyze_psf.py b/mantis/analysis/analyze_psf.py index 2eb0275c..5dc7c285 100644 --- a/mantis/analysis/analyze_psf.py +++ b/mantis/analysis/analyze_psf.py @@ -20,7 +20,6 @@ from numpy.typing import ArrayLike from scipy.signal import peak_widths -import mantis.acquisition.scripts import mantis.acquisition.templates From b6df77e38e163012f47ed44ea7127711f47146a3 Mon Sep 17 00:00:00 2001 From: Ivan Ivanov Date: Wed, 4 Dec 2024 15:50:11 -0800 Subject: [PATCH 21/24] turn off live mode, check for illumination.csv file, and tweak o3 refocus var names --- mantis/acquisition/acq_engine.py | 53 ++++++++++++++++++++------------ 1 file changed, 34 insertions(+), 19 deletions(-) diff --git a/mantis/acquisition/acq_engine.py b/mantis/acquisition/acq_engine.py index f5fb0a74..6297f0f0 100644 --- a/mantis/acquisition/acq_engine.py +++ b/mantis/acquisition/acq_engine.py @@ -211,6 +211,12 @@ def setup(self): Apply acquisition settings as specified by the class properties """ if self.enabled: + # Turn off Live mode + if self.mmStudio: + snap_live_manager = self.mmStudio.get_snap_live_manager() + if snap_live_manager.is_live_mode_on(): + snap_live_manager.set_live_mode_on(False) + # Apply microscope config settings for settings in self.microscope_settings.config_group_settings: microscope_operations.set_config( @@ -642,6 +648,13 @@ def setup_autoexposure(self): 'Autoexposure is not supported in demo mode. Using default exposure time and laser power' ) return + + if self.ls_acq.autoexposure_settings.autoexposure_method == 'manual': + # Check that the 'illumination.csv' file exists + if not (self._root_dir / 'illumination.csv').exists(): + raise FileNotFoundError( + f'The illumination.csv file required for manual autoexposure was not found in {self._root_dir}' + ) # initialize lasers for channel_idx, config_name in enumerate(self.ls_acq.channel_settings.channels): @@ -867,18 +880,18 @@ def refocus_ls_path( threshold_FWHM = 4.5 focus_indices = [] - peak_FWHM = [] + peak_indices = [] for stack_idx, stack in enumerate(data): - idx, fwhm = focus_from_transverse_band( + idx, stats = focus_from_transverse_band( stack, NA_det=NA_DETECTION, lambda_ill=wavelength, pixel_size=LS_PIXEL_SIZE, - threshold_FWHM=0, # we'll discard invalid peaks below + threshold_FWHM=threshold_FWHM, plot_path=self._logs_dir / f'ls_refocus_plot_{timestamp}_Pos{stack_idx}.png', ) focus_indices.append(idx) - peak_FWHM.append(fwhm) + peak_indices.append(stats['peak_index']) logger.debug( 'Stacks at galvo positions %s are in focus at slice %s', np.round(galvo_range, 3), @@ -887,9 +900,7 @@ def refocus_ls_path( # Refocus O3 # Some focus_indices may be None, e.g. if there is no sample - valid_focus_indices = [ - idx for (idx, fwhm) in zip(focus_indices, peak_FWHM) if fwhm > threshold_FWHM - ] + valid_focus_indices = [idx for idx in focus_indices if idx is not None] if valid_focus_indices: focus_idx = int(np.median(valid_focus_indices)) o3_displacement = int(o3_z_range[focus_idx]) @@ -905,14 +916,14 @@ def refocus_ls_path( ) if not any((scan_left, scan_right)): # Only do this if we are not already scanning at an extended range - focus_indices = np.asarray(focus_indices) + peak_indices = np.asarray(peak_indices) max_idx = len(o3_z_range) - 1 - if all(focus_indices < 0.2 * max_idx): + if all(peak_indices < 0.2 * max_idx): scan_left = True logger.info( 'O3 autofocus will scan further to the left at the next iteration' ) - if all(focus_indices > 0.8 * max_idx): + if all(peak_indices > 0.8 * max_idx): scan_right = True logger.info( 'O3 autofocus will scan further to the right at the next iteration' @@ -1138,15 +1149,19 @@ def acquire(self): # O3 refocus # Failing to refocus O3 will not abort the acquisition at the current PT index if self.ls_acq.microscope_settings.use_o3_refocus: - # O3 refocus can be skipped for certain wells - if well_id not in self.ls_acq.microscope_settings.o3_refocus_skip_wells: - current_time = time.time() - # Always refocus at the start - if ( - (t_idx == 0 and p_idx == 0) - or current_time - ls_o3_refocus_time - > self.ls_acq.microscope_settings.o3_refocus_interval_min * 60 - ): + current_time = time.time() + # Always refocus at the start + if ( + (t_idx == 0 and p_idx == 0) + or current_time - ls_o3_refocus_time + > self.ls_acq.microscope_settings.o3_refocus_interval_min * 60 + ): + # O3 refocus can be skipped for certain wells + if well_id in self.ls_acq.microscope_settings.o3_refocus_skip_wells: + logger.debug( + f'O3 refocus is due, but will be skipped in well {well_id}.' + ) + else: success, scan_left, scan_right = self.refocus_ls_path() # If autofocus fails, try again with extended range if we know which way to go if not success and any((scan_left, scan_right)): From e1600a472c824b13a06c794401e9a60da17521f0 Mon Sep 17 00:00:00 2001 From: Ivan Ivanov Date: Wed, 4 Dec 2024 17:00:15 -0800 Subject: [PATCH 22/24] update waveorder requirements --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 2c7d0520..175c6a0c 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -40,7 +40,7 @@ dependencies = [ "slurmkit @ git+https://github.com/royerlab/slurmkit", "tifffile", "torch>=2.3", - "waveorder @ git+https://github.com/mehta-lab/waveorder", + "waveorder @ git+https://github.com/mehta-lab/waveorder@return_peak_stats", "largestinteriorrectangle", "antspyx", "pystackreg", From 93f644ff38e649eda3767d2d2b785cc9e4ac9202 Mon Sep 17 00:00:00 2001 From: Ivan Ivanov Date: Wed, 4 Dec 2024 17:08:57 -0800 Subject: [PATCH 23/24] style --- mantis/acquisition/acq_engine.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mantis/acquisition/acq_engine.py b/mantis/acquisition/acq_engine.py index 6297f0f0..5a53e09e 100644 --- a/mantis/acquisition/acq_engine.py +++ b/mantis/acquisition/acq_engine.py @@ -648,7 +648,7 @@ def setup_autoexposure(self): 'Autoexposure is not supported in demo mode. Using default exposure time and laser power' ) return - + if self.ls_acq.autoexposure_settings.autoexposure_method == 'manual': # Check that the 'illumination.csv' file exists if not (self._root_dir / 'illumination.csv').exists(): From d2d67d92c1d1e973a680119030f6ea3cb6b40832 Mon Sep 17 00:00:00 2001 From: Ivan Ivanov Date: Thu, 5 Dec 2024 17:27:33 -0800 Subject: [PATCH 24/24] fix failing estimate_stabilization test --- mantis/cli/estimate_stabilization.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mantis/cli/estimate_stabilization.py b/mantis/cli/estimate_stabilization.py index d718acb8..ea02f3cf 100644 --- a/mantis/cli/estimate_stabilization.py +++ b/mantis/cli/estimate_stabilization.py @@ -47,7 +47,7 @@ def estimate_position_focus( if np.sum(data_zyx) == 0: focal_plane = 0 else: - focal_plane = focus_from_transverse_band( + focal_plane, _ = focus_from_transverse_band( data_zyx, NA_det=NA_DET, lambda_ill=LAMBDA_ILL,