Source code for mesoSPIM.src.utils.acquisitions

'''
acquisitions.py
========================================

Helper classes for mesoSPIM acquisitions
'''

import indexed
import os.path
import logging
logger = logging.getLogger(__name__)


[docs] class Acquisition(indexed.IndexedOrderedDict): ''' Custom acquisition dictionary. Contains all the information to run a single acquisition. Args: x_pos (float): X start position in microns y_pos (float): Y start position in microns z_start (float): Z start position in microns z_end (float): Z end position in microns z_step (float): Z stepsize in microns , theta_pos (float): Rotation angle in microns f_pos (float): Focus position in microns laser (str): Laser designation intensity (int): Laser intensity in 0-100 filter (str): Filter designation (has to be in the config) zoom (str): Zoom designation filename (str): Filename for the file to be saved Attributes: Note: Getting keys: ``keys = [key for key in acq1.keys()]`` Example: Getting keys: ``keys = [key for key in acq1.keys()]`` Todo: Testtodo-Entry ''' def __init__(self, x_pos=0, y_pos=0, z_start=0, z_end=100, z_step=10, planes=10, theta_pos=0, f_start=0, f_end=0, laser='488 nm', intensity=0, filter='Empty', zoom='1x', shutterconfig='Left', folder='tmp', filename='one.tif', etl_l_offset=0, etl_l_amplitude=0, etl_r_offset=0, etl_r_amplitude=0, processing='MAX'): super().__init__() self['x_pos']=x_pos self['y_pos']=y_pos self['z_start']=z_start self['z_end']=z_end self['z_step']=z_step self['planes']=planes self['rot']=theta_pos self['f_start']=f_start self['f_end']=f_end self['laser']=laser self['intensity']=intensity self['filter']=filter self['zoom']=zoom self['shutterconfig']=shutterconfig self['folder']=folder self['filename']=filename self['etl_l_offset']=etl_l_offset self['etl_l_amplitude']=etl_l_amplitude self['etl_r_offset']=etl_r_offset self['etl_r_amplitude']=etl_r_amplitude self['processing']=processing def __setitem__(self, key, value): super().__setitem__(key, value) def __call__(self, index): ''' This way the dictionary is callable with an index ''' return self.values()[index]
[docs] def get_keylist(self): ''' A list keys is returned for usage as a table header ''' return [key for key in self.keys()]
[docs] def get_capitalized_keylist(self): ''' Here, a list of capitalized keys is returned for usage as a table header ''' return [key.capitalize() for key in self.keys()]
[docs] def get_image_count(self): ''' Method to return the number of planes in the acquisition ''' return abs(round((self['z_end'] - self['z_start'])/self['z_step'])) + 1
[docs] def get_acquisition_time(self, framerate): ''' Method to return the time the acquisition will take at a certain framerate. Args: float: framerate of the microscope Returns: float: Acquisition time in seconds ''' return self.get_image_count()/framerate
[docs] def get_delta_z_and_delta_f_dict(self, inverted=False): ''' Returns relative movement dict for z- and f-steps ''' if self['z_end'] > self['z_start']: z_rel = abs(self['z_step']) else: z_rel = -abs(self['z_step']) ''' Calculate f-step ''' image_count = self.get_image_count() if image_count >= 1: f_rel = abs((self['f_end'] - self['f_start'])/image_count) else: f_rel = 0 if self['f_end'] < self['f_start']: f_rel = -f_rel if not inverted: return {'x_rel' : 0, 'y_rel': 0, 'z_rel' : z_rel, 'f_rel' : f_rel, 'theta_rel': 0} else: return {'x_rel' : 0, 'y_rel': 0, 'z_rel' : -z_rel, 'f_rel' : -f_rel, 'theta_rel': 0}
[docs] def get_delta_dict(self): ''' Returns relative movement dict for z-steps and f-steps''' ''' Calculate z-step ''' if self['z_end'] > self['z_start']: z_rel = abs(self['z_step']) else: z_rel = -abs(self['z_step']) ''' Calculate f-step image_count = self.get_image_count() f_rel = abs((self['f_end'] - self['f_start'])/image_count) if self['f_end'] < self['f_start']: f_rel = -f_rel ''' return {'z_rel' : z_rel}
[docs] def get_startpoint(self): ''' Provides a dictionary with the startpoint coordinates ''' return {'x_abs': self['x_pos'], 'y_abs': self['y_pos'], 'z_abs': self['z_start'], 'theta_abs': self['rot'], 'f_abs': self['f_start'], }
[docs] def get_endpoint(self): return {'x_abs': self['x_pos'], 'y_abs': self['y_pos'], 'z_abs': self['z_end'], 'theta_abs': self['rot'], 'f_abs': self['f_end'], }
[docs] def get_focus_stepsize_generator(self, f_stage_min_step_um=0.25): '''' Provides a generator object to correct rounding errors for focus tracking acquisitions. The focus stage has to travel a shorter distance than the sample z-stage, ideally only a fraction of the z-step size. However, due to the limited minimum step size of the focus stage, rounding errors can accumulate over thousands of steps. Therefore, the generator tracks the rounding error and applies correcting steps here and there to minimize the error. This assumes a minimum step size of around 0.25 micron that the focus stage is capable of. This method contains lots of round functions to keep residual rounding errors at bay. ''' steps = self.get_image_count() f_step = abs((self['f_end'] - self['f_start'])/steps) logger.debug(f"Focus interpolation: f_start, f_end, f_step, steps: {self['f_start'], self['f_end'], f_step, steps}") feasible_f_step = max(f_stage_min_step_um * (f_step // f_stage_min_step_um), f_stage_min_step_um) # Round to nearest multiple of f_stage_min_step_um if self['f_end'] < self['f_start']: f_step = -f_step feasible_f_step = -feasible_f_step expected_focus = 0 focus = 0 for i in range(steps): focus_error = round(expected_focus - focus, 5) new_step = round(focus_error / f_stage_min_step_um) * f_stage_min_step_um + feasible_f_step # this can be zero, and it is correct yield new_step logger.debug(f"Relative focus: new_step, actual, expected, error: {new_step, focus, expected_focus, focus_error}, um") focus += new_step focus = round(focus, 5) expected_focus += f_step
[docs] class AcquisitionList(list): ''' Class for a list of acquisition objects Examples: "([acq1,acq2,acq3])" is due to the fact that list takes only a single argument acq_list = AcquisitionList([acq1,acq2,acq3]) acq_list.time() > 3600 acq_list.planes() > 18000 acq_list[2](2) >10 acq_list[2]['y_pos'] >10 acq_list[2]['y_pos'] = 34 ''' def __init__(self, *args): list.__init__(self, *args) ''' If no arguments are provided, create a default acquistion in the list ''' if len(args) == 0: ''' Use a default acquistion ''' self.append(Acquisition()) # ''' # In addition to the list of acquisition objects, the AcquisitionList also # contains a rotation point that is save to rotate the sample to the target # value. # ''' # self.rotation_point = {'x_abs' : None, 'y_abs' : None, 'z_abs' : None}
[docs] def get_capitalized_keylist(self): return self[0].get_capitalized_keylist()
[docs] def get_keylist(self): ''' Here, a list of capitalized keys is returned for usage as a table header ''' return self[0].get_keylist()
[docs] def get_acquisition_time(self, framerate): ''' Returns total time in seconds of a list of acquisitions ''' time = 0 for i in range(len(self)): time += self[i].get_acquisition_time(framerate) return time
[docs] def get_image_count(self): ''' Returns the total number of planes for a list of acquistions ''' image_count = 0 for i in range(len(self)): image_count += self[i].get_image_count() return image_count
[docs] def get_startpoint(self): return self[0].get_startpoint()
# def set_rotation_point(self, dict): # self.rotation_point = {'x_abs' : dict['x_abs'], 'y_abs' : dict['y_abs'], 'z_abs':dict['z_abs']} # def delete_rotation_point(self): # self.rotation_point = {'x_abs' : None, 'y_abs' : None, 'z_abs' : None} # def get_rotation_point_status(self): # ''' Returns True if an rotation point was set, otherwise False ''' # if self.rotation_point['x_abs'] == None : # return False # else: # return True # def get_rotation_point(self): # return self.rotation_point
[docs] def get_all_filenames(self): ''' Returns a list of all filenames ''' filename_list = [] for i in range(len(self)): filename = self[i]['folder']+'/'+self[i]['filename'] filename_list.append(filename) return filename_list
[docs] def check_for_existing_filenames(self): ''' Returns a list of existing filenames ''' filename_list = [] for i in range(len(self)): filename = self[i]['folder']+'/'+self[i]['filename'] file_exists = os.path.isfile(filename) if file_exists: filename_list.append(filename) return filename_list
[docs] def check_filename_extensions(self): '''Returns files that have no extension, so their format is undefined.''' filename_list = [] for i in range(len(self)): filename = self[i]['filename'] ext = os.path.splitext(filename)[1] if ext == '': filename_list.append(filename) return filename_list
[docs] def check_for_duplicated_filenames(self): ''' Returns a list of duplicated filenames ''' filenames = [] # Create a list of full file paths for i in range(len(self)): if self[i]['filename'][-3:] != '.h5': filename = self[i]['folder']+'/'+self[i]['filename'] filenames.append(filename) duplicates = self.get_duplicates_in_list(filenames) return duplicates
[docs] def check_for_nonexisting_folders(self): ''' Returns a list of nonexisting folders ''' nonexisting_folders = [] for i in range(len(self)): folder = self[i]['folder'] if not os.path.isdir(folder): nonexisting_folders.append(folder) return nonexisting_folders
[docs] def get_duplicates_in_list(self, in_list): duplicates = [] unique = set(in_list) for each in unique: count = in_list.count(each) if count > 1: duplicates.append(each) return duplicates
[docs] def get_n_shutter_configs(self): """Get the number of unique shutter configs (1 or 2)""" sconfig_list = [a['shutterconfig'] for a in self] sconfig_set = set(sconfig_list) return len(sconfig_set)
[docs] def get_n_angles(self): """Get the number of unique angles""" angle_list = [a['rot'] for a in self] angle_set = set(angle_list) return len(angle_set)
[docs] def get_n_lasers(self): """Get the number of unique laser lines""" laser_list = [a['laser'] for a in self] laser_set = set(laser_list) return len(laser_set)
[docs] def get_n_tiles(self): """Get the number of tiles as unique (x,y,z_start,rot) combinations""" tile_list = [] for a in self: tile_str = f"{a['x_pos']}{a['y_pos']}{a['z_start']}{a['rot']}" if not tile_str in tile_list: tile_list.append(tile_str) return len(tile_list)
[docs] def get_tile_index(self, acq): """Get the the tile index for given acquisition""" acq_str = f"{acq['x_pos']}{acq['y_pos']}{acq['z_start']}{acq['rot']}" tile_list = [] for a in self: tile_str = f"{a['x_pos']}{a['y_pos']}{a['z_start']}{a['rot']}" if not tile_str in tile_list: tile_list.append(tile_str) return tile_list.index(acq_str)
[docs] def get_unique_attr_list(self, key: str = 'laser') -> list: """Return ordered list of acquisition attributes. Parameters: ----------- key: str One of ('laser', 'shutterconfig', 'rot') Returns: -------- List of strings, e.g. ('488', '561') for key='laser', in the order of acquisition. """ attributes = ('laser', 'shutterconfig', 'rot') assert key in attributes, f'Key {key} must be one of {attributes}.' unique_list = [] for acq in self: if acq[key] not in unique_list: unique_list.append(acq[key]) return unique_list
[docs] def find_value_index(self, value: str = '488 nm', key: str = 'laser'): """Find the attribute index in the acquisition list. Example: al = AcquisitionList([Acquisition(), Acquisition(), Acquisition(), Acquisition()]) al[0]['laser'] = '488 nm' # al[1]['laser'] = '488 nm' # gets removed because non-unique al[2]['laser'] = '561 nm' # al[3]['laser'] = '637 nm' # Output: al.find_value_index('488 nm', 'laser') # -> 0 al.find_value_index('561 nm', 'laser') # -> 1 al.find_value_index('637 nm', 'laser') # -> 2 """ unique_list = self.get_unique_attr_list(key) assert value in unique_list, f"Value({value}) not found in list {unique_list}" return unique_list.index(value)