diff --git a/crystfel_utils.py b/crystfel_utils.py index 18de74221109bad11a896fb34fd966d190a742af..5825fc176d987fbc9efcb1b7944aaed90bf23266 100644 --- a/crystfel_utils.py +++ b/crystfel_utils.py @@ -32,42 +32,39 @@ import re def _assplode_algebraic(value): # Reimplementation of assplode_algegraic from # libcrystfel/src/detector.c. - items = [ - item for item in re.split( - pattern='([+-])', string=value.strip() - ) if item != '' + item + for item in re.split(pattern='([+-])', string=value.strip()) + if item != '' ] if items and items[0] not in ('+', '-'): items.insert(index=0, object='+') return [ - ''.join( - iterable=(items[x], items[x + 1]) - ) for x in range(start=0, stop=len(items), step=2) + ''.join((items[x], items[x + 1])) + for x in range(0, len(items), 2) ] def _dir_conv(direction_x, direction_y, direction_z, value): # Reimplementation of dir_conv from # libcrystfel/src/detector.c. - direction = [ - direction_x, direction_y, direction_z + direction_x, + direction_y, + direction_z ] items = _assplode_algebraic(value) - - if items: - raise RuntimeError('Invalid direction: {}.'.format(value)) + if not items: + raise RuntimeError("Invalid direction: {}.".format(value)) for item in items: axis = item[-1] - if axis != 'x' and axis != 'y' and axis != 'z': raise RuntimeError( - 'Invalid Symbol: {} (must be x, y or z).'.format(axis) + "Invalid Symbol: {} (must be x, y or z).".format(axis) ) if item[:-1] == '+': @@ -90,16 +87,14 @@ def _dir_conv(direction_x, direction_y, direction_z, value): def _set_dim_structure_entry(key, value, panel): # Reimplementation of set_dim_structure_entry from # libcrystfel/src/events.c. - if panel['dim_structure'] is not None: dim = panel['dim_structure'] else: dim = [] dim_index = int(key[3]) - if dim_index > len(dim) - 1: - for _ in range(start=len(dim), stop=dim_index + 1): + for _ in range(len(dim), dim_index + 1): dim.append(None) if value == 'ss' or value == 'fs' or value == '%': @@ -107,35 +102,28 @@ def _set_dim_structure_entry(key, value, panel): elif value.isdigit(): dim[dim_index] = int(value) else: - raise RuntimeError('Invalid dim entry: {}.'.format(value)) + raise RuntimeError("Invalid dim entry: {}.".format(value)) def _parse_field_for_panel(key, value, panel): # Reimplementation of parse_field_for_panel from # libcrystfel/src/detector.c. - if key == 'min_fs': panel['origin_min_fs'] = int(value) panel['min_fs'] = int(value) - elif key == 'max_fs': panel['origin_max_fs'] = int(value) panel['max_fs'] = int(value) - elif key == 'min_ss': panel['origin_min_ss'] = int(value) panel['min_ss'] = int(value) - elif key == 'max_ss': panel['origin_max_ss'] = int(value) panel['max_ss'] = int(value) - elif key == 'corner_x': panel['cnx'] = float(value) - elif key == 'corner_y': panel['cny'] = float(value) - elif key == 'rail_direction': try: panel['rail_x'], panel['rail_y'], panel['rail_z'] = _dir_conv( @@ -145,20 +133,16 @@ def _parse_field_for_panel(key, value, panel): value=value ) except RuntimeError as exc: - raise RuntimeError('Invalid rail direction. ', exc) + raise RuntimeError("Invalid rail direction. ", exc) elif key == 'clen_for_centering': panel['clen_for_centering'] = float(value) - elif key == 'adu_per_eV': panel['adu_per_eV'] = float(value) - elif key == 'adu_per_photon': panel['adu_per_photon'] = float(value) - elif key == 'rigid_group': panel['rigid_group'] = value - elif key == 'clen': try: panel['clen'] = float(value) @@ -168,33 +152,27 @@ def _parse_field_for_panel(key, value, panel): panel['clen_from'] = value elif key == 'data': - if not value.startswith('/'): - raise RuntimeError('Invalid data location: {}'.format(value)) + if not value.startswith("/"): + raise RuntimeError("Invalid data location: {}".format(value)) panel['data'] = value elif key == 'mask': - if not value.startswith('/'): - raise RuntimeError('Invalid data location: {}'.format(value)) + if not value.startswith("/"): + raise RuntimeError("Invalid data location: {}".format(value)) panel['mask'] = value elif key == 'mask_file': panel['mask_file'] = value - elif key == 'saturation_map': panel['saturation_map'] = value - elif key == 'saturation_map_file': panel['saturation_map_file'] = value - elif key == 'coffset': panel['coffset'] = float(value) - elif key == 'res': panel['res'] = float(value) - elif key == 'max_adu': panel['max_adu'] = value - elif key == 'badrow_direction': if value == 'x': panel['badrow'] = 'f' @@ -207,13 +185,12 @@ def _parse_field_for_panel(key, value, panel): elif value == '-': panel['badrow'] = '-' else: - print('badrow_direction must be x, t, f, s, or \'-\'') - print('Assuming \'-\'.') + print("badrow_direction must be x, t, f, s, or \'-\'") + print("Assuming \'-\'.") panel['badrow'] = '-' elif key == 'no_index': panel['no_index'] = bool(value) - elif key == 'fs': try: panel['fsx'], panel['fsy'], panel['fsz'] = _dir_conv( @@ -222,9 +199,8 @@ def _parse_field_for_panel(key, value, panel): direction_z=panel['fsz'], value=value ) - except RuntimeError as exc: - raise RuntimeError('Invalid fast scan direction. ', exc) + raise RuntimeError("Invalid fast scan direction.", exc) elif key == 'ss': try: @@ -234,25 +210,22 @@ def _parse_field_for_panel(key, value, panel): direction_z=panel['ssz'], value=value ) - except RuntimeError as exc: - raise RuntimeError('Invalid slow scan direction. ', exc) + raise RuntimeError("Invalid slow scan direction.", exc) - elif key.startswith('dim'): + elif key.startswith("dim"): _set_dim_structure_entry( key=key, value=value, panel=panel ) - else: - raise RuntimeError('Unrecognised field: {}'.format(key)) + raise RuntimeError("Unrecognised field: {}".format(key)) def _parse_toplevel(key, value, detector, beam, panel): # Reimplementation of parse_toplevel from # libcrystfel/src/detector.c. - if key == 'mask_bad': try: detector['mask_bad'] = int(value) @@ -267,7 +240,6 @@ def _parse_toplevel(key, value, detector, beam, panel): elif key == 'coffset': panel['coffset'] = float(value) - elif key == 'photon_energy': if value.startswith('/'): beam['photon_energy'] = 0.0 @@ -278,19 +250,15 @@ def _parse_toplevel(key, value, detector, beam, panel): elif key == 'photon_energy_scale': beam['photon_energy_scale'] = float(value) - elif key == 'peak_info_location': detector['peak_info_location'] = value - elif ( - key.startswith('rigid_group') and not - key.startswith('rigid_group_collection') + key.startswith("rigid_group") and not + key.startswith("rigid_group_collection") ): detector['rigid_groups'][key[12:]] = value.split(',') - - elif key.startswith('rigid_group_collection'): + elif key.startswith("rigid_group_collection"): detector['rigid_group_collections'][key[23:]] = value.split(',') - else: _parse_field_for_panel( key=key, @@ -316,58 +284,54 @@ def _parse_field_bad(key, value, bad): # Reimplementation of parse_field_bad from # libcrystfel/src/detector.c. if key == 'min_x': - bad['min_x'] = float(x=value) + bad['min_x'] = float(value) _check_bad_fsss(bad_region=bad, is_fsss=False) elif key == 'max_x': - bad['max_x'] = float(x=value) + bad['max_x'] = float(value) _check_bad_fsss(bad_region=bad, is_fsss=False) elif key == 'min_y': - bad['min_y'] = float(x=value) + bad['min_y'] = float(value) _check_bad_fsss(bad_region=bad, is_fsss=False) elif key == 'max_y': - bad['max_y'] = float(x=value) + bad['max_y'] = float(value) _check_bad_fsss(bad_region=bad, is_fsss=False) elif key == 'min_fs': - bad['min_fs'] = int(x=value) + bad['min_fs'] = int(value) _check_bad_fsss(bad_region=bad, is_fsss=True) elif key == 'max_fs': - bad['max_fs'] = int(x=value) + bad['max_fs'] = int(value) _check_bad_fsss(bad_region=bad, is_fsss=True) elif key == 'min_ss': - bad['min_ss'] = int(x=value) + bad['min_ss'] = int(value) _check_bad_fsss(bad_region=bad, is_fsss=True) elif key == 'max_ss': - bad['max_ss'] = int(x=value) + bad['max_ss'] = int(value) _check_bad_fsss(bad_region=bad, is_fsss=True) elif key == 'panel': bad['panel'] = value else: - raise RuntimeError('Unrecognised field: {}'.format(key)) + raise RuntimeError("Unrecognised field: {}".format(key)) return -def _check_point(name, panel, fs_, ss_, min_d, max_d, detector): +def _check_point(name, panel, fs, ss, min_d, max_d, detector): # Reimplementation of check_point from # libcrystfel/src/detector.c. - - xs_ = fs_ * panel['fsx'] + ss_ * panel['ssx'] - ys_ = fs_ * panel['fsy'] + ss_ * panel['ssy'] - - rx_ = (xs_ + panel['cnx']) / panel['res'] - ry_ = (ys_ + panel['cny']) / panel['res'] - - dist = math.sqrt(x=rx_ * rx_ + ry_ * ry_) - + xs = fs * panel['fsx'] + ss * panel['ssx'] + ys = fs * panel['fsy'] + ss * panel['ssy'] + rx = (xs + panel['cnx']) / panel['res'] + ry = (ys + panel['cny']) / panel['res'] + dist = math.sqrt(rx * rx + ry * ry) if dist > max_d: detector['furthest_out_panel'] = name - detector['furthest_out_fs'] = fs_ - detector['furthest_out_ss'] = ss_ + detector['furthest_out_fs'] = fs + detector['furthest_out_ss'] = ss max_d = dist elif dist < min_d: detector['furthest_in_panel'] = name - detector['furthest_in_fs'] = fs_ - detector['furthest_in_ss'] = ss_ + detector['furthest_in_fs'] = fs + detector['furthest_in_ss'] = ss min_d = dist return min_d, max_d @@ -376,17 +340,14 @@ def _check_point(name, panel, fs_, ss_, min_d, max_d, detector): def _find_min_max_d(detector): # Reimplementation of find_min_max_d from # libcrystfel/src/detector.c. - min_d = float('inf') max_d = 0.0 - for name, panel in detector['panels'].items(): - min_d, max_d = _check_point( name=name, panel=panel, - fs_=0, - ss_=0, + fs=0, + ss=0, min_d=min_d, max_d=max_d, detector=detector @@ -395,18 +356,18 @@ def _find_min_max_d(detector): min_d, max_d = _check_point( name=name, panel=panel, - fs_=panel['w'], - ss_=0, + fs=panel['w'], + ss=0, min_d=min_d, max_d=max_d, detector=detector - ) + min_d, max_d = _check_point( name=name, panel=panel, - fs_=0, - ss_=panel['h'], + fs=0, + ss=panel['h'], min_d=min_d, max_d=max_d, detector=detector @@ -415,8 +376,8 @@ def _find_min_max_d(detector): min_d, max_d = _check_point( name=name, panel=panel, - fs_=panel['w'], - ss_=panel['h'], + fs=panel['w'], + ss=panel['h'], min_d=min_d, max_d=max_d, detector=detector @@ -424,13 +385,14 @@ def _find_min_max_d(detector): def load_crystfel_geometry(filename): - """Load a CrystFEL geometry file into a dictionary. + """ + Load a CrystFEL geometry file into a dictionary. - Reimplementation of the get_detector_geometry_2 function from CrystFEL - in python. Return a dictionary with the geometry information read from - the file. Convert entries in the geometry file to keys in the - returned dictionary. For a full documentation on the CrystFEL geometry - format, see: + Reimplementation of the get_detector_geometry_2 function from + CrystFEL in python. Return a dictionary with the geometry + information read from the file. Convert entries in the geometry + file to keys in the returned dictionary. For a full documentation + on the CrystFEL geometry format, see: http://www.desy.de/~twhite/crystfel/manual-crystfel_geometry.html @@ -444,11 +406,9 @@ def load_crystfel_geometry(filename): Returns: - dict: dictionary with the geometry information loaded from the file. + dict: dictionary with the geometry information loaded from the + file. """ - - fhandle = open(file=filename, mode='r') - beam = { 'photon_energy': 0.0, 'photon_energy_from': None, @@ -507,33 +467,34 @@ def load_crystfel_geometry(filename): 'name': '' } - default_dim = ['ss', 'fs'] + default_dim = [ + 'ss', 'fs' + ] + fhandle = open(file=filename, mode='r') fhlines = fhandle.readlines() - for line in fhlines: - - if line.startswith(';'): + if line.startswith(";"): continue line_without_comments = line.strip().split(sep=';')[0] line_items = re.split(pattern='([ \t])', string=line_without_comments) line_items = [ - item for item in line_items if item not in ('', ' ', '\t') + item + for item in line_items + if item not in ('', ' ', '\t') ] - if len(s=line_items) < 3: + if len(line_items) < 3: continue value = ''.join(line_items[2:]) - if line_items[1] != '=': continue path = re.split('(/)', line_items[0]) path = [item for item in path if item not in '/'] - - if len(s=path) < 2: + if len(path) < 2: _parse_toplevel( key=line_items[0], value=value, @@ -545,9 +506,7 @@ def load_crystfel_geometry(filename): curr_bad = None curr_panel = None - - if path[0].startswith('bad'): - + if path[0].startswith("bad"): if path[0] in detector['bad']: curr_bad = detector['bad'][path[0]] else: @@ -555,7 +514,6 @@ def load_crystfel_geometry(filename): detector['bad'][path[0]] = curr_bad else: - if path[0] in detector['panels']: curr_panel = detector['panels'][path[0]] else: @@ -579,9 +537,7 @@ def load_crystfel_geometry(filename): raise RuntimeError("No panel descriptions in geometry file.") num_placeholders_in_panels = None - for panel in detector['panels'].values(): - if panel['dim_structure'] is not None: curr_num_placeholders = panel['dim_structure'].values().count('%') else: @@ -592,14 +548,12 @@ def load_crystfel_geometry(filename): else: if curr_num_placeholders != num_placeholders_in_panels: raise RuntimeError( - 'All panels\' data and mask entries must have the same ' - 'number of placeholders.' + "All panels\' data and mask entries must have the same " + "number of placeholders." ) num_placeholders_in_masks = None - for panel in detector['panels'].values(): - if panel['mask'] is not None: curr_num_placeholders = panel['mask'].count('%') else: @@ -610,50 +564,50 @@ def load_crystfel_geometry(filename): else: if curr_num_placeholders != num_placeholders_in_masks: raise RuntimeError( - 'All panels\' data and mask entries must have the same ' - 'number of placeholders.' + "All panels\' data and mask entries must have the same " + "number of placeholders." ) if num_placeholders_in_masks > num_placeholders_in_panels: raise RuntimeError( - 'Number of placeholders in mask cannot be larger the number ' - 'than for data.' + "Number of placeholders in mask cannot be larger the number " + "than for data." ) dim_length = None - for panel in detector['panels'].values(): - if panel['dim_structure'] is None: panel['dim_structure'] = copy.deepcopy(default_dim) found_ss = False found_fs = False found_placeholder = False - for entry in panel['dim_structure']: if entry is None: raise RuntimeError( - 'Not all dim entries are defined for all panels.' + "Not all dim entries are defined for all panels." ) + elif entry == 'ss': if found_ss is True: raise RuntimeError( - 'Only one slow scan dim coordinate is allowed.' + "Only one slow scan dim coordinate is allowed." ) else: found_ss = True + elif entry == 'fs': if found_fs is True: raise RuntimeError( - 'Only one fast scan dim coordinate is allowed.' + "Only one fast scan dim coordinate is allowed." ) else: found_fs = True + elif entry == '%': if found_placeholder is True: raise RuntimeError( - 'Only one placeholder dim coordinate is allowed.' + "Only one placeholder dim coordinate is allowed." ) else: found_placeholder = True @@ -662,68 +616,67 @@ def load_crystfel_geometry(filename): dim_length = len(panel['dim_structure']) elif dim_length != len(panel['dim_structure']): raise RuntimeError( - 'Number of dim coordinates must be the same for all panels.' + "Number of dim coordinates must be the same for all panels." ) if dim_length == 1: raise RuntimeError( - 'Number of dim coordinates must be at least two.' + "Number of dim coordinates must be at least two." ) for panel in detector['panels'].values(): - if panel['origin_min_fs'] < 0: raise RuntimeError( - 'Please specify the minimum fs coordinate for ' - 'panel {}.'.format(panel['name']) + "Please specify the minimum fs coordinate for " + "panel {}.".format(panel['name']) ) if panel['origin_max_fs'] < 0: raise RuntimeError( - 'Please specify the maximum fs coordinate for ' - 'panel {}.'.format(panel['name']) + "Please specify the maximum fs coordinate for " + "panel {}.".format(panel['name']) ) if panel['origin_min_ss'] < 0: raise RuntimeError( - 'Please specify the minimum ss coordinate for ' - 'panel {}.'.format(panel['name']) + "Please specify the minimum ss coordinate for " + "panel {}.".format(panel['name']) ) if panel['origin_max_ss'] < 0: raise RuntimeError( - 'Please specify the maximum ss coordinate for ' - 'panel {}.'.format(panel['name']) + "Please specify the maximum ss coordinate for " + "panel {}.".format(panel['name']) ) if panel['cnx'] is None: raise RuntimeError( - 'Please specify the corner X coordinate for ' - 'panel {}.'.format(panel['name']) + "Please specify the corner X coordinate for " + "panel {}.".format(panel['name']) ) if panel['clen'] is None and panel['clen_from'] is None: raise RuntimeError( - 'Please specify the camera length for ' - 'panel {}.'.format(panel['name']) + "Please specify the camera length for " + "panel {}.".format(panel['name']) ) if panel['res'] < 0: raise RuntimeError( - 'Please specify the resolution or ' - 'panel {}.'.format(panel['name']) + "Please specify the resolution or " + "panel {}.".format(panel['name']) ) if panel['adu_per_eV'] is None and panel['adu_per_photon'] is None: raise RuntimeError( - 'Please specify either adu_per_eV or adu_per_photon ' - 'for panel {}.'.format(panel['name']) + "Please specify either adu_per_eV or adu_per_photon " + "for panel {}.".format(panel['name']) ) if panel['clen_for_centering'] is None and panel['rail_x'] is not None: raise RuntimeError( - 'You must specify clen_for_centering if you specify the' - 'rail direction (panel {})'.format(panel['name']) + "You must specify clen_for_centering if you specify the " + "rail direction (panel {})".format(panel['name']) ) if panel['rail_x'] is None: @@ -740,37 +693,34 @@ def load_crystfel_geometry(filename): for bad_region in detector['bad'].values(): if bad_region['is_fsss'] == 99: raise RuntimeError( - 'Please specify the coordinate ranges for bad ' - 'region {}.'.format(bad_region['name']) + "Please specify the coordinate ranges for bad " + "region {}.".format(bad_region['name']) ) for group in detector['rigid_groups']: for name in detector['rigid_groups'][group]: if name not in detector['panels']: raise RuntimeError( - 'Cannot add panel to rigid_group. Panel not' - 'found: {}'.format(name) + "Cannot add panel to rigid_group. Panel not " + "found: {}".format(name) ) for group_collection in detector['rigid_group_collections']: for name in detector['rigid_group_collections'][group_collection]: if name not in detector['rigid_groups']: raise RuntimeError( - 'Cannot add rigid_group to collection. Rigid group ' - 'not found: {}'.format(name) + "Cannot add rigid_group to collection. Rigid group " + "not found: {}".format(name) ) for panel in detector['panels'].values(): - - d__ = panel['fsx'] * panel['ssy'] - panel['ssx'] * panel['fsy'] - - if d__ == 0.0: - raise RuntimeError('Panel {} transformation is singluar.') - - panel['xfs'] = panel['ssy'] / d__ - panel['yfs'] = panel['ssx'] / d__ - panel['xss'] = panel['fsy'] / d__ - panel['yss'] = panel['fsx'] / d__ + d = panel['fsx'] * panel['ssy'] - panel['ssx'] * panel['fsy'] + if d == 0.0: + raise RuntimeError("Panel {} transformation is singluar.") + panel['xfs'] = panel['ssy'] / d + panel['yfs'] = panel['ssx'] / d + panel['xss'] = panel['fsy'] / d + panel['yss'] = panel['fsx'] / d _find_min_max_d(detector) fhandle.close() diff --git a/geometry_utils.py b/geometry_utils.py index 58533e45b5538cb77c4e5a1cb8a31bff199e0570..699d8223cd535cea34d711f17ad5bb2b200c7038 100644 --- a/geometry_utils.py +++ b/geometry_utils.py @@ -15,8 +15,8 @@ """ Geometry utilities. -Functions that load, manipulate and apply geometry information to detector -pixel data. +Functions that load, manipulate and apply geometry information to +detector pixel data. """ from __future__ import (absolute_import, division, print_function, @@ -27,63 +27,58 @@ import collections import numpy -PixelMaps = collections.namedtuple('PixelMaps', ['x', 'y', 'r']) -'''A namedtuple used for pixel maps objects. - -Pixel maps are arrays of the same shape of the data whose geometry they -describe. Each cell in the array holds the coordinate, in the reference system -of the physical detector, of the corresponding pixel in the data array. - -The first two fields store the pixel maps for the x coordinate -and the y coordinate respectively. The third field is instead a pixel map -storing the distance of each pixel in the data array from the center of the -reference system. -''' - - def compute_pixel_maps(geometry): - """Compute pixel maps from a CrystFEL geometry object. + """ + Compute pixel maps from a CrystFEL geometry object. Take as input a CrystFEL-style geometry object (A dictionary returned by the function load_crystfel_geometry function in the - crystfel_utils module) and return a PixelMap tuple . The origin - the reference system used by the pixel maps is set at the beam interaction - point. + crystfel_utils module) and return a PixelMap tuple . The origin the + reference system used by the pixel maps is set at the beam + interaction point. Args: - geometry (dict): A CrystFEL geometry object (A dictionary returned by - the :obj:`cfelpyutils.crystfel_utils.load_crystfel_geometry` + geometry (dict): A CrystFEL geometry object (A dictionary + returned by the + :obj:`cfelpyutils.crystfel_utils.load_crystfel_geometry` function). Returns: - PixelMaps: a PixelMaps tuple. + Tuple[ndarray, ndarray, ndarray] A tuple containing the pixel + maps. The first two fields, named "x" and "y" respectively, + store the pixel maps for the x coordinate and the y coordinate. + The third field, named "r", is instead a pixel map storing the + distance of each pixel in the data array from the center of the + reference system. """ - # Determine the max fs and ss in the geometry object. - max_slab_fs = numpy.array( - [geometry['panels'][k]['max_fs'] for k in geometry['panels']] - ).max() - - max_slab_ss = numpy.array( - [geometry['panels'][k]['max_ss'] for k in geometry['panels']] - ).max() - - # Create the empty arrays that will store the pixel maps. + max_slab_fs = numpy.array([ + geometry['panels'][k]['max_fs'] + for k in geometry['panels'] + ]).max() + + max_slab_ss = numpy.array([ + geometry['panels'][k]['max_ss'] + for k in geometry['panels'] + ]).max() + + # Create empty arrays, of the same size of the input data, that + # will store the x and y pixel maps. x_map = numpy.zeros( shape=(max_slab_ss + 1, max_slab_fs + 1), dtype=numpy.float32 ) + y_map = numpy.zeros( shape=(max_slab_ss + 1, max_slab_fs + 1), dtype=numpy.float32 ) - # Iterate over the panels. + # Iterate over the panels. For each panel, determine the pixel + # indeces, then compute the x,y vectors using a comples notation. for pan in geometry['panels']: - - # Determine the pixel indexes for the current panel. i, j = numpy.meshgrid( numpy.arange( geometry['panels'][pan]['max_ss'] - @@ -98,46 +93,43 @@ def compute_pixel_maps(geometry): indexing='ij' ) - # Compute the x,y vectors, using the complex notation. - d_x = ( - geometry['panels'][pan]['fsy'] + - 1J * geometry['panels'][pan]['fsx'] - ) - d_y = ( - geometry['panels'][pan]['ssy'] + - 1J * geometry['panels'][pan]['ssx'] - ) - r_0 = ( - geometry['panels'][pan]['cny'] + - 1J * geometry['panels'][pan]['cnx'] - ) + d_x = (geometry['panels'][pan]['fsy'] + + 1J * geometry['panels'][pan]['fsx']) + d_y = (geometry['panels'][pan]['ssy'] + + 1J * geometry['panels'][pan]['ssx']) + + r_0 = (geometry['panels'][pan]['cny'] + + 1J * geometry['panels'][pan]['cnx']) cmplx = i * d_y + j * d_x + r_0 - # Compute values for the x and y maps. - y_map[ + x_map[ geometry['panels'][pan]['min_ss']: geometry['panels'][pan]['max_ss'] + 1, geometry['panels'][pan]['min_fs']: geometry['panels'][pan]['max_fs'] + 1 - ] = cmplx.real + ] = cmplx.imag - x_map[ + y_map[ geometry['panels'][pan]['min_ss']: geometry['panels'][pan]['max_ss'] + 1, geometry['panels'][pan]['min_fs']: geometry['panels'][pan]['max_fs'] + 1 - ] = cmplx.imag + ] = cmplx.real - # Compute the values for the radius pixel map. + # Finally, compute the values for the radius pixel map. r_map = numpy.sqrt(numpy.square(x_map) + numpy.square(y_map)) - # Return the pixel maps as a tuple. + PixelMaps = collections.namedtuple( + typename='PixelMaps', + field_names=['x', 'y', 'r'] + ) return PixelMaps(x_map, y_map, r_map) def apply_pixel_maps(data, pixel_maps, output_array=None): - """Apply geometry in pixel map format to the input data. + """ + Apply geometry in pixel map format to the input data. Turn an array of detector pixel values into an array containing a representation of the physical layout of the detector. @@ -150,20 +142,19 @@ def apply_pixel_maps(data, pixel_maps, output_array=None): pixel_maps (PixelMaps): a pixelmap tuple, as returned by the :obj:`compute_pixel_maps` function in this module. - output_array (Optional[ndarray]): a preallocated array (of dtype - numpy.float32) to store the function output. If provided, this - array will be filled by the function and returned to the user. - If not provided, the function will create a new array - automatically and return it to the user. Defaults to None - (No array provided). + output_array (Optional[ndarray]): a preallocated array (of + dtype numpy.float32) to store the function output. If + provided, this array will be filled by the function and + and returned to the user. If not provided, the function + will create a new array automatically and return it to the + user. Defaults to None (No array provided). Returns: - ndarray: a numpy.float32 array containing the geometry information - applied to the input data (i.e.: a physical representation of the - layout of the detector). + ndarray: a numpy.float32 array containing the geometry + information applied to the input data (i.e.: a representation + of the physical layout of the detector). """ - # If no output array was provided, create one. if output_array is None: output_array = numpy.zeros( @@ -171,70 +162,83 @@ def apply_pixel_maps(data, pixel_maps, output_array=None): dtype=numpy.float32 ) - # Apply the pixel map geometry information the data. + # Apply the pixel map geometry information the data, then return + # the resulting array. output_array[pixel_maps.y, pixel_maps.x] = data.ravel() - - # Return the output array. return output_array def compute_minimum_array_size(pixel_maps): """ - Compute the minimum size of an array that can store the applied geometry. + Compute the minimum size of an array that can store the applied + geometry. - Return the minimum size of an array that can store data on which the - geometry information described by the pixel maps has been applied. + Return the minimum size of an array that can store data on which + the geometry information described by the pixel maps has been + applied. - The returned array shape is big enough to display all the input pixel - values in the reference system of the physical detector. The array is - supposed to be centered at the center of the reference system of the - detector (i.e: the beam interaction point). + The returned array shape is big enough to display all the input + pixel values in the reference system of the physical detector. The + array is supposed to be centered at the center of the reference + system of the detector (i.e: the beam interaction point). Args: - pixel_maps (PixelMaps): a PixelMaps tuple, as returned by the - :obj:`compute_pixel_maps` function in this module. + Tuple[ndarray, ndarray, ndarray]: a named tuple containing the + pixel maps. The first two fields, "x" and "y", should store + the pixel maps for the x coordinateand the y coordinate. + The third, "r", should instead store the distance of each + pixel in the data array from the center of the reference + system. Returns: - tuple: numpy shape-like tuple storing the minimum array size. + Tuple[int, int]: a numpy-style shape tuple storing the minimum + array size. """ - - # Recover the x and y pixel maps. + # Find the largest absolute values of x and y in the maps. Since + # the returned array is centered on the origin, the minimum array + # size along a certain axis must be at least twice the maximum + # value for that axis. 2 pixels are added for good measure. x_map, y_map = pixel_maps.x, pixel_maps.x.y + y_minimum = 2 * int(max(abs(y_map.max()), abs(y_map.min()))) + 2 + x_minimum = 2 * int(max(abs(x_map.max()), abs(x_map.min()))) + 2 - # Find the largest absolute values of x and y in the maps. - y_largest = 2 * int(max(abs(y_map.max()), abs(y_map.min()))) + 2 - x_largest = 2 * int(max(abs(x_map.max()), abs(x_map.min()))) + 2 - - # Return a tuple with the computed shape. - return (y_largest, x_largest) + # Return a numpy-style tuple with the computed shape. + return (y_minimum, x_minimum) def adjust_pixel_maps_for_pyqtgraph(pixel_maps): """ - Adjust pixel maps for visualization of the data in a pyqtgraph widget. + Adjust pixel maps for visualization of the data in a pyqtgraph + widget. The adjusted maps can be used for a Pyqtgraph ImageView widget. - Essentially, the origin of the reference system is moved to the - top-left of the image. Args: - pixel_maps (PixelMaps): pixel maps, as returned by the - :obj:`compute_pixel_maps` function in this module. + Tuple[ndarray, ndarray, ndarray]: a named tuple containing the + pixel maps. The first two fields, "x" and "y", should store the + pixel maps for the x coordinateand the y coordinate. The third, + "r", should instead store the distance of each pixel in the + data array from the center of the reference system. Returns: - PixelMaps: a PixelMaps tuple containing the ajusted pixel maps for - the x and y coordinates in the first two fields, and the - value None in the third. + Tuple[ndarray, ndarray] A tuple containing the pixel + maps. The first two fields, named "x" and "y" respectively, + store the pixel maps for the x coordinate and the y + coordinate. The third field, named "r", is instead a pixel + map storing the distance of each pixel in the data array + from the center of the reference system. """ - - # Compute the minimum image shape needed to represent the coordinates. + # Essentially, the origin of the reference system needs to be + # moved from the beam position to the top-left of the image that + # will be displayed. First, compute the size of the array used to + # display the data, then use this information to estimate the + # magnitude of the shift that needs to be applied to the origin of + # the system. min_shape = compute_minimum_array_size(pixel_maps) - - # Convert the old pixemap values to the new pixelmap values. new_x_map = numpy.array( object=pixel_maps.x, dtype=numpy.int @@ -245,4 +249,8 @@ def adjust_pixel_maps_for_pyqtgraph(pixel_maps): dtype=numpy.int ) + min_shape[0] // 2 - 1 - return PixelMaps(new_x_map, new_y_map, None) + PixelMapsForIV = collections.namedtuple( + typename='PixelMapsForIV', + field_names=['x', 'y'] + ) + return PixelMapsForIV(new_x_map, new_y_map) diff --git a/parameter_utils.py b/parameter_utils.py index 6e3f977d61c6d9ba94d9edc87a226cc1b0f15f4d..4a3fde67e974e14c1bdeeb734331021a91b40722 100644 --- a/parameter_utils.py +++ b/parameter_utils.py @@ -24,36 +24,35 @@ import ast def _parsing_error(section, option): # Raise an exception after a parsing error. - raise RuntimeError( - 'Error parsing parameter {0} in section [{1}]. Make sure that the ' - 'syntax is correct: list elements must be separated by commas and ' - 'dict entries must contain the colon symbol. Strings must be quoted, ' - 'even in lists and dicts.'.format( - option, - section - ) + "Error parsing parameter {0} in section [{1}]. Make sure that the " + "syntax is correct: list elements must be separated by commas and " + "dict entries must contain the colon symbol. Strings must be quoted, " + "even in lists and dicts.".format(option, section) ) def convert_parameters(config_dict): """Convert strings in parameter dictionaries to the corrent data type. - Read a parameter dictionary returned by the ConfigParser python module, - and convert each entry in an object of the corresponding type, - without changing the structure of the dictionary. + Read a parameter dictionary returned by the ConfigParser python + module, and convert each entry in an object of the corresponding + type, without changing the structure of the dictionary. - Try to convert each entry in the dictionary according to the following - rules. The first rule that applies to the entry determines the type. + Try to convert each entry in the dictionary according to the + following rules. The first rule that applies to the entry + determines the type. - If the entry starts and ends with a single quote or double quote, leave it as a string. - - If the entry starts and ends with a square bracket, convert it to a list. + - If the entry starts and ends with a square bracket, convert it to + a list. - If the entry starts and ends with a curly braces, convert it to a dictionary or a set. - - If the entry is the word None, without quotes, convert it to NoneType. - - If the entry is the word False, without quotes, convert it to a boolean - False. + - If the entry is the word None, without quotes, convert it to + NoneType. + - If the entry is the word False, without quotes, convert it to a + boolean False. - If the entry is the word True, without quotes, convert it to a boolean True. - If none of the previous options match the content of the entry, @@ -77,26 +76,24 @@ def convert_parameters(config_dict): Raises: - RuntimeError: if an entry cannot be converted to any supported type. + RuntimeError: if an entry cannot be converted to any supported + type. """ - # Create the dictionary that will be returned. monitor_params = {} - # Iterate over the sections in the dictionary (first level). + # Iterate over the sections in the dictionary (first level in the + # configuration file). Add the section to the dictionary that will + # be returned. for section in config_dict.keys(): - - # Add the section to the dictionary that will be returned. monitor_params[section] = {} - # Iterate over the content of the section (second level in the - # configuratio). + # Iterate then over the content of the section (second level in + # the configuration file). Get each option in turn and perform + # all the checks. If all checks fail, call the parsing_error + # function. for option in config_dict['section'].keys(): - - # Get the option from the dictionary. recovered_option = config_dict['section'] - - # Check if the option is a string delimited by single quotes. if ( recovered_option.startswith("'") and recovered_option.endswith("'") @@ -104,7 +101,6 @@ def convert_parameters(config_dict): monitor_params[section][option] = recovered_option[1:-1] continue - # Check if the option is a string delimited by double quotes. if ( recovered_option.startswith('"') and recovered_option.endswith('"') @@ -112,8 +108,6 @@ def convert_parameters(config_dict): monitor_params[section][option] = recovered_option[1:-1] continue - # Check if the option is a list. If it is, interpret it using the - # literal_eval function. if ( recovered_option.startswith("[") and recovered_option.endswith("]") @@ -126,8 +120,6 @@ def convert_parameters(config_dict): except (SyntaxError, ValueError): _parsing_error(section, option) - # Check if the option is a dictionary or a set. If it is, - # interpret it using the literal_eval function. if ( recovered_option.startswith("{") and recovered_option.endswith("}") @@ -140,40 +132,28 @@ def convert_parameters(config_dict): except (SyntaxError, ValueError): _parsing_error(section, option) - # Check if the option is the special string 'None' (without - # quotes). if recovered_option == 'None': monitor_params[section][option] = None continue - # Check if the option is the special string 'False' (without - # quotes). if recovered_option == 'False': monitor_params[section][option] = False continue - # Check if the option is the special string 'True' (without - # quotes). if recovered_option == 'True': monitor_params[section][option] = True continue - # Check if the option is an int by trying to convert it to an int. try: monitor_params[section][option] = int(recovered_option) continue except ValueError: - # If the conversion to int failed, try to convert it to a - # float. try: monitor_params[section][option] = float( recovered_option ) continue except ValueError: - # If the conversion to float also failed, return a parsing - # error. _parsing_error(section, option) - # Returned the converted dictionary. return monitor_params