From c73d1ff7a05755f688889ea91c682ce35c8ccc01 Mon Sep 17 00:00:00 2001 From: Nathan van Beelen Date: Fri, 1 Mar 2024 14:25:17 +0100 Subject: [PATCH 1/2] Add trigger to planar mode --- sashimi/hardware/scanning/scanloops.py | 103 +++++++++++++++++++++++++ sashimi/processes/scanning.py | 3 + sashimi/state.py | 3 +- 3 files changed, 107 insertions(+), 2 deletions(-) diff --git a/sashimi/hardware/scanning/scanloops.py b/sashimi/hardware/scanning/scanloops.py index 7f07ea79..3b4c4bc0 100644 --- a/sashimi/hardware/scanning/scanloops.py +++ b/sashimi/hardware/scanning/scanloops.py @@ -22,6 +22,7 @@ class ScanningState(Enum): PAUSED = 1 PLANAR = 2 VOLUMETRIC = 3 + TRIGGERED_PLANAR = 4 class ExperimentPrepareState(Enum): @@ -258,6 +259,108 @@ def fill_arrays(self): self.wait_signal.clear() +class TriggeredPlanarScanLoop(ScanLoop): + """Class for controlling the planar scanning mode, where we image only one plane and + do not control the piezo and vertical galvo.""" + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + buffer_len = int(round(self.sample_rate / self.parameters.triggering.frequency)) + self.camera_pulses = RollingBuffer(buffer_len) + set_impulses( + self.camera_pulses.buffer, + 1, + n_skip_start=0, + n_skip_end=0, + ) + self.current_frequency = self.parameters.triggering.frequency + self.camera_on = False + self.trigger_exp_start = False + self.camera_was_off = True + self.wait_signal.set() + + def initialize(self): + super().initialize() + self.camera_on = False + self.wait_signal.set() + + def loop_condition(self): + return ( + super().loop_condition() and self.parameters.state == ScanningState.TRIGGERED_PLANAR + ) + + def n_samples_period(self): + if ( + self.parameters.triggering.frequency is None + or self.parameters.triggering.frequency == 0 + ): + return super().n_samples_period() + else: + n_samples_trigger = int( + round(self.sample_rate / self.parameters.triggering.frequency) + ) + return max(n_samples_trigger, super().n_samples_period()) + + def update_settings(self): + updated = super().update_settings() + if not updated and not self.first_update: + return False + + if self.parameters.state != ScanningState.TRIGGERED_PLANAR: + return True + + if self.parameters != self.old_parameters: + self.initialize() + + if not self.camera_on and self.n_samples_read > self.n_samples_period(): + self.camera_on = True + self.trigger_exp_start = True + elif not self.camera_on: + self.wait_signal.set() + return True + + def fill_arrays(self): + # Fill the z values + self.board.z_piezo = self.parameters.z.piezo + if isinstance(self.parameters.z, ZManual): + self.board.z_lateral = self.parameters.z.lateral + self.board.z_frontal = self.parameters.z.frontal + elif isinstance(self.parameters.z, ZSynced): + self.board.z_lateral = calc_sync( + self.parameters.z.piezo, self.parameters.z.lateral_sync + ) + self.board.z_frontal = calc_sync( + self.parameters.z.piezo, self.parameters.z.frontal_sync + ) + + super().fill_arrays() + + camera_pulses = 0 + if self.camera_on: + self.logger.log_message("I") + if self.camera_was_off: + self.logger.log_message("Camera was off") + # calculate how many samples are remaining until we are in a new period + if self.i_sample == 0: + camera_pulses = self.camera_pulses.read(self.i_sample, self.n_samples) + self.camera_was_off = False + self.wait_signal.clear() + else: + n_to_next_start = self.n_samples_period() - self.i_sample + if n_to_next_start < self.n_samples: + camera_pulses = self.camera_pulses.read( + self.i_sample, self.n_samples + ).copy() + camera_pulses[:n_to_next_start] = 0 + self.camera_was_off = False + self.wait_signal.clear() + else: + camera_pulses = self.camera_pulses.read(self.i_sample, self.n_samples) + self.wait_signal.clear() + + self.board.camera_trigger = camera_pulses + + class VolumetricScanLoop(ScanLoop): def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) diff --git a/sashimi/processes/scanning.py b/sashimi/processes/scanning.py index 2dae5088..439ca798 100644 --- a/sashimi/processes/scanning.py +++ b/sashimi/processes/scanning.py @@ -5,6 +5,7 @@ ScanningState, ScanParameters, PlanarScanLoop, + TriggeredPlanarScanLoop, VolumetricScanLoop, ) from sashimi.hardware.scanning import ScanningError @@ -96,6 +97,8 @@ def run(self): with configurator(self.sample_rate, self.n_samples, conf) as board: if self.parameters.state == ScanningState.PLANAR: loop = PlanarScanLoop + elif self.parameters.state == ScanningState.TRIGGERED_PLANAR: + loop = TriggeredPlanarScanLoop elif self.parameters.state == ScanningState.VOLUMETRIC: loop = VolumetricScanLoop diff --git a/sashimi/state.py b/sashimi/state.py index fd72b3cc..363c8a0f 100644 --- a/sashimi/state.py +++ b/sashimi/state.py @@ -275,7 +275,7 @@ def convert_single_plane_params( calibration: Calibration, ): return ScanParameters( - state=ScanningState.PLANAR, + state=ScanningState.TRIGGERED_PLANAR, xy=convert_planar_params(planar), z=ZSynced( piezo=single_plane_setting.piezo, @@ -535,7 +535,6 @@ def camera_params(self): camera_params.trigger_mode = ( TriggerMode.FREE if self.global_state == GlobalState.PREVIEW - or self.global_state == GlobalState.PLANAR_PREVIEW else TriggerMode.EXTERNAL_TRIGGER ) if self.global_state == GlobalState.PAUSED: From c9438b3805df0da6fe1a8f39765accb9c294e3cb Mon Sep 17 00:00:00 2001 From: Nathan van Beelen Date: Fri, 1 Mar 2024 14:56:05 +0100 Subject: [PATCH 2/2] Format with black --- sashimi/hardware/cameras/hamamatsu/interface.py | 1 - sashimi/hardware/cameras/hamamatsu/sdk.py | 1 + sashimi/hardware/scanning/scanloops.py | 8 +++++--- sashimi/processes/camera.py | 1 - sashimi/state.py | 2 -- 5 files changed, 6 insertions(+), 7 deletions(-) diff --git a/sashimi/hardware/cameras/hamamatsu/interface.py b/sashimi/hardware/cameras/hamamatsu/interface.py index edcab219..0ddeff84 100644 --- a/sashimi/hardware/cameras/hamamatsu/interface.py +++ b/sashimi/hardware/cameras/hamamatsu/interface.py @@ -242,7 +242,6 @@ def get_property_value(self, property_name): def set_property_value(self, property_name, property_value, *args, **kwargs): # Check if the property exists. if not (property_name in self.properties): - raise CameraException(f"Unknown property name {property_name}") # If the value is text, figure out what the diff --git a/sashimi/hardware/cameras/hamamatsu/sdk.py b/sashimi/hardware/cameras/hamamatsu/sdk.py index ff973b10..699716eb 100644 --- a/sashimi/hardware/cameras/hamamatsu/sdk.py +++ b/sashimi/hardware/cameras/hamamatsu/sdk.py @@ -44,6 +44,7 @@ # Hamamatsu structures. + ## DCAMAPI_INIT # # The dcam initialization structure diff --git a/sashimi/hardware/scanning/scanloops.py b/sashimi/hardware/scanning/scanloops.py index 3b4c4bc0..e7f01c8e 100644 --- a/sashimi/hardware/scanning/scanloops.py +++ b/sashimi/hardware/scanning/scanloops.py @@ -111,7 +111,6 @@ def __init__( logger: ConcurrenceLogger, trigger_exp_from_scanner, ): - self.sample_rate = sample_rate self.n_samples = n_samples @@ -286,7 +285,8 @@ def initialize(self): def loop_condition(self): return ( - super().loop_condition() and self.parameters.state == ScanningState.TRIGGERED_PLANAR + super().loop_condition() + and self.parameters.state == ScanningState.TRIGGERED_PLANAR ) def n_samples_period(self): @@ -342,7 +342,9 @@ def fill_arrays(self): self.logger.log_message("Camera was off") # calculate how many samples are remaining until we are in a new period if self.i_sample == 0: - camera_pulses = self.camera_pulses.read(self.i_sample, self.n_samples) + camera_pulses = self.camera_pulses.read( + self.i_sample, self.n_samples + ) self.camera_was_off = False self.wait_signal.clear() else: diff --git a/sashimi/processes/camera.py b/sashimi/processes/camera.py index 809888a4..186fea29 100644 --- a/sashimi/processes/camera.py +++ b/sashimi/processes/camera.py @@ -178,7 +178,6 @@ def camera_loop(self): # if no frames are received (either this loop is in between frames # or we are in the waining period) if frames: - for frame in frames: self.logger.log_message( "received frame of shape " + str(frame.shape) diff --git a/sashimi/state.py b/sashimi/state.py index 363c8a0f..8f645037 100644 --- a/sashimi/state.py +++ b/sashimi/state.py @@ -228,7 +228,6 @@ def get_voxel_size( scanning_settings: Union[ZRecordingSettings, SinglePlaneSettings], camera_settings: CameraSettings, ): - binning = int(camera_settings.binning) if isinstance(scanning_settings, SinglePlaneSettings): @@ -668,7 +667,6 @@ def obtain_noise_average(self, n_images=50): self.dispatcher.calibration_ref_queue.put(self.calibration_ref) def reset_noise_subtraction(self): - self.calibration_ref = None self.noise_subtraction_active.clear()