From b50766a24b97de94934d250d922e2077d686d59b Mon Sep 17 00:00:00 2001 From: "bin.yang" Date: Wed, 23 Mar 2022 15:09:45 -0700 Subject: [PATCH 1/7] initial commit --- copylot/hardware/asi_stage/stage.py | 3 +- copylot/hardware/trial_timelapse.py | 70 +++++++++++++++++++++++++++++ 2 files changed, 72 insertions(+), 1 deletion(-) create mode 100644 copylot/hardware/trial_timelapse.py diff --git a/copylot/hardware/asi_stage/stage.py b/copylot/hardware/asi_stage/stage.py index 042d8f9a..91e6407b 100644 --- a/copylot/hardware/asi_stage/stage.py +++ b/copylot/hardware/asi_stage/stage.py @@ -6,6 +6,7 @@ class ASIStageScanMode(Enum): """ 0 for raster, 1 for serpentine """ + RASTER = 0 SERPENTINE = 1 @@ -38,7 +39,7 @@ def set_speed(self, speed): print("set speed to scan: " + message) self.ser.write(message) - def set_default_speed(self, speed): + def set_default_speed(self): message = "speed x=10 y=10\r" print("set speed to scan: " + message) self.ser.write(message) diff --git a/copylot/hardware/trial_timelapse.py b/copylot/hardware/trial_timelapse.py new file mode 100644 index 00000000..53ef7cd5 --- /dev/null +++ b/copylot/hardware/trial_timelapse.py @@ -0,0 +1,70 @@ +""" +This is a script to run basic timelapse together with the +nidaq script. This script tries to mimic the behavior given +in the stageScan_multiPos.bsh script. +""" +from os.path import join + +from copylot.hardware.asi_stage.stage import ASIStage, ASIStageScanMode + +path = "D:/data/20210506_xiang" +path_prefix = "stage_TL100_range500um_step0.31_4um_30ms_view2_interval_2s_488_20mW_561_10mW_1800tp_3pos" + +nb_channel = 2 +nb_view = 1 +nb_frames = 1000 +step_size_um = 0.310 * 2 +range_in_um = 500 +interval_in_seconds = 60 * 4 + +scan_mode = ASIStageScanMode.RASTER # raster or serpentine +custom_offset_in_um = 0 # offset between two views for better coverage +nr_channels = nb_view + +angle = 45 +res = 0.439 # xy pixel size + +alignment_offset_in_um = 0 +galvo_offset_in_um = 0 +offset = alignment_offset_in_um + galvo_offset_in_um + custom_offset_in_um + +nb_slices = range_in_um / step_size_um +# reset the scanning range for multi channel imaging +# if nb_slices % nb_channel != 0: +# nb_slices -= nb_slices % nb_channel +# range_in_um = nb_slices * step_size_um +print(f"nb_slices: {nb_slices}") + + +exposure_in_ms = 10 +print(f"exposure in ms: {exposure_in_ms}") + + +port = "COM4" +asi_stage = ASIStage(com_port=port) + +speed = step_size_um / exposure_in_ms +print(f"scan range in um: {range_in_um}") + + +save_path = join(path, path_prefix) + +axis_order = ["z", "channel", "time", "position"] + +asi_stage.set_scan_mode(scan_mode) +asi_stage.set_backlash() + + +# Set camera for external trigger and validate + +# Set camera trigger delay and validate + + +# Acquisition loop +# for f in range(nb_frames): + + +# Set camera for internal trigger and validate + + +asi_stage.set_default_speed() From 07259ec227231988607b5dc7c236c8cba36e70b8 Mon Sep 17 00:00:00 2001 From: "bin.yang" Date: Wed, 23 Mar 2022 16:50:44 -0700 Subject: [PATCH 2/7] wip, to keep changes --- copylot/hardware/trial_timelapse.py | 54 ++++++++++++++++++++++------- 1 file changed, 41 insertions(+), 13 deletions(-) diff --git a/copylot/hardware/trial_timelapse.py b/copylot/hardware/trial_timelapse.py index 53ef7cd5..c776538d 100644 --- a/copylot/hardware/trial_timelapse.py +++ b/copylot/hardware/trial_timelapse.py @@ -11,18 +11,29 @@ path_prefix = "stage_TL100_range500um_step0.31_4um_30ms_view2_interval_2s_488_20mW_561_10mW_1800tp_3pos" nb_channel = 2 -nb_view = 1 -nb_frames = 1000 -step_size_um = 0.310 * 2 -range_in_um = 500 -interval_in_seconds = 60 * 4 +nb_view = 2 +channels = ["488", "561"] +interleave = False + +nb_frames = 1 # number of timepoint +step_size_um = 0.155 * 5 +range_in_um = 250 +interval_timepoint_in_seconds = 0 +interval_in_seconds = 2 scan_mode = ASIStageScanMode.RASTER # raster or serpentine -custom_offset_in_um = 0 # offset between two views for better coverage -nr_channels = nb_view +custom_offset_in_um = 300 # offset between two views for better coverage +# nr_channels = nb_view + +if nb_channel == 1: + interleave = True + +if not interleave and len(channels) != nb_channel: + print("number of channels is not consistent") + nb_channel = len(channels) angle = 45 -res = 0.439 # xy pixel size +res = 0.219 # xy pixel size, TTL100 0.439, TTL200 0.219, TTL300 0.146, TTL165 0.266 alignment_offset_in_um = 0 galvo_offset_in_um = 0 @@ -30,9 +41,9 @@ nb_slices = range_in_um / step_size_um # reset the scanning range for multi channel imaging -# if nb_slices % nb_channel != 0: -# nb_slices -= nb_slices % nb_channel -# range_in_um = nb_slices * step_size_um +if nb_slices % nb_channel != 0 and interleave: + nb_slices -= nb_slices % nb_channel + range_in_um = nb_slices * step_size_um print(f"nb_slices: {nb_slices}") @@ -40,7 +51,7 @@ print(f"exposure in ms: {exposure_in_ms}") -port = "COM4" +port = "COM6" asi_stage = ASIStage(com_port=port) speed = step_size_um / exposure_in_ms @@ -53,6 +64,8 @@ asi_stage.set_scan_mode(scan_mode) asi_stage.set_backlash() +asi_stage.set_speed(speed=speed) +asi_stage.zero() # Set camera for external trigger and validate @@ -61,7 +74,22 @@ # Acquisition loop -# for f in range(nb_frames): +for f in range(nb_frames): + for view in range(nb_view): + + if view == 0: + asi_stage.scanr(x=0, y=range_in_um / 1000) + asi_stage.scanv(x=0, y=0, f=1.0) + else: + asi_stage.scanr(x=-offset / 1000, y=(-offset + range_in_um) / 1000) + asi_stage.scanv(x=0, y=0, f=1.0) + print(f"scan range in um: {range_in_um}") + + if interleave: + print(f"start interleaved acquisition: {interleave}") + asi_stage.start_scan() + + slice = 0 # Set camera for internal trigger and validate From 2657571b2fa88802a0d048ef892e1d1fff1e69a1 Mon Sep 17 00:00:00 2001 From: "bin.yang" Date: Thu, 24 Mar 2022 16:40:31 -0700 Subject: [PATCH 3/7] to keep changes --- copylot/hardware/asi_stage/stage.py | 22 +-- copylot/hardware/hamamatsu_camera/camera.py | 28 ++- copylot/hardware/hamamatsu_camera/dcam.py | 2 +- copylot/hardware/ni_daq/control.py | 196 ++++++++++++-------- copylot/hardware/trial_timelapse.py | 158 +++++++++++++--- 5 files changed, 273 insertions(+), 133 deletions(-) diff --git a/copylot/hardware/asi_stage/stage.py b/copylot/hardware/asi_stage/stage.py index 91e6407b..92d33066 100644 --- a/copylot/hardware/asi_stage/stage.py +++ b/copylot/hardware/asi_stage/stage.py @@ -1,8 +1,8 @@ import serial -from enum import Enum +from enum import IntEnum -class ASIStageScanMode(Enum): +class ASIStageScanMode(IntEnum): """ 0 for raster, 1 for serpentine """ @@ -37,17 +37,17 @@ def __del__(self): def set_speed(self, speed): message = f"speed x={speed}\r" print("set speed to scan: " + message) - self.ser.write(message) + self.ser.write(message.encode()) def set_default_speed(self): message = "speed x=10 y=10\r" print("set speed to scan: " + message) - self.ser.write(message) + self.ser.write(message.encode()) def set_backlash(self): message = "backlash x=0.04 y=0.0\r" print("set backlash: " + message) - self.ser.write(message) + self.ser.write(message.encode()) def set_scan_mode(self, mode: ASIStageScanMode = ASIStageScanMode.RASTER): """ @@ -58,9 +58,9 @@ def set_scan_mode(self, mode: ASIStageScanMode = ASIStageScanMode.RASTER): mode : ASIStageScanMode """ - message = f"scan f={mode}\r" + message = f"scan f={int(mode)}\r" print(message) - self.ser.write(message) + self.ser.write(message.encode()) def zero(self): """ @@ -68,19 +68,19 @@ def zero(self): """ message = f"zero\r" print(message) - self.ser.write(message) + self.ser.write(message.encode()) def start_scan(self): message = "scan" print(message) - self.ser.write(message) + self.ser.write(message.encode()) def scanr(self, x=0, y=0): message = f"scanr x={x} y={y}" print(message) - self.ser.write(message) + self.ser.write(message.encode()) def scanv(self, x=0, y=0, f=1.0): message = f"scanv x={x} y={y} f={f}" print(message) - self.ser.write(message) + self.ser.write(message.encode()) diff --git a/copylot/hardware/hamamatsu_camera/camera.py b/copylot/hardware/hamamatsu_camera/camera.py index b51f93d5..b26efb00 100644 --- a/copylot/hardware/hamamatsu_camera/camera.py +++ b/copylot/hardware/hamamatsu_camera/camera.py @@ -1,15 +1,17 @@ from copylot.hardware.hamamatsu_camera.dcam import Dcamapi, Dcam - - -class CameraException(Exception): - pass +from napari._qt.qthreading import thread_worker class Camera: def __init__(self, camera_index: int = 0): self._camera_index = camera_index - def run(self, nb_frame: int = 100000): + def run(self, visualization_napari_layer): + worker = self.threaded_run(visualization_napari_layer) + worker.start() + + @thread_worker + def threaded_run(self, visualization_napari_layer, nb_frame: int = 100000): """ Method to run the camera. It handles camera initializations and uninitializations as well as camera buffer allocation/deallocation. @@ -32,6 +34,8 @@ def run(self, nb_frame: int = 100000): for _ in range(nb_frame): if dcam.wait_capevent_frameready(timeout_milisec): data = dcam.buf_getlastframedata() # Data is here + visualization_napari_layer.data = data + print("data is here") else: dcamerr = dcam.lasterr() if dcamerr.is_timeout(): @@ -44,23 +48,17 @@ def run(self, nb_frame: int = 100000): dcam.cap_stop() else: - raise CameraException( - f"dcam.cap_start() fails with error {dcam.lasterr()}" - ) + print(f"dcam.cap_start() fails with error {dcam.lasterr()}") dcam.buf_release() # release buffer else: - raise CameraException( + print( f"dcam.buf_alloc({nb_buffer_frames}) fails with error {dcam.lasterr()}" ) dcam.dev_close() else: - raise CameraException( - f"dcam.dev_open() fails with error {dcam.lasterr()}" - ) + print(f"dcam.dev_open() fails with error {dcam.lasterr()}") else: - raise CameraException( - f"Dcamapi.init() fails with error {Dcamapi.lasterr()}" - ) + print(f"Dcamapi.init() fails with error {Dcamapi.lasterr()}") Dcamapi.uninit() diff --git a/copylot/hardware/hamamatsu_camera/dcam.py b/copylot/hardware/hamamatsu_camera/dcam.py index c884e7a8..31a0e75d 100644 --- a/copylot/hardware/hamamatsu_camera/dcam.py +++ b/copylot/hardware/hamamatsu_camera/dcam.py @@ -4,7 +4,7 @@ # # The declarations of classes and functions in this file are subject to change without notice. -from dcamapi4 import * +from .dcamapi4 import * import numpy as np diff --git a/copylot/hardware/ni_daq/control.py b/copylot/hardware/ni_daq/control.py index fe099da1..b3343f9a 100644 --- a/copylot/hardware/ni_daq/control.py +++ b/copylot/hardware/ni_daq/control.py @@ -22,10 +22,10 @@ def set_ao_value(ch, value): class NIDaq: # Channel information - ch_ao0 = "cDAQ1AO/ao0" # scanning channel - ch_ao1 = "cDAQ1AO/ao1" # view switching - ch_ao2 = "cDAQ1AO/ao2" # view switching - ch_ao3 = "cDAQ1AO/ao3" # gamma angle, stripe reduction + ch_ao0 = "cDAQ1AO/ao0" # scanning channel + ch_ao1 = "cDAQ1AO/ao1" # view switching + ch_ao2 = "cDAQ1AO/ao2" # view switching + ch_ao3 = "cDAQ1AO/ao3" # gamma angle, stripe reduction ch_ao4 = "cDAQ1AO2/ao0" # beta angle, light sheet incident angle ch_ao5 = "cDAQ1AO2/ao1" # O1 PIFOC control ch_ao6 = "cDAQ1AO2/ao2" # O3 PIFOC control @@ -61,12 +61,8 @@ class NIDaq: CONVERT_RATIO_SCAN_GALVO = ( 159 # unit: um / v, to convert from voltage to the scan distance of the galvo ) - CONVERT_RATIO_PIFOC_O1 = ( - 40 # unit: um / v, to convert from voltage to the scan distance of the PIFOC O1 [-400, 400] - ) - CONVERT_RATIO_PIFOC_O3 = ( - 10 # unit: um / v, to convert from voltage to the scan distance of the PIFOC O3 [0, 100] - ) + CONVERT_RATIO_PIFOC_O1 = 40 # unit: um / v, to convert from voltage to the scan distance of the PIFOC O1 [-400, 400] + CONVERT_RATIO_PIFOC_O3 = 10 # unit: um / v, to convert from voltage to the scan distance of the PIFOC O3 [0, 100] READOUT_TIME_FULL_CHIP = ( 0.01 # unit: second, the readout time of the full chip camera ) @@ -95,7 +91,7 @@ def __init__( stripe_reduction_offset: float = 0.58, # unit: v, to apply on glavo gamma to reduce stripe o1_pifoc=0, # unit: um, to apply on O1 PIFOC, [-400, 400] um. light_sheet_angle=-2.2, # unit: v, to apply on glavo beta to adjust light sheet angle - laser_power=100 # unit: percentage [0, 100], for laser analog control + laser_power=100, # unit: percentage [0, 100], for laser analog control ): """ Constructor of NIDaq. @@ -148,9 +144,7 @@ def __init__( @property def nb_slices(self): - return int( - self.scan_range / self.scan_step - ) + return int(self.scan_range / self.scan_step) @property def sampling_rate(self): @@ -170,7 +164,9 @@ def _get_ao_data(self, view: str, scan_option="Stage"): data_ao4 = [self.light_sheet_angle] * self.num_samples # AO7, for laser analog control - data_ao7 = [self.laser_power_percent / 100 * self.MAX_LASER_ANALOG] * self.num_samples + data_ao7 = [ + self.laser_power_percent / 100 * self.MAX_LASER_ANALOG + ] * self.num_samples # AO1, AO2 and AO6, for view switching and light sheet stabilization if view == "view1": @@ -198,25 +194,32 @@ def _get_ao_data(self, view: str, scan_option="Stage"): np.linspace(max_range + offset, min_range + offset, self.num_samples) ) data_ao0_off = list( - np.linspace(data_ao0_on[nb_on_sample], max_range + offset, nb_off_sample) + np.linspace( + data_ao0_on[nb_on_sample], max_range + offset, nb_off_sample + ) ) data_ao0 = data_ao0_on[:nb_on_sample] + data_ao0_off # AO5, for fixed O1 position data_ao5 = [self.o1_pifoc / self.CONVERT_RATIO_PIFOC_O1] * self.num_samples - return [data_ao0, data_ao1, data_ao2, data_ao3, data_ao4, data_ao5, data_ao6, data_ao7] + return [ + data_ao0, + data_ao1, + data_ao2, + data_ao3, + data_ao4, + data_ao5, + data_ao6, + data_ao7, + ] def _get_do_data(self, nr_channels, interleave=False, current_ch=0): """ Method to get digital output data. """ - nb_on_sample = round( - (self.exposure - self.readout_time) * self.sampling_rate - ) - data_on = [True] * nb_on_sample + [False] * ( - self.num_samples - nb_on_sample - ) + nb_on_sample = round((self.exposure - self.readout_time) * self.sampling_rate) + data_on = [True] * nb_on_sample + [False] * (self.num_samples - nb_on_sample) data_off = [False] * self.num_samples if nr_channels == 1: @@ -226,7 +229,9 @@ def _get_do_data(self, nr_channels, interleave=False, current_ch=0): # only generate data once if interleaved channel acquisition if interleave: for i in range(nr_channels): - data.append(data_off * i + data_on + data_off * (nr_channels - i - 1)) + data.append( + data_off * i + data_on + data_off * (nr_channels - i - 1) + ) # generate data for each channel for sequential channel acquisition else: for ch in range(nr_channels): @@ -269,8 +274,15 @@ def _crate_do_task_for_acquisition(self, channels): raise ValueError("Channel not supported") return task_do - def acquire_stacks(self, channels, view, scan_option='Stage', interleave=True, - low_power=10, high_power=100): + def acquire_stacks( + self, + channels, + view, + scan_option='Stage', + interleave=True, + low_power=10, + high_power=100, + ): """acquire stackes, depending on the given channel and view. view=1, first view only; view=2, second view only; @@ -304,25 +316,38 @@ def acquire_stacks(self, channels, view, scan_option='Stage', interleave=True, # if scan_option == 'Interleave_denoising': # fn_acquire(task_ao, task_do, nr_channels, ['view1'], scan_option, interleave, low_power, high_power) # else: - fn_acquire(task_ao, task_do, nr_channels, ['view1'], scan_option, interleave) + fn_acquire( + task_ao, task_do, nr_channels, ['view1'], scan_option, interleave + ) elif view == 2: # if scan_option == 'Interleave_denoising': # fn_acquire(task_ao, task_do, nr_channels, ['view2'], scan_option, interleave, low_power, high_power) # else: - fn_acquire(task_ao, task_do, nr_channels, ['view2'], scan_option, interleave) + fn_acquire( + task_ao, task_do, nr_channels, ['view2'], scan_option, interleave + ) elif view == 3: # if scan_option == 'Interleave_denoising': # fn_acquire(task_ao, task_do, nr_channels, ['view1', 'view2'], scan_option, interleave, # low_power, high_power) # else: - fn_acquire(task_ao, task_do, nr_channels, ['view1', 'view2'], scan_option, interleave) + fn_acquire( + task_ao, + task_do, + nr_channels, + ['view1', 'view2'], + scan_option, + interleave, + ) else: raise ValueError("View not supported") task_ao.close() task_do.close() - def _acquire_stacks(self, task_ao, task_do, nr_channels, views, scan_option, interleave): + def _acquire_stacks( + self, task_ao, task_do, nr_channels, views, scan_option, interleave + ): """set up the workflow to acquire multiple stacks""" data_ao = [ @@ -376,9 +401,7 @@ def _acquire_stacks(self, task_ao, task_do, nr_channels, views, scan_option, int task_ao.write(data_ao[v]) task_ao.start() print("number of slices to acquire:", self.nb_slices) - for ch in range( - nr_channels - ): + for ch in range(nr_channels): # regenerate do date for non-interleaved multichannel acquisition if not interleave and nr_channels > 1: data_do = self._get_do_data( @@ -439,7 +462,9 @@ def _get_ao_data_galvo(self, view: str, scan_option="O1"): # AO0, for fixed scan galvo position data_ao0 = [offset] * nb_samples # AO5, for O1 scanning - min_range = (self.o1_pifoc - self.scan_range / 2) / self.CONVERT_RATIO_PIFOC_O1 + min_range = ( + self.o1_pifoc - self.scan_range / 2 + ) / self.CONVERT_RATIO_PIFOC_O1 step = self.scan_step / self.CONVERT_RATIO_PIFOC_O1 data_ao5 = [x * step + min_range for x in range(nb_samples)] elif scan_option == "Galvo": @@ -452,7 +477,9 @@ def _get_ao_data_galvo(self, view: str, scan_option="O1"): return [data_ao0, data_ao1, data_ao2, data_ao3, data_ao4, data_ao5, data_ao6] - def _acquire_stacks_galvo(self, task_ao, task_do, channels, views, scan_option, interleave): + def _acquire_stacks_galvo( + self, task_ao, task_do, channels, views, scan_option, interleave + ): """set up the workflow to acquire multiple stacks todo, support multichanel imaging""" data_ao = [ @@ -493,7 +520,7 @@ def _acquire_stacks_galvo(self, task_ao, task_do, channels, views, scan_option, samps_per_chan=nb_samples, source=self.PFI1, sample_mode=nidaqmx.constants.AcquisitionType.CONTINUOUS, - active_edge=nidaqmx.constants.Slope.FALLING + active_edge=nidaqmx.constants.Slope.FALLING, ) task_ao.triggers.start_trigger.cfg_dig_edge_start_trig( @@ -538,8 +565,17 @@ def _acquire_stacks_galvo(self, task_ao, task_do, channels, views, scan_option, task_ctr_retrig.close() task_ctr_loop.close() - def _acquire_stacks_interleave_denoising(self, task_ao, task_do, nr_channels, views, scan_option, interleave, - low_power, high_power): + def _acquire_stacks_interleave_denoising( + self, + task_ao, + task_do, + nr_channels, + views, + scan_option, + interleave, + low_power, + high_power, + ): """set up the workflow to acquire multiple stacks, for interleaved denoisng purpose only""" data_ao = [ @@ -593,9 +629,7 @@ def _acquire_stacks_interleave_denoising(self, task_ao, task_do, nr_channels, vi task_ao.write(data_ao[v]) task_ao.start() print("number of slices to acquire:", self.nb_slices) - for ch in range( - nr_channels - ): + for ch in range(nr_channels): # regenerate do date for non-interleaved multichannel acquisition if not interleave and nr_channels > 1: data_do = self._get_do_data( @@ -626,7 +660,9 @@ def _acquire_stacks_interleave_denoising(self, task_ao, task_do, nr_channels, vi task_ctr_retrig.close() task_ctr_loop.close() - def _set_initial_ao_states(self, scan_galvo, galvo1, galvo2, angle_galvo, o1, o3, laser_power): + def _set_initial_ao_states( + self, scan_galvo, galvo1, galvo2, angle_galvo, o1, o3, laser_power + ): """ select the initial states for the AO channels, by using the correct offset of the scanning gavlo and the voltages of the switching galvos @@ -647,23 +683,25 @@ def select_view(self, view): """select one view in live mode Note: ao3 is not included here, it's with the select_channel function""" if view == 1: - self._set_initial_ao_states(self._offset_dis_to_vol(self.offset_view1), - self.view1_galvo1, - self.view1_galvo2, - self.light_sheet_angle, - self.o1_pifoc / self.CONVERT_RATIO_PIFOC_O1, - self.o3_view1 / self.CONVERT_RATIO_PIFOC_O3, - self.laser_power_percent / 100 * self.MAX_LASER_ANALOG - ) + self._set_initial_ao_states( + self._offset_dis_to_vol(self.offset_view1), + self.view1_galvo1, + self.view1_galvo2, + self.light_sheet_angle, + self.o1_pifoc / self.CONVERT_RATIO_PIFOC_O1, + self.o3_view1 / self.CONVERT_RATIO_PIFOC_O3, + self.laser_power_percent / 100 * self.MAX_LASER_ANALOG, + ) elif view == 2: - self._set_initial_ao_states(self._offset_dis_to_vol(self.offset_view2), - self.view2_galvo1, - self.view2_galvo2, - self.light_sheet_angle, - self.o1_pifoc / self.CONVERT_RATIO_PIFOC_O1, - self.o3_view2 / self.CONVERT_RATIO_PIFOC_O3, - self.laser_power_percent / 100 * self.MAX_LASER_ANALOG - ) + self._set_initial_ao_states( + self._offset_dis_to_vol(self.offset_view2), + self.view2_galvo1, + self.view2_galvo2, + self.light_sheet_angle, + self.o1_pifoc / self.CONVERT_RATIO_PIFOC_O1, + self.o3_view2 / self.CONVERT_RATIO_PIFOC_O3, + self.laser_power_percent / 100 * self.MAX_LASER_ANALOG, + ) else: raise ValueError("View not supported") @@ -681,23 +719,25 @@ def set_initial_states(self, scan_option, view): o3 = self.o3_view2 / self.CONVERT_RATIO_PIFOC_O3 if scan_option == 'O1': - self._set_initial_ao_states(self._offset_dis_to_vol(offset), - galvo1, - galvo2, - self.light_sheet_angle, - (self.o1_pifoc - self.scan_range / 2) / self.CONVERT_RATIO_PIFOC_O1, - o3, - self.laser_power_percent / 100 * self.MAX_LASER_ANALOG - ) + self._set_initial_ao_states( + self._offset_dis_to_vol(offset), + galvo1, + galvo2, + self.light_sheet_angle, + (self.o1_pifoc - self.scan_range / 2) / self.CONVERT_RATIO_PIFOC_O1, + o3, + self.laser_power_percent / 100 * self.MAX_LASER_ANALOG, + ) elif scan_option == 'Galvo': - self._set_initial_ao_states(self._offset_dis_to_vol(offset - self.scan_range / 2), - galvo1, - galvo2, - self.light_sheet_angle, - self.o1_pifoc / self.CONVERT_RATIO_PIFOC_O1, - o3, - self.laser_power_percent / 100 * self.MAX_LASER_ANALOG - ) + self._set_initial_ao_states( + self._offset_dis_to_vol(offset - self.scan_range / 2), + galvo1, + galvo2, + self.light_sheet_angle, + self.o1_pifoc / self.CONVERT_RATIO_PIFOC_O1, + o3, + self.laser_power_percent / 100 * self.MAX_LASER_ANALOG, + ) else: raise ValueError("Scanning mode not supported") @@ -802,7 +842,7 @@ def _retriggable_task(self, task_do, data_do, task_ao, data_ao, t=None): if __name__ == "__main__": daq_card = NIDaq( exposure=0.100, - nb_timepoints=2000, # number of timepoints + nb_timepoints=1, # number of timepoints scan_step=0.155 * 5, # TTL100, 0.310, TTL300 0.103, TTL200 0.155 0.155 * 2 scan_range=250, # starts from current position vertical_pixels=2048, # used to calculate the readout time @@ -817,7 +857,7 @@ def _retriggable_task(self, task_do, data_do, task_ao, data_ao, t=None): stripe_reduction_range=0.5, # 2-axes galvo, range of gamma control stripe_reduction_offset=-0.9, # 2-axes galvo, offset of gamma control o1_pifoc=0, # position of O1 pifoc, range [-400, 400] um - light_sheet_angle=-2.72 # 2-axes galvo, beta control of light sheet angle, -0.22 -> epi + light_sheet_angle=-2.72, # 2-axes galvo, beta control of light sheet angle, -0.22 -> epi ) """Methods are separated into live mode and aacquisition mode""" @@ -834,4 +874,6 @@ def _retriggable_task(self, task_do, data_do, task_ao, data_ao, t=None): # channel = ['405'], ['488'], ['561'], ['639'], ['bf'] or anY combination; # scanning mode: 'Stage', 'Gavlo', 'O1' - daq_card.acquire_stacks(channels=['488', '561'], view=3, scan_option="Stage", interleave=False) + daq_card.acquire_stacks( + channels=['488'], view=3, scan_option="Stage", interleave=False + ) diff --git a/copylot/hardware/trial_timelapse.py b/copylot/hardware/trial_timelapse.py index c776538d..3f111a8c 100644 --- a/copylot/hardware/trial_timelapse.py +++ b/copylot/hardware/trial_timelapse.py @@ -3,16 +3,19 @@ nidaq script. This script tries to mimic the behavior given in the stageScan_multiPos.bsh script. """ +import time from os.path import join +import tensorstore as ts from copylot.hardware.asi_stage.stage import ASIStage, ASIStageScanMode +from copylot.hardware.hamamatsu_camera.dcam import Dcamapi, Dcam, DCAM_IDPROP, DCAMPROP path = "D:/data/20210506_xiang" path_prefix = "stage_TL100_range500um_step0.31_4um_30ms_view2_interval_2s_488_20mW_561_10mW_1800tp_3pos" -nb_channel = 2 +nb_channel = 1 nb_view = 2 -channels = ["488", "561"] +channels = ["488"] interleave = False nb_frames = 1 # number of timepoint @@ -39,7 +42,7 @@ galvo_offset_in_um = 0 offset = alignment_offset_in_um + galvo_offset_in_um + custom_offset_in_um -nb_slices = range_in_um / step_size_um +nb_slices = int(range_in_um // step_size_um) # reset the scanning range for multi channel imaging if nb_slices % nb_channel != 0 and interleave: nb_slices -= nb_slices % nb_channel @@ -62,37 +65,134 @@ axis_order = ["z", "channel", "time", "position"] -asi_stage.set_scan_mode(scan_mode) -asi_stage.set_backlash() -asi_stage.set_speed(speed=speed) -asi_stage.zero() +def main(): + dataset = ts.open( + { + "driver": "zarr", + 'kvstore': { + 'driver': 'file', + 'path': r'C:\Users\PiscesScope\Documents\acs\coPylot\test_zarr', + }, + "key_encoding": ".", + "metadata": { + "shape": [nb_slices, nb_view, nb_frames, 2048, 2048], + "chunks": [128, 1, 128, 128, 128], + "dtype": " fps_calculation_interval: + print("FPS: ", counter / (time.time() - start_time)) + counter = 0 + start_time = time.time() + + for f in range(nb_frames): + for view in range(nb_view): + for slice in range(nb_slices): + write_futures[ + f * (nb_slices * nb_view) + view * nb_slices + slice + ].result() + + # Set camera for internal trigger and validate + dcam.prop_setvalue( + DCAM_IDPROP.TRIGGERSOURCE, DCAMPROP.TRIGGERSOURCE.INTERNAL + ) + + else: + print( + '-NG: Dcam.buf_alloc(3) fails with error {}'.format(dcam.lasterr()) + ) + dcam.dev_close() -# Set camera for external trigger and validate - -# Set camera trigger delay and validate - - -# Acquisition loop -for f in range(nb_frames): - for view in range(nb_view): - - if view == 0: - asi_stage.scanr(x=0, y=range_in_um / 1000) - asi_stage.scanv(x=0, y=0, f=1.0) else: - asi_stage.scanr(x=-offset / 1000, y=(-offset + range_in_um) / 1000) - asi_stage.scanv(x=0, y=0, f=1.0) - print(f"scan range in um: {range_in_um}") - - if interleave: - print(f"start interleaved acquisition: {interleave}") - asi_stage.start_scan() + print('-NG: Dcam.dev_open() fails with error {}'.format(dcam.lasterr())) - slice = 0 + else: + print(f"Dcamapi.init() fails with error {Dcamapi.lasterr()}") + Dcamapi.uninit() -# Set camera for internal trigger and validate + asi_stage.set_default_speed() -asi_stage.set_default_speed() +if __name__ == '__main__': + main() From 537b46bd8caa2715015cca66573afb8b8b35eb85 Mon Sep 17 00:00:00 2001 From: AhmetCanSolak Date: Thu, 7 Apr 2022 16:52:10 -0700 Subject: [PATCH 4/7] .gitignore update --- .gitignore | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.gitignore b/.gitignore index 264cc19d..d72bdfcb 100644 --- a/.gitignore +++ b/.gitignore @@ -133,3 +133,5 @@ ToDo.txt defaults.txt copylot/_version.py + +test_zarr From 4fd807f166a26af9691dc91f062a14579f51a347 Mon Sep 17 00:00:00 2001 From: AhmetCanSolak Date: Tue, 19 Apr 2022 09:20:31 -0700 Subject: [PATCH 5/7] stage debug script implemented --- copylot/hardware/live_asi_stage_debug.py | 35 ++++++++++++++++++++++++ 1 file changed, 35 insertions(+) create mode 100644 copylot/hardware/live_asi_stage_debug.py diff --git a/copylot/hardware/live_asi_stage_debug.py b/copylot/hardware/live_asi_stage_debug.py new file mode 100644 index 00000000..aed8f68e --- /dev/null +++ b/copylot/hardware/live_asi_stage_debug.py @@ -0,0 +1,35 @@ +from copylot.hardware.asi_stage.stage import ASIStage, ASIStageScanMode + + +# Define constants +scan_mode = ASIStageScanMode.RASTER +speed = 0.155 * 5 / 10 +range_in_um = 250 +offset = 300 +port = "COM6" + +# Create ASIStage instance +asi_stage = ASIStage(com_port=port) + +# Initialize the stage and zero it +asi_stage.set_scan_mode(scan_mode) +asi_stage.set_backlash() +asi_stage.set_speed(speed=speed) +asi_stage.zero() + +view = 0 + +# Set scan range +if view == 0: + asi_stage.scanr(x=0, y=range_in_um / 1000) + asi_stage.scanv(x=0, y=0, f=1.0) +else: + asi_stage.scanr( + x=-offset / 1000, y=(-offset + range_in_um) / 1000 + ) + asi_stage.scanv(x=0, y=0, f=1.0) + +asi_stage.start_scan() + +asi_stage.set_default_speed() + From 38e0f18fd091722e9465c6f5239cd81a2f55dff3 Mon Sep 17 00:00:00 2001 From: AhmetCanSolak Date: Tue, 19 Apr 2022 11:10:29 -0700 Subject: [PATCH 6/7] input trigger settings carried over from clearcontrol --- copylot/hardware/live_asi_stage_debug.py | 26 ++++++++++--------- copylot/hardware/trial_timelapse.py | 33 ++++++++++++++++++++---- 2 files changed, 42 insertions(+), 17 deletions(-) diff --git a/copylot/hardware/live_asi_stage_debug.py b/copylot/hardware/live_asi_stage_debug.py index aed8f68e..da9c0cfd 100644 --- a/copylot/hardware/live_asi_stage_debug.py +++ b/copylot/hardware/live_asi_stage_debug.py @@ -3,7 +3,7 @@ # Define constants scan_mode = ASIStageScanMode.RASTER -speed = 0.155 * 5 / 10 +speed = 0.155 / 2 range_in_um = 250 offset = 300 port = "COM6" @@ -19,17 +19,19 @@ view = 0 -# Set scan range -if view == 0: - asi_stage.scanr(x=0, y=range_in_um / 1000) - asi_stage.scanv(x=0, y=0, f=1.0) -else: - asi_stage.scanr( - x=-offset / 1000, y=(-offset + range_in_um) / 1000 - ) - asi_stage.scanv(x=0, y=0, f=1.0) - -asi_stage.start_scan() + +for _ in range(300): + # Set scan range + if view == 0: + asi_stage.scanr(x=0, y=range_in_um / 1000) + asi_stage.scanv(x=0, y=0, f=1.0) + else: + asi_stage.scanr( + x=-offset / 1000, y=(-offset + range_in_um) / 1000 + ) + asi_stage.scanv(x=0, y=0, f=1.0) + + asi_stage.start_scan() asi_stage.set_default_speed() diff --git a/copylot/hardware/trial_timelapse.py b/copylot/hardware/trial_timelapse.py index 3f111a8c..42278bff 100644 --- a/copylot/hardware/trial_timelapse.py +++ b/copylot/hardware/trial_timelapse.py @@ -20,7 +20,7 @@ nb_frames = 1 # number of timepoint step_size_um = 0.155 * 5 -range_in_um = 250 +range_in_um = 5 interval_timepoint_in_seconds = 0 interval_in_seconds = 2 @@ -104,9 +104,29 @@ def main(): if dcam.dev_open(): # Set camera for external trigger and validate + dcam.prop_setvalue( + DCAM_IDPROP.TRIGGER_MODE, DCAMPROP.TRIGGER_MODE.NORMAL + ) + dcam.prop_setvalue( + DCAM_IDPROP.TRIGGERPOLARITY, DCAMPROP.TRIGGERPOLARITY.POSITIVE + ) + dcam.prop_setvalue( + DCAM_IDPROP.TRIGGER_CONNECTOR, DCAMPROP.TRIGGER_CONNECTOR.BNC + ) + dcam.prop_setvalue( + DCAM_IDPROP.TRIGGERTIMES, 1 + ) + dcam.prop_setvalue( + DCAM_IDPROP.TRIGGERDELAY, 0 + ) + + dcam.prop_setvalue( DCAM_IDPROP.TRIGGERSOURCE, DCAMPROP.TRIGGERSOURCE.EXTERNAL ) + dcam.prop_setvalue( + DCAM_IDPROP.TRIGGERACTIVE, DCAMPROP.TRIGGERACTIVE.SYNCREADOUT + ) # Set camera trigger delay and validate dcam.prop_setvalue(DCAM_IDPROP.TRIGGERDELAY, 0.0) @@ -130,24 +150,24 @@ def main(): # if interleave: print(f"start interleaved acquisition: {interleave}") - asi_stage.start_scan() counter = 0 slice = 0 - timeout_milisec = 100 + timeout_milisec = 1000 fps_calculation_interval = 1 while slice < nb_slices: + asi_stage.start_scan() + if dcam.wait_capevent_frameready(timeout_milisec): data = dcam.buf_getlastframedata() + print(data.shape) # Async write write_futures[ f * (nb_slices * nb_view) + view * nb_slices + slice ] = dataset[slice, view, f, :, :].write(data) - slice += 1 - counter += 1 else: dcamerr = dcam.lasterr() if dcamerr.is_timeout(): @@ -160,6 +180,9 @@ def main(): ) break + slice += 1 + counter += 1 + if (time.time() - start_time) > fps_calculation_interval: print("FPS: ", counter / (time.time() - start_time)) counter = 0 From 5c4f940592462067777f7c95e26d78541182e6dd Mon Sep 17 00:00:00 2001 From: AhmetCanSolak Date: Tue, 19 Apr 2022 16:23:45 -0700 Subject: [PATCH 7/7] capture start added, now camera takes the input trigger and gets frames --- copylot/hardware/trial_timelapse.py | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/copylot/hardware/trial_timelapse.py b/copylot/hardware/trial_timelapse.py index 42278bff..452182ce 100644 --- a/copylot/hardware/trial_timelapse.py +++ b/copylot/hardware/trial_timelapse.py @@ -158,15 +158,16 @@ def main(): while slice < nb_slices: asi_stage.start_scan() + dcam.cap_start() if dcam.wait_capevent_frameready(timeout_milisec): data = dcam.buf_getlastframedata() print(data.shape) # Async write - write_futures[ - f * (nb_slices * nb_view) + view * nb_slices + slice - ] = dataset[slice, view, f, :, :].write(data) + # write_futures[ + # f * (nb_slices * nb_view) + view * nb_slices + slice + # ] = dataset[slice, view, f, :, :].write(data) else: dcamerr = dcam.lasterr() @@ -180,6 +181,9 @@ def main(): ) break + print(dcam.cap_status()) + print(dcam.cap_transferinfo().nNewestFrameIndex, dcam.cap_transferinfo().nFrameCount) + slice += 1 counter += 1