diff --git a/DCAMCamera/blacs_tabs.py b/DCAMCamera/blacs_tabs.py index b6acf27e7c61683a70dd924f33f27ad71663444d..b55abdf431c570e2e71c8c4bd72095988612b870 100644 --- a/DCAMCamera/blacs_tabs.py +++ b/DCAMCamera/blacs_tabs.py @@ -3,11 +3,14 @@ # /user_devices/DCAMCamera/blacs_tabs.py # # # # Jan 2023, Marvin Holten # +# 2025, Johannes Schabbauer # # # # # ##################################################################### from labscript_devices.IMAQdxCamera.blacs_tabs import IMAQdxCameraTab +import labscript_utils.h5_lock +import h5py class DCAMCameraTab(IMAQdxCameraTab): """Thin sub-class of obj:`IMAQdxCameraTab`. @@ -16,4 +19,30 @@ class DCAMCameraTab(IMAQdxCameraTab): :obj:`DCAMCameraWorker`.""" # override worker class - worker_class = 'user_devices.DCAMCamera.blacs_workers.DCAMCameraWorker' \ No newline at end of file + worker_class = 'user_devices.DCAMCamera.blacs_workers.DCAMCameraWorker' + + def initialise_workers(self): + table = self.settings['connection_table'] + connection_table_properties = table.find_by_name(self.device_name).properties + # The device properties can vary on a shot-by-shot basis, but at startup we will + # initially set the values that are configured in the connection table, so they + # can be used for manual mode acquisition: + with h5py.File(table.filepath, 'r') as f: + device_properties = labscript_utils.properties.get( + f, self.device_name, "device_properties" + ) + worker_initialisation_kwargs = { + 'serial_number': connection_table_properties['serial_number'], + 'orientation': connection_table_properties['orientation'], + 'camera_attributes': device_properties['camera_attributes'], + 'manual_mode_camera_attributes': connection_table_properties[ + 'manual_mode_camera_attributes' + ], + 'mock': connection_table_properties['mock'], + 'image_receiver_port': self.image_receiver.port, + 'occupation_receiver_port' : connection_table_properties['occupation_receiver_port'] + } + self.create_worker( + 'main_worker', self.worker_class, worker_initialisation_kwargs + ) + self.primary_worker = "main_worker" \ No newline at end of file diff --git a/DCAMCamera/blacs_workers.py b/DCAMCamera/blacs_workers.py index 03bacb39cf5631a3dcd876a617cbccdc599cf8ea..a98ac1371ff95b4956bf54e43281b4fe1ed8d92e 100644 --- a/DCAMCamera/blacs_workers.py +++ b/DCAMCamera/blacs_workers.py @@ -3,11 +3,20 @@ # /user_devices/DCAMCamera/blacs_workers.py # # # # Jan 2023, Marvin Holten # +# 2025, Johannes Schabbauer # # # # # ##################################################################### from labscript_devices.IMAQdxCamera.blacs_workers import IMAQdxCameraWorker +import threading +import numpy as np +import labscript_utils.h5_lock +import h5py +import labscript_utils.properties +import zmq +from labscript_utils.ls_zprocess import Context +from labscript_utils.shared_drive import path_to_local # Don't import API yet so as not to throw an error, allow worker to run as a dummy # device, or for subclasses to import this module to inherit classes without requiring API @@ -204,7 +213,7 @@ class DCAM_Camera(object): return image - def grab_multiple(self, n_images, images): + def grab_multiple(self, n_images, images, tweezer_socket=None, tweezer_img_no=None, get_occupation=None): """Grab n_images into images array during buffered acquistion. Grab method involves a continuous loop with fast timeout in order to @@ -222,6 +231,16 @@ class DCAM_Camera(object): self._abort_acquisition = False return images.append(self.grab(bufferNo=i)) + # Send image to occupation receiver + if tweezer_socket and tweezer_img_no==i: + occupation = get_occupation(images[-1]) + metadata = dict(dtype=str(occupation.dtype), shape=occupation.shape) + tweezer_socket.send_json(metadata, zmq.SNDMORE) + tweezer_socket.send(occupation, copy=False) + print(f"Trying to send image {len(images)} to occupation receiver...", end="\r") + response = tweezer_socket.recv() + assert response == b'ok', response + print(f"Sent image {len(images)} to occupation receiver.") print(f"Got image {i+1} of {n_images}.") print(f"Got {len(images)} of {n_images} images.") @@ -248,6 +267,16 @@ class DCAMCameraWorker(IMAQdxCameraWorker): :obj:`get_attributes_as_dict` to use DCAMCameraWorker.get_attributes() method.""" interface_class = DCAM_Camera + def init(self): + super().init() + + # Connect to occupation matrix receiver port for conditional Tweezer programming + if self.occupation_receiver_port is not None: + self.tweezer_socket = Context().socket(zmq.REQ) + self.tweezer_socket.connect( + f'tcp://{self.parent_host}:{self.occupation_receiver_port}' + ) + def get_attributes_as_dict(self, visibility_level): """Return a dict of the attributes of the camera for the given visibility level @@ -260,5 +289,91 @@ class DCAMCameraWorker(IMAQdxCameraWorker): return IMAQdxCameraWorker.get_attributes_as_dict(self,visibility_level) else: return self.camera.get_attributes(visibility_level) + + def transition_to_buffered(self, device_name, h5_filepath, initial_values, fresh): + if getattr(self, 'is_remote', False): + h5_filepath = path_to_local(h5_filepath) + if self.continuous_thread is not None: + # Pause continuous acquistion during transition_to_buffered: + self.stop_continuous(pause=True) + with h5py.File(h5_filepath, 'r') as f: + group = f['devices'][self.device_name] + if not 'EXPOSURES' in group: + return {} + self.h5_filepath = h5_filepath + self.exposures = group['EXPOSURES'][:] + self.n_images = len(self.exposures) + + # Get the camera_attributes from the device_properties + properties = labscript_utils.properties.get( + f, self.device_name, 'device_properties' + ) + camera_attributes = properties['camera_attributes'] + self.stop_acquisition_timeout = properties['stop_acquisition_timeout'] + self.exception_on_failed_shot = properties['exception_on_failed_shot'] + saved_attr_level = properties['saved_attribute_visibility_level'] + self.camera.exception_on_failed_shot = self.exception_on_failed_shot + + ### ADDED CODE TO PASS OCCUPATION RECEIVER ARGUMENTS TO grab_multiple() ### + self.images = [] + if properties["occupation_receiver_image_index"] is not None: + tweezers_centers = f["globals"].attrs["tweezers_centers"] + thresholds = f["globals"].attrs["thresholds"] + ROI_size = f["globals"].attrs["ROI_size"] + get_occupation = lambda image: self.get_occupation(image,tweezers_centers,thresholds, ROI_size) + args = (self.n_images, self.images,self.tweezer_socket, properties["occupation_receiver_image_index"], get_occupation) + else: + # Standard args from IMAQdxCameraWorker + args = (self.n_images, self.images) + + + # Only reprogram attributes that differ from those last programmed in, or all of + # them if a fresh reprogramming was requested: + if fresh: + self.smart_cache = {} + self.set_attributes_smart(camera_attributes) + # Get the camera attributes, so that we can save them to the H5 file: + if saved_attr_level is not None: + self.attributes_to_save = self.get_attributes_as_dict(saved_attr_level) + else: + self.attributes_to_save = None + print(f"Configuring camera for {self.n_images} images.") + self.camera.configure_acquisition(continuous=False, bufferCount=self.n_images) + + + self.acquisition_thread = threading.Thread( + target=self.camera.grab_multiple, + args=args, + daemon=True, + ) + self.acquisition_thread.start() + return {} + + def get_occupation(self, image, tweezer_centers, thresholds, size_px=3): + """ + Calculate the Tweezer occupancy of Tweezers, either 0 or 1. + + Parameters + ---------- + image : ndarray + Monochrome picture. + tweezers_centers : ndarray + Indices where the Tweezers aer in the image + thresholds : float or ndarray + Threshold in px counts, that distinghishes no atom or 1 atom. + Same threashold for all Tweezers, if a single number. If ndarray, + the size has to be equal to the size of tweezers_centers. + size_px : int + ROI around each tweezer center, where the pixels get summed up. + """ + px_sum = np.zeros(tweezer_centers.shape[0]) + lower = size_px//2 + upper = size_px-lower + for i in range(tweezer_centers.shape[0]): + x,y = tweezer_centers[i,:] + px_sum[i] = image[x-lower:x+upper, y-lower:y+upper].sum() + + return px_sum > thresholds + diff --git a/DCAMCamera/labscript_devices.py b/DCAMCamera/labscript_devices.py index 79a9f2689a2964f2cbb447dd2c032d50eb1f07de..bd52783d4d09a8ddeba9995f12f141b0ae4ed119 100644 --- a/DCAMCamera/labscript_devices.py +++ b/DCAMCamera/labscript_devices.py @@ -3,10 +3,14 @@ # /user_devices/DCAMCamera/labscript_devices.py # # # # Jan 2023, Marvin Holten # +# 2025, Johannes Schabbauer # + # # # # ##################################################################### +import h5py +from labscript import set_passed_properties, LabscriptError from labscript_devices.IMAQdxCamera.labscript_devices import IMAQdxCamera class DCAMCamera(IMAQdxCamera): @@ -14,3 +18,29 @@ class DCAMCamera(IMAQdxCamera): description = 'DCAM Camera' + @set_passed_properties( + property_names={ + "connection_table_properties": [ + "occupation_receiver_port", + ], + } + ) + def __init__(self, name, parent_device, connection, serial_number, orientation=None, pixel_size=..., magnification=1, trigger_edge_type='rising', trigger_duration=None, minimum_recovery_time=0, camera_attributes=None, manual_mode_camera_attributes=None, stop_acquisition_timeout=5, exception_on_failed_shot=True, saved_attribute_visibility_level='intermediate', occupation_receiver_port=None, mock=False, **kwargs): + self.occupation_receiver_image_index = None + self.exposure_times = [] + super().__init__(name, parent_device, connection, serial_number, orientation, pixel_size, magnification, trigger_edge_type, trigger_duration, minimum_recovery_time, camera_attributes, manual_mode_camera_attributes, stop_acquisition_timeout, exception_on_failed_shot, saved_attribute_visibility_level, mock, **kwargs) + + def expose(self, t, name, frametype='frame', trigger_duration=None, send_image_to_occupation_receiver=False): + self.exposure_times.append(t) + if send_image_to_occupation_receiver: + if getattr(self,"occupation_receiver_time",None) is not None: + raise LabscriptError(f"{self.name}: In the current implementation only a single image can be sent to the 'occupation receiver'") + self.occupation_receiver_time = t + + return super().expose(t, name, frametype, trigger_duration) + + def generate_code(self, hdf5_file): + super().generate_code(hdf5_file) + if getattr(self,"occupation_receiver_time",None) is not None: + self.occupation_receiver_image_index = self.exposure_times.index(self.occupation_receiver_time) + hdf5_file[f"devices/{self.name}"].attrs["occupation_receiver_image_index"] = self.occupation_receiver_image_index