Skip to content
Snippets Groups Projects
Commit 8db231d7 authored by Valerio Mariani's avatar Valerio Mariani
Browse files

Fixed a few syntax problems and did some code cleanup

parent 0961cb0a
No related branches found
No related tags found
No related merge requests found
This diff is collapsed.
...@@ -15,8 +15,8 @@ ...@@ -15,8 +15,8 @@
""" """
Geometry utilities. Geometry utilities.
Functions that load, manipulate and apply geometry information to detector Functions that load, manipulate and apply geometry information to
pixel data. detector pixel data.
""" """
from __future__ import (absolute_import, division, print_function, from __future__ import (absolute_import, division, print_function,
...@@ -27,63 +27,58 @@ import collections ...@@ -27,63 +27,58 @@ import collections
import numpy 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): 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 Take as input a CrystFEL-style geometry object (A dictionary
returned by the function load_crystfel_geometry function in the returned by the function load_crystfel_geometry function in the
crystfel_utils module) and return a PixelMap tuple . The origin crystfel_utils module) and return a PixelMap tuple . The origin the
the reference system used by the pixel maps is set at the beam interaction reference system used by the pixel maps is set at the beam
point. interaction point.
Args: Args:
geometry (dict): A CrystFEL geometry object (A dictionary returned by geometry (dict): A CrystFEL geometry object (A dictionary
the :obj:`cfelpyutils.crystfel_utils.load_crystfel_geometry` returned by the
:obj:`cfelpyutils.crystfel_utils.load_crystfel_geometry`
function). function).
Returns: 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. # Determine the max fs and ss in the geometry object.
max_slab_fs = numpy.array( max_slab_fs = numpy.array([
[geometry['panels'][k]['max_fs'] for k in geometry['panels']] geometry['panels'][k]['max_fs']
).max() for k in geometry['panels']
]).max()
max_slab_ss = numpy.array(
[geometry['panels'][k]['max_ss'] for k in geometry['panels']] max_slab_ss = numpy.array([
).max() geometry['panels'][k]['max_ss']
for k in geometry['panels']
# Create the empty arrays that will store the pixel maps. ]).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( x_map = numpy.zeros(
shape=(max_slab_ss + 1, max_slab_fs + 1), shape=(max_slab_ss + 1, max_slab_fs + 1),
dtype=numpy.float32 dtype=numpy.float32
) )
y_map = numpy.zeros( y_map = numpy.zeros(
shape=(max_slab_ss + 1, max_slab_fs + 1), shape=(max_slab_ss + 1, max_slab_fs + 1),
dtype=numpy.float32 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']: for pan in geometry['panels']:
# Determine the pixel indexes for the current panel.
i, j = numpy.meshgrid( i, j = numpy.meshgrid(
numpy.arange( numpy.arange(
geometry['panels'][pan]['max_ss'] - geometry['panels'][pan]['max_ss'] -
...@@ -98,46 +93,43 @@ def compute_pixel_maps(geometry): ...@@ -98,46 +93,43 @@ def compute_pixel_maps(geometry):
indexing='ij' indexing='ij'
) )
# Compute the x,y vectors, using the complex notation. d_x = (geometry['panels'][pan]['fsy'] +
d_x = ( 1J * geometry['panels'][pan]['fsx'])
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_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 cmplx = i * d_y + j * d_x + r_0
# Compute values for the x and y maps. x_map[
y_map[
geometry['panels'][pan]['min_ss']: geometry['panels'][pan]['min_ss']:
geometry['panels'][pan]['max_ss'] + 1, geometry['panels'][pan]['max_ss'] + 1,
geometry['panels'][pan]['min_fs']: geometry['panels'][pan]['min_fs']:
geometry['panels'][pan]['max_fs'] + 1 geometry['panels'][pan]['max_fs'] + 1
] = cmplx.real ] = cmplx.imag
x_map[ y_map[
geometry['panels'][pan]['min_ss']: geometry['panels'][pan]['min_ss']:
geometry['panels'][pan]['max_ss'] + 1, geometry['panels'][pan]['max_ss'] + 1,
geometry['panels'][pan]['min_fs']: geometry['panels'][pan]['min_fs']:
geometry['panels'][pan]['max_fs'] + 1 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)) 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) return PixelMaps(x_map, y_map, r_map)
def apply_pixel_maps(data, pixel_maps, output_array=None): 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 Turn an array of detector pixel values into an array
containing a representation of the physical layout of the detector. containing a representation of the physical layout of the detector.
...@@ -150,20 +142,19 @@ def apply_pixel_maps(data, pixel_maps, output_array=None): ...@@ -150,20 +142,19 @@ def apply_pixel_maps(data, pixel_maps, output_array=None):
pixel_maps (PixelMaps): a pixelmap tuple, as returned by the pixel_maps (PixelMaps): a pixelmap tuple, as returned by the
:obj:`compute_pixel_maps` function in this module. :obj:`compute_pixel_maps` function in this module.
output_array (Optional[ndarray]): a preallocated array (of dtype output_array (Optional[ndarray]): a preallocated array (of
numpy.float32) to store the function output. If provided, this dtype numpy.float32) to store the function output. If
array will be filled by the function and returned to the user. provided, this array will be filled by the function and
If not provided, the function will create a new array and returned to the user. If not provided, the function
automatically and return it to the user. Defaults to None will create a new array automatically and return it to the
(No array provided). user. Defaults to None (No array provided).
Returns: Returns:
ndarray: a numpy.float32 array containing the geometry information ndarray: a numpy.float32 array containing the geometry
applied to the input data (i.e.: a physical representation of the information applied to the input data (i.e.: a representation
layout of the detector). of the physical layout of the detector).
""" """
# If no output array was provided, create one. # If no output array was provided, create one.
if output_array is None: if output_array is None:
output_array = numpy.zeros( output_array = numpy.zeros(
...@@ -171,70 +162,83 @@ def apply_pixel_maps(data, pixel_maps, output_array=None): ...@@ -171,70 +162,83 @@ def apply_pixel_maps(data, pixel_maps, output_array=None):
dtype=numpy.float32 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() output_array[pixel_maps.y, pixel_maps.x] = data.ravel()
# Return the output array.
return output_array return output_array
def compute_minimum_array_size(pixel_maps): 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 Return the minimum size of an array that can store data on which
geometry information described by the pixel maps has been applied. the geometry information described by the pixel maps has been
applied.
The returned array shape is big enough to display all the input pixel The returned array shape is big enough to display all the input
values in the reference system of the physical detector. The array is pixel values in the reference system of the physical detector. The
supposed to be centered at the center of the reference system of the array is supposed to be centered at the center of the reference
detector (i.e: the beam interaction point). system of the detector (i.e: the beam interaction point).
Args: Args:
pixel_maps (PixelMaps): a PixelMaps tuple, as returned by the Tuple[ndarray, ndarray, ndarray]: a named tuple containing the
:obj:`compute_pixel_maps` function in this module. 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: Returns:
tuple: numpy shape-like tuple storing the minimum array size. Tuple[int, int]: a numpy-style shape tuple storing the minimum
array size.
""" """
# Find the largest absolute values of x and y in the maps. Since
# Recover the x and y pixel maps. # 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 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. # Return a numpy-style tuple with the computed shape.
y_largest = 2 * int(max(abs(y_map.max()), abs(y_map.min()))) + 2 return (y_minimum, x_minimum)
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)
def adjust_pixel_maps_for_pyqtgraph(pixel_maps): 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. 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: Args:
pixel_maps (PixelMaps): pixel maps, as returned by the Tuple[ndarray, ndarray, ndarray]: a named tuple containing the
:obj:`compute_pixel_maps` function in this module. 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: Returns:
PixelMaps: a PixelMaps tuple containing the ajusted pixel maps for Tuple[ndarray, ndarray] A tuple containing the pixel
the x and y coordinates in the first two fields, and the maps. The first two fields, named "x" and "y" respectively,
value None in the third. 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.
""" """
# Essentially, the origin of the reference system needs to be
# Compute the minimum image shape needed to represent the coordinates. # 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) min_shape = compute_minimum_array_size(pixel_maps)
# Convert the old pixemap values to the new pixelmap values.
new_x_map = numpy.array( new_x_map = numpy.array(
object=pixel_maps.x, object=pixel_maps.x,
dtype=numpy.int dtype=numpy.int
...@@ -245,4 +249,8 @@ def adjust_pixel_maps_for_pyqtgraph(pixel_maps): ...@@ -245,4 +249,8 @@ def adjust_pixel_maps_for_pyqtgraph(pixel_maps):
dtype=numpy.int dtype=numpy.int
) + min_shape[0] // 2 - 1 ) + 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)
...@@ -24,36 +24,35 @@ import ast ...@@ -24,36 +24,35 @@ import ast
def _parsing_error(section, option): def _parsing_error(section, option):
# Raise an exception after a parsing error. # Raise an exception after a parsing error.
raise RuntimeError( raise RuntimeError(
'Error parsing parameter {0} in section [{1}]. Make sure that the ' "Error parsing parameter {0} in section [{1}]. Make sure that the "
'syntax is correct: list elements must be separated by commas and ' "syntax is correct: list elements must be separated by commas and "
'dict entries must contain the colon symbol. Strings must be quoted, ' "dict entries must contain the colon symbol. Strings must be quoted, "
'even in lists and dicts.'.format( "even in lists and dicts.".format(option, section)
option,
section
)
) )
def convert_parameters(config_dict): def convert_parameters(config_dict):
"""Convert strings in parameter dictionaries to the corrent data type. """Convert strings in parameter dictionaries to the corrent data type.
Read a parameter dictionary returned by the ConfigParser python module, Read a parameter dictionary returned by the ConfigParser python
and convert each entry in an object of the corresponding type, module, and convert each entry in an object of the corresponding
without changing the structure of the dictionary. type, without changing the structure of the dictionary.
Try to convert each entry in the dictionary according to the following Try to convert each entry in the dictionary according to the
rules. The first rule that applies to the entry determines the type. 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, - If the entry starts and ends with a single quote or double quote,
leave it as a string. 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 - If the entry starts and ends with a curly braces, convert it to a
dictionary or a set. dictionary or a set.
- If the entry is the word None, without quotes, convert it to NoneType. - If the entry is the word None, without quotes, convert it to
- If the entry is the word False, without quotes, convert it to a boolean NoneType.
False. - 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 - If the entry is the word True, without quotes, convert it to a
boolean True. boolean True.
- If none of the previous options match the content of the entry, - If none of the previous options match the content of the entry,
...@@ -77,26 +76,24 @@ def convert_parameters(config_dict): ...@@ -77,26 +76,24 @@ def convert_parameters(config_dict):
Raises: 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 = {} 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(): for section in config_dict.keys():
# Add the section to the dictionary that will be returned.
monitor_params[section] = {} monitor_params[section] = {}
# Iterate over the content of the section (second level in the # Iterate then over the content of the section (second level in
# configuratio). # 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(): for option in config_dict['section'].keys():
# Get the option from the dictionary.
recovered_option = config_dict['section'] recovered_option = config_dict['section']
# Check if the option is a string delimited by single quotes.
if ( if (
recovered_option.startswith("'") and recovered_option.startswith("'") and
recovered_option.endswith("'") recovered_option.endswith("'")
...@@ -104,7 +101,6 @@ def convert_parameters(config_dict): ...@@ -104,7 +101,6 @@ def convert_parameters(config_dict):
monitor_params[section][option] = recovered_option[1:-1] monitor_params[section][option] = recovered_option[1:-1]
continue continue
# Check if the option is a string delimited by double quotes.
if ( if (
recovered_option.startswith('"') and recovered_option.startswith('"') and
recovered_option.endswith('"') recovered_option.endswith('"')
...@@ -112,8 +108,6 @@ def convert_parameters(config_dict): ...@@ -112,8 +108,6 @@ def convert_parameters(config_dict):
monitor_params[section][option] = recovered_option[1:-1] monitor_params[section][option] = recovered_option[1:-1]
continue continue
# Check if the option is a list. If it is, interpret it using the
# literal_eval function.
if ( if (
recovered_option.startswith("[") and recovered_option.startswith("[") and
recovered_option.endswith("]") recovered_option.endswith("]")
...@@ -126,8 +120,6 @@ def convert_parameters(config_dict): ...@@ -126,8 +120,6 @@ def convert_parameters(config_dict):
except (SyntaxError, ValueError): except (SyntaxError, ValueError):
_parsing_error(section, option) _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 ( if (
recovered_option.startswith("{") and recovered_option.startswith("{") and
recovered_option.endswith("}") recovered_option.endswith("}")
...@@ -140,40 +132,28 @@ def convert_parameters(config_dict): ...@@ -140,40 +132,28 @@ def convert_parameters(config_dict):
except (SyntaxError, ValueError): except (SyntaxError, ValueError):
_parsing_error(section, option) _parsing_error(section, option)
# Check if the option is the special string 'None' (without
# quotes).
if recovered_option == 'None': if recovered_option == 'None':
monitor_params[section][option] = None monitor_params[section][option] = None
continue continue
# Check if the option is the special string 'False' (without
# quotes).
if recovered_option == 'False': if recovered_option == 'False':
monitor_params[section][option] = False monitor_params[section][option] = False
continue continue
# Check if the option is the special string 'True' (without
# quotes).
if recovered_option == 'True': if recovered_option == 'True':
monitor_params[section][option] = True monitor_params[section][option] = True
continue continue
# Check if the option is an int by trying to convert it to an int.
try: try:
monitor_params[section][option] = int(recovered_option) monitor_params[section][option] = int(recovered_option)
continue continue
except ValueError: except ValueError:
# If the conversion to int failed, try to convert it to a
# float.
try: try:
monitor_params[section][option] = float( monitor_params[section][option] = float(
recovered_option recovered_option
) )
continue continue
except ValueError: except ValueError:
# If the conversion to float also failed, return a parsing
# error.
_parsing_error(section, option) _parsing_error(section, option)
# Returned the converted dictionary.
return monitor_params return monitor_params
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment