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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion sashimi/hardware/cameras/hamamatsu/interface.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
1 change: 1 addition & 0 deletions sashimi/hardware/cameras/hamamatsu/sdk.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@

# Hamamatsu structures.


## DCAMAPI_INIT
#
# The dcam initialization structure
Expand Down
107 changes: 106 additions & 1 deletion sashimi/hardware/scanning/scanloops.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ class ScanningState(Enum):
PAUSED = 1
PLANAR = 2
VOLUMETRIC = 3
TRIGGERED_PLANAR = 4


class ExperimentPrepareState(Enum):
Expand Down Expand Up @@ -110,7 +111,6 @@ def __init__(
logger: ConcurrenceLogger,
trigger_exp_from_scanner,
):

self.sample_rate = sample_rate
self.n_samples = n_samples

Expand Down Expand Up @@ -258,6 +258,111 @@ 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,
Copy link
Copy Markdown
Contributor Author

@nvbln nvbln Mar 1, 2024

Choose a reason for hiding this comment

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

I'm abusing the set_impulses function here to get a camera pulses buffer for planar mode by setting the number of planes to one. I think we can get rid of this function and instead simply set the first item in the buffer to 5. Increasing the frame rate will be reflected in n_samples_period() becoming shorter, so no further modification of the camera pulses buffer is necessary.

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())
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Admittedly, I don't fully understand what is happening here. In the triggerless planar mode this used to be encapsulated by the least common multiple. I changed it to max() like in the VolumetricScanLoop as it made sense to me that it would be the size of the frame trigger plus the time until the next one.


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)
Expand Down
1 change: 0 additions & 1 deletion sashimi/processes/camera.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
3 changes: 3 additions & 0 deletions sashimi/processes/scanning.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
ScanningState,
ScanParameters,
PlanarScanLoop,
TriggeredPlanarScanLoop,
VolumetricScanLoop,
)
from sashimi.hardware.scanning import ScanningError
Expand Down Expand Up @@ -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

Expand Down
5 changes: 1 addition & 4 deletions sashimi/state.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand Down Expand Up @@ -275,7 +274,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,
Expand Down Expand Up @@ -535,7 +534,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:
Expand Down Expand Up @@ -669,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()

Expand Down